2.3: Code Review Excellence
Code Reviews for Safety-Critical Systems
Why Code Reviews Matter
Industry Data:
- Code reviews catch 60–90% of defects before testing (IBM and Microsoft studies)
- Defect cost: Review (€50–200) vs. field (€10,000–100,000)
- Review effectiveness: 80–90% defect detection with trained reviewers
ASPICE Requirement:
- SUP.2: "Conduct reviews of work products" (code reviews are mandatory for CL2+)
- SWE.3 BP6: "Verify software units" (includes code review)
Safety Standards:
- ISO 26262 Part 6: "Semi-formal verification methods" (code reviews for ASIL-B/C/D)
- IEC 62304: "Software unit verification" (reviews required for Class B/C)
Code Review Process
1. Before Review (Author's Responsibility)
Author Checklist (before requesting review):
## Pre-Review Checklist
### Code Quality
- [ ] All functions have Doxygen headers?
- [ ] All public functions have @implements tags (traceability)?
- [ ] No magic numbers (all constants named)?
- [ ] Function length ≤50 lines (ideally 5–15)?
- [ ] Variable names are self-documenting?
### Testing
- [ ] All new code has unit tests?
- [ ] Test coverage ≥90% (check with gcov)?
- [ ] All tests pass locally?
### Standards Compliance
- [ ] MISRA C:2012 violations checked (cppcheck, PC-lint)?
- [ ] No compiler warnings (-Wall -Wextra)?
- [ ] Code formatted (clang-format)?
### Traceability
- [ ] All functions link to requirements (SWE-XXX)?
- [ ] Traceability matrix updated?
### Documentation
- [ ] README updated (if API changed)?
- [ ] Architecture Decision Record (ADR) written (if design decision made)?
Self-Review (author reviews own code before requesting peer review):
- Read your own code line-by-line as if reviewing someone else's.
- Check for obvious issues (typos, missing error handling, unclear names).
- Run static analysis (cppcheck, PC-lint).
Benefit: Catches 30–40% of defects before peer review, saving reviewer time.
2. During Review (Reviewer's Responsibility)
Review Checklist (SUP.2 compliant):
A. Correctness
- [ ] Does code implement requirements correctly?
- [ ] Are all edge cases handled (boundary, error, invalid input)?
- [ ] Are return codes checked (no ignored errors)?
- [ ] Is error handling defensive (fail-safe behavior)?
Example Issue:
/* [FAIL] Bad: Ignores CAN read error */
CAN_ReadMessage(0x200, buffer);
float distance = buffer[0]; /* What if CAN timed out? */
/* [PASS] Good: Checks error, handles failure */
if (CAN_ReadMessage(0x200, buffer) != CAN_OK) {
Log_Error(ERROR_CAN_TIMEOUT, 0x200);
return -1; /* Fail-safe */
}
B. Readability
- [ ] Are function/variable names clear (no cryptic abbreviations)?
- [ ] Are functions small (5–15 lines, single responsibility)?
- [ ] Are comments necessary (explain WHY, not WHAT)?
- [ ] Is code formatted consistently (indentation, braces)?
Example Feedback:
/* Reviewer comment: Function name unclear */
void Process(int x) { /* Process what? */
/* ... */
}
/* Author fix: Renamed to clarify purpose */
void ValidateSensorData(int sensor_id) {
/* ... */
}
C. Testability
- [ ] Can this function be unit tested (no global state, I/O dependencies)?
- [ ] Are unit tests included for new code?
- [ ] Do tests cover nominal, boundary, and error cases?
- [ ] Is test coverage ≥90%?
Example Issue:
/* [FAIL] Bad: Hard to test (depends on global CAN hardware) */
float GetDistance(void) {
return CAN_ReadRadarDistance(); /* Requires real CAN hardware */
}
/* [PASS] Good: Testable (dependency injection) */
float GetDistance(CAN_Interface* can) {
return can->ReadRadarDistance(); /* Can mock CAN interface */
}
D. MISRA C:2012 Compliance
- [ ] No MISRA violations (check cppcheck report)?
- [ ] If violations exist, are they documented and justified?
- [ ] Are all violations reviewed by safety engineer (for ASIL-B+)?
Example MISRA Violation:
/* cppcheck warning: MISRA 10.4 (incompatible essential types) */
uint16_t x = 10;
int16_t y = -5;
int16_t z = x + y; /* [FAIL] Unsigned + signed */
/* Fix: Explicit cast */
int16_t z = (int16_t)x + y; /* [PASS] Explicit, safe conversion */
E. Traceability
- [ ] Do all functions have @implements tags linking to requirements?
- [ ] Do all tests have @verifies tags linking to requirements?
- [ ] Is traceability matrix updated (req → code → test)?
Example:
/**
* @brief Calculate safe following distance
* @implements [SWE-045-11] Safe Following Distance
* ↑ Reviewer checks: Is [SWE-045-11] a valid requirement in DOORS?
*/
float ACC_CalculateSafeDistance(float speed_kmh) { /* ... */ }
F. Safety Requirements (ASIL-B+)
- [ ] Are safety requirements clearly marked (@safety_class ASIL-B)?
- [ ] Is fail-safe behavior defined for all error cases?
- [ ] Are assertions used for preconditions (defensive programming)?
- [ ] Is there evidence of defensive programming (input validation, range checks)?
3. Review Comments (How to Give Feedback)
Good Review Comments (constructive, specific):
[PASS] "Function name `Process()` is unclear. Suggest renaming to `ValidateSensorData()` to clarify purpose."
[PASS] "Line 45: Missing error handling for CAN_ReadMessage(). Add check for CAN_OK and return error code if timeout."
[PASS] "Lines 67-85: Function too long (19 lines). Consider extracting calculation into separate function `CalculateFusedDistance()`."
[PASS] "Line 102: Magic number `150`. Define as constant `MAX_SPEED_KMH` with comment explaining rationale (ISO 26262 requirement)."
Bad Review Comments (vague, critical tone):
[FAIL] "This is wrong." (What is wrong? How to fix?)
[FAIL] "You should know better." (Unhelpful, demotivating)
[FAIL] "I would have done it differently." (Not actionable feedback)
[FAIL] "This code is a mess." (Too general, no specific issues)
Tone Guidelines:
- Be kind: Assume positive intent (author did their best)
- Be specific: Point to exact line, explain issue, suggest fix
- Be educational: Explain why (not just what to change)
- Be collaborative: "We" (not "you") - "We should add error handling here"
4. After Review (Author Responds)
Author Response Options:
- Accept: "Good catch, fixed in commit abc123"
- Clarify: "This is intentional because... (explain rationale)"
- Discuss: "I considered that, but here's why I chose this approach..." (start discussion)
Example Response:
Reviewer: "Line 45: Missing error handling for CAN_ReadMessage()"
Author: "Good catch! Added error handling in commit abc123:
- Returns -1 on CAN timeout
- Logs error for diagnostics
- Updated unit test to verify error path
Thanks!"
Review Metrics (ASPICE SUP.2)
Track Review Effectiveness
Metrics to Collect:
| Metric | Formula | Target (ASIL-B) |
|---|---|---|
| Review Coverage | Lines reviewed / Total lines | 100% (all code reviewed) |
| Defect Density | Defects found / 1000 LOC | 5–10 defects/KLOC |
| Review Rate | LOC reviewed / hour | 100–200 LOC/hour (optimal) |
| Rework Rate | Lines changed / Lines reviewed | 5–15% |
| Defects Escaped | Defects found in testing / Total defects | <10% (catch 90% in review) |
Example Report:
## Code Review Report: PR#142 (ACC Speed Control)
**Summary**:
- Lines changed: 234 LOC
- Review time: 90 minutes
- Review rate: 156 LOC/hour [PASS] (target: 100-200)
- Defects found: 12
- Defect density: 51 defects/KLOC [WARN] (target: 5-10, high for safety-critical)
**Defects by Category**:
- MISRA violations: 5 (Rule 10.4, 12.1, 14.4)
- Missing error handling: 3
- Magic numbers: 2
- Unclear naming: 2
**Action**: Author to fix all defects, re-review required
**Re-Review**: After fixes, 0 defects found [PASS] Approved for merge
Review Best Practices
1. Keep Reviews Small
Guideline: Review ≤300 LOC per session (optimal: 150–200 LOC)
Rationale:
- Review effectiveness drops after 60–90 minutes
- At 100–200 LOC/hour, reviewing 300 LOC takes 90–180 minutes
- Large reviews miss defects (cognitive overload)
If PR is Large (>400 LOC):
- Break into smaller PRs (one feature per PR)
- Review in multiple sessions (take breaks)
2. Use Checklists
Checklist Ensures Consistency
Example Checklist (customize for your project):
## Code Review Checklist (ACC ECU)
### Correctness (10 min)
- [ ] Implements requirements correctly?
- [ ] All edge cases handled?
- [ ] Error handling defensive?
### Readability (10 min)
- [ ] Function/variable names clear?
- [ ] Functions small (5–15 lines)?
- [ ] Comments explain WHY (not WHAT)?
### Testability (10 min)
- [ ] Unit tests included?
- [ ] Coverage ≥90%?
- [ ] Tests cover nominal, boundary, error cases?
### MISRA Compliance (10 min)
- [ ] cppcheck report clean?
- [ ] Violations documented?
### Traceability (10 min)
- [ ] @implements tags present?
- [ ] Traceability matrix updated?
### Safety (10 min, ASIL-B only)
- [ ] @safety_class annotation?
- [ ] Fail-safe behavior defined?
- [ ] Defensive programming (input validation)?
3. Review in Context
Don't Just Review Diff (Changed Lines) - Review Entire Function
Example:
/* Diff (changed lines) shows only this change: */
- if (speed > 150) {
+ if (speed_kmh > MAX_SPEED_KMH) {
/* But reviewer should read entire function to understand context: */
void ACC_SetSpeed(int speed_kmh) {
const int MAX_SPEED_KMH = 150; /* ISO 26262 requirement */
if (speed_kmh > MAX_SPEED_KMH) { /* ← This line changed */
speed_kmh = MAX_SPEED_KMH;
}
/* Missing: Input validation for negative speed! */
/* Reviewer catches this by reading full function, not just diff */
g_set_speed = speed_kmh;
}
4. Automate What You Can
Automated Checks (run before human review):
- Formatting: clang-format (enforce consistent style)
- Static Analysis: cppcheck, PC-lint (catch MISRA violations)
- Tests: CI/CD runs tests automatically (verify all pass)
- Coverage: gcov (verify coverage ≥90%)
Benefit: Reviewer focuses on logic, design, safety (not formatting, MISRA)
CI/CD Pipeline (GitLab CI example):
stages:
- format-check
- static-analysis
- test
- review
format-check:
stage: format-check
script:
- clang-format --dry-run --Werror src/*.c # Fails if not formatted
static-analysis:
stage: static-analysis
script:
- cppcheck --enable=all --error-exitcode=1 src/*.c
- pc-lint std.lnt src/*.c # MISRA C:2012 check
test:
stage: test
script:
- mkdir build && cd build
- cmake .. && make
- ./acc_tests # Run unit tests
- gcov ../src/*.c # Generate coverage report
- lcov --summary coverage.info | grep "lines......: 9[0-9]%" # Verify ≥90%
review:
stage: review
script:
- echo "All automated checks passed. Ready for human review."
when: manual # Manual approval (human code review)
Common Review Anti-Patterns
Anti-Pattern 1: Rubber Stamp Review
Problem: Reviewer approves without reading code ("Looks good to me")
Solution:
- Require checklist completion
- Track review time (should be 60–90 min for 150–200 LOC)
- Measure defects escaped to testing (if too high, reviews not effective)
Anti-Pattern 2: Nitpicking
Problem: Reviewer focuses on style issues (spaces, brace position) instead of logic
Solution:
- Automate style checks (clang-format)
- Focus review on correctness, safety, design
Example:
[FAIL] "Line 45: Use spaces instead of tabs" (waste of reviewer time, automate this)
[PASS] "Line 45: Missing error handling for CAN timeout. Add check and fail-safe behavior." (real defect)
Anti-Pattern 3: Review After Implementation Complete
Problem: Large PR (1000+ LOC) at end of sprint, too late to make major changes
Solution:
- Review early and often (incremental PRs)
- Review design before implementation (ADR review)
- Pair programming (continuous review)
Pair Programming as Continuous Review
Alternative to Traditional Code Review
Pair Programming: Two engineers, one keyboard
- Driver: Types code
- Navigator: Reviews code real-time, thinks ahead
Benefits:
- Defects caught immediately (no separate review phase)
- Knowledge sharing (junior learns from senior)
- Higher code quality (two brains better than one)
When to Use:
- Complex features (Kalman filter, PID tuning)
- Safety-critical code (ASIL-B braking logic)
- Onboarding (new engineer pairs with experienced)
When NOT to Use:
- Simple tasks (formatting, documentation)
- Time-critical (pair programming 15–25% slower initially)
Summary
Code Review Best Practices:
- Before Review: Self-review, run static analysis, ensure tests pass (author's responsibility)
- During Review: Use checklist (correctness, readability, testability, MISRA, traceability, safety)
- Review Comments: Be kind, specific, educational (constructive feedback)
- After Review: Author fixes issues, responds to comments
ASPICE SUP.2 Compliance: Reviews are mandatory, track metrics (coverage, defect density, review rate)
Effectiveness: Reviews catch 60–90% of defects (much cheaper than finding in testing or field)
Automation: Automate formatting, static analysis, tests (reviewer focuses on logic, design, safety)
Next: Continuous Integration Mastery (34.04) — Automating builds, tests, and checks in CI/CD pipelines