Friday, December 7, 2007

Managing your project

Remember to regularly pause from your team's mad programming frenzy to put your heads up and think about the lifecycle and the schedule of your project. What build are you on, is it an alpha or a beta, when's your next demo, how much time is left, who's doing what now, and what are the major risks to finishing your project on time?

At the beginning of the project your team (possibly a team of one, if you're working alone!) is in the prototype phase; this is the phase when you barely know what you're doing and are still trying things out. Later on you're in the alpha phase where you're got a basic handle on things and you're adding neat features to the program as fast as you can. And then the team has something you can show people, and that's your alpha 1. Then you incorporate feedback from your demos, add more features, clean it up a bit and get to alpha 2. And so on. Eventually your team gets to the beta phase where you're not allowed to change things anymore and you have to focus on getting everything to work in the nicest possible way.

In the early stages of a software project you have to keep going back over it to make your design cleaner and simpler. When you first add some features, you're likely to do this in a messy, hurried fashion. As soon as you see that they work, you want to go back and clean them up. If your team leaves anything messy in your program it's going to cause you trouble later on after the program grows – maybe a lot of trouble.

In a simpler world, perhaps we would know what our programs are going to do before writing any code at all. But this is impossible when you're in a process of learning new programming tools and exploring new intellectual concepts. And, given the rapidly changing nature of the software business, a programmer is always in the process of learning new programming tools. The new features never stop, and it's a shame not to learn how to use them. Remember that process and project-management can be something you do to make things easier. Don't let it be an empty ritual done only to appease a boss.

Tracking the builds

Let's say a bit about how to describe the build you're currently on. How can you tell the difference between alpha and beta builds? Entering beta means that you've frozen your features and are now focusing on polishing and debugging. So before that you're in alpha phase. Is each new build an alpha? It's really just a matter of taste. Some software engineers call each successive build an alpha build.

It's more common to call a build a new alpha only if you plan to show it to people outside your group, that is, only if the build is in some sense a big deal, a rounding off point. In this way of thinking, the first true alpha of a program would be the first version that has the program's basic functionality and architecture in place.

Of course for a programmer what really matters is which version number of the program is being built. You start with version 1, and you go on from there, and you never ever mix up the code from different versions. Given the fractal nature of software development (remember the crinkled coastline), you sometimes will do a number of builds without changing the version number – in these cases be sure to add a date to the caption bar of the program and to the name of the directory where the code lives. It's worth the small extra organizational effort to avoid losing track of which is the latest build.

A formal name like 'the alpha 1 build' has more to do with your relationship to society than it does with your relationship to the code. That is, if you're in a software projects class, the professor is going to want you to hand in an 'alpha 1' build of your program, so whatever build you have done by that date is perforce your alpha 1. If you're writing a program for fun, the first build that's good enough for you to feel like showing it to your friends is your alpha 1. If you're working in a company, and there's going to be a little demo for a key manager, the program you get working for that first demo is your alpha 1. The boundary line between your prototypes and your alphas is hazy. It can very well happen that the 'alpha 1' is 'build 5' of your program.

By the same token, you will probably develop a bunch of intermediate builds between your official alpha 1 and your official alpha 2. If your professor wants an alpha 2 version of your project, that doesn't mean you're only supposed to rebuild the program once after the alpha 1! And of course in a business environment, the expectation is that the alpha 1 is going to be a springboard that suggests all kinds of improvements you can still make before the alpha 2. It might happen that you have two, three, or more version builds in between your official alphas.

No matter whether you call a build an alpha or a beta, it's extremely important to be fanatically, obsessively, compulsively organized about which files belong to which version of your program. By far the best strategy is to assign numbers and dates to the successive versions of your program, and to keep the code for the separate versions in separate directories whose names include the version number and the calendar date of the most recent build. Generally, before starting to make extensive new changes, you should copy the directory of the most recent successful build and change the directory name to include the new date. That way, if something goes wrong, you haven't thrown out the last good build.

A modern way to handle this is to use a revision control tool like Microsoft SourceSafe or the RCS (Revision Control Software) commonly used on Linux systems. A complicating factor with using a revision control tool is that there needs to be one master server directory which all team members use for checking out and checking in their code. Since a laboratory course will often involve teams of students working at home on disparate machines and without access to a single server site, Chapter 21: Tools for Software Engineering in Part II presents a cruder form of 'manual' directory-based revision control. But once you get more serious about software engineering, you will definitely want to learn how to use something like SourceSafe.

Commenting your code

Always try and construct the code so that it's easy for you or other programmers to understand it, and to tweak it. In this subsection we'll first give some very specific suggestions and then some more general ones.

It's a lot easier to read code that's properly indented. A good way to enforce this is to always use tabs for your indents, and never use spaces, the reason being that it's easier to be inconsistent with spaces for indents, you might easily vary between using three, four, or five spaces. In its default setting, the Visual Studio editor will muddy the water by sneaking and replacing tabs by spaces. To block this behavior, use Tools | Options dialog, go to the Tabs tab, check Keep Tabs instead of Insert Spaces. Regarding the Auto Indent selection below that, it's not a bad idea to work with it set to None so that you have full control over your tab indents. The basic principle is simple: each new block level is indented one more tab. When lines run off the right of the page you break them with an Enter, and add another indent to the typed lines.

As a rule, comments should be indented one tab more than the lines they are commenting on; it should be easy to scan down some code and see where the actual lines are. Every now and then, for a really long comment, particularly at the start of a block of code, you can bring it over to the leftmost margin. Indenting the comments is a little bit of work because as you edit a comment, say by adding a long phrase in the middle of a paragraph, you'll mess up the line breaks and have to keep going through them and reorganizing the tabs and Enters. Code editors like Visual Studio don't do automatic line-wrap like a text-processor.

Where should you comment? It's easy to tell someone to comment everything, but that's neither helpful nor practical. One useful rule of thumb is to comment the 'intense' parts of your code. If your heart actually beats faster when you are writing some code, this is definitely a spot where you should add a comment. Common coding emotions are confusion, pride, and anxiety.

When you're confused you're kind of feeling around in the dark, and the comment will be helpful if you need to change what you tried. If you're proud, it means you thought of some cool trick that needs some explanation. Or this might be a place where it took you a while to get things to work right, and now you've finally gotten out the bugs. Tell about it, so that others can learn. When you're anxious, it means you've gotten something to work that you or somebody else could easily break again, so you should explain what not to do.

The software lifecycle

Whether you're working alone or working as part of a large team, there will be a plan of action for how to design, code, test, debug, and document the software. A plan like this is usually called a software lifecycle. In this next section we'll discuss a couple of possible lifecyles and then describe an Inventor lifecycle to use for the kind of exploratory, time-constrained project that we'll do in this book.

If you do not consciously choose a particular software lifecycle, you end up in fact using a scenario known as 'code and fix.' It means making no plan at all, but instead simply diving in, writing code, and trying to fix each new problem as it develops. Code and fix is considered one of the most inefficient ways of developing software.

You should always take some time before starting a project to try and figure out what you are going to do. A good rule of thumb is to estimate how much time you should spend planning – and then plan for three times this long. One well-spent hour of planning can save hundreds of hours in coding and fixing further down the line. When you have a really clear vision of what you want to do, writing the code to do it does not in fact take all that long. The hard part is in getting the vision.

There are a number of tried and true software lifecycles which involve a good measure of planning. The most traditional model is called the 'Waterfall' software lifecycle. This model describes a straight-through process: completely plan what you want, specify how the program will behave, nail down the architecture, work out the detailed design, and only then begin coding, finally testing and debugging. The stages of the Waterfall are given in Figure

In practice, people tend not to use a pure Waterfall approach, because it is difficult if not impossible to completely specify and plan your program in advance of writing any code. It's more common to see a lifecycle that resembles the linear Waterfall approach but which allows for the possibility of 'swimming upstream' and revisiting the earlier stages. It would be quite reasonable, in other words, to draw additional upwards arrows from the specification, architecture, detailed design, coding, and testing and debugging boxes.

Another popular lifecycle is known as the Staged Delivery model. In this lifecycle we organize the requirement phase so as to break the program into several stages of functionality. The plan is that at the end of each stage the program should be fully releasable. But stage one might include only basic functionality, stage two a richer set of features, and perhaps stage three will have lots of bells and whistles, while stage four will be incredibly deluxe. So as to be sure of being able to deliver some kind of product when the time runs out, the Staged Delivery method completely finishes stage one, then stage two, and so on. The sketch for this lifecycle is given in Figure. Note that here we try and fix the architecture early in the process, but we allow for changing the detailed design at each stage. Note also that we try and figure out all of the features that we want right at the start so that then our architecture will be roomy enough to accommodate all of our planned functionality.

Names and descriptions for many other software lifecycles can be found, for instance, in Steve McConnell, Rapid Development (Microsoft Press, 1996), from which our description of the Staged Delivery model is taken.

In this book we're going to use a somewhat exploratory software development process where we tend to be occasionally groping in the dark. For this we'll use a model which is a linear process with two repetitive loops in the middle. Just to have a name for it, we call it the Inventor lifecycle, to suggest that it's a reasonable lifecycle to use when you're exploring an area that's new to you and are planning to discover new things about how to use your tools and your framework, possibly developing some entirely new features as well. The name isn't meant to rule out the possibility that you might use the Inventor lifecycle to make a highly polished final product. The Inventor lifecycle goes as shown in Figure

We expect to develop our program though a number of builds. The builds break into the alpha and the beta stage. In the alpha stage we still don't know exactly what features we're going to have, so we allow for the possibility of changing our specification several times. When we see our deadline coming into sight, we switch into beta mode by freezing our feature set and focusing on testing, and debugging.

Now let's discuss each stage of our Inventor lifecycle.

Requirements gathering

As discussed above, in the requirement phase you start with one or more software concepts and try them out on the other stakeholders, who will be your professor and your other team members in a classroom situation. If you're using this book for self-study, you might try and involve at least two other people as stakeholders – if only in the role of interested on-lookers. After several cycles of requirements gathering you arrive at a basic plan for how the program will behave. You get a specification sketch describing the program and including some drawings of how the screens will look. The specification sketch should have the four components (S1) concept, (S2) appearance, (S3) controls, and (S4) behavior.

Architecture

Before doing any coding, you need to figure out what classes you are going to use. It is likely that your class structures will change somewhat as time goes on, but it is important at the outset to make an honest effort to separate out your classes and, above all, to think about how they will inherit from existing classes. The most common design mistake that beginning programmers make is to block copy an existing class's code for a new class when it would be so much cleaner and easier to have the new class be a child of the existing class. UML class diagrams are a good tool for working out the high-level design.

Once you have a high-level design and a specification that's been honed by requirements gathering, you can put these together into a document sometimes called the 'RAD' for 'requirements and design'. (Presumably by the end of the requirements gathering, the requirement and the specification match.) Of course, in an exploratory classroom or individual project, we can expect the specification to get more detailed and feature-rich as time goes on.

Specification N

Once your requirement and basic architecture has the go-ahead, you need to figure out what members and methods go into your classes. You will also need to work out a more detailed draft of the User's Guide so that you know exactly what you want your program to do.

The specification N is a list of the features you expect the program to have, and the detailed design includes all the methods you need to implement them. When you get into the low-level design, what you will often be doing is to write out C++ headers for your classes. You can start the process informally, but given that you must eventually write the code, it's not a bad idea to simply do the low-level design by actually writing real headers. New inspirations will come as you try and implement the methods, get them to compile, and make them work in the program.

As time goes by, you will of course think of new features to add to your program – and this is why we talk about specification N and detailed design 'N', where N is a number that starts at 1 and usually ranges between ten and several hundred. In practice you will end up cycling through steps specification and detailed design N and alpha N many times. As you develop your program, more and more new features will suggest themselves, and it would be foolish not to include the good ones simply because they aren't on some list you made up before you really knew what you were doing. Conversely, you may also find that some features you'd planned to include will be too difficult or time-consuming; reduce your risk by throwing them out.

It seems odd to admit that it's not possible to fully control the development process, but this is a reality of contemporary software development. There seems to be no way around it. A completed program is such a large and complex object that it's impossible to fully predict the form of the finished object when you start. It seems likely that software engineering is intrinsically chaotic in the formal sense of not being entirely predictable. It's entirely possible that software engineering never will become an exact science. [There's an interesting book about this notion: David Olson, Exploiting Chaos: Closing in on the Realities of Software Development (Van Nostrand Reinhold, 1993).]

