Skip to content

Document current coding style guidelines. #2298

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions README/RCodingStyle.cxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/// \file RCodingStyle.cxx
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sometimes it makes sense to add \brief section which describes the contents and the purpose of the files.

/// \author Axel Naumann <axel@cern.ch>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how does this look for multiple authors?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be addressed.

/// \date 2018-07-24
// The above entries are mostly for giving some context.
// The "author" field gives a hint whom to contact in case of questions, also
// from within the team. The date shows whether this is ancient or only part
// of the latest release. It's the date of creation / last massive rewrite.

/*************************************************************************
* Copyright (C) 1995-2018, Rene Brun and Fons Rademakers. *
* All rights reserved. *
* *
* For the licensing terms see $ROOTSYS/LICENSE. *
* For the list of contributors see $ROOTSYS/README/CREDITS. *
*************************************************************************/

// This file demonstrates the coding style to be used for new ROOT files.
// For much of the rationale see Scott Meyer's "Efficient Modern C++" and the
// [C++ core guidelines](https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md),
// plus a fairly large portion of "coding style personality" inherited from 20 years
// of ROOT: there is nothing wrong with them; they make ROOT classes easily
// recognizable. Our users are used to it. No need to change.

// First, include the header belonging to the source file
#include "RCodingStyle.hxx"

// Now the same order as for the header:
// ROOT headers in alphabetical order; all new ROOT headers are under `ROOT/`
#include "ROOT/RAxis.hxx"

// Old ones are found in include/ without prefix.
// New headers should reduce the use of "old" ROOT headers, and instead use
// stdlib headers and new-style headers wherever possible.
#include "TROOT.h" // for TROOT::GetVersion()

// Then standard library headers, in alphabetical order.
#include <vector>

// Include non-ROOT, non-stdlib headers last.
// Rationale: non-ROOT, non-stdlib headers often #define like mad. Reduce interference
// with ROOT or stdlib headers.
#ifdef R__LINUX
// Indent nested preprocessor defines, keeping '#' on column 1:
#include <dlfcn.h>
#endif

// Do not use `namespace ROOT {` in sources; do not rely on `using namespace ROOT` for
// finding the implemented overload. This causes ambiguities if the header and the source
// disagree on the parameters of free functions, e.g.
// ```
// namespace ROOT {
// double Add(const RExampleClass& a, const RExampleClass& b, int i) { return 17.; }
// }
// ```
// will just compile (despite the wrong `int` argument), while
// ```
// double ROOT::Add(const RExampleClass& a, const RExampleClass& b, int i) { return 17.; }
// ```
// will not.

// Unused parameters are commented out; they often carry useful semantical info.
// We use the same parameter names in headers and sources.
double ROOT::Add(const RExampleClass & /*a*/, const RExampleClass & /*b*/)
{
return 17.;
}

// Pins the vtable.
ROOT::RExampleClass::~RExampleClass()
{
}
Comment on lines +69 to +71
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not ROOT::RExampleClass::~RExampleClass() = default; ?


/// Instead of static data members (whether private or public), use outlined static
/// functions. This solves the static initializion fiasco, and delays the initialization
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

initializion

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// functions. This solves the static initializion fiasco, and delays the initialization
/// functions. This solves the static initialization fiasco, and delays the initialization

/// to first use.
const std::string &ROOT::RExampleClass::AccessStaticVar()
{
// NOTE that this initialization is thread-safe!
static std::string sString = gROOT->GetVersion();
return sString;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about return "SomeString";?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a) that's not the point of this demo-function, and b) that won't give you a string&.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have improved the code, I hope it's less confusing now?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but maybe git commit --amend is better than adding a separate change for this.

}

// End the file with a trailing newline.
251 changes: 251 additions & 0 deletions README/RCodingStyle.hxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
/// \file RCodingStyle.hxx
/// \author Axel Naumann <axel@cern.ch>
/// \author Other Author <other@author>
/// \date 2018-07-24 or \date July, 2018
// The above entries are mostly for giving some context.
// The "author" field gives a hint whom to contact in case of questions, also
// from within the team. The date shows whether this is ancient or only part
// of the latest release. It's the date of creation / last massive rewrite.

/*************************************************************************
* Copyright (C) 1995-2018, Rene Brun and Fons Rademakers. *
* All rights reserved. *
* *
* For the licensing terms see $ROOTSYS/LICENSE. *
* For the list of contributors see $ROOTSYS/README/CREDITS. *
*************************************************************************/
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this header on every file? The top 3 lines are obsolete almost immediately, and git is there for that, while the copyright can be in a file at top level stating that that's the copyright unless a different one exists in the file. Also, many files don't have the header in any case:

