原文链接:
作者:
那是一个深夜。
我的同事刚刚检查了他们写了一周的代码。我们正在开发一个图形编辑器画布,他们实现了通过拖动矩形和椭圆边缘的小手柄来调整其大小的功能。
代码起作用了。
但这是重复的。每个形状(如矩形或椭圆形)都有一组不同的手柄,向不同方向拖动每个手柄会以不同的方式影响形状的位置和大小。如果用户按住 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
方法有相似之处。这是因为它们都处理椭圆形。Header 和 TextBlock 之间 Rectangle 也有一些重复,因为文本块是矩形。
我有个主意。
我们可以通过像这样对代码进行分组来 删除所有重复项 :
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 规则、命名模式、文件结构、缺乏重复。
您无法自动删除重复项,但通过练习 确实 会变得更容易。您通常可以判断每次更改后是少还是多。因此,删除重复感觉就像改进了有关代码的一些客观指标。更糟糕的是,它扰乱了人们的认同感 :“我是那种写整洁代码的人” 。它和任何形式的自欺欺人一样强大。
一旦我们学会了如何创建 ,就很容易提高这种能力,每当我们看到重复的代码时,就会凭空提取抽象。经过几年的编码,我们看到 到处都是 重复——抽象是我们新的超能力。如果有人告诉我们抽象是一种 美德,我们就会吃掉它。我们将开始评判其他人不崇拜“清洁”。
我现在看到我的“重构”在两个方面是一场灾难:
- 首先,我没有和写它的人谈过。我重写了代码并在没有他们输入的情况下签入了它。即使 这是一种改进 (我不再相信),这也是一种糟糕的方式。一个健康的工程团队会不断 建立信任。在没有讨论的情况下重写队友的代码会极大地打击您一起有效协作代码库的能力。
- 其次,没有什么是免费的。我的代码用更改需求的能力来减少重复,这不是一个好的交易。例如,我们后来需要许多特殊情况和行为来处理不同形状上的不同手柄。我的抽象必须变得复杂几倍才能负担得起,而对于原始的“凌乱”版本,这种更改就像蛋糕一样容易。
我是说你应该写“脏”代码吗?不。我建议深入思考你说“整洁”或“肮脏”的意思。你有反抗的感觉吗?义?美?优雅?您有多大把握能说出与这些品质相对应的具体工程成果?它们究竟如何影响代码的编写和 方式?
我当然没有深入思考这些事情。我考虑了很多关于代码 的外观 ,但没有考虑它如何与一群软绵绵的人类 一起进化 。
编码是一段旅程。想想你从第一行代码到现在的位置有多远。我想第一次看到提取函数或重构类如何使复杂的代码变得简单是一种乐趣。如果你对自己的手艺感到自豪,那么追求代码的简洁性是很诱人的。做一会儿。
但不要止步于此。不要做一个整洁的代码狂热者。整洁的代码不是目标。这是试图从我们正在处理的系统的巨大复杂性中找出一些意义。当您还不确定更改将如何影响代码库时,这是一种防御机制,但在未知的海洋中需要指导。
让整洁的代码指导您。 然后放手。