<CharlieDigital/> Programming, Politics, and uhh…pineapples

30Mar/12Off

Procedural vs. Structural Code

Posted by Charles Chen

In working with my development team, one of the things I've been working on is to figure out how to get them to be more object oriented. I've written about this topic before and I continue to evolve my expression of this idea to other developers that I work with.

To me, beyond the simple textbook definition of object oriented programming, at the level of construction, what it really boils down to is expressing logic structurally as opposed to procedurally. That is the very basis of what I've been trying to communicate and trying to understand how to impart that on other developers.

As a basic example, let's consider a user management API that has three operations for add, update, and delete.  Suppose I want to send a sequence of actions to this API.  We see this type of pattern of interaction all the time.  Assume we have a repository class:

public class UserRespitory
{
    public void Add(User user)
    {
        Console.Out.WriteLine("Added!");
    }

    public void Delete(User user)
    {
        Console.Out.WriteLine("Deleted!");
    }

    public void Update(User user)
    {
        Console.Out.WriteLine("Updated!");
    }
}

I've left the implementation of the actions intentionally simple.  Now assume that we have an operation and a user class like so:

public class Operation
{
    private readonly string _type;
    private readonly User _user;

    public Operation(string type, User user)
    {
        _type = type;
        _user = user;
    }

    public string Type
    {
        get { return _type; }
    }

    public User User
    {
        get { return _user; }
    }
}

public class User
{
    private string _username;

    public User(string username)
    {
        _username = username;
    }
}

Consider a use case where we must build an API to support bulk operations.  The typical implementation pattern that I will encounter will look more or less like this:

private static void Main(string[] args)
{
    List<Operation> inputs = new List<Operation>
    {
        new Operation("Add", new User("Charles")),
        new Operation("Update", new User("Steve")),
        new Operation("Delete", new User("John"))
    };

    UserRespitory repository = new UserRespitory();

    foreach (Operation operation in inputs)
    {
        switch (operation.Type)
        {
            case "Add":
                repository.Add(operation.User);
                break;
            case "Update":
                repository.Update(operation.User);
                break;
            case "Delete":
                repository.Delete(operation.User);
                break;
            default:
                throw new InvalidOperationException();
        }
    }
}

This is pretty much textbook procedural code and, as a consultant, this is pretty much the code I expect to see (and have seen countless times) on projects.  Of course, this is a pretty harmless case; in measuring the cyclomatic complexity and maintainability index of this code using Visual Studio's built-in analysis tools, I get values of 7 and 67 for each respectively.

Already a 7 with just a basic implementation.

