Code Clarity: Thinking About Code as Questions & Answers

Anyone can write code a computer can understand. It takes an expert to write code a human can understand. In this post we are going to look at an example of how to add clarity to our code by thinking about our logic as a set of questions & answers.

Code Clarity: Thinking About Code as Questions & Answers

There is a direct correlation between how much brain power I have to use to read your code & how bad I think your code is; The more brainpower I have to use to understand your code, the worse I think your code is.

If you don't take the time to make your code clear, I'm going to assume you didn't take the time to do anything well. That said, it is all to easy to make code into an incoherent mess.

Have you ever tried to read a book (or blog post) that didn't have clear sentences? It's not fun, is it? Code is the same way. If the intent of the statements and logic flow isn't clear the reader will get lost.

Anyone can write code a computer can understand. The hard part is writing code a human can understand.

In this post we are going to look at an example of how to add clarity to our code by thinking about our logic as a set of questions & answers.

Let's say we have two classes. An AppUser who can sign in to our application & a SignInManager who performs the action of signing in.

public class AppUser
{
    public bool IsActive { get; }
    public bool IsDeleted { get; }
    public string Password { get; }
    
    // Other properties
}


public class SignInManager
{
    public void SignInUser(AppUser user)
    {
        // Is the user allowed to sign in?
        if (user.IsActive && !user.IsDeleted && !String.IsNullOrWhiteSpace(user.Password))
        {
            // Sign in the user
        }
    }
}

In this example, we have a single question; "Can the user log in?". To answer that question, we need to check three properties on the user object: 1) Are they active? 2) Is their record in a deleted state? 3) Do they have a password?

That is a perfectly acceptable question to ask & our criteria is perfectly understandable to check. The problem is, that the SignInManager now has to keep track of multiple things: 1) How to know if a user is allowed to sign in & 2) how to sign in an AppUser. With a single if block we have violated the Single Responsibility Principal of S.O.L.I.D.

The Single Responsibility Principal indicates that a class should only have 1 reason to change. Our SignInManager example now has (at least) two. What if the way we sign in an AppUser changes? That's an update. We need to make sure the user's account doesn't have an outstanding balance before they log in? That's another update.

So how can we organize our code so that the SignInManager doesn't have to shoulder all of the responsibilities & we can closer align with S.O.L.I.D?

To answer that first we need to figure out who can answer the question. Right now the answer to that is the SignInManager. It is answering the question just fine as is. Another possibility is the AppUser. All of the criteria needed to answer the question exist inside it already.

Between the two options we can ask the question who should answer the question. Does the SignInManager continue managing the answer? Should the AppUser be smart enough to provide the answer? Should we bring in a third party to manage it?

We have established that the SignInManager should not answer the question; That class already has responsibilities for signing in users. Creating a whole new class to answer a question this simple would probably be overkill. So that leaves us with the AppUser class.


Our AppUser is a pretty straight forward class. As of right now, it only contains various properties & isn't doing any kind of logic (In C# these types of classes are refered to as POCOs). If we were to put a label on what the AppUser's responsibilities were, it would be "To provide information about the state of an app user".

With that definition, we can safely say that the AppUser class should be answering the question "Am I allowed to sign in?".

With that in mind, let's see what the code would look like if we moved the answer to that question to the AppUser class.

public class AppUser
{
    public bool IsActive { get; }
    public bool IsDeleted { get; }
    public string Password { get; }
    
    public bool HasPassword => !String.IsNullOrWhiteSpace(Password);
    
    public bool CanSignIn => (IsActive && HasPassword && !IsDeleted);
    
    // Other properties
}


public class SignInManager
{
    public void SignInUser(AppUser user)
    {
        if (user.CanSignIn)
        {
            // Sign in the user
        }
    }
}

Now the SignInManager knows it needs to ask the question "Can the user sign in?" without also knowing how to answer it. The AppUser knows all of the criteria needed to answer the question & provides a simple way to get that answer.

This is a super small change in code organization but, it provides a huge gain in readability for the developers.

You might also have noticed the AppUser class can also answer another question: "Do I have a password?". When we start thinking about our object relationships as questions & answers, we will start noticing more and more places that can be written more clearly.


All code has questions & answers. When it comes down to it branching logic is just our codes way of asking questions about the current execution. The problem we run into as developers is asking the question & answering the question in the same place. This leads to bleeding concerns & added complexity in places it isn't really needed.

When we change the lens we use to examine our code, examples of this will jump out almost immediately. When they do, it will be worth your time to make the small change needed to make the code readable. Your future self will be glad you did.