1. Design Challenges
software design이란 computer software를 운영 소프트웨어로 전환시키는 일종의 컨셉을 의미한다. Design을 잘해야 codding과 debuging을 쉽게 할 수 있으며, 대규모 프로젝트에는 필수적이다. design에 있어 challenge에 대해 알아보자.
- Design Is a Wicked Problem : wicked problem이란, 문제의 정의가 불명확하며, 해결책의 평가가 복잡하고, 모든 문제가 서로 얽혀 있으며, 문제를 정의하는 과정이 곧 해결의 일부일때 나타난다. 대표적으로 타코마 다리의 붕괴(1976)이 그러하다. 타코마 다리가 "공명" 현상에 약하다는 문제점은, 타코마 다리가 붕괴된 이후에야 파악되었다. 그 이후에야 다른 다리를 짓거나, 다리를 보수공사하면서 공명에 대한 문제를 해결하기 위해 노력할 수 있었다.
따라서 design이 wicked하다는 것은, 우리가 완벽하게 설계했다고 생각해도, 프로젝트를 진행하거나, test하면서 생각하지 못했던 문제들이 생성될 수 있다는 것이다.
- Design Is a Sloppy Process
Design이라는 것은, 항상 순탄하지 않다. 마치 안대를 끼고 골목을 지나가는 것과 같다. 왜 그럴까?
- 좋은 솔루션과 나쁜 솔루션은 의외로, 굉장히 사소한 차이에서 발생한다.
- 프로젝트의 진행중에는 지금 하는 디자인이 "충분히 좋은지" 판단하기 어렵다.
따라서, 저자는 어차피 디자인은 힘든 과정이므로 중간에 실수를 발견하면서 꾸준히 수정하는 것이 중요하다고 강조한다.
- Design Is About Tradeoffs and Priorities
- Design Involves Restrictions
- Design Is Nondeterministic
- Design Is a Heuristic Process - 디자인 역시 construction처럼, heuristic하다. deterministic하지 "않기" 때문이다. 따라서 완벽한 tool이란 존재하지 않으며, 시행착오법만이 유일한 디자인의 길이라고 다시한번 강조한다.
- Design Is Emergent
2. Key Design Concepts
이 장에서는 design에서 유용한 key concepts에 대해 설명한다.
2-1. Software's Primary Technical Imperative: Managing Complexity
Managing Complexity는 Fred Brooks의 유명한 논문(No Silver Bullets:Essence and Accidents of Software Engineering) 에서 나온 개념이다.
- Accidental / Essential Difficulties란?
Brooks는 SW 개발을 어렵게 만드는 두 가지 문제로 essential 과 accidental problem을 거론했다. 이는 사실 아리스토텔레스부터 시작된 철학적인 개념인데, 먼저 essential problem(필연적 문제)이란 그 프로젝트에서 반드시 있어야 할 것에서 발생하는 문제이다. 예를 들어, 차를 만들기 위해서는 엔진과, 바퀴와, 문을 달아야 한다. 이에 해당하는 것이 essential problem이다. 반면 Accidental problem(우연한 문제)이란, 반드시 발생하지는 않지만 프로젝트를 진행하면서 세부 사항에서 발생하는 문제를 일컫는다. 예를 들어, 자동차에서는 문을 네 개를 달 수도 있지만, 두 개만 달아도 된다. 즉 accidental problem이란, incidental하고, discretionary하고, optional하고, happenstance한 특징이 있다.
Brooks는 accidental problem에 대한 보완은 역사적으로 잘 이루어져 왔다고 주장한다. 예를 들어 assembly 언어에서 보였던 어려운 문법은 high-level language가 발전하면서 개선되었고, noninteractive computer에 대한 문제도, OS 수준에서 time-sharing processing을 지원하면서 해결되어왔다. 그러나, essential problem은 그 문제들이 복잡하고(intricate), 서로 상호작용(interlocking) 하기 때문에 개선 속도가 느리다. 만약 현재 개발 생태계의 문제점들을 잘 해결한 solution이 개발된다 하더라도, 그 solution의 개발에 의해 생기는 또 다른 문제들이 생겨나기 때문에, essential problem은 본질적으로 해결하기 어렵다.
2-2. Importance of Managing Complexity
프로젝트의 실패 원인으로는 주로 requirements, planning, management의 준비 미흡이 존재한다. 하지만, project가 기술적인 이슈로 실패한다면, 그 원인은 "통제할 수 없는 복잡성"(uncontrolled complexity) 때문에 발생한다. 만약 어떤 코드의 변경으로 발생하는 문제를 구성원들 중 아무도 인지하지 못한다면, 프로젝트 진행은 즉시 멈춰 버릴 것이다.
따라서, Managing Complexity는 SW개발에서 가장 중요한 기술적인 이슈고, 저자도 이를 정말 강조한다. Complexity는 새롭게 등장한 개념이 아니다. 컴퓨터공학의 위인 다익스트라는(그 다익스트라 맞다..) 숙련된 프로그래머는 bit 단위에서 10의 9승인 메가바이트 단위까지 모든 semantic level을 다루어야 하고, 우리에게 새로운 지적인 도전이라고 설명한다. 이는 현재에는 더하다. 과거보다 더하면 더한 복잡한 프로젝트를 수행해야한다.
다익스트라는 이 모든 컴퓨터 프로그램을 한꺼번에 이해할 수 있는 사람은 존재하지 않는다고 주장한다.(폰 노이만은 가능하지 않을까?) 그래서 다익스트라는 프로그램 개발 과정을 구조화 해야 하며, 구조를 정확하고 안전하게 나누어서, 한 부분만 바라보면서 일할 수 있는 환경을 만들어야 한다고 주장한다.
따라서 software architecture level에서는, system을 subsystem들로 쪼개는 과정에서 프로그램의 complexity는 감소한다. 인간들은 복잡한 한 조각보다, 간단한 여러 조각들을 분석하는 것이 쉽기 때문이다.(AI는 다를 수도 있다) 따라서 가장 중요한 점은, subsystem을 "안전하게(safety)" 분리하는 것이다. 따라서 한 계층만 건드려도 나머지 계층에 영향을 주지 않는 것이다. 즉, 너무 쉽게 문제를 분리하려다가 그 "복잡도"가 깨지면서 틀린 솔루션이 나오는 경우가 있다.
즉 다익스트라가 추구하는 것은,
- 인간 수준에서 이해할 수 있도록 essential complexity를 최대한 줄이기
- accidental complexity는 그때그때 해결할 수 있도록 냅두기
와 같다. 즉 complexity를 안전하게 control하는 것이 가장 중요하다는 것이다.
2-3. Desirable Characteristics of a Design
이제 high-quality design을 위한 조건들에 대해서 분석하자. 방금 강조한 대로, design의 최종적인 목표는 competing objectives(safety와 complexity)로 부터의 적절한 trade-off 이다. 좋은 design에 해당하는 특징들은 다음과 같다.
- Minimal Complexity - 여기에서 주의할 점은, "유식한" 디자인을 만들지 말라는 것이다. 유식한 디자인은 유식한 사람만이 이해할 수 있을 것이다. 그 대신, "간단"하면서 이해하기 쉬운 디자인이 중요하며, 간단하게 하려다가 safety를 파괴해서는 안된다.
- Ease of maintenance
- Loose coupling - subsystem간의 connection 숫자를 최대한 줄이는 것이 좋다. 그렇게 해야 design의 복잡성을 최대한 줄일 수 있으며 시스템 간의 프로토콜에 대한 복잡성을 고려하지 않아도 된다.
- Extensibility
- Reusability
- High fan-in - 중요한 말이 나왔다. High fan-in이란 특정 클래스가 다른 클래스에 의해 사용되는 경우가 "많은" 경우를 의미한다. 즉 "많이 재사용된다" 라는 뜻으로, 해당 클래스가 "유용하고 일반화된 기능"을 제공한다는 의미이다. High Fan-in은 시스템의 하위 레벨에서 유틸리티 클래스나 라이브러리에서 이러한 특성이 나타난다
- Low-to-Medium Fan-Out - 반면에 특정 클래스가 사용하는, 다른 클래스의 수가 너무 많으면 안된다. 왜냐하면, 복잡도가 증가하고, 유지보수가 힘들어지기 때문이다.
- Portability
- Leanness(날씬함) - 성공적인 프로젝트는, 단 한 줄의 코드라도 제외했을때 프로그램이 동작하지 않는 경우이다. 그만큼 extra code를 제거해야 한다는 의미이다.
- Startification - 프로젝트의 한 subsystem만 보고도 전체 프로젝트에서 어떠한 역할을 하는지 일관되게 알 수 있어야 한다.
- Standard techniques - 많은 사람들이 이해할 수 있도록 standard를 지킬것
2-4 Levels of Design
다음은 SW 시스템 디자인에 필요한 세부적인 단계들을 그림으로 나타낸 것이다. 모든 프로젝트에서 이 전 과정을 확실하게 지킬 필요는 없지만, 참고하면 도움이 된다. 5단계를 단계별로 이해해보자.
- Software System
이 단계는 전체 시스템에 대한 설계이고, 2단계에서 서술된 subsystem 또는 class들을 어떻게 통합할지에 대해 설계하는 단계이다.
2. Division into Subsystems or Packages
이 level에서는 모든 주요 subsystem에 대해 다룬다. subsystem의 구체적이 예시로는, database, userinterface, business rules, command interpreter 등이 있다. 이 단계에서는 system을 major subsystems로 분배(Divison)하고, 이 subsystem이 다른 subsystem들과 어떤 상호작용을 하는지를 결정한다.
중요한 것은 subsystem끼리의 통신방법 또는 권한을 결정하는 것이다. 예를 들어, 다음과 같이 subsystem끼리 통신의 제한이 없다면 다음과 같이 굉장히 복잡한(entroy가 높은) 상황이 발생한다.
하지만 이 방식은 비효율적이기도 하며, 원하지 않는 동작을 발생시켜 시스템에 위협을 주기도 한다. 예를 들어, 통신의 제한이 없다면 business rules를 건드릴때 graphic이 변경될 수도 있다. 이는 원하지 않는 상황이다.
따라서, subsystem끼리의 통신은 상대의 데이터를 필요로 할 때(need to know)를 기준으로 허용해줘야 한다. 만약 확신이 서지 않는다면, 일단 차단해두고 이후에 서로의 데이터가 필요로 해진다면 허용하는 쪽으로 설계하는 것이 좋다. 즉, 통신 line을 최소한으로 잡아서 시스템을 간단하게 만들어야 한다.
이러한 통신선들은 이해하기 쉽고, 유지보수가 간편해야 한다. 따라서 보통은 위 그림과 같이 acyclic graph 형태로 나타나야 한다. 즉 cycle이 있어서는 안된다는 이야기다.
구체적인 common subsystem들은 다음과 같다.
- Business rules - computer system에 대한 laws, regulations, policies ... 를 의미한다. 예를 들자면, payroll system에는 법적으로 정한 tax rate를 규정해야 한다.
- User interface - UI에서 가장 중요한 점은, UI에서 잘못된 조작으로 전체 시스템에 피해를 주지 않도록 설계해야 한다.UI의 종류로는 GUI, CLI, window management 등이 있다.
- Database access - DB에서 가장 중요한 것은, data를 저장하고, 로드하는 구체적인 과정은 user에게 black box로 보여야 한다는 것이다. 이 과정에서 complexity가 상당히 절감된다.
- System dependencies - operating system과 dependent한 부분을 따로 분리하는 것이 중요하다. windows에서 만든 프로그램을 mac이나 linux에서 동작시키기 위해 interface subsystem을 바꾸기만 하면 된다.
3. Divison into Classes
이 level에서는 system에서의 모든 class들을 identify한다. 예를 들어, DB interface subsystem의 경우 data access class와 framework class(metadata)로 분리할 수 있다. 따라서 우리는 Level2의 각각의 subsystem을 구성하는, 구체적인 클래스들을 모두 정해야 한다. 프로젝트가 간단한 경우 Level2와 함께 진행해도 무방하다.
* Classes vs Objects : object-oriented design의 핵심 개념은, object와 class의 분리에서 기인한다. object는, 프로그램의 런타임에서 존재하는 특별한 엔티티(entity)다. 클래스는, 프로그램 리스트에 있는 정적인 형식이다. object는 특별한 value와 attribute를 가지고 이들이 동적으로 움직이는 실체이다. 예를 들면, Person class에서, nancy, hank, tony 등의 이름을 가진 object들이 나올 수 있다. 이는 database에서 schema와 instance의 관계와 같다.
4. Divison into Routines
이 단계에서는 각각의 class를 routines로 분리한다. Level 4에서는 class 내부에 hierarchically하게 구성된 많은 루틴들로 구성되어 있음을 표현하고, 이를 위한 design을 실시한다. 예를 들어, Solider 클래스는 육군, 해군, 공군, 해병대로 나뉘고, 병과는 보병, 공병, 전차, 통신 .. 등으로 나뉘고, 각각의 병과에 따라 사용하는 무기나 주특기를 구분하기 위한 계층 구조가 필요하다.
5. Internal Routine Design
class 안의 각각의 루틴을 design했다면, 그 각각의 루틴에 대해 프로그래머가 이해할 수 있을 수준의 자세한 설계를 진행한다. 예를 들면, 동작 과정에 대한 알고리즘을 표현한다던가, 이를 pseudo code로 표현하는 단계이고, 실제적이고, 구체적인 기능(routine)에 대한 process를 자세히 정리하면 된다.
'Software Engineering > Code Complete, 2nd Edition' 카테고리의 다른 글
[Code Complete] CH5: Design in Construction(3) (0) | 2025.01.08 |
---|---|
[Code Complete] CH5: Design in Construction(2) (0) | 2024.12.31 |
[Code Complete] CH4: Key Construction Decisions (0) | 2024.12.26 |
[Code Complete] CH3: Measure Twice, Cut Once: Upstream Prerequisites(2) (0) | 2024.12.26 |
[Code Complete] CH3: Measure Twice, Cut Once: Upstream Prerequisites(1) (0) | 2024.12.24 |