10장 - 이름 설계: 구조를 파악할 수 있는 이름

10.1 악마를 불러들이는 이름

이름 설계가 부적절하면 악마를 불러 일으킴

상품을 그대로 '상품 클래스' 라고 하면 관련된 여러 로직을 가지며 거대하고 복잡해짐
변경사항이 생기면 클래스와 관련된 것들을 모두 확인 해봐야하는 문제가 생김

강한 결합을 해소하여 결합이 느슨하며 응집도가 높은 구조로 만들려면 관심사 분리 (separation of cencerns)를 할 줄 알아야함

관심사 분리
관심사(유스케이스, 목적, 역할)에 따라서 분리함

상품을 주문 상품, 예약 상품, 재고 상품, 발송 상품 등으로 나누며 관심사에 맞는 이름을 붙일 수 있음
분할 후에는 분할한 클래스 각각에 관심사에 맞는 로직을 캡슐화 하면 됨

이렇게 되면 관련된 사양 변경이 발생했을 때 주문과 관련된 클래스만 확인하면 될 것임

포괄적이고 불분명한 이름을 가지면 (굉장히 구체적으로 보일 수 있지만) 목적 불명 객체가 되고 목적 불명 객체는 규모가 커지기 쉬움

그렇기 때문에 관심사 분리를 쉽게 하려면 비지니스 목적에 ㄸ따라 이름으 붙여보면 됨

10.2 이름 설계하기 - 목적 중심 이름 설계

프로그래밍에서 이름은 가독성을 높이는 역할만 하는 것이 아님
관심사 분리를 생각하고 비지니스 목적에 맞게 '이름을 붙이는 것'은 느슨한 결합과 응집도 높은 구조를 만드는데 중요한 역할을 함

이름 설계에서 중요한 포인트

  • 최대한 구체적이고, 의미가 좁고 특화된 이름을 선택

    • 특정한 목적을 달성하는데 특화된 의미 범위가 좁은 이름을 클래스에 붙임 (비지니스 목적에 특화가 되어야함)


  • 존재가 아니라 목적을 기반으로 하는 이름 생각하기

    • 단순하게 존재를 나타내는 이름은 의미가 여러 곳에서 사용되기 쉬우며 목적이 불분명해지기 쉬움

    • 구체적인 목적을 알 수 있게, 목적을 기반으로 이름을 짓는 것이 좋음


  • 어떤 비지니스 목적이 있는지 분석하기
    • 소프트웨어가 추구하는 목적과 내용을 분석하여 비지니스 목적에 특화된 이름을 만들기 (비지니스 목적을 모두 파악해야함)

  • 소리 내어 이야기 해보기
    • 이름도 중요하지만 어떤 목적의 달성, 어떤 형태의 사용, 서로의 연관성에 대한 배경과 의도도 함께 정리하여 이를 팀과 소통해서 일치시키는 것이 중요
    • 러버덕 디버깅 : 오리한테 차근차근 설명해가며 해결해가는 방법
    • 유비쿼터스 언어 : 팀 전체에서 의도를 공유하기 위한 언어

  • 이용 약관 읽어보기
    • 이용 약관에는 관련된 규칙이 굉장히 엄격히 적혀있어 이를 활용해 비지니스와 관련된 이름을 알 수 있음

  • 다른 이름으로 대체할 수 없는지 검토하기
    • 이름의 의미 범위가 생각보다 넓을 수 있고 여러 의미가 내포된 이름일 수 있음, 이름의 의미를 좁게 만들 수 없는지 이상한 점은 없는직 검토하는 것이 좋음
    • ex. 호텔 관련이라면 '고객' (X) -> 투숙객/결제자 (부모님 해드릴 수도 있으니)

  • 결합이 느슨하고 응집도가 높은 구조인지 검토하기
    • 목적에 특화된 이름을 선택하면 목적 이외의 로직을 배제하기 쉬워짐
    • 결과적으로 목적과 관련된 로직이 모이므로 응집도가 높아짐

10.3 이름 설계시 주의 사항

이름에 관심 갖기

