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.
Structure
- Keep functions short, breaking them up at the joints
- Reuse code where possible
- Use library functions where possible
- Scope values narrowly
- Keep scopes of conditionals narrow
- Modules are constrained by signatures
- Use functional primitives (
map
,fold
,filter
) where applicable - Use partial application where possible
- Use reverse application (
|>
) to clarify deeply embedded calls
Documentation and commenting
- Comments answer “why” rather than “how”
- Comments match the code
- No redundant comments (merely restating the code)
- Nonobvious code bits are commented
- Consistent multi-line comment style
- Comment lines precede lines they describe
- Same-line comments follow what they describe
- Sufficient header documentation at file level
- Top-level declarations are commented
- Top-level declaration documentation conventions are consistent
Testing and debugging
- Code compiles cleanly without any warnings
- Unit tests provided
- Unit tests cover all edge cases
- Unit tests cover all execution paths, where possible
- Unit tests in separate file
Verbosity
- Eliminate redundant code
- Eliminate dead code
- Use library operators rather than prefix equivalents (
@
rather thanList.append
, e.g.) - Use logical operators rather than conditional expressions
- Don’t rewrap functions (
sqrt
rather thanfun x -> sqrt x
,(+)
rather thanfun x y -> x + y
, e.g.) - Factor recomputed values
- Use compact notations (
[x]
rather thanx :: []
,condition
rather thancondition = true
, e.g.) - Use module prefixes or local opens rather than opening module unless module is very broadly deployed
Naming and declarations
- Names are meaningful and specific
- Standard name structure conventions are followed
(
cCONSTANTS
,variable_names
,Constructors
,MODULE_TYPES
,ModuleDefns
,FunctorNames
) - Standard naming conventions are followed
- Use
t
for single type in module definition - Use
is_
prefix for boolean testing functions
- Use
- Naming conventions are consistent
- Names of ephemeral variables are short
- No magic numbers
- No gratuitous definitions; all defined variables are used
- Use defined types rather than built-in types for documentation and information-hiding
- Top-level value declarations explicitly constrained by types
- Functions are generalized where appropriate (
'a -> 'a
, rather thanint -> int
, e.g.) - Functions are specific where appropriate (
Module.t list -> int
, rather than'a list -> int
, e.g.) - Avoid global mutable variables
- No gratuitous renaming of variables
- Rename widely used long variables to shorten (especially module names)
- Standard ordering of declarations (types before exceptions before values) followed
Implementation choices
- Avoid side effects unless there is a good and well documented reason
- Avoid imperative constructs where possible
- Use structural comparison operators (
=
and<>
) unless physical comparisons (==
and!=
) are truly required
- Select appropriately between using option types and exceptions for handling anomolous conditions
- Select appropriately between using modules and classes
- Functions should be curried unless there is a good and well documented reason
- Argument order of curried functions should take into account likely partial applications
- Avoid deprecated functions and operators (
&
,or
, e.g.)
- Avoid side effects unless there is a good and well documented reason
Pattern matching
- Avoid nested pattern matches
- Avoid explicit pattern match with a single pattern (tuples, e.g.)
- Pattern matches are exhaustive
- No catch-all pattern matches
- Pattern match in function declaration where possible
- Use pattern-matching rather than complex conditionals
- Avoid match expressions that duplicate other constructs (simple conditionals, e.g.)
- Extract components with pattern matching instead of projection functions (
fst
,snd
,hd
,tl
, e.g.) - First of multiple matches preceded by
|
- Sole match not preceded by
|
Formatting
- Consistent formatting used throughout
- Blank lines emphasize structure
- Alignment emphasizes parallelism
- Indentation reflects code structure
- Spacing conventions are consistent
- No tab characters (use spaces instead for uniformity across devices)
- No overly long lines (over 80 characters)
- No needless blank lines
- No excessive parenthesization
- Add parentheses for clarity
- Operators surrounded by space
- Delimiters followed by space
- Line breaks only before operators
- Line breaks only after delimiters
- Standard formatting used in compound constructs (
let
,if
,while
, etc.)