User Tools

Site Tools


principles:principle_of_separate_understandability

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
Next revision
Previous revision
principles:principle_of_separate_understandability [2013-02-12 11:37]
christian example 1
principles:principle_of_separate_understandability [2014-07-02 19:42] (current)
christian SLA
Line 7: Line 7:
 /* fill in contexts here: */ /* fill in contexts here: */
   * [[contexts:​Object-Oriented Design]] ​   * [[contexts:​Object-Oriented Design]] ​
 +  * [[contexts:​Implementation]]
  
 ===== Principle Statement ===== ===== Principle Statement =====
  
-Each module shall be understandable on its own -- without knowing anything about other modules.+Each module shall be understandable on its own---without knowing anything about other modules.
  
  
Line 17: Line 17:
  
 PSU means that: PSU means that:
 +  * By looking at a class its purpose should be clear.
   * By looking at the public methods of a class it should be clear why they are there. That means there should be no method that is only there because a specific other module needs it.   * By looking at the public methods of a class it should be clear why they are there. That means there should be no method that is only there because a specific other module needs it.
   * By looking at the implementation of a module it should be clear how it works and why it was done that way. That means there should be no code that is solely there in order to make another module work.   * By looking at the implementation of a module it should be clear how it works and why it was done that way. That means there should be no code that is solely there in order to make another module work.
