65.9K
CodeProject 正在变化。 阅读更多。
Home

代码风格与可读性

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.23/5 (13投票s)

2022年5月24日

CPOL

8分钟阅读

viewsIcon

24899

代码风格背后的原因,以及我们为什么不应该总是使用“自动格式化工具”

引言

您认为哪段代码更容易阅读,并且能通过格式验证工具?- 我在本文中使用图片,是因为我不想因为 HTML 问题导致行被随意打断。

如果您说第一种选择更具可读性,那么您是对的

如果您说第二种选择更具可读性,那么您和我一样,根据许多新的编码实践,是“大错特错”。我没开玩笑……根据一些人的说法以及他们使用的工具,第一段代码是最具可读性的代码。它为if使用了开闭花括号。它正确地将一个参数对齐到另一个下方,并且由于))) {假设缩进会换行,因此它进行了额外的从右到左的换行,以放置所有缺失的)) {花括号。

第二段代码显然是不可读的,因为if没有使用{}。如果没有{}花括号,人们怎么会知道那里是什么意思?

如果您认为我刚才说的话像个笑话……对我来说,这是一个非常糟糕的笑话,但我看到越来越多的地方使用“自动格式化工具”,试图强加良好的编码风格和质量,但却让易于阅读的代码变得不可接受,而晦涩的代码却变得很好。

未理解问题

我现在的主要不满是,人们完全忽略了编程实践的目的,包括样式。

相反,他们只是制定需要盲目遵循的规则,说这会创建“标准化代码”,而这只是更好、更具可读性。

然而,对计算机来说更容易阅读的东西,对人类来说并不容易阅读。在许多编程语言中,将整个应用程序写在一行上对编译器来说是完全可读的。这根本不符合人类阅读代码的方式。

当它们起作用时,使用格式化工具很棒。拥有一致的代码风格也很棒,当它确实使代码更具可读性时。但是公司强制执行的标准真的让代码更具可读性吗?

我现在再给一个例子。这就是我编写这段虚构代码的方式

然而,我知道很多人会立即抱怨if缺少花括号。此外,由于花括号会浪费太多行,他们会删除空行,甚至方法声明也会以不同的方式使用)。他们最终会写出类似以下的内容

显然,这“更具可读性”且“不易出错”。请注意,在方法声明中,换行符没有放在括号周围。它只是在最后一刻才发生的。

但这还没完。太多人被告知(或不知何故就认为)使用var是个问题,因为我们不知道变量的类型。所以,我们需要明确写出来,如下所示

而且,无论我现在认为这是否可读性差很多,许多人将开始避免声明局部变量,如果类型很混乱的话。奇怪的是,使用var来创建一个局部变量来引用obj.SomeProperty是错误的……但到处编写obj.SomeProperty(这也不能立即显示类型)却不是。所以,他们最终会得到类似以下的内容

而在所有提供的解决方案中,只有第一个解决方案被认为是错误的。所有其他解决方案都被接受为“良好且可读的”。

它们是吗?

花括号问题也是一个从左到右的问题

“总是使用花括号”本身就是一个问题。然而,我认为这实际上是基于对主要问题的一个巨大误解:我们是从左到右阅读的。

当谈论总是使用花括号仅在必要时使用花括号时,我的观点是,如果开花括号放在行的末尾,我们就必须始终使用它们,因为我们不知道读者在假设存在闭花括号之前是否会阅读整行。所以,我们需要在每个可能使用花括号的语句(ifwhile等)上始终使用{}

另一方面,如果它们放在自己的行上(并正确缩进),我们就可以在只有内部单行语句时避免使用它们。事实上,我总是将{读作“开始多个”,而用“开始多个”来只有一个内部语句是没有意义的。

我不是在争论总是使用花括号还是不使用花括号,我想通过这两种风格展示的是,如果我们知道是这两种中的一种,我们只需要阅读一半的行就能很好地了解发生了什么。

然而,在我的某些例子中,情况并非如此。再看一遍

vs

在第一种情况下,我们看到一个带有方法调用的if,一个参数的开头,我们不知道为什么在DoSomethingHere之前似乎有一些空行。

在第二种情况下,因为我们只使用缩进来显示事物之间的关系(然后在一个单独的行上显示组的结束),我们可以更容易地假设关系存在。当然,如果有人故意错误地缩进,我们可能会出错,但假设情况并非如此,并且缩进是正确的,我们只需查看行的开头就能更好地了解发生了什么。

不幸的是,许多工具认为将项目逐行缩进“更容易”阅读,这意味着我们不能使用制表符,否则一切都会很容易出错(如果使用不重新格式化代码的重构工具将方法重命名为不同的长度,也会发生这种情况)。

许多工具只会尽可能晚地换行,而不是在最合适的时候换行,以允许“更紧凑”的代码。但这可能意味着下一行代码是内部调用的延续,而不是主调用的延续……如果主调用也跨越多行,则需要重新格式化所有内容。

然后,由于一些代码实践意味着代码跨越多行,语言中会添加新资源来避免跨越多行……对我来说,这是一个真正的问题,因为人们正在编写晦涩的代码来试图避免一些强制性的实践。

为了避免多行的晦涩代码

喜欢新的语言构造允许单行代码,而某些编码实践却将简单的事情扩展到4行或更多行。然后,开发人员会想出一些技巧来避免多行,例如

这只是一行代码,但它做了三件事:检查inputArgument,然后抛出异常或将inputArgument分配给“丢弃”。嗯,丢弃的使用实际上意味着它只做了两件事,但代码是这样写的,因为?? throw只在表达式(如赋值表达式)中起作用。这个技巧被用来避免编写“不可接受且不可读”的代码。

我非常确定,对于大多数英语读者,甚至对于那些还没有习惯新功能(如?? throw)的 C# 开发者来说,第二种选择应该很容易阅读,并且不像其他可接受的解决方案那样跨越多行。

然而,“代码风格是为了帮助。”

其他后果

代码风格实际上还有其他后果,除了代码易于阅读之外。例如,一些代码覆盖率工具只评估“已触及的代码行”。也就是说,如果我只为非null值编写单元测试,这段代码仍将具有 100% 的覆盖率

然而,其他两种替代方案只有在传入null时才具有 100% 的覆盖率。

我不会讨论为输入参数验证设置代码覆盖率是好是坏。我可以写一篇完整的关于代码覆盖率的文章。关键是,代码风格会影响某些代码覆盖率评估的结果。同样,根据调试器的不同,使用单行代码将throw语句放在断点上会非常困难,而在其他样式中,这并不是问题。

因此,作为一般规则,我认为强制执行一种可以“通过”使用更晦涩的代码的工具生成的格式是一个坏主意。

然而,我们可以尝试避免诸如

  • 在单个语句中执行多个方法调用——这将更自然地避免将语句拆分成多行,从而总体上更容易理解。
  • 使用更多的局部变量,即使使用var,而不是执行像obj.SomeProperty.Item1然后obj.SomeProperty.Item2这样的调用,仅仅是为了避免一次又一次地输入完整的类型。
  • 只需缩进和取消缩进事物,不要将项目逐行对齐,因为这很容易出错,如果使用制表符或重命名内容。
  • 如果您的语句需要跨越多行,请在第一个有意义的时刻换行,而不是最后一个。语句的第二行应该是第一行左侧内容的延续,而不是行末我们尚未看到的可能内部调用的延续。

而且,使用常识而不是僵化的规则肯定会更好。

历史

  • 2022年5月24日:初版
© . All rights reserved.