이펙티브 자바 9장) 일반적인 프로그래밍 원칙

동동주·2025년 12월 17일

이펙티브 자바

목록 보기
10/13

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

지역변수의 유효 범위를 최소로 줄이면 코드의 가독성과 유지보수성이 높아지고, 오류가 발생할 가능성은 낮아진다.
지역변수의 범위를 줄이는 가장 강력한 기법은 ‘가장 처음 쓰일 때 선언하기’다. 거의 모든 지역변수는 선언과 동시에 초기화하는 것이 바람직하다.

  • 반복문은 변수 범위를 자연스럽게 최소화해주는 대표적인 구조다.
    반복 변수의 값을 반복문이 끝난 뒤에 사용할 필요가 없다면, while문보다는 for문을 사용하는 편이 낫다.

  • for문이 유리한 이유는 반복 변수의 유효 범위가 for문 블록 안으로 제한되기 때문이다.
    이 덕분에 같은 이름의 변수를 여러 반복문에서 사용하더라도 서로 영향을 주지 않으며, 코드의 안정성이 높아진다.

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

반복자와 인덱스 변수는 모두 코드를 지저분하게 할 뿐 우리에게 진짜 필요한 건 원소들뿐이다. 혹시라도 잘못된 변수를 사용했을 때 컴파일러가 잡아준다는 보장도 없고... 등 문제가 많은데 이걸 for-each문을 사용하면 모두 해결이 된다!

for (Element e : elements) {
  ... // e로 무언가를 한다.
  }
  • for-each문은 중첩 순회할 때 이점이 더 커진다. 정말 운이 나빠서 바깥 컬렉션의 크기가 안쪽 컬렉션 크기의 배수라면 이 반복문은 예외를 던지지 않고 종료한다.
  • for-each문은 컬렉션과 배열은 물론 Iterable 인터페이스를 구현한 객체라면 무엇이든 순회할 수 있다!

하지만 for-each문을 사용할 수 없는 상황 세 가지 존재한다!
1) 파괴적인 필터링 - 컬렉션을 순회하면서 선택된 원소를 제거해야 한다면 반복자의 remove 메서드를 호출해야 한다. 자바 8부터는 Collection의 removeIf 메서드를 사용해 컬렉션을 명시적으로 순회하는 일을 피할 수 있다.
2) 변형 - 리스트나 배열을 순회하면서 그 원소의 값 일부 혹은 전체를 교체해야 한다면 리스트의 반복자나 배열의 인덱스를 사용해야한다.
3) 병렬 반복 - 여러 컬렉션을 병렬로 순회해야 한다면 각각의 반복자와 인덱스 변수를 사용해 엄격하고 명시적으로 제어해야 한다.

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

static Random rnd = new Random();

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

문제점
1) n이 그리 크지 않은 2의 제곱수라면 얼마 지나지 않아 같은 수열이 반복된다.
2) n이 2의 제곱수가 아니라면 몇몇 숫자가 평균적으로 더 자주 반환된다.
3) 지정한 범위 '바깥'의 수가 종종 튀어나올 수 있다. rnd.nextInt()가 반환한 값을 Math.abs를 이용해 음수가 아닌 정수로 매핑하기 때문이다.

  • 이 문제점을 해결하려면 의사난수 생성기, 정수론, 2의 보수 계산 등에 조예가 깊어야한다. 다행히 Random.nextInt(int)가 다 해결해준다.
  • 자바 7부터는 Random을 더 이상 사용하지 않는 게 좋다. ThreadLocalRandom으로 대체하면 대부분 잘 작동한다. 더 고품질의 무작위 수 생성 & 속도도 빠름

표준 라이브러리를 사용하면 좋은 점

  • 그 코드를 작성한 전문가의 지식과 여러분보다 앞서 사용한 다른 프로그래머들의 경험을 활용할 수 있다.
  • 핵심적인 일과 크게 관련 없는 문제를 해결하느라 시간을 허비하지 않아도 된다.
  • 따로 노력하지 않아도 성능이 지속해서 개선된다.
  • 기능이 점점 많아진다.
  • 자연스럽게 다른 개발자들이 더 읽기 좋고, 유지보수하기 좋고, 재활용하기 쉬운 코드가 된다.

