예제로 살펴 보는 Design By Contract

Posted 2006. 10. 13. 15:48
1980년대 중반에 Bertland MeyerDesign By Contract의 개념을 소개한지 벌써 20년의 세월이 흘렀고, 그 효용성은 이미 어느 정도 검증이 되었음에도 불구하고 관련 도구와 프로그래밍 언어의 지원 부족으로 아직도 널리 보급되지는 못하고 있다.  이 글에서는 개념 중심의 설명을 탈피하여, 예제를 중심으로, Design By Contract가 무엇인지 살펴보자.

지금부터는 우리는 자바를 사용해 은행 업무를 지원하는 소프트웨어를 만들 예정이다. 프로젝트에 착수하여 초기 요구 사항 분석과 디자인을 끝내고, 구현 단계에서 다음과 같이 은행 계좌를 나타내는 인터페이스를 만들었다. (실제 구현은 훨씬 복잡하겠지만 편의를 위해 간단하게 구현하였다.)


interface BankAccount {
    double getBalance();

    double deposit(double amount);

    double withdraw(double amount);
}

간단한 인터페이스인 만큼, 언뜻 보기에는 명료해 보이지만 실제로 BackAccount를 사용하는 클라이언트 입장에서는 여러 가지 의문점이 생긴다. 클라이언트 작성자의 가정이 항상 인터페이스 설계자의 의도와 맞아 떨어지지 않기 때문이다. 예를 들어 현재 잔고보다 많은 돈을 인출하면 어떻게 될까? 인터페이스 사용자는 마이너스 통장을 생각하고 대출로 처리될 것이라고 가정했는데, 인터페이스 설계자는 이런 인출이 불가능하다고 생각했다면 큰 문제가 생길 것이다.

또 다른 예로 withdraw() 메쏘드에 마이너스 값의 amount가 들어오면 어떻게 처리해야 할까? 인터페이스 설계자는 그런 요청은 일어나지 않는다고 가정하고 코드를 만들었는데, 클라이언트는 이 경우 에러 코드를 리턴해 줄 것이라고 가정하고 amount에 대한 확인 없이 메쏘드를 호출한다면 예상치 못한 문제가 생길 것이다. 이런 경우 클라이언트가 인자(argument) 검사를 안 하고 넘긴 것인지 잘못인지, 인터페이스 설계자가 에러 처리를 안 한 것이 문제인지도 분명히 알 수 없다. 즉 버그 발생 시 누구의 책임(caller 혹은 callee)인지가 불분명해진다.

사실 이 모든 문제에 대답은 이미 요구사항을 분석하고 세부 디자인을 하는 과정에서 논의가 되었을 것이다. 그렇지 않다면 디자인 단계에서 이미 실수가 있는 것이다. 하지만 자바 같은 일반적인 프로그래밍 언어에서 코드를 만드는 과정에는 인터페이스에 이러한 구체적인 제약 조건을 기술할 방법이 없다. 예를 들어 withdraw()와 deposit() 메쏘드에는 반드시 양수의 amount를 넘겨야 한다고 설계하였더라도, 자바에는 unsigned 타입이 없기 때문에 인터페이스만 보고서는 그 사실을 파악할 수 없다. 잔고가 0 이하로 내려갈 수 있느냐는 문제 또한 인터페이스 차원에서는 표현하기가 어렵다.

이 문제를 해결하는 전통적인 방법은 무엇일까? 인터페이스에 관련 사항들을 주석으로 달아놓는 것이다. 주석을 달면 다음과 같이 된다.


/**
* This class represents a customer's bank account.
* The balance cannot fall below zero.
* Any withdraw which exceeds the balance must be rejected.
*/

interface BankAccount {
    /**
     * Returns the account balance. The return value is always positive.
     */

    double getBalance();

    /**
     * Deposit the amount of money into the account. Amount cannot be negative.
     */

    double deposit(double amount);

    /**
     *  Withdraw the amount of money from the account. Amount cannot be negative.
     */

    double withdraw(double amount);
}
예제는 [1]에서 따왔습니다.

위 코드에서는 명세서(specification)에 나와 있는 제약 조건을 주석의 형식으로 코드에 삽입해서 인터페이스 이용자가 관련된 내용을 쉽게 알 수 있게 하였다. BankAccount의 deposit() 메쏘드를 사용하는 클라이언트는 deposit() 메쏘드의 주석을 읽고, amount 인자에 음수를 넘기면 안 된다는 사실을 알 수 있다. 또한 getBalance()의 주석을 보면 이 메쏘드는 항상 양수를 리턴함을 알 수 있다.

하지만 이렇게 코드에 부가 설명을 달아놓는 것만으로는 모든 문제가 해결되지 않는다. 일부 게으른 개발자들은 문서를 읽지 않을 수도 있고, 문서를 읽었더라도 코딩 과정 중에 실수로 deposit()에 음수의 amount를 넘길 수도 있을 것이다. 이렇게 인터페이스를 사용하는 통합 과정에서 발생하는 문제는 단위 테스트(unit testing) 만으로는 쉽게 발견하기가 어렵다.

이와 같은 요구 사항을 코드에 좀 더 명시적으로 표현하려면 어떻게 해야 할까? 다음 예제를 보자.


@Contract
@Invar("$this.balance > = 0.0")
interface BankAccount {

  @Post("$return >= 0.0")
  double getBalance();

  @Pre("amount >= 0.0")
  @Post("$this.balance == $old($this.balance)+amount
         && $return == $this.balance"
)
  double deposit(double amount);

  @Pre("amount >= 0.0 &&
        $this.balance -- amount >= 0.0"
)
  @Post("$this.balance == $old($this.balance)-amount
         && $return == $this.balance"
)
  double withdraw(double amount);
  ...
}

위 코드는 Contract4J라는 도구를 이용해 BankAccount 인터페이스의 요구 사항을 자바 어노테이션을 통해 표시해준 것이다. 여기서 @Pre는 메쏘드 수행 전에 클라이언트가 만족시켜주어야 하는 조건을 의미하고, @Post는 @Pre 조건이 만족되었을 때 해당 메쏘드가 보장해주는 조건을 말한다.

예를 들어 withdraw() 메쏘드의 경우 @Pre에서 amount >= 0.0 && $this.balance - amount >= 0.0 이라고 정의해 주었으므로, 클라이언트는 반드시 0보다 크거나 같으면서 현재 잔고보다 작은 amount를 인자로 넘겨야 한다. 대신 클라이언트가 이런 조건만 만족시켜주면 withdraw() 메쏘드는 @Post에 기술한 내용처럼 잔고를 (현재 잔고 - 인출한 액수)로 조정하고, 남은 잔고를 리턴해 주는 것을 보장한다.

이러한 어노테이션이 기존의 주석과 다른 점은 자연어가 아닌 프로그래밍 언어(혹은 논리 언어)에 가까운 표현식을 사용하면서 해당 요구사항(제약 사항)을 정형화하였다는 점이다. 이렇게 Pre-condition, Post-condition를 정형화하면 해당 조건들을 프로그램 수행시에 자동으로 검사하도록 만들 수 있다.

다시 withdraw()의 예를 들면, Contract4J는 @Pre와 @Post 조건을 읽어 들여 런타임에 해당 조건이 확실히 만족됨을 확인할 수 있도록 withdraw() 실행 앞 뒤로 Pre-condition과 Post-condition이 만족되는 확인하는 코드를 생성해 준다(AspectJ 사용). 만약 조건이 만족하지 않으면 해당 위치에서 즉각적으로 프로그램의 수행을 멈춘다. 따라서 일부 클라이언트가 실수로 잘못된 인자를 넘겼거나, withdraw()의 구현이 잘못되었을 경우 별도의 테스트 없이 코드를 실행해 보는 것만으로 이를 바로 알 수 있다.

같은 방식으로 @Invar로 표현되는 Invariant가 있는데, 이는 클래스가 항상 만족시켜야 하는 조건을 의미한다. @Invar ( $this.balance >= 0.0 )은 어떤 메쏘드를 실행하더라도 잔고가 절대로 0 밑으로 떨어지지 않음을 보장해 주는 역할을 한다. withdraw()나 deposit() 메쏘드를 실행한 전후로 잔고가 0 보다 낮아지면 프로그램 수행을 멈추게 된다. (하지만 메쏘드 수행 중에는 일시적으로 잔고가 0 이하가 될 수는 있다.)

