코드스피츠 S84 #2
계약
- 전달받은 메시지의 규격 (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);
}
패키지 구성의 핵심은 계약관계에 달려있다