作者|DanAbramov
译者|弯月,责编|屠敏
以下为译文:
深夜。
同事刚刚提交了他们编写了一整周的代码。我们在编写一个图形编辑器,他们实现了通过拖动矩形和椭圆的边缘来调整图形大小的功能。
他们的代码运行良好。
但代码中有重复。每种形状(如矩形或椭圆形)都有一组不同的拖动点,而且沿不同方向拖动也会以不同的方式影响形状的位置和大小。如果用户按住Shift键,我们还需要在调整大小时保持比例。这其中涉及很多数学运算。
他们的代码如下:
letRectangle={
resizeTopLeft(position,size,preserveAspect,dx,dy){
//10repetitivelinesofmath
},
resizeTopRight(position,size,preserveAspect,dx,dy){
//10repetitivelinesofmath
},
resizeBottomLeft(position,size,preserveAspect,dx,dy){
//10repetitivelinesofmath
},
resizeBottomRight(position,size,preserveAspect,dx,dy){
//10repetitivelinesofmath
},
};
letOval={
resizeLeft(position,size,preserveAspect,dx,dy){
//10repetitivelinesofmath
},
resizeRight(position,size,preserveAspect,dx,dy){
//10repetitivelinesofmath
},
resizeTop(position,size,preserveAspect,dx,dy){
//10repetitivelinesofmath
},
resizeBottom(position,size,preserveAspect,dx,dy){
//10repetitivelinesofmath
},
};
letHeader={
resizeLeft(position,size,preserveAspect,dx,dy){
//10repetitivelinesofmath
},
resizeRight(position,size,preserveAspect,dx,dy){
//10repetitivelinesofmath
},
}
letTextBlock={
resizeTopLeft(position,size,preserveAspect,dx,dy){
//10repetitivelinesofmath
},
resizeTopRight(position,size,preserveAspect,dx,dy){
//10repetitivelinesofmath
},
resizeBottomLeft(position,size,preserveAspect,dx,dy){
//10repetitivelinesofmath
},
resizeBottomRight(position,size,preserveAspect,dx,dy){
//10repetitivelinesofmath
},
};
这些重复的数学计算看着真碍眼。
这种代码不整洁。
大部分重复都发生在相似方向的拖动。例如,Oval.resizeLeft与Header.resizeLeft很相似。这是因为这两个方法都是向左拖动。
另一个相似之处出现在同一个形状的方法之间。例如,Oval.resizeLeft与Oval的其他方法都很相似。这是因为它们都是处理椭圆的方法。Rectangle、Header和TextBlock之间也有一些重复,因为文本块都是矩形的。
突然,我想到了一个好主意。
我们可以像下面这样将代码分组,然后就可以删除所有的重复代码:
letDirections={
top(...){
//5uniquelinesofmath
},
left(...){
//5uniquelinesofmath
},
bottom(...){
//5uniquelinesofmath
},
right(...){
//5uniquelinesofmath
},
};
letShapes={
Oval(...){
//5uniquelinesofmath
},
Rectangle(...){
//5uniquelinesofmath
},
}
然后是它们的行为:
let{top,bottom,left,right}=Directions;
functioncreateHandle(directions){
//20linesofcode
}
letfourCorners=[
createHandle([top,left]),
createHandle([top,right]),
createHandle([bottom,left]),
createHandle([bottom,right]),
];
letfourSides=[
createHandle([top]),
createHandle([left]),
createHandle([right]),
createHandle([bottom]),
];
lettwoSides=[
createHandle([left]),
createHandle([right]),
];
functioncreateBox(shape,handles){
//20linesofcode
}
letRectangle=createBox(Shapes.Rectangle,fourCorners);
letOval=createBox(Shapes.Oval,fourSides);
letHeader=createBox(Shapes.Rectangle,twoSides);
letTextBox=createBox(Shapes.Rectangle,fourCorners);
改完以后,代码行数只有原来的一半,重复完全消失了!好整洁的代码!如果我们想更改某个方向的行为或某个形状的行为,则只需要修改一个地方即可,不像原来的代码需要修改所有类似的方法。
修改完代码后我才发现已经很晚了,我完全忘记了时间。提交完重构后的代码后,我心满意足地上床睡觉了,内心因为帮助同事整理代码而充满了自豪。
第二天早上
情况与我预想的完全不一样。
老板找我去谈话,他们很客气地要求我把代码改回去。我吓傻了。原来的代码一团糟,而我的代码非常整洁!
我很不情愿地答应了,但是直至几年后,我才幡然醒悟:他们是正确的。
这是每个人都经历过的一个阶段
相信每位痴迷于“整洁的代码”,希望消除重复的人都经历过这样一个阶段。当我们对代码不自信时,很容易将自我的价值感和专业自豪感附加到某种可以衡量的事物上:一组严格的lint规则、命名架构、文件结构、没有重复代码。
你无法自动删除重复代码,但是通过练习确实很容易实现。通常,在修改完代码后,你很容易看出代码行数是增加了还是减少了。于是,消灭重复代码就成了改善代码的客观指标。更糟糕的是,它混淆了人们的认同感:“我就是那种编写整洁代码的人”。这是一种强大的自我欺骗。
在我们学习了如何创建抽象后,很容易痴迷于这种方式,每当看到重复的代码时,就忍不住想提取抽象。在积累了若干年的编程经验后,我们看到重复无处不在,而抽象就成了“超能力”。如果有人告诉我们抽象是一种美德,我们就会全盘接受。而且我们还会开始评判其他人是否不崇尚“整洁”。
回头再看当年我的“重构”完全是一场灾难,原因主要有两个方面:
首先,我没有和编写这段代码的人沟通。我重写了代码,并提交了代码,而他们却毫不知情。即便这是一种改进(虽然我不觉得),但这也是一种很糟糕的解决问题的方法。一个健康的工程团队需要不断地加强信任。不经讨论就重写队友的代码,这会重重地打击团队的协作能力。
其次,天底下没有免费的午餐。我的代码虽然消除了重复,却以改变需求作为代价,而且这笔买卖并不划算。例如,之后我们需要处理针对不同的形状的不同操作,而且还有很多特殊情况。在我的抽象代码中实现这样的变更必须付出数倍的努力,而在原来那个“凌乱”版本中进行此类修改就非常容易。
那么,你应该编写“脏乱”的代码吗?当然不应该。我建议你仔细思考“整洁”和“脏乱”的含义。你有什么感觉?革命?正义?美丽?优雅?你如何确定这种质量会带来怎样的结果?而它们又会对编写和修改代码的方式造成怎样的影响?
我相信很多人都没有仔细思考过这些问题。我曾经深思过代码的外观问题,但是没有考虑过为什么他们会采用这种方式编写这些代码。
编程是一次旅行。想一想从第一行代码到如今,你走过了多少崎岖。当第一次通过提取函数或重构类将复杂的代码简单化时,我的内心也非常高兴。如果你对自己的代码感到自豪,那么也很容易抵制不住诱惑追求代码的整洁性。你可以进行一些尝试。
但你所做的努力不能仅限于此。不要成为一个执着于追求代码整洁的人。整洁的代码不是目标。这只是我们为降低系统巨大的复杂性而做的尝试。如果你不确定你的修改对代码库会造成怎样的影响,但你需要从千头万绪找到一个切入点,那么“整洁的代码”可以成为你的方向指导,但你必须学会及时放手。
原文:https://overreacted.io/goodbye-clean-code/
本文为CSDN翻译,转载请注明来源出处。