论坛首页 综合技术论坛

关于用异常控制程序流程的看法

浏览 22473 次
精华帖 (3) :: 良好帖 (0) :: 新手帖 (5) :: 隐藏帖 (0)
作者 正文
   发表时间:2011-07-25   最后修改:2011-07-26

这个问题已经在iteye被讨论过很多了。robbin也发表过看法。现在我觉的有必要总结下。
围绕着这个问题的争论,主要集中在两点;一是效率,一是规范。还有少部分观点认为用异常作程序流程控制有时候会很方便。
以下面的代码为例:

public static boolean isDigit(String src){
    try{
        Integer.parseInt(src);
        return true;
    }catch(Exception e){
        return false;
    }
}

public static boolean isDigit(String src){
    return src.matches("^\\d+$")
}

private static Pattern digitRegex=Pattern.complie("^\\d+$");
public static boolean isDigit(String src){
     return digitRegex.matcher(src).matches();
}

 第一个写法是从别人的帖子里抄过来的,原帖我找不到了。当时作者认为这样写好,因为方便,只有6行代码。

个人觉的这纯粹是一种蛋疼的写法。从效率上说它比第二种写法快,但是恐怕不怎么方便的吧,感觉绕了个大弯。

实际上应该推荐第三种写法,当然占用的内存稍微多一些,但它效率最高。第二种方法写法最简单。

 

Robbin举了另外的例子

try{

   userService.add(user);

}catch(UserExistException e){

   //在此处同名添加用户已存在的处理逻辑

}

 个人认为如果为用户名字段添加了惟一性约束,这么写很好。如果没有惟一性约束恐怕就要这么做了

if(userDao.exist(user)){

   throw new UserExistException(user);

}else{

   userDao.save(user);

}

 在没有惟一性约束的情况下,为什么不这么做?

if(!userDao.exist(user)){
   return userDao.save(user);
}else{
   return USER_EXISTED;
}
public static final int USER_EXISTED=-1;

 不要说为了遵守第三范式什么的,数据量太大的时候,使用字符串做主键和增加太多约束,效率肯定降低。我参与过的所有项目,除了long型主键其它字段一律都不添加任何约束。

 

当然上面那个例子的返回值还可以改成枚举,个人更推荐使用枚举。不过这里只是个例子,本人太懒不想写太多代码。

 

究竟是不是要使用异常作为流程控制条件,恐怕还得依具体情况判断。

 

最后想说的是,不要为了面向对象而面向对象。

定义了太多的业务异常,然后依不同的业务情况去抛出它们,然后在方法的调用处捕获它们。个人一直觉的这是一种蛋疼的做法。

public LoginResult login(String username, String password){
    User user=userDao.find(username);
    if(user==null){
       return LoginResult.USER_NAME_NOT_FOUND;
   }else if(!user.getPassword.equals(password)){
       return LoginResult.INCORRECT_PASSWORD;
   }else{
       return LoginResult.LOGIN_SUCCESS;
   }
}

 如果采用异常恐怕得这么做

public Userlogin(String username, String password){
    User user=userDao.find(username);
    if(user==null){
       throw new UserNotFoundException(username);
   }else if(!user.getPassword.equals(password)){
       throw new IncorrectPasswordException(user,password);
   }else{
       return User;
   }
}
 

为什么不定义一系列枚举,依照不同的业务处理结果返回不同的枚举值?

单看上面的两段代码恐怕没法判断哪个更简单。但是逻辑显然是差不多的。

整个登录流程肯定不会就这么完了,上面只是service层的代码。在action层肯定还要根据service的执行状况做后续处理。

单纯从登录这个操作来看,个人更倾向于第一种做法,到了action层,只需要简单的向浏览器响应枚举值对应的字符串作为结果即可,代码大大减少,而且效率绝对比异常控制要高。

在浏览器端可以根据返回的字符串做后续处理。如果登录流程没有js参与,action也可简单的根据返回的枚举值做后续处理。

 

至少直到目前为止我还没在开发过程中发现非用异常做流程控制不可的理由。

