-
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
oF Code Style
Two concepts overlap in what defines the "style": (1) whitespace / indentation / bracing and (2) things like variable naming.
To simplify whitespace and bracing management, in 2023 a .clang-format file has been adopted. The starting point was the 'webkit' template, from which a few tweaks managed to conform to almost all specificities of the existing code. As a result of that, this styling guide can be simplified.
A decision was made not to "blanket-reformat" the codebase as even though git is not bad in ignoring whitespace when presenting diffs, it does cover the history with a "reformat" commit. In order to prevent that, the formatting is progressively applied as other changes are occurring in files, so the (squashed) commit message is relevant to the changes in design or implementation and not formatting. Included libs or headers are of course not reformatted.
No automatic "format-on-commit" has been installed, and the submitted code is not checked for conformity. The objective of the whitespace specification is to facilitate the homogenization of code, not install a point of control. You are encouraged to use clang-format during development, not just as the submitting level. There are many ways to execute clang-format, from the command line, within IDE's, as OS-level automations, or even within the git commit process.
The .clang-format file itself can be referred to as the reference, but here are the highlights:
-
Use tabs for indentation set to a width to 4 spaces. Keep in mind the Github source code viewer uses a tab width of 8 spaces by default; you can change this in your Github Settings.
-
Indent blocks inside preprocessor definitions
#ifdef WIN32
#define IS_WIN32
#endif
- The
public
,protected
, andprivate
keywords should not be indented inside the class
class myClass{
public:
myClass();
void aFunction();
};
-
Do not attempt to align things in columns with whitespace (only if is exceptionally helpful — such as perhaps a multi-line included lookup table — in which case
// clang-format off
can be used). -
Pointers or references as well as binary operators use a space on each side:
// correct
char * x;
const QString & myString;
const char * const y = "hello";
int a = b + c;
// wrong
char* x;
const QString &myString;
const char* const y = "hello";
int a = b+c;
- The left curly brace goes on the same line as the start of the statement.
// correct
if (codec) {
// do something
}
//wrong
if (codec)
{
// do something
}
But again, all the above should be mechanically processed by clang-format and should not require specific attention.
These are things that are not covered by the clang-format operation:
- Declare each variable on a separate line
- Avoid short (e.g.
a
,rbarr
,nughdeget
) names whenever possible - Single character variable names are only okay for counters and temporaries, where the purpose of the variable is obvious
// correct
int height;
int width;
char * nameOfThis;
char * nameOfThat;
// wrong
int a, b;
char * c, * d;
- Variables and functions start with a lower-case letter. Each consecutive word in a variable’s name starts with an upper-case letter (camel case).
int thisIsAnImportantInteger; // correct
int thisisanimportantinteger; // wrong
- Avoid abbreviations
// correct
short counter;
char itemDelimiter = '\t';
// wrong
short Cntr;
char ITEM_DELIM = '\t';
- OF Core class names start with "of". eg ofTexture. Addon authors are encouraged to use the "ofx" prefix.
class ofCoolClass; // correct
class OFCoolClass; // wrong
- Use parentheses to group expressions:
// correct
if ((a && b) || c) {
// do something
}
// wrong
if (a && b || c) {
// do something
}
-
Keep lines shorter than 100 characters; insert breaks if necessary.
-
Do not use multi-line comments. Yes, they save time, but make it a pain to comment out large sections of code when debugging.
// Wrong
/*
Yo.
This is
a multiline comment.
Peace.
*/
// Correct
//
// Yo.
//
// This is not
// a multiline comment.
//
// Peace.
//
We prefer clear code to clever code - ideally it can be both.
- Clearer code is better than explaining comments.
- Avoid redundancy; comment on the "why", not the "what".
- If code is changed, keep the comments up-to-date.
- Guidelines on Doxygen/automated comments are still pending.
- Never use printf or cout; use
ofLogSomething()
. - Warnings, Errors, Notice, and Verbose log level messages must have the name of the calling module (basically class name) in their header. This way users have more info as to where the issue may be happening. By default, ofApp reports at the Notice level.
// ie inside ofImage.cpp
// Wrong
ofLogWarning("HERE3") << "couldn't load image";
// Correct
ofLogWarning("ofImage::load()") << "couldn't load image: " << attempted_name;
(NB: if you find yourself peppering code with Logs to isolate a problem, it is a sign you are ready to learn about the Debugger breakpoints (Xcode, VSCode, Visual Studio, QtCreator).
Don't use ternary operators (i.e. result = a > b ? x : y;
). This is only clear to super nerds. Are you a super nerd?
Don't use while loops if possible.
Use simple incrementing for loops for simple cases, range-based for containers, and iterators as last resort.
Use vectors unless there is a strong reason not to.
When developing new code, you are encouraged to use const arguments and const methods wherever possible with the exception of return types. If you need a const return type, you must also provide a non-const version. From the user's perspective, we encourage const guarantees, but not const requirements. In general, do not create a situation that forces the user to be aware of const. We currently prefer that const is only added to existing code as needed, in accordance with the above guidelines.
We don't do this in the core. If you need it for initializing a reference then use it but otherwise do your initialization in the constructor.
- Always initialize variables with sensible starting values in the class constructor.
- Pointers should be initialized to NULL.
Please don't use assert. We will find you and and make you pay! :)
Please don't use exceptions. Log a warning or an error (ofLogError
/ ofLogWarning
) and try to anticipate and handle as much disappointments as possible within your functionality. If you are using a library that uses exceptions, make sure to catch and print the errors so exceptions are not thrown in user code.
- An object should have a
setup()
(or sometimesset()
) method that can be reapplied over it's lifetime - Complex setups use a
Settings
object (ofSoundStreamSettings
,ofGLWindowSettings
, etc) to keep arguments sane. - Constructors are encouraged but must mimic the
setup()
arguments order, and 100% of functionality should be accessible viasetup()
- Objects should be as functional as possible by default; if an object requires a
setup()
call to be operational, it should be well documented.
Please use // TODO:
comments if you find something that needs fixing later.
eg: // TODO: fix this as its not returning a value
.
-
We try and order the methods and member variables in the header file in the order that they should be used, and from least-technical to most-technical. For example:
- constructor / destructor
- initialization and setup related functions
- core functionality
- operator overloading
- member variables
-
This ordering also applies to non-class headers. For example, an overloaded function taking
drawStuff(int x, int y)
should precededrawStuff(ofPoint& point)
function.
-
We use the '
of
' prefix for global functions, and class names. egofImage
,ofSetColor
, etc. -
We use non of prefix for class methods eg:
ofImage::draw()
instead ofofImage::ofDraw
. -
Methods that modify their object are named like
ofColor::normalize()
, while methods that return a copy are named likeofColor::getNormalized
. -
Most importantly - please look at how other similar things in oF are named, e.g. if
ofGetSomething
,ofEnableSomething
,ofDisableSomething
are used. Use your judgment to name things in a way that will be intuitive to others (not just yourself) based on the current oF API. -
Avoid
using namespace std
globally.
- There should be no public member variables except in very small data passing classes/structs. Write getters/setters for access.
- Make variables protected for classes that are designed to be inherited. Use your best judgement here and try to expose only those variables which can't mess up the internal workings of the class easily.
- Make all other variables private and add getters/setters where applicable.
- If a function or method returns or takes as input an entry from a selection of options, use an enum instead of int. Make sure the function or method parameter is set to be of the
enumtype
and notint
.
// We don't do this:
#define OF_IMAGE_QUALITY_BEST 0
#define OF_IMAGE_QUALITY_LOW 1
class ofImage {
…
void setQuality(int quality);
…
};
//--------------------------------------------------
// We do this:
enum ofImageQualityType {
OF_IMAGE_QUALITY_BEST,
OF_IMAGE_QUALITY_LOW
};
class ofImage {
…
void setQuality(ofImageQualityType qualityType); // use this
…
};
void testApp::setup() {
…
myImage.setQuality(OF_IMAGE_QUALITY_BEST);
…
}