모범 사례
프로젝트에서 사용되는 관례 및 권장 사항 목록입니다. 엄격한 규칙은 아니며, 코드베이스의 대부분이 이러한 기준을 완전히 충족하지는 않습니다.
이름 짓기
좋은 이름의 조건:
- 의미가 명확하다면 축약하지 않기 -
cnt가 아닌count - 문맥에서 의미가 명확하기 -
direction::left는 명확하지만,int rotate는 문서를 읽어야 이해 가능 - 주변 이름과 일관된 규칙 따르기 -
max_stored_kcal과stored_kcal처럼,max_stored_calories와stored_kcal또는caloried_stored처럼 혼용하지 않기
프로젝트 코드의 일관성을 위해 snake_case를 권장합니다.
클래스
- 일반 함수로도 가능하다면 메서드를 추가하지 않기 -
Character.consume_tools(tool_list)가 아닌crafting::consume_tools(tool_list,Character &) - 단순히 필드를 읽고 쓰기만 하는 getter와 setter는 추가하지 않기 - 이는 단지 위장된 필드일 뿐
- (공개) setter 없이 getter만 있는 것은 괜찮음
- 가능하면
private사용, 불가능하면public, 명확한 이유가 있을 때만protected사용
타입
- 포인터 대신 참조,
std::optional또는 함수 오버로드 사용 권장 - 헤더에서
std::pair와std::tuple사용 지양, 대신 이름 있는 구조체 생성 enum class로 대체 가능한 곳에서는int나std::string사용 지양
파일 구성
헤더에서 다른 헤더를 인클루드하는 것을 가능한 한 피하세요. 이는 컴파일 시간(전체 및 부분 빌드 모두)에 부정적인 영향을 미치며, 특히 character.h, avatar.h, map.h, game.h, item.h, npc.h 등 크고 널리 사용되는 헤더의 경우 더욱 그렇습니다. 또한 소스 파일에 불필요한 정의들이 포함되는 문제가 발생합니다.
몇 가지 팁:
- 컴파일러가 항상 클래스의 전체 정의를 알아야 하는 것은 아니며, 선언만으로 충분할 수 있습니다. 예를 들어
character.h에서vehicle클래스가vehicle &타입의 함수 인자로만 사용되는 경우 - 참조는 타입에 관계없이 항상 같은 크기이므로, 컴파일러는vehicle이라는 타입이 존재한다는 것만 알면 되고 내부 세부사항을 알 필요가 없습니다. 이 경우character.h에class vehicle;선언을 추가하는 것만으로 충분합니다. - 클래스 멤버에
pointer-to-implementation관용구를 사용하여 정의가 필요하지 않게 할 수 있습니다. 구현 및 설명은src/pimpl.h를 참조하세요. 코드베이스에 많은 사용 예제가 있으며(주로game.h), 기본 아이디어는 클래스 멤버를 포인터로 교체하고 포인터를 통해 멤버에 접근할 때만 정의를 요구하는 것입니다. 약간의 성능 오버헤드가 있지만 대부분의 경우 무시할 수 있는 수준입니다. - 자주 인클루드되는 헤더에 기능을 추가하는데 그 기능이 소수의 소스 파일에서만 사용될 경우, 별도의 헤더를 만드는 것을 고려하세요. 이렇게 하면 수십 또는 수백 개의 컴파일 단위에 코드가 포함되지 않고, 컴파일러가 불필요하게 여러 번 빌드하는 시간을 낭비하지 않습니다.
- 시스템 및 std 헤더를 인클루드하는 것은 괜찮습니다. 대부분 미리 컴파일되어 있습니다.
- 일반적인 "유틸리티" 헤더(
optional.h,calendar.h,coordinates.h등)를 인클루드하는 것은 괜찮습니다. 이들은 이미 많은 헤더와 소스 파일에서 광범위하게 사용되므로, 인클루드하지 않는 것의 이점이 불편함을 상쇄할 만큼 크지 않습니다.