라이브러리에서 가시성을 처리하는 일반적인 방법은 무엇입니까?


12

개인 사용시기와 클래스에서 protected 사용시기에 관한 이 질문 은 저를 생각하게했습니다. (이 질문은 최종 클래스 및 메소드와 관련이 있기 때문에 확장 할 것입니다. Java로 프로그래밍 중이지만 모든 OOP 언어와 관련이 있다고 생각합니다)

허용되는 대답은 다음과 같습니다.

경험상 가장 좋은 방법은 모든 것을 가능한 한 비밀로 만드는 것입니다.

그리고 또 하나 :

  1. 즉시 서브 클래스를 작성하지 않으면 모든 클래스를 최종 클래스로 만듭니다.
  2. 서브 클래 싱하고 즉시 재정의 할 필요가없는 한 모든 메소드를 최종으로 만드십시오.
  3. 메소드의 본문 내에서 변경하지 않는 한 모든 메소드 매개 변수를 최종으로 지정하십시오. 어쨌든 대부분의 경우 다소 어색합니다.

이것은 매우 간단하고 명확하지만 응용 프로그램 대신 주로 라이브러리 (GitHub의 오픈 소스)를 작성하는 경우 어떻게됩니까?

많은 도서관과 상황을 말할 수 있습니다.

  • 개발자가 생각하지 못한 방식으로 라이브러리가 확장되었습니다.
  • 가시성 제약으로 인해 "클래스 로더 매직"및 기타 해킹으로 수행해야합니다.
  • 라이브러리는 빌드되지 않은 방식으로 사용되었으며 필요한 기능 방식은 "해킹"되었습니다.
  • 작은 가시성 (버그, 기능 누락, "잘못된"동작)으로 인해 라이브러리를 사용할 수 없었습니다. 가시성 감소로 인해 변경할 수 없었습니다.
  • 해결할 수없는 문제로 인해 간단한 기능 (비공개 또는 최종 기능)을 재정의하는 데 도움이 될 수있는 거대하고 못생긴 버그 해결 방법이 생겼습니다.

그리고 실제로 질문이 너무 길어질 때까지 이러한 이름을 짓기 시작했으며 제거하기로 결정했습니다.

필자는 필요한 것보다 많은 코드, 필요한 것보다 더 많은 가시성, 필요한 것보다 더 많은 추상화가 없다는 아이디어를 좋아합니다. 그리고 이것은 최종 사용자를위한 응용 프로그램을 작성할 때 작동 할 수 있으며, 코드를 작성하는 사람 만 코드를 사용합니다. 그러나 코드가 다른 개발자가 사용하도록 의도 된 경우 어떻게해야합니까? 원래 개발자가 가능한 모든 사용 사례를 미리 생각하고 변경 / 리 팩터를 수행하기가 어렵거나 불가능하다는 것이 불가능합니까?

큰 오픈 소스 라이브러리는 새로운 것이 아니기 때문에 객체 지향 언어를 사용하여 이러한 프로젝트에서 가시성을 처리하는 가장 일반적인 방법은 무엇입니까?



오픈 소스에 관해 물어 보면 폐쇄 소스보다 나열된 문제를 해결하기 위해 적절한 코딩 원칙을 구부리는 것이 훨씬 합리적이지 않습니다. 단순히 필요한 수정 사항을 라이브러리 코드에 직접 제공하거나 포크로 만들어 자체 버전을 만들 수 있기 때문입니다. 그들이 원하는 모든 수정
gnat

2
내 요점은 이것에 관한 것이 아니라 오픈 소스에 대한 귀하의 언급에 관한 것입니다. 실용적 요구가 어떤 경우 ( 기술적 부채 발생이라고도 함) 엄격한 원칙과의 편차를 정당화하는 방법을 상상할 수 있지만, 이러한 관점에서 코드가 닫혔는지 아니면 오픈 소스인지는 중요하지 않습니다. 또는 더 정확하게는 여기에서 상상했던 것과 반대 방향으로 중요합니다. 오픈 소스 인 코드는 이러한 요구를 해결하기위한 추가 옵션을 제공하기 때문에 닫힌 요구보다 덜 압축 할 수 있기 때문에 이러한 요구를 덜 해결할 수 있습니다.
gnat