정리하면 DbC의 효과는 프로그램을 보다 명료하게 만들어주는 데 있다. 왜냐하면 요구사항과 디자인 과정에서 나온 여러 가지 제약 사항(constraint)를 코드에 녹여 넣고, 자동으로 검사할 수 있기 때문이다. 또한 개발 과정에서 이러한 자동 검사를 이용한 후에, 안정성이 확보되었다고 생각하면 이후에는 이런 검사 코드를 생략할 수도 있다. 대신에 @Pre, @Post, @Invar 등의 정보를 파싱하여 사용자에게 유용한 문서를 자동으로 생성하는 데 사용될 수도 있다.

참고 문서
[1] AOP@Work: Component design with Contract4J, Improve your software with Design by Contract and AspectJ
http://www-128.ibm.com/developerworks/java/library/j-aopwork17.html

Complexity of AOP

Posted 2006. 10. 13. 11:15
오늘 과제 연구 때문에 지도 교수님과 면담을 하고 왔는데, AOP(Aspect-Oriented Programming)의 복잡도에 대한 이야기를 나누었습니다.

AOSD(Aspect-Oriented Software Development)가 crosscutting concern을 캡슐화하는 아이디어는 좋지만, 코드를 읽을 때는 이해도(understandability)는 생각보다 떨어진다는 것이 AOP의 큰 단점입니다. 특히 사실상 정규식(regular expression)의 방식으로 포인트컷(pointcut)을 지정해서는, 복잡하고 큰 소프트웨어의 경우 어떻게 영향을 받는지 이해하기는 상당히 어렵습니다.

현재 AspectJ의 Eclipse 플러그인을 보면 이렇게 Aspect의 기존 컴포넌트 사이의 관계를 도식적으로 보여주고, aspect가 지정한 포인트컷을 표시해주는 기능이 있던데, 이런 도구에 대한 연구가 좀 더 필요한 것 같습니다.
예전에 KLDP에 Io Language를 언급하면서 프로토타입 기반의 객체지향(OO) 언어에 대한 질문이 올라온 적이 있었습니다.

여기에 대해 설명을 달았었는데, 다시 정리하자면 객체 지향(Object-Oriented)을 프로그래밍 언어로 구현하는 방법에는 크게 두 가지 큰 줄기가 있습니다. 첫 번째가 C++, 자바, C# 등이 공통적으로 쓰고 있는 클래스 기반의 객체지향 언어이고, 또 다른 방식으로 자바 스크립트나 Io 언어 등이 사용하는 프로토타입 방식의 객체 지향 언어가 있습니다.

클래스 기반 OO의 경우 클래스가 객체를 찍어내는 틀 역할을 합니다. 객체는 클래스라는 틀에서 붕어빵을 찍듯이 하나씩 찍어내는 개념이죠. C++, 자바 등에 익숙한 사람이라면 new 연산자를 이용해서 클래스로부터 객체를 생성하는 과정을 이해하실 겁니다.

반면에 프로토타입 기반의 언어는 이런 개념을 차용하지 않았습니다. 클래스라는 틀의 존재를 상정치 않고(classless object model이라고도 불립니다), 곧바로 시스템에 여러 종류의 객체가 존재하게 됩니다. 프로토타입 기반의 객체 지향 언어는 이렇게 이미 존재하는 객체를 직접 사용하거나, 클론(clone)해서 사용합니다. 필요에 따라 클론한 객체에 추가적인 기능을 구현(일종의 inheritance)을 하기도 합니다.

현재는 클래스 기반의 OO가 대세이기 때문에 "객체 지향 = 클래스 기반"이라는 등식이 생겼지만, 실제로는 프로토타입 기반의 OO도 여러 언어에서 사용되고 있습니다. 현재 개발자들에게 가장 익숙한 프로토타입 OO 언어는 아마 JavaScript일 것입니다.

University of Washington의 Washington Advanced Systems for Programming에 있는 Craig Chambers 교수가 연구했던 프로토타입 기반 객체 지향 언어인 Cecil 프로젝트를 보시면, 프로토타입 기반 OO로 우리가 알고 있는 OO의 기본 개념들을 어떻게 구현하는지 힌트를 얻을 수 있으실 겁니다. 다만 현재 국내 개발자들 사이에서 인지도가 높은 Io 언어는 어떤 차별성이 있는지 잘 모르겠네요.

오픈 소스 개발

Posted 2006. 10. 12. 00:40
예전에 썼던 달콤하고도 쓴 과일: 오픈 소스을 조금 다듬어 봤습니다.

모두들 오픈 소스(Open Source)가 중요하다고 말한다. 소프트웨어 대기업이 오픈 소스를 후원하는 것도 일반적인 일이 되었다. 일례로, 구글이나 IBM 같은 기업이 오픈 소스를 지원해서 어떤 이익을 얻는지에 관한 분석도 쉽게 찾아 볼 수 있다. 하지만 정작 개발자로 하루 하루를 사는 우리 주변을 돌아보면 오픈 소스는 아직까지 딴 세상의 이야기란 생각이 드는 것도 사실이다. 세상은 오픈 소스로 넘쳐나지만, 한국의 개발자들은 여전히 닫힌 세상에서 살고 있다.

하지만 따지고 보면 우리 나라는 오픈 소스 운동의 최대 수혜자이다. 현재 한국의 실정을 보면, 소프트웨어 개발은 대게 중소 규모의 개발업체를 중심으로 이루어진다. 대기업에서 발주를 하더라도 실제 소프트웨어 제작은 외주가 되는 경우가 많으므로, 거의 모든 소프트웨어는 규모가 크지 않은 개발업체가 담당하고 있다고 생각할 수 있다. 이런 한국 중소 개발업체들이 마이크로소프트나 썬, IBM처럼 신제품 개발에 필요한 컴포넌트나 라이브러리를 모두 소유하고 있는 경우는 드물다. 제품 개발에 필요한 코드는 전부 외부에서도 조달해야 하고, 국내 기업이 이런 코드를 저가에 조달할 수 있는 곳은 결국 오픈 소스이다.

간단한 예를 들어 현재 개발하는 제품이 이미지 디코딩(image decoding) 기능이 필요로 하면 어떻게 하는 게 좋을까? JPEG, GIF, PNG 등의 이미지 포맷의 명세서를 보고 처음부터 다시 이미지 디코더(image decoder)를 만들 것인가? 디코더 개발이 경쟁력이 되는 이미지 관련 제품을 만드는 업체가 아닌 이상, 오픈 소스 프로젝트를 찾아볼 것이다. 각각의 포맷에 대해 이미 많은 사람들이 사용해 검증된 libjpeg, libungif, libpng 같은 오픈 소스 프로젝트가 있음을 알 수 있다. 노련한 개발자라면 오픈 소스를 적극 활용할 것이다.

즉 사용하는 면에서만 보면 한국은 이미 오픈 소스 강국이라 불러도 이상할 게 없다. 반대로 오픈 소스에 기여하는 면에서는 그 성적이 형편없다. 왜 오픈 소스에 참여하지는 않는 것일까? 가져다 쓸 것은 많지만 프로젝트로 환원할만한 것들은 없어서일까?

하지만 개발 활동의 특성을 보면 그럴 수가 없다. 소프트웨어 개발은 필연적으로 부산물을 만들기 마련이다. 소프트웨어 컴포넌트는 전자 부품처럼 해당 규격의 부품만 끼운다고 곧바로 동작하지 않는다. 간단한 라이브러리 하나를 가져오더라도 현재 제품에 맞추어 수정을 거쳐야 하고, 이 과정에서 불충분한 기능 하나 두쯤은 직접 개발해서 추가하게 된다. 또 오픈 소스가 안정성이 검증되어 있다지만, 사용하다 보면 버그를 발견하는 경우도 흔하다. 따라서 적극적으로 프로젝트에 참여하지는 못할지라도, 회사에서 오픈 소스를 활용하며 생긴 부산물만 적극적으로 오픈 소스 프로젝트에 환원한다면 한국도 오픈 소스에 가장 많은 기여를 하는 나라도 될지도 모른다.

