
                         GALACTICOMM CODING PRINCIPLES
                         -----------------------------
                                                              28 Jan 1994

1. Why Coding Principles?

     As a Galacticomm programmer, the integrity of the Galacticomm product
line is in your hands.  Thousands and thousands of customers will be
impacted by your actions.  It's very important that you keep that in mind
throughout the development process.

     Knowing every aspect of the C programming language and the Borland C++
compiler is just the beginning.  In order to succeed as a Galacticomm
programmer, you must also be able to write quality, bug-free code.  That's
the hard part.  This document is an extension of the coding standards
document.  The coding standards focus on syntax.  These principles focus
more on philosophy and method.

1.1 Code Categories

     Every line of code you write should fall into one of three categories:

     Ready-to-ship.  This should be (by far) the largest category.  This
         category of code is written right the first time, with every
         intention of its shipping with a final product.

     Debug.  This should be (by far) the smallest category.  This
         category of code is not meant to be included in a final product.
         Debug code is most often used to track down a problem in
         existing code.  Each and every line of this code should contain
         the word "debug" in it (usually in a comment).  No other code
         should contain the word "debug" in any context (literal, symbol,
         comment).  That way, ready-to-ship code can be verified to be
         100% free of debug code.

     Test.  This category of code applies to code written in order to
         figure out something which you don't fully understand.  Test
         code is a good way to learn new technology, test theories,
         figure out a function that's not well documented, etc.  This
         code should not be written in the same source files as
         ready-to-ship or debug code.  Put test code in TEST????.C files,
         and run it separately.  Once you figure something out, write the
         code for real in your regular source files.  Test code is
         generally not usable as ready-to-ship code.  (Ready-to-ship
         code is written right the first time, and is not a result of
         trial-and-error.)

     The rest of this document will deal with the strict principles of
making ready-to-ship code.  When writing debug or test code, you're released
from these rules on your own recognizance.

2. Software Design Strategies

     Think about the user interface first.  Imagine what kind of life you're
creating for the people using your software.

     Get a thorough picture of all the ins and outs of your program before
you code any of it.  Whether the picture's in your mind, on paper, on a
white board, or some written specifications...  Whatever medium works for
you, it'll be a better place to spin and solidify your ideas than C code.

     Don't forget to get input from others.  Bounce your ideas off of the
folks in tech support, salespeople, or other programmers.  Solicit ideas
from customers, or anyone else who might have something constructive to
contribute.  It's much easier to rethink a bad idea than to recode a bad
program.

     Focus on work that you'll still be using at the end of your development
effort, instead of work you know you'll have to throw away and redo.

     On the other hand, don't hesitate to throw away bad code and redesign it!
If you find yourself getting frustrated and confused while coding, it's most
likely time to stop and rethink your strategies and methods, instead of
pressing on even faster into the quicksand.  Recasting code from scratch after
a grueling development spurt can be immensely satisfying -- you're using your
hard-won experience to craft a much smarter solution.

     Develop software that will be useful for a long time.  Five years is
good.  Six months is bad.

     Develop software that will be useful in a broad array of contexts.

2.1 Simplicity

     One of the greatest coding challenges is simplicity in design.  First
you must clearly understand the task at hand.  Then progressively decompose
the problem.  Map the key data structures and file layouts.  Prototype the
primary functions with a few words describing their operation.  Anything
that looks incredibly hairy is probably poorly designed.

2.2 "Clever" Code

     Many bugs (and most maintenance problems) occur in "clever" code.
Clever code is code that utilizes interesting properties of the system to
gain some advantage.  For example, one programmer wrote an assembler that
relied heavily on certain binary relationships of the machine's opcodes and
operands in the assembler's data structures.  Of course, when an enhanced
version of the processor was announced, the assembler had to be rewritten.
Don't make this mistake.

2.3 Inference

     Inference inhibits the ability to maintain a system.  It is one of the
primary reasons large systems must be scrapped and rewritten.  Inference is
the implication of certain attributes based on other attributes.  For
example, if the processor type is set to be a 486, you might infer that
hardware floating point is available.  When someone else adds the 586
processor type, unaware of your inference, your code reverts to using
software floating point!  Avoid inferences and be explicit wherever
possible.

2.4 Recyclable Software Development

     Bill Gates once said something like there's only one trick for
