在程序设计中,缩进风格(indent style)是管理代码块缩进以表达程序结构的一种约定。本条目主要讨论自由形式语言,例如C及其后裔,但这也可以(并经常)适用於大多数其他编程语言(尤其是),其中的空白字符则并不重要。缩进风格是代码风格的一个方面。
缩进在大多数编程语言中不是必要条件,而只是作为。不过,缩进有助於更好地向人类阅读者表达程序的结构。尤其是用於澄清控制流程结构(例如条件或循环)与其内部、外部代码之间的关系。不过,部分语言(例如Python和occam)使用缩进而非花括号或关键词来确定结构,这被称为越位规则。在这种语言中,缩进对编译器或解释器有意义,而不仅仅是清晰度或风格问题。
研究
尽管缩进风格被广泛使用,但关於其价值的研究却很少。Weissman在1974年进行的初步实验没有显示出任何效果。
2023年,Morzeck等人的实验表明,对于嵌套的if语句,缩进有显著的积极影响:阅读非缩进代码所需的时间平均比缩进代码多179%。Hanenberg等人的后续实验证实了这一巨大影响(尽管在该实验中,非缩进代码的阅读时间仅增加了113%),并揭示了阅读时间的差异可以由(缩进代码中)可跳过的代码来解释。在另一个关于JSON对象的实验中,阅读非缩进代码的时间甚至增加了544%。
花括号位置
缩进风格的主要区别在于复合语句的花括号({...})的位置,这通常是为涵盖一个控制声明(if、while、for...)。下表展示了本条目中讨论的所有风格的所在位置。为了一致性,缩进深度(字符数)统一使用4个空格表示,这未考虑各风格中首选的缩进深度。
C/C++ 风格
C、C++和其他花括号编程语言的编码风格属性包括但不限于:
- 花括号相对于其他代码元素的位置
- 使用制表符还是空格
- 是否将单语句块包裹在花括号中。支持者认为这更安全,因为插入新语句不会导致控制流与缩进不一致。反对者则认为这会增加代码长度,因为除了 else if 结构和 do{}while 块之外,块的闭合花括号需要占用一行。
K&R
Kernighan & Ritchie (K&R) 风格常在C、C++代码中使用,并且是许多衍生风格的基础。它用于原始的Unix内核、Kernighan和Ritchie的《C程序设计语言》一书,以及Kernighan和Plauger的《》中。
尽管《C程序设计语言》没有明确定义这种风格,但它始终如一地遵循着它。书中提到:
花括号的位置不那么重要,尽管人们对此持有执着的信念。我们选择了这种广受欢迎的风格。选定一种适合你的风格,然后坚持使用它。
在这种风格中,函数的左花括号和右花括号各自独占一行,并与声明保持相同的缩进;而函数体内的语句则多缩进一级。然而,函数内部的多语句块的左花括号与其控制子句在同一行,而右花括号则独占一行,除非它后面紧跟 else 或 while 等关键字。
示例代码:
int main(int argc, char *argv[])
{
while (x == y) {
do_something();
do_something_else();
if (some_error)
fix_issue(); // 无花括号的单语句块
else
continue_as_usual();
}
final_thing();
}
埃及花括号
多行代码块的非对齐花括号被昵称为“埃及花括号”(或“埃及括号”),因为它们类似于古埃及人画像中手臂一上一下的奇特姿势。
单语句
单语句块不使用花括号,这容易导致诸如goto fail漏洞这样易被忽视的错误。
变种:1TBS (OTBS)
“唯一的真括号风格”(The One True Brace Style)(缩写为1TBS或OTBS)是K&R风格的一个变种,其中单语句块的括号不被省略。
bool is_negative(int x)
{
if (x
尽管C/C++等语言不要求这样做,但为单语句块使用花括号可以确保在插入新语句时,控制流不会与缩进不一致,从而避免诸如苹果公司臭名昭著的goto fail漏洞等问题。
一些来源对“唯一的真括号风格”的定义存在分歧——它可能根据特定语言中最流行的风格而略有不同,有些作者可能会根据主观偏好将截然不同的风格宣称为OTBS,而其他人则认为这是K&R风格的“黑客行话”称呼。
变种:Linux内核
Linux内核源码树使用K&R风格的一种变种。 林纳斯·托瓦兹建议贡献者遵循该风格。其属性包括:
- 使用制表符缩进(而非空格),并设定制表位为8个字符。
- 花括号布局与K&R一致:函数定义的花括号独占一行,复合语句的左花括号与控制子句在同一行,并用空格隔开。
- switch 语句中的标签(label)与外围块对齐(只有一级缩进)。
- 最大行长为100个字符,尽管首选2020年之前的80个字符限制。
- 复合语句(如if、while和do-while)的单语句体不需要用花括号包围。但是,如果 if-else 语句中的任何一个子语句需要花括号,则两个子语句都应包裹在花括号中:
int power(int x, int y)
{
int result;
if (y 0)
result *= x;
}
return result;
}
变种:Java
大量的Java代码使用K&R风格的一种变种,其中左花括号不仅对于函数内的块在同一行,对于类或方法声明也是如此。这种风格之所以普遍,主要是因为Sun Microsystems最初的风格指南使用了这种K&R变种。因此,Java API的大部分标准源代码都是用这种风格编写的。这在ActionScript和JavaScript中也是一种流行的缩进风格,与Allman风格并列。
变种:Stroustrup
比雅尼·斯特劳斯特鲁普(Bjarne Stroustrup)在他的书中(如《程序设计:原理与实践(使用C++)》和《C++程序设计语言》)针对C++调整了K&R风格。
与上述变种不同,Stroustrup不使用“cuddled else”(即 } else { 写在同一行)。因此,Stroustrup会这样写
变种:BSD KNF
伯克利软件套件(BSD)操作系统使用一种有时被称为内核标准格式(Kernel Normal Form, KNF)的风格。虽然主要用于内核代码,但也广泛用于用户空间代码。它本质上是贝尔实验室第6版和第7版Unix源代码中使用的K&R风格的一个详尽文档化的变体。
SunOS内核和用户空间使用类似的缩进风格。 SunOS指南发布于1996年。Bill Shannon编写的 cstyle 程序可以验证源代码文件的缩进正确性。
在这种风格中,硬制表符(vi中的ts)保持为8列,而软制表符通常定义为辅助工具(vi中的sw),并设置为4。硬制表符用于缩进代码块,而额外的软制表符(4个空格)用于所有必须分成多行的连续行。
此外,函数调用在括号前不使用空格,但C语言的原生语句如 if, while, do, switch 和 return 会使用空格(如果 return 使用括号的话)。在其顶层块中未声明局部变量的函数,也应在其左花括号后留一空行。
Allman风格
Allman风格以埃里克·奥尔曼(Eric Allman)命名。有时也被称为“BSD风格”,因为Allman为BSD Unix编写了许多实用程序(但这不应与上述的“BSD KNF风格”混淆)。
这种风格将与控制语句关联的花括号放在下一行,缩进到与控制语句相同的级别。花括号内的语句则缩进到下一级。
这种风格将与控制语句关联的花括号放在下一行,并进行缩进。花括号内的语句缩进到与花括号相同的级别。
与Ratliff风格一样,右花括号与花括号内的语句缩进相同。 在这两种情况下,包含的代码都相对于花括号再缩进两个空格。
这种风格由理查德·斯托曼普及,其布局可能受到他编写Lisp代码背景的影响。 在Lisp中,相当于块(progn)的是一等数据实体,给予它自己的缩进级别有助于强调这一点,而在C中,块仅仅是语法。这种风格也可以在20世纪60年代和70年代的一些ALGOL和XPL编程语言教科书中找到。
尽管不完全属于缩进,GNU编码风格还包括在函数名之后——参数列表的左括号之前——加一个空格。 Linux内核编码风格文档也建议不要使用这种风格,敦促读者烧掉GNU编码标准的副本作为“伟大的象征性姿态”。
Horstmann风格
Cay S. Horstmann在1997年版的《Computing Concepts with C++ Essentials》中通过将块的第一个语句放在与左花括号同一行来调整Allman风格。这种风格也用于Jensen和Wirth的《Pascal User Manual and Report》中的示例。
while (x == y)
{ something();
something_else();
//...
if (x
这种风格结合了Allman的优点,即保持花括号的垂直对齐以利于阅读,并易于识别块,同时也节省了K&R风格的一行。然而,2003年的版本现在完全使用Allman风格。
Pico风格
这是Pico语言设计者最常用的风格。Pico缺乏return语句,并使用分号作为语句分隔符而不是终止符。它产生了这种语法:
stuff(n):
{ x: 3 * n;
y: do_stuff(x);
y + x }
其优点和缺点类似于使用K&R风格节省屏幕空间。一个额外的优点是,起始和结束花括号在应用上是一致的(都与一行代码共享空间),而K&R风格中,一个花括号与一行代码共享空间,另一个花括号则独占一行。
Ratliff风格
在《Programmers at Work》一书中,流行的dBase-II和-III第四代编程语言背后的原始程序员C. Wayne Ratliff讨论了一种类似于1TBS的风格,但结束的花括号与嵌套块的缩进对齐。他表示这种风格最初记录在Digital Research公司的材料中。这种风格有时被称为“横幅(banner)”风格,可能因其类似于挂在杆子上的横幅。在这种风格中(它对Whitesmiths的关系就像K&R对Allman的关系),结束控制与列表中的最后一项缩进相同(因此适当地失去了显著性)。这种风格可能使某些人的视觉扫描更容易,因为任何块的标题是该级别唯一突出的东西。
// In C
for (i = 0; i
衍生C语言风格
以下风格可以被认为是“衍生”的C风格,因为它们很大程度上受到其他非C语言缩进风格的影响。它们可能适用于大部分用这些其他语言编写的项目中的C代码部分,为了保持项目核心代码的一致“外观和感觉”,这凌驾于使用更传统的C风格的考量之上。
Lisp风格
虽然GNU风格有时被描述为Lisp程序员缩进的C代码,但人们甚至可以更进一步,将结束花括号一起插入块的最后一行。这种风格使得缩进成为区分代码块的唯一方式,但其优点是不包含无信息的行。这很容易被称为Lisp风格,因为这种风格在Lisp代码中非常常见。在Lisp中,表达式树末尾的相同括号的分组旨在表明,视觉上跟踪嵌套级别不是用户的工作,用户只需理解树的结构。
这种风格的传统Lisp变体偏好非常窄的缩进级别(通常是两个空格),因为Lisp代码通常嵌套很深。Lisp仅具有表达式,没有独特的语句类;函数参数大多缩进到同一水平,以说明它们在封闭表达式中的共享状态。这也是因为,除了括号之外,Lisp传统上是一种非常简洁的语言,甚至省略了作为无信息样的常见样板代码,例如 if : then | else 块中的 else 关键字,而是将其统一渲染为 (if expr1 expr2 expr3)。
// C
for (i = 0; i
;; Lisp
(dotimes (i 10)
(if (= (rem i 2) 0)
(do-something i)
(progn
(do-something-else i)
(do-third-thing i))))
Haskell风格
Haskell是一种花括号可选的语言,也就是说,下面的两组代码在语义上是相等的:
braceless = do
text
braceful = do
{ text 在Haskell中,布局(layout)可以替代花括号。通常,过程式 do的段落和一般程序文本会省略花括号和分号,但这种风格通常用于由一对括号或花括号组成的列表、记录或其他句法元素,并用逗号或分号分隔。如果跟在关键字 where、let 或 of 后的代码省略了花括号和分号,那么缩进就是有意义的。
APL风格
作为APL通常是多么简洁的例子,这是康威生命游戏的step函数实现:
life←{⊃1⍵∨.∧3 4=+/+⌿¯1 0 1∘.⊖¯1 0 1⌽¨⊂⍵}
APL风格的C代码类似于APL代码的简洁风格,常用于其实现中。 这种风格由Arthur Whitney开创,并大量用于他自己的项目K的实现中。J编程语言也是用这种风格实现的。值得注意的是,并非所有APL实现都使用这种C风格,例如GNU APL和Dyalog APL。
除了APL风格的C缩进外,通常变量名也会缩短为单字符或双字符,以减少缩进量和跨越多行的表达式。
制表符、空格及缩进尺寸
缩进的尺寸通常与风格无关。通常,程序员使用相同宽度的空白字符来缩进每个代码块,常用的宽度从1到4个空格不等。1983年对PASCAL代码进行的一项实验发现,缩进大小显著影响可理解性。2到4个字符之间的缩进大小被证明是最佳的。
许多早期程序使用制表符来缩进,从而简化输入和节约源代码文件的大小。Unix编辑器通常将制表符视为等同八个字符,而Macintosh和Windows环境将它视作四个字符,这使代码在各环境间交换时产生一种混乱。现代的编程编辑器通常可以设置任意的缩进尺寸,并会插入适当的制表符与空格。对Ruby、许多shell脚本语言和某些形式的HTML格式,通常为每个缩进级别使用两个空格。
使用制表符还是空格作为缩进字符是编程界的一项持续争论。傑米·加文斯基等一些程序员认为空格而非制表符有助增加跨平台可移植性。而如WordPress编码规范的作者则认为制表符增加了可移植性。GitHub上前40万个存储库的调查显示,空格更为常见。
工具
目前已有许多计算机程序可以自动校正缩进风格(依照程序作者或用户的偏好)以及制表符表示的缩进长度。其中很著名的一个是indent,这个程序包含在许多类Unix操作系统中。
在Emacs中,有多种命令可用于自动解决缩进问题(如 M-x indent-region)。
Elastic tabstops是一种需要文本编辑器支持的制表风格,当块中的一行的长度改变时,整个文本块将自动对齐。
其他考虑
丢失块踪迹
在某些情况下存在着丢失块边界的轨迹的风险。这通常在包含许多复杂语句的大量代码中看到,这些复合语句嵌套了许多层的缩进。当程序员滚动到一大堆嵌套语句的底部时,他可能已经忘记了哪些控制语句转到哪里。长复合语句可能是代码异味或过于复杂的标志,面对这个问题的程序员可能会考虑代码重构以期待它在未来有更好的体验。
依赖计算左花括号的程序员可能会在K&R等风格中遇到困难,因为起始花括号在视觉上没有与控制语句分开。更多依赖缩进的程序员会从K&R等垂直紧凑的风格中受益更多,因为块更短。
为了避免丢失对 for 等控制语句的跟踪,可以使用大缩进(如8单位宽的硬制表符),并将大函数分解为更小、更易读的函数(Linux内核采用了这种方式)。
一些文本编辑器允许程序员在块的两个对应花括号之间跳转。例如,在vi中按 % 键。另一种保持块意识的方法是在右花括号后使用注释:
for (int i = 0; i
if (x
声明的插入
在使用标准的Unix行编辑器ed时,K&R风格能防止一个常见的错误。在控制语句与循环块的开启花括号之间错误地插入的语句将使循环体变为单次执行。
for (int i = 0; i K&R风格通过将控制语句和开启括号保持在同一行来避免此问题。
参见
*
- 語法突顯
*
参考资料
外部链接
- [http://syque.com/cstyle/index.htm C Style: Standards and Guidelines: Defining Programming Standards for Professional C Programmers] , Prentice Hall, ISBN 0-13-116898-3 / ISBN 978-0-13-116898-5 (full text is also online). Straker, David (1992).
- [http://milan.adamovsky.com/2010/08/contextual-indent.html Contextual Indent]
- [https://www.gnu.org/prep/standards/standards.html GNU Coding Standards]
*
制表符与空格
- [http://www.jwz.org/doc/tabs-vs-spaces.html Tabs versus Spaces: An Eternal Holy War] by Jamie Zawinski
- [http://adamspiers.org/computing/why_no_tabs.html Why I prefer no tabs in source code] by Adam Spiers
- [https://web.archive.org/web/20131209200846/http://derkarl.org/why_to_tabs.html Why I love having tabs in source code] (archived)
- [http://nickgravgaard.com/elastictabstops/ Elastic tabstops – the solution to the tabs-versus-spaces issue]
评论 (0)