공개적으로 제공되지 않는 코드에 대한 방어 적 프로그래밍 관행을 따르는 것이 얼마나 필요한가?


45

카드 게임의 Java 구현을 작성하고 있으므로 Zone이라고하는 특수 유형의 컬렉션을 만들었습니다. Java Collection의 모든 수정 메소드는 지원되지 않지만 Zone API에는 메소드가 있습니다.이 메소드 move(Zone, Card)는 카드를 지정된 영역에서 자신으로 이동시킵니다 (패키지 전용 기술에 의해 수행됨). 이런 식으로, 영역에서 카드를 가져 가지 않고 단순히 사라지지 않도록 할 수 있습니다. 다른 영역으로 만 이동할 수 있습니다.

내 질문은, 이런 종류의 방어 적 코딩이 얼마나 필요한가? "올 바르고"올바른 연습처럼 느껴지지만 Zone API가 일부 공용 라이브러리의 일부가되는 것은 아닙니다. 그것은 단지 나를위한 것이므로 표준 컬렉션을 사용하여 더 효율적 일 수있을 때 코드를 스스로 보호하는 것과 같습니다.

이 구역 아이디어를 얼마나 멀리 가져 가야합니까? 누구든지 내가 작성한 클래스의 계약을 유지하는 것에 대해, 특히 공개적으로 제공되지 않는 계약에 대해 얼마나 생각해야하는지에 대해 조언을 해줄 수 있습니까?


4
= ~ s / 필요 / 권장 / gi
GrandmasterB

2
구성에 따라 데이터 유형이 정확해야합니다. 그렇지 않으면 무엇을 구축하고 있습니까? 그것들은 변경 가능한지 아닌지에 따라 유효한 상태에있을 수있는 방식으로 캡슐화되어야합니다. 이것을 정적으로 시행 할 수 없거나 (또는 ​​비합리적으로 어려운 경우에만) 런타임 오류가 발생해야합니다.
Jon Purdy

1
절대 말하지 마 코드를 사용하지 않는 한 코드가 어디에 있는지 확실하게 알 수 없습니다. ;)
Izkata

1
@ codebreaker GrandmasterB의 의견은 대체 표현입니다. "필수"를 "권장"으로 바꾸십시오.
Ricardo Souza

1
Codeless Code # 116 Trust No 아무도 여기에 특히 적합 할 것입니다.

답변:


72

디자인 문제는 다루지 않겠습니다. 비공개 API에서 "올바로"해야하는지에 대한 질문입니다.

그것은 나만을위한 것이므로 내 자신의 코드를 자신으로부터 보호하는 것과 같습니다.

그것이 바로 요점입니다. 어쩌면 그들이 작성한 모든 클래스와 방법의 뉘앙스를 기억하고 실수로 잘못 계약하지 않은 코더가있을 수 있습니다. 나는 그들 중 하나가 아닙니다. 필자가 작성한 코드가 코드를 작성한 후 몇 시간 내에 어떻게 작동해야하는지 종종 잊어 버립니다. 한 번만 이해했다고 생각하면 마음이 기어를 현재 진행중인 문제로 바꾸는 경향이 있습니다 .

이를 방지하기위한 도구가 있습니다. 이러한 도구에는 규칙, 단위 테스트 및 기타 자동 테스트, 전제 조건 확인 및 문서가 포함됩니다 (특별한 순서는 아님). 단위 테스트는 계약이 어떻게 사용되는지에 대해 생각하고 나중에 인터페이스가 어떻게 설계되었는지에 대한 문서를 제공해야하므로 매우 중요합니다.


알아 둘만 한. 과거에는 가능한 한 효율적으로 프로그래밍하기 만 했으므로 이런 아이디어에 익숙해지는 데 어려움을 겪기도합니다. 나는 올바른 방향으로 가고있어 기쁘다.
codebreaker

15
"효율적으로"는 많은 다른 것을 의미 할 수 있습니다! 내 경험상, 초보자 (내가 당신이 하나라고 말하는 것이 아님)는 종종 그들이 얼마나 효율적으로 프로그램을 지원할 수 있는지 간과합니다. 일반적으로 코드는 "새 코드 작성"단계보다 제품 수명주기의 지원 단계에서 훨씬 더 오래 걸리므로 신중하게 고려해야하는 효율성이라고 생각합니다.
Charlie Kilian

2
나는 확실히 동의합니다. 대학으로 돌아가서 나는 그것에 대해 생각할 필요가 없었습니다.
codebreaker

25

나는 보통 몇 가지 간단한 규칙을 따릅니다.

  • 항상 계약에 따라 프로그램하십시오 .
  • 방법이 공개적으로 이용 가능하거나 외부 로부터 입력을받는 경우 방어 조치를 시행하십시오 (예 :) IllegalArgumentException.
  • 내부에서만 액세스 할 수있는 다른 모든 항목에는 어설 션 (예 :)을 사용하십시오 assert input != null.

