로버트 L.글래스의 우리가 미처 알지 못한 S/W공학의 사실과 오해

간만에 정신이 맑아지고 현실과 미래가 뚜렷히 보이는 명쾌한 작품을 읽은 느낌이다. 왜 이 책이 그렇게 많은 사람들에게 회자되고 인용되었는지 알 수 있었다. 이 책은 소프트웨어가 세상에 등장한 이후부터 이 책이 쓰여진 시점까지 소프트웨어 분야에서 일어났던 모든 일들을 집약한 책이라고 설명할 수 있겠다. 우리가 소프트웨어를 개발하면서 만났던 수많은 일들, 경험적으로는 알고 있었으나 증명하기 어려웠던 문제들, 소프트웨어 개발 과정에서 겪었던 어려움과 그 이유들, 수많은 문제들 중에서 어떤 문제에 집중하는 것이 옳고 어떤 자세로 임해야 하는지 등에 대한 명료한 답을 제시해 준다.

이 글은 이 책(원 제목은 Facts and Fallacies of Software Engineering)에서 얻은 내용들을 정리한 것이다. 하지만 이 글만 읽어보고 책을 읽지 않게 되는 것은 내가 원하는 것과는 정 반대의 일이다. 내가 이 책을 누군가에게 소개한다면 다음과 같을 것이다. 이 책은 소프트웨어 개발과 관련된 모든 사람들이 읽어야 할 책이다. 소프트웨어를 직접 개발하는 사람이라면 꼭 읽어야 할 책이다. 이 책을 모르고 소프트웨어 개발을 해왔던 시간들이 너무 아깝다.


1장  관리

사람

사실 1. 소프트웨어 직업에서 가장 중요한 요소는 프로그래머가 사용하는 도구나 기술이 아니라, 프로그래머의 자질이다.

-       사람이 (소프트웨어 개발) 성공의 열쇠다

-       엄격한 방법론을 적용한 프로젝트를 한꺼풀 벗기고 프로젝트의 성공 이유를 물으면 그 답은 사람이다.

-       소프트웨어 생산성에 있어 가장 중요한 요소는 소프트웨어 실무자 개인의 역량이다.

사실 2 최고의 프로그래머는 최하의 프로그래머보다 28배 뛰어나다.

-       뛰어난 소프트웨어 실무자가 (동료들보다) 5배에서 28배까지 뛰어나다는 사실을 알 수 있다면, 가장 뛰어난 사람들을 잘 돌보는 것이 소프트웨어 관리자의 가장 중요한 업무라는 것은 자명한 일이다.

-       우리의 연구에서 가장 중요한 실질적 발견은 프로그래머 실무 능력의 현저한 개인차다

-       개인간에 5배 정도의 생산성 차이는 흔한 것이다.”

사실 3 지체된 프로젝트에 사람을 추가 투입하면 프로젝트가 더 늦어진다.

-       사람이 많을수록 커뮤니케이션은 더욱 복잡해진다. 따라서 프로젝트가 지연될 때 인력을 투입하면 프로젝트는 더 늦어지는 경향이 있다.

사실 4 작업환경은 생산성과 품질에 지대한 영향을 미친다.

-       프로젝트에서 상위 25%와 하위 25%에 대해서(이들간에는 2.6배 생산성 차이가 났다) 작업환경을 조사한 결과, 상위 그룹은 1.7배 넓은 공간에서 일했고, 충분히 조용한 환경이라고 대답한 비율이 2배 높았고, 개인 공간이라고 말한 비율은 3배 이상 높았으며, 전화를 돌리거나 꺼놓을 수 있다고 답한 비율은 각각 4배와 5배 많았다.


도구와 기술

사실 5 소프트웨어 업계에는 과대선전(도구와 기술에 대한)이 만연해 있다.

-       소프트웨어 기술 각각으로 얻을 수 있는 생산성 향상은 기껏해야 5~35% 정도이다.

-       재사용(공용화)으로 얻을 수 있는 이득도 10~35% 정도이다.

사실 6 새로운 도구와 기술은 도입 초기에 생산성/품질 저하를 초래한다.

-       도구와 기술을 익히는데는 적어도 6개월 ~ 2년 이상이 걸리고, 이 기간동안의 생산성은 기존보다 저하된다.

사실 7 소프트웨어 개발자는 도구에 대해 많은 말을 하지만, 별로 사용하지 않는다.

-       좋은 개발 도구라는 것 중 대다수는 계속 사용되지 않고 폐기된다.

추정

사실 8 폭주하는 프로젝트의 가장 흔한 원인 두 가지 중 하나는 부정확한 추정이다.

-       아직까지 소프트웨어 프로젝트가 걸리는 시간을 추정할 수 있는 정밀한 방법은 없다.

-       다만 분야의 전문가가 하는 추정만이 좀 더 정확할 뿐이다.

사실 9 소프트웨어 추정은 보통 부적절한 시기에 수행된다.

-       소프트웨어 추정은 보통 요구사항이 구체화 되기 전에 이루어진다.

-       많은 프로젝트가 이미 완료 기한이 정해진 상태로 시작된다.

사실 10 소프트웨어 추정은 보통 부적절한 사람들에 의해 수행된다.

-       많은 추정은 경영진이나 마케팅에 의해 결정된다.

사실 11 프로젝트가 진행되면서 소프트웨어 추정을 수정하는 경우는 거의 없다.

-       NASA에서는 소프트웨어 추정 재평가를 주창하며 라이프 사이클 상의 재평가 시점까지 정의한 연구가 있으나 권고를 따르는 사람을 본 적은 없다.

사실 12 소프트웨어 추정이 부정확한 것은 별로 놀라운 일이 아니다. 그러나 우리는 추정이 죽고 산다!

-       일정에 대한 추정이 부적절하다고 해도 프로젝트는 대부분 정해진 일정에 의해 관리된다.

사실 13 경영진과 프로그래머 사이에는 단절이 있다.

-       (경영진이 정한 추정에 맞지 않아) 실패라고 평가된 프로젝트에 대해 기술자의 다수가 가장 성공적인 프로젝트로 평가했다. 애초에 불가능했던 일정과 예산을 포기하고 도전적인 목표를 성공적으로 완료 했다고 생각 했기 때문이다.

사실 14 타당성 조사에 대한 대답은 항상 타당하다이다

-       프로젝트 시작 전에 이루어지는 기술적 타당성(구현 가능성을 검증하는) 조사는 거의 항상 타당하다는 결론을 낸다.

재사용

사실 15 소규모 재사용은 잘 해결된 문제다.

-       소규모 재사용(보통 라이브러리라고 불리는 함수 단위 재사용) 1950년대부터 있어 왔으며 이미 증명된 문제다.

사실 16 대규모 재사용은 여전히 해결되지 않은 어려운 문제다.

-       대규모 재사용(일명 공용화”)은 일반적으로 다양해지는 요구사항에 의해 제대로 활용되기 어렵다.

-       매우 좁은 도메인(예에서는 항공 역학) 내에서는 70%까지 재사용 모듈로 구축될 수 있었다.

사실 17 대규모 재사용은 서로 관련 있는 시스템 사이에서 가장 잘 적용된다.

-       대규모 재사용은 도메인 종속적이다.

-       여러 프로젝트나 여러 도메인에 걸쳐 적용하려 한다면 성공할 가능성이 거의 없다.

사실 18 재사용 가능 컴포넌트는 만들기가 3배 어렵고, 3곳에 적용해봐야 한다.

-       3이라는 숫자는 모두 경험적인 숫자이다. 다만 재사용 가능한 컴포넌트는 일반적인 것보다 만들기가 훨씬 어렵고, 잘 적용되는지를 여러 번 검증해 봐야 한다.

사실 19 재사용된 코드를 수정하는 것은 특히 오류를 범하기 쉽다.

-       기존의 솔루션을 이해하는 것은 소프트웨어 작업 중에서 가장 어려운 일이기 때문에, 재사용을 위해서 기존의 컴포넌트를 수정하는 것은 새로 만드는 것에 비해 매우 어렵다.

-       20~25% 이상을 수정해야 할 경우 처음부터 다시 만드는 것이 더 효율적/효과적이다.

-       소프트웨어 작업은 인류가 지금까지 해온 것 중 가장 복잡한 작업이다.”

사실 20 디자인 패턴 재사용은 코드 재사용 문제에 대한 해결책이다.

-       이 사실은 디자인 패턴과 같은 잘 알려진 설계에 대한 지식이 매우 중요함을 드러낸다.

-       구현된 컴포넌트는 재사용이 어렵지만, 소프트웨어의 구조 및 설계의 재사용은 가능하다.

복잡성

사실 21 문제의 복잡성이 25% 증가하면 솔루션의 복잡성은 100% 증가한다.

-       왜 그렇게 사람이 중요한가? 복잡성을 극복하는 데는 상당한 사고력과 기술이 필요하기 때문이다.

-       왜 대규모 재사용은 성과가 좋지 않을까? (도메인의 차이로 인한) 복잡성이 증대되기 때문이다.

-       왜 코드 리뷰(inspection)가 오류 제거에 대한 가장 효과적, 효율적인 접근 방법인가? 그 모든 복잡성을 걸러내고 오류의 위치를 찾는 데는 결국 사람의 노력이 필요하기 때문이다.

-       기존의 제품을 이해하는 것이 소프트웨어 유지보수에서 가장 중요하고도 어려운 작업인가? 하나의 문제를 해결하는데 적용할 수 있는 접근방법이 매우 많기 때문이다.

-       왜 소프트웨어에는 그렇게 많은 오류가 있는가? 처음부터 소프트웨어를 올바르게 이해하는 것은 매우 어렵기 때문이다.(여기에는 다른 견해가 있다. 소프트웨어는 불완전성의 원리에 지배 받기 때문에 사람의 두뇌가 아니면 오류를 잡아 낼 수 없다. 그리고 그 사람들은 모두 전문가인 것이 아니다. 물론 전문가라고 해서 오류를 만들어 내지 않는 것은 아니지만.)

사실 22 소프트웨어 작업의 80%가 지적인 작업이다. 그 중 상당 부분이 창조적인 작업이다. 사무적인 일은 거의 없다.

-       소프트웨어 실무자가 하는 일에 대한 관찰 연구 결과 80%는 지적인 작업, 20%는 사무적인 작업으로 분류되었다. 이 중 창조적인 작업은 6%에서 29%에 해당한다.

2장  생명 주기

요구사항

사실 23 폭주하는 프로젝트에서 가장 흔한 원인 두 가지 중 하나는 불안정한 요구사항이다.

-       고객과 사용자는 (그리고 대부분의 관리자들도) 보통 어떤 문제를 해결해야 하는지에 대해 확실하게 알지 못한다.

-       프로토타이핑은 요구사항이 명확하지 않을 때 자주 사용한다.(AOM 패턴을 이용해 볼만한 과정이다.)

사실 24 요구사항의 오류는 생산 단계에서 수정하는데 가장 비용이 많이 든다.

(너무 당연한 일이다)

사실 25 누락된 요구사항은 가장 수정하기 힘든 오류다.

-       누락된 요구사항은 이미 만들어졌거나 만들어지고 있는 코드에 대한 설계 수준의 수정을 요구한다.

설계

사실 26 명시적 요구사항을 설계로 옮겨갈 때 파생 요구사항이 폭발적으로 증가한다.

-       (명시적) 요구사항은 실제 어떻게 구현해야 할지를 결정하는 설계 단계에 오면 암시적 요구사항을 파생 시키고, 이 파생 요구사항은 명시적 요구사항의 50배에 달한다.

사실 27 소프트웨어 문제에서 최적의 솔루션이 하나 존재하는 경우는 거의 없다.

-       소프트웨어적인 문제는 문제에 대한 해결책이 매우 많다. 그 중에서 최상의 해결책은 없거나 알아내기가 매우 어렵다.

사실 28 설계는 복잡하고 반복적인 과정이다. 초기 설계 솔루션은 보통 잘못 되었거나, 최적이 아닌 경우가 많다.

-       (이는 애자일 진영의 XP, TDD, 리팩토링과 같은 기술 및 개발 방법론의 정당성에 힘을 실어 준다.)

-       전문 설계자는 설계상 핵심적인 문제에 대해 직접 구현해 보거나 솔루션을 찾아 놓은 다음에야 전체 설계 문제로 넘어간다.(이는 아키텍쳐는 코딩을 해야 한다는 주장을 뒷받침한다.)

코딩

사실 29 설계자의 기본단위와 프로그래머의 기본단위가 일치하는 경우는 거의 없다.

-       설계자의 코딩 경험, 전문 코딩 분야, 역량 등에 의해 설계 기본 단위의 크기가 달라진다. 이와 함께 코딩 실무자 역시 경험, 전문 분야, 역량에 따라 이에 대응되는 기본 단위가 달라지게 된다. 따라서 이 둘이 매칭되기는 어렵다.

-       (이것은 코딩 실무자의 능력이 설계자 수준과 비슷하게 뛰어나야 하고, 설계 수준이 낮거나 설계자가 없을 경우에는 코딩 실무자의 능력이 (설계를 커버할 수 있을 만큼) 매우 뛰어나야 한다는 것을 의미한다.)

-       나는 이 사실 때문에, 설계와 코딩 작업을 분리하는 것은 좋지 않다고 생각한다.”

-       소프트웨어 개발에서 전통적인 작업 분할 방식(설계자, 구현자, 테스터와 같이 전문 담당 업무를 분할하는 방식)은 적절하지 않다.”

사실 30 COBOL은 별로 훌륭한 언어가 아니지만, (비즈니스 데이터 처리에 대해서는) 다른 언어도 마찬가지다.

오류제거

사실 31 오류 제거는 생명주기에서 가장 많은 시간을 소모하는 단계다.

-       일반적으로 요구사항분석(20%) – 설계(20%) – 코딩(20%) – 오류 제거(40%)의 시간을 소모한다.

테스트

사실 32 프로그래머가 완전하게 테스트 했다고 믿는 소프트웨어도 보통은 로직 경로의 55~60%만 테스트 된 경우가 많다.

사실 33 100% 테스트 커버리지도 결코 충분하지 않다.

-       대략 소프트웨어 결함의 35%는 누락된 로직 경로(구현하지 않은 로직)에서, 40%는 로직 경로의 특정 조합을 실행할 때(로직 실행 후 다시 실행할 때 일부 변수가 초기화 되지 않아서 발생하는 오류가 대표적인 예이다) 나타난다. 따라서 100% 커버리지로도 잡히지 않는다.

사실 34 테스트 도구는 꼭 필요하지만, 많은 경우 거의 사용되지 않는다.

-       이유는 테스트 단계 자체가 관심을 많이 받지 못하기 때문이다. 자동화 테스트 도구는 매우 유용하다.

사실 35 특정 테스트 프로세스는 자동화할 수 있고, 또 자동화해야 한다. 그러나 자동화 할 수 없는 테스트 작업도 많다.

