【C1-Listening】Bad Smells in Code-代码里的坏味道

【C1-Listening】Bad Smells in Code-代码里的坏味道

Chapter 3: Bad Smells in Code
“If it stinks, change it.”

— Grandma Beck, discussing child raising philosophy By now you have a good idea of how refactoring works. But just because you know how doesn’t mean you know when. Deciding when to start refactoring (and when to stop) is just as important to refactoring as knowing how to operate the mechanics of a refactoring.

Now comes the dilemma. It is easy to explain to you how to delete an instance variable or create a hierarchy. These are simple matters. Trying to explain when you should do these things is not so cut-anddried. Rather than appealing to some vague notion of programming aesthetics (which frankly is what we consultants usually do) I wanted something a bit more solid.

I was mulling over this tricky issue when I visited Kent in Zurich. Perhaps he was under the influence of the odors of his new born daughter at the time, but he had come up with the notion describing the “when” of refactoring in terms of smells. “Smells,” you say, “and that is supposed to be better than vague aesthetics?” Well, yes. We look at lots of code, written by projects that span the gamut from wildly successful to nearly dead. And in doing so, we have learned to look for certain structures in the code that suggest (sometimes they scream for) the possibility of refactoring. (We are switching over to “we” in this chapter to reflect the fact that Kent and I wrote this chapter jointly. You can tell the difference because the funny jokes are mine and the others are his.)

One thing we won’t try to do here is give you precise criteria for when a refactoring is overdue. In our experience there is no set of metrics that rival informed human intuition. What we will do is give you indications that there is trouble that could be solved by a refactoring. You will have to develop your own sense of how many instance variables is too many instance variables and how many lines of code in a method is too many lines.

Duplicated code

Number one on the stink parade is duplicated code. If you see the same code structure in more than one place, you can be sure that your program will be better if you find a way to unify them.

The simplest duplicated code problem is when you have the same expression in two methods of the same class. Then all you have to do is Extract Method (114) and invoke the code from both places.

Another common duplication problem is when you have the same expression in two sibling subclasses. You can eliminate this duplication by using Extract Method (114) in both classes, then Pull Up Method (271). If the code is similar but not the same, then you need to use Extract Method (114) to separate the similar bits from the different bits.

You may then find you can use Form Template Method (289). If the methods do the same thing using a different algorithm, you can choose the clearer of the two algorithms and use Substitute Algorithm (132).

If you have duplicated code in two unrelated classes, consider using Extract Component (166) in one class, and then use the new component in the other. Another possibility is that the method really belongs only in one of the classes and should be invoked by the other class, or that the method belongs in a third class that should be referred to by both of the original classes. You have to decide where the method makes sense, and ensure it is there and nowhere else.

Long method

The object programs that live best and longest are those with short methods. Programmers new to objects often get the feeling that no computation ever takes place, that object programs are endless sequences of delegation. When you have lived with a program for a few years, however, you learn just how valuable all those little methods are. All of the pay-offs of indirection- explanation, sharing, and choosing- are supported by little methods.

Since the early days of programming people have realized that the longer a procedure is, the more difficult it is to understand. Older languages carried an overhead in subroutine calls, which deterred people from small method, modern OO languages have pretty much eliminated that overhead. There is still an overhead to the reader of the code as you have to switch context to look and see what the sub-procedure does. Development environments that allow you to see two methods at once help to eliminate this; but the real key to making it easy to understand small methods is good naming. If you have a good name for a method you don’t need to look at the body.

The net effect of this is that you should be much more aggressive about decomposing methods. A heuristic we follow is whenever we feel the need to comment something, we instead write a method. Such a method contains the code that was commented, but is named after what the intention of the code is, rather than how it does it. We may do this on a group of lines, or on as small as a single line of code. We will do this even if the method call is longer than the code it replaces, providing the method name explains the purpose of the code. The key here is not the method length, but the semantic distance between what the method does and how it does it.

99% of the time, all you have to shorten a method is Extract Method (114). Find a part of the method that seems to go nicely together and make a new method.

If you have a method with lots of parameters and temporary variables, they get in the way of extracting methods. If you try to use Extract Method, you end up passing so many of the parameters and temporary variables as parameters to the extracted method that the result is scarcely more readable than the original. You can often use Inline Temp (121) to get rid of the temps. Long lists of parameters can be slimmed down with Introduce Parameter Object (247) and Preserve Whole Object (241).

If you’ve tried that, and you still have too many temps and parameters,
then it’s time to get out the heavy artillery: Replace Method with Method Object (130).

How do you identify the clumps of code to extract? A good technique is to look for comments. They often signal this kind of semantic distance.

A block of code with a comment that tells you what it is doing can be replaced by a method whose name is based on the comment.

Even a single line is worth extracting if it needs explanation.

Conditionals and loops also give signs for extractions. Use Decompose Conditional (136) to deal with conditional expressions. With loops extract the loop and the code within the loop into its own method.

Large Class

When a class is trying to do too much, it often shows up as too many instance variables. When a class has too many instance variables, duplicated code cannot be far behind.

You can Extract Component (166) to bundle up a number of the variables.

Choose variables to go together in the component that makes sense with each other. For example, “depositAmount” and “deposit-Currency” are likely to belong together in a component. More generally, common prefixes or suffixes for some subset of the variables in a class suggest the opportunity for a component. If the component makes sense as a subclass you’ll find Extract Subclass (277) often is easier.

However you can only do this if the subset of instance variables that are used do not change during an object’s lifetime.

Sometimes a class will not be using all of its instance variables all of the time. If so, you may be able to Extract Component (166) or Extract Subclass (277) many times.

