Search
Duplicate
🪬

5. 구현 설계

구현에 대한 설계 작업은 단순히 상세 부분을 작성하기 전 의사코드로 클래스 인터페이스를 작성하는 것일수도 있고 클래스간 관계의 다이어그램을 그려보는 것일 수 있다.
설계는 매우 광범위한 주제라서 이 장에서는 그 일부만 다룬다. 훌륭한 클래스와 루틴 설계의 상당 부분까지 다루기에 너무 커서 떨어져 나갔다. 이들은 다음, 다다음장에 알아본다.

1. 설계의 어려움

개요
소프트웨어 설계는 컴퓨터 소프트웨어에 대한 명세를 동작 가능한 소프트웨어로 변환하기 위한 계획에 대한 구상이나 창작, 도구를 의미한다.
설계는 요구사항을 코드 작성과 디버깅에 연결하는 작업이다. 훌륭한 상위 수준 설계는 여러 개의 하위 수준 설계를 무리 없이 담을 수 있는 구조를 제공한다.
훌륭한 설게는 규모가 큰 프로젝트에선 꼭 필요한 작업이며 작은 프로젝트에선 유용한 작업이다.
이 절에서는 설계가 가지고 있는 어려움을 통해 설계가 가지는 특징들을 알아보자.
설계는 불명확한 문제다.
호스트 리텔과 멜빈 웨버는 불명확한 문제의 정의를 전체 혹은 일부를 해결해야만 정의할 수 있는 문제라고 했다.
본질적으로 역설적인 구석이 있는 이 문장은 문제를 명확하게 정의하려면 문제의 답을 한 번 내려보아야 하며 작동하는 솔루션을 만들기 위해 다시 문제를 해결해야 한다는 의미를 가지고 있다.
이러한 프로세스는 소프트웨어 개발에서 수십 년 동안 전해져온 오랜 관습이다.
불명확한 문제의 가장 극단적인 예로 무너진 타코마 다리 설계 사례를 예로 들 수 있다.
다리를 지을 당시 가장 중요하게 고려했던 고려 사항이 무게를 견디기 위한 견고함을 유지하는 것이었으나 바람이 예상보다 많이 불어 요동치다 결국 무너지게 되었다.
이는 불명확한 문제 규명의 대표적인 예라고 할 수 있다. 왜냐면 다리가 붕괴될 때까지 엔지니어는 공기 역학을 그 범위까지 적용해야하는지 알지 못했기 때문이다.
결국 다리를 한 번 지어보고서야 공기역학도 추가로 고려해야한다는 사실을 배웠다.
코딩테스트를 풀어내는 것과 개발자로써 프로그램을 만드는 것의 주요한 차이점은 코딩테스트는 문제점이 명확하다는 점이다.
설계는 엉성한 프로세스다(결과는 정돈되었을지라도)
완성된 소프트웨어 설계는 정돈되어 있고 깔끔해 보여야 하지만 설계 과정은 최종 결과물만큼 깔끔하지 않다.
설계하면서 잘못된 길로 들어서는 경우도 많고 실수도 많이 하기 때문에 설계는 뭔가 엉성하다.
사실 실수를 하는 것이 설계의 핵심이다. 설계 단계에서 실수하고 고치는 것이 똑같은 실수를 코드 작성 후에 발견하여 코드를 수정하는 것보다 비용이 적다.
또한 설계는 충분하다라는 그 기준선을 알기 어렵기 때문에 엉성해보이기도 한다.
설계는 어디까지 구체적이어야 할까? 설계를 공식 설계 표기법을 얼만큼 적용해 작성하고 얼마 정도를 코드 작성의 몫으로 남겨두어야 할까?
설계를 언제 끝낼 것인가? 설계는 완성되는 시점이 분명하지 않으므로 이 질문의 가장 흔하면서도 적절한 대답은 최대한 정보를 얻고 더이상 선택을 미룰 수 없을 때다.
설계는 절충과 우선순위의 문제다.
이상적인 세계라면 모든 시스템이 즉각적으로 실행되어 요청을 처리하고 저장 공간따윈 필요없으며 비용같은 건 생각하지 않아도 될 것이다.
현실 세계는 냉혹하다. 설계자의 주요 업무는 서로 상충하는 설계 특징을 비교하여 그 특성들 사이의 균형을 맞추는 일이다.
개발 일정이 좀 더디더라도 응답 속도가 중요하다면 설계자는 그에 적합한 설계를 선택할 것이다.
개발 일정을 빠르게 처리하는 것이 더 중요하다면 좋은 디자이너는 그 목적에 맞게 설계를 변경할 것이다.
설계에는 제약이 따른다.
설계의 핵심은 어느 정도는 가능성을 만들고 어느 정도는 가능성을 제한하는 것에 있다.
물리적으로 구조물을 짓는데 필요한 시간과 자원, 공간이 무한정 존재한다면 꿈만 같을 것이다.
결국 자원들은 제한적이기 때문에 좀 더 간결하고 공학적인 해결방안을 선택할 수 밖에 없고 그 결과 비용측면에서는 더 나은 해결책이 나온다.
소프트웨어의 설계의 목표도 마찬가지다.
설계는 비결정적이다.
세 사람에게 같은 문제의 해결책을 설계해보도록 시켜보면 서로 완전히 다른 설계를 내놓을 것이다. 그렇지만 모두 쓸만할 것이다.
어떤 목적을 달성하는 방법은 한 가지 이상일 수 있지만 컴퓨터 프로그램을 설계하는 방법은 보통 수십 가지에 이른다.
설계는 발견적 학습 과정이다.
설계는 비결정적이기 때문에 설계 기법은 예상된 결과를 만들어내는 반복적인 처리 과정이라기보다는 발견적 학습이라고 할 수 있다.
설계에는 시행착오가 따른다. 불명확한 문제기 때문에, 어떤 작업에서 효과가 있던 설계 툴이나 기법이 다음 프로젝트에서는 무용지물일 수도 있다. 은탄환은 없다.
설계는 창발적이다.
설계의 이런 특성들은 설계가 창발적이라는 말로 깔끔하게 요약할 수 있다.
설계는 누군가의 머릿속에서 완전한 형태를 갖추고 짠하고 등장하지 않는다. 설계는 설계 검토와 격식없거나 있는 토론, 코드 작성 경험, 코드 수정 경험을 통해 진화하고 발전한다.
실질적으로 모든 시스템은 초기 개발 과정에서 어느 정도의 설계 변경을 의무적으로 거치게 되고 그 후 보통 애초에 예상했던 것보다 훨씬 많은 내용이 변경된다.
이 변경사항을 어느정도까지 수용할 것인가는 만드는 소프트웨어의 종류에 따라 달라진다.

2. 핵심 설계 개념

