8장 — 일반적인 프로그래밍 원칙들

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

  • 자바에서는 명령문(statement)을 둘 수 있는 자리에는 변수도 선언할 수 있다.
  • 지역 변수의 유효범위를 최소화하는 가장 강력한 기법은, 처음으로 사용하는 곳에서 선언하는 것이다.
  • 거의 모든 지역 변수 선언에는 초기값(initializer)이 포함되어야 한다.
  • while 문보다는 for 문을 쓰는 것이 좋다.
  • 메서드의 크기를 줄이고 특정한 기능에 집중하라는 것이다.

규칙 46 for 문보다는 for-each 문을 사용하라

  • for-each 문은 전통적인 for 문에 비해 명료하고 버그 발생 가능성도 적으며, 성능도 for 문에 뒤지지 않는다.
  • for-each 문으로는 컬렉션과 배열뿐 아니라 Iterable 인터페이스를 구현하는 어떤 객체도 순회할 수 있다.
  • for-each 미적용
    • 필터링
    • 변환
    • 병렬 순회

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

  • 라이브러리를 사용 장점
  • 표준 라이브러리(standard library)를 사용하면 그 라이브러리를 개발한 전문가의 지식뿐만 아니라 여러분보다 먼저 그 라이브러리를 시용한 사람들의 경험을 활용할 수 있다.
  • 실제로 하려는 일과 큰 관련성도 없는 문제에 대한 해결 방법을 임의로 구현하느라 시간을 낭비하지 않아도 된다는 것이다.
  • 여러분이 별다른 노력을 하지 않아도 그 성능이 점차로 개선된다는 것이다.
  • 중요한 새 릴리스(major new release)가 나올 때마다 많은 기능이 새로 추가되는데, 그때마다 어떤 것들이 추가되었는지를 알이두는 것이 좋다.
  • 자바 프로그래머라면 java.lang, java.util 안에 있는 내용은 잘 알고 있어야 하며, java.io의 내용도 어느 정도 알고 있어야 한다.
  • 릴리스 1.2에는 java.util 패키지에 컬렉션 프레임워크(Collections Framework)가 추가되었다.
  • 릴리스 1.5부터는 병행성(concurrency) 관련 유틸리티들이 java.util.concurrent 패키지에 추가되었다.
  • 바퀴를 다시 발명하지 말라(don’t reinvent the wheel)

규칙 48 정확한 답이 필요하다면 float와 double은 피하라

  • float와 double은 특히 돈과 관계된 계산에는 적합하지 않다.
  • 돈 계산을 할 때는 BigDecimal, int 또는 long을 사용한다는 원칙을 지켜야 한다.
  • 성능이 중요하고 소수점 아래 수를 직접 관리해도 상관없으며 계산할 수가 심하게 크지 않을 때는 int나 long을 쓰라
  • 관계된 수치
    • 십진수 아홉 개 이하로 표현이 기능할 때는 int를 쓰라.
    • 18개 이하로 표현 가능할 때는 long을 쓰라.
    • 그 이상일 때는 BigDecimal을 써야 한다.

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

  • 기본 자료형과 객체화된 기본 자료형 차이점
    • 기본 지료형은 값만 가지지만 객체화된 기본 자료형은 값 외에도 신원(identity)을 가진다는 것
    • 기본 자료형에 저장되는 값은 전부 기능적으로 완전한 값(fully functional value)이지만, 객체화된 기본 자료형에 저장되는 값에는 그 이외에도 이무 기능도 없는 값, 즉 null이 하나 있다는 것
    • 기본 자료형은 시간이나 공간 요구량 측면에서 일반적으로 객체 표현형보다 효율적이라는 것
  • 객체화된 기본 자료형에 == 연산자를 사용하는 것은 거의 항상 오류라고 봐야 한다.
  • 기본자료형과 객체화된 기본 자료형올 한 연산 안에 엮어 놓으면 객체화된 기본 자료형은 자동으로 기본 자료형으로 변환된다.
  • 객체화된 기본 자료형
    • 컬렉션의 요소, 키, 값으로 사용할 때다. 컬렉션에는 기본 자료형을 넣을 수 없으므로 객체화된 자료형
    • 형인자 자료형의 형인자료는 객체화된 기본 자료형
    • 리플렉션을 통해 메서드를 호출할 때도 객체화된 기본자료형을 사용
  • 자동 객체화는 번거로운 일을 줄여주긴 하지만, 객체화된 기본 자료형을 사용할 때 생길 수 있는 문제들까지 없애주진 않는다.
  • 객체화된 기본 자료형과 기본 자료형을 한 표현식 안에 뒤섞으면 비객체화가 자동으로 일어나며, 그 과정에서 NullPointerException이 발생할 수 있다.
  • 기본 자료형 값을 객체화하는 과정에서 불필요한 객체들이 만들어지면 프로그램 성능이 저하될 수도 있다.

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

  • 문자열은 값 자료형(value type)을 대신하기에는 부족하다.
  • 적절한 값 자료형이 있다면 그것이 기본 자료형이건 아니면 객체 자료형이건 상관없이 해당 자료형을 사용
  • 문자열은 enum 자료형을 대신하기에는 부족하다.
  • 문자열은 혼합 자료형(aggregate type)을 대신하기엔 부족하다.
  • 문자열은 권한(capability)을 표현하기엔 부족하다.
