夜深了。
同事们把这周写的代码提交了。我们在开发一个图形编辑器画布,已经实现了形状调整功能,即通过拖拽形状边缘的手柄来调整形状(比如矩形和椭圆形)。
代码可以正常运行。
但重复代码有点多。每一种形状(比如矩形和椭圆形)有不同的手柄,往不同方向拖拽手柄对形状的位置和大小影响也不一样。如果用户同时按住 Shift 键,在改变大小的同时要保持比例不变。这里涉及了很多数学运算。
代码看起来像这样:
let Rectangle = { resizeTopLeft(position, size, preserveAspect, dx, dy) { // 10 repetitive lines of math }, resizeTopRight(position, size, preserveAspect, dx, dy) { // 10 repetitive lines of math }, resizeBottomLeft(position, size, preserveAspect, dx, dy) { // 10 repetitive lines of math }, resizeBottomRight(position, size, preserveAspect, dx, dy) { // 10 repetitive lines of math }, }; let Oval = { resizeLeft(position, size, preserveAspect, dx, dy) { // 10 repetitive lines of math }, resizeRight(position, size, preserveAspect, dx, dy) { // 10 repetitive lines of math }, resizeTop(position, size, preserveAspect, dx, dy) { // 10 repetitive lines of math }, resizeBottom(position, size, preserveAspect, dx, dy) { // 10 repetitive lines of math }, }; let Header = { resizeLeft(position, size, preserveAspect, dx, dy) { // 10 repetitive lines of math }, resizeRight(position, size, preserveAspect, dx, dy) { // 10 repetitive lines of math }, } let TextBlock = { resizeTopLeft(position, size, preserveAspect, dx, dy) { // 10 repetitive lines of math }, resizeTopRight(position, size, preserveAspect, dx, dy) { // 10 repetitive lines of math }, resizeBottomLeft(position, size, preserveAspect, dx, dy) { // 10 repetitive lines of math }, resizeBottomRight(position, size, preserveAspect, dx, dy) { // 10 repetitive lines of math }, };
那些重复的代码真让我心烦。
这样的代码不够clean
。
大部分重复是因为朝相同方向调整形状的代码都差不多,比如Oval.resizeLeft()
和 Header.resizeLeft()
就很类似。
其他重复是因为同一种形状的方法之间很相像,比如 Oval.resizeLeft()
和 Oval
其他的方法就很类似。另外,Rectangle
、Header
和 TextBlock
之间也有重复的地方,因为文本框也是矩形。
基于上面的分析,重构的思路就清晰了。
我们可以将代码分组,然后把重复代码移除掉。就像下面这样:
let Directions = { top(...) { // 5 unique lines of math }, left(...) { // 5 unique lines of math }, bottom(...) { // 5 unique lines of math }, right(...) { // 5 unique lines of math }, }; let Shapes = { Oval(...) { // 5 unique lines of math }, Rectangle(...) { // 5 unique lines of math }, }
然后,把它们的行为组合起来。
let {top, bottom, left, right} = Directions; function createHandle(directions) { // 20 lines of code } let fourCorners = [ createHandle([top, left]), createHandle([top, right]), createHandle([bottom, left]), createHandle([bottom, right]), ]; let fourSides = [ createHandle([top]), createHandle([left]), createHandle([right]), createHandle([bottom]), ]; let twoSides = [ createHandle([left]), createHandle([right]), ]; function createBox(shape, handles) { // 20 lines of code } let Rectangle = createBox(Shapes.Rectangle, fourCorners); let Oval = createBox(Shapes.Oval, fourSides); let Header = createBox(Shapes.Rectangle, twoSides); let TextBox = createBox(Shapes.Rectangle, fourCorners);
代码量减少了一半,重复代码完全消失了!一下子clean
了。如果要修改某个形状
或方向
的行为,只需要在一个地方做出改动,不需要修改所有的方法。夜已深,我把改好的代码提交到 master 分支,然后上床睡觉。因为帮同事把杂乱的代码清理干净了,我心里很自豪。
第二天
事情并没有像我期待的那样发生。老板找我谈话,他们希望我把代码回滚回去。我感到很惊讶,毕竟原先的代码简直就是一团乱麻,而我改得很clean啊!我很不情愿地答应了,但几年之后,我才意识到他们其实是对的。
必经之路
痴迷于“clean Code”和删除重复代码是我们很多人都会经历的一个阶段。当我们对自己的代码不是很自信时,就很容易将自我价值感和职业自豪感与一些可以被衡量的东西联系在一起,比如严格的 lint 规则、命名规范、文件结构、没有重复。我们没办法自动去除重复代码,但可以自己动手做。每次修改代码之后,我们可以很容易地知道重复代码是少了还是多了。所以,去除重复代码感就像是在改进代码质量。更糟糕的是,它扰乱了人们的认同感,让他们觉得“我是那种编写CLean Code的人”,但这其实无异于自我欺骗。一旦学会了抽象,我们就很容易对这种能力产生很高的期望,每当看到有重复代码就会想要对它们进行抽象。在写了几年代码之后,我们发现重复代码到处都是,而抽象成了我们获得的一项超级能力。如果有人告诉我们说抽象是一种美德,那我们肯定会深信不疑,并且会因为别人不崇尚“CLean Code”而对他们品头论足。现在,我知道之前的代码重构就是一个灾难,原因如下。
- 首先,我没有事先和写代码的人沟通。我直接修改了他们的代码并提交,没有和他们讨论。即使这是一种改进(但我现在不这么认为了),但我这样的行事方式并不值得称道。一个健康的工程团队应该以信任为基础,不经过讨论就修改他人的代码会对团队协作造成沉重的打击。
- 其次,天下没有免费的午餐。我以牺牲灵活性为代价,以此来减少重复代码,这算不上是一个好的权衡。例如,后来我们要求不同形状的不同手柄具备一些特殊的行为,被我重构过的代码需要修改多次才能满足需求,而原先“杂乱”的代码却可以很容易实现这些需求。
那么,我的意思是我们应该尽量写“Dirty”代码吗?当然不是。我只是建议大家在考虑什么是“Clean”或“Dirty”代码时进行深度思考。你当时有什么样的感觉?厌恶?正义?美丽?优雅?你可以肯定这些品质会带来实质性的工程成果吗?它们又是如何影响代码的编写和修改方式的?我确实没有深入思考过这些事情。我只考虑到代码本身,但从来没有想过代码与团队之间的演化关系。编码就像是一段旅程,想想你从写第一行代码到现在走了多远。当第一次通过提取函数或重构类让复杂的代码变简单,我觉得那是一种乐趣。如果你对自己的“杰作”感到自豪,那么就很容易掉入追求clean Code
的旋涡。但不要就此止步!不要只做一个执着于clean Code的重构狂。写出clean Code并不是我们的终极目标,我们只是尝试通过这种方式找到处理复杂系统的方法。当你不确定代码改动会对代码库造成怎样的影响,在未知的海洋中需要灯塔的指引,那么这不失为一种防御机制。写出clean code
可以作为一种方向,但后面还有很长的路需要去探索。
作者:Dan Abramov | 译者:无明 | 策划:小智
原文链接