← 모든 게시물

더 나은 코드를 생성하기 위해 코드를 이해하기

FiboCode를 만들면서 내 프로젝트를 이해하고 있는지가 점점 더 중요해졌다. 자연어 프로그래밍, 모호한 요구사항, 아키텍처를 바탕으로 AI와 오래 협업하는 방법에 대한 생각.

내 프로젝트를 내가 이해하지 못한다는 느낌이 싫다.

요구사항은 내가 정했고 기능도 내가 원한 것이다. AI가 코드를 생성한 뒤 제대로 동작하는지도 확인했다. 그런데 이 기능이 어떤 모듈을 거치는지, 왜 이렇게 구현했는지, 다른 부분을 바꾸면 어디에 영향을 주는지 물으면 명확히 답하지 못할 때가 있다.

FiboCode를 만들면서 이 문제가 점점 더 신경 쓰이기 시작했다.

물론 AI가 코드를 더 많이 작성해 주면 좋겠다. 하지만 이 프로젝트는 계속 만들어 갈 생각이다. 오늘 기능을 완성하면 내일 또 다른 아이디어가 떠오른다. 수정할 때마다 다른 곳이 망가지지 않기를 기도하고 싶지는 않다.

A를 고치면 B에 문제가 생긴다. B를 고치면 A에 새로운 버그가 나온다. 그럴 때는 증상을 다시 AI에 보내고 다음 설명과 수정안을 기다리기 쉽다.

하지만 몇 번 반복하고 나면 오히려 더 불안해진다. 각각의 수정은 그럴듯하다. 그런데 모두 합치면 시스템은 대체 어떤 상태가 된 걸까?

그걸 이해하고 싶다. 이 질문을 따라가다 보니 코드 이해와 코드 생성이 처음 생각했던 것보다 훨씬 가깝게 연결되어 있다는 생각이 들었다.

자연어 프로그래밍을 보며 떠오른 생각

기계어에서 어셈블리어, 다시 고급 언어로 옮겨 오면서 우리는 계속 추상화를 해 왔다.

예전에는 직접 처리하던 세부 사항을 이제는 도구에 맡긴다. 더 적은 코드로 복잡한 기능을 표현하고, 매번 밑바닥부터 시작하지 않아도 된다.

이제 한 단계 더 올라갔다. 자연어로 요구사항을 설명하고 AI가 코드를 생성하게 한다.

언뜻 보면 표현 방식만 달라진 것 같다. 하지만 그냥 넘길 수 없는 차이가 있다고 생각한다.

어셈블러와 컴파일러는 명확한 규칙에 따라 코드를 변환한다. 프로그램에 버그가 없다는 뜻은 아니다. 다만 변환 과정 자체가 내가 무엇을 하려 했는지 추측할 필요는 없다.

모델은 추측해야 한다.

내 말과 눈앞의 코드, 문맥을 바탕으로 의도를 추론하고 구현을 선택한다. 같은 문장에서도 다른 결과가 나올 수 있다. 결과가 매번 같더라도 내가 명확히 말하지 못한 요구사항을 계속 구현하고 있을 수도 있다.

나중에 Birgitta Böckeler의 글을 읽었는데, 거기서도 이 차이를 이야기했다. 대규모 언어 모델은 추론으로 결과를 생성하므로 자연어 컴파일러처럼 그대로 다룰 수 없다는 것이다. 내가 고민하던 문제와 가까웠다.

그러면 무엇을 할 수 있을까?

요구사항을 분명히 쓰고, 규칙을 남기고, 중요한 동작을 테스트하면 일부 어긋남은 줄일 수 있을 것이다. 설계에서 모듈의 협력 방식과 관련 클래스, 메서드를 설명해 두면 AI도 구현의 근거를 더 얻는다.

다만 문서는 코드와 맞아야 한다. 이미 오래전에 바뀐 설계를 자세히 설명하는 문서는 아예 없는 것보다 더 곤란할 수 있다.

이 방법들로 생성의 불확실성을 전부 없앨 수 있다고 생각하지는 않는다. 그래도 모델이 그 자리에서 추측해야 할 일은 줄일 수 있다.

