Generalization의 오용 – 역사에 남을 인물이 아니면 강제하지 않는건 어떤가요?

8 Comments

무례한 제목입니다.

하지만 “Generalization”으로 이한 지난 몇 년간의 피해가 있어 몇 글자 적어 봅니다.

아래 그림은 “Design”이라는 이름하에 꿈과 희망을 주는 그림입니다.

승객용 열차 (Passenger) 클래스와 화물 열차 (Goods) 클래스에서 공통된 부분을 뽑아서 기타 (Train) 클래스를 만들고 상속 관계를 아래 그림처럼 구성한 “Generalization”을 하라는 것이겠죠?

하지만, 현실에서는 애석한 일이 발생합니다. 특히 “허공 답보”와 “사상누각”을 일삼는 무리에 의해서.

무지한 저에게 저 그림이 말하는 것은

“화물열차”와 “승객열차”를 별개로 개발하지 말고 공통적인 속성을 뽑아서 “기차”를 만든 후 상위 클래스의 속성 (attribute)이나 행동 (method)을 하위 클래스에서 그대로 이용하든지 응용해서 (overriding) 만들어보자

를 의미하지,

“화물열차”도 “기차”이고 “승객열차”도 “기차”이니 둘 다를 포함할 수 있는 “슈퍼 기차”를 만들어서 조금만 변경하면 개개의 열차가 될 수 있게 만들어보자

는 아닙니다.

“누가 두 번째와 같은 생각을 하냐구요?”

의외로 그런 것을 강요하는 사람들을 주변에도 많았고 이런 문제는 Effective STL과 같은 책에서도 다룰 만큼 전 세계적으로도 많은 것 같습니다.

이러한 현상은 자신의 총명함을 – 어느 정도 총명한 – 믿고 한눈에 상황을 보고 기민하게 판단하는 사람일수록 쉽게 빠질 수 있는 오류인 것 같습니다.

특히 다른 사람 – 총명함을 떠나 열심히 분석하고 생각한 – 을 존중하고 배려하지 못할수록 더 심각합니다.

또는 작업을 최종 마무리를 위해 구석구석 까지 해보진 않았거나 글자로만 모든 것을 익힌 사람일수록 쉽게 빠지는 것 같습니다.

“Generalization”을 부모 클래스가 아닌 슈퍼 클래스로 보는 문제는 비단 클래스 문제에만 국한되지는 않는 것 같습니다. 제품 (product) 자체에까지 마수가 펼쳐지는 경우가 많습니다. 

제 짧은 경험에 비추어 몇 가지 사례를 이야기해보겠습니다.

Worst cases

■ 만능 테스트 도구

제가 주로하는 일이 테스트 도구를 만드는 것이라 첫 번째 사례로 꼽아 봤습니다.

“No Silver Bullet” 이 말은 소프트웨어 공학과 여러 도메인에서 자주 거론되는 말일 것입니다. 말 그대로 모든 것을 잠재워줄 한 가지 해결책은 없다는 것이겠죠.

테스트 분야에서도 마찬가지일 것입니다. 테스트 레벨 (unit, integration, system)이 틀리고 검정하려는 목적이 틀린 데 (positive/negativae, functionality/non-functionality, static/dynamic, etc), 단지 “테스트 도구”라는 큰 타이틀을 앞세워 만능 테스트 도구를 찾거나 만들 것을 요구하는 경우가 종종 있는 것 같습니다.

특히, 수백 수천에 달하는 상용 도구를 대하거나 1년 이상 많은 사람이 투입된 도구 제작 프로젝트를 대할 때 높은 사람들의 입에서는 너무나도 거리낌 없이 General 한 만능 도구를 거론합니다.

■ 이 세상 모든 것에 Generic한 컨테이너 (container)

Effective STL의 항목 2이에서 아주 자세히 다루고 있어서 간단히 이야기하면, 머리에 먹물이 들었다는 사람일수록 여러 종류의 “시퀀스 컨테이너”와 “연관 컨테이너”를 일반화 시킬 수 있는 Generic 한 슈퍼 컨테이너를 만들려고 시도합니다.

이 시도는 아주 프로그램적으로 다가가 다양한 STL 컨테이너의 멤버 함수들을 포기해야 하는 문제도 발생하며 더욱이 상황에 맞게 잘 설계된 컨테이너의 특성들을 포기하는 “아무 쓸모도 없는” 컨테이너를 만들 수 밖에 없다는 결론에 도달합니다.

검색을 고려한 map이나 multimap이 vector나 list와 합쳐지면 무슨 이득 (부귀영화)이 있겠습니까?

※ 고객 데이터를 위한 클래스를 만들고 “고객 추가”를 위해 AddClient()를 만들고 그 내부에 실제 컨테이너의 기능을 넣는 “컨테이너 은닉화”를 이야기하자는 것은 아닙니다.