Cyclomatic complexity is a good tool because it gives us a quantitative metric of code quality that is indifferent to "style" or any qualitative prejudices that individual developers might have -- it's a pretty good starting point for determining where to focus your time in terms of code reviews and refactoring.  With that said, a value of 7 is certainly not bad (the NIST guidelines recommend a limit of 10 for methods), however, I think we've all encountered this before: if-else's nested under switch's nested under if-else's nested under for-loops and so on.  When I see this pattern, I usually encounter it in the 20-30 range (and I've seen it in the 100+ range, sadly (an extract from a 1078 line method with cyclomatic complexity of 136 in production code -- I couldn't zoom out far enough to capture the whole thing)) for cyclomatic complexity simply because the developer doesn't know when to stop the madness.

Well, let me correct that last statement: when a developer starts down this path and once they've gone deep enough, there is simply no way that they can recover from the madness without structurally refactoring the code; the switch-case traps you into a cycle of cancerous code unless you can find a different way of representing the same logic in a more modular, orthogonal manner.  To me, this is really the heart of understanding object oriented programming.  How we achieve this -- in this case -- is quite simple, really.  For starters, we make the operation abstract:

public abstract class Operation
{
    private readonly User _user;

    public Operation(User user)
    {
        _user = user;
    }

    public User User
    {
        get { return _user; }
    }

    public abstract void Execute(UserRepository repository);
}

We also add an abstract method called Execute which every inheriting class must implement -- it's here that we've captured the essence of the switch-case in a much more structural, object-oriented manner.  Instead of a switch statement, we build the same logic structurally into the code by using three classes:

public class DeleteOperation : Operation
{
    public DeleteOperation(User user) : base(user) {}

    public override void Execute(UserRepository repository)
    {
        repository.Delete();
    }
}

public class AddOperation : Operation
{
    public AddOperation(User user) : base(user) {}

    public override void Execute(UserRepository repository)
    {
        repository.Add();
    }
}

public class UpdateOperation : Operation
{
    public UpdateOperation(User user) : base(user) {}

    public override void Execute(UserRepository repository)
    {
        repository.Update();
    }
}

The abstract method now gives us a way of specifying a different action for each "case" by creating another class.  Now our main code can be updated:

private static void Main(string[] args)
{
    // Structural representation of the logic
    List<Operation> inputs = new List<Operation>
    {
        new AddOperation(new User("Charles")),
        new UpdateOperation(new User("Steve")),
        new DeleteOperation(new User("John"))
    };

    UserRepository repository = new UserRepository();

    inputs.ForEach(i => i.Execute(repository));
}

What is our end result?

Improvements in maintainability and cyclomatic complexity

The change in the code itself was very minor; we performed a slight-of-hand and simply moved the three logical branches in the switch into three separate classes instead with each class inherently representing one of the switch cases.  In doing so, however, the cyclomatic complexity for the main program halved to 3 and the the maintainability index increased 6 points, even with such a barebones change.

The overall maintainability index of the entire solution increased 6 points.  Our cyclomatic complexity for the solution did increase, but that's because we've added three new classes which adds to the sum.

There are several other advantages that are easy to extend from this point:

  1. It's easier to add new operations.  Instead of adding another case, we simply add another operation class and inherit from the abstract base class.  The logic for that particular operation -- for example, business logic or setup before the operation is invoked -- can be encapsulated in the new class instead of in the case.
  2. It's easier to add more logic around the operations.  Because we've removed the invocation from the switch, we can avoid a giant if with deeply nested code as the logic around the operations change.  With a class for each operation, we can add operation specific logic to the class instead.  If we need to add common logic, we can use the base class.
  3. It's easier to read.  I think the code is easier to read simply because the level of nesting is now greatly reduced.  McConnell writes in Code Completed, 2nd Edition:

    "The smaller part of the job of programming is writing a program so that the computer can read it; the larger part is writing it so that other humans can read it." (McConnell, 733)

  4. It's less prone to user error.  By removing the switch and getting away from a string comparison, we've made the solution more resilient to input errors.

Overall, it's a very minor change in the structure of the code (check it out, even the LoC is the same), but even in this most basic of scenarios, it yields clear objective and subjective improvements.  While this is a very basic example of a very specific case, the underlying idea here -- structural representation of logic and flow -- is the real essence of object oriented programming.  The intentionally simplistic example is meant to (hopefully) help surface this concept, but the general idea of refactoring to structural representations is applicable to many different use cases, scenarios, and coding patterns.

Code samples: SampleProcedural.zip and SampleStructuralCode.zip

Filed under: Dev No Comments
25Mar/12Off

Book Review: 17 Equations That Changed the World

Posted by Charles Chen

What could be more boring than mathematical equations?  The majority of folks would be hard pressed to find something to answer that hypothetical query. Myself included :) I'll be honest, I'm a math minor and I picked up this book on a whim in a bookstore thinking to myself "Now why would anyone want to write or buy a book on 17 equations?" I flipped through it and immediately knew that I had to consume the rest of this book.

What Stewart is able to do is to take these 17 equations that manifest in everything we do, everything we observe, everything bit of space around us and bring life to them. He presents the opening of each chapter with a concise summary of these equations that helps immensely in revealing the underlying nature of the equations and then goes into the history of the creation (discovery?) of each of these equations and it's been an eye-opening read.

As an example, having majored in computer science, I worked constantly with logarithms and natural logs (there's lumber joke here somewhere) but never once understood the nature of logarithms. How did they come about? Why do they exist? What problem do they address? Just what in the heck is a logarithm? I knew them only in the abstract -- as operations that yielded a result; I knew them as a general pattern but not the nature of the logarithm. The second chapter simply blew me away with the clarity and simplicity with which Stewart was able to pull back the covers on what logarithms actually mean. No one in my years of formal education had bothered to explain it in the same way that Stewart does in this book.

While I cannot say that this book is for everyone, I will say that I find it is surprisingly approachable for most folks who are scientifically or mathematically inclined. Certainly, there are many equations and plenty of mathematics (and it gets especially complex (pun intended :) in the later chapters. However, I think this book is still immensely readable and approachable, even for those who have never ventured deep into the vast field of mathematics or have long moved past their days of calculus, linear algebra, and so on. I, for one, will make sure that my daughter reads the chapter on logarithms as it starts to seep into her curriculum one day to make sure she understands the "why" and so that she has an appreciation for all of the history and magic behind that little "log n" button on her calculator.

This book is incredibly well written, well presented such that it is approachable for a large audience, an entertaining read, and highly recommended. If you've read this review to this point, you should probably just go ahead and by this book!