《设计模式之美》实战二(上):程序出错该返回啥?NULL、异常、错误码、空对象?

王争《设计模式之美》学习笔记

从上节课的 ID 生成器代码讲起

  • 对于 generate() 函数,如果本机名获取失败,函数返回什么?这样的返回值是否合理?
  • 对于 getLastFiledOfHostName() 函数,是否应该将 UnknownHostException 异常在函数内部吞掉(try-catch 并打印日志)?还是应该将异常继续往上抛出?如果往上抛出的话,是直接把 UnknownHostException 异常原封不动地抛出,还是封装成新的异常抛出?
  • 对于 getLastSubstrSplittedByDot(String hostName) 函数,如果 hostName 为 NULL 或者是空字符串,这个函数应该返回什么?
  • 对于 generateRandomAlphameric(int length) 函数,如果 length 小于 0 或者等于 0,这个函数应该返回什么?

函数出错应该返回啥?

1. 返回错误码

  • 在 C 语言中,错误码的返回方式有两种:
    • 一种是直接占用函数的返回值,函数正常执行的返回值放到出参中;
    • 另一种是将错误码定义为全局变量,在函数执行出错时,函数调用者通过这个全局变量来获取错误码。

2. 返回 NULL 值

  • 网上很多人不建议函数返回 NULL 值,认为这是一种不好的设计思路,主要的理由有以下两个:
    • 如果某个函数有可能返回 NULL 值,我们在使用它的时候,忘记了做 NULL 值判断,就有可能会抛出空指针异常(NullPointer Exception,缩写为 NPE)。
    • 如果我们定义了很多返回值可能为 NULL 的函数,那代码中就会充斥着大量的 NULL 值判断逻辑,一方面写起来比较繁琐,另一方面它们跟正常的业务逻辑耦合在一起,会影响代码的可读性。
  • 那我们是否可以用异常来替代 NULL 值:
    • 对于以 get、find、select、search、query 等单词开头的查找函数来说,数据不存在,并非一种异常情况,返回代表不存在语义的 NULL 值比返回异常更加合理。
    • 不过,还有一个比较重要的参考标准是,看项目中的其他类似查找函数都是如何定义的,只要整个项目遵从统一的约定即可。
    • 如果项目从零开始开发,并没有统一约定和可以参考的代码,你只需要在函数定义的地方注释清楚,选择两者中的任何一种都可以。
    • 除了返回数据对象之外,有的还会返回下标位置,比如 Java 中的 indexOf() 函数,返回值类型为基本类型 int,有两种思路:
      • 一种是返回 NotFoundException。
      • 一种是返回一个特殊值,比如 -1。
      • 显然 -1 更加合理,“没有查找到”是一种正常而非异常的行为。

3. 返回空对象

  • 返回 NULL 值有各种弊端,应对这个问题有一个比较经典的策略,那就是应用空对象设计模式(Null Object Design Pattern),后面的章节会详细讲。
  • 当函数返回的数据是字符串类型或者集合类型的时候,我们可以用空字符串或空集合替代 NULL 值,来表示不存在的情况。

4. 抛出异常对象

  • 最常用的函数出错处理方式就是抛出异常:
    • 异常可以携带更多的错误信息。
    • 异常可以将正常逻辑和异常逻辑的处理分离开来,这样代码的可读性就会更好。
非受检异常(Unchecked Exception)
  • 像 C++ 和大部分的动态语言(Python、Ruby、JavaScript 等)都只定义了一种异常类型:运行时异常(Runtime Exception)。
  • 对于运行时异常,我们在编写代码的时候,可以不用主动去 try-catch,编译器在编译代码的时候,并不会检查代码是否有对运行时异常做了处理。
  • 运行时异常也叫作非受检异常。
  • 对于代码 bug(比如数组越界)以及不可恢复异常(比如数据库连接失败),即便我们捕获了,也做不了太多事情,所以,我们倾向于使用非受检异常。
  • 非受检异常不需要显式地在函数定义中声明,那我们在使用函数的时候,就需要查看代码才能知道具体会抛出哪些异常。非受检异常不需要强制捕获处理,那程序员就有可能漏掉一些本应该捕获处理的异常。
受检异常(Checked Exception)
  • 像 Java,除了运行时异常外,还定义了另外一种异常类型:编时异常(Compile Exception)。
  • 对于编译时异常,我们在编写代码的时候,需要主动去 try-catch 或者在函数定义中声明,否则编译就会报错。
  • 编译时异常也叫作受检异常。
  • 对于可恢复异常、业务异常,比如提现金额大于余额的异常,我们更倾向于使用受检异常,明确告知调用者需要捕获处理。
Java 支持的受检异常一直被人诟病
  • 受检异常需要显式地在函数定义中声明。函数会冗长、可读性差。
  • 编译器强制我们必须显示地捕获所有的受检异常,代码实现会比较繁琐。
  • 受检异常的使用违反开闭原则。
如何处理函数抛出的异常
  • 直接吞掉。
  • 原封不动地 re-throw。
  • 包装成新的异常 re-throw。
  • 是否往上继续抛出,要看上层代码是否关心这个异常。
  • 是否需要包装成新的异常抛出,看上层代码是否能理解这个异常、是否业务相关。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值