문자열로 다른 타입을 대체하지 마라
: 문자열을 쓰고 싶은 유혹을 뿌리쳐라
"입력받을 데이터가 진짜 문자열일 때만 그렇게 하는 것이 좋다"
받은 데이터가 수치형이면 int, float, BigInteger 등으로 해야 하고,
예/아니오 형태라면 enum이나 boolean으로 해야 한다.
적절한 데이터 타입이 있다면 그것을 사용해야 한다.
없다면 데이터 타입을 새로 작성하는 편이 좋다.
지켜지지 않는 경우가 많아서 하는 이야기이다 (← 헉 저도 안 지키고 있었어요..!)
혼합 타입을 문자열로 표현하지 마라
className과 i.next()의 혼합 정보를 "#"로 구분하여 문자열 데이터로 보관하고 있다.
두 요소를 구분해 주는 문자 "#"가 명확하므로 괜찮지 않을까?
// 부적절 예시
String compoundKey = className + "#" + i.next();
🚫🚫🚫 아니다 아니다 아니다! ❌ ❌ ❌
1. 혹여나 "#"이 구분자뿐만 아니라 데이터에 사용되었을 때 문제
2. 파싱해야 해서 느리고, 귀찮고, 오류 가능성 커짐 (← 파싱을 생각없이 여기면 안 되겠군요..!)
3. String이 제공하는 기능에만 의존해야 함
🆘 두 데이터를 담는 전용 클래스를 새로 만드는 편이 좋다.
문자열은 권한을 표현하기 부적합하다
권한(capacity)를 문자열로 표현하는 경우가 종종 있는데, 부적절하다.
다음과 같이 '각 스레드가 자신만의 변수를 갖게 해주는 기능'을 설계한다고 해보자.
자바2부터 ThreadLocal이라는 이름으로 지원하는 기능이다. ThreadLocal은 각 쓰레드가 접근할 수 있는 특별한 저장소를 말한다. 즉 Thread-A는 Thread-A의 전용 보관소에서 데이터를 넣고(set) 꺼내쓰고(get) 지울 수(remove) 있다.
놀랍게도 자바2 이전의 프로그래머들은 이를 구현하다가, 모두 최종적으로는 같은 방법으로 설계했다. '클라이언트가 제공한 문자열 키를 통해 스레드별 지역변수를 식별하는 방법'으로 말이다. 즉, 스레드별 문자열 키가 ThreadLocal의 전역 이름공간에 다 노출이 되는 방식이다. 그리고 set(String key, Object value)를 통해 "문자열 키"로 해당 스레드의 어떤 변수를 세팅하고, get(String key)를 통해 "문자열 키"를 통해 해당 스레드의 어떤 변수를 꺼내온다. ThreadLocal 객체를 생성하지 않고 set과 get 정적 메서드로 값을 세팅 및 꺼내온다.
public class ThreadLocal {
private ThreadLocal() { } // 객체 생성 불가
// 현 스레드의 값을 키로 구분해 저장한다.
public static void set(String key, Object value);
// (키가 가리키는) 현 스레드의 값을 반환한다.
public static Object get(String key);
}
문제점은?
ⓐ 스레드 구분용 문자열 키가 전역 이름공간에서 공유된다는 점이다. 의도대로 잘 동작하기 위해서는 각 클라이언트가 고유한 키를 가져야 한다. 그러나 두 클라이언트가 서로 소통하지 못해 같은 키를 쓰고 있다면, TreadLocal이라는 저장소에서 동일한 값에 접근해버리게 된다.
ⓑ 보안에 취약하다. 악의적 클라이언트가 의도적으로 같은 키를 사용하여 값을 가로챌 수 있다.
.
.
올바른 권한 구분 방법은?
문자열을 사용하지 말고, 아래와 같은 Key 클래스를 사용하자.
이제 Key는 문자열과 다르게 위조할 수 없다. 이때 Key를 권한(capacity)이라고 부르면 된다.
public class ThreadLocal {
public ThreadLocal() { } // 객체 생성 불가
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);
}
사실상 Key 인스턴스를 두는 방식으로 개선했다면, Key는 더이상 쓰레드의 지역변수를 꺼내오기 위한 Key가 아니다. Key라는 인스턴스로 구분되므로 Key 자체가 쓰레드의 지역변수가 된다.
이를 고려하여 개선해보자.
- Key가 곧 ThreadLocal이 될 것이므로, 아예 Key 이름을 ThreadLocal로 하자.
- Key가 곧 ThreadLocal이 될 것이므로, set과 get은 정적 메서드일 필요 없다. 그냥 ThreadLocal 인스턴스에 set하고 get 하면 된다.
public final class ThreadLocal {
public ThreadLocal();
public void set(Object value);
public Object get();
}
취약한 부분을 더 개선(타입 안전) 하면 다음과 같다.
아래 구조는 실제 java.lang.ThreadLocal과 유사하다.
최종적으로 문자열 기반 문제점을 해결완료했으며, 키 기반의 API보다 빠르고 우아하다.
public final class ThreadLocal<T> {
public ThreadLocal();
public void set(T value);
public T get();
}
'JAVA > Effective Java' 카테고리의 다른 글
[이펙티브 자바] 객체는 인터페이스를 사용해 참조하라 ─ 9장:일반적인 프로그래밍 원칙:Item64 (0) | 2023.10.21 |
---|---|
[이펙티브 자바] 문자열 연결은 느리니 주의하라 ─ 9장:일반적인 프로그래밍 원칙:Item63 (0) | 2023.10.21 |
[이펙티브 자바] 박싱된 기본 타입보다는 기본 타입을 사용하라 ─ 9장:일반적인 프로그래밍 원칙:Item61 (1) | 2023.10.20 |
[이펙티브 자바] 정확한 답이 필요하다면 float와 double은 피하라 ─ 9장:일반적인 프로그래밍 원칙:Item60 (0) | 2023.10.20 |
[이펙티브 자바] 라이브러리를 익히고 사용하라 ─ 9장:일반적인 프로그래밍 원칙:Item59 (0) | 2023.10.19 |