Introduction
In this article I'll try to give you apt code styles which you can use when writing
programs using functions.
C++ code styles
Most C++ programmers agree that multiple exit points are a bad thing as single
exit point makes the code look tidy, easy to read, simplifies debugging when
having a single breakpoint (at the return statement) and you can be certain
to catch the processor before it leaves a function. One more considerable
drawback of multiple exit points I came across, during practical work, is code
where there are many initalization and clean up sections present. With use
of the return operator it is necessary to put uninitializing operators after each
failure in code. See Code Style 1 where I illustrate the
problem. See the solution I present with a goto Quit operator, Code Style
4, where you have all clean up operations in a special code section, not all
over the code.
Code Style 1
BOOL Func(LPOBJECT *ppObject)
{
LPOBJECT pObject1; LPOBJECT pObject2; LPOBJECT pObject3;
if(!CreateObject1(&pObject1)) return FALSE;
if(!CreateObject2(&pObject2))
{
DestroyObject1(&pObject1);
return FALSE;
}
if(!CreateObject3(&pObject3))
{
DestroyObject2(&pObject2);
DestroyObject1(&pObject1);
return FALSE;
}
etc ...
(*ppObject) = pObject3;
return TRUE;
}
Some programmers tolerate the use of the return operator (which causes multiple exit
point) for validity checks on parameters or initial conditions like this:
Code Style 2
HRESULT func(*pParam)
{
if(!pParam) return E_INVALIDARG;
...
}
Though I ought to say that multiple return points have their own advantages:
they make the code more readable, you can leave the failed function whenever
you encounter an error, return the error code at once without using the variable to store
it, and avoiding the needless commands which are not necessary as the whole
function fails (especially multiple return operators get gid of redundant
if/else and assignment statements Code Style 5,Code
Style 6). But when using this style you have an increased risk to make a mistake
in code, and you considerably increase the amount of times spent debugging your code. This code style,
Code Style 3, is safe when there are no other initialization/clean
up operations in the function.
Code Style 3
int Funct()
{
if(!func1()) return(ErrCode_A);
if(!func2()) return(ErrCode_B);
if(!func3()) return(ErrCode_C);
if(!func4()) return(ErrCode_D);
return(ErrCode_NOERROR);
}
Usually there is no reason in using the "goto Quit" operator when there are multiple
exit points in code. Now I'll show you a code style using a goto. As for this
operator, many programmers don't see a problem in using 'goto Quit' as it provides
a single point of exit which is the most reliable way to create solid code.
And it also helps in avoiding memory leaks, plus the code looks tidy. But almost
all programmers who allow the use of goto operators use them in only in case of
an error during the function's execution to go to the Quit label (the ONLY label in the function).
As for my opinion about using of goto operator, I personally don't see a problem
with it as long as it's used correctly. Many programmers (especially purists)
make difficulties for themselves avoiding a "goto Quit" operator. Use of it
removes error handling from the function mainstream and lets you have one common
block for error handling. It also keeps conditionals shorter and easier to
read. Compare Code Style 1 with Code
Style 4 to see how clean up code was moved to a separate section.
Code Style 4
BOOL Func(LPOBJECT *ppObject)
{
LPOBJECT pObject1=NULL; LPOBJECT pObject2=NULL; LPOBJECT pObject3=NULL;
BOOL bResult;
bResult = CreateObject1(&pObject1);
if(!bResult) goto Quit;
bResult = CreateObject2(&pObject2);
if(!bResult) goto Quit;
bResult = CreateObject3(&pObject3);
if(!bResult) goto Quit;
etc ...
bResult = TRUE;
Quit:
if(!bResult)
{
if(pObject2) DestroyObject2(&pObject2);
if(pObject1) DestroyObject1(&pObject1);
}
else
{
(*ppObject) = pObject3;
}
return bResult;
}
Now I am going to show you a couple other code styles that do not contain "problematic"
goto's or return statements. The Code Style 5 method only works
where each call has a unique failure value. In this case each function must return a boolean
value to apply this style.
Code Style 5
int Funct()
{
int iErr = ErrCode_NOERROR;
if(!func1()) iErr = ErrCode_A;
else if(!func2()) iErr = ErrCode_B;
else if(!func3()) iErr = ErrCode_C;
else if(!func4()) iErr = ErrCode_D;
switch(iErr)
{
case ErrCode_D: CleanUp4();
case ErrCode_C: CleanUp3();
case ErrCode_B: CleanUp2();
case ErrCode_A: CleanUp1();
}
return(iErr);
}
Here is another way, Code Style 6, to do what the previous code
did. This is the proper way to do it. But, I don't like it as it makes code
more difficult to read in my opinion. The indention in the code makes the use of the
if..else structure clearer but gets cumbersome as the number of entries grows.
Code Style 6 would be ideal for coding if it wouldn't look like
a pile of if/else's when you have to call many functions within the main one.
Code Style 6
int Funct()
{
int iErr = ErrCode_NOERROR;
if (func1())
if (func2())
if (func3())
if (func4())
iErr = ErrCode_NOERROR;
else
iErr = ErrCode_D;
else
iErr = ErrCode_C;
else
iErr = ErrCode_B;
else
iErr = ErrCode_A;
switch(iErr)
{
case ErrCode_D: CleanUp4();
case ErrCode_C: CleanUp3();
case ErrCode_B: CleanUp2();
case ErrCode_A: CleanUp1();
}
return(iErr);
}
There are some solutions with the use of a throw/catch. I like the one for use with a class
constructor. Although you can use the same idea for regular functions also.
Code Style 7
#define MyExceptions long
#define SomeException1 1L
#define SomeException2 2L
CSomeClass::CSomeClass() throw(MyExceptions)
{
try
{
DoSomeInitialization();
if(SomeError) throw(SomeException1);
DoMoreInitialization();
if(SomeOtherError) throw(SomeException2);
}
catch(MyExceptions lEx)
{
switch(lEx)
{
case SomeException2: CleanUpSecondaryInitialization();
case SomeException1: CleanUpPrimaryInitialization();
}
throw(lEx);
}
}
The following lines show how to create the instance of CSomeClass.
Code Style 8
try
{
pCSomeClass = new CSomeClass();
}
catch( MyExceptions lEx )
{
switch( lEx )
{
case SomeException2: Actions2(); break;
case SomeException1: Actions1(); break;
}
}
The following style removes some of the redundancy represented by nested
if/elses in Code Style 6 at the expense of extra
checking of the bSuccess variable. From the point of view of operating speed Code
Style 6 is faster then Code Style 9 as all "if(bSuccess)"
lines will be performed regardless of the fact that some error occured. You
might want to add a clean up section and return error codes like in Code
Style 6.
Code Style 9
BOOL Funct()
{
BOOL bSuccess;
bSuccess = funct1();
if(bSuccess) {
bSuccess = funct2();
}
if(bSuccess) {
bSuccess = funct3();
}
if(bSuccess) {
bSuccess = funct4();
}
return bSuccess;
}
I think that good code is a reasonable trade-off between good style,
speed and robustness. My code samples show many styles of function coding.
All of them have their own merits. Make your own decision of coding
style that suits your needs. Consider future modifications, also. If you
choose the right style, when you later make changes you will not need to change
too much code; otherwise you might be forced to rewrite the whole function.
Finally, don't try to cut and paste my samples of styles into your code,
they won't work in the form I gave you as they are just the ideas, not ready
to use snippets.