藍森林首頁 | 返回主頁 | 本站地圖 | 站內搜索 | 聯繫信箱 |
 您目前的位置:首頁 > 自由軟件 > 技術交流 > 應用編程


    

藍森林 http://www.lslnet.com 2006年6月6日 10:18


[轉帖]欣賞最優的程序代碼(二)  

[color=darkblue]
第二篇:好與壞?
上一節我們討論了優美的子程序對整體系統的影響,但是大家可能還是比較模糊,讓我們來看一個實際的例子:

   在討論高質量子程序的細節問題之前,我們首先來考慮兩個基本名詞。什麼叫「子程序」?子程序是具有單一功能的可調用的函數或過程。比如C 中的函數,Pascal 或Ada 中的函數或過程,Basic中的子程序或Fortran 中的子程序。有時,C 中的宏指令或者Basic中用GOSUB調用的代碼塊也可認為是子程序。在生成上述函數或過程中,都可以使用創建高質量子程序所使用的技術。什麼是「高質量的子程序」?這是一個比較難以回答的問題。反過來最簡單回答方式是指出高質量子程序不是什麼。下面是一個典型的劣質子程序(用Pascal寫成):

   Procedure HandleStuff ( Var InputRec:CORP_DATA,CrntQtr:integer,
             EmpRec:Emp_DATA, Var EstimRevenue:Real, YTDRevenue:Real,
             ScreenX:integer,ScreenY:integer,Var NewColor:Color_TYPE,
             Var PrevColor:COLOR_TYPE,Var Status:STATUS_TYPE,
             ExpenseType:integer);
       begin
       for i := 1 to 100 do begin
           InputRec.revenue[1]:= 0;
           InputRec.expense[i]:=CorpExpensse[CrntQtr,i]
           end;
       UpdateCorpDatabase(EmpRec);
       EstimRevenue:=YTDRevenue * 4.0 /real(CrntQtr)
       NewColor:=PrevColor;
       status:=Success;
       if ExpenseType=1 then begin
                              for i:= 1 to 12 do
                              Profit[i]:= Revenue[i]-Expense.Type[i]
                              end
       else If ExpneseType= 2 then begin
                              Peofit[i]:=Revenue[i] - Expense.Type2[i]
                              end
       else if ExpenseType= 3 then
       begin
           Profit[i]:=Revenue[i] - Expense.Type3[i]
           end
       end

   這個子程序有什麼問題?給你一個提示:你應該至少從中發現10個問題。當你列出所發現的問題後,再看一下下面所列出的問題;

   · 程序的名字讓人困惑。Handlestuff()能告訴我們程序是幹什麼的嗎?
   · 程序沒有被說明。
   · 子程序的佈局不好。代碼的物理組織形式幾乎沒有給出其邏輯組織形式的任何信息。佈局的使用過於隨心所欲,程序每一部分的佈局都是不一樣的。關於這一點。只要比較一下ExpenseType=2 和ExpenseType=3 兩個地方的風格就清楚了。
   · 子程序的輸入變量值InPutRec 被改變過。如果它作為輸入變量,那它的值就不該變化。如果要變化它的值,就不該稱之為輸入變量InputRec。
   · 子程序進行了全局變量的讀寫操作。它從CorpExpense 中讀入變量並寫給Profit。它應該與存取子程序通信,而不應直接讀寫全局變量。
   · 這個子程序的功用不是單一的。它初始化了某些變量。對一個數據庫進行寫操作,又進行了某些計算工作,而它們又看不出任何聯繫。一個子程序的功用應該是單一,明瞭的。
   · 子程序中沒有採取預防非法數據的措施。如果CrntQtr 的值為「0」,那麼,表達式YTDRevenue*4.0/real(CrntQtr)就會出現被零除的錯誤。
   · 程序中使用了幾個常數:100, 4.0, 12, 2和3。
   · 在程序中僅使用了域的CORP_DATA 型參數的兩個域。如果僅僅使用兩個域,那就該僅僅傳入特定的域而不是整個結構化變量。
   · 子程序中的一些參數沒有使用過。ScreenX 和ScreenY 在程序中沒有涉及。
   · 程序中的一個參數被錯誤標定了。PrevColor被標定為變量型參數,然而在程序中又沒有對其賦值。
   · 程序中的參數太多。程序中參數個數的合理上限應該是七個左右。而這個程序中則多達11個。程序中的參數多得怕人,恐怕沒誰會仔細檢查它們,而且連數一下都不願意。

   這麼一小段代碼竟然有這麼多的錯誤,這些錯誤你是不是能夠都避免呢?如果是,那麼恭喜你,你的程序員的功底相當深。雖然說這些錯誤並不是很大不了的東西,絲毫不影響程序的正確性和速度。在系統越來越複雜的現代,如果系統裡的每一個子程序的複雜度、可讀性、耦合度都影響到整體系統的成功與否。但是如果是在寫個人的程序,那你是對自己的虐待,如果是做為開發團體的一分子,那麼,你的做法是對同伴的虐待。

   除了計算機本身之外,子程序可以說是計算機科學最重大的發明。子程序使得非常好讀而且也非常容易理解,編程語言中的任何特性都不能和這一點相比。像上例中那樣使用子程序,簡直就是對子程序的踐踏,甚至可以說是一種犯罪。
