아라한사 2019. 9. 10. 21:07

계약

- 전달받은 메시지의 규격 (precondition )

- 사후조건 (postcondition ) 

- 자기검증? (invariant)

 

불변식 (invariant)

- 메시지와 무관한 객체의 상태

- 일반적으로 필드값의 상태 점검

- DI에게 위임하거나 초기화 할당으로 처리 : 초기화할당은 객체를 만드는 사람의 역량? 그렇기 때문에 DI

 

코드 이야기

지난 시간에 배웠던 요금제도를 가지고서 이야기를 하기로 함

 

Plan -> Calculator, Set<Call> 가지고 있음

 

필드에 대해서는 널을 없애기 위해서 엠프티를 가짐.

Calculrator calulator = new Calculrator()

 

사전조건 (precondtion)

일반적으로 validation

메시지로 받는 값을 스스로 검증함

검증이 확정된 형으로 갈음할 수 있음 ) Ex :: Money. 원래는 Integer이지만 형이 보장되게끔 중간에 브릿지를 두기도 함.

 

프로그래밍 이론으로는 화이트리스트 이론. 위는 블랙리스트 이론, 화이트리스트를 거친 애들만 가지고서 화이트에서 전개

쉴드패턴 ( if 문으로 자름 )

 

코드 이야기

