命名在编程中的重要性

2023-12-30  本文已影响0人  涅槃快乐是金

我坚信编写代码与此类似:通过为函数、变量和其他构造物找到好的名称,我们真正认识到我们正在解决的问题的本质。清晰性带来的不仅仅是好的名称,还有更清晰的代码和改进的架构。

我会说,写出干净代码的90%其实就是“仅仅”是正确命名事物。听起来很简单,但实际上并非如此!

让我们来看几个例子。

例1

// Given first and last name of a person, returns the
// demographic statistics for all matching people.
async function demo (a, b) {
  const c = await users(a, b);
  return [
    avg(c.map(a => a.info[0])),
    median(c.map(a => a.info[1]))
  ];
}

这段代码存在哪些问题?

让我们来修复它:

async function fetchDemographicStatsForFirstAndLastName (
  firstName, lastName
) {
  const users = await fetchUsersByFirstAndLastName(
    firstName, lastName
  );
  return {
    averageAge: avg(users.map(u => u.stats.age)),
    medianSalary: median(users.map(u => u.stats.salary))
  };
}

我们做了什么?

  • 请注意我们使用了名称 users 作为获取的用户,而不是像 usersWithSpecifiedFirstAndLastName 或 fetchedUsers 这样更长的名称:没有必要使用更长的名称,因为这个变量是非常局部的,存在时间很短,周围有足够的上下文来清楚它是关于什么的。

最后,请注意函数上方不再有注释。事实上,不再需要注释:从函数名称和参数中就足够清楚了!

例2

// Find a free machine and use it, or create a new machine
// if needed. Then on that machine, set up the new worker 
// with the given Docker image and setup cmd. Finally,
// start executing a job on that worker and return its id.
async function getJobId (
  machineType, machineRegion,
  workerDockerImage, workerSetupCmd,
  jobDescription
) {
  ...
}

在这个例子中,我们将忽略实现细节,只关注正确命名和参数。

这段代码有什么问题吗?

好的,这听起来很容易解决,让我们给它一个更好的名称!

async function procureFreeMachineAndSetUpTheDockerWorkerThenStartExecutingTheJob (
  machineType, machineRegion,
  workerDockerImage, workerSetupCmd,
  jobDescription
) {
  ...
}

唉,这是一个又长又复杂的名字。但事实上,如果我们不想失去有关这个函数的操作以及我们可以从中期望什么的有价值信息,我们实际上不能将其缩短。因此,我们陷入了困境,找不到更好的名称!现在怎么办?

问题在于,如果背后没有干净的代码,你就无法给出一个好的名字。因此,一个糟糕的名称不仅仅是一个命名错误,而且通常也是其背后代码有问题的指示,是设计的失败。代码如此问题重重,以至于你甚至不知道该如何命名它 → 没有直截了当的名字可供给它,因为它不是一个直截了当的代码!


在我们的情况下,问题在于这个函数一次性尝试做太多事情。长名称和许多参数是这一点的指标,尽管在某些情况下这些可能是可以接受的。更强烈的指标是名称中使用了 "and" 和 "then" 这样的词,以及参数名称可以通过前缀(machine、worker)分组。

解决方案是通过将函数拆分成多个较小的函数来清理代码:

async function procureFreeMachine (type, region) { ... }
async function setUpDockerWorker (machineId, dockerImage, setupCmd) { ... }
async function startExecutingJob (workerId, jobDescription) { ... }

什么是一个好的名字?

但让我们退后一步 - 什么是一个糟糕的名字,什么是一个好的名字?这意味着什么,我们如何辨别它们?

好的名字不会引导错误,不会省略信息,也不会假设。

一个好的名字应该让你对变量包含的内容或函数的作用有一个很好的想法。一个好的名字将告诉你所有你需要知道的,或者足以让你知道下一步应该去哪里查找。它不会让你猜测或纳闷。它不会误导你。一个好的名字是明显的,是符合预期的。它是一致的。不要过于创造性。它不会假设读者不太可能具有的上下文或知识。

此外,上下文至关重要:在读取的上下文中,你不能评估名称。verifyOrganizationChainCredentials 可能是一个糟糕的名字或一个好的名字。a 可能是一个好的名字或一个糟糕的名字。这取决于故事,环境,代码正在解决的问题。名称讲述一个故事,它们需要像一个故事一样配合。

一些著名的糟糕命名的例子:

我自己曾是这种糟糕命名的受害者:我的父母给我买了一本有关 JavaScript 的书,而我想学习的是 Java。

它被命名为 Authorization,但用于身份验证!而这两者并不相同:身份验证是关于确认身份,而授权是关于授予权限的。有关更多信息,请访问:https://stackoverflow.com/questions/30062024/why-is-the-http-header-for-authentication-called-authorization

这是我的过错:Wasp 是一个使用自定义配置语言作为其代码库的一小部分的全栈 JS Web 框架,但我在名称中加入了 -lang,并吓跑了许多人,因为他们认为它是一种全新的通用编程语言!

如何想出一个好的名字

不要随意起名字,而是要找到名字
最好的建议可能是不要随意起名字,而是要找到一个名字。你不应该像给宠物或孩子起名字那样创造一个原创的名字;相反,你应该寻找你要命名的事物的本质,基于这个本质,名字应该自然而然地出现。如果你不喜欢你发现的名字,这意味着你不喜欢你要命名的东西,你应该通过改进代码的设计来改变这个东西(就像我们在示例#2中所做的那样)。

在确定名称时要注意的事项

  • 对于变量为 Bool 时使用前缀(例如 isAuthEnabled)
  • 对于具有幂等性的函数使用前缀 ensure,该函数仅在到目前为止尚未设置时执行某些操作(例如 ensureServerIsRunning)。

想出一个名字时的简单技巧

如果你有困难想出一个名字,请按照以下步骤进行:

不要过于纠结于一开始总是找到完美的名字 → 多次迭代代码是可以的,每次迭代都会改进您的代码和名字。

以命名为中心的代码审查

一旦你开始深入思考命名,你就会看到它如何改变你的代码审查过程:焦点从查看实现细节转向首先查看名称。

当我进行代码审查时,我会有一个主导性的想法:“这个名字清晰吗?”从那里,整个审查就会发展,并导致整洁的代码。

检查一个名字是解开其背后整个混乱的单一点压力。搜索糟糕的名字,迟早会发现其中有一些糟糕的代码。

进一步阅读

如果您尚未阅读过,请推荐阅读 Robert Martin 的《Clean Code》一书。它有一章关于命名,还深入探讨了如何编写您和其他人都喜欢阅读和维护的代码。

上一篇 下一篇

猜你喜欢

热点阅读