개요
핵심 개념을 잘 이해하고 있어야 훌륭한 설계가 가능하다.
이 절에선 복잡성 관리, 바람직한 설계의 특징, 설계 수준에 대해서 설명한다.
소프트웨어의 주요 기술적 의무: 복잡성 관리
본질적 어려움과 비본질적 어려움
No Silver Bullets: Essence and Accidents of Software Engineering 논문을 작성한 브룩스는 소프트웨어 개발이 어려운 이유는 본질적이고 우연에 기인하는 서로 다른 종류의 문제 때문이라고 주장했다.
먼저 브룩스는 아리스토텔레스 시절의 철학에서 두 용어를 가져왔다.
먼저 철학에서 본질적인 속성이란 어떠한 사물이 그러한 사물이 되기 위해서 반드시 가져야하는 속성이다.
예를 들어 어떠한 물건을 자동차라고 부를 수 있으려면 엔진, 바퀴, 문이 있어야 한다. 이들 중 하나라도 빠지면 그것은 자동차라고 부를 수 없다.
다음으로 비본질적인 속성은 특정 사물을 지칭할 때, 그것이 무엇이라고 결정하는 데 영향을 끼치지 않은 우연의 산물이다.
4기통 터보 엔진 V8 자동차든 다른 엔진이 장착된 자동차든 엔진의 종류와 상관없이 그것은 자동차다.
자동차는 문이 2개, 4개일 수 있고 바퀴는 스키니, 매그일 수 있다. 이와 같은 구체적이며 세부적인 사항들은 모두 비본질적인 속성이다.
비본질적인 속성은 부수적이고 임의적이며 추가적이고 우연한 것으로 생각할 수도 있다.
브룩스는 소프트웨어에서의 중요한 비본질적인 어려움이 오래 전에 해결되었음을 발견했다.
브룩스는 소프트웨어에 남아 있는 본질적인 어려움은 느리게 해결될 수 밖에 없다고 주장한다.
소프트웨어 개발이 갖는 본질적인 어려움은 복잡하고 무질서한 현실 세계와 상호작용하고 종속 관계와 예외 상황들을 정확하고 완벽하게 규명해야 하며 대충이 아니라 정확한 솔루션을 설계해야하기 때문이다.
이 모든 본질적인 어려움의 근원은 본질적이고 비본질적인 복잡성 때문이다.
복잡성 관리의 중요성
만약 프로젝트가 기술적인 이유로 실패한 경우에는 그 원인을 복잡성 관리 부족에서 찾을 수 있다.
소프트웨어는 무슨 일이 일어나는지 아무도 모를 정도로 그 복잡성이 증가할 수 있다.
프로젝트에서 한 영역의 코드를 변경했을 때 다른 부분에 어떤 영향을 미칠 것인지 완벽하게 이해하는 사람이 하나도 없다면 더 이상 개발을 할 수 없는 지경에 이른 것이다.
복잡성 관리는 소프트웨어 개발에서 가장 중요한 기술적 주제다.
다익스트라는 현대 컴퓨터 프로그램을 보관할 수 있을 정도로 큰 두뇌를 가진 사람은 존재하지 않다고 지적했다.
이 말은 곧 소프트웨어 개발자가 전체 프로그램을 억지로 머릿속에 밀어 넣으려고 해서는 안 된다는 뜻이다.
따라서 한 번에 한 부분을 제대로 집중할 수 있게 프로그램을 구성하도록 노력해야 한다.
목표는 한 번에 생각해야 하는 프로그램의 크기를 최소화하는 것이다.
소프트웨어 아키텍처 수준에서는 시스템을 서브시스템으로 나누어 문제의 복잡성을 줄인다.
인간은 하나의 복잡한 부분보다 여러 개의 간단한 정보를 더 빠르게 이해한다.
모든 소프트웨어 설계 기법의 목표는 복잡한 문제를 간단한 문제 여러개로 만드는 것이다.
⇒ 어디서 본것 같지 않나요
서브 시스템이 독립적일수록 한 번에 복잡한 부분의 일부에 집중하기 더 쉽다. 객체를 신중하게 정의함으로써 한 번에 하나의 기능에만 집중할 수 있도록 작업을 나눈다.
루틴을 짧게 유지하면 신경 쓸 부분이 줄어든다. 저수준의 상세 구현보다는 문제 범위면에서 프로그램을 작성하고 추상성을 최대화하여 작업하면 뇌에 부담이 줄어든다.
결론적으로 인간의 선천적인 한계를 보완할 줄 아는 개발자는 자신뿐만 아니라 다른 사람도 이해하기 쉽고 오류가 적은 코드를 작성한다.
복잡성을 해결하는 방법
생산성이 떨어지는 설계는 다음과 같은 상황에서 발생한다.
간단한 문제를 복잡하게 해결할 때
복잡한 문제를 단순하고 잘못된 방법으로 해결할 때
복잡한 문제를 부적절하고 복잡하게 해결할 때
오늘날의 소프트웨어는 태생적으로 복잡할 수 밖에 없고 현실 세계의 문제가 가지고 있는 본질적인 복잡성을 피할 수 없다.
다음은 복잡성을 관리하는 두 가지 방법이다.
두뇌가 한 번에 처리해야 하는 본질적인 복잡성의 양을 최소화하라.
비본질적인 복합성이 불필요하게 증가하지 않도록 하라.
일단 소프트웨어에서 복잡성을 관리하는 것이 중요함을 알고 있자.
바람직한 설계의 특징
복잡성 최소화
설계의 일차적인 목표는 먼저 복잡성을 최소화하는 것이어야 한다.
간단하고 이해하기 쉬운 설계를 만들어라. 특정한 부분을 살펴보고 있을 때, 개념적으로 전혀 상관없는 다른 영역도 살펴보아야 한다면 이는 좋지 않은 설계다.
유지보수의 편리함
다시보아도 읽기 쉽다는 설계라는 것이다.
내 코드를 처음 보는 신입 개발자가 물어볼 만한 질문을 계속 떠올려 본다. 내 시스템이 그에게 어렵지 않게 쉽게 이애할 수 있도록 설계한다.
느슨한 결합
느슨한 결합은 프로그램의 각 연결을 최소화하도록 설계하는 것을 의미한다.
클래스 사이의 연결을 최소화하기 위해 인터페이스를 이용해 추상화, 캡슐화, 정보 은닉과 같은 방법을 사용한다. 연결을 최소화하면 통합, 테스트, 유지보수 시 비용을 최소화할 수 있다.
확장성
확장성이란 내부 구조를 변경하지 않고 시스템의 기능이 확장될 수 있다는 뜻이다.
다른 부분에 영향을 미치지 않고 시스템 일부분을 변경할 수 있어야 한다.
재사용성
재사용성은 현재 시스템의 일부를 다른 시스템에 사용할 수 있도록 시스템을 설계하는 것을 의미한다.
높은 팬인
높은 팬인은 특정 클래스를 사용하는 클래스의 수가 많다는 것을 의미한다.
높은 팬인은 시스템이 유틸리티 클래스를 잘 활용하게끔 설계되었다는 것을 의미한다.
낮은 팬아웃
낮은 팬아웃은 특정 클래스가 다른 클래스를 적게 사용한다는 것을 의미한다.
높은 팬아웃은 한 클래스가 다른 클래스를 여럿 사용한다는 것을 의미하며 따라서 지나치게 복잡한 로직을 담당하고 있는 가능성이 높다.
이식성
이식성은 시스템을 다른 환경으로 쉽게 이동시킬 수 있도록 설계하는 것을 의미한다.
간결성
간결성은 불필요한 부분이 없게 시스템을 설계하는 것을 의미한다.
계층화
계층화는 시스템에 대해 특정한 계층에서 바라보고 일관되게 이해할 수 있도록 분산 계층을 유지하는 것을 의미한다.
시스템을 다른 계층을 보지 않고도 특정 계층에서 볼 수 있도록 설계해야 한다.
표준 기법들
시스템이 색다른 방법에 의존하면 할수록 복잡도가 커진다. 표준화되고 일반적인 접근 방법을 사용해 전체 시스템이 친숙하다고 느껴지게 만들려고 노력해야 한다.
설계 수준
하나의 시스템에도 여러 상세한 수준이 존재하는데, 각 상세 수준마다 설계가 필요하다.
몇몇 설계 기법은 모든 수준에 적용할 수 있고 몇몇은 한두 가지 경우에만 적용 가능하다.
수준 1: 소프트웨어 시스템
첫 번째 수준은 전체 시스템이다.
시스템 수준은 서브시스템이나 패키지 같이 상위 수준의 클래스 조합을 충분히 생각하는 데 도움을 준다.
수준 2: 서브시스템이나 패키지로 분할
이 수준의 설계에서 얻게되는 주요 산물은 모든 중요한 서브시스템을 식별하는 것이다.
서브시스템엔 데이터베이스나 사용자 인터페이스, 비즈니스 로직, 명령 해석기, 보고서 엔진 등이 있다.
프로그램을 주요 서브시스템으로 어떻게 나눌 것인지, 각 서브시스템이 다른 서브시스템을 어떻게 의존할 것인지 결정하는 것이 주요 설계 작업이다.
서로 다른 서브시스템이 어떻게 소통할 것인지에 대한 규칙을 정하는 것이 중요하다. 모든 서브시스템이 다른 모든 서브시스템과 소통할 수 있다면 그것들을 굳이 분리할 이유가 없다.
커뮤니케이션을 제한하여 각 서브시스템의 분리를 정당하게 하라.
서브시스템이 무분별하게 다른 서브시스템과 커뮤니케이션한다면 어떤 문제가 생길까? 다음 예시들을 생각해보자.
개발자가 UI를 그리기 위해서 전체 시스템에서 얼마나 많은 부분을 알고 있어야 할까?
다른 시스템에서 비즈니스 규칙에 접근하여 뭔가를 사용한다면 어떤 일이 벌어질까?
당연한 말이지만 서브시스템 간의 커뮤니케이션은 서로 알 필요가 있을 때만 가능하게 만든다. 확신이 서지 않는다면 처음엔 커뮤니케이션을 제한하고 나중에 필요한 경우 완화하는 것이 낫다.
상호 연결 관게를 이해하고 유지하기 쉽게 하려면 내부 서브시스템의 관계를 간단하게 만든다.
가장 간단한 관계는 하나의 서브시스템이 다른 서브시스템에 있는 루틴을 호출하는 것이다.
가장 복잡한 관계는 한 서브시스템에 있는 클래스가 다른 서브시스템에 있는 클래스를 상속하는 것이다.
범용적으로 적용할만한 규칙은 시스템 수준의 다이어그램이 순환 구조를 갖지 않는 것이다.
공통적인 서브 시스템의 종류는 다음이 있다.
비즈니스 규칙
비즈니스 규칙은 컴퓨터가 풀어내는 문제이자 비즈니스에서 차별점을 만들어낼 수 있는 부분이다.
사용자 인터페이스
사용자 인터페이스가 프로그램의 나머지 부분에 지장을 주지 않고 발전할 수 있도록 독립적인 서브시스템을 개발한다.
데이터베이스 접근
데이터베이스 접근에 대한 구현 세부 사항을 감춰 프로그램 대부분이 저수준 구조체를 다루기 위한 복잡한 세부 사항에 의존하지 않게 만들고 비즈니스 수준의 매핑으로 처리될 수 있게 한다.
구현 세부 사항을 감춘 서브시스템은 프로그램의 복잡성을 줄여주는 유용한 추상화 수준을 제공한다.
시스템 의존성
하드웨어의 의존성을 패키지화하는 것과 같은 이유로 운영체제에 대한 의존성을 서브시스템으로 패키지화한다.
수준 3: 클래스로 분할
이 수준의 설계에서는 시스템에 필요한 모든 클래스를 구체화하는 작업이 들어간다.
예를 들어 데이터베이스 서브 시스템은 데이터베이스 메타데이터뿐만 아니라 데이터 접근 클래스와 지속성 프레임워크 클래스로 나뉠 수 있다.
각 클래스가 시스템의 나머지 부분과 상호작용하는 방법에 대한 세부적인 사항도 클래스에 명시되어야 한다.
특히 클래스의 인터페이스가 정의된다. 전반적으로 이 단계에서 가장 중요한 작업은 개별적인 클래스로 구현할 수 있을만큼 저수준으로 서브시스템을 분해하는 것이다.
프로젝트의 규모가 크다면 서브시스템을 클래스로 나누는 작업이 필요하다.
클래스와 객체
객체지향 설계에서 가장 핵심적인 개념은 객체와 클래스의 차이를 이해하는 것에 있다.
객체는 실행 중인 프로그램에 존재하는 구체적인 엔티티다.
클래스는 프로그램 코드로, 정적이지만 객체는 프로그램을 실행할 때 생성되는 특정한 값과 속성을 가지는 동적인 것이다.
데이터베이스 용어에 익숙하다면 스키마와 인스턴스의 차이를 떠올리면 된다.
수준 4: 메소드로 분할
이 단계에서는 클래스를 메소드로 즉 동작으로 나눈다.
수준 3에서 정의한 인터페이스가 몇 가지 메소드를 정의한다. 여기선 클래스의 비공개 메소드를 상세히 설계한다.
클래스 내부에 있는 메소드의 구현 부분을 살펴보면 간단한 메소드도 많지만 계획적으로 구성되어 있어 추상적이므로 좀 더 구체적인 설계가 필요한 메소드가 있는 것도 알 수 있다.
클래스의 메소드를 완전히 정의하는 작업을 통해서 인터페이스에 대해 잘 이해하게 되면 인터페이스의 변경도 필요한 경우도 생긴다.
수준 5: 내부 메소드 설계
이 단계의 설계에서는 각 메소드의 상세한 기능을 구현한다.
내부 메소드 설게는 일반적으로 개별적인 메소드를 개발하는 각 개발자의 몫이다. 설계는 의사코드를 작성하고 책이나 인터넷을 참고해 코드 단락을 어떻게 구성할 것인지 결정하고 코드를 작성하는 활동으로 구성된다.