-       요구사항 명세서로부터 코드를 자동 생성할 수 있다는 개념은 이미 사라졌다. 따라서 프로그래머 없는 프로그래밍프로그래밍의 자동화에 대한 아이디어 역시 이미 사라졌다.(따라서 코더 개념 역시 사라져야 할 개념이다.)

-       (소프트웨어 개발 단계에서 가장 단순해 보이는) 테스트 조차도 완전히 자동화 할 수 있는 방법은 없다.(사실 모든 소프트웨어 개발 단계 중에서 자동화 할 수 있는 것은 없다.)

사실 36 프로그래머가 작성한 디버그 코드는 테스트 도구에 대한 중요 보완 수단이다.

-       컴파일러 옵션이나 외부 파일을 이용하여 테스트 코드를 실행할 수 있도록 만들어 놓는 것도 테스트에 도움이 된다.(이 책이 쓰여질 당시에는 Unit Test가 널리 보급된 상태가 아니었음을 상기하기 바란다.)

검토와 검사

사실 37 엄격한 검사는 첫 번째 테스트 케이스를 실행시키기도 전에 소프트웨어 제품에 포함된 오류의 90% 까지 제거할 수 있다.

-       (엄격한 검사란 inspection이라는 것으로 우리가 보통 이야기 하는 코드 리뷰이다)

-       (이는 인간의 두뇌만이 소프트웨어를 관리할 수 있는 유일한 도구라는 내 생각을 뒷받침한다)

-       이렇게 좋은 방법임에도 잘 실행되지 않는 것은 오류 검사 단계에 대한 무관심과 엄격한 검사 과정의 어려움(“이미 작성된 코드에 대한 이해가 가장 어려운 것이라는 점을 상기하기 바란다.) 때문이다.

-       (여기에 덧붙이자면 많은 관리자들이 코드리뷰나 페어 프로그래밍을 지적 유희라고 생각하거나 베테랑이 초보자에게 가르치기 위해 하는 일이라고 생각한다. 우리나라의 많은 관리자들은 프로그래밍을 저급한 업무로 취급하면서 모든 개발자가 1~2년쯤 지나면 다들 베테랑이라고 생각하기 때문에 페어 프로그래밍 하려는 사람들을 야단친다.)

사실 38 엄격한 검사도 테스트를 대체할 수는 없다.

사실 39 출시 후 검토(회고라 부르는 사람들도 있다)는 중요하지만, 거의 실행되지 않는다.

사실 40 검토는 기술적 측면과 사회학적 측면을 모두 가지는데, 어느 쪽도 무시하면 안 된다.

-      동료 검토(peer review)에 의해 이성과 감정에 상처를 받지 않도록 해야 한다.

-      비자아적 프로그래밍(코드에 자신의 자존심을 투영하지 않는 것)을 당부하지만 대부분의 개발자들은 자신의 코드에 대해 자부심을 가지려고 한다. (이는 미묘한 문제이면서도 코드를 통한 커뮤니케이션이나 형상 관리를 방해하는 문제이기도 하다. 자신 있는 코드라면 공개하는 것이 마땅하고, 자신 없는 코드라면 공개하고 조언을 받아야 하는 것이 마땅하다. 어느 편이든 코드는 공개되어야 한다.)

사실 41 유지보수는 보통 소프트웨어 비용의 40~80%를 차지한다. 따라서, 유지보수는 소프트웨어 생명주기 중 가장 중요한 단계일 것이다.

-       (일단 다음 절에서 유지 보수란 단순한 오류 수정이 아니라 기능의 개선 및 추가까지 포함한 작업이라는 것을 기억하기 바란다.)

-       유지 보수 비용이 높은 이유는 이미 만들어진 기능을 이해하기가 어렵기 때문이다.

-       (따라서 가독성 높고 간결한 코드를 만드는 것이 개발자의 자질 중 가장 중요한 것이다.)

사실 42 유지보수 비용의 60%는 개선 작업에 소요되는 비용이다.

-       소프트웨어를 처음 개발할 때 고객과 사용자는 새로운 소프트웨어로 무엇을 할 수 있을지에 대해 단지 비전의 일부만 갖고 있을 뿐이다. 소프트웨어가 출시되어 한동안 사용해 본 후에야 사용자는 그 소프트웨어 시스템을 개선해 무엇을 더 할 수 있을지 깨닫기 시작한다.

-       60/60 법칙 : 소프트웨어 비용의 60%는 유지보수에 사용되며, 유지보수 비용의 60%는 개선에 사용된다. 따라서 기존 소프트웨어를 개선하는 것은 큰 일이다.

-       60%는 개선 17%는 오류 수정 18%는 포팅, 5%는 기타 작업

사실 43 유지보수는 문제가 아니라 해결책이다.

(다른 산업 분야와는 달리) 소프트웨어의 유지보수는 대부분 기능의 개선을 위한 것이기 때문에 문제가 아니라 해결책이다. 따라서 부정적으로 보지 않아야 한다.

사실 44 유지보수에서 가장 어려운 작업은 기존 시스템을 이해하는 것이다.

-       유지보수 작업에서 가장 중요한 요소는 이해력이다 –Ned Chapin, 유지보수 분야 개척자-

-       소프트웨어 업무 중에서 가장 어려운 일이 유지보수 작업이다. 보통은 (내가 만든 게 아닌) 다른 사람이 만든 소프트웨어를 다루어야 하기 때문이다.

사실 45 더 좋은 소프트웨어 공학 기술로 개발하면 더 많은(더 적은 게 아니라) 유지보수가 필요하다.

-       현대적 개발 방법론 및 소프트웨어 기술이 적용된 소프트웨어는 더 수정하기 쉽기 때문에 더 많은 수정이 가해지고 더 오랫동안 개선되면서 사용된다.

3장  품질

품질

사실 46 품질은 속성의 집합이다.

-       이식성, 신뢰성, 효율, 사용편의성, 테스트 용이성, 이해 용이성, 수정 용이성

사실 47 품질은 사용자 만족, 요구사항 충족, 비용과 일정 목표 달성, 또는 신뢰성이 아니다.

신뢰성

사실 48 대부분의 프로그래머가 흔히 범하는 오류가 있다.

-       인간은 하나씩 밀린 인덱스, 정의/참조의 불일치, 중요한 설계 항목 누락, 자주 사용하는 변수에 대한 초기화 실패, 일련의 조건 중 하나의 조건 누락 등 특정 종류의 작업에서 쉽게 실수한다.

사실 49 오류는 뭉치는 경향이 있다.

-       오류의 반이 모듈의 15%에서 발견된다.

-       오류의 80%가 단지 모듈의 20% 이내에서 발견된다.

-       대략 80%의 결함이 모듈의 20%에서 나오고, 모듈의 절반 정도는 오류가 없다.

-       특정 모듈이 특히 더 어렵기 때문에, 여러 개발자가 모듈 별로 개발하기 때문에(개인 능력차 때문에) 그럴 것이다.

사실 50 소프트웨어 오류 제거에 있어서 단 하나의 최상의 방법은 없다.

사실 51 오류는 항상 남아 있다. 심각한 오류를 제거하거나 최소화하는 것이 목표가 돼야 한다.

-       오류 없는 소프트웨어를 개발하는 것은 불가능하다.

-       CMM 레벨 4인 팀과 다른 정형방법을 사용하는 팀 둘이서 충분한 비용과 일정에도 불구하고 98% 신뢰성의 간단한 제품을 만들어 내지 못했다.

효율

사실 52 효율은 훌륭한 코딩보다는 훌륭한 설계에 더 많은 영향을 받는다.

사실 53 고급 언어 코드도 어셈블리어 코드의 90%에 가까운 효율을 낼 수 있다.

-       항공 애플리케이션에서 고급 언어의 비효율성(어셈블리 대비 C언어) 10~20% 정도이다.

>  최적화 컴파일러를 이용하면 10% 성능향상

> 튜닝을 통해 2~5% 더 향상 시킬 수 있다.

사실 54 크기와 속도 사이에는 트레이드오프가 있다.

4장  연구

연구

사실 55 많은 연구자들이 연구보다는 옹호에 치중한다.

 

2부 오해 5+5

5장  관리

관리

오해 1 측정할 수 없는 것은 관리할 수 없다.

-       측정할 수 없는 것은 통제할 수 없다는 말은 사실이지만 관리할 수 없다는 말은 아니다. 소프트웨어 설계라는 것은 측정 불가능하지만 관리할 수 있는 대상이다.

-       몇몇 회사에서는 메트릭을 통한 관리를 중요시한다. 그리고 자주 사용하는 소프트웨어 메트릭이 개발되어 사용되고 있다.

-       (우리나라에서 메트릭을 통해 소프트웨어를 관리하지 않는 이유는 관리자들의 소프트웨어에 대한 이해가 부족하고, 필요한 경우 외주를 통해 해결할 수 있는 수준의 저급한 업무로 폄하되고 있기 때문이다. 야구나 농구에서 데이터를 관리하거나 기업의 재무제표가 관리되는 이유와 정반대 이유다.)

오해 2 소프트웨어 품질은 관리로 해결할 수 있다.

-       소프트웨어 품질에는 기술적 요소가 많아 관리만으로 해결할 수는 없다.(이는 소프트웨어를 외주로 개발하는 행위에 대해 경종을 울린다. 자체 기술 부족으로 인하여 기술이 검증된 업체를 통해 개발하고 그 기술을 내제화 하기 위한 외주가 아니라 단지 시간이 부족하거나 허드레 업무라고 생각해서 외주를 주는 경우 관리만으로 품질을 확보하는 것은 불가능하다. 이로 인한 짐은 고스란히 개발자에게 넘겨진다.)

오해 3 프로그래밍은 비자아적이 될 수 있고, 또 되어야 한다.

-       (많은 개발자들은 자신의 코드가 자신의 지적 능력을 대변한다고 생각한다. 그래서 코드에 대해 자아를 투영하곤 한다. 하지만 이러한 자세는 원활한 시스템 통합, 오류 검사, 형상 관리를 방해한다.)

-       오류 없는 프로그램을 작성하는 것은 불가능하다는 것을 인정하고, 기술적 약점이 잘 발견될 수 있도록 열린 자세를 가져야 한다.

도구와 기술

오해 4 도구와 기술 : 한 가지로 모든 문제를 해결할 수 있다.

-       소프트웨어 문제를 해결할 완전한 한가지 기술이나 도구는 없다.

오해 5 소프트웨어 분야에는 더 많은 방법론이 필요하다.

-       많은 방법론이 교수들이나 대학원생 등 소프트웨어 비 실무자들에 의해 개발된다. 특히 엄격한 방법론(융통성을 거부하고 전체 개발 프로세스를 감시하려는 방법론, 예를 들어 전통 waterfall과 같은 방법론)을 경계해야 한다.

추정

오해 6 비용과 일정을 추정하기 위해서는 먼저 LOC(Lines of Code)를 추정해야 한다.

-       LOC 만으로는 부정확한 메트릭이다.(프로그래밍 언어, 도메인, 주석과 같은 요소를 고려해야 한다.)

-       (특히 이 절은 비용과 일정을 추정하기 위해 LOC추정하는 문제에 대해 언급하고 있음을 기억해야 한다. 추정을 위해서 부정확한 메트릭을 사용하는 것에 대한 주의이다.)

-       (LOC는 몇몇 부수적인 메트릭과 함께 사용되면 훌륭하게 기능할 수 있다. 특히 코드의 조건문 빈도, 중복 코드 비율, 모듈 특성 등과 함께 사용한다면 개발자의 생산성을 판단하는데 매우 유용할 수 있다. 같은 모듈이나 계층을 개발하는 개발자들간에도 LOC 10배 이상 차이 나는 경우가 흔하다. 2배 이하라면 큰 의미는 없다.)

6. 생명주기

테스트

오해 7 랜덤 테스트 입력은 테스트를 최적화 하는 좋은 방법이다.

-       소프트웨어의 복잡성이 늘어나면 늘어날수록 랜덤 테스트를 통해 찾아낼 수 있는 오류는 줄어든다.

검토

오해 8 “보는 눈이 많으면, 모든 버그는 그 깊이가 얕다.”

-       오픈소스에 대한 이야기인데, 많은 오픈소스가 다수의 눈을 거쳐 수정되는 과정을 거치지 않고, 수정되는 버그는 대부분 발견하기 용이한 것들이다. 중요하고 심각한 버그들은 여전히 숨어 있을 가능성이 있다.

유지보수

오해 9 과거의 비용 데이터를 살펴봄으로써 미래의 유지보수 비용을 예측할 수 있고 시스템 교체 결정을 내릴 수 있다.

-       다양한 연구가 있으나 (기본적으로 소프트웨어의 유지 보수가 대부분 새로운 기능을 추가하는 일이고, 이것은 이미 매우 어려운 일로 알려져 있으므로) 유지보수 비용을 예측하는 것은 매우 어려운 일이다. 따라서 이를 기반으로 시스템 교체 결정을 내리는 것은 불가능하다.

7장.  교육

테스트

오해 10 프로그래밍을 가르칠 때 프로그램을 어떻게 작성하는지 보여주며 가르친다.

-       잘 만들어진 코드를 읽어 보는 것은 직접 작성하는 것만큼(혹은 그보다 더) 중요하다. 하지만 잘 만들어진 코드 샘플을 찾아 내는 것은 어렵다.

-       다른 사람의 코드를 읽는 것은 소프트웨어 개발에서 가장 어려운 작업을 훈련할 수 있는 일이다.

-       (페어 프로그래밍이나 코드 리뷰에 힘을 실어 주는 사실이다. 다른 사람의 코드를 보지 않거나 고쳐보지 않은 개발자, 잘 만들어진 오픈소스나 라이브러리 코드를 보지 않는 사람의 개발 능력은 언제나 낮기 마련이다.)

Posted by 이세영2
,
Eclipse 단축키.xlsx


Java 프로그래밍 툴로 Eclipse를 많이 사용한다. Eclipse가 제공하는 단축키도 많고 단축키를 새로 바인딩 할 수도 있는데, 그 중에서도 매우 유용하게 사용할 수 있는 것들을 골라서 소개해 보도록 하겠다. 특히 단축키는 외우고 있을 때 더욱 위력일 발휘하기 때문에 나 같은 경우도 모니터 앞에 단축키 목록을 출력해서 복사해서 붙여 놓고 있다. 그럴때 사용하기 좋도록 엑셀파일로 만들어 첨부해 두었다.


이동 단축키

원하는 곳으로 이동을 쉽게 할 수 있는 단축키들이다.

Ctrl+객체클릭 변수나 클래스 등을 정의한 곳으로 이동

대부분 알만한 단축키인데 컨트롤을 누르고 객체명 또는 타입명을 클릭하면 해당 객체나 타입의 선언부로 이동한다.


