NSWI170 Computer Systems
Guidelines to write a better C/C++ code
Why? Writing well-designed, readable, and maintainable code is good. It is necessary for software sustainability and programmer cooperation. Although the main objectives of this course are to teach you to work with low-level hardware (and the fundamentals of C/C++), the coding skills need to be trained from the very beginning and they are as important as other skills (such as the knowledge of algorithms). To make this more manageable, we have selected some topics which will be mandatory to follow. However, the remaining issues presented on this page are also considered important and you are strongly encouraged to adopt them.
Please note that we will update the detailed explanations of individual rules during the semester to clarify them based on your feedback.
Unforgivable curses
The mandatory rules were called unforgivable curses. More precisely, it is unforgivable if you curse your code by violating one of the rules. These rules are introduced one at a time and you are obliged to follow them from the next lab once they are introduced (however, it might be prudent to make yourself acquainted with them at the beginning and try to follow them right away). Severe cases of unforgivable curses will cause you to forfeit your credit, especially in case of repeated occurrences.
Rule | Introduced | Enforced from | ||
---|---|---|---|---|
1. |
Code decomposition The code needs to be properly divided into functions (structures, logical blocks, ...). |
lab #1 | lab #2 | details |
2. |
Using constants Important constant values (pin numbers, animation periods, ...) should be declared as constants (not directly embedded as literals in code). |
lab #2 | lab #3 | details |
3. |
Keep your code DRY Do not repeat (copy-paste) your code. Use functions, for-loops, ... |
lab #3 | lab #4 | details |
4. |
Restricted use of global variables Only valid utilization of global variables is allowed. They must not replace local variables or take the role of function arguments. |
lab #4 | lab #5 | details |
5. |
Encapsulation Logical parts of the application must be grouped together (e.g., using structures). Communication between parts must be conducted solely through a well-defined interface (functions/methods). |
lab #5 | lab #6 | details |
You are also supposed to avoid unforgivable curses in your final lab test.
Other recommendations
The following suggestions will make your code better:
- Remove all warnings from the compilation. A warning often indicates a possible error so it is better to write your code in a way that does not produce any warning. Furthermore, from the 3rd assignment on, the ReCodEx will not accept solutions with warnings.
- Use adequate names for your variables and functions (not too long, not too short, and as much to the point as possible). A function name should describe, what the function does. If you cannot find a proper name for a function (or the name is too long), it might be a sign you need to improve your code decomposition.
- Avoid deeply nested
if
-branches as well asif
statements with very complex conditions. Heavy branching often indicates that your code is badly designed or in need of decomposition (needs to be divided into multiple functions). - Similarly, branching inside loops to get different behavior in individual iterations indicates badly designed code.
- The
switch-case
should be avoided altogether (similar problem as heavy branching, so replacing it withif
statements only makes it worse). - Do not create overcomplicated constructions like
if (cond) { return true; } else { return false; }
. This can be easily replaced byreturn cond;
(assuming the return type isbool
). - Do not mix integer and float arithmetic together since Floats are imprecise and you may end up with bugs caused by unexpected rounding (if you do not handle that correctly). Especially avoid using
pow()
orlog()
for integer computations when you can do the same thing using bit shifts or a few multiplications. - Use pointer arithmetic when dealing with pointer movement (e.g.,
*p++
), and use the array access operator in array-like operations -- i.e., writea[i]
instead of*(a+i)
. Use constant pointers for immutable dataconst char *str = "Hello";
. - Do not block
loop()
invocation in Arduino code. This is actually not a recommendation, it is a requirement embedded in most assignments (this is just a reminder).
In case of confusion, do not hesitate to consult your teachers for clarification.
Final words
Every rule has its exceptions. Experienced programmers know, when it might be ok to break these rules and when it is better to follow them rigidly. However, we would like to ask you to follow these rules completely during the Arduino courses even if you feel an exception would be warranted in a particular situation. The experience from past years suggests that following these rules in simple assignments (like the ones you are given) is virtually always better than the alternatives.
Do not resent refactoring. Refactoring (redesigning and rewriting parts of your code) is a natural process in software development (similar to when the dead plants fertilize the soil so new plants may grow). True professionals are not afraid to delete their code and rewrite it differently when it stands to reason. In the learning process, refactoring should be even more frequent as it is less likely you will write perfect code in the first shot. Do not remorse your deleted code, rejoice for it since it served its purpose (at the very least, it allowed you to learn something). On the other hand, it is wise to keep the old code revisions (e.g., using a local/private git repository), so you can revert your refactoring attempts (if you take a wrong turn) or compare old code and new code.