如何编写高质量的程序

学习任何编程语言都会有一个基本的过程,开始的时候学习基本的语法,然后学习各种库,框架,开始做各种项目。在做项目的过程中,随着代码量的增加,我们会渐渐感到失去对程序的掌控能力,bug开始增加,牵一发而动全身,顾此失彼。这充分说明了编写高质量程序的重要性,这里的“高质量”主要指程序的正确性,可读性,可维护性。

什么是高质量的程序

正确性

程序正确性的重要程度无需多言,尤其在一些特殊领域,例如芯片制造业,航天业,武器制造业,对程序正确性往往有着极其严格的要求,因为一旦程序出错,代价往往是巨大的。在这些领域,需要使用形式化方法(formal methods)来自动验证程序的正确性,也就是说你需要证明程序的正确性,而不仅仅保证程序在大多数情况下是正确的。在其它领域,对正确性没有这么高要求,形式化方法也不适用,但是我们还是需要使用其它手段,例如测试,code review等等来保证软件的正确性。

可读性

可读性可以帮助程序作者理清思路,思路清晰后,程序不容易出错。另外,其它程序员在维护你的代码时,更容易理解你的意思,方便修改bug,方便扩展。

不要浪费自己的时间,更不要浪费别人的时间。

可维护性

这里的可维护性主要指程序应对变化的能力。程序在完成基本功能后,可能会发生各种改变:用户需求变了,性能达不到要求需要重新实现算法,等等。一旦程序的一个点发生改变,其它点如果也需要同时手动改变,那么程序会变的不可控制,出bug的机会会增加。想像一下,我们的程序是一个盒子,在添加新功能时,如果只需要把新模块插到一个地方,新模块就可以被系统使用,这样的程序可维护性是很高的。但是如果添加新功能时,需要把原来的程序盒子拆开,其它模块也需要相应修改,才能加入新模块,这样的程序可维护性就很差。

提高程序质量的重要措施

Code Review
为代码做复查,是保证程序质量的有效手段,复查时需要检查代码风格,一致性,安全,是否正确实现了需求等方面。

测试

为什么强调自动化测试,而不是手动测试?因为自动化测试可以增加测试的便捷度,而人们通常会更多地使用那些便捷度高的东西。我在做个人项目的时候就发现,在编写了自动测试的脚本后,我每改动一点程序,就会自动运行一下脚本,在此之前,我明知道测试很重要,但是还是不会测试的如此频繁。这样的好处是可以方便定位bug,否则在系统经过了大量改动之后,出了bug都不知道可能在哪里。

在对程序进行重构时,很重要的一点就在于,一定要先写好测试用例,然后每改动一点,就自动测试一下,保证程序始终保持在可控状态。

良好的编程风格

良好的编程风格,可以增强程序的可读性,一个结构清晰的程序,你会更容易从中发现错误。另一方面,当程序发生变化时,很可能引入新的bug,良好的编程风格可以减少这种bug的出现。下面是与编程风格相关的一些措施。

  • 风格指南

找一份你使用的编程语言的风格指南,例如Google的编程语言风格指南系列,Python的PEP8,并一直遵守这份指南的内容,如果有自动化工具帮助你保持这种风格,那再好不过。

  • 最佳实践

寻找你所使用语言的最佳实践,他们可读性强,经过了大量实践的考验,被广泛接受,所以尽可能多地使用他们。

例如Python 的 The Hitchhiker's Guide to Python

  • 起一个好名字

变量,函数名,类名,都需要一个好名字。程序本身是对解决方案的一种描述,一个好的名字会增强这种描述性,也会让你的思维集中于解决方案,同时让其它人更容易理解你的解决方案。

  • 不要直接使用常量

在程序中直接使用的常量,一般被称为 Magic Numbers, 一方面它不利于其它程序员对程序的理解,因为没有人知道这个常量代表什么。另一方面,多个常量之间可能是有关系的,直接使用常量根本反应不出这种关系。

  • 同一变量名不要有多种含义