1
@piegames : 나는 여기에서 모욕에 동의합니다. 당신이 scetch 한 문제는 폐쇄 소스 라이브러리 에서 발생할 가능성이 훨씬 높습니다 . 허가 라이센스가있는 OS 라이브러리 인 경우 관리자가 변경 요청을 무시하면 라이브러리를 포크 할 수 있습니다. 필요한 경우 혼자서 가시성을 변경하십시오.
Doc Brown

1
@piegames : 귀하의 질문을 이해하지 못합니다. "Java"는 lib가 아닌 언어입니다. "작은 오픈 소스 라이브러리"에 가시성이 너무 엄격한 경우 나중에 가시성을 확장해도 이전 버전과의 호환성이 손상되지 않습니다. 다른 방향으로 만 라운드.
Doc Brown

답변:


15

불행히도 진실은 많은 라이브러리가 설계 되지 않고 작성 된다는 것입니다 . 약간의 사전 생각이 길 아래에서 많은 문제를 예방할 수 있기 때문에 이것은 슬픈 일입니다.

라이브러리 디자인을 시작하면 몇 가지 예상 사용 사례가 있습니다. 라이브러리가 모든 사용 사례를 직접 만족하지는 않지만 솔루션의 일부로 제공 될 수 있습니다. 따라서 라이브러리는 적응하기에 충분히 유연해야합니다.

제약 조건은 일반적으로 라이브러리의 소스 코드를 가져 와서 새로운 사용 사례를 처리하도록 수정하는 것이 좋지 않다는 것입니다. 독점 라이브러리의 경우 소스를 사용할 수 없으며 오픈 소스 라이브러리의 경우 분기 버전을 유지하는 것이 바람직하지 않을 수 있습니다. 매우 구체적인 적응을 업스트림 프로젝트에 병합하는 것은 불가능할 수 있습니다.

여기에는 공개 폐쇄 원칙이 적용됩니다. 소스 코드를 수정하지 않고 라이브러리를 확장 할 수 있어야합니다. 그것은 자연스럽게 오지 않습니다. 이것은 의도적 인 디자인 목표 여야합니다. 여기에 도움이되는 풍부한 기술이 있으며 고전적인 OOP 디자인 패턴이 그중 일부입니다. 일반적으로 사용자 코드가 라이브러리에 안전하게 연결되고 기능을 추가 할 수있는 후크를 지정합니다.

모든 메소드를 공개하거나 모든 클래스를 서브 클래 싱하도록 허용하는 것만으로는 확장 성을 달성하기에 충분하지 않습니다. 우선, 사용자가 라이브러리에 연결할 수있는 위치가 확실하지 않은 경우 라이브러리를 확장하는 것이 실제로 어렵습니다. 예를 들어, 기본 클래스 메소드는 암시적인 가정으로 작성되었으므로 대부분의 메소드를 대체하는 것은 안전하지 않습니다. 확장 성을 위해 실제로 설계해야합니다.

더 중요한 것은 일단 무언가가 퍼블릭 API의 일부라면 다시 되돌릴 수 없다는 것입니다. 다운 스트림 코드를 깨지 않으면 리팩토링 할 수 없습니다. 조기 개방성은 라이브러리를 차선책으로 제한합니다. 반대로 내부 물건을 비공개로 만들지 만 나중에 필요한 경우 후크를 추가하는 것이 더 안전한 방법입니다. 라이브러리의 장기적인 발전에 대처하는 건전한 방법이지만, 지금 라이브러리를 사용해야하는 사용자에게는 불만족 스럽습니다 .

대신 어떻게됩니까? 라이브러리의 현재 상태에 심각한 어려움이있는 경우 개발자는 시간이 지남에 따라 누적 된 실제 사용 사례에 대한 모든 지식을 가지고 라이브러리의 버전 2를 작성할 수 있습니다. 좋을 것이다! 모든 디자인 버그를 수정합니다! 또한 많은 경우에 눈부신 듯 예상보다 오래 걸릴 것입니다. 새 버전이 이전 버전과 다른 경우 사용자의 마이그레이션을 권장하기 어려울 수 있습니다. 그런 다음 두 가지 호환되지 않는 버전을 유지 관리합니다.


