Dev Book Review/Effective Java

[Effective Java] Item18. 상속보다는 컴포지션을 사용하라

상속이 안전 할 때 상위 클래스와 하위클래스를 모두 같은 프로그래머가 통제하는 패키지 안에서 사용한다. 확장할 목적으로 설계되었고 문서화도 잘되었다. 상속이 안전하지 않을때 다른 패키지의 구체 클래스를 상속하는 것은 위험하다. 1. 하위 클래스가 깨지기 쉬운 이유 상속은 캡슐화를 깨뜨린다. 상위클래스의 구현에 따라 하위 클래스의 동작에 이상이 생길 수 있다. 상위클래스는 릴리스마다 내부 구현이 달라질 수 있으며, 그 여파로 하위 클래스가 오동작할 수 있다. a. 자신이 다른 부분을 사용하는 자가사용 여부 public class InstrumentedHashSet extends HashSet{ private int addCount = 0; @Overrid public boolean add(E e){ add..

[Effective Java] Item18. 상속보다는 컴포지션을 사용하라

728x90

상속이 안전 할 때

  • 상위 클래스와 하위클래스를 모두 같은 프로그래머가 통제하는 패키지 안에서 사용한다.
  • 확장할 목적으로 설계되었고 문서화도 잘되었다.

상속이 안전하지 않을때

  • 다른 패키지의 구체 클래스를 상속하는 것은 위험하다.

 

1. 하위 클래스가 깨지기 쉬운 이유

상속은 캡슐화를 깨뜨린다.

상위클래스의 구현에 따라 하위 클래스의 동작에 이상이 생길 수 있다.

상위클래스는 릴리스마다 내부 구현이 달라질 수 있으며, 그 여파로 하위 클래스가 오동작할 수 있다.

 

a. 자신이 다른 부분을 사용하는 자가사용 여부

public class InstrumentedHashSet<E> extends HashSet<E>{
  private int addCount = 0;

  @Overrid public boolean add(E e){
    addCount++;
    return super.add(e);
  }

  @Overrid public boolean addAll(Collection<? extends E> c){
    addCount += c.size();
    return super.add(c);
  }
}

잘 작동하지 않는다 : addAll 메서드가 add를 사용해 구현되어 addCount 값이 중복해서 더해진다.

내부 구현 방식에 해당하는 부분을 고려해서 수정을 해도, 이 내부구현이 다음 릴리즈에서 유지될지 알 수 없다.

 

b. 상위클래스의 메서드 동작을 재구현

addAll 메서드를 다른식으로 재정의한다 : 주어진 컬렉션을 순회하며 원소 하나당 add 메서드를 한번만 호출

시간도 더들고, 오류를 내거나 성능저하가 있을 수 있다. 하위클래스에서 접근이 불가한 private이라면 이 방식 구현 자체가 불가능하다.

 

c. 다음 릴리즈에서 상위 클래스에 새로운 메서드 추가

상위클래스에 또다른 원소 추가 메서드가 생성된다 가정하자. 하위 클래스에서 재정의하지 못한 새로운 메서드를 사용해 허용되지 않은 원소를 추가할 수 있게된다.

 

d. 클래스 확장하더라도 메서드 재정의 대신 새로운 메서드 추가

운이 없게도, 다음 릴리즈에서 상위클래스에 추가된 새 메서드가 내가 하위클래스에 추가한 메서드와 시그니처가 같고 반환타입이 다르면 컴파일조차 안된다.

 

2. 상속의 문제를 해결하는 방법 : 컴포지션

컴포지션 설계 : 기존 클래스가 새로운 클래스의 구성요소로 쓰인다.
기존 클래스 확장 대신 새로운 클래스를 만들고 private 필드로 기존 클래스의 인스턴스를 참조하자

전달 : 새 클래스의 인스턴스 메서드는 기존 클래스에 대응하는 메서드들을 호출해 그 결과를 반환한다.

전달 메서드 : 새 클래스의 메서드

// 코드 18-3 재사용할 수 있는 전달 클래스 (118쪽)
public class ForwardingSet<E> implements Set<E> {
    private final Set<E> s;
    public ForwardingSet(Set<E> s) { this.s = s; }

    public void clear()               { s.clear();            }
    public boolean contains(Object o) { return s.contains(o); }
    public boolean isEmpty()          { return s.isEmpty();   }
    public int size()                 { return s.size();      }
    public Iterator<E> iterator()     { return s.iterator();  }
    public boolean add(E e)           { return s.add(e);      }
    public boolean remove(Object o)   { return s.remove(o);   }
    public boolean containsAll(Collection<?> c)
                                   { return s.containsAll(c); }
    public boolean addAll(Collection<? extends E> c)
                                   { return s.addAll(c);      }
    public boolean removeAll(Collection<?> c)
                                   { return s.removeAll(c);   }
    public boolean retainAll(Collection<?> c)
                                   { return s.retainAll(c);   }
    public Object[] toArray()          { return s.toArray();  }
    public <T> T[] toArray(T[] a)      { return s.toArray(a); }
    @Override public boolean equals(Object o)
                                       { return s.equals(o);  }
    @Override public int hashCode()    { return s.hashCode(); }
    @Override public String toString() { return s.toString(); }
}
public class InstrumentedSet<E> extends ForwardingSet<E> {
    private int addCount = 0;

    public InstrumentedSet(Set<E> s) {
        super(s);
    }

    @Override public boolean add(E e) {
        addCount++;
        return super.add(e);
    }
    @Override public boolean addAll(Collection<? extends E> c) {
        addCount += c.size();
        return super.addAll(c);
    }
    public int getAddCount() {
        return addCount;
    }

    public static void main(String[] args) {
        InstrumentedSet<String> s = new InstrumentedSet<>(new HashSet<>());
        s.addAll(List.of("틱", "탁탁", "펑"));
        System.out.println(s.getAddCount());
    }
}

래퍼클래스 : 다른 인스턴스를 감싸고 있다 InstrumentedSet : 데코레이터 패턴

위임(deleagtion) : 컴포지션과, 전달의 조합 : 래퍼 객체가 내부 객체에 자기 자신의 참조를 넘기는 경우

 

3. 래퍼클래스의 단점

콜백(callback) 프레임워크와는 어울리지 않는다.

SELF 문제 : 콜백에서는 자기 자신의 참조를 다른 객체에게 넘겨서 다음 호출때 사용한다. 내부 객체는 자신을 감싸는 래퍼의 존재를 모르니 대신 자신(this) 참조를 넘기고, 콜백때 래퍼 객체가 아닌 내부 객체를 호출한다.

전달 메서드 작성이 귀찮으면 : 재사용할 수 있는 전달클래스 만들기

 

4. 상속과 컴포지션

하위 클래스가 상위 클래스의 '진짜' 하위 타입인 상황에서만 쓰자. 클래스간 관계가 is-a 관계일때만 상속

컴포지션을 사용해야할 상황에서 상속을 사용하는 건 내부 구현을 불필요하게 노출하는 것이다.
API가 내부 구현에 묶이고 클래스의 성능도 영원히 제한된다.

상속을 결정하는 질문

  • 확장하는 클래스의 API에 아무런 결함이 없는가?
  • 결함이 있다면 이 결함이 내 클래스의 API까지 전파되도 괜찮은가?
    : 컴포지션으로는 이런 결함을 숨길 수 있지만, 상속은 아니다.

댓글

Comments

Dev Book Review/Effective Java

[Effective Java] item17. 변경가능성을 최소화하라

불변 클래스 : 그 인스턴스의 내부 값을 수정할 수 없는 클래스 예 ) String, Wrapper Class, BigInteger, BigDecimal - 설계 구현 사용이 쉽다. 오류 여지가 적고 안전하다. 1. 불변 클래스 5가지 규칙 ㄱ. 객체의 상태를 변경하는 메서드(변경자)를 제공하지 않는다. (setter) ㄴ. 클래스를 확장할 수 없게 한다. (extend X) ㄷ. 모든 필드를 final로 선언한다 ㄹ. 모든 필드를 private으로 선언한다. ​ [아이템 15] : 클래스와 멤버의 접근권한을 최소화하라 ​ [아이템 16] : public 클래스에서는 public 필드가 아닌 접근자 메서드를 사용하라 ㅁ. 자신 외에는 내부의 가변 컴포넌트에 접근할 수 없도록 한다. 객체 참조 X - ge..

[Effective Java] item17. 변경가능성을 최소화하라

728x90

불변 클래스 : 그 인스턴스의 내부 값을 수정할 수 없는 클래스

  • 예 ) String, Wrapper Class, BigInteger, BigDecimal - 설계 구현 사용이 쉽다.
  • 오류 여지가 적고 안전하다.

 

