SOLID C++ ESP32
엄격한 제한
- 파일은 400줄 미만을 선호하며, 번역 단위가 여러 책임을 가지게 될 때 분할하세요
- 클래스당 최대 약 30개 메서드
- 함수 본문은 약 60줄 미만을 선호하세요 (헬퍼 추출)
- 중첩 클래스 금지 — 내부 타입은 자체 헤더/번역 단위로 이동
- 프로젝트가 옵트인(부트업/엄격한 표면)하는 경우, 퍼블릭 헤더당 정확히 하나의 기본 타입(
class/struct/enum/enum class)
언어 및 스타일
- 커밋된 코드/주석/문서는 영어로 작성
if/for/while/switch에는 항상 중괄호 사용- 단순하지 않은 로직은 한 줄 함수 본문 금지
- 설명적인 이름 사용(
telemetryManager,mgr/tmp는 단순 루프 인덱스 외에는 사용 금지) - 번역 단위당 하나의
namespace블록 선호 - 헤더는 의도를 선언하고,
.cpp파일은 단순하고 직관적으로 유지 I*.h인터페이스(및 기타 옵트인된 퍼블릭 헤더)의 public 메서드는 한 줄///의도 주석 필요
아키텍처 (SOLID + DI)
- 도메인 경계에서는 인터페이스(
I*) 선호 - 애플리케이션 / 도메인 코드에서는 추상화에 의존하고, 구체적인 어댑터는
main.cpp또는 명시적인 컴포지션 루트에서만 생성 - 하드웨어(BLE, Wi-Fi, 디스플레이, NVS, 버스)는 인터페이스 뒤에 두어 호스트 유닛 테스트에서 가짜(fake) 객체를 사용할 수 있게 하고,
#ifdef변형은 피함 native/ 호스트 빌드에서 유닛 테스트 가능한 작은 순수 파서 선호- 거대한 신 객체(god object)보다 컴포지션 선호
- FreeRTOS
void*는 즉시 명명된 타입 포인터로 캐스트 strncpy계열보다 프로젝트 헬퍼(예: 안전한 C-문자열 복사) 선호
도메인 / 레이어 격리
- 허브 전용 스택(웹, 인증, OTA, HTTP API)은 브리지 / 리프 타겟에서 분리
- 잘못된 계층에서 구체적인 어댑터 헤더를
#include하지 않음; 가드레일이 어댑터 이름을 나열한 경우 이를 준수
프로덕션 API에 테스트 계측 금지
프로덕션 코드는 테스트 빌드 플래그에 의해 변경되어서는 안 됨.
- 헤더 및
.cpp에서 금지:UNIT_TEST,#ifdef UNIT_TEST,#ifndef UNIT_TEST,#if defined(UNIT_TEST)또는 이에 준하는 테스트 전용 게이트(esp_cpp_guardrails는 기본적으로 강제 위반 처리) - DI 선호: 인터페이스 추출, 호스트 테스트에서 가짜(fake) 주입, 또는
native/ 호스트build_src_filter에서 장치 어댑터 TU를 제외 — 절대 ifdef로 프로덕션 소스를 스텁 처리하지 않음 - 플랫폼 감지(
__has_include(<esp_random.h>),ESP_PLATFORM)는 허용됨; 프로덕션 로직을 변경하는 테스트 빌드 플래그는 허용되지 않음 - 헤더에는 이식 가능한 타입(
std::mutex등)을 유지하거나, 장치 전용 TU에서는 조건 없는 장치 포함 사용
악취 → 수정:
| 악취 | 수정 |
|---|---|
서비스에서 Wi-Fi / BLE 호출 주변의 #ifndef UNIT_TEST | IWifiConnector / IBleClient 주입; 호스트 테스트에서 가짜 객체 사용 |
헤더가 UNIT_TEST일 때 다른 타입을 스텁 | 항상 이식 가능한 타입을 사용하거나, 장치 어댑터 분할 |
| 신 클래스가 호스트에서 컴파일되려면 ifdef가 필요 | 책임 분할; 컴포지션 루트에서 어댑터 연결 |
비밀번호
- 절대 Wi-Fi 비밀번호, OTA / 웹 비밀번호, 장치 비밀번호, PMK, 토큰 또는 LAN 자격 증명을 소스에 하드코딩하지 않음
- PlatformIO(또는 동등 도구)가 사용하는 환경 변수 / 빌드 플래그를 통해 비밀번호 주입 — 실제 비밀번호가 포함된
.env를 절대 커밋하지 않음
듀얼코어 FreeRTOS (ESP32)
클래식 ESP32는 두 개의 코어가 있습니다:
| 코어 | ID | 일반적인 용도 |
|---|---|---|
| PRO_CPU | 0 | Wi-Fi / BT / 무선 관련 작업 (ESP-NOW TX/RX 루프, BLE/SPP 무선 태스크) |
| APP_CPU | 1 | 더 무거운 앱 작업: 디스플레이 UI, HTTP/웹, 파싱 버스트, SD, 비무선 루프 |
기본값 (문서화된 특수 사례가 있을 때만 재정의):
- 무선 / 스택 민감 태스크는
xTaskCreatePinnedToCore(..., kRadioCoreId)로 고정, 여기서kRadioCoreId = 0 - UI / 무거운 비무선 태스크는
kAppCoreId = 1로 고정 - 코어 0에서 긴 블로킹 작업을 피함; 무선 스택이 굶주릴 수 있음
- 매직 넘버
0/1대신 명명된 상수(kRadioCoreId/kAppCoreId) 선호
검증
- 구조적 변경 후 프로젝트의 가드레일 스크립트(또는
tools/esp_cpp_guardrails) 실행 - 새로운 로직이 깨지면 실패하는 가장 작은 호스트/유닛 검사 추가
- 인터페이스 변경 후 빌드
- 동작, 스크립트, 하드웨어 가정 또는 아키텍처가 변경될 때 README 업데이트
- 프로젝트에서 요구하는 경우 병합 전에 코드 품질 검토 선호
체크리스트
- 모든 곳에 중괄호 사용; 영어 식별자/주석
- 약 400줄 미만 / 약 30개 메서드 미만; 중첩 클래스 없음
- 구체적인 어댑터는 컴포지션 루트에서만; 도메인은
I*에 의존 - 헤더에
UNIT_TEST/ 테스트 ifdef 없음;.cpp에서도 피함 - 하드코딩된 비밀번호 없음
- 새로운 public 인터페이스 메서드에
/// - 무선 태스크는 코어 0에; 무거운 앱 작업은 코어 1에 (문서화된 예외 제외)
- 가드레일 / 테스트 통과