c语言 - 明确声明变量而不使用'much'有利于可读性的良好做法?



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

对于问题:从专业的角度来看,是更好的编程实践是保持代码精简和简短(不一定更有效)还是显式定义实例变量只是为了分配它们并立即返回?例如:

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 问题>> 是明确显示正在执行的操作更好,还是为了简洁明了,缩短完成相同任务但不一定提供性能提升的代码?

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

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:

..以你极简主义、微不足道的简短方式和简洁的方式回应。


考虑到这一点,我可以自由地说,最好是简单地正确应用两者。那就是..尽可能简短明了,

/* COMMENTED */

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

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

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

编写人类更容易理解的代码,使它

  • 更容易被他人接受
  • 更易于维护
  • 更易于调试
  • 最后但并非最不重要的一点是,您的救星PUN FUN打算)

有可读性,也有可调试性。

我会把你的例子写成(顺便说一下,它不编译)为

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

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

我反对这种风格,尽管我知道许多人这样做是为了调试目的。

典型的调试器中,可以将其视为一个谬误,即可能很难看到立即返回的值。

如果你觉得你想这样做,我非常推荐两件事:

  • 使用 C99,这样您就可以延迟声明。
  • 使用 const .

因此,如果必须,我会将foo()示例编写为:

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

注意const不能放在星号(const FILE *retVal = ...)的左边,因为那样会使类型const FILE *;我们想要的是一个常量指针,而不是一个指向常量事物的指针。

const的目的是对人类读者说"我在这里命名这个值,但这不是我要胡思乱

想的状态"。

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

  • 可读性:需要阅读和理解的更多代码。虽然对于示例来说,这可能并不难,但对于更复杂的类型来说可能并不难。
  • 可维护性:如果更改返回类型,则还必须更改 retval 声明。

许多编码样式需要在块的开头定义变量。他们中的一些人甚至只允许在功能级别开始。因此,您在函数声明附近声明了变量,远离返回值。

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

简短版本

仅在添加不明显的信息时才引入新变量。通常,当变量用于替换有点复杂的表达式时,就是这种情况

长版本

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

使用

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

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

例如,请考虑以下两个示例:

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 不会添加任何有意义的信息(变量和方法名称传达相同的信息),因此不会以任何方式真正帮助读者。

相关内容

  • 没有找到相关文章

最新更新