Ada 样式指南/源代码展示
源代码在页面或屏幕上的物理布局对代码的可读性有很大影响。本章包含源代码展示指南,旨在使代码更易读。
除了通用指南之外,"实例化"部分还提供了一些具体建议。如果您不同意这些具体建议,您可能需要采用自己的约定,这些约定仍然遵循通用指南。最重要的是,在整个项目中保持一致。
完全一致的布局很难手动实现或检查。因此,您可能更喜欢使用参数化代码格式化工具来自动化布局,或者将指南整合到自动编码模板中。本章中介绍的一些指南和具体建议无法通过格式化工具强制执行,因为它们基于 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)。