软件设计:勿以善小而不为,勿以恶小而为之

在实现一个软件系统时,作为系统的设计、实现人员,我们往往需要在选择一个好的方案或者说设计。有些选择针对的是诸如框架等大方向的设计,但更多的时候我们面临的则是针对某个具体模块或函数等小问题的解决方案的选择。


任何一个有经验的程序员都明白,并不是所有的选择都需要经过详细的考虑(否则我们可能陷入设计陷阱而永远无法正真实现我们所需要的功能),但这并不代表对那些小问题的设计方案选择上我们可以做随意的选择。古人说过,勿以善小而不为,勿以恶小而为之;万物皆是相同的,这句话应用到软件设计中,我想就是:这些小问题上,我们不能因为它在系统中起的作用很小要就可以做出草率的决策,但也不必担心这些小模块可能会严重影响系统性能而花费大量的时间和精力来权衡设计选择。如果在任何问题上都花费大量时间和精力去做“完美”的设计,期结果好一点的无非是一个华而不实的over design,但更糟糕的则是可能导致项目延期或最终流产(毕竟任何一个设计方案都有它的局限性,要想通过设计保证程序的效率、可维护性、可扩展性等等诸多方面是不可能的);但如果完全放弃对这些小问题设计上的权衡,我们又往往可能面临是灾难性的后果。


我在最近的一个项目总就犯了一个“勿以善小而不为”的错误:在我们的系统中,由于需要大量重复访问某类资源,而每次访问这些资源都相当消耗时间。一个自然的做法是在程序中引入一个cache,这样在访问资源时我们可以先查询cache,如果所需访问的资源存在cache中了,则直接返回所需的资源,否则才做一次真正的访问并把访问到的结果放到cache中去。从框架的角度来说,引入cache从设计的角度来说是经过比较详细的考虑的:比如哪些资源需要cache,cache的时效性如何处理等等。但在考虑用什么数据结构来做这个cache时,我草率的决定使用ConcurrentHashMap。


在我们的测试环境中,这个cache产生的效果是显而易见的:资源访问对的速度能有数量级的提升,而在cache本身引入的内存开销也没有产生什么异常。但当代码部署到生产环境中后,我们观察到有些客户的主机内存消耗会突然产生一个爆发式的增长,这时由于Java GC的开销,程序性能急剧下降!究其原因,则是这个用于作为cache实现的数据结构是可以无限增长的:在客户环境下,在一段时间内有数百万条记录被存放到了这个cache中!这个问题我在最初决定使用ConcurrentHashMap就意识到过,但考虑到我们会定期的让cache失效(即清空map里面的所有元素),我并没有打算采用稍微复杂点的数据结构来处理这个问题,最终导致了在这种极端情况下出现极其糟糕的结果。


那么如何在实际工作中,在不需要增加额外的时间和精力开销情况下,避免这类问题的产生呢?很简单:根据所做的项目和自己的经验,应该有一个checklist,在面临对模块或函数的设计抉择时,通过这个checklist快速而有效的选择一个可能不是最优,但能保证达到设计目的的一个方案(即所谓的次优解)。这个checklist里面应该包含对一些极端case的考虑:对这种小问题,我们并不是在设计上解决这些极端case,仅仅需要再设计上预防这种极端case则可。有了这个checklist,我们就可以保证在不需要额外太多时间和精力开销的基础上所做出的设计选择能很好的处理正常的逻辑并保证在极端情况下仍可以正常的工作。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值