따라서 공개 / 재정의 가능으로 만드는 것만으로는 충분하지 않으므로 확장을 위해 후크를 추가해야합니다. 또한 이전 버전과의 호환성으로 인해 변경 / 새 API를 언제 릴리스 할 것인지 고려해야합니다. 그러나 특별한 방법의 가시성은 어떻습니까?
piegames 2012 년

@piegames 가시성을 통해 공개 할 부분 (안정적인 API의 일부)과 비공개 부분 (변경 될 수 있음)을 결정합니다. 누군가가 그것을 반영하여 우회한다면, 나중에 그 기능이 깨질 때 문제가됩니다. 그런데 확장 점은 종종 재정의 할 수있는 방법의 형태로되어 있습니다. 그러나 방법의 차이있을 수 있습니다 오버라이드 (override), 그리고하는 방법 구성 (도 템플릿 메소드 패턴 참조) 오버라이드 (override) 할 수는.
amon

8

모든 공개 및 확장 가능한 클래스 / 방법은 반드시 지원해야하는 API의 일부입니다. 라이브러리 의 적절한 서브 세트로 설정 하면 최대한의 안정성을 확보하고 잘못 될 수있는 수를 제한합니다. 합리적으로 지원할 수있는 것을 기반으로하는 관리 결정 (및 OSS 프로젝트도 어느 정도 관리)입니다.

OSS와 비공개 소스의 차이점은 대부분의 사람들이 코드를 중심으로 커뮤니티를 만들고 성장 시키려고 노력하므로 한 사람 이상이 라이브러리를 유지 관리하고 있다는 것입니다. 즉, 사용 가능한 여러 관리 도구가 있습니다.

  • 메일 링리스트는 사용자의 요구와 구현 방법을 설명합니다
  • 이슈 추적 시스템 (JIRA 또는 Git 이슈 등)은 버그 및 기능 요청을 추적합니다.
  • 버전 관리는 소스 코드를 관리합니다.

성숙한 프로젝트에서는 다음과 같은 내용이 표시됩니다.

  1. 누군가가 원래 설계하지 않은 라이브러리로 무언가를하고 싶어합니다.
  2. 문제 추적에 티켓을 추가합니다.
  3. 팀은 메일 링리스트 또는 의견에서 문제를 논의 할 수 있으며 요청자는 항상 토론에 초대됩니다.
  4. 어떤 이유로 API 변경이 승인되고 우선 순위가 지정되거나 거부됩니다.

이 시점에서 변경 사항을 수락했지만 사용자가 수정을 가속화하려는 경우 작업을 수행하고 풀 요청 또는 패치를 제출할 수 있습니다 (버전 관리 도구에 따라 다름).

정적 API가 없습니다. 그러나 성장은 어떤 식 으로든 형성되어야합니다. 물건을 열어야 할 필요성이 보일 때까지 모든 것을 닫아두면 버그가 있거나 불안정한 라이브러리의 명성을 얻지 못합니다.


1
전적으로 동의합니다. 제 3 자 비공개 소스 라이브러리뿐만 아니라 공개 소스 라이브러리에 대해서도 변경 요청 프로세스를 성공적으로 수행했습니다.
Doc Brown

내 경험상 작은 라이브러리의 작은 변경조차도 많은 작업 (다른 것을 설득하는 경우에도)이며 많은 시간이 걸릴 수 있습니다 (다음까지 스냅 샷을 사용할 여유가 없다면 다음 릴리스를 기다리는 데 시간이 걸릴 수 있음). 따라서 이것은 분명히 선택의 여지가 없습니다. 그래도 관심이 있습니다 : 실제로 GitHub에 더 큰 라이브러리가 있습니까?
piegames 2012 년

