While we're not particularly insistent about coding standards, there are a few things that we try to do consistently as below.
When modifying existing files, the main things are:
spaces, no tabs
three spaces per indent
As far as naming:
classes and methods/functions are in CamelCase
local variables are firstLoweredCamelCase
member variables are mVar
globals are usually gVar
Our style (and that of wxWidgets) is to not leave a space between a function name like 'GetAllFormats' and the following '(', so please copy that style too. It can make searching for functions easier.
If you're defining a complex macro, add a comment. You may understand it without a comment. We might not.
When you're using a #ifdef to give different bodies to a function, and the function is fairly long, we prefer that the #ifdef include the function header, the '{' and the closing '}' inside it rather than 'sharing' them between the two versions. We think the slight duplication is worth it for the better readability.
Source files are in UTF-8 format (so they may contain accented and other characters where necessary). This ensures they are processed the same by all compilers on all platforms
Do's:
Ensure declaration and definition use the same type (not typedefed names that happen to be the same).
Ensure when using functions like wxPrintf, wxString::Format, wxString::Printf and passing a wxString, the wxString::c_str() method is used to pass the correct value. Otherwise, a compilation error will occur with the GCC compiler (Mac and Linux systems).
Place user interface strings inside _() (not wxT() which is used for non-translated or testing-only strings). This ensures gettext can extract the strings for translation. Any strings that are context-dependent or need explanation should be prefaced with a comment starting "i18n-hint:".
Don't's:
Prefix member functions with the class name in the header - it's a GCC compiler error.
If you're adding new files into Audacity source there is boilerplate header we like to use:
Some files will have wxWidgets license rather than GPL. The second part of the header, starting with the line ending in //**
is a Doxygen class-level comment on the class defined within the source file.
We're also slowly moving towards more Doxygen friendly code, so comments at the start of functions with the Doxygen /// marker are encouraged. We're not planning on having everything Doxygenated. We do aim to have doxygen comments at the class level for all classes.
See Making Audacity Doxygenable for details.
Note that some header files (.h) don't contain class declarations for classes defined in the corresponding C++ files (.cpp). These are described at Unusual Header Files; note in particular the Guidelines for using Experimental.h.
Do not use "naked" new and delete!
A comprehensive rewriting of naked new and delete in Audacity's source (not including the third party lib-src directories) was completed early in 2.2.0 development. Do not reintroduce such code. Learn the use of smart pointers and containers instead.
That is, do not manage allocated memory thus:
but rather thus:
The use of delete is now implied in the destructor of pObj, whose type is std::unique_ptr< Obj, std::default_delete< Obj > >.
The advantage is not merely elimination of one line of code.
It is now unnecessary for a reviewer to examine all the other lines, to be sure that there is no other early return path from the function, on which the programmer neglected to duplicate the delete statement.
The deletion is guaranteed in case the function exits because an exception is thrown, by a statement which might even be outside the body of the function itself.
To achieve completely equivalent safety with naked new and delete would require the verbosity of try, catch, duplicated delete, and re-throw.
The principle generalizes to the management of other resources than memory.
Do not rely on explicit calls that free resources (such as open files). Rather, have that happen in the destructor of some "smart" variable.
You may free a resource explicitly where there is a need to do that, conditionally, earlier than the end of scope, or to check error codes. Still, use a smart variable to guarantee the ultimate freeing along all possible paths, including throws.
If you must free early and unconditionally, consider limiting the scope of the smart variable with braces { } so that the freeing code is still implicit.
Do not make calls that acquire the resource without immediately entrusting the responsibility to free it to such a variable.
One somewhat frequent example of a non-memory resource is the locking and unlocking of a mutex. wxMutex lets you call its Lock() and Unlock() methods explicitly, but it is better instead to initialize a variable of type wxMutexLocker.
This idiom is often called RAII in the literature on C++, which stands for "Resource Acquisition Is Initialization."
Some complex, mutative operation, involving multiple steps that might fail, is done on one or more objects. What are the possible outcomes? Know these terms, which are used in comments.
This is not as strong as the no-fail guarantee, which refers generally and abstractly to "failure." Rather, this refers only to the language-technical matter of throwing of exceptions, which is only one way that failure might be signalled -- another way is the returning of error codes.
Almost always, destructors of C++ classes should give a no-throw guarantee, because if a destructor is executed during stack unwinding of one exception, whose catch block is not yet reached, but then a second exception escapes the destructor -- then this is a catastrophe that aborts the program at once.
The operation promises not to fail! This is the strongest guarantee.
Swapping operations of containers or smart pointers often give this guarantee. Such a guarantee is often used to implement the (weaker) "strong guarantee" (below).
Failure may happen, but if it does, then it is guaranteed that the objects remain unchanged.
Often an object implements a mutating operation with strong guarantee, by building up alternative state in stack variables, making no change in the object's own state. Only after that build-up succeeds, the changes are committed: the new contents are swapped into the object's member variables, and the old contents, now held by stack variables, are destroyed.
This is like committing and rolling-back of transactions in databases, and indeed similar terminology is sometimes used in code and comments.
This relies on (stronger) no-fail guarantees in the commitment steps. (For otherwise, suppose one swap succeeds, but the second fails. What then, to regain a consistent state? Undo the first swap, which might then fail? Uh oh.)
A complicated operation may fail in the middle, leaving some side effects. It is guaranteed that the objects remain in some unspecified but "consistent" state.
What does "consistent" mean? That may vary, and there may be degrees of weak guarantee. But at a minimum it means this: the objects can be destroyed without crashes or other undefined behavior.
A complicated object might be composed of other objects that provide strong guarantees on their operations. A higher-level operation implemented in terms of the strong-guarantee operations may achieve only weak guarantees.
Operations on an Audacity project might fail for reasons such as i/o errors or the user cancelling a progress dialog. There is no attempt to recover from exhaustion of memory. For recoverable failures:
Editing operations that, when successful, can be undone and redone, supply a strong guarantee that the state of the project before the attempted operation is restored.
An exception to that is recording, which guarantees that the project will save some initial portion of the sound, captured before the error was detected.
Saving operations guarantee that a formerly saved project is not overwritten and no new project is created.
Export operations guarantee that previously existing files are not overwritten and no new but unusable files are created.
What follows describes the strategy to achieve these guarantees.
Many errors are signalled by raising exceptions. It is easier not to test and propagate error values through many levels of stack. These levels, though not checking errors, still must be written with some care, including but not limited to the use of RAII as described above. (One might say each function must implement a strong guarantee for its net resource allocation. Likewise for other side-effects that outlast its invocation, but with some exemptions described below.) Such techniques are sufficient to supply the guarantees for saving and exporting.
For editing operations on the internal state of the Audacity project, it is more complicated.
Exceptions are caught in numerous places by the template function GuardedCall, which wraps a lambda expression in a try-catch block. These include places where a no-throw guarantee is required, such as in callback functions that are passed to third-party libraries, or where exceptions are caught in a worker thread, as during recording.
Not all exceptions will be caught by GuardedCall. Audacity relies on a catch block of last resort in the event loops implemented in wxWidgets. The function AudacityApplication::OnExceptionInMainLoop is called back from inside the catch block and handles the exception.
If the caught exception is of a class derived from AudacityException, then all of these handlers use wxEventHandler::CallAfter to enqueue delayed actions, to be done always in the main thread at the next idle time of the event loop. It is here that the previous state of the project is restored, rolling back to the last committed state of the Undo history; or in case of recording, that asynchronous recording action is stopped and partial results are committed to the history. Then the user is notified of some details of the cause of failure in a way determined by the exception object. (If the caught exception is of some other class, recovery is not implemented, and in most cases the program will crash.)
Therefore, the many functions that implement editing operations by changing the undoable state of the project are relieved of the responsibility to provide strong guarantees. Generally, all that is needed is the very weak guarantee that modified Tracks and other objects remain destructible.
This is most pertinent to the classes WaveTrack, WaveClip, and Sequence that frequently write BlockFile objects and are exposed to the risk of i/o errors. It is commented in each of their mutative operations how strong a guarantee is provided.
Sequence provides a strong guarantee for everything, even if that exceeds requirements. It is uncomplicated to do this, as Sequence is essentially an array of samples with a persistent back-up, for which the build and swap pattern is sufficient.
WaveClip, which may recursively contain other WaveClips, each with its own Sequence, and WaveTrack, which may contain multiple WaveClips, do not always implement strong guarantees. It is documented in comments where more than a basic weak guarantee was achievable, though usually only this basic guarantee is really needed. The exceptions to this are the appending operations, where some stronger promise is needed to support partial success in recording. Previous contents will be preserved, and some initial (possibly empty) portion of the data intended for appending will be appended.
We use Doxygen lightly.
We do aim to have Doxygen comments at the class level for all classes. This is our priority for Doxygenation of Audacity. Anything else is a bonus.
We're slowly moving towards more Doxygen friendly code, so comments at the start of functions with the Doxygen /// marker are encouraged. We're not planning on having everything Doxygenated.
Consider ways to make the code clearer that remove the need for code documentation. For example, if you need a code comment to explain what a function does, consider changing its name to make the comment unnecessary, or breaking it up into smaller functions each of which has a single clear purpose.
We do not want automatically added Doxygen mark up in the source code that has no new human generated content.
The whole point of adding a Doxygen comment is to add something that is not obvious from the unmarked up code itself.
This might change at a future date if we are documenting an external API we provide. In that case systematic mark up of everything in the API may be helpful for documenting it.
If you're adding new files into Audacity source there is boilerplate header we like to use. The second part of the header, starting with the line ending in //** is a Doxygen class-level comment on the class defined within the source file:
See CodingStandards for discussion of the license and other information we include.
For Doxygen comments, use JavaDoc style:
Or use a block of at least two C++ comment lines, where each line starts with a triple slash:
Within the comments, use \brief to label a paragraph that is part of the brief documentation. That paragraph with any remaining text are used in fuller documentation:
If you are documenting functions, and decide you do need to document parameters:
Or
We're more interested in what the function does than in exhaustive (and exhausting) listings of what the parameters are and are for. Parameter types and their purpose, with good design, should be obvious in most cases from the naming. When functions are documented, the @params
are optional.
Here is a full list of the Doxygen tags
The ones most relevant to us are below:
We're mostly documenting files and classes, rather than stuffing Audacity full of comments for lower level details where reading the code is better.
\file
to document a file. used particularly where the file is not about one class only.
If you go on to document global variables or typedefs or #defines in that file, then you must include the \file tag in the file, or Doxygen will ignore them.
\class
to document a class.
\todo
for a todo item. Note the capitalization.
\link ... \endlink
Used for internal documentation to create hyperlinks.
Completely optional.
\struct
to document a C-struct.
\enum
to document an enumeration type.
\fn
to document a function.
Doxygen recommend only using this tag if absolutely essential, as it interferes with automatically generated function documentation.
\var
to document a variable or typedef or enum value.
\def
to document a #define.
\typedef
to document a type definition.
\warning
We may use \warning to note hacks, where we record the consequences of the hack. Often a 'hack' is good enough, and only would break down in situations that don't occur in practice.