当然hibernate里面用了大量异常作流程控制。如果它不这样做,恐怕就要增加不少数据库访问量,这样在异常方面造成的效率影响没有了,却增加了网络通讯和数据库访问量,显然是得不偿失的。因此,减少数据库访问量、网络数据流量和网络访问次数恐怕是使用异常做程序控制的最大理由。

   发表时间:2011-07-26  
写得不错。
try catch作if业务逻辑这样的代码,我写过不少。但是不懂
“实际上应该推荐第三种写法,当然占用的内存稍微多一些,但它效率最高。第二种方法写法最简单”为什么第三种效率会更高呢。

“ 不要说为了遵守第三范式什么的,数据量太大的时候,使用字符串做主键和增加太多约束,效率肯定降低。我参与过的所有项目,除了long型主键其它字段一律都不添加任何约束”这句话也让我感受深刻。不错。这次单独写项目就这么干了。
0 请登录后投票
   发表时间:2011-07-26  
第三种效率高是因为正则表达式已经被预先编译并创建了Pattern对象了。这个对象作为一个static final属性。以后每次使用时都直接使用这个Pattern对象做正则表达式验证,当然效率高。
第二种方法,是每次调用都要重新生成新的Pattern对象然后再做正则表达式验证,所以效率低,比第一种方法效率还低。
0 请登录后投票
   发表时间:2011-07-26  
不要尝试自己解决约束维护数据一致性
不要尝试自己覆盖数据库异常提供的能力,数据库异常覆盖的可能比你想象的枚举多得多
字符串不做主键一样会作为唯一索引,尤其是注册名这类的,成本和主键没什么区别
add这种操作在流水式的大量插入下,exist这种额外的读操作根本没必要,即使必要也要想法避免,而且你即使exist检查了,你保证save插入时还是在数据库中吗?
0 请登录后投票
   发表时间:2011-07-26  
ppgunjack 写道
不要尝试自己解决约束维护数据一致性
不要尝试自己覆盖数据库异常提供的能力,数据库异常覆盖的可能比你想象的枚举多得多
字符串不做主键一样会作为唯一索引,尤其是注册名这类的,成本和主键没什么区别
add这种操作在流水式的大量插入下,exist这种额外的读操作根本没必要,即使必要也要想法避免,而且你即使exist检查了,你保证save插入时还是在数据库中吗?


至于字符串做惟一索引,我并没有说只有主键才能做惟一索引。关键是一个表里面创建的索引越多,新增和修改的时候消耗的资源就越多,索引越多消耗的存储资源也越多,不知楼上是否考虑过。
数据库异常确实比自己考虑到的枚举要多,但是在具体的业务中需要关心的只有那么几种情况,把这几种情况使用枚举或整数值表示出来在业务逻辑上会更清晰,阅读代码也更容易。
其它的数据库异常可以使用通用异常处理机制统一处理。
另外我在前面说了,具体怎么做取决于具体情况,我并没有说不能使用惟一性索引,创建了索引有利于查询,对于查询频繁但增删改不多的表可以针对常用做查询条件的字段创建相关索引。
楼上没有仔细看我写的东西。
我也不反对使用数据库异常,只是认为自定义一些枚举用来分辨具体业务中常见问题有利于增强代码可读性和执行效率。

有很多公司确实只创建主键,甚至数据一致性都是用程序维护的。ebay就是这么做的。我看过一篇介绍ebay架构的文章,文章介绍了ebay在不同发展阶段采用的不同架构。文中最后介绍的架构就是只创建主键,关系和数据一致通过程序维护。至于现在最新的架构是什么,我不得而知。

楼上说的最后一个问题,个人认为如果增删改都很频繁的表并不是什么大问题。如果增删改都很频繁,当然应该考虑数据一致性更严谨的方案。但是对于创建新用户这样的问题,一般都是一旦创建极少修改,删除更少。即使删除也是在访问量极少的时间段,执行批量操作。

针对如何维护数据一致性的问题再多说一些。在维护数据一致方面,自己尝试实现确实会增加编程难度和程序复杂性,但是降低了数据库压力,数据库只管增删改查就好了,不需要再为数据一致性做出判断,这样会提高数据库执行效率,也会优化数据表的难度。在极端考虑效率的情况下,可以往这方面考虑,尤其在互联网应用。

