📋 임베디드 코딩 컨벤션 v1.0

C/C++ (ESP32, ARM Cortex) | Python (테스트/스크립트)

목표: 안전성, 가독성, 유지보수성 확보 + 레거시 코드 통합

1. C/C++ 컨벤션

1.1 파일 구조

헤더 파일 (.h)

#ifndef PROJECT_MODULE_H
#define PROJECT_MODULE_H

// 1. 시스템 헤더
#include <Arduino.h>
#include <stdint.h>

// 2. 외부 라이브러리
#include <BLEDevice.h>

// 3. 프로젝트 헤더
#include "config.h"

// 4. 매크로 상수
#define MAX_BUFFER_SIZE 256

// 5. 타입 정의
typedef enum {
  STATE_IDLE,
  STATE_BUSY
} SystemState_t;

// 6. 전역 변수 선언 (extern)
extern volatile bool g_interruptFlag;

// 7. 함수 선언
void init_system(void);
bool process_command(const char* cmd);

#endif // PROJECT_MODULE_H

소스 파일 (.c/.cpp)

// 1. 대응 헤더
#include "module.h"

// 2. 기타 헤더들
#include <string.h>

// 3. 파일 스코프 상수
static const uint32_t TIMEOUT_MS = 5000;

// 4. 파일 스코프 변수
static uint8_t s_buffer[MAX_BUFFER_SIZE];
static volatile bool s_dataReady = false;

// 5. 함수 구현 (public 먼저, static 나중)
void init_system(void) {
  // ...
}

1.2 네이밍 규칙

대상 규칙 예시 비고
매크로/상수 UPPER_SNAKE_CASE LED_PIN, MAX_RETRY_COUNT #define, const 전역
전역 변수 g_camelCase g_systemState, g_bufferIndex 접두사 g_ 필수
정적 변수 s_camelCase s_timestamp, s_configMode 파일 스코프, 접두사 s_
지역 변수 camelCase retryCount, isValid 함수 내부
함수 snake_case init_hardware(), read_sensor() 동사+명사 조합
타입/구조체 PascalCase_t SensorData_t, ConfigState_t 접미사 _t 권장
클래스 PascalCase BleScanner, RelayController C++만 해당
열거형 멤버 UPPER_SNAKE_CASE CMD_OPEN, STATE_IDLE 타입 접두사 권장
⚡ 실시간 코드 특별 규칙:
• ISR(인터럽트) 함수: ISR_ 접두사 (ISR_timer_callback)
• 하드웨어 레지스터 접근: HW_ 접두사 (HW_set_gpio)

1.3 포맷팅

들여쓰기

중괄호

✅ 권장: K&R 스타일 (기존 코드 스타일)
if (condition) {
  action();
} else {
  other();
}

// ⚠️ 단일 문장도 중괄호 사용 (MISRA-C 준수)
if (flag) {
  return;  // 중괄호로 감싸기
}

줄 길이

1.4 안전성 규칙 (MISRA-C 핵심)

1) 포인터 안전

// ✅ NULL 체크 필수
void process_data(const uint8_t* data, size_t len) {
  if (data == NULL || len == 0) {
    return;  // 조기 리턴
  }
  // 처리 로직
}

// ⚠️ const 키워드로 의도 명확화
const char* get_version(void);  // 읽기 전용 반환

2) 정수 오버플로우 방지

// ❌ 나쁜 예
uint8_t counter = 255;
counter++;  // 오버플로우!

// ✅ 좋은 예
if (counter < UINT8_MAX) {
  counter++;
}

3) 부동소수점 금지

4) goto 금지

⚠️ 예외: 에러 처리 정리 패턴만 허용
int init_system(void) {
  if (!init_hw()) goto cleanup;
  if (!init_ble()) goto cleanup;
  return 0;
  
cleanup:
  deinit_all();
  return -1;
}

1.5 실시간 제약 고려사항

타이밍 크리티컬 코드

// ✅ 인터럽트 핸들러는 최소한으로
void IRAM_ATTR ISR_gpio_handler(void) {
  g_interruptFlag = true;  // 플래그만 설정
  // ❌ Serial.print() 금지
  // ❌ 동적 메모리 할당 금지
  // ❌ 복잡한 연산 금지
}

// ✅ 메인 루프에서 처리
void loop() {
  if (g_interruptFlag) {
    g_interruptFlag = false;
    handle_interrupt();  // 실제 처리
  }
}

타이밍 측정

