Skip to content

🔍 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

Minimum (Free): - clang-tidy (primary) - cppcheck (secondary)

Enhanced (Free): - clang-tidy - cppcheck - SonarQube Community

Professional (Paid): - clang-tidy - cppcheck - PVS-Studio - SonarQube Enterprise

📚 References