翻译《有关编程、重构及其他的终极问题?》——13.表格化的格式化

翻译《有关编程、重构及其他的终极问题?》——13.表格化的格式化

标签(空格分隔): 翻译 技术 C/C++
作者:Andrey Karpov
翻译者:顾笑群 - Rafael Gu
最后更新:2017年01月03日


13.表格化的格式化

下面这段代码摘自ReactOS项目(和Windows兼容的开源操作系统)。PVS-Studio诊断出来的错误描述为:V560 A part of conditional expression is always true: 10035L(译者注:大意是部分条件判断始终为true)。

void adns__querysend_tcp(adns_query qu, struct timeval now) {
    ...
    if (!(errno == EAGAIN || EWOULDBLOCK || 
          errno == EINTR || errno == ENOSPC ||
          errno == ENOBUFS || errno == ENOMEM)) {
    ...
}

解释
上面的代码很少,所以你能很容易发现错误的地方。但当我们处理现实中真实代码时,却是很难发现bug。当阅读到类似前述代码时,你一般都很可能无意识忽略掉类似的比较块而跳到下一段代码去了。

造成这种情况的主要原因是这些条件判断几乎没有被格式化,而且因为需要一些额外的精力,所以你也不会花费太多的注意力在上面——我们一般会潜意识的认为既然那些条件检查都是类似的,那么其中就应该不会有错误,每个判断都是好的。

避免这个错误一个方法是把代码进行表格化的格式化。

如果你因为太懒而没有发现上面代码的错误,我告诉你,其实是在检查中缺少了一个“errno ==”。因为EWOULDBLOCK一直是true,所以将导致这个条件判断一直未true。

正确的代码

if (!(errno == EAGAIN || errno == EWOULDBLOCK || 
      errno == EINTR || errno == ENOSPC ||
      errno == ENOBUFS || errno == ENOMEM)) {

建议
作为一个开始,这里我放一个表格化格式化的代码版本。当然,我其实并不喜欢这种。

if (!(errno == EAGAIN  || EWOULDBLOCK     || 
      errno == EINTR   || errno == ENOSPC ||
      errno == ENOBUFS || errno == ENOMEM)) {

这看上去是好了一点,但其实还可以更好。

因为两个原因,我不喜欢这种布局。首先,错误还不是太明显;第二,你不得不插入不少空格去对齐代码。

这时我们就需要采用两种方法去改进这个格式化。第一个方式就是我们每行不要包含超过一个比较:这样就可以让错误更容易发现。比如:

a == 1 &&
b == 2 &&
c      &&
d == 3 &&

第二个办法就是用更合理的方式写&&,||等操作符,比如放在左边对齐。

让我们看看用空格来对齐代码,太沉闷了:

x == a          &&
y == bbbbb      &&
z == cccccccccc &&

但当我们左对齐操作符时,一切就变得更快和更简单:

   x == a
&& y == bbbbb
&& z == cccccccccc

上面的代码虽然有一些奇怪,但你会很快适应它们。

让我们把这两种方式在我们前面的示例代码中合并在一起:

if (!(   errno == EAGAIN
      || EWOULDBLOCK
      || errno == EINTR
      || errno == ENOSPC
      || errno == ENOBUFS
      || errno == ENOMEM)) {

是的,现在代码变长了——但是错误也很容易被发现了。

我同意这看上去有些奇怪,然而,其实我建议使用的是另外一种技术。我已经使用这种技术一年半了,我非常享受它,所以我对我的推荐很有信息。

我从来没有发现代码变长会有什么不好的。所以我曾经这么写这段代码:

const bool error =    errno == EAGAIN
                   || errno == EWOULDBLOCK
                   || errno == EINTR
                   || errno == ENOSPC
                   || errno == ENOBUFS
                   || errno == ENOMEM;
if (!error) {

是不是因为代码变得太长和混乱而有些失望?我同意。所以让我们再使用一个函数!

static bool IsInterestingError(int errno)
{
  return    errno == EAGAIN
         || errno == EWOULDBLOCK
         || errno == EINTR
         || errno == ENOSPC
         || errno == ENOBUFS
         || errno == ENOMEM;
}
....
if (!IsInterestingError(errno)) {

也许你认为我所做的有点太完美主义了。但是我能确信,在复杂的表达式中错误是经常发生的,而我又不能每次都能把它们找出来——它们可能在任何地方,而且它们可能很难去发现(译者注:作者有些内疚的为太长的代码进行辩解)。

这里有另外一个WinDjView项目中的例子:

inline bool IsValidChar(int c)
{
  return c == 0x9 || 0xA || c == 0xD || 
         c >= 0x20 && c <= 0xD7FF ||
         c >= 0xE000 && c <= 0xFFFD || 
         c >= 0x10000 && c <= 0x10FFFF;
}

这个函数只包含了少数几行,但它其中还是有错误的。这个函数也会一直返回true。主要原因还是因为几乎没有对代码进行格式化(译者注:所以很难发现错误),而且维护这些代码很多年的程序员也不会主动去认真的读它们。

让我们把这段代码重构为表格化的,我另外还会增加一些括号:

inline bool IsValidChar(int c)
{
  return
       c == 0x9
    || 0xA
    || c == 0xD
    || (c >= 0x20    && c <= 0xD7FF)
    || (c >= 0xE000  && c <= 0xFFFD)
    || (c >= 0x10000 && c <= 0x10FFFF);
}

你不必把你的代码格式化成和我建议的一模一样。这一篇文章的目的是让你注意在混乱的代码中的输入错误。通过吧代码表格化,你能避免很多愚蠢的输入错误,真的很有效。所以,我希望这篇文章能帮到你。

注意
坦白说,我不得不警告你,表格化的格式化代码某些时候也会引起伤害。看看下面这个例子:

inline 
void elxLuminocity(const PixelRGBi& iPixel,
                   LuminanceCell< PixelRGBi >& oCell)
{
  oCell._luminance = 2220*iPixel._red +
                     7067*iPixel._blue +
                     0713*iPixel._green;
  oCell._pixel = iPixel;
}

这是从eLync SDK项目中拿出的代码。程序员想对齐代码,所以他在713前增加了0。不幸的的是,他忘记了以0为开头的数字一般意味着这个数字是8进制的(译者注:在C系语言中,这个定义很普遍)。

字符串数组
我想我已经把表格化的代码格式化表述的已经够清楚了,但我最好还是多给些例子。让我们再多看一个例子。顺便说一下,表格化的代码格式化不光可以用在条件判断上,还可以用在一个语言的其他各种构造上。

下面这段代码是Asterisk项目里的。PVS-Studio诊断的错误说明为:V653 A suspicious string consisting of two parts is used for array initialization. It is possible that a comma is missing. Consider inspecting this literal: “KW_INCLUDES” “KW_JUMP”(译者注:大意是说有一个字符串很可疑,很可能是其实是两个字符串,只是缺少了逗号,可以的字符串是“KW_INCLUDES”和“KW_JUMP”)。

static char *token_equivs1[] =
{
  ....
  "KW_IF",
  "KW_IGNOREPAT",
  "KW_INCLUDES"
  "KW_JUMP",
  "KW_MACRO",
  "KW_PATTERN",
  ....
};

这里其实有一个输入错误——一个逗号被遗漏了。结果两个完全不同的字符串被整合成了一个,比如,我们实际得到的是:

  ....
  "KW_INCLUDESKW_JUMP",
  ....

如果写这段代码的程序员使用了表格化格式化,那么这个错误是可以避免的。看下面的代码,如果逗号遗漏了,那将会很容易被发现:

static char *token_equivs1[] =
{
  ....
  "KW_IF"        ,
  "KW_IGNOREPAT" ,
  "KW_INCLUDES"  ,
  "KW_JUMP"      ,
  "KW_MACRO"     ,
  "KW_PATTERN"   ,
  ....
};

和前面提到的类似,请注意,如果我们把分割符号放在右边(这里是一个逗号),你不得不增加很多空格,这有点麻烦。而且,当我们有一个新的更长的行要加入时,我们不得不要重新进行表格格式化,这台不方便了。

这也是我们为何我再次建议使用如下方式进行表格化格式化:

static char *token_equivs1[] =
{
  ....
  , "KW_IF"
  , "KW_IGNOREPAT"
  , "KW_INCLUDES"
  , "KW_JUMP"
  , "KW_MACRO"
  , "KW_PATTERN"
  ....
};

现在依旧可以很容易的找到一楼的逗号,而且我们还不需要很多空格——代码也看上去很漂亮和直观。也许这种格式化的方式不是太常见,但你能很快适应它——亲自尝试一下吧。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值