1. 불변 클래스 5가지 규칙

ㄱ. 객체의 상태를 변경하는 메서드(변경자)를 제공하지 않는다. (setter)

ㄴ. 클래스를 확장할 수 없게 한다. (extend X)

ㄷ. 모든 필드를 final로 선언한다

ㄹ. 모든 필드를 private으로 선언한다.

[아이템 15] : 클래스와 멤버의 접근권한을 최소화하라
[아이템 16] : public 클래스에서는 public 필드가 아닌 접근자 메서드를 사용하라

ㅁ. 자신 외에는 내부의 가변 컴포넌트에 접근할 수 없도록 한다.

객체 참조 X - getter가 그대로 참조값 반환 X - 생성자, 접근자, readObject에서 방어적 복사
[아이템 88] : readObject 메서드는 방어적으로 작성하라

 

2. 불변 객체의 장점

ㄱ. 근본적으로 스레드 안전 : 동기화 할 필요가 없다.

 

ㄴ. 불변객체는 안심하고 공유 가능 : 스레드 간 영향을 주고받을 수 없기 때문

방어적 복사도 필요없다 > clone 메서드 복사 생성자 필요 없다
String 클래스의 복사생성자는 되도록 사용하지 말자

[아이템 50] 적시에 방어적 복사본을 만들라
[아이템 13] clone 재정의는 주의해서 진행하라
[아이템 6] 불필요한 객체 생성을 피하라

 

ㄷ. 한번 만든 인스턴스 최대한 재활용 가능

자주 사용되는 인스턴스를 생성하여, 정적팩터리를 제공할 수 있다.

[아이템 1] 생성자 대신 정적 팩터리 메서드를 고려하라

메모리 사용량 감소 / 가비지 컬렉션 비용 감소
예시) BigInteger, Wrapper Class

 

ㄹ. 불변객체는 자유롭게 공유할 수 있음은 물론, 불변 객체끼리는 내부 데이터를 공유할 수 있다.

BigInteger의 음수를 구하는 로직에서, mag 배열은 새로 만들지 않고 참조값을 넘기는 것을 볼 수 있다.

 

ㅁ. 객체를 만들 때 다른 불변 객체들을 구성요소로 사용하면 이점이 많다 : 구조가 복잡해도 불변식 유지가 수월

 

ㄹ. 불변객체는 그 자체로 실패 원자성을 제공한다

실패 원자성 : 메서드에서 예외가 발생한 후에도 그 객체는 메서드 호출전 상태와 같은 유효한 상태를 가진다.
[아이템 76] 가능한한 실패 원자적으로 만들어라

public Object pop(){
    if (size == 0)
        throw new EmptyStackException();
    Object result = element[--size];
    elements[size];
    return result;
}

이부분을 제거해도 스택이 비었다면 여전히 예외를 던진다 다만 size의 값이 음수가되어 다음번 호출도 실패하게 만들며, 이때 던지는 ArrayIndexOutOfboundsException은 추상화 수준이 상황에 어울리지 않는다.

 

3. 불변 객체의 단점

값이 다르면 반드시 독립된 객체로 만들어야 한다.값의 가짓수가 많으면 이를 모두 만드는데 큰 비용이 필요하다.

BigInteger의 flipBit 메서드 (O(N))

BitSet의 flip 메서드 (O(1))

3-1. 흔히 쓰일 다단계 연산들을 예측이 될 때.

다단계 연산 속도를 높여주는 가변 동반클래스를 package-privated으로 둔다.

BigInteger는 연산 속도를 높여주는 동반 클래스로 package-privated로 되어있는 MutableBigInteger / SignedMutableBigInteger / BitSieve를 사용할 것으로 예상

 

3-2. 흔히 쓰일 다단계 연산들이 예측이 안될 때.

다단계 연산 속도를 높여주는 가변 동반클래스를 public 으로 둔다.


예시 ) String 클래스의 연산속도를 높이기 위함 : StringBuilder / StringBuffer

참고 : https://github.com/Java-Bom/ReadingRecord/issues/47

 

4. 불변클래스를 만드는 설계 방법

ㄱ. final 클래스 : 상속을 막는다.

ㄴ. 정적 팩터리를 제공하는 방법

모든 생성자를 private 혹은 package-private으로 만들고 public 정적 팩터리를 제공한다. (캐싱 기능 추가도 가능하다.) public이나 protected 생성자가 없으니 다른 패키지에서는 이 클래스를 확장하는게 불가능하기 때문이다.

[아이템 1] 생성자 대신 정적 팩터리 메서드를 고려하라

 

5. BigInteger, BigDecimal 설계시 주의점

이 두 클래스의 메서드가 모두 재정의할 수 있게 설계되어있다.
인수로 받은 객체가 '진짜'인지 확인하고, 이 인수들은 가변으로 가정하고 방어적으로 복사해서 사용해야한다.

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

