• 重构—改善既有代码的设计9—简化条件表达式


    条件逻辑可能十分复杂

    decompose conditional:将一个复杂的条件逻辑分成若干个小块。使得“分支逻辑”、“操作细节”分离

    consolidate conditional expression:代码中多处测试有相同结果

    consolidate duplicate conditional fragment:去掉条件代码中的重复成分

    为了让条件表达式也遵循“单一出口原则”,往往向其中加入控制标记。

      不特别在意“一个函数一个出口的教条,使用replace nested conditional with cuard clauses标示出那些特殊情况,使用remove control flag去除那些讨厌的限制

    面向对象程序的条件表达式通常比较少(较之过程化程序而言):很多条件行为都被多态机制处理掉了。

      多态的好处:调用者无需了解条件行为的细节=》条件的扩展更容易

    replace conditional with polymorphism:将switch语句替换为多态

    introduce null object:去除对于null值的校验。多态的一种十分有用且鲜为人知的用途

    1.decompose conditional:分解条件表达式

    问题:有一个复杂的条件语句(if-then-else)

    解决:从if-then-else三个段落中分别提炼出独立的函数

    动机:

      复杂的条件逻辑:最常导致复杂度上升的地点之一

        必须编写代码来检查不同的条件分支,根据不同的分支做不同的事情=》导致相当长的函数。

        大型函数自身会使代码的可读性下降+条件逻辑会使代码更难阅读

      在带有复杂条件逻辑的函数中,代码(包括检查条件分支的代码、真正实现功能的代码)会告诉你法僧的事情。但是,常常让人弄不清楚为什么会发生这样的事=》代码的可读性的确大大降低了

      任何大块头代码,都可以分解为多个独立函数。根据每一小块代码的用途,为分解而得的新函数命名,更清楚地表达自己的意图

      对于条件逻辑,每个分支条件分解成新函数:突出条件逻辑,更清楚地表明每个分支的作用,突出每个分支的原因

    做法:

      将if-then-else中的每一个段落都提炼出来,构成一个独立函数

        如果发现嵌套的条件逻辑,会先观察是否可以使用replace nested conditional with guard clauses。如果不行,才开始分解其中的每一个条件

    注意:

      有的分支条件往往非常短,看上去似乎没有提炼分支的必要。

        尽管这些条件往往很短,在代码意图、代码自身之间往往存在不小的差距。提炼分支能够更好地表达自己的用途,提炼出来的函数可读性更高(就像一段注释那样清楚而明白)

    2.consolidate conditional expression:合并条件表达式

    问题:有一系列条件测试,都得到相同结果

    解决:将这些测试合并为一个条件表达式,并将这个条件表达式提炼成一个独立的函数

    动机:

      一串条件检查,检查条件各不相同,最终行为却一致

        立即用“逻辑或”、“逻辑与”将它们合并为一个条件表达式

      合并条件表达式的原因:

        1.合并后的条件代码:“实际上只有一次条件检查,只不过有多个并列条件需要检查而已”=》使这一次检查的用意更清晰

          合并前、合并后的代码有着相同的效果,但原先代码传达出的信息:“这里有一些各自独立的条件测试,只是恰好同时发生”

        2.为使用extract method做好准备。将检查条件提炼成一个独立函数,对于理清代码十分有用:把描述“做什么”的语句换成了“为什么这样做”

      不要合并的理由:

        1.如果检查彼此独立,不应该视为同一次检查,就不要使用本重构项

          代码已经清楚表达出自己的意义

    做法:

      确定这些条件语句没有副作用:有副作用不能使用本项重构

      使用适当的逻辑操作符,将一系列相关条件表达式合并为一个

      对合并后的条件表达式实施“extract method”,提炼成一个独立函数,并以函数名称表达该语句所检查的条件

        某些情况下,需要同时使用逻辑与、逻辑或、逻辑非,最终得到的条件表达式可能很复杂:先使用extract method将表达式的一部分提炼出来,从而使整个表达式变得简单一些

        如果所观察的部分只是对条件进行检查并返回一个值,就可以使用三元操作符,将这一部分变成一条return语句。

    3.consolidate dupulicate conditional fragment:合并重复的条件片段

    问题:在条件表达式的每个分支上有着相同的一段代码

    解决:将这段重复代码搬移到条件表达式之外。

    动机:

      一组体哦阿健表达式的所有分支都执行了相同的某段代码:将这段代码搬移到表达式的外面=》更清楚地表明:那些东西随条件的变化而变化、那些东西保持不变

    做法:

      鉴别出“执行方式不随条件变化而变化”的代码

      将共同代码移到条件表达式之外

        如果这些共同代码位于条件表达式的中断=》观察共同代码之前、之后改变了什么。如果的确有所改变,应该首相将共同代码向前、向后移动,移至条件表达式的起始处、尾端。

        共同代码不止一条,首先使用extract method将共同代码提炼到一个独立函数中

    注意:

      可以使用同样的手法来对待异常。如果在try-catch都执行了同一段代码,则将这段重复代码移到final区段

    4.remove control flag:移除控制标记

    问题:在一系列布尔表达式中,某个变量带有“控制标记control flag”的作用

    解决:用break 、return语句取代控制标记

    动机:

      条件表达式中,常常看到:判断何时停止条件检查的控制标记

        

        带来的麻烦>带来的遍历。

      使用control flag的原因:结构话编程原则,每个子程序只能有一个入口、一个出口

        赞同“单一入口”原则。

        但,“单一出口”原则会在代码中加入讨厌的控制标记,大大降低条件表达式的可读性=》break、continue。用它们跳出复杂的条件语句

      去掉control flag:条件语句的真正用途会清晰很多

    做法:

      找出跳出这段逻辑的控制标记值

      找出对标记变量赋值的语句,代以恰当的break、congtinue(对control flag最显而易见的方法)

    注意:

      未能提供break、continue语句的编程语言中

        extract method,整段逻辑提炼到一个独立函数中

        找出跳出这段逻辑的控制标记值

        找出对标记变量赋值的语句,代以恰当的return语句

      即使在支持break、congtinue语句的编程语言中,通常也优先考虑上述第二个方案。

        return语句可以非常清楚地表示:不再执行该函数中的其他任何代码

      注意标记变量,是否会影响这段逻辑的最后结果。

        如果有,使用break语句之后还得保留控制标记值。如果已经将这段逻辑提炼成一个独立函数,也可以将控制标记值放在return中返回

      既是控制标记,也是运算结果。将与次变量有关的代码,提炼到一个独立函数中,用return取代控制标记变量

      如果以此办法处理带有,有副作用的函数,会出现问题。先以separate query from modifier将函数副作用分离出去

    5.replace nested conditional with guard clauses:以卫语句取代嵌套条件表达式

    问题:函数中的条件逻辑使人难以看清正常的执行路径

    解决:用卫语句表现所有特殊情况

    动机:

      条件表达式,通常有两种情况:

        1.所有分支属于正常行为:应该用if-else-的条件表达式

        2.只有一种是正常行为,其他都是不常见的情况。不常见的条件单独检查;为真时,立刻从函数中返回。

          单独检查,常被称为“卫语句guard clauses”

      精髓:给某一条分支以特别的重视。

        if-then-else结构,对if、else的重视时同等的。

        guard clauses:这种情况很罕见,如果真的发生了,请做一些整理工作,然后退出

      “每个函数只能有一个入口、一个出口”

        编程语言会强制,保证每个函数只有一个入口。

        “单一出口”这个规则,没那么有用。保持代码清晰才是关键。如果单一出口能使函数更清楚易读,就使用单一出口;否则,不必这么做

          嵌套条件代码,往往由那些深心“每个函数只能由一个出口”的程序员写出。此条规则太简单粗暴了。如果对函数剩余部分不再由兴趣,应该立即退出。引导阅读者去看一个没有用的else区段,只会妨碍他们的理解

    做法:

      对于每个检查,放进一个guard clauses。卫语句要不从函数中返回,要不就抛出一个异常

        如果所有卫语句都导致相同的结果,请使用 consolidate conditional expressions

    注意:

      常常可以将条件表达式反转,从而实现replace nested conditional with guard clauses。

      推荐在guard clauses内返回一个明确的值,可以一目了然地看到guard clauses返回的失败结果。这是也会考虑使用replace magic number with symbolic constant

    6.replace conditional with polymorphism:以多态取代条件表达式

    问题:条件表达式,是根据对象类型的不同,而选择不同的行为

    解决:将这个条件表达式的每个分支放进一个子类内的覆写函数中,然后将原始函数声明为抽象函数

    动机:

      多态的好处:根据需要对象的不同类型,而采取不同的行为。多态,使你不必编写明显的条件表达式

        类型码的switch语句、基于类型名称的if-then-else语句,在面向对象程序中很少出现

      同一组条件条件表达式在程序许多地点出现,那使用多态的收益是最大的

        使用条件表达式时,如果项添加一种新类型,就必须查找并更新所有条件表达式。

        如果改用多态,只需建立一个新的子类,并在其中提供适当的函数就行了。

        类的用户不需要了解这个子类,就大大降低了系统各部分之间的依赖,使系统升级更容易

    做法:

      使用replace conditional with polymorphism之前,首先必须由一个继承结构。

      建立继承结构由两种选择:

        1.replace type code with subclasses:简单,应尽可能使用这一种

        2.replace type code with state/strategy:对象创建之后修改类型码;或者,要重构的类已经有了子类

        如果若干switch语句针对的是同一个类型码,只需针对这个类型码建立一个继承结构就行了

      1.如果要处理的条件表达式是一个更大函数中的一部分。首先,对条件表达式进行分析,然后使用extract method将其提炼到一个独立函数去

      2.如果由必要,使用move method、将条件表达式放置到继承结构的顶端

      3.任选一个子类,在其中建立一个函数,使之覆写超类中容纳条件表达式的那个函数。将与该子类相关的条件表达式分支复制到新建函数中,并对其进行适当调整

        可能需要将超类中某些private字段声明为protected

      4.超类中删掉条件表达式内被复制了的分支

      5.将超类中容纳条件表达式的函数声明为抽象函数

    7.intruduce null object:引入null对象

    问题:需要再三检查某对象是否为null 

    解决:将null值替换为null对象

    动机:

      多态的最根本好处:不必再向对象询问“你是什么类型”。而后根据得到的答案调用对象的某个行为。只管调用该行为就是了,其他的一切,多态机制会为你安排妥当

      当某个字段内容为null时,多态可扮演另一个较不直观,亦较不为人所知的用途

        解决:每次向一个对象发送一个消息之前,总是要检查对象是否存在。此类检查出现很多次,造成大量的重复代码

        null object==miss object。不让实例变量被设为null,而是插入各式各样的空对象,都知道如何正确的显示自己=》拜托大量过程化的代码

          mock test object:使用此原理。便于模块化开发、测试。

          missing bin:虚构的箱仓。自己不带任何数据,总值为0.

            箱仓:指集合,用来保存某些资薪值,并常常需要对各个资薪值进行加和、遍历

          系统几乎从来不会因为空对象而被破坏。null object对所有外界请求的相应和真实对象一样=》系统行为总是正常的

          并非总是好事儿。有时会造成问题侦测、查找上的困难。因为从来没有任何东西被破坏。

            只要认真检查,就会发现空对象有时出现在不该出现的地方

          空对象一定是常量,它们的任何成分都不会发生变化。可以使用singleton模式来实现=》任何时候,只要请求一个miss**对象,得到的一定是miss**的唯一实例

    做法:

      为源类建立一个子类,使其行为就像是源类的null版本。在源类、null子类中都加上isnull(),前者返回false,后者返回true。

        有帮助的做法:建立一个nullable接口,将isnull()放入其中,让源类实现这个接口:昭告大家,这里使用了空对象

        也可以创建一个测试接口,专门用来检查对象是否为null:无法修改null对象的

        工厂函数:专门用来创建null**对象=》用户不必知道空对象的存在

      找出所有“索求对象却获得一个null”的地方。修改这些地方,使其获得一个null对象

      找出所有“将源对象与null作比较”的地方。修改这些地方,使其调用isnull()

        每次只处理一个源对象、其客户程序,编译、测试后,再处理另一个源对象

        可以在“不该出现null”的地方放上一些断言,确保null不再出现

        大多数情况,需要做大量的替换工作(视null对象的使用频率来决定),很凌乱、恼人

        实现nullable接口的对象:(aCustomer is null)来判断

      找出这样的程序点:如果对象不是null,做A动作,否则做B动作

        在null类中覆写A动作,使其行为和B动作相同

        使用被覆写的动作,删除“对象是否等于null”的条件测试

    注意:

      只有当大多数(不是所有)客户代码,都要求空对象做出相同响应时,这样的行为搬移才有意义。

        任何用户如果需要空对象做出不同相应,仍然可以使用isnull()函数来测试,只要大多数客户端都要求空对象做出相同响应,就可以调用默认的null行为,自己也就受益匪浅了

      使用本项重构时,可以有几种不同的空对象(例如:没有顾客、不知顾客名的顾客),针对不同情况建立不同的空对象类。

        有时候空对象也可以携带数据:不知名顾客的使用记录……,查出顾客姓名后,将账单寄给他

        本质上是特例模式special case:比null object模式更大的模式。

          某个类的特殊情况,有着特殊行为

          例如:浮点数:有“正无穷大”、“负无穷大”、“NaN”……

          价值:可以降低“错误处理”开销。NaN做浮点运算,结果是个NaN。与“空对象的访问函数通常返回另一个空对象”同理

    8.intruduce assertion:引入断言

    问题:某一段代码需要对程序状态做出某种假设

    解决:以断言明确某种假设

    动机:

      只有当某个条件为真时,该段代码才能正常运行。例如:平方根计算只对正值才能进行……

      这样的假设通常并没有在代码中明确表现出来,必须阅读整个算法才能看出。有时会以注释写出这样的假设=》更好的技术:使用断言,明确表明这些假设

      断言是一个条件表达式,应该总为真。如果失败,表示程序员犯了错误

        断言的失败应该导致一个非受控异常unchecked exception。

        断言绝对不能被系统的其他部分使用。

        实际上,程序最后的成品,往往将断言统统删除=》标记“某些东西是断言”,很重要

      断言,可以作为交流、调试的辅助。

        交流角度上,断言可以帮助程序阅读者,理解代码所做的假设(代码正确运行的必要条件)

        调试角度上,断言可以在距离bug最近的地方抓住它们

        =》编写自我测试代码时,断言在调试方面的帮助变得不那么重要了。但,仍要非常看重断言在交流方面的价值

    做法:

      如果程序不犯错,断言就应该不会对系统运行造成任何影响=》加入断言,不会影响程序的行为

        如果反写代码假设某个条件始终为真。就加入一个断言,明确说明这种情况

          可以新建一个Assert类,用于处理各种情况下的断言

      不要滥用断言。不要使用它来检查“你认为应该为真”的条件,请只用它来检查“一定必须为真”的条件

        滥用断言,可能会造成难以维护的重复逻辑。

        在一段逻辑中加入断言是有好处的:迫使你重新考虑这段代码的约束条件

        如果不满足这些约束条件,程序也可以正常运行=》断言不会带来任何帮助,只会把代码变得混乱,可能妨碍以后的修改

      应该常常问自己:如果断言所只是的约束条件不能满足,代码是否仍能正常运行?如果可以,将断言删掉

      断言中的重复代码。和其他任何的地方的重复diamagnetic一样不好闻,大胆使用extract method(去重、更清楚说明函数用途)去掉那些重复代码。

    注意:

      断言可以被轻松拿掉=》不可能影响最终成品的性能。

      编写一个辅助类(例如:Assert类)是有帮助的。

        缺点:断言参数中的任何表达式不论什么情况,都一定会被执行一遍。

        阻止它的唯一办法:如果Assert.ON是个常量,编译器就会对其进行检查。如果==false,就不会再执行表达式后半段代码

          加此语句有些丑陋,嗯对程序源宁可仅仅使用Assert.isTrue()函数。在项目结束前,过滤掉使用断言的每一行diamagnetic

        

      Assert类应该有很对个函数,函数名称应该帮助程序员理解其功用。

        isTrue()、equals()、shouldNeverReachHere()……

      

      

      

      

  • 相关阅读:
    6.数字三角形
    5.分组背包问题
    1.商品模块表结构分析
    AUTH_USER_MODEL refers to model 'user.User' that has not been installed
    发布品论接口
    查询指定课程评论接口
    1.评论模块表结构
    上传视频课程到七牛云后存储到django后端接口
    5.上传视频到七牛云django端实现
    4.七牛云上传前台页面
  • 原文地址:https://www.cnblogs.com/panpanwelcome/p/7806416.html
Copyright © 2020-2023  润新知