飞奔的小小番茄 申_宇
'͜'哈哈哈
关注数: 70 粉丝数: 36 发帖数: 816 关注贴吧数: 104
《重构》第十二章:读书笔记 继承是开发过程中使用最为熟悉的特性之一,但是团队开发的过程中如同书中描述的一样,会出现由于大家的认知不一致导致的一些痛点,而且这个痛点往往在用了一段时间后才会发现,这样其实也难以纠正。书中本章的介绍其实除了手段的介绍还有一些认知的统一。 1.重复代码 提炼超类:如果多个类在做相似的事,可以利用基本的继承机制把他们的相似之处提炼到超类。 但是一段重复代码到底是放在子类中还是父类中?首先得判断这段代码的特性属于子类还是父类,其次观察是否存在重复的函数,重复的函数之间是否有差异?如果有差异的话思考使用字段上移还是整段函数上移。 当然如果重复代码只是存在少数的几个子类中,那么最好将其从父类中挪走,放在真正需要的子类中,当然对于重复的代码可以放在独立的一个helper类中,由子类各自去调用。 2.继承体系 随着继承体系的演化,会发现一个类与其超类已经没有多大差别,不值得再作为独立的类存在。这时候可以将超类和子类合并。但是当选择想移除的类:是超类还是子类?书中推荐根据哪个类的名字放在未来更有意义,如果两个名字都不够好可以先随便挑一个。 当然在使用继承的时候就要想明白,继续是有短板的。导致行为不同的原因可能有多种,但继承只能用于处理一个方向上的变化。比如说,我可能希望”人”的行为根据”年龄段”不同,并且根据”收入水平”不同,使用继承时子类可以是”年轻人”和”老人”,也可以是”富人”和”穷人”,但不能同时采用两种继承方式。更大的问题是,继承给类之间引入了非常紧密的关系,在超类上做任何修改,都很可能破坏子类,所以需要格外的小心。如果两个类的逻辑分处于不同的模块、由不同的团队负责,问题就会更麻烦。
《重构》第十章:读书笔记 条件逻辑增加了程序的完整性,但同样也增加了程序的复杂度。本章会通过分解条件表达式、合并条件表达式以及用卫语句取代嵌套条件表达式等方法来简化复杂的表达式,以表达更清晰的用意。 1.分解条件表达式(Decompose Conditional) 代码中,条件逻辑是最常导致复杂度上升的方式之一。编写代码来检查不同的条件分支,根据不同的条件做不同的需求,这样久而久之,很快会获得一个相当长的函数。大型函数本身就会让代码的可读性下降,而条件逻辑则会让代码更难理解。 和任何大型函数一样,将各个条件中的行为分解成多个独立的函数,从而更清楚的表达不同条件需要的行为需求。 2.合并条件表达式(Consolidate Conditional Expression) 有时在代码中会发现一串条件检查逻辑:检查条件各不相同,但最终的行为却一致。如果发现这种情况,就应该使用"逻辑与"和"逻辑或"将它们合并为一个条件表达式。 因为这样不仅让检查的用意更清晰,合并后的条件代码会表达出"实际只有一次条件检查,只不过有多个并列条件需检查";还对之后提炼函数做好了准备。 当然如果这些检查确实彼此独立,那么不应该被视为同一次检查,不要使用本项重构手段。 3.以卫语句取代嵌套条件表达式(Replace Nested Conditional with Guard Clauses) 条件表达式通常有两种风格: ① 两个条件分支都属于正常开发行为② 只有一个条件分支是正常开发行为,另一个分支则是异常情况。 如果两条分支都是正常行为,就应该使用形如 if... else... 或 switch... case...(多条件)的条件表达式;但是当其中一个条件分支是处理异常情况时,就应该单独检查该条件,并在该条件为真时立刻从函数返回。这样单独检查常常被称为"卫语句"(Guard clauses)。 理解"卫语句"所表达的含义: "这种情况不是本函数的核心逻辑所关心的,如果它真发生了,请做一些必要的整理工作,然后退出。"
《重构》第九章:读书笔记 数据结构在程序中扮演着重要的角色,所以在第九章中作者介绍了一组重构手法专门用于数据结构的组织。 1.拆分变量 变量有各种不同的用途,其中某些用途会很自然地导致临时变量被多次赋值:循环变量idx: for (NSInteger idx = 0; idx < self.count; idx++) ; 结果收集变量 负责将"通过整个函数的运算"而构成的某个值收集起来,如: 不规则的多个图形拼接,计算总面积 area。 除了以上两种情况,还有很多变量用于保存一段冗长代码的运算结果,这种变量应该只被赋值一次。如果被赋值超过了一次,就意味着在函数中它们承担了一个以上的责任,会令代码阅读者困扰。 2.字段改名 命名很重要,好的命名会帮助阅读者对代码的理解。需要注意一点: 命名并不是越长越好,需要适时地使用缩写!当然上下文的变化、业务的迭代会不断改变开发者对于好名字的理解,要做到随时的优化。 3.将引用对象改为值对象 在把一个对象(或数据结构)嵌入另一个对象时,位于内部的这个对象可以被视为引用对象,也可以视为值对象。两者最明显的差异在于如何更新内部对象的属性,如果将内部对象视为引用对象,在更新其属性时要保留原对象不动;如果将其视为值对象,需要替换整个内部对象,新换上的对象会有想要的属性。 如果把一个对象视为值对象,这个对象是不可变的,可以放心将不可变的数据值传给程序的其他部分,而不必担心对象中包装的数据被偷偷修改。 4.将值对象改成引用对象 当需要在多个数据结构中共享某个对象时,该对象应使用引用对象类型。如 多个订单属于同一个顾客,顾客的数据视为引用对象类型在订单数据中共享;如果视为值对象,那么每份订单数据中都会复制顾客的数据。 当变量视为值对象还是引用对象,从开发的角度来说都可以,但是对于数据是否需要定义成可变的引用对象类型(其他位置改变时,所有调用位置都会接收到),这才是我们需要深思的问题!
《重构》第八章:读书笔记 到目前为止,介绍的重构手法都是关于如何新建、移除或重命名程序的元素。此外还有另一种类型的重构也很重要,那就是在不同的上下文之间搬移元素。 1.搬移函数 任何函数都需要具备上下文环境才能存活,这个上下文可以是全局的,但更多时候是由某种形式的模块所提供的。而搬移函数最直接的一个动因是:它频繁引用其他上下文中的元素,而对自身上下文中的元素却关心甚少。是否需要搬移函数常常不易决择,所以需要仔细检查函数当前上下文与目标上下文之间的区别,需要查看函数的调用者都有谁?自身又调用了什么函数?被调用函数需要什么参数?等。 2.搬移语句到函数 当发现调用某个函数时,总有一些相同的代码也需要每次执行,那么就应该考虑将此段代码合并到函数里面。这样日后对这段代码的修改只需改动一处地方,就能对所有调用者生效。如果将来代码对不同的调用者需要不同的行为,那时再通过搬移语句到调用者将它(或其中一部分)搬移出来也十分简单。 3.搬移语句到调用者 函数边界发生偏移时的一个征兆是,以往在多个地方共用的行为,如今需要在某些调用位置表现出不同的行为。于是我们得把表现不同的行为从函数里挪出,并搬移到其调用处。 4.移动语句 让存在关联的语句一起出现,可以让代码更容易理解。如果几行代码取用了同一个数据结构,那么最好是让它们在一起出现,而不是夹杂在取用其他数据结构的代码中间。最简单的情况下,开发者只需要移动语句就可以让它们聚集起来。推荐: 不是在函数顶部一口气声明函数用到的所有变量,而是在使用到变量的地方再声明它
《重构》第七章:读书笔记 面向对象的三大特点是什么? 大多数人第一反应都能说出:封装、继承、多态。而本章就是关于 封装 相关的重构手段介绍。截取几个重要的方式介绍: 1.封装记录 当面对一组数据时,开发者常面对两种类型结构的选择: 1.声明类结构,明确定义属性名、类型等,可以很好的隐藏结构的细节,调用方无需追究计算等细节。但是这种方式也有缺陷,在不同的类结构间传递一些数据时,需要不断的处理转换。 2.使用字典、数组等系统类结构,很多编程语言都提供了方便的语法来创建这类结构记录数据,使得在面对不同场景时都能快速开发。但是这种方式也有缺陷,让开发者无法直观快速的知道持有了哪些字段,随着使用范围变广,造成的困扰也会不断的增加。 2.封装集合 使用面向对象技术的开发者对封装尤为重视,但封装集合时开发者常常会犯一个错误:只对集合变量的访问进行了封装,但依然让取值函数返回集合本身。这使得集合的成员变量可直接被修改,而封装它的类则全然不知,无法介入。所以最常见的做法是为集合提供一个取值函数,令其返回一个集合的副本。然后提供一些可供修改集合的方法 --- 通常是"添加"和"移除"方法。这样对集合的修改必须经过类,当程序演化变大时,依然可轻易找出修改点。 3.以对象取代基本类型 开发初期,往往会使用简单的数据项表示,比如使用数字或字符串等。但随着开发的进行,会发现简单数据项不再那么简单了。比如一开始可能会使用一个字符串来表示"电话号码"的概念,但是随后又需要支持"格式化输出"、"提取区号"之类的特殊行为。这类业务逻辑的迭代会逐渐的制造出许多重复代码,增加使用时的成本。 所以当意识到某个数据的操作不仅仅局限于直接输出时,就可以创建一个新类,一开始可能只是简单的包装,但是只要有声明的类,日后添加的业务逻辑就有通用化存放的位置了。
《重构》读书笔记:第六章 作者详细的从动机、做法、示例方面介绍了常使用的重构方法,其中需要深入的思考的除了具体的某个方法使用场景外,还有对使用该方法该掌握的度,以 提炼函数 和 内联函数 为例: 1.提炼函数 动机: 当声明与实现不同时,这时候导致再次开发时需要通过理解逻辑才能明白用途时。如: 函数名highlight(高亮),但是实现逻辑是调用了reverse方法,这样其实声明和实现之间有相当大的距离。 有些人会担心短函数会造成大量函数调用,栈内层级的增加会影响运行的性能,但是现有硬件的支持这种影响其实已经可以忽略;而且短函数常常能让编译器的优化功能运转更良好,因为短函数可以更容易的被缓存,所以不用过早的担心性能问题 做法 ① 命名: 在提取逻辑上写上一行注释,说明这段代码要做什么,然后直接翻译注释,但是命名不是一蹴而成,随着理解的不断加深和上下文的变化,是需要不断的更新的。 ② 参数: 提取部分中的参数,如果只有该部分使用直接提取成一个 参数查询函数;如果提出部分的上下文均使用,那么可以作为参数传递进来 ③ 多位置替换,并测试: 调用位置替换 2.内联函数 动机 当提取出来的函数 声明和实现一致的情况(开发可以很明确的通过实现理解用途),这时候为了代码结构更加清晰易读,应该去掉这个函数直接使用其中的代码;毕竟非必要的间接性使用总是让人不舒服的(开发时点击跳来跳去的!) 示例 注意: ① 当多个位置调用时,由于函数的复用性也应该保留函数 ② 当函数内部定义无法直接的表达出 函数名的声明意思(较复杂的情况),这时候也应该保留函数 初看在于某些方面两者存在矛盾的定义,看后还会存在疑惑:短函数是否应该存在?提取函数的必要性?我觉得其中最关键的是作者对于开发者的提醒:把握住 声明和实现 的定义,然后不断的去修正对于这些方法使用的理解。
《重构》第三章:代码的坏味道 如何判断一段代码需要重构,并且怎么重构,可以从以下的角度进行分析: 1.命名 在开发过程中每一个逻辑都可能涉及到一个函数的命名,函数的命名还必须是英文表达,所以总会遇到一些问题: ① 想到的命名已经在该类中:所以具有一段类似的逻辑,这时候其实会触发重复代码开发,我们可以尝试用移动语句重组代码顺序,把相似的部分放在一起以便提炼,这样只需要针对不同的逻辑进行新命名即可。 ② 函数名过长:想表达的意思没有一个主题时,过长的And拼接的命名需要思考潜藏着更深的设计问题;如果单纯只是单词长度导致可以适当的考虑缩写。 2.过长的函数/参数列表 函数内有大量的参数和临时变量,对于函数具体实现的阅读存在一定的难度,需要积极的分解函数,比如将集中可独立出去的部分采用提炼函数,采用查询的方式来减少临时变量; 对于函数的参数列表多长时,如果多个参数总是同时出现,那么可以通过中间层定义一个对象,以对象作为传参;当然考虑参数数量、中间层定义的成本后,可以先找到参数之间的规则,看是否可以由其中某个参数查询到另一个,以此来逐渐减少传参。 3.发散式变化 当某个模块发生变化时,有多处需要同时变化,这就通常是上下文边界定义不清晰导致。如果发生变化的位置存在先后顺序,那么可以使用拆分阶段来区分;如果会来回调用,就应该先创建适当的模块,将逻辑进行封装。 4.消息链&中间人 消息链过长时,会导致代码之间的依赖性较强,导致其中一个对象接口出现变动、对象关系出现变化,都会需要追踪消息链进行修改。所以可以使用隐藏委托关系在消息链的各个环节中使用,从而达到各个部分对外部隐藏其内部的所有细节;当然在实际的开发中最好是先观察消息链最终得到的对象是用来干什么的,判断是否能提炼独立的模块或函数,以此来替换原有的消息链中的一环。 5.注释 当需要注释来解释一块代码做了什么,那么代码本身就存在复杂的设计;明确注释是用来记述将来的打算、并无十足把握的区域,或者只是写下当下需求的原因,以用来帮助将来的修改者。
《重构》第二章:读书笔记 为什么重构? 1.不断叠加的为短期目的而进行的代码修改,很容易让内部设计走上一种难以控制的局面,容易造成简单的需求往往需要更多代码兼容 2.需求的迭代过程中,小步调的重构提高代码的可理解性,让后续的开发人员能更快速的理解代码意图 3.缺乏质量的代码设计,影响软件的耐久性,长远来看会不断降低团队的开发效率 何时重构? 1.重构是为了更快的实现某个功能,所以在面临大型项目时可以先在理解某个代码区域时小步小步的进行重构,当然如果处于紧急、小型的需求中,可以优先需求之后再进行重构 2.在任何需要并已完全理解代码结构,同时有思路后再动手重构。 重构 vs 架构 在实际遇到某个需求功能之前,就在代码中植入灵活性机制这一行为,从某个角度验证了开发人员对于整体的把控、业务的理解;但是业务的不断更新中,灵活性有时会影响开发的需求进度,要明确一点:没有完美的架构。 理解:每次想要开发组件时都会尽量往通用组件的方向走,即便和设计沟通清楚仍然避免不了后期的改动,慢慢的组件暴露的自定义参数增加,积累一定程度后会发现比如 某个参数定位的和说明不一致、某两个参数冲突、必须理解必传参数的含义等,这些又实际上降低了开发的效率。此时回想下文中说的挺有道理:1.命名的艺术 2.函数化:一次只做一件事 3.避免重复的代码,当然还是要多多学习演进式架构。
《重构》第一章: 读书笔记 重构前 程序并不是一开始就拥有很好的可扩展性,在需求的变动过程中不断提高对程序可扩展性的要求,而这些要求也使得重构变得必要。 理解: 如果一段代码能正常工作,并且不会再被修改,那么就完全可以不去重构,因为它并不会妨碍任何情况下任何人的理解。当然在添加新业务时可以不考虑重构,但是要做到在你离开后这块代码的结构是更清晰、而不是更糟糕。 重构中 1.提炼函数 (有哪些变量会离开原本的作用域) -> (变量名简洁有意义,可以带上类型名) -> (函数名在使用范围内简洁清楚) 2.查询取代临时变量 (将临时变量右侧提炼函数) -> (使用内联变量手法内联变量) -> (移除临时变量) 3.拆分循环 (分离循环中的逻辑) -> (提炼函数) 思考 性能问题vs重构 在面对一段程序代码时性能当然是很重要的一部分,但是面对越来越优秀的硬件系统上,很多直觉并不准确;同时重构将代码结构改良,反而能更快的进行性能调优。所以当决定重构时,在每一步细微的思考中,可暂时抛开性能问题,完成后再进行性能优化(这时候可能会造成回退到重构前的代码) 分支代码 开发过程中很容易写出很多if-else 、switch的逻辑,这些逻辑很容易随着堆积而腐坏 ==> 以多态取代条件表达式的方式来尝试解决这个问题,这个要求定义基类结构(属性 + 方法),子类继承的方式将各个行为归置到位。
首页 1 2 下一页