本書 Rational Unified Process - Ada Programming Guidelines 是一本範本,可用來引申出您公司自己的程式碼撰寫標準。它指定 Ada 程式必須如何撰寫。它的適用讀者是所有使用 Ada 作為實作語言,或是作為設計語言(例如用於指定介面或資料結構)的應用軟體設計師和開發人員。
本書中所述的規則涵蓋了程式碼撰寫的大部分層面。一般規則適用於程式佈置、 命名慣例、註解的用法。特定規則適用於所選的 Ada 特性,以及指定禁止的建構、 建議的使用型樣,以及用於提升程式品質的一般提示。
專案設計準則與目前的程式設計準則有某種程度的重疊,這是故意的。介紹了許多程式碼撰寫規則(特別是在命名慣例的範圍內),以積極地對軟體設計支援及加強物件導向方法。
這些準則原先係針對 Ada 83 而撰寫。它們包括與 Ada 95 相容的規則,但是對於在已修訂的語言標準中所介紹的語言之新增特性(如標示類型、 子單元或十進位類型)的用法上,並沒有特定的準則。
本書的編排並沒有嚴格地遵循 Ada Reference Manual [ISO 8052] 的結構。
第 2 章、簡介,解說準則所依據的基本原則,並介紹準則的分類。
第 3 章、程式碼佈置,論及程式之本文的一般視覺化編排。
第 4 章、註解,提供有關如何以結構化、有用以及可維護的方式,利用註解來註記程式碼的指引。
第 5 章、命名慣例,提供一些關於語言實體的命名以及範例的一般規則。必須調整本章來合乎您的特定專案或組織的需求。
第 6 章宣告及第 7 章表示式與陳述式提供有關每一種類型的語言建構的進一步忠告。
第 8 章有效範圍問題 及第 9 章程式結構和編譯問題提供有關程式之廣域結構化和編排的指引。
第 10 章、並行,論及使用語言的作業和時間相關特性的專用主題。
第 11 章、錯誤處理與異常狀況提供一些有關如何以有系統的以及輕型的方式,使用或不使用異常狀況來處理錯誤。
第 12 章、低階程式設計,處理呈現子句的問題。
第 13 章、摘要,扼要重述最重要的準則。
本書取代 Ada Guidelines: Recommendations for Designers and programmers, Application Note #15, Rational, Santa Clara, CA., 1990。
Ada 是明確為了支援高品質、可信賴、可重複使用及可攜的軟體之開發而設計 [ISO 87, sect. 1.3]。不過,沒有任何程式設計語言憑自己就能夠確定達到了這個目標。程式設計必須以規範極佳的流程來完成。
這裡所提供的大部分準則的首要目標是明確而易於瞭解的 Ada 程式碼。這是構成軟體的可靠性和可維護性的主要因素。明確又易於瞭解的程式碼的意義,可以在下列三個基本原則中得知。
在程式碼的生命期限期間,閱讀它的頻率高於撰寫它的頻率,特別是規格。在理想上,程式碼讀起來應該像是英語的說明,指出它做了什麼,以及它執行的附加好處。為人撰寫的程式比為機器撰寫的程式還多。判讀程式碼是一項繁複的思考過程,可受一致性所支持,這在本指引中又稱為降低判讀意外原則。在整個專案中的一致風格,是軟體開發人員小組對程式設計標準取得一致意見的主因,它不應被認知為某種懲罰或是創造力或生產力的阻礙。
本指引的另一個重要的基礎原則是單一維護點原則。應盡可能在 Ada 程式碼中的一個點上表達一個設計決策,同時應該依程式的方式,從這一點衍生其大部分的結果。違反這個原則會非常危及可維護性和可靠性,以及可瞭解性。
最後,套用了最少的視覺干擾原則,這是易讀性的主要構成要素。也就是說,已努力避免用視覺上的「干擾」雜陳於程式碼中: 含有極少的資訊內容或是對於瞭解軟體的用途無甚用處之資訊的列、框以及其他文字。
可攜性及可復用性也是許多準則的原因。程式碼將必須移轉至不同目標電腦的不同編譯器,而最終會移轉至更進階的 Ada 版本,稱為 "Ada 95" [PLO92, TAY92]。
這裡呈現的準則做了少量的基本假設:
只要有助益,就鼓勵使用進階的 Ada 特性,而不是基於某些程式設計師不熟悉它們而勸阻使用。這是專案唯一可以真正受益於使用 Ada 的唯一方法。您不應將 Ada 當作是 Pascal 或 FORTRAN 來使用。奉勸您不要用註解來分程式碼的段落;相反的,只要可行,都應使用 Ada 代替註解。
許多命名慣例都是依據英文,不管是詞彙和語法皆然。此外,Ada 關鍵字都是常用的英文單字,將它們與另一種語言混用會使易讀性降級。
命名慣例及幾個其他的規則假設未使用 "use" 子句。
許多規則在大型的 Ada 系統中提供大部分的價值,雖然它們也可以用於小型系統中,但是只限於為了在專案或共同層次的作法和一致性。
在使用 Rational 環境時,Ada 編輯器及格式製作程式會處理諸如程式碼佈置、 結尾建構中的識別字等問題。不過,本書內含的佈置建議事項可以套用在任何開發平台上。
許多規則將會支援從物件導向 (OO) 概念到 Ada 特性及特定命名慣例的有系統對映。
這些準則的重要性不一。它們約略遵循下列級別:
本準則是一段簡單的忠告;沒有遵循它並不會真正有什麼害處,可以按品味加以取捨。本書中用上述符號標示提示。
本準則通常依據較為技術上的領域;在某些實作中,可能會影響可攜性或可復用性,以及效能。建議事項必須要遵循,除非有什麼充分的理由不這麼做。本文件會提及一些異常狀況。在文件中是用以上所示的符號標示建議事項。
使用有問題的特性非常危險,但不是完全禁止;它的使用決策應屬專案層決策,並且應使該決策經常出現。在文件中是用上面所呈現的符號標示限制。
違規將會明確地導致不當的、無法信賴的或不可攜的程式碼。不能違反要求。在本文件中是用上面的指向手把標示要求。
將會使用 Rational Design Facility 來標示受限特性的使用 以及強制實施必要的規則及許多建議事項。
與許多其他的 Ada 程式碼撰寫標準相反,在這些準則中被完全禁止的 Ada 特性實際上是非常少的。好軟體的關鍵在於:
善用常識。
當您找不到規則或準則時、當規則顯然不適用時、當其他作法都不管用時:請善用常識,以及檢查基本原則。這個規則超越其他的所有規則。常識乃是必要的。
程式單元的佈置完全受「Rational 環境格式製作程式」的控制,除了註解和空格以外,程式設計師不需要過於憂慮程式的佈置。Reference Manual for the Ada Programming Language [ISO87] 的附錄 E 中陳述了這套工具所採用的格式化慣例。它們特別建議開頭及結尾為結構化建構的關鍵字要垂直對齊。同時建構的識別字也在建構的結尾處有系統地重複。
格式製作程式的確切行為受一系列的程式庫 switch 所控制,其依據一般模型區,接收在整個專案中一致的值集。以下列出相關的 switch,以及建議其在模型區中所使用的現行值。
以 Ada 單位指定識別字的大小寫: 最先的字母以及底線後每一個第一個字母都用大寫。大寫格式被視為在使用最先進的螢幕和雷射印表機字型下,用肉眼最容易辨識的格式。
指定 Ada 關鍵字的大小寫。這可以將它們與識別字稍作區分。
以浮點文字指定字母 "E" 的大小寫,並以基底文字指定基底數字("A" 到 "F")。
Ada 單位是根據 Ada Reference Manual [ISO87] 的附錄 E 中陳述的一般慣例來格式化。這表示開頭及結尾為結構化建構的關鍵字已對齊。例如,"loop" 與 "end loop",以及 "record" 與 "end record"。在結構化建構內部的元素會縮排到右邊。
指定格式製作程式將結構化(主要)建構(如 "if" 陳述式、"case" 陳述式及 "loop" 陳述式)縮排的欄數。
指定格式製作程式將次要建構縮排的欄數: 記錄宣告、可變記錄宣告、類型宣告、 異常狀況處理常式、替代方案、case 陳述式,以及已命名及已標示的陳述式。
指定格式製作程式在折返行之前,用來列印行的欄數(採用 Ada 單位)。這可讓您使用傳統的 VT100 式終端機來顯示格式化的單位。
指定當陳述式由於其長度超過 Line_Length 而必須中斷時,格式製作程式縮排陳述式的第二行及後續行的欄數。格式製作程式縮排 Statement_Indentation 欄數的前提是: 沒有詞彙建構可用來對齊縮排的程式碼。
指定在每一行上保留供顯示陳述式的欄數。如果現行的縮排層次容許一行上的直欄少於 Statement_Length,則格式製作程式會從 Wrap_Indentation 直欄重新開始作為它新的縮排層次。這種作法可以防止巢狀結構較深的陳述式列印到超過右邊距。
當現行的縮排層次不容許 Statement_Length 時,指定格式製作程式開始下一個縮排層次的直欄。這種作法可以防止巢狀結構較深的陳述式列印到超過右邊距。
控制格式 (xxx:aaa; yyy:bbb) 的清單格式化,其出現在子程式的形式部分中,並作為類型宣告中的區別元件。它也控制格式 (xxx=>aaa, yyy=>bbb) 的清單格式化,其出現在子程式的呼叫和聚集中。由於這個選項非零 (True),因此當某清單無法合於一行時,該清單的每個元素都會在新的一行上開始。
指定格式製作程式可以插入的空格數,以對齊連續陳述式中的詞彙建構,如具名表示法中的冒號、指派和箭頭。如果會需要超過這個數字的空格以對齊建構,則建構會保留為不對齊。
請注意,為了施行某版面配置,程式設計師可以輸入 <space>
<space> <carriage-return> 來插入行尾或換行,這些不會被格式製作程式所移除。
利用這種方法時,以及為了增進易讀性和可維護性,當 Ada 元素的清單超過 3 個項目,而且它們無法合於一行時,應將這份清單拆開,讓每行僅包含一個元素。這特別適用於下列的 Ada 建構(如 Ada
Reference Manual [ISO87] 的附錄 E 中所定義):
引數關聯
pragma Suppress (Range_Check, On => This_Type, On => That_Type, On => That_Other_Type);
識別字清單、元件清單
Next_Position, Previous_Position, Current_Position : Position; type Some_Record is record A_Component, B_Component, C_Component : Component_Type; end record;
列舉類型定義
type Navaid is (Vor, Vor_Dme, Dme, Tacan, VorTac, NDB);
區別限制
subtype Constrained is Element (Name_Length => Name'Length, Valid => True, Operation => Skip);
陳述式的順序(由格式製作程式執行)
形式參數部分、同屬形式參數部分、實際參數部分、 同屬實際參數部分
procedure Just_Do_It (This : in Some_Type; For_That : in Some Other_Type; Status : out Status_Type); Just_Do_It (This => This_Value; For_That => That_Value; Status => The_Status);
好程式並不是以註解的數量見長,而是以它們的品質見長,這與普遍抱持的理念相反。
註解應用來補充 Ada 程式碼,而不可改寫它。Ada 本身是一種非常容易判讀的程式設計語言,在有很好的命名慣例支援下更是如此。註解應解說不明確的事項來補充 Ada 程式碼;它們不應重複 Ada 語法或語意。註解應幫助讀者理解背景概念、相依關係,尤其是繁複的資料編碼或演算法。註解應強調從程式碼撰寫或設計標準衍生、使用受限的特性,以及特別的「手法」。針對每一個主要的 Ada 建構(如子程式和套件)有系統出現的註解頁框或表單,具有一致性以及提醒程式設計師記載程式碼的優點,但是它們往往導致段落化的風格。針對每一個註解,程式設計師應能夠好好回答下列問題: 「這個註解有什麼加值效果?」。
會誤導人的或錯誤的註解比完全沒有註解還要糟糕。編譯器並不會檢查註解(除非它們參與一些正式的 Ada Design Language (ADL) 或 Program Design Language (PDL),如同 Rational Design Facility)。因此,按照單一維護點的原則,就算得多用幾個宣告,設計決策仍應在 Ada 中而非註解中表達。
作為一個範例(並不是那麼好),請考量下列宣告:
------------------------------------------------------------ -- procedure Create ------------------------------------------------------------ -- procedure Create (The_Subscriber: in out Subscriber.Handle; With_Name : in out Subscriber.Name); -- -- Purpose: This procedure creates a subscriber with a given -- name. -- -- Parameters: - The_Subscriber :mode in out, type Subscriber.Handle - It is the handle to the created subscriber - With_Name :mode in, type Subscriber.Name - The name of the subscriber to be created. - The syntax of the name is -- <letter> { <letter> | <digit> } -- Exceptions: -- Subscriber.Collection_Overflow when there is no more -- space to create a new subscriber -- Subscriber.Invalid_Name when the name is blank or -- malformed -- -------------------------------------------- end Create ----
本範例有幾個點可以提出來。
在此情況下,下列更為簡潔及有用的版本會比較好:
procedure Create (The_Subscriber : in out Subscriber.Handle; With_Name : in Subscriber.Name);-- --Raises Subscriber.Collection_Overflow. --Raises Subscriber.Invalid_Name when the name is --blank or malformed (see syntax description --attached to declaration of type Subscriber.Name).
註解應以相同的縮排放在與它們相關聯的程式碼附近、
並附加到該程式碼,也就是說,在視覺上,空白註解行將註解建構連結到 Ada 構造:
procedure First_One; -- -- This comment relates to First_One. -- But this comment is for Second_One. -- procedure Second_One (Times : Natural);
使用空白行來分隔相關的程式碼建構(註解和程式碼),而不要用太長的註解行。
在單一註解建構內使用空註解(而非空行)來分隔段落:
-- 這裡的一些說明必須延續到 -- 後續的段落。-- -- 上面的空註解行可讓人清楚看見 -- 我們處理的是單一註解建構。
雖然您可以將註解放在與它們相關的 Ada 構造的上方或下方,但是請將適用於數個 Ada 構造的註解(如區段標題或主要資訊)放在構造的上方。將本身為備註或相關資訊的註解放在它們適用的 Ada 構造下方。
利用整頁的寬度,將註解集中在 Ada 構造的開頭。請避免將註解放在與 Ada 構造同一行上。它們經常會變成沒有對齊。不過,在長宣告中的每一個元素之說明中,還是可以容許這類的註解,例如列舉類型文字。
使用一個小的標準註解建構階層來放置區段標題,但是必須是在非常大的 Ada 單元中(>200 個宣告或陳述式):
--=========================================================== -- -- 主要標題的位置 -- --=========================================================== ------------------------------------------------------------- -- 次要標題的位置 ------------------------------------------------------------- -- -------------------- -- 子區段標頭 -- --------------------
在這類的標題註解上方放比下方更多的空白行,例如,之前放兩行,之後放一行。在視覺上,這會將標題與後面的文字結合。
避免使用含有諸如以下資訊的標頭:
作者、電話號碼、建立和修改日期以及單元的位置(或檔名),因為這項資訊很快就過時。將擁有權的版權聲明放在裝置的結尾,特別是在使用 Rational 環境時。在存取套件規格的程式檔時(例如,藉由按 Rational 環境上的 [定義]),使用者並不想捲動對於瞭解程式毫無用處的兩頁或三頁的文字,和(或)未載有任何程式資訊的文字,例如版權聲明。請避免使用垂直列或是封閉的頁框或方框,它們只會使畫面更加雜亂,而且難以保持一致。請使用 Rational
CMVC 附註(或是某些其他格式的軟體開發檔案)來保存裝置歷程。
請勿抄寫通常會在他處出現的資訊;提供指向該資訊的指標。
盡可能使用 Ada,而不使用註解。要達到這個目的,您可以使用較好的名稱、額外的暫存變數、資格、重新命名、子類型、
靜態表示式,以及屬性,這些全部都不會影響產生的程式碼(至少好的編譯器是如此)。您也可以使用小的列入述詞函數,並將程式碼分成數個無參數的程序,
其名稱提供程式碼的數個個別區段的標題。
範例:
替換:
exit when Su.Locate (Ch, Str) /= 0; -- Exit search loop when found it.
Search_Loop : loop
Found_It := Su.Locate (Ch, Str) /= 0;
exit Search_Loop when Found_It
end Search_Loop;
替換:
if Value < 'A' or else Value > 'Z' then -- If not in uppercase letters.
subtype Uppercase_Letters is Character range 'A' .. 'Z'; if Value not in Uppercase_Letters then ...
替換:
X := Green; -- This is the Green from -- Status, not from Color. raise Fatal_Error; -- From package Outer_Scope. delay 384.0; -- Equal to 6 minutes and 24 -- seconds.
The_Status := Green;
X := Status'(Green); raise Outer_Scope.Fatal_Error; delay 6.0 * Minute + 24.0 * Second;
替換:
if Is_Valid (The_Table (Index).Descriptor(Rank).all) then -- This is the current value for the iteration; if it is -- valid we append to the list it contains. Append (Item, To_List => The_Table (Index).Descriptor(Rank).Ptr);|
declare Current_Rank : Lists.List renames The_Table (Index).Descriptor (Rank); begin if Is_Valid (Current_Rank.all) then Append (Item, To_List => Current_Rank.Ptr); end if; end;
注意註解中的風格、
語法和拼字。請勿使用過於簡潔的、
加密的風格。使用拼字檢查程式(在 Rational 環境上呼叫 Speller.Check_Image)。
請勿使用加重音字母以及其他非英文字元。在某些開發系統上可能支援使用非英文字元,而根據 Ada Issue AI-339,某些 Ada 編譯器上僅限於支援在註解中使用非英文字元。但這是無法轉移的,而且它在其他的系統上很可能會失效。
針對子程式,至少記載下列項目:
針對類型和物件,記載任何無法用 Ada 表示的不變量或其他限制。
避免在註解中重述。例如,用途區段應該是對於問題「這做什麼用?」而不是「它的執行方式是什麼?」的簡要回答。概觀應為設計的簡要呈現。說明不應說明所用的演算法,而應解說要如何使用套件。
Data_Structure 和演算法區段應包含可幫助瞭解主要實作策略的足夠資訊(以便能適當使用套件),但是不須提供所有的實作詳細資料,或是與適當使用這個套件不相關的資訊。
選擇好的名稱來指定 Ada 實體(程式單位、類型、子類型、 物件、文字、異常狀況),是在所有的軟體應用程式中要處理的最棘手課題之一。在中型到大型的應用程式中,又出現另一個問題:名稱衝突,或者更確切地說,是難以找到足夠的同義字來指定不同但類似的想法,這些想法是關於相同的實際概念(或是為類型、 子類型、物件、參數命名)。在這裡可以利用不使用 "use" 子句(或是在高度受限的狀況中)的規則。在許多情況下,這將允許縮短名稱以及重複使用相同的敘述性用字,而沒有混淆的風險。
選擇明確、易辨認、有意義的名稱。
有別於許多其他的程式設計語言,Ada 並沒有將 ID 的長度限制為 6、8 或 15 個字元。為了加速打字而調整為使用短名稱並不恰當。選擇單字母 ID 往往表示選擇極糟或是只求輕省。可能少許的例外,例如使用 E 作為自然對數 Pi 的基底,或是其他一些經詳細辨認的情況。
用底線來分隔名稱的不同單字:
Is_Name_Valid
而不用 IsNameValid
使用完整名稱而非縮寫。
僅使用經專案核准的縮寫
如果使用縮寫,則它們不是在應用程式領域中非常常見(例如,FFT 代表 Fast Fourier Transform),要不就是應該將它們從專案層的獲認可縮寫清單中移除。否則,就非常有可能到處都出現類似但又不是完全相同的縮寫,造成稍後的混淆和錯誤(例如,Track_Identification 縮寫成 Tr_Id、Trck_Id、Tr_Iden、Trid、Tid、Tr_Ident 等等)。
謹慎使用指出 Ada 建構種類的字尾。它們不會增進易讀性。
就判讀及瞭解程式碼而言,在後面冠上 Ada 實體的種類(例如,_Package 代表套件、_Error 代表異常狀況、_Type 代表類型,以及 _Param 代表子程式參數)通常並不是非常有效。甚至像 _Array、_Record 和 _Function 這樣的字尾會更糟糕。Ada 編譯器及讀者可以用背景來區別異常狀況與子程式: 很明顯的,只有異常狀況名稱可能會出現在 raise 陳述式或異常狀況處理常式中。在下列受限的情況下,這類的字尾非常有用:
字尾為 _Constrained 的一般正式類型
字尾為 _Pointer 的存取類型,或是其他形式的間接參照: 字尾為 _Handle 或 _Reference
隱藏可能被 _Or_Wait 暫停執行的進入呼叫之子程式
表達名稱,讓它們從使用的觀點看來是不錯的名稱。
請試著考量匯出的實體將在其中使用的環境,然後從該觀點選擇名稱。實體宣告一次,然後使用許多次。對於子程式的名稱以及它們的參數而言更是如此: 使用具名關聯產生的呼叫應盡可能接近自然語言。請記住,如果沒有 use 子句,會強制使用大部分已宣告實體的完整名稱。必須為同屬形式參數找到比較好的折衷辦法,因為同屬形式參數在同屬單元中使用的次數比在其用戶端中還多,但是對於子程式形式參數而言,肯定會優先採用在用戶端上較好的外觀。
使用英文字,並正確拼寫它們。
混雜語言(例如,法文和英文)會使程式碼難以閱讀,且有時會為 ID 的意義帶來模稜兩可。由於 Ada 關鍵字原本已採用英文,因此需要用英文字。最好是採用美式英文拼字,以便能夠在 Rational 環境上使用內建拼字檢查程式。
請勿重新定義 Standard 套件中的任何實體。這是絕對禁止的。
這麼做會導致混淆及戲劇性的錯誤。這項規則可以延伸到其他預先定義的程式庫單位:Calendar、System。同時這包括識別字 Standard 本身。
避免從其他預先定義的套件(如 System 或 Calendar)重新定義識別字。
請勿用來作為識別字:Wide_Character 和 Wide_String,這些將在 Ada 95 中的 Standard 套件中引入。請勿引入名為 Ada 的編譯單元。
請勿使用下列單字作為識別字:abstract、aliased、protected、requeue、
tagged 和 until,這些將在 Ada 95 中變成關鍵字。
以下為不同 Ada 實體的一些命名建議。假設了通常為「物件型」的設計風格。請參閱附錄 A 以取得進一步的解說。
當套件引入一些物件類別時,請賦予它物件類別的名稱,此名稱通常是採用單數格式的常用名詞,必要時在後面冠上 _Generic(也就是說,如果已定義參數化類別的話)。只有在物件固定以群組的方式進來時才使用複數格式。例如:
package Text is package Line is package Mailbox is package Message is package Attributes is package Subscriber is package List_Generic is
當某套件指定介面或功能的一些分組,並且與物件不相關時,請在名稱中表達這種情況:
package Low_Layer_Interface is package Math_Definitions is
當必須以數個套件表達「邏輯」套件時(使用平面分解),請使用取自在專案層同意之清單的字尾。例如,邏輯套件 Mailbox 可以實作為:
package Mailbox_Definitions is package Mailbox_Exceptions is package Mailbox_Io is package Mailbox_Utilities is package Mailbox_Implementation is package Mailbox_Main is
其他可接受的字尾有:
_Test_Support _Test_Main _Log _Hidden_Definitions _Maintenance _Debug
在定義物件類別的套件中,當隱含複製語意時(亦即,當類型可以實例化,而且某種形式的指派可行時),請使用:
type Object is ...
。請注意,類別的名稱不應在識別字中重複,因為會固定用其完整格式來使用它:
Mailbox.Object Line.Object
當隱含共用語意時,也就是說,類型是使用存取值(或是其他的某種間接形式)來實作,而指派(若有的話)不會複製物件;請使用下列各項來指出這個事實:
類型 Handle
用於參照類型 Reference
作為可能替代
這些元素在單獨使用時被用來作為字尾,若是前面冠上套件名稱,則會不明確或模稜兩可。
當隱含多個物件時,請使用下列其中一項:
type Set
type List
type Collection
type Iterator
針對物件的一些字串指定,使用:
type Name
在定義套件的整個過程中,也應使用類型的完整名稱,以取得較佳的易讀性。在 Rational 環境中,這也會導致在子程式呼叫上使用 [Complete] 函數時有較好的行為。
例如,請注意以下的完整名稱 Subscriber.Object:
package Subscriber is type Object is private; type Handle is access Subscriber.Object; subtype Name is String; package List is new List_Generic (Subscriber.Handle); Master_List : Subscriber.List.Handle; procedure Create (The_Handle : out Subscriber.Handle; With_Name : in Subscriber.Name); procedure Append (The_Subscriber : in Subscriber.Handle; To_List : in out Subscriber.List.Handle); function Name_Of (The_Subscriber : Subscriber.Handle) return Subscriber.Name; ... private type Object is record The_Name : Subscriber.Name (1..20); ... end Subscriber;
在其他情況下,請使用名詞或限定元+名詞作為類型的名稱。您可以對類型使用複數格式,而留單數格式給物件(變數):
type Point is record ... type Hidden_Attributes is ( ... type Boxes is array ...
如果是列舉類型,請單獨使用 Mode、Kind、Code 等等或作為字尾。
如果是陣列類型,當已經對元件類型使用簡稱時,可以使用 suffix _Table。只有在使用隱含的語意維護陣列時,才使用像是 _Set 和 _List 等名稱或字尾。保留 _Vector 和 _Matrix 作為相對應的數學概念。
由於將會避免單數作業物件(基於稍後解說的原因),因此應引入作業類型,即使只有一個該類型的物件。這是在滿足了單純的字尾策略(如 _Type)的情況:
task type Listener_Type is ... for Listener_Type'Storage_Size use ... Listener : Listener_Type;
同樣地,當使用名詞(或名詞短語)作為類型的名稱之間存在衝突,或是物件或參數之名稱的數個地方中有衝突,則針對類型將該名詞的字尾設為 _Kind,並保留簡單的名詞給物件:
type Status_Kind is (None, Normal, Urgent, Red); Status : Status_Kind := None;
或者,如果到達時永遠是多個的事項,請對類型使用複數表單。
由於存取類型原本就有危險,因此應讓使用者注意到它們。一般而言,它們被稱為「指標」。如果名稱本身語意不明,請使用 suffix _pointer。也可以使用 _Access 作為替代。;
使用巢狀子套件來引入次要抽象化有時可以簡化命名:
package Subscriber is ... package Status is type Kind is (Ok, Deleted, Incomplete, Suspended, Privileged); function Set (The_Status : Subscriber.Status.Kind; To_Subscriber : Subscriber.Handle); end Status; ...
由於異常狀況必須只能用來處理錯誤狀況,因此請使用能明確傳達負面想法的名詞或名詞短語:
Overflow、Threshold_Exceeded、Bad_Initial_Value
當定義於類別套件時,讓識別字包含類別的名稱(例如,Bad_Initial_Subscriber_Value)是毫無用處的,因為異常狀況將一律用來作為 Subscriber.Bad_Initial_Value。
使用 Bad、Incomplete、Invalid、Wrong、Missing 或 Illegal 之一作為名稱的一部分,而非照計劃使用 Error,其並沒有傳達特定的資訊:
Illegal_Data、Incomplete_Data
使用動詞代表程序(以及作業項目)。對於函數,請使用具有物件類別之屬性或特性的名詞。對於傳回 Boolean(述詞)的函數,請使用形容詞(或過去分詞)。s
Subscriber.Create Subscriber.Destroy Subscriber.List.Append Subscriber.First_Name -- 傳回字串。Subscriber.Creation_Date -- 傳回日期。Subscriber.List.Next Subscriber.Deleted -- 傳回 Boolean。Subscriber.Unavailable -- 傳回 Boolean。Subscriber.Remote
以述詞而言,在某些情況下在名詞前面冠上 Is_ 或 Has_ 可能非常有用;在時態方面請保持精確和一致:
function Has_First_Name ... function Is_Administrator ... function Is_First... function Was_Deleted ...
這在已經使用簡稱作為類型名稱或列舉文字時非常有用。
採正面的形式使用述詞,亦即,它們不應包含「不」。
如果是一般作業,請保持一致地使用從所選的專案清單中取出的動詞(當我們更瞭解系統時,清單會隨之擴充):
Create Delete Destroy Initialize Append Revert Commit Show, Display
使用正面名稱代表述詞函數和 Boolean 參數。使用負面名稱可能會造成雙重負面(例如,Not Is_Not_Found),並且可能使程式碼較難以判讀。
function Is_Not_Valid (...) return Boolean procedure Find_Client (With_The_Name : in Name; Not_Found : out Boolean)
應定義為:
function Is_Valid (...) return Boolean; procedure Find_Client (With_The_Name: in Name; Found: out Boolean)
其可讓用戶端視需要使它們的表示式無效(這麼做不會損失執行時間):
if not Is_Valid (...) then ....
在某些情況下,也可以藉由使用反義字(例如 "Is_Invalid" 而非 "Is_Not_Valid")來使負面述詞成為正面述詞,而不會改變其語意。不過,正面名稱比較容易判讀: "Is_Valid" 會比 "not Is_Invalid." 來得容易理解。
當隱含相同的一般意義時,請使用相同的單字,而非嘗試尋找同義字或變式。因此,在保持最少意外的原則下,鼓勵使用超載來增進一致性。
如果使用子程式作為進入呼叫的「外觀」或「封套」,則在動詞後面加上字尾 _Or_Wait,或是在名詞後面跟著 Wait_For_ 之類的詞組,
以名稱來反映這個事實會非常有用:
Subscriber.Get_Reply_Or_Wait Subscriber.Wait_For_Reply
某些作業應固定使用相同的名稱保持一致地定義:
對字串來回的類型轉換,為對稱函數:
function Image and function Value
對某些低階表示法來回的類型轉換(如 Byte_String 代表資料交換):
procedure Read and Write
對於已配置的資料:
function Allocate(而非 Create) function Destroy(或是 Release,以表示物件將會消失)
當有系統地使用一致的命名完成這項作業時,類型組合就輕易得多了。
如果是作用中的反覆元,則一律必須定義下列基本元素:
Initialize Next Is_Done Value_Of Reset. 如果在同一個範圍內引進了數個反覆元類型,則應超載這些基本元素,而不是對每一個反覆元引進不同的識別字集。Cf. [BOO87]。
在使用
Ada 預先定義屬性作為函數名稱時,請確定它們與相同的一般語意一起使用:'First、'Last、'Length、'Image、'Value 等等。請注意,數個屬性(例如,'Range 和 'Delta)不能用來作為函數名稱,因為它們是保留字。
如果要指出獨特性,或是顯示這個實體是動作的主要焦點,請在物件或參數名稱前面冠上 The_ 或 This_。如果要指出次要、暫時、輔助的物件,請在它前面冠上 A_ 或 Current_:
procedure Change_Name (The_Subscriber : in Subscriber.Handle; The_Name : in Subscriber.Name ); declare A_Subscriber : Subscriber.Handle := Subscriber.First; begin ... A_Subscriber := Subscriber.Next (The_Subscriber); end;
如果是 Boolean 物件,請使用具有正面格式的述語子句:
Found_It Is_Available
務必要避免 Is_Not_Available
。
如果是作業物件,請使用意味著作用中實體的名詞或名詞短語:
Listener Resource_Manager Terminal_Driver
如果是參數,在類別名稱或某些特性前面冠上前置詞也會增加易讀性,特別是在使用具名關聯時的呼叫方這一端。輔助參數的其他有用字首具有 Using_ 的格式,而在受到一些次要效果所影響的 in out 參數的案例中,Modifying_:
procedure Update (The_List : in out Subscriber.List.Handle; With_Id : in Subscriber.Identification; On_Structure : in out Structure; For_Value : in Value); procedure Change (The_Object : in out Object; Using_Object : in Object);
站在呼叫方的觀點,定義參數的順序也非常重要:
這可讓您利用預設值,而無需對主要參數使用具名關聯。
必須明確指出模式
"in"(即使是在函數中)。
挑選一個您會用於非同屬版本的最佳名稱:
類別名稱代表套件,或是及物動詞(或動詞片語)代表程序(請參閱上述),並在它後面冠上 _Generic。
如果是同屬形式類型,當同屬套件定義某抽象資料結構時,請使用
Item
或 Element
代表同屬形式,並使用 Structure
或是其他更貼切的名詞代表已匯出的抽象化。
如果是被動反覆元,請在識別字中使用
Apply
、Scan
、Traverse
、Process
或 Iterate
:
generic with procedure Act (Upon : in out Element); procedure Iterate_Generic (Upon : in out Structure);
同屬形式參數的名稱不能為同形異義字。
generic type Foo is private; type Bar is private; with function Image (X : Foo) return String; with function Image (X : Bar) return String; package Some_Generic is ...
應替換為:
generic type Foo is private; type Bar is private; with function Foo_Image (X : Foo) return String; with function Bar_Image (X : Bar) return String; package Some_Generic is ...
必要的話,可以在同屬單元中重新命名同屬形式參數:
function Image (Item : Foo) return String Renames Foo_Image; function Image (Item : Bar) return String Renames Bar_Image;
當大型系統分割成 Rational 子系統(或是另一種形式的交互連接程式庫)時,定義遵循下列準則的命名策略非常有用:
如果某系統包含數百個物件和子物件,則在程式庫單位層很可能會發生一些名稱衝突,程式設計師將會缺少一些非常有用之名稱(像是 Utilities、Support、Definitions)的同義字。
在 Rational 主機上使用瀏覽機能尋找實體定義所在的位置是一項輕易的差事,但是當程式碼殖入目標並使用目標工具(除錯程式、測試工具等等)時,在 1000 子系統中的 2,000 個單位之中尋找程序 Utilities.Get 的位置,對於專案的新進人員而言可能是相當大的挑戰。
在程式庫層單位名稱前面冠上子系統(包含該單位)的四字母縮寫。
您可以在 Software Architecture Document (SAD) 中找到子系統清單。請將高度可重複使用元件(這些元件很可能會在眾多的專案、COTS 產品及標準單位之間重複使用)的程式庫排除在這項規則之外。
範例:
Comm 通訊
Dbms 資料庫管理
Disp 顯示器
Math 數學套件
Drive 驅動程式
例如,從子系統 Disp 匯出的所有程式庫單位前面都會冠上 Disp_,可讓負責 Disp 的團隊或公司除此之外都能完全自由命名。如果 DBMS 和 Disp 都需要引入名為 Subscriber 的物件類別,這會導致如下的套件:
Disp_Subscriber Disp_Subscriber_Utilities Disp_Subscriber_Defs Dbms_Subscriber Dbms_Subscriber_Interface Dbms_Subscriber_Defs
將會使用 Ada 的高度類型化來防止不同的類型混合。在概念上,不同的類型必須實現化為不同的使用者定義類型。應該使用子類型來增進程式的可讀性,以及提升編譯器所產生的執行時期檢查的效率。
每當可能時,請將一些額外的文字值引入列舉中,這些文字值代表未起始設定的值、無效的值,或是沒有值:
type Mode is (Undefined, Circular, Sector, Impulse); type Error is (None, Overflow, Invalid_Input_Value,Ill-formed_Name);
這將支援有系統地起始設定物件的規則。請將這段文字放在清單的開頭而非結尾,以便於維護,以及容許有效值的連續次範圍:
subtype Actual_Error is Error range Overflow .. Error'Last;
避免使用預先定義的數值類型。
當目標是高度的可攜性和可復用性時,或是需要控制數值物件所占用的記憶體空間時,則不可使用預先定義的數值類型(來自 Standard 套件)。這項要求的原因是 Ada Programming Language [ISO87] 的「參考手冊」中並未刻意指定預先定義類型 Integer 和 Float(來自 Standard 套件)的特性。
第一個有系統的策略是在 System_Types 套件中引入專案特有的數值類型,例如,帶有精度或記憶體大小之指示的名稱:
package System_Types is type Byte is range -128 .. 127; type Integer16 is range -32568 .. 32567; type Integer32 is range ... type Float6 is digits 6; type Float13 is digits 13; ... end System_Types;
請勿重新定義標準類型(Standard 套件中的類型)。
請勿指定應從中衍生它們的基本類型;讓編譯器自行選擇。以下是不良的範例:
type Byte is new Integer range -128 .. 127;
Float6 這個名稱要比 Float32 來得好,即使是在大部分的機器上,32 位元浮點數都會達到 6 位數的精度。
在專案的不同部分中,衍生其名稱比 Baty_System_Types 中的名稱有意義的類型。可以使其中的一些最精確的類型成為 private,以支援最終埠至具有有限精準度支援的目標。
這項策略的使用時機為:
如果不是這種情況,則有另一個較簡單的策略,就是一律定義新的類型,定義時指定所要求的範圍和精度,但是不要指定應從中衍生它們的基本類型。例如,宣告:
type Counter is range 0 .. 100; type Length is digits 5;
最好是改成下式:
type Counter is new Integer range 1..100; -- could be 64 bits type Length is new Float digits 5; -- could be digits 13
這個次要策略會強迫程式設計師考量每一種類型所需的精確界限和精確度,而不是任意選取某個位元數。不過,請注意,如果範圍與基本類型的範圍不同,則舉例而言,編譯器會針對上述的類型 Counter,套用有系統的範圍檢查,檢查基本類型是否為 32 位元整數。
如果範圍檢查變成是個問題,則避免它們的一個方法是宣告:
type Counter_Min_Range is range 0 .. 10_000; type Counter is range Counter_Min_Range'Base'First .. Counter_Min_Range'Base'Last;
避免標準類型透過建構(如迴圈、
索引範圍等等)漏失到程式碼中。
只有在下列情況下才會使用預先定義的數值類型之子類型:
範例:
for I in 1 .. 100 loop ... -- I 的類型為 Standard.Integer type A is array (0 .. 15) of Boolean; -- 索引是 Standard.Integer。
改用下列格式:Some_Integer range L .. H
for I in Counter range 1 .. 100 loop ... type A is array (Byte range 0 .. 15) of Boolean;
請勿試圖實作不帶正負號的類型。
Ada 中並不沒有含不帶正負號之運算表示式的整數類型。在語言定義下,所有的整數類型都間接或不是從預先定義的類型衍生,而這些都必須依次對稱在零附近。
如果需要可攜性,請僅信任其值在下列範圍內的實數類型:
[-F'Large .. -F'Small] [0.0] [F'Small .. F'Large]
請注意,F'Last 和 F'First 可能不是模型號碼,甚至可能不在任何模型間隔中。F'Last 與 F'Large 的相對位置視類型定義及基礎硬體而定。一個特別棘手的範例是其中固定點類型的 'Last 不屬於類型的情況,如以下所示:
type FF is delta 1.0 range -8.0 .. 8.0;
其中,根據 Ada Reference Manual 3.5.9(6) 的嚴格看法,FF'Last = 8.0 不能屬於類型。
如果要代表大型或小型的實數,請使用屬性 'Large 或 'Small(以及它們的負數),而不是使用 'First 和 'Last,這是用於整數類型。
如果是浮點數類型,請僅使用 <= 和 >=,不要用 =、<、>、/=。
絕對比較的語意含混不清(表示法相等,但是在必要的精確程度內不相等)。例如,X < Y 可能不會產生與 not (X >= Y) 生產相同的結果。等式 A = B 的測試應表示為:
abs (A - B) <= abs(A)*F'Epsilon
如果要增進可讀性及可維護性,請考慮提供封裝上述表示式的 Equal 運算子。
另請注意較簡單的表示式:
abs (A - B) <= F'Small
僅適用於較小值的 A 和 B,因此通常並不建議使用。
避免對預先定義異常狀況 Numeric_Error 的任何參照。Ada Board 的連結解譯已經使過去一向引發 Numeric_Error 的所有情況,現在都引發 Constraint_Error。異常狀況 Numeric_Error 已在 Ada 95 中作廢。
如果實作仍然提出 Numeric_Error(Rational 原始編譯器會有這種情況),則請在異常狀況處理常式中的同一個替代方案中一起檢查 Constraint_Error 與 Numeric_Error:
when Numeric_Error | Constraint_Error => ...
注意下溢。
在 Ada 中不會偵測到下溢。其結果為 0.0,且不會引發任何異常狀況。請注意,可以用下列方式來明確達成下溢的檢查: 測試當沒有任何運算元是 0.0 時,乘以或除以 0.0 的結果。另請注意,您可以實作您自己的運算子來自動執行這類的檢查,不過會一些效率上的損失。
限制固定點類型的使用。
請盡可能使用浮點數類型。在 Ada 實作上不均衡的固定點類型實作會導致可攜性問題。
以固定點類型而言,'Small 應等於 'Delta。
程式碼應指定這一點。'Small 的預設選項是 2 次方的事實,導致了各種類型的問題。使選項較明確的一種方法是寫成:
Fx_Delta : constant := 0.01; type FX is delta Fx_Delta range L .. H; for FX'Small use Fx_Delta;
如果固定點類型的長度子句不受支援,則遵守這項規則的唯一方法是明確指定 'Delta 是 2 次方。子類型的 'Small 可以與 'Delta 不同(這項規則僅適用於類型定義,或是 Ada Reference Manual 的專有名詞中的 "first named subtype")。
每當可能時,都請為記錄類型的元件提供簡單、靜態的起始值(經常可以使用像是 'First 或 'Last 等值)。
但是不要將這項規則套用到區別元件。語言的規則是區別元件永遠具有值。應只有在易變性是所要的特性時,才引進易變記錄(亦即,具有區別元件之預設值的記錄)。否則,易變記錄會在記憶體空間(經常會配置最大的變式)以及時間(要達到的變式檢查較為繁複)上引來額外的負荷。
請避免在任何元件的預設起始值中進行函數呼叫,因為這可能導致「在詳述之前存取」錯誤(請參閱「程式結構和編譯問題」)。
以易變記錄(其區別元件具有預設值的記錄)而言,如果在設定一些其他元件的範圍中使用區別元件,請將它指定為合理的小範圍。
範例:
type Record_Type (D : Integer := 0) is record S : String (1 .. D); end record; A_Record : Record_Type;
在大部分的實作上都很可能引發 Storage_Error。請對區別元件 D 的子類型指定更合理的範圍。
請勿對關於記錄的實際佈置做任何假設。
特別是元件不必按照定義中給定的次序來佈置,這有別於其他的程式設計語言。
限制存取類型的使用。
這對於有意在沒有虛擬記憶體的小型機器上永久執行的應用程式更是如此。存取類型非常危險,因為小量的程式設計錯誤可能導致儲存體耗盡,即使是好的程式設計也可能粉碎記憶體。存取類型也較為緩慢。存取類型的使用應該是全專案策略的一部分,並且應追蹤集合、它們的大小,以及配置和解除配置的點。如果要使抽象化的用戶端知道存取值已被操作,所選擇的名稱應指出:指標或是後面冠上 _Pointer 的名稱。
在程式詳述期間配置集合,並有系統地指定每一個集合的大小。
給定的值(採用貯藏單位)可為靜態或動態計算(例如,從檔案中讀取)。這項規則的基本原理是程式應在啟動時立即失效,而不是在 N 天後莫名其妙地終止。可以為此提供一些同屬套件,其中具有指定大小的額外同屬形式參數。
請注意,每一個已配置的物件經常有一些額外負荷: 可能是目標系統上的執行時期在各個記憶體片段上配置一些其他的資訊以進行內務處理。因此,如果要儲存大小為 M 個貯藏單位的 N 個物件,則可能有必要為集合配置超過 N * M 個貯藏單位,例如,N * (M + K)。請從 [ISO87] 的「附錄 F」或是進行實驗來取得這個額外負荷 K 的值。
簡化配置程式(Ada 基本元素 new)和版本的使用。如果可行,請管理內部可用清單,而不是依據 Unchecked_Deallocation。
如果使用存取類型來實作某些遞迴資料結構,則很可能存取具有(作為一個元件)相同存取類型的記錄類型。這可讓您藉由在可用清單中鏈結可用的 Cell 來加以回收,而沒有額外的空間額外負荷(而不是指向清單表頭的指標)。
明確處理 new 所引發的 Storage_Error 異常狀況,並重新匯出更具意義的異常狀況,來指出集合的最大儲存體大小已耗盡。
使用單一的配置和解除配置點也可讓您在發生問題時較易於追蹤及除錯。
僅對已配置的相同大小(因此也具有相同的區別元件)的 Cell 使用解除配置。
這對於避免記憶體片段化非常重要。Unchecked_Deallocation 很可能不提供記憶體壓縮服務。您應檢查執行時期系統是否提供鄰接的已釋放建構的聯合。
有系統地提供具有存取類型的 Destroy(或是 Free 或 Release)基本元素。
這對於以存取類型來實作的抽象資料類型而言特別重要;其應該有系統地執行,以具備組合多個這種類型的功能。
有系統地釋出物件。
嘗試將呼叫對映至配置和解除配置,以確定所有已配置的資料都已解除配置。嘗試解除配置在其配置所在的相同範圍內的資料。別忘了在發生異常狀況時也要解除配置。請注意,這是一個使用 when others 替代方案(結尾為 raise 陳述式)的案例。
偏好的策略為套用下列型樣:取得-使用-釋出。程式設計師「取得」物件(其建立某個動態資料結構),然後「使用」它,之後它必須「釋出」它。請務必在程式碼中清楚識別這三項作業,並確定在頁框的所有可能的出口(包括因為異常狀況)進行釋出。
請小心解除配置暫時複合資料結構,其可能內含於記錄中。
範例:
type Object is record Field1: Some_Numeric; Field2: Some_String; Field3: Some_Unbounded_List; end record;
其中 'Some_Unbounded_List' 是一個複合的鏈結結構,也就是說,它是由若干個鏈結在一起的物件所組成。現在,請考量一個寫成如下的典型屬性函數:
function Some_Attribute_Of(The_Object: Object_Handle) return Boolean is Temp_Object: The_Object; begin Temp_Object := Read(The_Object); return Temp_Object.Field1 < Some_Value; end Some_Attribute_Of;
在物件讀入 Temp_Object 時於資料堆中隱含建立的複合結構從未解除配置,但是現在卻已經無法連結。這是由於記憶體洩漏。適當的解決方案是對這類繁複的結構實作「取得-使用-釋出」參照範例。換句話說,您的用戶端應先「取得」物件,然後視需要「使用」它,之後再「釋出」它:
procedure Get (The_Object : out Object; With_Handle : in Object_Handle); function Some_Attribute_Of(The_Object : Object) return Some_Value; function Other_Attribute_Of(The_Object : Object) return Some_Value; ... procedure Release(The_Object: in out Object);
用戶端程式碼可能如以下所示:
declare My_Object: Object; begin Get (My_Object, With_Handle => My_Handle); ... Do_Something (The_Value => Some_Attribute_Of(My_Object)); ... Release(My_Object); end;
每當有必要隱藏實作詳細資料時,請將類型宣告為 專用。
在下列情況下,必須以專用類型隱藏實作詳細資料:
在 Rational 環境中,專用類型與已關閉的專用組件和子系統一起使用,可以大幅降低最終介面設計變更的影響。
與所謂「純」物件導向程式設計相反;如果相對應的完整類型是可能的最佳抽象化時,請勿使用專用類型。請務實一點;考量一下使類型為 private 是否有多了什麼益處。
例如,以迴圈表示數學向量,或是以迴圈表示平面上的一個點,這些都比使用專用類型來得好:
type Vector is array (Positive range <>) of Float; Type Point is record X, Y : Float := Float'Large; end record;
陣列檢索、記錄元件選取及聚集表示法,會遠比由於不必要地使類型為 private 而需要一系列的子程式呼叫來得可行(而且最終會較有效率)。
當實際的物件和值之預設指派或比較毫無意義、
非直覺或不可能時,請將專用類型宣告為有限。
這適用於下列情況:
有限的專用類型應自行起始設定。
這種類型的物件宣告必須接收合理的起始值,因為指派稍後的值通常會不可行(在沒有於子程式呼叫期間引發某異常狀況的風險下)。
每當可行或有意義時,為受限類型提供「複製」(或「指派」)程序和「摧毀」程序。
在設計同屬的形式類型時,只要內部不需要等式或指派時,請指定有限專用類型,使相對應的同屬單元能夠有較大的使用性。
在具有上一個規則的行中,您應該匯入「複製」及「摧毀」同屬形式程序以及 Are_Equal 述詞(如果有意義的話)。
針對同屬形式專用類型,在規格中指出是否必須限制相對應的實際。
可利用命名慣例和(或)註解來達到這個目的:
generic --必須被限制。 type Constrained_Element is limited private; package ...
您可以交替使用 Rational 定義的 pragma Must_Be_Constrained
:
generic type Element is limited private; pragma Must_Be_Constrained (Element); package ...
請記住,衍生某類型也會衍生所有在與親項類型相同的宣告部份中宣告的子程式:
可衍生的子程式。因此,在衍生類型的宣告部份中將它們重新定義為外觀是毫無用處的。但是同屬子程式無法交付,可能有必要將它們重新定義為外觀。
範例:
package Base is type Foo is record ... end record; procedure Put(Item: Foo); function Value(Of_The_Image: String) return Foo; end Base; with Base; package Client is type Bar is new Foo; -- At this point, the following declarations are -- implicitly made: -- -- function "="(L,R: Bar) return Boolean; -- -- procedure Put(Item: bar); -- function Value(Of_The_Image: String) return Bar; -- end Client;
因此,沒有必要將這些作業重新定義為外觀。不過,請注意,同屬子程式(如被動反覆元)並不連同其他作業一起衍生,因此必須重新匯出為外觀。在包含基本類型宣告的規格以外的地方定義的子程式也是無法衍生,因此也必須重新匯出為外觀。
請在物件宣告中指定起始值,除非該物件是自行起始設定,或是有隱含的預設起始值(例如,存取類型、作業類型、
具有非區別元件欄位之預設值的記錄)。
所指派的值必須是個實際、有意義的值,而不只是該類型的任何值。如果有實際的起始值可用,例如其中一個輸入參數,則請指派它。如果無法計算有意義的值,則考慮稍後才宣告物件,或是指派任何 "nil" 值(若有的話)。
"Nil" 這個名稱意指「未起始設定」,
其會用來宣告可用來作為「無法使用但已知的值」,且可能被演算法以受管制的方式拒絕的常數。
每當可能時,都不應將 Nil 值用作起始設定以外的任何其他的用途,因此當它出現時,都一定表示發生未起始設定的變數錯誤。
請注意,並不是所有的類型都能夠宣告 Nil 值,特別是模數類型,例如一個角度。在此情況下,請選擇較不可能的值。
請注意,要起始設定大型記錄,可能耗用極大量的程式碼,特別是在記錄具有變式,以及某些起始值是非靜態時(或者,更確切地說,是在編譯時期無法計算該值時)。有時針對所有的起始值(或許是在定義類型的套件中)詳述一次,然後再明確地指派它會比較有效率。
R : Some_Record := Initial_Value_For_Some_Record;
附註:
經驗顯示,變數未起始設定是植入程式碼時發生問題的主要原因之一,也是程式設計錯誤的其中一個主要原因。這在下列情況下會更為嚴重:當開發主機為了試著要對程式設計師「好一點」而至少提供部分物件的預設值時(例如,Rational 原始編譯器上的類型 Integer),或是當目標系統在程式載入之前將記憶體歸零時(例如,在 DEC VAX 上)。為達到可攜性,請一律假設最壞的情況。
當代價非常高,而且很顯然的物件在使用之前就先被指派值時,可以省略在宣告中指派起始值。
範例:
procedure Schmoldu is Temp : Some_Very_Complex_Record_Type; -- 稍後起始設定。 begin loop Temp := Some_Expression ... ...
避免在程式碼中使用文字值。
當所定義的值被連結至某類型時,請使用常數(具有類型)。否則,請使用具名數字,特別是針對沒有尺寸的值(純值):
Earth_Radius : constant Meter := 6366190.7; -- 以公尺為單位。Pi : constant := 3.141592653; -- 沒有單位。
使用通用、靜態的表示式定義相關的常數
Bytes_Per_Page : constant := 512; Pages_Per_Buffer : constant := 10; Buffer_Size : constant := Bytes_Per_Page * Pages_Per_Buffer; Pi_Over_2 : constant := Pi / 2.0;
這利用了這些表示式必須就在編譯時期計算的事實。
請勿宣告具有匿名類型的物件。如果需要詳細資訊,請參閱 Ada Reference Manual 3.3.1。
降低了可維護性、物件無法當作參數傳遞,同時它經常導致類型衝突錯誤。
子程式可以宣告為程序或函數;以下是一些可用來要宣告之格式的一般準則。
在下列情況下宣告函數:
在下列情況下宣告程序:
避免賦予預設值給用於調整結構大小的同屬形式參數(表格、
集合等等)
撰寫副作用盡可能少的本端程序,以及完全沒有副作用的函數。記載副作用。
副作用通常是廣域變數的修正,可能只有在讀取子程式的主體時會被注意到。程式設計師可能未察覺到在呼叫端的副作用。
作為參數傳入必要物件會使程式碼更為健全、 更容易理解,而且較不依附於其內容。
這項規則主要適用於區域子程式: 匯出的子程式經常需要對套件主體中的廣域變數之合法存取權。
使用冗餘括弧來使複合表示式更明確。
表示式的巢狀結構層次被定義為巢狀結構的括弧集數目,這些括弧需用來從左到右求表示式的值(如果忽略運算子優先順序的規則)。
將表示式的巢狀結構層次限制為四。
記錄聚集應使用具名關聯,並且應界定為:
Subscriber.Descriptor'(Name => Subscriber.Null_Name, Mailbox => Mailbox.Nil, Status => Subscriber.Unknown, ...);
禁止使用
when others 作為記錄聚集。
這是因為記錄在本質上是異質結構(與陣列相反),因此統一指派是不合理的。
使用簡單的
Boolean 表示式代替 "if...then...else" 陳述式作為簡單的述語:
function Is_In_Range(The_Value: Value; The_Range: Range) return Boolean is begin if The_Value >= The_Range.Min and The_Value <= The_Range.Max then return True; end if; end Is_In_Range;
使用下式會比較好:
function Is_In_Range(The_Value: Value; The_Range: Range) return Boolean is begin return The_Value >= The_Range.Min and The_Value <= The_Range.Max; end Is_In_Range;
如果會影響可讀性,則不應用這種方式變更含有一或多個 if 陳述式的複式表示式。
在下列情況下,loop 陳述式應具有名稱:
Forever: loop ... end loop Forever;
當迴圈有名稱時,它所包含的任何 exit 陳述式都應指定它。
在開始時需要完成測試的迴圈應使用 "while" 迴圈格式。在別處需要完成測試的迴圈則應使用一般格式以及 exit 陳述式。
將迴圈中的 exit 陳述式減至最少。
在陣列上反覆的 "for" 迴圈中,請使用套用在陣列物件上的 'Range 屬性,而不是使用明確範圍或是其他某子類型。
將任何獨立於迴圈的程式碼移出迴圈。雖然「程式碼升級」是一項共同的編譯器最佳化,但是當不變程式碼呼叫其他的編譯單元時,就無法做到這一點。
範例:
World_Search: while not World.Is_At_End(World_Iterator) loop ... Country_Search: while not Nation.Is_At_End(Country_Iterator) loop declare City_Map: constant City.Map := City.Map_Of (The_City => Nation.City_Of(Country_Iterator), In_Atlas => World.Country_Of(World_Iterator).Atlas); begin ...
在上述程式碼中,對 "World.Country_Of" 的呼叫獨立於迴圈(亦即,country 在內部迴圈中保持不變)。不過,在大部分的情況下,是禁止編譯器將呼叫移出迴圈,因為該呼叫可能有副作用,可能影響程式的執行。因此,該程式碼會不必要地每次都通過迴圈來執行。
迴圈如果改寫成如下,則會較有效率且較易於維護:
Country_Search: while not World.Is_At_End(World_Iterator) loop declare This_Country_Atlas: constant Nation.Atlas := World.Country_Of (World_Iterator).Atlas; begin ... City_Search: while not Nation.Is_At_End (The_City_Iterator) loop declare City.Map_Of ( The_City => Nation.City_Of (Country_Iterator), In_Atlas => This_Country_Atlas ); begin ...
子程式及進入呼叫應使用具名關聯。
不過,如果很顯然的第一個(或是唯一的一個)是作業的主要焦點(例如,及物動詞的直接受詞),則只能省略這個參數的名稱:
Subscriber.Delete (The_Subscriber => Old_Subscriber);
其中 Subscriber.Delete 是及物動詞,Old_Subscriber 是直接受詞。下列沒有具名關聯 The_Subscriber => Old_Subscriber 的表示式是可以接受的:
Subscriber.Delete (Old_Subscriber); Subscriber.Delete (Old_Subscriber, Update_Database => True, Expunge_Name_Set => False); if Is_Administrator (Old_Subscriber) then ...
也有一些情況是參數的意義極為明顯,以致具名關聯只會降低易讀性。舉例而言,當所有參數的類型和模式都相同,而且沒有預設值時,就會發生這種情況:
if Is_Equal (X, Y) then ... Swap (U, B);
when
others 不應用於 case 陳述式或記錄類型定義(針對變式)。
不使用 when others 會在維護階段期間有所助益,作法為在每當離散類型定義被修改時,都使這些建構無效,以強迫程式設計師考量應執行什麼動作才能處理修正。不過,當選取元是大整數範圍時,可以接受它。
當分支條件是離散值時,請使用 case 陳述式而非一系列的 "elsif"。
子程序應該有單一返回點。
嘗試在陳述式部分的結尾離開子程式。函數應該有單一傳回陳述式。傳回陳述式任意散佈在函數主體上,就好像是 goto 陳述式,會使程式碼較難以判讀及維護。
程序應完全沒有傳回陳述式。
只有在非常小的函數中可以容忍多個傳回值,但是條件是可以同時看到所有的傳回值,而且程式碼有非常固定的結構:
function Get_Some_Attribute return Some_Type is begin if Some_Condition then return This_Value; else return That_Other_Value; end if; end Get_Some_Attribute;
限制使用 goto 陳述式。
在為 "goto" 陳述式辯護時,應該註明在 Ada 中使用 goto 標籤的語法以及 goto 的限制條件會使這個陳述式並非如想像中那麼有害,而且在許多情況下,它比某些相等的建構(例如,使用異常狀況所建置的偽造 goto)更好,並且更可行且更有意義。
在操作陣列時,請不要假設它們的索引是從 1 開始。請使用 'Last、'First、'Range 等屬性。
定義未受限類型的最常見受限子類型(大部分是記錄),並對參數以及傳回值使用那些子類型,來增加用戶端程式碼中的自我檢查:
type Style is (Regular, Bold, Italic, Condensed); type Font (Variety: Style) is ... subtype Regular_Font is Font (Variety => Regular); subtype Bold_Font is Font (Variety => Bold); function Plain_Version (Of_The_Font: Font) return Regular_Font; procedure Oblique (The_Text : in out Text; Using_Font : in Italic_Font); ...
建議採行下列準則:
超載子程式。
然而,在使用相同的識別字時,請確定它確實意指同類型的作業。
避免在巢狀範圍內隱藏同形異義字 ID。
這會導致讀者混淆以及維護上的潛在風險。也請知悉 "for" 迴圈控制變數的存在和範圍。
請勿在子類型上超載作業,請一律在類型上超載。
超載將會套用到基本類型以及其所有的子類型,這與單純的讀者可能被指引認定的背道而馳。
範例:
subtype Table_Page is Syst.Natural16 range 0..10; function "+"(Left, Right: Table_Page) return Table_Page;
編譯器在比對子程式時,會尋找基本類型,不尋找參數的子類型。因此,在上面的範例中,實際上是重新定義了現行套件中的所有 Natural16 值(不只是 Table_Page)的 "+"。因此,現在任何表示式 "Natural16 + Natural16" 都會被對映至 "+"(Table_Page, Table_Page) 呼叫,其將會傳回錯誤的結果或是產生異常狀況。
將 "with" 子句所引起的相依關係數減至最少。
當使用 "with" 子句而延伸了有效範圍時,此子句應涵蓋盡可能小的程式碼區域。請在有必要時才使用 "with" 子句,最理想的是只在主體上使用,甚至只在大型的主體 Stub 上使用。
使用介面套件來重新匯出低層實體,因而避免明顯地 "with" 大量的低層套件。如果要這麼做,請使用衍生類型、重新命名、外觀子程式,以及甚或是預先定義的類型,如字串(使用「環境」指令套件執行)。
使用同屬形式參數,在單元之間使用軟(弱)耦合,而不是使用 "with" 子句來使用硬(強)耦合。
範例:如果要在合成類型上匯出 Put 程序,請針對其子元件,將某程序 Put 當作同屬形式參數匯入,而非直接對 Text_Io 進行 with。
不應使用 "Use" 子句
盡可能避免使用 "use" 子句可以增加可讀性和易讀性,但條件是有效利用環境的命名慣例以及適當的重新命名能夠適當支援這項規則(請參閱上述的「命名慣例」)。它也有助於防止一些有效範圍的意外,特別是在維護階段期間。
以定義字元類型的套件而言,在任何需要依據這個字元類型來定義字串文字的編譯單元中,"use" 子句是必要的:
package Internationalization is type Latin_1_Char is (..., 'A', 'B', 'C', ..., U_Umlaut, ...); type Latin_1_String is array (Positive range <>) of Latin_1_Char; end Internationalization ; use Internationalization; Hello : constant Latin_1_String := "Baba"
沒有 "use" 子句可以防止以插入詞的格式使用運算子。那些可以在用戶端單元中重新命名:
function "=" (X, Y : Subscriber.Id) return Boolean renames Subscriber."="; function "+" (X, Y :Base_Types.Angle) return Base_Types.Angle renames Base_Types."+";
由於缺少 "use" 子句時經常會導致在眾多的用戶端單元中併入相同的重新命名集,因此可以在定義方套件本身中,利用在定義方套件中巢狀結構的 Operations 套件,將全部的那些重新命名分解為因數。因此建議在用戶端單元中,對套件 Operations 使用 "use" 子句:
package Pack is type Foo is range 1 .. 10; type Bar is private; ... package Operations is function "+" (X, Y : Pack.Foo) return Pack.Foo renames Pack."+"; function "=" (X, Y : Pack.Foo) return Boolean renames Pack."="; function "=" (X, Y : Pack.Bar) return Boolean renames Pack."="; ... end Operations; private ... end Pack; with Pack; package body Client is use Pack.Operations; -- Makes ONLY Operations directly visible. ... A, B : Pack.Foo; -- Still need prefix Pack. ... A := A + B ; -- Note that "+" is directly -- visible.
「套件作業」應固定具有這個名稱,並且應固定放在定義方套件的可見部分底端。"use" 子句應只放在有必要的地方,也就是說,如果規格中未使用任何作業(經常是這種情況),則它應只放在 Client 的主體。
with Defs; package Client is ... package Inner is use Defs; ... end Inner; -- use 子句的範圍在這裡結束。 ... end Client; declare use Special_Utilities; begin ... end; -- use 子句的範圍在這裡結束。
使用重新命名宣告。
建議將重新命名與對於 "use" 子句的限制一起搭配,使程式碼較易於判讀。當具有極長名稱的單元被參照數次時,提供它一個極簡的名稱將會增進易讀性:
with Directory_Tools; with String_Utilities; with Text_Io; package Example is package Dt renames Directory_Tools; package Su renames String_Utilities; package Tio renames Text_Io; package Dtn renames Directory_Tools.Naming; package Dto renames Directory_Tools.Object; ...
在專案中選擇短名稱時應完全保持一致,以保持最少意外的原則。達到這個目的的方法是在套件本身中提供短名稱:
package With_A_Very_Long_Name is package Vln renames With_A_Very_Long_Name; ... end with With_A_Very_Long_Name; package Example is package Vln renames With_A_Very_Long_Name; -- 從這裡開始,Vln 都是縮寫。
請注意,套件重新命名只會賦予有效範圍給被重新命名套件的可見組件。
匯入套件的命名應群集在宣告部份的開頭,並按字母順序排序。
重新命名可以在其中會增進易讀性的本端環境使用(這麼做並不沒有執行時間上的損失)。類型可以重新命名為子類型,沒有任何限制。
如有關註解的章節中所示,重新命名往往提供記載程式碼的一個既精緻又可維護的方式,例如,賦予一個簡單的名稱給某複式物件,或是在本端環境修正類型的意義。應選擇重新命名識別字的範圍以避免引起混淆。
異常狀況重新命名可以在數個單元之中(例如,在同屬套件的所有實例化之中)將異常狀況分解為因數。請注意,在衍生類型的套件中,衍生的子程式可能引發的異常狀況應該與衍生的類型一起重新匯出,
以避免用戶端必須 "with" 原始套件:
with Inner_Defs; package Exporter is ... procedure May_Raise_Exception; -- Raises exception Inner_Defs.Bad_Schmoldu when ... ... Bad_Schmoldu : exception renames Inner_Defs.Bad_Schmoldu; ...
以 "in" 參數的不同預設值重新命名子程式,可以簡化程式碼因數分解以及增進易讀性:
procedure Alert (Message : String; Beeps : Natural); procedure Bip (Message : String := ""; Beeps : Natural := 1) renames Alert; procedure Bip_Bip (Message : String := ""; Beeps : Natural := 2) renames Alert; procedure Message (Message : String; Beeps : Natural := 0) renames Alert; procedure Warning (Message : String; Beeps : Natural := 1) renames Alert;
避免在重新命名方之宣告的直接範圍內使用被重新命名之實體的名稱(舊名稱);僅使用重新命名方之宣告所引進的識別字或運算子符號(新名稱)。
多年以來,Ada 社群中一直有 "use" 子句的爭議,以致有時處於信仰戰爭的邊緣。雙方都用各種往往與大型專案或範例不均衡的論點,而那些論點都過於不切實際或刻意偏頗。
"use" 子句的擁護者宣稱它可以增加易讀性,同時他們提供了一些特別是無法判讀、太長以及冗餘之名稱的範例,而這些名稱如果使用好幾次,則會從重新命名得到好處。他們也宣稱 Ada 編譯器可以解決超載,這固然沒錯,但是埋首於大型 Ada 程式的人類在執行解決方案超載時,並無法像編譯器一樣地令人信賴,當然速度也沒那麼快。他們宣稱更準確的 APSE(如 Rational 環境)會使明確的完整名稱毫無用處;但是並非如此:對於使用者不確定的每一個識別字,他們不應該都得按 [定義] 才行。使用者不應該去猜測,而應該能夠立即看到使用的是哪些物件和抽象化。"use" 子句的 Rosen 擁護者否認它在程式維護中可能造成風險,並且建議對造成這類風險的程式設計師給予不及格的成績;我們認為完整名稱可以消除該風險。
如果上述建議用來減輕對於 "use" 子句之限制的影響似乎需要打太多字,請想一想 Norman H. Cohen 的結論:「在程式打字上所節省的任何時間,會在審查、除錯及維護程式時多浪費掉許多時間」。
最後,過去以來顯示,在大型的系統中,如果沒有 "use" 子句,可以改善編譯時間,原因是降低了在符號表中查閱的額外負荷。
對於進一步瞭解 use 子句爭議有興趣的讀者可以查閱下列的資料來源:
D. Bryan, "Dear Ada," Ada Letters, 7, 1, January-February 1987, pp. 25-28.
J. P. Rosen, "In Defense of the Use Clause," Ada Letters, 7, 7, November-December 1987, pp. 77-81.
G. O. Mendal, "Three Reasons to Avoid the Use Clause," Ada Letters, 8, 1, January-February 1988, pp. 52-57.
R. Racine, "Why the Use Clause Is Beneficial," Ada Letters, 8, 3, May-June 1988, pp. 123-127.
N. H. Cohen, Ada as a Second Language, McGraw-Hill (1986), pp. 361-362.
M. Gauthier, Ada-Un Apprentissage, Dunod-Informatique, Paris (1989), pp. 368-370.]
有兩個將源自起始設計階段的大型"邏輯"套件,拆解成數個較易於管理、 編譯、維護及瞭解的較小 Ada 程式庫單位的方式:
巢狀分解
這種方法強調 Ada 子單元和(或)子套件的使用。會有系統地分隔主要子程式、 作業主體和內部套件。這個流程會在那些子單元/子套件內遞迴地重複。
b) 平面分解
邏輯套件分解成一個較小套件的網路,這些套件是以 "with" 子句交互連接,而原始邏輯套件則大部分是重新匯出的外觀(或是甚至已不存在的設計構件)。
每一種方法都有它的優缺點。巢狀分解所需撰寫的程式碼較少,而且可以簡化命名(許多識別字不需要冠上名稱): 而且至少在 Rational 環境上,結構在程式庫影像中非常明顯,結構也較易於轉換(Ada.Make_Separate、Ada.Make_Inline 指令)。平面分解往往會使重新編譯較少,以及更好或更明確的結構(特別是在子系統界限);它也會促進重複使用。也較容易利用自動重新編譯工具和配置管理加以管理。不過,在使用平面結構時,會有由於 "with" 其中一些已建立於分解中的較低層套件而違背原始設計的較大風險。
若是子程式,巢狀結構的層次應限制為三個,若是套件則為兩個;請勿在子程式內巢狀結構套件。
package Level_1 is package Level_2 is package body Level_1 is procedure Level_2 is procedure Level_3 is
如果發生下列其中一種情況,
請使用巢狀單元的主體 Stub(「分開的主體」):
套件規格的宣告部份含有一些應按照下列順序編排的宣告:
1) 重新命名套件本身的宣告
2) 重新命名所匯入實體的宣告
3) "Use" 子句
4) 具名號碼
5) 類型和子類型宣告
6) 常數
7) 異常狀況宣告
8) 匯出的子程式規格
9) 巢狀套件(如果有的話)
10) 專用部分。
以引入數個主要類型的套件而言,可能最好有數組相關宣告集:
5) A 的類型和子類型宣告
6) 常數
7) 異常狀況宣告
8) A 作業的已匯出子程式規格
5) B 的類型和子類型宣告
6) 常數
7) 異常狀況宣告
8) B 作業的已匯出子程式規格
其他
當宣告部份較大時(>100 行),請使用較小的註解建構來界定不同的區段。
套件主體宣告的宣告部份含有一些應按照下列順序編排的宣告:
1) 重新命名宣告(針對匯入的實體)
2) "Use" 子句
3) 具名號碼
4) 類型和子類型宣告
5) 常數
6) 異常狀況宣告
7) 區域子程式規格
8) 區域子程式主體
9) 匯出的子程式主體
10) 巢狀套件主體(如果有的話)。
其他宣告部份(例如在子程式主體、
作業主體及建構陳述式中)遵循相同的一般型樣。
每個匯入的程式庫單位都使用一個 "with" 子句。按字母順序排序 with 子句。如果在已 "with" 的單位上使用 "use" 子句是適當的,則它應緊接在相對應的 "with" 子句後面。請參閱以下的 pragma Elaborate。
請勿依賴程式庫單位的詳述次序來達到任何特定的效果。
每一個 Ada 實作都可以任意選擇計算詳述次序的策略,前提是其符合 Ada Reference Manual [ISO87] 中陳述的一些極簡單的規則。某些實作使用比其他實作來得聰明的策略(例如,在相對應的規格後可行時立即詳述主體),但是某些實作並沒有努力要這麼聰明(特別是針對同屬實例化),導致極為嚴重的可攜性問題。
在程式詳述期間發生不名譽的「在詳述之前存取」錯誤有三個主要的原因(其通常應提出 Program_Error 異常狀況):
task type T; type T_Ptr is access T; SomeT : T_Ptr := new T; -- 在詳述之前存取。
為避免在將應用程式從一個 Ada 編譯器植入另一個編譯器時發生問題,程式設計師應藉由重組程式碼來解決問題(這並不一定可行),或是採用下列策略,以 pragma Elaborate 明確地控制詳述的次序:
在單位 Q 的環境子句中,應將 pragma Elaborate 套用到 "with" 子句中出現的每一個單位 P:
此外,如果 P 匯出類型 T,使類型 T 的物件詳述呼叫套件 R 中的函數,則 Q 的環境子句應包含:
with R; pragma Elaborate (R);
即使沒有對 Q 中的 R 之直接參照!
實際上,陳述套件 P 應包含內容的規則可能較為容易(但不一定可行):
with R; pragma Elaborate (R);
套件 Q 必須僅僅包含:
with P; pragma Elaborate (P);
因而提供按傳遞性的正確詳述次序。
限制作業的使用。
作業是一項非常強有力的特性,但是要小心使用。如果不審慎使用作業,則在空間和時間上所要付出的額外負荷可能極為龐大。對系統的某些部分進行小幅變更可能會徹底危害到一組作業集的存活,導致資源用盡和(或)死鎖。作業程式的測試和除錯相當困難。因此作業的使用、它們的安置,以及它們的互動,都屬於專案層的決策。作業不能用隱藏的方式使用,也不能由沒有經驗的程式設計師撰寫。必須要讓 Ada 程式的作業模型明確而且容易瞭解。
除非有來自並行硬體的有效支援,否則應只有在並行真正有必要時才引進作業。這是在表達視時間而定的動作時的情況: 逾時的定期活動或引進,或是取決於外部事件的動作(例如,外部事件的岔斷或送達)的動作。也需要引入作業以退耦其他的活動,例如: 緩衝、佇列化、分派,以及同步化存取共同資源。
使用 'Storage_Size 長度子句指定作業堆疊大小。
基於導致集合具有長度子句之需求的相同原因以及相同情況(上述的「存取類型」一節),應該按其中記憶體為珍貴資源的情況來指定作業的大小。如果要這麼做,請一律宣告明確宣告之類型的作業(因為長度子句只能套用至類型)。可以利用函數呼叫來動態調整堆疊的大小。
附註:要猜測每一項作業需要多少堆疊可能極為困難。如果要幫助猜測,執行時期系統可以配備「高臨界值」機制。
在作業主體中使用異常狀況處理常式,來避免或至少報告未經說明的作業終止。
不處理異常狀況的作業會終止,而且通常是沒有跡象。如果可行,請盡量嘗試報告終止的緣由,特別是 Storage_Error。這樣可以微調堆疊的大小。請注意,這需要將配置(基本元素 new)封裝在子程式中, 來重新匯出 Storage_Error 以外的異常狀況。
在程式詳述期間建立作業。
基於導致在程式詳述期間配置集合之需求的相同原因以及相同情況(上述的「存取類型」一節),整個應用程式作業結構應在程式啟動時很早就建立。讓程式由於記憶體耗盡而完全無法啟動,會比幾天後終止而完全無法啟動來得好。
在後續的規則,在服務作業與應用程式作業之間做了一項區別。服務作業是一種較小、而且在演算法上較簡單的作業,用來提供應用程式相關作業之間的「中介」。服務作業(或是媒介服務)的例子包括緩衝區、 轉換程式、轉遞、代理程式、監視器等等,通常提供同步化、 分離、緩衝及等待等服務。應用程式作業則如同其名稱所傳達的,是與應用程式的主要函數比較有直接的關係。
避免混合作業:
應用程式作業應做為純呼叫方;服務作業則應做為純被呼叫方。
純被呼叫方是一項僅包含接受陳述式或是選擇性等待,且沒有進入呼叫的作業。
避免在進入呼叫中循環。
這會大幅降低死鎖的風險。如果無法完全避免循環,則至少要在系統的穩定狀態時避免。這兩項規則也會使結構較易於瞭解。
限制共用變數的使用。
請特別注意隱藏的共用變數,舉例而言,也就是隱藏在套件主體中、 並且由數項作業可見的基本元素所存取的變數。當會合的成本過高時,可以在存取共同資料結構同步化的特別情況下使用共用變數。請檢查是否有效支援 pragma Shared。
限制 abort 陳述式的使用。
abort 陳述式被公認為是語言的其中一個最危險且最有害的基本元素。用它來無條件地(且幾乎非同步地)終止作業,使它幾乎不可能推斷給定作業結構的行為。不過,有必要使用 abort 陳述式的情況非常有限。
範例:某些所提供的低階服務並沒有逾時的機能。引入逾時的唯一方法,是能夠有某些輔助代理程式作業所提供的服務、 等待(有逾時值)來自代理程式的回覆,然後若是在延遲時間內未提供服務,則使用中止來結束代理程式。
可以接受中止的情況是:可以證明只有中止方及被中止方有可能會受影響,例如,當沒有任何其他的作業有可能呼叫遭中止的作業時。
限制 delay 陳述式的使用。
任意暫停作業可能導致嚴重的排程問題,這種問題很難追蹤到及更正。
限制 'Count、'Terminated 和 'Callable 等屬性的使用。
屬性 'Count 應僅用來作為粗略的指示,同時排程決策不應依據其零或非零值,因為實際的等待作業數可能在評估屬性的時間與使用其值的時間之間變更。
使用條件式進入呼叫(或是含接受的相等建構),以確實地檢查是否缺少等待作業。
select The_Task.Some_Entry; else -- do something else end select;
最好是改成下式:
if The_Task.Some_Entry'Count > 0 then The_Task.Some_Entry; else -- do something else end if;
屬性 'Terminated 只有在它產生 True 時才有意義,而 'Callable 只有在它產生 False 時才有意義,這因而相當限制它們的有用性。它們不應用來提供系統關機期間作業與作業之間的同步化。
限制優先順序的使用。
Ada 中的優先順序對於排程的影響相當有限。特別是,在排序項目佇列或是選取要在選擇性等待中提供的項目時,並不會將作業等待項目的優先順序納入考量。這可能導致優先順序倒置(請參閱 [GOO88]) 優先順序僅被排程器用來在準備好可以執行的作業中,選取下一個要執行的作業。由於優先順序倒置的風險,請勿依賴優先順序進行互斥。
若利用幾個系列的項目,則可以將進入佇列分割成數個子佇列,這樣一來,就經常有可能引進明確的緊急性概念。
如果優先順序並非必要,請勿指派任何優先順序給任何作業。
在指派優先順序給一項作業後,請指派優先順序給應用程式中的所有作業。
這項規則是必要的,因為若沒有 pragma Priority,則作業的優先順序是未定義的。
為了可攜性,請保持較少的優先順序層次數。
子類型 System.Priority 的範圍是按實作定義,而經驗顯示可用的實際範圍隨系統的不同而有很大的差異。不過,集中定義優先順序是一個不錯的想法,這可以賦予它們名稱和定義,而不是在所有的作業中使用整數文字。這種集中的 System_Priorities 套件可簡化可攜性,這與上一個規則搭配可以輕易找到所有作業規格。
如果要避免在循環作業中漂流,請將 delay 陳述式制訂為將處理時間、額外負荷及作業先行納入考量:
Next_Time := Calendar.Clock; loop -- Do the job. Next_Time := Next_Time + Period; delay Next_Time - Clock; end loop;
請注意 Next_Time - Clock 可能是負數,表示循環作業的執行時間較晚。可能可以除去一個循環。
為了保證可排程性,請根據 Rate Monotonic Scheduling Algorithm 來指派優先順序給循環作業,也就是從最高優先順序到最常用的作業(請參閱 [SHA90] 以取得詳細資料)。
指派較高的優先順序給極快的媒介伺服器:
監視器、緩衝區。
但是請確定這些伺服器不會因為與其他作業會合而阻斷了它們自己。請在程式碼中記載這個優先順序,以便它能夠在程式維護期間受到重視。
如果要將 "jitter" 的影響降至最低,請依賴時間戳記輸入範例或輸出資料,而不是依賴期間本身。
避免忙碌等待(輪詢)。
請確定作業用選取或進入呼叫等待或是被延遲,而不是劇烈地檢查可做的事。
針對每一個會合,請確定至少一端在等待,而且只有一端具有條件式進入呼叫或是定時進入呼叫或等待。
否則,會有程式碼執行到競爭狀況中的風險(尤其是在迴圈中),非常類似於忙碌等待。不當使用優先順序可能會累積成這種情況。
在封裝作業時。請務必讓它們的部分特殊字元高度可見。
如果進入呼叫隱藏在子程式中,請確定那些子程式之規格的讀取器知道對這個子程式的呼叫可能暫停執行。此外,請指定等待是否被連結;如果是,請提供一些上限的預估。使用命名慣例來指出可能的等待(上述的「子程式」一節)。
如果套件的詳述、子程式的呼叫,或同屬單元的實例化啟動某作業,請讓用戶端看到這個事實:
package Mailbox_Io is -- 這個套件詳述內部的「控制」作業, -- 此作業同步處理對外部信箱 -- 的所有存取。 procedure Read_Or_Wait (Name: Mailbox.Name; Mbox: in out Mailbox.Object); -- -- 暫停執行(未連結的等待)。
請勿根據選擇性等待中的項目選取的任何特定順序。
如果在挑選排入項目佇列的作業上需要某種程度的公平性,可以藉由按所要的順序明確地檢查沒有等待的佇列,然後等待所有的項目,來達到這個目的。請勿使用 'Count。
請勿根據在同一個宣告部份中詳述之作業的任何特定的啟動次序。
如果尋求特定的啟動次序,則這應藉由與特殊啟動項目會合來達成。
實作作業以正常終止。
除非應用程式的本質要求作業在啟動後就永遠執行,否則作業應該要終止,終止的方法是達到正常的完成或是透過終止替代方案。對於其主常式是程式庫層套件的作業而言,這也許不可能達成,因為 Ada Reference Manual 並沒有指定它們在什麼狀況下應該要終止。
如果主常式相依結構不容許全新的終止 則作業應提供及等待特殊的關機項目,此項目是在系統關機期間被呼叫。
一般原理是只對錯誤使用異常狀況:邏輯和程式設計錯誤、配置錯誤、 資料毀損、資源耗盡等等。一般規則是系統在正常狀況以及沒有超載或硬體故障的情況下,不應引發任何異常狀況。
使用異常狀況來處理邏輯和程式設計錯誤、
配置錯誤、資料毀損,以及資源耗盡。儘早由適當的記載機制報告異常狀況,包括引發異常狀況的點。
將從給定的抽象化所匯出的異常狀況數減至最少。
在大型系統中,處理每一個層次的大量異常狀況會使程式碼難以判讀及維護。異常狀況處理程序有時會阻礙正常處理程序的進展。
有好幾個方法可以將異常狀況數減至最少:
不要傳送設計中未指定的異常狀況。
避免在異常狀況處理常式中使用 when
others 替代做法,除非捕捉到的異常狀況再度引發。
這讓您進行一些區域的內務處理,而不會干擾在這個層次上所無法處理的異常狀況:
exception when others => if Io.Is_Open (Local_File) then Io.Close (Local_File); end if; raise; end;
另一個可以使用 when others 替代方案的地方是在作業主體的底端。
請勿使用異常狀況來代表頻繁、預期的事件。
在使用異常狀況來代表不是明確錯誤的狀況上,有好幾個不便之處:
例如,請勿使用異常狀況作為函數所傳回的某種形式的額外值(像是搜尋中的 Value_Not_Found);使用帶有 "out" 參數的程序,或採用表示 Not_Found 的特殊值,或是在記錄中將傳回的類型壓縮為區別元件 Not_Found。
請勿使用異常狀況來實作控制結構。
這是上一個規則的特殊情況: 異常狀況不應用來作為一種形式的 "goto" 陳述式。
在捕捉預先定義的異常狀況時,請將處理常式放在一個極小的頁框中,此頁框圍繞引發它的建構。
預先定義的異常狀況(如 Constraint_Error、Storage_Error 等等)可能發生在許多地方。如果基於某特定原因必須捕捉一個這類的異常狀況,處理常式必須盡可能限制在範圍內:
begin Ptr := new Subscriber.Object; exception when Storage_Error => raise Subscriber.Collection_Overflow; end;
以 "return" 陳述式或
"raise" 陳述式終止函數中的異常狀況處理常式。否則呼叫端中會引發 Program_Error 異常狀況。
限制檢查的抑制。
在使用現今的 Ada 編譯器時,藉由抑制檢查所獲得的在程式碼大小上的可能縮減以及在效能上的提升,已變得微不足道。因此,抑制檢查應限於極有限的程式碼片段,這些程式碼被識別為(藉由執行一些測量)為效能瓶頸;絕不應將它廣泛地套用到整個系統。
必然的是,不要只為了有人稍後會決定要抑制檢查這種未必會發生的情況,就加入了額外的明確範圍和區別元件檢查。請根據 Ad 的內建限制核對機能。
請勿將異常狀況傳送到超出它們的宣告範圍外。
這會使用戶端程式碼無法明確地處理異常狀況,而不是使用 when others 替代方案,其可能會不夠確切。
這個規則的必然結果是:舉例而言,當藉由衍生來重新匯出類型時,想一想藉由重新命名來重新匯出衍生的子程式可能引發的異常狀況。否則,用戶端將必須 "with" 原始的定義方套件。
一律一併處理 Numeric_Error 和 Constraint_Error。
Ada Board 已決定所有會引發 Numeric_Error 的情況都應引發 Constraint_Error。
請確定狀態碼具有適當的值。
當使用子程式所傳回的狀態碼作為 "out" 參數時,請務必確定有一個值指定給 "out" 參數,方法為使這個陳述式成為子程式主體中的第一個可執行陳述式。有系統地使所有的狀態依預設成功或失敗。考量離開子程式的所有可能的出口,包括異常狀況處理常式。
在本端環境執行安全檢查;不要期待您的用戶端這麼做。
也就是說,如果子程式在沒有被提供適當的輸入時可能會產生錯誤輸出,請在子程式中安裝程式碼,以受控制的方式來偵測及報告無效的輸入。請勿依賴註解來告知用戶端傳遞適當的值。註解幾乎遲早一定會被忽略,因此若是未偵測到無效的參數,則會導致難以除錯的錯誤。
如需進一步資訊,請參閱 [KR90b]。
本節論及事前不可攜的 Ada 特性。它們定義於 Reference Manual for the Ada Programming Language [ISO87] 的第 13 章,而編譯器特有的特性則說明於 Ada 編譯器供應商所提供的「附錄 F」。
請仔細研讀 Ada Reference Manual 的「附錄 F」(並進行一些小實驗來確定已充分瞭解它)。
限制呈現子句的使用
不同的實作對於呈現子句的支援並不一致。它們的使用含有許多陷阱。因此,不應在系統中任意使用它們。
呈現子句應執行下列動作:
在下類的情況下可以避免使用呈現子句:
範例:
替換:
type Foo is (Bla, Bli, Blu, Blo); for Foo use (Bla => 1, Bli =>3, Blu => 4, Blo => 5);
type Foo is (Invalid_0, Bla, Invalid_2, Bli, Blu, Blo);
將具有呈現子句的類型分組到套件中,這些套件被明確識別為包含相依實作程式碼。
不要假設記錄佈置中的特定順序。
在記錄呈現子句中,請一律指定所有區別元件的佈置,並在變式中指定任何元件之前這麼做。
避免對齊子句。
請信任編譯器會做得很好;它深知目標對齊限制。程式設計師使用對齊子句很可能會導致稍後發生對齊衝突。
請注意,編譯器以未受限的合成類型所產生的欄位之存在性:
在記錄中,動態欄位的偏移、變式子句索引、受限的位元等等。
在陣列中:訊息向量。
請參閱編譯器的附錄 F 以取得詳細資料。請勿依賴 Ada Reference Manual [ISO87] 的第 13 章中所寫的內容。
限制 Unchecked_Conversion 的使用。
Unchecked_Conversion 的支援範圍會隨不同的 Ada 編譯器而有極大的差異,同時其確切的行為可能有些微不同,特別是在套用到合成類型和存取類型時。
在 Unchecked_Conversion 的實例化中,請確定來源類型和目標類型都受到限制,而且大小都相同。
這是唯一能達到某些有限的可攜性,以及避免無意間遇到加入實作之資訊(如訊息向量)問題的方式。有一個確定兩種類型的大小都相同的方式,是利用記錄類型呈現子句將它們「包裝」在記錄類型中。
使類型受限制的一個方式,是在 "skin" 函數內執行實例化,其中限制項已預先計算好。
請勿將
Unchecked_Conversion 套用至存取值或作業。
不但所有的系統都不支援這個動作(例如,Rational 的 native 編譯器),而且不應假設:
我們在這裡扼要重述要注意的一些最重要的事項:
本文件直接衍生自 Ada Guidelines: Recommendations for Designer and Programmers, Application Note #15, Rev. 1.1, Rational, Santa Clara, Ca., 1990. [KR90a]。不過,有許多不同的資料來源促成了這件精心之作。
BAR88 B. Bardin & Ch. Thompson, "Composable Ada Software Components and the Re-export Paradigm", Ada Letters, VIII, 1, Jan.-Feb. 1988, p.58-79.
BOO87 E. G. Booch, Software Components with Ada, Benjamin/Cummings (1987)
BOO91 Grady Booch: Object-Oriented Design with Applications, Benjamin-Cummings Pub. Co., Redwood City, California, 1991, 580p.
BRY87 D. Bryan, "Dear Ada," Ada Letters, 7, 1, January-February 1987, pp. 25-28.
COH86 N. H. Cohen, Ada as a Second Language, McGraw-Hill (1986), pp. 361-362.
EHR89 D. H. Ehrenfried, Tips for the Use of the Ada Language, Application Note #1, Rational, Santa Clara, Ca., 1987.
GAU89 M. Gauthier, Ada-Un Apprentissage, Dunod-Informatique, Paris (1989), pp. 368-370.
GOO88John B. Goodenough and Lui Sha: "The Priority Ceiling Protocol," special issue of Ada Letters, Vol., Fall 1988, pp. 20-31.
HIR92 M. Hirasuna, "Using Inheritance and Polymorphism with Ada in Government Sponsored Contracts", Ada Letters, XII, 2, March/April 1992, p.43-56.
ISO87 Reference Manual for the Ada Programming Language, International Standard ISO 8652:1987.
KR90a Ph. Kruchten, Ada Guidelines: Recommendations for Designer and Programmers, Application Note #15, Rev. 1.1, Rational, Santa Clara, Ca., 1990.
KR90b Ph. Kruchten, "Error-Handling in Large, Object-Based Ada Systems," Ada Letters, Vol. X, No. 7, (Sept. 1990), pp. 91-103.
MCO93 Steve McConnell, Code Complete-A Practical Handbook of Software Construction, Microsoft® Press, Redmond, WA, 1993, 857p.
MEN88 G. O. Mendal, "Three Reasons to Avoid the Use Clause," Ada Letters, 8, 1, January-February 1988, pp. 52-57.
PER88 E. Perez, "Simulating Inheritance with Ada", Ada letters, VIII, 5, Sept.-Oct. 1988, p. 37-46.
PLO92 E. Ploedereder, "How to program in Ada 9X, Using Ada 83", Ada Letters, XII, 6, November 1992, pp. 50-58.
RAC88 R. Racine, "Why the Use Clause Is Beneficial," Ada Letters, 8, 3, May-June 1988, pp. 123-127.
RAD85 T. P. Bowen, G. B. Wigle & J. T. Tsai, Specification of Software Quality Attributes, Boeing Aerospace Company, Rome Air Development Center, Technical Report RADC-TR-85-37 (3 volumes).
ROS87 J. P. Rosen, "In Defense of the Use Clause," Ada Letters, 7, 7, November-December 1987, pp. 77-81.
SEI72 E. Seidewitz, "Object-Oriented Programming with Mixins in Ada", Ada Letters, XII, 2, March/April 1992, p.57-61.
SHA90 Lui Sha and John B. Goodenough: "Real-Time Scheduling Theory and Ada," Computer, Vol. 23, #4 (April 1990), pp. 53-62.)
SPC89 Software Productivity Consortium: Ada Quality and Style-Guidelines for the Professional Programmer, Van Nostrand Reinhold (1989)
TAY92 W. Taylor, Ada 9X Compatibility Guide, Version 0.4, Transition Technology Ltd., Cwmbran, Gwent, U.K., Nov. 1992.
WIC89 B. Wichman: Insecurities in the Ada Programming Language, Report DITC137/89, National Physical Laboratory (UK), January 1989.
本文件中使用的大部分術語都定義於 Reference Manual for the Ada Programming Language ([ISO87]) 的附錄 D。其他的術語定義在這裡:
ADL;Ada 作為設計語言;指的是使用 Ada 來表示設計的各方面的方式;a.k.a、PDL 或 Program Design Language。
環境:使用中的 Ada 軟體開發環境。
程式庫 switch:在 Rational 環境中,適用於整個程式庫的編譯單元。
模型區:Rational 環境中的一個特殊程式庫,用來擷取統一的全專案程式庫 switch 設定。
易變的:其區別元件具有預設值之記錄的內容;可以指派類型的任何值給易變類型的物件,即使是使它變更其區別元件(因此也變更其結構)的值。
外觀:一個子程式,其主體完全當作傳遞來運作。理想上,它僅包含一個陳述式:對另一個子程式的呼叫,具有相同的參數集,或是可以在參數中來回轉換的參數。
PDL:程式設計語言