识别代码中的缺陷——名称、函数和注释 给出好的名称! 函数 注释 如果我必须注意所有这些规则,我将如何编写代码?

2025-05-28

识别代码中的污垢——名称、函数和注释

給出好名字吧!

功能

评论

如果我必须注意所有这些规则,我将如何编写代码?

代码不太干净的问题在于,随着我们维护它,代码会变得越来越复杂,并且对其理解也会受到影响。

我所说的复杂代码,不是指业务规则,而是指理解和代码易读性的复杂性。我指的是在没有任何上下文的情况下,花一些时间去理解什么是函数p,或者函数做什么。它读什么?什么是函数?啊啊啊啊 /o\read(p, i)pi

当我们花费更多时间去理解旧代码,而不是思考新代码的实现时,这可能预示着某些方面可以改进。在一项任务上花费的时间比我们预想的要多,会让我们感到沮丧。

当代码难以维护时,我们便不再关心其质量,结果就是写出“糟糕”的代码。我们让代码变得越来越复杂,而代码越复杂,维护起来就越困难。我们的生产力几乎降为零,如果不在此过程中再开发三个代码,修复一个问题就会变得非常危险。我们变得不敢碰那些已经“神奇地”运行的代码(因为我们显然不知道发生了什么)。

其后果是代码难以维护,最终导致公司倒闭。我曾经见过这种情况。当时唯一的产品就有几个bug,修复一个bug就必然会引发更多bug,客户满意度已经到了不想再使用该产品的地步,最终公司倒闭了。

如果我们知道这样做有害,为什么还要这样做?

匆忙。

通常,我们的期限非常紧迫,以至于我们无法很好地完成任务而跳到下一个任务。

我想避免这种情况发生。我该怎么做?

这是关于代码整洁系列的第一部分。第一部分主要讨论如何命名、如何编写更好的函数以及一些关于注释的内容。

接下来,我打算讨论格式、对象和数据结构、如何处理错误、边界(如何处理另一个代码)、单元测试以及如何更好地组织你的课程。

我知道它会遗漏一个关于代码异味的重要主题,但这是一个有可能再写一个系列的庞大主题。

那么,我们开始吧?

給出好名字吧!

gif 写“你好,我的名字是”

使用能够表明代码功能的名称:

int d可能是int days

相反while x == 7,我们可以使用while time == DAYS_OF_WEEK

避免错误信息。

如果accountsList不是列表,它应该有一个更好的名字。

名称差异非常细微的变量也属于此主题,例如
VeryNiceControllerToDealWithStringsVeryNiceControllerToStoreStrings

您花了多长时间才发现这两个变量之间的差异?

正确区分你的变量。

如果我们有两个变量,一个叫bananae ,另一个叫theBanana,我们如何区分它们呢?同样的规则也适用于CustomerDataCostumerCostumerInfo。那么它们之间有什么区别呢?

使用您可以发音的名字

