작년부터 파트타임으로 박사과정에 진학 중이다.

이번 3학기에 신청한 데이터 마이닝 수업에서 알게된 내용을 정리하고자 한다.


데이터 마이닝 이란?

데이터로부터 잠재적으로 유용한 정보를 추출하는 것이다.

- 의미있는 패턴 추출을 위해 자동, 반자동 방법을 사용

아래의 그림처럼, 원시 데이터를 유용한 정보로 변환하는 전체 프로세스 중 데이터 마이닝은 필수인 부분이다.

지식 탐사 프로세스(KDD;Knowledge Discovery Database)

전처리(Preprocessing)의 목적은 원시 입력 데이터를 후속 분석에 적합한 형식으로 변환하는 것이다.

- 가장 힘들고 시간이 많이 걸리는 단계

후처리(Postprocessing)은 유효하고 유용한 결과만 의사 결정 지원 시스템에 통합되도록 보장한다.

- 시각화 등

왜 데이터 마이닝을 해야하는가?

데이터 생성과 수집의 발전으로 인해 엄청나게 많은 데이터가 빠른 속도로 저장되고 있다.

- 동시에 컴퓨팅 자원은 저렴해지면서 강력해지고 있다.

기존 기술들은 아래의 이유로 원시 데이터에 적용할 수 없다.

- Scalability, High dimensionality, Heterogeneous and complex data

데이터 마이닝은 과학자들에 도움이 된다.

- 데이터를 분할하는 것, 분류하는 것, 가설 형성

많은 학문이 융합된 데이터 마이닝

사회의 주요 문제들을 해결하기 위한 좋은 기회가 된다.

- 복지 정책 및 비용 감소

- 기후 변화의 영향 예측

- 대체 에너지 찾기 등

데이터 마이닝 작업

예측(Prediction) 작업 : 다른 속성(attribute)의 값을 기반으로 특정 속성의 값을 예측하는 것

- 예측될 속성 : 목표, 종속 변수 / 예측에 사용되는 속성 : 설명, 독립 변수

서술(Description) 작업 : 데이터를 서술하는 인간이 이해 가능한 패턴을 찾는 것

- 상관관계, 추세, 클러스터, 궤적 및 이상

4가지 핵심 데이터 마이닝 작업

예측 모델링(Predictive Modeling)

독립 변수의 함수로 대상 변수에 대한 모델을 작성하는 작업이다.

이러한 예측 모델에는 지도 학습(Supervised learning), 분류(classification)와 회귀(regression)가 있다.

분류 : 튜플(tuple)로 기록된 데이터 (attribute : x, predefined class : y) 에 대한 임의의 x를 통해 y 예측을 목표

General Approach for Building Classification Model

- Base Classifiers : Nearest-neighbor classifier, Decision Tree based Methods, Neural Networks, Deep Neural Networks, Naive Bayes and Bayesian Belief Networks, Support Vector Machines

- Ensemble Classifiers : Boosting, Bagging, Random Forests 

회귀 : 튜플로 기록된 데이터 (attribute : x, continuous value : y) 에 대한 임의의 x를 통해 y 예측을 목표

classification , regression

- Base : Linear regression, Logistic regression, Neural Networks, Support Vector Regression, ...

군집화(Clustering) 

동일한 군집에 속하는 관측치가 다른 군집에 속하는 관측치보다 서로 유사하도록 밀접하게 관련된 관측치 그룹을 찾는다.

내부 클러스터 (그룹) 안에 오브젝트들은 거리가 최소가 되도록, 군집 간 거리는 최대가 되도록 한다. 

- Base : K-means clustering, Hierarchical clustering, DBSCAN,...

연관 규칙(Association Rule)

데이터에서 강하게 연관된 기능을 설명하는 패턴을 발견하는데 사용된다.

가장 흥미로운 패턴을 효율적으로 추출한다.

Association Rule Discovery

이상 탐지(Anomaly/Outlier Detection)

나머지 데이터와 특성이 크게 다른 관측치를 식별하는 작업이다.

