2017년 3월 30일 목요일


API설계는 문제점의 분석에서 시작하며 문제를 해결할 수 있는 방법을 설계하고 그 설계대로 코드를 구현한다. 이런 작업들은 지속적이고 반복적으로 되풀이된다.


4.1 좋은 설계를 위한 사례



4.1.1 기술적 부채의 증가

만약 하나의 소프트웨어가 기업을 성공으로 이끌 수 있다면, 오히려 코드를 사용하기 쉬우면서 안정적으로 만들고 제품에 관한 문서화 역시 잘 해두는 것이 첫 번째 목표가 되어야 한다. 새로운 요구사항이 있을 경우, 이 요구사항들은 오래 지속될 수 없게 설계된 제품의 핵심 기능 위에 덫붙여 구현될 것이다. 이는 기술적 부채의 증가를 불러온다.

처음 코드를 작성하는 것은 빚을 지는 것과 같다. 그리고 코드를 개선하는 것은 빚을 갚는 활동이다. 약간의 빚을 지고 있는 동안에는 빚을 갚기 위한 개발이 가속화도니다. 위험은 채무가 상환되지 않을 때 발생하며, 올바르지 않은 코드에 시간을 쏟는 일은 빚에 이자까지 쌓는 격이다. 결국엔 낮은 코드로 인해 전체 엔지니어링 조직은 빚더미에 놓인다.



4.1.2 부채 상황

레거시 코드를 업그레이드하고 문제점들을 해결하기 위해 '차세대' 프로젝트를 시작할 때, 이를 위해 두 가지 전략을 고민할 수 있다.
1. 점진적 발전 : 모든 요구사항을 포함한 시스템을 설계한 뒤, 기존 시스템이 어느 정도 수준을 만족시킬 때까지 반복적으로 코드를 리팩토링 한다.
2. 급진적 혁신 : 이전 코드는 모두 버리고 완전히 새로운 코드를 설계하고 구현한다.

점진작 발전을 위해 필요한 접근 방법은 이전의 좋지 못한 코드를 새로 잘 설계한 API 밑으로 숨기고 차근차근히 모든 클라이언트의 코드를 새롭게 정비한 API로 변경시킨 다음 테스트 자동화를 통해 버그를 잡아가는 것이다.


4.1.3 멀리 보는 설계

훌륭한 설계는 프로젝트의 진행 속도를 늦춘다는 인식이다. 좋은 소프트웨어를 개발하기 위해서 Pimple 같은 추가적인 코드를 클래스에 구현하거나 API의 행동을 검증하기 위해 자동화된 테스트 코드를 작성하는 등의 시간 소비적인 일들이 수반되는 것은 맞다. 하지만 좋은 설계는 생각보다 시간을 소비 하지 않으며, 장기적으로 더 좋은 결과를 낳기 마련이다.

인터페이스와 구현코드를 분리하면 유지보수에 큰 장점을 얻을 수 있고, 테스트 코드를 자동화하면 빠른 시간 내에 원하는 기능을 문제 없이 적용할 수 있다.


4.2 기능적 요구사항 수집

소프트웨어 개발에는 여러 개의 요구사항 정의에 관한 방식이 존재한다.
1. 비지니스 요구사항 : 비지니스의 관점에서 소프트웨어가 조직의 요구를 얼마나 잘 만족시키는가에 대한 가치를 설명한다.
2. 기능적 요구사항 : 목표를 만족시키기 위해 필요한 소프트웨어의 행동들을 설명한다.
3. 비기능적 요구사항 : 사용자가 만족할 수 있는 수준의 소프트웨어 품질 표준을 설명한다.


4.2.1 기능적 요구사항

잘못된 것을 개발하느라 시간과 비용을 낭비하지 않도록 정확히 무엇을 개발해야 할지를 이해하는 방법이다. 요구사항에 대해 스스로 상황을 예측하거나 연구해서 사용자에게 기능적 요구사항을 정의하도록 주도해 나갈 필요도 있다. 예로 인터뷰나 회의를 소집 혹은 사용자에게 직접 아래의 질문한다.

비기능적 요구사항이 기능적 요구사항을 지원할 수도 있다.
비 기능적 요구사항 예) 성능, 플랫폼 호환성, 보안, 확장성, 유연성, 사용성, 동시 접근성, 비용


4.2.2 기능적 요구사항의 예

