Code Craft(编程匠艺)之代码的生命(一)

一、欲善其事,先利其器——使用工具构建软件

1、我们为什么使用工具

    工具不是替代我们该做的工作,而是使我们有能力做我们的工作,软件的质量取决于程序员的能力。

2、了解你的工具的最新发展情况

    了解你的工具的最新发展情况 ,但是不要随便进行升级,只有当新版本提供了重大的修改并且已证明是稳定的时候,再进行更新。大多数IDE都是模块化的——你可以用一个更好的组件来代替其中的某个组件,也可以将目前没有的功能添加进去。

3、你必须使用一个源代码控制工具,否则你就会像缺了左膀右臂一样

4、调试和调查工具

   ①调试器。调试器使你可以对程序中的执行路径进行分析,中断程序的执行,调查变量的值,设置断点,以及通常将运行的代码分割成不同的部分;

   ②分析器。如果你的代码运行的速度过慢,那么就会用到这个工具,分析器用于分析代码各部分运行的时间并找出瓶颈所在,使用分析器可以为切合实际的优化找到优化对象;

   ③代码校验器。分为静态和动态两类,前者以一种编译器类似的方式整理代码,检查你的源文件,以确定可能存在问题的区域以及对语言的错误使用(例如,lintC语言中的一系列常见的编码错误执行静态检查),静态校验器的大部分功能已经内置再现代编译器中。动态校验器在代码编译时对代码进行修改和插装,然后在运行时进行检查(例如,内存分配/边界检查器)这些工具在大多数情况下比调试器更有用,因为他们像是一种预防机制,而不是单纯的补救:他们将在代码缺陷有机会破坏你的程序之前找到他;

   ④度量工具。这些工具用于执行代码检查,通常的形式为静态分析器,他们会生成关于代码质量的评估可以帮助你挑出具体的目标来进行代码审查。度量数据通常是以函数为基础来收集的,圈复杂度是代码复杂性的度量数据,它考虑了决定点和潜在控制流的数量,较高的圈复杂度预示着难以理解的代码,这些代码很可能比较脆弱或存在缺陷;

   ⑤反汇编程序;

   ⑥缺陷追踪。一个缺陷追踪系统提供一个共享的数据库,其中包含在你的系统中找到的bug的追踪记录,它使你的同事可以报告缺陷,对缺陷进行查询、分配或注释,并最终将缺陷标记为已修正,是确保产品质量的一种关键工具。

5、“自行设计的”工具

    当你无法为一项任务找到工具,而且这项任务重复出现,“自行设计的”工具从长期看来会为你节省大量的时间。以下是一些创建工具的常见方式:

    ①以一种新的方式来组合现有的工具(通常使用Unix的管道机制),也许需要编写一些连接代码;

    ②使用脚本语言(通常是Perl);

    ③从零开始创建完善的程序,只有它是一种你将会不断反复地使用的重要工具时你才需要这样做,否则投入的精力可能就不划算。


 二、测试时代——测试代码的魔术

 1.质量保证 

    人们错误的把QA(质量保证)和测试绑在一起,但实际两者之间有明显的区别,测试的目标是找出错误的行为即软件和它的规范不符的地方,它实际上是一种检测。而真正的QA则是预防,测试只是QA的一小部分——软件质量的指标不仅仅是指较低的bug数量,它还意味着软件按时发布,成本合理,并满足所有的要求和预期。

 2. 谁来进行测试

    程序员有责任对他或她编写的源代码进行测试,发布包含未经测试代码的产品是愚蠢的,QA部门的工作是进行测试,但应该是测试产品,而不是测试你新写出的代码。

 3. 何时进行测试

    尽早的开始有效的代码测试,这样你就可以在bug的危害最小的时候发现它们,为你发现的每个缺陷都编写一个测试,尽可能频繁地进行测试。研究表明,即便是经过最细致的测试的软件,在每1000行代码中也会包含0.53个错误,现实世界中的测试极少能证明软件刀枪不入,为了进行最有效的测试,我们就需要将精力集中在有可能找到大部分软件缺陷的关键测试上。

 4. 测试的类型

    ①单元测试;②组件测试;③集成测试;④回归测试;⑤负载测试;⑥压力测试;⑦疲劳测试;⑧可用性测试;⑨黑盒测试(功能测试);⑩白盒测试(结构测试)。

 5. 选择单元测试用例

    ①选择一些良好的输入,确保软件正常情况下可以正确的运行,包括中间位置的值、下边界和上边界附近的值;

    ②选择一些经过挑选的不好的输入,确保软件是健壮的,包括在数值上太大或太小的值(负值常常会被忘记)、过长或过短的输入(试着发送一个空字符串或不同大小的数组和列表)、在内部不一致的数值;

    ③边界值本身、刚好在边界上方的值、刚好在边界下方的值;

    ④随机数据;

    ⑤零,如果输入的是数字不要忘记对零的情况进行测试,C/C++语言的指针常常会被赋予零值以表示未设置或未定义,试着将零值针抛给代码以观察它的反应是否正确,在Java中你可以发送一个null对象的引用达到类似的效果。