참고 : https://github.com/Java-Bom/ReadingRecord/issues/48

 

6. 불변 객체 기준 완화

"어떤 메서드도 객체의 상태 중 외부에 비치는 값을 변경할 수 없다."
계산 비용이 큰 값을 나중에 계산하여 final 이 아닌 필드에 캐싱해둔다.

실제로 String 클래스는 불변객체이지만, final이 아닌 필드 hash를 이용해 캐시하여 hashcode 재 연산 비용을 줄인다.
[아이템 83] 지연초기화는 신중히 사용하라

참고 : https://github.com/Java-Bom/ReadingRecord/issues/51

 

7. 정리

  • 클래스가 꼭 필요한 경우가 아니면 불변이어야 한다. (getter가 있다고 해서 setter를 무조껀 만들지는 말자)
  • 무거운 값객체의 경우 성능때문에 어쩔수 없다면, 불변 클래스와 쌍을 이루는 가변 동반 클래스를 public 클래스로 제공하자.
    [아이템 67] 최적화는 신중히 하라
  • 불변으로 만들 수 없는 클래스라도 변경할 수 있는 부분을 최소한으로 줄이자
  • 다른 합당한 이유가 없다면 모든 필드는 private final이어야 한다.
  • 생성자는 불변식 설정이 모두 완료된, 초기화가 완벽히 끝난 상태의 객체를 생성해야한다.

댓글

Comments

Dev Book Review/Effective Java

[Effective Java] Item16. public 클래스에서는 public 필드가 아닌 접근자 메서드를 사용하라

1. public 클래스의 가변 필드 절대 가변 필드를 public으로 노출하면 안된다. 캡슐화의 이점을 제공하지 못한다. API를 수정하지 않고는 내부 표현을 바꿀 수 없다. 불변식을 보장할 수 없다. 외부에서 필드에 접근할 때 부수 작업을 수행할 수도 없다. 패키지 바깥에서 접근할 수 있는 클래스라면 접근자(getter)를 제공하자. 클래스 내부 표현 방식을 언제든 바꿀 수 있다. package-private 클래스, private 중첩 클래스 는 데이터 필드를 노출해도 하등의 문제가 없다. package-private : 패키지 바깥 코드를 손대지 않고 데이터 표현 방식을 바꿀 수 있다. private 중첩 : 이 클래스를 포함하는 외부 클래스까지로 제한한다. 2. public 클래스의 불변 필드 ..

[Effective Java] Item16. public 클래스에서는 public 필드가 아닌 접근자 메서드를 사용하라

728x90

1. public 클래스의 가변 필드

절대 가변 필드를 public으로 노출하면 안된다.

  • 캡슐화의 이점을 제공하지 못한다.
  • API를 수정하지 않고는 내부 표현을 바꿀 수 없다.
  • 불변식을 보장할 수 없다.
  • 외부에서 필드에 접근할 때 부수 작업을 수행할 수도 없다.

패키지 바깥에서 접근할 수 있는 클래스라면 접근자(getter)를 제공하자.
클래스 내부 표현 방식을 언제든 바꿀 수 있다.

package-private 클래스, private 중첩 클래스 는 데이터 필드를 노출해도 하등의 문제가 없다.

  • package-private : 패키지 바깥 코드를 손대지 않고 데이터 표현 방식을 바꿀 수 있다.
  • private 중첩 : 이 클래스를 포함하는 외부 클래스까지로 제한한다.

 

2. public 클래스의 불변 필드

직접 노출할 때의 단점이 조금 줄어들지만 여전히 좋은 생각은 아니다.

단1. API를 변경하지 않고는 표현방식을 바꿀 수 없다.
단2. 필드를 읽을 때 부수적인 작업을 수행할 수 없다.

장1. 불변식을 보장할 수 있다.

댓글

Comments

Dev Book Review/Effective Java

[Effective Java] item 15. 클래스와 멤버의 접근 권한을 최소화하라

잘 설계된 컴포넌트 : 클래스 내부 데이터와 내부 구현정보를 외부 컴포넌트로부터 얼마나 잘 숨겼는가 오직 API를 통해서만 다른 컴포넌트와 소통한다. [정보은닉, 캡슐화] 1. 정보 은닉의 장점 시스템 개발 속도를 높인다 - 여러 컴포넌트 병렬 개발 시스템 관리 비용을 낮춘다 - 컴포넌트 교체 비용 하락 성능 최적화에 도움이 된다 - 완성된 시스템을 프로파일리하고 최적화할 컴포넌트만 가능하다. [아이템 67 - 최적화는 신중히 하라] 소프트웨어 재사용성을 높인다 - 의존성이 낮은 컴포넌트라면 다른 환경에서도 유용 큰 시스템을 제작하는 난이도를 낮춘다 - 개별 컴포넌트 동작 검증 2. 정보 은닉 기본원칙 모든 클래스와 멤버의 접근성을 가능한 한 좁힌다 (최대한 public을 지양하는 듯) 접근성 : 요소가..

[Effective Java] item 15. 클래스와 멤버의 접근 권한을 최소화하라

728x90

잘 설계된 컴포넌트 : 클래스 내부 데이터와 내부 구현정보를 외부 컴포넌트로부터 얼마나 잘 숨겼는가
오직 API를 통해서만 다른 컴포넌트와 소통한다. [정보은닉, 캡슐화]

 

1. 정보 은닉의 장점

  1. 시스템 개발 속도를 높인다 - 여러 컴포넌트 병렬 개발
  2. 시스템 관리 비용을 낮춘다 - 컴포넌트 교체 비용 하락
  3. 성능 최적화에 도움이 된다 - 완성된 시스템을 프로파일리하고 최적화할 컴포넌트만 가능하다.
    [아이템 67 - 최적화는 신중히 하라]
  4. 소프트웨어 재사용성을 높인다 - 의존성이 낮은 컴포넌트라면 다른 환경에서도 유용
  5. 큰 시스템을 제작하는 난이도를 낮춘다 - 개별 컴포넌트 동작 검증

 

2. 정보 은닉 기본원칙

모든 클래스와 멤버의 접근성을 가능한 한 좁힌다 (최대한 public을 지양하는 듯)
접근성 : 요소가 선언된 위치 + 접근 제한자 (private, protected, public)

 

ㄱ. 톱레벨 클래스, 인터페이스

public : 공개 api / package-private : 패키지 안에서만 사용

package-private : 클라이언트에 피해 없이 다음 릴리즈에서 수정,교체,제거 가능

 

ㄴ. private static 중첩 클래스

한 클래스에서만 사용하는 package-private 클래스의 경우에는 private staticd으로 중첩시키자
[아이템 24 - 멤버클래스는 되도록 static으로 만들어라]

 