- 이러한 관찰을 이상치 또는 국외자(outlier)라고 함

- KNN distance-based method, Tree based method, Density-based method, SVM-based method, Autoencoder based method, Clustering-based method

'CSE > Data Mining' 카테고리의 다른 글

Data Mining - Data (2)  (0) 2025.03.08
Data Mining - Data (1)  (0) 2025.03.07

Formatting

모든 사람이 동일한 스타일을 사용하면 프로젝트를 훨씬 더 쉽게 따라갈 수 있다.

Line Length

한 줄의 길이는 최대 80자로 제한한다. (1960년대 기준)

하지만 다음과 같은 경우에는 80자를 초과할 수 있다.

  • 긴 URL이나 명령어가 포함된 주석 (잘못 나누면 복사, 붙여넣기가 어려움)
  • string literal 내의 중요한 데이터 (URL, JSON, SQL 등)
  • #include 문, 헤더 가드, using 선언

사용성/검색성과 가독성 사이에서 균형을 맞춰야 한다.

Non-ASCII Characters

되도록 사용을 피하되 필요한 경우 UTF-8 인코딩을 사용한다.

단, char16_t, char32_t, wchar_t는 사용하지 않는다.

std::string greeting = u8"こんにちは";  // 일본어 "안녕하세요"
std::string bom = "\xEF\xBB\xBF";  // UTF-8 BOM(Byte Order Mark)
std::string text = "안녕하세요";  // ✅ UTF-8 인코딩 사용

char16_t text[] = u"안녕하세요";  // ❌ char16_t 사용 금지
wchar_t text[] = L"Hello";       // ❌ wchar_t 사용 금지

Spaces vs. Tabs

들여쓰기는 space 2칸을 사용하며, tab은 절대 사용하지 않는다.

  • 에디터를 설정하여 Tab 키를 누르면 space가 삽입되도록 한다.

Function Declarations and Definitions

함수 선언과 정의는 가독성을 고려하여 적절히 정렬해야 한다.

  • 반환 타입은 함수 이름과 같은 줄에 배치하는 것이 원칙이다.
  • 매개변수가 많아 한 줄에 다 들어가지 않으면 줄을 나누어 정렬해야 한다.
  • 예시 1. 매개변수가 많을 경우 줄 나누기
ReturnType ClassName::ReallyLongFunctionName(Type par_name1, Type par_name2,
                                             Type par_name3) {
  DoSomething();
}
  • 예시 2. 함수 이름이 너무 길 경우
ReturnType LongClassName::ReallyReallyReallyLongFunctionName(
    Type par_name1,  // 4 space indent
    Type par_name2,
    Type par_name3) {
  DoSomething();  // 2 space indent
}

[[nodiscard]], ABSL_ATTRIBUTE_NOINLINE 같은 속성(Attribute)은 반환 타입 앞에 배치한다.

[[nodiscard]] bool IsValid();
ABSL_ATTRIBUTE_NOINLINE void ExpensiveFunction();

Lambda Expressions

람다의 매개변수와 본문은 일반 함수와 동일한 스타일을 적용해야 한다.

  • 짧은 람다는 인라인으로 사용할 수 있다.
  • 참조 캡쳐(&x)에는 공백을 두지 않는다.
int x = 0;
auto x_plus_n = [&x](int n) -> int { return x + n; };
  • 예시 1. 여러 줄 람다를 정렬
digits.erase(std::remove_if(digits.begin(), digits.end(),
                            [&to_remove](int i) {
                              return to_remove.contains(i);
                            }),
             digits.end());
  • 예시 2. 잘못된 예시 (들여쓰기 불일치)
digits.erase(std::remove_if(digits.begin(), digits.end(),
                            [&to_remove](int i) { return to_remove.contains(i); }),
             digits.end());  // ❌ 너무 길어서 가독성이 떨어짐.

Floating-point Literals

