【译】再见了,整洁的代码

原文连接:overreacted.io/goodbye-cle…javascript

本文主要介绍Dan大佬对于"整洁的代码"理解的心路历程。

Let clean code guide you. Then let it go.

那是一个深夜。java

个人同事刚刚检查完他们花了一整个周所完成的代码。当时咱们正在作一个图形编辑器,已经实现了经过拖动边缘的小手柄来调整矩形和椭圆形等形状的功能。react

代码运行起来没问题。程序员

可是代码里有不少重复的地方。每一个形状(例如矩形或椭圆形)都有一组不一样的手柄,当咱们沿不一样方向拖动手柄时,它们会以不一样的方式影响形状的位置和大小。若是用户按住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
  },
};
复制代码

这些重复的代码让我很是困扰。ide

这些代码一点都不整洁。函数

大部分的重复代码存在于类似方向的拖拽上。举个例子,Oval.resizeLeft()Header.resizeLeft()有类似之处,由于它们都须要处理左侧把手的拖拽。学习

还有一些重复代码存在于同一个形状的方法中。举个例子,Oval.resizeLeft()和其余的Oval方法有类似之处。这是由于他们都在处理椭圆形。一样的道理,在矩形、页头和文本框中也存在这个问题,由于它们都是矩形。ui

我想到了一个办法!

咱们能够经过这样的方式,把重复的代码都抽离出来:

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);
复制代码

代码总量减小一半,而且重复代码所有消失了!好整洁。若是要更改特定方向或形状的行为,只须要在一个地方进行修改,不须要像之前那样把全部方法都更新一遍。

已经很晚了(睡意逐渐让我分心)。我把重构后的代码合并到了主分支,而后满怀着清理完同事的凌乱代码而产生的自豪感,进入了梦乡。

次日早上

... 好像有点不对劲

老板把我叫出去进行了一次面对面交谈,他很礼貌的让我把昨晚的代码还原回去。我懵了,由于我坚信上一版的代码一团糟,而个人很是整洁!

我很勉强的答应了,但却一直心有不甘。直到不少年后,我才明白了其中的道理。

这是一个阶段

痴迷于"整洁的代码"和"消除重复"是咱们许多人都会经历的一个阶段。当咱们对本身的代码不够自信时,就很容易将自我价值感和专业自豪感附加到能够衡量的事物上。好比一系列的严格lint规则、命名规范、文件结构、禁止重复等。

刚开始你可能并不清楚如何清理重复代码,可是随着实践增多你会愈来愈驾轻就熟。渐渐地,你能够判断出在每一次代码更改后,代码的重复度究竟是增多了仍是减小了。结果,年轻的咱们就会以为,消除重复代码就是在提高代码的质量。更糟糕的是,这种想法会跟人们的自我认同感交织在一块儿:"我就是那种会写整洁代码的人"。这种想法就像任何一种自我欺骗同样,如此的强烈。

一旦咱们知道了何为抽象,就会按耐不住想要去提高这种能力,以致于每当看到重复代码就会去凭空提取一些抽象出来。通过几年的"磨练",咱们终于作到了——目光所及之处,全都是有待抽象的重复代码。若是有人对咱们说抽象是一种美德,那咱们必定会跟他作同志了。而后一块儿去批判那些不崇尚"整洁代码"的人。

如今我明白了,个人"重构"从两个方面来讲是一场灾难:

首先,我没有跟写下它们的人交谈过。我在没有他们参与的状况下,独自重写了代码并合并进了主分支。即便这是一项改进(其实并非),这也不是解决问题的好办法。健壮的工程团队最重要的是创建起信任。在项目协做开发中,没有和同事深刻讨论就去重写他们的代码,对咱们的团队协做能力是一个巨大的打击。

其次,没有什么是免费的。个人代码牺牲了应对需求变化的能力,换来了更少的重复代码,但这其实不是一个好买卖。例如,后来咱们针对不一样形状的不一样手柄,新增了许多特殊的情景和行为。个人抽象必须用数倍的复杂度去完成这些需求,而对于原来凌乱的版本,这样的改动跟切蛋糕同样容易。

我是在说你应该写"脏乱"的代码吗?不,我建议你深刻思考一下何为"整洁"何为"脏乱"。你感觉到过"反叛"?"正义"?"美丽"?"优雅"吗?你怎么肯定你能把这些精神品质映射到具体的工程成果呢?它们究竟如何影响编写和修改代码的方式呢?

我能确定的是我本身对这些事情没有深思过。我仅仅对代码的外观进行了不少思考,但却没有考虑过它们是如何从一个团队中造成的。

编码是一段旅程。考虑一下你从写下第一行代码至今所经历的事情。我猜,当你第一次抽离出一个函数或者重构了一个类,并让代码变得更简单的时候,你当时必定很开心。若是你对本身的技术感到自豪,就去追求代码的整洁性。先这样作一段时间吧。

可是不要停在那里。不要成为一个整洁代码的狂热者。整洁的代码历来不是最终目标。而是一种从正在处理的系统的巨大复杂性中得到某种意义的尝试。这是一种防护机制,当你不肯定你的更改会对项目产生怎样的影响时,你可能须要一些指导。

让整洁的代码指导你前进。 Then let it go.


总结

  • 适量的重复代码赛过糟糕的抽象
  • 尊重并学习他人的劳动成果,而不是一味的贬低

Read More

DRY(Don't Repeat Yourself)在Wikipedia中是这么描述的:

Every piece of knowledge must have a single, unambiguous, authoritative representation within a system

这个方法论是如此的重要,它几乎贯穿咱们Coding的整个过程,稍有追求的程序员都会时刻注意代码的抽象性,打心底里拒绝复制/粘贴。

可是若是咱们不假思考的去遵照DRY原则,就可能会遇到本文中的问题:

不肯定代码的当下,没想好代码的未来,在这种状况下,以错误的假设为前提,强行遵照DRY原则,结果写出了在未来变得不可维护的代码抽象

所以,除了理解透DRY原则,咱们还须要更多方法论来指导咱们编程,AHA原则就是其中一个,来看一下吧:Avoid Hasty Abstractions

相关文章
相关标签/搜索