Skip to content

Code Style and Recommendations#

AUI's code should be kept with the following code style:

  • No tabs; only four spaces (exception: for UI building, 2 spaces is acceptable)
  • Row length: up to 120 characters
  • Class names: CamelCase. Every framework public API class name starts with capital 'A'. Use GenericSpecific pattern (EventClose, not CloseEvent, PacketConnect, not ConnectPacket). Using that convention files group nicely in file lists.
  • Functions, variables: lowerCamelCase
  • Constants: UPPER_SNAKE_CASE
  • Member fields: m... (lineNumber -> mLineNumber)
  • Getters: ... (lineNumber -> lineNumber(), (field: mLineNumber )
  • Getters: set.../with... (lineNumber -> setLineNumber(...), accessible -> withAccessible(...))
  • Structures: commonly holds some data and does not have member functions; CamelCase. Also used for stl-like functionality (AUI/Traits), in that case, snake_case used for file names, struct name and its member functions.
  • Constructors and setters: move semantics. This allows caller to choose whether copy or move the passed object
  • Use const, noexcept and [[nodiscard]] whenever possible
  • Tend to not to use macros. UPPER_SNAKE_CASE prefixed with 'AUI_'
  • Use #pragma once instead of C-style include guards
  • Use Doxygen (@-style, not \)
  • Avoid global functions (at least put them to namespaces)
  • Every symbol (class, struct, namespace, file) prefixed with 'A', 'aui', 'AUI_' is a part of AUI's stable public API ready for the external usage. Symbols without such prefixes are called internal and can be also used (if possible) when needed. Be careful using internal symbols since there's no guarantee about their API stability.
  • Follow visibility in this order when possible: public, protected, private. Header's user is more interested in public APIs rather than in private.

Basic example:

class User {
public:
  // User(const AString& username): mUsername(username) {} <--- outdated
  // use this instead:
  User(AString username) noexcept: mUsername(std::move(username)) {}

  [[nodiscard]
  const AString& username() const noexcept { // a typical getter; notice the const and noexcept keywords and [[nodiscard]] attribute
    return mUsername;
  }

  void setUsername(AString username) noexcept { // a typical setter; also uses noexcept
    mUsername = std::move(username);
  }

private:
  AString mUsername;

};

Note

With property-system, a better way of defining data models will be:

struct User {
    AProperty<AString> username;
};
It allows aggregate initialization: User u { .username = "Test" };

Assertions#

The whole AUI framework's code filled with assertion checks so if you do something wrong the framework will tell you about it. Also in AUI almost every assertion contains a quick tip how to solve the problem. It is recommended to you to do the same. For example:

AUI_ASSERTX(mId == std::this_thread::get_id(),
            "AAbstractThread::processMessages() should not be called from other thread");

The code above ensures that the function was not called from some other thread.

Note

Do not put algorithm-necessary code inside assert(), AUI_ASSERT or AUI_ASSERTX since asserts are removed in release builds on some compilers, i.e. don't assert(("someAction failed!", someObject->someAction() != 0)) since it leads to hard-to-find bugs.

Assert or exception?#

Assert is an enemy for the production application since it terminates program execution. Use it when it's condition relies only on the developer. Quick example:

connect(mLoginButton->clicked, me::loginButtonClicked);
...
void loginButtonClicked() {
  AUI_ASSERT(mUsername->text().length() < 32); // bad! throw an exception instead so it can be handled: throw AException("username is too long!")
}

Code style exceptions#

Commonly, any iterator-based algorithm (i.e. aui::binary_search), global functions, trait structs are STL-like functionality. The final goal is to avoid mixed-style expressions like AString::const_iterator which hurts eyes.

Template metaprogramming and macros#

Both C++ template instantiation mechanism and macro preprocessor are Turing complete. However, writing and understanding C++ template metaprogramming (TMP) and macro preprocessor code requires expert knowledge of C++ and a lot of time to understand. Use TMP deliberately.

Since TMP and macros often evolve custom syntax and usage scenarios, consider writing especially well documentation with examples when defining public API templates and macros.

Improving compiler error messages techniques#

Try to break your templates#

After considering actions listed below, try your types/traits/concepts against various awkward types/arguments/use cases.

Concepts are preferable#

Use concepts instead of SFINAE were possible.

Raise static_assert messages#

With static_assert with potentially helpful message, use ====================> prefix in your message to raise your message among a long list of compiler diagnostics.

1
2
3
4
5
6
7
8
/**
 * @brief Compile-time class introspection.
 * @ingroup reflection
 */
template<class T>
class AClass {
public:
    static_assert(!std::is_reference<T>::value, "====================> AClass: attempt to use AClass on a reference.");

Single line comment error messages#

You can exploit the fact that a compiler prints code lines in its diagnostics. Put a single line comment with long arrow prefix to put potentially helpful messages. Use Try to break your templates technique to discover the lines to put the comments in.

Cast failure example:

auto& [a] = const_cast<std::remove_cv_t<Clazz>&>(clazz); // ====================> aui::reflect: Clazz is not a SimpleAggregate.

Overload substitution failure example:

...
template<typename T>
requires requires(T& t) { std::hash<T>{}(t); }
constexpr std::size_t forEachKey(const T& value) { // ====================> std::hash based specialization
    return std::hash<T>{}(value);
}

template<typename T>
requires requires(T& t) { { t.base() } -> ranges::range; }
constexpr std::size_t forEachKey(const T& value) { // ====================> specialization for subranges
...    

Produces the following diagnostics:

.../AForEachUI.h:220:92: error: no matching function for call to forEachKey(...)
220 |     return AForEachUIBase::Entry { .view = mFactory(t), .id = forEachKey(t) };
    |                                                               ~~~~~~~~~~^~~
...View/AForEachUI.h:34:23: note: candidate: template<class T> requires(T& t) {{}(t);} ...
34 | constexpr std::size_t forEachKey(const T& value) { // ====================> std::hash based specialization

This makes it obvious what does this overload do.

.clang-format#

clang-format is a de facto standard tool to auto format (C++) code with style described by .clang-format file that typically located inside your project's root directory. AUI has such file.

Since AUI abuses C++'s syntax, it's important to set up appropriate auto formatting, or you will often find yourself struggling with AUI's DSL especially in large portions of layout, despite the fact we recommend to decompose large AUI DSLs into smaller pieces (i.e., functions). For your project, we recommend to start with AUI's formatting configuration listed below. AUI's App Template has .clang-format already. Your IDE should pick up it without further configuration.

Always Use Trailing Comma in Initializer Lists#

When it comes to clang-format there's one an unobvious feature when using AUI's DSL. Consider the example:

setContents(Vertical {
  Button { "Up" },
  Button { "Down" },
});
setContents(Vertical {
  Button { "Up" },
  Button { "Down" }
});

See the difference? The second example lacks one comma. If we try to trigger clang-format (ALT+CTRL+L in CLion), we'll get the following results (assuming AUI's ".clang-format"):

setContents(Vertical {
  Button { "Up" },
  Button { "Down" },
});
setContents(Vertical {
  Button { "Up" }, Button { "Down" } });

The first example left as is (correct), the second example formatted confusingly.

When using any kind of AUI's DSL with initializer lists please always use trailing comma. Not only it helps with reordering, git diffs, etc..., but also .clang-format makes proper formatting for lists with trailing commas.

Use clang-format off/on#

In some scenarios clang-format may fight against you, especially with complicated syntax. You can use // clang-format off and // clang-format on to disable and enable clang-format, respectively.

struct DataOptional {
    int v1;
    int v2;
};

// clang-format off
AJSON_FIELDS(DataOptional,
             (v1, "v1")
             (v2, "v2", AJsonFieldFlags::OPTIONAL))
// clang-format on

AUI's .clang-format#

Place this .clang-format file in root of your project.

# options: https://clang.llvm.org/docs/ClangFormatStyleOptions.html
BasedOnStyle: Google
IndentWidth: 4
ColumnLimit: 120
# does (int) x instead of (int)x
SpaceAfterCStyleCast: true
# spaces, not tabs!
UseTab: Never
# if (x) doStuff()  is not allowed, bad style
AllowShortIfStatementsOnASingleLine: false
AlignTrailingComments: true
SpacesBeforeTrailingComments: 3
AlignConsecutiveMacros: Consecutive

# use \n instead of \r\n
UseCRLF: false

AccessModifierOffset: -4
EmptyLineBeforeAccessModifier: Always
EmptyLineAfterAccessModifier: Never

NamespaceIndentation: None
BreakConstructorInitializers: BeforeComma

# we have some problems with ranges-v3 and AUI's AUI_LET, so keep it disabled
SortIncludes: Never


################################################# UI BUILDING ##########################################################

# use shorter indentation for UI building stuff (like in Dart/Flutter)
BracedInitializerIndentWidth: 2
ConstructorInitializerIndentWidth: 2

# better spacing around AUI_WITH_STYLE and AUI_LET.
# before:
# view AUI_WITH_STYLE{Expanding()},
# view AUI_LET{printf("Hello");},
#
# after:
# view AUI_WITH_STYLE { Expanding() },
# view AUI_LET { printf("Hello"); },
Cpp11BracedListStyle: false
SpaceBeforeCpp11BracedList: true

# forces nested initializer lists to begin with their own lines.
# note: you should use comma after last element in initializer lists.
# before:
# Centered { Horizontal {
#
# after:
# Centered {
#   Horizontal {
PenaltyIndentedWhitespace: 1000
BreakBeforeBraces: Attach
BreakBeforeBinaryOperators: None

StatementAttributeLikeMacros:
  - emit

# fix AUI's scary macros.
Macros:
  - AUI_LET=+[]
  - AUI_THREADPOOL=[]
  - AUI_DECLARATIVE_FOR(a,b,c)=[v2=b,v3=c](a)
  - AUI_DECLARATIVE_FOR_EX(a,b,c,d)=[d,v2=b,v3=c](a)
  - AUI_WITH_STYLE=+std::vector