»
S
I
D
E
B
A
R
«
Testable Design Anti-Patterns
Dec 17th, 2009 by Wegra Lee

한 4년쯤 전에 정리를 시작했던 주제로, 지인 몇 명과 논문으로 써볼까 기고를 할까 고민하다 제 때 빛을 보지 못했다. 실례를 찾아 보강하는 것이 가장 큰 숙제였는데, 불행히도 그 후 테스트 관련 업무를 접할 기회가 없었어서 기고까지는 하지 못했다.

Testable Design Fundamentals

In other to make software testable, designer should follow some rules. If you are interested in software design, some of them must be familiar to you. That’s because they are rules for improving software ‘design‘ in testability perspective. Lets look into the rules one by one.

  1. Clear Specification: Specification should cover all possible situations even for illegal conditions. Moreover, it must be clearly defined without any ambiguous sentences.
  2. Controlability: Target should provide mechanisms to read/write the conditions or to run the operations which are required to verify its functionality. It should be easy enough to implement.
  3. Modularity: Adequate modularization is one of the fundamental requirements not only for design, implementation, and reuse, but also for test. For an example, a module which has many relationships with other modules is hard to test independently. It should use stub/mock object of wait until the dependant modules are implemented.
  4. Readability: Easy and intuitive naming reduces human errors and decreases design/implementation time. It makes overall testing time shorter.
  5. Consistency: All of the above rules should be reflected consistently so that the software can be look like designed and implemented by one person.

Software which satisfies these rules can be called testable. However, it is very hard to measure quantitatively how well the rules are reflected. I’ll remain this issue for other dedicated articles.

(in Korean)

테스트 가능한 소프트웨어를 만들기 위해서는, 설계자는 몇 가지 규칙을 따라야 한다. 소프트웨어 설계에 관심있는 사람이라면, 친숙한 이름의 규칙들을 찾을 수 있을 것이다. 이유인 즉, 이 규칙들은 테스트의 관점에서 소프트웨어의 ‘설계‘를 향상시키기 위한 지침이기 때문이다. 그럼 그 규칙들을 하나씩 살펴보도록 하자.

  1. 명확한 기능 명세: 소프트웨어 명세서는 잘못된 상황까지 포함한 모든 가능한 상황을 기술해야 한다. 이 때, 의미가 분명한 문장만을 사용해야 한다.
  2. 조작성: 테스트 대상은 그 기능 동작 여부를 판단할 수 있는 정보를 읽고/쓰거나 기능을 동작시킬 수 있는 메커니즘을 제공해야 한다. 또한 소프트웨어 적으로 쉽게 구현할 수 있어야 한다.
  3. 모듈화: 적절한 모듈화는 소프트웨어 설계, 구현, 재활용 뿐 아니라 테스트를 위해서도 꼭 필요하다. 예를 들어, 다수의 다른 모듈과 종속성이 있는 모듈은 독립적으로 테스트하기 어렵다. Stub/Mock Object를 사용하거나, 타 모듈들이 구현되기까지 기다려야 한다.
  4. 가독성: 쉽고 직관적인 이름(모듈, 함수 등 모든 경우에 해당됨)은 휴먼 에러(human error)를 줄여주고, 설계와 구현 시간을 단축시킨다. 결과적으로 테스트에 소요되는 전체 시간을 단축시키는 효과가 있다.
  5. 일관성: 이상의 모든 규칙들은 마치 한 사람의 설계하고 구현한 것처럼 일관성 있게 적용되어야 한다. 각각의 모듈마다 그 정도가 다르다면 휴먼 에러(human error)를 증가시킬 것이다.

이상의 규칙들이 충족된 소프트웨어라면 ‘테스트할 수 있다’고 얘기할 수 있을 것이다. 단, 이런 규칙들이 얼마나 잘 반영되었는지를 정량적으로 측정하는데는 분명한 한계가 있다. 측정 이슈는 주제의 범위를 벗어나므로 본 글에서는 더 자세히 다루지 않을 것이다.

(Read the full article)

설계 vs. 프로그래밍 언어
Dec 14th, 2009 by Wegra Lee
나는 객체지향 개념을 접하기 시작한 97년부터
모든 프로그램을 짤 때에 설계에 초점을 맞춰왔다.
그래서 자연스럽게
객체지향, UML, GRASP, Design Pattern, Bug Pattern, Refactoring, Aspect-Oriented 등등..
의 개념을 접하게 되었고,
실제로 3개월 정도 설계만 하고 보름 미만으로 코딩해서
제대로 돌아가는 프로그램을 만들어본 경험도 있다.
이리저리하여 난 설계의 중요성을 인식하고 권장하게 되었지만,
이상하게도 주위 다른 사람들은 그렇지 않았다.
처음엔 굉장히 답답하여 계몽(?) 시키고자 노력해보았지만,
설계를 시켜도 도저히 설계라 하기 민망한 수준에서 더 이상 진척이 없었던 것이다.
그러면서도 코딩을 시키면 마음엔 안들어도 그럭저럭 돌아가게는 만들어 냈다.
내게 ‘아하~!’하는 깨달음이 온 것은..
1년하고 조금 더 전쯤..
영어 공부 한답시고 한동안 자칭 ‘영어로 사고하기’ 놀이를 하고 다닌 적이 있다.
그러면서 깨달은 것은 ‘아는 만큼 생각한다’이다.
한국어로 생각하고 영어로 표현하려면 어휘력이나 문장력이 부족해 표현을 못한다.
하지만 처음부터 영어로 생각하면 사고의 범위 자체가 좁고 얕아지는 걸 느꼈다.
나는 남들에게 자신 있게
‘난 Java로 free talking 할 수 있다’고 얘기한다.
그리고 대다수의 배테랑 개발자들은
나와 같은 의미에서 자신의 주 언어로 free talking할 수 있을 것이다.
문제는 설계이다.
UML은 개발 언와와는 또 다른 종류의 언어이다.
하지만 거의 100%의 개발자들은 설계용 언어 보다는
개발용 언어를 먼저 익힌다.
필요에 의해서건, 흥미에 의해서건
설계용 언어를 접할 때는 이미 상당 수준의 개발 경험이 갖춰진 후이다.
그들에겐 낯설은 설계 언어로 사고하는 것은 너무 답답하다.
코드로 작성하면 금방 만들 수 있는 것도
설계 언어로는 어떻게 표현해야할 지 알질 못한다.
영어에 능숙치 못한 한국인이
미국인에게 무언가 얘기는 하고 싶은데
말 못하는 것과 마찬가지 이치다.
결국 설계를 하느니

그 시간에 직접 코딩하는 걸 택한다.

우연히 하드 디스크에서 발견한 글이다. 마지막 수정 시간은 2005년 4월 5일. 무슨 계기로 적어놨는지 기억도 나지  않고, 지금보니 설익은 느낌도 들지만 이곳으로 옮겨놓고 지워버리기로 했다. ^^

나는 객체지향 개념을 접하기 시작한 97년부터 모든 프로그램을 짤 때에 설계에 초점을 맞춰왔다. 그래서 자연스럽게 객체지향, UML, GRASP, Design Pattern, Bug Pattern, Refactoring, Aspect-Oriented 등의 개념을 접하게 되었고, 실제로 3개월 정도 설계만 하고 보름 미만으로 코딩해서 제대로 돌아가는 프로그램을 만들어본 경험도 있다.

이리저리하여 난 설계의 중요성을 인식하고 권장하게 되었지만, 이상하게도 주위 다른 사람들은 그렇지 않았다. 처음엔 굉장히 답답하여 계몽(?) 시키고자 노력해보았지만, 설계를 시켜도 도저히 설계라 하기 민망한 수준에서 더 이상 진척이 없었던 것이다. 그러면서도 코딩을 시키면 마음엔 안들어도 그럭저럭 돌아가게는 만들어 냈다.

