57. 지역변수의 범위를 최소화하라
•
지역변수의 유효 범위를 최소로 줄이면 코드 가독성과 유지보수성이 높아지고 오류 가능성은 낮아진다.
◦
지역변수의 범위를 줄이는 가장 강력한 기법은 ‘가장 처음 쓰일 때 선언하기’다.
•
지역변수를 생각 없이 선언하다 보면 변수가 쓰이는 범위보다 앞서 선언하거나 다 쓴 뒤에도 여전히 살아있게 되기 쉽다.
◦
그래서 실수로 의도한 범위 앞 혹은 뒤에서 그 변수를 사용하면 끔찍한 결과로 이어질 수 있다.
•
또한 거의 모든 지역변수는 선언과 동시에 초기화해야 한다.
◦
초기화에 필요한 정보가 충분하지 않다면 충분해질 때까지 선언을 미뤄야 한다.
◦
try-catch문은 이 규칙에서 예외다.
◦
변수를 초기화할 때 예외를 던질 가능성이 있으면 try 블록 안에서 초기화해야 한다.
◦
try 블록을 넘어서도 사용할 것 같으면 try 블록 앞에서 선언해야 한다.
반복문
•
반복문은 독특한 방식으로 변수 범위를 최소화해준다.
•
예전의 for 형태든 새로운 for-each 형태든, 반복문에서는 반복 변수의 범위가 반복문의 몸체, 그리고 for 키워드와 몸체 사이의 괄호 안으로 제한된다.
for (Element e : c) {
...
}
Java
복사
컬렉션이나 배열을 순회하는 권장 관용구
for (Iterator<Element> i = c.iterator(); i.hasNext(); ) {
Element e = i.next();
...
}
Java
복사
반복자가 필요할 때의 관용구
정리
•
지역변수의 범위를 줄이는 가장 강력한 기법은 가장 처음 쓰일 때 선언하기다.
•
메서드를 작게 유지하고 한 가지 기능에 집중하는 것이다.
주저리
•
메서드를 작게 유지하는건 별개로 보더라도 요즘 변수를 착각해 사용하는 일은 많이 줄어들었다고 생각한다.
◦
IDE가 발전해서 사용하지 않는 변수나 이상하게 사용되는 변수는 대다수 경고를 주기때문
58. 전통적인 for문 보다는 for-each문을 사용하라
컬렉션 순회하기
for (Iterator<Element> i = c.iterator(); i.hasNext(); ) {
Element e = i.next();
...
}
Java
복사
더 나은 방법이 있다.
for (int i = 0; i < a.length; i++) {
...
}
Java
복사
더 나은 방법이 있다.
•
반복자와 인덱스 변수를 코드를 지저분하게하고 오류가 생길 가능성이 높아진다.
•
1회 반복에서 반복자는 세 번 등장하고 인덱스는 네 번 등장하고 있다.
•
혹시라도 잘못된 변수를 사용했을 때 컴파일러가 잡아주지 못할 수도 있다. 또한 컬렉션이나 배열이냐에 따라 코드 형태도 달라지고 있다.
향상된 for 문
for (Element e: elements) {
...
}
Java
복사
•
반복자와 인덱스 변수를 사용하지 않으니 코드가 깔끔해지고 오류가 날일도 없다.
•
하나의 관용구로 컬렉션과 배열을 모두 처리할 수 있어서 어떤 컨테이너를 다루는지는 신경쓰지 않아도 된다.
for-each문을 사용할 수 없는 세 가지 상황
•
파괴적인 필터링
◦
컬렉션을 순회하면서 선택된 원소를 제거해야 한다면 remove 메서드를 호출해야 한다.
◦
자바 8부터는 Collection의 removeIf 메서드를 사용해 컬렉션을 명시적으로 순회하는 일을 피할 수 있다.
•
변형
◦
리스트나 배열을 순회하면서 그 원소의 값 일부 혹은 전체를 교체해야 한다면 리스트의 반복자나 배열의 인덱스를 사용해야 한다.
•
병렬 반복
◦
여러 컬렉션을 병렬로 순회해야 한다면 각각의 반복자와 인덱스 변수를 사용해 명시적으로 제어해야 한다.
59. 라이브러리를 익히고 사용하라
라이브러리의 장점
•
표준 라이브러리를 사용하면 그 코드를 작성한 전문가의 지식과 우리보다 앞서 사용한 다른 프로그래머들의 경험을 활용할 수 있다.
•
표준 라이브러리를 사용하면 핵심적인 일과 크게 관련 없는 문제를 해결하느라 시간을 허비하지 않아도 된다.
•
따로 노력하지 않아도 성능이 지속해서 개선된다.
•
기능이 점점 많아진다.
•
여러분이 작성한 코드가 많은 사람에게 익숙한 코드가 된다.
◦
다른 개발자들이 더 읽기 좋고, 유지보수하기 좋고, 재활용하기 쉬운 코드가 된다.
장점이 많은데 왜 사람들은 직접 구현할까?
•
아마도 라이브러리에 그런 기능이 있는지 모르기 때문일 것
•
메이저 릴리즈마다 주목할 만한 수많은 기능이 라이브러리에 추가된다.
•
라이브러리가 방대해 모든 API 문서를 공부하기는 힘들다.
◦
하지만 자바 프로그래머라며 적어도 java.util, java.lang, java.io와 그 하위 패키지들에는 익숙해져야 한다.
60. 정확한 답이 필요하다면 float과 double은 피하라
•
float과 double은 부동소수점 타입으로 넓은 범위의 수를 빠르고 정밀하게 근사치로 계산하도록 설계되었다.
•
float과 double은 특히 금융 관련 계산과는 맞지 않는다.
어설픈 코드
System.out.println(1.03 - 0.42);
Java
복사
•
이 출력의 결과는 0.61000000000001이다.
•
잘못된 결괏값을 출력하기 전에 반올림하면 해결되리라 생각할지도 모르지만, 반올림 해도 틀린 답이 나올 수 있다.
•
정확한 값을 원한다면 BigDecimal, int, long을 이용하자
BigDecimal의 단점
•
기본타입보다 쓰기고 훨씬 불편하고 느리다.
•
단발성 계산이라면 느리다는 무시할 수 있지만, 쓰기 불편하다는 점은 못내 아쉬울 것이다.
◦
속도가 중요한 경우 int나 long을 이용해 소수점을 직접 관리하는 방법도 있다.
61. 박싱된 기본 타입보다는 기본 타입을 사용하라
•
자바는 크게 기본타입(int, long, char 등)과 참조타입(모든 Object)으로 나눌 수 있다.
•
기본타입에 대응하는 참조타입이 하나씩 있고, 이를 박싱된 타입이라고 한다.
박싱 타입과 기본 타입의 차이점
•
기본 타입은 값만 가지고 있으나, 박싱된 기본 타입은 값에 더해 식별성이란 속성을 갖는다.
◦
박싱된 기본 타입의 두 인스턴스는 값이 같아도 서로 다르다고 식별될 수 있다.
•
기본 타입의 값은 언제나 유효하나, 박싱된 기본 타입은 null을 가질 수 있다.
•
기본 타입이 박싱된 기본 타입보다 시간과 메모리 사용면에서 더 효율적이다.
박싱 타입 사용시의 주의사항
•
박싱 타입은 참조타입이기 때문에 == 연산자를 사용하면 의도한 동작과 다르게 나타난다.
•
박싱 타입을 초기화 하지 않았을 경우 기본 값이 null로 초기화 되기 언박싱시 때문에 NPE이 일어난다.
•
오토 박싱/언박싱으로 인해서 성능이 저하될 수 있다.
62. 다른 타입이 적절하다면 문자열 사용을 피하라
•
문자열은 텍스트를 표현하도록 설계되었지만 편의성 때문에 의도하지 않은 용도로 쓰이는 경향이 있다.
문자열 사용이 적합하지 않은 경우
•
문자열은 다른 값 타입을 대신하기에 적합하지 않다.
•
문자열은 열거 타입을 대신하기에 적합하지 않다.
•
문자열은 혼합 타입을 대신하기에 적합하지 않다.
◦
특정 문자열로 구분시에 파싱을 해야하는데 느리고, 귀찮고, 오류 가능성이 생길 가능성이 높다.
•
문자열은 권한을 표현하기에 적합하지 않다.
◦
악의적인 사용자가 의도적으로 같은 키값을 위조할 수 있다.
63. 문자열 연결은 느리니 주의하라
•
문자열 연결 연산자(+)는 여러 문자열을 하나로 합쳐주는 편리한 수단이다.
◦
그러나 본격적으로 사용할 경우에는 성능 저하를 감내하기 어렵다.
•
문자열 연결 연산자로 문자열 n개를 잇는 시간은 n^2에 비례한다.
◦
문자열은 불변이라서 두 문자열을 연결할 경우 양쪽 내용을 모두 복사해야 한다.
성능을 챙기고 싶다면?
public String statement() {
String result = "";
for (int i = 0; i < numItems(); i++)
result += lineForItem(i); // 문자열 연결
return result;
}
Java
복사
느리다!
public String statement() {
StringBuilder b = new StringBuilder(numItems() * LINE_WIDTH);
for ( int i = 0; i < numItems(); i++)
b.append(lineForItem(i));
return b.toString();
}
Java
복사
빠르다!
•
성능을 포기하고 싶지 않다면 String 대신 StringBuilder를 사용하자.
여담
어디서 듣기로는 + 연산을 사용해도 컴파일시엔 StringBuilder로 바꿔준다고 본거같은데 추후 찾아봐야겠다.
64. 객체는 인터페이스를 사용해 참조하라
•
적합한 인터페이스만 있다면 매개변수뿐 아니라 반환값, 변수, 필드를 전부 인터페이스 타입으로 선언하라.
•
객체의 실제 클래스를 사용해야 할 상황은 생성자로 생성할 때 뿐이다.
•
적합한 인터페이스가 없다면 클래스의 계층구조 중 필요한 기능을 만족하는 가장 덜 구체적인 (상위의) 클래스 타입으로 사용하자.
인터페이스를 타입으로 써야하는 이유
// 좋은 예. 인터페이스를 타입으로 사용했다.
Set<Son> sonSet = new LinkedHashSet<>();
// 구현 클래스 변경
Set<Son> sonSet = new HashSet<>();
// 나쁜 예. 클래스를 타입으로 사용했다!
LinkedHashSet<Son> sonSet = new LinkedHashSet<Son>();
Java
복사
•
인터페이스를 타입으로 사용하는 습관을 길러두면 프로그램이 휠씬 유연해질 것이다.
◦
나중에 구현 클래스를 교체하고자 한다면 그저 새 클래스의 생성자를 호출해주기만 하면 된다.
•
다른 코드는 전혀 손대지 않고 구현체 변경이 가능하다.
주의점
•
원래의 클래스가 인터페이스의 일반 규약 이외의 특별한 기능을 제공하며, 주변 코드가 이 기능에 기대어 동작한다면 새로운클래스도 반드시 같은 기능을 제공해야 한다.
•
예시로, LinkedHashSet이 따르는 순서 정책을 가정하고 동작하는 상황에서 이를 HashSet으로 바꾸면 문제가 될 수 있다.
◦
HashSet은 반복자의 순회 순서를 보장하지 않기 때문이다.
예외사항
•
적합한 인터페이스가 없다면 당연히 클래스로 참조해야 한다.
◦
String, BigInteger과 같은 값 클래스가 그렇다.
•
클래스 기반으로 작성된 프레임워크가 제공하는 객체들일 경우
◦
이런 경우에도 특정 구현 클래스보다는 기반 클래스를 사용하여 참조하는게 좋다.
◦
OutputStream 등 java.io 패키지의 여러 클래스들이 이 부류에 속한다.
•
인터페이스에는 없는 특별한 메서드를 제공하는 클래스일 경우
◦
PriorityQueue 클래스는 Queue 인터페이스에 없는 comparator 메서드를 제공한다.
◦
클래스 타입을 직접 사용하는 경우 이런 추가 메서드를 꼭 사용해야하는 경우로 최소화해야한다.
65. 리플렉션보다는 인터페이스를 사용하라
•
리플렉션 기능을 이용하면 프로그램에서 임의의 클래스에 접근할 수 있다.
•
Class 객체가 주어지면 그 클래스의 생성자, 메서드 필드에 해당하는 Constructor, Method, Field 인스턴스를 가져올 수 있다.
◦
이어서 이 인스턴스들로는 그 클래스의 멤버 이름, 필드 타입, 메서드 시그니처 등을 가져올 수 있다.
•
Constructor, Method, Field 인스턴스들을 이용해 각각에 연결된 실제 생성자, 메서드, 필드를 조작할 수도 있다.
◦
이 인스턴스들을 통해 해당 클래스의 인스턴스를 생성하거나, 메서드를 호출하거나, 필드에 접근할 수 있다.
리플렉션의 단점
•
컴파일타임 타입 검사가 주는 이점을 하나도 누릴 수 없다.
◦
리플렉션 기능을 써서 존재하지 않는/접근할 수 없는 메서드를 호출하려 시도하면 런타임 오류가 발생한다.
•
리플렉션을 이용하면 코드가 지저분하고 장황해진다.
◦
지루하고, 읽기 어렵다.
•
성능이 떨어진다.
◦
리플렉션을 통한 메서드 호출은 일반 메서드 호출보다 훨씬 느리다.
•
런타임에 6개나 되는 예외를 던질 수 있다.
◦
리플렉션 없이 생성했다면 컴파일타임에 잡어낼 수 있는 예외들이다.
•
클래스 이름으로 인스턴스를 생성하기 위해 코드가 25줄이나 된다.
◦
리플렉션이 아니라면 생성자 호출 한줄로 끝날 수 있었다.
리플렉션의 장점
•
런타임에 존재하지 않을 수도 있는 다른 클래스, 메서드, 필드와의 의존성을 관리할 때 적합하다.
리플렉션을 사용한다면?
•
리플렉션은 아주 제한된 형태로만 사용해야 그 단점을 피하고 이점을 취할 수 있다.
•
리플렉션은 인스턴스 생성에만 쓰고, 이렇게 만든 인스턴스는 인터페이스나 상위 클래스로 참조해 사용하자.
66. 네이티브 메서드는 신중히 사용하라
•
네이티브 인터페이스는 자바 프로그램이 네이티브 메서드를 호출하는 기술이다.
•
네이티브 메서드란 C나 C++같은 네이티브 프로그래밍 언어로 작성한 메서드를 말한다.
네이티브 메서드의 쓰임새
•
레지스트리와 같은 플랫폼 특화 기능을 사용
•
네이티브 코드로 작성된 기존 라이브러리를 사용한다.
•
성능 개선을 목적으로 성능에 결정적인 영향을 주는 영역만 따로 네이티브 언어로 작성한다.
◦
성능을 개선할 목적으로 네이티브 메서드를 사용하는 것은 권장하지 않는다.
네이티브 메서드의 단점
•
네이티브 언어가 안전하지 않으므로 네이티브 메서드를 사용하는 애플리케이션도 메모리 훼손 오류로부터 안전하지 않다.
•
네이티브 언어는 자바보다 플랫폼을 많이 타서 이식성도 낮다.
•
디버깅도 더 어렵다.
•
주의하지 않으면 속도가 오히려 더 느려질 수 있다.
•
가비지 컬렉터가 네이티브 메모리는 자동 회수하지 못하고, 심지어 추적조차 할 수 없다.
•
자바 코드와 네이티브 코드 사이의 접착 코드를 작성해야하는데, 이는 가독성이 떨어지고 귀찮은 작업이다.