老码农:如何写出让自己满意的代码

今天有位朋友在微博上问我这样一个问题:

@老码农的自留地 ,最近出于学习目的写一个管理系统,越到后边,越觉得自己前边的代码写得烂。前辈,我想让代码写得更好一点,能不能谈谈你的经验,给我指点一下!”

我在回复里刚写了几句,就意识到140个字很难把我的想法说清楚,本着知无不言言无不尽的好为人师精神,我决定把我的回答写成一篇博文。

首先要说明的是,我写这篇博客并不代表本人觉得自己的代码写得有多好。事实上我很清楚自己的水平,作为一个做应用系统的程序员,和那些做框架做系统的大牛根本就不在一个层次。而且即使在应用层次,我的水平大概也只能算二三流,只是因为热爱编程所以一直在努力而已,但不管怎么说,能做自己喜欢的工作我已经很满足了。所以我稍微篡改了一下问题,针对“觉得自己前边的代码写得烂”这个重点,把这位朋友问的如何“让代码写得更好一点”改成了“如何写出让自己满意的代码”

言归正传,我自己的体会是写代码很像写作文,开始写之前的构思过程是最关键的。记得高中的时候,有位语文老师给我传授的经验是,至少花三分之一的时间来构思,反复斟酌中心思想、各个段落的大意,文章的脉络,主要的修辞手法,等等。把这些要素都想清楚了,写起来就可以一气呵成。

我觉得写代码也是一样,思路是最关键的。假定采用的技术平台、框架、工具等已经确定了,那么在开始动手写之前,花三分之一以上的开发时间去把所有的数据结构及其相互关系考虑清楚。例如需要定义几个类,类和类之间的关系是怎样的,每个类里都有什么属性,每个类提供一些什么样的方法,等等,这些是最核心的。这些数据结构要考虑得尽可能细,比如功能实现可能没问题,但是性能上不理想,这就说明你的数据结构设计还需要改进。这些细节要反复考虑,交叉检验,直到自己觉得很周到了为止。在此基础上,再注意实现的细节、测试用例、代码可读性,就应该可以写出让自己满意的代码。具体说明如下:

1. 数据结构和核心算法

关于数据结构的重要性,大神Linus Torvalds讲过这样的话,我觉得非常赞同:”Bad programmers worry about the code. Good programmers worry about data structures and their relationships.” (低水平程序员总在考虑代码,高水平程序员总在考虑数据结构及其之间的关系)

数据结构考虑清楚了,核心的算法自然就出来了,这就是关于每个类的每个方法如何实现的问题。比如需要实现一个中位数查询方法,如果你前面确定了数据保存的格式是一个列表,那么你可以考虑采用插入排序法;如果数据格式是自平衡二叉排序树(AVL),则只需直接返回根节点就可以了。

数据结构决定算法,所以你在考虑数据结构的时候,一定要尽可能地使数据的结构和它的自然属性相匹配,不然后面的实现就会是一场噩梦。比如,你把一个多层级的结构定义成二维数组,看上去也靠谱,相当于在一个表格里维护一个组织结构图,可是当你做到部门增减的时候,本层级的数组元素移动自不必说,下面各个层级的元素移动就很容易乱套,而且性能很差,可能你写了2000行代码还有很多边界条件会出错。相反,如果用一个孩子兄弟链表来表示这个树型结构,操作起来就非常容易,可能100行都足够了。

2. 功能实现

思路确定后,实现过程也需要大量的构思活动。碰到你比较熟悉有经验的领域,你自然可以轻车熟路,但难免会有一些你不太熟悉的技术需要尝试。有的同学比较排斥这种领域,比如我好不容易才掌握了Struts 2,领导又让我去学习Grails框架,我就会觉得很不爽,大概看了看就挑出它的一堆问题,然后能躲多远就躲多远。可是我要说,这样的心态会阻碍自己不断提高技术水平。作为一个程序员,最大的挑战也是最大的乐趣所在,就是不断学习新的技术,没有这样的心态,很快就会落后。

好,那么遇到不熟悉的技术怎么办?我的体会是,先不要急着实现项目中的代码,自己另外维护一个测试项目,在里边边查文档边学习,边做一个小功能,把所有需要在项目中实现的功能先在测试项目里跑通,然后再写项目里的代码。这样做的好处是把单个技术问题和其他潜在的bug隔离开来,便于快速学习新技术。否则,你直接在项目里写代码出错以后,要判断问题的源头都要多费好几倍的精力。

3. 测试

测试很重要,设计测试用例就像开发时设计数据结构一样,也是很关键的。在设计测试用例的时候,要把当时自己设计数据结构的思路全部忘掉,或者找别人来设计测试用例,不然会不由自主地测试那些你已经考虑到了的地方。这样测试是跑通了,用户一用起来可能各种边界条件会到处出问题。

