계약, 혹은 약속에 의한 프로그래밍을 하는 기법이 있습니다.
코드를 작성할 때 주어진 입력값에 대해 검증을 하고, 검증된 입력값에 대해 올바른 동작으로 수행하여 계약한대로 결과값을 내놓게 만드는 이 기법을 지원하기 위해 D 언어의 여러 언어요소가 존재합니다. 이런 방법으로 코드를 작상하면 코드의 품질을 향상시킬 수 있습니다.
코드로 표현된 계약 검증 요소들은 테스트와 디버그용으로 컴파일할 때만 기계어로 변환되며, -release 옵션을 컴파일러에 주어 릴리즈 모드로 컴파일하게 되면 계약 검증 요소가 모두 제거된 말끔한 릴리즈용 파일이 생성됩니다. 따라서 계약 검증 요소들은 사용자의 입력을 검증하거나 Exception 을 대체하여 사용되어서는 안됩니다. 실 배포시에 검증 기능이 동작하지 않기 때문입니다.
assertD 언어에서 사용할 수 있는 가장 간단한 계약 검증 요소는 assert(...) 구문입니다. assert 는 구문 내에 입력된 조건이 충족되어 참이 되지 않으면 AssertionError 를 발생시켜 프로그램 실행을 중단시킵니다.
assert(sqrt(4) == 2);
// AssertionError를 발생시킬 때 표시할 특별한 메시지를 작성할 수 있습니다
assert(sqrt(16) == 4, "sqrt(16) 이 4가 아닙니다. sqrt 함수가 이상합니다.");
in 과 out 코드 블럭으로 입력값과 반환값이 어떤 약속을 지켜야하는지 정할 수 있습니다. body 에 실제 실행될 함수를 작성합니다.
long square_root(long x)
in {
assert(x >= 0);
} out (result) {
assert((result * result) <= x
&& (result+1) * (result+1) > x);
} body {
return cast(long)std.math.sqrt(cast(real)x);
}
in 부분은 사실 실제 실행될 코드가 적힌 함수의 body 에 작성해도 됩니다. 하지만 in 을 사용하여 입력값에 대한 약속을 검증할 때, 프로그래머가 입력값에 대한 약속을 확인하려는 의도가 더욱 분명히 나타납니다.
out 부분을 작성할 때는 함수의 body 가 반환할 값을 잡아내기 위해 out(result) 형태로 작성합니다. 이렇게 함수의 반환값은 반환과는 별도로 result 라는 이름으로 접근할 수 있게 됩니다. 이어서 result 를, 약속에 따라 값이 처리되었는지 out 코드 블럭에서 검증하면 됩니다.
invariant() 는 struct 나 class 로부터 만들어진 값이나 객체가, 내부 함수가 호출된 후에도 약속대로 동작하기 위한 정상 상태를 유지하고 있는지 점검하기 위한 특별한 내부 함수입니다.
struct 나 class 의 내부 함수가 호출될 때 호출되고, 종료될 다시 invariant() 가 호출됩니다. 즉, 함수 하나가 호출되면 총 2회 호출됩니다
섹션 첫머리에서부터 언급했듯, 이러한 프로그래밍 계약 요소들은 릴리즈 모드로 컴파일될 때 모두 사라집니다. 그리고 assert 는 Exception 이 아니라 Error(프로그램 실행 중단) 를 일으키기 때문에 nothrow 로 표시된 함수 내에서도 쓰일 수 있습니다.
그래도 프로그램 실행 중에 사용자 입력을 검증하는 것은 중요합니다. 이런 용도로 릴리즈 모드에서도 assert 와 비슷한 기능을 이용하고 싶다면 std.exception.enforce 를 사용하면 됩니다. enforce 는 프로그래머가 처리할 수 있는 Exception 을 Error 대신 발생시키기 때문에, 입력값이 올바르지 않을 경우의 처리를 해줄 수 있습니다.