Monday, January 12, 2009

Good Design is Easily-Learned

The essence of "good design" is it's ability to be absorbed by a human mind. Design is "good" when it can be easily-learned.

The term "testability" refers to how easily the objects in a design can be learned, albeit in a roundabout way. "Testability" is also a catch-all term for good design, and encompasses design qualities that are reflected in the SOLID acronym. Each of the solid principles talks about a specific goodness of software design. The essential, root cause of those goodnesses is the readiness of the design to be turned into knowledge that a person can immediately turn into effort and action.

We have good, simple techniques for getting software to the point where its design is optimally-learnable, and where it's structural qualities aren't compromised. We can create designs that are easy to learn and understand by approaching design from the roundabout course of "testability". The word itself is misleading unless you already know what "testability" means when used to describe design.

Testability isn't a term that describes whether software can be tested. It refers to software that is easily tested. The more "testable" something is, the easier it is to learn.

Use What You Know

If you can't learn something, you won't understand it. If you can't understand something, you can't use it - at least not well enough to avoid creating a money pit. You can't maintain a system that you don't understand - at least not easily. And you can't make changes to your system if you can't understand how the system as a whole will work once the changes are made.

Testability is all about how little effort I need to make to understand how the system works, and what changes I need to make to the system's modules to make a needed change.

To make an object or component easily testable, you have to be able to create one with little fuss, and poke and prod it to see how it works. But if you don't design your software so that it's easy to set up scenarios that help you learn, then it's going to be harder to maintain your system.

For example, if you need to understand logic that might save a new database record in one branch of an if statement, and update an existing row in another branch of the if statement, and if you can set up that scenario to prove that the logic works without also having the set up the database and create sample data and then query the database to verify whether the if statement routes to the right actions under the right conditions, then you've got a testable design. It's also likely that you used the Dependency Inversion Principle and the Interface Segregation Principle to get there (the "D" and "I" letters of the SOLID acronym).

If it's hard to set up an object in order to learn what it does and whether it does it right, then you've probably got a poorly-designed object. This suggests that you can use test code to prove that you've got the right design. And this is what testability means. If I can easily create a scenario that helps me learn what my objects do, then I have also very likely applied SOLID principles - whether I planned to or not.

Even without tests or test-driven development, code that follows SOLID principles is easier to understand and easier to work with. However, if I don't consistently write sample code that concretely and completely demonstrates the desired level of ease that I'm after, I'll consistently over-estimate the ease of setting up an object, and I won't arrive at the best design.

No matter how good I become in object-oriented design, the only proof that a design is good is code that proves concretely that I have achieved the highest level of ease of setting up an object for use. If that object is easy to set up in the example code, it will be easy to set up in the system's functional code, lending ease to the functional code.

The most insidious aspect of poor design is that when two poorly-designed objects are used together, the resulting code is even worse than the sum of the poorly design code in each object. As more poorly designed code is added to the system, more of it will interact, causing an exponential decline in ease of learning, understanding, and using that code.

To Test, or Not

If you choose to ignore testability because you're not interested in jumping on the unit testing bandwagon, then you're falling prey to an unfortunate side-effect of esoteric jargon.

"Testability" means: "Designed well enough that it can be easily tested - if an only if I am one of those people who's into testing." Don't be set back by the jargon. Object-oriented design terminology is often over-the-top at best and rarely self-evident until you've been initiated into the workings of the minds of people who name things.

Despite the jargon, well-designed code has benefits above and beyond testing. You can test code that is poorly-designed using tools like TypeMock (.NET), but you don't get the essential benefits of good design just because you can test. Be careful not to equate "testability" with the mere ability to test something.

Remember, testing is about making observations, and observations are about learning. Using runtime interception to get at the innards of a system that hasn't been designed to readily offer up knowledge of itself is like making an appendectomy incision with a hand grenade.

The necessary use of force on a system to derive understanding from it is one of those tell-tale signs that the system is poorly-designed. And even if you find a way to test an object that is difficult to test, the object will remain more difficult to maintain because the object has not been shaped to have greater ease. A system of objects that are poorly designed will be much more difficult to maintain the the sum of the difficulty of the individual objects.

Design needs to be supple. Supple so that you can easily peel back layers and partitions like rose petals that fit intricately into each other and learn from what's inside. Blasting open a design with a profiler is probably a sign that your system takes its design hints from a chunk of inert rock rather than a living rose - at once intricate as a whole and simple in its components. Regardless of whether you succeed in testing such an inert, rigid design, the essential qualities of design that generate tremendous leaps in productivity and value will not be present.

Make It

Good design is about knowledge. We wrap it up in all kinds of terminology, but in the end it's about making smallish bits of software that are easy to understand on their own, and that fit together exquisitely to make things bigger than themselves, and that can still be as easily understood.

Design so that you can pick up an object, set it up with ease and with little fuss, and see how it works. Design an object so that you can easily introduce it to another object so that both objects are capable of achieving something greater than the sum of their parts without sacrificing an ability to understand either the parts or the system they form.

An object or a system of objects that is easy to understand also very likely reflects SOLID principles, but make sure of this by writing a bit of code that proves it. If you don't constantly endeavor to prove that your code is SOLID, it won't be. No matter how good you are at software development, software is always more complex than what you can hold in your mind at any time.

Write the code that demonstrates the ease of using your objects. If you write this demo code first, you'll have better results because you'll be writing the objects to fulfill your expectations for ease, rather than trying to force-fit ease into your objects later. Once you've over-stepped the window of opportunity to create ease and productive designs, it's often too costly to ever get it back.

It's hard to work with things that are hard to understand. In the extreme, you simply can't work with something you don't understand. Things that are SOLID are easy to understand because they're easy to observe and they're easy to explore. They make sense. They're logical, and rational, and clean.

When you hear someone say "testability" have some sympathy for the poor bugger - he's usually not aware that he's speaking gibberish. He's been through hell and back trying to discover all this OO stuff on his own, and it takes a great toll over the years. Be thankful that there are people who've made the trek to hell who are willing and able to teach. Your aren't required to loose yourself in the wilds on your own, potentially loosing your own ability to communicate with other humans.

If you achieve the ease in software design, you'll find that other desirable qualities will come along for the ride almost incidentally. The qualities that make design easy to learn also make it reusable, adaptable, distributable, and lend the software you already have to changes in architecture, new non-functional requirements, and the necessary changes to the software that result from changes to the competative environment where your business does its work.