내가 무엇을 바꾸려는지 정말 알고 있을까?

생각을 이어 가면 또 다른 문제를 만난다. 사람도 요구사항을 분명하게 설명하기 어렵다.

코드를 잘 모를 때는 특히 그렇다.

화면에 버튼 하나를 더 놓거나 어떤 조작을 편하게 만들고 싶다는 건 분명할 수 있다. 하지만 기존의 어떤 모듈에 넣어야 하는지, 어떤 처리를 거쳐야 하는지, 어떤 기존 동작을 유지해야 하는지는 모를 수 있다.

그런 상태에서 AI에게 “다른 기능에 영향 주지 마”라고 반복한들, 그 말을 어떻게 해석해야 할까?

예를 들어 “여기를 통일해 줘”는 표시 방식을 맞추라는 뜻일 수도, 데이터 처리 로직을 합치라는 뜻일 수도 있다. 두 해석의 수정 범위는 크게 다르다.

나조차 범위를 설명하지 못하면 모델은 보이는 것에서 채워 넣을 수밖에 없다. 예상보다 많은 곳을 바꾸거나, 내가 존재조차 몰랐던 약속을 우회할 수 있다.

수정 뒤 문제를 발견하면 또 고치게 한다. 변경이 쌓일수록 최초 요구와 중간에 추가한 제약, 코드에 원래 있던 관계가 모두 온전히 문맥에 남아 있지는 않을 수 있다.

그러다 부분적으로는 동작하지만 이전 문제를 고치느라 남긴 땜질로 가득한 코드가 될까 걱정된다. AI만 계속 수정할 의향이 있고 사람은 손대기 두려워하는 코드 말이다.

그런 코드는 AI slop을 떠올리게 한다. 더 곤란한 점은 다시 AI에 맡겨도 모델이 시스템 전체를 제대로 이해한다는 보장이 없다는 것이다. 설명을 또 써 준다고 해서 우리가 마침내 이해한 것은 아니다.

그래서 요구를 정확히 말하려면 우선 프로젝트의 전체적인 모양부터 알아야 한다고 느끼게 됐다.

처음부터 모든 세부 사항을 알 필요는 없다. 그래도 내가 원하는 변화가 어디에 들어갈지는 알고 싶다.

아키텍처라는 층에서 AI와 대화하고 싶다

AI를 쓰려면 생성된 코드를 한 줄씩 전부 검토해야 한다고 주장하는 것은 아니다.

프로젝트가 복잡해지면 사람의 집중력에도 한계가 있다. 먼저 뼈대를 알고 싶다. 핵심 모듈은 무엇인지, 각각 무엇을 맡는지, 기능이 어디에서 시작해 어떤 처리를 거치고 어디에서 데이터와 상태를 바꾸는지 말이다.

고급 언어로 된 구현 위에 시스템을 이해하는 층을 하나 더 정리하고 싶다. 일단 “아키텍처 층”이라고 부르지만 이름 자체에는 크게 신경 쓰지 않는다.

중요한 것은 이 층이 실제 코드와 연결되어 있어야 한다는 점이다.

모듈을 보면 구현을 찾을 수 있고, 기능 흐름을 보면 어떤 클래스와 메서드를 거치는지 알 수 있다. 동작을 바꾸고 싶으면 관계를 따라 관심을 둘 위치를 찾을 수 있다.

그러면 AI와 더 구체적인 대화를 할 수 있다.

가령 어떤 작업이 이미 공통 작업 큐를 거친다는 사실을 알면 새 기능도 그 흐름을 이용할지 논의할 수 있다. AI가 상태 관리 로직을 하나 더 만들었다면 기존 것은 왜 못 쓰는지, 두 체계가 나중에 영향을 주고받지는 않을지 물을 수 있다.

그런 질문을 해 본 적이 없다면 실행되는 구현을 그냥 받아들였을 것이다. 이제는 적어도 불필요하게 복잡해지는지 미리 살펴볼 기회가 생긴다.

이런 비교를 통해 내 코드 작성 능력도 나아질 수 있다고 생각한다.

