https://link.coupang.com/a/37DdD
↑ 정말 재밌게 봤고 좋은 책
응집도란 모듈 내부에 있는 데이터와 로직 사이의 관계가 얼마나 강한지 나타내는 지표이다.
5.1 static 메서드 오용
5.1.1 static 메서드는 인스턴스 변수를 사용할 수 없음
static 메서드는 당연하게도 인스턴스 변수를 사용할 수 없다.
그렇기에, static 메서드로 만든 시점에 바로 데이터와 그 데이터를 조작하는 로직의 위치가 나눠지게 된다. (응집도가 낮아진다)
class OrderManager{
static int add(int moneyAmount1,int moneyAmount2){
return moneyAmount1 + moneyAmount2;
}
}
moneyData1.amount = OrderManager.add(moneyData1.amount, moneyDate2.amount);
5.1.2 인스턴스 변수를 사용하는 구조로 변경하기
class MoneyData{
final int amount;
MoneyData( final int amount){
this.amount = amount;
}
MoneyData add(final MoneyData other){
return new MoneyData(this.amount + other.amount);
}
}
5.1.3 인스턴스 메서드인 척하는 static 메서드 주의하기
결국 어떤 클래스 안에 로직이 해당 클래스의 인스턴스를 사용하고 있지 않으면 static 메서드인 것이나 다름이 없다는 의미이다.
이런 메서드들은 메서드 앞에 static을 붙여도 IDE에서 오류가 발생하지 않는다. 왜냐하면 인스턴스 변수를 사용하고 있지 않았기 때문이다!!
5.1.4 왜 static 메서드를 사용할까?
객체지향 언어를 사용할 때 절차 지향 언어의 접근 방법을 사용하려고 하기 때문이다. (절차 지향 언어에서는 데이터와 로직이 따로 존재하도록 설계한다)
5.1.5 어떤 상황에서 static 메서드를 사용해야 좋을까?
응집도의 영향을 받지 않는 경우, static 메서드를 사용해도 괜찮다.
ex : 로그 출력, 포맷 변환, 팩토리 메서드 등
5.2 초기화 로직 분산
표준 회원으로 가입했을 때, 3000 포인트 제공하는 코드
GiftPoint standardMembershipPoint = new GiftPoint(3000);
프리미엄 회원으로 가입했을 때, 10000 포인트 제공하는 코드
GiftPoint premiumMembershipPoint = new GiftPoint(10000);
생성자를 public으로 만들면, 의도하지 않은 용도로 사용될 수 있다.
결과적으로 인스턴스 생성을 여러 코드에서 한다면, 예를들어 표준 회원 포인트를 5000원으로 바꾸고 싶을 때, 소스 코드 전체를 찾아서 변경해야 한다.
5.2.1 private 생성자 + 팩토리 메서드를 사용해 목적에 따라 초기화하기
위 같은 초기화 로직의 분산을 막으려면 생성자를 private로 만들고, 대신 목적에 따라 팩토리 메서드를 만들면 된다.
private static final int STANDARD_MEMBERSHIP_POINT = 3000;
private static final int PREMIUM_MEMBERSHIP_POINT = 10000;
static GiftPoint forStandardMembership(){
return new GiftPoint(STANDARD_MEMBERSHIP_POINT );
}
static GiftPoint forPremiumMembership(){
return new GiftPoint(PREMIUM_MEMBERSHIP_POINT );
}
위 처럼 만들면 신규 가입 포인트와 관련된 로직이 GiftPoint 클래스에 응집된다.
GiftPoint standardMembershipPoint = GiftPoint.forStandardMembership();
GiftPoint premiumMembershipPoint = GiftPoint.forPremiumMembership();
5.2.2 생성 로직이 너무 많아지면 팩토리 클래스를 고려해보자
5.3 범용 처리 클래스는 가능하면 사용하지 말기(Common/Util)
5.4 결과를 리턴하는데 매개변수 사용하지 않기
매개변수를 잘못 다루면, 응집도가 낮아지는 문제가 발생한다. 출력 매개변수도 같은 문제를 일으킨다.
class ActorManager{
void shift(Location location, int shiftX, int shiftY){
location.x += shiftX;
location.y += shiftY;
}
}
shift는 캐릭터의 위치를 이동하는 메서드이고, 데이터 조작 대상은 Location, 조작 로직은 ActorManager 이다. (데이터와 조작 로직이 각자 다른 클래스에 있어 응집도가 낮다.)
응집도가 낮은 구조는 중복을 쉽게 만들 수 있다. (다른 사람이 이 데이터 조작 로직이 다른곳에 있는 것을 모르고 새로 만들 수 있다.)
출력 매개변수는 응집도 문제 이외에 다른 문제도 일으킨다.
아래 문제는 매개변수가 변경되었다는 것을 외부에서 알 수 없는 문제
discountManager.set(money);
class DiscountManager{
void set(MoneyData money){
money -= 2000;
if(money.amount < 0){
money.amount = 0;
}
}
}
매개변수는 입력으로 전달하는 것이 일반적이다.
이렇게 출력으로 사용해 버리면, 매개변수가 입력인지 출력인지 내부의 로직을 확인해야 한다.
메서드의 내용을 하나하나 확인하게 만드는 구조는 로직을 읽고 이해하는데 시간이 오래 걸려, 가독성이 좋지 않다.
출력 매개변수로 설계하지 말고, 객체 지향 설계의 기본으로 돌아가 '데이터'와 '데이터를 조작하는 논리'를 같은 클래스에 배치하는게 좋다.
class Location{
final int x;
final int y;
Location(final int x, final int y){
this.x = x; this.y = y;
}
Location shift(final int shiftX, final int shiftY){
final int nextX = x + shiftX;
final int nextY = y + shiftY;
return new Location(nextX, nextY);
}
}
5.5 매개변수가 너무 많은 경우
문제 가능성 1 : 너무 많은 매개변수를 받는 메서드는 실수로 잘못된 값을 대입할 가능성이 높다.
문제 가능성 2 : 책의 예시에서는 메서드 이름에서 나타내는 기능 외에 추가적인 기능도 수행되고 있는 문제가 있다
-> 문제가 생기는 이유 : 메서드에 매개변수를 전달한다는 것은 이 매개변수들을 사용해 어떤 기능을 수행하고 싶다는 것이다. 즉, 매개변수가 많다는 것은 많은 기능을 처리하고 싶다는 의미가 되고, 로직이 복잡해지거나 중복 코드가 생길 가능성이 높아진다.
5.5.1 기본 자료형에 대한 집착
프로그래밍 언어가 기본적으로 제공하는 자료형을 기본 자료형이라고 한다.
메서드의 매개변수와 리턴 값에 기본 자료형을 남용하는 현상을 기본 자료형 집착 이라고 한다.
int 기본자료형으로 넘어온 값 유효성 검사가 필요할 때, 아래처럼 regularPrice의 값 유효성을 체크하는 부분이 여러군대 중복되어 생길 수 있다!
이렇게 어떤 기본 자료형 값이 유효성 검사가 필요한 경우 값 객체를 만들고, 유효성 검사 로직을 값 객체에 넣으면 코드의 중복 생성을 막을 수 있는 장점이 있다.
class Common{
int discountedPrice(int regularPrice, float discountRate){
if(regularPrice < 0){
throw new IllegalArgumentException();
}
...
}
}
class Util{
int isFairPrice(int regularPrice){
if(regularPrice < 0){
throw new IllegalArgumentException();
}
...
}
}
'정가'를 의미하는 자료형 클래스 정의 결과,
기본 자료형을 가지는 값 객체를 만들고, 그 값의 유효성 검사 로직을 클래스 안에 넣으면, 유효성 검사 로직의 중복 생성 가능성을 막을 수 있고, 인스턴스 변수와 변수를 제어하는 로직을 같이 둠으로써 응집도를 높일 수 있다.
class RegularPrice{
final int amount;
RegularPrice(final int amount){
if(amount < 0){
throw new IllegalArgumentException();
}
this.amount = amount;
}
}
5.5.2 의미 있는 단위는 모두 클래스로 만들기
이 책의 예시에서 조언은, 매개변수가 많으면 데이터 하나하나를 매개변수로 다루지 말고, 그 데이터를 인스턴스 변수로 갖는 클래스를 만들고 활용하는 설계로 변경해 보라고 한다.
5.6 메서드 체인
.(점) 연속으로 찍으면서 메서드 호출해서 값 가져오는 부분은 응집도를 낮출 수 있어 좋지 않은 방법이다.
5.6.1 묻지 말고 명령하기
인스턴스 내부의 값을 알아내서 무엇을 하려고 하지 말고, 인스턴스의 메서드로 명령을 해서 해당 인스턴스가 알아서 판단하고 제어하도록 설계하라는 것
5.6, 5.6.1 내용을 포함하는 법칙이 디미터의 법칙이다.
'책 내용 정리 > [책] 내 코드가 그렇게 이상한가요?' 카테고리의 다른 글
12장 메서드: 좋은 클래스에는 좋은 메서드가 있다. (0) | 2023.07.28 |
---|---|
7장 컬렉션: 중첩을 제거하는 구조화 테크닉 (0) | 2023.07.27 |
6장 조건 분기: 미궁처럼 복잡한 분기 처리를 무너뜨리는 방법 (0) | 2023.07.23 |
4장 불변 활용하기: 안정적으로 만들기 (0) | 2023.07.17 |
3장 클래스 설계: 모든 것과 연결되는 설계 기반 (0) | 2023.07.16 |
댓글