ㄷ. 멤버필드

private / package-private / protected / public

  1. private 으로 만들기
  2. package-private으로 멤버 접근 수준 풀기
  3. 권한 풀기가 잦다 : 컴포넌트 분해 여부를 좀 더 고민한다.
    Serializabl을 구현한 클래스에서는 의도치 않게 (package-private, private 멤버가) 공개 API가 될 수 있으니 조심
  4. protected로 멤버 접근 수준 풀기

Public 클래스의 protected 멤버는 공개 API이므로 주의
[아이템 19 - 상속을 고려해 설계하고 문서화하라. 그러지 않았다먼 상속을 금지하라]

 

3. 멤버 접근성 제약

상위클래스의 메서드를 재 정의할 때 상위보다 좁게 설정을 할 수 없다.

[아이템10 - equals는 일반 규약을 지켜 재정의하라] : 리스코프 치환원칙 (상위클래스 인스턴스는, 하위클래스 인스턴스로 대체가 가능해야한다.)

 

4. 주의점

코드 테스트만을 위해 클래스, 인터페이스, 멤버를 공개 API로 만들면 안된다.

 

ㄱ. public 클래스의 인스턴스 필드는 되도록 public이 아니어야한다.

[아이템 16 - public 클래스에서는 public 필드가 아닌 접근자 메서드를 사용하라]

필드와 관련된 모든 것의 불변성을 잃기 때문이다.

public 가변 필드를 갖는 클래스는 스레드 안전하지 않다.

심지어 필드가 final이면서 불변 객체를 참조하더라도 문제는 여전히 남는다. 내부 구현을 바꾸고 싶어도 그 public 필드를 없애는 방식으로는 리팩터링 할 수 없다

헷갈렸지만 : 어찌됐든 public필드일 경우는 필드를 없애는 방식의 리팩터링이 안되서 쓰지 말라는 내용으로 이해함

정적필드도 마찬가지이다.

 

ㄴ. 정적필드에서의 public 필드 예외

public static final 필드 공개는 허용한다. 이런 필드는 반드시 기본 타입 값이나 불변 객체를 참조해야 한다.
[아이템 68 - 일반적으로 통용되는 명명 규칙을 따르라]
[아이템 17 - 변경 가능성을 최소화하라]

 

ㄷ. 클래스에서 public static final 배열필드를 두거나 이 필드를 반환하는 접근자 메서드를 제공해서는 안된다.

클라이언트에서 배열의 내용을 수정할 수 있기 때문이다.

[권장 방법]

1. public 불변리스트를 추가한다.

private static final Thing[] PRIVATE_VALUES = {...};
public static final List<Thing> VALUES = Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES));

2. 복사본을 반환하는 public 메서드를 추가한다 : 방어적 복사

private static final Thing[] PRIVATE_VALUES = {...};
public static final Thing[] values(){
  return PRIVATE_VALUES.clone();
}

타입, 성능에 따라서 사용하자.

 

5. Java 9 : 모듈 시스템 개념의 도입

패키지 : 클래스의 묶음 // 모듈 : 패키지의 묶음

모듈 : 자신에 속하는 패키지 중 공개 (export) 할 것들을 선언한다. [module-info.java 파일에]

클래스를 외부에 공개하지 않으면서도 같은 모듈을 이루는 패키지 사이에서 자유롭게 공유할 수 있다.

암묵적 접근 수준 : public, protected 수준의 효과가 모듈 내부로 한정된다.

 

ㄱ. 모듈에 적용되는 새로운 두 접근 수준은 주의해야한다.

모듈의 jar파일을 모듈 경로가 아니라 애플리케이션 클래스 패스에 두면, 모듈안 패키지가 모듈이 없는 것 처럼 행동한다.

일반 클래스 파일이 있는 것처럼 행동한다 : 모듈 시스템 암묵적 접근 수준이 해제된다

JDK : 자바라이브러리에서 공개하지 않은 패키지는 모듈 밖에서 접근 할 수 없다.

 

ㄴ. 모듈의 장점을 누리기 위한 조치

  • 패키지를 모듈 단위로 묶는다.
  • 모듈 선언에 패키지들의 의존성을 명시한다.
  • 소스트리 재배치
  • 모듈 안으로 부터 (모듈 시스템을 적용하지 않는) 일반 패키지로의 모든 접근에 특별한 조치를 취해야한다.

그러나 모듈의 개념은 아직은 사용하지 않는게 좋은 것 같다.

댓글

Comments

Dev Book Review/Clean Code

CleanCode 2장 의미 있는 이름

1. 의도를 분명이 밝혀라 변수나 함수 클래스 이름은 의도가 분명한 이름이어야한다. 변수(함수, 클래스)의 존재 이유 변수(함수, 클래스)의 수행 기능 변수(함수, 클래스)의 사용 방법 // 변경 전 int d; //경과 시간 (단위: 날짜); // 변경 후 int daysSinceCreation; 코드의 단순성이 아닌 코드의 함축성을 고려해야한다 -> 코드 맥락을 코드 자체에 명시적으로 드러나야한다. 코드 맥락 정보 제공 방법 : 개념에 이름을 붙인다 // 변경 전 public List getThem(){ List list1 = new ArrayList(); for(int[] x : the List) if(x[0] == 4) list1.add(x); return list1; } /* 변경 후 - 각 칸..

CleanCode 2장 의미 있는 이름

728x90

1. 의도를 분명이 밝혀라

변수나 함수 클래스 이름은 의도가 분명한 이름이어야한다.

  • 변수(함수, 클래스)의 존재 이유
  • 변수(함수, 클래스)의 수행 기능
  • 변수(함수, 클래스)의 사용 방법
// 변경 전
int d; //경과 시간 (단위: 날짜);

// 변경 후
int daysSinceCreation;

코드의 단순성이 아닌 코드의 함축성을 고려해야한다 -> 코드 맥락을 코드 자체에 명시적으로 드러나야한다.
코드 맥락 정보 제공 방법 : 개념에 이름을 붙인다

// 변경 전
public List<int[]> getThem(){
  List<int[]> list1 = new ArrayList<int[]>();
  for(int[] x : the List)
    if(x[0] == 4)
      list1.add(x);
  return list1;
}

/* 변경 후
- 각 칸을 Cell 클래스로 표현
- Cell 필드 값 flagged 확인 메서드로 변경
*/
public List<Cell> getFlaggedCells(){
  List<Cell> flaggedCells = new ArrayList<Cell>();
  for(Cell cell: gameBoard)
    if(cell.isFlagged())
      flaggedCells.add(cell);
  return flaggedCells;
}

 

2. 그릇된 정보를 피하라

여러 계정을 그룹으로 묶을 때, 실제 List가 아니면 accountList라 명명하지 않는다.

  • accountGroup, bunchOfAccounts, Accounts

