Wednesday, February 10, 2010

Productive by Design

Productive designs are compartmentalized. The parts of your software are shaped to the way that your mind holds on to them. "Abstraction" means the same thing to software as to thinking. "Concern" is another word the shows how software reflects thinking. A software "concern" is something that I have put my focus on that is my present concern.

The compartmentalized bits of software are the shapes of your software's parts. The shape of your software and its parts is only one aspect of your software's design. The shapes of your software's parts is a "static design". "Schema" is another word for it. The other aspect of your software's design has to do with what happens when we bring the parts together, give them a problem to solve, and give them the green light.

We lose software development productivity to relearning and to misunderstanding. The design quality issues that makes it hard to learn, re-learn, and understand your software also make it hard to understand how it works and whether it works. Powerful tools that optimize the moment of creation of the parts of your software enhance your ability to lower productivity.

"Module" is another word for "compartment". A class is a module. Modular design has to be productive not only in its shape, which helps us understand how the software is broken down into concepts, but also in how those parts do some work, and how they work together, and whether they leave their tools out where you can trip on them in the dark.

Software is a machine. It's not a machine like your car's engine or your cordless drill, or like any machine that is made with material. Analogies to physical machines break down almost immediately. There are no machines in physical space that are anything like software machines. They're machines that run individuals' personal way of thinking of how to solve a problem. There are as many ways to program a solution as there are programmers.

Software is a machine. It's made of parts. The word "soft" in software says that it's easy to not only change the working of the parts, but also the shapes of the parts, and to even move the workings to a different part (which often then tells you to change the shape again).

The first great matter of software development is proving that the new shape of some part of your software will still do what you expect it to do when you turn the machine on. The second great matter of software development is whether the shape that you've given to the software will continue to make sense, and that the workings of the software are placed in the right parts.

There's one gigantic difference between working on software machines and working on physical machines that bears mention:

The parts of physical machines wear out, and we replace them with another, new copy of the same part. This doesn't happen with the software parts that you make. When we change software parts, we don't replace ones that have worn out with new parts from the same mold. When we change software software parts, we change the very shape of those parts, often re-shaping the parts around them as well, and moving the workings of one part to a different part, or even a new part created on the spot as part of a new compartmentalization. In relation to a physical machine, software is much more like the mold than the product. Software development is closer to die making than stamping.

Every time you make a change to a software machine, there's a good chance that the change shows up in an adjacent part, and there's a good chance that it'll show up when you're not looking at it. It's a lot easier to get the code you're looking at right than the code your not looking at. That's what "control and observe" is talking about.

To increase the chances of software working as expected after you've tried to turn a part of it into a hat, a broach, or a pterodactyl, you make observations of it. If the only way that you can observe the functioning of the part is to bring the whole machine online, your productivity is many times less than what it should be. If you can make easy observations of the part that you're working on (and maybe parts in the vicinity), you can also make those observations while you're working on it so that not all of the steps are wild leaps into the yonder.

Imagine software that routes baggage from an airline check-in desk through the bowels of an airport's network of conveyor belts and scanners until it arrives at the right gate for the airplane that you're traveling on. Let's say you reshape the part of the software that figures out whether to route baggage to gate 1 in terminal A or to gate 2 in terminal A. Same terminal, different gate. You're losing productivity if you have to start the process from the part of the software that decides to route the baggage between terminal A or terminal B.

Here's the problem:

You need to observe the gate router, but you must control both the gate router and the terminal router. Controlling the terminal router now is wasted work.

You need to feed the terminal router with the circumstances so that you're controlling it to choose the terminal A route. You also feed it the circumstances that it passes on to the gate router so that it does the thing that you expect it to, and so that you can observe that. The terminal router is superfluous in this scenario. You're concerned with the gate router, but you also have to be concerned with the terminal router's workings, even to the point of being concerned with how it gathers the information from its inner workings to pass to the gate router.