有人会推崇TDD的方法,先设计好测试用例,然后在开发过程中确保所有测试通过。我个人不喜欢这种方法,虽然承认从开发质量管理和长期维护的角度来说TDD是很有必要的,但我个人尝试的结果是,设计完测试用例后,想到开发的目标不是实现功能,而是为了跑通测试,就感到毫无乐趣可言。这一点我自己也觉得很矛盾。

写到这里我又想到大神Linus说过的另一句话:”Regression testing” What’s that If it compiles, it is good; if it boots up, it is perfect. (“回归测试”?这是什么东西?如果代码能编译就是好的,如果它启动了,那就是完美的。)
当然了,大神水平摆在那里,他有资本目空一切,咱确实没资格仿效。但是我还是觉得TDD也有TDD的问题,测试是很重要,但把它摆到驱动开发的高度,就有点本末倒置了。这个是我自己的一点看法,本人对TDD了解得不深入,如果有谬误之处,请多多指教。

4. 代码可读性

要想自己满意,代码的可读性一定要好。要做到一年后甚至几年后你拿到自己写的代码,还能很容易看明白当时的思路和实现。这就涉及到命名和注释的问题。

命名就像超市里的商品标签一样,要让看得人一目了然就知道这是个什么东西,比如你的员工类里有两个属性分别是到岗日期和离职日期,把它们定义成date1和date2就没有多少可读性,而定义成dateOnBoard和dateQuit就比较清晰。

注释也是很重要的,它可以用来说明一段代码的作用,算法的设计思想,或者是方法调用的参数格式要求等。有人觉得命名就是注释,代码本身就为自己代言了。我觉得这种说法用来强调命名规范的重要性是很好的,但是因此说不需要注释则有失偏颇。试想,如果Dijkstra首次发明最短路径算法的时候,他给出的代码里没有一行注释,即使所有的变量命名都定义得准确而严谨,又有几个人能看懂他的算法呢?所以,在重要或者复杂的地方,都需要详细地写一些注释,便于看代码的人清晰地了解你的思路。

最后总结一下:要想写出自己满意的代码,首先不要急于动手,要先仔细想清楚思路性的东西,尤其是数据结构,然后在实现过程中大胆尝试小心验证,设计好测试用例,确保代码的可读性,就可以在代码中表现出自己的最高水平。但毕竟各人水平是有差异的,自己满意并不等于其他人欣赏。我对此的看法是,不求尽如人意,但求无愧我心,足矣。最后再啰嗦一句,技术水平是可以慢慢提高的,但是好的编程习惯需要从一开始就养成,它会让你在前进的道路上事半功倍,受益终生。

针对评论中提到的需求不确定导致设计和实现困难的问题,我又写了一篇有关需求分析的文章:《关于需求分析的几点体会》,欢迎批评指正。

打赏支持我写出更多好文章,谢谢!

打赏作者

打赏支持我写出更多好文章,谢谢!

任选一种支付方式

2 3 收藏 25 评论

关于作者:老码农

搞得定代码,罩得住娃;治得好跟腱,踢得了球。Hi,我是老码农,蜀黍有练过,小盆友们不要随便模仿喔。(新浪微博:@老码农的自留地) 个人主页 · 我的文章 · 122 ·    

相关文章

可能感兴趣的话题



