Devin에 API와 화면 수정을 함께 맡기면서 “PR은 백엔드와 프론트엔드로 나눠줘”라고 요청할 수 있습니다. 그런데 폴더가 다르다는 이유만으로 나누면, 먼저 배포된 쪽이 기존 화면을 깨뜨릴 수 있어요. 나눌 때 확인할 것은 각 PR이 끝난 시점의 동작입니다.
2026-10-01 작성. 아래 코드는 API 응답과 화면의 읽기 동작을 단순화한 설명용 예제입니다. 실제 Devin의 작업 결과나 운영 장애를 재현한 후기가 아닙니다.
이름 하나를 바꿔도 서버와 화면의 순서가 생깁니다
사용자 정보의 name을 displayName으로 바꾼다고 가정해보겠습니다. 여기서는 필드 이름만 바뀌며, 값의 의미는 같습니다. 기존 화면은 응답에서 user.name을 읽고 있어요.
// 기존 응답
{ name: "Mina" }
// 이름을 바로 바꾼 응답
{ displayName: "Mina" }
서버 PR을 먼저 배포하면 기존 화면이 찾는 name이 사라집니다. 순서를 바꿔 새 화면을 먼저 배포해도, 이전 서버에는 displayName이 없어요. 새 서버와 새 화면을 함께 확인할 때만 보면 놓치기 쉬운 조합입니다.
Google의 API 설계 지침인 AIP-180도 필드 이름 변경을 기존 항목의 삭제와 새 항목의 추가로 봅니다. 같은 메이저 버전에서 기존 항목을 제거하지 말라는 원칙을 제시해요. “이름만 바꿨다”는 설명으로 호환성 검토를 생략할 수 없는 이유입니다. AIP-180의 삭제·이름 변경 지침
먼저 두 이름이 함께 있는 상태를 만듭니다
이 예제라면 첫 PR에서 응답에 displayName을 추가하고 name은 유지하겠습니다. 다음 PR에서 화면을 전환하고, 기존 필드의 제거 여부는 별도로 판단합니다. 둘 다 반환하는 동안에는 같은 원본 값에서 만들어 값이 어긋나지 않게 합니다.

