On Documenting a System...

What is good practice for understanding a system? What is the point of understanding a system? How and when does that understanding become outdated? These questions are relevant to system documentation - our topic. This article seeks to directly answer each of these in turn.

"Understanding a system" differs from "learning a system" in one key aspect: ownership. When we share ownership of a system we may successfully contribute to its change, which leads us to a crux of the problem: understanding a system is directly dependent upon its vectors of change. But to come back to ownership for a moment, that is the main point of understanding a system. If you have no need to own a system - there is no point in understanding it. On the other hand, the act of enhancing or fixing a system necessitates understanding it.

If owners must understand a system then what is good practice for doing so? We will begin to answer that by looking at how a system changes...and how understanding becomes stale. Let us examine an elemental part of a system: variables.

Documenting Variables

  IDbConnection con1 = new ConnectionFactory().Create(ACME_PRODUCTS);
  IDbConnection con2 = new ConnectionFactory().Create(CUSTOMERS);

Let's say these assignments appear early in a long, complicated method. At some point you or another developer may confuse con1 with con2. This isn’t good, so let’s fix it:

  // con1 is a connection to Acme's product database on CORPDEVSQL01
  IDbConnection con1 = new ConnectionFactory().Create(ACME_PRODUCTS);
  // con2 is a connection to our customer database
  IDbConnection con2 = new ConnectionFactory().Create(CUSTOMERS);

Better, although what we have done here is change the system by adding uncoverable code. There is no way to test assertions on the above comments. When the company switches distributors from Acme to Amazon - the comments will be stale and misleading. Or stating this another way - there are numerous changes around variables. With no assertable connection between code and comments, risk is high that our understanding will suffer. Another drawback to commenting variables is readability: they slow the reader down. That's definitely no good so let's fix it.

  IDbConnection distributorDatabaseConnection =
      new ConnectionFactory().Create(distributorUrn);
  IDbConnection customerDatabaseConnection =
      new ConnectionFactory().Create(customerUrn);

This version uses a good practice for documenting variables: use descriptive symbols; and another: name the instance, not just the type. This code has a lower staleness factor. Connection URN may change - vendors may change - but your understanding of the variables remains unchanged. Okay, simple enough; let us move on to behavior.

Documenting Behavior

Know your patterns and principles? Well have a gander at this method and try not to puke.

public void OnLogonUser()
{
  String username = Request.Form("username");
  if (null == username || username.Length == 0)
    throw new InvalidUsernameException("username may not be null or empty");
  if (Regex.IsMatch(username, ".?\d+"))
    throw new InvalidUsernameException("username may not contain numbers");
  String password = Request.Form("password");
  if (null == password || password.Length == 0)
    throw new InvalidPasswordException("password may not be null or empty");
  Account account = ACCOUNTS["username"];
  if (null == account)
    throw new InvalidUsernameException("username is not correct");
  if (! account.Password.Equals(password)
    throw new InvalidPasswordException("password is not correct");
  lock (account) { account.Status = AccountStatus.LoggedIn; }
}

Methods are the workhorse of any system (language syntax such as C# properties are methods sprinkled with syntactical sugar). All system behavior is contained within them. Thus to understand a system you must understand its methods. The thing is - we should be able to leave it at that. But frequently we find that multiple responsibilities - multiple vectors of change - are combined together as in the case above. To understand this part of the system you have to remember that OnLogonUser has responsibilities of: 1) Username validation; 2) Password validation; and 3) Logon status management in a table of accounts. Let's try another way.

public void OnLogonUser(IUser user, ICredential credential)
{
  itsUserValidationStrategy.ValidateUser(user);
  itsCredentialValidationStrategy.ValidateCredential(credential);
  user.IsLoggedIn = true;
  itsUserRepository.UpdateUser(user);
}

Much better, no?

What we have done here is to reduce the internal responsibility of OnLogonUser. There are other improvements to make here - but by refactoring the behavior the risk to your understanding of the method is minimal. This is a powerful tool for documenting a system. By creating singly responsible constructs (types, methods, etc.) we are documenting the system. When people say, the code is the documentation - it is important to know when this principle applies. Keep to a single responsibility - your code will document itself.

We have been working our way up to the big leagues in terms of understanding a system. There are two more areas left to discuss: documenting the domain and documenting the tests. In my next article we will see how to hitch your understanding of a system to its inevitable change. If he was alive today Marcus Aurelius Antoninus might have said,

The universe is change; the system is what our tests make it.

 

Stay tuned!