1. 程式人生 > 實用技巧 >寫不出這種程式碼,就等著被leader開除吧!

寫不出這種程式碼,就等著被leader開除吧!

前言

在我們平時刷題的時候,你可能會寫過很多諸如

int a,b,c
int [] arrays=new int arrays[10];
if((numbers > 10 && flag == 'true') || flag =='false')

這種程式碼,對於我們自己練習程式設計或者解決一個演算法題,當然沒有問題。但是如果是在一個工程中,尤其是幾十上百人維護了幾年的工程中,還使用這種寫法,傾瀉自己天馬行空的才華,保證leader不打死你哦。

所以,對於程式碼的整潔性,可讀性,自古以來就有很多大神做出過總結,比如這本《clean code》,中文名叫做《程式碼整潔之道》,今天,我們就來看看吧。

命名

命名思想

首先就是命名,命名可以說是一切程式的基礎,如果用三個字來形容那就是——“有意義”。

你要做到,當一個人看到你的命名,就知道這個變數/函式是幹什麼的

來看這一段程式碼:

public List<int[]> getThem() {
  List<int[]> list1 = new ArrayList<int[]>();
  for (int[] x : theList)
    if (x[0] == 4)
      list1.add(x);
  return list1;
}

這段程式碼非常簡潔,但是非常模糊,我們不知道theList到底是什麼,為什麼x==4作為判斷,list1又是什麼?

現在我們來改一改:

public List<int[]> getFlaggedCells() {
  List<int[]> flaggedCells = new ArrayList<int[]>();
  for (int[] cell : gameBoard)
  if (cell[STATUS_VALUE] == FLAGGED)
  flaggedCells.add(cell);
  return flaggedCells;
}

從上面的程式碼中,我們可以馬上看出來,flaggedCells表達的是標緻cell,而cell位於gameBoard中,就是一個遊戲面板,只需要再通過檔名,知道這是一個【掃雷遊戲】,那麼cell就是每一個格子,if語句中就是判斷每一個格子是否被點選過,如果是,就新增到flaggedCells中,我們顯然知道他想要幹什麼——蒐集玩家點選過的格子並返回。

但是,上面的程式碼嗎使用的是 int 陣列,比如int[] cell,每一個數組內部的數表示cell的狀態,可是cell並不需要那麼多狀態,而且這樣導致每次使用這個狀態的時候都要重新定義陣列,狀態是cell的一個屬性,所以,完全可以定義一個cell的類,將他的狀態封裝進入。

public List<Cell> getFlaggedCells() {
  	List<Cell> flaggedCells = new ArrayList<Cell>();
    for (Cell cell : gameBoard)
    if (cell.isFlagged())
    flaggedCells.add(cell);
    return flaggedCells;
}

這樣子很清晰看出,gameBoard由Cell組成,從gameBoard中取出標記過的cell放入flaggedCells中,這樣的程式碼是不是感覺渾然天成,自然而然呢?

命名規範

通過以上事例,你應該理解在寫工程專案時,諸如陣列,連結串列,字典這些底層結構應該要封裝在User,Cell,Address這種類中,使用的時候直接使用這些類即可,這是一種大局觀的思維,現在我們來做一些較為細節的落地規範。對於什麼駝峰命名,匈牙利命名我相信你不會陌生,但是我在這裡再強調兩個地方。

  • 類名

    • 類作為一個物件,需要的是名詞或者名詞短語,gameBoard,Ueser都是如此,不要使用模糊名詞(就是概念很大的名詞),比如Data,因為可以細分為UserData,MoneyData等,對於可以細分的模糊名詞,一定要用名詞短語。
    • 類名都是第一個字母大寫的名詞組合。
  • 方法名

    • 方法作為具體幹事的執行者,當然是使用動詞或者動詞短語了,同樣注意的是,不要使用模糊動詞,和上面不一樣的是,解決模糊名詞的方法是增加名詞修飾,而解決模糊動詞的方法是【換更精準的動詞】
    • 比如getInformation,就不好,因為get太大了,你的information是pull過來的還是clone過來的?是被動接受的還是主動去取的?Information這個詞也很模糊,所以可以根據情況拆分為直接取使用者資料——fetchUserData,上傳新資料再取使用者資料——uploadAndfetchUserData。
    • 不是不能用get,是說如果有更好的選擇,儘量用更精準的動詞。
    • 注意方法名第一個字母小寫。

函式

第一原則

短小

如果還有第二原則,那還是短小。

短小到什麼程度,最多20~30行吧,

所以要求,一個函式,只做一件事情

什麼叫做只做一件事情呢?

就拿處理資料來說,我們說【處理使用者資料】是一件事,你也可以說是做了三件事:

  1. 取資料
  2. 處理資料
  3. 返回資料

當然,這個例子有點抬槓的意思,不過反映的現實是,程式碼中各種邏輯往往你中有我,我中有你,到底一件事情的邊界在哪裡?

這個因人而異,我只能提出書中的方法。

  1. 同一抽象級

    剛才那個取資料,就是在同一抽象級下完成的事情,就可以看做是一件事,如果再來一件——儲存使用者資料,顯然,可以歸到前面,還是一件事,如果再來一件——取車輛資料,顯然,就是另外一個抽象的東西了。

  2. 分割判定與處理

    這個好理解,就是立法與司法的分割,比如:

    if (set("username", "unclebob"))
      ...
    

    就讓人迷惑,他表達的意思是不是如果unclebob成功賦值給username就返回true呢?還是說如果username為null時就用unclebob

    賦值進入呢?username到底是屬性還是碰巧一個字串叫做username呢?隨便猜。

    但是我們這樣修改:

    if (attributeExists("username")) {
    	setAttribute("username", "unclebob");
    	...
    }
    

    如果有username這個屬性,就賦值,非常清晰,也就是說,判定與處理要分隔開。