Ctrl+Shift+G 변수나 함수 등을 레퍼런스 하는 곳으로 이동

    변수나 함수명을 드래그 또는 더블 클릭 해서 선택한 후 이 단축키를 누르면 아래와 같이 해당 변수나 함수를 레퍼런스 하고 있는 곳이 열거 된다. 클릭하면 해당 코드로 이동할 수 있다.


Alt+LEFT 이전 커서 위치로 이동

    매우 유용한 단축키인데, 한 곳에서 편집을 하고 있다 다른 곳으로 이동한 후, 다시 이전 위치로 이동하고 싶을 때 이 단축키를 누르면 된다. 여러번 반복해서 이전 위치로 이동하는 것도 가능하다.


Alt+RIGHT 다음 커서 위치로 이동

    위의 키와 함께 쓰이는 키로써, 편집을 하던 이전 위치로 이동했다가(Alt + LEFT) 다시 다음 위치로 이동하고 싶을 때 이 단축키를 누르면 다시 돌아간다. 역시 반복적으로 복귀하는 것이 가능하다.


찾기 단축키

Ctrl+Alt+G 전체 workspace에서 문자열 찾기

    문자열을 선택한 후 이 단축키를 누르면 아래쪽 Search 창에 해당 문자열이 들어 있는 모든 프로젝트의 모든 파일을 찾아서 보여 준다.


Ctrl+K 선택한 문자열을 파일 내에서 찾기

    문자열을 선택한 후 이 단축키를 누르면 파일 내에 있는 동일한 문자열을 "위에서 아래" 순서로 찾아서 커서를 이동시켜준다.


Ctrl+Shift+K 선택한 문자열을 파일 내에서 역순으로 찾기

    문자열을 선택한 후 이 단축키를 누르면 파일 내에 있는 동일한 문자열을 "아래에서 위" 순서로 찾아서 커서를 이동시켜준다.



주석 단축키

Ctrl+Shift+/ 블록을 블록 주석으로 처리

    화면에서 일부 블럭을 드래그 하여 선택한 후 이 단축키를 누르면 /*로 시작하여 */로 끝나는 블럭 주석으로 만들어준다.


Ctrl+Shift+\ 블록 주석 제거

    이미 블럭 주석이 되어 있는 부분을 선택한 후 이 단축키를 누르면 블럭 주석이 해제된다.


Ctrl+/ 한줄 주석 처리 또는 제거

    화면에서 일부 블럭을 드래그 하여 선택한 후 이 단축키를 누르면 각 줄이 "//"로 시작하는 한줄 주석들로 만들어준다.


자동화 단축키

Alt+Shift+R 변수나 클래스 등의 리팩토링

    변수나 클래스명 등 모든 명칭에 마우스를 올리거나 드래그 해서 선택 한 후 이 단축키를 누르면 아래와 같이 명칭에 박스가 생긴다. 그 후 명칭을 편집하여 변경하면 그 명칭을 사용하는 곳 전체에서 명칭이 한꺼번에 변경된다. 종종 변경된 명칭이 기존의 명칭과 충돌되면 에러가 발생한다.


Ctrl+Shift+O 자동으로 import

    외부 패키지나 라이브러리에 있는 클래스를 사용하게 되면 참조 오류가 발생한다. 만약 패키지나 라이브러리가 이미 프로젝트에 등록이 되어 있다면 이 단축키를 눌렀을 때 자동으로 import 코드를 생성해 준다.


Ctrl+I     들여쓰기 자동 수정

    일부 블럭을 드래그 하여 선택한 후 이 단축키를 누르면 들여쓰기를 설정된 포맷에 맞게 수정해 준다. 외부 소스를 복사해 왔을 때 종종 들여쓰기 단 수나 스페이스바 들여쓰기가 안맞는 경우가 있는데 이때 사용하면 알아서 사용하는 포맷에 맞게 들여쓰기를 해준다.


구조 보기 단축키

Ctrl+T(또는 F4) 클래스 Hierarchy 보기

    클래스의 계층이 복잡할 경우, 또는 인터페이스가 정의되어 있는데 인터페이스를 구현한 구체 클래스를 찾기 힘든 경우에 클래스 명 또는 인터페이스 명 위에 커서를 놓고 이 단축키를 누르면 그 클래스의 계층도를 보여 준다. 아래는 IDestination이라는 인터페이스에 이 단축키를 눌렀을 때 보여지는 화면이다. 인터페이스는 I 모양의 아이콘, 클래스는 C 모양의 아이콘으로 나온다.


Ctrl+O 클래스 멤버 함수 보기

    이 단축키는 파일 내에 선언되어 있는 모든 클래스와 모든 인터페이스들에 내부에 선언된 모든 멤버 함수들을 보여준다. 아래는 그 예시이다.



편집 단축키

Alt+Shift+A 상하 편집 모드로 전환

   종종 한 줄이 아니라 여러줄에 걸쳐 선언되어 있는 클래스 명이나 변수명만을 선택적으로 복사하고 싶을 때가 있다. 이럴때 이 단축키를 누르고 블럭을 선택한 후 Ctrl+C를 하고, 다시 이 단축키를 눌러서 상하 편집 모드에서 나온 후에 붙여 넣기를 하면 블럭 내에 선택되었는 부분만 복사가 된다.



Ctrl+Shift+X 선택된 문자열을 대문자로 전환

    선택한 문자열을 대문자로 전환해 주는 단축키이다. 보통은 일반 변수로 선언했다가 enum 타입 또는 상수로 선언을 바꾸고자 할 경우에 유용하다.


Ctrl+Shift+Y 선택된 문자열을 소문자로 전환

    선택한 문자열을 소문자로 전환해 주는 단축키이다. Java의 일반적인 명명법으로는 클래스는 대문자로, 인스턴스명은 소문자로 시작된다. 그런데 보통은 클래스명의 첫머리를 소문자로 한 명칭을 인스턴스명으로 사용하는 경우가 많다. 가령 TcpCommunication 클래스의 인스턴스명은 보통 tcpCommunication이라는 식이다. 이런 경우 클래스명을 복사하여 붙여 넣은 후, T를 선택하고 이 단축키를 누르는 식으로 사용한다. 이 단축키는 위에서 소개한 상하 편집 모드로 변환 단축키(Alt+Shift+A)와 함께 사용했을 때 더 강력하다. 즉 상하 모드에서 여러줄에 걸쳐 선언된 클래스명을 복사해서 붙인 후 이 단축키로 첫머리를 소문자로 변환하면 금새 인스턴스명으로 변환된다.



리팩토링 단축키

Alt+Shift+S R Getter/Setter 자동 생성 창 열기

    Getter / Setter를 자동 생성해주는 창을 여는 단축키이다. Getter와 Setter를 여럿 만들어야 하는 경우에 유용하게 사용할 수 있다.

Alt+Shift+M Method로 추출

    소스 코드를 블럭 선택 한 후 이 단축키를 누르면 메소드 생성 창이 뜬다. 메소드 명과 변수명을 적절히 입력하고 나면 입력한 메소드 명의 메소드가 생성되고, 선택한 소스는 그 메소드 내로 이동하며, 기존 소스가 있던 자리는 메소드 콜로 대체된다.


Alt+Shift+I Method를 인라인 하기

    Method로 추출 단축키의 반대이다. 메소드에 커서를 놓고 이 단축키를 누르면 이 메소드를 사용하고 있는 모든 곳에 메소드 내의 소스 코드가 삽입되고, 메소드는 삭제된다.


기타 단축키

Ctrl+W 현재 파일 닫기

    편집하고 있는 파일을 닫는다.

Ctrl+Shift+W 열린 파일 모두 닫기

    종종 편집하기 위해 열어 둔 파일이 너무 많은 경우가 있다. 이 때 이 단축키를 누르면 모든 창이 닫힌다. 닫히기 전에 저장이 안된 파일에 대해서는 저장하라고 경고 창을 띄워 주기 때문에 안전하게 사용할 수 있다.

Ctrl+F11 최근 실행 파일 실행

    최근에 실행했던 프로그램 실행 파일을 실행해 준다. 만약 현재 편집 중인 파일에 main() 메소드가 있을 경우 현재 파일을 실행한다.(최근 실행 파일과 현재 파일 중 어떤 것을 실행할지를 선택할 수 있는 옵션이 있다.)

Alt+Shift+ X T Unit Test 실행

    유닛 테스트를 작성하여 사용하는 경우에 유용한 단축키이다. 이 단축키는 여러모로 유용한데, 테스트 파일 전체를 실행하고 싶으면 그냥 이 단축키를 누르면 된다. 만약 특정한 한 개의 유닛 테스트 함수만을 실행하고 싶다면 함수를 드래그 해서 선택하고 이 단축키를 누르면 그 테스트 함수만 실행된다. 만일 특정 패키지를 실행하고 싶다면 Package Explorer 창에서 패키지를 선택하고 이 단축키를 누르면 된다. 또 전체 프로젝트에 대한 테스트를 하고 싶다면 프로젝트를 선택한 후 단축키를 누르면 된다.


Ctrl+ + / - 텍스트 에디터 폰트 크기 조절

    이 단축키는 내 경험상 Eclipse 최신 버전인 Neon에서만 동작한다. 종종 텍스트 폰트 크기를 손쉽게 변경하고 싶을 때가 있다.(세미나나 강의를 위해서 프로젝터를 사용하게 될 경우 특히 그렇다) 이 때 이 단축키를 사용하면 폰트 크기가 조절된다. 텍스트 에디트 창에서만 실행 가능하다.


사용자 지정 단축키

Eclipse에서는 다양한 커맨드에 대해서 사용자가 직접 단축키를 지정할 수 있다. 아래는 단축키로 지정해 두면 도움이 되는 것 들이다.

Ctrl+Shift+P 새로운 패키지 생성( New (Package)에 대해서 )

    새로운 패키지를 생성하는 단축키이다. 기본적으로는 지정이 안되어 있다. 따라서 Preferences / General / Keys 에 들어가서 검색창에 package를 입력한다. 그러면 그 중에 창 모양 아이콘과 함께 "New (Package)" 라는 것이 있을 것이다. 그것을 선택한 후 Binding 입력란에 이 단축키를 입력한다. 그리고 When 란에는 In Windows를 선택한다. 그러면 이후 새로운 패키지를 추가할 때 이 단축키만 누르면 패키지 생성창이 뜬다.


Ctrl+Shift+M 새로운 클래스 생성

    위와 비슷하게 새로운 클래스를 생성하기 위한 단축키이다. Preferences / General / Keys에 들어가서 검색창에 class를 입력한다. 그 중에서 "New (Class)"를 선택한다. 만약 Java와 C/C++을 동시에 사용하는 경우라면 Java 클래스를 생성하고 싶다면 "New (Class (org.eclipse.jdt.ui.....)"로 되어 있는 것을 선택하고, C++ 클래스를 생성하고 싶다면 "New (Class)"를 선택하면 된다. 그리고 위와 비슷한 방법으로 단축키를 입력하고 When란에 Java는 Editing Java Source를, C++은 In C/C++ Views를 선택한다. 이후에 클래스를 생성할 때 이 단축키를 입력하면 클래스 생성창이 뜰 것이다.


Posted by 이세영2
,

복사해서 붙여 넣으면 끝!

중복 코드가 나쁘다는 것은 익히 알고 있지만 문제는 중복 코드가 자주 나타난다는 점이다. 여러가지 이유가 있겠지만 가장 큰 이유는 복사해서 붙여 넣기가 너무 쉽기 때문이다. 조금 다른 코드를 작성하는 경우에도 복사해서 붙여 넣고 일부만 수정해 주면 끝이다. 즉, 중복을 만드는 것은 매우 쉬운 일인데 상대적으로 중복을 없애기는 어렵다. 중복은 머리가 없어도 만들 수 있지만 중복을 없애는 것은 머리가 없이 할 수 없는 일이다.


중복에 대한 인식

중복에 대해서 크게 문제 삼지 않는 경우이다. 중복 코드에 대한 인식 문제는 프로그래밍에 대한 자세와 연관이 깊다. 코드를 만들고 돌아가면 끝이고 다시는 그 코드를 들여다 보고 싶지 않은 개발자들이 보통 이런 사고를 가지고 있다.


객체지향 설계 및 디자인 패턴에 대한 지식 부재

요컨데 중복 코드를 제거하는 도구와 정형화 된 방법들에 대한 이해가 부족하기 때문이다. 객체지향의 겉모습은 온톨로지(Ontology)를 기반으로 한 실제 세계에 대한 인식을 컴퓨터 상에 모사한 것으로 보이지만, 깊숙히 들여다 보면 중복을 효율적으로 제거하는 도구들로 채워져 있다. 문제는 객체지향을 이해하는 것보다 코드를 만드는 일이 더 선행된다는 점이다. 즉, 중복을 제거할 수 있는 도구는 모른체 코드를 만들기 시작한다. 당연히 코드는 중복으로 넘쳐날 수 밖에 없다.


발견하기 어려운 경우

중복 코드가 너무 멀리 있는 경우, 중복 코드를 만든지 너무 오래된 경우, 중복 코드가 너무 짧은 경우, 중복 코드가 다른 코드에 뭍혀 있는 경우, 중복 코드 블럭 중간에 다른 코드가 들어가 있는 경우 등이다. 아래 코드를 보자.

class Example{

    private int data;

    public Example(int data){

        this.data = data; // 중복

    }

    public void setData(int data){

        this.data = data; // 중복

    }

}

이 코드는 중복이 있다. 아래와 같이 고쳐 줘야 한다.

class Example{

    private int data;

    public Example(int data){

        setData(data); // 중복 제거

    }

    public void setData(int data){

        this.data = data;

        /* data 갱신시 수행할 일들 추가 */

    }

}

얼핏 보면 우스운 일이다. 단 한 줄의 코드고, 직접 수행하던 코드를 함수까지 써가면서 수정했다. 코드는 줄지도 않았고 오히려 함수 콜에 의한 연산만 증가했다. 하지만 기존의 코드는 명백한 중복 코드이다. 만약 data 값이 변경 되었을 때 해야 할 일이 생겼다면 어떻게 할 것인가? 기존의 코드에서는 생성자와 setter 함수 모두 그 일을 수행하도록 변경해야 할 것이다. 하지만 아래 코드에서는 setData() 함수 내부만 수정해 주면 된다. 코드는 한 줄 바뀌었지만 "data가 갱신 되었을 경우 해야 할 일"에 대한 코드가 들어가야 할 위치가 setter 함수 쪽으로 단일화 되었다. 단 한 줄의 코드가 중복인 경우라도 수정에 닫혀 있지 않다면 중복이다. 그리고 중복을 해결하면 코드가 짧아질 것이라는 선입견을 버려야 한다. 중복 코드를 없애는 것은 코드를 짧게 줄이는 것이 아니고 중복 코드가 발생시킬 수 있는 문제를 차단하는 것이다.


다 똑같은데 일부만 다른 경우