서로 흡사한 이름을 사용하지 않도록한다.

  • XYZControllerForEfficientHandlingOfStrings <-> XYZControllerForEfficientStorageOfStrings

이름만 보고 객체를 선택할 수 있게, 유사한 개념은 유사한 표기법을 사용해 일관성을 준다.
주의 ) l O 1 0 조심히 사용하기!

 

3. 의미있게 구분해라

연속된 숫자를 덧붙인 이름은 아무런 정보를 제공하지 못한다.

public static void copyChars(char a1[], char a2[]){ // source, destination으로 고치자
  for (int i =0; i < a1.length; i++){
    a2[i] = a1[i];
  }
}

불용어를 추가한 이름은 아무런 정보도 제공하지 않는다. 불용어는 중복이기 때문이다.

Product; ProductInfo; ProductData // 의미가 불분명하다.
NameString; Name;    
Customer; CustomerObejct;     // 차이를 알 수가 없다.

읽는 사람이 차이를 알 수 있도록 이름을 지어라

moneyAmount; money;
customerInfo; customer;
accountData; account;
theMessage; message;

 

4. 발음하기 쉬운 이름을 사용하라

발음하기 쉬운 단어를 사용할 때 대화가 편해진다.

// 변경 전
private Date genymdhms;
private Date modymdhms;
private final String pszqint = "102";

// 변경 후
private Date generationTimeStamp;
private Date modificationTimeStamp;
private final String recordId = "102";

 

5. 검색하기 쉬운 이름을 사용하라

문자 하나를 사용하는 이름이나 상수는 검색이 어렵다.

검색의 관점에서 긴 이름이 짧은 이름보다 좋다.
간단한 메서드의 로컬변수 : 한 문자 (이름 길이는 범위 크기에 비례)

상수의 의미를 나타내는 변수로 바꾸어주자.

// 변경 전
for(int j=0; j>34; j++){
  s += (t[j]*4)/5;
}

// 변경 후 : 검색도 가능하고, 이름의 의미를 쫒아 코드 파악이 가능하다.
int realDaysPerIdealDay = 4;
const int WORK_DAYS_PER_WEEK = 5;
int sum = 0;
for(int j=0; j < NUMBER_OF_TASKS; j++){
  int realTaskDays = taskEstimate[j] * realDaysPerIdealDay;
  int realTaskWeeks = (realTaskDays / WORK_DAYS_PER_WEEK);
  sum += realTaskWeeks;
}

 

6. 인코딩을 피하라

6-1. 헝가리식 표기법

이전에 컴파일러가 타입을 점검하지 않아 타입을 기억할 단서로 헝가리식 표기법을 사용했다.
그러나 지금은 변수 이름에 타입을 인코딩할 필요가 없다.

// 헝가리식 표기법
PhoneNumber phoneString;    // 타입 바뀌어도 이름은 안 바뀐다.
// 헝가리식 표기법 적용 X
PhoneNumber phonenumber;

 

6-2. 멤버변수 접두어

접두어를 무시하고 이름을 해독하자. 멤버 변수를 눈에 띄게 보여주는 IDE를 사용하자.

// 멤버변수를 의미하는 접두어 m_
public class Part{
  private String m_dsc;  
}

// 접두어가 필요없을 정도로 작아야한다.
public class Part{
  private String description;
}

 

6-3. 인터페이스 클래스와 구현 클래스

인코딩이 필요한 경우가 있다.

  • 인터페이스 클래스 : ShapeFactory.class
  • 구현 클래스 : ShapeFactoryImpl.class

 

7. 자신의 기억력을 자랑하지 마라

문자 하나만 사용하는 변수이름은 문제가 있다. (루프에서 반복횟수를 세는 변수는 괜찮다.)

전문가 프로그래머는 명료함이 최고다 : 남들이 이해하는 코드를 내놓는다.
URL에서 r이라는 변수를 과시하는 것처럼 기억력을 과시하지말자.

 

8. 클래스와 메서드 이름

클래스 이름 : 명사구

Customer, WikiPage, Account, AddressParser

Manager, Processor, Data, Info 단어는 피하자.

메서드 이름 : 동사나 동사구

postPaymet, deletePage, save
  • 접근자 (Accessor) : getVerb();
  • 변경자 (Mutator) : setVerb();
  • 조건자 (Predicate) : isVerb();

생성자 중복정의 : 정적 팩토리 메서드 - 인수를 설명한다.

Complex fulcrumPoint = Complex.FromRealNumber(23.0);
  • 생성자 사용 제한을 위해 private으로 생성자를 선언한다.

 

9. 기발한 이름은 피하라

재미난 이름보단 명료한 이름 HolyHandGrenade -> DeleteItems

구어체나 속어의 이름보단 의도를 분명하게 하는 이름 whack() -> kill()

 

10. 한 개념에 한 단어 & 한 단어 한 목적

메서드 이름은 문맥에 독자적이고 일관적이어야한다.
추상적인 개념 하나에 여러 단어를 선택하지 말자

// 어떤 클래스에서 어떤 걸 사용했는지 혼란스럽지 않게 일관성을 유지한다.
fetch, retrieve, get    
controller, manager, driver

한 단어를 두 가지 목적으로 사용하지 마라
같은 맥락일 경우에만 '일관성'을 고려하자.

// 기존 : 기존 값 두개를 더하거나 이어서 새로운 값 만듬
add();
// 새로운 것 : 집합에 값 하나 추가 (다른 맥락)
insert(); append()

 

11. 해법 영역 & 문제 영역에서 가져온 이름을 사용하라

해법 영역 (Solution Domain) : 개발자라면 당연히 알고 있을 전산용어, 알고리즘 이름, 패턴 이름, 수학 용어 등은 사용하자.

JobQueue, AccountVisitor(Visitor pattern)

문제 영역 (Problem Domain) : 실제 도메인의 전문가에게 의미를 물어 파악할 수 있도록 문제 영역에서 이름을 가져오자.

 

12. 의미있는 맥락을 추가하라 & 불필요한 맥락을 없애라

의미가 분명한 이름이 있게 하자 : 클래스, 함수, 이름 공간에 넣어 맥락을 부여한다.

// 하나만 사용하면 주소 관련 변수임을 모른다.
String firstName, lastName, street, houseNumber, city, state, zipcode;

// 접두어를 사용하면 맥락이 좀 더 분명해진다.
String addrFirstName, addrLastName, addrStreet, addrHouseNumber, addrCity, addrState, addrZipcode;

// 클래스를 생성 : 변수가 큰 개념임이 컴파일러에도 분명해진다.
class Address{
  String addrFirstName;
  String addrLastName;
  String addrStreet;
  String addrHouseNumber;
  String addrCity;
  String addrState;
  String addrZipcode
}

