Building Changeability in Design

March 17, 2014

Development teams everywhere are under increased pressure to deploy more often. In the age of Internet, we can instantly delight thousands of users through a small improvement. It’s rare nowadays even for business customers to be happy with release cycles of years; one year for a new release sounds too slow. A few months is the norm.

Reducing the length of release cycle is impossible without designing the software with change in mind. Yet we have learned in the hard way that designing too much flexibility for the future brings more trouble than benefits in the medium and long run. That’s how the YAGNI (“You Aren’t Gonna Need It”) principle was born.

Developers everywhere are challenged to find the balance of designing just enough flexibility into the system. What can help us meet this challenge?

Why Software Design?

To answer this question, we need to go back to basics. What is software design? Who needs it? Why?

Not that many years ago, many programs were written in a language similar with this:

section	.text
    global _start   ;must be declared for linker (ld)
_start:	            ;tells linker entry point
    mov	edx,len     ;message length
    mov	ecx,msg     ;message to write
    mov	ebx,1       ;file descriptor (stdout)
    mov	eax,4       ;system call number (sys_write)
    int	0x80        ;call kernel

    mov	eax,1       ;system call number (sys_exit)
    int	0x80        ;call kernel

section	.data
msg db 'Hello, world!', 0xa  ;our dear string
len equ $ - msg     ;length of our dear string

(source: <a title="Assembly Programming Tutorial" href="http://www.tutorialspoint.com/assembly_programming/index.htm" target="_blank">http://www.tutorialspoint.com/assembly_programming/index.htm</a>)

With just a few simple keywords and an intimate knowledge of the CPU internals, programmers were able to write code in the assembler language. This is probably the fastest implementation of “Hello world”, so why aren’t we using assembly today?

It had a few problems. First, the code is difficult to read. Writing large applications would be very difficult in this language. Second, the code needs to be rewritten or changed for each CPU it works on. In other words,we needed another language for programmers, not for computers.

I have come to the conclusion that this is the essence of software design.

Software design means structuring the code so that:

  • It does what is expected
  • It optimizes for desired business characteristics, usually fast change

There are two parts of this definition that deserve more explanation: how we structure the code and how we optimize for fast change.

Abstractions

The “Hello world” program in C looks quite different from the assembler version:

#include <stdio.h>

int main(void)
{
  printf("Hello World\n");
  return 0;
}

What’s different? There’s a library of functions called “stdio.h”. There’s a main function. There’s a printffunction used from that library. The main function returns an int value.

When we talk about design, things like: types, functions or libraries are called abstractions. We use abstractions to structure the code in easy to understand bits. Programmers read the code above easier than the assembler code. Computers couldn’t care less; give them any correct machine code and they’ll do the job.

Abstractions have evolved in time. Let’s see the equivalent program in Java:

class HelloWorldApp {
    public static void main(String[] args) {
        System.out.println("Hello World!");
    }
}

Most of the abstractions are the same as in the C version. The newcomer is the class. A class is a different type of abstraction, one that combines data (value members) and behavior (member functions)  in order to contain complexity. Again, computers don’t care; containing complexity is only important for programmers.

Different design paradigms exist, and they are defined by the abstractions used. These examples come from the structured paradigm, that later evolved into the object-oriented paradigm. The functional paradigm has chosen a different type of abstraction: the function. It later evolved into lambda calculus and higher-level abstractions like monads.

Optimize for Fast Change

The C version of the “Hello world” program has a very interesting characteristic. If we change the implementation of printf in the stdio.h file, the program will do something completely different without changing one line of code! This basic observation shows how you can structure your design so that it’s easy to change: clearly separate responsibilities in different abstractions.

The good news is that we can do even better. Take a look at this piece of code:

class HelloWorldApp {
    public static void main(String[] args) {
        new HelloWorldPrinterToStream(System.out).printHelloWorld();
    }
}

interface IHelloWorldPrinter{
    void printHelloWorld();
}

class HelloWorldPrinterToStream implements IHelloWorldPrinter{
    public HelloWorldPrinter(PrintStream stream){
        this.stream = stream
    }

    public void printHelloWorld(){
        stream.println("Hello world!");
    }
}

(I realize this design is an overkill for this very simple example. Please bear with me, because it does have an important point.)

The difference is small but important. To replace the implementation that writes at console with an implementation that writes to a web service, log or messaging system, you need to do two steps:

  • Write a new class that implements the IHelloWorldPrinter interface
  • Change the reference in main to call the new class

You have probably met this pattern before: it’s called Dependency InjectionDependency Injection is characterized by one interesting fact: an implementation (HelloWorldPrinterToStream) is injected from the main entry point of the program, configuring the rest of the program. It is very used nowadays, and for an important reason: changing code is safer when using it. To see why, you will need to imagine that this piece of code is part of something much bigger; let’s say 2000 classes that all use println. If println is used everywhere as in the first Java version, you are faced with the two most common software problems:

  • Fragility: Any change in the implementation of System.out.println() propagates through all calls and programs that use it. One error and everything will break down.
  • Rigidity: You could implement instead a new method called printLnToWebService(). For any large program, you will need to manually replace all calls to println() with calls to printLnToWebService(). Needless to say, making the change and testing for it will take a time directly related to how used println()is.

Using dependency injection means that you can make the change safely because you add code (which is safer than changing code) and inject the new implementation in exactly one place. As an additional advantage, it is very simple to unit test it because it’s isolated.

Imagine now that when you run the program you can pass in a command line argument that is the name of the implementation to use to write the message. The simple “Hello world” program has become extensible on one axis: message writing. Any new implementation is allowed as long as it follows two constraints: no exception is thrown and avoids concurrency issues.

Principles and Patterns

Dependency Injection is a commonly used design pattern. The examples above have shown how software professionals identified in time design patterns. They come from practice, not from theoretical ideas, and keep appearing in programs. Because they kept appearing, a group of people decided to document them. That’s how we got the Gang of Four “Design Patterns” book and the Pattern Languages of Programs (PLoP)communities.

But how did we get to the above design? We tried to optimize for flexibility and identified a few ways to do it: separation of responsibilities, adding new features with minimal changes to the existing code, identifying constraints for extensions. These are concerns that were discussed for a long time in the community and were documented as sets of design principles. The set of principles that focuses the most on changeability is called SOLID principles.

I will describe in the next articles more about design patterns and SOLID principles. Until then, let me know in the comments what design challenges you face. Also, if you can’t wait to learn more about SOLID principles and Design Patterns, learn the theory and practice them during our very intense workshops.

Categorised in:

1 Comment

    Leave a Reply

    Your email address will not be published. Required fields are marked *