목적 중심 이름 설계는 '이름에 주의를 기울이고, 이름과 로직을 대응 시킨다'라는 접근 방법을 전제로함
팀 개발에서는 이름이 중요함, 이름과 로직과 대응된다는 전제, 이름이 프로그램 구조를 크게 좌우한다는 사실을 팀원들과 이야기 해야함

사양 변경시 '의미 범위 변경' 경계하기

이름 설계는 중간중간 다시 검토 해봐야함
(개인 고객만 존재하다가 법인 고객도 추가 되어 로직이 섞이게 되면 클래스가 복잡해짐)

대화에는 등장하지만 코드에 등장하지 않는 이름 주의학

대화에는 등장하는 중요한 개념이 소스코드에서는 이름조차 붙어있지 않고 이상한 로직에 묻혀있는 경우가 있음

구현한 사람에게 직접 묻지 않으면 존재조차 알 수 없고 존재를 확인해도 이해하기 힘ㄷ름

대화에서 등장하는 이름을 신경써야하고 그 이름을 기반으로 메서드와 클래스를 설계 해야함

수식어를 붙여서 구별해야 하는 경우는 클래스로 만들어 보기

악세사리에도 추가 hp를 올려주고 방어구에도 같은 효과가 생겼다고 하면

다음과 같이 코드를 짤 수도 있음

maxHitPoint = member.maxHitPoint + armor.maxHitPointIncrements();

// 동작하는 코드
let maxHitPoint =
  member.maxHitPoint +
  accessory.maxHitPoinatIncrements() +
  armor.maxHitPointIncrements();

하지만 위에 코드는 정상적 동작을 하지 않음
왜냐하면 '캐릭터 원래의 HP'와 '장비 착용으로 높아진 HP'를 maxHitPoint만을 보곤 알 수 없기 때문

그렇기에 원래 체력과 장비(악세사리 + 방어구) 착용로 올라간 체력을 계산하는 클래스를 각각 만들어야 함

// '캐릭터의 원래 최대 히트포인트'를 나타내는 클래스
class OriginalMaxHitPoint {
	private static readonly MIN = 10;
	private static readonly MAX = 999;

	readonly value: number;

	constructor(value: number) {
		if(value < OriginalMaxHitPoint.MIN || OriginalMaxHitPoint.MAX < value) {
			throw new Error();
		}
		this.value = value;
	}
}

// '장비 착용'으로 높아진 최대 히트포인트'를 나타내는 클래스
class CorrectedMaxHitPoint {
	readonly value: number;
	
	constructor(originalMaxHitPoint: OriginalMaxHitPoint, accessory: Accessory, armor: Armor) {
		this.value = originalMaxHitPoint.value + accessory.maxHitPointIncrements() + armor.maxHitPointIncrements();
	}
}

단순히 이름만 바꾼 것이 아닌 이렇게 구분을 해놓아야 '캐릭터의 원래 hp', '장비 착용으로 높아진 hp'를 다양한 상황에서 구분해 사용할 수 있음

수식어를 붙이면서까지 차이를 나타내고 싶은 대상은 각각 클래스로 설계하는 것이 좋음

이렇게 의미가 다른 개념들을 서로 다른 클래스로 설계해서 구조화 하면 개념 사이의 관계를 이해하기 쉬움

10.4 의미를 알 수 없는 이름

이름만 보고 목적을 알 수 없는 코드들이 있음 (tmp 등)
잘못 이해하고 구현한 롲기은 버그를 유발할 수 밖에 없음

기술 중심 명명

이름을 프로그래밍과 관련된 용어, 컴퓨터와 관련된 용어에서 유래되는 경우가 많은데, 이를 기반으로 이름 짓는 방법을 기술 중심 명명이라고 함

기술 중심 명명을 사용해야하는 분야

임베디드 처럼 하드웨어와 가까운 레이어의 미들웨어에서는 메모리와 프로세서 등 하드웨어에 직접 접근하는 로직이 많이 사용됨 이때는 어쩔수 없이 기술을 중심으로 이름을 짓는다. 최대한 목적과 의도를 전달할 수 있게 지어야 함

로직 구조를 나타내는 이름

무엇을 의도하는지 메서드 이름만 보고는 알기 힘듦
의도와 목적을 이해하기 쉽게 이름을 붙이는 것이 중요함

