有一些人虽然工作了很多年,但工作表现就像刚入行的新人。他们几乎不学习软件开发的基础知识 。除了最初几年有所成长,后期一直停滞不前,而且他们不明白为什么。 与此同时,我也曾与一些只有几年工作经验的开发人员共事,他们表现出惊人的增长潜力。他们工作态度端正,并且明白如何避免不称职的行为。 根据开发人员的某些习惯,可以非常明显地分辨出谁更专业,谁更业余。让我们深入剖析下业余程序开发人员的几种表现,每个程序开发人员都应该引以为戒,这些错误会阻碍我们的职业发展。 一次性提交大量代码 回忆下,你是否碰到过一次性提交大量代码的人,你都不想给他做代码评审。是的,不专业的开发人员就会这样做。他们会在一次代码评审请求中包含多个模块的修改,而且会催促你优先评审他们的代码。 是啊,能不急吗,排到后边,还需要解决代码冲突的问题。这个问题在很多高级开发工程师中也存在,他们在功能开发期间不做任何提交,只有在功能彻底完工后,才会提交所有修改,于是代码评审中的任何意见都会引起大量的修改。 当我碰到这种代码评审请求时,我首先做的是要求提交者按功能模块将其拆分成多个小的请求。 我只会对 issues(任务管理系统)中的第一个功能需求评审,然后将其转回提交者。如果我有时间,我会和提交者连线进行代码实时评审。 你能做什么? 进行小的代码提交。一个好的做法是:每个工作日都进行代码提交。 不要提交没有编译或者会导致构建失败的代码。 代码写的很烂 缺乏经验的开发人员写不出漂亮的代码,他们写出的代码会很混乱,而且分布在代码库的各个部分。 当你尝试阅读这类代码时,会感觉自己身处一座迷宫之中。你会逐渐忘记自己是从什么地方开始的,要寻找什么以及这段代码完成了什么功能。 有经验的开发人员知道代码如何设计。除非要开发的功能显而易见,首先需要在纸上写出你对需求的理解并画出流程图(简化版的规格需求说明书),在脑海里对这段代码进行一个完整的构思。除非你彻底弄清楚了如何修改,否则不要开始代码编写。 如果你不遵守以上的规则,当你回顾自己完成的代码时会非常痛苦。以后如果需要修正问题或者增加功能,也会变得非常棘手。 你能做什么? 编写代码之前,对你要实现的功能有个清晰的了解。为了清楚地理解需求,你需要尽量多问问题。 让你的代码简洁而优雅。其他团队成员可以读懂代码并理解它打算做什么。 同时开展多项工作 缺乏经验的开发人员不知道什么时候开始一项任务、如何推进、什么时候结束。他们试图并行处理多项任务。他们不知道如何将一项大任务分解为小的模块,从而减轻实现的难度。 当他们收到一项任务时,并不是第一时间和上级确认需求,而是立刻就开始编程,而且在做任务期间,也不会和上级就任务进度进行沟通。 只有当任务完成时,他们才会向你反馈。到那个时候,你只能祈祷他们完成的功能就是你想要的。 缺乏经验的开发人员的另一个表现是同时推进多项任务,他们会同时处理多项事情,如:实现多个没有太大联系的功能点、解决生产环境问题、协助其他同事工作等。 最终,从他们那里得不到有效的产出。虽然他们的态度和出发点是好的,但对整个团队造成的后果是灾难性的,浪费了很多的时间,导致团队得日夜赶工。 你能做什么? 专注完成小的任务。将收到的任务分解为小块,明确需求的优先级,一小块一小块地完成。 领取一项任务,完成后再开始新的任务。 性格傲慢 对于缺乏经验的开发人员,傲慢是非常致命的。傲慢会导致他们不能接受别人的批评和建议。当你对他们的代码或者陈述给出意见时,他们会认为你是在质疑他们的能力。 许多新人由于无知,都会表现出这种傲慢。刚走出校门的他们充满自信,并没有意识到他们在学校学到的东西离社会要求还有很大差距。这些人中的聪明者会很快调整自己,以归零的心态,努力学习并适应公司文化。 其实不只是新人——一些有几年工作经验的开发人员也会表现出这种傲慢,一部分原因是其满足于个人获得的专业成就,另一部分可能的原因是其缺乏和优秀的人共事的机会,有点坐井观天。 此外,傲慢的行为也从另一方面证明这样的开发人员确实缺乏经验。这样的行为会对他们的职业发展造成很多阻碍,因为没有人喜欢和一个傲慢的人共事。当成长变慢时,他们不会从自身找原因,而是更多的归罪于别人。 你能做什么 在前行的路上保持谦卑。礼貌地对待别人会让你在软件开发职业生涯中走得更远。 尊重每一个人。出现分歧后,在你发表意见时,不管对方是什么身份,都要尊重对方。 不能从之前的错误中学到经验 我一直认为,对于软件开发人员,反馈机制是一个很有效的工具。来自他人的反馈,会让我们明白自己的短板是什么以及如何去改进。一个聪明的开发人员明白如何借助他人反馈来促进自己的成长。 根据一个开发人员对建设性意见的反应,你可以判断出他是否缺乏经验。缺乏经验的开发人员不接受任何建设性的建议,甚至代码评审中的评论,他都会认为是对他个人的一种攻击。 很多年前,我有一个同事给我写了很长的一封邮件,教我如何来评审代码,他对我给他代码的评论感到愤怒。他的主要观点是我不应该关注编码标准,因为他知道如何编码,我应该只关注代码能否满足功能需求。 如果一个开发人员因为别人对他代码给出的评论,而感觉被冒犯,只能表明他不具有真正的开发经验。他抱着做一天和尚撞一天钟的态度工作,却感慨没有遇到赏识自己的伯乐。 你能做什么? 对每个反馈保持积极的态度。对于每个反馈,你可以选择是接受还是拒绝,但拒绝之前要保持心平气和的态度。 从错误中学习。没有人能永远正确,保持终身学习才能让自己持续强大。 工作时间处理私人事务 日常工作中,总是发现团队里的一些成员在工作时间处理私人事务,如:看社交媒体,浏览购物网站,玩游戏。 我之前还有个团队成员,上班时间炒股。因为他需要不时地关注股票的 K 线走势,造成个人的产出质量不高。其他同事对他很有意见,因为他们需要花费更多的时间去赶工期。 当开发经理和这个开发人员谈话之后,他改变了一段时间,但是很快就故态复萌。最终,公司只能把他开除了。 工作时间处理私人事务,这是违反商业道德,并且表现了你的不专业。我们需要对工作敬业,毕竟我们要靠它谋生。 你能做什么? 工作时间尽量不要处理私人事务。当你需要离开几个小时去处理个人事情时,请向你的管理者请假。 使用休息时间浏览你的社交媒体。如果必须要点外卖或炒股,请利用午休时间。 盲目追逐技术潮流 开发人员缺乏经验的另一个表现是面对技术潮流的态度。你会发现他们总是在谈论技术潮流,当有一个新的潮流出现时,他们会立刻丢弃原来的潮流,投入新的怀抱。 缺乏经验的开发人员总是在学习教程。毫无疑问,教程是很有用的学习工具,但是,不进行任何实践而只是按照教程一步步操作无疑是浪费时间。 它会让你虚幻地觉得自己好像都掌握了,但是知识是否掌握了,需要通过真实的项目进行检验。 开发人员很少会用热门技术或者从教程中学到的知识来实现新的东西,他们学习热门技术或者教程很多是为了满足自己的虚荣心,或者担心自己会错过什么。 你能做什么? 花费时间和精力学习那些能在工作中或者实际项目中真正用到的技术。 从教程中学习并及时练习,相对于新手教程,自己实现一个功能能学到更多的东西。 缺乏经验的开发人员会因为自己的效率低下进而降低整个团队的效率。他对待自己工作的错误态度,会让其在职业发展中错失很多机会。 了解并避免这种错误的态度和工作方式,是聪明人的做法。如果你不幸染上了这些坏习惯,随着时间的推移,你会越来越难以摆脱。
一、异常处理实践在编写 C++ 代码时会遇到不可预期的错误和异常情况。为了让我们的代码更健壮和可靠,我们需要使用异常处理机制来处理这些情况。
在软件开发过程中,一般来说,花在测试比花在编码的时间要多很多,通常为3:1(甚至更多)。 这个比例随着你的编程和测试水平的提高而不断下降,但不论怎样,软件测试对一般人来讲很重要。 今天以嵌入式开发为例,给大家分享一下常见软件的调试方法有哪些? 很多年前,一位开发人员为了在对嵌入式有更深层次的理解,询问了这样的一个问题:我怎么才能知道并懂得我的系统到底在干些什么呢? 面对这个问题有些吃惊,因为在当时没有人这么问过,而同时代的嵌入式开发人员问的最多的大都围绕“我怎么才能使程序跑得更快”、“什么编译器最好”等问题。 面对这个不同寻常却异乎成熟的问题,可能很多人都不知道怎么办,下面就来讲讲软件测试找bug常见方法和秘诀。 1 懂得使用工具 通常嵌入式系统对可靠性的要求比较高。嵌入式系统安全性的失效可能会导致灾难性的后果,即使是非安全性系统,由于大批量生产也会导致严重的经济损失。 这就要求对嵌入式系统,包括嵌入式软件进行严格的测试、确认和验证。随着越来越多的领域使用软件和微处理器控制各种嵌入式设备,对日益复杂的嵌入式软件进行快速有效的测试愈加显得重要。 就像修车需要工具一样,好的程序员应该能够熟练运用各种软件工具。 不同的工具,有不同的使用范围,有不同的功能。使用这些工具,你可以看到你的系统在干些什么,它又占用什么资源,它到底和哪些外界的东西打交道。 让你郁闷好几天的问题可能通过某个工具就能轻松搞定,可惜你就是不知道。 那么为什么那么多的人总是在折腾个半死之后才想到要用测试工具呢?原因很多,主要有两个: 一个是害怕; 另一个是惰性; 害怕是因为加入测试工具或测试模块到代码需要技巧同时有可能引入新的错误,所以他们总喜欢寄希望于通过不断地修改重编译代码来消除bug,结果却无济于事。 懒惰是因为他们习惯了使用printf之类的简单测试手段。 下面来介绍一些嵌入式常用的测试工具(1)、源码级调试器????[Source-levelDebugger] 这种调试器一般提供单步或多步调试、断点设置、内存检测、变量查看等功能,是嵌入式调试最根本有效的调试方法。比如VxWorksTornadoII提供的gdb就属于这一种。 (2)、简单实用的打印显示工具???? [printf] printf或其它类似的打印显示工具估计是最灵活最简单的调试工具。 打印代码执行过程中的各种变量可以让你知道代码执行的情况。但是,printf对正常的代码执行干扰比较大(一般printf占用CPU比较长的时间),需要慎重使用,最好设置打印开关来控制打印。 (3)、ICE或JTAG调试器????[In- circuitEmulator] ICE是用来仿真CPU核心的设备,它可以在不干扰运算器的正常运行情况下,实时的检测CPU的内部工作情况。 像桌面调试软件所提供的:复杂的条件断点、先进的实时跟踪、性能分析和端口分析这些功能,它也都能提供。ICE一般都有一个比较特殊的CPU,称为外合(bond-out)CPU. 这是一种被打开了封装的CPU,并且通过特殊的连接,可以访问到CPU的内部信号,而这些信号,在CPU被封装时,是没法 “看到”的。 当和工作站上强大的调试软件联合使用时,ICE就能提供你所能找到的最全面的调试功能。 但ICE同样有一些缺点:昂贵;不能全速工作;同样,并不是所有的CPU都可以作为外合CPU的,从另一个角度说,这些外合CPU也不大可能及时的被新出的CPU所更换。 JTAG(JointTestActionGroup)虽然它最初开发出来是为了监测IC和电路连接,但是这种串行接口扩展了用途,包括对调试的支持。 (4)、ROM监视器???? [ROMMonitor] ROM监控器是一小程序,驻留在嵌入系统ROM中,通过串行的或网络的连接和运行在工作站上的调试软件通信。 这是一种便宜的方式,当然也是最低端的技术。它除了要求一个通信端口和少量的内存空间外,不需要其它任何专门的硬件。 提供了如下功能:下载代码、运行控制、断点、单步步进、以及观察、修改寄存器和内存。 因为ROM监控器是操作软件的一部分,只有当你的应用程序运行时,它才会工作。 如果你想检查CPU和应用程序的状态,你就必须停下应用程序,再次进入ROM监控器。 (5)、Data监视器???? [DataMonitor] 这种监视器在不停止CPU运行的情况下不仅可以显示指定变量内容,还可以收集并以图形形式显示各个变量的变化过程。 (6)、OS监视器???? [OperatingSystemMonitor] 操作系统监视器可以显示诸如任务切换、信号量收发、中断等事件。 一方面,这些监视器能够为你呈现事件之间的关系和时间联系;另一方面,还可以提供对信号量优先级反转、死锁和中断延时等问题的诊断。 (7)、性能分析工具???? [Profiler] 可以用来测试CPU到底耗在哪里。profiler工具可以让你知道系统的瓶颈在哪里、CPU的使用率以及需要优化的地方。 (8)、内存测试工具???? [MemoryTeseter] 可以找到内存使用的问题所在,比如内存泄露、内存碎片、内存崩溃等问题。如果发现系统出现一些不可预知的或间歇性的问题,就应该使用内存测试工具测测看。 (8)、运行跟踪器???? [ExecutionTracer] 可以显示CPU执行了哪些函数、谁在调用、参数是什么、何时调用等情况。这种工具主要用于测试代码逻辑,可以在大量的事件中发现异常。 (9)、覆盖工具[CoverageTester] 主要显示CPU具体执行了哪些代码,并让你知道那些代码分支没有被执行到哪里。这样有助于提高代码质量并消除无用代码。 (10)、GUI测试工具???? [GUITester] 很多嵌入式应用带有某种形式的图形用户界面进行交互,有些系统性能测试是根据用户输入响应时间进行的。 GUI测试工具可以作为脚本工具有开发环境中运行测试用例,其功能包括对操作的记录和回放、抓取屏幕显示供以后分析和比较、设置和管理测试过程(Rational 公司的robot和Mercury的Loadrunner工具是杰出的代表)。 很多嵌入式设备没有GUI,但常常可以对嵌入式设备进行插装来运行GUI测试脚本,虽然这种方式可能要求对被测代码进行更改,但是节省了功能测试和回归测试的时间。 (11)、自制工具???? [Home-madetester] 在嵌入式应用中,有时候为了特定的目的,需要自行编写一些工具来达到某种测试目的。 本人曾经编写的视频流录显工具在测试视频会议数据流向和变化上帮了大忙,帮公司找到了几个隐藏很深的bug。 2 尽早发现内存问题 内存问题危害很大,不容易排查,主要有三种类型:内存泄露、内存碎片和内存崩溃。 对于内存问题态度必须要明确,那就是早发现早“治疗”。在软件设计中,内存泄露的“名气”最大,主要由于不断分配的内存无法及时地被释放,久而久之,系统的内存耗尽。 即使细心的编程老手有时后也会遭遇内存泄露问题。有测试过内存泄露的朋友估计都有深刻地体验,那就是内存泄露问题一般隐藏很深,很难通过代码阅读来发现。有些内存泄露甚至可能出现在库当中。 有可能这本身是库中的bug,也有可能是因为程序员没有正确理解它们的接口说明文档造成错用。 在很多时候,大多数的内存泄露问题无法探测,但可能表现为随机的故障。程序员们往往会把这种现象怪罪于硬件问题。 如果用户对系统稳定性不是很高,那么重启系统问题也不大;但,如果用户对系统稳定很高,那么这种故障就有可能使用户对产品失去信心,同时也意味着你的项目是个失败的项目。 由于内存泄露危害巨大,现在已经有许多工具来解决这个问题。 这些工具通过查找没有引用或重复使用的代码块、垃圾内存收集、库跟踪等技术来发现内存泄露的问题。 每个工具都有利有弊,不过总的来说,用要比不用好。总之,负责的开发人员应该去测试内存泄露的问题,做到防患于未然。 内存碎片比内存泄露隐藏还要深。随着内存的不断分配并释放,大块内存不断分解为小块内存,从而形成碎片,久而久之,当需要申请大块内存是,有可能就会失败。如果系统内存够大,那么坚持的时间会长一些,但最终还是逃不出分配失败的厄运。在使用动态分配的系统中,内存碎片经常发生。 目前,解决这个问题最效的方法就是使用工具通过显示系统中内存的使用情况来发现谁是导致内存碎片的罪魁祸首,然后改进相应的部分。 由于动态内存管理的种种问题,在嵌入式应用中,很多公司干脆就禁用malloc/free的以绝后患。 内存崩溃是内存使用最严重的结果,主要原因有数组访问越界、写已经释放的内存、指针计算错误、访问堆栈地址越界等等。 这种内存崩溃造成系统故障是随机的,而且很难查找,目前提供用于排查的工具也很少。 总之,如果要使用内存管理单元的话,必须要小心,并严格遵守它们的使用规则,比如谁分配谁释放。 3 深入理解代码优化 讲到系统稳定性,人们更多地会想到实时性和速度,因为代码效率对嵌入式系统来说太重要了。 知道怎么优化代码是每个嵌入式软件开发人员必须具备的技能。就像女孩子减肥一样,起码知道她哪个地方最需要减,才能去购买减肥药或器材来减掉它。 可见,代码优化的前提是找到真正需要优化的地方,然后对症下药,优化相应部分的代码。 前面提到的profile(性能分析工具,一些功能齐全IDE都提供这种内置的工具)能够记录各种情况比如各个任务的CPU占用率、各个任务的优先级是否分配妥当、某个数据被拷贝了多少次、访问磁盘多少次、是否调用了网络收发的程序、测试代码是否已经关闭等等。 但是,profile工具在分析实时系统性能方面还是有不够的地方。 一方面,人们使用profile工具往往是在系统出现问题即CPU耗尽之后,而 profile工具本身对CPU占用较大,所以profile对这种情况很可能不起作用。 根据Heisenberg效应,任何测试手段或多或少都会改变系统运行,这个对profiler同样适用! 总之,提高运行效率的前提是你必须要知道CPU到底干了些什么干的怎么样。 4 不要让自己大海捞针 大海捞针只是对调试的一种生动比喻。经常听到组里有人对自己正在调试的代码说shit! 可以理解,因为代码不是他写的,他有足够的理由去 shitbug百出的代码,只要他自己不要写出这种代码,否则有一天同组的其它人可能同样会shit他写的代码。 为何会有大海捞针呢?肯定是有人把针掉到海里咯;那针为何会掉在海里呢?肯定是有人不小心或草率呗。 所以当你在抱怨针那么难找的时候,你是否想过是你自己草率地丢掉的。 同样,当你调试个半死的时候,你是否想过你要好好反省一下当初为了寻求捷径可能没有严格地遵守好的编码设计规范、没有检测一些假设条件或算法的正确性、没有将一些可能存在问题的代码打上记号呢? 关于如何写高质量请参考林锐的《高质量c++/c编程指南》或《关于C的0x8本“经书》。 如果你确实已经把针掉在海里是,为了防止在找到之前刺到自己,你必须要做一些防范工作,比如戴上安全手套。 同样,为了尽能地暴露和捕捉问题根源,我们可以设计比较全面的错误跟踪代码。怎么来做呢? 尽可能对每个函数调用失败作出处理,尽可能检测每个参数输入输出的有效性,包括指针以及检测是否过多或过少地调用某个过程。错误跟踪能够让你知道你大概把针掉在哪个位置。 5 重现并隔离问题 如果你不是把针掉在大海了,而是掉在草堆里,那要好办些。因为至少我们可以把草堆分成很多块,一块一块的找。 对于模块独立的大型项目,使用隔离方法往往是对付那些隐藏极深bug的最后方法。 如果问题的出现是间歇性的,我们有必要设法去重现它并记录使其重现的整个过程以备在下一次可以利用这些条件去重现问题。 如果你确信可以使用记录的那些条件去重现问题,那么我们就可以着手去隔离问题。怎么隔离呢? 我们可以用#ifdef把一些可能和问题无关的代码关闭,把系统最小化到仍能够重现问题的地步。 如果还是无法定位问题所在,那么有必要打开“工具箱”了。可以试着用ICE或数据监视器去查看某个可疑变量的变化;可以使用跟踪工具获得函数调用的情况包括参数的传递;检查内存是否崩溃以及堆栈溢出的问题。 6 以退为进 猎人为了不使自己在森林里迷路,他常常会在树木上流下一些标记,以备自己将来有一天迷路时可以根据这些标记找到出路。对过去代码的修改进行跟踪记录对将来出现问题之后的调试很有帮助。 假如有一天,你最近一次修改的程序跑了很久之后忽然死掉了,那么你这时的第一反映就是我到底改动了些什么呢,因为上次修改之前是好的。 那么如何检测这次相对于上次的修改呢?没错,代码控制系统SCS或称版本控制系统 VCS可以很好地解决这个问题。 将上个版本checkin下来后和当前测试版本比较。比较的工 具可以是SCS/VCS/CVS自带的diff工具或其它功能更强的比较工具,比如BeyondCompare和 ExamDiff。通过比较,记录所有改动的代码,分析所有可能导致问题的可疑代码。 7 确定测试的完整性 你怎么知道你的测试有多全面呢?覆盖测试(coveragetesting)可以回答这个问题。覆盖测试工具可以告诉你CPU到底执行了哪些代码。 好的覆盖工具通常可以告诉你大概20%到40% 代码没有问题,而其余的可能存在bug.覆盖工具有不同的测试级别,用户可以根据自己的需要选择某个级别。 即使你很确信你的单元测试已经很全面并且没有 deadcode,覆盖工具还是可以为你指出一些潜在的问题。 看下面的代码: if(i>=0&& (almostAlwaysZero==0||(last=i))) 如果almostAlwaysZero为非0,那么last=i赋值语句就被跳过,这可能不是你所期望的。 这种问题通过覆盖工具的条件测试功能可以轻松得被发现。总之,覆盖测试对于提高代码质量很有帮助。 8 提高代码质量意味着节省时间 有研究表明软件开发的时间超过80%被用在下面几个方面:调试自己的代码(单元测试)。 调试自己和其他相关的代码(模块间测试)。调试整个系统(系统测试),更糟糕的是你可能需要花费10-200倍的时间来找一个 bug,而这个bug在开始的时候可能很容易就能找到。 一个小bug可能让你付出巨大的代价,即使这个bug对整个系统的性能没有太大的影响,但很可能会影响让那些你可以看得到的部分。 所以我们必须要养成良好的编码和测试手段以求更高的代码质量,以便缩短调试的代码。 9 发现它、分析它、解决它 这世界没有万能的膏药。profile再强大也有力不从心的时候;内存监视器再好,也有无法发现的时候;覆盖工具再好用,也有不能覆盖的地方。 一些隐藏很深的问题即使用尽所有工具也有可能无法查到其根源,这时我们能做的就是通过这些问题所表现出来的外在现象或一些数据输出来发现其中的规律或异常。 一旦发现任何异常,一定要深入地理解并回溯其根源,直到解决为止。 10 请利用初学者思维 有人这样说过:“有些事情在初学者的脑子里可能有各种各样的情况,可在专家的头脑里可能就很单一”。 有时候,有些简单的问题会被想得很复杂,有些简单的系统被设计得很复杂,就是由于你的“专家思维”。 当你被问题难住时,关掉电脑,出去走走,把你的问题和你的朋友甚至你的小狗说说,或许他们可以给你意想不到的启发。 11 总结 嵌入式调试也是一门艺术。就想其它的艺术一样,如果你想取得成功,你必须具备智慧、经验并懂得使用工具。
所谓领域专用语言(domain specific language / DSL),其基本思想是“求专不求全”,不像通用目的语言那样目标范围涵盖一切软件问题,而是专门针对某一特定问题的计算机语言。DSL之于程序员正如伽南地之于以色列人,是...
C语言是一种计算机程序设计语言,它既具有高级语言的特点,又具有汇编语言的特点。它由美国贝尔研究所的D.M.Ritchie于1972年推出,1978年后,C语言已先后被移植到大、中、小及微型机上,它可以作为工作系统设计语...
由于可移植性好,相当一部分嵌入式软件都是用C/C++语言开发的,而C/C++语言编写的程序中数据存储字节顺序是与编译平台所用的CPU相关的,所以嵌入式软件移植过程中,数据存储字节顺序是需要重点处理的地方。 在嵌入...