This fact leads some people to question if we should really call it engineering. If you ask a mechanical engineer to build a bridge, he or she can tell you precisely how long it will take, how much it will cost, and what the finished bridge will look like; but thanks to the chaos of complex systems, it's hard to make firm predictions about a software project. Of course your managers will ask for predictions anyway. Try and buy yourself as much time as you can, and if there's still not enough time, remember the Constraint Triangle, and negotiate to reduce the feature set or to add programmers to your team.

Alpha N program

The nearly-finished version of a program is usually called the beta version, and the alpha versions are the ones that come before that. An alpha version of a program is normally somewhat rough and unfinished.

The very first version of the program – the alpha 1 – is sometimes more of a 'prototype', which is a quick and dirty version of the program simply to prove that your concepts will work. Very commonly there will be some existing program that you use as a kind of 'seed' or 'starter dough' to get your program going. These are prototypes of a kind. But for your real alpha 1, you need to make the program show at least some minimal functionality in implementing your required features. If there are several possible approaches, you will sometimes want to prototype all of them so that you can compare. So in some situations you may have several competing alpha 1 programs. But, by the time you get to alpha 2, there should be only one version of the program.

As mentioned above, you can expect to run through at least ten or 20, and more typically over 100 alpha versions of your program while developing it. One thing to be careful about is that you don't get stuck with some sloppy design that happened to get into the alpha 1. During the early stages of alpha development you should keep thinking about your class structures. If anything is crude or awkward, now is the time to fix it, before the program goes on and gets a lot more complicated.

