Coding Standards
From JUCE
The coding style in JUCE is pretty strict and consistent, but it's a style that evolved gradually, and I've never tried to actually write down a set of "rules" before.
But recently chatting with someone who'd made a few code contributions, they commented that no matter how much they tried to make their code look like mine, I always seemed to completely re-write it before accepting it! So as an experiment, I've tried to come up with a set of rules that, if followed, should go some way towards producing code that resembles the JUCE codebase.
Some of this stuff is basic C++ good practice. Some of it's JUCE-specific. Most of it's just my personal preference! I'm certainly not claiming this to be a definitive set of rules by which you should live, and I'm not particularly interested in arguing or defending any of the points here - it's just a description of what I ended up doing after many years of coding. YMMV!
Contents |
Basic layout and whitespace
- No tab characters!
- Tabs are 4 spaces!
- Braces are indented in the Allman style.
- Always put a space before and after all binary operators! And this rule especially applies to the '=' operator!
- The ! operator should always be followed by a space.
- The ~ operator should be preceded by a space, but not followed by one.
- The ++ and -- operators should have no spaces between the operator and its operand.
- Never put a space before a comma. Always put a space after a comma.
- Always put a space before an open parenthesis! (One exception to this is if you've got a pair of empty parentheses)
- Don't put spaces after a parenthesis. So a typical method call might look like this: foobar (1, 2, 3);
- In general, leave a blank line before an 'if' statement.
- In general, leave a blank line after a closing brace '}'.
- Do not write 'if' statements all-on-one-line. The exception to this is when you've got a sequence of similar if statements, and are aligning them all vertically to highlight their similarities.
- In an if-else statement, if you surround one half of the statement with braces, you also need to put braces around the other half, to match.
- When writing a pointer type, my preferred spacing is like this: "SomeObject* myObject". Yes, yes, I know that technically, the more correct spacing would be "SomeObject *myObject", but to me it makes more sense for the asterisk to be grouped with the type name, since being a pointer is part of the type, not the variable name. The only time that this can lead to any problems is when you're declaring multiple pointers of the same type in the same statement - which leads on to the next rule...
- When declaring multiple pointers, never do so in a single statement, e.g. "SomeObject* p1, *p2;" - instead, always split them out onto separate lines and write the type name again, to make it quite clear what's going on, and avoid the danger of missing out any vital asterisks.
- The previous point also applies to references, so always put the '&' next to the type rather than the variable, e.g. "void foo (const Thing& thing)". And don't put a space on both sides of the '*' or '&' - always put a space after it, but never before it.
- This one's really just a matter of taste, but for consistency, whenever I have a const object pointer or reference, I write the 'const' first - e.g. "const Thing&" rather than "Thing const&". Both are equivalent, but I prefer the former style because it's closer to the way you'd describe it in English, i.e. verb-before-noun order.
- Something I've never been able to decide is whether to leave a space before a template type, e.g. "Array <int>" or "Array<int>"...? The codebase contains a mixture of both styles - maybe one day I'll make up my mind and go through and make them all consistent!
Naming conventions
- Member variables and method names are written with camel-case, and never begin with a capital letter.
- Class names are also written in camel-case, but always begin with a capital letter.
- For global variables... well, you shouldn't have any, so it doesn't matter.
- Do not use prefixes for your variable names (e.g. 'm' for members, 'l' for locals, etc). Choose a name that clearly describes the purpose of the variable, not its physical properties.
- Avoid underscores in your names. (Although I do have a strange habit that I've somehow picked up over the years, which is to use an underscore suffix when a constructor parameter has the same name as the class member that it's going to be used to initialise. When this happens, I often disambiguate the two symbols by adding an underscore to the parameter name. I've never been 100% happy with this, but it seems to look ok.. I think it'd probably be better to disambiguate by just renaming the parameter, but it's often difficult to think of an alternative name which works as well as the original).
- If you really have to write a macro for some reason, then make it all caps, with underscores to separate the words. And obviously make sure that its name is unlikely to clash with symbols used in other libraries or 3rd party code.
Types, const-correctness, etc
- If a method can (and should!) be const, make it const!
- If a method definitely doesn't throw an exception (be careful!), mark it as 'noexcept'
- When returning a temporary object, e.g. a String, the returned object should always be const.
- If a local variable can be const, then make it const!
- Remember that pointers can be const as well as primitives - for example, if you have a char pointer whose contents are going to be altered, you may still be able to make the pointer itself const, e.g. "char* const foobar = getFoobar();".
- Do not declare all your local variables at the top of a function or method (i.e. in the old-fashioned C-style). Declare them at the last possible moment, and give them as small a scope as possible.
- Object parameters should be passed as const references wherever possible. Only pass a parameter as a copy-by-value object if you really need to mutate a local copy inside the method, and if making a local copy inside the method would be difficult.
- Use portable for() loop variable scoping (i.e. do not have multiple for loops in the same scope that each re-declare the same variable name, as this fails on older compilers)
- When you're testing a pointer to see if it's null, never write "if (myPointer)". Always avoid that implicit cast-to-bool by writing it more fully: "if (myPointer != nullptr)". And likewise, never ever write "if (! myPointer)", instead always write "if (myPointer == null)". I find it much more readable like that.
- Avoid C-style casts except when converting between primitive numeric types. Some people would say "avoid C-style casts altogether", but I find that static_casts are a bit unreadable when you just want to cast an int to a float. But whenever a pointer is involved, or a non-primitive object, always use static_cast. And when you're reinterpreting data, always use reinterpret_cast.
- Until C++ gets a universal 64-bit primitive type (coming in c++2011), please stick to the int64 and uint64 types for this.
Object lifetime and ownership
- Absolutely do not use 'delete', 'deleteAndZero', etc. There are very very few situations where you can't use a ScopedPointer or some other automatic lifetime management class.
- Do not use 'new' unless there's no alternative. Whenever you type 'new', always treat it as a failure to find a better solution. If a local variable can be allocated on the stack rather than the heap, then always do so.
- Do not ever use 'new' or malloc to allocate a c++ array. Always use a HeapBlock instead.
- ..and just to make it doubly clear: Never use malloc or calloc.
- If a parent object needs to create and own some kind of child object, always use composition as your first choice. If that's not possible (e.g. if the child needs a pointer to the parent for its constructor), then use a ScopedPointer.
- If possible, pass an object as a reference rather than a pointer. If possible, make it a const reference.
- Obviously avoid static and global values. Sometimes there's no alternative, but if there is an alternative, then use it, no matter how much effort it involves.
- If allocating a local POD structure (e.g. an operating-system structure in native code), and you need to initialise it with zeros, use the "= { 0 };" syntax as your first choice for doing this. If for some reason that's not appropriate, use the zerostruct() function, or in case that isn't suitable, use zeromem(). Don't use memset().
- Treat Component::deleteAllChildren as a last resort - never use it if there's a cost-free alternative.
Classes
- Declare a class's public section first, and put its constructors and destructor first. Any protected items come next, and then private ones.
- My preferred positioning for any inherited classes is to put them to the right of the class name, vertically aligned, e.g.
class Thing : public Foo,
public Bar
{
- Put a class's member variables (which should almost always be private, of course) , after all the public and protected method declarations.
- Any private methods can go towards the end of the class, after the member variables.
- If your class does not have copy-by-value semantics, always use the JUCE_DECLARE_NON_COPYABLE macro. This should be the last item in your class's declaration.
- If your class is likely to be leaked, then you can use the JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR macro. If your object is copy-by-value but also likely to be leaked, then you can use JUCE_LEAK_DETECTOR instead - but be aware that this combination of behaviours is a bit suspicious, since copy-by-value objects should probably never be allocated using 'new', so shouldn't ever be leaked..
- Constructors that take a single parameter should be default be marked 'explicit'. Obviously there are cases where you do want implicit conversion, but always think about it carefully before writing a non-explicit constructor.
- Do not use 'NULL' , 'null', or 0 for a null-pointer. And especially never use '0L', which I particularly hate! Use 'nullptr' instead - this is the C++2011 standard, so get used to it! I've implemented a fallback definition for nullptr in JUCE, so it's always possible to use it even if your compiler isn't yet C++2011 compliant.
- All the C++ 'guru' books and articles are full of excellent and detailed advice on when it's best to use inheritance vs composition. If you're not already familiar with the received wisdom in these matters, then do some reading!
Miscellaneous
- Don't use the old T() macro - it's deprecated. Just write your strings as plain old C string literals. For extended characters, the only fully cross-compiler way to write them is as a plain old C string which contains UTF-8 codes written as escape character sequences (so that the source file is still pure ascii). If you do that, you should wrap the literal in the CharPointer_UTF8 class, so that when cast to a String, everything's nice and clear about the format that's being used.
- In general, I don't like unnecessary use of the = operator when creating an object. So rather than 'const String s = "foo";', I prefer 'const String s ("foo");'. I do understand that this is personal taste, but I guess I just don't really trust the compiler to always optimise-away that = operator...
- ...but having said that, avoid rubbish like "const String s = String (123);" !
- Don't use macros! OK, obviously there are many situations where they're the right tool for the job, but treat them as a last resort. Certainly don't ever use a macro just to hold a constant value or to perform any kind of function that could have been done as a real inline function. And it goes without saying that you should give them names which aren't going to clash with other code. And #undef them after you've used them, if possible.
- when using the ++ or -- operators, never use post-increment if pre-increment could be used instead. Although it doesn't matter for primitive types, it's good practice to pre-increment since this can be much more efficient for some objects. In particular, if you're writing a for loop, always use pre-increment, e.g. "for (int = 0; i < 10; ++i)"
I'm sure this list is nowhere near complete, (and probably never could be), but I'll keep adding to it when things occur to me...