锁定老帖子 主题:编程中一个很常见的问题,有帮助的
精华帖 (1) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (17)
|
|
---|---|
作者 | 正文 |
发表时间:2008-12-12
最后修改:2008-12-12
抛出异常的爱 写道 fjlyxx 写道 难道你们的系统都没有进行过重构吗? 好的系统是重构出来的,好好的看看重构的原则,如果你没有这么做过,你怎么会发现模式的重要。如果你的接口都是随便定义的那么你怎么能体会这两种方式的区别呢?
public Object findUser(final Map parameters) { return this.getSqlMapClientTemplate().queryForObject("findUser", parameters); // | | | // 返回结果 组装SQL并执行数据库操作 根据用户输入 这完全是一个不合格程序员写的代码。 去看看自己作的系统中有多少这样的情况,不是我自大,我只是想提醒这么一种常见的错误,让后辈人少走点弯路。 我见过很多这样的代码..... 很多很多..... 容我再吐一次回来跟你说. 吐完回来写出你解决当前问题不吐的代码? 这是逻辑代码的分层和拆分合并的粒度问题。It totally depends on your business logic. LZ给的代码有明显错误。 我猜想想表达的意思是 第一个: public void test() throws [color=red]E2,E3,E4,E5,E6[/color]{ test10(); } public void test10() throws E2,E3,E4,E5,E6{ [color=red]logicOfTest1();[/color] test2(); } public void test20() throws E3,E4,E5,E6{ [color=red]logicOfTest2();[/color] test3() } public void test30() throws E4,E5,E6{ [color=red]logicOfTest3(); [/color] test4() } public void test40() throws E5,E6{ [color=red]logicOfTest4();[/color] test5() } public void test50() throws E6{ [color=red]logicOfTest5();[/color] } 第二个: public void test() throws E1{ test1(); test2(); test3(); test4(); test5(); } public void test1() throws E2{ logicOfTest1(); } public void test2() throws E3{ logicOfTest2(); } public void test3() throws E4{ logicOfTest3(); } public void test4() throws E5{ logicOfTest4(); } public void test5() throws E6{ logicOfTest5(); } 1 第一个方法和第二个方法的下层方法完全没有可比性,因为所包含的逻辑不同,不同的原因是根据当时的场景分拆和合并。下层的代码还要被其他上层代码调用。 2 第一个方法中,上层代码调用下层代码中对于异常也不用更高层次的业务逻辑语义进行封装,直接抛出下层的E2,E3,E4,E5,E6 |
|
返回顶楼 | |
发表时间:2008-12-12
第一种相当于一个A调B,B调C,C调D。。。。,只有等到调用最后一个结束之后才能回归,这是浪费资源的,也就是说前面的 test必须等到它所调用的test结束之后才能继续;而第二种做同样的事就必免了类似的浪费,它是依次调用,直到结束。
如果是我,我就选第二种。 |
|
返回顶楼 | |
发表时间:2008-12-12
最后修改:2008-12-13
1.难点
1 修改接口(调用函数重命名) 需要修改相应的调用代码片段,建议保留旧接口,用旧接口去调用新方法. 类似适配器 2 增加异常处理throws语句 需要修改相应的调用代码片段,这个比较难解决.(我在帖子里加这么多异常也就是要说明这个问题) 3 数据库 一般不考虑,除非你有很成熟的设计了 4 颗粒度 和业务相关,帖子里的testx()可以抽象为业务颗粒度,横向和纵向的把我 就在这里,最难把握的地方,不过如果你有成熟的设计文档可以根据那个走。 5 入参和返回值定义 对于委托关系是很难的,我曾经说过如果你发现你的接口定义很简单 那么就只有两种可能,第一 你考虑的不全面,第二 用不用接口无所谓。 接口的定义是复杂的如果要和扩展以及兼容性联系在一起。 第一种实现接口定义很简单 因为当TESTX直接把自己的处理结果给TESTX+1 这里有又一个问题当TESTX逻辑改变了 那么是否会影响TESTX+1 。 写程序的时候 经常会这样做的,就比如SQL的执行,A生成了SQL 它不返回 直接把SQL传到B B拿到后就执行查询生成结果集RS C又直接将RS给D 这样是做已经司空见惯了。可是这里面出现了多少问题,最起码安全和健壮就没可更别说可读性和扩展,维护性。 3.方法重构 Extract Method 将能独立的代码片段独立出来,并且用函数名来解释这个代码片段是干什么的.颗粒度要尽可能的小.函数命名规则应该是说明这个函数是做什么而不是怎么做.检查变量,考虑入参和返回值.(变量有局部变量和公共变量的区别.如果是局部变量必须声明临时变量) 4.长表达式 将复杂表达式的结果存放在一个临时变量里面,并且用这个临时变量的名词来解释这个表达式的意义 5.提炼类 超类,子类,同级别的类,辅助类等 你都需要进行提炼。 假设A B两个类 A B如果有共同的方法 那么你需要提炼出一个父类,不同的方法是同级类,一些无状态通用的 可以进行辅助类的提炼。 6.隐藏委托关系 就是我要重点说明的问题 |
|
返回顶楼 | |
发表时间:2008-12-12
最后修改:2008-12-13
这个帖子 其实就是要描述 隐藏委托关系 的坏处 不好意思表达不清楚
关于委托: 虽然第一种方法和第二重方法实现功能完全一样,但是你想想 第一种方法的每个方法间 比如A B C D B是和A去约定的 C是和B去约定的 类推,第二种不一样 B C D都和A进行约定。在结构上 这已经是很大的差别了。 |
|
返回顶楼 | |
发表时间:2008-12-12
最后修改:2008-12-13
2.重新组织数据 用对象代替数据值 用对象取代数字 这样比较容易明白什么意思
比如 String system_app_name="xxxxx" 和你直接写 xxxxxx 虽然逻辑上一样 但是 我怎么知道这个xxxxxx是什么意思呢 如果有 system_app_name这个说明下 就很容易理解了。 4.简化函数表达式 可以将条件判断语句,计算语句等改为函数 if(a==b) 你不能看出为什么a==b的时候要这么做,如果写成 private boolean isXXXX(a,b) 那么你就能从isXXXX 去明白 a==b要表达的意思 简化函数表达式 是说不要写 a=a+b*(c+d*D)这样的东西 最好能明确表示出d*D是什么意思,类推。 5.合并重复的条件片段 比如下面的m(); if(p==q){ a=b; m(); }else{ a=c; m(); } 6.对于返回如果有可能if语句只写一层 这个不是很有必要的。 7.工具(ECLIPSE有带) 1.Extract Method 抽取方法 2.Push down 下移 将父类的东东移到子类 3.Pull up 上移 将子类的东东移到父类 4.Extract SubClass提取子类 5.Extract SuperClass提取超类 6.提取接口 7.生成委托 |
|
返回顶楼 | |
发表时间:2008-12-12
最后修改:2008-12-13
发现需要重构的代码工具
SourceMonitor是个源代码衡量工具,由http://www.campwoodsw.com/研发,免费下载。 这个工具只能从深度和覆盖面去说明,具体的还要和业务进行结合。 以上只是比较容易碰见的问题,希望对初学者有帮助。 |
|
返回顶楼 | |
发表时间:2008-12-13
个人感觉第一种就是在讲一种分层调用view->sevice->dao。这种业务下敢不敢写成第二种?
第二种感觉很有业务逻辑性,我一般同层调用中喜欢这样写,直观上看到了业务的逻辑。 不过还是要根据业务需求来做。 ps:很寒的异常抛出,喜欢BaseException的方式,不过要明确有那些异常抛出很头疼。最后说一句重构很重要。 |
|
返回顶楼 | |
发表时间:2008-12-13
最后修改:2008-12-13
qxhzzz 写道 个人感觉第一种就是在讲一种分层调用view->sevice->dao。这种业务下敢不敢写成第二种?
第二种感觉很有业务逻辑性,我一般同层调用中喜欢这样写,直观上看到了业务的逻辑。 不过还是要根据业务需求来做。 ps:很寒的异常抛出,喜欢BaseException的方式,不过要明确有那些异常抛出很头疼。最后说一句重构很重要。 呵呵 这是在同一个类里面的方法 你说是哪种呢?view->sevice->dao 他本来就是一个顺序执行过程,你可以这么理解 在你的view->sevice->dao之上还有一个 调用的方法TEST(), 这个方法在用了SPRING的应用中 就体现为你的SpRINGBEAN的配置。 你觉得呢? |
|
返回顶楼 | |
发表时间:2008-12-13
最后修改:2008-12-13
fjlyxx 写道 qxhzzz 写道 个人感觉第一种就是在讲一种分层调用view->sevice->dao。这种业务下敢不敢写成第二种?
第二种感觉很有业务逻辑性,我一般同层调用中喜欢这样写,直观上看到了业务的逻辑。 不过还是要根据业务需求来做。 ps:很寒的异常抛出,喜欢BaseException的方式,不过要明确有那些异常抛出很头疼。最后说一句重构很重要。 呵呵 这是在同一个类里面的方法 你说是哪种呢?view->sevice->dao 他本来就是一个顺序执行过程,你可以这么理解 在你的view->sevice->dao之上还有一个 调用的方法TEST(), 这个方法在用了SPRING的应用中 就体现为你的SpRINGBEAN的配置。 你觉得呢? 同一个类中倾向于第二种,Test()是暴露给上层的接口,test()1,test()2就像是实现该业务功能错做,逻辑上应该是平等的。逻辑上有上下级还是调用吧。 ps:写的太烂的东西重构很难,也太费时。我都不敢看我的代码。曾今见过800行的case语句。见过jsp里取log的和写jsp的成员函数。那时刚学java,影响大概到现在还在吧。 |
|
返回顶楼 | |
发表时间:2008-12-13
最后修改:2008-12-13
这不好说,分什么情况了。没有具体的场景,这两种都有自己的优势。
如果就是简单的拼个SQL,执行个查询,组装个对象,等等等等等等等。。。。还是后一个清晰一些。而且方法也容易被其他地方重用。 |
|
返回顶楼 | |