Usually you'll run through two or three alphas before going back and changing the design, so it's more like you'll do a specification and detailed design step, a couple of alpha programs, then another specification and detailed design, then a few alpha programs, and so on.

The most important practical thing of all when doing multiple versions of a program is to keep the versions straight. There is so much to say about this issue that there is a File Names and Directory Structure section in Chapter 21: Tools for Software Engineering.

Alpha N User's Guide

Just as there is a distinction between a detailed design and actual code, there is a distinction between a specification and actual User's Guide documentation. While doing new versions of the program, be sure and keep your documentation current. Put your documentation in a handy text file, and every time you change a feature in your program, write this change down in your documentation. At the early stages, you do not want to be involved with a technical writer or an expensive technical publications division. The alpha documentations should be quick and light, preferably written by the programmers. The alpha documentation doesn't need to be anything fancy, but it does need to clearly state what the controls are and what the ranges of the control parameters are. Otherwise you're likely to forget. This is particularly important if some of your controls are still in the popup or hot-key stage. In a way, the ongoing documentation acts as notes for the next specification. It's also a good idea to keep a separate document listing known bugs and desired features.

The User's Guide should include an explanation of why your program is interesting, a guide to installation and quick start, and a feature by feature explanation of all of the menu and dialog controls. Often working on the documentation will give you ideas on how to improve the user interface.

You should make your documentation as tight and neat as your code. Use good clear English sentences, and always be sure to use a spell-checker on your documentation. Avoid repeating obvious things over and over, and avoid uninformative statements like 'The Change Size control changes the size.' Instead explain what size is being changed, what the allowable range of size values is, why someone might want to change size, and give examples of relevant behavior at the lower and higher ends of the range.

As well as the User's Guide, there is another kind of documentation which you can create: the programmer's documentation. Most of the programmer's documentation appears inside your code: as dated logs at the beginning of the main program files, as short comments on individual lines of code, and as extensive comments next to the 'tricky' parts of the code. In addition there might be a short overview document that explains to a new programmer how all of your project files fit together.

Final design and feature freeze

In developing software, you are usually faced with some kind of temporal deadline. You can't go on changing and adding to the program forever if you are going to hit your ship date. Polishing up the program and getting the final bugs out is usually going to take more time than you expected. In fact there's a saying among software engineers: 'The first 90% of the program takes the first 90% of the time, and the last 10% takes the second 90% of the time.'

The final design has a set-in-concrete nature that the alpha N designs do not. Once you get to this point, this is what you are going to finish, and nothing more or less. 'Feature freeze' means, of course, that you are not going to be adding any more features, no matter how enticing they may seem.

Regarding how long it takes add things to a program, the author often thinks of a fractal such as a coastline. Standing on one rocky outcropping of a coast, you might look along the coast towards the next promontory and think it's an easy walk. But coasts and programs are fractals, and you're likely to find inlets blocking your way, inlets with further smaller inlets along them.

Beta N Program and Beta N User's Guide

At this point you know exactly what the program is supposed to do. The problem is to make this really true. So now you alternate making new versions of the beta N release with testing and debugging the release. This phase is also when you get really serious about your User's Guide.

In software companies, the creation of the documentation is often farmed out to a technical writing division within the company. The final specification and detailed design acts as a good starting point for the tech writers; although it is easier for them if you have been dutiful about your alpha N documentations. In general it is not a good idea to let the tech writers get started before you have done your feature freeze and gotten your final design together, otherwise they may waste a lot of time working on documentation for features which are still subject to change.

What's wrong with that? The problem is that your company will account the cost of the tech writers' time as part of your project's expense, making your work appear much less cost-effective.

Testing Beta N