As with a class with too many instance variables, a class with too much code is prime breeding ground for duplicated code, chaos, and death.

The simplest solution (have we mentioned that we like simple solutions?)

is if you can eliminate redundancy in the class itself. If you have five hundred line methods with lots of code in common, you may be able to turn them into five ten line methods with another ten two line methods extracted from the original.

As with a class with a huge wad of variables, the usual solution for a class with too much code is either to Extract Component (166) or Extract Subclass (277).

Long Parameter List

In our early programming days we were taught to pass everything a routine needed in as parameters. This was understandable because the alternative was global data, and global data is evil and usually painful.

Objects change this situation because if you don’t have something you need, you can always ask another object to get it for you. Thus with objects you don’t pass in everything the method needs, instead you pass enough so that the method can get to everything it needs. A lot of what a method needs is available on method’s host class. Thus in object-oriented programs parameter lists tend to be much smaller than on traditional programs.

This is good because long parameter lists are hard to understand, they get inconsistent and difficult to use, and because you are forever changing them as you need more data. Most changes are removed by passing objects because you are much more likely to just need to make a couple of requests to get at a new piece of data.

Use Replace Parameter with Method (245) when you can get the data in one parameter by making a request of an object you already know about. This object might be a field or it might be another parameter.

Use Preserve Whole Object (241) to take a bunch of data gleaned from an object and replace it with the object itself. If you have several data items with no logical object, then Introduce Parameter Object (247).

There is one important exception to doing these changes. This is when you explicitly do not wish to create a dependency from the called object to the larger object. In those cases unpacking data and sending it along as parameters is reasonable, but pay attention to the pain involved. If the parameter list is too long or changes too often you will need to rethink your dependency structure.

Divergent Change

We structure our software to make change easier, after all software is meant to be soft. So when we make a change we want to be able to jump to a single clear point in the system and make the change.

If you look at a class and say, “Well, I will have to change these three methods every time I get a new database. I have to change these four methods every time there is a new financial instrument.” you likely have a situation where two objects are better than one. Of course you often discover this only after you’ve added a few databases or financial instruments. Any change to handle a variation should change a single class and all the typing in the new class should be expressing the variation.

So for this you identify those things you are changing and use Extract Component (166) to put them all together.

Shotgun Surgery

You whiff this when every time you add some variation, you have to make a lot of little changes to a lot of different classes. When the changes are all over the place, they are hard to find, and it’s easy to miss an important change. In this case you want to use Move Method (160) and Move Field (164) to get all the changes into a single class. If no current class looks like a good candidate, then create one. Often you can use Inline Component (170) to bring a whole bunch of behavior together. You then get a small dose of Divergent Change, but you can easily deal with that.

Feature Envy

The whole point of objects is that they are a packaging technique that packages data together with the processes upon that data. A classic smell is thus a method that seems more interested in another class than the one it’s actually in. The most common focus of the envy is the data.

We’ve lost count of the times we’ve seen a method that invokes half-adozen getting methods on another object in order to calculate some value. Fortunately the cure is obvious, the method clearly wants to be elsewhere so you use Move Method (160) to get it there. Sometimes only part of the method suffers from envy, in that case use Extract Method (114) on the jealous bit and then Move Method (160) to give it a dream home.

Of course not all cases are so cut and dried. Often a method uses features of several classes, so which one should it live with? The heuristic we use is to which class has most of the data and put the method with that data. This is often made easier by using Extract Method (114) to break into methods that go into different places.

Of course there several sophisticated patterns that break this rule.

From the [Gang of Four] Strategy and Visitor immediately leap to mind. Kent’s Self Delegation is another. You do these to combat the Divergent Change smell. The fundamental rule of thumb is to put things together that change together. Data and the behavior that references that data usually change together, but there are exceptions.

When those exceptions occur we move the behavior to keep changes in one place. Strategy and Visitor allow you to change behavior easily because it isolates the small amount of behavior that needs to be overridden, at the cost of further indirection.

Data Clumps

Data items tend to be like children — they enjoy hanging around in groups together. Often you’ll see the same three or four data items together in lots of places: fields in a couple of classes, parameters in many method signatures. Bunches of data that hang around together really ought to be made into their own object. The first step is to look for where they appear as fields. Use Extract Component (166) on the fields to turn them into an object. Then turn your attention to method signatures, using Introduce Parameter Object (247) or Preserve Whole Object (241) to slim them down. The immediate benefit here is that you can shrink a lot of parameter lists and make method calling a lot simpler.

Don’t worry about data clumps that only use some of the fields of the new object. As long as you are replacing a couple or more fields with the new object, you’ll come out ahead.

A good test is to consider deleting one of the data values: if you did this would the others make any sense? If they don’t that’s a sure sign that you have an object that’s dying to be born.

Reducing field lists and parameter lists will certainly remove a few bad smells, but once you have the objects you get the opportunity to make a nice perfume. You can now look for cases of Divergent Change which will suggest behavior that can be moved into your new classes.

Before long these classes will be productive members of society.

Case Statements

One of the most obvious symptoms of object-oriented code is its lack of case (or switch) statements. The problem with case statements is essentially that of duplication. Often you find the same case statement scattered about a program for different purposes. If you add a type to the case you have to find all these case statements and change them. The object-oriented notion of polymorphism gives you an elegant way to deal with this problem.

So most times you see a case statement you should think about polymorphism, the issue is where should the polymorphism occur. Usually case statement will switch on a type code. You will want the method on class that hosts the type code value. So use Extract Method (114) to extract the switch statement and then Move Method (160) to get it onto the class where the polymorphism is needed. At that point you have to decide whether to Replace Type Code with Subclasses (224) or Replace Type Code with State/Strategy (227). When you have set up the inheritance structure you can then Replace Switch with Polymorphism (147).

