如何写出高质量的代码?

来源:互联网 发布:软件验收标准模板 编辑:程序博客网 时间:2024/05/21 10:00

读书笔记:《剑指offer》

思先于行

在实际的软件开发周期中,设计的时间通常不会比编码的时间短。我们先不要急于动手写代码,而是一开始仔细的分析和设计。与其写出一段漏洞百出的代码,倒不如仔细分析再写出鲁棒的代码。

代码的鲁棒性

鲁棒是英文 Robust 的英译,有时也翻译成健壮性。

所谓的鲁棒性是指程序能够判断输入是否合乎规范要求,对不和要求的输入予以合理的处理。

容错性是鲁棒性的一个重要体现。不鲁棒的软件在发生异常事件的时候,比如用户输入错误的用户名、视图打开的文件不存在或者网络不能连接,就会出现不可预见的诡异行为,或者干脆整个软件崩溃。这样的软件对于用户而言,不亚于一常灾难。

由于鲁棒性对软件开发非常重要,面试官在招聘的时候对应聘者写出的代码是否鲁棒也非常关注。

提高代码鲁棒性的有效途径是进行防御性编程。防御性编程是一种编程习惯,是指预见在什么地方可能会出现问题,并为这些可能出现的问题制定处理方式。比如试图打开文件时发现文件不存在,我们可以提示用户检查文件名和路径;当服务器链接不上时,我们可以试图链接备用服务器等。这样当异常情况发生时,软件的行为也尽在我们的掌握之中,而不至于出现不可预见的事情。

在面试时,最简单也最实用的防御性编程就是在函数入口添加代码已验证用户输入是否符合要求。通常面试要求的是写一两个函数,我们需要格外关注这些函数的输入参数。如果输入的是一个指针,那指针是空指针怎么办?如果输入的是一个字符串,那么字符串的内容为空怎么办?如果我们能把这些问题都提前考虑到,并做相应的处理,那么面试官就会觉得我们有防御性编程的习惯,能够写出鲁棒的软件。

当然,并不是所有与鲁棒性相关的问题都只是检查输入的参数那么简单。当我们看到问题的时候,要多问几个“如果不…..那么…..”这样的问题。

比如面试题:“链表中倒数第k个结点”,这里隐含的一个条件就是链表中节点的个数大于k。我们就要问如果链表中结点的数目不是大于k个,那么代码会出什么问题?这样的思考方式能够帮助我们发现潜在的问题并提前解决问题。这比让面试官发现问题之后我们再去慌忙分析代码查找问题的根源要好的多。

如何才能发现并纠正代码中的问题?

一个很好的办法就是提前想好测试用例。在写出代码之后,立即用事先准备好的测试用例检查测试。如果面试是以手写代码的方式,那也要在心里默默运行代码做单元测试。只有确保代码通过测试之后,再提交面试官。

我们要记住一点:自己多花时间找出问题并修正问题,比在面试官找出问题之后再去慌慌张张修改代码要好的多。

其实面试官检查应聘者的代码的方法也是用他事先准备好的测试用例来测试。如果应聘者能先想到这些测试用例,并用他们来检查测试自己的代码,那就能保证有备无患,万无一失了。

代码的规范性

1.清晰地书写:绝大部分面试都是要求应聘者在白纸或者白板上书写,面试过程中减慢写字的速度,尽量把每个字母写清楚,书写清晰。

2.清晰地布局:如果布局不够清晰,缩进也不能体现出代码的逻辑性,面试官对这样的代码会头昏脑涨。

3.合理的命名:在书写代码的时候,用完整的英文单词组合命名变量和函数,比如函数需要传入一个二叉树的根节点作为参数,则可以把该参数命名为 BinaryTreeNode * pRoot,不要因为这样会多写几个字母而觉得麻烦。合理的命名应该一眼能看出变量函数的用途。


代码的完整性

面试官会非常关注应聘者考虑问题是否周全,通过检查代码是否完整来考察应聘者的思维是否全面,通常会检查代码是否完成了基本功能,输入边界值是否能得到正确的输出,是否对各种不合规范的非法输入做出了合理的错误处理。

应聘者在写代码之前,首先要把可能的输入都想清楚,从而避免在程序中出现各种各样的质量漏洞。也就是说在编码之前要考虑单元测试。如果能够设计全面的单元测试用例并在代码中体现出来,那么写的代码自然也就是完整正确的了。通常我们可以从功能测试,边界测试和负面测试三方面设计测试用例,以确保代码完整性。

1.首先要考虑的是普通功能测试的测试用例。我们首先要保证写出的代码能够完成面试官的基本要求。比如面试题要求的功能是把字符串转换成整数,我们可以考虑输入字符串“123”来测试你的代码。这里要把零、整数和负数都考虑进去。

2.考虑功能测试的时候,我们要尽量突破常规思维的限制。比如面试题“打印从 1 到最大的 n 位数”,很多人觉得这很简单。最大的二位数是99,最大的三位数是 999,这些数字很容易就能算出来。但是最大的 n 位数都能用 int 型表示吗?超出 int 型的范围我们可以考虑 long long 类型,超出 long long 类型能够表示的范围呢?面试官是不是要求考虑任意大的数字?如果面试官确认题目要求的是任意大的数字,那么这个题目就是一个大数问题,此时我们需要特殊的数据结构来表示数字,比如用字符串或者数组来表示大的数字,以确保不会溢出。

3.其次需要考虑各种边界值的测试用例。很多时候我们的代码中都会有循环和递归。如果我们的代码是基于循环的,那么结束循环的边界条件是否正确?如果使递归,递归终止的边界值是否正确?这些都是边界测试时要考虑的用例。还是以字符串转换成整数的问题为例,我们写出的代码应该确保能够正确转换最大的正整数和最小的负整数。

4.最后还要考虑各种可能的错误的输入,也就是通常所说的负面测试的测试用例。我们写的函数除了要顺利的完成要求的功能之外,当输入不符合要求的时候还能作出合理的错误处理。在设计把字符串转换成整数的函数的时候,我们就要考虑当输入的字符串不是一个数字,比如“1a2b3c”,我们怎么告诉函数的调用者这个输入是非法的。

5.最后也是最重要的一点。在软件开发过程中,永远不变的就是需求一直会改变。如果我们在面试的时候写出的代码能够把将来需求可能的变化都考虑进去,在需求发生变化的时候能够尽量减少代码改动的风险,那我们就向面试官展示了自己的对程序可扩展性和可维护性的理解,通过面试就是水到渠成的事情了。