带注释的简短代码与无注释的较长且易于理解的代码-哪个是首选?


18

有时可以用两种方式编写算法:

  • 一种简短的幻想方式;要么
  • 更长的,易于理解的方式。

例如,这是一种将字符串复制sourcedestC中的更长,更简单的方法:

*dest = *source;
while (*source != '\0') {
    source++;
    dest++;
    *dest = *source;
} (true);

这是一种简短的幻想方式。

// Copy string source to dest
while (*dest++ = *source++);

我一直听到并读过,应该避免使用花哨的代码,我倾向于同意。但是,如果我们考虑到评论该怎么办?假定像上面的示例一样,我们有一个未注释的,较长的并且据说更易于理解的代码,以及一个注释良好的,简短的,精美的代码?非花哨的代码还是首选吗?

编辑:许多人对变量名发表了评论,所以我修改了示例代码,以免在优先选择其他变量时使它成为一个因素。我试图在第一个示例中删除双重分配,但这只会使代码的可读性降低。

也许这不是最好的示例,因为许多人发现“花哨的”代码比较长的代码更具可读性和可理解性。这个想法是要有一个更长的代码,比一个简短但复杂的代码更容易理解。

EDIT2:这是我从SO获得的一个新例子:

评论版本:

//direct formula for xoring all numbers from 1 to N
int Sum = (N & (N % 2 ? 0 : ~0) | ( ((N & 2)>>1) ^ (N & 1) ) );

非注释长版:

int Sum = 0;
for (int i = 1; i < N; ++i)
{
   Sum ^= i; //or Sum = Sum ^ i;
}

2
@gablin:“好”是一个词,但这是一个形容词,其含义与“好”有所不同,并且不再被广泛使用(至少在美国是这样)。“好”的副词形式是“好”,如“注释良好的代码”。
John M Gant

@约翰:是的,当然。我已经相应地更新了问题。谢谢。
gablin

2
简短的方式是“花式”是什么?那是一本C代码的教科书,任何声称知道C的人都应该能够阅读而没有任何问题。简洁并不意味着“幻想”。
JBR威尔金森

@JBRWilkinson:正如我在问题的修改部分中所说的那样,该示例不是一个好例子,因为“花哨”版本显然不是那么花哨。目的是要有一种简短但不易理解的注释方式,然后是一种更长但更易理解的无注释方式。
gablin

@gablin:您的编辑会影响当前的答案。
Maniero

Answers:


28

我通常更喜欢将精美代码提取到自己的方法中。

它的方法名称应该是使事情变得清晰所需的全部内容,而不是注释花哨的代码。

char *copy_string(char *s, const char *t) {    
    while (*s++ = *t++); 
    return s;
}

3
好点。排除独立代码比注释好得多。编译器知道如何在需要时进行内联。
dbkk

是的,这是使评论过时的好方法。但是尽管如此,相对于较长的代码,您是否总是喜欢精美的代码?
gablin 2010年

一般来说,是的。我觉得简短的命名方法使代码更易于阅读和维护。自身方法中考虑的复杂性使重用更加容易访问和明显。总是有例外,最终它总是取决于代码。
孟格斯·庞

当然,您的函数应该命名为“ strcpy”吗?
JBR威尔金森

那段代码有错误。返回什么?应该退还什么?只需将其设为无效方法即可;)
Christian Mann 2010年

10

我全都支持更长的版本。短代码版本的问题,除了某些程序员更难以阅读之外,还在于大多数情况下,仅通过查看代码就很难发现错误。

我有一个真实的例子。在我们的产品中,我们具有以下代码片段:

if (++someCounter < MAX_VALUE) {
    // Do something that has to be done only MAX_VALUE times
}

乍一看,这段代码看起来完全合理,但是经过很长一段时间后,该计数器溢出并破坏了条件(在现实生活中,我们使用OOM粉碎了客户的生产系统)。这段代码不太“复杂”,但很明显,它可以执行应做的事情:

if (someCounter < MAX_VALUE) {
    ++someCounter;
    // Do whatever it is we came here for
}

