잭 리브스는 소스 코드를 표현하는 다이어그램은 설계에서 부수적인 것일 뿐, 설계 그 자체는 아니라고 주장했다. 일련의 UML 다이어그램이 설계의 일부를 나타낼 수는 있지만 설계 자체는 아니다. SW의 설계는 추상적인 개념이다. 그리고 구체적인 모듈, 클래스, 메서드의 형태와 구조뿐만 아니라 프로그램의 전체 형태와 구조와도 관련되어 있다. 결국 이 설계의 최종적인 구현은 소스 코드가 된다. 결국 소스 코드가 설계이다.
소프트웨어는 상한 음식처럼 점점 부패하기 시작해서, 시간이 지남에 따라 부패 범위가 넓어지고 심해진다. 부패가 진행되면 진행될수록 유지보수하기가 어려워진다. 그리고 결국에는 재설계를 필요로 하게 된다.
하지만 이런 재설계는 성공하기가 어렵다. 기존 시스템은 계속 발전하고 변경되며, 새로운 설계는 그것을 쫓아가야하기 때문이다.
아래의 냄새가 나기 시작하면 소프트웨어가 부패하고 있는 것이다.
변경을 하려면 시스템의 다른 부분들까지 많이 변경해야하기에 변경하기 어려워지는 것을 뜻한다. 경직성을 가진 소프트웨어는 단순한 방법으로도 소프트웨어를 변경하기 어렵다. 실제로 그 원하는 변경 작업을 하면서 자신이 예상하지 못했던 변경의 간접적 영향이 있다는 사실을 알게 된다. 그러다가 결국 자신이 엄청난 양의 코드에서 변경할 부분을 쫓아다니고 있음을 깨닫게 된다.
변경을 하면 시스템에서 그 부분과 개념적으로 아무런 관련이 없는 부분이 망가지는 것을 뜻한다. 그리고 이런 문제를 고치려다 보면 더 많은 문제로 이어지는 경우도 많다. 취약성이 심해질수록, 어떤 부분의 변경이 예상하지 못한 문제를 불러일으킬 가능성이 점점 높아진다. 결국 고칠수록 점점 나빠지게 되는 것이다.
시스템을 다른 시스템에서 재사용할 수 있는 컴포넌트로 분리해내기가 어려운 것을 뜻한다. 다른 시스템에서 유용하게 쓸 수 있는 부분이지만 원래 시스템에서 그 부분을 분리하는 수고와 위험성이 지나치게 클 때 설계는 움직이게 할 수 없다.
옳은 동작을 하는 것이 잘못된 동작을 하는 것보다 더 어려워지는 것을 뜻한다. 이런 점착성은 소프트웨어 점착성과 환경의 점착성으로 나뉜다.
변경사항을 마주했을 때, 일반적으로 설계를 유지하는 방법 또는 그렇지 않은 방법(책에서는 이를 엉터리 방법이라 한다.)을 택하게 된다. 설계를 유지하는 방법이 그렇지 않은 방법보다 사용하기가 어렵다면, 설계의 점착성은 더욱 높아지게 되고 잘못된 동작을 하기는 쉽지만, 옳은 동작을 하기는 어려워진다. 개발자는 설계를 유지할 수 있도록 변경이 쉬운 소프트웨어를 설계하고 싶어한다.
개발 환경이 느리고 비효율적일 때 발생한다. 예를 들어, 컴파일 시간이 아주 길면 개발자는 재컴파일을 필요로 하지 않는 방식으로 변경을 하려할 것이다. 문제는 이런 변경이 설계를 유지시키지 않는 변경이여도 그렇게 하려한다는 것이다.
점착성이 있는 프로젝트는 소프트웨어의 설계를 유지하기 어려운 프로젝트이다.
직접적인 효용이 전혀 없는 기반구조가 설계에 포함되어 있는 것을 뜻한다. 현재 시점에서 유용하지 않는 요소가 설계에 포함되어 있다면, 그 설계는 불필요한 복잡성을 포함하고 있는 것이다. 이런 현상은 개발자가 변경을 미리 예상하고 잠재적인 변경을 처리하기 위해 소프트웨어에 기능을 집어넣을 때 자주 발생한다. 하지만 미래를 위해 했던 이 행동은 부정적인 효과를 나타내는 경우가 있다. 과도하게 미래를 위한 준비를 해놓은 프로젝트는 설계가 사용되지 않는 요소들로 어지러워진다. 결국 준비를 함으로써 얻는 효과보다 피해가 더 커지게 되는 것이다. 불필요한 복잡성이 있는 소프트웨어는 복잡하고 이해하기 어려워진다.
단일 추상 개념으로 통합할 수 있는 반복적인 구조가 설계에 포함되어 있는 것을 뜻한다. 같은 코드가 조금씩 다른 형태로 계속 반복되어 나타나면서, 개발자는 추상화된 개념을 잃게 된다. 반복된 부분을 찾아내고, 적절한 추상화를 통해 이를 없애는 일은 시스템을 이해하고 유지보수하기 쉽게 만드는데 큰 도움이 된다.
이런 반복은 시스템을 변경하기도 힘들게 하는데, 반복되는 단위에서 발견된 버그는 모든 반복 부분에서 고쳐져야한다. 심지어 반복 부분이 조금씩 전부 다른 경우 고치는 작업은 더욱 힘들어진다.
읽고 그 코드를 이해하기 어려운 것을 뜻한다. 코드는 시간이 지남에 따라 점점 불명료해지는 경향이 있다. 불투명성을 최소로 유지하기 위해서는 코드를 명료하고 표현적으로 유지하려는 지속적인 노력이 필요하다.
개발자가 처음 코드를 작성할 때는 스스로 명료한 코드라고 느낄 수 있다. 하지만 그것은 친말한 수준에서 그 코드를 이해하기 때문일 수 있다. 시간이 지나서 그 코드를 보면 매우 불명료하게 느낄 수 있다는 것이다. 즉 개발자는 읽는 사람의 입장에서 생각하고 자신의 코드를 리팩토링하는 것에 일치된 노력을 기울여야한다. 다른 사람이 자신의 코드를 검토하도록 하는 것도 매우 좋은 방법이다.
요구사항은 변경되기 마련이다. 자연스러운 것이다. 계속되는 요구사항 변경 때문에 설계가 실패한다면, 그 설계와 방식이 문제가 있는 것이다. 애자일 팀은 변경을 보람으로 삼는다. 시스템의 설계를 가능한 한 명료하고 단순하게 유지하고, 이것을 많은 단위 테스트와 인수 테스트로 뒷받침한다. 이런 작업들을 통해서 설계를 유연하고 변경하기 쉬운 탄력적인 설계를 만든다. 그리고 팀은 이런 유연성을 이용하여, 각 반복이 가능한 한 해당 반복의 요구사항에 가장 적합한 설계를 가진 시스템을 만들어내는 것으로 끝나게 한다.
- 우리는 변하는 요구사항의 세계에 살고 있고, 우리가 만든 소프트웨어가 이런 변화 속에서 살아남을 수 있게 만드는 것이 바로 우리가 해야하는 일이다.
- 팀은 설계를 개선할 수 있는 기회를 잡아 미래에 있을 비슷한 종류의 변경에도 탄력적이 되도록 만든다.
- 모듈을 수정하지 않고도 설계를 확장하도록 이끌 수 있는 OCP(개방 폐쇄 원칙)을 따르자.
- 처음부터 얼마나 어떻게 변경될 지 생각하지 않고, 가능한 가장 간단한 방식으로 구현하라. 그리고 요구사항이 변경된 다음에야 그와 같은 종류에 변경에 탄력적이 되도록 모듈의 설계를 바꿔라.
- 상위 수준의 모듈이 하위 수준의 모듈에 직접 의존하면 안된다. 그렇게되면 하위 수준의 세부 사항이 바뀔 때, 상위 수준의 정책도 영향을 받게 된다. 즉 상위 모듈은 하위 모듈이 어떤 일을 하는지 몰라야한다. 이런 상황에서는 의존성을 거꾸로 뒤집어서 상위 수준의 모듈이 더이상 하위 모듈에 의존하지 않도록 해야한다.
아래의 3가지 측면 사이에서 일어나는 상호작용이 바로 설계 작업이다.
개발자는 설계를 가능한 한 적절하고 명료한 상태로 유지하기 위해 애써야한다. 몇 주마다 한 번씩 설계를 청소하는 것이 아닌 매일, 매시간, 매분마다 소프트웽어를 가능한 명료하고, 간단하고, 표현적인 상태로 유지해야한다. 가장 작은 비트 단위의 부패라도 시작되도록 방치하면 나중에는 감당할 수 없을 만큼 위험이 커질 것이다.
설계는 명료한 상태로 유지되어야 하고, 설계의 가장 중요한 표현인 소스 코드 역시 명료한 상태로 유지되어야 한다.
애자일 설계는 과정이지, 결과가 아니다. 원칙, 패턴, 그리고 소프트웨어의 구조와 가독성을 향상하기 위한 방식의 연속적인 적용인 것이다. 앞으로 소프트웨어 설계의 원칙과 패턴을 자세하게 살펴볼 것이다. 그리고 이러한 원칙과 패턴은 매 주기를 거치면서 코드 및 코드가 포함하는 설계를 명료하게 유지하려는 시도의 일환으로 적용될 것이다.