New coding guidelines: Difference between revisions

From Multi Theft Auto: Wiki
Jump to navigation Jump to search
m (Added another entry: Unnecesasry parenthesis)
m (typo)
 
(7 intermediate revisions by 3 users not shown)
Line 5: Line 5:
<li>Use '''const''' and '''constexpr''' wherever possible.</li>
<li>Use '''const''' and '''constexpr''' wherever possible.</li>


<li>Use the '''noexcept''' specifier whenever possible.</li>
<li>Use the '''noexcept''' specifier whenever possible, but be cautious as it can cause the program to terminate if an exception is thrown.</li>


<li>Use '''nullptr''' instead of '''NULL''' when setting or returning a null pointer.</li>
<li>Use '''nullptr''' instead of '''NULL''' when setting or returning a null pointer.</li>
Line 176: Line 176:
const CPositionRotationAnimation* CObject::GetMoveAnimation()
const CPositionRotationAnimation* CObject::GetMoveAnimation()
{
{
     return (IsMoving() ? m_pMoveAnimation : nullptr);
     return IsMoving() ? m_pMoveAnimation : nullptr;
}
}
</syntaxhighlight>
</syntaxhighlight>
Line 195: Line 195:
</li>
</li>


<li>Use Hungarian notation for variable names when you can. It's not mandatory but helps with code readability.
<li>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.).
<syntaxhighlight lang="cpp">
<syntaxhighlight lang="cpp">
float        fValue;              // Local variable
float        fValue;              // Local variable
Line 240: Line 240:
</syntaxhighlight>
</syntaxhighlight>


</li>
<li>In new codes, try not to use the '''SString''' type anymore, use '''std::string''' instead.</li>
<li>Class member, should start with the prefix '''m_'''
<syntaxhighlight lang="cpp">
CVector      m_vecPosition;
CVector      m_vecRotation;
bool          m_isVisible;
bool          m_isFrozen;
</syntaxhighlight>
</li>
<li>Use member initialization lists whenever possible. Instead of doing:
<syntaxhighlight lang="cpp">
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);
}
</syntaxhighlight>
Do:
<syntaxhighlight lang="cpp">
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);
}
</syntaxhighlight>
</li>
<li>If you want to return an error to the debug script, throw a '''std::invalid_argument''' exception or use '''std::logic_error'''.
<syntaxhighlight lang="cpp">
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");
</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 = true</code>
</li>
</li>


</ol>
</ol>
[[Category: Development]]

Latest revision as of 13:32, 26 June 2024

This page contains new and unofficial coding guidelines.

  1. Use const and constexpr wherever possible.
  2. Use the noexcept specifier whenever possible, but be cautious as it can cause the program to terminate if an exception is thrown.
  3. Use nullptr instead of NULL when setting or returning a null pointer.
  4. 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; }
    
  5. 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;
    }
    
  6. 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);
    
  7. 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.

  8. 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)
    
  9. 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)
                ...
    
  10. 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++)
    
  11. Define and call functions without arguments simply with empty parentheses.
    // Bad
    void MyFunction(void);
    MyFunction(void);
    
    // Good
    void MyFunction();
    MyFunction();
    
  12. 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;
    }
    
  13. Use lower camel case for variable names of types like custom structs and enums:
    SSomeStruct   valueOne;
    ESomeEnum     m_valueTwo;
    
  14. Functions and classes use UpperCamelCase:
    void UpperCamelCase();
    class Vector;
    
  15. 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
    
  16. 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.
    By adhering to these practices, you ensure that your codebase remains clean, consistent, and adheres to modern C++ standards, which ultimately contributes to better maintainability and fewer integration issues.
  17. Don't use unnecessary parenthesis. Instead of:
    bool CClientPed::IsDead()
    {
        (return (m_status == STATUS_DEAD));
    }
    

    Do:

    bool CClientPed::IsDead()
    {
        return m_status == STATUS_DEAD;
    }
    
  18. In new codes, try not to use the SString type anymore, use std::string instead.
  19. Class member, should start with the prefix m_
    CVector       m_vecPosition;
    CVector       m_vecRotation;
    bool          m_isVisible;
    bool          m_isFrozen;
    
  20. 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);
    }
    
  21. 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