클라이언트가 실제로 있다면 코드가 잘못 작동하는 방법을 항상 찾을 것입니다. 그들은 항상 반사를 통해 그것을 할 수 있습니다. 그러나 이것이 계약에 의한 디자인 의 아름다움입니다 . 이러한 코드 사용을 승인하지 않으므로 이러한 시나리오에서 해당 코드가 작동한다고 보장 할 수 없습니다.

특정 경우 Zone에 대해 외부인이 사용하거나 액세스하지 않아야하는 경우 클래스를 패키지 전용 (또는 가능하게 final)으로 설정하거나 Java에서 이미 제공하는 모음을 사용하십시오. 테스트를 거쳤으므로 휠을 재발 명할 필요가 없습니다. 이렇게해도 코드 전체에서 어설 션을 사용하여 모든 것이 예상대로 작동하는지 확인할 수 있습니다.


1
계약에 의한 디자인 언급에 +1 행동을 완전히 금지 할 수 없다면 (그리고 그렇게하기 어렵다) 최소한 나쁜 행동에 대한 보장이 없다는 것을 분명히한다. 또한 IllegalStateException 또는 UnsupportedOperationException을 던지는 것을 좋아합니다.
user949300

@ user949300 물론입니다. 나는 그러한 예외가 의미있는 목적으로 도입되었다고 생각합니다. 계약을 존중하는 것이 그러한 역할에 맞는 것 같습니다.
afsantos

16

방어 프로그래밍은 매우 좋습니다.
코드 작성이 시작될 때까지 그렇다면 그렇게 좋은 것은 아닙니다.


좀 더 실용적으로 말하기 ...

물건을 너무 멀리 가져가는 것이 옳은 것 같습니다. 문제 (및 질문에 대한 답변)는 프로그램의 비즈니스 규칙 또는 요구 사항이 무엇인지 이해하는 데 있습니다.

예를 들어 카드 게임 API를 사용하면 부정 행위를 방지하기 위해 수행 할 수있는 모든 것이 중요한 환경이 있습니다. 많은 양의 실제 돈이 관련 될 수 있으므로 부정 행위가 발생하지 않도록 많은 수표를 마련하는 것이 좋습니다.

반면에 SOLID 원칙, 특히 단일 책임에 유의해야합니다. 컨테이너 클래스에 카드가 어디로 가는지 효과적으로 감사하도록 요청하는 것은 다소 어려울 수 있습니다. 카드 컨테이너와 이동 요청을 수신하는 기능 사이에 감사 / 컨트롤러 계층이있는 것이 좋습니다.


이러한 우려와 관련하여 API의 어떤 구성 요소가 공개적으로 노출되어 (따라서 취약한 지), 비공개적이고 덜 노출 된 것에 대해 이해해야합니다. 나는 "부드러운 내부를 가진 단단한 외부 코팅"을 전적으로 옹호하지는 않지만 최선의 노력은 API의 외부를 강화하는 것입니다.

나는 도서관의 최종 사용자가 얼마나 많은 방어 프로그래밍을했는지 결정하는 데 비판적이라고 생각하지 않습니다. 내가 사용하기 위해 작성한 모듈을 사용하더라도 장래에 라이브러리를 호출 할 때 실수로 실수하지 않았는지 확인하기 위해 점검 조치를 취했습니다.


2
"코드 작성이 시작될 때까지 +1" 특히 단기 개인 프로젝트의 경우 방어 적으로 코딩하는 것이 가치보다 훨씬 많은 시간이 걸릴 수 있습니다.
Corey

2
동의하지만, 방어 적으로 프로그래밍하는 것이 / able /하는 것이 좋지만 프로토 타이핑 방식으로 프로그래밍하는 것도 중요합니다. 두 가지를 모두 수행 할 수있는 능력은 방어를 프로그래밍 할 수있는 많은 프로그래머들보다 훨씬 더 적절한 조치를 선택할 수있게 해줍니다.
David Mulder

13

방어 코딩은 퍼블릭 코드에 대한 좋은 아이디어가 아닙니다. 즉시 버리지 않는 코드 에는 좋은 생각입니다. 물론 지금 어떻게 부름을 받아야할지 알지만 , 프로젝트로 돌아올 때부터 6 개월 후를 얼마나 잘 기억할 지 잘 모릅니다.

Java의 기본 구문은 각각 C 또는 Javascript와 같은 저수준 또는 해석되는 언어에 비해 많은 방어 기능을 제공합니다. 메서드 이름을 명확하게 지정하고 외부 "메소드 시퀀싱"이 없다고 가정하면 인수를 올바른 데이터 형식으로 지정하고 올바른 형식의 데이터가 여전히 유효하지 않은 경우 합리적인 동작을 포함 시키면됩니다.

