您的位置:首页 > 编程语言

翻译《有关编程、重构及其他的终极问题?》——10.避免使用多个小的#ifdef块

2016-12-19 21:13 337 查看

翻译《有关编程、重构及其他的终极问题?》——10.避免使用多个小的#ifdef块

标签(空格分隔): 翻译 技术 C/C++

作者:Andrey Karpov

翻译者:顾笑群 - Rafael Gu

最后更新:2016年12月19日

本书背景说明、总目录等介绍,可以跳转到以下链接进行查看:

http://blog.csdn.net/headman/article/details/53045891

欢迎大家转载,但请附上原作者以及翻译者的名字、原文出处,以尊重光荣的劳动者。

10.避免使用多个小的#ifdef块

下面这段有问题的代码来自CoreCLR项目。PVS-Studio的诊断说明是这样描述这段错误的:V522 Dereferencing of the null pointer ‘hp’ might take place(译者注:大意是说可能会碰到hp指针无效的情况)。

heap_segment* gc_heap::get_segment_for_loh (size_t size
#ifdef MULTIPLE_HEAPS
, gc_heap* hp
#endif //MULTIPLE_HEAPS
)
{
#ifndef MULTIPLE_HEAPS
gc_heap* hp = 0;
#endif //MULTIPLE_HEAPS
heap_segment* res = hp->get_segment (size, TRUE);
if (res != 0)
{
#ifdef MULTIPLE_HEAPS
heap_segment_heap (res) = hp;
#endif //MULTIPLE_HEAPS
....
}


解释

我相信#ifdef/#endif宏是邪恶的——不幸的是,这一种无法避免的邪恶。它们是必须的而且我们不得不使用它们。所以我不会强烈建议你停止使用#ifdef,我没有这个意思。但是我非常想让你非常要小心的不要过度使用它们。

我想,诸位都有因为代码中的多个#ifdef而陷于细读代码的情况,特别痛苦的是那些代码每10行甚至更少就有一个#ifdef的情况。那些代码一般都是系统相关的,所以虽然你不情愿,但还不得不使用#ifdef。

看看前面的代码多难读!而且代码阅读还是程序员们每天必须的工作。是的,我的意思就是:我们在阅读代码和学习已有代码上花费的时间远远多于写新的代码。这也导致了难以阅读的代码大大降低了我们的效率,并且给一些新的错误有机可乘。

让我们回到上面的代码,当MULTIPLE_HEAPS宏没有被声明的时候,指针hp是空的,这样用这个指针调用对象方法就是出错。为了简便期间,上面的代码等同于如下:

heap_segment* gc_heap::get_segment_for_loh (size_t size)
{
gc_heap* hp = 0;
heap_segment* res = hp->get_segment (size, TRUE);
....


程序员声明了hp变量,并且初始化为NULL(译者注:更准确的说是0),而且马上就使用了这个变量。如果MULTIPLE_HEAPS没有被定义,这个程序就会出问题。

正确的代码

尽管我的一个同事已经在文章“25 Suspicious Code Fragments in CoreCLR”中报告了这个错误,但这个错误目前依旧在CoreCLR项目中存在(截止2016年12月4日),所以我无法确认那一种是最好修复这个错误的方式。

如代码所示,既然(hp == nullptr),那么res变量也应该被初始化为对应的值——但是我们现在无法确认具体应该是什么值,所以我们这次就无法修复这个问题。

建议

既然大量的#ifdef让代码难以阅读和理解,并且更容易犯错误,那就干脆尽量在你的代码中去除小的#ifdef/#endif块吧。

这个建议并不适用所有的情况——这要看具体情况如何。不管如何,只需要记住,#ifdef是麻烦的来源之一,所以尽量让你的代码保持干净吧。

技巧一:尽量不使用#ifdef

#ifdef有时可以用常量或者if语句来代替。比较一下下面两段代码:

首先是用宏申明的变量:

#define DO 1

#ifdef DO
static void foo1()
{
zzz();
}
#endif //DO

void F()
{
#ifdef DO
foo1();
#endif // DO
foo2();
}


上面这段代码很难阅读,甚至你都不想去阅读,你是不是希望能把它忽略过去?那么现在比较下面这段代码:

const bool DO = true;

static void foo1()
{
if (!DO)
return;
zzz();
}

void F()
{
foo1();
foo2();
}


现在,代码易读多了。一些人也许会说这个代码效率不如前面那段,因为有了多余的函数调用以及内部的检查。但我不同意:首先现代的编译器非常聪明,这使得很可能你在release版本中得到是没有额外检查和函数调用的版本;其次,相较上面的问题而言,这点效率损失(译者注:如果编译器没有进行优化)可以忽略,干净和整齐的代码更重要。

技巧二:让你的#ifdef块大一些

如果是我写get_segment_for_loh()函数,我就不会用这么多#ifdef;相反,我会写两个版本的函数。当然,那会写更多的代码,但这样代码就更容易读,而且也能更容易编辑。

也许有些人会再次争论到,既然是复制的代码,而且每段里面都有被#ifdef管理的冗长函数,如果我改变了其中一段,而忘记改变另外一段,那不就会引起新的问题吗!

嗨,等等!为什么你要写如此冗长的函数?把很多通用的逻辑放到不同的辅助函数中——那么你的两个函数都会变的更短,并且能让你更容易的确认两个函数的不同点。

我知道这个技巧不是万能药,但请务必按照这个方向去思考解决问题的方案。

技巧三:考虑使用模板——可能会有帮助

技巧四:在使用#ifdef前花点时间仔细思考下:

你是否能不使用#ifdef?或者你是否能使用更少的#ifdef?反正#ifdef就是不好的,能不用就不用。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