Parallel Inheritance Hierarchies

This smell is really a special case of “the same rate off change in two objects”. In this case, every time you make a subclass of one class you also have to make a subclass of another. You can recognize this smell because the prefixes of the class names in one hierarchy are the same as the prefixes in another hierarchy.

The general strategy for eliminating the duplication is to make sure that instances of one hierarchy refer to instances of the other, then use Move Method (160) and Move Field (164) the hierarchy disappears on the referring class.

Lazy Class

Each class you create costs money. If there is a class that isn’t doing enough to pay for itself, it should be eliminated.

If you have subclasses that aren’t doing enough, try to use Collapse Hierarchy (288). Nearly useless components should be subjected to Inline Component (170).

Similar subclasses

Sometimes you will see a class with four subclasses, each of which only implements three simple methods. Often you will get a vague feeling that the class doesn’t deserve subclasses, but you won’t immediately be able to see how to eliminate them. This feeling can last for months or even years. Don’t worry. If you keep nibbling away at the problems you can see how to solve, eventually you will find yourself looking at the subclasses again, and all the difficult issues to resolve will have disappeared.

Once you’ve done this, look for new opportunities to use inheritance now that you are no longer wasting it.

Temporary Field

Sometimes you will see an object where an instance variable is only set in certain circumstances. Such code is difficult to understand, because you expect an object to need all of its variables. Trying to understand why a variable is there when it doesn’t seem to be used can drive you nuts.

Use Extract Component (166) to create a home for the poor orphan variables. Put all the code that concerns the variables in the component.

You may also be able to eliminate conditional code by using Introduce Null Object (151) to create an alternative component for when the variables aren’t valid.

Middle Man

One of the prime features of objects is encapsulation: hiding internal details from the rest of the world. Often this encapsulation comes with delegation, you ask a director if she is free a meeting, she delegates the message to her diary, and gives you an answer. All well and good,there is no need to know whether the director uses a diary, an electronic gizmo, or a secretary to keep track of her appointments.

However this can go too far (particularly with those who take the Law of Demeter too seriously). You look at a class’s interface and find half the methods are delegating to this other class. After a while it’s time to cut out the middle man and talk to the object that really knows what’s going on. Use Move Method (160) and Move Field (164) to move features out of the middle man into the other objects until the middle man has nothing left.

Alternative classes with different interfaces

Use Rename Method (234) on any methods that do the same thing but have different signatures for what they do. Often this doesn’t go far enough. In these cases the classes aren’t doing enough yet. Keep using Move Method (160) to move behavior to them until the protocols are the same. If you have to redundantly move code to accomplish this, you may be able to use Extract Superclass (282) to atone.

Comments

Don’t worry, we aren’t saying that people shouldn’t write comments.

In our olfactory analogy, comments aren’t a bad smell, indeed they are a sweet smell.

The reason we mention them here is because comments are often used as a deodorant. It’s surprising how often you look at thickly commented code, and notice that the comments are there because the code is bad.

Thus comments lead us to bad code that has all the rotten whiffs we’ve discussed in the rest of this chapter. Our first action to remove the bad smells by refactoring. When we’re done we often find that the comments are now superfluous.

When you feel the need to write a comment, try first to refactor the code so that any comment would be superfluous.

Chapter 3: Bad Smells in Code 第 3 章:代码中的坏味道

“If it stinks, change it.”
“如果臭了,就换掉。”

— Grandma Beck, discussing child raising philosophy
——贝克奶奶,讨论育儿哲学

By now you have a good idea of how refactoring works. But just because you know how doesn’t mean you know when. Deciding when to start refactoring (and when to stop) is just as important to refactoring as knowing how to operate the mechanics of a refactoring.
现在你已经很清楚重构是如何运作的了。但仅仅因为你知道如何做并不意味着你知道什么时候做。对于重构来说,决定何时开始重构(以及何时停止)与了解如何操作重构机制同样重要。

Now comes the dilemma. It is easy to explain to you how to delete an instance variable or create a hierarchy. These are simple matters. Trying to explain when you should do these things is not so cut-and-dried. Rather than appealing to some vague notion of programming aesthetics (which frankly is what we consultants usually do) I wanted something a bit more solid.
现在困境来了。向您解释如何删除实例变量或创建层次结构很容易。这些都是简单的事情。尝试解释何时应该做这些事情并不是那么简单。我不想诉诸一些模糊的编程美学概念(坦率地说,这是我们顾问通常所做的),我想要一些更扎实的东西。

I was mulling over this tricky issue when I visited Kent in Zurich. Perhaps he was under the influence of the odors of his new born daughter at the time, but he had come up with the notion describing the “when” of refactoring in terms of smells. “Smells,” you say, “and that is supposed to be better than vague aesthetics?” Well, yes. We look at lots of code, written by projects that span the gamut from wildly successful to nearly dead. And in doing so, we have learned to look for certain structures in the code that suggest (sometimes they scream for) the possibility of refactoring. (We are switching over to “we” in this chapter to reflect the fact that Kent and I wrote this chapter jointly. You can tell the difference because the funny jokes are mine and the others are his.)
当我访问苏黎世的肯特郡时,我正在思考这个棘手的问题。也许他当时受到了刚出生的女儿的气味的影响,但他提出了用气味来描述重构“何时”的概念。 “闻起来,”你说,“这应该比模糊的美学更好吧?”嗯,是。我们查看了大量由项目编写的代码,这些项目的范围从非常成功到几乎死亡。在这样做的过程中,我们学会了在代码中寻找某些表明(有时他们尖叫着)重构可能性的结构。 (我们在本章中改用“我们”,以反映肯特和我共同撰写这一章的事实。你可以看出区别,因为有趣的笑话是我的,其他的是他的。)

