내 코드가 이렇게 이상한가요? 2부

devty·2023년 11월 6일
0

BookReview

목록 보기
2/2

7. 컬렉션 : 중첩을 제거하는 구조화 테크닉

  • 이미 존재하는 기능을 다시 구현하지 말자
    • for문과 if문이 동시에 있는 코드는 가독성이 좋지 못합니다.
    • 안 좋은 예시
      public boolean hasAnyProcessingOrder(List<Order> orders) {
          for (Order order : orders) {
              if ("processing".equals(order.getStatus())) {
                  return true;
              }
          }
          return false;
      }
    • 좋은 예시
      public boolean hasAnyProcessingOrder(List<Order> orders) {
          return orders.stream().anyMatch(order -> "processing".equals(order.getStatus()));
      }
      • anyMatch 메소드를 알고 있으면, 복잡한 로직을 직접 구현하는 것이 아닌 한줄로 처리가 가능하다.
  • 조기 Contiune로 조건 분기 중첩 제거하기
    • for문 안에 조건 분기문이 중첩 되어 있는 경우 가독성이 좋지 못합니다.
    • 안 좋은 예시
      public boolean hasSpecificOrder(List<Order> orders) {
          for (Order order : orders) {
              if ("processing".equals(order.getStatus())) {
                  if (order.getAmount() > 1000) {
                      if (order.getOrderDate().isEqual(LocalDate.now())) {
                          order.setStatus("processed");
      				            System.out.println("Order has been processed: " + order);
                      }
                  }
              }
          }
      }
    • 좋은 예시
      public boolean hasSpecificOrder(List<Order> orders) {
          for (Order order : orders) {
              if (!"processing".equals(order.getStatus())) continue;
              if (order.getAmount() <= 1000) continue;
              if (!order.getOrderDate().isEqual(LocalDate.now())) continue;
      
              order.setStatus("processed");
              System.out.println("Order has been processed: " + order);
          }
      }
      • 중첩 분기문으로 들여쓰기 하는 것보다 훨씬 더 나은 코드의 형태를 볼 수 있다.
  • 응집도가 낮은 컬렉션 처리하기
    • 컬렉션과 관련된 작업을 처리하는 코드는 여기저기 생길 수가 있다.
    • 안 좋은 예시
      public class OrderService {
          private List<Order> orders;
      
          public boolean hasSpecificOrder() {
              return orders.stream().anyMatch(order -> 
                  "processing".equals(order.getStatus()) &&
                  order.getAmount() > 1000 &&
                  order.getOrderDate().isEqual(LocalDate.now())
              );
          }
      }
      
      public class ReportService {
          private List<Order> orders;
      
          public List<Order> getProcessingOrdersFromTodayWithHighAmount() {
              return orders.stream().filter(order -> 
                  "processing".equals(order.getStatus()) &&
                  order.getAmount() > 1000 &&
                  order.getOrderDate().isEqual(LocalDate.now())
              ).collect(Collectors.toList());
          }
      }
    • 좋은 예시
      public class Orders {
          private List<Order> orders;
      
          public Orders(List<Order> orders) {
              this.orders = orders;
          }
      
          public boolean hasSpecificOrder() {
              return orders.stream().anyMatch(this::matchesCriteria);
          }
      
          public List<Order> getProcessingOrdersFromTodayWithHighAmount() {
              return orders.stream().filter(this::matchesCriteria).collect(Collectors.toList());
          }
      
          private boolean matchesCriteria(Order order) {
              return "processing".equals(order.getStatus()) &&
                     order.getAmount() > 1000 &&
                     order.getOrderDate().isEqual(LocalDate.now());
          }
      }
      public class OrderService {
          private Orders orders;
      
          public boolean hasSpecificOrder() {
              return orders.hasSpecificOrder();
          }
      }
      
      public class ReportService {
          private Orders orders;
      
          public List<Order> getProcessingOrdersFromTodayWithHighAmount() {
              return orders.getProcessingOrdersFromTodayWithHighAmount();
          }
      }
      • 이렇게 되면 컬렉션과 관련된 응집도가 높아진다. → 데이터와 비즈니스 로직이 같이 있다.