내게 ‘아하~!’하는 깨달음이 온 것은.. 1년하고 조금 더 전쯤.. 영어 공부 한답시고 한동안 자칭 ‘영어로 사고하기’ 놀이를 하고 다닌 적이 있다. 그러면서 깨달은 것은 ‘아는 만큼 생각한다’이다. 한국어로 생각하고 영어로 표현하려면 어휘력이나 문장력이 부족해 표현을 못한다. 하지만 처음부터 영어로 생각하면 사고의 범위 자체가 좁고 얕아지는 걸 느꼈다.

나는 남들에게 자신 있게 ’난 Java로 free talking 할 수 있다’고 얘기한다. 그리고 대다수의 배테랑 개발자들은 나와 같은 의미에서 자신의 주 언어로 free talking할 수 있을 것이다.

문제는 설계이다. UML은 개발 언와와는 또 다른 종류의 언어이다. 하지만 거의 100%의 개발자들은 설계용 언어 보다는 개발용 언어를 먼저 익힌다. 필요에 의해서건, 흥미에 의해서건 설계용 언어를 접할 때는 이미 상당 수준의 개발 경험이 갖춰진 후이다.

그들에겐 낯설은 설계 언어로 사고하는 것은 너무 답답하다. 코드로 작성하면 금방 만들 수 있는 것도 설계 언어로는 어떻게 표현해야할 지 알질 못한다.

영어에 능숙치 못한 한국인이 미국인에게 무언가 얘기는 하고 싶은데 말 못하는 것과 마찬가지 이치다.

결국 설계를 하느니 그 시간에 직접 코딩하는 걸 택한다.

[나쁜 팀 문화] 너는 생각할 필요 없어. 생각은 나 혼자..
Dec 11th, 2009 by Wegra Lee

내가 최근 과제를 진행하면서 가장 불만이 많았던 문화 중 하나로, 간단히 이야기하면 소수의 누군가가 생각해서 가이드를 만들고 다수의 사람들에게 기계적으로 적용하는 문화이다. 얼핏 생각에 괜찮은 어프로치로 생각될 수 있고, 심지어 권장되는 상황도 많다. 하지만 잘못 남발하면 부작용이 큼을 몸소 느꼈기에 주의하자는 차원에서 정리해보았다.

시작..

우리 팀은 ‘아키텍트(Architect)’라는 멋진 이름의 조직을 갖추고 있다. 이름에 걸맞게 팀내 주요 아키텍쳐적 이슈들을 논의하고 해결안을 찾아 지침을 내려준다. 팀원들도 나름 팀내에서 선별된 사람들로 구성되어 있다. 여기까지만 보면 문제가 있기는 커녕 모범적이라 할 수 있다.

하지만 아키텍트 그룹이 내놓은 가이드의 상당수가 많은 헛점을 보여왔다. 지침대로 적용을 하려던 개발자들로부터 많은 공격을 받아 갈팡질팡하고 번복되기 일쑤였다. 아키텍트들의 결정에 대한 신뢰가 점점 떨어졌고, 개발자들 사이에서는 ‘최대한 늦게 적용하는게 좋다’ 라는 말까지 공공연히 오고가기도 했다.

무엇이 문제였을까? 단순히 아키텍트들의 실력이 부족해서는 아니었다. 이런 현상이 발생할 수 밖에 없었던 상황을 나름 조명해보았다.

팀 환경/문화

팀의 아키텍트 그룹이 제 역할을 하지 못하게 된 데에는 수많은 문제들이 복합적으로 작용한 것으로 보인다. 팀의 전반적인 상황은 지금까지 기술되었던 (그리고 앞으로 더 추가될) 모든 Bad Team Culture 시리즈[1]의 종합 선물세트라 보면 된다. 빠른 진행을 위해 간략히 요점을 정리해보았다.

우리 팀은 거의 항상 무리한 일정에 맞추기 위해 과속 주행을 해왔다. 산적한 모든 일들이 최상의 시나리오대로 완료되어야만 한다. 그 시나리오도 실무자가 아닌 윗선에서 정한 deadline 에 기반한다. 팀 창설 이래 단 한 번도 deadline 에 맞춰본 역사가 없음에도 항시 같은 패턴이다.

거의 모든 아키텍트들의 주 업무는 사실 관리다. 이들은 대부분 서브팀의 리더들로, 자신의 서브팀 업무 처리로도 이미 숨이 벅차다. 더구나 이들은 개발 실무를 담당할 여력도 없다. 큰 그림의 아키텍처나 팀원들이 제기한 구현 상의 이슈에 대해 의사 결정은 참여하지만 직접 구현에 참여하거나 팀원들이 작성한 소스를 살펴보진 못한다.

개발자들 역시 발등에 떨어진 업무들로 다른 논의에 참여할 마음의 여유를 갖지 못한다. 구현 하나만으로도 일정이 빠듯한데, 각종 요청들이 ‘가능한 빨리’ 라는 수식어와 함께 동시 다발적으로 날아온다. 코드 리뷰나 리펙토링 같은 사치스런 용어는 책속에서나 볼 수 있을 뿐이다.

다수 모듈에 적용되는 공통 가이드에 대해서도 깊이 있는 논의와 충분한 공유 대신, ‘일주일 내로 모두 적용해!’ 와 같은 명령이 떨어진다. 가이드 자체도 결함 없는 완벽한 것이라는 이상적 결과를 기준으로 한다. 보완책으로 일부 모듈을 대표로 적용해보기도 하지만 서브팀 내에서의 커뮤니케이션도 부족한 상황에서 도메인이 다른 특정 모듈이 전체를 대표하리란 기대는 품지 않는 것이 좋다.

부작용과 악영향

결과는 아주 부정적이다.

‘아키텍트 = 관리자’ 이므로 논의가 계속 산으로 간다. 한국적 정서상의 문제도 있겠지만, 회의의 비효율성을 불평하면서도 둘을 분리하지 못한다. 어떤 회의에서건 두 이슈가 마구 섞여 논의되므로, 소수의 전담 아키텍트(non-관리자)는 진행 상황조차 파악하기 어려울 때도 많다. 예를 들어, 잠시 묻혀 있던 이슈를 결론짓기 위해 꺼내놓으면 ‘아! 그거 지난번 관리회의에서 x로 결정해서 a, b, c 가 진행중이야.’ 라는 이야기를 아무렇지도 않게 들려준다. 그러면서 전담 아키텍트이니 아크 이슈들을 추적 조율하라는 모순적인 요구를 한다.

팀 전체의 상황을 고루 파악하고 있는 아키텍트가 없다. 다른 서브팀의 상황까지 신경쓸 여유가 없으므로, 아키텍트 회의가 소집되어도 자신의 팀과 직접적으로 관련되어있지 않다면 잘 참석하지 않는다. 충분한 사전 조사와 깊은 논의 없이, 모인 사람만으로 쉽게쉽게 결론을 내는 경향이 생겨 추후 번복의 여지가 많다. 일부 모듈을 대표로 선정해 해결책을 검증해 보더라도, 다른 모듈에는 대대적 칼질을 요하는 경우가 많았고, 가끔은 적용 과정에서 지침을 수정해야만 하는 counter example 이 나오기도 한다.

General 아키텍트의 고뇌.. 에피소드

이러한 상황에서도 어쩔 수 없이 아키텍트로써의 일을 수행하며 개인적으로 많은 고충들을 겪었다. 다른 아키텍트들 대부분은 자신의 분야에 대해서만 직접적인 책임을 지는데 반해, 나는 어지간한 이슈들에는 다 끼어 들어가야 했다. 즉 general 아키텍트였고.. 팀에 단 한 명 뿐이었다.

그러는 와중, 여러 모듈에 걸친 가이드를 만들어야 하는 업무들이 자주 떨어졌다. 나름 최선의 안을 내보려 노력하지만, 결정 과정에서는 언제나 trade off 가 발생한다. 특히나 논의 단계에서의 실무자 참여를 배제하는 상황에서는 수많은 추측과 가정들 위에서 이리저리 저울질을 하게된다. 중간중간 상황을 공지하지만 관심을 주는 사람은 거의 없다. 관심 좀 가져달라고 애원을 해야 한 두 명 답변을 준다. 실무자들을 참여시켜달라는 요청에는 그들은 시급한 다른 일로 시간이 없다는 답변만이 메아리칠 뿐이다.

