-
[Java] Effective Java 3/E 정리 - 9장 일반적인 프로그래밍 원칙Study/이펙티브 자바 2021. 7. 28. 19:09반응형
이번 장에서는 지역변수, 제어구조, 라이브러리, 데이터 타입, 리플렉션과 네이티브 메서드에 대해서 다룬다.
그리고 추가로 최적화와 명명 규칙에 대해서도 알아보자.
참고로 이번 장은 아이템 제목만 봐도 알 수 있을 정도이기 때문에 핵심 방법과 그 이유에 대해서만 간단하게 설명한다.
(절대 과제 기한이 촉박해서 그런게 아니다 😉....)
Item57. 지역변수의 범위를 최소화하라
지역변수의 유효 범위를 최소로 줄이면 코드 가독성과 유지보수성이 높아지고 오류 가능성은 낮아진다.
지역범수의 범위를 줄이면서 효과적으로 사용하는 방법은 아래와 같다.
- 가장 처음 쓰일 때 선언하기
- 모든 지역변수는 선언과 동시에 초기화한다.
- while문보다는 반복문을 사용하면 반복 변수의 범위를 제한하기 좋다.
- 메서드를 작게 유지하고 한 가지 기능만을 처리한다.
Item58. 전통적인 for문보다는 for-each문을 사용하라
for문의 문제점은 아래와 같다.
- 반복자와 인덱수 변수가 많이 쓰이면 코드가 지저해질 뿐 도움이 되지 않는다.
- 컬렉션이냐 배열이냐에 따라 코드가 달라지므로 주의해야 한다.
for-each는 반복자와 인덱스 변수를 사용하지 않을 뿐더러,
하나의 관용구로 컬렉션과 배열을 모두 처리할 수 있다.
따라서 for-each문을 사용하면 for문보다 코드를 깔끔하게 작성할 수 있고, 오류도 줄어든다.
특히 컬렉션을 중첩해서 사용할 때 더욱 효과적이다.
그러나 세가지 상황에서는 for-each문을 사용할 수 없다.
- 파괴적인 필터링(destructive filtering) - 컬렉션을 순회하면서 선택된 원소를 제거해야 한다면 반복자의 remove 메서드를 호출해야 한다. 자바 8부터는 Collection의 removeIf 메서드를 사용해 컬렉션을 명시적으로 순회하는 일을 피할 수 있다.
- 변형(transforming) - 리스트나 배열을 순회하면서 그 원소의 값 일부 혹은 전체를 교체해야 한다면 리스트의 반복자나 배열의 인덱스를 사용해야 한다.
- 병렬 반복(parallel iteration) - 여러 컬렉션을 병렬로 순회해야 한다면 각각의 반복자와 인덱스 변수를 사용해 엄격하고 명시적으로 제어해야 한다.
세가지 상황에 속한다면 일반적인 for문을 사용하되, for문을 쓸 때의 문제점을 주의하자.
Item59. 라이브러리를 익히고 사용해라
라이브러리를 사용한다면 아래와 같은 이점이 있다.
- 코드에 발생하는 결함을 직접 해결할 필요없이 라이브러리를 사용함으로써 해결할 수 있다. (특히 전문가의 지식과 다른 프로그래머의 지식으로...)
- 핵심적인 기능과 관련없는 일에 시간을 허비하지 않아도 된다.
- 따로 노력하지 않아도 성능이 지속해서 개선된다.
- 기능이 점점 많아진다.
- 작성한 코드가 많은 사람들에게 낯익은 코드가 된다. 이는 더 가독성을 좋고, 유지보수 하기 좋고, 활용하기 좋다는 것과 동일하다.
표준 라이브러리를 잘 사용하지 못하는 것은 그런 기능이 있는지 몰라서일 것이다.
메이저 릴리스마다 수많은 기능이 추가되니, 한 번쯤 읽어보자.
라이브러리가 필요한 기능을 충분히 제공하지 못할 경우, 서드파티 라이브러리도 좋다.
Item60. 정확한 답이 필요하다면 float와 double은 피하라
float와 double은 과학과 공학 계산용으로 설계되었다.
이진 부동소수점 연산에 쓰이며, 넓은 범위의 수를 빠르게 정밀한 '근사치'로 계산하도록 설계되었다.
따라서 정확한 결과가 필요할 때는 쓰면 안되고, 특히 금융 관련 계산에 사용하면 안된다.
이를 대체해서 BigDecimal, int 혹은 long을 사용하자.
단, 각각의 장단점이 있기 때문에 범위와 성능을 고려해서 선택한다.
BigDecimal은 18자리 이상의 숫자에도 속도는 느리나 반올림을 완벽히 제어할 수 있고,
int는 아홉 자리 십진수, long은 18자리 십진수까지 사용가능하다.
Item61. 박싱된 기본 타입보다는 기본 타입을 사용하라
자바에서는 기본 타입과 참조 타입으로 나눌 수 있다.
그중에서 int, double, boolean과 같은 기본타입에 대응하는 Integer, Double, Boolean과 같은 참조타입을 박싱된 기본 타입이라고 한다.
기본 타입과 박싱된 기본 타입은 오토박싱과 오토언박싱덕분에 크게 구분없이 사용할 수 있다.
그러나 둘 사이에는 분명히 차이점이 존재하기 때문에 주의해서 사용해야 한다.
- 기본 타입은 값만 가지고 있으나, 박싱된 기본 타입은 값에 더해 식별성이란 속성을 갖는다.
- 기본 타입 값은 언제나 유효하나 박싱된 기본 타입은 null을 가질 수 있다.
- 기본 타입이 시간과 메모리 사용면에서 더 효율적이다.
따라서 기본 타입과 박싱된 기본 타입 둘 다 선택 가능하다면 기본 타입을 사용하자.
박싱된 기본타입은 컬렉션의 원소, 키와 값으로 쓰거나 리플렉션을 통해 메서드를 호출할 때 사용하자.
Item62. 다른 타입이 적절하다면 문자열 사용을 피하라
문자열을 의도하지 않은 용도로 사용하지 말자.
잘못 사용할 경우 번거롭고 덜 유연하고 느리고 오류 가능성이 크다.
문자열은 다른 값 타입을 대신하기에 적합하지 않다.
입력 받는 데이터가 수치 데이터라면 int, float, BigInteger 등의 적당한 수치 타입으로 변환해야 한다.
또한 '예/아니오'와 같은 질문의 답이라면 boolean을 사용해야 한다.
문자열은 열거 타입을 대신하기에 적합하지 않다.
상수를 열거할 때는 문자열보다는 열거 타입을 사용하자.
문자열은 혼합 타입을 대신하기에 적합하지 않다.
여러 요소가 혼합된 타입이라면 구분해주는 문자가 요소에 사용됐는지 알 수 없다면 오류 발생 가능성이 있다.
차라리 전용 클래스를 생성하자.
문자열은 권한을 표현하기에 적합하지 않다.
문자열 키로 권한을 구분할 경우 의도치 않은 오류가 발생하거나 보안에 취약하다.
예를 들어 아래와 같이 문자열 키를 이용해 스레드별 지역변수를 식별하는 코드가 있다.
public class ThreadLocal { private ThreadLocal() { } // 객체 생성 불가 // 현 스레드의 값을 키로 구분해 저장한다. public static void set(String key, Object value); // (키가 가리키는) 현 스레드의 값을 반환한다. public static Object get(String key); }
문제는 스레드 구분용 문자열 키가 전역 namespace에서 공유된다는 점이다.
만약 두 클라이언트가 같은 키를 사용하게되면 의도치 않게 같은 변수를 공유하게 되고, 두 클라이언트 모두 제대로 기능하지 못할 것이다.
Item63. 문자열 연결은 느리니 주의하라
문자열 연결 연산자로 문자열 n개를 잇는 시간은 n의 제곱에 비례한다.
성능 저하를 피하고 싶다면 String 대신 StringBuilder를 사용하자.
Item64. 객체는 인터페이스를 사용해 참조하라
적합한 인터페이스만 있다면 매개변수뿐만 아니라 반환값, 변수, 필드를 전부 인터페이스 타입으로 선언하라.
// 좋은 예. 인터페이스를 타입으로 사용했다. Set<Son> sonSet = new LinkedHashSet<>(); // 나쁜 예. 클래스를 타입으로 사용했다. LinkedHashSet<Son> sonSet = new LinkedHashSet<>();
인터페이스를 타입으로 사용한다면 프로그램이 유연해질 수 있다.
단, 주의할 점으로는 원래의 클래스가 인터페이스 일반 규약 외의 특별한 기능을 제공하며,
주변 코드가 이 기능에 기대어 동작한다면 새로운 클래스도 반드시 같은 기능을 제공해야 한다.
예를 들어 첫번째 코드의 LinkedHashSet이 따르는 순서 정책을 가정하고 동작하는 상황에서 이를 HashSet으로 변경하면 문제가 된다.
Item65. 리플렉션보다는 인터페이스를 사용하라
리플렉션 기능(java.lang.reflect)을 이용하면 프로그램에서 임의의 클래스에 접근할 수 있다.
나아가 Constructor, Method, Field 인스턴스를 이용해 각각에 연결된 실제 생성자, 메서드, 필드를 조작할 수도 있다.
예를 들면 Method.invoke는 어떤 클래스의 어떤 객체가 가진 어떤 메서드라도 호출할 수 있게 해준다.
리플렉션을 이용하면 컴파일 당시에 존재하지 않았던 클래스도 이용할 수 있다.
그러나 아래와 같은 단점이 있다.
- 컴파일타임 타입 검사가 주는 이점을 누릴 수 없다. 리플렉션 기능을 써서 존재하지 않는, 접근할 수 없는 메서드를 호출하면 런타임 오류 발생
- 리플렉션을 이용하면 코드가 지저분하고 장황해진다.
- 성능이 떨어진다.
따라서 리플렉션은 아주 제한된 형태로 사용해야한다.
컴파일타임에 적절한 인터페이스나 상위클래스를 이용할 수 있는 경우에 리플렉션은 인스턴스 생성에 사용하고,
인스턴스는 인터페이스나 상위클래스로 참조해 사용하자.
다른 방법으로는 런타임에 존재하지 않을 수도 있는 다른 클래스, 메서드, 필드와의 의존성을 관리할 때 적합하다.
이 기법은 버전이 여러 개 존재하는 외부 패키지를 다룰 때 유용하다.
그러나 이경우에도 해당 클래스나 메서드가 런타임에 존재하지 않을 수도 있다는 사실을 주의하며 구현하도록 하자.
Item66. 네이티브 메서드는 신중히 사용하라
자바 네이티브 인터페이스는 자바 프로그램이 네이티브 메서드를 호출하는 기술이다.
여기서 네이티브 메서드란 C나 C++ 같은 네이티브 프로그래밍 언어로 작성한 메서드를 말한다.
네이티브 메서드의 주요 쓰임은 다음 세 가지다.
- 레지스트리 같은 플랫폼 특화 기능을 사용한다.
- 네이티브 코드로 작성된 기존 라이브러리를 사용한다. 레거시 데이터를 사용하는 레거시 라이브러리가 그 예다.
- 성능 개선을 목적으로 성능에 결정적인 영향을 주는 영역만 따로 네이티브 언어로 작성한다.
성능을 개선할 목적으로 네이티브 메서드를 사용하는 것은 거의 권장하지 않는다.
그러나 네이티브 라이브러리 쪽은 GNU 다중 정밀 연산 라이브러리(GMP)를 필두로 개선 작업이 계속되어왔기 때문에
고성능의 다중 정밀 연산이 필요하다면 네이티브 메서드를 통해 GMP를 사용하는 것을 고려해도 좋다.
네이티브 메서드에는 심각한 단점이 있는데,
네이티브 언어가 안전하지 않으므로 메모리 훼손 오류로부터 안전하지 않다.
자바보다 플랫폼을 많이 타서 이식성도 낮고, 디버깅도 더 어렵다.
가비지 컬렉터도 사용할 수 없고, 자바 코드와 네이티브 코드를 넘나들 때마다 비용도 추가된다.
마지막으로 네이티브 메서드와 자바 코드 사이의 접착 코드를 작성해야 하는데, 이는 귀찮은 작업이고 가독성도 떨어진다.
Item67. 최적화는 신중히 사용하라
빠른 프로그램보다 좋은 프로그램을 작성하라 → 성능 때문에 견고한 구조를 희생하지 말자.
- 성능을 제한하는 설계를 피하라
- API를 설계할 때 성능에 주는 영향을 고려하라
- 성능을 위해 API를 왜곡하지 말라
- 각각의 최적화 시도 전후로 성능을 측정하라
Item68. 일반적으로 통용되는 명명 규칙을 따르라
자바는 명명 규칙이 잘 정립되어 있으며, 자바 언어 명세에 기술 되어 있다.
표준 명명 규칙을 따르면 좋다 :)
'Study > 이펙티브 자바' 카테고리의 다른 글
[Java] Effective Java 3/E 정리 - 11장 동시성 (0) 2021.08.11 [Java] Effective Java 3/E 정리 - 10장 예외 (0) 2021.07.31 [Java] Effective Java 3/E 정리 - 8장 메서드 (2) 2021.07.18 [Java] Effective Java 3/E 정리 - 6장 열거 타입과 어노테이션 (0) 2021.07.07 [Java] Effective Java 3/E 정리 - 4장 클래스와 인터페이스 (0) 2021.06.23