[C++] 코딩 스타일에 대하여
🌈 프로그래밍/C++

[C++] 코딩 스타일에 대하여

반응형

 

안녕하세요? 수구리입니다

 

 

지난 포스팅에서는 동적 스트링에 대한 마무리를 했습니다..!

 

 

2021.08.26 - [프로그래밍/C++] - [C++] 동적 스트링 Part 3

 

[C++] 동적 스트링 Part 3

안녕하세요! 드디어 이번 포스팅으로 2장까지 마무리 짓게 되었습니다. 아직 갈길은 멀지만 열심히 해보겠습니다. 2021.08.25 - [C++] - [C++] 동적 스트링 Part 2 [C++] 동적 스트링 Part 2 안녕하세요? 수구

tasddc.tistory.com

 

 

이번 포스팅은 코딩의 스타일에 대해서 알아볼 예정입니다.

 

 

당연하다고 느끼는 것이지만,, 혼자 할 때와는 다르게 어느 집단에 속하게 된다면

 

 

또는 협업을 진행할 때라면 코딩의 스타일에 대한 것들도 가볍게 넘어가면 안 된다고 생각합니다!

 

 

그래야 일을 진행함에 있어서 효율적으로 진행할 수 있기 때문이죠

 

 

3.1 코딩 스타일의 중요성

  • 이번 Chapter 3 에서는 다음과 같은 내용에 대하여 살펴볼 것이다.
    • 코드 문서화의 중요성, 다양한 주석 스타일
    • 분할의 의미와 적용법
    • 명명 규칙
    • 포매팅 규칙

 

3.1.1 생각해보기

  • 스타일 즉, 가독성이 뛰어난 코드를 작성하는 데는 시간이 많이 걸린다.
  • 어떤 간단한 프로그램을 작성하는데 시간은 오래 걸리지는 않지만 기능별로 분할하고, 주석도 달고 구조도 깔끔하게 할 만큼의 스타일에 투자할 가치가 있을까?
  • 하지만 이 작업은 개인적으로 중요하다고 생각한다. 누군가에게 보여주거나, 리뷰를 받는다고 했을 때, 코드를 이해하기 쉽게 작성한다면 좋을 것 같기 때문이다.

 

3.1.2 바람직한 스타일이란?

  • 딱히 정해진 정답은 없지만, 잘 작성된 코드에서는 다음과 같은 공통적인 특징이 있다.
    • 문서화
    • 분할
    • 명명 규칙
    • 언어 사용
    • 포매팅

 

3.2 코드 문서화

  • 문서화 : 소스 파일에 작성된 주석을 의미함.
  • 코드만 보았을 때 명백하게 드러나지 않는 사항을 표시하기 좋다.

 

3.2.1 주석을 작성하는 이유?

  • 주석을 잘 사용하면 좋지만, 그 이유에 대하여 생각해본 적은 없는 것 같다.
  • 그러므로 주석의 필요성을 제대로 파악해야 한다.
  • 다음으로는 주석을 작성하는 이유에 대해서 알아보자.

 

사용법을 알려주는 주석

  • 클라이언트 코드에 사용법을 알려주는 주석에 대하여 많이 보았을 것이다.
  • 어떤 함수를 사용하기 위해서는 어떠한 매개변수가 필요한지, 또 함수가 던지는 예외에 대한 내용들에 대한 내용이 주석으로 담겨 있다.
  • 주석을 포함할지 말지는 개발자의 온전한 몫이다.
  • 주석은 코드로 직접 표현하기 힘든 내용을 자연어로 표현하기 좋은 수단이다.
  • C++에서는 메서드를 선언할 때 반드시 리턴 타입을 지정해야 한다. 하지만, 구체적으로 무엇을 가리키는지는 표현할 방법이 없다.
  • 그럴 때 다음과 같이 주석을 포함하여 표현을 해준다.
// 리턴값 : int
//      저장된 레코드의 ID를 표현하는 정수
// 발생 가능 예외:
//      openDatabase()를 먼저 호출하지 않은 상태에서
//      아래의 메서드를 호출하면 DatabaseNotOpenedException 발생
int saveRecord(Record& record);

 