6.为测试而测试

     设计你的代码以使其易于测试。设计原则:
    ①使各个代码部分都自包含,而不要与外界建立未经说明的或缺失实质的依赖关系;
    ②不要依赖全局变量;
    ③限制你的代码的复杂度;
    ④保持代码的可观测性。

7. 自动化测试

    自动化实际上是可靠地代码开发的一个基本概念,例如,JUnit就是一个常见的Java单元测试框架。遗憾的是,不是所有的测试都能够自动进行,对库函数进行单元测试相对比较容易,而自动地测试用户接口则非常困难。

 8. 面对故障时该怎么办

    ①注意当时你在做什么,以及是什么操作引发了这个故障;
    ②再试一次;
    ③完整的描述故障(包括出现问题是的环境、可以使问题重复出现的最简单的步骤、关于问题出现的可重复性和频率的信息、软件的确切版本号以及使用的硬件、有可能相关的其他事物);
    ④把这些描述记录下来发在你的缺陷追踪系统中,即使是一个你准备自己修正的简单的编码错误;
    ⑤编写可以检测出这个缺陷的最简单的测试工具并将其添加到自动测试套件中。

 9.缺陷追踪系统

    “缺陷跟踪系统”是我们在管理缺陷时的一个重要武器,通常执行的操作包括:①报告故障;②分配责任;③确定报告的优先级;④标记已修正;⑤结束报告;⑥查询数据库;⑦修改条目。

 

三、寻找缺陷——调试:当事情进展得不顺利时该怎么办

1、从远处看,软件bug的三个主要类别

    ①编译失败。这种错误最直接,最显而易见容易被发现和修改。编译失败的原因一般是一个愚蠢的语法错误或者一个简单的疏忽,如调用函数时使用了数量或类型不正确的参数类型,也可能是makefile中的缺陷,如一个链接阶段的错误(或许是缺少某个函数的实现),甚至可能是构建服务器的磁盘空
间不足;
    ②运行时崩溃。这种错误比编译错误难处理的多,不过还算是比较简单的。当程序出现故障并崩溃时,你可以退后一步,开始着手调查程序出错的地方;
    ③非预期的行为。非预期的行为是真正难处理的错误——你的程序并没有崩溃,而只是在准备跳下悬崖。例如,你本来想得到一个蓝色正方形却得到了一个黄色三角形。故障可能是执行代码内部的一个微小的逻辑问题,可能是由于某行代码有缺陷,也可能是将几个相互连接但是不太匹配的模块最终集成在一起的时候出现。