One thing we won’t try to do here is give you precise criteria for when a refactoring is overdue. In our experience there is no set of metrics that rival informed human intuition. What we will do is give you indications that there is trouble that could be solved by a refactoring. You will have to develop your own sense of how many instance variables is too many instance variables and how many lines of code in a method is too many lines.
我们在这里不会尝试做的一件事是为您提供重构何时过期的精确标准。根据我们的经验,没有任何衡量标准可以与人类直觉相媲美。我们要做的就是向您表明存在可以通过重构解决的问题。您必须自行判断有多少实例变量算实例变量太多,以及方法中的多少行代码算不算太多行。

Duplicated code重复的代码

Number one on the stink parade is duplicated code. If you see the same code structure in more than one place, you can be sure that your program will be better if you find a way to unify them.
臭游行中排名第一的是重复代码。如果您在多个地方看到相同的代码结构,那么如果您找到一种方法来统一它们,您可以确信您的程序会更好。

The simplest duplicated code problem is when you have the same expression in two methods of the same class. Then all you have to do is Extract Method (114) and invoke the code from both places.
最简单的重复代码问题是当同一类的两个方法中有相同的表达式时。然后您所要做的就是提取方法 (114) 并从两个地方调用代码。

Another common duplication problem is when you have the same expression in two sibling subclasses. You can eliminate this duplication by using Extract Method (114) in both classes, then Pull Up Method (271). If the code is similar but not the same, then you need to use Extract Method (114) to separate the similar bits from the different bits.
另一个常见的重复问题是当两个兄弟子类中有相同的表达式时。您可以通过在两个类中使用 Extract Method (114),然后使用 Pull Up Method (271) 来消除这种重复。如果代码相似但不相同,则需要使用提取方法(114)将相似位与不同位分开。

You may then find you can use Form Template Method (289). If the methods do the same thing using a different algorithm, you can choose the clearer of the two algorithms and use Substitute Algorithm (132).
然后您可能会发现可以使用表单模板方法(289)。如果这些方法使用不同的算法执行相同的操作,您可以选择两种算法中更清晰的一个并使用替代算法 (132)。

If you have duplicated code in two unrelated classes, consider using Extract Component (166) in one class, and then use the new component in the other. Another possibility is that the method really belongs only in one of the classes and should be invoked by the other class, or that the method belongs in a third class that should be referred to by both of the original classes. You have to decide where the method makes sense, and ensure it is there and nowhere else.
如果两个不相关的类中有重复的代码,请考虑在一个类中使用提取组件 (166),然后在另一个类中使用新组件。另一种可能性是,该方法实际上只属于其中一个类,并且应该由另一个类调用,或者该方法属于第三个类,而该第三个类应该由两个原始类引用。您必须决定该方法在哪里有意义,并确保它在那里而不是在其他地方。

Long method长方法

The object programs that live best and longest are those with short methods. Programmers new to objects often get the feeling that no computation ever takes place, that object programs are endless sequences of delegation. When you have lived with a program for a few years, however, you learn just how valuable all those little methods are. All of the pay-offs of indirection- explanation, sharing, and choosing- are supported by little methods.
寿命最长、寿命最长的目标程序是那些方法较短的程序。刚接触对象的程序员经常会感觉没有发生任何计算,对象程序是无休止的委托序列。然而,当您使用一个程序几年后,您就会知道所有这些小方法有多么有价值。间接的所有回报——解释、共享和选择——都是通过一些小方法来支持的。

Since the early days of programming people have realized that the longer a procedure is, the more difficult it is to understand. Older languages carried an overhead in subroutine calls, which deterred people from small method, modern OO languages have pretty much eliminated that overhead. There is still an overhead to the reader of the code as you have to switch context to look and see what the sub-procedure does. Development environments that allow you to see two methods at once help to eliminate this; but the real key to making it easy to understand small methods is good naming. If you have a good name for a method you don’t need to look at the body.
自从编程的早期以来,人们就意识到程序越长,理解起来就越困难。较旧的语言在子例程调用方面会产生开销,这阻止了人们使用小方法,现代面向对象语言几乎消除了这种开销。对于代码的读者来说仍然有一定的开销,因为您必须切换上下文才能查看子过程的作用。允许您同时查看两种方法的开发环境有助于消除这种情况;但让小方法易于理解的真正关键是良好的命名。如果你有一个好的方法名称,你就不需要查看方法体。

The net effect of this is that you should be much more aggressive about decomposing methods. A heuristic we follow is whenever we feel the need to comment something, we instead write a method. Such a method contains the code that was commented, but is named after what the intention of the code is, rather than how it does it. We may do this on a group of lines, or on as small as a single line of code. We will do this even if the method call is longer than the code it replaces, providing the method name explains the purpose of the code. The key here is not the method length, but the semantic distance between what the method does and how it does it.
这样做的最终效果是您应该更加积极地使用分解方法。我们遵循的一个启发是,每当我们觉得需要注释某些内容时,我们就会编写一个方法。此类方法包含已注释的代码,但以代码的意图命名,而不是根据其执行方式命名。我们可以在一组行上执行此操作,或者在小至单行代码上执行此操作。即使方法调用比它所替换的代码长,我们也会这样做,只要方法名称解释了代码的用途。这里的关键不是方法的长度,而是方法做什么和如何做之间的语义距离。