코드의 알고리즘을 설명하는 주석

  • 실제 코드 안에 들어갈 주석도 잘 작성해야 한다.
  • 알고리즘 자체가 복잡하거나 작성 방식이 난해 또는 코드만 봐서는 이해하기 힘들 때 주로 사용한다.
/*
    삽입 정렬 알고리즘
    => 주어진 배열을 두 부분으로 나눠 하나는 정렬된 부분이고 다른 하나는 정렬되지 않은 부분
    => 각 원소의 위치는 1부터 시작, 모든 원소를 차례로 검사한다.
    => 배열의 앞부분은 모두 정렬된 부분이므로 현재 원소를 삽입할 정확한 지점을 찾을 때까지 각 원소를 하나씩 검사.
    => 마지막 원소까지 알고리즘을 수행하면 전체 배열이 정렬
*/
void sort(int inArray[], size_t inSize){
    // 위치 1부터 시작해서 모든 원소를 검사
    for (size_t i = 1; i < inSize; i++) {
        // 불변 속성 : 0부터 i-1 사이에 있는 원소(경계값 포함)는 모두 정렬된 상태.
        int element = inArray[i];
        // j는 정렬된 부분의 마지막지점을 가리리며, 그 뒤에 원소를 추가
        size_t j = i - 1;
        // 정렬된 배열에서 현재 위치가 이 원소보다 높다면 오른쪽 자리에 원소를 삽입할 자리를 확보하도록 값을 오른쪽으로 이동
        while (j >= 0 && inArray[j] > element) {
            inArray[j+1] = inArray[j];
            j--;
        }
        // 이 시점에서 정렬된 배열의 현재 위치는 현재 원소보다 높지 않음.
        // 따라서 이 자리가 원소의 새 위치가 된다.
        inArray[j+1] = element;
    }
}

 

메타 정보를 제공하는 주석

  • 코드의 내용과는 다른 차원의 정보를 제공하기 위한 목적으로 사용
  • 코드 생성과 관련된 세부사항만 표현한다.
  • 파일의 앞부분에 변경내역도 추가할 수 있다.
  • 저작권 문구도 메타 정보로 표현한다. 소스 파일마다 맨 앞에 항상 저작권 문구를 적도록 하는 회사도 있다.
/*
    저자 : hong
    작성일 : 211111
    기능 : PRD 버전 3, 기능 5.10
*/
RecordID saveRecord(Record& record) {
    if (!mDatabaseOpen) {
        throw DatabaseNotOpenedException();
    }
    RecordID id = getDB() -> saveRecord(record);
    if (id == -1) return -1; // 버그 #142를 해결하도록 추가한 문장 - kim
    record.setId(id);
    // TODO : setId() 에서 익셉션이 발생할 때 대처하기 - lee
    return id;
}

 

3.2.2 주석 스타일

  • 주석의 스타일은 조직마다 다르다. 코드 문서화에 대한 공통 표준에 반드시 특정 스타일을 따르도록 명시하기도 한다.
  • 이번 part에서는 다양한 주석의 스타일에 대해서 알아보자.

문장 단위의 주석

  • 모든 문장에 주석을 다는 행위로 복잡하고 지저분해 보일 뿐만 아니라 단순 노동이 되어버릴 수 있다.
  • 코드만 보아도 내용을 쉽게 알 수 있다면 작성을 스킵하자.
// 결과에 대한 모듈로 2의 결과가 0이면..
if (result % 2 == 0) { ... }

// 결과가 짝수면 ..
if (result % 2 == 0) { ... }
  • 위의 예제에서는 result 값에 % 2를 적용한 이유에 대한 주석이 좀 더 좋아 보인다.

 

