본문 바로가기

개발/이펙티브 자바

Effective Java ( 이펙티브 자바 ) - 아이템 59

라이브러리를 익히고 사용하라


무작위 정수 하나를 생성하고 싶다고 해보자. 값의 범위는 0부터 명시한 수 사이다. 

static Random rnd = new Random();

static int random(int n) {
   return Math.abs(rnd.nextInt()) % n;
}

괜찮은 듯 보여도 문제를 세 가지나 내포하고 있다.

  1. n이 그리 크지 않는 2의 제곱수라면 얼마 지나지 않아 같은 수열이 반복된다.
  2. n이 2의 제곱수가 아니라면 몇몇 숫자가 평균적으로 더 자주 반환된다. n 값이 크면 이 현상은 더 두드러진다.

다음 코드는 예시를 위해 필자가 신중히 선택한 범위에서 무작위 수를 백만개를 생성한 다음, 그중 중간 값보다 작은게 몇 개인지를 출력한다.

public static void main(String[] args) {
   int n = 2 * (Integer.MAX_VALUE / 3);
   int low = 0;
   for (int i = 0;i < 1000000; i++)
      if (random(n) < n/2)
         low++;
   System.out.println(low);
}

이상적으로는 약 50만개가 출력돼야 하지만, 실제로 돌려보면 666,666에 가까운 값을 얻는다. 2/3가량이 중간값보다 낮은 쪽으로 쏠린 것이다.

 

   3. 지정한 범위 '바깥'의 수가 종종 튀어나올 수 있다. 

rnd.nextInt()가 반환한 값을 Math.abs를 이용해 음수가 아닌 정수로 매핑하기 때문이다. Integer.MIN_VALUE를 반환하면 Integer.MAX_VALUE로 바뀌고, 나머지 연산자(%)는 음수를 반환해버린다(n이 2의 제곱수가 아닐 때의 시나리오다). 이렇게 되면 프로그램은 실패하고, 현상을 재현하기가 쉽지 않을 것이다.

 

  이 결함을 해결하려면 의사난수 생성기, 정수론, 2의 보수 계산 등에 조예가 깊어야 하지만, 직접 해결할 필요는 없다. Random.nextInt(int)가 이미 해결해놨으니 말이다. 이 메서드의 자체한 동작 방식은 몰라도 된다. 

 

표준 라이브러리를 사용하면 그 코드를 작성한 전문가의 지식과 여러분보다 앞서 사용한 다른 프로그래머들의 경험을 활용할 수 있다.

 

자바 7부터는 Random을 더 이상 사용하지 않는 게 좋다. ThreadLocalRandom 으로 대체하면 대부분 잘 작동한다. 더 고품질의 무작위 수를 생성할 뿐 아니라 속도도 더 빠르다. 포크-조인 풀이나 병렬 스트림에서 사용하기 위한 SplittableRandom도 있다. 

 

표준 라이브러리를 쓰면 핵심적인 일과 크게 관련 없는 문제를 해결하느라 시간을 허비하지 않아도 된다.

프로그래머들은 애플리케이션 기능 개발에 집중하고 싶어 한다. 

 

따로 노력하지 않아도 성능이 지속해서 개선된다.

사용자가 많고 업계 표준 벤치마크를 사용해 성능을 확인하기 때문에 표준 라이브러리 제작자들은 더 나은 방법을 꾸준히 모색할 수밖에 없다. 

 

기능이 점점 많아진다.

부족한 부분이 있다면 개발자 커뮤니티에서 이야기가 나오고 논의된 후 다음 릴리즈에 해당 기능이 추가되곤 한다.

 

작성한 코드가 많은 사람에게 낯익은 코드가 된다.

 

자연스럽게 다른 개발자들이 더 읽기 좋고, 유지보수하기 좋고, 재활용하기 쉬운 코드가 된다.

 


이렇게 표준 라이브러리의 기능을 사용하는 것이 좋아 보이지만, 실상은 많은 프로그래머가 직접 구현해 쓰고 있다. 왜 그럴까?

아마도 라이브러리에 그런 기능이 있는지 모르기 때문일 것이다. 메이저 릴리즈마다 주목할 만한 수많은 기능이 라이브러리에 추가된다. 자바는 메이저 릴리즈마다 새로운 기능을 설명하는 웹페이지를 공시하는데, 한 번쯤 읽어볼 만하다.   

 

확실한 예를 보여주기 위하여, 지정한 URL의 내용을 가져오는 명령줄 애플리케이션을 작성해보겠다(리눅스의 curl - GET을 생각하면 된다). 예전에는 까다로운 기능이었지만, 자바 9에서 InputStream에 추가된 transferTo 메서드를 사용하면 쉽게 '완벽히' 구현할 수 있다. 

public static void main(String[] args) throws IOException {
   try (InputStream in = new URL(agrs[0]).openStream()) {
      in.transferTo(System.out);
   }
}

 


 

라이브러리가 너무 방대하여 모든 API 문서를 공부하기는 벅차겠지만 자바 프로그래머라면 적어도 java.lang, java.util, java.io와 그 하위 패키지들에는 익숙해져야 한다. 다른 라이브러리들은 필요할 때마다 익히기 바란다. 라이브러리는 매년 아주 빠르게 성장하고 있으니 모든 기능을 요약하는 건 무리다. 

 

  하지만 언급해둘 만한 라이브러리는 몇 개 있다. 컬렉션 프레임워크와 스트림 라이브러리다. java.util.concurrent의 동시성 기능도 마찬가지로 알아두면 큰 도움이 된다. 이 패키지는 멀티스레드 프로그래밍 작업을 단순화해주는 고수준의 편의 기능, 자신만의 고수준 개념을 직접 구현할 수 있도록 도와주는 저수준 요소들을 제공한다. 

 

우선은 라이브러리를 사용하려 시도해보자. 원하는 기능이 아니라면 대안을 사용하자. 자바 표준 라이브러리에서 원하는 기능을 찾지 못하면, 그 다음 선택지는 고품질의 서드파티 라이브러리가 될 것이다. 그것조차 없다면 다른 선택이 없으니 직접 구현하자.

 

바퀴를 다시 발명하지 말자. 라이브러리가 있다면, 쓰면 된다. 찾아보라. 내가 작성한 것보다 품질이 좋고, 점차 개선될 가능성이 크다.