■ 너무나 많은 것을 고려해서 쓸모없어진 프로그램

주제가 비약되는 느낌도 들지만, 같은 문제 범위에서 다루어도 큰 문제는 없을 것 같습니다. 슈퍼 기차의 문제로 볼 수 있다고 생각합니다.

누구나 아는 “Simple is beautiful”이라는 만고불변의 진리도 이 사례 설명을 도와줄 것입니다.

명확한 요구 사항을 가지고도 팀을 이끌어서 소프트웨어를 만들기도 힘든 상황에서 미래의 기능이나 미래의 확장 또는 다른 도구임이 분명한 도구의 기능까지 고려한 프로그램을 만들려는 시도는 정말 쓸모없는 괴물을 만드는 일일 것입니다.

“그들은” 왜 간절히 “소박한 기능”을 원하는 고객을 생각하지 않을까요? 고객이 누구인지는 알까요?

■ 애플리케이션 (Application)을 라이브러리 (Library)처럼 사용하려는 그릇된 시도

삼겹살집으로 건축한 건물을 목욕탕으로 재 사용하기는 너무 막막한 일입니다.

요구사항이 바뀌면 애플리케이션이라는 것은 많은 수정을 요하고 때로는 새로 만드는 것이 좋을 것입니다.

하지만 이렇게 지극히 상식적인 것을 – 모르진 않을 것이지만 – 망각하고 애플리케이션을 재사용 또는 커스터마이징 (customizing)이라는 말을 오용해 마치 라이브러리 처럼 사용하려고 합니다.

이러한 강요는 순수하고 순진한 개발자들에게서 간결함과 최적화를 뺏어가는 것 같습니다.

제가 “역사에 남을 인물” 운운한 것은 최근 영화인 “어거스트 러쉬 (August Rush)”에 나오는 음악 천재 소년처럼 저의 이 모든 다소 주관적인 불평을 잠재워줄 만큼 천재적인 디자이너나 개발자도 있을 수 있기 때문입니다.

하지만, 과연 그런 사람이 얼마나 될까요?

오히려 천재에 가까운 사람들은 – 정말로 – 복잡하고 불필요한 많은 것을 고려하기보다는 간단하고 최적화된 것을 선택하는 것 같습니다.

그릇이야기

No Comments

그릇이야기

[테스트 단상] (거창하게) “패러다임이 변하고 있으니 테스트가 조금 더 각광받지 않을까요?

4 Comments

지극히 주관적인 의견이지만 전자 제품은 텔레비전 뿐인 산골에서의 망상 (illusion)은 아니라고 생각되어 몇 글자 적어 봅니다.

소프트웨어 테스트가 현실 (일정과 인력)을 만났을 때, 아마 가장 많이 연상되는 단어는 “트레이드 오프 (trade off)”일 것입니다.

- 출시가 얼마 남지 않았기 때문에
  (어느 정도의 결함을 안고서라도 “타임 투 마켓”이 더 우위에 있기 때문에)
- 개발 인력이 턱없이 부족하기 때문에

와 같은 이유로 테스트는 (어쩔 수 없이) 뒷전으로 밀려나는 경우가 많은 것 같습니다.
그렇지 않은 경우를 찾는 것이 더 힘들겠죠.

저의 제목과 이 포스트에서 말하고자하는 것은 무엇일까요?
그 것은 무엇이라고 정의할 수는 없지만 그 “변화”가 “테스트”에 유리하게 흘러간다는 것입니다.

기존에 테스트를 어렵게 만든 것 큰 이유 중의 하나는, 개발 자체에 소요되는 비용 (인력/시간)일 것입니다.

제가 패러다임 운운한 것은 이 개발에 대한 비용이 점점 줄어들 것 같기 때문입니다.
즉, 개발 비용이 줄어들기 때문에 다른 것에 남는 비용을 (물론 전체 비용을 줄여 버릴 수도 있지만) 다른 곳으로 돌릴 여지가 많아진다는 것입니다.

동의할 수도 있고 동의하지 않을 수도 있지만 “개발 비용”이 줄어들 것 같다는 것에 대한 몇 가지 현상들을 거론해보면 다음과 같습니다.

※ 물론, SE나 공중 부양을하고 있는 몇몇 언어의 순수 이론자 (doctrinaire)들의 말을 하기 시작한다면 끝도 없을 것입니다. 저는 제가 주로 보고 느낀 것들에 대해서 쓰겠습니다.

- 통으로 만들던 것들을 점점 레이어를 나누어 프레임워크, 플랫폼이라는 말을 하면서 개발하려고 합니다. (reusability를 이용한 생산성 향상이겠죠?)