머리말 주석

  • 파일을 새로 생성할 때 자동으로 머리말 주석이 나오는 템플릿을 작성하게 하는 개발 환경도 존재한다.
  • 서브버전(Subversion, SVN)을 비롯한 몇몇 소스 관리 시스템은 메타데이터를 추가하는 기능도 제공한다고 한다.
  • 예로 주석에 "$Id$"라는 스트링을 적어두면 그 자리에 작성자, 파일명, 리비전 번호 그리고 작성일자를 SVN이 넣어준다.

 

고정 양식 주석

  • 최근 주석을 외부 문서화 도구로 처리할 수 있도록 표준 양식에 따라 작성하는 사례가 늘어나고 있다.
  • 자바 프로그래머는 JavaDoc이란 도구, C++ 프로그래머는 Doxygen이라는 툴을 사용하여 코드에 주석을 인식하여 파싱 해준다.
  • 자동으로 문서를 생성하는 도구를 사용하더라도 의미 없는 주석이 담기지 않도록 주의해야 한다.

 

임의 주석

  • 정해진 형식과 관계없이 필요할 때마다 주석을 달 때가 있다. 아래와 같은 가이드라인을 따라야 한다.
    • 공격적이거나 무시하는 표현은 삼간다. 다른 사람이 언제든지 볼 수 있기 때문.
    • 팀원끼리만 이해하는 농담을 적어도 문제 되진 않지만, 관리자의 검토를 받는 것이 좋다.
    • 주석을 작성하기 전에 굳이 주석을 달 필요가 없도록 코드를 수정할 수 없는지 검토.
    • 누군가 여러분이 작성한 코드를 읽고 있다고 생각하고 쉽게 이해할 수 없는 부분마다 주석을 남긴다.
    • API를 사용하는 과정이 명확하지 않다면 사용한 API에 대한 참고 문헌을 남긴다.
    • 코드를 업데이트하면 주석도 반드시 업데이트해준다.
    • 한 함수를 여러 부분으로 나누기 위해 주석을 작성할 때, 그 함수를 더 작은 단위의 함수로 나눌 수 없는지 검토한다.

 

주석이 필요 없는 코드

  • 잘 작성된 코드는 대체로 주석이 적음. 좋은 코드는 읽기 쉬움.
  • 만약 모든 문장마다 설명이 필요하여 주석을 달아야 한다면?
  • 주석에서 표현하는 내용에 가깝게 코드를 수정할 수 없는지를 검토한다.
  • 주석이 필요없는 코드를 작성하기 위한 다른 방법으로는 분할이 있다.

 

3.3 코드 분할

  • 코드를 더 작은 단위로 나눠서 작성하는 방식이다.
  • 바람직한 형태는 함수나 메서드마다 한 가지 작업만 수행하는 것이다.
  • 하지만 분할의 기준을 명확하게 정의하기는 쉽지 않다.

 

3.3.1 리팩터링을 통한 코드 분할

  • 리팩터링이란? 코드의 구조를 재조정하는 작업이라고 한다. 리팩터링의 기법은 간단하게 다음과 같다.
    • 추상화 수준을 높이는 기법
      • 필드 캡슐화 : 필드를 private로 설정, getter & setter로 접근
      • 타입 일반화 : 코드를 좀 더 공유할 수 있도록 일반적인 타입 사용
    • 코드를 좀 더 논리적으로 분할하는 기법
      • 메서드 추출 : 거대한 메서드를 좀 더 이해하기 쉽도록 일부를 발췌하여 새로운 메서드로 정의
      • 클래스 추출 : 기존 클래스에 있는 코드 중 일부를 새 클래스로 옮긴다.
    • 명칭과 위치를 개선하는 기법
      • 메서드 및 필드 이동 : 좀 더 적합한 클래스나 소스 파일로 이동한다.
      • 메서드 및 필드 이름 변경 : 목적이 잘 드러나도록 이름을 바꾼다.
      • 올리기 : OOP에서 기본 클래스로 옮기는 기법
      • 내리기 : OOP에서 상속 클래스로 옮기는 기법
  • 리펙토링은 코드 분할을 수행할 좋은 기회이다.
  • 리펙토링 하는 과정에서 버그가 발생하는 부분을 찾을 수 있도록 테스팅 프레임워크(단위 테스트 등)를 사용하는 것이 좋다.

 

