본문 바로가기
프로그래밍 놀이터/디자인 패턴, 리펙토링

[Effective Java] 독자적인 직렬화 형태의 사용을 고려하자

by 돼지왕 왕돼지 2017. 3. 23.
반응형

 [Effective Java] 독자적인 직렬화 형태의 사용을 고려하자


0, @serial, @serialdata, annotation, API, boolean, CLASS, defaultreadobject, defaultwriteobject, deserializing, Effective JAVA, FALSE, invalidclassexception, javadoc 유틸리티, jvm, lazy initialization, long, Private, readobject, Serializable, serialver, serialVersionUID, Stream, streamcorruptedexception, synchronized, transient, writeobject, [Effective Java] 독자적인 직렬화 형태의 사용을 고려하자, 객체 참조, 결함, 고려, 권장 사항, 그래프, 기본 값, 기본 직렬화, 네이티브 데이터 구조, 논리적 내용, 단점, 독자적인 직렬화, 동기화, 런타임, 마킹, 문서 페이지, 물리적 표현, 보안상 안전, 복잡도, 부정적 영향, 불변 규칙, 비호환성, 상위 호환성, 설계, 성능, 성능 향상, 숫자 타임, 스레드, 스택 오버플로우, 시간, 심각한 결함, 역직렬화, 영구, 유연성, 유틸, 이상적인 객체 직렬화, 이식성, 인코딩, 저장 공간, 적합 여부, 정확성, 주석, 직렬화, 직렬화 버전, 직렬화 버전 uid, 직렬화 형태, 클래스, 포기화, 하휘 호환성, 해시 테이블, 해시키


-
클래스를 설계할 때 클래스가 Serializable 을 구현하면서 기본 직렬화 형태를 사용한다면,
나중에 함부로 버릴 수 없고, 그 직렬화 형태를 계속 유지해야 할 가능성이 높다.


-
적합 여부를 우선적으로 고려해보고 기본 직렬화 형태를 수용하자.
기본 직렬화 형태는 유연성, 성능, 정확성의 관점에서 타당하다는 결정이 섰을 때 사용해야 한다.
일반적으로 말하면, 우리가 독자적인 직렬화 형태를 설계한다고 할 때 하게될 인코딩과 대부분 같은 경우에만 기본 직렬화 형태를 사용해야 한다.


-
이상적인 객체 직렬화 형태는 그 객체가 표현하는 논리적 데이터만 포함한 것이다.


-
기본 직렬화 형태는 객체의 물리적 표현이 논리적인 내용과 동일할 때 적합하다.
(물리적 표현은 Class 전체를 묘사한다는 의미인듯)


-
기본 직렬화 형태가 적합하다고 결정했더라도 불변 규칙의 준수와 보안 상의 안전을 보장하기 위해 readObject 메소드를 제공해야 한다.


-
@serial annotation 은 이 문서를 직렬화 형태를 수록하는 문서 페이지에 넣어 달라고 javadoc 유틸리티에 알려주는 역할을 한다.


-
객체의 물리적인 표현이 자신의 논리적인 데이터 내용과 다를 때 기본 직렬화 형태를 사용하면 다음 네 가지 단점을 갖는다.
    1. 외부 API 가 현재의 내부 구현에 영원히 얽매이게 된다.
    논리만 있으면 되는데 물리까지 포함해서 구조를 바꿀 수 없다.
    2. 과도한 저장 공간을 차지할 수 있다.
    3. 시간이 너무 오래 걸린다.
    기본 직렬화 메커니즘은 그래프를 따라 전체를 훑는다.
    4. 스택 오버플로우가 생길 수 있다.
    기본 해당 객체의 그래프를 순환하면서 처리하므로, 그리 크지 않은 크기의 객체 그래프에서 조차 스택 오버플로우가 생길 수 있다.


-
transient 변경자는 그 인스턴스 필드가 기본 직렬화 형태에서 빼라고 마킹하는 것이다.
따라서 기본 직렬화를 사용할 때는 transient 를 잘 사용하는 것도 tip 이다.





-
writeObject 와 readObject 에서 각각 최초 호출하는것은 defaultWriteObject 와 defaultReadObject 여야 한다.
모든 인스턴스 필드가 transient 일 때는 defaultWriteObject 와 defaultReadObject 를 호출하지 않아도 되지만 권장 사항은 아니다.
모든 인스턴스 필드가 transient 이더라도 defaultWriteObject 를 호출하면 직렬화 형태에 영향을 주어 유연성이 훨씬 좋아진다.
하위 호환성, 상위 호환성을 유지하면서 transient 가 아닌 인스턴스 필드를 추가할 수 있기 때문이다.
하위 버전 class의 readObject 메소드에서 상위버전의 stream 을 받았는데 defaultReadObject 호출을 하지 않는다면 StreamCorruptedException 예외가 발생하며 deserializing 이 실패한다.


