最常见的基本重构方法可以归纳为两个方向。通过归纳方法将一个长的过程分解为小的可以重用的组件,和通过内联(inline)方法来消除那些不够份量的小方法。我们可以提炼方法来让大量的子类共享相同的功能特征,我们可以下放方法来让只有用到这些功能的子类才知道它们的存在。重构就是爬山,通过一步一步的小的提高来逐渐的改进整体的质量,但在重构时,我们如何知道哪种方法是上山的正确道路?
关于代码地形学的这个问题公认的方法有两种。去除有异味的代码和重构成模式。如果能做到这样,当然是很好的。就像是纠正作文里的一个语法错误或不恰当的比喻。如果我们可以找到这些四处隐藏的有异味的代码,将它们重写成整洁的,条理的,结构化的形式,何乐而不为。但这些都是特殊情况。如果没有明显的模式来重构,或没有很直接的方法来去除代码异味,那该怎么办呢?
这才是我们如今编程艺术的中心问题,而很少人讨论这些。通常我们讨论这些问题时都是罗列出更多更长的有异味的代码模式的清单,但这并不是解决问题的方法。代码异味应该是我们公认的不好的东西,而不是那些置之不理也无妨的事情。我们经常会说到老板不给我们重构的机会,甚至代码有明显的异味,老板们认为这是浪费时间。并不是每个人都有懂软件的老板。我很吃惊为什么只有很少的讨论谈到点子上。。也许我这篇文章才说到问题关键处。
我的观点,当重构没有现成的明显的方向时,我们可以遵循下面的原则:
- 当属性、方法或类存在任何的需要复用的意向时,归纳提炼它们。
- 不要低估小方法对代码整洁的作用。使用小方法能让你节省很多笔墨。
- 能让代码长度变短的提炼都应该去提炼,包括注释。
- 用switch()代替多形——即使这样做会使代码变长。
- 用封装控制可见度。
- 消除依赖。
- 简化构造方法——即使这样做会使代码变复杂。
- 封装或避免条件表达式。使用guard语句,避免使用
else
语句。 - 使用常量代替魔幻数字。
- 不确定时,偏向使用组合而不是继承。
- 不确定时,将计算操作移入到这些数据的所有者对象里,或将数据移动到执行计算操作的对象里(也就是迪米特法则(Law of Demeter))。
- 使用小对象,松耦合,避免大对象,高聚合。
- 不确定时,偏向使用递归而不是循环。
- 使用代理对象,模拟对象和辅助对象来隔离网络,数据库,文件和用户接口。
- 不确定时,尽量在model里添加代码,必要时才往controler添加代码。view里添加的都应该是便捷功能和简写方法,但不要局限于此。
- 偏向使用apply, each, mapcar,而不是loop.
- 尽量使用新技术。
Refactoring is a process of editing software that doesn’t change what the software does, but instead improves it by making it more clear, more concise, or more direct. Refactoring is to code what structural editing is to prose. The debate about the utility of refactoring is just a rehash of the perpetual debate about revision. Everyone agrees that you ought to revise and that you never can revise enough. There are people like Mozart and Trollope who never revise and who do just fine, but most writers agree that’s fine for them and not a great idea for you and me.
Most of the classic refactorings run in two directions. We can Extract Method to break a long procedure into small and reusable components, and we can Inline Method to get rid of a small method that’s not pulling its weight. We can Pull Up Method to let a bunch of subclasses share common behavior, or we can Push Down Method so only the subclass that uses this behavior needs to know about it. Refactoring then becomes a matter of hill climbing, gradually improving the entire work by making a series of minor changes that facilitate larger and larger changes. When refactoring, how do we know which way is up?
Two broad approaches to the topography of code have been proposed. We move away from code smells, and we refactor toward patterns. These make sense, as far as they go. We should repair blunders in code just as we’d fix grammatical accidents or mixed metaphors, and if we see an opportunity to take some scattered code and rewrite it in a clean, ordered, and structured manner, that’s always a good idea. But these are exceptional cases. How do we refactor when there’s no grand pattern at hand and where we don’t see a straightforward way to expunging a code smell?
This is a central question of the craft of software today, and it’s seldom discussed. We talk around the question by contriving ever longer lists of code smells, but that’s not really a solution: code smells should be things we agree are usually or always bad, not things that might often be perfectly fine. We talk around the question by assuming that management won’t let us refactor even the obvious cases and so worrying about subtle situations is a waste. But not everyone has a pointy-haired boss. I’m surprised there’s so little literature on this question. I think at this point I’ve covered the books, but perhaps I’ve missed great stuff in the journal literature. Email me.
My two cents holds that, when the direction of refactoring is not immediately clear, we drive the ponies in the following directions:
- Extract fields, methods, and classes when there is any prospect of eventual reuse by additional methods.
- Do not underestimate the impact of tiny methods on cleaning up your code. Invoking small methods let you omit needless words.
- Extract whenever extracting reduces the length of code — including comments (not counting doc comments required by coding standards, which don’t count).
- Prefer polymorphism to switch(), even if the code is longer.
- Prefer encapsulation to visibility.
- Break dependencies.
- Simplify constructors, even if it complicates the code.
- Encapsulate or avoid conditional expressions. Prefer guard expressions and avoid
else
clauses. The gate is straight down. - Prefer a dedicated type to a primitive, even if the code is longer; it’s better to pass Money than an integer even if you know the integer is money.
- When in doubt, prefer composition to inheritance .
- When in doubt, move computation to the object that owns the data or move the data to the object that does the computation (aka Law of Demeter).
- Prefer small objects, loosely coupled, to large objects and to tight coupling.
- When in doubt, prefer recursion to loops.
- Isolate the network, the database, files, and the user interface with facades, fakes, and humble objects.
- When in doubt, add code to the model if you can, to the controller if you must. Regard any work in the view as a convenience function or syntactic sugar, but don’t let that stop you.
- Prefer blocks (apply, each, mapcar) to loops.
- Prefer new technology.