Readable Code: 읽기 좋은 코드 - CH3. 논리, 사고의 흐름 ~ CH4. 객체 지향 패러다임

joona95·2024년 10월 3일
1

📌 논리, 사고의 흐름에 따른 읽기 좋은 코드

AS IS

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;
}

TO BE

1. early return

public boolean validateOrder(Order order) {
	if (order.getItems().size() == 0) {
    	log.info("주문 항목이 없습니다.");
        return false;
    }
   	if (order.getTotalPrice() > 0) {
        if (!order.hasCustomerInfo()) {
            log.info("사용자 정보가 없습니다.");
            return false;
        }
        return true;
    } 
    if (!(order.getTotalPrice() > 0)) {
      	log.info("올바르지 않은 총 가격입니다.");
        return false;
    }
    return true;
}
  • else 를 사용하던 구문 대신 early return 을 하도록 변경
  • else if 사용할 필요 없이 if문으로 조건문 처리

2. 사고의 depth 줄이기

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;
}
  • 앞에서 early return 으로 변경하면서 '주문 가격이 0 초과인 경우와 아닌 경우'에 대한 분기문이 각각 if문으로 분리된 것을 볼 수 있음
  • 주문 가격이 0 미만인 경우를 먼저 확인하고 아래에서 사용자 정보 여부를 확인하면 동일한 결과지만 depth가 한차례 줄일 수 있음

3. 공백라인, 부정어

public boolean validateOrder(Order order) {

	if (order.getItems().size() == 0) {
    	log.info("주문 항목이 없습니다.");
        return false;
    }
    
    if (order.getTotalPrice() <= 0) {
      	log.info("올바르지 않은 총 가격입니다.");
        return false;
    }
    
    if (order.hasNotCustomerInfo()) {
        log.info("사용자 정보가 없습니다.");
        return false;
    }
    
    return true;
}
  • if문마다 각각의 조건을 확인하는 것이므로 공백을 추가해서 의미적인 분리를 해줌
  • 부정어는 또 한 번 생각을 하게 만들기 때문에 0보다 작거나 같은지 체크하는 것으로 조건을 바꿔줌
  • 사용자 정보가 있는지 확인하는 메서드를 사용자 정보가 없는지 확인하는 메서드로 변경해줌

4. 예외 처리

public void validateOrder(Order order) {

	if (order.getItems().size() == 0) {
    	throw new OrderException("주문 항목이 없습니다.");
    }
    
    if (order.getTotalPrice() <= 0) {
      	throw new OrderException("올바르지 않은 총 가격입니다.");
    }
    
    if (order.hasNotCustomerInfo()) {
        throw new OrderException("사용자 정보가 없습니다.");
    }
}
public class OrderException extends RuntimeException {

	public OrderException(String message) {
    	super(message);
    }
}
  • 올바른 주문인지 아닌지 체크가 필요한 이유는 올바르지 않은 주문인 경우 제대로 된 작동이 힘들어서일 것임
  • 그렇다면 올바른 주문인지 아닌지 확인 후 Boolean 값을 던져주는 것보다는 Exception을 통해 예외를 발생시키고 예외 메세지를 전달하는 편이 의도를 더 명확히 전달할 수 있음
  • IllegalArgumentException 보다 의도한 예외라는 걸 명확히 하기 위해 Custom Exception 클래스를 만들어줌

5. 객체 지향

public class Order {

	private List<Item> items;
    private Customer customer;
    
    private Order(List<Item> items, Customer customer) {
    	this.items = items;
        this.customer = customer;
        
        validateOrder();
    }
    
    public static Order of(List<Item> items, Customer customer) {
    	return new Order(items, customer);
    }
    
    public int getItemCount() {
    	return items.size();
    }
    
    public int getTotalPrice() {
    	return items.stream()
        	.mapToInt(Item::getPrice)
            .sum();
    }
    
    private void validateOrder() {

        if (hasNotItems()) {
            throw new OrderException("주문 항목이 없습니다.");
        }

        if (isIllegalTotalPrice()) {
            throw new OrderException("올바르지 않은 총 가격입니다.");
        }

        if (hasNotCustomerInfo()) {
            throw new OrderException("사용자 정보가 없습니다.");
        }
	}
    
    private boolean hasNotItems() {
    	return items == null || getItemCount() == 0;
    }
    
    private boolean isIllegalTotalPrice() {
    	return getTotalPrice() <= 0;
    }
    
    private boolean hasNotCustomerInfo() {
    	return customer == null;
    }
}
public class OrderException extends RuntimeException {

	public OrderException(String message) {
    	super(message);
    }
}
  • 객체 지향적으로 Order 클래스를 구현
  • 유효성 검사는 주문을 생성할 시에 일어난다고 생각해서 Order 클래스 내부에 지금까지 수정한 유효성 검사 메서드를 가져오고 생성자에 유효성 검사 추가
  • 유효석 검사 내부에서 확인하는 '주문 항목이 있는지 여부', '올바르지 않은 총 가격인지 여부', '사용자 정보가 없는지 여부'를 얻기 위해 boolean 값을 리턴하는 메서드로 분리해줌

📌 SOLID 원칙

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

단일 책임 원칙이란 말 그대로 하나의 클래스는 하나의 책임만을 가져야 한다는 의미이다.

하나의 클래스가 여러 책임을 가지게 되면 클래스의 기능이 불분명해지고 가독성이 떨어지게 된다.

한 클래스가 어떠한 책임을 가지게 할 것인가는 개발자의 판단에 달려 있고 이에 대한 판단을 명확히 하기 위해 노력해야 한다.

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

개방 폐쇄 원칙이란 확장에는 열려있지만 수정에는 닫혀있다는 의미이다.

요구사항에 따라 변경이 필요할 때 다른 클래스에까지 영향을 주지 않아야 한다.

추상화한 클래스를 의존함으로써 내부 수정이 있더라도 해당 클래스를 의존하는 다른 클래스에는 영향을 미치지 않을 수 있다.

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

리스코프 치환 원칙은 상위 타입의 인스턴스가 하위 타입의 인스턴스의 역할을 다할 수 있어야 한다는 의미이다.

어떠한 하위 타입이 상위 타입 대신 들어오더라도 정상적인 기능을 해야 한다.

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

인터페이스 분리 원칙은 인터페이스의 단일 책임 원칙이다.

인터페이스 또한 하나의 책임을 가지고 있어야 하고, 만약 그렇지 않다면 분리가 필요하다.

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

의존성 역전 원칙은 고수준 모듈이 저수준 모듈에 의존해서는 안된다는 의미이다.

고수준 모듈이 저수준 모듈을 의존하는 게 순방향적인 관계지만, 이렇게 되면 저수준 모듈에 변경이 발생하는 경우 고수준 모듈에도 영향이 간다.

이를 방지하기 위해서 둘 다 추상화에 의존하여야 한다.

0개의 댓글