- 원문 출처 : https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#S-interfaces
-------------------------------------------------------------------------------------------------------------------------------------
Interfaces
인터페이스는 프로그램의 두 부분이 맺고있는 계약이다. 계약에는 한 서비스의 공급자와 사용자가 기대하는 것을 정확하게 기술하는 것이 필수적이다. 좋은 인터페이스란 서로간의 계약이 잘 맺어진 인터페이스다. 좋은 인터페이스는 이해하기 쉽고, 효율적인 사용을 장려하고, 오류 발생 가능성이 낮춘다. 좋은 인터페이스를 가지는 것은 코드 조직에 가장 중요한 요소일 것이다.
I.1 : 인터페이스를 명확하게 설정해라.
인터페이스에 명시되어 있지 않은 가정은 쉽게 간과될 수 있고, 테스트 하기 힘들어 진다.
나쁜 예시
1 2 3 4 | int round(double d) { return (round_up) ? ceil(d) : d; // don't: "invisible" dependency } | cs |
I.2 : const 가 아닌 글로벌 변수는 피해라.
const가 아니는 글로벌 변수는 독립성을 해치고, 종속성은 예측 불가능한 변화를 일으킨다.
I.3 : 싱글톤 객체는 피해라.
싱글톤 객체는 글로벌 객체이다.
I.4 : 인터페이스를 정확하고 명확한 타입으로 전달해라.
타입은 가장 간단하고 좋은 문서이다. 타입이 잘 선언된 경우 가독성을 향상시키고, 컴파일 시간에 확인할 수 있다. 또한 정밀하게 입력된 코드는 더 최적화가 잘되어 있는 경우가 많다.
1 2 3 4 5 6 7 8 9 10 11 12 | // Bad Example draw_rect(100, 200, 100, 500); // what do the numbers specify? draw_rect(p.x, p.y, 10, 20); // what units are 10 and 20 in? ============================================ // Better void draw_rectangle(Point top_left, Point bottom_right); void draw_rectangle(Point top_left, Size height_width); draw_rectangle(p, Point{10, 20}); // two corners draw_rectangle(p, Size{10, 20}); // one corner and a (height, width) pair | cs |
I.5 : (있다면) 전제조건을 명시해라
함수로 전달되는 파라미터는 함수에서 특정 조건을 만족해야만 의미가 있는 경우가 존재한다.
I.6 : 전제조건을 명시할 때 Expects() 사용을 권장한다.
Expects() 함수는 GSL 라이브러리로 사용할 수 있다.
I.7 : 사후 조건을 명시해라.
원하는 결과가 나오는지 체크하고, 혹시 올바른 결과가 나오지 않는다면 구현이 잘못되었는지 확인할 수 있도록 해야 한다.
I.8 : 사후 조건을 명시할 때 Ensure() 사용을 권장한다.
역주)
Expects와 Ensure는 C++20 이후에서 표준으로 정의 될 예정이다.
https://en.cppreference.com/w/cpp/language/attributes/contract
I.9 : 인터페이스가 템플릿이면, 해당 파라미터를 concepts 을 이용해 기록해라.
I.10 : 요구하는 작업 수행에 실패했음을 나타내기 위한 에러를 사용해라.
I.11 : 레퍼런스(T&)나 로우 포인터(T*)로 소유권을 이동시키지 마라.
객체의 소유권이 함수를 호출한 객체가 가지고 있는지, 함수를 가진 객체가 가지고 있는지 확실하지 않다면 메모리 릭이 발생하거나 댕글링 포인터가 발생할 것이다.
예시
1 2 3 4 5 6 | X* compute(args) // don't { X* res = new X{}; // ... return res; } | cs |
위의 예시에서 반환된 X는 누가 지워야 할까? compute 함수가 레퍼런스로 반환하면 더 골치아파 질것이다. 그냥 value 값으로 넘기는게 더 좋을 수 있다. (결과 반환값이 크다면 move 함수를 사용해라)
I.12 : null이 되어서는 안되는 포인터는 not_null을 이용해서 선언해라.
not_null 은 guidelines support library에 선언되어 있다.
I.13 : 배열을 단일 포인터로 전달하지 마라.
(포인터, 사이즈) 스타일의 인터페이스는 개발자가 실수하기 쉽다. 또한 배열을 가르키는 포인터는 함수를 부르는 객체가 배열의 사이즈를 결정해야 하기 때문에 특정 컨벤션에 의존적일 수 밖에 없다.
1 2 3 4 | void draw(Shape* p, int n); // poor interface; poor code Circle arr[10]; // ... draw(arr, 10); | cs |
I.22 : 글로벌 객체의 복잡한 초기화는 피해라.
I.23 : 함수 인자는 되도록 적게 해라.
인자가 많아질수록 복잡도는 높아진다. 넘겨줘야 하는 인자가 많아진다면 구조화 해서 넘겨주는 인자를 줄이는 것이 좋다.
I.24 : 동일한 타입의 관계 없는 변수들은 인접하지 않도록 해라.
동일한 타입의 인접한 변수는 바꿔서 넣기 쉽다.
1 2 3 4 5 | // Example. Bad void copy_n(T* p, T* q, int n); // copy from [p:p + n) to [q:q + n) // Better.. void copy_n(const T* p, T* q, int n); // copy from [p:p + n) to [q:q + n) | cs |
예외사항
인자 순서에 상관이 없다면 문제 없다.
1 2 | int max(int a, int b); | cs |
I.25 : 클래스 계층구조를 위해서는 함수를 추상 클래스로 만드는 것을 선호해라.
추상 클래스가 기본 클래스에 구현되어 있는것 보다 안정적일 가능성이 높다
I.26 : ABI 크로스 컴파일을 원하면 C스타일을 이용해라.
각 컴파일러는 클래스, 예외 처리, 기능 이름 및 기타 구현 세부사항에 대해 서로 다른 레이아웃을 구성한다.
I.27 : 라이브러리 ABI 안정성을 고려해야 한다면 Pimpl 이론을 사용해라.
private 데이터 변수는 클래스 레이아웃에 참여하고, 멤버 함수는 오버로드 확인되기 때문에 구현 세부정를 변경하려면 이 기능을 사용하는 클래스의 모든 부분을 다시 컴파일해야 한다. Pimpl 기법은 클래스의 변경에도 컴파일하지 않아도 되도록 분리할 수 있다.
예시
인터페이스 (widget.h)
1 2 3 4 5 6 7 8 9 10 11 12 | class widget { class impl; std::unique_ptr<impl> pimpl; public: void draw(); // public API that will be forwarded to the implementation widget(int); // defined in the implementation file ~widget(); // defined in the implementation file, where impl is a complete type widget(widget&&) = default; widget(const widget&) = delete; widget& operator=(widget&&); // defined in the implementation file widget& operator=(const widget&) = delete; }; | cs |
구현(widget.cpp)
1 2 3 4 5 6 7 8 9 10 | class widget::impl { int n; // private data public: void draw(const widget& w) { /* ... */ } impl(int n) : n(n) {} }; void widget::draw() { pimpl->draw(*this); } widget::widget(int n) : pimpl{std::make_unique<impl>(n)} {} widget::~widget() = default; widget& widget::operator=(widget&&) = default; | cs |
(역주) ABI란
- 참조 : https://stackoverflow.com/questions/2171177/what-is-an-application-binary-interface-abi
(역주) 함수 오버로드 확인(Overload Resolution)
- https://en.cppreference.com/w/cpp/language/overload_resolution
I.30 : 규칙을 깨야할 상황이 생기면 캡슐화해라.
'C++' 카테고리의 다른 글
[C++]CppCoreGuideLine(2) - Philosophy (0) | 2019.01.26 |
---|---|
[C++]CppCoreGuideLine(1) - Abstract (0) | 2019.01.24 |
[C++] std chrono (0) | 2019.01.19 |
[C++] Smart Pointer (0) | 2019.01.05 |