부동소수점에는 항상 소수점(radix point)를 포함해야한다.

  • 소수점 앞 뒤에는 항상 숫자가 있어야 함 (.5 ❌ → 0.5 ✅)
  • 지수 표기법(Exponential Notation)에서도 소수점을 포함 (1e6 ❌ → 1.0e6 ✅)
  • 정수를 부동소수점 변수에 할당하는 것은 가능하지만, 명확성을 위해 .0을 추가하는 것이 좋음
  • float, double, long double 타입에 맞는 접미사(f, L)를 사용

Function Calls

가능한 경우 함수 호출은 한 줄에 작성한다.

bool result = DoSomething(argument1, argument2, argument3); // ✅

bool result = DoSomething(
    argument1, argument2, argument3);  // ❌ 불필요한 줄 나누기

모든 인자가 한 줄에 들어가지 않을 경우, 첫 번째 인자와 정렬하거나 4칸 들여쓰기 한다.

// 첫 번째 인자와 정렬
bool result = DoSomething(averyveryveryverylongargument1,
                          argument2, argument3);
// 4칸 들여쓰기
bool result = DoSomething(
    argument1, argument2,  // 4 space indent
    argument3, argument4);

여는 괄호 ( 뒤와 닫는 괄호 ) 앞에는 공백을 추가하지 않는다.

가독성을 높이기 위해 복잡한 인자는 별도의 변수에 할당할 수도 있다.

bool result = DoSomething(scores[x] * y + bases[x], x, y, z);  // ❌ 가독성 낮음

// ✅ 1. 변수로 정리
int my_heuristic = scores[x] * y + bases[x];
bool result = DoSomething(my_heuristic, x, y, z);

// ✅ 2. 주석으로 의미 설명
bool result = DoSomething(scores[x] * y + bases[x],  // Score heuristic.
                          x, y, z);

행렬과 같이 논리적인 그룹이 있는 경우, 그 구조를 유지하면서 정렬할 수 있다.

// Transform the widget by a 3×3 matrix.
my_widget.Transform(x1, x2, x3,
                    y1, y2, y3,
                    z1, z2, z3);

Braced Initializer List Format

중괄호 {} 초기화는 함수 호출과 동일한 스타일을 적용해야 한다.

한 줄에 표현할 수 있으면 한 줄로 유지한다.

return {foo, bar};
functioncall({foo, bar});
std::pair<int, int> p{foo, bar};

여러 줄로 나누어야 할 경우, 첫 번째 요소와 정렬하거나 4칸 들여쓰기 한다.

// When you have to wrap.
SomeFunction(
    {"assume a zero-length name before {"},
    some_other_function_parameter);
    
SomeType variable{
    some, other, values,
    {"assume a zero-length name before {"},
    SomeOtherType{
        "Very long string requiring the surrounding breaks.",
        some, other, values},
    SomeOtherType{"Slightly shorter string",
                  some, other, values}};
                  
SomeType variable{
    "This is too long to fit all in one line"};

중첩된 리스트는 논리적인 그룹을 유지하면서 정렬해야 한다.

명확성을 위해 필요하면 중괄호 {}를 함수 호출처럼 정렬한다.

Looping and branching statements

if, else, switch, while, do, for 등의 키워드는 띄어쓰기를 포함해야 한다.

조건문 또는 반복문의 괄호 ( ) 내부에는 공백을 추가하지 않는다.

중괄호 {}는 항상 사용해야 하며, 열리는 { 뒤와 닫히는 } 앞에는 한 줄씩 추가한다.

  • 예시 1. 올바른 예시
if (condition) {  // ✅ 공백 형식 유지
  DoOneThing();
  DoAnotherThing();
} else if (int a = f(); a != 3) {  // ✅ 초기화된 변수 포함 가능
  DoAThirdThing(a);
} else {
  DoNothing();
}

// Good - the same rules apply to loops.
while (condition) {
  RepeatAThing();
}

// Good - the same rules apply to loops.
do {
  RepeatAThing();
} while (condition);