addCall(Call call) { ==> null 문제

모든 메서드의 시그니처를 무력화 할 수 있다

 

널문제의 해결

1. 언어나 컴파일러의 기능 @NonNull 이 어노테이션은 컴파일타임임....

언어에서 변수가 할당될 때 널인지 아닌지 아는 경우에는 막을 수 있음 ( 타입스크립트, 코틀린, 스위프트 등등 )

 

2. if로 슬그머니 무시 :: 하지만 자바 컬렉션에서 은근히 많이 사용되고 있다.

최소한 if 자체를 사용하는 것은 나쁘지 않지만 외부에서 확인할 수 있는 플래그를 두는게 맞다, 불린값리턴하여 했다 못했다.

이렇게 부드럽게 처리되는게 맞지 안맞는지를 명확히 해야함.

 

3. 예외처리 (throw)

불린도 런타임, 쓰로우도 런타임이다.

 

+ 추가적인 상태 검사

아무런 전략객체도 없는 경우 == calc.isEmpty() 인 경우?

 

사후조건 (post condition )

일반적으로 결과값 검증이라고 함

보내줄 값이 올바름을 검증함

검즈이 확정된 형으로갈음할 수 있음

리턴을 만드는데 성공하냐 마냐로 밀어버릴 수가 있음

 

코드이야기

calculateFee() 에 대해서 사후검증이 있어야한다. 

0원이 있어서는 안되고, - 이어서는 안된다

 

Money result = calc.calcCallFee(calls, Money.ZERO); 

if(calls.size() > 0 && (result.equals(Money.ZERO) || result.isLessthan(Money.ZERO) throw new RunTimeException("값이 어떻게 음수?"));

 

계약별 책임할당

그런 계약체크를 객체가 가지고 있는게 맞는가?

isEmpty() 를 하고서 예외를 던지는게 Plan 에서 체크하는게 맞나, Calculator 가 맞나?

isEmpty() 는 이미 간접적으로 물어보는 함수 

물어보지 말고 시켜야 한다!

isEmpty() -> check() 체크를 시켰더니 지 스스로 죽인다!

 

사후계약조건 책임할당

result를 가지고서 Plan이 검사함

Plan이 result를 검사해야할 이유가 1도 없다

유일한 의존성을 갖고 있는게 calls 임.. 하지만 파라미터로 줘버림.

Money result = calc.calcCallFee(calls, Money.ZERO); 

 

this 가 안 나왔으므로 calcCallFee 내부에 사후검증을 넣으면 됨.

계약의 준수는 어떠한 사람이 그 책임을 가져야하느냐가 섬세하게 판단되는 것

 

몰았다고 좋은 것이 아님.

진정한 계약은 이것(메서드)을 호출하는 것이다. postCondition은 위임된 메서드안에서 지 스스로 알아서 줘야함

 

받아들일때의 책임은 나한테 있다 

calc.check()를 수행하는 사람은 precondition 받는 쪽임. 

postcondition은 니가 처리해와~

 

처음에는 전부 Plan 에 두었었다.

 

회사에서 룰로 정해야 한다 프리컨디션은 받은 놈이 결정해. 포스트컨디션은 돌려주는 놈이 결정해..

이런 것때문에 코드가 장난감이 된다

프리컨디션과 포스트컨디션의 검사가 일관성이 없기 때문

 

프로그래밍의 70%는 밸리데이션이다.  (일기거리!)

 

협력을 통한 책임분할

Calculator 클래스 이야기

calls.size() > 0 이 빨간색인 이유?

calls 이 외부에서 준거인데 당연히 ?

 

외부에서 호출 할 때 플랜에서 이런식으로 호출

calcCallFee는 절대 0건인 calls를 받지 않는다. 오염되지 않았다! 

 

public final Money calculateFee(){

 calls.size() == 0 ? Money.ZERO : calc.calcCallFee(calls, Money.ZERO)

}

 

런타임에 의한 계약조건

 

Calculator 에 

if(cals.size() == 0) 이라는 check 로직이 있음. 

플랜에 들어가는 시점에 호출되어지게 되어져있음

 

setNext(Calc next){} 는 런타임에 얼마든지 추가될 수 있다는 것을 알 수 있음

 

check 와 setNext간의 시점차이가 있다.

런타임이 어려운 이유는 상태변화가 있기 때문

집어넣을 때 검사하고, 나중에 추가하게 되면..?

 

우리는 setCalculrator 가 check 가 호출되는 것이 뭔가 이상함을 알 수 있다. 얘는 pre나 post 가 아니라

Calculrator.calcCallFee(Set<Call> calls, Money result) 안에 들어가는 자기 실행적 invariant 한 기능이었음.

 

setNext(Calc calc) 안에서도 검증

 

계약의 전파

질문 :: 검증로직을 반복해서 집어넣는 것이 정상인가?

갑을병정무기경신임계에서는 어떻게 계약하나..?

삼성은 갑-병끼리도 계약해야 한다.

 

쌍방간의 계약을 성립해야하는 것인가, 아니면 똑같은 검증로직은 중복되어도 괜찮은가?

이것이 진정한 문제

 

PricePerTime이 사용하는 calc 에서는

Plan 이  calc.calcCallFee(Set<Call> calls, Money moey)을 호출.  올바른 파라미터들을 보냄. 

 

Plan -> Calculator -> Calc 가 보냄

 

public 이기 때문에 다른 메서드에서 나쁜 인자들을 호출할 수가 있다.

이 모든 것들을 계약관계로 사용하면 된다? 패키지를 통해서 이를 해결할 수 있다?

 

가시성을 통한 계약보증

이를 해결하기 위하여 패키지 보증

 

Calculator 의 가시성을

final Money calcCallFee(Set<Call> calls, Money result){ 로 변경하여서 외부에서 호출하지 말고,

plan 이라는 계약서에서 호출 할 수 있게 처리함

 

자바인터페이스는 가시성 누수때문에 public 을 사용해야함

abstract class Calc{

 abstract Money calc(Set<Call> calls, Money result);

}

 

가시성 문제

가시성문제도 계약의 일부

 

설계를 잘하면  패키지로 나눠지고, 패키지에 퍼블릭한 인터페이스가 몇개정도 있다

 

abstract class Calc{

 abstract Money calc(Set<Call> calls, Money result);

}

에서

public abstract class Calc{
	Money calc(Set<Call> calls, Money result){return calculrate(calls, result);}
	abstract protected Money calculrate(Set<Call> calls, Money result);
}

패키지 구성의 핵심은 계약관계에 달려있다