Code-Review Standards
Code-Review Standards
Cross-cutting standards codified during Phases 19 and 20. These supplement Coding Rules (formatting, naming, header conventions) with behavioural standards that reviewers should enforce.
C++ style
Constructor-sink ownership
Pass owning collections by value and std::move them into fields.
// GoodPart(juce::Uuid id, std::vector<Measure> measures) : id_(id), measures_(std::move(measures)) {}
// Bad — caller's vector is left in an unspecified statePart(juce::Uuid id, std::vector<Measure>&& measures) : id_(id), measures_(measures) {}Private-by-default class members
Class members are private unless a deliberate public-field decision is
documented (see IntermediatePart for the parser-internal IR exception).
Getters return const& for non-trivial types, by value for int / bool /
small enums.
Reference > pointer > value
Function parameters prefer const T& for non-owning input, T* when nullable
or representing weak ownership, and T by value only for small / cheap-to-
copy types. Never pass T& for mutable output — return-by-value with a
struct (see “Out-parameter purge” below).
Out-parameter purge
Functions that need to return multiple values return a struct, not multiple non-const reference parameters. Phase 19 examples:
// Good — DecodedSlideFlags is a translation-unit-local return structDecodedSlideFlags decodeSlideFlags(int flags);
// Bad — caller has to allocate three uninitialised varsvoid applySlideFlags(int flags, TechniqueSet&, bool&, bool&);The accumulator-style “walk a container and add to a set” pattern (e.g.
collectTechniquesFromContainer(node, TechniqueSet&)) is the only acceptable
exception — it mirrors std::ranges output-iterator conventions.
File-local transfer structs
Parser-internal types like GpNoteProps, GpBeatProps, ParsedScorePart
stay as anonymous-namespace structs with public fields, not classes with
private fields + getters. Document the “build then read as const” invariant
in a header comment so the pattern is clear.
Public-header documentation
Every public type, function, and non-obvious invariant gets Doxygen comments
with @brief, @param, @return. Reference standard:
src/public/midi/MidiWriter.h.
Skip Doxygen for:
- File-local helpers (anonymous-namespace functions and structs).
- Trivial getters where the field name is self-documenting.
Tests
No tests reaching into private state
Tests assert via the public API. Measure::notes / Measure::spans are
public fields by design — walking them is the public API for spans.
Engraving::techniques_ is private — use getTechniques() instead.
No construct-then-getter-equals-constructor-arg tests
// Bad — tests the language, not the codeconst Part p(uuid, "Lead", "Guitar", 1, 25, 0, measures, {}, {}, tuning);CHECK(p.getTuningMidiPitches() == tuning);
// Good — tests behaviour that's observable through real code pathsconst auto body = readFixture("staff-details-tuning.xml");const auto result = MusicXmlParser::parse(std::string_view(body));REQUIRE(result.ok());CHECK(result.score->getParts()[0].getTuningMidiPitches().size() == 6);Shared test support
tests/support/Builders.h and tests/support/XmlFixtures.h host the shared
builders and fixture loader. Add to them rather than duplicating per file —
seven copies of makeNote is the symptom that drove the refactor.
Inline XML → fixture files
Multi-line XML literals belong in tests/fixtures/*.xml, loaded via
readFixture. Single-element snippets passed to a builder function are OK to
keep inline.
Property tests for stochastic code
The humanizer is seeded — but tests assert properties (mean within sigma, floor / ceiling honoured, same seed → identical output) rather than pinning specific byte sequences. New stochastic features should follow the same pattern.
Commit hygiene
- Subject line ≤ 50 chars, body wrapped at 80 (see
.claude/rules/commit- messages.md). - Each phase’s incremental commits squash to one commit at the phase boundary
via
git reset --soft HEAD~N && git commit. - Never
git pushwithout explicit user instruction. - Never
--no-verifyto bypass a failing hook — fix the underlying issue.
Cross-platform
New file IO goes through Util::getPersistenceRoot() — never hardcode
~/Library/Application Support or %AppData%. The Windows path is
portable-mode (exe parent dir); the macOS path is OS-conventional.
Licence
Project licence is CC BY-NC-ND 4.0 today; a custom licence will replace it in the future. Never embed specific licence names in code comments, docs, or commit messages — refer to “the project’s licence” so the prose stays accurate across future licence changes.