그런데 현실은 왜 이럴까? 첫 번째 이유로 개발업체가 오픈 소스를 활용하고 있다는 사실을 숨기기 때문이다. 오픈 소스를 사용한다고 떳떳하게 공개하지 못하는 이유로 보통은 라이선스(license) 문제를 든다. 상당수의 오픈 소스가 코드를 공개해야 하는 GPL(General Public License)로 작성되어 있기 때문이다. BSD 라이선스처럼 GPL과 달리 비교적 자유로운 이용을 허용하는 경우도 있지만, GPL의 주류인 오픈 소스 세상에서 소스 코드 공개는 코드가 회사 자산의 전부인 중소 규모의 개벌업체의 선택 사항은 아니다.

단순한 라이선스 문제만은 아니다. 한국에서 오픈 소스의 이용을 공표하는 것은 실상 그 업체가 자체적인 기술력이 없음을 보여주는 커밍아웃에 가깝다. 한국에는 원천 기술이 없다는 이야기를 많이 하는데, 소프트웨어 업체의 실상도 크게 다르지 않다. 중소 업체들이 시장에 내 놓은 상당수의 제품들은 오픈 소스 프로젝트를 가져다가 외양을 바꾸고 치장한 것에 지나지 않기 때문이다.

2000년 초부터 난립했던 국내 보안 업체들이 만든 상당수의 방화벽(firewall)과 침임 탐지시스템(intrusion detection system), PKI 관련 암호 시스템 등이 대부분 OpenSSL 등 여러 오픈 소스 프로젝트를 가져와 시작했다는 이야기를 들은 적이 있다. 그 당시에는, 보안 관련 개발자가 회사를 관두고 같은 제품을 만드는 경쟁사로 옮겨도 같은 소스 코드를 보게 된다는 일화가 우스개 소리만은 아니었다.

오픈 소스 활성화에 걸림돌이 되는 또 하나의 문제는 의사소통의 장애일 것이다. 의사소통은 단순히 개발자들의 영어 실력만을 말하는 게 아니다. 영어가 작은 장벽이라면, 더 큰 장벽은 의사소통 그 자체다. 오픈 소스를 가져와 필요에 따라 고치는 일은 혼자서도 해치울 수 있지만, 프로젝트에 패치를 제출하고 버그를 알리는 일은 다른 개발자들과 의사소통을 필요로 한다. 예를 들어, 코드에 기능을 추가하거나 수정을 가했으면, 어떤 이유에서 그러한 수정을 했는지 명확히 전달할 수 있어야 하는데, 이 과정에서 다른 개발자들을 납득시키는 것은 상당한 끈기와 노력을 요한다.

우리나라 오픈 소스 현황을 살펴보면 이런 점이 두드러진다. 많은 개발자가 의사소통을 힘들어한다. 어렵게 시작한 국내 오픈 소스 프로젝트도 다른 개발자의 도움을 받는 일은 극히 드물다. 개발자들은 기존의 프로젝트에 참여하기보다는 자신만의 새로운 프로젝트를 만드는 쪽을 선호한다. 기존의 프로젝트에 자신의 생각을 설득력 있게 관철시키는데 어려움을 느끼게 때문에, 자기만의 새로운 프로젝트를 시작하는 것이다. 덕택에 이름만 다른 유사 프로젝트가 만들어지게 되거나, 포기해 버리고 만다.

요약하면 국내에서 오픈 소스 하기란 어렵다. 많은 사람들이 오픈 소스를 이야기하지만, 기대만큼 오픈 소스를 후원함으로써 직접적인 이익을 얻을 수 있는지도 의문이다. IBM 정도의 거물 소프트웨어 회사라면 뭔가 비전을 가지고 오픈 소스를 전략적으로 활용할 수 있겠지만, 당장 한 명의 개발 인력이 아쉬운 중소 규모의 국내 개발업체들이 거창하게 오픈 소스를 후원한다는 것도 실상은 우스운 면이 많이 있다.

하지만 개발자 개인의 입장에서 보면 오픈 소스는 여전히 매력적 장소이다. 회사 일과 별개로 자신이 만들고 싶은, 자신이 필요로 하는 제품을 만들 수 있는 즐거움은 개발자들의 특권이다. 개발자가 대접받지 못하는 한국 IT 현실에서 전세계의 개발자들과 소통하며 실력을 인정받는 일은 아무나 누릴 수 없는 기쁨이기도 하다.

또한 오픈 소스 프로젝트는 자신이 스스로 결정하기 힘든 회사 프로젝트와는 별개로 자신만의 경력을 쌓을 수 있는 곳이다. 오픈 소스는 큰 규모의 소프트웨어를 어떻게 개발해야 하는지를 배울 수 있는 이상적인 장소이다. 이렇게 쌓은 경력은 자신의 커리어를 만들어 나가는데 중요한 역할을 한다. 구글이 채용한 파이썬의 아버지의 Guido van Rossm, 파이어 폭스(Firefox) 개발자인 Ben Goodger, 가임(Gaim) 개발자인 Sean Egan 등 유명한 오픈 소스 개발자들도 다들 자기만의 작은 프로젝트로 시작한 것이다.

미국 대학에서는 소프트웨어 산업계로 나아갈 학부 졸업생들을 위해 실용적인 프로그래밍 과목을 개설하는데, 여기서 빠지지 않는 것이 오픈 소스 프로젝트에 기여하는 것이다. 메릴랜드 대학의 경우 ‘CMSC433 프로그램 언어 패러다임과 기술’의 프로젝트 중에 하나가 오픈 소스 프로젝트를 하나 선정하고, 여기에 버그 패치와 기능 추가 등의 구체적인 활동을 해서 보고서를 제출하는 일이다. 이런 과정을 통해 학생들은 자연스럽게 오픈 소스 개발 과정을 몸에 익히고 업계에 진출해서도 오픈 소스를 적극 활용한다.

시작은 보잘것없지만 그 끝은 장대한 법이다. 우리도 당장 자기 업무에 사용하는 오픈 소스에 작은 패치라도 하나 제출해 봄이 어떨까?

AOP and DBC

Posted 2006. 10. 11. 22:59
최근에 AOP(Aspected Oriented Programming) 관련해서 과제 연구 주제를 찾고 있었는데, 흥미롭게도 AOP의 어플리케이션 중에 하나가 DBC(Design By Contract)의 구현이다. 사실 아직 AOP 초보인지라, 사용 사례를 로깅이나 보안 쪽 정도 밖에 생각 못하고 있었는데, DBC의 Pre/Post-Condition과 Invariant도 business logic과는 별개의 separate concern으로 생각할 수 있었다.

실제로 Developers@Work에 실린 Contract enforcement with AOP를 보면 AspectJ를 이용해 자바에서 DBC를 구현하는 간단한 예를 보여주고 있다. 간단하게 생각하면 Precondition는 메쏘드 실행 전에, Postcondition는 메쏘드 실행 후에 불러주고, Invariant는 실행 전후로 확인하면 되므로,

<Contract enforcement with AOP에서 발췌한 코드>
public abstract aspect AbstractContract
{
   /**
   * Define the pointcut to apply the contract checking
   * MUST CONTAIN A METHOD CALL
   */

   public abstract pointcut targetPointcut();

   /**
   * Define the ContractManager interface implementor to be used
   */

   public abstract ContractManager getContractManager();

   /**
   * Perform the logic necessary to perform contract checking
   */

   Object around(): targetPointcut()
   {
      ContractManager cManager = getContractManager();
      System.out.println("Checking contract using:" +
        cManager.getClass().getName());

      if (cManager!=null)
      {
         System.out.println("Performing initial invariants check");
         cManager.checkInvariants(thisJoinPoint.getTarget());
      }
      if (cManager!=null)
      {
         System.out.println("Performing pre-conditions check");
         cManager.checkPreConditions(thisJoinPoint.getTarget(), thisJoinPoint.getArgs());
      }

      Object obj = proceed();

      if (cManager!=null)
      {
         System.out.println("Performing post conditions check");
         cManager.checkPostConditions(thisJoinPoint.getTarget(),
           obj, thisJoinPoint.getArgs());
      }
      if (cManager!=null)
      {
         System.out.println("Performing final invariants check");
         cManager.checkInvariants(thisJoinPoint.getTarget());
      }
      return obj;
   }
}

