Software Design: Consistency & real life examples

April 19, 2016

While a lot of the design principles that we use in software can be found in other design disciplines, there is one very present in UX and graphical design that is very rarely discussed in software circles: consistency.

It’s not because we weren’t trying. The system metaphor idea from XP and the conceptual integrity idea from Frederick Brooks are just two of the notable essays for consistency. Yet there are very few systems who achieve a good level of consistency. The UNIX file system with its “everything is a file” philosophy is one of them. Eclipse, with “everything is a plugin” is another.

There are various levels of consistency, and I place the conceptual integrity and system metaphor very high on the hierarchy. I honestly don’t know a simple way to get there. Microservices seem to promise consistency at architectural level, but I have yet to be convinced.

My experience with Usable Software Design has shown that there are many other levels of consistency that can be achieved and are very useful for the daily development work. They can all be derived from one principle:

Clearly define your design entities

 Design Entities 

What artifacts can we use to design software? Simple: methods (or functions), classes and modules. Depending on your language, one or more of the above.

Classes are great because they can express a lot of different types. That’s also their problem. What if we don’t want everything a class can do? After all, your knife has limited uses, it doesn’t do everything you can do with steel.

Limiting behavior may sound scary to developers, but it’s actually in their favour. Imagine that your controller doesn’t let you save to the database; this means you know to not search for the save code in the controller. What if the only place where you can save to the database is a command object? Then you know to search in command objects for the save code. The code is smaller and therefore less prone to errors. It will be easier to change the save user code without changing anything else. And so on.

We want to constrain classes. We need to constrain classes. I call these constrained classes design entities.

 Examples of Design Entities 

I’ve recently redefined the design entities for our product, eventrix.co. I’ve defined in total 6 different design entities:

  • Controller
  • Command Object
  • Application Service
  • Database Service (Command)
  • Database Query
  • View Model

I used CRC cards to define these entities, and I also added how to test them. Here are just a couple of examples:

Controller

Responsibilities:

  • Delegate the operations on the request input
  • Render the correct view / template / error

Collaborators:

  • Command Objects
  • Application Services
  • View Models

How To Test:

  • Component Tests

Command Object

Responsibilities:

  • Validate the request
  • Delegate update / delete operations

Collaborators:

  • Database Services
  • Database Queries

How to test:

  • Unit tests

These entities are great for a number of reasons:

  • consistency: all features are implemented in the same way
  • less decisions to make when implementing: decision fatigue can appear when making a lot of difficult choices; there still a lot of things the developers have to decide
  • improved conversation: we have names for these design entities, and we can use them when discussing design or testing
  • fit for the framework: they were tested with our framework of choice, grails, and they work great

However, these design entities are by no means fixed and exhaustive. When defining them, I focused on covering 80% of the features we are implementing. When things will change, more design entities will appear and some might go away.

But isn’t this up-front design? It can be, if you’re doing it up-front. In my case, I came up with these design entities for this specific project after about 6 months of implementing various features. They also come from a very good knowledge of the framework and libraries we’re using. If you start by defining design entities, it’s very possible they will slow down the teams instead of helping them speed up.

The only way I know to come up with good design entities is by seeing many examples of features, (I estimate at least 10). So don’t start with design entities; start by implementing features and refactoring; soon enough you will start to see some patterns that will lead to more complete sets of constraints. And once you found them, don’t be afraid to change them when other situations appear.

These are just a few examples of constraints that you can apply on classes. Let’s explore other types of constraints.

 Constraints You Can Use Today 

Constraining design entities is not a new idea. In fact, you can use certain constraints right now:

  • Functional programming uses immutability as a constraint for functions and, therefore, most classes / objects
  • Design by Contract is a way of designing in which each method defines a contract of things that should be true before calling it (preconditions), things that should be true after calling it (postconditions) and things that shouldn’t change during its execution
    (invariants).
  • UML stereotypes are a way of describing constraints on classes.

The issue is with large scale use of constraints. Developers tend to shy away from constraints, fearing that they make development less creative and less interesting. I have yet to work in a project that wasn’t interesting or creative, no matter how many and what types of constraints we used.

Constraints are however unpopular. The typical project has no constraints, while some projects use unit testing to constrain the classes behavior. Immutability seems to catch on, while Design by Contract is rarely used. It’s a pitty; we have a lot of design challenges ahead of us, if only we could create more consistent code.

 A Plea For More Constraints 

I not only think that immutability, design by contract, unit tests or CRC cards for design entities (equivalent with UML stereotypes) should be used more. I’m advocating even more types of constraints. Here are a couple of examples:

Constrained inheritance

Too often we use generic classes such as String, Int or List to represent more specific things like name, duration or bag of cells (for code retreat participants).

The typical way to represent this in code is by encapsulating the primitive type and add constraints. But what if we could inherit instead from the primitive type and restrict access to the base methods?

Example:

class MyList extends List restrictedTo {add, remove, first, moveNext()}

Explanation:

restrict usage of base class methods for the callers of the derived class

Reasoning:

to reduce primitive obsession problems

Constrained collaboration

As you’ve seen in the eventrix.co design entities, an important part is restricting collaboration. I’ve seen too often database queries in the controller or even in the view. What if we could restrict what a controller can do so that the compiler tells us when someone breaks the rules?

Example:

// DesignEntities.java

designEntity Controller

can render, redirect

canCall service, command

// MyController.java

Controller MyController{

// Implement controller methods here

}

Explanation:

  • define what a design entity can do and what it can call
  • it won’t compile or throw an exception if something happens that’s not allowed

Reasoning:

  • to enforce separation of concerns

Discipline

Since we lack these capabilities in our languages, we have to constrain ourselves through discipline. I try to define as much as possible our design entities and make it very clear how to use them: how to name them, when to throw exceptions, what they can and cannot do, what are the collaboration patterns that apply to them etc.

This is only the beginning. I found that a team debate and agreement on the usage of the design constraints helps a lot. Code and design reviews then help everyone keeping others honest about this agreement.

Overall, anything that makes usage of design entities easier than the alternative helps. For example: code samples, usable code generation, support for testing are all good things to have that reduce the cost of using these entities over their alternatives.

 Constraints and Consistency 

You can probably see now how well defined design entities lead to the various levels of consistency. For example: consistency in naming, contracts, collaboration etc. Well defined design entities really mean that you know exactly where to go when you search a bit of code, where to write a certain part of the implementation, how to test it etc.

The best correspondent of design entities I found is in UI and it’s called “graphical language”. It’s a set of widgets a designer creates that are reused throughout the graphical interface to maintain consistency. If you are a web developer, you’ve probably seen one already.

 What If We Find A Use Case That Doesn’t Match? 

I rarely meet this problem. Yet when I do, it’s usually time to create a new module.

Consistency doesn’t have to apply in the same way at all the code from your product. As I said, conceptual integrity and system metaphor are levels of consistency that I find very difficult to attain. However, consistency at the module level is easier. It will require some adjustments, refactoring and decisions, but it can be maintained without much effort.

So my advice is: consider extracting the offending code into a small library or plugin for your application, where you use different consistency rules.

Alex is currently writing a book on Usable Software Design and explores with other interested people these ideas. If you’re interested in becoming a reviewer of the book, or just in exploring these ideas, just let him know at alex.bolboaca@mozaicworks.com or @alexboly.

Categorised in: , , ,

Leave a Reply

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