跳轉到內容

Ada 樣式指南/原始碼展示

來自 Wikibooks,開放世界中的開放書籍

簡介 · 可讀性

原始碼在頁面或螢幕上的物理佈局對程式碼的可讀性有很大影響。本章包含原始碼展示指南,旨在使程式碼更易讀。

除了通用指南之外,"例項化"部分還提供了一些具體建議。如果您不同意這些具體建議,您可能需要採用自己的約定,這些約定仍然遵循通用指南。最重要的是,在整個專案中保持一致。

完全一致的佈局很難手動實現或檢查。因此,您可能更喜歡使用引數化程式碼格式化工具來自動化佈局,或者將指南整合到自動編碼模板中。本章中介紹的一些指南和具體建議無法透過格式化工具強制執行,因為它們基於 Ada 程式碼的語義,而不是語法。更多詳細資訊將在"自動化說明"部分提供。

程式碼格式

[編輯 | 編輯原始碼]

Ada 原始碼的"程式碼格式"影響程式碼的外觀,而不是程式碼的功能。這裡包含的主題包括水平間距、縮排、對齊、分頁和行長。最重要的指南是在整個編譯單元以及整個專案中保持一致。

水平間距

[編輯 | 編輯原始碼]
  • 在分隔符周圍使用一致的間距。
  • 使用與普通散文相同的間距。

例項化

[編輯 | 編輯原始碼]

