今天在公司改了一天代码,仅仅是改了dao层组件的一个控制器(负责获取session、管理事务、获取dao实例),涉及到好几个action控制类,由于action的控制模块设计的也比较恶心,一个execute方法一两百行,改起来也是浑浑噩噩,好几层的if..else。
---------------------
先前写servlet+javaBean+jdbc,我把数据库连接、数据库操作组件dao、控制器、对应主要逻辑这么几个模块分的还是比较清晰的,可以自由组合和控制,能很方便的通过继承、覆盖方法来表述特例,但最近新网站用上了struts2和hibernate就反而糊涂了,dao层到底要不要写?action该怎么设计?
---------------------
我觉得通常不好的设计会在扩展和维护时体现出问题来,可我这次的设计在编码阶段就出问题了,不改设计就不好扩展。主要问题还是挺多的,比如:
1.dao层的控制模块设计有问题,构造方法初始化此控制模块时把hibernate的session和事务统统进行了初始化和开启,并通过参数实例化一个对应的dao实例,相互耦合在了一起,以至于在通常事务操作中如果使用其他服务,而服务中又有类似事务,就导致了事务嵌套(写了例子才发现,hibernate的事务不支持嵌套,看来我得研究下他获取session与当前线程的关系了)。2.dao层的某些实现类中涉及了业务逻辑,比如部分字段使用某种服务来生成,或是添加判断账号是否匹配,或是又有什么其他花样,我一直觉得,dao层他就只管最基本的数据库与业务模型之间的写入与生成,不要去夹杂额外的逻辑才好。当然这里似乎也违背了里氏替换原则。3.dao层我设计的所有方法均是以所有业务模型的上层超类为接口,导致从数据库获取业务模型后如果碰到一些非共性的操作,就得经常作类型强制转换,这个不知道怎么解决。4.action控制层无比的乱,我设计成了常见的增删查改各一个,经过层层判断后执行对应的操作,所有业务模型均通过此来操作,带来的问题碰到特例,比如不可以重复某字段的,只得在控制模块里硬生生的加入一个判断if 。很恶心。但是如果我仅仅写好架子,把最终的那块增删查改的逻辑抽象化,让每个业务模型对应一个他的子类,根据自己情况分别实现,是否又太麻烦了,一大堆类......一大堆请求...----------------------
除此之外我还有一些问题:
1.有关里氏替换原则的,假如dao有个方法是查询,dao的某个子类重写了这个方法,过滤掉了一些“不合法”的数据,这就造成了“子类无法替代父类”的情况,所以我就疑惑了,继承和重写,就必然会破坏里氏替换原则??
2.越封装,就越只能适应更窄的使用范围(功能作用愈发的有针对性),我在封装了很多常用功能后一直有这个感觉,最近才发现设计的“粒度”很重要,且很难把握,粒度大了似乎就是造成适用范围缩小的原因,所以在此问问诸位前辈,设计一个方法,应该考虑哪些问题,或是看些什么书比较有帮助??
----------------------
最后感谢所有耐心看完并提出意见和建议的朋友!!!!
----------------------
以下是我那无比恶心的action控制层的execute方法的伪代码,boolean success = true;
if (是否在session发现了操作者的对象) {
    if (当前操作者是否有权限) {
        if (从前台获取的必要数据是否合法) {
            if (数据库操作的初始化是否正常) {
                if (添加数据到数据库是否成功) {
                    ........
                    //有时这里可能还会有针对某一种情况的分支而出现一组if else
                    try {
                     
                    } catch (......) {
                        setAttribute("内部服务错误");
                        writeLog("何时,在添加xx数据时事务提交出错");
                        success = false;
                    }  catch (......) {
                        setAttribute("内部服务错误");
                        writeLog("何时,在添加xx数据时又发生了啥啥啥错误");
                        success = false;
                    } 
                } else {
                    setAttribute("内部服务错误");
                    writeLog("何时,在添加xx数据时数据库操作错误");
                    success = false;
                }  
            } else {
                setAttribute("内部服务错误");
                writeLog("何时,在添加xx数据时初始化数据库操作单元出错");
                success = false;
            }
        } else {
            setAttribute("非法操作");
            writeLog("何时,在添加xx数据时前台参数非法");
            success = false;
        }
    } else {
        setAttribute("您没有当前操作的权限");        success = false; 
    }
} else {
    setAttribute("登录状态丢失,请重新登录");
    success = false; 
}
if (success) {
    forward ....
} else {
    forward ....
}这段代码可怎么优化??

