FortisMain Page | About | Help | FAQ | Special pages | Log in

Fortis Coding Standards

From Fortis

Contents

General

This normative document sets out best practises for Fortis development. As we move forward in developing Fortis, it is our goal not only to make a robust powerful application, but also to make it a pleasure to develop. This is why we have created this document, and ask that you adhere to these standards.

Fortis is a large heterogenous project that has been handled at various times by different development teams. You will notice a disparity in code style and quality throghout the various subsystems.

Fortis uses Win32 API for its base level system and GUI interface. While we have abstracted some of the Win32 API calls to generic modules, this is still very much a windows program. All non-GUI code should use an abstraction layer for its system interfaces where feasible. This has allowed us to develop several newer modules in UNIX, and then integrate them into the Fortis environment. We want everything that we develop to be as portable as possible. This means avoiding data types such as 'CString' and 'TCHAR'.

Where possible, we adhere to UNIX programming methodologies and practices. We advise some background reading about UNIX methodologies regarding good programming practices.

Indentation

Fortis code is best viewed with tabstops set to every eight (8) columns. (Note: in the visual studio, editor tabstops can be set in the menu options Tools|Options.)

It is important that tabs are ONLY used for the initial indentation of code. They must not be used to get alignment of variable names or comments. Use spaces for that alignment. This ensures that the code remains readable no matter what the editor view settings are. When a line needs to be continued with some additional leading space, then it should use a mixture of tabs and spaces to achieve this indentation. The number of tabs should match exactly the number of tabs used in the line above it, and use spaces from that point on to move the start to be aligned with a particular character of the source line it is split from.

When creating a macro with various levels of indentation, then spaces should be used for all indentation. In the case of a multiline macro, indentation of four (4) columns per level is preferred. All line continuation characters should be aligned with spaces in a column just beyond the longest line of the macro block.


Margins

Lines should not exceed the length of 80 characters.

Rationale Excessively long lines inhibit readability in some editors, and the ability to print cleanly. Even though modern terminals have no problem displaying lines of more than 80 characters, lines of this length usually are not necessary. When all code fits in 80 columns, then more code listings can be viewed simultaneously. It is particularly annoying when comments formatted to say 90 columns are word wrapped like at 80, giving a staggered effect. Please be mindful that this is a collaborative effort.

Function prototypes and declarations with long argument lists can be declared like this:

int disprove(
  struct options *options,
  struct theorem *thm,
  char *rationale,
  int nepoch,
  void (*callback)(void *param),
  void *param)
{
    /* function body goes here. note that because
       the declaration has been split across lines,
       this function's open brace is placed on its own
       line at column zero. */

    return 0;
}