-
writeObject 와 readObject 함수는 private 이지만 주석을 달아 설명해야 한다.
@serialData annotation 역시 문서를 직렬화 형태 페이지에 넣어달라고 Javadoc 유틸에 마킹하는 것이다.


-
해시 테이블 객체를 직렬화할 때 기본 직렬화 형태를 사용하면 심각한 결함을 초래할 수 있다.
JVM 에 따라 항목의 해시키가 달라질 수 있고, 같은 JVM 에서 실행될 때에도 매번 실행할 때마다 다른 값이 나올 수도 있다.


-
기본 직렬화 형태를 사용하건 안 하건, transient 가 지정되지 않은 모든 인스턴스 필드는 defaultWriteObject 메소드가 호출될 때 직렬화 된다.
JVM 에 따라 값이 변할 수 있는 이식성이 떨어지는 녀석들은 모두 transient 로 바꾸어야 하고,
네이티브 데이터 구조의 포인터를 나타내는 long 필드처럼 하나의 JVM 을 실행하는 동안에 자신의 값이 밀접하게 연관되는 그런 필드들도 transient 로 지정해야 한다.


-
transient 가 아닌 필드를 결정하기에 앞서, 그 필드의 값이 해당 객체의 논리적인 상태에 속하는지 확인하자.
만일 독자적인 직렬화 형태를 사용하면, 많은 부분의 인스턴스 필드가 transient 로 되기 쉽다.
만일 기본 직렬화 형태를 사용하면서 하나 이상의 필드가 transient 라면 인스턴스가 역직렬화될 때 그런 필드들이 자신의 타입에 맞는 기본 값으로 초기화된다는 것을 기억해야 한다.
객체참조 필드는 null, 기본형 숫자 타입 필드는 0, boolean 필드는 false.
만일 transient 필드가 기본 초기화 값으로 초기화되기를 원치 않는다면, defaultReadObject 메소드를 호출하는 readObject 메소드를 제공해야 하며, 그 다음에 원하는 값을 transient 필드에 저장하면 된다.
그런 필드들은 이 방법 대신 최초 사용되는 시점에 lazy initialization 될 수도 있다.


-
기본 직렬화 형태의 사용 여부와는 무관하게, 해당 객체의 전체 상태를 읽는 메소드에 대해서는 객체 직렬화에 따른 동기화를 해야 한다.
예를 들어 모든 메소드를 동기화하여 스레드 안전을 성취하는 스레드 안전 객체가 있고, 기본 직렬화 형태를 사용하기로 한다면, writeObject 메소드에 synchronized 를 걸어주자.


-
동기화 형태의 선택과는 무관하게, 우리가 작성하는 모든 동기화 가능한 클래스에는 명시적인 직렬화 버전 UID 를 선언하자.
이렇게 하면 직렬화 버전을 사용하지 않아서 생길 수 있는 비호환성을 제거할 수 있고, 성능이 약간 향상되기도 한다.
만일 직렬화 버전을 사용하지 않으면 런타임 시에 자동 생성하기 때문에 과도한 연산이 수행될 수 있다.


private static final long serialVersionUID = anyValue;



-
새로운 클래스 작성 시 어떤 값을 주든 상관없다.
serialver 유틸로 값을 생성할 수도 있지만 그냥 아무값이나 줘도 된다.
만약 직렬화 버전 UID 가 없는 기존 클래스를 변경하면서 구 버전을 기준으로 직렬화한 인스턴스를 새 버전에서 deserializable 하도록 하려면
구 버전에서 자동 생성된 값을 사용해야 한다.
구 버전 클래스에 대해 serialver 유틸을 실행하면 그 번호를 얻을 수 있다.


-
기존 버전과 호환되지 않는 새 버전의 클래스를 만든다면 직렬화 버전 UID 선언에 있는 값만 바꿔주면 된다.
이렇게 하면 구 버전의 직렬화된 인스턴스를 역직렬화 하려고 할 때 InvalidClassException 예외가 발생한다.



Summary


클래스가 직렬화 가능해야 하는지 결정할 때는 어떤 직렬화 형태를 사용해야 하는지 신중하게 생각하자.
직렬화 형태가 객체의 논리적인 상태를 적합하게 표현하는 경우에 한해서 기본 직렬화 형태를 사용하자.
그렇지 않다면, 객체를 적합하게 표현하는 독자적인 직렬화 형태를 설계하자.
클래스의 직렬화 형태는 충분한 시간을 들여 설계해야 한다.
외부에 제공하는 메소드를 향후 버전에서 쉽게 제거할 수 없듯이, 직렬화 형태에 포함된 필드도 쉽게 제거할 수 없다.
직렬화 호환성을 유지하기 위해 영원히 보존해야 하기 때문.
직렬화 형태를 잘못 선택하면, 클래스의 복잡도와 성능에 영구적이면서 부정적인 영향을 줄 수 있다.





반응형

댓글