public class ThreadLocal {
  private ThreadLocal() {} //객체를만들수없다

  // 주어진 이름이 가리키는 스레드 지역 변수의 값 설정.
  public static void set(String key, Object value);

  // 주어진 이름이 가리키는 스레드 지역 변수의 값 반환.
  public static Object get(String key);
}
public class ThreadLocal {
  privateThreadLocal(); //객체를만들수없다

  public static class Key { //(권한)
    Key();
  }

  // 유일성이 보장되는, 위조 불가능 키를 생성
  public static Key getKey() {
    return new Key();
  }

  public static void set(Key key, Object value);
  public static Object get(Key key);
}
public class ThreadLocal {
  public ThreadLocal();
  public void set(Object value);
  public Object get();
}
public class ThreadLocal<T> {
  public ThreadLocal();
  public void set(T value);
  public Object get();
}

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

  • n개의 문자열에 연결 연산자를 반복 적용해서 연결하는 데 드는 시간은, n^2에 비례한다.
// 문자열을 연결하는 잘못된 방법 - 성능이 엉망이다.
public class Sample {
    public String statement() {
      String result= "";
      for (int i = 0; i < numItems(); i++)
        result += lineForItem(i); //String concatenation
      return result;
    }
}
  • 만족스런 성능을 얻으려면 String 대신 StringBuilder를 써서 청구서를 저장해야 한다.
    • 릴리즈 1.5에 추가된 것으로, StringBuffer에서 동기화 synchronization 기능을 뺀 것이다.
