Table of Contents

Principle Of Separate Understandability (PSU)

Variants and Alternative Names

Context

Principle Statement

Each module shall be understandable on its own—without knowing anything about other modules.

Description

PSU means that:

Rationale

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.

An important aspect of PSU is readability or rather understandability. If a module—say a method—requires to understand several other modules (other methods, the usage of certain attributes, the idea of the whole class, …), a 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 on. This is unnecessarily time consuming:

In a nutshell, if you have to mentally inline code, it would have been better if it was already inlined. The 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

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) or the module has the wrong abstraction (example 3). So strategies for making a solution more compliant with PSU are:

Caveats

See section contrary principles.

Origin

This principle is newly proposed in this wiki. Nevertheless it is believed that it is not “new” in the sense that its a new insight. Its rather something that is commonly known but hasn't been expressed as a principle, yet.

Evidence

Relations to Other Principles

Generalizations

Specializations

Contrary Principles

Complementary Principles

Principle Collections

OOD Principle Language
General Principles
ML KISS MIMC DRY GP RoE
Modularization Principles
MP HC ECV
Module Communication Principles
TdA/IE LC DIP
Interface Design Principles
EUHM PLS UP
Internal Module Design Principles
IH/E IAP LSP PSU

Examples

Example 1: Parsing Data

Suppose a program parses data stored in an spreadsheet file. There are three classes:

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 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.

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.

private int ball;
private int[] itsThrows = new int[21];
 
private boolean strike()
{
    if (itsThrows[ball] == 10)
    {
        ball++;
        return true;
    }
    return false;
}

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:

private int rolls[] = new int[21];
 
private boolean isStrike(int frameIndex)
{
    return rolls[frameIndex] == 10;
}

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.

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 Clean Code:

public String make(char candidate, int count)
{
    createPluralDependentMessageParts(count);
    return String.format("There %s %s %s%s", verb, number, candidate, pluralModifier);
}

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:

private void createPluralDependentMessageParts(int count)
{
    if (count == 0)
    {
        thereAreNoLetters();
    }
    else if (count == 1)
    {
        thereIsOneLetter();
    }
    else
    {
        thereAreManyLetters(count);
    }
}

Again, you most likely won't be satisfied and ask for the rest of the implementation:

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";
         }
     }
}

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:

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";
     }
}

Here virtually every piece of code is understandable on its own.

Description Status

Complete

Further Reading

Discussion

Discuss this wiki article and the principle on the corresponding talk page.

2)
Robert C. Martin: Agile Software Development, Principles, Patterns, and Practices