인간이 LLM보다 코드를 더 잘 작성할 수 없을까? 무엇을 기준으로 비교해야 할까?
요약
LLMs는 그럴듯한 코드를 생성하지만, 실제 시스템의 복잡한 제약 조건이나 엣지 케이스를 놓칠 수 있습니다. 시니어 엔지니어링은 단순히 코딩하는 능력이 아니라, 언제 코드를 작성하지 말아야 하는지를 아는 판단력에 달려있습니다. 코드의 가치는 생산성 지표가 아닌 정확성과 견고함으로 측정되어야 합니다.
핵심 포인트
- LLMs는 그럴듯하게 보일 뿐, 실제 비즈니스 제약 조건을 놓칠 수 있다.
- 시니어 엔지니어링은 코딩 능력보다 판단력과 문제 정의 능력이 핵심이다.
- 코드의 가치는 라인 수가 아닌 정확성(Correctness)과 견고함에 달려있다.
- LLM 생성 코드는 예기치 않은 프로덕션 환경 오류를 유발할 위험이 있다.
한 트윗이 돌았습니다. 그 내용은 "어떤 인간도 LLMs보다 코드를 더 잘 쓸 수 없을 것이다. 이 사실을 받아들이고, 꾸미는 것을 멈추고 넘어가라."였습니다.
여기에 제가 답글을 달았습니다.
질문에 내포된 '더 좋다(better)'라는 단어의 사용이 너무 많습니다. LLMs는 '맞게' 작동하도록 훈련된 것이 아니라, 그럴듯하게(plausible) 보이도록 훈련되었습니다. 아무리 우아하거나/관용적이거나/부드럽게 들리는 코드를 생성하더라도, 실제로 중요한 제약 조건들(기존 시스템, 팀 패턴, 비즈니스 고려 사항, 실패 모드, 아직 현실화되지 않은 기능 등)을 놓칠 수 있습니다.
시니어 레벨의 엔지니어링은 단순히 코드를 '작성'하는 것에 관한 것이 아닙니다. 언제 작성하지 말아야 하는지를 아는 것입니다. 실제 엣지 케이스(edge cases)와 아직 구현되지 않은 추상화 개념을 인식하고, 어떤 문제가 더 간단하게 해결될 수 있는지, 혹은 아예 해결할 필요가 없는 경우를 아는 것입니다. 이러한 종류의 판단력은 정량화하기 어렵고, 프롬프트로 요청하기도 어려우며, 생성된 솔루션에서는 종종 명확하지 않습니다.
따라서 네, LLMs는 유용한 도구가 될 수 있지만, 실제 인간 전문가의 수준에는 한참 미치지 못합니다.
저를 가장 거슬리게 하는 단어는 그 트윗 속의 "better"였습니다. 그래서 저는 이 단어가 무엇을 의미하는지 적어보았습니다.
'Better'는 우리가 스칼라 값(scalar value)처럼 취급하는 단어입니다. 즉, '더 좋다'를 정의하는 하나의 값이 존재한다는 것입니다. 하지만 '무엇을 기준으로 더 낫다고 측정할 것인가?'라고 질문합니다. 분당 라인 수? 정확성? 유지보수성? 그것을 만들지 않을 용기? 당신은 곧 'better'가 어떤 차원을 따라 측정하느냐에 따라 의미가 완전히 달라진다는 것을 깨닫게 됩니다.
생산성 지표는 분당 라인 수입니다. 이는 좋고 간단하며, 측정하기 쉽습니다. 이 지표에는 개선처럼 느껴지기 때문에 많은 추진력이 있습니다. 하지만 이것은 단지 타이핑의 양만을 측정할 뿐입니다. 결과로 나온 프로그램이 더 좋다는 것을 의미하지 않습니다.
잘못된 코드 1000줄은 아예 코드가 없는 것보다 나쁩니다. 심지어 당신이 보는 순간 무너져 버리는 코드 1000줄은 그보다도 더 나쁩니다. 그래서 우리는 정확성(correctness)을 살펴봅니다. 우리의 코드가 '더 좋다'는 것은 그것이 정확하기 때문입니다. 올바른 기능을 가지고 있고, 명세(spec)를 충족합니다. 테스트를 통과합니다. 이것이 개선입니다. 하지만, 그것만으로는 충분하지 않습니다.
진짜 악몽은 LLM이 생성한 코드가 작동하다가 갑자기 작동하지 않는 경우입니다. 그리고 그 이유를 당신은 이해할 수 없습니다. 겉보기에는 그럴듯해 보이는 코드를 얻게 되고, 이 코드는 어찌어떻게든 프로덕션 환경에 도달합니다. 그러다 어느 날 결제 파이프라인에서 거래가 조용히 폐기되기 시작합니다. 모델이 단순히 트랜잭션 핸들러 함수에 retry_on_timeout 데코레이터를 추가하기로 결정했기 때문입니다. 이 데코레이터는 예외를 잡아내고, WARNING을 로깅한 뒤 None을 반환합니다. 결국 후속 코드는 이 None 값을 받아 0달러의 성공적인 청구액으로 읽어버립니다. 결과적으로 고객들은 주문한 물건을 공짜로 받게 되는 상황이 발생합니다.
Gramli는 To Retry or Not to Retry에서 이와 같은 사례를 테스트했습니다. 한 시나리오에서는 결제 요청이 Retry-After 헤더가 포함된 503 에러를 반환했지만, Idempotency Key는 없었습니다. 8개의 모델 중 대부분은 해당 헤더를 따라 재시도하라고 답했습니다. 단 두 개만이 그렇지 않다고 했습니다.
HumanLayer의 Dex는 강연 "Harness Engineering Is Not Enough and Why Software Factories Fail"에서 이러한 현상이 발생하는 이유를 설명합니다. 많은 코딩 벤치마크는 모델에게 통과(pass) 또는 실패(fail) 보상을 제공합니다. 테스트가 통과하면 모델은 승리하고, 나쁜 설계에 대해서는 아무런 페널티도 받지 않습니다. 따라서 모델은 테스트를 통과시키는 트라이/캐치(try/catch) 구문이나 타입 캐스팅(type cast)을 추가하는 방법을 학습하게 됩니다.
우리는 변경할 수 없는 완벽하게 좋은 소프트웨어를 가질 수도 있습니다. 하지만 이것은 우리가 원하는 것과는 정반대입니다. 이것이 바로 레거시 코드(legacy code)이며, 이는 나쁩니다. 우리는 고통 없이 변경할 수 있어야 합니다.
그래서 우리는 그것을 유지보수하기 쉽게 만듦으로써 개선합니다. 유지보수 가능한 코드란 읽기 쉽고 단순한 코드를 의미하며, 다른 사람들에게 사무실을 불태우고 싶게 만들지 않는 코드입니다. 시간이 지나도 고장 나지 않습니다. 유지보수 가능한 소프트웨어는 그저 작동합니다(just works). 새로운 요구사항과 변화에 직면해도 깨지지 않고 대응합니다.
마지막 선택지가 가장 좋습니다. 이것이 바로 지혜의 척도입니다. "아니다, 그렇게 해서는 안 된다"라고 말할 수 있게 해주는 '더 나은' 상태입니다. 적지만 필수적이기 때문에 더 좋은 것입니다.
다시 말해, 코드는 일종의 부채(liability)와 같아서 최소한으로 유지해야 하는 것입니다. 이 '더 나은 것'을 실천하는 것이 가장 어렵습니다. 이는 압력, 예산, 혹은 자신이 훌륭한 아이디어를 가지고 있다고 생각하는 이해관계자에게 '아니오'라고 말할 것을 요구합니다. 이것은 '적을수록 좋다(less is more)'라는 지표입니다. 하지만 무언가를 구축하지 않음으로써 얻는 복리 이자(compounding interest)는 계속해서 증가합니다.
따라서 '더 나은 것'은 단 하나의 것이 아닙니다. 그것은 여러 가지의 요소들로 이루어져 있습니다. 이는 계층 구조(hierarchy)이며, 우리가 경험을 쌓으면서 변화합니다. 우리는 분당 라인 수(lines per minute)에서 정확성(correctness), 유지보수성(maintainability)을 거쳐 궁극적으로는 아예 구축하지 않음(not building)에 이르게 됩니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기