K&R is still valid but dated.
A newer book:
C Programming: A Modern Approach, 2nd Edition 2nd Edition by K. N. King (Author)
Today, you must learn how to *design* software, not only how to *program* it. The old literature doesn't teach you that at all.
The example below gives a very bad example of commenting functions.
Literally stating in the comment what a function does is not only useless (just read the function) it is even harmful.
You need time to write, read and maintain such comments, and they go out of sync with the sourcecode all the time.
Which makes them misleading, or at least you can't trust them, and you have to read the code anyway. Just yuck.
I worked at a company that hadn't updated the documentation in over 12 years.But the example I showed does neither. It does not describe how the function is used, It does not say anything about it's parameters. It is just a direct transliteration of the code, and that is useless. I have seen code like:
Luckily they did use coding guidelines that had some sort of Doxygen type of headers for functions,
describing the use of the function, all the input and output parameters and return value if applicable.
The description should be sufficient to know it could be a valid or invalid function for that purpose and when it is valid then you read the code to verify.
The example below gives a very bad example of commenting functions.
It starts with the decoration of all the stars around the comment. Some IDE's have baked in functions for that, but manual maintaning those is a big bore, which makes your code less portable, but more important: Literally stating in the comment what a function does is not only useless (just read the function) it is even harmful. You need time to write, read and maintain such comments, and they go out of sync with the sourcecode all the time. Which makes them misleading, or at least you can't trust them, and you have to read the code anyway. Just yuck.
I saw some part about stating the length of integers, long ints and such, but not the uint16_t standardized typedefs.
Quite some notes about multiline macro's and those are indeed a part of the C syntax, but bad practice.
Today, you must learn how to *design* software, not only how to *program* it. The old literature doesn't teach you that at all.Amen to that. Learning a programming language and learning to program are two separate subjects.
But the example I showed does neither. It does not describe how the function is used, It does not say anything about it's parameters. It is just a direct transliteration of the code, and that is useless.Agree, I think it is just an example to show and teach to write comments.
My best guess about your last remark is that you have not realized how bad that comment is.Always difficult to communicate over forums, but had enough of 1 years arduino guys that think to know it all and show the worst ugly code you ever seen. Sorry but I read in your comments that "to write comments what a function does is useless" which probably is not what you meant. BTW personally I like the
Literally stating in the comment what a function does is not only useless (just read the function) it is even harmful.
int linear(double x0, double x1, long count,
int (*func)(double x, void *data), void *data)
{
if (count < 1 || !func)
return -EINVAL;
if (count == 1)
return func((double)(0.5*x0) + (double)(0.5*x1), data);
--count;
for (long i = 0; i <= count; i++) {
const double x = ( (double)((count - i) * x0) + (double)(i * x1) ) / count;
const int err = func(x, data);
if (err)
return err;
}
return 0;
}
int linear(double x0, double x1, long count,
int (*func)(double x, void *data), void *data)
{
/* Count must be positive and func non-NULL for this function to work. */
if (count < 1 || !func)
return -EINVAL;
/* If only one call is done, use the midpoint between x0 and x1. */
if (count == 1) {
/* Be careful, and avoid domain cancellation error if x0 and x1 differ a lot in magnitude. */
return func((double)(0.5*x0) + (double)(0.5*x1), data);
}
/* We'll use (count) instead of (count - 1) from now on in this function. */
--count;
for (long i = 0; i <= count; i++) {
/* Avoid domain cancellation error when x0 and x1 differ a lot in magnitude. */
const double x = ( (double)((count - i) * x0) + (double)(i * x1) ) / count;
/* We need to abort if the callback function tells us to. */
const int err = func(x, data);
if (err)
return err;
}
return 0;
}
/* Call callback function 'count' times, linearly distributed
between x0 and x1. Aborts and returns the callback function
return value if it returns nonzero. Returns zero when successful. */
int linear(double x0, double x1, long count,
int (*func)(double x, void *data), void *data)
{
/* Abort if count is not positive, or func is not a valid function pointer. */
if (count < 1 || !func)
return -EINVAL;
/* The loop later on will not handle the count==1 case right,
so we need to handle that here. */
if (count == 1) {
/* Cast to double, to avoid domain cancellation error when x0 and x1
are of wildly different magnitudes; otherwise the larger one in magnitude
would just be halved, and the smaller one ignored. */
return func((double)(0.5*x0) + (double)(0.5*x1), data);
}
/* Since (count-1) occurs often in the rest of this function,
we decrement it by one so we can use (count) instead. */
--count;
/* Iterate i from 0 to count, inclusive, so we have 'count+1' iterations
of the loop body. Note, 'count+1' is the original 'count' amount,
as we decremented 'count' before. */
for (long i = 0; i <= count; i++) {
/* x = (1.0 - i/count) * x0 + (i/count) * x1.
When |x0| >> |x1| or |x1| >> |x0|, we don't want the larger magnitude one
to define x (that's domain cancellation error), so we're careful we multiply
x0 and x1 by the weight before adding them together. */
const double x = ( (double)((count - i) * x0) + (double)(i * x1) ) / count;
/* If the callback function returns nonzero, we need to abort the loop and return that value immediately. */
const int err = func(x, data);
if (err)
return err;
}
return 0;
}
int linear(double x0, double x1, long count,
int (*func)(double x, void *data), void *data)
{
/* Sanity checks. */
if (count < 1 || !func)
return -EINVAL;
if (count == 1)
return func((double)(0.5*x0) + (double)(0.5*x1), data);
--count;
for (long i = 0; i <= count; i++) {
/* Calculate x. */
const double x = ( (double)((count - i) * x0) + (double)(i * x1) ) / count;
/* Call callback function. */
const int err = func(x, data);
if (err)
return err;
}
/* Success! */
return 0;
}
I'm particularly ashamed of the final one, the /* Success! */ comment: I like it, even when I know it is useless, and probably irks some other developers to no end. Sorry.
int linear(double x0, double x1, long count,
int (*func)(double x, void *data), void *data)
Quoteint linear(double x0, double x1, long count,What does it do?
int (*func)(double x, void *data), void *data)
/* Call callback function 'count' times, linearly distributed
between x0 and x1. Aborts and returns the callback function
return value if it returns nonzero. Returns zero when successful. */
Although I see I missed "with the argument" after the first comma.Honestly, expecting someone to decipher the entire codebase just to find out what this stuff does from a 200ft view is taking the piss If you can't write good comments, in English, no less, what makes you think you can write code in some weirdo foreign language? Or that the reader will understand your dialect?Fuck you too.
The function header comment should document the contract of the function: what it does, what input it expects, what it returns, what side effects it has, and what error conditions may occur. The bounds of valid input and the range of non-error output should be stated, where these are not equivalent to those set by the parameter and function types. Where applicable, the algorithm used should be described, or a reference provided to a published description.For library code, I disagree. Aside from the algorithm and references to the theory behind the implementation (which should be included as a comment), everything else belongs to the man page documenting that interface function. If you don't have man pages, you're doing things wrong anyway.
I do not recall ever seeing satisfactory documentation based on Doxygen
The function header comment should document the contract of the function: what it does, what input it expects, what it returns, what side effects it has, and what error conditions may occur. The bounds of valid input and the range of non-error output should be stated, where these are not equivalent to those set by the parameter and function types. Where applicable, the algorithm used should be described, or a reference provided to a published description.For library code, I disagree. Aside from the algorithm and references to the theory behind the implementation (which should be included as a comment), everything else belongs to the man page documenting that interface function. If you don't have man pages, you're doing things wrong anyway.
At the object level (compilation units, separate files that are part of the same project), I do agree.
/**
* Call a callback function with a linearly progressing argument
*
* This function linearly interpolates between two real numbers,
* and calls the specified callback function for each value,
* for the specified number of calls. If the callback function returns nonzero,
* iteration is aborted, and the function returns the callback return value.
* If only one call is requested, it is done at the midpoint.
*
* @param x0 Initial value of the argument
* @param x1 Final value of the argument
* @param count Number of calls (including initial and final calls
* @param func Callback function
* @param data User pointer provided to the callback function as-is
* @return Zero if success, callback function nonzero return value,
* or -EINVAL if count < 1 or func is NULL.
*/
int linear(double x0, double x1, long count,
int (*func)(double x, void *data), void *data)
{
if (count < 1 || !func)
return -EINVAL;
/* Interpolating between x0 and x1 can lead to domain cancellation error,
if the two values differ a lot in magnitude, because a + b == a for |a| >> |b|
using double a, b. To avoid this, we need to scale the two values first,
and only sum the scaled values. We can use either
x = ((n - 1 - i)/(n-1))*x0 + (i/(n-1))*x1
or
x = ((n - 1 - i)*x0 + i*x1) / (n-1)
to do this safely; we can also ensure the multiplications are done before
the additions by using a cast to double, because a cast requires the compiler
to restrict the value to the range and precision of that type.
*/
/* One call is special; the argument is at the mid point. */
if (count == 1)
return func((double)(0.5*x0) + (double)(0.5*x1), data);
/* Since the rest of this function only uses (count-1), shorten
the expressions by pre-decrementing count. Note that then
the loop iteration range is from 0 to count, inclusive. */
--count;
for (long i = 0; i <= count; i++) {
const double x = ( (double)((count - i) * x0) + (double)(i * x1) ) / count;
const int err = func(x, data);
if (err)
return err;
}
return 0;
}
but obviously, I'd love to get feedback from cow-orkers or other developers, since I know my comments can definitely be improved upon.it seemed to me to be just blah blah about how a non-English speaker like myself shouldn't talk about comments or write any code
Here is the same function I talked about above, commented in a way I myself would like to have commented it
The function header is all I really want to know when I am figuring out what use the function is and how it fits into the program. The in-function comments are useful when I have to deal with the detail of what it's doing, so there are two separate levels of interest and requirements.Well put.
The function header comment should document the contract of the function: what it does, what input it expects, what it returns, what side effects it has, and what error conditions may occur. The bounds of valid input and the range of non-error output should be stated, where these are not equivalent to those set by the parameter and function types. Where applicable, the algorithm used should be described, or a reference provided to a published description.
Do you (you as in everyone) add the comment to the header file (declaration), or the implementation, when the two are separate?The function header comment should document the contract of the function: what it does, what input it expects, what it returns, what side effects it has, and what error conditions may occur. The bounds of valid input and the range of non-error output should be stated, where these are not equivalent to those set by the parameter and function types. Where applicable, the algorithm used should be described, or a reference provided to a published description.Agree with this (if what I said earlier was not clear enough). Documenting *how* a function does what it does in a function "header" is useless in general - and worse. But it should indeed document what it takes to consider the function a black box, so the "contract" as you said. I don't agree with even stating what kind of "algorithm" it uses in this header - this pertains to the comments inside the function, as long again as it's not obvious. Exception to this may be if some algorithm it uses would matter to "users" of the function. But it's usually not the case. What matters would more be, the complexity of the function's code (if it matters, say for a sorting function), whether the function acts on a given array in place or not, things like that. Keep it "high-level". The innards should stay inside.
Do you (you as in everyone) add the comment to the header file (declaration), or the implementation, when the two are separate?
Like I said, I agree if external documentation is not possible/viable.QuoteDo you (you as in everyone) add the comment to the header file (declaration), or the implementation, when the two are separate?Personally, I think it's worth [adding to both] (a quick copy'n'paste is all it takes) but it wouldn't surprise me if mine was a minority view :)
In a company, you have supervisors to deal with such conflicts.
Do you (you as in everyone) add the comment to the header file (declaration), or the implementation, when the two are separate?The function header comment should document the contract of the function: what it does, what input it expects, what it returns, what side effects it has, and what error conditions may occur. The bounds of valid input and the range of non-error output should be stated, where these are not equivalent to those set by the parameter and function types. Where applicable, the algorithm used should be described, or a reference provided to a published description.Agree with this (if what I said earlier was not clear enough). Documenting *how* a function does what it does in a function "header" is useless in general - and worse. But it should indeed document what it takes to consider the function a black box, so the "contract" as you said. I don't agree with even stating what kind of "algorithm" it uses in this header - this pertains to the comments inside the function, as long again as it's not obvious. Exception to this may be if some algorithm it uses would matter to "users" of the function. But it's usually not the case. What matters would more be, the complexity of the function's code (if it matters, say for a sorting function), whether the function acts on a given array in place or not, things like that. Keep it "high-level". The innards should stay inside.