대부분의 메서드와 생성자는 인자로 사용할 수 있는 값을 제한한다. 이런 제한들은 반드시 문서로 남겨야 할 뿐 아니라 메서드 시작 부분에서 검사해야 한다. 오류는 가급적 빨리 탐지해야 한다는 일반 원칙의 특수한 경우이다.


 이런 오류를 생략한다면 어떻게 될까? 

  • 첫 째, 처리 도중에 이상한 예외를 내면서 죽어버리는 경우. 
  • 둘 째, 실행이 제대로 되는 것 같은데 잘못된 결과가 나오는 것. 
  • 셋 째, 정상적으로 반환값을 내기는 하지만 어떤 객체의 상태가 비정상적으로 바뀌는 경우. 

위와 같은 골치아픈 상황을 보게 될 것이다. 그러나 메서드가 실제 계산을 수행하기 전에 그 인자를 반드시 검사해야 한다는 원칙에도 예외는 있다. 그 중 가장 중요한 것은 유효성 검사를 실행하는 오버헤드가 너무 크거나 비현실적이고, 계산 과정에서 유효성 검사가 자연스럽게 이루어지는 경우이다.  


 때로는 계산 과정에서 암묵적으로 유효성 검사가 이루어지기는 하는데, 검사가 실패했을 때 엉뚱한 예외가 던져지는 경우가 있다. 계산 도중에 인자 값이 잘 못되어 발생하는 예외가 메서드 문서에 명시된 예외가 다를 수 있다는 것인데, 이런 경우는 예외 변환을 통해 메서드 문서에 명시된 예외로 변환해야 한다.


 이번 절의 내용을 잘 못 받아 들여 "인자에 제약을 두는 것은 바람직하다" 라고 믿어 버리면 위험하다. 메서드는 가능하다면 일반적으로 적용할 수 있도록 설계해야 한다.  인자로 받는 모든 값에 대한 처리가 잘 되어있다면, 인자에 대한 제약은 적을수록 좋다.

요약

메서드나 생성자를 구현할 때는 받을 수 있는 인자에 제한이 있는지 따져봐야 한다. 제한이 있다면 그 사실을 문서에 남기고 메서드 앞부분에서 검사하도록 해야한다. 


표식 인터페이스는 아무 메서드도 선언하지 않는 인터페이스다. Serializable 인터페이스가 대표적인 예이다. 그런데, 표식 어노테이션을 쓰게 되면 표식 인터페이스는 필요없는 것이 아닌가?  표식 어노테이션과 비교했을 떄 표식 인터페이스에는 두 가지 장점이 있따. 첫 째, 표식인터페이스는 결국 표식 붙은 클래스가 만드는 객체들이 구현하는 자료형이라는 것이다. 표식 어노테이션은 자료형이 아니다. 자료형이라면, 런타임중에 발견될 오류를 컴파일 시점에 발견할 수 있다. 

 표식 인터페이스가 어노테이션보다 나은 점 두 번째는, 적용 범위를 좀 더 세밀하게 지정할 수 있다는 것이다. 그렇다면 표식 어노테이션의 주된 장점은 무엇일까? 프로그램 안에서 어노테이션 자료형을 쓰기 시작한 뒤에도 더 많은 정보를 추가할 수 있다는 것이다. 이에 반해 표식 인터페이스를 쓰는 경우에는 일단 구현이 이루어지고 난 다음에는 새로운 메서드를 추가하는 것이 일반적으로 불가능하기 때문이다. 인터페이스를 구현하는 모든 클래스에 메서드를 추가해야 하기 때문이다. 


 그렇다면 표식 어노테이션과 표식 인터페이스는 각각 어떤 상황에 걸맞나? 클래스나 인터페이스 이외에 프로그램 요소에 적용되어야 하는 표식은 어노테이션이 적절하다. 이 표식이 붙은 객체만 인자로 받을 수 있는 메서드를 만들 생각이라면, 표식 인터페이스를 써야 한다. 그러면 해당 메서드의인자 자료형으로 해당 인터페이스를 사용할 수 있어서 컴파일 시점에서 오류를 발견할 수 있다.


요약