3.3.2 디자인 기준으로 코드 분할하기

  • 흔히 어떤 구상을 대략적으로 하고 곧바로 코드 에디터를 실행시켜서 타이핑을 해보기 마련이다. (나도 그렇다.)
  • 하지만 곧바로 코드부터 작성하지 말고 먼저 디자인부터 해야 한다.

 

3.4 명명 규칙

  • C++ 컴파일러의 명명 규칙
    • 이름의 첫 글자로 숫자가 나올 수 없다.
    • 더블 언더스코어는 특정 용도로 사용되기 때문에 이름에 넣을 수 없다.
    • 언더스코어로 시작하는 이름도 특정한 용도로 사용하기 때문에 쓸 수 없다.

 

3.4.1 좋은 이름과 나쁜 이름

  • 좋은 이름은 이름만 보아도 어떤 함수인지, 변수인지, 클래스인지를 바로 알 수 있는 것이 좋은 이름이라고 할 수 있다.
  • 이도 주석과 마찬가지로 확실히 정해진 답은 없으며, 조직 내부에서도 반드시 지키도록 정하기 나름이다.
// 좋은 이름
sourceName, destinationName // 객체 구분 명확
gSettings // 전역 변수
mNameCounter // 데이터 멤버 변수
calculateMarigoldOffset() // 간결 정확
mTypeString
errorMessage // 용도 명확
sourceFile, destinationFile // 준말 없음

// 나쁜 이름
this1, this2 // 광범위
mNC // 너무 짧음
doAction() // 광범위, 불분명
string // 용도를 알 수 없음
srcFile, dstFile // 준말

 

3.4.2 흔히 쓰는 명명 규칙

  • 하지만 위처럼 창의력을 발휘할 필요는 없다. 흔히 사용하는 규칙만 따라도 좋은 이름을 쉽게 지을 수 있다.
  • 다음으로는 이름에 대한 관례를 살펴보자.

 

카운터

  • 반복문에서 쉽게 볼 수 있는 변수에는 i, j 가 있다. 거의 관행으로 굳어져있다.
  • 2차원 행렬을 다룰 때에는 행과 열에 대한 인덱스로 row, column을 사용한다.
  • 하지만 반복문을 사용할 때 i번째, j번째를 헷갈리지 않도록 주의!

 

접두어

  • 변수의 이름 앞에 변수의 타입이나 용도를 암시하는 문자를 붙인다.
  • 이 방법은 시간이 갈수록 코드를 관리하기 힘들어져 별로 선호하지 않는 프로그래머도 있다고 한다.
  • 흔히 사용하는 몇 가지에 대해서 알아보자.
// m : member
mData, m_data // 클래스의 데이터 멤버

// s : static
sLookupTable, msLookupTable, ms_lookupTable // 정적 변수 또는 데이터 멤버

// k : konstant
kMaximumLength // 상숫값, 접두사를 사용않고 모두 대문자로도 사용함

// b : Boolean
bCompleted, is Completed 

// n : number
nLines, mNumLines // 카운터로 사용하는 데이터 멤버를 의미함.

 

헝가리안 표기법

  • MS 윈도 프로그래머가 변수와 데이터 멤버에 주로 적용하던 명명규칙이다.
  • 한 글자로 된 접두사 대신 정보를 좀 더 담도록 접두사를 길게 쓰는 방법이다.
// psz는 'null로 끝나는 스트링에 대한 포인터' 를 의미함.
char* pszName;

 

 

코드의 가독성을 해치지 않으면서 용도가 명확히 드러나는 이름이 좋은 이름이다!

게터와 세터

  • 클래스에 mStatus라고 private으로 정의된 멤버에 접근할 때에는 getStatus()와 setStatus()과 같은 게터와 세터를 사용한다.

 

