Style Guidelines

Even if you follow the approach I've outlined above, it is still as easy to write unreadable and undebuggable code in C++ as it is in C, and perhaps easier, given the more powerful features the language provides. For the Nachos project, and in general, we suggest you adhere to the following guidelines (and tell us if you catch us breaking them):

  1. Words in a name are separated SmallTalk-style (i.e., capital letters at the start of each new word). All class names and member function names begin with a capital letter, except for member functions of the form getSomething() and setSomething(), where Something is a data element of the class (i.e., accessor functions). Note that you would want to provide such functions only when the data should be visible to the outside world, but you want to force all accesses to go through one function. This is often a good idea, since you might at some later time decide to compute the data instead of storing it, for example.

  2. All global functions should be capitalized, except for main and library functions, which are kept lower-case for historical reasons.

  3. Minimize the use of global variables. If you find yourself using a lot of them, try and group some together in a class in a natural way or pass them as arguments to the functions that need them if you can.

  4. Minimize the use of global functions (as opposed to member functions). If you write a function that operates on some object, consider making it a member function of that object.

  5. For every class or set of related classes, create a separate .h file and .cc file. The .h file acts as the interface to the class, and the .cc file acts as the implementation (a given .cc file should include it's respective .h file). If using a particular .h file requires another .h file to be included (e.g., synch.h needs class definitions from thread.h) you should include the dependency in the .h file, so that the user of your class doesn't have to track down all the dependencies himself. To protect against multiple inclusion, bracket each .h file with something like:
    #ifndef STACK_H
    #define STACK_H
    
    class Stack { ... };
    
    #endif
    
    Sometimes this will not be enough, and you will have a circular dependency. For example, you might have a .h file that uses a definition from one .h file, but also defines something needed by that .h file. In this case, you will have to do something ad-hoc. One thing to realize is that you don't always have to completely define a class before it is used. If you only use a pointer to class Stack and do not access any member functions or data from the class, you can write, in lieu of including stack.h:
    class Stack;
    
    This will tell the compiler all it needs to know to deal with the pointer. In a few cases this won't work, and you will have to move stuff around or alter your definitions.

  6. Use ASSERT statements liberally to check that your program is behaving properly. An assertion is a condition that if FALSE signifies that there is a bug in the program; ASSERT tests an expression and aborts if the condition is false. We used ASSERT above in Stack::Push() to check that the stack wasn't full. The idea is to catch errors as early as possible, when they are easier to locate, instead of waiting until there is a user-visible symptom of the error (such as a segmentation fault, after memory has been trashed by a rogue pointer).

    Assertions are particularly useful at the beginnings and ends of procedures, to check that the procedure was called with the right arguments, and that the procedure did what it is supposed to. For example, at the beginning of List::Insert, you could assert that the item being inserted isn't already on the list, and at the end of the procedure, you could assert that the item is now on the list.

    If speed is a concern, ASSERTs can be defined to make the check in the debug version of your program, and to be a no-op in the production version. But many people run with ASSERTs enabled even in production.

  7. Write a module test for every module in your program. Many programmers have the notion that testing code means running the entire program on some sample input; if it doesn't crash, that means it's working, right? Wrong. You have no way of knowing how much code was exercised for the test. Let me urge you to be methodical about testing. Before you put a new module into a bigger system, make sure the module works as advertised by testing it standalone. If you do this for every module, then when you put the modules together, instead of hoping that everything will work, you will know it will work.

    Perhaps more importantly, module tests provide an opportunity to find as many bugs as possible in a localized context. Which is easier: finding a bug in a 100 line program, or in a 10000 line program?