It's hard to anticipate all of the bugs that a program may contain. The more people you can get testing it the better. Often the writers working on the documentation function as a kind of testing staff; they try writing down what the specification says the program does, and they see if this is true as they write it.

While testing your program, always run it in debug mode (by pressing the F5 key) so that if and when it crashes, you will be able to use the debugger information.

It's a good idea to develop an 'autorun' mode for your program under which it will run and do things without any user input. This is a type of automated testing that can be pushed pretty far; you can, for instance, have your automated test periodically change values of the program parameters as if a user were doing things.

Larger companies will have a special group devoted to testing the software; this is sometimes called the QA group. The fact is, developers don't want their code to break. Whether consciously or not, they know which kinds of tests to avoid. Only a dispassionate QA tester can really find the problems in your code. By way of testing the Pop program, the author has let successive waves of students try to find bugs in it, with extra homework points going to those who succeed.

If a lot of people are interested in your program, you may be able to hand out beta versions to them and have them try the program out.

As with the sequential alpha versions, you need to be careful to keep the successive beta versions distinct. Another issue is that of bug tracking. You should have a big document (or data base file) which includes a brief description of each bug and how to reproduce the bug, along with a record of what has been done to fix the bug. For the purposes of a student project, a simple text document with a name like bugs.txt can do the job. You might keep such a file in with your source code and revise it as time goes on.

Debugging Beta N

There are a lot of special techniques software engineers use to try and keep bugs out of their code.

Using the object-oriented language C++ instead of C is one good way for avoiding bugs. C++ allows you to encapsulate closely related variables and functions into the special kinds of types we call classes. (The instances of your classes are your objects.) With the object-oriented approach, your code becomes simpler to read and to understand, and this means it is less likely to have major bugs in its logic. The use of 'operator overloading', for instance, enables you to write something like a = b + c to stand for, say, vector addition just like you would want it to.

Another good thing about object-oriented programming (called OOP for short) is that it allows you to code up some frequently used routine only once, and to provide interfaces so this same piece of code can be used over and over. It is much easier to perfect and maintain a piece of code if it lives only in one place instead of having variant versions copied all over the place. OOP also provides a kind of access-protection for the member variables of objects, which makes it harder to carelessly alter a variable without taking into account the side-effects that this change may have. Instead of arbitrarily changing member variables, you use special 'mutator' functions that you have written so as (hopefully) to nail down all side-effects once and for all.

Still another gain from OOP is the use of constructor and destructor functions. These functions, which you write yourself for each class you define, take care of initializing the fields of your objects to default values, allocating necessary memory and resources for your objects, and freeing up memory and resources when you are through with an object.

A final benefit to C++ is the availability of template libraries which include, for instance, templates which encapsulate the notion of a linked list, a map (also known as a hash table) and an array. The MFC templates for these useful classes are called CList, CMap, and CArray, respectively. A CArray template class, for instance, takes care of the memory management issues involved with allocating and deallocating space for an array.

It should go without saying that learning how to use the debugger is all-important. Beginning and intermediate programmers tend to avoid the debugger, as it seems too confusing. But really and truly, the debugger is your friend. While developing a program you should primarily be building the 'Debug' version of the program as opposed to the 'Release' version – there is a switch for selecting between the two in the Microsoft Visual Studio compiler (see Appendix C for the control sequence). See Chapter 21: Tools for Software Engineering in Part II for more detailed information about using the debugger.

Final version and product ship

Putting together the final version can involve figuring out things like how to fit it all on the required number of disks, and how the users are going to install the software from the disks. Lots of issues relating to the documentation will arise as well. Often you will want to provide screen-shots for use in the documentation.

In a truly Staged Delivery cycle, it's conceivable that after you reach this level you jump all the way back to the specification and detailed design N stage, and implement a new layer of features. Note, however, that this is time-consuming, as once you start adding new features, you need to take them through the repeated alphas to get them working, and then take them through multiple betas to get them tested and debugged.

Trying to add new features late in the lifecycle is risky, but sometimes the pressure is irresistible. The urge is known as feature creep. Unless you know that you're going to have enough time to fully test the new features after implementing them, resist feature creep.

The development spiral

We mentioned above that there's a kind of software lifecycle known as the Spiral lifecycle. This means thinking in terms of spiraling clockwise around and around through four stages: analysis, design, implementation, and maintenance.

Analysis

Design

Maintenance

Implementation

The analysis phase involves figuring out what you want the program to do. This is similar to making a software requirement. In reality, we don't immediately know all the things we want the software to do, so actually we pass back through this stage numerous times.

The design phase involves several things. One part is the object-oriented design: figuring out which classes to use, and what the class methods should be. Another part is the program design, figuring out how to break your code into modules, and how to hook the modules together with global variables and function calls. A third part of the design means figuring out your user interface. All this is too much to do at once; what you do is to keep extending and improving the designs as you pass through the design phase over and over.

The implementation phase means writing the code. As with design there are at least three types of coding you need to do: the class method coding, the program flow code, and the user interface code.

As used here, the maintenance phase includes the debugging and tweaking that goes into the program to make it work properly. The first time you implement something it rarely works just as you wanted it to. You may need to fix a bug, alter a function's behavior, or change a dialog box design.

After each cycle through the four phases, you look at what you have and try and document it. The documentation is itself a kind of analysis, and as you get a deeper understanding of your program you're ready to alter the design, implement the new design, do some maintenance on the new implementation, analyze what you've done, and so on.

Like most lifecycles, our Inventor lifecyle is a kind of cross between the Waterfall and the Spiral lifecyles.

Some students are disappointed when they take a course in software engineering. They had hoped to learn a clear and simple series of steps to follow so as to build a program. But the process turns out to be neither clear nor simple. Like it or not, software engineering is a fuzzy discipline which involves a certain amount of creativity.

