跳轉到內容

Ada 風格指南/可讀性

來自華夏公益教科書

原始碼展示 · 程式結構

本章建議使用 Ada 特性來使程式碼更易於閱讀和理解。關於註釋和可讀性,有很多誤解。真正的可讀性更多地取決於命名和程式碼結構,而不是註釋。註釋行和程式碼行的數量一樣多並不意味著可讀性;更可能表明作者沒有理解需要傳達什麼。

原始碼中的拼寫約定包括大小寫規則以及下劃線、數字和縮寫的用法。如果您始終如一地遵循這些約定,則生成的程式碼將更清晰、更易讀。

下劃線的用法

[編輯 | 編輯原始碼]
  • 使用下劃線分隔複合名稱中的單詞。
Miles_Per_Hour
Entry_Value

當識別符號包含多個單詞時,如果單詞之間用下劃線分隔,則更易於閱讀。事實上,英語中也有用連字元或空格分隔複合詞的先例。除了提高程式碼可讀性之外,如果在名稱中使用下劃線,程式碼格式化程式就可以更好地控制大小寫更改。請參見指南 3.1.3。

  • 以一致的方式表示數字。
  • 以適合問題的基數表示字面量。
  • 使用下劃線分隔數字,就像在普通文字中使用逗號或句號(或非十進位制基數的空格)一樣。
  • 使用科學計數法時,始終使 E 為大寫或小寫。
  • 在備用基數中,以全大寫或全小寫表示字母字元。

例項化

[編輯 | 編輯原始碼]
  • 十進位制和八進位制數字從基數點左側開始每三位一組,從基數點右側開始每五位一組。
  • 科學記數法中的 E 始終大寫。
  • 大於 10 進位制的數字,用大寫字母表示。
  • 十六進位制數字從基數點兩側開始每四位一組。

示例

[edit | edit source]
type Maximum_Samples     is range          1 ..  1_000_000;
type Legal_Hex_Address   is range   16#0000# ..   16#FFFF#;
type Legal_Octal_Address is range 8#000_000# .. 8#777_777#;

Avogadro_Number : constant := 6.02216_9E+23;

要表示 1/3 作為常量,使用

One_Third : constant := 1.0 / 3.0;

避免使用

One_Third_As_Decimal_Approximation : constant := 0.33333_33333_3333;

One_Third_Base_3 : constant := 3#0.1#;

理由

[edit | edit source]

一致地使用大寫或小寫有助於掃描數字。下劃線用於將數字部分分組為熟悉的模式。與日常使用環境中的通用用法保持一致是可讀性的重要組成部分。

notes

[edit | edit source]

如果一個有理分數在某個基數下表示為有限小數而不是無限迴圈小數,如上例中的 3#0.1#,則在轉換為機器基數後,其精度可能會有所提高。(對於像本例中這樣的命名數字來說,這是錯誤的——它們必須精確計算。)

Capitalization

[edit | edit source]

指南

[edit | edit source]
  • 使保留字和其他程式元素在視覺上彼此區分。

例項化

[edit | edit source]
  • 所有保留字(用作保留字時)用小寫。
  • 所有其他識別符號用混合大小寫,每個單詞以大寫字母開頭,並用下劃線分隔。
  • 縮略語和首字母縮略詞用大寫字母(參見自動化說明)。

示例

[edit | edit source]
...

type Second_Of_Day      is range 0 .. 86_400;
type Noon_Relative_Time is (Before_Noon, After_Noon, High_Noon);

subtype Morning   is Second_Of_Day range 0 .. 86_400 / 2 - 1;
subtype Afternoon is Second_Of_Day range Morning'Last + 2 .. 86_400;

...

Current_Time := Second_Of_Day(Calendar.Seconds(Calendar.Clock));
if Current_Time in Morning then
   Time_Of_Day := Before_Noon;
elsif Current_Time in Afternoon then
   Time_Of_Day := After_Noon;
else
   Time_Of_Day := High_Noon;
end if;

case Time_Of_Day is
   when Before_Noon =>   Get_Ready_For_Lunch;
   when High_Noon   =>   Eat_Lunch;
   when After_Noon  =>   Get_To_Work;
end case;

...

理由

[edit | edit source]

在視覺上區分保留字可以讓你專注於程式結構本身,也可以幫助掃描特定識別符號。

這裡選擇的例項化對於有經驗的 Ada 程式設計師來說更易讀,他們不需要保留字來突出顯示。任何語言的初學者往往會發現保留字應該得到強調,以幫助他們更容易找到控制結構。因此,課堂上的講師和介紹 Ada 語言的書籍可能會考慮使用另一種例項化。Ada 參考手冊 (1995) 選擇將所有保留字以粗體小寫顯示。

automation notes

[edit | edit source]

Ada 名稱不區分大小寫。因此,名稱 max_limitMAX_LIMITMax_Limit 表示同一個物件或實體。一個好的程式碼格式化程式應該能夠在不同風格之間自動轉換,只要單詞以下劃線分隔。

正如指南 3.1.4 中建議的那樣,縮寫應在整個專案中使用。自動化工具應允許專案指定這些縮寫並相應地格式化它們。

Abbreviations

[edit | edit source]

指南

[edit | edit source]
  • 不要在存在更短同義詞的情況下,使用長單詞的縮寫作為識別符號。
  • 使用一致的縮寫策略。
  • 不要使用模稜兩可的縮寫。
  • 為了證明縮寫的必要性,縮寫必須比完整單詞節省許多字元。
  • 使用在應用領域中被廣泛接受的縮寫。
  • 維護一個已接受縮寫的列表,並且只使用該列表中的縮寫。

示例

[edit | edit source]

使用

Time_Of_Receipt

而不是

Recd_Time or R_Time

但在通常處理符合軍用標準的訊息格式的應用程式中,DOD_STD_MSG_FMT 是對

Department_Of_Defense_Standard_Message_Format.

理由

[edit | edit source]

許多縮寫都是模稜兩可的,或者除非在上下文中理解,否則無法理解。例如,Temp 可以表示 temporary 或 temperature。因此,使用縮寫時應謹慎選擇。指南 8.1.2 中的理由更詳細地討論了上下文如何影響縮寫的使用。

由於非常長的變數名可能會掩蓋程式的結構,尤其是在巢狀很深(縮排)的控制結構中,因此儘量保持識別符號簡短且有意義是一個好主意。儘可能使用簡短的非縮寫名稱。如果找不到任何簡短的單詞可以用作識別符號,那麼一個眾所周知的、非模稜兩可的縮寫是次佳選擇,尤其是在它來自整個專案中使用的標準縮寫列表的情況下。

可以使用 renames 子句為完全限定名建立縮寫格式。當一個非常長、完全限定的名稱否則將在程式碼的本地化部分多次出現時,此功能非常有用(參見指南 5.7.2)。

專案中已接受的縮寫列表為使用每個縮寫提供了標準上下文。

Naming Conventions

[edit | edit source]

選擇能說明物件或實體預期用途的名稱。Ada 允許識別符號為任何長度,只要識別符號在一行中可以容納,並且所有字元都有意義(包括下劃線)。識別符號是用於變數、常量、程式單元和其他程式實體的名稱。

Names

[edit | edit source]

指南

[edit | edit source]
  • 選擇儘可能自說明的名稱。
  • 使用簡短的同義詞代替縮寫(參見指南 3.1.4)。
  • 使用應用程式提供的名稱,但不要使用晦澀的術語。
  • 避免使用相同名稱來宣告不同型別的識別符號。

示例

[edit | edit source]

在樹遍歷器中,使用 Left 而不是 Left_Branch 就足以在給定上下文中傳達完整含義。但是,使用 Time_Of_Day 而不是 TOD

數學公式通常使用單字母名稱來表示變數。在數學方程中繼續使用這種約定,這些方程可以回憶公式,例如

   A*(X**2) + B*X + C.

使用子包時,如果包、子單元和識別符號名稱選擇不當,會導致子單元的可見性衝突。有關結果程式碼(相當晦澀)的示例,請參見理由 (1995, §8.1)。

理由

[edit | edit source]

遵循這些指南的程式可以更容易地理解。自說明名稱需要更少的解釋性註釋。實證研究表明,如果變數名稱不至於過長,則可以進一步提高理解力 (Schneiderman 1986, 7)。上下文和應用可以提供很大的幫助。數值實體的度量單位可以作為子型別名稱的來源。

應儘量避免在不同的宣告中使用相同的名稱作為識別符號,例如物件和子包。在看似不同的名稱空間中過度使用識別符號實際上會導致可見性衝突,如果封閉的程式單元旨在協同工作。

notes

[edit | edit source]

