ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Effective Java] 8장 : 메서드
    책/Effective Java 3판 2024. 2. 14. 00:55
    반응형

    아이템 49 - 매개변수가 유효한지 검사하라

    • 함수의 맨 처음에 가급적 매개변수의 유효성을 검사하는 것이 좋다 → null 체크, index 음수 체크 등
    • 중간 계산 과정에서 자연스럽게 나오는 것은 굳이 할 필요는 없다.
    • public, protected 메서드는 매개변수가 잘못됐을 때 던지는 예외를 문서화해야함 → @throws

    아이템 50 - 적시에 방어적 복사본을 만들라

    final class Period {
        private final Date start;
        private final Date end;
    
        public Period(Date start, Date end) {
            if (start.compareTo(end) > 0) {
                throw new IllegalArgumentException();
            }
    
            this.start = start;
            this.end = end;
        }
    
        public Date start() {
            return start;
        }
    
        public Date end() {
            return end;
        }
    }
    
    public static void main(String[] args) {
            Date start = new Date();
            Date end = new Date();
            Period p = new Period(start, end);
    
            System.out.println(p.end());
    
            // 불변식 깨짐
            System.out.println("after : ");
            end.setYear(78);
            System.out.println(p.end());
        }
    • Period 클래스는 불변을 목적으로 만들었다.
      • 그러나 Date가 가변이기 때문에, 손쉽게 수정할 수 있었다 → 불변식이 깨짐
      • 간단한 해결 : Date 말고 Instant, LocalDateTIme 등을 사용
    • 꼭 Date를 사용해야 한다면, 매개변수의 유효성 검사 전에 생성자에서 방어적 복사본을 만든다.
      • 유효성 검사 전에 복사한 이유 → 멀티스레딩 환경일 경우 그 찰나의 순간에 수정할 위험이 있으므로
      • public Period(Date start, Date end) { this.start = new Date(start.getTime()); this.end = new Date(end.getTime()); if (start.compareTo(end) > 0) { throw new IllegalArgumentException(); } }
      • 또한, clone()을 사용하지 않았는데, 이는 매개변수 Date가 final 클래스가 아니므로 누군가 확장한 클래스의 clone()을 호출할 수 있기 때문 ← 다형성
    • 이렇게 해도 아직 수정 가능
    public static void main(String[] args) {
            Date start = new Date();
            Date end = new Date();
            Period p = new Period(start, end);
    
            System.out.println(p.end());
    
            // 불변식 깨짐
            System.out.println("after : ");
            p.end().setYear(78);
            System.out.println(p.end());
        }
    • 이는 start(), end() 가 내부 가변 정보를 그대로 드러내기 때문
      • 방어 : 접근자 또한 가변 필드의 방어적 복사본을 리턴하도록
      • public Date start() { return new Date(start.getTime()); } public Date end() { return new Date(end.getTime()); }
      • 이 경우 clone()을 사용해도 괜찮은데, 이는 매개변수가 아닌 자기 클래스의 필드에 존재하는 값이고, Date임이 확실하기 때문이다.
    • 방어적 복사는 무조건 이뤄져야 하는 것이 아님 → 복사 비용이 너무 크거나 클라이언트가 신뢰 가능하다면 방어적 복사 대신 해당 요소를 수정했을 때의 책임이 클라이언트에게 있음을 문서에 명시하면 됨

    아이템 51 - 메서드 시그니처를 신중히 설계하라

    • 메서드 이름 신중히 짓기
    • 편의 메서드를 너무 많이 만들지 말자
    • 매개변수 목록을 짧게 유지하자 ← 특히 같은 자료형이 연속된 매개변수 리스트는 어렵다. - 4개 이하로
      • 여러 메서드로 쪼갠다 → 쪼개진 메서드 각각이 원래 매개변수 목록의 부분집합을 받음
      • 매개변수 여러개를 묶는 도우미 클래스를 만듦 → 정적 멤버 클래스로 둔다.
      • 빌더 패턴을 메서드 호출에 응용 → 매개변수가 많고, 일부를 생략할 수 있을 때 유용
        • 세터를 만들고, 각 세터를 호출해 필요 값을 설정한 후 execute()로 유효성 검사
    • 매개변수 타입으로는 인터페이스가 훨씬 나음
    • boolean보다는 원소 2개짜리 열거 타입이 낫다.
      • 이는 추가하기도 쉬움

    아이템 52 - 다중정의는 신중히 사용하라

    public class CollectionClassifier {
        public static String classify(Set<?> s) {
            return "집합";
        }
    
        public static String classify(List<?> s) {
            return "리스트";
        }
    
        public static String classify(Collection<?> s) {
            return "그 외";
        }
    
        public static void main(String[] args) {
            Collection<?>[] collections = {
                    new HashSet<String>(),
                    new ArrayList<BigInteger>(),
                    new HashMap<String, String>().values()
            };
    
            for (Collection<?> c : collections) {
                System.out.println(classify(c));
            }
        }
    }
    • 위 코드는 “그 외” 를 3번 출력하게 된다.
      • 이유 : 세 개의 classify 중 어느 메서드를 호출할지가 컴파일 타임에 결정되기 때문
        • 오버라이딩한 메소드는 동적으로 선택되고, 오버로드한 메소드는 정적으로 선택된다
    public class Overriding {
        public static void main(String[] args) {
            List<Wine> wineList = List.of(new Wine(), new SparklingWine(), new Champagne());
    
            for (Wine wine : wineList) {
                System.out.println(wine.name());
            }
        }
    }
    
    class Wine {
        String name() {
            return "포도주";
        }
    }
    
    class SparklingWine extends Wine {
        @Override
        String name() {
            return "발포성 포도주";
        }
    }
    
    class Champagne extends SparklingWine {
        @Override
        String name() {
            return "샴페인";
        }
    }
    • 위 코드의 실행 결과는 포도주, 발포성 포도주, 샴페인 이다.
      • for문 컴파일 타입이 Wine 임과 무관하게, 담겨있는 실제 객체의 메서드들 중 가장 하위에 있는 것을 호출함 → 동적 바인딩
    • 따라서 다중정의는 이러한 일반적인 기대를 가볍게 무시 → 다중 정의가 혼동을 일으키는 상황을 피해야함
    • 어떻게 피함?
      • 매개변수 수가 같은 다중정의는 만들지 말자.
      • 가변인수를 사용하는 메서드라면 다중정의를 아예 안하는 것도

    아이템 53 - 가변인수는 신중히 사용하라

    • 메서드를 정의할 때 필수 매개변수는 가변인수 앞에 두고, 가변인수를 사용할 때는 성능 문제까지 고려하자.

    아이템 54 - null이 아닌, 빈 컬렉션이나 배열을 반환하라

    • null을 리턴하게 되면
      • 클라이언트에서 이를 체크하는 코드가 반드시 들어가게 됨
      • 만약 체크하지 않으면 예상하지 못한 오류가 발생할 수 있다.
    • 빈 컨테이너를 리턴하도록 수정하자
      • 만약 성능이 걱정된다면(거의 그럴일이 없지만) - 매번 똑같은 빈 불변 컬렉션을 반환하면 됨
      • Optional.empty(), Colletions.emptyList() 가 대표적
      • static final로 선언하고 이를 반환하는 방식

    아이템 55 - 옵셔널 반환은 신중히 하라

    • null을 던지는 방식은 당연히 안좋음
    • 그러나 예상 외로 조건이 안 맞을 때마다 예외를 던지는 방식도 좋지 못하다
      • 예외는 진짜 예외적인 상황에서만 사용
      • 스택 추적 전체를 캡처하므로 비용이 꽤 높기 때문
    • 이 경우 Optional를 리턴하는 방식이 도움이 됨
      • 예외를 던지는 대신 Optional.empty()를 리턴하도록
      • Optional을 사용할 때는 절대 null을 반환하지 않게 → 이는 옵셔널의 취지를 완전히 무시한것
    • 그러나 항상 좋은 건 아님
      • 컬렉션, 스트림, 배열, 옵셔널 같은 컨테이너 타입은 옵셔널로 감싸면 안됨
        • 그냥 빈 List를 반환하는게 좋다.
        • 어차피 빈 List를 리턴할 수 있으므로 굳이 옵셔널을 사용할 필요가 없음
      • 컬렉션의 키, 값, 원소나 배열의 원소로 사용하는 경우는 거의 없을 것
      • 박싱된 기본 타입을 담는 옵셔널은 기본 타입보다 무거울수밖에 없다
        • OptionalInt 등 전용 클래스가 존재
      • 그럼 어떤 경우에?
        • 결과가 없을 수 있으며, 클라이언트가 상황을 특별 처리해야 할 때 옵셔널 사용

    아이템 56 - 공개된 API 요소에는 항상 문서화 주석을 작성하라

    • 생략

     

    https://product.kyobobook.co.kr/detail/S000001033066

    반응형
Designed by Tistory.