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

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

    • 다른 타입이 적절하면, 문자열 대신 새로운 타입을 만들어라.
    • 문자열은 정말로 문자열을 표현할 때만 사용해라
    • 문자열은 다른 값 타입을 대신하기에 적절하지 않음. 
    • 문자열은 열거 타입을 대신하기에 적합하지 않음.
    • 문자열은 혼합 타입을 대신하기에 적합하지 않음. 
    • 문자열은 권한을 표현하기에 적합하지 않음

     


    문자열은 문자열인 경우에만 사용해라.

    자바에서는 문자열을 잘 지원해준다. 그래서 사람들은 애매할 때는 문자열을 이용해서 표현을 할 때가 많다. 하지만 입력받을 데이터가 진짜 '문자열'을 의미할 때만 문자열을 사용해야하고 그렇지 않다면 적절한 다른 타입을 사용해야만 한다.

    • 받은 데이터가 수치형 → int, float, BigInteger 등 적당한 수치 타입으로 변환. 
    • 예/아니오 질문의 답 → Boolean

    문자열이 정말로 문자열을 표현하고자 하는 것이 아니라면 기존에 존재하는 적절한 타입으로 바꿔서 사용해야 한다. 


    문자열은 혼합 타입을 표현하기에 적합하지 않음. 

    문자열은 혼합 타입을 표현하기에는 적합하지 않다. 아래처럼 문자열을 이용해서 혼합 타입을 만들었다고 가정해보자. 어떤 문제가 있을까?

    public static void main(String[] args) {
        String className = "A";
        String value = "B";
    
        String compoundKey = className + "#" + value;
    }
    • className, value 중 하나라도 "#"를 가지는 경우라면?
    • 혼합 타입을 위해서 다양한 기능이 필요하다면? 

    두 가지 모두 적절하지 않다. 문자열로 혼합 타입을 이용하려고 한다면, "#" 부분을 파싱해서 별개로 처리해주는 로직이 들어가야 할 것이다. 이 로직을 구현하는 것 자체도 문제가 있을 수 있지만, 더욱 문제는 className, value에 "#"를 오는 예외 케이스의 경우에 어떤 대응을 해야할지 추가적으로 고민을 해야한다는 것이다.

    또한 compoundKey에서 String이 제공하는 메서드 외에도 다양한 기능이 필요한 경우에, String 만으로는 한계가 존재한다. 

    이 방법의 해결책은 앞서 이야기 한 것처럼 문자열이 아닌 경우에는 '적절한 타입'을 하나 새로 생성하면서 해결할 수 있다. 문자열로 표현된 위의 혼합 타입은 아래처럼 클래스로 표현하는 것이 더 자연스럽고, 기능을 확장하기에도 편리하다.

    static class CompoundKey{
        private final String className;
        private final String value;
    
        public CompoundKey(String className, String value) {
            this.className = className;
            this.value = value;
        }
        
        public void addFunction(){}
    }

     


    문자열을 사용해 권한을 표현한 잘못된 케이스

    문자열은 '문자열'의 의도로만 사용되어야 한다. 만약 문자열을 권한으로 사용한다면, 그에 따른 문제가 발생할 수 있다. 문자열로 권한을 표현했을 때 발생할 수 있는 문제점을 아래에서 살펴보고자 한다.

    public class ThreadLocal1 {
        // 객체 생성 불가.
        private ThreadLocal1() {}
    
        // 현 스레드의 값을 키로 구분해 저장한다.
        public static void set(String key, Object value) {};
    
        // (키가 가리키는) 현 스레드의 값을 반환한다.
        public static Object get(String key) {return null; }
    }
    

    문자열로 권한을 구분해서 쓰레드 로컬을 구현하고자 하는 코드다. 각 쓰레드는 Key 값을 이용해 ThreadLocal 클래스에 값을 저장하려고 한다. 그런데 이 때 한 가지 문제점이 있다.

    • 쓰레드1, 쓰레드2가 "A"라는 Key에 대해서 value를 저장하려고 한다면? 

    위 경우에는 반드시 동시성 문제가 발생한다. 이대로 쓰기에는 ThreadLocal 클래스를 사용할 때 고려해야 할 점이 너무 많아진다. 이 문제는 각 쓰레드가 접근 가능한 '권한' 문제를 String 타입으로 표현했기 때문이다. String 대신에 적절한 타입을 만들어서 권한을 표현해줘야 한다.

    public class ThreadLocal2 {
        // 객체 생성 불가.
        private ThreadLocal2() {}
    
        // 현 스레드의 값을 키로 구분해 저장한다.
        public static void set(Key key, Object value) {};
    
        // (키가 가리키는) 현 스레드의 값을 반환한다.
        public static Object get(Key key) {return null; }
        
        static class Key{}
    }

    String으로 권한을 표현했었는데, 대신 적절한 클래스 Key를 하나 만들어준다. 이 클래스를 이용해서 쓰레드 로컬에 접근하는 쓰레드의 권한을 분리할 수 있다. 권한을 분리할 수 있는 이유는 각 쓰레드가 Key 인스턴스를 생성했을 때, 그 인스턴스는 서로 다른 객체이기 때문이다. 

    public class ThreadLocal3 {
        public void set(Object value) {};
        public Object get() {return null; }
    }
    • ThreadLocal2는 껍데기의 역할만 하고 있고, 실질적으로 Key라는 클래스가 모든 작업을 한다. 따라서 ThreadLocal2를 없애고, Key 클래스를 ThreadLocal3로 바꾼다. 
    • Key 클래스가 쓰레드로컬 그 자체가 되었기 때문에 set(), get() 메서드는 더 이상 static일 필요가 없다.

    그런데 현재 ThreadLocal은 저장된 값이 모두 Object라는 문제점이 있다. 값을 넣을 때는 문제가 있으나, 값을 꺼내서 사용할 때 사용하는 쪽에서 '타입 변환'을 해야한다는 단점이 있다. 이 부분을 해결하기 위해 제네릭을 사용하면 된다.

    public class ThreadLocal4<T> {
        public void set(T value) {};
        public T get() {return null; }
    }

    최종적으로는 문자열을 사용했던 권한 분리를 이처럼 클래스로 적절히 분리해 볼 수 있게 된다. 

    댓글

    Designed by JB FACTORY