어찌저찌 가이드를 만들어 공지하지만, 본격적인 일은 이 때부터다. 가이드를 만들면 매니저는 N 일 내로 가이드를 적용하라고 공지한다. 그제서야 드디어 실무자들의 피드백들이 쏟아지기 시작한다. 질문이 터져나오고, 문제점을 지적하고, 나아가 대안을 제시하기도 한다. 개발자들의 성향과 경험이 다양한 만큼 다양한 안들이 나올 수 있다. 대부분은 사전 고려된 안들이기어, 그들들에게 trade off 와 이러저런 상황 요소를 열심히 설명한다. 결국 논리와 양해로 설득을 한다 치더라도 가이드와는 다른 이야기들이 오고가기 시작하면 개발자들은 우왕좌왕 하기 시작하고, 정리된 하나의 안으로 재공지될 때까지 자신의 모듈에 적용하는 것을 미루려 한다. 정리되는듯 싶다가도, 늦게서야 적용하는 모듈 때문에 또 한바탕 소란이 벌어지기도 한다.

묵묵히 가이드를 잘 따르는 것 역시 그리 좋지 않다. 대부분의 가이드는 이상 추구보다는 현실 수용적이다. 과제 초기라면 누가봐도 깔끔한 가이드를 제시하며 떳떳해할 수 있겠지만 현실은 너무도 다르다. 수년에 걸쳐 이미 수십만 라인의 코드를 만들어놨고, 개발자들을 릴리즈 일정에 쫓기고, 다수의 레거시 모듈들을 버무려야 하는 상황인 것이다. 그렇다면 변경량이 적고 에러 유발 가능성이 적으면서 그럭저럭 봐줄만한 절충안을 내놓을 수 밖에 없다. (싹 뜯어 고치자 하면 기획팀/매니저들이 동의하지 않는다.) 내가 봐도 부끄러운 가이드를 던져주면서 사람들이 뭐라고 생각할 지 걱정한 적도 있다. 실력있는 사람들은 보자마자 훨씬 좋은 방법들이 머릿속에 떠올릴 수 있었을 것이고, 그렇지 않은 사람들에겐 그닥 좋지 않은 가이드를 익히게 만든 것이다. 결과로 만들어진 가이드 자체는 부족하더라도, 그 결론까지의 여러 대안들을 연구/분석하고 장단점을 저울질하는 과정을 함께 한다면 모두의 역량 향상에 큰 도움이 됐을 것이지만, 안타깝게도 그럴 시간은 주어지지 않았다.

그나마 현 상황을 함께 겪고 있는 지금의 동료들은 부족함을 이해해 주겠지만, 나중에 합류한 사람들은 어떨까? 결과물만 보고 전임자의 무능함을 욕해도 딱히 비난할 수도 없다. 그리고 그 제품이 세상에 공개된다면? 이력에 적어 넣기도 부끄럽고, 공백기로 둘 수도 없는 계륵 경력이 되어버린다.

가정 자체가 틀어질 때도 있다. 한 번은 개발자들이 도메인에 익숙하지 않고 영향 범위가 제법 크다는 가정으로 2~3주에 걸쳐 좀 억지스러운 가이드를 만들었다. 첫 설명회에서 모두들 ‘우린 그런 문제 없어.’, ‘해당 사항이 거의 없으니 조금만 신경쓰면 충분히 할 수 있을 거 같아.’ 라는 반응들이 것이다. 그 자리에서 바로 정석적인 방식으로 진행키로 결정하고 그에 맞게 약간의 보강 설명을 해주는 것으로 설명회를 마무리했다. 2~3주의 시간을 쓸데 없이 허비한 꼴이 되었다. ‘사전 조사해서 상황을 파악해달라’ 는 초창기 요청에는 한 두 모듈만 피드백을 주었고, 그래서  ’모듈별로 실무자들을 한 명씩만 배정해달라’는 몇 차례의 요청 역시 묵살되었던 케이스였다.

아키텍트 시절 초기에는 의욕을 불태워봤지만, 나의 개선 요구들이 매번 거절당하면서 점차 회의를 느끼게 되었다.

다시 돌아가..

이런 현상의 더 근본적인 원인은 지도층의 마인드 때문이 아닐까 싶다. 리더가 아닌 매니저(관리자) 중심[2]의 팀이라, 중장기 비전을 위한 팀원들의 역량 향상보다는 눈앞의 단기 목표 달성을 최우선시한다. 심지어 팀원 역량 개발은 관리자의 롤에서 배제시키기도 한다. 같은 맥락에서, 소수의 핵심 멤버가 의사를 결정하고 그 외의 다수는 기계적으로 구현만 하면 된다고 얘기하는 것도 가끔 들을 수 있었다.

물론 윗사람들 모두가 이렇지는 않다. 어쩌면 대부분은 단지 정신없는 일정 압박에 어쩔 수 없이 끌려가고 있을 수도 있다. 하지만 일부는 분명 위와 같이 생각하고 있고, 그들의 힘이 강하게 작용하고 있다.

100% 틀렸다고 얘기하는 것도 역시 아니다. 상황에 따라선 이런 어프로치가 도움이 될 때도 있다. 단발성 프로젝트라던가, 초단기로 1차 결과물을 내놓아야 한다던가, 너무나도 자기 주장이 강한 독불장군들이 모인 팀이라던가, 명확한 스팩/설계의 외주 과제라던가, 누구나 인정하는 천제 아키텍트가 이끌고 있다던가 등 다양한 상황들이 떠오른다. 심지어 커뮤니케이션만 잘 이루어진다면 보통의 팀에서도 크게 문제될 것이 없다. 하지만 어설프게 머리를 정하고 권한을 주기에 앞서 팀의 모습을 세심히 살펴보도록 하자.

그리고 앞으로 몇 년 몇 십년을 이 분야에 종사해야 할 지 모르는데 나의 미래에는 아무런 관심도 없는 상사를 만났다고 생각해보자. 변화의 여지가 보이지 않는다면, 오래 함께하고픈 타입은 아닐 것이다. 소프트웨어는 사람이 머리로 만드는 것임을 잊지 않기를 바란다. 지금의 아주 일부만이라도 미래를 위해 투자하자. 팀원을 키우라는 이야기다.

p.s. 이 주제는 정말 오랫동안 수정에 수정을 거듭했다. 아무리 고치고 고쳐봐도 내가 진정 하고픈 말을 정확하고 효과적으로 표현하지 못하겠다. 여전히 맘에 들지 않는 상태이지만.. 언제까지고 고치고만 있는 것도 지겨워서 포스팅한다.


References

  1. [나쁜 팀 문화] 시리즈 (wegra.org)
  2. 리더 vs. 매니저 (wegra.org)
Dependency Injection
Dec 7th, 2009 by Wegra Lee

Spring Framework 관련 한글책[1]을 보다가 Dependency Injection(DI)의 번역에 대한 역자주에서 생각해볼 만한 것이 있어 보충 설명 겸, 잠시 끄적여본다. DI의 개념이 아직 모호한 사람이라면 이 글을 먼저 읽어본 후 참조한 wikipedia[2]의 글을 보면 전체적인 윤곽을 잡는데 도움이 될 것이다.

역자주: 종속객체 주입, 즉 DI(dependency injection)는 스프링에서 가장 기본이 되면서도 매우 중요한 의미를 갖는다. 기존에 가장 많이 쓰이던 번역은 ‘의존성 주입’이었다. 그러나 역자가 보기에 이 번역은 ‘없는 의존성을 만들어 주입한다’는 오해를 일으키고, 이 때문에 DI의 이해가 어려웠다고 생각한다. DI는 없는 의존성을 주입하는 것이 아니라 의존성은 이미 존재하되, 실제 객체가 필요로 하는 종속객체를 주입하는 것을 의미한다. 따라서 ‘의존성 주입’보다 뜻이 명확하도록 ‘종속객체 주입’이라고 번역했다. .. (후략)

