再见了,干净整洁的代码
这是一个深夜。
我的同事刚刚提交了他们写了一周的代码。我们正在开发一个图形编辑器画布,他们实现了通过拖动矩形和椭圆边缘的小手柄来调整其大小的功能。
代码成功了。
但它是重复的。每个形状(如矩形或椭圆形)都有一组不同的手柄,向不同方向拖动每个手柄都会以不同的方式影响形状的位置和大小。如果用户按住 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() 与其他 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);
代码的总量减少了一半,重复的代码也完全消失了!如此简洁。如果我们想改变某个特定方向或形状的行为,我们可以在一个地方完成,而不用到处更新代码。
夜已经深了(我走神了)。我把我的重构结果报告给了主控程序,然后就上床睡觉了。
第二天早上
……并没有像预期的那样。
我的老板邀请我进行了一次一对一的谈话,他们礼貌地要求我还原我的改动。我大吃一惊。旧代码一团糟,而我的代码很干净!
我勉为其难地答应了,但过了好几年才发现他们是对的。
这是一个阶段
执着于 “简洁代码 “和删除重复代码是我们很多人都会经历的一个阶段。当我们对自己的代码不自信时,很容易将自我价值感和职业自豪感寄托在一些可以衡量的东西上。一套严格的检查规则、一个命名模式、一个文件结构、一个没有重复的代码。
你无法自动删除重复内容,但通过练习确实会变得更容易。通常每次修改后,你都能判断出重复是少了还是多了。因此,删除重复就像是在改善代码的某些客观指标。更糟糕的是,它会扰乱人们的认同感:”我是那种写干净代码的人”。这与任何一种自欺欺人的行为一样强大。
一旦我们学会了如何创建抽象,就很容易被这种能力所吸引,每当我们看到重复的代码时,就会凭空产生抽象。经过几年的编码工作,我们会发现重复的代码无处不在,而抽象就是我们新的超级能力。如果有人告诉我们抽象是一种美德,我们就会吃这一套。我们会开始指责其他人不崇拜 “整洁”。
现在我明白了,我的 “重构 “在两个方面都是一场灾难:
- 首先,我没有和写代码的人谈过。我重写了代码,并在没有他们意见的情况下进行了检查。即使这是一种改进(我不再相信这一点了),这也是一种糟糕的方式。一个健康的工程团队需要不断建立信任。不经讨论就改写队友的代码,会极大地打击大家在代码库上有效合作的能力。
- 其次,没有什么是免费的。我的代码用改变需求的能力来换取减少重复,这不是一个好的交易。例如,我们后来需要为不同形状的不同手柄设置许多特殊情况和行为。要做到这一点,我的抽象必须变得更加复杂几倍,而在最初的 “混乱 “版本中,这种变化就像蛋糕一样容易。
我是说你应该写 “丑陋 “的代码吗?我建议你深入思考一下你所说的 “简洁”或 “丑陋 “是什么意思。你会有一种反抗的感觉吗?正义感?美?优雅?你能说出与这些品质相对应的具体工程结果吗?它们究竟如何影响代码的编写和修改方式?
我肯定没有深入思考过这些问题。我考虑了很多代码的外观,但没有考虑它是如何与一队有个性的人共同演进的。
编码是一段旅程。想想你从写下第一行代码到现在走了多远。我认为,第一次看到提取一个函数或重构一个类如何让复杂的代码变得简单,是一件非常快乐的事情。如果你对自己的手艺感到自豪,那么追求代码的简洁是很有诱惑力的。先这样做一段时间吧。
但不要就此止步。不要成为简洁代码的狂热分子。简洁代码不是目标。它是一种尝试,试图从我们正在处理的系统的巨大复杂性中找出一些意义。它是一种防御机制,当你还不确定一项变更会对代码库产生怎样的影响,但你需要在一片未知的海洋中得到指引。
让简洁的代码引导你。然后放手。
本文文字及图片出自 Goodbye, Clean Code
你也许感兴趣的:
- 【外评】好的重构与不好的重构
- 【译文】高风险重构
- 【译文】如何坚持长期重构
- 【译文】关于重构的十条戒律
- 十年“屎山”终重构,但 QQ选用了微软 Teams 放弃的 Electron
- 重构的重构 – 《重构》第二版导读
- 代码重构技巧
- 代码重构的那些坑和实战经验
- [外文翻译]Martin Fowler:机会主义式的代码重构
- 代码审查与重构的5个层次
你对本文的反应是: