코드스피츠 오브젝트 1회차
객체지향 공부하다 뉴런데브에서 올린 영상을 보고 일부 내용을 내 나름대로 정리해본 글
유연하고 견고하고 변화에 격리되어있는 코드를 만들자
켄트백이 제시하는 생각하는 코드 의 툴
1. Value (가치) – 왜 이렇게 코드를 짜야 하는가
Kent Beck은 코드 스타일을 취향 문제가 아니라 가치 문제로 본다. 코드가 어떤 가치를 높이느냐가 코드 품질의 기준이다.
1) Communication (의사소통)
- 코드는 사람 사이의 소통 도구다.
- 변수명, 메서드명, 클래스 구조, 예외 처리 방식 등은 모두 “이 코드가 무슨 일을 하는지”를 팀에 전달하기 위한 문법이다.
- 코드가 읽기 어려우면, 의사소통 비용이 커지고, 버그와 오해가 증가한다.
즉,
코드 품질 = 컴퓨터가 이해하는 속도보다 사람이 이해하는 속도가 더 중요하다.
2) Simplicity (단순성)
- 단순한 코드는 이해하기 쉽고, 변경하기 쉽고, 테스트하기 쉽다.
- 불필요한 추상화, 과한 패턴, 의미 없는 계층을 제거하는 것이 단순성이다.
- “일단 돌아가게 만들고 나중에 정리”가 아니라, 처음부터 가능한 한 단순하게 설계하는 것을 지향한다.
3) Flexibility (유연성)
일반적으로 “유연하다 = 복잡하다”고 생각하는데, 실제로는 유연한 코드는 더 단순한 경우가 많다.
-
여기저기 재사용 가능한 코드는 보통 작고, 역할이 명확하고, 부수 효과가 적다.(마치 pure function 처럼)
Math.sqrt()는 엄청 많은 곳에서 쓰인다.- 특정 프로젝트의
MyApplicationService.doSomethingComplex()는 재사용 범위가 훨씬 좁다.
-
유연함 = 많은 맥락에서 사용 가능함 → 많은 맥락에서 사용 가능하려면 → 구체적 의존이 적고, 입출력이 명확하고, 부작용이 제한적이어야 한다 → 이건 구조적으로 “단순한 모듈”을 필요로 한다.
그래서 유연성은 단순함으로 부터 만들어짐ㄴ다.
2. Principle (원칙) – 가치를 실천하기 위한 집단 규칙
원칙은 “다 같이 지키기로 약속한 행동 양식”이다. 왜 다 같이 지켜야 하는가?
- 그걸 지킬 때 Value(가치)가 발생**하기 때문이다.
- 대표적인 가치: 예외 상황을 빨리 인지할 수 있음, 변화 영향 범위를 쉽게 추적 가능, 코드 공유 이해도 상승.
당신이 적은 네 가지 원칙을 하나씩 정리해보겠다.
1) Local Consequences (로컬 결과)
-
코드 변경의 영향 범위는 최대한 국소적이어야 한다.
-
어떤 메서드를 수정했을 때,
- 어디까지 영향이 퍼질지
- 어떤 모듈이 깨질 수 있는지
- 어떤 사이드 이펙트가 있는지 를 쉽게 추적할 수 있어야 한다.
이 원칙이 잘 지켜진 코드는:
- 디버깅/리팩토링 시, “여기 고치면 저기까지 터질까?” 하는 불안이 줄어든다. Regression bug 걱정을 덜 수 있다.
2) Minimize Repetition (중복 최소화)
“반복은 제거되는 게 아니라 발견되는 것이다. 개발자의 수준에 따라 발견 능력이 다르다.”
-
단순히 복붙 줄이는 정도 아니라, 코드에서 반복되는 구조·개념·규칙을 발견하고 개념화(추상화)하는 능력이 중요하다.
-
예:
- 비슷한 if–else 블록이 여러 군데 있다 → 조건과 정책을 전략 객체나 규칙 객체로 추출
- 비슷한 유효성 검사 코드가 여러 군데 있다 → Validator / Specification 패턴으로 추출
여기서 핵심은:
- “무조건 DRY(Don’t Repeat Yourself)를 밀어붙여라”가 아니다.
- “반복이 뭘 의미하는지 이해할 수 있을 때” 적절히 추상화하는 것이다.
3) Symmetry (대칭성)
-
동일한 역할/개념에는 유사한 구조와 이름을 부여한다.
-
예:
createUser,updateUser,deleteUservscreateUser,modify,removeUserInfo→ 대칭성 없음, 읽는 사람 입장에서 패턴이 깨진다.
-
Controller – Service – Repository 계층의 메서드 구성이 서로 일정한 패턴을 가지는 것도 대칭성이다.
대칭성이 있으면:
- 코드를 읽지 않고도 예상할 수 있다.
- 예상이 가능하므로 인지 부하가 줄어든다.
4) Convention (관례)
-
팀/조직 레벨의 합의된 규칙이다.
-
예:
- 패키지 구조 컨벤션
- 네이밍 컨벤션
- Service/Repository/Controller 책임 범위 컨벤션
-
Convention의 목적은:
- 매번 “이걸 어떻게 짤까?”를 다시 고민하지 않게 하는 것
- 코드가 사람에 따라 완전히 달라지는 걸 막는 것
- 새로 온 사람이 적응하기 쉽게 하는 것
결론적으로,
Principle은 Value를 위해 다 같이 지키는 규칙이다. 원칙을 공유하면, 예외 상황과 이상 징후를 즉각적으로 인식할 수 있다.
3. Pattern – 가치와 원칙 위에서 반복을 일반화한 것
“가치와 원칙을 베이스로 해서, 반복되는 것들을 패턴이라고 한다.”
조금 더 구조적으로 표현하면:
-
Value
- 커뮤니케이션, 단순성, 유연성 등 → 우리가 “좋다”고 합의한 방향성
-
Principle
- Local consequences, minimize repetition, symmetry, convention … → 가치를 실천하기 위해 다 같이 지키는 행동 규칙
-
Pattern
-
그 가치와 원칙이 반복적으로 사용된 구체적인 설계/구조의 이름
-
예:
- Command, Strategy, Template Method, Value Object, Aggregates, Layered Architecture 등
-
패턴은 혼자 뜬금없이 튀어나온 게 아니다. 어떤 가치(Value)를 지키기 위해 어떤 원칙(Principle)을 반복적으로 적용하다가 공유 가능한 형식으로 이름 붙인 것이 패턴이다.
그래서 패턴을 쓸 때는 항상 질문이 이런 식이어야 한다.
- 이 패턴은 어떤 가치를 강화하는가?
- 이 패턴이 지키려는 원칙이 무엇인가?
- 현재 상황에서 그 가치/원칙이 중요한가?
4. X-oriented – 관점 중심 설계
- Object-oriented (객체 지향)
- Responsibility-oriented (책임 지향)
- Message-oriented (메시지 지향)
- Test-oriented (테스트/피드백 지향) 등등... 공통점은 다음이다.
“무엇을 기준으로 코드를 바라볼 것인가?”에 대한 선택이다.
예를 들어,
- 절차 지향: “순서대로 뭐가 실행되는지”에 집중
- 객체 지향: “누가 책임지는지”에 집중
- 메시지 지향: “어떤 메시지가 오가는지”에 집중
- 테스트 지향: “어떻게 검증 가능한 구조인지”에 집중
어떤 관점을 택할지는 상대적이지만, 그 관점을 택하는 기준은 합리적이다. (변경 용이성, 이해 가능성, 테스트 용이성 등)
이걸 머릿속에 두고 코드 리뷰나 설계 토론을 할 때 “지금 이건 어떤 가치 때문에 이렇게 짜야 한다고 말하는 건가?” 라는 질문을 항상 해야한다.
상대주의 와 합리주의
상대주의와 합리주의가 충돌하는 것처럼 보이지만 실제로는 서로를 보완하며 작동한다.
1. 상대주의 — 관점이 바뀌면 구조도 달라진다
프로그래밍에서 객체지향 구조를 바라볼 때, “무엇이 부모이고 무엇이 자식인가”는 고정되지 않는다. 도메인 관점, 프레임워크 관점, 런타임 관점에 따라 역할이 달라진다.
예를 들어
- OS 입장 → JVM은 단순한 User Process
- Java 개발자 입장 → JVM은 일종의 “추상 OS”
- 브라우저를 플랫폼으로 쓴다면 → Web API와 JS 런타임은 일종의 OS
어떤 관점을 채택하느냐에 따라 동일한 대상이 시스템의 중심이 되기도 하고 부속 요소가 되기도 한다. 이것이 상대주의의 핵심이다. 진리나 구조가 본질적으로 고정된 것이 아니라 관점을 규정하는 프레임에 따라 달라진다.
프로그래밍에서도 “어떤 계층을 기준으로 모델링하느냐”가 시스템을 재구성한다.
2. 합리주의 — 다르게 볼 수 있지만 기준이 필요하다
상대적 관점이 가능하다고 해서 아무 기준 없이 구조를 구성할 수 있는 것은 아니다. 유지보수, 일관성, 예측 가능성, 성능, 책임 분리 등 객관적이고 합리적인 근거를 통해 합의하며 기준을 정해야한다.
예를 들면
- JVM을 “OS처럼” 바라볼지 여부는 성능·메모리 모델·추상화 수준·API 안정성 등을 기준으로 평가해야 한다.
- 웹 브라우저를 “OS처럼” 모델링하는 것도 sandbox 구조, event loop, 파일/네트워크 접근 등 구조적 조건들이 기준이 된다.
- 클래스 계층도 역시 재사용성, 책임 단일성(SRP), 변경 용이성 같은 합리적 원리가 기준이 된다.
즉 관점은 상대적이지만, 좋은 관점을 선택하는 기준은 합리적이다.
프로그래밍에서도 “더 잘 작동하는 기준을 충족하는 구조”가 선택된다.
3. 상대주의와 합리주의의 공존
두 관점은 서로 상반되는 것이 아니라 다음과 같이 결합된다.
-
상대주의는 관점을 열어준다. 다양한 설계 방식, 다양한 추상화, 다양한 계층 모델링이 가능하다.
-
합리주의는 관점 중 선택을 가능하게 한다. 실제로 의미 있는 설계를 골라내는 기준을 제공한다.
이 조합이 있어야
- 관점의 다양성을 인정하면서도
- 아무 말이나 다 옳다고 말하지 않는 실질적 판단이 성립한다.
순수한 상대주의는 기준이 없어서 공허하고, 순수한 합리주의는 맥락을 무시해서 경직되기 때문이다.
추상화는 상대적이지만, 좋은 추상화를 선택하는 기준은 합리적이다.
Abstraction
이러한 흐름에서 바라볼떄 Abstraction은
복잡한 현실을(여러가지 관점을) 일정한 규칙과 기준에 따라 (합리적인 기준에 따라) 단순화 하여 표현한것
무엇을 버리고 무엇을 남길지, 어떤 관계로 묶어서 볼지를 결정하는 “표현 방식의 구조”라고 할 수 있다.
이때 추상화는 보통 다음 세 가지 방향/기법을 가진다.
- generalization(일반화)
- association(연관화)
- aggregation(집단화)
Abstraction의 세 가지 방향
1) generalization (일반화)
- 다양한 사례의 공통점을 뽑아서 하나의 규칙, 개념, 형태로 표현하는 방향.
- 키워드:
modeling,function,algorithm - 예:
- 2, 8, 16의 공통점 → “2의 배수”
→ 여러 개별 숫자가 “하나의 성질”로 묶여서 설명된다. - 다양한 정렬 전략이 “정렬 알고리즘”이라는 일반화된 개념으로 표현된다.
- 여러 종류의 결제 방식이 “결제”라는 하나의 일반적인 행위로 다뤄진다.
- 2, 8, 16의 공통점 → “2의 배수”
여기서 중요한 건,
현상은 여러 가지인데, 설명은 하나의 구조/규칙으로 가능해지는 상태라는 점이다.
2) association (연관화)
- 서로 다른 대상들 사이의 연결, 관계, 의존이 드러나는 방향.
- 키워드:
reference,dependence - 대표적인 형태는 “위임”이다.
- 한 역할을 다른 객체에 맡겨서, 한 객체 안에 섞여 있던 역할들을 분리한다.
- 예:
- 한 사람을 “전체”로 보면 복잡하지만,
- 아버지로서의 역할
- 학생으로서의 역할
- 사원으로서의 역할
로 나누어 보면 각각은 단순하다.
- 코드에서는 이 역할 분리가
- 다른 타입/모듈에 위임되거나
- 참조/의존 관계로 표현된다.
- 한 사람을 “전체”로 보면 복잡하지만,
3) aggregation (집단화)
- 여러 대상을 하나의 집단(group), 범주(category) 로 묶어 보는 방향.
- 키워드:
group,category - 예:
- 여러 사람을 “Employees”라는 한 집단으로 본다.
- 여러 주문을 “오늘 주문들”이라는 범주로 본다.
- 각각의 요소가 따로따로 보이는 게 아니라,
- “같은 종류의 것들의 모음”으로 인식되는 상태가 aggregation 방향의 추상화다.
Data Abstraction
Data Abstraction은 대상을 데이터 구조로 표현할 때의 추상화다.
여기서는 세 가지 하위 개념으로 나눠 볼 수 있다.
modelinggroupingcategorization
이 셋은 큰 틀로 보면 generalization 방향에 속해 있다.
(association은 거의 개입하지 않고, 데이터 구조를 어떻게 일반화해서 표현하는지에 초점을 둔다.)
1) modeling
특정 루프(관점/용도)에 따라서 기억해야만 할 것들을 추린 것.
내 목적에 따라서 기억해야만 할 것들.
- 현실의 대상은 훨씬 많은 정보를 갖고 있지만,
- 어떤 시스템, 어떤 관점에서 볼 때 필요한 정보만 선택해서 구조화한다.
- 예:
- 같은 “사람”이라도
- 인증 관점: id, password hash, 잠김 여부
- 과금 관점: 결제 수단, 청구지 정보
- 통계 관점: 나이, 지역, 가입 경로
처럼 관점에 따라 “기억해야 할 필드”가 달라진다.
- 같은 “사람”이라도
modeling은,
- “어떤 속성/정보까지를 이 데이터 모델의 책임으로 볼 것인가”를 결정하는 단계이고,
- 다양한 현실 상태를 하나의 데이터 구조로 일반화한다는 점에서 generalization의 구체적인 형태다.
2) grouping
그냥 모아두는 것.
마냥 아무렇게나 모으는 게 아니라, 수학의 유한집합 개념에 가깝다.
함수라는 건 규칙성 있는 집합을 표현하는 일반화된 방법.
- 같은 구조와 의미를 가진 값들을 하나의 집합으로 본다.
- 예:
- “모든 사용자”라는 집합
- “오늘 로그인한 사용자들”이라는 집합
- 타입(type)이나 테이블(relation)도
- “어떤 조건/구조를 만족하는 값들의 집합”으로 볼 수 있다.
함수에 대한 너의 표현:
- “함수는 규칙성 있는 집합을 표현하는 일반화된 방법”
→ 입력 집합과 출력 집합 사이의 관계를 하나의 규칙으로 표현한다는 의미에서
generalization + grouping이 동시에 드러나는 개념이라고 볼 수 있다.
3) categorization
카테고리에 따라서 속성은 변한다.
아빠이자 오빠이자 사장이자 개발자인 한 사람이,
어떤 카테고리에서 어떤 역할로 취급되느냐에 따라 의미가 달라진다.
- 하나의 실체는 다중 역할을 가진다.
- 아버지, 오빠, 사장, 개발자 …
- 그런데 “회사”라는 카테고리 안에서
- 아버지/오빠 역할로 행동하면 안 된다.
- 그건 다른 맥락의 역할이고, 회사에서는 “사장/상사/동료” 같은 역할만 유효하다.
- 데이터 관점에서 categorization은,
- 하나의 실체를 여러 카테고리/역할로 나누어 표현하는 방식이다.
- 어떤 카테고리에서는 어떤 속성과 규칙만 살아 있고,
다른 속성은 “이 맥락에서는 무시되거나 금지된다”.
요약하면,
- Data Abstraction은 generalization이라는 큰 방향 안에서
- modeling: 무엇을 기억할지 선택하고
- grouping: 같은 종류의 것들을 집합으로 보고
- categorization: 동일 실체를 카테고리/역할에 따라 나누어 표현하는
세 레이어로 나뉘어 나타난다.
Procedural Abstraction
Procedural Abstraction은 행위/절차를 어떻게 표현할 것인가에 대한 추상화다.
여기서는 두 가지 하위 개념이 핵심이다.
generalizationcapsulization
association/aggregation보다는,
“행동을 어떻게 하나의 패턴으로 묶어서, 어떻게 바깥에 보여줄지”에 초점이 맞춰져 있다.
1) generalization
문제점: 우리는 머리가 나빠서 잘 못한다.
복잡한 상황을 일반화할 수 있을까?
본 적 없는 공통점을 찾아낼 수 있을까? → 훈련하면 된다.
- 여러 구체적인 절차를 하나의 일반적인 절차로 묶어 표현하는 방향이다.
- 예:
- 여러 결제 수단(카드, 계좌이체, 포인트, 쿠폰)을
→pay()라는 추상적인 행위로 표현. - 여러 단계의 검증/전처리/로깅/후처리를
→process()같은 하나의 절차로 취급.
- 여러 결제 수단(카드, 계좌이체, 포인트, 쿠폰)을
여기서 generalization은,
- “각 경우마다 따로 코드를 써서 다루는 상태”에서
- “하나의 패턴/알고리즘으로 다뤄지는 상태”로 넘어가는 걸 말한다.
2) capsulization
여러 정의가 있지만, 여기서는
data hiding이 아니라
절차의 복잡성을 감추는 것에 가까운 의미로 쓴다.
예: ATM 출금
- 내부 절차:
- 사용자 인증
- 계좌/잔고 조회
- 은행 시스템과의 통신
- 보안 채널 설정
- 트랜잭션 처리
- 타은행 상호 인증 …
- 하지만 외부(사용자/클라이언트) 입장에서는
출금기능 하나로 보인다.
capsulization은,
- 절차를 쪼개고 조합하는 디테일을 내부에 숨기고,
- 외부에는 간단한 인터페이스(“출금”)만 드러나는 상태라고 보면 된다.
복잡성을 그대로 드러내면?
- “이걸 누가 어떻게 쓰냐” 수준의 인터페이스가 된다.
- Java IO처럼, 내부 구조를 너무 많이 알아야만 제대로 쓸 수 있는 API는
capsulization이 부족한 예로 볼 수 있다.
OOP Abstraction
OOP Abstraction은,
- Data Abstraction에서 이야기한 것들
- Procedural Abstraction에서 이야기한 것들
- 그리고 객체들 사이의 관계 구조
를 한 객체/타입 체계 안에서 동시에 표현하는 방식이다.
여기서는 다음과 같은 개념들이 함께 등장한다.
generalizationrealizationdependencyassociationdirected associationaggregationcomposition
1) generalization / realization
- generalization
- 인터페이스나
abstract class로 공통된 역할/계약을 표현한다. - 예:
PaymentGateway,Repository<T>,Specification<T>
- 인터페이스나
- realization
- 그 역할을 실제로 수행하는 구체(concrete) 클래스.
- 예:
KakaoPaymentGateway,InMemoryUserRepository
여기서는 “다양한 구현”을
“하나의 역할 개념”으로 일반화한다는 점에서,
generalization 방향의 추상화가 분명하게 드러난다.
2) dependency
- 함수 인자로 받거나, 일시적으로 참조하는 의존.
- 예:
void placeOrder(Inventory inventory)
→OrderService는 인자로Inventory를 받는다.
- 생명주기가 짧고, 호출 시점에만 연결되는 관계다.
association보다는 느슨하고, “잠깐 빌려 쓰는” 느낌에 가깝다.
3) association
- 필드로 선언된 연관.
- 예:
class Order { private User user; }
- 항상 구조적으로 연결되어 있는 관계다.
- dependency와의 차이:
- dependency: 단발/일시적
- association: 구조적으로 항상 이어져
소감
솔직히 이걸 코드 짜면서 실시간으로 떠올릴 수 있을지 모르겠다. 지금 당장은 짜놓고 평가 할떄 즉 '왜 이 코드가 불편한지'를 설명할 때 쓰는 도구로 활용하는 게 현실적일 것 같다. 그런 평가를 여러번 반복하다 보면 훈련이 되고 설계할떄 자연스럽게 떠올릴수 있지않을까 싶다. 직접 코드 타이핑 할 일이 없어지는 시대에 이런 개념이 더 필요한 건지 덜 필요한 건지 아직 모르겠다"