vlambda博客
学习文章列表

Dan Abramov(React 核心开发,Redux 作者) 为什么说“再见了 整洁代码”?

Dan Abramov 的博客(overreacted.io/)是我的必读列表中的一个,他这个人总是能发表一些和我们所谓“大神“称号的价值观完全相悖的文章,我特别喜欢他独特的观点。前天他在博客上竟然发表了一篇名为《再见了,简洁代码》的文章,我们从写代码那天起就一直被灌输要写出整洁的代码,并且把

代码整洁之道 (豆瓣) book.douban.com

这本书奉为圣经。那么他到底是什么样的观点呢?我特地找了专业的英文翻译 liusha 来翻译本篇文章,希望大家读完以后会有自己的看法。

再见了,简洁代码

有天深夜,我一个同事提交了他写了一个礼拜的代码。我们当时正在做一个图形编辑器,拖拽长方形或椭圆形等形状边缘的控制点对其进行放大或缩小。

代码完美运行,但是存在冗余。每个形状的控制点都不一样,而且拖拽方向不同也会对形状的位置和大小产生不同影响。如果用户按住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
},
};

重复的代码逻辑让我看得很闹心,一点都不简洁。

实现相同方向的代码逻辑有许多相似之处。例如, Oval.resizeLeft()与Header.resizeLeft()的代码逻辑是重复的。这是因为它们都在实现拖拽左边的控制点。

另外,同样形状的实现也存在相似之处。例如,Oval.resizeLeft()与其他椭圆形的实现是相似的。这是因为它们实现的形状都是椭圆形。另外,Rectangle、Header和TextBlock之间也存在重复,因为text block也是长方形。

所以我灵光一现,决定消除所有冗余,把代码精简成下方这样:

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);


精简过的代码是原来的一半,所有冗余都消除了!如果想要改变方向或形状,只要修改一处就可以了,而不需要在各处都修改。

那天我一直改到半夜(因为改得太嗨了)。我把重构后的代码提交到主分支,然后就心满意足地去睡觉了,因为我觉得自己把同事写的乱七八糟的代码理顺了。

第二天早晨

出乎我意料之外的事情发生了......

我领导找我私聊,礼貌地让我回滚我昨天晚上提交的代码。我惊呆了,最初的代码写得乱七八糟,而我把它改得简洁了!

我心不甘情不愿地照办了,但是几年后我才真正认同他的做法。

只是一个阶段而已

过分追求简洁和高效的代码是许多人都会经历的一个阶段。当我们对自己写的代码没有十足把握时,往往会在一些量化指标上找回尊严,比如代码规范、命名方式、文件结构和简洁程度等。

虽然没人天生就会写简洁代码,但是一回生二回熟。通常每一次修改后都能感觉出来代码冗余程度到底是升高了还是降低了。因此,消除代码冗余感觉像是在提升代码的客观指标。更糟糕的是,有些人会以“我能写出简洁代码”来标榜自己,这其实是自欺欺人的心理。

当我们学会精简代码逻辑之后,就忍不住会毫无节制地使用这项能力,一看到重复的代码逻辑就会进行精简。写了几年代码后,我们会随时随地发现重复的代码逻辑,精简成为了我们新的超能力。如果有人跟我们说精简代码逻辑是件好事,我们就会深信不疑,而且会鄙视那些不信奉“简洁代码”的人。

而我现在发现“重构代码”会在以下两个方面引起麻烦:

一,没跟写代码的人沟通过就擅自重构代码。即使你重构的代码确实比之前的版本好(我现在已经不相信有这种情况了),这个方法也是很糟糕的。一个健康的技术团队应该不断建立并维持团队互信,你没跟团队成员商量就擅自重构别人的代码会严重损害你在团队中的声誉,导致无法在同一个代码库中有效合作。

二,一切都是有成本的。我重构的代码虽然更精简,但却削弱了后续修改需求的能力,这不是一笔合算的交易。举个例子,之后我们需要许多特殊的用例和行为处理不同形状,我精简的代码逻辑可能会将难度加大好几倍,而原版代码虽然臃肿,但却可以轻松应付这样的改变。

当然我并不是说你就该把代码写得臃肿一些。我的意思是应该好好考虑一下“简洁”和“臃肿”到底代表着什么。简洁代码是不是让你有一种反叛或正义感?或是让你觉得很美或很优雅?但你能不能说出这些感觉在技术层面对应的结果是什么呢?它们到底如何影响着代码的编写和修改方式呢?

我之前完全没有深究这个问题,我只是在乎代码最终呈现出来的形态,而没有考虑到代码是人写出来的,而人充满变数。

写代码是一场旅程。你可以回想你第一次写代码一直到现在经历了多少事情。我觉得第一次学会通过抽取函数或重构类将代码简化是非常令人愉悦的体验。如果精简代码能让你对自己的技术感到骄傲,那么就自然想要这么做,这无可厚非。

但不能一直沉迷于此,不要变成一个过度追求简洁代码的狂人。因为简洁代码并不是最终目标,它只是一个方法,帮我们弄清楚我们所面对的复杂系统。如果你还不确定你所做的改变会如何影响代码库,但是想在未知的海洋里拼命抓住一根稻草,那就会把简洁代码作为你的自我防御机制。

先让简洁代码为你导航,然后就放手吧。