有關如何使用應用程式領域作為選擇縮寫的指南,請參見指南 8.1.2。

Subtype Names

[編輯 | 編輯原始碼]
  • 使用單數、通用名詞作為子型別識別符號。
  • 選擇描述子型別之一的值的識別符號。
  • 考慮為定義可見訪問型別、可見子範圍或可見陣列型別的子型別識別符號使用字尾。
  • 對於私有型別,不要使用僅限於子型別識別符號的識別符號結構(例如,字尾)。
  • 不要使用預定義包中的子型別名稱。
type Day is
   (Monday,    Tuesday,   Wednesday, Thursday,  Friday,
    Saturday,  Sunday);

type Day_Of_Month    is range      0 ..    31;
type Month_Number    is range      1 ..    12;
type Historical_Year is range -6_000 .. 2_500;

type Date is
   record
      Day   : Day_Of_Month;
      Month : Month_Number;
      Year  : Historical_Year;
   end record;

特別是,Day 應優先於 DaysDay_Type 使用。

識別符號 Historical_Year 看起來可能很具體,但實際上很通用,其中形容詞 historical 描述了範圍約束。

------------------------------------------------------------------------
procedure Disk_Driver is
 
   -- In this procedure, a number of important disk parameters are
   -- linked.
   Number_Of_Sectors  : constant :=     4;
   Number_Of_Tracks   : constant :=   200;
   Number_Of_Surfaces : constant :=    18;
   Sector_Capacity    : constant := 4_096;

   Track_Capacity   : constant := Number_Of_Sectors  * Sector_Capacity;
   Surface_Capacity : constant := Number_Of_Tracks   * Track_Capacity;
   Disk_Capacity    : constant := Number_Of_Surfaces * Surface_Capacity;

   type Sector_Range  is range 1 .. Number_Of_Sectors;
   type Track_Range   is range 1 .. Number_Of_Tracks;
   type Surface_Range is range 1 .. Number_Of_Surfaces;

   type Track_Map   is array (Sector_Range)  of ...;
   type Surface_Map is array (Track_Range)   of Track_Map;
   type Disk_Map    is array (Surface_Range) of Surface_Map;

begin  -- Disk_Driver
   ...
end Disk_Driver;
------------------------------------------------------------------------

字尾 _Capacity_Range_Map 有助於定義上述子型別的目的,並避免搜尋扇區、軌道和表面抽象的同義詞。如果沒有後綴,您將需要為每個抽象使用三個不同的名稱,每個名稱都簡潔地描述字尾中命名的概念。此建議僅適用於某些可見子型別。例如,私有型別應該使用一個好的名稱來反映正在表示的抽象。

當使用這種風格和建議的物件識別符號風格時,程式程式碼更類似於英語(參見指南 3.2.3)。此外,這種風格與語言的預定義識別符號的名稱一致。它們沒有被命名為 IntegersBooleansInteger_TypeBoolean_Type

但是,使用預定義包中的子型別名稱肯定會讓程式設計師在沒有包限定的情況下看到該子型別時感到困惑。

本樣式指南嘗試在使用“型別”和“子型別”名稱方面與 Ada 參考手冊 (1995) 保持一致。一般來說,“型別”指的是抽象概念,如型別宣告,而“子型別”指的是在實際宣告中給該抽象概念的名稱。因此,在 Ada 83(Ada 參考手冊 1983)中被稱為型別名稱的現在被稱為子型別名稱。

物件名稱

[編輯 | 編輯原始碼]
  • 對布林物件使用謂詞子句或形容詞。
  • 使用單數、具體名詞作為物件識別符號。
  • 選擇描述物件在執行期間的值的識別符號。
  • 使用單數、通用名詞作為記錄元件的識別符號。

非布林物件

Today           : Day;
Yesterday       : Day;
Retirement_Date : Date;

布林物件

User_Is_Available : Boolean;        -- predicate clause
List_Is_Empty     : Boolean;        -- predicate clause
Empty             : Boolean;        -- adjective
Bright            : Boolean;        -- adjective

對物件使用具體名詞為理解物件的值建立了上下文,該值是子型別名稱描述的通用值之一(參見指南 3.2.2)。使用這種風格,物件宣告變得非常類似於英語。例如,上面的第一個宣告被解讀為“今天是一天”。

對記錄元件使用通用名詞,而不是具體名詞,因為記錄物件的名稱將提供理解元件的上下文。因此,以下元件被理解為“退休年份”。

Retirement_Date.Year

遵循將物件型別和詞性聯絡起來的約定使程式碼更像文字。例如,由於選擇了名稱,以下程式碼段不需要註釋。

if List_Is_Empty then
   Number_Of_Elements := 0;
else
   Number_Of_Elements := Length_Of_List;
end if;

如果難以找到一個描述程式整個執行期間物件值的具體名詞,那麼該物件可能在執行多個目的。在這種情況下,應該使用多個物件。

標記型別和關聯包的命名

[編輯 | 編輯原始碼]
  • 對標記型別和關聯包使用一致的命名約定。

例項化

[編輯 | 編輯原始碼]

命名約定引發“宗教戰爭”;因此,提供了兩種不同的例項化。第一個例項化集成了面向物件功能的使用。除了兩個特殊情況外,它對宣告應用相同的命名約定,無論它們是否使用面向物件功能。

  • 以與子型別名稱相同的方式命名標記型別(參見指南 3.2.2)。
  • 對匯出抽象的包使用字首 Abstract_,您打算為該抽象提供多個實現(參見指南 9.2.4)。
  • 對提供可以“混合”到核心抽象中的功能單元的包使用字尾 _Mixin

第二個例項化透過特殊名稱或字尾突出顯示面向物件功能的使用。

  • 以它們所代表的物件命名類包,不帶字尾(Rosen 1995)。
  • 以它們所代表的方面命名混合包,附加字尾 _Facet(Rosen 1995)。
  • 將主要標記型別命名為 Instance(Rosen 1995)。
  • 在特定型別的宣告之後,為相應的類範圍型別使用一個名為 Class 的子型別(Rosen 1995)。

以下來自 Rationale (1995, §§4.4.4 和 4.6.2) 的兩部分示例應用了第一個例項化的命名約定。

對於此示例的第一部分,假設型別 Set_Element 在其他地方宣告。

package Abstract_Sets is

   type Set is abstract tagged private;

   -- empty set
   function Empty return Set is abstract;

   -- build set with 1 element
   function Unit (Element: Set_Element) return Set is abstract;

   -- union of two sets
   function Union (Left, Right: Set) return Set is abstract;

   -- intersection of two sets
   function Intersection (Left, Right: Set) return Set is abstract;

   -- remove an element from a set
   procedure Take (From    : in out Set;
                   Element :    out set_Element) is abstract;

   Element_Too_Large : exception;
private
   type Set is abstract tagged null record;
end Abstract_Sets;

with Abstract_Sets;
package Bit_Vector_Sets is   -- one implementation of set abstraction

   type Bit_Set is new Abstract_Sets.Set with private;
   ...
private
   Bit_Set_Size : constant := 64;
   type Bit_Vector is ...
   type Bit_Set is new Abstract_Sets.Set with
      record
         Data : Bit_Vector;
      end record;
end Bit_Vector_Sets;

with Abstract_Sets;
package Sparse_Sets  -- alternate implementation of set abstraction

   type Sparse_Set is new Abstract_Sets.Set with private;
   ...
private
   ...
end Bit_Vector_Sets;

此示例的第二部分將命名約定應用於支援視窗系統的混合包。

-- assume you have type Basic_Window is tagged limited private;

generic
   type Some_Window is abstract new Basic_Window with private;
package Label_Mixin is 
   type Window_With_Label is abstract new Some_Window with private;
   ...
private
   ...
end Label_Mixin;

generic
   type Some_Window is abstract new Basic_Window with private;
package Border_Mixin is 
   type Window_With_Label is abstract new Some_Window with private;
   ...
private
   ...
end Border_Mixin;

以下示例應用了第二個例項化的命名約定,如 Rosen (1995) 中所述。

package Shape is
   subtype Side_Count is range 0 .. 100;
   type Instance (Sides: Side_Count) is tagged private;
   subtype Class is Instance'Class;
   . . .
   -- operations on Shape.Instance
private
   . . .
end Shape;

with Shape; use Shape;
package Line is
   type Instance is new Shape.Instance with private;
   subtype Class is Instance'Class;
   . . .
   -- Overridden or new operations
private
   . . .
end Line;

with Shape; use Shape;
generic
   type Origin is new Shape.Instance;
package With_Color_Facet is
   type Instance is new Origin with private;
   subtype Class is Instance'Class;
   -- operations for colored shapes
private
   . . .