기능적 요구사항은 API가 무엇을 해야 하는지 정의하는 문서이므로 어떻게 그것을 구현하는지는 정의할 필요 없다.
ex) 자동 현금 인출기를 이용하는 사용자의 기능적 요구사항



4.2.3 요구사항 유지보수

안정화된 요구사항이란 존재하지 않는다. 시간이 지나면서 요구사항 역시 변경되기 때문이다. 새로운 요구사항의 반영은 제품의 출시를 지연 시키며 동시에 설계의 변경과 더불어 많은 양의 코드를 다시 개발해야 할 수 있다.


4.3 유즈 케이스 생성

유즈 케이스는 사용자나 다른 소프트웨어와의 상호작용에서 발생하는 API의 행동을 설명하며, 기능적 요구사항은 API의 기능 목록을 설명하거나 구체적 알고리즘을 정의하는 데 사용한다. 이 두 가지 중 하나만 집중해도 충분히 원하는 결과를 얻을 수 있다.
Tip) 유즈 케이스는 사용자의 관점에서 API에 관한 요구사항을 도출해 낸다.


4.3.1 유즈 케이스 개발

모든 유즈 케이스는 액터(Actor)가 달성하고자 하는 목적을 설명한다.
ex) ATM 기계
1. 사용자가 ATM에 카드를 넣는다.
2. 시스템이 ATM을 통해 전달된 카드가 유효한지 체크한다.
3. 시스템이 사용자에게 비밀번호를 입력하도록 유도한다.
4. 사용자가 비밀번호를 입력한다.
5. 시스템은 사용자가 입력한 비밀번호가 올바른지 체크한다.


4.3.2 유즈 케이스 템플릿 사용

잘 정의된 유즈 케이스는 하나의 행동에 대해 목표 지향적인 설명 제공 및 목표를 달성하기 위한 워크플로를 여러 단계로 나누어 설명한다.

Tip) 유즈 케이스를 간단하게 목표 중심으로 설명한 단순한 목록 형태로 작성하거나 미리 정해진 템플릿을 사용해서 좀 더 격식을 갖춘 형태로도 작성할 수 있다.


4.3.3 좋은 유즈 케이스 작성

유즈 케이스 작성은 직관적인 과정이어야 한다. 유즈 케이스는 사용자의 관점에서 API를 어떻게 사용해야 하는지 묘사하기 위해 읽기 쉬운 평문으로 작성한다.
- 도메인에 관련된 용어를 사용한다 : API를 사용자가 쉽게 받아 들이기 위해
- 너무 구체적인 유즈 케이스는 피한다 : 외부에서 보는 시선에서 설명하기 때문에
- 유즈 케이스는 모든 요구사항을 명시하지 않는다 : 사용자가 API와 상호작용하는 요구사항에 관한 행동에만 집중
- 유즈 케이스는 설계를 정의하지 않는다.
- 유즈 케이스에 설계를 명시하지 않는다.
- 유즈 케이스는 직접 테스트가 가능하다
- 반복적으로 수행한다.
- 이것으로 충분하다고 여기지 않는다.


4.3.4 요구사항과 애자일 개발

애자일 개발은 애자일 성명에 기반한 SW개발 방법으로 익스트림 프로그래밍(XP, Extreme Programming), 스크럼(Scrum), DSDM등이 여기에 속한다.



4.4 API 설계 요소

좋은 API설계의 비법은 문제 도메인을 올바르게 추상화한 후 추상화를 표현하기 위한 적절한 객체와 클래스 계층을 만드는 데에 있다. 추상화란 프로그래밍 방식으로 어떻게 구현될 것인지 알지 못해도 이해할 수 있게끔 무언가를 단순하게 설명해 놓은 것이다.

대부분의 복잡한 SW 설계는 좀 더 상세하게 여러 수준으로 나누어져 있으며 이들의 계층 구조를 다른 방법으로 설명할 수 있다. 모든 시스템이 갖는 두 가지 중요한 계층 구조는 아래와 같다.

1. 객체 계층 구조 : 서로 다른 객체들이 시스템에서 어떻게 협업하는지 설명
2. 클래스 계층 구조 : 관련된 객체들이 서로 공유하는 공통 구조와 행동을 설명하며 객체 속성의 일반화와 특수화를 보여준다.

Tip) API설계는 최상위 수준의 아키텍처와 상세 클래스 계층 구조를 만드는 과정을 통해 진행된다.


4.5 아키텍처 설계