8. 강한 결합 : 복잡하게 얽혀서 풀 수 없는 구조

  • 결합이 생긴 여러 코드
    • 결합도가 높으면 안 좋은 점
      • 한 클래스의 변경이 다른 의존하는 클래스 모두 영향을 미친다.
      • 의존 받는 클래스가 변경되면 의존 하는 클래스들은 변경이 필수적이다. → 유지보수가 어려움.
      • 단일 책임 원칙을 위반한다.
    • 안 좋은 예시
      public class OrderService {
          private List<Order> orders = new ArrayList<>();
          private int totalAmount = 0;
      
          public boolean addOrder(Order order) {
              if (!isValid(order)) {
                  throw new IllegalArgumentException();
              }
      
              int amountAfterDiscount = order.getPrice();
              if (order.isDiscountApplicable()) {
                  amountAfterDiscount = applyDiscount(order.getPrice());
              }
      
              if (totalAmount + amountAfterDiscount <= 10000) {
                  totalAmount += amountAfterDiscount;
                  orders.add(order);
                  return true;
              } else {
                  return false;
              }
          }
      
          private boolean isValid(Order order) {
              return order.getId() >= 0 && !order.getName().isEmpty();
          }
      
          private int applyDiscount(int price) {
              return price - 100; 
          }
      }
      
      public class SeasonalOrderService {
          private OrderService orderService = new OrderService();
      
          public boolean addSeasonalOrder(Order order) {
              if (order.getId() < 0 || order.getName().isEmpty()) {
                  throw new IllegalArgumentException();
              }
      
              int amountAfterDiscount = order.getPrice();
              if (order.isDiscountApplicable()) {
                  amountAfterDiscount = orderService.applyDiscount(order.getPrice());  // Problem here
              }
      
              if (orderService.totalAmount + amountAfterDiscount <= 12000) {  // Problem here
                  orderService.totalAmount += amountAfterDiscount;  // Problem here
                  orderService.orders.add(order);  // Problem here
                  return true;
              } else {
                  return false;
              }
          }
      }
      • SeasonalOrderServiceOrderService의 내부 로직과 상태를 변경하고 있습니다.
      • 이는 단일 책임 원칙을 위배하며, 두 클래스가 강하게 결합됩니다.
  • 중복된 코드를 핸들링하기
    • 중복된 코드를 핸들링 하기 위해서는 아래와 같은 방법들이 있다.
      • 상속
      • 컴포지션
      • 전략패턴
    • 상속은 후에 다룰 예정이라 넘어가겠다.
    • 컴포지션과 전략패턴이 유사한 내용을 띄고 있지만 내가 생각하기엔 약간에 차이가 있는 것 같다.
    • 둘의 차이는 동일한 코드의 중복을 피하려면 컴포지션을 사용하고 Class를 이용한다. 동일한 문제를 해결하는 다양한 방법(전략)을 유연하게 교체하려면 전략 패턴을 사용하고 Interface를 사용한다.
    • 컴포지션 + 전략패턴 예시
      // 전략 패턴(기본 할인 정책과 여름 할인 정책이 개념(할인 정책)은 같으나 비즈니스 로직이 다름.)
      interface DiscountPolicy {
          int MIN_AMOUNT = 0;  // 최소 할인 금액
          int applyDiscount(int price);
      }
      
      // 할인 정책 1 (기본 할인 정책)
      class FlatDiscountPolicy implements DiscountPolicy {
          private final int discountAmount;
      
          FlatDiscountPolicy(int discountAmount) {
              this.discountAmount = discountAmount;
          }
      
          @Override
          public int applyDiscount(int price) {
              return Math.max(price - discountAmount, MIN_AMOUNT);
          }
      }
      
      // 할인 정책 2 (여름 할인 정책)
      class PercentageDiscountPolicy implements DiscountPolicy {
          private final double discountPercentage;
      
          PercentageDiscountPolicy(double discountPercentage) {
              this.discountPercentage = discountPercentage;
          }
      
          @Override
          public int applyDiscount(int price) {
              return (int) Math.max(price * (1 - discountPercentage), MIN_AMOUNT);
          }
      }
      
      // OrderBasicSaleService , OrderSummerSaleService 동일 코드 분리
      class OrderProcessor {
          private final DiscountPolicy discountPolicy;
      
          OrderProcessor(DiscountPolicy discountPolicy) {
              this.discountPolicy = discountPolicy;
          }
      
          public int processOrder(Order order) {
              int amountAfterDiscount = order.getPrice();
              if (order.isDiscountApplicable()) {
                  amountAfterDiscount = discountPolicy.applyDiscount(order.getPrice());
              }
              return amountAfterDiscount;
          }
      }
      
      // 기본 할인
      class OrderBasicSaleService {
          private final OrderProcessor orderProcessor; // 컴포지션(has-a 관계)
      
          OrderBasicSaleService() {
              this.orderProcessor = new OrderProcessor(new FlatDiscountPolicy(100));
          }
      
          public boolean addOrder(Order order) {
              int finalAmount = orderProcessor.processOrder(order); // 위임(orderProcessor에게 위임함.)
              return true;
          }
      }
      
      // 여름 할인
      class OrderSummerSaleService {
          private final OrderProcessor orderProcessor; // 컴포지션(has-a 관계)
      
          OrderSummerSaleService() {
              this.orderProcessor = new OrderProcessor(new PercentageDiscountPolicy(0.1));
          }
      
          public boolean addOrder(Order order) {
              int finalAmount = orderProcessor.processOrder(order); // 위임(orderProcessor에게 위임함.)
              return true;
          }
      }
      • 이렇게 만들게 되면 재사용성에 유용하다. → 새로운 할인 정책이 나온다고 했을 때 OrderProcessor를 변경하는 것이 아닌 새로운 클래스를 만들고 생성자를 통해 생성하면 된다.
      • 단일 책임 원칙을 지킨다. → OrderProcessor는 할인 로직을 처리하는 책임만 가지고 있으므로 서비스 클래스들은 주문 처리 로직에만 집중하면 된다.
  • DRY(Don’t Repaet Yourself) 원칙의 잘못된 적용
    • 위에서 예시로 만든 OrderService, SeasonalOrderService 두 클래스는 거의 유사한 코드를 가지고 있다.
    • 유사한 코드를 무작정 줄이고 싶은 생각이 들기도 하지만, 책무를 생각하지 않고 로직의 중복을 제거해서는 안 된다.
    • 예를 들어 OrderService, SeasonalOrderService를 컴포지션를 사용해서 묶었다고 가정 했을 때, 갑자기 SeasonalOrderService의 할인 정책이 바뀌게 된다면 오히려 중복을 제거하려 했다가 더 큰 공사를 하게 될 것이다.
    • 따라서 같은 로직, 비슷한 로직이라도 개념이 다르면 중복을 허용해줘야한다.
  • 상속과 관련된 강한 결합
    • 상속을 사용하게 되면 단점
      • 하위 클래스가 상위 클래스의 구현에 의존하게 되어, 상위 클래스의 변경이 하위 클래스에 영향을 줄 수 있다. → 강한 결합이 생김.
      • 하위 클래스가 상위 클래스의 내부 구조와 상태에 접근이 가능하다. → 캡슐화 유지 어려움.
    • 해결하기 위해서는 컴포지션, 전략 패턴, 위임으로 해결이 가능하다.
  • Private 메소드가 많다는 것은 책임이 너무 많다는 것
    • private 메소드를 많이 사용하는 것보단 책임을 다른 클래스로 분리하는 것이 낫다.

