你在命名布尔返回类型的方法时是否使用时态?

13

所以,当你编写布尔方法时,在返回方法名称中使用时态(如“has”或“was”),还是仅使用“is”?

以下是我最近编写的一个非常简单的Java方法..

boolean recovered = false;

public boolean wasRecovered()
{
     return recovered;
}
在这种情况下,"recovered" 是一个状态,可能已经在代码中的某个时刻发生或者还没有发生,因此从语法上讲,“was”是合理的。但在代码中,通常使用 "is" 命名惯例,那么它是否有相同的意义呢?
9个回答

9

我更倾向于使用IsFoo(),不考虑时态,因为这是一个被普遍理解的惯例,非英语母语的人通常也能理解。在当今全球开发行业中,非英语母语的人是一个经常需要考虑的因素。


1
直到最近几年,我从未想过非母语使用者会查看“我的”代码 - 但这种情况越来越普遍。这是一个我之前没有意识到的问题。 - Ragster
3
不仅对于非英语母语的人来说如此,即使我自己能流利地讲英语,我仍然认为有一个一致的 is 前缀比为不同(但非常相似)的用途使用不同的前缀更易读且不易混淆。 - Sasha Chedygov

4
我使用适合值含义的时态。否则,将会创建一种读起来一样但行为不同的代码。让我们看一个在.Net Framework中的真实例子:Thread.IsAlive 这个属性用现在时态表示。这意味着该值是指当前状态,并且使得以下代码非常易读:
if (thread.IsAlive ) {
  // Code that depends on the thread being alive
  ...

问题在于该属性不代表对象的当前状态,而是代表过去的状态。一旦计算出该值为true,相关线程便可立即退出并使该值无效。因此,该值只能安全地用于识别线程的过去状态,并且使用过去时态属性更加合适。现在让我们重新审视一下读起来有些不同的示例。
if ( thread.WasAlive ) {
  // Code that depends on the thread being alive
  ...

它们的行为相同,但其中一个读起来很差,因为它实际上代表着糟糕的代码。

以下是其他一些有问题的代码:

  • File.Exists
  • Directory.Exists
  • DriveInfo.IsReady
  • WeakReference.IsAlive

3
System.getCurrentTimeMillis - Steve Jessop
4
@slebetman: 确实如此,但仅适用于您应用非布尔值的情况。如果我实际询问的是“JaredPar在一秒钟前还活着吗?”,那么显然您可以回答“是”的,而不会让我认为他可能已经去世。我认为WasAlive函数不会引起混淆,或者更好的是MaybeStillAlive。另请参见hasStoppedBeatingHisWife - Steve Jessop
5
我更喜欢使用IsAlive,因为它在语义上是正确的。我们试图回答的是线程是否还活着,而不是之前是否存活过。但对于一个带有日期时间参数的方法,WasAlive会比较好——Thread.WasAlive(DateTime.Now - 5)。 - Max
10
"IsAlive"是正确的,因为它说明了在特定时刻被询问的条件。如果程序员对该条件的持续时间做出任何假设,那么这就是程序员的问题。"WasAlive"似乎表示对象是否曾经——在任何时候——存活过,这不是我们关心的。按照你的逻辑,“A.Equals(B)”没有意义,因为在下一条语句之前,A或B可能会被另一个线程修改。如果使用"A.DidEqual(B)",那么我很快就会发疯。 - Jeffrey L Whitledge
7
例如,人们可能需要等待线程更改其活动状态;因此应该有一种方法来实现这一点。或者他们可能希望在线程仍然存在时执行某些操作;因此应该有某种东西,在你手中握着它时,可以防止线程死亡。我认为其中存在模式:当您发现很难适当地命名某个东西时,这可能是由于其定义错误。API是供人类使用的。它应该用简单的术语来定义。如果您无法用一个简单的属性名称表达它,那么很可能是错误的。 - sbi
显示剩余41条评论

2
isXxx前缀是一种广泛使用的命名约定,因此通常是最好的选择。
对于有顺序要求的操作,wasXxx是适当的。例如,在JDBC中,检索数据库列的值可能在字段实际上未设置为NULL时返回零;在这种情况下,调用wasNull进行后续调用以确定实际检索执行后的情况。
对于检索属性设置,hasXxx可能更合适。这是语法偏好,例如,“对象的标志已设置”与“对象具有属性”。
然后有能力测试canXxx。例如,调用canWrite查看文件是否可写。但是这些名称可以重命名为isXxx形式,例如isWritable

我认为CanWrite和IsWriteable可能有两个非常不同的含义。CanWrite指的是实例能够将某些内容写入某个地方。而IsWriteable则指的是其他人对该实例进行写入的能力,即实例本身没有进行写入,而是被写入了。 - Marjan Venema
@Venema,我明白你的观点,但我认为它是“我(调用方法)可以写入它”而不是“它可以被我(调用方法)写入”。 - David R Tribble

2
这取决于您是否关心所涉及的财产的过去或未来状态。
为了简化语义,应该意识到有一些情况下使用IsXXX形式是值得商榷的,并且有一些非常常见的情况下只有IsXXX形式是有用的。
下面是Thread.IsAlive()的“真值表”,基于线程随时间可能出现的状态。不要考虑线程为什么会翻转状态,我们需要专注于使用的语言。
可能的线程状态随时间变化的情境:
    Past        Present     Future  
    =====       =======     =======  
 1. alive       alive       alive  
 2. alive       alive       dead  
 3. alive       dead        dead  
 4. dead        dead        dead
 5. dead        dead        alive
 6. dead        alive       alive
 7. dead        alive       dead
 8. alive       dead        alive

注意:下面我会谈论未来状态以保持一致。知道一个线程是否会死亡很可能是无法预测的,因为这是停机问题的一个子集。
当我们通过调用方法来询问一个对象时,有一个常见的假设:“在我询问的时候,这个线程是否还活着?对于这些情况,我们只关心“现在”列中的答案,并且使用IsXXX形式是可以的。
情况#1(始终活着)和情况#4(始终死亡)是最简单和最常见的。调用IsAlive()的结果不会在调用之间改变。关于语言的争论是由其他6种情况引起的,在这些情况下,调用IsAlive()的结果取决于何时调用它。
情况#2(将死亡)和情况#3(已死亡)从活动状态转换为死亡状态。 情况#5(将开始)和情况#6(已开始)从死亡状态转换为活动状态。
对于这四个(2、3、5、6),IsAlive()的答案不是恒定的。问题变成了,我关心现在的状态,IsAlive(),还是我对过去/未来的状态感兴趣,WasAlive()WillBeAlive()?除非你能预测未来,否则WillBeAlive()调用对于除了最具体的设计之外的所有情况都变得无意义。
处理线程池时,我们可能需要重新启动处于“死亡”状态的线程来服务连接请求,并且它们曾经是否存活并不重要,只要它们目前处于死亡状态即可。在这种情况下,我们实际上可能想使用WasDead()。当然,我们应该尽力保证不重新启动刚刚重新启动的线程,但这是一个设计问题,而不是语义问题。假设没有其他人可以重新启动线程,那么使用IsAlive() == falseWasDead() == true并没有太大区别。
现在来看最后两种情况。方案#7(曾经死亡,现在存活,将会死亡)实际上与#6几乎相同。你知道它在未来的什么时候会死吗?10秒钟、10分钟还是10小时?在决定该做什么之前,你会等待吗?不会,你只关心当前(现在)的状态。我们在这里谈论的是命名问题,而不是多线程设计。
方案#8(曾经存活,现在死亡,将会存活)实际上与#3几乎相同。如果你正在重复使用线程,那么它们可以循环多次地在存活/死亡状态之间切换。担心#3#8之间的区别回溯到停机问题,因此可以忽略它们。 IsAlive() 应该适用于所有情况。对于#5#6IsAlive() == false 可以起作用,而不需要添加 WasAlive()

1

我并不太在意wasRecovered。恢复是过去发生的事件,可能已经发生或者没有发生 - 这告诉你它是否发生了。但是,如果您使用它是因为某些恢复后果,我更喜欢isCachedisValid或其他描述这些后果的方式。仅仅因为您已经恢复了某些东西,并不意味着您没有再次失去它。

请始终注意,在英语中,过去分词作为形容词的用法在及物动词和不及物动词之间(也许在主动语态和被动语态之间)存在歧义。isRecovered可能意味着对象已经被其他东西恢复,也可能意味着对象已经恢复了。如果您的对象代表医院的患者,“isRecovered”是否意味着患者健康状况良好,还是有人从X光科取回了患者?因此,对于后者,wasRecovered可能更好。


1

我不确定你是否正确地思考了这个问题。使用Recovered属性的原因是因为这是对象当前所处的状态,而不是它曾经的状态。过去可能发生了一些进程(恢复),但我们现在访问该属性的事实意味着已完成的进程中有一些东西改变了当前状态,并且当前状态很重要。对我来说,“Recovered”捕捉了该状态的本质。对于这个例子(以及大多数类似情况),我会使用IsRecovered来命名指示此条件的谓词。(这也符合正常的英语:“这是一个恢复的文档。”)

在程序中,我几乎从不使用除现在时态以外的任何形式来命名谓词(IsDirtyHasCoupon)或布尔函数(IsPrime(x))。

一个例外是指示已更改的状态,可能需要重新安装的状态(DocumentWindow.WasMaximizedAtLastExit)。

通常我会使用不定式来表示将来时(ToBeCopied而不是WillBeCopied),因为软件的最佳计划有时会被修改(或取消)。

那么“isInARecoveredState()”在您的意见中更正确吗?我可能倾向于同意这一点。 - P. Deters

1

我倾向于是的。例如在错误检查方面:

$errors = false;
public function hasErrors()
{
  return $this->errors;
}

哦天啊...我绝不想学习一个有$errors = false约定的语言。PHP? - nawfal

0
方法命名的妙处在于您正在检索有关所讨论对象的信息。如果以过去式命名,则必须是关于对象的先前状态的信息,而不是其当前状态。
我能想到使用过去式的唯一原因是,如果我正在检查先前发生但现在不再存在的某些内容的缓存结果。例如,可能在像swap()调用之后检索先前的值。这在设计上是原子操作时可能会有用。虽然在实际情况中不太可能出现。

每次检索不都是关于先前状态的吗?有些人可能认为这只是语义问题,但是在状态分配和(1)检索以及(2)使用该状态之间的时间并不总是微不足道的。请参阅Jared关于thread.IsAlivethread.WasAlive的示例。 - Kevin Vermeer

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接