표식 인터페이스와 표식 어노테이션은 쓰임새가 다르다. 새로운 메서드가 없는 자료형을 정의하고자 한다면 표식 인터페이스를 이용해야 한다. 클래스나 인터페이스 이외의 프로그램 요소에 표식을 달아야 하고, 앞으로 표식에 더 많은 정보를 추가할 가능성이 있다면 표식 어노테이션을 사용해야한다. 


 위 코드의 문제점과 결과값이 무엇이 나올지 예측이 되는가? 아마 결과값으로 26이 찍히기를 예상하겠지만 실제로는 260이 찍힌다. 미묘하게 숨어있는 한 가지 문제점 때문이다. 이 문제점은 바로 equals의 오버라이딩에 숨어있다. 프로그래머는 equals 메소드를 오버라이드 할 목적으로 작성했겠지만, equals의 인자를 보면 Object가 아니라 Bigram이다. 즉 , 오버로딩을 한 것이다. 따라서 equals는 Object에 구현되어 있는 equals를 재사용한 것이며, 따라서 결과값이 260이 나오게 된다. 

 다행인 것은 컴파일러가 이런 오류를 찾을 수 있도록 돕는다는 것인데, 이러한 도움을 받기 위해서는 메소드에 @Override 어노테이션을 추가해야 한다.

위와 같이 수정하면, 컴파일러는 오류 메세지를 출력하게 된다. 즉 상위 클래스에 선언된 메서드를 재정의할 때는 반드시 선언부에 Override 어노테이션을 붙혀야 한다는 것이다. 비 abstract클래스에서 abstract 메서드를 재정의할 때는 꼭 @Override 어노테이션을 사용할 필요는 없지만 명시적으로 알리고 싶을 때는 사용해도 무관하다.

하지만 추상 클래스나 인터페이스으 ㅣ경우에는 그 상위 클래스나 상위 인터페이스 메서드를 재정의하는 모든 메서드에, abstract 메서드건 아니건 어노테이션을 붙힐 필요가 있다.

요약

상위 자료형에 선언된 메서드를 재정의하는 모든 메서드에 Override 어노테이션을 붙이도록 하면 굉장히 많은 오류를 막을 수 있다. 예외적으로, 비-abstract 클래스에서 상위 클래스의 abstract 메서드를 재정의 할 때는 Override 어노테이션을 붙이지 않아도 된다.



도구나 프레임워크가 특별히 취급해야 하는 프로그램 요소를 구별하기 위해 작명 패턴을 사용했다. 이러한 작명 패턴에는 몇 가지 문제점이 있다.

우선, 철자를 틀리면 알아채기 힘든 문제가 생긴다. 예를 들어 테스트 메서드의 이름을 testSafetyOverride가 아니라 tsetSafetyOverride라고 했다 치자. JUnit은 아무런 불평도 하지 않을 것이다, 테스트를 실행하지도 않을 것이다.

 둘 째, 특정한 프로그램 요소에만 적용되도록 만들 수 없다.

 셋 째, 프로그램 요소에 인자를 전달할 마땅한 방법이 없다는 것이다. 예를 들어 특정 예외가 발생해야 성공으로 판정하는 테스트를 지우너하고 싶다고  해 보자. 해당 테스트에는 예외 자료형이 반드시 인자로 전달되어야 할 것이다. 


 어노테이션은 이러한 문제를 해결한다. 아래의 코드는 예외가 발생하면 실패하도록 하는 테스트 코드이다.



요약

대부분의프로그래머는, 도구 개발에 관심있는 개발자가 아니라면, 어노테이션 자료형을 정의할 필요가 없다. 그렇다 하더라도 모든 프로그래머는 자바 플랫폼이 제공하는 어노테이션 자료형들을 사용하도록 해야 한다. 


형 안전 enum 패턴을 쓸 경우에는 다른 열거 자료형을 계승해서 새로운 열거 자료형을 만드는 것이 가능하지만 enum 자료형으로는 그럴 수 없다.  그러나 이것을 단점으로 볼 수는 없는데, enum 자료형을 계승한다는 것은 대체로 바람직하지 않기 때문이다. 하지만 열거 자료형의 확장이 가능하면 좋은 경우가 있는데 연산코드를 만들어야 할 때이다.  때로는 API 사용자가 API가 기본 제공하는 연산 집합을 확장하여 자기만의 연산을 추가할 수 있도록 해야할 때가 있기 때문이다


 이러한 연산 자료형의 확장은 Enum 자료형이 임의의 이넡페이스를 구현할 수 있다는 사실을 이용하는 것이다. 


 BasicOperation은 enum자료형이라 계승할 수 없지만 Operation은 인터페이스라 확장이 가능하다. API가 사용하는 모든 연산은 이 인터페이스로 표현한다. 따라서 이 인터페이스를 계승하는 새로운 ENUM 자료형을 만들면 Operation 객체가 필요한 곳에 해당 enum 자료형의 상수를 이용할 수 있다. 

