Making Software Suck Less - ProcessesOnce upon a time, a prominent writer and programmer rose to declare "I want software that doesn't suck!" He then explained that certain successful free software projects have similar development features that contribute to software quality. Most of us aren't gifted with the organizational clarity of a Linus, or the brilliant non-orthogonal design of a Larry.
There's hope, though. Improving the ways in which we produce software can dramatically improve the software itself. Extreme Programming suggests that simple habits, acting in concert, produce extremely powerful results. By adapting these techniques to the unique world of free software, we can improve the quality of our programs. We start by restating some common truths about free software development.
Distributed DevelopmentOpen development, of course, means that anyone with the time and the inclination can look at the code. Developers and dabblers have the opportunity to modify it to their needs. This by no means guarantees that anyone will do so. Making source code available is no silver bullet. Many qualified eyes actively looking for bugs make bugs shallow.
To harness the power of additional developers, you must attract them to the project. A simple design and clear documentation reduce the amount of work needed to come to grips with the system. XP suggests an overall metaphor to describe the system as a whole and to provide good names for the components and subsystems.
Test FirstThe most powerful weapon in your toolbox is comprehensive unit testing. (Unit tests are automated, independent procedures that exercise the smallest possible pieces of functionality individually.) Extreme Programming recommends a test-first approach. Before adding a new feature, the programmer writes a test for the feature and runs all the unit tests. The new test fails, so he writes code to pass the test. When all tests pass, the feature can be checked in. Tests cover everything that could possibly break.
When a bug appears, the programmer writes tests to demonstrate it. After fixing the bug in the code, he runs all tests again. This not only proves that the fix corrects the defect, but it proves that the correction did not break any other features. Besides that, it can prompt the programmer to write additional tests he had not previously considered.
Test-first programming ensures that all features have unit tests. Coders receive immediate feedback -- it's almost like a game. It produces a different mindset, freeing the programmer to concentrate solely on the task at hand. Code becomes easier to write, in the same way that finding the correct piece for a jigsaw puzzle is easier with its neighbors in place. Finally, it gives programmers the confidence to refactor, modifying existing code, by identifying the effects of a change.
Consider providing your test suite with the project. Add a 'test' or 'check' target to the Makefile. Projects designed to run on multiple platforms and distributions can generate better bug reports by including test results. Make it easy for users to report failures.
Simple Solutions FirstAfter writing a test, write the simplest possible code that could pass it. Resist the tendency to make things more flexible than you need at the moment. Concentrate only on the task at hand, programming only what you need to pass the test. Don't sacrifice current elegance and simplicity for a feature months down the road.
Good tests free you to change things in the future by identifying the effects of a change. Simple code localizes changes, reducing interconnections. Besides that, your design will change. Adding a feature will take the same amount of time whether you do it now or in the future. Don't spend time and energy overgeneralizing for something six months away when no one will use it in the meantime. Reduce the need for costly and time-consuming rewrites by avoiding extraneous complexity."It's true that 'simple and tested code means less bugs'.... Good and clear interfaces reduce bugs. Avoiding complexity reduces bugs. Using your brain before you code something up reduces bugs."
Have a Plan, Code For Your UsersWrite a plan for the software. Describe each feature in a paragraph or less, with sufficient detail that people will know when it is complete. Arrange the stories by importance, then tackle them in that order. This allows you to identify the work that will provide the most value to the customer. (In a free software project, the lead developers may be the customers.)
Dividing the project into stories allows delegation, especially in free software projects. Some tasks require an experienced hacker, while others serve as a gentle introduction to the program. Writing stories also provides sane goals and a project roadmap. Choose a few stories for each release. This gives end users a clear view of project progress.
Continuous Design, RefactorWith tests in place, you have the freedom to refine your initial design. By using the simplest solution first, you avoid investing time in hard to follow and difficult to maintain code. Your initial design will change. Your plan will change. Expect this and allow it to happen.
When you see an opportunity to refactor, do it! Eliminate duplicate code. Relocate common features into a new object or a function. Reduce complexity and interconnections. Don't maintain the same functionality in multiple places. Use simple, well-defined, and consistent interfaces. This, along with your test suite, will allow you to overhaul individual pieces without having to track down holes in far-flung files."Premature optimization-including premature low-level design--is the root of all evil."
Release Often, Release Working FeaturesCommit to regular release dates. This shortens the feedback loop. Users who can count on regular, stable releases with valuable new features will be more likely to use the software. It also provides more frequent entry points for new developers.
Follow your plan to add a few important features to each release. Focus programmer time and effort towards a simple, reachable goal. Allow time for design, testing, documenting, and packaging. The more often you have to do this, the more likely you are to streamline these processes. Resist the urge to add features you cannot complete in time. Instead, break them up into smaller stories and implement the most important parts. As usual, the unit tests help to stabilize the codebase and keep it in a state of stability. After completing a release, take time to modify your stories, shuffling priorities as necessary. Then commit to the next release.