end With_Color_Facet;

with Line; use Line;
with With_Color_Facet;
package Colored_Line is new With_Color_Facet (Line.Instance);

示例宣告可能如下所示。

Red_Line : Colored_Line.Instance;

procedure Draw (What : Shape.Instance);

無論您使用完整名稱還是 use 子句,上述方案都有效。只要您對所有特定型別(即 type Instance)和類範圍型別使用相同的名稱,非限定名稱將始終相互隱藏。因此,編譯器會要求您使用完整名稱限定來解決 use 子句引入的歧義(Rosen 1995)。

您需要使用一致且可讀的命名方案,並傳達抽象的意圖。理想情況下,命名方案在處理使用標記型別建立類的方式方面應該是統一的。但是,如果命名約定過於嚴格,您將編寫從可讀性角度來看顯得生硬的程式碼片段。透過對透過派生和通用混合進行型別擴充套件使用類似的命名約定(另請參見指南 9.5.1),您可以實現物件和過程的可讀宣告。

notes

[edit | edit source]

用於類的命名約定在面向物件抽象和其他型別的抽象之間劃清了界限。鑑於工程師已經在 Ada 83(Ada 參考手冊 1983)中定義了抽象資料型別超過 10 年,您可能不希望僅僅為了使用型別擴充套件而更改命名約定。您必須考慮在程式中整體使用抽象時,呼叫繼承的用途有多重要。如果您更傾向於強調抽象,而不是實現抽象所使用的機制(即繼承、型別擴充套件和多型性),您可能不想強制執行如此嚴格的命名約定。您不會透過有利於從沒有繼承開發的抽象到有繼承開發的抽象的命名約定更平滑過渡來妨礙質量。

如果您選擇一個突出顯示面向物件功能的使用的命名約定,然後決定將宣告更改為不使用面向物件功能的宣告,則更改可能很昂貴。您必須自然地更改所有名稱的出現,並且必須小心不要在更新名稱時引入錯誤。如果您選擇一個禁止使用字尾或字首來表徵宣告的命名約定,那麼您將失去傳達宣告專案的預期用途的機會。

程式單元名稱

[edit | edit source]

指南

[edit | edit source]
  • 對過程和條目使用動作動詞。
  • 對布林函式使用謂詞子句。
  • 對非布林函式使用名詞。
  • 為包提供暗示比子程式更高級別組織的名稱。通常,這些是描述所提供抽象的名詞短語。
  • 為任務提供暗示活動實體的名稱。
  • 對受保護單元使用描述正在保護的資料的名詞。
  • 考慮將泛型子程式命名為非泛型子程式。
  • 考慮將泛型包命名為非泛型包。
  • 使泛型名稱比例項化名稱更通用。

示例

[edit | edit source]

以下是構成 Ada 程式的元素的示例名稱

示例過程名稱

procedure Get_Next_Token          -- get is a transitive verb
procedure Create                  -- create is a transitive verb

用於布林值函式的示例函式名稱

function Is_Last_Item             -- predicate clause
function Is_Empty                 -- predicate clause

用於非布林值函式的示例函式名稱

function Successor                -- common noun
function Length                   -- attribute
function Top                      -- component

示例包名稱

package Terminals is               -- common noun
package Text_Routines is           -- common noun

示例受保護物件

protected Current_Location is      -- data being protected
protected type Guardian is         -- noun implying protection

示例任務名稱

task Terminal_Resource_Manager is  -- common noun that shows action

以下示例程式碼片段展示了使用詞性命名約定帶來的清晰度

Get_Next_Token(Current_Token);

case Current_Token is
   when Identifier =>         Process_Identifier;
   when Numeric    =>         Process_Numeric;
end case;  -- Current_Token

if Is_Empty(Current_List) then
   Number_Of_Elements := 0;
else
   Number_Of_Elements := Length(Current_List);
end if;

當包及其子程式一起命名時,生成的程式碼非常具有描述性

if Stack.Is_Empty(Current_List) then
   Current_Token := Stack.Top(Current_List);
end if;

理由

[edit | edit source]

使用這些命名約定建立易於理解的程式碼,這些程式碼讀起來很像自然語言。當對動作(如子程式)使用動詞,對物件(如子程式操作的資料)使用名詞時,程式碼更容易閱讀和理解。這模擬了讀者已經熟悉的交流媒介。在程式的各個部分模擬現實生活的情況時,使用這些約定減少了閱讀和理解程式所涉及的翻譯步驟。從某種意義上說,您選擇的名稱反映了從計算機硬體到應用程式需求的抽象級別。

另請參見指南 3.2.4,瞭解在與標記型別關聯的包中使用專用字尾。

notes

[edit | edit source]

目前在使用任務條目時存在一些相互矛盾的約定。一些程式設計師和設計人員主張對任務條目使用與子程式相同的約定進行命名,以模糊任務參與的事實。他們的理由是,如果任務被重新實現為包,反之亦然,則名稱不必更改。另一些人更喜歡儘可能明確地說明任務條目的存在,以確保可以識別具有其假定開銷的任務的存在。專案特定的優先順序可能有助於在這些約定之間進行選擇。

常量和命名數字

[edit | edit source]

指南

[edit | edit source]
  • 在符號值可以提高可讀性的情況下使用符號值而不是文字。
  • 如果值在多個地方出現並且可能需要更改,則使用符號值而不是文字。
  • 對數學常量 Pi 和 e 使用預定義常量 Ada.Numerics.Pi 和 Ada.Numerics.e。
  • 對常數值使用常量而不是變數。
  • 當值特定於型別或當值必須是靜態時使用常量。
  • 儘可能使用命名數字而不是常量。
  • 使用命名數字替換型別或上下文真正通用的數字文字。
  • 對在細化後其值無法更改的物件使用常量(United Technologies 1987)。
  • 透過使用靜態表示式定義它們來顯示符號值之間的關係。
  • 使用線性無關的文字集。
  • 儘可能使用 'First 和 'Last 等屬性而不是文字。

示例

[edit | edit source]
3.14159_26535_89793                                 -- literal
Max_Entries : constant Integer       := 400;        -- constant
Avogadros_Number  : constant := 6.022137 * 10**23;  -- named number
Avogadros_Number / 2                                -- static expression
Avogadros_Number                                    -- symbolic value

Pi 宣告為命名數字(假設在Ada 參考手冊 1995,第 A.5 節[註釋] 中為預定義包 Ada.Numerics 提供 with 子句)允許它在下面的賦值語句中以符號方式引用

Area :=       Pi * Radius**2;       -- if radius is known.

而不是

Area := 3.14159 * Radius**2;        -- Needs explanatory comment

此外,Ada.Characters.Latin_1.BelCharacter'Val(8#007#) 更具表現力。

透過使用其他常量和命名數字,可以提高常量和命名數字宣告的清晰度。例如

Bytes_Per_Page   : constant := 512;
Pages_Per_Buffer : constant := 10;
Buffer_Size      : constant := Pages_Per_Buffer * Bytes_Per_Page;

比以下內容更具自解釋性,更容易維護

Buffer_Size : constant := 5_120;   -- ten pages

以下文字應該是常量

if New_Character  = '$' then  -- "constant" that may change
...
if Current_Column = 7 then    -- "constant" that may change

理由

[edit | edit source]

使用識別符號而不是文字使表示式的目的明確,減少了對註釋的需求。由數字文字表達式組成的常量宣告更安全,因為它們不需要手動計算。它們也比單個數字文字更具啟發性,因為有更多機會嵌入解釋性名稱。透過在定義新常量的靜態表示式中使用其他相關常量,可以進一步提高常量宣告的清晰度。這並不影響效率,因為命名數字的靜態表示式在編譯時計算。

常量具有型別。命名數字只能是通用型別:universal_integer 或 universal_real。常量強制執行強型別,而命名數字或文字則不強制執行。命名數字允許編譯器生成比常量更有效的程式碼,並在編譯時執行更完整的錯誤檢查。如果文字包含大量的數字(如上面的 Pi 示例),使用識別符號可以減少按鍵錯誤。如果出現按鍵錯誤,則可以透過檢查或在編譯時更輕鬆地找到它們。

文字的獨立性意味著使用的少量文字彼此不依賴,並且常量或命名值之間的任何關係都顯示在靜態表示式中。文字值的線性獨立性具有以下屬性:如果一個文字值發生變化,則所有依賴該文字的命名數字值將自動發生變化。

有關選擇無引數函式與常量的更多指南,請參見指南 4.1.4。

notes

[edit | edit source]

