명확한 추상화, 네이밍, 경계가 대규모 코드베이스에서 위험을 줄이고 변경을 더 빠르게 만드는 이유를 알아보세요 — 종종 구문 선택보다 더 큰 영향이 있습니다.

사람들이 프로그래밍 언어를 놓고 논쟁할 때 주로 논쟁하는 것은 구문입니다: 아이디어를 표현하기 위해 타이핑하는 단어와 기호. 중괄호 대 들여쓰기, 변수 선언 방식, map()을 쓸지 for 루프를 쓸지 같은 것들이 구문에 해당합니다. 구문은 가독성과 개발자 편의에 영향을 주지만—주로 “문장 구조” 수준에서 그렇습니다.
추상화는 다릅니다. 추상화는 코드가 들려주는 “이야기”입니다: 선택한 개념들, 책임을 그룹화하는 방식, 변경이 시스템 전체로 번지지 않도록 막는 경계들. 추상화는 모듈, 함수, 클래스, 인터페이스, 서비스로 나타나고, 심지어는 “모든 금액은 센트로 저장한다” 같은 단순한 규약으로도 드러납니다.
작은 프로젝트에서는 시스템 대부분을 머릿속에 넣고 관리할 수 있습니다. 대규모로 오래 운영되는 코드베이스에서는 그럴 수 없습니다. 새로운 동료가 합류하고 요구사항이 바뀌며 기능이 놀라운 곳에서 추가됩니다. 그 순간에는 언어가 "쓰기 편한가"보다 코드에 명확한 개념과 안정적인 접합점(Seams)이 있는지가 더 중요해집니다.
언어도 여전히 중요합니다: 어떤 언어는 특정 추상화를 더 쉽게 표현하게 해 주거나 남용을 어렵게 만들기도 합니다. 요점은 “구문이 무관하다”가 아니라 시스템이 커지면 구문이 병목이 되는 경우는 드물다는 것입니다.
강한 추상화와 약한 추상화를 식별하는 법, 경계와 네이밍이 어떻게 큰 역할을 하는지, 누수 추상화 같은 흔한 함정, 두려움 없이 변경할 수 있는 코드로 리팩터링하는 실용적 방법들을 다룹니다.
작은 프로젝트에서는 “좋은 구문”만으로도 버틸 수 있습니다. 실수가 국소적으로 끝나기 때문입니다. 대규모·장기간 운영되는 코드베이스에서는 모든 결정이 곱해집니다: 파일 수 증가, 기여자 증가, 릴리스 주기 증가, 고객 요청 증가, 통합 포인트 증가 — 깨질 수 있는 지점이 늘어납니다.
대부분의 엔지니어링 시간은 새 코드를 쓰는 데 쓰이지 않습니다. 주로 다음을 합니다:
이게 일상이면, 언어가 루프를 우아하게 표현하는지보다 코드베이스에 "어디를 보면 안전하게 변경할 수 있는지"를 알려주는 명확한 심이 있는지가 더 중요합니다.
큰 팀에서는 “국소적” 선택이 국소적이지 않게 됩니다. 한 모듈이 다른 에러 스타일, 네이밍 방식, 의존성 방향을 쓰면 나중에 그 모듈을 건드리는 모든 사람에게 추가적인 정신적 부하를 줍니다. 이를 수백 개 모듈과 수년의 인력 교체에 곱하면 코드베이스를 탐색하기 비쌀 수밖에 없습니다.
추상화(좋은 경계, 안정적인 인터페이스, 일관된 네이밍)는 조정 도구입니다. 서로 다른 사람들이 적은 놀라움으로 병렬로 작업할 수 있게 해 줍니다.
"체험판 만료 알림"을 추가한다고 상상해 보세요. 간단히 들리지만 추적해 보면:
이 영역들이 명확한 인터페이스(예: 테이블을 노출하지 않는 "trial status"를 제공하는 결제 API)를 통해 연결되어 있다면 변경은 국소적 수정을 통해 구현할 수 있습니다. 모든 것이 서로 엉켜 있다면 기능 추가는 위험한 횡단 수술이 됩니다.
대규모에서는 우선순위가 기교적 표현에서 안전하고 예측 가능한 변경으로 이동합니다.
좋은 추상화는 복잡성을 숨기는 것보다 의도를 드러내는 것입니다. 잘 설계된 모듈을 읽으면 어떤 일을 하는지를 먼저 알 수 있어야 하며, 어떻게 하는지를 강제로 배우게 되어서는 안 됩니다.
좋은 추상화는 여러 단계를 하나의 의미 있는 아이디어로 바꿉니다: Invoice.send()는 “PDF 포맷 → 이메일 템플릿 선택 → 파일 첨부 → 실패 시 재시도”보다 이해하기 쉽습니다. 세부는 여전히 존재하지만 경계 뒤에 숨어 있어, 변경되더라도 나머지 코드를 끌고 가지 않습니다.
대규모 코드베이스가 어려워지는 이유는 작은 변경마다 열 파일을 "안전하게" 읽어야 하기 때문입니다. 추상화는 필요한 읽기량을 줄입니다. 호출 코드가 "이 고객을 청구해라", "사용자 프로필을 가져와라", "세금을 계산해라" 같은 명확한 인터페이스에 의존하면 구현을 바꿔도 관련 없는 동작을 실수로 바꾸지 않을 수 있습니다.
요구사항은 단지 기능을 추가하는 것이 아니라 가정들을 바꿉니다. 좋은 추상화는 그런 가정을 업데이트해야 할 장소를 적게 만듭니다.
예: 결제 재시도, 사기 검사, 환전 규칙이 바뀌면 하나의 결제 경계만 업데이트하면 되기를 바랍니다—앱 전역에 흩어진 호출 지점을 고치는 대신.
팀은 동일한 시스템 "핸들"을 공유할 때 더 빠르게 움직입니다. 일관된 추상화는 정신적 지름길이 됩니다:
Repository를 사용하라"HttpClient를 통하라"Flags 뒤에 둔다"이런 지름길은 코드 리뷰에서 논쟁을 줄이고 온보딩을 쉽게 합니다. 패턴이 반복되면 예측 가능해지기 때문입니다.
언어를 바꾸거나 프레임워크를 도입하거나 더 엄격한 스타일 가이드를 강제하면 난잡한 시스템이 "고쳐진다"고 믿고 싶어지기 쉽습니다. 하지만 구문을 바꾼다고 근본적인 설계 문제가 사라지진 않습니다. 의존성이 엉켜 있고 책임이 불명확하며 모듈을 독립적으로 변경할 수 없으면, 보기 좋은 구문은 보기 좋은 매듭만 만들어줄 뿐입니다.
두 팀이 다른 언어로 같은 기능 세트를 구현해도 같은 고통에 이르는 경우가 있습니다: 비즈니스 규칙이 컨트롤러에 흩어지고, 모든 곳에서 직접 DB에 접근하며, 유틸리티 모듈이 서서히 쓰레기장처럼 되는 경우입니다.
그 이유는 구조가 구문과 대부분 독립적이기 때문입니다. 어떤 언어든:
변경하기 어려운 코드베이스의 근본 원인은 대체로 경계입니다: 불명확한 인터페이스, 섞인 관심사, 숨겨진 결합. 구문 논쟁은 함정이 될 수 있습니다—팀은 중괄호, 데코레이터, 이름 규칙 같은 것에 수시간을 낭비하는 동안 실제 작업(책임 분리와 안정적 인터페이스 정의)은 미뤄집니다.
구문이 무의미하진 않습니다; 다만 좁고 실용적인 방식으로 중요합니다.
가독성. 명확하고 일관된 구문은 사람이 코드를 빠르게 훑어보는 데 도움을 줍니다. 이는 많은 사람이 건드리는 핵심 도메인 로직, 공용 라이브러리, 통합 지점에서 특히 가치가 있습니다.
핫스팟의 정확성. 모호한 우선순위를 피하거나, 오용을 방지하는 명시적 타입을 선호하거나, 불법 상태를 표현 불가능하게 만드는 구문 선택은 버그를 줄입니다.
지역적 표현력. 성능 또는 보안 민감 영역에서는 에러 처리, 동시성 표현, 자원 획득과 해제 방식 같은 세부가 중요합니다.
요약: 스타일 규칙은 마찰을 줄이고 흔한 실수를 방지하도록 사용하되, 설계 부채를 낫게 해줄 것으로 기대하지는 마세요. 코드베이스가 저항하면 먼저 더 나은 추상화와 경계를 만드는 데 집중하고, 그 구조를 돕는 방향으로 스타일을 맞추세요.
큰 코드베이스가 보통 실패하는 이유는 팀이 “잘못된” 구문을 선택해서가 아닙니다. 모든 것이 모두를 건드릴 수 있기 때문입니다. 경계가 흐릿하면 작은 변경이 시스템 전체로 파급되고, 리뷰가 시끄러워지며, "빠른 수정"이 영구적 결합이 됩니다.
건강한 시스템은 명확한 책임을 가진 모듈들로 이루어집니다. 건강하지 않은 시스템은 검증, 영속성, 비즈니스 규칙, 캐싱, 포맷팅, 오케스트레이션을 모두 한곳에서 하는 ‘갓 모듈’을 축적합니다.
좋은 경계는 다음 질문에 답할 수 있게 합니다: 이 모듈은 무엇을 소유하는가? 명시적으로 무엇을 소유하지 않는가? 한 문장으로 말할 수 없다면 아마도 그 모듈은 너무 광범위합니다.
경계는 입력, 출력, 동작 보장 같은 안정적인 인터페이스로 실체화됩니다. 이걸 계약으로 다루세요. 시스템의 두 부분이 대화할 때는 테스트와 버전 관리가 가능한 작은 표면적을 통해 해야 합니다.
이것이 팀이 확장되는 방식이기도 합니다: 서로 다른 사람이 각기 다른 모듈을 조율 없이 작업할 수 있는 이유는 계약이 중요하기 때문입니다.
레이어링(UI → 도메인 → 데이터)은 세부가 위로 새어나오지 않을 때 잘 작동합니다.
상세가 새어나오면 "데이터베이스 엔티티를 그대로 UI로 전달" 같은 지름길이 생기고, 현재 저장소 선택에 묶이게 됩니다.
간단한 규칙 하나가 경계를 지켜줍니다: 의존성은 도메인 쪽으로 안쪽을 향해야 합니다. 모든 것이 서로 의존하는 설계를 피하세요; 변화가 위험해지는 곳이 바로 그 지점입니다.
어디서부터 시작할지 모르겠다면 한 기능의 의존성 그래프를 그려보세요. 가장 고통스러운 엣지가 보통 고쳐야 할 첫 경계입니다.
이름은 사람들이 처음 접하는 추상화입니다. 타입 계층, 모듈 경계, 데이터 흐름을 이해하기 전에 식별자들을 파싱하고 그로부터 정신 모델을 만듭니다. 네이밍이 명확하면 그 모델이 빠르게 형성되고, 애매하거나 ‘귀여운’ 이름이면 한 줄 한 줄이 퍼즐이 됩니다.
좋은 이름은 무엇을 위한 것인가를 답합니다, 어떻게 구현했는가를 말하지 않습니다. 비교:
process() vs applyDiscountRules()data vs activeSubscriptionshandler vs invoiceEmailSender기교적인 이름은 시간이 지나면서 잘 맞지 않게 됩니다: 내부 농담, 약어, 언어 유희에 의존하기 때문입니다. 의도를 드러내는 이름은 팀, 시차, 신규 채용자를 넘어 잘 전달됩니다.
대규모 코드베이스는 공유 언어에 의해 살거나 죽습니다. 비즈니스에서 어떤 것을 "policy"라고 부른다면 코드에서 contract라고 이름짓지 마세요—도메인 전문가에게는 그게 다른 개념일 수 있습니다.
어휘를 도메인과 정렬하면 두 가지 이점이 있습니다:
도메인 언어가 지저분하다면 제품/운영팀과 협력해 용어집을 합의하세요. 코드가 그 합의를 강화할 수 있습니다.
네이밍 관례는 스타일 그 자체라기보다 예측 가능성에 관한 것입니다. 읽는 사람이 형태만 보고 목적을 유추할 수 있을 때 더 빠르고 덜 실수합니다.
유용한 관례 예시:
Repository, Validator, Mapper, Service 같은 접미사는 실제 책임에 맞을 때만 사용is, has, can으로 시작하고 이벤트는 과거형으로 (PaymentCaptured) 표현users는 컬렉션, user는 단일 항목목표는 엄격한 규제 아니라 이해 비용을 낮추는 것입니다. 장기 시스템에서는 그게 복리 효과를 만듭니다.
대규모 코드베이스는 쓰이는 것보다 읽히는 빈도가 훨씬 높습니다. 각 팀이나 개발자가 같은 문제를 다른 방식으로 풀면 새 파일마다 작은 퍼즐이 됩니다. 그 불일치는 독자가 각 영역의 "지역 규칙"(여기선 에러 처리 방식, 저기선 데이터 검증 방식, 저곳의 서비스 구조)을 다시 배우게 만듭니다.
일관성이란 지루한 코드가 아니라 예측 가능한 코드입니다. 예측 가능성은 인지 부하를 줄이고 리뷰 주기를 단축하며, 사람들이 기발한 구성 대신 익숙한 패턴에 의존할 수 있게 해 변경을 더 안전하게 만듭니다.
기교적인 해결책은 종종 작성자의 단기 만족을 최적화합니다: 멋진 트릭, 간결한 추상화, 맞춤형 미니 프레임워크. 하지만 장기 시스템에서는 비용이 나중에 드러납니다:
결과적으로 코드베이스는 실제보다 커 보입니다.
팀이 API 엔드포인트, DB 접근, 백그라운드 작업, 재시도, 검증, 로깅 같은 반복 문제에 대해 공유 패턴을 쓰면 새 인스턴스를 이해하는 데 드는 시간이 줄어듭니다. 리뷰어는 구조 논쟁 대신 비즈니스 로직에 집중할 수 있습니다.
패턴 세트는 작고 의도적으로 유지하세요: 문제 유형당 승인된 몇 가지 패턴만. 페이징을 하는 방법이 다섯 가지라면 사실상 표준이 없는 것입니다.
표준은 구체적일 때 가장 잘 작동합니다. 짧은 내부 페이지에 다음을 보여주세요:
…이런 문서는 긴 스타일 가이드보다 더 효과적입니다. 코드 리뷰에서 중립적 참조 지점이 되므로 "취향" 싸움을 줄여줍니다.
고민할 곳이 있다면 변경이 자주 일어나는 한 영역을 고르고 패턴을 합의한 뒤, 시간이 흐르며 그 쪽으로 리팩터링하세요. 일관성은 명령으로 얻어지지 않고 꾸준한 조율로 이뤄집니다.
좋은 추상화는 코드를 읽기 쉽게 할 뿐만 아니라 변경하기 쉽게 만듭니다. 올바른 경계를 찾았다는 최고의 증거는 새 기능이나 버그 픽스가 작은 영역만 건드리고 나머지 시스템은 자신 있게 손대지 않아도 되는 경우입니다.
추상화가 실체화되면 그것은 계약으로 설명할 수 있습니다: 이러한 입력이 주어지면 이 출력이 나온다, 몇 가지 규칙과 함께. 테스트는 대부분 그 계약 수준에 있어야 합니다.
예: PaymentGateway 인터페이스가 있다면 테스트는 결제가 성공할 때, 실패할 때, 타임아웃일 때 무슨 일이 나는지를 검증해야지 어떤 헬퍼 메서드가 호출되었는지를 검증하면 안 됩니다. 이렇게 하면 성능 개선, 제공자 교체, 내부 리팩터링을 해도 테스트의 대부분을 다시 쓰지 않아도 됩니다.
계약을 쉽게 나열할 수 없다면 추상화가 흐릿하다는 힌트입니다. 다음을 답함으로써 좁히세요:
이것들이 명확해지면 테스트 케이스는 저절로 작성됩니다: 규칙마다 한두 개, 그리고 몇 가지 엣지 케이스.
테스트가 구현 선택을 고정할 때 취약해집니다. 흔한 냄새:
리팩터가 사용자에게 보이는 동작을 바꾸지 않았는데도 테스트를 많이 고쳐야 한다면 대개는 테스트 전략의 문제입니다. 경계에서 관찰 가능한 결과에 집중하면 빠르고 안전하게 변경할 수 있습니다.
좋은 추상화는 생각해야 할 것을 줄입니다. 나쁜 추상화는 그 반대입니다: 겉보기엔 깔끔하지만 실제 요구가 닥치면 내부 지식이나 추가 의례를 요구합니다.
누수 추상화는 호출자가 제대로 사용하려면 내부 세부를 알아야 하게 만듭니다. 징후는 "X를 호출한 뒤에 Y를 호출해야 한다"거나 "이건 연결이 미리 웜업되어 있어야 동작한다" 같은 주석이 필요할 때입니다. 그 시점에 추상화는 복잡성을 보호해 주는 대신 옮겨놓은 것뿐입니다.
전형적인 누수 패턴:
호출자가 같은 가드 코드, 재시도, 순서 규칙을 반복적으로 추가한다면 그 로직은 추상화 안으로 들어가야 합니다.
너무 많은 레이어는 단순한 동작을 추적하기 어렵게 만듭니다. 래퍼 위의 래퍼는 한 줄의 결정을 보려면 보물찾기를 해야 하는 상황을 만듭니다. 이런 현상은 보통 "혹시 몰라" 미리 추상화를 만들 때 발생합니다.
자주 발생하는 우회, 반복되는 특수 사례, 증가하는 탈출구(플래그, 우회 메서드, 고급 파라미터)가 보이면 곧 문제가 생깁니다. 이는 추상화의 형태가 실제 사용 방식과 맞지 않는 신호입니다.
일반 경로를 잘 커버하는 작고 의견이 분명한 인터페이스를 선호하세요. 여러 실제 호출자가 필요하다고 말할 수 있고, 내부를 참조하지 않고 새 동작을 설명할 수 있을 때만 기능을 추가하세요.
탈출구를 노출해야 한다면 기본 경로가 아니라 명시적이고 드문 방법으로 만드세요.
더 나은 추상화로의 리팩터링은 "정리"가 아니라 작업의 형태를 바꾸는 일입니다. 목표는 미래 변경을 더 싸게 만드는 것: 수정해야 할 파일 수를 줄이고, 이해해야 할 의존성을 줄이며, 작은 수정이 관련 없는 무언가를 깨뜨리는 지점을 줄이는 것입니다.
대규모 재작성은 명확성을 약속하지만 시스템에 내재된 소중한 지식(엣지케이스, 성능 특성, 운영 동작)을 초기화하는 위험이 큽니다. 작은 지속적 리팩터링은 기능을 출시하면서 기술 부채를 갚아갑니다.
실용적 접근법은 실제 기능 작업에 리팩터링을 붙이는 것입니다: 어느 영역을 건드릴 때마다 다음 번 건드리기 쉽게 조금씩 개선하세요. 이게 몇 달이면 누적됩니다.
로직을 옮기기 전에 심(인터페이스, 래퍼, 어댑터, 파사드)을 만드세요. 심은 한 번에 모든 것을 다시 쓰지 않고 동작을 리디렉션할 수 있게 합니다.
예: 직접 DB 호출을 레포지토리 유사 인터페이스 뒤에 래핑하세요. 그러면 나머지 코드는 같은 경계와 대화하는 동안 쿼리, 캐싱, 심지어 저장소 기술을 바꿀 수 있습니다.
이 사고방식은 AI 보조 도구로 빠르게 구축할 때도 유용합니다: 가장 빠른 길은 여전히 먼저 경계를 설정한 뒤 그 뒤에서 반복하는 것입니다.
좋은 추상화는 일반 변경을 위해 수정해야 할 코드베이스 양을 줄입니다. 비공식적으로 측정하세요:
변경이 일관되게 접촉 지점을 줄이면 추상화가 개선되고 있다는 신호입니다.
주요 추상화를 바꿀 때는 조각으로 마이그레이션하세요. 심 뒤에서 병렬 경로(구/신)를 사용하고 점진적으로 더 많은 트래픽/케이스를 새 경로로 보내세요. 점진적 마이그레이션은 위험을 줄이고 다운타임을 피하며, 놀라움이 있을 때 롤백을 현실적으로 만듭니다.
실용적으로, 롤백 비용이 저렴한 도구가 팀에 도움이 됩니다. 예를 들어 Koder.ai 같은 플랫폼은 스냅샷과 롤백을 워크플로에 내장해 경계 리팩터링 같은 아키텍처 변경을 배포에 걸고 하더라도 안전하게 반복할 수 있게 합니다.
장기 코드베이스에서 리뷰 목표는 "가장 예쁜 구문을 찾는 것"이 아닙니다. 향후 비용을 줄이는 것: 놀라움이 적고, 변경이 쉬우며, 릴리스가 안전한 코드가 목표입니다. 실용적 리뷰는 경계, 이름, 결합, 테스트에 초점을 맞추고 포맷팅은 도구에 맡깁니다.
이 변경이 무엇에 의존하며, 무엇이 이제 이것에 의존하게 되는지를 물어보세요.
어울려야 할 코드와 얽혀 있는 코드를 살펴보세요.
네이밍을 추상화의 일부로 다루세요.
이 단순한 질문이 많은 결정을 안내합니다: 이 변경이 미래의 유연성을 증가시키는가 감소시키는가?
형식적 스타일은 포맷터/린터로 자동화하세요. 리뷰 시간은 설계, 경계, 네이밍, 결합 같은 논점에 쓰면 됩니다.
대규모·장기 코드베이스가 보통 실패하는 이유는 언어 기능이 없어서가 아닙니다. 사람들이 어디서 변경해야 하는지, 무엇을 깨뜨릴지, 어떻게 안전하게 변경할지 알지 못하기 때문에 실패합니다. 그건 추상화 문제입니다.
언어 논쟁 대신 명확한 경계와 의도를 우선하세요. 작은 공개 표면과 분명한 계약을 가진 잘 그려진 모듈 경계는 얽힌 의존성 그래프 안의 "깔끔한" 구문보다 낫습니다.
토론이 "탭 vs 스페이스"나 "언어 X vs 언어 Y"로 흐를 때는 다음 질문으로 되돌리세요:
도메인 개념과 아키텍처 용어를 위한 공유 용어집을 만드세요. 두 사람이 같은 아이디어에 대해 다른 단어를 쓰거나 같은 단어를 다르게 쓰면 이미 추상화가 새고 있는 것입니다.
모두가 인식하는 작은 패턴 집합을 유지하세요(예: "서비스 + 인터페이스", "레포지토리", "어댑터", "커맨드"). 적게 쓰이고 일관되게 쓰이는 소수 패턴이 수십 가지 기발한 설계보다 코드를 더 잘 탐색하게 합니다.
모듈 경계에 테스트를 두세요, 내부에만 두지 마세요. 경계 테스트는 내부를 공격적으로 리팩터링하더라도 호출자에게 보이는 동작을 안정적으로 지켜줍니다—이것이 추상화가 시간이 지나도 "정직"하게 유지되는 방법입니다.
새 시스템을 빠르게 만드는 중이라면—특히 AI 보조 워크플로로—경계를 먼저 고정하는 것을 첫 아티팩트로 고려하세요. 예를 들어 Koder.ai에서는 계획 단계에서 계약(React UI → Go 서비스 → PostgreSQL 데이터)을 스케치하고, 그 계약 뒤에서 구현을 생성·반복하며 필요할 때 소스 코드를 내보낼 수 있습니다.
변경이 빈번한 한 영역을 고르고:
이런 움직임을 규범으로 만드세요—작업하면서 리팩터링하고, 공개 표면을 작게 유지하며, 네이밍을 인터페이스의 일부로 취급하세요.
구문은 표면적 형태입니다: 키워드, 구두점, 들여쓰기 방식(중괄호 vs 들여쓰기), map() vs 루프 같은 코드 표현 방식입니다. 추상화는 개념적 구조입니다: 모듈, 경계, 계약, 네이밍처럼 시스템이 무엇을 하고 어디에서 변경이 일어나야 하는지를 알려주는 요소입니다.
대규모 코드베이스에서는 대부분의 작업이 코드를 안전하게 읽고 변경하는 것이므로, 추상화가 보통 더 큰 영향을 미칩니다.
규모가 커지면 결정의 비용이 여러 파일, 여러 팀, 여러 해에 걸쳐 곱해집니다. 작은 구문 선호도는 국소적으로 머물지만, 약한 경계는 시스템 전반에 파급효과를 만듭니다.
실무에서는 새 코드를 쓰는 것보다 동작을 찾아내고 이해하며 안전하게 수정하는 데 더 많은 시간을 쓰므로, 명확한 심(Seam)과 계약이 ‘쓰기 편한’ 구문보다 더 중요해집니다.
한 동작을 바꾸기 위해 관련 없는 부분을 이해할 필요 없이 바꿀 수 있다면 그 추상화는 좋은 편입니다. 강한 추상화는 보통 다음을 갖습니다:
심(Seam)은 구현을 호출자와 바꾸지 않고 바꿀 수 있게 해주는 안정적인 경계입니다. 보통 인터페이스, 어댑터, 파사드, 래퍼 같은 형태입니다.
안전하게 리팩터링하거나 마이그레이션하려면 먼저 안정적인 API(초기에 기존 코드를 위임하도록 해도 됨)를 만들어 두고, 그 뒤에 내부 로직을 점진적으로 옮기세요.
누군가가 올바르게 사용하려면 숨겨진 규칙을 알아야 하는 추상화가 바로 누수(leaky) 추상화입니다(호출 순서 제약, 라이프사이클 요건, 매직 기본값 등).
일반적인 수정법:
오버엔지니어링은 단순한 로직을 추적하기 어렵게 만드는 과도한 계층입니다. 래퍼의 래퍼가 생기면 한 줄짜리 결정을 찾는 데 사냥 본능이 필요해집니다.
실용적인 규칙: 여러 실제 호출자가 같은 필요를 보일 때만 새로운 계층을 추가하세요. 내부를 참조하지 않고도 계약을 설명할 수 있어야 합니다. 다재다능한 ‘모두 해결’ 인터페이스보다는 작고 의견이 명확한 인터페이스를 선호하세요.
네이밍은 사람들이 처음 마주하는 추상화입니다. 의도를 드러내는 이름은 누군가가 동작을 이해하기 위해 검사해야 할 코드 양을 줄입니다.
좋은 관행:
applyDiscountRules가 process보다 낫다)Repository 접미사, 불리언은 으로 시작, 이벤트는 과거형 등)경계는 입력/출력, 보장되는 동작, 정의된 에러 처리 같은 계약을 수반할 때 ‘실제’가 됩니다. 그 덕분에 팀들이 서로 독립적으로 작업할 수 있습니다.
UI가 DB 테이블을 알거나 도메인이 HTTP 개념에 의존한다면 상세가 레이어 간에 새어나오는 겁니다. 의존성은 도메인 개념을 향해 안쪽으로 향하도록 하고, 경계의 가장자리에 어댑터를 두세요.
계약 수준에서 동작을 테스트하세요: 입력이 주어지면 출력, 에러, 부작용을 검증합니다. 내부 단계에 의존하는 테스트는 피하세요.
부서지기 쉬운 테스트의 냄새:
경계 중심 테스트는 내부를 공격적으로 리팩터링해도 테스트를 대폭 고치지 않게 해 줍니다.
코드 리뷰는 미학보다는 향후 변경 비용을 줄이는 데 초점을 맞춰야 합니다. 유용한 질문들:
형식적 스타일은 포맷터/린터로 자동화하고, 리뷰 시간은 설계와 결합 문제에 쓰세요.
is/has/can