您可能会说,编写此代码的开发人员还不够好(这是不对的,他是一个非常聪明的人),但是我认为,如果您的编码技术要求您成为超级傻瓜编程高手,为了使您的代码正确,您做错了什么。为您着想,以及您之后必须维护此代码的任何人(他们可能不如您那样聪明),您的代码应尽可能简单。

所以-我都是为了简单明了。

编辑:哦,关于评论-没关系。无论如何,许多人不会阅读注释(http://www.javaspecialists.co.za/archive/Issue039.html),而那些不加注释就无法理解您的代码的人,对他们的理解将不够好,以至于他们可以维护它。目的是帮助人们看到某些代码是“正确的”,注释对此无济于事。


1
“压碎了客户的生产系统”-相当糟糕:-S

“短代码版本的问题,除了某些程序员更难以阅读之外” –任何一段代码对于“某些程序员”来说都难以阅读。您只需要找到足够愚蠢的程序员即可。不要将代码精简到最低分母。
quant_dev

@quant_dev:相反,“调试是一开始编写代码的两倍。因此,如果您尽可能聪明地编写代码,那么就定义而言,您不够聪明,无法对其进行调试。” —布莱恩·W
·克尼根

@MichaelBorgwardt我不会将Kernighan的格言盲目地应用于每种可能的情况。OPs示例是一个“尽可能巧妙”(或接近它)编写的函数,但是如果需要任何调试,它应该很容易调试。另一方面,复杂的按位纠缠Oneliners可能非常聪明,但是调试起来肯定会更加困难。(此外:Kernighan假设编码技能=调试技能。不一定是这样。我已经成功调试了我本来无法编写的代码。)
quant_dev 2012年

6

我通常希望使用更长的版本。我想到两个主要原因:

  • 更多的人可能会更轻松地理解它(假设它不是此字符串副本之类的“标准”示例)。
  • 可以在单个语句上设置断点,并通过调试器逐步执行。

为了两全其美,请将代码包装在一个函数中,该函数的名称为您注释:

void copy_string(char *s, char *t)
{
    *s = *t;
    while (*t != '\0') {
        t++;
        s++;
        *s = *t;
    }
}

唯一不这样做的原因是,如果性能是一个问题,并且性能分析确实表明功能开销很大。


1
那不是内联的目的吗?
Antsan,2010年

确实,尽管内联有其自身的问题,例如必须在头文件中公开函数主体。现代编译器不是在合理的地方自动为您内联东西吗?
Paul Stephenson

4

最清楚的是什么。通常,这是一个紧凑且易于理解的代码片段,不需要注释,就像第二个示例一样。

通常,较短的代码段更易于理解,因为读者一次也不必费神。显然,当代码过于混淆时有一个限制,我并不是说应该修剪提高清晰度的空白。

注释永远不要声明代码中显而易见的任何内容,而只是使读者两次阅读相同的内容。对我来说,第一个摘要需要澄清。开发人员为什么不使用do...while循环来删除重复的*s = *t;作业?我必须解析更多代码才能意识到它正在实现字符串副本。在这段较长的代码上,注释比在较短的代码上更有用。

关于第二个代码段的注释几乎是多余的,因为while循环实际上是惯用的,但是它确实说了代码在比代码本身更高的层次上所做的工作,这使它成为有用的注释。


开发人员(me)并没有do ... while仅仅因为我在写问题时没有想到它而使用过。感谢您指出了这一点。^^第二个片段对您来说可能很明显,但对我而言当然不是。
gablin

4

问题在于没有明确的定义,short fancy code因为它很大程度上取决于程序员的水平。虽然有些人在理解while(*s++ = *t++);表达式时没有问题,但其他人会。

就我个人而言,我认为它具有while(*s++ = *t++);很好的可读性,而较长的则很难阅读。其他人可能不同意。

无论如何,这只是使用常识的问题。一定要优先考虑可读性,但是有一点会说,较长的代码变得更长时,其可读性就会降低。从我的经验来看,细节常常会降低可读性。


让我感到遗憾的是,许多答案的主题是“开发人员可能不认识用C编写strcpy()的标准方式”
AShelly 2010年

它不应该-并不意味着不再有优秀的程序员,而是a)我们不再教C了,并且b)C的强项也是它的弱点-在那个循环中对于在C中实践过的人来说,这也是很神秘的,并且在一个试图编写安全代码的世界中,这隐含着您在C中可以做什么的含义,这很可怕
Murph 2010年

