BottleH Blog

이펙티브자바 3판 9장 정리

    Tags

  • Java
이펙티브자바 3판 9장 정리 thumbnail

9장 일반적인 프로그래밍 원칙

이번 장에서는 자바 언어의 핵심 요소에 집중한다. 지역변수, 제어구조, 라이브러리, 데이터 타입, 그리고 언어 경계를 넘나드는 기능인 리플렉션과 네이티브 메서드를 다룬다. 마지막으로는 최적화와 명명 규칙을 논한다.

목차


아이템57. 지역변수의 범위를 최소화하라


지역변수의 유효 범위를 최소로 줄이면 코드 가독성과 유지보수성이 높아지고 오류 가능성은 낮아진다.

57-1. 지역변수 선언 기법

  1. 가장 처음 쓰일 때 선언하라

  2. 거의 모든 지역변수는 선언과 동시에 초기화해야 한다.

    • 초기화에 필요한 정보가 충분하지 않다면 충분해질 때까지 선언을 미뤄야 한다.
    • try-catch문은 이 규칙에서 예외다.
  3. 반복 변수의 값을 반복문이 종료된 뒤에도 써야하는 상황이 아니라면 while 문보다는 for 문을 쓰는 편이 낫다.

    • while 문은 반복변수를 반복문 바깥 블록에 선언해야 함.
    • for 문은 복사해 붙여넣기 오류를 컴파일 타임에 잡아준다.(while 은 안 잡아줌.)
    • while 문보다 짧아서 가독성이 좋다.
// 컬렉션이나 배열을 순회하는 권장 관용구 for (Element e : c) { // e로 무언가 수행 } // 반복자가 필요할 때의 관용구 for (Iterator<Element> i = c.iterator(); i.hasNext(); ) { Element e = i.next(); // e와 i로 무언가 수행 }
  1. 메서드를 작게 유지하고 한 가지 기능에 집중하는 것이다.
    • 단순히 메서드를 기능별로 쪼개면 된다.

아이템58. 전통적인 for 문보다는 for each 문을 사용하라


for-each 문의 정식 이름은 '향상된 for 문' 이다. for-each을 사용하면 반복자와 인덱스 변수를 사용하지 않으니 코드가 깔끔해지고 오류가 날 일도 사라진다.

for (Element e : c) { // e로 무언가 수행 }

여기서 콜론(:)은 "안의(in)" 라고 읽으면 된다.

58-1. for-each 문을 사용할 수 없는 경우

  1. 파괴적인 필터링: 컬렉션을 순회하면서 선택된 원소를 제거하려면 반복자의 remove를 사용해야 함.

    • 자바8부터는 CollectionremoveIf 메서드를 사용해 컬렉션을 명시적으로 순회하는 일을 피할 수 있다.
  2. 변형: 리스트나 배열을 순회하면서 그 원소의 값 일부 혹은 전체를 교체해야 함

  3. 병렬 반복: 여러 컬렉션을 병렬로 순회해야 한다면 각각의 반복자와 인덱스 변수를 사용해 엄격하고 명시적으로 제어해야 함.

결론: 전통적인 for 문과 비교했을 때 for-each 문은 명료하고, 유연하고, 버그를 예방해준다. 성능 저하도 없다. 가능한 모든 곳에서 for 문이 아닌 for-each 문을 사용하자.

아이템59. 라이브러리를 익히고 사용하라


59-1. 표준 라이브러리 사용시 이점

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

    • 자바7부터는 Random을 사용하지 말자. => ThreadLoscalRandom으로 대체할 것!
  2. 핵심적인 일과 크게 관련 없는 문제를 해결하느라 시간을 허비하지 않아도 된다.

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

  4. 기능이 점점 많아진다.

  5. 여러분이 작성한 코드가 많은 사람에게 낯익은 코드가 된다.

java.lang, java.util, java.io와 그 하위 패키지들에는 익숙해지도록 하자.

표준 라이브러리에서 원하는 기능을 찾지 못하면, 고품질의 서드파티 라이브러리를 사용하자

  • ex) 구글 구아바 라이브러리

아이템60. 정확한 답이 필요하다면 float와 double은 피하라


floatdouble 타입은 과학과 공학 계산용으로 설계되었다. 이진 부동소수점 연산에 쓰이며, 넓은 범위의 수를 빠르게 정밀한 근사치로 계산하도록 세심하게 설계되었다. 따라서, 정확한 결과가 필요할 때는 사용하면 안 된다.

  • 특히 금융 관련 계산과는 맞지 않는다.

금융계산에는 BigDecimal, int, long을 사용해야 한다.

