I.8: Prefer Ensures() for expressing postconditions
이유
실수로 잘못된 결과를 반환하는 것을 막을 수 있습니다.
예시 (나쁜 예)
int area(int height, int width) { return height * width; } // 나쁨
사각형의 넓이를 구하는 함수를 만들었습니다.
다만 전제 조건 검사를 생략했기 때문에 height 와 width 값으로 음수가 들어올 수도 있습니다.
그것 뿐인가요? height * width 결과가 int 로 표현할 수 있는 최대값 보다 크다면 오버플로우도 발생합니다.
예시 (좋은 예)
int area(int height, int width)
{
auto res = height * width;
Ensures(res > 0); // 반환값이 양수임을 보장합니다
return res;
}
예시 (나쁜 예)
void f() // 문제가 있습니다
{
char buffer[MAX];
// ...
memset(buffer, 0, sizeof(buffer));
}
보안 문제를 발생시키는 유명한 코드입니다. (스택 오버플로우)
buffer[] 는 지역 변수이고 함수를 나갈때 memset 으로 메모리를 0으로 초기화 하는 것은 분명 불필요하기 때문에
컴파일러는 최적화 옵션에 따라 이 코드를 지워버릴 것입니다.
예시 (좋은 예)
void f() // 더 나은 코드
{
char buffer[MAX];
// ...
memset(buffer, 0, sizeof(buffer));
Ensures(buffer[0] == 0);
}
임베디드 시스템과 같이 스택 메모리를 임의로 0으로 초기화해야 하는 경우도 있습니다.
특정 메모리 상태를 확인하는 코드나 이 메모리를 사용하는 코드가 memset 이후에 존재한다면
컴파일러는 성능을 향상시키기 위해 memset과 같은 코드를 임의로 제거할 수 없습니다.
이때 Enaches() 를 사용하면 완료 조건을 명시하면 컴파일 타임에 검사하고, 가독성도 좋아집니다.
알아두기
반환 조건은 자료구조의 상태처럼 반환값으로 결과가 직접 보여지지 않는 경우에 더욱 유용합다.
예시 (나쁜 예)
mutex m;
void manipulate(Record& r) // don't
{
m.lock();
// ... no m.unlock() ...
}
경쟁 조건을 피하기 위해 뮤텍스를 사용하여 데이터를 조작하는 함수를 만들었습니다.
실수로 뮤텍스가 해제되어야 한다고 주석으로 명시하는 것을 까먹었습니다.
따라서 뮤텍스 해제를 보장하지 못한 것이 버그인지 기능인지 알 수 없습니다.
예시 (그럭저럭 괜찮은 예)
void manipulate(Record& r) // 완료 조건 : 함수를 나갈때 m 을 꼭 unlock 시키세요!!
{
m.lock();
// ... no m.unlock() ...
}
완료 조건을 주석으로 명시해서 뮤텍스를 해제해야 하는 것을 명확하게 표현했습니다.
그렇지만 오직 사람만이 알아볼 수 있는 코드입니다.
주석에 주의를 기울이지 않는 사람도 많습니다.
예시 (좋은 예)
void manipulate(Record& r) // 최고
{
lock_guard<mutex> _ {m};
// ...
}
RAII 기법은 완료 조건을 "보장" 합니다.
위처럼 코드를 작성하면 함수를 빠져 나갈 때 문법적으로 m 이 해제 될 것임을 보장할 수 있습니다.
알아두기
이상적으로 완료 조건은 사용자가 쉽게 볼 수 있도록 인터페이스/선언에 명시되어 있습니다.
인터페이스에는 사용자와 관련된 완 조건만 기술할 수 있습니다.
함수 내부에서만 필요한 조건 검사는 완료 조건이 아니라 정의/구현에 속합니다.
알아두기
일반적인 경우 위 가이드라인처럼 RAII 를 사용할 수 없는 경우도 있습니다.
이런 경우 lock 유지 검사 도구와 같이 특정 분야에서 많이 사용하는 툴체인에 검사를 의존해야 합니다.
알아두기
완료 조건도 전제 조건처럼 주석, if 문, assert() 등 다양한 방법으로 정의할 수 있습니다.
다만 일반 코드와 구별이 어렵고, 도구로 검사하기 어렵고, 의미가 잘못 전달될 위험이 있습니다.
"이 자원은 반드시 해제되어야 합니다" 와 같은 의미의 완료 조건은 RAII 에 의해 가장 잘 표현됩니다.
알아두기
이상적으로는 Ensures() 와 같은 기능이 인터페이스에 포함되었어야 합니다.
지금은 어쩔 수 없이 이를 정의 (함수 본문) 에 배치합니다.
언어 레벨에서의 지원이 가능해지면 우리는 preconditions, postconditions, assertions의 표준을 만들 것입니다.
정리
1. "이 자원은 반드시 해제되어야 합니다" 를 표현하고 싶은 경우 RAII 로 구현하세요.
2. 나머지 조건은 Ensures() 를 사용해서 검사하세요.
참고자료
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0380r1.pdf
'C++' 카테고리의 다른 글
[C++ Core Guidelines] I.10 예외를 사용해 필수 작업의 실패를 알리자 (0) | 2024.09.12 |
---|---|
[C++ Core Guidelines] I.9 템플릿으로 만든 인터페이스는 concepts 문법을 사용해서 구체화하자 (1) | 2024.07.05 |
[C++ Core Guidelines] I.5, I.6 전제 조건을 명시하자 (0) | 2024.05.17 |
[C++ Core Guidelines] I.4 명확한 타입을 사용해서 인터페이스를 만들자 (0) | 2024.05.09 |
[C++ Core Guidelines] I.3 싱글톤은 가급적 쓰지 말자 (0) | 2024.05.04 |