|
Liskov 교수의 책을 제작년에 수업시간에 배우고 문서화에 대한 새로운 개념을 가지게 되었습니다. 이 책의 내용을 보면 하나의 함수는 세가지의 내용을 항상 포함해서 문서화 되어야 한다는 것입니다.
Requires 는 그 함수가 정상적으로 동작하기 위해 만족되어야 하는 조건을 명시하고 Effect는 그 함수가 종료된 후의 상태를 명시합니다. 다시 말해 Pre-condition 과 post-condition 에 해당하겠습니다. ![]() int GCD( int a, int b);여기에서 a나 b 가 음수인 경우에는 어떻게 해야할까요? 두가지 방법이 있습니다. 1. 음수인지를 매번 확인하고 음수의 경우에는 예외를 던지거나 예약된 값(예를 들어 -1)을 return 하는 것.두가지의 차이점은 첫번째것은 함수가 호출될때마다 각 argument 의 값을 확인하는 연산이 들어간다는 점과 후자의 경우는 그런 연산을 생략한다는 점입니다. 프로그래머에 따라 선호도가 다를텐데요. 성능을 중요시 하는 게임 프로그래머의 경우라면 후자를 선호해야한다고 생각하고요, 일반 프로그래머라면 첫번째 것을 선호할거라고 생각합니다. 여기에서 Liskov 는 이 문제를 프로그램의 선호도에 의해서가 아니라 정형화된 문서화의 형태로 해결하고 있습니다. 다시 말해 컴파일러나 프로그램적인 요소가 아니라 사람간의 계약(문서)을 통해 문제를 해결하는 방법입니다. 각 경우에 대한 Requires 와 Effect 는 다음과 같습니다. 첫번째의 경우 Requires: none.두번째의 경우 Requires: a 와 b 가 음수가 아니다.여기에서 두번째의 경우를 따를 경우 사용자가 Requires 절을 어기고 음수를 입력하면 어떤일이 벌어질까요? 그것은 정의되지 않은 상태가 되어서 Effect절에서 명시한 효과가 일어날 것이 보장되지 않는 것입니다. 코딩을 오래하신분이라면 프로그램 코드에 의해 강제되지 않고 문서에 의존하는 것에 대해 거부감을 갖을거라고 생각합니다. 하지만 precondition 과 postcondition 개념은 상당히 중요한 개념이고 C++나 Java에서는 문법적으로 지원을 못하고 있는 것 뿐이지 다른 일부 언어에서는 이 기능을 문법적으로 보장해주기도 합니다. C++ 프로그래머라면 Requires 절의 조건을 assert로 확인하고 debug 모드에서만 조건 체크를 하는 것도 한가지 방법이겠습니다. 하지만 이 경우라도 Requires절의 문서화는 필요합니다. 왜냐하면 assert 로 확인하기 어렵거나 (예를 들어 '소수(prime number)'인지를 확인하려면 연산에 비용이 많이 들게되지요), 불가능한 경우도 존재하기 때문입니다. 그리고 해당 함수의 사용자에게 사용할때마다 assert 절을 확인하고 사용하라고 하는 것은 문서화를 하고나서 문서를 항상 확인하라는 것과 마찬가지 비용이 드는 것이죠. Effect 절의 중요성에 대해 좀 더 설명하기 전에 Liskov Substitution principle 을 먼저 설명해야 하겠습니다. 상속을 사용할때에는 항상 하위 클래스가 상위 클레스의 기능을 다 하고 거기에 추가적인 기능을 하는 것이 허용된다는 것입니다. 다른 말로 하면 하위 클래스가 하는 일 중에 상위클레스가 하던 일을 하지 않으면 문제가 발생한다는 것입니다. Liskov 는 이것을 다음과 같이 요약했습니다.
class MyMath {그리고 이것을 사용하는 사용자 코드는 다음과 같겠습니다. MyMath math1 = new MyMath();여기서 -1 이라는 값을 리턴 값으로 (사용자가) 기대하고 코딩을 하고 있습니다. 여기에 이것을 상속해서 문서(specification)를 수정하려고 합니다. class MyNewMath extends MyMath {이 경우 Requires 절이 더 많은 제약을 요구하고 있기 때문에 강해졌습니다. 그리고 Effect절은 하는일이 줄어들었기 때문에 약해졌습니다. 이 기존 사용자는 "하위 클래스는 항상 상위클레스를 대체해서 사용할수 있어야 한다는 상속의 기본 정의"에 따라 기존 코드를 그대로 사용하게 되는데요. 다음과 같이 되겠습니다. MyMath math1 = new MyNewMath();더이상 MyNewMath가 음수 여부를 확인하지 않고, 거기에 더해서 -1을 리턴하지도 않게 되었기 때문에 이 코드는 문제를 발생시키게 됩니다. Effect 절이 힘을 발휘하는 것은 상속 관계에 있어서 post-condition 이 강해졌는지 약해졌는지를 쉽게 확인할수 있게 해준다는 점입니다. Effect 절의 문서화 없이 완전히 test case에만 의존해서 코딩을 한다면, 상위 클레스에서 사용되는 모든 테스트 케이스들이 하위 클래스를 사용했을때에도 동작하는지 모두 확인해보면 되겠습니다. (*PS: 생각해보니 이 테스트에 대한 표현은 정확치 않군요. 자연어상의 표현상으로는 같더라도 동작 방식은 다를수 있습니다. 예를 들어 상위 함수 giveSalary() 의 Effect 가 "월급을 준다." 인 경우, 하위 함수는 "월급을 *반만* 준다"가 될수 있습니다. 왜냐하면 반만 주더라도 월급을 준다는 특성에는 변화가 없기 때문입니다. 그리고 하위 함수에는 예외가 추가로 더 던져질수 있고 이것이 Effect 절에 나열될수 있습니다. 이런 경우 상위 함수에 대한 테스트 케이스는 하위 함수에서 통과 할수 없겠습니다. ) 하지만 역시 test cases 보다는 Effect 절을 문서화 하는 것이 더 실용적일수 있다는 점을 간과해서는 안되겠습니다. 아시다시피 test cases 들은 복잡도는 낮지만 말로 한두 마디로 표현될수 있는 것도 test 코드로 구현을 해 놓으면 몇 페이지 분량이 될수도 있기 때문입니다. 예를 들어 "볼링 점수를 계산한다" 라고 한마디로 설명되는 것을 test case로 구현하려고 하면 몇 백 라인이 필요할수도 있고 가독성이 오히려 떨어지거나 실수가 발생할 여지도 있습니다. 문서화 없이 테스트 케이스에 버그가 있다면 나중에 다른 프로그래머가 봤을때는 그게 버그인지 원래 의도인지 알 방법도 없지요. ![]() Requires 와 Effect 절을 종합해서 정리해보면, 하나의 함수는 이 두가지 설명으로 완전하게 정의될수 있어야 합니다. 다른 말로 하면 이 문서만 작성되면 함수 구현은 누가 어떤식으로 만들던지 별로 상관이 없는 것입니다. 이 개념은 설계의 관점에서 보면 매우 중요합니다. 만약 문서로 설명하는 것이 구현하는 것보다 더 번거롭고 복잡해진다면, 설계에 문제가 있는게 아닌지 다시 한번 확인해볼 필요가 있겠습니다. 왜냐하면 그런 경우 하나의 함수가 하나 이상의 기능을 섞어서 동작하고 있는 경우일 가능성이 높기 때문입니다. 그런 함수는 두개의 다른 함수로 분리해서 다시 설계할 필요가 있겠습니다. Computer Science 연구하는 분들은 이런 문서화 조차 정형화된 문법을 사용해서 표현하려고 노력중인데요, 예를 들면 Z 언어 같은 언어를 사용해서 문서(Specification)를 컴파일하고 동작을 미리 확인하는 것도 가능합니다. 한번 써보려고 SMV 를 구해서 사용해봤는데 아직은 실용성이 없어 보여서 좀 안타깝더군요. | ||||