class Magic {
	// Before
	isMemberHpMoreThanZeroAndIsMemberCanActAndIsMemerMpMoreThanMagicCostMp(member: Member) {
		if (0< member.hitPoint) {
			if (member.canAct()) {
				if (costMagicPoint <= member.magicPoint) {
					return true;
				}
			}
		}
		return false;
	}
	// After
	canEnchant(member: Member) {
		if (member.hitPoint <= 0) return false;
		if (!member.canAct()) return false;
		if (member.magicPoint < costMagicPoint) return false;
		return true;
	}
}

놀람 최소화 원칙

itemCount()라는 메세지가 있는데 상품 수 리턴뿐만이 아니라 기프트 포인트까지 추가하고 있는 경우

놀람 최소화 원칙
사용자가 예상하지 못한 놀라움을 최소화하도록 설계해야한다는 접근 방법

그렇기에 itemCount 메서드는 반드시 주문 상품 수만 리턴해야하고 기프트 포인트를 추가하는 메서드는 shouldAddGiftPoint라는 이름을 붙여야함

처음에는 로직과 의도가 일치하게끔 구현했다 해도, 사양을 변경하며 별 생각 없이 기존 메서드에 로직을 추가하는 경우가 있음

그러므로 로직을 변경할 땐 항상 놀람 최소화 원칙을 신경쓰고

로직과 이름 사이에 괴리가 있다면 이름을 수정하거나, 메서드와 클래스를 의도에 맞게 따로 만들어야함

10.5 구조에 악영향을 미치는 이름

데이터 클래스처럼 보이는 이름

ProductInfo라는 클래스가 있을 땐 데이터만 갖는다는 인상을 주는 이름이 될 수 있으므로 그냥 Product로 개선하여 캡슐화 해야함

DTO

예외적으로 데이터 클래스를 사용하는 경우가 있음

변경 책무와 참조 책무를 모듈로 분리하는 명령 쿼리 역할 분리 (CQRS)라 불리는 아키텍처 패턴

CQRS에서 참조 책무란 DB에서 값을 추출하는 처리로, 오직 화면 출력만 하면 됨

단순히 값만 추출하기 때문에 계산과 데이터 변경을 동반하지 않음
그렇기에 DB의 값을 저장하고 출력하는 쪽에 전송하기만 하는 클렛,를 설계 해야함

class ProductDto {
	readonly id: number;
	readonly name: string;
	readonly price: number;
	readonly productCode: string;

	constructor(name: string, price: number, productCode: string) {
		this.name = name;
		this.price = price;
		this.productCode = productCode;
	}
} 

데이터 전송 용도로 사용되는 디자인 패턴

'데이터 클래스를 절대 사용해선 안 된다'가 아니라 의도를 이해하고 상황에 맞게 사용해야 한다는 것

클래스를 거대하게 만드는 이름

Manager라는 클래스가 있으면 너무 많은 책무를 떠안아 단일 책임 원칙을 위배하고 있음

'관리'라는 단어가 가진 의미가 넓고 애매하기 때문

이 뿐만 아니라 Processor와 Controller 같은 이름도 주의 해야함

심지어 Controller는 웹 프레임워크의 MVC 구조에 등장하는 패턴, MVC에서 Controller는 전달받은 요청 매개변수를 다른 클래스에 전달하는 책무만 가져야함

상황에 따라 의미가 달라질 수 있는 이름

Account는 금융에서는 '계좌'를 컴퓨터 보안에서는 '로그인 권한'을 의미

상황, 문맥(컨텍스트)이 바뀌면 의미가 반대 될 수 있음

  • 배송 컨텍스트: 자동차가 화물로 배송되는 컨텍스트 → 자동차 - 발송지, 배송지, 배송 경로와 관련
  • 판매 컨텍스트: 딜러에 의해서 고객에게 판매되는 컨텍스트 → 자동차 - 판매 가격, 판매 옵션과 관련

이렇듯 컨텍스트가 서로 다른데 한 곳에 구현하려고 하면 여러 로직을 갖게 되고 클래스가 거대해짐

