2013年3月28日 星期四

Code Complete II《軟體建構之道 2》#9 讀書心得與整理

第九章 虛擬碼程式設計流程
The Pseudocode Programming Process

定義:
PPP(Pseudocode Programming Process)
虛擬碼程式設計流程

這一章節,在訴說著一個由思緒→文字→程式碼的過程,而著重於文字的階段。在設計的過程之中,總是有著許許多多的草稿,而PPP就是一種思緒草稿的實現。
副加價值在於程式碼的文件化,以及不另外花力氣就可以得到很棒的註解。

9.1 構築類別及常式
Summary of Steps in Building Classes and Routines

構築類別的步驟
Steps in Creating a Class

構築類別的大體設計
構築類別內的各常式
全面檢閱並測試類別



建立常式的步驟
Steps in Building a Routine

虛擬常式設計流程(作者最喜歡)
設計→虛擬碼→程式碼

9.2 虛擬碼的優點
Pseudocode for Pros

使用虛擬碼的方針:
  •   精確的說明特定的行為
  •   避免使用程式語言的元素。虛擬碼讓你可以在略高於程式碼本身的層次進行設計
  •   描述實現的目標,而非實現的語法
  •   記載低層次的目標,降低產生程式碼的難度,提高程式碼實現的易錯或矛盾之處。

不好的虛擬碼
Example of Bad Pseudocode
increment resource number by 1
allocate a dlg struct using malloc
if malloc() returns NULL then return 1
invoke OSrsrc_ini 109 t to initialize a resource for the operating system
*hRsrcPtr = resource number
return 0


好的虛擬碼
Example of Good Pseudocode
Keep track of current number of resources in use
If another resource is available
Allocate a dialog box structure
If a dialog box structure could be allocated
Note that one more resource is in use
Initialize the resource
Store the resource number at the location provided by the caller
Endif
Endif
Return TRUE if a new resource was created; else return FALSE

使用虛擬碼的好處
  產生程式碼之後,虛擬碼成為註解
  從高層級分解到低層級的虛擬碼,可以讓你在各層級中發現各層級的錯誤,不會讓高層級的錯誤成為底層級執行的障礙。
  虛擬碼好修改,大改的成本低
  簡化註解的工作

9.3 使用PPP構築常式
Constructing Routines Using the PPP


構築常式會做的事
  1.   設計常式
  2.   撰寫常式
  3.   檢查程式碼
  4.   清除零星問題
  5.   視需要重覆
在這之中,使用PPP會是如何呢?

設計常式
Design the routine

例子:ReportErrorMessage()
ReportErrorMessage()非正式規格:
輸入: error code
輸出: error code對應的error message。
還可以處理無效的錯誤碼。

如果程式是以互動式介面(視窗介面環境)執行:
那ReportErrorMessage()需要向用戶顯示錯誤訊息;

如果程式是以command line環境執行:
那ReportErrorMessage()應把錯誤訊息記錄在一個訊息文件裡。在輸出錯誤訊息之後,ReportErrorMessage()返回一個狀態值,表示其操作是成功還是失敗。
檢查必要條件
  是否定義完成
  確實受到呼叫
定義常式必須解決的問題

    高層級設計必須能指示以下的項目:
  •     常式要隱藏的資訊
  •     常式的輸入
  •     常式的輸出
  •     在常式受到呼叫前,可確定的前置條件(防火牆)
  •     將控制權回傳呼叫者後,可確定的後置條件(防火牆)

  • 以下是ReportErrorMessage()考量的方式:
  •   常式隱藏兩項事實:
    1. error message的文字
    2. 目前處理方法(無論是互動列還是command line)
  •   常式沒有確定的前置條件
  •   常式的輸入是error code
  •   兩種輸出會受到呼叫:
    1. error message
    2. status, ReportErrorMessage() return的值。
  •   常式保證狀態值是「成功」或「失敗」

下列這些步驟主要是建立編寫常式的方向:
命名常式
決定如何測試常式
研究標準程式庫內可用的功能
    重覆使用良好的程式碼
研究演算法及資料類型
    如果在程式庫找不到,就找演算法的書
考慮錯誤處理
考慮效率
    兩種情況:
      1. 效率不重要: 介面常式摘要是否良好,是否具有可讀性
      2. 效率超重要: 架構應指示每一個常式或類別可使用的資源數量
                     因為速度十分重要,所以在此犧牲一點資源換取速度
    在各別常式層級下苦工是白費工夫。架構層級設計的改善才是最佳化的最大功臣。
    除非架構層級無法再最佳化了,再進行各別常式層級的最佳化
    「不要浪費時間壓枝徵末節的改進上,等到有需要再動手也不遲。」
編寫虛擬碼
    到目前都還沒寫出任何的code,但是已萬事俱備,可以開始編寫高層級的虛擬碼,之後虛擬碼會成為程式碼的基礎
    先寫常式標題虛擬碼(註解),再開始寫常式內部行為虛擬碼
//常式標題虛擬碼
This routine outputs an error message based on an error code
supplied by the calling routine. The way it outputs the message
depends on the current processing state, which it retrieves
on its own. It returns a value indicating success or failure.
//常式內部行為虛擬碼
set the default status to "fail"
look up the message based on the error code

if the error code is valid
if doing interactive processing, display the error message interactively and declare success

if doing command line processing, log the error message to the command line and declare success

if the error code is not valid, notify the user that an internal error
has been detected

return status information
  • 設計→概述→角色→常式
考慮資料
    如果資料操作在常式是很重要的部份,在思考常式的邏輯之前,先思考資料的型態,好好的定義常式內的關鍵資料。