위 코드의 예처럼 Template 메쏘드를 사용하면 일반적인 Contract enforcement가 구현 가능하다. 따라서 사용자는 targetPointcut을 override해서 어디에 삽입할 것인지를 정하고, ContractManager를 통해 실제 구현을 집어넣을 수 있다.

이 아이디어를 발전시켜서 실제로 DBC 구현에 Aspect를 사용한 예가 Contract4J이다. 해당 프로젝트 홈페이지에 나온 AOP와 DBC의 관계는 다음과 같다.

Design by Contract and Aspect-Oriented Programming

So what does DbC have to do with Aspect-Oriented Programming (AOP)? On the one hand, the component's contract is an essential part of the complete, logical component specification that clients must support. For example, an interface for a bank account may have a contract requirement that all methods that return a balance must always return a non-negative number (ignoring overdraft features). However, in practical terms, contracts often include implementation concerns that may have little relationship to the domain logic of the application. For example, the code implementing the bank account may prohibit passing null values as method parameters.

For both types of contract details, AOP allows us to specify the details with sufficient "proximity" to the interface so that clients can see the constraints and AOP gives us an elegant way of testing the constraints at runtime without cluttering the code with logic to run the tests and handle failures.

More generally, AOP is a new approach to modularizing "concerns" that need to be handled by a component, but which tend to obscure the main logic of the component, often compromising clarity, maintainability, reusability, etc. For example, modern web and enterprise applications typically must support secure access, transactional behavior, persistence of data, and mundane support issues like logging. Without AOP, the code for these "concerns" gets mixed in with the domain logic, thereby cluttering the code and diminishing the "ilities" we all strive for. AOP keeps these concerns in separate modules and provides powerful facilities for "injecting" the concern behavior in the specific execution points where needed. Contract4J5 uses AOP techniques to find the contract specifications and test them at runtime at the appropriate execution points.

AOP is a good approach to supporting DbC because it permits DbC concerns to be managed in a modular and minimally-intrusive way, without cluttering application logic, while still allowing the contracts to be integrated into the runtime environment for development and testing. Contract4J5 uses the best-known AOP language, AspectJ, to support DbC for Java.

For more information on AOP, see the references below.

요약하면 application logic에서 DBC를 분리해 @pre ( ), @post ( ) 같은 방식으로 써준다. 이렇게 하면 DBC 관련 코드와 application code가 뒤섞여서 생기는 문제를 해결할 수 있다. 하지만 실제 실행 시에 해당 코드를 메쏘드 앞 뒤로 자연스럽게 껴 넣을 수 있기 때문에 AOP가 DBC 구현에 좋다.

Design By Contract(DBC) and Eiffel

Posted 2006. 10. 11. 22:40
흔히 Object-Oriented 코드를 디자인할 때 "Design By Contract(DBC)"를 따르면, robust/reliable한 코드를 얻을 수 있다고 이야기를 합니다. DBC의 특징을 간략하게 요약하면 컴포넌트를 디자인할 때 Pre-Condition, Post-Condition, Class Invariants 등을 명확하게 정의하는 것을 말합니다. DBC의 창시자인 버트란드 마이어(Bertlant Meyer)가 만든 Eiffel 프로그래밍 언어는 이런 DBC를 프로그래밍 언어 속에 녹여 넣은 것으로 유명하고요.

DBC를 언급하면 항상 Eiffel이 따라 나오긴 하는데, 실제로 Eiffel로 뭔가 프로젝트를 진행하고 있는 사례가 궁금합니다. 특히 한국에서도 일부 블로그에 보면 DBC 이야기를 하시던데, 실제로 DBC, 더 나아가서 Eiffel을 프로젝트에 사용하고 있는 경우가 있는지요? 사례를 한 번 들어보면 좋을 것 같다는 생각이 듭니다.

Dynamic Typing과 Test Driven Development

Posted 2006. 10. 8. 19:40
Dynamic Typing과 관련된 글을 블로그에 Static Type Checking이란 제목으로 쓴 적이 있는데 여기서 다시 한 번 정리해 보고자 한다.

블로그를 돌아다니다보면 많은 사람들이 TDD가 Dynamic Typing하는 언어의 특징이라고 생각하고 있음을 알 수 있다. 특히 일부 파이썬/루비 팬들이 운영하는 블로그에서 TDD가 언급되면 어김없이 동적 타이핑 언어의 테스트 편리성(혹은 크게는 생산성)에 대한 이야기가 뒤따라 온다.

하지만 C/C++처럼 수십 년도 더 된 구세대 언어와, 소프트웨어 개발 방법론과 테스팅에 대한 어느정도 이해가 쌓인 후에 나온 파이썬/루비와 같은 언어를 정적/동적 타이핑 면에서만 놓고 비교하는 것은 공정하지 못한다. 사실 너무 불공평하다.

파이썬/루비와 정반대 선상에서 정적 타입핑의 극상을 달리는 친구들은 ML과 Haskell 같은 함수형 언어(functional)들인데, 이 놈들은 C/C++ 보다 더 엄격하게 컴파일 타임에 타입 검사를 한다. 하지만 이쪽 언어 사용자들의 주장에 따르면 ML과 Haskell은 절대 테스트하기 불편한 언어도 아니고, 코드 길이가 길거나 생산성이 떨어지는 언어도 아니다.

순수하게 타이핑 측면에서만 바라본다면, 동적 타이핑은 컴파일 타임에 해주는 검사가 적은만큼(상당히 귀납적이다) 테스트를 통해 더 많은 버그를 찾아내야 하므로, 테스트를 훨씬 더 철저하게 많이 해야 한다. 잘못된 타입 연산 등의 간단한 버그 조차도 테스트를 통해서만 발견할 수 있다는 것은 재앙이다(타입 검사는 연역적이다)

테스트의 부담이 더 큰 파이썬/루비 같은 동적 타이핑 언어가 테스트를 강조하는 것은 당연한 일이다. 같은 기능을 하는 다른 프로그램 보다 더 많은 테스트를 해야 프로그램이 제대로 동작하기 때문이다. 즉 원래 언어적인 측면에서 태생적인 약점이 있으므로, 후천적인 노력으로 개선을 한 셈이다.

파이썬과 루비 같은 동적 타이핑하는 스크립트 언어의 테스트 편리성은 동적 타이핑이라는 속성에 기인한 것이 아니다. 이는 언어의 추상화(abstraction) 수준과 관계가 있다. 추상화 수준이 높을수록 코드는 사람이 생각한 바(스펙)에 가까워지고, 알아보기 쉬워지며, 금방 만들 수 있다.

C 언어와 파이썬/루비의 추상화 수준은 어떻게 다를까? 간단한 통계로 C 언어 construct 하나가 보통 1-10개의 머신 인스트럭션으로 컴파일 되는 반면에, 파이썬과 루비의 construct 하나는 100-1000개 이상의 머신 인스트럭션으로 실행된다.

즉, 파이썬이나 루비 개발자가 그토록 강조하는 "가독성", "짧은 코드"는 절대로 동적 타이핑(흔히 스크립트 언어)의 특성이 아닌 셈이다. 강력한 정적 타이핑 언어인 헤스켈(Haskell)의 퀵 소트 코드를 본 적이 있다면, 타이핑은 생산성이나 테스트 편의와는 직접적인 상관 관계가 없다는 데 동의할 것이다.

qsort [] = []
qsort (x:xs) = qsort (filter (< x) xs) ++ [x] ++ qsort (filter (>= x) xs)

위 Haskell 코드는 polymorphic하다. qsort에 넘길 수 있는 타입은 >= 연산자가 정의된 list이기만 하면 된다. Haskell이 이처럼 간결한 코드를 보여줄 수 있는 이유는 파이썬/루비와 같은 수준의 추상화 레벨이면서, type inference를 통해 불필요한 타입 어노테이션을 필요를 상당히 줄였기 때문이다.

