六种与语言无关的编写更好代码的方法


与部门中的其他程序员一起工作最令人惊奇的事情之一是,你日复一日、月复一月、年复一年地持续看到同样的错误。无论是由于普遍的胆怯、害怕批评被个人化、缺乏正式的审查流程,还是不鼓励反思和改进的公司文化,程序员们往往不愿给其他程序员提供建设性的批评。没有这样的批评,程序员们往往缺乏认识和提高自己编码技能的机会,这导致同样的错误在未来重复出现。而团队中一个程序员的错误和坏习惯最终会浪费大家的时间,这在生产环境中总是最受限制的资源。为了最大限度地减少此类瓶颈,第一次就以正确的方式设计代码至关重要——这意味着要有良好的习惯。

以下这些常识性建议是从多年在专业层面和大型个人项目上的经验中总结出来的。本文献给我所有在专业和业余水平上工作的程序员同仁。



1) 规划可扩展性

这或许是我几乎每天都会遇到的头号问题。通常当我们编写一段代码时,我们是为了解决一个非常具体的问题。我们很容易陷入“这是我必须解决的问题,这就是我将要如何解决它”的心态。然后,针对该特定问题量身定制的解决方案被实现,这在短期内效果很好,但最终会导致巨大的后期维护问题。

你编写的任何一段代码都应该始终牢记可扩展性。无论是由于功能蔓延(最初未预期的功能)、更快的计算机,还是更复杂的客户用你的程序做更复杂的事情,今天实现的特定解决方案在明天都需要扩展以完成其他事情。例如,你可能会编写一个处理BMP文件的小型库。明天你的客户就会想要JPEG支持。今天你正在将某些信息写入文件,明天需要写入文件的信息将是原来的3倍,而且旧文件仍然需要正常工作。你的客户今天正在使用你的软件完成一项任务,明天他们会想使用你的软件完成一项包含10倍字段、对象或任何适用于你的软件的单元的任务。如果你的代码没有预先设置为支持扩展,你最终将陷入维护的泥沼,并且每一次添加都会使你的软件变得更复杂且难以维护。

有许多简单的建议可以帮助实现可扩展性:不要编写特定解决方案的代码——编写通用的、可重用的库。不要假设任何东西的长度或大小会是常量。所有用户数据文件都要进行版本控制。使用封装。

到目前为止,软件生命周期中花费时间最多的阶段是维护阶段——因此,以鼓励轻松维护的方式设计软件至关重要——这意味着要为可扩展性进行设计。


2) 不要使用魔法数字

魔法数字是硬编码到你的项目中,通常没有解释的数字。它们通常代表对某物的大小、长度或价值所做的明确假设。由于魔法数字往往有些随意,它们本身通常不带有任何意义,这最终可能使你的代码更难阅读、理解和维护。

例如,你可能会看到一段代码,如下所示

for iii = 0 to 15
    if (slot[iii].type == 12)
        return true;

在这种情况下,数字 15 和 12 意味着什么?为什么选择了这些特定的数字?这些数字没有向读者传达关于这段代码中发生了什么的具体信息。

理想情况下,魔法数字应该参数化——实现此目的的机制因语言而异,但使用符号常量(例如通过 const 变量、#defines 或枚举值)是更好的选择。上述代码,使用符号常量重写后

for iii = 0 to MAX_INVENTORY_SLOTS
    if (slot[iii].type == TYPE_ITEM_POTION)
       return true;

突然间,这段代码变得更有意义了。即使对程序的其余部分了解不多,也很明显我们正在检查库存(可能是生物的库存),看看是否有任何药水。

此外,使用符号常量而不是魔法数字有助于提高可扩展性。将来,你的角色可能需要 20 个物品栏槽位而不是 15 个。使用魔法数字,你必须在代码中搜索所有数字 15 的实例,并手动更改它们。更改符号常量通常涉及搜索 MAX_INVENTORY_SLOTS 并将定义从 15 更改为 20。这不仅可以节省大量时间,还可以防止因无意中更改错误的魔法数字而造成的错误。例如,考虑这段代码片段

if (iii == 15)
    return;

这个 15 意味着什么?它是最大物品栏槽位数(这种情况下你应该更改它),还是意味着其他什么(这种情况下你不应该更改它)?如果不了解其周围代码的上下文,就不可能判断。这使得当你需要扩展物品栏槽位数时进行大规模更改变得危险,因为它引入了无意中破坏其他已正常工作的代码的错误。

(作者注:也建议使用比 iii 更好的变量名)


3) 记录“为什么”,而不是“是什么”

几乎所有的程序员都直观地理解良好注释的维护价值。然而,许多程序员却滥用注释,导致它们对维护过程几乎没有增加任何价值。

这是一个没有添加任何内容的注释示例

# Loop through all of the inventory slots
for i = 0 to MAX_INVENTORY_SLOTS

即使没有不必要的注释,代码在做什么也相当明显。

这是你有时会看到的另一个无用注释

iii = iii + 1; // increment iii

注释不应该用来描述代码在**做什么**。代码的语法告诉我们代码在做什么。好的注释应该告诉我们代码**如何**或者**为什么**做它正在做的事情。**如何**注释通常对于概述代码用来解决问题的算法很有用

/* The following code scans the entire dungeon looking for spots
    where a hallway connects to a room.  This done by examining
    adjacent square of the dungeon.  If a square is a hallway
    square and the hallway square has one adjacent room
    neighbor, this is a candidate square. */

// Algorithm implemented here

“如何”注释最好放在函数或代码块的顶部,因为它们允许程序员在不必阅读和理解块中所有代码的情况下,了解后面的代码是否与他们当时的兴趣相关。这在维护阶段尝试定位特定代码段时可以节省大量时间。