대표적인 예가 순회(방문) 코드일 것이다. 즉, 여러 객체들을 방문해서 어떤 작업을 수행하는 코드이다. 이 때 방문 코드가 매우 길어지면 상대적으로 방문해서 할 작업 코드는 짧아진다. 그러면 다른 작업이 추가되면 방문 코드는 복사해서 붙여 넣게 된다. 이런 형태의 코드들은 발견해도 바로 수정하기는 어렵다.


다른 코드와 섞여 있는 경우

중복 코드가 블럭 A와 블럭 B의 연속이라고 하자. 그리고 이 중복 코드가 두 군데 이상 존재하는데, 어느 한 쪽에서 블럭 A와 블럭 B 사이에 흐름과 관계 없는 코드를 집어 넣었다고 가정하자. 이런 경우에 중복 코드를 발견해 내기가 어려울 수 있다. 그리고 상황에 따라서는 삽입된 코드가 중복 코드들과 유사하거나 어떤 영향이 있는지를 알기 힘들어서 분리해 내기가 어려울 수도 있다.

List<String> list1 = new ArrayList<String>();

List<String> list2 = new ArrayList<String>();

List<String> list3 = new ArrayList<String>();

public void example(){

    for(int i = 0; i < list1.size(); i++){

        /* list1에 대한 연산 */

        /* list1 + list2 + list3에 대한 연산 */

    }

} 

list1에 대한 연산이 중복 코드일 경우, 아래에 있는 코드와의 관계를 재빨리 파악하기는 힘들다. 왜냐하면 둘 모두 같은 for 문에 묶여 있기 때문이다. 이 경우 코드를 유심히 들여다 보지 않고는 중복 코드가 있는지 발견하기 어렵다.


매개가 필요한 경우

이 경우가 해결하기 어려운 문제 중 하나이다. 분명 코드는 중복인 것처럼 보이지만 다들 조금씩 다르고 해결하기에는 쉬워 보이지 않는다. 아래 코드를 보자.

class Boundary{

    int northLimit = 100;

    int southLimit = 50;

    int eastLimit = 20;

    int westLimit = 10;

   

    public int getNorthLimit() { return northLimit; }

    public int getSouthLimit() { return southLimit; }

    public int getEastLimit() { return eastLimit; }

    public int getWestLimit() { return westLimit; }

    public void setNorthLimit(int northLimit) { this.northLimit = northLimit; }

    public void setSouthLimit(int southLimit) { this.southLimit = southLimit; }

    public void setEastLimit(int eastLimit) { this.eastLimit = eastLimit; }

    public void setWestLimit(int westLimit) { this.westLimit = westLimit; }

}

각 데이터들에 대해서 반복적인 패턴이 나타난다. 즉, 데이터 선언과 getter / setter 선언이 그것이다. 이러한 중복은 얼핏 해결이 불가능해 보인다. 완벽하게 동일한 코드가 아니고 유사한 코드들의 나열이기 때문이다. 이것을 해결하기 위해서는 매개체가 필요하다.

enum Direction{

    NORTH,

    SOUTH,

    EAST,

    WEST

    ;

    public static int size(){ return values().length; }

}

class Boundary{

    int[]boundaries = new int[Direction.size()];

    public int getLimit(Direction direction) { return boundaries[direction.ordinal()]; }

    public void setLimit(Direction direction, int limit) { boundaries[direction.ordinal()] = limit; }

}

이것이 중복의 해결책인 이유는 다음과 같다. 만약 Direction이 추가되었을 경우, Boundary 클래스는 수정이 전혀 필요하지 않게 된다. 기존의 코드에서는 새로운 데이터가 추가될 때 getter/setter가 추가 되어야 했다는 점을 주목하자. 이렇게 매개체가 필요한 형태의 코드들은 중복을 발견해 내기가 어렵다.

Posted by 이세영2
,

많은 책에서 코드 중복이 나쁘다는 말이 나온다. 이 글에서 코드 중복이 발생시키는 문제들을 정리해 보도록 하겠다.


완벽하게 논리적인 사고로 만들어진 버그

중복 코드가 있을 경우, 개발자가 완벽하게 논리적인 사고를 한다고 해도 버그가 발생하게 된다. 이 문제를 첫번째로 놓은 이유는 그만큼 이 문제가 심각한 영향을 미치기 때문이다. 개발자의 능력이 아무리 뛰어나고 논리적 사고를 잘 한다고 해도 중복 코드가 있으면 비 논리적인 코드를 만들어 내게 된다.

아래 코드를 보자.

public void function1(){

    task1();

}

public void function2(){

    task1();

} 

위와 같이 두 개의 함수가 있다고 가정하자. 편의상 두 함수를 나란히 배치했지만, 두 함수가 멀리 떨어져 있다고 가정하자. 중복된 코드는 task1()이다. 여기서 task2()를 추가한다고 가정해 보자. 논리적으로 task1() 이후에는 항상 task2()가 와야 한다. 그러면 각 함수의 task1() 이후에 task2()가 실행되도록 수정되어야 할 것이다.

그래서 개발자는 function1()에서 task1() 이후 task2()를 실행하도록 코드를 수정한다. 하지만 가정했듯이 두 함수가 아주 멀리 떨어져 있다면 function2() 함수가 있는지 모를 수도 있고, 그래서 task2()를 실행하도록 수정하지 않았다면 그 코드는 버그로 남게 된다.

이 문제는 중복 코드가 들어 있는 함수 간의 거리, 중복 코드를 만든 후 지나간 시간, 중복 코드의 개수, 중복 코드의 길이에 비례하여 커진다.


중복의 강요

중복된 코드는 중복을 강요한다. Unit Test를 한다면 중복된 코드에 대해서 중복으로 테스트를 만들어야 한다. 중복된 코드는 중복된 주석, 문서, 설명을 요구한다. 복사 해서 붙여 넣기는 쉽지만 그것을 계속 유지하기는 어렵다.


OCP(Open Close Principle) 위배

코드는 수정에 닫혀 있어야 한다는 SOLID 원칙에 위배 된다. 하나의 문제를 수정하기 위해 여러 코드를 수정해야 하기 때문이다.


코드량 증가

중복 코드는 코드의 양을 증가 시킨다. 코드의 양이 늘어나면 코드를 읽는데 걸리는 시간이 늘어나고, 수정이나 디버깅 이슈가 발생할 가능성이 높아진다. 같은 기능을 구현한다면 짧고 간결한 코드를 작성하는 것이 좋다.


중복 코드 동일성 검사

우스운 일일지 모르지만 중복 코드들은 모두 동일해야 한다. 그래서 개발자들은 종종 중복 코드가 완벽하게 동일한지를 검사한다. 이런 일이 발생하는 이유는 중복 코드를 발견하기는 쉽지만 생각보다 제거하기는 더 어렵기 때문이다. 어쨌든 이런 검사도 불필요한 비용을 발생시킨다.


Posted by 이세영2
,

이 글에 앞서

모든 악의 근원 : 불완전성의 원리

- 비 구조적 언어와 예견된 위기


소프트웨어 기술에 대한 이야기를 본격적으로 하기에 앞서, 소프트웨어 - 불완전성 - 인간의 인지 능력에 대한 관계를 명확히 해 둘 필요가 있다. 특히 이 글에서는 인간의 인지 능력의 특징을 살펴 보도록 하겠다. 인간의 인지 능력이 어떤 것인지 알아야만 왜 소프트웨어 기술이 현재와 같은 방향으로 발전하게 되었는지를 이해할 수 있다.(내가 누구이며 왜 내가 그것을 알아야 하는지를 아는 것을 메타 인지라고 한다. 소프트웨어 기술이 왜 필요한지를 아는 메타 인지가 있어야 기술을 배우고 익히는데 주저함이 없어지고 과감하게 도전하게 되는 것이다)

우선 불완전성의 원리와 소프트웨어의 관계에 대해서는 모든 악의 근원 : 불완전성의 원리에서 이야기 했다. 그리고 초창기 소프트웨어 기술이 어떤 문제로 불완전성을 관리하지 못했는지를 비 구조적 언어와 예견된 위기에서 이야기 했다.

불완전성과 인간

만약에 소프트웨어에 불완전성이 없었다면 어떤 세상이 되어 있었을까? 즉, "완전성의 원리"라는 것이 증명이 되었다고 가정해 보자. "완전성의 원리"는 어떤 공리계가 무모순이고, 그 공리계는 자기 자신의 무모순에 대한 정리를 포함할 수 있다고 가정해 보자. 좀 더 쉽게 이야기 하자면, 어떤 알고리즘이 있고, 이 알고리즘이 오류가 없을 때 이 알고리즘은 자기 스스로 오류가 없음을 증명할 수 있다고 하자. 좀 더 과감하게 가정해 보면 모든 알고리즘이 스스로 오류가 있는지 없는지를 검사할 수 있다고 해보자. 

만약 그런 세상이었다면 이제 모든 수학자들이 알고리즘 연구에 매달리게 되었을 것이다. "알고리즘 스스로 자신의 알고리즘이 완벽함을 증명하는 알고리즘"은 당연히 먼저 만들어지게 되었을 것이다. 그 다음에는 "스스로의 완전함을 증명할 수 있으면서 다른 알고리즘의 완전함도 증명할 수 있는 알고리즘"이 만들어졌을 것이다. 그리고 이게 발전하면 "모든 다른 알고리즘의 완전함을 증명할 수 있는 알고리즘"이 만들어지게 될 것이다. 

이것은 상상하기 힘들 정도로 엄청난 알고리즘임에 틀림 없다. 인간이 어떤 소프트웨어를 작성하더라도 이 알고리즘은 소프트웨어에 오류가 있음을 알아서 찾아 준다. 좀 더 지나면 찾는 것에서 끝나지 않고 사람이 의도한 바를 알고 알아서 오류가 없도록 만들어 주기도 할 것이다. 이제 사람이 소프트웨어를 개발한다는 것은 이 알고리즘에게 자신이 개발하려는 것이 무엇인지 그 의도를 알려 줄 수만 있으면 된다. 이런 세계에서라면 소프트웨어 개발자는 전 세계에 몇 명 되지 않을 것이다.

몇가지 좀 더 상상력을 발휘해 볼 수도 있지만 애초에 가정에서 시작한 것이니 이쯤에서 접어 두겠다. 그럼 이제 현실은 어떠한가? 모든 소프트웨어는 불완전성의 원리에 의해 지배 된다. 소프트웨어 스스로는 자신의 동작에 오류가 없음을 증명할 수 없다. 증명은 불완전성의 원리에 의해 불가능하다. 오직 잘 관리할 수만 있을 뿐이다. 그러면 무엇이 그것을 관리할 수 있을까?

답은 "인간의 두뇌" 밖에 없다. 다른 소프트웨어는 자기 스스로도 완전함을 증명할 수 없다. 따라서 당연히 다른 소프트웨어를 관리할 수 없다. 결국은 소프트웨어를 만드는 사람이 소프트웨어를 관리할 수 밖에 없다. 소프트웨어의 문제는 인간의 문제인 것이다.

자 여기서 결론을 먼저 이야기 해 보도록 하겠다. 모든 소프트웨어 기술, 정확히 말해서 소프트웨어를 만드는 기술은 (소프트웨어를 위해서 만들어진 것이 아니라) 인간을 위해 만들어진 것이다.

기술까지 가지 않고도 이야기 할 수 있는 부분은 많다. int count를 int a라고 쓰지 않는 이유는 무엇인가? int count든 int a든 컴퓨터에게는 어떤 차이도 없다. 둘 중 무엇을 쓰든지 컴퓨터에는 어떤 문제도 발생하지 않는다. 문제는 인간에게 생긴다. 인간은 count 대신 a를 사용하는 것과 같은 짓을 10번만 해도 코드를 이해하기 힘들어진다. 코드를 이해하기 힘들어지면 소프트웨어를 개발하는 과정도 힘들어진다. 그리고 소프트웨어는 스스로 완전함을 증명할 수 없다. 그러면 소프트웨어에는 문제가 생긴다. 간단한 예시였지만 불완전성과 인간의 관계가 어떤 것인지 이해할 수 있었을 것이다.


인간의 인지 능력

소프트웨어의 불완전성을 관리하는 것은 인간의 몫이다. 그 중에서도 특히 인간의 두뇌이고, 이 중에서도 소프트웨어를 다루는 능력이 될 것이다. 우선 인간의 두뇌 능력을 포괄적으로 인지 능력이라고 부르도록 하자. 인지 능력이란 무엇인가를 이해하는 능력이다. 두뇌가 뛰어난 사람들은 더 많은 것을 이해할 수 있다. 그러나 두뇌가 뛰어나다고 해서 모든 것에 대한 이해가 다른 사람들보다 뛰어난 것은 아니다. 두뇌의 능력은 사람에 따라 다르고 경험에 따라 다르다. 그래서 개개인의 특성을 이야기하는 것은 별 의미가 없다. 우리에게 필요한 것은 소프트웨어를 관리할 수 있는 두뇌의 능력을 이해하는 것이다.

인지 능력 중 필요한 것을 나열하는 것으로 시작해 볼 수도 있다. 하지만 인간의 두뇌는 당연하고 일상적인 것을 받아 들일 때에는 그 중요성을 잊어버리는 경향이 있다. 그래서 두뇌의 능력에 맞게 오히려 인지를 방해해서 이해하기 어렵게 하는 것들이 무엇인지 이야기 해보자.


사실이 아닌 것

개인적인 경험일 뿐이지는 모르겠지만 다른 사람과 이야기를 하다 보면 너무나도 당연한 사실을 아니라고 우기는 사람들이 있다. 그래서 사실이 아닌 것을 사실이라고 말하는 사람에게 그 이유를 물어 보게 된다. 왜 믿는지를 이야기하는 과정에서 잘못된 가정이나 왜곡된 사실이 있는지를 알아보기 위해서다. 그래서 이야기를 듣고 나서는 일단 그렇게 믿게 된 이유를 알게 되긴 했다. 그 자리에서 그것을 알고 나서 얼마 지난 후에 그 사람의 이야기를 다시 조립해보면 다시 이해가 가지 않는다. 그리고 그 사람과의 대화 내용이 제대로 기억이 나질 않는다.

공감이 가는 이야기인가? 사람은 사실이 아닌 것을 기억하기 힘들어 한다. 사람의 기억은 (적어도 그 개인에게는) 너무나 자명한 사실이다. 사실이라는 것은 생존을 이롭게 만든다. 송이 버섯을 먹을 수 있다는 것은 사실이다. 그것을 기억하고 있으면 생존에 더 이롭다. 그래서 사람들은 생존에 이로운 것을 사실로 기억하고, 다른 것을 기억해야 할 때 이미 가지고 있는 기억과 연관지어 다른 것들을 계속 생각하게 된다. 그래서 사실인 것은 기억하기가 쉽고 사실이 아닌 것은 기억하기가 어렵다.

이것이 소프트웨어와 무슨 연관이 있을까? 아래 코드를 보자.

class Dog{