일단.. 이 역자가 잘 봤다고 말하고 싶다. DI에서 dependency를 의존성으로 번역한 것은 확실히 잘못된 것이다. UML, OOAD 등 지금까지 IT 기술 관련 대부분의 상황에서는 모듈 간 관계를 명시하기 위해 dependency라는 용어를 사용해왔기 때문에 여기서도 습관적으로 의존성이라는 단어를 선택했으리라 생각한다. 하지만 단어는 문맥에 따라 여러 가지 의미를 가지고 있음을 잊어서는 안 된다.

DI를 직역하더라도 의존성 주입은 아니다. 오히려 위 역자가 사용한 종속객체 주입이 더욱 적절하다. 약간 아쉬운 게 있다면 DI는 object 세상에 국한되지 않기 때문에 ‘객체’라는 말은 여전히 논란의 소지가 있다는 점 정도. 범위를 제한하지 않으려면 module 정도의 추상적인 용어를 사용할 수 있겠지만, object라는 용어도 꼭 OOP에서 말하는 object로 제한되는 것은 아니니 상관없을 듯하다.

그럼 다시.. 왜 직역을 했는데도 의존성은 틀린 것일까? 이는 DI 패턴의 구조[2]를 살펴보면 쉽게 이해가 된다. DI 패턴은 세 개의 요소로 구성된다. 적절한 한글 대용어를 찾기 어려워 대강 번역해보면 이렇다.

이 패턴은 최소 3개의 구성 요소로 이루어진다: dependent와 그의 dependencies, 그리고 injector (혹은 provider, container). Dependent 는 컴퓨터 프로그램에서 작업(task)을 수행해야 할 소비자(consumer)다. 작업 완료를 위해선 특정 부작업들(sub-tasks)을 수행하는 다양한 서비스(dependencies)들을 활용해야 한다. 마지막으로 Injector는 dependent와 그에 필요한 dependency들을 조합해서 작업을 수행할 준비를 갖추는 컴포넌트로써, 이 객체들의 전반적인 라이프 사이클을 관리하기도 한다.

위 설명에서와 같이 DI의 dependency는 객체 간의 관계 속성을 의미하는 것이 아니라 서비스를 제공해주는 ‘실체’를 가리킨다. 그 실체를 (의미 그대로) dependent에서 주입시켜주는 것이다. 위 역자의 용어에 맞추어보면 대략 이렇게 되지 않을까 싶다.

  • Dependent: 의존객체
  • Dependency: 종속객체
  • Injector: 주입자
  • Dependency Injection: 종속객체 주입

이 용어들을 적용해 다시 번역해보면 아래처럼 되겠다. 깔끔하려나.. ^^

이 패턴은 최소 3개의 구성 요소로 이루어진다: 의존 객체(dependent)와 그의 종속 객체들(dependencies), 그리고 주입자(injector, provider, or container). 의존 객체는 컴퓨터 프로그램에서 작업을 수행해야 할 소비자다. 작업 완료를 위해선 특정 부작업들을 수행하는 다양한 서비스(종속객체)들을 활용해야 한다. 마지막으로 주입자는 의존객체와 그에 필요한 종속객체들을 조합해서 작업을 수행할 준비를 갖추는 컴포넌트로써, 이 객체들의 전반적인 라이프 사이클을 관리하기도 한다.


References

  1. Spring in Action SE (Craig Walls / 장시형, 전지훈 / Manning)
  2. Dependency injection (wikipedia)
중복 제거: Master Method
Nov 29th, 2009 by Wegra Lee

이번엔 널리 알려져서 누구나 다 알고 있을법한 (하지만 의외로 안지키는 사람도 많은) 방법을 소개한다.

Problems & Constraints

  1. 메서드를 오버로딩 해야한다.
  2. 입력 인자의 타입은 동일하며 그 유무만이 다르다.
    e.g.> doSomething(a, b) and doSomething(a, b, c)

Solution

가장 많은 인자를 받는 메서드를 Master Method 로 정하고, 다른 메서드에서는 부족한 인자를 default 값으로 채워 Master Method 를 호출한다. 생성자(constructor) 구현 시에도 동일한 규칙이 적용된다.

Examples

int read(byte[] buf)
{  .. // tens of lines
}

int read(byte[] buf,  int offset, int length)
{  .. // tens of lines
}

위에서 첫 번째 메서드는 두 번째 메서드의 특수한 형태로, offset 을 0으로, length 를 buf 의 size 로 해서 호출한 것과 완벽히 동일하게 동작한다. 따라서 첫 번빼 메서드는 아래와 같이 구현할 수 있다.

int read(byte[] buf)
{ return read(buf, 0, buf.size); // redirect to Master Method with appropriate parameters
}

Notes

Master Method 는 코드 중복 최소화 외에도, 오버로딩된 메서드들을 가지고 있는 클래스를 상속할 때 개발자 실수를 최소화 시킬 수 있다는 장점도 있다. 예를 들어, 오버로딩된 메서드 a, b, c 를 정의하고 있는 클래스 A 가 있다. 이 때 이를 상속한 클래스 B 에서 기능을 약간 변경하고자 할 때, Master Method 가 없다면 메서드 a’, b’, c’ 를 잊지 말고 모두 정의해줘야 한다. 실수로 무엇 하나를 빼먹었다면 의도치 않은 동작을 하게될 것이고, 그 원인을 찾기는 쉽지 않을 것이다. 하지만 만약 a 가 Master Method 였다면, a’ 만 재정의하면 문제를 미연에 예방할 수 있다. 따라서 Master Method 를 정하고, 어느 메서드가 Master 인지 명시하는 것이 좋다.

또, C++ 의 경우 Google 은 default parameter 를 흉내낼 목적으로는 사용하지 말기를 권고한다[4]. 그 단점을 아래와 같이 기술하고 있으니 참조하기 바란다.

One reason to minimize function overloading is that overloading can make it hard to tell which function is being called at a particular call site. Another one is that most people are confused by the semantics of inheritance if a deriving class overrides only some of the variants of a function. Moreover, reading client code of a library may become unnecessarily hard because of all the reasons against default function parameters.

If you want to overload a function, consider qualifying the name with some information about the arguments, e.g., AppendString(), AppendInt() rather than just Append().


References

  1. 권장 리팩터링 순서Recommended Sequence for Refactoring (wegra.org)
  2. 중복 제거: God Method (wegra.org)
  3. 중복 제거: Convert & Redirect (wegra.org)
  4. Google C++ Style Guide : Function Overloading (Google)
중복 제거: Convert & Redirect
Nov 26th, 2009 by Wegra Lee

계속해서 Recommended Sequence for Refactoring[1] 의 가장 첫 단계인 Remove duplications 에 적용할 수 있는 리펙토링 기법 하나를 더 소개한다. 먼저 소개한 God Method[2] 와는 얼핏 유사한 상황 같지만 분명한 차이가 있다.

Problems & Constraints

  1. 복수의 동일 목적 함수가 입력 인자의 타입만이 다르다.
  2. 입력 인자들 사이에선 서로 타입 변환이 가능하다. 즉 값(value)은 같으나 표현 형태만 다르다.

Solution

하나의 마스터 함수만 구현은 유지하고, 다른 함수들은 받은 입력의 타입만 변화시켜 마스터 메소드를 호출한다.

Examples

다음의 두 함수는 다른 입력을 받지만 목적은 같다.

toDate(long  timestamp)
{  .. // tens of lines
}

toDate(DateTime dateTime)
{  .. // tens of lines
}

long 타입의 timestamp 를 DateTime 으로 변환시켜 (convert) Date 용 함수를 호출한다.

toDate(long timestamp)
{  DateTime dateTime = new DateTime(timestamp); // convert
return ConvertDateTimeToServerDate(dateTime); // then, redirect
// no more duplicated lines
}