이처럼 같은 추상화 수준이라면 당연히 동적 타이핑보다는 정적 타이핑이 유리하다. 비유하자면, 타입 이론은 '사람은 모두 죽는다' '그러므로 소크라테스도 죽는다'라고 말하는 것이고, 테스트는 'A도 죽었고, B도 죽었고, C도 죽었으니 아마 소크라테스도 죽을 것이다'라고 말하는 셈이다.

Static Checker for Python: PyChecker

Posted 2006. 10. 8. 11:48
파이썬을 위한 정적 검사(static analysis) 도구를 찾아 보다 PyChecker 프로젝트를 발견했다. PyChecker는 C의 lint와 유사한 프로그램으로 파이썬 프로그램을 읽어서 실행 전에 다음 오류들을 알려준다.

- 사용된 전역 변수가 정의되지 않았을 때. (예를 들어 import 없이 모듈을 사용)
- 함수/메쏘드/생성자에 넘기는 인자 수가 맞지 않을 때.
- 내부(builtin) 함수/메쏘드에 넘기는 인자 수가 맞지 않을 때.

- 포맷 문자열과 인자가 맞지 않을 때.

- 존재하지 않는 메쏘드나 속성(attribute)을 사용했을 때.

- 메쏘드를 오버라이드할 때 시그너처를 바꾼 경우.

- 같은 스코프에서 함수/클래스/메쏘드를 재정의했을 때.

- 변수를 초기화 없이 사용했을 때.

- 메쏘드의 첫 번째 인자가 self가 아닐 때.

- 사용되지 않은 전역 변수와 지역 변수.

- 사용되지 않은 함수/메쏘드 인자.

- 모듈, 클래스, 함수, 메쏘드에 doc이 없을 때.


사실 이 정도 검사는 별도의 어노테이션 없이 가능한데, 왜 파이썬 인터프리트가 직접 지원하지 않는지는 잘 모르겠다. 저런 뻔한 버그 때문에 몇 번이나 코드를 테스트해야 한다는 것은 엄청난 약점일 것 같은데 말이다.

참고 문서
1. Regular Expressions: Syntax Checking the Scripting Way by Cameron Laird and Kathryn Soraiz
http://www.unixreview.com/documents/s=2426/uni1018986621203/0204h.htm

Toy Problem과 Toy Program

Posted 2006. 10. 8. 01:07
인공 지능(Artificial Intelligence) 수업에는 여러 가지 장난감 문제(toy problem, 현실의 문제를 간단하게 만든 것)을 다룬다. 인공지능이 풀어야 하는 현실 문제는 정형화 하기에 너무 복잡하기 때문에 개념을 소개하기 위해 인위적으로 쉬운 문제를 제시하고, 이를 풀어나가는 알고리즘을 설명하는 것이다. 대부분의 인공 지능 알고리즘은 장난감 문제를 거의 완벽하게 풀어낸다. 그냥 수업만 들어서는 정말 좋은 알고리즘인 것 같고, 더 공부할 게 남았을까라는 생각이 들 정도이다.

하지만 장난감 문제를 쉽게 풀어낸 알고리즘을 현실적인 문제로 가져가면 예상치 못했던 여러가지 문제가 발생한다. 문제의 탐색 공간(Search Space)가 기하 급수적으로 커져서 쓸모 없는 알고리즘이 되기 다반사이고, 알고리즘이 상정한 몇 가지 가정이 무너짐으로써 알고리즘 자체가 성립되지 않는 경우도 많이 있다. 그 순간 인공지능은 순수한 과학(science)에서 엔지니어링(engineering)으로 넘어가게 된다.

비슷한 비유가 프로그래밍 언어에도 적용되지 않나 싶다. 보통 새로운 언어가 개발되면, 언어의 장점을 알리기 위해 간단한 문제 몇 가지를 골라 기존의 프로그래밍 언어에 비해서 얼마나 세련되고 아름답게 그리고 짧은 코드로 그 문제를 풀 수 있는지 보여준다. 이런 장난감 프로그램 몇 개만 잘 골라서 보여주면 기존 언어에 식상해 있던 사람들은 금세 그 언어에 열광하게 된다. 파이썬/루비에 열광하는 많은 개발자들은 실상은 이런 코드 단축의 예에 매료되어 해당 언어를 공부하게 되었을 것이다.

스크립트 언어의 특징은 다음과 같다.

1) 인터프리트 된다.

2) 타입 선언이 암묵적이다. (Implicit Declaration)

3) 동적 타입(Dynamic Typing)이다.


4) 정규식(RE)와 문자열 처리 기능이 내장되어 있다.


위4가지의 특징은 스크립트 언어를 강력하게 만들며, 작은 프로그램을 빠른 속도로 프로토타이핑(prototyping)할 수 있게만들어주는 언어의 특징이다. 반대로 이런 특징(특히 1,2,3)은 크고 정교한 프로젝트에는 부정적인 영향을 미치는 요소이기도하다. 파이썬이나 루비가 이런 한계를 뛰어 넘었다면 그 이유는 무엇일까? 회사에서 파이썬이나 루비를 도입하고 싶다면, 직장상사나 팀원들에게 무엇을 근거로 해당 언어의 도입을 이야기할 것인가?

소프트웨어 개발을 업으로 삼고 컴퓨터 공학을 하는 사람이라면, 여기서 한 걸음 다 나아가야 한다. 국내 스크립트 언어 사용 개발자들에게 아쉬운 것은 실제 프로젝트의 적용 사례가 빈약하다는 점이다. 개발자들이 개별적으로 써보니깐 코드도 짧고 가독성도 높고 생산성이 뛰어나서 좋다고 말은 하는데, 실제로 스크립트 언어를 써서 어떤 프로젝트를 성공적으로 수행했는지에 대한 말을 찾아보기가 어렵다.

회사에서 사용하는 개발 언어를 바꾸고 싶다면 언어의 장점을 회사의 생산성 향상과 연결시킬 수 있는 구체적인 숫자를 제시할 수 있어야 한다. 회사 내 혹은 팀 내의 보수적인 개발자들을 설득하고 싶다면 토이 프로그램의 마법만을 보여줄 것이 아니라 구체적인 적용 사례을 보여주고 생산성 증가 정도를 구체적인 숫자로 제시해야 할 것이다. 그런 정보 없이는 만병통치약을 선전하는 약장수와 다를 것이 없게 된다.

한국의 개발자 블로그

Posted 2006. 10. 7. 20:59
컴퓨터 공학과 소프트웨어 개발에 대한 블로그를 운영하다 보니 자연스럽게 다른 개발자들 혹은 컴퓨터공학 전공 학생들의 블로그를 종종 들여다 보게 됩니다. 한국의 소프트웨어 개발자들은 요즘 무슨 문제로 고민하는지, 어떤 기술에 관심을 가지고 있는지 알아보는 것은 늘 즐거운 일이기 때문입니다. 그런데 블로그를 보다 보니 사람들마다 특색도 있지만 또한 상당한 유사성도 있다는 느낌이 들었습니다. 객관적인 통계에 기초하고 있는 것은 아니지만, 요 며칠 블로그를 탐색하면서 받은 제 인상은 다음과 같습니다.


1. 대부분 웹 개발에 종사한다.

블로그 내용의 상당수가 웹 개발과 관련된 이슈를 다루고 있었습니다. 해당 글의 내용으로 미루어보아 상당수가 웹 개발자로 일하고 있음도 알 수 있었습니다. 기술적인 내용으로는 웹 2.0과 AJAX, JSP, PHP 등이 주류를 이루었고, 비지니스적인 고민도 대부분 웹 서비스 모델과 관련된 것들이 많았습니다. 반면에 패키지 소프트웨어나 임베디드 소프트웨어를 개발하는 것으로 보이는 개발자 분은 찾아보기가 힘들었습니다. 임베디드 분야에 종사하는 개발자도 상당수 계실 텐데, 그 분들은 웹(?)과 친하지 않아서인지 블로그가 별로 없더군요.


