Coding Guidelines#
Note
The authoritative coding standards live in CLAUDE.md at the repository root.
This page summarises the key rules for quick reference.
General Principles#
Focus on readability when writing code: readable code is easier to understand, thus easier to maintain, modify, and debug. Write code that reads from left-to-right and from top-to-bottom.
To improve maintainability (see “Building Maintainable Software” by Joost Visser):
write short units of code
write simple units of code
write code once
keep unit interfaces small
separate concerns in modules
automate tests
write clean code
write all possible scenarios of a case statement
Imports#
Use qualified imports for everything except:
Types (e.g.
Entity (..),Key,Handler,DB)Lens operators (e.g.
(^.),(&),(.~),(?~))Language extensions and pragmas
import ClassyPrelude
import ClassyPrelude.Yesod (Entity (..), Key)
import qualified ClassyPrelude.Yesod as Yesod
import Control.Lens ((^.), (&), (.~), (?~))
import qualified Data.Aeson as Aeson
import qualified Data.Text as Text
Models, Lenses, and Record Access#
Use lenses to get and set record fields. Do not use RecordDotPreprocessor
or RecordWildCards; use lens operators instead.
When using lenses: prefer operators over functions (e.g. ^. over view),
except when partially applying lens expressions (e.g. fmap (view userName)).
Use lenses inline — do not assign them to let or where bindings.
-- Good
langCode = domain ^. langCode
-- Bad (RecordDotPreprocessor)
langCode = domain.langCode
Naming#
Abbreviations#
variable name |
meaning |
|---|---|
|
entity |
|
key |
|
lang code |
|
property |
|
value |
|
vacancy |
|
vacancy filter |
|
vacancy filter option |
Prefixes#
prefix |
meaning |
|---|---|
|
this is a |
|
unsafe function, i.e. function might throw an exception |
Postfixes#
postfix |
meaning |
|---|---|
|
persistent entity |
|
persistent key |
Variable Naming for Key and Entity Types#
Use the pattern {firstLetter}{TypeIndicator}:
type |
variable name |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
Prefer generic Key types over specific ID types (e.g. Key User instead of UserId),
except in Persistent model definitions where the generated types are required.
Database Indices#
Use the naming scheme $table_$column_idx, e.g. an index for the
version_id column on the component table must have the name
component_version_id_idx.
Nested Case and If Expressions#
Avoid nested case and if expressions by defining additional functions, using monads and monad transformers.
Operators#
$Do not use
$, use parentheses.<|/|>/.>/<.Do not use Flow operators, use parentheses or
>>=chains.=<<Do not use
=<<, use>>=.
Style Preferences#
whereoverletUse
whereclauses for local definitions instead ofletexpressions.- Minimal
dosyntax Only use
dowhen binds are not readable or when arguments are needed much later in the bind chain.
Prefer Complete Entities#
For consistency pass and receive complete entities instead of just entity keys or just entity values (if possible). Thus prefer:
optionInputValue :: Entity VacancyFilterOption -> Text
optionInputValue vfoE =
toPathPiece (Yesod.entityKey vfoE)
over:
optionInputValue :: Key VacancyFilterOption -> Text
optionInputValue vfoK =
toPathPiece vfoK