Find and remove dead code and unused APIs in OCaml projects
at main 216 lines 8.7 kB view raw view rendered
1# Coding guidelines 2 3## 1 Overall philosophy 4 5* Strive for **small, orthogonal modules** that can be composed freely. 6* Depend only on the standard library (and occasionally on other “micro-libs”, e.g. `Fmt`, `Rresult`). 7* Preserve **purity** and determinism in the core; confine effects to thin I/O layers. 8* Offer **total functions** whenever feasible and expose *explicit* failure via `'a result`. 9* Maintain **API stability**: once an identifier is public, avoid breaking changes; add instead of mutate. 10 11--- 12 13## 2 Project layout 14 15``` 16my_lib/ 17├─ dune-project 18├─ README.md – high-level overview & minimal example 19├─ CHANGES.md – human-written change-log 20├─ lib/ – library source code (alternative to src/) 21│ ├─ my_lib.mli – canonical public interface 22│ └─ my_lib.ml – implementation 23├─ bin/ – executable entry points 24└─ test/ – Alcotest or inline-tests 25``` 26 27*Put the public interface in a single `.mli` whenever practical. Split only when the conceptual surface really warrants distinct compilation units. Using `lib/` and `bin/` directories instead of `src/` is acceptable for projects with both libraries and executables.* 28 29--- 30 31## 3 Modules and sub-modules 32 33| Purpose | Idiom | 34| ---------------- | ------------------------------------------------------------------------------ | 35| Canonical type | `type t` (abstract) | 36| Pretty printer | `val pp : t Fmt.t` | 37| Conversions | `val to_string : t -> string`<br>`val of_string : string -> (t, error) result` (when meaningful) | 38| Equality & order | `val equal : t -> t -> bool`<br>`val compare : t -> t -> int` (when meaningful) | 39| Constructors | `val v : ... -> (t, error) result` (or pure `t` if infallible) | 40| Low-level view | `module Unsafe : sig … end` (optional) | 41 42*Keep the root module *flat*; introduce nested modules only for clearly separable concerns (e.g. `My_lib.Path`, `My_lib.Map`).* 43 44--- 45 46## 4 Naming conventions 47 48* **Modules / files** – lower-case, underscore-separated file names (`my_lib.ml`), capitalised module names (`My_lib`). 49* **Values** – short but descriptive (`pp`, `v`, `find`, `with_…`). 50* **Labels** – prefer them only when they disambiguate (`~dir`, `~mode`); avoid gratuitous labels on simple data. 51* **Boolean arguments** – almost never positional; wrap in a variant or use a labelled argument (`?dry_run:bool`). 52* **Infix operators** – expose only if widespread (`>|=`) or central to the library (`Fpath.( / )`). Put them in a dedicated sub-module `Op`. 53 54--- 55 56## 5 Types 57 58* Keep *representation hidden* (`type t`) and provide construction/destruction helpers. 59* Introduce **phantom parameters** to encode invariants if it avoids run-time checks. 60* Use **private types** when callers may inspect but not forge values (`type t = private string`). 61* For **enumerations**, use a closed variant; supply `val all : t list` and `val to_string`. 62* Reserve **exceptions** for programming errors (`Invalid_argument`, assertion failures). Operational errors travel in the `'error` part of `('a, error) result`. 63* **Formatting**: Use `Fmt` consistently for all output. Never mix `Printf` and `Fmt` - use `Fmt.pr` for user-facing messages, keep `Printf.printf` only for TTY progress display. 64* **Regular expressions**: Use the Re DSL instead of Re.Perl. Prefer `Re.(compile (seq [...]))` over `Re.Perl.compile_pat`. 65 66--- 67 68## 6 Error handling pattern (`Rresult`) 69 70### Base Error Types 71 72```ocaml 73type error = [ `Msg of string | `Build_error of context ] 74val pp_error : error Fmt.t 75val v : ... -> (t, error) result 76``` 77 78### Error Helper Functions Pattern 79 80Start implementation files with error helper functions (`err_*`) for consistent error messages: 81 82```ocaml 83(* Error helper functions *) 84let err fmt = Fmt.kstr (fun e -> Error (`Msg e)) fmt 85 86let err_file_read file msg = err "Failed to read %s: %s" file msg 87let err_file_write file msg = err "Failed to write %s: %s" file msg 88``` 89 90Use `%a` with pretty printers for complex formatting: 91```ocaml 92let pp_build_error ppf ctx = Fmt.pf ppf "%s" ctx.output 93let err_build_failed ctx = err "Build failed:@.%a" pp_build_error ctx 94``` 95 96For structured errors with context: 97```ocaml 98let err_build_error ctx = Error (`Build_error ctx) 99``` 100 101### Library vs Main Separation 102 103**Critical Rule**: Library code never calls `exit` directly - only returns errors for main.ml to handle. 104 105```ocaml 106(* In library code - ALWAYS return structured errors *) 107let handle_build_failure ctx = 108 (* Display specific context *) 109 Fmt.pr "Loop detected - stopping to prevent infinite iterations.@."; 110 (* Return error for main.ml to handle *) 111 err_build_error ctx 112 113(* In main.ml - handle all exit logic *) 114match analyze mode root_dir files with 115| Ok result -> result 116| Error (`Build_error ctx) -> 117 System.display_build_failure_and_exit ctx (* Exits with build's code *) 118| Error e -> 119 Format.eprintf "Error: %a@." pp_error e; 120 exit 1 (* Generic error code *) 121``` 122 123### Error Display Consistency 124 125Use unified display functions for consistent UX: 126```ocaml 127(* In system.ml *) 128let display_build_failure_and_exit ctx = 129 let all_warnings = (* parse build output *) in 130 Fmt.pr "%a with %d %s - full output:@." 131 Fmt.(styled (`Fg `Red) string) "Build failed" 132 (List.length all_warnings) 133 (if List.length all_warnings = 1 then "error" else "errors"); 134 Fmt.pr "%a@." pp_build_output ctx; 135 let exit_code = get_build_exit_code ctx in 136 exit exit_code 137``` 138 139### Guidelines 140 141* Accept a caller-provided buffer (`Buffer.t`) or formatter when the operation might produce extensive diagnostics. 142* Re-use `Fmt.failwith`/`Fmt.invalid_arg` for internal invariants; never expose raw `Printexc` traces. 143* **Never mix exit logic in library code** - always return structured errors that main.ml can handle appropriately. 144 145--- 146 147## 7 Public API surface 148 1491. **Minimal** yet **composable**. Do not wrap the entire Unix API—wrap just enough to remove boilerplate. 1502. Expose **first-class values** rather than functors where a closure suffices. 1513. Keep *effectful* helpers separate (`My_lib_unix`, `My_lib_lwt`) to avoid dragging unwanted dependencies. 152 153--- 154 155## 8 Interface documentation (`.mli`, `.mld`) 156 157*Document **every** public item in the `.mli`. Place docstrings **after** the item unless it spans multiple lines, using code-first voice.* 158 159### 8.1 Structure and style 160 161``` 162(** {1 Overview} 163 164 One concise paragraph explaining the abstraction. Link to the README 165 for a tutorial. 166 167 {2 Types} 168 169 {3 Constructors} 170 171 {4 Accessors} 172 173 {5 Derived combinators} 174 175 {6 Pretty-printing} 176 177*) 178``` 179 180* Use **section headings** (`{1 …}`, `{2 …}`) to group logically related functions. 181* Provide **usage examples** in `[{[ … ]}]` blocks that compile under `odoc-latency-level:normal`. 182* Annotate complexity (`@since`, `@deprecated`, `@raise`, `@see`) rigorously. 183* Keep comments declarative: *what* and *guarantees*, never implementation detail. 184 185--- 186 187## 9 Testing & auxiliary artefacts 188 189* Unit tests with **Alcotest** (`test/`) mirroring the public API sections. 190* **Cram tests**: Use directory-based cram tests (`test/name.t/`) instead of inline `cat EOF` commands for complex file creation. This is cleaner and easier to maintain. 191* Provide a `tool/` directory of executable samples that double as integration tests. 192* Ship an `odig` metadata file so `odig odoc` builds HTML docs out-of-the-box. 193 194--- 195 196## 10 Versioning & release process 197 198* Tag releases with `vN.N.N`; follow semantic versioning. 199* Maintain `CHANGES.md` with *user-visible* entries only. 200* Publish through `topkg` or Dune’s built-in release workflow; push docs to `gh-pages` via `odoc`. 201 202--- 203 204## 11 Style Checklist 205 206| Step | Done | 207| ----------------------------------------------------------- | ---- | 208| `src/<lib>.mli` written first, fully documented | ☐ | 209| All errors returned as `'a result` with `Rresult` helpers | ☐ | 210| `pp`, `equal`, `compare`, `hash` supplied where meaningful | ☐ | 211| No hidden global side-effects; pure core separated | ☐ | 212| Example snippets compile under `dune build @doc` | ☐ | 213| Dependency list reviewed: stdlib ± {Fmt, Rresult, Alcotest} | ☐ | 214| `test/` provides Alcotest suites mirroring API sections | ☐ | 215 216Tick every box before publishing.