# Sane Code
*version 1*
- * prefer single imperative block of code in one file that is readable from top to bottom
- * variables serve following purposes (otherwise prefer inlining values in the program source code):
- * avoiding repetition: storing a value that would otherwise be repeated multiple times in the source code
- * value changes over time
- * temporary value storage for data restructuring to be immediately passed into the next chunk of code
- * what variables are not:
- * variables must not be used purely for aesthetically naming values -- use comments instead
- * variables must not be used for storing one-off values that are discarded -- chain functions instead
- * functions serve following purposes (otherwise prefer inlining code in the single block):
- * avoiding repetition: reusing code that would otherwise be repeated multiple times
- * segregation of the enclosed logical units for further testing
- * what functions are not:
- * do not use functions for aesthetic code formatting -- prefer linear block of code instead
- * avoid functions that are only called from 1 place -- prefer linear block of code instead
- * prefer pure functions (output of the function only depends on its inputs)
- * function and variable names must name the purpose as directly as possible, unless variable is technical one-off (e.g.
`i`,`x`,`y`,`n`, ...) - * function parameters must be ordered from most stable to least stable
- * function return values must represent all possible outcomes explicitly (use result types, not exceptions for expected errors)
- * function names must indicate side effects (prefer
`get`for pure functions,`set`/`update`for mutating functions) - * separating configuration file from source code file serves following purposes
- * to have single file for referencing values that would otherwise be repeated multiple times in the source code
- * to allow program behaviour change without program recompilation (in case of compiled program)
- * to allow program behaviour change without program restart (in case of long-running process)
- * avoid creating scopes, closures -- prefer storing value in broader scope or directly passing data as function argument
- * program may use define code or variable globally when items are expected to be referenced throughout all files, examples:
- * configuration
- * logging
- * unit testing
- * separating source code file into multiple files serves following purpose:
- * to reduce length of a single file, consider file splitting at around 1000 lines, in which case separate out by largest logically independent area
- * prefer organizing code by logical domain/functionality over technical layers
- * assume that code is self-explanatory, add comments to serve following purpose:
- * to document algorithm intent
- * to document unobvious external factors
- * code explains what algorithm does on most precise level, comments explain what algorithm does 'outside of the box'
- * where code uses uncommon techniques, add comments to prevent novices from getting confused
- * add comments to all places where code purposefully uses gotchas (for example, deliberately not using
`await`on an async function to schedule it in background execution) - * there are no limits on function length
- * prefer least nesting depth
- * prefer early returns
- * use all advanced control flow tools that language natively provides (labels,
`goto`,`break`/`continue`,`switch`/`case`fall-through,`try`/`continue`/`finally`,`yield`,`defer`, etc..) - * prefer explicit loops (for of...continue/break) when functional style (map/filter/reduce) would not significantly reduce complexity
- * prefer in-place modification when original data is not needed
- * avoid borderline undefined edge semantics (e. g.
`!!`,`!~indexOf(...)`,`+""`,`x << 0`) - prefer explicit statements that reflect intent - * avoid altering (overloading) native standard library objects and functions (semantic hijacking)
- * avoid unambiguity that can be caused by macros and overloads (prefer glanceable code) unless line count can be significantly reduced
- * error handling serves following purposes:
- * prefer bubbling errors up to the highest logical level that will catch errors
- * prefer bubbling system, library, built-in errors up
- * while program is starting up: prefer early fail if anything is wrong during initialization
- * while program is already running: prefer best effort recovery from all possible errors while logging errors, program should stay up
- * input checking, validation must be performed at the receiving function, there should not be value validation within own code, trust own code within the program
- * always explicitely validate data from external sources (field existance, field not empty/null/undefined, data type, data length)
- * prefer implementing all code within the program instead of dependencies
- * dependencies serve following purposes:
- * to import complex implementations that can't be sanely implemented yourself
- * when adding dependencies, consider dependencies of dependencies, prefer least dependency depth possible
- * always prefer least-complexity implementation of best-in-class most efficient algorithms for a given task but without bloating the scope
- * purpose of abstractions is reducing amount of code needed
- * if abstraction such as object would not reduce total lines of code to implement same behaviour, abstraction is not worth it
- * code duplication serves following purposes:
- * when abstraction would increase total lines of code -- prefer inlining simple and reproducible boilerplate
- * when abstraction would obscure algorithm intent -- prefer inlining generic code
- * refactor code when:
- * refactoring would reduce total lines of code or improve readability without changing behaviour
- * refactoring would clean up/remove code that is no longer needed/relevant leftover from the past
- * avoid runtime dynamic allocations such as dynamically creating objects, functions, closures in runtime
- * prefer object pooling to garbage collection
- * prefer statically assigned array and tree lengths/sizes to dynamic allocation (resize in chunks if needed instead of frequent/constant resizes)
- * when it would not significantly increase code size and complexity prefer efficient types, examples: time as integer, pack binary flags into bits of one value, pack multiple small ints into one large int, enums instead of strings
- * implement unit testing and prefer test-driven approach (when tests are changed to represent intended behavior, then code is changed to pass the tests)
- * testing serves following purposes:
- * to verify that program produces expected output for given input
- * to document expected program behaviour through executable examples
- * test following scenarios:
- * program input validation at receiving functions
- * error handling paths
- * edge cases and boundary conditions
- * tests must aim to test each edge branch of execution using as few test cases as possible
- * do not test:
- * implementation details that do not affect program output
- * third-party library functionality
- * trivial code that is self-evidently correct
- * prefer static type systems when available
- * prefer most concrete types over generic types (avoid over-abstracting types)
- * purpose of concurrency is load distribution for CPU-heavy tasks
- * prefer sequential execution when possible, concurrency adds complexity and potential race conditions
- * when concurrency is required, prefer shared mutable state over IPC
- * threads must read only shared state by default, use binary mutual exclusion mutex lock for write permission
- * resources must be released in reverse order of acquisition
- * use global program state (side effects) for storage needed for implementation of core program functionality
- * use side effects only when necessary for core program functionality/purpose
- * when side effects are required, make them explicit in function name and documentation
- * prefer stateless design, delegate storage to purpose-built database, unless program is explicitly supposed to handle data storage
- * logging serves following purposes:
- * to record important program decisions (major behaviours) for monitoring program behaviour in production
- * to record errors with full context (input values, stack trace, system state)
- * external system interactions (network requests, file operations)
- * prefer all input and output versioning (examples:
`version`field in configs,`/v1/`prefix for APIs, version number in binary types, etc...) - * prefer maintaining backwards compatibility when it would not involve significant code bloat
- * when breaking changes are necessary:
- * if backwards compatibility is reasonable (would not involve significant code structure bloat), prefer converting old input types into new input types before processing over separate functions
- * if backwards compatibility is not reasonable (would involve significant code structure bloat), increment supported version, validate and throw error on old versions
- * program is an algorithm that turns input into output
- * technical documentation should describe:
- * expected inputs
- * how program is expected to process data (logging, algorithm, error handling)
- * expected outputs
- * pre-existing technical conditions (OS, arch, language, compiler/interpreter, ...)
- * pre-existing technical decisions about the implementation (libraries, APIs, ...)
- * algorithm is defined collectively by all things that define the way program processes data, including:
- * program code, either compiled or interpreted
- * configuration: 1.1. environment variables 1.2. command line arguments 1.3. configuration file(s)
- * external dependencies: 2.1. operating system 2.2. libraries
- * program input and output usually fits into two general areas
- * set data (one-off programs, e. g.
`sort`,`sed`,`grep`,`uniq`, etc...) - * events (long-running programs, e. g.
`web server`, etc...)
- * program input and/or output can be a combination of set data and events