有没有这样觉得,以前做过的,刚做完的,或者正在做的项目,简直就是狗屎,不想去维护,不想去看以前写的代码?如果有,那么我们可以继续下面的内容。
分析一下原因,项目为什么会烂,从纯技术上去看无非有以下两个问题:
- 项目架构烂
- 代码质量差
下面我们各个击破,分别说说我的一些愚见。项目架构往往是项目经理,架构师,团队中技术较好程序员,或者呆公司较久程序员的事,由于对架构的理解不同,做事方式不同,项目的周期不同,技术选型不同,所以架构也就形形色色,但以我看来较鲜明的也就是较为守旧的数据库先行,和时下流行的领域驱动。这两者有明显的区别,我觉得后者在架构上更有优势,前者在开发周期上更有优势,而且不需要很多前期设计,但是优雅程度远不及后者,但在外包公司或者遇到很急的预算不多的项目往往会选择前者。
数据库先行架构的一般流程:
- 需求分析
- 设计数据库
- 编写或使用工具生成实体类(DTO)
- 编写逻辑
领域模型架构的一般流程:
- 需求分析
- 分析业务,整理领域对象,划分对象之间关系(理出聚合根)
- 为领域对象写行为
两者共同点:都可以采用AOP,面向对象,设计模式来提升程序的可维护性,但后者面向对象的味道更浓,从架构上讲有先天的优势。
数据库先行给项目带来的问题
1. 由于依赖数据库,团队中多数人更喜欢写sql,写sql不是坏事,但是太多的sql,甚至逻辑都采用写sql去处理,势必造成可维护性降低,原因如下:
- sql先天性决定,sql没有面向对象的概念,这意味着,它的抽象层次较低,流程式的代码很难做到高维护性,高封闭性,很多相同逻辑的代码需要重复写在不同的存储过程里面,虽然可以提炼到函数或者另外的存储过程中,但那是个不容易的事。
- 既然很多逻辑写到数据层面,那比如时间的处理,数据的转换,验证,错误的处理,很大程度也都依赖数据库,很明显,数据库相对于面向对象的语言并不擅长。这样做几乎让项目的可维护性降到最低。
- 缺少或薄弱的现代IDE的很多编码优势,如智能提示,编译期纠错,这一点使可测试性大大降低,你甚至很难做单元测试。
当然优点也有,那就是易于发布和性能会略高一筹。
2. 较好的方式,是把逻辑写到代码里,数据库只用来持久化数据,这里又有两种风格
- 使用ORM,sql为框架生成,这种方式普遍,好处是有成熟的ORM框架(如EF,NHibernate等)帮我们生成Sql,我们省去了在代码里写Sql造成如,可维护性低,被sql注入的风险,且能享受代码智能提示,编译期纠错等待遇。
- 写存储过程,这里与上一点的不一样,这种方式只是将CRUD操作写成存储过程,逻辑写代码里,这种方式的优点是利于发布。缺点是要手动写实体类和存储过程,不过我们可以使用工具生成。
数据库先行的优点
- 架构没什么复杂性,容易被团队成员理解,容易交流
- 开发周期短,成本低
说说领域模型,它更像是一种思考问题的方式,它的出现就是为了为软件开发提供一个切实可行的指导思想,里面包含太多思想,包括软件开发原则,OO思想,解决问题的思路,如何易于测试,AOP,等,感兴趣的朋友可以去系统的学习一下,会有收获的。
以上是架构上的问题,如果你有空间和时间建议你的考虑了顺序为:
- 领域驱动
- 数据库驱动,采用ORM,逻辑写程序
其他方式慎用。
回到开篇,再来说说如何写出优雅的代码,其实这是一个累积的过程,当然前提是你在进步,进步是因为你意识到自己写的代码不够优雅打算改进而采取学习和重构的结果。所以当你觉得代码烂的时候,不要放弃,不要破罐子破摔,重构吧。既然要重构,就得找到需要重构的代码,我能想到的烂代码有:
1. 过长的方法
这里是指,方法内容太长,发生这种情况,往往是封装不好的结果,往往在一个方法里去实现一个业务,而这个业务其实可是拆开到不同的对象中去,也就是这个操作不够原子性,举个例子,人喝可乐这个需求,业务流程应该是这样:给人一瓶可乐,人需要打开瓶子然后喝到不渴为止。
伪代码如下:
public class Person { pubic void Drink(Bottle bottle) { if(bottle == null) bottle = new Bottle(); if(cola.Capacity <=0) throw new Exception("没有可乐"); if(!bottle.IsOpen) { this.LeftHand(bottle); //左手拿瓶子 this.RightHand(bottle.Kou);//右手拉环 this.pull();//拉开 } var needCapactity = xx+yy-zz; // while(cola.Capacity <=needCapactity ) { cola.Capacity--; cola.Refresh(); this.ColaCapacity++; this.Refresh(); Thread.Sleep(1000); } } }
当然这段代码并不长,只是为了说明方式,看得出来,这里的问题有,验证逻辑,获取需要喝的容量,开盖的方式都写到里面。
2. 硬编码的字符串和数字
if(age>50) if(city.ToLower()=="shengzhen")
3. 职责不分明的类
4. 重复的代码
其实提高代码的重用,有几个途径:
- 继承
- 工具方法
- 使用委托
前两点都很容易理解,说一下这一点,举个DataContext事务的例子。
using(var cOntext= new DataContext()) { context .BeginTransaction(); try { context.User.GetUser(); context.User.add(new User{name="xian"}); context.User.add(new User{name="hong"}); context.Commit(); } catch { context.Rollback(); } }
以上代码很常见吧,是不是每个使用事务地方都需要这么写,其实这个时候我们可以利用委托来实现。
public class DbInvoker { public void Transaction(Actionaciton) { using (var cOntext= new DataContext()) { context.BeginTransaction(); try { aciton(context); context.Commit(); } catch { context.Rollback(); } } } }
以后用到事务的地方这样调用就行了。
DbInvoker.Transaction(cOntext=>{ context.User.Add(new User{name="xian"}); context.User.Add(new User{name="hong"}); });
是不是方便了许多?
5. 意义不准确的命名
6. UI与逻辑隔离差
这个最典型的我觉得就是比如在做WEB时,用MVC框架,却在后端网前段输出js的引用,或者长篇js,客户端的东西了。
我就不一一举例子了,最后说一句,优秀的开源项目要多看,感谢你的阅读。
本文地址:http://www.nowamagic.net/librarys/veda/detail/2305,欢迎访问原出处。