Main Points

  • Increments Getting started with Incremental Development.
  • Checkpoints Opening up the application for testing outside the development team.
  • Test for Understanding Testing accomplishes a major goal, it creates coding experts.
History is Now

Incremental development is, for me, a no brainer. Developing a project in increments is nothing more or less than testing the project thoroughly so that you can be sure it's functional and that you really understand how it works. The alternative as I see it is wing-and-a-prayer development; you throw something together, don't bother to test it sufficiently, and hope that everything turns out all right in the end.

This being software development, there are always bugs and errors you need to correct, and without sufficient testing you have to search through the entire program in order to find them. That's almost a worst case scenario, almost because you haven't really spent any time with the program and have no idea how it really works. Hopefully, you have extensive pseudocode designs and good diagrams or you may know virtually nothing about the insides of the program you're trying to fix. That would be the worst case.

Incremental development is a preventative measure that stops bugs from getting inside your code in the first place, and that teaches you all the details of how your code really works (as opposed to how you thought it should work during design). I don't think it will surprise anyone which type of development has a higher success ratio, lower costs, and a shorter time to the completion of deployment.

Increments

Incremental development (like its large scale sister, iterative development) is about breaking down a project into small pieces (increments) and testing each one thoroughly. When we build a new code on top of what's already built, it's because we already know it's solid and will do what we expect. The core of how incremental development accomplishes this is two pillars.

  • Small is Better - If the most important goal in software development is managing complexity, the biggest adversary is size. Large projects tend to be more complicated qualitatively, but even on simple projects, the sheer quantitative mass of material can be mentally overwhelming. Therefore, if you can break a project down into smaller pieces, the more effectively you can program and the faster you can finish the project. You should follow good abstractions when organizing your code into classes and functions in order to keep the relationships as clear and as simple as possible. In general though, if you can encapsulate out a piece of functionality, you can help simplify your code and speed development.
  • Test Everything - It is great if you can work on small projects, but there is a secret way that they can reconnect to become too massive for you to deal with – Insufficient testing. Once you have a program flaw, or a behavior that you do not want, you have to find a way to fix it. The question immediately becomes where did this flaw creep in. If you program small pieces of well abstracted code but fail to test each completely, then you may have minimized the number of errors in the code, but not the time it will take you to find each one; you have a lot of code to search through.

The secret to accomplishing these goals is to break the project up into increments. An increment is a function(s) and / or custom object(s) that developers can build and test thoroughly to be sure that the code does exactly what it's supposed to. If you find an error, you only have to recheck that increment to solve the problem (usually). Therefore, you'd ideally like to make each function and each object an increment and test it thoroughly when it's completed. At the very least, you try to get as close as you can to that ideal.

Defining Increments

Sadly, the ideal is not always possible or efficient. Frequently there is code which is well encapsulated, but which requires at least one calling function to do anything really useful. This of course is how you really want to test it, by asking it to do what you really want. It is a best practice to always test as near to real world conditions as you can get. In this case, it only makes sense to test the pair (or triad) of functions. This situation actually comes up quite a lot if your application makes good use of utility functions. Sometimes it may be possible to test a function with an extensive testing harness, but that may not be the most efficient way to conduct your tests. It may be wiser to rearrange your project increments a little to build other pieces of your application which can then serve as the testing harness. The guidelines I use when defining my increments are...

  • Make Increments as Small as You Can - If you find a problem, the less code you've written, the fewer places you need to look to find out where it is. A function is about the smallest piece of code that I will test, but then 99% of my functions are less than 100 lines (including whitespace), and I aim for 20 lines of code per function. Once I get past 20 lines, I'm looking for a decent abstraction that I can yank out and encapsulate.
  • Use the Application as its Own Testing Harness - Testing code doesn't just happen, you need at least one other piece of code to use the function thoroughly and provide you access to the function results so you can check it. This is not a trivial process and creating a testing harness can consume a fair amount of time in its own right. Therefore, anytime you can use the code you've already written as the testing harness, you eliminate a fair chunk of programming. Using the current modules of an application also allows you to perform integration testing at the same time and see if the function really results in the application behavior that you want.
  • Test Increments Thoroughly - Using the application as its own testing harness effectively combines two phases of testing, unit testing and integration testing. Frequently, this makes sense and does not cause problems; when the application as constructed gives you the ability to fully test an increment, then you don't have to create a testing harness which definitely reduces construction time. However, if the application only partially tests out a function, then it is wise to go back to two testing steps. Create a testing harness so you can build the increment and conduct unit testing, then integrate it into the application and make sure everything still works the way you expect.

You can see how slippery this is; some of these goals are clearly contradictory. Defining increments is not a deterministic process, it requires making the best, most educated guess you can. If you can put off an increment so that the application becomes its own testing harness, that's great. However, you don't want to postpone a mission critical increment too long or you are violating good risk management. Such an increment requires a balancing act between building it too soon vs. waiting too long. It's also possible that you could use the application to test out a function, but that a testing harness is required to fully test the code. Under those conditions, you should back up, use a testing harness, and then integrate the code. I typically encapsulate the process of defining increments as prioritizing construction in the final step in my design process. Basically, I create a list of all my functions and objects ordered by their relative risk (riskiest items first). Then I write a second list as close to the first as I can, but which groups functions and objects into increments to allow for complete and efficient testing.

Checkpoints

