forked from Alepha/Alepha
Adding a format style guide.
This commit is contained in:
388
Format
Normal file
388
Format
Normal file
@ -0,0 +1,388 @@
|
||||
Basic Code Formatting Rules:
|
||||
|
||||
0) Like most things in Alepha, these rules are "strong guidelines". When in doubt, these rules
|
||||
form a baseline of good current practice. However, there's no substitute for good judgement.
|
||||
Therefore, a large degree of formatting latitude is afforded to code. The primary value
|
||||
being sought is clarity, not consistency. Therefore, new stylistic forms are constantly being
|
||||
adopted for specific cases -- it is cumbersome (at best) to keep track of all of them here.
|
||||
|
||||
Reason: Format should convey meaning. It's fine to invent new conventions. Documenting them is
|
||||
good too, but it shouldn't be cumbersome to do so -- thus this file itself will always be an
|
||||
incomplete representation of the format.
|
||||
|
||||
Exceptions: Certain core guidelines are universally required, like tabs.
|
||||
|
||||
1) Alepha code uses tab character ('\t', ASCII:0x09) for indentation. One tab per indent level. Tabs
|
||||
are not to be used for alignment, except with other tabs. Tabs have no expected set width.
|
||||
|
||||
Reason: Tabs permit customization of code indent level to a reader's taste via the tabwidth settings
|
||||
of editors.
|
||||
|
||||
Exceptions: None. The tab character as _indent only_ permits cooperation amongst those with different
|
||||
visual preferences.
|
||||
|
||||
2) Semicolons on all lines shall appear immediately after the last syntactic character, without a separating space,
|
||||
immediately followed by a newline (or trailing comment). It is permissible put a semicolon on its own line to
|
||||
facilitate a block structure where appropriate for the language.
|
||||
|
||||
Examples:
|
||||
|
||||
++i;
|
||||
f( a, b );
|
||||
|
||||
const bool value= true
|
||||
or a
|
||||
or b
|
||||
;
|
||||
|
||||
3) Declare only one variable per "line".
|
||||
|
||||
Examples:
|
||||
|
||||
int x;
|
||||
float *y;
|
||||
int &xr= x;
|
||||
|
||||
Reason: C++ declaration grammar inherits ambiguities and oddities from C. By declaring one variable per line, we sidestep this issue.
|
||||
|
||||
Exceptions: None.
|
||||
|
||||
|
||||
4) All bracing constructs, "(), {}, [], and <>", must have a space between the opening brace and the first
|
||||
argument, and between the last argument and the closing brace. If there are no arguments then there should
|
||||
be no spaces between the opening and closing brace. No space shall exist between a keyword or an entity name
|
||||
and the following opening bracing construct. A newline character counts as a space for these constructs,
|
||||
thus permitting block-like structure.
|
||||
|
||||
Examples:
|
||||
|
||||
if( someFunction( argument ) ) return 1; // Good. Notice that there is no trailing space after the "if"
|
||||
std::vector< std::map< std::string, std::set< std::string > > > complexDataEntity; // Even for templates
|
||||
complexDataEntity[ 10 ][ "Hello "].find( "Something" ); // Even for array-like operations.
|
||||
std::cout << [](){ return "Hello World!"; }() << std::endl; // For one-lining lambdas and initializers, the rule applies.
|
||||
for( int i= 0; i < 10; ++i ) std::cout << i << std::endl; // Even within a loop construct.
|
||||
|
||||
Reason: Extra space can be visually helpful. Many styles encourage visual cues to code flow.
|
||||
|
||||
|
||||
5) All 2-argument operators that do not modify the left hand side (binary-function operators) must have a space
|
||||
on both sides of the operator, whereas data-modifying equals-based operators like ' =, +=, -=, *=, /=, ^=, |=,
|
||||
&=, ~=, <<=, >>=, %= ' shall have no space to their left, thus binding them to the expression that they modify.
|
||||
Note that ' !=, <=, >= ' are not assignment-like operators, but a comparison operators. Comma "operators"
|
||||
shall also follow left-hand no spacing rules, as this matches their natural use in other circumstances.
|
||||
|
||||
Examples:
|
||||
|
||||
int a= 10;
|
||||
int b= 11, c= 12;
|
||||
c= ( a * b + c ) / ( a * 22 );
|
||||
a%= c / b;
|
||||
if( ( a != b ) or ( c < a ) && ( a= b ) ) return true;
|
||||
|
||||
Reason: Extra visual space helps. Cuddling the assignment operators helps distinguish them from comparison
|
||||
operators. "<=" vs "<<=" and "==" vs "=". By having extra code-intent presented in a side-channel (spacing),
|
||||
it helps prevent accidental typos and bugs.
|
||||
|
||||
Exceptions: `<=` when used as a "left fat arrow" operator may be used with the spacing rules that most
|
||||
make sense for their context. `<=[]` on lambdas, for example. `/` when used as a directory separator
|
||||
between path components from `std::filesystem` should also omit surrounding spaces.
|
||||
|
||||
|
||||
6) All unary operators shall "cuddle" their applied variable. Pointers and references shall be be declared
|
||||
with the "&" or "*" attached to the variable name.
|
||||
|
||||
Examples:
|
||||
|
||||
int a= 10;
|
||||
++a;
|
||||
a--;
|
||||
a= ~a;
|
||||
if( !a ) return false;
|
||||
|
||||
Reason: Somewhat improves readability. For declarations, although "int &" is the type of "int &x;", this
|
||||
does not extend to: "int &x, y". Those are distinct types, but by banning multiple declarations per line,
|
||||
we mostly avoid this issue.
|
||||
|
||||
|
||||
7) Brace placement shall be "Allman/BSD"-like. Opening braces shall be on a new line, closing braces on a
|
||||
new line. Braces shall be indented by the same level as the line of their "control" statement. A braced-block
|
||||
that is not subordinate to a control statement shall be indented as if it had an "if( true )" control statement
|
||||
above it. A do-while loop shall have the while on its own line, after the closing brace, where possible. Note
|
||||
that one-line compaction is permitted, as is omitting braces for fully subsumed blocks.
|
||||
|
||||
Examples:
|
||||
|
||||
int
|
||||
main()
|
||||
{
|
||||
int x= 10;
|
||||
int y= 11;
|
||||
if( x < y )
|
||||
{
|
||||
std::vector< int > v;
|
||||
if( y % 2 )
|
||||
{
|
||||
rv.push_back( v );
|
||||
}
|
||||
|
||||
// Note that this if block has no parent, but it is indented like it has one.
|
||||
{
|
||||
std::list< int > l( v.begin(), v.end() );
|
||||
if( x == y ) for( const auto &x: stuff )
|
||||
{
|
||||
std::cout << x << std::endl;
|
||||
}
|
||||
std::cout << l.size() << std::endl;;
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
std::cout << v.size() << std::endl;
|
||||
}
|
||||
while( false );
|
||||
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Exceptions: Very small functions or blocks can be subsumed into one line, for brevity.
|
||||
|
||||
8) Return types shall be placed above the name of the function in definitions, but on the same line for
|
||||
declarations. inline or explicit for ctors, dtors, and conversion functions shall follow this rule as well.
|
||||
|
||||
Example:
|
||||
|
||||
bool is_even( int x );
|
||||
|
||||
bool
|
||||
is_even( int x )
|
||||
{
|
||||
return ~x & 1;
|
||||
}
|
||||
|
||||
|
||||
9) Template clauses for definitions shall be on their own line. For declarations, a separate line is optional.
|
||||
|
||||
Examples:
|
||||
|
||||
template< typename T > bool is_even( T x );
|
||||
|
||||
template< typename T >
|
||||
bool
|
||||
is_even( T x )
|
||||
{
|
||||
return ( x % 2 ) == 0;
|
||||
}
|
||||
|
||||
|
||||
10) In class definitions, public, protected, and private regions shall be indented beyond the access control
|
||||
keywords, which must also be indented. In switch/case blocks, cases shall be similarly single-indented,
|
||||
as too breaks shall remain single indented. All switch statements shall have a break, return, or throw
|
||||
at the end of each case clause or shall use [[fallthrough]] to document fallthrough behavior, where
|
||||
it is used. Default clauses are not mandatory, but are recommended. (Default clauses might be blank,
|
||||
or all cases might be covered, especially when the argument is an enum.)
|
||||
|
||||
Examples:
|
||||
|
||||
class Foobar
|
||||
{
|
||||
private:
|
||||
int x;
|
||||
|
||||
public:
|
||||
void setX( int newx ) { x= newx; }
|
||||
int getX() const { return x }
|
||||
|
||||
void
|
||||
operation( int val )
|
||||
{
|
||||
switch( val )
|
||||
{
|
||||
case 2:
|
||||
x= 11;
|
||||
break;
|
||||
|
||||
case 11:
|
||||
x= 2;
|
||||
break;
|
||||
|
||||
case 12:
|
||||
specialCase();
|
||||
[[fallthrough]];
|
||||
|
||||
default:
|
||||
x= val;
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct Open
|
||||
{
|
||||
int x;
|
||||
};
|
||||
|
||||
|
||||
11) Continued lines shall be indented twice past the main parent's indent. When breaking a line on an operator,
|
||||
the next line should begin with an operator. Spaces may also be used to line up nicely for vertical alignment
|
||||
of operators; however, note that spacing alignment starts from the tab-depth of the previous line. (Never use
|
||||
spaces to align to a specific tab depth.) Control structures which have a single subordinate statement
|
||||
on a separate line shall enclose that line in braces. If wrapping becomes necessary with "one line" control
|
||||
structures, then bracing is required.
|
||||
|
||||
Example:
|
||||
|
||||
int
|
||||
main()
|
||||
{
|
||||
std::cout << "A really long message which "
|
||||
<< "needed to be indented "
|
||||
<< "across multiple lines." << std::endl;
|
||||
|
||||
std::cout << "A really long message which "
|
||||
<< "needed to be indented "
|
||||
<< "across multiple lines. "
|
||||
<< "This message was aligned "
|
||||
<< "on ostream operators."
|
||||
<< std::endl;
|
||||
|
||||
if( condition ) action();
|
||||
|
||||
if( moreComplicatedCondition )
|
||||
{
|
||||
moreComplicatedAction();
|
||||
}
|
||||
}
|
||||
|
||||
Reasons: It is important to know when lines have wrapped. A specific visually distinct
|
||||
style helps make this stand out from other indented scenarios. By avoiding braceless
|
||||
subordinates to control structures we avoid a class of silly maintenance bugs.
|
||||
|
||||
|
||||
12) Initializer lists shall be treated as-if they're part of the ctor body. They get
|
||||
single indented, unless the function is really short.
|
||||
|
||||
Example:
|
||||
|
||||
struct StringWrapper
|
||||
{
|
||||
std::string string;
|
||||
|
||||
explicit
|
||||
StringWrapper( const std::string &s )
|
||||
: string( s ) {}
|
||||
|
||||
explicit StringWrapper() : string( "no value" ) {} // A short ctor or other function can be on one-line.
|
||||
};
|
||||
|
||||
Exceptions: Single line functions and ctors, and the like are sometimes good to conserve space. Trivial
|
||||
implementations should take trivial amounts of space.
|
||||
|
||||
|
||||
13) Private data shall be declared before the functionality of the class is declared or defined. Constructors,
|
||||
copy assignment, and destructors shall be declared (or defined) before other member functions.
|
||||
|
||||
Example:
|
||||
|
||||
template< typename T >
|
||||
class smart_ptr : boost::noncopyable
|
||||
{
|
||||
private:
|
||||
T *ptr;
|
||||
|
||||
public:
|
||||
explicit inline
|
||||
smart_ptr( T *const p )
|
||||
: ptr( p ) {}
|
||||
|
||||
inline ~smart_ptr() { delete p; }
|
||||
|
||||
T &
|
||||
operator *() const
|
||||
{
|
||||
return *ptr;
|
||||
}
|
||||
|
||||
T *
|
||||
operator->() const
|
||||
{
|
||||
return ptr;
|
||||
}
|
||||
};
|
||||
|
||||
Reasons: It's easier to reason about the correctness of a class's primary invariant mechanism (construction
|
||||
and destruction) when its near the variables and types it manages. Knowing the contents of a class helps to
|
||||
understand about the invariants which must be maintained. Some argue this makes finding the public interfaces
|
||||
harder -- but Hera classes are designed to be perused by doxygen, and thus this documentation shall serve as
|
||||
a primary public usage reference. Within Hera, the only reason to read a header file is to understand more
|
||||
about a particular implementation.
|
||||
|
||||
Exceptions: None.
|
||||
|
||||
|
||||
14) Class parents (inheritance) shall logically be on the same line as the declaration. If on another line,
|
||||
must count as a continuation.
|
||||
|
||||
Reasons: It helps to isolate a class's name from the parent list, which is (when you think about it) part of
|
||||
the class, and needs indentation.
|
||||
|
||||
|
||||
15) Preprocessor nested scopes (which sometimes do happen) shall indent, by requisite spacing, all characters
|
||||
AFTER the leading "#".
|
||||
|
||||
Examples:
|
||||
|
||||
#ifndef SOME_THING
|
||||
# ifndef SOME_OTHER_THING
|
||||
# define NEITHER_THING_IS_AVAILABLE
|
||||
# else
|
||||
# error Must have SOME_THING if SOME_OTHER_THING is around
|
||||
# endif
|
||||
#endif
|
||||
|
||||
Reasons: Because C preprocessor macros also follow logical scope, it helps to trace their nesting by indentation.
|
||||
By indenting after the leading "#", we help distinguish C++ code from C preprocessor code.
|
||||
|
||||
Exceptions: The header guards, being ubiquitous, shall not count as a level of indentation in nested preprocessor
|
||||
construct
|
||||
|
||||
|
||||
16) In declaration of entities, the following order of keywords shall always be followed:
|
||||
|
||||
virtual explicit extern static constexpr consteval constinit inline mutable const volatile <TYPE>
|
||||
|
||||
For integers & double:
|
||||
unsigned long long int
|
||||
signed short int
|
||||
long double
|
||||
|
||||
signed and int are each optional.
|
||||
|
||||
For characters:
|
||||
unsigned char
|
||||
signed char
|
||||
char
|
||||
|
||||
Each of those char types are three distinct types -- signed or unsigned are NOT optional.
|
||||
|
||||
|
||||
Examples:
|
||||
struct Type
|
||||
{
|
||||
virtual inline ~Type() noexcept {} // Yes, this is actually allowed.
|
||||
explicit inline Type() {}
|
||||
static inline const std::string string() { return "String"; }
|
||||
|
||||
// Allowed, but violates the style guide:
|
||||
long const unsigned volatile int virtual long inline *resource() const;
|
||||
|
||||
// The same declaration, following the style guide:
|
||||
virtual inline const volatile unsigned long long int *resource() const;
|
||||
};
|
||||
|
||||
Reasons: C++ is very forgiving in the order of its type-and-qualifiers, but standardizing them makes finding
|
||||
specific things simpler. There are many mutually exclusive keywords above, like static and virtual and explicit,
|
||||
for example. Many may not know it but 'long const unsigned volatile int virtual long inline *' is a valid token
|
||||
sequence for declaring a member function. Standardization gives us: 'virtual inline const volatile unsigned
|
||||
long long int *', which makes the declaration type far less confusing.
|
Reference in New Issue
Block a user