    public final int LEGS = 2;

    public final int HEAD = 25;

    public final String name = "cat";

}

class Cat{

    public final int LEGS = 4;

    public final int HEAD = 1;

    public final String name = "cat";

}

첫번째 클래스와 두번째 클래스 중에서 기억하기 쉬운 것은 어떤 것인가? 아마 Dog 클래스를 이해하려고 시도하면 머리 속에 혼란이 오기 시작할 것이다. 이 코드를 보고 기분이 나빠지기 시작했다면 미안하다. 하지만 이 코드로 의도하고자 했던 것은 이해할 수 있을 것이다. 우리는 소프트웨어를 작성하면서 소프트웨어가 완전히 논리적인 것이며, 그렇기 때문에 실제 세계와는 생각하는 방식이 완전히 다를 것이라고 추측하곤 한다. 하지만 이 예제에서 보듯이 소프트웨어를 만드는 일은 실제 세계에서 하는 일과 유사하다. 즉, 기억하고 있는 것과 새로 기억해야 할 것들을 연관지어서 생각하는 것이다. 단기적으로 Dog 클래스를 기억할 수는 있다. 하지만 그 기억은 오래가지 못할 것이다. 우리가 아는 실제 세상, 즉 사실과 다르기 때문이다. 사실과 다른 것은 생존에 도움이 되지 않는다. 따라서 머리 속에서 금방 지워진다.


개념의 매핑이 올바르지 않은 것

빨강 노랑 파랑 녹색 주황

위의 단어 중 맞는 매핑이 맞는 단어는?

답을 3초 이내에 맞춰냈다면 대단한 사람이라고 칭찬할 수 밖에 없다.


그러면 아래에 주어질 단어와 색깔의 매칭은 맞는가?

빨강 노랑 파랑 녹색 주황

성인이 이 매칭에 3초 이상 걸렸다면 약간 문제가 있는 것일 수도 있다. 이 문제는 내가 매우 좋아 하는 문제 중 하나이다. 위의 것은 맞는 건 단 하나 뿐인데 찾는데 시간이 걸리고, 아래는 모두 매칭 시켜 봐야 하는데도 시간이 덜 걸렸을까?

답은 매우 간단하다. 우리가 색깔에 대한 단어를 훈련할 때 그렇게 훈련했기 때문이다. 즉 빨강 색깔을 보여 주고 단어를 이야기 해주거나 빨강이라는 단어를 주고 색을 찾도록 훈련해 왔기 때문이다. 우리가 익히 아는 색깔이 아닌 다른 색깔을 주어주고 이름을 이야기 했을 때 다른 색에 비해 새로 배운 색깔을 찾아 내는데 어려움을 겪는 것도 같은 이유다. 이 매칭 문제는 훈련이 없이는 얻어지는 것이 아니다. 첫번째 문제가 어려운 이유는 단순하다. 저런 식으로 색깔과 단어를 매칭시키는 훈련을 한 적이 없기 때문이다.


비 구조적인 것

비 구조적이라는 말은 구조가 없다는 말이다. 간단히 말하면 여러 부분으로 분리될 수 없음을 의미 한다. 소프트웨어에서 보면 우리가 익히 알고 있는 함수나 객체, 패키지나 모듈 단위로 쪼갤 수 없음을 의미한다. 현대에 와서 이런 소프트웨어 구조는 상상도 할 수 없다. 그러기에는 현대 소프트웨어는 너무 크다.

그런데 잠깐 생각해보자. 왜 사람은 비 구조적인 것을 구조적인 것보다 어렵게 느끼는 것일까? 왜 비 구조적인 것은 소프트웨어 크기를 크게 만들 수 없고, 구조적인 것은 더 크게 만들 수 있는 것일까?

일단 구조적인 것은 부분으로 분리가 가능하다. 부분으로 나누고 나면 관리하기가 편해지는데 이는 로마에서 말하는 "분할하여 통치하라"는 격언과 딱 들어 맞는다. 사람은 잡다한 것들의 덩어리 보다는 명확히 규정되어 있는 것들의 부분 부분을 이해하는 데 훨씬 뛰어나다. 이것이 어느 정도나 그러냐 하면, 실제 세계는 매우 다양한 양상들의 뒤섞임을 통해 만들어지는데도 불구하고 자꾸 분리하려고 할 정도다. 미시적으로 보면 동물과 식물은 명확히 구분되지 않는다. 미생물의 영역에서 보면 동물인지 식물인지를 구분하기 힘든 생물들이 존재하기 때문이다. 하지만 우리는 생물이 동물과 식물로 구분되어 있다고 규정 짓는다. 이 편이 이해하기 훨씬 쉽기 때문이다. 동물과 식물을 나누면 동물의 특성과 식물의 특성을 규정 짓기가 쉬워지고, 그러고 나면 동물과 식물을 더 이해하기 쉬워진다. 그리고 나면 자연스레 그 경계 영역에 있는 생물의 특성도 이해하기가 쉬워진다.(이러한 방식이 문제가 되는 곳도 있다. 바로 인간 스스로를 이렇게 구분 짓는 경우다. 그 문제도 중요하긴 하지만 주제 밖이므로 일단 논리적인 것에 집중하자.)

분할은 인간의 인지 능력의 기본 특성이다. 지식은 분리를 통해 시작된다. 소프트웨어를 배울 때 처음에는 무작정 다 같은 소프트웨어인 줄 알고 시작하지만 금새 여러 언어가 있음을 알게 된다. 하나의 언어를 배우고 나면 다른 언어와의 차이를 모르지만 여러 언어를 배우다 보면 왜 언어 마다 다른 특성이 있는지 어느 정도 이해하게 된다. 문법을 익히고 나면 구조 설계라는 것이 있다는 것을 알게 되고, 코딩 룰이나 디자인 패턴과 같이 좋은 설계나 구현의 방식들이 있음을 알게 된다. 이런 형태로 지식은 계속 분화하면서 이해는 계속 깊어지게 된다.

그런데 만일 소프트웨어가 구조가 없다면 어떻게 될까? 그 소프트웨어를 접한 사람은 그것을 이해하기 위해 알고 있는 지식들을 총 동원하게 될 것이다. 장담하건데 적어도 전체 코드의 일부를 분할해서 그 블럭에 이름을 붙이려고 시도할 것이다. 설령 goto 문으로 이리저리 얽혀 있는 코드라고 해도 그렇게 이름을 붙여서 한 덩어리로 된 소프트웨어를 어떻게든 분할해 놓는 편이 이해하기가 훨씬 쉽기 때문이다.


너무 큰 것

너무 큰 소프트웨어는 인지 능력에 방해가 된다. 인간의 두뇌는 명백히 제한적인 리소스만 가지고 있기 때문이다. 소프트웨어의 위기는 소프트웨어가 커지면서 시작되었다. 그리고 그러한 노력이 구조적 언어와 객체지향 언어를 만들어 냈다. 하지만 언제나 이런 통찰력 있는 시도들을 모든 사람이 따르는 것은 아니다. 여전히 수천줄짜리 함수를 누군가는 만들어 내고 있고, 그 많은 소스들을 한 개의 파일에 담으려고 노력하고 있다. 마치 내일은 없는 것처럼.


비 가시적인 것

눈으로 볼 수 없는 것은 이해하기도 어렵다. 잘 그려진 플로우 차트나 시퀀스 다이어그램 만으로도 복잡한 코드를 손쉽게 이해해 본 경험이 있을 것이다. 코드는 스스로 그림을 그려주지 않기 때문에 코드만을 가지고는 소프트웨어의 동작을 이해하기 어려울 수 있다. 이것이 정적인 뷰와 동적인 뷰가 따로 존재하는 이유이다.


표현하기 어려운 것

모든 소프트웨어 개발자들은 이름을 짓는데 어려움을 느낀다. 어떤 통계에서는 소프트웨어 개발자로서 가장 어려운 것이 무엇인지 조사했더니 절반 가까이가 이름 짓는 것이라고 말했다고 한다. 이름을 짓는 것이 어려운 이유는 자신이 만들어 낸 것을 현실 세계에서 찾아 낼 수 있는 개념이 아니거나, 이미 유사한 개념들을 다른 곳에 많이 써버렸기 때문이다. 그래서 현실 세계에서의 개념이 빈약한 사람들은 프로그래밍을 잘 할 수 없다. 이름을 지을 수 없기 때문이다. 이름을 잘 지을 수 있는 것도 능력이다.

이름이 없는 것을 이야기 하는 일을 이야기 해보자.

"지금으로부터 바로 이전 해가 뜬 시점에서 얼마 지나지 않은 그 때에 색깔이 얼룩이거나 단색이면서 네 개의 지면에 붙은 관절이 있는 부위로 걷고 머리 쪽에 밥을 먹는 구멍으로는 "야옹"이라는 소리를 내는 짐승을 보았다."

"아침에 고양이를 보았다"

같은 이야기를 하려 해도 너무 힘들다. 이름이 없다는 것은 이만큼 힘들다. 이름이 없어도 표현하려고 하다보면 말은 길어지고 의미 전달은 힘들어진다. 그 마저도 표현력이 뛰어나고 이해력이 뛰어난 두 사람이 만났다면 모를까 이름 없이 소프트웨어의 구현을 이야기 한다는 건 너무 힘든 일이다. 


너무 긴 시간

시간은 기억의 적이다. 모든 기억은 시간이 지날수록 희미해진다. 아무리 날카로운 지성이라고 해도 긴 시간 앞에서는 무기력 할 수 밖에 없다. 시간이 지나면서 프로그래밍 실력이 늘어났다고 해서 예전에 만든 코드에 대한 기억력이 더 높아지지는 않는다. 따라서 인지 능력을 최대한으로 발휘하기 위해서는 가능한 짧은 시간 내에 필요한 모든 일을 처리해야 한다.

어떤 일을 인지하는데 시간이 문제가 되는 또 다른 이유는 시간이 길어지면 길어질수록 집중력은 떨어지고, 다른 일들이 중간에 발생할 여지가 커진다는 점이다. 그러면 다시 이전의 이해로 돌아가는데 시간이 걸린다. 이것은 효율적으로 인지 능력을 사용하는 방법이 아니다.


그리고 소프트웨어

이처럼 인지능력에 해가 되는 것들이 무엇인지 알게 되었으니 이를 소프트웨어와 연관 지어 보도록 하자. 소프트웨어는 불완전성을 가지고 있다. 따라서 소프트웨어를 개발할 때 불완전성을 관리할 수 있는 도구는 오직 두뇌밖에 없다. 그리고 두뇌에는 한계가 있다. 이 한계 내에서 두뇌를 효율적으로 사용하려면 인지 능력을 최대한 발휘할 수 있는 형태로 소프트웨어를 관리해야 한다. 따라서 인지능력이 올바르게 발휘되도록 하려면 소프트웨어는 다음과 같은 특성을 가지고 있어야 한다.

최대한 실제 세계와 유사하도록 만들 것, 개념( = 메소드 혹은 변수, 클래스의 명칭)과 실제(구현)가 맞도록 할 것, 구조적일 것, 작은 단위로 나눠져 있을 것, 가시적인 도구를 사용할 것, 명확하게 표현할 것, 개발의 주기를 짧게 가져갈 것.

이와 같이 불완전성을 관리하는 도구로서의 두뇌를 최대한 활용할 수 있는 형태로 소프트웨어를 만들어야만 성공적인 소프트웨어를 만들 수 있다. 소프트웨어 분야에서는 이러한 특성을 이미 경험적으로 알고 이를 소프트웨어 기술 형태로 지속적으로 발전 시켜왔다. 어떤 때에는 선구자들의 통찰력을 통해, 어떤 때에는 그 통찰을 언어나 툴, 프레임워크에 담음으로써 기술적인 완성도를 높여 왔다. 소프트웨어는 거의 컴퓨터 초창기부터 있어 왔으나, 소프트웨어 기술을 기반으로 해서 세계 최고의 매출을 올리는 기업들이 속속 등장하는 이유도 이러한 소프트웨어 기술들이 만들어지고 많은 개발자들이 이를 적용하게 되었기 때문이다.

이 다음 글에서는 이렇게 인간의 인지 능력을 최대한 활용할 수 있도록 만들어 준 소프트웨어 기술들에 대해 하나씩 알아 보도록 하겠다.

Posted by 이세영2
,


지금까지 공부해 온 소프트웨어 영역의 기술들을 트리 형식으로 정리해 본 것이다.


소프트웨어 기술은 그 발전 속도가 너무 빠르다. 그리고 새로운 기술은 매일 매일 쏟아져 나온다. 이런 상황에서 이제 소프트웨어를 접한지 얼마 되지 않은 사람들은 어떤 것을 먼저 공부해야 할 지 갈피를 잡기 힘들 것이다. 개인적으로 이제껏 소프트웨어를 공부해 오면서 안타까웠던 점을 꼽자면 이런 급변하는 상황에서도 기술들 간에 어느 정도 줄기가 있다는 것, 그리고 줄기가 되는 기술들 간에 선후 관계가 있다는 것을 처음부터 알지 못했다는 것이다. 그런 관계를 알게 된 것은 이제 소프트웨어의 근간이 되는 기술들을 대부분 알게 된 이후였다. 개인적인 능력의 문제도 있겠지만 이 점을 미리 알았다면 그것들을 모두 익히는데 이렇게 오랜 시간이 걸리지는 않았을 것이다. 

그런 안타까움이 뭍어 있는 것이 바로 이 기술 트리다. 만약 이제 소프트웨어를 막 공부하기 시작한 사람이라면 이 트리에 맞춰 공부하기를 추천한다. 그리고 다른 수많은 기술들이 있지만 적어도 이 영역 내의 기술들은 소프트웨어를 하는 사람들이라면 거의 필수적인 기술들이라고 봐야 한다.

일부 개발 영역에 따라서는 더 중요한 것이 빠져 있을 수도 있다. 개발자라고 해서 모두 같은 영역에서 일하는 것이 아니기 때문이다. 웹 프론트, 백엔드, 임베디드, 데이터베이스 영역에서는 세부적으로 보다 더 중요한 기술도 있을 수 있다. 그래도 역시 위의 기술들이 뼈대를 이루는 것들이다. 그리고 그 중에서도 가장 중요하다고 생각되는 기술들은 볼륨 처리를 해 두었다. 저 중에서 볼륨 처리된 기술에 대해서 간략히 이야기해 볼까 한다.


객체지향(OOP, Object Oriented Programming)

현대 소프트웨어 개발에 있어서 가장 중요한 되는 개념이라고 생각하면 된다. 스크립트 언어나 함수형 언어를 접하게 되더라도, 그리고 구조적 언어를 통해 개발을 하게 되더라도 객체지향은 꼭 알고 지나가야 하는 개념이다. 트리에서도 보듯이 프로그래밍 언어의 기초 문법을 익히고 나서 소프트웨어를 구조적으로 작성하기 위해 배우는 첫번째 단계이며 이후 필요한 소프트웨어 기술들의 모태가 되는 기술이다. 즉, 객체지향을 모르고는 어떤 소프트웨어적인 개념도 제대로 이해하기 힘들고, 객체지향을 모르는 사람을 소프트웨어 개발자라고 말하기 어렵다.