在某些情況下,文字比名稱更適合。要滿足這種情況,必須滿足以下條件

  • 文字必須在各自的上下文中具有自解釋性,以便用符號值替換文字不會提高可讀性。
  • 該值要麼不可變,要麼只在程式碼中的一個地方出現,因此用符號值替換文字不會提高可維護性。

例如,在以下眾所周知的表示式中的文字既具有自解釋性又不可變

   Fahrenheit := 32.0 + (9.0 * Celsius) / 5.0;

第二個例子是,在二分查詢演算法的上下文中,除以字面量 2 是不言自明的。並且,由於該值也與演算法不可改變地相關,因此程式碼中字面量出現多次(例如,由於迴圈展開)也不重要。因此,使用如下所示的符號值既不會提高可讀性,也不會提高可維護性。

   Binary_Search_Divisor : constant := 2;
  • 使用一個名稱來指示異常代表的問題型別。
Invalid_Name: exception;
Stack_Overflow: exception;

根據異常檢測的問題型別對其進行命名,可以提高程式碼的可讀性。您應該儘可能精確地命名異常,以便程式碼維護人員瞭解異常可能丟擲的原因。對於宣告異常的包的客戶端而言,一個命名良好的異常應該是意義重大的。

建構函式

[編輯 | 編輯原始碼]
  • 在命名建構函式(在此意義上,指用於建立和/或初始化物件的運算子)時,包含類似 NewMakeCreate 的字首。
  • 對於包含建構函式的子包,請使用反映其內容的名稱。

例項化

[編輯 | 編輯原始碼]
  • 將包含建構函式的子包命名為 <whatever>.Constructor
function Make_Square (Center : Cartesian_Coordinates; 
                      Side   : Positive)
  return Square;

在建構函式名稱中包含類似 NewMakeCreate 的詞語可以使其目的明確。您可能希望將 New 字首的使用限制為返回訪問值的建構函式,因為該字首暗示了分配器的內部使用。

將所有建構函式放在一個子包中,即使它們返回訪問值,也是一個有用的組織原則。

有關 Ada 建構函式使用的資訊,請參閱指南 9.3.3。

原始碼中的註釋是一個有爭議的話題。關於註釋是否提高可讀性,存在著正反兩方面的論點。實際上,註釋最大的問題是人們經常在修改相關原始碼時沒有更新註釋,從而導致註釋誤導人。註釋應該保留用於表達程式碼中無法表達的必要資訊,並突出顯示違反其中一項指南的必要理由。如果可能,原始碼應該使用自解釋的名稱來命名物件和程式單元,並且應該使用簡單易懂的程式結構,以便不需要額外的註釋。選擇(並輸入)適當名稱的額外努力,以及設計簡潔易懂的程式結構所需的額外思考,是完全合理的。

使用註釋來陳述程式碼的意圖。提供程式碼概述的註釋可以幫助維護程式設計師看到森林而不是樹木。程式碼本身是詳細的“如何”,不應該在註釋中進行釋義。

註釋應該儘量減少。它們應該提供程式碼中無法表達的必要資訊,強調程式碼結構,並提請注意對指南的故意和必要的違反。註釋的存在要麼是為了引起對示例中所闡述的真實問題的注意,要麼是為了彌補示例程式中的不完整性。

維護程式設計師需要了解非連續程式碼片段的因果關係,才能對程式有一個全域性的、或多或少完整的認識。他們通常從對程式碼部分的思維模擬中獲得這種資訊。註釋應該足以支援此過程(Soloway 等人,1986)。

本節介紹了關於如何編寫良好註釋的一般指南。然後定義了幾個不同的註釋類別,並提供每個類別的使用指南。這些類別包括檔案頭、程式單元規範頭、程式單元體頭、資料註釋、語句註釋和標記註釋。

一般註釋

[編輯 | 編輯原始碼]
  • 使程式碼儘可能清晰,以減少對註釋的需求。
  • 切勿在註釋中重複程式碼中已有的資訊。
  • 在需要註釋的地方,註釋應該簡潔完整。
  • 在註釋中使用正確的語法和拼寫。
  • 使註釋在視覺上與程式碼區分開來。
  • 對註釋進行結構化,以便工具可以自動提取資訊。

編寫良好的程式碼的結構和功能在沒有註釋的情況下也很清晰。無論註釋如何,難以理解、維護或重用的程式碼都應該被改進,而不是進行解釋。閱讀程式碼本身是確保程式碼功能的唯一方法;因此,程式碼應該儘可能易讀。

使用註釋重複程式碼中的資訊是一個不好的做法,原因有很多。首先,這是不必要的工作,會降低生產效率。其次,在修改程式碼時很難正確維護重複的資訊。當對現有程式碼進行更改時,會對其進行編譯和測試以確保其再次正確。但是,沒有自動機制來確保註釋已正確更新以反映更改。通常,註釋中的重複資訊在第一次程式碼更改時就會變得過時,並在軟體的生命週期中一直保持過時狀態。第三,當從單個子系統作者的有限視角來編寫有關整個系統的註釋時,註釋從一開始就經常是錯誤的。

註釋對於揭示從程式碼中難以或無法獲得的資訊是必要的。本書的後續章節包含此類註釋的示例。完整且簡潔地呈現所需資訊。

註釋的目的是幫助讀者理解程式碼。拼寫錯誤、語法錯誤、含糊不清或不完整的註釋會破壞這個目的。如果值得添加註釋,就應該正確新增以提高其有用性。

透過縮排註釋、將註釋分組到標題中或使用虛線突出顯示註釋,可以使註釋在視覺上與程式碼區分開來,因為這使得程式碼更容易閱讀。本書的後續章節將詳細闡述這一點。

automation notes

[編輯 | 編輯原始碼]

關於在註釋中儲存冗餘資訊的指南僅適用於手動生成的註釋。有一些工具可以自動維護有關程式碼的資訊(例如,呼叫單元、被呼叫單元、交叉引用資訊、修訂歷史記錄等),並將這些資訊儲存在與程式碼位於同一檔案中的註釋中。其他工具讀取註釋但不會更新它們,使用註釋中的資訊自動生成詳細的設計文件和其他報告。

鼓勵使用此類工具,這可能需要您構建頭部註釋,以便它們可以被自動提取和/或更新。注意,修改檔案註釋的工具只有在足夠頻繁地執行時才有用。自動生成的過時資訊比手動生成的過時資訊更危險,因為它更容易被讀者信任。

配置管理工具可以更準確、更完整地維護修訂歷史記錄。沒有工具支援,工程師在進行更改後忘記更新修訂歷史記錄的情況很常見。如果您的配置管理工具能夠在原始檔中維護修訂歷史記錄作為註釋,那麼無論您可能不得不對修訂歷史記錄的格式或位置做出什麼妥協,都應該利用此功能。將完整的修訂歷史記錄附加到檔案末尾比將部分修訂歷史記錄以良好的格式嵌入檔案頭中更好。

檔案頭

[編輯 | 編輯原始碼]
  • 每個原始檔中都需要一個檔案頭。
  • 將檔案的所有權、責任和歷史資訊放在檔案頭中。

例項化

[編輯 | 編輯原始碼]
  • 在檔案頭中新增版權宣告。
  • 在檔案頭中新增作者姓名和部門。
  • 在檔案頭中新增修訂歷史記錄,包括每次更改的摘要、日期和進行更改的人員姓名。
------------------------------------------------------------------------
--      Copyright (c) 1991, Software Productivity Consortium, Inc.
--      All rights reserved.
--
-- Author: J. Smith
-- Department:System Software Department
--
-- Revision History:
--   7/9/91 J. Smith
--     - Added function Size_Of to support queries of node sizes.
--     - Fixed bug in Set_Size which caused overlap of large nodes.
--   7/1/91 M. Jones
--     - Optimized clipping algorithm for speed.
--   6/25/91 J. Smith
--     - Original version.
------------------------------------------------------------------------

如果您想確保保護您對軟體的權利,每個檔案中都應該包含所有權資訊。此外,為了提高可見度,它應該是檔案中第一條資訊。

為了方便未來的維護者,每個檔案中都應該包含責任和修訂歷史記錄資訊;這是維護者最信任的頭資訊,因為它會積累。它不會演變。沒有必要返回並修改作者姓名或檔案的修訂歷史記錄。隨著程式碼的演變,修訂歷史記錄應該更新以反映每次更改。最糟糕的是,它將是不完整的;它應該很少出錯。此外,更改的數量和頻率以及在單元歷史記錄中進行更改的不同人員的數量可以很好地指示實現相對於設計的完整性。

