Effective Java 2판 - 8장 정리 (일반적인 원칙)

 

1. 들어가며

 

자바 언어의 일반적인 원칙들에 대해 다룹니다. 지역 변수, 자료형, 리플렉션, 최적화까지 다양하게 다루는 파트입니다.

 

2. 일반적인 프로그래밍 원칙들

 

규칙45: 지역 변수의 유효범위를 최소화하라.

이는 클래스와 멤버의 접근 권한은 최소화하라와 유사하다. 지역 변수의 유효 범위를 최소화하면 가독성과 유지보수성이 좋아지고 오류 발생 가능성도 적어진다.

해당 방법을 보장하기 위해서는 다음과 같은 방법이 있다.

 

1)     처음 사용하는 곳에서 선언한다. c언어처럼 블록 앞부분에 선언하는 건 자바와 어울리지 않는다.

2)     거의 모든 지역 변수 선언엔 초기값이 포함되어야 한다.

3)     while문보다는 for문을 쓰는 것이 좋다. 순환문 변수와 가독성 덕분이다.

4)     메서드 크기를 줄이고 특정한 기능에 집중해라.

 

규칙46: for 문보다는 for-each 문을 사용해라.

최근엔 Java Stream이 나와 있으므로 Java Stream을 활용하는 것도 좋다. 단 다음의 경우 적용하기 힘들다.

 

1)     필터링: collection을 순회하다가 특정 원소를 삭제해야할 경우

2)     변환: 리스트나 배열을 순회하다가 그 원소 가운데 일부 또는 전부의 값을 변경

3)     병렬 순회: 여러 컬렉션을 병렬적으로 순회해야 하고, 모든 반복자나 첨자 변수가 발맞춰 나아가야 한다.

 

규칙47: 어떤 라이브러리가 있는지 파악하고, 적절히 활용하라.

예를 들어 random을 라이브러리 없이 직접 구현한다고 해보자. 쉽지도 않을 뿐더러 정말로랜덤을 구현했는지 확신할 수 있는가. (사실 Java의 Random 패키지도 pseudo-random입니다..!)

수백만 프로그래머가 사용하고 검증된 라이브러리를 사용해라.

실제로 하려는 일과 큰 관련성도 없는 문제를 해결하려고 골머리를 않을 필요 없다. 표준 라이브러리는 가독성이 높고 유지보수가 쉬우며 재사용하기 좋은 코드를 만든다.

그러므로 새 릴리즈가 나올 때마다 어떤 것들이 추가되는지 알아두는 것이 좋다. 자바 프로그래머라면 java.lang, java.util, java.io에 대해서는 빠삭하게 공부해야 한다.

쉽게 말해 바퀴를 재발명하지 마라.

 

규칙 48: 정확한 답이 필요하다면 floatdouble은 피해라.

이는 컴퓨터 구조만 배워도 알 수 있을 것이라 생각한다. 부동 소수점은 근사치를 구하는 것이다.

 

규칙 49: 객체화된 기본 자료형 대신 기본 자료형을 이용해라.

객체화가 된 기본 자료형을 사용한다면 다음과 같은 단점을 가진다.

 

1)     객체화된 기본 자료형에 == 연산자를 사용하면 거의 항상 오류를 낸다.

2)     객체화된 기본 자료형과 기본 자료형을 뒤섞으면 자동으로 비객체화가 일어나며 그 과정에서 NullPointerException을 유발한다.

 

규칙 50: 다른 자료형이 적절하다면 문자열 사용은 피하라.

1)     문자열은 값 자료형을 대신하기엔 부족하다. 연산자가 원치 않는 방식으로 동작할 것이다.

2)     num 자료형을 대신하기에도 부족하다. 규칙 30을 참조하자.

3)     혼합 자료형을 대신하기에도 부족하다. equals, toString, compareTo도 제공할 수 없고 혼동을 발생시킨다.

4)     권한을 표현하기에 부족하다. 문자열은 스레드 지역 변수의 global namespace이다. 문자열 키의 유일성을 보장하지 않는 한 오류를 내며 보안 문제도 발생시킨다.

 

규칙 51: 문자열 연결 시 성능에 주의하라.

문자열 연결 연산자 +는 여러 문자열을 하나로 합하는 편리한 수단이다. 하지만 n개의 문자열에 연결 연산자를 반복 적용해서 연결하는 데 드는 시간은, n^2에 비례한다. 문자열은 변경 불가능하기 때문이다. StringBuilder를 사용하라.

