모범 사례

프로젝트에서 사용되는 관례 및 권장 사항 목록입니다. 엄격한 규칙은 아니며, 코드베이스의 대부분이 이러한 기준을 완전히 충족하지는 않습니다.

이름 짓기

좋은 이름의 조건:

  • 의미가 명확하다면 축약하지 않기 - cnt가 아닌 count
  • 문맥에서 의미가 명확하기 - direction::left는 명확하지만, int rotate는 문서를 읽어야 이해 가능
  • 주변 이름과 일관된 규칙 따르기 - max_stored_kcalstored_kcal처럼, max_stored_caloriesstored_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::pairstd::tuple 사용 지양, 대신 이름 있는 구조체 생성
  • enum class로 대체 가능한 곳에서는 intstd::string 사용 지양

파일 구성

헤더에서 다른 헤더를 인클루드하는 것을 가능한 한 피하세요. 이는 컴파일 시간(전체 및 부분 빌드 모두)에 부정적인 영향을 미치며, 특히 character.h, avatar.h, map.h, game.h, item.h, npc.h 등 크고 널리 사용되는 헤더의 경우 더욱 그렇습니다. 또한 소스 파일에 불필요한 정의들이 포함되는 문제가 발생합니다.

몇 가지 팁:

  • 컴파일러가 항상 클래스의 전체 정의를 알아야 하는 것은 아니며, 선언만으로 충분할 수 있습니다. 예를 들어 character.h에서 vehicle 클래스가 vehicle & 타입의 함수 인자로만 사용되는 경우 - 참조는 타입에 관계없이 항상 같은 크기이므로, 컴파일러는 vehicle이라는 타입이 존재한다는 것만 알면 되고 내부 세부사항을 알 필요가 없습니다. 이 경우 character.hclass vehicle; 선언을 추가하는 것만으로 충분합니다.
  • 클래스 멤버에 pointer-to-implementation 관용구를 사용하여 정의가 필요하지 않게 할 수 있습니다. 구현 및 설명은 src/pimpl.h를 참조하세요. 코드베이스에 많은 사용 예제가 있으며(주로 game.h), 기본 아이디어는 클래스 멤버를 포인터로 교체하고 포인터를 통해 멤버에 접근할 때만 정의를 요구하는 것입니다. 약간의 성능 오버헤드가 있지만 대부분의 경우 무시할 수 있는 수준입니다.
  • 자주 인클루드되는 헤더에 기능을 추가하는데 그 기능이 소수의 소스 파일에서만 사용될 경우, 별도의 헤더를 만드는 것을 고려하세요. 이렇게 하면 수십 또는 수백 개의 컴파일 단위에 코드가 포함되지 않고, 컴파일러가 불필요하게 여러 번 빌드하는 시간을 낭비하지 않습니다.
  • 시스템 및 std 헤더를 인클루드하는 것은 괜찮습니다. 대부분 미리 컴파일되어 있습니다.
  • 일반적인 "유틸리티" 헤더(optional.h, calendar.h, coordinates.h 등)를 인클루드하는 것은 괜찮습니다. 이들은 이미 많은 헤더와 소스 파일에서 광범위하게 사용되므로, 인클루드하지 않는 것의 이점이 불편함을 상쇄할 만큼 크지 않습니다.