출처 : http://eslife.tistory.com/entry/안전하고-좋은-냄새가-나는-C-코드-만들기
C++ 을 배운지 꽤 오래 되었지만, 아직도 코드 리뷰를 통해 다른 개발자들의 코드를 볼 때면 한숨이 나오는 경우가 많습니다.
이런 경우가 반복되다 보니, 예전에 몇 가지 코딩 규칙 비슷하게 만들어놓은 자료가 있어 포스팅해 봅니다. 자료가 만들다 말아 내용이 중구난방이네요.. 앞으로도 계속 내용 보강해 나가겠습니다.
▤ 포인트 핸들 및 윈도우 핸들 체크
- 함수로 전달된 포인터 인자나, new 로 생성한 포인터 등 모든 포인터는 사용하기 직전 포인터 검사를 수행
- 가급적 STL 컨테이너를 사용해서 아예 포인터를 사용하지 않는 것이 정신건강에 이롭다. (STL 자체적으로 메모리 생성 및 소멸)
- 메모리 LEAK 을 막기 위해 SMART POINT 와 같은 Wrapper 클래스를 만든다.
- (윈도우 프로그램의 경우)윈도우 API 호출 직전 항상 윈도우 핸들이 정상인지 IsWindow 와 같은 함수로 검사한다.
▤ DRY(Don't Repeat Your Code)원칙
- 같은 코드를 여기 저기 중복해서 사용하지 말아야 한다. (☞ 실용주의 프로그래머 참고). 중복해서 사용할 경우 코드 량이 늘어날 뿐만 아니라 유지보수 cost 가 같이 늘어 나고, 추후 추가/개선/변경 등의 요구사항으로 인한 모듈 수정 시 여러 소스를 함께 수정해야 한다.
- 동일한 코드를 공용 루틴(함수)으로 개발 – 2~3줄 짜리 복잡한 if 문도 bool 를 리턴 하는 함수로 개발(☞ Refactoring 참고)
▤ 클래스간 종속성 최소화
- 대규모 프로젝트에서는 공통모듈이 변경되어도 이를 사용하는 모듈이 문제가 없도록 반드시 공통모듈은 인터페이스 기반(데이터가 없는, virtual 가상함수로만 구성된)으로 구현한다. (☞ The C++ Programming Language 참고)
- A 클래스 → B 클래스 → C 클래스를 사용할 경우 각 클래스는 자기가 직접 다루는 클래스(A 는 B, B 는 C) 만 알아야 한다. 즉 A 클래스는 C 클래스를 직/간접적으로 include 해서는 안되며 C 클래스의 변경으로 인해 A 클래스가 컴파일 되어서는 안 된다. A 클래스가 C 를 직접 접근할 경우가 있다면 B 클래스에 위임함수를 만들어 해결한다. (☞ 실용주의 프로그래머 참고)
- B 클래스가 A 클래스로 어떤 데이터를 전달할 경우가 있다면(주로 이벤트 발생시 값을 전달하기 위해) A 클래스의 최소 인터페이스만을 B 클래스에 전달하여(COM 의 이벤트 소스 방식) 종속성을 최소화 한다. (☞ Developer’s Workshop To COM and ATL 3.0 참고)
▤ 함수 인자 전달
- 함수의 인자는 최대 7개를 넘지 않아야 한다(☞ Code Complete 2 참고)
- 클래스 인스턴스를 함수 인자로 넘겨서는 안 된다(특히 함수 인자로 CString 을 넘기는 경우가 많음. CString → const CString& 나 CString*, const char* 등으로 수정한다. )
▤ 오류조건을 먼저 검사하여 오류 체크로 인해 코드 가독성이 떨어지지 않도록 하자.
- 특정 오류조건이 발생하면 더 이상 함수 수행에 의미 없을 경우, 이러한 오류 조건들은 함수초입단계부터 먼저 확인해서 바로 리턴 한다
- 위와 같이 오류조건이 먼저 확인되면 프로그램 코드에서 if depth 가 줄어 들고 코드 읽기가 수월해 지는 장점이 있다.
Before |
After |
void CClass::OnTest() { ... ... if (m_pWndMain) { DoSomething1(); } ... if (m_pWndMain) { DoSomething2(); } } |
void CClass::OnTest() { if (m_pWndMain == NULL) return; ... ... DoSomething1(); ... DoSomething2(); } |
▤ 긴 함수 쪼개기 (☞ Refactoring 참고)
- 긴 함수는 작은 여러 개의 함수로 쪼갠다(Refactoring 책에 의하면 2-3줄 짜리 함수로까지 쪼개어서 코드 가독성을 높이도록 하고 있음)
- 작은 함수로 쪼개다 보면, 작은 함수를 재활용하게 될 수 있고
- 긴 함수보다 코드 읽기가 수월해 지며
- 결국 유지보수가 쉬워진다
▤ enum 값 선언 시 _START, _END 를 활용하자(☞ CODE COMPLETE2 참고)
- enum 으로 선언된 값들을 변수나 for loop 를 이용하여 반복 계산할 경우가 종종 있는데 이 경우 enum 시작과, 끝을 나타내는 값을 enum 선언에 포함하면 유용하게 사용할 수 있다.
- 추후 enum 에 값을 추가해야 할 경우 _END 값을 사용한 배열은 코드 변경없이 크기가 늘어남
- For 루프 등에 _START, _END 을 사용할 수 있어 유지 보수에 유리
Before |
After |
enum WEEK_NAME { SUNDAY = 0, MONDAY = 1, TUESDAY = 2, WEDNESDAY = 3, THURSDAY = 4, FRIDAY = 5, SATERDAY = 6, }; int nWeeks[SATERDAY + 1]; } |
enum WEEK_NAME { WEEK_STAR = 0, SUNDAY = 0, MONDAY = 1, TUESDAY = 2, WEDNESDAY = 3, THURSDAY = 4, FRIDAY = 5, SATERDAY = 6, WEEK_END = 6, }; int nWeeks[WEEK_END + 1]; |
▤ 복잡한 제어문을 가진 루틴을 피하자
- If 문은 2 depth 이상 들어 갈 경우 refactoring 하라 (☞ CODE COMPLETE2)
① 대부분 코드를 제대로 이해하지 못해 복잡한 코드를 작성하고 있다.
② 개발자 90%는 2 depth 이상 들어가는 if 문장을 이해하지 못한다
- IF~ELSE IF ~ELSE IF ~ …. ELSE 가 길게 이어질 경우 SWITCH 문으로 바꾼다
- IF 문이 길게 늘어 질 경우 함수로 분리한다
- C++ 의 factory 개념으로 상속을 이용한 클래스를 만드는 방법도 활용