所以,这是另一个'好'的编程实践问题。我确实搜索了一下,但这样的事情通常很难用几句话来定义。
对于问题:从专业的角度来看,是更好的编程实践是保持代码精简和简短(不一定更有效)还是显式定义实例变量只是为了分配它们并立即返回?例如:
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 不会添加任何有意义的信息(变量和方法名称传达相同的信息),因此不会以任何方式真正帮助读者。