2013年1月20日 星期日

Peopleware:腦力密集產業的人才管理之道 (Peopleware: Productive Projects and Teams, 2/e)

就如同封面所寫,這是一本軟體專案管理的聖經本,和人月神話齊名。雖然這是一本26年前出版的書籍,但是書中觀念現在同樣還是適用。甚至現在還有許多軟體公司使用書中所寫得錯誤方法來管理員工。

書中一開始就講到,軟體業和一般製造業完全不同。軟體業比較像是藝術創作。老闆如果把員工當成可替換的零件,那這家公司註定是要失敗的。Peopleware提到幾個成功的關鍵,例如管理底下的軟體工程師的方式會決定這家公司會成功還是失敗。書中提了許多實際案例以及有趣的實驗,一一破解傳統主管的迷思。舉例來說,書中提到強迫員工加班的成效並沒有一般人想的這麼大,長久下來反而會造成員工效率低落。書中也提到環境對員工的影響,優秀的程式設計師大多數都來自於安靜不易受干擾的工作環境,但是老闆往往為了省小錢而造成工程師效率低落。

我最喜歡書中做的一個實驗,作者把不同公司的工程師們安排在自己的工作環境進行程式競賽。結果發現最優秀的工程師和最差的工程師效率可以差到10倍。同時最優秀的那群工程師們大多數擁有良好的工作環境。

Peopleware這本書不只適合給主管閱讀,也很推薦一般工程師去閱讀這本書。書中提到許多改善工作效率的方法。讀者可一一檢視,改善自己的工作效率。

2013年1月10日 星期四

[C]函式指標陣列用法(Array of Function Pointers)

在寫程式時候,我們常常會碰到根據不同狀態去執行不同函式的情況。比如說下列範例,根據state變數的狀態去呼叫對應的函式。

state: 0,執行run()
state: 1,執行stop()
state: 2,執行exit()

void run() {
    printf("start\r\n");
}

void stop() {
    printf("stop\r\n");
}

void exit() {
    printf("exit\r\n");
}
bool OnStateChange(uint state) {
    if (state == 0) {
        run();
    }
    else if (state == 1) {
        stop();
    }
    else if (state == 2) {
        exit();
    }
    else {
        printf("Wrong state!\n");
        return false;
    }       

    return true;
}
用if判斷式是最簡單的方法。不過如果state變多。整段code就會變得很冗長。
int OnStateChange(uint state) {
    switch (state) {
    case 0:
        run();
        break;
    case 1:
        stop();
        break;
    case 2:
        exit();
        break;
    default:
        printf("Wrong state!\n");
        return false;
    }
    return 0;
}
改用switch看起來有比較乾淨一點。不過state變多,程式碼也會變得冗長。有沒有什麼更精簡的作法呢?這時候函式指標陣列就派上用場了。
static void (*command[])(void) = {run, stop, exit};

int OnStateChange(uint state) {
       
    if (state > 3) { 
        printf("Wrong state!\n");
        return false;
    }

    command[state]();
    return 0;
}
這種寫法比前兩個例子都來的精簡。函式指標陣列宣告的第一個void表示函式的回傳值,第二個void表示函式的參數。所以這是一個帶有三個函式指標的陣列,陣列中函式的回傳值以及參數都是void。

2013年1月7日 星期一

The Art of Readable Code



這是一本教你如何寫出容易閱讀程式碼的書。其實市面上已經有許多這類型的書籍了,比如說Clean Code。如果你是一位很有經驗的程式設計師,你會發現這本書內容其實和之前市面上這類型的書籍都差不多,並沒有什麼新的東西。這本書作者很強調一個觀念,好讀的程式碼是指讓別人花最少時間就可以完全瞭解它,整本書就是圍繞這個原則來教你如何寫出容易閱讀程式碼。

這本書最大的特色是例子很豐富,書中舉的例子包涵了C, C++, Java, Python, JaveScript。其中大多數的範例都是作者花超過五年的時間從實際專案中的程式碼擷取下來的,看得出來作者花了很多時間在收集這些範例。書中會先解釋舊的程式碼為什麼不好,並且教讀者該如何修改成好的程式碼。

另一個本書的特色是書中的漫畫,書中每個章節都會有一些漫畫。而這些漫畫都和所談得主題有些呼應,讓讀者讀起來相當輕鬆。本書頁數也不長,只有180頁。這樣的篇幅我覺得十分洽當,不會太長也不至於到沒有內容。

書中內容專注在如何寫出別人容易閱讀的程式碼,從命名、註解一直到程式邏輯,最後教你如何重新精簡你的程式碼以及如何測試。重要的主題都有涵蓋在裡頭。書中最後也列出作者推薦相關類型的書籍。讓讀者在讀完這本書後能繼續深入閱讀。

很推薦想增加自己程式碼品質的人去閱讀這本書。如果之前沒讀過這類型得書籍,相信收穫會很多。

讀書筆記:

2013年1月4日 星期五

