Monday, March 28, 2011

軟體設計:給Developer的四個好工作習慣提案

在我的工作環境中,Developer和QA是兩個不同的腳色。在開發的過程中兩者就不斷的在Coding-Testing-Debugging的反覆循環中讓事情繼續前進。就因為這個生產線般的過程,Developer所做的事情都會直接影響QA的工作效率。

所以,如果你是Developer,在你提交你的程式碼以前,請保持以下的好習慣:
  • 至少測過一次:
    不管修了多小的Defect,請在自己的機器上測過一次,然後再交給QA。如果你聲稱解決了某個Defect,QA卻驗證失敗,除了浪費了QA的驗證時間(人家也是要在很多Test Case之間Context-Switch的...),也會讓QA對於Developer的程式碼失去信心。
  • 確認已同步最新的程式碼:
    如果你的團隊有用SCM(沒有嗎!God bless you...),在提交程式碼前,請再一次同步程式碼。如果有變動,就請再重新編譯一次。如果你的專案編譯一次就是好幾個小時,編譯失敗的成本就是浪費N小時乘上QA人數的時間。
  • 和QA討論你的變更:
    Developer專注於單獨的設計或是獨立的Bug Fixing,可是QA卻是要保證系統整體的品質。所以請和QA討論你的變更在哪的元件,在架構上和這個元件相關的其他部分又有哪些。這些資訊都可以幫助QA在有限的時間內安排最合適的測試案例。
  • 重大變更請提交到一個獨立一個Build:
    如果你的團隊有在使用Daily Build,對於任何重大的變更,例如:共用元件的變更,升級某個第三方的元件。這些可能對於軟體系統的風險影響較大的改變,請留到下一個Build(或是這個Build就只進這個重大改變)。如果QA在Regression Testing中真的發現了影響,Developer可以直接比較Build-to-Build的改變,快速地隔離出可能有問題的變更。
要讓QA對團隊做最大的貢獻,就是不要浪費他們的時間。留給QA更多時間去設計複雜的測試案例,就更有機會在早期發現問題,早期治療。身為Developer也就少一點機會成為下一個在版本釋出後才出包的苦主。XD


Thursday, March 24, 2011

User-Buffered I/O for C Library

昨天討論到了 Win32 Kernel Mode 下的 Buffered I/O 和 Unbuffered I/O 的差異。其實在 User Mode 的環境下,C標準函式庫的File I/O(fread, fwrite...)也存在自己的 Buffered 方式,稱之為 User-Buffered I/O 或稱為 Stream Buffering。重點在於決定何時把 Buffer 內的東西透過 System Call 倒入 Kernel Mode。

  • Fully Buffered: 
    • 系統或 Caller 決定一個固定大小的緩衝區。
    • 當透過 File I/O 寫滿緩衝區,I/O 才會發生。
  • Line Buffered: 
    • 當 File I/O 寫到換行符號,I/O才會發生。
    • 在 Win32,沒有這個選項...
  • No Buffered:
    • 就是沒有Buffer...
參考資料:

Wednesday, March 23, 2011

Buffered I/O & Unbuffered I/O

簡單來說,

  • Buffered I/O 讀寫的資料都會經過 Cache Manager,會暫存一份在 Memory 當中,然後會根據作業系統決定寫回硬碟的時機(如果用 Write Through,還是可以自行控制)。此模式適合常讀寫檔案,因為可以快速的從 Memory 讀寫。
    (想想 L1, L2 cache 之於 RAM 的關係。)
  • Unbuffered I/O 讀寫的資料不會經過 Cache Manager,而是直接對硬碟讀寫。因此也不會因為大量讀寫資料造成 Cache Trashing。
以下是我的推測,不代表真實測試結果。XD
  • Installer 的檔案複製可能就適合 Unbuffered I/O,因為 Buffered I/O 可能會造成大量的 Cache Trashing。
  • 當有大量的 Cache Trashing 發生時,可能也會影響到 Unbuffered I/O 的效能。因為 Cache Manager 會對硬碟做大量讀寫,這個行為可能會和 Unbuffered I/O 產生 Resource Contention。

參考資料:

Tuesday, March 22, 2011

C++:auto_ptr, scoped_ptr, shared_ptr and weak_ptr

簡單整理各種Smart Pointer的特色:
  • auto_ptr
    • Supported in C++ Standard Library
    • Support Ownership Transfer
  • scoped_ptr
    • Supported in C++ TR1
    • Not Support Ownership Transfer
  • shared_ptr
    • Supported in C++ TR1
    • Using Reference Count
    • Object copy or assignment will increase the reference count.
  • weak_ptr
    • Supported in C++ TR1
    • Created from shared_ptr. Cannot be created alone.
    • Support Ownership Transfer
    • Easy and safe to check a pointer valid or not, without increasing the reference count.
