Twelve Principles of Good Software Design: An Introduction.

Introduction

I started my series of posts about my 12 principles of good software design in part as a criticism of the various software practices I’ve seen while writing software, and in part as a prescription for a possible answer to help developers write better code.

I know this sounds arrogant. But after nearly 28 years of writing software, I’ve seen quite a bit. I’ve seen a software team where developers were encouraged to “refactor” their code, which featured what should have been three or four methods turned into a 20-class library, with layers on layers of layers of “refactoring” piled on top. (My refactoring? Spending a week understanding the code, then rewriting it into a single four method class.)

I’ve seen code which looked like it was assembled by throwing darts at a design pattern dart board, with patterns which made absolutely no sense for the context in which it was used. (As an example, I’ve seen publish-subscribe to tie views to the view controller–an extra bit of machinery which makes it harder to figure out the relationship between views and the view controller which manages it.)

I’ve seen over-engineered applications created by people who were testing their own pet theories, such as trying to “offload” functionality out of a view controller by using a “mediator” and an “interactor” class which only serve to obscure the functionality of a single view controller behind two additional classes which serve no real purpose.

I’ve seen people create applications using a misunderstanding of certain design patterns. For example, I’ve seen someone implement a user interface using “Model-View-Presenter” in conjunction with a fourth class that implemented a Mediator pattern which tied models directly to views. The “mediator” was nearly a no-op: once the model and the views were tied together it did little but pass data back and forth without interpretation. And when I pointed out that several hundred lines of code could be removed from his code by eliminating the useless “mediator” and tying the views directly to the model (effectively implementing Model-View-Controller), I was told that MVC had been discredited as a design pattern and MVP was a superior design pattern.

It’s not just limited to stuff seen “on the ground” by random developers. I’ve also seen people fall on the Model-View-ViewModel bandwagon without understanding what MVVM is, how to best implement it, or even why they’re implementing it, other than it somehow makes your software better and stronger. Like Popeye eating spinach: shut up and eat it, because it’s good for you.

And even in the justifications for MVVM in various tutorials for MVVM, you find nonsense like this:

The definition of MVC – the one that Apple uses – states that all objects can be classified as either a model, a view, or a controller. All of ‘em. So where do you put network code? Where does the code to communicate with an API live?

But if you read the original papers covering MVC, you find a few things. First, you find the obvious answer to the above question: network code lives in the model. You also find there are no requirements on how the model be structured. And while asynchronous calls can be tricky, hiding asynchronous calls through key-value observing does not suddenly alleviate you from the need to understand the asynchronous nature of your application; it simply moves the icky parts “off-stage” where you can’t easily find them.

(Its not to suggest MVVM does not make sense; certainly in Microsoft’s development environment, MVVM is a preferred design pattern given the way the development tools have been designed. But hoisting MVVM into a generic environment requires building a lot of extra machinery which leads to confusion, rather than clarity. And in many cases it attempts to “solve problems” which only exist because of a misunderstanding of the original design patterns you presume are broken.)


At some point, in all this nonsense, it’s hard not to snap, and perhaps try to impart whatever little scraps of wisdom you’ve picked up over the past three decades.

If this series of blog posts is completely worthless to you, then my apologies for wasting your time. But I believe my twelve principles, if allowed to inform your approach to writing and simplifying software, may help you write better code.

By “better”, of course, I specifically mean code that is easier to understand, easier to maintain, and which can be handed off to another software developer without undue consternation. Now, naturally all software handed to another developer is generally met with derision; a lack of understanding can make even the clearest code seem poorly written and badly put together. But at the very least you can try to make the task of the next developer who has to look at your code a little easier.

These principles, by the way, are not really design “patterns”, but more guidelines that one can use when deciding how to put together your application.


The tyranny of choice

When you think about the problem of software development in general, you come across a curious thing. Unlike most professions where how you do your job is rigidly defined by legal constraints, software developers have the unique problem of creating projects almost entirely out of whole cloth, without any real constraints whatsoever.

Take, for example, the problem of building a house.

In the United States, in order to build a house, you must first draw up plans which take into account the location of the property, the legal limits of the property (setbacks, zoning limits, orientation, altitude, latitude, environmental surroundings), and submit a set of plans describing how the house will be built to a government building commission who reviews the plans to verify it meets the legal requirements for constructing the house. While many of those legal limits are established from underlying engineering principles, from a practical perspective house plans are constrained by legal limits rather than engineering limits.

Then a number of subcontractors are hired, each of whom have legal authority (through a licensing board) to perform specific tasks, which are completed in a given order: the lot is cleared (and rubble disposed of according to a legal framework established by the local municipality, state and Federal government), the foundation framed (and inspected by a government inspector), poured (and inspected by a government inspector).

