C++ 编码风格,创建健壮代码的方法






3.50/5 (6投票s)
2001 年 1 月 15 日

134938

37
C++ 编码风格的示例,可帮助您创建健壮的代码
引言
在本文中,我将尝试为您提供编写函数程序时可以使用得体的代码风格。
C++ 代码风格
大多数 C++ 程序员都认为多个退出点是一个糟糕的做法,因为单个退出点可以使代码看起来整洁、易于阅读,在设置单个断点(在 return 语句处)时可以简化调试,并且您可以确定在处理器离开函数之前捕获它。我在实际工作中遇到的另一个多退出点的主要缺点是代码中存在许多初始化和清理部分。使用 return 运算符后,有必要在代码中的每个失败处放置取消初始化运算符。请参阅 代码风格 1,其中我说明了这个问题。请参阅我提出的使用 goto Quit 运算符的解决方案 代码风格 4,其中您可以在一个特殊代码段中进行所有清理操作,而不是散布在整个代码中。
代码风格 1
BOOL Func(LPOBJECT *ppObject) { LPOBJECT pObject1; LPOBJECT pObject2; LPOBJECT pObject3; if(!CreateObject1(&pObject1)) return FALSE; // No uninit code here if(!CreateObject2(&pObject2)) { DestroyObject1(&pObject1); // must remember to destroy first object return FALSE; } if(!CreateObject3(&pObject3)) { DestroyObject2(&pObject2); // must remember to destroy second object DestroyObject1(&pObject1); // must delete first object AGAIN return FALSE; } etc ... (*ppObject) = pObject3; return TRUE; }
有些程序员会容忍使用 return 运算符(会导致多个退出点)来进行参数有效性检查或初始条件检查,如下所示:
代码风格 2
HRESULT func(*pParam) { if(!pParam) return E_INVALIDARG; ... }
尽管我应该说,多个返回点有其自身的优点:它们使代码更具可读性,您可以在遇到错误时随时离开失败的函数,立即返回错误代码而无需使用变量来存储它,并避免了不必要的命令,因为整个函数都会失败(特别是多个 return 运算符可以摆脱冗余的 if/else 和赋值语句 代码风格 5,代码风格 6)。但是,使用这种风格时,您会增加出错的风险,并且会显著增加调试代码所花费的时间。这种代码风格 代码风格 3,在函数中没有其他初始化/清理操作时是安全的。
代码风格 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); }
通常,当代码中有多个退出点时,没有理由使用“goto Quit”运算符。现在我将向您展示一种使用 goto 的代码风格。至于这个运算符,许多程序员在使用“goto Quit”时没有看到问题,因为它提供了一个单一的退出点,这是创建健壮代码最可靠的方法。它还有助于避免内存泄漏,并且代码看起来很整洁。但是,几乎所有允许使用 goto 运算符的程序员都只在函数执行期间出错时使用它们,以跳转到 Quit 标签(函数中唯一的标签)。至于我关于使用 goto 运算符的看法,只要使用得当,我个人认为没问题。许多程序员(尤其是纯粹主义者)在避免“goto Quit”运算符时给自己制造了困难。使用它将错误处理从函数主流程中移除,并允许您有一个通用的错误处理块。它还能使条件语句更短、更易读。将 代码风格 1 与 代码风格 4 进行比较,看看清理代码是如何被移到一个单独的部分。
代码风格 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) { // tidy up if(pObject2) DestroyObject2(&pObject2); if(pObject1) DestroyObject1(&pObject1); } else { (*ppObject) = pObject3; // pass back data } return bResult; }
现在我将向您展示几种不包含“有问题的”goto 或 return 语句的代码风格。 代码风格 5 方法仅在每个调用都有一个唯一的失败值时才有效。在这种情况下,每个函数都必须返回一个布尔值才能应用此样式。
代码风格 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); }
这是另一种执行前一个代码所做的事情的方式,代码风格 6。这是正确的方法。但是,我认为它并不好,因为它使代码更难阅读。代码中的缩进使 if..else 结构的使用更清晰,但随着条目数量的增加会变得笨拙。如果当您不得不在主函数中调用许多函数时,代码风格 6 会是理想的编码方式,但它看起来会像一堆 if/else。
代码风格 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); }
有一些使用 throw/catch 的解决方案。我喜欢用于类构造函数的那个。虽然您也可以对普通函数使用相同的想法。
代码风格 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); } }
以下几行展示了如何创建 CSomeClass 的实例。
代码风格 8
try { pCSomeClass = new CSomeClass(); } catch( MyExceptions lEx ) { switch( lEx ) { case SomeException2: Actions2(); break; case SomeException1: Actions1(); break; } }
以下风格消除了 代码风格 6 中嵌套 if/else 所表示的一些冗余,但代价是额外检查 bSuccess 变量。从运行速度的角度来看,代码风格 6 比 代码风格 9 快,因为所有“if(bSuccess)”行都将被执行,而不考虑发生了一些错误的事实。您可能希望添加一个清理部分并返回错误代码,就像在 代码风格 6 中一样。
代码风格 9
BOOL Funct() { BOOL bSuccess; bSuccess = funct1(); if(bSuccess) { bSuccess = funct2(); } if(bSuccess) { bSuccess = funct3(); } if(bSuccess) { bSuccess = funct4(); } return bSuccess; }
我认为好的代码是良好风格、速度和健壮性之间合理的权衡。我的代码示例展示了许多函数编码风格。它们都有各自的优点。根据您的需求,做出适合您的编码风格的决定。同时也要考虑将来的修改。如果您选择了正确的风格,以后进行更改时就不需要修改太多代码;否则,您可能会被迫重写整个函数。最后,不要试图将我的风格示例复制粘贴到您的代码中,它们以我给出的形式无法正常工作,因为它们只是想法,而不是可直接使用的代码片段。