목요일, 12월 28, 2006

코딩 가르치기

현대의 교육에서 비참하게 실패하는 부분은 코딩 가르치기인것 같다. 실제로 대단한 내용이 없음에도 불구하고, 우리때 c 프로그래밍 랭귀지의 평균학점은 미적분학이나 물리I 보다 나빴던것으로 기억한다. 심지어 M$ 같은 돈있고 사람 많은데서 일한 Joel Spolsky 의 경우도 "학생들아 잡질하지 말고 c 나 지대로 열심히 배워라!" 라고 이야기한다. 면접을 들어가봐도 비슷하다. 랭귀지 컨스트럭트를 잘 이해하고 쓰기는 커녕 strcpy 도 못만드는 친구들이 수두룩하다.

이 문제에 대해서 나는 전혀 이해할 수 없었다. 코딩을 배운지가 너무 오래된지라, 코딩이 어렵다는것 자체가 이해가 불가능했던 것이다. 그러나 몇번 코딩 초년생들과 이야기 할 기회가 있었고 그 이유를 대강 감을 잡았다.

현 대의 코딩은 Dijkstra 의 "goto statement considered harmful" 에서 파생된 structured programming (구조화된 프로그래밍)에서 시작한다. 구조화된 프로그래밍의 사전적 정의는 제쳐두고, 어떻게 하는것이 '구조화된 프로그래밍'인가 하면, function(함수) 을 잘 사용해서 코딩을 하는것을 결국에는 뜻한다. OOP 도 고작해봐야 functional abstraction(함수를 이용한 추상화) 을 잘해서 유지 보수도 쉽고, 보기도 이쁜 코드를 만들자. 라는 취지에서는 그게 그거라고 볼 수 있다.

- 문제는 바로 이 함수를 이용한 추상화에 있다.

우리 두뇌가 문제를 푸는 방법은 어떤 반복되는 패턴을 블랙박스로 만들고 그걸 레고 블록 쌓듯이 쌓아 올라가는것을 즐기지 않는다. 해법은 철저하게 시작이 있고 끝이 있으며, 그 자연스러운 흐름을 따르는데, 함수를 이용한 추상화 보다는 순서도 형태에 적합한 형태인데, 이는 현대의 코딩 훈련도감인 구조화된 프로그래밍에 맞지 않는다.

엎친데 덮친격으로, functional abstraction 을 배우려면, 코드를 읽고, 쓸줄 알아야 하는데, 현대의 언어는 아무리 쉽다고 해도 숙지해야 될 요소가 많고, code 라고 불리듯이 이미 암호여서, 자기 생각을 코드로 만들고 꺼꾸로 읽는것을 익히는것 자체도 어려운일중에 하나가 된다.

그렇다면 코딩을 어떻게 가르쳐야 할까?

1. 코드를 찍어내는 공장에 취업하지 않을사람 (fortran/ matlab/ mathematica 를 쓰게 될 사람)

BASIC(original) 을 가르친다. BASIC 은 구조화된 프로그래밍에 반하고 파워풀 하지도 않지만, 뭔가 computer 로 하기에는 큰 지장없다. 재귀호출 안되는게 뭐 대수라고. 자연스런 문제해결과 코드사용이 맞아 떨어진다는것은 큰 장점이다.

2. 코드를 찍어내는 공장과 여러모로 관계가 생길 사람

이 분들은 어쨌든 structured programming 을 익혀야 한다. 그렇지 않고는 공동작업, 유지보수가 가능한 코드를 만들수 없다. 이쪽의 문제는 코딩을 잘하기 위해서는 functional abstraction 을 잘해야하고, 그 이전에 language 의 construct 를 익히는것 자체가 장애로 작용한다는 것이다. 이쪽의 방법은 langauge 의 construct 를 배우지 않고, functional abstraction 하는 방법을 intensive 하게 익히는 것이다. 언뜻 듣기에 말이 안되지만, 가능하다!

태초에 괄호가 있었다. scheme 을 배우면 된다. 이쪽은 language construct 자체가 거의 없을뿐 아니라, 다른 언어처럼 어설프게 자연어를 사용하여, 인간이 자연어스러운 추론을 하는것을 괄호가 애초에 배제한다. 이게 어려우면 선수과정으로 1번정도를 가볍게 해주는것은 어떨까? 함수를 사용한 추상화에 능숙해지면, 랭귀지 자체를 배우는 것은 어렵지 않다.

적합한 교재로는 어떻게 프로그램을 디자인할까? 를 추천한다


P.S: 다음은 이 주제에 대해 읽을만한 아티클이다.

왜 죠니는 코딩을 못하나요?

코딩 적성 테스트

월요일, 11월 21, 2005

ultimate dynamic language?

다들 lisp family 를 이야기 하는 이유는?