A main design methodology we're going to be using in this book is the object-oriented approach described in Chapter 4: Object-Oriented Software Engineering. To begin with, we're using the object-oriented language C++, but we need to do more than write in C++ to make our design and our code truly object-oriented. More than anything else, doing object-oriented software engineering involves iteration and successive levels of refinement.

Here's a relevant passage from a classic book on object-oriented software engineering:

B. Curtis studied the work of professional software developers by videotaping them in action and then by analyzing the different activities they undertook (analysis, design, implementation, etc.) and when. From these studies he concluded that 'software design appears to be a collection of interleaved, iterative, loosely-ordered processes under opportunistic control . . . Top-down balanced development appears to be a special case occurring when a relevant design schema is available or the problem is small . . . Good designers work at multiple levels of abstraction and detail simultaneously.'

Most software systems are highly unique, and therefore their developers have only a restricted basis of experience from which to draw. In such circumstances, the best we can do during the design process is to take a stab at the design, step back and analyze it, then return to the products of the design and make improvements based upon our new understanding. We repeat this process until we are confident about the correctness and completeness of the overall design.

—[Grady Booch, Object-Oriented Design (Benjamin/Cummings, 1991), p. 189]

The software engineering process

A software project consists of both the code and the process by which you develop the code. It is important to formalize the process that you use. This means that you should have a set of documents describing your process, and that you should frequently look at and revise the documents while the project is underway.

There are many ways to separate out the different aspects of the software development process (as distinct from writing, testing, and debugging the code). Here we'll view the process as having four pieces.

  • Requirement and specification.

  • Schedule.

  • Design.

  • Project documents.

We already discussed requirements and specifications. Now let's say a bit about the next three areas.

Schedule

We put a number of things under the category of schedule: lifecycle, milestones, task list, QA plan, and risk management.

A software lifecycle is plan for what to do when, i.e. what order to carry things out in. Different projects use different kinds of lifecycle models. The lifecycle is a large enough topic that we'll devote a whole section to it a little later on in this chapter, eventually focusing on the Inventor lifecycle that you will use for your game project.

Setting milestones means (a) figuring out some definite, identifiable stages to reach, and (b) setting dates for when you plan to hit these milestones. As well as the finish-line milestone of shrink-wrap (or of posting your package on the Web), you have many preliminary milestones.

In a typical classroom project, your main milestones might be these.

  • Preliminary specification sketch (followed by requirements gathering).

  • PowerPoint presentation of an approved specification sketch and a UML class diagram for the design.

  • Classroom demo of an alpha build.

  • Classroom demo of a beta build.

  • Final demo.

Several times during the semester, you and the professor (the project stakeholders) need to make out a list of your remaining class meeting dates and figure out reasonable delivery dates for the milestones.

If you're using this book for self-study, pretty much the same kind of schedule might apply – with the difference that you'll want to find friends or relatives to discuss the specifications with you and to view and test your demos.

One thing to realize about the milestones and the schedule is that they need to be continually revised – like everything else in software engineering. Managers often make use of the Microsoft Project software to keep track of their schedule and their milestones.

As well as the main milestones, you may also want to think in terms of smaller milestones. In the case of a PacMan style game, getting to a presentable alpha build would involve, for instance, the milestone of creating a cMaze class or writing a method that simply builds such a maze out of cCritterWall objects.

When thinking about how to fit the smaller kinds of milestones into your schedule, it's useful to have a task list. This would be a list of all the things you need to do before the project is done. Particularly on your first few projects, you tend to underestimate the number of little extra tasks you're going to have to do near the project's end. These might include getting the bitmaps or art ready, making demonstration files, checking the help file against the program, and so on.

In making a schedule it's also important to allocate time for testing the program and testing how well the documentation matches the behavior. This is why software projects are usually divided into an alpha phase and a beta phase. The beta phase is when the testing and debugging takes place.

The process of testing is called QA, for quality assurance. It's important to allocate sufficient time to this, and to accept that you really need to retest after each new beta build. It's not unusual for a bug fix in one spot to break something somewhere else. In making out the schedule you need to consciously plan in enough time for sufficient QA. We'll say more about the testing process in Section 2.3: The Software Lifecycle.

As mentioned above, when you manage a software project you have to keep going back over your schedule and making sure that it matches the reality of what you've currently done. The process of risk management means looking ahead and trying to anticipate some of the possible ways in which you may go off schedule.

There are two main parts to risk management: monitoring and recovery. Monitoring means that you have to honestly admit what the most dangerous problems are so that you will immediately recognize them if and when they start to happen. If your program hinges on your being able to integrate a certain kind of image file into your code, there is a risk that you're not going to be able to do it. Monitoring means facing the fact that the worst can happen – and persistently asking if it's happened yet. The risk of not being able to use a type of image remains until it's been demonstrated that it can be done. Because this task is a risk, it is not left until the very last minute.

The recovery aspect of risk assessment means formulating a Plan B, an alternate strategy to pursue if a given fear comes true. If, say, such and such a team member is unable to integrate *.gif image files into your code by the second alpha build, then you will reduce the risk by using, say, only *.bmp image files. If the team member who was supposed to provide your enemy creature's behavior algorithm stops coming to class or answering email, then someone else better start working on it, and if no one can, then you better figure out how to have a game in which the enemies simply use a default framework behavior algorithm.

Risk assessment monitoring is about having your team be honest with yourselves and not hiding your heads in the sand. Risk assessment recovery is about formulating Plan B, and, to mix up the metaphors, being willing to throw the stove and food out of your balloon basket if that's what it takes to stay aloft.

Design

Design breaks into two levels: the high-level design and the detailed design. The high-level design is also known as the architecture, which tends to sound a bit more impressive.

The architecture or high-level design involves specifying the program's 'nouns' and its 'verbs', that is, the program's classes and the program's runtime behavior. The detailed design involves getting more specific about the classes and beginning to write out prototype code for them.

When we are doing the high-level design, we use a process known as object-oriented analysis to help figure out what classes we should use. This is a matter of singling out the key concepts used by your problem, and thinking about how best to represent the concepts as classes. Once you've decided which classes to use, the process known as object-oriented design helps you find the best way to make your classes work together.

