﻿<?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/</link><description>感兴趣领域:高性能服务器编程,存储,算法,Linux内核</description><language>zh-cn</language><lastBuildDate>Thu, 16 Apr 2026 17:12:18 GMT</lastBuildDate><pubDate>Thu, 16 Apr 2026 17:12:18 GMT</pubDate><ttl>60</ttl><item><title>博客迁移</title><link>http://www.cppblog.com/converse/archive/2010/08/08/122713.html</link><dc:creator>那谁</dc:creator><author>那谁</author><pubDate>Sun, 08 Aug 2010 15:44:00 GMT</pubDate><guid>http://www.cppblog.com/converse/archive/2010/08/08/122713.html</guid><wfw:comment>http://www.cppblog.com/converse/comments/122713.html</wfw:comment><comments>http://www.cppblog.com/converse/archive/2010/08/08/122713.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.cppblog.com/converse/comments/commentRss/122713.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/converse/services/trackbacks/122713.html</trackback:ping><description><![CDATA[我申请了个人博客,域名为:codedump.info ,如果您有订阅我的博客,劳烦将链接更新到新的地址.最好使用RSS阅读工具订阅(比如google reader),我已经设置了博客文章采用全文RSS输出,所以使用RSS阅读器并不会降低阅读的质量.<br><br>在这里已经写了快五年的博客, 感谢<a  href="http://www.cppblog.com/converse/admin/cppblog.com">cppblog</a> 这些年来一直提供的优质服务, 以至于我都忘了他们的存在--因为实在没有什么不痛快的事情要投诉反馈的,不过,似乎目前对chrome的支持并不太好, 我每次写博客都需要切换到Firefox上面,希望能有改善.<br><br>
<div><br></div><img src ="http://www.cppblog.com/converse/aggbug/122713.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-08-08 23:44 <a href="http://www.cppblog.com/converse/archive/2010/08/08/122713.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><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>20</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>C++的流设计很糟糕</title><link>http://www.cppblog.com/converse/archive/2010/07/06/119427.html</link><dc:creator>那谁</dc:creator><author>那谁</author><pubDate>Tue, 06 Jul 2010 05:04:00 GMT</pubDate><guid>http://www.cppblog.com/converse/archive/2010/07/06/119427.html</guid><wfw:comment>http://www.cppblog.com/converse/comments/119427.html</wfw:comment><comments>http://www.cppblog.com/converse/archive/2010/07/06/119427.html#Feedback</comments><slash:comments>79</slash:comments><wfw:commentRss>http://www.cppblog.com/converse/comments/commentRss/119427.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/converse/services/trackbacks/119427.html</trackback:ping><description><![CDATA[最近需要提供一个功能,采用类似C++流输出的格式输出一些日志信息, 例如Log(FATAL) &lt;&lt; "log to" .<br><br>我找了两个类似项目来研究,google的<a href="http://code.google.com/p/google-glog/">glog</a> 和 <a href="http://log4cpp.sourceforge.net/">log4cpp</a>, 它们都支持以C++流输出格式进行输出.<br><br>但是研究到最后,我发现最大的问题是, 如果按照C++的流输出格式进行输出, 将无法判定需要输出的信息到哪里是结束.比如log &lt;&lt; "hello " &lt;&lt; "world",是无法判断到底在输出"hello"还是"world"的时候上面的参数输入已经结束了.上面两个项目中, 解决这个问题的办法大致是相同的,以下面可编译运行代码为例说明它们的做法(在linux g++下面编译通过):<br>
<div style="background-color: #eeeeee; font-size: 13px; border: 1px solid #cccccc; padding: 4px 5px 4px 4px; width: 98%;"><!--<br><br>Code highlighting produced by Actipro CodeHighlighter (freeware)<br>http://www.CodeHighlighter.com/<br><br>--><span style="color: #000000;">#include&nbsp;</span><span style="color: #000000;">&lt;</span><span style="color: #000000;">iostream</span><span style="color: #000000;">&gt;</span><span style="color: #000000;"><br>#include&nbsp;</span><span style="color: #000000;">&lt;</span><span style="color: #000000;">sstream</span><span style="color: #000000;">&gt;</span><span style="color: #000000;"><br><br>#ifdef&nbsp;__DEPRECATED<br></span><span style="color: #008000;">//</span><span style="color: #008000;">&nbsp;Make&nbsp;GCC&nbsp;quiet.</span><span style="color: #008000;"><br></span><span style="color: #000000;">&nbsp;#&nbsp;undef&nbsp;__DEPRECATED<br>&nbsp;#&nbsp;include&nbsp;</span><span style="color: #000000;">&lt;</span><span style="color: #000000;">strstream</span><span style="color: #000000;">&gt;</span><span style="color: #000000;"><br>&nbsp;#&nbsp;define&nbsp;__DEPRECATED<br></span><span style="color: #0000ff;">#else</span><span style="color: #000000;"><br>&nbsp;#&nbsp;include&nbsp;</span><span style="color: #000000;">&lt;</span><span style="color: #000000;">strstream</span><span style="color: #000000;">&gt;</span><span style="color: #000000;"><br></span><span style="color: #0000ff;">#endif</span><span style="color: #000000;"><br><br></span><span style="color: #0000ff;">using</span><span style="color: #000000;">&nbsp;</span><span style="color: #0000ff;">namespace</span><span style="color: #000000;">&nbsp;std;<br><br></span><span style="color: #0000ff;">class</span><span style="color: #000000;">&nbsp;LoggerStream&nbsp;:&nbsp;</span><span style="color: #0000ff;">public</span><span style="color: #000000;">&nbsp;std::ostrstream&nbsp;{<br>&nbsp;</span><span style="color: #0000ff;">public</span><span style="color: #000000;">:<br>&nbsp;&nbsp;LoggerStream(</span><span style="color: #0000ff;">char</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">*</span><span style="color: #000000;">&nbsp;buf,&nbsp;</span><span style="color: #0000ff;">int</span><span style="color: #000000;">&nbsp;len)<br>&nbsp;&nbsp;&nbsp;:&nbsp;ostrstream(buf,&nbsp;len),<br>&nbsp;&nbsp;&nbsp;&nbsp;buf_(buf),<br>&nbsp;&nbsp;&nbsp;&nbsp;len_(len)&nbsp;{<br>&nbsp;&nbsp;}<br><br>&nbsp;&nbsp;</span><span style="color: #000000;">~</span><span style="color: #000000;">LoggerStream()&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000;">//</span><span style="color: #008000;">&nbsp;do&nbsp;the&nbsp;real&nbsp;fucking&nbsp;output</span><span style="color: #008000;"><br></span><span style="color: #000000;">&nbsp;&nbsp;&nbsp;&nbsp;cout&nbsp;</span><span style="color: #000000;">&lt;&lt;</span><span style="color: #000000;">&nbsp;buf_;<br>&nbsp;&nbsp;}<br><br>&nbsp;</span><span style="color: #0000ff;">private</span><span style="color: #000000;">:<br>&nbsp;&nbsp;</span><span style="color: #0000ff;">char</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">*</span><span style="color: #000000;">buf_;<br>&nbsp;&nbsp;</span><span style="color: #0000ff;">int</span><span style="color: #000000;">&nbsp;len_;<br>};<br><br></span><span style="color: #0000ff;">int</span><span style="color: #000000;">&nbsp;main()&nbsp;{<br>&nbsp;&nbsp;</span><span style="color: #0000ff;">char</span><span style="color: #000000;">&nbsp;buf[</span><span style="color: #000000;">100</span><span style="color: #000000;">]&nbsp;</span><span style="color: #000000;">=</span><span style="color: #000000;">&nbsp;{</span><span style="color: #000000;">'</span><span style="color: #000000;">\0</span><span style="color: #000000;">'</span><span style="color: #000000;">};<br><br>&nbsp;&nbsp;LoggerStream(buf,&nbsp;</span><span style="color: #0000ff;">sizeof</span><span style="color: #000000;">(buf))&nbsp;</span><span style="color: #000000;">&lt;&lt;</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">1</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">&lt;&lt;</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">"</span><span style="color: #000000;">&nbsp;hello&nbsp;world\n</span><span style="color: #000000;">"</span><span style="color: #000000;">;<br><br>&nbsp;&nbsp;cout&nbsp;</span><span style="color: #000000;">&lt;&lt;</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">"</span><span style="color: #000000;">buf&nbsp;=&nbsp;</span><span style="color: #000000;">"</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">&lt;&lt;</span><span style="color: #000000;">&nbsp;buf&nbsp;</span><span style="color: #000000;">&lt;&lt;</span><span style="color: #000000;">&nbsp;endl;<br><br>&nbsp;&nbsp;</span><span style="color: #0000ff;">return</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">0</span><span style="color: #000000;">;<br>}<br></span></div>
<br>在上面的代码中, 开始进行输出的时候首先初始化一个LoggerStream对象, 而在输出参数输入完毕的时候将调用它的析构函数,在这个析构函数中才完成真正的输出动作.也就是说,由于对输入参数结束位置判断手段的缺失,C++中不得不采用这个手段在析构函数中完成最终的输出工作.<br>这样的做法,最大的问题是,频繁的构造/析构开销大,而且每个"&lt;&lt;"操作符背后又需要调用ostream的operator&lt;&lt;,也就是假如你的输入参数有三个将调用operator &lt;&lt;三次(当然是经过重载的,不一定都是同一个operator&lt;&lt;),因此,假如需要考虑多线程的话,那么一次输入有多个函数函数中被调用,仍然是问题.天,要使用这门语言写出正确的程序来,需要了解底下多少的细节呢?!<br><br>最后,我向项目组反映这个问题,一致同意以C中类似sprintf可变参数的形式实现这个功能.可变参数解决这个问题,就我的感觉而言,就是输入参数的时候,稍显复杂,需要用户指定输入的格式.然而,其实这个做法也有好处:作为函数的使用者,你必须明确的知道你在做什么并且反馈给你所使用的函数.明确的,无歧义的使用函数,而不是依靠所谓函数重载猜你的用意,我想也是避免问题的一个手段.gcc中, 提供了对可变参数检查的机制,见<a href="http://unixwiz.net/techtips/gnu-c-attributes.html">这里</a>.<br><br>   <img src ="http://www.cppblog.com/converse/aggbug/119427.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-06 13:04 <a href="http://www.cppblog.com/converse/archive/2010/07/06/119427.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>向德国人低头</title><link>http://www.cppblog.com/converse/archive/2010/07/04/119287.html</link><dc:creator>那谁</dc:creator><author>那谁</author><pubDate>Sun, 04 Jul 2010 02:55:00 GMT</pubDate><guid>http://www.cppblog.com/converse/archive/2010/07/04/119287.html</guid><wfw:comment>http://www.cppblog.com/converse/comments/119287.html</wfw:comment><comments>http://www.cppblog.com/converse/archive/2010/07/04/119287.html#Feedback</comments><slash:comments>4</slash:comments><wfw:commentRss>http://www.cppblog.com/converse/comments/commentRss/119287.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/converse/services/trackbacks/119287.html</trackback:ping><description><![CDATA[北京时间2010年七月四日早上.距离2010年世界杯阿德大战已经过去12小时.<br><br>作为资深的已经喜欢阿根廷人十多年的人,昨晚看到0:2落后的时候,我迅速走回自己的房间.我知道已经崩盘,然而比赛还有几十分钟的时间,以这个态势发展下去,结果还会更差.<br><br>但是,过了几分钟,我又走出来继续看完剩下的比赛,0:3,0:4,阿根廷人眼中的无奈,映衬着德国人的轻松.德国人以及其轻松的,或者说是对手阿根廷最擅长的方式,微笑着一刀一刀将对手切割.<br><br>德国人是世界杯历史上发挥最为稳定的一支球队(没有之一),这个数据的得来根据是他们是迄今为止进入世界杯八强最多的球队.<br><br>"稳定"是什么意思?稳定就是自己不犯错,正常发挥自己的水平,有60分打出60分的水平,有100分打出100分的水平.保证了自己不犯错,才能等待对手的犯错.<br><br>即使在94,98两年世界杯,德国人由于整体人员的老化,没能取得好成绩,他们也依然很"稳定",因为当时的人员能力也确实只有那个水平了,他们没有自我犯错,依然把自己最好的水平发挥出来.<br><br>看看其他的队伍吧,法国内讧,意大利固执,巴西保守,阿根廷(我就不评价了).几乎都有各自的问题,最终都倒在自己的脚下.<br><br>足球是一个充满偶然性的运动,一个擦身,探头,伸脚都可能造成进球.遇到一个几十年一见的天才,就可以让球队呼风唤雨好多年.<br><br>但是,你不能总是凭借着偶然性,运气走的更远.好比说,你不能总是指望着出现贝利,马拉多纳,齐祖这样的天才能站出来一夫当关拯救球队.我们提倡英雄,崇拜英雄,但是,英雄并不总是存在的.出现英雄这本身就是件运气十足的事情.<br><br>更多的,应该是按照规律办事,一丝不苟,严谨求实.写下上面那句话时,我想到了德国人在06年世界杯的对阿根廷互罚点球时的小纸条.德国除了贝肯鲍尔之外似乎没有出现公认的超级巨星,然而就是能取得如此稳定的成绩,这本身已经是很好的解释.<br><br>曾经我迷恋巴乔,巴蒂那样的悲剧哥,上天给予他们天赋,却没有给他们好的结果.曾经我在他们身上,相信有所谓的"命运".<br><br>我这么说,并不是说他们的结果完全的无外因所作用.只是我觉得,当你把事情的结果过度的归咎于那些玄之又玄的东西时,人就会变得矫情,文艺,不去反思自己.<br><br>佛说,世间一切事,皆是因果报应.说的通俗些,可怜之人必有其可恨之处.<br><br>所以,去他妈的&lt;&lt;阿根廷别为我哭泣&gt;&gt;吧,矫情和文艺并不能让你的生活因此改变,抬起头来面对现实反思自己吧.<br><br>向德国人低头,这个民族的字典里没有"命运"一说.<br><br> <img src ="http://www.cppblog.com/converse/aggbug/119287.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-04 10:55 <a href="http://www.cppblog.com/converse/archive/2010/07/04/119287.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>集成libevent,google protobuf的RPC框架</title><link>http://www.cppblog.com/converse/archive/2010/06/20/118310.html</link><dc:creator>那谁</dc:creator><author>那谁</author><pubDate>Sun, 20 Jun 2010 08:30:00 GMT</pubDate><guid>http://www.cppblog.com/converse/archive/2010/06/20/118310.html</guid><wfw:comment>http://www.cppblog.com/converse/comments/118310.html</wfw:comment><comments>http://www.cppblog.com/converse/archive/2010/06/20/118310.html#Feedback</comments><slash:comments>4</slash:comments><wfw:commentRss>http://www.cppblog.com/converse/comments/commentRss/118310.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/converse/services/trackbacks/118310.html</trackback:ping><description><![CDATA[RPC(<font size="-1">Remote Procedure Call</font>),中文翻译是远程过程调用,其实从原理来说这并不是一个新的概念.我的理解是, 不同的机器之间定义了一些接口, 也有客户端和服务器端,客户端可以通过协商好的接口调用服务器端已经注册好的服务.说白了,还是网络通信的那一套机制.既然还是网络通信,那么为什么需要使用RPC而不是自己去完成这样的一套工作呢?假如是自己做这样的事情,需要考虑编解码,网络层,尤其很多细节需要去关注:协议有哪些?如何定义格式?涉及到整数的还要考虑网络和主机字节序等,如果逻辑程序员还需要关注这些细节,显然太繁琐了.还有就是,国内的公司开发很少有文档,假如查找问题时还需要通过读代码才能知道协议中各个字段的含义,这样对项目的可维护性会有很大的影响.假如使用了RPC,通过RPC工具定义的格式来定义协议,可以一目了然.而且,网络层就应该只关注网络层的工作,逻辑层架构在网络层之上再完成逻辑的操作.把网络和逻辑分开,也是清晰的架构设计.<br><br><a href="http://code.google.com/p/protobuf/">google protobuf</a> 是google公开的一套用于网络通信时用于协议编解码的工具库,使用它定义的格式,你可以定义协议的字段,由它自带的编译器生成出负责编解码的代码文件(可生成许多不同的语言文件).同时,它还包括了基本的RPC接口定义.但是,这个工具用在RPC上比较大的问题是它只负责生成代码文件,而如果要真正使用起来做为一个RPC框架,还需要对它进行网络层上的封装,但是在它自己的官方文档上并没有给出一个demo告诉读者如何一步一步的来完成这样一个工作.<a href="http://incubator.apache.org/thrift/">thrift</a>是与google protobuf同样定位的一个工具库,除了具备google protobuf相同的功能外,如支持多语言,跨平台,高效的编解码,还集成了网络通信层,可以使用它完成所有RPC所需要完成的工作.在<a href="http://code.google.com/p/protobuf/wiki/ThirdPartyAddOns">这个页面</a>中,google protobuf给出了一些已知的使用不同语言对它进行封装的项目.<br><br><a href="http://blog.csdn.net/Solstice">chenshuo</a>的<a href="http://code.google.com/p/evproto/">evproto</a>同样也是集成libevent与google protobuf的RPC框架,不过在对libevent的使用上,这里的做法与他不尽相同:<br>1) 他使用了libevent自带的RPC功能, 而这里只使用到libevent对网络I/O进行的封装的最基本的功能.<br>2) 之所以有1)的考虑,是因为我认为一个工具最好应该是"do one thing, do it better"的(也许从这点可以解释为什么google protobuf没有像thrift那样自带网络层,而是把这个工作留给了用户),libevent已经越来越大,除了对I/O,信号,定时器等的封装之外,现在还有RPC,异步DNS,http协议的支持等等,说真的,如果只是关注到网络I/O的多路复用机制,那么几乎任何一个熟练的程序员都可以很快的自己做出这样的一套东西来,使用libevent无非就是为今后可能的跨平台做准备罢了.随着我对libevent发展方向的不认同,还曾经想过使用libev替代libevent,不过现在暂时不想折腾这个事情了.<br><br>eventrpc项目目前是<a href="http://code.google.com/p/avidya/">avidya</a>下的一个子项目,avidya项目的定位是实现一些分布式的玩具系统(比如google已经公开论文的chubby,mapreduce,GFS等),也许以后不一定能被用上,但是也要实践做一把.由于有一个好用的RPC框架是做分布式的必需品,所有首先实现eventrpc这个子项目了,以后也许还会实现其他语言的版本,如python,java.<br><br>eventrpc的网络模型上,使用以前提到的<a href="http://www.cppblog.com/converse/archive/2010/03/11/109449.html">memcached的网络模型</a>, 主线程负责接收新的连接, 再将这些新的连接交由副线程处理,每个副线程自带I/O dispatcher.在samples目录下,有一个实现了echo服务的客户端和服务器端示例.<br><br>在使用之前,请确保libevent和google protobuf已经安装成功,当前只在linux下可用.<br><br>  <img src ="http://www.cppblog.com/converse/aggbug/118310.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-20 16:30 <a href="http://www.cppblog.com/converse/archive/2010/06/20/118310.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>解读google C++ code style谈对C++的理解</title><link>http://www.cppblog.com/converse/archive/2010/05/29/116689.html</link><dc:creator>那谁</dc:creator><author>那谁</author><pubDate>Sat, 29 May 2010 12:34:00 GMT</pubDate><guid>http://www.cppblog.com/converse/archive/2010/05/29/116689.html</guid><wfw:comment>http://www.cppblog.com/converse/comments/116689.html</wfw:comment><comments>http://www.cppblog.com/converse/archive/2010/05/29/116689.html#Feedback</comments><slash:comments>43</slash:comments><wfw:commentRss>http://www.cppblog.com/converse/comments/commentRss/116689.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/converse/services/trackbacks/116689.html</trackback:ping><description><![CDATA[
C++是一门足够复杂的语言.说它"足够复杂",是因为C++提供了足够多编程范式--泛型, 模板, 面向对象, 异常,等等.顺便说说,我已经很久没有跟进C++的最新发展了(比如C++0x), 所以前面列举出来的特性应该只是C++所有特性的一个部分罢了.C++特性过多很难驾驭好C++的原因之一.另一个原因是C++过于"自作聪明",在很多地方悄无声息的做了很多事情, 比如隐式的类型转换, 重载, 模板推导等等.而很多时候,这些动作难以察觉,有时候会在你意想不到的地方发生,即使是熟练的C++程序员也难免被误伤.(关于了解C++编译器自作聪明做了哪些事情, &lt;&lt;深入理解C++物件模型&gt;&gt;是不错的选择).<br><br>世界上有很多问题, 人们知道如何去解决.但是, 似乎这还不算是最高明的,更高明的做法是学会避免问题的发生.而如何避免问题的发生, 需要经验的积累--曾经犯下错误,吃一堑长一智,于是知道哪些事情是不该做的或者是不应该这么做的.<br><br><a href="http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml">google C++ code style</a>是google对外公布的一份google内部编写C++的代码规范文档.与其他很多我曾经看过的编码文档一样,里面有一些关于代码风格的规定,也就是代码的外观,这一部分不在这里过多讨论,毕竟代码如何才叫"美观"是一个见仁见智的话题.在这里专门讨论这份文档中对一些C++特性该如何使用的讨论,最后再做一个总结.注意其中的序号并不是文档中的序号,如果要详细了解,可以自己去看这份文档.<br><br><div style="background-color: rgb(238, 238, 238); font-size: 13px; border-left-color: rgb(204, 204, 204); padding-right: 5px; padding-bottom: 4px; padding-left: 4px; padding-top: 4px; width: 98%; word-break: break-all; "><!--<br><br>Code highlighting produced by Actipro CodeHighlighter (freeware)<br>http://www.CodeHighlighter.com/<br><br>--><span style="color: #000000; ">1</span><span style="color: #000000; ">)&nbsp;Static&nbsp;and&nbsp;Global&nbsp;Variables<br>&nbsp;&nbsp;&nbsp;Static&nbsp;or&nbsp;global&nbsp;variables&nbsp;of&nbsp;</span><span style="color: #0000FF; ">class</span><span style="color: #000000; ">&nbsp;type&nbsp;are&nbsp;forbidden:&nbsp;they&nbsp;cause&nbsp;hard</span><span style="color: #000000; ">-</span><span style="color: #000000; ">to</span><span style="color: #000000; ">-</span><span style="color: #000000; ">find&nbsp;bugs&nbsp;due&nbsp;to&nbsp;indeterminate&nbsp;order&nbsp;of&nbsp;construction&nbsp;and&nbsp;destruction.</span></div>google明确禁止全局对象是类对象, 只能是所谓POD(Plain Old Data,如int char等)数据才行.因为C++标准中没有明确规定全局对象的初始化顺序, 假设全局类对象A,B,其中A的初始化依赖于B的值, 那么将无法保证最后的结果.如果非要使用全局类对象, 那么只能使用指针, 在main等函数入口统一进行初始化.<br><br><div style="background-color: rgb(238, 238, 238); font-size: 13px; border-left-color: rgb(204, 204, 204); padding-right: 5px; padding-bottom: 4px; padding-left: 4px; padding-top: 4px; width: 98%; word-break: break-all; "><!--<br><br>Code highlighting produced by Actipro CodeHighlighter (freeware)<br>http://www.CodeHighlighter.com/<br><br>--><span style="color: #000000; ">2</span><span style="color: #000000; ">)&nbsp;Doing&nbsp;Work&nbsp;</span><span style="color: #0000FF; ">in</span><span style="color: #000000; ">&nbsp;Constructors<br>In&nbsp;general,&nbsp;constructors&nbsp;should&nbsp;merely&nbsp;</span><span style="color: #0000FF; ">set</span><span style="color: #000000; ">&nbsp;member&nbsp;variables&nbsp;to&nbsp;their&nbsp;initial&nbsp;values.&nbsp;Any&nbsp;complex&nbsp;initialization&nbsp;should&nbsp;go&nbsp;</span><span style="color: #0000FF; ">in</span><span style="color: #000000; ">&nbsp;an&nbsp;</span><span style="color: #0000FF; ">explicit</span><span style="color: #000000; ">&nbsp;Init()&nbsp;method.&nbsp;</span></div>文档规定, 在类构造函数中对类成员对象做基本的初始化操作, 所有的复杂初始化操作集中一个比如Init()的函数中,理由如下:<br>
<ul>
    <li> There is no easy way for constructors to signal errors,
    short of using exceptions (which are
    <a href="http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Exceptions">forbidden</a>).
    </li>
    <li> If the work fails, we now have an object whose
    initialization code failed, so it may be an
    indeterminate state.
    </li>
    <li> If the work calls virtual functions, these calls will
    not get dispatched to the subclass implementations.
    Future modification to your class can quietly introduce
    this problem even if your class is not currently
    subclassed, causing much confusion.
    </li>
    <li> If someone creates a global variable of this type
    (which is against the rules, but still), the
    constructor code will be called before
    <code>main()</code>, possibly breaking some implicit
    assumptions in the constructor code.  For instance,
    <a href="http://google-gflags.googlecode.com/">gflags</a>
    will not yet have been initialized.
    </li>
</ul>
简单的概括起来也就是:构造函数没有返回值, 难以让使用者感知错误;假如在构造函数中调用虚拟函数, 则无法按照使用者的想法调用到对应子类中实现的虚拟函数(理由是构造函数还未完成意味着这个对象还没有被成功构造完成).<br><br><div style="background-color: rgb(238, 238, 238); font-size: 13px; border-left-color: rgb(204, 204, 204); padding-right: 5px; padding-bottom: 4px; padding-left: 4px; padding-top: 4px; width: 98%; word-break: break-all; "><!--<br><br>Code highlighting produced by Actipro CodeHighlighter (freeware)<br>http://www.CodeHighlighter.com/<br><br>--><span style="color: #000000; ">3</span><span style="color: #000000; ">)&nbsp;Default&nbsp;Constructors<br>You&nbsp;must&nbsp;define&nbsp;a&nbsp;</span><span style="color: #0000FF; ">default</span><span style="color: #000000; ">&nbsp;constructor&nbsp;</span><span style="color: #0000FF; ">if</span><span style="color: #000000; ">&nbsp;your&nbsp;</span><span style="color: #0000FF; ">class</span><span style="color: #000000; ">&nbsp;defines&nbsp;member&nbsp;variables&nbsp;and&nbsp;has&nbsp;no&nbsp;other&nbsp;constructors.&nbsp;Otherwise&nbsp;the&nbsp;compiler&nbsp;will&nbsp;</span><span style="color: #0000FF; ">do</span><span style="color: #000000; ">&nbsp;it&nbsp;</span><span style="color: #0000FF; ">for</span><span style="color: #000000; ">&nbsp;you,&nbsp;badly.&nbsp;</span></div>当程序员没有为类编写一个默认构造函数的时候, 编译器会自动生成一个默认构造函数,而这个编译器生成的函数如何实现(比如如何初始化类成员对象)是不确定的.这样,假如出现问题时将给调试跟踪带来困难.所以, 规范要求每个类都需要编写一个默认构造函数避免这种情况的出现.<br><br><div style="background-color: rgb(238, 238, 238); font-size: 13px; border-left-color: rgb(204, 204, 204); padding-right: 5px; padding-bottom: 4px; padding-left: 4px; padding-top: 4px; width: 98%; word-break: break-all; "><!--<br><br>Code highlighting produced by Actipro CodeHighlighter (freeware)<br>http://www.CodeHighlighter.com/<br><br>--><span style="color: #000000; ">4</span><span style="color: #000000; ">)&nbsp;Explicit&nbsp;Constructors<br>Use&nbsp;the&nbsp;C</span><span style="color: #000000; ">++</span><span style="color: #000000; ">&nbsp;keyword&nbsp;</span><span style="color: #0000FF; ">explicit</span><span style="color: #000000; ">&nbsp;</span><span style="color: #0000FF; ">for</span><span style="color: #000000; ">&nbsp;constructors&nbsp;with&nbsp;one&nbsp;argument.</span></div>假如构造函数只有一个参数, 使用explicit避免隐式转换,
因为隐式转换可能在你并不需要的时候出现.<br>
<br><div style="background-color: rgb(238, 238, 238); font-size: 13px; border-left-color: rgb(204, 204, 204); padding-right: 5px; padding-bottom: 4px; padding-left: 4px; padding-top: 4px; width: 98%; word-break: break-all; "><!--<br><br>Code highlighting produced by Actipro CodeHighlighter (freeware)<br>http://www.CodeHighlighter.com/<br><br>--><span style="color: #000000; ">5</span><span style="color: #000000; ">)&nbsp;Copy&nbsp;Constructors<br>Provide&nbsp;a&nbsp;copy&nbsp;constructor&nbsp;and&nbsp;assignment&nbsp;</span><span style="color: #0000FF; ">operator</span><span style="color: #000000; ">&nbsp;only&nbsp;when&nbsp;necessary.&nbsp;Otherwise,&nbsp;disable&nbsp;them&nbsp;with&nbsp;DISALLOW_COPY_AND_ASSIGN.</span></div>只有当必要的时候才需要定义拷贝构造函数和赋值操作符.
同上一条理由一样, 避免一些隐式的转换.另一条理由是,"="难以跟踪,如果真的要实现类似的功能,可以提供比如名为Copy()的函数,这样子一目了然,不会像赋值操作符那样可能在每个"="出现的地方出现.<br><br><div style="background-color: rgb(238, 238, 238); font-size: 13px; border-left-color: rgb(204, 204, 204); padding-right: 5px; padding-bottom: 4px; padding-left: 4px; padding-top: 4px; width: 98%; word-break: break-all; "><!--<br><br>Code highlighting produced by Actipro CodeHighlighter (freeware)<br>http://www.CodeHighlighter.com/<br><br>--><span style="color: #000000; ">6</span><span style="color: #000000; ">)&nbsp;Operator&nbsp;Overloading<br>Do&nbsp;not&nbsp;overload&nbsp;operators&nbsp;except&nbsp;</span><span style="color: #0000FF; ">in</span><span style="color: #000000; ">&nbsp;rare,&nbsp;special&nbsp;circumstances.</span></div>不要重载操作符.同样, 也是避免莫名其妙的调用了一些函数.同上一条一样, 比如要提供对"=="的重载, 可以提供一个名为Equal()的函数, 如果需要提供对"+"的重载, 可以提供一个名为Add()的函数.<br><br><div style="background-color: rgb(238, 238, 238); font-size: 13px; border-left-color: rgb(204, 204, 204); padding-right: 5px; padding-bottom: 4px; padding-left: 4px; padding-top: 4px; width: 98%; word-break: break-all; "><!--<br><br>Code highlighting produced by Actipro CodeHighlighter (freeware)<br>http://www.CodeHighlighter.com/<br><br>--><span style="color: #000000; ">7</span><span style="color: #000000; ">)&nbsp;Function&nbsp;Overloading<br>Use&nbsp;overloaded&nbsp;functions&nbsp;(including&nbsp;constructors)&nbsp;only&nbsp;</span><span style="color: #0000FF; ">in</span><span style="color: #000000; ">&nbsp;cases&nbsp;where&nbsp;input&nbsp;can&nbsp;be&nbsp;specified&nbsp;</span><span style="color: #0000FF; ">in</span><span style="color: #000000; ">&nbsp;different&nbsp;types&nbsp;that&nbsp;contain&nbsp;the&nbsp;same&nbsp;information.&nbsp;Do&nbsp;not&nbsp;use&nbsp;function&nbsp;overloading&nbsp;to&nbsp;simulate&nbsp;</span><span style="color: #0000FF; ">default</span><span style="color: #000000; ">&nbsp;function&nbsp;parameters.</span></div>只有在不同的类型表示同样的信息的时候, 可以使用重载函数.其他情况下,一律不能使用.使用重载, 也可能出现一些隐式出现的转换.所以, 在需要对不同函数进行同样操作的时候, 可以在函数名称上进行区分, 而不是使用重载,如可以提供针对string类型的AppendString()函数, 针对int类型的AppendInt()函数,而不是对string和int类型重载Append()函数.另一个好处在于, 在阅读代码时,通过函数名称可以一目了然.<br><br><div style="background-color: rgb(238, 238, 238); font-size: 13px; border-left-color: rgb(204, 204, 204); padding-right: 5px; padding-bottom: 4px; padding-left: 4px; padding-top: 4px; width: 98%; word-break: break-all; "><!--<br><br>Code highlighting produced by Actipro CodeHighlighter (freeware)<br>http://www.CodeHighlighter.com/<br><br>--><span style="color: #000000; ">8</span><span style="color: #000000; ">)&nbsp;Exceptions<br>We&nbsp;</span><span style="color: #0000FF; ">do</span><span style="color: #000000; ">&nbsp;not&nbsp;use&nbsp;C</span><span style="color: #000000; ">++</span><span style="color: #000000; ">&nbsp;exceptions.</span></div>不使用异常.理由如下:<br>
<ul>
    <li>When you add a <code>throw</code> statement to an existing
    function, you must examine all of its transitive callers.
    Either
    they must make at least the basic exception safety guarantee,
    or
    they must never catch the exception and be happy with the
    program terminating as a result. For instance, if
    <code>f()</code> calls <code>g()</code> calls
    <code>h()</code>, and <code>h</code> throws an exception
    that <code>f</code> catches, <code>g</code> has to be
    careful or it may not clean up properly.</li>
    <li>More generally, exceptions make the control flow of
    programs difficult to evaluate by looking at code: functions
    may return in places you don't expect. This results
    maintainability and debugging difficulties. You can minimize
    this cost via some rules on how and where exceptions can be
    used, but at the cost of more that a developer needs to know
    and understand.</li>
    <li>Exception safety requires both RAII and different coding
    practices. Lots of supporting machinery is needed to make
    writing correct exception-safe code easy. Further, to avoid
    requiring readers to understand the entire call graph,
    exception-safe code must isolate logic that writes to
    persistent state into a "commit" phase. This will have both
    benefits and costs (perhaps where you're forced to obfuscate
    code to isolate the commit). Allowing exceptions would force
    us to always pay those costs even when they're not worth
    it.</li>
    <li>Turning on exceptions adds data to each binary produced,
    increasing compile time (probably slightly) and possibly
    increasing address space pressure.
    </li>
    <li>The availability of exceptions may encourage developers
    to throw them when they are not appropriate or recover from
    them when it's not safe to do so. For example, invalid user
    input should not cause exceptions to be thrown. We would
    need to make the style guide even longer to document these
    restrictions!</li>