나의 리습에 대한 태도는 여러번 바뀌어 왔다.

1. it's elegant & beautiful (학부 2년때 처음 접하면서)
2. stupid syntax all over the place (1년 전쯤 다시 공부하기 시작하면서)
3. ultimate cheating!(이건 사기야)

아무튼 3번은 리습이 좋은 랭귀지란 뜻이고, 이글은 왜 리습이 ultimate dynamic language 인지를 설명하기 위한 글이다.

소프트웨어의 세계는 complex software 와 complicated software 로 양분 되어 있다. 현재의 discipline 의 주류는 complex software 제작에 맞추어져 있다. 이해 못할 만큼 복잡한 것은 없지만, 컴포넌트간의 플로우가 복잡한 시스템을 complex software 라고 하고, 대표적인 예로 word processor 를 들 수 있으며, 이런 소프트웨어 제작에는 M$ 가 무적이다.

그와 반대되는 complicated software 는 내부가 아무리 복잡하고 어렵더라도, 외부로 노출되는 인터페이스는 몇개의 API 로 정리되는 물건을 이야기하고, OS 의 서브시스템인 file system 과 memory management API 가 대표적인 예다.

문제는 현재의 디서플린과 다르게, 우리가 일상생활에서 만드는 대부분의 소프트웨어는 complicated software 이다.

complex software 는 top down 으로 서로간의 interaction 과 제작자와 컴포넌트의 룰을 명확하게 구분하면서 만드는것이 맞지만, complicated software 의 경우에는 그 따위로 개발했다가는 중복 투자는 기본에, 바닥부터 다시 만드느라 data structure 와 algorithm 및 flow 조금이 바뀐다면, 꽤나 괴로워 진다. (물론 핸들하는 방법은 있겠으나, 핸들하는 방법은 correct 한 software 를 upgrade 한다는 가정하에 이루어지므로 현실적이지 않다.)

저런 분야야 말로 bottom-up programming 이 빛을 발한다. 제대로 된 bottom-up programming 을 하기에 필수적인 요소는

1. universal data type 및 dynamic typing - data structure 가 바뀔경우 처리가 자연스럽다.
2. reflection - program 이 자신에 대해 discover 를 하고 그 내용을 이용하거나 수정할 수 있는 것을 말한다. 가장 일반적인 예는 eval() 이다.

이 둘을 사용하기 위해서는 랭귀지가 dynamic 인 편이 좋다. 1번은 보통 쉽게 제공 된다. 2번의 경우는 대부분의 다이나믹 랭귀지에서 제공된다.

직관적으로 2번을 제공하기 위해서는 code parsing module 및 AST 를 들고 다녀야 함을 알 수 있다. run-time parsing & code generation 을 위해서는 그런 이유로 꽤 overhead 가 커진다.

그렇지만, lisp 의 case 를 살펴 본다면 code 와 data 가 s-expression 으로 표현되고 parsing 하기가 쉽기 때문에, additional cost 는 zero 에 가까움을 알 수 있다. 심지어는 compile time syntax translation 따위도 공짜로 따라온다. reflection 을 마음껏 쓰면서도 performance penalty 에 대한 죄책감을 덜 가져도 된다. 게다가 오래전 부터 lisp 의 성명절기는 self-modifying code 였다.

이런 이유로 lisp 은 die-hard 랭귀지이고, dynamic language 를 쓰는 사람은 결국 lisp 에 맛을 들이는게 아닐까 싶다.

화요일, 8월 30, 2005

What should we learn from Functional Languages?

1. side-effect management

side effect 는 버그의 근원이다. 성능을 희생하지 않으면서 side effect 를 줄이는 디자인을 언제나 생각해야 한다. 다른말로는 그러한 design philosophy 를 information hiding 이라고도 할 수 있다.

2. bottom-up programming (DSL)

lisp/scheme 을 프로그래밍 하다 보면, 자신이 랭귀지의 키워드를 늘이고 있다는 느낌을 갖게 된다. 잘 프로그램된 lisp/scheme program 은 문제에 걸맞는 keyword 와 문제자체를 잘 기술하는 sub-language 가 프로그램안에 랭귀지 형태로 합쳐지게 된다. 이 어프로치를 계속 따라가다보면 어떤 문제를 만났을때, programming language 를 디자인 하는 방법을 사용하게 되며, 이렇게 해서 생겨난 언어를 Domain Specific Language 라고 한다.

c 같으면 library 를 만드는 방법으로 시작하겠지만, 코드/데이타의 구분이 약한 scheme/lisp 에 비해 문제를 describe 하도록 언어를 만드는 것이 쉽지는 않다. 그렇지만 c++ 에서는template/generic 은 bottom-up programming 의 좋은 툴이다.