2. 대안 언어를 찾아 헤맨다.

개발자라면 당연히 프로그래밍 언어를 구사해야 하므로 프로그래밍 언어에 대한 관심도 높을 것입니다. 블로그를 잘 살펴보면 상당수의 개발자가 기존의 개발 언어(C, C++, 자바 등)에서 탈피해 새로운 대안을 모색하고 있는 것으로 보였습니다. 특히 파이썬과 루비에 대한 인기가 가장 높아서 상당수의 블로그가 이들 스크립트 언어를 다루고 있었습니다. 대안 언어의 구사 여부가 개발자의 쿨(cool)함의 정도를 나타내는 지표에 가깝다고 할 정도로 새로운 언어를 추구하시는 블로거가 많이 계셨습니다.


3. 실제 업무에 대한 이야기는 별로 없다.

실제 업무가 없는 학생들은 차치하고 현업에 종사하고 계신 개발자들도 실제 회사에서 개발하는 이슈에 대한 이야기는 거의 하지 않음을 알 수 있었습니다. 언급을 하더라도 피상적인 수준에서 문제점이나 생각들을 언급할 뿐 구체적인 프로젝트나 이슈들에 대한 언급을 찾아보기는 힘들었습니다. 기술적인 면에서 상당히 개방적인 태도를 취하는 서양 엔지니어들과 달리 한국 개발자들은 회사 일은 회사 내부에서만 이야기해야 한다는 생각이 강한 것 같습니다.

4. 유행에 민감하다.

블로그를 돌아보면서 블로그 글들도 상당히 유행에 민감하다는 사실을 알 수 있었습니다. AJAX, 루비 등 몇 개의 기술이나 화두가 거의 모든 블로그에서 논의되는 모습은 다양성 면에서 조금 아쉬운 감이 있었습니다. 유행과 관련 없이 특정 기술이나 주제로 깊이 있는 글을 쓰시는 분들이 많이 있었으면 하는 바램이 있습니다.


글이 상당히 피상적이 되었네요. 한국의 개발자 블로그들이 많이 활성화 되길 바랍니다.

ML 공부를 위한 참고 자료

Posted 2006. 10. 7. 20:13
ML 참고 자료입니다. 이 글을 앞으로 계속 갱신할 예정입니다.

ML 참고 문서 Version 0.1 (2006-10-07)

[1] Programming in Standard ML,Robert Harper Carnegie Mellon University Spring Semester, 2005
http://www.cs.cmu.edu/~rwh/smlbook/

카네기 멜론 대학(CMU)에서 나온 SML 입문서입니다. CMU 학부 학생들이 SML을 처음 공부할 때 보는 책이라고 나와 있습니다. 총 293 페이지로 SML의 기본적인 내용을 아주 자세히 설명하고 있어서 함수형 언어를 처음 배우는 (혹은 프로그래밍을 처음 접하는) 입문자에게 좋은 책입니다.

Pascal에는 subrange type이라는 게 있다. subrange 말이 내포하듯이 정수 타입의 변수에 범위를 주는 것인데, 다음과 같은 형태로 사용할 수 있다.

type Atype = 0..20;
     Btype = 10..20;

var a : Atype;
    b : Btype;

이 경우 변수 a는 0에서 20까지의 값을, 변수 b는 10-20까지의 값만 가질 수 있는 타입이다. 정수의 일부 범위만 사용할 수 있으므로 subrange 타입이라 불린다. Pascal의 type rule은 무척 간단한 편인데, subrange 타입이 들어가면 약간 복잡해진다.

예를 들어 a + b의 타입은 무엇일까? 직관적으로 생각해보면 a + b가 가능한 값은 10에서 40이니깐 10..40 sunbrange 타입이 되어야 할 것이다. 즉 a + b는 a의 타입도 b의 타입도 아닌 새로운 타입이 나오게 된다. 하지만 Pascal에서는 이런 type inference가 복잡하니깐 subrange 타입의 연산은 subrange type의 base type인 integer라고 하고 이 문제를 해결해 버렸다.

만약 a + b의 계산 결과를 새로운 subrange type의 변수 c에 대입(assign) 한다면 컴파일러는 어떻게 해야 할까? 이 경우 보통 dynamic check가 필요하다. 계산 결과가 subrnage type의 범위 내에 들어간다고 보장할 수 없다면 런타임에 검사를 해줘야 하기 때문이다. 물론 위의 예에서 우리가 a + b가 10에서 40을 벗어나지 않는다고 계산했던 것처럼 같은 정보를 컴파일러가 계산해 낸다면, 일부 dynamic check를 성능 향상을 위해 제거할 수 있을 것이다.

이렇게 계산한 값과 c의 subrange를 비교해서 만약 c의 값이 반드시 c의 subrange에 들어가면 dynamic check를 안전하게 제거할 수 있다. 반대로 계산한 값이 항상 c의 subrange를 벗어난다면, dynamic check를 넣는 대신에 컴파일 에러를 발생시켜야 할 것이다.

하지만 항상 이런 식으로 계산 결과의 범위를 예측할 수 있지는 않다. 다음 예제를 살펴보자.


a : integer range 0..20;
b : integer range 10..20;

function foo(i : integer) return integer is ...
...

a := b - foo(10);

이 경우에는 foo(10)이 외부 함수이므로, 이 코드만 봐서는 foo(10)의 범위가 어떻게 될지 예측하기가 어렵다. 결과적으로 b -  foo(10)이 0..20 사이에 포함될 것인지를 결정할 수 없게 된다. 따라서 컴파일러는 실제로는 필요가 없을 수도 있는 dynamic check를 넣어줘야만 한다. 만약 intraprocedural analysis를 통해서 foo(10)의 range를 계산할 수 있었다면 이런 불필요한 dynamic check를 없앨 수도 있었을 것이다.

이 케이스의 교훈은 type inference를 열심히 할수록 불필요한 런타임 오버헤드를 줄일 수 있다는 것이다. 여기서는 subrange type을 예로 들었지만, statically typed language에서도 type inference의 부재 혹은 한계로 인해 불필요한 dynamic check가 들어가 있는 경우가 많이 있다. Java의 배열 경계 검사(array bound checking)도 subrange type과 동일하다. 물론 정확한 계산 값을 얻는 것이 외부적인 요인, 혹은 계산의 복잡성으로 인해 원천적으로 불가능한 경우가 많이 있지만, type inference의 발전은 프로그램의 성능 향상을 가져올 것이다.


참고 문서
1. Programming Languages Pragmatics Michael L Scott p342-343,7.2.4 Type Inference

Python 2.5 With Statement