9. 설계의 건전서을 해치는 여러 악마

  • YAGNI 원칙
    • YAGNI(You Aren’t Gonna Nedd It)은 지금 필요 없는 기능을 만들지 말라 입니다.
    • 왜냐하면 소프트웨어에 대한 요구사항은 매일매일 변화한다. 사양으로 확정되지 않고 명확하게 언어화되지 않은 요구를 미리 예측하고 구현해도, 이러한 예측은 대부분 맞지 않는다.
    • 예측에 들어맞지 않는 로직은 데드 코드가 된다.
    • 또한, 가독성을 낮추고 읽는 사람을 혼란스럽게 만든다.
  • null 문제
    • null로 객체를 초기화 시킨다면, 여러 군데에서 null을 체크해야하는 일이 발생한다. → 가독성이 떨어집니다.
    • 또한 실수로 null을 체크 안 하는 곳이 생길 수도 있다. → 에러 발생
    • 따라서, null을 리턴하지 않는 설계, null을 전달하지 않는 설계를 해야한다.
  • null 안전
    • null 안전이란 null에 의한 오류가 아예 발생하지 않게 만드는 구조이다.
    • null 안전 예시
      String name1 = "John";
      Optional<String> opt1 = Optional.ofNullable(name1); // opt1은 "John" 값을 포함하는 Optional이다.
      
      String name2 = null;
      Optional<String> opt2 = Optional.ofNullable(name2); // opt2는 값이 없는 Optional로, Optional.empty()와 같다.
  • 예외를 Catch 하고서 무시하는 코드
    • 코드에 오류가 났는데도 오류를 탐지할 방법이 없어진다.
    • 어느 시점에서 어떤 코드에서 문제가 발생했는지 찾기가 힘들어진다.
    • 안 좋은 예시
      try {
          int result = 10 / 0;
      } catch (ArithmeticException e) {
      
      }
    • 좋은 예시
      try {
          int result = 10 / 0;
      } catch (ArithmeticException e) {
          // 1. 로그 남기기
          System.err.println("An error occurred: " + e.getMessage());
      
          // 2. 사용자에게 의미 있는 메시지 표시
          System.out.println("You tried to divide by zero. Please provide a valid denominator.");
      
          // 3. 프로그램의 흐름 조정
          // 예를 들면, 다시 입력을 받는다거나 기본값을 설정할 수 있다.
      }
      • 문제가 생겼다면 Catch에서 통지 및 기록을 해두는것이 좋다.
  • Sliver Bullet
    • Sliver Bullet이란? 울버린, 늑대인간, 드라큘라와 같은 괴물들에게 은탄환(one seeks bullets of silver)은 한번에 무력화 시킬 수 있는 최고의 도구이다.
    • 1986년 프레드 브룩스가 쓴 소프트웨어 공학 논문에서 은탄환의 존재에 대해 최초로 언급하였다. 이와 동시에 소프트웨어 개발의 복잡성을 한번에 해소할 마법같은 솔루션(은탄환)은 없다고 선언한다.
    • 우리의 설계에는 Best라는 것은 없다. 항상 Better을 목표로 할 뿐이다.
      • 항상 제일 좋은 코드가 무엇인가를 생각하면서 짰었는데, 나의 입장에서 최대한 한 발자국이라도 더 나아갔다면 그걸로 ok…입니다…

