再见,干净的代码

不换
2023-12-11 01:59
阅读9.63分钟

声明:翻译出自 某PT X.5,原文出自:https://overreacted.io/goodbye-clean-code/

作者:dan abramov

这是一个深夜。

我的同事刚刚提交了他们整个星期一直在编写的代码。我们正在开发一个图形编辑器画布,他们实现了通过拖动边缘的小手柄来调整矩形和椭圆等形状的大小。

代码运行正常。

但它是重复的。每个形状(如矩形或椭圆)都有一组不同的手柄,拖动每个手柄在不同方向上会以不同方式影响形状的位置和大小。如果用户按住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);
 

代码只有总大小的一半,并且完全消除了重复!太干净了。如果我们想要改变特定方向或形状的行为,我们可以在一个地方完成,而不是到处更新方法。

已经很晚了(我太过分了)。我将我的重构提交到主分支并上床睡觉,为自己整理同事混乱代码的能力感到骄傲。

第二天早上

...没有按预期进行。

我的老板邀请我进行一对一的交谈,在那里他们礼貌地要求我撤销我的更改。我感到震惊。旧代码很混乱,而我的代码是干净的!

我勉强同意了,但花了我好几年才明白他们是正确的。

这是一个阶段

沉迷于“清洁代码”和消除重复是我们许多人经历的一个阶段。当我们对自己的代码不太有信心时,很容易将自我价值感和职业自豪感与可以衡量的东西联系在一起。严格的lint规则集、命名模式、文件结构以及缺乏重复。

你无法自动消除重复,但通过实践它会变得更容易。通常情况下,您可以根据每次更改后是否减少或增加来判断是否存在较少或较多的重复。因此,消除重复就像改善代码中某个客观指标一样。更糟糕的是,它会干扰人们对身份认同的感知:“我是那种写出整洁代码的人”。这与任何形式的自欺欺人一样强大。

一旦我们学会了如何创建抽象,就很容易因为这种能力而陷入沉迷,并且在看到重复的代码时随意地创造出抽象。编程几年后,我们会发现重复无处不在——而抽象成为了我们的新超能力。如果有人告诉我们抽象是一种美德,我们会全盘接受。并且开始对其他人不崇尚“整洁”而进行评判。

我现在明白,我的“重构”在两个方面都是一场灾难:

  • 首先,我没有和写这段代码的人交谈。我重写了代码并在没有他们参与的情况下进行了检查。即使这是一个改进(我不再相信),这种做法也是非常糟糕的。一个健康的工程团队应该不断建立信任。在没有讨论的情况下重写你同事的代码对于你们共同有效地合作编码基础设施来说是一个巨大打击。

  • 其次,没有什么是免费的。我的代码以减少重复为代价交换了改变需求的能力,并且这并不是一个好的交易。例如,我们后来需要许多特殊情况和不同形状上不同句柄的行为。我的抽象化将变得更加复杂数倍才能应对这些情况,而原始的“混乱”版本则可以轻松地进行此类更改。

我是说你应该写“肮脏”的代码吗?不是的。我建议你深入思考当你说“干净”或者“肮脏”时,你到底意味着什么。你有一种反叛的感觉吗?正义感?美感?优雅感?你对于能够命名与这些特质相对应的具体工程结果有多确定呢?它们究竟如何影响代码编写和修改的方式呢?

我确实没有深入思考这些事情中的任何一个。我对代码的外观进行了很多思考,但并没有考虑它如何与一群有血有肉的人一起发展。

编码是一段旅程。想想你从第一行代码走到现在的距离有多远。我估计第一次看到提取函数或重构类可以使复杂的代码变得简单,肯定感到很开心。如果你对自己的技艺感到骄傲,就会有诱惑去追求代码的整洁性。试着这样做一段时间吧。

但不要止步于此。不要成为一个痴迷于清洁代码的人。清洁代码并非目标,而是试图从我们所处理的庞大复杂系统中找出一些意义的尝试。当你还不确定某个变化会如何影响代码库时,它是一种防御机制,帮助你在未知领域中寻求指导。

让整洁的代码指引你。放手去做吧。

本文内容来源于网络,不代表当前发布者的观点,在尊重原作者著作版权的前提下翻译、分享,不携带任何其它批判、盈利的性质,理性阅读,理性思考。

不换
不换
中国.上海