3.5 언어의 기능에 스타일 적용하기

  • C++에서 제공하는 강력한 기능을 제대로 활용하려면 언어의 기능을 어떻게 활용해야 바람직한 스타일로 작성할 수 있는지 생각해야 함.
  • 즉, 결과를 예측할 수 있는 가독성이 좋은 코드를 작성해야 한다.

 

3.5.1 상수 사용법

  • 상수 기능을 활용하면 변하지 않는 값에 이름을 붙일 수 있다.
const double kApproximationForE = 2.718281828459045235;

 

3.5.2 포인터 대신 레퍼런스 사용하기

  • 포인터 대신 레퍼런스를 사용하면 좋은 점
    1. 메모리 주소를 직접 다루지 않고 nullptr가 될 수 없기 때문에 포인터보다 레퍼런스가 더 안전하다.
    2. 포인터를 전달한다고 해서 그 객체가 항상 수정되는 것이 아니다. 함수에서 객체를 수정하는지 알려면 함수의 원형을 봐야 한다. 따라서 코딩 스타일 측면에서 포인터보다 레퍼런스를 사용하는 것이 더 낫다.
    3. 메모리의 소유권을 명확히 표현할 수 있다.

참고로 메모리를 다룰 때는 1장에서 소개한 스마트 포인터를 사용하자.

 

3.5.3 사용자 정의 익셉션

  • C++에서는 익셉션을 무시하기 쉬운 편이다. 반드시 익셉션에 대한 처리 규칙이 없을뿐더러 처리하는 방식도 다양하기 때문.
  • 나중 14장에서 자세히..

언어에서 제공하는 기능은 프로그래머에게 도움을 주기 위한 것임. 이 기능들을 제대로 이해하여 활용하자.

 

3.6 포매팅

  • 같은 팀에 있는 프로그래머들이 같은 명명 규칙과 서식을 따르면 코드 통일성과 가독성을 높일 수 있음..
  • 논란이 있는 포매팅 방식에 대해서 알아보자.

 

3.6.1 중괄호 정렬에 관련된 논쟁

  • 코드 블록을 표시하는 중괄호를 적는 위치에 대한 논쟁이 있다.
  • 한 문장으로만 구성된 블록에도 중괄호를 사용해야 하는지?
  • 줄 사이의 간격은 최소한으로 사용하는지?

아무튼 중요 포인트는 코드를 슬쩍 보기만 해도 코드 블록이 한눈에 드러나야 한다!

 

 

3.6.2 스페이스와 소괄호에 대한 논쟁

  • 문장 단위에 적용되는 포매팅도 흔한 논쟁이다.

if (i == 2) {
    j = i + (k / m);
}

if ( i == 2 ) {
    j = i + k / m;
}
  • 어떤 것이 더 나은지는 취향이다. 단지, if는 함수가 아니라는 것!

 

3.6.3 스페이스와 탭

  • 스페이스와 탭은 명백히 다르다!
  • 이는 충분한 협의를 통해서 하나로 통일해야 한다. 그렇지 않으면 합쳐서 수행해야 하는 작업이 필요하다..

 

3.8 요약

이번 장에서 알아본 내용은 코드의 가독성을 높이는데 초점을 맞추고 어떤 스타일을 적용해야 적합한지에 대하여 알아보았다. 따라서, 팀 단위로 코드를 작성할 때 스타일에 관련된 이슈는 프로젝트의 언어와 도구를 선정하는 초기 단계에 결정해야 한다! 또한 스타일은 프로그래밍 단계에서 굉장히 중요한 요소라고 인지해야 한다. 자신이 작성한 코드를 공유하기 전에 반드시 스타일에 따라서 작성되었는지 검토하는 습관을 들이자!
반응형

'🌈 프로그래밍 > C++' 카테고리의 다른 글

[C++] OOP 디자인의 개념  (0) 2021.09.02
[C++] 전문가다운 디자인이란?  (2) 2021.08.30
[C++] 동적 스트링 Part 3  (4) 2021.08.26
[C++] 동적 스트링 Part 2  (0) 2021.08.25
[C++] 동적 스트링 Part 1  (2) 2021.08.24