best practices
List of conventions used in the project, plus just plain good practices. Not rules per se, most of the codebase doesn’t meet those standards.
Naming
Good names:
- Are not abbreviated if it could make meaning less obvious -
count
, notcnt
- Are obvious from context -
direction::left
is clear,int rotate
requires reading the documentation - Obey same conventions as nearby names -
max_stored_kcal
andstored_kcal
, notmax_stored_calories
butstored_kcal
orcaloried_stored
snake_case
is preferred for consistency with most of the project’s code.
Classes
- Don’t add methods where a regular function in a namespace would work -
crafting::consume_tools(tool_list,Character &)
, notCharacter.consume_tools(tool_list)
- Don’t add both getters and setters if all they do is read/write a field - those are just disguised
fields
- Getters without (public) setters are OK
- Use
private
where possible,public
when not,protected
only when there’s a clear reason for it
Types
- Avoid pointers, instead use references,
std::optional
or even a function overload where possible - Avoid using
std::pair
andstd::tuple
in headers, instead create a named struct - Avoid using
int
orstd::string
where anenum class
would work
File organization
Try to avoid including headers from other headers. This negatively impacts compilation times (both
clean and partial builds), especially when the header in question is already big and widely used
(such as character.h
, avatar.h
, map.h
, game.h
, item.h
, npc.h
, etc.), and frequently
pollutes source files with definitions they have no need to know.
Some tips here:
- Compiler doesn’t always need to know the whole definition of a class, a declaration may be enough.
Say, if in
character.h
thevehicle
class is only ever used for a function argument of typevehicle &
- references are always of the same size regardless of what type they refer to, so compiler only needs to know there exists a type namedvehicle
, not its internal details. Addingclass vehicle;
declaration incharacter.h
is enough in this case. - It’s possible to use
pointer-to-implementation
idiom for class members to remove the need for the definition, seesrc/pimpl.h
for implementation and explanation. There are many usage examples in the codebase (mainlygame.h
), but the overall idea is to replace class member with a pointer, and only require definition when accessing the member through pointer. This incurs a tiny performance overhead, but it’s unnoticeable in most cases. - If you’re adding some functionality to a frequently included header, and that functionality is supposed to be used only in a few source files, consider creating a separate header instead. This way, your code won’t make it into dozens or hundreds of compilation units which don’t ever use it, and the compiler wouldn’t spend time to build it all these dozens or hundreds of unneeded times.
- Including system and std headers is OK, most of them are precompiled anyway.
- Including common “utility” headers (
optional.h
,calendar.h
,coordinates.h
, etc.) is OK, those are already extensively used in many headers and source files, so not including them makes too little impact to warrant the inconvenience.