Posted 2006. 10. 6. 20:18
어제 밤에 중국 마이크로소프트 리서치에 있는 후배와 엠에센으로 놀다가 새로 나온 파이썬 2.5(What's New in Python 2.5)의 특징에 대한 이야기를 하였다. 각자 주요하게 봤던 내용 중에 하나씩 이야기 했는데 나는 ctypes 패키지를 꼽았고, 후배는 with 문을 꼽았다. 이 글에서는 PEP 343에서 논의를 걸쳐 파이썬 2.5에 포함된 'with' 문을 살펴보려 한다.

with 문은 자원 관리를 용이하게 하기 위한 syntactic sugar로 다음과 같은 형태를 가진다.

with open('/etc/passwd', 'r') as f:
    for line in f:
        print line
        ... more processing code ...

위 예제는 /etc/passwd 파일을 읽기 모드(r)로 연 후에 f라는 파일 디스크립토로 처리한다. with 블록이 끝나면 자동으로 파일을 닫아준다.

lock = threading.Lock()
with lock:
    # Critical section of code

마찬가지로 위 예제는 Lock을 잡고 Critical Section을 처리한 후에 with 블록을 벗어나면 자동으로 락을 해제해준다. 즉 일반적으로 try/finally 구문에서 처리하던 것을 with 블록을 통해 좀 더 편리하게 하자는 것이다.

구현 원리는 생각보다 간단하다. with에 사용되는 객체는 파이썬 용어에 따르면 컨텍스트 매니저(Context Manager)를 구현해야 하는데, __enter__와 __exit__ 메쏘드를 가지면 된다. 컨텍스트 매니저 객체를 with 문에 사용해주면 with 블록에 들어갈 때 __enter__가 불리고, 빠져나올 때 __exit__가 불린다. finally와 마찬가지로 예외(exception)가 발생해도 __exit__가 불리는 것이 보장된다. 컨텍스트 매니저 구현의 편의를 위해서 contextlib 모듈도 추가 되었다.

사실 with 문은 파이썬이 처음으로 도입한 것은 아니고, C#에서는 using이라는 construct로 이미 사용되고 있었다. 내가 알기로 C#도 처음은 아닌 것 같고, 다른 고전적인 언어에서 이미 사용이 되어왔던 걸로 안다. (어떤 언어인지 확실히 기억은 안 나지만...)

try/finally 구문의 불편함을 생각해 봤을 때 편리한 기능인 것만은 분명한 것 같다.


PS: ETH Zürich에서 만든 "The Oberon Programming Language"도 With 문을 제공하는데, 파이썬과 의미가 다르다. Oberon2 Language Report를 읽어보면, With문은 일종의 Type Guard 역할을 한다.

If v is a variable parameter of record type or a pointer type, and if it is of static type T0, the statement

WITH v:T1 DO s1 | v:T2 DO s2 ELSE s3 END

has the following meaning: if the dynamic type of v is T1, then the statement sequence S1 is executed where v is regarded as if it had the static type T1; else if the dynamic type of v is T2, then S2 is executed where V is regarded as if it had the static type T2; else S3 is executed. T1 and T2 must be extensions of T0. If no type test is satisfied and if an else clause is missing the program is aborted.

Example
  WITH t:CenterTree DO i := t.width; c := t.subnode END

Parametric Polymorphism과 Dynamic Typing

Posted 2006. 10. 5. 23:18
동적 타이핑에 대한 여러 가지 오해가 있는데, 그 중 하나가 동적 타이핑을 써야만  Polymorphism이 간단해진다고 데 믿는 것이다. 일반적으로 많은 프로그래머들이 C++의 Template이나, Java/C#의 Geneircs을 쓰다가 파이썬/루비의 Parametric Polymorphism만  접해보고 나서 성급하게 내리는 결론이 아닐까 쉽다.

실제로 2 개의 인자를 받아서 큰 값을 리턴하는 max 함수를 예로 들어보자.

<파이썬의 예>
>>> def max(a, b):
...     if (a >= b): return a
...     else: return b
...
>>> max(3,4)
4
>>> max(3.5,4.2)
4.2000000000000002


<자바의 예>
public class Foo {
    public static <T extends Comparable > T max(T a, T b) {
        if (a.compareTo(b) >= 0) return a;
        else return b;
    }

    public static void main(String... args) {
        System.out.println(max(3,4));
        System.out.println(max(3.5,4.2));
    }
}


위의 예를 보면 똑같은 일을 하기 위해서 파이썬은 간단히 변수 a, b만 있으면 되는 반면에 자바는 복잡한 문법으로 타입 변수  T에 대한 정보를 줘야 한다. 여기까지만 살펴보면, 파이썬이 훨씬 좋아 보인다.

한 가지 차이가 있다면 파이썬은 동적 타입 검사를 수행하기 때문에 a, b의 인자가 실제로 비교할 수 있는 연산자(>)가 정의되어 있지 않더라도 실행이 된다(실제로 실행할 때 런타임 에러가 발생한다)는 것이고, 자바의 경우는 Comparable 인터페이스를 구현하지 않은 객체를 넘기면 바로 컴파일 에러가 뜬다는 점이다.

그럼에도 불구하고, 여기까지만 살펴보면, 파이썬과 같은 동적 타입 언어가 Parametric Polymorphism을 훨씬 손쉽게 사용할 수 있는 것처럼 보인다. 자바의 Generics는 한 번 보고 직관적으로 사용할 수 있는 수준은 아니기 때문이다. 파이썬 입문자가 엄청난 생산성 향상을 맞보고 동적 타입 언어야 말로 개발자를 위한 언어라고 생각하는 게 무리가 아니다.

그렇지만 정적 타입 언어는 반드시 C++의 Template, Java/C#의 Generics처럼 저렇게 복잡한 타입 정보를 일일이 적어줘야만 하는 운명에 놓인 것인가? 정답은 그렇지 않다. 강력한 정적 타입 언어로 유명한 ML의 예를 살펴보자.

- fun umax (x, y, gt) = if gt(x,y) then x else y;
- umax (2, 3, op >);
val it = 3 : int
- umax (2.3, 3.2, op >);
val it = 3.2 : real

코드만 놓고 보면 파이썬과 별로 차이가 없다. (op >를 직접 코드에 쓰지 않고 인자로 넘긴 이유는 ML에서 > 연산자가 오버로딩 되었다는 점과 ML의 기본 타인 추론 규칙 때문인데, 자세한 설명을 생략한다.) 어쨌거나 x, y 두 개의 인자를 넘기는데, 타입 정보는 따로 준 적이 없다.

혹시ML은 파이썬과 마찬가지로 동적 타입 언어인데, 우리가 잘못 알았던 것일까? 그렇진 않다. ML은 무척 강력한 정적 타입 체킹을 하는 언어이다. 다만 타입 추론(type inference)를 통해서 불필요한 타입 정보를 생략할 수 있게 만들었다. 위 정보를통해서 ML은 x, y 변수가 >= 연산자가 정의되어 있는 임의의 타입임을 안다.

그럼 이 ML 코드가 파이썬 코드랑 뭐가 다른 것일까? 하는 일이 똑같다면 정적 타입 언어와 동적 타입 언어의 구분이 무의미할 테니깐 말이다. 개발자가 실수로 서로 비교할 수 없는 타입인 정수와 문자열을 비교했다고 하자.

ML의 경우
- umax (2.3, "hello", op >);
stdIn:1.1-17.7 Error: operator and operand don't agree [tycon mismatch]
operator domain: real * real * (real * real -> bool)
operand: real * string * ('Z * 'Z -> bool)
in expression:
umax (2.3,"hello",>)

바로 타입 에러가 나는 반면의 파이썬의 경우 int를 문자열로 바꿔서 비교한 후에 아무렇게나 리턴해 버린다. 물론 파이썬의 경우도 정말 정의가 안 된 연산(예를 들어 정수 + 문자열)에 대해서는 런타임에 타입 오류를 내는데, ML은 컴파일 타임에 알 수 있는 버그를 파이썬은 해당 코드를 실행시켜 보아야만 알 수 있다.

요약하면, 동적 타이핑 언어를 사용하면 Polymorphism 구현이 간단해지는 것은 사실이지만, 이 역시 컴파일 타임에 오류를 알 수 없다는 동적 타이핑 특유의 단점을 그대로 가지고 있다. 반대로 정적 타입 언어는 약간의 타입 정보만을 주면 이런 오류를 컴파일 타임에 모두 잡아낼 수 있다. C++, Java/C#처럼 뒤 늦게 Polymorphism을 추가한 경우 사용의 불편함이 있지만, ML처럼 처음부터 언어에 Polymorphism을 녹여넣고 타입 추론을 적극 활용하면 불편함을 최소한으로 줄이는 것이 가능하다.

똑같은 버그를 두고 1) 실행도 해보기 전에 컴파일하면서 잡는 방식, 2) 일일이 테스트하고 QA를 거쳐서 잡는 방식이 있다면 어느 쪽을 선택해야 할지는 자명하다. 소프트웨어 결함의 제거 비용은 시간에 대한 x^2 이상의 함수임을 잊지 말자. 현대의 스크립트 언어는 여러 가지 훌륭한 점이 많지만, 그 훌륭함이 동적 타입 체킹을 하기 때문은 아닐 것이다.

JSR305

Posted 2006. 10. 5. 16:02
Jsr-305 -- Annotations for Software Defect Detection란 글에서 소프트웨어 버그 탐지 도구를 위한 표준 어노테이션을 제정하는 JSR 305에 대한 간략한 소개를 한 바 있다. 초안(first draft)이 11월에 나오기로 되어 있기 때문에 아직 자세한 내용을 알 수는 없지만, JSR 홈페이지에 가보면 몇 가지 예제를 볼 수 있다.