延伸閱讀:

Saturday, March 12, 2011

軟體設計:設計是什麼?從自身經驗說起

在1992年,Jack W. Reeves發表了一篇"What is Software Design"。主要的論述就是表達"Source code is the design."的看法。

這點也和我自己的經驗不謀而合。在專案的規劃中,我們都把需求(Requirement), 設計(Design)和建構(Construction)分開成為一個循序的過程。設計過程是確保你可以建構出符合需求的working software/component。但是在真實的情況下,我所經歷的設計過程其實是一些不同的Activities不停的來回,而且沒有一定的順序。譬如說:


  • 首先要了解需求,開始設計。
  • 如果設計發現有定義模糊的需求,要進一步和stakeholder釐清。
  • 為了要確保設計是可行的,也須需要做POC(Proof of Concept)。
  • 魔鬼都在細節中。所以POC也不能馬虎,幾乎就是要做到Production品質的Code。
  • 如果POC發現問題,要回頭修正設計。
  • 如果POC發現其他Dependency的元件的問題,也要回頭在設計中考慮workaround。
  • 如果設計怎麼調整都沒有辦法解決,又要回頭修正需求/規格。
  • 甚至設計也有可能會發現需求/規格從未考慮過的事情,必須回頭和stakeholder反應。
  • 需求會中途改變,要重新修正設計,同時又要做POC。
  • ...


這一切的活動幾乎不會有停下來的一天,不過這個過程會逐漸收斂(當然也有可能不會收斂,那可能是某種大災難的指標。)。最終確保我可以建構出符合需求的程式碼,這個過程中也有個副產品,就是程式碼本身。

就譬如設計一台太空梭,為了要驗證太空梭的設計藍圖,團隊就必須打造出一台太空梭來通過各式各樣的測試。團隊可能先做出第一版的太空梭,發現一些問題,在重頭修正,打造第二版的太空梭,不停的重複這個過程,直到打造出大家都有信心把太空人送上天的太空梭。

這真的很難區分工程師到底是在建造還是在設計。很有可能在動手做的過程中,卻啟發了他可以改善設計元素。這就是真實世界的情況。

回到Jack的文章,對我來說Jack就只是希望軟體業界能夠承認這個事實,"Source code is the design."。基於這個事實,我們可能需要一些討論來看"需求, 設計, 建構"這樣的模型,到底適不適合用來詮釋軟體開發的過程。

P.S. 其實軟體專案開發過程中還有品質控管(Quality Control)的階段,為了方便說明,在此簡化了這個部分。

Friday, March 11, 2011

Security Enhancement in CRT: Good or Bad Side-Effect?

從Visual Studio 2005開始,編譯器都會建議使用者把某些CRT function換成更安全的版本。舉例來說,編譯器會建議你把strcpy改成strcpy_s。

這樣改的好處是什麼呢?在程式執行的過程中,有一種類型的bug叫做buffer overflow。以strcpy來說,就是你的來源字串長度可能比準備的buffer來的長,又因為strcpy沒有buffer size的資訊,所以在資料複製的過程中,就有可能覆蓋到buffer後面的資料。

如果buffer在stack上,那就有可能覆蓋到其他變數,甚至是call stack的return address。總之,小則程式執行不正常或崩潰,大則會被attacker利用來取得控制權。

換成安全的版本,編譯器就會嘗試把buffer size偷偷帶進strcpy_s當中,幫你在執行的過程中檢查有沒有buffer overflow。如果有,就會呼叫invalid parameter handler。

看到這裡,你可能就直接把全部的strcpy換成strcpy_s。

等等,預設的invalid parameter handler會丟出一個Exception,如果沒有exception handler,預設會執行UnhandledExceptionHandler()。講白話一點,就是會造成程式的崩潰。那使用者會看到什麼呢?答案是一個程式執行無效之類的對話框。

嘿,如果這是個文件編輯程式,使用者編輯到一半的文件就這樣不見了,不氣的跳腳才奇怪。假設樂觀的工程師如你,也是會考慮使用者經驗,又專注完美近乎科科吧!XD

要避免字串複製的buffer overflow,你還可以選擇用strncpy。如果buffer比較小,頂多就是來源字串塞不進去,之後你還可以做一些比較友善的處理。(例如跳出一個切腹道歉的視窗,幫使用者自動儲存文件,之後再crash。噗~)

另外一個方式也沒忘記,你可以覆蓋預設的invalid parameter handler,指到自製的例外處理函式。如果你想要集中處理的話,這樣可以讓所有的安全版的CRT function全都使用同一個handler。

結論:多想兩分鐘,你其實也可以有另一個選擇。XD