新人程序员不会写代码新人怎样在复杂代码中找 bug

& 程序员新人,如何在复杂代码中找 bug?
程序员新人,如何在复杂代码中找 bug?
编程学习网
首先,从bug入手,了解codebase,应该是平衡mentor和新人之间利益最大的办法。其实要想入手最快,就应该是让mentor24*7的在你旁边手把手教你,但这根本不现实,也没有意义。
修改bug入手,通过一个个小bug去了解整个project的结构和design pattern,对新人来说,这种学习既直观又不会被复杂的代码吓死。最主要的是,当你成功fix了一个bug,这种成就感是一个新入职的程序员勇往直前的动力。而且,修了一个bug,最重要的不是你unblock了多少人,或者帮助了多少用户,而是你从这个bug里看到了project怎么样的结构。当初为什么这么设计,为什么会出bug,时不时codebase里还有类似的bug,以后怎么避免。
几点debug的经验:
1. 优先解决那些可重现的,可重现的bug特别好找,反复调试测试就好了,先把好解决的干掉,这样最节约时间。
2. 对于某些bug没有头绪或者现象古怪不知道从哪里下手,找有经验的同事问一下思路,因为在那种开发多年的大型系统里,经常会反复出现同样原因的bug,原因都类似,改了一处,过一阵子另外一处又冒出来,而且无法根治。
比如:我那个系统里有个特别危险的API,接口参数比较难用,一旦有人用错了某些情况下就会出诡异的现象,解决很简单,找到调用这个API的地方把调用方式写对就好了。为什么不根治呢?因为要保持兼容性不能改接口了。Windows系统里就好多这种烂API。
问下老员工吧,说不定他们都遇到过好多次了。
3. 放大现象,有些bug现象不太明显,那么就想办法增大它的破坏性,把现象放大。这只是个思路,具体怎么放大只能根据具体的代码来定。
比如:美剧《豪斯医生》里有一集,怀疑病人心肺有问题,就让病人去跑步机上跑步,加重心肺负担,从而放大症状。
4. 二分法定位,把程序逻辑一点点注释掉,看看还会不会出问题,类似二分查找的方法,逐步缩小问题范围。
5. 模拟现场,有时候问自己,如果我要实现bug描述的现象我要怎么写代码才行?
比如:遇到一个死锁问题,但是检查代码发现所有的锁都是配对的,没有忘记解锁的地方,而且锁很简单就是一个普通的临界段,保护几行赋值语句而已。这样的代码怎么写才能让他死锁呢?
如果让我故意制造这样一个现象,只有在上锁的时候强制杀掉线程了。
既然这样就可以去看看有谁强杀线程了没有。
6. 制作工具,针对某些bug编写一些调试辅助工具。
比如,那个系统没有完善的崩溃报告,虽然也有dump,但是分析出来的callstack经常不准。于是为解决崩溃问题编写了个工具,会自动扫描代码,在每个函数入口和出口插入log,以此来定位崩溃点。
7. 掩盖问题,虽然这样做有点不厚道,但是有时不得不这么做。有些bug找不到真正的root cause,但是又要在规定时间内解决,那么我们就可以治疗症状而不去找病因。比如用try catch掩盖一些奇怪的崩溃。不到万不得已不要这么干,未来可能会付出更大代价。
其实新人很多时候都会觉得程序某个地方很“神奇”,明明应该这样,但却那样。千万不要跟你的mentor抱怨“神奇”,因为这两个字在整个代码行业就不存在。老板经常给我们讲的一句话是,“code never lies”。代码运行异常,一定有运行异常的原因。不要揪着“为什么是这种异常”不放,而要去想“什么样的结果是对的”以及“怎么产生对的结果”。
学习framework。project变大了,framework必不可少。但因为framework的存在,让整个project变得更“捉摸不透”。所以,花点时间学学你们用得framework,以及针对这种framework debug的工具,静下心看看文档,自己在动手写一写,其实framework真的很美。
最后,时刻保持跟你mentor之间的sync up。让他知道你的困境,也让他知道你的成就。mentor也是从新人走过来的,没什么不能讲的。
文章整理于【知乎】
图片来源于【网络】
扫一扫 关注敲代码 聊聊编程技术 聊聊程序人生。
本文固定链接:
转载请注明:转载必须在正文中标注并保留原文链接
编辑:放飞梦想
有人的地方,就有江湖;有网络的地方,就有编程学习网。程序员的家园,IT界的烟火。
学习编程5个常见的疑问你从入职第一天起就要应对复杂代码。
若是还未遇到过无法理解的程序,那说明你编程的年头还不够长。在行业里,要不了多久你就会碰到让人发懵的混乱代码:巨兽、面条工厂、来自地狱的遗留系统。我曾接手过一个程序,它的前任在听说要增加一个分量不轻的新特性时,选择了辞职。(我并不怪他。)
软件系统的复杂度是不可避免的。有些问题就是很难,它们的解决方案很复杂。然而,你在软件中找到的大多数复杂度是我们自己造成的。在《The Mythical Man-Month》(人月神话)[Bro95]里,Fred Brooks将复杂度的两个来源分成必然(necessary)复杂度和偶然(accidental)复杂度。
这里有一种区分必然复杂度和偶然复杂度的思考方法:什么复杂度是问题域固有的?假设你面对的是一个日期/时间处理代码散落各处的程序。在处理时间时,存在一些必然复杂度:每月的天数不同,必须考虑闰年,等等。但多数我碰到的程序充斥着大量与处理时间相关的偶然复杂度:用不同格式保存的时间,加减时间的新奇(同时也是充满Bug的)方法,不一致的时间打印格式,说都说不完。
复杂度的死亡螺线
编程时常会遇到这种情况:产品代码库中的偶然复杂度渐渐压倒必然复杂度。情况在某一时刻会自我放大,我称这种现象为复杂度的死亡螺线,如图1所示。
图1 复杂度的死亡螺线
问题1:代码规模
构建产品时,它的代码规模最终将远超任何在学校或消遣项目中所遇到的。行业中的代码库的度量结果从成千到上百万代码行(Line of Code, LOC)不等。
John Lions在《Lions’ Commentary on UNIX 6th Edition》一书中写道:单个程序员能够理解和维护的程序大小的实际限制规模是1万行代码。于1975年发布的UNIX第6版的规模大约是9000行代码(不算机器特定的设备驱动程序)。
相比而言,Windows NT在1993年有4百万~5百万行代码。10年后,Windows Server 2003配备了2000名开发人员和2000名测试人员,他们管理多达5千万行代码。大多数行业项目并不像Windows那样巨大,但它们也都轻易地跨过了Lions设定的1万行代码的警戒线。这样的规模意味着公司内部没有人能理解整个代码库。
问题2:复杂度
随着代码规模的增长,最初想法的概念优雅性消失了。曾经对于车库中两个小伙水晶般清澈的想法变成了大批开发人员艰难跋涉其中的阴暗沼泽。
复杂度并不是代码规模的必然产物。大型代码库完全有可能被拆分成许多模块,其中每个模块都有清晰的用途、优雅的实现和为人熟知的与邻近模块的交互。
然而,即使设计良好的系统也会在它们变大时变得复杂。一旦没有一个人可以理解整个系统,这时多个人必须去理解系统中自己那部分—且没有人的理解跟其他人是完全一样的。
问题3:Bug
产品复杂度飙升,Bug也就不可避免地出现了。这是注定的—就算是伟大的程序员也不是完人。但每个Bug并非生而平等:高度复杂系统里的那些Bug尤其难觅踪迹。总是听到程序员说:“真搞不懂,伙计,系统刚刚崩溃了。”欢迎来到这糟糕的调试世界!
问题4:快速修补
问题并不在于产品是否有Bug—它肯定有,关键在于工程团队在出现Bug之后如何响应。在推出产品的压力之下,大多数程序员经常求助于快速修补。
快速修补是给问题打补丁,而非解决其根本原因。甚至常常不寻找根本原因。
程序员:在试图往网络队列中放入一个任务(job)且队列在10秒内无响应时,程序崩溃了。
经理:重试队列操作100次。
根本原因是什么?天知道,只要重试次数够多,你就可以掩盖任何问题。但如车身修补一样,某一位置的霸道胶水(Bondo)比实际残留的车本身部件还要多。
更难找的问题发生在补丁并没有解决问题根本原因的时候,问题通常根本没有消失—它只是转移到别处。在前面的对话中,重试100次可能很好地掩盖了问题,但万一需要101次重试怎么办?经理只是随便捏了个数字,这种膏药式修补只会让问题更难查。
沿着“快速修补”上行,我们现在得到了一个增加代码规模的完整闭环。
提起复杂的反面,人们通常会想到简单。但由于领域的必然复杂度,我们并不是总能写出简单的代码。应对复杂更好的方法是清晰。你是不是明白自己的代码要做什么?
明确两点会有助于我们减少软件偶然复杂度:清晰思考和清晰表达。
在分析问题的原因时,我们试图做出像“保存时间的方式应该只有一种”这样的清晰陈述。那为何UNIX C代码里还混杂着像time_t、struct timeval和struct timespec这样的结构呢?那并不是太清晰。
如何调和你的清晰陈述和UNIX计时功能的复杂度?你需要隔离复杂度,或将其抽象到单个模块中。在C里,这可能是结构体和操作它的函数;在C++里,它会是一个类。模块化设计让程序的其余部分可以用一种清晰的方式推导时间,而不用了解系统计时功能的内部机制。
一旦能将时间作为程序的一个单独模块进行对待,你也就能证明你的计时机制的正确性。完成这一工作的最佳方式就是单独测试,但是同行评审或书写规格说明也行。当一组逻辑是隔离的而不是内嵌在一大段代码体内时,它的测试和严格证明要容易得多。
随着你清晰地思考模块并将它与其余程序隔离,最终程序也就能更清晰地表达它的用途。处理问题域的代码应该真正专注于问题域。
将辅助代码抽出放入自己的模块之后,剩余逻辑读起来应该越来越像问题域的规格说明(虽然有更多分号)。
让我们看看前后对比。我已经无数次看到过如下这种C++代码:
循环的关键是“干活”部分,但在实际干活之前有20行的POSIX计时代码块。这并没有什么不对,但……就没有一种方法让循环保持对其问题域而不是对计时的关注吗?
让我们把所有时间代码放入它自己的类:
我们现在可以简化循环了:
计算机在上述两种情况下做的事情是相同的,但考虑第二个例子对程序可维护性带来的影响:
Timer类的测试和证明可独立于它在程序中的使用方式。“干活”循环内的计时有了有意义的语义—triggered()和reset(),而不是一堆获取、增加和比较函数。现在对于计时的终止位置和(捏造的)循环实际起始位置都清晰了。
当工作在巨大丑陋的代码上时,依次考虑:这段代码想表达什么含义?它有没有办法说得更清楚一点?如果它是清晰表达的问题,你需要把那些碍事的代码段抽象出来,同前面展示的Timer类一样。若代码还是有点混乱,那可能是没有清晰思考的产品,需要在设计层面返工。
聚焦于可被隔离和严格推导的一个编程方面,如计时。挖掘你正在从事的项目,识别出这样的代码段:若那部分逻辑被抽象到自己的模块,它能否表达得更清晰。
动手尝试更模块化的方法:选一组混乱的代码,分离必然复杂度和偶然复杂度。在这一刻不要操心细节,只看如何可以清晰地表达必要的业务逻辑,假设你有独立模块来处理支撑逻辑。
------------------------------------
本文节选自《程序员修炼之道:专业程序员必知的33个技巧》“技巧4:驯服复杂度”。
书名:《程序员修炼之道:专业程序员必知的33个技巧》
原书名:New Programmer's Survival Manual: Navigate Your Workplace, Cube Farm, or Startup
作者:Josh Carter
译者:胡键
定价:49.00元
豆瓣收藏:
当当购买:
内容简介:
这是每一位致力于成为专业程序员的软件开发新手都应该阅读的一本书。它是资深软件开发专家Josh Carter 20余年编程生涯的心得体会,从程序员成长的视角,系统总结和阐述了专业程序员在专业技能、编程工具、自我管理、团队协作、工作态度以及需要采取的行动等方面应该掌握的33个非常重要且实用的技巧。作者以自己以及身边的同事积累下来的经验、犯过的错误为素材,旨在为新人们引路,让他们在能力修炼的过程中少走弯路!
全书分为四个部分:第一部分(技巧1~14),从编程技能和工具使用两个方面总结了14个技巧,包含如何正确地书写代码、测试驱动设计、管理代码复杂度、改善遗留代码、代码评审、开发环境优化、自动化等;第二部分(技巧15~24),从自我管理和团队协作两个方面总结了10个技巧,包括如何树立自我形象、压力管理、建立良好人脉和高效会议等;第三部分(技巧25~30),介绍了典型高科技公司的组织结构以及你在整个公司中的位置,并且阐述了薪酬分配的问题;第四部分(技巧31~33),介绍了在日常工作中如何持续改善自己的工作和学习状态。
作者简介:
Josh Carter,资深软件设计师,具有超过20年编程行业从业经验。热衷于编程和追逐前沿技术,但同时谨记史蒂夫o乔布斯的箴言“真正的艺术家能让产品面市”。他还涉足工程管理领域,曾经主管大型企业软件开发团队。目前已出版多本关于计算机软件的技术书籍,同时他还在主流计算机杂志的技术专栏发表文章。
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:934375次
积分:12203
积分:12203
排名:第883名
原创:150篇
转载:941篇
评论:206条
(1)(1)(1)(9)(4)(6)(3)(4)(5)(11)(3)(6)(9)(9)(18)(9)(19)(23)(8)(32)(17)(7)(8)(12)(2)(30)(20)(27)(31)(34)(42)(43)(59)(41)(31)(74)(11)(16)(4)(37)(25)(43)(23)(51)(146)(80)程序员如何做到「编程速度又快,Bug 数量又少」?李清波个人博客网站,分享个人技术,分享个人心得。程序员如何做到「编程速度又快,Bug 数量又少」?  最近看到一个Quora中的回答,答到心坎上。译文引用自伯乐在线:  三个程序员被要求穿过一片田地,到达另一侧的房子。  菜鸟程序员目测了一下之间很短的距离,说:“不远!我只要十分钟。”  资深程序员看了一眼田地,想了一会,说:“我应该能在一天内过去。”菜鸟程序员很惊讶。  大神程序员看了一眼田地,说:“看起来要十分钟,但我觉得十五分钟应该够了。” 资深程序员冷笑了一声。  菜鸟程序员出发了,但只过了一会,地雷爆炸了,炸出了巨大的洞。这下他必须偏移预定的路线,原路返回,反复尝试穿过田地。最后他花了两天到达目的地,到的时候颤颤发抖,还受了伤。  资深程序员一出发就匍匐前进,仔细地拍打地面,寻找地雷,只有在安全的时候才前进。他在一天的时间内小心谨慎地缓慢爬过了这片地,只触发了几个地雷。  大神程序员出发之后径直穿过了田地,十分果断。他只用了十分钟就到了另一边。  “你是怎么做到的?”另外两个人问道,“那些地雷怎么没有伤到你?”  “很简单,”他回答道,“我最初就没有埋地雷。”  地雷从何而来?  与大神一起工作的时候就是这种感觉,也是我的第一印象「代码整洁而且没有地雷」。可是反过来想,为什么有的人就是习惯于埋雷呢?难道看不到后果吗?
再深一层来讲,没人愿意给自己埋地雷,主要还是没有对习惯引起重视。培养起好的编程习惯是非常重要的。编程中的坏习惯,是很多程序员上升的天花板,十年原
地踏步的原因。想要写出好代码,一定要常常问自己「我还能做的更好吗?」(好吧,这是算法课的口号)。只有想不断提升的人,才会注意去扣各种各样的细节,
使得自己做得比前一次好。下面总结一下,常见的「地雷」有哪些:  没有提前构建  没有规划完整系统的生命周期,内存泄露到处都是。  系统没有设计好,存在不少重复功能的类。  系统的行为没有定义好,接口设计不完整,写了创建不写删除。  没有重视代码的可读性  代码没有经过提炼,到处都是重复代码,改一个功能常常要改很多处代码。  缺少抽象,将具体实现暴露得到处都是。比如一个状态机在外部设置它的状态切换。  代码没有紧贴语义。  没有重视开发效率  到处都是繁杂重复的配置项,通过约定可以省去很多配置。  很多中间代码,比如解析xml、解析协议等等的工作,通过元编程可以将这些中间工作自动化。  没有重视数据  数据没有处理好,敏感数据要保护好,比如角色的属性,到处都是直接赋值的话,很容易出错,要把直接改变限制在少数的某几个函数里。  某些系统知道得太多,比如数据层就不应该知道显示层的东西,显示和数据掺杂在一起。  数据依赖于代码,比如写在C++的模板里面,没有为数据设计序列化文件。  更深层的原因  “我不是什么伟大的程序员,我只是一个有着很多好习惯的程序员”—-Kent Beck  在《程序员修炼之道》里,第一条就是「关心你的技艺」。如果你不在乎能否漂亮地开发出软件,你又如何要耗费生命去开发软件呢?上一篇:下一篇:推荐博文程序员,你调试过的最难的 Bug 是_程序员吧_百度贴吧
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&签到排名:今日本吧第个签到,本吧因你更精彩,明天继续来努力!
本吧签到人数:0成为超级会员,使用一键签到本月漏签0次!成为超级会员,赠送8张补签卡连续签到:天&&累计签到:天超级会员单次开通12个月以上,赠送连续签到卡3张
关注:79,395贴子:
程序员,你调试过的最难的 Bug 是收藏
回想起这个 bug,仍然让我有些痛苦。作为一个程序员,在发现bug时,你学会了首先在自己代码中找问题,或许在测试一万次之后,你会把问题归咎于编译器。只有在这所有的都不起作用之后,你才会把问题归咎于硬件。这是我遭遇一个硬件bug的故事。抛开别的不说,我曾为《Crash Bandicoot》写存储卡(读写)代码。对于一个自大的游戏程序员,这就像是在公园里散步一样轻松愉快,我认为只要几天就写完了,最终调试用了六个礼拜。在此期间我做一些其他的事情,但我一直回来处理这个bug——几天内每天几个小时。这个bug实在烦人。这个bug的症状是,当你需要保存你的进度时,代码会访问存储卡,而大部分情况下没有什么问题…但是偶尔读写会超时…没有任何明显的原因。一个短小的写入经常毁掉存储卡。玩家要保存进度,我们不仅不保存,还擦除他们存储卡上的全部东西。天哪。过了一段时间,我们在Sony的制作人Connie Booth慌了。我们显然不能带着这个bug发布游戏,而六个星期之后我对于问题出在哪一点线索都没有。通过Connie我们向其他 PS1 开发者求助:有没有人出现过像我们这样的情况?没有。绝对没有任何人在存储卡系统上出现任何问题。在你绞尽脑汁之后,你能做的唯一一个调试方法就是分而治之:一点点去除程序中的代码,直到留下的代码很少但你仍然出问题。像木雕一样去除没有问题的代码,留下的就是你的bug所在。在这样的背景下挑战在于,视频游戏是很难去除某一部分的。在你删除模拟重力或者显示字符的代码后,如何运行游戏?你必须做的是用一个假装做真正的事情,但实际上只是做很简单的不会出现bug事情的东西来替换掉整个模块。你必须写新的支撑代码来让这些玩意正常工作。这是一个缓慢而痛苦的过程。长话短说:我做完了。我移除了大片大片的代码,相当多,只留下了初始化代码——就是准备游戏运行系统,初始化底层硬件等等。当然,我不能显示加载/保存菜单,因为我截除了所有的图像代码。但是我能够假装用户使用(不可见的)加载/保存屏幕并且请求保存,然后写入卡中。我最终以一个带有这个bug的很少量的代码结束——但问题仍然随机出现!在大多数情况下没啥问题,但是偶尔会失效。基本上所有的Crash的实际代码都被移除了,但还是这样。这实在是莫名其妙:留下来的代码基本上都没做什么事。在那时——估计是凌晨3点——一个想法蹦了出来。读写(I/O)涉及精确定时。无论是硬盘、存储卡、蓝牙发送器——随便啥——做读写的底层代码都是根据时钟来的。时钟让不直接连接到CPU的硬件设备和cpu运行的代码同步。时钟决定了波特率——数据从一头传到另一头的速率。如果计时有什么问题,硬件或者软件或者两者都会乱七八糟的。这真的,真的很糟糕,并且通常导致数据损坏。如果我们的初始化代码以某种方式弄乱了计时会怎么样?我又看了一遍测试程序中和计时有关的代码,并注意到我们将PS1上的可编程计时器设置到了1kHz(1000跳每秒)。这是比较快了,当PS1启动的时候,默认状态大概是100Hz。因此,大多数游戏将他们的计时器设置为100Hz。这个游戏的带头(和除我外的唯一)开发者Andy,将计时器设置为1kHz,使得Crash的动作计算更加准确。Andy喜欢矫枉过正,如果我们要模拟重力,我们应该尽可能的提高精度!然而如果提高计时器频率莫名其妙的干扰了整个程序的计时,故而将这个计时器设置到存储卡的波特率上会怎样呢?我将计时器代码注释掉。然后我就无法复原这个bug了。但是这并不表示bug被修复了,这个问题是随机发生的。万一我只是运气好呢?几天过去了,我还是在玩我的测试程序。Bug没有再出现。我回到全部的Crash代码中,修改了加载/保存代码,在访问存储卡之前将可编程计时器重置为默认设置(100Hz),之后设置回1kHz。从此之后没有发现问题再次出现。但是…为什么?我重新回到测试程序上,试着检测当计时器设置为1kHz时出现的那些错误的模式。终于,我注意到这些错误出现在使用PS1手柄的人身上。因为我自己很少这样做,所以我没有注意到(为啥我要在测试加载/保存代码的时候用手柄)。但是有一天我们的美工等我去完成测试(我确定那时候我在爆粗口),而他紧张的摆弄着手柄。卡损坏了。“等下,怎么回事?喂,再来一次!”一旦我发现了这两件事是联系着的,就很容易重现bug:开始写入存储卡,动一下手柄,存储卡损坏。在我看来完全是硬件bug。我去找Connie告诉他我的发现。她转述给设计过PS1的硬件工程师。她被告知:“不可能,这不可能是硬件问题。”我跟她说问一下我能不能直接和他说。那个工程师给我打电话了,他用着他的烂英语,我用着我更烂的日语,我们争论一会。我最后说:“我给你一个30行的测试程序,让你在动手柄的时候能够出现这问题。”他答应了。他向我保证,这是浪费时间,而他正在一个新项目上很忙,但因为我们是Sony很重要的开发者,他会试的。第二天晚上(我们在洛杉矶,而他在东京,所以对于我来说是晚上而他是到了第二天),他给我打电话,不好意思的向我道歉。这是个硬件问题。我还是没有完全搞清楚问题到底在哪,但是我的印象中,从Sony总部的反馈听到的是,如果将可编程计时器设置到足够高的时钟频率,会影响到主板上时钟晶振附近的一些东西。这些东西之一就是存储卡的波特率控制器,同时也设置手柄的波特率。我不是搞硬件的,所以对于细节我相当模糊。但是主旨是主板上两个独立部分的串扰,以及手柄接口和存储卡接口数据发送的结合在1kHz的时钟频率下会导致丢位,从而数据丢失,以致卡损坏。这是我全部编程生涯中,唯一一次因为量子力学 debug 的问题。
登录百度帐号推荐应用
为兴趣而生,贴吧更懂你。或}

我要回帖

更多关于 程序员有必要带新人么 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信