C++ 정적 분석 도구 | Clang-Tidy·Cppcheck·SonarQube [#53-5]
이 글의 핵심
C++ 코드 품질 도구: Clang-Tidy 규칙, Cppcheck 설정, SonarQube 통합, CI 연동. 문제 시나리오, 완전한 예제, 자주 발생하는 에러, 프로덕션 패턴까지.
들어가며: 기술 부채를 숫자로 관리하고 싶다면
”코드 품질이 얼마나 나쁜지 모르겠어요”
41-1에서 Clang-Tidy와 Cppcheck로 정적 분석 기초를 다뤘다면, 이 글은 SonarQube를 추가해 품질 메트릭·기술 부채·팀 정책까지 한 번에 관리하는 고급 구성을 다룹니다.
실제 겪는 문제 시나리오:
"Clang-Tidy와 Cppcheck는 돌리는데, 품질이 개선되는지 추적이 안 돼요."
"버그·취약점·코드 스멀을 숫자로 보고 싶어요."
"PR마다 '이번에 기술 부채가 얼마나 늘었는지'를 보고 싶어요."
"팀 전체의 코드 품질 트렌드를 대시보드로 보고 싶어요."
SonarQube는 정적 분석 결과를 저장·집계·시각화하고, Quality Gate(품질 게이트)로 “이 수준 이하면 병합 불가”를 강제할 수 있습니다. Clang-Tidy·Cppcheck 결과를 SonarQube에 리포트하면, 통합 대시보드에서 버그·취약점·코드 스멀·기술 부채를 한눈에 볼 수 있습니다.
이 글에서 다루는 것:
- 문제 시나리오: 품질 추적·기술 부채 관리의 어려움
- Clang-Tidy·Cppcheck: 고급 설정·규칙 선택·완전한 예제
- SonarQube: 설치·C++ 연동·Quality Gate·대시보드
- 완전한 정적 분석 예제: 세 도구 통합 프로젝트
- 자주 발생하는 에러와 해결법
- CI 통합: GitHub Actions·PR 품질 게이트
- 프로덕션 패턴: 대규모 프로젝트·점진적 도입·팀 정책
실무 적용 경험: 이 글은 대규모 C++ 프로젝트에서 실제로 겪은 문제와 해결 과정을 바탕으로 작성되었습니다. 책이나 문서에서 다루지 않는 실전 함정과 디버깅 팁을 포함합니다.
목차
- 문제 시나리오: 왜 SonarQube까지 필요한가
- Clang-Tidy 고급 설정
- Cppcheck 고급 설정
- SonarQube 통합
- 완전한 정적 분석 예제
- 자주 발생하는 에러와 해결법
- CI 통합
- 프로덕션 패턴
- 정리
1. 문제 시나리오: 왜 SonarQube까지 필요한가
시나리오 1: “품질이 개선되는지 모르겠어요”
"Clang-Tidy·Cppcheck를 CI에 넣었는데, 경고 수가 500개에서 480개로 줄었는지 확인이 안 돼요."
"이번 스프린트에 버그를 몇 개 고쳤는지 숫자로 보고 싶어요."
원인: Clang-Tidy·Cppcheck는 실행 시점에만 결과를 보여줍니다. 이력 저장·트렌드 분석이 없습니다.
해결: SonarQube에 리포트를 보내면 버그·취약점·코드 스멀·기술 부채가 누적되고, 트렌드 차트로 개선 여부를 확인할 수 있습니다.
시나리오 2: “PR마다 품질 게이트를 통과해야 해요”
"새 버그를 추가하면 병합을 막고 싶어요."
"기술 부채 비율이 5%를 넘으면 PR을 거절하고 싶어요."
원인: CI에서 --error-exitcode=1만 쓰면 “경고 1개라도 있으면 실패” 수준입니다. 세밀한 정책(예: 버그는 막고, 스타일은 허용)이 어렵습니다.
해결: SonarQube Quality Gate로 “새 버그 0개”, “새 취약점 0개”, “기술 부채 비율 5% 이하” 등을 조합해 정책을 정의할 수 있습니다.
시나리오 3: “레거시 코드에 정적 분석을 도입하려 해요”
"10만 줄 코드베이스에 Clang-Tidy를 돌리면 3000개 경고가 나와요."
"한 번에 다 고칠 수 없어요. 새 코드부터만 적용하고 싶어요."
원인: 기존 코드에 정적 분석을 처음 적용하면 경고 폭발이 발생합니다.
해결: SonarQube의 신규 코드에만 적용 정책, Clang-Tidy의 --line-filter·NOLINT 조합으로 점진적 도입이 가능합니다.
시나리오 4: “팀 전체 품질을 대시보드로 보고 싶어요”
"프로젝트 A, B, C의 품질을 한 화면에서 비교하고 싶어요."
"매니저에게 '이번 분기 품질 개선 보고서'를 보여주고 싶어요."
원인: 여러 프로젝트·브랜치의 결과를 수동으로 모으기 어렵습니다.
해결: SonarQube 포트폴리오·프로젝트 그룹으로 여러 프로젝트를 묶어 대시보드를 구성할 수 있습니다.
해결 방향
flowchart TB
subgraph Tools[정적 분석 도구 체인]
T1[Clang-Tidy] --> R[결과 리포트]
T2[Cppcheck] --> R
R --> S[SonarQube]
end
subgraph SonarQube[SonarQube]
S --> D[대시보드]
S --> Q[Quality Gate]
S --> T[트렌드]
end
Q --> M{병합 허용?}
M -->|통과| Merge[병합]
M -->|실패| Block[병합 차단]
2. Clang-Tidy 고급 설정
설치 및 버전
# Ubuntu/Debian
sudo apt install clang-tidy
# macOS (Homebrew)
brew install llvm
# clang-tidy: /opt/homebrew/opt/llvm/bin/clang-tidy
# Windows (vcpkg)
vcpkg install clang-tools
버전: C++17/20을 쓰면 Clang 10+ 권장. clang-tidy --version으로 확인하세요.
.clang-tidy 고급 설정
# .clang-tidy - 프로덕션용 예시
Checks: >
bugprone-*,
-bugprone-easily-swappable-parameters,
modernize-use-nullptr,
modernize-use-auto,
modernize-use-override,
performance-for-range-copy,
performance-unnecessary-copy-initialization,
performance-move-const-arg,
readability-magic-numbers,
readability-simplify-boolean-expr,
readability-identifier-naming
WarningsAsErrors: 'bugprone-use-after-move,bugprone-narrowing-conversions'
HeaderFilterRegex: '(?!.*/third_party/)(?!.*/vcpkg_installed/)'
FormatStyle: file
CheckOptions:
- key: readability-magic-numbers.IgnoredValues
value: '0;1;2;3;4;5;6;7;8;9;10;16;32;64;128;256;512;1024;2048;4096;8192'
- key: readability-identifier-naming.ClassCase
value: CamelCase
- key: readability-identifier-naming.FunctionCase
value: camelBack
설명:
- WarningsAsErrors: 지정한 체크는 경고를 에러로 승격. CI에서 실패 시 병합 차단
- HeaderFilterRegex:
third_party,vcpkg_installed제외 - CheckOptions: 매직 넘버 허용 값, 네이밍 규칙 등
compile_commands.json 연동
# CMake로 compile_commands.json 생성
cmake -B build -S . -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
# 프로젝트 루트에 심볼릭 링크 (일부 도구용)
ln -sf build/compile_commands.json compile_commands.json
# Clang-Tidy 실행
clang-tidy -p build src/**/*.cpp
체크별 Before/After 예시
bugprone-use-after-move:
// ❌ Before: 이동 후 사용
std::vector<int> vec = get_data();
std::vector<int> other = std::move(vec);
vec.push_back(42); // Clang-Tidy: use-after-move
// ✅ After
std::vector<int> vec = get_data();
std::vector<int> other = std::move(vec);
other.push_back(42);
performance-for-range-copy:
// ❌ Before: 요소 복사
for (auto item : large_vector) {
process(item); // item이 복사됨
}
// ✅ After: 참조로 순회
for (const auto& item : large_vector) {
process(item);
}
modernize-use-nullptr:
// ❌ Before
int* p = 0;
if (ptr == NULL) { }
// ✅ After (--fix로 자동 수정 가능)
int* p = nullptr;
if (ptr == nullptr) { }
JSON 출력 (SonarQube 연동용)
# Clang-Tidy를 JSON 형식으로 출력 (SonarQube C++ 플러그인이 파싱)
clang-tidy -p build src/main.cpp --format=json 2>/dev/null | head -100
3. Cppcheck 고급 설정
설치
# Ubuntu/Debian
sudo apt install cppcheck
# macOS
brew install cppcheck
# Windows (vcpkg)
vcpkg install cppcheck
고급 옵션
# 전체 확장 체크 + XML 출력 (SonarQube 연동)
cppcheck --enable=all \
--suppress=missingIncludeSystem \
-I./include \
-I./third_party \
--xml \
--xml-version=2 \
-j 4 \
src/ 2> cppcheck-report.xml
# 플랫폼 지정 (Windows/Linux 등)
cppcheck --platform=win64 --enable=all src/
# C++ 표준 지정
cppcheck --std=c++17 --enable=all src/
cppcheck.cfg 프로젝트 표준화
<?xml version="1.0"?>
<!-- cppcheck.cfg: 프로젝트 루트에 두면 자동 인식 -->
<project>
<include>
<path name="include"/>
<path name="third_party/optional"/>
</include>
<exclude>
<path name="third_party/legacy"/>
<path name="build"/>
</exclude>
<suppress>
<id>missingIncludeSystem</id>
</suppress>
</project>
Suppression
// cppcheck-suppress: 해당 라인만 무시
void foo() {
int* p = (int*)0x1234; // cppcheck-suppress nullPointer
(void)p;
}
# 명령줄에서 무시
cppcheck --suppress=missingInclude:external.h --suppress=unusedFunction:util.cpp src/
Clang-Tidy vs Cppcheck 비교
| 항목 | Clang-Tidy | Cppcheck |
|---|---|---|
| 의존성 | Clang/LLVM 필요 | 독립 실행 |
| 강점 | 모던 C++, 성능, 스타일 | 메모리, 널, 경계 |
| compile_commands | 필수 (정확한 분석) | 불필요 |
| 자동 수정 | —fix 지원 | 미지원 |
| 권장 | 둘 다 사용 (보완적) | 둘 다 사용 (보완적) |
4. SonarQube 통합
SonarQube란?
SonarQube는 정적 분석 결과를 저장·집계·시각화하는 플랫폼입니다. C++의 경우 Clang-Tidy·Cppcheck 결과를 SonarQube Generic Issue Import 형식으로 보내면, 버그·취약점·코드 스멀·기술 부채로 분류해 대시보드에 표시합니다.
Docker로 SonarQube 실행
# SonarQube Community Edition (무료)
docker run -d --name sonarqube \
-p 9000:9000 \
sonarqube:lts-community
# 초기 접속: http://localhost:9000
# 기본 로그인: admin / admin (최초 로그인 시 비밀번호 변경)
SonarQube C++ 플러그인
SonarQube C++ 플러그인은 Clang-Tidy·Cppcheck 리포트를 자동으로 파싱합니다. Marketplace에서 C++ 플러그인을 설치하세요.
또는 sonar-cxx (Community) 플러그인을 사용할 수 있습니다:
# sonar-cxx 플러그인 (Community)
# SonarQube Marketplace에서 "C++" 검색 후 설치
sonar-project.properties
# sonar-project.properties - 프로젝트 루트
sonar.projectKey=my-cpp-project
sonar.projectName=My C++ Project
sonar.projectVersion=1.0
# 소스 경로
sonar.sources=src
sonar.exclusions=**/third_party/**,**/build/**
# C++ 플러그인: Clang-Tidy·Cppcheck 리포트 경로
sonar.cxx.clangtidy.reportPaths=build/clang-tidy-report.json
sonar.cxx.cppcheck.reportPaths=build/cppcheck-report.xml
# 또는 Generic Issue Import
sonar.externalIssuesReportPaths=build/sonar-issues.json
리포트 형식 변환 (Generic Issue Import)
Clang-Tidy·Cppcheck 결과를 SonarQube Generic Issue Import 형식으로 변환하는 스크립트 예시:
#!/usr/bin/env python3
"""
Clang-Tidy JSON 출력을 SonarQube Generic Issue 형식으로 변환
"""
import json
import sys
import xml.etree.ElementTree as ET
def convert_clang_tidy_to_sonar(clang_tidy_json_path, output_path):
with open(clang_tidy_json_path) as f:
data = json.load(f)
issues = []
for entry in data:
if "Diagnostics" not in entry:
continue
path = entry.get("File", "")
for diag in entry[Diagnostics]:
issues.append({
"engineId": "clang-tidy",
"ruleId": diag.get("DiagnosticName", "unknown"),
"severity": "MAJOR" if "error" in str(diag.get("Level", "")).lower() else "MINOR",
"primaryLocation": {
"filePath": path,
"message": diag.get("Message", ""),
"textRange": {
"startLine": diag.get("Line", 0),
"endLine": diag.get("Line", 0),
}
}
})
with open(output_path, "w") as f:
json.dump({"issues": issues}, f, indent=2)
if __name__ == "__main__":
convert_clang_tidy_to_sonar(sys.argv[1], sys.argv[2])
Quality Gate 설정
SonarQube Quality Gate에서 다음 조건을 설정할 수 있습니다:
| 조건 | 설명 |
|---|---|
| 새 버그 | 0개 |
| 새 취약점 | 0개 |
| 기술 부채 비율 | 5% 이하 |
| 커버리지 | 80% 이상 (테스트 연동 시) |
5. 완전한 정적 분석 예제
프로젝트 구조
static-analysis-demo/
├── CMakeLists.txt
├── .clang-tidy
├── cppcheck.cfg
├── sonar-project.properties
├── scripts/
│ ├── run-static-analysis.sh
│ └── convert-reports.py
├── src/
│ ├── main.cpp
│ ├── util.cpp
│ └── buggy.cpp
└── include/
└── util.h
CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(StaticAnalysisDemo LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
add_executable(demo
src/main.cpp
src/util.cpp
src/buggy.cpp
)
target_include_directories(demo PRIVATE ${CMAKE_SOURCE_DIR}/include)
.clang-tidy
Checks: 'bugprone-*,modernize-use-nullptr,performance-*,readability-magic-numbers'
WarningsAsErrors: 'bugprone-use-after-move'
HeaderFilterRegex: '(?!.*/third_party/)'
cppcheck.cfg
<?xml version="1.0"?>
<project>
<include>
<path name="include"/>
</include>
</project>
include/util.h
#pragma once
#include <vector>
int process(const std::vector<int>& vec);
src/util.cpp
#include "util.h"
#include <numeric>
int process(const std::vector<int>& vec) {
return std::accumulate(vec.begin(), vec.end(), 0);
}
src/main.cpp
#include <iostream>
#include <vector>
#include <memory>
#include "util.h"
int main() {
int* p = nullptr; // modernize-use-nullptr 적용됨
(void)p;
std::vector<int> data = {1, 2, 3, 4, 5};
for (const auto& x : data) { // performance-for-range-copy 적용됨
std::cout << x << " ";
}
std::cout << "\n";
constexpr int kMaxResult = 1024;
if (process(data) > kMaxResult) { // readability-magic-numbers 적용됨
std::cout << "Large result\n";
}
return 0;
}
src/buggy.cpp (의도적 버그 - 정적 분석 연습용)
#include <iostream>
#include <vector>
#include <memory>
// Clang-Tidy: bugprone-use-after-move
void use_after_move_demo() {
std::vector<int> vec = {1, 2, 3};
std::vector<int> other = std::move(vec);
vec.push_back(42); // 버그: 이동 후 사용
}
// Cppcheck: arrayIndexOutOfBounds
void array_bounds_demo() {
std::vector<int> vec(10);
vec[10] = 0; // 버그: 인덱스 10은 범위 밖
}
// Cppcheck: uninitvar
void uninit_demo() {
int x;
std::cout << x << std::endl; // 버그: 미초기화 변수
}
int main() {
use_after_move_demo();
array_bounds_demo();
uninit_demo();
return 0;
}
scripts/run-static-analysis.sh
#!/bin/bash
# run-static-analysis.sh - Clang-Tidy + Cppcheck + 리포트 생성
set -e
BUILD_DIR=build
REPORT_DIR=$BUILD_DIR/reports
mkdir -p $REPORT_DIR
# 1. CMake 설정 (compile_commands.json 생성)
cmake -B $BUILD_DIR -S . -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
cmake --build $BUILD_DIR
# 2. Clang-Tidy (JSON 리포트)
echo "=== Clang-Tidy ==="
clang-tidy -p $BUILD_DIR src/*.cpp --format=json 2>/dev/null > $REPORT_DIR/clang-tidy.json || true
# 3. Cppcheck (XML 리포트)
echo "=== Cppcheck ==="
cppcheck --enable=all --suppress=missingIncludeSystem \
-I./include --xml --xml-version=2 \
-j 4 src/ 2> $REPORT_DIR/cppcheck.xml || true
# 4. SonarQube 스캔 (sonar-scanner 설치 필요)
if command -v sonar-scanner &>/dev/null; then
echo "=== SonarQube ==="
sonar-scanner
fi
echo "Done. Reports in $REPORT_DIR/"
실행 및 검증
# 실행 권한 부여
chmod +x scripts/run-static-analysis.sh
# 정적 분석 실행
./scripts/run-static-analysis.sh
# Clang-Tidy 단독 (경고 확인)
clang-tidy -p build src/buggy.cpp
# Cppcheck 단독
cppcheck --enable=all --suppress=missingIncludeSystem -I./include src/buggy.cpp
6. 자주 발생하는 에러와 해결법
문제 1: “clang-tidy: Could not find compile_commands.json”
원인: -p build로 지정한 디렉터리에 compile_commands.json이 없음.
해결법:
cmake -B build -S . -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
ln -sf build/compile_commands.json compile_commands.json
문제 2: “clang-tidy: ‘vector’ file not found”
원인: include 경로 없음. 단일 파일 검사 시 -- 뒤에 옵션 필요.
해결법:
clang-tidy src/main.cpp -- -std=c++17 -I./include -I/usr/include
문제 3: “Cppcheck: Too many #ifdef configurations”
원인: #ifdef 조합 폭발.
해결법:
cppcheck --enable=all --max-configs=8 src/
문제 4: “Cppcheck: missingIncludeSystem”
원인: 시스템 헤더를 찾지 못함.
해결법:
cppcheck --suppress=missingIncludeSystem -I./include src/
문제 5: “Clang-Tidy: 수백 개 경고가 한 번에 나와요”
원인: 레거시 코드베이스에 처음 도입 시.
해결법:
WarningsAsErrors를 빈 문자열로 두고 경고만 수집NOLINT로 일단 무시, 새 코드부터 규칙 적용- 점진적으로
// NOLINT제거
int* p = 0; // NOLINT(modernize-use-nullptr) - 추후 수정
문제 6: “SonarQube: C++ 리포트를 인식하지 못해요”
원인: 리포트 형식이 SonarQube C++ 플러그인과 맞지 않음.
해결법: 플러그인 문서에서 요구하는 형식 확인. Generic Issue Import 형식을 사용하면 호환성이 높습니다.
문제 7: “SonarQube: Quality Gate가 너무 엄격해요”
원인: 기본 Quality Gate가 “버그 0개” 등으로 설정됨.
해결법: SonarQube UI에서 Quality Gate를 복제해 팀에 맞게 조정. 예: “새 버그 0개”만 적용, 기존 코드 스멀은 제외.
문제 8: “CI에서 clang-tidy가 너무 오래 걸려요”
해결법:
- 변경된 파일만 검사 (
git diff기반) run-clang-tidy -j 8로 병렬화compile_commands.json캐시- Clang-Tidy job을 빌드와 병렬 실행
문제 9: “third_party에서 경고가 많이 나와요”
해결법:
# .clang-tidy
HeaderFilterRegex: '(?!.*/third_party/)(?!.*/vcpkg_installed/)'
문제 10: “Cppcheck: 오탐(false positive)이 많아요”
해결법:
// cppcheck-suppress로 해당 라인만 무시
void* ptr = get_address_from_hardware();
// cppcheck-suppress invalidPointer
int* p = (int*)ptr;
7. CI 통합
GitHub Actions: Clang-Tidy + Cppcheck + SonarQube
# .github/workflows/static-analysis.yml
name: Static Analysis
on:
pull_request:
branches: [main, develop]
push:
branches: [main]
jobs:
analyze:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y clang-tidy cppcheck cmake build-essential
- name: Configure
run: cmake -B build -S . -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
- name: Build
run: cmake --build build
- name: Run Clang-Tidy
run: |
for f in $(find src -name "*.cpp"); do
clang-tidy -p build "$f" --warnings-as-errors="bugprone-use-after-move" || true
done
- name: Run Cppcheck
run: |
cppcheck --enable=all --suppress=missingIncludeSystem \
-I./include --error-exitcode=1 -j 4 src/
- name: SonarQube Scan
if: github.event_name == 'pull_request' || github.ref == 'refs/heads/main'
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: |
# sonar-scanner 설치 (또는 action 사용)
curl -sSLo $HOME/sonar-scanner.zip \
https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-4.8.0.2856-linux.zip
unzip -o $HOME/sonar-scanner.zip -d $HOME
$HOME/sonar-scanner-*/bin/sonar-scanner \
-Dsonar.token=$SONAR_TOKEN \
-Dsonar.host.url=${{ secrets.SONAR_HOST_URL }}
GitLab CI
# .gitlab-ci.yml
static-analysis:
stage: test
image: ubuntu:22.04
before_script:
- apt-get update && apt-get install -y clang-tidy cppcheck cmake g++
- cmake -B build -S . -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
script:
- find src -name "*.cpp" -exec clang-tidy -p build {} \;
- cppcheck --enable=all --suppress=missingIncludeSystem -I./include --error-exitcode=1 src/
변경된 파일만 검사 (Incremental)
# .github/workflows/static-analysis-incremental.yml
name: Static Analysis (Incremental)
on:
pull_request:
branches: [main]
jobs:
incremental:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install tools
run: sudo apt-get install -y clang-tidy cppcheck cmake g++
- name: Configure
run: cmake -B build -S . -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
- name: Get changed files
id: changed
run: |
FILES=$(git diff --name-only origin/main...HEAD | grep '\.cpp$' || true)
echo "files<<EOF" >> $GITHUB_OUTPUT
echo "$FILES" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: Run Clang-Tidy on changed files
run: |
for f in ${{ steps.changed.outputs.files }}; do
[ -f "$f" ] && clang-tidy -p build "$f" --warnings-as-errors="*" || true
done
- name: Run Cppcheck on changed files
run: |
FILES="${{ steps.changed.outputs.files }}"
[ -n "$FILES" ] && cppcheck --enable=all --suppress=missingIncludeSystem \
-I./include --error-exitcode=1 $FILES || true
CMake 타겟으로 정적 분석
# CMakeLists.txt에 추가
find_program(CLANG_TIDY_BIN clang-tidy)
find_program(CPPCHECK_BIN cppcheck)
if(CLANG_TIDY_BIN)
add_custom_target(clang-tidy
COMMAND ${CLANG_TIDY_BIN} -p ${CMAKE_BINARY_DIR} ${CMAKE_SOURCE_DIR}/src/*.cpp
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
COMMENT "Running clang-tidy"
)
endif()
if(CPPCHECK_BIN)
add_custom_target(cppcheck
COMMAND ${CPPCHECK_BIN} --enable=all --suppress=missingIncludeSystem
-I${CMAKE_SOURCE_DIR}/include --error-exitcode=1 ${CMAKE_SOURCE_DIR}/src/
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
COMMENT "Running cppcheck"
)
endif()
# 사용: cmake --build build --target clang-tidy
8. 프로덕션 패턴
패턴 1: 점진적 도입
flowchart LR A[1단계: 경고만] --> B[2단계: 신규 코드] B --> C[3단계: WarningsAsErrors] C --> D[4단계: 전체 적용]
- 1단계:
WarningsAsErrors: ''로 경고만 수집, 팀에 공유 - 2단계: 새로 추가되는 코드에만 적용 (
--line-filter또는 경로 필터) - 3단계: 핵심 체크를
WarningsAsErrors에 추가 - 4단계: 전체 프로젝트 적용, 레거시 코드 점진적 수정
패턴 2: 체크 그룹별 분리
# .clang-tidy - 스타일은 경고, 버그는 에러
Checks: 'bugprone-*,modernize-*,performance-*,readability-*'
WarningsAsErrors: 'bugprone-use-after-move,bugprone-narrowing-conversions'
패턴 3: 서드파티 제외
# .clang-tidy
HeaderFilterRegex: '(?!.*/third_party/)(?!.*/external/)'
패턴 4: pre-commit 훅
#!/bin/bash
# .git/hooks/pre-commit
set -e
cmake -B build -S . -DCMAKE_EXPORT_COMPILE_COMMANDS=ON 2>/dev/null || true
for f in $(git diff --cached --name-only --diff-filter=ACM | grep '\.cpp$'); do
[ -f "$f" ] && clang-tidy -p build "$f"
done
cppcheck --enable=all --suppress=missingIncludeSystem -I./include \
$(git diff --cached --name-only --diff-filter=ACM | grep '\.cpp$' | tr '\n' ' ')
패턴 5: 대규모 프로젝트
- incremental: 변경된 파일만 검사
- 캐시: CI에서
compile_commands.json캐시 - 병렬:
run-clang-tidy -j 8,cppcheck -j 8
패턴 6: 에디터 연동
| 에디터 | 방법 |
|---|---|
| VS Code | clangd 확장 (Clang-Tidy 내장), Cppcheck 확장 |
| CLion | Clang-Tidy, Cppcheck 플러그인 |
| Vim | ALE, coc-clangd |
| Emacs | flycheck-clang-tidy |
패턴 7: NOLINT 정책
// NOLINT: 전체 라인 무시
int* p = 0; // NOLINT
// NOLINT(체크이름): 특정 체크만 무시
int* p = 0; // NOLINT(modernize-use-nullptr)
// NOLINTNEXTLINE: 다음 라인 무시
// NOLINTNEXTLINE(modernize-use-nullptr)
int* p = 0;
정책: NOLINT는 임시로만 사용하고, 이슈를 생성해 추후 제거 권장.
패턴 8: SonarQube Quality Gate 전략
| 전략 | 조건 | 용도 |
|---|---|---|
| 엄격 | 새 버그 0, 새 취약점 0, 기술 부채 5% 이하 | 핵심 프로젝트 |
| 완화 | 새 버그 0만 | 일반 프로젝트 |
| 점진적 | 신규 코드에만 적용 | 레거시 도입 |
구현 체크리스트
- [ ] .clang-tidy 생성 및 Checks 설정
- [ ] CMake에 CMAKE_EXPORT_COMPILE_COMMANDS=ON 추가
- [ ] compile_commands.json 생성 확인
- [ ] clang-tidy -p build로 로컬 검사
- [ ] cppcheck --enable=all로 로컬 검사
- [ ] SonarQube 설치 및 C++ 플러그인 설정
- [ ] sonar-project.properties 작성
- [ ] CI 워크플로에 Clang-Tidy·Cppcheck·SonarQube job 추가
- [ ] Quality Gate 설정
- [ ] 브랜치 보호 규칙과 연동
- [ ] 에디터(clangd, Cppcheck 확장) 연동
- [ ] 팀 정책 문서화 ("정적 분석 통과 시 병합")
9. 정리
| 도구 | 역할 |
|---|---|
| Clang-Tidy | 모던 C++·버그·성능 체크, compile_commands 연동·자동 수정 |
| Cppcheck | 컴파일 없이 메모리·널·경계 등 검사, CI에 병렬 실행 |
| SonarQube | 결과 저장·집계·대시보드·Quality Gate·기술 부채 추적 |
핵심: Clang-Tidy·Cppcheck로 실행 전에 버그를 잡고, SonarQube로 품질 트렌드·기술 부채·팀 정책을 숫자로 관리하면, “컴파일은 되는데 나중에 터지는” 상황을 줄일 수 있습니다.
자주 묻는 질문 (FAQ)
Q. 이 내용을 실무에서 언제 쓰나요?
A. 코드 리뷰 자동화, 버그 사전 탐지, 코딩 표준 강제, 기술 부채 관리, 품질 게이트 설정 등에 활용합니다. 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.
Q. Clang-Tidy·Cppcheck·SonarQube를 모두 써야 하나요?
A. Clang-Tidy와 Cppcheck는 보완적이므로 둘 다 사용을 권장합니다. SonarQube는 품질 추적·대시보드·Quality Gate가 필요할 때 추가합니다. 소규모 프로젝트는 Clang-Tidy·Cppcheck만으로도 충분할 수 있습니다.
Q. 선행으로 읽으면 좋은 글은?
A. 각 글 하단의 이전 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.
Q. 더 깊이 공부하려면?
A. cppreference와 Clang-Tidy, Cppcheck, SonarQube 공식 문서를 참고하세요.
한 줄 요약: Clang-Tidy·Cppcheck·SonarQube로 정적 분석을 마스터하고 품질을 숫자로 관리할 수 있습니다.
관련 글
- C++ 문서화 도구 완벽 가이드 | Doxygen·Sphinx
- C++ 코드 리뷰 |
- C++ 디버깅 기초 완벽 가이드 | GDB·LLDB 브레이크포인트·워치포인트로 버그 5분 만에 찾기