除了作者姓名之外,檔案頭中還應該包含有關如何找到原始作者的資訊,以使維護者在出現問題時更容易找到作者。但是,諸如電話號碼、郵件停止、辦公室號碼和計算機帳戶使用者名稱等詳細資訊過於不穩定,因此沒有太大用處。最好記錄作者編寫程式碼時所在的部門。如果作者搬遷辦公室、更換部門,甚至離開公司,此資訊仍然有用,因為該部門可能會保留對程式碼原始版本的責任。

對於現代配置管理系統,顯式地將版本歷史記錄作為頭註釋捕獲可能是多餘的。配置管理工具維護著更可靠、更一致(從內容角度來看)的更改歷史記錄。某些系統可以重新建立單元的早期版本。

程式單元規範頭

[編輯 | 編輯原始碼]
  • 在每個程式單元的規範中新增一個頭。
  • 將程式單元使用者所需的資訊放在規範頭中。
  • 不要重複規範頭中已有的資訊(單元名稱除外)。
  • 解釋單元的功能,而不是解釋它如何或為何執行該功能。
  • 描述程式單元的完整介面,包括它可能引發的任何異常以及它可能產生的任何全域性影響。
  • 不要包含有關單元如何適應封閉軟體系統的資訊。
  • 描述單元的效能(時間和空間)特徵。

例項化

[編輯 | 編輯原始碼]
  • 在頭中新增程式單元的名稱。
  • 簡要解釋程式單元的目的。
  • 對於包,描述可見子程式彼此之間的影響以及它們應如何一起使用。
  • 列出單元可能引發的所有異常。
  • 列出單元的所有全域性影響。
  • 列出單元的前置條件和後置條件。
  • 列出由單元啟用的隱藏任務。
  • 不要列出子程式的引數名稱。
  • 不要只為了列出它們而列出包子程式的名稱。
  • 不要列出單元使用的所有其他單元的名稱。
  • 不要列出使用該單元的所有其他單元的名稱。
     ------------------------------------------------------------------------
     -- AUTOLAYOUT
     --
     -- Purpose:
     --   This package computes positional information for nodes and arcs
     --   of a directed graph.  It encapsulates a layout algorithm which is
     --   designed to minimize the number of crossing arcs and to emphasize
     --   the primary direction of arc flow through the graph.
     --
     -- Effects:
     --   - The expected usage is:
     --     1. Call Define for each node and arc to define the graph.
     --     2. Call Layout to assign positions to all nodes and arcs.
     --     3. Call Position_Of for each node and arc to determine the
     --        assigned coordinate positions.
     --   - Layout can be called multiple times, and recomputes the
     --     positions of all currently defined nodes and arcs each time.
     --   - Once a node or arc has been defined, it remains defined until
     --     Clear is called to delete all nodes and arcs.
     --
     -- Performance:
     --   This package has been optimized for time, in preference to space.
     --   Layout times are on the order of N*log(N) where N is the number
     --   of nodes, but memory space is used inefficiently.
     ------------------------------------------------------------------------

     package Autolayout is

        ...

        ---------------------------------------------------------------------
        -- Define
        --
        -- Purpose:
        --   This procedure defines one node of the current graph.
        -- Exceptions:
        --   Node_Already_Defined
        ---------------------------------------------------------------------
        procedure Define
              (New_Node : in     Node);

        ---------------------------------------------------------------------
        -- Layout
        --
        -- Purpose:
        --   This procedure assigns coordinate positions to all defined
        --   nodes and arcs.
        -- Exceptions:
        --   None.
        ---------------------------------------------------------------------
        procedure Layout;

        ---------------------------------------------------------------------
        -- Position_Of
        --
        -- Purpose:
        --   This function returns the coordinate position of the
        --   specified node.  The default position (0,0) is returned if no
        --   position has been assigned yet.
        -- Exceptions:
        --   Node_Not_Defined
        ---------------------------------------------------------------------
        function Position_Of (Current : in     Node)
              return Position;

        ...

     end Autolayout;

程式單元規範上的頭註釋的目的是幫助使用者瞭解如何使用程式單元。使用者可以透過閱讀程式單元規範和頭瞭解使用該單元所需的一切。沒有必要閱讀程式單元的主體。因此,每個程式單元規範上都應該有一個頭註釋,並且每個頭都應該包含規範本身未表達的所有使用資訊。此類資訊包括單元彼此之間的影響以及對共享資源的影響、引發的異常以及時間/空間特徵。所有這些資訊都無法從程式單元的 Ada 規範中確定。

當您在頭中重複可以從規範中輕鬆獲得的資訊時,這些資訊在維護過程中往往會變得不正確。例如,在描述過程時,不要特意列出所有引數名稱、模式或子型別。此資訊已從過程規範中獲得。類似地,不要在頭中列出包的所有子程式,除非這樣做對於說明有關子程式的重要宣告是必要的。

不要在頭中包含程式單元使用者不需要的資訊。尤其是,不要包含有關程式單元如何執行其功能或使用特定演算法的原因的資訊。此資訊應隱藏在程式單元的主體中,以保留單元定義的抽象。如果使用者知道這些細節並根據這些資訊做出決策,那麼當這些資訊稍後發生更改時,程式碼可能會受到影響。

在描述單元的目的時,避擴音及封閉軟體系統的其他部分。最好說“該單元執行……”而不是說“該單元被 Xyz 呼叫以執行……”。單元應該以一種不知道或不在乎哪個單元正在呼叫的方式編寫。這使單元更加通用且可重用。此外,有關其他單元的資訊在維護過程中很可能變得過時和不正確。

包含有關單元效能(時間和空間)特徵的資訊。Ada 規範中沒有包含許多這些資訊,但使用者需要它們。為了將單元整合到系統中,使用者需要了解單元的資源使用情況(CPU、記憶體等)。需要注意的是,當子程式呼叫導致在包主體中隱藏的任務啟用時,該任務可能在子程式結束之後繼續消耗資源。

一些專案已將大多數註釋推遲到程式單元的末尾,而不是放在開頭。他們的理由是程式單元只編寫一次,但閱讀多次,並且長的頭註釋會使規範的開頭難以找到。

異常

[edit | edit source]

當一組程式單元密切相關或易於理解時,可以使用單個頭檔案來描述整個程式單元組。例如,使用單個頭檔案來描述 Max 和 Min 函式的行為、Sin、Cos 和 Tan 函式的行為或一組查詢封裝在包中的物件相關屬性的函式是合理的。當集合中的每個函式都能夠引發相同的異常時,這尤其重要。

程式單元體標頭檔案

[edit | edit source]

指南

[edit | edit source]
  • 將程式單元維護人員所需的資訊放在標頭檔案的主體中。
  • 解釋單元如何以及為何執行其功能,而不是解釋單元做什麼。
  • 不要在標頭檔案中重複程式碼中顯而易見的資訊(單元名稱除外)。
  • 不要在主體標頭檔案中重複規範標頭檔案中可用的資訊(單元名稱除外)。

例項化

[edit | edit source]
  • 在頭中新增程式單元的名稱。
  • 在標頭檔案中記錄可移植性問題。
  • 在標頭檔案中總結複雜演算法。
  • 記錄重大或有爭議的實現決策的原因。
  • 記錄已捨棄的實現方案,以及捨棄的原因。
  • 在標頭檔案中記錄預期的更改,尤其是一些更改已經完成,並且已經對程式碼進行了更改以使更改易於完成。

示例

[edit | edit source]
------------------------------------------------------------------------
-- Autolayout
--
-- Implementation Notes:
--   - This package uses a heuristic algorithm to minimize the number
--     of arc crossings.  It does not always achieve the true minimum
--     number which could theoretically be reached.  However it does a
--     nearly perfect job in relatively little time.  For details about
--     the algorithm, see ...
--
-- Portability Issues:
--   - The native math package Math_Lib is used for computations of
--     coordinate positions.
--   - 32-bit integers are required.
--   - No operating system specific routines are called.
--
-- Anticipated Changes:
--   - Coordinate_Type below could be changed from integer to float
--     with little effort.  Care has been taken to not depend on the
--     specific characteristics of integer arithmetic.
------------------------------------------------------------------------
package body Autolayout is

   ...

   ---------------------------------------------------------------------
   -- Define
   --
   -- Implementation Notes:
   --   - This routine stores a node in the general purpose Graph data
   --     structure, not the Fast_Graph structure because ...
   ---------------------------------------------------------------------
   procedure Define
         (New_Node : in     Node) is
   begin
      ...
   end Define;

   ---------------------------------------------------------------------
   -- Layout
   --
   -- Implementation Notes:
   --   - This routine copies the Graph data structure (optimized for
   --     fast random access) into the Fast_Graph data structure
   --     (optimized for fast sequential iteration), then performs the
   --     layout, and copies the data back to the Graph structure.  This
   --     technique was introduced as an optimization when the algorithm
   --     was found to be too slow, and it produced an order of
   --     magnitude improvement.
   ---------------------------------------------------------------------
   procedure Layout is
   begin
      ...
   end Layout;

   ---------------------------------------------------------------------
   -- Position_Of
   ---------------------------------------------------------------------
   function Position_Of (Current : in     Node)
         return Position is
   begin
      ...
   end Position_Of;

   ...