SW 아키텍처는 아직 다듬어지지 않은 전체 시스템의 구조를 설명하며 API의 최상위 수준에 위치한 객체들과 그들 간의 관계를 모아 놓은 것이다.

4.5.1 아키텍처 수립

최상위 수준에서 API 아키텍처를 생성하는 과정은 아래와 같다.
1. 아키텍처에 영향을 미치는 기능적 요구사항 분석
2. 아키텍처의 제약사항 식별
3. 시스템의 주요 객체 생성과 그들 간의 관계 수립
4. 아키텍처에 대한 논의와 문서화

위의 과정을 다 거치더라도 한번에 완벽한 아키텍처를 만들 수는 없다. SW설계는 반복적인 프로세스이다.

4.5.2 아키텍처의 제약사항

아키텍처에 영향을 미치는 요소와 제약사항이 존재하는지 반드시 확인해야 한다. 이를 전역 분석(global analysis) 과정이라고 부르며, 전역은 시스템 전체에 영향을 미치는 요소들을 합축하며 종종 상호 의존적이거나 모순된 집합을 가리키기도 한다.

Tip) 아키텍처를 설계할 때는 조직적, 환경적, 운영적 요소들이 갖는 다양한 제약사항에 부딪힌다.

시스템 설계에 어떤 변화가 발생할 수 있을지를 실질적인 관점에서 예측해 봐야 한다. 변화에도 대응할 수 있도록 시스템을 설계해야 한다.

의존성있는 API의 레퍼를 제공한다면, API가 의존하고 있는 다른 API의 변경이 발생한다 해도 래퍼 계층을 통해서 변화에 대응하며, API 버그를 우회하거나 특정 플랫폼에 구애받지 않게 개발할 수도 있다.


4.5.3 주요 추상화 객체 식별



4.5.4 핵심 객체 생성

시스템 내에서 핵심 추상화 객체를 분리하는 일은 어렵기 때문에 아래의 기법을 통해 핵심 객체집합으로 나누고 각 객체 간의 관계를 식별하자.

- 자연어
- 속성 : 비슷한 속성이나 조건으로 분류
- 행동 : 서로가 공유하는 동적인 행동에 따라 분류
- 프로토 타입 : 초기에 식별된 객체보다 일반화된 프로토 타입을 발견하여 사용
- 도메인(슬래어-맬러) : 포괄적 도메인을 만들기 위해 처음에 수평으로 분류 후 각각의 도메인에 다른 분석 방법을 적용시켜 다시 수직으로 분류하는 SW설계 방법
- 도메인(네이버스) : 문제 도메인 안에서 관련 시스템들을 분석하고 버그 추적 시스템에 저장된 공통 요소나 관련성 있는 프로그램의 공통적 특징을 찾는 방식으로 발견
- 도메인(에반스) : 핵심 비즈니스 개념의 발전적 모델을 사용해서 복잡한 시스템을 설계하는데 사용

Tip) API의 핵심 객체 식별은 분명 어려운 일이다. 그렇기 때문에 반복적으로 다른 시각에서 문제를 살펴보고 모델을 새로 정의하는 노력을 계속해야 한다.


4.5.5 아키텍처 관점 패턴

SW 패턴의 또 다른 종류인 아키텍처 관점 패턴은 전체 시스템에 관한 대규모의 구조와 구성을 설명하는데 사용된다.
- 구조적 패턴 : 레이어, 파이프와 필터 그리고 블랙보드
- 상호작용 시스템 : 모델-뷰-컨트롤러(MVC), 모델-뷰-프리젠터(MVP), 프리젠테이션-추상화-컨트롤
- 분산 시스템 : 클라이언트/서버, 3-계층, P2P, 브로커
- 어댑터 시스템 : 마이크로-커널, 리플렉션

하나의 API라 할지라도 물리적으로 아키텍처를 분리하는 것이 좋다.
1. 문자열 조작이나 수학 함수 또는 스레드 모델 등의 API에 중립적인 저수준의 루틴
2. API의 기본적인 함수를 구현하는 핵심 비지니스 로직
3. 사용자가 API의 기능성을 확장시킬 수 있는 플러그인 또는 스크립팅 API
4. 핵심 API를 기반으로 구현된 편리함을 제공하는 API
5. API의 결과를 시각적으로 표현하는 프레젠테이션 계층

중요한 것은, 시스템의 계층 사이에는 엄격한 의존 관계를 적용해야 한다. 그렇지 않으면 두 계층 간의 의존성 순환고리가 발생한다. 각 계층 안에 자리잡은 구성요소들도 마찬가지다.

