再见了,干净整洁的代码

这是一个深夜。

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

代码成功了。

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

余下全文(1/3)
分享这篇文章:

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注