7

再见了,干净整洁的代码

 9 months ago
source link: https://www.techug.com/post/goodbye-clean-code/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

再见了,干净整洁的代码

这是一个深夜。

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

代码成功了。

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK