老文重登:程序员的成长从开窍开始之《如何摆脱低级错误的困扰》

tinyfool 发布于 2013年08月31日 | 更新于 2013年09月09日
无人欣赏。

把我一篇我自己网站都已经佚失的老文分享给大家,至少主题到现在还没有过时,虽然也许是05-06年写的。

程序员的成长从开窍开始之《如何摆脱低级错误的困扰》

最近,有两位Google Maps API的初学者向我请教他们按照最简单例子写的程序为什么不能正常的运行。

其中一位用GTalk跟我交流,我仔细了看了他的代码,没看出问题,把代码保存在本地,打开Firefox的错误控制台,用Firefox打开他的页面。出错的那一行被清晰的显示出来,我再仔细端详那句话,原来有两个应该是英文逗号的地方,写上了中文逗号。

另一位,在我的论坛跟我交流他的Google Maps API中遇到的问题,我看他代码的时候也没有马上发现问题。然而,同样在用Firefox打开后,问题很明显的找到了,原来是一个方法openInfoWindow被他写成OpenInfoWindow了。

在我帮助别人解决的程序调试问题中,这是非常常见的。人人都可能打出中文逗号,人人都可能把大小写写错。但是在我帮助他们解决问题以后,他们总是感慨的说,谢谢我解决了这个问题,这个问题困扰了他们几个小时,甚至是几天。

这其实并不是只有初学者才会遇到的问题,我还帮助过些有非常丰富经验的工程师解决问题,有时候问题仅仅出自某个参数没有传递进来,或者是拼接字符串的时候少些了一个冒号,或者是拼接地址的时候漏掉了http:。我甚至帮助一些人调试一些我根本不懂的语言的程序,因为多半出现的问题,都和语言特性无关,不是程序员写错了字符,就是写错了逻辑,或者是错误理解了一个函数。

出问题是正常的,写程序是一个复杂的边思考边打字的过程,笔误和一时糊涂都是难以避免的。程序员一般把这种问题叫做低级问题,因为这类问题跟你的智商完全无关,任何人都可能犯。

但是,问题在于,有时候即使是很优秀的程序员,也会被一个低级错误困扰,可能会几天都解决不了。所以,关键在于,如何找到问题。

遇到问题的时候:

  1. 不要怨天怨地。出了问题,当然有可能是系统的bug,API的问题,但是那些几率往往比你犯低级错误的几率要低多了,先从自己身上找原因,是不是自己写错了。
  2. 要掌握工具。最低限度你要会写Log,最好是Log和调试器结合。好 的工具可以大大的提高效率。以前有人跟我说,Dll不能调试,我发现可以;有人说多线程不能调试,我发现可以;有人说COM不能调试,我发现可以;有人说 IE插件不能调试,我发现可以;有人说OE插件不能调试,我发现也可以。当然,你确实会遇到不能调试的时候,当年我们做东芝芯片的嵌入程序,一个组都没有 一个仿真器和调试器,但是至少可以用Log嘛,无非是麻烦点。
  3. 分析问题要有逻辑。遇到问题可以先把所有的可能性都列出来,然后一个一个分析,肯定能找到原因的。
  4. 要学会隔离问题。问题涉及到的代码越多,越难以理解,问题越难以解决。遇到这样的情况,可以利用Log或者调试器,一行代码一行代码的给它们洗清嫌疑,这样很快你就可以找到出问题的地方。如果代码特别长,程序特别复杂,可以用二分法来做,效率很高。
  5. 千万不要懒惰,不要事事求别人。一次复杂的调试过程就像一部侦探剧,如果你有非常好的逻辑性,那这部剧的主角就是福尔摩斯,剧情一定非常精彩。我说这个是有巨大风险的,说真的我帮人调东西挺上瘾的,很有意思。但是我还是要告诉大家,一次高难度的调试之后,你的满足感绝对不亚于写了一个伟大的程序。

