[0:00] 这段代码完全是一团糟,而我就是写这个的人。 [0:04] 今天,我将与您分享编写干净代码的五个技巧。 [0:08] 最后一个技巧很少有人谈论, [0:11] 但在我看来,如果你想成为一名优秀的高级开发人员,这一点至关重要。 [0:15] 例如,我将使用我们自己的发票和付款处理系统中的代码。 [0:21] 这是代码的旧版本,我已经简化并删除了该视频的一些内容, [0:25] 但它仍然非常接近已经生产了一段时间的内容。 [0:29] 首先让我简单解释一下这段代码实际上做了什么。 [0:34] 所以这里有四个文件。我已经稍微简化了这一点。 [0:37] 有一个主文件从 Stripe API 获取支付意图。 [0:42] 这些基本上是人们所做的付款。 [0:44] 然后,根据这些付款意图,我创建会计系统所需的发票。 [0:50] 该主文件基本上每 x 分钟在服务器上运行一次, [0:54] 并且它还依赖于其他几个文件。 [0:57] 其中之一就是这个,moneybird.py。 [1:00] Moneybird 是我碰巧使用的会计系统。 [1:03] 那里有很多会计系统。 [1:05] 这个在荷兰相当有名。 因此,我在这个特定文件中所做的是, [1:08] 在 Moneybird API 之上 创建了一种简单的抽象层 [1:13] ,这是一个 RESTful API。 [1:16] 例如,我这里有一个函数 postMoneybirdRequest, [1:22] 它获取路径,获取一些数据。 [1:24] 它基本上负责传递管理 ID,这是访问我的管理所需的令牌。 [1:30] 然后将请求发送到 Moneybird API 的 URL。 [1:36] 所以这个函数不知道我喜欢做的请求类型的任何具体信息。 [1:40] 它只是 API 之上的一个简单层,为我处理授权。 [1:45] 该函数所做的最后一件事是将响应转换为 JSON 对象 [1:50] ,然后我可以在代码中使用它。 [1:52] 这个简单应用程序中的另一个文件是processing.pi。 [1:55] 它的作用是对其检索到的付款意图进行一些检查。 [2:00] 例如,有 getSuccessfulPaymentIntent。 [2:03] 还有一个函数可以确定您是否可以实际处理其中一个付款意图。 [2:08] 还有另一个从 Stripe 数据发票数据创建的辅助函数。 [2:14] 所以发票数据是发票生成系统所需要的。 [2:18] 这是在另一个文件中,invoices.pi,它创建并发送发票。 [2:23] 这样就得到了付款意图。 [2:25] 然后,它根据 Stripe 付款意图构造此发票数据。 [2:30] 然后它会做一些其他的事情,创建财务报表等等, [2:34] 预订付款,你需要在会​​计中做的那种事情,无聊的事情。 [2:39] 此文件中还有几个辅助函数,它们 [2:46] 通过传递特定 URL 来调用 postMoneybirdRequest 函数。 [2:48] 所以这预订了付款。 [2:49] 这预订了发票付款,其处理方式略有不同,等等。 [2:55] 这里还有一些其他的事情,比如创建财务报表。 [2:59] 这就是这样设置的。 [3:01] 看起来并没有那么糟糕,但实际上当您仔细观察( [3:04] 这就是我们今天要做的)时,我们会发现代码中存在一些问题。 [3:08] 现在,让这变得困难的原因 [3:13] 是它集成了各种第三方的东西,特别是在本例中,Stripe 和 Moneybird 是 [3:19] 人们所编写的许多代码的典型特征 。 他们每个人都有自己的数据呈现方式。 [3:23] 因此,您需要执行一些转换步骤,并需要确保这些事情正确解耦。 [3:28] 简而言之,这段代码的工作原理是首先从 Stripe 检索支付意图。 [3:33] 这些是最近完成的付款。 [3:36] 然后,它根据这些付款意图构建发票数据。 [3:41] 然后它根据发票数据构建发票并将其发送给客户。 [3:46] 最后,像付款本身和申请费( [3:51] Stripe 收取的费用)这样的交易会自动记录在会计系统中。 [3:56] 编写干净代码的第一步是选择有意义的名称。 [4:00] 您需要选择有意义的名称。 [4:02] 使用能够揭示意图的描述性名称,这使您的代码更易于理解和更改。 [4:08] 让我们看一个例子,看看我们在这方面做得不太好。 [4:13] 例如,该文件名为processing.pi。 [4:17] 我们不知道这里实际正在处理什么。 [4:20] 当您仔细查看源代码时, [4:23] 您会发现它围绕 Stripe API 提供了一些方便的函数和类。 [4:30] 因此,我们应该选择一个更能准确反映这一点的名称。 [4:34] 我们将此文件重命名为 stripe-api-helpers。 [4:39] 这使得该文件的用途以及您应该如何使用它变得更加清晰。 [4:44] 另一个例子是,如果您查看此处的一些函数名称。 [4:48] 因此,有意义的名称可以广泛应用。 [4:51] 因此,不仅是文件名,还包括变量名、函数名、类名以及任何在代码中具有名称的内容。 [4:57] 这是一个我们可以改进的函数示例。 [5:00] 所以我们有一个函数 getSuccessfulPaymentIntent。 [5:04] 但如果你看看这个函数实际做了什么,它实际上并没有获得成功的支付意图。 [5:09] 它得到了所有这些,对吧? [5:11] 它计算时间戳,然后检索支付意图,然后将其作为 Python 列表返回。 [5:17] 第二个问题是它上面的函数。 [5:20] 它的名字叫canProcessPi。 [5:23] 这需要支付意图并检查您是否可以实际处理它。 [5:27] 这会检查付款意图是否成功。 [5:30] 这个名字并没有解释这一点。 [5:32] 当你查看该函数时,你可能还会认为它可能会做其他事情来检查你是否可以处理这个支付意图。 [5:40] 但它只看成功状态。 [5:42] 我们能做的,不是让这两个函数有奇怪的名字,而是重构它们。 [5:47] 将它们变成一个真正获得成功支付意图的函数。 [5:51] 这实际上很容易做到,因为我们已经进行了比较。 [5:54] 我们可以简单地将其添加到此处的列表理解中。 [5:58] 当然,这就是 Pi。 [6:01] 所以现在这实际上返回了成功的付款意图。 [6:04] 让我们也更改评论以反映这一点。 [6:08] 然后canProcessPi,我们可以简单地删除它。 [6:12] 我们保存文件。 [6:14] 当然,现在在主文件中,我们还需要更新代码,因为我们不再有这个了。 [6:18] 所以我删除了导入,因为它不再存在。 [6:22] 然后这里,实际上,这已经获得了成功的支付意图。 [6:26] 所以我什至不再需要这张支票了。 [6:30] 我们开始吧。 [6:31] 顺便说一句,如果您想了解如何快速识别和修复代码中的此类问题,请加入我的免费代码诊断研讨会。 [6:38] 它使用生产代码中的 Python 示例教您一个三因素框架。 [6:43] 在 arjan.codes/diagnosis 上注册。 [6:46] 该链接也位于该视频的说明中。 [6:48] 编写干净代码的第二个技巧是明智地使用注释。 [6:52] 你应该用它们来解释为什么要做某事,而不是做了什么。 [6:57] 对于后者,代码应该不言自明。 [7:00] 例如,如果您再次查看 getSuccessfulPaymentIntents,您会发现此注释(减去时间增量、小时前等)实际上并没有真正解释为什么代码在那里。 [7:12] 为什么我们需要计算时间戳? [7:14] 这根本没有帮助。 [7:16] 让我们重写此注释,以便更清楚地说明为什么需要该行代码。 [7:27] 您会看到 GitHub Copilot 在这里帮了我一点忙。 [7:30] 所以现在已经更清楚时间戳计算的用途了。 [7:35] 我们需要它来确定我们要检索哪些付款意图。 [7:39] 注释并不能真正解释正在发生的事情的另一个例子是在constructInvoiceDataFromStripe 函数中。 [7:46] 让我们看一下这里实际发生了什么。 [7:49] 所以我们看到一些评论,比如从 Stripe 检索客户,你知道,这还不错。 [7:54] 但随后有一条注释用于获取最新费用、检索余额交易,最后返回发票数据。 [8:03] 很难理解为什么我们需要获取费用对象然后获取余额交易对象。 [8:09] 如果您仔细观察,您会发现这是必要的,因为我们需要确定申请费是多少。 [8:15] 这是我们构建发票数据时从此处发生的余额交易中获取的唯一内容。 [8:22] 因此,让我们用实际的解释来代替这些无用的注释,获取最新的费用并检索余额交易。 [8:33] 稍后我将回到这个函数来讨论第三个技巧,即函数或模块应该负责一件事。 [8:41] 保持你的职能小而集中。 [8:44] 他们应该做一件事,做好,只做。 [8:47] 例如,如果您向上滚动以获取成功的付款意图,则这不应该负责计算时间戳。 [8:56] 因此,让我们将其提取到一个单独的函数中。 [9:02] 现在我们有了单独的函数,我可以简单地替换这段代码。 [9:15] 我们开始吧。这已经让事情变得简单了。 [9:19] 另一个例子是我之前提到的,再次从 Stripe 构建发票数据。 [9:25] 现在,这目前负责相当多的事情。 [9:28] 它负责创建发票数据对象,还负责确定交易的申请费用。 [9:35] 支付。因此,我们已经通过更改注释对这里的代码进行了一些改进,但实际上, [9:40] 将这段代码放入一个单独的函数中更有意义,这样 [9:47] 构造发票数据就会变得更小,并且只有一个职责。因此,我将 [9:52] 创建一个函数 getApplicationFee,它将获取付款意图。因此,Copilot [10:03] 所做的是,如果没有费用且没有余额交易,则简单地返回零。因此,让我们 [10:08] 确保此代码实际上与我们之前的代码匹配。 因此, 为了确定起见, 我将复制此内容 [10:14] ,因为这略有不同。 我们开始吧。现在,我可以简单地编写申请费等于,而不是所有这些代码, [10:21] 从付款意图中获取申请费。 [10:29] 然后在这里我们还将它传递给发票数据对象。就这样,这是另一个改进。 [10:38] 但这也是模块级别责任问题的一个例子。让我们看一下 [10:44] 在invoices.pi 中创建和发送发票。所以这个函数也有相当多的职责, [10:52] 我稍后会讨论,但它最主要的作用是获取付款意图 [10:58] ,然后从 Stripe 构建发票数据。但现在这意味着这个模块, [11:05] invoices.pi,负责处理付款处理和会计。这会导致 [11:13] 各种各样的问题。例如,有一个循环导入。从 Stripe API [11:19] 助手导入发票。如果您转到 Stripe API 帮助程序,您会看到它是从发票导入的。另外, [11:26] 如果您这样做,那么创建和发送发票不仅可以从 Stripe 中实现, [11:30] 因为该函数直接使用它。因此,如果您想添加对其他支付方式( [11:35] 例如 PayPal 或 Apple Pay)的支持,那么您将需要进行大量重构工作或 [11:41] 复制大量代码。因此,让我们从此函数中删除发票数据构造 [11:48] ,然后在主文件中处理它。 [11:59] 当然,我们还需要从 Stripe API 帮助程序模块导入它。 [12:06] 现在我们需要支付意图,即 pi。我们开始吧。现在 [12:14] 创建并发送发票,我们应该将发票数据传递给它。 因此, [12:20] 我们传递的不是 Stripe 特有的 付款意图,而是 发票数据。 [12:28] 然后我将删除这个打印语句,因为这里不需要它。但现在你发现 [12:32] 有问题了。财务报表需要某种 ID,以便将其 [12:38] 与实际付款联系起来。因此,为了了解如何解决这个问题,我们必须 [12:42] 更深入地研究这个发票数据对象。因此,如果我们回顾一下,我们会发现 [12:48] 实际上,作为发票数据的一部分,有一个 Stripe 付款意图 ID。所以我们能做的就是 [12:55] 将其更改为发票数据 Stripe 付款意图 ID。让我们看看这是否是唯一的事情。这 [13:07] 是另一个时间。所以在这里我们也会这样做。我们开始吧。现在,当我滚动回顶部时, [13:13] 您还会看到我们现在可以解决此依赖性问题。所以发票,我们可以删除 [13:18] Stripe,我们可以删除 Stripe API 帮助程序。所以它不再依赖于Stripe,这 [13:24] 正是我们想要的。然后,当然是主函数,我们现在不在这里传递付款意图, [13:30] 而是传递发票数据,就像这样。创建和创建过程中还有另一个责任问题 [13:36] 发送发票功能。我的意思是,它仍然做得太多了。然而,这是一个更大问题的一部分 [13:42] ,很少有开发人员知道如何解决。但在讨论这个之前,我们首先看看错误 [13:47] 是如何处理的。这是编写干净代码的第四个技巧。您需要在 [13:53] 单独的逻辑中处理异常,而不是将它们与正常逻辑集成。这可以使您的主代码保持干净 [13:58] 且流程清晰。现在,如果您查看确定申请费用的代码, [14:03] 让我们回到 Stripe 助手。这就是这个功能。所以你会看到,在某些情况下, [14:09] 这不会返回任何结果。例如,如果没有费用或没有余额交易。 [14:15] 这意味着,我没有正确执行此操作,但这意味着您需要 在调用此函数的函数中显式 [14:22] 处理非案例 。 所以在这里,申请费可能是免费的。因此,要 [14:28] 正确执行此操作,您会发现还存在一些类型问题。所以这可以返回浮动或 [14:33] 不返回,对吗?这就是返回类型。然后申请费现在是浮动的或没有。因此,如果 [14:40] 申请费为“无”,那么我们需要返回“无”。但现在这会产生更多 [14:45] 问题,因为发票数据可能没有。所以我们必须处理这里的案子。这就是不使用单独的异常逻辑的问题 [14:50] ,我们必须在各处添加所有这些检查 [14:55] 。因此,我们可以做的是,让我们回到 Stripe API 帮助程序文件, [15:00] 当出现问题时我们会引发错误。因此, [15:07] 我们不会让 getApplicationFee 返回浮点值或无值,而是让它返回浮点值。但是,如果我们找不到费用, [15:12] 那么我可以提出一个值错误。未找到收费。余额交易也是如此。 [15:20] 我们开始吧。所以现在它总是返回一个浮点数,否则会引发错误。然后在这里, [15:25] 在 ConstructionInvoiceDataFromStripe 中,我不再需要此检查。所以这实际上简化了 [15:31] 我的代码。然后,因为现在申请费总是有一个值,否则会引发错误, [15:35] 我们也可以删除这个类型注释。所以现在总是会返回发票数据。 [15:41] 然后,在主文件中,我们有发票数据。我们不再需要这张支票了。 [15:46] 我们有发票数据。我们不再需要这张支票了。 [15:50] 这样就进一步简化了我们的代码。在我继续讲最后一个技巧之前,有一点额外的好处。 [15:56] 确保所有内容的格式一致。使用自动格式化程序,例如粗糙或黑色。 [16:03] 一致的格式有助于减少认知负荷和合并冲突。 假设您使用相同的格式化程序和格式化规则, [16:08] 它使与其他人的协作变得更加容易。 您 是否 [16:13] 知道可以在 Git post commit hook 中进行自动格式化?如果您喜欢这些技巧并且想要 [16:19] 更多类似的技巧,请不要忘记在 thefridayloop.com 上订阅我的每周时事通讯 FridayLoop。 [16:25] 它是免费的,您每周都会获得这样的见解。现在来说说第五点。正如我 [16:31] 之前所说,createAndSendInvoice 函数有太多职责。但如何分割呢? [16:37] 这不是那么明显。 预订付款和创建 财务报表、创建发票等 之间共享大量数据 [16:44] 。 那你该怎么办?要解决这个问题, 你需要成为领域专家,而不是成为领域专家。 [16:51] 作为软件开发人员,这是最难学习的事情 之一 [16:57] 。在我看来, 如果你想成为一名优秀的高级开发人员, [17:03] 这是你需要掌握的最重要的技能 。 那么让我们来分解一下。首先, [17:09] 如果您不了解领域,那么重构 [17:14] 这样的函数将非常困难。您需要知道什么是金融交易或发票,并且发票 [17:19] 需要在分类账户上进行登记。简而言之,您需要财务和会计知识。第二 [17:24] 部分是这个函数是按照会计师的指示编写的。 [17:29] 当然,你永远不应该听会计师的话,但这不是重点。问题是,作为 [17:34] 一名技术人员,你需要了解这个领域,但同时,你需要确保 [17:39] 你足够站在领域之外,这样你才能为问题带来新的视角。 [17:45] 您需要成为领域专家,而不是领域专家。那么让我们看看如何解决 [17:51] 这个特定问题。一个大问题是,如果我们将此函数拆分为,例如, [17:56] 发票处理和付款处理部分,我们需要将发票和原始 [18:01] 发票数据传递给付款处理部分,因为它依赖于它来创建财务报表。 [18:07] 例如,它需要发票数据,还需要发票日期。然后预订付款, [18:11] 这又依赖于我们从财务报表中获得的突变 ID,我们需要 [18:16] 深入研究财务报表对象才能得到我们想要的东西。现在,如果您知道 [18:21] 会计的工作原理,那么您还会了解付款会计所需的大部分数据 [18:25] 实际上已经在发票中。因此,我们实际上可以通过 [18:30] 访问发票而不是发票数据来获取所有这些信息。目前发票中唯一没有的 [18:35] 就是申请费。因此,为了正确拆分此功能,将发票处理与 [18:42] 付款处理分开,我们需要首先修改发票结构。为了做到这一点,您 [18:48] 需要一些领域知识。我们还需要以一种需要技术角度的方式来做这件事。 [18:54] 现在,在 MoneyBird 的会计系统中,您可以向发票添加自定义字段。您实际上可以 [19:00] 在此处的创建发票函数中看到发生的情况。所以你会看到它添加了一个自定义 [19:07] 字段,即条纹。 [19:08] 付款意图 ID,稍后我们将使用它来映射到付款。因此,在这种情况下,为了解决这个责任问题,我们可以做的 [19:15] 是,我们实际上在此处添加一个用于申请 [19:21] 费的自定义字段,这样我们就有了发票的申请费部分,然后我们不再需要 [19:26] 原始发票数据对象来创建付款。因此,让我们在此处添加一个自定义字段 [19:32] ,正如您所看到的,Copilot 已经为我完成了此操作。因此,我们有申请费自定义字段 [19:39] ,我为其提供价值数据点申请费。所以现在这意味着发票将包含 [19:45] 申请费,然后我们可以做的是我们实际上可以重构这个函数并将 [19:50] 其分成两部分。因此,我要做的第一件事就是删除这些对 [19:55] 发票数据的直接引用,并改用自定义字段。现在,在 Moneyboard.pi 文件中,我有一个获取自定义 [20:02] 字段值函数可以帮助我解决这个问题。因此,让我将其导入到此文件中,然后 [20:08] 我们可以使用它来访问自定义字段。因此,让我们 [20:13] 从发票中检索申请费,为了使类型正确,我将其转换为浮点数 [20:21] 。这就是申请费,从现在开始我可以在这些财务 [20:25] 报表中使用它。我们开始吧。这里也一样。 现在,我们 在这部分函数中 仍然访问发票数据的唯一地方 [20:32] 就是在这里获取付款意向 ID。 因此我们也可以 [20:37] 从发票自定义字段值中获取该值。因此,我们再次传递发票,然后传递 [20:44] 条带付款意图 id 字段。现在我可以简单地将其删除,我想这就是全部 [20:52] 了。哦,这里还有另外一个。我们也删除它。我们看到我们在这里使用发票数据的最后一个地方 [20:57] 是获取金额,金额当然也是 [21:03] 发票的一部分。从 API 来看,这实际上是含税的总价,我们 [21:11] 这里不考虑申请费。那么这就是看起来的样子。现在我们已经将 [21:16] 这些部分分开得更正确了,这部分现在负责创建和发送发票 [21:22] ,这部分负责创建财务报表和预订付款。 [21:28] 所以我们现在能做的就是我们可以把这一部分从这部分中完全分离出来。因此, [21:35] 创建并发送发票仍然应该正确执行此操作。它获取发票数据,然后 [21:41] 通过电子邮件或其他方式选择交付方式,然后发送。所以我不会 [21:46] 改变这一点,除非我希望这个函数实际返回发票。所以这是一个 [21:52] 任何字符串的字典。我们或许可以通过查看类型来使其更加精确, [21:57] 但我暂时只是保持简单。现在我们需要退回正在创建的发票 [22:03] 。然后这部分将成为一个新函数,我们将其称为“帐本发票” [22:12] ,并将获取发票作为参数。我们开始吧。现在您会发现我们没有收到任何 [22:21] 错误。所以现在这部分实际上被分开了,然后在主文件中我们可以存储 [22:27] 发票在一个变量中,然后我们将获取此功能簿发票。 [22:32] 我将从发票模块导入它,然后作为最后一步,我们将 [22:39] 像这样预订发票。我们仍然可以进行一些小的重构,以使事情变得 [22:44] 更好一些。例如,我真的不喜欢我们直接访问财务 [22:50] 报表对象来获取突变 ID。这种情况发生在这里,但如果我们 [22:55] 在这里向下滚动几行也会发生这种情况。因此,我们可以使用这一部分来查看创建财务 [23:04] 报表功能,然后在那里执行此操作。这样,我们 [23:09] 在处理财务报表的地方发布金钱鸟请求的功能也是我们 [23:12] 访问该对象的部分。当然,您可能需要 [23:17] 财务报表中的其他信息,但在这种情况下,我们只需要突变 ID,所以我将保持 [23:22] 简单。在这里,我不会直接返回此发布请求的响应,而是将 [23:28] 其存储在财务报表变量中,然后返回突变 ID。 [23:40] 然后这将返回一个整数。我们开始吧。现在,这 [23:45] 大大简化了代码,因为我们可以简单地说,这是突变 id。我们不需要 [23:49] 在这里提取它,也不需要在这里提取同样的东西。 这是通过 简单地进一步划分职责来 清理代码的另一个例子 [23:59] 。 顺便说一下,我们在这里解决的问题是 [24:04] 计量定律问题,这是一个典型的问题,因为您需要深入研究 [24:08] 一个对象,您需要了解该对象的各种实现细节,但 [24:13] 现在不再是这样了。需要。我们可以在主文件中做的最后一件事是,现在主 [24:17] 函数变得相当大。例如,我们可以将这一部分分离到 [24:23] 另一个函数中。所以这需要付款意向。 [24:39] 让我也导入 Stripe,这样我们就有了类型。 [24:43] 我们开始吧。然后在主函数中,我们将为每个 [24:48] 付款意图调用创建发票。 如果您愿意, 您还可以进一步将其变成一个单独的 [24:53] 函数。 但老实说对我来说这完全没问题。 这段 代码现在在做 [24:57] 什么非常清楚 。 所以这是相当大的重构,对代码进行了大量更改。让我们回顾一下。 [25:03] 我已经讨论过使用有意义的名称,编写有用的注释,确保函数名称 [25:11] 确保函数和模块具有明确的单一职责,将错误作为 [25:17] 单独的逻辑处理,最后确保您对域有足够的了解,但同时是时候 [25:24] 为您要解决的问题带来全新的技术视角了。现在我想听听你的消息。 [25:29] 您对这次重构有何看法?你同意我所做的改变吗?我错过了什么 [25:34] 重要的事情吗?您将如何建立这样的集成?请在评论中告诉我。 [25:40] 在本视频中我没有太多关注的 一部分 是如何将软件测试纳入 [25:44] 重构过程。如果您想了解更多信息,请观看接下来的视频,其中我 [25:50] 向您展示了进行重构而不会陷入困境的五步流程。感谢您的观看 [25:56] ,下次再见。