給JAVA設計開發新手的一些建議和意見
作者:飛云小俠????來自:CSDN
為了給朋友同事一些設計問題上的指導,特撰寫此文,很多觀點都是從別人的文章中獲取,有些觀點肯定也有偏頗,有些觀點也僅僅是提出并沒有做詳細論述,請多拍磚,以便改正。
【概述】
在工作中,作為一個程序員或者一個設計師,總是要設計一些函數庫或者一個框架,當然最經常的還是做項目,即使是一個項目,也會被經常改動,甚至交給別人改動。
當你做這些工作的時候,你的這些成果都是要給別人了解使用的,或者說給以后的你使用的,為了別人的方便或者為了自己的方便,我們要盡可能做好設計。
【放正心態,任何東西都是不斷發展的】
技術是日新月異的,每一天都有新的技術出來,正所謂"山外有山,人外有人",每一個新的輪子出來,都可能比你要設計的輪子好,所以在設計的時候,應該了解一下是否已經有了類似的輪子,是否要設計一個新的輪子。
即使你的輪子已經設計好了,也不好認為自己的輪子一定比別人的輪子好,雖然你的輪子可能更適合你的實際使用。
技術在不斷的發展中,你以及你的朋友/同事都在不斷進步,"士別三日,當刮目相看",所以不要認為你的水平一定比別人高,"尺有所短,寸有所長",所以別人對你的函數庫/框架提出意見,提出疑問的時候,請不要驚奇,不要反感,不要認為別人在"挑刺",也許你的函數庫/框架早就不適合當前的發展了。
態度決定一切。你的領導或許更重視這一點。
【必要的組成部分:單元測試,文檔,實例,手冊etc】
單元測試,文檔,API Doc,手冊,演示程序,Change Log,Readme,build。xml等等
有一天別人使用了你設計的函數庫/框架,當你升級后,原來的項目卻不能工作了,經過一天的調試,你終于找到了原因,原來是不小心寫錯了一個東西。
你肯定不希望上述的事情發生,那么請你寫單元測試吧,這樣既不浪費自己的時間,也不耽誤別人的工作,何樂而不為。你花在寫單元測試的時間/帶來的樂趣和你升級后改正莫名其妙的錯誤的時間和苦惱相比,肯定更有價值。你看到單元測試的綠條,難道不感到高興嗎?!
如果你不能保證你的程序修改沒有錯誤,不要指望你的同事認為你的錯誤是可以容忍的,他們在心里早就開始罵你了,呵呵。寫單元測試吧
看看任何一個知名的框架,都包含完善的文檔,單元測試,示例程序,用戶手冊,那么請你也包含這些吧。哦,對了,請詳細地寫好JavaDoc,它很重要。
使用你的框架/函數庫的人如果到處去找使用方法,去找某個類(但是他不知道是否有這個類),那么說明你的文檔沒有到位。如果你希望別人使用你的這個類或者功能,那么請寫好文檔,不要指望別人去讀你的源碼然后就能理解它是干什么用的。
如果你做到這些,那么你的函數庫/框架也有了"知名"的前提,難道不是嗎?如果沒有,我想是沒法讓別人更好地使用的。
對了,有了這些東西,還要有一個良好的目錄組織,這個也可以參考別的框架的組織方式。
【借鑒成熟的設計,參考已有的項目】
1. 要做一個新的東西,沒有想法。不要驚訝,我肯定先找一個現有的東西來借鑒。
當然前提是不要重新發明輪子,或者是你有充分條件要重新發明一個輪子。
Struts,WebWork,Spring等等都是成熟的框架,不管你使用起來是否符合你的習慣。
在你成為大師之前,你的設計思想估計前人都已經提出并實踐過了,所以要勇敢地去借鑒。"站在巨人的肩膀上"我們能更近一步。
例如我們厭倦了在訪問數據庫時使用如下的代碼:
try
{
//your code here
}
catch(Exception e)
{
//catch Exception
}
finally
{
//must do something
}
我們就可以借鑒Spring框架的JdbcTemplate類,看看它是如何利用回調函數來處理的。
我們使用hibernate時是不是也會使用類似上面的代碼,那么可以參考Spring框架的HibernateTemplate。
借鑒也是一種捷徑。
警告:借鑒但不要抄襲,借鑒代碼要注明來源,尊重他人也是尊重自己。
2. 在實際的項目中,往往可以參考已經有的項目來做自己的設計。
例如做一個網站,我不知道如何訪問數據庫,如何布局,如何分層,那么我們可以參考已經有的網站程序,看看別人是如何利用SiteMesh或者tiles布局,如何使用Hibernate來訪問數據庫或者使用已經封裝好的JDBC類來訪問數據庫,如何利用Struts,WebWork或者其他訪問來分層。
【遵守約定俗成的一些做法】
為了使別人更方便地使用你的東西,那么在設計一些通用的函數或者類的時候,請遵守通用的做法,不要與眾不同,除非你的內部實現確實與眾不同。
例如實現一個類似ArrayList的類,那么請不要這樣寫:
public int count()
{
return list.size();
}
public Item getItem(int i)
{
return list.get(i);
}
而應該這樣:
public int size()
{
return list.size();
}
public Item get(int i)
{
return list.get(i);
}
當然每個人都有自己的想法,如果你非常認為你原來的方式比普通的好,那么請提供2套方式供別人選擇。它不會給你帶來麻煩,只是一個一看就懂的做法,不用懷疑,這樣做有好處。
很多類的設計都有一些約定俗成的做法,那么在你設計一個新類的時候,先借鑒一下吧,多看看JDK的源碼/文檔,看看別人是怎么實現的。這更有助于推廣你的成果。
【不要迷信權威】
在使用已有的框架或者函數庫時,不要認為所有的東西都是正確的或者是最好的最好,肯定不是。沒有完美的東西,已經存在的東西在設計的時候因為種種局限或者因為作者的水平,對現在來說肯定存在不合理的設計,或者過于理想化的設計,而不能滿足實際情況。
不迷信權威,才能到達新的境界。
【不要輕易排斥,不了解就不要草率發表意見,要嚴謹】
在網上經常看到。Net和Java的比較/火拼,或者是Struts VS Webwork或者是其他等等,非常之多。經常看到的是一方對對方的東西不甚了解,就開始批評,結果說不到點子上,反而被嘲笑一番。
幾種技術的比較有時候是必要的,例如技術選型的時候。但是如果一些對這些技術根本不了解的人來選型,來評判,你能對結果信服嗎?
存在就是合理,任何技術都有其存在的理由,雖然有些東西早就過時了,但是在當時它也是應運而生的。
幾種技術,都是來解決同樣的問題,但是問題也有很多方面,解決方式也有很多種,每個人的想法也都不一樣,思路也不一樣,所以沒有絕對符合要求的技術,但是應該有符合你的技術,不符合你的技術不等于也不滿足別人的要求。所以不要輕易排斥別的東西。
在做技術比較的時候,如果你不了解,那么請不要輕易發表意見,至少你可以親自去了解,去實踐之后在發表你的意見豈不是更好。
在發表意見的時候,也要嚴謹,不要輕易下結論,要經過求證,否則一旦錯誤只會讓對手笑話,讓你的同事看不起你。例如你說Hibernate3不支持jdk1。3,那么最好去好好找到你的證據,否則就會成為錯誤。(Hibernate3支持jdk1。3)
作為一個技術人員,嚴謹應該是我們的習慣之一,無論做開發還是做設計。
【處理好你的異常】
異常處理是Java編程中非常重要的一個部分。建議在使用異常之前閱讀或者。
下面從書中摘出幾條建議:
* 絕對不要忽略異常
* 千萬不要隱藏異常
* 僅在不正常的情況下使用異常
* 對可恢復的情況使用可檢查異常,對程序錯誤使用運行時異常(RunTimeException)
* 給方法引發的異常做文檔
* 在詳細信息里面包括失敗捕獲信息
* 使用finally避免資源泄漏
* ....
在這里特別提出的是,在開發中要特別處理NULL的情況,否則經常引發NullPointException異常,在Java里這是一個最令人頭疼的異常了。
如果你的程序因為一個NULL值,而報了幾十個NullPointException的話,不但得讓人煩死,而且還非常難以找到錯誤所在。所以在Java中一定要注意這個問題。
如果你的函數不允許Null值,那么可以截獲它,拋出一個異常,或者給客戶更友好的提示,難道不好嗎?
讓我們來看一個例子:
public String getName(User aUser)
{
//如果aUser為Null,會發生什么情況
return aUser.getName();
}
很明顯,如果參數為Null,就會拋出異常。應該改為:
public String getName(User aUser)
{
if(null=aUser)
{
return "";
}
else
{
return aUser.getName();
}
}
或者你要求參數不能為空,還可以拋出一個異常,強制使用者不能傳入空值。
還有經常被忽略的是RunTimeException和普通異常的區別,在Java中,這是一個特殊的異常類,程序中如果遇到這個異常,用戶可以不截獲它,而如果是其他的普通異常,就不許要截獲它。我們的代碼經常這么寫:
try
{
//your code here
}
catch(Exception e)
{
//do warn
}
這樣寫的話,就截獲了所有異常,當然也包括了RunTimeException。 在很多情況下,這是不合適的處理方式,我們只應截獲必要的異常,而應該忽略RuntimeException。
關于RunTimeException,在Spring中還有更好的利用方式,建議閱讀Spring框架中在事務中對異常的處理代碼,例如對Jdbc拋出的SqlException的轉換。
關于異常處理,我提出幾點建議:
* 捕獲異常而且再次拋出時要包含原來的異常信息
* 不要忘了RunTimeException,除非必要,否則不要用catch(Exception e)的方式捕獲所有異常。
* 不要用異常做流程控制,異常的性能代價比較高昂。(對此,可能有人不同意。此處不詳細討論)
* 不要把異常處理都拋給別人,本函數有能力處理的就不要拋出。
在此建議讀者詳細閱讀或者。
【過度依賴】
在定位錯誤的時候,經常遇到瀏覽了七 八個文件還是沒有找到什么地方執行了真正需要的函數,這個時候就非常郁悶。A調用了B,B調用了C,C調用了D。。。。。。讓人找不到北
面對這樣的程序,存在的問題不僅僅是定位錯誤麻煩,而且如果需要維護這樣的函數庫/框架,恐怕你的有非常高的統御能力才行,否則打死我也不去維護。
那么我們自己最好不要寫這樣的程序出來給人用。
【濫用接口】
現在流行"面對接口編程",這本身本來是不錯,但是濫用接口的現象卻經常發生。
"面向接口",于是所有的類都有一個對應的接口,接口的函數聲明和類一模一樣,而且一個接口只有一個類來實現它。這樣的面向接口有什么意義哪? (為了用Spring的事務的情況除外)
根據"迪比特法則(Law of Demter)",一個對象應當對其他對象有盡可能少的了解。一個接口內應該只定義對方所需要的方法,而不要把一些沒用的方法聲明放在接口里面。
例如如下一個類:
public class MyCounter
{
private int n1;
private int n2;
public MyCounter(int n1,int n2)
{
this。n1=n1;
this。n2=n2;
}
public void setN1(int n1)
{
return this.n1 = n1;
}
public void setN2(int n2)
{
return this.n2 = n2;
}
public int getN1()
{
return n1;
}
public int getN2()
{
return n2;
}
public int getResult()
{
return n1 + n2;
}
}
我們可以看到,這個類的主要目的是得到計算結果,所以正確的接口應該類似:
public interface Counter
{
int getResult();
}
但是很多情況下,經常是這樣的接口:
public interface Counter
{
int getResult();
int getN1();
int getN2();
void setN1(int n1);
void setN2(int n2);
}
我們想一想,這樣做有2個后果:
1. 除了getResult之外,其他的函數我們根本用不到,所以是多余的。
2. 如果我們要自己實現一個Counter,如果接口中僅僅定義了getResult,我們僅僅需要實現它就可以了。我們自己的類可能是多個數運算,有乘除加減等等各種運算,參數也有可能是一些數組。但是如果按照第二種方法聲明接口的話,我們就必須實現后面的四個方法,如果這樣的話,實現這樣東西不僅沒用,而且浪費時間。我們恐怕要大聲罵娘了吧。
所以,接口有好的作用,但是不要濫用。
■ 如果你的接口永遠只有一個類實現,那么可能就沒有必要用接口。
■ 你的接口只需要聲明別人用到的函數即可。
【空接口的使用】
在接口使用的時候,空接口有2種情況:
1. 類似Cloneable,Serializable,他們往往是做一個標記,表示需要某個功能。當然你也可以這么用,來表示你的類具有某個功能,實現了你的某個接口。
2. 你的接口繼承了別的接口(非空),你的接口本身沒有聲明函數。這種情況一般是你不希望用戶使用父接口來作為參數類型,因為他們的用途可能不同,此時就可以用空接口來實現。
第一種情況我們不再多說,搜索一下關于Cloneable,Serializable的文章就會了解很多。
我們來看下面的代碼:
public interface Text
{
String getText();
}
public interface SqlText extends Text
{
}
可以看到,Text接口是用于返回一個字符串。而SqlText是一個空接口,它繼承了Text接口。也就是說SqlText也是一種Text。但是我們可以知道,任何一個字符串不一定是Sql字符串,所以此時聲明了一個SqlText接口來用于表名當前的字符串是一個Sql字符串。你的函數可以這樣聲明:
public void doQuery(SqlText aSqlText)
而不是這樣
public void doQuery(Text aText)
避免用戶產生歧義的想法,一眼看去,就明白應該傳入一個Sql字符串。
【繼承層次過多】
一般來說,繼承的層次不要過多,否則使用者可能會討厭,找一個函數會很麻煩。很多Java語言檢查工具都建議你的繼承層次不要超過3層。
【Has A ,Is A,不要濫用繼承】
"我是一個Mp3","我有一個Mp3",其實很容易分辨。但是在實際應用中,往往存在把"我有一個Mp3"的情況當作"我是一個Mp3",或者是為了偷懶方便而放松了對自己的要求,甚至還沾沾自喜,感覺找到一個捷徑。(scud以前也干過這種事情)。
以前我曾經這樣干過:我的邏輯類直接繼承了我的數據庫訪問類,這樣我可以直接在邏輯類里面訪問:
public MyLogic extends MyDBA
aLogic.getInt("click");
aLogic.getString("name");
看起來是非常方便,但是你的邏輯類就牢牢綁在了DBA上,是一種非常不好的做法。現在我這樣聲明:
public MyLogic
MyDBA adba;
adba.getInt("click");
adba.getString("name");
其實代碼改動不大,但是你的邏輯類不在牢牢綁在DBA身上了,何樂而不為。
其實這種現象在開發人員中間可能經常見到,我們要盡量避免。下面再來看一個例子:
//一個保存分頁信息的類
public class PageInfo
{
private int page;
private int pageCount;
private int recPerPage;
private int recCount;
//get,set method list...
}
一般的情況是,在Dao中進行分頁查詢,計算總記錄,總頁數等等,所以需要把PageInfo傳給Dao。而在邏輯類中,把傳回來的分頁信息數據推到FormBean或者是Action中。
也許你會這么想,如果我的Action或者FormBean繼承了PageInfo,豈不是要省很多事。
千萬別這么干。并不是所有的動作都需要分頁信息,你的FormBean和PageInfo沒有繼承的關系。也就是說FormBean Has A PageInfo,但是不是Is A PageInfo。
【保持外觀/行為一致】
外觀一致其實很容易理解,例如你用size()表示得到一個List的大小,那么在所有的List類中你都用size()得到它的大小,這就是外觀一致。
外觀一致讓用戶更方便使用你的函數庫,不用記住幾個不同的表示同一個功能的函數名字。或者幾個名字相同功能卻不同的函數。那就很糟糕了。
行為一致相對外觀一致就相對比較難做到,但是優秀的設計師肯定會讓他的成果行為一致,而不是出人意料的行為,也不是一套強行規定的行為。
我們來看下面的代碼:
import java.util.HashMap;
import java.util.Map;
class UserInfo
{
private String realname;
public UserInfo(String sName)
{
this.realname = sName;
}
public void setName(String sName)
{
this.realname = sName;
}
public String getName()
{
return this.realname;
}
}
public class MyTest
{
Map userInfoMap = new HashMap();
public void setUserInfo(String sName,UserInfo aInfo)
{
userInfoMap.put(sName,aInfo);
userInfoMap.put(aInfo.getName(),aInfo);
}
public UserInfo getUserInfo(String sName)
{
return (UserInfo)userInfoMap.get(sName);
}
public static void main(String args[])
{
MyTest aTest = new MyTest();
UserInfo aUserInfo = new UserInfo("王小二");
aTest.setUserInfo("兒童團團長",aUserInfo);
aTest.setUserInfo("三班班長",aUserInfo);
UserInfo 兒童團團長 = aTest.getUserInfo("兒童團團長");
if(null!=兒童團團長)
{
System.out.println(兒童團團長.getName());
}
else
{
System.out.println("兒童團團長 Not Found");
}
UserInfo 王小二 = aTest.getUserInfo("王小二");
if(null!=王小二)
{
System.out.println(王小二.getName());
}
else
{
System.out.println("王小二 Not Found");
}
}
}
可以看到,上面的代碼運行結果是"王小二",也就是說兒童團團長是王小二,王小二本身也是王小二,這一切正常。
現在我們把setUserInfo里面的第一句注釋掉:
public void setUserInfo(String sName,UserInfo aInfo)
{
//userInfoMap.put(sName,aInfo);
userInfoMap.put(aInfo.getName(),aInfo);
}
再次運行上面的代碼,我們發現兒童團團長不存在了,但是王小二還在。還可以看出,如果找"三班班長"的話,肯定也找不到,也就是說只有依據王小二的真名才能找到王小二,其他方法就不行了。
從上面的setUserInfo和getUserInfo分析,如果采用修改后的代碼,我們的程序就出現了行為表現不一致,而這是令人迷惑不解的,我們set了半天,卻找不到,豈不是令人惱火!
當然上面的代碼比較簡單,通過簡單的修改就能做到行為一致,但在實際編程中,往往因為復雜的行為操作,經常會造成行為不一致,從而給開發人員帶來困惑。
【MVC,MVC2,WEB設計編程的分層】
請閱讀文章 http://forum.javaeye.com/viewtopic.php?t=11712&postdays=0&postorder=asc&start=0
【可擴展不等于功能強大,不要夸大其辭】
現在的系統,因為接口或者其他方法的使用,都具有很大的擴展性。但是擴展性不等于功能強大。
存在一個接口,用戶可以實現自己的接口,確實非常方便。但是如果你的系統本身只實現了一個接口或者根本沒有實現,那么對用戶來說就談不上方便。
例如WebWork的validators,本身是一個接口,但是實際上本身實現的具體類很少,而且功能很差,這個時候如果你說WebWork的校驗器很厲害,那么就可能不太恰當了。當然擴展Webwork的Validator還是非常方便的。
當然,可擴展性還是需要的,但是不要吹噓,在這個浮躁的年代,讓我們多干點實事。 :)
【20/80原則】
在工作中,我經常想到20/80原則,也就是"巴雷多原則"。例如我們可以看到:
時間:我們20%的時間會產生成果的80%
產品:產品的20%帶來利潤的80%
閱讀:20%的書篇幅包括了內容的80%
工作:20%的工作給我們80%的滿意
演講:20%的演講產生影響的80%
領導:20%的人作出80%的決定
從上面可以看出,很多時候它都很有說服力。
在這里我想提到幾點,但是和上面的可能出發點有所不同:
1、程序的80%都是在處理特殊情況,所以我們一定要對特殊情況重視,不要因為是特殊情況,就不很重視。80%的客戶對特殊情況都很重視。
文檔對特殊情況也要詳細描述,因為開發人員80%的時候在查找這些東西,而對那些經常用到的用法卻很少查閱文檔。
2、優化問題:80%的瓶頸都出在20%的代碼上,所以在優化代碼的時候不需要優化所有代碼,只需要優化20%的關鍵代碼就夠了。當然追求完美的人我們就不多說了。
記得有一條優化的原則是"不要優化!不要優化",是非常有道理的。
3、如果你20%的事情做砸了,往往會導致80%的事情都砸了,或者是導致別人認為你把事情幾乎都做砸了。
如果你對一些事情發表了一些很不嚴謹的看法,那么別人會認為你在別的事情上也很不嚴謹。
依此類推,代碼質量,文檔完整性等等,都會讓人產生類似的推理。
(當然一個代碼寫的很亂的人,往往文檔也很亂。)
【強制綁定是不受歡迎的】
不要在程序中強制綁定一些額外的功能。
有的框架往往功能很多,是"大型計算機",有很多功能,但是在我需要打字的時候,給我打字的功能即可,不要強制我使用網絡功能,打印功能,負載均衡功能等等。
一般來說,如果一個東西有很多功能,那么做好做成可配置,可插拔的,這樣用戶使用你的東西,沒必要在不使用高級功能的時候,浪費用戶的內存,磁盤。開發人員還得多copy好多lib文件,占用調試時間,豈不是很麻煩。
不要買一送一,我不想要就別給我。 :)
【有時候也得考慮兼容性】
一般來說,一個公司的客戶會有很多,用戶的運行環境是各種各樣的。jdk1.3,jdk1.4甚至還有jdk1.2。這樣我們在編程的時候就必須做一些妥協,有些函數庫就不能使用。
如果這些用戶的jdk不能升級(一般來說都需要購買新的產品才能升級),或者我們必須對這些情況妥協,那么我們就要在開發中考慮這些問題。
例如以前,在Servlet 2.2的時候,因為沒有setCharacterEncoding,我們必須手動對各種字符進行轉換。當Servlet2.3的時候,可以使用這個函數了。但是為了客戶考慮,我們只好沒有升級還是使用原來的方法。(當然后來大多數用戶都使用了新的App Server,我們就可以使用filter來處理編碼問題了)。
向下兼容性確實讓人頭疼,JDK1.5也發布好久了,不過我們現在也不能使用,只能自己沒事測試測試。
在編程的時候,一定要設置好IDE的兼容性設置,防止我們使用了不能使用的特性。Jbuilder,Eclipse都有類似的設置。
【成本與現實,給用戶以選擇余地】
全文檢索,lucene,like是三種對大文本字段檢索的方法。那么你采用哪一種呢?
也許你會毫不猶豫的說"全文檢索" (我看你像TRS公司的托 :P)。
正如"強制綁定是不受歡迎的"里面所說的一樣,我還是覺得應該給用戶以選擇的余地。
全文檢索是要花錢的或者需要配置,而且一般來說數據庫專用的全文檢索都是不通用的,lucene是需要開發人員開發的,只有like最簡單了,但是太簡單了,而且性能也差。
這個時候,也許我們就應該提供幾種方式供用戶選擇了,用戶如何選擇那就看他們了。。。
【結束語】
實際開發設計中肯定還存在很多其他的問題,本文不可能一一論述。到此為止。 :)
希望各位在開發設計中成為高水平的設計師。 :)
posted on 2006-05-09 23:57 Shooper.Java 閱讀(297) 評論(0) 編輯 收藏 所屬分類: 程序心得體會