불완전성의 관리 관점에서 보면 객체지향은 갈수록 대형화 되어 가는 소프트웨어를 작은 단위로 축소시켜 주는 역할을 한다. 하위 타입에 대한 은폐를 통해서 작성해야 할 코드의 양을 줄이면서도 수정 및 확장이 용이한 소프트웨어 구조를 만들어 준다. 상속을 통해서는 중복된 코드가 발생하는 것을 막아주고, 인터페이스와 타입의 개념을 통해서는 내부 구현에 대한 은폐를 가능하게 해준다. 변수 대신 객체를 바꿈으로써 조건문/제어문을 사용하는 대신 직접 행위를 변경할 수 있게 한다.

객체지향이 소프트웨어 영역에 가져온 영향력은 막대하다. 사실상 소프트웨어에 설계의 개념이 도입된 것이나 설계의 원칙이 도입되게 된 것, 올바르고 좋은 설계의 패턴, 소프트웨어의 가시화(UML) 등 거의 모든 소프트웨어 기술은 객체지향을 이용하거나 객체지향에서 파생된 것, 또는 객체지향을 개선한 것들이다. 현대의 대부분의 언어들은 객체지향을 온전히, 혹은 적어도 부분적으로 지원한다.


UML(Unified Modeling Language)

UML이 있기 전까지 소프트웨어는 비 가시적인 기술 영역이었다. 인간이 눈으로 얼마나 많은 양의 정보를 얻는지를 안다면 이것은 치명적인 문제였다. UML이 없었던 시절, 소프트웨어를 여럿이서 함께 개발한다는 것이 무척 어려웠을 것이다. 인간의 언어는 코드보다 부정확하다. 코드는 완벽하게 진실만을 이야기 하지만 구조를 이해하지 못한 상태에서의 코드는 줄거리를 모르는 대서사시처럼 장황하다. 인간의 언어로 대화하다가 서로 막히는 곳이 있으면 그 대서사시를 살펴봐야 한다. 이 와중에 일부 개발자들은 자신의 코드를 신성시 한다. 아마도 UML이 없던 시절에 소프트웨어를 바라보는 다른 엔지니어들의 시선은 그리 좋지 못했을 것이다. 소프트웨어 개발자 간에도 의사 소통이 신통치 않았을텐데 다른 분야의 사람들과 원활히 대화하기는 더욱 어려웠을 것이다.

사람들이 소프트웨어를 (자기 나름대로의 방법으로) 가시화 하기 시작했을 때에도 그 가시적인 도안들을 통한 커뮤니케이션이 원활하지 않았다. 작은 그룹에서는 통용될지 몰라도 의사소통의 단위가 커지면 가시화의 방식이 달라 서로 이해하기 어려웠다. 

UML은 이런 가시적인 툴로서는 최초로 보편적인 표시 언어로 사용된 것이다. 개발자들은 UML을 통해 비로소 서로의 코드를 보지 않아도 소프트웨어의 구조를 이해하게 되었고, 코드를 먼저 만들지 않고도 구현을 이야기 할 수 있게 되었다. 

아직까지는 코드와 유사한 수준의 소프트웨어 이해를 가능하게 하는 언어는 UML이 유일하다. 


디자인 패턴

디자인 패턴이 탄생한 후부터 개발자들은 좋은 설계를 인간의 언어로 말할 수 있게 되었다고 할 수 있다. 아기로 비유하자면 이제 막 첫 마디 단어를 말하는 그 시점만큼 극적인 일이다. 디자인 패턴이 있기 전에는 어떤 설계가 다른 설계보다 어떻게 나은지를 설명하기 위해 코드를 작성하거나 UML을 그리거나 자신이 하려고 하는 일에 대해서 상대방에게 인간의 언어로 수 십 분에 걸쳐 이야기 해야 했다. 디자인 패턴이라는 것이 개발자들이 설계 문제를 해결하던 여러 방법들에 이름을 붙여 놓은 것이기 때문에, 설계에 대해 한참 이야기를 하다 보면 서로 같은 이야기를 하고 있었다는 것을 알게 되었을 것이다. 디자인 패턴은 이런 "같은 이야기"들에 이름을 붙였다. 그 이후부터는 같은 이야기를 지루하게 반복하는 일이 없어졌다.

사람들이 잘 된 설계에 대해 이름을 붙이기 시작하면서 대화는 짧아지고 정밀한 설계에 대해 집중할 수 있게 되었다. 그러면서 다른 디자인 패턴들도 많이 생겨나게 되었고, 대화는 더욱 풍성해졌다. 같은 설계 문제에 대해 어떤 패턴을 적용하는 것이 더 나은 설계인지를 이야기할 수 있게 되었다. 

디자인 패턴을 모르고는 설계를 이야기 할 수 없다.


Unit Test(단위 테스트)

단위 테스트는 소프트웨어의 안전망이다.

단위 테스트 이전의 소프트웨어는 주로 정밀한 설계를 통한 구현 상에서의 오류 감소, 그리고 통합 테스트를 통한 디버깅이 불안전성 제거를 위한 거의 유일한 방법이었다. 이 방법을 제외하고는 인간의 두뇌가 유일한 불안정성 관리 도구였다. 불안전성의 원리 때문에 직접적으로 소프트웨어의 완전성을 증명할 수 없지만 유닛 테스트는 간접적인 방법으로 안전망을 구축해준다.

유닛 테스트의 유용성을 이야기 해보면 다음과 같다. 

우선 직접 작성하지 않은 소스에 유닛 테스트가 있을 경우, 소스의 의도를 파악하는데 도움이 된다. 필요한 경우에는 리팩토링을 통해서 소스를 더욱 잘 이해할 수도 있고, 설계를 바꿈으로써 소스의 흐름을 더 원활하게 가져갈 수도 있다. 

유닛 테스트는 구현에서 발생한 버그를 테스트 단계에서 발견하게 됨으로써 생기는 디버깅의 어려움을 감소시켜 준다. 버그는 발생한 시점에 발견하여 즉각 수정하는 것이 손쉬운데 이는 버그가 발생한 시점이 코딩 시점과 가까울수록 해당 버그의 문제점을 짚어 내기가 용이하기 때문이다.(사실 이 부분은 불완전성 관리의 도구가 오직 두뇌임을 명시적으로 보여주는 대목이다) 그런데 프로젝트가 커지면 커질수록 전통적인 개발 프로세스에서는 구현과 테스트 간의 간격이 더 벌어졌다. 대형 프로젝트일수록 더 정밀한 관리가 필요하고 더 나은 방식으로 문제점을 해결해야 함에도 전통적인 프로세스는 이 문제를 더 키우기만 할 뿐이었다. 유닛테스트가 생겨남으로써 일시적인 버그는 즉시 판단하고 제거할 수 있게 되었다.

유닛 테스트의 또 다른 이점은 설계에 준하는 수준의 소프트웨어 동작 지침을 제공한다는 것이다. 이는 TDD(Test Driven Development)가 추구하는 방향인데, 테스트 코드를 구현 코드보다 먼저 작성함으로써 구현 코드가 작성되어야 할 방향을 정해주는 것이다. 이로써 설계 단계에서 미비했거나 요구사항의 불확실성 때문에 완벽하지 못했던 설계를 유닛 테스트를 통해 보충해 줄 수 있다.


리팩토링

현대의 소프트웨어는 늘 수정된다는 특성이 있다. 그래서 요즘에는 완벽한 설계보다는 실행 가능하고 수정 가능한 설계를 추구하는 경향이 있다. 이에 따라 별다른 수정 사항이 없어도 구현 중에 일부 설계가 부적절한 것을 발견하게 되는 경우도 있고, 초기에는 잘 된 설계임에도 불구하고 기능적인 수정이 늘어나면서 설계의 효율이 떨어지는 경우도 있다. 이렇게 효율이 떨어진 설계를 널리 잘 알려진 좋은 설계, 즉 디자인 패턴을 중심으로 좋은 설계로 바꾸어 나가는 작업을 리팩토링이라고 한다.

이 과정은 근본적으로는 설계의 변경이지만, 이미 만들어진 기능에 대해 수행하는 작업이므로 실질적으로는 잘 동작하고 있는 코드를 수정하여 설계 맞추는 작업이라고 할 수 있다. 이 과정에서는 잘 동작하는 코드가 수정 중에 버그가 발생하지 않도록 안전장치를 해 둘 필요가 있다. 이 역할을 하는 것이 유닛 테스트이다. 리팩토링 과정은 어떤 경우에는 별다른 어려움 없이 끝날 수도 있지만 어떤 경우에는 상당한 시간 동안 진행 될 때도 있다. 이 때 리팩토링의 각 단계에서 기존 기능과 동일하게 동작함을 확인시켜주는 유닛 테스트는 필수적이다.

리팩토링은 디자인 패턴이 나온 이후에 생겨난 것이고, 유닛 테스트를 통해서 그 안정성을 보장 받게 되었다고 볼 수 있다. 또한 구현 이후에 설계를 변경한다는 점에서 정통의 소프트웨어 개발 프로세스와는 상반된 개념이기도 하다. 소프트웨어 분야는 아직도 한창 발전하고 있는 분야이기 때문에 혁신적인 사고가 언제든 기존의 사고를 제치고 자리 잡을 수 있다. 설계를 반영하여 코드를 작성하고, 이미 작성된 코드를 수정하고, 수정된 코드에 맞춰 설계를 변경하는 일련의 과정은 소프트웨어가 가진 유연성이라는 장점을 가장 잘 드러내는 과정이라고 볼 수 있다. 리팩토링은 개발자가 설계와 코드 안에서 자유로워 질 수 있음을 보여주는 기술이라 할 수 있다.


Agile

Agile은 전통적인 소프트웨어 개발 방법론의 단점을 보완하기 위해 생겨난 개발 방법론이다. 전통적인 개발 방법론은 철저한 요구사항 수집 및 분석, 이를 바탕으로 한 세밀한 설계, 설계에 딱 맞는 구현, 설계-구현에서의 부족한 점을 테스트를 통해 보완하는 구조로 되어 있다. 이는 개발 방법론이 정립되지 않았던 시기 보다는 나은 결과물을 내줄 수는 있었지만 현대의 소프트웨어 분야의 트렌드와는 잘 맞지 않는다. 현대에는 개발 시작 시점에 요구사항이 완벽한 경우가 별로 없고(거의 없다), 시장의 요구 변화에 맞춰 개발 진행 중에 상당 부분 변경이 이루어진다. 개발 중간에 수많은 요구사항들이 새로 생겨나고 없어지거나 수정된다. 또한 개발이 완료되었다고 해도 지속적인 수정 요청이 발생하기도 한다. 이러한 요구사항 변화를 기존 프로세스 상에 반영하는 것은 거의 불가능에 가깝다.

Agile은 현대 소프트웨어 개발 과정의 특성을 반영하고자 하는 프로세스이다. 시장은 항상 변하고, 이에 따라 요구사항은 항상 변한다. 시간이 지날수록 사용자의 요구사항은 더 많아지게 된다. Agile에서는 이러한 요구사항을 수용하기 위해서 요구사항들을 중요도, 개발 기간, 구체화 정도 등의 요소를 통해 순위를 매기고 이들 중 일부를 가지고 개발에 착수한다. 따라서 전체 요구사항을 모두 수집하는 방식에 비해 요구사항 분석이 짧다. 또한 요구사항의 개수가 적으므로 각 단계별 수행 시간도 짧아지게 된다. 이를 통해 프로세스의 기간을 단축시킬 수 있다.

이런 방식으로 1차 개발을 완료한 후 남아 있거나 새로 추가된 요구사항, 수정된 요구사항들을 모아 다시 같은 과정을 반복한다. 그리고 이 과정에서 소프트웨어 결과물은 항상 동작 가능한 상태를 유지한다.

Agile은 구현에서 테스트로 넘어가는 기간을 단축시켜 디버깅이 용이하게 해준다. 짧고 반복적인 개발을 통해서 전체 프로세스의 종료 시간을 예측하는데 도움을 준다. 새로운 요구사항이 나올 경우 다음번 주기에 바로 반영시킬 수 있으므로 고객 피드백이 빨라진다.

Posted by 이세영2
,

*이 글을 보기 전에

- "모든 악의 근원 : 불완전성의 원리"




어떤 사람들은 완벽하려고 하고, 어떤 사람들은 위험을 먼저 본다. 그리고 어떤 사람들은 위험보다 기회가 먼저 보인다.


불완전성의 원리를 증명한 쿠르트 괴델은 완벽해지려고 노력했던 사람인 듯 하다. 그리고 당대의 많은 수학자들은 수학의 완전성이 무너지는 것을 보고 그 분야를 연구하기를 외면했다.


하지만 지금 우리가 컴퓨터를 최초로 만든 사람들로 알고 있는 일련의 사람들은 그곳에서 기회를 찾았다. 이들이 불완전성을 무시했던 것은 아니다. 이미 불완전성은 다양한 형태로 소프트웨어 분야에서 명제화 되어 있다. 하지만 괴델이 증명에 사용했던 알고리즘의 시초에서 어떤 이들은 컴퓨터와 소프트웨어라는 자동 계산 기계에 대한 기회를 발견했다.


당시는 한창 2차 세계 대전이 진행 중이었고, 전쟁에서 이기기 위해 암호 해독, 무기 개발 등 다양한 연구를 수행하고 있었다. 연구를 위해서는 수학, 물리학 등 다양한 분야에서 당대 최고의 학자들을 모아 놓을 필요가 있었는데, 이들 중에 현대 컴퓨터 구조의 아버지라 불리는 폰 노이만이 있다.


그는 당대의 모든 학자들 중에서도 단연 최고의 천재라 불릴 만큼 대단한 사람이었다. 그에 대한 수많은 일화가 있고, 그것이 사실인지는 확인이 필요하지만 소프트웨어라는 것을 어떻게 생각했는지를 알 수 있는 일화가 하나 있다. 


컴퓨터는 만들어졌고, 컴퓨터를 구동시킬 알고리즘을 구현해야 했던 상황에서 당시에 활용할 수 있는 것은 0과 1로만 이루어진 기계어 뿐이었다. 폰 노이만의 제자들 중에서 기계어를 가지고 알고리즘을 구현하는 것이 무척 어려움을 느끼고 그보다 더 고급 언어(아마 어셈블리어 쯤 되지 않을까 한다)를 만들려고 하고 있었다. 고급 언어를 만들기 위해서는 언어를 정의하고, 정의된 형식대로 프로그램을 작성하고, 작성된 프로그램을 컴퓨터에 입력하고, 컴퓨터에 입력된 프로그램을 다시 기계어로 바꾸는 작업이 필요하다. 이러한 일련의 과정에는 컴파일러라는 프로그램이 필요하고 컴파일러는 컴퓨터의 컴퓨팅 능력이 없으면 동작하지 않는다.


