I just finished Norman's
The Design of Everyday Things. As the name suggest it's a look at how the things of everyday life are designed, both the successes and mistakes. I think it'd be an interesting read even for those who aren't involved with design. If you've ever used something wondering why it is so difficult to use, you'll appreciate the direction in looking for products that are well designed.
I read the book primarily for my work to get a better understanding of how to design software. Unfortunately due to when it was written it doesn't cover much on computers. Some of the principles apply, but it seemed every shortcoming or difficulty in the real world applies doubly in software. Though software also can potentially provide opportunities around all these shortcomings, reading it helped me understand why software design can be so difficult.
One thought that stuck me as I was pondering the principles of design proposed is how important design is in software. Not just for the end user, but in the source code itself.for the sake of the programmer. I'm not just talking about efficiency or correctness, but a user-friendliness of the source code. This isn't anything revolutionary, I'm sure. Plenty has been said about making code readable, and on how to design code. But it certainly struck me, and made plain the reason for many of the problems I am facing with the code I work with.
To make this understandable for both the developer and the layman, let me first attempt to describe source code to you. Source code is essentially instructions for carrying out a set of processes or actions. It is often compared to a recipe. If you follow the instructions of a recipe as written you will get the results desired. But if you were to randomly rearrange the recipe, it might not work. If the recipe called for placing the dish in the oven after turning the oven off rather than right after preheating it the results would be less than desirable. So it is with source code—if all the right instructions are in the wrong places the results won't be what was intended.
Now we could compare a computer to a chef, but that doesn't quite give us the picture. A chef seeing the mixed up recipe above would correct it, probably without even realizing there was a mistake to begin with. But a computer will perform the instructions exactly as it is given. So let us consider a robot chef who follows his instructions to the letter.
But this description still falls shy. Software can be composed of hundreds thousands of lines of code. It covers a wide variety of tasks that the user may trigger at any time. So let us take our robot chef away from his simple recipe, and place him in charge of catering a wedding. And not just any wedding, a week long affair like in India or the Middle East. Now our chef has hundreds of dishes to prepare, and all of them simultaneously. The appetizers, the main course, the deserts. Breakfast, lunch and dinner. Now you begin to see the magnitude of the task of writing a proper program.
So our robot chef has his source code—his list of recipes—and when each part of each recipe is to be performed. He performs each task without deviation, exactly as described. A wonder of modern technology! But alas, the description is wrong! He is putting garlic in the smoothies and too much salt in the omelettes. He consistently burns a dish from the main course, and in one recipe is using motor oil instead of olive oil. Exactly as we asked him to do, but not what we intended! Now we need to find those incorrect instructions and fix them.
But where! Now the problem becomes clear. The number of instructions given our robot chef to prepare the week long banquet is overwhelming. Where do we even hope to begin? Well, you say, let's start somewhere simple. We know he is putting too much salt in the omelets. Just pull out that recipe and update the salt amount.
Now we have stumbled upon one of the first important principles of design. That the user of a design assumes a model of the design. You have assumed (or so I assume) that we have written our source code in such a way that each recipe is independent. The robot simply goes from one recipe to the next. But what if this is not the real model? What if in reality the recipes are all broken up into parts—prep, mix, heat, and serve—and are mixed together for all the recipes? The robot cracks the eggs for the omelet right along with the eggs for the pancakes. He cuts up the veggies for the omelet with the potatoes for the hash browns. The robot is working on everything simultaneously. This model, with all the recipes seemingly mixed together is a little more complicated, and perhaps harder to understand. But it is understandable enough, and presents a clear model for the programmer who manages our robot.
This is the first key. The programmer is in fact also a user of the source code. Just as a casual observer of our robot chef would come up with a model of how he organizes our work, so too will our programmer come up with a model. And his model needs to match the actual model that is described by the source code. Otherwise he will find it difficult to edit. Unfortunately most code is written without any sort of model defined, making it near impossible for a programmer to find a coherent model to follow.
For instance the original programmer probably first just wanted to make omelets. So his model was one recipe sheet for the omelet. No separation of the steps to prepare the omelet. Just lump everything in one place. Then his boss realized there was money to be made not just in omelets, but in breakfast. Now he's added pancakes, hash browns, fresh orange juice and a side of toast, drizzled in strawberry compote. And he kept each recipe separate to keep in understandable. Good work programmer!
Yet this doesn't work. Everyone is getting their breakfast too late. They get their hash browns first, but by the time the pancakes are ready, the hash browns have gone cold. Now he has to reorganize, putting the first steps of each recipe together, and the second steps, and so on, so that everything is on the griddle at just the right time to all be served together, as described above. But he just put all the steps in one long list without taking the time to organize them so as to match a clear model.with understandable sections: Prep, Mix, Heat, Serve. Before he can do this he had to move onto project "lunch."
Now we've arrived at our recipe list for the wedding banquet and need to track down how we end up with too much salt in the omelet. But was the omelet itemB or itemD in that long list of instructions? Our programmer didn't think to identify things very well since at first everything was separate, and the separation itself made it clear what was what. But now that every recipe is mixed together we'll have to read through the whole recipe until we can find something that identifies which one is the omelet. We can't necessarily look for a high salt value as the hash browns also need salt, and they need more than the omelet.
Here in is the root of the problem. If we don't have a good model in the code, things are hard to understand and debug (i.e. remove problems). As more things are added to the program things get worse. When you add a new recipe to the mix, throwing it in anywhere seems fine if there is no clear place to put it, making it less and less clear what piece of code belongs to which task.
What's worse is in real code we have plenty of more things to confuse us. Frequently the worst code will not only be poorly organize, but we also be poorly labeled as in our itemB and itemD example. We'll have a piece that says MakeItemC, but what in the world is that. I guess we have to look at the recipe in MakeItemC to find out. Hmmm. It's compromised of Step1, Step3, Step4, and PtOvn. Oops. Turns out itemC was a quiche. Had they labeled it MakeQuiche we would have known never to look here, as the quiche doesn't have enough salt if anything! Now we've just wasted our time following what should have been an obvious dead end if the code was user-friendly.
Unfortunately this can be quite bad, and quite easy for software companies to not understand this. It is easy to miss because at first your source code is too small to be misunderstood. Plus there is only one guy writing the code, so as long as he understands it things are good. But this early failure to keep things in check, and to have a clear plan of how the source code should be organized quickly balloons. Suddenly they are getting successful, and hiring more developers. Now they have 20 who are all writing things their own way, and nothing quite matches up. That's fine as long as we keep them on their own projects.
Oh no! Now some people are leaving. How can we get to the bottom of their code? And now we're up to 500 programmers, and we need to get everyone up to speed in a couple weeks. How are we ever going to manage that? If only we had kept things organized at the beginning but now it's too late. Let's just try to keep things organized from here on out, and minimize changes to that old garbage. It's been running all these years anyway. I'm sure no more bugs will pop up, right?
Open source code can have a potential advantage in this area because the code is much more of an asset. Programmers don't have to work on open source projects if they don't want to. So if the code is a jumbled disorganized mess, they will take one look at it, grimace, and wish the maintainer luck before trying to find a project that looks more manageable. I know the few times I've dug into the source code of an open source project it took me only a few minutes to get up to speed, whereas at work it can take the better part of a day or month. Even then I often only understand things well enough to fix it with some assurance I didn't make things worse.
If businesses thought about it, they would realize that taking the time to design their code well is money well spent. I'm not going to say it takes little effort up front to make this happen, as the effort is certainly significant. Especially as things grow it may be necessary to completely reorganize core concepts in order to keep things orderly and understandable. But it will save thousands of hours down the road. Less time will be spent bringing new programmers up to speed as they can quickly be told the rough flow of the source code. And it will be
accurate! They won't constantly have to learn something completely different for each piece of code they touch, but just enough to locate the problem and fix it.
Just as when software is user-friendly, it provides a better experience for the user, when source code is coder-friendly, it provides a better experience for the programmer. It allows them to take less time to make fixes and add new features, all with reduced chance of introducing bugs. And that is something that benefits everyone.