한마디: 단 jdk 9 이후에는 StringConcatFactory를 사용하여 개선되었다. 그래도 자바 버전에 의존하는 습관은 좋은 것은 아닐 것이다.

 

규칙 52: 객체를 참조할 때는 그 인터페이스를 사용하라.

객체를 참조할 때는 클래스 보다 인터페이스를 사용하라. 만일 적당한 인터페이스 자료형이 있다면 인자나 변환값, 변수, 그리고 필드의 자료형은 클래스 대신 인터페이스로 선언하자. 이를 통해 프로그램이 더 유연해질 수 있다. 물론 적당한 인터페이스가 없는 경우에는 객체를 클래스로 참조하는 것은 당연하다.

규칙 53: 리플렉션 대신 인터페이스를 이용하라.

리플렉션을 사용하면 메모리에 적재된 클래스 정보를 가져올 수 있다. 단 다음과 같은 대가를 치르게 된다.

 

1)     컴파일 시점에 자료형을 검사함으로써 얻을 수 있는 이점들을 포기해야 한다.

2)     코드를 읽기 힘들다.

3)     성능이 매우 느리다.

 

따라서 일반적인 프로그램은 프로그램 실행 중에 리플렉션을 통해 객체를 이용하려하면 안 된다는 것이다.

물론 리플렉션을 아주 제한적으로만 사용하면 오버헤드는 피하면서도 리플렉션의 다양한 장점을 누릴 수 있다.

한마디: 예를 들어 lombok이나 spring에서 활용하는 annotation들이 있을 것이다.

 

규칙 54: 네이티브 메서드는 신중하게 사용하라.

JNI를 말하는 것으로 c와 같은 네이티브 프로그래밍 언어의 메서드를 호출하는 데 이용하는 기능이다. 하지만 네이티브 메서드를 통해 성능을 개선하는 것은 추천하기 어려운 일이다. 현재 JVM의 성능은 높고 그에 필적하는 성능을 낼 수 있다. 무엇보다 안전하지 않은 언어이므로 디버깅하기 어려운 프로그램을 만들어낸다. 굳이 그래야 한다면 성능 개선 용도로만 쓰라.

 

규칙 55: 신중하게 최적화하라.

모든 프로그래머가 알아둬야 하는 최적화 관련 격언이 세 가지 있다.

 

1)     맹목적인 어리석음을 비롯한 다른 어떤 이유보다도, 효율성이라는 이름으로 저질러지는 죄악이 더 많다.

2)     작은 효율성에 대해서는 말하자면 97% 정도에 대해서는, 잊어버려라. 섣부른 최적화는 만악의 근원이다.

3)     최적화를 할 때는 아래의 두 규칙을 따라라. a) 하지 마라. b) (전문가들만 따를 것) 아직 하지 마라. – 완벽히 명료한, 최적화되지 않은 해답을 얻을 때까지는.

 

즉 성능 때문에 구조적인 원칙을 희생하지 마라. 빠른 프로그램이 아닌 좋은 프로그램을 만들려 노력하라. 정보 은닉을 잘 지키자.

그와 더불어 설계를 할 때는 성능을 제약할 수 있는 결정들은 피하라. API 통신 프로토콜, 지속성 데이터 형식 등은 나중에 성능 문제가 발견되면 고치기 힘들다.

API를 설계할 때 내리는 결정들이 성능에 어떤 영향을 끼칠지를 생각하라.

예를 들어 public 자료형을 변경 가능하게 만들면 쓸데없이 방어적 복사를 많이 해야 하거나, 계승 기법을 적용하면 하위 클래스 성능에 인위적인 계약이 맺어질 수 있다.

좋은 성능을 내기 위해 API를 급진적으로 바꾸는 것은 바람직하지 않으며 두고두고 개발자를 괴롭힐 것이다.

무엇보다 최적화를 시도할 때마다 전후 성능을 측정하고 비교하라. 개선되지 않거나 더 나빠지는 일이 의외로 많다. 그 주된 이유는, 프로그램이 어디에 시간을 쓰고 있는지 추측하기가 어렵다는 것이다. 따라서 프로파일링 도구를 통해 최적화 지점을 먼저 찾는 것이 좋다.

 

규칙 56: 일반적으로 통용되는 작명 관습을 따르라.