(단, 카드가 항상 존에 있어야한다면, 게임중인 모든 카드가 게임 오브젝트의 전역 컬렉션에 의해 참조되어 존이 더 나은 벅을 얻을 수 있다고 생각합니다. 카드를 가지고있는 것 외에 당신의 존이 무엇을하는지 모르겠 기 때문에 그것이 적절한 지 알기가 어렵습니다.)


1
영역이 카드의 속성 인 것으로 간주했지만 내 카드가 변경 불가능한 개체로 더 잘 작동하기 때문에이 방법이 최선이라고 결정했습니다. 충고 감사합니다.
codebreaker

3
@codebreaker이 경우 도움이 될 수있는 한 가지는 카드를 다른 개체에 캡슐화하는 것입니다. 스페이드의 에이스가 바로 그것입니다. 위치는 신원을 정의하지 않으며 카드는 변경 불가능할 수 있습니다. 영역에 카드 CardDescriptor가 포함되어 있을 수 있습니다. 카드, 위치, 위 / 아래로 상태가 표시되거나 관심이있는 게임의 회전이 포함 된 카드 가있을 수 있습니다. 그것들은 모두 카드의 신분을 변경하지 않는 변경 가능한 속성입니다.

1

먼저 존 목록을 유지하는 클래스를 만들어 존이나 그 안의 카드를 잃지 않도록하십시오. 그런 다음 전송이 ZoneList 내에 있는지 확인할 수 있습니다. 이 클래스는 아마도 하나의 인스턴스 만 필요하기 때문에 일종의 싱글 톤 일 것입니다. 그러나 나중에 Zone 세트를 원할 수 있으므로 옵션을 열어 두십시오.

둘째, Zone 또는 ZoneList가 Collection 또는 기타 필요한 것을 구현하지 않는 한 Collection을 구현하지 마십시오. 존 또는 ZoneList이 컬렉션을 기대하고 뭔가에 전달 될 경우 즉, 다음 을 구현합니다. 메소드가 예외 (UnimplementedException 또는 이와 유사한 것)를 발생 시키거나 단순히 아무것도하지 않도록함으로써 많은 메소드를 사용 불가능하게 할 수 있습니다. (두 번째 옵션을 사용하기 전에 정말 열심히 생각하십시오. 쉽게 할 수 있기 때문에 조기에 발견했을 수있는 버그가 빠진 것을 발견 할 수 있습니다.)

"올바른"에 대한 실제 질문이 있습니다. 그러나 일단 그것이 무엇인지 알아 내면 그런 식으로 일을하고 싶을 것입니다. 2 년 안에 당신은이 모든 것을 잊어 버릴 것입니다. 그리고 당신이 코드를 사용하려고하면, 그와 같은 반 직관적 인 방식으로 코드를 작성한 사람에 대해 정말 화가 났으며 아무 것도 설명하지 않았습니다.


2
귀하의 답변은 OP가 일반적으로 방어 프로그래밍에 대해 요구하는 광범위한 질문 대신에 당면한 문제에 너무 집중합니다.

실제로 Zones를 Collections를 사용하는 메소드로 전달하므로 구현이 필요합니다. 그러나 게임 내 영역의 레지스트리는 흥미로운 아이디어입니다.
codebreaker

@ GlenH7 : 특정 예제로 작업하면 추상적 이론보다 더 많은 도움이되는 것으로 나타났습니다. OP는 다소 흥미로운 것을 제공했기 때문에 그와 함께했습니다.
RalphChapin

1

API 디자인의 방어 적 코딩은 일반적으로 입력을 검증하고 적절한 오류 처리 메커니즘을 신중하게 선택하는 것입니다. 다른 답변에서 언급 한 것들도 주목할 가치가 있습니다.

이것은 실제로 당신의 예가 아닙니다. 매우 구체적인 이유로 API 표면을 제한하고 있습니다. GlenH7이 언급 했듯이 , 카드 세트가 실제 게임에서 사용될 때, 예를 들어 ( 'used'및 'unused') 데크, 테이블 및 손을 사용하면 각 카드를 확인하기 위해 체크를해야합니다. 세트의 카드는 한 번만 존재합니다.

"영역"으로 이것을 설계했다는 것은 임의의 선택입니다. 구현에 따라 (영역은 핸드, 데크 또는 테이블 일 수 있음) 철저한 디자인 일 수 있습니다.

그러나 그 구현 Collection<Card>은 덜 제한적인 API를 사용 하여 더 유사한 카드 세트의 파생 된 유형처럼 들립니다 . 예를 들어 손값 계산기 또는 AI를 구축하려는 경우 반복 할 각 카드의 수와 개수를 자유롭게 선택할 수 있습니다.

따라서 해당 API의 유일한 목표가 각 카드가 항상 영역에 있는지 확인하는 것이라면 이러한 제한적인 API를 공개하는 것이 좋습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.