by Matt Slot
Soon enough, you learn that it's important to keep a backup of the work -- data loss happens, or you rip apart some code and just can't seem to put it back together. The easiest way to implement this safety net is just to copy the entire directory. Eventually the harddisk (or hopefully a remote server volume) becomes a haven for various snapshots.
Okay, that's fine for small, single-person projects but it's not really a practical solution for real world software development. Even adding just one developer to the mix means that every assumption has to be reexamined, every design decision debated, and even the formatting of the source code clearly spelled out.
What a pain!
Well, it's not really that bad. Enough people have already charted this territory so that there are many solutions and techniques to reduce conflicts. This month we'll discuss ways to improve collaborative work of closely knit and well organized programmers (for a typical commercial software product). Internet and "open software" development strategies will be deferred to an upcoming article.
Once the interface and feature set has been sketched out, it's time to plot out how large portions of the application will actually be written. For some tasks, the specifics are obvious and need little discussion; for others, it's prudent to seek the input of others before committing to a plan.
There are several key ingredients to this brainstorming process: several of your peers, a whiteboard, and a couple hours. Most of all, don't be afraid go back to the so called drawing board when something sticky interferes with your progress. Sometimes the best path is to throw away a portion of already working code to improve the code by an order of magnitude with a fresh start.
There are many other substantive issues that really need to be addressed early in development so that each contributor works from the same set of assumptions. (Note that I simply ignored the language issue, as in C vs C++. If you can't agree on that, you probably shouldn't be working together.)
As each piece is improved, the "lead programmer" oversees integration of changes. He adds and optimizes existing code at the architectural level, and may even reassign the other programmers as necessary to keep the whole project on track.
The best approach is use specific allocator and deallocator functions for any data (especially those with internal structure). The module which requests a block is responsible for passing it to the appropriate cleanup routine. And always, ALWAYS check for memory leaks.
First decide exactly which services the application will need, then try to find libraries which provide those services conveniently for as many target platforms as possible. If a library does not work on a certain system, then you will probably need to find something similar or write your own. In fact, it's often best to "abstract" how the service is provided by writing wrapper routines, presenting a consistent interface. Meanwhile the module consists of glue code or a full implementation.
Decide which modules can fail, which are "fault tolerant", and when a propogated error should be displayed to the user. I prefer to pass errors up from implementation functions to those of the user interface, so that an error while saving a document is intercepted and displayed at the document level (rather than at the file system). This makes it easier to explain which action failed and why ("Unable to save "Term Paper" because disk is full" instead of just "disk is full").
The biggest problems are tracking down bugs across revisions, keeping pace with changing interfaces, and simply keeping various modules synchronized and up to date. In fact, performing these tasks manually requires signficant programmer time that can be used elsewhere -- and the overhead grows exponentially with more project members.
The general solution to these problems is called "source code control" or "revision control." Basically, it's a formal and automated process for storing source code edits in a common repository and accessing them again. Everybody's work is stored in a single location, making it easy to search and synchronize "local" working copies with those on the "server."
By formalizing the process, incompatibilities tend to arise only at specific times instead of behind your back. One programmer can spend a month fleshing out a library, massaging the function declarations as necessary; another coder who depends on the library isn't plagued by problems with interim versions. Once the edits to the module are complete, the source can be "checked in" to the source database. Only when the second programmer is ready to "merge" his edits with the new version does he actually "check out" the new library.
Each developer is responsible for making sure his edits work with the rest of the source on the server before checking them in. Other developers can proceed with older, "working" code in a controlled manner until it's convenient to update. (Without such controls, debugging would suck because different modules might break while people edit them concurrently.)
There are other benefits to source code control as well: provides a revision by revision history of each source file (tremendously useful for finding new bugs), it lets you track several versions of the same code concurrently (new and maintenance versions), and it encourages developers to make changes in smaller and more managable chunks.
Careful use of revision control and file compare tools let each developer control not only what changes between edits, but also when these changes are made. There is still an element of cooperation required to work on a project with others, but enforcing reasonable guidelines helps smooth the overall process. In fact, developers who've used revision control on large projects often start using it on small or solo projects as well.
Overall, these techniques reduce the complexity of large scale projects to a manageable chaos. Anything which streamlines the collaboration process at the cost of a little overhead will greatly outweigh the costs of poorly integrated modules and disagreements between project members. Take the proactive approach and establish guidelines for each project, or even company-wide.
Matt Slot, Bitwise Operator