05 axp0132 04前言epaper.gotop.com.tw/pdfsample/axp013200.pdfxxviii 前言 1990...

16
xxvii 前言 Preface 身為明導科技(Mentor Graphics Corporation) 積體電路處的一名成員,我非常 幸運地與一群聰慧的天才軟體工程師一起開發非常大型的軟體系統。 明導科技在 1985 年成為嘗試開發大型 C++ 軟體的領先公司,回顧那個年 代,沒有人知道如何開發如此大型的系統,也沒有人能預測到應用一般的開 發方式於大型軟體上,會帶來許多額外的成本支出、開發時間的延遲、超大 的執行檔、極差的效能以及非常冗長的編譯建構時間。 在如此苦澀的經驗下,我們學到了許多寶貴的知識,當時沒有任何一本書籍 能夠指引我們開發大型軟體的流程,物件導向設計在如此大系統下的應用尚 未有人嘗試過。 十年過去了,在經歷過這段寶貴的開發過程中,明導科技得到了許多珍貴的 經驗,這段經驗免除掉後來我們開發大型軟體的額外高花費,進而開發出許 多以 C++ 開發出的大型軟體系統。 在我 13 年的 C( 轉型至 C++) 電腦輔助系統(Computer-Aided Design) 軟體開發 的生涯中,我看過太多“良好的先期規劃總會產生高品質與易維護的軟體系 統”的實證。我在明導科技的職務重點便在於協助確認在先期規劃中軟體系 統的品質已被考慮及整合進開發流程中。

Upload: others

Post on 06-Mar-2020

11 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: 05 AXP0132 04前言epaper.gotop.com.tw/PDFSample/AXP013200.pdfxxviii 前言 1990 年時,我在哥倫比亞大學準備開授一門研究所的課程:物件導向設計 與程式實作,自1991

xxvii

前言 Preface

身為明導科技(Mentor Graphics Corporation)積體電路處的一名成員,我非常幸運地與一群聰慧的天才軟體工程師一起開發非常大型的軟體系統。

明導科技在 1985 年成為嘗試開發大型 C++ 軟體的領先公司,回顧那個年代,沒有人知道如何開發如此大型的系統,也沒有人能預測到應用一般的開

發方式於大型軟體上,會帶來許多額外的成本支出、開發時間的延遲、超大

的執行檔、極差的效能以及非常冗長的編譯建構時間。

在如此苦澀的經驗下,我們學到了許多寶貴的知識,當時沒有任何一本書籍

能夠指引我們開發大型軟體的流程,物件導向設計在如此大系統下的應用尚

未有人嘗試過。

十年過去了,在經歷過這段寶貴的開發過程中,明導科技得到了許多珍貴的

經驗,這段經驗免除掉後來我們開發大型軟體的額外高花費,進而開發出許

多以 C++ 開發出的大型軟體系統。

在我 13年的 C(轉型至 C++)電腦輔助系統(Computer-Aided Design)軟體開發的生涯中,我看過太多“良好的先期規劃總會產生高品質與易維護的軟體系

統”的實證。我在明導科技的職務重點便在於協助確認在先期規劃中軟體系

統的品質已被考慮及整合進開發流程中。

Page 2: 05 AXP0132 04前言epaper.gotop.com.tw/PDFSample/AXP013200.pdfxxviii 前言 1990 年時,我在哥倫比亞大學準備開授一門研究所的課程:物件導向設計 與程式實作,自1991

xxviii 前言

1990 年時,我在哥倫比亞大學準備開授一門研究所的課程:物件導向設計與程式實作,自 1991 年以來,身為這門課的講師,我有機會去分享我在明導科技開發工業級強度的軟體所得到的想法。由數百名研究生及專業程式員

逐字逐句的提出問題及回應中,使我粹煉出去許多重要的想法與知識。據我

所知,本書是論及大型 C++ 專案中關於“軟體開發”與“軟體品質”諸議題的第一本書,我希望本書提供的資訊能對您未來的工作中帶來收穫,正如同它

對我的工作一樣。

誰適合閱讀本書

這本大型 C++ 軟體設計是為有經驗的 C++ 軟體設計工程師、系統架構設計師以及專業的軟體品質測試人員所撰寫的。本書非常適合有參與開發過類

