照例先發發牢騷,然后再進入正題。
領導說每個人應該看清自己的短板,我看的很清,我只是沒上過他們所謂的學而已,這並不能成為我發展的障礙,更不能作為貶低我價值的理由。
最會做生意的人是,把成年的蛋雞殺掉,買一批剛出生的小雞,然后用盡各種手段逼着小雞下蛋。
開始吧。如題,我們某些公司或者僅僅只是一個開發團隊,如果不是遇到一些特殊的規則,總是要求程序員拼命的加班,努力的縮短開發周期,而且還要求比較好的編碼質量。正所謂:多快好省。這就是悖論,不可能在更短的時間內做更多的事情又要花更少的錢,誰也不是愛上報紙的雷鋒,誰也不是編碼機器人。那么口中所說的某些公司或者開發團隊是指什么樣子的呢?就我目前的閱歷來說,很容易理解。小弟我工作一年有余,經歷過兩家公司,第一家是做了兩個月的實施,第二家現在還在職。我們的研發部曾經號稱有數字校園產品部和互聯網產品部,加起來才僅僅不過二十來人。我們這些家伙是新研發部的第一批員工,但是由於公司傳統,一一離開了。
我所說的公司和團隊就鮮明了:研發部力量薄弱,甚至不願意更多的投入研發成本,要求以最快的開發速度完成項目。時間就是金錢,縮減成本和節約時間都是賺錢。在這種開發環境下,我們務必要使用更快速的開發框架。這種開發框架要求,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,我用它寫了一個簡單的事例小項目叫做:蜜蜂的智慧。