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

翻译《有关编程、重构及其他的终极问题?》——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就是不好的,能不用就不用。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值