Nullness annotations (e.g., @NonNull and @CheckForNull). Both FindBugs and IntelliJ already support their own versions of nullness annotations.


일단 FindBugs가 "David Hovemeyer, Jaime Spacco, and William Pugh. Evaluating and Tuning aStatic Analysis to Find Null Pointer Bugs, Proceedings of the 2005 ACM SIGPLAN-SIGSOFTWorkshop on Program Analysis for Software Tools and Engineering (PASTE 2005),Lisbon, Portugal, September, 2005." 논문에서 소개한 어노테이션으로 @NonNull, @CheckForNull 등이 있다. 메쏘드의 인자에 @NonNull의 어노테이션이 붙어있다면 해당 인자는 절대 null이 되지 않으므로 null 검사 없이 사용이 가능하다. 반대로 @CheckForNull이면, null이 될 수도 있는 인자이므로 반드시 null 검사를 한 후에 사용해야 한다. 이런 정보를 모아서 정적 검사 도구는 프로그램을 실행시켜 보기도 전에 '널 포인터 오류'가 발생할 것인지를 어느 정도 찾아낼 수 있게 된다.

Check return value annotation - an annotation that says ignoring the return value of a method is likely incorrect (e.g., String.toLowerCase())

또 하나의 활용 처는 리턴 값을 반드시 체크해야 하는지 여부를 어노테이션으로 표시하는 것이다. JSR에서 예를 든 것처럼 String.toLowerCase() 함수를 살펴보자. 자바의 String은 immutable 클래스이다. 따라서 toLowerCase() 메쏘드를 호출하면 해당 객체의 문자열을 소문자로 치환하는 것이 아니라 소문자로 치환된 새로운 문자열을 리턴해준다. 이 메쏘드를 호출하면서 리턴 값을 무시했다면 무엇인가 잘못 생각했음이 틀림 없다. String.toLowserCase()에 리턴 값을 반드시 체크하라는 어노테이션이 있다면, 결함 검사 도구가 오류를 자동으로 찾아낼 수 있을 것이다.

그 외에도 Taint, Concurrency 등 여러 가지 이슈에 대한 어노테이션을 생각 중이라고 한다. 이런 어노테이션 표준화로 얻을 수 있는 장점은 한 번 코드를 어노테이션 해놓으면 여러 검사 도구를 이용해서 다양한 버그를 자동으로 검출할 수 있다는 점이다. 현재는 FindBugs를 비롯한 개별 검사 도구가 각자 다른 어노테이션을 요구하므로, 검사 도구를 수행하기 위한 오버헤드가 상당하기 때문이다.

[참고자료]
1. JSR 305: Annotations for Software Defect Detection
2. Artima: Annotations for Java Software Defect Detection

git - the stupid content tracker

Posted 2006. 10. 5. 01:21
IBM developers@work을 보다가 git에 대한 소개 글을 읽게 되었다. 오픈 소스 계의 거장 리누스 토발즈(Linus Torvalds)가 리눅스 커널 소스 관리에 사용했던 BitKeeper를 버리고 새로 개발한 도구라고 하길래 유심히 살펴보았다.

프로그램을 설치하고, 입문서(tutorial)를 읽으면서 잠시 사용해 본 소감은, 기존 리눅스 커널 개발 환경에 최적화된 소스 관리 도구라는 거다. (그러려고 만든 거니 당연한 거지만...)

일반적으로 CVS나 Subversion이 중앙에 하나의 저장소(Repository)를 두고, 여러 사용자가 커밋(commit)하는 방식인 반면에, git(지트라고 발음함)는 이미 만들어진 저장소를 복사(clone)해 온 후에 로컬 저장소에 커밋하는 방식으로 각 개발자가 버전 관리를 하고, 이를 통채로 원래 저장소에 통합(merge)하거나 패치(patch)를 생성할 수 있게 되어 있다.

이른바 분산식 버전 관리 도구라는 건데, 리눅스처럼 거대한 프로젝트의 경우 많은 개발자가 달라 붙어서 작업하게 되는 만큼, 한 사람의 잘못된 커밋이 엄청난 숫자의 개발자를 고통에 빠뜨릴 수 있으므로, 반드시 필요한 방식이라 할 것이다.

하지만 이는 일반적인 개발 조직처럼 소수의 프로젝트 인원이 같은 장소에서 개발할 때는 크게 필요 없는 기능일 것 같기도 하다. 물론 git는 PUSH 기능을 이용하면 기존 CVS나 Subversion의 중앙식 버전 관리를 지원하기도 하는데, 이 경우에 git가 가지는 비교 우위가 무엇인지는 아직 잘 모르겠다.
Univ. of Maryland 있을 때 교수랑 대학원생들의 프로그래밍 언어 연구 모임(Software Chat)에 나갔었는데, 그때 가입한 메일링 리스트에서 종종 세미나 공지나 여러 이슈에 대한 알림글이 옵니다.

며칠 전에는 자바 메모리 모델(Java Memory Model)과 FindBugs로 유명한 Bill Pugh 교수님이 JSR 305 - 소프트웨어 결함 탐지를 위한 어노테이션을 출범했다고 메일을 보냈네요.

자바 쪽에 왕성한 활동을 하더니 JSR 스펙 리드도 맡고 있군요. 원래 FindBugs 프로젝트할 때 메쏘드 인자와 리턴 타입에 @Nullable, @NonNull, @CheckForNull 등의 어노테이션을 이용해서, 널 포인터 버그를 검사하는 등의 일을 했었는데, 이런 어노테이션을 표준화하려는 노력으로 보입니다.

개인적으로 가장 많은 관심을 가지고 있는 소프트웨어가 자동 버그 탐지 도구 쪽이라 관심이 많이 가네요.  메일링 리스트는 여기입니다.

일본 회사 테스트

Posted 2006. 10. 4. 02:07
현재 H 선배의 추천으로 일본에 있는 IT 회사 하나와 채용 프로세스를 진행하고 있습니다. 이 회사만의 특징인지는 모르겠지만, 추천-전화면접-프로그램 테스트 등의 순으로 면접이 진행되고 있습니다. 현재 전화 면접을 마쳤고, 개발 실력을 알아보기 위한 간단한 과제가 나왔습니다.

제가 다른 아르바이트로 바쁘다고 징징 거렸더니 생각보다 간단한 프로젝트를 줬네요. 매우 간단한 CPU 아키텍처를 정의하고 어셈블리를 주고서는, 인터프리터를 만드는 것입니다. 입력 어셈블리는 맞다고 가정하고(에러 처리할 필요가 없다는), 원하는 프로그래밍 언어와 환경을 마음대로 쓰라고 한 것을 보면 과제의 결과물을 꼼꼼하게 확인하기 보다는 전반적인 코딩 스타일과 습관을 보려고 하는 게 아닐까 합니다.

학교 숙제였으면 돌아가는 그 순간 그냥 제출해 버리고 잊었을텐데, 은근히 신경이 쓰이네요. 나름 테스트 케이스도 많이 넣고, 정성을 보여야 겠습니다.

Eiffel Language

Posted 2006. 9. 27. 21:09
컴퓨터공학 수업 시간에 "Eiffel Language"에 대해 발표했습니다. Eiffel 언어는 "Design by Contract"를 모토로 디자인/명세를 프로그래 속에 녹여 넣을 것을 강조한 언어입니다. 그 일환으로 언어 자체에 pre/postconidition, class invariant를 지정할 수 있게 되어 있습니다. 자세한 내용은 PPT를 참조하세요.

Debian 개발자

Posted 2006. 9. 24. 01:48
Debian 패키징 시스템에 대한 글을 보다가 마지막에 데비안 개발자 분포도 그림을 보고 새삼 미국과 유럽이 거의 모든 활동을 주도한다는 사실을 알 수 있었습니다.


다른 대륙은 정말 황량하네요.
« PREV : 1 : ··· : 8 : 9 : 10 : 11 : 12 : 13 : NEXT »