10. 이름 설계 : 구조를 파악할 수 있는 이름

  • 악마를 불러들이는 이름
    • 온라인 쇼핑몰은 상품을 중심으로 이루어져있다.
    • 따라서 이름을 단순하게 상품 클래스라고 붙이, 여러 유스케이스와 관계를 맺게 된다.
    • 그러면 상품 클래스가 여러 클래스와 관련 로직을 갖게 되어 결합도가 높아진다.
  • 관심사 분리
    • 상품이 예약, 주문, 발송등 다양한 관심사에 관한 로직을 갖고 있다. → 강합 결합 상태
    • 강한 결합을 해소시키기 위해 관심사 분리를 해야한다.
    • 관심사 분리란 관심사(유스케이스, 목적, 역할)에 따라서 분리한다.
      • 분할 후에는 분할한 클래스 각각에 관심사에 맞는 로직을 캡슐화하면 된다.
      • 이렇게 관심사 분리를 하면 결합도를 낮추고 응집도를 높힐수 있다.
      • 또한, 주문에 대한 로직이 변경 되었을 경우 주문과 관련된 클래스만 확인하면 되기에 개발 생산성이 향상될 것이다.
  • 이름 설계하기
    • 최대한 구체적이고, 의미 범위가 좁고, 특화된 이름 선택하기
    • 존재보단 목적 기반의 이름 생각하기
    • 어떤 관심사를 가졌는지 분석하기
    • 소리 내어 이야기해보기
    • 대체 이름 생각해보기
    • 결합이 느슨하고 응집도가 높은 구조인지 검토하기
  • 최대한 구체적이고, 의미 범위가 좁고, 특화된 이름 선택하기
    • 안 좋은 예시
      class OrderService {
          public void add(Order o) { /*...*/ }
          public void update(Order o) { /*...*/ }
          public void get(int id) { /*...*/ }
          public void del(int id) { /*...*/ }
      }
    • 좋은 예시
      class OrderService {
          public void addNewOnlineOrder(Order onlineOrder) { /*...*/ }
          public void updateUnshippedOrder(Order unshippedOrder) { /*...*/ }
          public void retrieveOrderById(int orderId) { /*...*/ }
          public void deleteCanceledOrder(int canceledOrderId) { /*...*/ }
      }
      • 메소드 명만 보더라도 이 메소드가 어떤 작업을 하는지 이해할 수 있다.
    • 더 좋은 예시
      // 주문 생성과 관련된 로직만 담당하는 서비스
      class OrderCreationService {
          public void addNewOnlineOrder(Order onlineOrder) {
              // 온라인 주문 추가 로직
          }
      }
      
      // 주문 상태와 관련된 업데이트 로직만 담당하는 서비스
      class OrderUpdateService {
          public void updateUnshippedOrder(Order unshippedOrder) {
              // 발송되지 않은 주문 업데이트 로직
          }
      
          public void deleteCanceledOrder(int canceledOrderId) {
              // 취소된 주문 삭제 로직
          }
      }
      
      // 주문 조회와 관련된 로직만 담당하는 서비스
      class OrderRetrievalService {
          public Order retrieveOrderById(int orderId) {
              // 주문 ID를 통한 주문 조회 로직
              return new Order(); // 예시입니다. 실제 로직에 따라 변경이 필요합니다.
          }
      }
      • 이렇게 서비스를 세분화하면 각 서비스는 자신의 주된 책임에만 집중하게 된다.
      • 또한, 클래스의 크기가 줄어들어 관리하기 쉬워집니다.
  • 가능하면 메서드의 이름은 동사 하나로 구성되게 하기