Complicated conditions can be split up around the logical operators &&, ||. In this case indentation should bring the start of the next clause in line with the one it is continuing.


    if ((mode == 4 && conditioncount < 2) ||
        (mode == 5 && (conditioncount > 9 ||
                       conditioncount == -4))
    {
        /* Again, notice that since the line has been split,
           the open brace for the consequence block occupies its
           own line. An if with a condition complicated enough to
           be line split should always have braces around the
           body, even if it is a simple statement. */
        mode = 2;
    }

Sometimes a line just barely extends beyond column 79. In this case it is discretionary whether the line is to be split or some whitespace is to be cut out to make the line fit (if possible).

Trailing whitespace (any whitespace followed directly by a newline character) is forbidden. This is necessary to avoid sloppy and spurious line changes when committing changes to the source repository.

Comments

Fortis contains over one million lines of code. It is important that you comment your code well, especially if it is obscure.

Typically, a comment "summary" is placed at the top of a section of code, such as a procedure, class or group of procedures. This is used to describe the overall functionality of the said code. This is probably the most useful comment in complex applications because it helps give you the big picture, so you know what to expect from that component. Summary comments do not need to be signed, but may contain author attribution for the body of code it describes.

Additional line-based "annotative" comments can be helpful if the purpose of that line is not easily understood, or some other information needs to be known. Not everything needs comments. We like to keep comments useful and concise. If what you are commenting is obvious, then a comment is probably not warranted.

Please sign and date these annotative comments with your initials. This is useful in many situations, and also helps to identify when a comment may be out of sync with the code it describes.

An empty box at the beginning of a comment such as [ ] serves as a "todo" notice. Sometimes these are urgent TODOS, and sometimes they are aesthetic or wishlist items. Once the task has been performed we place an x in this box: [x].

Variables

Intervals

In programming it is frequently the case that we need to track intervals over a range. This is especially true in text editors. We use mathematical interval notation in the code. [a, b) Represents the range from a to b that includes a but not b. When this is an interval over integers it means {a, a+1, ..., b-2, b-1}. This is the empty interval whenever a==b. The notation obviously gives four types of interval to work with. Whenever possible the code should use [a, b); only in an unusual circumstance would we consider using an [a,b] or worse one of the other interval types. Adhering to this convention keeps our loops consistent, and also decreases debugging and development time because you never have to verify what the interval convention is. This also allows a compact representation of disjoint covering intervals as an array of integers.

A related preference in our code is to use the less than < predicate in preference to the others. When equality is also needed <= is appropriate. Sometimes it is advisable to replace a <= to an equivalent < expression. Use your best judgment to decide whether the algebra you are working over might reasonably be abstracted to having to replace your <'s with a predicate.

Classes

Fortis does not make heavy use of classes. We make use of concepts from object oriented programming where necessary. For instance, good module design includes a functional interface that hides the implementation details and data layout from client code. We don't feel there is any need for the syntactic sugar of invoking a method on an object. Instead we generally put that "object" as the first parameter to would-be member functions in its module.

More sophisticated OO concepts such as inheritance and interfaces are not often needed. When the underlying code actually merits these techniques we simulate them by constructing function tables using pointers.

Functions

The conventions are different for C functions than C++ methods. Try to avoid adding to the existing C++ code. When it is necessary, use conventions that match the surrounding code.

C functions should be named in all lowercase. Most functions can be organized into logical groups and placed into a single file. This is called a "module." Functions within a module should use a consistent naming scheme. mod_func is the suggested convention. In some cases the underscore between in the name is omitted, such as in our w32 module, and some of the unicode support routines.

One time module initialization is performed mod_init, finalization in mod_fini. Allocators should use the name mod_alloc when possible. Destuctors use mod_free. In-place initializers should use mod_stamp, while the in place finalizers use mod_clear. Besides these conventions the rest of the routines in a module are chosen as appropriate.

Conditionals

The consequent of an if statement must always appear on a separate line from the test. Even a simple clause such as if(x) y=0; must be separated onto two lines. In certain cases the alternative may be folded onto the same line as the else.

The following are both permissible:


    if (x) {
        y = 2 * x;
    } else y = 0;

    if (x)
        y = 0;


Iterations

We use the following standard index variables: i, j, k. If you have to go beyond k consider whether the loop you are writing could be factored to use a helper function to perform some subcomputations. If you really do need more index variables, q, r, w, y, z are good choices.

For each index variable there is usually a corresponding limit variable. The limit variable's name should be the index variable followed by the letter n. The one exception to this rule is the index variable i whose limit variable is simply n. Thus, the possible limit variables we use are: n, jn, kn, qn, rn, wn, yn, zn.

Here is an example which demonstrates good usage. Notice that a local variable n was declared even though it receives its value by simple assignment from a formal parameter. Also, notice that our test is i<n. Except in un-ordinary circumstances the condition of a for loop should always be index < limit. One exception to this is when a NUL terminator is used as a limit, in this case the condition would be arr[index] .

int frobfiddle(int (*fiddle)(), struct frob *frob, int count) {
    int i, n;
    
    n = count;
    for (i=0; i<n; ++i) {
        fiddle(frob[i]);
    }
}

File Names

The best practice here is to use filenames as UTF-8 encoded character data. That means char *fname in declarations. We have OS wrapper routines that convert these to the data the operating system expects. This is UTF-16 in the case of Win32. Be careful not to use fopen(), remove() or other "portable" filesystem API's because it is most likely that these APIs are not configured to interpret the file names using UTF8. (Consult common/filesystem.c for our wrapper details.)

That said, some of our existing code is in violation of this principle. Depending on the particular code this may or may not be a problem. Also, of course, code that runs server-side should freely use the portable routines.

The legacy classes UnicodeFileName, FortisFileName, and passing file name data in a UnicodeString class is always bad style, and should be fixed whenever it is encountered.

Internally it is best to use / as the path delimiter. Windows APIs support this natively for the most part. Some of our helper routines convert these silently to \ for you anyway.

Error Handling

Include Files

The project is organized into subsystem each comprising a single directory. Generally, each source file in a directory has a corresponding header file. Try to keep the header file self contained. It is acceptable to have prerequisites for a header, but:

Each directory also contains a file named catalog.h. This file is an exception to both of the preceding rules. Its purpose is to include every header within the directory to make the extern functions available to other subsystems. This file should be protected with a macro CATALOG_H_DIRECTORY where DIRECTORY is the name of the subsystem's directory. catalog.h should test the macro __cplusplus and include code appropriately for either C or C++ compilers. Finally catalog.h should include all of its subordinates prerequisites. Use a test file named catalog.c or catalog.cpp which contains only the line #include "catalog.h" to verify that the header takes care of all of its prerequisites.

The project is also set up to compile with precompiled headers. As the first non-comment line in a source file should be #include "directory_pch.h" (directory_pchpp.hpp, for C++.) Beneath that within an #if !defined USE_PCH block, include all the headers that are actually required for the particular source file.

Retrieved from "http://starfish.multiling.com/wiki/index.php/Fortis_Coding_Standards"

This page has been accessed 1,716 times. This page was last modified 16:54, 11 October 2010.


Find

Browse
Main Page
Recent changes
Random page
Help
Edit
View source
Editing help
This page
Discuss this page
Post a comment
Printable version
Context
Page history
What links here
Related changes
My pages
Log in / create account
Special pages
New pages
File list
Statistics
Bug reports
More...