﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-那谁的技术博客-随笔分类-经验教训</title><link>http://www.cppblog.com/converse/category/12068.html</link><description>感兴趣领域:高性能服务器编程,存储,算法,Linux内核</description><language>zh-cn</language><lastBuildDate>Thu, 08 Jul 2010 00:52:16 GMT</lastBuildDate><pubDate>Thu, 08 Jul 2010 00:52:16 GMT</pubDate><ttl>60</ttl><item><title>谈目前项目组的代码提交制度</title><link>http://www.cppblog.com/converse/archive/2010/07/08/119660.html</link><dc:creator>那谁</dc:creator><author>那谁</author><pubDate>Wed, 07 Jul 2010 17:53:00 GMT</pubDate><guid>http://www.cppblog.com/converse/archive/2010/07/08/119660.html</guid><wfw:comment>http://www.cppblog.com/converse/comments/119660.html</wfw:comment><comments>http://www.cppblog.com/converse/archive/2010/07/08/119660.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/converse/comments/commentRss/119660.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/converse/services/trackbacks/119660.html</trackback:ping><description><![CDATA[(转载请注明出自 http://www.cppblog.com/converse 那谁)<br><br>一直以来想找机会谈谈目前这个项目组内采用的代码提交制度,今天整理一下.<br><br>分如下几个流程:<br>1) 在trac系统上建立ticket,写好这个任务的目的,并且accept这个ticket.<br>2) 修改代码,把相关的修改过的代码(一般还应包括相关的测试用例的代码,后面会加以说明)提交到review board上.<br>3) 在review board上填写如下几个必要的信息:<br>a)第1)步建立的ticket的号码(可以有好几个,也就是一次修改可以针对好几个任务),以明确这次提交针对的是哪个任务;<br>b)写下测试了哪几个测试用例,对这次的提交写一段描述.<br>c)写下给哪些人做review.reviewer的角色分为两种,一种是负责人,另一种就是普通的组员.每一次提交,必须保证reviewer中有至少一个负责人,并且需要所有的reviewer都通过了这次修改,才能向代码库提交代码.reviewer会针对提交的代码进行批注回复.<br>4)一般情况下,代码不会在第一次提交就能通过review,大多数情况会被打回修改,于是2)-4)三个步骤将循环进行下去直到代码通过review为止.<br>5) 提交了代码之后,1)中建立的ticket会被自动关闭,并且将在ticket的回复自动写上本次提交修改的文件以及svn revision号,这样以后再看起来就知道是哪次提交并且修改了哪些文件针对的又是哪个功能了.<br>6) 有一台服务器专门作为buildbot机器, 这台机器在每次提交了代码之后,
将自动清空原来的代码目录,更新最新的代码,重新编译,然后把里面的测试用例全部跑一遍.我们的项目使用的tcmalloc,会检查内存泄漏,所以在测试用例测试失败,以及有内存泄漏的时候,buildbot都会失败.需要补充一点的是,除了每次提交代码会导致buildbot重新编译新的代码,在每晚的一个固定时间,即使没有更新代码,也会做相同的动作.buildbot的存在,就是为了不断的清空编译文件重新编译再跑测试用例,以大量的测试消除随机性保证正确性.<br><br>以上是整个代码提交机制的大体流程说明,下面谈里面的细节.<br><br>1)上面的第1)步中,建立trac的ticket时,需要指定一个milestone,一般我们对必须做的事情是每周以日期命名建立一个milestone,这样,你做的任务就会自然的变成每周可以去跟进的任务.<br>2)第2)步中,写测试用例针对的是每个类或者每个头文件对外暴露的API接口,比如对外的API c使用内部的函数B,那么是没有办法对函数B编写测试用例的.我们的要求是, 任何的一个新增的API都需要写针对它进行测试的测试用例,不一定只有一个,因为需要考虑的情况可能很多,总而言之,尽可能的考虑齐全.假如本次修改修改了内部函数B,那么依赖于函数B的API c它的测试用例也就需要再测试了.这些测试用的代码也会一并提交到代码库中,因为这样才能保证buildbot更新之后也按照最新的测试用例进行测试.<br>3) 代码提交到reviewboard之前,还需要过lint这一关,对基本的代码风格进行检查.我们使用的是google c++ code style.<br>4) reviewer中的负责人角色很重要, 起着看门人的作用,任何的一次修改提交,都必须至少经过一个负责人的review, 所以对他的要求就相对高些了,除了要完成自己的工作外,还需要认真review他人的代码,而要review他人的代码并且给出好的意见来,又要求他本人除了编码能力外,还要在业务层面对别人的工作有大体的了解,不然没法review了.<br>5) 从前文可以看出,review实在是一个繁杂的工作,很有可能在review阶段被打回修改代码,就我的经验而言,提交review的人要将提交的任务尽量的划分的细一些就来的很重要了.在提交review的时候,我一直坚持DOTDIW原则(Do One Thing,Do It Well).相反的例子,我们组有个同事,做了一个很大的功能,光是完成这个大的功能,就花费好几周的时间,提交review的时候代码量大,有个几千行的,这样别人review起来也慢,而且一旦不通过需要修改,又是一个苦力活儿.这样一折腾,一个月时间过去了.如果当时能对整体的功能有个把握,懂得划分模块层次,逐个提交,也许会好些.当然,大规模的代码提交有时并不能完全的避免,比如一个比较大的重构,牵一发而动全身的,我只是说如果可能,应尽量避免大规模代码的提交,并且最重要的是:每次提交最好仅针对一个功能点.<br><br>以上是对流程从整体到细节的描述.现在谈谈我的看法.<br>先来谈优点:<br>1) 通过codereview制度,保证了项目组成员之间能够在代码层面上直接的进行交流.我想这一点是最重要的,没有之一.<br>如果你是一个水平差一些的程序员,那么有比你牛叉的人帮你review,相当于是读书的时候有老师帮你阅卷修改作业,可以指正你的问题所在.我在被人review的过程中就听到了别人对我很多的意见.而如果是一个水平较高的人,是不是对水平差的人进行codereview就是浪费他的时间了呢?我个人认为,这个事情分怎么看.从团队的角度看,按照木桶理论,最短的短板往往决定了能达到的水平.总不能指望所有的事情都由老鸟完成,所以老人在帮新人review代码的时候间接的帮助了新人的成长,同时作为项目组中资历比较深的人,也应该对项目多费一些时间进行把关.我觉得这一点无可厚非.同时,即使是新人,通过阅读他人的代码并且交流,也可以学习到别人的思想.<br>codereview制度从上面的角度上保证了项目成员可以直接通过代码进行交流,简单的说,谁写的代码质量如何,一到了review,一目了然.写的好的通过的快,写的不好被打回修改多了自己也会长记性,还可以多看别人的代码进行学习.这样,在一定层面上可以使项目组成员的能力尽可能的接近.我能力长了一级,相应的也会拉动项目组中的人升级.<br><br>2) 测试用例.我之前提到的测试驱动开发,想法就来自于项目组中编写测试用例+buildbot执行测试用例的做法.如何证明一个API是确定无误的?我觉得这个问题似乎非常难.除了一些可以通过数学上逻辑上证明的情况外,还需要考虑很多其他的随机情况.比如线程的切换是随机的,某个文件夹恰好存在是随机的,等等.这些随机出现的问题,很多时候不能在某一次测试中显现出来.但是如果有了buildbot,不停的更新,编译,执行,总会有暴露问题的一天.另外,之前我也提到过,脑中如果有了测试用例的概念存在,每写一个API时都会考虑到针对它的用例应该是怎样的,从另一个角度,也帮助你的设计--你需要考虑这个API的输入,输出,异常情况都有哪些,如何测试到.测试用例的存在,保证项目尽量的做到了"可控制".<br><br>缺点:<br>1) 从上面的流程可以看出,走完一个代码提交的流程需要花费大量的精力/时间(有一些还是他人的精力/时间),所以,也许这个制度并不适合于那种时间压力比较大的项目.<br><br>2) 有几个地方很难坚持,比如codereview时,有些reviewer会走过场,也没怎么看代码就直接通过了,这样就会流于形式了.还有编写测试用例,也是一件很耗时间的事情.<br><br>3) 由于codereview制度的存在,给review中的负责人带去的压力很大,因为本身他有自己的工作,又要尽量对他人的业务/代码进行了解,就像那种负载很大的服务器一样,有时候会不堪重负.这一点上,我的想法是,项目组内的其他成员,如果能积极主动一些,多花时间了解别人的代码,尽量分担review的任务,或者试着成长为负责人级别(降低单点故障率:),也许是一个好办法.不过,这还得看人了.<br><br>整个代码提交流程上的软件,目前我所知的都是使用的开源软件:SVN, google c++ code style lint,review board,buildbot,trac,测试用例的编写上使用的是google的测试用例框架gtest.不过,整合起这些软件搭建好这个流程就不是那么容易了,用了一人/半年左右的时间,才让这个流程基本稳定下来.<br><br>总之,这套机制中,既有人为的干涉,也有机器层面上的干涉,也许并不是最完美的,但是尽了最大的力量去保证项目的正确性和可控性.<br><br>以正确的方式正确的制度做事,是将事情做好的一个关键.<br><br>       <img src ="http://www.cppblog.com/converse/aggbug/119660.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/converse/" target="_blank">那谁</a> 2010-07-08 01:53 <a href="http://www.cppblog.com/converse/archive/2010/07/08/119660.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>测试驱动开发</title><link>http://www.cppblog.com/converse/archive/2010/06/10/117596.html</link><dc:creator>那谁</dc:creator><author>那谁</author><pubDate>Thu, 10 Jun 2010 15:19:00 GMT</pubDate><guid>http://www.cppblog.com/converse/archive/2010/06/10/117596.html</guid><wfw:comment>http://www.cppblog.com/converse/comments/117596.html</wfw:comment><comments>http://www.cppblog.com/converse/archive/2010/06/10/117596.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/converse/comments/commentRss/117596.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/converse/services/trackbacks/117596.html</trackback:ping><description><![CDATA[<span style="font-size: 12pt;">有一本书就叫<a href="http://book.douban.com/subject/1230036/">&lt;&lt;测试驱动开发&gt;&gt;</a> , 我没有看过,这里仅谈论我所理解的"测试驱动开发".<br><br>我对这句话的理解是: <br>1) 任何一次提交新的代码都需要添加针对这些新功能的测试用例 <br>2) 无论设计函数还是类, 对外暴露的接口都应该做到明确, 清晰, 不会给人模棱两可的感觉,提供的功能点尽可能的单一, do one thing, do it well.<br><br>简而言之, 我所理解的"测试驱动开发", 十分强调对接口的设计, 以及针对这个接口所需要考虑的异常和测试用例.接口是对外的保证, 而测试用例是验收者, 每次的修改, 都需要保证之前和现在的用例能顺利通过.<br><br>所以, 对开发人员来说, 如果有这个"测试驱动开发"的观念, 那么在设计编写代码的时候会很容易的形成几个好习惯, 比如他会反问自己以下几个问题:<br>1) 新增的代码提供的是什么功能? 功能点是否足够的单一, 明确, 比如本次测试的代码仅针对功能A, 下一次的仅针对功能B, 假如B功能还依赖于A功能, 那么首先要保证A功能点正确提交.切忌万不得已的情况下不可以将多个功能点放在一次提交中, 这样, 以后回溯问题时会加大难度, 也会给codereview等带来困难.<br>2) 新增的功能, 对外暴露的接口是哪些?有没有冗余, 不明确的接口设计?这些接口是不是刚刚好不多不少足够了?<br>3) 对新增的功能, 明确了对外应该提供什么接口之后, 还需要反问自己:可能在哪些情况下出错, 每种出错的情况应该如何处理, 如何通知调用者, 代码的注释是不是对一些情况作了说明.<br>4) 最后, 对新增的功能, 考虑了哪些测试用例, 测试是否充分, 是否考虑了很多异常的情况?<br><br>所以, 每次的代码提交都是一件很严肃的事情, 这意味着, 你对系统现有的代码做出了一些修改, 可能是接口的修改, 可能是实现的修改.如何能保证你的修改没有问题, codereview是一点, 好的codereview是一件很耗时的事情, 这需要reviewer负责任,同时最好还要多少对这部分代码有了解.如果reviewer能力较强, 又比较负责, 那么一次review相当于是一个老师在阅读作业, 他会给出你一些建议;反之, 如果你作为reviewer去review一个高水平的人的代码, 又可以从阅读中学习对方的思路.总而言之, 我觉得做好codereview是一件能够迅速提高"经验值"的捷径, 早前我阅读过许多开源项目, 学习了很多别人的技巧思路, codereview比之更近了一步--因为我还有机会与作者面对面的一起交流.另外, 除了codereview之外, 每次提交都有测试用例, 也是保证代码质量的方式之一, 如果把代码比做一个球场, 那么测试用例就是站在这个球场门口进行安检工作的保安, 不论做了什么修改, 只要保证测试用例写的好, 那么基本上都跑不过这个保安的掌心.有了测试用例, 项目的修改才有了保证, 它所提供的功能, 都是可控的,有保证的.<br><br>另外, 每次提交的修改功能点尽量的单一也是很重要的一点, 因为假设你想做的事情很多, 比如做了A又想做B,发现做B功能需要实现C功能,实现C功能首先要做D功能....子子孙孙,无穷尽也.这样会导致你的代码提交codereview时被通过的时间慢(原因有很多, 比如你需要提交测试用例多了, 比如别人的codereview时间多了).还有一点, 假如别人赶在你之前提交了代码, 而你的修改需要依赖别人的代码, 这样导致了你需要合并别人的改动, 这又是一件很麻烦的事情.<br><br>所以, 当接手一个任务时, 如何按照层次顺序划分任务, 每次提交都保证尽可能提交少的功能点, 而且又能保证每次的提交都有严格的测试用例, 也是对开发人员的一个考验.当然,这些也许在动手的时候不能百分百的考虑清楚, 但是如果完全的没有考虑过, 上手就做, 迟早都要还的.<br><br>另外, 有了同新增代码一起提交测试用例的要求之后, "看上去"每次提交的速度慢了, 因为还需要撰写测试用例, 所以对任务时间点的估计可能也需要改变, 我个人的估计是写代码时间 : 测试时间(包括写测试用例+改bug) : 根据codereview修改代码的三者之间比例大概为4:3:3, 所以如果一个任务给我五个工作日的时间完成, 也许以前我到了第四天才编码完成, 而现在就要尽量做到第三天之内能完成编码了.不过这个比例并不确定, 依个人的素质而定, 有的人写代码质量很高, 自己已经把很多情况在写的时候考虑进去了, 所以后期测试和codereview时出现问题的机会少, 反之, 有的人的代码质量较差的, 可能经常在codereview的时候被打回去重构(甚至于重写)的, 后面的比例就要增加了.以我而言, 如果能在保证代码质量的同时, 减少后面两项的时间比例, 那应该是说明了我的代码质量有了提高了.<br><br>总而言之, 接口的设计, 任务层次顺序的划分, 都是很考验人经验的活, 语言的表达总是苍白的, 需要实实在在的去实践体会.<br><br>K.I.S.S<br>  </span>  <img src ="http://www.cppblog.com/converse/aggbug/117596.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/converse/" target="_blank">那谁</a> 2010-06-10 23:19 <a href="http://www.cppblog.com/converse/archive/2010/06/10/117596.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>方法与工具</title><link>http://www.cppblog.com/converse/archive/2010/04/15/112605.html</link><dc:creator>那谁</dc:creator><author>那谁</author><pubDate>Wed, 14 Apr 2010 16:57:00 GMT</pubDate><guid>http://www.cppblog.com/converse/archive/2010/04/15/112605.html</guid><wfw:comment>http://www.cppblog.com/converse/comments/112605.html</wfw:comment><comments>http://www.cppblog.com/converse/archive/2010/04/15/112605.html#Feedback</comments><slash:comments>8</slash:comments><wfw:commentRss>http://www.cppblog.com/converse/comments/commentRss/112605.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/converse/services/trackbacks/112605.html</trackback:ping><description><![CDATA[
从最近遇到的几个故事说起.<br><br>故事一:<br>某天晚上和室友聊天,谈到使用Vim阅读代码,室友也是使用Vim的人,他说用类似ctags的查找定位功能不多,更多的时候,他阅读一段代码,要定位一个功能点,首先是从阅读代码文件的组织,了解项目的功能等入手,等这些都基本清楚了,定位起来就会快很多.我虽然认为,ctags实在是Vim里面一个很不错的功能,不用这个实在可惜,但是他说的那套定位思路其实也是不错的方法.其实我自己用惯了ctags类的功能之后,阅读代码的时候也会用惰性,更多的时候是要靠这些工具来帮我定位,而不是通过自己主动的思考和分析.<br><br>故事二:<br>我的经验里面,写完一段代码之后的第一次编译,如果编译器报错越少,那么可以认为这段代码将来可能出现bug的几率越小,简而言之,我认为代码的质量与第一次编译的报错数量成反比(哦,不用拿helloworld类的程序跟我钻牛角尖了:).有些人写的代码,哗哗的写了一大段,写之前不考虑好,只想着到时候写的不对了可以在编译由编译器的报错来帮忙找问题,这个思路是不对的.编译报错越少的代码,说明了作者写的时候思路更清晰一些,考虑的更周全一些,所有的这些流程上的步骤走的都不错了,所以才有最后编译报错少的结果.编译报错应该是写代码的结果之一,而不应当当成了找代码问题的手段,这样的思路,本末倒置了.<br><br>故事三:<br>进入新项目组后,组内对代码编码的流程有比较严格的要求,包括写一个API之后需要写一个针对这个API的各种情况的测试用例,提交代码的时候,需要走codereview流程,需要使用cpplint检验代码风格,需要将对应的测试用例也提交上去.这一套流程走下来,提交代码的频率比之以前,降低了很多,但是不可否认的是,也确实提高了代码的质量.假设一个功能,由N个API构成,如果能对这N个API都做过详细的测试,保证它们的输入和输出在各种情况下都能符合要求,那么很显然的,最后这个功能也应该是正确的.反之,越是到了测试阶段需要使用类似gdb这样的调试器来定位问题的,会越让人不放心---因为没有制度和流程的保证,谁也不能说将来可能在哪个点会出问题.它可以帮你定位问题,但是没有办法告诉你已经没有问题了,最后这一点,最终还是要通过严格的单元测试等流程来保证的.与第二个故事相同,我认为,在测试阶段使用gdb定位问题的次数也与代码的质量成反比:)<br><br>这几个故事综合起来,想要表达的是,做一件事情,需要有流程,制度的保证,当然也需要工具(比如这几个故事里面提到的Vim,gcc,gdb),但是工具就是工具,它只是一种手段,没有办法替代正确的思路,流程和制度等,仅能在整个过程中起到辅助的作用.而且,过分依赖工具,也会给人以惰性(比如前面提到的使用编译器编译代码来找错,比如使用gdb来保证功能正确性等).<br><br>所以,更应该提倡的是正确的流程,制度,有了这些,项目的质量才能有所保证.比如,在写一个功能点时,需要具体分解为哪几步,每一步有哪几个API,针对它们的测试用例有哪些,测试时的输入和输出有哪些,需要考虑的异常情况有哪些,等等的.这些都考虑清楚了,再动手写,久而久之,我想这方面的能力会慢慢的提高.<br><br><br><br><img src ="http://www.cppblog.com/converse/aggbug/112605.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/converse/" target="_blank">那谁</a> 2010-04-15 00:57 <a href="http://www.cppblog.com/converse/archive/2010/04/15/112605.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>[经验教训总结]协议包头结构体定义不严谨造成的错误</title><link>http://www.cppblog.com/converse/archive/2009/10/15/98714.html</link><dc:creator>那谁</dc:creator><author>那谁</author><pubDate>Thu, 15 Oct 2009 12:32:00 GMT</pubDate><guid>http://www.cppblog.com/converse/archive/2009/10/15/98714.html</guid><wfw:comment>http://www.cppblog.com/converse/comments/98714.html</wfw:comment><comments>http://www.cppblog.com/converse/archive/2009/10/15/98714.html#Feedback</comments><slash:comments>6</slash:comments><wfw:commentRss>http://www.cppblog.com/converse/comments/commentRss/98714.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/converse/services/trackbacks/98714.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: &nbsp;&nbsp;<a href='http://www.cppblog.com/converse/archive/2009/10/15/98714.html'>阅读全文</a><img src ="http://www.cppblog.com/converse/aggbug/98714.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/converse/" target="_blank">那谁</a> 2009-10-15 20:32 <a href="http://www.cppblog.com/converse/archive/2009/10/15/98714.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>