0. 설계
: 요구 분석은 "무엇을" 만들 것인기를 정하는 것. 설계는 "어떻게" 만들 것인가를 정하는것.
(1) 기본 구조 설계: 각 모듈의 역할과 인터페이스 정의.
(2) 상세 설계: 모듈 내 알고리즘, 데이터 명세화.
6-1 설계 기본 개념
1. 설계 방법
: (과거) 분할 정복, 추상화, 합성 등의 원리 적용.
: (최근) 아키텍쳐 기반.
2. 서브 시스템, 모듈
- 아키텍처: 시스템을 구성하는 컴포넌트(서브시스템, 모듈) + 컴포넌트 상호작용의 집합
- 서브시스템: 시스템의 복잡도를 줄이기 위해 분할한 것.
3. 아키텍처 관점(4 + 1)
- 논리적 관점: UML 클래스도, 상태도, 교류도
- 프로세스 관점: UML 액티비티도
- 개발 관점: UML 컴포넌트도
- 물리적 관점: UML 배치도
- 유즈케이스 관점: UML 유즈케이스도
4. 아키텍처 관점 표현(2)
: UML, ADL(거의 안 씀)
5. 설계 작업 과정
(1) 품질 목표 설정
(2) 시스템 스타일 결정
(3) 서브 시스템 기능 및 인터페이스 명세
(4) 설계 리뷰
6-2 품질 목표
1. 품질
: 품질 제약사항은 설에 대한 목표가 될 수 있음.(ISO 25010 - sw품질 속성)
6-3 전통적인 설계 원리
1. 전통적인 설계 원리
2. Recurring concepts in design(설계에서 반복되는 개념들)(7)
(1) 추상화 (Abstraction)
: 코드 단순화를 위해. 구체적인 세부사항에 의존 않고, 추상적인 개념에 의존.
(2) 상향식 분해 (Top-down Decomposition)
: 단계적 정제. 고수준의 프로세스를 하위 수준으로 나누는 것. 데이터 흐름도에 사용됨. 분해 안될 때까지.
(3) 정보 은폐 (Information Hiding)
: 데이터 구조, 내부디자인을 숨김.다른 모듈에 미치는 영향 최소화. 정보은폐(원칙) : 캡슐화(기술)
(4) 관심사의 분리 (Separation of Concerns) - SoC
: 섹션별로 프로그램을 나눔(캡슐화).(입력 부분, 출력 부분...). SoC 잘 됨? -> 모듈화된 프로그램.
(5) 인터페이스 (Interfaces)
: 모듈이나 시스템이 통신하는 지점. 객체가 캡슐화되면 다른객체에 접근할 유일한 방법 제공.(C#, Java..)
(6) 모듈화 (Modularity)
: 큰 SW를 작은 모듈로 분할하는 것. 모듈들은 인터페이스를 통해 연결됨.(최대한 간단, 최소한 유지)
: 직접적으로 연결 -> 하나의 모듈이 다른 모듈 호출.
: 간접적으로 연결 -> 공통의 파일이나 전역 데이터 구조 공유
- 모듈화 측정: 결합력(coupling), 응집력(cohesion)
(7) 분할 정복 (Divide and Conquer):
: 모듈의 상세 개발할 때 사용됨. 문제를 재귀적으로해 더 작은 문제로 분해하는 것.
: 가장 낮은 문제가 해결되면 백트래킹이 발생해 원래 문제 해결.
3. 결합력(Coupling)(6)
: 모듈 간의 의존도 정도. 낮은 결합 - good. 높은 결합 - bad.(수정이 어려움.)
<<Coupling 종류(6)>> - good(낮은 결합) ~> bad(높은 결합) 순서임.
- 메시지 결합: 모듈끼리 의존 X. 매개변수 없는 메시지 교환. public 인터페이스 사용.
- 데이터 결합: 매개변수를 통해 통신.
- 스탬프 결합: 특정 데이터의 일부만 사용하는 모듈에 데이터를 전달 할 때 발생. 예를 들어, 세 개의 필드가 있는 레코드를 처음 두 개의 필드만 필요한 모듈에게 매개변수로 전달하는 경우.
- 제어(control) 결합: 모듈이 다른 모듈의 로직을 매개변수를 통해 제어. 다른 모듈의 작동 방식을 알아야함. -> 융통성 부족. 그림에서 print함수를 호출한 모듈은 print함수가 정의된 모듈과 제어 결합 수준임.
- 공통(common) 결합: 한 모듈이 다른 모듈이 읽은 전역 변수 값을 쓰거나 변경하는 경우 발생.(위험-연쇄 효과, 융통성 부족, 데이터 사용 이해하기 어려움, 가독성 저하, 재사용 어려움.)
- 내용(content) 결합: 한 모듈이 다른 모듈의 내용을 직접 참조하는 경우. 예를 들어, P라는 모듈이 Q 모듈 안의 로컬 데이터 값을 참조하는 경우, P가 Q의 내부로 분기하는 경우.
4. 응집력(Cohesion)
: 모듈 내 요소들의 관련도 정도. 낮은 응집 - bad. 높은 응집 - good.(수정이 어려움.)
: 강한 응집력은 결합을 최소화함.
<<Cohesion 종류(7)>> - good(높은 응집) ~> bad(낮은 응집) 순서임.
- 기능적(functional) 응집: 모듈 내부의 요소들이 모듈에 대해 정의된 하나의 기능에 모두 밀접하게 관련. 관련 없는 활동을 하는 요소 없음.
- 순차적(sequential) 응집: 요소의 출력 데이터가 다음 활동의 입력 데이터가 됨. 절차적과 달리 부분 간에 데이터가 흐름. 함수형 프로그래밍 언어에서 자연스럽게 발생.
- 교환적(communication) 응집: 모듈의 요소들이 동일한 데이터를 조작해서 그룹화 된 경우. 유연성 떨어짐. 기능적 응집 모듈로 분할하는 것이 더 좋음!
- 절차적(procedural) 응집: 순차적 응집과 유사하지만 요소 간의 관련성이 없음.
- 시간적(temporal) 응집: 요소는 시간에 관련된 활동을 함. 초기화, 종료 모듈에서 많이 사용되고, 기본적으로 관련이 없으므로 모듈 재사용이 어려움.
- 논리적(logical) 응집: 구성요소들이 기능적으로 응집되어 있지 않고, 논리적으로 관련있음. 예를 들어, 보고서 모듈, I/O모듈 등. 보통 제어 결합을 가짐. 코드가 중첩되기도 함.
- 우연적(coincidental) 응집: 요소는 서로 관련 없는 활동에 기여. 이해와 유지가 어려움.
6-4 객체지향 설계 원리
0. 객체지향 설계 원리(5)
: 상속과 인터페이스 등 새로운 구문과 함께 발전.
<<SOLID(5)>>
- 단일 책임의 원리(Single Responsibility Principle)
- 개방 폐쇄의 원리(Open Close Principle)
- 리스코프 교체의 원리(Liskov Substitution Principle)
- 인터페이스 분리의 원리(Interface Segregation Principle)
- 의존관계 역전의 원리(Dependency Inversion Principle)
1. 단일 책임의 원리(SRP)
: "한 클래스는 변경할 이유(책임)가 하나이어야 한다."
- 클래스가 하는 일이 많을수록 변경 가능성이 더 높아지고, 클래스가 변경될수록 버그 도입 가능성이 더 높아짐.
- 하나의 변경이 다른 변경에 영향을 미칠 수 있음.
- 결합된 책임을 별도의 클래스로 분리해야 함.
- 예시: 학생을 SSN(주민번호)나 이름으로 정렬해야함.
- 따라서 객체를 비교해 정렬할 수 있는 Comparable 인터페이스 구현.
- Student 클래스의 핵심 역할은 학생 정보를 나타내고 관리하는 것임.
- 근데 학생 객체가 정렬 방식을 지정하는 것은 Student 클래스의 추가 책임이 됨.
- 문제는, 학생 객체를 다른 방식으로 정렬할 때마다, 학생 클래스와 이를 사용하는 모든 클라이언트 코드를 다시 컴파일해야 한다는 점임.
- 이는 SRP를 위반. Student 클래스는 두 가지 서로 다른 책임을 갖고 있기 떄문.(학생 정보 관리, 정렬)
- 해결: 두 가지 책임을 두 개의 클래스로 분리하고, Collection.sort()의 다른 버전을 사용.
- 예시: Rectangel 클래스가 서로 다른 출처에서 변경 사항을 받을 수 있는 상황임.
- CGA(계산 기하학 앱)에서 변경: ex. 길이와 너비의 데이터 형식을 double로 만들어 넓이 기능 추가.
- GA(그래픽 앱)에서 변경: ex. 기존의 X윈도우의 draw()에 윈도우XP에서 draw()를 추가.
- 위의 두 출처 중 하나에서 발생하는 변경 사항은 다른 앱을 다시 컴파일하게 만듦.
- 아래의 그림은 GA가 바뀌어도 CGA는 안 바꿔도 됨. 그러나 CGA가 바뀌면 GA는 컴파일 되어야 함.
- 아래의 그림에서 Rectangle 클래스는 직사각형의 가장 기본적인 속성과 작업을 포함함.
- Geometric Rectagle클래스와 Graphic Rectangle 클래스는 서로 독립적임. 각자의 변경사항은 다른 측면을 컴파일하게 만들지 않음.
2. 개방 폐쇄의 원리(OCP)
: "소프트웨어 개체가 확장을 위해서는 열려있고, 수정을 위해서는 닫혀 있어야 한다."
- OCP의 핵심: 추상화
- 새로운 기능을 추가할 때 기존의 코드를 변경하지 않아야함.
- class로 구현하는게 아니고, interfce(또는 추상클래스)로 추상화!
- 예시: 직원 애플리케이션(Employee Application)
- Employee 클래스에는 EmpType(직원 유형)과 관련된 정보 포함.
- 교수, 스태프, 비서의 하위 클래스가 있고, 각 클래스는 특정 동작을 가짐.
- Employee 배열을 받아 직원 목록을 출력하는 printEmpRoster함수가 있음. 이는 Employee배열의 각 요소의 직원 유형을 확인하고 해당 유형에 맞는 출력함수 호출.
- 문제: 만약 Engineer 직원을 추가해야한다면???
- 아래처럼 printEmpRoster함수를 재설계하면 하위 클래스 변경에 대해 오픈되어 있고, 함수의 수정에 대해 폐쇄될수 있음.
- 각 Xmployee 하위 클래스는 printinfo함수를 구현해 자체 출력 동작을 정의.
- 이렇게 하면 새로운 직원 유형을 추가할 떄 함수를 수정할 필요가 없음.
<<미래 변경을 예상해야한다!>>
- 전략 필요: 어떤 종류의 변경에 대해 설계를 닫을건지 선택해야함.
- 비용 고려: OCP 준수하는 것은 비용이 발생함.
- 변경을 예상하는 것TDD를 사용하보다는 실제로 변경이 발생할 때까지 기다려야 함.
- 처음부터 코드를 변경 않을 것으로 예상하고 작성해야 함.
- 만약 변경이 발생하면 추상화 구현!
3. 리스코프 교체의 원리(LSP)
: "하위 유형은 기본 유형을 대체할 수 있어야 함."
- 상속 사용 여부를 결정할 때 확인하는 규칙.
- C가 P의 하위 유형인 경우, P유형의 객체를 변경하지 않고 C클래스의 객체로 대체할 수 있어야 함.
- Subtyping(하위 유형화): IS_A 관계를 설정. C is a P.
- Inheritance(상속): IS_A 관계일 때 상속!!
- 예시: LSP위반의 결과.
- PType 대신에 CType이 함수 f()에 전달될 때, 함수 f는 잘못된 동작을 유발함.
- 물론 f()함수 소유자는 CType에 대한 테스트 코드를 추가하려고 할 수 있지만 이는 OCP위반임.
- 이는 f()가 PType의 다양한 파생 클래스에 대해 폐쇄되지 않음을 의미.
- 예시: list를 사용하여 큐를 구현할 때, 상속보다는 집합-부분 관계 사용.
- list와 queue는 is_a 관계가 아님!
- 예시: 사각형과 정사각형. 상속 사용!
- square(정사각형) is_a rectangle(직사각형)
- 직사각형의 불변성: 너비와 높이는 서로 독립적이어야함.
- 그러나!! 지금 Square에서 직사각형의 불변성을 위반함!
- 이는 LSP를 위배하는 것임.
- 불변성을 위반하므로써 Square가 Rectangle을 대체할 수 없기 때문!
4. 인터페이스 분리의 원리(ISP)
: "클라이언트는 사용하지 않는 메서드에 의존하지 않아야 함"
- Fat Interface: 많은 메서드를 포함하는 인터페이스
- 여러 함수를 하나의 인터페이스나 클래스를 묶을 때, 불필요한 결합이 생기기도 함. 인터페이스는 메서드 그룹으로 분리될 수 있음.
- ISP는 비 일관된 인터페이스 해결.
- 예시: 문을 잠그고 열 수 있는 보안 시스템, 문이 오래 열려있으면 경보음이 울림.
- Timer 클래스: 시간 관리함. 그것의 클라이언트가 TimerClients 인터페이스를 구현하는 것을 필요로함.
- 결국 TimedDoor는Door클래스와 TimerClient 인터페이스를 사용해야 함.
5. 의존관계 역전의 원리(DIP)
: "고수준 모듈은 저수준 모듈에 의존하면 안됨, 둘 다 추상화에 의존해야 함."
: "추상화는 세부 사항에 의존하면 안됨. 세부 사항은 추상화에 의존해야 함"
- 고수준 모듈이 영향을 받지 않으려면 고수준과 저수준 모듈을 분리하는 추상화를 도입해야 함.
- 즉, 고수준의 모듈이 저수준의 추상된 인터페이스에 의존하게 해야함.
- (고수준 모듈을 구체화해 저수준 모듈을 만듦.)
- DIP 기본 휴리스틱 -> 만약 큰 문제 없으면 어겨도 상관없음.
- 어떤 변수도 구체 클래스를 참조하면 안 됨.
- 어떤 클래스도 구체 클래스에서 파생되면 안 됨.
- 어떤 메서드도 기본 클래스의 구현된 메서드를 재정의하면 안 됨.
6-5 설계 메트릭
1. 전통적인 메트릭
2. 객체지향 메트릭
'Computer Science > Software Engineering' 카테고리의 다른 글
[소공] 9장 코딩 (0) | 2023.12.14 |
---|---|
[소공] 7장 아키텍쳐와 패턴 (0) | 2023.12.14 |
[소공] 5장 요구 모델링 (0) | 2023.10.27 |
[소공] 객체지향 방법론과 UML (1) | 2023.10.13 |
[소공] 4장 요구 분석 (1) | 2023.10.09 |