인터페이스를 사용해 확장 가능한 enum 자료형을 만드는 방법에는 한 가지 사소한 문제가 있다. enum 구현 자체는 계승할 수 없다는 것이다. 공유해야 하는 부분이 많아질 수록 중복코드가 많아지게 된다(인터페이스를 다 구현해야 하니까). 공유가 되는 기능은 도움 클래스 (helper class)나 정적 도움 메서드(static helper method)에 구현하여 중복을 최소화 해야한다.

요약

계승 가능 enum 자료형은 만들 수 없지만, 인터페이스를 만들고 그 인터페이스를 구현하는 기본 enum 자료형을 만들면 계승 가능 enum 자료형을 흉내낼 수 있다.



떄로 ordinal 메서드가 반환하는 값을 배열 첨자로 이용하는 코드를 만날 때가 있다. 



 이제 화단에 심은 허브들을 나타내는 배열이 하나 있다고 해 보자. 이 허브들을 품좀별로 나열해야 한다. 그러러면 품졸별 집합을 세 개 만든 다음에, 허브 각각을 그 품종에 맞는 집합에 넣어야 한다. 일부 프로그래머들은 배열을 사용할 테지만 배열로 구현하면 많은 문제점이 있다. 

 배열은 제네릭과 호환되지 않으므로 배열을 쓰려면 무점검 형변환이 필요하며 깔끔하게 컴파일 되지 않는다. 거디가가 배열은 첨자가 무엇을 나타내는지 모르므로 라벨을 수동으로 달아줘야 하며, eunum이 ordinal값으로 배열 원소를 참조할 때 정확한 int값이 사용되도록 해야 한다는 것이다. int enum과 같은 수준의 형 안전성을 보장하지 않으므로 위험하다.

 사실 enum 상수를 키로 사용할 목적으로 설계된 성능이 아주 우수한 Map있는데 바로 EnumMap이다.



요약

ordinal 값을 배열 첨자로 사용하는 것은 적절치 않다는 것. 대신 EnumMap을 사용하라


열거 자료형 원소들이 주로 집합에 사용될 경우, 전통적으로는 INT ENUM 패턴을 이용했다. 아래와 같이 하면 상수들을 집합에 넣을 때 비트별로 OR연산을 사용할 수 있다.

 집합을 비트 필드로 나타내면 비트 단위 산술 연산을 통해 합집합이나 교집합 등의 집합 연산도 효율적으로 할 수 있다. 하지만 위와 같은 코드는 int enum 패턴과 똑같은 단점들을 가지고 있다. 따라서 EnumSet이라는 클래스가 있는데, 이 클래스를 사용하면 특정한 enum 자료형의 값으로 구성된 집합을 효율적으로 표현할 수 있다. 


 appleyStyles 메서드에 EnumSet 객체를 전달하는 클라이언트 코드는 아래와 같다. EnumSet에는 정적 팩터리 메서드가 다양하게 준비되어 있어서 여러개중 선택해서 사용하면 된다.

요약

열거 자료형에 집합에 사용해야 할떄는 EnumSet을 사용하여 구현하도록 하자.



모든 enum에는 ordinal이라는 메서드가 있는데, enum 자료형 안에서 enum 상수의 위치를 나타내는 정수값을 반환한다. 정말 되도록 이면 쓰지마라. 아래의 예제를 보자.


 위의 코드는 유지보수 관점에서 보면 정말 안좋은 코드다. 상수 순서를 변경하는 순간 numberOfMusicians 메서드는 깨지고 만다. 게다가 이미 사용한 정수에 값에 대응되는 새로운 enum 상수를 정의하는 것은 아예 불가능하다.  enum 상수에 연계되는 값을 ordinal을 사용해 표현하지 말고, 그런 값이 필요하다면 그 대신 객체 필드에 저장해야 한다는 것.


 

+ Recent posts