【文章转载】The Wrong Abstraction 错误的抽象

49 阅读7分钟

原文作者:Sandi Metz

原文链接: sandimetz.com/blog/2016/1…

I've been thinking about the consequences of the "wrong abstraction." My RailsConf 2014 "all the little things" talk included a section where I asserted:
我一直在思考“错误抽象”的后果。我在 RailsConf 2014 上的“ 所有小事 ”演讲包括一个部分, 我断言 

duplication is far cheaper than the wrong abstraction
复制比错误的抽象便宜得多

And in the summary, I went on to advise:
在总结中, 我继续建议 

prefer duplication over the wrong abstraction
宁愿重复也不愿采用错误的抽象

This small section of a much bigger talk invoked a surprisingly strong reaction. A few folks suggested that I had lost my mind, but many more expressed sentiments along the lines of:
一场规模大得多的演讲中的这一小部分引起了令人惊讶的强烈反应。有些人认为我失去了理智,但更多的人表达了这样的情绪:

This, a million times this! "@BonzoESC: "Duplication is far cheaper than the wrong abstraction" @sandimetz @rbonales pic.twitter.com/3qMI0waqWb"
这,一百万倍!“@BonzoESC:”复制比错误的抽象便宜得多“@sandimetz@rbonalespic.twitter.com/3qMI0waqWb

— 41 shades of blue (@pims) March 7, 2014
— 41 种蓝色 (@pims) 三月 7, 2014

The strength of the reaction made me realize just how widespread and intractable the "wrong abstraction" problem is. I started asking questions and came to see the following pattern:
反应的强度让我意识到“错误的抽象”问题是多么普遍和棘手。我开始提问,看到了以下模式:

  1. Programmer A sees duplication.
    程序员 A 看到重复。

  2. Programmer A extracts duplication and gives it a name.
    程序员 A 提取重复并命名。

    This creates a new abstraction. It could be a new method, or perhaps even a new class.
    这会创建一个新的抽象。它可以是一种新方法,甚至可能是一个新类。

  3. Programmer A replaces the duplication with the new abstraction.
    程序员 A 用新的抽象替换重复。

    Ah, the code is perfect. Programmer A trots happily away.
    啊,代码很完美。程序员 A 高兴地小跑着走了。

  4. Time passes.  时间流逝。

  5. A new requirement appears for which the current abstraction is almost perfect.
    出现了一个新的需求,当前的抽象几乎是完美的

  6. Programmer B gets tasked to implement this requirement.
    程序员 B 的任务是实现此要求。

    Programmer B feels honor-bound to retain the existing abstraction, but since isn't exactly the same for every case, they alter the code to take a parameter, and then add logic to conditionally do the right thing based on the value of that parameter.
    程序员 B 觉得保留现有抽象是有义务的,但由于并非每种情况都完全相同,因此他们更改代码以获取参数,然后添加逻辑以根据该参数的值有条件地执行正确的作。

    What was once a universal abstraction now behaves differently for different cases.
    曾经的通用抽象现在在不同情况下表现不同。

  7. Another new requirement arrives.
    另一个新要求到来了。
    Programmer X.  程序员 X.
    Another additional parameter.
    另一个附加参数。
    Another new conditional.  另一个新的条件。
    Loop until code becomes incomprehensible.
    循环直到代码变得难以理解。

  8. You appear in the story about here, and your life takes a dramatic turn for the worse.
    你出现在关于这里的故事中,你的生活发生了戏剧性的转变。

Existing code exerts a powerful influence. Its very presence argues that it is both correct and necessary. We know that code represents effort expended, and we are very motivated to preserve the value of this effort. And, unfortunately, the sad truth is that the more complicated and incomprehensible the code, i.e. the deeper the investment in creating it, the more we feel pressure to retain it (the "sunk cost fallacy"). It's as if our unconscious tell us "Goodness, that's so confusing, it must have taken ages to get right. Surely it's really, really important. It would be a sin to let all that effort go to waste."
现有代码具有强大的影响力。它的存在本身就表明它既正确又必要。我们知道代码代表了所花费的努力,我们非常有动力保持这种努力的价值。而且,不幸的是,可悲的事实是,代码越复杂和难以理解,即创建它的投资越深,我们就越感受到保留它的压力(“ 沉没成本谬误 ”)。就好像我们的无意识告诉我们“天哪,这太令人困惑了,一定花了很长时间才弄好。当然,这真的非常重要。让所有的努力白费都是一种罪过。

