[이펙티브 자바 아이템 51] 메서드 시그니처를 신중히 설계하라

박상준·2024년 6월 27일
0

이펙티브 자바

목록 보기
14/19
post-custom-banner

매서드 이름 짓기

  • 표준 명명 규칙을 준수하라
    • 소문자로 시작할것
    • camelCase 사용
    • 동사 또는 동사구로 시작
    • 의미있고 설명적인 이름을 사용
    • 약어 사용을 자제
    • 특수문자 사용금지(언더스코어 → - 제외 )
  • 이해하기 쉽고 일관성이 있는 이름을 사용
  • 개발자 커뮤니티에서 널리 받아들여지는 이름을 선택
  • 긴 이름을 피하라
  • 애매한 경우 자바 라이브러리 API 가이드를 참조

편의 메서드의 관리법

  • 과도한 편의 메서드는 생성을 자제해야한다
  • 각 메서드의 경우 고유한 역할을 수행해야한다
    • SLP 를 따르라
  • 메서드가 너무 많으면 발생하는 문제점
    1. 익히기 어렵다
    2. 문서화가 어려움
    3. 테스트가 어려움
    4. 유지보수가 어렵
  • 인터페이스도 동일한 원칙을 적용
  • 자주 사용되는 경우에만 약칭 메서드를 고려하