// Good - the same rules apply to loops.
for (int i = 0; i < 10; ++i) {
  RepeatAThing();
}
  • 예시 2. 잘못된 예시
if(condition) {}    // ❌ `if` 뒤에 공백 없음
else if ( condition ) {}  // ❌ 괄호 안쪽에 공백이 포함됨
else if(condition){}  // ❌ 여러 곳에서 공백 누락

if (condition)
  foo;  // ❌ 중괄호 `{}` 없으면 가독성이 떨어지고, 버그 가능성 증가
else {
  bar;
}

// Bad - `if` statement too long to omit braces.
if (condition)
  // Comment
  DoSomething();

// Bad - `if` statement too long to omit braces.
if (condition1 &&
    condition2)
  DoSomething();

if ... else, do ... while 문은 한 줄로 축약할 수 없다.

  • 간결한 경우 예외적으로 한줄로 표현 가능
if (x == kFoo) return new Foo();  // ✅ 짧고 간결한 경우만 허용
if (x == kBar)
  Bar(arg1, arg2, arg3);  // ✅ 한 줄 조건문, 다음 줄 실행문 가능

switch 문에서 case 블록은 {}를 포함할 수도 있고 생략할 수도 있다.

빈 루프(Empty Loop)는 {} 또는 continue;를 사용해야 한다.

while (condition) {}  // Good - `{}` indicates no logic.
while (condition) {
  // Comments are okay, too
}
while (condition) continue;  // Good - `continue` indicates no logic.

Pointer and Reference Expressions

멤버 접근 연산자(. 및 →) 주변에는 공백을 추가하지 않는다.

포인터(*), 참조(&)연산자는 뒤에 공백을 두지 않는다.

x = *p;
p = &x;
x = r.y;
x = r->y;

포인터 및 참조 선언에서 * 및 & 의 위치는 앞 또는 뒤에 둘 수 있다.

  • 일관성을 유지하는게 중요함

같은 선언 문에서 포인터/참조 변수와 일반 변수를 함께 선언하지 않는다.

int x, *y;  // ❌ 일반 변수와 포인터 변수 혼합 선언 금지
int* x, *y;  // ❌ `*`가 변수별로 다르게 적용되어 가독성 저하

템플릿에서 *> 처럼 * 뒤 공백을 생략해야 한다.

// These are fine, space preceding.
char *c;
const std::string &str;
int *GetPointer();
std::vector<char *>

// These are fine, space following (or elided).
char* c;
const std::string& str;
int* GetPointer();
std::vector<char*>  // Note no space between '*' and '>'

Boolean Expressions

긴 표현식을 줄바꿈할 때 연산자(&&, ||)의 위치를 일관되게 유지해야 한다.

연산자는 일반적으로 줄 끝에 배치하지만, 줄 시작에 배치하는 것도 허용된다.

불필요한 괄호 사용을 피하면서도, 가독성을 높이기 위해 적절한 괄호를 추가할 수 있다.

&&, || 등의 연산자는 and, or 같은 키워드 대신 기호(&&, ||)를 사용한다.

if (this_one_thing > this_other_thing &&
    a_third_thing == a_fourth_thing &&
    yet_another && last_one) {
  DoSomething();
}

if (this_one_thing > this_other_thing
    && a_third_thing == a_fourth_thing
    && yet_another && last_one) {  // ❌ 연산자가 줄 시작에 배치됨 -> 가독성 하락
  DoSomething();
}

Return Values

단순한 return 표현식에서는 불필요한 괄호를 사용하지 않는다.

return 문에서 괄호는 복잡한 표현식을 명확하게 하는 경우에만 사용한다.

return (some_long_condition &&
        another_condition);

return은 함수가 아니므로 return(result); 같은 표현은 잘못된 스타일이다.

  • return(result); ❌ → return result; ✅

Variable and Array Initialization

변수 초기화 시 =, (), {} 세 가지 스타일을 선택할 수 있다.

std::initializer_list 생성자가 있는 경우 {} 초기화 방식이 예상과 다르게 동작할 수 있다.

