现实世界中的人们与状态有种奇怪的关系。那天一早,我在当地一家商店门口停了下来,为新一天将咖啡因转化为代码作准备。由于我喜欢喝点拿铁再干活,但我找不到牛奶,我就问店员。
“抱歉,我们是大骗子,没有牛奶了。”
对于程序员而言,这是中奇怪的说法。你到底是没有牛奶,还是不是。也许她想告诉我他们已经一星期没有牛奶了,但对我而言结果是一样的——浓缩咖啡的一天。
现实世界中多数情况下,人们对状态持放松态度没什么问题。然而不幸的事,程序员对此也很模糊——这就是问题了。
设想一个网店,只接受信用卡并且不向顾客提供发票,那么Order类会包含如下方法:
1 | public boolean isComplete() { |
合情合理对吧?好,即使把该表达漂亮地提取到一个方法中,而非复制粘贴到其它地方,该表达式也不应该存在。事实上它有个明显的问题。为什么?因为一个订单在付款之前是不可能发货的。因此,除非isPaid为真,否则hasShipped不可能为真,这使得表达式冗余了。为了让代码清晰,你可能仍然想用isComplete,所以它应该会像下面这样:
1 | public boolean isComplete() { |
在我的工作中,我总能看到缺失的检查和冗余的检查。这个例子太小了,但如果你再把取消和还款等功能加进去后,它就会变得更复杂,对良好的状态处理需求也会随之增加。在本案例中,一个订单仅仅包含三种案例:
- 处理中:可以添加或删除项目,但不能发货。
- 支付完成:不能添加或删除项目,可以发货
- 发货:结束。不再接受变更。
这些状态很重要,在进行操作之前你需要检查是否处于期望的状态中,并只能从当前转移到一个合法状态。简而言之,你必须谨慎保护好你的目标对象,让它在该有的地方。
但你是如何开始思考状态相关的呢?将表达式提取为一个有意义的方式是种非常好的开端,但也仅仅是开始。根本上是要理解状态机。我知道你可能还有一些CS类的糟糕记忆,但请把它们抛之脑后。状态机没有那么难。具象化它们以便于更好地理解和讨论。测试驱动你的代码,解耦有效和无效的状态和转换,保持它们的正确性。学习状态模式。当你感到舒适时,读一下合同设计。它通过每个公开方法在进入和退出时校验相关的数据,以确保一个合法的状态。
如果你的状态不正确,那就有bug,如果不终止就会破坏数据。如果你发现状态检查变成了一种干扰,那就学者运用工具、代码生成器、编织器、或者从其它方面来隐藏它们。不管你选择何种方式,思考状态能让你的代码更简单和健壮。