The attachment points on the foundation are then used to frame the house (constructed in strict accordance to the requirements on the plans, and verified by a government inspector). Then the plumbing and wiring is drawn through the frame (and inspected by a government inspector), the walls are filled with insulation and covered with sheetrock or with an outer covering (like stucco or siding, all engineered according to government regulations, and inspected by a government inspector). Roofing material is also added (and inspected), and the house is finished (and inspected), and if the job was done correctly according to rigidly defined limits established by law, a government official signs off on the house, stating that the house was constructed according to these strict government limits and is therefore suitable for habitation.

And this is true of most professions and most of the things in our lives. Cars are designed by law to have a hump or void in the front hood which is designed to absorb the impact of a pedestrian, to have crumple zones which cushion the passengers from a front-end impact with another car, and in the future to have backup cameras so drivers have better visibility when the put their car in reverse. Furniture makers are required to use approved non-flammable materials, to make sure their furniture fits through the opening of a front-door (whose size is defined by building code), to make sure it contains no small bits which can break off and represent a choking hazard. Even designers laying out the cubicles for a floor plan must submit their plans for review to the fire department in many municipalities to make sure the building can be evacuated according to government regulation.

Software developers, however, have no such constraints.

Sure, we are often told what sort of application a project manager wants. We may be told the target platform (desktop, mobile, embedded), and that may define the development tools we can use and the programming language we are using. But in a world where embedded processors have more computational power than high performance workstations did 20 to 30 years ago, even embedded development generally is not constrained by processing power.

And within these rather loose constraints we are free to do whatever we wish.

The tyranny of choice.

From whole cloth how do we decide where to start? How do we decide how to start designing and building our application?


Design Patterns

A design pattern is defined as a general repeatable solution to a commonly occurring problem in software design. In general software design patterns describe a fixed and well established solution to a common problem which, in conjunction with other design patterns, can be used as fundamental building blocks to creating a computer program.

The concept of design patterns dates back to the early 1980’s, but did not really gain popularity until the publication of the book “Design Patterns: Elements of Reusable Object-Oriented Design” was published in 1994. In it, the so called “Gang of Four” outlined a number of notable “design patterns” commonly seen in software development, each solving a well-defined problem and providing a fixed solution to that problem.

Take the problem of one component in your application notifying another component in your software of a state change. For example, you may have a word processing program where a chunk of code responsible for saving your file needs to notify another chunk of code responsible for updating the window’s appearance that your file successfully saved—so the window’s title can be changed to show the file was written to disk.

A specific solution to this problem is the Publish-subscribe pattern. In this pattern, an object is created which holds a list of interested subscribers who wish to receive notifications about a given topic. Publishers can then send out notifications by handing them off to this object, which is then responsible for sending the notifications out to all interested subscribers.

This design pattern has the advantage that a publisher does not need to know about the subscribers; it simply sends a notice. And a subscriber does not need to know anything about the publishers; it can simply listen for notices.

So in our example, we can define a new notification, called “file saved”, which then indicates as part of the notice the file that was saved.

Our window UI can then subscribe for all “file saved” notices, and when it receives a notice that the file it is responsible for is saved, it can update the window’s state.

Likewise, our file system code, when it is done saving a file, simply sends a “file saved” notice with an indication of which file was actually saved.

This design pattern helps solve a specific problem. It also has the nice property that, in the future, if our window code changes (such as, for example, instead of showing if the file is saved by updating the window’s name, we instead display a checkbox inside the window’s content area), the file save code never needs to change. We can, instead, create a view object with a checkbox that listens for notifications, and shows or hides the checkbox as appropriate.

This separation of concerns helps make our code easier to understand, by allowing us to build our code in reusable chunks, and by having those chunks wired up in well-defined ways. By separating out the part of the code which saves the file from the part of the code that shows if the file is saved, we can change how we show how our file is saved without having to dive deep into the file save code. We just hook up a new subscriber to our publish-subscriber system.


Yet somehow, even within all these patterns, within all of these methods which allow us to construct our applications out of whole cloth, somehow projects still fail. They are still hard to maintain. Our projects go over budget. Deadlines are missed. Even if our design patterns are properly understood and properly designed, somehow we still are writing code that is hard to maintain, and sometimes we’re writing code that simply does not work.

All this failure suggests to me that perhaps we’ve lost sight of the real problem. Perhaps we need a different point of view.


Programming Idioms

Because the term “design pattern” is so well known, I’d like to use a different term to capture something a little more general. The intent is to capture the idea of how we assemble programs—not as a rigid solution to a fixed problem, but more the “style” of the code and the ways we express ourselves in software.

For this, I’m using the phrase programming idiom. Like a language idiom (a way a language is spoken in a given area or a style that is characteristic of a particular subject), a programming idiom is intended to capture the idea of how we organize the code we write. This not only applies to rigid solutions to fixed problems (such as with design patterns), but also applies to how we think of grouping statements into a function, how we insert white spaces between groups of statements (to show functionality), and how we choose the names of variables or functions or classes.