</ul>
上面提到的理由中, 我认为使用异常最大的害处就是:异常的使用导致了程序无法按照代码所展现的流程去走的, 比如代码里面写了步骤一二三,但是假如有异常出现, 这就不好预知代码真正步进的步骤了, 在出现问题时, 给调试和跟踪带来困难.<br>另外, 我更喜欢unix API的设计.熟悉unix编程的人都知道, unix API基本上都遵守下列规则:<br>a) 返回0表示成功, 其他(一般是-1)表示失败.<br>b) 在失败时, 可以根据errno判断失败的原因, 这些在man手册中都是会清楚的描述.<br><br>总结一下, 这份规范中规避的C++特性大致分为以下几类:<br>a) 避免使用那些没有确定行为的特性:如全局变量不能是类对象(初始化顺序不确定), 不使用编译器生成的默认构造函数(构造行为不确定), 异常(代码走向不确定).<br>b) 避免使用那些隐式发生的操作:如声明单参数构造函数为explict以避免隐式转换, 不定义拷贝构造函数避免隐式的拷贝行为, 不使用操作符重载避免隐式的转换<br>c) 对模棱两可的特性给予明确的规定:不使用函数重载而是定义对每个类型明确的函数.<br>d) 即使出错了程序也有办法知道: 比如不能在类构造函数中进行复杂的构造操作, 将这些移动到类Init()的函数中.<br><br>同时, 这份文档中描述的大部分C++特性, 都是我之前所熟悉的(除了RTTI之外, 不过这里提到它也是要说明不使用它,另外还提到boost, 不过也是说的要对它"有限制"的使用,比如里面的智能指针).可以看到, 面对这样一门复杂同时还在不停的发展更新特性的语言, google的态度是比较"保守"的.这与我之前对C++的理解也是接近的, 我一直认为C++中需要使用到的特性有基本的面向对象+STL就够了(经过最近的编码实践,我认为还得加个智能指针).我对这个"保守"态度的理解是, 以C++当前的应用场景来看, 这些特性已经足够, 如果使用其他一些更加复杂的, 对人的要求提高了, 代码的可读性以及以后的可维护性就下降了.<br><br>前面说过, 避免问题的出现比解决问题来的更加高明些, 而面对C++这一个提供了众多特性, google C++ code style给予了明确的规定, 也就是每个行为, 如果都能做到有明确的动作, 同时结果也都是可以预知的, 那么会将出问题的概率最大可能的降低, 即使出了问题, 也容易跟踪.<br><br>上面描述的并不是这份文档中有关C++的所有内容, 只不过我觉得这些更加有同感些, 详细的内容, 可以参看这份文档.都知道google的作品,质量有保证, 除了人的素质确实高之外, 有规范的制度保证也是重要的原因, 毕竟只要是人就会犯错, 为了最大限度的避免人犯错, 有一份详尽的代码规范, 写好哪些该做哪些不该做哪些不该这么做, 也是制度上的保证.另外, 假如每个人都能以一个比较高的标准要求自己所写的代码, 久而久之, 获得进步也是必然的结果.<br><br>从这套规范里面, 我的另一个感悟是, 不论是什么行业, "学会如何正确的做事情", 都是十分必要的.这个"正确的做事情", 具体到编码来说, 就是代码规范里面提到的那些要求.而除去编码, 做任何的事情, 使用正确的方式做事, 都是尽可能少的避免错误的方法.但是, "错"与"对"是相对而言的, 没有之前"错"的经历, 就不好体会什么叫"对".所以, "如何正确的做事", 说到了最后, 还得看个人的经验积累, 有了之前"错误"的经历,才能吃一堑长一智, "错误"并不是一无是处的, 只不过, 并不是谁都去尝试着从中学习.<br><br><br>        <img src ="http://www.cppblog.com/converse/aggbug/116689.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-05-29 20:34 <a href="http://www.cppblog.com/converse/archive/2010/05/29/116689.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Callback在C\C++中的实现</title><link>http://www.cppblog.com/converse/archive/2010/04/19/113023.html</link><dc:creator>那谁</dc:creator><author>那谁</author><pubDate>Mon, 19 Apr 2010 14:45:00 GMT</pubDate><guid>http://www.cppblog.com/converse/archive/2010/04/19/113023.html</guid><wfw:comment>http://www.cppblog.com/converse/comments/113023.html</wfw:comment><comments>http://www.cppblog.com/converse/archive/2010/04/19/113023.html#Feedback</comments><slash:comments>12</slash:comments><wfw:commentRss>http://www.cppblog.com/converse/comments/commentRss/113023.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/converse/services/trackbacks/113023.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: Callback是这样的一类对象(在这里不能简单的理解为"回调函数"了):你注册一个函数,以及调用它时的参数,希望在满足某个条件时,以这些注册的函数调用这个回调,完成指定的操作.很多地方会使用到这个概念.比如,UI程序中,注册一个函数,当某个鼠标事件发生的时候自动调用;比如,创建一个线程,线程开始运行时,执行注册的函数操作.Callback的出现,本质上是因为很多操作都有异步化的需要---你不知道...&nbsp;&nbsp;<a href='http://www.cppblog.com/converse/archive/2010/04/19/113023.html'>阅读全文</a><img src ="http://www.cppblog.com/converse/aggbug/113023.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-19 22:45 <a href="http://www.cppblog.com/converse/archive/2010/04/19/113023.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>memcached采用的网络模型</title><link>http://www.cppblog.com/converse/archive/2010/03/11/109449.html</link><dc:creator>那谁</dc:creator><author>那谁</author><pubDate>Thu, 11 Mar 2010 12:30:00 GMT</pubDate><guid>http://www.cppblog.com/converse/archive/2010/03/11/109449.html</guid><wfw:comment>http://www.cppblog.com/converse/comments/109449.html</wfw:comment><comments>http://www.cppblog.com/converse/archive/2010/03/11/109449.html#Feedback</comments><slash:comments>6</slash:comments><wfw:commentRss>http://www.cppblog.com/converse/comments/commentRss/109449.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/converse/services/trackbacks/109449.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: memcached采用的网络模型是早前提到的半同步半异步的网络模型.&nbsp;&nbsp;<a href='http://www.cppblog.com/converse/archive/2010/03/11/109449.html'>阅读全文</a><img src ="http://www.cppblog.com/converse/aggbug/109449.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-03-11 20:30 <a href="http://www.cppblog.com/converse/archive/2010/03/11/109449.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>