std::vector<int> v(100, 1);  // ✅ 100개의 요소가 1로 채워진 벡터
std::vector<int> v{100, 1};  // ✅ 2개의 요소를 가진 벡터 (100, 1)

std::vector<int> v{100};  // ❌ 벡터 크기 100을 의도했으나, 원소가 1개(100)만 있음

{} 초기화 방식은 암시적 변환(narrowing conversion)을 방지한다.

int pi(3.14);   // ✅ OK, pi == 3
int pi = 3.14;  // ✅ OK, pi == 3

int pi{3.14};  // ❌ 컴파일 오류: narrowing conversion 금지

기본 생성자를 호출하려면 {} 또는 ()를 사용할 수 있다.

std::vector<int> v1;     // ✅ 기본 생성자 호출 (빈 벡터)
std::vector<int> v2{};   // ✅ 기본 생성자 호출 (빈 벡터)
std::vector<int> v3();   // ❌ 함수 선언으로 해석됨 (Most Vexing Parse)

Preprocessor Directives

#include, #define, #ifdef, #endif 등 반드시 줄의 시작 부분에서 시작해야 한다.

코드 블록 내에 있어도 전처리기 지시문은 절대 들여쓰기(indentation)하지 않는다.

# 뒤에는 공백을 넣을 수도 있지만, 일반적으로 넣지 않는 것이 권장된다.

#if ~ #endif 블록을 사용할 때는 일관된 스타일을 유지해야 한다.

if (lopsided_score) {
#if DISASTER_PENDING  // ✅ 줄의 시작에서 시작
  DropEverything();
# if NOTIFY          // ✅ `#` 뒤에 공백을 넣어도 되지만 권장되진 않음
  NotifyClient();
# endif
#endif
  BackToNormal();
}

if (lopsided_score) {
    #if DISASTER_PENDING  // ❌ `#if`가 들여쓰기 됨
    DropEverything();
    #endif                // ❌ `#endif`도 들여쓰기 됨
    BackToNormal();
}

Class Format

public:, protected:, private: 키워드는 한 칸 들여쓰기한다.

public: → protected: → private: 순서로 배치한다.

public:, protected:, private: 키워드 앞에 빈 줄을 추가할 수 있지만, 첫 번째는 제외한다.

public: 이후 빈 줄을 두지 않는다.

기본적인 들여쓰기는 2칸을 사용한다.

class MyClass : public OtherClass {
 public:      // Note the 1 space indent!
  MyClass();  // Regular 2 space indent.
  explicit MyClass(int var);
  ~MyClass() {}

  void SomeFunction();
  void SomeFunctionThatDoesNothing() {
  }

  void set_some_var(int var) { some_var_ = var; }
  int some_var() const { return some_var_; }

 private:
  bool SomeInternalFunction();

  int some_var_;
  int some_other_var_;
};

클래스 상속 시, 베이스 클래스는 동일한 줄에 배치하며, 80자 제한을 고려한다.

  • 상속받을 때도 public→ protected→ private 순서를 유지
class DerivedClass : private BaseClass, public AnotherBase {  // ❌ `public`이 먼저 와야 함
 public:
  void SomeFunction();
};

Constructor Initializer Lists

모든 초기화 목록이 한 줄에 들어가면 그대로 유지한다.

MyClass::MyClass(int var) : some_var_(var) { // ✅ 올바른 예시 (한 줄 유지)
  DoSomething();
}

MyClass::MyClass(int var) 
    : some_var_(var) {  // ❌ 한 줄에 충분히 들어가므로 줄바꿈 불필요
  DoSomething();
}

여러 줄로 나눠야 할 경우, :(콜론) 앞에서 줄바꿈하고 4칸 들여쓰기한다.

MyClass::MyClass(int var)
    : some_var_(var), some_other_var_(var + 1) { // ✅ 올바른 예시 (줄바꿈 후 4칸 들여쓰기)
  DoSomething();
}