-  * By looking at a private method it should be clear what it does. That means there should be no (private) method that is only meaningful in the context of another method. +  * By looking at a private method it should be clear what it does. That means there should be no (private) method that is only meaningful in the context of another method ​(see [[#Example 2: Dependent Private Methods|example 2]])
 +  * By looking at a method invocation it should be clear what happens, why the parameters are there, and what they specify. It should not be necessary to look up the method implementation (see [[#Example 3: Unnecessary State and Wrong Abstractions|example 3]]). 
 +  * By looking at a single line of code it should be clear what it does without having to look up other code.
  
 ===== Rationale ===== ===== Rationale =====
Line 26: Line 28:
 When a module is separately understandable,​ it is easier to maintain, as no other modules have to be considered during maintenance. It is furthermore more testable, as a unit test can easily test only this particular module without requiring integration with other modules. When a module is separately understandable,​ it is easier to maintain, as no other modules have to be considered during maintenance. It is furthermore more testable, as a unit test can easily test only this particular module without requiring integration with other modules.
  
-Another point of view is that violation ​of PSU either means that a part of the functionality does not belong ​to that module or the module has the wrong abstractionSo this is a sign of a design that needs improvement.+An important ​ aspect ​of PSU is readability or rather understandability. If module---say a method---requires to understand several other modules (other methods, the usage of certain attributes, the idea of the whole class, ...), much larger ​part of the code has to be read and kept in memory. And if a method call is not separately understandable,​ the reader of the code will have to jump to the implementation of the method in order to see what's going onThis is unnecessarily time consuming: ​
  
 +  * You have to find the implementation and jump there (modern IDEs help here but it takes time nevertheless)
 +  * While doing so, you have to memorize the call and the context of the call. If implementation and call are not colocated (which is preferable but not always possible) you won't see the call anymore so you have to memorize it.
 +  * Then you have to read the code and [[glossary:​mental inlining|mentally inline]] it.
 +  * If you could not memorize everything, you might have to jump back and forth to do the job.
 +  * After you did all that you have to jump back and continue reading the method with the call you just mentally inlined.
 +
 +In a nutshell, if you have to mentally inline code, it would have been better if it was already inlined. The [[refactorings:​extract method|method extraction]] in fact was harmful to readability and not beneficial. Note that not extracting a method needn'​t be the best solution to the problem. Often renaming the extracted method already does the job. Maybe this also hints that not the right piece of code has been extracted.
 +
 +Another point of view is that a violation of PSU either means that a part of the functionality does not belong to that module or the module has the wrong abstraction. So this is a sign of a design that needs improvement.
  
 ===== Strategies ===== ===== Strategies =====
  
-When a module does not comply with PSU, this means that either a part of the functionality of the module does not belong here or the module has the wrong abstraction. So strategies for making a solution more compliant with PSU are:+When a module does not comply with PSU, this means that either a part of the functionality of the module does not belong here (see [[#Example 1: Parsing Data|example 1]]) or the module has the wrong abstraction ​([[#Example 3: Unnecessary State and Wrong Abstractions|example 3]]). So strategies for making a solution more compliant with PSU are:
  
-  * Move the conflicting functionality to another module where it fits better (see [[Tell don't Ask/​Information Expert|IE]]). +  * Move the conflicting functionality to another module where it fits better: [[refactorings:​Move Field]], [[refactorings:​Move Method]] ​(see [[Tell don't Ask/​Information Expert|IE]], [[High Cohesion|HC]],​ and [[Model Principle|MP]]). 
-  * Build up a new module for the conflicting functionality (see [[High Cohesion|HC]]).+  * Build up a new module for the conflicting functionality: [[refactorings:​Extract Method]], [[refactorings:​Extract Class]] ​(see [[High Cohesion|HC]]).
   * Find the right abstraction for the module that allows the functionality to stay here (see [[Model Principle|MP]]).   * Find the right abstraction for the module that allows the functionality to stay here (see [[Model Principle|MP]]).
 +  * Find a name which properly describes the abstraction of the module: [[refactorings:​Rename Module]] ([[Model Principle|MP]]).
 +
 +===== Caveats =====
 +
 +See section [[#contrary principles]].
 +
  
 ===== Origin ===== ===== Origin =====
Line 65: Line 82:
  
   * [[Information Hiding/​Encapsulation]] (IH/E): PSU is about constructing a module such that its inner workings (and its usage also) can be understood without knowledge about other modules. IH/E on the other hand is about constructing a module in a way that hides the inner workings so it can be used without knowing //them//.   * [[Information Hiding/​Encapsulation]] (IH/E): PSU is about constructing a module such that its inner workings (and its usage also) can be understood without knowledge about other modules. IH/E on the other hand is about constructing a module in a way that hides the inner workings so it can be used without knowing //them//.
-  * [[Low Coupling]] (LC): One kind of couplings are logical couplings. These are especially hard to detect but should be avoidedPSU describes one aspect ​of these logical couplingswhereas LC relates ​this kind of coupling ​to others+  * [[Model Principle]] (MP): The model contains the only information that should be necessary to understand the moduleAnd if the abstraction ​of the model is wrong, MP helps getting it right. 
-  * [[Model Principle]] (MP): The model contains the only information that should be necessary ​to understand ​the module+  * [[Tell, don't Ask/​Information Expert]] (TdA/IE): At its heart PSU is about responsibility assignment. When a module is not separately understandable, this means that a responsibility is scattered across several modules. TdA/IE gives another aspect ​of responsibility assignment. 
 +  * [[Low Coupling]] (LC): Not adhering ​to PSU means that responsibilities are scattered across several modules. This typically also means increased coupling
 +  * [[Single Level of Abstraction]] (SLA): The purpose of PSU is to avoid [[glossary:​mental inlining]]. SLA on the other hand is about the avoiding the opposite: [[glossary:​mental grouping]].
  
 ==== Principle Collections ==== ==== Principle Collections ====
Line 73: Line 92:
  
  
-===== Example ​=====+===== Examples ​=====
  
 ==== Example 1: Parsing Data ==== ==== Example 1: Parsing Data ====
Line 82: Line 101:
   * ''​SpreadsheetWriter'':​ This class takes a ''​DomainObject''​ and writes it back to the spreadsheet.   * ''​SpreadsheetWriter'':​ This class takes a ''​DomainObject''​ and writes it back to the spreadsheet.
  
-In such a scenario it might be convenient to simplify ''​SpreadsheetWriter''​ by adding information about the spreadsheet to ''​DomainObject''​. This might be some cell coordinates for example. ''​SpreadsheetReader''​ can store them into the newly created ''​DomainObject''​ and ''​SpreadsheetWriter''​ uses the data to store the ''​DomainObject''​ to the correct position in the spreadsheet.+In such a scenario it might be convenient to simplify ''​SpreadsheetWriter''​ by adding information about the spreadsheet to ''​DomainObject''​. This might be some cell coordinates for example. ''​SpreadsheetReader''​ can store them into the newly created ''​DomainObject''​ and ''​SpreadsheetWriter''​ uses the data to store the ''​DomainObject''​ to the correct position in the spreadsheet. The problematic method is ''​DomainObject.getCellPositionInSpreadsheet()''​
  
 This is a simple solution (see [[Keep It Simple Stupid|KISS]]) but it violates PSU. ''​DomainObject''​ is not understandable on its own. It holds data (namely the cell position in the spreadsheet) that is only meaningful in the context of the other two modules. During maintenance this data could accidentally be altered (resulting in a corrupted output file). Maintenance effort is also increased simply by distracting the maintainers who might wonder what this data is and if it is relevant for their task. This is a simple solution (see [[Keep It Simple Stupid|KISS]]) but it violates PSU. ''​DomainObject''​ is not understandable on its own. It holds data (namely the cell position in the spreadsheet) that is only meaningful in the context of the other two modules. During maintenance this data could accidentally be altered (resulting in a corrupted output file). Maintenance effort is also increased simply by distracting the maintainers who might wonder what this data is and if it is relevant for their task.
Line 88: Line 107:
 A better solution (wrt. PSU) would be to give ''​SpreadSheetWriter''​ the ability to determine the correct position in the spreadsheet itself. This is more complicated and may involve searching the spreadsheet for the correct position. But ''​DomainObject''​ is easier to understand and less prone to errors. A better solution (wrt. PSU) would be to give ''​SpreadSheetWriter''​ the ability to determine the correct position in the spreadsheet itself. This is more complicated and may involve searching the spreadsheet for the correct position. But ''​DomainObject''​ is easier to understand and less prone to errors.
  
 +==== Example 2: Dependent Private Methods ====
 +
 +In a module that computes results in a bowling game there might be a method ''​strike()''​ which returns true when the player has thrown a strike, i.e. hit all 10 pins with only one ball throw.
 +
 +<code java>
 +private int ball;
 +private int[] itsThrows = new int[21];
 +  ​
 +private boolean strike()
 +{
 +    if (itsThrows[ball] == 10)
 +    {
 +        ball++;
 +        return true;
 +    }
 +    return false;
 +}
 +</​code>​
 +
 +Here the method not only computes if the current throw is a strike or not but also advances the counting variable ''​ball''​. This is only meaningful in the context of another method. If this is correct behavior or a defect cannot be told solely by looking at this method. Should ball be increased by 1 or 2? Should it also be increased when the throw is not a strike? Should it be increased at all? It cannot be told without looking at other parts of the code. So this method violates PSU.
 +
 +The following solution is better:
 +<code java>
 +private int rolls[] = new int[21];
 +
 +private boolean isStrike(int frameIndex)
 +{
 +    return rolls[frameIndex] == 10;
 +}
 +</​code>​
 +Here no counting variable is increased in some way. Furthermore this  method does not rely on a correctly set private variable but gets a parameter.
 +
 +This example is taken from Robert C. Martin. ​
 +  * First version: see ((http://​www.objectmentor.com/​resources/​articles/​xpepisode.htm)) or ((Robert C. Martin: //Agile Software Development,​ Principles, Patterns, and Practices//​))
 +  * Second version: see ((http://​butunclebob.com/​ArticleS.UncleBob.TheBowlingGameKata)) or ((http://​www.slideshare.net/​lalitkale/​bowling-game-kata-by-robert-c-martin))
 +
 +
 +==== Example 3: Unnecessary State and Wrong Abstractions ====
 +
 +This example is also inspired by Robert C. Martin. Have a look at the following piece of code from [[resources:​Clean Code]]:
 +<code java>
 +public String make(char candidate, int count)
 +{
 +    createPluralDependentMessageParts(count);​
 +    return String.format("​There %s %s %s%s", verb, number, candidate, pluralModifier);​
 +}
 +</​code>​
 +What does it do? Certainly some information is missing to answer this question. This piece of code is not separately understandable. You might feel the urge to ask for the implementation of ''​createPluralDependentMessageParts''​ as especially this method call is not separately understandable. OK, here it is:
 +
 +<code java>
 +private void createPluralDependentMessageParts(int count)
 +{
 +    if (count == 0)
 +    {
 +        thereAreNoLetters();​
 +    }
 +    else if (count == 1)
 +    {
 +        thereIsOneLetter();​
 +    }
 +    else
 +    {
 +        thereAreManyLetters(count);​
 +    }
 +}
 +</​code>​
 +
 +Again, you most likely won't be satisfied and ask for the rest of the implementation:​
 +
 +<code java>
 +public class Statistics2
 +{
 +     ​public static void main(String...args)
 +     {
 +         ​GuessStatisticsMessage statistics = new GuessStatisticsMessage();​
 +         ​System.out.println(statistics.make('​d',​ 0));
 +         ​System.out.println(statistics.make('​d',​ 1));
 +         ​System.out.println(statistics.make('​d',​ 25));
 +     }
 +
 +     ​static class GuessStatisticsMessage
 +     {
 +         ​private String number;
 +         ​private String verb;
 +         ​private String pluralModifier;​
 +
 +         ​public String make(char candidate, int count)
 +         {
 +             ​createPluralDependentMessageParts(count);​
 +             ​return String.format("​There %s %s %s%s", verb, number, candidate, pluralModifier);​
 +         }
 +
 +         ​private void createPluralDependentMessageParts(int count)
 +         {
 +             if (count == 0)
 +             {
 +                 ​thereAreNoLetters();​
 +             }
 +             else if (count == 1)
 +             {
 +                 ​thereIsOneLetter();​
 +             }
 +             else
 +             {
 +                 ​thereAreManyLetters(count);​
 +             }
 +         }
 +
 +         ​private void thereAreNoLetters()
 +         {
 +             ​number = "​no";​
 +             verb = "​are";​
 +             ​pluralModifier = "​s";​
 +         }
 +
 +         ​private void thereIsOneLetter()
 +         {
 +             ​number = "​1";​
 +             verb = "​is";​
 +             ​pluralModifier = "";​
 +         }
 +
 +         ​private void thereAreManyLetters(int count)
 +         {
 +             ​number = Integer.toString(count);​
 +             verb = "​are";​
 +             ​pluralModifier = "​s";​
 +         }
 +     }
 +}
 +</​code>​
 +
 +Only if you read all that code, you really get what's going on. Also if you started with some other method, you would not understand it. It's clear what ''​thereIsOneLetter()''​ does as the code is trivial. But you cannot understand //why// that code is there without knowing the rest. 
 +
 +The problem cannot be solved by moving or renaming methods or fields. The abstraction of the methods is wrong. The methods are just groupings of code and have no distinct meaning. The uncommon naming scheme of the methods lacking an imperative form of a verb might be an indicator for that. 
 +
 +The functionality is buried in the class which is most obvious with the ''​pluralModifier''​. This value is used to construct a plural form by appending it to another value in the ''​String.format''​ statement. The concept of making a plural form is not present in the code. Rather the code centers around assigning values to variables.
 +
 +A better solution might be the following:
 +
 +<code java>
 +public class Statistics3
 +{
 +     enum Number {SINGULAR, PLURAL}
 +
 +     ​public static void main(String...args)
 +     {
 +         ​Statistics3 statistics = new Statistics3();​
 +         ​System.out.println(statistics.composeGuessStatistics('​d',​ 0));
 +         ​System.out.println(statistics.composeGuessStatistics('​d',​ 1));
 +         ​System.out.println(statistics.composeGuessStatistics('​d',​ 25));
 +     }
 +
 +     ​private String composeGuessStatistics(char candidate, int count)
 +     {
 +         ​Number number = requiresPluralForm(count) ? Number.PLURAL : Number.SINGULAR;​
 +         ​return String.format("​There %s %s %s", thirdFormOfToBe(number),​ countToString(count), ​
 +                 ​declineLetter(candidate,​ number));
 +     }
 +
 +     ​private boolean requiresPluralForm(int count)
 +     {
 +         ​return count != 1;
 +     }
 +
 +     ​private String thirdFormOfToBe(Number number)
 +     {
 +         ​return number == Number.SINGULAR ? "​is"​ : "​are";​
 +     }
 +
 +     ​private String countToString(int count)
 +     {
 +         ​return count == 0 ? "​no"​ : Integer.toString(count);​
 +     }
 +
 +     ​private String declineLetter(char letter, Number number)
 +     {
 +         ​return number == Number.SINGULAR ? Character.toString(letter) : letter + "​s";​
 +     }
 +}
 +</​code>​
 +
 +Here virtually every piece of code is understandable on its own. 
  
 ===== Description Status ===== ===== Description Status =====
 /* Choose one of the following and comment out the rest: */ /* Choose one of the following and comment out the rest: */
 /​*[[wiki:​Stub]]*/​ /​*[[wiki:​Stub]]*/​
- +/*[[wiki:​Incomplete]]*/ 
-[[wiki:​Incomplete]] +[[wiki:​Complete]]
- +
-/*[[wiki:​Complete]]*/+
  
 ===== Further Reading ===== ===== Further Reading =====
  
 +  * [[http://​www.christian-rehn.de/​2013/​10/​06/​clean-code-und-das-principle-of-separate-understandability/​|Clean Code und das Principle of Separate Understandability]]:​ Example 3 in more detail (German)
 +
 +===== Discussion =====
  
 +Discuss this wiki article and the principle on the corresponding [[talk:​principles:​Principle Of Separate Understandability|talk page]].
principles/principle_of_separate_understandability.1360665435.txt.gz · Last modified: 2013-05-20 12:46 (external edit)