New coding guidelines: Difference between revisions
Jump to navigation
Jump to search
m (Updated LuaFunctionParser) |
m (typo) |
||
Line 318: | Line 318: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
If you want to throw a warning instead of an error, use [https://github.com/multitheftauto/mtasa-blue/blob/master/Shared/mods/deathmatch/logic/lua/CLuaFunctionParser.h#L22 LuaFunctionError] class with <code>throwWarning = | If you want to throw a warning instead of an error, use [https://github.com/multitheftauto/mtasa-blue/blob/master/Shared/mods/deathmatch/logic/lua/CLuaFunctionParser.h#L22 LuaFunctionError] class with <code>throwWarning = true</code> | ||
</li> | </li> | ||
Revision as of 13:32, 26 June 2024
This page contains new and unofficial coding guidelines.
- Use const and constexpr wherever possible.
- Use the noexcept specifier whenever possible, but be cautious as it can cause the program to terminate if an exception is thrown.
- Use nullptr instead of NULL when setting or returning a null pointer.
- Functions that only return a value should be placed in header files. For example, instead of:
// In .cpp file int GetGameSpeed() { return m_iGameSpeed; }
Do it in the header:
int GetGameSpeed() const { return m_iGameSpeed; }
- Use early-return to improve code readability. For example, instead of:
bool CStaticFunctionDefinitions::RespawnObject(CElement* pElement) { if (IS_OBJECT(pElement)) { CObject* pObject = static_cast<CObject*>(pElement); if (pObject) { pObject->Respawn(); return true; } } return false; }
It's clearer to do:
bool CStaticFunctionDefinitions::RespawnObject(CElement* pElement) { if (!IS_OBJECT(pElement)) return false; CObject* pObject = static_cast<CObject*>(pElement); if (!pObject) return false; pObject->Respawn(); return true; }
- Always strive to maintain code readability. If a type is lengthy to write, you can use auto to improve readability.
CDeatchmatchObject* pObject = static_cast<CDeathmatchObject*>(pEntity); // Can be auto* pObject = static_cast<CDeathmatchObject*>(pEntity);
- If you use addresses of values or functions, document what the address is with a comment or use a #define macro.
float CWeatherSA::GetWetRoads() const { return *(float*)0xC81308; // CWeather::WetRoads }
// In the header #define NUM_WETROADS 0xC81308 // In the .cpp float CWeatherSA::GetWetRoads() const { return *(float*)NUM_WETROADS; }
Both methods are OK.
-
When using the #define macro for addresses, you should use an appropriate prefix that indicates what the address represents.
#define FUNC_RemoveRef 0x4C4BB0 // Function address #define ARRAY_aCannons 0xC80740 // Array address #define STRUCT_CAESoundManager 0xB62CB0 // Struct address #define SIZE_CWaterCannon 0x3CC // Size of object (e.g., struct object) #define CLASSSIZE_WeaponInfo 112 // Class size #define NUM_CWaterCannon_Audio_Offset 0x32C // Number (e.g., offsets, integers, etc.) #define CLASS_CText 0xC1B340 // Class address #define VAR_CTempColModels_ModelPed1 0x968DF0 // Variable address (CColModel colModelPeds)
- For readability, if a loop or condition is short, omit curly braces. For example, instead of:
if (!bStillRunning) { StopMoving(); }
You can do:
if (!bStillRunning) StopMoving();
However, do not omit curly braces for larger blocks:
for (dont) for (do) for (this) ...
- Use range-based for loops when possible.
std::vector<int> vec; // Example std container // Bad: for (std::vector<int>::iterator it = vec.begin(); it != vec.end(); it++) // Good: for (const auto& v : vec) for (const int& v : vec) // In case of maps you can use structured binding: std::map<std::string, int> myMap; for (const auto& [k, v] : myMap) // There are situations where you must use old style loops, in this case use `auto` for (auto it = vec.begin(); it != vec.end(); it++)
- Define and call functions without arguments simply with empty parentheses.
// Bad void MyFunction(void); MyFunction(void); // Good void MyFunction(); MyFunction();
- When possible, use a logical condition for code readability. Instead of:
const CPositionRotationAnimation* CObject::GetMoveAnimation() { if (IsMoving()) { return m_pMoveAnimation; } else { return nullptr; } }
Do:
const CPositionRotationAnimation* CObject::GetMoveAnimation() { return IsMoving() ? m_pMoveAnimation : nullptr; }
- Use lower camel case for variable names of types like custom structs and enums:
SSomeStruct valueOne; ESomeEnum m_valueTwo;
- Functions and classes use UpperCamelCase:
void UpperCamelCase(); class Vector;
- Use Hungarian notation only when necessary to maintain consistency with existing code; however, try to avoid using it in entirely new code (new files, etc.).
float fValue; // Local variable unsigned char m_ucValue; // Class member variable char ms_cValue; // Class static member variable bool g_bCrashTwiceAnHour; // Global variable char* szUsername; // Zero-terminated string SString strUsername; // String CVector vecPosition; // 3D Vector
-
In C++, prefer using types from the std namespace provided by appropriate headers (such as <cstdint>, <cstddef>, etc.). This is recommended for several reasons:
- Namespace Safety: Types defined in these headers are encapsulated within the std namespace, which helps avoid naming conflicts with user-defined types or macros. This follows the C++ standard practice of minimizing global namespace pollution.
- Consistency and Readability: Using std:: types ensures consistency and improves code readability by making it clear that these types are part of the standard library.
- C++ Standard Compliance: The C++ standard (C++11 and later) includes headers like <cstdint> and <cstddef>, which provide standardized types:
- <cstdint> includes exact-width integer types such as 'std::uint32_t, std::int32_t, etc.
- <cstddef> includes types like std::size_t, std::ptrdiff_t, etc.
- Portability and Maintainability: Using these headers makes your code more portable across different compilers and platforms, as it guarantees that these types are defined in a standardized way. This is especially important in a C++ environment, where the focus is on maintainability and cross-platform compatibility.
-
Don't use unnecessary parenthesis. Instead of:
bool CClientPed::IsDead() { (return (m_status == STATUS_DEAD)); }
Do:
bool CClientPed::IsDead() { return m_status == STATUS_DEAD; }
- In new codes, try not to use the SString type anymore, use std::string instead.
- Class member, should start with the prefix m_
CVector m_vecPosition; CVector m_vecRotation; bool m_isVisible; bool m_isFrozen;
- Use member initialization lists whenever possible. Instead of doing:
CObject::CObject(CElement* pParent, CObjectManager* pObjectManager, bool bIsLowLod) : CElement(pParent), m_bIsLowLod(bIsLowLod), m_pLowLodObject(NULL) { // Init m_iType = CElement::OBJECT; SetTypeName("object"); m_pObjectManager = pObjectManager; m_usModel = 0xFFFF; m_pMoveAnimation = NULL; m_ucAlpha = 255; m_vecScale = CVector(1.0f, 1.0f, 1.0f); m_fHealth = 1000.0f; m_bSyncable = true; m_pSyncer = NULL; m_bIsFrozen = false; m_bDoubleSided = false; m_bBreakable = false; m_bCollisionsEnabled = true; // Add us to the manager's list pObjectManager->AddToList(this); }
Do:
CObject::CObject(CElement* pParent, CObjectManager* pObjectManager, bool bIsLowLod) : CElement(pParent), m_bIsLowLod(bIsLowLod), m_pLowLodObject(nullptr), m_iType(CElement::OBJECT), m_pObjectManager(pObjectManager), m_usModel(0xFFFF), m_pMoveAnimation(nullptr), m_ucAlpha(255), m_vecScale(1.0f, 1.0f, 1.0f), m_fHealth(1000.0f), m_bSyncable(true), m_pSyncer(nullptr), m_bIsFrozen(false), m_bDoubleSided(false), m_bBreakable(false), m_bCollisionsEnabled(true) { SetTypeName("object"); // Add us to the manager's list pObjectManager->AddToList(this); }
- If you want to return an error to the debug script, throw a std::invalid_argument exception or use std::logic_error.
if (!CClientBuildingManager::IsValidModel(modelId)) throw std::invalid_argument("Invalid building model id"); if (!CClientBuildingManager::IsValidPosition(pos)) throw std::invalid_argument("Position is outside of game world");
If you want to throw a warning instead of an error, use LuaFunctionError class with
throwWarning = true