end Autolayout;

理由

[edit | edit source]

程式單元主體上的頭註釋的目的是幫助程式單元的維護人員理解單元的實現,包括不同技術之間的權衡。一定要記錄實現過程中做出的所有決策,以防止維護人員犯同樣的錯誤。對維護人員最有價值的評論之一是對為什麼正在考慮的更改不起作用的清晰描述。

標頭檔案也是記錄可移植性問題的好地方。維護人員可能需要將軟體移植到不同的環境中,因此,他們將受益於一個不可移植功能列表。此外,收集和記錄可移植性問題會集中關注這些問題,並可能從一開始就產生更可移植的程式碼。

如果程式碼難以閱讀或理解,則在標頭檔案中總結複雜演算法,但不要只是將程式碼改寫一遍。這種重複是不必要的,而且難以維護。類似地,不要重複程式單元規範標頭檔案中的資訊。

notes

[edit | edit source]

程式單元通常是自解釋的,因此不需要主體標頭檔案來解釋如何實現或為什麼實現。在這種情況下,完全省略標頭檔案,如上面的 Position_Of 一樣。但是,請確保你省略的標頭檔案確實不包含任何資訊。例如,請考慮以下兩個標頭檔案部分之間的區別

-- Implementation Notes:  None.

-- NonPortable Features:  None.

第一個是作者向維護人員傳送的資訊,表示“我想不出其他要告訴你的資訊”,而第二個可能意味著“我保證這個單元完全可移植”。

資料註釋

[edit | edit source]

指南

[edit | edit source]
  • 除非資料型別、物件和異常的名稱不言自明,否則請對所有資料型別、物件和異常進行註釋。
  • 包括有關複雜、基於指標的資料結構的語義結構的資訊。
  • 包括有關資料物件之間維護的關係的資訊。
  • 省略只是重複名稱中資訊的註釋。
  • 對於標記型別,在打算讓特化(即派生型別)覆蓋這些重新分派操作的情況下,包括有關重新分派的資訊。

示例

[edit | edit source]

可以按目的對物件進行分組並進行註釋,如

...

---------------------------------------------------------------------
-- Current position of the cursor in the currently selected text
-- buffer, and the most recent position explicitly marked by the
-- user.
-- Note:  It is necessary to maintain both current and desired
--        column positions because the cursor cannot always be
--        displayed in the desired position when moving between
--        lines of different lengths.
---------------------------------------------------------------------
Desired_Column : Column_Counter;
Current_Column : Column_Counter;
Current_Row    : Row_Counter;
Marked_Column  : Column_Counter;
Marked_Row     : Row_Counter;

應註釋引發異常的條件

---------------------------------------------------------------------
-- Exceptions
---------------------------------------------------------------------
Node_Already_Defined : exception;   -- Raised when an attempt is made
                                    --|   to define a node with an
                                    --|   identifier which already
                                    --|   defines a node.
Node_Not_Defined     : exception;   -- Raised when a reference is
                                    --|   made to a node which has
                                    --|   not been defined.

以下是一個更復雜的示例,它涉及多個記錄型別和訪問型別,這些型別用於形成複雜的資料結構

---------------------------------------------------------------------
-- These data structures are used to store the graph during the
-- layout process. The overall organization is a sorted list of
-- "ranks," each containing a sorted list of nodes, each containing
-- a list of incoming arcs and a list of outgoing arcs.
-- The lists are doubly linked to support forward and backward
-- passes for sorting. Arc lists do not need to be doubly linked
-- because order of arcs is irrelevant.
--
-- The nodes and arcs are doubly linked to each other to support
-- efficient lookup of all arcs to/from a node, as well as efficient
-- lookup of the source/target node of an arc.
---------------------------------------------------------------------

type Arc;
type Arc_Pointer is access Arc;

type Node;
type Node_Pointer is access Node;

type Node is
   record
      Id       : Node_Pointer;-- Unique node ID supplied by the user.
      Arc_In   : Arc_Pointer;
      Arc_Out  : Arc_Pointer;
      Next     : Node_Pointer;
      Previous : Node_Pointer;
   end record;

type Arc is
   record
      ID     : Arc_ID;        -- Unique arc ID supplied by the user.
      Source : Node_Pointer;
      Target : Node_Pointer;
      Next   : Arc_Pointer;
   end record;

type Rank;
type Rank_Pointer is access Rank;

type Rank is
   record
      Number     : Level_ID;  -- Computed ordinal number of the rank.
      First_Node : Node_Pointer;
      Last_Node  : Node_Pointer;
      Next       : Rank_Pointer;
      Previous   : Rank_Pointer;
   end record;

First_Rank : Rank_Pointer;
Last_Rank  : Rank_Pointer;

理由

[edit | edit source]

添加註釋來解釋資料結構的用途、結構和語義非常有用。許多維護人員在嘗試瞭解單元的實現時,首先檢視資料結構。瞭解可以儲存的資料以及不同資料項之間的關係以及資料在單元中的流動,是瞭解單元詳細資訊的重要第一步。

在上面的第一個示例中,Current_Column 和 Current_Row 的名稱相對不言自明。Desired_Column 的名稱也很好,但它讓讀者想知道當前列和所需列之間的關係。註釋解釋了同時擁有兩者的原因。

對資料宣告進行註釋的另一個好處是,宣告上的單個註釋集可以替換程式碼中其他地方可能需要的多個註釋集。在上面的第一個示例中,註釋簡要擴充套件了“當前”和“標記”的含義。它指出“當前”位置是游標的位置,“當前”位置在當前緩衝區中,而“標記”位置是使用者標記的位置。此註釋以及變數的助記符名稱,大大減少了在整個程式碼中的單個語句中進行註釋的需求。

重要的是要記錄異常的完整含義以及它們在什麼條件下會被引發,如上面的第二個示例所示,尤其是在包規範中宣告異常時。讀者別無選擇,只能透過閱讀包主體中的程式碼來了解異常的確切含義。

將所有異常分組在一起,如第二個示例所示,可以為讀者提供“術語表”的效果。當包中的許多不同的子程式可以引發相同的異常時,這很有用。對於每個異常只能被包中的一個子程式引發的包,將相關的子程式和異常分組在一起可能更好。

註釋異常時,最好用一般術語描述異常的含義,而不是列出所有可能導致引發異常的子程式;這樣的列表更難維護。當新增新例程時,這些列表很可能不會更新。此外,此資訊已存在於描述子程式的註釋中,這些註釋應列出子程式可能引發的所有異常。按子程式列出的異常列表比按異常列出的子程式列表更有用,也更易於維護。

在第三個示例中,記錄欄位的名稱很短,而且是助記符,但它們並不完全不言自明。對於涉及訪問型別的複雜資料結構,情況往往如此。沒有辦法選擇記錄和欄位名稱,以便它們完全解釋記錄和指標到一組巢狀的排序列表中的總體組織。顯示的註釋在這種情況下很有用。沒有它們,讀者將不知道哪些列表是排序的,哪些列表是雙向連結的,或者為什麼。註釋表達了作者對這種複雜資料結構的意圖。維護人員仍然必須閱讀程式碼,如果他想確保所有雙向連結都得到正確的維護。在閱讀程式碼時牢記這一點,可以使維護人員更容易找到一個指標被更新而另一個指標沒有被更新的錯誤。

有關記錄重新分派操作使用情況的理由,請參見指南 9.3.1。(重新分派是指將一個基本操作的引數轉換為一個類寬型別,並對另一個基本操作進行分派呼叫。)指南 9.3.1 中的理由討論了這種文件是否應該放在規範中還是主體中。

語句註釋

[edit | edit source]

指南

[edit | edit source]
  • 儘量減少嵌入語句中的註釋。
  • 僅使用註釋來解釋程式碼中不明顯的部分。
  • 對程式碼中故意省略的部分進行註釋。
  • 不要使用註釋來解釋程式碼。
  • 不要使用註釋來解釋遠端程式碼片段,例如當前單元呼叫的子程式。
  • 在需要註釋的地方,使其在視覺上與程式碼區分開來。

示例

[edit | edit source]

以下是一個註釋非常糟糕的程式碼示例

...

-- Loop through all the strings in the array Strings, converting
-- them to integers by calling Convert_To_Integer on each one,
-- accumulating the sum of all the values in Sum, and counting them
-- in Count.  Then divide Sum by Count to get the average and store
-- it in Average. Also, record the maximum number in the global
-- variable Max_Number.

