美文网首页Fluent C++
Fluent C++:如何选择好的命名

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

作者: sunix | 来源:发表于2020-03-18 19:34 被阅读0次

    原文

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

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

    • 非常好的名字会立即告诉你周边代码在做什么,而不是查找文档并通过它来查看代码
    • 命名可以快速得到改善。 你可以通过手动或使用工具(例如流行的clang-tidy)进行快速修复来更新代码中的某些名称,并且如果你的代码是可构建的,则几乎可以肯定它会通过测试。

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

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

    不要做任何非法的事情

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

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

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

    • 带有两个下划线(__)的任何名称,
    • 任何以一个下划线开头的名称,后跟一个大写字母(_isOk,isOk_too,_IsNotOk),
    • 以一个下划线开头并在全局命名空间中的名称。

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

    不要浪费信息

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

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

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

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

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

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

    • 如果某个函数的名称因该函数执行过多操作而过长,则解决方法不是在命名上,而是通过将其分解为几个逻辑部分。

    • 当函数名称包含的多余信息已经由其参数类型表示时,它们就会人为膨胀。 例如:

      void saveEmployee(Employee const& employee);
      

      可以重命名为:

      void save(Employee const& employee);
      

      这会引导调用者写出更自然的代码:

      save(manager);
      

      而不是:

      saveEmployee(manager);
      

      这与接口原则和ADL(关于调用者删除多余的名称空间)一致,后者将成为专门帖子的主题。

    • 命名包含不必要的信息的另一个原因是它包含否定词。 如下代码:

      if (isNotValid(id))
      {
      

      可以通过使用肯定命名来改进:

      if (!isValid(id))
      {
      

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

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

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

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

    为了说明这一点,让我们以一个计算公司中所有员工薪水的函数为例。 该函数返回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;
    }
    

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

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

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

    相关文章

      网友评论

        本文标题:Fluent C++:如何选择好的命名

        本文链接:https://www.haomeiwen.com/subject/hpwcyhtx.html