바이브 코딩(Vibe-coded) 코드가 부패하지 않게 유지하는 한 가지 간단한 명명 규칙
요약
모호한 클래스 명명(예: Manager, Service)이 초래하는 설계의 부패를 방지하기 위한 명명 규칙을 제안합니다. 구체적이고 행위 중심적인 이름을 사용함으로써 코드의 책임 범위를 명확히 하고, 잘못된 의존성 확장을 방지할 수 있습니다.
핵심 포인트
- 모호한 이름은 클래스의 과도한 책임과 나쁜 설계를 은폐함
- 행위 주체적인 이름(예: PasswordResetter)은 설계 오류를 즉각 드러냄
- 이름을 짓기 어렵다면 그것은 명명의 문제가 아닌 설계(책임 분리)의 문제임
- 정확한 명명은 컴포넌트 간의 의존성 그래프를 더 명확하게 만듦
_I Shall Call It… SomethingManager_에서, Jeff Atwood는 다음과 같은 이름들을 비판했습니다:
SessionManager
ConnectionManager
UrlManager
Manager는 클래스가 무언가를 가지고 무언가를 한다는 것을 알려줍니다.
유용하긴 하죠.
우리는 대부분 모든 것을 Manager라고 부르는 것을 멈췄습니다. 이제는 다음과 같이 부릅니다:
UserService
PaymentHandler
OrderProcessor
...
접미사(suffix)는 바뀌었지만, 모호함은 살아남았습니다.
모호한 이름은 모호한 코드를 허용한다
UserService에는 무엇이 포함되어야 할까요?
register()
resetPassword()
mergeAccounts()
...
이 모든 것들은 사용자와 관련이 있습니다.
하지만 아키텍처 측면에서의 유사성은 여기서 끝납니다.
이들은 서로 다른 규칙, 의존성(dependencies), 부작용(side effects), 보안 고려 사항, 그리고 변경 이유를 가지고 있습니다. 하지만 이름은 그 중 어느 것에도 이의를 제기하지 않습니다.
UserService는 경계(boundary)가 아닙니다. 그것은 허가(permission)입니다.
이제 다음을 비교해 보세요:
UserRegistrar
PasswordResetter
AccountMerger
...
이것들은 행위 주체적인(agentive) 이름들입니다. 이들은 특정 기능(capability)을 담당하는 컴포넌트를 명명합니다.
이들의 가치는 문법적 우아함이 아닙니다. 이들의 가치는 저항력입니다.
만약 PasswordResetter가 할인율을 계산하기 시작한다면, 파일을 열어보기 전에도 그 실수가 눈에 보입니다.
UserService 내부에서는 그것이 아주 적절하게 수행되고 있는 것처럼 보입니다.
이름은 의존성을 형성한다
다음 의존성 그래프(dependency graph)는 그럴듯해 보입니다:
UserService
├── EmailSender
├── PaymentGateway
...
사용자는 이 모든 것들과 상호작용합니다.
반면, 다음은 더 구체적인 이야기를 들려줍니다:
PasswordResetter
├── UserFinder
├── ResetTokenIssuer
...
만약 PasswordResetter가 갑자기 InvoiceGenerator를 필요로 하게 된다면, 누군가는 그 이유를 설명해야 합니다.
그러한 마찰(friction)은 유용합니다.
정확한 이름은 자연스러워 보이는 책임과 의존성을 좁혀줍니다. 그것이 나쁜 설계를 방지하지는 못하지만, 나쁜 설계의 위장(camouflage)을 제거합니다.
명명은 분해 문제를 드러낸다
때때로 어떤 컴포넌트는 거짓말을 하지 않고서는 구체적인 이름을 붙여줄 수 없습니다.
그래서 우리는 다음과 같이 부릅니다:
Manager
Service
Handler
...
이것은 대개 명명(naming)의 문제로 취급됩니다.
하지만 그것은 설계(design)의 문제일 수 있습니다.
만약 하나의 클래스가 사용자를 등록하고, 비밀번호를 재설정하며, 역할을 할당하고, 할인을 계산하고, 계정을 병합한다면, 그 클래스에 대해 정직하고 짧은 이름을 붙이는 것은 불가능할 수도 있습니다.
그것은 정보입니다.
컴포넌트의 이름을 짓는 것이 어렵다는 것은, 해당 컴포넌트가 하나의 일관된 책임 (responsibility)을 나타내지 못한다는 증거입니다.
책임을 분리하면, 이름은 자연스럽게 나타납니다:
UserRegistrar
PasswordResetter
RoleAssigner
...
이름이 설계를 만든 것이 아닙니다.
이름은 누락된 설계를 무시하기 어렵게 만들었을 뿐입니다.
이름은 구성을 가시화합니다
거대한 기능들은 종종 더 작은 기능들로부터 구축됩니다.
OrderPlacer
├── CartValidator
├── PriceCalculator
...
OrderPlacer는 시퀀스 (sequence)를 소유합니다. 협력자 (collaborators)들은 각자의 규칙을 소유합니다.
이러한 책임들에 대한 이름이 없다면, 동일한 설계는 종종 OrderService 내부의 긴 메서드로 변질되며, 생성되지 않은 컴포넌트들을 설명하는 주석들로 분리될 뿐입니다.
행위 중심적 명명 (Agentive naming)은 책임에 주소 (address)를 부여합니다.
그 덕분에 책임들을 테스트, 교체, 재사용 및 구성하기가 더 쉬워집니다.
이것이 모든 동사에 대해 하나의 클래스를 만들어야 한다는 뜻은 아닙니다. 그것은 단지 명사 중심의 관료주의를 동사 중심의 관료주의로 대체할 뿐입니다.
규칙은 더 간단합니다:
책임에 대해, 관련 없는 동작이 부적절해 보일 정도로 충분히 정밀한 이름을 부여하세요.
이것은 아키텍처가 아닙니다
행위 중심적 명명 (Agentive naming)은 아키텍처 패턴이 아닙니다.
그것은 단지 명명 (naming)일 뿐입니다.
하지만 이름은 어떤 책임이 자연스럽게 보일지에 영향을 미칩니다. 책임은 의존성 (dependencies)에 영향을 미칩니다. 의존성은 아키텍처가 됩니다.
따라서 "단순한 명명"은 광고된 것보다 훨씬 더 많은 일을 수행하고 있는 셈입니다.
좋은 이름이 좋은 소프트웨어를 보장하지는 않습니다.
다만, 나쁜 소프트웨어를 위장하기 어렵게 만들 뿐입니다.
이것은 AI와 함께할 때 더 중요합니다
AI 코딩 에이전트 (AI coding agents)는 이미 존재하는 코드로부터 구조를 추론합니다.
만약 다음과 같이 준다면:
UserService
PaymentManager
OrderProcessor
그들은 매우 자신 있게 다음 책임을 가장 가까운 모호한 컨테이너 (container) 안에 집어넣을 것입니다.
상당히 빠른 속도로 말이죠.
만약 다음과 같이 준다면:
PasswordResetter
RefundIssuer
OrderCanceller
그리고 의도된 경계(boundaries)를 더 쉽게 추론할 수 있습니다.
이제 당신의 코드베이스는 하나의 프롬프트(prompt)이기도 합니다.
모호한 이름은 모호한 설계를 가르칩니다. AI는 단지 그 교훈을 산업화할 뿐입니다.
명명(Naming)은 책임을 위한 약한 타입 시스템(weak type system)입니다.
UserService는 any입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기