几天前,Digg 上链接了一篇文章,题为“编写更好代码的 6 种方法”。它提供了很好的建议(在大多数情况下),所以我想在这里链接一下。
http://www.ibm.com/developerworks/linux/library/l-clear-code/index.html?ca=drs-
以下是作者的建议,以及我自己的评论
1) 像聪明人一样写注释。
我再怎么强调都不为过。几个月后,那些看起来显而易见的代码就会变得像一门外语一样难以理解。给所有东西都加上注释——不要仅仅陈述显而易见的事实。尝试编写注释,就像你在为没有你正在做的事情的先验知识的人编写一样。因为在三到六个月后,那个人就是你自己。
2) 大量使用 #define。不,是“非常”大量。
想法没错,实现方式错了。正如文章所说,将硬编码数字写入程序是一个非常糟糕的主意。不可避免地,你将来会想改变一些东西,如果数字散布在各处,这将非常困难。使用 #define 有助于记录你正在做的事情,并使其在将来更容易更改这些数字。
但是 #define 太……C 了。使用 #define 有几个问题。首先,因为 #define 是一个预处理器命令,预处理器会遍历并用它们相应的值替换你定义的所有名称。这意味着这些名称在你调试时不可用,这使得调试更加困难。假设你有一个函数调用,它传入一个名为 EXP_PER_KILL 的 #defined 值。即使你正在调试的代码可能显示 AddExp(EXP_PER_KILL),你也不知道 EXP_PER_KILL 的评估结果是什么,除非你能找到它的定义。
其次,无论 #defined 变量在哪里定义,它们总是声明在全局作用域中。
在 C++ 的世界里,我们可以做得更好。一个更好的选择是使用 const 变量。const 变量是实际的对象,所以你可以很容易地在调试器中获取它们的值,并且它们遵循正常的范围规则。另一个更好的选择是使用 enum,它具有相同的优点。
3) 不要使用会嘲笑你的变量名。
关于这一点没什么好说的——为变量命名使其目的清晰是文档化代码的一个重要方面。
4) 做错误检查。你会犯错。是的,就是你。
这比看起来更容易也更难。你不可避免地会通过传递无效参数来滥用你编写的函数,或者会出错,指针最终会变成 NULL,而你期望它有一个值,反之亦然。检查这些事情是好的,并且可以而且会防止你的程序崩溃。然而,如果你确实检测到错误,但没有对该信息做任何明智的处理,那么你的程序不如直接崩溃了事。
与检测错误同样大的问题是如何明智地处理它。如果你正在编写一个可重用函数,明智的做法通常包括通知调用者发生了错误,并由调用者来处理问题。如果你是调用者,或者你正在编写一个对程序至关重要的函数,那么弄清楚如何处理错误可能是一个棘手的提议。你会弹出一个警告消息框吗?你会要求用户重新输入吗?你会保存用户数据然后终止程序吗?这确实取决于情况,没有简单的解决方案。
但在做这些事情之前,你必须检测到错误是否发生,这就是我同意作者认为这很重要原因。
5) “过早优化是万恶之源。”——唐纳德·克努特
新手或过于热情的程序员遇到的最大问题之一是试图编写尽可能快的代码,而牺牲了代码可读性等因素。这几乎总是一个坏主意。选择适合你要解决的问题的算法确实是一个好主意——例如,如果你要进行大量的元素插入和删除,链表可能比数组是更好的选择。但这并不意味着你必须设计一个算法,从链表中榨取每一丝性能。效率通常是以可读性为代价的,老实说,除了少数例外,可读性更重要,因为在某个时候,你将不得不修复一个错误,或者扩展你的代码,而为了尽可能高效而精心设计的代码将不利于这两者。
一旦你的代码编写完成,你总是可以对其进行分析,以找出实际的瓶颈在哪里,而不是过早地根据你认为瓶颈可能在哪里采取行动。对于利用封装等概念的正确实现的代码,在需要时用更好的算法替换一个算法通常不是问题。
6) 不要太自作聪明。
这有点像第 5 条。编写清晰、直接、易读的代码几乎总是一个更好的主意,而不是编写尽可能高效的代码。正如我在关于注释的部分中所写,如果你需要一个注释来解释你的代码行在做什么,它可能需要重写,而不是注释。
你们中的许多人都听说过 KISS 这个缩写——“Keep It Simple, Stupid”(保持简单,傻瓜)。它也适用于代码。