Skip to main content

김주엽

1. 📌 핵심 개념 정리

✅ 요약하기

  1. 작게 만들어라!
    함수를 만드는 첫번째 규칙은 작게 만들기다.
    일반적으로 함수는 20줄 이하로 작성하고 if/else, while, for문 등에 들어가는 블록은 1줄이어야 한다.
    또한 들여쓰기 수준은 1단, 2단을 넘어서면 안 된다.

    • 개선 전
    public class OrderProcessor {
      public void processOrder(Order order) {
          if (order != null) {
              if (order.isPaid()) {
                  if (order.isShipped()) {
                      System.out.println("이미 배송된 주문입니다.");
                  } else {
                      order.ship();
                      System.out.println("주문이 배송되었습니다.");
                  }
              } else {
                  System.out.println("결제되지 않은 주문입니다.");
              }
          } else {
              System.out.println("잘못된 주문입니다.");
          }
      }
    }
    

    위의 코드는 함수의 길이가 길고 들여쓰기 수준이 깊다.

    • 개선 후
    public class OrderProcessor {
      public void processOrder(Order order) {
          if (order == null) {
              handleInvalidOrder();
              return;
          }
    
          if (!order.isPaid()) {
              handleUnpaidOrder();
              return;
          }
    
          processShipping(order);
      }
    
      private void handleInvalidOrder() {
          System.out.println("잘못된 주문입니다.");
      }
    
      private void handleUnpaidOrder() {
          System.out.println("결제되지 않은 주문입니다.");
      }
    
      private void processShipping(Order order) {
          if (order.isShipped()) {
              System.out.println("이미 배송된 주문입니다.");
              return;
          }
          order.ship();
          System.out.println("주문이 배송되었습니다.");
      }
    }
    
    

    코드 개선 후 들여쓰기 제한을 준수하여 if문 중첩을 최소화하고 가독성을 향상시켰다.


  1. 한 가지만 해라!
    "함수는 한 가지를 해야 한다. 그 한 가지를 잘 해야 한다. 그 한 가지만을 해야 한다."

    • 지정된 함수 이름 아래에서 추상화 수준이 하나인 단계만 수행해야 한다.
      • 추상화란 필요한 정보만 남기고 불필요한 세부사항을 숨기는 개념이다.
      • "무엇을 할 것인가?"(What)만 신경 쓰고 "어떻게 처리할 것인가?"(How)는 감춘다는 원칙
    • 단순히 다른 표현이 아닌 의미 있는 이름으로 다른 함수를 추출할 수 있다면
      그 함수는 여러 작업을 하는 함수이기에 분리가 필요하다.

  1. 함수당 추상화 수준은 하나로!
    함수가 한 가지 일만 하려면 함수 내 모든 문장의 추상화 수준이 동일해야 한다.
    또한 코드는 위에서 아래로 책처럼 읽혀야 좋다.

    • 개선 전
    public class OrderProcessor {
      private double getPrice(String item) {
          return switch (item) {
              case "Apple" -> 1.0;
              case "Banana" -> 0.5;
              case "Orange" -> 0.75;
              default -> 0.0;
          };
      }
    
      public void processOrder() {
          System.out.println("Processing order...");
    
          List<String> items = fetchItems();
          System.out.println("Items to pack: " + items);
    
          for (String item : items) {
              System.out.println("Packing: " + item);
          }
    
          System.out.println("Calculating total price...");
          double total = 0;
          for (String item : items) {
              total += getPrice(item);
          }
          System.out.println("Total price: $" + total);
    
          System.out.println("Generating invoice...");
          String invoice = "Invoice: " + items + " | Total: $" + total;
          System.out.println(invoice);
    
          System.out.println("Order processed.");
      }
    
      private List<String> fetchItems() {
          return List.of("Apple", "Banana", "Orange");
      }
    }
    

    한 함수 안에 너무 많은 세부 구현이 섞여 있어서 코드를 위에서 아래로 자연스럽게 읽기 어렵다.

    • 개선 후
    public class OrderProcessor {
      public void processOrder() {
          printStartMessage();
          List<String> items = fetchItems();
          packItems(items);
          double total = calculateTotalPrice(items);
          generateInvoice(items, total);
          printEndMessage();
      }
    
      private void printStartMessage() {
          System.out.println("Processing order...");
      }
    
      private List<String> fetchItems() {
          return List.of("Apple", "Banana", "Orange");
      }
    
      private void packItems(List<String> items) {
          System.out.println("Items to pack: " + items);
          for (String item : items) {
              System.out.println("Packing: " + item);
          }
      }
    
      private double calculateTotalPrice(List<String> items) {
          System.out.println("Calculating total price...");
          double total = 0;
          for (String item : items) {
              total += getPrice(item);
          }
          System.out.println("Total price: $" + total);
          return total;
      }
    
      private double getPrice(String item) {
          return switch (item) {
              case "Apple" -> 1.0;
              case "Banana" -> 0.5;
              case "Orange" -> 0.75;
              default -> 0.0;
          };
      }
    
      private void generateInvoice(List<String> items, double total) {
          System.out.println("Generating invoice...");
          String invoice = "Invoice: " + items + " | Total: $" + total;
          System.out.println(invoice);
      }
    
      private void printEndMessage() {
          System.out.println("Order processed.");
      }
    }
    

    각 단계를 함수로 분리하여 책처럼 자연스럽게 읽히도록 개선했다.


  1. Switch 문
    본질적으로 N가지를 처리하는 switch문을 완전히 피하는 방법은 없다.
    하지만 각 switch문을 다형성을 이용해서 숨기고 반복하지 않게 구현하는 방법은 있다.

    • 개선 전
    public class Payroll {
      public Money calculatePay(Employee e) throws InvalidEmployeeType {
          switch (e.type) {
              case COMMISSIONED:
                  return calculateCommissionedPay(e);
              case HOURLY:
                  return calculateHourlyPay(e);
              case SALARIED:
                  return calculateSalariedPay(e);
              default:
                  throw new InvalidEmployeeType(e.type);
          }
      }
    }
    

    위의 코드는 여러 문제가 존재한다.

    1. 함수가 길고 새 직원 유형을 추가하려면 코드가 더 길어질 위험이 있다.
    2. 한 가지 작업만을 수행하지 않는다.
    3. SRP 원칙을 위반한다.
      • 급여를 계산해야 하는 함수가 직원을 구분하는 역할까지 하고 있다.
    4. OCP 원칙을 위반한다
      • 새로운 직원 유형을 추가하려면 기존 코드를 수정해야 한다.
    5. 구조가 동일한 함수가 추가될 수 있는 위험이 존재한다.
      • isPayDay(Employee e, Date date), deliverPay(Employee e, Money pay)와 같은 메서드를 추가하면
        각 함수 내부에서 직원 유형별로 switch 문을 사용하게 될 위험이 있다.
    • 개선 후
    public abstract class Employee {
      public abstract boolean isPayday();
      public abstract Money calculatePay();
      public abstract void deliverPay(Money pay);
    }
    
    public interface EmployeeFactory {
      public Employee makeEmployee(EmployeeRecord r) throws InvalidEmployeeType;
    }
    
    public class EmployeeFactoryImpl implements EmployeeFactory {
      public Employee makeEmployee(EmployeeRecord r) throws InvalidEmployeeType {
          switch (r.type) {
              case COMMISSIONED:
                  return new CommissionedEmployee(r);
              case HOURLY:
                  return new HourlyEmployee(r);
              case SALARIED:
                  return new SalariedEmployee(r);
              default:
                  throw new InvalidEmployeeType(r.type);
          }
      }
    }
    

    코드 개선 후 switch문은 추상 팩토리에 의해 숨겨지고 객체를 생성하는 부분에만 사용된다.


  1. 서술적인 이름을 사용하라!
    • 이름이 길어도 괜찮다. 길고 서술적인 이름이 짧고 어려운 이름보다 좋고 서술적인 주석보다도 좋다.
    • 여러 단어가 쉽게 읽히는 명명법을 사용하고 함수 기능을 잘 표현하는 이름을 선택한다.
    • 모듈 내에서 함수 이름은 같은 문구, 명사, 동사를 사용한다.
      • 좋은 예: includeSetupAndTeardownPages, includeSetupPages, includeSuiteSetupPage

  1. 함수 인수
    • 함수의 인수 개수는 짧을수록 좋다.
    • 함수 인수에 bool값을 넘기는 것은 피하자.
    • 함수 인수의 개수가 3개, 4개 이상은 가능한 피하는 게 좋다.
      • 인수의 개수가 길어진다면 독자적인 class로 선언할 수 있는지 확인하자.
    • 함수 이름에 인수 이름을 추가하면 좋다.
      • assertEquals(expected, actual)보다 assertExpectedEqualsActual(expected, actual)이 더 좋다.

  1. 오류 코드보다 예외를 사용하라!
    • if (deletePage(page) == E_OK)같은 오류 코드를 작성하는 것을 피하고 별도의 try-catch 함수를 작성한다.
      public void delete(Page page) {
          try {
              deletePage(page);
          } catch (Exception e) {
              log.error(e);
          }
      }
      