首先这种做法降低了可读性,一个变量前面一个含义,后面一个含义,这会给阅读程序的人带来困扰。

  • 尽可能保证变量作用域小

尽量减少变量定义的点与变量最后一次使用的点之间的跨度,这样可以使变量与其相关代码变得紧凑,提高可读性,不用在使用变量时再去很多的地方查看其它引用。

  • 保证函数短小精悍

过长的函数会让读者陷入细节的泥潭,还需要前后来回看才能明白前面一大段和后面一大段代码的关系。将函数分解,然后给函数起一个好名字,读者马上就能明白这段代码在做什么。

提高应变能力

程序应对变化的能力强,可扩展性就强,也更容易在变化时保证正确性,这样的程序可维护性强。下面是一些提高程序应变能力的措施。

  • 不要使用常量

不要使用常量的另一个原因在于常量可能变化,如果程序中多次引入了这个常量,那么一旦这个常量要发生变化,就需要同时改动许多地方,这时候,如果有些地方没有改,就会使程序不一致,可能引入bug。

  • 同一变量名不要有多种含义

同一变量名不要有多种含义另一个原因在于,多种含义之间可能会相互影响,第一次写程序时你可能记得这些影响,但是以后对程序进行改动的时候,你可能就忘记了。例如函数内一段代码执行后,索引i 的值等于一个长度,但是这段代码后,你没有将i赋值给另一个变量len,而是直接使用它。等过一段时间后,你或者其它人修改这段程序时,很可能忘了这段代码执行后i的值需要等于一个长度,因为这是一种隐式的约定,所以很容易被忽视。

  • 尽可能保证变量作用域小

保证变量作用域小也有利于重构。当一个函数变得很长时,你可能需要将它分解成多个函数,这时候,如果变量跨度小,就可以很方便地提取函数,不用来回查找与此函数相关的变量的引用。

  • 减少代码重复

如果有一段代码在很多地方重复,这就告诉你,需要把他们提取成一个函数。因为代码的重复意味着这是一块独立的逻辑,独立的逻辑可以抽象成一个函数。另一方面,一旦这段逻辑需要发生变化,只需要修改这个函数就可以了,不需要把所有地方都手动修改一遍。

  • 数据驱动

数据驱动的意思是用数据表示来代替程序逻辑。例如,我们需要一个程序,判断某个月有几天,在实现时,最好用一个数组表示各个月的天数,需要哪个月直接查询就好,而不要使用大量的if语句来作逻辑判断。这只是一个小例子,它提醒我们,如果程序中含有大量判断语句,就应该想一想,能不能用数据来驱动逻辑,这样需要修改的时候,我们直接修改数据就好,而不用修改程序逻辑。

我曾经接手过一个项目,这个项目其实是一个工具集,根据用户的选择,调用不同的工具。原始的代码里,就使用了大量if语句,并且每个工具其实调用方式和代码都很相似。这样,我每次添加新工具时,就需要找到多个if语句块,作相应修改。如果用数据驱动的话,我们完全可以去掉这些if语句,在用户的选择与工具之间建立对应关系,这样每当新添加工具时,只需要把工具加到系统里,系统会根据这个表直接找到这个工具。这其实和之前举的盒子的例子很相似,添加新工具时,只需要把工具插到盒子上的槽上,根本不用打开盒子。这就大大提高了程序的可扩展性。

控制复杂度

要保证软件的高质量,很重要的一方面在于控制复杂度。控制复杂度的一个很重要的手段在于分解复杂的事物。我们之所以觉得一个事物复杂,是因为同一时间需要关心的事情太多,把复杂事物分解后,每次我们只需要关心很少的事情,这样就控制住了复杂度。

  • 不要使函数或类过大

如果一个函数或类过大,他们会变得过分复杂,你同一时间需要关心许多细节。将函数或类变小之后,你的思维在一段时间内可以集中在同一个抽象层次,而不必过于深入其细节,这样更容易发现程序中的缺陷,因为你每次只需要关心很少的事情。在最高层,你只需要关心模块之间的关系,关心算法的流程,不必关心模块内部的事情。在最低层,你只需要关心一个模块内部的事情,而不必关心其它事情。

  • 不要使函数参数过多