不推荐自己解决是因为编程难度和代码复杂性,对开发人员的编程功底有很高的要求。所以在数据库压力不大,开发人员水平普遍不够高的情况下,把更多数据一致性工作交给数据库去做,毕竟数据库有成熟的解决方案。

还有我发这篇帖是为了讨论异常控制程序流程的不是为了讨论数据一致性该如何操作的的。侧重点并不在于此。
我整理下,过几天再发一篇有关数据一致性的帖。
0 请登录后投票
   发表时间:2011-07-26  
add的时候尽量不要引入一次额外读,这不是update,并且这种额外读如果没有事务性其实也靠不住,有了事务性成本又会提高
没有参照性索引的需求和数据条目唯一性需求是可以不要索引
自己实现外部唯一索引不是不可能,但面临的另一个问题就是要解决索引和数据同步和实现分布式防止主机crash,这实际是为了扩缩能力花更多成本做同样的事情,这和中小公司不用索引根本是两回事
应用系统的表多数表都是检索用的,没有索引基本就没可用性
try{  
 
   userService.add(user);  
 
}catch(UserExistException e){  
 
   //在此处同名添加用户已存在的处理逻辑  
 
}
这个实现实际很合理,因为你会发现,user一定会携带某个需要一致性约束的字段,并且这个字段也肯定会因为其他操作需要而建立了一致性要求索引,比如delete
0 请登录后投票
   发表时间:2011-07-26   最后修改:2011-07-26
> 不要说为了遵守第三范式什么的,数据量太大的时候,使用字符串做主键和增加太多约束,效率肯定降低。我参与过的所有项目,除了long型主键其它字段一律都不添加任何约束。

这只能说明你所参与的项目都不怎么样。SF上有很多企业应用的项目,就我看到的一些像 openerp, archiva, phpbb, oscommerce 等,他们不但充分使用了数据库约束,甚至有点滥用约束到变态的地步。我问过某开发者,他说,(大概意思)商业数据是非常宝贵的,程序只是为数据服务的代码,为了保护数据一致性,定义多少约束都不为过,不仅如此,只要有理由,你应该尽可能的定义更多的约束。至于效率问题,数据库系统是相当复杂的,一定有办法可以优化,如果数据库由于效率问题不能定义约束,这种数据库还有什么用?

Ebay 的数据库只用主键,这应该不可能,又不是中国铁道部,这样的大公司不可能连个专业的DBA都请不起。
0 请登录后投票
   发表时间:2011-07-26  
谢继雷 写道
> 不要说为了遵守第三范式什么的,数据量太大的时候,使用字符串做主键和增加太多约束,效率肯定降低。我参与过的所有项目,除了long型主键其它字段一律都不添加任何约束。

这只能说明你所参与的项目都不怎么样。SF上有很多企业应用的项目,就我看到的一些像 openerp, archiva, phpbb, oscommerce 等,他们不但充分使用了数据库约束,甚至有点滥用约束到变态的地步。我问过某开发者,他说,(大概意思)商业数据是非常宝贵的,程序只是为数据服务的代码,为了保护数据一致性,定义多少约束都不为过,不仅如此,只要有理由,你应该尽可能的定义更多的约束。至于效率问题,数据库系统是相当复杂的,一定有办法可以优化,如果数据库由于效率问题不能定义约束,这种数据库还有什么用?

Ebay 的数据库只用主键,这应该不可能,又不是中国铁道部,这样的大公司不可能连个专业的DBA都请不起。


同感,关键字段该约束还是要约束,毕竟程序是人写的,总有出错总有测试漏掉的地方
0 请登录后投票
   发表时间:2011-07-26  
好贴 希望大家多发表下自己的观点
0 请登录后投票
   发表时间:2011-07-26  
完全难以接受。

效率根本不是理由,那些需要pool的东西稍稍优化一下, 就够上万次的throw了。

楼主你忽略了接口定义本身的明确的自解释性了。API设计语义很重要,并且Exception机制本身还有继承关系和checked\unchecked区分, 这些不是用error code以及带mask的errorcode能描述和实现的很好的。 枚举, 你会让维护人员生不如死的。
0 请登录后投票
论坛首页 综合技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics