블로그
dev
5遺??뚯슂

코드는 줄고 검증은 늘었다: AI 에이전트 시대 개발자의 일

AI 도구를 "한 번 잘 쓰는 법"보다, 여러 도구를 "연결하고 검증하는 법"이 개발 생산성을 결정한다. 오케스트레이션(역할 분담+검증 루프)이 성과를 가른다.

한 문장 결론: AI 도구를 "한 번 잘 쓰는 법"보다, 여러 도구를 "연결하고 검증하는 법"이 개발 생산성을 결정한다.

AI 도구가 빨라진 것만이 포인트는 아니다. 더 중요한 변화는 개발 과정의 무게 중심이 '작성'에서 '조율·검증'으로 이동했다는 점이다. 이 전환을 잡아두면 UX/품질(버그·회귀), 릴리스 안정성, 팀 유지보수 비용이 동시에 달라진다. 반대로 여기서 흔들리면 "속도는 빨라졌는데 사고는 늘어나는" 상태로 쉽게 간다.


배경/문제

최근 개발 흐름에서 눈에 띄는 현상은 단순하다.

  • 내가 직접 타이핑하는 코드가 줄어든다.
  • 대신 요구사항을 잘 쪼개서 맡기고, 결과물을 검증하고, 다음 단계로 이어 붙이는 시간이 늘어난다.
  • 도구는 강력하지만 결과가 확률적이다. 같은 입력에서도 결과가 달라질 수 있고, 원인을 추적하기 어렵다.

문제는 여기서 발생한다. 도구 자체가 아니라, "연결 방식"이 표준화되어 있지 않다. 그래서 개인/팀마다 성과 편차가 커진다.


핵심 개념

1) 에이전트 오케스트레이션(Agent Orchestration)

에이전트를 단독으로 쓰는 방식은 금방 한계가 온다. 현실적인 방식은 "역할 분담 → 산출물 합치기 → 검증 → 반복"이다.

다이어그램 불러오는 중...

기대 결과/무엇이 달라졌는지: "한 번에 정답"이 아니라 "작게 만들어 빨리 검증"하는 흐름이 된다. 결과 품질은 검증 단계의 밀도로 결정된다.

2) 프롬프트는 "요청"이 아니라 "명세"

좋은 프롬프트는 "말 잘하기"가 아니라 명세를 갖춘 작업 지시서다. 특히 문서/테스트/리팩토링은 명세의 차이가 품질 차이로 바로 이어진다.

