3 분 소요

출처: 인프런 워밍업 클럽 스터디 2기 - 백엔드 클린 코드, 테스트 코드 (Java, Spring Boot)
Readable Code: 읽기 좋은 코드를 작성하는 사고법

워밍업 클럽 스터디 백엔드 클린코드, 테스트 코드 day4 미션 내용 입니다.

미션 1. 코드 리팩토링

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public boolean validateOrder(Order order) {  
    if (order.getItems().size() == 0) {  
        log.info("주문 항목이 없습니다.");  
        return false;  
    } else {  
        if (order.getTotalPrice() > 0) {  
            if (!order.hasCustomerInfo()) {  
                log.info("사용자 정보가 없습니다.");  
                return false;  
            } else {  
                return true;  
            }  
        } else if (!(order.getTotalPrice() > 0)) {  
            log.info("올바르지 않은 총 가격입니다.");  
            return false;  
        }  
    }  
    return true;  
}

해당 코드를 리팩토링 해야 한다.

먼저 이 메서드가 동작하는 과정을 보면

  1. orderItems가 있는지 체크를 한다.
    1. 있으면 다음으로 없으면 false를 리턴한다.
  2. ordertotalPrice가 0 이상인지 체크한다.
    1. 0 이상이라면 다음으로 넘어간다.
    2. 0 이하라면 또(?) 검사하고, false를 리턴한다.
  3. hasCustomerInfo로 사용자 정보를 체크한다.
    1. 있으면 true를 리턴하고, 없으면 false를 리턴한다.

다음과 같이 검증을 하는 로직이다.

이를 리팩토링 해보자.

먼저 나는 if-else 가 필요하지 않을 것 같아서 다 분리하겠다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public boolean validateOrder(Order order) {  
    if (order.getItems().size() == 0) {  
        log.info("주문 항목이 없습니다.");  
        return false;  
    }   

	if (!(order.getTotalPrice() > 0)) {
		log.info("올바르지 않은 총 가격입니다.");
		return false;
	}

	if (!order.hasCustomerInfo()) {  
		log.info("사용자 정보가 없습니다.");  
		return false;  
	}   
    
    return true;  
}

이제 return false보다는 오류 메시지를 넘겨서 확실하게 표현 해주자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public boolean validateOrder(Order order) {  
    if (order.getItems().size() == 0) {  
        throw new IllegalArgumentException("주문 항목이 없습니다.");  
    }  
  
    if (!(order.getTotalPrice() > 0)) {  
        throw new IllegalArgumentException("올바르지 않은 총 가격입니다.");  
    }  
  
    if (!order.hasCustomerInfo()) {  
        throw new IllegalArgumentException("사용자 정보가 없습니다.");  
    }  
  
    return true;  
}

마지막으로 if문 안에 조건을 하나의 로직 보다는 추상화를 하여, 다른 사람들이 읽어도 이해가 될 수 있게 바꾸자.

  • order.getItems().size() == 0 -> orderHasNoItems()
    • 오더가 아이템이 없는가?
  • !(order.getTotalPrice() > 0) -> isTheTotalPriceZero()
    • 전체 가격이 0인가? (의미가 좀 다른거 같은데.. 0 이하인가)
  • !order.hasCustomerInfo() -> orderHasNoCustomerInfo()
    • 오더가 사용자정보를 가지고 있는가?

(영어가 ㅎㅎ..)

그럼 전체 코드를 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class OrderService {  
  
    public boolean validateOrder(Order order) {  
  
        if (orderHasNoItems(order)) {  
            throw new IllegalArgumentException("주문 항목이 없습니다.");  
        }  
  
        if (isTheTotalPriceZero(order)) {  
            throw new IllegalArgumentException("올바르지 않은 총 가격입니다.");  
        }  
  
        if (orderHasNoCustomerInfo(order)) {  
            throw new IllegalArgumentException("사용자 정보가 없습니다.");  
        }  
  
        return true;  
    }  
  
    private static boolean orderHasNoCustomerInfo(Order order) {  
        return !order.hasCustomerInfo();  
    }  
  
    private static boolean isTheTotalPriceZero(Order order) {  
        return order.getTotalPrice() <= 0;  
    }  
  
    private static boolean orderHasNoItems(Order order) {  
        return order.getItems().isEmpty();  
    }  
      
}

사실 아래 private 메서드는 Order 객체 내에 선언하면 더 좋을것 같지만, 오늘은 여기다 만들었다.

미션 2. SOLID 정리

내가 생각하는 SOLID를 정리해 본다.

Single Responsibility Principle (SRP) - 단일 책임 원칙

  • 하나의 클래스는 하나의 책임만 가져야 한다.
  • 클래스를 작은 단위로 나누어 책임을 명확하게 해서 코드를 더 이해하기 쉽고, 변경에 유연하게 만들 수 있다.
  • 내가 생각하기에 하나에 집중하기도 어려운데 두가지 일을 하려하면 분명 문제가 발생한다..

Open/Closed Principle (OCP) - 개방-폐쇄 원칙

  • 소프트웨어 요소(클래스, 모듈 등)은 확장에는 열려있고, 변경에는 닫혀있어야 한다는 원칙
  • 기존 코드를 수정하지 않고, 새로운 기능을 추가할 수 있도록 설계해야 한다.
  • 기존걸 건들면 어떤 일이 벌어질 지 모른다. 달리는 마차에서 바퀴를 바꿔 끼워야 한다.

Liskov Substitution Principle (LSP) - 리스코프 치환 원칙

  • 자식 타입은 언제나 부모 타입으로 교체할 수 있어야 한다.
  • 자식 클래스를 사용중인 자리에 부모 클래스를 꼽아도 이상이 없어야 한다.
  • 즉 자식은 부모의 행동을 따를 수 있어야 한다.
  • 내가 생각하는 이 원칙은 약속이라고 생각한다.

Interface Segregation Principle (ISP) - 인터페이스 분리 원칙

  • 인터페이스는 클라이언트가 자신이 사용하지 않는 메서드에 의존하지 않도록 작게 분리되어야 한다.
  • 클라이언트가 그 안이 어떻게 돌아가는지 이해할 필요가 없다. 클라이언트한테 원하는 결과만 잘 전달하면 된다.

Dependency Inversion Principle (DIP) - 의존 역전 원칙

  • 고수준 모듈은 저수준 모듈에 의존하면 안되고, 둘 다 추상화에 의존해야 한다. 즉, 구체적인 클래스에 의존하는 것이 아니라 인터페이스나 추상 클래스에 의존해야 한다.
  • 개발을 하면서 구체적인 클래스를 다 의존해놓으면 만약에 다른 구현체로 바꿀려면 진짜 다바꿔야 한다.
  • 인터페이스로 의존해놨으면 그냥 생성자 주입 같은걸로 바꿔주기만 하면 된다.

댓글남기기