如何命名變數以及函式名稱

  • 命名要精準

    • 命名要精準,不要使用模糊的單字
    • def GetPage(url):
      
    • Get是一個不好得命名,沒有明確指出從什麼地方GetPage
    • 如果是從Internet,應命名為FetchPage()或DownloadPage()
    • class BinaryTree:
          def Size(self):
              ...
      
    • Size是一個不好得命名,沒有明確指出是tree的高度還是有多少個nodes
    • 直接命名為Height()或是NumNodes()會比較明確

  • 找尋更能表達意義的單字

    • 使用字典找尋適合的單字
    • 以下是一些colorful word的範例,=>左手邊的單字可用右手邊的單字取代
    • send => deliver, dispatch, announce, distribute, route
    • find => search, extract, locate, recover
    • start => launch, create, begin, open
    • make => create, set up, build, generate, compose, add, new

  • 避免tmp和retval這類通用的命名

    • 讀者沒辦法從retval中獲得更多資訊,使用更清楚的命名取代retval
    • tmp只適合用在很短生存範圍的程式碼,例如下列swapping範例tmp生存範圍只在if{}內
    • if (right < left) {
          tmp = right;
          right = left;
          left = tmp;
      }
      

  • Loop Iterator

    • iterator的名稱使用i, j, k是OK的,因為這是大家都習慣的用法
    • for (int i = 0; i < clubs.size(); i++)
          for (int j = 0; j < clubs[i].members.size(); j++)
              for (int k = 0; k < users.size(); k++)
                  if (clubs[i].members[k] == users[j])
                      cout << "user[" << j << "] is in club[" << i << "]" << endl;
      }
      
    • 最好在i, j, k前加入prefix的字串,用來辨別iterator所走訪的物件
    • if (clubs[ci].members[mi] == users[ui])
      
    • 如此一來有bug就能即時發現
    • if (clubs[ci].members[ui] == users[mi]) # Bug! First letters don't match up.
      

  • 數值和單位

    • 在命名數值變數時可以加上單位
    • Start(int delay); 
      CreateCache(int size); 
      ThrottleDownload(float limit); 
      Rotate(float angle); 
      
      Start(int delay_secs); 
      CreateCache(int size_mb);
      ThrottleDownload(float max_kbps); 
      Rotate(float degrees_cw); 
      
    • 第二個範例會比第一個範例好

  • 命名要包含資料狀態

    • 加入目前資料的狀態到命名
    • password : plaintext_password
    • comment : unescaped_comment
    • html : html_utf8
    • data : data_urlenc
  • 在短生存範圍內可使用簡短的命名

    • 變數m只出現在if的範圍內,不影響其他人理解這段程式碼
    • if (debug) {
          map<string,int> m;
          LookUpNamesNumbers(&m);
          Print(m);
      }
      

  • 不使用不常見的縮寫

    • 用BEManager取代BackEndManager並不是一個好得命名
    • 團隊新來的成員沒辦法搞懂這些縮寫

  • 刪除多餘的命名

    • ConvertToString() => ToString()
    • DoServeLoop() => ServeLoop()

  • 使用命名格式來區分不同意義

    • const, macro, class, private variable都有不同的命名格式,方便一眼區分
    • static const int kMaxOpenFiles = 100;
      class LogReader {
        public:
          void OpenFile(string local_file);
        private:
          int offset_;
          DISALLOW_COPY_AND_ASSIGN(LogReader);
      };
      

    2013年1月3日 星期四

    撰寫良好程式碼最重要的原則


  • 程式碼應該要容易被理解

    • 程式碼應該要容易被理解,讓別人花最少時間讀懂程式碼
    • 易讀的程式碼同事意味著有良好的架構且較容易測試
    • 大多數時候,攥寫較短的程式碼會比長的程式碼來得好(但是也有例外)



  • 撰寫較少的程式碼通常比較好

    • 2000行程式碼會比5000行程式碼來的容易瞭解
    • 某些情況長的程式碼會比短的程式碼來得容易瞭解
    • assert((!(bucket = FindBucket(key))) || !bucket->IsOccupied());
      
    • 第二個例子雖然比較長,但是比第一個例子容易理解
      bucket = FindBucket(key);
      if (bucket != NULL) assert(!bucket->IsOccupied());
      
    • 加上一些註解會更容易瞭解
      // Fast version of "hash = (65599 * hash) + c"
      hash = (hash << 6) + (hash << 16) - hash + c;
      
    • 永遠都以撰寫好讀的程式碼為目標

    2013年1月2日 星期三

    [Python]os.rename()修改檔案檔名

    os module中的rename()可以幫助我們修改檔案或是資料夾的檔名,以下是一個簡單的範例簡介如何使用os.rename來更改檔名。範例中把path底下的所有檔案從0開始依序編號更改檔名。此範例使用os.listdir()來列出目錄下的所有檔案名稱,以及使用os.path.join()來產生路徑。

    os.rename(sourece, dest) - 更改資料夾或檔案的名稱(從source改成dest)
    os.listdir(dir_path) - 回傳一個list包涵dir_path資料夾中的所有檔案名稱
    import os
    
    def batch_rename(path):
        count = 0
        for fname in os.listdir(path):
            new_fname = str(count)
            print os.path.join(path, fname)
            os.rename(os.path.join(path, fname), os.path.join(path, new_fname))
            count = count + 1