aseprite/docs/CODING_STYLE.md
2024-08-08 16:17:56 -03:00

6.2 KiB

Code Style Guidelines

Some general rules to write code: Try to follow the same style/format of the file that you are editing (naming, indentation, etc.) or the style of the module (some submodules, created by us, or by third-parties, have their own style).

There is a .clang-format file available but we are not using it at the moment, probably we should start using some clang-format-diff.py for patches, but this wasn't yet adopted in the development process.

There is a .clang-tidy file used in the GitHub actions executed on each PR. These rules are adopted progressively on patches because are only executed in the diff, and if some rule is violated a comment by aseprite-bot is made.

Basics

Basic statements:

void global_function(int arg1,
                     const int arg2, // You can use "const" preferably
                     const int arg3, ...)
{
  int value;
  const int constValue = 0;

  // We prefer to use "var = (condition ? ...: ...)" instead of
  // "var = condition ? ...: ...;" to make clear about the
  // ternary operator limits.
  int conditionalValue1 = (condition ? 1: 2);
  int conditionalValue2 = (condition ? longVarName:
                                       otherLongVarName);

  // If a condition will return, we prefer the "return"
  // statement in its own line to avoid missing the "return"
  // keyword when we read code.
  if (condition)
    return;

  // You can use braces {} if the condition has multiple lines
  // or the if-body has multiple lines.
  if (condition1 ||
      condition2) {
    return;
  }

  if (condition) {
    ...
    ...
    ...
  }

  // We prefer to avoid whitespaces between "var=initial_value"
  // or "var<limit" to see better the "; " separation. Anyway it
  // can depend on the specific condition/case, etc.
  for (int i=0; i<10; ++i) {
    ...
    // Same case as in if-return.
    if (condition)
      break;
    ...
  }

  while (condition) {
    ...
  }

  do {
    ...
  } while (condition);

  switch (condition) {
    case 1:
      ...
      break;
    case 2: {
      int varInsideCase;
      ...
      break;
    }
    default:
      break;
  }
}

Namespaces

Define namespaces with lower case:

namespace app {

  ...

} // namespace app

Classes

Define classes with CapitalCase and member functions with camelCase:

class ClassName {
public:
  ClassName()
    : m_memberVarA(1),
      m_memberVarB(2),
      m_memberVarC(3) {
    ...
  }

  virtual ~ClassName();

  // We can return in the same line for getter-like functions
  int memberVar() const { return m_memberVar; }
  void setMemberVar();

protected:
  virtual void onEvent1() { } // Do nothing functions can be defined as "{ }"
  virtual void onEvent2() = 0;

private:
  int m_memberVarA;
  int m_memberVarB;
  int m_memberVarC;
  int m_memberVarD = 4; // We can initialize variables here too
};

class Special : public ClassName {
public:
  Special();

protected:
  void onEvent2() override {  // No need to repeat virtual in overridden methods
    ...
  }
};

Const

We use the const-west notation:

There is a problem with clang-tidy that will make comments using East const notation: #4361

C++17

We are using C++17 standard. Some things cannot be used because we're targetting macOS 10.9, some notes are added about this:

  • Use nullptr instead of NULL macro
  • Use auto/auto* for complex types/pointers, iterators, or when the variable type is obvious (e.g. auto* s = new Sprite;)
  • Use range-based for loops (for (const auto& item : values) { ... })
  • Use template alias (template<typename T> alias = orig<T>;)
  • Use generic lambda functions
  • Use std::shared_ptr, std::unique_ptr, or base::Ref, but generally we'd prefer value semantics instead of smart pointers
  • Use std::min/std::max/std::clamp
  • Use std::optional but taking care of some limitations from macOS 10.9:
    • Use std::optional::has_value() instead of std::optional::operator bool() (example)
    • Use std::optional::operator*() instead of std::optional::value() (example)
  • Use std::variant but taking care of some limitations from macOS 10.9:
    • Use T* p = std::get_if<T>(&value) instead of T v = std::get<T>(value) or create an auxiliary get_value() using std::get_if function (example)
    • Don't use std::visit(), use some alternative with switch-case and the std::variant::index() (example)
  • Use std::any but taking care of some limitations from macOS 10.9:
    • Use T* p = std::any_cast<T>(&value) instead of T v = std::any_cast<T>(value) (example)
  • Use static constexpr T v = ...;
  • You can use <atomic>, <thread>, <mutex>, and <condition_variable>
  • Prefer using T = ...; instead of typedef ... T
  • Use [[fallthrough]] if needed
  • Use = {} only to specify a default argument value of an user-defined type in a function declaration, e.g. void func(const std::string& s = {}) { ... }. In other cases (e.g. a member variable of an user-defined type) it's not required or we prefer to use the explicit value for built-in types (int m_var = 0;).
  • We use gcc 9.2 or clang 9.0 on Linux, so check the features available in https://en.cppreference.com/w/cpp/compiler_support