팀원: '이건 왜 이렇게 복잡하게 했어요?' 나: '...이유가 있었어'

팀원: '이건 왜 이렇게 복잡하게 했어요?' 나: '...이유가 있었어'

팀원: ‘이건 왜 이렇게 복잡하게 했어요?’ 나: ’…이유가 있었어’

코드 리뷰 시간

회의실에 모였다. 신입 팀원이 내 코드를 보고 있다.

“선배님, 이 부분요.”

화면을 가리킨다. 인터럽트 핸들러 안에 이상한 딜레이가 있다.

void EXTI_IRQHandler(void) {
    // Clear interrupt flag
    EXTI->PR |= EXTI_PR_PR0;
    
    // WTF delay?
    for(volatile int i=0; i<100; i++);
    
    // Read GPIO
    status = GPIOA->IDR;
}

“이건 왜 이렇게 했어요? 인터럽트 안에서 딜레이는…”

맞는 말이다. 인터럽트는 빨리 끝내야 한다. 교과서에도 나온다.

“이유가 있었어.”

대답하면서 기억을 더듬는다. 뭔가… 있었는데.

6개월 전 그날

2월이었다. 양산 직전.

보드에서 간헐적으로 GPIO 읽기가 실패했다. 10번에 1번꼴.

“왜지?”

오실로스코프를 꺼냈다. 프로브를 GPIO 핀에 댔다.

파형을 봤다. 인터럽트 발생 → GPIO 읽기까지 시간이 너무 짧다.

약 50ns.

하드웨어 팀에 물어봤다.

“GPIO 셋팅 후에 읽을 수 있을 때까지 시간이 얼마나 걸리나요?”

“음… 데이터시트에는 없는데. 실측해보니까 200ns 정도?”

”…”

인터럽트가 발생하는 순간, GPIO는 아직 안정화 안 됐다.

그래서 딜레이를 넣었다. 100 루프면 약 250ns.

문제가 해결됐다. 양산 나갔다.

커밋 메시지에는 이렇게 썼다.

“Add delay for GPIO stabilization (HW issue workaround)”

그런데 지금

“HW 이슈 워크어라운드요?”

신입이 묻는다.

“Rev2 보드부터는 그 문제 없지 않나요?”

맞다. 하드웨어가 개선됐다. 풀업 저항 값을 바꿨던가.

“그러면 이 딜레이는 이제 필요 없는 거 아닌가요?”

맞는 말이다.

“근데 Rev1 보드도 아직 쓰는 곳 있지 않아?”

“그건… 재고 소진용으로 몇 개 있긴 한데…”

애매하다.

지워도 될까? 아니면 조건부 컴파일?

#ifdef HW_REV1
    for(volatile int i=0; i<100; i++);
#endif

이렇게? 근데 누가 매번 매크로 체크할까.

“일단 놔두자.”

“네?”

“건드려서 문제 생기는 것보다는…”

변명처럼 들린다. 나도 안다.

코드 고고학

6개월은 짧은 시간이다. 그런데 기억이 흐릿하다.

커밋 로그를 뒤졌다.

commit a3f5c82
Author: 김펌웨어
Date: Fri Feb 16 23:47:32 2024

Add delay for GPIO stabilization (HW issue workaround)

- Rev1 board GPIO settling time issue
- Measured 200ns required
- Added 250ns delay loop
- See issue #247

Issue #247을 찾았다. 닫혀 있다.

관련 댓글들:

  • HW팀 김과장: “Rev2에서 수정 완료”
  • 나: “확인했습니다”
  • PM: “양산은 Rev2로 진행”

그럼 이제 Rev1 코드는?

재고 확인했다. Rev1 보드 37개 남음.

렌탈 제품 AS용. 언젠가 다 쓸 거다.

근데 그때까지 이 코드를 유지?

워크어라운드의 무덤

내 코드에는 이런 게 많다.

// TODO: Remove after HW fix
// FIXME: Temporary solution
// HACK: Don't ask why
// NOTE: See email from 2023.08.14

다 이유가 있었다.

  • 전원 시퀀스 타이밍
  • I2C 버스 플로팅
  • ADC 노이즈
  • 타이머 클럭 지터

하드웨어 문제를 소프트웨어로 땜질.

그게 펌웨어 개발자의 숙명이다.

시간이 지나면 하드웨어는 개선된다.

근데 코드는 남는다.

“혹시 몰라서” 못 지운다.

쌓인다.

스파게티가 된다.

정리할 시간은 없고

신입이 물었다.

“그럼 이번에 리팩토링하면서 정리할까요?”

