Two examples of Connascence of Position
Published by Manuel Rivero & Fran Reyes on 31/07/2017
A first example.
As we saw in our previous post about connascence, Connascence of Position (CoP) happens when multiple components must be adjacent or appear in a particular order. CoP is the strongest form of static connascence, as shown in the following figure.
A typical example of CoP appears when we use positional parameters in a method signature because any change in the order of the parameters will force to change all the clients using the method.
The degree of the CoP increases with the number of parameters, being zero when we have only one parameter. This is closely related with the Long Parameters List smell.
In some languages, such as Ruby, Clojure, C#, Python, etc, this can be refactored by introducing named parameters (see Introduce Named Parameter refactoring).
Now changing the order of parameters in the signature of the method won’t force the calls to the method to change, but changing the name of the parameters will. This means that the resulting method no longer presents CoP. Instead, now it presents Connascence of Name, (CoN), which is the weakest form of static connascence, so this refactoring has reduced the overall connascence.
The benefits don’t end there. If we have a look at the calls before and after the refactoring, we can see how the call after introducing named parameters communicates the intent of each parameter much better. Does this mean that we should use named parameters everywhere?
Well, it depends. There’re some trade-offs to consider. Positional parameters produce shorter calls. Using named parameters gives us better code clarity and maintainability than positional parameters, but we lose terseness. On the other hand, when the number of parameters is small, a well chosen method name can make the intent of the positional arguments easy to guess and thus make the use of named parameters redundant.
We should also consider the impact that the degree and locality of each instance of CoP can have on the maintainability and communication of intent of each option. On one hand, the impact on maintainability of using positional parameters is higher for public methods than for private methods (even higher for published public methods). On the other hand, a similar reasoning might be made about the intent of positional parameters: the positional parameters of a private method in a cohesive class might be much easier to understand than the parameters of a public method of a class a client is using, because in the former case we have much more context to help us understand.
The communication of positional parameters can be improved a lot with the parameter name hinting feature provided by IDEs like IntelliJ. In any case, even though they look like named parameters, they still are positional parameters and have CoP. In this sense, parameter name hinting might end up having a bad effect in your code by reducing the pain of having long parameter lists.
Finally, moving to named parameters can increase the difficulty of applying the most frequent refactoring: renaming. Most IDEs are great renaming positional parameters, but not all are so good renaming named parameters.
A second example.
There are also cases in which blindly using named parameters can make things worse. See the following example:
The activate_alarm method presents CoP, so let’s introduce named parameters as in the previous example:
We have eliminated the CoP and now there’s only CoN, right?
In this particular case, the answer would be no. We’re just masking the real problem which was a Connascence of Meaning (CoM) (a.k.a. Connascence of Convention). CoM happens when multiple components must agree on the meaning of specific values. CoM is telling us that there might be a missing concept or abstraction in our domain. The fact that the lower_threshold and higher_threshold only make sense when they go together, (we’re facing a case of data clump), is an implicit meaning or convention on which different methods sharing those parameters must agree, therefore there’s CoM.
We can eliminate the CoM by introducing a new class, Range, to wrap the data clump and reify the missing concept in our domain reducing the CoM to Connascence of Type (CoT). This refactoring plus the introduction of named parameters leaves with the following code:
This refactoring is way better than only introducing named parameters because it does not only provides a bigger coupling reduction by going down in the scale from from CoP to CoT instead of only from CoP to CoM, but also it introduces more semantics by adding a missing concept (the Range object).
Later we’ll probably detect similarities in the way some functions that receives the new concept are using it and reduce it by moving that behavior into the new concept, converting it into a value object. It’s in this sense that we say that value objects attract behavior.
We have presented two examples of CoP, a “pure” one and another one that was really hiding a case of CoM. We have related CoP and CoM with known code smells, (Long Parameters List, Data Clump and Primitive Obsession), and introduced refactorings to reduce their coupling and improve their communication of intent. We have also discussed a bit, about when and what we need to consider before applying these refactorings.
- Refactoring Ruby, Jay Fields, Kent Beck, Martin Fowler, Shane Harvie
- Refactoring Ruby: Bad Smells in Code, Jay Fields, Kent Beck, Martin Fowler, Shane Harvie
- Ruby 2 Keyword Arguments, Ian C. Anderson
- The problem with code smells, Kevin Rutherford
- A problem with Primitive Obsession, Kevin Rutherford
- About Connascence, Manuel Rivero
Originally published in Manuel Rivero's blog.