항상 많은 작업입니다. 내가 기여한 거의 모든 프로젝트에는 비슷한 프로세스가 있습니다. 우리가 아파치 시절에 우리는 우리가 만든 것에 열정을 가지고 있기 때문에 며칠 동안 무언가에 대해 토론 할 수 있습니다. 우리는 많은 사람들이 라이브러리를 사용할 것이라는 것을 알고 있었으므로 제안 된 변경이 API를 중단 시킬지 여부와 같은 것들을 논의해야했습니다. 제안 된 기능은 언제해야합니까? 등
Berin Loritsch

0

나는 그것이 몇몇 사람들과 신경을 강타한 것처럼 보이기 때문에 나의 대답을 다시 말하겠다.

클래스 속성 / 메소드 가시성은 보안 또는 공개 소스와는 아무 관련이 없습니다.

가시성이 존재하는 이유는 객체가 4 가지 특정 문제에 취약하기 때문입니다.

  1. 동시성

캡슐화되지 않은 모듈을 빌드하면 사용자는 모듈 상태를 직접 변경하는 데 익숙해집니다. 이것은 단일 스레드 환경에서 잘 작동하지만 일단 스레드 추가에 대해 생각할 수도 있습니다. 상태를 비공개로 설정하고 getter 및 setter와 함께 잠금 / 모니터를 사용하여 다른 스레드가 리소스를 경주하지 않고 리소스를 기다리게해야합니다. 이는 개인 변수에 일반적인 방식으로 액세스 할 수 없으므로 사용자 프로그램이 더 이상 작동하지 않음을 의미합니다. 이것은 많은 재 작성이 필요하다는 것을 의미 할 수 있습니다.

진실은 단일 스레드 런타임을 염두에두고 코딩하는 것이 훨씬 쉽고 개인 키워드를 사용하면 키워드를 동기화하거나 몇 가지 잠금을 간단히 추가 할 수 있으며 처음부터 캡슐화하면 사용자의 코드가 깨지지 않습니다. .

  1. 사용자가 인터페이스를 간단하게 사용하여 촬영하는 것을 방지 할 수 있습니다. 본질적으로 객체의 불변을 제어하는 ​​데 도움이됩니다.

모든 객체에는 일관된 상태를 유지하기 위해 진실해야하는 많은 것들이 있습니다. 불행히도, 이러한 것들은 각 객체를 자체 프로세스로 옮기고 메시지를 통해 이야기하기 때문에 비용이 많이 들기 때문에 클라이언트 가시 공간에 있습니다. 즉, 사용자에게 전체 가시성이있는 경우 개체가 전체 프로그램을 중단시키는 것이 매우 쉽습니다.

피할 수는 없지만 사용자가 프로그램을 훨씬 더 견고하게 만드는 신중하게 제작 된 인터페이스를 통해 객체의 상태와 상호 작용할 수있게함으로써 우발적 인 충돌을 방지하는 서비스에 대한 인터페이스 클로저를 만들어 실수로 객체가 일관성이없는 상태가되는 것을 방지 할 수 있습니다. . 그렇다고 사용자가 의도적으로 불변량을 손상시킬 수는 없지만 충돌하는 경우 클라이언트가 프로그램을 다시 시작하기 만하면됩니다 (보호하려는 데이터는 클라이언트 측에 저장해서는 안 됨) ).

모듈의 유용성을 향상시킬 수있는 또 다른 좋은 예는 생성자를 비공개로 만드는 것입니다. 생성자가 예외를 throw하면 프로그램이 종료되기 때문입니다. 이것을 해결하는 한 가지 게으른 접근법은 생성자가 try / catch 블록에 있지 않으면 생성 할 수없는 컴파일 타임 오류를 발생시키는 것입니다. 생성자를 private으로 만들고 public static create 메소드를 추가하면 create 메소드가 구성에 실패한 경우 null을 리턴하거나 오류를 처리하기 위해 콜백 함수를 사용하여 프로그램을보다 사용자 친화적으로 만들 수 있습니다.

  1. 범위 오염