99% of the time, all you have to shorten a method is Extract Method (114). Find a part of the method that seems to go nicely together and make a new method.
99% 的情况下,您所需要缩短的方法就是 Extract Method (114)。找到该方法中似乎可以很好地结合在一起的部分并创建一个新方法。

If you have a method with lots of parameters and temporary variables, they get in the way of extracting methods. If you try to use Extract Method, you end up passing so many of the parameters and temporary variables as parameters to the extracted method that the result is scarcely more readable than the original. You can often use Inline Temp (121) to get rid of the temps. Long lists of parameters can be slimmed down with Introduce Parameter Object (247) and Preserve Whole Object (241).
如果您的方法包含大量参数和临时变量,它们会妨碍提取方法。如果您尝试使用提取方法,您最终会传递如此多的参数和临时变量作为提取方法的参数,以致结果几乎不比原始结果更具可读性。您通常可以使用 Inline Temp (121) 来消除临时值。可以使用“引入参数对象”(247) 和“保留整个对象”(241) 来精简长参数列表。

If you’ve tried that, and you still have too many temps and parameters,
如果您已经尝试过,但仍然有太多的临时值和参数,

then it’s time to get out the heavy artillery: Replace Method with Method Object (130).
那么是时候拿出重炮了:用方法对象替换方法(130)。

How do you identify the clumps of code to extract? A good technique is to look for comments. They often signal this kind of semantic distance.
如何识别要提取的代码块?一个好的技巧是寻找注释。它们经常表示这种语义距离。

A block of code with a comment that tells you what it is doing can be replaced by a method whose name is based on the comment.
带有注释的代码块可以告诉您它正在做什么,可以用名称基于注释的方法来替换。

Even a single line is worth extracting if it needs explanation.
如果需要解释,即使是一行也值得提取。

Conditionals and loops also give signs for extractions. Use Decompose Conditional (136) to deal with conditional expressions. With loops extract the loop and the code within the loop into its own method.
条件和循环也给出了提取的标志。使用 Decompose Conditional (136) 来处理条件表达式。使用循环将循环和循环内的代码提取到其自己的方法中。

Large Class大类

When a class is trying to do too much, it often shows up as too many instance variables. When a class has too many instance variables, duplicated code cannot be far behind.
当一个类试图做太多事情时,它通常会表现为实例变量太多。当一个类的实例变量过多时,重复的代码也就不远了。

You can Extract Component (166) to bundle up a number of the variables.
您可以提取组件 (166) 来捆绑多个变量。

Choose variables to go together in the component that makes sense with each other. For example, “depositAmount” and “deposit-Currency” are likely to belong together in a component. More generally, common prefixes or suffixes for some subset of the variables in a class suggest the opportunity for a component. If the component makes sense as a subclass you’ll find Extract Subclass (277) often is easier.
选择在组件中组合在一起的彼此有意义的变量。例如,“depositAmount”和“deposit-Currency”可能一起属于一个组件。更一般地说,类中某些变量子集的公共前缀或后缀暗示了组件的机会。如果该组件作为子类有意义,您会发现提取子类 (277) 通常更容易。

However you can only do this if the subset of instance variables that are used do not change during an object’s lifetime.
但是,只有当所使用的实例变量子集在对象的生命周期内不发生更改时,您才能执行此操作。

Sometimes a class will not be using all of its instance variables all of the time. If so, you may be able to Extract Component (166) or Extract Subclass (277) many times.
有时,类不会一直使用其所有实例变量。如果是这样,您可以多次提取组件 (166) 或提取子类 (277)。

As with a class with too many instance variables, a class with too much code is prime breeding ground for duplicated code, chaos, and death.
与具有过多实例变量的类一样,具有过多代码的类是重复代码、混乱和死亡的主要滋生地。

The simplest solution (have we mentioned that we like simple solutions?)
最简单的解决方案(我们是否提到过我们喜欢简单的解决方案?)

is if you can eliminate redundancy in the class itself. If you have five hundred line methods with lots of code in common, you may be able to turn them into five ten line methods with another ten two line methods extracted from the original.
就是你是否可以消除类本身的冗余。如果您有 500 行方法并且有很多共同代码,您可以将它们变成 5 个十行方法,并从原始代码中提取另外 10 个两行方法。

As with a class with a huge wad of variables, the usual solution for a class with too much code is either to Extract Component (166) or Extract Subclass (277).
与具有大量变量的类一样,对于具有过多代码的类,通常的解决方案是提取组件 (166) 或提取子类 (277)。

Long Parameter List长参数列表

In our early programming days we were taught to pass everything a routine needed in as parameters. This was understandable because the alternative was global data, and global data is evil and usually painful.
在我们早期的编程时代,我们被教导将例程所需的所有内容作为参数传递。这是可以理解的,因为替代方案是全球数据,而全球数据是邪恶的,而且通常是痛苦的。

Objects change this situation because if you don’t have something you need, you can always ask another object to get it for you. Thus with objects you don’t pass in everything the method needs, instead you pass enough so that the method can get to everything it needs. A lot of what a method needs is available on method’s host class. Thus in object-oriented programs parameter lists tend to be much smaller than on traditional programs.
对象改变了这种情况,因为如果你没有需要的东西,你总是可以要求另一个对象为你获取它。因此,对于对象,您不必传递方法所需的所有内容,而是传递足够的内容,以便方法可以获取它需要的所有内容。方法所需的很多内容都可以在方法的宿主类上找到。因此,在面向对象的程序中,参数列表往往比传统程序小得多。