Tip) API를 개발할 때는 두 컴포넌트 간의 의존성 순환을 피하라.


4.5.6 아키텍처 논의

아키텍처를 문서화할 때는 반드시 그 설계에 대한 근거를 명시해야 한다. 대체 가능한 방법과 무엇을 얻기 위해 무엇을 포기했는지 그리고 어떻게 해서 최종 의사 결정이 결과로 반영됐는지 등이 여기에 포함된다.

Tip) 사용자가 참조할 문서에 고수준의 아키텍처와 API 설계에 관한 이유들을 설명하라.

아키텍트는 적극적인 대화의 중계자가 되어 엔지니어의 질문에 충실히 답할 수 있도록 문서화 작업을 주도해야 하며, 문서가 전체적인 아키텍처의 관점에서 설계와 관련된 의사소통을 지속적인  이어 갈 수 있는 가장 효율적인 방법이 되도록 노력해야 한다.


4.6 클래스 설계

클래스 설계 작업은 최상위 아키텍처를 설계하는 것과 달리 설계를 구체화하는 과정이다. 클라이언트가 사용할 실제 클래스를 식별하고 어떻게 클래스들이 서로 연결되는지, 주요 함수와 애트리뷰트는 무엇인지 명시해야 한다.

Tip) 여러분이 개발 중인 API에서 80%의 기능을 정의하는 20%의 클래스를 설계하는데 초점을 맞춰라.


4.6.1 객체 지향 개념

: 클래스, 객체, 캡슐화, 상속, 구성, 다형성


4.6.2 클래스 설계의 선택 사항

- 상속의 사용
- 구성의 사용 : 상속하기 보단 구성을 통해 포함해야 하는가?
- 추상 인터페이스의 사용
- 표준 설계 패턴의 사용
- 초기화 및 소멸 모델
- 복사 생성자와 할당 연산자의 정의
- 템플릿의 사용
- const와 explict의 사용
- 연산자 정의
- 타입 강제성 정의 : 올바른 형 변환 연선자 선언
- Friend의 사용
- 비 기능적 제약사항 : 성능과 메모리 사용등


4.6.3 상속 사용

- 상속 또는 상속 금지에 관한 설계
- 적합한 경우에만 상속을 사용
- 깊은 상속 트리는 피한다
- 서브 클래스에서 구현 코드를 제공하도록 순수 가상 멤버 함수를 사용한다
- 기존의 인터페이스에 새로운 순수 가상 함수를 추가하지 않는다
- 과도한 설계 확장은 피한다
- 인터페이스와 혼합형 클래스를 제외하고는 다중 상속을 피하라


4.6.4 리스코브 대리 원칙

LSP(Liskov Substitution Principle)라 불리는 이 원칙은 만약 S가 T의 서브 클래스라면 T 형식의 객체들은 행동의 변경 없이도 S 타입의 객체로 교체 가능하다.
만약 S가 T 종류를 좀 더 구체적으로 명시한다면 S는 T의 하위 타입이라고 생각할 수 있다.

Tip) 리스코브 대리 원칙(LSP)는 파생 클래스는 어떠한 상황에서도 행동을 변경하지 ㅇ낳고 베이스 클래스를 대신할 수 있어야 한다고 강조한다.

Private 상속
다른 클래스의 public 인터페이스 아닌 기능만 상속받는다. 만약 Circle 클래스에서 Ellipse 클래스의 public, protected 메서드를 노출하고 싶다면 아래의 방법을 사용한다.

class Circle : private Ellipse
{
public :
   ...
   //Ellipse의 public 메서드 노출
   using Ellipse::GetMajorRadius;
   using Ellipse::getMinorRadius;
};


구성(Composition)
T에서 S를 상속받기 보다, S에서 T를 private 데이터 멤버로 선언하거나 (has-a) S의 멤버 변수로(holds-a) T의 포인터나 참조를 선언하는 것이다.

class Circle
{
private :
   Ellipse mEllipse;
}

상속보다는 구성을 사용하라. 첫 번째 이유는 설계 시 상속은 좀 더 의존성이 강한 관계를 만든다. 또한, 다른 객체의 포인터만 가지고 있다면 인터페이스에 #include를 사용해서 전체 정의를 포함할 필요 없이 클래스를 미리 선언해 놓기만 하면 된다.


4.6.5 개방/폐쇄 원칙

