- AI 코딩 도우미는 프로젝트 의도를 스스로 알 수 없으므로, 개발자가 맥락·목표·예시를 얼마나 잘 주느냐에 따라 결과 품질이 크게 달라짐
- 디버깅에서는 코드와 오류만 던지기보다 기대 동작, 실제 동작, 실행 환경을 함께 제공하고 라인별 추적이나 최소 재현 예제로 문제 범위를 좁혀야 함
- 리팩터링과 최적화는 “더 좋게”가 아니라 중복 제거, 병렬 처리, 오류 처리 유지, 버전 제약 같은 성공 기준을 명확히 해야 함
- 새 기능 구현은 전체를 한 번에 맡기기보다 계획, 골격, 상태 관리, API 연동, 엣지 케이스를 작은 단계로 나눠 검토하며 확장하는 방식이 안전함
- 모호한 질문, 과도한 요구, 질문 없는 코드 덤프, 불명확한 참조는 결과를 흔들며, AI는 일회성 생성기보다 반복 대화형 페어 프로그래머로 다뤄야 함
AI 코딩 도우미를 잘 쓰기 위한 기본 원칙
- AI 코딩 도우미는 함수 자동완성, 버그 수정 제안, 모듈이나 MVP 생성까지 도울 수 있지만 프로젝트의 구체적 의도는 사용자가 제공한 정보에 의존함
- 좋은 프롬프트는 AI를 “문자 그대로 받아들이는 협업자”처럼 대하고, 원하는 결과와 형식을 명확히 안내함
- 기본 원칙은 다음과 같음
- 풍부한 맥락 제공: 언어, 프레임워크, 라이브러리, 관련 함수나 코드 조각, 정확한 오류 메시지, 기대 동작을 포함함
- 목표 구체화: “왜 안 되나?”보다 “이 JavaScript 함수가 기대값 대신 undefined를 반환하는 이유와 수정 방법”처럼 질문을 좁힘
- 복잡한 작업 분해: 큰 기능을 한 번에 요청하지 않고 React 컴포넌트 골격 생성, 상태 관리 추가, API 호출 통합처럼 순차적으로 진행함
- 입출력 예시 제공:
[3,1,4]입력이[1,3,4]를 반환해야 한다는 식의 예시는 요구사항의 모호성을 줄임 - 역할 지정: “senior React developer”, “JavaScript performance expert”, “code reviewer”처럼 역할을 주면 답변의 깊이와 스타일을 조정할 수 있음
- 반복과 수정: 첫 답변이 맞지 않으면 재귀 대신 반복문 사용, 변수명 개선, 주석 추가처럼 후속 요청으로 방향을 조정함
- 코드 명확성 유지: 의미 있는 함수명과 변수명, 일관된 포맷, 주석과 docstring은 AI가 코드 의도를 파악하는 단서가 됨
디버깅 프롬프트 패턴
- 디버깅 요청에는 코드가 무엇을 해야 하는지, 실제로 무엇이 잘못됐는지, 어떤 오류가 발생했는지가 함께 들어가야 함
- 좋은 디버깅 프롬프트는 보통 다음 정보를 포함함
- 사용 언어와 실행 환경
- 문제 코드
- 정확한 오류 메시지 또는 잘못된 출력
- 기대 출력
- 예시 입력
- 이미 시도한 방법
- 복잡한 로직 버그는 AI에게 라인별 실행 추적을 요청해 상태 변화를 따라가게 할 수 있음
- 예: “이 함수에서 total 값이 각 단계마다 어떻게 변하는지 추적하고, 어디서 누적이 잘못되는지 찾아줘”
- 코드베이스가 크더라도 버그를 재현하는 작은 코드 조각을 만들 수 있다면 최소 재현 예제로 제시하는 편이 좋음
- 첫 답변이 부분적으로만 유용하면 “수정된 코드를 보여줘”, “왜 이 변경이 문제를 해결하는지 설명해줘”처럼 후속 질문을 이어갈 수 있음
디버깅 예시: 모호한 질문과 개선된 질문
- 예시 코드는
mapUsersById(users)가 사용자 배열을 ID 기반 객체로 변환하려는 Node.js/JavaScript 함수임 - 버그는
for (let i = 0; i <= users.length; i++)조건에 있음- 배열 길이가 1이면
i = 0과i = 1이 실행됨 users[1]은undefined가 되고user.id접근에서TypeError가 발생함
- 배열 길이가 1이면
- “왜
mapUsersById함수가 작동하지 않나?”처럼 함수명만 주는 프롬프트는 맥락이 부족함- AI는 배열이 비었는지, 각 user에
id가 있는지,users가undefined인지 같은 일반적 가능성을 나열할 수 있음
- AI는 배열이 비었는지, 각 user에
- 개선된 프롬프트는 다음을 함께 제공함
- 함수의 목적: 사용자 객체 배열을 ID 키 기반 객체로 변환
- 예시 입력:
{ id: 1, name: "Alice" } - 오류 메시지:
Cannot read property 'id' of undefined - 코드 조각
- 기대 결과:
{ "1": {id: 1, name: "Alice"} }
- 이렇게 정보를 좁히면 AI가 루프 조건의
<=를i < users.length로 바꾸는 수정과 그 이유를 직접 제시할 수 있음
디버깅을 보완하는 요청 방식
- 원인을 모를 때는 코드와 함께 “이 코드에서
TypeError: cannot read property 'foo' of undefined가 발생할 수 있는 이유는 무엇인가?”처럼 가능한 원인 목록을 요청할 수 있음 - 러버덕 방식으로 사용자가 함수 동작을 설명하고, AI에게 그 설명이 맞는지 검토하게 할 수 있음
- 깨질 가능성이 있는 테스트 케이스를 AI에게 만들게 할 수 있음
- 빈 배열
- 매우 큰 숫자
null값- 예상하지 못한 입력
- “code reviewer” 역할을 부여하면 버그뿐 아니라 누락된 null 체크나 나쁜 관행도 함께 찾기 쉬움
최신 디버깅 시나리오 예시
- React Hook 의존성 문제에서는 “
useEffect가 이상하다”보다 정확한 코드, 기대 동작, 실제 동작, 콘솔 오류를 함께 제공하는 프롬프트가 더 유용함- 예시 오류는
Warning: Maximum update depth exceeded - 기대 동작은
userId가 바뀔 때 한 번 사용자 데이터를 가져오는 것 - 실제 동작은 무한 재렌더링임
- 질문은 의존성 배열에서 무엇이 무한 루프를 일으키는지와 수정 방법에 집중함
- 예시 오류는
- Next.js 14 전자상거래 앱의 상태 관리 설계 요청은 기술 스택과 요구사항을 함께 제시함
- App Router와 Server Components
- TypeScript strict mode
- SEO를 위한 서버 측 데이터 가져오기
- 장바구니와 사용자 동작을 위한 클라이언트 상호작용
- 내비게이션 간 상태 유지
- 상태 관리 선택지는
Zustand,React Query/TanStack Query + Zustand, 단일Zustandstore with slices처럼 구체적으로 비교 요청함
리팩터링과 최적화 프롬프트 패턴
- “이 코드를 리팩터링해줘”는 너무 열려 있어 AI가 사용자의 기준과 다른 방향으로 코드를 바꿀 수 있음
- 리팩터링 목표는 구체적으로 지정해야 함
- 가독성 개선
- 유지보수성 향상
- 중복 제거
- 성능 최적화
- 더 관용적인 코드 스타일
- 특정 라이브러리나 패러다임 사용
- 코드 맥락도 함께 제공해야 함
- 전체 함수나 관련 코드 조각
- 사용 언어와 프레임워크
- React class component를 Hooks 기반 functional component로 바꾸는지 같은 목표 스타일
- Node.js v14, ES6 modules 같은 버전과 환경 제약
- 리팩터링 결과와 함께 변경 이유를 요청하면 AI가 코드 의도를 제대로 이해했는지 검토할 수 있음
- “seasoned TypeScript expert”, “experienced Python developer mentoring a junior”처럼 역할을 지정하면 더 높은 기준의 리팩터링과 설명을 유도할 수 있음
리팩터링 예시: getCombinedData
- 예시 함수
getCombinedData(apiClient)는/users와/orders를 각각 가져오고, 사용자별 주문을 찾아 결과 배열을 만드는 코드임 - 원래 코드에는 다음 문제가 있음
- 사용자와 주문을 가져오는 fetch 로직이 중복됨
- 오류 메시지가 단순함
- 두 요청을 순차 실행함
- 사용자마다
orders.filter를 수행해 조합 단계가 비효율적일 수 있음
- “위 함수를 리팩터링해줘”라는 요청만으로도 AI가
Promise.all,ordersByUser맵,.map을 사용할 수 있지만 사용자의 의도와 맞지 않는 변경이 생길 수 있음- 예시에서는 개별 오류 메시지가
Failed to fetch data로 합쳐져 어떤 호출이 실패했는지 정보가 줄어듦
- 예시에서는 개별 오류 메시지가
- 개선된 프롬프트는 원하는 기준을 나열함
- fetch 중복 제거
- 두 목록을 병렬로 가져오기
- 사용자와 주문 fetch의 오류 처리를 각각 유지
- 중첩 루프 대신 더 효율적인 조회 구조 사용
- 변경 이유를 주석으로 설명
- “더 좋게”의 기준을 사용자가 정의하면 AI가
Promise.all, 개별ok체크,ordersByUser조회 맵,users.map기반 결과 생성을 요구에 맞춰 구성할 수 있음
리팩터링 후 검증과 학습
- 큰 코드나 긴 변경 목록은 한 번에 처리하지 말고 단계별로 나누는 편이 좋음
- 먼저 가독성을 위한 리팩터링
- 이후 알고리듬 최적화
- 대안 비교도 요청할 수 있음
- 함수형 스타일로 바꾸기
- 반복문 대신 배열 메서드 사용
- 재귀 방식과 반복 방식 비교
- AI가 제안한 리팩터링의 설명을 읽으면
reduce로 맵을 만드는 방식 같은 새로운 API나 기법을 배울 수 있음 - AI가 만든 리팩터링은 항상 테스트나 샘플 입력으로 검증해야 함
- 프롬프트에 중요한 제약이 빠졌다면 AI가 미묘한 버그를 만들 수 있음
- 필요하면 “리팩터링된 함수의 단위 테스트를 만들어줘”라고 요청할 수 있음
새 기능 구현 프롬프트 패턴
- 새 기능은 구현 방식이 여러 가지라서 AI가 프로젝트 요구와 스타일에 맞도록 안내해야 함
- 먼저 큰 목표를 설명한 뒤 단계별로 나누는 방식이 권장됨
- 예: React 앱에 제품 이름 검색 기능 추가 계획 요청
- 이후 검색 입력 컴포넌트, 검색 상태, 필터링 로직을 각각 구현 요청
- 기존 프로젝트에 기능을 추가한다면 비슷한 컴포넌트나 참조 코드를 제공하는 것이 좋음
UserList와 유사한ProductList를 만들되 검색 바를 포함하도록 요청하는 식임
- 프로젝트의 아키텍처나 스타일도 명시해야 함
- Redux 사용 여부
- 특정 CSS 프레임워크
- MVC 패턴
- 상태를 controller에 둘지 view에 둘지 같은 제약
- IDE에서 Copilot을 사용할 때는 주석과 TODO를 인라인 프롬프트로 활용할 수 있음
- 예:
// TODO: Validate the request payload (ensure name and email are provided)
- 예:
- 새 함수나 기능에는 사용 예시나 간단한 테스트 케이스를 함께 제시하는 편이 좋음
formatPrice(2.5)가'$2.50'을 반환해야 한다는 식의 예시는 출력 형식을 고정함
React 제품 목록 기능 구현 예시
- 예시 요청은
ProductListReact functional component 생성을 목표로 함/api/products에서{id, name, ...}배열을 가져옴- 상태에 저장함
- 검색 입력으로 제품 이름을 대소문자 구분 없이 필터링함
<ul>에 제품명을 표시함- 로딩 상태와 API 실패 오류 메시지를 처리함
- AI는
useState,useEffect,fetch,products.filter,toLowerCase,loading,error상태를 포함한 컴포넌트를 생성할 수 있음 - 프로젝트가 직접
fetch를 쓰지 않고useProducts()커스텀 훅을 사용한다면, 후속 프롬프트에서 그 제약을 추가해 컴포넌트를 조정해야 함 - 정렬 드롭다운 같은 추가 요구사항은 기존 대화 맥락 위에 확장할 수 있음
sortOrder상태 추가- A-Z 또는 Z-A 선택
- 필터링된 목록을
localeCompare로 정렬
- 기능 구현에서는 스캐폴딩 → 검토 → 수정 → 확장의 반복이 중요함
기능 구현을 더 안전하게 만드는 요청
- AI에게 먼저 골격을 만들게 하고, 프로젝트 고유의 검증 규칙이나 DB 호출은 사용자가 채울 수 있음
- 예: 사용자 등록용 Node.js Express route 골격 생성
- 엣지 케이스를 함께 묻는 방식도 유용함
- 검색 결과가 비는 경우
- 제품이 아직 로드되지 않은 상태
- 매우 큰 목록에서 debounce가 필요한 경우
- docstring이나 사용 예시를 먼저 작성하고 AI에게 구현을 채우게 할 수 있음
fibonacci(n)의 0-indexed 정의fibonacci(5) -> 5예시- 반환값 설명
- 이런 방식은 주석을 사실상 명세로 사용해 AI가 요구에 맞춰 코드를 작성하게 함
흔한 프롬프트 안티패턴
-
모호한 프롬프트
- “작동하지 않으니 고쳐줘”처럼 맥락이 부족하면 AI는 일반론을 말할 가능성이 큼
- 코드, 오류 메시지, 기대 결과, 실제 결과를 추가해야 함
-
과부하된 프롬프트
- 인증, React 프런트엔드, 배포 스크립트를 포함한 전체 Node.js 앱 생성처럼 너무 많은 작업을 한 번에 요청하면 결과가 뒤섞이거나 일부 요구가 누락될 수 있음
- 작업을 순차 단계로 나눠야 함
-
질문이 없는 코드 덤프
- 큰 코드만 붙이고 “여기 내 코드야”라고 하면 AI가 요약, 수정, 설명 중 무엇을 해야 할지 알기 어려움
- “버그를 찾아줘”, “TODO를 완성해줘”, “이 코드가 하는 일을 설명해줘”처럼 목적을 명시해야 함
-
성공 기준이 모호함
- “더 빠르게”, “더 깨끗하게”는 기준이 주관적임
- 선형 시간으로 최적화, 전역 변수 제거, 클래스로 변경, 변수명 개선처럼 기준을 구체화해야 함
-
AI의 확인 질문 무시
- AI가 React class component인지 functional component인지 묻는다면 필요한 정보가 부족하다는 신호임
- 같은 요청을 반복하지 말고 빠진 세부사항을 보완해야 함
-
불명확한 참조
- 긴 대화에서 “위 코드”라고만 말하면 AI가 다른 코드 조각을 참조할 수 있음
- 함수명을 명확히 쓰거나 코드를 다시 인용하는 편이 안전함
프롬프트가 실패했을 때 고치는 방법
- 먼저 AI 출력에서 무엇이 빠졌거나 잘못됐는지 확인해야 함
- TypeScript를 요청했는데 JavaScript가 나왔는지
- 반복문을 원했는데 재귀가 나왔는지
- 브라우저용 코드가 필요한데 Node.js API를 썼는지
- 새 프롬프트에는 빠진 요구사항을 강조함
- “TypeScript로 작성하고 타입 주석을 포함해줘”
- “재귀를 쓰지 말고 반복문을 사용해줘”
- “외부 라이브러리는 사용하지 마”
- “브라우저에서 실행되어야 하므로 Node 전용 API를 쓰지 마”
- 복잡한 요청에서 반복 실패하면 더 작은 단위로 나누거나, AI에게 먼저 요구사항을 어떻게 이해했는지 되풀이하게 할 수 있음
- 대화가 여러 번 꼬였다면 새 세션에서 개선된 프롬프트로 다시 시작하는 방법도 있음
개발 워크플로에 적용되는 결론
- 프롬프트 엔지니어링은 AI 코딩 도우미를 일회성 코드 생성기가 아니라 페어 프로그래머처럼 쓰기 위한 기술임
- 효과적인 프롬프트는 동료 개발자에게 도움을 청할 때와 같은 정보를 제공함
- 코드가 해야 할 일
- 실제로 잘못된 동작
- 관련 코드 조각
- 오류 메시지
- 기대 출력
- 역할 지정, 반복 질문, 단계적 구현, 설명 요청은 문제 해결뿐 아니라 사용자가 새 패턴을 배우는 데도 도움이 됨
- AI는 많은 코드와 문제 해결 사례를 학습했지만, 현재 문제에 어떤 사례가 관련 있는지는 사용자가 제공하는 맥락에 따라 달라짐
- 좋은 프롬프트는 한 번에 완성하는 문장이 아니라, 다른 엔지니어와 대화하듯 명확성, 인내, 세부사항을 더해 가는 반복적 대화임