c++ 에서 template/generic 은
1) 랭귀지의 세만틱과 충돌하지 않는다. (특히 class의 inheritance)
2) code generation 시 많은 부분의 validation 이 가능하다
는 특성때문에 특히 bottom-up programming 에 적합하며, 실제의 예도 c++ 의 언어적으로 약한 면을 강화시키는 경우를 많이 찾을 수 있다. 예) stl -> data structure 의 부재 해결, smart_ptr -> auto deallocation의 부재 해결

3. function 에 대한 이해

dijkstra 가 "goto considered harmful" 을 썼을때 의미하는 바는, goto 를 쓰지말고, function 을 사용한 abstraction 을 하자이다. (스티브 맥코넬이 주장하는 micro-scopic 한 주장이 아니다.) 그 당시는 function call 시 context save 의 cost 가 아까왔던 때이고 assembly 를 이용한 label 로 떡칠된 spagetti code 가 주류를 이루던 시절이다.

그 당시에 lisp 은 여전히 있었고, lisp 은 이러한 문제를 tail-call optimization 과 macro 를 사용한 inlining 으로 해결했다. 그 외에 lexical closure 와 first-class citizenship 이 제공되는 랭귀지의 특성상 lisp/scheme 을 잘 사용한다면, function 을 잘 사용할 수 있다. (대표적인 예가 c++::boost 의 lambda 이다.)

토요일, 6월 18, 2005

Back to Basic

요즘 어처구니 없는 실수를 해댔다. writing solid code 에서 제일 감동 받은 부분은 코딩은 신념의 우선순위를 관리하는 일이라는 주장이었다. 해이해졌는지, 가장 바닥에 있는 신념을 잊었다.

1. 절대로 특정 영역에 버그가 없을거라 신뢰하기.
2. 클라이언트 (물론 안심할 영역안에 있는)를 신뢰하기.

이런 짓을 했다. 반성하는 의미에서, 다시 주문을 건다.

클라이언트는 한사람의 생명을 책임지지만, 서버는 several thousand 이상의 생명을 책임진다. 아무것도 믿어서는 안된다!

% 신선놀음으로 하고 있던 리습 공부 덕일지도 --;

목요일, 5월 26, 2005

language power

c/c++

그만큼 최적화된 속도로 어플리케이션을 만들 수 있게 만들어주는 것은 assembly 뿐, assembly code 의 잇점은 고작해야 멋대로 stack unwinding 따위를 할 수 있게 해주고, spagetti code 를 이용하여 context save를 줄일 수 있는 ( c/c++ 의 공격적인 inlining 으로 비슷한 효과를 볼 수 있다) 정도다. c++ 의 경우 memory hungry 한 환경에서는 쓸 수 없다. exception 과 RTTI 따위로 이래저래 쓸데 없이 메모리를 먹는다. (물론 디스에이블하고 쓰는 pedant 들도 있다. )
다른 언어가 c/c++ 만큼 최적화를 할 수 없는 단 한가지 이유는 c/c++ 의 자료형태가 memory 의 alignment 와 cache-miss 를 생각하면서 프로그래밍하기 편하게 되어 있기 때문이다. 소위 high-level language 에서는 그런 자유가 없다.

java

java 는 문법만 보면 pascal 과 비슷할 정도로 pedentic 하고 쓸데 없지만, java 를 웹 플랫폼으로 볼 경우 큰 장점을 제공한다. java 는 html embeding 이 jsp 를 사용해서 편리하게 되고, remote call 을 EJB 로 제공하고, 소위 web 을 통한 system 을 구축할 경우 all-in-one package 다. 게다가 static language 라서 native code genration 시 performance 또한 강력하다.

python

python 의 가장 큰 장점은 productivity 다. peopleware 에서는 툴에 따라 생산성이 달라지지 않는다고 했지만, python 을 쓴 rapid-prototyping 이나, c 의 6~10 배 정도의 computation load 를 참아 줄 수 있는 상황이라면 정말 강력하다. 생산성의 이유는 1. language 의 core 가 가장 잘 관리되고 있다. (IDE 관점에서도 다른 script language 는 따라오기 힘들다. ) 2. 어떤 라이브러리를 찾던 다 있을지어다 이다. 약점? 없을리 없다. threading 이다. 더 자세한 것에 관심이 있다면 GIL 을 구글링 하면 된다.

perl

perl 도 python 과 같은 장점을 가지고 있다고 주장할 perl aficianado 들이 있겠지만, 개인적인 경험으로는 cpan 의 criss-cross reference 되어 있고 perl core 에 따라서 인컴패터블한 라이브러리를 깔려고 시도해 본사람이라면, perl 이 만만치 않은 물건이라는데 동의할 것이다. perl 의 장점은 5.8 에서 부터 지원된 java 의 threading semantic 과도 흡사한 threading 을 통한 industrial strength 와 regular expression engine 이다. perl/expect/bash 는 awk/sed/sh 을 현재로썬 대체해버린 어드민들의 친구이기도 하다.