버트랜드 메이어(Bertrand Meyer)는 클래스는 확장에 열려 있어야 하고 변경에 닫혀 있어야 한다는 개방/폐쇄의 원칙을 소개했다. 줄여서, OCP의 원칙은 클래스를 구현한 후 한번 배포하고 나면 버그를 수정하는 경우에만 클래스를 변경해야 하고 새로운 기능의 추가나 변경에 대해서는 새로운 클래스를 추가시켜 행동을 정의해야 한다는 것이다.

OCP 원칙을 토대 삼아 현실적으로 필요한 원칙은 클라이언트의 코드에는 영향을 미치지 않고 구현 로직을 유연하게 변경할 수 있도록 인터페이스를 안정적으로 유지보수하는 것이다. 확장 가능한 회귀 테스트는 사용자에게 영향을 미치지 않고 내부 코드를 변경시킬 수 있는 방법을 제공하며, 올바른 플러그인 아키텍처 역시 클라이언트가 다양한 기능을 확장시킬 수 있도록 유연성을 제공한다.

Tip) API는 조화롭지 못한 인터페이스의 변경에는 폐쇄적이어야 하며, 반대로 기능의 확장에는 개방적이어야 한다.


4.6.6 데메테르의 법칙

최소한의 지식 원칙(Principle of Least Knowledge)으로 알려진 데메테르의 법칙(LOD, Law of Demeter)은 느슨하게 연결된 설계에 관한 가이드 라인을 제공한다.

LOD에 객체 지향의 설계 개념을 적용해 보면, 함수는
- 같은 클래스에서 다른 함수를 호출할 수 있다.
- 같은 클래스의 데이터 멤버가 가진 함수를 호출할 수 있다.
- 함수가 전달받은 모든 파라미터의 함수를 호출할 수 있다.
- 함수가 지역적으로 생성한 객체의 함수를 호출할 수 있다.
- 전역 객체의 함수를 호출할 수 있다.

Tip) 데메테르의 법(Lod)은 자기 클래스의 함수만을 호출하거나 아주 밀접한 객체의 함수만 호출해야 한다고 강조한다.


4.6.7 클래스 이름

- 클래스 이름은 단순하고 강력하면서도 예상 가능하고 자기 설명적이어야 한다.
- 클래스는 한 가지만 잘해야 하고 클래스 이름은 바로 자신의 목적을 표현할 수 있어야 한다.
- 인터페이스는 객체 모델에서 대부분 형용사로 이름을 지어지며, 공통적으로 'I'라는 접두어를 사용해서 명명하기도 한다.
- 이해하기 힘든 축약형 명명법은 피하는 것이 좋다.
- 클래스나 자유 함수 같은 최상위 심벌의 경우에는 네임 스페이스 형태를 일부 포함하는 것이 좋다. 그래야 다른 API와 이름이 충돌하는 상황을 피할 수 있다.


4.7 함수 설계

4.7.1 함수 설계의 선택사항

- 정적 함수 대 비 정적 함수
- 파라미터를 값 또는 참조 혹은 포인터로 전달
- 파라미터를 상수 또는 변수로 전달
- 기본 값을 가진 파라미터 사용 가능
- 값 또는 참조, 포인터로 결과 리턴
- 상수 또는 변수로 결과 리턴
- 연산자 함수 또는 비 연산자 ㅎ마수
- 예외 명세 사용

멤버 함수에 대해서는 자유 함수에 대한 선택사항도 고려해야 한다
- 가상 멤버 함수 대 비 가상 멤버 함수
- 순수 가상 멤버 함수 대 비순수 가상 멤버 함수
-상수 멤버 함수 대 변수 멤버 함수
- public, protected 또는 private 멤버 함수
- 기본 생성자에 대한 명시적 키워드 사용

조직화에 대한 애트리뷰트도 존재한다
- 프렌드 함수 대 비-프렌드 함수
- 인라인 함수 대 비-인라인 함수

객체를 변경하지 않는 다는 것을 알리기 위해 멤버 함수를 상수로 선언하자.
예상치 못하게 비기본 생성자에서 발생하는 오류를 피하기 위해 explicit 키워드를 사용하자.


4.7.2 함수 이름