11. 주석 : 유지보수와 변경의 정확성을 높이는 주석 작성 방법

  • 실제 코드와 내용이 다르거나 낡은 주석은 바로 제거하거나 재 작성한다.
    • 이러한 주석들은 읽는 사람에게 혼란을 줄 뿐더러, 이로 인해 버그를 발생시킨다.
  • 주석 규칙 정리
    규칙이유
    로직을 변경할 때는 주석도 함께 변경해야 함.주석을 제대로 변경하지 않으면, 실제 로직과 달라져 주석을 읽는 사람에게 혼란을 줌.
    로직의 내용을 단순하게 설명하기만 하는 주석은 달지 않음.실질적으로 가독성을 높이지 않고, 주석 유지보수가 힘듦.
    가독성이 나쁜 로직에 설명을 추가하는 주석은 달지 않음. 대신 로직의 가독성을 높여야함.주석 유지 보수가 힘들고, 갱신되지 않아 낡은 주석이 될 가능성이 높음.
    로직의 의도와 사양을 변경할 때 주의할 점을 주석으로 달아야함.유지 보수와 사양 변경에 도움이 됨.
  • Javadoc 사용하기
    /**
     * 주문 항목을 추가한다.
     *
     * @param orderId 주문 ID
     * @param orderItem 추가할 주문 항목
     * @return 추가된 후의 전체 주문
     * @throws IllegalArgumentException 주문 ID가 유효하지 않은 경우 스로우
     * @throws IllegalStateException 주문 상태가 추가를 허용하지 않는 경우 스로우
     */
    public Order addOrderItem(final int orderId, final OrderItem orderItem) {
        Order order = orderRepository.findById(orderId);
        if (order == null) {
            throw new IllegalArgumentException("유효하지 않은 주문 ID입니다.");
        }
    
        if (!order.isAddable()) {
            throw new IllegalStateException("현재 주문 상태에서는 항목을 추가할 수 없습니다.");
        }
    
        order.addOrderItem(orderItem);
        orderRepository.save(order);
        return order;
    }
    • 이렇게 두면 Javadoc를 이용해서 API 문서를 자동으로 생성도 가능하다.
    • 또한, 해당 API가 무슨 역할을 하는지 코드를 보지 않아도 이해가 가능하다.