for I in Strings'Range loop
   -- Convert each string to an integer value by looping through
   -- the characters which are digits, until a nondigit is found,
   -- taking the ordinal value of each, subtracting the ordinal value
   -- of '0', and multiplying by 10 if another digit follows.  Store
   -- the result in Number.
   Number := Convert_To_Integer(Strings(I));
   -- Accumulate the sum of the numbers in Total.
   Sum := Sum + Number;
   -- Count the numbers.
   Count := Count + 1;

   -- Decide whether this number is more than the current maximum.
   if Number > Max_Number then
      -- Update the global variable Max_Number.
      Max_Number := Number;
   end if;

end loop;
-- Compute the average.
Average := Sum / Count;

以下透過不重複程式碼中顯而易見的註釋內容,不描述Convert_To_Integer內部的細節,刪除錯誤的註釋(累加總和語句上的註釋),並使剩餘的幾個註釋在視覺上與程式碼區分開來,得到了改進。

Sum_Integers_Converted_From_Strings:
   for I in Strings'Range loop
      Number := Convert_To_Integer(Strings(I));
      Sum := Sum + Number;
      Count := Count + 1;

      -- The global Max_Number is computed here for efficiency.
      if Number > Max_Number then
         Max_Number := Number;
      end if;

   end loop Sum_Integers_Converted_From_Strings;

Average := Sum / Count;

理由

[edit | edit source]

示例中顯示的改進,不僅僅是透過減少註釋總數來實現的;它們是透過減少無用註釋的數量來實現的。

解釋程式碼的明顯方面的註釋毫無價值。它們浪費了作者的寫作時間和維護人員的更新時間。因此,它們往往最終會變得不正確。此類註釋還會使程式碼混亂,隱藏了少數重要的註釋。

描述另一個單元內部發生的事情的註釋違反了資訊隱藏原則。關於Convert_To_Integer的細節(上面已刪除)與呼叫單元無關,最好隱藏起來,以防演算法發生變化。解釋程式碼中其他地方發生的事情的示例非常難以維護,並且幾乎總是在第一次程式碼修改時就變得不正確。

使註釋在視覺上與程式碼區分開來的好處在於,它使程式碼更容易掃描,並且少數重要的註釋更容易突出。突出顯示不尋常或特殊的程式碼特徵表明它們是故意的。這透過將注意力集中在維護或將程式移植到另一個實現時可能導致問題的程式碼部分,幫助維護人員。

註釋應用於記錄不可移植、實現相關、環境相關或以任何方式棘手的程式碼。它們通知讀者,一些不尋常的內容在那裡是有原因的。一個有益的註釋是解釋編譯器錯誤解決方法的註釋。如果您使用的是更低級別的(在軟體工程意義上不是“理想的”)解決方案,請對其進行註釋。註釋中包含的資訊應說明您為什麼使用該特定結構。還應包含有關失敗嘗試的文件,例如,使用更高級別的結構。這種註釋對於維護人員來說,具有歷史意義。您向讀者展示了在選擇結構時,已經進行了一系列認真的思考。

最後,註釋應用於解釋程式碼中不存在的內容,以及存在的內容。如果您做出了不執行某些操作的決定,例如釋放您似乎已經完成的資料結構,請務必添加註釋解釋為什麼不這樣做。否則,維護人員可能會注意到明顯的遺漏,並在以後對其進行“更正”,從而引入錯誤。

另請參見準則 9.3.1,瞭解有關您應提供的有關標記型別和重新分派的文件型別的討論。

notes

[edit | edit source]

可以透過在區域性塊中宣告變數CountSum,使它們的範圍受限,並且它們的初始化在使用附近發生,從而對上述示例進行進一步改進,例如,透過命名塊Compute_Average或透過將程式碼移至名為Average_Of的函式中。Max_Number的計算也可以與Average的計算分離。但是,這些更改是其他準則的主題;本示例僅用於說明註釋的正確用法。

標記註釋

[edit | edit source]

指南

[edit | edit source]
  • 使用分頁標記來標記程式單元邊界(請參見準則 2.1.7)。
  • 如果begin之前有宣告,則在註釋中重複單元名稱以標記包體、子程式體、任務體或塊的begin
  • 對於長或巢狀很深的ifcase語句,使用註釋標記語句的結尾,以總結控制語句的條件。
  • 對於長或巢狀很深的if語句,使用註釋標記else部分,以總結控制此部分語句的條件。

示例

[edit | edit source]
if    A_Found then
   ...
elsif B_Found then
   ...

else  -- A and B were both not found
   ...

   if Count = Max then
      ...

   end if;

   ...
end if;  -- A_Found

------------------------------------------------------------------------
package body Abstract_Strings is
   ...

   ---------------------------------------------------------------------
   procedure Concatenate (...) is
   begin
      ...
   end Concatenate;
   ---------------------------------------------------------------------

   ...
begin  -- Abstract_Strings
   ...
end Abstract_Strings;
------------------------------------------------------------------------

理由

[edit | edit source]

標記註釋強調程式碼的結構,並使其更容易掃描。它們可以是用於分離程式碼部分的行,或用於構造的描述性標籤。它們幫助讀者解決關於程式碼中當前位置的問題。對於大型單元來說,這比小型單元更重要。一個簡短的標記註釋可以與它關聯的保留字放在同一行。因此,它可以新增資訊而不會造成混亂。

if語句的ifelsifelseend if通常由長語句序列隔開,有時會涉及其他if語句。如第一個示例所示,標記註釋在很長的視覺距離內強調了同一語句的關鍵字之間的關聯。對於塊語句和迴圈語句,標記註釋不是必需的,因為這些語句的語法允許它們使用在結尾重複的名稱進行命名。使用這些名稱比使用標記註釋更好,因為編譯器會驗證開頭和結尾處的名稱是否匹配。

包體的語句序列通常與包的第一行相距很遠。許多子程式體,每個子程式體都包含許多begin行,可能首先出現。如第二個示例所示,標記註釋強調了begin與包的關聯。

notes

[edit | edit source]

如果過度使用,重複名稱和註釋條件表示式會使程式碼混亂。正是視覺距離,尤其是分頁符,使標記註釋變得有益。

使用型別

[edit | edit source]

強型別化在軟體中促進了可靠性。物件的型別定義定義了所有合法的值和操作,並允許編譯器在編譯期間檢查和識別潛在錯誤。此外,型別規則允許編譯器生成程式碼來檢查執行期間對型別約束的違反。與型別較弱的語言相比,使用這些 Ada 編譯器的功能可以實現更早、更完整的錯誤檢測。

宣告型別

[edit | edit source]

指南

[edit | edit source]
  • 儘可能限制標量型別的範圍。
  • 從應用程式中查詢有關可能值的資訊。
  • 不要重用包Standard中的任何子型別名稱。
  • 使用子型別宣告來提高程式可讀性(Booch 1987)。
  • 將派生型別和子型別結合使用(請參見準則 5.3.1)。

示例

[edit | edit source]
subtype Card_Image is String (1 .. 80);
Input_Line : Card_Image := (others => ' ');
-- restricted integer type:
type    Day_Of_Leap_Year     is                  range 1 .. 366;
subtype Day_Of_Non_Leap_Year is Day_Of_Leap_Year range 1 .. 365;

透過以下宣告,程式設計師的意思是,“我完全不知道有多少”,但實際的基本範圍將出現在程式碼中或作為系統引數被隱藏。

Employee_Count : Integer;

理由

[edit | edit source]

從合法範圍內消除無意義的值可以提高編譯器在物件設定為無效值時檢測錯誤的能力。這也提高了程式的可讀性。此外,它迫使您仔細考慮對宣告為子型別物件的每個使用。

不同的實現為大多數預定義型別提供了不同的值集。讀者無法從預定義名稱中確定預期的範圍。當預定義名稱被過載時,這種情況會更加嚴重。

物件及其子型別的名稱可以闡明其預期的用途,並記錄低階設計決策。上面的示例記錄了一個設計決策,即將軟體限制為物理引數源自穿孔卡特性的裝置。此資訊很容易找到,以便進行任何後續更改,從而增強了程式的可維護性。

可以透過宣告沒有約束的子型別來重新命名型別(Ada 參考手冊 1995,第 8.5 節 [註釋])。不能過載子型別名稱;過載僅適用於可呼叫實體。列舉文字被視為無引數函式,因此包含在此規則中。

型別可以具有高度受限的值集,而不會消除有用的值。如準則 5.3.1 中所述的用法消除了可執行語句中的許多標誌變數和型別轉換。這使得程式更易讀,同時允許編譯器強制執行強型別約束。

