Fluent C++

Fluent C++:如何选择好的命名

2020-03-18  本文已影响0人  sunix

原文

命名是如此重要。 如果你的代码至少要被阅读一次(如果只有你自己阅读的话),那么名称将在你使用它时扮演重要角色。 变量名称,函数名称,类名称,接口名称,都是无价的方法来让你的代码更多地描述它们在做什么。 在工作中进行代码审查时,我和团队成员对好命名非常挑剔(伙计们,对此表示抱歉!),但我相信命名会提高或损害我们代码的质量。

尽管还有其他方法可以知道一段代码在做什么,例如文档;但出于以下两个原因,好的命名是传递有关代码信息的极其有效的渠道:

这篇文章旨在提供有关如何选择好名字的指南。 我已经从Steve McConnell Code Complete的参考书中删除了其中一些指南(如果你尚未阅读它,我建议停止阅读本文或其他正在做的事情,然后开始阅读本书🙂)。 我从与同事一起工作的讨论,建议和代码审查中学到了其他一些知识。 多年来,我通过自己的努力,通过阅读和编写代码尝试各种不同的事情。

我们将从告诉您如何避免使用不良名字开始,然后着重于如何挑选好名字。

不要做任何非法的事情

有些命名在C++中是不允许使用的

除了使用标准保留的名称(如“ int”)将停止编译外,名称中的下划线(_)的某些组合将在不合法的情况下进行编译,因为它们是为编译器或标准库实现者保留的。 使用它们可能会与它们声明的对象或例程冲突,从而导致细微的错误和意外的行为。

这是为编译器和标准库实现者保留的名称:

因此,不要考虑使用此类名称,因为它们可能会给你带来麻烦。

不要浪费信息

想想看,你的代码完全知道它在做什么。实际上,它是最了解的:它尽可能忠实地执行其中的内容!
给出好的命名实际上是在尽可能多地保留这些信息。换句话说,这不是通过混淆代码来浪费信息。有趣的是,通常鼓励通过封装来隐藏信息。但是在这种情况下,你想要针对的只是信息披露。

因此,请限制使用缩写词。缩写词和首字母缩写词写起来很方便,但很难阅读。俗话说,代码只写一次,却要读很多遍。现在,你不必系统地拼写所有首字母缩写词以使代码更清晰,而某些重复的未缩写代码甚至可能会损害可读性。例如,在你的代码中似乎合理地使用了“ VAT”,而不是每次使用它时都写valueAddedTax,因为每个人都知道VAT是什么。(嗯,其实我们真不知道啥是含税价格。。。)

如何选择在代码中是否使用首字母缩写词?一个好的经验法则是,如果你的应用程序的最终用户可以理解特定的缩写或首字母缩写,那么可以在代码中使用它,因为这表明你所在领域的每个人都知道这意味着什么。

不要尝试针对最小字符数进行优化。在论坛上,你会看到有人认为他们的方法更好,因为它涉及的打字更少。但是,哪个更麻烦,几次击键还是盯着代码看几分钟来试图理解它的意思呢?

对于函数和方法名称,尤其如此,你可以根据需要进行设置。研究表明(Rees 1982),函数和方法的名称最多可以包含35个字符,这听起来确实很多。

但是,函数名称的长度也会由于不好的原因变得过大:

现在,我们已经排除了一些不良的命名习惯,让我们集中讨论如何挑选好名字。

选择与抽象级别一致的名称

如之前的文章所述(一切都归结为尊重抽象级别),尊重抽象级别是许多良好实践的根本。 这些做法之一就是好的命名。

好的命名是与周围代码的抽象级别一致的名称。 就像在抽象级别的帖子中解释的那样,可以用不同的方式来表达:好的命名表示代码在做什么,而不是怎么做

为了说明这一点,让我们以一个计算公司中所有员工薪水的函数为例。 该函数返回key(员工)与value(工资)相关联的结果的集合。 这个代码的虚构实现者观看了钱德勒·卡鲁斯(Chandler Carruth)关于数据结构性能的演讲,并决定放弃map,改为使用包含pair的vector。