- Visual C++ Team 블로그에서도 C++의 미래를 system programming을 위한 최적화 언어로 이야기한 적이 있습니다. 즉, 그 만큼 system level 이상에서 무언가 만들기 위한 고급 언어들이 많아졌고 그들의 한결같은 특징은 높은 생산성입니다.

- Java, C/C++ 등으로 열심히 플랫폼 (or 프레임워크)를 만들던 사람들이 파이어폭스 (정확히는  Gecko 겠죠?)를 플랫폼 자체로 사용하려고 합니다. 안드로이드 (Android)를 보는 시각도 같겠죠.

- 일본의 어떤 회사들은 perl을 사용한다고 해도 모든 것들을 창조한다고 합니다. 하지만 점점 더 “잘 가져다 쓰는 사람”들의 창조물을 더 이상 구태의연한 변명으로 덮어 버릴 수 없게 되어갑니다.

- 이제는 특정 OS (e.g. MS Windows)에서 나 홀로 (standalone) 애플리케이션을 만들던 시대는 점점 가버리고 환상적인 웹 기반 애플리케이션의 시대가 오고 있는 것 같습니다.

- (불특정의) 내가 고집하고 대대로 계승할 것만 같던 기술들에 대해 정체성을 느끼기 시작합니다.

- 더 이상 Java는 예전의 달팽이에 볼품없던 외관을 가졌던 그 Java가 아닌것 같습니다.

좀 더 많은 것들을 이야기 할 수도 있겠지만, 왠지 어느 양심수의 독백과 같은 풍으로 글이 흘러가는 것 같아서 줄이겠습니다.

하지만 말하고 싶은 것은
너무나 빨리 (쉽게라는 말은 하지 않겠습니다) 멋진 것들을 만들 수 있게 되어가고
“잘 만드는 사람” 보다는 “잘 이용해서 가져다 쓰는 사람”이 필요해지고
개발 그 자체의 비용이 줄어 들어감에 따라 이제는 생각 (기획)하고 품질(테스트를 포함하는)을 위해 투자할 비용이 넉넉해지는 것 같습니다.

그렇습니다.

[ [Mistakable Codes - #4] 연산자 오로딩 시 주의 사항. operator<< operator overloading, error C2666: ‘<<‘ : 13 overloads have similar conversions

8 Comments

근래 (약 1주일 동안) 이런저런 결정할 일들과 알아볼 일이 있어서 블로깅을 거의 못했습니다.
아무튼, 오늘은 간과하기 쉬운 실수에 대해서 포스팅을 합니다.
(이와 관련되어서는 언제 한 번 포스팅하겠습니다. ^^;; 지극히 사적인 부분이기는 하나)

다음 코드는 간단히 CTest라는 class를 만들어서 std::ostringstream::operator<<를 overloading했습니다.
즉, CTest를 operator<<가 인자로 받을 때 int 멤버 변수인 m_nNum을 operator<<에 넘겨주는 코드입니다.

하지만 위 코드를 컴파일 하면 아래와 같은 에러 메시지를 만나게 됩니다.

error C2666: ‘<<’ : 13 overloads have similar conversions

말그대로 operator<<를 overloading한 것중 어느 것을 선택해야 할지 모르겠다는 것입니다.
즉, (1)에서 컴파일러는 13개의 overloading 된 함수 후보 중 어떤 것을 사용할지 모르겠다는 것입니다.

해결책 – 1
간단하게 컴파일러에게 매개 변수로 int를 받으면 CTest의 생성자를 써라고 명시적으로 지정해주는 것입니다. 즉, explicit 키워드를 사용하는 것입니다.
※ CTest의 생성자를 int에 대해서 명시적으로 사용하게 정의하는 것으로 볼 수 있겠죠.


해결책 -2

하지만 explicit를 사용하면 다음과 같이 컴파일러가 알아서 디폴트 생성자를 불러주는 호화스러운 기능은 사용할 수 없습니다.

이런 부분을 고려해서, 아래와 같이 디폴트 생성자를 따로 만들어주고 CTest(int)의 디폴트 값을 제거할 수도 있습니다.
gimmesilver 님이 지적해주신 것 처럼 아래 코드 처럼 디폴트 생성자를 작성한다고 해서 A a =1;과 같은 코드가 가능해지는 것은 아닙니다.
디폴트 생성자가 필요한 다른 부분 (즉, CTest(int a =0); 으로 하면 디폴트 생성자가 굳이 없어도 되지만)을 위한 것이라고 보시면 됩니다.)

Ref: comp.lang.c++ Is it VC++ or I am going crazy???

^^ 그리고 gimmesilver님의 마지막 지적 처럼 위 코드의 옳고 그름의 가치를 떠나 Legacy나 가져다 쓰고 있는 오픈 소스에서 발생했을 경우 때문에 포스팅을 합니다.