似資料庫、作業系統、編譯器或應用程式框架(Frameworks)的專業人士。

使用 C++ 來開發大型的軟體系統不只需要擁有紮實的邏輯設計知識(坊間絕大部分 C++ 程式教學書籍所涵括的),好的設計還需要掌握實體設計的觀念。實體設計的觀念雖然與軟體開發層面息息相關,但是許多相當有經驗的

軟體開發工程師可能都只知皮毛或一無所知!

這本書提出的大多數忠告也適用於小型的軟體專案,通常程式設計師先由小

型專案開始練功,逐漸能夠接手大型商業軟體等級的專案。通常來說,一個

軟體專案的規模會逐漸地變大,但是在一開始專案尚小時的初始設計卻會永

久保存,所以說,在一個軟體專案規模尚小時若不考慮將一些適合設計大型

軟體的好習慣帶進去,則後果將不堪設想。

本書整理了許多高階的設計觀念與一些特殊的 C++ 語言細節以滿足下列兩大需求:

1. 一本適合實際應用的 C++ 物件導向設計教科書

2. 一本專門探討如何使用 C++ 來設計非常大型軟體系統的教科書

Page 3: 05 AXP0132 04前言epaper.gotop.com.tw/PDFSample/AXP013200.pdfxxviii 前言 1990 年時,我在哥倫比亞大學準備開授一門研究所的課程:物件導向設計 與程式實作,自1991

前言 xxix

嚴格來說,本書是一本進階的教材,本書並不打算介紹 C++ 的語法或者 C++ 語言的特殊應用。本書著重於介紹讀者如何發揮 C++ 的全部威力以設計非常大型的軟體。

簡單來說,如果您自認您已經夠瞭解 C++ 了,但您想更深入的學習如何將此語言有效的發揮於大型軟體專案上,這本書就是為你所寫的。

本書所列之範例

許多人是由範例中學習,我在本書中所舉的例子都是“真實世界的設計”,我不會為了講解某些觀念而舉出有明顯設計上錯誤的例子。我也不會舉出一些

專為介紹程式語言的某些細節而沒有任何實用價值的例子。

除非特別提及,不然本書所有的例子都是為了介紹“好的設計”。所以每章的範例都不會與違背本書任一章所介紹的觀念。不過這也會帶來缺點,您也許

會看到一些程式碼與您常見到的不大一樣而且不知道為何我會這麼寫。我

想,為了能夠舉出與整本書觀念一致的範例,這樣的犧牲是必要的。

不過本書有兩個顯著的例外:分別是程式碼註解與前綴符號 (prefix)。在本書中的許多例子當中,為了多放些程式碼,省些空間,註解將會被省略。一

旦我寫了註解,那麼它們將會非常簡略。很不幸的,讀者可能會在這點上面

抱怨我在本書中“言行不符”,在此我得向您解釋,在我平常的工作中,對於我所撰寫的程式介面,我都會一並附上仔細與清楚的註解,這些註解皆非事

後被要求再加上去的,而是在一開始撰寫程式即一並寫上去。

第二個例外就是本書前幾章中的例子,直接省去了前綴符號的修飾,在大型

軟體專案中,根據不同套件或是模組會加上一些前綴符號,用以表明某些變

數或某些函式所屬的用途,但對於不常使用的人來說,這樣的程式碼有點難

讀。我會省略這種前綴符號,直到我在第七章中正式介紹它們,以免對於我

在此之前想探討的其他重點因此失焦。

許多著重於介紹“程式功能面”的教科書常使用 inline 函式於它們的例子當中,因為本書著重於介紹“程式組織面”,比如說何時才使用 inline 等議題,我傾向在範例中一律不使用 inline,而一旦我使用了 inline,一定會有比“方便”更好的理由。

Page 4: 05 AXP0132 04前言epaper.gotop.com.tw/PDFSample/AXP013200.pdfxxviii 前言 1990 年時,我在哥倫比亞大學準備開授一門研究所的課程:物件導向設計 與程式實作,自1991

xxx 前言

