Distilled from 8,484 inline review comments across 1,457 pull requests spanning 2017–2026. These are patterns that ITK's core reviewers flag repeatedly; an AI assistant that avoids them will produce PRs that pass review with fewer round-trips.
Flagged on 31% of reviewed PRs (453/1,457). Active 2018–2026.
When you remove the last user of a helper function, a local variable,
an #include, or a type alias — also remove the definition itself.
Reviewers catch orphaned code more than any other single issue.
// BAD — removed the std::cout print that used chBuffer,
// but left the clGetPlatformInfo() call that populated it.
clGetPlatformInfo(platform, CL_PLATFORM_NAME, sizeof(chBuffer), chBuffer, nullptr);
// GOOD — removed both the consumer AND the producer.Checklist before submitting a refactoring PR:
- Grep for every symbol you modified or deleted.
- If a helper, typedef, or include has zero remaining callers, remove it.
- If a
usingalias was only public for one consumer, move it toprivateor remove it entirely.
Flagged on 28% of reviewed PRs (411/1,457). Active 2017–2026.
// BAD — "HeavisideStepFunction" reused across multiple test files:
TEST(HeavisideStepFunction, ConvertedLegacyTest) // in file A
TEST(HeavisideStepFunction, AnotherTest) // in file B
// GOOD — one unique suite name per .cxx file:
TEST(SinRegularizedHeavisideStepFunction, ConvertedLegacyTest)When converting a legacy itkFooTest.cxx to GTest, name the test
TEST(Foo, ConvertedLegacyTest) unless the test has a more specific
purpose worth naming.
ITK_TEST_EXPECT_TRUE is non-fatal — it records failure but continues.
If a dynamic_cast might return null, guard before dereferencing:
// BAD — continues past null and crashes:
auto * p = dynamic_cast<Derived *>(base.GetPointer());
ITK_TEST_EXPECT_TRUE(p != nullptr);
p->DoSomething(); // CRASH if dynamic_cast failed
// GOOD — bail immediately on null:
auto * p = dynamic_cast<Derived *>(base.GetPointer());
if (p == nullptr)
{
std::cerr << "dynamic_cast failed" << std::endl;
return EXIT_FAILURE;
}Flagged on 21% of reviewed PRs (313/1,457). Active 2018–2026.
- Include only what you use. Do not leave includes for removed code.
- Prefer forward declarations in headers when only a pointer or reference is needed.
- After removing code, check whether any
#includedirectives became orphaned.
Flagged on 17% of reviewed PRs (254/1,457). Active 2018–2026.
- Variable names should describe what they hold, not how they were computed.
- After applying a limit or filter, rename the variable to reflect its
new meaning (e.g.,
nodes→displayed_nodesafter truncation). - Magic numbers should be named constants or use ITK's existing named
constants (e.g.,
itk::Statistics::MersenneTwisterRandomVariateGenerator::DefaultSeedinstead of121212).
Flagged on 15% of reviewed PRs (214/1,457). Active 2018–2026.
After a partial fix, check that the modified code is consistent with the rest of the function and file:
- If a function parameter type was updated, check that the function body uses the same qualified form.
- If a naming pattern was corrected, apply the correction to all similar instances in the same scope — do not leave a mix.
- Sub-section numbering, comment formatting, and JSON keys should be consistent within and across files.
Flagged on 14% of reviewed PRs (201/1,457). Active 2018–2026.
- Check return values from functions that can fail (
dynamic_cast, Python C API calls, file I/O). Anullptror error return passed silently to the next line is a crash. - After removing cleanup code (e.g.,
delete m_Writer; m_Writer = nullptr;), verify the replacement (unique_ptr, RAII) provides equivalent exception-safety guarantees. EXPECT_NO_THROWin GTest should wrap only the call being tested, not surrounding side-effects likestd::cout.
Flagged on 8% of reviewed PRs (120/1,457). Active 2018–2026.
- Avoid narrowing conversions between signed and unsigned types.
Prefer ITK's
SizeValueTypeorunsigned intconsistently. - Do not add
static_castjust to silence a warning — ask whether the conversion is actually safe. Unnecessary casts obscure real bugs. - For template parameters, prefer
unsigned{VRows}overstatic_cast<unsigned int>(VRows)— it is type-safe and rejects narrowing.
Flagged on 8% of reviewed PRs (113/1,457). Active 2018–2026.
std::stod, std::stof, atof, and std::to_string are
locale-dependent. Under European locales they produce/consume ,
instead of . as the decimal separator, silently corrupting
medical image metadata.
// BAD — locale-dependent:
double value = std::stod(str);
buffer << std::fixed << value;
// GOOD — locale-safe:
buffer << itk::ConvertNumberToString(value);Use itk::ConvertNumberToString() for serialization.
See itk-locale-safe-migration for the full set of affected functions.
Flagged on 7% of reviewed PRs (105/1,457). Active 2018–2026.
ITK is not anti-auto, but reviewers reject it when the deduced type is
not obvious from the initializer.
// GOOD — type is obvious from the RHS:
const auto size = image->GetLargestPossibleRegion().GetSize();
auto filter = MedianImageFilter::New();
const auto it = container.begin();
// BAD — reader cannot guess the deduced type:
const auto value = interp->EvaluateDerivativeAtContinuousIndex(index);
// (value is a CovariantVector — not obvious)
// BETTER — spell out non-obvious types:
const CovariantVectorType value = interp->EvaluateDerivativeAtContinuousIndex(index);Rule of thumb: use auto when the type name appears on the same line
(factory methods, iterators, casts) or is unambiguous from the method name
(GetSize(), GetSpacing()). Spell out the type when the return type
requires knowledge of the class's internal typedefs.
Flagged on 4% of reviewed PRs (57/1,457). Active 2018–2026.
Prefer single-expression initialization over declare-then-mutate
patterns. This applies to Size, Index, Offset, and similar
aggregate types that provide Filled(), as well as Image::Allocate:
// BAD — two-line declare-then-fill:
SizeType size;
size.Fill(2);
// GOOD — single expression:
constexpr auto size = SizeType::Filled(2);
// BAD — zero-initialize in two lines:
IndexType start;
start.Fill(0);
// GOOD — brace initialization is zero-fill for aggregate types:
constexpr IndexType start{};
// BAD — boolean argument is unclear at the call site:
image->Allocate(true);
// GOOD — named method communicates intent:
image->AllocateInitialized();Flagged on 10 PRs (0.7%). Active 2019–2026. Every instance was a correctness concern.
// BAD:
unsigned int rows = (unsigned int)VRows;
// GOOD — prefer no cast; if needed:
unsigned int rows = unsigned{VRows}; // type-safe, rejects narrowing
unsigned int rows = static_cast<unsigned int>(VRows);Before adding any cast, ask: "Do I actually get a compiler warning without this?" If not, the cast is unnecessary and obscures the code.
When a cast exists because a local variable has a mismatched type, it is
often better to change the local variable's type to eliminate the cast
entirely. However, do not cascade type changes into function signatures,
template parameters, or public API boundaries to avoid a cast. A small
local static_cast or T{x} conversion is preferable to changing an
API that downstream consumers depend on. The rule: fix the narrowest
scope that removes the cast without altering any interface.
Flagged on 11 PRs (0.8%). Active 2021–2026.
Code inside namespace itk { ... } should not prefix ITK symbols with
itk::.
// BAD — inside a .cxx file that is already in namespace itk:
itk::ConvertNumberToString(value);
// GOOD:
ConvertNumberToString(value);Flagged on 1 PR (2026). Severity: high — incorrect claims erode reviewer trust.
AI-generated PR descriptions and review summaries have been observed claiming incorrect counts (e.g., "three temporary variables eliminated" when only one existed). This has been compared to known LLM counting errors.
Rule: Before submitting an AI-generated description, manually verify every concrete claim — counts, variable names, file paths, and behavioral assertions. If the AI says "N items were changed," count them yourself.
Refactoring, squashing, addressing reviewer comments, and adding fixup commits frequently change the scope of a PR. After any such change, AI tools must re-read all commit messages and the PR title/body and verify they still accurately describe what the PR does. Stale descriptions that reference removed work, omit added work, or overstate the change are a common source of reviewer confusion and erode trust in AI-assisted PRs.
Checklist after every scope change:
- Does the PR title still describe the current change set?
- Does each commit message accurately reflect its diff?
- Were claims about "N files changed" or "M patterns fixed" invalidated by the scope change?
- If commits were squashed, does the squashed message cover everything that was folded in?
Generated 2026-04-12 by analyzing 8,484 inline review comments across 1,457 PRs (2017–2026) from the ITK GitHub repository. Topics counted per distinct PR, not per comment. See the PR description for details.