This page contains my notes and opinions on good software development
practices (that I try to live up to). Comments welcome to
jbasney@ncsa.uiuc.edu.
General Principles
- Keep it simple. Keep your code small.
Small code is easier to secure and maintain.
- Carefully consider using existing solutions
(open source libraries and open standards)
before creating your own.
Using well established standards and implementations is particularly
important for cryptographic algorithms and protocols.
Beware of tying your work to someone else's poor quality
and/or poorly supported solution.
Sometimes a general-purpose solution will be more difficult to support
and maintain than something you develop specifically for your needs.
- Always consider the security implications of the code you write.
Beware of buffer overflows.
Beware of passing unclean ('tainted') input data to
external programs, especially the system shell (avoid popen()).
Follow the least privilege principle.
Carefully audit access control functions.
Use source code security scanners (for example,
flawfinder,
splint,
ITS4,
RATS, and
pscan
).
- Write documentation for end-users, system administrators,
developers, and the development/support/maintenance team.
Your new feature doesn't exist until it's documented.
This includes all public APIs.
Use DocBook,
Javadoc,
and Doxygen.
- Design public APIs for cross-version compatibility.
Don't change the types of your interfaces between releases.
Give developers time to migrate to new APIs by following
good deprecation practices.
Provide methods for checking version compatibility at runtime.
- Write portable C code. Follow ISO C89.
Pay attention to the CONFORMING TO section of Linux man pages.
Use POSIX functions where available.
Refer to the The
Single Unix Specification for definitions of portable Unix
functions.
Don't use C++-style comments in C code.
All C declarations must be at the start of a block.
(ISO C89 forbids mixed declarations and code.)
Use splint to check your code.
Fix compiler warnings.
- Develop reliable software. Handle all errors.
Write thread-safe code.
Avoid race
conditions on shared resources.
Synchronize access to shared variables.
Use read/write locks on shared files.
- Good error messages and debug logs make software easier to support.
When something goes wrong, display an error message that users will
understand and will tell them how to solve the problem. Include
copious debugging information that can be optionally enabled by users
when reporting problems.
- Try to leave things in a better state than you found them.
Simplify a confusing function.
Make an error message or section of documentation clearer.
Any review of existing code or documentation is an opportunity for
improvements.
- Every software release must be clearly identified with a unique
version number corresponding to a tag in the version control system.
Make it easy for users to find out the version number of their
installed software with a --version option or an "About"
dialog box.
Include version numbers in network protocols, so clients and servers
can report each other's version in debug logs.
Include optional build-time configurations in version strings.
For example,
"OpenSSH_3.9p1 NCSA_GSSAPI_3.5 GSI, OpenSSL 0.9.7d 17 Mar 2004".
Publish a list of revisions for each release.
- Test your software.
Do automated nightly builds and tests.
Write JUnit tests.
- Respect intellectual property rights.
This is very important.
Before redistributing any software,
verify that the software's license allows redistribution.
Explicitly acknowledge any software you redistribute
in README files, public documentation, and in the source code.
Many software licenses have specific requirements for acknowledgment.
Include copies of the licenses of any software you are redistributing.
Clearly indicate any modifications you've made to software you
redistribute.
Submit patches back to the software owners.
- Follow an open development process.
Use Bugzilla
or Trac
or Roundup
or Wiki pages or something else
to publicly track development of new features and bug fixes.
Write a blog! Publish an RSS feed!
- "Premature optimization is the root of all evil (or at least most
of it) in programming." - Donald Knuth
Java
Debugging
- Use gdb and
jdb.
- Use ngrep to view
network protocol traffic.
For example, sudo ngrep -d lo0 -q -W byline port 1118.
- Ethereal can also be very
helpful for network debugging.
- For memory allocation debugging in C, try
valgrind.
For example, valgrind --leak-check=yes ./a.out > valgrind.out 2>&1.
- For performance tuning in Java, try
YourKit.
Automated Builds
Community Participation
- Have a question?
First, read
How To Ask Questions The Smart Way.
(Hint: Always ask Google first.)
When you receive a helpful answer to a question on a mailing list,
consider staying subscribed to the mailing list and answering some
questions yourself once you become more familiar with the software.
- Find a bug? Report it!
First, read
How to
Report Bugs Effectively.
Even better, if it's open source software, try fixing the bug and
submitting a patch to the maintainers.
WWW
CVS
- To learn about CVS, run info cvs and/or read
The CVS Book and/or
The CVS Manual.
Follow the procedures they recommend.
- Include descriptive comments on all CVS commits.
Describe the bug you fixed or the feature you added.
Test and commit often. Fix one bug or add one feature per commit.
That makes it easier to undo if necessary and
also helps the merge process.
Commit one file at a time unless the change to multiple files can be
described clearly in a single comment.
Include references to related bug reports in
Bugzilla or elsewhere.
Always run 'cvs diff' first to verify the changes you're about to commit.
Don't commit whitespace changes, because they make it more difficult
to tell real changes from reformatting.
- Tags are cheap!
Create a tag with 'cvs tag' or 'cvs rtag'.
All software releases must be made from a CVS tag.
Create tags before and after large commits.
Tags should always be applied to all files in the module so a full
module can be checked out using the tag at a later time.
- When developing a new feature, create a branch and commit to the
branch as you go. When the feature is stable, merge to the trunk.
Tag the trunk both pre and post merge.
Merge with care. Make sure you're only merging in your changes and
you're not undoing other changes made on the trunk since you branched.
- The trunk should always be stable and ready to release.
Bug fixes should be checked in directly to the trunk.
Releases are made from the trunk.
All releases are tagged with a unique version number.
- The best way to create and maintain a patch to external sources is
with a CVS vendor branch.
Refer to the "Tracking third-party sources"
chapter of the The CVS Manual.
See the patch(1) man page (especially the "NOTES FOR PATCH SENDERS"
section) for more information about creating and applying patches.
Avoid including whitespace changes or other superfluous changes in
your patches so as not to distract the reader/reviewer from your
important modifications.
- Authenticated CVS access to cvs.ncsa.uiuc.edu is via Kerberized rsh with
an NCSA.EDU Kerberos ticket or via ssh. By default CVS will use
rsh from your $PATH. Set $CVS_RSH if you want to use ssh or a
different rsh.
Anonymous access is also available via
':pserver:anonymous@cvs.ncsa.uiuc.edu'.
- Consider using
Subversion instead of CVS.
NCSA Infrastructure
- See
NCSA's
security documentation. Leverage the existing NCSA security
infrastructure for your work. NCSA staff, including graduate research
assistants, can get the Kerberos credentials they need from the NCSA KDC.
Outside collaborators can also get NCSA Kerberos credentials.
- NCSA runs a Certificate Authority. See
this page
for details.
You can also get test certificates from the
Globus Certificate Service.
- Use NCSA's
Bugzilla and
CVS servers.
Email help@ncsa.uiuc.edu for access.
Resources
Last modified
.