開發大型 C++ 系統是在一系列取捨中做決定,通常沒有一定的準則。在本書中使用“決不”或“一定”等強烈的字眼會有點不切實際,這樣子的字眼通常是為了簡化教材才用的,對於我預設會閱讀本書的 C++ 程式設計師,這樣強烈的字眼與句子應該會常被質疑,為了避免產生這樣的窘境,我會註明什

麼是(幾乎)永遠正確的事,然後搭配一些註解或者舉出反例。

There 有許多副檔名用來區分 C++ 的表頭檔 (Header File)與實作檔

(Implementation File)。舉例來說:

表頭檔副檔名: .h .hxx .H .h++ .hh .hpp

實作檔副檔名: .c .cxx .C .c++ .cc .cpp

我將使用 .h 作為表頭檔的副檔名,使用 .c 作為實作檔的副檔名。而且當我提到“.h檔”就代表表頭檔,提到.c檔,就代表實作檔。

最後,本書中的所有範例皆於 Sun SPARC 工作站與 HP700 工作站上編譯測試無誤,分別使用 CFRONT 3.0 與 HP 內建編譯器,當然,其中若有任何錯誤,都是作者我的責任。

閱讀指引

本書包含了許多主題,而不是所有本書的讀者都有一樣的程度。為此我在第

一章提供了一些基本課題(但不是 C++ 語言簡介),輔助入門。C++ 老手可以跳過本章,等到有需要時再看。第二章則包含了許多軟體設計準則,而我

則希望老手們能夠快速的瀏覽過該章。

Chapter 0:簡介

初窺本書提供給志於設計 C++ 大型軟體的工程師之菜色

PART I:BASICS

Chapter 1:預備知識

回顧一些語言的特性、常見的設計範式(Design Patterns)以及本書使用的符號表示法

Page 5: 05 AXP0132 04前言epaper.gotop.com.tw/PDFSample/AXP013200.pdfxxviii 前言 1990 年時,我在哥倫比亞大學準備開授一門研究所的課程:物件導向設計 與程式實作,自1991

前言 xxxi

Chapter 2:基本規則

介紹一些非常重要而且所有 C++ 專案都適用的設計經驗與通則

本書剩下的內容將分成兩大部分,第一部分:“實體設計的觀念 (Physical Design Concepts)”,將介紹一系列與大型軟體的實體結構(檔案、資料夾…等)有關係的主題。這部份章節(3 至 7)的內容對於許多程式設計師來說相當陌生,但卻是大型軟體設計知識中的骨幹,這部分的內容是用“先見樹後見林”的方式呈現,每一章都架構在前一章所新學的知識上。

PART II:實體設計的觀念

Chapter 3:元件

系統最基本的實體建構單元

Chapter 4:實體層級

為了測試、維護以及重複使用,建構一個實體上具備非環形相依性

的元件層級是很重要的。

Chapter 5:階層化

減少連結時期相依性的特殊技術

Chapter 6:絕緣

減少編譯時期相依性的特殊技術

Chapter 7:套件

延伸上述技術到較大的系統

最後一部分,“邏輯設計的議題”,重點放在伴隨實體設計的傳統邏輯設計知識與訓練,這些章節(8~10)以設計一個元件作為探討的整體,概括介面設計會遇到的諸多問題以及大型專案環境中會遇到的實作議題。

Page 6: 05 AXP0132 04前言epaper.gotop.com.tw/PDFSample/AXP013200.pdfxxviii 前言 1990 年時,我在哥倫比亞大學準備開授一門研究所的課程:物件導向設計 與程式實作,自1991

xxxii 前言

PART III:邏輯設計的議題

Chapter 8:元件架構設計

概觀介紹對元件整體設計有顯著影響的諸多議題

Chapter 9:函式設計

深入探討建構元件功能介面等議題

Chapter 10:實作一個物件

探討數個在大型專案中實作物件關於組織的議題

某些在本書中參考到的資料將列於附錄中。

Page 7: 05 AXP0132 04前言epaper.gotop.com.tw/PDFSample/AXP013200.pdfxxviii 前言 1990 年時,我在哥倫比亞大學準備開授一門研究所的課程:物件導向設計 與程式實作,自1991

1

0簡介

Introduction