toDate(DateTime dateTime) // master method
{  .. // tens of lines (unchanged)
}


References

  1. 리팩터링 권장 순서 (wegra.org)
  2. 중복 제거: God Method (wegra.org)
중복 제거: God Method
Nov 25th, 2009 by Wegra Lee
1.1. Problems & Constraints복수의 유사 목적 함수가 존재하고 구현 로직이 거의 동일하다. 극히 일부만 다른 코드가 여러 함수에 중복되어 있어, 향후 로직/결함 수정 시 human error 를 유발시키기 쉽다.입력 인자 중 일부가 서로 베타적이어서 하나로 합쳐서 제공하거나, 하나의 함수에서 다른 함수를 직접적으로 호출할 수 없다.Public API 가 이미 고정되어 있어, 서로 다른 인자들을 묶는 공통 타입을 만들고 함수들을 하나로 통합시키는 방법을 사용할 수 없다.혹은, 공통 타입이 존재하나 그 중 일부에 대해선 아무런 지원 계획이 없어, 이를 명확히 알리기 위해 복수의 독립적인 API 를 제공하고 싶다.1.2. Solution하나의 God Method(모든 상황에 필요한 인자의 합집합 받으며, 입력 조합에 반응하여 동작함)을 ‘내부’에 두고, 공개 메서드에서는 인자를 적절히 조합하여 이 God Method 를 호출한다.1.3. CautionsGod Method 는 하나의 기능에만 특화된 메서드에 비해 로직이 복잡하므로, 일반적인 경우에는 지양해야할 어프로치다.’중복량 vs. 복잡도 증가’ 를 잘 판단하여 적용 여부를 결정해야 하며, 적용하더라도 내부(private) 메서드에 한정 것이 좋다.1.4. Example아래의 세 함수들은 입력 인자 중 하나만이 다르며, 구현에서도 총 70라인 중 단 한 라인(CreateHttpRequestN 호출부)만이 다르다.DoSomething(..){       ..requestString = CreateRequestString(.., null, null,  ..);..}DoSomething(Circle circle, ..){       ..requestString = CreateRequestString(.., null, circle,  ..);..}DoSomething(Rectangle rectangle, ..){       ..requestString = CreateRequestString(.., rectangle, null,  ..);..}CreateHttpRequestN 함수에 필요한 모든 인자를 받아들이니 God Method 를 만들어 구현을 하나로 모은다.DoSomething_God // 새로 추가된 God Method(       Rectangle rectangle, Circle circle, ..){       ..requestString = CreateRequestString(.., rectangle, circle,  ..); // 받은 인자를 bypass..}DoSomething(..){       // 전후 중복 라인 사라짐return DoSomething_God(null, null,  ..); // God Method 호출}DoSomething(Circle circle, ..) const{return DoSomething_God(null, circle,  ..);}DoSomething(Rectangle rectangle, ..) const{return DoSomething_God(rectangle, null,  ..);}God Method 를 자세히 보면, public API 와 달리, Rectangle 와 Circle 인자를 포인터로 받고 있다. 이는 Rectangle, Circle 중 아무것도 사용하지 않는 메서드를 포용하기 위해서이다.
Problems & Constraints
복수의 유사 목적 함수가 존재하고 구현 로직이 거의 동일하다. 극히 일부만 다른 코드가 여러 함수에 중복되어 있어, 향후 로직/결함 수정 시 human error 를 유발시키기 쉽다.
입력 인자 중 일부가 서로 베타적이어서 하나로 합쳐서 제공하거나, 하나의 함수에서 다른 함수를 직접적으로 호출할 수 없다.
Public API 가 이미 고정되어 있어, 서로 다른 인자들을 묶는 공통 타입을 만들고 함수들을 하나로 통합시키는 방법을 사용할 수 없다.
혹은, 공통 타입이 존재하나 그 중 일부에 대해선 아무런 지원 계획이 없어, 이를 명확히 알리기 위해 복수의 독립적인 API 를 제공하고 싶다.
Solution
하나의 God Method(모든 상황에 필요한 인자의 합집합 받으며, 입력 조합에 반응하여 동작함)을 ‘내부’에 두고, 공개 메서드에서는 인자를 적절히 조합하여 이 God Method 를 호출한다.
Cautions
God Method 는 하나의 기능에만 특화된 메서드에 비해 로직이 복잡하므로, 일반적인 경우에는 지양해야할 어프로치다.
‘중복량 vs. 복잡도 증가’ 를 잘 판단하여 적용 여부를 결정해야 하며, 적용하더라도 내부(private) 메서드에 한정 짓는 것이 좋다.
Example
아래의 세 함수들은 입력 인자 중 하나만이 다르며, 구현에서도 총 70라인 중 단 한 라인(CreateHttpRequestN 호출부)만이 다르다.
DoSomething(..)
{       ..
requestString = CreateRequestString(.., null, null,  ..);
..
}
DoSomething(Circle circle, ..)
{       ..
requestString = CreateRequestString(.., null, circle,  ..);
..
}
DoSomething(Rectangle rectangle, ..)
{       ..
requestString = CreateRequestString(.., rectangle, null,  ..);
..
}
CreateHttpRequestN 함수에 필요한 모든 인자를 받아들이니 God Method 를 만들어 구현을 하나로 모은다.
DoSomething_God // 새로 추가된 God Method
(       Rectangle rectangle, Circle circle, ..)
{       ..
requestString = CreateRequestString(.., rectangle, circle,  ..); // 받은 인자를 bypass
..
}
DoSomething(..)
{       // 전후 중복 라인 사라짐
return DoSomething_God(null, null,  ..); // God Method 호출
}
DoSomething(Circle circle, ..) const
{
return DoSomething_God(null, circle,  ..);
}
DoSomething(Rectangle rectangle, ..) const
{
return DoSomething_God(rectangle, null,  ..);
}
God Method 를 자세히 보면, public API 와 달리, Rectangle 와 Circle 인자를 포인터로 받고 있다. 이는 Rectangle, Circle 중 아무것도 사용하지 않는 메서드를 포용하기 위해서이다.

지난달 포스팅한 Recommended Sequence for Refactoring[1] 의 가장 첫 단계인 Remove duplications 에 적용할 수 있는 리펙토링 기법이다.

곧 한 두개의 기법이 추가로 뒤따를 예정이다.

Problems & Constraints

  1. 복수의 유사 목적 함수가 존재하고 구현 로직이 거의 동일하다. 극히 일부만 다른 코드가 여러 함수에 중복되어 있어, 향후 로직/결함 수정 시 human error 를 유발시키기 쉽다.
  2. 입력 인자 중 일부가 서로 베타적이어서 하나로 합쳐서 제공하거나, 하나의 함수에서 다른 함수를 직접적으로 호출할 수 없다.
  3. Public API 가 이미 고정되어 있어, 서로 다른 인자들을 묶는 공통 타입을 만들고 함수들을 하나로 통합시키는 방법을 사용할 수 없다.
  4. 혹은, 공통 타입이 존재하나 그 중 일부에 대해선 아무런 지원 계획이 없어, 이를 명확히 알리기 위해 복수의 독립적인 API 를 제공하고 싶다.

Solution

하나의 God Method(모든 상황에 필요한 인자의 합집합 받으며, 입력 조합에 반응하여 동작함)을 ‘내부’에 두고, 공개 메서드에서는 인자를 적절히 조합하여 이 God Method 를 호출한다.

Cautions

God Method 는 하나의 기능에만 특화된 메서드에 비해 로직이 복잡하므로, 일반적인 경우에는 지양해야할 어프로치다.

‘중복량 vs. 복잡도 증가’ 를 잘 판단하여 적용 여부를 결정해야 하며, 적용하더라도 내부(private) 메서드에 한정 짓는 것이 좋다.

Examples

아래의 세 함수들은 입력 인자 중 하나만이 다르며, 그에 따라 달라지는 코드 라인수는 전체 코드 구현에서 비중이 작다. 혹은 중복 코드의 절대량이 많다.