要想不遇到问题,写代码的时候:

  1. 要对写出来的代码负责。我很佩服那些写代码写100行都不执行一次的 高手,如果他们最后不被低级错误困扰的话我就更加的佩服了。我写程序几乎是写一行两行就要执行一次,每句话我都要确保执行效果跟我的预期一致。没错这样写的时候 可能慢一些,但是调试的时候很轻松,我可以很简单的确定哪些代码绝对没有问题。所以我写代码整体速度比一般人高。很多人学习新东西的时候喜欢把例子抄一遍,运行一下,改改,再运行。我喜欢一句一句的抄例子,抄一句两句执行一次,这样可以把例子透彻的理解,而且很难会遇到出现了问题找不到原因的时候。
  2. 函数体功能块不要过长。我认为我的智商并不高,我很难接受一个程序的一个函数体或者一个功能块超越3屏(当然逻辑真的有那么复杂除外,你会发现越是简单的逻辑越是容易被人写的冗长)。很多人对面向对象耳熟能详,对封装继承看起来驾轻就熟。但是动不动就写出来个函数体超长的程序。这就像写本书从头到尾不点句号一样,会累死读者的。自己看的时候,估计也会被累的喘不过来气。这是我对基础教育的微词所在,他们连教会学生写函数都没教会,虽然表面上他们连面向对象这么高深的东西都教。
  3. 缩进要对。这点很重要,虽然大部分语言不是像Python那样用缩进来决定逻辑块的位置,但是人看到缩进的时候,总是会以为这些缩进位置跟逻辑相关。尤其是在有大量的ifelse或者for循环等等的嵌套逻辑的时候,如果缩进错了,可能会直接让人把程序的逻辑读错。所以我拿到别人的代码,第一件事情就是整理缩进。我见过一些比较优秀的页面工程师,他们会在div结束的位置用注释写上这个div的id,这样层级关系就一目了然了。
  4. 不断重构。随着程序的不断修改,有些部分会不断的增长,原来看着清晰的架构可能因为问题的复杂而慢慢模糊,也可能被修正bug的权宜之计弄的面目全非。不信你找一个经过多次修改的程序看看,是不是满目疮痍,是不是都很难认出是你自己的作品了。这在多人参与的项目中更加严重,每个人有不同的代码风格,经过多次杂交后,你肯定认不出你的代码是骡子是马,还是四不像了。随着程序的慢慢成长,原来有些函数体会慢慢膨胀,需要拆分;有些原来简单的功能块四处都需要,应该被提炼成函数或者方法,等等。现在不重构,未来等到代码复杂到无法控制的时候,重构的工作就会变得更加困难。我见过最强的案例是,一个几千行的电子辞典配套联机软件,经过无数次的改版,变成了一个几乎无法维护的主窗体的cpp有1万8千行的怪物。最后经过复杂的重构,才变成一个出新版本只需要新增一个驱动程序的可以维护的几千行的程序。
共16条回复
tinyfool 回复于 2013年08月31日

这么多年了,我的想法也有了很多变化,但是还没时间整理关于这篇文章的升级版,等到整理了再发布升级版。

vikiliu0310 回复于 2013年08月31日

听说是胖子老师送的,哈哈哈~~必须拜读了

昨天问的一个关于XCode的问题,研究了一下午,无果,上网也没翻到答案,终于在今天,那个错误才因为另外一个同事的误打误撞,我发现了原因。

随着踏入职场,我认为自己越来越有调理,例如缩进,例如功能分隔标识,我都慢慢习惯了,觉得不写不舒服

我自己个人而言,并不喜欢着急问问题,特别是我成为程序员以后,恩~还有关注胖头陀和瘦头陀以后,问问题其实一点也不能马虎,各种整理后发现问题情况整理出来后还没找到原因,这时候才是该问的时候,如果着急问那是白白浪费一个思考的机会。 其实遇到问题是挺好的,无论大小,这是经验累计的机会,小至低级错误,下次就能快速解决,大至逻辑实现,逻辑能力能有好的锻炼。

WeZZard 回复于 2013年09月01日

针对中英文符号的问题,我推荐使用日文布局的键盘,可以一键切换到英文,而且Control在锁大写的位置,非常方便地就能搓出eMacs快捷键。 alt text

haiwuxing 回复于 2013年09月01日

受教....

灵感之源 回复于 2013年09月01日

教训是从错误中吸取的。排错能力基于经验的积累。遇到某个错误,如果以前遇到过类似的,就容易解决,反之就只能提问或者自己输出日志或者单步调试了。

