Philip asked via the Suggestion Box about what sort of coding standards and design philosophies do we use at REAL Software when working on REALbasic, so I figured I'd discuss that a little bit. We don't do anything too terribly radical, so much of this won't come as a surprise to you.
We use two different languages to do our work: REALbasic is used when working on the IDE and parts of the framework, and C++ is used when working on the compiler and parts of the framework. So, without further ado, here's the coding standards we have (pulled straight from our "handbook.")
General
- All control statements have a space after the control keyword and before the open paren (if a paren is needed)
// Correct if (true) { } // Incorrect if(true) { } - All expressions should use spaces between parts
// Correct if (i == 12 and cool == true) { } // Incorrect if (i==12&&cool==true) { } - All class and method names are titlecased
// Correct class Win32SerialControl // Incorrect class Win32_Serial_Control class Win32serialcontrol - All class properties are titlecased and prefixed with "m"
- All constants are titlecased and prefixed with "k"
- You should use globals sparingly, but if you use them, they should be in globals.cpp/h, and be prefixed with "g"
- If you create a static variable, it should be prefixed with "s"
// Correct int mTesting; const int kTesting = 12; extern int gSomeGlobal; bool sSomeStatic = true;// Incorrect
int testing;
int Testing;
int m_Testing;
const int Testing = 12;
const int k_Testing = 12;
- All local variables and method parameters are titlecased, except for the first letter
- All parameter lists have a space after the open paren, and before the close paren
// Correct
int Foo( int localVariableOne, string localVariableTwo );
// Incorrect
int Foo(int local_variable, string HaHaHa);
int foo(int local_variable, string HaHaHa);
- All classes and class members are as closely encapsulated as possible by making them protected or private, based on need.
- Make use of the assert macro/function call to test your assumptions about code invariants. If something should always be true, then assert it! If your assertion fails, then you should expect that the application will terminate. To this end, you should not be using assertions to validate user input (or the likes). If you wish to make a recoverable assertion (in the IDE only), then you should use the AssertOrRecover function instead of Assert.
- You should never have any "magic numbers" in your code. You should always use a descriptive constant instead
// Correct
ctl.Left = ctl.Left + kMargin// Incorrect
ctl.Left = ctl.Left + 2
- If you have incomplete code that needs to be addressed in the future, you should put in a ToDo comment that explains what needs to happen, and wrap it in a #if RBVersion block as to when the code should be addressed.
// ToDo: This code needs to be updated because
// gremlins live in it here and here, and since
// it is 2008r1, we want to address this in the
// next release.
#if RBVersion > 2008.01
Bad code
#endif
- You should never introduce new warnings. When you're working on some source code, you should always try to remove warnings that you come across. The goal is to have no warnings, ever.
C++ Specific
- All macros are all uppercased
#define MIN( a, b ) ((a - Always use curly braces for multiline control statements
- Openining curly braces are at the end of the line, not the beginning, closing curly braces line up with scope opener
// Correct if (true) { printf( "true" ); } // Also correct if (true) printf( "true" ); // Incorrect if (true) printf( "true" ); // Incorrect if (true) { printf( "true" ); } - All methods should have a comment header that describes the method, its parameter list, and its return value
// IsDataAvailable // // Checks to see whether there is new data available for // the serial device to read // // Author: AJB // Used In: runtime // Gets: the serial control // Returns: true if there's data waiting to be read // Comment: Feb 17 2006 -- AJB (1) static Boolean IsDataAvailable( SerialControl *ctl ) { ... }
RB Specific
- The only Global items allowed are classes
- Do not make Public simple properties -- use computed properties instead
- If a method raises an exception, explicitly document it at the top of the method
- If an API you're calling can raise an exception, enclose it in a try/catch block with an explicit exception filter
// Correct try foo = someDict.Value( 12 ) catch err as KeyNotFoundException end try // Incorrect try foo = someDict.Value( 12 ) catch end try // Incorrect foo = someDict.Value( 12 ) .. .. exception err as KeyNotFoundException - All method calls should use parens for the parameter list, even when their use is optional
// Correct
MsgBox( "Hello world" )
// Incorrect
MsgBox "Hellow world"
- All permanent comments should be using // instead of ' or REM
// Correct
// See how pretty my code is?
' Incorrect
' See how ugly my comments are?
- All temporary comments should be using ' instead of // or REM
// Correct
' Dim i as Integer = 12
// Incorrect
// Dim i as Integer = 12
- You should never use a hard-coded string in the IDE for any text that is displayed to the user. You should always use a dynamic string constant (generally in the Strings module)
// Correct
MsgBox Strings.kHelloWorld// Incorrect
MsgBox "Hello world"
Now, Philip also asked about design philosophies such as when to improve speed over readability, etc. That's actually a much less formal topic that doesn't have any hard and fast rules. However, it's also pretty easy to explain: write the code to be correct, readable, and maintainable. If speed becomes an issue, then optimize for speed. Very rarely is speed a consideration in the first writing of code, with the exception of the compiler. The compiler requires more of an eye towards speed because it's very, very easy to make a small change with a large speed or memory impact.
// Correct
if (i == 12 and cool == true) {
}
I think your use of 2 languages at once is showing :-)
@Paul -- hah! I can see how you might think that. But we actually have some custom "keywords" defined globally for our C++ code. You can see them in the plugin SDK, I believe.
#define and &&
#define or ||
#define not !
It helps to make the code more readable, and was also a hold over from our CodeWarrior days, since CW had those defined as operators.
@Aaron
I was wondering if maybe that was the case... I have been bouncing between RB, and Objective-C and find myself typing godawful combinations like:
if (x and y) then {
}
The macros "and", "or", "not", and several others are part of ISO C, defined in .
Thanks for writing. This was an interesting entry. Interesting that you always use // for all comments. It would be nice for RB to put these as comment characters when you choose the "Comment" button in the code editor, instead of a single quote, or at least allow this to be parameterized! This has always been a pet peeve. I feel a new feature request coming on...
I find it odd that you have a standard for two types of comments - a permanent one and a temporary one. This must mean that at the end of some coding you must then go through and find all single quote comments and then make them double slash comments - surely a pain and easy to forget. This also runs the risk you might make a mistake and restore some code by accident. If you really need to identify temporarily commented code, you could add something to the end of a double-slash comment line instead, like a question mark. A question mark would also be easier to find and replace than a single quote, as single quotes are often used in the building of SQL statements and in other comments. Or am I missing something?
@Eric -- it's more loose than that. It's mostly just a matter of "if you're in some code and see something commented out with ', then you can just go ahead and remove it without worrying."
Oops - that previous comment was supposed to explain that the named operator macros are defined in a header file named "iso646.h".
Eric, the distinction when I was there was that we used slash comments for English text explaining intent, and apostrophe comments to disable lines of code. This is convenient because RB's editor has a menu command which enables or disables the selection by inserting or removing apostrophes. Apostrophes thus become "temporary" comments because the intent is that we not keep obsolete, disabled code in our code base.
I visually prefer this "incorrect" style when writing code:
// Incorrect
if (true)
{
printf( "true" );
}
@Christian -- every person (and every company) has their own differing set of standards for style. No one style is "right", but the important thing is to be consistent. When we bring someone new into the code base, I want it to appear as though one person wrote the entire thing.
Consistency is key!
I don't know how widespread this practice is, but I tend to format my expressions in such a way that, if possible, unintended assignments (by way of a typo) are prevented, eg:
if( i = 10 ) doSomething(); // oops -- always evaluates to true
if( 10 = i ) doSomething(); // semantic error kills compilation process and shows me what a horrible mistake I made
Basically, if a constant or function call is used in a conditional statement, I set it on the left sife of the equality test. While this this isn't an issue in RB, I've found it extremely helpful in C/C++ and PHP.
@Adam -- we typically tend to do that as well, but it's not part of the coding standard. Not certain why, since we all do it internally anyways.
It used to be part of the RS coding standards; I remember retraining myself to write conditions that way when I first started working there.
@Mars -- it's a part of them again, I just added it to the C++ section. I recall the same thing you recall, just forgot to formalize it until now.
It seems I'm late for the party! Thanks, Aaron, for the insight into how Real staffers programs their own product. It was interesting to see what you and I both have in our respective coding standards pages (I keep one in a personal wiki. Maybe I'll share it in my blog one of these days just for kicks).
I think at the same time that it benefits the community as well for new programmers as well to see that there are simple ways to create readable manageable code right from the start. I've been thinking lately that a chapter or section on coding standards would be a great addition to the beginner's tutorial. For example, I can understand the need for single letter variables in example code for the sake of brevity, but I don't think that those have any business in production code (or at least now I don't). Certainly, something that like could grow in size quickly, but a little something would be better than nothing, I think.
As for the speed vs. readability part, I think that idea was born from the chapter on code tuning in Code Complete, 2e. The author discusses the relative speed changes in the different ways of constructing arrays, loops, if...then, select...case. There is some common sense stuff in there (don't use Ubound for the upper bound of a for...next), but the results differ dramatically from language to language. Tips and tricks come up from time to time in the forums and the NUG, but I don't think there's anything in one place and I don't think there's been any vaguely official look into what optimizations work, which don't, and which can actually make things worse.
Thanks for the post. It was a great read.
It is hard to get the hang of it that style if you dont write a lot of C code but it sure makes sense since you can get the compiler to check for those accidental places where you inadvertently write
if ( 10 = i )
Of course there are folks who do deliberately write
if ( i = someOtherObject.memberValue )
and mean it
I've had the pleasure of working with one fellow who truly thought his hand coded C was any faster that the compiler chunked out (it wasn't and we proved it by going through the assembly) but man was his code hard to read and maintain
Thankfully RB doesn't allow such silliness :)