它促进了团队内部沟通。如果你结对编程,就不需要将变量引用为wpdw。 AworkDaysPerWeek听起来更好(:

使用可搜索的名称

您是否曾尝试使用ctrl+f某个变量,但却记不起它的名字?或者,它太常见了,您根本记不住?您是否尝试过搜索一个名为 的变量f

避免使用前缀!

另一个让搜索变得困难的因素是搜索本身,尤其是自动完成功能。如果变量以icsd、likeicsd_name或开头,icsd_date我们就很难找到想要的内容。

避免思维导图

当我们使用大量没有多大意义的变量时,我们需要记住它们是什么,同时试图弄清楚代码的作用。

user.each do |u|
  u.each do |s|
    s.each do |h|
      print h.name
    end
  end
end

(此代码块还有另一个问题,即每个代码块内部都包含太多代码......但我们稍后会讨论这个问题)

类名

应该是名词,而不是动词,因为类代表具体的对象。它们是事物的表征。

方法名称

应该是动词,而不是名词,因为方法代表对象必须执行的动作。

添加上下文!

如果你单独碰到一个变量state,可能很难弄清楚它的含义。

但如果这个变量很接近streetnumber就更容易理解它的作用。

添加上下文,但不是免费的。

假设我们正在开展一个名为的项目chocolate ice cream。我们不需要CIC在所有变量前面添加。

同时,在一个User类内部,我们不需要将所有的变量都调用为userAdressuserAgeuserFavoriteColor

功能

瑞典厨师敲着锅

它们应该很小

它们应该小巧易读,嵌套较少。理想情况下,缩进应该为 1 到 2 级。

只做一件事。

他们应该只做一件事,仅此而已。

但我怎么知道它只做一件事?

def verifyNameAndLogout
  if rightName?
    showMessage
  end

  logout
end

如果你看一下代码,你会发现它做了三件事

  1. 验证名称是否正确
  2. 如果是,则显示一条消息
  3. 注销,无论名字正确与否。

我们知道它只在函数内部的一个级别上执行所提出的内容,因此我们可以说这个函数只做一件事。

我们可以在此代码中做的唯一修改是将 if 语句重定位到另一个函数,但在这种情况下,它只是重定位,没有任何额外的优势,

函数不能被拆分成多个部分。如果可以拆分,则表明它可能包含多个函数

每个函数一个抽象级别

当我们谈论抽象级别时,我们可以将代码分为 3 个级别:

  1. 高的:getAdress
  2. 中等的:inactiveUsers = Users.findInactives
  3. 低的:.split(" ")

理想情况下,不要在一个函数中混合使用不同的抽象级别。如果混合使用,函数会变得难以理解,我们也无法像文章那样,从上到下一步一步地阅读代码。

最好的情况是,你可以从上到下阅读代码,就像阅读叙述一样。

前任:

  1. 为了包含设置和拆卸,我们首先包含设置,然后包含页面内容,然后包含拆卸。
  2. 为了包含设置,我们添加了设置套件,然后添加了默认设置。
  3. 为了包含测试设置,我们搜索层次结构...

(等等...)

避免 switch 语句和过多的 if/else

我们应该避免使用大量 switch/if-else 的原因是,除了它们太大、能做很多事情之外,它们还违反了面向对象编程的开放封闭原则,O在 SOLID 中也称为。

一个实体应该对修改封闭,对扩展开放。我所说的实体,指的是类、模块、函数等等。

当单个模块中有多个 if/else/switch 时,如果我们想添加任何额外的情况,就必须修改代码,这会使代码更加脆弱且难以测试。

有时我们需要验证不同的情况,并根据情况采取不同的行动。

减少 switch/if/else 的一种方法是使用多态性。

使用一个简单的例子:

foreach (var animal in zoo) {
  switch (typeof(animal)) {
    case "dog":
      echo animal.bark();
      break;

    case "cat":
      echo animal.meow();
      break;
  }
}

可以这样重构:

foreach (var animal in zoo) {
  echo animal.speak();
}

在这个例子中,我们创建了一个动物类,它有方法speak。然后我们创建了一只狗和一只猫的对象,并赋予它们说话的行为,就这样。

有时候,多态性可能有点难以解决这个问题。有时我们会重新整理逻辑,创建一些额外的函数来解决问题。

少争论才是出路

当我们谈论争论时,少即是多

当我们有许多参数时,我们的函数的复杂性以及我们的测试的复杂性就会大大增加。

发生的另一件事是,当我们调用函数时,参数顺序错误。

有时我们可以通过创建一个类来减少参数的大小。

一个带有多个参数的函数可以在类中被转换,并且这个函数可以作为这个类中的一个方法。很多时候,参数可以是它们的属性,这样我们就不需要传递任何参数了。

在某些情况下,我们不需要这么多,所以我们可以使用其他方法来减少参数的数量,比如分组。

Circle makeCircle(double x, double y, double radius)可以转化为Circle makeCircle(Point center, double radius)

函数是动词,参数是名词

当我们将函数命名为动词,将参数命名为名词时,它会变得更加清晰,更容易理解我们期望的行为,例如write(name)

参数顺序也很重要,这样您就不会忘记何时使用该函数。

想象一下,如果参数改变,顺序就会正确:assertExpectedEqualsActual(expected, actual)

避免将标志作为参数。

避免将布尔值作为参数传递,因为这会使重构变得困难。

尝试在调用函数时验证其真实性或虚假性,而不是在函数内部验证。

功能不应该具有附带影响。

如果我们有一个名为的函数checkPassword,它不应该在其主体内执行login

我们没有准备好随时登录,这可能会引起不良的副作用,此外测试功能也会变得更加困难。

输出参数

应避免使用输出函数。如果函数需要改变某个对象的状态,则应该改变该对象本身。

有时我们会遇到类似的函数appendFooter(s),我们不确定参数是输入(s是页脚吗?我们将它附加到某个地方?)还是输出(我们将附加s到页脚?)。

如果第一种情况是正确的,如果s是页脚,最好的做法是objectInstance.appendFooter

命令查询分离

函数应该改变对象的状态,或者返回有关它的一些信息。

这是一个不遵循命令查询分离的函数示例:

if name?
  name.change
  true
else
  false
end

一个改变某些内容并返回 true 或 false 的函数应该被替换成两个。一个用于验证,另一个用于执行更改。

DRY——不要重复自己

重复代码的问题在于,除了会使代码膨胀之外,我们需要做的任何更改都必须在多个地方进行更改。

然而,我们必须意识到,过度遵循 DRY 原则可能会有害。我们经常将代码重复与业务规则重复混淆。有时,只要不重复本应唯一的业务规则,复制代码是可以的。

还有一条格言说:同样的代码最多只能写三次。第三次就应该考虑重构,减少重复。

避免返回错误代码

当方法返回错误消息时,它实际上违反了“命令查询分离”原则。当我们将错误放在 if 块中时,可能会引起一些混淆,因为它可能返回某些内容,也可能返回错误。

if name?
  name.change
else
  raise 'An error has occured'
end

在上面的例子中,如果成功,它会执行一个操作。如果失败,它会弹出一个错误

评论

狗狗正在接受采访。字幕显示“暂无评论”
避免

代码注释带来的弊端往往大于好处。大多数情况下,关于变量作用或代码运行细节的注释可能会:

  1. 变得过时,让未来的自己感到困惑(昨天因为一条过时的评论浪费了宝贵的时间)
  2. 可以用一些更好的命名变量/函数/类来替换。
  3. 它们不必要地污染了代码。

应该明确的是,避免!等于禁止。如果你是一名 Java 程序员,并且正在处理一些公共事务,那么 Javadocs 就非常重要。有时解释一些代码会很有趣(当你看到一个正则表达式时,你会花多少时间去弄清楚它?),但 99% 的情况下,注释是可以避免的。

如果我必须注意所有这些规则,我将如何编写代码?

惊讶的皮卡丘

在一篇好的文章中,你会把想法写在纸上,然后不断完善文本。编码功能也是如此。

首先,让它工作,然后重构。

Bob 大叔在《整洁代码》一书中主张,编写代码的最佳顺序是:

  1. 编写单元测试。
  2. 创建可运行的代码。
  3. 重构以清理代码。

编写所有测试,使其运行,当您确定一切都按预期进行时,重构删除所有脏代码并应用设计模式。

具体来说,我没有在编写代码之前编写测试(TDD)的习惯,然而,在重构之前测试代码是必不可少的。只有这样,我们才能确保你的代码即使经过如此多的修改也能始终正常工作。

Clean code本书关注的是不良做法。它详细阐述了几个问题,但并未深入探讨解决方法。

如果你想更深入地了解如何修复脏代码问题,我们可以看看这本书refactoring。这本书详细介绍了几种不同的方法来消除代码中的脏代码。

文章来源:https://dev.to/rachc/identifying-the-dirt-in-our-code-2f3h
PREV
作为一名 Web 开发人员,如何保持最新状态
NEXT
你可能不知道的 6 种有用的前端技术