…type 0 and type 1. (Laugh here.) Okay, so software engineering isn’t really binary. Still, I’ve found that the world of computer programmers makes a lot more sense if you divide it into two categories: engineered and improvised. If you think in this way, you’ll find that many of the holy wars among developers are a result of improvisers who don’t see engineering problems and vice versa.
Engineered programming is appropriate for problems with solutions that can be defined mathematically. People rely on these programs to act correctly. This includes wind tunnel simulators, machine controllers, pacemakers, and even tax preparation software. But it also includes software within a computer program for storing, retrieving, and interpreting data. For example, your web browser must follow many different protocols so a JPEG image will look like Justin Bieber and not random garble. Engineering problems need to be tested for correctness, because the end user may not be able to evaluate it. If your accounting software is buggy, and you’re not an accountant, you’re in trouble. Not only can they be tested, but the components of engineered software can be divided into sub-components, each of which can be tested independently. In fact, it’s possible to develop a test suite for every branch in the code. How much testing you do is limited only by how concerned you are about bugs. A pacemaker company hires 10 test engineers for every programmer, while an accounting company is considerably more relaxed, while the data loader for a video game may get fairly little testing. The tools of the trade for engineered software make testing quick and easy, and often automated. For example, when I’m writing Java code, my IDE highlights certain errors as I type them and offers corrections, just like a spell checker. As these tools get more sophisticated, bugs get harder to write.
Improvised programming is appropriate for problems where there’s no right or wrong answer, just better or worse solutions. And better or worse may be in the eye of the beholder. User interfaces are the most common situation, but there are others, such as special effects. Even things that ought to have well-specified requirements may require improvisation, particularly when the people providing the requirements keep changing their minds. When I write screen savers, I often find that my bugs are more interesting than what I’d planned to write. This is not a “no tests, just wing it” environment, though. The difference is in the kind of testing that’s appropriate. You can’t sub-divide a user interface into components and test each one separately; holistic tests are the rule. Usability tests tell you if the program works in a particular context for particular people. An iPhone widget that works great for kids may be completely unusable for fat-fingered football players. The tools of the trade for improvised programming make it easy to make changes, including throwing everything away and starting from scratch. This is often called rapid prototyping, even though the finished product is often the best prototype.
So there are two completely different types of programming, for which completely different types of tools–even different programming languages– are called for, and they are mingled together in most programs. What to do?
There’s a design pattern called Model-View-Controller, or MVC, which offers a solution. It’s enforced by Apple’s development tools, and Ruby on Rails is built around it. In MVC, the model is the engineered portion of your code. It describes the universe in which your game exists. If you can prove its correctness, it belongs in the model. The view is the pure UI portion, where user feedback rules. The controller is the in-between portion; it’s the logic of the program which isn’t as clearly defined as Model and isn’t as flexible as UI. The genius of MVC is that it cleanly separates the parts of the code that are best suited for test-driven design from those that require rapid prototyping.