解决方案 »

  1.   

    设计粒度是一个很让人头疼的问题,过度的设计往往导致浪费和局限性,你可以看看重构的思想和敏捷建模的一些原则,本着够用就好,不断优化的理念来做开发。什么什么不是一天建起来的,优秀的代码也不是一次就写出来的。具体到这段代码,我发现作者似乎在遵循着一个唯一返回的原则,
    其实没有必要这么固执:
    boolean success = true;
    if (未在session发现了操作者的对象) {
      setAttribute("登录状态丢失,请重新登录");
      return false;
    }
    if (当前操作者没有权限) {
      setAttribute("当前用户无权访问");
      return false;
    }
    if (从前台获取的必要数据不合法) {
      setAttribute("数据有异常,请重新输入");
      return false;
    }
    if (数据库操作的初始化正常 && 添加数据到数据库成功) {
                        ........
                        //有时这里可能还会有针对某一种情况的分支而出现一组if else
                        try {
                         
                        } catch (......) {
                            setAttribute("内部服务错误");
                            writeLog("何时,在添加xx数据时事务提交出错");
                            success = false;
                        }  catch (......) {
                            setAttribute("内部服务错误");
                            writeLog("何时,在添加xx数据时又发生了啥啥啥错误");
                            success = false;
                        } 
                    } else {
                        setAttribute("内部服务错误");
                        writeLog("何时,在添加xx数据时数据库操作错误");
                        success = false;
                    }  
    }
    return success;
      

  2.   

    十分感谢jinxfei,适当够用两词真得好好体会
      

  3.   

    大量的逻辑判断应放在service里去做
    struts2可以用拦截器等实现一些异常应统一处理
    不应设在action中给与提示还有,如果有大量的逻辑判断,然后要根据不同的情况返回给页面不同的错误提示,可以在service中
    使用抛出自定义异常的方式给与提示,或在service中返回信息到action中,从而在action中
    只用拿出那个消息,再给显示在页面上。addFieldError()等错误,有错误就返回INPUT
      

  4.   

    返回的状态信息如果很多的话就不应该用boolean(比如出现很多不同的错误信息 如果说boolean仅仅是为了判断属于哪个页。。那就更没必要了。。) if else套if else可以改成 if else if 来搞定 那些try/catch大部分可以去掉 可以配置下异常显示的专用页 毕竟这些错误不是经常出现的(如果经常出现的话那就是逻辑有问题了 拿try/catch做条件判断也太无耻了。。) 至于记录日志信息的话不用把日志信息记录成那个样子。。 直接把错误 异常往里一贴就完事。。像权限判断这种东西应该放到过滤器里去判断 实际上代码上有很多木有用的地方(比如那几个判断参数非法的地方 实际上完全可以通过其他手段组织发生这类问题 毕竟这是Action 不需要考虑太多的可能发生的事 毕竟是由其他页面到达这个页的 有些参数是不可能为空的)
    实际上还有很多其他的问题(如果说真的想要精确的错误信息就不应该以这种形式判断 就算判断的话 详细的错误信息至少也要放到一个常量对象里封装起来啊或者弄一个方法专门根据异常写出不同的错误日志 你这么写 有人会发疯的。。)同情LZ 哀悼LZ 围观LZ 阿门。。
      

  5.   

    DAO层参与事务,但不管理事务的生命周期,这部分内容应该放到业务层,或者一个单独的事务控制层。DAO层不应该包含任何业务逻辑。这可以用泛型解决。不明白你的意思。不过考虑到action类处于依赖关系的顶端,对它们的改动不会对系统造成太大的影响。这说明当前设计的继承关系是不合理的。Robert C. Martin的《敏捷软件开发——原则、模式与实践》一书中描述了一个长方形和正方形的例子。楼主可以去看一下。从大粒度着手进行设计,设计过程中如有必要,再进行细化。
      

  6.   

    权限控制不要放在action中,可以放在spring中配置 ,用AOP
      

  7.   

    回忆踏入这行业的第一天起,除了开始2个月(实习期)用过JDBC直接操作数据库,之后的岁月了,几乎没有再涉足过类似的编码。其实这样的现象并不少,一方面为自己基础不扎实感到羞愧;另一方面也感叹Java的分工如此明细。
    其实Java的这种分工也有其合理之处。并不是贬低楼主能力,但觉得类似的开发如果能找到通用的开源框架,就应该勇敢的舍去现有已经阻碍自己项目的实现,并大胆引入外部组件。
    其实只要有着执着的心,有着能理解优秀代码的眼光(感觉),设计就并不痛苦,亦或是一种幸福;如果若干年后回头看自己代码,能感受到当初的那份热诚,其实已经足够,有时并不需要强求。对了给楼主个建议:数据库操作应该是组件层的,而具体应用是交易层的,先把这个分开吧,否则日后组件不能重用,交易亦如此。
      

  8.   

    老大别急,分肯定是有的。要能加200分就好了,可惜我是三角
    回21楼,网站规模其实不算大,业务逻辑也不算复杂,所以没好意思把spring也整上去
      

  9.   

    servlet+javaBean+jdbc 写的代码 我都赖的看 
    一看头都大了 , 现在SSH来写好多了    
    层次分明  aciton  dao service一点也不混乱 
      

  10.   

    我的理解是,先玩好servlet+javaBean+jdbc,再来搞ssh,要不然说不定不会设计了。
      

  11.   

    这段代码太吓人了,我们做的系统中有七层的数据权限都没有那么恶心的代码对于普通的检查,我一般会抽出一个 check() : boolean 方法来,如果 check 返回 true 则执行操作。
      

  12.   

    好了,结贴,收获颇丰,谢谢各位前辈!!!!!!!!
    网站研发任务时间上不是很充足,如果时间允许的话,我打算这么改:
    1.把dao层中的逻辑全部抽出来,并在业务逻辑层中加以封装
    2.抽出action中有关权限控制及其他一些常用的逻辑到服务层,尽量使action只做组件与组件之间的调用协调工作,成为一系列组件关系的维持者
    3.修改日志系统,输出所有必要的错误异常信息
    4.简化部分意义不大的判断和异常设计