요즘 고민하는 것 : 추상화
AI를 잘 활용하고 있는지 모르겠습니다. Claude Code를 사용하고 있고, Claude Code는 계속 업데이트가 되어 다양한 기능들이 있습니다. 저는 Claude Code가 스마트폰과 동일하다는 생각이 듭니다. 스마트폰에는 다양한 기능이 있지만 정작 SNS나 카카오톡, 유튜브 보는 것 이외에 스마트폰을 활용해서 특별히 무언가를 하지 않습니다.
Claude Code는 skills라는 게 있습니다. CLAUDE.md라는 것을 이용해서 프로젝트에 AI가 매번 해당 문서를 지침으로 하여 코드 생산을 더 효율적으로 할 수 있도록 하고, 서브 에이전트를 통해서 하게 될 과업을 더 전문적으로 할 수 있도록 돕는 기능도 있습니다. 저는 대체로 이러한 기본적인 기능조차 제대로 숙지하고 활용하며 사용하고 있는 것 같지는 않습니다. 저는 단순하게 Claude Code 대화 세션을 여러 터미널에 여러 개 띄워놓고, 중복되지 않은 작업들을 할 수 있도록 병렬적으로 작업을 요청하는 정도로 AI를 활용하고 있습니다.
추상화에 대한 고민
요즘 하고 있는 고민은 제목에서도 언급했듯, 추상화와 테스트 코드에 대해서입니다. 저는 추상화를 통해서 비슷한 기능을 개발할 일이 있다면 재사용, 재활용하기가 용이하다고 믿고 있습니다. 저는 하나의 서비스를 개발하는 것이 아니기 때문에, 각 서비스마다 필요한 기능이 이전에 만든 서비스에서 비슷하게 구현했고, 그게 인터페이스로 정의된 상태에서 구현이 됐다면, 새로 만들게 되는 시스템에서는 이를 참조해서 개발할 경우 더 정확하고 의도에 맞게 AI가 코드를 생산해줄 것이란 생각이 들었습니다.
예를 들어, 현재 제 블로그 프로젝트에서는 인증(Auth), AI 통합, 캐시, 메일러, 스토리지 등의 기능을 core 패키지로 분리하고, 이를 NestJS 어댑터로 연결하는 구조를 사용하고 있습니다. 이렇게 하면 나중에 다른 프레임워크(Express, Fastify 등)를 사용하더라도 core 로직은 그대로 재사용할 수 있습니다.
packages/
├── core/ # Framework-agnostic 공통 로직
│ ├── auth/ # @my-blog/auth-core (인터페이스 정의)
│ ├── ai/ # @my-blog/ai-core
│ └── storage/ # @my-blog/storage-core
├── adapters/
│ └── nestjs/ # NestJS 구현체
│ ├── auth/ # @my-blog/auth-nestjs
│ └── ai/ # @my-blog/ai-nestjs
코드가 명세가 되는 개발
이는 단순히 skills, CLAUDE.md, sub agents와 같이 자연어를 통해서 특정 과업에 대해 맥락을 제공한다는 개념이 아니라, 코드 자체가 명세가 돼서 좀 더 컴퓨터 언어에 가깝게 개발을 접근하게 되는 방식이라는 생각이 들었습니다.
예를 들어, 인증 기능의 인터페이스를 다음과 같이 정의해두면:
// auth-core의 인터페이스 정의
interface IAuthService {
login(credentials: LoginDto): Promise<TokenPair>;
logout(userId: string): Promise<void>;
refreshToken(token: string): Promise<TokenPair>;
validateUser(payload: JwtPayload): Promise<User | null>;
}
AI에게 "새 프로젝트에서 인증 기능을 구현해줘"라고 요청할 때, 이 인터페이스를 참조하라고 하면 자연어로 설명하는 것보다 훨씬 정확한 결과물을 얻을 수 있습니다. 인터페이스는 메서드 시그니처, 입출력 타입, 에러 처리 방식까지 명확하게 정의하기 때문입니다.
테스트 코드와의 연관성
AI가 생성해낸 코드를 일일이 다 검토하기는 당연히 어렵습니다. 의도에 맞게 생산했는지 확인하고, 일부 기존 시스템을 수정했다면 기존에 동작하는 시스템은 여전히 안녕한지를 어떻게 기술적으로 검증할지에 대해서는 여러 이론들이 있을 거 같습니다.
여기서 테스트 코드의 역할이 중요해진다는 생각이 들었습니다. 인터페이스와 함께 테스트 케이스를 정의해두면:
- 구현 검증: AI가 생성한 코드가 기대하는 동작을 하는지 자동으로 확인
- 회귀 방지: 기존 기능이 여전히 정상 동작하는지 확인
- 명세로서의 테스트: 테스트 코드 자체가 "이 기능은 이렇게 동작해야 한다"는 명세가 됨
// 테스트가 명세가 되는 예시
describe("AuthService", () => {
it("올바른 자격증명으로 로그인하면 토큰 쌍을 반환해야 한다", async () => {
const result = await authService.login({
email: "test@example.com",
password: "valid"
});
expect(result).toHaveProperty("accessToken");
expect(result).toHaveProperty("refreshToken");
});
it("잘못된 비밀번호로 로그인하면 UnauthorizedException을 던져야 한다", async () => {
await expect(
authService.login({ email: "test@example.com", password: "wrong" })
).rejects.toThrow(UnauthorizedException);
});
});
앞으로의 방향
AI에게 일을 맡기고도 정작 불안에 떨며 걱정을 하지 않기 위해서는 어떻게 하면 좋을지를 계속 연구하고, 일부 덜 걱정될 수 있게 하는 방법을 적용해봐야 할 것 같습니다. 제가 생각하는 방향은 다음과 같습니다:
- 인터페이스 우선 설계: 구현 전에 인터페이스를 먼저 정의하여 AI에게 명확한 계약을 제공
- 테스트 주도 개발(TDD): 테스트를 먼저 작성하고 AI에게 테스트를 통과하는 코드를 요청
- 점진적 구현: 한 번에 큰 기능을 요청하기보다 작은 단위로 나누어 검증
- 코드 리뷰 자동화: 타입 체크, 린트, 테스트를 CI/CD에 포함하여 자동 검증
결국 추상화와 테스트 코드는 AI 시대에 더욱 중요해진 것 같다는 생각이 들었습니다. 자연어로 의도를 전달하는 것보다 코드로 명세를 작성하는 것이 더 정확하고, 테스트로 검증하는 것이 더 신뢰할 수 있기 때문입니다. 앞으로 이 방향으로 개발 프로세스를 개선해나가려고 합니다.
관련 글
블로그에 임베딩 기반 관련 글 추천 시스템 구축하기
Gemini Embedding API와 코사인 유사도를 활용해 블로그에 관련 글 추천 시스템을 구축한 경험을 공유합니다. 태그 기반의 단순한 방식에서 벗어나, 글의 실제 내용을 분석해 더 정확한 관련 글을 추천하는 방법을 소개합니다.
Prettier + husky + lint-staged로 팀 코드 스타일 자동화하기
코드 스타일 논쟁을 없애고 git commit 시 자동으로 포매팅되는 환경을 구축한 경험. Prettier 설정부터 husky + lint-staged 연동, git-blame-ignore-revs까지 실제 적용 과정을 정리했습니다.
Claude Code에서 블로그 글 작성하기: MCP를 직접 만들어 활용한 경험
Claude Code로 개발하면서 얻은 지식을 블로그에 정리하고 싶었습니다. 하지만 AI에게 블로그 관리자 권한을 모두 주기엔 불안했고, 필요한 API만 분기 처리하기엔 번거로웠습니다. MCP(Model Context Protocol)를 직접 만들어서 권한을 명확히 분리하고, Few-shot 예시와 SEO 가이드라인을 1회 호출로 제공하도록 개선한 경험을 공유합니다.