public Object DoSomething(.. /* series of params */)
{  .. // tens of lines
MakeSomething(.., null, null,  ..);
.. // tens of lines
}

public Object DoSomething(Apple apple, .. /* series of params */)
{  .. // tens of lines
MakeSomething(.., apple, null,  ..);
.. // tens of lines
}

public Object DoSomething(Orange orange, .. /* series of params */)
{   .. // tens of lines
MakeSomething(.., null, orange,  ..);
.. // tens of lines
}

DoSomething 함수에 필요한 모든 인자를 받아들이니 God Method (DoAnything) 를 만들어 구현을 하나로 모은다.

private Object DoAnything(Apple apple, Orange orange, ..) // newly introduced god method
{  .. // tens of lines
MakeSomething(.., apple, orange, ..); // bypass the given params
.. // tens of lines
}

public Object DoSomething(..)
{  // no more duplicated lines
return DoAnything(null, null,  ..); // invoke the god method
}

public Object DoSomething(Apple apple, ..)
{
return DoAnything(apple, null,  ..);
}

public Object DoSomething(Orange orange, ..)
{
return DoAnything(null, orange,  ..);
}


References

  1. 권장 리팩터링 순서 (wegra.org)
  2. 중복 제거: Convert & Redirect (wegra.org)
[나쁜 팀 문화] 유언이 되어 버리는 Lessons Learned
Nov 20th, 2009 by Wegra Lee

Lessons Learned

그간 이 팀 저 팀에서 발간(?)한 수많은 소위 Lessons Learned 라는 것을 봐온바 있다. 과제를 진행하면서 느끼고 배운 것을 정리하여 다음 과제 진행시 참고하거나 진행중인 다른 과제에서 참고할 만한 좋은 정보들이 적혀 있다. 자신이 걸어온 길을 되돌아보는 것은 좋은 경험이고, 그 결과를 정리해 놓는 것 역시 권장할 만한 일이다.

문제는 그 시점이 항상 과제가 뿌러지는 등 완전히 종료되는 시점에 일어난다는 것이다. 많은 경우 팀 자체가 뿔뿔이 흩어지거나 축소된다. 다음에 진행할 과제도 이전 과제와 성격이 달라지기 쉽다. 얻은 것이 있어도 그것을 적용해볼 상황이 못되는 것이다.

과연 이전 과제에서 깨우친 것들이 새로운 과제 새로운 팀에서 제대로 먹혀들 수 있을까? 시행착오를 거쳐보지 않은 새 팀 멤버들이 가슴 깊이 공감하고 따라줄 것인가? 배경이 다른 사람들에게도 똑같은 처방이 여전히 유효할까? 내가 얻은 결론은 정말 더 나은 개선책인가? 아니면 단지 ‘다음엔 이렇게 해봐야지’ 정도인가? 검증되지 않았고 경험해보지도 못한 방법을 새 팀 새 과제에서 시험해볼 용기는 있는가?

위와 같은 고민들도 어느 정도 영향력이 있는 윗사람들에게 해당하는 것이지, 중간 이하의 힘없는 관리자나 실무자들은 새로운 리더가 시키는데로 그냥 따를 수 밖에 없는 것이 또 현실이다.

결국 이런 식의 Lessons Learned 는 읽을 거리나 유언장 이상의 실질적 가치를 갖지 못한다.

Retrospective

Retrospective 는 Lessons Learned 의 Agile 식 표현 정도로 생각하면 좋을 것이다. 하지만 많은 차이를 안고 있음을 잊어서는 안된다. Agile 이 강조하는 iterative, responsive, incremental 같은 속성을 Lessons Learned 에 부여해보자. 짧은 간격으로 주기적으로 회고하며(iterative), 현 시점에 필요한 이슈에 대한 개선안 만들어 다음 개발 주기에 적용해보아 좋은 것을 취하고 잘못된 것은 버리거나 다른 안을 찾아본다(responsive). 팀은 과제가 진행될 수록 조금씩 조금씩 더 나은 방향으로 발전해 간다(incremental).

이미 끝나버린 과거에 대한 무책임한 회상록이 아닌, 지금 살아서 꿈틀대는 과제에 대한 진중한 고민을 경험해볼 수 있다. 바로 그 시점에 팀원들 스스로가 개선이 필요하다고 느끼는 이슈들이 다뤄지고 합의된 개선책을 시행한다는 점에서 참여율이 높아지고, 높은 성공 확률은 덤으로 얻게된다. 혹 실패한다면 바로 다음 회고 때 그 원인과 또 다른 개선안을 찾게될 것이다. 시행착오를 통해 더 많은 경험을 얻게되고, 미처 고려하지 못했던 요소들이 있음을 깨닫게 된다. 넓고 세심해진 시야는 다른 문제에 직면했을 때도 좀 더 빠르게 더 효과적인 개선안을 찾게 도와준다. 반면 Lessons Learned 방식에서는 실패한 처음 안만이 남겨질 가능성이 농후하다. 회고가 거듭될 수록 팀의 생산성이 향상됨을 느낄 수 있을 것이다.

Barriers and Ways to Overcome

다 좋고 완벽해 보이는 회고도 무턱대고 적용하려 하면 말처럼 쉽지 않음을 깨닫게 된다. 내가 지금까지 발견한 중요 요인은 3 가지 이다.

첫째, 일정에 쫓겨서 회고에 할애할 시간적 여유가 없다.

이런 답변을 많이 들었는데, 시간적 여유가 없는 것이 아니라 회고의 효과에 대한 믿음이 부족하거나 새로운 것을 시도하는 것을 꺼리는 보수적 마인드의 팀일 가능성이 높다. 이런 경우엔 분위기 조성을 위한 물밑 작업부터 시작해야 한다. 말이 통하고 깨어있는 사람들이라면 직접적인 대화로 돌파할 수 있겠다. 뜻이 맞는 팀원이 소수라도 존재한다면 솔선수범해서 더 나아지는 모습을 직접 보여줄 수도 있을 것이다. 상황이 다양한 만큼 방법도 다양할 터이니 숙고해서 도전해보자. 단, 상대를 공격하는 것은 확실한 실패 방법이니 절대 피하길 바란다.

참고로 회고에 필요한 시간은 보통 2~4주에 1시간 정도면 충분하다. 물론 처음 시작할 때는 좀 더 많은 시간이 필요할 것이지만 두세번만 해보면 익숙해지고 소요 시간도 줄어든다.

둘째, 팀원들의 참여가 저조하다.

기껏 회고를 해보자는 허락을 받았는데, 정작 모인 사람들이 꿀먹은 벙어리 마냥 침묵으로 일관한다. 회고를 처음 시도하는 조직에서는 대부분 겪게 되는 인기 코스일 것이다. 그들이 침묵하는데에는 여러 가지 이유가 있을 것이다. 무슨 얘길 하면 되는거지? 예제 같은 거 없나? 얘기한다고 정말 개선해 주나? 말 꺼내면 나보고 꺼낸 사람이 하라고 하겠지? 불평하면 괜시리 밉보일 거야.. 등등.

이런 우려를 해소하고 적극적인 참여를 유도하려면 맛을 보여줘야 한다. 사소한 것부터라도 정말 개선되는 모습을 보여주는 것이다. 첫 회의에서 아무런 아이디어가 없을 것을 대비해 문제점과 개선안까지 몇 가지를 준비해둔다. 뜻이 맞는 사람이 한 명이라도 있다면 역할극을 해보는 것도 좋다. 혼자 북치고 장구치는 것보다 훨씬 효과적으로 분위기를 이끌어 갈 수 있을 것이다. 그리고 개선안이 없더라도 불편한 사항을 자유롭게 이야기하도록 하고, 그에 대해 반박을 하거나 방어적인 모습을 보여서는 안된다. Brain storming 방식으로  문제들을 나열하고 중요도와 시급성, 파급 효과 등을 생각해 이슈를 선별해서 개선안을 모색해보자.

셋째, 팀에 부여된 권한이 너무 작다.

