程式碼的靜態分析工具有一個用途,就是依據許多針對程式碼的量度指標,透過分析的過程取得相關的數據,供程式設計者參考。

檢視這些指標後,便可以讓程式設計者了解目前程式碼的特性,以及依據各種量度指標需要特別值得留意的部分。此外,如果想改善程式碼的品質,又應該優先針對那些程式碼來進行處理,或是該如何擬定改進的策略。

程式碼量度指標中,不少是和程式碼的可維護性和複雜度有關。現在,就讓我們討論這類指標。

程式碼行數
所有原始碼檔案加總的程式碼行數,可以被拿來估算系統的規模,而這也是這一項指標時常被運用的方式。不過,除了用於系統規模的估算之外,它也有助於我們研判可能需要特別留意的程式碼。

對物件導向的程式碼而言,我們可能會想知道個別的類別,其程式碼行數分別為何,也會想知道每個類別的每個函式,其程式碼行數又為何。

無論是單一類別的程式碼行數過長,或是單一函式的程式碼行數過長,通常都不是太好的現象。在重構裡也針對所謂的「冗長類別(long class)」或是「冗長函式(long method)」提出對應的重構方法。

冗長類別為什麼不好?因為,這通常代表這個類別裡頭能夠執行的工作太多。它不見得複雜,但時常具備很多的功能,例如,它有很多的函式,每個函式都能夠提供不同作用。具備眾多功能的類別,就跟超人一樣,所以稱為超級類別(super class)。

只要寫一個類別,就可以解決各種疑難雜症,不是很方便嗎?這正是很多超級類別產生的原因。

就像好萊塢知名電影「蜘蛛人」中的臺詞所說的:「能力愈大,責任愈大」。責任愈多的類別,代表有愈多的類別會傾向於依賴它,這會自然而然形成其他類別對它的相依性。愈高的相依性,就代表這個類別的改變,會波及到愈多的類別,對程式的維護或日後的修改而言,當然不是什麼好事。超級類別愈多,系統的複雜程度肯定會相對提高。

當你分析出每個類別的程式行數之後,就可以依照程式行數由大至小排序,找出程式行數特別長的類別,然後逐一檢視是否應該進行類別的調整工作(例如運用重構技巧中的「類別萃取」方法)。

同樣的,一個類別即使總行數沒有特別的長,但是其中也有可能有過長的函式,而過長的函式同樣也會構成問題。

過長的函式常常不是邏輯過於複雜,就是內容含有和其他函式重複內容的程式碼,而過長的函式也會影響到可讀性,因此,利用工具找出過長的函式,就可以重新審視該函式是否有縮短的必要,以及應當如何縮短。

類別的個數與各類別中的函式個數
除此之外,分析工具也能協助你得到每個類別的函式個數,以及所有類別的平均函式個數。得到這個資訊能提供什麼作用呢?它可以讓我們對於類別被賦予的責任有一個大概的概念。

雖然,可以從程式行數切入,來判斷某個類別是否是個超級類別,不過有時候,程式總行數多的類別,可能不見得是超級類別,它只不過是具有若干個冗長的函式罷了,其實它的函式總數並不多。有些類別,或許總行數並不長,責任卻很多,因為它擁有很多的函式,尤其是會出現在介面上的公開函式。

什麼樣的類別有可能會有很多的函式,但實際程式碼總長度並不長呢?例如像Facade模式中的Facade類別,就可能具備這樣的特性,因為此類的類別只是一個子系統的入口,時常只是再轉遞到子系統中的內部類別。當一個類別的函式個數過多時,即使它的程式碼總長度沒有明顯的過長,但仍然有可能是個超級類別,這個時候,就應該為它做一些責任的拆解,避免該類別成為一個超級類別。

當然,我們也可以知道所有類別的平均函式個數,這有助於評估系統整體的生態及目前程式碼的特性。

繼承深度
繼承深度代表的是某個類別在樹狀的繼承階層架構中至根類別的距離,也就是說,從根類別開始,究竟經過了多少次的繼承關係,才得到這個類別。

繼承深度愈深,程式碼可讀性就可能愈差。怎麼說呢?例如,當我們在檢視某個類別的程式碼時,看到其中運用到某個函式或資料欄位,這函式或欄位並不是在這個類別中所定義的,而是從父祖類別繼承得來。為了明白如何繼承,就必須在繼承階層架構中一層層尋找,在理解上便有可能造成困難。

許多初學物件導向的程式設計者,會因為繼承是物件導向的重要特色,而大量的運用繼承,甚至設計出高深度的繼承階層。這樣的設計,很多時候,都含有一些不必要的繼承關係。繼承也不像一些程式設計者想像得美好,因此,慎用繼承是目前普遍的觀念。而無論如何,深度過高的繼承階層,可能會衍生出一些問題,需要檢視是否必要。

軟體模組內部決策邏輯複雜度
之前介紹過所謂McCabe的Cyclomatic Complexity(循環複雜度)。這是由Thomas J. McCabe 在1976年所提出,是評估軟體模組內部決策邏輯複雜度的一個指標。它的基本精神即在於,程式碼中若決策分支(例如if-else的條件分支)愈多,那麼,決策的邏輯複雜度就相對愈高。

為什麼這指標可以做為程式碼複雜度的參考呢?你可以想像,若一段程式碼的控制流程中有愈多的可能路徑,程式碼本身行為就愈難理解。這種情況下,程式設計者可能無法很完備地思考、檢視、理解程式碼,因為分支太多,所以,程式碼出錯的機會就會提高許多。此外,太多的可能路徑,也意謂著測試上的困難度。因為必須設計更多的測試案例,才足以涵蓋這麼多的可能執行路徑。

無論是正在撰寫的程式或是已經存在的程式碼,都可以利用工具計算出程式碼的循環複雜度,找出高複雜度的程式碼,重新予以審視、思考如何調整修改,以降低其複雜度。同樣的,評估平均的循環複雜度,也有助於了解整體的情況。

程式碼相依性
而在軟體設計上,通常都希望模組間低耦合,而模組內高聚合。但是耦合力和聚合力如何評估呢?關於耦合力,有兩個指標可以參考:一個是向心耦合(Afferent Coupling,Ca),另一個是離心耦合(Efferent Coupling,Ce)。

向心耦合的意義是依賴該模組的外部模組個數,而離心耦合的意義則是該模組所依賴的外部模組個數。基於這兩個指標,可以計算出另一個叫做不穩定性(Instability,I)的指標。

I = Ce/(Ce+Ca)

不穩定性的值,會落在0與1之間,0代表最穩定,而1則是很不穩定。

在物件導向設計的原則裡,有一個叫做穩定相依原則(Stable Dependencies Principle,SDP)。這個設計的原則是希望模組間的相依關係,是朝向比較穩定的方向去進行。所以呢,若在從事設計時,希望某個模組A相依於另一個模組B,那麼B就應該要比A穩定,也就是說,B的I值應該要小於A,這麼一來,相依關係才能朝向穩定的方向發展。

被別的模組依賴,相較於依賴別的模組是一種穩定。因為依賴另一個模組,意謂著當另一個模組有所變化時,自身也有可能被這變化所波及,因而必須改變。當然,設計上我們會希望被倚賴的模組本身,被變化所波及的可能性愈低愈好。有了這些指標,便可以協助我們來評估這種情況。

 

專欄作者

熱門新聞

Advertisement