開發一個好的 C++ 程式並不容易,當軟體專案的規模擴大,考慮增進其穩定性與易維護性會使難度大大提高,而且需要許多新知識與技術才能完成。

就如同一名工匠不能以建造小木屋的經驗來建造摩天大樓。在小型 C++ 專案設計上得到的經驗與知識通常對於大型軟體專案來說不是很適用。

本書的內容是介紹讀者如何開發非常大型且非常高品質的軟體系統,尤其適

用於那些努力開發具高維護性且容易測試的軟體架構的資深軟體工程師。本

書並不是一本理論型程式設計專書,而是從許多非常大型、多終端(multi-site)系統的長年開發經驗萃取出來一本實際型的專書。我們將會展示如何設計具

有數百個開發人員、上千個 class 與數百萬行程式碼這種規模的大型軟體。

本章重點將放在一些當我們開發大型 C++ 軟體所會碰到的問題,並為前幾個章節中所將介紹的知識建構一些基礎的觀念,本簡介中,許多名詞在我們

尚未介紹或正式定義前便使用,但大多數這樣的名詞應該由上下文便能了

解,在後續的章節中,這些名詞將會有更精確的定義,投資將在第五章開始

回收,我們開始在我們的 C++ 系統中採用特別的技術以減少耦合性

(coupling,相互依賴關係的程度)。

Page 8: 05 AXP0132 04前言epaper.gotop.com.tw/PDFSample/AXP013200.pdfxxviii 前言 1990 年時,我在哥倫比亞大學準備開授一門研究所的課程:物件導向設計 與程式實作,自1991

2 簡介

0.1 由 C 到 C++

普遍認為以物件導向設計來對付大型軟體設計的複雜細節會有許多潛在的

好處。在我撰寫本書的當下,C++ 設計設計師的數量大約是每 7 到 9 個月就增加了兩倍

1,在資深的 C++ 程式設計師手中,C++ 是一項非常有威力

而且能將個人技術與天賦發揮的強而有力的工具,但“採用 C++ 便能保證大型軟體專案的成功”的想法卻大錯特錯。

C++ 並不只是 C 語言的延伸,它提供一種全新的思維。物件導向設計比“程序式設計(Procedural)”難懂而且需要花比較多的功夫,C++ 比 C 更難精通而且隱藏了許多會讓你犯錯的細節,通常你不會意識到你犯了某些重大的錯誤

直到交差在即而難以修補,甚至一些小小的“失言”,比如說濫用虛擬函式

(virtual function)或將使用者自定型別以傳值方式傳入函式,這將會造成看似語法非常正確的一支 C++ 程式,卻可能十倍慢於你以 C 所寫的同樣程式。

在一開始接觸 C++ 時,總有一段生產力大幅下降的磨合期,此時你會發現許多替代的設計方式,在這段時期中,許多慣用程序式進行設計的程式設計

師不是十分樂意轉向物件導向式設計。

縱然是一名 C 語言的專業老手,在一開始使用 C++ 語言可能會寫出較大與較複雜的程式碼,但其實不需多久,一名有足夠能力的 C 程式設計師便能將其改寫,程式的大小與複雜度都能獲得改善,但很不幸的,一些未經熟慮便

使用於小型軟體專案的技術可能在開發大型 C++ 軟體時變得全不適用。換個角度講,一般的 C++ 知識對於大型軟體專案不見得有用,例子不勝枚舉。

0.2 使用 C++ 開發大型軟體專案

如同 C 程式碼一樣,寫的不好的 C++ 程式是非常難理解與維護的,如果程式介面沒有好好的加以封裝(encapsulating),將不容易調校或改進,不好的封裝將使複用性(reuse)與可測性(testability)降低。

1 stroustrup94,7.1 節,163~164 頁。

Page 9: 05 AXP0132 04前言epaper.gotop.com.tw/PDFSample/AXP013200.pdfxxviii 前言 1990 年時,我在哥倫比亞大學準備開授一門研究所的課程:物件導向設計 與程式實作,自1991

0.2 使用 C++ 開發大型軟體專案 3

也許與你所想的不一樣,採用物件導向設計出來的程式,其最廣義的形態遠

比採用程序式設計出來的版本來的難測試與驗證2。虛擬函式所擁有改變物

