[오브젝트 - 코드로 이해하는 객체지향 설계]의 6장 메시지와 인터페이스 챕터이다.
이번 챕터에서는 영화 예매 애플리케이션을 예제로 두고, 유연하고 재사용 가능한 퍼블릭 인터페이스를 만드는데 도움이 되는 설계 원칙과 기법을 살펴보는것을 주제로 하고있다.
00_들어가기
- 객체지향 프로그램에 대한 가장 흔한 오해
- 애플리케이션이 클래스의 집합으로 구성된다.
- 클래스라는 구현 도구에 지나치게 집착하면 경직되고 유연하지 못한 설계에 이를 확률이 높아진다.
- 애플리케이션이 클래스의 집합으로 구성된다.
- 훌륭한 객체 지향 코드를 얻기 위해서는 클래스가 아닌 객체를 지향해야 한다.
- 협력 안에서 객체가 수행하는 책임에 초점을 맞춰야 한다.
- 책임이 객체가 수신할 수 있는 메시지의 기반이 된다.
- 협력 안에서 객체가 수행하는 책임에 초점을 맞춰야 한다.
- 클래스 사이의 정적인 관계에서 메시지 사이의 동적인 흐름으로 초점을 전환하는 것은 미숙함을 벗어나 숙련된 객체지향 설계자로 성장하기 위한 첫 걸음
- 애플리케이션은 클래스로 구성되지만 메시지를 통해 정의된다.
01_협력과 메시지
- 클라이언트-서버 모델
- 협력은 객체가 다른 객체에게 무언가 요청할 때 시작
- 메시지는 객체 사이의 협력을 가능하게 하는 매개체
- 메시지를 매개로 하는 요청과 응답의 조합이 두 객체 사이의 협력을 구성
- 클라이언스-서버(Client-Servier) 모델
- 협력 안에서 메시지를 전송하는 객체를 클라이언트, 메시지를 수신하는 객체를 서버라고 한다.
- 협력은 클라이언트가 서버의 서비스를 요청하는 단방향 상호작용이다.
그림 6-1. 메시지를 통해 협력하는 클라이언트와 서버 - 그림 6-1에서
Screening
은 클라이언트의 역할을 수행하고,Moive
는 서버의 역할을 수행한다. Screening
은 가격을 계산하라 메시지를 전송함으로써 도움을 요청하고Movie
는 가격을 계산하는 서비스를 제공하여 메시지에 응답한다.그림 6-2. 클라이언트의 역할을 수행하는 Movie - 그림 6-2에서
Movie
는 할인 요금을 계산하라 메시지를DiscountPolicy
의 인스턴스에 전송해서 할인 요금을 반환 - 여기서는
Movie
가 클라이언트의 역할을 수행 - 그림 6-1과 6-2에서 알 수 있는것 처럼 객체는 협력에 참여하는 동안 클라이언트와 서버의 역할을 동시에 수행하는 것이 일반적
- 협력에 적합한 객체를 설계하기 위해서는 객체가 수신하는 메시지의 집합과 외부에 전송하는 메시지의 집합을 함께 고려해야 한다.
- 메시지와 메시지 전송
- 메시지(Message) : 객체들이 협력하기 위해 사용할 수 있는 유일한 의사소통의 수단
- 메시지 전송(Message Sending) : 한 객체가 다른 객체에게 도움을 요청하는 것
- 메시지 패싱(Message Passing) 이라고도 함
- 메시지 전송자(Message Sender) : 메시지를 전송하는 객체
- 메시지 수신자(Message Receiver) : 메시지를 수신하는 객체
- 클라이언트-서버 모델 관점에서는 메시지 전송자를 클라이언트로, 메시지 수신자를 서버라고 한다.
- 메시지 전송(Message Sending) : 한 객체가 다른 객체에게 도움을 요청하는 것
- 메시지는 오퍼레이션명(Operation Name)과 인자(Argument)로 구성되고, 메시지 전송은 여기에 메시지 수신자를 추가한 것.
- 메시지 전송 = 수신자 + 오퍼레이션명 + 인자
그림 6-3. 언어별 메시지 전송 표기법 - 그림 6-3과 같이 프래그래밍 언어에 따라 메시지 전송의 표기법은 차이가 있지만, 메시지 전송을 구성하는 요소는 동일
- 메시지(Message) : 객체들이 협력하기 위해 사용할 수 있는 유일한 의사소통의 수단
- 메시지와 메서드
- 메서드(Method) : 메시지를 수신했을 때 실제로 실행되는 함수 또는 프로시저
- 코드상에서 동일한 이름의 변수에 동일한 메시지를 전송하더라도, 객체의 타입에 따라 실행되는 메서드가 달라질 수 있다.
- 상속구조인 경우 자식 클래스 호출되는 범위와 관련되어 달라질 수 있음
- 예시를 통해서는
isSatisfiedBy
메서드를 실행시켰으나 수신하는 객체가PeriodCondition
또는SequenceCondition
둘 중 어떤 객체인지에 따라 다른 메서드가 호출된다.
- 메시지와 메서드의 구분은 메시지 전송자와 메시지 수신자가 느슨하게 결합될 수 있게 한다.
- 메시지 전송자 : 자신이 어떤 메시지를 전송해야 하는지만 알면 됨
- 메시지 수신자 : 누가 메시지를 전송하는지 알 필요가 없음, 메시지가 도착했다는 사실만 알면 됨
- 메시지 수신자는 메시지를 처리하기 위해 필요한 메서드를 스스로 결정할 수 있는 자율권을 누린다.
- 실행시점에 메시지와 메서드를 바인딩하는 메커니즘은 두 객체 사이의 결합도를 낮춤으로써 유연하고 확장 가능한 코드를 작성할 수 있게 만듬
- 퍼블릭 인터페이스와 오퍼레이션
- 퍼블릭 인터페이스(Pulic Interface) : 객체가 의사소통을 위해 외부에 공개하는 메시지의 집합
- 오퍼레이션(Operation) : 프로그래밍 언어의 관점에서 퍼블릭 인터페이스에 포함된 메시지
- 수행 가능한 어떤 행동에 대한 추상화
- UML의 공식적 인터페이스 정의
- 오퍼레이션이란 실행하기 위해 객체가 호출될 수 있는 변환이나 정의에 관한 명세
- 메서드(Method) : 메시지를 수신했을 때 실제로 실행되는 코드
- 시그니처
- 시그니처(Signature)
- 오퍼레이션(또는 메서드)의 이름과 파라미터 목록을 합친 것
- 오퍼레이션은 실행코드 없이 시그니처만 정의 한 것
- 메서드는 시그니처에 구현을 더한 것
- 오퍼레이션의 관점에서 다형성이란 동일한 오퍼레이션 호출에 대해 서로 다른 메서드들이 실행되는 것으로 정의할 수 있다.
- 오퍼레이션(또는 메서드)의 이름과 파라미터 목록을 합친 것
- 시그니처(Signature)
02_인터페이스와 설계 품질
- 좋은 인터페이스는 최소한의 인터페이스와 추상적인 인터페이스의 조건을 만족해야 한다.
- 최소한의 인터페이스는 꼭 필요한 오퍼레이션만을 인터페이스에 포함한다는 것
- 추상적인 인터페이스는 어떻게 수행하는지가 아닌 무엇을 하는지를 표현하는 것
- 최소주의를 따르면서도 추상적인 인터페이스를 설계할 수 있는 가장 좋은 방법은 책임 주도 설계 방법을 따르는 것이다.
- 메시지를 먼저 선택함으로써 협력과는 무관한 오퍼레이션이 인터페이스에 스며드는것을 방지
- 메시지가 객체를 선택하게 함으로써 클라이언트의 의도를 메시지에 표현
- 퍼블릭 인터페이스의 품질에 영향을 미치는 원칙과 기법
- 디미터 법칙
- 묻지말고 시켜라
- 의도를 드러내는 인터페이스
- 명령-쿼리 분리
- 디미터 법칙
- 디미터 법칙(Law Of Demeter)
- 협력하는 객체의 내부 구조에 대한 결합으로 인해 발생하는 설계 문제를 해결하기 위해 제안된 원칙
- 객체의 내부 구조에 강하게 결합되지 않도록 협력 경로를 제한하는 것
자바
나C#
과 같이 '도트(.)'를 이용하는 메시지 전송을 표현하는 언어에서는 오직 하나의 도트만을 사용하라(use only one dot) 라는 말로 요약되기도 한다.
- 디미터 법칙을 따르기 위해서는 클래스가 특정한 조건을 만족하는 대상에게만 메시지를 전송하도록 프로그래밍 해야한다.
# 모든 클래스 C와 C에 구현된 모든 메서드 M에 대해서, M이 메시지를 전송할 수 있는 모든 객체는 다음에 서술된 클래스의 인스턴스여야 한다. # 이때 M에 의해 성성된 객체나 M이 호출하는 메서드에 의해 생성된 객체, 전역 변수로 선언된 객체는 모두 M의 인자로 간주한다. - M의 인자로 전달된 클래스(C 자체를 포함) - C의 인스턴스 변수의 클래스 # 클래스 내부의 메서드가 아래 조건을 만족하는 인스턴스에만 메시지를 전송하도록 프로그래밍 해야 한다고 이해해도 무방하다. 1. this 객체 2. 매서드의 매게변수 3. this의 속성 4. this의 속성인 컬렉션의 요소 5. 메서드 내에서 생성된 지역 객체
- 디미터 법칙을 따르면 부끄럼타는 코드(Shy Code)를 작성할 수 있다.
- 부끄럼타는 코드 : 불필요한 어떤 것도 다른 객체에게 보여주지 않으며, 다른 객체의 구현에 의존하지 않는 코드를 말한다.
- 메시지 전송자가 수신자의 내부 구조에 대해 물어보고 반환받은 요소에 대해 연쇄적으로 메시지를 전송하는 것을 기차 충돌(Train Wreck)이라고 한다.
- 메시지 수신자의 캡슐화는 무너지고, 메시지 전송자가 메시지 수신자의 내부 구현에 강하게 결합된다.
- 디미터 법칙 효과
- 정보를 처리하는 데 필요한 책임을 정보를 알고 있는 객체에게 할당하기 때문에 응집도가 높은 객체가 만들어짐
- 부작용
- 무비판적으로 디미터 법칙을 수용하면 퍼블릭 인터페이스의 관점에서 객체의 응집도가 낮아질 수 있다.
- 디미터 법칙(Law Of Demeter)
- 묻지 말고 시켜라
- 묻지 말고 시켜라(Tell. Don't Ask)
- 디미터 법칙 스타일의 메시지 작성을 장려하는 원칙을 가리키는 용어
- 훌륭한 메시지는 객체의 상태에 관해 묻지 말고 원하는 것을 시켜야 한다.
- 디미터 법칙 스타일의 메시지 작성을 장려하는 원칙을 가리키는 용어
- 묻지 말고 시켜라 원칙을 따르면 밀접하게 연관된 정보와 행동을 함께 가지는 객체를 만들 수 있다.
- 묻지 말고 시켜라 원칙에 따르도록 메시지를 결정하다 보면 자연스럽게 정보 전문가에게 책임을 할당하게 되고 높은 응집도를 가진 클래스를 얻을 확률이 높아진다.
- 묻지 말고 시켜라 원칙과 디미터 법칙은 훌륭한 인터페이스를 제공하기 위해 포함해야 하는 오퍼레이션에 대한 힌트를 제공
- 묻지 말고 시켜라(Tell. Don't Ask)
- 의도를 드러내는 인터페이스
- 메서드를 명명하는 두 가지 방법
- 메서드가 작업을 어떻게 수행하는지를 나타내도록 이름 짓는 것
- 단점
- 메서드에 대해 제대로 커뮤니케이션을 하지 못한다.
- 메서드 수준에서 캡슐화를 위반한다.
- 단점
- '어떻게'가 아니라 '무엇'을 하는지를 드래내도록 이름 짓는 것
- 장점
- 코드를 읽고 이해하기 쉽게 만들 뿐만 아니라 유연한 코드를 낳을 수 있다.
- 메서드늬 내부 구현을 설명하는 이름
- 장점
- 메서드가 작업을 어떻게 수행하는지를 나타내도록 이름 짓는 것
- 의도를 드러내는 선택자(Intention Revealing Selector)
- '어떻게'가 아닌 '무엇을'하느냐에 따라 메서드의 이름을 짓는 패턴
- 의도를 드러내는 인터페이스(Intention Revealing Interface)
- 구현과 관련된 모든 정보를 캡슐화하고 객체의 퍼블릭 인터페이서에는 협력과 관련된 의도만을 표현한다는 것.
- 메서드를 명명하는 두 가지 방법
- 함께 모으기
- 디미터 법칙 위반
- 근본적으로 디미터 법칙을 위반하는 설계는 인터페이스와 구현의 분리 원칙을 위반
- 디미터 법칙을 위반한다는 것은 클라이언트에게 구현을 노출한다는 것을 의미하며, 그 결과 작은 요구사항 변경에도 쉽게 무너지는 불안정한 코드를 얻게된다.
- 디비터 법칙을 위반한 코드는 사용하기 어렵다.
- 묻지 말고 시켜라
- 디미터 법칙과 묻지말고 시켜라 스타일을 따르면 자연스럽게 자율적인 객체로 구성된 유연한 협력을 얻게 된다.
- 구현이 객체의 퍼블릭 인터페이스에 노출되지 않기 대문에 객체 사이의 결합도는 낮아진다.
- 책임이 잘못된 곳에 할당될 가능성이 낮아지기 때문에 객체의 응집도는 높아진다.
- 디미터 법칙과 묻지말고 시켜라 스타일을 따르면 자연스럽게 자율적인 객체로 구성된 유연한 협력을 얻게 된다.
- 인터페이스에 의도를 드러내자
- 오퍼레이션의 이름은 협력이라는 문맥을 반영해야 한다.
- 오퍼레이션은 클라이언트가 객체에게 무엇을 원하는지 표현해야 한다.
- 디미터 법칙 위반
03_원칙의 함정
- 디미터 법칙과 묻지 말고 시켜라 스타일은 객체의 퍼블릭 인터페이스를 깔끔하고 유연하게 만들 수 있는 훌륭한 설계 원칙이다.
- 절대적 원칙은 아니다.
- 설계는 트레이드 오프의 산물
- 원칙이 현재 상황에 부적합하다고 판단된다면 과감하게 원칙을 무시
- 원칙을 아는 것보다 더 중요한 것은 언제 원칙을 유욧ㅇ하고 언제 유용하지 않은지를 판단할 수 있는 능력을 기르는 것
- 디미터 법칙은 하나의 도트(.)를 강제하는 규칙이 아니다.
- 예시
IntStream.of(1,15,20,3,9).filter(x -> x > 10).distinct().count();
- 위 코드에서 of, filter, distinct 메서드는 모두
IntStream
이라는 동일한 클래스의 인스턴스를 반환한다. 즉, 이들은 Instream의 인스턴스를 또 다른 IntStream으로 변환하기 때문에 이 코드는 디미터 법칙을 위반하지 않는다.
- 위 코드에서 of, filter, distinct 메서드는 모두
- 디미터 법칙은 결합도와 관련된 것이며, 이 결합도가 문제가 되는 것은 객체의 내부 구조가 외부로 노출되는 경우로 한정
- 기차 충돌처럼 보이는 코드라도 객체의 내부 구현에 대한 어떤 정보도 외부로 노출하지 않는다면 그것은 디미터 법칙을 준수한 것
- 객체를 다른 객체로 변환하는 작업을 수행하라고 시킬 뿐이기 때문에 묻지 말고 시켜라 원칙을 위반하지 않음
- 예시
- 결합도와 응집도의 충돌
- 묻지 말고 시켜라와 디미터 법칙을 준수하는 것이 항상 긍정적인 결과로만 귀결되는 것은 아니다.
- 모든 상황에서 맹목적으로 위임 메셔드를 추가하면 같은 퍼블릭 인터페이스 안에 어울리지 않는 오퍼레이션들이 공존하게 됨
- 결과적으로 객체는 상관 없는 책임들을 한꺼번에 떠안게 되기 때문에 결과적으로 응집도가 낮아진다.
- 디미터 법칙의 위반 여부는 묻는 대상이 객체인지, 자료구조인지에 달려있다.
- 객체일 경우 내부구조를 숨겨야 하므로 디미터 법칙을 따름
- 자료구조일 경우 내부구조를 노출해야 하므로 디미터 법칙을 따를 필요가 없음
- 묻지 말고 시켜라와 디미터 법칙을 준수하는 것이 항상 긍정적인 결과로만 귀결되는 것은 아니다.
04_명령-쿼리 분리 원칙
- 용어정리
- 루틴(routine) : 어떤 절차를 묶어 호출 가능하도록 이름을 부여한 기능 모듈
- 프로시저(procedure) : 정해진 절차에 따라 내부의 상태를 변경하는 루틴의 한 종류
- 함수(method) : 어떤 절차에 따라 필요한 값을 계산해서 반환하는 루틴의 한 종류
- 프로시저와 함수 구분을 위한 루틴 작성 제약
- 프로시저는 부수효과를 발생시킬 수 있지만 값을 반환할 수 없다.
- 함수는 값을 반환할 수 있지만 부수효과를 발생시킬 수 없다.
- 명령-쿼리 분리 원칙(Command-Query Separation)
- 명령(Command)과 쿼리(Query)는 객체의 인터페이스 측면에서 프로시저와 함수를 부르는 또 다른 이름
- 오퍼레이션 명령 : 객체의 상태를 수정(프로시저)
- 오퍼레이션 쿼리 : 객체와 관련된 정보를 반환(함수)
- 명령-쿼리 분리 원칙의 요지
- 오퍼레이션은 부수효과를 발생시키는 명령이거나 부수효과를 발생시키지 않는 쿠리 중 하나여야 한다.
- 명령과 쿼리 분리를 위한 규칙
- 객체의 상태를 변경하는 명령은 반환값을 가질 수 없다.
- 객체의 정보를 반환하는 쿼리는 상태를 변경할 수 없다.
- 명령-쿼리 분리 원칙에 따라 작성된 객체의 인터페이스를 명령-쿼리 인터페이스(Command-Query Interface)라고 한다.
- 명령(Command)과 쿼리(Query)는 객체의 인터페이스 측면에서 프로시저와 함수를 부르는 또 다른 이름
- 반복 일정의 명령과 쿼리 분리하기
- 반복 일정 애플리케이션을 구현하여 예시를 통해 명령-쿼리 분리과정을 확인하였다.
- Github_01 명령-쿼리 분리하기
- https://github.com/JIHYEON-PF/book_review_object/pull/13
06_메시지와 인터페이스 첫번째 PR - 명령 - 쿼리 분리 원칙 코드예시 by JIHYEON-PF · Pull Request #13 · J
06_메시지와 인터페이스의 첫 번째 PR로 반복 일정과 관련된 애플리케이션을 구현하였다. 1. 개요 특정 단발성 이벤트 역할을 담당하는 Event와 반복일정 관리의 역할을 담당하는 RecurringSchedule 클
github.com
- 명령-쿼리 분리와 참조 투명성
- 명령과 쿼리를 엄격하게 분리하면 객체의 부수효과를 제어하기 수월해진다.
- 명령과 쿼리를 분리함으로써 명령형 언어의 틀 안에서 참조 투명성(Referential Transparency)의 장점을 제한적으로 누릴 수 있다.
- 버그가 적고, 디버깅이 용이하며, 쿼리의 순서에 따라 실행 결과가 변하지 않는 코드를 작성할 수 있다.
- 참조 투명성
- 어떤 표현식 e가 있을 때 e의 값으로 e가 나타나는 모든 위치를 교체하더라ㅓ도 결과가 달라지지 않는 특성을 의미
- 참조 투명성을 만족하는 식이 제공하는 장점
- 모든 함수를 이미 알고 있는 하나의 결과값으로 대체할 수 있기 때문에 식을 쉽게 계산할 수 있다.
- 모든 곳에서 함수의 결과값이 동일하기 때문에 식의 순서를 변경하더라ㅓ도 각 식의 결과는 달라지지 않는다.
- 책임에 초점을 맞춰라
- 디미터 법칙을 준수하고 묻지 말고 시켜라 스타일을 따르면서도 의도를 드러내는 인터페이스를 설계하는 아주 쉬운 방법
- 메시지를 먼저 선택하고 그 후에 메시지를 처리할 객체를 선택하는 것
- 명령과 쿼리를 분리하고 계약에 의한 설계 개념을 통해 객체의 협력 방식을 명시적으로 드래낼 수 있는 방법
- 객체의 구현 이전에 객체 사이의 협력에 초점을 맞추고 협력 방식을 단순하고 유연하게 만드든 것
- 훌륭한 메시지를 얻기 위한 출발점 : 책임 주도 설계 원칙을 따르는 것
- 계약에 의한 설계(Design By Contract)
- 협력을 위해 클라이언트와 서버가 준수해야 하는 제약을 코드 상에서 명시적으로 표현하고 강제할 수 있는 방법
- 부록 A "계약에 의한 설계" 참고
- 디미터 법칙을 준수하고 묻지 말고 시켜라 스타일을 따르면서도 의도를 드러내는 인터페이스를 설계하는 아주 쉬운 방법
05_Review
- 얼마 전 회사 프로젝트를 하며 다른 팀원분이 작성한 코드를 인용하여 유사한 기능을 하는 함수를 만들어 사용한 적이 있다. 코드를 짜고 MR을 작성하여 올렸을 때 다른 리뷰어님께서 인용코드에 디미터법칙을 적용해보는것이 어떻냐는 제안을 했다. 디미터 법칙을 알지 못해 검색을 해보고 대략적으로 이해한 내용은
하나의 도트(.)만을 사용하라
와객체간의 의존성을 최소화 해야한다는 개념
이라는 것을 겉햝기식으로 알게됬었다. MR을 수정하면서 든 생각은 단순히 의존성을 낮추기 위해 필요한 작업이다 라고 생각하였지만, 책을 통해 상세내용을 조금 더 알게되고나서는 객체지향 설계의 기본이고, 유연하고 재사용 가능한 인터페이스를 설계하는 지침서라는 것을 알게되었다. - 그리고 디미터 법칙과 묻지 말고 시켜라, 그리고 의도를 드러내는 인터페이스와 관련되어 스쳐지나가는 코드들이 있었고, 어떤 식으로 리팩토링을 가져갈 것인지, 그 코드들이 사용되는 구조적 상황과 성능에 대한 트레이드오프 과정을 개인 뿐만 아닌 팀 내부적으로도 회의해보고 컨벤션으로 잡고 일반화 시키는 것도 좋을것 같다고 생각하였다.
오브젝트
객체지향으로 향하는 첫걸음은 클래스가 아니라 객체를 바라보는 것에서부터 시작한다. 객체지향으로 향하는 두번째 걸음은 객체를 독립적인 존재가 아니라 기능을 구현하기 위해 협력하는 공동체의 존재로 바라보는 것이다. 세번째 걸음을 내디딜 수 있는지 여부는 협력에 참여하는 객체 들에게 얼마나 적절한 역할과 책임을 부여할 수 있느냐에 달려 있다. 객체지향의 마지막 걸음은 앞에서 설명한 개념들을 여러분이 사용하는 프로그래밍 언어라는 틀에 흐트러짐 없이 담아낼 수 있는 기술을 익히는 것이다. 《객체지향의 사실과 오해》가 첫번째 걸음과 두번째 걸음인 객체와 협력에 초점을 맞췄다면 《오브젝트: 코드로 이해하는 객체지향 설계》는 세번째와 네번째 걸음인 책임의 할당과 그 구현에 초점을 맞춘다. 이 책을 읽고 나면 객체에 적절한 역할과 책임을 부여하는 방법과 유연하면서도 요구사항에 적절한 협력을 설계하는 방법을 익히게 될 것이다. 나아가 프로그래밍 언어라는 도구를 이용해 객체지향의 개념과 원칙들을 오롯이 표현할 수 있는 방법 역시 익힐 수 있을 것이다. ★ 이 책에서 다루는 내용 ★ ◎ 역할, 책임, 협력에 기반해 객체지향 프로그램을 설계하고 구현하는 방법 ◎ 응집도와 결합도를 이용해 설계를 트레이드오프하는 방법 ◎ 설계를 유연하게 만드는 다양한 의존성 관리 기법 ◎ 타입 계층을 위한 상속과 코드 재사용을 위한 합성의 개념 ◎ 다양한 설계 원칙과 디자인 패턴
- 저자
- 조영호
- 출판
- 위키북스
- 출판일
- 2019.06.17
(참고문헌: 조영호, 오브젝트 - 코드로 이해하는 객체지향 설계, 위키북스, 2022.11.30(6쇄 발행), p.175 ~ p.215)
'BookReview > 오브젝트' 카테고리의 다른 글
08_의존성 관리하기 (0) | 2023.02.09 |
---|---|
07_객체 분해 (0) | 2023.02.05 |
05_책임 할당하기 (0) | 2023.01.18 |
04_설계 품질과 트레이드 오프 (2) | 2023.01.09 |
03_역할, 책임, 협력 (0) | 2023.01.08 |