Recall that we use 'UML' to stand for 'Unified Modeling Language'. A good way to talk about your class design is to use a UML class diagram, which is a bunch of rectangles representing classes, with lines showing the relationships among the classes. Drawing a class diagram is a good way to get a useful discussion going about the spec, and can be helpful in moving from the architecture to the detailed design. A class diagram on a whiteboard makes a focus for a group discussion of class design, and provides a non-technical channel by which coders and managers can usefully interact. Look ahead at the class diagrams of the Pop Framework in Chapter 3: The Pop Framework (Figures 3.4 and 3.10).

Describing the program's runtime behavior means figuring out the order in which things happen. How, for instance, does an animation program update itself? When a user clicks the mouse, what is the sequence of events we expect to have? UML sequence diagrams are very useful for sketching this out. Look at, for instance, some of the sequence diagrams in Chapter 6: Animation, for instance Figure 6.3 showing the sequence diagram of how our Pop programs animate the creatures onscreen.

The detailed design for your program states what members and methods your classes will have. In C++, the detailed design can consist of explicit definitions of the classes you will use; a good way to be precise about your classes is to go ahead and start writing up the formal class definitions as *.h header files. You can postpone the *.cpp implementation of the class methods for a little while. But you will find, once you do get into implementing a class's method, that you often need to rethink the original class design. This kind of back and forth is one of the enjoyable parts of object-oriented design. We say more about this in Chapter 4: Object-Oriented Software Engineering.

Project documents

The most visible project document is the User's Guide, whether in printed or in help file form. For now suffice it to say that the User's Guide might typically include sections called Overview, Getting Started, Things to Try, and Controls, where the last section exhaustively describes the effect of each control found in the user interface.

Table 2.1. Documents for software development.

Specification

Revised requirement document

Specification sketch with concept, appearance, controls, behavior

Scheduling

Calendar with 'milestones'

Risk list

Design

UML class diagram

UML sequence diagrams

Class headers

Documentation

All of the above, plus User's Guide

Looking back over the first three parts of our project process, we can imagine making a document (or set of documents) for each of them, that is a specification document, a schedule document, and a design document – all these in addition to the User's Guide document.

Table 2.1 lists software process stages and some of the documents that might accompany them.

All documents should be visible to all the stakeholders involved in the project: the managers, the coders, and the customers. On a really well-run project, one might put all four pieces up on a website, possibly an intranet site or password-mandatory site rather than a public one.

None of these documents is set in stone. We expect that each of them is going to change somewhat during the project lifecycle, although there will normally be a 'feature freeze' date after which no further changes to the specification documents are allowed. But, up until that point, we are going to learn more about our project from the code, from the early builds, and from the way we see the schedule unfolding, so it's reasonable to keep changing things.

There are various models for how to update the documents. Either one person is in charge of maintaining each document, or they are changed during group meetings, or stakeholders might be allowed to 'check out' a copy of the document for revision, with the earlier versions being preserved.

Requirements and specifications

Requirements

The development of a software product begins with a requirement for a certain kind of program and a brief specification for what such a program might be. The requirement is a little like a question and the specification is like an answer. Put a little differently, the requirement is like a request and the specification is a proposed solution.

The usage of the words 'requirement' and 'specification' is somewhat fluid, and you will find different conventions in different books on software engineering. In Software Engineering and Computer Games, we treat the requirement as a request for a certain kind of software and a specification as a proposed description of the software. And we stress that there is considerable interplay between the requirement and the specification. During this requirements gathering process, the stakeholders in the project try to converge on coming up with a requirement and a specification that match each other. The stakeholders might include corporate customers, investors, the managers, the programmers and perhaps a sampling of eventual users.

So, once again, a software requirement says what the target program is supposed to do. A requirement might be something as clear-cut as, 'Write a program which displays our inventory data in an attractive format,' or something less precise like, 'Write a Web browser that runs on cell phones,' or something open-ended like, 'Write a really nice game.' At the preliminary level, the initial requirement can also be called a vision or a software concept.

As well as saying what the program is supposed to do, a requirement may list some specific features that the program is expected to have. Sometimes a software requirement starts out very detailed, but more often it will be brief.

In industry, another aspect of a software requirement is that it will include some ideas about the marketability of the intended product. An industrial software requirement will say why the program is worth doing and why people will want to have it. Managerial types have a very persistent way of asking, 'What is the intended market?' So usually a software requirement will address this question as well as the question of what the program will do.

UML diagrams

In recent years there's been a movement to consolidate the different kinds of ways that software engineers talk about what they do. The result is a loosely defined set of names and conventions called the Unified Modeling Language, or UML. UML is primarily used as a methodology for drawing diagrams relating to the creating of programs. The fact that it is 'unified' means that UML includes the contributions of many different computer scientists, to the point where it is a bit of a catch-all. There are at least nine different kinds of UML diagram: use case, class, object, activity, statechart, sequence, collaboration, component, and deployment. We should also mention that much, though not all, of the UML assumes that you are using an object-oriented style of software engineering like we'll be discussing in this book.

In Software Engineering and Computer Games we'll discuss only these five kinds of UML diagrams.

  • Use case diagrams for software requirements.

  • Component diagrams for the dependencies of the source-code files.

  • Activity diagrams for program execution flowcharts.

  • Class diagrams for the high-level structure.

  • Sequence diagrams for interactions of program objects.

A basic thing to remember about UML diagrams is that they're meant to be quite simple. The primary purpose of UML diagrams is to make it easy for a project's various stakeholders to communicate with each other about the project. Keep in mind that a number of the stakeholders are likely to be non-technical. There's nothing like putting some UML diagrams on a white-board to get a discussion going. UML use case diagrams are of particular value during requirements gathering.