하고 싶다. 정말.

근데 일정표를 봤다.

  • 다음 주: 신규 센서 드라이버 추가
  • 2주 후: OTA 업데이트 기능
  • 3주 후: 전력 최적화
  • 4주 후: 필드 테스트

리팩토링 일정은 없다.

“나중에 하자.”

“언제요?”

”…여유 생기면.”

여유는 안 생긴다. 안다.

PM이 회의실에 들어왔다.

“다들 있네. 좋아. 급한 거 하나 있는데.”

또 시작이다.

레거시의 탄생

6개월 전의 나는 급했다.

  • 양산 일정 D-7
  • GPIO 문제 해결 필요
  • 하드웨어 수정 불가 (보드 이미 제작됨)
  • 소프트웨어로 해결해야 함

딜레이 100루프를 넣었다.

테스트했다. 됐다.

커밋했다. 푸시했다.

양산 나갔다.

“나중에 정리하지 뭐.”

그때는 생각했다.

근데 나중은 안 왔다.

지금의 나는 6개월 전의 나를 이해한다.

그리고 6개월 후의 나도 지금의 나를 이해할 거다.

“왜 이걸 안 지웠지?”

”…이유가 있었어.”

주석이라도

결국 타협했다.

코드는 남기되, 주석을 자세히 달았다.

void EXTI_IRQHandler(void) {
    EXTI->PR |= EXTI_PR_PR0;
    
    /**
     * [WORKAROUND] GPIO stabilization delay
     * 
     * Context: Rev1 board HW issue (resolved in Rev2)
     * - GPIO settling time: ~200ns required
     * - Interrupt to GPIO read: ~50ns (too fast)
     * - This delay: ~250ns (100 loops at 72MHz)
     * 
     * Status: Required for Rev1 boards (37 units remaining)
     * Reference: Issue #247, Email thread 2024.02.16
     * 
     * TODO: Remove when Rev1 inventory depleted
     *       Estimated: Q2 2025
     */
    for(volatile int i=0; i<100; i++);
    
    status = GPIOA->IDR;
}

신입이 봤다.

“오, 이러면 나중에 봐도 알겠네요.”

“응. 그게 최선이야.”

주석이 코드보다 길다.

근데 이게 맞다. 미래의 나를 위해.

6개월 뒤에 또 누가 물어볼 거다.

“이건 왜 이렇게 했어요?”

그때 나는 이 주석을 보여주면 된다.

“이유가 여기 있어.”

펌웨어 개발자의 기억

웹 개발은 다르다.

친구가 말했다. 프론트엔드 개발자.

“우리는 레거시 코드 그냥 지워. 빌드 깨지면 고치면 되지.”

부럽다.

“배포 잘못해도 롤백하면 되잖아.”

우리는 못 한다.

펌웨어는 하드웨어에 묶여 있다.

  • 보드 리비전
  • 양산 시기
  • 재고 현황
  • AS 정책

다 고려해야 한다.

코드 한 줄을 지우려면:

  1. 언제 들어간 코드인가
  2. 왜 들어갔는가
  3. 어느 하드웨어 버전에 해당하는가
  4. 그 하드웨어가 아직 쓰이는가
  5. 지워도 안전한가

5단계 검증.

그래서 안 지운다.

“혹시 몰라서.”

다음 프로젝트에서는

회의가 끝났다.

신입이 물었다.

“다음 프로젝트에서는 이런 거 어떻게 관리할까요?”

생각해봤다.

“처음부터 조건부 컴파일로 가야겠지.”

#if (HW_REVISION == 1)
    workaround_rev1_gpio_delay();
#endif

“문서화도 확실히 하고.”

워크어라운드 전용 문서. 스프레드시트로 관리.

  • 날짜
  • HW 리비전
  • 문제 설명
  • 해결 방법
  • 제거 예정일

“그리고 정기적으로 리뷰하는 시간을 잡자.”

“진짜요?”

”…잡을 수 있으면.”

현실은 안다. 또 급하게 될 거다.

근데 시도는 해야지.

그래도

퇴근길.

오늘 딜레이 코드는 안 지웠다.

주석만 달았다.

찝찝하다.

근데 어쩌겠어.

이게 펌웨어 개발이다.

하드웨어의 역사를 코드로 짊어지는 일.

6개월 전의 나, 1년 전의 나, 2년 전의 나.

다 코드 안에 있다.

“이건 왜 이렇게 했어요?”

“이유가 있었어.”

항상 있었다.

지금도, 앞으로도.



주석이 코드보다 긴 날이 온다. 그게 성장이다.