最好有2种方法具有明确的含义,或者只有1种双重用途方法?


30

为了简化界面,最好没有这种getBalance()方法吗?传递0charge(float c);将产生相同的结果:

public class Client {
    private float bal;
    float getBalance() { return bal; }
    float charge(float c) {
        bal -= c;
        return bal;
    }
}

也许在做个记录javadoc?或者,仅将其留给班级用户以弄清楚如何获得余额?


26
我很慷慨地假设这是示例代码,并且您没有使用浮点数学来存储货币值。否则,我必须全力以赴。:-)
corsiKa '16

1
@corsiKa不。那只是一个例子。但是,是的,我本可以使用浮点数来表示真实类中的钱...感谢您提醒我有关浮点数的危险。
大卫

10
某些语言区分增变器和访问器以取得良好效果。如果无法获得只能作为常量访问的实例的余额,那将很烦人!
JDługosz

Answers:


90

您似乎建议接口的复杂性是通过接口具有的元素数量(在这种情况下为方法)来衡量的。许多人会争辩说,必须记住该charge方法可用于返回a的余额,这Client比具有该getBalance方法的额外元素要复杂得多。使事情更明确的过程要简单得多,尤其是在界面不增加歧义的情况下,也是如此。

此外,呼叫charge(0)违反了最小惊讶原则,也称为每分钟WTF指标(摘自“清洁代码”,如下图),这使得团队的新成员(或当前成员,在离开代码一段时间后)很难直到他们了解该电话实际上是用来获取余额的。想想其他读者会如何反应:

在此处输入图片说明

另外,该charge方法的签名违反了做一件事情命令查询分离的准则,因为它导致对象更改其状态,同时还返回一个新值。

总而言之,我认为这种情况下最简单的接口是:

public class Client {
  private float bal;
  float getBalance() { return bal; }
  void charge(float c) { bal -= c; }
}

25
关于命令查询分离的观点非常重要,IMO。假设您是该项目的新开发人员。查看代码,可以立即清楚地getBalance()返回一些值,并且不进行任何修改。另一方面,charge(0)看起来它可能修改了某些内容……也许它发送了PropertyChanged事件?它返回的是先前值还是新值?现在,您必须在文档中查找它并浪费脑力,如果使用更清晰的方法可以避免这种情况。
法师Xy'2

19
另外,charge(0)由于获得平衡,因为它恰好没有作用而用作平衡的方式,现在让您熟悉此实现细节。我可以轻松想象一个未来的需求,在该需求中跟踪费用的数量变得很重要,这时让代码库中充斥的费用不是真正的费用,这将成为一个痛点。您应该提供表示客户需要做的事情的界面元素,而不是仅仅碰巧做客户需要的事情的界面元素。
2016年

12
CQS的建议不一定是适当的。对于charge()方法,如果该方法在并发系统中是原子方法,则返回新值或旧值可能是适当的。
Dietrich Epp

3
我发现您对CQRS的两次引用都令人难以置信。例如,我通常喜欢Meyer的想法,但是查看函数的返回类型以判断它是否发生了某些变化是任意且不可靠的。许多并发或不可变的数据结构需要突变+返回。如何在不违反CQRS的情况下追加到字符串?你有更好的引用吗?
user949300 '02

6
几乎没有代码符合命令查询分隔。它在任何流行的面向对象语言中都不是惯用语言,并且被我曾经使用的每种语言的内置API所违反。因此,将其描述为“最佳实践”似乎很奇怪。您能否命名一个实际上遵循这种模式的软件项目?
Mark Amery

28

IMO,更换getBalance()charge(0)在您的应用程序不简化。是的,它的行数减少了,但是却混淆了charge()方法的含义,当您或其他人需要重新访问此代码时,这有可能引起麻烦。

尽管它们可能得出相同的结果,但获得帐户余额与收取零费用并不相同,因此最好将您的疑虑分开。例如,如果您需要修改charge()以在有帐户交易时记录日志,那么您现在就遇到了问题,并且无论如何都需要分离功能。


13

重要的是要记住,您的代码应该是自我记录的。当我打电话时charge(x),我希望x被收费。有关余额的信息是次要的。而且,我charge()调用它时可能不知道如何实现,而且明天肯定不会实现。例如,考虑此潜在的将来更新charge()

float charge(float c) {
    lockDownUserAccountUntilChargeClears();
    bal -= c;
    Unlock();
    return bal;
}

突然间使用它charge()来获得平衡看起来并不好。


是! 好一点charge是重量级的。
Deduplicator

2

使用charge(0);获取余额是一个坏主意:有一天,某人可能会在其中添加一些代码来记录所产生的费用,而没有意识到该功能的其他用途,然后每当有人获得余额时,就会将其记录为费用。(有一些解决方法,例如一条条件语句,内容如下:

if (c > 0) {
    // make and log charge
}
return bal;

但是这些依赖于程序员知道如何实现,如果不是很明显他们是必须的,他就不会这样做。

简而言之:不要依赖您的用户或程序员后继者意识到这charge(0);是获得平衡的正确方法,因为除非有保证确保他们不会错过的文档,否则坦率地说,这似乎是获得平衡的最可怕的方法可能。


0

我知道有很多答案,但是另一个反对的理由charge(0)是,简单的错字charge(9)会导致您的客户余额在每次要获得余额时都会减少。如果您具有良好的单元测试,则可以减轻这种风险,但是如果您在每次致电时都不勤奋,则charge可能会遇到这种麻烦。


-2

我想提到一个特殊的情况,那就是减少数量,更多用途的方法是有意义的:如果存在很多多态性,即该接口的许多实现;特别是如果这些实现是在单独开发的代码中无法同步更新的(该接口由库定义)。

在那种情况下,简化编写每个实现的工作比清楚地使用它更有价值,因为前者避免了违反合同的错误(这两种方法彼此不一致),而后者仅会损害可读性,这可以通过辅助功能或超类方法限定中回收getBalance在以下方面charge

(这是一种设计模式,我不记得它的具体名称:用最小的实现者友好的接口定义一个复杂的调用者友好的接口。在Classic Mac OS中,用于绘图操作的最小接口称为“瓶颈”。但这似乎不是一个受欢迎的术语。)

如果不是这种情况(几乎没有实现或只有一个实现),则为了清楚起见而将方法分开,并允许将与非零费用相关的行为简单地添加到charge()中就有意义。


1
确实,在某些情况下,我选择了最小的界面以简化完整的覆盖范围测试。但这并不一定要暴露给班级的用户。例如,insertappenddelete都可以是常规替换的简单包装,该替换具有经过全面研究和测试的所有控制路径。
JDługosz

我看过这样的API。Lua 5.1+分配器使用它。您提供一个函数,并根据传递的参数确定是要分配新内存,取消分配内存还是从旧内存重新分配内存。编写这样的函数很痛苦。我宁愿Lua刚给您提供两个功能,一个用于分配,一个用于释放。
Nicol Bolas

@NicolBolas:没有用于重新分配的内容吗?无论如何,realloc在某些系统上,有时它与几乎具有相同的附加“好处” 。
Deduplicator

@Deduplicator:分配与重新分配是...好。将它们放在相同的函数中并不是很好,但是它不比将释放分配到相同的函数中那么糟糕。这也很容易区分。
Nicol Bolas

我要说的是,将释放分配与(重新分配)混合是这种接口的一个特别糟糕的例子。(重分配有一个非常不同的合同:它并没有导致一个有效的指针。)
凯文·里德
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.