열심히 분위기 잡고 멋진 개선안들도 마련했다. 그런데 그 개선안들이 우리힘으로 할 수 있는 일들이 아니라면? 운영부서는 돈 들어가는 일은 절대 허락을 안해주고, 보안 부서는 개발자 편의성은 절대악으로 규정짓고, 프로세스 팀은 표준 프로세스에 어긋난 어떠한 시도도 용납하지 않고, 기획/마케팅 부서는 자신들이 바라는 모든 기능을 역시 자신들이 정한 시한까지 완수해야 한다고 못박고, 책상 앞에 앉아 있는 시간과 개발진도는 정비례한다고 철석같이 믿고 있는 임원이 군림하고 있다면?

회고는 불평불만을 토로하고 남들 뒷담화가 남무하는 장이 되던가, 소꼽장난하듯 사소한 문제들만 다뤄지는 비중없는 시간으로 전락할 것이다. 물론 작은 개선들이 차곡히 쌓여 큰 도움을 줄 수도 있을 것이고, 공동의 적을 향한 불평불만과 뒷담화는 팀원들을 단합시켜주는 효과도 있기는 하다. ^^

모든 것을 통틀어 가장 큰 장벽으로, 딱히 뾰족한 해결책도 없다. 때마침 임원 인사가 단행되어 깨어 있는 사람으로 교체되던가, 무슨 바람이 불어서 아주 권위 있는 컨설턴트의 도움을 받을 수 있다면 큰 힘이 될 것이다. 하지만 모두 다 운에 기대는 것이고.. 리더나 팀원들이 몸소 나서서 설득하고 싸우는 적극적 쟁취 방법도 물론 있다. 하지만 생태계에서 식물 급으로 취급되는 힘없는 개발팀이 얻어낼 수 있는 것은 많지 않을 것이다.


References

  1. Retrospectives by RTC dev. teams
  2. Agile Retrospective – Lessons Learned
[나쁜 팀 문화] 점진적 지연
Nov 19th, 2009 by Wegra Lee

점진적 지연이란 아주 짧은 간격으로 릴리스 날짜를 조금씩 미루는 행태를 뜻한다. 일단 릴리스를 시도해보고, 안되면 조금  (보통 1~3일) 미룬 후 다시 시도한다. 역시 잘 안되면 또 다시 미룬 후 시도한다. 이렇게 릴리스가 될 때까지 무한 반복하는 릴리스 관리 방식이다.

사례

내가 경험해본 최악의 상황은 이러했다. 원래의 릴리스 목표일은 금요일이다. 금요일이 되어서 시도를 해보았으나, 도저히 릴리스할 만큼의 품질이 나오지 않는다. 그래서 월요일로 목표를 바꾼다. 월요일에도 역시 원하는 품질에 도달하지 못했다. 다음날 다시 한 번 시도해보기로 한다. 마찬가지로 실패했다. 이와 늦은거 금요일로 다시 미룬다. 금요일도 여전히 실패.. 토요일.. 월요일.. 수요일.. 금요일.. 월요일.. 화요일.. 토요일.. ..

이런 식으로 가장 길게는 6주간 계속 릴리스만 한 경우도 있었다. 릴리스가 이렇게 한달 가량 지연되면 당연히 다음 릴리스에 일정에 지장을 주게 된다. 새로운 기능을 추가하는데 필요한 최소 시간이 있으므로 차례차례로 전체 일정이 연기된다. 하지만 다음 릴리스 시점에 와서도 크게 다르지 않다. 역시 또 몇 주의 지연이 발생한다. 그래서 ‘릴리스가 끝나면 (다음) 릴리스 일정이 바뀐다’, ‘릴리스가 되는 날이 릴리스 날이다 (역: 릴리스 날에 릴리스를 한다)’ 라는 말까지 하게 되었다.

위의 경우는 continuous integration (CI) 도 도입되지 않은 열악한 환경에서의 상황이었고(도입을 요구했지만 관심 갖는 사람이 없었음), 그 후 CI 가 도입되면서 상황은 많이 호전되었다. 하지만 여전히 제 때 릴리스 되는 경우는 전무했고.. 심한 경우 3주가 밀리기도 했다. 금요일 릴리스를 (주말을 열심히 일해서) 그 다음주 월요일 저녁에 할 수 있다면 대단히 이례적인 성공이 케이스가 될 정도였다.

악효과

Incremental Delay 가 팀 문화처럼 정착되면서 다음과 같은 부작용들이 나타나기 시작했다.

팀원들은 윗선에서 정한 릴리스 날짜(deadline)에 큰 의미를 부여하지 않게 되었다. 한 차례도 성공적으로 릴리스한 경험이 없으며, 릴리스가 연기되어도 아무 일도 발생하지 않음이 학습되었기 때문이다. 꼭 지켜져야하는 중요한 릴리스라 강조해도 마음속으론 ‘어짜피 안될걸..’이란 생각을 자연스레 하게된다.

서브팀별 개발 일정이 꼬이기 시작한다. 팀별로 이터레이션(iteration) 주어진 일의 양이나 생산성, 예측 정확성 등의 차이가 생겨 발생하는 현상으로.. 일부 팀은 일정에 맞춰 일을 끝내놓고 다음 릴리스를 위해 할 일을 준비하고 있다. 하지만 곧 될 듯 말 듯 하면서 릴리스는 기약없이 지연된다. 처음엔 2~3일 후면 될 거라 기대하고 잠시 쉬던 것이 하루 이틀씩 밀리면서 결국 이 주 삼 주 동안 제대로 진행할 수 없게 된다. 이런 경험이 잦아지면서 일부에선 중간중간에 변경된 코드를 밀어 넣는다. 다음 릴리스를 위한 개발 코드 브랜치와 (지연중인) 릴리스 브랜치 간에 코드 격차가 심해져, 개발 브랜치에서 해결된 문제를 릴리스 브랜치에 반영하는데 한참을 씨름하기도 한다.

윗선도 계속되는 릴리스 관련회의로 시간을 낭비하기는 마찬가지다. 현황을 파악하고, 얼마나 더 지연시킬 지 정하고, 지연이 미치는 영향을 분석하고, 이왕 지연된 것 미리 구현된 간단한 기능을 포함시킬 지 논의하고.. 자료를 준비해 더 윗선이나 마케팅, 기획, 운영(예산) 팀 등과도 계속 조율을 해야 한다.
야근과 주말 근무가 늘어난다. 일주일 단위로만 지연시켜도 호흡 조절과 재충전이 가능하겠으나, 지연 단위가 보통 1~3일 정도이고, 특히나 금요일 일정은 반드시 토/일/월로 지연된다.

고찰

무엇하나 좋을 게 없는 이런 비합리적 문화가 왜 개선되지 않는 것일까?

이런 현상이 반복되고 있음은 윗선에서 과제가 어떻게 돌아가고 있는지 전혀 파악하지 못하고 있다는 확실한 반증이다. 릴리스를 하기 위해 해야할 일이 얼마만큼 남아 있는지 도통 감이 없다. 개발자들의 생산성과 문제 해결능력에 대한 어떠한 정량적/경험적 데이터도 갖춰져 있지 못하다. 개발자 각각이 지금 무슨 일들로 얼만큼의 로드가 걸려있는지도 잘 파악이 안되고 있다. 해결해야할 문제들이 얼마만큼의 노력이 들어가야 하는지도 역시 감이 없다. 아직 드러나지 않은 문제들은 또 얼마나 될 지 역시 예상하기 어렵다. 결국 위에서 할 수 있는 말은 보통 ‘언제까지 이 문제들을 해결해라’ 정도에서 그친다.

제품의 설계나 코드의 품질이 크게 떨어지거나, 다뤄야할 대상을 개발자들의 완벽하게 이해하지 못하고 있을 경우 사태는 더 심각해진다. 분석하고 수정하는데 필요한 시간을 예측하기 힘들고, 수정시 부작용(side-effect)이 따를 가능성이 높다. 동작 여부만이 아니라 코드의 품질을 챙기는 개발 문화를 조성해야 하고, 서로 간의 잦은 리뷰를 통해 다른 팀원들과 개발 패턴, 노하우 등을 공유해야 한다.