件內在行為的能力足以使原始設計認為不會被改變的行為改變。猶有甚者,

物件導向程式的潛在控制流程路徑(control flow path)的數量是相當大的。

幸好,一般來說不必寫出如此廣義(同時也難測)的物件導向程式,略為限制這種設計思維將可帶來較容易測試的子集合,程式也將較為穩定。

當程式越來越龐大時,不同面向的課題將逐漸湧現,以下的小節將解說一些

此時易於面臨到的問題。

0.2.1 環形相依性

身為一位軟體專家,您大概曾邂逅過某個素昧平生的軟體系統,卻怎麼也找

不到一個適當的切入點或者是找出一個合理的子系統。不易瞭解或不易拆解

的軟體系統是具有環形相依特性的徵兆。C++ 的物件之間具有互相糾結的傾向,圖 0-1 說明了這種實體面緊密耦合的不好特性,在例子當中,電路

(circuit)是組件(element)與導線(wire)的組合。因此,電路這個物件清楚知道組件與導線的定義。而組件則知道它隸屬於哪個電路,而且也知道它是否

有連接到某根導線,所以說組件這個物件也認識電路與導線這兩個物件。最

後,導線的兩端要不連接組件,要不連接某個電路。為了完成這個任務,導

線這個物件必須能夠存取組件與電路的定義。

為了增進模組化,這三個物件的定義分別放在三個實體“元件”(translation unit 編譯單元)中,雖然它們各自的實作細節都被其介面良好的封裝起來,但每個元件的.c檔卻得引入(include)另外兩個元件的表頭檔,因此,這三個元件彼此之間的相依關係若以圖形表示則為環狀的,這意味著,沒有一個元

件能夠獨立被使用或者被獨立進行測試。

2 perry,13~19 頁。

Page 10: 05 AXP0132 04前言epaper.gotop.com.tw/PDFSample/AXP013200.pdfxxviii 前言 1990 年時,我在哥倫比亞大學準備開授一門研究所的課程:物件導向設計 與程式實作,自1991

4 簡介

圖 0-1:環形相依的元件

若某個大型系統天生因為具有環形相依性,因而彼此之間傾向緊密耦合難以

分解,那麼對於這種系統的後續維護會是一場噩夢,通常也無法有效地進行

模組化測試。

Page 11: 05 AXP0132 04前言epaper.gotop.com.tw/PDFSample/AXP013200.pdfxxviii 前言 1990 年時,我在哥倫比亞大學準備開授一門研究所的課程:物件導向設計 與程式實作,自1991

0.2 使用 C++ 開發大型軟體專案 5

本例子是一個用於 IC 設計的資料庫的初始設計,在一開始,本例子的原作者並沒有意識到“避免在實體設計中產生環形相依”的重要性,其結果慘烈可期,數百個物件與數千個函式彼此之間互相依賴,竟然找不出方法可以切出

一塊乾淨的模組來進行測試,此系統擁有非常差的可靠度,不好增加功能更

不好維護,而最終將被完全的重寫。

反之,階層式的實體設計(即完全不具環形相依性)相對來說好理解好測試也易於重複使用。

0.2.2 過度的連結時期相依關係 (Excessive Link-Time Dependencies)

若您曾經為了使用某功能而去連結(link)某個函式庫,卻發現您所得到的效益與您連結該函式庫所多花的時間不成比例,那麼您所使用的則是一種“重型元件”而非“輕型元件”。

我們非常容易在物件上頭增加功能,這也是物件設計上的一個好性質,但這

樣的好特性往往誘惑了許多勤奮的軟體工程師將一個經過深思熟慮過的瘦

小物件,變成一隻背負著許多程式碼的大恐龍,而這隻巨獸的絕大部分功

能,竟都沒被它多數的使用者用到。圖 0-2 說明了一個簡單的字串物件為了滿足所有使用者的需求而逐漸變的龐大,每次當這個物件為了某個使用者增

加功能時,無形中對於其餘的使用者來說都有可能會帶來編譯後的檔案大小

增加、程式碼長度增加、執行時間變長與實體層面的相依性增加。