函数参数过多可能说明这个函数负责了太多的事情,你需要将这个函数分解。另一方面,你需要从逻辑上考虑,这些参数是不是一个整体,如果是一个整体,那么直接传过来一个结构体,或者传过来一个对象,是不是更合适?

  • 不要使抽象层次过多

如果一个函数或类被分解为过多的抽象层次,在模块内部,你确实只需要关心很小的事情,但是这时候,由于模块过多,抽象层次过深,他们之间的关系又使复杂度增长起来。

使用自动化工具

自动化工具迫使我们养成良好的编程习惯,而且不容易出错。再次强调:

    工具越是使用方便,你越会频繁使用它。

所以,尽可能地让你的工具使用便捷。 例如:

  • 使用一些静态检测工具在编辑时自动帮助你检测程序的不良风格
  • 使用静态检测工具检测程序常见错误
  • 使用重构工具帮助你重构
  • 使用自动化测试工具在保存时自动运行测试

注意事项

没有什么事情是一成不变的,所有的法则都需要考虑具体的情况。如果你要用一个法则,需要真正明白自己为什么要用,需要去权衡,而不要为了能用上这个法则而生搬硬套。

好好问问自己:

  • 变化真的存在么?
  • 真的需要抽象么?
  • 真的需要面向对象么?
  • 真的xxx么?

参考资料

这篇文章是我这段时间阅读过一些书后的想法,书目有

  • 代码大全(Code Complete)
  • 重构——改善既有代码的设计(Refactoring Improving the Design of Existing Code)
  • 程序设计实践(The Proactice of Programming)

在阅读这些书的同时,我还在维护其它人的代码,做自己的个人项目。在阅读的过程中,我会不断地想到我做的项目哪里有问题,可以用书中提到的方法去修改,因此印象深刻。这些书单纯读也非常有好处,但是如果可以结合到自己的项目中,会有更大裨益。因为只有产生了强烈的共鸣,才能保证真正理解了一个东西。

上面提到的一些措施,都是我遇到过的,所以印象比较深刻,这几本书中还有大量提高程序质量的方法,我这里只是一个引子,希望给有心人打开一扇窗户。