📋
프롬프트 기본 템플릿(명세형)
  • 목표: (무엇을 만들지)
  • 범위: (수정/추가 가능한 파일/모듈)
  • 제약: (지켜야 할 규칙, 변경 금지 항목)
  • 출력 형식: (예: markdown 섹션, JSON 스키마)
  • 검증 기준: (통과해야 할 테스트/린트/타입 체크)
  • 예시: (입력/출력 예)
  • 기대 결과/무엇이 달라졌는지: 결과물을 "읽어보고 감으로 판단"하는 대신, 명세 대비 충족 여부로 빠르게 판정할 수 있다.

    3) IDE 통합은 "편의 기능"이 아니라 "작업 공간"

    채팅형 도구는 컨텍스트가 끊기기 쉽다. 반면 IDE 통합형 도구는 프로젝트 구조/파일 관계를 기반으로 작업을 제안하고 변경 범위를 관리하기 쉽다.

    • 채팅 중심 워크플로우: 설명은 쉽지만 "파일 간 일관성"이 깨지기 쉽다.
    • IDE 중심 워크플로우: 변경(diff) 단위로 검토가 쉬워지고, 테스트·린트까지 연결하기가 좋다.

    기대 결과/무엇이 달라졌는지: "대화 로그"보다 "변경 diff + 테스트 결과"로 협업이 정리된다.


    해결 접근

    핵심은 하나다: AI를 "생성기"로만 쓰지 말고 "파이프라인"으로 다룬다.

    1. 입력을 작게 쪼갠다(작업 단위 축소)
    2. 산출물을 구조화한다(출력 형식 고정)
    3. 검증을 자동화한다(테스트/린트/타입)
    4. 실패를 전제로 반복한다(수정 루프)

    대안/비교(최소 2개)

    한 도구 올인 vs 멀티 도구 조합

    • 올인: 단순하지만 모델/도구 특성에 따라 "약점 구간"이 생길 수 있다.
    • 조합: 번거롭지만 역할 분담이 가능하고, 상호 검증(교차 체크)에 유리하다.

    대화로 끝내기 vs 코드베이스에서 끝내기

    • 대화로 끝내기: 속도는 빨라 보이나, 나중에 재현/리뷰가 어렵다.
    • 코드베이스에서 끝내기: PR·diff·테스트로 남아 팀 자산이 된다.

    구현(코드)

    아래 예시는 Next.js에서 재현 가능한 형태로 "작업 분해 → 구현 → 검증"을 고정하는 방법이다.

    1) 작업 지시서를 PR 템플릿으로 고정하기

    PULL_REQUEST_TEMPLATE.md

    markdown
    ## 목표
    - (예: /api/todos에 생성/조회 추가)
    
    ## 범위
    - 수정: app/api/todos/route.ts
    - 추가: tests/todos.test.ts
    
    ## 제약
    - 기존 응답 스키마 변경 금지
    - 에러 응답은 JSON 형식 유지
    
    ## 검증 기준
    - typecheck 통과
    - lint 통과
    - test 통과

    기대 결과/무엇이 달라졌는지: AI가 만든 변경도 팀의 리뷰 기준(명세) 위에서 평가되며, 작업이 흔들리지 않는다.

    2) Route Handler에 "검증 가능한" 경계 세우기

    app/api/todos/route.ts

    typescript
    import { NextResponse } from "next/server";
    import { z } from "zod";
    
    const CreateTodoSchema = z.object({
      title: z.string().min(1),
    });
    
    type Todo = { id: string; title: string };
    
    const memoryStore: Todo[] = [];
    
    export async function POST(req: Request) {
      const body = await req.json().catch(() => null);
    
      const parsed = CreateTodoSchema.safeParse(body);
      if (!parsed.success) {
        return NextResponse.json(
          { error: "INVALID_BODY", details: parsed.error.flatten() },
          { status: 400 }
        );
      }
    
      const todo: Todo = {
        id: crypto.randomUUID(),
        title: parsed.data.title,
      };
      memoryStore.push(todo);
    
      return NextResponse.json({ todo }, { status: 201 });
    }
    
    export async function GET() {
      return NextResponse.json({ todos: memoryStore }, { status: 200 });
    }

    기대 결과/무엇이 달라졌는지: 입력 검증이 명확해져서 에이전트가 생성한 코드도 "어디까지가 안전한지" 경계가 생긴다.

    참고: Next.js Route Handlers, Zod

    3) 테스트로 "AI 산출물 검증 루프" 만들기

    tests/todos.test.ts

    typescript
    import { describe, it, expect } from "vitest";
    
    describe("todo api contract", () => {
      it("rejects invalid body", async () => {
        const res = await fetch("http://localhost:3000/api/todos", {
          method: "POST",
          headers: { "content-type": "application/json" },
          body: JSON.stringify({ title: "" }),
        });
    
        expect(res.status).toBe(400);
        const json = await res.json();
        expect(json.error).toBe("INVALID_BODY");
      });
    });

    기대 결과/무엇이 달라졌는지: "그럴듯함"이 아니라 테스트가 통과하는지로 결과를 판정한다. 에이전트 실수(엉뚱한 파일 수정/계약 변경)를 조기에 잡는다.

    참고: Vitest, MDN fetch

    4) 클라이언트/서버 경계는 더 엄격하게

    브라우저 전용 API(window/document/localStorage 등)는 Client Component에서만 다룬다.

    app/components/ClientOnlyWidget.tsx

    typescript
    "use client";
    
    import { useEffect, useState } from "react";
    
    export function ClientOnlyWidget() {
      const [value, setValue] = useState<string>("");
    
      useEffect(() => {
        const saved = window.localStorage.getItem("demo") ?? "";
        setValue(saved);
      }, []);
    
      return <div>Saved: {value}</div>;
    }

    기대 결과/무엇이 달라졌는지: 서버 렌더링/하이드레이션 과정에서 발생할 수 있는 오류를 피하고, 실행 위치가 명확해진다.

    참고: Next.js Client Components, React useEffect


    검증 방법(체크리스트)

    아래 체크리스트를 "에이전트 결과물 승인 조건"으로 고정한다.

    변경 범위가 PR 템플릿의 범위/제약을 만족한다
    타입 체크가 통과한다: pnpm typecheck(또는 프로젝트 명령)
    린트가 통과한다: pnpm lint
    테스트가 통과한다: pnpm test
    API/컴포넌트 계약(입출력)이 변경되지 않았다
    서버/클라이언트 경계가 올바르다(브라우저 API는 Client Component에서만)

    기대 결과/무엇이 달라졌는지: 생산성이 올라가도 품질이 무너지지 않는다. "빠른데 불안한 코드"를 "빠르고 예측 가능한 코드"로 바꾼다.


    흔한 실수/FAQ

    Q1. 한 도구로 설계+구현을 다 하면 더 빠르지 않나요?

    가능하다. 다만 팀/프로젝트에 따라 역할 분리(설계/구현/검증)가 더 안정적일 수 있다. 특히 복잡한 변경은 "교차 검증"이 사고를 줄인다.

    Q2. 생산성이 늘면 코드 리뷰는 더 쉬워지나요?

    리뷰의 초점이 바뀐다. 문법/스타일보다 계약 준수, 회귀 위험, 테스트 보강에 집중하는 편이 효과적이다.

    Q3. AI가 잘하는 것과 사람이 해야 하는 것은 어떻게 나누나요?

    반복 작업(스캐폴딩, 문서 초안, 테스트 뼈대)은 자동화하고, 의사결정(요구사항 경계, 아키텍처, 리스크 판단)과 검증은 사람이 책임지는 구성이 안정적이다.


    요약(3~5줄)

    • AI 도구 자체보다 오케스트레이션(역할 분담+검증 루프)이 성과를 가른다.
    • 프롬프트는 요청이 아니라 명세로 다뤄야 재현성과 품질이 오른다.
    • IDE 통합은 "편의 기능"이 아니라 변경 diff와 테스트로 귀결되는 작업 공간이다.
    • Next.js에서는 서버/클라이언트 경계를 명확히 하고, 테스트로 결과물을 판정한다.

    결론

    AI 시대의 개발자는 "코드를 더 빨리 치는 사람"에서 "시스템을 더 안전하게 움직이는 사람"으로 이동하고 있다.

    도구는 계속 바뀌어도, 작게 만들고(분해) → 구조화하고(명세) → 검증하는(테스트) 파이프라인은 남는다. 이 파이프라인을 팀의 기본 동작으로 고정하는 것이 가장 확실한 대응이다.


    참고(공식 문서 링크)

    관련 게시물