我在 關於極簡編程的思考 中曾提到要編寫可閱讀的代碼。由於代碼是編寫一次,閱讀屢次。 閱讀者包括代碼編寫者,以及後來的維護人員。能讓閱讀代碼更輕鬆,有利於加強項目或者產品的可維護性。java
本博客分爲上下倆部分,第一部分講解在代碼層次 編寫可閱讀的代碼, 第二部分講解方法,類,以及一些設計上的考慮 讓代碼更適合閱讀。這些都是我在實際工做的一些體會以及代碼審查過程當中跟同事一塊兒得出的一些經驗。沒有過高深的理論,適合全部人借鑑交流。sql
if(xxx){ return false; } if(yyy){ return false; } if(zzz){ throw new Exception(); } //主邏輯代碼在下面 ....... return true;
使用if語句,對於不符合主邏輯的,要儘早返回,這樣能夠減輕代碼閱讀者的負擔,下次再看,直接就能夠從主邏輯開始。直接跳過不關心的代碼塊(這樣代碼塊必然返回都是fasle) 以下是一個很差的例子編程
if(xxx){ return false; } if(yyy){ return true; } //主邏輯代碼在下面
在主邏輯前面分別返回了true 或者 false,閱讀者會形成混亂,由於說明這個方法任何一處都有可能返回不一樣的狀況,更糟糕的是數組
if(!xxx){ return true }else{ if(yyy){ return false; }else{ //主邏輯代碼在下面 ....... return true; } }
顯然這段代碼會形成較大的閱讀負擔性能
/* int c = getBalance(userId); if(isGreat(date){ } */ int c = getBalance(userIddate);
顯然,有多行是無用的註釋,若是當初爲了調試保留在這裏,那麼調試成功後要儘快刪除,不要給後來人留下疑惑。.net
代碼註釋會隨時過期,但IDE並無像代碼那樣能充分管理註釋,不須要的註釋應該當即刪除,以下注釋剛開始看起來不錯設計
public class User{ // 0 表示性別爲女,1表示爲男 int gender = 0; }
項目中,狀態值會隨着項目發展而不斷增長,上面的註釋會誤導閱讀者覺得性別只有倆個狀態,正確的作法是調試
public class User{ int gender = Gender.MALE.value(); }
這樣,閱讀者能夠經過枚舉類找到性別對應全部的狀態,好比 Gender.Male,Gender.FEMALE,Gender.UNKNOWNexcel
int total = a*b+c/rate+d*e;
上面的代碼一鼓作氣,且只用了一行,但沒有下面的代碼更容易閱讀code
int yearTotal = a*b; int lastYearTotal = c/rate; int todayTotal = d*e; int total = yearTotal+lastYearTotal+todayTotal;
看似其餘人一行代碼完成彷佛更牛,你用了多行代碼才完成了一個功能,但你的代碼顯然更容易被後來人閱讀。我一直以爲寫代碼就跟寫小說同樣,要看得懂纔是真正的小說,若是從任何地方切入都能看懂,那就是本好小說。
甚至能夠將一部分代碼封裝到一個方法裏,經過方法名和參數來提升可閱讀性。後來者雖然第一閱讀到這樣的代碼還須要進入方法體瞭解用法,但下次再次閱讀,或者再次修改,就能夠跳過他已經熟悉的方法,好比以下解析excel的文件,須要讀出多個片斷數據
public void parse(Sheet sheet){ User user = readUserInfo(sheet); List<Order> orders = readUserOrderInfo(sheet); UserCredit credit = readUserCreditInfo(sheet); }
如上parse方法將分紅三個方法完成,這對於性能和代碼行數,可能略有影響(其實根本算不上影響),但加強了代碼的可閱讀性,若是後來人重構了用戶積分部分,能夠直接修改readUserCreditInfo方法,而不須要從上百行代碼裏找到本身應該修改的地方。
......省略50行代碼 int year =c.get(Calendar.YEAR); int month = c.get(Calendar.MONTH)+1; int month_date= c.get(Calendar.DATE); String message ="今天"+year+"年"+month+"月"+month_date+"日,您在【業務系統】中有【"+paymentTotal+"】客戶須要還款、有【"+settleTotal+"】客戶須要結清、【"+verdueTotal+"】客戶已經逾期,請儘快處理。"; .......省略50行代碼
這段代碼若是單獨看尚可,若是這是在成百行代碼的一部分,建議放到一個小方法裏,好比,重構爲
...... String str = getPayMessage(paymentTotal,settlleTotal,verdueTotal) ....... protected String getPayMessage(BigDecimal paymentTooal,BigDecimalBigDecimal settlleTotal,verdueTotal){ int year =c.get(Calendar.YEAR); int month = c.get(Calendar.MONTH)+1; int month_date= c.get(Calendar.DATE); String message ="今天"+year+"年"+month+"月"+month_date+"日,您在【業務系統】中有【"+paymentTotal+"】客戶須要還款、有【"+settleTotal+"】客戶須要結清、【"+verdueTotal+"】客戶已經逾期,請儘快處理。"; }
重構後,代碼閱讀者每次看到這裏,都會放心的跳過這部分代碼。由於從方法名已經瞭解其做用,能很快的掃過這片代碼區域
程序裏的數組只適合代碼編寫者看,閱讀者沒法判斷數組表明的業務含義,好比
Object[] rets = call(); boolean success = (Boolean)rets[0]; String msg = (String)rets[1];
較好的方法是定義一個對象代替數組
CallResult rets = call(); boolean success = rets.isSuccess(); String msg = rets.getMessage();
也許你以爲調用後馬上轉化成有意義的參數名會不影響閱讀,這確實是一種補救辦法,但不及CallResult好。代碼編寫者應該能時刻想到給閱讀者減輕負擔。
相似的列子還有JPA的查詢,對於不能映射爲實體的,老是返回一個數組,好比
Object[] array = jpa.query("select * from xxx ,yyy ....."); Integer id = (Integer)array[0]; String name = (String)array[1]; ..... String tradeId = (String)array[22];
返回這樣一個數組,若是sql要改寫,那麼代碼對array的的處理也確定要修改。看代碼的人也不得不閱讀這些無聊的代碼。
相對於MyBatis和我寫的BeetlSql,這一點JPA就不行了-提供了一個返回數組的查詢接口。
我發現我每次在博客提到我寫的開源,就有人說我想宣傳本身的開源。我想強調一下,我只是踐行知行合一,我不會輕易評判一個我不熟悉領域技術,除非我真的實踐過。若是噴子對此不爽,你大能夠忽略我「自我宣傳部分」,僅看到我博客其餘內容。
java 裏的for循環通常都是使用i變量,這說明了有些狀況下,能夠用一些簡單的變量名字代替有意義的變量名字。前提是這些名字約定俗成,或者能在閱讀代碼的人眼裏,這個變量就是幾行以內就使用完畢,好比
boolean success = calc(); if(success){ return false; }
success 變量可能不如paySuccess更好,但鑑於很快使用完畢,仍是能夠接受。相反,若是在sucess變量定義後面的100行,還用到了這個變量,那麼「success」 就可能讓人疑惑了,代碼閱讀者不得再也不翻回去瞭解success的含義
代碼是一次寫入,屢次閱讀,從代碼層次去看待如何編寫容易閱讀代碼,可能還能列出更多的規則,我我的以爲這些規則並不重要,重要的是能時刻想到後來人會如何閱讀你的代碼纔是最重要的,若是他閱讀你的代碼,毫無障礙的達到一目十行,以爲你寫的代碼沒什麼高深,那就是好代碼。
博文參考了 王垠的《編程的智慧》