Code style
Rules for pencilmark's Python and C++. Each names its source. Docs have their own page: writing style.
Where code goes
- 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.
- Solver logic in C++, interface in Python. Python parses, formats, explains and orchestrates; anything that searches a grid belongs in the engine.
- One module, one job, named after it. No
extra,miscorutils. I/O functions only do I/O; logic takes and returns plain data (Scientific Python: design). - Prefer functions over classes; group values in a dataclass when they travel together (Scientific Python: design).
- 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).
- src layout; tests run against the installed package (Packaging guide).
Names
- Python follows PEP 8:
lower_with_undermodules and functions,CapWordsclasses,UPPER_CASEconstants, one leading underscore for private names (PEP 8). - C++ follows the engine's existing convention:
CamelCasetypes,snake_casefunctions and variables,UPPER_CASEenum values and constants. - Descriptive over short. Abbreviations only from the glossary (
r1c1, chip codes such asXWG,Dfor the digit count) (Google Python: naming).
Functions
- Small and focused: past about 40 lines, look for a split that keeps the structure clear (Google Python).
- Same kind of return value for every input; options are keyword-only (after
*); no**kwargsthat hide what a function accepts (Scientific Python: design). - Type hints on the public API,
X | Nonerather thanOptional[X](Google Python).
Errors
- The library raises; it never prints. The CLI catches and reports (Scientific Python: design).
- 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). - Built-in exceptions where they fit; new ones subclass one. No bare
except:;assertonly for things that cannot happen, never for checking input (Google Python).
Comments and docstrings
- Comment why, not what. Fix a comment with the code it describes (Google C++, PEP 8).
- Every calibrated number says where it came from: data, fit, date.
# 1.23 per point: fed + Wang human times, 2026-10-29. - Every file opens with what it holds and where it sits (PEP 257).
- numpydoc on the public API (imperative one-line summary, then Parameters / Returns / Raises / Examples); a one-line summary on everything else (numpydoc).
- C++ documents at the declaration (header): purpose, inputs, outputs, ownership. Bindings carry
py::argnames and one docstring (pybind11).
Tests
- Outside in: public API first, then integration, then units; one behaviour per test (Scientific Python: testing).
- 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).
- The golden corpus guards every refactor:
python tools/golden.py checkmust report every record identical; a deliberate change of output rewrites it in its own commit with the reason. - Check that a new test can fail: break the code once and watch it go red (Scientific Python: testing).
- Extension points have a contract, a worked example and a test that fails until every part exists (scikit-learn).
Tools
- 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).
- Measure speed before and after any change meant to be faster, on the same machine and puzzles.