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

翻译 2017年01月03日 20:42:29

翻译《有关编程、重构及其他的终极问题?》——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"
  ....
};

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

相关文章推荐

翻译《有关编程、重构及其他的终极问题?》——1. 别把编译器的事给做了

所以,我的建议是——写简单且易读的代码。这里有个规则:简单的代码通常就是正确的代码。不要尝试去做编译器的活——比如去展开循环。编译器无须你的帮助,就可以把绝大多数事情做好。这样的人工优化工作一般只有在...

“汉语编程”是解决安全问题的终极之路?

“汉语编程”是解决安全问题的终极之路? 出处:汉语编程BLOG 文: CIOAge 评论( 0 )条 论坛 博客 导读:眼下,无论多么高级的黑客破密软件,也很难以英文破密的方式来破解中文加...

网络编程(13)—— 利用信号处理函数signal和sigaction销毁僵尸进程

linux中的信号处理类似于windows中的消息处理,原理是利用回调函数进行信号处理器和信号的关联。 一、signal函数 signal函数的原型如下: #include typedef vo...

黑马程序员-Java基础总结13——网络编程

网络编程 ------- android培训、java培训、期待与您交流! ----------  一、网络编程概述 1、网络模型: OSI参考模型 : Open System Inte...

HTML5 脚本编程——JavaScript高级程序设计笔记(13)

第16章 HTML5脚本编程 跨文档消息传送(cross-document messaging),有时候简称为XDM,指的是在来自不同域的页面间 传递消息。例如,www.wrox.com域中的页面与...

《Java编程思想》学习笔记13——Java new I/O(二)

1.数据转换: 使用字节缓冲区时,向字节缓冲区写入或者从字节缓冲区读取内容时,如果内容是字符类型,写入时指定字符编码集对内容进行编码,读取时指定字节编码集对内容解码。 每次从字节缓冲区中读取一...

《Java编程思想》学习笔记13——Java new I/O(二)

1.数据转换: 使用字节缓冲区时,向字节缓冲区写入或者从字节缓冲区读取内容时,如果内容是字符类型,写入时指定字符编码集对内容进行编码,读取时指定字节编码集对内容解码。 每次从字节缓冲区中读取一个字...

《VC++深入详解》学习笔记[13]——第16章 线程同步与异步套接字编程

第16章 线程同步与异步套接字编程 1.事件对象        事件对象与互斥对象一样也属于内核对象。事件对象有两种不同的类型:        ①人工重置的事件对象:当人工重...

网页编程基础第四章知识点总结——表格

网页编程基础第四章知识点总结——表格 知识点预览 表格模型 与表格有关的元素 举例——创建表格 caption表格的标注 行分组 定义列 列分组 跨行和跨列   表格模型   组织结构化的信息 ...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)