如何编写高质量的程序相关推荐

  1. C/C++怎样编写高质量的程序:头文件和源文件模板------高质量C++/C编程指南-第1章-文件结构

    http://www.bianceng.cn/Programming/cplus/200705/614.htm 高质量C++/C编程指南-第1章-文件结构 第1章 文件结构 每个C++/C程序通常分为 ...

  2. 编写高质量代码改善C#程序的157个建议——建议148:不重复代码

    建议148:不重复代码 如果发现重复的代码,则意味着我们需要整顿一下,在继续前进. 重复的代码让我们的软件行为不一致.举例来说,如果存在两处相同的加密代码.结果在某一天,我们发现加密代码有个小Bug, ...

  3. 编写高质量代码改善C#程序的157个建议——建议86:Parallel中的异常处理

    建议86:Parallel中的异常处理 建议85阐述了如何处理Task中的异常.由于Task的Start方法是异步启动的,所以我们需要额外的技术来完成异常处理.Parallel相对来说就要简单很多,因 ...

  4. 《编写高质量代码:改善c程序代码的125个建议》——建议3-5:避免使用浮点数作为循环计数器...

    本节书摘来自华章计算机<编写高质量代码:改善c程序代码的125个建议>一书中的第1章,建议3-5,作者:马 伟 更多章节内容可以访问云栖社区"华章计算机"公众号查看. ...

  5. 《编写高质量代码:改善c程序代码的125个建议》——建议2-6:防止无符号整数回绕...

    本节书摘来自华章计算机<编写高质量代码:改善c程序代码的125个建议>一书中的第1章,建议2-6,作者:马 伟 更多章节内容可以访问云栖社区"华章计算机"公众号查看. ...

  6. 《编写高质量代码:改善c程序代码的125个建议》——第1章 数据,程序设计之根本建议1:认识ANSI C...

    本节书摘来自华章计算机<编写高质量代码:改善c程序代码的125个建议>一书中的第1章,建议1,作者:马 伟 更多章节内容可以访问云栖社区"华章计算机"公众号查看. 第1 ...

  7. 编写高质量代码改善C#程序的157个建议——建议87:区分WPF和WinForm的线程模型...

    建议87:区分WPF和WinForm的线程模型 WPF和WinForm窗体应用程序都有一个要求,那就是UI元素(如Button.TextBox等)必须由创建它的那个线程进行更新.WinForm在这方面 ...

  8. 编写高质量代码改善C#程序的157个建议——建议127:用形容词组给接口命名

    建议127:用形容词组给接口命名 接口规范的是"Can do",也就是说,它规范的是类型可以具有哪些行为.所以,接口的命名应该是一个形容词,如: IDisposable表示可以被释 ...

  9. 《编写高质量代码:改善c程序代码的125个建议》——建议4-1:整数转换为新类型时必须做范围检查...

    本节书摘来自华章计算机<编写高质量代码:改善c程序代码的125个建议>一书中的第1章,建议4-1,作者:马 伟 更多章节内容可以访问云栖社区"华章计算机"公众号查看. ...

  10. 编写高质量代码改善C#程序的157个建议——建议133:用camelCasing命名私有字段和局部变量...

    建议133:用camelCasing命名私有字段和局部变量 私有变量和局部变量只对本类型负责,它们在命名方式也采用和开放的属性及字段不同的方法.camelCasing很适合这类命名. camelCas ...

最新文章

  1. Ubentu编译Android源码(AOSP)
  2. docker离线包相关脚本编写示例:docker镜像load/push/save脚本
  3. 脱了马甲我也认识你: 聊聊 Android 中类的真实形态
  4. 《团队激励与沟通》第 2 讲——激励的方法与应用 重点部分总结
  5. centos7:塔建pure_ftpd虚拟用户
  6. wordpress 运行_如何为您的教室设置和运行WordPress
  7. C++设计模式详解之工厂模式解析
  8. AIoT、DevOPS、数据平台、开源,你不可不知的微软 Azure 黑科技大公开
  9. HDU2083 简易版之最短距离【最值】
  10. 【Spring】- 属性注入方式
  11. 给马云的一幅画——阿里国际站用户体验设计案例精选
  12. Python 绝技 —— UDP 服务器与客户端
  13. 兼容ie\firefox\chrome的cursor
  14. 软件测试思维导图大全
  15. jpg与gif的互换(使用ImageIO代替JPEGImageEncoder)
  16. html5手机电商网页设计代码_一部手机,万物皆可复制粘贴,这位兼职写代码的设计师将AR玩出了新高度...
  17. Mathematica中用有限元方法解不规则区域上的波动方程
  18. Python opencv保存视频打不开
  19. 天堂祭祀php,test_《扶摇柳真真免费阅读》
  20. 2020年世界航天发射统计

热门文章

  1. 关于Session、Cookie、Token你知道多少?
  2. Cloudflare的介绍可以防DDOS 能防多少G呢?
  3. NYOJ题目1045看美女
  4. OBD芯片应用开发手册 OBD2开发 内部资料分享 汽车电子通讯开发TDA61 TDA66芯片
  5. 使用AJAX如何得到数据库当中的值!
  6. poj 1159 (DP LCS)
  7. 让div不占位置_开箱测评户外折叠桌椅,收纳起来真的不占位置,强行不血亏啊!...
  8. 斜齿轮重合度计算公式_基于KISSsoft的电动汽车变速箱齿轮修形优化设计
  9. Qt QLabel实现自动换行 字符断行 自适应
  10. 方法二 NTC热敏电阻转换温度的计算方式