I.4: Make interfaces precisely and strongly typed
이유
잘 정의된 타입은 훌륭한 문서입니다. 가독성을 높이고, 컴파일러가 타입이 정확히 쓰였는지 검증할 수도 있고, 쉽게 최적화 할 수도 있습니다.
예시 (나쁜 예)
void pass(void* data); // void* 가 뭔지 이 코드만 보고는 의미를 알 수 없습니다
pass() 함수를 부를 때 함수의 정의만 보고는 정확히 어떤 타입의 포인터를 전달해야 하는지, *data를 수정하는지도 알 수 없습니다. 모든 포인터 유형은 암시적으로 void*로 변환되므로 실수로 다른 포인터를 전달하기도 쉽습니다.
또한, 함수 내부에서 data 를 사용하기 위해 void* 를 보다 정확한 타입으로 static_cast 해야 하는데 이 과정에서도 실수가 발생할 여지가 추가로 생깁니다.
C++ 의 데이터 형식이 아닌 경우 const void*만 사용하세요. 아니면 std::variant 나 부모 클래스 포인터로 전달하는 방법도 있습니다.
알아두기
보통 템플릿 매개변수는 void* 를 T* 또는 T&로 전환할 수 있습니다. 제네릭 코드의 경우 이러한 T는 general 또는 concept 제약적인 템플릿 매개변수일 수 있습니다.
예시 (나쁜 예)
draw_rect(100, 200, 100, 500); // 이 숫자들이 나타내는 의미가 무엇일까요?
draw_rect(p.x, p.y, 10, 20); // 10, 20 의 단위가 무엇일까요?
사각형을 그리는 함수라는 것은 함수 이름을 보고 충분히 예측할 수 있지만, 파라미터로 제공되는 4개의 값이 각각 어떤 꼭지점을 나타내는지 쉽게 알 수 없습니다.
예시 (좋은 예)
void draw_rectangle(Point top_left, Point bottom_right);
void draw_rectangle(Point top_left, Size height_width);
draw_rectangle(p, Point{10, 20}); // 두개의 꼭지점
draw_rectangle(p, Size{10, 20}); // 하나의 꼭지점과 높이 너비 쌍
빌트인 타입 (int, float, ...) 만으로는 위처럼 함수 오버로딩을 했을 때 실수를 잡기가 쉽지 않습니다.
이제는 (사용자 정의 타입으로) 타입을 명확히 함으로써 이러한 문제를 해결하고 있습니다.
왼쪽 위 꼭지점의 위치로 무조건 첫번째 요소로 넣기로 하는 등 규칙을 정하고 변수명과 주석을을 정확히 적어서 의도를 표현하면 더 좋습니다.
예시 (나쁜 예)
set_settings(true, false, 42); // 42 는 어떤 것을 나타낼까요?
역시나 파라미터로 전달되는 값들이 어떤 값들인지 파악하기 쉽지 않습니다.
(Named parameter 를 지원하는 현대적 언어들은 이러한 문제가 없지만, C++ 는 타입으로 이러한 문제를 해결해야 합니다)
예시 (좋은 예)
alarm_settings s{};
s.enabled = true;
s.displayMode = alarm_settings::mode::spinning_light;
s.frequency = alarm_settings::every_10_seconds;
set_settings(s);
true, false 같은 불리언 값들을 아래처럼 flag enum 셋을 사용하여 표현하면 더욱 보기 좋습니다.
enable_lamp_options(lamp_option::on | lamp_option::animate_state_transitions);
예시 (나쁜 예)
time_to_blink means: Seconds? Milliseconds?
void blink_led(int time_to_blink) // 시간의 단위가 명확하지 않습니다
{
// ...
// do something with time_to_blink
// ...
}
void use()
{
blink_led(2);
}
예시 (좋은 예)
template<class rep, class period>
void blink_led(duration<rep, period> time_to_blink) // good -- accepts any unit
{
// assuming that millisecond is the smallest relevant unit
auto milliseconds_to_blink = duration_cast<milliseconds>(time_to_blink);
// ...
// do something with milliseconds_to_blink
// ...
}
void use()
{
blink_led(2s);
blink_led(1500ms);
}
std::chrono::duration 타입을 사용하면 시간의 단위를 명확히 표현할 수 있습니다.
정리
1. void* 를 함수의 입력 파라미터나, 리턴 타입으로 사용하지 마세요.
2. 많은 불리언 값들을 입력 파라미터로 나열하여 전달하지 마세요.
3. (어렵겠지만..) 빌트인 타입을 입력 파라미터로 받는 함수들을 사용자 정의 타입을 받도록 수정하세요.
참고자료
https://en.cppreference.com/w/cpp/chrono/duration/duration
'C++' 카테고리의 다른 글
[C++ Core Guidelines] I.7, I.8 완료 조건을 명시하자 (0) | 2024.05.27 |
---|---|
[C++ Core Guidelines] I.5, I.6 전제 조건을 명시하자 (0) | 2024.05.17 |
[C++ Core Guidelines] I.3 싱글톤은 가급적 쓰지 말자 (0) | 2024.05.04 |
[C++ 23] std::basic_string_view<CharT,Traits>::contains 사용하기 (0) | 2024.05.04 |
[C++ Core Guidelines] I.2 전역 변수는 상수로 만들자 (0) | 2024.04.30 |