When starting a new programming task (or company 😉 ), it is very tempting to just plunge in and start coding (or doing) right away. The release early-and-often mentality encourages this approach. Documentation becomes optional and design is seat-of-the-pants.
Hard-lessons learned here tell me this is very, very bad.
Take the time for these steps and minimize use of the computer, because it is very easy to give into the temptation to start coding. And when you are coding, you are thinking about implementation issues, not high-level design issues.
- Write down the goals: This is the easiest trap to avoid and the trap that catches most people. If the developer knows the goals enough to write them down, they can be confident that they are building what was asked for ( just send the written description to the manager or client ). Equally important, the developer knows when they are “done” and what is “good enough” to satisfy the immediate need
- create the use Case tests: Create pseudo-code showing the various operations needed by the project. Missing steps are discovered prior to implementation. Did a previously unknown piece of information just “magically” appear? How was that magic knowledge actually retrieved? Are operations out of order?
To help clarify what is meant by a “Use Case Test”, here is an example of what I am currently working on:
Peter wishes to use Amplafi to upload an image to his website.From Peter’s perspective,
- Peter uploads a image to Amplafi
- (Peter does other things)
- Amplafi asks Peter about Ftp username/password
- Amplafi ftp posts the file to Peter’s web site.
An equivalent Use Case test looks like:
This example Use Case test discovers issues like:
- Create a ResourceLocation representing the local temporary place an uploaded file will be stored
- upload the image from the user’s computer. (mocked, testing the use case – not the actual upload)
- look up the (test) ftp authentication information for the customer’s website (the ftp server is mocked as well, so username/password is fake)
- Determine the location the image will be stored on the ftp server
- create ResourceLocation holding the image’s ftp location
- perform the upload from Amplafi’s computer to the ftp server (the actual upload is to the fake FTP server)
- validate that the ftp upload was successful
- clean up the temporary copy on Amplafi’s server
- update the ResourceLocations created
- Notify the various statuslisteners about the transfer’s success.
- The code not having access to the Ftp authentication information
- Not being able to determine where the image should be stored temporarily on the Amplafi server
- Not knowing when the temporary copy can be deleted.
- No mechanism to handle a ftp upload failure ( ftp server dropped connection, had over quota error)
- No mechanism for handling overly large files. ( 100TB anyone? )
- create the diagrams ( use paper ) It is tempting to use a UML tool to create the diagrams; resist temptation. You want to be able to throw away ideas with abandon. Spending effort to create a nice looking UML diagram is wasted if the design concepts need revisiting. Save the tool for when the design has been validated.
- create the interfaces: Create the interfaces needed by the pseudo-code in step 2. Define the purpose and nature of the interfaces and its implementors.
- Are implementors expected to be stateless singleton services? Or are they database objects?
- How are implementors created?
- What services does an implementor have access to?
- How much behavior should be the implementors have? For example, database objects should have minimal logic and have specialized managers do the business logic level operations.
- What is the expected lifespan of implementors? Single transaction? Only for a session?
- Life cycle control/ownership. Are implementors “owned” by another object and when the owner is removed so is this implementation?
At this point, I have lots of interfaces, enumerations ( java enum ) that I am creating and destroying willy-nilly. With lots of TODO, FUTURE, and (yes even) HACK comments. Because I haven’t spent any time actually creating an implementation. I have no reluctance to discard, combine, or separate interfaces.
- create the test framework: At this point, the interfaces are nailed down, the test pseudo-code can be converted to actual test code, and …
- create the implementation Unlike true Test Driven Design methodology, I tend to write the implementations in conjunction with the test code. Too many times, I have discovered that nasty little issues around implementation require rethinking the interfaces and the tests. I want to minimize my investment in tests until I am reasonably certain that the production code being tested will not have major API changes because of implementation issues. (Example of an implementation issue: an external library requires data in an order that my planned implementation cannot handle)