语境
我最近对产生更好的格式化代码感兴趣。更好的意思是“遵循足够多的人认可的规则,将其视为一种良好的做法”(当然,因为永远不会有一种独特的“最佳”编码方式)。
如今,我主要使用Ruby进行编码,因此我开始使用linter(Rubocop)为我提供一些有关代码“质量”的信息(此“质量”由社区驱动的项目ruby-style-guide定义)。
请注意,我将使用“质量”作为“质量格式化的”,与其说是对代码的效率,即使在某些情况下,代码效率实际上是由代码是怎么写的影响。
无论如何,做完所有这些事情,我意识到(或至少记得)一些事情:
- 一些语言(最著名的是Python,Ruby等)允许编写出色的一线代码
- 遵循一些代码准则,可以使代码大大缩短,但仍然非常清晰
- 但是,过于严格地遵循这些准则会使代码不太清晰/不易阅读
- 该代码几乎可以完全遵守某些准则,但质量仍然很差
- 代码的可读性主要是主观的(如“我发现对开发人员完全不清楚的内容”)
这些只是观察,并非绝对的规则。您还将注意到,此时代码的可读性和遵循的准则似乎无关紧要,但此处的准则是一种缩小重写代码块的方式的方法。
现在,举一些例子,使所有这些变得更加清楚。
例子
我们来看一个简单的用例:我们有一个带有“ User
”模型的应用程序。用户具有可选firstname
和surname
必填email
地址。
我想编写一个方法“ name
”,firstname + surname
如果至少存在用户firstname
或,则将返回该用户的名称(),如果没有,则返回surname
其email
后备值。
我还希望此方法采用“ use_email
”作为参数(布尔值),从而允许将用户电子邮件用作后备值。此“ use_email
”参数应默认(如果未传递)为“ true
”。
用Ruby编写最简单的方法是:
def name(use_email = true)
# If firstname and surname are both blank (empty string or undefined)
# and we can use the email...
if (firstname.blank? && surname.blank?) && use_email
# ... then, return the email
return email
else
# ... else, concatenate the firstname and surname...
name = "#{firstname} #{surname}"
# ... and return the result striped from leading and trailing spaces
return name.strip
end
end
这段代码是最简单易懂的方式。即使对于不“说” Ruby的人。
现在,让我们尝试将其缩短:
def name(use_email = true)
# 'if' condition is used as a guard clause instead of a conditional block
return email if (firstname.blank? && surname.blank?) && use_email
# Use of 'return' makes 'else' useless anyway
name = "#{firstname} #{surname}"
return name.strip
end
它更短,但即使不容易理解,也仍然易于理解(guard子句比条件块更自然阅读)。Guard子句还使其更符合我使用的准则,因此在这里是双赢的。我们还降低了缩进级别。
现在,让我们使用一些Ruby魔术使它更短:
def name(use_email = true)
return email if (firstname.blank? && surname.blank?) && use_email
# Ruby can return the last called value, making 'return' useless
# and we can apply strip directly to our string, no need to store it
"#{firstname} #{surname}".strip
end
甚至更短,并且完全遵循准则……但是由于缺乏return语句,因此不清楚得多,这对于那些不熟悉这种做法的人来说有点令人困惑。
在这里,我们可以开始提问:真的值得吗?我们应该说“不,使其可读并添加' return
'”(知道这将不遵守准则)。还是应该说:“很好,这是Ruby的方法,学习该死的语言!”?
如果我们选择选项B,那么为什么不做得更短:
def name(use_email = true)
(email if (firstname.blank? && surname.blank?) && use_email) || "#{firstname} #{surname}".strip
end
这就是单线!当然,它更短...在这里,我们利用Ruby将返回一个值或另一个值取决于定义的一个事实的事实(因为电子邮件将在与以前相同的条件下定义)。
我们也可以这样写:
def name(use_email = true)
(email if [firstname, surname].all?(&:blank?) && use_email) || "#{firstname} #{surname}".strip
end
简短,不是很难读(我的意思是,我们都已经看到了丑陋的单行代码会是什么样子),好的Ruby,它符合我使用的准则...但是,与第一种编写方式相比它,它不容易阅读和理解。我们也可以说这行太长(超过80个字符)。
题
一些代码示例可以表明,在“全尺寸”代码及其许多降级版本(低至著名的单线)之间进行选择可能很困难,因为如我们所见,单线可能并不那么可怕,但不过,就可读性而言,没有什么比“完整”代码更好的了……
所以这才是真正的问题:在哪里停下来?什么时候短,足够短?如何知道代码何时变得“太短”且可读性差(请记住,这是很主观的)?甚至更多:当我感到满意时,如何始终进行相应的编码并避免将单行代码与“完整”的代码块混合使用?
TL; DR
这里的主要问题是:当要在“长而清晰,可读和可理解的代码块”和“强大,短而难于阅读/理解的一线代码”之间进行选择时,要知道这两个是最重要的刻度的底部,而不是两个唯一的选择:如何定义“足够清晰”和“不够清晰”之间的边界?
主要问题不是经典的“单线与可读性:哪一种更好?” 但是“如何找到两者之间的平衡?”
编辑1
代码示例中的注释旨在“忽略”,此处用于阐明正在发生的事情,但在评估代码的可读性时不予考虑。
return
添加关键字。这七个字符使我眼神清晰。
[firstname,surname,!use_email].all?(&:blank?) ? email : "#{firstname} #{surname}".strip
...,因为false.blank?
返回true,三元运算符可以为您节省一些字符... \ _(ツ)_ /
return
关键字应该添加什么清晰度?它不提供任何信息。真是混乱。