New, more comprehensive guidelines. Compiled existing Empire
practice, rounded off with classical C usage, edited into a readable form. Work in progress.
This commit is contained in:
parent
3629f0fe1e
commit
1e6c4a7a5e
1 changed files with 421 additions and 1 deletions
422
doc/coding
422
doc/coding
|
@ -1,4 +1,424 @@
|
|||
Guidelines for writing Empire code
|
||||
Guidelines for Writing Empire Code
|
||||
|
||||
compiled and edited by Markus Armbruster
|
||||
|
||||
Preface
|
||||
-------
|
||||
|
||||
Empire is an old program, and the age shows in places. It took a lot
|
||||
of people a lot of effort to write it. Chances are that some of them
|
||||
were smarter than you and me. Treat the code with the respect it
|
||||
deserves.
|
||||
|
||||
These guidelines are just that: guidelines. Not law. There's no
|
||||
Empire Coding Police. People hacked on Empire code long before I knew
|
||||
it existed, and I hope they still will when I fade into Empire
|
||||
history. But there's substantial experience behind these guidelines,
|
||||
so don't dismiss them cavalierly.
|
||||
|
||||
Here's our goal: algorithmic clarity. All the rules and guidelines
|
||||
are subservient to it. If you'd have to obfuscate your code to
|
||||
satisfy the letter of some guideline, don't!
|
||||
|
||||
Mind, I said `clarity', not `self-realization' or `expression of your
|
||||
individuality'. Dave Pare wrote:
|
||||
|
||||
Be invisible. When you make a change, think not about marking your
|
||||
place in history, or about showing off how much nicer your two-space
|
||||
tabs are than those old, icky eight-space tabs that the stupid
|
||||
empire hackers of old used, but think instead about the aesthetics
|
||||
of the whole. The resulting lines of code should flow smoothly from
|
||||
the beginning of the procedure to the end. Empire is 60,000 lines
|
||||
of code. If you're the general case, you're only changing fifty
|
||||
lines, so be aware of that.
|
||||
|
||||
Some guidelines are more serious than others. When I use words like
|
||||
`is', `shall' or `must', I mean it. When I write `should', it's a
|
||||
recommendation.
|
||||
|
||||
Many guidelines concern style, i.e. they involve matters of taste.
|
||||
That's how it has to be. Uniformity of style is important for
|
||||
clarity.
|
||||
|
||||
These guidelines don't attempt to be exhaustive. More complete
|
||||
guidelines that are mostly compatible with Empire can be found at
|
||||
http://www.jetcafe.org/~jim/c-style.html
|
||||
|
||||
|
||||
Source tree organization
|
||||
------------------------
|
||||
|
||||
FIXME
|
||||
|
||||
|
||||
Code formatting
|
||||
---------------
|
||||
|
||||
In My Egotistical Opinion, most people's C programs should be
|
||||
indented six feet downward and covered with dirt."
|
||||
-- Blair P. Houghton
|
||||
|
||||
Over the years, enough Empire coders lacked the good taste to preserve
|
||||
the style of the original Empire code in their changes, and thus
|
||||
turned the source code into an unreadable mess. In 2003, we fed it to
|
||||
GNU indent.
|
||||
|
||||
We tried to restore things to the original style, mostly. There is
|
||||
one noteable change: basic indentation is now four spaces. Restoring
|
||||
the original eight spaces would have resulted in many more long lines,
|
||||
which would have to be broken by indent. Since indent isn't good at
|
||||
breaking lines tastefully, we reluctantly chose four instead.
|
||||
|
||||
FIXME mention src/scripts/intend-emp, even better: convert it into an
|
||||
indent profile and mention that.
|
||||
|
||||
If you use Emacs, `stroustrup' indentation style produces satisfactory
|
||||
results. The command `c-set-style', normally bound to `C-c .', lets
|
||||
you set the style for the current buffer. Set variable
|
||||
`c-default-style' to "stroustrup" to switch to this style for all
|
||||
buffers.
|
||||
|
||||
Tab character use
|
||||
|
||||
Whether you use tab characters or not doesn't really matter that much,
|
||||
but tab stops are every eight characters, period.
|
||||
|
||||
Indentation, placement of braces, function name
|
||||
|
||||
Basic indentation is four spaces. The opening brace goes on the same
|
||||
line as `if', `struct', etc. Put `else' and do loops' `while' one the
|
||||
same line as the closing brace. You are encouraged to leave out
|
||||
syntactically optional braces. Don't indent case labels.
|
||||
|
||||
Example:
|
||||
|
||||
if (is_fubar())
|
||||
despair();
|
||||
else {
|
||||
do {
|
||||
char ch = getchar();
|
||||
switch (ch) {
|
||||
case EOF:
|
||||
return;
|
||||
case '\n':
|
||||
break;
|
||||
default:
|
||||
grind_along(ch);
|
||||
}
|
||||
} while (happy());
|
||||
}
|
||||
|
||||
In a function definition, the return type, function name and the
|
||||
braces must all start on a new line, unindented, like this:
|
||||
|
||||
type
|
||||
name(arguments)
|
||||
{
|
||||
body
|
||||
}
|
||||
|
||||
This does not apply to function declarations.
|
||||
|
||||
Breaking long lines
|
||||
|
||||
Line length should not exceed 75 characters. Break such lines at a
|
||||
logical place, preferably before an operator with low precedence.
|
||||
Line up the continuation line with the innermost unclosed parenthesis,
|
||||
if there is one, else indent it four spaces.
|
||||
|
||||
Example:
|
||||
|
||||
FIXME
|
||||
|
||||
Bad example:
|
||||
|
||||
FIXME
|
||||
|
||||
Blank lines
|
||||
|
||||
Use blank lines to separate different things. Functions must be
|
||||
separated by a blank line. You are encouraged to insert a blank line
|
||||
between a block's declarations and statements.
|
||||
|
||||
Spaces
|
||||
|
||||
There is a space after `for', `if', and `while'. If `for' and `while'
|
||||
are followed by `;' (empty loop body) on the same line, there is a
|
||||
space before the `;'. There is no space between the function name and
|
||||
the `(', but there is a space after each `,'. There should be no
|
||||
space between type cast operator and operand. There is no extra space
|
||||
after '(' and before ')'.
|
||||
|
||||
Example:
|
||||
|
||||
for (p = s; *p; ++p) ;
|
||||
printf("%ld\n", (long)(p-s));
|
||||
|
||||
Comments
|
||||
|
||||
The function comment describing what a function does goes directly
|
||||
above the definition.
|
||||
|
||||
Comments to the right of code should start in column 32 if possible
|
||||
(counting from zero).
|
||||
|
||||
Comment lines should be indented exactly like the code the belong to.
|
||||
|
||||
You are encouraged to format multi-line comments like this:
|
||||
|
||||
/*
|
||||
* Please use complete sentences, with proper grammer,
|
||||
* capitalization and puntuation. Use two spaces between
|
||||
* sentences.
|
||||
*/
|
||||
|
||||
But please avoid elaborate boxes like this:
|
||||
|
||||
/***********************************************************
|
||||
* Such decorations are a waste of time, hard to edit, and *
|
||||
* ugly, too. *
|
||||
***********************************************************/
|
||||
|
||||
Conditional compilation
|
||||
|
||||
Unless the conditional code is very short, please comment it like
|
||||
this:
|
||||
|
||||
#ifdef FOO
|
||||
...
|
||||
#endif /* FOO */
|
||||
|
||||
#ifdef BAR
|
||||
...
|
||||
#else /* !BAR */
|
||||
...
|
||||
#endif /* !BAR */
|
||||
|
||||
|
||||
Style
|
||||
-----
|
||||
|
||||
Names
|
||||
|
||||
DoNotUseStudlyCaps! Stick to lower_case and use underscores. Upper
|
||||
case is for macros and enumeration constants.
|
||||
|
||||
File names should not differ in case only, since not all file systems
|
||||
distinguish case. Best stick to lower case. Avoid funny characters
|
||||
in file names. This includes space.
|
||||
|
||||
Preprocessor
|
||||
|
||||
Like many good things, the preprocessor is best used sparingly.
|
||||
Especially conditional compilation.
|
||||
|
||||
Do not use the preprocessor to `improve' the syntax of C!
|
||||
|
||||
Macro names should be ALL_CAPS to make them stand out. Otherwise,
|
||||
they can be mistaken for objects or functions. This is bad, because
|
||||
`interesting' behavior can hide inside macros, like not evaluating
|
||||
arguments, or changing them. Exception: if a function-like macro
|
||||
behaves exactly like a function, then you may use a lower case name.
|
||||
|
||||
Parameters that can take expression arguments must be parenthesized in
|
||||
the expansion. If the expansion is an expression, it should be
|
||||
parenthesized as well.
|
||||
|
||||
You are encouraged to use enumeration constants instead of macros when
|
||||
possible. The existing code doesn't, but it makes sense all the same.
|
||||
|
||||
Comments
|
||||
|
||||
Every file should have a file comment FIXME
|
||||
|
||||
Every function should have a function comment that describes what it
|
||||
does. FIXME elaborate. Writing such comments helps both future
|
||||
maintainers and yourself: if it's too hard to write a concise function
|
||||
comment, then your function is likely too complicated and could use a
|
||||
redesign.
|
||||
|
||||
The existing code has very little useful comments, and it'll likely
|
||||
take us years to fix it. Please don't make it harder than it already
|
||||
is.
|
||||
|
||||
Declarations
|
||||
|
||||
Do not declare system functions yourself; include the appropriate
|
||||
system header.
|
||||
|
||||
Use prototypes, not old-style function declarations.
|
||||
|
||||
To get the best use of C's type checking, each function or variable
|
||||
with external linkage should have exactly one declaration, in some
|
||||
header file, and that declaration must be in scope at the definition.
|
||||
No other declarations should exist. In particular, please include the
|
||||
appropriate header instead of just declaring stuff again. The code
|
||||
used to be full of this kind of junk, and it was quite some work to
|
||||
clean it up.
|
||||
|
||||
Forward declarations of static functions should all go in one place
|
||||
near the beginning of the file.
|
||||
|
||||
If you want a typedef name in addition to a structure or union tag,
|
||||
give it the same name, like this:
|
||||
|
||||
typedef struct foo foo;
|
||||
|
||||
Yes, this is incompatble with C++. Reducing the number of names for
|
||||
the same thing is more useful than compatibility to a programming
|
||||
language we don't use.
|
||||
|
||||
Please don't hide away pointers with typedefs, like this:
|
||||
|
||||
typedef struct foo *foop;
|
||||
|
||||
When I see `foo *', I *know* what it is. When I see `foop', I have to
|
||||
look it up.
|
||||
|
||||
Type casts
|
||||
|
||||
Casting pointers to and from `void *' clutters the code and serves no
|
||||
purpose (repeat after me: C is not C++). It is also unsafe, because
|
||||
the cast suppresses type warnings.
|
||||
|
||||
Casting function values to void clutters the code and serves little
|
||||
purpose (repeat after me: C is not Pascal).
|
||||
|
||||
Casting an argument to the type of the parameter clutters the code and
|
||||
serves no purpose (repeat after me: ISO C is not K&R C).
|
||||
|
||||
Factor out common code
|
||||
|
||||
Do not gratuitiously duplicate code! Ken Stevens said it well, and
|
||||
it's as relevant as ever:
|
||||
|
||||
Cut-and-Paste coding is by far the biggest problem that the current
|
||||
Empire suffers from. This is how cut-and-paste coding happens. Joe
|
||||
Shmuck decides that he wants to add a new function to the server.
|
||||
So he goes hunting through the server to find some already existing
|
||||
code which does something similar to what he wants to do. This is
|
||||
good. You should always write new code to imitate old code. What
|
||||
is bad is when Joe Shmuck decides to simply "copy" 200 lines of code
|
||||
from the old function into his new function, and then just change a
|
||||
couple of little things here and there to suit his needs. This
|
||||
method, known as Cut-and-Paste coding is the fastest and easiest way
|
||||
to code. However, it results in a server that is impossible to
|
||||
maintain. What Joe _should_ have done, is "move" the 200 lines of
|
||||
code into a new _third_ function which the first two both call.
|
||||
This is called writing a "general solution" to handle both cases.
|
||||
Most of my work in the Empire2 project consisted in cleaning up
|
||||
after a bunch of Joe Shmucks.
|
||||
|
||||
|
||||
Portability
|
||||
-----------
|
||||
|
||||
FIXME C89, POSIX
|
||||
|
||||
FIXME sizes, printf formats
|
||||
|
||||
FIXME reserved names
|
||||
|
||||
FIXME conditional compilation is a last resort
|
||||
|
||||
FIXME s_char
|
||||
|
||||
|
||||
Robustness
|
||||
----------
|
||||
|
||||
Error checking
|
||||
|
||||
Check error conditions meticulously. The existing code is bad enough
|
||||
at this, please don't make it worse.
|
||||
|
||||
FIXME what to do on error
|
||||
|
||||
Buffer overruns
|
||||
|
||||
FIXME
|
||||
|
||||
Thread safety
|
||||
|
||||
Empire uses non-preemptive threads: a thread runs until it yields the
|
||||
processor. This simplifies programming, but there are still traps to
|
||||
avoid. Empire threads may yield the processor whenever they do I/O or
|
||||
FIXME elaborate
|
||||
|
||||
Be careful with static storage. Read-only data is fine. More than
|
||||
one thread writing static data is problematic, but there are safe
|
||||
uses. For instance, a static scratch buffer that is never used across
|
||||
`yields' is safe.
|
||||
|
||||
Thread stacks cannot grow. If you use too much automatic storage, you
|
||||
can overrun the stack with disastrous consequences. FIXME elaborate
|
||||
|
||||
Yielding the processor invalidates *all* the game state you copied
|
||||
into variables. You have to reread and possibly recheck. See below.
|
||||
|
||||
Accessing game state
|
||||
|
||||
Game state consists of several sets of objects, e.g. sectors, ships,
|
||||
bmaps, ... Each set is fully held in memory and backed by a file in
|
||||
the data directory.
|
||||
|
||||
There are several ways to access an object in set FOO:
|
||||
|
||||
* You can get a pointer to the object (not a copy!) with getFOOp().
|
||||
|
||||
This is inherently dangerous, because it allows you to update the
|
||||
object in memory without updating the disk file. It's used mostly
|
||||
in update code, where it pays major performance dividends, but there
|
||||
are uses in other places. Whether they are wise is debatable.
|
||||
|
||||
Obviously, the object can change when you let another thread run.
|
||||
This is not an issue in update code, because it never yields.
|
||||
|
||||
* You can get a copy with getFOO() and write it back with putFOO().
|
||||
|
||||
Yielding the processor invalidates the copy. In particular, if you
|
||||
yield the processor between get and put, and another thread changes
|
||||
the game data, then put will clobber that change, possibly resulting
|
||||
in a corrupted game.
|
||||
|
||||
Therefore, you have to re-get after a yield, and repeat any checks
|
||||
you already made. If you can afford to bail out when something
|
||||
changed at all, use check_FOO_ok().
|
||||
|
||||
Putting updates both the object in memory and the disk file.
|
||||
|
||||
If you write a function that takes a pointer to an object, think
|
||||
twice before you put it, and if you do, mention it in the function
|
||||
comment.
|
||||
|
||||
* Bmaps have special access functions.
|
||||
|
||||
Each country has to bmaps: the working bmap and the true bmap.
|
||||
Unfortunately, the bmap code called the former `bmap' and the latter
|
||||
`map'.
|
||||
|
||||
You update bmaps with map_set(). This doesn't write through to the
|
||||
file; instead, it returns non-zero when the update changed anything.
|
||||
In that case, you must write bmaps to disk with writemap() before
|
||||
you yield. If you only updated the working bmap, then you can call
|
||||
writebmap() instead.
|
||||
|
||||
|
||||
CVS
|
||||
---
|
||||
|
||||
Commit related changes together, unrelated changes separately.
|
||||
|
||||
See chapter Change Logs in the GNU coding standards for guidelines on
|
||||
log messages. Try http://www.gnu.org/prep/standards_40.html#SEC40 or
|
||||
search the web for `gnu coding standards change logs'.
|
||||
|
||||
Don't change whitespace gratuitiously, say just because your editor
|
||||
screws up tabs. Such changes make it much harder to figure out who
|
||||
changed what and when.
|
||||
|
||||
|
||||
Historical guidelines, superseded by the above
|
||||
|
||||
|
||||
Remarks from Dave Pare:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue