#7 - AI 코딩의 함정: 검증된 테스트 코드가 제품을 망칠 때

이번 주말 MyStock의 .env를 없애고 JSON Local Config로 바꾸는 작업을 하면서 예상하지 못했던 구조를 하나 발견했다. 처음에는 설정 파일 형식만 바꾸면 되는 작업이라고 생각했다. App Key와 Secret Key, 계좌정보처럼 GitHub에 올릴 수 없는 값을 .env 대신 로컬 JSON에 저장하고, 프로그램 안에서 사용자가 직접 등록할 수 있게 만들면 될 거라고 봤다.

 

그런데 실제 변경 범위를 따라가기 시작하자 수정해야 할 코드가 생각보다 너무 많았다. 단순히 .env를 읽는 부분이 많은 정도가 아니었다. STOCK, ETF, ISA, IRP처럼 테스트와 초기 구현에서 편하게 사용하던 계좌 이름과 슬롯이 프로그램 곳곳에서 계좌를 구분하는 기준처럼 사용되고 있었고, 그 영향은 UI나 API 호출을 넘어 DB의 저장과 복원 구조까지 이어져 있었다.

 

처음에는 왜 이런 구조가 만들어졌는지 이해가 잘 되지 않았다. 그런데 개발 과정을 다시 생각해보니 원인이 꽤 명확했다.

테스트 코드와 개발 코드의 경계

내가 직접 개발할 때 테스트 코드는 기능을 검증하기 위한 코드다. 확인하려는 기능이 내가 의도한 대로 동작하는지 빠르게 판단하는 것이 목적이기 때문에 가능한 한 단순하게 만든다. 예를 들어 여러 종류의 계좌를 구분하는 기능을 검증한다면 테스트에서는 STOCK, ETF, ISA처럼 알아보기 쉬운 고정 문자열을 사용해도 전혀 이상하지 않다. 테스트하려는 조건만 분명하면 되고, 그 코드가 실제 제품의 계좌 구조를 대표할 필요는 없기 때문이다.

 

그 검증이 끝난 뒤 실제 개발 코드에 적용할 때는 이야기가 달라진다. 테스트에서 확인한 동작과 아이디어를 바탕으로 실제 프로그램의 구조, 데이터의 책임, 확장 가능성을 고려해 다시 설계하고 구현한다. 테스트 코드를 그대로 제품 구조로 사용하는 것이 아니라 테스트를 통해 확인한 내용을 제품 구조에 맞게 옮기는 것이다. 나는 이 구분을 너무 당연하게 생각하고 있었다.

검증된 코드 재사용의 함정

그런데 AI가 구현을 이어가는 방식은 내가 생각하던 것과 달랐다. 이미 테스트에서 동작했고 문제가 없었던 코드와 패턴은 AI 입장에서는 매우 강한 근거가 된다. 새 기능을 구현할 때도 기존에 검증된 구조를 다시 이용하는 편이 안전하고, 테스트까지 이미 준비되어 있다면 그 방식을 유지하는 것이 실패 가능성을 낮추는 선택처럼 보일 수 있다.

 

문제는 테스트를 위해 단순하게 만든 코드도 똑같이 “검증된 코드”로 취급될 수 있다는 점이었다. 처음에는 테스트나 제한된 기능에서만 사용하던 계좌 이름과 고정 슬롯이 실제 개발 코드에도 재사용됐고, 이후 새로운 기능이 추가될 때 다시 그 구조를 기준으로 구현이 이어졌다. 그렇게 만들어진 개발 코드를 검증하기 위해 테스트가 추가되면서 어느 순간부터는 원래 테스트를 편하게 하기 위해 사용했던 방식이 오히려 제품의 정상 동작을 정의하는 기준처럼 굳어졌다.

 

겉으로는 문제가 보이지 않았다. 기능은 정상적으로 동작했고 테스트도 계속 통과했다. 새로운 기능을 추가해도 기존 테스트가 깨지지 않았으니 개발은 안정적으로 진행되는 것처럼 보였다. 하지만 내부에서는 테스트를 위한 단순화가 실제 아키텍처로 조금씩 확장되고 있었다.

구현에 관여하지 않은 AI 개발 실험

이 문제가 이렇게 오래 남을 수 있었던 가장 큰 이유는 내가 실제 구현에 거의 관여하지 않았기 때문이다. MyStock은 개인 프로젝트이기도 했고, AI가 어느 정도까지 개발을 맡을 수 있는지 확인해보려는 실험의 성격도 있었다. 나는 요구사항과 프로그램이 최종적으로 어떻게 동작해야 하는지를 정했고, 실제 코드를 수정하고 테스트하는 작업은 거의 전적으로 AI에게 맡겼다. 실제 구현은 Codex가 진행했고 나는 주로 실행 결과와 기능 동작, 테스트 결과를 확인했다. 필요하면 ChatGPT와 문제를 정리하고 다음 작업의 방향을 잡았지만, 구현된 코드를 매번 직접 열어 구조까지 리뷰하지는 않았다.

 

결과가 내가 요구한 대로 나오고 테스트까지 통과하면 다음 기능으로 넘어갔다. 이 방식에서는 테스트를 위한 코드가 실제 개발 코드에 재사용되더라도 중간에서 “이건 테스트에서 편하게 쓰려고 만든 방식인데 왜 제품 구조에서도 그대로 사용하고 있지?”라고 물을 사람이 없었다. AI는 이미 동작한 방식을 계속 활용했고, 나는 결과가 맞는지만 확인했다. 둘이 결합되면서 잘못된 구조를 멈출 계기가 사라진 셈이다.

.env에서 JSON 전환으로 드러난 폭발 반경

문제가 한꺼번에 드러난 것은 .env를 JSON Local Config로 바꾸기 시작하면서였다. 처음 예상대로라면 환경 변수에서 값을 읽던 부분을 새로운 설정 객체로 바꾸고, 사용자가 설정 화면에서 계좌와 인증정보를 입력할 수 있도록 연결하면 됐다. 하지만 작업을 시작하자 생각보다 훨씬 많은 코드가 함께 움직여야 했다.

 

이유를 따라가 보니 .env에는 단순한 인증정보만 들어 있던 것이 아니었다. 계좌가 어떤 슬롯에 들어 있는지, 그 슬롯을 어떤 이름으로 부르는지 같은 가정이 프로그램의 여러 영역과 연결되어 있었다. 더 놀라웠던 것은 계좌 라벨의 하드코딩이 화면 표시 수준에서 끝나지 않았다는 점이다.

 

계좌 이름이나 종류처럼 보였던 값이 실제 계좌를 구분하는 기준처럼 사용되고 있었고, 그 전제가 데이터를 저장하고 다시 복원하는 DB 구조에도 영향을 주고 있었다. 이름은 사용자가 바꿀 수 있는 표시값이어야 하고, 계좌 종류는 현재 설정이 결정할 정보이며, DB의 내부 번호는 DB 안에서 관계를 연결하기 위한 값이어야 하는데 이 역할들이 여러 곳에서 섞여 있었다.

 

결국 .env를 JSON으로 바꾸는 작업은 설정 파일 하나를 교체하는 수준에서 끝날 수 없었다. Account Identity를 다시 정의하고, 계좌 종류와 표시 이름을 분리하고, DB가 무엇을 기억해야 하는지까지 다시 정리해야 했다. 이 작업량이 커진 것 자체보다 더 신경 쓰였던 것은 이런 구조가 기능 오류 없이 오랫동안 유지되고 있었다는 사실이었다.

테스트가 잘못된 구조를 보호하는 순간