“为什么”注释是作为独立代码行注释的理想选择。“为什么”注释应该解释程序员做某事的原因

// Because all creatures have been priority sorted by time,
//   if this creature (which is the next to move) has a time
//   greater than the current time, we can advance the
//   CurrentTime variable so that the creature can take it's
//    move.
if (pCreature->GetTime() > CurrentTime)
    CurrentTime = pCreature->GetTime()

如果你的代码确实需要注释来解释代码在**做什么**,那么你的代码可能需要重构或简化,而不是添加注释。


4) 不要重复造轮子

面向对象语言的程序员经常会想自己编写类来做其他程序员已经解决了上百万次的事情。对于容器类来说尤其如此。虽然编写自己的数组和链表容器类可能是一个很好的学术练习,甚至如果你喜欢这类事情还会很有趣,但当你可以重用一个已经写好的类时,为什么还要这样做呢?这不仅节省了你实现类所需的时间,还节省了你测试和调试它的时间,而且该类的未来版本可能会提供额外功能,而你除了安装它们之外什么都不用做。此外,如果容器类是语言的一部分(例如C++的标准模板库),阅读你代码的程序员更有可能已经熟悉这些容器的工作方式,从而减少了新程序员熟悉代码所需的时间。

重复造轮子还会导致代码重复,这会使你的项目更复杂,更难以维护。如果你有两三段代码完成相同的任务,你可能需要修改所有这些代码才能增强其功能。这实际上是浪费了本可以用于其他地方的时间。此外,它会导致哪个版本具有什么责任的混淆,并使查找错误变得更加困难(因为它更难隔离哪个代码用于特定问题)。

关于某些库(例如C++标准模板库)的抱怨之一是其接口笨拙。如果对你来说是这样,那么与其重写一个已经存在的容器,不如创建一个包装类,并将你最常使用的功能封装到你自己的接口中。通常,这样做的性能开销很小,你可以根据自己的需求定制接口,而且如果你需要扩展底层库的功能,你已经有了一个方便的地方可以这样做。

网络上已经有许多优秀的资源可以找到免费代码,例如 Koders.comGoogle code searchKrugleSourceforge Code Project 等等。请使用它们。

还有许多优秀的免费库可能已经解决了你正在尝试解决的问题:例如,SDLFreeTypeWxWidgets 都是跨平台库的例子,它们可以为你的问题提供简单的解决方案。尽管你必须学习如何使用它们,但通常所需的时间远少于实现自己的解决方案所需的时间,更不用说调试和维护它了!

注意:请注意许可问题,尤其是在商业项目中使用开源代码时!


5) 增量工作

业余程序员犯的最大错误之一是试图一次性实现太多功能,而没有花费足够的时间测试每个部分。结果,bug开始堆积在其他bug之上,不仅使它们更难找到和隔离,而且使需要修复的bug数量看起来难以管理。

一个更好的主意是增量工作。每写完一段代码,就思考:“我将如何测试它?”,然后实际测试它。如果你发现任何bug,无论是你刚刚编写的代码中,还是你的代码所依赖的代码中,这些bug都应该立即处理。

一次只专注于一个领域。专注于某一段代码,直到它实现你的目标。每次在不同领域之间切换时,你的大脑都必须进行上下文切换,这会降低你的工作效率并让你更快疲惫。如果你正在编写某种容器类,不要只实现现在需要的部分,然后以后再来完成其余部分。如果你知道以后会需要它,现在就实现它,趁着细节还在你的脑海中。一个人忘记已经编写的代码的细节的速度真是惊人!你犯错的可能性会更小,而且你的测试计划会更加连贯和结构化。


6) 找一个愿意批评你工作的人

正如本文引言中提到的,找到一个愿意给你建设性反馈的人是学习的最佳途径之一。如果他们愿意,这个人甚至在你开始编码之前就应该被咨询!将你提议的解决方案概要带给他们,向他们解释,并让他们给你反馈。在向他们解释的过程中,你不仅会在自己心中巩固问题,他们也能够为你提供你可能没有预料到的方面的建议,例如潜在的问题区域、你没有想到的更好的算法,或者你打算重复造轮子的地方。

一旦你编写了解决方案,请找人查看一下。他们很可能会指出你代码扩展性不足、使用了魔法数字或文档不完善的地方。

记住,在一个团队中,每个人都是休戚与共的。当你的队友现在编写糟糕的代码时,明天可能就是你不得不去扩展它。因此,每个人都值得花时间共同努力,确保从一开始就采用良好的习惯,这样以后的代价就不会那么严重。如果你是单独工作,拥有良好的习惯将有助于保持你的代码结构化和灵活,使其更易于使用,并且不那么考验你的耐心。


总结

尽管上述许多内容似乎显而易见,但在时间紧迫、需要完成任务的情况下,这些事情往往会被简化。然而,这些建议不应被视为预防措施,而应被视为投资。现在多花一点时间,将来就能节省大量额外时间。“但是,如果我现在没有很多时间怎么办?”你可能会问。无论如何都要去做。现在已经是从过去某个时间点过来的“未来”,而且随着代码的老化,这类错误往往会加剧。现在做正确的事情将为你将来继续做正确的事情赢得更多时间。最终,这不仅能带来更高质量的产品,还能让程序员更快乐。

如果您认为本文值得一读,请使用下面的社交书签图标推荐它。非常感谢,祝您编码愉快。

相关文章

guest
您的电子邮箱地址将不会被显示
发现错误?请在上方留言!
与勘误相关的评论在处理后将被删除,以帮助减少混乱。感谢您帮助使网站对每个人都更好!
来自 https://gravatar.com/ 的头像与您提供的电子邮箱地址相关联。
有回复时通知我:  
22 评论
最新
最早 最多投票
内联反馈
查看所有评论