public class Sample {
	public String statement() {
      StringBuilder b = new StringBuilder(numItems() * LINE_WIDTH);
      for (int i = 0; i < numItems(); i++)
        b.append(lineForItem(i));
      return b.toString();
    }
}

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

  • 만일 적당한 인터페이스 자료형이 있다면 인자나 반환값, 변수, 그리고 필드의 자료형은 클래스 대신 인터페이스로 선언하자.
  • 인터페이스를 자료형으로 쓰는 습관을 들이면 프로그램은 더욱 유연해진다.
  • 적당한 인터페이스가 없는 경우에는 객체를 클래스로 참조하는 것이 당연하다.
  • 객체를 참조할 때 인터페이스를 사용하면 훨씬 유연한 프로그램을 만들 수 있다.
  • 인터페이스가 없는 경우에는 필요한 기능을 제공하는 클래스 7陷데 가장 일반적인 클래스를 클래스 계층 안에서 찾아서 이용해야 한다.

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

  • java.lang.reflect의 핵심 리플렉션 기능(core reflection facility)을 이용하면 메모리에 적재된(load) 클래스의 정보를 가져오는 프로그램을 작성할 수 있다.
  • 명심할 것은, 일반적인 프로그램은 프로그램 실행 중에 리플렉션을 통해 객체를 이용하려 하면 안 된다는 것이다.
  • 리플렉션을 아주 제한적으로만 사용하면 오버헤드는 피하면서도 리플렉션의 다양한 장점을 누릴 수 있다.
  • 객체 생성은 리플렉션으로 하고 객체 참조는 인터페이스나 상위 클래스를 통하면 된다.

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

  • 네이티브 메서드를 통해 성능을 개선하는 것은 추천하고 싶지 않다.
  • 네이티브 언어는 안전하지 않으므로 (규칙 39), 네이티브 메서드를 이용하는 프로그램은 메모리 훼손 문제(memory corruption error)로부터 자유로울 수 없다.
  • 네이티브 언어는 플랫폼 종속적(platform dependent)이므로, 이식성이 낮다.
  • 네이티브 코드를 사용하는 프로그램은 디버깅하기도 훨씬 어렵다.
  • 네이티브 코드를 넘나드는 데 필요한 기본적인 비용 때문에, 네이티브 메서드가 하는 일이 별로 없다면 오히려 성능을 떨어뜨릴 수도 있다.
  • 네이티브 메서드를 시용하려면 이해하기도 어렵고 작성하기도 난감한 “접착 코드(glue code)”를 작성해야 한다.
  • 네이티브 코드에 있는 아주 작은 버그라도 시스템 전체를 훼손시킬 수 있다.

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

  • 성능 때문에 구조적인 원칙(architectural principle)을 희생하지 마라.
  • 빠른 프로그램이 아닌, 좋은 프로그램을 만들려 노력하라.
  • 설계를 할 때는 성능을 제약할 가능성이 있는 결정들은 피하라.
  • API를 설계할 때 내리는 결정들이 성능에 어떤 영향을 끼칠지를 생각하라.
  • 좋은 성능을 내기 위해 API를 급진적으로 바꾸는 것은 바람직하지 않다.
  • “최적화를 시도할 때마다, 전후 성능을 측정하고 비교하라.”

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

  • 자바의 작명 관습
    • 철자 관련
    • 문법 관려
  • 철자 관련
    • 패키지
      • 이름은 마침표를 구분점으로 사용하는 계층적 이름이어야 한다.
      • 이름을 구성하는 각각의 컴포넌트는 알파벳 소문자로 구성하고, 숫자는 거의 사용하지 않는다.
      • 조직 바깥에서 이용될 패키지 이름은 해당 조직의 인터넷 도메인 이름으로 시작 (최상위 도메인 이름이 먼저 온다.)
      • 예의적으로, 표준 라이브러리와 그 옵션 패키지 명은 java와 javax로 시작한다. (java나 javax로 시작하는 패키지 이름을 만들면 안 된다.)
      • 이름의 나머지 부분은 어떤 패키지인지 설명하는 하나 이상의 컴포넌트로 구성된다.
      • 패키지명 컴포넌트는 짧아야 하며, 보통 여덟 문자 이하로 만들어진다.
      • 의미가 확실한 약어를 활용하면 좋다.(utilities 대신 util, awt 같은 두문자도 사용)BotDetect CAPTCHA
    • 두문자의 경우 전부 대문자로 써야 하는지, 아니면 그 첫 글자만 대문자로 써야 하는지에 대해서는 합의된 것이 별로 없다. (예 : HTTPURL, HttpUrl)
    • 메서드와 필드 이름은 클래스나 인터페이스 이름과 동일한 철자 규칙을 따른다.
      • 다만 첫 글자는 소문자로 한다.
      • 메서드나 필드 이름 맨 앞에 두문자를 두어야 하는 경우, 소문자로 해야 한다.
    • 상수 필드의 이름은 하나 이상의 대문자 단어로 구성되며, 단어 사이에는 밑줄 기호를 둔다. (예 : VALUES, NEGATIVE_INFINITY)
    • 지역 변수 이름은 멤버 이름과 같은 철자 규칙을 따르는데, 약어가 허용된다는 것만 다르다.
    • 자료형 인자의 이름은 보통 하나의 대문자다.
  • 문법적(grammatical)
    • 작명 관습은 더 가변적일 뿐만 아니라, 철자 관습에 비해 논쟁의 여지가 많다.
    • 패키지의 경우에는 문법적 작명 관습이라 할 만한 것이 없다.
    • enum 자료형을 비롯한 클래스에는 단수형의 명사나 명사구(noun phrase)가 이름으로 붙는다. (예 : Timer, BufferedWinter, ChessPiece)
    • 인터페이스도 클래스와 비슷한 작명 규칙을 따른다.
    • able이나 ible 같은 형용사격 어미가 붙기도 한다. (예 : Runnable, Iterable, Accessible)
    • 어노테이션 자료형은 쓰임새가 너무 다양해서 딱히 지배적이라 할 만한 규칙이 없다.
      • 명사, 동사, 전치사, 형용사 기운데 어느 것이나 널리 쓰인다. (예 : BindingAnnotations, Inject, ImplementedBy, Singleton)
    • 어떤 동작을 수행하는 메서드는 일반적으로 동사나 동사구(목적어 포함)를 이름으로 갖는다. (예 : append, drawImage)
    • boolean 값을 반환하는 메서드의 이름은 보통 is, 드물게는 has로 시작하고, 그 뒤에는 명사나 명사구, 또는 형용시나 형용시구가 붙는다. (isDigit, isProbablePrime, isEmpty, isEnabled, hashSiblings)
    • boolean 이의의 기능이나 객체 속성을 반환하는 메서드에는 보통 명사나 명사구, 또는 get으로 시작하는 동시구를 이름으로 붙언다. (size, hashCode, getTime)
    • 객체의 자료형을 변환하는 메서드, 다른 자료형의 독립적 객체를 반환하는 메서드에는 보통 toType 형태의 이름을 붙인다. (예 : toString, toArray)
    • 인자로 전달받은 객체와 다른 자료형의 뷰(view) 객체를 반환하는 메서드에는(규칙 5) asType 형태의 이름을 붙인다.
    • 호출 대상 객체와 동일한 기본 자료형 값을 반환하는 메서드에는 typeValue와 같은 형태의 이름을 붙인다.
    • 정적 팩터리 메서드에는 valueOf, of, getInstance, newInstance, getType, newType 같은 이름을 붙인다.
    • 필드 이름에는 특별한 문법적 관습이 없을 뿐더러, 클래스나 인터페이스, 메서드 이름 규칙에 비하면 별로 중요하지도 않다.
      • 잘 설계된 API에는 외부로 공개된 필드가 별로 없기 때문이다.
    • boolean 형의 필드에는 보통 boolean 메서드와 같은 이름을 붙이나, 접두어 is는 생략한다. (예 : initialized, Composite)
    • 다른 자료형의 필드에는 보통 명사나 명사구를 이름으로 쓴다. (height, digits, bodyStyle)
    • 지역 변수에도 비슷한 규칙이 적용되나, 훨씬 느슨하다.