Skip to main content

Code style

Rules for pencilmark's Python and C++. Each names its source. Docs have their own page: writing style.

Where code goes​

  1. One fact, one place. A technique's code, name, band and order, or a rule family's keywords, live in one registry that everything else reads. A second copy is a bug waiting to happen.
  2. Solver logic in C++, interface in Python. Python parses, formats, explains and orchestrates; anything that searches a grid belongs in the engine.
  3. One module, one job, named after it. No extra, misc or utils. I/O functions only do I/O; logic takes and returns plain data (Scientific Python: design).
  4. Prefer functions over classes; group values in a dataclass when they travel together (Scientific Python: design).
  5. No hidden state. Results come back as return values, never through module globals; a cache is private, keyed on its inputs and documented (Google Python: global state).
  6. src layout; tests run against the installed package (Packaging guide).

Names​

  1. Python follows PEP 8: lower_with_under modules and functions, CapWords classes, UPPER_CASE constants, one leading underscore for private names (PEP 8).
  2. C++ follows the engine's existing convention: CamelCase types, snake_case functions and variables, UPPER_CASE enum values and constants.
  3. Descriptive over short. Abbreviations only from the glossary (r1c1, chip codes such as XWG, D for the digit count) (Google Python: naming).

Functions​

  1. Small and focused: past about 40 lines, look for a split that keeps the structure clear (Google Python).
  2. Same kind of return value for every input; options are keyword-only (after *); no **kwargs that hide what a function accepts (Scientific Python: design).
  3. Type hints on the public API, X | None rather than Optional[X] (Google Python).

Errors​

  1. The library raises; it never prints. The CLI catches and reports (Scientific Python: design).
  2. Messages say what was wrong, with which value, and how to fix it: givens has 3 entries; a 9x9 puzzle needs 81 (Scientific Python: design).
  3. Built-in exceptions where they fit; new ones subclass one. No bare except:; assert only for things that cannot happen, never for checking input (Google Python).

Comments and docstrings​

  1. Comment why, not what. Fix a comment with the code it describes (Google C++, PEP 8).
  2. Every calibrated number says where it came from: data, fit, date. # 1.23 per point: fed + Wang human times, 2026-10-29.
  3. Every file opens with what it holds and where it sits (PEP 257).
  4. numpydoc on the public API (imperative one-line summary, then Parameters / Returns / Raises / Examples); a one-line summary on everything else (numpydoc).
  5. C++ documents at the declaration (header): purpose, inputs, outputs, ownership. Bindings carry py::arg names and one docstring (pybind11).

Tests​

  1. Outside in: public API first, then integration, then units; one behaviour per test (Scientific Python: testing).
  2. Fast by default, slow on request: the unit suite runs in seconds; slow suites carry a pytest marker and run separately (pytest good practices, Scientific Python: pytest).
  3. The golden corpus guards every refactor: python tools/golden.py check must report every record identical; a deliberate change of output rewrites it in its own commit with the reason.
  4. Check that a new test can fail: break the code once and watch it go red (Scientific Python: testing).
  5. Extension points have a contract, a worked example and a test that fails until every part exists (scikit-learn).

Tools​

  1. ruff lints and formats Python (line length 120); clang-format formats C++. Formatting is never mixed with logic changes in one commit (ruff, clang-format).
  2. Measure speed before and after any change meant to be faster, on the same machine and puzzles.