온라인 쇼핑몰에는 예약, 주문, 출고, 발송
관련 유스케이스가 존재한다.
만약 4가지 유스케이스와 관련된 클래스 이름을 상품
이라 지어버리면 해당 클래스에 여러 클래스와 관련있는 로직이 담기고 거대해진다. 클래스가 거대해지면 사양 변경이 힘들고 버그가 발생할 확률이 커진다.
여러 클래스와 관련이 있는 상품
클래스는 강한 결합 구조이다.
결합이 느슨하고 응집도가 높은 구조로 만들기 위해 관심사(유스케이스, 목적, 역할) 분리
가 필요하다.
관심사에 따라 분리하기 위해 가장 첫번째로 해야할 작업이 관심사에 맞는 이름 붙이기
이다
예약, 주문, 출고, 발송
유스케이스에 따라 예약 상품, 주문 상품, 출고 상품, 발송 상품
으로 분리해 볼 수 있다.상품
과 같이 클래스명이 포괄적이라면 온갖 로직이 구현되게 된다.
이 처럼 이름이 포괄적이여서 목적이 불분명한 클래스를 목적 불명 객체
라 부를 수 있다.
목적이 불명한 객체가 생성되지 않도록 관심사에 맞는 이름 설계를 해야 한다.
클래스 이름을 잘 설계하려면 => 관심사 분리가 필요하다
관심사 분리를 쉽게 하려면 => 비지니스 목적에 따라 이름을 지어보면 된다
관심사 분리를 생각하고 비지니스 목적에 맞게 이름을 설계의 장점
클래스 이름을 비지니스 목적에 특화된 이름으로 짓을 경우 얻을 수 있는 효과
존재 기반 이름
목적 기반 이름
소프트웨어가 추구하는 목적을 분석해야 비지니스 목적에 특화된 이름을 만들 수 있다
프로그램에 문제가 발생했을 때, 누군가에게 설명하다 보면 스스로 원인을 깨닫는다는 고무 오리 디버깅
방식처럼 이야기를 나누다보면 관심사를 더 수집하고 비지니스에 대해 자세히 알 수 있게 된다.
이야기하고 분석하는 활동을 유비쿼터스 언어
라 설명한다. (도메인 주도 설계 책에서)
팀에서 유비쿼터스 언어를 사용할 때 계속해서 대화하고 이름을 다듬어나가는 것이 중요하다.
이용 약관에는 서비스와 관련된 규칙들이 엄격한 표현으로 작성되어있다.
이를 참고하여 클래스 이름을 설계할 수 있다.
비지니스 규칙과 클래스를 일치하게 만들면 정확하고 빠르게 변경할 수 있다.
의미를 더 좁게 만들 수 없는지, 이상한 점이 없는지 검토하는 것이 좋다.
ex) 호텔 숙박 예약 시스템 유저 클래스 이름
사용자
: 의미가 너무 넓다고객
: 숙박하는 사람과 숙박 요금을 결제하는 사람을 구분지을 수 있을 만큼 세분화가 필요하다
투숙객
,결제자
: '사용자'보다 의미가 좁은 이름으로 이름을 변경해볼 수 있다.
목적에 특화된 이름을 선택하면 목적 이외의 로직을 배제하기 쉬워진다.
관련있는 클래스가 많다면 좋지 않은 징조이다
이름과 로직을 대응시킨다, 이름이 프로그램 구조를 크게 좌우한다는 사실을 팀원들과 이야기해야 한다.
반복되는 사양 변경에 의해 개발 맥락에서 말이 의미하는 바가 변화한다
그에 따라 이름 설계도 다시 검토해봐야 한다.
이름 없는 로직 (ex. 문제가 있는 회원)
로직에 묻혀 있는 경우
가 꽤 많다.이름 없는 로직을 지양하고 메소드와 클래스로 설계하도록 하자.
// 악세서리에 최대 히트포인트 증가 효과가 있으므로
// 악세서리의 최대 히트포인트 증가 효과 적용
int maxHitPoint = member.maxHitPoint + accessory.maxHitPointIncrements();
// 방어구에 최대 히트포인트 증가 효과가 추가되어
// 다른 위치에 히트 포인트 증가 효과 적용 로직 추가 (버그 발생)
maxHitPoint = member.maxHitPoint + armor.maxHitPointIncrements();
버그 발생
버그 발생 원인
아이템(악세서리, 방어구, 회복 아이템)에 의한 증가 효과가 전혀 반영되지 않은 '캐릭터의 원래 최대 히트 포인트'
이다. 하지만 팀원이 member.maxHitPoint를 아이템에 의한 증가효과가 누적된 히트포인트로 잘못 이해하여 버그가 발생했다.문제점
캐릭터의 원래 최대 히트포인트
인지 장비 착용으로 높아진 최대 히트포인트
인지 이름만 보고 알 수 없다.originalMaxHitPoint
correctedMaxHitPoint
class OriginalMaxHitPoint {
final int value;
OriginalMaxHitPoint(final int value) {
validate(value);
this.value = value;
}
}
class CorrectedMaxHitPoint {
final int value;
CorrectedMaxHitPoint(final OriginalMaxHitPoint originalMaxHitPoint,
final Accessory accessory,
final Armor armor) {
this.value = originalMaxHitPoint.value + accessory.maxHitPointIncrements() + armor.maxHitPointIncrements();
}
}
의미가 다른 개념들을 서로 다른 값 클래스로 설계하면 개념 사이의 관계를 이해하기 쉬워진다.
개념에 수식어를 붙여서 차이를 나타낸다면 각각을 클래스로 설계할 수 없는지 검토해보자.
int maxHitPoint
-> int originalMaxHitPoint
, int correctedMaxHitPoint
-> OriginalMaxHitPoint.class
, CorrectedMaxHitPoint.class