notes

[edit | edit source]

子型別宣告不會定義新的型別,而只是為現有型別定義約束。

任何偏離此準則的行為都會損害 Ada 語言強型別設施的優勢。

異常

[edit | edit source]

在某些情況下,您不依賴任何數值範圍。例如,在陣列索引中會出現這種情況(例如,大小不受任何特定語義限制的列表)。有關預定義型別的適當用法的討論,請參見準則 7.2.1。

列舉型別

[edit | edit source]

指南

[edit | edit source]
  • 使用列舉型別而不是數字程式碼。
  • 只有在絕對必要時,才使用表示子句來匹配外部裝置的要求。

示例

[edit | edit source]

使用

type Color is (Blue, Red, Green, Yellow);

而不是

Blue   : constant := 1;
Red    : constant := 2;
Green  : constant := 3;
Yellow : constant := 4;

並在必要時新增以下內容

for Color use (Blue   => 1,
               Red    => 2,
               Green  => 3,
               Yellow => 4);

理由

[edit | edit source]

列舉型別比數字程式碼更健壯;它們減少了因解釋錯誤以及維護期間的值集新增和刪除而導致的錯誤的可能性。數字程式碼是來自沒有使用者定義型別的語言的遺留物。

此外,Ada 為列舉型別提供了許多屬性('Pos、'Val、'Succ、'Pred、'Image 和 'Value),這些屬性在使用時比使用者編寫的編碼操作更可靠。

數字程式碼可能乍一看似乎適合匹配外部值。相反,這些情況需要對列舉型別使用表示子句。表示子句記錄了“編碼”。如果程式結構合理,以便隔離和封裝硬體依賴項(參見準則 7.1.5),數字程式碼最終會出現在介面包中,在那裡可以輕鬆找到和替換,以防需求發生變化。

一般來說,避免對列舉型別使用表示子句。如果沒有列舉文字的明顯排序,如果必須重新排序列舉型別以適應新平臺上的表示順序變化,則列舉表示可能會導致可移植性問題。

總結

[edit | edit source]

拼寫

[edit | edit source]
  • 使用下劃線分隔複合名稱中的單詞。
  • 以一致的方式表示數字。
  • 以適合問題的基數表示字面量。
  • 使用下劃線分隔數字,就像在普通文字中使用逗號或句號(或非十進位制基數的空格)一樣。
  • 使用科學計數法時,始終使 E 為大寫或小寫。
  • 在備用基數中,以全大寫或全小寫表示字母字元。
  • 使保留字和其他程式元素在視覺上彼此區分。
  • 不要在存在更短同義詞的情況下,使用長單詞的縮寫作為識別符號。
  • 使用一致的縮寫策略。
  • 不要使用模稜兩可的縮寫。
  • 為了證明縮寫的必要性,縮寫必須比完整單詞節省許多字元。
  • 使用在應用領域中被廣泛接受的縮寫。
  • 維護一個已接受縮寫的列表,並且只使用該列表中的縮寫。

命名約定

[edit | edit source]
  • 選擇儘可能自說明的名稱。
  • 使用簡短的同義詞而不是縮寫。
  • 使用應用程式提供的名稱,但不要使用晦澀的術語。
  • 避免使用相同名稱來宣告不同型別的識別符號。
  • 使用單數、通用名詞作為子型別識別符號。
  • 選擇描述子型別之一的值的識別符號。
  • 考慮為定義可見訪問型別、可見子範圍或可見陣列型別的子型別識別符號使用字尾。
  • 對於私有型別,不要使用僅限於子型別識別符號的識別符號結構(例如,字尾)。
  • 不要使用預定義包中的子型別名稱。
  • 對布林物件使用謂詞子句或形容詞。
  • 使用單數、具體名詞作為物件識別符號。
  • 選擇描述物件在執行期間的值的識別符號。
  • 使用單數、通用名詞作為記錄元件的識別符號。
  • 對標記型別和關聯包使用一致的命名約定。
  • 對過程和條目使用動作動詞。
  • 對布林函式使用謂詞子句。
  • 對非布林函式使用名詞。
  • 為包提供暗示比子程式更高級別組織的名稱。通常,這些是描述所提供抽象的名詞短語。
  • 為任務提供暗示活動實體的名稱。
  • 對受保護單元使用描述正在保護的資料的名詞。
  • 考慮將泛型子程式命名為非泛型子程式。
  • 考慮將泛型包命名為非泛型包。
  • 使泛型名稱比例項化名稱更通用。
  • 儘可能使用符號值而不是文字。
  • 對數學常量 Pi 和 e 使用預定義常量 Ada.Numerics.Pi 和 Ada.Numerics.e。
  • 對常數值使用常量而不是變數。
  • 當值特定於型別或當值必須是靜態時使用常量。
  • 儘可能使用命名數字而不是常量。
  • 使用命名數字替換型別或上下文真正通用的數字文字。
  • 對於在詳細說明後其值無法更改的物件,使用常量。(聯合技術公司 1987 年)。
  • 透過使用靜態表示式定義它們來顯示符號值之間的關係。
  • 使用線性無關的文字集。
  • 儘可能使用 'First 和 'Last 等屬性而不是文字。
  • 使用一個名稱來指示異常代表的問題型別。
  • 在命名建構函式(從這個意義上講,用於建立和/或初始化物件的運算)時,包含 New、Make 或 Create 之類的字首。
  • 對於包含建構函式的子包,請使用反映其內容的名稱。

註釋

[edit | edit source]
  • 使程式碼儘可能清晰,以減少對註釋的需求。
  • 切勿在註釋中重複程式碼中已有的資訊。
  • 在需要註釋的地方,註釋應該簡潔完整。
  • 在註釋中使用正確的語法和拼寫。
  • 使註釋在視覺上與程式碼區分開來。
  • 在標題中構建註釋,以便工具可以自動提取資訊。
  • 每個原始檔中都需要一個檔案頭。
  • 將檔案的所有權、責任和歷史資訊放在檔案頭中。
  • 在每個程式單元的規範中新增一個頭。
  • 將程式單元使用者所需的資訊放在規範頭中。
  • 不要重複規範頭中已有的資訊(單元名稱除外)。
  • 解釋單元的功能,而不是解釋它如何或為何執行該功能。
  • 描述程式單元的完整介面,包括它可能引發的任何異常以及它可能產生的任何全域性影響。
  • 不要包含有關單元如何適應封閉軟體系統的資訊。
  • 描述單元的效能(時間和空間)特徵。
  • 將程式單元維護人員所需的資訊放置在主體標題中。
  • 解釋單元如何以及為何執行其功能,而不是解釋單元做什麼。
  • 不要在標頭檔案中重複程式碼中顯而易見的資訊(單元名稱除外)。
  • 不要在主體標頭檔案中重複規範標頭檔案中可用的資訊(單元名稱除外)。
  • 除非資料型別、物件和異常的名稱不言自明,否則請對所有資料型別、物件和異常進行註釋。
  • 包括有關複雜、基於指標的資料結構的語義結構的資訊。
  • 包括有關資料物件之間維護的關係的資訊。
  • 省略只是重複名稱中資訊的註釋。
  • 對於標記型別,在打算讓特化(即派生型別)覆蓋這些重新分派操作的情況下,包括有關重新分派的資訊。
  • 儘量減少嵌入語句中的註釋。
  • 僅使用註釋來解釋程式碼中不明顯的部分。
  • 對程式碼中故意省略的部分進行註釋。
  • 不要使用註釋來解釋程式碼。
  • 不要使用註釋來解釋遠端程式碼片段,例如當前單元呼叫的子程式。
  • 在需要註釋的地方,使其在視覺上與程式碼區分開來。
  • 使用分頁標記來標記程式單元邊界。
  • 如果 begin 之前有宣告,則在註釋中重複單元名稱以標記包體、子程式體、任務體或塊的開頭。
  • 對於長或嚴重巢狀的 if 和 case 語句,使用註釋標記語句的結尾,總結控制語句的條件。
  • 對於長或嚴重巢狀的 if 語句,使用註釋標記 else 部分,總結控制此部分語句的條件。

使用型別

[edit | edit source]
  • 儘可能限制標量型別的範圍。
  • 從應用程式中查詢有關可能值的資訊。
  • 不要重用包Standard中的任何子型別名稱。
  • 使用子型別宣告來提高程式可讀性(Booch 1987)。
  • 協同使用派生型別和子型別。
  • 使用列舉型別而不是數字程式碼。
  • 只有在絕對必要時,才使用表示子句來匹配外部裝置的要求。

程式結構

華夏公益教科書