Skip to content

Style notes for developers

Nicolas Tessore edited this page Mar 26, 2015 · 1 revision

Most astronomers/astrophysicists have not worked on a large program in collaboration with other developers. Following a few rules and, most of all, keeping the philosophy of those rules in mind can make this much easier and more productive for everyone. Here are a few points that I have found it important to follow over the years.

All rules have exceptions, but you should understand why the rule is important and why you are violating it before doing so.

The User

When programing you should always keep in mind the "user". The user is not just an end user that wants to use a final version of the program to calculate something and doesn't care how it works internally. The user is also your collaborator that is using, testing or modifying your code. The user might be doing this years in the future and you will never meet this person. The user is also you. It is you now and it is you years from now when you want to modify the code and you no longer remember what you were thinking when you make such a Gordian knot out of it.

Make it hard for the user to do something wrong and easy for them to do something right

When designing a class or an interface or a GUI, etc. you should take into account that the user might try to use it in a completely wrong and unexpected way. You should make it difficult or impossible for the user to do that.

Some examples of this:

If a function is not valid when the input is negative, then it should not happily go on its business if given negative number. If some calculation is not valid at a redshift of 5,000 then it should not accept such a value. Functions should check these things and exit the program with an appropriate error message if they are violated. There are cases when the execution of a function is time sensitive and checking the validity of the inputs would slow the code down. Consider using an assert() statement in these cases or checking the validity at a higher level on completion of the task.

When a user uses a class it should create itself and assign valid values to internal variable automatically. If is does not do this it should not function until these variables are properly assigned. The user should not have to remember that he needs to do multiple steps to get a working object.

It should not be possible for the user to change a class in such a way that its internal variables are left in a state that is incompatible. For example, a class that calculates the power spectrum might have some internal variables that are calculated on initialization to make subsequent calls faster. If omega is changed during the running of the code all these internal variable should be updated when omega is changed with a setOmega() method for example. Omega should be a private or protected variable so that no one tries to change it directly without updating the internal parameters.

Control should come from the top down

As much as possible the choices between different ways of calculating things, different options of models, random number seeds, etc. should be made by the user at a high level and then passed down through the code to the lower levels. It is often easy to take a short cut by setting some variable to some value or using one derived class instead of another somewhere in the code. Try not to do this. You may have the intention of changing this later, but you might forget. The user at a higher level should know what he is getting. Parameters that are not often changed can be given default values leaving open the option of changing them in a well controlled way. There are times when an internal variable is not likely to be changed and putting it into the interface would complicate things too much to be worth it, but these should be the exception.

Have the code document all parameter options at runtime

Use assert() statements liberally

An assert() statement can be used to check the validity of a statement that should always be true. These statements can be removed from the executable code with a simple compiler flag so they will not slow they final version of the code down in any way. I have found that regularly checking that pointers are valid addresses and variable are within the expected range and many other things with assert() statements before any problem arises can greatly reduce the time spent debugging when a problem does arise.

Commenting Code

All class and function comments should be in doxygen format (see Doxygen and examples in code). The parameters for public functions and constructors should be commented. Individual line comment do not have to be in doxygen format.

Use complete sentences and avoid acronyms. Make them clear.

If a function uses parameter values, a formula or an algorithm taken from a published paper the paper should be cited within a comment so a user can find the paper.

Names of Classes and Variables

The names of classes and structs should start with a capital letter. Instances of a class should start with a lower-case letter. Make the name descriptive. There is a natural tendency to make the names very short. This is fine in a small function where the variable exists for only a few lines. But when in doubt, make the variable/class name longer, more descriptive and more specific. Pre-prossesor def's should be all capitals, but these should be avoided.

Random Number Seeds

No random number seeds should ever be assigned a value except in main.

Pointers and Handler Types

The pointer declaration Type *variable is generally used for pointers to an array of objects while the handler declaration TypeHndl variable is the same thing but is generally used as a pointer to a single object and used in a function declaration so that the object is not copied when the function is called. Good practice in C++ is to use references instead of handler typedef's, but the habit from C dies hard.

Classes that are members of another class should not take the Owner class as an argument of their methods or constructors

This practice violates the philosophy of object oriented programing and negates its advantages. A class should be in charge of its own variables. This practice can make the flow of information very confusing and make it not immediately clear whether the variable of the parent class are being changed by the member class.

Members that can be private should be private

You should have a reason to make a variable public and not the other way around. A corollary to this is that if some one else made a variable/method private you should think carefully before making it public.

Clean up your mess

You should strive to have all memory allocated in your routines automatically freed or freed by the user in a straightforward way and not rely on the automatic freeing of all memory when exiting main(). You may use your routine once and it is not important to you that memory is not freed on exit. Another user my use your routine repeatedly, perhaps a million times, this memory leak will build up and cause a problem. Every "new" should have a "delete" unless a smart pointer (such as a auto_ptr) is used. Repeated new's should not happen without a delete every time. Using a smart pointer is a good idea when applicable.