맥락을 개선하여 클래스로 만들면 함수를 쪼개기도 쉬워진다.

의미가 분명하다면 일반적으로 짧은 이름이 긴 이름보다 좋다 : 불필요한 맥락을 추가하지 말자.

  • 고급 휘발유 충전소 (Gas Station Deluxe) 애플리케이션을 짠다고, 모든 클래스 이름을 GSD로 시작하지 말자
  • accountAddress, customerAddress : 클래스 이름 X, 인스턴스 이름 O
  • PostalAddress, MAC, URI : 클래스 이름 O

'Dev Book Review > Clean Code' 카테고리의 다른 글

CleanCode 1장 깨끗한 코드  (0) 2020.04.28

댓글

Comments

Dev Book Review/Clean Code

CleanCode 1장 깨끗한 코드

1. 코드가 존재하리라 코드는 요구사항을 표현하는 언어이다 언어는 요구사항에 가깝게한다 요구사항에서 정형구조를 뽑아낸다. 코드의 도움 없이 요구사항을 상세히 표현하기는 불가능하다 : 코드는 정밀한 표현이다. 고도로 추상화된 언어나 특정 응용 분야 언어로 기술하는 명세도 코드이다. 프로그래밍 언어에서 추상화 수준은 점차 높아질 것이다. 2. 나쁜 코드 우리 모두는 좋은 코드가 중요하다는 사실을 안다. 회사가 망한 원인 -> 나쁜 코드 버그가 남아있고, 프로그램이 죽는 횟수가 늘어짐 출시에 바빠 코드를 마음대로 짜고, 기능을 추가할 수록 엉망이 되었다. 고행(wading) = 나쁜 코드를 헤쳐나간다 르블랑의 법칙(Leblanc's Law) = 나중은 결코 오지 않는다. 나중에 손보겠다고 한 코드 + 돌아간다..

CleanCode 1장 깨끗한 코드

728x90

1. 코드가 존재하리라

코드는 요구사항을 표현하는 언어이다

  • 언어는 요구사항에 가깝게한다
  • 요구사항에서 정형구조를 뽑아낸다.

코드의 도움 없이 요구사항을 상세히 표현하기는 불가능하다 : 코드는 정밀한 표현이다.
고도로 추상화된 언어나 특정 응용 분야 언어로 기술하는 명세도 코드이다.

프로그래밍 언어에서 추상화 수준은 점차 높아질 것이다.

 

2. 나쁜 코드

우리 모두는 좋은 코드가 중요하다는 사실을 안다.

회사가 망한 원인 -> 나쁜 코드

  • 버그가 남아있고, 프로그램이 죽는 횟수가 늘어짐
  • 출시에 바빠 코드를 마음대로 짜고, 기능을 추가할 수록 엉망이 되었다.

고행(wading) = 나쁜 코드를 헤쳐나간다

르블랑의 법칙(Leblanc's Law) = 나중은 결코 오지 않는다.

  • 나중에 손보겠다고 한 코드 + 돌아간다는 사실에 안도감을 느끼며 위로함 -> 고치지 않는다.
  • 짤 때부터 클린하게 잘 짜보자

 

3. 나쁜 코드로 치르는 대가

개발속도를 크게 떨어뜨린다.
나쁜 속도가 쌓일수록 팀 생산성은 떨어진다. - 얽히고 설킨 코드를 '해독'하고 더한다.

 

3-1. 원대한 재설계의 꿈

깨끗한 코드를 만드는 노력이 비용을 절감할 뿐아니라 전문가로서 살아남는다.

원대한 재설계 : 타이거팀 - 기존 시스템 기능을 제공하는 새 시스템 + 새로운 변경 > (10년 지속) 이에 대한 새로운 재 설계도 나온다

 

3-2. 태도

좋은 코드를 사수하는 일은 프로그래머의 책임이다.

나쁜코드로 전략한 실패 -> 잘못은 프로그래머 책임이다.
요구사항 변경, 일정 촉박, 관리자 고객 마케팅에 대한 불평 : 프로그래머가 프로젝트 계획을 실패한 것 (전문성의 요소였다)

 

3-3. 원초적 난제

기한을 맞추는 방법 = 빨리가는 방법 = 언제나 코드를 최대한 깨끗하게 유지하는 습관
나쁜코드가 업무 속도를 늦춤을 안다.
그럼에도 기한을 맞추려고 나쁜코드를 짠다 = 빨리가려고 시간을 들이지않는다.

 

3-4. 깨끗한 코드라는 예술

깨끗한 코드 = 코드감각 & 절제와 규율

'코드감각'이 있는 프로그래머 = 나쁜 모듈을 보면 좋은 모듈로 개선할 방안을 떠올린다.

 

3-5. 깨끗한 코드란?

a. 비야네 스트롭스트룹 (Biarne Stroustrup)

  • '보기에 즐거운' 코드
  • 나쁜코드는 나쁜코드를 '유혹'한다 = 나쁜 코드를 고치면서 오히려 더 나쁜 코드를 만든다.
    깨진 창문 ) 창문이 일단 깨지고 나면 쇠퇴하는 과정이 시작된다
  • 세세한 사항까지 꼼꼼하게 처리해야한다. (세세한 오류처리)
    메모리 누수, 경쟁상태(race condition), 일관성 없는 명명법
  • 한가지 목적에 '집중'한다

b. 그래디 부치 (Grady Booch)

  • 가독성을 강조 = 깨끗한 코드가 잘 쓴 문장처럼 읽혀야한다.
  • 코드는 추측이 아니라 사실에 기반해야한다. 필요한 내용만 담아야한다 (명쾌한 추상화)

 

c. '큰(big)' 데이브 토마스 (Dave Thomas)

  • 깨끗한 코드란 다른 사람이 고치기 쉽다
  • 아무리 코드가 우아하고, 가독성이 높아도, 테스트 케이스가 없으면 깨끗하지 않다.
  • 큰 코드보다 작은 코드에 가치를 둔다.
  • 코드가 '문학적'이어야한다 - 인간이 읽기 좋은 코드를 작성하라

 

d. 마이플 페더스 (Michael Feathres)

  • 깨끗한 코드는 시간을 들여 깔금하고 단정하게 정리한 코드다 = 주의를 기울인 코드다.

 

e. 론 제프리스 (Ron Jeffries)

  • 모든 테스트를 통과한다
  • 중복이 없다.
  • 시스템 내 모든 설계 아이디어를 표현한다.
  • 클래스, 메서드, 함수 등을 최대한 줄인다.
    여러 객체로 나누기 : 한 객체가 여러 기능을 수행할 때
    메서드 추출 : 기능을 명확히 기술하는 메서드 하나 + 기능을 실제로 수행하는 메서드 여러개
  • 집합에서 특정 항목을 찾는다 : 추상메서드나 추상클래스를 만들어 실제 구현을 감싼다

 

f. 워드 커닝햄 (Ward Cunningham)

  • 읽으면서 짐작한 대로 돌아가는 코드가 깨끗한 코드다.
  • 코드는 그 문제를 풀기위한 언어처럼 보일때 아름다운 코드다
    언어를 단순해 보이게 만드는 열쇠는 프로그래머

 

4. 우리들 생각 & 우리는 저자다

오브젝트 멘토 진영 [Robert C Martin] 이 생각하는 깨끗한 코드를 설명한다 : 깨끗한 변수 이름, 깨끗한 함수, 깨끗한 클래스

새 코드를 짜면서 우리는 끊임없이 기존 코드를 읽는다 [ 10 : 1 = 읽는 시간 : 짜는 시간 ]

읽기쉬운 코드가 매우 중요하다.

 

5. 보이스카우트 규칙

체크아웃할 때보다 좀 더 깨끗한 코드를 체크인 하자.
캠프장은 처음 왔을 때보다 더 깨끗하게 해놓고 떠나라

시간이 지날수록 코드가 좋아지는 프로젝트를 작업하자

 

6. 객체 지향 설계의 다섯가지 원칙

SRP (The Single Reponsibility Principle) : 단일 책임의 원칙
클래스에는 한 가지, 단 한 가지 변경 이유만 존재해야 한다.

OCP (The Open Closed Principle) : 개방 폐쇄의 원칙
클래스는 확장에 열려있어야하며, 변경에 닫혀있어야 한다.

LSP (The Liskov Substitution Principle) : 리스코프의 치환 법칙
상속받은 클래스는 기초 클래스를 대체할 수 있어야 한다.

DIP (The Dependency Inversion Principle) : 의존 역전의 법칙
추상화에 의존해야하며, 구체화에 의존하면 안된다.

ISP (The Interface Segregation Principle) : 인터페이스 분리 원칙
클라이언트에 밀접하게 작게 쪼개진 인터페이스를 유지한다.

'Dev Book Review > Clean Code' 카테고리의 다른 글

CleanCode 2장 의미 있는 이름  (0) 2020.04.29

댓글

Comments

Dev Book Review/Effective Java

[Effective Java] Chapter 3: 모든 객체의 공통 메서드

Item10. equals는 일반 규약을 지켜 재정의하라 필요한 경우가 아니라면 equals를 재정의 하지말자. 많은 경우에 Object의 equals가 우리가 원하는 비교를 정확히 수행해준다. 재정의해야 할 때는 그 클래스의 핵심필드 모두를 빠짐없이 5가지 규약을 지켜가며 비교해야한다. equals의 5가지 규약 : 반사성, 대칭성, 추이성, 일관성, null-아님 Link : https://jyami.tistory.com/66 item10. equals는 일반 규약을 지켜 재 정의하라 1. equals를 재정의 하면 안되는 경우 equals는 재정의하기 쉬워보이지만 곳곳에 함정이 있다. 문제를 회피하는 가장 쉬운 길은 아예 재정의하지 않는 것 a. 각 인스턴스가 본질적으로 고유할 때 값 표현 객체가...

[Effective Java] Chapter 3: 모든 객체의 공통 메서드

728x90

Item10. equals는 일반 규약을 지켜 재정의하라

  • 필요한 경우가 아니라면 equals를 재정의 하지말자.
  • 많은 경우에 Object의 equals가 우리가 원하는 비교를 정확히 수행해준다.
  • 재정의해야 할 때는 그 클래스의 핵심필드 모두를 빠짐없이 5가지 규약을 지켜가며 비교해야한다.
  • equals의 5가지 규약 : 반사성, 대칭성, 추이성, 일관성, null-아님
  • Link : https://jyami.tistory.com/66
 

item10. equals는 일반 규약을 지켜 재 정의하라

1. equals를 재정의 하면 안되는 경우 equals는 재정의하기 쉬워보이지만 곳곳에 함정이 있다. 문제를 회피하는 가장 쉬운 길은 아예 재정의하지 않는 것 a. 각 인스턴스가 본질적으로 고유할 때 값 표현 객체가..

jyami.tistory.com

 

 

Item11. equals를 재정의하려거든 hashCode도 재정의하라

  • equals를 재정의할 때는 hashCode도 반드시 재정의해야 한다. (프로그램이 제대로 동작해야하므로)
  • 재정의한 hashCode는 Object의 API 문서에 기술된 일반 규약을 따라야한다.
  • 3번에 써둔 좋은 HashCode를 작성하는 방법을 참고하자
  • 서로 다른 인스턴스라면 되도록 해시코드도 서로 다르게 구현해야한다.
  • AutoValue 프레임워크를 사용하면 멋진 equals와 hashCode를 자동으로 만들어준다.
  • Link : https://jyami.tistory.com/67
 

Item11. equals를 재정의하려거든 hashCode도 재정의하라

equals를 재정의한 클래스 모두에서 hasCode도 재정의해야한다. 일반 규약을 어기게 되어 HashMap이나 HashSet 같은 컬렉션 원소로 사용할 때 문제를 일으킬 수 있다. 1. Object 명세의 3가지 규약 equals() 에 사..

jyami.tistory.com

 

Item12. toString을 항상 재정의하라

  • 모든 구체 클래스에서 Object의 toString을 재정의하자.
  • 상위 클래스에서 이미 알맞게 재정의한 경우에는 예외다.
  • toString을 재정의한 클래스는 사용하기도 즐겁고 그 클래스를 사용한 시스템 디버깅에 용이하다.
  • toString은 해당 객체에 관한 명확하고 유용한 정보를 읽기 좋게 반환해야한다.
  • Link : https://jyami.tistory.com/68
 

Item12. toString을 항상 재정의하라

Object.toString() 메서드 : [클래스이름]@[16진수로 표시한 해시코드] 포맷을 갖는다. 예시 ) PhnoneNumber@adbbd 1. toString() 규약 1) 간결하면서 사람이 읽기 쉬운 형태의 유익한 정보 2) 모든 하위 클래스에..