PR 1: 기존 화면을 유지하면서 응답을 확장합니다
응답을 { name: "Mina", displayName: "Mina" }로 만듭니다. 이 PR의 완료 조건은 기존 화면이 그대로 이름을 읽고, 새 필드에서도 같은 값을 읽을 수 있는 것입니다. 필드를 추가하는 일이라도 엄격한 응답 검증을 하는 클라이언트가 있다면 그 검증도 확인해야 합니다.
PR 2: 화면을 전환하고 이전 서버에 대한 동작을 정합니다
화면이 displayName을 우선 사용하도록 바꿉니다. 서버가 이전 버전으로 돌아갈 가능성까지 지원하려면, 이 예제에서는 displayName ?? name으로 읽는 방법을 택할 수 있어요. 새 필드가 없거나 null이면 기존 필드를 읽습니다.
기존 필드 제거는 지원 정책을 확인한 뒤 결정합니다
구버전 앱이나 오래 열린 웹 화면, 외부 연동이 name을 계속 읽을 수 있습니다. 이들이 남아 있다면 필드를 유지해야 합니다. 공개 API의 호환성 약속이 있다면 새 버전 제공이나 정해진 폐기 절차가 필요할 수 있고요. 화면 PR을 합쳤다는 사실만으로 삭제 조건이 충족되지는 않습니다.
PR은 코드를 검토하고 합치는 단위이고, 배포는 사용자가 받을 버전을 바꾸는 작업입니다. PR을 나눴다고 배포 순서가 자동으로 정해지지는 않습니다. 각 PR 설명에 선행 변경과 적용 조건도 적어두세요.
9가지 조합을 실행해 보면 빠진 조건이 보입니다
응답 세 가지와 읽기 함수 세 가지를 조합했습니다. original은 기존 응답, expanded는 두 필드가 있는 응답, contracted는 기존 필드를 제거한 응답입니다. old는 기존 필드만, new는 새 필드만, fallback은 새 필드를 우선하되 기존 필드도 읽습니다.
아래 코드를 compatibility.mjs로 저장하고 Node.js에서 실행할 수 있습니다. HTTP 서버나 화면을 띄우지 않고 읽은 값이 예상 이름과 같은지만 확인합니다. 이름이 없는 두 조합도 의도한 비교 대상이라, 예상 결과와 일치하면 프로그램은 정상 종료합니다.
// Explanatory in-memory API/client combinations, not a Devin session.
import assert from 'node:assert/strict';
const responses = {
original: { name: 'Mina' },
expanded: { name: 'Mina', displayName: 'Mina' },
contracted: { displayName: 'Mina' },
};
const readers = {
old: (user) => user.name,
new: (user) => user.displayName,
fallback: (user) => user.displayName ?? user.name,
};
const expected = {
original: { old: true, new: false, fallback: true },
expanded: { old: true, new: true, fallback: true },
contracted: { old: false, new: true, fallback: true },
};
for (const [api, payload] of Object.entries(responses)) {
for (const [client, read] of Object.entries(readers)) {
const value = read(payload);
const compatible = value === 'Mina';
assert.equal(compatible, expected[api][client]);
console.log(`${api} / ${client}: ${compatible ? 'OK' : 'MISSING'} (${String(value)})`);
}
}
node compatibility.mjs
Node.js v22.12.0에서 확인한 출력입니다. MISSING은 이 예제에서 기대한 이름을 읽지 못했다는 표시이지, Devin이나 HTTP의 오류 코드가 아닙니다.
original / old: OK (Mina)
original / new: MISSING (undefined)
original / fallback: OK (Mina)
expanded / old: OK (Mina)
expanded / new: OK (Mina)
expanded / fallback: OK (Mina)
contracted / old: MISSING (undefined)
contracted / new: OK (Mina)
contracted / fallback: OK (Mina)
기존 화면은 확장된 응답에서는 이름을 읽지만, 기존 필드를 제거한 응답에서는 undefined를 얻었습니다. 새 필드만 읽는 화면 역시 기존 서버의 응답에서는 값을 얻지 못했어요. 두 필드를 지원하는 함수는 이 예제의 세 응답에서 모두 이름을 읽었습니다.
“문제 생기면 서버만 되돌리자”도 확인이 필요합니다
새 화면이 displayName만 읽는데 서버를 이전 응답으로 되돌리면 어떨까요? 위 출력의 original / new 조합이 됩니다. 앞으로 적용하는 순서뿐 아니라, 되돌렸을 때 만나는 조합도 작업 범위에 넣어야 합니다.

