那是一个夜深。
我的共事刚刚提交了他们一周编写的代码。咱们正在开采一个图形裁剪器的画布,他们杀青了通过拖动角落的小手柄,来调度方式(如矩形和椭圆)的大小的功能。
代码是灵验的。
然则,它有些重迭。每种方式(如矩形或椭圆)都有一组不同的手柄,每个手柄在不同的方朝上拖动,会以不同的方式影响方式的位置和大小。要是用户按住 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;functioncreateHandle(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])];functioncreateBox(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);
代码的总量减半,重迭的部分都备消散了!它是如斯整洁。要是想改变某个场合或方式的步履,咱们不错在一个地方进行修改,而不是在各处更新身手。
还是是夜深了(我有点过于进入)。我将重构代码提交到 master 分支,然后满怀自重地上床休眠,因为我解开了共事紊乱的代码。
第二天早上
...并不像我预期的那样。
我的雇主邀请我进行一双一的聊天,他礼貌地条件我取销昨夜的改革。我感到恐惧,旧的代码一团糟,而我的代码整洁!
我拼凑甘愿了,但我花了好几年的时间才看出他们是对的。
这仅仅一个阶段
烂醉于“整洁的代码”和放手重迭是咱们许多东谈主都会履历的阶段。当咱们对我方的代码莫得信心时,咱们很容易将自我价值和劳动随性交付在一些不错掂量的东西上。一套严格的 lint 轨则,一个定名决策,一个文献结构,莫得重迭代码。
你不可自动放手重迭,但跟着引申的增多,这如实会变得更容易。你常常不错看出每次改革后重迭的部分是增多如故减少。因此,放手重迭嗅觉就像是改善了代码的某种客不雅目的。更糟糕的是,它侵犯了东谈主们的身份招供感:_“我即是那种写整洁代码的东谈主”_。这就像任何种类的自我哄骗相通有劲。
一朝咱们学会怎么创建详尽,咱们就会很容易对这种本领产生依赖,每当看到重迭的代码,就会杜撰漠视详尽。编程几年后,咱们看到重迭无处不在——详尽是咱们的新超本领。要是有东谈主告诉咱们详尽是一种良习,咱们会全盘剿袭,致使会启动评判其他东谈主为什么不发达“整洁”。
我当今澄澈我的“重构”在两个方面都是恶运性的:
当先,我莫得和写这段代码的东谈主交谈。我重写了代码,莫得他们的参与就提交了。即使这是一个修订(我当今不再这样合计),这亦然一个糟糕的作念法。一个健康的工程团队需要不断拔擢信任。在莫得商议的情况下重写你共事的代码,会严重打击你们在代码库上灵验合作的本领。其次,莫得什么是免费的。我的代码以减少重迭为代价,葬送了改变需求的本领,这是不值得的。举例,咱们自后需要为不同方式的不同手柄添加许多特殊情况和步履。我的详尽需要变得愈加复杂才能杀青这些,而在原始的“紊乱”版块中,这样的改革则举手之劳。
我是在说应该写“脏”代码吗?不是。我建议真切想考你说“整洁”或“脏”时的含义。有一种反感的嗅觉吗?正义感?好意思感?优雅感?你有多笃定不错列出对应于这些品性的具体工程效果?它们怎么的确地影响代码的编写和修改?
我折服莫得真切想考过这些问题。我谈判了好多对于代码看起来怎么 —— 但并莫得谈判它如安在一个由复杂多变的东谈主构成的团队中发展。
编程是一场旅程。想想你从编写第一滑代码到当今走过的路程。我想,第一次看到索要函数或重构类不错让复杂的代码变得粗浅,一定是一种欣喜的体验。要是你对我方的时间感到自重,那么就会很容易追求代码的整洁性。那就去追求吧。
但不要留步于此。不要成为一个整洁代码的狂热者。整洁的代码不是终极观点,它是咱们试图意会咱们所搞定的重大复杂系统的一种尝试。当你还不笃定一个改革会怎么影响代码库,但你在未知的肮脏中需要指引时,它是一种驻扎机制。
让整洁的代码指点你,然后截至。
原文:Dan Abramov - 2020.01.11