java.util.concurrent를 알아두면.. 동시성 기능에 좋다?
하여튼간 자바 표준 라이브러리를 잘 이용해보자. 근데 원하는 기능이 거기 없다면 다음 선택지는 고품질의 서드파티 라이브러리에서 찾으면 되고.... (ex. 구글의 구아바 라이브러리) 그것도 없으면 직접 구현해야한다.

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

float와 double은 넓은 범위의 수를 빠르게 정밀한 '근사치'로 계산하도록 설계되어 정확한 결과가 필요할 때는 사용하면 안 된다. 특히 금융 관련 계산과는 맞지 않는다!!

그래서 금융 계산에는 BigDecimal, int, long을 사용해야한다.
하지만 BigDecimal에는 단점이 두가지가 있다. 기본 타입보다 쓰기가 훨씬 불편하고 훨씬 느리다.
그래서 대안으로 int/long을 쓸 수도 있다.
이 경우에는 다룰 수 있는 값의 크기가 제한되고 소수점을 직접 관리해야 한다.

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

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

기본 타입과 박싱된 기본 타입의 주된 차이
1. 기본 타입은 값만 가지고 있으나, 박싱된 기본 타입은 값에 더해 식별성이란 속성을 갖는다.
2. 기본 타입의 값은 언제나 유효하나, 박싱된 기본 타입은 유효하지 않은 값, 즉 null을 가질 수 있다.
3. 기본 타입이 박싱된 기본 타입보다 시간과 메모리 사용면에서 더 효율적이다.

박싱된 기본 타입 사용 시 발생할 수 있는 문제

  • 박싱된 기본 타입에 == 연산자를 사용하면 값 비교가 아니라 객체 참조 비교가 이루어진다.
    이로 인해 의도하지 않은 오류가 발생할 수 있다.

해결 방법

  • 비교가 필요하다면 Comparator나 naturalOrder()를 사용한다.
  • 박싱된 타입의 값을 기본 타입 변수에 담아 비교 연산을 수행한다.

기본 타입과 박싱된 기본 타입을 혼용한 연산에서는 대부분 자동 언박싱이 일어난다.
하지만 실수로 지역변수를 박싱된 타입으로 선언하면, 눈치채지 못한 채 박싱·언박싱이 반복되어 성능 저하가 발생할 수 있다.

박싱된 기본 타입을 사용해야 하는 경우

  • 컬렉션의 원소, 키, 값
  • 매개변수화 타입이나 매개변수화 메서드 (ThreadLocal<Integer> 등)
  • 리플렉션을 통한 메서드 호출

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

문자열은 만능 타입처럼 보이지만, 다음과 같은 경우에는 적절하지 않다.
1. 다른 값 타입을 문자열로 대신하는 경우
수치 데이터는 숫자 타입으로, 예/아니오 값은 boolean이나 열거 타입으로 표현하는 것이 좋다.
2. 열거 타입을 문자열로 표현하는 경우
열거 타입을 사용하면 가독성과 타입 안정성이 크게 향상된다.
3. 혼합 타입을 문자열로 표현하는 경우

String compoundKey = className + "#" + i.next();

이 방식은 문자열 파싱이 필요해 번거롭고 오류 가능성이 크며,
equals, toString, compareTo 같은 메서드를 제대로 활용할 수 없다.

전용 클래스를 만드는 편이 훨씬 낫다.

  1. 문자열로 권한을 표현하는 경우
    스레드 구분용 키를 문자열로 사용하면 전역에서 키가 충돌해 값이 공유되는 문제가 발생할 수 있다.

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

문자열 연결 연산자 +는 간편하지만, 문자열을 많이 연결하는 경우 성능 문제가 심각해진다.
문자열 n개를 +로 연결하는 시간은 n²에 비례한다.

성능을 고려해야 한다면 StringBuilder를 사용하자.
StringBuilder는 문자열 개수에 따라 선형적으로 성능이 증가한다.

public String statement2() {
    StringBuilder b = new StringBuilder(numItems() * LINEWIDTH);
    for (int i = 0; i < numItems(); i++)
        b.append(lineForItem(i));
    return b.toString();
}

저자의 실험 기준으로 StringBuilder를 사용한 코드가 약 6.5배 더 빨랐다고 한다.

