Documentation on coding conventions and ArmarX package structure
From the SoftwareWiki, which is about to be closed, the following content needs to be migrated (@birr, @daab):
Click to open
# Conventions for ArmarX PackagesIn the redesign of the ArmarX software architecture of 2022, we introduced new conventions for the naming and structure of ArmarX packages, applying to both framework and application packages.
Overview
The goal is to follow the principle of the least surprise. This means making the following aspects as consistent as possible, within the respective possibilities:
- name of the ArmarX package (CMake project),
- its location in the ArmarX workspace (as defined by Axii),
- its location on GitLab,
- library names and locations,
- include paths, and
- C++ namespaces.
Here is an example of a general (application) package and an ArmarX framework package:
Use Case | Application without Namespace | Application with Namespace | ArmarX Framework |
---|---|---|---|
Package Name | my_robot_code |
skills |
mobile_manipulation |
Package Namespace | -- | my_robot |
armarx |
CMake Declaration | armarx_package(my_robot_code) |
armarx_package(skills NAMESPACE my_robot) |
armarx_package(mobile_manipulation NAMESPACE armarx) |
CMake Project Name | my_robot_code |
my_robot_skills |
armarx_mobile_manipulation |
Top-Level C++ Namespace | ::my_robot_code:: ... |
::my_robot::skills:: ... |
armarx::mobile_manipulation:: ... |
Top-Level Include Path | <my_robot_code/ ...> |
<my_robot/skills/ ...> |
<armarx/mobile_manipulation/ ...> |
Project Directory Path | .../my_robot_code |
.../my_robot/skills |
.../armarx/.../mobile_manipulation |
GitLab Project Path (Slug) | .../my-robot-code |
.../my-robot/skills |
.../armarx/.../mobile-manipulation |
CMake Target (e.g. library) | my_robot_code::my_library |
my_robot_skills::my_library |
armarx_mobile_manipulation::my_library |
ArmarX Package Naming
- ArmarX packages have a name and an optional namespace for semantic grouping.
- The resulting CMake project is
<namespace>_<name>
if a namespace is specified, and just<name>
otherwise. - Examples:
-
armarx_package(my_robot_code)
=> CMake projectmy_robot_code
-
armarx_package(skills NAMESPACE armar6)
=> CMake projectarmar6_skills
-
- See also: ArmarX CMake documentation
- The resulting CMake project is
- The
armarx
package namespace is reserved for ArmarX packages.- Example:
-
armarx_package(navigation NAMESPACE armarx)
=>armarx_navigation
-
- Example:
- Packages integrating specific methods or devices should be suffixed by "ArmarX"
(and may be grouped in namespaces as suited).
- Examples:
simtrack_armarx
roboception_armarx
ros_armarx
- Examples:
Source Structure
The source
directory of an ArmarX package my_robot_code
(without a namespace) looks like this:
source/
my_robot_code/
# Libraries:
core/
CMakeLists.txt # Library my_robot_code::core, namespace ::my_robot_code
client/
CMakeLists.txt # Library my_robot_code::client, namespace ::my_robot_code::client
conversions/ # Just a directory for semantic structuring.
eigen/
CMakeLists.txt # library my_robot_code::conversions_eigen, namespace ::my_robot_code::conversions::eigen
opencv/
CMakeLists.txt # library my_robot_code::conversions_opencv, namespace ::my_robot_code::conversions::opencv
components/ # Parent directory for all components.
# Components:
component01/
CMakeLists.txt # Component my_robot_code::component01, namespace ::my_robot_code::components::component01
component02/
CMakeLists.txt # Component my_robot_code::component01, namespace ::my_robot_code::components::component02
qt_plugins/
# Plugins for the ArmarX GUI:
...
If the package has a namespace (e.g. skills NAMESPACE my_robot
),
the top-level directory matches the namespace:
source/
my_robot/
skills/
...
-
Libraries are defined in directories directly under the top-level source directory
(
my_robot_code/source/my_robot_code
) of the package, or in semantically sensible subdirectories.- This rule emphasises the role of libraries as first class elements of a package.
- (Libraries are not in a common
libraries
directory, as in old packages.) -
Note: CMake targets can contain
::
only once. Therefore, this::
is reserved for the separation of CMake project name and target name. For nested targets, use_
as delimiter instead.
-
Components are defined in directories in a common
components
directory (in contrast to libraries).- Note: Components should be implemented as shallow as possible, only providing properties entry points for your code, properties for parametrization and connection other Ice components or topics. Put as much code as possible into sensible libraries.
- An Ice or ARON library is associated with a library or component.
- Its target name has an
_ice
or_aron
suffix, respectively. - The namespaces introduced by Ice and ARON are
dto
andarondto
, respectively. (Note that Slice prohibits modules starting with "Ice".) - Example:
my_robot_code/ core/ # Library my_robot_code::core, namespace ::my_robot_code (skip 'core', see above) ice/ # Library my_robot_code::core_ice, namespace ::my_robot_code::dto aron/ # Library my_robot_code::core_aron, namespace ::my_robot_code::arondto my_skill/ # Library my_robot_code::my_skill, namespace ::my_robot_code::my_skill ice/ # Library my_robot_code::my_skill_ice, namespace ::my_robot_code::my_skill::dto aron/ # Library my_robot_code::my_skill_aron, namespace ::my_robot_code::my_skill::arondto
- Its target name has an
Include Paths and C++ Namespaces
- The top-level namespace of all C++ types matches the package name.
- Example:
- Declaration:
armarx_package(my_robot_code)
- Namespace:
my_robot_code::
- Declaration:
- Example:
-
Include paths (and thus the location of libraries) match the namespace:
- Example cont.:
- Include:
<my_robot_code/ ...>
- Include:
- Example cont.:
- If a packages has a package namespace, the namespace is the first component of the C++ namespace and includes:
- Example:
- Declaration:
armarx_package(skills NAMESPACE armar6)
- Namespace:
armar6::skills::
- Include:
<armar6/skills/...>
- Declaration:
- Example:
-
Nested namespaces match the directory (i.e. include) structure.
- Example:
-
armar6/skills/client/Bar.h
=>armar6::skills::client::Bar
-
- Example:
- Directories named
core
are excluded from C++ namespaces (but of course are part of the include path).- This allows creating
core
libraries providing core code in a non-nested namespace. - Example:
-
armar6/skills/core/Foo.h
=>armar6::skills::Foo
-
- This allows creating
Click to open
# Coding ConventionsThis document is a compilation of general code conventions. For many projects (e.g., all ArmarX framework projects) corresponding code formatting configuration files are distributed via Axii to enforce them. This document serves as a reference for cases where formatting tools are not employed and to justify the made decisions.
In case you notice any discrepancies between this document and any code formatter configuration files (such as clang-format), open an issue for discussion.
ToDo: Review and update.
General
The documentation as well as class and variable names are written in English.
The rule set for the coding conventions is based on the BSD/Allman style. Here is an example:
// Header file "Point.h"
#pragma once
#include <memory>
namespace foo
{
class Point
{
public:
Point(float x = 0, float x = 0);
bool isZero() const;
float distanceTo(const Point& other) const;
static float distanceBetweeen(const Point& a, const Point& b);
private:
double x;
double y;
};
using PointPtr = std::shared_ptr<Point>;
enum class MyEnum
{
First,
Second,
Third
};
const float pi;
}
// Source file "Point.cpp"
#include "Point.h"
#include <cmath>
namespace foo
{
Point::Point(double x, double x) :
x(x), y(y)
{
}
bool Point::isZero() const
{
bool isZero = false;
if (0 == x and 0 == y)
{
isZero = true;
}
return isZero;
}
float Point::distanceTo(const Point& other) const
{
float dx = x - other.x;
float dy = y - other.y;
return std::sqrt(dx * dx + dy * dy);
}
float Point::distanceBetweeen(const Point& a, const Point& b)
{
return a.distance(b);
}
}
Note: Don't implement a Point
type. In this example, use Eigen::Vector2f
instead.
Classes
- Class names are written in
PascalCase
(starting with an uppercase letter). - Classes are stored in files
ClassName.h
andClassName.cpp
named exactly like the class. - Class methods should preferably be implemented in the source (.cpp) file (including constructors)
- For type declarations, use
using Y = X;
instead oftypedef X Y;
.
Class members
- All class members (non-static and static, methods and variables, const and non-const) are written in `camelCase``.
- Members are NEVER aligned in the source code. There is only one space between the type name and the variable name (
Foo foo
instead ofFoo foo
for alignment). Maintaining this order is too much work if members get added or removed.
Enums
- Use enum classes (
enum class MyEnum {...};
) - Enums are named like classes (
PascalCase
) without special prefix. - Enum entries are
PascalCase
as well, without special prefix (TypeOne
instead ofeTypeOne
) - If you cannot use enum classes (e.g. in slice files), prefix the enum entries with the enum's name:
enum class MyEnum { MyEnum_One, MyEnum_Two, MyEnum_Three };
C++ Pointers
- When using pointers, use smart-/shared pointer whenever possible
- Prefer
std::unique_ptr
. Usestd::shared_ptr
only when necessary. (std::shared_ptr
makes it unclear who is owning the object.) This requires#include <memory>
. - For classes inheriting from Ice use
IceUtil::Handle
. - For classes inheriting from Qt classes use
QPointer
if the object has a parent andQSharedPointer
if no parent is set. - If your class is commonly used in pointers, provide a
ClassNamePtr
type definition (withusing
), e.g.using ClassNamePtr = std::unique_ptr<ClassName>
.
Naming Conventions
ToDo:
- Topic names
- Library/component names
Indentation
- NO TABS
- Standard indentation value is 4 Spaces
- No trailing white spaces at the end of line (most editors can handle that automatically)
- All opening/closing brackets start on a separate line aligned with the enclosing code block they belong to.
- No space between method name and parenthesis. (
method()
instead ofmethod ()
) - One space between all control structures and the beginning parenthesis of their corresponding condition (
if
,while
,for
,else if
,switch
). (if ()
instead ofif()
) - One space after each comma or semicolon in the middle of a statement. (
a, b
instead ofa,b
) - One space before and after a comparison or assignment operator (
=
,==
,!=
,>
,<
,>=
,<=
, ...). (a == b
instead ofa==b
) -
public:/protected:/private:
Align with the class definition. Put two blank lines above and one blank line below (exceptpublic:
at the top of a class definition). - The
:
of the initialisation list is placed after the closing bracket of the constructor with a white space between. (Foo::Foo() :
instead ofFoo::Foo():
) - The initialisation list itself is placed on a new line and indented by the standard indentation value.
- Everything inside namespaces is indented by the standard indentation value.
- If a "normal" pointer is needed it is written the following way: * (one space before the variable name, no space after the class/type name).
- One space between leading 'const' and a class/type name.
- Class/ type name followed by a space followed by 'const' followed by another space followed by the variable name.
Natural Langauge Logical Operators
Similar to Python, C++ allows for expressing logical operators such as conjunctions and disjunctions with natural language keywords.
- Use
if (not boolean_expression)
instead ofif (!boolean_expression)
- Use
if (bool_expr_1 and bool_expr_2)
instead ofif (bool_expr_1 && bool_expr_2)
- Use
if (bool_expr_1 or bool_expr_2)
instead ofif (bool_expr_1 || bool_expr_2)
- You can use
if (bool_expr_1 xor bool_expr_2)
instead ofif (bool_expr_1 ^ bool_expr_2)
, but this is not supported in Python - Do not use the other operators
compl
,bitand
,bitor
,and_eq
,or_eq
,xor_eq
,not_eq
, digraphs or trigraphs.
Reasons for this decision:
- Natural language is more expressive
- Consistency with Python
- Discouraged operators are not well-known and create more confusion than they help. Especially assignment operators
and_eq
etc. are confusing, since they suggest "and equals" but actually mean "and assign". Digraphs and Trigraphs have been deprecated and marked for removal.
Files
- Files containing one class are named like the classes they contain with exactly the same upper-/lowercase spelling.
- Files mainly containing functions or includes are named in
snake_case.h/cpp
. - Headers start with an include guard:
#pragma once
(except a license note) - Header files end with
.h
. Pure template headers may end with.hpp
. - C++ files end with
.cpp
(no.cxx
,.cc
or anything else). - Slice files end with
.ice
- Source files are located in
source/PackageName/
- Slice files are located in
source/PackageName/interface/
General
- Never place a 'using namspace XYZ;' directive in header files. This is strict. This results in namespace pollution and might override method definitions in namespace XYZ.
- Include what you use: Remove all
#include
directives in header files which are not required for error free compilation (see next item). Move includes which are not needed by including files to the.cpp
file. - Use forward class declarations in header files for each class (Reason: this results in a cleaner and faster build).
- Use
cmath
instead ofmath.h
andcstdio
instead ofstdio.h
in C++ code (applies to most of the standard C header files). This is the official C++ way of including C headers. Using the.h
versions can result in namespace issues. The method in the 'c' prefixed files are all defined in the std namespace. See here for a list of available headers: http://www.cplusplus.com/reference/clibrary/. Only use the C versions if method definitions should be missing in the C++ headers.
Slice Definitions
- Interface, class, struct, and exception names are written
PascalCase
. - Interface definitions always end with "Interface" or "Topic".
- Implementations of an interface definition omit the "Interface" suffix.
- Slice files start with an include guard:
#pragma once
Business Objects and Data Transfer Objects
If you need to send a C++ class over ice, follow this pattern:
- Implement a pure C++ implementation using useful C++ types (from Eigen, Simox, ...), which can be used in server and client code to process and handle the data. This the business object (BO).
- In ice, define a struct or class which mirrors the C++ type's data, using available Ice types (e.g.
VectorBase
,PoseBase
). This is the data transfer object (DTO). If the business object is defined asarmarx::foo::MyClass
, define it in the DTO as module ``armarx::foo::icedto::MyClass`. - Provide free
fromIce() and
toIce()` methods in the BO's namespace (note the constness).
namespace armarx::foo
{
void fromIce(const icedto::MyClass& dto, MyClass& bo);
void toIce(icedto::MyClass& dto, const MyClass& bo);
// Optional
MyClass fromIce(const icedto::MyClass& dto);
icedto::MyClass toIce(MyClass& bo);
}
Feel free to provide a constructor in the BO taking a DTO. Put these functions either in the BO's file or in a ice_conversions.h
in the BO's library.
- In library code, use the BO.
- When sending the data over ice, use
toIce()
. When receiving data, usefromIce()
. This should be done at the interfaces to ice, usually in ice interface functions.
Follow the same namespace convention for other serialization frameworks. E.g. for ARON, use the intermediate namespace arondto
, functions fromAron(), toAron()
and file aron_conversions.h
.
Potentially outdated:
- Class names in Slice files end with "IceBase".
- Subclasses of classes defined in Slice files omit the "IceBase" suffix.
- For enums, apply the rules for enums where no enum classes are available.
ToDo
- Naming of report functions?
- Slice Documentation?
- #ifdef convention?
CMake
- Functions and macros are spelled lowercase.
- All header and source files are listed explicitly (not by using GLOB or GLOB_RECURSE).
- NEVER use
find_package(Xyz REQUIRED)
. Instead use a combination offind_package(Xyz QUIET)
andarmarx_build_if(Xyz_FOUND "Xyz is missing")
- Every generated file must be generated inside CMAKE_BINARY_DIR.
Units
- Angle: radian
- Angular velocity: radian / second
- Length/distance/position: millimeters
- Time (especially in Ice): microseconds, stored as long. (In C++, use
IceUtil::Time
.) - Coordinate Systems: right handed. Global +z points up.
ToDo:
- Velocity: millimeter / second?
- Current: ?
- Force: ?
- Torque: ?
Scenarios
- Scenarios are located in the "scenarios" directory of each ArmarX Package.
- Each scenario is placed inside its own directory under "scenarios".
- Configuration files must follow the format: "config/${COMPONENT_NAME}[.optionalString].cfg".
Documentation
Potentially outdated:
- Documentation is done using Doxygen.
- Slice files are documented using slice2html.
- All classes, methods, members, etc. are documented in the header files, NOT in the cpp files. (Template methods can only be implemented and thus documented in header files.)
- Section identifiers on pages are prefixed with the page name to prevent duplicate identifiers which lead to inconsistent cross-linking.
- It does not matter if you use
\param
or@param
, but be consistent within your project. Also keep the current documentation style of other projects you are working on. -
${ArmarXPackage_DIR}/etc/doxygen/images
contains images which are included in the documentation with the following doxygen command:
@image html Image.svg "Text Description of image" width=300px
- ${ArmarXPackage_DIR}/etc/doxygen/snippets contains sourcecode stored in .dox files. These are later added with the following command:
@include MyCodeExample.dox`
- Additional documentation resides in
${ArmarXPackage_DIR}/etc/doxygen/pages
and is put into.dox
files. - API documentation is divided into groups defined by
@defgroup
which is usually defined in the header file of the base class - Any class belonging to that group must use the following doc header
@class ClassName
@brief Description of the class in one line
@ingroup GroupOfClass (defined by a @defgroup command)
Here comes the rest of the documentation
How To #include
Include files in the following order:
-
""
: Relative includes including files belonging to the same project and going into the same library. -
<>
: Absolute includes including system headers and headers from other libraries.