Skip to content
alexandre burton edited this page Oct 5, 2023 · 51 revisions

The openFrameworks code style

Two concepts overlap in what defines the "style": (1) whitespace / indentation / bracing and (2) things like variable naming.

1. Whitespace / indentation / bracing

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, and private 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.

2. Class and variable naming, etc.

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.
//

Other OF-specific considerations

Clear vs Clever

We prefer clear code to clever code - ideally it can be both.

Commenting

  • 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.

Logging

  • 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).

Ternary operators

Don't use ternary operators (i.e. result = a > b ? x : y;). This is only clear to super nerds. Are you a super nerd?

While loops

Don't use while loops if possible.

Iterators and loops

Use simple incrementing for loops for simple cases, range-based for containers, and iterators as last resort.

C arrays vs vectors

Use vectors unless there is a strong reason not to.

Const correct / adding const

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.

Initialization member variables

  • Always initialize variables with sensible starting values in the class constructor.
  • Pointers should be initialized to NULL.

Assert

Please don't use assert. We will find you and and make you pay! :)

Exceptions

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.

Setup and Constructors

  • An object should have a setup() (or sometimes set()) 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 via setup()
  • Objects should be as functional as possible by default; if an object requires a setup() call to be operational, it should be well documented.

//TODO:

Please use // TODO: comments if you find something that needs fixing later. eg: // TODO: fix this as its not returning a value.

Class method order

  • 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 precede drawStuff(ofPoint& point) function.

Naming conventions

  • We use the 'of' prefix for global functions, and class names. eg ofImage, ofSetColor, etc.

  • We use non of prefix for class methods eg: ofImage::draw() instead of ofImage::ofDraw.

  • Methods that modify their object are named like ofColor::normalize(), while methods that return a copy are named like ofColor::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.

Member variable accessibility

  • 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.

Enums

  • 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 not int.
// 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);
 …
}