성능을 신경써야 한다면 많은 문자열을 연결할 때는 문자열 연결 연산(+)를 피하자.
대신 StringBuilder의 append 메서드를 사용하라.
문자 배열을 사용하거나, 문자열을 (연결하지 않고) 하나씩 처리하는 방법도 있다.

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

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

인터페이스 사용 장점

  • 인터페이스를 타입으로 사용하는 습관을 길러두면 프로그램이 훨씬 유연해진다.
  • 나중에 구현 크래스를 교체하고자 한다면, 그저 새 클래스의 생성자(혹은 다른 정적 팩터리)를 호출해주기만 하면 된다.

인터페이스 대신 클래스 타입을 사용해도 되는 경우
적합한 인터페이스가 없다면 당연히 클래스로 참조해야 한다.

  1. String과 BigInteger 같은 값 클래스
    값 클래스를 여러 가지로 구현될 수 있다고 생각하고 설계하는 일은 거의 없다.
    값 클래스는 매개변수, 변수, 필드, 반환 타입으로 사용해도 무방하다.

  2. 클래스 기반으로 작성된 프레임워크가 제공하는 객체들
    이런 경우라도 특정 구현 클래스보다는 (보통은 추상 클래스인) 기반 클래스를 사용해 참조하는게 좋다. OutStream 등 java.io 패키지의 여러 클래스가 이 부류에 속한다.

  3. 인터페이스에는 없는 특별한 메서드를 제공하는 클래스
    예시) PriorityQueue 클래스는 Queue 인터페이스에는 없는 comparator 메서드를 제공한다.

이러한 클래스 타입을 직접 사용하는 경우, 클래스 타입에서 제공하는 추가 메서드를 꼭 사용해야 하는 경우로 최소화해야 하며, 절대 남발하지 말아야 한다.

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

  1. 리플렉션이란?
java.lang.reflect API
  • 실행 중에 임의의 클래스에 접근할 수 있다.
  • 클래스, 인터페이스, 메서드를 찾을 수 있다.
  • 객체를 생성하거나 변수를 변경할 수 있고 메서드를 호출 할 수 있다.
  • 나아가 Constructor, Method, Field 인스턴스를 이용해 실제 생성자, 메서드, 필드를 조작할 수 있다.
  1. 리플렉션 단점
  • 컴파일타임 타입 검사가 주는 이점을 하나도 누릴 수 없다.
  • 예외 검사도 마찬가지다. 프로그램이 리플렉션 기능을 써서 존재하지 않는 혹은 접근할 수 없는 메서드를 호출하려고 하면 런타임 오류가 발생한다.
  • 리플렉션을 이용하면 코드가 지저분하고 장황해진다.
  • 성능이 떨어진다.
  • 리플렉션을 통한 메서드 호출은 일반 메서드 호출보다 훨씬 느리다.
  1. 정리
    리플렉션은 런타임에 존재하지 않을 수도 있는 다른 클래스, 메서드, 필드와의 의존성을 관리할 때 적합하다. 하지만 단점도 많기 때문에 되도록 객체 생성에만 사용하고, 적절한 인터페이스나 상위 클래스로 형변환해 사용하자.

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

네이티브 메서드

  • 자바 네이티브 인터페이스(Java Native Interface, JNI)는 자바 프로그램이 네이티브 메서드 를 호출하는 기술을 말한다.
  • 네이티브 메서드 란 C나 C++ 같은 네이티브 프로그래밍 언어로 작성한 메서드를 말한다.

네이티브 메서드는 주로 다음과 같은 용도로 사용된다.

  1. 플랫폼 특화 기능 사용
  2. 네이티브 코드로 작성된 기존 라이브러리 (레거시 라이브러리가 그 예다)
  3. 성능 개선을 목적으로 성능에 결정적인 영향을 주는 영역만 따로 네이티브 언어로 작성

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

네이티브 메서드의 단점
1. 네이티브 언어가 안전하지 않으므로 네이티브 메서드를 사용하는 애플리케이션도 메모리 훼손 오류로부터 더 이상 안전하지 않다.
2. 네이티브 언어는 자바보다 플랫폼을 많이 타기 때문에 이식성이 낮다.
3. 디버깅이 더 어렵다.
4. 주의하지 않으면 속도가 오히려 느려질 수도 있다.
5. 가비지 컬렉터가 네이티브 메모리는 자동 회수하지 못한다. 심지어 추적조차 할 수 없다.
6. 네이티브 메서드와 자바 코드 사이의 접착 코드(glue code)를 작성해야 한다. 이는 작업하기 귀찮을 뿐만 아니라 가독성도 떨어진다.

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