2、从近处看,错误的四个类型

    ①句法错误。在条件表达式中将“==”写成了“=”或将“&&”写成了“&”,忘记了句尾分号或者在错误的地方添加了分号(典型的位置是在一条for语句之后),忘记将一组循环语句用括号括起来,括号不匹配;
    ②构建错误。对项目进行彻底的清理,然后从头开始重新构建。
    ③基本的语义bug。包括:使用未经初始化的变量;比较浮点型变量是否相等(毫无意义);编写不处理数值溢出的计算;隐式转换造成舍入错误(常常会丢掉char符号);声明一个"unsigned int foo"然后写下语句"if (foo < 0)";
    ④语义bug。运行时的那些并不是每次都出现的故障很可能是缘于内存问题,可能是在错误的地方使用了错误的变量,没有验证函数的输入参数,或者使一个循环出现错误;也可能是调用API的方式不正确,或者没有使一个对象的状态在内部保持一致。

3、从更远处看,常见的语义缺陷

    ①段错误。源自程序访问那些并没有分配给它的存储单元,涉及指针的输入错误或糟糕的指针算法都非常容易造成这种错误。"scanf("%d", number);"就是一个会造成段错误的常见C输入错误。由于number前缺少"&",会造成scanf尝试写入有number的(无效)内容所引用的存储单元;
    ②内存溢出。这类错误是由于写入那些以为你的数据结构(数组、矢量或其他自定义的结构)分配的内存造成的,当你将值写入这些内存时,你很可能会破坏你的程序的其他部分中的数据。这种问题很难检测到,常见的征兆是在溢出的很长一段时间之后(也许是几千条指令之后)出现随机的非预期行为;
    ③内存泄露。当你需要一些内存时,你必须客气地请求运行时为你分配(C语言中使用malloc,C++中使用new),使用完之后礼貌的将内存归还(分别是free和delete)。如果你无礼的忘记释放内存,你的程序将慢慢消耗掉越来越多的稀缺的计算机资源。还有其他两种错误类型与此无关,即释放某个内存块的次数过多,从而造成不可预知的环境故障,以及没有认真地管理其他稀缺资源,如文件句柄或网络连接等。
(记住:任何你手动获取的资源都必须手动的释放);
    ④内存耗尽。某些操作系统(如linux)在任何情况下都不会从内存分配的调用返回故障,而是每次分配时返回一个指针,指向保留的但是未分配的内存页。当程序尝试访问这个内存页时,操作系统的进程将截获访问,然后才将内存分配到内存页,并返回正常的程序操作。知道耗尽了虚拟内存地址空间之后,malloc可能会返回“0”,但是在你有机会注意到之前,系统很可能已经崩溃了;
    ⑤数学错误。这类错误有很多形式:浮点异常、不正确的数学结构、上溢/下溢或可能失败的表达式(例如,除数是零)、尝试通过“printf("%f")”输出一个float但是传递了一个int等;
    ⑥程序暂停。这类错误通常都归因于糟糕的程序逻辑。

4、搜寻bug

    ①编译时错误。当你的构建失败时,先看看第一个编译时错误,并解决这个问题,这个错误的可信度要远远高于其后的消息;
    ②运行时错误。调试步骤:确定故障(把缺陷记录下来,确定错误行为的特征,它是否与时间有关?是否由输入、系统负载或程序状态决定?)、使故障再现、定位缺陷(如果程序没有崩溃,那么就从你所知道的出现不正确行为的位置开始,从那里沿着控制流向后查找,检查每个位置的代码是否在执行你期望它们执行的操作)、理解问题、创建测试、修正缺陷、证明你确实修正路缺陷、如果没有成功(有时你尝试了所有的方法都不管用,不要沮丧,向别人述说整个问题会对你有所帮助)。

5、如何修正缺陷

    ①修正缺陷时要极为小心,不要冒着在进行修改时破坏其他代码的风险;
    ②在修正一个bug时,检查一下,确定相关的代码部分中是否存在相同的错误。永久的根除这个bug:现在就在所有出现这个缺陷的地方进行修正;
    ③每修正一个缺陷,都要从中吸取教训。你怎样才能预防它的出现?你怎样才能更快地发现它?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

画茧自缚

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值