에이전트 시대에 코드 작성, 코드 리뷰, 프레임워크, 관찰 가능성, 그리고 소프트웨어 엔지니어링의 역할이 어떻게 바뀌는지에 대한 고찰입니다.
저는 인생의 대부분 동안 코드를 작성해 왔고, NestJS의 첫 번째 버전의 모든 줄은 손으로 입력했습니다. 그래서 이 글을 쓰는 일이 조금 이상하게 느껴지지만, 우리는 더 이상 코드를 손으로 작성하지 않게 될 것이라고 생각합니다. 적어도 대부분은 그럴 것입니다. 그리고 그 일은 아주 곧 일어날 것이라 믿습니다. 무엇을 작업하는지와 사용할 수 있도록 허용된 도구에 따라 아직은 그렇지 않은 분들도 있겠지만, 더는 몇 년의 문제가 아니라고 생각합니다.
이 글은 발표가 아니며, 끝에 설치할 패키지도 없습니다. 그저 몇 주 동안 머릿속에 있던 생각들입니다. 이제는 적어 둘 때가 되었다고 생각했습니다.
여기저기에서 조금씩 개입하는 경우를 제외하면, 그렇지 않을 것이라 믿습니다.
에이전트는 잘 설명된 작업을 받아 처음부터 끝까지 처리할 만큼 충분히 좋아졌습니다. 코드베이스를 읽고, 변경하고, 테스트를 실행하고, 깨진 부분을 고친 뒤 다시 시도합니다. 또한 우리보다 훨씬 빠릅니다. 예전에는 제게 오후 한나절이 걸리던 일이 이제는 몇 분이면 끝나며, 그런 작업을 여러 개 동시에 실행할 수도 있습니다.
제 작업의 예를 하나 들어 보겠습니다. 에이전트는 SQL 쿼리 디버깅을 정말 잘하게 되었습니다. 쿼리를 작성하고, EXPLAIN을 실행하고, 느린 부분을 파악해 최적화합니다. 문제를 재현해야 할 때는 로컬에서 재현할 수 있도록 로컬 데이터베이스에 수백만 건의 레코드를 시드합니다. 이런 즉각적인 피드백 루프가 있으니 문제가 해결될 때까지 계속 반복합니다. 일반적인 엔지니어라면 이런 일을 조사하는 데 몇 시간이 걸리겠지만, 에이전트는 몇 분 만에 끝냅니다.
아마 코드의 양은 결코 병목이 아니었고 품질이 병목이었다고 말할 것입니다. 첫 번째 부분에는 동의합니다. 여기에는 진짜 이야기가 있다고 생각하므로 나중에 다시 돌아오겠습니다. 하지만 품질이 우리에게 유리한 논거인지에 대해서는 더 이상 확신하지 못하겠습니다.
과거에는 사람이 작성한 코드보다 AI 보조 코드에서 더 많은 문제가 발견된다는 연구들이 있었지만, 이제는 아무도 사용하지 않는 모델을 사용한 연구였고, 오늘날의 최전선 LLM은 훨씬 더 뛰어납니다. 1년 전만 해도 저 역시 에이전트에게 실제 작업을 맡기는 데 주저했습니다. 주로 자동 완성을 위해 LLM을 사용했습니다. 하지만 완전히 달라졌습니다. 테스트를 실행하고, 로그를 읽고, 명확한 규칙이 있는 코드베이스에서 스스로의 작업을 반복 개선하는 오늘날의 에이전트는 아주 다르게 보입니다. 반면 우리는 피곤해지고, 오후 6시에는 예외 상황을 건너뛰며, 잘못된 변수를 복사해 붙여 넣습니다. 에이전트에게 명확한 제약과 작업을 검증할 방법을 제공한다면, 이미 우리보다 실수할 가능성이 낮으며 그 격차는 계속 커질 것이라 믿습니다.
다만 그 “만약”은 매우 중요합니다. 에이전트는 주어진 작업만큼만 뛰어나므로, 가장 중요한 역량은 코드 작성에서 코드 설명으로 옮겨갑니다. 무엇을 만들어야 하는지, 무엇이 바뀌면 안 되는지, 어떤 절충이 괜찮은지, 그리고 어떻게 완료되었음을 알 수 있는지를 설명하는 일입니다. 평이한 영어로 정확하게 말하는 것이 TypeScript로 정확하게 표현하는 것보다 더 어렵다는 사실이 드러납니다.
그렇다고 코드가 사라지는 것은 아닙니다. 단지 다르게 사용하게 될 것입니다. 구현을 작성하는 대신, 우리는 소통하기 위해 코드를 사용할 것입니다. 원하는 바를 말로 설명하는 것보다 몇 줄을 작성해 에이전트에게 건네는 편이 쉬울 때가 있습니다. 인터페이스, 함수 시그니처, 데이터가 흘러가야 하는 방식의 대략적인 스케치, 혹은 최종적으로 만들고 싶은 API의 간단한 예시가 될 수 있습니다. 그러면 에이전트는 여러분이 정확히 무엇을 원하는지 알고 나머지를 처리합니다. 이것이 앞서 언급한 “조금씩 개입하는 것”입니다. 코드를 작성하는 일이라기보다 동료에게 화이트보드에 빠른 다이어그램을 그려 주는 일에 가깝습니다.
그리고 이 지점에서 경험 많은 사람들은 여전히, 어쩌면 그 어느 때보다도 매우 중요합니다. 시스템을 잘 설명하려면 무엇이 잘못될 수 있는지, 어떤 부분은 생략해도 되는지, 어떤 부분은 1년 뒤에 문제로 돌아올지를 알아야 합니다. 그런 것은 프롬프트로 배우지 못합니다. 수년간 무언가를 만들고, 망가뜨리고, 고치면서 배웁니다.
저는 이를 다른 직업과 비교하는 것을 좋아합니다. 건축가는 벽돌을 쌓지 않으며, 아주 오래전부터 그랬습니다. 건축가는 무엇을 지어야 하는지 설명하고, 시공업자가 건설하며, 검사관이 건축법규에 맞는지 확인하고, 건축가가 최종 승인합니다. 시공업자를 에이전트로, 검사관을 테스트 스위트로 바꾸면 우리가 향하는 곳이 거의 그대로 보입니다. 그렇더라도 시공업자가 아무리 뛰어나도 교육을 받지 않은 사람에게 그 설계도에 서명하게 하지는 않습니다. 누군가는 왜 건물이 서 있는지 이해해야 하며, 콘크리트가 부어지기 전에 설계도의 실수를 발견해야 합니다. 또는 조종사를 생각해 보세요. 오늘날 자동 조종 장치는 상업 비행의 대부분을 담당하지만, 조종사들은 여전히 수년간 훈련하고 기량을 유지하기 위해 의도적으로 수동 비행을 합니다. 자동화가 포기하는 때는 대개 최악의 순간이며, 바로 그때 비행할 줄 아는 사람이 필요합니다. 소프트웨어가 다를 이유는 없다고 봅니다. 도구는 훨씬 좋아졌지만, 여전히 무엇을 왜 만들고 있는지 이해해야 합니다.
부분적으로는 그렇습니다. 하지만 지금까지 해 오던 방식은 아닐 것입니다.
앞으로 몇 달 안에 변경 사항을 한 줄씩 훑고, 변수 이름을 꼬투리 잡거나, for 루프 대신 map()을 썼어야 하는지 논의할 필요는 없어질 것이라 생각합니다. 한 사람이 하루에 수천 줄을 만들어 내는 여러 에이전트를 지휘할 때, 이런 종류의 리뷰는 확장되지 않습니다. 또한 에이전트는 업데이트 후 절대 무효화되지 않는 캐시나 같은 행을 두 핸들러가 두고 경쟁하는 상황처럼, 어떤 린터도 잡아내지 못할 것들을 포착하는 데 이미 우리 대부분보다 뛰어납니다.
그러므로 제 생각에 그 작업을 리뷰해야 할 이유는 몇 가지밖에 남지 않을 것입니다.
마지막 항목에 동의하지 않는다면, 우리 업계 밖의 누군가가 여러분에게 무엇을 작업하는지 물었던 때를 생각해 보세요. 단순화해야 했고, 전문 용어를 일상적인 말로 바꾸고, 코드와 전혀 관계없는 비유를 만들어야 했습니다. 때로는 그래도 통하지 않아 결국 세부 사항을 설명하지 않고서는 설명하기 어렵다고 말하며 포기했을 것입니다. 이제 그런 위치에서 자신의 시스템에 관한 결정을 내린다고 상상해 보세요. 시스템을 이해하지 않게 되면 바로 그곳에 이르게 됩니다.
이 부분이 제가 가장 신경 쓰는 부분입니다. 코드 리뷰는 품질 관리에서 지식 전달로 바뀌고 있습니다. 더는 오타를 잡기 위해 코드를 읽지 않고, 주도권을 유지하기 위해 읽게 될 것입니다.
더 단순한 이유도 있습니다. 에이전트가 코드를 작성할 수는 있지만 그 코드에 책임질 수는 없습니다. 프로덕션에서 무언가가 고장 났을 때 “에이전트가 그랬다”는 답을 받아들이는 사람은 없을 것이며, 배포되는 결과물에는 여전히 여러분의 이름이 걸려 있습니다.
우리가 수년간 주장해 온 많은 관행은 사실 우리 자신의 한계에 대한 답이었다고 생각합니다. 그 한계가 더 이상 병목이 아니라면, 그것들을 계속 유지할 이유도 많지 않습니다.
페어 프로그래밍이 가장 명확한 예입니다. 한 문제에 두 개의 두뇌를 투입하고, 한 사람은 입력하며 다른 사람은 앞을 내다보고, 둘 다 서로의 실수를 잡는다는 발상이었습니다. 오늘날 한 명의 개발자는 여러 에이전트와 동시에 작업할 수 있으며, 각각에게 다른 작업을 맡기고 자신의 주의력은 지시와 검증에 쓸 수 있습니다. 입력의 관점에서는 한 키보드 앞에 두 사람이 앉는 것이 더는 큰 의미가 없습니다. 다만 페어링의 다른 부분, 즉 시니어가 경험이 적은 개발자에게 사고하는 방식을 보여 주는 일은 여전히 중요하며 나중에 다시 이야기하겠습니다.
의식으로서의 TDD도 사라질 것입니다. 실패하는 테스트를 손으로 작성하고, 최소한의 구현을 작성한 후, 작은 주기로 리팩터링하는 방식은 우리를 규율 있게 만들고 먼저 설계를 생각하게 하는 방법이었습니다. 에이전트에게는 같은 방식으로 그런 규율을 강요할 필요가 없습니다.
‼️ 그렇다고 테스트가 사라지는 것은 아닙니다. 오히려 이전보다 더 중요하다고 말하겠습니다. 테스트 스위트는 에이전트가 자신의 작업을 검증할 수 있는 몇 안 되는 방법 중 하나이며, 여러분이 모든 줄을 읽지 않고도 검증할 수 있는 몇 안 되는 방법 중 하나이기 때문입니다. 사라지는 것은 작은 레드-그린 단계로, 먼저 손으로 테스트를 작성하는 일입니다. ‼️
그다음은 DRY입니다. 모든 중복 코드 조각을 사람이 찾아 보통 다섯 곳에서 업데이트해야 했고 여섯 번째를 잊어버리곤 했을 때는 “반복하지 말라”가 매우 타당했습니다. 에이전트는 스스로 여러분의 코드를 DRY하게 유지하지는 않습니다. 실제로 복사와 붙여 넣기를 꽤 좋아합니다. 하지만 한 에이전트에게 코드베이스 전체에서 중복을 찾아 공유 컴포넌트로 추출하라고 요청하면 몇 분 안에 해냅니다. 따라서 중복이 실제로 문제가 되었을 때 그렇게 하면 됩니다. 만일의 경우를 대비해 일찍 중복을 제거하거나, 코드 리뷰에서 “올바른” 추상화를 두고 논쟁하는 일은 더 이상 가치가 없습니다. 이제 약간의 반복은 저렴하지만, 잘못된 추상화는 언제나 그랬듯 비쌉니다.
SOLID도 비슷한 위치에 있습니다. 두뇌를 위한 좋은 훈련이고, CPU가 어떻게 작동하는지 아는 것이 좋은 것처럼 여전히 배우기를 권합니다. 하지만 대부분의 클래스를 여러분이 작성하지는 않을 것이며, 그것들이 서로 어떻게 상호작용할지 전부 결정하는 사람도 여러분이 아닐 가능성이 큽니다. 여러분의 일은 한 단계 위로 올라가 모듈 간 경계, 서비스 간 계약, 그리고 에이전트가 따라야 하는 제약을 다루게 됩니다.
제가 그렇다고 말할 것이라 예상하실 테고, 실제로 그렇습니다 🙃 하지만 이유는 이제 조금 다르다고 생각합니다.
첫째, 프레임워크는 에이전트를 더 효율적으로 만듭니다. 프레임워크 안에서 작업하는 에이전트는 매번 바퀴를 다시 발명할 필요가 없습니다. 자체 의존성 주입 컨테이너, 검증 계층, 모듈 시스템 또는 오류 처리를 작성할 필요가 없습니다. 이는 생성할 코드가 줄고, 토큰이 줄며, 채울 컨텍스트가 줄고, 미묘한 문제가 생길 수 있는 곳도 줄어든다는 뜻입니다. 그리고 에이전트가 작성하지 않는 모든 줄은 아무도 검증할 필요가 없는 줄입니다.
둘째, 그리고 이것이 더 중요하다고 생각하는데, 선언형 프레임워크는 우리에게도 일을 더 쉽게 만듭니다. 에이전트가 NestJS 애플리케이션을 변경할 때, 변경 사항은 예상할 수 있는 위치에 들어갑니다. 새 엔드포인트는 컨트롤러에서 @Get()으로 데코레이팅된 메서드이고, 인가는 가드이며, 검증은 파이프나 DTO이고, 비즈니스 로직은 프로바이더에 있으며, 모듈은 무엇이 무엇에 의존하는지를 알려 줍니다. 요청이 어떻게 처리되는지 알고 싶다면 어디를 봐야 하는지 압니다.
이를 구조가 팀이, 또는 특정 실행에서 에이전트가 결정한 대로인 Express나 Hono 애플리케이션과 비교해 보세요. 그러면 에이전트가 무엇을 바꿨는지 따라가기 훨씬 어려워지며, 그것은 빠르게 우리 일의 대부분이 되고 있습니다. 명백한 해결책은 규칙을 AGENTS.md 또는 CLAUDE.md 파일에 작성하는 것이고, 솔직히 그것만으로도 꽤 멀리 갈 수 있습니다. 하지만 규칙 파일은 제안일 뿐입니다. 에이전트는 대부분의 경우 이를 따르며, 수백 번의 실행에 걸친 “대부분의 경우”는 코드베이스에 같은 일을 수행하는 다섯 가지 다른 방식이 생기는 방법입니다.
프레임워크는 규칙을 제안하는 것이 아니라 강제합니다. 에이전트가 해석할 수 없는 의존성을 연결하면 앱이 시작되지 않습니다. 내보내지 않은 모듈의 프로바이더를 주입해도 앱은 시작되지 않습니다. 인증은 @Authenticate() 데코레이터 같은 것으로 핸들러에 바로 선언되므로, 엔드포인트가 보호되는지는 한 줄만 보면 알 수 있고 다른 모든 엔드포인트에 그것이 있다면 빠진 한 줄은 눈에 띕니다.
따라서 규칙은 더 이상 온보딩만을 위한 것이 아닙니다. 프레임워크가 이를 강제하면 에이전트가 무시할 수 없는 제약이 되며, 이는 이 글 처음에서 말한 “명확한 제약”에 정확히 해당합니다.
프레임워크에 관해 방금 말한 모든 것에는 한 가지 문제가 있습니다. LLM은 학습한 대상, 즉 GitHub 스타, Stack Overflow 답변, 블로그 글이 가장 많은 라이브러리 쪽으로 자연스럽게 편향됩니다. 에이전트에게 도구를 고르라고 하면, 학습 데이터가 수집되었을 당시 인기가 있던 것을 선택할 가능성이 가장 큽니다. 기존 프로젝트에는 훌륭하지만, 새로운 모든 것에는 실제 문제가 됩니다.
이는 피드백 루프입니다. 에이전트가 인기 있는 것을 추천하고, 그래서 더 많이 사용되며, 그것으로 더 많은 코드가 작성되고, 다음 세대 모델은 그것을 더욱 추천합니다. 지난달에 출시된 더 나은 라이브러리는 모델의 관점에서는 사실상 존재하지 않으므로, 애초에 인기를 얻을 기회가 없습니다.
막다른 길이라고 생각하지는 않습니다. 에이전트는 문서를 읽고, 웹을 검색하고, 도구를 사용할 수 있으므로, 이제 프로젝트가 에이전트에게 자신을 어떻게 보여 주는지는 사람에게 어떻게 보여 주는지만큼 중요합니다. LLM이 쉽게 소비할 수 있는 문서(llms.txt 파일 같은 것), MCP 서버, 스킬, 그리고 에이전트가 따라 할 수 있는 예시를 생각해 보세요. 새 도구는 사람뿐 아니라 에이전트도 발견할 수 있도록 만들어져야 하며, 솔직히 우리 중 누구도 이를 잘하는 방법을 아직 완전히 알아낸 것 같지는 않습니다.
이는 또 다른 질문을 제기합니다. 업무에 맞는 올바른 도구를 선택하는 것은 누구의 결정일까요?
저는 여전히 여러분의 결정이라고 믿으며, 방금 설명한 편향이 바로 그 이유입니다. 에이전트가 기본적으로 라이브러리를 선택할 때, 실제로 결정을 내리는 것은 아닙니다. 가장 자주 본 것을 택하는 것입니다. 에이전트는 여러분의 팀, 제약, 컴플라이언스 요구 사항, 혹은 2년 전 바로 그 라이브러리 때문에 이미 곤란을 겪었다는 사실을 알지 못합니다. 스택, 아키텍처, 의존성은 시스템을 이해해야 내릴 수 있는 결정이며, 여러분은 그것들과 수년간 함께 살아가게 됩니다. 에이전트에게 선택지를 조사하고 절충안을 정리하게 하되, 최종 결정은 직접 내리세요.
관찰 가능성입니다.
코드의 모든 줄을 읽지 않을 것이라면, 그 코드가 어떻게 동작하는지 알 다른 방법이 필요합니다. 실제로 알 수 있는 유일한 곳은 프로덕션입니다. 트레이스, 로그, 오류, 지연 시간, 새벽 4시에 조용히 실패하는 백그라운드 작업이 그것입니다. 이것이 에이전트가 만든 것을 검증하는 방법이 될 것이며, 점점 더 에이전트가 우리와 같은 텔레메트리를 읽어 자신의 작업을 검증하고 고치는 방법이 될 것입니다.
이것이 우리가 NestJS Observe를 만든 큰 이유입니다. 범용 Node.js 에이전트를 프로세스에 연결하는 대신, Nest 자체의 라이프사이클(컨트롤러, 가드, 인터셉터, 리졸버, 큐 컨슈머, 크론 작업)에 연결합니다. 따라서 대시보드에 표시되는 내용은 여러분 또는 여러분의 에이전트가 코드를 작성할 때 사용한 것과 같은 용어를 사용합니다. 코드를 직접 작성하지 않았을 때, 익명의 함수 무더기 대신 요청이 여러분의 컨트롤러와 프로바이더를 통과하는 모습을 보는 것은 큰 차이를 만듭니다.
에이전트를 도입하는 데 진지하다면, 프롬프트에 투자하는 만큼 최소한 관찰 가능성에도 투자하세요.
네, 물론입니다.
외국인과 소통하려면 그들의 언어를 배워야 하며, 코드는 여러분의 에이전트가 말하는 언어입니다. 코드를 읽을 수 없다면 정확히 무엇을 원하는지 말할 수 없고, 그들이 준 것이 여러분이 요청한 것인지도 알 수 없습니다. 코딩을 모른다면 눈을 가린 채 배를 조종하는 셈입니다. 한동안은 잘될지 모르지만, 잘되지 않게 되었을 때 왜 그런지도 알지 못할 것입니다.
제가 생각하는 변화는 무엇을 배울 가치가 있는가입니다. LLM은 몇 초 만에 한 언어의 문법을 다른 언어로 번역하므로, 하나를 깊이 익히는 대신 프로그래밍 언어 다섯 개를 배우는 것은 더는 큰 의미가 없습니다. 그들이 제공할 수 없는 것은 판단력입니다. 따라서 서로 다른 세 언어로 클래스, 클로저, 비동기 이터레이터를 작성하는 방법을 아는 대신, 그것들이 무엇인지, 언제 사용해야 하는지, 어떤 비용이 드는지를 정말로 이해하도록 하세요. 메모리, 동시성, 네트워크가 어떻게 동작하는지, 트랜잭션이 실제로 무엇을 보장하는지, 의존성이 느리거나 중단되면 어떤 일이 일어나는지처럼 문법 아래에 있는 것들도 마찬가지입니다. 이런 지식은 모든 언어와 모든 에이전트에서 통하며, 에이전트가 만든 것이 올바른지 판단하는 데 필요합니다.
문제는 읽기만 해서는 그러한 이해를 얻을 수 없다는 점입니다. 다른 사람이 코딩하는 것을 본다고 시니어 엔지니어가 된 사람은 없습니다. 작동하지 않는 코드를 작성하고, 이유를 알아낼 때까지 들여다보고, 고치면서 그 경지에 이릅니다. 그러니 막 시작했다면 에이전트가 더 빠르더라도 의도적으로 손으로 코드를 작성하세요. 조종사가 여전히 수동 비행을 하는 것과 같은 이유입니다. 핵심은 결과물이 아니라, 나중에 필요할 판단력을 쌓는 것입니다. 그리고 시니어라면 주변 사람들이 여전히 그 기회를 얻도록 하세요. 이것이 제가 다시 이야기하겠다고 한 페어 프로그래밍의 부분입니다.
몇 달 동안 매일 에이전트와 작업한 후, 저는 한 가지 결론에 도달했습니다. 여전히 사람이 내려야 할 결정은 많습니다. 컴퓨터 과학 배경이 없는 사람도 이미 에이전트로 작동하는 소프트웨어를 만들 수 있고, 매달 더 많은 것을 만들 수 있게 되겠지만, 한계가 있습니다. 문제는 그들이 그 한계를 보지 못한다는 것입니다. 어떤 질문을 해야 하는지 모르고, 에이전트가 제시하는 절충안을 판단할 수 없으며, 무언가가 잘못되어도 알아차리지 못합니다. 단순한 앱은 그 한계에 가까이 가지 않을 수도 있습니다. 하지만 결제를 처리하고, 개인 데이터를 저장하거나, 실제 트래픽을 처리해야 하는 시스템은 아주 빠르게 그 한계에 도달합니다.
그러므로 네, 여전히 엔지니어링이라고 믿습니다. 앞서 코드의 양은 결코 병목이 아니었다는 데 동의했고, 에이전트는 이를 바꾸지 않습니다. 단지 이를 무시할 수 없게 만들 뿐입니다. 코드를 작성하는 데 몇 분이 걸리는 상황에서 병목은 우리의 주의력입니다. 무엇을 만들지 결정하고, 이를 정확히 설명하며, 돌아온 결과가 실제로 원했던 것인지 검증하는 일입니다. 이 글의 거의 모든 섹션은 그 세 가지 중 하나에 관한 이야기입니다. 입력은 결코 어려운 부분이 아니었고, 어려운 부분은 여전히 우리에게 남아 있습니다.
그렇다고 아무것도 변하지 않는다는 뜻은 아닙니다. 어떤 종류의 일은 사라질 것입니다. 예를 들어, 주로 기업용 랜딩 페이지를 만드는 에이전시는 오래 남아 있지 않을 것이라 생각합니다. 그런 종류의 일은 제가 언급한 한계보다 훨씬 아래에 있으므로, 예전에 그런 에이전시를 고용하던 기업들은 이제 직접 할 수 있습니다.
조금은 그렇습니다.
어느 정도는 두렵고, 조금 공허하게 느껴집니다. 코드 작성은 제게 단순한 일이 아니었습니다. 애초에 NestJS가 존재하게 만든 일이었습니다. 이제 그 대부분을 다른 무언가가 하게 될 것이라는 사실에 적응하려면 시간이 조금 필요합니다.
하지만 LLM은 사라지지 않을 것이며, 우리가 예상하는 것보다 빠르게 계속 좋아질 가능성이 큽니다. 그래서 다른 길은 딱히 보이지 않습니다. 달랐으면 좋겠다고 바랄 수도 있고, 이제 우리의 역할이 어떤 모습인지 알아낼 수도 있습니다. 제게 그것은 제약을 설정하고, 결정을 내리고, 시스템을 이해하며, 배포되는 결과물에 책임지는 일입니다.
저는 후자를 택하겠습니다.