【译】如何征服史诗级代码

原文链接

在你开发者生涯的某些时刻,你的老板会交给你一些史诗级代码——天知道是谁在多久前写的代码。你的老板会说:读懂它,修复它,再加点儿新特性给它。

近两年这种情况我见得多了。我来帮你。

如何读懂史诗级代码

幸运的话你会得到一些文档介绍,或者至少代码里有注释。也可能找到一或两个元老级作者寻求帮助。呵呵呵呵通常你没这么幸运。

咱们看看没这么幸运的时候,该做些什么。

首先,你要保持谦逊。尊重面前的代码,尊重写下它的开发者。

吐槽面前的工作很容易,觉得“这代码太烂了”“我能写的更好”也很容易。但,这是错误的态度,会带你走上一条很危险的路。

如果沿着这条危险的路走下去,你开始修改代码,却并不完全知道所做的修改会产生怎样的影响。你会“修复”并没有坏掉的代码,只因为你不喜欢它们的代码风格,或者它们的写法比较老旧。终于,由于这种态度,你浪费了大量到难以想象的时间。

停!退一步想想,codebase(代码库,即项目里的所有代码)里的一切一定是有理由的。

直到前前后后读懂所有的代码之前,你应该假设,这些代码被写成这样一定有恰当的理由,你暂时不明白是什么理由。

这是一种更有生产力的态度。免得你搞坏一切之后没法尽快还原,简直要跳窗自尽。

不要让你的 codebase 蛋碎一地

“呃,我对史诗级代码的‘快速修复’实际上搞坏了一切然而我并不知道为什么。永别了!”

我发现,学习一个 codebase 最好的方式是:从用户界面开始,再回到代码层面。

选一条用户流程,比如登录,下单,评论,或者任何与你的 application (应用) 相关的都可以。以一名终端用户的身份过一遍流程。然后开始看代码,从用户界面(的代码)开始——这部分应该比较容易理解——然后一步步看下去,一直到数据库。

与此同时,画一个 sequence diagram(顺序图)来帮助理解发生了什么。如果你不知道什么是顺序图,或者不会画,看这个免费教程。如果你没有画 UML(Unified Modeling Language ,统一建模语言) 的工具,这儿有个免费的

译者注:UML是很好的描述工具,但容易产生繁琐的细节而且难以维护更新,而且并不是每个人都看得懂。所以没必要完全遵循 UML ,你可以按自己的方式去画图,但一定要保证别人能看懂。

UML 顺序图示例

一旦你完成了第一个顺序图,拷贝一份 database 到本地,开始对你遇到的 components (组件。就是在整个 app 中实现某个特定作用的一堆代码,比如下单功能、评论功能) 做一些轻微的修改,看看产生的效果是否与你预期相符。这是检测你对代码的理解的好方法。

这样反复理解,并完善你的图,直到你对整个 application (或者至少是你负责的部分)有一张完整的图为止。

最好分享你的笔记和图表。把它们放在容易被看见的地方,这样下一个接盘的开发者会很容易发现它们。不必担心做的是否完美,尽力去做,一点一滴也会起到帮助。

总之,最重要的是要有耐心,不要打(zi)你(bao)自(zi)己(qi)。代码是个复杂的东西。弄懂史诗级代码需要一些时间。保持冷静。

如何修复史诗级代码

在修复史诗级代码时,你将面对的最大挑战是:做出“修复到什么程度”的决定。强烈建议从小的、切实可行的修改开始。这意味着,你应该以尽量不会引起混乱的修改去完成问题的修复,而不是企图清理或者重构所有代码。

这样就给你提供了一个救生舱。最坏的情况,如果你被拉去处理其它优先级更高的事项——或者 deadline 快到了——至少你可以回滚到上一个版本。

一旦你接手了这项工作,只要你有时间,你就可以一点点地做出改进。

Martin Fowler 给代码重构项列了个目录,你可以从这里找到一些逐步改善 codebase 的好主意。原则是:每次修改后,都要让代码变得比之前更好。

有时你会遇到一些 structural defect(结构缺陷)导致的 bug ,这些 bug 不是简单改几个条件逻辑就能解决的,必须动大手术。

