C
Carl Colijn
Hi all,
Disclaimer: before I might trigger your "let's start a holy war!" button,
I'd like to say I'm not intended to; I just post this message to get some
input and not to promote "Yet Another Naming Convention". It's a bit of a
long post, but I've spent the past few days on perfecting this, finally came
to the conclusion that maybe I went a bit overboard with it, almost willing
to just say "the @#$ with it" but I didn't want to let it all go to waste...
Anyway, here it goes:
Up till now, I used a naming convention I borrowed from my last 2 jobs (I
switched jobs together with the team manager). Since I didn't have any
convention before that, I just adopted it and thought nothing about it ever
since, and I also used it for hobby projects at home. It's a form of
shortened and generalized Hungarian Notation, applied the wrong way as well;
"explicit data type = prefix" in stead of "intended data type = prefix". To
give an idea: all numbers have a prefix 'n' (from number), all class
instances an 'o' (from object), possibly resulting in e.g. compiling but not
wrong-looking "nDayOfTheWeek = nCircleRadius" or "nColor =
oBankAccount.GetID()" (where GetID returns e.g. an int or so).
But now the times have changed, and I've started making serious programs for
my own company (hurray!). And then a shortcoming in my inherited coding
convention started biting me in the @ss (two distinct usage of the same data
type getting mixed up, causing an infrequent bug that almost slipped my
attention); time to rethink the coding convention.
I read some (well, a lot of) posts, trying to extract some usefull ideas
from out of the usual bickering about coding conventions. What I've come up
with so far is a syntax that has aspects of "True" Hungarian notation and
common SmallTalk conventions (or so I've heard). I think the benefit of it
is that code inspection will catch some bugs compilers won't catch (like in
True Hungarian; one basic type being used for more than one variable, all
with another purpose, and mixing the purposes up). Furthermore it's much
more closer to standard English than Hungarian is, so the names (kind of)
read out like you'd call the thing in plain English. And the downside of
Hungarian (getting to remember all those zillion standard abbreviations, and
then also invent and maintain your own) is also bypassed.
============== Syntax ==============
0) Scope prefixes
m: member scope
g: global scope
1) Modifier prefixes
c: const
h: handle to
p: pointer to
r: reference to
s: set of
2) Typenames
T[<modifiers>]<Type>;
So, typenames can have modifiers embedded into them if needed for
clarification.
3) Subject (helper intermediate stage for Variables and Functions)
[<modifiers>][<Name>]<Type>
Any modifiers embedded in the used type will migrate to the front with the
rest of the modifiers (but keep their relative order).
I'm still not sure whether the <Name> part should be optional...
4) Variables
[<scope>]<Subject>
5) Functions
<Name(Action)>_<Subject(ReturnType)>[_<Name(Clause1)>_<Subject(Clause1)>[_<Name(Clause2)>_<Subject(Clause2)>[...]]]
The optional clauses denote extra info on the workings of the function in
terms of the parameters; clause 1 descibes parameter 1, etc. Not all
parameters need to be described (I want to keep overloading to work), but
immutable _intended_ parameter usage will benefit from this I think.
6) 'Procedures' (void functions)
<Name(Action)>[<Name(Clause1)>_<Subject(Clause1)>[_<Name(Clause2)>_<Subject(Clause2)>[...]]]
7) Additional rules
Type will be the name of the intended type used, not the exact
implementation.
Abbreviation of names and types is allowed, unless it diminishes the
clarity.
Ok, now that the rules are laid out, lets show some examples of what this
boils down to:
============== Examples ==============
// Date class that manipulates dates, and is compatible with the type
'double'
class TDate {
...
};
// Set of pointers to dates
typedef std::vector<TDate*> TspDate;
// Date variable
TDate* pStartDate;
double EndDate;
// Member instance of the set of pointers to date;
// keeps a set of references to start dates.
class ... {
TspDate mspStartDate;
TColor mColor; // No explicitly added name; can only have 1 color
};
// Function returning a date
// Declaration:
TDate* Get_pDate_From_crDataFile(const TDataFile& crInputDataFile, TDate
DefaultStartDate);
// Usage:
double UnknownDate = 0.0;
*ppStartDate = Get_pDate_From_crDataFile(TempDataFile, UnknownDate);
// Function performing some action
// The 2nd parameter can be said to be of type "format"
// Declaration:
Print_Date(double Date, char* DisplayFormat);
// Usage:
Print_Date(mStartDate, "ddmmyyyy");
Ok, these examples are good to understand for everyone I think (well, I
hope). The downside I think is that the notation is _quite_ verbose, but
any misinterpretation is minimalized.
Another downside is that the verbosity can quickly add up when you have
extended types via typedefs; a live example from the code I used to test the
convention:
class ... {
private:
typedef std::map<TStatCacheID, TStatInt*> TspStatCacheID2StatInt;
TspStatCacheID2StatInt mspStatCacheID2StatInt;
}
where before I would have used:
typedef std::map<CStatCacheID, CStatInt*> CID2Int;
CID2Int m_apIntsByID;
In this case the extra abbreviation of the class type isn't harmfull,
because all classes involved have no conversion operators whatsoever (all
misuse will be penalized with a compile error). Using the new convention
doubles the names, which makes code lines scroll off the screen quite
fast... And though I strive for perfection of my code, I'm also lazy enough
not to want to type "mspStatCacheID2StatInt" every time...
Yet another thing I find at fault with this convention is that, althoug the
name is suffixed with the type, there is no delimiter between them (DataFile
InputDataFile; is that a File for InputData, or a DataFile for Input?). The
underscore is already reserved for use in functions (where I need to
seperate the actions from the subjects), and CamelCase is already at work
within the names and types. If only I could use bold or italics in my code
And a final grudge I have about this new convention is that flag values
(booleans) have no clear type; what about e.g.:
bool Get_Success_FromPrint_pDate(TDate* pDate);
I'd rather just use Print_pDate(...) ... Or:
bool FailSuccess = !Get_Success_From....()
type returned by the function is "Success", so the variable better have that
type as well (don't want to loosen the usage of the convention; if I allow
so, then what's the use?). I could use a general flag type called "Flag"
(typedef bool TFlag), but that makes the looks-like-English to fail;
TFlag SuccessFlag = Get_Flag_From...()
or
TFlag SuccessFlag = Get_SuccessFlag_From...()
which is even more verbose...
In my code I make use of two types of dates; dates in local time and dates
in UTC. Obviously it's not a good idea to mix these without using mapping
functions ("ToLocal()", "ToUTC()"). In my old notation, both types would
cause a prefix letter 'd' (from 'date') to be used, but now I just 'invent'
the type TDateL and TDateU (both typedefs to a class TDate from a library I
use). This still won't cause the compiler to catch any semantic errors, but
any code review will.
I did try a better solution: generating 2 derived classes from TDate. But
that caused a lot of headaches as well; I either have to recreate all
constructors and assignment operators (and TDate has a lot of those );
this task I can alleviate by adding a templatized constructor like
template<typename TParam>
TDateU(const TParam& crParam): TDate(crParam) {};
but that still leaves implicit conversions like e.g.
TDateU => double => TDateL, or
TDateU => SYSTEMTIME => TDateL
and I _need_ those conversions (I do a lot of arithmetic with and
conversions of dates).
And so the bottom line: what do you think of it? Any ideas about the
verbosity?
Thanks for reading this far
Kind regards,
Carl
Disclaimer: before I might trigger your "let's start a holy war!" button,
I'd like to say I'm not intended to; I just post this message to get some
input and not to promote "Yet Another Naming Convention". It's a bit of a
long post, but I've spent the past few days on perfecting this, finally came
to the conclusion that maybe I went a bit overboard with it, almost willing
to just say "the @#$ with it" but I didn't want to let it all go to waste...
Anyway, here it goes:
Up till now, I used a naming convention I borrowed from my last 2 jobs (I
switched jobs together with the team manager). Since I didn't have any
convention before that, I just adopted it and thought nothing about it ever
since, and I also used it for hobby projects at home. It's a form of
shortened and generalized Hungarian Notation, applied the wrong way as well;
"explicit data type = prefix" in stead of "intended data type = prefix". To
give an idea: all numbers have a prefix 'n' (from number), all class
instances an 'o' (from object), possibly resulting in e.g. compiling but not
wrong-looking "nDayOfTheWeek = nCircleRadius" or "nColor =
oBankAccount.GetID()" (where GetID returns e.g. an int or so).
But now the times have changed, and I've started making serious programs for
my own company (hurray!). And then a shortcoming in my inherited coding
convention started biting me in the @ss (two distinct usage of the same data
type getting mixed up, causing an infrequent bug that almost slipped my
attention); time to rethink the coding convention.
I read some (well, a lot of) posts, trying to extract some usefull ideas
from out of the usual bickering about coding conventions. What I've come up
with so far is a syntax that has aspects of "True" Hungarian notation and
common SmallTalk conventions (or so I've heard). I think the benefit of it
is that code inspection will catch some bugs compilers won't catch (like in
True Hungarian; one basic type being used for more than one variable, all
with another purpose, and mixing the purposes up). Furthermore it's much
more closer to standard English than Hungarian is, so the names (kind of)
read out like you'd call the thing in plain English. And the downside of
Hungarian (getting to remember all those zillion standard abbreviations, and
then also invent and maintain your own) is also bypassed.
============== Syntax ==============
0) Scope prefixes
m: member scope
g: global scope
1) Modifier prefixes
c: const
h: handle to
p: pointer to
r: reference to
s: set of
2) Typenames
T[<modifiers>]<Type>;
So, typenames can have modifiers embedded into them if needed for
clarification.
3) Subject (helper intermediate stage for Variables and Functions)
[<modifiers>][<Name>]<Type>
Any modifiers embedded in the used type will migrate to the front with the
rest of the modifiers (but keep their relative order).
I'm still not sure whether the <Name> part should be optional...
4) Variables
[<scope>]<Subject>
5) Functions
<Name(Action)>_<Subject(ReturnType)>[_<Name(Clause1)>_<Subject(Clause1)>[_<Name(Clause2)>_<Subject(Clause2)>[...]]]
The optional clauses denote extra info on the workings of the function in
terms of the parameters; clause 1 descibes parameter 1, etc. Not all
parameters need to be described (I want to keep overloading to work), but
immutable _intended_ parameter usage will benefit from this I think.
6) 'Procedures' (void functions)
<Name(Action)>[<Name(Clause1)>_<Subject(Clause1)>[_<Name(Clause2)>_<Subject(Clause2)>[...]]]
7) Additional rules
Type will be the name of the intended type used, not the exact
implementation.
Abbreviation of names and types is allowed, unless it diminishes the
clarity.
Ok, now that the rules are laid out, lets show some examples of what this
boils down to:
============== Examples ==============
// Date class that manipulates dates, and is compatible with the type
'double'
class TDate {
...
};
// Set of pointers to dates
typedef std::vector<TDate*> TspDate;
// Date variable
TDate* pStartDate;
double EndDate;
// Member instance of the set of pointers to date;
// keeps a set of references to start dates.
class ... {
TspDate mspStartDate;
TColor mColor; // No explicitly added name; can only have 1 color
};
// Function returning a date
// Declaration:
TDate* Get_pDate_From_crDataFile(const TDataFile& crInputDataFile, TDate
DefaultStartDate);
// Usage:
double UnknownDate = 0.0;
*ppStartDate = Get_pDate_From_crDataFile(TempDataFile, UnknownDate);
// Function performing some action
// The 2nd parameter can be said to be of type "format"
// Declaration:
Print_Date(double Date, char* DisplayFormat);
// Usage:
Print_Date(mStartDate, "ddmmyyyy");
Ok, these examples are good to understand for everyone I think (well, I
hope). The downside I think is that the notation is _quite_ verbose, but
any misinterpretation is minimalized.
Another downside is that the verbosity can quickly add up when you have
extended types via typedefs; a live example from the code I used to test the
convention:
class ... {
private:
typedef std::map<TStatCacheID, TStatInt*> TspStatCacheID2StatInt;
TspStatCacheID2StatInt mspStatCacheID2StatInt;
}
where before I would have used:
typedef std::map<CStatCacheID, CStatInt*> CID2Int;
CID2Int m_apIntsByID;
In this case the extra abbreviation of the class type isn't harmfull,
because all classes involved have no conversion operators whatsoever (all
misuse will be penalized with a compile error). Using the new convention
doubles the names, which makes code lines scroll off the screen quite
fast... And though I strive for perfection of my code, I'm also lazy enough
not to want to type "mspStatCacheID2StatInt" every time...
Yet another thing I find at fault with this convention is that, althoug the
name is suffixed with the type, there is no delimiter between them (DataFile
InputDataFile; is that a File for InputData, or a DataFile for Input?). The
underscore is already reserved for use in functions (where I need to
seperate the actions from the subjects), and CamelCase is already at work
within the names and types. If only I could use bold or italics in my code
And a final grudge I have about this new convention is that flag values
(booleans) have no clear type; what about e.g.:
bool Get_Success_FromPrint_pDate(TDate* pDate);
I'd rather just use Print_pDate(...) ... Or:
bool FailSuccess = !Get_Success_From....()
type returned by the function is "Success", so the variable better have that
type as well (don't want to loosen the usage of the convention; if I allow
so, then what's the use?). I could use a general flag type called "Flag"
(typedef bool TFlag), but that makes the looks-like-English to fail;
TFlag SuccessFlag = Get_Flag_From...()
or
TFlag SuccessFlag = Get_SuccessFlag_From...()
which is even more verbose...
In my code I make use of two types of dates; dates in local time and dates
in UTC. Obviously it's not a good idea to mix these without using mapping
functions ("ToLocal()", "ToUTC()"). In my old notation, both types would
cause a prefix letter 'd' (from 'date') to be used, but now I just 'invent'
the type TDateL and TDateU (both typedefs to a class TDate from a library I
use). This still won't cause the compiler to catch any semantic errors, but
any code review will.
I did try a better solution: generating 2 derived classes from TDate. But
that caused a lot of headaches as well; I either have to recreate all
constructors and assignment operators (and TDate has a lot of those );
this task I can alleviate by adding a templatized constructor like
template<typename TParam>
TDateU(const TParam& crParam): TDate(crParam) {};
but that still leaves implicit conversions like e.g.
TDateU => double => TDateL, or
TDateU => SYSTEMTIME => TDateL
and I _need_ those conversions (I do a lot of arithmetic with and
conversions of dates).
And so the bottom line: what do you think of it? Any ideas about the
verbosity?
Thanks for reading this far
Kind regards,
Carl