lisp

lisp 이라고 하면 lisp dialect 의 총칭이고, 현재 살아남은 놈들이라면, ansi common lisp/scheme/emacs lisp 정도다. 이 패밀리의 특징이라면. 1. interpreter 든 compiler 든 만들기 위해서 문법 파싱이 필요 없다. 2. church 의 computation model 인 lambda calculus 와 잘 대응한다. (특히 scheme) 사실 두가지 이유로 랭귀지의 문법이 아닌 시멘틱스를 연구하는 입장에서는 툴로 많이 쓴다. 그 외에 정말 powerful 한 feature 라면 syntax 자체를 변형하여 사용할 수 있는 강력한 마크로 기능이다. 이에 대해서는 Paul Graham 의 yahoo 에 팔아먹은 회사 이야기가 좋은 레퍼런스다.

토요일, 4월 02, 2005

peopleware

game architecture & design 이라는 책이 있다.

실패케이스와 원인을 줄줄이 늘어놓은 다음, 성공한 사람들의 인터뷰후에 "그들은 소림사에서 왔다. 배우거나 따라할 수 없다" 로 결론을 내버린다. 참으로 한심하기 그지 없었지만, 나 또한 밥먹듯이 success is not reproducible 을 외친고로 그 책이 솔직하다고 생각했다.

이 책은 원래는 소림사가 되어야 할 회사가 마굿간으로 전락하는 이유를 잘 설명했다.

사람을 일하게 하는데 가장 큰 모티베이션은 당근과 채찍이 아닌 "자긍심" 이다. "자긍심"은 납기일을 맞추는 것이 아닌, 훌륭한 퀄리티의 물건을 만드는데서 나온다. 여기서 나오는 자발적인 에너지를 이용하자는 것이 이 책의 결론이다.

주의할 점은 그러려면 그럴수 있는 인간을 뽑아야 한다. 무능한 인간은 아무짝에도 도움이 안된다. 더 중요한 것은 슈퍼 개인이 아닌 "슈퍼 팀"이 필요하다는 것이고, 그들을 거느리기 위해서는 사조직을 용납할 수 있어야 한다는 것을 보여준다.

화요일, 3월 08, 2005

implicit things

c 언어가 주류이던 시절에는 성능을 끌어내기 위해서는 architecture 를 잘알아야 했고, cache miss 와 alignment 를 신경써야 했다.

그런데 지금처럼 c++ 이나 스크립트 언어가 주류인 상황에서는 그보다 더 중요한것은 language 에서 해주는 implicit 한 feature 들의 구현에 대해 잘 알아야 성능을 끌어낼 수 있다. 현대 컴퓨터 언어는 사용이 편한 대신에 c 와 다르게 under the hood 에서 해주는 일이 꽤 많다. 그런 이유로 compiler 와 interpreter 의 동작에 대해서 알면 효율적인 코드를 쓰는데 도움이 된다. 다음은 일반적으로 현대 언어 ( c++/java 또는 interpreter 기반의 script 언어) 에서 성능이 좋은 코드를 쓰기 위해서 고려해야할 것들이다.

1. instance 들의 카피를 어떻게 최적화 하고 피할 것인가?
- reference 의 적극적인 사용과 c++ 의 경우 RVO (return value optimization ) 을 제공하는지 체크 한다.

2. 긴 list 나 array 또는 dictionary 를 어떻게 바닥부터 채우는것을 피할것인가?
- script language 에서는 흔히 list 종류의 자료구조들은 hash_table 로 구현되어 있으며, 사이즈가 어느 이상 커질때마나 rebucketting 을 한다.

3. garbage collection
- 단단한 script 언어의 경우 gc 가 제공된다. ref count 기반에 상호참조의 경우 leak 이 생기는 것을 막기 위해서 이기도 하고, 한편으로는 귀찮은 ref count 를 구현하기 싫어서이기도 하다.( 후자는 java) 이런 경우 오랜 동안 실행이 pending 되는 garbage collection 을 피하는 디자인을 고려해 봐야한다.

4. string append
string 이 정해진 size 보다 커져야 할때는 다시 할당되고 카피가 일어나야 한다. 보통의 스크립트 랭귀지에서는 append 마다 일어난다. 고로 python/php 는 이런 케이스에 join 을 제공한다. c++ 도 안심할바는 못된다. 충분한 크기를 잡고 sprintf 를 쓰는 것에 비하면 확실히 재할당과 카피는 std::string 에서는 많이 일어난다.

5. exception
dynamic language 처럼 local resource 를 book-keeping 을하고 있다면 추가 코스트는 없지만, c++ 의 경우 추가 코스트가 무엇인지를 알고 있어야 한다.