창업하고 나서 GUI 프로그래밍을 많이 하게 된다. 원래 하던 웹에 더해서 플래시에 아이폰에 안드로이드까지. 그러면서 개발자들의 실력 차이가 정말 크게 드러나는 분야가 GUI라는 생각이 들곤 한다. 실력차가 크게 드러나는 이유 중 하나는 OOP다. GUI 프로그래밍은 OOP를 모른다면 제대로 돌아가게 만드는 것조차 힘든 분야다. 겉으로는 얼추 돌아가는 것처럼 보여도 버그가 산재해 있는 경우도 많다. 코드의 양도 엄청난 차이가 난다. 내가 본 경우에도 OOP를 아는 사람과 모르는 사람의 코드 라인수가 10배 가까이 차이가 나는 것을 본 적이 있다. 생산성의 차이는 그보다 더 컸으리라 짐작된다. 사실 난 개발이란 분야에서 전문가와 초보자간 생산성 차이가 최대 40배까지 난다는 이야기를 처음 들었을 때는 믿을 수가 없었다. 에이, 아무리 그래도 설마. 하지만 몇 차례의 경험 이후 조금 믿게 되었다.
이를테면 이런 식이다. 초보자에게만 개발을 맡기면 처음에 기능의 개수가 적고 단순할 때는 그럭저럭 생산성을 낸다. 전문가만큼은 아니라도 전문가 절반만큼은 되는 것도 같다. 그런데 시간이 지나면서 점점 생산성이 떨어지고 어느 순간 생산성이 0이 되는 순간을 맞이한다. 더욱 놀라운 것은 그 시점을 지나면 마이너스로 내려가기도 한다는 것이다. 되던 기능을 망가뜨리고, 다른 사람이 코드를 건드리는 것 자체를 불가능하게 만들어버린다. 결국 그 사람이 만든 소스 코드를 폐기하게 되면 마이너스의 크기는 더 극적으로 변한다. 이 단계에 이르면 생산성 차이는 40배 정도가 아닐 것이다.
써놓고 보니 굉장히 익숙한 이야기다. 개발자들이 자주 하는 푸념 아니던가? 코드에 더 이상 손을 댈 수가 없어. 다시 짜는 것 말고는 답이 없어. 이런 일이 자신(혹은 팀)에게도 자주 일어난다면 왜 자신의 실력이 발전하지 않는지 고민해야 한다.
그렇지만 그래도 대부분의 프로그래머들은 그럭저럭 헤쳐 나간다. 시스템의 복잡도가 늘 엄청나게 큰 것은 아니기 때문이다. 시스템 프로그래밍이 어렵다 어렵다 하지만 중요한 알고리즘 문제는 이미 다 해결되어 있기 때문에 공학적인 복잡도는 별로 높지 않다. 엔터프라이즈 쪽도 비즈니스 로직의 복잡도는 대부분 RDBMS가 커버하며, 아키텍처도 결국 요청을 받아서 응답을 내주는 것으로 어느 정도 단순화되어 있다. 하지만 GUI는 알고리즘 하나 잘 짠다고 해결되는 것도 아니고, RDBMS가 쿼리 로직을 다 감당해주는 것도 아니다. 거기에 사용자 인터액션이 다양하게 들어온다. 엔터프라이즈는 보통 stateless가 많고, 설령 기능적으로 stateful하다고 하더라도 거시적인 비즈니스 로직 관점에서만 stateful하다. 하지만 GUI는 비즈니스로직 뿐 아니라 화면에 뿌려지는 모든 컴포넌트가 다 stateful하다. 엔터프라이즈에서 stateful이 될 때 stateless보다 얼마나 복잡해지는지를 생각해보면 GUI가 얼마나 복잡할지 상상할 수 있을 것이다.
이것이 아마 제대로 동작하는 GUI 애플리케이션이 드물고, 또 그래서 몇 안되는 GUI 애플리케이션이 시장을 다 잡고 있는 이유일 것이다. 해당 분야에서 시장을 지배하지 못하는 애플리케이션은 기능도 기능이지만 대체로 제대로 동작하지 않는 상황을 흔하게 접한다. 프로그램 쓰다가 아니 이 기능도 얼마 안되는 프로그램이 왜 이렇게 느린거야하는 생각이 든 적 없는가? 아이폰은 저렇게 플릭이 매끄러운데 왜 옴니아는 손가락을 고문하는가? 도대체 MSN 메신저 live는 왜 이렇게 느린 거야. 어떻게 더 intelligent한 IDEA는 메모리 60메가 먹고 있는데 이클립스는 왜 600메가씩 먹고도 느린 거야. 왜 Anycall PC manager는 자꾸 에러를 띄우는 것인가. 왜 우리투자증권의 머그는 띄우는데 5분씩 걸리고 창도 안 닫아지는 거야. 왜 오픈오피스는 느려터진데다가 자꾸 죽는 거야.
GUI 개발이 쉬운 일이라면 이렇게 되지는 않았을 것이다.
그럼 어떻게 하면 GUI 개발을 좀더 잘할 수 있을까? 뭐 개발을 잘하는 방법에는 여러 가지 도가 있겠지만 오늘은 OOP에 초점을 맞춰보자. GUI 개발은 OOP가 없이는 불가능하다. 비 OOP 언어로 개발된 Gtk조차도 OOP적인 개념을 차용하고 있다. OOP에도 여러 가지 특성이 있겠지만 GUI 개발에서 특히 중요한 요소는 다형성이다. GUI에서 다형성이 중요한 이유는 뭘까. 간단한 예를 들어보자. 탐색기에서 파일을 선택하고 Ctrl-C를 누르면 파일이 클립보드로 복사된다. 반면, 웹브라우저에서 텍스트를 긁어서 Ctrl-C를 누르면 텍스트가 복사된다. 이건 어떻게 구현할까? 다음처럼 짜면 어떨까?
function copy(source) { if (source is file) { copy_file_to_clipboard() } else if {source is text) { copy_text_to_clipboard() } }
돌아가는데는 문제가 없다. 그런데 이미지도 복사하고 싶으면 어떻게 될까? 저기에 else if가 한 줄 추가되어야 할 것이다. 복사하려는 객체의 종류가 늘어날 때마다 copy함수도 같이 늘어난다. copy만 있으면 뭐 그것만 고치면 되겠지. 그런데, 윈도우 시스템을 다 인스톨해놨는데 고칠 수는 없다. 누군가 새로운 종류의 객체를 copy&paste 가능하게 만들고 싶어도 할 수가 없는 것이다. 설령 고칠 수 있다 하더라도 문제는 남는다. paste는 어떻게 할 것인가? paste에도 똑같은 if else가 들어가야 할 것이다.
OOP에서는 이렇게 해결한다.
function copy(source) { source.copyTo(clipboard) }
즉, 각 객체의 copy 동작은 각 객체에 위임하는 것이다. 다형성은 이런 식으로 if문을 제거한다.
이건 너무 전형적인 예라고 생각하는가? 그렇다면 이건 어떨까. 메일 클라이언트를 만든다고 해보자. 받은편지함에 들어가서 메일을 읽을 때는 답장, 전달, 삭제 등의 메뉴가 나와야 한다. 임시보관함에서 메일을 읽을 때는 보내기 메뉴가 나와야 하고 휴지통에서 읽을 때는 복원이나 완전히 삭제하기 등의 메뉴가 나와야 한다. 이 메뉴를 어떻게 그릴 것인가? 혹시 이렇게 메뉴를 그리려면 지금 어느 편지함에 있는지 알아야 한다라고 생각했다면 당신은 아직 절차적 패러다임으로 생각하고 있는 것이다. 그럼 아마 이렇게 짤 것이다.
class 메일클라이언트: def drawMenu(편지함): if 편지함 is 받은편지함: menu.add(답장) menu.add(전달) menu.add(삭제) elif 편지함 is 임시보관함: menu.add(보내기) elif 편지함 is 휴지통: menu.add(복원) menu.add(완전삭제) draw(menu)
이제 이걸 어떻게 짜야하는지 알 것이다. 어느 편지함에 있는지를 알려 하지 말고 편지함에 그 일을 맡기면 된다.
class 메일클라이언트: def drawtMenu(편지함): draw(편지함.getMenu()) class 받은편지함: def getMenu(self): menu.add(답장) menu.add(전달) menu.add(삭제) return menu
말하자면, 어떤 상태에 따라서 동작이 달라져야 하는 경우에 if else를 쓰지 말고 다형성으로 해결해야 한다는 것이다. if else를 쓰면 그 상태에 따라 달라지는 분기문이 코드 이곳 저곳에 산재하게 된다. 그리고 보통 그 분기 조건도 쓰는 곳마다 미세하게 다르다. 받은편지함의 목록 볼 때 나와야 할 필드와 보낸편지함의 목록을 볼 때 나와야 할 필드는 다르지만 또 사용자가 임의로 만든 폴더는 받은편지함과 비슷할 수 있다. 이런 것들이 다 if else로 처리되면 나중에 감당이 안되는 코드가 남는 것이다.
기능 차원의 거시적인 예를 들었지만 사실 미시적인 부분에서도 이런 다형성은 계속 필요하다. 이를테면 자식 뷰를 가질 수 있는 뷰 컨테이너를 그리는 코드는 어떻게 짤까? 컨테이너에 무슨 뷰가 들어와 있는지 if else로 확인하면서 그릴 수는 없을 것이다. 이 경우도 자식 뷰의 draw를 차례로 호출해서 그리는 로직을 위임한다. 그래서 화면 가득이 컴포넌트가 넘쳐나는 화면도 if else를 쓰지 않고 그리는 코드를 짤 수 있는 것이다.
사실 이런 것을 설명해야 한다는 사실 자체가 당혹스러웠던 적도 있다. 심지어 코드 리뷰할 때 type code를 쓰는 부분을 제거해야 된다는 이야기를 했더니 그럼 어떻게 해야 되느냐는 질문이 나왔는데 그 질문에 대답할 수 있는 사람이 아무도 없었던 적도 있다. 하지만 사실 생각해보면 대학에서 OOP를 가르치는 것도 아니고, 학원에서 OOP를 가르칠 리도 없고, 실무에서 가르쳐줄 수 있는 실력자가 많은 것도 아니니 당연한 현상인지도 모른다. 아마 다형성만 정확하게 이해하고 써도 전세계 개발자의 10% 안에는 충분히 들지 않을까.
굳이 전세계를 언급한 이유는 안드로이드의 소스코드들도 반 OOP적인 특성으로 가득하기 때문이다. 처음에 다른 개발자들이 짠 안드로이드 앱 코드를 보면서 뭐 이따위로 짜놨나 생각을 했었는데, 안드로이드 소스코드와 예제들에 다 그렇게 되어 있었다. 그 소스를 보고는 정말 이게 구글에서 짠 코드인가 하는 생각마저 했었으니까. 이를테면 이런 것들이다. 앞서 이야기한 type code, 거의 OOP의 적이다. 그런데 안드로이드는 내부 소스 뿐 아니라 API에서도 type code를 개발자가 선언하고 쓰게 만드는 API가 많다. 게다가 거의 최근 10년 간 본 적도 없던 out parameter까지 등장했다. OOP 뿐 아니라 절차적 프로그래밍의 관점에서도 아주 나쁜 관례다. 상태를 많이 만들고 기억하게 만드는 코드이기 때문이다. 객체를 생성하고 사용하는 코드를 봐도 일단 생성한 다음 이것저것 세팅을 하고 나서 실행해야 하는 API가 여럿 있다. 헝거리안 표기법은 앞서의 단점들에 비하면 그래도 참아줄 만한 단점인지도 모른다. 아마도 C 개발자들이 자바 문법만 배우고 개발한 게 아닌가 하는 생각이 든다.
안드로이드 공식 문서의 퍼포먼스 팁도 거의 반 OOP 가이드나 다름 없다. 객체를 만들지 말라고 하고 인터페이스 대신 버추얼을, 버추얼 대신 스태틱을 쓰라고 할 정도니 심각하다. internal getter/setter도 나쁘다고 하니 상속할 때는 멤버를 죄다 protected로 하란 말인가. 또 하나 심각한 것은 field lookup을 멤버로 캐시해두라는 것. 거의 써먹을 만한 팁이 몇 안된다. 이런 식으로 코딩했는데도 안드로이드의 화면 응답성은 아이폰만 못하다면 안드로이드 개발자들의 실력이 부족한 거라고 봐야 하지 않을까.
자바도 OOP의 전통이 상당히 깊은 언어지만 아직 다형성을 정확하게 이해하는 개발자는 별로 많이 만나지 못한 것 같다. 자바 계열은 프레임웍을 상당히 많이 쓰고 프레임웍은 다형성 없이 존재할 수 없는 것인데 왜 이런 현상이 발생할까? 내가 세운 가설 하나는, 프레임웍이 나무만 보고 숲을 보지 못하게 만드는 넛지를 주기 때문이 아닌가 싶다. 위의 메일 클라이언트로 예를 들면, 받은편지함.getMenu만 짜게 만들기 때문에 메일클라이언트.drawMenu를 보지 못하는 것이다. 사실 다형성은 편지함.getMenu를 implement하는 곳에 있는 게 아니고 메일클라이언트.drawMenu에 있다. 이걸 봐야 다형성이 왜 필요한지 아는데, implement만 하다보니 이런 것을 잃어버리는 것이다. DependencyInjection이나 InversionOfControl도 결국 다형성으로 설명하는 것들이다. 그런데 다형성을 모르고 이런 개념들을 쓰는 사례가 간간이 보인다. AOP로 나아가는 것도 좋겠지만 그 전에 OOP부터 마스터하고 넘어가야 한다. 언제 또 이야기할 기회가 있겠지만 AOP는 OOP 없이 제대로 활용할 수 있는 개념이 아니며, 또한 AOP가 필요하다고 생각하는 많은 부분이 OOP로 해결된다.
아뭏든, OOP 개발자로 거듭나기 위해서는 다형성을 이해하는 것이 그 첫걸음이다. if else가 코드 안에서 번식하기 시작한다면 다형성을 생각해보자.