increasing software productivity:  make reusable software.  Software
technology should, as much as practical, be embodied in cohesive lumps with
general-purpose and well-defined interfaces.  Huge chunks of technology
should be as portable as possible, with interceptable I/O pipes,
well-behaved uses of memory and continuous computer time, and avoiding all
DOS dependencies.  The interface must be documented somewhere so we all can
use it.

     It may be appropriate to wrap complex core technology in "glue" source
code for convenient use with specific machines or applications.  For
example, the code in FTF*.C handles general-purpose file transfers, while
the code in FILEXFER.C makes it all usable on the BBS.  FSD.C supplies
general-purpose full-screen data entry, FSDBBS.C makes it usable on the BBS,
FSDDOS.C makes it usable in a conventional DOS program.

     It's not always practical to package or layer new software technology.
But, always be on the lookout for opportunities to not only solve your
immediate problem, but also help solve others in the future.

2.5 Developer Accessible Tools

     Almost all major new modules should contribute some kind of developer
accessible tools.  The File Library eventually contributed file transfer.
Electronic Mail should go hand-in-hand with a developer-usable/expandable
messaging interface.  A new Shopping Mall should allow expandable methods for
payment, shipment, invoicing, product description/display, inventory, etc.
Data search should have text file and other search algorithms usable in other
contexts.  A database Add-on should have hooks for new data types, search/scan
methods, data entry methods, import/export.

     Separating tools from the user interface is nothing radical -- it's the
seed idea of client/server, of course -- but for us it has further
implications.  When we release a product with both developer-accessible
tools and user-accessible services, we create a new market for TPD's to
write improved user-accessible services based on our developer-
accessible tools.  Durand's picture database is one example:  Sysops have to
buy The Major Database if they want Durand's product -- and this came about
in spite of The Major Database not having any "official" developer-
accessible tools!

     When developing new technology for the BBS, we should seek to package
the core technology inside a general-purpose programmer interface, so that
developers (especially at Galacticomm) can use it in other projects.
Ideally, this package would be general-purpose enough so it could even be
used outside an online environment.  This can make the difference between
code with a 2-year lifetime and code with a 5-year lifetime.  Imagine:  a
few weeks extra work leading to a few years extra usefulness.

2.6 Challenging the Institutions

     Know when the time has come to make a change to the packaged,
institutional technology that Galacticomm has amassed.  If you're finding
yourself going to great lengths to get around a deficiency in a XXGCOMM.LIB
library routine, it may well be a good time to fix the routine, or write a
variant yourself.

     Here are a few good reasons to change the institutional technology:

   o   You see a way to make it more useful in more applications.
   o   A work-around is much more work.

     These are good reasons NOT to change the institutional technology:

   o   There's any chance your changes will cause problems for existing
       applications.
   o   You'll make existing applications more than negligibly slower, or
       more of a memory hog.

3. Reliability -- Coding Carefully

     The secret to success in programming is coding carefully.  Review each
line of code as it's written.  Review each routine as it's completed.  When
you review your code, don't have a bias against rewriting it.  If, in
reviewing a routine you've just finished, you notice a better way to do it,
you should feel a moral obligation to rewrite it.

     Strive to write code that compiles perfectly.  Be surprised if you get
compiler errors or warnings.  Nobody's perfect, but compiler errors or
warnings mean that you not only coded something improperly, but you missed
it in your review as well.  This is something to avoid.

     Strive to write code that runs perfectly.  Be confident in your code.
Be sure there aren't any bugs.  In testing, seek to break your code -- be
surprised and concerned if you can.  Confidence in your code is a wonderful
and necessary thing.  Bugs eat away at that very confidence.  When you find
a bug, don't just fix it.  Figure out why you missed it.  Immediately look
for places where you may have made the same mistake.  Restore your
confidence by being sure that the bug was an isolated incident, not to be
repeated.

     Again, nobody's perfect.  Software products will always have some bugs.
But, the number of bugs depends greatly on the extent to which the given
programmer involved is bothered by them.  Be bothered by bugs.

3.1 Walk-Throughs

     If you find yourself thinking of your code as a mysterious tangle of
machinery that "probably works somehow", you're in for trouble.  Know thy
code!  This is where walk-throughs can help.

     When you've finished the first draft of your code, put the project on
idle for at least an hour, preferably a day, then walk through your code
from top to bottom.  Don't forget your "code buddy".  Find the best code
buddy you can, and explain to him how everything works.  A good code buddy
will be skeptical and thorough.  He won't make assumptions and he'll verify
every line of code with you.

     Each time you find a bug, you and your code buddy should increase your
