如何命名作为参数副本的变量?

How to name a variable that is a copy of a parameter?

我有一个方法可以处理作为参数传入的 Collection<Nodes>。这个Collection会被修改,所以我觉得先复制一份比较好。如何命名参数和局部变量,例如nodes 在下面的例子中?

List<Nodes> process(Collection<Nodes> nodes) {
  List<Nodes> nodes2 = new ArrayList<>(nodes);
  ...
}

作为另一个示例,考虑以下变量是从 String 参数解析的 int

public void processUser(final String userId) {
  final int userId2 = Integer.parseInt(userId);
  ...

当然要看实际情况。我不会使用其他编程语言的方法,例如 _ 这对于命名 bash 脚本很有用,IMO my 也不是一个好的选择 - 它看起来像一段代码从教程中复制(至少在 Java 中)。

最简单的解决方案是将方法参数命名为 nodesParamnodesBackup,然后您可以简单地使用 nodes 作为副本,或者更具体地说,您可以将其命名为 nodesCopy

无论如何,您的方法 process 有一些任务要做,也许它不是复制节点列表的最佳位置。您可以在调用该方法的地方制作一个副本,然后您可以简单地使用 nodes 作为对象的名称:

List<Nodes> process(Collection<Nodes> nodes) {
  // do amazing things here
  // ...
}

// ...
process(new ArrayList<>(nodes))
// ...

我猜,你有一个集合,你想保留原始版本并修改副本,也许对你来说真正的解决方案是使用 java.util.stream.Stream.

解决名称变量问题的一个好方法是使用能够暗示变量实际含义的名称。在您的示例中,您使用的名称没有说明方法功能或变量的含义,这就是为什么很难选择名称的原因。

在JDK中有很多像你这样的案例,例如Arrays#copyOf:

public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
    @SuppressWarnings("unchecked")
    T[] copy = ((Object)newType == (Object)Object[].class)
        ? (T[]) new Object[newLength]
        : (T[]) Array.newInstance(newType.getComponentType(), newLength);
    System.arraycopy(original, 0, copy, 0,
                     Math.min(original.length, newLength));
    return copy;
}

在这种情况下,他们调用参数original和局部变量copy,这完美地表达了返回值是参数的副本。准确地说,复制就是这个方法所做的,并相应地命名。

对您的案例使用相同的推理(考虑重构以给您的方法和变量赋予更有意义的名称)我会将 nodes 的本地副本命名为 processedNodes,以表达变量与您的方法名称一致。

编辑:

您在编辑中添加的新方法的名称也未提供有关其作用的提示。我假设它修改了通过参数传递 id 的用户的某些属性(可能在数据库中)。

如果是这种情况(或类似情况),我认为您采用合适的方法 可以适用的是每个方法都应该有一个单一的责任。根据您的方法名称,它应该处理用户,为此您需要一个 int userId。解析 String userId 的责任应该超出此方法的范围。

使用提议的方法具有以下优点:

  • 如果您必须对输入添加额外的验证,您的 class 不会改变。

  • 您的 class 将不负责处理 NumberFormatException,这必须是应用程序的责任。

  • 如果您必须处理不同类型的输入(例如 float userId),您的 processUser 方法不会改变。

这取决于您要对局部变量做什么。

例如,在第一个示例中,变量 nodes2 似乎很可能实际上是最后返回的值。我的建议是简单地称它为 resultoutput.

在第二个示例中...不太清楚您可能想要实现的目标...我想 userIdAsInt 应该适合当地人。但是,如果这里总是需要 int 并且您仍然希望将参数保留为字符串(也许您想将该验证从方法中推出)我认为将局部变量设置为 [=15 更合适=] 和参数 userIdAsStringuserIdString 暗示 String,虽然在这里接受,但不是 userId 的规范表示,它是一个 int

我习惯于起名字,反映和强调主要的事情。因此,潜在的 reader(包括几个月后的我自己)可以立即得到,仅通过其签名就可以在方法内部完成的操作。

讨论中的 API 收到一个 input ,做一些 processing 和 returns 输出。这是这里的三个主要内容。

如果不重要,做了什么处理,输入的类型是什么,最通用的就是这种形式:

List<Nodes> process(Collection<Nodes> input) {
  List<Nodes> output = new ArrayList<>(input);
  ...
}

