As a super reviewer, I've run into many of the same issues over and over again.. I'm starting to compile them into a larger list that I'd like people to start reviewing. The idea is not to teach people about architecture or COM or C++, but rather the conventions that we use throughout our code. I'm soliciting feedback, especially in the form of a tip or two from fellow reviewers- something simple is fine, I'll probably know what you're talking about if you say "No tabs" or "leading interCaps in IDL" - I'll write up some nice explanations and code. I've attached the latest version, which should be appearing here in the next 30 minutes: http://www.mozilla.org/hacking/mozilla-style-guide.html AlecTitle: Mozilla Coding Style Guide
Mozilla Coding Style Guide
This document attempts to explain the basic styles and patterns that are used in the mozilla codebase. New code should try to conform to these standards so that it is as easy to maintain as existing code. Of course every rule has an exception, but it's important to know the rules nonetheless!
This is particularily directed at people new to the mozilla codebase, who are in the process of getting their code reviewed. Before getting a review, please read over this document and make sure your code conforms to the recommendations here.
COM and pointers
IDL
Error Handling
Strings
Naming and Formatting code
COM and pointers
IDL
long updateStatusBar();
This example has too many methods:
interface nsIFoo : nsISupports {
long getLength();
void setLength(in long length);
long getColor();
};
The code below will generate the exact same C++ signature, but is more script-friendly.
interface nsIFoo : nsISupports {
attribute long length;
readonly attribute long color;
};
const long ERROR_UNDEFINED_VARIABLE = 1;
Error handling
- Someone may change the callee in the future to return a failure condition.
- The object in question may live on another thread, another process, or possibly even another machine. The proxy could have failed to actually make your call in the first place.
rv = foo->Call1();
if (NS_SUCCEEDED(rv)) {
rv = foo->Call2();
if (NS_SUCCEEDED(rv)) {
rv = foo->Call3();
}
}
}
return rv;
Instead, do this:
rv = foo->Call1();
if (NS_FAILED(rv)) return rv;
rv = foo->Call2();
if (NS_FAILED(rv)) return rv;
rv = foo->Call3();
if (NS_FAILED(rv)) return rv;
Why? Because error handling should not obfuscate the logic of the code. The author's intent in the first example was to make 3 calls in succession, but wrapping the calls in nested if() statements obscured the most likely behavior of the code.
Consider a more complicated example that actually hides a bug:
PRBool val;
rv = foo->GetBooleanValue(&val);
if (NS_SUCCEEDED(rv) && val)
foo->Call1();
else
foo->Call2();
The intent of the author may have been that foo->Call2() would only happen when val had a false value. In fact, foo->Call2() will also be called when foo->GetBooleanValue(&val) fails. This may or may not have been the author's intent, and it is not clear from this code. Here is an updated version:
PRBool val;
rv = foo->GetBooleanValue(&val);
if (NS_FAILED(rv)) return rv;
if (val)
foo->Call1();
else
foo->Call2();
In this example, the author's intent is clear, and an error condition avoids both calls to foo->Call1() and foo->Call2();
Strings
nsresult foo() {
nsCString bar;
..
}
instead:
nsresult foo() {
nsCAutoString bar;
..
}
static char *GetStringValue() {
..
return resultString.ToNewCString();
}
..
WarnUser(GetStringValue());
In the above example, WarnUser will get the string allocated from resultString.ToNewCString() and throw away the pointer. The resulting value is never freed. Instead, either use the string classes to make sure your string is automatically freed when it goes out of scope, or make sure that your string is freed.
Automatic cleanup:
static void GetStringValue(nsAWritableCString& aResult) {
..
aResult.Assign("resulting string");
}
..
nsCAutoString warning;
GetStringValue(warning);
WarnUser(warning.get());
Free the string manually:
static char *GetStringValue() {
..
return resultString.ToNewCString();
}
..
char *warning = GetStringValue();
WarnUser(warning);
nsCRT::Free(warning);
Incorrect:
nsAutoString warning; warning.AssignWithConversion("danger will robinson!");
..
foo->SetUnicodeValue(warning.get());
Correct:
nsAutoString warning(NS_LITERAL_STRING("danger will robinson!"));
..
// if you'll be using the 'warning' string, you can still use it as before:
foo->SetUnicodeValue(warning.get());
// alternatively, use the wide string directly:
foo->SetUnicodeValue(NS_LITERAL_STRING("danger will robinson!").get());
Futures issues I'll be addressing:
- macros: NS_
- don't check after QI - use try/catch
Naming and Formatting code
- k=constant (e.g. kNC_child)
- g=global (e.g. gPrefService)
- m=member (e.g. mLength)
- a=argument (e.g. aCount)
- Macros begin with NS_, and are all caps (e.g. NS_IMPL_ISUPPORTS)
- Global (exported) functions begin with NS_ and use LeadingCaps (e.g. NS_NewISupportsArray)