jyami.tistory.com

 

Item13. clone 재정의는 주의해서 진행하라

  • Cloneable이 몰고 온 모든 문제를 되짚어봤을 때, 새로운 인터페이스를 만들 때는 절대 Cloneable을 확장해서는 안되며, 새로운 클래스도 이를 구현해서는 안된다.
  • final 클래스라면 Clonealbe을 구현해도 위험이 크지 않지만, 성능 최적화 관점에서 검토후 별다른 문제가 없을 대만 드물게 허용하자
  • 기본 원칙은 '복제 기능은 생성자와 팩터리를 이용하는게 최고'라는 것이다.
  • 단, 배열만은 clone 메서드 방식이 가장 깔끔한, 이 규칙의 합당한 예외라 할 수 있다.
  • Link : https://jyami.tistory.com/69
 

item13. clone 재정의는 주의해서 진행하라

1. Cloneable interface cloneable : 복제해도 되는 클래스임을 명시하는 용도의 믹스인 인터페이스이다. 믹스인 : 클래스가 자신의 "본래 타입"에 추가하여 구현할 수 있는 타입. 선택 가능한 기능을 제공하며,..

jyami.tistory.com

 

Item14. Comparable을 구현할지 고려하라

  • 순서를 고려해야하는 값 클래스를 작성한다면 꼭 Comparable 인터페이스를 구현하여, 그 인스턴스들을 쉽게 정렬하고, 검색하고, 비교 기능을 제공하는 컬렉션과 어우러지도록 해야 한다.
  • compareTo 메서드에서 필드의 값을 비교할 때 < 와 > 연산자는 쓰지 말자.
  • 그대신 박싱된 기본 타입 클래스가 제공하는 정적 compare 메서드나, Comparator 인터페이스가 제공하는 비교자 생성 메서드를 사용하자
  • Link : https://jyami.tistory.com/70
 

