2012年8月5日 星期日

從需求出發理解背後技術的思考脈胳

最近在技術上多了不少體悟,關鍵是要掌握住問題的需求,接著不斷思考和嘗試,避免卡在特定的規則或工具裡,將重心放在隱藏在這些東西背後的核心思想。

直接看現有的工具或別人的作法很難有所體會,但自己從頭邊做邊想,會得出自己一套理論, 接著會出乎意料地更快理解這些工具和作法。畢竟在足夠嚴苛的需求下,方法可能有些差異,背後的精神卻是類似的。

比方說,若程式反應速度要求在 1ms 以內,全部資料來源都得在記憶體或網路,不能使用硬碟。那麼,針對這種應用,單機程式能做的事只有將東西塞在記憶體裡取用

然而記憶體有限,資料總有放不進去的時候。在這種速度要求且資料過大的前提下,可能的作法大概是:

  • 像 cache 那般有套機制留住最常用的資料。
  • 在記憶體裡存 index,index 內存必要的資料,若需要更多資料再從硬碟讀。
  • 先做前處理「壓縮」資料成摘要,摘要必須小到可以放入記憶體內。資料的品質(正確性)也許會有一些損失,但是是必要的取捨。之前在讀 YouTube 關聯影片和 IBM 的 Watson 論文時,有看到類似的思路,工作上也做過類似的事。和前述方法不同的地方是,資料的筆數變少,轉為另一種高質量的資料。

再以 Test Driven Development 為例,最重要的不是 TDD 的三步規則,也不是相關工具要怎麼用,而是導入「在設計的開頭, 就將測試視為主要考慮項目」,其它東西都是這個想法的衍生。

從這個角度出發,實作久了自然會理解為什麼介面要開洞放入物件,為什麼需要 factory 隔離生成和操作邏輯。至於是否真的有先寫測試,現在我覺得不是鐵則,在有為測試而考慮的設計下,在必要時補測試可能更划算,將時間花在刀口上。但若一開始沒為測試考慮,事後要補就很辛苦,加測試的價值又更低了

再往上拉一個層次來看,解決問題的前提,本來就是如何確認問題有被解決,若無法確認問題有無被解決,用什麼方法也是白搭,不知成效如何。從這個角度來看,在設計之初就考慮測試,並不是什麼新穎或強人所難的事。但要走到這步,如同學其它東西一般,需要累積不少經驗

總結來說,要學習一個技術最好的方法,就是在有適當的需求時再學。平時看到和自己需求無關的知識,加減有個印象在腦裡即可。待要用到時可利用腦裡的索引找出相關資訊,先有個廣泛認知。接著根據自己的需求可以明白那些是比較相關的知識,邊看邊動手做些雛型試試。有了這些操作經驗和背景知識後,再從頭確認一次自己真正的需求為何、有那些限制和對應解法,就能理解要如何應用這些技術和工具到自己的情況裡。

2012年5月6日 星期日

The Linux Programming Interface 讀書心得

書還沒出以前就聽 Scott 推薦過 TLPI,直到因為工作需要,才買回來看。斷斷續續大概看了三個月半,來寫一下心得。先說結論,若需要寫 Linux programming,這本是極佳的選擇,它是我買過最貴最厚的書,同時也是我買過最划算的書。

全書共 64 個章節近 1500 頁,在 2010 年底出版,算是相當新且完整的書。我看的方式是:每天沒事翻個四頁,有事時針對需要的重點看,不見得每個章節都要看完,有解決當下需求即可。持續這樣運作個一陣子,效果相當好。從上面的照片可看到我插的一堆書籤,發票、鐵尺、名片、計算紙等,不知不覺就放了 14 個,看了小有成就感。

本書最大的好處是,作者對每個主題循序漸進地提供鉅細靡遺的說明,不只是 POSIX 或 Linux 能做什麼,同時也會提供必要的基礎知識,像在 network programming 的章節,會補充說明 TCP/IP 的運作方式,而且相當容易吸收。ch 41、42 則是說明 static 和 shared libraries,也是相當實用的知識。之前曾在網路上看過一些片段知識和一些動手操作的經驗,但一直沒有拼出全貌。直到看了這兩章後,才有種打通的感覺,更有把握知道底層是怎麼運作的,從而解決工作上的疑問。

所以,只要有耐心啃完書中相關內容,就有個八成把握知道怎麼做才對。相較於在網路上搜尋,更能系統地了解 Linux 能與不能做什麼事,有利於判斷可行的方案。偶而我也會看 man page 的說明,交叉對照效果更好。作者說的是他整理消化後各系統通用的知識,而 man page 可進一步反映出自己使用 OS 版本的細節。