- 값을 할당하거나 리턴하는 함수는 Get 또는 Set과 같은 표준 접두어를 사용하자
- yes/No를 리턴하는 함수의 경우에는 IsEnabled(), ArePrependicular(), HasChildren()등과 같이 Is, Are, Has같은 적절한 접두어를 사용하자.
- 어떤 동작을 실행하는 함수의 경우 Enable(), Print() 또는 Save()와 같은 분명한 동사 형태를 띠는 것이 좋다. 자유 함수의 이름을 짓는다면 FileOpen(), FormatString()
- 부정적이기 보다는 긍적적인 방향으로 이름을 짓자. IsUnconnected()보다는 IsConnected()
- 함수 이름은 구현된 코드가 무엇을 하는지 잘 설명할 수 있어야 한다. 메서드는 SharpenImage()보다는 SharpenAndSaveImage()로
- 축약형은 피해야 한다
- 자연히 두개가 하나의 쌍으로 묶여 함수의 경우 상호 보완적인 용어를 사용해야 한다.
Add/Remove   Begine/End   Create/Destroy   Enable/Disable   Insert/Delete   Lock/Unlock   Next/Previous   Open/Close   Push/Pop   Send/Receive   Show/Hide   Source/Target


4.7.3 함수 파라미터

많은 수의 파라미터는 피하라, 맵이나 구조체를 파라미터로 사용하는 방법도 생각해 보자.

명명된 파라미터 이디엄(NPI, Named Parameter Idiom)은 계속해서 함수를 열거하는 방법을 제공하기 때문에 적은 코드로 원하는 기능을 구현할 수 있게 해준다.
QTimer timer = QTimer().setInterval(1000).setSingleShot(true).start();


4.7.4 예외 처리

오류를 다루는 주요 세가지 방법은 1. 오류코드를 리턴한다. 2. 예외를 던진다. 3. 프로그램을 종료시킨다.

Tip) 일관된 오류 처리 방법을 적용하고 잘 문서화 하라.

일반적으로 오류코드는 함수의 실행 결과로 리턴되는데 예를 들어 많은 수의 Win32 함수는 HRESULT 데이터 타입으로 오류를 리턴한다.

클라이언트에 오류가 발생하는 경우를 전달하기 위해 예외를 던지는 몇 개의 Boost 라이브러리를 생각해 볼 수 있는데, boost::iostreams와 boost::program_options 라이브러리가 여기에 해당된다.

API가 실행 결과뿐만 아니라 오류 코드도 같이 리턴하게 만들고 싶은 딜레마에 빠질 수 있는데, 일반적으로 이런 상황에서는 오류 코드를 함수의 리턴 값으로 전달하고 out 파라미터를 통해서 함수의 실제 실행 결과를 전달할 수 있다.

함수의 실행 결과로 boost::tuple를 사용해서 한 번에 여러 개의 리턴 값을 리턴할 수 있다.

예외 처리는 단순히 발생한 오류 코드만이 아니라 그와 관련된 구체적인 정보들을 제공하는데, 예를 들어 STL의 예외 처리는 what() 메서드를 통해서 개발자가 읽기 쉬운 설명들을 제공한다.

만약 예상치 못한 상황을 대비해서 예외 처리를 적용하기로 결정했다면 아래에 나열한 내용들을 잘 참조하기 바란다.
- std::exception을 상속받아 예외 클래스를 생성하고 what()메서드를 구현해서 오류 확인
- 예외를 안전하게 처리하도록 RAII의 사용 고려
- 함수가 리턴할 수 있는 발생 가능한 예외들을 모두 주석처리
- 함수가 던질 수 있는 예외를 문서화 하기 위해 예외 지정을 사용하고 싶을 수 있는데, 런타임 시에 컴파일런느 이 예외 지정을 제약 조건으로 강제하기 때문에 아래와 같은 예외 지정은 사용하지 않는 것이 좋다.
ex)
void MyFunc1() throw(); //예외를 던지지 않는다.
void MyFunc2() throw(A,B) //A또는 B를 던진다.
- catch(...)는 피하는게 좋다 assert()나 세분화한 오류를 던질 때 일부 컴파일러는 예외를 발생시키기 때문이다.
- 만약 하나 이상의 예외 베이스 클래스를 상속 받아 코드를 구현해야 한다면 클라이언트가 예외를 잡을 때 오류를 애매모호하게 처리되지 않도록 가상 상속을 통해서 코드를 구현해야 한다.

가장 좋은 오류 보고 방법은 일단 오류가 발생하면 가장 빨리 API 실행을 중지하고 오류 전에 할당된 자원을 해제하는 것을 포함해서 아직 완결되지 않은 중간 상태의 값들을 정리하는 것이다.

0 개의 댓글:

댓글 쓰기