워밍업 클럽 2기 백엔드 클린코드&테스트 코드 1주차 발자국

Readable Code: 읽기 좋은 코드를 작성하는 사고법 섹션 1~5까지의 강의 수강과 미션을 진행하고 남긴 1주차 발자국입니다.

 

강의

  • Section 1: 소개

    • 환경 설정 및 용어 정리

  • Section 2: 추상화

    • 추상과 구체

      • '하나의 세계 안에서는 추상화 레벨이 동등해야 한다'는 메시지가 인상 깊었습니다. 이는 미래의 나와 동료 개발자들을 위한 배려라는 점이 특히 의미있었습니다. 개발 당시에는 해당 도메인과 비즈니스 로직이 머릿속에 선명하기 때문에 구체적 표현이 크게 문제되지 않습니다. 하지만 시간이 지나 다시 코드를 읽을 때 이해가 어려워 시간을 낭비한 경험이 많았습니다. 이러한 문제는 추상화를 통해 해결할 수 있으며, 이것이 바로 미래의 나를 배려하는 방법이라고 깨달았습니다.

    • 네이밍

      • 메서드명 작성 시 메서드 이름과 매개변수를 전치사로 연결하는 방법은 새로운 인사이트였습니다. 특히 오버로딩을 적용할 때 효과적일 것이라는 생각이 들었습니다.

  • Section 3: 논리와 사고의 흐름

    • 부정어 처리

      • 부정어 처리를 위해 별도의 메서드를 만드는 방식은 깊이 고민해보지 않았던 부분입니다. 강의에서 제시된 코드를 보니 직관성이 크게 향상되어, 필요한 접근이라고 느꼈습니다.

    • Optional

      • 그동안 Optional을 깊이 이해하지 못한 채 관성적으로 사용해왔습니다. 강의에서 Optional을 매개변수로 사용하지 말아야 하는 이유와 orElse()와 orElseGet()의 소스코드를 분석하면서, Optional의 잘못된 사용을 반성하게 되었습니다.

  • Section 4: 객체 지향 패러다임

    • SOLID

      • 이 부분은 미션과 밀접하게 연관되어 있었습니다. 개발 공부 과정에서 SOLID 원칙을 자주 접했지만, 실제 코드에 적용하기는 어려웠습니다. 따라서 SOLID에 대한 개인적 견해를 질문 형태로 정리하여, 작성한 코드가 이 원칙들을 잘 반영하고 있는지 확인하는 체크리스트로 활용하면 좋겠다고 생각했습니다.

  • Section 5: 객체 지향 적용

    • 객체 지향 구현을 위한 다양한 도구

      • VO, 일급 컬렉션, Enum, 다형성 등 객체 지향을 실현하기 위한 유용한 도구들이 소개되었습니다. VO와 일급 컬렉션은 간단한 미션에서만 적용해보았고, 실제 프로젝트에서는 활용하지 못했습니다. 이는 필요성을 제대로 인식하지 못했기 때문입니다. 강의를 통해 이러한 도구들의 용도와 가치를 깊이 이해하게 되었습니다. 시간 관계 상 프로젝트를 개선하지는 못했지만, 객체 지향을 효과적으로 적용할 수 있는 방향성을 발견할 수 있었습니다.

  • 1주차 강의 회고

    • Keep

      • 수강과 미션을 일정에 맞춰서 진행함

      • 강의를 들으면서 이전에 진행한 프로젝트에 적용할 부분을 찾아본 것

    • Problem

      • 시간 분배

        • 강의 수강에 예상보다 많은 시간이 소요됨

           

        • 자소서, 새로 시작하는 프로젝트에 쓸 수 있는 시간이 부족했음

    • Try

      • 시간 분배 다시 생각

      • 일주일 계획 짜기

 

DAY4 미션

  1. 아래 코드와 설명을 보고, [섹션 3. 논리, 사고의 흐름]에서 이야기하는 내용을 중심으로 읽기 좋은 코드로 리팩토링해 봅시다.

     

     

    • 요구사항

       

    사용자가 생성한 '주문'이 유효한지를 검증하는 메서드.

Order는 주문 객체이고, 필요하다면 Order에 추가적인 메서드를 만들어도 된다. (Order 내부의 구현을 구체적으로 할 필요는 없다.)

필요하다면 메서드를 추출할 수 있다.

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;
}
  • 리펙토링 후 코드

public class OrderService {

	private final LogPrinter logPrinter;