C++ 程式碼通常會有許多多餘的部份,如果沒有仔細設計,編譯後的可執行檔通常會比單純用 C 寫出來的要大。也許是忽視了考慮物件對外的相依關係,許多過於充滿雄心壯志的程式師會設計出與許多程式碼直接或是間接

相依的物件,一個採用上述的複雜字串物件的簡單“Hello World !” 程式,會產生 1.2MB 大小的執行檔!

Page 12: 05 AXP0132 04前言epaper.gotop.com.tw/PDFSample/AXP013200.pdfxxviii 前言 1990 年時,我在哥倫比亞大學準備開授一門研究所的課程:物件導向設計 與程式實作,自1991

6 簡介

圖 0-2:過大的,負擔過重的,不可複用的 String class

Page 13: 05 AXP0132 04前言epaper.gotop.com.tw/PDFSample/AXP013200.pdfxxviii 前言 1990 年時,我在哥倫比亞大學準備開授一門研究所的課程:物件導向設計 與程式實作,自1991

0.2 使用 C++ 開發大型軟體專案 7

如上所述的重型字串物件將不只增加可執行檔大小,連結時間(link time)也會變的太慢,如果連結該字串物件的時間遠超過連結您其餘的子系統,那您

大概會覺得使用這樣的物件很惱人。(譯註:少許的程式修改卻還是得等很久的連結時間,將使您編譯時間增加而開發軟體的速度則降低。)

幸好,有技巧可以消除這些您不想要的連結時期相依性。

0.2.3 過度的編譯時期相依關係 (Excessive Compile-Time Dependencies)

若您開發過具有多個程式碼檔案的 C++ 軟體,那您一定知道改動其中一個表頭檔將可能使許多編譯單元需要重新編譯(譯註:有引入該表頭檔案的檔案),當整個系統還小時,一個小小個改變卻導致重新編譯整個系統,也許對您來說並不會有什麼太大的負擔,當整個系統逐漸變大時,改動這樣的表

頭檔所帶來的效果就不那麼美妙了,不只是編譯整個系統的時間變長,連編

譯其中一個編譯單元的時間都變長,漸漸的,當你需要修改位於這種表頭檔

中的物件時,你會因為編譯時間太長而不想改,這時,你大概正經歷這段所

要介紹的“編譯時期過度相依”。

這種在編譯時期的過度耦合性,對於小型軟體專案來說根本沒有影響,但在

大型專案卻幾乎主宰了整個開發時間,圖 0-3 展示了一個這樣的例子,其設計一開始立意甚美,但漸漸的當系統變大,壞處開始浮現,如圖所示,

myerror 元件定義了一個簡單物件:MyError,該物件非常單純,只不過列舉

(enumeration )出所有可能的錯誤碼,每個新加入本系統的元件,為了能夠容錯,必須引入本表頭檔,不幸的是,這些新的元件也許有會有一些尚未被放

到這個物件中,是該元件自己特有的錯誤碼。

Page 14: 05 AXP0132 04前言epaper.gotop.com.tw/PDFSample/AXP013200.pdfxxviii 前言 1990 年時,我在哥倫比亞大學準備開授一門研究所的課程:物件導向設計 與程式實作,自1991

8 簡介

// myerror.h #ifndef INCLUDED_MYERROR #define INCLUDED_MYERROR struct MyError { enum Codes { SUCCESS = 0, WARNING, ERROR, IO_ERROR, // ... READ_ERROR, WRITE_ERROR, // ... // ... BAD_STRING, BAD_FILENAME, // ... // ... CANNOT_CONNECT_TO_WORK_PHONE, CANNOT_CONNECT_TO_HOME_PHONE, // ... // ... MARTIANS_HAVE_LANDED, // ... }; }; #endif

圖 0-3:潛伏的編譯時期耦合的原始程式碼

當元件逐漸變多時,把新的錯誤碼加入到這個物件的意願我想會降低,我們

會為了盡量不要改動 myerror.h 而傾向使用既有但卻不是非常適當的錯誤

碼。最後,我們不加思索的一律不增加新的錯誤碼於 myerror.h而使用傳回

如 ERROR或 WARNING等最低階的錯誤碼。等到這天來臨時,這個原本立意甚

