本文是我在 Hacker News 上看到的一篇文章。原文讲述了作者对整洁代码的一些思考。本人在学生时期看过一些关于代码风格的书,比如《高质量程序设计指南》、《代码大全》、《代码整洁之道》等等。也养成了自己对优雅、简洁代码的偏执,相信很多程序员小伙伴都有这种偏执。但工作后,随着代码经验的积累和同事的打脸,开始反思自己坚持的风格。偶然看到 Hacker News 上的这篇文章,和作者有点感同身受,情不自禁想把这篇文章分享给大家。
原文地址:https://overreacted.io/goodbye-clean-code/
原文在 Hacker News 上引发的讨论:https://news.ycombinator.com/item?id=22022466
以下是译文。
那是一个深夜。
我的同事们刚刚提交了他们花了整整一周编写的代码。我们的需求是在图形编辑器画布上工作,同事们刚提交的代码实现了通过拖动图形边缘的小手柄来调整矩形、椭圆等形状。
代码工作良好。但是有重复代码。每个形状(例如矩形和椭圆)都有一组不同的手柄,在不同的方向上拖动每个手柄,会以不同的改变形状的位置和大小。如果用户按住 Shift 键,我们还需要在调整形状大小时保持原来的比例。这里面有一堆数学问题。
代码大概是这样的:
let Rectangle = {
resizeTopLeft(position, size, preserveAspect, dx, dy) {
// 这里省略 10 行重复的数学代码
},
resizeTopRight(position, size, preserveAspect, dx, dy) {
// 这里省略 10 行重复的数学代码
},
resizeBottomLeft(position, size, preserveAspect, dx, dy) {
// 这里省略 10 行重复的数学代码
},
resizeBottomRight(position, size, preserveAspect, dx, dy) {
// 这里省略 10 行重复的数学代码
},
};
let Oval = {
resizeLeft(position, size, preserveAspect, dx, dy) {
// 这里省略 10 行重复的数学代码
},
resizeRight(position, size, preserveAspect, dx, dy) {
// 这里省略 10 行重复的数学代码
},
resizeTop(position, size, preserveAspect, dx, dy) {
// 这里省略 10 行重复的数学代码
},
resizeBottom(position, size, preserveAspect, dx, dy) {
// 这里省略 10 行重复的数学代码
},
};
let Header = {
resizeLeft(position, size, preserveAspect, dx, dy) {
// 这里省略 10 行重复的数学代码
},
resizeRight(position, size, preserveAspect, dx, dy) {
// 这里省略 10 行重复的数学代码
},
}
let TextBlock = {
resizeTopLeft(position, size, preserveAspect, dx, dy) {
// 这里省略 10 行重复的数学代码
},
resizeTopRight(position, size, preserveAspect, dx, dy) {
// 这里省略 10 行重复的数学代码
},
resizeBottomLeft(position, size, preserveAspect, dx, dy) {
// 这里省略 10 行重复的数学代码
},
resizeBottomRight(position, size, preserveAspect, dx, dy) {
// 这里省略 10 行重复的数学代码
},
};
这些重复出现的数学运算让我心烦。一点都不整洁。
大多数重复代码都发生在相似的方向上。例如,Oval.resizeLeft()
和 Header.resizeLeft()
的代码类似。因为它们都要处理左边的拖拽操作。
另外,同一形状的不同方法也有相似之处。例如,Oval.resizeLeft()
和 Oval
的其他方法也有相似之处。因为它们的处理对象都是椭圆。矩形、Header
和 TextBlock(文本块)
也有重复的地方,因为文本块也是矩形。
我想到了一个好主意。我们可以通过以下代码分组来消除所有的重复:
let Directions = {
top(...) {
// 这里省略 5 行独特的数学代码
},
left(...) {
// 这里省略 5 行独特的数学代码
},
bottom(...) {
// 这里省略 5 行独特的数学代码
},
right(...) {
// 这里省略 5 行独特的数学代码
},
};
let Shapes = {
Oval(...) {
// 这里省略 5 行独特的数学代码
},
Rectangle(...) {
// 这里省略 5 行独特的数学代码
},
}
然后组合它们:
let {top, bottom, left, right} = Directions;
function createHandle(directions) {
// 这里省略20行代码
}
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行代码
}
let Rectangle = createBox(Shapes.Rectangle, fourCorners);
let Oval = createBox(Shapes.Oval, fourSides);
let Header = createBox(Shapes.Rectangle, twoSides);
let TextBox = createBox(Shapes.Rectangle, fourCorners);
代码量减少了一半,完全没有重复代码!很整洁。如果想要在某个特定方向或针对某个特定形状做修改,只需要修改一个对应的方法,而不需要修改每个形状的对应的方法或者某个形状所有的方法。
夜已经很深了。我向 master 分支提交了我重构后的代码,然后就上床睡觉,我为自己解决了同事杂乱的代码而感到自豪。
第二天早晨
...没有发生我预期的剧情(为什么没人夸我)。
老板找我进行了一对一的谈话,他们(老板和同事)很礼貌的要求我撤销我提交的代码更改。我惊呆了。旧代码一团糟,我的代码多整洁!
我勉强答应了,但是过了几年我才发现他们是对的。
一个必经阶段
痴迷于“整洁的代码”和删除重复代码是我们很多人都会经历的一个阶段。当我们对自己的代码不自信时,很容易将自我价值和职业自豪感与一些可以衡量的东西关联起来。比如,一套严格的 lint 规则,一个命名模式,一个文件结构,消除重复等等。
译者注:Lint 指一些列静态代码分析工具,能够发现代码中潜在的缺陷和质量问题。
你不能自动删除重复代码,但通过实践会变得越来越容易。你通常可以分辨每次更改后代码是多了还是少了。因此,删除重复代码感觉就像提高了某些和代码相关的客观指标。更糟糕的是,它与我们的认同感交织在一起:“我就是那种编写简洁代码的人”。这种认同感和任何形式的自我欺骗一样强大。
一旦我们学会了如何创建抽象,就很容易对这种能力产生很高的期望,并且每当我们看到重复代码时就会凭空想象出抽象。写几年代码后,我们发现到处都是抽象——抽象就是我们新的超能力。如果有人告诉我们抽象是一种美德,我们就会全盘接受,并且会批评那些不崇尚“整洁”的人。
现在我发现我之前的“重构”在两方面做的很糟糕:
- 首先,我没有和写代码的人沟通过。没有了解他们的想法和意图就自己重写了代码并提交。即使我提交的代码是一种进步(我现在不信了),这也是种糟糕的方式。一个健康的工程团队需要不断建立信任。在没有沟通的情况下重写队友的代码,极大的降低你在代码库中和队友有效协作的能力。
- 其次,任何事都有代价。我的代码减少了重复代码,但牺牲了应对需要变更的能力,明显不划算。例如,我们后续需要为不同的形状上不同的句柄添加很多特殊的情况和行为。我的抽象代码必须变得复杂好几倍才能应对这种需求,而在最初“混乱”的版本中,这种更改就像切蛋糕一样简单。
我是在说你必须写“脏”的代码吗?不,我是在建议你认真思考,当我们讨论“整洁”和“脏”时,我们到底是在讨论什么。你有反抗的感觉吗?正义?美丽?优雅?你确定你能列出和这些品质相对应的具体工程成果吗?它们究竟佮如何影响了我们编写和修改代码的方式?
我之前确实没有深入思考过这些东西。我对代码的外表思考了很多——但没有考虑它是如何在一群糊涂的人手中演变出来的。
写代码就像一段旅程。想想你从第一行代码到现在已经走了多远。我理解第一次看到如何提取一个函数或重构一个类可以简化复杂的代码时是多么有趣。如果你对自己的手艺感到自豪,追求诱人的整洁代码。你可以逗留一段时间。
但是不要止步于此。不要做一个整洁代码的狂热分子。整洁的代码不是目标。整洁的代码只是我们处理极其复杂的系统时的一种尝试。当你不确定更改将对代码库造成什么样的影响时,整洁的代码风格是一种防御机制,指导我们在未知的海域航行。
让整洁代码风格指导你。该忘记的时候就忘记吧。
译者后记:翻译完这篇文章,发现原作者的思维有点跳跃。从一次擅改代码被打脸,到后面关于整洁代码一大段思考,中间过了几年时间,这几年时间作者肯定还经历很多其他事情,这些事情加在一起才能得出本文后面一大段思考和结论。不知道你有没有类似的经历和感悟,欢迎讨论。
如果习惯在微信公众号上阅读,欢迎关注我的公众号:不只是Python 以后会继续在开源中国和微信公众号上同步更新文章
来源:oschina
链接:https://my.oschina.net/u/2473171/blog/3160107