Another purpose of UML diagrams is for what is called forward engineering, where we move from a concept towards actual code. UML class diagrams are particularly useful when you are in the stage of working out the high-level design for your program. The UML activity diagrams are useful for understanding the overall program flow. And the UML sequence diagrams are good for working out the details of how your objects interact.

A third purpose of UML diagrams is for reverse engineering, that is, for understanding how an existing program works. This is in fact the situation that the readers of Software Engineering and Computer Games are in. In order to use the Pop Framework to build a new game, you need to have some understanding of the Pop program's design structure and running behavior. This is also the situation that you're usually in when you start working for a big company. They have some large body of code in place, and you're supposed to start maintaining or extending it. UML diagrams are a perfect tool for getting started with the process.

A use case diagram can help you understand what the program is supposed to do. A component diagram lays out the interdependencies of the source-code files. An activity diagram shows the overall program flow. Class diagrams show you the interrelationships between the kinds of objects the program uses. And sequence diagrams can clarify the details of how the objects interact.

Use case diagrams

In a UML use case diagram, you represent your program as a big rectangle. Outside the program you put one or more stick-figures corresponding to the actors, that is, the people or other programs who might make requests to your program. Inside the program box you put ovals corresponding to use cases, which are things the actors might ask the program to do. You may also draw relationship lines from actors to use cases to indicate which kinds of actors get involved with which kinds of use case.

Even more than other UML diagrams, use case diagrams are exceedingly simple. The stick-figures are a low-tech bit of psychology to make you to feel more engaged with what is really little more than a list of requirements.

A use case diagram for the Pop Framework code might look like

Since Pop is both a program and a framework, its use case diagram mentions two kinds of actors: the users who play the Pop games and the programmers who use the Pop Framework to build new games.

Rather than going into a high level of detail about how we will display our graphics, we simply have the 'watch' use case to express the idea that the game should be pleasant to look at. The 'resize' case expresses the requirement that the game should be resolution independent. The 'adjust' use case expresses the fact that we require an ability to be able to do things like selecting game levels or resetting the game. The 'check progress' use case leads to a requirement that the game should display the current score, player health, and so on.

On the programmer side of things, we mention an 'extend' use case and a 'test' use case. Requirements coming out of the 'extend' case are that the code should have clear, easily extended classes, and that the various numerical parameters should be easy to find and easy to change. Requirements arising from the 'test' use case might be that the framework should have methods for randomizing parameters for so-called black-box testing, as well as an autorun mode in which the game 'plays by itself'.

Requirements gathering

A software requirement is a more or less detailed request for a certain kind of program. A requirement asks, 'Will you write a program to do such and such which appeals to so and so and runs on the following platforms?'

A software specification is a more or less detailed description of a program. A specification arises as an answer to the question posed by a requirement. A specification answers a requirement by saying, 'I can write a program with the following features, and its appearance and behavior will be something like this.'

As mentioned before, the key thing to realize is that – like so many aspects of software engineering – arriving at a final requirement and specification is an iterative process. This is the process we call requirements gathering.

It is essential to spend a good amount of time on requirements gathering before writing a single line of code!

Without enough requirements gathering you run the risk of spending a lot of energy writing a program that your customer doesn't want.

In order to discuss requirements gathering a bit more, let's think of a simple situation where you are the lead software engineer planning to create a program for some customer. The customer proposes a requirement, you propose a specification and show it to the customer, the customer alters the requirement, you alter the specification, and the process is continued until the customer has figured out what he or she really wants, and you have figured out an answer which the customer finds satisfactory. This is an example of requirements gathering.

What about the situation where you are developing a program on speculation, without any investors or corporate clients involved? Well, you really shouldn't try to develop a program for an imaginary customer that exists only in your head. You need to get out and talk to real people, to people other than yourself. If you're developing for the mass market, your 'customer' might be possible users. If you're well-funded, you might have a formal focus-group of users. If you're upgrading an existing product, your users might be your customer base. If you're pretty much on your own, your sample users might be whatever friends or family you can find who are interested in what you're doing.

In most situations there are project stakeholders other than customers. If you're starting a company, one of your stakeholders might be a venture capitalist. If you're an executive of a company, a key stakeholder might be an executive at another company that's contracting for you to write a specific program. If you're a low-level employee at a company, your most important stakeholder is your boss. If you're a student in a software projects class, your stakeholders are your professor, the other members of your team, and any sample users you can manage to talk to.

In each case one of the stakeholders proposes some more or less vague requirements and it's up to you to come up with a specification of a program such that (a) all the stakeholders agree that the specification satisfies the requirement and (b) you feel you can complete the program given the existing time, cost, and quality constraints.

Condition (b) means that in the requirements gathering phase you need to keep the Constraint Triangle well in mind. It's a mistake to 'gold-plate' the requirement and insist that you will be able to include a huge list of fancy features. Always remember the Constraint Triangle of cost, time, and quality. If there are some absolutely necessary fancy features which are bulking up the quality corner, you need to make sure you get allowances to the cost and/or time corners to compensate. If your customer, or your boss, or your marketing department, won't accept a realistic feature set, you should quietly start looking for a new customer or a new job. It's too stressful to work on a project that's doomed from the outset by unrealistic estimates.

The specification sketch

It's a bad idea to code without having any written plan at all. If you have a written plan, looking at it can keep you from going off on tangents that may not be crucial. And a short written plan makes a good starting point for discussions with others. So you really do need to write up a specification before starting to code.

What should a specification look like? We distinguish between two kinds of specifications: a short, casual one called a specification sketch, and a longer, cleaner, more formal one called a full specification.

An ideal full specification might consist of your class header files and a complete User's Guide for the program. Realistically, you usually aren't in a position to correctly write a full specification until your program's nearly done! That's why, to start with, we settle for a specification sketch. Better a sketch than nothing.

It's liberating to accept that it's unreasonable to want to write out a full specification before you write a line of code. In the real world, it helps to play with some code while you're thinking and to try a few things out so that you have some idea of what your possibilities are.