Item 14. Comparable을 구현할지 고려하라

1. compareTo()와 equals()의 차이 compareTo는 Object의 메서드가 아니다. 성격은 두가지만 빼면 Object의 equals와 같다. compareTo는 단순 동치성 비교에 더해 순서까지 비교 가능하다. 그 클래스의 인스턴스들..

jyami.tistory.com

 

댓글

Comments

Dev Book Review/Effective Java

[Effective Java] item 14. Comparable을 구현할지 고려하라

1. compareTo()와 equals()의 차이 compareTo는 Object의 메서드가 아니다. 성격은 두가지만 빼면 Object의 equals와 같다. compareTo는 단순 동치성 비교에 더해 순서까지 비교 가능하다. 그 클래스의 인스턴스들에 자연적인 순서가 있음을 뜻할 수 있다. 그래서 Comparable을 구현한 객체들의 배열은 손쉬운 정렬이 가능하다. 알파벳, 숫자, 연대 등 순서가 명확한 값 클래스 작성할땐 구현하자. 2. CompareTo() 메서드 규약 equals와 같은 내용이다 (주의점, 우회법 모두 같다.) this object : 1 ㄱ. 반사성, 대칭성, 추이성을 충족해야한다 ● sgn(x.compar..

[Effective Java] item 14. Comparable을 구현할지 고려하라

728x90

1. compareTo()와 equals()의 차이

compareTo는 Object의 메서드가 아니다.

성격은 두가지만 빼면 Object의 equals와 같다.

  • compareTo는 단순 동치성 비교에 더해 순서까지 비교 가능하다.
  • 그 클래스의 인스턴스들에 자연적인 순서가 있음을 뜻할 수 있다.

그래서 Comparable을 구현한 객체들의 배열은 손쉬운 정렬이 가능하다.
알파벳, 숫자, 연대 등 순서가 명확한 값 클래스 작성할땐 구현하자.

 

2. CompareTo() 메서드 규약

equals와 같은 내용이다 (주의점, 우회법 모두 같다.)

this < object : -1
this == object : 0
this > object : 1

 

ㄱ. 반사성, 대칭성, 추이성을 충족해야한다

sgn(x.compareTo(y) == -sgn(y.compareTo(x))

x.compareTo(y)y.compareTo(x)가 예외를 던질 때에 한해 예외를 던진다.
두 객체 참조의 순서를 바꾸어 비교해보아도 예상한 결과가 나와야한다.

x.compareTo(y) > 0 && y.compareTo(z) > 0이면 x.compareTo(z) > 0 이다.

첫번째가 두번째보다 크고 두번째가 세번째보다 크면, 첫번째는 세번째보다 크다.

 Comparalbe을 구현한 클래스는 모든 z에 대해 x.compareTo(y) == 0 이면 sgn(x.compareTo(z)) == sgn(y.compareTo(z)) 이다.

크기가 같은 객체들끼리 어떤 객체와 비교해도 항상 같아야한다.

(x.compareTo(y) == 0) == (x.equals(y)) 여야 한다.

compareTo로 수행한 동치성 테스트의 결과가 equals와 같아야한다.
지키지 않을 때는 명시해야한다. "주의 : 이 클래스의 순서는 equals 메서드와 일관되지 않는다."

컬렉션 구현 인터페이스(Collection, Set, Map) - 구현에 따른 주의가 필요하기 때문이다.
: equals 메서드 규약을 따른다 되어있다.
: 정렬된 컬렉션들은 동치성 비교시 equals대신 compareTo 사용

 

ㄴ. 기존 클래스를 확장한 구체클래스에서 새로운 값 컴포넌트를 추가하면 compareTo 지킬 방법이 없다.

우회법 : 컴포지션을 사용하고 + '뷰' 메서드를 제공하자

 

3. compareTo 메서드 작성요령

equals와의 차이점만 주의하면 된다.

 Comparable은 타입을 인수로 받는 제네릭 인터페이스이다 : 메서드의 인수타입은 컴파일타임에 정해진다.

 compareTo 메서드는 필드의 동치가 아니라 순서를 비교한다.Comparable을 구현하지 않았다면, Comparator를 사용할 수 있다.

 compareTo 메서드 구현시 관계연산자 <, > 사용하는 방식은 거추장 스럽고 오류를 유발한다. [Java7]

// 아래 방법을 사용하자.
Integer.compare(a,b);
Float.compare(a,b);
Double.compare(a,b);

   클래스의 핵심필드 여러개중 어떤것을 먼저 비교할 지에 대해 집중하라

 비교자 생성 메서드(comparator construction method)와 팀을 꾸려 메서드 연쇄로 비교자를 생성. [Java 8]

private static final Comparator<PhoneNumber> COMPARATOR =
  comparingInt((PhoneNumber pn)->pn.areaCode)	// Comparator의 인스턴스 메서드
  	.thenComparingInt(pn -> pn.prefix)			// 원하는 만큼 연달아 호출 가능
		.thenComparingInt(pn -> pn.lineNumber);

public int compareTo(PhoneNumber pn){
  return COMPARATOR.compare(this, pn);
}

    이 람다에서 입력 인수의 타입을 명시해 주었다. (프로그램 컴파일을 도와준 것과 같다.)

 Comparator의 보조 생성 메서드

  comparingLong, thenComparingLong
  comparingDouble, thenComparingDouble

 값의 차를 이용한 compareTo, compare 메서드를 사용하지 말자.

  정수 오버플로 / 부동 소수점 계산 방식 오류  
  개선 : Integer.compare(a,b); || Comparator.comparingInt(x -> x.hashCode())

댓글

Comments