MyClass::MyClass(int var) : 
    some_var_(var), some_other_var_(var + 1) {  // ❌ `:` 뒤에서 줄바꿈 금지
  DoSomething();
}

여러 개의 멤버를 초기화하는 경우, 각 멤버를 개별 줄에 배치하고 정렬한다.

중괄호 {}는 한 줄에 배치할 수도 있고, 새로운 줄에 배치할 수도 있다.

  • 중괄호 {}는 :과 동일한 줄에 위치해야 함
MyClass::MyClass(int var) : some_var_(var) {}

MyClass::MyClass(int var)
    : some_var_(var) 
    {
  DoSomething();
}  // ❌ `{`의 위치가 잘못됨

Namespace Formatting

네임스페이스 내부의 코드(함수, 클래스, 변수 등)는 들여쓰기하지 않는다.

namespace {

void foo() {  // ✅ 네임스페이스 내부지만 추가 들여쓰기 없음
  DoSomething();
}

}  // namespace

네임스페이스 {}는 일반적인 코드 블록처럼 취급하지만, 내부 내용은 평소대로 들여쓴다.

namespace my_namespace {

void foo() {
  if (condition) {
    DoSomething();
  }
}

}  // namespace my_namespace

여러 개의 네임스페이스가 중첩될 경우, 들여쓰기 없이 같은 규칙을 적용한다.

namespace outer_namespace {
namespace inner_namespace {

void foo() {
  DoSomething();
}

}  // namespace inner_namespace
}  // namespace outer_namespace

Horizontal Whitespace

한 줄 끝에는 공백(trailing whitespace)을 추가하지 않는다.

// 주석 앞에는 공백 두 칸을 추가한다.

int i = 0;  // ✅ 공백 두 칸 유지
int i = 0;// ❌ 공백이 없음
int i = 0; // ❌ 공백 하나만 있음

{} 중괄호 앞에는 공백을 추가한다.

void f(bool b) {  // ✅ `{` 앞에 공백 포함
  ...
}

void f(bool b){  // ❌ `{` 앞에 공백이 없음
  ...
}

int x[] = { 0 };   // ✅ `{}` 내부 공백을 넣을 수도 있음
int x[] = {0};     // ✅ `{}` 내부 공백을 제거할 수도 있음

:(콜론)은 상속 및 초기화 목록에서 공백을 포함해야 한다.

class Foo : public Bar {  // ✅ `:` 앞뒤에 공백 포함
};

Foo(int b) : Bar(), baz_(b) {}  // ✅ `:` 앞뒤에 공백 포함

class Foo:public Bar {  // ❌ `:` 앞뒤에 공백 없음
};

if, while, for 등의 키워드 뒤에는 공백을 추가한다.

if (b) {  // ✅ `if` 뒤에 공백 포함
  ...
}

while (test) {  // ✅ `while` 뒤에 공백 포함
  ...
}

for (int i = 0; i < 5; ++i) {  // ✅ `for` 뒤에 공백 포함
  ...
}

for (auto x : counts) {  // ✅ `:` 앞뒤 공백 포함
  ...
}

if(b) {  // ❌ `if` 뒤에 공백 없음
  ...
}

if  (b) {  // ❌ `if` 뒤에 공백이 두 개 이상 있음
  ...
}

연산자(=, +, -, *, /)는 일반적으로 공백을 포함하지만, 곱셈과 나눗셈은 예외 가능하다.

x = 0;  // ✅ `=` 연산자 양쪽 공백 포함
v = w * x + y / z;  // ✅ `*`, `/` 연산자는 생략 가능
v = w*x + y/z;  // ✅ `*`, `/` 연산자 붙여도 허용됨

x=0;  // ❌ 공백 없음

템플릿 및 캐스트 연산에서 < > 사이에는 공백을 넣지 않는다.

std::vector<std::string> x;  // ✅ `< >` 내부 공백 없음
std::vector< std::string > x;  // ❌ `< >` 내부 공백 있음

