明确声明变量而没有“多”使用以支持可读性的好习惯?

时间:2015-06-12 13:03:00

标签: c coding-style code-structure

所以,这是另一个'好'的编程实践问题。我做了一些搜索,但这样的事情通常很难用几句话来定义。

问题:从专业角度来看,保持代码简化和简短(不一定更高效)或明确定义实例变量只是为了分配它们并立即返回是更好的编程实践吗?例如:

FILE * foo(){
    FILE * retVal; 
    foo2(); 
    retVal = foobar(); 
    return retVal;
}

从上面我们可以立即看到foobar返回FILE *。因此,从这种编程风格我们可以更快地提取重要信息。与以下情况相比,这是真的:

FILE * foo(){
    foo2(); 
    return foobar(); 
}

当然,这完成了同样的事情。但是,必须更深入地找到相同的信息。我倾向于赞成后一种编程风格,因为它看起来更好。由于这个程序运行的性质,我怀疑使用任何一个程序都可以立即获得性能提升,因为无论哪种选择仍然需要内存 - 不同之处在于用户或编译器是否分配它。

另一个保持代码简洁明了的例子:

int foo(){
    int i = 0;     
    while(foobar())
        i++:    
    return i;
}

TL:DR问题>>是否更好地明确地显示正在做什么,或者是否可行,支持简洁和简洁,缩短完成相同任务但不一定提供性能增益的代码?

6 个答案:

答案 0 :(得分:3)

由于您预测的原因,准确和缩短代码之间的选择是主观的。 在维护方面,我们大多数人都更喜欢简短的代码。 即使学习者更喜欢简短的代码,尽管这与他们所希望的相反。

C设计为人类可读的,并且尽可能少地编译。它是程序性的,非常华丽。编码的另一个原因是支持可读性和时间消耗。

您在示例中提供的两种方式都生成完全相同的ASM代码(请注意-O)。

            .Ltext0:
                    .globl  foobar
                foobar:
                .LFB13:
                    .cfi_startproc
0000 B8000000       movl    $0, %eax
     00
0005 C3             ret
                    .cfi_endproc
                .LFE13:
                    .section    .rodata.str1.1,"aMS",@progbits,1
                .LC0:
0000 666F6F32       .string "foo2 called"
     2063616C 
     6C656400 
                    .text
                    .globl  foo2
                foo2:
                .LFB14:
                    .cfi_startproc
0006 4883EC08       subq    $8, %rsp
                    .cfi_def_cfa_offset 16
000a BF000000       movl    $.LC0, %edi
     00
000f E8000000       call    puts
     00
                .LVL0:
0014 B8000000       movl    $0, %eax
     00
0019 4883C408       addq    $8, %rsp
                    .cfi_def_cfa_offset 8
001d C3             ret
                    .cfi_endproc
                .LFE14:
                    .globl  foo
                foo:
                .LFB15:
                    .cfi_startproc
001e 4883EC08       subq    $8, %rsp
                    .cfi_def_cfa_offset 16
0022 B8000000       movl    $0, %eax
     00
0027 E8000000       call    foo2
     00
                .LVL1:
002c B8000000       movl    $0, %eax
     00
0031 4883C408       addq    $8, %rsp
                    .cfi_def_cfa_offset 8
0035 C3             ret
                    .cfi_endproc
                .LFE15:
                    .globl  main
                main:
                .LFB16:
                    .cfi_startproc
0036 4883EC08       subq    $8, %rsp
                    .cfi_def_cfa_offset 16
                .LBB8:
                .LBB9:
003a B8000000       movl    $0, %eax
     00
003f E8000000       call    foo2
     00
                .LVL2:
                .LBE9:
                .LBE8:
0044 B8000000       movl    $0, %eax
     00
0049 4883C408       addq    $8, %rsp
                    .cfi_def_cfa_offset 8
004d C3             ret
                    .cfi_endproc
                .LFE16:
                .Letext0:

..在您的简约,无关紧要的brief wayterse way的回复中。

考虑到这一点,我可以自由地说,如果你只是正确地应用它们是最好的。这是......尽可能简短明了,

/* COMMENTED */

