ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Item 15: Minimize the accessibility of classes and members
    독서/Effective Java 2021. 12. 18. 21:01

    클래스와 멤버의 접근 권한을 최소화하라

     

    잘 설계된 구성 요소와 잘못 설계된 구성 요소를 구별하는 가장 중요한 차이점은 구성 요소가 내부 데이터 및 기타 구현 세부 정보를 다른 구성 요소로부터 숨기는 정도이다.

    잘 설계된 구성 요소는 모든 구현 세부 정보를 숨기고 API를 구현과 명확하게 분리한다. 그런 다음 구성 요소는 API를 통해서만 통신하고 서로의 내부 작동을 무시한다.

    정보 은닉 또는 캡슐화로 알려진 이 개념은 소프트웨어 설계의 기본 원칙이다.

     

    정보 은닉, 캡슐화의 이점

    주된 목적은 시스템을 구성하는 구성 요소를 분리하여 개별적으로 개발, 테스트, 최적화, 사용, 이해 및 수정할 수 있기 때문이다.

    • 구성 요소를 병렬로 개발할 수 있으므로 시스템 개발 속도가 빨라진다.
    • 구성 요소를 더 빨리 이해하고 다른 구성 요소에 해를 끼칠 염려가 거의 없이 디버그 또는 교체할 수 있으므로 유지 관리 부담이 줄어든다.
    • 그 자체로 좋은 성능을 유발하지는 않지만 효과적인 성능 조정을 가능하게 해준다. 다른 구성 요소의 정확성에 영향을 주지 않고 해당 구성 요소를 최적화할 수 있다.
    • 소프트웨어 재사용을 높여준다.
    • 시스템이 성공하지 못하더라도 개별 구성 요소가 성공할 수 있기 때문에 대규모 시스템 구축의 위험을 줄여준다.

     

    Java에서의 정보 은닉 기능들

    접근 제어 메커니즘[JLS, 6.6]은 클래스, 인터페이스 및 구성원의 접근 가능성을 지정한다.

    엔터티의 접근 가능성은 해당 선언의 위치와 접근 한정자(private, protected 및 public)에 따라 결정된다.

    이러한 수식어들을 적절히 사용해 정보를 은닉한다.

     

    클래스와 접근 제한자

    각 클래스나 멤버를 가능한 한 접근할 수 없도록 만들자. 작성 중인 소프트웨어의 적절한 기능과 일치하는 가능한 가장 낮은 접근 수준을 사용하자.

    최상위 클래스 및 인터페이스의 경우 package-private 및 public의 두 가지 가능한 접근 수준만 존재한다. public 한정자를 사용하여 최상위 클래스 또는 인터페이스를 선언하면 공개되고, 사용하지 않은 경우 package-private가 된다.

    최상위 클래스나 인터페이스를 package-private으로 만들 수 있다면 그렇게 해야 한다. package-private로 설정하면 구현의 일부로 만들 수 있으며 기존 클라이언트에 해를 끼칠 염려 없이 후속 릴리스에서 이를 수정, 교체 또는 제거할 수 있다. 공개할 경우 호환성을 유지하기 위해 영구적으로 지원해야 한다.
    package-private 최상위 클래스 또는 인터페이스가 하나의 클래스에서만 사용되는 경우 최상위 클래스를 이를 사용하는 유일한 클래스의 private static 중첩 클래스로 만드는 것을 고려하자(Item 24). 이렇게 하면 패키지의 모든 클래스에서 이를 사용하는 하나의 클래스로의 액세스 가능성이 줄어든다.

    그러나 package-private 최상위 클래스보다 public 클래스의 접근 가능성을 줄이는 것이 훨씬 더 중요하다. public 클래스는 패키지 API의 일부이고 package-private 최상위 클래스는 이미 구현의 일부이기 때문이다.

     

    멤버의 접근 수준

    멤버(필드, 메서드, 중첩 클래스 및 중첩 인터페이스)의 경우 4가지 가능한 액세스 수준이 존재한다.

    • private - 멤버가 선언된 클래스에서만 멤버에 액세스할 수 있다.
    • package-private - 멤버가 선언된 패키지의 모든 클래스에서 멤버에 액세스할 수 있다. 멤버가 선언될 때 액세스 수준이 지정되지 않는다면 기본으로 설정된다(기본적으로 공용인 인터페이스 멤버 제외).
    • protected - 멤버가 선언된 클래스의 하위 클래스(몇 가지 제한 사항[JLS, 6.6.2]에 따름) 및 선언된 패키지의 모든 클래스에서 멤버에 액세스할 수 있다.
    • public - 어디에서나 액세스 가능.

     

    공개 범위 설정 방법

    클래스의 public API를 신중하게 디자인한 후에는 다른 모든 멤버를 private로 만들어야 한다.

    동일한 패키지의 다른 클래스가 실제로 멤버에 액세스해야 하는 경우에만 private 한정자를 제거하여 멤버를 package-private로 만들어야 한다. 이 작업을 자주 수행하는 경우 시스템 설계를 재검토하여 다른 분해가 서로 더 잘 분리된 클래스를 생성할 수 있는지 확인해야 한다. private 및 package-private 멤버 모두 클래스 구현의 일부이며 일반적으로 내보낸 API에 영향을 주지 않는다. 그러나 이러한 필드는 클래스가 직렬화 가능(Item 86 & 87)을 구현하는 경우 내보낸 API로 "유출"될 수 있다.
    Public 클래스의 멤버는 접근 수준이 package-private에서 protected로 올라갈 때 접근 가능성이 크게 증가한다. protected 멤버는 공개 API의 일부이며 영원히 지원되어야 한다. 또한 public 클래스의 protected 멤버는 구현 세부 사항에 대해 공개해야 한다(Item 19). protected 멤버는 최대한 줄여야 한다.

    메소드가 수퍼클래스 메소드를 재정의하는 경우 수퍼클래스[JLS, 8.4.8.3]보다 하위 클래스에서 더 제한적인 접근 수준을 가질 수 없다. 이유는 서브클래스의 인스턴스가 슈퍼클래스의 인스턴스를 사용할 수 있는 곳이라면 어디에서나 사용할 수 있어야 하기 때문이다(리스코프 치환 원칙). 이 규칙을 위반하면 하위 클래스를 컴파일하려고 할 때 컴파일러에서 오류 메시지를 생성한다. 이 규칙의 예외는 클래스가 인터페이스를 구현하는 경우 인터페이스에 있는 모든 클래스 메서드를 클래스에서 public으로 선언해야 한다는 점이다.
    테스트를 위해 public 클래스의 private 멤버를 package-private로 만드는 것은 허용되지만 더 높은 접근성을 높이는 것은 허용되지 않는다. 즉, 테스트를 용이하게 하기 위해 클래스, 인터페이스 또는 멤버를 공개 API의 일부로 만드는 것은 허용되지 않는다. 운 좋게도 테스트가 테스트 중인 패키지의 일부로 실행되도록 만들어 패키지 전용 요소에 액세스할 수 있기 때문에 필요하지 않다.
    public 클래스의 인스턴스 필드는 public이면 안된다(Item 16). 필드와 관련된 불변성을 적용하는 기능을 포기하게 되고, 스레드로부터 안전하지 않게 된다. 
    상수는 public static final 필드를 통해 노출할 수 있다. 이러한 필드의 이름은 대문자, 단어는 밑줄로 구분되는 명명법을 사용한다(Item 68). 이러한 필드에는 기본 값이나 불변 객체를 참조해야 한다(Item 17). 가변 객체에 대한 참조를 포함하는 필드에는 최종 필드가 아닌 모든 단점이 존재한다. 참조는 수정할 수 없지만 참조된 개체는 수정될 수 있다.

    길이가 0이 아닌 배열은 항상 변경 가능하므로 클래스에 public static final array 필드 또는 이러한 필드를 반환하는 접근자가 있다면클라이언트는 배열의 내용을 수정할 수 있다. 

    // Potential security hole!
    public static final Thing[] VALUES = { ... };

    일부 IDE는 private 배열 필드에 대한 참조를 반환하는 접근자를 생성하므로 정확히 이 문제가 발생한다는 사실을 주의하자. 문제를 해결하는 방법에는 두 가지가 있다.

     

    배열을 안전하게 사용하는 방법

     

    public 배열을 private로 만들고 public 불변 list을 추가할 수 있다.

    private static final Thing[] PRIVATE_VALUES = { ... };
    public static final List<Thing> VALUES = Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES));

    또는 배열을 private로 만들고 private 배열의 clone을 반환하는 public 메서드를 추가할 수 있다.

    private static final Thing[] PRIVATE_VALUES = { ... };
    public static final Thing[] values() {
    	return PRIVATE_VALUES.clone();
    }

     

     

    모듈 시스템

    Java 9부터 모듈 시스템의 일부로 도입된 두 가지 추가 암시적 액세스 수준이 존재한다. 모듈은 패키지가 클래스의 그룹인 것처럼 패키지의 그룹이다. 

    모듈은 모듈 선언의 공개 선언을 통해 일부 패키지를 명시적으로 내보낼 수 있다. 모듈에서 공개하지 않은 패키지의 Public 및 Protected 멤버는 모듈 외부에서 접근할 수 없다. 모듈 내에서 접근성은 공개 선언의 영향을 받지 않는다.

    모듈 경로 대신 애플리케이션의 클래스 경로에 모듈의 JAR 파일을 배치하면 모듈의 패키지는 공개 여부와 관계 없이 모듈이 아닌 동작으로 되돌아간다. 새로 도입된 접근 수준이 엄격하게 적용되는 한 곳은 JDK 이다. Java 라이브러리에서 공개하지 않은 패키지는 모듈 외부에서 실제로 접근할 수 없다.
    일반적인 Java 프로그래머에게 제한된 유틸리티의 모듈이 제공하는 접근 보호 기능은 대부분 권고 사항이다. 이를 활용하려면 패키지를 모듈로 그룹화하고, 모든 종속성을 모듈 선언에서 명시적으로 만들고, 소스 트리를 재정렬하고, 모듈 내에서 모듈화되지 않은 패키지에 대한 액세스를 수용하기 위해 특별한 조치를 취해야 한다. 모듈이 JDK 자체 외부에서 널리 사용되는지 여부를 말하기에는 너무 이르다. 그 동안에는 꼭 필요한 경우가 아니면 쓰지 말자.

     

    정리

    프로그램 요소의 접근성을 최대한 줄여야 한다. 최소한의 Public API를 신중하게 디자인한 후에는 길잃은 클래스, 인터페이스 또는 멤버가 API의 일부가 되지 않도록 해야 한다. 상수 역할을 하는 public static final 필드를 제외하고 public 클래스에는 public 필드가 없어야 한다. public static final 필드에서 참조하는 객체가 변경 불가능한지 확인하자.

     

    댓글

Designed by Tistory.