When you appear in this story in step 8 above, this pressure may compel you to proceed forward, that is, to implement the new requirement by changing the existing code. Attempting to do so, however, is brutal. The code no longer represents a single, common abstraction, but has instead become a condition-laden procedure which interleaves a number of vaguely associated ideas. It is hard to understand and easy to break.
当您出现在上述第 8 步的这个故事中时,这种压力可能会迫使您继续前进,即通过更改现有代码来实现新要求。然而,试图这样做是残酷的。代码不再代表单一的、通用的抽象,而是变成了一个充满条件的过程,它交错了许多模糊的关联想法。它很难理解,也很容易打破。

If you find yourself in this situation, resist being driven by sunk costs. When dealing with the wrong abstraction, the fastest way forward is back. Do the following:
如果您发现自己处于这种情况,请避免被沉没成本所驱使。在处理错误的抽象时, 最快的前进方式是返回 。请执行以下作:

  1. Re-introduce duplication by inlining the abstracted code back into every caller.
    通过将抽象代码内联回每个调用者来重新引入重复。
  2. Within each caller, use the parameters being passed to determine the subset of the inlined code that this specific caller executes.
    在每个调用方中,使用传递的参数来确定此特定调用方执行的内联代码的子集。
  3. Delete the bits that aren't needed for this particular caller.
    删除此特定调用方不需要的位。

This removes both the abstraction and the conditionals, and reduces each caller to only the code it needs. When you rewind decisions in this way, it's common to find that although each caller ostensibly invoked a shared abstraction, the code they were running was fairly unique. Once you completely remove the old abstraction you can start anew, re-isolating duplication and re-extracting abstractions.
这删除了抽象条件,并将每个调用者减少到它只需要的代码。当您以这种方式倒回决策时,通常会发现,尽管每个调用方表面上调用了共享抽象,但他们运行的代码却相当独特。一旦你完全删除了旧的抽象,你就可以重新开始,重新隔离重复并重新提取抽象。

I've seen problems where folks were trying valiantly to move forward with the wrong abstraction, but having very little success. Adding new features was incredibly hard, and each success further complicated the code, which made adding the next feature even harder. When they altered their point of view from "I must preserve our investment in this code" to "This code made sense for a while, but perhaps we've learned all we can from it," and gave themselves permission to re-think their abstractions in light of current requirements, everything got easier. Once they inlined the code, the path forward became obvious, and adding new features become faster and easier.
我见过这样的问题:人们勇敢地试图推进错误的抽象,但收效甚微。添加新功能非常困难,每次成功都会使代码更加复杂,这使得添加下一个功能变得更加困难。当他们将观点从“我必须保留我们对这段代码的投资”转变为“这个代码有一段时间有意义,但也许我们已经从中学到了一切”,并允许自己根据当前需求重新思考他们的抽象时,一切都变得更容易了。一旦他们内联了代码,前进的道路就变得显而易见,添加新功能变得更快、更容易。

The moral of this story? Don't get trapped by the sunk cost fallacy. If you find yourself passing parameters and adding conditional paths through shared code, the abstraction is incorrect. It may have been right to begin with, but that day has passed. Once an abstraction is proved wrong the best strategy is to re-introduce duplication and let it show you what's right. Although it occasionally makes sense to accumulate a few conditionals to gain insight into what's going on, you'll suffer less pain if you abandon the wrong abstraction sooner rather than later.
这个故事的寓意是什么?不要被沉没成本谬误所困。如果您发现自己通过共享代码传递参数并添加条件路径,则抽象不正确。一开始可能是对的,但那一天已经过去了。一旦抽象被证明是错误的,最好的策略是重新引入重复,让它告诉你什么是正确的。尽管有时积累一些条件来深入了解正在发生的事情是有意义的,但如果你尽早放弃错误的抽象,你所遭受的痛苦会更少。

When the abstraction is wrong, the fastest way forward is back. This is not retreat, it's advance in a better direction. Do it. You'll improve your own life, and the lives of all who follow.
当抽象错误时,最快的前进方式就是返回。这不是撤退,而是朝着更好的方向前进。做吧。您将改善自己的生活,以及所有追随者的生活。