This is good because long parameter lists are hard to understand, they get inconsistent and difficult to use, and because you are forever changing them as you need more data. Most changes are removed by passing objects because you are much more likely to just need to make a couple of requests to get at a new piece of data.
这很好,因为长参数列表很难理解,它们变得不一致且难以使用,并且因为您需要更多数据时会永远更改它们。大多数更改都是通过传递对象来删除的,因为您更有可能只需要发出几个请求即可获取新数据。

Use Replace Parameter with Method (245) when you can get the data in one parameter by making a request of an object you already know about. This object might be a field or it might be another parameter.
当您可以通过向您已知的对象发出请求来获取一个参数中的数据时,请使用“用方法替换参数”(245)。该对象可能是一个字段,也可能是另一个参数。

Use Preserve Whole Object (241) to take a bunch of data gleaned from an object and replace it with the object itself. If you have several data items with no logical object, then Introduce Parameter Object (247).
使用“保留整个对象”(241) 获取从对象收集的一堆数据,并将其替换为对象本身。如果您有多个没有逻辑对象的数据项,则引入参数对象 (247)。

There is one important exception to doing these changes. This is when you explicitly do not wish to create a dependency from the called object to the larger object. In those cases unpacking data and sending it along as parameters is reasonable, but pay attention to the pain involved. If the parameter list is too long or changes too often you will need to rethink your dependency structure.
进行这些更改有一个重要的例外。这是当您明确不希望创建从被调用对象到更大对象的依赖关系时。在这些情况下,解包数据并将其作为参数发送是合理的,但要注意所涉及的痛苦。如果参数列表太长或更改太频繁,您将需要重新考虑您的依赖结构。

Divergent Change发散性变化

We structure our software to make change easier, after all software is meant to be soft. So when we make a change we want to be able to jump to a single clear point in the system and make the change.
毕竟软件本来就应该是软的,我们构建软件是为了让改变变得更容易。因此,当我们进行更改时,我们希望能够跳转到系统中的单个明确点并进行更改。

If you look at a class and say, “Well, I will have to change these three methods every time I get a new database. I have to change these four methods every time there is a new financial instrument.” you likely have a situation where two objects are better than one. Of course you often discover this only after you’ve added a few databases or financial instruments. Any change to handle a variation should change a single class and all the typing in the new class should be expressing the variation.
如果您查看一个类并说:“好吧,每次获得新数据库时我都必须更改这三个方法。每次有新的金融工具出现时,我都必须改变这四种方法。”您可能会遇到两个对象比一个对象更好的情况。当然,您通常只有在添加了一些数据库或金融工具后才会发现这一点。处理变体的任何更改都应该更改单个类,并且新类中的所有类型都应该表达变体。

So for this you identify those things you are changing and use Extract Component (166) to put them all together.
因此,为此,您需要确定要更改的内容,并使用提取组件 (166) 将它们全部放在一起。

Shotgun Surgery霰弹枪手术

You whiff this when every time you add some variation, you have to make a lot of little changes to a lot of different classes. When the changes are all over the place, they are hard to find, and it’s easy to miss an important change. In this case you want to use Move Method (160) and Move Field (164) to get all the changes into a single class. If no current class looks like a good candidate, then create one. Often you can use Inline Component (170) to bring a whole bunch of behavior together. You then get a small dose of Divergent Change, but you can easily deal with that.
当每次添加一些变化时,您都必须对许多不同的类进行大量的小更改,您就会感到这一点。当变化遍布各处时,就很难发现,而且很容易错过重要的变化。在本例中,您希望使用 Move Method (160) 和 Move Field (164) 将所有更改放入单个类中。如果当前没有一个类看起来像一个好的候选者,那么创建一个。通常,您可以使用内联组件 (170) 将一大堆行为整合在一起。然后你会得到少量的发散变化,但你可以轻松应对。

Feature Envy功能羡慕

The whole point of objects is that they are a packaging technique that packages data together with the processes upon that data. A classic smell is thus a method that seems more interested in another class than the one it’s actually in. The most common focus of the envy is the data.
对象的全部要点在于它们是一种打包技术,将数据以及对该数据的处理打包在一起。因此,经典气味是一种似乎对另一个类比它实际所在的类更感兴趣的方法。最常见的嫉妒焦点是数据。

We’ve lost count of the times we’ve seen a method that invokes half-adozen getting methods on another object in order to calculate some value. Fortunately the cure is obvious, the method clearly wants to be elsewhere so you use Move Method (160) to get it there. Sometimes only part of the method suffers from envy, in that case use Extract Method (114) on the jealous bit and then Move Method (160) to give it a dream home.
我们已经记不清有多少次看到一个方法调用另一个对象上的六种获取方法来计算某个值。幸运的是,解决方法是显而易见的,该方法显然想要在其他地方,因此您可以使用“移动方法”(160) 将其到达那里。有时,只有部分方法受到嫉妒,在这种情况下,对嫉妒位使用提取方法 (114),然后使用移动方法 (160) 给它一个梦想的家。

Of course not all cases are so cut and dried. Often a method uses features of several classes, so which one should it live with? The heuristic we use is to which class has most of the data and put the method with that data. This is often made easier by using Extract Method (114) to break into methods that go into different places.
当然,并不是所有的情况都是如此。通常一个方法会使用多个类的特性,那么它应该使用哪一个呢?我们使用的启发式方法是哪个类拥有最多的数据,并将该方法与该数据放在一起。通过使用 Extract Method (114) 来分解进入不同位置的方法,这通常会变得更容易。

Of course there several sophisticated patterns that break this rule.
当然,有几种复杂的模式打破了这一规则。

