STYLE: make rules more granular and consistent, add examples #156

Open
trinity wants to merge 6 commits from style-c into main
Showing only changes of commit 8646d5c4ee - Show all commits

185
STYLE
View File

@ -1,11 +1,26 @@
The following guidelines are conducive to clear and readable code that is The following guidelines are conducive to clear and readable code that is
consistent with the style of the rest of the Bonsai Computer System. consistent with the style of the rest of the Bonsai Computer System.
0. Braces are mandatory for all control flow. 0. Use:
- a single line for control flow statements short enough to be easily
understood at a glance:
1. Nested indentation should be kept to a minimum. if !(argc < 0) { usage(program_name); }
2. Empty lines should be placed between different kinds of statements: This applies to C switch cases, as well.
switch (value) { /* aligning stuff to make it easier to read is fine */
case possibility: variable = foo; break;
default: variable = NULL; break;
}
- as little nested logic as possible (within reason).
- braces in control flow, when their inclusion is left optional by a
programming language (in, for example, C).
if (condition) { statement; }
- empty lines between different kinds of statements:
int t; int t;
@ -25,56 +40,66 @@ consistent with the style of the rest of the Bonsai Computer System.
return io; return io;
3. Each block of code should be indented once more than the keyword which - compiler options that yield the most useful warnings, such as -Wpedantic in
initiated the block: a lot of C compilers. Fix the warnings, too. See [0].
- fixed bounds for loops; see [0].
switch (c) { - one more level of indentation and one line per argument, when a function
case 'e': mode |= EQUAL; break; call or statement header is too long to fit on one line:
case 'g': mode |= GREATER; break;
case 'l': mode |= LESS; break;
default: return usage(s);
}
4. In C, spaces should be placed in control flow statements after the keyword
and before the opening brace:
for (i = 2; i < argc; ++i) {
5. If a function, a C control flow statement, or a Rust macro has arguments that
cause the statement to be broken into multiple lines, this should be done by
placing the arguments on a new line inside the parentheses:
let usage = format!( let usage = format!(
"Usage: {} [-d delimiter] index command [args...]", "Usage: {} [-d delimiter] index command [args...]",
argv[0], argv[0],
); );
6. If Rust function arguments or fields are on their own lines, they should - one more level of indentation than the keyword that initiated a block.
always have a trailing comma:
if (condition) {
statement;
statement;
}
- the return value of all non-void functions, or explicitly ignore them (like
casting to void in C):
if ((a = malloc(sizeof char)) == NULL) { /* handle this error */
(void)fprintf(stderr, "oh noes!"); /* explicitly ignore this one */
return EX_OSERR; /* ...because the program is exiting anyway */
}
See [0].
- the smallest possible scope for data; see [0].
emma marked this conversation as resolved Outdated

I fear this is impossible; dj(1), for instance, necessarily can't put an upward bound on read cycles.

I fear this is impossible; dj(1), for instance, necessarily can't put an upward bound on read cycles.

Maybe, "where possible"?

Maybe, "where possible"?
- (C) comments noting all the symbols and macros used from a header, next to
its include macro:
#include <unistd.h> /* close(2), getopt(3), lseek(2), read(2), write(2),
(space-aligned) * optarg, optind, STDIN_FILENO, STDOUT_FILENO */
- (C) one more level of indentation within switch cases.
switch (value) {
case possibility:
statement;
default:
statement;
}
- (C) spaces in control flow statements, after the keyword and before the
opening brace:
for (i = 2; i < argc; ++i) {
- (Cpp) as little of the preprocessor as possible; see [0].
- (Rust) a trailing comma on all arguments or fields that are on their own
lines:
return Err(EvaluationError { return Err(EvaluationError {
message: format!("{}: Invalid token", i), message: format!("{}: Invalid token", i),
code: EX_DATAERR, code: EX_DATAERR,
}) })
7. If text is on the same line as a brace, spaces should be placed after an - (Rust) extern statements after use statements that include standard library
opening curly brace and before a closing one: crates. Group like statements:
use sysexits::{ EX_DATAERR, EX_IOERR, EX_UNAVAILABLE, EX_USAGE };
8. If a control flow statement is short enough to be easily understood in a
glance, it may be placed on a single line:
if !(argc < 0) { usage(program_name); }
9. In C, note everything you use from a library in a comment subsequent to its
#include statement:
#include <unistd.h> /* close(2), getopt(3), lseek(2), read(2), write(2),
* optarg, optind, STDIN_FILENO, STDOUT_FILENO */
10. In Rust, place extern statements after use statements that include standard
library crates. Group alike statements:
use std::fs::Path; use std::fs::Path;
@ -84,40 +109,74 @@ library crates. Group alike statements:
use strerror::StrError; use strerror::StrError;
use sysexits::{ EX_OSERR, EX_USAGE }; use sysexits::{ EX_OSERR, EX_USAGE };
11. Do not use do while loops in C. - (Rust) if text is on the same line as a brace, spaces after an opening curly
brace and before a closing one:
12. Adhere to the following rules from the paper The Power of 10: Rules for use sysexits::{ EX_DATAERR, EX_IOERR, EX_UNAVAILABLE, EX_USAGE };
Developing Safety-Critical Code [0]:
1. Avoid complex flow constructs, such as goto and recursion.
2. All loops must have fixed bounds. This prevents runaway code.
3. Avoid heap memory allocation.
4. Restrict functions to the length of a single printed page.
6. Restrict the scope of data to the smallest possible. - (Rust) one more level of indentation within match arms.
7. Check the return value of all non-void functions, or cast to void to
indicate the return value is useless (such as in the case of using
fprintf(3p) to print to the standard error).
8. Use the preprocessor sparingly.
9. Limit pointer use to a single dereference, and do not use function
pointers.
10. Compile with all possible warnings active; all warnings should then be
addressed before release of the software (for C compilers, compile with
-Wpedantic).
13. Remember this quote from The Elements of Programming Style by Brian 1. Avoid:
Kernighan: - function pointers; see [0].
Everyone knows that debugging is twice as hard as writing a program in the - heap memory allocation; see [0].
first place. So if you're as clever as you can be when you write it, how - too many levels of dereferences; see [0].
trinity marked this conversation as resolved Outdated

I struggled with coming up with this example. Could someone proofread this?

I struggled with coming up with this example. Could someone proofread this?
will you ever debug it?
/* do not do this */
for (size_t i = 0; i < sizeof a / sizeof *a; ++i) {
if (a[i].id == MATCH) { a[i].val = 0; }
}
/* do this */
for (struct MadeUp *s = &a[0]; *s != NULL; s = &s[1]) {
if (s->id == MATCH) { s->val = 0; }
}
2. Do not use:
- more than the length of one printed page for a function; see [0].
- recursion, as it's complex and can unexpectedly overflow the stack; see [0]
and section 2 of this document.
- (C) any language features not in C99.
- (C) do-while loops, as they're unique to the language and confusing for
casual C programmers.
- (C) gotos; use sensible flow control, see [0].
- (C) pointer arithmetic, as it tends to be confusing and unnecessary; use
index-reference patterns like &p[1] instead of p + 1. &p[n] is the address at
p + sizeof p * n, not p + n, like pointer arithmetic suggests.
- (C) struct bitfields in unions, to access certain bits of bigger data types,
as it's poorly defined in the C standards; use bit arithmetic.
- (C) trigraphs.
- (Cpp) inclusions in C header files, to prevent multiple file inclusions.
- (Cpp) variables to prevent multiple inclusions of the same file, such as:
#ifdef _FILE
#define _FILE
/* file body */
#endif /* ifdef _FILE */
Instead, take the time to ensure other files aren't including the any files
twice.
- (libc) any functionality not in the latest POSIX or C99.
- (libc) gets(3p) from <stdio.h>, as it's impossible to prevent buffer
overflows when it's used; use fgets(3p) from <stdio.h>.
- (libc) scanf(3p) from <stdio.h>; see [1].
- (Make) any functionality not described in make(1p) from the latest POSIX.
2. Keep the following in mind:
- Everyone knows that debugging is twice as hard as writing a program in the
first place. So if you're as clever as you can be when you write it, how will
you ever debug it?
-- Brian Kernighan, in The Elements of Programming Style
References References
========== ==========
[0] <https://web.eecs.umich.edu/~imarkov/10rules.pdf> [0] <https://web.eecs.umich.edu/~imarkov/10rules.pdf>
[1] <http://sekrit.de/webdocs/c/beginners-guide-away-from-scanf.html>
-- --
Copyright © 2024 Emma Tebibyte <emma@tebibyte.media> Copyright © 2024 Emma Tebibyte <emma@tebibyte.media>
Copyright © 2024 DTB <trinity@trinity.moe>
Copyright © Wikipedia contributors Copyright © Wikipedia contributors
This work is licensed under CC BY-SA 4.0. To view a copy of this license, visit This work is licensed under CC BY-SA 4.0. To view a copy of this license, visit