3. 설계 빌딩 블록: 휴리스틱

소프트웨어 설계에는 정답이 없으므로 좋은 소프트웨어 설계를 위해서는 발견적 학습을 효과적으로 적용해야 한다.
휴리스틱이었다.. 발견적 학습이..
여기서 의미하는 휴리스틱은 소프트웨어의 패턴을 경험적, 직관적 학습을 통해 체계화하는 것이라고 생각한다.
현실 세계의 객체를 찾아라.
설계의 대안을 규명하는 접근 방법으로 객체지향적 접근 방법이 가장 널리 알려져있으며 이는 현실 세계의 객체와 가상의 객체를 찾는 것에 초점을 맞춘다.
객체를 설계하는 단계는 다음과 같다.
객체와 객체의 속성(메소드와 필드)를 식별한다.
각 객체가 무엇을 할 수 있는지 결정한다.
각 객체가 다른 객체에 무엇을 할 수 있는지 결정한다.
각 객체에서 다른 객체에 노출할 부분을 결정한다.
각 객체의 공개 인터페이스를 정의한다.
객체와 객체의 속성을 결정한다.
프로그램은 현실의 문제를 풀어낸다. 따라서 대개 현실 세계의 엔티티에 기반을 둔다. 따라서 먼저 객체를 식별한다.
예를 들어 시간별 청구 시스템은 현실 세계의 직원, 고객, 청구서에 기반을 둔다.
객체를 식별하고 나면 객체의 속성을 찾는 것은 크게 어렵지 않다. 각 객체는 프로그래밍 언어에서 정의한 타입의 속성을 갖는다.
각 객체가 무엇을 할 수 있는지 결정한다.
각 객체에서 어떤 작업을 수행할 수 있는지 확인해보자.
청구 시스템에서 직원 객체는 청구율이나 청구를 수행하며 고객 객체는 청구서 배달 주소를 변경할 수 있을 것이다.
각 객체가 다른 객체에 무엇을 할 수 있는지 결정한다.
통상 객체가 서로에게 할 수 있는 두 가지는 포함과 상속이다.
어떤 객체가 어떤 객체를 가지고 있는가?(속성으로 가질 수도 있고 어떠한 동작의 매개변수로써 가지고 있을수도 있지 않을까~)
어떤 객체가 어떤 객체로부터 상속받을 수 있는가?
청구서 객체는 고객 객체에게 전달되어 고객 객체는 해당 청구서에 대한 요금을 지불할 수 있다.
각 객체에서 다른 객체에 노출할 부분을 결정한다.
설계 시 객체의 어느 부분을 공개하고 어느 부분을 비공개로 유지해야 하는지를 파악하는 것은 가장 중요한 결정 중 하나다.
속성, 메소드 모두에 이러한 사항을 결정해야 한다.
각 객체의 인터페이스를 결정한다.
각 객체에 대해 형식적이고 이해하기 쉬운 프로그래밍 언어 수준의 인터페이스를 정의한다.
해당 객체가 다른 모든 객체에 노출하는 속성과 메소드를 public이라 한다.
객체가 상속을 통해서 결합하고 있는 객체에 노출하는 부분을 객체의 protected 인터페이스라고 한다.
일관성 있게 추상화하라.
다른 수준에 있는 서로 다른 세부 사항을 다루지만 세부 사항을 무시해도 문제가 없는 개념과 맞물리는 능력을 추상화라고 한다.
집합체라는 개념을 도입해 작업할 때 추상화를 통해 작업하고 있다고 말할 수 있다.
어떤 물체를 유리, 나무, 못의 결합이라고 하지 않고 집이라고 부른다면 추상화한 것이다.
집이 모여있는 집합을 집의 집합이라 하지 않고 마을이라 한다면 그 또한 추상화한 것이다.
훌륭한 개발자는 인터페이스 수준, 클래스 수준, 패키지 수준의 단계적인 추상화를 제공함으로써 더 빠르고 안전하게 프로그래밍할 수 있게 해준다.
구현 세부 사항을 캡슐화하라.
추상화 다음으로 캡슐화를 진행한다.
추상화는 객체를 상위 수준에서 바라볼 수 있도록 하고 캡슐화는 다른 수준에선 해당 객체를 특정한 수준 이상으로 알지 못하게 하는 것이다.
캡슐화는 집의 외관을 볼 수 있지만 집 내부의 가구는 무엇이 있는지 알 수 있을 정도로 접근할 수는 없다는 개념이다.
캡슐화는 복잡한 부분을 보지 못하게 함으로써 복잡성을 관리하는데 도움을 준다.
상속은 설계를 단순화할 수 있을때 사용하라.
상속은 적절히 추상화하였을 때, 해당 추상화에 변경이 발생할 가능성이 없을 때 사용하자.
정규직과 계약직은 직원이라는 기본적인 속성에 계약 기간만 다른 정도다. 따라서 사용해도 적합하다.
상속은 객체지향 프로그래밍에서 가장 강력한 도구로 잘 사용하면 엄청난 이득을 보지만 제대로 사용하지 못하면 생산성이 크게 떨어진다.
비밀을 숨겨라(정보 은닉)
정보 은닉은 구조적 설계와 객체지향 설계 모두에 있어서 기본적인 부분이다.
구조적인 설계에서는 블랙박스라는 개념이 정보 은닉에 해당한다.
객체지향적인 설계에서는 정보 은닉을 위해 캡슐화와 모듈성 개념이 생겼고 이는 추상화 개념과 연결된다.
정보 은닉은 비밀이라는 개념을 특징으로 하는데, 이는 소프트웨어 개발자가 설계나 구현에서 나머지 프로그램으로부터 한 부분을 숨기기로 결정하는 것이다.
정보 은닉은 복잡성을 감추는 데 중점을 두고 있기 때문에 소프트웨어의 주요 기술적 의무에 특히 강력한 발견적 기법이다.
비밀과 프라이버시
정보 은닉 관점에서 클래스들은 그것을 다른 클래스로부터 숨기는 설계나 구현으로 특정지어진다.
그 비밀은 변경 가능성이 높은 영역일수도 구현 상세일 수도 있다. 클래스의 역할은 이러한 정보들을 숨기고 프라이버시를 보호하는 것이다.
시스템에서 발생한 사소한 변경사항은 클래스 내부의 메소드에 영향을 미칠 수는 있지만 해당 클래스의 범위를 벗어나는 범위까지 영향을 미쳐서는 안 된다.
클래스는 가능한 내부 작업을 드러내지 않아야 한다. 좋은 클래스는 빙산의 일각과 같아서 클래스 대부분을 노출하지 않는다.
정보 은닉의 예
특정 객체의 속성 중 id가 존재한다고 할 때, 이 객체의 생성 메소드를 감추지 않는다면 무슨 일이 벌어질까?
해당 id를 생성하는 로직이 여기저기 퍼져있게 될 것이고 유지보수가 어렵게 될 것이다.
은닉의 두 가지 부류
정보 은닉에서 비밀은 다음과 같은 두 개의 그룹으로 나뉜다.
특별하게 관심이 없는 경우에 고민할 필요가 없도록 복잡성을 감추는 것
변경이 발생했을 때 그 효과가 일부에만 영향을 미치도록 변경의 원인을 감추는 것
정보 은닉의 장애물
정보의 지나친 배분
이는 시스템 전체에 정보를 지나치게 배분하는 것이다.
시스템 전체에 리터럴 100을 코드로 사용하는 것보다 MAX_EMPLOYEE와 같은 상수에 이 정보를 숨겨서 한 곳에서만 값이 변경되게 하는 것이 좋다.
순환 의존성
순환 의존성도 정보 은닉을 불가능하게 만든다. 이는 일반적으로 A 클래스가 B 클래스의 메소드를 호출하고 클래스 B가 클래스 A의 메소드를 호출하는 것이다.
의존성 순환을 피해야 한다. 그러한 구조는 어느 한쪽이 준비되기 전까진 둘 다 테스트할 수 없으므로 테스트하기 어렵게 만든다.
전역 데이터로 오해받는 클래스 데이터
객체의 값을 전역에 두지 말자, 전역 변수를 사용하면 지옥으로 가는 급행열차를 타는 것이다.
다른 메소드가 전역 데이터를 다루고 있다는 사실을 알지 못한 채 전역 데이터에 접근하는 경우 문제가 될 것이다.
전역 데이터를 다루고 있다는 사실을 알고는 있지만 정확하게 무엇을 하는지 알지 못하는 경우 문제가 될 것이다.
성능 손해
아키텍처 수준과 코드 작성 수준에서 성능 손해를 피하기 위한 시도일 것이다.
어느 경우에도 걱정할 필요가 없다. 아키텍처 수준에서는 정보 은닉을 위한 시스템 설계가 문제되지 않는다.
코드 작성 시 데이터에 간접적으로 의존하는 과정에서 발생하는 메소드와 객체 생성이 성능 손해가 발생할 것이라는 걱정이다.
하지만 이는 너무 성급한 것이다.
시스템의 성능을 측정하여 병목이 발생하는 위치를 정확하게 찾기 전까지 코드 수준에서 성능 개선을 미리 대비하는 방법은 코드를 가능한 모듈화하는 것이다.
정보 은닉의 가치
정보 은닉을 사용한 큰 프로그램이 그렇지 않은 프로그램보다 4배나 수정하기 쉽다.(Korson and Vaishnavi 1986).
게다가 정보 은닉은 구조적인 설계와 객체지향적인 설계 모두에 있어서 기본적인 부분이다.
정보 은닉은 고유한 특성에 기반을 두고 발견적인 방법으로 좋은 설계를 돕는다.
이 클래스에서 무엇을 숨겨야 하는가라고 묻는 것이 인터페이스 설계 문제를 해결하는데 가장 중요하다.
무엇을 숨겨야 하지라고 질문하는 습관을 갖자 설계에 관한 결정하기 어려운 문제의 상당수가 해결될 것이다.
변경될 것 같은 영역을 찾아라
변경의 효과가 한 메소드나 클래스, 패키지에 제한되도록 불안정한 영역을 고립시키는 것이 목표다. 다음은 이를 달성하는데 도움을 주는 단계다.
1.
변경될 것처럼 보이는 항목을 찾는다.
2.
변경될 것 같은 항목을 분류한다.
3.
변경될 것처럼 보이는 항목을 분리시켜 제한한다.
다음은 변경될 가능성이 큰 영역이다.
비즈니스 규칙
하드웨어 의존성
입출력
표준을 따르지 않는 언어
어려운 설계 및 구현 부분
상태 변수
데이터 크기 제약
변경의 정도 예측하기
시스템에 대한 잠재적인 변경에 대해 생각할 때 변경의 효과나 범위가 변경이 발생할 가능성에 비례하도록 시스템을 설계한다.
변경이 잦을 경우, 그 범위는 제한시키는 것이 좋다. 그렇지 않다면 큰 영역에 영향을 끼쳐도 괜찮다.
변경될 것 같은 영역을 식별하는 좋은 기법은 우선 프로그램에서 사용자에게 쓸모가 있는 최소한의 부분을 파악하는 것이다.
해당 부분이 시스템의 비즈니스 로직을 많이 가지고 있을 가능성은 작다.
다음으로 시스템에 대한 최소한의 변경 사항이 무엇인지 정의한다.
핵심적인 부분을 먼저 식별함으로써 어떤 컴포넌트가 추가되는 부분인지 알 수 있고 거기서부터 개선할 내용을 추정하고 숨길 수 있다.
결합을 느슨하게 유지하라
결합은 클래스나 메소드가 다른 클래스의 루틴과 얼마나 밀접하게 연관되어 있는지를 기술한다.
결합을 느슨하게 유지하려는 목표는 다른 클래스나 메소드와 작고 직접적이며 눈에 띄고 유연한 관계를 갖는 클래스를 만드는 것으로 이를 느슨한 결합이라고 한다.
이 장에선 모듈을 클래스와 메소드의 집합이라고 표현하고 있다. 하나의 인스턴스화 된 클래스나 클래스 개념 관점에서 보면 될 것 같다.
하나의 모듈이 다른 모듈에 의해 쉽게 사용될 수 있을 정도로 느슨한 상태가 훌륭한 결합 형태다.
즉 분해가 어렵지 않아야 한다. 얼마든지 대체될 수 있으며 클라이언트 코드가 사용하는 형태여야 한다.
결합의 기준
크기
크기는 모듈 사이의 연결 횟수를 의미한다. 결합에서는 작은 인터페이스를 갖는 모듈이 다른 모듈에 연결하기가 상대적으로 쉽기 때문에 가능하면 작은 것이 좋다.
가시성
가시성은 두 모듈 간의 연결이 얼마나 명시적인지를 읨한다. 프로그래밍은 가능한 연결 부분을 눈에 띄게 만들어야 좋다.
유연성
유연성은 얼마나 쉽게 모듈 사이의 연결을 변경할 수 있는지를 의미한다.
한마디로 어떤 모듈이 다른 모듈을 호출하는 게 쉬울수록 결합은 더 느슨해지는데, 이렇게하면 유연성은 커지고 유지보수하기도 쉬워진다.
시스템 구조를 만들 때는 상호 연결을 최소화하도록 프로그램을 나누는 것이 좋다.
결합의 종류
간단한 데이터 매개변수 결합
두 모듈 사이에서 전달되는 모든 데이터가 기본 데이터형이고 모든 데이터가 매개변수로 전달된다면 두 모듈은 단순-데이터-매개변수로 결합된 것이다.
이러한 종류의 결합은 자연스럽고 허용할 수 있다.
간단한 객체 결합
모듈이 특정 클래스를 인스턴스화한다면 그 모듈은 객체에 단순-객체로 결합된 것이다.
이러한 종류의 결합은 좋다.
객체 매개변수 결합
객체 1이 객체 2에게 객체 3을 달라고 요구한다면 두 모듈은 서로에 대해서 객체-매개변수로 결합한 것이다.
이런 종류의 결합은 객체 2가 객체 3의 정보를 알고 있어야 하기 때문에 결합이 좀 강하다.
의미론적인 결합
한 모듈이 다른 모듈의 프로그래밍 요소를 이용하지 않고 다른 모듈의 내부 작동에 대한 논리적인 지식을 사용할 때다.
가장 좋지 않은 결합이다.
사용된 모듈의 코드를 변경했을 때, 사용하는 모듈에 어떻게 영향을 끼칠지 런타임까지는 알지 못하기 때문에 매우 위험하다.
일반적으로 널리 사용되는 디자인 패턴을 찾아라
패턴은 스스로의 설계로는 얻을 수 없는 여러 이점을 제공한다.
패턴은 이미 만들어진 추상화를 제공함으로써 복잡성을 줄인다.
패턴은 일반적으로 널리 사용되는 해결책의 세부 사항들을 규정함으로써 오류를 줄인다.
패턴은 대안을 제시함으로써 발견적 학습의 가치를 제공한다.
패턴은 설계에 대해 수준 높은 논의를 할 수 있게 해서 의사소통을 원활하게 한다.
전반적으로 디자인 패턴은 복잡성을 관리하기 위한 강력한 도구다.
다른 휴리스틱
응집력을 강하게 하라
응집력은 좋은 구조적인 설계의 결과로 얻을 수 있고 대개 결합과 같은 맥락으로 다뤄진다.
응집력은 클래스에 있는 모든 메소드나 메소드에 있는 코드가 얼마나 밀접하게 클래스의 책임을 지원하고 있는지 그 클래스가 얼마나 해당 책임에 집중되어 있는지를 나타낸다.
매우 연관성이 높은 기능들을 포함하고 있는 클래스를 응집력이 강하다고 말하며 생산성을 좋게하는 클래스들은 보통 이런 응집력이 높다.
계층을 만들어라
계층은 단계식 정보 구조로 가장 일반적이거나 추상적인 항목이 최상위에 위치하고 점차 상세하고 구체적인 항목이 낮은 수준에 위치한다.
계층은 소프트웨어의 주요 기술적 책임이자 의무인 복잡성 관리를 달성하는데 요긴한 도구다. 계층을 이용하면 현재 계층에서 다루는 책임에만 집중할 수 있기 때문이다.
클래스 계약을 형식화하라
더 상세한 수준에서는 각 클래스의 형식을 나머지 프로그램과의 계약으로 이해하는 것이 도움이 된다.
어떠한 특징을 가진 그들이 뭔가를 제공했을때 이 클래스의 메소드는 이렇게 하기로 했다.와 같이 말이다.
메소드 시그니처를 계약이라고 생각해도 될 것 같다. 적어도 이론상으로 객체는 계약에 없는 행위를 무시해도 되기 때문에 복잡성을 관리하는 데 유용하다.
책임을 할당하라
객체에 어떻게 책임을 할당하는 것인가 생각하는 방법이다.
객체가 무엇을 책임져야 하는지 질문하는 것은 어떤 정보를 감추고 어떤 정보를 드러낼 것인가를 묻는 것과 유사하지만 책임을 묻는 것이 더 추상적인 답을 제공해 특별한 발견적 가치를 부여한다.
테스트가 가능하도록 설계하라
테스트가 용이한 시스템이 무엇인지를 고민한다면 좋은 설계를 만들 수 있다.
테스트는 일반적으로 독립되어 수행되기 때문에 각 영역을 고립시키는 좋은 설계를 만들어낼 수 있다.
실패를 피하라
설계자가 다른 성공적인 설계 사례의 특성을 단순히 따르기만 한다면 이는 실패하게 될 가능성이 있다.
실패에 대해서 고민해야한다. 성공의 특징에 의존하는 것은 내게 주어진 요구사항과 제약사항들을 충분히 고려하지 않은 선택이니까.
결합 시점을 의도적으로 선택하라
결합 시점은 특정 값이 변수에 결합하는 시점을 의미한다. 초기에 결합하는 코드는 쉽지만 유연성이 떨어진다.
다음과 같은 질문을 통해 좋은 설계에 대한 통찰력을 얻어보라.
이 변수를 초기에 결합한다면 어떻게 될까?
이 값들을 나중에 결합한다면 어떻게 될까?
이 테이블을 코드에서 초기화하거나 실행 시 사용자로부터 변수의 값을 얻는다면 어떻게 될까?
제어 지점을 정하라
제어는 클래스와 메소드, 전처리기, 유틸리티 파일에 집중될 수 있다.
어느 것이 흐름을 제어할 지 결정하라. 이는 복잡성을 줄게 해준다. 복잡성이 줄어들면 흐름을 파악하기 위해 찾아야하는 코드가 줄고 더 쉽고 안전하게 변경할 수 있다.
브루트포스 기법의 사용을 고려하라
가장 강력한 휴리스틱 도구 중 하나는 브루트포스다.
우아한 해결책은 해당 해결책이 모든 부분을 커버할 수 있는지 사고해야하는 시간이 필요하므로 보통 오래 걸린다.
다이어그램을 그려라
다이어그램 또한 강력한 휴리스틱 도구 중 하나다.
천 마디 말보다 그림 한 장이 낫다. 그림을 사용하면 높은 추상화 수준에서 문제를 표현할 수 있으므로 이해가 쉬워진다.
모듈화를 유지하라
모듈화의 목표는 각 루틴이나 클래스를 블랙박스처럼 만드는 것이다.
무엇이 들어가고 무엇이 나올지를 알지만 안에서 무슨 일이 일어나는지 알지는 못해야 한다.
모듈화 개념은 정보 은닉과 캡슐화, 다른 설계에서의 휴리스틱과 관련이 있다.
설계의 휴리스틱에 대한 요약
다음은 주요한 설계의 휴리스틱에 대한 요약이다.
현실 세계의 객체를 찾아라.
일관성 있는 추상화를 구성하라.
세부 사항을 캡슐화하라.
가능할 때 상속하라.
비밀을 숨겨라(정보 은닉).
변경될 것 같은 영역을 규명하라.
느슨한 결합을 유지하라.
일반적으로 널리 사용되는 디자인 패턴을 찾아라.
다음 휴리스틱 역시 때때로 유용하다.
응집력을 강하게 하라.
계층을 만들어라.
클래스 계약을 형식화하라.
책임을 할당하라.
테스트가 가능하도록 설계하라.
실패를 피하라.
결합 시점을 의식적으로 선택하라.
제어 지점을 정하라.
브루트포스 기법의 사용을 고려하라.
다이어그램을 그려라.
모듈화를 유지하라.
휴리스틱을 위한 지침
문제 해결을 위한 휴리스틱에 대한 초창기 책 중 하나로 어떻게 문제를 풀 것인가(G. Polya 저)라는 책이 있다.
폴리아가 일반화한 문제 해결 접근 방법은 수학 문제를 해결하는 데 초점을 맞추었다.
그 내용은 다음과 같다.
1.
문제를 이해한다.
2.
계획의 고안
3.
계획의 실행
4.
검토
해당 내용중 가장 효과적인 지침 중 하나는 한 가지 접근 방법에 매달리지 말라는 것이다.
설계 문제 전체를 한 번에 해결할 필요는 없다. 작업이 막히면 잠시 숨을 고르고 현재 상황을 돌아본다. 그리고 내가 해당 문제를 지금 풀기에는 충분한 정보가 부족하다는 것을 인식하자.
나중에 해결될 수 있는데 왜 아까운 자원을 낭비해야할까. 문제는 그 문제를 해결할 필요가 있을때 해결하는 것이다.

