🔍 Linting Philosophy¶
🎯 Por Qué Linting¶
╔════════════════════════════════════════════════════════════╗ ║ Problema │ Linting previene ║ ╠═════════════════════════╪═══════════════════════════════╣ ║ Bugs sutiles │ bugprone-* checks ║ ║ Performance issues │ performance-* checks ║ ║ Code smells │ readability-* checks ║ ║ C++ anti-patterns │ modernize-* checks ║ ║ Race conditions │ concurrency-* checks ║ ║ Memory leaks │ clang-analyzer-* checks ║ ║ API misuse │ cppcoreguidelines-* checks ║ ╚════════════════════════════════════════════════════════════╝
Real Examples¶
Bug Prevention:
// ❌ BAD: bugprone-dangling-handle
std::string_view getName() {
std::string name = "audio";
return name; // Dangling! Returns view to destroyed string
}
// ✅ GOOD: Linter catches this
std::string getName() {
return "audio"; // Return value, not view
}
Performance:
// ❌ BAD: performance-unnecessary-copy-initialization
void process(const std::vector<float>& data) {
auto copy = data; // Unnecessary copy!
// ... only read from copy
}
// ✅ GOOD: Linter suggests
void process(const std::vector<float>& data) {
const auto& ref = data; // No copy
}
Modernization:
// ❌ BAD: modernize-use-nullptr
int* ptr = NULL; // Old-style
// ✅ GOOD: Linter auto-fixes
int* ptr = nullptr; // Modern C++11
⚖️ Balance: Strictness vs Practicidad¶
Too Strict ⚠️¶
Problems:
- Developer frustration ("why is linter complaining about this?!")
- Workarounds proliferate (// NOLINT everywhere)
- Standards ignored (crying wolf effect)
- Productivity loss (fighting linter instead of coding)
Example:
// TOO STRICT: Forbidding all magic numbers
// NOLINTNEXTLINE - Sample rate
constexpr int SAMPLE_RATE = 48000;
// NOLINTNEXTLINE - Buffer size
constexpr int BUFFER_SIZE = 512;
// NOLINTNEXTLINE - PI
constexpr float PI = 3.14159f;
// Too many suppressions → developers ignore linter
Too Lax ⚠️¶
Problems: - Quality erosion (no guardrails) - Bugs slip through (linter not catching real issues) - Technical debt accumulates - Inconsistent codebase
Example:
// TOO LAX: No checks enabled
void process(float* buffer, int size) {
for (int i = 0; i <= size; ++i) { // Off-by-one bug! Linter silent
buffer[i] *= 2.0f;
}
}
Sweet Spot ✅¶
Characteristics: - Catch real bugs (memory safety, logic errors) - Allow justified exceptions (with explanatory comments) - Pragmatic enforcement (warnings in dev, errors in CI) - Actionable warnings (developer can understand and fix)
Example:
// BALANCED: Catch real issues, allow reasonable code
// OK: Named constants are clear
constexpr int SAMPLE_RATE = 48000;
constexpr int BUFFER_SIZE = 512;
// WARNING: Magic number in logic (unclear intent)
if (value > 3.7f) { // What is 3.7? Why?
// ...
}
// GOOD: Named constant explains intent
constexpr float MAX_AMPLITUDE = 3.7f;
if (value > MAX_AMPLITUDE) {
// ...
}
📋 Warnings as Errors Policy¶
Policy Matrix¶
| Environment | Warnings as Errors | Rationale |
|---|---|---|
| CI/CD | ✅ YES | Block merge, enforce quality |
| Local Dev | ❌ NO | Allow iteration, don't block flow |
| Pre-commit | ⚠️ OPTIONAL | Developer choice (git hooks) |
Implementation¶
CMakeLists.txt:
# Local dev: Warnings only
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra")
# CI/CD: Warnings as errors
if(DEFINED ENV{CI})
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror")
endif()
CI Configuration:
# GitHub Actions / GitLab CI
steps:
- name: Run clang-tidy
run: |
clang-tidy --warnings-as-errors='*' src/**/*.cpp
Rationale¶
Why YES in CI: - Enforce quality gate (no regressions) - Consistent standards across team - Fail fast (catch issues before merge) - Force resolution (can't ignore warnings)
Why NO in local dev: - Developer flow matters (don't interrupt coding) - Iterative development (code evolves) - Refactoring-friendly (can have temporary warnings) - Trust developers to fix before PR
🔄 Progressive Adoption¶
Phase 1: Enable Checks (Warnings Only) 📊¶
Goal: Establish baseline, gather data
Actions:
# Run linters, output to file
clang-tidy src/**/*.cpp > linting_baseline.txt
cppcheck --enable=all src/ > cppcheck_baseline.txt
# Count violations
echo "Total clang-tidy warnings: $(wc -l < linting_baseline.txt)"
echo "Total cppcheck warnings: $(wc -l < cppcheck_baseline.txt)"
Outcome: - Know where we stand (e.g., "456 warnings") - Identify common violations (e.g., "80% are readability issues") - Prioritize fixes (critical bugs first)
Phase 2: Fix Existing Violations 🔧¶
Strategy: Triage and fix systematically
Priority Order: 1. Critical: Memory safety, logic bugs (bugprone-, clang-analyzer-) 2. High: Performance issues (performance-) 3. Medium: Modernization (modernize-) 4. Low: Style issues (readability-*)
Approach:
# Fix one category at a time
clang-tidy --checks='bugprone-*' --fix src/**/*.cpp
# Commit after each category
git add .
git commit -m "fix: Resolve bugprone-* violations"
Team Coordination: - Assign violations to team members - Track progress in spreadsheet/Jira - Regular check-ins (weekly standup)
Phase 3: Warnings → Errors ⚠️➡️❌¶
Trigger: When violation count < threshold
Example:
# Enable errors when clean
if [ $(clang-tidy src/**/*.cpp | wc -l) -eq 0 ]; then
echo "WarningsAsErrors: '*'" >> .clang-tidy
fi
Gradual Enforcement:
# .clang-tidy (incremental)
WarningsAsErrors: 'bugprone-*' # Week 1
WarningsAsErrors: 'bugprone-*,performance-*' # Week 2
WarningsAsErrors: 'bugprone-*,performance-*,modernize-*' # Week 3
WarningsAsErrors: '*' # Week 4
Phase 4: Add Stricter Checks 📈¶
Goal: Continuous improvement
Candidates:
- cert-* (CERT C++ Coding Standard)
- hicpp-* (High Integrity C++)
- google-* (Google C++ Style Guide)
Process:
# Test new checks on subset
clang-tidy --checks='cert-*' src/core/*.cpp
# If reasonable, add incrementally
Checks: >
bugprone-*,
performance-*,
cert-err58-cpp, # Add one at a time
cert-dcl50-cpp
📊 Metrics & Monitoring¶
Key Metrics¶
| Metric | Good Target | How to Measure |
|---|---|---|
| Linter violations | < 10 | clang-tidy \| wc -l |
| Critical violations | 0 | grep "bugprone-" linter.log |
| Suppressions | < 50 | grep "NOLINT" src \| wc -l |
| Coverage | > 95% | Files checked / Total files |
Dashboards¶
CI/CD Integration:
# Generate linting report
clang-tidy src/**/*.cpp --export-fixes=fixes.yaml
# Publish to dashboard (SonarQube, CodeClimate, etc.)
sonar-scanner \
-Dsonar.projectKey=audiolab \
-Dsonar.sources=src \
-Dsonar.cxx.clangtidy.reportPath=fixes.yaml
Trend Analysis:
# Track violations over time
echo "$(date +%Y-%m-%d),$(clang-tidy src/**/*.cpp | wc -l)" >> violations.csv
# Plot with gnuplot
gnuplot -e "set term png; set output 'violations.png'; plot 'violations.csv' using 1:2 with lines"
🧰 Tools Comparison¶
| Tool | Strengths | Weaknesses | Use Case |
|---|---|---|---|
| clang-tidy | Modern C++, auto-fixes | Slower, C++ only | Primary linter |
| cppcheck | Fast, C/C++, no dependencies | Fewer checks, false positives | Complementary |
| PVS-Studio | Deep analysis, enterprise | Proprietary, expensive | Optional (pro teams) |
| SonarQube | Multi-language, dashboard | Server required | CI/CD integration |
Recommended Stack¶
Minimum (Free): - clang-tidy (primary) - cppcheck (secondary)
Enhanced (Free): - clang-tidy - cppcheck - SonarQube Community
Professional (Paid): - clang-tidy - cppcheck - PVS-Studio - SonarQube Enterprise
📚 References¶
- clang-tidy checks: https://clang.llvm.org/extra/clang-tidy/checks/list.html
- cppcheck manual: https://cppcheck.sourceforge.io/manual.pdf
- C++ Core Guidelines: https://isocpp.github.io/CppCoreGuidelines/
- CERT C++ Coding Standard: https://wiki.sei.cmu.edu/confluence/pages/viewpage.action?pageId=88046682