But we’re not really talking about style; to me, programming style is the overall umbrella; it is how we may write a book or organize the chapters or the paragraphs. An idiom, on the other hand, implies something looser than a design pattern, but not as broad as a programming style.


“Design Patterns” as Programming Idioms

I contend that it is useful to separate out design patterns which represent perhaps a fixed way to solve a given problem, and programming idioms which represent a more general way we organize code.

And I further contend that not all design patterns are really design patterns, but are instead programming idioms.

For example, a design pattern (a fixed solution to a given problem) would be the singleton pattern. A singleton could be expressed in a variety of different ways (naturally), but the general pattern of the solution is the same: a single entry point returns our singleton object, creating it if necessary. In C++, for example, we could write:

MyThing *MyThing::Get()
{
    static MyThing *singleton;
    if (singleton == NULL) {
        singleton = new MyThing;
    }
    return singleton;
}

The particulars of the implementation could, of course, change: we could use the pthread library to make sure our method is not re-entrant. We could declare the method as a function instead of as a class function. We could use a variety of other names for the method. We can even construct our object in advance rather than construct the object inside this method. But the essence of the pattern is exactly the same: we call something which returns the only object in our system.

The same can be said of the Publish-Subscribe pattern: we could have a single notification system (such as Apple MacOS X’s NSNotificationCenter object), or multiple notification objects. But the essence of the pattern remains the same: a subscriber subscribes for notifications, a publisher delivers notifications to the object the subscribers subscribed to for notifications.

Some “design patterns”, however, are not as clean as this. For example, take the Decorator pattern. The motivation of this pattern comes from user interface design, whereby individual components can have additional “decorations” added to them without affecting the way the underlying component operates. For example, windows in your user interface may not have scroll bars by default; one way to allow your system to add scroll bars is by having a mechanism by which the interface of the window can be extended; a decorator then provides a way to add scroll bars to the window. The decorator mechanism can then allow multiple other decorations to be added, such as a border or a title bar. This keeps your basic window simple: it’s just a region of the screen. And for each new feature you want to add to your window, you add a decorator rather than make your window code more complex.

Unlike the singleton example, however, there are multiple ways to approach the problem of creating a window which allows decorators to be attached.

Consider also the model-view-controller pattern. There are as many ways to write views (the components in a user interface) as there are user interface libraries, and different libraries provide different degrees of support for controller code (from none at all, as was the case in Windows 95, to a very rich and expressive system such as on Apple’s iOS), and many libraries attempt to provide tools to make creating the model code (the code which represents the data in your application), from basic objects (such as Apple’s MacOS X’s NSDocument class) to the more complex.

I would contend these are not “design patterns” at all, as a design pattern represents a more rigid solution to a fixed problem. These architectural “idioms” represent a more complex way to view code than the fixed solutions given in a design pattern.

Idioms may also concern themselves the smaller chunks of code than architectural design. For example, take the way we group chunks of code into reusable functions. There is no fixed solution to how we group statements into functions or methods. Some books attempt to give advise based on folklore; I’ve read one book which suggested functions should be no longer than 24 lines long, representing the most code that can be displayed on a VT-100 terminal.

Thankfully it is not 1985 anymore. But that doesn’t alleviate the problem.


Programming Idioms as ways we express ourselves.

At the bottom of the stack, programming is essentially as much expressive art as it is engineering. While programming has at its core the requirement that code compile, execute and perform the tasks we desire in a correct and reliable fashion, that really is no different than technical writing. Our goal is to express an idea in a way which is correct, and which communicates our intent clearly. 

Programming also has other goals, of course. We should be able to hand our code off to other people so they can fix problems that may come up later. We should be able to cooperate with other people in writing more complex systems. We should be able to go back to code we wrote months or years ago and understand what we were doing. We should be able to find problems in the code we wrote during development.

But ultimately this is an art. It may be more akin to industrial design (such as designing a vacuum cleaner or a chair) than it is to more artistic endeavors (such as painting); the product of our work has a clear function it must satisfy. But that doesn’t change the fact that we are creating something against a blank slate using phrasing that is familiar to us, and which helps express to the next developer what it is we are doing.

And, I think, it is important to separate out “design patterns” from “programming idioms” so we can remember that not all problems are rigidly defined or have only one possible solution. We also need to keep from falling into the trap that previous idioms are somehow fixed design patterns which we must discard because our problem doesn’t fit the rigid definition we learned from someone else. Sometimes that can cause us to write the very complicated spaghetti code we are striving to avoid.


With this idea of programming idioms in mind, and with the problem framed: that what we really need is a new point of view if we are to write better software and escape ruts that were never really fixed over the past 30 years, we can move onto what makes for a good programming idiom.

What makes for good development style.

12 Principles of Good Software Design.

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s