大師級的程序員把系統看成故事來說,而不是看成程序來寫。
若是同一做用範圍內兩樣不一樣的東西不能重名,那其意思也應該不一樣纔對。那麼這兩樣東西應該取不一樣的名字而不是以數字區分。若是如下代碼參數名改成 source
和 destination
,這個函數就會像樣許多java
public static void copyChars(char a1[], char a2[]){ for(int i = 0; i < a1.length; i++){ a2[i] = a1[i]; } }
例如循環中的 i、j、k
等單字母名稱不是個好選擇;讀者必須在腦中將它映射爲真實概念。最好用 filter、map
等方法代替 for循環
程序員
名稱或名詞短語
動詞或動詞短語
例如 fetch
、retrive
、 get
表達同一個意思,應該選定一個,而後在各個類中使用相同的方法名。算法
避免將同一單詞用於不一樣的目的。同一術語用於不一樣概念,基本上就是雙關語了。app
記住,只有程序員纔會讀你的代碼。因此,儘管用那些計算機科學(Computer Science,CS)術語、算法名、模式名等。函數
給函數取個好名字,能較好地解釋函數的意圖,以及參數的順序和意圖。fetch
對於一元函數,函數和參數應當造成一種很是良好的動詞/名詞形式。編碼
// good write(name) // better // 更具體,它告訴咱們,"name"是一個"field" writeField(name)
函數名稱的關鍵字(keyword)形式。使用這種形式,把參數的名稱編碼成了函數名spa
// bad assertEqual(expected, actual); // good // 這大大減輕了記憶參數順序的負擔 assertExpectedEqualsActual(expected, actual);
20行封頂
if
、else
、while
等語句,其中的代碼應該只有一行。該行大抵應該是一個函數調用語句。由於塊內的函數擁有較具體說明性的名稱,從而增長了文檔上的價值確保函數不能被再拆分
日誌
最理想的參數數量是零,其次是一,再次是二,應儘可能避免三code
true
將會這樣,標識爲 false
則會那樣二元函數:有些時候兩個參數正好。例如 Point p = new Point(0, 0)
;由於點天生擁有兩個參數。但大多數狀況下應該儘可能利用一些機制將二元函數轉換成一元函數。例如,把writeField 方法寫成outputStream的成員之一
// bad writeField(outputStream, name); // good outputStream.writeFiled(name);
參數對象:若是函數看起來須要兩個、三個、或三個以上參數,就說明其中一些應該封裝爲類了
// bad Circle makeCircle(double x, double y, double, radius); // good Circle makeCircle(Point center, double radius);
從參數封裝成對象,從而減小參數數量,看起來像是在做弊,但實則並不是如此。當一組參數被共同傳遞,就像上例中的x和y那樣,每每就是該有本身名稱的某個概念的一部分
確保函數功能就像函數名描述的同樣,不要作函數名描述之外的事情。應該爲起一個更能描述函數功能的函數名
public UserValidator { private Cyptographer cyptographer; public boolean checkPassword(String userName, String password) { User user = UserGateway.findByName(userName); if(user != User.NULL) { String codePhrase = user.getPhraseEncodedByPassword(); String phrase = cyptographer.decrypt(codePhrase, password); if("Valid Password".equals(phrase)) { // 反作用在於對這個調用 // checkPassword函數,顧名思義,就是用來檢查密碼。該名稱並未暗示它會 // 初始化該次會話。能夠重命名爲 checkPasswordAndIntializeSession Session.initialize(); return true; } } return false; } }
普通而言,應該避免使用輸出參數,若是函數必需要修改某種狀態,就修改所屬對象的狀態
// bad // 讀者會弄不清s是輸入參數仍是輸出參數 // 也會弄不清這函數是把s添加到什麼東西后面,仍是把什麼東西添加到s後面 appendFooter(s); // 函數簽名 public void appendFooter(StringBuffer report){} // good report.appendFooter();
函數要行作什麼事( 例如 user.setName('xxx')
)、要麼回答什麼事( 例如 user.isVip()
)。一個函數裏不要把兩件事都幹了。
TODO 註釋
雖好,但也要按期查看,刪除再也不須要的切斷代碼間的聯繫
// bad public class ReportConfig { // // The class name of the reporter listener // private String m_className; // //The properties of the reporter listener // private List<Property> m_properties = new ArrayList<Property>(); public void addProperty(Property property) { m_properties.add(property); } } // good public class ReportConfig { private String m_className; private List<Property> m_properties = new ArrayList<Property>(); public void addProperty(Property property) { m_properties.add(property); } }
概念相關。 概念相關的代碼應該放到一塊兒。相關性越強,彼此之間的距離就該越短
public class Assert { static public void assertTrue(String message, boolean codition(){} static public void assertTrue(boolean codition(){} static public void assertFalse(String message, boolean codition(){} // ..... }
這些函數有關極強的概念相關性,由於他們擁有共同的命名模式,執行同一基礎任務的不一樣變種。互相調用是第二位的。即使沒有互相調用。也應該放在一塊兒。
更多例子查看 p79 -- 5.25 垂直順序
函數應該只作一件事,錯誤處理就是一件事。
// bad public void delete(Page page) { try{ deletePage(page); registery.deleteReference(page.name); configKeys.deleteKey(page.name.makeKey(); }catch(Expection e){ logError(e); } } // good public void delete(Page page) { try{ // 將上例的操做,封裝到一個方法 deletePageAndAllReferences(page); }catch(Expection e){ logError(e); } }
受控異常:Checked Exception(FileException、SQLException等)
,這類異常必須寫 try/catch
,或者 throw拋出
,不然編譯通不過。
非受控異常:Unchecked Exception
,這類異常也叫作運行時異常(與非受控異常 字數相等),這類異常不須要 try/catch
,也不須要 throw拋出
。即便 throw 了,上層調用函數也非必須捕獲,編譯能經過。
受控異常的代價就是違反開放/閉合原則。若是你在方法中拋出受控異常,這意味着每一個調用該函數的函數都要修改,捕獲新異常,或在其簽名中添加合適的throw語句。對於通常的應用開發,受控異常依賴成本要高於收益成本,儘可能 try/catch
處理,不要拋出。
應建立信息充分的錯誤信息,並和異常一塊兒傳遞出去。在消息中,包括失敗的操做和失敗類型。若是你的應用程序有日誌系統,傳遞足夠的信息給catch塊,並記錄下來。
// bad ACMEPort port = new ACMEPort(12); try { port.open(); } catch(DeviceResponseException e) { reportPortError(e); logger.log("Device response exception",e); } catch(ATM1212UnlockedException e) { reportPortError(e); logger.log("Unlock exception",e); } catch(GMXError e) { reportPortError(e); logger.log("Device response exception",e); } finally { // ..... }
經過打包調用API,確保它返回經過用異常類型,從而簡化代碼
// good LocalPort port = new LocalPort(12); try { port.open(); } catch(PortDeviceFailure e) { reportError(e); logger.log(e.getMessage(),e); } finally { // ..... } public class LocalPort{ private ACMEPort innerPort; public LocalPort(int portNumber){ innerPort = new ACMEPort(portNumber); } public open() { try { innerPort.open(); } catch(DeviceResponseException e) { // 自定義的異常類 throw new PortDeviceFailure(e); } catch(ATM1212UnlockedException e) { throw new PortDeviceFailure(e); } catch(GMXError e) { throw new PortDeviceFailure(e); } } }
將第三方API打包是個良好的實踐手段。當你打包一個第三方API,你就下降了對它的依賴。