요컨데 인간이 이해하기 쉬운 컴퓨터 언어를 만들어 사용하려면 컴퓨터의 연산 능력을 활용해야 한다는 의미이다. 폰 노이만이 이에 대해 한 말은 '완벽한 신의 언어가 있는데, 그걸 놔두고 저런 조잡한 걸 만들려고 하느냐?(위키)'였다.


개인적으로 저 일화가 진짜인지 확인하기는 어렵지만 일화가 알려주는 몇가지 중요한 문제들이 있어 짚어보고자 한다.


코드의 명료성

그렇다. 기계어는 인간이 이해하기 너무 어렵다. 얼마나 어렵냐면, 기계어로는 너무나 어려워서 기계어로 컴파일러를 만들고 조금 쉬운 언어를 정의해서(언어가 '조금' 더 쉬운 것이지 언어를 정의하는 일이나 새로 정의된 언어가 쉬운건 아니다) 그 언어를 컴퓨터에 입력할 수 있는 기계어 프로그램을 만들 생각을 하고 실천해야 했을 만큼 어렵다. 당시의 소프트웨어는 현대의 소프트웨어만큼 비 결정적이고 변덕스럽지 않다.(이 말의 의미는 잘 알고 있을 것이다. 현대의 소프트웨어게 사람들은 미래의 기대도 만족 시킬 수 있을 정도의 유연성을 요구한다. 미래의 기대는 아직 나타나지도 않았는데 말이다.) 폰 노이만과 같은 천재가 아니라면 기계가 바로 동작 시킬 수 있는 기계어로 현대의 소프트웨어를 만든다는 것은 불가능한 일이다.


이 일화에서 끄집어 내고 싶었던 부분은 범용 컴퓨터가 만들어진 시절부터 이미 코드의 이해 측면, 즉 인간이 이해할 수 있는 언어를 만들겠다는 생각이 존재했다는 점이다. 이것은 중요한 관점인데, 현대에도 잘 짜여진 코드의 중요한 덕목이 명료성이기 때문이다. 명료성이란 한마디로 말하자면 인간이 이해하기 편해야 한다는 특성이다. 어떻게 만들든지 기계어는 명료하지 않다. 현대의 언어로 만들어진 코드라고 해도 명료성을 추구하지 않으면 코드의 품질은 이루 말할 수 없이 저하된다. 하물며 기계어를 계속 다루었던 사람들이라면 (폰 노이만을 제외하고) 보다 고급 언어를 정의하여 사용하고 싶다는 욕구가 지금보다 더 강했을 것을 것이라 생각해 볼 수 있다.


현대의 언어를 접하면서 우리가 염두해 두어야 할 것은 언어의 문법을 이해하는 것보다 언어의 관점을 이해하는 것이 더 중요하다는 점이다. 문법은 우리에게 가능성을 열어준다. 문법이 정의되어 있지 않으면 우리는 어떤 생각을 코드로 표현할 수 없다. C언어에는 객체지향 문법이 없다. 따라서 C언어로 객체지향을 표현하기가 어렵다. 따라서 문법을 잘 알지 못하면 표현하고 싶은 것이 있어도 표현하지 못한다. 따라서 문법을 공부하는 것은 당연히 중요하다. 문제는 많은 이들이 문법을 공부하고 깊이 이해하면 이해할 수록 문법이 주는 자유에 빠져들어 간다는 것이다. 어떤 이들은 문법이 "허용된 자유의 범위"라고 생각한다. 마치 문법을 문자 그대로 "법"이라고 생각하는 것 같다. 그래서 법으로 정해진 범위 내에서 모든 것이 가능하다고 생각한다. 이러한 생각을 표현한 말이 "돌아가기만 하면 된다"는 말이다. 문법이 "법"으로써 강제하는 정도는 그리 심하지 않다. 개수를 나타내는 변수 이름을 count라고 쓰지 않는다고 해서 컴파일러가 문법 오류를 발생시키지 않는다. 그래서 어떤 사람들은 이것을 "자유"라고 칭하고 변수 이름을 a라고 짓는다. 이것은 인간의 인지 능력에 대한 테러에 가깝다.


문법을 "자유"라고 생각하는 것은 관점을 무시한 생각이다. 관점이란 언어를 디자인한 목적을 말한다. 현대의 많은 언어들이 객체지향 언어이다. 객체지향 언어는 문법도 상당히 어렵다. [상속에 대한 올바른 이해]에서도 이야기 했듯이 객체지향 언어를 처음 접한 이들은 문제 해결과 직접 연관이 없는 문법들을 공부하느라 머리가 아파진다. 하지만 그러한 문법이 왜 생겼는지에 대한 생각, 즉 언어가 드러내고자 하는 관점을 이해하지 않으면 문법도 이해하기 힘들다. 문법이 정해져 있다는 것은 일반 "법"과 마찬가지로 제약이 정해진다는 것이다. 제약이 정해졌다는 것은 "법"과 마찬가지로 해악을 바로잡겠다는 의지가 담긴 것이다. 인간의 의지가 담겨 있을 정도로 언어적 제약이 중요했기 때문이다.


그러면 문법에 어떤 의지가 담겨 있는 것일까? 딱 한가지 의지, 즉 관점만 이야기 해보자면 바로 "아무렇게나 한다고 모두 코드가 아니다"라는 관점이다. 어셈블리어를 정의한 이유는 기계어가 가졌던 완벽히 기계어적인 자유를 박탈해야 했기 때문이다. 0과 1로만 이루어진 코드가 가진 자유는 인간에게 허용되기에는 너무나 큰 자유다. 정확히 어떤 자유냐면 "인간이 이해하기 힘들 만큼 어려운 자유, 인간이 다루기에는 너무 이해하기 힘들 만큼의 자유"다. 불완전성의 원리에서 이야기 했듯이 불완전성을 다룰 수 있는 유일한 도구는 바로 인간의 두뇌다. 따라서 인간의 두뇌가 가진 인지 능력을 자꾸 벗어나려고 하는 코드는 제약을 가해 바로 잡을 필요가 있다. 어셈블리어의 관점이란 그것을 표현한 것이다.


그러면 C언어의 관점은 무엇일까? C언어는 구조적 언어의 관점을 포함하고 있다. 소프트웨어가 점점 커지고 복잡해질수록 거대한 소프트웨어를 부분으로 나누어 개발해야 한다는 생각이 자리 잡기 시작했다. 구조적 언어를 달리 표현하는 말이 "절차지향 언어"라는 말인데, 이 말은 구조적으로 분리된 부분들을 순서에 맞춰 수행시키겠다는 의미이다. 이것은 이전 언어인 어셈블리어가 가지지 못한 관점이다. 어셈블리어는 이해 측면에서는 기계어보다 나았을지 몰라도 역시 비 구조적 언어의 특성을 함께 가지고 있었다. 따라서 전체 소프트웨어가 부분 부분으로 분리되어 있지 않았고, 전체 소프트웨어의 절차, 즉 어떤 순서로 실행되는 것인지 이해하기가 어려웠다. 이러한 문제점을 보고 해결하려고 했던 노력이 담긴 것이 C언어이다. C언어는 문제를 함수라는 부분으로 나누고, 부분으로 나뉜 함수들을 어떤 순서로 실행할지를 정할 수 있다. 왜 이렇게 만들었을까? 언어가 발전해 온 이유, 즉 무엇인가를 왜 만들었을까에 대한 답은 항상 한가지이다. 인간이 이해하는데는 그렇게 만드는 것이 더 낫기 때문이다.


자 이제 말하고 싶었던 내용을 정리해 보면 이렇다. 소프트웨어를 만드는 프로그래밍 언어는 점점 더 인간이 이해하기 좋은 형태로 발전되어 왔다. 그 이유는 인간이 이해하지 않으면 소프트웨어를 관리하는 것이 불가능해지기 때문이다. 그래서 다시 언어의 관점을 정의해 보면 다음과 같다. 언어의 관점은 인간의 인지 능력을 보다 효율적으로 사용할 수 있도록 해야 한다는 것이다. 이것이 어셈블리어 같은 자연어 언어를 만들어 내고, C언어와 같은 구조적 언어를 만들어 내고, 객체지향 언어를 만들어 냈다. 함수형 언어나 스크립트 언어는 뭔가 특별한가? 아니다. 그것도 그들 나름대로 인간의 인지 능력에 최적화하려고 노력한 언어들이다. 어떤 부분에서는 인간이 신경쓰지 않아도 될 일들을 적절한 수준에서 감추었고, 어떤 부분에서는 기존의 언어들이 주지 못했던 유연성을 보충해 주었다. 이렇게 함으로써 인간이 해야 할 일과 인간이 하지 않아도 될 일들을 구분해 주었다. 인간이 해야 하는 일은 소프트웨어가 불완전성에 의해 오동작 하는 것을 방지하는 일이다. 인간이 하지 않아도 되는 일은 무의미한 반복, 비즈니스 로직과 상관 없는 코드의 갑작스런 출현 등이다.


비 구조적 언어

그렇다면 왜 처음부터 인간이 이해하기에 좋은 언어를 만들어 내지 못했을까?


폰 노이만은 괴델이 불완전성의 원리를 발표했던 장소에 있었다고 한다. 그리고는 "모든게 다 끝장 났다"고 말했다는 일화도 있다. 이 일화는 폰 노이만이 불완전성의 원리를 이해하고 있었다는 의미이다. 하지만 컴퓨터를 만들 생각을 한 걸 보면 그는 역시 기회를 더 많이 본 것이 아닐까 생각한다.


하지만 안타깝게도 그가 설계한 컴퓨터가 이해한 언어, 즉 기계어는 그 이후에 출현한 어셈블리어와 마찬가지로 비 구조적인 언어이다. 비 구조적 언어라는 것은 "구조가 없는 소프트웨어를 만들어 내는 언어", 즉 소프트웨어가 일정한 부분들로 분리될 수 없고 소프트웨어 전체가 하나의 단위로 만들어지는 언어라는 말이다. 현대에 소프트웨어를 공부하는 사람들이 이해하기 쉽게 설명하자면, 최근의 소프트웨어는 적어도 전체 소프트웨어를 다수의 변수와 다수의 함수로 구분시킬 수 있다. 객체지향 언어라면 객체 단위로도 구분지을 수 있다. 비 구조적 언어를 현대 언어로 개발하는 방식에 비유해 보자면 몇만 라인짜리 소프트웨어를 단 하나의 함수에 구현한 것이라고 볼 수 있다.(지금도 이렇게 하는 사람이 많지만...... 이렇게 만든 것이 쓸모 있다면 그를 폰 노이만으로 부르겠다) 지금은 몇 만 라인 코드가 흔하지만 당시에 언어는 기계어나 어셈블리어이다. 만만치 않은 일이었을 것이다. 그것도 함수 하나에다 구현하는 것은 더더욱 그렇다.


이렇게 된 데에는 두가지 원인이 있다. 


하나는 최초 괴델의 증명에 사용되었던 알고리즘의 시초는 구조적인 형태가 아니었다. 그것에 영감을 받아 만들어진 것이 폰 노이만의 컴퓨터이다. 그래서 소프트웨어를 구조적으로 만들어야겠다는 생각을 먼저 하지는 못했을 것이다. 최 우선으로 생각해야 할 것은 괴델이 만든 알고리즘이 동작 가능하도록 하는 것이었다. 그런 상황에서 소프트웨어의 머나먼 미래를 내다 보고 구조적 언어로 기계어를 설계한다는 것은 불가능했다.


두번째 원인은 폰 노이만의 천재성 때문이다. 앞서의 일화가 사실이라면 그에게는 기계어를 다루는 것이 현대의 고급 언어들을 다루는 일보다 쉬운 일이었을 것이다. 그런 그가 알고리즘을 구조적으로 구현할 필요성을 느끼지 못했을 것은 자명하다. 기계어로도 그런 일을 충분히 할 수 있는데 굳이 컴퓨터의 연산 능력을 사용해 가면서 컴파일이라는 비 생산적인 일을 해야 할 이유를 못 느꼈을 것이다. 어쩌면 컴퓨터의 구조는 단순한 것이 좋고, 인간이 이해하고 작성하기 편한 소프트웨어를 만드는 것은 컴퓨터의 연산 능력을 이용하는 편이 좋다고 생각했을지도 모른다. 하지만 아무래도 폰 노이만에게는 그런 생각은 없었을 것 같다.


사실 위의 두가지 원인은 결과를 놓고 유추해 본 것에 불과하다. 폰 노이만은 천재이면서도 인간에 대한 이해가 높은 사람이었다. 컴퓨터가 유용해지고 더 많은 사람들이 컴퓨터를 접하는 시기가 오면 소프트웨어도 보통의 인간들이 다룰 수 있는 수준이 되어야 할 것이라는 생각을 못하지는 않았을 것 같다. 그래서 다른 하나의 원인을 생각해 보자면 인간이 이해하기 쉬운 언어를 만들어 내는 일보다는 다른 일이 더 중요했고 시간이 부족했지 않을까 생각한다. 천재라고 해서 모든 일을 다 할 수는 없는 것이니 말이다.


소프트웨어의 예견된 위기

컴퓨터는 폰 노이만과 같은 천재들에 의해 만들어졌다. 그리고 암호 해독이나 무기 개발 등 다양한 분야에 필요한 계산 기능을 수행함으로써 그 유용성이 증명되었다. 그리고 컴퓨터가 계산한 결과로 만들어진 무기들이 정확하게 작동하는 것을 확인하면서 이제 사람들은 컴퓨터의 연산 능력을 계속 향상시켜서 더 많은 계산을 수행하게 만들어야겠다는 생각을 하게 된다. 폰 노이만 이외에도 수많은 천재 수학자, 물리학자들이 같은 일을 하고 있었지만, 그들 같은 천재는 세상에 그렇게 많지 않다. 


폰 노이만은 당시의 컴퓨터나 계산기들보다 더 빠른 계산을 할 수 있었다고 한다. 하지만 계산을 항상 천재들에게 의존하는 것은 효율적이지 못하다. 우선 인간은, 특히 그런 천재들은 돈만 준다고 해서 일하는 사람들이 아니다. 그들 나름대로의 지적 탐구 방향과 주어진 일이 맞아 떨어져야만 가능한 일이다. 그들도 의지를 가진 인간이기 때문이다. 하지만 컴퓨터에게는 그런류의 불확실성이 없다. 컴퓨터를 이용해서 더 많은 계산을 시키겠다는 것은 합리적인 생각이다. 비록 당대의 천재보다는 못했다 하더라도 기술을 계속 발전시키면 그렇게 되지 못할 것도 없었고, 실제로도 그렇게 되었다.