BigDecimal의 단점

  • 기본 타입보다 쓰기가 훨씬 불편하고, 훨씬 느리다.

intlong은 다룰 수 있는 값의 크기가 제한되고, 소수점을 직접 관리해야함.

  • 숫자를 아홉 자리 십진수로 표현할 수 있다면 int 열여덟 자리 십진수로 표현할 수 있다면 long을 사용하라. 그 이상은 BigDecimal을 사용해야 한다.

아이템61. 박싱된 기본 타입보다는 기본 타입을 사용하라


자바의 데이터 타입은 크게 두가지로 나눌 수 있다. int, double, boolean 같은 기본 타입, String, List 같은 참조 타입이다. 각각의 기본 타입에는 대응하는 참조 타입(박싱된 기본타입)이 존재한다.

61-1. 기본 타입과 박싱된 기본 타입의 차이

  1. 기본 타입은 값만 가지고 있으나, 박싱된 기본 타입은 값에 더해 식별성(identity)이란 속성을 갖는다.

    • 즉, 박싱된 기본 타입의 두 인스턴스는 값이 같아도 서로 다르다고 식별될 수 있다.
  2. 기본 타입의 값은 언제나 유효하나, 박싱된 기본 타입은 유효하지 않은 값(null)을 가질 수 있다.

  3. 기본 타입이 박싱된 기본 타입보다 시간과 메모리 사용면에서 더 효율적이다.

Comparator<Integer> naturalOrder = (i, j) -> (i < j) ? -1 : (i == j ? 0 : 1);

위에서 Integer(42)와 Integer(42)를 비교하면 0이 아니라 1을 출력한다!

❗박싱된 기본 타입에 == 연산자를 사용하면 오류가 일어난다. 또한, 기본 타입과 박싱된 기본 타입을 혼용한 연산에서는 박싱된 기본 타입의 박싱이 자동으로 풀린다.

61-2. 박싱된 기본 타입을 쓰는 경우

  1. 컬렉션의 원소, 키 값으로 쓴다.

    • 컬렉션은 기본 타입을 담을 수 없기 때문
  2. 매개변수화 타입이나 매개변수화 메서드의 타입 매개변수로는 박싱된 기본 타입을 써야 한다.

    • 자바가 타입 매개변수로 기본 타입을 지원하지 않기 때문
  3. 리플렉션을 통해 메서드를 호출할 때도 박싱된 기본 타입을 사용해야 한다.

아이템62. 다른 타입이 적절하다면 문자열 사용을 피하라


  1. 문자열은 다른 값 타입을 대신하기에 적합하지 않다

  2. 문자열은 열거 타입을 대신하기에 적합하지 않다.

  3. 문자열은 혼합 타입을 대신하기에 적합하지 않다.

String compoundKey = className + "#" + i.next();
  1. 문자열은 권한을 표현하기에 적합하지 않다.

아이템63. 문자열 연결은 느리니 주의하라


문자열 연결 연산자(+)로 문자열 n개를 잇는 시간은 n^2에 비례한다.(문자열은 불변이라서 두 문자열을 연결할 경우 양쪽의 내용을 모두 복사해야 하기 때문) String은 힙 메모리의 String Pool에 저장됨.

63-1. StringBuilder

성능을 포기하고 싶지 않다면 String 대신 StringBuilder를 사용하자.

자바6 이후 문자열 연결 성능을 다방면으로 개선했지만 여전히 차이는 크다.

아이템64. 객체는 인터페이스를 사용해 참조하라


64-1. 인터페이스 사용

적합한 인터페이스만 있다면 매개변수뿐 아니라 반환값, 변수, 필드를 전부 인터페이스 타입으로 선언하라

// 좋은 예 Set<Son> sonSet = new LinkedHashSet<>(); // 나쁜 예 LinkedHashSet<Son> sonSet = new LinkedHashSet<Son>();

인터페이스를 타입으로 사용하는 습관을 길러두면 프로글매이 훨씬 유연해질 것이다.

  • 나중에 구현 클래스를 교체하고자 한다면 그저 새 클래스의 생성자를 호출해주기만 하면 된다.
  • 단, 특별한 기능을 같이 공유해야한다.(ex) LinkedHashSet->HashSet)

64-2. 클래스 사용

적합한 인터페이스가 없다면 당연히 클래스로 참조해야 한다.

  1. 값 클래스 -String, BigInteger

  2. 클래스 기반으로 작성된 프레임워크가 제공하는 객체들

    • 특정 구현 클래스보다는 기반 클래스를 사용해 참조하는 것이 좋음
    • java.io 패키지의 여러 클래스
  3. 인터페이스에는 없는 특별한 메서드를 제공하는 클래스들

    • PriorityQueue클래스는 Queue 인터페이스에는 없는 comparator 메서드를 제공한다.