美的設計已經瀕臨無法維護而且無法使用的地步。

Page 15: 05 AXP0132 04前言epaper.gotop.com.tw/PDFSample/AXP013200.pdfxxviii 前言 1990 年時,我在哥倫比亞大學準備開授一門研究所的課程:物件導向設計 與程式實作,自1991

0.2 使用 C++ 開發大型軟體專案 9

還有許多因素會造成這種不好的編譯時期相依性,大型的 C++ 程式會比同等的

C程式有更多的表頭檔,表頭檔引入非必要的表頭檔則是一種常見的因素。如圖

0-4 例中所示,許多表頭檔其實不必在 simulator.h 這個表頭檔被引入,而在此之所以會引入這麼多不必要的表頭檔也僅僅是因為使用 Simulator 這個物件的使

用者覺得這樣引入方便(譯註:仰賴別人幫你引入只有你會用到的表頭檔,自己的程式只需引入這一個檔案即可,多麼乾淨!)。這樣做會迫使每一個用到這個表頭檔的使用者與該表頭檔引入的元件在編譯時期產生相依性,就算沒有實際用

到這些多餘的元件亦然。過度的引入非必要檔案其實對於編譯其使用者元件來說

不會增加多餘時間,但是在底層表頭檔被小幅修改時,該元件被重新編譯的機率

則會大幅增加(譯註:任一個多餘引入的表頭檔有變動時,所有的使用者都必須重新編譯)。

若不去降低編譯時期的相依性,則每個編譯單元幾乎都引入了整個系統的表

頭檔,這樣會導致整個編譯時間變的非常緩慢,在明導科技實際上真有這種

大型的 C++ 軟體專案(約有數千人年,譯註:一名普通素質的軟體工程師對於該專案努力工作一年則稱為一個人年),該專案是一個用於晶片設計的底層軟體平台,它的開發者一開始對於編譯時期的相依性對專案帶來的傷害毫

無概念,我們當時就算使用許多工作站同時來編譯這樣的系統,我們都花上

以週為單位的時間!

如圖 0-4 所見,此問題的根源在於 simulatior 元件的組織架構,雖然有許多旁門左道的方法可以修飾,但最正確的解法是消除非必要的編譯時期相依關

係。

Page 16: 05 AXP0132 04前言epaper.gotop.com.tw/PDFSample/AXP013200.pdfxxviii 前言 1990 年時,我在哥倫比亞大學準備開授一門研究所的課程:物件導向設計 與程式實作,自1991

10 簡介

// simulator.h #ifndef INCLUDED_SIMULATOR #define INCLUDED_SIMULATOR #include "cadtool.h" // required by "IsA" relationship #include "myerror.h" // bad idea (see Section 6.9) #include "circuitregistry.h" // unnecessary compile-time dependency #include "inputtable.h" // unnecessary compile-time dependency #include "circuit.h" // required by "HasA" relationship #include "rectangle.h" // unnecessary compile-time dependency // ... #include <iostream.h> // unnecessary compile-time dependency class Simulator : public CadTool { // mandatory compile-time dependency CircuitRegistry *d_circuitRegistry_p; InputTable& d_inputTable; Circuit d_currentCircuit; // mandatory compile-time dependency // ... private: Simulator(const Simulator &); Simulator& operator=(const Simulator&); public: Simulator(); ~Simulator(); // ... MyError::Code readInput(istream& in, const char *name); MyError::Code writeOutput(ostream& out, const char *name); MyError::Code addCircuit(const Circuit& circuit); MyError::Code simulate(const char *outputName, const char *inputName, const char *circuitName); Rectangle window(const char *circuitName) const; // ... }; #endif

圖 0-4:不必要的編譯時期的相依關係

如同之前所述關於消除連結時期相依性一樣,的確有招式可以消除非必要的

編譯時期相依關係。

0.2.4 全域名稱空間 (The Global Name Space)

若您曾參與過一個擁有數名開發者的軟體專案,那麼您一定能認同軟體整合

是一個充滿“不愉快的驚奇”的過程,換句話說,漸增的全域物件帶來許多問題,最明顯的例子是這些物件彼此之間可能“撞名”,結果是,各個獨立開發