具體來說,在以下位置留至少一個空格,如本書中的示例所示。後續指南中推薦的垂直對齊可能需要更多空格。

  • 在以下分隔符和二元運算子之前和之後
  + - * / &
  < = > /= <= >=
  := => | ..
  :
  <>
  • 在字串(")和字元(')文字的引號外部,除了禁止的地方。
  • 在括號外部,但不在括號內部。
  • 在逗號(,)和分號(;)之後。

不要在以下位置留任何空格,即使這與上述建議衝突。

  • 在作為一元運算子使用的加號(+)和減號(-)之後。
  • 在函式呼叫之後。
  • 在標籤分隔符(<< >>)內部。
  • 在指數運算子(**)、撇號(')和句點(.)之前和之後
  • 在多個連續的左括號或右括號之間。
  • 在逗號(,)和分號(;)之前。

當由於運算子優先順序規則省略了多餘的括號時,可以可選地在該表示式中圍繞最高優先順序運算子刪除空格。

Default_String : constant String :=
      "This is the long string returned by" &
      " default. It is broken into multiple" &
      " Ada source lines for convenience.";

type Signed_Whole_16 is range -2**15 .. 2**15 - 1;
type Address_Area  is array (Natural range <>) of Signed_Whole_16;

Register : Address_Area (16#7FF0# .. 16#7FFF#);
Memory   : Address_Area (       0 .. 16#7FEC#);

Register (Pc) := Register (A);

X := Signed_Whole_16 (Radius * Sin (Angle));

Register (Index) := Memory (Base_Address + Index * Element_Length);

Get (Value => Sensor);

Error_Term := 1.0 - (Cos (Theta)**2 + Sin (Theta)**2);

Z      := X**3;
Y      := C * X + B;
Volume := Length * Width * Height;

在分隔符和運算子周圍使用空格是一個好主意,因為它們通常是短序列(一兩個字元),很容易在較長的關鍵字和識別符號中迷失。在它們周圍新增空格可以使它們突出顯示。間距的一致性也有助於使原始碼更容易視覺掃描。

但是,許多分隔符(逗號、分號、括號等)在普通文字中是熟悉的標點符號。在計算機程式中看到它們與普通文字中不同的間距會令人分心。因此,使用與文字中相同的間距(逗號和分號之前沒有空格,括號內部沒有空格等)。

一個值得注意的例外是冒號(:)。在 Ada 中,將冒號用作製表符或列分隔符非常有用(參見指南 2.1.4)。在這種情況下,在冒號前後新增空格,而不是像普通文字那樣只在冒號之後新增空格是有意義的。

自動化說明

[編輯 | 編輯原始碼]

本節中的指南可以使用自動程式碼格式化工具輕鬆強制執行。

  • 對巢狀控制結構、續行和嵌入單元進行一致的縮排和對齊。
  • 區分巢狀控制結構和續行的縮排。
  • 使用空格進行縮排,而不是製表符(Nissen 和 Wallis 1984,第 2.2 節)。

例項化

[編輯 | 編輯原始碼]

具體來說,建議使用以下縮排約定,如本書中的示例所示。請注意,此處描述的是最小縮排。後續指南中建議的垂直對齊可能需要更多空格。

  • 使用 Ada 語言參考手冊(1995)中顯示的推薦段落格式。
  • 使用三個空格作為巢狀的縮排基本單位。
  • 使用兩個空格作為續行的縮排基本單位。

標籤向外縮排三個空格

begin
<<label>>
   <statement>
end;
<long statement with line break>
  <trailing part of same statement>

if 語句和普通迴圈

if <condition> then
   <statements>
elsif <condition> then
   <statements>
else
   <statements>
end if;
<name>:
loop
   <statements>
   exit when <condition>;
   <statements>
end loop <name>;

使用 for 和 while 迭代方案的迴圈

<name>:
   for <scheme> loop
      <statements>
   end loop <name>;
<name>:
   while <condition> loop
      <statements>
   end loop <name>;

塊語句和 case 語句,如 Ada 語言參考手冊(1995)中推薦的

<name>:
   declare
      <declarations>
   begin
      <statements>
   exception
      when <choice> =>
         <statements>
      when others =>
         <statements>
   end <name>;
case <expression> is
   when <choice> =>
      <statements>
   when <choice> =>
      <statements>
   when others =>
      <statements>
end case;  --<comment>

這些 case 語句節省了與 Ada 語言參考手冊(1995)推薦相比的空間,並且分別依賴於非常短的語句列表。無論您選擇哪種方式,請保持一致。

case <expression> is
when <choice> =>
     <statements>
when <choice> =>
     <statements>
when others =>
     <statements>
end case;
case <expression> is
   when <choice> => <statements>
                    <statements>
   when <choice> => <statements>
   when others   => <statements>
end case;

各種形式的選擇性 accept 語句以及定時和條件入口呼叫

select
   when <guard> =>
      <accept statement>
      <statements>
or
   <accept statement>
   <statements>
or
   when <guard> =>
      delay <interval>;
      <statements>
or
   when <guard> =>
      terminate;
else
   <statements>
end select;
select
   <entry call>;
   <statements>
or
   delay <interval>;
   <statements>
end select;

select
   <entry call>;
   <statements>
else
   <statements>
end select;

select
   <triggering alternative>
then abort
   <abortable part>
end select;

accept 語句

accept <specification> do
   <statements>
end <name>;
separate (<parent unit>)
<proper body>

子單元

separate (<parent unit>)
<proper body>
end <name>;

程式單元的適當主體

procedure <specification> is
   <declarations>
begin
   <statements>
exception
   when <choice> =>
      <statements>
end <name>;

function <specification>
  return <type name> is
   <declarations>
begin
   <statements>
exception
   when <choice> =>
      <statements>
end <name>;
package body <name> is
   <declarations>
begin
   <statements>
exception
   when <choice>=>
      <statements>
end <name>;

task body <name> is
   <declarations>
begin
   <statements>
exception
   when <choice>=>
      <statements>
end <name>;

編譯單元上的上下文子句以表格形式排列。泛型形式引數不會遮蔽單元本身。函式、包和任務規範使用標準縮排。

with <name>; use <name>;
with <name>;
with <name>;

<compilation unit>

generic
   <formal parameters>
<compilation unit>
function <specification>
  return <type>;

package <name> is
   <declarations>
private
   <declarations>
end <name>;

task type <name> is
   <entry declarations>
end <name>;

泛型單元的例項化和記錄縮排

procedure <name> is
   new <generic name> <actuals>

function <name> is
   new <generic name> <actuals>

package <name> is
   new <generic name> <actuals>
type ... is
   record
      <component list>
      case <discriminant name> is
         when <choice> =>
            <component list>
         when <choice> =>
            <component list>
      end case;
   end record;

記錄對齊的縮排

for <name> use
   record <mod clause>
      <component clause>
   end record;

帶標記型別和型別擴充套件

type ... is tagged
   record
      <component list>
   end record;

type ... is new ... with
   record
      <component list>
   end record;
Default_String : constant String :=
      "This is the long string returned by" &
      " default.  It is broken into multiple" &
      " Ada source lines for convenience.";

...

   if Input_Found then
      Count_Characters;

   else  --not Input_Found
      Reset_State;
      Character_Total :=
        First_Part_Total  * First_Part_Scale_Factor  +
        Second_Part_Total * Second_Part_Scale_Factor +
        Default_String'Length + Delimiter_Size;
   end if;

end loop;

縮排透過提供程式結構的視覺指示來提高程式碼的可讀性。巢狀級別透過縮排清晰地識別,並且可以透過視覺方式匹配構造中的第一個和最後一個關鍵字。

雖然關於縮排空格數存在很多爭論,但縮排的目的是程式碼清晰。程式碼一致縮排比使用的空格數更重要。

此外,Ada 語言參考手冊 1995,第 1.1.4 節 [帶註釋的] 指出,手冊中的示例和語法規則中顯示的佈局是建議用於 Ada 程式的程式碼佈局:“描述結構化構造的語法規則以與推薦的段落格式相對應的形式呈現……。如果規則描述的構造的對應部分打算位於不同的行上,則語法規則的不同部分將使用不同的行……。建議所有縮排都是縮排基本步長的倍數(基本步長的空格數未定義)。”

用不同的方式縮排續行和巢狀控制結構,以使它們在視覺上區別開來非常重要。這樣可以防止它們在掃描程式碼時遮蔽程式碼結構。

在單獨的行上列出上下文子句可以更容易維護;更改上下文子句的錯誤率更低。

使用空格縮排比使用製表符縮排更便攜,因為製表符在不同的終端和印表機上顯示不同。

如果您使用的是可變寬度字型,則製表符比空格對齊效果更好。但是,根據您的製表符設定,連續縮排的行可能會導致非常短的行長。

自動化說明

[編輯 | 編輯原始碼]

本節中的指南可以使用自動程式碼格式化工具輕鬆強制執行。

運算子對齊

[編輯 | 編輯原始碼]
  • 垂直對齊運算子以突出顯示區域性程式結構和語義。
    if Slot_A >= Slot_B then
       Temporary := Slot_A;
       Slot_A    := Slot_B;
       Slot_B    := Temporary;
    end if;

    ----------------------------------------------------------------
    Numerator   := B**2 - 4.0 * A * C;
    Denominator := 2.0 * A;
    Solution_1 := (B + Square_Root(Numerator)) / Denominator;
    Solution_2 := (B - Square_Root(Numerator)) / Denominator;
    ----------------------------------------------------------------

    X := A * B +
         C * D +
         E * F;

    Y := (A * B + C) +  (2.0 * D - E) -  -- basic equation
         3.5;                            -- account for error factor

對齊可以更容易地看到運算子的位置,因此,可以直觀地突出顯示程式碼的功能。

在長表示式中使用行和空格可以突出顯示術語、運算子的優先順序和其他語義。它還可以為表示式中的註釋提供高亮顯示空間。

如果運算子的垂直對齊迫使語句跨越兩行,尤其是在斷點不合適的情況下,可能最好放寬對齊指南。

自動化說明

[編輯 | 編輯原始碼]

上面的最後一個示例顯示了一種“語義對齊”,它通常不會被自動程式碼格式化程式強制執行或甚至保留。如果您將表示式分解為語義部分並將每個部分放在單獨的行上,請注意稍後使用程式碼格式化程式。它很可能會將整個表示式移到單行上並將所有註釋累積到最後。但是,有一些格式化程式足夠智慧,能夠在該行包含註釋時保留換行符。一個好的格式化程式會識別出上面的最後一個示例沒有違反指南,因此會按原樣保留它。

宣告對齊

[編輯 | 編輯原始碼]
  • 使用垂直對齊以增強宣告的可讀性。
  • 每個宣告最多佔一行。
  • 將單個說明性部分中所有位於同一級別的宣告縮排。

例項化

[編輯 | 編輯原始碼]

對於沒有用空行分隔的宣告,請遵循以下對齊規則。

  • 對齊冒號分隔符。
  • 對齊 := 初始化分隔符。
  • 當使用尾隨註釋時,對齊註釋分隔符。
  • 當宣告超出行時,換行並在換行處新增一個縮排級別。首選斷點,按順序排列: (1) 註釋分隔符;(2) 初始化分隔符;(3) 冒號分隔符。
  • 對於不適合單行的列舉型別宣告,將每個文字放在單獨的一行上,使用下一個縮排級別。在適當的情況下,可以按行或列排列語義相關的文字以形成表格。

變數和常量宣告可以以表格格式佈局,列由 :, := 和 -- 符號分隔。

    Prompt_Column : constant        := 40;
    Question_Mark : constant String := " ? "; -- prompt on error input
    Prompt_String : constant String := " ==> ";

如果這會導致行太長,則可以將每個部分放在單獨的一行上,並使用其唯一的縮排級別。

    subtype User_Response_Text_Frame is String (1 .. 72);
    -- If the declaration needed a comment, it would fit here.
    Input_Line_Buffer : User_Response_Text_Frame
           := Prompt_String &
              String'(1 .. User_Response_Text_Frame'Length -
                           Prompt_String'Length => ' ');

列舉文字的宣告可以列在一列或多列中,如下所示。

  type Op_Codes_In_Column is
        (Push,
         Pop,
         Add,
         Subtract,
         Multiply,
         Divide,
         Subroutine_Call,
         Subroutine_Return,
         Branch,
         Branch_On_Zero,
         Branch_On_Negative);

或者,為了節省空間。

    type Op_Codes_Multiple_Columns is
          (Push,            Pop,                Add,
           Subtract,        Multiply,           Divide,
           Subroutine_Call, Subroutine_Return,  Branch,
           Branch_On_Zero,  Branch_On_Negative);

或者,為了強調相關的值組。

    type Op_Codes_In_Table is
          (Push,            Pop,
           Add,             Subtract,          Multiply,    Divide,
           Subroutine_Call, Subroutine_Return,
           Branch,          Branch_On_Zero,    Branch_On_Negative);

許多程式設計標準文件要求在單元頭註釋中以表格形式重複名稱、型別、初始值和含義。這些註釋是冗餘的,並且可能與程式碼不一致。以表格形式對宣告本身進行對齊(參見上面的示例)為編譯器和讀者提供了相同的資訊;最多強制每行一個宣告;並透過為初始化和必要的註釋提供空間來簡化維護。表格佈局增強了可讀性,從而防止名稱在大量的宣告中“隱藏”。這適用於所有宣告:型別、子型別、物件、異常、命名數字等等。

自動化說明

[edit | edit source]

本節中的大多數指南很容易透過自動程式碼格式化程式強制執行。唯一的例外是最後一個列舉型別示例,它基於列舉文字的語義以行排列。自動程式碼格式化程式將無法做到這一點,並且可能會將列舉文字移動到不同的行。但是,僅檢查指南違規的工具應該接受列舉型別宣告的表格形式。

更多關於對齊

[edit | edit source]

指南

[edit | edit source]
  • 垂直對齊引數模式和括號。

例項化

[edit | edit source]

具體來說,建議您

  • 每行放置一個形式引數規範。
  • 垂直對齊引數名稱、冒號、保留字 in、保留字 out 和引數子型別。
  • 將第一個引數規範放在與子程式或條目名稱相同的行上。如果任何引數子型別被強制超過行長度限制,則將第一個引數規範放在新行上,該行縮排與續行相同。

示例

[edit | edit source]
    procedure Display_Menu (Title   : in     String;
                            Options : in     Menus;
                            Choice  :    out Alpha_Numerics);

以下兩個示例展示了此指南的備選例項化

    procedure Display_Menu_On_Primary_Window
          (Title   : in     String;
           Options : in     Menus;
           Choice  :    out Alpha_Numerics);

    procedure Display_Menu_On_Screen (
          Title   : in     String;
          Options : in     Menus;
          Choice  :    out Alpha_Numerics
        );

對齊括號使複雜的相對錶達式更加清晰

    if not (First_Character in Alpha_Numerics and then
            Valid_Option(First_Character))        then

原理

[edit | edit source]

這種對齊有助於提高可讀性和可理解性,並且在有自動支援的情況下很容易實現。對齊引數模式提供了引數名稱、模式、子型別以及必要時引數特定註釋的表格效果。跨子程式在編譯單元中垂直對齊引數可以進一步提高可讀性。

備註

[edit | edit source]

子程式佈局有多種選擇。上面的第二個示例在程式中對齊所有子程式名稱和引數名稱。這具有佔用不必要的行(子程式名稱較短時)或看起來很奇怪(只有一個引數時)的缺點。

第三個示例是通常用於減少新增、刪除或重新排序引數行時所需編輯量的格式。括號不必在行之間移動。但是,最後一個引數行是唯一一個沒有分號的行。

例外

[edit | edit source]

當運算子函式具有兩個或多個相同型別的形式引數時,將引數宣告在一個單行列表中比將形式引數列表分成多個形式引數規範更易讀。

    type Color_Scheme is (Red, Purple, Blue, Green, Yellow, White, Black, Brown, Gray, Pink);

    function "&" (Left, Right : Color_Scheme) return Color_Scheme;

自動化說明

[edit | edit source]

本節中的大多數指南很容易透過自動程式碼格式化程式強制執行。唯一的例外是最後一個示例,它展示了括號的垂直對齊以強調錶達式的項。這很難透過自動程式碼格式化程式實現,除非表示式的相關項可以透過運算子優先順序嚴格地確定。

空白行

[edit | edit source]

指南

[edit | edit source]
  • 使用空白行對邏輯相關的文字行進行分組(NASA 1987)。

示例

[edit | edit source]
    if ... then

       for ... loop
          ...
       end loop;

    end if;

此示例使用空白行將不同型別的宣告隔開

    type Employee_Record is
       record
          Legal_Name    : Name;
          Date_Of_Birth : Date;
          Date_Of_Hire  : Date;
          Salary        : Money;
       end record;

    type Day is
          (Monday,    Tuesday,   Wednesday, Thursday,  Friday,
           Saturday,  Sunday);

    subtype Weekday is Day range Monday   .. Friday;
    subtype Weekend is Day range Saturday .. Sunday;

原理

[edit | edit source]

當以深思熟慮且一致的方式使用空白行時,相關程式碼部分對讀者來說更清晰可見。

自動化說明

[edit | edit source]

自動格式化程式無法很好地強制執行此指南,因為在何處插入空白行的決定是語義性的。但是,許多格式化程式能夠保持現有的空白行不變。因此,您可以手動插入這些行,並且在執行此類格式化程式時不會丟失效果。

分頁

[edit | edit source]

指南

[edit | edit source]
  • 突出顯示每個包或任務規範的頂部、每個程式單元體的頂部以及每個程式單元的結束語句。

例項化

[edit | edit source]

具體來說,建議您

  • 使用檔案序言、規範標題和主體標題來突出顯示這些結構,如指南 3.3 中所推薦。
  • 使用一行短劃線,從當前縮排相同的列開始,突出顯示嵌入在宣告部分的巢狀單元的定義。將短劃線行插入定義的正前方和正後方。
  • 如果兩條短劃線相鄰,則省略較長的那條。

示例

[edit | edit source]
    with Basic_Types;
    package body SPC_Numeric_Types is
       ---------------------------------------------------------------------
       function Max
             (Left  : in     Basic_Types.Tiny_Integer;
              Right : in     Basic_Types.Tiny_Integer)
             return Basic_Types.Tiny_Integer is
       begin
          if Right < Left then
             return Left;
          else
             return Right;
          end if;
       end Max;
       ---------------------------------------------------------------------
       function Min
             (Left  : in     Basic_Types.Tiny_Integer;
              Right : in     Basic_Types.Tiny_Integer)
             return Basic_Types.Tiny_Integer is
       begin
          if Left < Right then
             return Left;
          else
             return Right;
          end if;
       end Min;
       ---------------------------------------------------------------------
       use Basic_Types;
    begin  -- SPC_Numeric_Types
       Max_Tiny_Integer := Min(System_Max, Local_Max);
       Min_Tiny_Integer := Max(System_Min, Local_Min);
       -- ...
    end SPC_Numeric_Types;

原理

[edit | edit source]

很容易忽略當前頁面或螢幕上不可見的程式單元的部分。演示硬體和軟體的頁面長度差異很大。透過清楚地標記程式的邏輯頁面邊界(例如,使用短劃線),您可以讓讀者快速檢查程式單元是否全部可見。這種分頁還可以使快速掃描大型檔案更容易,以便查詢特定程式單元。

例外

[edit | edit source]

此指南不涉及物理“頁面”上的程式碼佈局,因為此類頁面的尺寸差異很大,並且沒有一個指南適合所有情況。

自動化說明

[編輯 | 編輯原始碼]

本節中的指南可以使用自動程式碼格式化工具輕鬆強制執行。

每行語句數量

[編輯 | 編輯原始碼]
  • 每條語句都從新的一行開始。
  • 每行最多寫一條簡單語句。
  • 將複合語句拆分成多行。

使用

    if End_Of_File then
       Close_File;
    else
       Get_Next_Record;
    end if;

而不是

    if End_Of_File then Close_File; else Get_Next_Record; end if;

異常情況

    Put("A=");    Natural_IO.Put(A);    New_Line;
    Put("B=");    Natural_IO.Put(B);    New_Line;
    Put("C=");    Natural_IO.Put(C);    New_Line;

每行一條語句可以增強讀者查詢語句的能力,並有助於防止語句被遺漏。同樣地,當複合語句的各個部分在單獨的行上時,它的結構會更加清晰。

如果一條語句超過了行上剩餘的空間,則將其繼續到下一行。這條指南包括宣告、上下文子句和子程式引數。

根據 Ada 參考手冊 1995,§1.1.4 [註釋],"其他換行符的最佳位置是在分號之後。"

自動化說明

[編輯 | 編輯原始碼]

本節中的指南很容易用自動程式碼格式化程式來強制執行,唯一的例外是最後一個示例,它展示了將多個語句語義分組到一行上的情況。

Put 和 New_Line 語句的示例展示了一個合理的例外。將緊密相關的語句分組在同一行上可以使各組之間的結構關係更加清晰。

原始碼行長度

[編輯 | 編輯原始碼]
  • 遵守原始碼的最大行長度限制 (Nissen 和 Wallis 1984,§2.3)。

例項化

[編輯 | 編輯原始碼]

具體來說,建議您

  • 將原始碼行長度限制為最大 72 個字元。

當 Ada 程式碼從一個系統移植到另一個系統時,原始碼行語句的記錄大小可能會受到限制,這可能是由於以下原因之一:一些作業系統可能不支援用於磁帶 I/O 的可變長度記錄,或者一些印表機和終端支援 80 個字元的線寬且沒有換行。在指南 7.1.2 的註釋中檢視更多原理。

原始碼有時需要出於各種原因而釋出,而信紙大小的紙張在可用列數方面不如計算機清單那樣寬容。

此外,在閱讀原始碼所需的理解層次上,人們在視野寬度方面也存在一定的限制。這些限制大約對應於 70 到 80 列的範圍。

另一種例項化方法是將原始碼長度限制為 79 個字元。79 個字元的限制可以將程式碼與 FORTRAN 72 個字元的限制區分開來。它還可以避免在 80 個字元寬的終端上出現問題,在這種終端上,最後一列中的字元可能無法正確列印。

自動化說明

[編輯 | 編輯原始碼]

本節中的指南可以使用自動程式碼格式化工具輕鬆強制執行。

程式碼格式

[編輯 | 編輯原始碼]
  • 在分隔符周圍使用一致的間距。
  • 使用與普通散文相同的間距。
  • 對巢狀控制結構、續行和嵌入單元進行一致的縮排和對齊。
  • 區分巢狀控制結構和續行的縮排。
  • 使用空格進行縮排,而不是製表符(Nissen 和 Wallis 1984,第 2.2 節)。
  • 垂直對齊運算子以突出顯示區域性程式結構和語義。
  • 使用垂直對齊以增強宣告的可讀性。
  • 每個宣告最多佔一行。
  • 將單個說明性部分中所有位於同一級別的宣告縮排。
  • 垂直對齊引數模式和括號。
  • 使用空白行對邏輯相關的文字行進行分組(NASA 1987)。
  • 突出顯示每個包或任務規範的頂部、每個程式單元體的頂部以及每個程式單元的結束語句。
  • 每條語句都從新的一行開始。
  • 每行最多寫一條簡單語句。
  • 將複合語句拆分成多行。
  • 遵守原始碼的最大行長度限制 (Nissen 和 Wallis 1984,§2.3)。

可讀性

華夏公益教科書