epsft-53 root $ git ls-files | grep '\.[ch][x][x]' | wc -l
2847
epsft-53 root $ git ls-files | grep '\.[ch][x][x]' | xargs -n1 grep 'Rene Brun' | wc -l
1739

and many others are out of date:

epsft-53 root $ grep -hor '1995-[0-9]\+' | sort | uniq -c
      4 1995-01
      1 1995-10
      4 1995-1996
    114 1995-1997
      3 1995-1998
      3 1995-1999
   1308 1995-2000
     70 1995-2001
    149 1995-2002
     49 1995-2003
    279 1995-2004
    213 1995-2005
    137 1995-2006
    321 1995-2007
    184 1995-2008
     48 1995-2009
     20 1995-2010
     55 1995-2011
     57 1995-2012
     77 1995-2013
     11 1995-2014
     44 1995-2015
     53 1995-2016
     89 1995-2017
     29 1995-2018
      1 1995-8

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The top 3 lines are obsolete almost immediately, and git is there for that,

There is a difference between touching a file and writing it. I have argued before that this is a nice way of leaving traces also for people with limited participation in ROOT, and it's a good way of knowing whom to contact / ownership.

the copyright can be in a file at top level stating that that's the copyright unless a different one exists in the file

Multiple opinions exist on that one.

Also, many files don't have the header in any case

Indeed we have many files that have a different (c).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a difference between touching a file and writing it. I have argued before that this is a nice way of leaving traces also for people with limited participation in ROOT, and it's a good way of knowing whom to contact / ownership.

I still think that git blame is more than sufficient for that, but the final choice is up to you.


// This file demonstrates the coding styles to be used for new ROOT files.
// See the accompanying source file for context.

// File names correspond to the name of the main class that they contain.
// Our users will expect that a file called "RCodingStyle.hxx" contains a class
// named `RCodingStyle`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I totally agree with this, my comment is that there are/will be exceptions: see e.g. ROOT/TypeTraits.hxx, ROOT/RDFNodes.hxx, ROOT/RDFHelpers.hxx

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are not "follow or die" rules. You simply need to have good reasons for violating this.


// All files start with a Doxygen header, followed by the copyright statement.
// The "-year" part is kept up to date by a script.

// The file must contain no trailing whitespace including no indentation-only lines.
// Except for extreme cases, line breaks should happen between 80 and 120 characters.

// The first non-comment, non-empty line must be the #include guard.
// Acceptable forms are:
// ROOT_RCodingStyle, ROOT_RCodingStyle_hxx, ROOT_RCodingStyle_Hxx,
// ROOT_RCODINGSTYLE, ROOT_RCODINGSTYLE_HXX
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not choose one only?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What or whom could a difference here confuse? What's the advantage of choosing one? They should be file-local, I sort of like that nobody can rely on them outside this file ;-)

Copy link
Member

@vgvassilev vgvassilev Jul 17, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should likely mangle at least the component path in include protector. It may become very ugly to debug if the include protector names are duplicate between two unrelated files.

I'd make sense to me to stick with one include protector style (the latter).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Choosing only one is good for consistency. Isn't this trying to improve exactly that? I don't really care which one we choose, as long as it's consistent throughout the code. At the very least one choice should be recommended over the others. There's also #pragma once, but since it's not in any standard, maybe we should stay away from that.

// The first is preferred.
#ifndef ROOT_RCodingStyle

// Especially within header files, using forward declarations instead of
// including the relevant headers wherever possible.
// EXCEPTION: standard library classes must be #included, not forward declared.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even the easy-to-forward-declare ones?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doh, I get your point :)

// All files must be stand-alone: all types must be available through a
// forward declaration or an #include; it is not acceptable to rely on an
// included header to itself include another header needed by the file.

// Include ROOT's headers first, in alphabetical order:
// Rationale: make sure that ROOT headers are standalone.
// Do not use #include guards at the point of `#include`.
// Rationale: compilers optimize this themselves, this is interfering with their optimization.
// Use <>, not "".
// Rationale: "" looks at ./ first; as ROOT headers are included by user code
// that adds context fragility.
#include <ROOT/TSeq.hxx>
// Include v6 ROOT headers last.
// You're welcome to state what's used from a header, i.e. why it's included.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In practice, this might be outdated the second time a file is touched.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fully agree!

#include <Rtypes.h> // for ULong64_t

// Now include other C++ headers hat are not from the stdlib:
// e.g. #include <cppyy/CPpYy.h>