y = static_cast<char*>(x);  // ✅ `>(` 사이 공백 없음
y = static_cast<char*> (x);  // ❌ `>(` 사이 공백 포함됨

Vertical Whitespace

불필요한 빈 줄(blank lines)은 최소화해야 한다. (필요한 곳에서만 사용)

함수 시작과 끝에 빈 줄을 추가하지 않는다.

if-else 블록 내부에서는 가독성을 위해 빈 줄을 적절히 사용할 수 있다.

주석 앞에는 빈 줄을 추가하여 주석이 뒤의 코드와 연결되도록 한다.

DoSomething();

// 다음 단계에서 값을 초기화한다.
InitializeValues();

네임스페이스 선언 내부에서 첫 번째 선언과 구분을 위해 빈 줄을 추가할 수 있다.

namespace my_namespace {

// 첫 번째 함수 선언
void Foo();
void Bar();

}  // namespace my_namespace

주석이 매우 중요하지만, 최고의 코드는 자체 문서화이다.

변수에 합리적인 이름을 부여하는 것이 주석을 통해 설명해야 하는 모호한 이름을 사용하는 것보다 훨씬 낫다.

다음 규칙들은 무엇을 어디에 댓글을 달아야 하는지를 설명한다.

Comment Style

// 또는 /* */ 구문을 일관성있게 사용한다.

  • // 가 훨씬 더 일반적
  • 어떤 스타일을 어디에 사용하는지 일관성을 유지

File Comments

모든 파일의 맨 위에 라이선스 주석(license boilerplate)을 작성한다.

  • 프로젝트에 따라 Apache, BSD, MIT, GPL 등 적절한 라이선스를 선택

헤더 파일에 여러 개의 관련 클래스를 포함하는 경우, 해당 파일이 어떤 개념을 다루는지 설명해야 한다.

  • 파일의 역할을 한 문단으로 설명
  • “이 파일이 무엇을 포함하는지” 명확히 서술
  • 파일에 추가할 내용과 추가하면 안될 내용을 정리하면 나중에 관리하기 용이해짐
// frobber.h - Defines the Frobber class and related utilities.
//
// This file provides an abstraction for frobbing data structures
// in an efficient manner. It includes:
//   - `Frobber`: A base class for handling frobbing operations
//   - `FrobberFactory`: A factory class for creating `Frobber` instances
//
// If you need additional frobbing utilities, consider adding them here.
#ifndef FROBBER_H_
#define FROBBER_H_

class Frobber { ... };

class FrobberFactory { ... };

#endif  // FROBBER_H_

일반적으로 .cc 파일은 헤더 파일의 구현체이므로, 주석이 필요하지 않다.

  • 그러나 헤더 파일이 없는 .cc 파일이라면, 개념을 설명하는 주석을 추가해야함

파일 상단에 author 라인을 포함하지 않는다.

  • 팀 프로젝트에서는 여러 사람이 코드를 수정하므로 author 라인이 불필요함
  • Git 과 같은 버전 관리 시스템이 변경 기록을 추적하므로, 따로 작성할 필요가 없음
  • 예시 1. 잘못된 주석 (author 라인 포함)
// Copyright 2024 MyProject Developers
// Author: John Doe
//
// Licensed under the Apache License, Version 2.0 (the "License")...

Struct and Class Comments

무엇을 위한 것인지, 어떻게 사용해야 하는지를 설명하는 주석이 첨부되어야 한다.

  • 예시 1.
// Iterates over the contents of a GargantuanTable.
// Example:
//    std::unique_ptr<GargantuanTableIterator> iter = table->NewIterator();
//    for (iter->Seek("foo"); !iter->done(); iter->Next()) {
//      process(iter->key(), iter->value());
//    }
class GargantuanTableIterator {
  ...
};
  • 예시 2.
// Stores configuration settings for the application.
struct Config {
  int timeout;  // Request timeout in milliseconds.
  bool enable_feature;  // Whether to enable the experimental feature.
};

Function Comments