이번 사건에서 가장 무서웠던 부분은 테스트가 실패한 것이 아니었다. 오히려 테스트가 계속 잘 통과했다는 점이었다. 테스트 코드에서 시작한 단순한 구조가 개발 코드에 재사용되고, 그 개발 코드를 기준으로 다시 테스트가 만들어지면 이후의 테스트는 원래 의도를 검증하는 것과 동시에 현재 구조를 보호하게 된다. 그러면 어느 순간부터 “테스트를 통과한다”는 사실이 “이 구조가 올바르다”는 것처럼 보이기 시작한다.

 

하지만 둘은 전혀 다른 이야기다. 테스트는 내가 정한 조건에서 현재 구현이 기대한 결과를 내는지를 확인할 수는 있어도, 그 구현의 구조가 제품 전체에 적합한지까지 보장해주지는 않는다. 특히 AI가 이미 성공한 코드와 패턴을 계속 재사용하는 방식으로 개발할 때는 이 차이가 더 크게 느껴졌다. 검증된 코드라는 이유로 같은 구조가 반복되고, 반복될수록 변경 비용이 커지고, 다시 그 구조를 기준으로 테스트가 늘어나면서 처음의 작은 편의가 제품 전체의 전제가 될 수 있었다.

 

처음에는 단순하고 편했던 값이 나중에는 설정 구조와 Account Identity, DB까지 연결된 기준이 되어 있었다.

극단적인 AI 개발 실험의 경계

여기까지 확인하고 나니 한 가지 의문이 생겼다.

AI에게 구현을 거의 전부 맡기는 이런 실험을 계속 진행하는 것이 맞는가?

MyStock 같은 개인 프로젝트라면 아직은 계속해볼 생각이다. 문제가 생겼을 때 그 비용을 내가 감당할 수 있고, 필요하다면 구조를 크게 바꾸거나 DB를 새 기준으로 다시 만들 수도 있다. 무엇보다 이런 극단적인 방식으로 개발해봤기 때문에 AI가 어떤 상황에서 위험한 선택을 할 수 있는지 실제 사례로 확인할 수 있었다.

 

하지만 회사 업무에서는 절대 같은 방식으로 진행하면 안 된다는 판단도 분명해졌다. 회사 코드는 한 사람이 혼자 사용하는 프로그램이 아니다. 다른 개발자가 유지보수해야 하고, 기존 제품이나 양산 코드와 연결될 수 있으며, 잘못된 구조 하나의 비용이 일정과 품질, 이후의 유지보수까지 이어진다. 문제가 발견됐다고 개인 프로젝트처럼 과거 구조를 버리고 다시 시작할 수도 없다. 그런 환경에서 요구사항만 전달하고 실제 구현을 AI에게 맡긴 뒤 “기능이 동작하고 테스트가 통과했으니 됐다”라고 판단하는 것은 위험하다.

 

AI에게 구현을 많이 맡기는 것과 사람이 설계를 확인하지 않는 것은 다른 문제다. 구현 자체는 AI가 할 수 있어도 테스트 코드와 제품 코드의 경계, 데이터의 책임, 모듈 간 의존성, 장기적으로 유지해야 할 구조는 사람이 중간에서 직접 확인해야 한다. 이번 MyStock 작업은 그 차이를 꽤 극단적인 방식으로 보여줬다.

 

AI가 코드를 못 짜서 발생한 사고가 아니었다. 오히려 이미 검증된 코드를 너무 자연스럽게 재사용했고, 기능과 테스트가 계속 정상적으로 동작했기 때문에 문제를 늦게 발견했다. 그리고 내가 실제 구현 코드를 거의 보지 않았기 때문에 그 구조가 제품 전반으로 퍼지는 동안 제동을 걸지 못했다. 개인 프로젝트에서는 이런 위험까지 포함해서 계속 실험해볼 수 있다. 하지만 회사 업무에서는 AI가 작성한 코드가 동작한다는 것만으로 충분하지 않다.

 

검증된 테스트 코드가 제품 코드에서 어떤 의미로 사용되고 있는지, 그리고 그 구현이 실제 구조에 맞는지는 결국 사람이 확인해야 한다.