많은 클래스는 많은 상태와 메소드를 가지고 있으며, 그것들을 스크롤하려고 시도하는 것은 압도적입니다. 이러한 방법 중 많은 부분은 도우미 기능, 상태와 같은 시각적 노이즈입니다. 변수와 메소드를 비공개로 설정하면 범위 오염을 줄이고 사용자가 원하는 서비스를 쉽게 찾을 수 있습니다.

본질적으로 클래스 외부가 아닌 클래스 내부에서 도우미 기능을 사용하여 벗어날 수 있습니다. 사용자가 절대 사용해서는 안되는 많은 서비스로 사용자를 혼란스럽게하지 않고 가시성을 제어하지 않으므로 메소드를 여러 가지 도우미 메소드로 분류하여 벗어날 수 있습니다 (여전히 범위를 오염 시키지는 않지만 사용자의 범위는 오염 시키지는 않습니다).

  1. 의존성에 묶여있다

잘 만들어진 인터페이스는 작업 수행에 의존하는 내부 데이터베이스 / 윈도우 / 이미징을 숨길 수 있으며 다른 데이터베이스 / 다른 윈도우 시스템 / 다른 이미징 라이브러리로 변경하려는 경우 인터페이스를 동일하게 유지하고 사용자를 유지할 수 있습니다 통지하지 않습니다.

반면에, 이렇게하지 않으면 종속성이 노출되어 코드에 의존하기 때문에 종속성을 변경하는 것이 불가능 해집니다. 충분히 큰 시스템을 사용하면 마이그레이션 비용을 감당할 수없는 반면,이를 캡슐화하면 향후의 의사 결정으로부터 고객 행동을 보호하여 종속성을 교체 할 수 있습니다.


1
"아무것도 숨길 필요가 없습니다"-왜 캡슐화에 대해 생각해야합니까? 많은 맥락에서 성찰에는 특별한 특권이 필요합니다.
Frank Hileman

캡슐화는 모듈을 개발하는 동안 호흡 공간을 제공하고 오용 가능성을 줄이므로 캡슐화에 대해 생각합니다. 예를 들어, 클래스의 내부 상태를 직접 수정하는 4 개의 스레드가있는 경우 문제를 쉽게 야기 할 수 있지만 변수를 전용으로 설정하면 사용자가 공용 메소드를 사용하여 월드 상태를 조작하도록 장려합니다. 모니터 / 잠금을 사용하여 문제를 방지 할 수 있습니다. . 이것이 캡슐화의 유일한 이점입니다.
Dmitry

보안을 위해 물건을 숨기는 것은 API에 구멍이 있어야하는 디자인을 쉽게 만들 수있는 방법입니다. 이에 대한 좋은 예는 도구 상자가 많고 하위 창이있는 여러 창이있는 다중 문서 응용 프로그램입니다. 캡슐화에 신경 쓰면 결국 하나의 문서에 무언가를 그리는 상황이 생길 것입니다. 내부 문서에 내부 문서를 요구하여 내부 문서에 무언가를 요구하도록 요청하는 창을 요청해야합니다 컨텍스트를 무효화합니다. 클라이언트 쪽이 클라이언트 쪽과 놀고 싶을 때는 막을 수 없습니다.
Dmitry

환경이 지원하는 경우 액세스 제어를 통해 보안을 달성 할 수 있지만 OO 언어 설계의 원래 목표 중 하나였습니다. 또한 캡슐화를 홍보하고 동시에 사용하지 말라고 말합니다. 약간 혼란 스럽습니다.
Frank Hileman

나는 그것을 사용하지 않으려 고 결코; 나는 보안을 위해 그것을 사용하지 말 것을 의미했다. 전략적으로이를 사용하여 사용자 경험을 개선하고보다 부드러운 개발 환경을 제공하십시오. 내 요점은 그것이 보안이나 공개의 개방성과는 아무런 관련이 없다는 것입니다. 클라이언트 측 객체는 정의에 따라 내부 검사에 취약하므로 사용자 프로세스 공간 밖으로 이동하면 캡슐화되지 않은 항목이 캡슐화되는 것과 동일하게 액세스 할 수 없습니다.
Dmitry
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.