함수 선언부(헤더 파일 .h)와 정의부(구현 파일 .cc)의 주석은 각각 다른 목적을 가진다.

  • 선언부 주석 : 함수의 사용법(입출력, 제약 조건, 예제 등)을 설명
  • 정의부 주석 : 함수의 내부 동작 방식과 구현상의 중요한 고려사항을 설명

단순한 Getter/Setter 함수는 주석을 생략한다.

함수가 포인터를 받으면 nullptr 허용 여부를 명확하게 설명한다.

생성자와 소멸자는 특별한 동작이 없다면 주석을 생략한다.

Variable Comments

변수 이름은 그 목적을 설명할 수 있도록 직관적으로 작성해야 한다.

클래스 멤버 변수는 이름만으로 충분한 의미를 전달할 수 있으면 주석을 생략할 수 있다.

nullptr, -1 등의 센티널 값(Sentinel Values) 을 사용할 경우, 반드시 의미를 설명해야 한다.

전역 변수(Global Variables)는 반드시 주석을 포함해야 하며, “왜 전역 변수로 선언했는지” 명확히 설명해야 한다.

Implementation Comments

어렵거나 복잡하거나 중요한 부분에는 반드시 코드 앞에 주석을 추가해야 한다.

// Uses a binary search to find the insertion point for the new element.
// This ensures O(log N) insertion time in a sorted list.
auto it = std::lower_bound(sorted_list.begin(), sorted_list.end(), new_element);
sorted_list.insert(it, new_element);

함수 인자의 의미가 명확하지 않으면 개선 방법을 고려해야 한다.

  • 예시 1. literal constant 제거, 명명된 상수 사용
// const DecimalNumber product = CalculateProduct(values, 7, false, nullptr);

constexpr int kDefaultPrecision = 7;
constexpr bool kDisableCache = false;

const DecimalNumber product = CalculateProduct(values, kDefaultPrecision, kDisableCache, nullptr);

코드가 자체적으로 의미를 전달 할 수 있도록 노력하고, 불필요한 주석은 피해야 한다.

  • 예시 1. std::find() 는 원래 특정 요소를 찾는 함수이므로 주석이 불필요
// Find the element in the vector.
if (std::find(v.begin(), v.end(), element) != v.end()) {
  Process(element);
}
  • 예시 2. 주석으로 코드의 의도를 설명(”이전에 처리된 요소는 다시 처리하지 않는다”
// Process "element" unless it was already processed.
if (std::find(v.begin(), v.end(), element) != v.end()) {
  Process(element);
}

Self-Describing Code 활용 (코드가 스스로 의미를 전달할 수 있도록)

if (!IsAlreadyProcessed(element)) {
  Process(element);
}

Punctuation, Spelling, and Grammar

구두점, 철자, 문법을 올바르게 사용해서 가독성이 좋아야 한다.

int count = 0;  // Counts the number of processed elements.

코드 리뷰에서 주석을 지적 받는 것이 불편하더라도 최종적으로는 코드의 가독성에 도움이 된다.

// Intializes the buffer and sets its size.  // ❌ 'Intializes' → 'Initializes'
buffer.Init(size);

TODO Comments

TODO 주석은 임시 코드, 단기 해결책, 또는 완벽하지 않은 코드에서 사용해야 한다.

TODO 는 항상 대문자로 작성하며, 이후에 관련 정보를 포함해야 한다.

TODO에 버그 ID, 담당자 이름, 이메일, 또는 관련 문서를 포함해야 한다.

// TODO: bug 12345678 - Remove this after the 2047q4 compatibility window expires.
// TODO: example.com/my-design-doc - Manually fix up this code the next time it's touched.
// TODO(bug 12345678): Update this list after the Foo service is turned down.
// TODO(John): Use a "\*" here for concatenation operator.

단순한 문구보다는 언제 해결해야 하는지 명확한 조건(날짜 또는 특정 이벤트)을 포함해야 한다.

  • Fix by November 2005 , Remove this code when all clients can handle XML responses.

+ Recent posts