答案 1 :(得分:2)

  

免责声明:以下任何内容均不是来自任何标准

通常,在启用适当优化的情况下,编译器将优化大部分冗余 dead 部分,并使二进制文件尽可能高效。

记住这一点,建议编写易于人类理解的代码。将优化部分(主要)留给编译器。

编写人类可以理解的代码,使其成为

  • 其他人更容易接受
  • 易于维护
  • 易于调试
  • 最后但并非最不重要,A lifesaver for you PUN FUN意图)

答案 2 :(得分:1)

具有可读性,并且具有可调试性。

我会写你的例子(顺便说一下,它没有编译)

FILE* foo ()
{
    foo2(); 
    FILE* retVal = foobar(); 
    return retVal;
}

这样,如果我需要调试,我可以在return语句上设置一个断点,看看retVal是什么。避免过于复杂的表达式并使用中间变量通常也是一个好主意。首先,为了便于调试,其次,为了便于阅读。

答案 3 :(得分:0)

我反对这种风格,虽然我知道很多人都是为了调试而做的。

在典型的调试器中,可以看作是一个谬误,可能很难看到立即返回的值。

如果你觉得你想要这样做,我推荐两件非常高的东西:

  • 使用C99,这样你就可以迟到了。
  • 使用const

所以我会写foo()例子,如果我不得不:

FILE * foo(void)
{
  foo2();
  FILE * const retVal = foobar();
  return retVal;
}

请注意,const不能位于星号(const FILE *retVal = ...)的左侧,因为它会生成类型const FILE *;我们想要的是一个常量指针,而不是一个指向常量的指针。

const的目的是向人类读者说“我在这里命名这个值,但这并不表示我将会捣乱”。

答案 4 :(得分:0)

我同意代码具有可读性。但是,我不同意第一个更容易阅读甚至维护。

  • 可读性:更多代码可供阅读和理解。虽然这个例子可能并不那么难,但它可能适用于更复杂的类型。
  • 可维护性:如果更改了返回类型,则还必须更改retval声明。

许多编码样式需要在块开头定义的变量。其中一些甚至只允许在功能级别开始。所以你在函数声明附近声明了变量,远离返回。

即使这是允许的:你获得了什么?它也可能在返回时隐藏强制,因为编译器也会抱怨错误的返回类型 - 如果你启用大多数警告将增加代码质量(如果认真对待)。

答案 5 :(得分:0)

简短版

只有在添加非显而易见的信息时才引入新变量。通常情况是,变量用于替换有点复杂的表达式

长版

与所有事情一样,这取决于。我认为,解决这个问题的最佳方法是对情况进行成本效益分析。

使用中间变量的成本(纯粹从代码质量/理解的角度来看,我很确定任何体面的现代编译器都会优化那些)我会说解析变量的努力,理解上下文中的定义,更重要的是将后者对该变量的使用与程序的工作模型联系起来,无论是谁在阅读你的代码都在他的脑海中。

好处是通过引入更多信息,新元素可以帮助读者形成更简单的代码库心理模型,或者更精确的模型。对于新的变量声明,大多数信息都包含在类型或名称中。

例如考虑以下两个例子:

if(isSocial)
     return map[*std::min(d.begin(),d.end())].first;
else
     return map[*std::max(d.begin(),d.end())].first;
return idealAge;


if(isSocial)
     int closestPersonAge = map[*std::min(d.begin(),d.end())].first;
     idealAge = closestPersonAge
else
     int futhestPersonAge = map[*std::max(d.begin(),d.end())].first;
     idealAge = futhestPersonAge
return idealAge;

在第一个例子中,你的读者需要了解std :: min的作用,'d'和'map'是什么,它们的类型是什么,map的元素类型是什么...... 在第二个例子中,通过提供有意义的变量名称,您基本上可以使读者不必理解计算,从而使他能够拥有更简单的代码心智模型,同时大致保留相同数量的重要信息。

现在将其与:

进行比较
int personAge = person.age();
return personAge;

在这种情况下,我认为让personAge不添加任何meanifull信息(变量和方法名称传达相同的信息),因此不会以任何方式真正帮助读者。