直接登录
最新评论
  • 咆哮的独角兽   2013/09/16

    理清数据结构和关系?还是得想办法先请业务和产品把自己想要什么梳理清楚吧。在一家公司工作了八年,我最大的感概就是:这帮人总能用一个凭空而来脱离基础数据逻辑的需求,让你精心设计的结构一毛不值。最后结合“客户今天就要”以及一帮刚毕业的码农,把本来一个还看得过去的应用变成一坨难以持续维护的狗屎。

    • Hong   2013/09/16

      有同感,即使良好的数据结构也禁不起大量的需求变更,各种特殊case层出不穷,很多时候只能靠重构保持良好的结构。

    • 老码农 其实,我是一个作家 2013/09/16

      我这里没有谈到需求分析的问题,也许以后有时间专门写一篇体会,但理清数据结构和关系与理清需求之间没有矛盾啊。又不是说需求清晰合理了,才需要考虑清楚数据结构,或者说需求不清晰,就没有必要考虑数据结构,它们是做好一个软件都必需的环节。

      另外,现实中确实有很多不确定的需求,有时候水平最高的需求分析人员也难以预知,这时候就需要设计中保留足够的灵活性,尽可能避免在需求变化的时候伤筋动骨,这也是考验设计人员功力的时候。

      所以,我觉得作为程序员的心态很重要。积极的心态是这样:碰到不明确的需求,找需求分析人员讨论,用自己的思路和疑问去引导他们把需求细化,对于实在无法细化的,在代码中留有足够的余地,未雨绸缪;消极的心态就是抱着对付事的心态消极应付,出了问题就互相指责,到处抱怨。这种心态的差别我看到过很多,在职业成长方面导致的差别也是明显的。

      • shuige2606   2013/11/11

        碰到不明确的需求,找需求分析人员讨论,用自己的思路和疑问去引导他们把需求细化。这点深表赞同,有可能是几方博弈才能确定一个细节方案。

  • 看完后感慨,这真的是一个老码农! 你已经老了~
    现在需求变化那么快,这套方法早就不合适了…

    • monkWong   2013/09/16

      对需求变化快有点同感,不过说不清楚。你能不能指点他一下现在合适的方法,大伙儿也好跟着学习学习。谢了!

    • 黄余粮 站长 2013/09/16

      项目或者产品的背景不同,开发流程会有差异。有的项目需求很明确,基本没什么变化;有的连客户都不知道要什么,实施过程中,需求变化快正常。一概而论地定性需求变化那么快,就没什么意思了。

      不知道 Dozer 指 @laomanong 的这套方法的哪一块不合适?

    • Cloud   2013/10/11

      虽然未必完全同意作者的所有观点。但是也不至于这样全盘否定。不知道有什么样的开发方式和作者是完完全全没有一丁点相同的。

  • way   2013/09/16

    看您的博客总出现在CSDN首页上 慢慢看过一两篇发现都挺受益的
    就像这篇文章说的先思考再动手 和我师傅告诉我的一模一样 我才工作1年尽量每次都这样做
    确实跟刚工作那会先code 再理顺结构比 感觉少了很多rework 和很多潜在问题

  • 古月摇光   2013/09/16

    客户总是要阶段性验收,尤其是不懂程序的客户,只要看那些看得到的东西,前期构思的阶段对方直接觉得你啥也没做,然后隔一段时间验收一次,你不得不做一些明明不利于长远但阶段性能给客户看到的东西来应付客户,最终项目变得一团浆糊

  • dlitchi   2013/09/16

    受教了

  • D_clock爱吃葱花 Android开发者 2013/09/16

    I agree with the author!

  • SkyLin   2013/09/17

    每位前者都会告诉后者,不要急着上手,要多思考,考虑清楚。但是有几个新手会遵循?好的代码不是一次就能完成的,需要不断的积累中完成,新手建议是迭代。

    • Cloud   2013/10/11

      不矛盾,迭代也要先想清楚这个迭代要做什么,怎么做。

  • daizhen   2013/09/17

    比如需要实现一个中位数查询方法,如果你前面确定了数据保存的格式是一个列表,那么你可以考虑采用插入排序法
    --------------------
    如果不需要排序的话,使用快速排序算法(部分功能)可以更高效。

  • Yangff   2013/09/22

    http://www-m3.ma.tum.de/foswiki/pub/MN0506/WebHome/dijkstra.pdf
    我他妈看懂了怎么办,一行注释都没有。

    • 老码农 其实,我是一个作家 2013/09/22

      兄弟,你看的这分明是论文不是代码。如果你直接看懂了他没给注释的代码,说明你是程序员里万里挑一的人才,因为从后台看本文的阅读数是一万多次。即使你属于能看懂的那“几个人”之一,可是注释是写给大部分人看的,没有注释大部分人还是看不懂嘛。否则要是有人说自己连命名混乱的代码都能看懂,那岂不是连命名也不需要规范了么。

      • iyuan   2013/10/10

        其实个人还是比较认同:注释是写给自己看得(没准得返工)。抱着这种心思,或者会写的好一些吧。

  • 现在工作都催得紧,都是赶进度功能实现了就ok了,代码都烂得不得了啊

  • 先思考,再实现。有哪个家伙可以不思考就写代码的。每个人都是思考后再写代码的,区别在于思考的深度和广度而已。优秀的、有经验的会思考的全面点。想写出满意的代码,思考,写代码,思考,写代码这样一个迭代过程是不可避免的。

  • dhqcl 程序员 2015/01/29

    严重同意。数据结构的重要性远超算法。

  • sikele2236   2015/01/29

    难道不是要写难以维护的代码么。。
    其实一开始写的时候就要注意功能和代码的分离,后期容易迭代增量或者重构,代码的组织比架构更重要。

  • 风中劲草 码农 2015/10/16

    很多时候都是为了完工而实现,至于完工后的质量和维护成本,完全不关心。所以很多人写了很多年代码,依旧处在实现问题,而不能解决问题

跳到底部
返回顶部