이외에도 여러 경우가 있다.

결론: 적합한 인터페이스가 없다면 클래스의 계층구조 중 필요한 기능을 만족하는 가장 덜 구체적인(상위의) 클래스를 타입으로 사용하자.

아이템65. 리플렉션보다는 인터페이스를 사용하라


리플렉션(java.lang.reflect)을 이용하면 프로그램에서 임의의 클래스에 접근할 수 있다.

65-1. 리플렉션의 단점

  1. 컴파일타임 타입 검사가 주는 이점을 하나도 누릴 수 없다.

    • 예외 검사도 마찬가지다.
  2. 리플렉션을 이용하면 코드가 지저분하고 장황해진다.

  3. 성능이 떨어진다.

결국, 리플렉션은 아주 제한된 형태로만 사용해야 그 단점을 피하고 이점만 취할 수 있다. 리플렉션은 인스턴스 생성에만 쓰고, 이렇게 만든 인스턴스는 인터페이스나 상위 클래스로 참조해서 사용하자.

결론: 리플렉션은 복잡한 특수 시스템을 개발할 때 필요한 강력한 기능이지만, 단점도 많다. 컴파일타임에는 알 수 없는 클래스를 사용하는 프로그램을 작성한다면 리플렉션을 사용해야 할 것이다. 단, 되로록 객체 생성에만 사용하고, 생성한 객체를 이용할 때는 적절한 인터페이스나 컴파일타임에 알 수 있는 상위 클래스로 형변환해 사용해야 한다.

아이템66. 네이티브 메서드는 신중히 사용하라


자바 네이티브 인터페이스(JNI) - 자바 프로그램이 네이티브 메서드를 호출하는 기술

네이티브 메서드 - C나 C++ 같은 네이티브 프로그래밍 언어로 작성한 메서드

66-1. 네이티브 메서드의 주요 쓰임

  1. 레지스트리 같은 플랫폼 특화기능 사용

  2. 네이티브 코드로 작성된 기존 라이브러리를 사용

    • 레거시 데이터를 사용하는 레거시 라이브러리
  3. 성능 개선을 목적으로 성능에 결정적인 영향을 주는 영역만 따로 네이티브 언어로 작성

66-2. 네이티브 메서드 사용

성능을 개선할 목적으로 네이티브 메서드를 사용하는 것은 거의 권장하지 않는다.

예를 들어, java.math가 처음 추가된 자바 1.1 시절 BigInteger는 C로 작성한 고성능 라이브러리에 의지했다. 그러다 자바 3 때 순수 자바로 다시 구현되면서 세심히 튜닝한 결과, 원래의 네이티브 구현보다도 더 빨라졌다.

  • 그런데 BigInteger는 자바 8에서 큰 수의 곱셈 성능을 개선한 것을 제외하고는 더 이상의 커다란 변화가 없다..

  • 정말로 고성능의 다중 정밀 연산이 필요하다면 네이티브 메서드를 통해 다중 정밀 연산 라이브러리(GMP)를 사용하는 걸 고려해도 좋다.

66-3. 네이티브 메서드 단점

  1. 메모리 훼손 오류로부터 더 이상 안전하지 않다.

  2. 자바보다 플랫폼을 많이 타서 이식성도 낮다.

  3. 디버깅이 더 어렵다.

  4. 주의하지 않으면 속도가 오히려 느려질 수 있다.

  5. 가비지 컬렉터가 네이티브 메모리는 자동 회수하지 못하고, 심지어 추적조차 할 수 없다.

  6. 자바 코드와 네이티브 코드의 경계를 넘나들 때마다 비용도 추가된다.

  7. 네이티브 메서드와 자바 코드 사이의 '접착 코드(glue code)'를 작성해야 한다.

    • 귀찮고, 가독성이 떨어짐.

아이템67. 최적화는 신중히 하라


67-1. 최적화 격언

(맹목적인 어릿석음을 포함해) 그 어떤 핑계보다 효율성이라는 이름 아래 행해진 컴퓨팅 죄악이 더 많다(심지어 효율을 높이지도 못하면서). -윌리엄 울프

(전체의 97% 정도인) 자그마한 효율성은 모두 잊자. 섣부른 최적화가 만악의 근원이다. - 도널드 크누스

최적화를 할 때는 다음 두 규칙을 따르라.

첫번째, 하지 마라.

두번째, (전문가 한정) 아직 하지 마라. 다시 말해, 완전히 명백하고 최적화되지 않은 해법을 찾을 때까지는 하지 마라. - M.A. 잭슨