你可能會說,對於像if,while,switch這種語句,往往動輒十幾行,短不了啊!

首先,這個原則不是鐵律,實在太長也沒辦法

其次,對於這些語句,完全可以講裡面的邏輯做一個封裝,比如這種:

public static String renderPageWithSetupsAndTeardowns(
  PageData pageData, boolean isSuite) throws Exception {
  if (isTestPage(pageData))
    includeSetupAndTeardownPages(pageData, isSuite);
  return pageData.getHtml();
}

if 語句之後做了一件includeSetupAndTeardownPages的事情,不僅極大增強了可讀性,而且程式碼短了不少。

關於引數

當你看jdk原始碼或者是android原始碼的時候,你會發現他們經常做呼叫,尤其是同名的方法過載,在看《演算法》這本書的時候,也是,比如關於快速排序的:

public static void quickSort(int [] arrays){
  quickSort(arrays,0,arrays.length-1);
}
private static void quickSort(int [] arrays,int left,int right){
  ...
}

我在一開始接觸的時候,覺得雖然好看,但未免麻煩,隨著經驗的提升,這是一種非常好的程式設計習慣。

首先,對於使用者來說,他想要進行快排,想傳的只有陣列,左右邊界都包含進去了,你為什麼要他多傳引數?同時第一個方法使用的是public,就表示這是暴露給使用者的。

其次,第二個方法在第一個方法中被呼叫,用private保護了起來,避免了無數麻煩

所以,越是業務層的邏輯,越要寫引數少的程式碼,如果引數必須要很多,那就用一個private的函式封裝起來,你讓使用者擁有一百個引數輸入,對他來說,那不是自由,那是災難。

蘋果和微信的使用體驗就是將這種哲學貫徹到極致的代表。

註釋

最好的註釋,就是程式碼本身

用程式碼能解釋清楚的事情,儘量少用註釋,如果註釋太多,只能證明程式碼寫得爛……

不過有些地方還是有必要寫程式碼的。

位置

最好寫在方法頂部,不插入到實際程式碼中,比如那個面試幾乎必問的String中的equals方法,原始碼如下:

 ... 
 * @see  #compareTo(String)
 * @see  #equalsIgnoreCase(String)
 */
public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = length();
        if (n == anotherString.length()) {
            int i = 0;
            while (n-- != 0) {
                if (charAt(i) != anotherString.charAt(i))
                        return false;
                i++;
            }
            return true;
        }
    }
    return false;
}

警示的註釋

這裡可以寫一些告誡他人的程式碼,讓後來的接盤俠能夠引起重視。

//When I wrote this, only God and I understood what I was doing
//Now, God only knows

簡單說就是,下面的程式碼一定有用,但是我也看不懂了,你別碰O(∩_∩)O哈哈~

在知乎上看到還有這樣的。

//如果這段報錯,你在機器上裝一個360安全衛士,相信我我
以為是開玩笑,結果裝了就真的好了。這個是前人留給我的。
//這個服務有問題的話,你可以問某某某,這段是他寫的。
這個是在我離職交接時寫的,出賣了未離職的一個同事。
//執行成功後傳送一條通知簡訊,穩定後註釋掉
手機號是寫死的,我看到這段的時候還沒有註釋,這一年每天凌晨他都能收到簡訊。

作者:hll
連結:https://www.zhihu.com/question/296123587/answer/498701733
來源:知乎

這些人水平怎麼樣另說,但是對於後人還是用心的。

TODO註釋

這個就不說了,很常用。


看完了有用的註釋,我們來看看沒用的註釋

多餘的

// Utility method that returns when this.closed is true. Throws an exception
// if the timeout is reached.
public synchronized void waitForClose(final long timeoutMillis)
  throws Exception
  {
		if(!closed)
	{
  wait(timeoutMillis);
  	if(!closed)
  		throw new Exception("MockResponseSender could not be closed");
  }
}

這就是把程式碼做了什麼又描述一遍,沒有任何意義。

被註釋掉的程式碼

InputStreamResponse response = new InputStreamResponse();
response.setBody(formatter.getResultStream(), formatter.getByteCount());
// InputStream resultsStream = formatter.getResultStream();
// StreamReader reader = new StreamReader(resultsStream);
// response.setContent(reader.read(formatter.getByteCount()));

相信很多人都有這樣的行為,在自己寫演算法題的時候用來測一測沒有任何問題,但是對於後來者來說,他該怎麼辦?

他一定會想:也許是有用的呢?不然為什麼之前要寫。

但是他又看不懂或者覺得沒必要看,於是就留了下,然後這樣的程式碼就會越來越多,最終成為傳說中的祖傳程式碼。

不用心

註釋要清楚,如果註釋還要寫註釋來解釋,根本沒有意義。

比如下面這個,為什麼要用200?

/*
* start with an array that is big enough to hold all the pixels
* (plus filter bytes), and an extra 200 bytes for header info
*/
this.pngBytes = new byte[((this.width + 1) * this.height * 3) + 200];

小結

對於多數人來說,命名函式與註釋基本上就是程式的主要組成部分,能夠處理好這三樣就能寫出非常好的程式碼了,當leader看到你的提交的時候,看到的是如此優雅的程式碼,我想,他也會覺得是一種享受吧,就和詩歌一樣。

作者簡介:小松漫步,一個剛入職的新人,微信公眾號【小松漫步】,文章參考自《程式碼整潔之道》,公眾號回覆【程式碼整潔之道】即可獲取資源,一起加油吧。