事情变的棘手了。你必须冷酷的看清——什么是最小的、实际可行的改动。你的每一根肌肉纤维都想把所有的代码推倒重来,忍住!

只要时间允许,你每一次对代码的修复都会让它变得更好。这就是你的目标。你维护这个 codebase 越久,它就会变的越好。

甲壳虫乐队(The Beatles) - 变的越好(Getting Better)
当我在折腾史诗级代码时,我常常会单曲循环这首歌以保持好心情。亲测有效。

为了保持这种方法有效果,请在每次预估修改代码所需时间时留一点裕量。

有时,structural defect 导致的 bug 已经是补丁无法拯救的了。这种情况其实比你想象的要罕见。

重复一遍,你必须冷酷的看清重写或重新设计带来的利与弊。你得接受这一点,毕竟这不仅是一个技术上的决定,更是一个商业上的决定。

做好准备用商业术语陈述你的 case 。对代码做出巨大的构造改革会产生哪些方面的开销?不这样做有哪些实际存在的商业风险?如果你有理有据,你的观点终将被采纳。如果一开始兜了些圈子也别大惊小怪,这很正常。

记住:如果你要做一个巨大的修改,首先得确保足够的支持和合理的预算。不要私自开工。除非你盼望着捅了篓子且 deadline 逼近后,与管理层尴尬的“开个小会”。

如何给史诗级代码添加新特性

终于,你被要求给史诗级代码添加几个新特性。此时此刻,你有一个重要的决定要做:是依照代码库“入乡随俗”(按已有的模式写代码)呢还是“开辟新航路”(用新的模式写代码)?

再重复一遍,你必须冷酷的看清你做出的评估。按照当前 codebase 的代码模式继续写下去会让它更糟吗?会堆积已经存在的问题吗?

通常情况下,你想让一切保持稳定。那就按照已有的代码模式一点点添加(代码)。重复使用已有的基本部分。用尽量不会引起混乱的修改一点点改进代码。

如果你坚信“开辟新航路”是十分必要的,那你需要找到一个方法,把你所做的改变隔离起来,尽量不要影响到已经存在的 codebase 。

试着把你的新特性划归为一个独立的 project 。你可以露出一个 API 和史诗级代码对接。让你的代码和史诗级代码不需要互相了解。

那么问题来了,当你需要用到史诗级代码里的功能去实现新特性时,会变得很麻烦。最好的方法是——用 Adapter Pattern(适配器/转接头)。


咳,不是这种转接头(别瞎想,不是这种)

Do Factory很好的解释了 Adapter Pattern:

“The Adapter pattern translates one interface (an object’s properties and methods) to another. Adapters allow programming components to work together that otherwise wouldn’t because of mismatched interfaces. The Adapter pattern is also referred to as the Wrapper Pattern.

One scenario where Adapters are commonly used is when new components need to be integrated and work together with existing components in the application.

Another scenario is refactoring in which parts of the program are rewritten with an improved interface, but the old code still expects the original interface.”

这块儿不太好翻译,大意是:

Adapter Pattern 对一个 interface(接口)(一个 object 的 property 和 method ) 进行转换。让 interface 不相匹配的 programming component 在 adapter 的帮助下一起工作。Adapter Pattern 也被称作 Wrapper Pattern 。

Adapter 通常用于这种情况,新的 component 需要被 integrated(整合)与 application 里已有的 component 一起工作。

另一种需要使用 Adapter 的常见情况是,把程序的一部分重构(导致 interface 改变)之后,以前的那部分代码仍期待着之前的 interface 。

这儿有各种语言的(关于 adapter 的)解释和例子。

关键点

总结一下,这些关键点能帮你从根本上搞定 codebase :

  1. 在你花时间完全弄懂史诗级代码前,不要轻易修改,也不要妄加评判。
  2. 顺序图是你的好伙伴。
  3. 优先小的、一步步的改进,而不是大批量的重写或修改。
  4. 每一次修改都应该让代码比之前更好。
  5. 如果你要做大改动,提起一个 business case 看看能否得到支持。
  6. 添加新特性时,请“入乡随俗”。
  7. 如果非要“开辟新航路”,请让你所做的改变保持独立,再用 Adapter Pattern 来整合。
posted @ 2017-04-13 21:45  rainmakerneon  阅读(277)  评论(0)    收藏  举报