【问题标题】:Picking good identifier names选择好的标识符名称
【发布时间】:2010-10-24 22:07:17
【问题描述】:

好的,所以你可以阅读标识符命名指南,直到你脸色发青......骆驼大小写,帕斯卡大小写,让它们具有描述性......但它们不会帮助你真正选择最好的名字对于给定的应用程序域。

最容易选择(恕我直言)是一两个词名词组:

  • 报名表
  • 员工
  • 小部件集合

但并不是每个类都可以巧妙地融入名词,所以我看到很多人通过在动词末尾添加 -er 来将动词变成名词:

  • 客户经理
  • 记录计数器
  • ProcessRunner

我看到的最大问题是很多时候它们是模棱两可的……尤其是经理。它到底在管理什么?

所以我的问题是你如何为一个班级取一个好听的名字?我所说的“好”是指信息丰富且明确。

我知道,我知道。几乎每个现代 IDE 都内置了重构支持,您可以毫不费力地更改名称,那有什么意义呢?好吧,一个选择不当的名字会迷惑和误导任何偶然发现它的人,直到它被重命名,所以它仍然是一个有效的问题。

相关

What’s the best approach to naming classes?

【问题讨论】:

标签: naming-conventions


【解决方案1】:

Clean Code 是一本好书,但不要遵循关于标识符名称和 cmets 的建议。 (见Self Describing CodeLong Identifiers Make Code Unreadable

例如,这里是鲍勃叔叔的保龄球示例(请参阅鲍勃叔叔的代码的其他回复):

// Returns total score for a game of ten-pin bowling. On entry the number of fallen pins
// for each ball is provided in fallen[] and the number of balls bowled is in balls_bowled.
public int score()
{
  int points = 0;                                            // Cumulative count of points for the game
  int ball = 0;                                               // Current shot or "ball" (1 or 2 per frame)
  int frame;                                                  // Current frame of the game


  for (frame = 0; frame < max_frames; ++frame)
  {
    if (fallen[ball] == 10)
    {
        // Strike (all pins knocked over from 1 ball) gives bonus of next two balls
        points += 10 + fallen[ball + 1] + fallen[ball + 2];
        ++ball;                                                 // Move to next frame (no 2nd ball)
    }
    else if (fallen[ball] + fallen[ball + 1] == 10)
    {
        // Spare (all pins knocked over in 2 balls) gives bonus of following ball
        assert(fallen[ball + 1] > 0);
        points += 10 + fallen[ball + 2];
        ball += 2;                                             // Move to first ball of next frame
    }
    else
    {
        // Open frame just gives score of all fallen pins of both frame's balls
        assert(fallen[ball] + fallen[ball + 1] < 10);
        points += fallen[ball] + fallen[ball + 1];
        ball += 2;                                             // Move to first shot of next frame
    }
  }
  assert(frame == max_frames && ball == balls_bowled);
  assert(points <= max_frames*30);                // Maximum possible score
  return points;
}

【讨论】:

    【解决方案2】:

    当我发现很难选择一个有意义的名字时,它会提醒我这个类太大或者我并不真正了解我正在编码的领域。

    当您最终提出一个好的设计来解决 Uncle Bob清洁代码中提出的关于适当抽象等概念的问题时,单一责任原则之类的,那么名字就更容易挑了。

    难以选择名称几乎总是表明逻辑组织仍需改进。毕竟,编译器并不关心你的逻辑组织得好坏。组织的存在只是为了帮助您管理复杂性和确保开发人员的理智。

    【讨论】:

    • 我完全同意。如果您的首选名称“不好”,请进行一些思考、绘图或向其他人解释,这应该有助于您确定此代码是否属于其他类或类似的东西,如果有用的描述性名称不出现。
    【解决方案3】:

    我一直在阅读 Robert C. Martin 的Clean Code,但我还没有从第 17 章进入这一部分,但我认为它最接近于回答这个问题。

    N1:选择描述性名称

    选择名称不要太快。 确保名称具有描述性。 请记住,含义往往会漂移 随着软件的发展,如此频繁 重新评估的适当性 您选择的名称。这不仅仅是一个 “感觉良好”的建议。中的名称 软件是制造产品的 90% 软件可读。你需要采取 是时候明智地选择它们了 保持相关性。名字也是 重要的是不小心对待。 考虑下面的代码。它有什么作用 做?如果我给你看代码 精心挑选的名字,它会让 对你来说完美的感觉,但是像这样 它只是符号的大杂烩 神奇的数字。

    public int x() {
        int q = 0;
        int z = 0;
        for (int kk = 0; kk < 10; kk++) {
            if (l[z] == 10)
            {
                q += 10 + (l[z + 1] + l[z + 2]);
                z += 1;
            }
            else if (l[z] + l[z + 1] == 10)
            {
                q += 10 + l[z + 2];
                z += 2;
            } else {
                q += l[z] + l[z + 1];
                z += 2;
            }
        }
        return q;
    }
    

    这是代码应该是这样的 书面。这个sn-p其实少了 比上一个完整。然而你 可以立即推断出它是什么 试着去做,你可以非常 可能写缺少的功能 基于该推断的含义。这 魔术数字不再是魔术,而且 算法的结构是 令人信服的描述。

    public int score() {
        int score = 0;
        int frame = 0;
        for (int frameNumber = 0; frameNumber < 10; frameNumber++) {
            if (isStrike(frame)) {
                score += 10 + nextTwoBallsForStrike(frame);
                frame += 1;
            } else if (isSpare(frame)) {
                score += 10 + nextBallForSpare(frame);
                frame += 2;
            } else {
                score += twoBallsInFrame(frame);
                frame += 2;
            }
        }
        return score;
    }
    

    精心挑选的名字的力量是 他们超载的结构 带有描述的代码。那 超载设置了读者的 对对方的期望 模块中的功能。你可以 推断执行 isStrike() 查看上面的代码。当您阅读 isStrike 方法,它将是“几乎什么 你期望的。”

    private boolean isStrike(int frame) {
        return rolls[frame] == 10;
    }
    

    N2:选择适当抽象级别的名称

    不要选择能交流的名字 执行;选择名称 反映抽象层次 你正在工作的类或函数 中。这很难做到。再次,人们 只是太擅长混合水平 抽象。每次你做一个 传递您的代码,您可能会 找到一些名为的变量 太低的水平。你应该采取 有机会更改这些名称时 你找到他们。使代码可读 需要持续不断的奉献精神 改进。考虑 调制解调器 界面如下:

    public interface Modem {
        boolean dial(String phoneNumber);
        boolean disconnect();
        boolean send(char c);
        char recv();
        String getConnectedPhoneNumber();
    }
    

    起初这看起来不错。这 功能似乎都合适。 事实上,对于许多应用来说,它们 是。但是现在考虑一个应用程序 其中一些调制解调器未连接 通过拨号。相反,它们是连接的 通过硬接线永久 一起(想想电缆调制解调器 为大多数人提供 Internet 访问 现在的家)。也许有些是 通过发送端口号连接到 切换 USB 连接。 显然电话号码的概念是 在错误的抽象级别。一种 更好的命名策略 场景可能是:

    public interface Modem {
        boolean connect(String connectionLocator);
        boolean disconnect();
        boolean send(char c);
        char recv();
        String getConnectedLocator();
    }
    

    现在这些名字没有任何意义 关于电话号码的承诺。他们 仍可用于电话号码, 或者它们可以用于任何其他 一种连接策略。

    N3:尽可能使用标准命名法

    如果名字更容易理解 基于现有约定或 用法。例如,如果您正在使用 DECORATOR 模式,你应该使用 名称中的装饰器 装饰课。例如, AutoHangupModemDecorator 可能是 装饰调制解调器的类的名称 具有自动挂起的能力 在会话结束时。模式 只是一种标准。在 例如,Java 中的函数 将对象转换为字符串 表示通常被命名为 toString。最好遵循 像这样的约定而不是发明 你自己。团队经常会发明 他们自己的标准名称系统 一个特定的项目。埃里克·埃文斯 将此称为无处不在 项目的语言。你的代码 应该使用此条款 语言广泛。简而言之, 更多你可以使用的名字 带有特殊含义的重载 与您的项目相关, 读者更容易知道 你的代码在说什么。

    N4:明确的名称

    选择能够发挥作用的名称 明确的函数或变量。 看看 FitNesse 的这个例子:

    private String doRename() throws Exception
    {
        if(refactorReferences)
            renameReferences();
        renamePage();
        pathToRename.removeNameFromEnd();
        pathToRename.addNameToEnd(newName);
        return PathParser.render(pathToRename);
    }
    

    这个函数的名字没有说 除了广泛的功能外,该功能的作用是什么 和模糊的术语。这强调了 有一个功能的事实 在函数中命名为 renamePage 命名为 doRename!名字说明了什么 你关于两者的区别 两个功能?没有。更好的名字 因为那个函数是 重命名PageAndOptionallyAllReferences。 这可能看起来很长,而且确实很长,但是 它只从一个地方调用 模块,所以它是解释值 超过长度。

    N5:对长范围使用长名称

    名字的长度应该是相关的 到范围的长度。你可以 对 tiny 使用非常短的变量名 范围,但对于大范围,你应该 使用更长的名称。变量名称如 ij 如果他们的范围就好了 是五行长。考虑这个 sn-p 来自旧标准“保龄球 游戏”:

    private void rollMany(int n, int pins)
    {
        for (int i=0; i<n; i++)
            g.roll(pins);
    }
    

    这是非常清楚的,将是 如果变量 i 是混淆的 取而代之的是烦人的东西 滚动计数。另一方面, 变量和函数短 名字长期失去意义 距离。所以范围越长 名字越长越准确 名字应该是。

    N6:避免编码

    名称不应编码 带有类型或范围信息。 m_f 等前缀是无用的 在当今的环境中。还有项目 和/或子系统编码,例如 vis_(用于视觉成像系统)是 分散注意力和多余的。再次, 今天的环境提供了所有这些 无需篡改的信息 名字。保持你的名字免费 匈牙利污染。

    N7:名称应描述副作用

    名字应该 描述一个函数的一切, 变量,或类是或确实。别 用名字隐藏副作用。别 用一个简单的动词来描述一个 功能不止于此 简单的动作。例如,考虑 来自 TestNG 的这段代码:

    public ObjectOutputStream getOos() throws IOException {
        if (m_oos == null) {
            m_oos = new ObjectOutputStream(m_socket.getOutputStream());
        }
        return m_oos;
    }
    

    这个函数的作用远不止 get “oos”;如果它创建“oos” 尚未创建。因此,一个 更好的名字可能是 createOrReturnOos

    【讨论】:

    • 我以前从未听说过“匈牙利污染”这个词。知道为什么这么叫吗?
    • 这是对 Hungarian notation 的贬义引用,在现代 IDE 轻松检查类型信息之前,这曾经是一种常见做法。
    【解决方案4】:

    kekoav 一样,我对命名 FooManager 或 FooController 持谨慎态度。它不仅表明了一个遭受身份危机的对象,而且还引入了歧义。我在一个项目中工作过,其中有一个“AccountManager”,它是一个域对象,代表一个管理帐户的人(员工的子类型)。当然,有人做了一个“AccountManagerManager”,其他人搞糊涂了,创建了一个 Account 域对象(这不是我们的应用程序处理的东西),然后将一些东西混合到 AccountManager 中来管理 Account。有点乱。

    Quis custodiet ipsos custodes? Quoque quis administro ipsos administratorum?

    是的,我的拉丁文生疏了。

    【讨论】:

    • “Quis custodiet ipsos custodes”是“谁来守卫?”这是 Juvenal 的一首讽刺诗中的一句台词。我可能会修改的内容类似于“同样,谁将管理经理?”
    【解决方案5】:

    我讨厌看到像下面这样命名的类:

    XxxHandler XXX经理 Xxx控制器

    Handler 和 Manager 很难描述类是什么。如果我们不能为一个类找到一个好名字,我们可能应该确保我们的类所做的事情是明确的,而不是试图做太多的事情。较小的类通常更容易命名类,因为它们的作用范围通常很窄。

    但有时,我们用完了可以调用事物的创造性事物,而只是恢复到相同的旧事物。

    【讨论】:

    • 我认为很多时候这只是你必须继续前进的事实。与其想着有史以来最伟大的名字,我宁愿编写代码来完成任务。
    【解决方案6】:

    1) 不要缩写。仅当它们是行业标准的缩写词(如 HTML)时才使用缩写词。

    2) 在你的名字中保持一致。

    3) 使用您的应用程序所在域中的术语。在提出名称时,假设您在添加人们不熟悉的新名称时可以使用有限数量的项目符号。

    您可能会觉得您的班级中有一些微妙的特征将它与“Foo”区分开来,并因为这种微妙的区别而倾向于为它创造一些新概念。如果使用它的 80% 的人不会关心这种细微的差异,那就坚持使用已知术语。

    4) 在大多数 OO 语言中,自动完成很重要。如果可能,第一个词应该是人们在探索命名空间时最有可能输入的词。其他人觉得用英文读得好更重要,所以这有点值得商榷。

    【讨论】:

      【解决方案7】:

      一个好的点是使用设计模式,你会做的一切:)

      【讨论】:

        【解决方案8】:

        为事物选择简洁、信息丰富、恰当的名称是编程中最困难的部分之一。

        没有公式。这是您通过经验掌握的技能。

        注意对简洁的强调。 Java 中的长名称是该语言的主要障碍之一。它基本上已经到了没有带有自动完成功能的 IDE 就不可能用 Java 编程的地步。

        在命名时,缩写可以成为您的朋友。例如,“经理”而不是“经理”。当“经理”明白这一点时,我真的需要每天输入“经理”一千次吗?只要确保人们始终如一且明智地使用它们。

        【讨论】:

        • 我认为现在使用缩写或不缩写基本上是一个随意的决定,IDE 自动完成。
        • 好吧,也许不是随意的,而是基于个人喜好。
        • Mgr 对我来说意味着微薄或偏头痛。老实说,把它打出来并不难,而且我从来不需要每天做数千次 -> 还有自动完成和其他工具。我宁愿清楚,所以我不必破译认为是一个明显简洁的名字,我不知道它是什么。射击,而不是 Mgr,为什么不只是'm'?
        • @kekoav,我们中的一些人仍在使用 cough C++ cough 语言工作,而自动完成功能很糟糕。在许多动态语言中,自动完成是没有希望的。另外,我认为自动完成已经毁了很多程序员。他们使用自己的 IDE 作为拐杖。不过,对于 Java,这是不可避免的。类型系统的僵化和狭隘只会导致怪物名称。更不用说缺少可以解决几乎所有这些问题的 typedef。他们在想什么? AbstractVisitorAdapterFactoryImplementationStrategy
        • @rball,嗯,当人们在一个团队中工作时,他们通常会提出约定。 'Mgr' 将是一种约定,可以在项目过程中节省数万(可能是数百)次击键。注意到我在哪里说“始终如一且明智地”吗?
        【解决方案9】:

        有一个你坚持的标准,然后就不会有任何混乱。如果您的页面名为 AccountManager,那么按照我的标准,它的意思是“您管理帐户的页面”。

        对类名、事件等进行类似操作。

        【讨论】:

        • 如果它是代表管理帐户的员工的域对象怎么办?管理经理的东西叫什么?帐户经理经理?
        • AccountManagersManager。我想如果你不喜欢“经理”这个词,那就叫它编辑器。当然可能会有冲突,但你真的在乎吗?命名并继续前进! :P
        【解决方案10】:

        如果您处于 .NET 环境中,则可以使用 FxCop 作为指南。至于命名一个类,我通常会用名词/目的。你说得对,Manager 类是做什么的?如果它是一个控制器类,则称它为 ManagerController。

        【讨论】:

        • 如果它管理ManagerControllers呢?管理器控制器管理器? :P
        • 听起来你需要一个抽象工厂。
        猜你喜欢
        • 2014-09-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-09-27
        相关资源
        最近更新 更多