모든 사람이 마음 깊이 새겨야 할 최적화 격언 세 개를 소개 한다.

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

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

최적화를 할 때는 다음 두 규칙을 따르라. 첫 번째, 하지 마라. 두 번째, (전문가 한정) 아직 하지 마라. 다시 말해, 완전히 명백하고 최적화되지 않은 해법을 찾을 때까지는 하지 마라.

M. A 잭슨
최적화는 좋은 결과보다는 해로운 결과로 이어지기 쉽고, 섣불리 진행하면 특히 더 그렇다. 빠르지도 않고 제대로 동작하지도 않으면서 수정하기는 어려운 소프트웨어를 탄생시키는 것이다.

  • 빠른 프로그램보다 좋은 프로그램을 작성하라.
  • 설계 단계에서 성능을 반드시 염두에 두어야 한다.
    - 성능을 제한하는 설계를 피하라.
    • API를 설계할 때 성능에 주는 영향을 고려하라.
    • "최적화 시도 전후로 성능을 측정하라"

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

자바 플랫폼은 명명 규칙이 잘 정립되어 있으며, 그중 많은 것이 자바 언어 명세[JLS, 6.1]에 기술되어 있다. 자바의 명명 규칙은 크게 철자 규칙과 문법 규칙, 두 범주로 나뉜다.

1. 철자 규칙 (Lexical Rules)

대상명명 규칙예시 / 설명
패키지 / 모듈점(.)으로 계층 구분, 모두 소문자com.google.common, org.eff
조직 외부 공개용 패키지는 도메인 역순edu.cmu, com.google
표준 라이브러리는 java, javax로 시작java.util, javax.servlet
요소는 짧은 단어(보통 8글자 이하)utilitiesutil, awt
클래스 / 인터페이스각 단어의 첫 글자를 대문자로Thread, PriorityQueue
불필요한 축약은 피함HttpUrl (권장), HTTPURL (비권장)
메서드 / 필드첫 글자 소문자, 나머지는 클래스와 동일remove, ensureCapacity
상수 필드모두 대문자 + 밑줄VALUES, NEGATIVE_INFINITY
static final + 불변 값기본 타입 또는 불변 참조
지역변수메서드/필드와 유사, 약어 허용문맥상 의미 유추 가능
매개변수지역변수이지만 더 신중하게 작성문서에 노출되기 때문
타입 매개변수보통 한 글자T, E, K, V, R, X
여러 개일 경우 순서 사용T, U, V, T1, T2

2. 문법 규칙 (Grammatical Rules)

대상규칙예시
패키지별도 문법 규칙 없음
클래스 (인스턴스 생성 가능)단수 명사 또는 명사구Thread, PriorityQueue
클래스 (인스턴스 생성 불가)복수형 명사Collections, Collectors
인터페이스클래스와 동일하거나 -able, -ible 형용사Runnable, Iterable
애너테이션명확한 규칙 없음명사, 동사, 형용사 등
메서드 (동작 수행)동사 또는 동사구add, remove, calculateTotal
메서드 (boolean 반환)is, 드물게 has로 시작isEmpty, isEnabled, hasNext
메서드 (속성 반환)명사 / 명사구 또는 getsize, hashCode, getTime
타입 변환 메서드toType 형태toString, toArray
뷰 반환 메서드asType 형태asList
기본 타입 값 반환typeValue 형태intValue
정적 팩터리관례적인 이름 사용from, of, valueOf, getInstance
필드 (boolean)접근자에서 접두어 제거한 이름initialized, composite
필드 (그 외)명사 또는 명사구size, count
지역변수필드와 유사하나 더 자유로움짧고 명확하면 OK

명명 규칙은 단순한 스타일 문제가 아니라, 가독성·일관성·API 품질을 좌우하는 설계 요소다.
자바의 관례를 따르는 것만으로도 코드의 신뢰도가 크게 올라간다.

0개의 댓글