Design and style checklist

Design and style checklist #

This checklist provides a nonexhaustive listing of design and style dicta. Many of these are discussed in greater detail in the textbook’s Style Guide appendix. The criteria here are stated positively (what you should do), not negatively (what you shouldn’t); and should be interpreted accordingly.

Some of these dicta seem contradictory (“avoid excessive parenthesization”, “add parentheses for clarity”). They are not. As with all such dicta, these are not hard and fast rules, but need to be interpreted flexibly and contextually. See the Style Guide for further discussion.

  1. Structure

    1. Keep functions short, breaking them up at the joints
    2. Reuse code where possible
      1. Use library functions where possible
    3. Scope values narrowly
    4. Keep scopes of conditionals narrow
    5. Modules are constrained by signatures
    6. Use functional primitives (map, fold, filter) where applicable
    7. Use partial application where possible
    8. Use reverse application (|>) to clarify deeply embedded calls
  2. Documentation and commenting

    1. Comments answer “why” rather than “how”
    2. Comments match the code
    3. No redundant comments (merely restating the code)
    4. Nonobvious code bits are commented
    5. Consistent multi-line comment style
    6. Comment lines precede lines they describe
    7. Same-line comments follow what they describe
    8. Sufficient header documentation at file level
    9. Top-level declarations are commented
    10. Top-level declaration documentation conventions are consistent
  3. Testing and debugging

    1. Code compiles cleanly without any warnings
    2. Unit tests provided
    3. Unit tests cover all edge cases
    4. Unit tests cover all execution paths, where possible
    5. Unit tests in separate file
  4. Verbosity

    1. Eliminate redundant code
    2. Eliminate dead code
    3. Use library operators rather than prefix equivalents (@ rather than List.append, e.g.)
    4. Use logical operators rather than conditional expressions
    5. Don’t rewrap functions (sqrt rather than fun x -> sqrt x, (+) rather than fun x y -> x + y, e.g.)
    6. Factor recomputed values
    7. Use compact notations ([x] rather than x :: [], condition rather than condition = true, e.g.)
    8. Use module prefixes or local opens rather than opening module unless module is very broadly deployed
  5. Naming and declarations

    1. Names are meaningful and specific
    2. Standard name structure conventions are followed (cCONSTANTS, variable_names, Constructors, MODULE_TYPES, ModuleDefns, FunctorNames)
    3. Standard naming conventions are followed
      1. Use t for single type in module definition
      2. Use is_ prefix for boolean testing functions
    4. Naming conventions are consistent
    5. Names of ephemeral variables are short
    6. No magic numbers
    7. No gratuitous definitions; all defined variables are used
    8. Use defined types rather than built-in types for documentation and information-hiding
    9. Top-level value declarations explicitly constrained by types
    10. Functions are generalized where appropriate ('a -> 'a, rather than int -> int, e.g.)
    11. Functions are specific where appropriate (Module.t list -> int, rather than 'a list -> int, e.g.)
    12. Avoid global mutable variables
    13. No gratuitous renaming of variables
    14. Rename widely used long variables to shorten (especially module names)
    15. Standard ordering of declarations (types before exceptions before values) followed
  6. Implementation choices

    1. Avoid side effects unless there is a good and well documented reason
      1. Avoid imperative constructs where possible
      2. Use structural comparison operators (= and <>) unless physical comparisons (== and !=) are truly required
    2. Select appropriately between using option types and exceptions for handling anomolous conditions
    3. Select appropriately between using modules and classes
    4. Functions should be curried unless there is a good and well documented reason
    5. Argument order of curried functions should take into account likely partial applications
    6. Avoid deprecated functions and operators (&, or, e.g.)
  7. Pattern matching

    1. Avoid nested pattern matches
    2. Avoid explicit pattern match with a single pattern (tuples, e.g.)
    3. Pattern matches are exhaustive
    4. No catch-all pattern matches
    5. Pattern match in function declaration where possible
    6. Use pattern-matching rather than complex conditionals
    7. Avoid match expressions that duplicate other constructs (simple conditionals, e.g.)
    8. Extract components with pattern matching instead of projection functions (fst, snd, hd, tl, e.g.)
    9. First of multiple matches preceded by |
    10. Sole match not preceded by |
  8. Formatting

    1. Consistent formatting used throughout
    2. Blank lines emphasize structure
    3. Alignment emphasizes parallelism
    4. Indentation reflects code structure
    5. Spacing conventions are consistent
    6. No tab characters (use spaces instead for uniformity across devices)
    7. No overly long lines (over 80 characters)
    8. No needless blank lines
    9. No excessive parenthesization
    10. Add parentheses for clarity
    11. Operators surrounded by space
    12. Delimiters followed by space
    13. Line breaks only before operators
    14. Line breaks only after delimiters
    15. Standard formatting used in compound constructs (let, if, while, etc.)