그렇다고 모든 필드에 ??를 붙이면 해결되는 것은 아닙니다. 이번에는 이름만 바뀌었고 두 필드의 의미가 같다는 전제가 있어요. null이 “이름을 숨김”이라는 의도적인 값이라면 기존 이름을 다시 보여주는 동작은 잘못일 수 있습니다. 빈 문자열도 ??의 대체 조건에 포함되지 않습니다.
예제에서는 실제 API 응답 검증, 데이터 저장, 브라우저 캐시, 앱 업데이트 상태까지 확인하지 않았습니다. 실서비스에서는 지원할 버전 조합과 값의 의미를 먼저 정하고, 그 조건에 맞는 검증을 붙여야 합니다.
Devin에는 구현 전에 나눌 경계부터 요청합니다
Devin 공식 가이드도 큰 작업을 집중된 작업으로 나누고, 시작과 끝 및 성공 조건을 명확히 정하라고 권합니다. 독립적인 작업을 병렬로 진행하는 것과, 앞 작업의 결과가 필요한 작업을 동시에 맡기는 것은 구분해야 해요. 작업 범위를 정하는 공식 가이드
저라면 “백엔드 PR과 프론트엔드 PR로 나눠줘” 대신 아래 요청으로 시작하겠습니다. 구현할 파일 목록을 받기 전에, 사용하는 쪽과 배포 중에 생길 조합부터 확인하는 요청입니다.
사용자 정보의 name을 displayName으로 전환하려고 한다.
우선 수정하지 말고 작업을 나눌 계획을 작성해줘.
1. 응답을 만드는 곳과 읽는 곳을 찾아 경로를 적어줘.
이 저장소에서 확인할 수 없는 외부 사용자는 미확인으로 남겨줘.
2. 기존 버전과 새 버전이 섞이는 동안 필요한 중간 상태를 제안해줘.
3. 각 PR에 다음 내용을 적어줘.
- 바꾸는 동작과 이번 PR에서 유지할 동작
- 선행 PR 및 배포 순서
- 따로 배포 가능한지와 그 조건
- 검증할 버전 조합, 실행 방법, 완료 조건
- 되돌릴 경우 지원할 조합과 제한
4. 기존 필드의 제거는 별도 항목으로 두고,
구버전 사용 여부와 호환성 정책을 확인하기 전에는 진행하지 마.
계획을 검토한 뒤 응답 확장 단계부터 구현을 요청하겠다.
이 요청은 구현 범위와 성공 조건, 참고할 코드를 구체적으로 전달하라는 공식 지침을 예제에 맞게 풀어 쓴 것입니다. 실제 파일 경로와 API 동작은 저장소 조사 결과로 채워야 합니다. 지시문 작성 공식 가이드
모든 작업을 세 PR로 만들 필요는 없습니다. 함께 배포되는 작은 변경은 한 PR이 더 명확할 수도 있고, 서로 기대는 PR로 검토 순서만 나눌 수도 있어요. 다만 의존성이 있는 PR을 독립적으로 배포 가능한 것으로 표시하면 안 됩니다.
자주 묻는 질문
PR은 파일 수나 변경 줄 수로 나누면 되나요?
크기는 검토 부담을 가늠하는 데 도움이 되지만, 경계를 정하는 유일한 기준은 아닙니다. 각 변경의 완료 조건과 선행 작업, 중간 버전의 호환성을 함께 봐야 합니다.
새 화면이 배포되면 기존 API 필드는 바로 지워도 되나요?
구버전 앱, 오래 열린 웹 화면, 외부 연동이 기존 필드를 사용하는지 확인해야 합니다. 지원 정책과 제거 조건이 충족되기 전에는 유지하고, 공개 API라면 버전 및 폐기 정책도 따라야 합니다.
다음 PR 없이도 설명할 수 있는 완료 조건을 적습니다
첫 PR의 완료 조건은 “새 필드를 추가했다”에서 끝내지 않겠습니다. “기존 화면이 계속 이름을 읽고, 새 필드에서도 같은 값을 읽는다”까지 적어두겠습니다. 그러면 다음 작업이 늦어져도 현재 상태를 검토할 기준이 남습니다.
완성된 PR을 어떻게 확인할지는 Devin이 만든 PR, 테스트 통과만 보고 합쳐도 될까?에서 이어집니다. Devin의 기본 작업 방식은 Devin 소개 글에, 작업 도중 같은 오류가 반복될 때의 확인 순서는 반복 오류 진단 글에 정리했습니다.
공식 자료 확인: 2026-10-01. 로컬 실습: Node.js v22.12.0에서 응답 객체 3개와 읽기 함수 3개의 조합 확인. 실제 Devin 실행, HTTP 통신, 화면 렌더링, 운영 배포 및 시간·비용 변화는 측정하지 않았습니다. 도식은 설명용 생성 이미지입니다.
'Devin · Windsurf' 카테고리의 다른 글
| Devin에 같은 설명을 반복한다면? AGENTS.md에 남길 것과 빼야 할 것 (0) | 2026.09.28 |
|---|---|
| Devin이 같은 오류를 반복할 때, 다시 시키기 전에 확인할 것 (0) | 2026.09.25 |
| Devin이 만든 PR, 테스트 통과만 보고 합쳐도 될까? (0) | 2026.09.24 |
| Devin에 실제 작업 4개 시켜본 한 달 실전기: ACU 비용, PR 품질, 삽질까지 (2026) (0) | 2026.09.13 |
| Devin 지시문 템플릿 7종 — 복붙해서 빈칸만 채우면 되는 프롬프트 모음 (2026) (0) | 2026.08.30 |