If your concern is the workings of the gate router, then you should only have to control the gate router, feeding it the information it needs directly without going through any intermediaries. It's what "separation of concerns" is talking about. Collusion of concerns makes you write more code (and more complex code) to make observations about your software, but it also throws attention switching into work that is trying to be a feedback loop.

If you have a gate routing module, you can likely control it and observe it directly. But what happens when you want to control and observe the terminal routing module? The terminal router will immediately pass the baggage off to the gate router after it has done its work. Can you control the workings of the terminal router without also having to feed it the right information that it needs to feed to the gate router so that the gate router doesn't accidentally raise an error? In other words, how do you control and observe the terminal router without having to be concerned about what the gate router needs to know to do it's job. If there was an accident rate in software development like there is in other production jobs, it would be measured by how often we accidentally have our attention sucked too far into a wood chipper of dependency inversion.

With the right modularity, you've addressed the "static design". The "dynamic design" is how the parts of the software are put together to make the machine. A software machine is put together at runtime, usually by something called a front controller. A Main method is an example of a front controller. So is the thing that receives an HTTP request and sends its instructions it to the right part of your application. This is the part of design that a lot of developers don't recognize as the sore spot of their productivity problems.

There some esoteric terms for this issue of controlling which modules you have to be concerned with when making observations. Much of the language is more complicated than the actual work that you have to do. If you don't get distracted by the lingo, you'll find that this is not only one of the most important problems to solve, but also one of the easiest!

If the part of your software machine that routes baggage from the check-in desk to the right terminal passes control to the part of the software that routes the baggage to the right gate, then the terminal router will likely need to call a method on the gate router (or send a message to it). If I just want to control and observe the terminal router without having to be concerned with the gate router, then I've got problem that can be solved by getting the terminal router to use a dummy gate router instead of the real gate router. I can't change the terminal router code so that it doesn't use a gate router at all when I'm trying to make these observations, because that's not the code that would run live anyway. It wouldn't be a real observation of the thing that I'm concerned with in the first place.

If the terminal router uses a dummy gate router, I don't have to be nearly as concerned with it as I am with my primary concern, which is the terminal router. This is what "separation of concerns" is talking about.

The last thing that you have to solve is a way to switch the gate routing part for a dummy gate router. That's not a hard problem to solve. If you have a terminal router object, you can create a constructor that accepts a fake gate router. The terminal router's default constructor can create a real gate router. When you need to observe the terminal router's workings, create a dummy gate router object that doesn't have to be more talented than not raising errors when it's not supposed to and pass it to the terminal router's constructor.

The term for this pattern is called "inversion of control", or "dependency inversion", or "don't distract me with superfluous concerns".

This is a really simple way to get superfluous concerns out of the way so that they don't interfere with the parts of your software that you're actually concerned with. The above technique is sometimes called "poor man's inversion of control". There are many free libraries that you can use to automate all this stuff and make describing how the parts fit together effortless (or better, unnecessary). They are "dependency injection frameworks" or "inversion of control containers". I like the word "composer".

The dynamic side of productive software design is all about putting the parts of the machine together when your system starts up (or on-demand). The effort that you put into the static parts of the design - the shapes, the geometry, the abstractions - is paid off when you're in complete control of which parts are in-motion when you need to make observations of your software. And to belabor the point: it's the ability to control and observe your software that will give you more productivity, or to restore your productivity. You can't use what you can't understand, and you can't understand what you can't control and observe.

If you can't directly control and observe your software, you might be mistaking efficiency for productivity. You're optimizing your work within a range of improvement that is too close to the floor that make a difference in how you to get the lid off the cookie jar.

Productivity is right there for you to reach out and harvest. It's locked up in your code like potential energy waiting to be turned into kinetic energy when you free it from its moorings. You can find it in all the ways that design interferes with your ability to work with the software that you already have. There's little value in optimizing the pace that you create new software if you're optimizing the pace that you incur compound interest.

Productivity is usually something that you free from your software rather than something you add to software. You create the interference. If you don't want the interference, don't add it to your software.

Working with software developers and organizations to help realize the potential of software product development through higher productivity, higher quality, and improved customer experience