|
| 1 | +# ITK Code Review Lessons — Recurring Reviewer Concerns |
| 2 | + |
| 3 | +Distilled from 8,484 inline review comments across 1,457 pull requests |
| 4 | +spanning 2017–2026. These are patterns that ITK's core reviewers flag |
| 5 | +repeatedly; an AI assistant that avoids them will produce PRs that pass |
| 6 | +review with fewer round-trips. |
| 7 | + |
| 8 | +--- |
| 9 | + |
| 10 | +## 1. Remove All Orphaned Artifacts After Refactoring |
| 11 | + |
| 12 | +**Flagged on 31% of reviewed PRs (453/1,457). Active 2018–2026.** |
| 13 | + |
| 14 | +When you remove the last user of a helper function, a local variable, |
| 15 | +an `#include`, or a type alias — also remove the definition itself. |
| 16 | +Reviewers catch orphaned code more than any other single issue. |
| 17 | + |
| 18 | +```cpp |
| 19 | +// BAD — removed the std::cout print that used chBuffer, |
| 20 | +// but left the clGetPlatformInfo() call that populated it. |
| 21 | +clGetPlatformInfo(platform, CL_PLATFORM_NAME, sizeof(chBuffer), chBuffer, nullptr); |
| 22 | + |
| 23 | +// GOOD — removed both the consumer AND the producer. |
| 24 | +``` |
| 25 | +
|
| 26 | +**Checklist before submitting a refactoring PR:** |
| 27 | +- Grep for every symbol you modified or deleted. |
| 28 | +- If a helper, typedef, or include has zero remaining callers, remove it. |
| 29 | +- If a `using` alias was only public for one consumer, move it to `private` |
| 30 | + or remove it entirely. |
| 31 | +
|
| 32 | +--- |
| 33 | +
|
| 34 | +## 2. Test Quality and GTest Conventions |
| 35 | +
|
| 36 | +**Flagged on 28% of reviewed PRs (411/1,457). Active 2017–2026.** |
| 37 | +
|
| 38 | +### Unique suite names per `.cxx` file |
| 39 | +
|
| 40 | +```cpp |
| 41 | +// BAD — "HeavisideStepFunction" reused across multiple test files: |
| 42 | +TEST(HeavisideStepFunction, ConvertedLegacyTest) // in file A |
| 43 | +TEST(HeavisideStepFunction, AnotherTest) // in file B |
| 44 | +
|
| 45 | +// GOOD — one unique suite name per .cxx file: |
| 46 | +TEST(SinRegularizedHeavisideStepFunction, ConvertedLegacyTest) |
| 47 | +``` |
| 48 | + |
| 49 | +### Use `ConvertedLegacyTest` for migrated CTest tests |
| 50 | + |
| 51 | +When converting a legacy `itkFooTest.cxx` to GTest, name the test |
| 52 | +`TEST(Foo, ConvertedLegacyTest)` unless the test has a more specific |
| 53 | +purpose worth naming. |
| 54 | + |
| 55 | +### Non-fatal assertions need null guards |
| 56 | + |
| 57 | +`ITK_TEST_EXPECT_TRUE` is non-fatal — it records failure but continues. |
| 58 | +If a `dynamic_cast` might return null, guard before dereferencing: |
| 59 | + |
| 60 | +```cpp |
| 61 | +// BAD — continues past null and crashes: |
| 62 | +auto * p = dynamic_cast<Derived *>(base.GetPointer()); |
| 63 | +ITK_TEST_EXPECT_TRUE(p != nullptr); |
| 64 | +p->DoSomething(); // CRASH if dynamic_cast failed |
| 65 | + |
| 66 | +// GOOD — bail immediately on null: |
| 67 | +auto * p = dynamic_cast<Derived *>(base.GetPointer()); |
| 68 | +if (p == nullptr) |
| 69 | +{ |
| 70 | + std::cerr << "dynamic_cast failed" << std::endl; |
| 71 | + return EXIT_FAILURE; |
| 72 | +} |
| 73 | +``` |
| 74 | +
|
| 75 | +--- |
| 76 | +
|
| 77 | +## 3. Include and Header Hygiene |
| 78 | +
|
| 79 | +**Flagged on 21% of reviewed PRs (313/1,457). Active 2018–2026.** |
| 80 | +
|
| 81 | +- Include only what you use. Do not leave includes for removed code. |
| 82 | +- Prefer forward declarations in headers when only a pointer or |
| 83 | + reference is needed. |
| 84 | +- After removing code, check whether any `#include` directives became |
| 85 | + orphaned. |
| 86 | +
|
| 87 | +--- |
| 88 | +
|
| 89 | +## 4. Naming Clarity |
| 90 | +
|
| 91 | +**Flagged on 17% of reviewed PRs (254/1,457). Active 2018–2026.** |
| 92 | +
|
| 93 | +- Variable names should describe what they hold, not how they were computed. |
| 94 | +- After applying a limit or filter, rename the variable to reflect its |
| 95 | + new meaning (e.g., `nodes` → `displayed_nodes` after truncation). |
| 96 | +- Magic numbers should be named constants or use ITK's existing named |
| 97 | + constants (e.g., `itk::Statistics::MersenneTwisterRandomVariateGenerator::DefaultSeed` |
| 98 | + instead of `121212`). |
| 99 | +
|
| 100 | +--- |
| 101 | +
|
| 102 | +## 5. Style Consistency Within a Function |
| 103 | +
|
| 104 | +**Flagged on 15% of reviewed PRs (214/1,457). Active 2018–2026.** |
| 105 | +
|
| 106 | +After a partial fix, check that the modified code is consistent with the |
| 107 | +rest of the function and file: |
| 108 | +
|
| 109 | +- If a function parameter type was updated, check that the function body |
| 110 | + uses the same qualified form. |
| 111 | +- If a naming pattern was corrected, apply the correction to all similar |
| 112 | + instances in the same scope — do not leave a mix. |
| 113 | +- Sub-section numbering, comment formatting, and JSON keys should be |
| 114 | + consistent within and across files. |
| 115 | +
|
| 116 | +--- |
| 117 | +
|
| 118 | +## 6. Error Handling and Exception Safety |
| 119 | +
|
| 120 | +**Flagged on 14% of reviewed PRs (201/1,457). Active 2018–2026.** |
| 121 | +
|
| 122 | +- Check return values from functions that can fail (`dynamic_cast`, |
| 123 | + Python C API calls, file I/O). A `nullptr` or error return passed |
| 124 | + silently to the next line is a crash. |
| 125 | +- After removing cleanup code (e.g., `delete m_Writer; m_Writer = nullptr;`), |
| 126 | + verify the replacement (`unique_ptr`, RAII) provides equivalent |
| 127 | + exception-safety guarantees. |
| 128 | +- `EXPECT_NO_THROW` in GTest should wrap only the call being tested, |
| 129 | + not surrounding side-effects like `std::cout`. |
| 130 | +
|
| 131 | +--- |
| 132 | +
|
| 133 | +## 7. Signed/Unsigned Conversions and `size_t` |
| 134 | +
|
| 135 | +**Flagged on 8% of reviewed PRs (120/1,457). Active 2018–2026.** |
| 136 | +
|
| 137 | +- Avoid narrowing conversions between signed and unsigned types. |
| 138 | + Prefer ITK's `SizeValueType` or `unsigned int` consistently. |
| 139 | +- Do not add `static_cast` just to silence a warning — ask whether the |
| 140 | + conversion is actually safe. Unnecessary casts obscure real bugs. |
| 141 | +- For template parameters, prefer `unsigned{VRows}` over |
| 142 | + `static_cast<unsigned int>(VRows)` — it is type-safe and rejects |
| 143 | + narrowing. |
| 144 | +
|
| 145 | +--- |
| 146 | +
|
| 147 | +## 8. Locale-Safe Numeric Parsing |
| 148 | +
|
| 149 | +**Flagged on 8% of reviewed PRs (113/1,457). Active 2018–2026.** |
| 150 | +
|
| 151 | +`std::stod`, `std::stof`, `atof`, and `std::to_string` are |
| 152 | +locale-dependent. Under European locales they produce/consume `,` |
| 153 | +instead of `.` as the decimal separator, silently corrupting |
| 154 | +medical image metadata. |
| 155 | +
|
| 156 | +```cpp |
| 157 | +// BAD — locale-dependent: |
| 158 | +double value = std::stod(str); |
| 159 | +buffer << std::fixed << value; |
| 160 | +
|
| 161 | +// GOOD — locale-safe: |
| 162 | +buffer << itk::ConvertNumberToString(value); |
| 163 | +``` |
| 164 | + |
| 165 | +Use `itk::ConvertNumberToString()` for serialization. |
| 166 | +See `itk-locale-safe-migration` for the full set of affected functions. |
| 167 | + |
| 168 | +--- |
| 169 | + |
| 170 | +## 9. When to Use `auto` |
| 171 | + |
| 172 | +**Flagged on 7% of reviewed PRs (105/1,457). Active 2018–2026.** |
| 173 | + |
| 174 | +ITK is not anti-`auto`, but reviewers reject it when the deduced type is |
| 175 | +not obvious from the initializer. |
| 176 | + |
| 177 | +```cpp |
| 178 | +// GOOD — type is obvious from the RHS: |
| 179 | +const auto size = image->GetLargestPossibleRegion().GetSize(); |
| 180 | +auto filter = MedianImageFilter::New(); |
| 181 | +const auto it = container.begin(); |
| 182 | + |
| 183 | +// BAD — reader cannot guess the deduced type: |
| 184 | +const auto value = interp->EvaluateDerivativeAtContinuousIndex(index); |
| 185 | +// (value is a CovariantVector — not obvious) |
| 186 | + |
| 187 | +// BETTER — spell out non-obvious types: |
| 188 | +const CovariantVectorType value = interp->EvaluateDerivativeAtContinuousIndex(index); |
| 189 | +``` |
| 190 | +
|
| 191 | +**Rule of thumb:** use `auto` when the type name appears on the same line |
| 192 | +(factory methods, iterators, casts) or is unambiguous from the method name |
| 193 | +(`GetSize()`, `GetSpacing()`). Spell out the type when the return type |
| 194 | +requires knowledge of the class's internal typedefs. |
| 195 | +
|
| 196 | +--- |
| 197 | +
|
| 198 | +## 10. ITK Initializer Patterns |
| 199 | +
|
| 200 | +**Flagged on 4% of reviewed PRs (57/1,457). Active 2018–2026.** |
| 201 | +
|
| 202 | +Use the single-expression forms for FixedArray-based types: |
| 203 | +
|
| 204 | +```cpp |
| 205 | +// BAD — two-line declare-then-fill: |
| 206 | +SizeType size; |
| 207 | +size.Fill(2); |
| 208 | +
|
| 209 | +// GOOD — single expression: |
| 210 | +constexpr auto size = SizeType::Filled(2); |
| 211 | +
|
| 212 | +// BAD: |
| 213 | +image->Allocate(true); |
| 214 | +
|
| 215 | +// GOOD: |
| 216 | +image->AllocateInitialized(); |
| 217 | +
|
| 218 | +// BAD — zero-initialize in two lines: |
| 219 | +IndexType start; |
| 220 | +start.Fill(0); |
| 221 | +
|
| 222 | +// GOOD — brace initialization is zero-fill: |
| 223 | +constexpr IndexType start{}; |
| 224 | +``` |
| 225 | + |
| 226 | +--- |
| 227 | + |
| 228 | +## 11. No C-Style Casts; Prefer Local Fixes Over Cascading Changes |
| 229 | + |
| 230 | +**Flagged on 10 PRs (0.7%). Active 2019–2026. Every instance was a correctness concern.** |
| 231 | + |
| 232 | +```cpp |
| 233 | +// BAD: |
| 234 | +unsigned int rows = (unsigned int)VRows; |
| 235 | + |
| 236 | +// GOOD — prefer no cast; if needed: |
| 237 | +unsigned int rows = unsigned{VRows}; // type-safe, rejects narrowing |
| 238 | +unsigned int rows = static_cast<unsigned int>(VRows); |
| 239 | +``` |
| 240 | +
|
| 241 | +Before adding any cast, ask: "Do I actually get a compiler warning without |
| 242 | +this?" If not, the cast is unnecessary and obscures the code. |
| 243 | +
|
| 244 | +When a cast exists because a local variable has a mismatched type, it is |
| 245 | +often better to change the local variable's type to eliminate the cast |
| 246 | +entirely. However, **do not cascade type changes into function signatures, |
| 247 | +template parameters, or public API boundaries** to avoid a cast. A small |
| 248 | +local `static_cast` or `T{x}` conversion is preferable to changing an |
| 249 | +API that downstream consumers depend on. The rule: fix the narrowest |
| 250 | +scope that removes the cast without altering any interface. |
| 251 | +
|
| 252 | +--- |
| 253 | +
|
| 254 | +## 12. Redundant Namespace Qualifiers |
| 255 | +
|
| 256 | +**Flagged on 11 PRs (0.8%). Active 2021–2026.** |
| 257 | +
|
| 258 | +Code inside `namespace itk { ... }` should not prefix ITK symbols with |
| 259 | +`itk::`. |
| 260 | +
|
| 261 | +```cpp |
| 262 | +// BAD — inside a .cxx file that is already in namespace itk: |
| 263 | +itk::ConvertNumberToString(value); |
| 264 | +
|
| 265 | +// GOOD: |
| 266 | +ConvertNumberToString(value); |
| 267 | +``` |
| 268 | + |
| 269 | +--- |
| 270 | + |
| 271 | +## 13. AI-Generated Descriptions Must Be Factually Verified |
| 272 | + |
| 273 | +**Flagged on 1 PR (2026). Severity: high — incorrect claims erode reviewer trust.** |
| 274 | + |
| 275 | +AI-generated PR descriptions and review summaries have been observed |
| 276 | +claiming incorrect counts (e.g., "three temporary variables eliminated" |
| 277 | +when only one existed). This has been compared to known LLM counting |
| 278 | +errors. |
| 279 | + |
| 280 | +**Rule:** Before submitting an AI-generated description, manually verify |
| 281 | +every concrete claim — counts, variable names, file paths, and behavioral |
| 282 | +assertions. If the AI says "N items were changed," count them yourself. |
| 283 | + |
| 284 | +### Keep commit messages and PR descriptions in sync with scope |
| 285 | + |
| 286 | +Refactoring, squashing, addressing reviewer comments, and adding fixup |
| 287 | +commits frequently change the scope of a PR. After any such change, AI |
| 288 | +tools must re-read all commit messages and the PR title/body and verify |
| 289 | +they still accurately describe what the PR does. Stale descriptions |
| 290 | +that reference removed work, omit added work, or overstate the change |
| 291 | +are a common source of reviewer confusion and erode trust in |
| 292 | +AI-assisted PRs. |
| 293 | + |
| 294 | +**Checklist after every scope change:** |
| 295 | +- Does the PR title still describe the current change set? |
| 296 | +- Does each commit message accurately reflect its diff? |
| 297 | +- Were claims about "N files changed" or "M patterns fixed" invalidated |
| 298 | + by the scope change? |
| 299 | +- If commits were squashed, does the squashed message cover everything |
| 300 | + that was folded in? |
| 301 | + |
| 302 | +--- |
| 303 | + |
| 304 | +## Methodology |
| 305 | + |
| 306 | +Generated 2026-04-12 by analyzing 8,484 inline review comments across |
| 307 | +1,457 PRs (2017–2026) from the ITK GitHub repository. Topics counted |
| 308 | +per distinct PR, not per comment. See the PR description for details. |
0 commit comments