already-high skepticism.  Check neighboring or connected code for more bugs.
Try to think of any similar mistakes you might have made throughout your
algorithm.

     As you fix a bug, reflect smugly on how hard it would have been to find
that bug by the throw-it-against-the-wall-and-see-if-it-sticks approach.
Renounce the "code, crash, and cry" method one more time.

     Many times I've groaned before doing a code review.  "I have to get ON
with the project" I say.  I've got all the features coded.  I've fixed all
the compiler errors.  There's a great temptation to just "run it and see if
it works".  But then I make a bet with myself.  I bet that the code review
won't lead to any changes, at least no changes in function.  I don't believe
I've ever won this bet.  There's always been something in never-run code
that needs repair.  And it's always easier to do during the review.

     The hard way really is the run-and-pray method.

     After the code review, I get very proud of my code.  Sometimes after a
coding spree I'm in a fog, with fragments of code whirling around my head
and invading my thoughts deep in the night...  I seem to have all this
unfinished business and I can't quite remember what it was.  No fun.

     After a code review I have lots of confidence in my code.  It seems so
logical and well connected.  Boo-boos leapt out and I nailed them.  I can
sleep at night.

     And best of all, the stuff works.  Maybe not the first time, but
remarkably soon thereafter.

     I've found that code walk-throughs tend to catch obscure hard-to-test-
for bugs.  And rigorous testing catches those obvious "if it'd been a snake
it woulda bit me" bugs.  A good match.

     Anybody who doesn't appreciate the astronomically high cost of bugs has
never worked in a company with "free unlimited technical support within
reason".

     A 2-person walk-through also gives the team one more member who
understands your program.  Go ahead and beam with pride as your code buddy
discovers the elegance of your solution.

     Having said all that, you may want to become your own best code buddy.

3.2 Test Everything

     No matter how small a change, no matter how confident you are in your
code, test everything you do as you do it.  Alpha and Beta test periods test
only a fraction of the code.  Every possible pathway through the code must
be tested at least once.  This can only happen if the programmer tests every
new code pathway as part of the development process.

3.3 Ruthlessly Hunt Down Bugs

     Try to fix all bugs as discovered.  If you find a bug that you can't
explain -- TAKE CAREFUL NOTE.  If you just hope it goes away, or think "I'll
figure that one out later", you might just find yourself trying to debug
released code over the phone, years, and oceans away.  Try to account for
everything your program does.  If you see weird effects, figure out why.

     Those bugs that every programmer has experienced where cause and effect
is impossible or meaningless to trace are the ones to watch closest (often
called "memory corruption bugs").  When you have no idea how to reproduce
them, consider every fix from that point on and think carefully if it could
possibly have eradicated the causes of the weird effects.  Even if you did
fix something that could have had undefined consequences, you may not have
gotten the problem.  If you can rule out each fix, you definitely haven't
fixed it.  Then you've got a problem waiting to happen.  A hacker-mystery
bug with your name on it, that may show up somewhere in your lifetime.

3.4 Bias for Review

     When something goes wrong, don't be bashful about looking at the code
first.  Avoid the temptation to run a misbehaving program over and over,
getting to know the defect intimately.  Get to know your code.  Take joy in
any excuse to review your own code.  If you get discouraged about having to
wade through your own code, it's probably time to rethink and recode.

     Reviewing code after not looking at it for a while can often times be
the best thing for the health of your code.  Be proud of code that:

   o   You understand thoroughly
   o   Appears clean
   o   Reads smoothly
   o   Is robust and free of defects
   o   Runs in all kinds of weather

3.5 Before PVCS

     Take one last look at any new code before you check it into PVCS.  Just
page through it, making sure nothing was missed.  (Make sure there's no
"debug" code!)

4. Tips from the Tradesmen

4.1 Search and Replace with Care

     Don't take short-cuts.  Things like global search and replace
operations should be done with care, verifying each change.  Never change a
line of code without reviewing it personally.  (In KEDIT, use the "sch"
command, not "ch".)

4.2 Common Nasty Bugs in C Programs

     Be on the lookout for:

   o   De-referencing an uninitialized, NULL, or nonexistent pointer
   o   Subscripting off the bounds of an array
   o   Divide by 0 -- whenever coding a divide, you should make absolutely
       sure that the denominator can never be zero
   o   Forgetting to put a break at the end of a case in a switch
   o   Using memory beyond what you've allocated
   o   Using allocated memory after you've freed it

4.3 Global Data Structures

     Global data should be used as sparingly as possible.  Try to keep