	public OrderService() {
		logPrinter = new LogPrinter(this.getClass());
	}

	public boolean validateOrder(Order order) {
		if (order.hasNoItems()) {
			logPrinter.printInfo("주문 항목이 없습니다.");
			return false;
		}
		if (order.hasInvalidTotalPrice()) {
			logPrinter.printInfo("올바르지 않은 총 가격입니다.");
			return false;
		}
		if (order.hasNoCustomerInfo()) {
			logPrinter.printInfo("사용자 정보가 없습니다.");
			return false;
		}

		return true;
	}
}

public class Order {

	private final List<Item> items;
	private final Customer customer;

	public Order(List<Item> items, Customer customer) {
		this.items = items;
		this.customer = customer;
	}

	public boolean hasNoItems() {
		return items == null || items.isEmpty();
	}

	public boolean hasInvalidTotalPrice() {
		return calculateTotalPrice() <= 0;
	}

	public long calculateTotalPrice() {
		return items.stream()
                        .mapToLong(Item::getPrice)
			.sum();
	}

	public boolean hasNoCustomerInfo() {
		return customer == null || customer.isNotExist();
	}

}
  • 구현 과정

    • if문 depth 줄이기

    • if문 조건절 메서드로 바꾸기(추상)

    • 로그 출력을 메서드로 빼서 일괄적으로 처리하게 하기

  • 구현 중 고민 사항

    • Order 객체가 items의 총 가격을 계산하는 기능의 책임을 가지는 것이 맞는가?

      • 계산기 객체 추가 구현 고려

    • validateOrder(Order order) 기능 자체가 Order 객체가 가져야 할 책임이지 않는가?

      • validateOrder(Order order) 메서드를Order 객체로 옮기는 것 고려하기

  • 구현 후 아쉬웠던 점

    • 매직 넘버, 스트링 처리하지 않은 것

    • 로그 출력 기능에서 warning등 다른 메서드 활용을 고려하지 않은 것

    • Order 객체에서 items나 customer 필드 관련 null 검증을 생성자에서 하지 않은 것

 

2. SOLID에 대하여 자기만의 언어로 정리해 봅시다.

  • SRP

    • 한 클래스는 하나의 책임만 가져야 한다

    • 클래스를 변경하는 이유는 오직 하나여야 한다

    • ‘한 클래스, 기능이 가지는 책임의 범위가 어디까지 인가?’ 생각하기

  • OCP

    • 확장에는 열려 있고, 변경에는 닫혀 있어야 한다

    • 기존 코드의 변경을 최소화 하면서 새로운 기능을 추가할 수 있어야 한다

    • 객체 지향에 집착하지 않기. 요구 사항에 따라 객체 지향을 고수하는 것보다 절차 지향이 확장에 더 열려있을 수 있다.

  • LSP

    • 상위 타입의 객체를 하위 타입의 객체로 치환해도 프로그램은 정상적으로 동작해야 한다

    • 하위 클래스는 상위 클래스의 규약을 지켜야 한다

    • '어떤 동작을 추상화하면 모든 하위 클래스가 올바르게 구현할 수 있을까?' 생각하기

  • ISP

    • 클라이언트는 자신이 사용하지 않는 메서드에 의존하지 않아야 한다

    • 큰 인터페이스를 작은 단위로 분리해야 한다

    • ‘필요로 하는 메서드만 제공하는가?’ 생각하기

  • DIP

    • 고수준 모듈은 저수준 모듈에 의존하지 않아야 한다. 둘 모두 추상화에 의존해야 한다

    • 추상화는 세부 사항에 의존하지 않야야 한다. 세부 사항이 추상화에 의존해야 한다

  • SOLID

    • 원칙을 위한 원칙이 되지 않도록 하자

    • 팀의 상황과 프로젝트의 요구 사항에 맞게 사용하자

  1. 미션 회고

  • 리펙토링 미션을 시간에 쫒겨 급하게 한 것이 아쉽다. 회고록을 작성하는 중에도 계속 아쉬운 점이 보임.

  • 일정 때문에 다른 사람의 미션을 읽어보지 못한 것이 아쉽다.

  • 같은 이유로 코드 리뷰를 받지 못한 것이 아쉽다.

  • SOLID에 대한 내 생각을 질문 형태로 남기는 것은 좋은 생각이었던 것 같다. 이를 발전시켜 더 구체적인 질문으로 남기기

 

 

댓글을 작성해보세요.

채널톡 아이콘