尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

重构读书笔记

重构读书笔记 我所说的偏爱对象是对可变数据而言。如果数据不可变我大可直接将这3个值保存在记录里需要做数据变换时增加一个填充步骤即可。程序中间常常需要互相传递嵌套的列表list或散列映射结构这些数据结构后续经常需要被序列化JSON或XML。这样的嵌套结构同样值得封装这样如果后续其结构需要变更或者需要修改记录内的值封装能够帮我更好地应对变化。如果该记录比较复杂例如是个嵌套解构那么先重点关注客户端对数据的更新操作对于读取操作可以考虑返回一个数据副本或只读的数据代理。记录的更新点需要同样小心处理但对记录的读取点则有多种处理方案。不过也正如我对性能问题的一贯态度这样的性能损耗也许是可以接受的——只有测量到可见的影响我才会真的关心它。我喜欢封装程序中的所有可变数据。这使我很容易看清楚数据被修改的地点和修改方式这样当我需要更改数据结构时就非常方便。我们通常鼓励封装——使用面向对象技术的开发者对封装尤为重视——但封装集合时人们常常犯一个错误只对集合变量的访问进行了封装但依然让取值函数返回集合本身。这使得集合的成员变量可以直接被修改而封装它的类则全然不知无法介入。自己的总结为什么有函数式编程变量不变流派。为什么要对变量进行封装而不是直接数据结构存储不让其他人随意修改数据可以返回副本也可以知道哪里对值进行了修改。不要让集合的取值函数返回原始集合这就避免了客户端的意外修改。还有一种方法是以某种形式限制集合的访问权只允许对集合进行读操作。比如在Java中可以很容易地返回集合的一个只读代理这种代理允许用户读取集合但会阻止所有更改操作——Java的代理会抛出一个异常。有一些库在构造集合时也用了类似的方法将构造出的集合建立在迭代器或枚举对象的基础上因为迭代器也不能修改它迭代的集合。采用哪种方法并无定式最重要的是在同个代码库中做法要保持一致。我建议只用一种方案这样每个人都能很快习惯它并在每次调用集合的访问函数时期望相同的行为。一旦我发现对某个数据的操作不仅仅局限于打印时我就会为它创建一个新类。一开始这个类也许只是简单包装一下简单类型的数据不过只要类有了日后添加的业务逻辑就有地可去了。这些小小的封装值开始可能价值甚微但只要悉心照料它们很快便能成长为有用的工具。创建新类无须太大的工作量但我发现它们往往对代码库有深远的影响。实际上许多经验丰富的开发者认为这是他们的工具箱里最实用的重构手法之一——尽管其价值常为新手程序员所低估。每当我在不同的地方看见同一段变量的计算逻辑我就会想方设法将它们挪到同一个函数里。自己的话对于不变的计算出的数据应该查询而不是临时变量。以查询取代临时变量178手法只适用于处理某些类型的临时变量那些只被计算一次且之后不再被修改的变量。最简单的情况是这个临时变量只被赋值一次但在更复杂的代码片段里变量也可能被多次赋值——此时应该将这些计算代码一并提炼到查询函数中。并且待提炼的逻辑多次计算同样的变量时应该能得到相同的结果。因此对于那些做快照用途的临时变量从变量名往往可见端倪比如oldAddress这样的名字就不能使用本手法。如果某些客户端先通过服务对象的字段得到另一个对象受托类然后调用后者的函数那么客户就必须知晓这一层委托关系。万一受托类修改了接口变化会波及通过服务对象使用它的所有客户端。我可以在服务对象上放置一个简单的委托函数将委托关系隐藏起来从而去除这种依赖。这么一来即使将来委托关系发生变化变化也只会影响服务对象而不会直接波及所有客户端。在隐藏委托关系189的“动机”一节中我谈到了“封装受托对象”的好处。但是这层封装也是有代价的。每当客户端要使用受托类的新特性时你就必须在服务端添加一个简单委托函数。随着受托类的特性功能越来越多更多的转发函数就会使人烦躁。服务类完全变成了一个中间人81此时就应该让客户直接调用受托类。这个味道通常在人们狂热地遵循迪米特法则时悄然出现。我总觉得如果这条法则当初叫作“偶尔有用的迪米特建议”如今能少很多烦恼。很难说什么程度的隐藏才是合适的。还好有了隐藏委托关系189和删除中间人我大可不必操心这个问题因为我可以在系统运行过程中不断进行调整。随着代码的变化“合适的隐藏程度”这个尺度也相应改变。6个月前恰如其分的封装现今可能就显得笨拙。重构的意义就在于你永远不必说对不起——只要把出问题的地方修补好就行了。有时我会想修改原先的算法让它去做一件与原先略有差异的事。这时候可以先把原先的算法替换为一个较易修改的算法这样后续的修改会轻松许多。使用这项重构手法之前我得确定自己已经尽可能分解了原先的函数。替换一个巨大且复杂的算法是非常困难的只有先将它分解为较简单的小型函数我才能很有把握地进行算法替换工作。对付循环我有两个常用的手法拆分循环227可以确保每个循环只做一件事以管道取代循环231则可以直接消灭整个循环。最后这项手法我相信一定会是任何一个合格程序员的至爱那就是移除死代码237。没什么能比手刃一段长长的无用代码更令一个程序员感到满足的了。值对象和引用对象的区别也告诉我何时不应该使用本重构手法。如果我想在几个对象之间共享一个对象以便几个对象都能看见对共享对象的修改那么这个共享的对象就应该是引用。这两类条件表达式有不同的用途这一点应该通过代码表现出来。如果两条分支都是正常行为就应该使用形如if…else…的条件表达式如果某个条件极其罕见就应该单独检查该条件并在该条件为真时立刻从函数中返回。这样的单独检查常常被称为“卫语句”guard clauses。以卫语句取代嵌套条件表达式的精髓就是给某一条分支以特别的重视。如果使用if-then-else结构你对if分支和else分支的重视是同等的。这样的代码结构传递给阅读者的消息就是各个分支有同样的重要性。卫语句就不同了它告诉阅读者“这种情况不是本函数的核心逻辑所关心的如果它真发生了请做一些必要的整理工作然后退出。”最明显的征兆就是有好几个函数都有基于类型代码的switch语句。若果真如此我就可以针对switch语句中的每种分支逻辑创建一个类用多态来承载各个类型特有的行为从而去除重复的分支逻辑。另一种情况是有一个基础逻辑在其上又有一些变体。基础逻辑可能是最常用的也可能是最简单的。我可以把基础逻辑放进超类这样我可以首先理解这部分逻辑暂时不管各种变体然后我可以把每种变体逻辑单独放进一个子类其中的代码着重强调与基础逻辑的差异。多态是面向对象编程的关键特性之一。跟其他一切有用的特性一样它也很容易被滥用。我曾经遇到有人争论说所有条件逻辑都应该用多态取代。我不赞同这种观点。我的大部分条件逻辑只用到了基本的条件语句——if/else和switch/case并不需要劳师动众地引入多态。但如果发现如前所述的复杂条件逻辑多态是改善这种情况的有力工具。如果你发现代码假设某个条件始终为真就加入一个断言明确说明这种情况。因为断言应该不会对系统运行造成任何影响所以“加入断言”永远都应该是行为保持的。函数参数化Parameterize Function曾用名令函数携带参数Parameterize Method如果我看见代码从一个记录结构中导出几个值然后又把这几个值一起传递给一个函数我会更愿意把整个记录传给这个函数在函数体内部导出所需的值。函数的参数列表应该总结该函数的可变性标示出函数可能体现出行为差异的主要方式。和任何代码中的语句一样参数列表应该尽量避免重复并且参数列表越短就越容易理解。在浏览函数实现时我有时会发现一些令人不快的引用关系例如引用一个全局变量或者引用另一个我想要移除的元素。为了解决这些令人不快的引用我需要将其替换为函数参数从而将处理引用关系的责任转交给函数的调用者。如果为某个字段提供了设值函数这就暗示这个字段可以被改变。如果不希望在对象创建之后此字段还有机会被改变那就不要为它提供设值函数同时将该字段声明为不可变。很多面向对象语言都有特别的构造函数专门用于对象的初始化。需要新建一个对象时客户端通常会调用构造函数。但与一般的函数相比构造函数又常有一些丑陋的局限性。例如Java的构造函数只能返回当前所调用类的实例也就是说我无法根据环境或参数信息返回子类实例或代理对象构造函数的名字是固定的因此无法使用比默认名字更清晰的函数名构造函数需要通过特殊的操作符来调用在很多语言中是new关键字所以在要求普通函数的场合就难以使用。工厂函数就不受这些限制。工厂函数的实现内部可以调用构造函数但也可以换成别的方式实现。继承本身是一个强有力的工具但有时它也可能被用于错误的地方有时本来适合使用继承的场景变得不再合适——若果真如此我就会用以委托取代子类381或以委托取代超类399将继承体系转化成委托调用。另一种选择就是提炼类182。这两种方案之间的选择其实就是继承和委托之间的选择总之目的都是把重复的行为收拢一处。提炼超类通常是比较简单的做法所以我会首选这个方案。即便选错了也总有以委托取代超类399这瓶后悔药可吃。在重构类继承体系时我经常把函数和字段上下移动。随着继承体系的演化我有时会发现一个类与其超类已经没多大差别不值得再作为独立的类存在。此时我就会把超类和子类合并起来。这就是一个用得上以委托取代超类手法的例子——如果超类的一些函数对子类并不适用就说明我不应该通过继承来获得超类的功能。除了“子类用得上超类的所有函数”之外合理的继承关系还有一个重要特征子类的所有实例都应该是超类的实例通过超类的接口来使用子类的实例应该完全不出问题。
返回列表