관련 개발팀간 커뮤니케이션 효율성도 큰 영향을 미친다. 심한 경우는 핵심 모듈을 전혀 다른 팀에서 관리하고 있고, 우리 과제를 지원하는 것은 그 팀의 최우선 역할이 아닐 때이다(실제로 자주 접해보았다). 유관 팀들이 지리적으로 멀리 떨어져 있고, 원격지에선 문제를 재현하기 어려울 때도 마찬가지.. 어떠한 경우건 커뮤니케이션 지연은 일의 진행 속도를 현저히 떨어뜨리는 주요 원인이 된다. 과제를 기획하고 팀을 조직할 때 그 과제와 팀이 적시에 충분한 지원을 받을 수 있도록 고려해야 한다. 필요하다면 조직 구성을 바꿔 같은 명령 계통 내로 흡수할 필요도 있다.

미흡한 인프라에 의한 불확실성도 크다. 기본적인 테스트 자동화 시스템도 갖추지 못해 매번 개발자들이 수동으로 테스트하는 것도 많이 보아왔다. 나름 도입한 continuous integration 이 기껏 빌드만 해주고 기본적인 unit test 도 수행하지 못한는 경우를 상상해보자. 내가 경험한 어떤 팀은 unit test 를 CI 에 통합해달라는 요청을 한 지 1년이 넘도록 반영하지 못했다. 다른 자동화는 물론 말할 것도 없었다. 과제 규모가 커질 수록 그에 걸맞게 인프라 효율성에도 투자를 해야한다. 발등에 떨어진 일정에만 쫒겨 제때 투자를 못하면 일을 해야할 정작 개발자들의 시간을 어먼 곳에 낭비하게 만든다.

개발 문화와 생산성 향상, 효율성, 팀웍에 대한 인식이 부족하다. 위에서 가장 중요시 하는 것은 언제 릴리스 되는가이다. 어떤 때는 그것 외에는 전혀 관심이 없는 것 같다는 인상을 받곤 한다. 일벌레인 사람들이 지배하는 조직에서 더 심한 경향이 있는 것 같다. 종종 일찍 퇴근하고 싶어하는 사람을 이해하지 못하는 듯한 발언을 듣기도 한다. 아무래도 사람은 자신의 기준에서 남을 이해하는 성향이 있으니, 어찌보면 당연한 것일 수 있다. 하지만 사람(뿐 아니라 모든 생물)은 기계와 달리 충분한 휴식 없이는 높은 집중력과 생산성을 유지할 수 없음을 주지시켜야 한다.

The Go Programming Language
Nov 12th, 2009 by Wegra Lee

Noop 에 이어 구글이 또 하나의 프로그래밍 언어를 소개했다. Go 라는 이름의 언어로, 벌써 제법 성숙한 상태라, 과거 Turbo C 나 초창기 UltraEdit 로 Java 코딩하던 추억을 떠올리면 충분히 직접 사용해볼 수 있는 수준이다.

Go 언어는 크게 두 가지 관점에서 아주 마음에 든다.

처음부터 다시 시작했다.

현재의 것에 계속해서 군더더기를  붙여 나가다 보면 점차 비대해져 관리를 어렵게 하고 혁신을 가로막는다. 때문에 종종 처읍부터 다시 시작하는 것이 필요하다.

번외로.. 구글이나 애플과 같은 회사가 이런 일을 참 잘하는데, 최근 40살 된 e-mail 을 대체하겠다며 큰 기대를 받고 있는 Google Wave 나 이미 성숙할 대로 성숙한 핸드폰의 개념을 바꿔버린 iPhone 같은 것이 좋은 예이다.

덕분에 더 이상 필요 없거나 폐해가 더 큰 기능들을 과감히 없애 날렵해짐과 동시에 병렬 프로그래밍과 같이 새시대에 필요한 기능들을 실장하고 있다.

C/C++ 를 경량화시켰다.

내가 가장 혐오하는 프로그래밍 언어인 C++. C++ 는 기능 확장이 아니라 축소를 먼저 해야 한다고 내가 몇 년 전부터 얘기하고 다녔는지 모른다. 물론 Go 가 C++ 의 직접적인 축소판은 아니지만, 같은 C 계열 (호환이 아니라 파생의 의미로) 언어로써 정말 최소한부터 다시 시작하는 언어이다. 홈페이지에서 언급한 몇몇 설계상의 결정 사항들은 정말 내 가슴을 후련하게 해준다. 포인터 연산, exception, assertion, 오버로딩 등에 대한 설명은 나의 그간 경험에 비춰 특히 공감이 된다. 수많은 개발자들.. 아무리 설명을 해줘도 계속 교체되고, 새로온 사람들에게 또 설명하고.. 그러다 지쳐 포기했던 기억이.. ^^

Go 가 잘 발전해서 C++ 의 영향력이 감소하는데 한 몫 해주길 바래본다.


References

  1. Official Site
  2. Tech Talk (YouTube Video, 1 hour)
  3. PDF for the above Tech Talk

잡설.

소프트웨어 플랫폼 경쟁에 있어 우리 나라 기업들은 도저히 상대가 안되는게.. 애플이나 구글같은 회사는 핵심 소프트웨어 개발 역량이 정말 대단하다. 우리처럼 API 겉만 감싸는 수준이 아니라, 필요하면 언어 자체를 수정하거나(최근 GCD 를 위해 C 언어를 확장함) 심지어 만들어버리기까지 한다(Go, Objective-C). 애플은 LLVM/CLang 이라는 GCC 대체 프로젝트를 진행해 큰 성취를 보이고 있고, 구글 가상 머신을 뜯어 고쳤다(Dalvik VM). 소프트웨어 설계 자체도 참 대단하다. 이미 25년의 OS 개발 경험을 축적하고 있는 애플의 OS X 는 전신인 NextStep 시절부터 소프트웨어 설계의 바이블 격인 Design Patterns 책 저술에 지대한 영향을 미쳤다. 현재의 플랫폼도 깊이 들여다보면 ‘우와!’ 하는 감탄사가 절로 나오는 멋진 구조를 어렵지 않게 찾아볼 수 있다.

핵심 기술이 없으면 오픈 소스/커뮤니티에라도 적극 참여해 그들의 노하우를 최대한 빨리 흡수하며 기반을 쌓는 것이 큰 도움이 된다. 모토롤라의 이번 Droid 출시가 좋은 예이다. 작년, 큰 위기를 느낀 모토롤라는 진행중이던 프로젝트들을 대다수 취소하고 회사 전체를 환골탈퇴하는 모험을 감행했다. 덕분에 신규 제품이 나오지 않아 시장 점유율을 급격하게 떨어졌지만, 구글과의 밀접한 협력으로 Android 2.0 폰을 타 기업들보다 몇 개월 앞서 출시할 수 있었다. 결과는 더 지켜봐야겠지만, 구글과의 돈독한 관계뿐 아니라, Android 시장에서 적어도 당분간은 타 업체들을 선도할 수 있는 충분한 노하우를 얻었을 것이다.

허나 안타깝게도 우리 나라 기업들은 이런 방향으로의 가시적인 움직임은 보이질 않는다. 티맥스 윈도우나 삼성 바다의 예에서 보듯이, 오히려 독자적인 플랫폼을 시도하고 있다. 아직은 실체가 드러나지 않아 확언할 수는 없지만, 중학생들에게 ACM 이나 IEEE 논문을 쓰라는 격으로 보인다. 유사하거나 오히려 더 참신한 아이디어가 나올 수는 있으나, 그 깊이와 완성도는 비교할 수 없을 것이다. 유수의 석학들과 경쟁해 진정 그들을 뛰어 넘고자 한다면, 조급함을 억누르고 차근히 기초를 닦는 인내의 기간이 필요하다.

»  Substance: WordPress   »  Style: Ahren Ahimsa