This is where the notion of a specification sketch comes in. If you feel like the only possible specification you can write up is a full specification, you're going to be tempted to not make any kind of specification at all and just start right in on coding. But if all you have to write up is a one or two page specification sketch, the process feels more like a helper than like an obstacle.

As your program evolves, your specification will gain more and more detail. Some aspects of the specification may not be worked out until you've written several builds of the code.

The specification sketch should describe four basic areas: (S1) the concept, (S2) the appearance, (S3) the controls and (S4) the behavior of the program.

  • (S1) Concept. The concept states what the program is about, and describes the unifying theme of the program.

  • (S2) Appearance. The appearance should be specified by a few drawings of what you expect the screens of your program to look like. For a very large project you might go so far as to make software mockups of sample screens, creating these either as paint program bitmaps or as outputs of a quick and dirty prototype program. But for a small project, a pencil drawing on a piece of paper may be enough.

  • (S3) Controls. The controls should be specified by saying how the keyboard, mouse, and more important menu controls will work.

  • (S4) Behavior. The description of the behavior should mention the main features of the program. It's often useful to step through how the program would respond to user actions in a typical use case scenario. Also describe how a typical user will get started with the program, and mention some expected tips for successfully using the program.

Suppose that your requirement is to write a computer game. A proposed specification sketch might be based on, for instance, the concept for a game resembling the PacMan game. (S1) The sketch's concept would also need to include an idea for a coherent graphical theme distinct from the graphics of PacMan. (S2) The sketch would include a drawing of the screen of your game. (S3) The controls are simply the arrow keys. (S4) Regarding the behavior, the sketch would have some details about how many enemies there will be, what the score for eating a power pellet will be, what the shape of the maze will be, and how the successive levels of the game might differ.

Of course the customer's response to such a specification might be, 'I want a new kind of computer game, not a clone of an existing game.' And then you'd need to find a way to make your PacMan specification more original, and continue on through the next cycle of requirements

The Constraint Triangle

If there's one single thing you should know about software engineering it's the Constraint Triangle

Cost is the measure of how many programmers are hired to be on your team. Time is the measure of how long you have to finish the project. Quality is the measure of how many features your software will include and of how extensively it will be tested.

Controlling time, cost, and quality are all important goals. You want to manage time so that your project will be ready by its deadline. You want to control development costs so that the project will be affordable and even profitable. And you want the quality of the software to be good enough to make the software attractive to users.

In a fantasy world, we'd like for our projects to be done instantly, to cost nothing, and to be of infinitely good quality. But in the real world, we have to compromise. The reality is that in order to change one of the time, cost or quality goals we need to provide some slack by adjusting one of the other goals.

  • You can decrease the time needed for your project, but to do so means increasing the cost by hiring more programmers and/or reducing the quality by eliminating features and perhaps cutting corners on the product testing.

  • You can reduce the cost of your project by using fewer programmers, but this means you'll need more time and/or to reduce the quality.

  • You can opt for a very high level of quality, but this means your project must take more time and/or cost more.

Any change to one goal must be compensated for by a change to one or both of the other goals.

If you let your customer (or your manager) arbitrarily specify all three corners of the Constraint Triangle, your project is doomed to fail. Any change to one corner must be balanced off by changes to the other corners.

The moral is that if your project is to be successful, you must be permitted to make a realistic assessment of cost, time, and quality, and you must be permitted to make the necessary adjustments to at least one of the goals. Unless you are allowed to realistically adjust at least one corner of the Constraint Triangle, your project will fail.

In the 1990s, NASA briefly adopted the slogan: 'Faster, cheaper, better.' This was followed by a series of unsuccessful projects – and then they abandoned the slogan. It's important to realize that, pushed to the limit, the 'Faster, cheaper, better' slogan is impossible to satisfy. It's as absurd a statement as 'I can fly' or 'I can turn rocks into gold.' There's a saying among software engineers that a correct statement of NASA's praiseworthy but impossible goal is this: 'Faster, cheaper, better: pick two out of three.'

At some point in your career you're likely to be saddled with a manager who thinks a rah-rah, can-do attitude is enough to get things done. Always speak up and protest if you hear anything like 'faster, cheaper, better.' Don't accept it if your manager suddenly decides to halve the cost, halve the time, or double the quality without making any compensatory changes to the other corners of the Constraint Triangle. If you let so foolish a plan stand, it will come back to haunt you. Mention the Constraint Triangle and draw a picture of it. Explain that it is a simple impossibility to arbitrarily specify all three corners.

Unless you are in a fairly powerful position, you usually don't have much control over the time and cost corners. In particular, if you're a student doing a team software project in a course, you aren't going to have any control over the time you have to do the project, and you aren't going to have much to say about how many programmers you get to have on your team. The only corner of the Constraint Triangle that you have control over is the quality corner.

The way to economize on the quality corner is not to say, 'Well, I'll write a program with lots of bugs and I won't fix them.' The idea is, rather, to say, 'we're going to strictly limit the number of features that our program will have.'

In limiting features, we need to avoid gold-plating, which is the mistake of accepting overly strong requirements for the program. In addition, we need to avoid feature creep, which is the tendency to keep adding cool new features as the program goes on.

Basics of software engineering

Before talking any further about your specific project, let's look at some basic software engineering issues. How should you organize your effort and your time? How do big programs get written?

In this chapter we lay out some of the basic software engineering principles and tools that you need to carry out the goal of the course, which is to produce a fairly large and complete computer game program based on an existing object-oriented framework.

Software Engineering and Computer Games focuses on showing you how to carry out one particular kind of software project. We are not going to give you a complete or advanced treatment of the whole field of software engineering here. Rather than attempting a broad-based survey, we are out to give you the tools to carry out one kind of task in depth. And while discussing this task we'll also show you some things about the practice of object-oriented software engineering.

Hopefully the lessons you learn here will give you a better insight into the more theoretical principles of software engineering when you encounter them in some other context. If this chapter gives you an appetite to learn more about software engineering, consult some of the books suggested in the Introduction.