[C++]CppCoreGuideLine(2) - Philosophy
- 원문 출처 : https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#S-philosophy
-------------------------------------------------------------------------------------------------------------------------------------
개요
다른 가이드라인을 정하기 전에 일반적인 규칙부터 정의한다. 철학적으로 제시되는 규칙들은 기계적으로 체크할 수는 없다. 하지만 이런 철학적은 주제를 반영하는 규칙들은 기계적으로 체크할 수 있다. 철학적인 근거를 납득할 수 없으면 이 후에 제시되는 구체적인 규칙들의 근거가 부족할 것이다.
P.1 : 생각을 코드로 직접 표현해라
컴파일러가 주석이나 문서를 읽지 않는것 처럼 많은 프로그래머들도 그렇다. 코드로 표현된다는 것은 의미를 정의한다는 것이고, 다른 컴파일러나 도구 또한 알아야 한다.
P.2 : C++ ISO 표준을 사용해라
P.3 : 표현식에 의도를 표현하자
어떤 코드의 의도가 네이밍이나 주석으로 서술되어 있지 않으면 이 코드의 목적이 뭔지 알 수 없다. 표현식으로 이 코드의 의도가 무엇인지 알수 있게 해야 한다.
예시
1 2 3 4 | gsl::index i = 0; while (i < v.size()) { // ... do something with v[i] ... } | cs |
위의 예시에서는 단순히 v의 요소들을 루프 도는 의도가 표현되어 있지 않다. index 구성 변수 i가 loop 밖에 선언되어 있고, 의도가 불분명한다. 코드를 읽는 사람은 이 부분만 봐서는 의도를 파악하기 힘들다.
개선
1 | for (const auto& x : v) { /* do something with the value of x */ } | cs |
위의 코드에서는 루프를 돌기위한 외부 작업이 없고, const refernece를 보니, 루프 작업시 수정 작업이 없다는 것을 확인할 수 있다. 만약 수정 작업이 요구되면 아래와 같이 표현하면 된다
1 | for (auto& x : v) { /* do something with the value of x */ } | cs |
P.4 : 프로그램은 Type-Safe 해야한다.
P.5 : 런타임 체크보다는 컴파일 타임 체크를 선호하자.
컴파일 타임에 에러가 잡히면 에러 핸들링을 할 필요가 없다. 이 경우 코드가 명확해지고 퍼포먼스가 증가한다.
P.6 : 컴파일 타임에 체크되지 않는건 런타임에 체크될 수 있어야 한다.
프로그램에서 에러를 찾기 힘들게 하면 크래시나 원치 않은 결과가 나타난다. 이상적으로는 컴파일타임이나 런타임에 모든 에러를 체크하고 처리할 수 있다. 하지만 모든 에러를 컴파일 타임에 처리하는 것은 불가능하고, 런타임 처리도 모두 잡을수는 없을 것이다. 하지만 충분한 자원을 가지고 원칙적으로 점검할 수 있는 프로그램을 작성해야 한다.
나쁜 예시
1 2 3 4 5 6 7 8 | // separately compiled, possibly dynamically loaded extern void f(int* p); void g(int n) { // bad: the number of elements is not passed to f() f(new int[n]); } | cs |
p.7 : 런타임 에러는 최대한 일찍 체크하자.
P.8 : 메모리 누수는 어떤 리소스던 발생시키면 안된다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | // Bad Exapmle void f(char* name) { FILE* input = fopen(name, "r"); // ... if (something) return; // bad: if something == true, a file handle is leaked // ... fclose(input); } =========================================================================================== // Prefer RAII void f(char* name) { ifstream input {name}; // ... if (something) return; // OK: no leak // ... } | cs |
P.9 : 시간이나 공간을 낭비하지 마라
지금 쓰는건 C++다. (역주 : 간지난다. C++ 개발자들이 다른 언어들에 비해 느끼는 자부심이 돋보인다.)
나쁜 예시
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | struct X { char ch; int i; string s; char ch2; X& operator=(const X& a); X(const X&); }; X waste(const char* p) { if (!p) throw Nullptr_error{}; int n = strlen(p); auto buf = new char[n]; if (!buf) throw Allocation_error{}; for (int i = 0; i < n; ++i) buf[i] = p[i]; // ... manipulate buffer ... X x; x.ch = 'a'; x.s = string(n); // give x.s space for *p for (gsl::index i = 0; i < x.s.size(); ++i) x.s[i] = buf[i]; // copy buf into x.s delete[] buf; return x; } void driver() { X x = waste("Typical argument"); // ... } | cs |
P.10 : 변경 불가능한 변수를 선호해라.
P.11 : 코드를 파편화 시키지 말고, 지저분한 구조를 캡슐화 해라.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | // Bad Example int sz = 100; int* p = (int*) malloc(sizeof(int) * sz); int count = 0; // ... for (;;) { // ... read an int into x, exit loop if end of file is reached ... // ... check that x is valid ... if (count == sz) p = (int*) realloc(p, sizeof(int) * sz * 2); p[count++] = x; // ... } ================================================================ // Better. Use vector vector<int> v; v.reserve(100); // ... for (int x; cin >> x; ) { // ... check that x is valid ... v.push_back(x); } | cs |
P.12 : 적절한 지원 도구를 사용해라
P.13 : 필요에 따라 적절한 라이브러리를 사용해라.
잘 디자인, 문서화 되어 있는 라이브러리를 사용하면 시간과 노력을 절약할수 있다. 보통 문서화하는 시간이 코드를 작성하는 시간 만큼 많은 시간을 필요로 한다.
예시
1 | std::sort(begin(v), end(v), std::greater<>()); | cs |
정렬 알고리즘의 전문가가 아니거나 엄청난 시간을 정렬 시간에 투자하지 않았으면 대부분 위의 함수가 정확하고 꽤 빠른 답을 찾아줄 것이다. 표준 라이브러리를 써야하는 이유보다는 안써야 되는 이유를 찾아야 할것이다.