Comments - should I and how often?
When I was in school I loved to read, but nothing spoiled my reading fun as much as having to write a book report.
When writing software I used to regard comments the same way. I figured I could write understandible code well enough. Why should I bother writing lots and lots of comments. Especially if I'm the one who will be maintaining that code...
Then I started noticing that things that seemed really obvious to me a when I wrote a program, weren't so obvious after a year or two when I was trying to find a bug or add a new function to the code.
Comments to the rescue! Now I believe that it's nearly impossible to have too many comments. Unless I'm writing a piece of throwaway code that won't last longer than a day or two, I try to discipline myself to festoon programs with useful comments.
By useful I don't mean
x = x + 2; // increment x by 2
If that line gets a comment, it should explain WHY x is being incremented.
C/C++ provides two styles of complementing code:
It's a good idea to develop a style of comments and stick to it.
//------------------------------------------------------ // bcsjTimer.h - class declaration for bcsjHeader class // // This class performs the following functions: // ... // // Resources used: no system resources used // // Revisions: // 16Nov16 // 1. Created file //------------------------------------------------------- #ifndef __BCJSTIMER_H__ #define __BCSJTIMER_H__ /* * CLASS DECLARATION */ class bcsjTimer { // Member functions public: bcsjTimer(void); // constructor void start( unsigned long d ); // start timer running with duration d (micros) boolean active(void); // is timer running? boolean done(void); // is timer done? unsigned long getTimeRemaining(void); // how long to end of time period (micros) // class data private: unsigned long starttime; // time we started timing (micros) unsigned long duration; // time period we are waiting (micros) boolean isActive; // true if timer is active }; #endif
The header block should tell the reader what this class does so they don't need to dig through the code to figure that out. It also lists the Arduino hardware timers, interrupts, or other system resoures that are used. This is important to alert anyone using this class of possible system resource contention (a BIG issue when using classes/libraries you got from elsewhere!). Finally, it provides a revision list of bug fixes and additions.
Inside I try to give a quick definition of what the member functions do. Note that I've documented what goes in the time values - microseconds in this case. Don't leave the class users guessing about this. NASA has lost rockets because one person assumed a velocity was in miles per hour while another program though it was meters per second!
I also provide a quick definiton of what the private class data does. This is important. If there's not enough room on a single line then use as many lines as needed.
// class data private: // Specifies the time, in microseconds at the beginning of the // intervale we are timing. unsigned long starttime; // The interval, in microseconds, we are timing. unsigned long duration; boolean isActive; // true if timer is active }; #endif
As the program, or class in this case, is writing take the time to keep the previously written comments up to date.
At my previous gig as a software engineer, the modeling group wrote the comments for all their code first. Then when the comments were coherent enough, the programmers used the comments as a guide to writing the code. Of course this has the problem of without the code, it can be difficult to know if the proposed algorithms will actually work as intended!
Someimtes comments aren't used for information. Occaisionally, during the software debug process, it can be handy to temporarily remove a line of code. Or you may have added considerable debug scaffolding (debug print statements for example) that you want to turn off, but don't want to remove from the program entirely (because there was a lot of typing in placing them there and you might be needing to put them back in again...
In that case you might turn the program lines of interest into a comment.
for (int k=0; k<arraySize; ++k) { if (myArray[k] == 17) { // Serial.print( "loop ended k="); // Serial.println( k ); break; } // Serial.print( "loop[" ); // Serial.print( k ); // Serial.print( "] array val: "); // Serial.println( myArrayk] ); }
In the above example, the debug printing has been "turned off" by commenting out the print statements. While this could also be doen by using an "if" statement to test a debugEnabled variable, it avoids the use of extra code.
If the debug print statements are needed later in the development cycle they can be uncommented. In general, such statements should not be released with the code when it "ships" as it tends to make the source code harder to read and understand.
There's another way to comment and uncomment those print statements, conditional compilation.
C/C++ compilers make multiple passes through the source file. The first pass is often performed by a preprocessor. It scans the source file looking for compiler directives. Directives start with a '#' pound-sign and usually begin in the first column. Directives include:
Note that the program is NOT yet runing so only constant values can be used.
#if 0 x = 7; // this line not compiled y = x * 8; // this line not compiled #endif #if 1 Serial.print( "timeval = "); // this line compiled Serial.println( timeval ); // this line compiled #endif #define DEBUG_ENABLED 0 #if DEBUG_ENABLED Serial.print( "timeval = "); // this line not compiled Serial.println( timeval ); // this line not compiled #else Serial.print( "counterLimit = "); // this line compiled Serial.println( counterLimit ); // this line compiled #endif
You may have noticed that my header (.h) files start with a #ifndef. What in earth is that and why is it there?
//------------------------------------------------ // header file for class myCounter //------------------------------------------------ #ifndef __MYCOUNTER_H__ #define __MYCOUNTER_H__ // class declaration here... #endif
These are what are sometimes called include guards. Their purpose is to keep the contents of a header file from being included twice.
__MYCOUNTER_H__ is the name of a macro. The #ifndef looks to see if that macro has been defined already. If NOT, then the lines following the #ifndef are compiled. The first line after the #ifndef defines a value for the macro __MYCOUNTER__H__. If myInclude.h is included a second time, the macro will be present on the contents of the file won't be compiled a second time.
But if the macro has been defined, then the class declaration is NOT included in the compiled.
If we looked in the class body file - myCounter.cpp and our Arduino sketch (.ino) file both would have an include of myCounter.h in them.
Arduino sketch file... #incldue <include myCounter.h > myCounter.cpp file... #include <include myCounter.h >
Without the include guard in myCounter.h it would be compiled twice. The second time through the compiler would complain that the myCounter class was being redefined.
A #define can also be used to declare numeric constants. For example:
#define LED_TRACK_1 9 #define LED_TRACK_2 10 ... pinMode( LED_TRACK_1, OUTPUT ); pinMode( LED_TRACK_2, OUTPUT ); or #define NUM_LEDS 7 ... for (int idx=0; idx>0; idx+=1) { ... handle array of LEDs ... } or #define DEBUG_ENABLED 0 #if DEBUG_ENABLED .. code block being conditionally compiled ... #endif or #define DEBUG_ENABLED 1 // comments NOT allowed here!
There are a few things to keep in mind when using macro definitions:
This page and files copyright © 2016 by Charlie Comstock.