Not just similar, but based on MISRA C!
MISRA C can be a total PITA though (I get why, but it doesn't make it any less annoying). Some well known storage products follow similar principles without being quite so stringent. It's quite a nice middle ground, and - if you are, err, detail oriented - can be a less aggravating experience than sloppy, higher level codebases (yes, Java, I'm looking at you mainly).
What is annoying is the paperwork when you need a deviation. However that's kind of the point of the process. Make it annoying enough that people really think about whether the deviation is necessary.
At the same time it's only a coding standard. Not a panacea for all your coding issues. You can have 100% MISRA conformant spaghetti.
It does reduce the "shoot yourself in the foot" surface area a little bit though.
IMHO some of MISRA rules lead directly to hard to read/maintain code. Consider a function that:
1. opens a file 2. allocates enough memory to store the whole file 3. reads the file 4. return the allocated buffer on success or NULL on failure
You want to write it so it doesn't leak either memory or file handles whether successful or not (if successful ownership of the memory buffer is passed to the caller so it must not be freed in that case).
To be MISRA compliant you'd either end up with a "Christmas tree" of nested scopes or if-statements with extra && in them (pseudo C-code):
char *buf = NULL;
FILE *fh = fopen(...);
if (fh) {
if (success(fseek(fh, end))) {
long int sz = ftell(fh);
if (sz > 0) {
buf = malloc(sz);
if (buf) {
if (failed(fread(fh, buf))) {
report_error();
free(buf);
buf = NULL;
}
} else {
report_error();
}
} else {
report_error();
}
} else {
report_error();
}
fclose(fh);
} else {
report_error();
}
return buf;
However if you were allowed to use goto with a single exit-label the code would be much cleaner and easier to follow: char *buf = NULL;
char *rv = NULL;
FILE *fh = fopen(...);
if (!fh) {
report_error();
goto exit;
}
if (fail(fseek(fh, end))) {
report_error();
goto exit;
}
long int sz = ftell(fh);
if (sz <= 0) {
report_error();
goto exit;
}
buf = malloc(sz);
if (!buf) {
report_error();
goto exit;
}
if (failed(fread(fh, buf))) {
report_error();
goto exit;
}
rv = buf;
buf = NULL;
exit:
if (fh) {
fclose(fh);
}
if (buf) {
free(buf);
}
return rv;I think an exception here makes sense
True, but I've seen enough MISRA code bases plagued with the "Christmas tree" layout even if dynamic memory allocation isn't allowed. This was just a generic example most people can relate to.
Still single return statement, which usually makes for more spaghetti.
FILE *fh;
char *buf = NULL;
char *rv = NULL;
do {
fh = fopen(...);
if (!fh) {
report_error();
break;
}
if (fail(fseek(fh, end))) {
report_error();
break;
}
long int sz = ftell(fh);
if (sz <= 0) {
report_error();
break;
}
buf = malloc(sz);
if (!buf) {
report_error();
break;
}
if (failed(fread(fh, buf))) {
report_error();
break;
}
rv = buf;
buf = NULL;
} while(false);
if (fh)
fclose(fh);
if (buf)
free(buf);
return rv;Plus, it might be slightly more convoluted than goto, but is less convoluted than the original over-nested code, which was the intention.
Fortunately MISRA seems to have gotten back some sanity in the 2012 revision compared to the one the above JPL standard is based on.
Only if the "more convoluted and harder-to-read code" is worse than what the standard tries to avoid.
A standard that calls for not allocating memory dynamically for example might result in less elegant code than one that does, for example, but that's not a "clear failure" since that away it avoids the uncertainty and variable runtime performance that malloc brings.
I'm addressing the general claim here, that "if a coding standard forces you to write more convoluted and harder-to-read code just to work around some of its rules, that is a clear failure of said standard" -- not specifically where the particular no-goto rule was justified.
A standard can be perfectly valid and good in restricting things even if that forces programmers to write "less elegant code" -- as long as this is necessary to fulfil some other objective of the standard (e.g. easy formal verification or real-time behavior).
You're allowed to use GOTO provided you're jumping forward, and only for error handling.
Shoehorning an existing codebase into being MISRA compliant is utter hell. It's something you really need to design-in from the beginning.
Also for anyone in the audience who's going "ugh, MISRA..." -- the 2012 spec revision is a significant improvement on the 2004 version and cleans up a lot of the more troublesome rules.
Even if I'm not doing MISRA-required code, I still find myself sticking to its suggestions... "Allocate memory statically" is a really good one, especially on embedded systems.