编程风格的艺术与实践
1. 引言
编程风格不仅仅是代码的排版和格式问题,它关乎代码的可读性、可维护性和可移植性。良好的编程风格可以使代码更容易理解和维护,从而提高开发效率并减少错误。本篇文章将深入探讨编程风格的重要性,并提供一些实用的建议,帮助你在编写代码时做出更好的选择。
2. 代码布局
2.1 代码布局的重要性
代码不仅仅是为了被计算机处理而编写的,同样也是为了被其他程序员阅读。因此,在编写可读性强(以及易于维护、减少错误)的程序时,除了简单地满足编译器的接受标准外,还需要注意其他因素。风格上的考虑无疑是计算机编程中最不客观的方面:关于代码风格的看法,就像关于宗教的看法一样,可以无休止地争论。
2.2 代码布局的实践建议
一个好的代码布局应该保持一致(既与自身保持一致,也要与附近的或常见的代码保持一致),这比布局的“完美”更为重要。如果你的编程环境(即本地习惯或公司政策)没有建议一种风格,而且你也不想自己发明一种,那么就复制K&R的风格吧。
示例代码布局
int main() {
int i;
for (i = 0; i < 10; i++) {
printf("%d\n", i);
}
return 0;
}
2.3 缩进与花括号
每种流行的样式都有其优点和缺点。将开放大括号单独放在一行会浪费垂直空间;将其与下一行结合会使编辑变得繁琐;将其与前一行结合则会阻止其与闭合大括号对齐,可能还会使其更难以看清。
| 样式 | 优点 | 缺点 |
|---|---|---|
| 单独一行 | 清晰可见 | 浪费垂直空间 |
| 与下一行结合 | 节省空间 | 编辑困难 |
| 与前一行结合 | 紧凑 | 不易对齐 |
3. 函数分配
3.1 函数分配的原则
通常,相关的函数会被放置在同一个文件中。有时(比如在开发库的时候),每个独立的函数都应当有恰好一个源文件(因此也就有一个对象模块)。其他时候,特别是对于一些程序员来说,大量的源文件可能会显得繁琐,将大部分或全部程序放在几个大的源文件中可能会显得诱人(甚至合适)。
3.2 使用静态关键字
当使用
static
关键字来限制某些函数或全局变量的作用范围时,源文件的布局会变得更加受限:
static
函数和变量以及与它们共享访问权限的函数都必须位于同一个文件中。
mermaid流程图
graph TD;
A[相关函数] --> B[同一文件];
B --> C[库开发];
B --> D[减少文件数量];
D --> E[使用static];
E --> F[作用范围受限];
4. 字符串比较
4.1 使用
strcmp
进行字符串比较
这里有一个检查两个字符串是否相等的方法:
if (!strcmp(s1, s2))
这不是特别好的风格,尽管它是一个流行的习语。如果两个字符串相等,测试就成功了,但是使用了
!
(“不是”)符号,这暗示它测试的是不等性。
4.2 更好的选择
更好的选择是定义一个宏:
#define Streq(s1, s2) (strcmp((s1), (s2)) == 0)
然后你可以像这样使用它:
if (Streq(s1, s2))
4.3 高级用法
另一种可能性(这接近于预处理器滥用;参见问题10.2)是定义:
#define StrRel(s1, op, s2) (strcmp(s1, s2) op 0)
之后你可以像这样使用:
if (StrRel(s1, ==, s2)) ...
if (StrRel(s1, !=, s2)) ...
if (StrRel(s1, >=, s2)) ...
5. 条件判断
5.1
if
语句的书写方式
为什么有些人会写
if (0 == x)
而不是
if (x == 0)
?
这是一种防止常见错误的方法。如果你习惯于在
==
前写常量,编译器会在你误写成
if (x = 0)
时发出警告。例如:
if (0 = x) // 编译错误
这种写法可以防止无意的赋值操作。
继续探讨更多关于编程风格的话题,包括匈牙利命名法的应用、
goto
语句的使用以及如何在保持风格的同时优化代码性能。我们将进一步分析这些风格选择对代码质量的影响,并提供具体的实践建议。
6. 匈牙利命名法
6.1 什么是匈牙利命名法?
匈牙利命名法是一种由查尔斯·西蒙尼发明的命名约定,它通过变量的名称编码关于变量类型(以及可能的用途)的信息。在某些圈子里它备受喜爱,在其他地方则遭到严厉批评。它的主要优点是使变量的类型或预期用途从其名称中变得显而易见;其主要缺点是类型信息未必是值得在变量名称中携带的东西。
6.2 匈牙利命名法的优缺点
| 优点 | 缺点 |
|---|---|
| 使变量的类型或用途显而易见 | 类型信息不一定值得在变量名称中携带 |
| 提高代码的可读性和维护性 | 可能导致变量名称过长和复杂 |
| 有助于团队协作和代码审查 | 如果类型频繁变化,维护成本较高 |
6.3 实践建议
尽管匈牙利命名法有其优点,但也并非适用于所有场景。在选择是否使用时,应根据项目的具体需求和团队的习惯来决定。例如,在大型项目中,使用匈牙利命名法可以帮助新成员快速理解代码;而在小型项目中,简单的命名规则可能更为合适。
7.
goto
语句的使用
7.1
goto
语句的争议
编程风格,就像写作风格一样,具有一定的艺术性,不能仅通过僵硬的规则来规范。长期以来,人们观察到,不受限制地使用
goto
语句很快会导致难以维护的“意大利面条式代码”。然而,简单粗暴地禁止
goto
语句并不一定立即导致编程变得优美。
7.2 适度使用
goto
语句
许多程序员采取了温和的立场,认为通常应避免使用
goto
语句,但在一些严格限制的情况下,如果必要的话,使用
goto
语句是可以接受的。例如:
- 作为多级退出语句
-
将
switch语句中的共同动作合并 - 在一个有多个错误返回的函数中集中处理清理任务
7.3 示例代码
void process_data() {
FILE *file = fopen("data.txt", "r");
if (file == NULL) {
fprintf(stderr, "无法打开文件\n");
goto cleanup;
}
// 处理文件数据
// ...
cleanup:
if (file != NULL) {
fclose(file);
}
}
8. 风格与效率的平衡
8.1 风格与效率的关系
人们总是说良好的风格很重要,但当他们不遗余力地使用清晰的技术并使他们的程序易于阅读时,似乎最终得到的程序效率较低。然而,效率极低的程序确实是一个问题,但许多程序员盲目追求效率的热情也是一个问题。繁琐、晦涩的编程技巧不仅会破坏代码的可读性和可维护性,而且可能会导致长期效率的提升不如选择更合适的设计或算法。
8.2 实践建议
通过仔细设计,可以编写出既简洁又高效的代码。以下是一些提高代码效率的具体方法:
-
优化关键内循环 :将关键的、内循环代码从函数中移出,放入宏或内联函数中(如果表达式是不变的,则移出循环)。如果循环的终止条件是一个复杂的但循环不变的表达式,预先计算它并将其放在一个临时变量中。
-
减少精度 :在ANSI编译器下,使用
float代替double可能会得到更快的单精度运算,尽管较旧的编译器会将所有内容转换为double,因此使用float也可能更慢。用自己定制的版本替换耗时的三角函数和对数函数,这些版本根据你需要的范围和精度进行调整,可能使用查找表。 -
缓存或预先计算 :缓存或预先计算经常需要的值的表格。这可以显著提高性能,尤其是在频繁调用相同计算的情况下。
-
使用标准库函数 :优先使用标准库函数,因为编译器可能会内联或特别优化这些函数。如果程序的调用模式特别规律,你自己的特殊用途实现可能优于库的通用版本。
8.3 示例代码
// 使用查找表优化三角函数
float sin_table[360];
void init_sin_table() {
for (int i = 0; i < 360; i++) {
sin_table[i] = sin(i * M_PI / 180);
}
}
float fast_sin(int degrees) {
return sin_table[degrees % 360];
}
9. 结论
编程风格不仅仅是代码的排版和格式问题,它关乎代码的可读性、可维护性和可移植性。良好的编程风格可以使代码更容易理解和维护,从而提高开发效率并减少错误。通过合理选择编程风格,可以在保证代码正确性的前提下,写出既简洁又高效的代码。希望本文提供的建议和实践方法能够帮助你在编写代码时做出更好的选择。
mermaid流程图
graph TD;
A[优化代码性能] --> B[优化关键内循环];
A --> C[减少精度];
A --> D[缓存或预先计算];
A --> E[使用标准库函数];
B --> F[移出不变表达式];
C --> G[使用float];
C --> H[自定义数学函数];
D --> I[缓存常用值];
E --> J[优先使用库函数];
E --> K[特殊用途实现];
通过合理选择编程风格,可以在保证代码正确性的前提下,写出既简洁又高效的代码。希望本文提供的建议和实践方法能够帮助你在编写代码时做出更好的选择。
超级会员免费看
1303

被折叠的 条评论
为什么被折叠?



