1. 악마를 불러들이는 이름
class 상품 {
public void 상품 예약(){
//예약과 관련된 로직
...
}
public void 상품 주문(){
//주문과 관련된 로직
...
}
public void 상품 출고(){
//출고과 관련된 로직
...
}
public void 상품 발송(){
//발송과 관련된 로직
...
}
}
[문제점]
거대해진 상품 클래스에 사양 변경이 발생하면 변경된 부분의 영향을 받아 동작에 버그가 생기지는 않는지, 상품 클래스와 관련된 클래스를 모두 확인해야 합니다.
[개선점]
1) 관심사 분리하기
관심사 분리는 '관심사(유스케이스, 목적, 역할)에 따라서 분리한다'라는 소프트웨어 공학의 개념입니다.
따라서 상품 클래스는 관심사에 따라서 각각 클래스로 분할해야 합니다.
2) 비즈니스 목적에 따라 이름을 붙이기
class 예약 상품 {
...
}
class 주문 상품 {
...
}
class 재고 상품 {
...
}
class 발송 상품 {
...
}
2. 이름 설계하기 - 목적 중심 이름 설계
목적 중심 이름 설계는 목적에 맞게 이름을 설계하는 것입니다. 소프트웨어로 달성하고 싶은 목적과 의도를 이름만으로도 알 수 있게 하는 것입니다.
1) 최대한 구체적이고, 의미 범위가 좁고, 특화된 이름 선택하기
2) 존재가 아니라 목적을 기반으로 하는 이름 생각하기
존재 기반 | 목적 기반 |
주소 | 발송지, 배송지, 업무지 |
금액 | 청구 금액, 소비세액, 연체 보증료, 캠페인 할인 금액 |
사용자 | 계정, 개인 프로필, 직무 |
사용자 이름 | 계정 이름, 닉네임(별칭), 본명, 법인명 |
상품 | 입고 상품, 예약 상품, 주문 상품, 발송 상품 |
3) 어떤 관심사가 있는지 분석하기
4) 소리 내어 이야기해 보기
5) 이용 약관 읽어 보기
- '구매자', '판매자', '매매 계약' 등 비즈니스 측면에서 명확한 이름들이 여러가지 나옵니다. 이를 참고하면 사용자를 나타내던 단순한 '사용자' 클래스를 '구매자' 클래스와 '판매자' 클래스 등으로 구분할 수 있을 것입니다.
6) 다른 이름으로 대체할 수 없는지 검토하기
7) 결합이 느슨하고 응집도가 높은 구조인지 검토하기
3. 이름 설계 시 주의 사항
1) 이름에 관심 갖기
- 목적 중심 이름 설계는 '이름에 주의를 기울이고, 이름과 로직을 대응시킨다'라는 접근 방법을 전제로 합니다.
- 팀 개발에서는 이름이 중요합니다. 이름과 로직이 대응된다는 전제, 이름이 프로그램 구조를 크게 좌우한다는 사실을 팀원들과 이야기해야 합니다.
2) 사양 변경 시 '의미 범위 변경' 경계하기
- 개발 중에 계속해서 반복되는 사양 변경에 의해, 개발 맥락에서 말이 의미하는 바가 점점 변화하는 경우가 있습니다. 따라서 이름 설계는 중간중간 다시 검토해 봐야 합니다.
3) 수식어를 붙여서 구별해야 하는 경우는 클래스로 만들어 보기
예시
Member 클래스의 maxHitPoint 필드의 의미는 캐릭터의 원래 최대 히트포인트
Armor 클래스의 maxHitPointIncrements 메서드는 방어구 장착시 증가되는 히트포인트
Accessory 클래스의 maxHitPointIncrements 메서드는 액세서리 장착시 증가되는 히트포인트
개선 전 코드
int maxHitPoint = member.maxHitPoint + accessory.maxHitPointIncrements()
+ armor.maxHitPointIncrements();
개선 후 코드 (클래스로 변경)
Member 클래스의 캐릭터의 원래 최대 히트포인트를 OriginalMaxHitPoint 클래스로 변경
class OriginalMaxHitPoint{
private static final int MIN = 10;
private static final int MAX = 999;
final int value;
OriginalMaxHitPoint(final int value){
if(value < MIN || MAX < value){
throw new IllegalArgumentException();
}
this.value = value;
}
}
Accessory와 Armor 장착 후 높아진 최대 히트 포인트를 CorrectedMaxHitPoint 클래스로 변경
class CorrectedMaxHitPoint{
final int value;
CorrectedMaxHitPoint(final OriginalMaxHitPoint originalMaxHitPoint,
final Accessory accessory,
final Armor armor){
value = originalMaxHitPoint.value + accessory.maxHitPointIncrements() + armor.maxHitPointIncrements();
}
}
단순하게 이름만 바꾼 것 같지만 그렇지 않습니다. 이름을 이렇게 바꾸는 것만으로 '캐릭터의 원래 최대 히트포인트'와 '장비 착용으로 높아진 최대 히트포인트'를 다양한 상황에서 구분해서 사용할 수 있습니다.
4. 의미를 알 수 없는 이름
1) 이름만 보고 목적을 알 수 없는 이름
예 - tmp1, tmp2
2) 기술 중심 명명
프로그래밍과 관련된 용어, 컴퓨터와 관련된 용어에서 유래해서 기술을 기반을 이름 짓는 방법을 기술 중심 명명이라고 부릅니다. 기술 중심 명명은 지양하고 최대한 목적과 의도를 전달 할 수 있는 이름으로 지어야합니다.
예 - int, Memory, flag, MemorystateManager, changeIntValue01
3) 로직 구조를 나타내는 이름
개선 전 메서드 이름
isMemberHpMoreThanZeroAndIsMemberCanActAndIsMeberMpMoreThenMagicCostMp(Member member)
- 히트포인트가 0보다 큰가(생존해 있나)?
- 행동 가능한 상태(canAct)인가?
- 매직포인트가 남아있는가?
위 메서드 이름은 로직 구조를 그대로 드러내고 있습니다.
개선 후 메서드 이름
canEnchat(final Member member)
의도와 목적을 이해하기 쉽게 이름을 붙입시다.
4) 놀람 최소화 원칙
int count = order.itemCount();
위 itemCount 메서드는 주문 상품 수를 리턴하는 것 처럼 보이지만, 실제 메서드 코드를 보면 기프트 포인트 까지 추가하고 있습니다.
class Order {
private final OrderId id;
private final Items items;
private GiftPoint giftPoint;
int itemCount(){
int count = items.count();
//주문 상품 수가 10 이상일 때, 기프트 포인트를 100만큼 추가
if(10 <= count){
giftPoint = giftPoint.add(new GiftPoint(100));
}
return count;
}
}
개선 후 코드
class Order {
private final OrderId id;
private final Items items;
private GiftPoint giftPoint;
int itemCount(){
return items.count();
}
boolean shouldAddGiftPoint(){
return 10 <= itemCount();
}
void tryAddGiftPoint(){
if(shouldAddGiftPoint()){
giftPoint = giftPoint.add(new GiftPoint(100));
}
}
}
처음에는 로직과 의도가 일치하게끔 구현했다고 해도, 사양을 변경하면서 별생각 없이 기존 메서드에 로직을 추가하는 경우가 있습니다.
로직을 변경할 때는 로직과 이름 사이에 괴리가 있다면 이름을 수정하거나, 메서드와 클래스를 의도에 맞게 따로 만드세요.
5. 구조에 악영향을 미치는 이름
1) 데이터 클래스처럼 보이는 이름
ProductInfo -> Product
2) 클래스를 거대하게 만드는 이름
//멤버를 관리하는 클래스
class MemberManager{
//멤버의 히트포인트 추출하기
int getHitPoint(int memberId){...}
//멤버의 매직포인트 추출하기
int getMagicPoint(int memberId){...}
//멤버 보행 애니메이션 시작하기
void startWalkAnimation(int memberId){...}
//멤버의 능력치를 CSV 형식으로 내보내기
void exportParamsToCsv(){...}
}
단일 책임 원칙의 관점에서 이 클래스의 메서드가 가진 책무는 위 메서드 히트포인트 관련 책무, 애니메이션 출력 책무, csv 출력 책무가 있습니다. 하지만 csv는 관심사가 파일 출력이므로 다른 책무라고 볼 수 있습니다.
즉, MemberManager는 너무 많은 책무를 떠안아서 단일 책임 원칙을 위반하고 있습니다.
Manager 이라는 클래스 대신 Hitpoint 클래스, WalkAnimation 클래스 등으로 설계합니다. 개념을 하나하나 분석해서, 한 가지만 책임을 지는 클래스가 되도록 설계합니다.
주의해야하는 이름
Manager, Processor, Controller
3) 상황에 따라 의미가 달라질 수 있는 이름
컨텍스트의 차이를 생각하고 클래스를 설계합시다.
컨텍스트의 차이를 생각하지 않고 구현한 Car 클래스
class Car{
id
발송지
배송 경로
판매 가격
판매 옵션
....
}
컨텍스트마다 따로 설계한 Car 클래스
배송 패키지
class Car {
id
발송지
배송지
배송 경로
...
}
판매 패키지
class Car{
id
판매 가격
판매 옵션
...
}
4) 일련번호 명명
예 - method1, method2 ...
6. 이름을 봤을 때, 위치가 부자연스러운 클래스
'동사+목적어' 형태의 메서드 이름 대신 '동사' 하나로 구성되게 하기
목적어 개념의 클래스를 따로 만들고, 그 클래스에 '동사 하나' 형태의 메서드를 추가합니다.
예시
- addItemToParty (파티의 인벤토리에 아이템을 추가하는 메서드)
- '파티의 인벤토리'라는 개념을 별도의 클래스를 만들고 add() 메서드를 정의한다.
7. 이름 축약
기본적으로 이름은 축약하지 말자. 조금 귀찮더라도 이름을 축약하지 말고 모두 쓰자.
'일상 > 레벨업 독서' 카테고리의 다른 글
[내 코드가 그렇게 이상한가요?] 12장 메서드(함수):좋은 클래스에는 좋은 메서드가 있다 (0) | 2024.11.15 |
---|---|
[내 코드가 그렇게 이상한가요?] 11장 주석:유지 보수와 변경의 정확성을 높이는 주석 작성 방법 (0) | 2024.11.14 |
[내 코드가 그렇게 이상한가요?] 9장 설계의 건전성는 여러 악마 (2) | 2024.10.28 |
[내 코드가 그렇게 이상한가요?] 8장 강한 결합: 복잡하게 얽혀서 풀 수 없는 구조 (1) | 2024.10.27 |
[내 코드가 그렇게 이상한가요?] 7장-컬렉션:중첩을 제거하는 구조화 테크닉 (0) | 2024.08.28 |