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가 가지는 비교 우위가 무엇인지는 아직 잘 모르겠다.
« PREV : 1 : ··· : 69 : 70 : 71 : 72 : 73 : 74 : 75 : ··· : 82 : NEXT »