Have No Side Effects

Clean Code introduces side effects in a somewhat casual terms:

"Side effects are lies. Your function promises to do one thing, but it also does other hidden things. Sometimes it will make unexpected changes to the variables of its own class. Sometimes it will make them to the parameters passed into the function or to system globals. In either case they are devious and damaging mistruths that often result in strange temporal couplings and order dependencies."

To make it more formal: a side effect is any operation that:

  • Modifies state outside the function's scope
  • Interacts with the external world (I/O, network, database)
  • Relies on non-deterministic behavior (e.g., random number generation, system clock)
  • Throws exception (which can alter the program's control flow in unexpected ways)

Pure functions—those without side effects—are easier to reason about, test, and reuse. They work like black boxes: given the same inputs, they always produce the same outputs, with no hidden dependencies or interactions.

However, the example provided in Clean Code is somewhat incomplete and misses critical aspects.

"Consider, for example, the seemingly innocuous function in Listing 3-6. This function uses a standard algorithm to match a userName to a password. It returns true if they match and false if anything goes wrong. But it also has a side effect. Can you spot it?"

public class UserValidator {
    private Cryptographer cryptographer;

    public boolean checkPassword(String userName, String password) {
        User user = UserGateway.findByName(userName);
        if (user != User.NULL) {
            String codedPhrase = user.getPhraseEncodedByPassword();
            String phrase = cryptographer.decrypt(codedPhrase, password);
            if ("Valid Password".equals(phrase)) {
                Session.initialize();
                return true;
            }
        }
        return false;
    }
}

"The side effect is the call to Session.initialize(), of course. The checkPassword function, by its name, says that it checks the password."

He implies that renaming function would get rid of side-effects: "we might rename the function checkPasswordAndInitializeSession, though that certainly violates 'Do one thing'"

This analysis misses several critical issues:

  1. UserGateway.findByName(userName) - From the name this looks like a remote call to some-kind of storage, which is also a side-effect. And it also creates temporal coupling: the checkPassword would fail if there is no connection to the UserGateway.
  2. UserGateway is a singleton - i.e. it is a global implicit dependency.

A more formal understanding of side effects helps spot issues more easily.

This also makes it obvious that moving input arguments to object fields, especially when function has to mutate them to keep intermediate state, is incompatible with idea of side-effect free.

In rewrite suggestion from chapter 2, all new methods have a side effect - they mutate fields - Modify "state outside the function's scope":

Original Code:
private void printGuessStatistics(char candidate, 
                                  int count) {   
    String number;
    String verb;
    String pluralModifier;
    if (count == 0) {
        number = "no";
        verb = "are";
        pluralModifier = "s";
    } else if (count == 1) {
        number = "1";
        verb = "is";
        pluralModifier = "";
    } else {
        number = Integer.toString(count);
        verb = "are";
        pluralModifier = "s";
    }
    String guessMessage = String.format(
        "There %s %s %s%s", verb, number, 
        candidate, pluralModifier
    );
    print(guessMessage);Interracts with the outside world - prints to STD-IO
     
     
}
Proposed rewrite:
public class GuessStatisticsMessage {
    private String number;
    private String verb;
    private String pluralModifier;
  
    public String make(char candidate, int count) {
        createPluralDependentMessageParts(count);Calling a function with side effects spreads those effects to the caller
        return String.format(
                "There %s %s %s%s", 
                verb, number, candidate, pluralModifier );
    }
  
    private void createPluralDependentMessageParts(int count) {
        if (count == 0) {
            thereAreNoLetters();Calling a function with side effects spreads those effects to the caller
        } else if (count == 1) {
            thereIsOneLetter();Calling a function with side effects spreads those effects to the caller
        } else {
            thereAreManyLetters();Calling a function with side effects spreads those effects to the caller
        }
    }
  
    private void thereAreManyLetters(int count) {
        number = Integer.toString(count);Modifies state outside function scope
        verb = "are";Modifies state outside function scope
        pluralModifier = "s";Modifies state outside function scope
    }
  
    private void thereIsOneLetter() {
        number = "1";Modifies state outside function scope
        verb = "is";Modifies state outside function scope
        pluralModifier = "";Modifies state outside function scope
    }
  
    private void thereAreNoLetters() {
        number = "no";Modifies state outside function scope
        verb = "are";Modifies state outside function scope
        pluralModifier = "s";Modifies state outside function scope
    }
}