子程序也是節省空間和提高性能的最好手段。想像一下,如果用代碼段去代替程序中對子程序的每一次調用,那麼程序將會有多麼龐大。如果不是把多次重複使用的代碼段存放在子程序中,而是直接把它放在程序中,那麼對其進行性能改進將是一件很困難的事。是子程序使現代編程成為可能。

   現在,你可能有些不耐煩。「是好,子程序的確很了不起,我一直都在使用它」。你說,「你的討論似乎像是在糾正什麼,你到底想讓我做什麼呢?」
   我想說的是:有許多合理的原因使得我們去生成子程序。但是生成方法有好有壞。作為一個計算機專業的本科生,可以認為生成子程序的主要原因是避免代碼段的重複。我所用的入門課本告訴我說,之所以使用於程序,是因為它可以避免代碼段的重複,從而使得一個程序的開發、調試、註釋和維護工作都變得非常容易。除了一些關於如何使用參數和局部變量的語法細節之外,這就是那本課本關於子程序理論與實踐內容的全部。這實在不是一個完全而合理的解釋。

   子程序的好處我們在上一節有詳細的闡述,既然子程序有著這麼多的好處,那麼編寫一個優美甚至完美的子程序對我們有什麼困難的呢,其實這是一個思想和習慣的問題。程序員就一定是那種穿褲衩兒背心,聽著搖滾寫程序的人嗎?我覺得真正的程序員應該是一絲不苟的,分析需求、確定架構、做概要設計和詳細設計、單元測試、整理所有文檔。麻煩吧,是很麻煩,但是這會為你帶來意想不到的驚喜。你會發現你的程序是那麼的清晰:再多的版本也不會把你的腦袋搞暈,一個小錯誤在數萬行的代碼中輕易的被定位del,用戶提出新的需求是你的臉色不再難看…。所有的這些只意味著一件事:你成功的控制住了出自你手的程序。

   編寫子程序的最大心理障礙是不情願為了一個簡單的目的而去編寫一個簡單的子程序。寫一個只有兩或三行代碼的子程序看起來是完全沒有必要的。但經驗表明,小的子程序也同樣是很有幫助的。
   小型子程序有許多優點,其中之一是改進了可讀性。我曾在程序中採用過如下這樣一個僅有一行的代碼段,它在程序中出現了十幾次:

   Points = DeviceUnits * ( POINTS_PER_INCH / DeviceUnitsPerInch ( ) )

   這決不是你所讀過的最複雜的一行代碼。很多人都明白它是用來轉換的。他們也會明白程序中的每行這個代碼都在作同一件事,但是,它還可以變得更清楚些,所以,我創建了一個恰當命名的子程序來作這些工作。

   DeviceUnitsToPoints(DeviceUnits Integer): Integer;
   begin
       DeviceUnitsToPoints = DeviceUnits *
                             ( POINTS_PER_INCH / DeviceUnitsPerInch ( ) )
   end

   在用這段子程序來代替那些十幾次重複出現的代碼行後,這些代碼行基本上都成了如下的樣子:
   Points = DeviceUnitsToPoints ( DeviceUnits )
   這顯然更具可讀性,甚至已經達到了自我說明的地步。
   這個例子還暗示了把簡單操作放入函數的另外一個原因:簡單操作往往傾向於變成複雜操作。在寫這個子程序時我還不知道這一點,但在某種情況下,當某個設備打開時,DeviceUnitPerInch()會返回零,這意味著我不得不考慮到被「0」除的情況,這樣,又需要另外的三行代碼;

   DeviceUnitsToPoints( DeviceUnit:integer):integer;
   begin
       if( DeviceUnitsPerInch ( ) <>; 0 ) then
            DeviceUnitsPoints = DeviceUnits *
                                ( POINTS_PER_INCH / DeviceUnitsPerInch ( ) )
       else
             DeviceUnitsToPoints= 0
   end

   如果原來的代碼行仍然在程序中出現十幾次,那麼這一測試也要重複十幾次,需要新增加36 行代碼。而一個簡單子程序輕而易舉地便把36 變成了3。

   在這一節中,我們看到了子程序的質量問題,高質量的子程序和低質量的子程序對系統的影響的差別是極為明顯的。你可以喝著咖啡,好整以暇的看著你井然有序的程序,也可以抓耳撓腮的對著你混亂不堪的程序幾乎發狂。這都取決於你良好的編程習慣,選擇一下吧。



[/color]



Copyright © 1999-2000 LSLNET.COM. All rights reserved. 藍森林網站 版權所有。 E-mail : webmaster@lslnet.com