以简求快--java快速开发框架
照例先发发牢骚,然后再进入正题。
领导说每个人应该看清自己的短板,我看的很清,我只是没上过他们所谓的学而已,这并不能成为我发展的障碍,更不能作为贬低我价值的理由。
最会做生意的人是,把成年的蛋鸡杀掉,买一批刚出生的小鸡,然后用尽各种手段逼着小鸡下蛋。
开始吧。如题,我们某些公司或者仅仅只是一个开发团队,如果不是遇到一些特殊的规则,总是要求程序员拼命的加班,努力的缩短开发周期,而且还要求比较好的编码质量。正所谓:多快好省。这就是悖论,不可能在更短的时间内做更多的事情又要花更少的钱,谁也不是爱上报纸的雷锋,谁也不是编码机器人。那么口中所说的某些公司或者开发团队是指什么样子的呢?就我目前的阅历来说,很容易理解。小弟我工作一年有余,经历过两家公司,第一家是做了两个月的实施,第二家现在还在职。我们的研发部曾经号称有数字校园产品部和互联网产品部,加起来才仅仅不过二十来人。我们这些家伙是新研发部的第一批员工,但是由于公司传统,一一离开了。
我所说的公司和团队就鲜明了:研发部力量薄弱,甚至不愿意更多的投入研发成本,要求以最快的开发速度完成项目。时间就是金钱,缩减成本和节约时间都是赚钱。在这种开发环境下,我们务必要使用更快速的开发框架。这种开发框架要求,1:如果不是特别有技术要求的项目,任何一个培训一个两个月的程序员都可以“按时”完成任务。2:要有自由的代码,条条大路通罗马,不要求就是简单,自由就是快速。3:所谓快,目前仅仅局限于开发速度快,至于运行效率快否并不是首要考虑因素。必定像淘宝这种级别的应用,应用我所说的快是没有意义的。
背景:我是一个.Net程序员,自学未成才,一直使用某经理带来的.Net 框架Castle MonoRail。据我所知该框架应用并不广泛,只是被吹得神乎其神。当然了,如果排除运行效率的话,这不失为一款优秀的框架,正如我上面所说,如果不是特别有技术要求的项目,面对这个框架,我这种一年半左右工作经验的家伙都可以失业了。以上情况仅限于我现在这种公司,并无针对之意。快就是快,不得不承认。下面贴一段列表显示的代码,表明立场。
public void List(int page) { try { Pager pager = Pager.ShowPager(this, "app", "*", OrderByColumn("createTime"), OrderBy.Desc, page, PageSize, GetWhere()); } catch (Exception E) { WriteErrorLog(E); } } /// <summary> /// 功能: 查询条件 /// [2012-03-20 18:09 Bee]<para /> /// </summary> /// <returns></returns> private string GetWhere() { string strWhere = ""; SQLBuilderHelper.QueryString(ref strWhere, "appType", "'7'", OrAnd.And, Express.In); SQLBuilderHelper.QueryString(ref strWhere, "provider", ParamsValue("provider", true), OrAnd.And, Express.Like); SQLBuilderHelper.QueryString(ref strWhere, "title", ParamsValue("title", true), OrAnd.And, Express.Like); return strWhere; }
之前曾经使用过MS MVC3.0,光是一个分页就要在好几层里面,分别写上一坨坨代码,运行效率不见得高了,开发效率肯定的低了。Castle MonoRail中的分页使用存储过程,数据集其实就是DataTable,省去了MVC3.0中几乎为了每个页面都要建立ViewModel的烦恼,而且又去掉了冗余字段,一举N得。这里只是作为一个引子,它并不是男猪脚,此处便不多说了。
某领导曾经语重心长的说:我不走,你们怎么升职加薪。
刚开始我可以编码如吃饭,然后当我成长到需要品读框架源代码的时候,却得不到官方许可。框架是开源的,而我其实是不想触犯某领导的权威。保持权威,方能保持职位与薪水。另外一方面,我也很讨厌看米国人写的英文注释。
穷途末路,才开始寻找新的出路。我开始在公司逼出来的空闲时间制作自己的框架,从.Net到Java。很多地方,我还在一点点的完善,这并不是一朝一夕。我一直在进步,相比某些抱着大饼不撒手的好多了。
下面贴出来一段CRUD代码,以简求快。
package LML.Action.Article; //第一部分 import java.sql.SQLException; import java.sql.Time; import java.sql.Timestamp; import java.util.UUID; import LML.Core.Helper.DBHelper; import LML.Core.Helper.StringHelper; import LML.Core.System.Area; import LML.Core.System.Helper; import LML.Core.System.PageBaseAction; import LML.Core.System.Pager; import LML.Core.System.SkipCheckPower; import LML.Core.System.SysMenu; import LML.Core.System.SysPower; import LML.Model.Diary; //第二部分 @Area("Article") //第三部分 @SkipCheckPower() //第四部分 @Helper({DBHelper.class,StringHelper.class}) //第五部分 public class DiaryAction extends PageBaseAction{ //第六部分 private String title; public void setTitle(String title) { this.title = title; } public String getTitle() { return title; } private String id; private Diary diary; public String getId() { return id; } public void setId(String id) { this.id = id; } public Diary getDiary() { return diary; } public void setDiary(Diary diary) { this.diary = diary; } //第七部分 @SysMenu(MenuId="DiaryList",MenuName="日志列表",MenuPic="",MenuParent="Article",MenuLevel=2,MenuSort=1) //第八部分 @SysPower(PowerId="DiaryListPower",PowerMenu="DiaryList",PowerName="日志列表查看") //第九部分 public String List() { try { //第十部分 Pager pager=DBHelper.ShowPager(pageSize, page, "* ", " diary ", strWhere(), " order by pubTime desc"); setPager(pager); } catch (SQLException e) { // TODO Auto-generated catch block } return SUCCESS; } private String strWhere() { String strWhere=""; //第十一部分 strWhere+=SQLBuilderHelper.QueryString(strWhere,"message","like","'%",ParamValue("title",title,true),"%'","or"); return strWhere; } public String Edit() { if(id!=null&&!id.equals("")) { //第十二部分 Diary diary=(Diary) DBHelper.Find(Diary.class, id); setDiary(diary); } return SUCCESS; } //第十三部分 public String Save() { try { java.util.Date date=new java.util.Date(); Timestamp tt=new Timestamp(date.getTime()); diary.setPubTime(tt); if(diary.getId()==null||diary.getId().equals("")) { diary.setId(UUID.randomUUID().toString()); DBHelper.Create(StringHelper.ModelHtmlEncode(diary)); } else { DBHelper.Update(StringHelper.ModelHtmlEncode(diary)); } } catch(Exception ex) {} return JavaScript("alert('保存成功');parent.location.href='Diary_List.action'"); } //第十四部分 public String Delete() { try { if(id!=null&&!id.equals("")) { DBHelper.Delete(Diary.class, id); } } catch(Exception ex) { } //第十五部分 return JavaScript("alert('删除成功!');parent.location.href='Diary_List.action'"); } }
以上代码,我把我写的蹩脚的注释去掉了。现在稍微的做一下解释。
1, 第一部分是一大堆的import。
2, 第二部分是一个域的概念,对应着struts2的package概念,目前主要用于生成菜单,也起到隔离功能的作用。
3, 第三部分是为了框架的权限管理而设计,这个注解可加在类上或者方法上,用于使Action或者方法跳过验证。就算在一个权限管理比较严格的项目中,总也有几个链接是不需要验证的。
4, 第四部分是传奇,我们偶尔会怀念那种部分前后台的代码,想怎么写就怎么写。有人批判过我,说我怎么可以在“前台”写这么多逻辑代码。我对他说:有种你连一个循环也别写。我有时候会希望可以在html页面中直接调用公用类的方法(静态或者非静态),而不是一定要通过Request,那么我就这么做,搞定了他。
5, 第五部分,先说明我不是牛人,我现在做的东西并不能称之为框架,我只是在SSH的基础上稍微的改造了一下,所以我一直遵循struts2规则,这里的Action继承PageBaseAction,而PageBaseAction其实又间接继承于ActionSupport,另外PageBaseAction还实现了一些分页相关的方法,以及日志操作等。
6, 第六部分的getter和setter不用多说,和C#中的属性访问器也是大同小异,一点点不同,才能叫做个性。
7, 第七部分,用于自动生成菜单,你可以在某一处写一个链接用于初始化菜单和权限,这些收集到的菜单会指向某一个具体的链接,这当然是有用的。
8, 第八部分,用于自动生成权限。权限毋庸置疑,配合角色很好使。
9, 第九部分,CRUD,一个标准的简单模块开始了,按照常规标准即可。
10,第十部分是一个分页操作,写法如此简单。更底层一些使用了存储过程。这里并没有多余的考虑什么跨平台,我觉得对于一个我们公司做的十万元级,百万元级别的项目也没有必要做什么跨平台。谁会闲着没事把用的好好的mysql换成mssql呢?
11, 第十一部分,组合查询字符串,顺便能够过滤不安全字符,另外还有一个比较重要的作用就是分页的时候还能继续保持查询条件。这里也保证了灵活性,只是为普通程序员提供一种简单的查询字符串拼接,如果你要使用更复杂的子查询或者其他一些查询手段,或者更加优化查询,那么你完全可以自己拼接。对于一些做过数据统计的同志们来说,我觉得sql要比hql或者linq等等的要好的多!
12, 第十二部分,我总觉得没有必要每一个程序员都要在自己负责的每一个模块都要针对每一个实体类去编码一个重复的find方法,那么有这样一个公用的也算是避免重复造轮子吧?
13, 第十三部分,实体保存,主键推荐使用UUID(GUID)。
14, 第十四部分,实体删除。以上所有关于实体的操作,或者以后会出现的关于数据库的操作都经过封装,保证在事务内执行。至于效率,我不知道怎么测试。
15, 第十五部分,向网页弹出消息,这种写法在MS的MVC中写过。很方便的在后台直接输出提示,而不是导向一个VIEW。这里不仅仅向网页弹出消息那么简单,它能做的很多,比如页面导向。。其实就是简单的向页面输出一段脚本,你要相信脚本能做的事很多,不敢想象,
一个简单的Action到此结束,我觉得这应该能达到以简求快的要求。简单的意思是对简单的模块就简单,并不是说我们将不能使用它开发比较复杂的应用。多复杂才是不简单呢?
各位哥哥姐姐,我是一个脆弱的小朋友,要多多鼓励我,我会继续写下去。
我会努力的。
以后会有更多的代码贴出来,最起码要把“前台”贴出来。
这个小家伙名字叫LML,我用它写了一个简单的事例小项目叫做:蜜蜂的智慧。