.. _contribcode: Code ==== Some general tips to keep in mind: - If you add functionality, please update the documentation accordingly. - If you add functionality, add tests if applicable. This helps make sure Arbor is stable and functionality does what it's supposed to do. - If you work on the public C++ API, provide Python wrappings. - Make sure Arbor compiles and has no new warnings. - Consider the coding guidelines below. Python ------ We follow the `black `__ coding style. It is enforced by an automated check on each pull request. You can run the following commands to apply it: .. code:: # Install the formatter if not present pip install black # Automatically apply style to all Python files. If unsure what this does read on. black . The formatter can also be run with ``--check`` to list offending files and ``--diff`` to preview changes. Most editors can `integrate with black `__. C++ --- The main development language of Arbor is C++. For Arbor, we start with the community guidelines set out in the `C++ Core Guidelines `__. These guidelines are quite generic and only give loose guidelines for some important topics like variable naming and indentation. This wiki will describe the specific extensions and differences to the C++ Core Guidelines - variable naming. - code formatting (indentation, placement of curly braces, ``int const&`` vs ``const int&`` etc.) - rules for topics not covered in the Core Guidelines (e.g., CUDA). - exceptions to the rules given in the Core Guidelines. - rules for the CMake build system and directory structure. - rules for external dependencies (e.g., Boost). .. TODO:: This page needs revision. Code organisation ----------------- Source files naming conventions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. TODO:: describe the public/private header and source code organization. - ``.cpp`` extension for source files - ``.hpp`` extension for header files - Names must be lowercase with words separated with underscores. Namespaces ---------- All code is in ``namespace arb``. As an example, the ``std::make_unique<>`` function template was not provided as part of C++11 (it wasn’t introduced until C++14), and we would like to use it in Arbor. The code sample below shows how namespaces are declared and formatted in Arbor: :: namespace arb { namespace util { // just because we aren't using C++14, doesn't mean we shouldn't have make_unique template std::unique_ptr make_unique(Args&&... args) { return std::unique_ptr(new T(std::forward(args) ...)); } } // namespace util } // namespace arb In the example above, the namespaces are not indented. However, namespaces should be indented if they are declared in the middle of code to make their existence obvious to the person reading the code. Use an ``impl`` namespace to hide implementation details that should not be exposed to user space. You can use ``using`` statements to import individual types or functions from a namespace - only if it really improves the readability of your code - only in a function or class scope: don’t pollute namespaces Formatting statements --------------------- A lot of the rules here are purely a matter of personal taste, imposed by the guy who got to set the rules. That said, it follows the accepted practice in the C++ community (if we accept that not everybody has the same accepted practice), and if followed consistently, will make code easier to understand. .. code:: c++ template class array { public: using value_type = T; value_type& operator[] (std::size_t i) { return data_[i]; } const value_type& operator[] (std::size_t i) const { return data_[i]; } std::size_t size() const { return size_; } private: value_type* data_; std::size_t size_; }; // use new lines and indentation to make complex template expressions // human readable template < typename T, typename = typename // assert that T is a built-in arithmetic type std::enable_if< std::is_arithmetic::value > > T sum(const array& in) { return std::accumulate(in.begin(), in.end(), 0); } TODO: When declaring an operator, should we leave a space between the operator and the following opening parenthesis, or should we follow the convention we use for functions where we don’t leave a space? Indentation and whitespace cleanup ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - No tabs, 4 spaces - Take the extra effort to remove trailing whitespace (at the end of the lines and the file). - Respect 80-column limit, but go for longer lines when they make sense (and make the code clearer) Variable naming conventions ~~~~~~~~~~~~~~~~~~~~~~~~~~~ All lowercase, words separated by ``_``, but template parameters follow camel case: .. code:: c++ template class my_class { public: // ... private: ValueType val_; }; Single letter template parameters should be preferred. TODO: Or should we force single letter parameters aliased by more meaningful type names inside the class (either public or private depending on our intent)? *Avoid* obfuscated names of old C heritage. Recurring variables naming conventions ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TODO: Some variable names are recurring a lot inside every code. It would be nice if we could decide on the most common ones. - ``count`` or ``cnt`` - ``index`` or ``idx`` - ``iter`` or ``it`` - … Ben says “depends… I would use ``count`` or ``index`` unless the scope of the variable is very small. Using ``it`` is standard C++ shorthand, but again for fairly limited scope.” Member variables ~~~~~~~~~~~~~~~~ Private member variables must be suffixed by ``_``, while public member variables must not. TODO: Any conventions about ``static`` variables, ``const``\ s or global ``const``\ s? Member initialisation lists ~~~~~~~~~~~~~~~~~~~~~~~~~~~ Constructors member initialisation lists should be as follows: .. code:: c++ // everything goes on one line if clear class my_class { public: my_class(int a): a_(a) {} my_class(int a, int b, int c): a_(a), b_(b) , c_(c) {} private: int a_ = 0; int b_ = 0; int c_ = 0; }; // use one entry per line if multiple lines needed class my_class { public: my_class(int a, int o, int p): apple_(a), orange_(o), pear_(find_pair_type(p)) {} private: int apple_; int orange_; int pear_; }; Member functions ~~~~~~~~~~~~~~~~ Make sure to declare ``const`` if it is not changing the object’s state. Getters and Setters ~~~~~~~~~~~~~~~~~~~ Before filling up a class with getters and setters, consider seriously if those members are meant actually to be public. If, nonetheless, getters and/or setters are needed, don’t use the ``get_`` and ``set_`` prefixes. .. code:: c++ template class my_class { public: // ... T value() const { return value_; } void value(const T& val) { // perhaps do something before assigning, otherwise it could be just public value_ = val; } private: T value_; }; Declaring references and pointers ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code:: c++ // ok std::string& s = ...; const std::string& s = ...; std::string* s = ...; const std::string* s = ...; std::string* const s = ...; // not ok std::string &s = ...; const std::string &s = ...; std::string *s = ...; const std::string *s = ...; std::string *const s = ...; Generally, we follow C++’s convention for references and pointers, as it is the style used in the C++ standard, and also the recommendation of the `C++ Core Guidelines NL.18 `__. Precedence and the C++ language grammar may offer some support the other convention, but not enough support! Macros ~~~~~~ Macros are C-ish, so they must be avoided. If not possible, they must be written in capitals, with words separated by underscores. Always use ``{}``, even for single statement ``if``, ``for``, etc ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ It makes code clearer and avoids nasty bugs that occur when refactoring. It also avoids some errors when merging with git. :: // ok for (auto& v: vector) { // increment the value! v++; } // bad for (auto& v: vector) // increment the value! v++; don’t put ``{`` on a new line ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Except when indentation of arguments or when doing member initialization in constructors. :: // it makes sense to have the { on a new line here for clarity std::vector foo( std::vector>& values, std::map& name_table) { // do some work } leave a space between ``if``, ``for`` etc and following parenthesis ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Accords with `K&R style `__, and makes a visual distinction with function evaluation :: // ok for (auto& v: vector) { v++; } // not ok for(auto& v: vector) { v++; } use ``using`` instead of ``typedef`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ It is easier to read, consistent with ``auto``: :: // good using int_container = std::vector; // bad typedef std::vector int_container; and can be used for template aliases :: template using aligned_container = std::vector>; Use scoped enum instead of enum ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :: // good enum class ionKind {sodium, calcium}; // bad enum ionKind {ion_sodium, ion_calcium}; And stick to the naming scheme for all enums of ``xxxKind`` to make it clear throughout the code whenever an enum is being used, for example: :: auto i = current(voltage, ionKind::calcium); Use ``struct`` for POD wrappers ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ But ``class`` if it has any sort of fancy logic associated with it Memory management ----------------- use ``unique_ptr`` ~~~~~~~~~~~~~~~~~~ Actually, feel free to use naked pointers in your code, but make sure that you use smart pointers to handle allocation and freeing of memory. When a developer sees a naked pointer in Arbor, they can think, “good, I don’t have to worry about responsibility for freeing that memory”. Furthermore, if ``unique_ptr`` handles allocation and freeing of memory, the user doesn’t have to concern themselves with freeing memory ever. This practice implies that care must be taken to ensure that the resource managed by a ``unique_ptr`` has to outlive any raw pointers that are obtained from its ``get()`` member. while avoiding ``shared_ptr`` whenever possible ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If you think long and hard, you will probably realise that you actually want a ``unique_ptr``. Shared pointers have performance overheads and are quite easy to misuse. For example, by creating circular references that ironically lead to memory never being freed. Header files ------------ use pragma once ~~~~~~~~~~~~~~~ Use ``#pragma once`` to guard against including the same header twice. This might not be completely standard compliant, but it is supported by every compiler under the sun, and is much cleaner than ``#ifdef`` guards. don’t rely on headers being included elsewhere ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ For example, if you use ``std::vector`` in a file, make sure to have ``#include `` at the top of the source file. Relying on headers being included elsewhere can lead to portability problems, for example, on MacOS you have to ``#include `` for some math functions that are imported via other header files with gcc on Linux. Sort headers alphabetically ~~~~~~~~~~~~~~~~~~~~~~~~~~~ To make it easy to search for a header in a long list of includes. For example: .. code:: c++ #include #include #include #include #include use C++ wrappers for C standard headers ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code:: c++ // good #include #include // bad #include #include when calling C stdlib functions, use the ``std::``-prefix versions, e.g., ``std::printf(...)`` instead of ``printf``. Most of the times C++ wrappers just bring into ``std`` the C declarations, but sometimes the wrappers have more syntactic sugar and call the same internal builtins that their C counterparts call (for example GCC). group headers together ~~~~~~~~~~~~~~~~~~~~~~ In the following order 1. C++ standard libary 2. system C headers (POSIX, kernel interfaces, etc.) 3. external libraries 4. public arbor headers 5. private arbor headers For example: .. code:: c++ // first C++ standard headers #include #include #include // then system C headers #include #include // externals #include // public arbor headers #include #include // private arbor headers (note we use quotes for private project headers). #include "cell_group.hpp" #include "util/optional.hpp"