From the [Gang of Four] Strategy and Visitor immediately leap to mind. Kent’s Self Delegation is another. You do these to combat the Divergent Change smell. The fundamental rule of thumb is to put things together that change together. Data and the behavior that references that data usually change together, but there are exceptions.
脑海中立即浮现出《四人帮》的策略和访客。肯特的自我委托是另一个例子。你这样做是为了对抗发散变化的味道。基本的经验法则是将变化的事物放在一起。数据和引用该数据的行为通常会一起更改,但也有例外。

When those exceptions occur we move the behavior to keep changes in one place. Strategy and Visitor allow you to change behavior easily because it isolates the small amount of behavior that needs to be overridden, at the cost of further indirection.
当这些异常发生时,我们会移动行为以将更改保留在一处。策略和访问者允许您轻松更改行为,因为它隔离了需要覆盖的少量行为,但代价是进一步间接。

Data Clumps数据块

Data items tend to be like children — they enjoy hanging around in groups together. Often you’ll see the same three or four data items together in lots of places: fields in a couple of classes, parameters in many method signatures. Bunches of data that hang around together really ought to be made into their own object. The first step is to look for where they appear as fields. Use Extract Component (166) on the fields to turn them into an object. Then turn your attention to method signatures, using Introduce Parameter Object (247) or Preserve Whole Object (241) to slim them down. The immediate benefit here is that you can shrink a lot of parameter lists and make method calling a lot simpler.
数据项往往就像孩子一样——他们喜欢成群结队地在一起。通常,您会在很多地方看到相同的三到四个数据项:几个类中的字段、许多方法签名中的参数。一堆聚集在一起的数据确实应该被制作成它们自己的对象。第一步是查找它们作为字段出现的位置。对字段使用提取组件 (166) 将其转换为对象。然后将注意力转向方法签名,使用引入参数对象 (247) 或保留整个对象 (241) 来精简它们。这里直接的好处是你可以缩小很多参数列表并使方法调用变得更加简单。

Don’t worry about data clumps that only use some of the fields of the new object. As long as you are replacing a couple or more fields with the new object, you’ll come out ahead.
不必担心仅使用新对象的某些字段的数据块。只要您用新对象替换几个或多个字段,您就会取得领先。

A good test is to consider deleting one of the data values: if you did this would the others make any sense? If they don’t that’s a sure sign that you have an object that’s dying to be born.
一个好的测试是考虑删除一个数据值:如果您这样做,其他数据值是否有意义?如果他们不这样做,那就是一个明确的信号,表明你有一个即将诞生的物体。

Reducing field lists and parameter lists will certainly remove a few bad smells, but once you have the objects you get the opportunity to make a nice perfume. You can now look for cases of Divergent Change which will suggest behavior that can be moved into your new classes.
减少字段列表和参数列表肯定会消除一些难闻的气味,但是一旦你拥有了这些对象,你就有机会制作出美味的香水。您现在可以寻找发散性变化的案例,这些案例将建议可以转移到您的新类别中的行为。

Before long these classes will be productive members of society.
不久之后,这些阶级将成为社会的富有生产力的成员。

Case Statements条件分支语句

One of the most obvious symptoms of object-oriented code is its lack of case (or switch) statements. The problem with case statements is essentially that of duplication. Often you find the same case statement scattered about a program for different purposes. If you add a type to the case you have to find all these case statements and change them. The object-oriented notion of polymorphism gives you an elegant way to deal with this problem.
面向对象代码最明显的症状之一是缺少 case(或 switch)语句。 case 语句的问题本质上是重复。通常,您会发现出于不同目的,相同的 case 语句分散在程序中。如果向 case 添加类型,则必须找到所有这些 case 语句并更改它们。面向对象的多态性概念为您提供了一种处理此问题的优雅方法。

So most times you see a case statement you should think about polymorphism, the issue is where should the polymorphism occur. Usually case statement will switch on a type code. You will want the method on class that hosts the type code value. So use Extract Method (114) to extract the switch statement and then Move Method (160) to get it onto the class where the polymorphism is needed. At that point you have to decide whether to Replace Type Code with Subclasses (224) or Replace Type Code with State/Strategy (227). When you have set up the inheritance structure you can then Replace Switch with Polymorphism (147).
所以大多数时候你看到一个case语句你应该考虑多态性,问题是多态性应该发生在哪里。通常 case 语句会打开类型代码。您将需要托管类型代码值的类上的方法。因此,使用 Extract Method (114) 提取 switch 语句,然后使用 Move Method (160) 将其放到需要多态性的类中。此时,您必须决定是用子类替换类型代码 (224) 还是用状态/策略替换类型代码 (227)。设置继承结构后,您可以用多态性替换 Switch (147)。

Parallel Inheritance Hierarchies并行继承层次结构

This smell is really a special case of “the same rate off change in two objects”. In this case, every time you make a subclass of one class you also have to make a subclass of another. You can recognize this smell because the prefixes of the class names in one hierarchy are the same as the prefixes in another hierarchy.
这种气味实际上是“两个物体变化率相同”的特例。在这种情况下,每次创建一个类的子类时,您还必须创建另一个类的子类。您可以识别出这种气味,因为一个层次结构中的类名前缀与另一个层次结构中的前缀相同。

The general strategy for eliminating the duplication is to make sure that instances of one hierarchy refer to instances of the other, then use Move Method (160) and Move Field (164) the hierarchy disappears on the referring class.
消除重复的一般策略是确保一个层次结构的实例引用另一层次结构的实例,然后使用移动方法 (160) 和移动字段 (164),层次结构在引用类上消失。

Lazy Class懒惰类

Each class you create costs money. If there is a class that isn’t doing enough to pay for itself, it should be eliminated.
您创建的每个类都需要花钱。如果有一个阶级的努力不足以维持其自身的收入,那么它就应该被淘汰。

