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 4cb5ea78d7 - Show all commits

198
STYLE
View File

@ -1,26 +1,33 @@
“Everyone knows that debugging is twice as hard as writing a program in the
first place. So if youre as clever as you can be when you write it, how
will you ever debug it?”
Brian Kernighan, The Elements of Programming Style
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. Use:
- a single line for control flow statements short enough to be easily Use
understood at a glance: ===
0. A single line for control flow statements short enough to be easily
understood at a glance:
if !(argc < 0) { usage(program_name); } if !(argc < 0) { usage(program_name); }
This applies to C switch cases, as well. This applies to C switch cases, as well:
switch (value) { /* aligning stuff to make it easier to read is fine */ switch (value) { /* aligning stuff to make it easier to read is fine */
case possibility: variable = foo; break; case possibility: variable = foo; break;
default: variable = NULL; break; default: variable = NULL; break;
} }
- as little nested logic as possible (within reason). 1. Braces in control flow where their inclusion is left optional in C:
- braces in control flow, when their inclusion is left optional by a
programming language (in, for example, C).
if (condition) { statement; } if (condition) { statement; }
- empty lines between different kinds of statements: 2. Empty lines between different kinds of statements:
int t; int t;
@ -40,66 +47,59 @@ programming language (in, for example, C).
return io; return io;
- compiler options that yield the most useful warnings, such as -Wpedantic in 3. Compiler options that yield the most useful warnings, such as -Wpedantic in
a lot of C compilers. Fix the warnings, too. See [0]. a lot of C compilers. Fix the warnings, too [0].
- fixed bounds for loops; see [0].
- one more level of indentation and one line per argument, when a function 4. Fixed bounds for loops [0].
call or statement header is too long to fit on one line:
5. One more level of indentation and one argument per line when a function
call or statement header is too long to fit on one line:
let usage = format!( let usage = format!(
"Usage: {} [-d delimiter] index command [args...]", "Usage: {} [-d delimiter] index command [args...]",
argv[0], argv[0],
); );
- one more level of indentation than the keyword that initiated a block. 6. One more level of indentation than the keyword that initiated a multi-line
block.
if (condition) { if (condition) {
statement; statement;
statement; statement;
} }
- the return value of all non-void functions, or explicitly ignore them (like 7. The return value of all non-void functions, or explicitly ignore them (like
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"?
casting to void in C): casting to void in C) [0]:
if ((a = malloc(sizeof char)) == NULL) { /* handle this error */ if ((a = malloc(sizeof char)) == NULL) { /* handle this error */
(void)fprintf(stderr, "oh noes!"); /* explicitly ignore this one */ (void)fprintf(stderr, "oh noes!"); /* explicitly ignore this one */
return EX_OSERR; /* ...because the program is exiting anyway */ return EX_OSERR; /* ...because the program is exiting anyway */
} }
See [0]. 8. The smallest possible scope for data [0].
- the smallest possible scope for data; see [0]. 9. Comments noting all the symbols and macros used from a C header file, next
- (C) comments noting all the symbols and macros used from a header, next to to its include macro:
its include macro:
#include <unistd.h> /* close(2), getopt(3), lseek(2), read(2), write(2), #include <unistd.h> /* close(2), getopt(3), lseek(2), read(2), write(2),
(space-aligned) * optarg, optind, STDIN_FILENO, STDOUT_FILENO */ (space-aligned) * optarg, optind, STDIN_FILENO, STDOUT_FILENO */
- (C) one more level of indentation within switch cases. 10. Spaces in control flow statements, after the keyword and before the
opening brace:
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) { 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 11. In Rust, a trailing comma on all arguments or fields that are on their own
lines: lines:
return Err(EvaluationError { return Err(EvaluationError {
message: format!("{}: Invalid token", i), message: format!("{}: Invalid token", i),
code: EX_DATAERR, code: EX_DATAERR,
}) })
- (Rust) extern statements after use statements that include standard library 12. In Rust, place extern statements after use statements that include standard
crates. Group like statements: library crates. Group like statements:
use std::fs::Path; use std::fs::Path;
@ -109,17 +109,29 @@ crates. Group like statements:
use strerror::StrError; use strerror::StrError;
use sysexits::{ EX_OSERR, EX_USAGE }; use sysexits::{ EX_OSERR, EX_USAGE };
- (Rust) if text is on the same line as a brace, spaces after an opening curly 13. If text is on the same line as a brace, spaces after an opening brace and
brace and before a closing one: before a closing one:
use sysexits::{ EX_DATAERR, EX_IOERR, EX_UNAVAILABLE, EX_USAGE }; use sysexits::{ EX_DATAERR, EX_IOERR, EX_UNAVAILABLE, EX_USAGE };
- (Rust) one more level of indentation within match arms. 14. One more level of indentation for match arms and switch cases.
1. Avoid: 15. Alphabetic sorting, where applicable:
- function pointers; see [0].
- heap memory allocation; see [0]. use std::io::{ BufWriter, Read, Write, stderr, stdin, stdout }
- 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?
16. In Rust, use the to_owned() method on string types (str, OsStr, CStr, etc.)
and the to_string() method on other types.
Avoid
=====
0. Heap memory allocation [0].
1. Using too much nested logic (within reason).
2. Too many levels of dereferences [0]:
/* do not do this */ /* do not do this */
for (size_t i = 0; i < sizeof a / sizeof *a; ++i) { for (size_t i = 0; i < sizeof a / sizeof *a; ++i) {
@ -131,48 +143,92 @@ brace and before a closing one:
if (s->id == MATCH) { s->val = 0; } if (s->id == MATCH) { s->val = 0; }
} }
2. Do not use: 3. Using C preprocessor macros; the fewer, the better [0].
- 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] 4. The exit(3p) and std::process::exit() functions; returning from the main
and section 2 of this document. function skips a system call.
- (C) any language features not in C99.
- (C) do-while loops, as they're unique to the language and confusing for
casual C programmers. Do Not Use
- (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 0. Function pointers [0].
p + sizeof p * n, not p + n, like pointer arithmetic suggests.
- (C) struct bitfields in unions, to access certain bits of bigger data types, 1. More than the length of one printed page for a function [0].
as it's poorly defined in the C standards; use bit arithmetic.
- (C) trigraphs. 2. Recursion, as its complex and can unexpectedly overflow the stack [0].
- (Cpp) inclusions in C header files, to prevent multiple file inclusions.
- (Cpp) variables to prevent multiple inclusions of the same file, such as: 3. Any functionality not in the POSIX C specification and language features not
in C99.
4. Do-while loops, as theyre unique to C and confusing for casual programmers.
5. Labels and goto statements; use sensible flow control [0].
6. 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.
7. C struct bitfields in unions, to access certain bits of bigger data types,
as its poorly defined in the C standards; use bit arithmetic.
8. C trigraphs.
9. Inclusions in C header files, to prevent multiple file inclusions.
10. C preprocessor variables to prevent multiple inclusions of the same file,
such as:
#ifdef _FILE #ifdef _FILE
#define _FILE #define _FILE
/* file body */ /* file body */
#endif /* ifdef _FILE */ #endif /* ifdef _FILE */
Instead, take the time to ensure other files aren't including the any files Instead, take the time to ensure other files arent including the any files
twice. twice.
- (libc) any functionality not in the latest POSIX or C99. 11. The gets(3p) function from <stdio.h>, as its impossible to prevent buffer
- (libc) gets(3p) from <stdio.h>, as it's impossible to prevent buffer overflows when it's used; use fgets(3p) from <stdio.h>.
overflows when it's used; use fgets(3p) from <stdio.h>.
- (libc) scanf(3p) from <stdio.h>; see [1]. 12. The scanf(3p) function from <stdio.h> [1].
- (Make) any functionality not described in make(1p) from the latest POSIX.
13. Any functionality not described in the latest POSIX make(1) specification.
14. Macros which panic on failure in Rust (such as the print!() and println!()
macros). Use a function and handle any errors. However, do use the eprintln!()
macro for error messages. Handling an error for writing an error message is
redundant.
Usage Text
==========
This section is adapted from the NetBSD style guide [2].
When programs are invoked incorrectly and in the synopsis of manual pages, uasge
text should be provided to the user. The following is the format used by this
project for this purpose:
All optional arguments are to be placed in square brackets (U+005B, U+005D).
Mutually exclusive arguments can be separated by a vertical line (U+007C).
Groups of arguments should be specified in alphabetical order in most cases. The
order of arguments and an example of these rules follows:
0. Options with no option arguments.
1. Options with option arguments. Arguments should be specified inside the same
square brackets as the options.
3. Non-option arguments.
"usage: f [-aDde] [-b b_arg] [-m m_arg] req1 req2 [opt1 [opt2]]\n"
"usage: f [-a | -b] [-c [-de] [-n number]]\n"
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> [1] <http://sekrit.de/webdocs/c/beginners-guide-away-from-scanf.html>
[2] <http://cvsweb.netbsd.org/bsdweb.cgi/~checkout~/src/share/misc/style>
-- --
Copyright © 2024 Emma Tebibyte <emma@tebibyte.media> Copyright © 2024 Emma Tebibyte <emma@tebibyte.media>