一个错误的,让人关注函数实现方式的函数名称是:

std::vector< pair<EmployeeId, double> > computeSalariesPairVector();

这种函数名称的问题在于它表示该函数以PairVector的形式来计算其结果,而不是专注于其工作,即计算雇员的薪水。 快速解决方案是将名称替换为以下内容:

std::vector< pair<EmployeeId, double> > computeEmployeeSalaries();

这样可以使调用者摆脱一些实现细节,让你作为代码阅读者专注于代码打算做什么。

尊重抽象级别会对变量和对象名称产生有趣的影响。在代码的许多情况下,变量和对象表示的内容比其类型所暗示的内容更抽象。

例如,一个int通常代表的不仅仅是一个int:它可以代表一个人的年龄或集合中元素的数量。或者Employee类型的特定对象可以代表团队的经理。或者 std::vector<double> 可以表示过去一个月在纽约观察到的每日平均温度。 (当然,这在非常低级的代码(例如添加两个int或使用强类型的地方)中不适用)。

在这种情况下,你想要基于变量表示的含义命名而不是其类型。你可以将int变量命名为“ age”,而不是“ i”。你将上述员工称为“manager”,而不仅仅是“employee”。你可以将向量命名为“temperatures”,而不是“doubles”。

很明显,至少在两种情况下,我们通常忽略应用此准则:迭代器和模板类型。

尽管随着算法和范围库的发展,迭代器将趋于消失,但是仍然需要一些迭代器,并且无论如何,今天仍有许多迭代器。例如,让我们收集从金融产品支付或收取的现金流量的集合。这些现金流量有些是正数,有些是负数。我们想获取流向我们的第一个现金流量,因此是第一个正数。这是编写此代码的第一次尝试:

std::vector<CashFlow> flows = ...

auto it = std::find_if(flows.begin(), flows.end(), isPositive);
std::cout << "Made " it->getValue() << "$, at last!" << std::endl;

该代码使用名称“ it”,以反映其实现方式(使用迭代器),而不是变量的含义。 与以下代码进行比较:

std::vector<CashFlow> flows = ...

auto firstPositiveFlow = std::find_if(flows.begin(), flows.end(), isPositive);
std::cout << "Made " << firstPositiveFlow->getValue() << "$, at last!" << std::endl;

哪种代码能让你最省力地理解它? 你能想象你要读的不是两行代码而是10或50行时候的区别么? 请注意,这与我们在上一节中描述的不浪费代码自解释的宝贵信息有关。

相同的逻辑适用于模板参数。 尤其是在开始使用模板时(我们看到的大多数示例都是来自学术界),我们倾向于为所有模板类和函数编写以下代码行:

template <typename T>

尽管你可能对T的了解不仅限于它是一种类型

在非常通用的代码中,如果你不清楚关于类型的任何信息,使用T作为类型名是很好的,比如在std::is_const中:

template<typename T>
struct is_const;

但是如果你知道任何关于T表示的含义,你可以将尽可能多的信息写入代码中。我们之前在Fluent C++上一篇专门文章(了解STL <algorithm>算法库的重要性)中看到了很多与此相关的例子,在这里让我们用一个解析序列化输入的函数做一个简单的例子:

template <typename T>
T parse(SerializedInput& input)
{
    T result;
    // ... perform the parsing ...
    return result;
}

另外展示一个更准确表示T的含义的例子:

template <typename ParsedType>
ParsedType parse(SerializedInput& input)
{
    ParsedType result;
    // ... perform the parsing ...
    return result;
}

比较这两段代码。 你认为哪一种更容易使用?

你可能会认为这有很大的不同,也可能没有。 但是可以确定的是,第二段代码包含了更多的信息,而且无需付出其他代价。

总的来说,这对于良好的命名是正确的:一旦那里有免费的午餐,就来抢一顿。

上一篇下一篇

猜你喜欢

热点阅读