4. 설계 실천법

개요
이 절에서는 종종 좋은 결과물을 산출할 수 있는 단계인 경험적 설계 방법을 소개한다.
반복
설계는 반복적인 프로세스다. 보통 한 번 작성해둔 코드는 다시 한번 읽게 되어있다.
설계 후보를 여러번 훑어보고 여러 가지 방법을 시도해보면 상위 수준과 하위 수준, 모든 관점에서 바라볼 수 있게 된다.
상위 수준 문제를 다루면서 얻는 큰 그림은 거시적 관점에서 하위 수준의 세부 사항을 작성하는 데 도움이 될 것이다.
하위 수준 문제를 다루면서 얻게 되는 세부 사항은 미시적 관점에서 상위 수준의 문제를 더 현실적으로 설계하는데 도움이 될 것이다.
최상위 수준과 최하위 수준의 고려 사항을 각자 조율하는 것은 좋은 행동이다. 이는 전체를 하향식, 또는 상향식으로만 흐름을 두는 것보다 안정적인 강화 구조를 만든다.
처음 설계가 충분히 좋아보여도 멈추지 말라. 두 번째 시도는 보통 첫 시도보다 좋고 설계를 반복할 때마다 전체 설계를 향상시킬 수 있다는 것을 배우게 된다.
분할과 정복
다익스트라가 말했듯 복잡한 프로그램의 모든 세부 사항을 기억할만큼 똑똑한 사람은 없으며 설계도 마찬가지다.
프로그램을 서로 다른 관심 영역으로 나눠두고 각 부분을 개별적으로 처리하도록 한다.
점증적인 개선은 복잡성을 다룰 수 있는 강력한 도구다. 문제를 이해하고 계획을 수립하여 수행하고 어떻게 했는지를 검토하도록 한다.
하향식과 상향식 설계 접근 방법
이 접근 방법은 객체지향적인 설계를 작성하는 데 도움이 되는 통찰력을 제공한다.
하향식 설계는 높은 추상화 수준에서 시작한다. 기본 클래스나 구체적이지 않은 다른 설계 요소를 정의한다.
이는 하위 클래스 규명하기와 추상 클래스가 가져야할 작업들, 다른 세부적인 설계 요소 등 상세화 수준을 높일 수 있게 한다.
상향식 설계는 구체적인 것부터 시작해서 일반적인 쪽으로 이어진다. 구체적인 객체를 규명하는 것부터 시작해서 그 내용으로부터 객체와 기본 클래스 집합을 일반화한다.
두 설계는 개념적으로 대립되기에 각 설계가 좋다고 하는 진영이 존재한다. 다음은 양측 주장이다.
하향식 접근 방법을 지지하는 주장
인간의 두뇌가 한 번에 집중할 수 있는 영역과 그 양에 한계가 있으므로 추상적인 개념부터 시작하면 두뇌가 한 번에 너무 많은 세부 사항을 처리하지 않아도 된다는 장점이 존재한다.
상향식 접근 방법을 지지하는 주장
하향식 접근 방법은 추상적이기 때문에 너무 어렵다. 더 가시적으로 작업해야 한다면 상향식 설계 접근 방법이 좋다고 주장한다.
다음은 상향식 구성을 할 때 기억해야할 사항이다.
시스템이 무엇을 해야 하는지 알고 있는지 스스로 묻는다.
그 질문으로부터 구체적인 객체와 책임을 파악한다.
공통적인 객체를 식별하고 서브시스템 구조나 패키지 객체 내에서의 합성, 상속 등 적합한 것을 사용해 그 객체들을 그룹짓는다.
다음 상위 수준에서 작업을 계속하거나 최상위로 돌아가 다시 하위 수준으로 작업을 진행한다.
사실상 논쟁은 불필요하다.
하향식과 상향식의 가장 큰 차이는 분해 전략이냐 결합 전략이냐다.
각각 장점과 단점이 있고 이들은 서로 다른 상황에서 무엇을 선택해야 하는지 어렵지 않게 해준다.
하향식은 쉽다. 그리고 세부 사항 구현을 미룰 수 있다.
상향식은 필요한 유틸리티 기능을 초기에 파악할 수 있어서 간결하고 잘 구성된 설계가 만들어진다는 것이다.
다만 이 접근 방법만 사용해서는 개발이 쉽지 않다. 특정 영역에만 집중된 클래스 여럿이 하나의 응집도를 가진 시스템을 만들어내는 것은 그 확률이 매우 낮다.
둘은 배타적인 전략이 아니고 상호 보완이다. 적절한 상황에 적절한 접근법을 선택하라.
실험적인 프로토타이핑
구현 세부 사항을 제대로 이해하기 전까지 설계가 적합한지 알 수 없는 경우가 있다.
성능 목표를 만족하는지 등 말이다.
이들은 최소한의 부분을 해결하지 않고서는 설계 문제를 완전하게 정의할 수 없다.
적은 비용으로 이런 문제를 해결하는 기법이 실험적인 프로토타이핑이다.
협력적인 설계
설계에서는 조직 구성이 공식적이든 비공식적이든 둘이 하나보다 낫다. 협력은 다음과 같이 다양한 형태를 취한다.
비공식적으로 동료에게 다가가 아이디어를 생각해 보자고 요청한다.
동료와 함께 회의실에 앉아서 화이트보드에 여러 설계 아이디어를 그려본다.
동료와 함께 키보드 앞에 앉아 사용하는 언어로 세부적인 설계를 수행한다.
하나 이상의 동료와 디자인 아이디어를 검토할 수 있게 회의 일정을 정한다.
공식 검토를 수행한다.
자신의 작업을 검토해줄 수 있는 동료가 없다면 초기 설계 작업을 서랍 속에 넣어 놓고 일주일이 지난 후 다시 살펴본다.
조직 외부에 도움을 요청한다. 커뮤니티에 질문을 올린다.
설계를 어디까지 해야 할까?
구현 시작 전 설계를 어디까지 해야 하는지, 설계를 문서화하기 위해서 설계를 얼마나 형식화해야 하는지 결정하는 공식은 없다.
팀의 경험과 시스템의 예상 수명, 신뢰도, 프로젝트와 팀의 크기가 모두 고려되어야 한다.
요소
설계 수준
문서화 형식
팀이 경험이 많은 편
팀이 경험은 많지만 이번 스택의 경험은 적은 편
팀이 경험이 적은 편
중상
중하
팀의 이직률이 높은 편
-
프로그램이 높은 안정성을 요구
프로그램이 특수 업무용
중상
프로젝트가 큰 편
프로젝트가 작은 편
소프트웨어의 예상 수명이 짧은 편(몇 주, 몇 달)
소프트웨어의 예상 수명이 긴 편(몇 달, 몇 년)
설계 작업 기록하기
설계 작업을 기록하는 전형적인 방법은 형식적인 설계 문서를 작성하는 것이다.
하지만 규모가 작거나 비형식적인 프로젝트와 같인 설계를 가볍게 기록해도 되는 프로젝트라면 여러 방법으로 설계를 기록할 수 있다.
설계 문서를 코드 자체에 넣는다.
핵심적인 설계 결정 사항들을 코드 주석으로 문서화하는 방법이다.
설계에 대한 논의와 결정을 위키에 기록하라.
프로젝트 구성원이라면 누구나 쉽게 편집, 접근할 수 있는 웹 페이지 모음이다.
이메일로 요약하라.
설계 논의가 종료된 후, 해당 내용에 대한 요약을 누군가에게 보내라.
디지털카메라를 사용하라.
설계를 위해 사용했던 메모지나 화이트보드를 찍어서 보관하라라는 의미다.
설계 플립 차트를 보관하라.
큰 플립 차트를 그리고 이를 보관하는 방법이다.
클래스, 책임, 협력자 카드(CRC)를 사용하라.
각각의 카드 위에 설계자가 클래스의 이름과 책임, 협력자를 기록한다.
적절한 상세 수준에서 UML 다이어그램을 작성하라.
UML은 설계 엔티티와 관계에 대한 형식적인 표현 방법을 풍부하게 제공한다.
영속성 수준에서도 비즈니스 규칙 수준에서도 사용할 수 있는 적절한 방법같다.
이러한 기법들은 어느 하나만 선택하는 것이 아닌 조합되어 사용ㅎ 가능하므로 혼합하여 사용하도록 하자.