어떤 컨텍스트가 둘러싸고 있는지 분석하고 컨텍스트 별로 경계를 지어 이를 기반으로 클래스를 설계하면 됨

일련 번호 명명

클래스가 메서드에 이름을 붙이는 방법을 일련 번호 명명이라 부름
목적과 의도를 알기 힘들지만 구조를 개선하기도 힘듦

기술 중심 명명과 비슷하지만 개선하기가 훨씬 힘든 악질적인 방법
기술 중심 이름은 목적 중심 이름 설계로 변경하면 됨

하지만 일련 번호는 이것이 힘들고 질서가 무너질 수 있음

그러다가 트랜잭션 스트립트 패턴이 되기 쉬움

트랜잭션 스크립트 패턴
트랜잭션 스크립트 패턴(Transaction Script Pattern)은 소프트웨어 개발에서 사용되는 디자인 패턴 중 하나입니다. 이 패턴은 간단한 CRUD(Create, Read, Update, Delete) 작업이나 비즈니스 로직을 처리하기 위해 사용됩니다. 주로 데이터베이스와의 상호작용이 많은 서비스나 애플리케이션에서 사용됩니다.

10.6 이름을 봤을 때, 위치가 부자연스러운 클래스

'동사 + 목적어' 형태의 메서드 이름 주의하기

class Enemy {
	isAppeared: boolean;
	magicPoint: number;
	dropItem: Item;

	escape(): void {
		this.isAppeared = false;
	}
	consumeMagicPoint(costMagicPoint: number) {
		this.magicPoint -= costMagicPoint;
		if (this.magicPoint < 0) {
			this.magicPoint = 0;
		}
	}
	
	addItemToParty(items: Item[]) {
		if(items.size() < 99) {
			items.add(dropItem);
			return true;
		}
		return false;
	}
}

이렇게 적에 대한 Enemy클래스가 있지만 마지막 addItemToParty와 같이 주인공의 소지품을 다루는 것이 들어갈 수 있음

서둘러서 구현하려할 때 기존 클래스만을 가지고 어떻게든 끝내고자 무리하게 구현했을 때 이렇게 되기 쉬움

관심사가 다르거나, 관계없는 책무를 가진 메서드는 AddItemToPart처럼 동사 + 목적어 형태가 되는 경향이 있음

가능하면 메서드의 이름은 동사 하나로 구성되도록 하기

'동사 + 목적어' 혀앹의 메서드 ㅡ> 목적어의 개념을 잘 나타내는 클래스를 따로 만들고, 그 클래스에 '동사 ㅎ하나' 형태의 메서드를 추가

위 코드로 예시를 들면 인벤토리에 아이템을 추가하는 add메서드를 추가해서 정의할 수 있음

부적절한 위치에 있는 boolean 메ㅐ서드

boolean 자료형을 리턴하는 메서드도 적절하지 않은 클래스에 정의되어 있는 경우가 많음

// Bad
class Common {
	// 멤버가 혼란 상태라면 true를 리턴
	static isMemberInConfusion(member: Member) {
		return member.status.contains(StateType.confused);
	}
}

// Good
class Member {
	readonly private states: States;

	isInConfusion() {
		return this.states.contains(StateType.confused);
	}
}

메서드가 정의되어 있는 클래스 위치가 적절한지 쉽게 확이낳는 방법
ㅡ> boolean 자료형의 메서드는 is~, has~, can~ 형태의 이름이 붙기 때문에

클래스 is 상태로 바꿨을 때 자연스러운지 확인하면 됨

Common is member in confusion

Member is in confusion

누가봐도 아래가 옳음

10.7 이름 축약

let trFee = brFee + LRF * dod;

는 사실 

기본요금 + 연체료 * 연체일

기본적으로 이름은 축약하지 말기

귀찮더라도 축약하지 말고 사용하는 것이 좋음

옆자리 동료 뿐만 아니라 ㅈ미래의 자신에게도

이름을 축약할 수 있는 경우

최대한 축약하지 않고 의도를 명확하게 전달하는 것이 중요

반복문의 변수처럼 i, j처럼 범위가 적고 헷갈릴 여지가 적다면 축약해도 좋음

profile
기록, 꺼내 쓸 수 있는 즐거움

0개의 댓글