public void process(final String input) {
    final int output = Integer.parseInt(input);
    ...

如果提供有关处理和输入类型的更多信息很重要,名称如下:processCollectioninputCollectionprocessUser, inputUserId 更合适,但是局部变量仍然是 output - 很明显 self-explained 姓名:

List<Nodes> processCollection(Collection<Nodes> inputCollection) {
  List<Nodes> output = new ArrayList<>(inputCollection);
  ...
}

public void processUser(final String inputUserId) {
    final int output = Integer.parseInt(inputUserId);
    ...

这取决于用例,有时详细说明处理更合适,已完成:asArrayasFilteredArray等而不是 processCollection.

有些人可能更喜欢 source-destination 术语而不是 input-output - 我看不出主要区别它们之间。如果这有助于用标题讲述方法故事,那就足够了。

最终归结为您想与未来的程序员交流的内容。电脑显然不在乎;是你正在与之交谈的其他人。所以最重要的因素是那些人需要知道什么:

  • 这个变量的逻辑(抽象、概念)含义是什么?
  • 关于如何使用此变量的哪些方面可能会让程序员感到困惑?
  • 这个变量最重要的是什么?

看你的第一个例子,很难理解你的程序,很难真正选择一个好名字。该方法称为process;但是一般来说,方法实现了计算过程,所以这个名字真的没有告诉我任何东西。你在处理什么?流程是什么?你为谁处理它,为什么?了解该方法的作用及其所在的 class 将有助于告知您的变量名称。

让我们添加一些假设。假设您正在构建一个应用程序来定位建筑物中的 Wi-fi 接入点。所讨论的 Node 是一个无线节点,具有子classes RepeaterAccessPointClient。假设它是一个在线处理的数据集,因此给定的节点集合可能会随时更改,以响应后台线程接收当前可见节点的更新。在方法的开头复制集合的原因是为了在本地处理期间将自己与这些更改隔离开来。最后,假设您的方法是按 ping 时间对节点进行排序(解释为什么该方法采用通用 Collection 但 returns 更具体的 List 类型)。

既然我们更好地了解了您的系统,让我们利用这种理解来选择一些名称,这些名称可以向未来的开发人员传达您系统的逻辑意图

class NetworkScanner {
    List<Node> sortByPingTime(Collection<Node> networkNodes) {
        final ArrayList<Node> unsortedSnapshot;

        synchronized(networkNodes) {
            unsortedSnapshot = new ArrayList<>(networkNodes);
        }

        return Utils.sort(unsortedSnapshot, (x,y) -> x.ping < y.ping);
    }
}

所以方法是sortByPingTime来定义它做什么;参数是 networkNodes 来描述我们正在查看的节点类型。并且变量被称为 unsortedSnapshot 来表达关于它的两件事,仅通过阅读代码是看不到的:

  • 这是某物的快照(暗示原件在某种程度上易变);和
  • 它没有对我们重要的顺序(表明在我们完成它时它可能有)。

我们可以将 nodes 放在那里,但从输入参数中可以立即看到。我们也可以调用它 snapshotToSort 但这是可见的,因为我们将它交给下面的 sort 例程。

这个例子仍然有点做作。该方法对于变量名来说真的太短了。在现实生活中,我可能只是称它为 out,因为选择一个好名字所花的时间比任何人花在弄清楚这种方法如何工作上所花的时间都要长。

其他相关说明:

  • 命名本身就有点主观。我的名字永远不会适合所有人,尤其是考虑到多种人类语言时。
  • 我发现最好的名字往往是根本没有名字。如果我可以匿名做一些事情,我会——这最大限度地减少了变量被重用的风险,并减少了 IDE 'find' 框中的符号。一般来说,这也促使我编写更紧凑、更实用的代码,我认为这是一件好事。
  • 有些人喜欢在名称中包含变量的类型;我一直觉得这有点奇怪,因为类型通常很明显,如果我弄错了,编译器通常会抓住我。
  • "Keep it Simple" 在这里和其他任何地方都一样。大多数时候,您的变量名不会帮助某人避免将来的工作。我的经验法则是,给它起一个愚蠢的名字,如果我最终对某事的含义摸不着头脑,就选择那个场合给它起一个好的名字。

简单地说,在命名变量时,我考虑了一些事情。

  1. 副本是如何创建的? (是否从一种类型转换为另一种类型?...)
  2. 我要用这个变量做什么?
  3. 名字短小,but/and有意义吗?

考虑到您在问题中提供的相同示例,我将这样命名变量:

List<Nodes> process(Collection<Nodes> nodes) {
  List<Nodes> nodesCopy = new ArrayList<>(nodes);
  ...
}

这可能只是 collection 的副本,因此得名 nodesCopy。有意义且简短。如果你使用 nodesList,那可能意味着它不仅仅是一个 Collection;还有一个 List (更具体)。

public void processUser(final String userId) {
  final int userIdInt = Integer.parseInt(userId);
  ...

StringuserId进行解析,结果为整数(int)!它不仅仅是一个副本。为了强调这一点,我将其命名为 userIdInt

最好不要使用下划线_,因为它通常表示实例变量。而 my 前缀:没有太大意义,它是 noobylocal 会做得更好)。

当涉及到方法参数命名约定时,如果方法参数表示的东西不会由任何其他变量表示,请使用方法参数名称,使该方法参数在方法体。例如,primaryTelephoneNumber 可能是 JavaBean setter 方法中可接受的方法参数名称。

如果一个事物在方法上下文中有多种表示形式(包括方法参数和局部变量),请使用能够让人们清楚地了解该事物是什么以及应该如何使用的名称。例如,providedPrimaryTelephoneNumberrequestedPrimaryTelephoneNumberdirtyPrimaryTelephoneNumber 可能用于方法参数名称,而 parsedPrimaryTelephoneNumbercleanPrimaryTelephoneNumbermassagedPrimaryTelephoneNumber 可能用于保留用户提供的主要电话号码的方法中的局部变量名称。

主要 objective 是使用的名称可以让人们在今天和明天阅读源代码时清楚地知道这些东西是什么。避免使用 var1var2ab 等名称;这些名称在阅读和理解源代码时增加了额外的工作量和复杂性。

不要太拘泥于使用长方法参数名或局部变量名;源代码是为了人类可读性,当 class 被编译时,方法参数名称和局部变量名称与机器无关。