global data references limited to a small set of functions.  The idea is to
isolate function interdependence to the highest degree possible.  This is
referred to as "data hiding."  Use API functions whenever possible to
isolate the data structures of a module from other modules.  References to
other modules' globals is the glue that holds spaghetti code together!

4.4 Top-down vs. Bottom-up

     Top-down development is good, other things being equal, but so is
bottom-up utility coding for functions you know you'll need.  This may get
your creative juices flowing and make the problem universe smaller.

4.5 State Machines

     When your code needs to keep track of a series of operations, state
machines usually work out best.  Using individual progress flags often leads
to confusing code with strange relationships between flags.

5. Maintaining Code

     With many megabytes of existing source code in PVCS, the ability
to maintain code is a very important part of your job as a Galacticomm
programmer.  There are two basic rules to follow:

   o   make code better, not worse
   o   don't break anything!

5.1 Understand Everything

     It's absolutely vital that you understand everything about a given
piece of code before you even think about modifying it.  Don't jump right
in.  Spend time researching the code.  Send feelers out in all directions.
Make sure you understand all possible implications.

5.1.1 Investigate Variables

     Investigate all the variables used by code you're planning to modify.
Where are they declared?  Where are they initialized?  How are they used by
other code?  Do a GREP for variables you'll be dealing with.  Make sure you
understand everything they're used for.  Maybe they have some dual purpose
that you need to be aware of.  Maybe they're exported, meaning TPD's could
be relying on certain things about them.

5.1.2 Investigate Functions

     Investigate all the functions you're planning to modify.  Never change
anything until you understand all the current uses of a function.  If a
function is exported, TPD's may be using it.  Don't just worry about adding
new return values or parameters.  Also worry about things like changing the
current Btrieve pointer or message file pointer without restoring it.

5.2 Avoid "Kludges"

     It's often tempting to quickly patch little fixes into code when the need
arises.  These quick little patches don't often integrate well.  These are
kludges.  Avoid them whenever you can.  They accumulate over time (you'd be
surprised how quickly), transforming code that was once clean into code that
is full of bugs and impossible to maintain.

     Be sure that you solve the original problem, and not just remove your
immediate source of grief.  Don't just cover up the crime, find the criminals
and prosecute them to the fullest extent.  Find their boss, and his bosses,
and send 'em all packing.

     On the other hand, in order to correct a minor bug two days before
shipping, you don't want to modify two thousand lines of code.  Make code
changes in the best possible fashion under the circumstances.  In most cases,
this will mean making the change the "right" way, where the code remains clean
(hopefully cleaner) and maintainable.

5.3 Follow the Unwritten Standards

     All creative efforts involve, to some extent, infusing the machinery
with the spirit and vision of the developer.  There's a good reason for
this.  Symmetry and consistency are good things, other things being equal.
They help all developers understand the code and verify its reliability.

     When you're changing code that someone else originated, the underlying
patterns may not be apparent at first.  Take a little time and dig deeper.

     The better your changes are aligned with the original programmer's
intentions, the more those changes will increase the power of the software
without decreasing its flexibility and reliability.

7.  Getting the Job Done

7.1 99% Done

     Most programmers suffer from the 99% done syndrome.  Such programmers
may be 99% done with a project after one month, but not be 100% done for
another six months.  This can be avoided by simply doing things right the
first time.  Don't say to yourself "I'll get back to that later".  Get back
to it now.  This applies to most things, such as:

   o   MSG file text
   o   help messages
   o   rewriting of unclean code

     It's extremely tempting to put things off until later.  Especially when
you'd rather be getting the exciting aspects of a project over with first.
Just don't do it.  Other undesirable results of putting things off:

   o   unclean code
   o   bugs in last-minute code changes
   o   missing help messages

7.2 Optimizing for Development Time

     Think again before you choose to optimize your coding for a shorter
development time.  More often than not, optimizing for development time
leads to a much longer development time, not shorter!  Speed-coding leads
to lots of unclean, bug-ridden code.  Testing will bring out bugs.  Sections
will be rewritten in haste.  More bugs will result.  Even after the code
finally ships, it's difficult to maintain, and just winds up having to be
rewritten some day.

8. Applying These Principles

     There's a lot of ground between agreeing with these principles at an
intellectual level and following these principles when you're all alone,
rushing to meet a deadline.  Don't let your guard down.  Always follow these
general principles (or a comparable set), or else you probably won't be
writing quality, bug-free code.

