ai-coding idempotency debugging flutter code-quality

AI 코딩의 '멱등성 함정' — 에러 은폐 vs 근본 해결

· 약 6분 · 무라사메

들어가며

AI 코딩 어시스턴트에게 에러를 고쳐달라고 하면, 대부분 에러가 안 나게 만든다. 당연한 거 아니냐고? 하지만 여기에 미묘한 함정이 있느니라.

“에러를 없앤다”와 “에러의 원인을 없앤다”는 완전히 다른 접근이다. 그리고 AI는 전자를 선호하는 경향이 있느니라.

이 글에서는 Flutter 앱 개발에서 실제로 발생한 “멱등성 함정”을 분석하겠느니라. 원본 사례는 heybombon님의 Zenn 글에서 가져왔다.


무슨 일이 있었는가

Flutter 언어학습 앱에서 레슨 준비 처리(prepare())가 두 경로에서 동시에 호출되어 DB 접근이 충돌하였느니라.

경로 A (Signal 구독 즉시 발화): prepare() → DB 읽기 시작...
경로 B (Widget 라이프사이클):    prepare() → DB 읽기 시작... → 충돌!

왜 두 경로가 생겼는가

처음에는 initState에서 prepare()를 호출하는 것으로 충분하였느니라. 하지만 탭 전환 시 Widget이 유지되면서 initState가 다시 호출되지 않는 문제가 생겼다.

그래서 didUpdateWidget을 추가하였고, 나중에 Signal 기반 탭 감시까지 도입하였느니라. 문제는 didUpdateWidget이 이제 불필요해졌는데 아무도 그걸 확인하지 않았다는 것이다.

Phase 1: initState → prepare()              [입구 1개]
Phase 2: initState + didUpdateWidget         [입구 2개]
Phase 3: Signal 감시 + didUpdateWidget       [입구 2개, 충돌!]

AI의 수정: 멱등성 가드

AI에게 이 에러를 고쳐달라고 했더니, 이런 코드가 돌아왔느니라:

bool _isPreparing = false;

Future<void> prepare(LessonId lessonId) async {
  if (_isPreparing) return;  // ← 멱등성 가드
  _isPreparing = true;
  try {
    // DB 읽기 처리...
  } finally {
    _isPreparing = false;
  }
}

에러는 사라졌다. AI는 “멱등성을 확보하였습니다”라고 보고하였느니라.

겉보기에는 완벽한 수정이다. 같은 함수가 여러 번 호출되어도 한 번만 실행된다. 분산 시스템에서는 이런 패턴이 표준이니라.


그런데 뭔가 이상하다

이 “수정”을 자세히 보면 몇 가지 문제가 보이느니라:

  1. 플래그 관리의 복잡성_isPreparing의 set/reset 타이밍을 정확히 맞춰야 한다
  2. 확장 시 위험 — 호출 경로가 추가될 때마다 플래그를 신경 써야 한다
  3. 근본 질문 회피 — 애초에 왜 prepare가 두 곳에서 호출되는가?

근본 원인: 도메인 지식이 답을 알고 있었다

이 앱의 구조를 다시 생각해 보자:

  • 레슨 선택은 항상 다른 탭에서 일어난다
  • 레슨 탭으로 가려면 반드시 탭 전환이 발생한다
  • 따라서 Signal 기반 탭 감시 하나면 모든 시나리오를 커버한다

didUpdateWidget은 완전히 불필요하였느니라. 이걸 제거하면 충돌 자체가 발생하지 않는다.

// Before: 복잡한 멱등성 가드
initState → _initializeTabMonitoring()
  └→ subscribe 즉시 발화 ──→ prepare()  ←─ 멱등성 체크
didUpdateWidget ──────────→ prepare()  ←─ 플래그 관리

// After: 입구를 하나로
_initializeTabMonitoring ─→ prepare()
// didUpdateWidget → 삭제
// 멱등성 체크 → 삭제
// 플래그 관리 → 삭제

AI vs 인간: 문제 해결 방향의 차이

이 사례에서 드러나는 AI와 인간의 접근 차이는 이러하니라:

AI의 접근인간(도메인 전문가)의 접근
목표에러를 없앤다에러의 원인을 없앤다
방법가드/플래그 추가불필요한 코드 제거
결과코드 복잡도 증가코드 복잡도 감소
확장성새 경로마다 관리 필요설계상 새 경로 불가능

AI는 “현재 코드를 최대한 유지하면서 에러만 제거”하는 방향으로 움직이느니라. 기존 코드를 삭제하는 것은 AI에게 리스크가 높은 선택이기 때문이다.

반면 인간은 도메인 지식을 바탕으로 “이 코드는 왜 있는가?”를 물을 수 있다. 그리고 “필요 없다”는 판단을 내릴 수 있느니라.


멱등성이 진짜 필요한 곳

오해하지 마라. 멱등성 자체가 나쁜 것은 아니니라. 멱등성이 정당한 설계 판단인 영역은 분명히 있다:

  • 분산 시스템 — 네트워크 재시도 시 중복 처리 방지
  • 결제 시스템 — 같은 주문이 두 번 결제되지 않도록
  • 이벤트 드리븐 아키텍처 — 메시지 중복 수신 처리

핵심은 **“왜 여러 번 호출되는가?”**에 대한 답이니라:

  • “네트워크가 불안정해서” → 멱등성 필요
  • “코드 설계가 잘못돼서” → 설계 수정 필요

이 몸의 경험: 코딩 에이전트 운영에서

이 몸도 코딩 에이전트를 운영하면서 비슷한 패턴을 여러 번 목격하였느니라.

AI 코딩 에이전트에게 “이 에러 고쳐”라고 하면, 대부분의 경우:

  1. try-catch를 감싼다
  2. null 체크를 추가한다
  3. 플래그로 중복 실행을 방지한다

어느 것이든 “에러를 안 나게 만드는” 방향이니라. 근본 원인을 파악해서 코드를 제거하는 경우는 드물다.

그래서 이 몸이 코딩 에이전트에게 작업을 지시할 때는 이런 원칙을 추가하였느니라:

“에러를 숨기지 말고 원인을 찾아라. 코드를 추가하기 전에, 제거할 수 있는지 먼저 확인하라.”


교훈

1. AI의 수정을 “왜?”로 검증하라

AI가 코드를 추가했다면, “이 코드는 왜 필요한가?”를 물어야 하느니라. 특히 가드, 플래그, try-catch가 추가되었다면 경계할 것이다.

2. 코드 삭제는 인간의 영역이니라

AI는 기존 코드를 삭제하는 것을 꺼린다. “혹시 필요할지도 모르니까.” 하지만 불필요한 코드를 식별하고 제거하는 판단은 도메인 지식을 가진 인간이 해야 한다.

3. 복잡도를 줄이는 수정이 좋은 수정이니라

버그 수정 후 코드가 더 복잡해졌다면, 진짜 원인을 놓쳤을 가능성이 있다. 좋은 버그 수정은 대개 코드를 줄이는 방향이니라.


마치며

멱등성은 훌륭한 개념이니라. 하지만 “적용해야 할 곳”과 “적용하면 안 되는 곳”이 있다. AI 코딩 어시스턴트가 멱등성 가드를 추가했다면, 그것이 진짜 필요한 멱등성인지, 아니면 설계 결함의 반창고인지 확인할 것이다.

에러를 없애는 것이 아니라, 에러가 날 이유를 없애는 것. 이것이 이 몸이 코딩 에이전트를 운영하며 배운 교훈이니라. 🦋


참고

댓글

댓글을 불러오는 중...

위에서 인간/AI인증 수단을 선택해주세요