20세기 중반 미국의 물리학자 리처드 파인만은 21세기에도 절대 통용되는 궁극의 알고리즘을 가지고 있었습니다.
Write down the problem.
Think very hard.
Write down the solution.
정말 대단하지 않습니까? 예전에 어느 블로그에선가 한번 보고 기억해두고 있었는데, 최근에 회사에서 제 옆자리에 앉는 "하녕오빠"-_-라는 분이 파인만의 여섯가지 물리 이야기라는 책을 보고 있길래 생각나 버렸습니다.ㅎㅎ
실용주의 프로그래밍이니, eXtreme Programming이니, 애자일이 어쩌구, TDD가 어쩌구 하는 소위 개발방법론이라는 분야의 수많은 저술들은 결국 파인만의 알고리즘을 벗어나지 못하고 있는것 같습니다. 수많은 용어들이 난무하고 새로운 대안인듯 갖은 방법론을 제시하는건, 단지 세번째 단계에서 생기는 약간의 서술방식의 차이가 아닌가 합니다.
결국 어떤 방법론을 타고 개발 프로젝트를 수행하던지 간에, 개발이란 목적이 되는 무언가를 만드는 것이고, 목적이 되는 무언가는 그 자체로서 목적을 가지는 하나의 산물이죠.. 즉, 누군가가 "problem to be solved"을 제기하였고, 그래서 개발자들은 두번째 단계인 "빡쎄게 머리를 굴리는" 과정을 거쳐, 세번째 단계의 코딩을 하는 겁니다.
"그렇게 하는건 실용주의 프로그래밍 정신에 위배되는 거자나.." "애자일이란 그게 아니지.." "XP를 도입하기로 해놓고 이제와서 왜 그러는거야" "Test-driven을 지향하려면 지금 단계에선 그걸 하고 있을때가 아냐" 등등등.. 이런 말들은 개발경력도 좀 되고 나름 실력도 인정받고 있는 분들이, 후배나 동료에게 즐겨 하시는 말들일 겁니다.
방법론의 허상(??)에 얽매여 있다는 생각은 해 보셨나요?
프로그래머는 어떤 기능을 수행하는 소프트웨어를 만들어 내는 일을 하는 것이지, 소프트웨어제작 과정에 수행 절차를 설계하는 사람은 아닙니다. 물론, 개발이라는 작업을 어떻게 수행할지를 계획하고 설계하는 일이야 말로 개발을 "삽질"이 되지 않게 하는, 중요하며 필수적인 단계임은 누구나 다 압니다. 하지만 그 방법론의 셋업이 작업의 목적은 아닙니다.
원하는 기능이 충분히 수행되며, 꽤 적절히 에러를 처리하여 안정성도 어느정도 되는데다가, 비교적 다른 개발자도 읽기에 무리가 없는 소스코드를 만들고 있는 후배 개발자에게 (게다가 일정을 못 맞출것 같아 보이지도 않습니다.) "애자일하지 못한데.." 라고 찬물 끼얹고 갈 필요는 없는 일 아닐까요..
정말 애자일이 뭔지, XP가 뭔지 등등을 가르쳐 주고 싶다면, 함께 수행할 프로젝트가 생겼을 때, 아니면 현재 프로젝트를 현시점부터 공동 진행하도록 해서, 함께 머리맞대고 의논하고 개발하는 과정에서, 가르치고 싶은 방향으로 유도해주어야 하는 겁니다.
찬물 끼얹고 가는 하늘같은 선배에게 "그럼 지금 어떻게 작업을 진행해야 할까요?" 라고 되묻는다면, 그 선배는 아마도 기꺼이 현 상태를 짚어보고 수행중인 프로젝트의 내부를 자신의 빵빵한 경력에서 나온 통찰력으로 들여다 봐줄 것입니다. 그러나 그정도 들여다본후에 대부분은 "이런.. 지금은 그냥 이렇게 진행하는게 불가피한 상황이자나.." "불쌍한 녀석.. 그냥 니가 참아라.. 니가 잘못해서 이렇게 된게 아니다." 이런식으로 말해 버립니다. 차라리 그냥 "나도 잘 몰라.."라고 말하면 비겁하지나 않죠..
무척이나 안타깝게도 저보다 개발경력이 길면서 현재도 뛰어난 통찰과 노하우로 개발을 하고 계시는 선배님은 많지 않습니다. 도중에 개발영역을 벗어나, 기획, 관리, 전략 이런 직책이나 부서로 이동을 해버리기 때문이죠.. 그래서 저런 아쉬운 소리나마 해줄만큼 애정어린 선배들을 만나기가 쉽지 않게 됐습니다. 이젠 제가 선배노릇을 해줘야 하는데.. 저는 그렇게 잘 알지 못합니다. 돌아버리겠습니다.
다만 한가지.. 방법론의 화려함에 현혹되어, 목적을 잃고 주객이 전도되는 추한 모양새는 보이지 않기 위해 오늘도 애쓸 따름입니다.
파인만의 궁극의 알고리즘을 이야기 해 보고 싶어서 시작한 글이 어쩌다 이런 방법론의 허상이 어쩌구 하는 신세한탄으로 흘렀는지 모르겠군요;; 역시 전 긴 글은 안맞나봅니다.ㅡ_ㅡ;
이곳에서 발견한 기압조정 귀마개 상품.. 우리 마님께서 비행기 탈때 약간의 귀 통증을 호소 하시는데 하나 선물해줘야 겠군요^^
기압조정 귀마개
프로그램 언어들의 이름은 어떻게? 프로그래밍 언어들은 각기 다 이름을 가지고 있죠.. 바로 그 프로그래밍 언어의 작명에 대한 포스트네요.. 알고 있던것도 있고, 처음 알게된것도 있고.. 재밌네요^^
Overview of Clustering and Clusty Search Engine 웹검색을 개발하고 있는 입장에서 클러스터에 대한 글을 빼놓을수가 없군요.. 다만 영어울렁증이라는 고질병때문에 반쯤 읽다 말았다는..ㅠㅠ 좀이따가 다시 잘 읽어봐야겠습니다^^
이올린스토리 이올린이 개편을 했습니다. 개편과 함께 오픈한 이올린에 관한 TNC의 공식적인 발표가 올라오는 블로그 입니다. 자주 들여다 보고 태터+이올린의 좋은 기능들을 잘 활용해볼랍니다^^
Keely Hauzel 파이 영국 최고의 섹시 글래머 Keely Hauzel 이라는군요..
-------- 프로그래밍을 하다 보면 참으로 어처구니없이도 많은 Exception을 만나게 됩니다. 언어와 플랫폼에 무관하게 언제든지 exception은 프로그래머를 잡아먹을 기세로 눈을 번뜩이고 있습니다. 그래서 사용하는 exception처리 기법이 try-catch 구조 입니다.
의외로 경력 깨나 된다는 분들도 try-catch 구문을 무시하는 경향이 있습니다. readability(가독성)을 몹시 떨어뜨리는 데다가 로직의 흐름을 한눈에 보기 힘들게 만들기도 하고, 코드가 길어지게 합니다. 때로는 디버그를 위해서 어떤 에러가 나는지를 직접 에러를 일으켜 보면서 따라가 봐야 할 때도 있습니다. 그래서 경험많은 프로그래머들도 깜빡 빼먹기 쉽습니다.
이쯤 읽으셨으면 "나는 이거 안읽어도 되겠다", 혹은 "한번 읽어봐야겠다"는 판단이 드실겁니다. 맞게 판단하셨으니 그렇게 하시면 됩니다^^ 어렵게 익힌 테크닉이나 심오한 로직따위는 나오지 않습니다.;;;
각설하고.. try-catch의 아이디어는 간단합니다.
어떤 코드를 우선 try(시도)합니다. 시도 결과 exception이 발생하면, 미리 정의된 exception 대응 행동(catch)을 수행합니다. 요즘의 대부분의 CPU는 exception이 생기는 몇가지 상황을 정의해 두었고 이 때에 특정 핀에 신호가 뜸으로서 exception을 알려줍니다. 이를 직접 사용하기도 하고, 컴파일러, 특정 API 또는 인터프리터레벨에서 한단계 더 포장하여 사용하는 경우도 많이 있습니다. 뭐가 됐건 간에 시도후에 실패하면 처리할 코드를 미리 정의해 두는게 기본 아이디어입니다.
C++, C# 등에서는, [CODE type=cpp] try { // some statements... } catch (Exception e) { // handling statements... } [/CODE] 이와 같은 구문을 쓰죠.. 발생한 exception은 보통 객체(객체의 참조)로 전달받을수 있는데, 이는 사용하는 컴파일러, API, SDK, Framwork등 오만가지 이름으로 불리워지는 개발 플랫폼-_-에 의해 그 Type 명이 달라지기도 하므로 사용하는 개발환경의 문서를 참고하여 만들면 됩니다.
Python에서는 [CODE type=python] try: # some statements except: # handling statements [/CODE] 이렇게 쓰면 됩니다.
미리 정의된 구체적인 Exception을 핸들링 할 때에는, 예를들어, [CODE type=python] try: # some statements except KeyboardInterrupt: # Ctrl-C handling statements except: # handling statements else: # No exception [/CODE] 이런 구문으로 Ctrl-C키를 눌러 중단시그널을 보냈을 경우와 기타 exception이 일어났을 경우, exception이 하나도 안생겼을 경우 수행할 코드까지 모두 정의 가능합니다. [CODE type=python] try: # some statements finally: # whether an exception is asserted or not [/CODE] 이렇게 하면 try후에 exception이 있던 없던 간에 finally 코드가 수행됩니다. 하지만 except: 구문과 finally: 구문은 함께 있을수는 없습니다. Python document에 의하면, 어떤 것을 먼저 수행해야 하는지가 모호해 지기 때문에 막는다고 되어있군요..
어찌됐건 예기치 못한 에러 상황에서 애써 작성한 프로그램이 죽어버리는 일이 생기지 않도록 하려면 exception handler를 만들어 주어서 에러를 견뎌 내도록 만들어 주어야 합니다. 이게 말이 쉽지 막상 그렇게 코드를 만들려고 하면 보통 짜증나는 일이 아닙니다.ㅡ_ㅡ
다음 예를 보죠.. [code type=python] def get_host_and_port(s_addr='localhost:13579'): ''' s_addr로 hostname:port notation의 서버주소를 받아서 호스트명과 포트번호를 쪼갠다. ''' s_fields = s_addr.split(':') from string import atoi return s_fields[0], atoi(s_fields[1]) [/code] 이 간단한 함수는 고맙게도 왠만해서는 매우 훌륭히 작동해 줍니다^^ Python의 interactive-mode prompt에서 수행하면 이렇게 됩니다. [code type=python] >>> get_host_and_port('asdf.com:1234') ('asdf.com', 1234) [/code]
그런데 문제가 있죠.. [code type=python] >>> get_host_and_port('asdf.com') Traceback (most recent call last): File "<stdin>", line 1, in ? File "<stdin>", line 4, in get_host_and_port IndexError: list index out of range [/code] 그렇습니다. 포트번호를 안주면 에러가 납니다. 그래서 코드를 고칩니다. [code type=python] def get_host_and_port(s_addr='localhost:13579'): ''' s_addr로 hostname:port notation의 서버주소를 받아서 호스트명과 포트번호를 쪼갠다. ''' s_fields = s_addr.split(':') from string import atoi try: hostname = s_fields[0] portnum = atoi(s_fields[1]) except: portnum = 13579 # default port로 가정 return hostname, portnum [/code] 이번에는 잘 될까요?
[code type=python] >>> get_host_and_port('asdf.com') ('asdf.com', 13579) >>> get_host_and_port(3) Traceback (most recent call last): File "<stdin>", line 1, in ? File "<stdin>", line 5, in get_host_and_port AttributeError: 'int' object has no attribute 'split' [/code] 이전에 exception을 내던 문제는 해결됐는데 다른 문제가 또 있군요.. 이번 문제는 parameter의 type문제 입니다. 결정을 해야 합니다. 숫자 파라메터를 받을수 없다고 exception을 내버리던지, 에러를 리턴하던지, 무시하고 미리 정의된 기본값으로 리턴할지..
from string import atoi try: hostname = s_fields[0] portnum = atoi(s_fields[1]) except: portnum = 13579 # default port로 가정 return hostname, portnum [/code] 처음의 단 두줄짜리 함수가 이렇게 지저분하게 indenting되며 늘어집니다. 자칫 코드에 로직이 선명히 보이지 않게 될 수 있습니다. 지금 예로 든 함수도 적절히 디버그되지 못하였습니다. 다만 try-catch 구문의 거의 유일한 단점인 가독성 저해를 강조해 보려고 일부러 저렇게 만든 것입니다. 가져다 쓰지 마세요. 위험한 프로그램을 만들어 줄겁니다;;; (참고로 위 함수는 리턴이 두번 나옵니다. 매우 바람직하지 못하므로 도저히 피해갈수 없는 상황을 제외하고서는 절대 여러개의 리턴을 가지는 함수를 만들지 마세요. 석달후에 후회합니다. 게다가 아직도 핸들링되지 않은 exception발생가능성이 10,12,16 라인 등에 도사리고 있습니다. 로직 자체에도 결함이 있습니다. 귀차니즘의 압박으로...쿨럭;;;)
이렇게 코드가 더러워진듯 보여도 실제 저 코드를 사용하는 프로그램이 죽어버릴 가능성은 많이 줄어들었습니다^^
안정적으로 잘 돌아가는 프로그램일수록 소스코드는 try-catch로 얼룩져 있습니다. 개발직후 갖은 혹독한 테스트 환경에서 수도없이 많은 어처구니없는 exception을 리포팅 받아 모두 적절히 견뎌낼 수 있도록 만들어지기 때문에 안정적인 프로그램이라고 칭송받는 겁니다. 많은 이들이 욕하는 M$의 제품군들 역시 수많은 try-catch로 얼룩져 있으며, 윈도우98시절에 비해 현재는 M$의 소프트웨어가 대단한 안정성 향상을 이룩했다는 것을 누구나 인정합니다. (절대 밝혀져서는 안될 어둠의 루트로 M$ 코드의 일부를 본적이 있습니다^^;;;)
try-catch의 거의 유일한 단점이 코드를 지저분하게 만드는 것이라고 했는데, 사실 습관 들이기 나름으로 가독성을 저해하지 않기도 합니다. 이건 제가 뭐라 말할 수 있는 성격이 아니군요.. 프로그래머 각자가 exception handling 코드를 포함해서도 읽기 좋은 코드로 만드는 습관을 들이는 것이 스스로에게도 도움되는 일일 것입니다. try-catch를 가급적 nesting안한다던지.. 등등등
유일한 단점이 아니라 "거의" 유일한 단점이라고 했는데, 다른 단점들도 많이 있겠지만 또 한가지 들수 있는 단점은, 완벽한 try-catch는 에러를 내지 않는다는 겁니다. 이건 또 무슨 말일까요?ㅡ_ㅡ
try-catch로 잘 막아서 절대 에러가 나지 않는 어떤 함수를 만들었다고 해 봅시다. 이런 함수를 다른팀의 누군가가 사용하려고 합니다. 이사람은 잘못된 parameter를 입력으로 넘겨주는 엄청난 버그를 만들어 놓고도 절대 발견하지 못합니다. 우리가 제공해준 함수는 어떤 잘못된 입력이 와도 어쨌거나 에러 없이 코드를 수행하고 적당히 리턴값을 만들어 주니까요.. 버그를 만든 사람은 역시 다른팀 코드를 가져다 쓰니 로직에 문제가 생긴다고 생각해 버리겠죠..
그래서 assert나 raise (어떤 언어에서는 throw)구문을 활용해서 적절히 일부러 exception을 일으켜 주거나, 전통적인 C함수의 방법처럼 리턴코드를 항상 리턴값에 포함시켜서, 현재 리턴값은 exception handling의 결과임을 알리는 방법을 사용해야 합니다. exception으로 인해 소프트웨어가 멈추지는 않더라도 call-stack의 상위 코드에 exception발생사실을 알려줄 필요가 있다는 거죠..
도무지 엔지니어링의 세계는 얼마나 많은 trade-off를 더 해야 할지 모르는 세계입니다. 저는 아직도 어디까지를 raise하고 어디까지를 감춘채 handling할지를 결정보지 못했습니다. 아마 적어도 제가 죽기전에는 그런 공식은 나오지 않을겁니다.
마지막으로 은근히 유용한 try-catch 구문의 장점을 말씀드리겠습니다. "실력있어 보인다"는 겁니다.ㅡ_ㅡ;;; 서두에도 말했던 것처럼 생각보다 많은 개발자 분들이 십수년의 경력에도 불구하고 exception에는 관대합니다. 이 글을 읽으신 여러분이 exception에 엄격해 진다면 실력있어 보이는 것을 넘어 실력있는 개발자로 인정 받으실 겁니다.
자신감을 가지세요.. 여러분이 생각하는것보다 훨씬 많은 사람이 당신의 코드를 보고 감탄합니다. 진짜로 여러분이 만든 코드는 뛰어납니다. 모든 사람의 머리가 다르기 때문에 항상 새로운 코드가 만들어지기 때문입니다. 자신있게 작성하고 테스트하고 진화(evolution, innovation)해야 더 훌륭한 코드를 만들수 있을 겁니다.