本書另一大好處是,每個章節都有附完整的程式碼,輔以執行結果,來說明系統的一些特性。像是「20.12 Signals Are Not Queued」,為了說明 signal 只有保證送出後至少會收到一次,但不是送幾次就收到幾次,作者寫了個小程式 A 送個上一百萬次 signal 給 B,B 則是在 signal handler 裡計數,結果在作者的這次實驗裡, B 只收到 52 次。我很喜歡這種實證的方式,現在的系統太複雜,在不同平台會有不同狀況。了解知識的概念後,手邊還是要有程式實測,感覺才會踏實。

除了內容完善以外,對書中有疑問時,寄信給作者也會得到很友善的回應。舉了這麼多優點,實在是讓人想不出不買它的理由,也難怪 Amazon 上 35 個評價全都是五顆星了

2012年2月19日 星期日

專注於滿足需求而非工具或方法

看到 command 提到《Don't Fall in Love With Your Technology》,而有一些感觸。

從高中開始,我一直想弄明白 Perl、Python 到底那一個比較好用,這樣我學其中一個就可以了。後來又多了 Ruby 這個選項,讓這問題變得更複雜。大概到大學後期或研究所的時候,我才肯定這是一種信仰上的爭辯,而將這個問題拋之於腦後。

同一時期,我也花了滿長一段時間才明白許多問題沒有標準答案得視情況而定。每當對此有所體會時,就會想起大學電子學老師整年重覆強調的一句話:「沒有前提,就沒有答案」。雖然兩學期的電子學都是低空飛過,這句話深深印在心裡,只是那時我對這句話的理解仍不深,還需時時重新琢磨它的含意。

我花了更長的時間才將前面兩個心得連結在一起,從而明白任何工具或方法的爭辯很可能都是偽命題,重點在於需求是什麼?要如何滿足需求?如今回想起來,《不要自称为程序员》將這個觀念解析得相當清楚,相當值得一看。

舉例來說,「vim vs. emacs」是個偽命題,這取決於自己當下的環境為何。若團隊內多數人使用 emacs 且自己兩者都不熟,那麼 emacs 是較為合理的選擇。反之,若自己相當熟 vim 而團隊內多數人兩者都不熟,那繼續使用 vim 較為合理。重點在於「如何有效率地在自己的環境下解決問題」,而非「一般而言,那一個編輯器比較強?」

再以軟體開發的方法來看,「agile vs. 某個軟體開發方法」也是偽命題,不論 agile 公認的定義為何,重點在於滿足需求,而滿足需求不見得需要一套完備的軟體開發方法;有完備的軟體開發方法不見得能滿足需求。要滿足需求有太多事要做,研讀相關技術、軟體開發、市場行銷等,軟體開發可能是滿足需求的其中一項基石,但不是全部。若滿足需求的前提需要改善軟體需求,自然需要改善它;反之則否。《Joel on Software - 別讓架構太空人嚇到你》對「開發軟體的方法 vs. 滿足需求」提了生動的描述。

舉另一個具體例子,「是否需要重構?」往往帶來許多爭議性的討論,各方人馬(PM、RD、QA、...) 對此有不同看法。若這段程式一直都不需要加新功能,那的確不需要重構。重構只會花費時間讓程式碼變漂亮,對於滿足需求沒有任何影響。反之,之後需要繼續大幅加功能,逐步重構部份功能,則對完成產品(滿足需求)大有幫助。

最近幾年有一個很紅的議題,開發網站是用 Ruby on Rails 好,還是用 ... 好。最近一年可能還會多一些人問是否要改用基於 node.js 的新 framework。要回答這問題得先看需求為何,若只是做幾頁的小網站,用什麼方法差異都不大;若是做長期維護的大網站,要看目前團隊成員熟悉的工具和程式語言為何,再來評估使用 Rails 的相對成本。若再涉及和後端整合,又和既有的 code base 有大幅關聯。而要回答這一切一切衍生的議題,還是得先看:究竟需求為何,基於什麼原因而採用 X 會更好?以 Justin.tv 為例,《Django: Why is Justin.tv porting their codebase to Django from RoR?》說明 Justin.tv 轉換的主因是全部程式都是用 Python 寫的,此外,他們也想藉機重新設計一遍架構,去除 legacy codes,以符合現今的使用需求。

舉這些例子的用意不是無限上綱地說工具和方法都不重要,而是強調將焦點放在如何滿足需求,若有需要選用好工具,才有必要討論它。問錯問題的話,永遠不會得到有用的答案。

2012年1月19日 星期四

在 Linux 下開發 C/C++ 的新手指南

新加入一個專案,最先面對的課題是如何正確地編譯和執行專案,可從 "It works on my machine" 如此地風行,印證這件事的困難性;再來則是閱讀負責工作相關的程式碼。至於發揮程式語言的特性,運用高階設計模式等,都是另開新專案或熟悉狀況後才有機會發揮。