12. 메소드 : 좋은 클래스에는 좋은 메소드가 있다.

  • 묻지 말고 명령해라
    • Getter, Setter 사용에 문제점
      1. 캡슐화 침해 : Getter와 setter는 객체의 내부 상태를 외부에 노출시켜, 객체의 구현 세부 사항을 공개합니다. 이로 인해 객체의 상태가 외부에서 변경될 수 있게 되어, 캡슐화가 약화된다.
      2. 자율성 감소 : 객체가 스스로 상태를 관리하는 대신, 상태 관리가 외부 객체에 의존하게 되어 객체 간 결합도가 증가하고 자율성이 감소한다.
      3. 리팩토링 어려움 : 많은 외부 코드가 getter와 setter에 의존할 때, 내부 구현의 변경이 어려워지고 리팩토링 시 많은 부수적인 변경이 필요해질 수 있다.
      4. 로직 분산 : 비즈니스 로직이 객체 밖으로 흩어져 객체의 경계를 넘어설 수 있으며, 작업 이해나 변경을 위해 여러 위치의 코드를 확인해야 할 수 있다.
    • 안 좋은 예시
      public class OrderItems {
          private List<OrderItem> items;
      
          public OrderItems(List<OrderItem> items) {
              this.items = new ArrayList<>(items);
          }
      
          // 내부 컬렉션에 대한 직접적인 접근을 허용하는 getter
          public List<OrderItem> getItems() {
              return items; // 외부에서 items를 직접 수정할 수 있음
          }
      
          // 내부 컬렉션을 변경할 수 있는 setter
          public void setItems(List<OrderItem> items) {
              this.items = items; // 외부로부터 받은 리스트로 내부 상태 변경
          }
      
          public BigDecimal calculateTotalPrice() {
              // 아이템 가격의 총합을 계산하여 반환하는 로직 구현
              return new BigDecimal("0"); // 임시 코드
          }
      }
      
      public class Order {
          private final OrderItems orderItems;
      
          public Order(OrderItems orderItems) {
              this.orderItems = orderItems;
          }
      
      		// Getter 사용
          public OrderItems getOrderItems() {
              return orderItems;
          }
      }
      
      public class OrderService {
          public void processOrder(Order order) {
              BigDecimal totalPrice = order.getOrderItems().calculateTotalPrice();
          }
      }
      • OrderService에서 order.getOrderItems().calculateTotalPrice();를 호출하면서, Order의 내부 상태에 대해 두 단계에 걸쳐 접근하고 있다.
        • 객체가 자신의 직접적인 구성 요소에만 메시지를 보내야 한다는 걸 어겼다.
      • setItems() 메서드를 통해 외부에서 List<OrderItem>를 변경할 수 있게 하여 OrderItems의 상태를 언제든지 변경할 수 있다.
        • 이는 객체의 불변성을 해치고 예상치 못한 부작용을 일으킬 수 있다.
      • OrderItems 클래스에서 getItems() 메서드를 통해 내부의 List<OrderItem>에 대한 직접 접근을 허용하고 있습니다.
        • 이것은 List의 내용이 변경될 수 있다는 것을 의미하며, 이는 객체의 캡슐화 원칙을 위반하는 것입니다.
    • 좋은 예시
      public class OrderItems {
          private final List<OrderItem> items;
      
          public OrderItems(List<OrderItem> items) {
              // 내부 리스트를 불변으로 만들어 외부에서 변경할 수 없도록 함
              this.items = Collections.unmodifiableList(new ArrayList<>(items));
          }
      
          public BigDecimal calculateTotalPrice() {
              // 아이템 가격의 총합을 계산하여 반환하는 로직 구현
              BigDecimal total = BigDecimal.ZERO;
              for (OrderItem item : items) {
                  total = total.add(item.getPrice());
              }
              return total;
          }
          
          // OrderItems의 다른 동작을 위한 메서드들 (가변성을 방지하기 위해 내부 상태 변경 메서드는 제공하지 않음)
      }
      
      public class Order {
          private final OrderItems orderItems;
      
          public Order(OrderItems orderItems) {
              this.orderItems = orderItems;
          }
      
          public BigDecimal calculateTotalPrice() {
              // 직접 OrderItems 객체를 노출하지 않고 필요한 동작을 수행
              return orderItems.calculateTotalPrice();
          }
      }
      
      public class OrderService {
          public void processOrder(Order order) {
              // Order 객체를 통해 필요한 정보를 얻음
              BigDecimal totalPrice = order.calculateTotalPrice();
          }
      }
      • OrderItems는 내부 리스트에 대한 직접 접근을 허용하지 않으므로, 객체의 상태를 보호하고, 객체의 행동을 통해서만 상태가 변경될 수 있도록 한다.
        • 이것은 객체 지향 설계의 핵심 원칙 중 하나인 캡슐화를 강화한다.
      • OrderItems의 내부 상태가 불변으로 만들어져 있기 때문에, 객체가 생성된 후에는 그 상태가 변경되지 않습니다.
        • 이는 다양한 부작용과 오류 가능성을 줄이고, 코드를 이해하고 유지보수하기가 더 쉬워집니다.
      • OrderServiceOrder 객체를 통해서만 필요한 작업을 수행하므로, Order의 내부 구조에 대해 알 필요가 없습니다.
  • null 전달하지 않기
    • null을 활용하는 로직은 NullPointerExcepetion이 발생할 수 있으면, null을 확인해야 하므로 로직이 복잡해지는 등 다양한 문제가 생길 수 있다.
  • 메소드 이름 설계
    • 메소드 이름은 동사 + 목적어 형태로 이루워진다.
    • 좋은 메소드 이름을 아래와 같은 장점이 있다.
      1. 명확성 : 메서드가 수행하는 작업을 명확하게 표현해야 한다. 이름만 보고도 메서드의 기능을 유추할 수 있어야 한다.
      2. 간결성 : 가능한 간결하게, 하지만 명확성을 해치지 않는 범위 내에서 이름을 지어야 한다.
      3. 일관성 : 같은 동작을 하는 메서드는 프로젝트 전반에 걸쳐 동일한 이름을 가져야 한다.
    • 좋은 메서드 이름 예시
      • addOrderItem(OrderItem item): 주문에 새 항목을 추가합니다.
      • removeOrderItem(OrderItem item): 주문에서 항목을 제거합니다.
      • calculateTotalAmount(): 주문의 총 금액을 계산합니다.
profile
지나가는 개발자

0개의 댓글