因此,我们发现自己在日常工作中面临另一个挑战。需要猜测我们直接同行的语言理解的一般水平,以便我们的代码审查将保持建设性。不陷入战斗的是“正确的”,更明确的意图表达。
frogstarr78

2

我必须不同意这里的大多数其他答案-我认为(至少在这种情况下)较短的代码更好。与您的主张相反,较长的代码至少对于读者而言“ 并不容易”。如果有的话,即使它使代码更难以理解和/或确保正确的操作,您似乎也想延长它的时间。

尤其是具有外循环的字符串的第一个字节的分配,从分配分开的其他字节,手段之一必须小心阅读,以确保所有字节正确地复制。较短版本中的基本操作顺序更容易验证。唯一真正的问题是格式设置-当您有一个故意为空的循环正文时,最好将其弄清楚,例如:

while (*dest++ = *source++)
    ;

甚至:

while (*dest++ = *source++)
    ; /* no body */

有些人更喜欢使用括号:

while (*dest++ = *source++)
    {}

不管您希望使用哪种确切格式,都已经对诸如以下内容犯了足够的错误:

if (x>y);
     x = z;

...很重要的一点是要确保1)显而易见,任何流控制实际上控制着什么,以及2)显而易见,代码是在知道由其控制的情况下编写的,因此读它的人不会浪费时间去尝试弄清楚他们是否刚刚发现了错误。


是的,回想一下这个特定示例,“花哨”版本可能比较长版本更具可读性。如果我能想到一个更好的例子,我会做,但目前无法。但是我并没有“延长自己的时间”来延长它的长度-至少在最初,这就是我编写字符串副本的方式。这可能不是最好的方法,但是并没有故意延长它的使用时间。
gablin

2
Keep it simple, stupid!

确保任何其他程序员都可以理解您的代码的功能- 乍一看甚至更好(这是好名字和注释出现的地方)。

以(也许是不必要的和过早的)优化的名义花哨的纠结功夫和内联汇编都是很棒的,但是当您两个月后遇到一个bug 时,即使您甚至不理解您编写的代码。什么意思呢?您会浪费时间和精力。

在这些情况下,我经常提到PythonZen。尤其是:

Explicit is better than implicit.
Simple is better than complex.

1

不要在生产软件中编写奇特的代码。我知道能够实际编写它感觉很好,而且它更短。编写奇特的代码会大大增加“ WTF /分钟”值,换句话说,会降低质量。

替代文字


1

当然,这完全取决于环境。但总的来说,我发现这是一个很好的经验法则:

调试的难度是一开始编写代码的两倍。因此,如果您尽可能聪明地编写代码,那么根据定义,您就不够聪明,无法对其进行调试。

〜布赖恩·克尼根(Brian Kernighan)


比更紧凑的KISS首字母缩略词更长,更有意义,甚至更优雅;希望具有讽刺意味的地方不要丢失; p
violet313

0

以我的经验,就短代码而言,最大的胜利往往是使用递归而不是迭代。乍看之下,这也是最难理解的事情之一,除非您使用的语言总是递归的。对于它提供的优雅和快速的开发,我还是会喜欢它,但是我尝试确保它对将来的维护者或我自己的未来看起来是不透明的,并附有详细的评论。


0

我开始不再相信评论。通常,注释不会在代码更新时更新,并且注释已过时,或者表示来自客户/管理部门的不再相关的规则。

在某些情况下,注释甚至与描述的代码都不匹配,这已经到了关键的程度。

如果我有短/幻想和之间选择较长/更容易理解,然后我总是选择后者,除非有真正的好理由。


0

可读性和可维护性是关键,而第二个示例(即更长的示例)则更多。但是为什么要限制自己有两个选择呢?甚至更长的代码也太复杂(IMHO),以至于无法进一步注释。我将其放在具有合适的Javadoc(或任何其他名称)和合适的方法名称的方法中。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.