Software development is always about acquiring knowledge. How we get that knowledge can vary, but it is never enhanced by being lazy in thought or deed and there are capabilities in modern systems development that can encourage laziness. It was not so years ago...
When I started programming professionally in the early 1970s, the only available computer was a large mainframe. When I say large, I mean physically; in memory size it was miniscule.a The computer's primary function was running a steelworks and any software development task requiring computer resources had to wait until the machine was not engaged in more important tasks. This would generally be around 2:00 A.M.
Any computer operation: compile, assemble, or test, could only take place overnight and we had to wait until the next morning to find out if it worked. So every software development activity had to be very, very carefully thought out. Machine runs were terribly time-consuming and extremely expensivemuch more than the cost of programmers. So to get the best use out of the scarce machine time we did a lot of desk-checking. We reviewed and inspected everything: documentation, code, tests. We even developed an inspection technique wherein programmers would "play computer" by manually walking through assembly language programs with people taking the roles of registers, accumulators, and memory locations. Test data was written on slips of paper that were passed across the table from memory to accumulators or registers and back again. The program stack was just that: a stack of these pieces of paper. This process was very tedious, it was very slow, and it required a lot of time and a lot of effort. But it made you think.
Fast-forward 40 years and things are different. Most of us carry, in our pockets or in our purses, more computing power than existed in the whole North of England in 1972. Chances are you could not find even one chip as small as 32k in any laptop or desktop you would buy today. There are libraries full of books on languages, methodologies, and development lifecycle models. Modern systems development environments are exactly that: environments. Integrated development environments (IDEs) contain programming language syntax checkers and compilers, editors, functional code and code snippet libraries, object browsers, code navigation and coverage aids, environment support and virtual machine interfaces, authoring tools, configuration management and build functions, even embedded project management capabilities. Critically, IDEs usually contain interactive testing and debugging environments and this can be where problems may arise.
These powerful tools allow us to create something that "works" and create it very quickly. From a standing start, IDEs allow developers to build and test complex systems in days if not hours.
This is very seductive.
Humans, like most dynamic systems, often attempt to operate at the lowest energy state possible. When the perceived job of a software engineer is to "build something," and the environment provides a very quick way to build something, it is difficult not to follow that road to its end. Fast compilers and interactive debug environments can encourage shortcut approaches that compromise the learning essential to effective systems development. We may build something when we really need to build the thing.
There is instant gratification in coding and immediately seeing the code run. It can encourage programmers to throw something together and then play with it to see how (or even if) it works. At its best this can be considered learning by experimentation; at its worst it can be mindless hacking.
Don't experiment when you should think; don't think when you should experiment.
Jon Bentley, computer scientist
There are two ways to approach understanding something. We can think carefully about the required logic to solve a problem before trying to produce something that will show how our understanding holds up. Or we can experiment by running a program in different states of development against different inputs and see what happens. Both are legitimate approaches when used carefully and thoughtfully. But we have entered an era where computing power and speed are so readily available and so cheap that the experimenting approach may be causing us problems; it may encourage programmers to be lazy and that is never a good thing.
Modern systems development environments are just that: environments.
When modern IDEs allow us to build something that works very quickly and modern debuggers allow us to execute it before our eyes in real time there is a strong temptation to design and program by trial and error. Programs can be patched together as a sequence of ad hoc amendments to an initially weak and ill-considered design. Once the program seems to work, it may be bundled with other programs similarly designed. Systems built this way do not work very well, are difficult to understand, and are hard to maintain.
Experimenting and Thinking are Different. Experimenting tends to be situational and instance-driven. It is highly influenced by our initial (and necessarily imperfect) understanding or simply by how the program was first thrown together. Experimenting also encourages understanding-in-the-small since the focus is usually to more and more detail to try to somehow make this program work.
Thinking tends to be more contextual. Using it, we build mental models that allow understanding of the instance in a larger framework. So Thinking is more aligned with higher-level abstractions and global knowledge. Thinking encourages understanding-in-the-large and this usually makes for better systems design.
As well as encouraging unplanned experimenting, faster development tools may paradoxically increase the time to complete programming tasks. They may do this by encouraging a large number of small iterations where the learning increment is minimal, instead of a small number of thoughtful iterations where we learn a lot. So if faster might be slower, would slower speed things up?
There are two forces at work here. One is the cost of experimenting, the other is the cost of all those iterations. As development tool speed increases, clearly it costs less and less to play with a problem. This is shown in the accompanying figure by the blue bars. When the turnaround time is very long, it is usually too expensive to consider experimenting unless Thinking has come to a complete dead end. As turnaround time drops it is likely there is a threshold where Experimenting becomes much more attractive. The threshold is probably related to the perceived effort required using the two approaches.
At the same time, all the iterations generated by Experimenting have a cost that is rising gradually (shown by the red bars). With 100% Thinking, there may only be a single iteration that proves the thinking is correct. As Experimenting becomes less costly and more impromptu many of the experimental iterations may either eliminate specious dead-ends or may not expose any new knowledge. So while the iterations might not cost much, they may also be low-value in terms of learning. And we tend to do a lot of them.
Faster development tools may paradoxically increase the time to complete programming tasks.
When Thinking departs the scene, Experimenting turns into hacking. Here the programmer may not even have an idea of what to look for (or even what he or she is looking at), not having thought through the problem at all. In this case, provided the system does not actually crash, an iteration might be considered "successful"after all, it did "work." But we do not learn much from it.
This hypothesis infers there might be a "sweet spot" where development tools are fast, but not too fastnot so fast that they encourage laziness. Not so fast they encourage writing programs rather than designing programs and running programs rather than thinking about programs. This raises some questions:
There is only one criterion that should determine how we should experiment and how much we should think: Which approach will allow us to learn most efficiently?
The putative goal of systems development is the production of code that "works" just as the putative goal of a college education is to obtain a degree. These obvious targets mask the true purpose of both activities which is to learn. We can print up a degree certificate in minutes and we can create code pretty much as quickly as we can type. Creating code that does what is needed requires first learning what is needed and learning is always an active and effortful endeavor. If powerful tools allow us to coast at a low energy state they can shortcut the learning process altogether. This is true of programmers, of college students, and of college students studying programming.
I have sometimes thought the manual computer simulation we played with pieces of paper, pretending to be a PLAN assembly language program, was so interesting and informative that it could be made into a fun board game.
Just kidding.
a. It was an ICL 1901 series with 8k (words) of memory.
The idea for this column came from a series of discussions with Dave Berque ([email protected]), professor and chair of computer science at DePauw University, to whom I would like to express my thanks.
The Digital Library is published by the Association for Computing Machinery. Copyright © 2013 ACM, Inc.
I can just imagine the outrage and opposition to the suggestion of introducing deliberate delays! Would the developers know that these delays were being set and would they really use those delays
to think a little harder about the problem at hand? I think the topic is fascinating and I propose a different solution. My suggestion is that the "delay" that needs to be introduced should be in the form of an automated inspection tool running in the background. Every so often, the developer should be alerted to the results of the inspection (ie. specific pclint errors were found or some modules exceed the acceptable mccabe complexity or nesting levels or whatever the chosen coding violations may be). This form of a "delay" actually makes use of the computing power rather than just having the computer "wait" for the developer to "think". Also, I think it could force programmers to work smarter if they know they will be faced with inspection results that they will then have to spend time fixing before moving on. A good implementation of this proposed solution might actually equate to faster=faster!
That's a really good idea, it makes use of the delay rather than simply, well, delaying.
There is quite a bit of evidence that much of our truly innovative thinking occurs in slack time--time when we are not "working" but any reaction to the delay would somewhat negate the benefits.
Also, I recall back in the 1980s working on IBM mainframes running under "TSO" that it would typically have a most irritating delay. Each VT terminal interaction might take 2-30 seconds to respond to a simple key entry. The duration of the delay did not allow any other work (or even thinking) to occur while the uncertainty of the response time prevented you from leaving. So there might be characteristics of the delay that would work and others that wouldn't. It might be better referred to as a "batch mode" processing where there is a period of activity followed by a period of inactivity (where the thinking occurs) rather than a "delay" which might just mean doing things slower.
A project team could easily set up a work schedule or cadence that would achieve this without the necessity of inserting artificial slow downs. Worth thinking about.
Great article. It's hard to get developers to think, because they think they don't have to. And it's hard to convince clients that thinking is important when they hear that you should be able to build a new system, any system, almost overnight. Just grab a bunch of JS frameworks, fire up some virtual servers, and use MySQL.
Part of the problem is tools and languages that don't reward thinking. The dead ends we go down on our own projects are local versions of what the industry does on a large scale with paradigms.
Ms. Jutras's suggestion points to one of our major issues, complexity. We need to teach people to fight complexity at every turn.
When I read the note about PLAN, it made me think of Logo and "playing turtle". When I saw your joke about making a game, it reminded me that I saw one: http://www.kickstarter.com/projects/danshapiro/robot-turtles-the-board-game-for-little-programmer?ref=search
I'm not affiliated with it in any way, but it looks like there are geeks out there who want to teach their kids this way.
Great article! I came from a similar background I think, except that I had been spoilt rotten by working at light-pen graphics development on a Univac 1108 super-computer, albeit at assembler level, that even had drum storage, in 1967, before joining ICL in Putney, London, on a 1900 computer using PLAN assembler. That was certainly a slow-down. Since then I have used a huge variety of machines including many micro-processors, and now Arduino, and certainly appreciate a responsive environment, but I can't help but think that there is a new class of people that are way into the red part of Phillip Armour's graph using the current trend towards the Sprint methodology.
Even the existence of a two-level Git source control system which mandates passing a code test before the second level places the changed code into the global project space does not really deter verification that "something runs" as distinct from validating that it does something useful.
Perhaps all that is needed as a useful slowdown is a code walk-through between the first and second stages of introducing changes into the system.
Thanks for a great article, Phillip. Maybe I even met you in London in the period 1967 to 1970!
Displaying all 4 comments