2. 🤔 이해가 어려운 부분

🔍 질문하기

책을 읽으며 이해하기 어려웠던 개념이나 명확하지 않았던 내용을 정리합니다.

  1. 함수당 추상화 수준은 하나로!
    • 어려웠던 부분
      책에서 말하는 추상화 수준에 대해 잘 이해가 안갔다.

    • 궁금한 점
      추상화 수준이 "높다, 낮다, 중간이다" 라는 의미가 대체 무엇인가?

    • 이해한 점

      펼치기/접기

      1. 고수준(High-level) 코드란?

      ✔ 핵심 개념만 보여주는 코드

      • "이 코드가 무엇을 하는지"를 설명하는 큰 그림(전체 흐름) 을 보여줌.
      • 세부 구현을 감추고, 전체 로직을 명확히 이해할 수 있게 함.
      • 주로 함수 호출이나 비즈니스 로직(업무 흐름) 에 해당함.

      💡 예제

      const placeOrder = ({ order }) => {
        validateAvailability(order);  // 주문 가능 여부 확인 (고수준)
      
        // (여기서 중간 및 저수준 작업 진행)
      
        shipOrder(order);  // 주문 배송 처리 (고수준)
      };
      

      왜 고수준인가?

      • validateAvailability(order) → "주문이 가능한지 확인"
      • shipOrder(order) → "주문을 배송한다"
      • 세부 구현이 아닌 "이 코드가 무엇을 하는지"를 표현하는 큰 개념 이다.
      • 내부 구현이 감춰져 있어서, "어떻게 하는지"는 신경 쓸 필요 없음.

      2. 저수준(Low-level) 코드란?

      ✔ 실제로 동작하는 상세 코드

      • 세부적인 계산, 데이터 변환, API 호출 같은 작업을 직접 수행하는 코드
      • "어떻게 작동하는지"를 설명하는 코드
      • 구현 세부사항이 많아서 가독성이 떨어질 수 있음.

      💡 예제

      const total = order.items.reduce(
        (item, totalAcc) =>
          totalAcc + item.unitPrice * item.units,
        0,
      );
      

      왜 저수준인가?

      • reduce()를 사용해 주문 항목의 총 가격을 계산하는 세부적인 연산 코드
      • "어떤 방식으로 가격을 계산하는지"에 집중한 구체적인 구현 코드

      3. 중간수준(Mid-level) 코드란?

      ✔ 고수준과 저수준을 연결하는 코드

      • 고수준 코드와 저수준 코드 사이에서 데이터를 가공하고, 각 단계를 연결하는 역할을 함.
      • 하나의 개념을 실행하는데 필요한 여러 작업을 모아서 처리하는 코드.

      💡 예제

      const invoiceInfo = getInvoiceInfo(order);
      const request = new PaymentService.Request({
        total,
        invoiceInfo,
      });
      const response = PaymentService.pay(request);
      sendInvoice(response.invoice);
      

      왜 중간수준인가?

      • getInvoiceInfo(order) → 주문 정보를 가져옴.
      • PaymentService.Request({ total, invoiceInfo }) → 결제 요청 객체를 생성함.
      • PaymentService.pay(request) → 결제 요청을 보냄.
      • sendInvoice(response.invoice) → 결제 후 인보이스(청구서)를 보냄.

      이들은 개별적으로 보면 특정 기능을 처리하는 코드이지만, 전체 주문 처리 과정에서 보면 중간 과정(연결 역할)을 담당하고 있음!


      정리: 코드의 추상화 수준 이해하기

      수준특징예제
      고수준 (High-level)- "무엇을 하는지" 설명
       - 전체 흐름을 보여줌
       - 세부 구현을 감춤
      validateAvailability(order); 
       shipOrder(order);
      중간수준 (Mid-level)- 고수준과 저수준을 연결함
       - 여러 작업을 모아서 처리함
      const invoiceInfo = getInvoiceInfo(order); 
       const response = PaymentService.pay(request);
      저수준 (Low-level)- "어떻게 동작하는지" 설명
       - 세부적인 계산 및 구현
      const total = order.items.reduce(...);

3. 📚 참고 사항

📢 논의하기

  1. 관련 자료 공유

  1. 논의하고 싶은 주제
    • 주제
      switch문과 if/else 중 어떤 것을 써야 하는가?
    • 설명
      저자는 switch문을 작성하는 것보다 단일 블록이나 함수를 사용하는 것을 선호한다고 한다.
      그렇다면 우리는 어떤 것을 써야 할까?