우리는 Claude와 함께 5,600줄의 코드를 삭제했고 (버그 1개 발견)
I’m happy to translate the article for you, but I’ll need the full text you’d like translated. Could you please paste the content (or the portion you want translated) here? I’ll keep the source line exactly as you provided and preserve all formatting, markdown, and code blocks.
내가 만든 과도하게 설계된 시스템
나는 제공자(Google Cloud, AWS, Postgres)와 그들의 서비스(BigQuery, Firestore, DynamoDB)를 관리해야 했다. 충분히 간단해 보였다.
하지만 나는 이렇게 만들었다:
| Location | What It Stored |
|---|---|
seeds/sources_seed.go | 서비스 배열을 가진 Source 엔티티 |
seeds/templates_seed.go | 스키마를 가진 ConnectionTemplate 엔티티 |
seeds/constants.go | 하드코딩된 UUID |
frontend/components/connections/index.ts | UUID → Component 매핑 |
frontend/components/connections/*.tsx | 하드코딩된 selectedServices 배열 |
frontend/lib/services/google-cloud-capabilities.ts | 서비스별 OAuth 스코프 |
| Database (Firestore) | Sources와 ConnectionTemplates 컬렉션 |
여러 파일에 동일한 정보를 서로 다른 방식으로 정의하고 있다(8개 이상).
- 새로운 제공자를 추가하고 싶다면? 8개의 파일을 수정해야 한다.
- 기존 제공자에 서비스를 추가하고 싶다면? 6개의 파일을 수정해야 한다.
- 전체 구조가 어떻게 맞물리는지 이해하고 싶다면? 행운을 빈다.
나는 이것을 “유연하고” “확장 가능하다”고 스스로에게 말했다.
깨달음
- 아무것도 동적이지 않았다. OAuth 범위는 OAuth 앱 수준에서 고정되어 있다. 연결마다 다른 범위를 부여할 수 없으며—OAuth 동의 화면에 이미 포함되어 있다.
- 부분적인 서비스 접근에 대한 사용 사례가 없었다. “이 Google Cloud 연결은 BigQuery는 있지만 Firestore는 없다” – 그런 경우가 언제 있을까? 연결은 단지 자격 증명일 뿐이다. 해당 자격 증명이 BigQuery에 접근할 수 없으면 API가 401을 반환한다. 끝이다.
- 나는 결코 사용하지 않을 유연성을 위해 복잡성을 유지하고 있었다.
“정교한” 아키텍처는 존재하지 않는 문제를 해결하려고 하고 있었다.
교체: 20줄
아직 사용자가 없었고, 이전 호환성 문제도 없으며, 마이그레이션할 데이터도 없었습니다.
그래서 리팩터링 대신에 이렇게 물었습니다: 모든 것을 삭제하고 하나의 설정 파일로 교체한다면 어떨까요?
// frontend/lib/providers.ts
export const PROVIDERS = {
'google-cloud': {
name: 'Google Cloud Platform',
auth: 'oauth2',
services: ['bigquery', 'firestore', 'gcs', 'pubsub'],
},
'aws': {
name: 'Amazon Web Services',
auth: 'iam',
services: ['dynamodb'],
},
'postgres': {
name: 'PostgreSQL',
auth: 'database',
services: ['postgres'],
},
} as const
export type ProviderId = keyof typeof PROVIDERS
그게 전부입니다—데이터베이스 테이블, 시드, UUID, 기능 파일 및 레지스트리를 대체하는 약 20줄.
이전 → 이후 연결 모델
이전: 6 필드, UUID 조회, 중복 데이터
// Before
Connection = {
id: 'abc123',
sourceId: 'c7b3d8e9-5f2a-4b1c-9d6e-8a3b5c7d9e1f', // UUID lookup
templateId: 'template-google-cloud-oauth2', // Another UUID
services: ['bigquery', 'firestore'], // Redundant
connectionConfig: {
selectedServices: ['bigquery', 'firestore'], // Duplicate
projectId: 'my-project',
},
credentials: { /* … */ }
}
이후: 4 필드, 문자열 ID, 중복 없음
// After
Connection = {
id: 'abc123',
providerId: 'google-cloud', // Just the key from PROVIDERS
config: { projectId: 'my-project' }, // Provider‑specific config
credentials: { /* … */ }
}
Source: …
Claude가 이것을 가능하게 만든 방법
이것은 “Claude에게 코드를 작성하게 하라”는 것이 아니었습니다. 이것은 AI‑보조 아키텍처 수술이었습니다.
1. 영향을 받는 범위 매핑
코드베이스 컨텍스트를 Claude에게 제공하고, 오래된 시스템을 참조하는 모든 파일을 찾아 달라고 했습니다. Claude가 찾아낸 내용은 다음과 같습니다:
- 오래된 타입들의 모든 import
- UUID 상수들의 모든 사용처
sourceId혹은templateId를 사용하는 프론트엔드 컴포넌트- 업데이트가 필요한 테스트 파일들
- 중간 상태가 깨지는 것을 방지하기 위한 작업 순서
2. 체계적인 실행
194개의 파일을 수작업으로 바꾸는 것은 많고, 정확하게 바꾸는 일도 많습니다. Claude는 이를 체계적으로 진행했습니다:
백엔드 (Go)
connections/model.go–services,templateId제거;providerId추가connections/service.go– 생성/검증 로직 업데이트connections/handler.go– API 요청/응답 업데이트connections/dao.go– Firestore 쿼리 업데이트router.go–/v1/sources/*라우트 제거sources/패키지 전체 삭제 (9 파일)connectiontemplate/패키지 전체 삭제 (10 파일)seeds/sources_seed.go,templates_seed.go삭제
프론트엔드 (TypeScript)
lib/providers.ts– 새로운 정적 설정 (20줄)hooks/use-source.ts삭제hooks/use-connection-template.ts삭제services/source-service.ts삭제services/google-cloud-capabilities.ts삭제- 오래된 타입을 사용하던 40개 이상의 컴포넌트 파일 업데이트
3. 테스트를 동시에 업데이트
핵심 포인트: 테스트를 삭제하지 않았고, 업데이트했습니다.
sources/패키지를 삭제할 때, 해당 패키지의 테스트도 함께 삭제했습니다.- 연결 모델을 단순화하면서, 연결 테스트를 업데이트했습니다.
리팩터링 내내 테스트 스위트는 녹색 상태를 유지했습니다.
왜 버그가 하나뿐인가?
194개의 파일을 변경한 후, 엔드‑투‑엔드 테스트에서 정확히 한 개의 버그만 발견했습니다. 이는 운이 아니라 다음과 같은 결과입니다:
- 포괄적인 테스트 커버리지. 테스트는 회귀를 즉시 포착했습니다. 연결 모델을 변경했을 때, 어떤 핸들러와 서비스가 업데이트가 필요한지 정확히 알려주었습니다.
- 테스트와 함께 리팩터링, 나중이 아니라. 모든 변경에는 해당 테스트 업데이트가 포함되어, 각 단계마다 테스트 스위트의 신뢰성을 유지했습니다.
Bottom Line
과도한 설계는 유지보수 악몽에 빠지게 만들 수 있습니다. 불필요한 레이어를 제거하고 AI가 영향을 파악하도록 함으로써, 5,600‑줄 시스템을 깔끔한 20‑줄 구성으로 축소했습니다—시간, 복잡성, 그리고 향후 버그를 절감했습니다.
동일 커밋에서 테스트 업데이트
테스트는 사후 생각이 아니었다.
3. AI가 체계적으로 도와줍니다
클로드는 코드베이스의 먼 구석에 있는 파일을 업데이트하는 것을 잊지 않습니다. 파일 80을 넘어서도 피곤해져서 실수를 시작하지 않습니다.
4. 명확한 아키텍처 비전을 갖고 있습니다
코드를 건드리기 전에 상세한 계획 문서를 작성했습니다. 목표 상태는 명확했습니다: 하나의 설정 파일, 문자열 기반 제공자 ID, 연결에 서비스 배열 없음.
내가 배운 것
복잡성은 선택이다
나는 복잡한 시스템을 만들었다. 정적 데이터를 위한 UUID 기반 조회와 데이터베이스 시드를 만들도록 강요받은 사람은 없었다. 나는 그것이 “올바른” 느낌이라서 그렇게 했다.
때때로 올바른 해결책은 20줄짜리 설정 파일일 수도 있다.
AI는 자동완성뿐 아니라 아키텍처에 가장 적합하다
가치가 “Claude가 코드를 더 빨리 작성했다”는 것이 아니라 다음과 같다:
- Claude가 기존 시스템의 모든 복잡한 부분을 찾아내는 데 도움을 줬다
- Claude가 194개의 파일에 걸친 컨텍스트를 유지했다
- Claude가 내가 지치기 쉬운 부분을 체계적으로 처리했다
잘 테스트된 코드는 두려움 없는 삭제를 가능하게 한다
테스트를 신뢰했기 때문에 코드를 대량 삭제할 수 있었다. 모든 삭제는 검증되었다. “이건 안전할 것 같다”는 식의 추측은 없었다 – 테스트가 통과했든 안 통과했든 결과가 명확했다.
사용자가 없음 = 변명 없음
아직 사용자가 없었기 때문에 복잡성을 유지할 변명이 없었다. 뒤로 호환성도 없고, 마이그레이션 스크립트도 없었다. 그냥 삭제하고 진행하면 된다.
초기 단계에서 기술 부채를 안고 있다면, 지금이 가장 저렴하게 고칠 수 있는 시점이다.
새로운 개발자 경험
새 공급자 추가
PROVIDERSconfig에 항목 추가 (1줄)- connection‑form 컴포넌트 생성
- service‑config 컴포넌트 생성
- 백엔드 핸들러 생성
기존 공급자에 서비스 추가
PROVIDERS[providerId].services배열에 추가 (1줄)- service‑config 컴포넌트 생성
- 백엔드 핸들러 생성
시드 없음. 마이그레이션 없음. UUID 없음. 템플릿 엔티티 없음.
직접 해보세요
만약 당신이 필요 이상으로 무거워 보이는 시스템을 바라보고 있다면:
- 해결하려는 문제가 무엇인지 물어보세요. 그 문제가 실제 문제인지 가상의 문제인지?
- 실제로 동적인 것이 있는지 확인하세요. “유연한” 부분이 전혀 유연하지 않다면, 그것은 단지 복잡성일 뿐입니다.
- 좋은 테스트가 있다면, 그것을 믿으세요. 테스트가 실수를 잡아줄 것입니다.
- AI를 사용해 영향을 파악하세요. AI가 모든 참조를 찾는 데 당신보다 더 뛰어납니다.
때때로 답은 대량 삭제입니다.
여러분은 과도하게 설계된 시스템을 직접 삭제한 적이 있나요? 그 결정을 내리는 데 무엇이 도움이 되었나요?