例外处理(Exception Throwing)设计準则

这天小明问我说

我这个API 方法会回传错误代码, 呼叫端要处理这些各种不同的错误代码, 可以帮忙看看这些程式码有没有没考虑到的地方?

许多人没有使用异常处理的藉口有很多, 但是大多数归结为两种看法:

异常处理语法是不可取的, 因此以某种方式返回错误代码是比较好的做法."抛出异常"方式 的性能不如 "传回错误代码"方式

最好用 "例外" 取代 "回传错误代码"

我们看看下面的示範程式码:

switch( checkLogin() ){   case -1:      //Invalid credentials      ...      break;   case -2:      //Too many login attempts      ...      break;   default:      // Successful      break;}

以上程式码有两个问题

为了知道执行 checkLogin 方法的回传值 "-1" 是什么意思, 我必须看一下 checkLogin 方法的实作内容.更改错误代码值怎么办? 我将必须检查 checkLogin 方法的所有用法, 才能更改接收到的回传值!

您可以採用另一种方​​法来解决前面所述的2个主要问题

switch( checkLogin() ){   case ErrorCode.INVALID_LOGIN_CREDENTIALS:      ...      break;   case ErrorCode.TOO_MANY_LOGIN_ATTEMPTS:      ...      break;   default:      // Successful scenario, log in the user      break;}

但是如果我们想在 checkLogin 方法中重构一个 Extract 方法, 那将是很困难的工作: 我们必须从 checkLogin 方法中携带错误代码, 在 Extract 方法中有用到的错误代码必须将其传回去. 为了在我们的应用程序的外层中委派处理这种特殊情况的逻辑, 我们可能需要更大的灵活性.

例如以下 checkLogin 程式码, 虽然用了 enum 方式取代了错误代码的魔法数字

private ErrorCode checkLogin() {   ...   if ( hasNotValidCredentials ) {      return ErrorCode.INVALID_LOGIN_CREDENTIALS;   }   ...   if ( hasTooManyLoginAttempts ) {      return ErrorCode.TOO_MANY_LOGIN_ATTEMPTS;   }   return ErrorCode.LOGIN_SUCCESSFUL;}

当我们想要尝试 Extract 这一段程式码

if ( hasTooManyLoginAttempts ) {   return ErrorCode.TOO_MANY_LOGIN_ATTEMPTS;}

重构变成下面程式码...

private ErrorCode Extract() {  if ( hasTooManyLoginAttempts ) {      return ErrorCode.TOO_MANY_LOGIN_ATTEMPTS;  }  ???}

进行到这里, 你就会发现这还得花更大的力气才能往下做...

考虑到前面描述的问题, 在这种情况下唯一可以帮助的是用 "丢例外错误" 替换 "错误代码"

private void checkLogin() {   ...   if ( hasNotValidCredentials ) {      throw new InvalidLoginCreadentialsException();   }   ...   if ( hasTooManyLoginAttempts ) {      return new TooManyLoginAtteptsException();   }}

然后你就可以轻鬆的进行重构(Refactoring), 变成下面程式码

private void checkLogin(){   checkLoginCredentials();   checkLoginAttempts();   checkBannedUser();}

在重构过程中(Refactoring), 完全根本不需要考虑回传值的问题


如果将异常用于经常失败的代码, 则程式码执行的性能将是不可接受的. 这是一个的确令人担忧的问题. 当程式码抛出异常时, 其性能可能会降低几个数量级. 但是在严格遵守不允许使用错误代码的例外处理準则的同时, 我们也可以获得良好的性能. 有两种建议可以解决这个问题.

测试者-执行者模式(Tester-Doer Pattern)

有时候将发生例外的方法内容可以拆成两部分, 这可以提高其性能. 例如下面程式码示範:

让我们看一下Dictionary 类的indexed 属性.

var table = new Dictionary<string,int>();...int value = table["key"];

如果table 字典中不存在该键值(Key), 则索引器将引发例外错误. 在这段程式码经常执行失败的情况下, 这会导致引起执行性能问题(Performance Problem). 缓解问题的方法之一是在访问键值之前测试键是否在字典中.

var table = new Dictionary<string,int>();...if( table.Contains("key") ){   int value = table["key"];}

在上面的示例中, 包含条件的用于测试条件的成员称为"测试者".

if( table.Contains("key") )

用于执行潜在发生例外的成员(索引器) 称为"执行者".

int value = table["key"];

TryParse 模式(TryParse Pattern)

对于性能要求极高的API , 应使用Tester-Doer 更快的模式.

例如DateTime 定义了一个Parse 方法, 该方法在字符串解析失败时抛出例外. 但它还定义了一个相应的TryParse 方法, 该方法尝试进行解析, 但是如果解析失败则返回false, 并使用out 参数返回成功解析的结果.

使用此模式时, 在"try" 的功能中, 如果尝试了所有方法无效, 最后仍然失败时, 则该方法仍必须抛出例外.


关于作者: 网站小编

码农网专注IT技术教程资源分享平台,学习资源下载网站,58码农网包含计算机技术、网站程序源码下载、编程技术论坛、互联网资源下载等产品服务,提供原创、优质、完整内容的专业码农交流分享平台。

热门文章