程序出错该返回啥?

程序出错该返回啥?

NULL,异常,错误码,空对象?

函数运行结果分为两类:一类预期结果,也就是正常输出的结果,一类非预期结果,异常或出错情况下的输出。

(注:以下描述中使用一个id生成器的功能来举例。id由本机名,时间戳,随机数组成。)

 

1.返回NULL

很多人认为这是种不好的设计思路,主要理由:

  • 如果某个函数可能返回NULL值,我们使用它时,忘了做NULL值判断,就可能会抛出异常。

  • 如果定义了很多返回值可能为NULL的函数,那么代码中会充斥着大量的NULL值逻辑判断,写起来比较繁琐,且跟正常业务逻辑耦合在一起影响代码的可读性。

尽管返回NULL值有许多弊端,但对于以get,find,select,search,query等单词开头的查找函数来说,数据不存在,并非一种异常情况,是一种正常行为,所以返回不能存在语义的NULL比返回异常更加合理。

对于查找数据不存在的情况,函数到底该用NULL值还是异常,有一个比较重要的参考标准是,看项目中的其他类似查找函数都是如何定义的,只要整个项目遵从统一约定即可。

 

2.返回空对象

当函数返回的数据是字符串类型或者集合类型时,我们可以用空字符串和空集合代替NULL值,来表示不存在的情况。这样我们在使用函数时,就可以不用做NULL值判断

 

3.抛出异常对象

最常用的函数出错处理方式就是抛出异常,异常可以携带更多的错误信息,比如函数的调用栈信息;除此之外,异常可以将正常逻辑与异常逻辑的处理分离开来,增加可读性。

3.1 不可恢复异常 和 可恢复异常
  • 对于bug(比如数组越界)和不可恢复异常(数据库连接失败),即使捕获了,也做不了太多事情。

  • 对于可恢复异常,明确告知调用者需要捕获处理(比如,输入提现金额大于余额的异常,捕获后则可直接使用余额)

3.2 如何处理函数抛出的异常?

一般有三种方法:

  • 直接吞掉,输出日志

  • 原封不动的向上层调用者抛出

  • 包装成新的异常向上层抛出

具体该选择哪种方式处理异常呢?三个参考原则:

  • 如果 func1() 抛出的异常是可以恢复的,且 func2() 的调用者并不关心此异常,我们完全可以在func2() 内将func1() 抛出的异常吞掉,输出日志即可。

  • 如果func1() 抛出的异常对func2() 的调用者来说,也是可以理解的,关心的,并且在业务概念上有一定相关特性,我们可以直接将func1()抛出的异常原封不动抛出。

    • 例如,获取主机名getHostName()出错,抛出错误 getHostNameError, 其调用者getHostNameLastparm()与其有业务相关性,也能理解它抛出的异常getHostNameError,则可以直接将此异常抛出

  • 如果func1() 抛出的异常太过底层,对func2() 的调用者来说,缺乏背景去理解,且业务概念不相关,我们可以将它重新包装成调用方可以理解的新异常。

    • 例如,getHostNameLastparm()抛出的getHostNameError异常,其调用方generateID()直接将其抛出,那么generateID()的调用者将无法理解这个底层异常,我们可以在generateID()内部将其包装为一个新的异常getIDError抛出。此时它的调用者将一目了然,而且也没有暴露底层实现细节,破环封装的特性

总结:异常是否继续向上抛出,要看上层代码是否关心这个异常。关心就将它抛出,否则就直接吞掉。是否需要包装成新的异常抛出,就看上层代码是否能够理解这个异常、是否业务相关。

 

 

编写一个函数,参数传进来为NULL, 空字符串如何处理?

理论上讲,参数传递的正确性应该由程序员来保证,我们无需做NULL值或空字符串的判断和特殊处理。但谁也没法保证程序员就一定不会传递NULL和空串。那怎么办?

  • 如果函数是私有的,只在内部调用,完全在自己的掌控下,保证自己调用时不传NULL和空串就行了。

  • 如果是一个公共的功能函数,无法掌控会被谁调用及如何调用,可以写上相应的注释提醒调用者;但为了尽可能提高代码健壮性,最好在公共函数中做NULL和空串判断。即使有些冗余。

 

参考来源: 极客时间 王争 老师的 《设计模式之美》 

 

posted @ 2020-11-08 12:13  Deaseyy  阅读(198)  评论(0编辑  收藏  举报