When I'm working on increments, some code-centric developers are surprised to see UI output classes front-loaded pretty heavily. After all, this code is comparatively simple; however, risk management is not about complex vs. simple, it's about risk. Complex code is certainly one particular kind of risk to manage on your project. However, in my experience, surprises arise during testing and these are key sources of risk; user acceptance testing (UAT) is certainly no exception.

UAT is a critical stage to manage in any project. Poor management of clients during UAT is a frequent cause of project failure (either too dismissive, or too eager to please). Irrational fear of UAT can lead to overly detailed requirements that straight-jacket the development team and cause project failure. In fact, each of these three items (dismissing client needs, pandering to client needs, and overly-detailed specs) probably each causes more project failures than complex construction challenges. That's clearly an important need which you should address in your risk management strategy.

This is where checkpoints come in. Increments are the smallest possible pieces you can define for thorough testing; they are project units that the development team can use to track its progress. However, there are frequently far too many increments to make good UAT checkpoints. A checkpoint is a particular stage of software development where you have sufficiently advanced the user experience (since the last checkpoint), that it is worth having the end users (and other stakeholders) perform UAT on those items.

While controlling scope creep is important, the earlier in a project that a client needs changes, the more flexible I'm willing to be. Anything that requires some design changes but doesn't change the effort or the timeframe necessary to complete the project is fine by me. In order to push projects and clients toward this goal automatically, I use a formal change management system that requires signatures and approvals; I also make sure to clearly document additional time and cost factors involved in the change documentation. Those are two things that most effectively counter a client's zeal for something new and force a more objective appraisal of whether they really want to make a formal request.

One of the best ways to ensure good UAT is to hit your checkpoints early. By front loading the application UI, you have to hard code responses to provide users with close to the full user experience, and get UAT check offs. Yet this completes checkpoints without coding most of the functionality behind it. Then all you have to do is construct the code which provides the full functionality dynamically behind the scenes which won't affect the application behavior. Thus well chosen and early checkpoints help to balance client satisfaction with a smooth development life cycle.

Defining Checkpoints

Just like increments, checkpoints involve tradeoffs. Too few checkpoints and you can run into nasty surprises when a lot of work that you've done is invalidated during a user acceptance test. However, too many checkpoints and the end users / clients can become frustrated, the development team can be held up due to missed appointments and rescheduled UATs, and there are more communications which open the door for more miscommunications.

Worse, the appropriate frequency of UAT will usually depend on the client. UAT is not a logical test, it is an acceptance test; you are asking a person to sign off on the work that's been done for them. This is not a wholly rational decision. The upshot is that you don't conduct UAT with the same frequency or in the same way from client to client, just as you would never communicate identically with every client.

A checkpoint is therefore a point in the application where a particular increment has been finished that completes some set of functionality which you want to present. As much as possible you would like the functions to be related, but in reality, checkpoints always involve a somewhat diverse group of capabilities. Frequently, I want the user experience (UX) designers and client managers to define these points, and / or the QA department. Just make sure to plan your UAT with your client in mind.

Quality Assurance (QA)

I strongly believe in QA departments, however, I disagree with the common practice that a program is finished and then turned over to QA. I believe that QA is something that happens throughout construction, and there are a number of development teams (especially in the agile community) that practice this as well. Therefore, managing hand over of work from the development team to QA is a critical workflow that quite literally demonstrates application progress, and checkpoints are the perfect time to make the handover. As the development team completes a checkpoint, push the current version of the application from the development server to the testing server where QA can quite literally tear it to pieces. Since this all happens on another server, the development team is in no way inhibited during their development (unless QA finds serious problems that require backtracking), and QA is not restrained by direct or indirect contact with the development team. This ensures the quality of your program and ensures that given any development emergency, you always have not only the original developer(s) of a section of code, but an entire QA team that can place an expert eye on the problem.

Test for Understanding

Having experts on your code available is a key priority for any development team. So even though I've said it already, it's worth saying again. I conduct testing not only to make sure that my code works, I test because it's in testing that I understand how it works.

There are all kinds of wonderful theoretical designs you'll develop that turn out to have nasty little implementation surprises; this problem will also depend on your technology. C# is a very stable language which is one of the reasons I like working with it. Javascript, on the other hand, is so notorious that almost from its birth there have been developers who've advocated for "the Good Parts", a restricted set of Javascript functionality that works reliably. No matter how reliable your technology, however, you will find critical little details where you thought your programming language would give you output X, and you get Y instead. That's why it's always critical to test your code, so that these issues turn up ASAP; you want to modify your code to get the desired output or redesign the application so that you can succeed.

In a disturbing number of programming circles, this is considered inefficient, but the alternative is that when something fails, no one on the team has the slightest idea why. If you skip important testing on each section of code in the name of saving time (this requires a few minutes or a few hours depending on the size of the project and module), you now have days or months of debugging to resolve your problem. When discussing construction, I mentioned that you need to save the right time on your projects. Abbreviating testing is one of the most common major errors that causes development teams to waste time; that's why its ironic (and so tragic) that it's usually done in the name of "saving" time.

Incremental development allows you to progress efficiently and track your progress systematically. When planned well with user interface output prioritized early in the construction list, you can hit UAT checkpoints early in the process and then write the dynamic functionality in later when the client has OKed the module. This enables you to balance risk management, change management, client management, at the exact moment when you're trying to complete the project and hit your deadline within budget. Who could ask for more? That paves the way for as smooth a transition as possible to the day when you will deploy the application and hand it over to the end users.