Head First Object-Oriented Analysis & Design이라는 책을 읽다 보니 Programming By Contract와 Defensive Programming 방법을 비교한 부분이 있습니다. ((Head First 시리즈는 Head First Design Pattern 다음으로 두번째인데 너무 맘에 드는 시리즈입니다. Design Pattern이 뭔지 OOA&D가 뭔지 다 아시는 분들에게도 강추!!)) Programming By Contract는 Design By Contract(DBC)라는 말로 더 잘 알려져 있죠.
간단히 비교하자면 DBC에서는 client가 service를 사용하기 전에 service를 제공하는 supplier와의 contract에 따라 precondition을 만족시키는 입력만을 제공해야 하고, Defensive Programming에서는 client가 어떤 입력을 주더라도 supplier는 안전하게 동작해야 한다 정도가 됩니다.
일반적으로는 입력이 올바르지 않을 경우 DBC나 Defensive Programming 모두 supplier가 이를 처리해야 하므로 DBC와 같이 확실히 contract을 가지는 것이 supplier 입장에서 구현이 쉬워집니다. 또한 DBC를 사용하는 쪽이 코드도 간결해지죠. 코드끼리 서로 믿지 않으면서 작업하려면 체크해야 할것들이 많아지니까요.
이 부분을 읽으면서 제가 지금 하고 있는 프로젝트에 대해 생각을 해봤습니다.
지금 작업하고 있는 소프트웨어는 덩치가 엄청 큽니다. 이 하나의 소프트웨어에 4~5개의 랩들이 포함되어 있으며 각 랩은 2~3개의 파트로 나누어져 있습니다. 각 파트의 개인들이 담당하고 있는 block도 모두 다르죠.
이런 상황에서 core dump 같은 문제가 발생하면 문제의 원인이 누구인지가 모두의 관심사가 됩니다. :-)
보통 pstack을 확인하면 나오는 부분은 supplier쪽 코드들이죠. 예를 들어 null pointer access로 에러가 발생했다고 하면 null을 넘겨준 client가 문제인지 null check를 하지 않은 supplier가 문제인지 결정해야 하는데 대부분 null check를 하지 않은 supplier쪽 잘못으로 결정됩니다. (이렇게 범인(?)이 결정되면 범인은 주변의 따가운 시선을 받아가며 코드 수정하고 시험하고 문서 작성까지 해야 합니다.)
그리고 어느 랩에서 문제를 일으켰는지 저 위에 계신 분들에게도 보고가 됩니다. 문제가 발생한 랩의 장들은 주간 회의같은 곳에 들어가면 씁쓸한(?) 기분으로 나오게 됩니다. 물론 랩 분위기도 싸늘해지죠. :-)
이러다보니 코드는 갈수록 지저분해집니다. 체크하고 또 체크하고, 체크하는 코드를 체크하고 필요없는 체크가 들어가고... 심한 경우는 포인터를 쓸때마다 null check를 하는 분도 나옵니다. (too defensive? over defensive?) 왈, 포인터를 쓰고 있는데 중간에 다른 쓰레드가 해당 메모리를 null로 만들면 어떡하냐 입니다. 이런 경우 보통 pstack에 나온 코드의 owner가 진범을 찾아야 하므로 이렇게 무식하게 체크를 넣는 편이 낫다는 것이죠. 이렇게 되는 또 한가지 이유는 문제가 발생하더라도 이렇게 해 놓으면 높은 분들이 보시기에 여기는 할일을 다 했구나라고 생각하여 비난의 강도가 낮아진다는 점입니다. 결국 책임 회피용이죠.
이런 환경에서 위의 글을 보니 우리도 처음부터 DBC를 했더라면 어땠을까 하는 생각이 들더군요. 일단 책임 소재가 분명해집니다. precondition 체크를 안하면 supplier에 있는 assert와 같은 DBC 체크에 걸릴 것이고 원인은 client로 결정됩니다. supplier의 precondition 체크에 걸리지 않고 발생한 문제들은 물론 supplier의 책임이 되겠죠?
이렇게 하면 supplier 코드는 수행전에 문제 발생시 잘못이 자신에게 없음을 확인하기 위해 precondition 체크를 자발적으로(?) 하게 될것입니다. 마지막으로 client는 supplier가 제공하는 contract를 준수하기 위해 supplier가 제공하는 기능과 입/출력에 대해 좀 더 잘 이해하게 될것입니다.
물론 가장 좋은 길은 모든 랩이 하나의 목적을 위한 하나의 팀으로 기능하고 동작하는 것이겠지만 현재 제가 처한 분위기가 그렇질 못하다는 것이 부끄럽네요. 분위기가 이렇다보니 나올 수 있는 방법들은 주로 또 하나의 관리로 귀결되는 것 같습니다.
간단히 비교하자면 DBC에서는 client가 service를 사용하기 전에 service를 제공하는 supplier와의 contract에 따라 precondition을 만족시키는 입력만을 제공해야 하고, Defensive Programming에서는 client가 어떤 입력을 주더라도 supplier는 안전하게 동작해야 한다 정도가 됩니다.
일반적으로는 입력이 올바르지 않을 경우 DBC나 Defensive Programming 모두 supplier가 이를 처리해야 하므로 DBC와 같이 확실히 contract을 가지는 것이 supplier 입장에서 구현이 쉬워집니다. 또한 DBC를 사용하는 쪽이 코드도 간결해지죠. 코드끼리 서로 믿지 않으면서 작업하려면 체크해야 할것들이 많아지니까요.
이 부분을 읽으면서 제가 지금 하고 있는 프로젝트에 대해 생각을 해봤습니다.
지금 작업하고 있는 소프트웨어는 덩치가 엄청 큽니다. 이 하나의 소프트웨어에 4~5개의 랩들이 포함되어 있으며 각 랩은 2~3개의 파트로 나누어져 있습니다. 각 파트의 개인들이 담당하고 있는 block도 모두 다르죠.
이런 상황에서 core dump 같은 문제가 발생하면 문제의 원인이 누구인지가 모두의 관심사가 됩니다. :-)
보통 pstack을 확인하면 나오는 부분은 supplier쪽 코드들이죠. 예를 들어 null pointer access로 에러가 발생했다고 하면 null을 넘겨준 client가 문제인지 null check를 하지 않은 supplier가 문제인지 결정해야 하는데 대부분 null check를 하지 않은 supplier쪽 잘못으로 결정됩니다. (이렇게 범인(?)이 결정되면 범인은 주변의 따가운 시선을 받아가며 코드 수정하고 시험하고 문서 작성까지 해야 합니다.)
그리고 어느 랩에서 문제를 일으켰는지 저 위에 계신 분들에게도 보고가 됩니다. 문제가 발생한 랩의 장들은 주간 회의같은 곳에 들어가면 씁쓸한(?) 기분으로 나오게 됩니다. 물론 랩 분위기도 싸늘해지죠. :-)
이러다보니 코드는 갈수록 지저분해집니다. 체크하고 또 체크하고, 체크하는 코드를 체크하고 필요없는 체크가 들어가고... 심한 경우는 포인터를 쓸때마다 null check를 하는 분도 나옵니다. (too defensive? over defensive?) 왈, 포인터를 쓰고 있는데 중간에 다른 쓰레드가 해당 메모리를 null로 만들면 어떡하냐 입니다. 이런 경우 보통 pstack에 나온 코드의 owner가 진범을 찾아야 하므로 이렇게 무식하게 체크를 넣는 편이 낫다는 것이죠. 이렇게 되는 또 한가지 이유는 문제가 발생하더라도 이렇게 해 놓으면 높은 분들이 보시기에 여기는 할일을 다 했구나라고 생각하여 비난의 강도가 낮아진다는 점입니다. 결국 책임 회피용이죠.
이런 환경에서 위의 글을 보니 우리도 처음부터 DBC를 했더라면 어땠을까 하는 생각이 들더군요. 일단 책임 소재가 분명해집니다. precondition 체크를 안하면 supplier에 있는 assert와 같은 DBC 체크에 걸릴 것이고 원인은 client로 결정됩니다. supplier의 precondition 체크에 걸리지 않고 발생한 문제들은 물론 supplier의 책임이 되겠죠?
이렇게 하면 supplier 코드는 수행전에 문제 발생시 잘못이 자신에게 없음을 확인하기 위해 precondition 체크를 자발적으로(?) 하게 될것입니다. 마지막으로 client는 supplier가 제공하는 contract를 준수하기 위해 supplier가 제공하는 기능과 입/출력에 대해 좀 더 잘 이해하게 될것입니다.
물론 가장 좋은 길은 모든 랩이 하나의 목적을 위한 하나의 팀으로 기능하고 동작하는 것이겠지만 현재 제가 처한 분위기가 그렇질 못하다는 것이 부끄럽네요. 분위기가 이렇다보니 나올 수 있는 방법들은 주로 또 하나의 관리로 귀결되는 것 같습니다.
저도 고민을 많이 한 부분이네요. 그렇지만 custumer(client)를 신뢰할수 없다면 계약 자체가 의미가 없어집니다. 저 같은 경우 게임 서버를 만들고 있는데 온갖 핵이 난무하기때문에 클라이언트와의 계약을 절대로 신뢰하지 않습니다. 그렇기때문에 대단히 방어적인 프로그래밍을 하죠.
ReplyDelete물론 전체를 다 그렇게 검사하는게 불가능하기때문에 청정 구역을 두고 그 앞단까지만 데이터의 유효성을 검사합니다. (Code Complete의 방어적 프로그래밍 챕터에 나오는 내용이죠) 연구실의 살균실 같은 공간을 두는 건데 괜찮은 방법 같습니다. 현재는 그렇게 하고 있네요.
항상 눈팅만 하고 있다가 저도 많이 고민을 한 부분이라서 답글 남깁니다.
게임 클라이언트들은 게임 서버와 한 시스템이라 보기 어려울 것 같습니다. 답글을 읽으니 오히려 서버의 빈틈을 노리는 적들일것 같네요. :-) 따라서 말씀하신대로 대단한 방어적 프로그래밍이 필요할 듯 합니다.
ReplyDelete저희 경우는 한 시스템인데도 불구하고 다른 랩들에서 나누어 개발하다보니 서로의 책임을 면하기 위해 불필요한 방어적 코딩이 많아지는 경향이 있습니다. 이런 식의 방어적 코딩들은 대개 readability를 떨어뜨리는 경우가 많아 더 문제가 되고 있답니다. :-|
(어라 커멘트 수정이 안되네요;;)
ReplyDelete그러니까, contract를 적어주면 이것을 언어가 체크한다면 좋을거란 생각이 듭니다.
DBC를 언어적으로 체크할 수 있다면 더 좋겠다는 생각이 듭니다.
ReplyDeleteBertrand Meyer가 Eiffel이라는 언어를 디자인하면서 나온 말이라니까 Eiffel이라는 언어는 DBC를 native하게 지원하겠죠? ^^ 자바에는 DBC 툴들이 많이 있는것 같은데 C++은 아직 assert밖에 없는 듯 하네요. :-|
ReplyDelete그런데 이 assert라는게 양날의 검인지라 이를 run-time시에도 빼지 않고 넣기에는 상당히 부담되는 것도 사실입니다. 함수들 사이에 계약 위반했다고 client 앞에서 core dump를 내버리면 곤란하니까요. assert에서 exception을 던지도록 하는 것이 괜찮을 것 같기는 한데 현재 저희 코드들의 exception-safety 레벨이 매우 낮다는 문제가 있습니다.
얼마전 회사내에서 왜 S/W의 품질이 이렇게 떨어지나? 어떤 정책을 사용해야 하나? 개발자들에겐 어떤 교육을 시켜야 하나?등을 논의하는 자리가 있었는데 여기서 나온 얘기중 제가 뽑은 최고의 정답은... 애초에 잘하는 사람을 뽑았어야지... 였다죠? :-)
트랙백을 걸수 없어서 뎃글 남깁니다.
ReplyDelete좋은글 잘 봤습니다. ^^
http://freesearch.pe.kr/685