// ✅ millis() 오버플로우 안전 비교
unsigned long current = millis();
if ((current - lastTime) >= INTERVAL_MS) {
  lastTime = current;
  do_periodic_task();
}

1.6 메모리 관리

동적 할당 최소화

// ❌ 피하기 (프래그멘테이션 위험)
char* buffer = (char*)malloc(256);

// ✅ 권장: 정적 할당
static char buffer[256];

// ✅ 스택 사용 (작은 버퍼만)
char temp[32];

1.7 주석 규칙

파일 헤더

/**
 * @file    relay_controller.c
 * @brief   BLE 기반 릴레이 제어 모듈
 * @author  [이름]
 * @date    2025-10-20
 * @version 1.0
 * 
 * @note    ESP32 전용, FreeRTOS 미사용
 */

함수 주석

/**
 * @brief BLE 광고 데이터에서 명령어 추출
 * 
 * @param[in]  data     광고 데이터 버퍼
 * @param[in]  dataLen  버퍼 길이
 * @param[out] cmdOut   추출된 명령어 (최소 16바이트)
 * 
 * @return true  명령어 추출 성공
 * @return false 데이터 형식 오류
 * 
 * @warning cmdOut 버퍼 오버플로우 체크 필수
 * @note    2바이트 헤더는 제외하고 파싱
 */
bool parse_ble_command(const uint8_t* data, size_t dataLen, char* cmdOut);

인라인 주석

✅ 왜(Why)를 설명
delay(100);  // 릴레이 안정화 대기 (데이터시트 Figure 3 참조)
❌ 무엇(What)을 반복하지 말 것
int count = 0;  // count를 0으로 초기화 ← 불필요!

1.8 에러 처리

반환값 규칙

// ✅ 성공/실패를 명확히
typedef enum {
  ERR_OK = 0,
  ERR_INVALID_PARAM = -1,
  ERR_TIMEOUT = -2,
  ERR_HW_FAULT = -3
} ErrorCode_t;

ErrorCode_t send_command(const char* cmd) {
  if (cmd == NULL) {
    return ERR_INVALID_PARAM;
  }
  // ...
  return ERR_OK;
}

// ✅ 호출 측에서 항상 체크
ErrorCode_t ret = send_command(buffer);
if (ret != ERR_OK) {
  Serial.printf("Error: %d\n", ret);
  // 복구 로직
}

2. Python 컨벤션

2.1 스타일 가이드

2.2 네이밍

# 모듈/패키지: lowercase
# test_utils.py

# 상수: UPPER_SNAKE_CASE
MAX_RETRIES = 5
DEFAULT_TIMEOUT_SEC = 3.0

# 함수/변수: snake_case
def parse_serial_data(raw_bytes: bytes) -> dict:
    result_dict = {}
    return result_dict

# 클래스: PascalCase
class FirmwareFlasher:
    def __init__(self, port: str):
        self.port = port

2.3 타입 힌트 (Python 3.7+)

from typing import List, Optional, Tuple

def process_logs(
    log_file: str,
    filter_level: Optional[str] = None
) -> Tuple[int, List[str]]:
    """로그 파일 파싱 및 필터링
    
    Args:
        log_file: 로그 파일 경로
        filter_level: 필터링할 로그 레벨 (예: "ERROR")
    
    Returns:
        (총 라인 수, 필터링된 메시지 리스트)
    """
    return 0, []

2.4 펌웨어 인터페이스

import serial
import time

class DeviceInterface:
    BAUDRATE = 115200
    TIMEOUT_SEC = 2.0
    
    def __init__(self, port: str):
        self.ser = serial.Serial(
            port=port,
            baudrate=self.BAUDRATE,
            timeout=self.TIMEOUT_SEC
        )
    
    def send_command(self, cmd: str) -> bool:
        """명령어 전송 및 ACK 대기"""
        try:
            self.ser.write(f"{cmd}\n".encode())
            response = self.ser.readline().decode().strip()
            return response == "OK"
        except serial.SerialException as e:
            print(f"Serial error: {e}")
            return False

2.5 테스트 코드 (pytest)

import pytest

def test_command_parsing():
    # Given
    raw_data = b"abcd1234-open"
    
    # When
    cmd = parse_command(raw_data)
    
    # Then
    assert cmd == "open"
    assert is_valid_command(cmd)

3. Git 워크플로우

3.1 브랜치 전략 (GitHub Flow 간소화)