위의 격언들의 주는 교훈은 '빠른 프로그램보다는 좋은 프로그램을 작성하라' 라고 볼 수 있다. 하지만 성능을 무시하라는 뜻은 아니다.

67-2. 설계 단계에서의 성능

  1. 성능을 제한하는 설계를 피하라.

  2. API 를 설계할 때 성능에 주는 영향을 고려하라.

  3. 각각의 최적화 시도 전후로 성능을 측정하라.

    • C, C++ 에서도 중요하지만 성능 모델이 덜 정교한 자바에서는 중요성이 더욱 크다.
  4. 성능을 위해 API 를 왜곡하는 건 매우 안 좋은 생각이다.

✔ 성능체크하고 싶을 때는 JMeter, JMH 를 사용해보자

아이템68. 일반적으로 통용되는 명명 규칙을 따르라


자바의 명명 규칙은 크게 철자와 문법 두 범주로 나뉜다.

68-1. 철자규칙

철자 규칙은 패키지, 클래스, 인터페이스, 메서드, 필드, 타입 변수의 이름을 다룬다.

  1. 패키지와 모듈 이름은 각 요소를 점(.)으로 구분하여 계층적으로 짓는다.

    • ex) org.junit.jupiter.api, com.google.common.collect
  2. 클래스와 인터페이스의 이름은 하나 이상의 단어로 이뤄지며, 각 단어는 대문자로 시작한다. 널리 통용되거나 첫 글자만 딴 약자를 제외하고는 단어를 줄여쓰지 않도록 한다.

    • ex) Stream, FutureTask, LinkedHashMap, HttpClient
  3. 메서드와 필드 이름은 첫 글자를 소문자로 쓴다는 점만 빼면 클래스 명명 규칙과 같다.

    • ex) remove, groupingBy, getCrc
  4. 단, 상수 필드는 예외다. 모두 대문자로 쓰며 단어 사이는 밑줄로 구분한다.

    • ex) MIN_VALUE, NEGATIVE_INFINITY
  5. 지역변수에도 다른 멤버와 비슷한 명명 규칙이 적용되지만 약어를 써도 좋다.

    • ex) i, denom, houseNum
  6. 타입 매개변수 이름은 보통 한 문자로 표현한다.

    • 임의의 타입: T
    • 컬렉션 원소의 타입: E
    • 맵의 키와 값: K, V
    • 예외: X
    • 메서드의 반환 타입: R
    • 이외의 임의 타입의 시퀀스: T,U,V,T1,T2,T3

68-2. 문법규칙

철자 규칙에 비해 유연하고 논란이 많다.

  1. 패키지에 대한 규칙은 따로 없다.

  2. 객체를 생성할 수 있는 클래스의 이름은 보통 단수 명사나 명사구 사용

    • ex) Thread
  3. 객체를 생성할 수 없는 클래스의 이름은 보통 복수형 명사 사용

    • ex) Collectors, Collections
  4. 인터페이스 이름은 클래스와 똑같이 짓거나, able 혹은 ible 로 끝나는 형용사로 짓는다.

    • ex) Collection, Iterable
  5. 애너테이션은 워낙 다양하게 활용되어 지배적인 규칙이 없이 두루 쓰인다.

  6. 어떤 동작을 수행하는 메서드의 이름은 동사나 동사구로 짓는다.

    • ex) append, drawIgmage
  7. boolean 값을 반환하는 메서드는 보통 is나 드물게 has로 시작하고 그 뒤에 아무 단어나 구로 짓는다.

    • ex) isDigit, isEmpty
  8. 반환 타입이 boolean 이 아니거나 해당 인스턴스의 속성을 반환하는 메서드의 이름은 보통 명사, 명사구, 혹은 get 으로 시작하는 동사구로 짓는다.

    • ex) size, getTime

68-3. 특별한 메서드 이름

  1. 객체의 타입을 바꿔서, 다른 타입의 또 다른 객체를 반환하는 인스턴스 메서드의 이름은 보통 toType 형태로 짓는다.

    • ex) toString
  2. 객체의 내용을 다른 뷰로 보여주는 메서드의 이름은 asType 형태로 짓는다.

    • ex) asList
  3. 객체의 값을 기본 타입 값으로 반환하는 메서드의 이름은 보통 typeValue 형태로 짓는다.

    • ex) intValue
  4. 정적 팩터리의 이름은 다양하지만 from, of, valueOf, instance, getInstance, newInstance, getType, newType을 흔히 사용한다.

Written by@BottleH
Back-End Developer

GitHub