5. 잘 알려진 방법론에 대한 의견

요즘은 모든 것을 설계하라에서 아무것도 설계하지 말라쪽으로 분위기가 변했다.
여기에 대한 저자의 의견은 선행 설계를 적게하거나 현재 정보로 판단하기에 적절한 수준까지의 설계만 진행하라는 것이다.
YANGNI가 생각났다.
어느 정도가 충분한지 어떻게 알 수 있을까? 그건 내가 판단하는 것이다. 얻을 수 있는 정보들로
다음 두 설계는 분명히 잘못된 것이다.
모든 세부 사항까지 설계하는 것
전혀 설계하지 않는 것
설계 방법을 적용하는 데 있어 하나만 고집한다면 해결할 수 있는 문제는 줄어든다.
첫 설계에 안주하지 말라. 협력하라. 단순함을 추구하라. 필요할 때 프로토타입을 만들라. 반복하고 반복하고 또 반복하라. 그러면 만족스러운 설계가 나올 것이다.

참고 자료

다음 자료들은 각 영역에 있어서 추천하는 자료나 서적들이다.
일반적인 소프트웨어 설계
Weisfeld, Matt. The Object-Oriented Thought Process, 2d ed
객체지향을 소개하는 책이다. 입문자에게 유용하다.
Riel, Arthur J. Object-Oriented Design Heuristics. Reading
클래스 수준에서의 설계에 초점을 뒀다.
Plauger, P. J. Programming on Purpose: Essays on Software Design
이 책에서 플로거는 다양한 설계 접근 방법을 소개한다.
Meyer, Bertrand. Object-Oriented Software Construction, 2d ed
Raymond, Eric S. The Art of UNIX Programming. Boston, MA: Addison-Wesley, 2004
유닉스 관점에서 소프트웨어 설계를 바라보는 책이다.
Larman, Craig. Applying UML and Patterns: An Introduction to Object-Oriented Analysis and Design and the Unified Process, 2d ed
디자인 패턴
Gamma, Erich, et al. Design Patterns. Reading, MA: Addison-Wesley, 1995
Shalloway, Alan, and James R. Trott. Design Patterns Explained
일반적인 설계
Adams, James L. Conceptual Blockbusting: A Guide to Better Ideas, 4th ed.
효과적인 설계를 위한 사고의 종류의 예제나 창조적인 사고 프로세스에 관해서 설명하는 책이다.
Polya, G. How to Solve It: A New Aspect of Mathematical Method, 2d ed
수학적인 문제를 해결할 때 휴리스틱을 사용하는 방법에 대해서 기술되어 있다.
Michalewicz, Zbigniew, and David B. Fogel. How to Solve It: Modern Heuristics.
비수학적 예제를 포함해 폴리아의 책을 향상된 방법으로 다루고 있다.
Simon, Herbert. The Sciences of the Artificial, 3d ed. Cambridge, MA: MIT Press, 1996
생물학, 지질학 등 자연을 다루는 과학과 경제학, 컴퓨터 과학 등 인공저긴 세상을 다루는 과학을 구별한다.
설계 과학에 중점을 두면서 인공 과학의 특징에 대해서 설명한다.
Glass, Robert L. Software Creativity. Englewood Cliffs, NJ: Prentice Hall PTR, 1995.
설계를 중심으로 소프트웨어의 본질에 대해서 논의한다.
Petroski, Henry. Design Paradigms: Case Histories of Error and Judgment in Engineering
성공 못지 않게 실패를 통해서도 성공적인 설계를 배운다는 주장을 설명하기 위해 토목 공학 분야로부터 결론을 끌어낸다.
표준
IEEE Std 1016-1998, Recommended Practice for Software Design Descriptions
이 문서는 소프트웨어 설계 기술에 대한 IEEE-ANSI 표준을 포함한다.
소프트웨어 설계 문서에 무엇이 포함되어야 하는지를 기술한다.
IEEE Std 1471-2000. Recommended Practice for Architectural Description of Software Intensive Systems
이 문서는 소프트웨어 아키텍처 명세를 생성하기 위한 IEEE-ANSI 지침이다.

요점 정리

소프트웨어의 주요 기술적 의무는 복잡성을 관리하는 것이다. 이것은 단순함에 초점을 맞춘 설계로 달성할 수 있다.
단순함은 다음 두 가지 일반적인 방법으로 달성할 수 있다.
한 번에 뇌에서 처리해야하는 본질적인 복잡성의 양을 최소화하는 것
부수적인 복잡성이 불필요하게 증가하지 않도록 하는 것
설계는 휴리스틱이다. 한 가지 방법론만 고집하면 독창성과 프로그램에 해가 된다.
좋은 설계는 반복적이다. 여러 번 시도할수록 점차 개선된다.
정보 은닉은 매우 유용한 개념이다. 무엇을 숨겨야 하지라는 질문은 해결하기 어려운 설계상의 문제를 해결해준다.