我遇到了一些代码:
BOOL CBlahClass::SomeFunction(DWORD *pdw)
{
RETURN_FALSE_IF_FILE_DOESNT_EXIST
//the rest of the code makes sense...
//...
}
我所看到的一切都很有道理,除了我对这条线有一点疑问 RETURN_FALSE_IF_FILE_DOESNT_EXIST
我搜索了这个字符串,然后找到#define:
#define RETURN_FALSE_IF_FILE_DOESNT_EXIST \
if (FALSE==DoesFileExist()) return FALSE;
我的问题是......到底是什么?有没有什么好的理由让#define像这样?为什么不写:
BOOL CBlahClass::SomeFunction(DWORD *pdw)
{
if ( FALSE == DoesFileExist() ) return FALSE
//the rest of the code makes sense...
//...
}
我能想到这样做的唯一原因是,小位更容易,并且小更难以写出" RETURN_FALSE_IF_FILE_DOESNT_EXIST&#34 ;然后写出" if(FALSE == DoesFileExist())返回FALSE"。
任何人都有其他理由这样做吗?这类事有名字吗?
答案 0 :(得分:7)
嗯,使用宏的想法是在特定检查可能发生变化的情况下简化维护。当你有一个宏时,你需要改变的只是宏定义,而不是遍历整个程序并单独更改每个检查。此外,它还为您提供了通过更改宏定义来完全消除此检查的机会。
通常,当一个人拥有相当全面的重复代码时,通常会将该代码分离为一个函数。但是,当重复的代码片段必须执行某些非正统操作时,例如退出封闭函数的return
,宏基本上是唯一的选择。 (有人可能会同意这样的技巧应保留给调试/维护/系统级代码,应该在应用程序级代码中避免使用。)
答案 1 :(得分:4)
该宏可能是guard clause,它声明了一个先决条件。前提条件是某个文件必须存在,因为如果它没有调用该函数是没有意义的。作者可能希望保护条款明显不同于#34;常规"检查并使其成为一个明显的宏观。它在这里是一个宏只是一种风格偏好。
当您要求条件为真时,您可以编写保护条款。在名为parseFile()
的函数中,您可能确实希望调用者检查该文件是否存在。但是在一个名为openFile()
的函数中,该文件尚不存在是完全合理的(因此你不会有一个警卫)。
您可能更习惯于看assert(...)
。但是,如果您不希望程序在条件错误时停止,并且您希望在NDEBUG
出现时检查条件,该怎么办?您可以实现像assert_but_return_false_on_fail(...)
这样的宏,对吗?而这就是这个宏的意义所在。替代assert()
。
glib是一个非常受欢迎的defines its precondition assertions喜欢的库 这样:
#define g_return_if_fail()
#define g_return_val_if_fail()
#define g_return_if_reached
#define g_return_val_if_reached()
#define g_warn_if_fail()
#define g_warn_if_reached
与glib中相同的代码为g_return_val_if_fail(DoesFileExist(), FALSE);
。
您粘贴的代码可能会更好。一些问题是:
这是非常具体的。为什么不在任何条件下使它成为通用的而不是 只是文件是否存在?
将返回值硬编码到名称中。
它没有进一步的诊断。 glib函数记录相当详细 失败时出错,包括堆栈跟踪,以便您可以看到一系列调用 这导致了失败。
这些问题使得宏看起来很愚蠢和毫无意义。否则它似乎毫无意义。
或者说,作者可能会被宏观迷恋,而且我读得太多了。
答案 2 :(得分:0)
没有理由这样做。程序员编写这样的宏的常用方法是因为他们的代码具有多返回语句(如果可能的话应该避免使用 - 但有时候避免使用它是不明智的)。例如:
// Bad code
err_t func (void)
{
err_t err;
...
if(err == error1)
{
cleanup();
return err;
}
...
if(err == error2)
{
cleanup();
return err;
}
cleanup();
return err;
}
由于代码重复,要在整个函数中进行清理是不好的做法。
更清晰地解决问题的古老方法是“在错误转到”,你在标签的最后添加一个标签。并做清理并返回那里。它实际上是一个有点可接受的解决方案,但是goto是禁忌所以人们回避它。相反,他们想出了类似的东西:
// Slightly less bad code
#define RETURN_FROM_FUNCTION(err) { cleanup(); return (err); }
err_t func (void)
{
err_t err;
...
if(err == error1)
{
RETURN_FROM_FUNCTION(err);
}
...
if(err == error2)
{
RETURN_FROM_FUNCTION(err);
}
RETURN_FROM_FUNCTION(err);
}
这会删除代码重复,因此程序更安全,更易于维护。这可能是你问题的答案。
但是这个宏仍然不是一个好的解决方案,因为它使得代码更难以阅读,因为C程序员擅长阅读C,但不善于阅读“家庭酿造,秘密宏语言”。当然,宏的常见问题是:类型安全,难以调试/维护等。
但是,所有上述方法(包括on-error-goto)都源于无法在框外思考。最佳解决方案是:
// proper code
err_t func (void)
{
err_t err;
allocate(); // allocate whatever needs to be allocated
err = func_internal();
cleanup(); // one single place for clean-up
return err;
}
static err_t func_internal (void) // local file scope, private function
{
err_t err;
...
allocate_if_needed(); // or maybe allocation needs to be here, based on results
...
if(if(err == error1)
{
return err;
}
...
if(err == error2)
{
return err;
}
return err;
}