이처럼 컴퓨터의 유용성에 눈 뜬 과정은 상당히 짧았으나, 그와 함께 탄생했던 소프트웨어 기술의 중요성에 눈을 뜨기 시작한 것은 그보다 한참 후였다. 그 원인을 좀 생각해 보면 다음과 같다.


우선 초기 컴퓨터는 성능이 떨어졌기 때문에 지금처럼 거대한 소프트웨어를 만들 필요가 없었다. 컴퓨터를 이용하는 목적은 일반적인 사람들보다 더 빠른 공학적, 수학적 계산이다. 목적이 명확한 상태에서 성능이 부족한 컴퓨터에 작은 소프트웨어를 개발하는 것이다. 아마도 많은 소프트웨어가 단 한 사람의 개발자의 손에서 개발될 수 있었을 것이다. 이러한 환경에서는 굳이 컴파일러는 만들고 프로그래밍 언어에 대한 이론을 만들 필요가 없었다. 


두번째 이유는 초기 컴퓨터를 개발하고 소프트웨어를 개발하던 사람들이 당대 최고의 지성인들이었다는 점이다. 초창기 소프트웨어 개발자들의 전공은 다른 분야였겠지만 많은 사람들이 수학자나 물리학자, 공학자들이었고, 수학과 같은 논리적인 사고에 익숙했던 사람들이었다. 게다가 당시는 전시이다. 그들 중에서도 뛰어난 사람들만 끌어 모을 수 있었을 만큼 자본은 충분했다. 이런 이들이 모여 만든 소프트웨어다. 장담하건데 당시에는 소프트웨어에 작은 버그 하나만 나와도 수많은 천재들 앞에서 조롱을 당했을 것 같다. 이런 분위기라면 대부분의 소프트웨어가 완벽한 상태였을 것이고, 그런 상황에서 소프트웨어 기술을 개발한다는 것은 생각하기 힘들다.


하지만 시간이 지날수록, 즉 컴퓨터의 성능은 점차 좋아지고, 상업적인 이용 가치가 부각되면서 컴퓨터와 소프트웨어를 연구하는 사람들은 점점 더 많아지고, 이와 더불어 경쟁이 치열해 지면서 시간적 여유는 부족해지게 되었다. 특히 상업적으로 발전하게 된 상황을 보면 소프트웨어가 위기에 빠지게 된 이유를 이해할 수 있을 것이다.


최초의 컴퓨터가 군사적 목적이었다는 점은 자원이 풍부했다는 것을 의미한다. 소프트웨어에서 자원이 의미하는 것은 곧바로 인적 자원을 말한다. 즉 논리적 사고에 특화된 고급 인력들이 소프트웨어를 만들어 왔다. 하지만 컴퓨터와 소프트웨어 개발이 상업화 되면서 금전적 이득을 추구하게 되면서 초창기 우수한 인력들에 비해 낮은 비용으로 채용된 사람들이 그들의 자리에 들어 앉게 되었다. 이것은 그들과 현대의 수많은 소프트웨어 개발자들을 비하하고자 해서 하는 말이 아니다. 전체적으로 보면 소프트웨어 개발자 집단은 예전이나 지금이나 상당한 수준의 창의성과 지성을 갖춘 인재들이다. 하지만 상업적인 목적을 위해서 어느 정도 규모 이상의 인력을 확충하다 보면 아무래도 군사적 목적으로 끌어들인 최고의 인력들과 같은 수준의 비용을 지불하기 어렵고, 그러면 그렇게 뛰어난 사람을 모두 잡기는 어려웠을 것이다. 이 상황에서 나름대로 선발 기준을 잘 정립한다 해도 개중에는 질이 떨어지는 사람들이 끼어 들어오는 것을 막기가 힘들다. 그리고 그때나 지금이나 마찬가지로 소프트웨어는 개개인의 생산성을 측정하기가 상당히 어려운 분야이다. 대다수의 사람들이 창의성과 지적인 능력을 가지고 성실하게 일을 했다고 해도 문제의 대다수는 소수 인원의 무지에 의해서 발생한다. 그리고 이것이 상업적 목적, 즉 이윤 추구와 만나면서 개발 기간이 자주 축소되고, 요구사항이 자주 변경되며, 개개인의 사정에 의해 개발 인력이 자주 바뀌는 상황과 맞물리게 되면 아무리 뛰어난 인력이라고 해도 고전하는 것은 당연하다.


이렇게 고전할만한 요소들이 갖춰지자 이전에는 간과되었던 부분들이 중요한 부분으로 떠오르게 되었다. 소프트웨어도 하드웨어 개발에 준하는 개발 프로세스가 필요하다는 것, 소위 말하는 '통짜' 프로그램을 개발하는 것이 아니라 구조적인 개발, 즉 설계가 필요하다는 것, 그리고 설계의 의도를 반영할 수 있고, 이전의 비 구조적 언어의 관행을 돌아가지 않게 해 줄 단단한 문법을 갖춘 언어가 필요하다는 것이다.

Posted by 이세영2
,

불완전성의 원리는 쿠르트 괴델이 증명한 원리이다.


쿠르트 괴델이 불완전성을 증명한 과정은 BBC에서 제작한 "Dangerous Knowledge" 라는 제목의 다큐멘터리에 잘 나와 있다.


게오르그 칸토어는 미지의 대상이던 무한에 관한 이론을 정립하고자 노력한 수학자였다. 그는 무한과 무한과의 관계를 정립하는 과정에서 연속체 가설을 증명하고자 하였다. 이전까지는 수학이 논리적으로 완전하다는 의식이 팽배해 있었는데, 칸토어가 연속체 가설을 내놓은 이후부터 수학에는 논리적 완전성을 증명하기 난해한(당대까지는) 문제가 있음을 알게 되었다. 즉, 단편적인 논리적 사실은 증명이 되지만 전체 체계를 놓고 보면 완전함이 증명되지 않는 것이다.

이후 많은 수학자들이 어떤 논리적인 체계가 완벽하게 증명되기를 기대하면서 연구를 진행했는데(이 기대는 수천년간 수학의 논리적인 완전성을 믿어왔던 수많은 수학자들의 바램이기도 했다) 쿠르트 괴델도 그 중 한 사람이다.

하지만 수많은 수학자들의 바램과는 달리 괴델은 "불완전성의 원리"로 불리우는 반대 증명을 해낸다.


불완전성의 원리

정리1. 자연수의 사칙연산을 포함하는 어떠한 공리계도 무모순인 동시에 완전할 수 없다. 어떤 체계가 무모순이라면, 그 체계에서는 참이면서도 증명할 수 없는 명제가 존재한다.


정리2. 자연수의 사칙연산을 포함하는 어떠한 공리계가 무모순일 경우, 그 공리계는 자기 자신의 무모순에 대한 정리를 포함할 수 없다.



불완전성의 원리를 해석해보면 다음과 같다. (실제가 아닌) 순수 논리의 세계에서 어떤 논리 체계가 완전하게 참과 거짓으로 증명될 수 있느냐는 물음에 불완전성의 원리는 "그럴 수 없다"는 답을 내놓은 것이다. 특히 이 과정에서 괴델은 "스스로 증명(계산)하는 논리적 체계"를 고안해 내고 이를 통해 불완전성 원리를 증명해 냈는데, 이 체계가 우리가 알고 있는 "알고리즘"의 시초이다. 알고리즘과 불완전성의 원리가 내포하는 관계를 명확히 해보면 다음과 같다.


대응관계

알고리즘 : 스스로 계산하는 논리적 체계

불완전성의 원리 : 알고리즘은 스스로 논리적으로 완전함을 증명할 수 없다.



컴퓨터, 특히 소프트웨어의 시작을 알리는 이 대응 관계는 비참하게도 커다른 악과 함께 탄생한 셈이다. 어떤 알고리즘도 스스로 완전하다고 증명할 수 없다. "스스로 할 수 없다면 다른 알고리즘으로 증명하면 되지 않을까?" 하는 생각도 금방 깨지게 된다. 다른 알고리즘의 완전함을 증명할 방법이 없기 때문이다. 그래서 소프트웨어 영역에는 "알고리즘은 자기 스스로 논리적 완결성을 증명할 수 없다"는 명제가 있는 것이다.

만약 불완전성이 없다면, 즉 논리적 체계가 스스로 완전함을 증명할 수 있다면 소프트웨어가 의도한 대로 동작하는지를 인간이 증명해야 할 필요가 없었을 것이다. 그렇다면 인간은 어떻게든 스스로 증명할 수 있는 알고리즘을 구현만 한다면 그 후에는 오류나 버그가 나오지 않는다는 것을 확신할 수 있게 될테고, 현대 개발자들이 가지고 있는 불안감이 모두 해결될 것이다. 반대로 이야기 하자면, 불완전성의 원리 때문에 자동으로 소프트웨어의 완전함을 증명할 방법이 없어지고, 소프트웨어의 완전함을 관리할 수 있는 도구는 오직 인간의 두뇌 밖에 없다는 말이 된다. 이것이 악의 근원이 아니면 무엇이겠는가?


불완전성과 소프트웨어

불완전성의 원리는 이후 소프트웨어의 발전 과정과 현대에 개발된 소프트웨어 기술에 대한 미래, 그리고 소프트웨어가 가진 본질적인 특성을 규정하게 되었다. 이 부분은 추후 포스팅을 통해서 계속 다룰 예정이다.


한가지 명료하게 이야기 해 두자면, 불완전성의 원리는 소프트웨어의 탄생이자 성격의 규정자라는 사실이다. 우리는 소프트웨어 및 프로그래밍, 소프트웨어 설계나 개발 방법론들을 접하는데, 이들은 모두 불완전성이라는 악을 다스리는 기술이다. 그리고 우리가 프로그래밍을 공부하면서 받아 들이는 수많은 격언, 설계 원칙, 변수나 함수의 명명 규칙

과 같은 사소한 규정까지도 사실 불완전성에 기인하는 것이다. 


이미 소프트웨어로 생계를 꾸려 나가기로 결정한 개인이나 소프트웨어를 이용해서 돈을 벌고자 하는 기업에게 한마디 하자면 다음과 같다.


"You have a big problem!"


'1.프로그래밍 일반' 카테고리의 다른 글

소프트웨어 기술 트리  (0) 2016.08.19
비 구조적 언어와 예견된 위기  (2) 2016.08.13
인간의 능력과 소프트웨어  (1) 2016.08.07
좋은 코드가 갖춰야 할 요소  (0) 2016.08.07
좋은 코드란?  (0) 2016.08.07
Posted by 이세영2
,

소프트웨어를 개발할 때는 다음과 같은 전제가 있음을 미리 알아 둘 필요가 있다. 아래의 전제들을 무시하고 개발에 임하면 개발자 간의 협력이 어려워지고, 투입되는 비용과 인력이 증가하며 개발 기간은 길어지게 된다. 개발자라면 언제나 효율성을 추구해야 한다. 그리고 효율성을 측정하는 단위는 프로젝트다. 개인 단위가 아님을 명심해야 한다.


소프트웨어를 관리 할 수 있는 도구는 인간의 두뇌 뿐이다 

잘 이해가 되지 않는다면 "불완전성의 원리"를 읽어 보기 바란다.

소프트웨어는 스스로 잘 동작함을 검증할 수 없다.

검증용 소프트웨어가 다른 소프트웨어를 검증할 수도 없다.


관리 가능한 소프트웨어의 크기가 기회의 크기이다

갈수록 소프트웨어의 규모는 커져가고, 큰 소프트웨어를 관리할 수 있는 기술력이 있어야 시장에서 기회를 잡을 수 있다.


개인의 능력으로는 부족하다

시장에서 요구하는 소프트웨어를 혼자서 모두 개발할 수 있는 사람은 없다.

다수의 개발자가 만든 소프트웨어를 통합해야만 큰 소프트웨어를 만들 수 있다.

따라서 커뮤니케이션을 통한 개발자간의 협력은 필수 요소이다.


언어를 통한 소통은 부정확하다

인간의 언어는 코드보다 부정확하다.(기능에 대한 설명은 개요 이상에 대한 설명을 기대하기 어렵다.)

코드는 가장 정확한 진실을 담고 있다.

원칙적으로 코드를 통한 커뮤니케이션이 가능해야 한다.


인지 능력에는 한계가 있다(인지능력의 한계를 시험하는 예)

명칭과 실제의 부조화

동등한 기능에 대한 서로 다른 구조의 구현

중복된 코드

추상 레벨의 편차

기능 흐름의 분산

잦은 제어 분기

암기할 것이 많은 코드

합리성의 결여

예측 불가능


인간의 인지 능력에 맞는 코드를 작성해야 한다

코드는 인간의 인지 능력 내에서 관리 될 수 있어야 한다.

개인의 능력을 넘는 소프트웨어 작성을 위해서 코드는 커뮤니케이션 도구 역할도 수행해야 한다.

따라서 코드는 모든 인간의 인지 능력의 효율에 맞게 작성되어야 한다.


Posted by 이세영2
,

좋은 코드는 제품, 소프트웨어, 커뮤니케이션 도구로서의 특성을 모두 만족시켜야 한다. 이들 특성으로부터 좋은 코드가 갖춰야 할 요소를 뽑아 낼 수가 있다.


좋은 코드의 정의로부터는

명료성, 간결성, 유연성


켄트 벡의 구현 패턴에서는

커뮤니케이션, 단순성, 유연성



많은 개발자들은 이미 코드가 커뮤니케이션 수단이라는 데 동의하고 있다.
커뮤니케이션을 위해서나, 좋은 품질의 제품으로서의 특성을 위해서나 간결함은 꼭 갖춰야 할 요소이다.
유연성은 소프트웨어의 본질적인 특성으로서, 언제든지 수정이 용이한 상태로 유지되어야 한다.(유연성이 요구되지 않는 경우라면 좋은 코드의 특성이 아니라 다른 요소를 더 만족시켜야 할 것이다.)




Posted by 이세영2
,

좋은 코드는 다음과 같은 특성을 만족시켜야 한다.


제품으로서의 특성

제품은 좋은 품질을 가져야 하고, 품질을 명료하게 확인할 수 있어야 한다.
결점(버그)이 있을 경우 빠르게 해결할 수 있어야 한다.

소프트웨어의 특성

기능의 수정, 추가, 제거가 용이해야 한다.
항상 수정이 가능한 상태를 유지해야 한다.

커뮤니케이션 도구로서의 특성

개발자 간의 커뮤니케이션 도구로서 코드만한 것이 없다.
논리적이고 명료해야 한다.(= 내용이 장황하거나 불필요한 부분이 없고 중복되지 않아야 한다.)
좋은 글의 요건*을 갖추고 있어야 한다.

* 좋은 글의 요건

가치 있는 내용(contents)을 담고 있을 것.

내용이 분명하고 논리 정연할 것.

쉽고 간결할 것.


"코드는 컴퓨터나 동료가 아닌 작성자 본인과 먼저 대화한다"


Posted by 이세영2
,