기능을 만드는 방법이 여럿인데 이 프로젝트는 왜 이 방법을 골랐을까? 추상화는 멋져 보이지만 수정할 때마다 여러 곳을 건드려야 한다면 도움이 되는 걸까? 반대로 평범한 모듈이 여러 차례 요구 변경에도 다른 부분을 흔들지 않았다면 무엇을 잘한 걸까?

시간을 들여 생각할 만한 질문들이다. 내 판단이 생기면 AI에 무엇을 조정할지 말하기 쉬워지고, 다른 제안도 더 잘 이해할 수 있다.

물론 내가 이해한 아키텍처도 틀릴 수 있다. 그림의 연결은 코드에서 확인해야 한다. 영향이 없다고 여긴 기능도 테스트하고 실제로 실행해 봐야 한다. 중요한 상태나 복잡한 경계를 만나면 구현을 더 깊이 살펴본다.

대부분은 어디를 봐야 하는지 알고, 아직 모르는 곳도 알고 있는 상태에 조금씩 가까워지고 싶다.

FiboCode의 코드 분석 기능을 만드는 이유도 여기에 있다.

코드와 문서, 관계도를 연결해서 낯선 구현에서 뼈대를 파악하고 하나의 기능 흐름을 따라갈 수 있게 하고 싶다. 그런 이해를 가지고 AI와 수정을 논의하면 마음이 조금 놓일 것 같다.

Unmesh Joshi도 What Is Code?에서 좋은 추상화와 타입, 테스트, 명확한 규칙이 모델이 코드를 이해하고 바꾸는 근거가 될 수 있다고 이야기한다. 나도 이 방향에 공감한다. 시스템에 대한 이해를 되도록 프로젝트에 남겨서 다음 변경에서도 쓸 수 있으면 좋겠다.

그래도 코드 읽는 데 시간을 쓰고 싶다

AI는 빠르게 코드를 쓴다. 다음 일도 곧바로 맡기고 싶어진다.

기능은 하나씩 완성되고 프로젝트는 계속 나아가는 것처럼 보인다. 하지만 그 일이 끝나고 내가 무엇을 더 이해하게 됐는지도 신경 쓰인다.

요구를 말하고 결과를 받기만 하다가 프로젝트가 감당하기 어려워진 뒤 오랜 시간 수리하거나 처음부터 다시 만든다면, 앞서 아낀 시간을 다시 쓰는 셈일 수 있다. 모델 호출 비용과 구현을 다시 익히는 수고도 든다.

그중 일부는 차분히 코드를 읽는 데 쓰고 싶다.

한꺼번에 많이 읽지 않아도 된다. 모듈을 왜 이렇게 나눴는지, 기능에 왜 이런 단계가 필요한지, 이번에 AI가 쓴 낯선 방법은 무엇인지부터 알아볼 수 있다. 모르겠으면 묻고 코드에서 근거를 찾고 조금 바꿔 보며 이해가 맞는지 확인한다.

자료를 찾다가 Anthropic의 학습 실험을 봤다. AI를 이용해 낯선 프로그래밍 과제를 마친 참가자들은 직후 평가에서 직접 코딩한 집단보다 평균 점수가 낮았다. 작은 규모의 실험이라 장기적인 능력 변화를 말해 주지는 않는다. 그래도 과제를 마친 뒤 내가 정말 배웠는지 더 생각하게 됐다.

그런 이해가 조금씩 쌓이면 좋겠다. 비슷한 설계를 다시 만났을 때 원리를 알아보고, AI의 제안에도 근거 있는 질문을 할 수 있기를 바란다.

FiboCode가 그 일을 잘 도울 수 있을지는 더 탐색해야 한다. 하지만 코드 분석을 만들고 싶은 이유의 큰 부분은 확실히 여기에 있다.

앞으로도 AI와 코드를 쓰고 싶다. 그리고 프로젝트가 커진 뒤에도 내가 어떻게 동작하는지 설명할 수 있으면 좋겠다.


이 생각을 간단히 그리면 다음과 같다.

함께 읽을 만한 자료