檢查虛擬碼(給別人看之類的)
    虛擬碼可以輕易地讓你的假設和高層級的錯誤曝光
    請別人幫你看短短的虛擬碼比看長長的程式碼來得對得起別人。
用虛擬碼嘗試某些想法,然保留最佳的想法(重覆執行這一步)
    高層級虛擬碼→(分解)→低層級虛擬碼


撰寫常式
Code the routine

編寫常式宣告(開始寫程式語言)
/*
This routine outputs an error message based on an error code
supplied by the calling routine. The way it outputs the message
depends on the current processing state, which it retrieves
on its own. It returns a value indicating success or failure.
*/
//新增這一個常式宣告
Status ReportErrorMessage(
ErrorCode errorToReport
)

set the default status to "fail"
look up the message based on the error code

if the error code is valid
if doing interactive processing, display the error message interactively and declare success

if doing command line processing, log the error message to the command line and declare success

if the error code is not valid, notify the user that an internal error
has been detected

return status information
將常式內的虛擬碼轉為高層級註解
/*
This routine outputs an error message based on an error code
supplied by the calling routine. The way it outputs the message
depends on the current processing state, which it retrieves
on its own. It returns a value indicating success or failure.
*/
//新增這一個常式宣告
Status ReportErrorMessage(
ErrorCode errorToReport
)
{
//set the default status to "fail"
//look up the message based on the error code

//if the error code is valid
//if doing interactive processing, display the error message interactively and declare success

//if doing command line processing, log the error message to the command line and declare success

//if the error code isn't valid, notify the user that an internal error
//has been detected

//return status information
}
在每個註解的下方撰寫程式碼(一直到把程式寫完)
/*
This routine outputs an error message based on an error code
supplied by the calling routine. The way it outputs the message
depends on the current processing state, which it retrieves
on its own. It returns a value indicating success or failure.
*/
//新增這一個常式宣告
Status ReportErrorMessage(
ErrorCode errorToReport
)
{
//set the default status to "fail"
Status errorMessageStatus = Status_Failure;
//look up the message based on the error code
Message errorMessage = LookupErrorMessage( errorToReport );

//if the error code is valid
if ( errorMessage.ValidCode() )
{
// determine the processing method →在此新增的
ProcessingMethod errorProcessingMethod = CurrentProcessingMethod();
//if doing interactive processing, display the error message interactively and declare success
if ( errorProcessingMethod == ProcessingMethod_Interactive )
{
DisplayInteractiveMessage( errorMessage.Text() );
errorMessageStatus = Status_Success;
}
//if doing command line processing, log the error message to the command line and declare success
else if ( errorProcessingMethod == ProcessingMethod_CommandLine )
{
CommandLine messageLog;
if ( messageLog.Status() == CommandLineStatus_Ok )
{
messageLog.AddToMessageQueue( errorMessage.Text() );
messageLog.FlushMessageQueue();
errorMessageStatus = Status_Success;
}
}
}
//if the error code isn't valid, notify the user that an internal error has been detected
else
{
DisplayInteractiveMessage("Internal Error: Invalid error code in ReportErrorMessage()");
}
//return status information
return errorMessageStatus;
}
檢查程式碼是否需要進一步重整
    常式→虛擬碼→程式碼→提成一個新常式
                          ↖   ↙
                          再分解


檢查程式碼
Check the code

虛擬碼的錯誤,會在細實作邏輯中變得較為明顯
優雅的虛擬碼→笨拙的實作語言

細部實作時,高層級、架構的錯誤可能會揭露
可能還有一些常見的錯誤,未能保持一致化(畢竟人不是完美的)
所以,在此要再檢查程式碼

在心裡檢查常式的錯誤
  在心中模擬常式有一定的難度,但這樣的難度卻是常式精簡的原因之一
  • desk checking
  • peer review
  • walkthrough
  • inspection
在此專不專業的最大差異:   「迷信」vs「真正理解」
「迷信」不是指讓你毛骨悚然的程式,或是在月圓時會產生額外錯誤的程式;而是以感覺取代對程式碼的了解
  • 程式碼出錯原因機率分佈: 
  • 硬體、編譯器、作業系統: 5%
    程式碼的問題: 95%
「假設沒問題」→不存在
不了解「為什麼這段程式碼可行」=「可能出問題的程式碼」
務必了解程式為什麼可以工作。

編譯常式
  流程後面再來編譯。
  當你編譯新程式碼時,內心的時鐘會開始走動。
  第一次編譯後,就會增加壓力,心想「再編譯一次就完成了!」
  「再編譯一次就完成了!」的心理吶喊,會在變更程式碼的過程匆促,容易出錯。然後開始東拼西湊,try and error編寫程式,陷入hacking-and compiling,大鍋炒編譯惡性循環。
注意人類對「hacking(大鍋炒), compiling, and fixing」程式寫作方法的情感牽扯

充份發揮編譯功能的指南:
  1. 將編譯器設定成最挑剔的等級
  2. 使用驗證工具(validators)
  3. 消除警告的原因
在除錯工具中逐步執行程式碼
測試程式碼
    開發不出貨的支架(鷹架)
移除常式錯誤
    錯誤太多,重寫常式,絕對值回票價


收拾殘局
Clean up leftovers

檢查....別的章節的東西

視需要重覆步驟
Repeat as needed

必要的時候,重新流程或...重新設計

9.4 PPP的替代方法
Alternatives to the PPP


Test-first development
參考《測試驅動開發》-Kent Beck
Refactoring
參考《重構:改善既有的程式碼的設計》-Martin Fowler
Design by contract
參考《物件導向之軟體構築》-Bertrand Meyers
Hacking
還是使用PPP吧!XDD

沒有留言:

張貼留言