main (배포 가능 상태)
  ├─ feature/ble-scan-improve
  ├─ fix/relay-timing-bug
  └─ test/integration-pytest

브랜치 네이밍

타입 용도 예시
feature/ 새 기능 개발 feature/add-config-mode
fix/ 버그 수정 fix/relay-timing-bug
test/ 테스트 추가 test/integration-pytest
docs/ 문서 작업 docs/api-reference
refactor/ 코드 개선 refactor/split-functions

3.2 커밋 메시지 (Conventional Commits)

형식

<타입>: <제목> (50자 이내)

<본문> (선택, 72자 줄바꿈)

<푸터> (선택)

타입

예시

feat: EEPROM 기반 UUID 저장 기능 추가

- 첫 부팅 시 설정 모드 진입
- GPIO22 3초 롱프레스로 공장 초기화
- ConfigCallbacks로 BLE 설정 수신

Closes #12

3.3 코드 리뷰 프로세스

  1. PR(Pull Request) 생성
  2. AI 도구로 1차 검토 (Claude/GitHub Copilot)
  3. 팀원 리뷰 (최소 1명)
  4. 수정 반영 후 main에 머지

4. 코드 리뷰 체크리스트

4.1 AI 활용 검토 (1차)

💡 Claude에게 요청할 내용:
  1. 이 코드에서 버퍼 오버플로우 위험이 있나요?
  2. 타이밍 경쟁 상태(race condition)가 있나요?
  3. MISRA-C 위반 사항은?
  4. 메모리 누수 가능성은?

4.2 사람 리뷰 (2차)

기능성

안전성

성능

가독성

5. 문서화 규칙

5.1 README.md 템플릿

# 프로젝트명

## 개요
- 목적: BLE 기반 원격 릴레이 제어
- 타겟: ESP32-WROOM-32

## 하드웨어 구성
- ESP32 DevKit v1
- 릴레이 모듈: GPIO19(CLOSE), GPIO21(OPEN)
- 리셋 버튼: GPIO22

## 빌드 방법
```bash
pio run -t upload
pio device monitor
```

## 설정
1. GPIO22를 GND에 연결 후 부팅 → 공장 초기화
2. "ESP32-Config" BLE 장치 연결
3. Characteristic FFF1에 UUID prefix 전송 (예: "myid1234")

## 테스트
```bash
pytest tests/
```

## 라이센스
MIT

5.2 API 문서 (Doxygen)

/**
 * @defgroup RelayControl 릴레이 제어 모듈
 * @{
 */

/**
 * @brief 릴레이 펄스 출력
 * 
 * @param[in] pin      GPIO 번호
 * @param[in] duration 펄스 지속 시간 (ms)
 * 
 * @return void
 * 
 * @pre  init_relay()가 호출되어야 함
 * @post 지정된 시간 후 자동으로 릴레이 OFF
 * 
 * @code
 * pulse_relay(OPEN_PIN, 500);
 * @endcode
 */
void pulse_relay(int pin, unsigned long duration);

/** @} */

6. 도구 설정

6.1 .editorconfig

root = true

[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

[*.{c,cpp,h}]
indent_style = space
indent_size = 2

[*.py]
indent_style = space
indent_size = 4
max_line_length = 88

6.2 .clang-format (C/C++)

BasedOnStyle: Google
IndentWidth: 2
ColumnLimit: 80
AllowShortFunctionsOnASingleLine: None

6.3 pyproject.toml (Python)

[tool.black]
line-length = 88

[tool.pylint]
max-line-length = 88
disable = ["C0111"]  # missing-docstring 경고 off

[tool.pytest.ini_options]
testpaths = ["tests"]

7. 예외 상황 및 레거시 코드

7.1 점진적 적용

코드 유형 적용 수준 설명
신규 코드 100% 적용 본 컨벤션 완전 준수
레거시 코드 수정 시 부분 적용 수정하는 부분만 컨벤션 적용
긴급 핫픽스 리뷰 간소화 배포 후 정리 작업

7.2 컨벤션 위반 허용 (명시 필요)

// NOLINT: 성능 최적화를 위해 인라인 어셈블리 사용
asm volatile("nop");

// TODO(yourname): MISRA 준수로 리팩토링 필요 (#이슈번호)
global_var++;  // 경쟁 상태 위험

부록: 작업 체크리스트

코드 작성 전

커밋 전

PR 생성 전

버전 히스토리

버전 날짜 변경사항
v1.0 2025-10-20 초안 작성 (ESP32/ARM Cortex 지원)