// Include stdlib headers next, in alphabetical order.
#include <string>

// Include C headers last.
// Rationale: non-ROOT, non-stdlib headers often #define like mad. Reduce interference
// with ROOT or stdlib headers.
#include <curses.h>
Comment on lines +45 to +66
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be great if these rules could be automated via the IncludeCategories setting in clang-format.


// Say NO to
// - `using namespace` in at global scope in headers
// - variables, types, functions etc at global scope
// - preprocessor `#define`s except for #include guards (and are cases where
// `#include`d headers require them)

namespace ROOT {
// All new classes are within the namespace ROOT.

// Forward declare before class definitions.
namespace Experimental {
class RAxisConfig;
}

///\class RExampleClass
/// Classes and structs start with 'R'. And they are documented.
class RExampleClass {
public:
// FIRST section: public types.

/// Nested types are documented, too. Do not use `typedef` but `using`.
/// Type aliases end with "_t".
using SeqULL_t = TSeq<ULong64_t>;

///\class Nested
/// This is a nested class. Only use `struct`s for simple aggregates without
/// methods and all public members.
struct Nested {
std::string fNestedName; ///< The nested name.
};

private:
// SECOND section: private types.

// Any type used by any public / protected interface must also be public / protected!
// This is not enforced by C++ but required by this coding style.
using Nesteds_t = std::vector<Nested>;

// THIRD section: data members.

// Data member names start with `f`.
// Documentation comments can be in front of data members or right behind them, see below.
// For the latter case, comments must be column-aligned:

int fIntMember = -2; ///< Use inline initialization at least for fundamental types.
std::vector<Nested> fManyStrings; ///<! This marks a member that does not get serialized (I/O comment).
static constexpr const int kSeq = -7; ///< A static constexpr (const) variable starts with `k`.

/// Don't explicitly default initialize (`{}`)
std::vector<ROOT::Experimental::RAxisConfig> fAxes; //! For pre-comment cases, ROOT I/O directives go here.

/// A static variable starts with `fg`. Avoid all non-constexpr static variables! Long data
/// member documentation is just fine, but then put it in front of the data member.
static std::string fgNameBadPleaseUseStaticFunctionStaticVariable;

protected:
/// FOURTH section: non-public functions.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There seems to be no THIRD.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// THIRD section: data members.?


/// Instead of static data members (whether private or public), use outlined static
/// functions. See the implementation in the source file.
static const std::string &AccessStaticVar();

public:
// FIFTH section: methods, starting with constructors, then alphabetical order, possibly
// grouped by functionality (\ingroup or \{ \}).

/// Use `= default` inside the class whenever possible
RExampleClass() = default;

/// Classes with virtual functions must have a virtual destructor. If all other functions are
/// pure virtual or inlined, the destructor should be outlined to pin the class's vtable to an
/// object file. I.e. say `= default` in the source, not here.
virtual ~RExampleClass();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is not pinned in the source file, it would be nice to add it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be addressed.


/// For trivial functions, use `const` and possibly `constexpr`, but not `noexcept`.
int GetAsInt() const { return fIntMember; }

/// Static functions line up alphabetically with the others.
static int GetSeq() { return kSeq; }

/// It is fine to have one-line function declarations.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess you mean function definitions.

void SetInt(int i) { fIntMember = i; }

/// \brief Replace the many strings
/// \param[in] input A collection of strings to use.
/// \return Always return true but if it could fail would return false in that case.
///
/// All functions must have at least one line of comments, parseable/useable by doxygen.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That sounds very strict to me. Simple functions with good names should not need comments.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree and disagree. One line of comment should be mandatory, because otherwise the docs look like this:
https://root.cern/doc/master/classTInterpreter.html

This one line should be one sentence, ending with a ., so doxygen automatically recognises this as a \brief.

I agree, though, that after having written a single sentence, you may leave out the rest for trivial functions.

bool SetManyStrings(const std::vector<Nested>& input) { fManyStrings = input; return true; }

/// Virtual functions are still fine (though we try to avoid them as they come with a call
/// and optimization penalty). See `RCodingStyle` for overriding virtual functions.
virtual void VirtualFunction() = 0;
};

///\class RCodingStyle
/// This is exhaustive documentation of the class template, explaining the constraints
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The first thing after the \class macro should be the brief documentation. Doxygen takes the first sentence of the docs if no \brief is passed, so try to just write one sentence in one line that ends with a ..

/// on the template parameters, e.g.
/// `IDX` must be positive, `T` must be copyable.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recommend using doxygen's\tparam, e.g.:

Suggested change
/// `IDX` must be positive, `T` must be copyable.
/// \tparam IDX The index type. Must be positive.
/// \tparam T A class that is copyable.

/// Disabled if `T` is of reference type.
template <int IDX, // Template parameters are all-capital letters
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is the only new convention that I don't like, it makes template-heavy code a bit heavy to look at (just go to TypeTraits.hxx and capitalize all template parameters...) and macro-like.

Copy link
Member Author

@Axel-Naumann Axel-Naumann Jul 9, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It helps to identify template parameters as such. I often use a using declaration to "convert" the template param to a type (with type-spelling).

But I see your point. Let's discuss at a team meeting!

class T, // Use `typename` for T being a builtin (int, float,...) else `class`.
// template parameter name `T` is fine for generic class names
// we use `enable_if` through unnamed template parameters and provide a `assert`-style message
// to make the diagnostics more understandable.
class = std::enable_if<"Must only be used with non-reference types" && !std::is_reference<T>::value>>
class RCodingStyle : public RExampleClass {
private:
T fMember; ///< The wrapped value.

public:
// Use static_assert excessively: it makes diagnostics much more readable,
// and helps readers of your code.
static_assert(IDX > 0, "IDX must be >0.");

/// Defaulted default constructor.
RCodingStyle() = default;

// Open a group:
/// \{ \name copy pperations
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// \{ \name copy pperations
/// \{ \name copy operations


// Use `= delete` instead of private, unimplemented.
// To avoid copy and move construction and assignment, `delete` the copy
// constructor and copy-assignment operator, see https://stackoverflow.com/a/15181645/6182509
RCodingStyle(const RCodingStyle &) = delete;
RCodingStyle &operator=(const RCodingStyle &) = delete;
/// \}

/// Virtual destructors of derived classes may be decorated with `virtual` (even though
/// they "inherit" the virtuality of the virtual base class destructor).
virtual ~RCodingStyle() {}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should probably be virtual ~RCodingStyle() = default;.


/// Overridden virtual functions do not specify `virtual` but either `override` or `final`.
void VirtualFunction() override {}

/// "Free" (i.e. non-class member) operators should be declared as "hidden friends".
/// This makes them available only when needed, instead of adding to the long list of
/// "no matching overload for a+b, did you mean ...(hundreds of unrelated types)..."
friend RCodingStyle operator+(const RCodingStyle &a, const RCodingStyle &b)
{
RCodingStyle ret{a};
ret.SetInt(ret.GetAsInt() + b.GetAsInt());
return ret;
}

/// Heterogenous operators should be implemented as hidden-friend functions:
friend RCodingStyle operator+(const RCodingStyle &a, int i)
{
RCodingStyle ret{a};
ret.SetInt(ret.GetAsInt() + i);
return ret;
}

/// ...as the symmetrical / inverse version can only be specified as non-member:
friend RCodingStyle operator+(int i, const RCodingStyle &a) { return a + i; }
};

/// Functions that access only public members of ROOT classes shall be in here.
/// \param a - first RExampleClass to add.
/// \param b - second RExampleClass to add.
Comment on lines +226 to +227
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that doxygen generates better docs without the -:

Suggested change
/// \param a - first RExampleClass to add.
/// \param b - second RExampleClass to add.
/// \param a First RExampleClass to add.
/// \param b Second RExampleClass to add.

double Add(const RExampleClass &a, const RExampleClass &b);

// Rules on sub-namespaces.
// All namespaces are CamelCase, i.e. starting with an upper case character.
namespace CodingStyle {
// Parts of ROOT might want to use sub-namespaces for *internal* code.
// Such code might include the following two namespaces:
namespace Detail {
// Contains code that is not expected to be seen by the everage user.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo everage

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Contains code that is not expected to be seen by the everage user.
// Contains code that is not expected to be seen by the average user.

// It might provide customization points for advanced users, or
// interfaces that are useful for performance-critical code, at the
// expense of robustness.
}

namespace Internal {
// Contains code that is meant to be used only by ROOT itself. We do not guarantee
// interface stability for any type inside any sub-namespace of `ROOT::` called
// `Internal`, i.e. `ROOT::Internal` or `ROOT::...::Internal`.
}
}

} // namespace ROOT
#endif // ROOT_RCodingStyle
Copy link
Member

@amadio amadio Jul 23, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commenting on the commit message... Maybe a Suggested-by: tag in the body of the commit would be better?
There's a JIRA issue on me for the style of commit messages, maybe we should discuss this today too?

// The file must end on a trailing new-line.