매개변수 목록을 줄이는 방법

  1. 여러 메서드로 쪼개기

    1. 특징
      1. 원래 매개변수 목록의 부분집합을 받는 메서드로 분할한다

      2. 직교성(orthogonality)을 높여 메서드 수를 줄이는 효과가 가능

        직교성?

        서로 직각(90도)을 이루며 교차한다는 수학적 개념이다.

        SW 상에서는 기능들이 서로 독립적이라는 의미로 사용된다.

        java.util.List 인터페이스상에서는

        • subList 메서드
          • 부분리스트로 반환
        • indexOf 메서드
          • 주어진 원소의 인덱스를 반환
        • 두 메서드 조합으로 복잡한 기능 구현이 가능함.

        2 가지 메서드는 서로 관련이 없다.

        해당 메서드같은 경우 같이 개발 메서드로 각각의 기능을 독립적으로 제공하는 것이, 직교성이 매우 높은 설계라고 볼 수 있다.

    2. 장점
      1. 강력하고 유연한 API 생성이 가능하다.
      2. 기본 기능으로서 다양한 조합이 가능하다.
    3. 주의 사항
      1. 메서드가 너무 많아질 우려
      2. API 사용자의 눈높이에 맞는 추상화 수준의 조절이 필요하다
      3. 자주 사용되는 패턴은 편의 기능으로 제공을 고려!
  2. 도우미 클래스의 활용

    1. 특징
      1. 여러 매개변수를 하나의 개념으로 묶는 클래스를 생성
      2. 주로 정적 멤버 클래스로 구현한다.
    2. 적용 상황
      1. 연관된 매개변수들을 하나의 개념으로 볼 수 있을 때
    3. 예시
      1. 카드 게임 클래스
        • 원래는 카드를 표현하는 메서드가 이렇게 생겼다고 가정
          public void createCard(int rank, String suit) {
              // 카드 생성 로직
          }
          • 사용시

            createCard(1, "hearts");
            createCard(12, "spades");
        • 해당 방식의 문제점
          • 매개변수의 의미가 불분명하다.
          • 잘못된 값을 쉽게 전달이 가능하다.
            createCard(14, "triangle")
        • Card 라는 도우미 클래스를 만들면
          public class Card {
              private final int rank;
              private final String suit;
          
              public Card(int rank, String suit) {
                  this.rank = rank;
                  this.suit = suit;
              }
          
              // getters 등
          }
          public void createCard(Card card) {
              // 카드 생성 로직
          }
          • 메서드를 위와 같이 변경하면

            createCard(new Card(1, "hearts"));
            createCard(new Card(12, "spades"));
          • 이런식으로 Card 객체를 통해 데이터의 의미가 명확해지고

          • 유효성 검사를 할때 Card 클래스내에서 모두 제어가 가능해진다.

          • 또한 Card 객체를 다른 메서드에서도 사용할 수 있게 된다.

  3. 정적 멤버 클래스로 구현한다면

    public class CardGame {
        public static class Card {
            private final int rank;
            private final String suit;
    
            public Card(int rank, String suit) {
                this.rank = rank;
                this.suit = suit;
            }
        }
    
        public void createCard(Card card) {
            // 카드 생성 로직
        }
    }
    • 카드게임과 카드의 관계가 더 명확해지며,
      • CardCardGame 의 컨텍스트 내에서만 사용하도록 제한이 가능하다.
  4. 빌더 패턴 사용

    • 만약 빌더 패턴을 사용하지 않는다면.
    public class SearchService {
        public List<Result> search(String keyword, int maxResults, boolean caseSensitive, 
                                   String category, Date fromDate, Date toDate) {
            // 검색 로직
        }
    }
    • 빌더 패턴을 응용해 다음과 같이 변경이 가능하다
    public class SearchService {
        public List<Result> search(SearchCriteria criteria) {
            // 검색 로직
        }
    
        public static class SearchCriteria {
            private String keyword;
            private int maxResults = 10; // 기본값 설정
            private boolean caseSensitive = false; // 기본값 설정
            private String category;
            private Date fromDate;
            private Date toDate;
    
            public SearchCriteria setKeyword(String keyword) {
                this.keyword = keyword;
                return this;
            }
    
            public SearchCriteria setMaxResults(int maxResults) {
                this.maxResults = maxResults;
                return this;
            }
    
            // 다른 setter 메서드들...
    
            public List<Result> execute(SearchService service) {
                // 유효성 검사
                if (keyword == null || keyword.isEmpty()) {
                    throw new IllegalStateException("Keyword must be set");
                }
                return service.search(this);
            }
        }
    }
    SearchService service = new SearchService();
    List<Result> results = new SearchService.SearchCriteria()
        .setKeyword("Java")
        .setMaxResults(20)
        .setCategory("Programming")
        .setFromDate(new Date())
        .execute(service);
    • 선택적인 매개변수 활용
      • 필요한 매개변수만 설정가능하다.
    • 메서드 체이닝
      • 메서드를 연속해서 호출가능하며 코드가 간결해진다.
    • 불변성
      • 최종 객체를 불변으로 만들 수 있다.
    • 단계별 생성
      • 복잡한 객체를 여러 단계로 나누어 생성가능하다.
  • 근데 굳이 execute 를 사용하지 않아도
    • 생성자에 빌더를 추가하여 사용하면된다
      @Builder
      public SearchCriteria(String keyword, int maxResults, boolean caseSensitive, 
                            String category, Date fromDate, Date toDate) {
          // 유효성 검증
          if (keyword == null || keyword.isEmpty()) {
              throw new IllegalArgumentException("Keyword must not be empty");
          }
          if (maxResults <= 0) {
              throw new IllegalArgumentException("Max results must be positive");
          }
          if (fromDate != null && toDate != null && fromDate.after(toDate)) {
              throw new IllegalArgumentException("From date must be before to date");
          }
      
          this.keyword = keyword;
          this.maxResults = maxResults;
          this.caseSensitive = caseSensitive;
          this.category = category;
          this.fromDate = fromDate;
          this.toDate = toDate;
      }

매개변수 타입으로 인터페이스를 선호해야한다.

왜?

  1. 더 유연한 설계가 가능하며
  2. 다양한 구현체를 사용 가능하다
  3. 미래의 구현체도 수용이 가능해진다

예시

  • 좋은 예
    • Map 인터페이스를 사용
    • HashMap 클래스 사용

장점

  • 클라이언트에게 더 많은 선택권을 제공가능
  • 입력 데이터의 변환 비용을 절감가능하다

boolean 대신 열거 타입(ENUM) 을 사용해야한다

  • 열거 타입의 장점
    • 코드 가독성을 향상가능하다
    • 작성 용이성이 있다
    • 확장성을 개선할 수 있다
  • 예시
    public enum TemperatureScale {
        FAHRENHEIT, CELSIUS
    }
  • 좋은 예
    Thermometer.newInstance(TemperatureScale.CELSIUS)
  • 나쁜 예
    Thermometer.newInstance(true)

true false 로 구분하는건 멍청한 짓임

새로운 옵션이 추가되는 경우 대비하기가 힘들다.

profile
이전 블로그 : https://oth3410.tistory.com/
post-custom-banner

0개의 댓글