If you have subclasses that aren’t doing enough, try to use Collapse Hierarchy (288). Nearly useless components should be subjected to Inline Component (170).
如果您的子类做得不够,请尝试使用 Collapse Hierarchy (288)。几乎无用的组件应该受到内联组件(170)的影响。

Similar subclasses类似的子类
Sometimes you will see a class with four subclasses, each of which only implements three simple methods. Often you will get a vague feeling that the class doesn’t deserve subclasses, but you won’t immediately be able to see how to eliminate them. This feeling can last for months or even years. Don’t worry. If you keep nibbling away at the problems you can see how to solve, eventually you will find yourself looking at the subclasses again, and all the difficult issues to resolve will have disappeared.
有时你会看到一个类有四个子类,每个子类只实现三个简单的方法。通常你会隐约感觉到该类不值得有子类,但你不会立即知道如何消除它们。这种感觉可以持续数月甚至数年。不用担心。如果你不断地啃那些你能看到如何解决的问题,最终你会发现自己再次查看子类,所有需要解决的难题都将消失。

Once you’ve done this, look for new opportunities to use inheritance now that you are no longer wasting it.
完成此操作后,请寻找新的机会来使用继承,因为您不再浪费它了。

Temporary Field临时字段

Sometimes you will see an object where an instance variable is only set in certain circumstances. Such code is difficult to understand, because you expect an object to need all of its variables. Trying to understand why a variable is there when it doesn’t seem to be used can drive you nuts.
有时您会看到一个对象,其中的实例变量仅在某些情况下设置。这样的代码很难理解,因为您期望一个对象需要它的所有变量。试图理解一个似乎没有被使用的变量为什么会存在,这会让你发疯。

Use Extract Component (166) to create a home for the poor orphan variables. Put all the code that concerns the variables in the component.
使用提取组件 (166) 为可怜的孤儿变量创建一个家。将所有涉及变量的代码放在组件中。

You may also be able to eliminate conditional code by using Introduce Null Object (151) to create an alternative component for when the variables aren’t valid.
您还可以通过使用引入空对象 (151) 来消除条件代码,以便在变量无效时创建替代组件。

Middle Man中间人

One of the prime features of objects is encapsulation: hiding internal details from the rest of the world. Often this encapsulation comes with delegation, you ask a director if she is free a meeting, she delegates the message to her diary, and gives you an answer. All well and good,there is no need to know whether the director uses a diary, an electronic gizmo, or a secretary to keep track of her appointments.
对象的主要特征之一是封装:向外界隐藏内部细节。通常这种封装伴随着授权,你问一位主管她是否有空开会,她将信息委托给她的日记,然后给你一个答案。一切都很好,没有必要知道主任是否使用日记、电子小玩意或秘书来记录她的约会。

However this can go too far (particularly with those who take the Law of Demeter too seriously). You look at a class’s interface and find half the methods are delegating to this other class. After a while it’s time to cut out the middle man and talk to the object that really knows what’s going on. Use Move Method (160) and Move Field (164) to move features out of the middle man into the other objects until the middle man has nothing left.
然而,这可能太过分了(特别是对于那些过于认真对待德墨忒尔定律的人)。您查看一个类的接口,发现一半的方法委托给了另一个类。过了一会儿,就可以去掉中间人,与真正知道发生了什么的对象进行对话。使用移动方法 (160) 和移动字段 (164) 将特征从中间人移到其他对象中,直到中间人没有任何剩余。

Alternative classes with different interfaces具有不同接口的替代类

Use Rename Method (234) on any methods that do the same thing but have different signatures for what they do. Often this doesn’t go far enough. In these cases the classes aren’t doing enough yet. Keep using Move Method (160) to move behavior to them until the protocols are the same. If you have to redundantly move code to accomplish this, you may be able to use Extract Superclass (282) to atone.
对执行相同操作但具有不同功能签名的任何方法使用重命名方法 (234)。通常这还不够。在这些情况下,班级做得还不够。继续使用移动方法 (160) 将行为移动到它们,直到协议相同。如果您必须冗余地移动代码来完成此操作,您可以使用 Extract Superclass (282) 来弥补。

Comments注释

Don’t worry, we aren’t saying that people shouldn’t write comments.
别担心,我们并不是说人们不应该写注释。

In our olfactory analogy, comments aren’t a bad smell, indeed they are a sweet smell.
在我们的嗅觉比喻中,注释并不是难闻的气味,实际上它们是一种甜蜜的气味。

The reason we mention them here is because comments are often used as a deodorant. It’s surprising how often you look at thickly commented code, and notice that the comments are there because the code is bad.
我们在这里提到它们的原因是因为注释经常被用作除臭剂。令人惊讶的是,当您查看大量注释的代码时,您会发现注释的存在是因为代码很糟糕。

Thus comments lead us to bad code that has all the rotten whiffs we’ve discussed in the rest of this chapter. Our first action to remove the bad smells by refactoring. When we’re done we often find that the comments are now superfluous.
因此,注释会导致我们编写出糟糕的代码,其中包含我们在本章其余部分讨论的所有腐烂的味道。我们的第一个行动是通过重构来消除不良气味。当我们完成后,我们经常发现注释现在是多余的。

When you feel the need to write a comment, try first to refactor the code so that any comment would be superfluous.
当您觉得需要编写注释时,请首先尝试重构代码,以便任何注释都是多余的。


【C1-Listening】Bad Smells in Code-代码里的坏味道
http://coderdream.github.io/2024/04/12/bad-smells-in-code/
作者
CoderDream
发布于
2024年4月12日
许可协议