diff --git a/STYLE b/STYLE index 5322661..1e6dadb 100644 --- a/STYLE +++ b/STYLE @@ -1,11 +1,51 @@ +“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, The Elements of Programming Style + + The following guidelines are conducive to clear and readable code that is consistent with the style of the rest of the Bonsai Computer System. -0. Braces are mandatory for all control flow. -1. Nested indentation should be kept to a minimum. +Use +=== -2. Empty lines should be placed between different kinds of statements: + 0. A single line for control flow statements short enough to be easily + understood at a glance: + + if !(argc < 0) { usage(program_name); } + + This applies to C switch statements and cases and Rust match statements, as + well: + + switch (value) { /* aligning stuff to make it easier to read is fine */ + case possibility: variable = foo; break; + default: variable = NULL; break; + } + + 1. Switch cases in C and match arms in Rust should start another level of + indentation: + + switch (value) { + case possibility: + statement; + break; + default: + statement; + break; + } + + match result { + Ok(n) => variable = n, + Err(e) => error = e, + } + + 2. Braces in control flow where their inclusion is left optional in C: + + if (condition) { statement; } + + 3. Empty lines between different kinds of statements: int t; @@ -25,56 +65,57 @@ consistent with the style of the rest of the Bonsai Computer System. return io; -3. Each block of code should be indented once more than the keyword which -initiated the block: + 4. Compiler options that yield the most useful warnings, such as -Wpedantic in + a lot of C compilers. Fix the warnings, too [0]. - switch (c) { - case 'e': mode |= EQUAL; break; - 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: + 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!( "Usage: {} [-d delimiter] index command [args...]", argv[0], ); -6. If Rust function arguments or fields are on their own lines, they should -always have a trailing comma: + 6. One more level of indentation than the keyword that initiated a multi-line + block. + + if (condition) { + statement; + statement; + } + + 7. The return value of all non-void functions, or explicitly ignore them (like + casting to void in C) [0]: + + 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 */ + } + + 8. The smallest possible scope for data [0]. + + 9. Comments noting all the symbols and macros used from a C header file, next + to its include macro: + + #include /* close(2), getopt(3), lseek(2), read(2), write(2), + (space-aligned) * optarg, optind, STDIN_FILENO, STDOUT_FILENO */ + + 10. Spaces in control flow statements, after the keyword and before the + opening brace: + + for (i = 2; i < argc; ++i) { + + + 11. In Rust, a trailing comma on all arguments or fields that are on their own + lines: return Err(EvaluationError { message: format!("{}: Invalid token", i), code: EX_DATAERR, }) -7. If text is on the same line as a brace, spaces should be placed after an -opening curly brace and before a closing one: - - 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 /* 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: + 12. In Rust, place extern statements after use statements that include standard + library crates. Group like statements: use std::fs::Path; @@ -84,40 +125,129 @@ library crates. Group alike statements: use strerror::StrError; use sysexits::{ EX_OSERR, EX_USAGE }; -11. Do not use do while loops in C. + 13. If text is on the same line as a brace, spaces after an opening brace and + before a closing one: -12. Adhere to the following rules from the paper The Power of 10: Rules for -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. + use sysexits::{ EX_DATAERR, EX_IOERR, EX_UNAVAILABLE, EX_USAGE }; - 6. Restrict the scope of data to the smallest possible. - 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). + 14. Alphabetic sorting, where applicable: -13. Remember this quote from The Elements of Programming Style by Brian -Kernighan: - 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? + use std::io::{ BufWriter, Read, Write, stderr, stdin, stdout } + + 15. In Rust, use the to_owned() method on string types (str, OsStr, CStr, etc.) + and the to_string() method on other types. + + +Avoid +===== + + 16. Unbounded loops [0]. + + 17. Function pointers [0]. + + 18. Heap memory allocation [0]. + + 19. Using too much nested logic (within reason). + + 20. Too many levels of dereferences [0]: + + /* 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; } + } + + 21. Using C preprocessor macros; the fewer, the better [0]. + + 22. The exit(3p) and std::process::exit() functions; returning from the main + function skips a system call. + + +Do Not Use +========== + + 23. More than the length of one printed page for a function [0]. + + 24. Recursion, as it’s complex and can unexpectedly overflow the stack [0]. + + 25. Any functionality not in the POSIX C specification and language features not + in C99. + + 26. Do-while loops, as they’re unique to C and confusing for casual programmers. + + 27. Labels and goto statements; use sensible flow control [0]. + + 28. 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. + + 29. 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. + + 30. C trigraphs. + + 31. Inclusions in C header files, to prevent multiple file inclusions. + + 32. C preprocessor 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 any files twice. + + 33. The gets(3p) function from , as it’s impossible to prevent buffer + overflows when it's used; use fgets(3p) from . + + 34. The scanf(3p) function from [1]. + + 35. Any functionality not described in the latest POSIX make(1) specification. + + 36. 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" References ========== [0] +[1] +[2] -- Copyright © 2024 Emma Tebibyte +Copyright © 2024 DTB Copyright © Wikipedia contributors This work is licensed under CC BY-SA 4.0. To view a copy of this license, visit