AI 생성 코드에서 관심사 분리 (Separation of Concerns)가 더 중요해지는 이유
요약
AI 코딩 도구를 사용하면서, 시스템이 너무 많은 책임을 한 곳에 섞어 놓는 것이 코드 생성 및 유지보수 측면에서 심각한 문제를 야기한다는 점을 깨달았습니다. 특히 책임들이 혼재된(Tightly coupled) 구조는 AI 모델에게 과도한 인지 부하를 주어 일관성 있고 예측 가능한 출력을 얻기 어렵게 만듭니다. 따라서 검증, 데이터베이스 액세스, 캐싱 등 각 관심사(Concerns)를 명확하게 분리하는 것이 단순히 유지보수성을 높이는 것을 넘어, AI 시스템이 안정적으로 작동하기 위한 필수적인 구조적 요구사항임을 강조합니다.
핵심 포인트
- 시스템의 책임(Responsibilities)을 명확히 분리해야 AI 생성 코드의 일관성과 예측 가능성이 높아진다.
- 책임이 혼재된 코드는 모델에게 과도한 인지 부하를 주어, 작은 요청에도 신뢰할 수 없는 출력을 만들게 한다.
- AI는 인간과 달리 제한된 맥락(Working memory) 내에서 작동하므로, 관심사 분리는 필수적인 구조적 기반이다.
- 검증, 영속성, 캐싱 등 각 기능을 전용 컴포넌트나 레이어로 격리하는 리팩토링이 필요하다.
AI 코딩 도구를 사용하는 것은 아키텍처에 대한 제 생각을 변화시켰지만, 그것이 어떤 거창한 "소프트웨어의 미래"와 같은 방식은 아니었습니다. 오히려 매일 실제 코드를 생성하고 수정하면서 깨닫기 시작한 실무적인 측면에 더 가깝습니다. 한 가지 패턴이 계속해서 반복적으로 나타나고 있습니다. 시스템이 너무 많은 책임 (Responsibilities)을 한데 섞어 놓으면, AI 생성 코드는 가이드하기가 더 어려워지고 일관성이 떨어집니다. 하지만 책임이 명확하게 분리되어 있으면, 출력 결과가 갑자기 훨씬 더 안정적이고 예측 가능해집니다. 이론적으로는 꽤 당연하게 들리겠지만, AI 도구를 정기적으로 사용하다 보면 그 차이가 훨씬 크게 느껴집니다.
상황이 엉망이 되기 시작하는 지점
많은 실제 프로젝트, 특히 오래된 프로젝트에서는 시간이 흐름에 따라 책임들이 서서히 한곳으로 흘러 들어갑니다. 단일 서비스가 검증 (Validation), 데이터베이스 액세스 (Database access), 캐싱 (Caching), 오케스트레이션 (Orchestration), 매핑 (Mapping), 그리고 때로는 권한 부여 (Authorization)까지 모두 처리할 수도 있습니다. 이는 보통 점진적으로 발생하기 때문에, 나중에 훨씬 더 복잡해질 때까지 아무도 그것이 복잡해지고 있다는 사실을 정말로 알아차리지 못합니다. 인간에게 이것은 짜증 나는 일이지만, 이미 코드베이스를 잘 알고 있다면 여전히 관리할 수 있는 수준입니다. 하지만 AI 도구의 경우, 이러한 구조는 매우 빠르게 문제를 일으킵니다. 이제 모델은 여러 관심사 (Concerns)를 동시에 해석해야 합니다. 무엇이 어디에 속하는지 결정해야 하고, 관련 없는 로직을 깨뜨리는 것을 피해야 하며, 더 이상 명확하게 정의되지 않은 경계 안에서 어떻게든 일관성을 유지해야 합니다. 대부분의 경우 생성된 코드가 완전히 틀린 것은 아닙니다. 문제는 그보다 더 미묘합니다. 출력 결과가 덜 신뢰할 수 있게 된다는 점입니다. 프롬프트 (Prompts)의 작은 변화가 매우 다른 구현으로 이어질 수 있으며, 전반적인 동작이 일관성이 없다고 느껴지기 시작합니다.
강하게 결합된 (Tightly coupled) 코드가 어려워지는 이유
문제는 대개 AI가 코딩을 할 수 없어서가 아닙니다. 시스템 자체가 충분한 구조를 제공하지 못하기 때문입니다.
모든 것이 긴밀하게 연결되어 있을 때:
- 변경 사항이 어디에서 일어나야 하는지 불분명해집니다.
- 책임 (Responsibilities)이 중첩됩니다.
- 작은 수정이 관련 없는 영역에 영향을 미칩니다.
- 그리고 아키텍처 결정 (Architectural decisions)이 명시적 (Explicit)인 대신 암시적 (Implicit)이 됩니다.
따라서 코드를 생성하는 동안, 모델은 동시에 아키텍처에 대한 가정을 강제로 수행해야 합니다. 보통 바로 이 지점에서 상황이 어긋나기 시작합니다. 제가 이 사실을 깨닫기 시작한 순간은 미묘한 점 하나를 발견했을 때였습니다. AI 모델은 인간이 오랜 시간에 걸쳐 코드베이스 (Codebase)를 처리하는 방식과는 다르게 작동한다는 점입니다.
개발자로서 우리는 천천히 정신적 맥락 (Mental context)을 구축합니다. 우리는 왜 특정 결정이 내려졌는지, 시스템의 어느 부분이 취약한지, 그리고 역사적인 이유로 어떤 지름길 (Shortcuts)이 존재하는지를 기억합니다. 설령 지저분한 시스템이라 할지라도, 우리가 그 맥락을 머릿속에 담고 있기 때문에 어느 정도 관리 가능한 수준이 됩니다.
LLM은 다르게 작동합니다. 이들은 한 번에 제한된 양의 맥락만을 보며, 그 맥락은 일종의 작업 기억 (Working memory)처럼 작동합니다. 따라서 단일 클래스 (Class)나 컴포넌트 (Component)가 너무 많은 책임을 섞어 놓으면, 제한된 맥락의 상당 부분이 당신이 시도하려는 실제 변경 사항 대신 관련 없는 세부 사항들에 의해 소비되어 버립니다.
모델은 더 이상 기능 (Feature) 자체에만 순수하게 집중할 수 없게 됩니다. 검증 로직 (Validation logic), 인프라 관련 사항 (Infrastructure concerns), 상태 처리 (State handling), 부작용 (Side effects), 포맷팅 (Formatting), 오케스트레이션 (Orchestration), 그리고 같은 곳에 존재하는 그 외 모든 것들에 주의 (Attention)를 기울이게 됩니다. 그 시점에서는 신호 (Signal)가 너무 많은 노이즈 (Noise) 속에 파묻히기 때문에, 아주 작은 요청조차 일관되게 실행하기가 더 어려워집니다.
그 순간 저에게 관심사 분리 (Separation of concerns)는 다르게 느껴지기 시작했습니다. 그것은 단순히 유지보수성 (Maintainability) 원칙처럼 느껴지는 것을 넘어, 인간과 AI 시스템 모두를 위한 인지 부하 (Cognitive load)를 줄이는 방법처럼 느껴지기 시작했습니다.
당신은 다음과 같은 현상들을 목격하게 됩니다:
- 계층 (Layers) 전반에 걸쳐 로직이 중복됨
- 인프라 관련 사항이 도메인 로직 (Domain logic)으로 유출됨
- 메서드 (Methods)가 점점 더 커짐
- 컴포넌트가 서서히 관련 없는 책임들을 떠맡게 됨
이 과정에서 반드시 즉각적으로 무언가가 망가지는 것은 아닙니다.
시간이 흐를수록 코드는 정밀함이 떨어질 뿐입니다. 책임을 분리한 후 무엇이 변했을까요? 어느 시점에 저는 이러한 서비스 중 하나를 리팩터링(Refactoring)하며 더 공격적으로 분리했습니다. 모든 것을 처리하는 하나의 거대한 서비스 대신 다음과 같이 구성했습니다:
- 검증(Validation)은 전용 컴포넌트로 이동
- 영속성(Persistence)은 리포지토리(Repositories) 뒤로 이동
- 캐싱(Caching)은 격리됨
- 오케스트레이션(Orchestration)은 워크플로우(Workflow)만 처리
- 도메인 로직(Domain logic)은 비즈니스 규칙에만 집중
시스템의 동작은 거의 변하지 않았습니다. 하지만 구조는 훨씬 더 추론하기 쉬워졌습니다. 저를 놀라게 했던 점은 이것이 AI가 생성한 출력물에도 큰 영향을 미친다는 사실이었습니다.
UI 컴포넌트에서도 동일한 현상이 나타났습니다. 프론트엔드(Frontend) 코드에서도 매우 유사한 패턴이 관찰되기 시작했습니다. 일부 UI 컴포넌트들이 렌더링(Rendering), API 호출, 검증, 상태 관리(State management), 포맷팅(Formatting), 그리고 비즈니스 로직을 한꺼번에 처리하는 거대한 파일로 점차 변해 있었습니다.
인간에게 이러한 컴포넌트는 이미 유지보수하기 어렵게 느껴집니다. 하지만 AI가 생성한 변경 사항이 더해지면 문제는 더욱 명확해집니다. 모델에게 로딩 상태(Loading state)를 추가해 달라고 요청하면, 갑자기 관련 없는 렌더링 로직까지 함께 변해버립니다. 또는 작은 시각적 조정을 요청했는데, 모든 것이 동일한 컴포넌트 내부에 연결되어 있기 때문에 검증 동작이 실수로 수정되기도 합니다.
책임을 더 명확하게 분리한 후, 상황은 크게 개선되었습니다. 예를 들면 다음과 같습니다:
- 데이터 페칭(Data fetching)이 훅(Hooks) 또는 서비스로 이동
- 비즈니스 규칙이 컴포넌트 외부로 이동
- 포맷팅 로직이 재사용 가능한 유틸리티(Utilities)가 됨
- 상태 관리(State management)가 더 격리됨
- 컴포넌트가 주로 렌더링과 상호작용(Interaction)에 집중함
UI 레이어가 더 집중된 형태가 되자, AI가 생성한 변경 사항은 눈에 띄게 예측 가능해졌습니다. 작은 프롬프트(Prompts)는 작은 변화에 머물렀습니다. 시각적 업데이트가 관련 없는 로직에 영향을 주는 일이 줄어들었습니다. 그리고 생성된 코드는 매번 기존 구조를 부분적으로 재발명하는 대신, 기존 구조와 훨씬 더 잘 일치하는 느낌을 주었습니다.
이후에 제가 발견한 점
이러한 리팩터링 (refactoring) 이후, 몇 가지 사항이 지속적으로 개선되었습니다:
- 프롬프트 (Prompt)가 더 짧아졌습니다.
- 생성된 코드가 올바른 레이어 (layer) 내에 머무는 경우가 더 많아졌습니다.
- 불필요한 의존성 (dependency)이 줄어들었습니다.
- 중복이 감소했습니다.
- 이후 생성된 코드를 수정하는 데 쓰는 시간이 줄어들었습니다.
흥미로운 점은 이러한 개선이 더 나은 프롬프팅 기술 (prompting techniques)에서 온 것이 아니라는 사실입니다. 이는 주로 코드베이스 (codebase) 자체 내부의 모호함 (ambiguity)을 줄임으로써 얻어진 결과였습니다. 경계 (boundaries)가 명확해지자, 모델이 추측해야 할 사항이 줄어들었습니다.
이것은 사실 새로운 개념이 아닙니다.
소프트웨어 엔지니어링 (software engineering) 관점에서 이 중 새로운 것은 아무것도 없습니다. 관심사 분리 (Separation of concerns)는 항상 중요했습니다. 지금의 차이점은 AI 도구들이 이를 무시했을 때 발생하는 비용을 훨씬 더 눈에 띄게 만든다는 것입니다. 개발자로서 우리는 시간이 흐르면서 서서히 정신적 맥락 (mental context)을 쌓아가기 때문에 지저분한 시스템을 보완할 수 있습니다. 우리는 과거의 결정, 임시방편 (workarounds), 그리고 숨겨진 가정들을 기억합니다. AI는 그러한 종류의 축적된 이해를 실제로 가지고 있지 않습니다. AI는 주로 바로 앞에 놓인 구조와 함께 작동합니다. 따라서 아키텍처 (architecture)가 불분명하면, 그 불일치는 생성된 결과물에 즉각적으로 나타납니다.
관점의 작은 변화
우리가 오직 AI만을 위해 시스템을 설계하기 시작해야 한다고 생각하지는 않습니다. 하지만 이러한 도구들을 사용하면서, AI 코딩 어시스턴트 (AI coding assistants)가 존재하기 훨씬 전부터 이미 사실이었던 한 가지가 강화되었습니다: 좋은 아키텍처는 모호함을 줄여줍니다. 명확한 경계는 시스템을 추론하기 쉽게 만듭니다. 강한 결합 (Tight coupling)은 마찰을 일으킵니다. 혼합된 책임 (Mixed responsibilities)은 변경 사항을 제어하기 어렵게 만듭니다.
어느 시점에 이르러, 이것은 저에게 더 이상 "AI 문제"처럼 느껴지지 않고, 오래된 소프트웨어 엔지니어링 원칙을 상기시켜 주는 것처럼 느껴지기 시작했습니다: 만약 어떤 시스템을 이해하기 어렵다면, 그것을 다루는 대상이 인간이든 기계든 상관없이 대개 다루기 어려울 것입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기