工作10年了,遇到各种各样的错误,低级错误常有,譬如多了个“{”或者少了个“;”。

不过随着岁月的增长,数量应该会越来越少,因为你写的时候就应该意识到哪里会有问题而避开。

在诸多做过的系统中,常发现开发人员复制粘贴,这是非常坏的做法,如果原始代码中有一个错误,那意味着复制一份,错误就多一个。这些错误修正起来,将会错综复杂而难以解决。

我非常赞同tinyfool说的重构,这是很好的习惯,在重构的过程中,开发人员可以不断提高代码质量/运行性能,减少错误。

如果你的IDE做得好,应该有快捷的文档格式化功能,自动缩进换行等等。我个人不喜欢手工按多个空格索引,喜欢用Tab,因为简单。

工欲善其事,必先利其器,你使用一种工具,要充分利用之,就要对其有很好的理解。我发现一些开发人员对开发工具停留在敲代码的层面上,怎么充分利用之做快速高效的开发和便捷的调试排错没有认识,这是可以改进的地方。

指针为空 回复于 2013年09月02日

代码缩进

函数、字段的命名

重构

单元测试

我总结的一般只要强迫症式的纠结以上四点,代码就可以很清晰,错误率就会很好。

p.s.微软的VS做的很不错,代码写完之后,只需要按crtl+k+d就自动缩进排版了。Xcode还没找到类似功能,求指点。

ibuick 回复于 2013年09月02日

低级错误,需要耐心。

说说我自己的,和见过的别人的低级错误吧。

1,我曾经在路透的时候,负责股票搜索。在某一天一个功能上线后多天,我看 Log,发现虚拟机内存占用以一个几乎不可查的微小增量在增长。明显是有泄漏,但查了很久没有查出来,最后借助 JProfiler,看到一个地方我错用了静态属性,导致对象无法释放。

2,还是路透的项目,金融辞典,也是发现有内存泄漏,但是奇怪的是此内存泄漏还会自我修复。一般情况下没问题,但是并发数超过一定数量后系统反应迟缓,Heap 几乎爆满。那个组的同事查了几天也没查出任何问题,最后求助于我。我挂上 JProfiler 压上压力跑了一晚上,重现了此问题。但是到这里我依然也没看出问题。我就盯着 Heap 堆历史记录看,看了一个小时,看出了明显的 30 分钟规律,即每30分钟,Heap 占用有明显下降。我当即就明白了原因,很简单,只要你熟悉 Tomcat,各位可以猜猜看。

3,还是路透,股票搜索时,日本站处理非常麻烦,由于日本常用三种拼写,一种 Kanji,一种平假名,一种片假名。我做了平假名转片假名的代码,但是始终通不过,最后我在编辑器里动了动光标,发现平假名带音标的,转换成片假名就变成两个字符,那么肯定是匹配不上了。

4,ezShare 1.3 发布前,build总是出问题,window.title 不显示,各类字符在 UI 上都不显示。我看了俩小时代码没看出问题。Xcode 也只是警告资源文件 .strings 可能有问题。我看了又看也没发现问题,最后拷贝到别的编辑器里,发现分号用的是中文。(我 Xcode 调节过字体,中英文分号几乎分不出。)

这都是些小例子而已,但是你应该能看到,排除 bug 往往需要经验,有一定运气,有趁手的工具。

我得经验是,遇到问题,不要慌忙去问,你这时应该发挥自给自足的精神,去查,把错误信息直接贴 Google 里,很多时候,貌似无关的一条信息中可能就隐含着答案。在搜索的过程中,你能对问题本身有进一步的认识。其次就是经验,这个东西没人能教你,都是靠日积月累的知识储备,而这些经验,恰恰能通过反复的捉虫,优化中获得。如果你经验丰富,很多时候都不用看代码,只看行为就能确定他是哪里出了问题。再次就是耐心与恒心,这俩对程序员来说是宝贵的品质,很多时候他们能帮你化险为夷。最后是工具,从最简单的 Log,到下断点调试,到设定调试参数,到看 Call Stack,到使用 Instruments,JProfiler 这样的工具,一步步来,你会发现 debug 也有其自己的乐趣所在。

tinyfool 回复于 2013年09月02日

那时候你们的搜索量是多大啊?

ibuick 回复于 2013年09月02日

@tinyfool 起不起来了,,,,,

灵感之源 回复于 2013年09月02日

我负责过单表几十亿记录的数据库,还有一票10亿级记录的表。。。。

tinyfool 回复于 2013年09月02日

10楼这是个祸害啊,我们以后怎么吹牛皮啊

灵感之源 回复于 2013年09月02日

11楼,那如果我说原来跑一个查询要几分钟,现在只需要1秒,你是不是要把我给宰了 :-)

tinyfool 回复于 2013年09月02日

宰了那是杀生,还是阉了你算了,人道主义最重要啊

灵感之源 回复于 2013年09月02日

不要啊,我还要生第三个娃的啊。。。。呜呜呜

指针为空 回复于 2013年09月03日

说个记忆最深的低级错误吧

写一个采集卡通过脚踏板保存图像的程序(医疗工作站)脚踏板插在电脑串口上,踏下去的时候连接串口中的2个针(忘了是哪两个了),系统监控到连接之后就保存采集卡正在采集的视频中的一帧图像。

程序工作的时候发现偶尔会直接崩掉,没有规律,没有预警,没有先兆。有时候第一次就崩了,有时候一天都不崩。

开始认为是脚踏板焊接短路了(我自己焊接的),重新做了,没效果。然后认为是脚踏板的监测程序问题,几乎重写了,还没效果。这个问题找了好久好久。

最后某次调整软件界面的时候发现,在VB里采集卡采集图像的显示区设置的x=1,y=1,而在VC里保存图像时候取得是x=0,y=0.。。。改掉后,问题再也没有出来过了

denganliang 回复于 2013年09月09日

受教了,感谢分享

本帖有16个回复,因为您没有注册或者登录本站,所以,只能看到本帖的10条回复。如果想看到全部回复,请注册或者登录本站。

登录 或者 注册
[顶 楼]
|
|
[底 楼]
|
|
[首 页]