Contributing
Overview
Contributions to the Balau library are welcome. The easiest way to contribute is to create a fork of the library's repository and submit pull requests for new or enhanced features.
The Balau library has been conceived for two distinct aims:
- to provide a foundation for the development of Unicode aware C++ software applications that have a dependency injection based architecture, have complex logging requirements, and that will be developed with a test driven development methodology;
- to provide a simple and intuitive API for core C++ components and utilities.
Contributions to the injector, logging framework, and test runner are likely to be incremental improvements and bug fixes. Contributions of new components and improvements to other existing components and utilities are open to our imagination and creativity.
Planned features
The general aim of the library's development is reactive rather than proactive. If a useful feature for enterprise quality C++ application development is missing, convoluted, or lacking in features in the C++ standard library or Boost libraries, then the feature is a good candidate for development and inclusion in Balau.
If an existing feature in the standard library or Boost exists and is designed as low level, fine grained, or does not have a straight forward API, then it would be useful to develop a corresponding Balau feature with a simple and intuitive API, as a high level facade of the standard library or Boost feature.
Consequently, there is no complex plan of planned features to be added to the library. The current list of planned features can be seen on the planned features page.
License
Balau is licensed under the Boost Software License - Version 1.0 - 2003. All contributions will need to be licensed under the same license or under a license that allows relicensing under the Boost license.
Repository
The main repository is hosted at https://github.com/borasoftware/balau.
Guidelines
The following guidelines may help with contributions. In addition to avoiding defects, the aim is to maintain an easy to understand code base in order to facilitate with rapid application development.
General
- Classes of appreciable complexity are normally contained in their own header plus optional body file. Smaller classes should share a header with other similar classes / the complex class that uses them.
- Implementation specific classes are placed in files inside an Impl sub-folder next to the main class(es), and namespaced within an inner Impl sub-namespace. The injector and logging system are examples of this arrangement.
- Logging in Balau library components and functions should be restricted to code in body files only. This allows an incomplete declaration to be made for the Logger class in header files, thereby simplifying the include order in application code.
- Before creating a pull request, the code should be formatted to the Balau code style guidelines provided below.
Testing
- All anticipated uses cases of the new or enhanced feature should be tested by adding or revising the feature's test cases in the src/test folder.
- When a modification is made or new code is introduced and the tests are written, the test application should be run with Valgrind memcheck and helgrind tools, in order to verify that there are no memory and threading issues. If there are memory and/or threading issues, these should be debugged and resolved before raising a pull request.
- After completing the modification or new feature, the Balau test suite should be verified to pass in release mode in addition to debug mode.
Strings
- All features that use or manipulate std::string objects should be designed to function correctly with UTF-8 text, unless there is a valid reason not to do so (i.e. the data in the std::string is not UTF-8 text and/or is clearly processing pure bytes of information). Such exceptions should be documented as being so, otherwise it will be assumed that the bytes within a std::string object are UTF-8.
- The wchar_t character type and associated string and stream types must not be used anywhere in the library.
- UTF-8 character processing is supported by the ICU wrapper functions found within the Balau::Character namespace.
- When developing a utility function or component that manipulates UTF-8 strings, an equivalent UTF-32 version should normally be created alongside the UTF-8 version.
- When creating a function that accepts one or more const strings, string views should be used instead of std strings.
- Beware of the std::string_view::data() method. Do not use this method unless you are 100% sure null pointer character array termination is not required in the program logic that uses the data.
- Beware of returning std::string_view from methods. Returning std::string_view from a method should only be done when the lifetime of the referenced string data is guaranteed to outlive the string view being returnd.
Const correctness
- All global variables, member variables, and local variables should be made const unless there is a reason to make them non-const.
- All member functions should be made const unless there is a reason to make them non-const.
Concurrency
- When developing code that relies on multi-threaded execution and inter-thread data sharing, the developed code should use standard C++ 11 memory ordering features, principally the atomic operations library. Mindful use of std::memory_order may improve performance on some platforms, but is usually not necessary, especially for the principal x86-64 target platform.
Memory management
- Manual use of memory allocation via the delete operator should be avoided, except in exceptional circumstances where the code itself is acting as a pointer container that provides object lifetime management. Otherwise, the C++11 standard library pointer containers should be used for heap based objects.
- Use of C++11 move semantics should be preferred to copying. Copying should be limited to cases where the caller must retain ownership of the passed argument. When copying is used, a pass by value and move approach should be used in preference to pass by reference and copy.
Templates
- Unnecessarily complex templated code should normally be avoided. Complex templated code should be used when the resulting solution is more elegant and/or the resulting public API is more simple than it would be if an alternative approach were taken AND quick start documentation for common use cases is provided. Our aim is to provide components and utilities for rapid enterprise quality application development. Development teams working on enterprise software applications do not normally have enough time for in-depth studies of the inner workings of a complexly templated component in order to fulfil a simple use case.
- Variadic functions and methods should always be implemented via C++11 parameter packs. Fold expressions may assist in the simplification of otherwise complex variadic tasks.
Macros
- The use of macros should be avoided, unless there is no other way of implementing a feature (examples include the file/line logging macros and the injector class macros).
- The use of conditional compilation macros should be avoided, unless there is no other way of implementing a feature. If some complex conditional compilation is required for resolving differences across platforms, put the code in platform specific headers and include them appropriately in the main source code file. The exception to this is the use of the BALAU_DEBUG macro, which is used to enable code in debug builds.
Documentation
- Public classes, functions, and methods should be documented with triple forward slash /// documentation entries. Public items that are not documented will not be added to the API documentation. Each entry should have a single line description, a line break, then a more in-depth description. Parameters, exceptions, and return variables that are non-obvious should be documented with @tparam, @param, @throws, and @return entries. As Balau is a software library, users rely on the API documentation to use it, and consequently the API documentation is as essential as the code itself.
- Permanent block style /* */ code comments should never be used, as they complicate temporary block commenting/uncommenting.
- Protected and private classes, functions, and methods of any complexity should be documented with a brief, usually single line double forward slash // comment that indicates the goal of the entry. Triple forward slash /// documentation entries should not be used unless the information for the item needs to appear in the API documentation.
- Code comments are generally unnecessary, unless a code fragment is unusually difficult to understand. In this case, a line or two of comments clarifying the goal of the fragment is useful.
- Author or version information should not be included in source code files. Detailed author information is available by examining the source code repository (git-blame).
- When a new class or feature is developed or an existing class or feature is enhanced, a corresponding BDML documentation page should be created or revised with the new or revised usage. The BDML documentation pages are found in the src/doc/manual folder. As the documentation is XML based, it can be written during development of the code and committed in the same change-sets as the code. Writing BDML documentation can be performed during compilation pauses. BDML documentation should follow the standard structure of Overview, Quick start (starting with the header(s) to include), optional detailed documentation sections, and optional Design section.
Code style
The aim of the code style used in the Balau library is maximum readability. The following sub-sections discuss aspects of the code style.
Indentation
Balau code style uses smart tab indenting. This allows developers to choose the tab size they desire in their source code editor, whilst maintaining correct alignment of vertically aligned items. Indentation size is thus not specified in this code style.
Files
Lines should be limited to 120 characters, when viewed with a tab size of 4 characters. Comment lines should normally be limited to approximately 80 characters.
Newlines in files must be LF, not CRLF or CR. Files should end with a newline. Whitespace should be removed at the ends of lines (configure the IDE to do it for you on saving).
Files should be named "([A-Z][a-z]*)+.hpp" for header files and "([A-Z][a-z]*)+.cpp" for body files.
Files should be grouped into a hierarchy of folders, the names of which are normally the same as the local namespace of the files contained within. The folder structure should normally follow the namespace structure.
Headers should use include barriers, the names of which exactly follow the names in the folder hierarchy in which the file is situated. The #pragma once definition should not be used. This will be revised when C++20 modules are standardized.
Includes in header files should be avoided when possible (use incomplete declarations). Otherwise, the full include path of non test header files should be used between < and > tokens.
// Example include in header file.
#include <Balau/Network/Http/Server/HttpWebApp.hpp>
Identifiers
Identifiers should be styled according to the following rules.
- Namespace, class, enum, and enum entry names should be upper camel case.
- Function, method, field names, and local variables should be lower camel case.
- Public macros (macros to be used in end application code) should be uppercase camel case.
- Private macros (macros to be used in other Balau macros) should be uppercase camel case and start with an underscore.
- Underscores should not be used apart from starting private macro identifiers and as a suffix for constructor parameters that set similarly named class fields in constructor initialisation lists.
- Identifiers other than local variables should be as short as possible whilst providing adequate information on their goals and avoiding abbreviations. It is fine to use long identifiers if there is no other way of providing sufficient information, but this should be the case for a minority of identifiers.
- Local variables, especially those used for temporary counters and the like, should be short, often a single letter.
Metadata naming conventions (type, kind) must not be used (use an IDE that indicates identifier semantics for you). Semantic apps identifiers should mostly be avoided, apart for template parameter identifiers.
Spacing
Spacing of source code tokens aims to maximise visual grouping of related tokens, whilst maintaining a compact representation. The spacing rules are specified as a subtractive list. All tokens should be surrounded by a space, with the exception of the following, which do not have space before and/or after them:
- after '(', '[', and before ']', ')';
- after '<' and before '>' when these characters are template header tokens;
- before '(' and after ')' when these characters are function call parameter list parentheses;
- after '::';
- before '::', unless the character pair references the global scope;
- between public, protected, and private tokens and their associated ':';
- between 'case' and its associated ':' in a switch statement;
- before ',' and ';'
- after '*' and '&' when these characters are de-reference and address-of tokens;
- after '+' and '-' when these characters are unary operators.
Braces
Opening '{' braces are placed on the same line as the associated statement.
Single line code blocks should not be used.
Single line code blocks must use braces.
// Single line code block in if statement.
if (i < 0) {
foo();
}
Case blocks within switch statements should also use braces.
// Switch statement case blocks.
switch (type) {
case Type::Simple: {
foo();
break;
}
case Type::Composite: {
foo2();
break;
}
default: {
throwError();
break;
}
}
Horizontal/vertical
This principal maximises the readability of parameter lists, enum entries, argument lists, and other delimited lists found in the source code.
The general idea is that it is easiest to read a delimited list when it is presented either in a single line or as a vertically aligned list. The choice of the two approaches is dictated by the length of the line when the delimited list is presented on a single line. If a single line fits within the 120 character limit, a single line is chosen. Otherwise, a vertically aligned list is chosen.
The following extracts illustrate this approach on method parameter lists.
// From HttpWebApp class.
public: virtual void handleGetRequest(HttpSession & session, const Request & request) = 0;
In this example, the method's header fits on a single line without overrunning the 120 character limit.
// From HttpServer class.
public: HttpServer(std::shared_ptr<Injector> injector,
const std::string & serverIdentification,
const TCP::endpoint & endpoint,
std::string threadNamePrefix_,
size_t workerCount_,
std::shared_ptr<HttpWebApp> httpHandler,
std::shared_ptr<WsWebApp> wsHandler,
std::shared_ptr<MimeTypes> mimeTypes = MimeTypes::defaultMimeTypes);
In this example, the method's header would overrun the 120 character limit if it were presented on a single line, so the parameter list is arranged vertically aligned.
Delimiters
With the exception of commas in function and method parameter lists (as illustrated in the previous code example), the delimiters in a vertically aligned, delimiter separated list are each considered to belong to the following item in the list.
For example, the commas in the vertically delimited superclass call in the following code extract lead the arguments.
protected: MultiProcessTestRunnerExecutor(CompositeWriter & writer_,
bool useNamespaces_,
GroupedTestCaseMap & testCases,
unsigned int concurrencyLevel_)
: TestRunnerExecutor(
std::unique_ptr<TestResultQueue>(new MultiProcessTestResultQueue)
, writer_
, useNamespaces_
, testCases
, true
)
, concurrencyLevel(concurrencyLevel_)
, sharedMemoryNamePrefix(createSharedMemoryNamePrefix()) {}
This code extract also illustrates comma delimited field initialisation.
Closing brackets
When a vertically aligned list is used within opening and closing brackets/parentheses, the closing bracket/parenthesis is placed on a newline. An example of this is shown in the previous code extract. The following is an example with two levels.
tests.emplace_back(
FlattenedTestCase(
testIndex
, executionModels
, preText
, ""
, testCase.name
, testCase.group
, std::move(testCase.method)
)
);
Visibility prefixes
The visibility prefixes are the public, protected, and private tokens used in class declarations.
In Balau sources, class declarations use explicit visibility prefixes on all declaration items (fields, methods, inner class/enum declarations). This provides physically colocated visibility information for all items, avoiding the need to search upwards in the source code for the visibility of a class item and declaring that the item is part of a class declaration.