過去數年沉浸在愉快的 scripting language 和開發新專案中,一直沒踏入這殘酷的世界。這篇記錄在這樣的情境下,可能需要的技能,結算一下這一個多月的心得,全都是血淚談啊 ...。

系統工具

熟悉作業系統的安裝套件是首要之務,這樣才知道如何補足需要的 header、library,或是安裝含 debug symbol 版的函式庫以執行 gdb 觀察程式或除錯。參見《自行編譯含 debug symbol 的套件 (package)》了解 Ubuntu/Debian 下的套件命名規則。

在未安裝套件的情況下,可用

  • aptitude search SUBSTRING # 找套件
  • aptitude show PACKAGE # 顯示套件用途
  • apt-file search X # 找出 X 包在那個套件裡,找 header 時很有用。

注意在用 apt-file 前要先跑 sudo apt-file update,不然搜不出東西來。

對於已安裝套件,可用

  • dpkg --search SUBSTRING # 找出安裝在那個套件,已知 header 時,適合用來找 library
  • dpkg -L PACKAGE # 列出套件內容,可用來找 header、library
  • locate SUBSTRING # 我比較常用它找 header 的位置,再觀看 header 內容

執行 locate 前記得先執行 sudo updatedb,原因同 apt-file。

《除錯技巧:在 Ubuntu 上找出第三方函式庫的程式碼》用一個小例子說明如何使用這些工具找出原始碼協助除錯。

其它參考資料: How To Manage Packages Using apt-get, apt-cache, apt-file and dpkg Commands ( With 13 Practical Examples )

編譯

連結

這一塊讓我卡了一陣子。一些粗淺心得:

執行

光只是讀程式碼就像大海撈針一樣,不太有效率。可從動態執行過程找出主要執行的路徑,再專注相關的程式碼。

1. strace 和 ltrace

srace 是分析執行行為的強大工具,google 一下會看到很多別人的個案心得,看看再自己試一試,很快能上手,不知能發揮它多少功能。這裡列自己用的兩個小案例:

反而是 ltrace 一直都想不到使用它的時機,也沒找到好的個案心得文。

2. gdb

gdb 的重要性不需多說明,之前的幾則心得:

強烈建議使用 cgdb,簡易安裝 + 無痛上手,瞬間省下大量操作和讀碼的時間。

3. 打開除錯功能

依照開發者的習性,一定會留後門讓自己方便除錯,從這角度下手也可省下不少時間:

4. 載入函式庫

除以上所言外,我另外有找過畫出程式流程的靜態和動態分析工具,像是畫 call graph 或是 C 的 cflow。不過 C++ 的靜態分析效果很糟,就沒花太多時間研究。目前用 strace 和 gdb 覺得已夠用了,不知用工具產生 call graph、class 相依圖或其它東西,是否會更有幫助。待有需求看整體的程式時再來試試。

閱讀程式碼

聽了大家的建議後,做了一些實際操作,而有些心得:

Eclipse CDT 雖然方便,後來我還是用 gj 居多。原因有幾點:

  • 我已很習慣用 vim + screen 做事,gj 最合這個情境
  • id-utils 真的是超級快
  • 我針對自己的需求更新 gj 多次,愈用愈順手

另外 ack 也滿方便的,懶得建 index 或是想比對子字串時,可直接使用。當然 id-utils 也支援子字串比對,只是暫時懶得為此修改 gj 的程式,目前大部份需求是找完整的 symbol。

熟悉 Linux 系統程式

在基本工具都上手後,打算每天抽一點時間加減讀一點相關知識。一兩年下來應該會有不錯的成果。目前打算讀《The Linux Programming Interface》,年假時試看看效果如何。

這一個月的心得以了解 /proc 為主,對觀察 CPU 用量、RAM 用量、載入那些函式庫、multi-thread、程式執行狀態等都很有幫助:

結論

即使大概知道有那些東西,還是需要實際動手的經驗,才會真的學進去。一個月下來進步了不少,不過對於要面對的戰役,還有一大段路要趕上,還有很多很多要學的。

2012-01-29 更新

補上一些後來新寫的連結。此外,《The Linux Programming Interface》 相當實用,讀 ch1 ~ 3 讓我補足不少基礎知識。ch41、42 講解 shared library 也相當值得一看。相關心得見《The Linux Programming Interface 讀書心得》

2013-07-13 更新

備忘效能分析相關的工具:

出處:Linux Performance Analysis and Tools

2013-07-20 更新

將後半部份內容抽出來,另寫了一篇比較完整的文章:《了解 C/C++ 程式行為的技巧》。