2009年4月5日星期日

救火沉思录--关于项目最后阶段的思考

原帖:http://blog.csdn.net/absurd/archive/2005/12/13/551588.aspx
作者联系方式:李先静 <xianjimli at hotmail dot com>

这几个月来,大部分业余时间,都花在阅读软件工程和编译原理方面的书籍上了。软件工程方面的书,包括软件需求、风险管理、敏捷建模,系统设计,软件项目管理,还有一些类似于的沉思录书籍等。

在这些书中,都只是讲了如何让项目健康发展,最后成功的提交一个产品。尽管它们都是从不同的角度,用不同的方法去完成同样的事。但它们几乎都支持这样的观点:计划+修正计划(不但设计是迭代的,计划也是迭代的)。用其中一个作者的话说,伤害你的,不是那些你没有考虑完整的,而是你根本没去考虑的事情。

然而,几乎没有一本书里,讲到关于消防队的事,唉,真是奇怪,老外声称有超过50%的项目是失败的,那么在他们的项目中,失火也是常事,为什么就不谈谈救火的招数呢?难道他们也相信,不叫出魔鬼的名字,魔鬼就不会找上门来吗?

唯一的解释就是,救火太难了,可能老外的救火能力远不如我们,他们干脆就不谈了。我在上一个项目中牺牲惨重,巨大的压力之下,和女友分手,精神上和身体上都受到极大的伤害,当然,我不是那个项目唯一的牺牲品,很多同事,他们很优秀,也一样的无助。之后,我一直在想,既然有50%的项目会失火,那么救火能力和计划能力至少是等同重要了。我苦苦的思索,回忆上次的经历,查找相关资料,然而收获甚微。

救火的银弹也许永远不会出现,我把自己一些经验写出来,或许对大家有点帮助,如果能达到抛砖引玉的效果那是更好了:

1. 在FIX
BUG过程中,持续进行重构。在设计时没有做好,重做是不太可能的了,但绝望也是没有意义的,我们只能想法去改进它。利用前人一些经验,持续进行重构,每FIX一个BUG,我们让代码更好一点,而不是更坏一点,FIX了一个BUG,代码中就少了一个BUG,而不是引更多的BUG。在实际上,重构最大的困难是没有完整的自动测试程序和测试用例,这使得我们根本不敢去改动代码,或者为了让改动最小,采取一些折中的方法,这都使得代码不断的变臭。在这种情况下,建议是建立自动测试,然后不断完善测试用例,我觉得建立自动测试任何时候都不晚。如果建立自动测试确实比较困难,那就列出所有的测试用例,然后手工测试。这时候,工程师的工作就是:重构à测试àFIX
BUGà测试。有人说,我没有时间去重构,没有时间去测试。呵,这会使我想到,一个人围绕着一个小圆圈拼命的奔跑,累得半死的时候,发现在原地,他还在说,我没有时间去看清方向。

2.
关注常用功能。在项目的最后阶段,千万不要被QA牵着走,他们发现一个BUG,我们就FIX它。FIX一个BUG当然好,但是FIX
BUG不是免费的,要不但要成本,还有潜在的风险。编译的优化原理是基于:20%的代码花了80%的时间。如果这个原理成立,可以推出:80%的用户实际上只使用20%的功能。QA并不是最终用户,QA和最终用户的不同在于:QA尽力去发现不常见的问题,而最终用户经常使用最常用的功能。这时候我们可以把自己想成最终用户,列出最常用的测试用例,如果不在这些测试用例中的情况,即使BUG的现象很严重,我们也要考虑一下再决定是否修改它。

3.
确定哪些BUG不改同样重要。这一点与2有一定的重复,为了强调有必要单独提出来。在软件需求分析时,分析师们都认为,要确定什么不在系统内和什么在系统内一样重要。程序员对于BUG态度,有时往往走两个极端:一种是老子就不改。一种QA怎么说我就怎么改。前者往往被看着工作态度不端正。而后者呢,却被视为好孩子。其实,在项目的最后阶段,后者未必正确,正如前面所说,FIX
BUG不是免费的。这时候建立一个仲裁委员会有必要的,确定哪些BUG不改是他们的职责之一。

4.
BUG分类,明确责任。以前接手别人一个模块,处于Pending状态的BUG已经有110多个了。要把每一个BUG都看一遍就要花几个小时,不看吧,每次改一个BUG时,总有只见树木不见森林的感觉。最初,我很努力的去修改BUG,进展还是甚微。后来我花了几天时间,仔细分析了所有BUG,把它们归纳几类:其它模块引起的BUG;
和其它模块的接口引起的BUG; 超出需求之外的BUG;
完全是本模块内部的BUG。然后把其它模块引起的BUG提交给相关人员,和相关人员确认因接口不统一引起的BUG,把超出需求之外的BUG提交给需求控制委员会,最后剩下本模块的BUG又根据引起BUG的原因分为几类。这样,这些BUG很快被FIX了。

5.
工程师应该积极寻求帮助。有什么自己解决不了问题,应该向知道的人请教,或者向上司寻求帮助,不要出于面子或者其它原因,而花费大量的时间。在项目的最后阶段,每一分钟都很宝贵,不要重新发明轮子,对于有共性的难题也应该由专人解决。

6.
项目经理应该把眼光放在全局上。项目经理应该更多的关注于全局的事务,不要学只想拿大红花的小学生。别只顾修改自己的BUG,你的BUG少,并不能说明你是个好项目经理,在项目失败时,你个人的BUG少,并不能真正减轻你的罪恶感。据说软件团队遵循水桶原则,最低的那块木板才是决定装多少水要素,而不是最高的那块。项目经理应该随时关注哪块是最低的,然后把它补起来,自己成为最高的那块是没有意义的。

7. Person Review以提高士气。呵,不知道有没有Person
Review这个术语,反正我觉得挺好的,在项目的最后阶段,士气是非常宝贵的东西,可以说得士气者得天下。在前一个公司,每周一,老板会把每个工程师叫到他的办公室,一起聊会儿,聊天内容不限,多半是问问你这边工作上存在什么问题,有什么看法,非常坦白的谈一会儿,最后会得到他的鼓励和赞扬,自己感觉这对提高士气很有帮助的,当然老板最好是个好的煽动者。

8. Bug Review。建立一个Bug Review小组,他们的主要责任是:
发现一些具有共性的BUG,确认哪些BUG需要FIX,哪个BUG不用FIX。有共性的BUG,让专人解决或者督促。不管一个BUG是要FIX还是不用FIX,都要注明足够的理由。

9.
加强QA和RD之间的合作。呵,根据遗传学和适者生存原理可以知道,在最后阶段,BUG的生命力极强,往往花费很长时间才能重现。加上自然语言本身具有的二义性和个人看问题的侧重点不同,QA可能忽略了RD让认为很重要的重现步骤,QA的BUG描述在RD眼中也可能迥然不同。在这个阶段,直接到现场和QA交流一下,可能会节省很多时间。同时也要尊重QA的劳动成果,这样他们才会更积极的配合。

10.
经验积累。每遇到一个BUG,想一想,它为什么会出现,为什么才出现,修改它后会有什么后果。把重要的记录下来,可能对自己和别人都有所启发,以减少犯同样错误的机会。

徐志摩陆小曼

七夕节是中国的情人节,就在牛郎织女相会的那天,1926年北京北海公园进行了一场人们议论了很久的婚事。新郎徐志摩,新娘陆小曼早已成了舆论的中心,婚礼上,徐志摩老师梁启超的祝词使得在场的所有人,除了他自己之外,都感到难堪。他不仅没有讲吉祥话,反而对新郎、新娘痛斥,切责他们不该把婚姻当成儿戏,最后说道:"祝你们这是最后一次结婚!"事后梁启超写信给他的女儿梁令娴,说道:"我昨天做了一件极不愿意做的事,就是去替徐志摩证婚。他的新妇是王赓的夫人。与志摩爱上才和王赓离婚,实在是不道德之极。志摩找到这样一个人做伴侣,怕将来痛苦会接踵而来。所以不惜声色俱厉地予以当头棒喝,盼能有所觉悟,免得将来把志摩弄死。我在结婚礼堂上大大地予以教训,新人及满堂宾客无不失色,此恐为中外古今未闻之婚礼也。"
  陆小曼是北京城里有名的交际花,她的父亲陆定原是财政部的赋税司司长,后来弃政从商,出任震华银行总经理,算得上是一位财神爷型的人物。陆家有的是钱,舍不得让宝贝女儿进学堂,便把需要的老师都请到家里,所以陆小曼虽然没有上过学,除中国文字颇具造诣外,英文、法文的口语笔译都流畅自然。在艺术方面,除写得一手绢秀的毛笔字外,国画、京戏、舞蹈样样都行。再加上她从小口齿伶俐,长得象个小仙女似的,便赢得了"绝代佳人"的美誉。
  陆小曼的第一个丈夫就是梁启超信中提到的王赓,曾留学美国普林斯顿大学,西点军校。与陆小曼结婚的时候,正任教北京大学。婚后不久,两人的生活方式与生活习惯便产生了差异。王赓办事认真负责,为准备授课经常埋头研究。陆小曼生性风流,三天两头到外头游乐。于是王赓认为陆小曼没有尽到一个妻子的责任,也没有守住妇道人家的本份;陆小曼则认为王赓不够体贴,喝过洋墨水的的人还如此古板。这时王赓留学美国时的好友,北京大学的同事徐志摩悄悄地介入进来。
  陆小曼是江苏武进人,徐志摩是浙江硖石人,比王赓小一岁,比陆小曼大六岁。曾入北京大学、美国克拉克大学、英国剑桥大学读书,生来绝顶聪明,后来拜在梁启超的门下。他的散文、新诗把中国古典文学和西方文学揉合在一起,深入浅出、华而不腻、媚而不俗。"五四"运动前夕,他提任《北京晨报》的副主编,后来又成立"新月书店",发行《新月杂志》。在新文化运动中推波助澜,他成了追求时髦的人崇拜的偶像。徐志摩卓尔不群、兴趣广泛、风流潇洒。他早就有了结发妻子张嘉玲,但在欧洲留学期间拼命追求林徽音。这样的人自然是陆小曼喜爱的。
  徐志摩是王赓的好朋友,渐渐地和陆小曼也就熟悉了。王赓一旦遇到事情多分不开身或是懒得出去的时候,便叫徐志摩陪着陆小曼外出游山玩水或钻进灯红酒绿的场合消遣。那时,徐志摩正处在失恋阶段,他拚命追求的林徽音瞧不起他,和梁启超的长子梁思成结婚了。于是便把满腹的柔情转移到陆小曼身上。恰好王赓受聘赴哈尔滨提任警察局长,陆小曼空闺独守、芳心寂寞。陆、徐二人就象A、B胶一样,越粘越紧。
  从传统道德讲,徐志摩追求陆小曼,算是对结发妻子张嘉鈖不忠,对朋友王赓不义。然而徐志摩向来是随兴而为、不拘绳墨,一旦"邂逅赏心,相倾怀抱",就顾不了身外之事了。陆家和徐家都认为他们是不孝子女,是丑闻,极力阻止。徐志摩、陆小曼认为:"真爱不是罪恶,在必需时未尝不可以付出生命的代价来争取,与烈士殉国、教徒殉道,同是一理。"


徐志摩向世人宣示:"我之甘冒世之不韪,乃求良心之安顿,人格之独立。在茫茫人海中,访我灵魂之伴侣,得之我幸,不得我命,如此而已!"两人在风气初开的潮流中,受到青年男女的怂恿、喝采,豪气干云地踏上"不思旧姻求新婚"的道路。像郁达夫就说:"志摩热情如火,小曼温柔如棉,两人碰在一起,自然会烧成一团,那里还顾得了伦教纲常,更无视于宗法家风。"
  这时王赓受了孙传芳的邀请到了南京,在五省联军总司令部内提任总参谋长的职务,位高权重。风闻妻子行为有异,以快刀斩乱麻的方式,写了一封快信给陆小曼声言:"如念夫妻之情,立刻南下团聚,倘若另有所属,决不加以拦阻。"几经周折,徐志摩与张嘉玲离了婚,王赓与陆小曼也办了分离的手续。王赓对陆小曼酸溜溜地说:"合得来是夫妻,合不来就分开,我自愿退让来成全你们,希望你能过得幸福。"
  在胡适、郁达夫等一批朋友的帮助下,徐志摩积极筹备婚礼。徐家和陆家的长辈对徐志摩、陆小曼的事情十分痛恨,是坚决不参加婚礼的。于是徐志摩的老师梁启超尽管也反对他们两人的结合,是一定要请到的。
在胡适等人一再相劝,好说歹说的情况下,梁启超终于答应参加婚礼。婚礼如期举行,梁启超说了前面提到的那段惊世骇俗的话,想不到竟灵验如神。婚后不久,陆小曼就提出要移居上海,说是要借十里洋场的五光十色,冲淡在北京积累下来的一身晦气。
  他们在福熙路四明村里筑起爱的窝巢,有一段时间两人过得十分甜蜜。第二年春天,暖风醉人、百花怒放。陆小曼终于按捺不住蠢蠢欲动的荡漾春心,开始故态复萌。才刚刚投身社交圈里,便立刻造成极大的轰动。上海是藏龙卧虎之地,多的是满清遗老、王孙贵胄、富商巨贾,以及有钱又有闲的世家子弟。于是有人请她吃饭;有人邀她跳舞;更有人出来怂恿她票戏义演。风头算是出足了,时间、精神与金钱都一齐赔了进去,陆小曼认为十分值得,徐志摩却暗暗叫苦不已。
  有一个叫翁端午的苏州人,家财丰厚、赋性风流、吃喝玩乐、不务正业、出手阔绰、挥霍无度,是雅歌集票房的台柱,更有一手推拿的医道本领。通过票戏与陆小曼相识,于是在陆小曼面前大献殷勤,两人常常搭挡演出获得满堂喝彩。有一次陆小曼演出大轴,唱做累人,曾经一度晕厥。翁端午施展他的推拿绝技,为陆小曼捏捏揉揉,居然解除了陆小曼的疲劳。于是陆小曼便常常要翁端午为她推拿,感到通体舒服,两人的关系渐入佳境。翁端午又教会陆小曼吃鸦片。这样翁端午在陆小曼身上一会儿上下其手,抚摸揉搓;一会儿又和陆小曼倚枕横陈,对灯吞云吐雾,连旁人都看不过去。然而徐志摩仍以赤子之心为娇妻辩护,他解释说:"夫妇的关系是爱,朋友的关系是情,罗襦半解、妙手摩挲,这是医病;芙蓉对枕,吐雾吞云,最多只能谈情,不能做爱。"
于是陆小曼得寸进尺,完全不把徐志摩放在眼里,当着徐志摩的面与翁端午出双人对,甚至做出亲妮的举动来。当年徐志摩所做的,如今翁端午做得似乎更彻底;当年王庚所难堪的情事,此时徐志摩也尝到了个中苦涩的滋味。真是报应,徐志摩现在是血淋淋地跌在人生现实的荆棘丛中。
  夫妻的感情出现了裂痕,陆小曼仍毫不在乎,昏天黑地地玩着。徐志摩为了供应妻子无底的挥霍,除了在上海教书写作赚钱以外,还风尘仆仆地远赴北京开源。时而上海、时而北京,两头奔忙。
  一九三一年十一月十七日,徐志摩从北京回到上海,晚上和几个朋友在家中聊天。陆小曼依然是很晚才回家,而且喝得醉眼朦胧。朋友们先后了,徐志摩窝了一肚子的火。
第二天,徐志摩耐心开导劝说陆小曼,陆小曼根本就听不进去,两人于是大吵一场。陆小曼正在烟榻上过鸦片烟瘾,突然发起小姐脾气,抓起烟灯就往徐志摩身上砸。虽然没有砸中徐志摩的脑袋,却贴着额角飞过,打掉了徐志摩的眼镜。
徐志摩彻底地绝望,悄然离家到了南京,十九日搭乘中国航空公司京平线的济南号飞机,飞往北平。飞到济南附近的党家庄,遇到漫天大雾,飞机误触开山山头,机毁人亡,徐志摩手脚烧成焦炭,死状极惨。
  徐志摩的死引起极大的震撼,朋友们纷纷从各地赶来,为他操持丧事,郁达夫撰写的挽联高挂灵堂:

       两卷新诗,廿年旧友,相逢同是天涯,只为佳人难再得;
       一声河满,几点齐烟,化鹤重归华表,应愁高处不胜寒。

  而灵堂中最显眼、最感人的挽联还是徐志摩的原配夫人张嘉鈖和宣布脱离父子关系的徐志摩父亲徐申甫的挽联。徐申甫深为儿子的死所不值,他哭道:
  考史诗所载,沉湘捉月,文人横死,各有伤心,儿本超然,岂期邂逅罡风亦遭惨劫;
  自襁褓以来,求学从师,夫妇保持,最怜独子,母今逝矣,忍使凄凉老父重赋招魂。

  张嘉鈖本因为陆小曼的缘故,徐志摩已与她离婚。可她深爱着徐志摩,徐志摩的父亲徐申甫也觉得张嘉玲是难得的好儿媳妇,所以张嘉玲一直仍住在徐家。她是这样哭徐志摩的:

       万里快飞鹏,独撼翳云遂失路;
       一朝惊鹤化,我怜弱惜去招魂。

  灵堂上,徐申甫不愿见陆小曼,张嘉鈖却不避嫌忌,走去安慰陆小曼。陆小曼良心发现,愧悔交加,两人遥遥相对,哭倒灵堂。灵堂中,徐志摩脚下的长明灯忽暗忽明,闪烁不定。
  徐志摩与陆小曼结婚五年,付出了宝贵的生命。老师梁启超的顾虑成了无法挽留的事实。徐志摩死时三十六岁,正当有为之年。陆小曼此时刚刚三十岁,正是女人最绚烂的年华。然而顶着徐志摩未亡人的头衔,自然不得不在社交场所有所收敛,社会上对她也颇不谅解。一般人尽量避免与她发生牵扯,她今天向贺天健学画、明天向汪星伯学诗,打发凄清的岁月。她干脆与翁端午同居,最后为生活所迫,连徐志摩《爱眉小扎》和《志摩日记》的版权也卖给了晨光出版公司。
  "美人自古如名将,不许人间见白头。"
  陆小曼终日疏懒困倦,打不起精神,很快地便憔悴了。她是在一九六五年文化大革命即将来临的时候,死在上海。

我的vim技巧集

平常用到的vim技巧,整理记录在blog上,方便查找。

VIM中,移动光标到下一个单词的词首,使用命令"w",移动光标到上一个单词的词首,使用命令"b";移动光标到下一个单词的结尾,用命令"e",移动光标到上一个单词的结尾,使用命令"ge"。

上面这些命令都使用'iskeyword'选项中的字符来确定单词的分界,还有几个命令,只把空白字符当做"单词"的分界。当然,这里说的"单词"已经不是传统意义上的单词了,而是由非空白字符构成一串字串。命令"W"移动光标到下个字串的开始,命令"B"移动到上个字串的开始;命令"E"移动到下个字串的结尾,命令"gE"移动到上个字串的结尾。

使用H/M/L这三个键,可以让光标跳到当前窗口的顶部、中间、和底部,停留在第一个非空字符上。H命令和L命令前也可以加一个数字,但数字的含义不再是倍数,而是指距窗口顶部、底部的行数。例如,"3H"表示光标移动到距窗口顶部第3行的位置;"5L"表示光标移动到距窗口底部5行的位置

在阅读代码时,有时我们需要根据光标所在的位置滚屏,把光标所在行移动窗口的顶端、中间或底部,这时就可以用到"zt"、"zz"和"zb"。这种滚屏方式相对于翻页来讲,它的好处在于,你能够始终以当前光标位置做为参照,不会出现翻几次页后,发现自己迷失了方向。
^_^
********************************************************************************

1.
选取多个文件用vim打开,只用一个窗口,文件都列在buffer中(这个在windows找到办法了,我用的TC+F4menu,在F4menu设置中的调用vim的"打开方式"项选择"所有文件以列表方式打开",就会将选取的文件都读进buffer,但窗口只显示一个),
gvim --remote-tab-silent
如果在命令行中,直接加参数-p即可在多tab页中分别打开文件,如:vim -p
file1.c file2.c

2. vim用什么方法显示16进制?
:%!xxd

3. 怎样在vim中粘贴进桌面剪贴板里的内容?
比如我在opera里复制的内容粘贴到vim里.
"*p
"+p

4. 在文本wrap的时候怎样才能跳到上一行而不是上一段? 如同notepad里那样?
加个g,如:gj,gk.....
d:\soft

5. 请问vim在命令行上可以复制粘贴么?
Ctrl+r"或者Ctrl+r Ctrl+w可以粘贴到命令行 "
而对把编辑区到命令行,可以先拷贝到如a寄存器,然后在命令行ctrl-r a

6. 如何实现行之间的倒序排序
比如
a
c
b
变成
b
c
a
答案::g/./m0
解释:
:h :m
m->move <address>
m0->把匹配行移动到第一行之前

这是VIM帮助文档里一个tip
:h 12.4

7.怎么实现文本的自然排序。
比如
d
a
c
b
排成
a
b
c
d
答案::sort

8. 如何删除含有某些内容的行?
例如,想要删除含有console的行,怎么写呢?
:g/console/d

9.搜索到一行如何删除该行上面的5行?
:g/string/norm d5k
:g/搜索内容/normal d5k

10. 请问怎样每隔x行插入一个空行
答:
假设每隔5行吧,
qa4jo<Esc>jq
然后 @a就可以了
或者:
:%s/\(.*\n\)\{3}/\0\r/g
3换成你要的数字

11. 如何用vim的列操作加注释
用c-v选中了一列,输入大写I或者大写A,然后输入comment符号,然后esc

12.操作多个文件其实超级简单
o newfilename
:bn buffer Next
:bp Buffer Pervious
:bN The list number N of the buffers
:sp newfile // open a newfile with a splited window
:vs newfile // open a newfile with a vertical splited window
[ctrl] +j // down window (same with the key j)
[ctrl] +k // up window (same with the key k)
[ctrl] +h // left window (same with the key h)
[ctrl] +l // right window (same with the key l)

13.一组能让你爽出内伤的 Vim motion
用vim这么长时间,当看到下边几句,对vim的认识提高一个台阶。
ci[ 删除一对 [] 中的所有字符并进入插入模式
ci( 删除一对 () 中的所有字符并进入插入模式
ci< 删除一对 <> 中的所有字符并进入插入模式
ci{ 删除一对 {} 中的所有字符并进入插入模式
cit 删除一对 HTML/XML 的标签内部的所有字符并进入插入模式
ci" ci' ci` 删除一对引号字符 (" 或 ' 或 `) 中所有字符并进入插入模式

14. 统计关键字的个数
:%s/pattern/&/g
&代表的意思就是用来表示前面比对的字串,所以做这个指令其实对档案本身并不会有什么改变。但是由於做的是全域的取代置换,vim会告诉你有从多少行中多少个字串被取代。轻轻松松很漂亮地用一行命令解决这个问题。
输入只有一行的命令:结果就会出现在最后一行上,而且不会改变到档案本身.

15. 查找关键字
可按『*』来选取关键字。不过 vim 在这里搜寻边界是根据空白或者像「 { 」、「 (
」这些符号等来决定,所以对於中文来说,这个功能可能就不是那么好用。可以用『v』选取然后『y』复制。搜索时,在『/』后,按<
Ctrl-R >然后跟上数字键 0 ,就会出现刚刚复制进入register的字串。指令如下:
/<Ctrl-R>0<CR>
在 vim的register中,register 0是作删除或是 yank 动作时,预设使用的暂存器。
不过在前面可以发现,如果我们用「 * 」作搜寻的时候,字串的前后会被「 \< \>
」夹著。举例来说,如果你找的是 \<link\>,那么, min_links
就不会符合,同样,linknode也不会符合。这也就是说「 \<
」表示的是一个字的头,而 「 \>
」代表的是一个字的尾,当你写\<link的时候,表示你要找的字,是以link开头的,同理,link\>就是以link结尾的字,所以用两个夹起来,就代表要字串的的确确是所要的那个字。

而既然有了 「*」往下找的,就有往回找的,按键是「 # 」
,你懒得记那么多的话,就用 「*」 然后搭配 「 N 」 吧!

到这里,你可能会想,如果我不想打那么多字,可是我想要的又不要完全是一样的,怎么办?
vim 也有这样的东西,就是在你打「*」之前,先加一个 「g」,也就是
g*
这样 vim 在做搜寻的时候,就不会包含 \< \> 这两个东西在前后了。同样「 #
」的使用也可以变成「 g# 」

16. 查看符号定义
Vi提供了像Win32下IDE那样的功能,可以很方便的查看函数原型、结构声明、宏的定义等。只是它的功能相比之下,要弱一些,不过,结合其它一些技巧,完全可以满足你的需要。使用这个功能,需要做一些设置:
1.
安装ctags软件包。在安装光盘可以找到,也可以到网上下载源码包,自己编译。
2. 生成tags文件。进入到你的源代码所在的目录,运行ctags
-R命令,它会为当前目录及子目录下的源程序建立索引,并在当前目录下创建一个tags文件,里面保存的是符号索引信息。
3.
设置tags路径。在vi的起动脚本文件中(一般是~/.vimrc),告诉vi在哪里可以找到tags文件,一般尽量用相对路径。如:set
tags=./tags,../tags,../../tags,../../../tags
4. 跳到指定的符号。<ctrl> + ]
可以跳转到光标所在处的符号的符号的定义那里。
5. 返回到原来的位置。<ctrl> + t 或者<ctrl> + o可以返回原来的位置。

17. 查看系统函数的帮助
Vi也可以像VC那样,很方便的跳转到系统函数的帮助那里。原理很简单,在vi运行外部命令man就行了:!man
fopen,这样做,可能会觉得有些麻烦,vi提供了快捷的方式: <shift> +
k可以跳到光标所在处的符号的帮助那里。

18. 自动定位编译错误处
在VC里,编译时,如果出现编译错误,双击错误信息,编辑器自动切换到出现错误的地方,是不是很方便呢?其实不用羡慕,vi也有这种功能,在vi里,运行make命令后,如果有编译错误,你按一下回车,vi自动定位到第一个编译错误那里。记得,要用内置的make命令,即运行:make,不是外部命令:!make。

19.自动定位查找结果
在写程序时,我们常常想知道,有哪些地方使用了某个函数,怎么办呢,你可以在shell里,用grep查找,然后打开对应的文件,可以看到相关的上下文信息。但这样做比较麻烦,Vi有个内置的grep命令,用起来很方便:
1. 查找。用法和shell中的grep一致。
2. 跳到第一个查找结果处。直接回车就行了。
3.
列出所有的查找结果。:cl命令可以列出所有的查找结果,每个结果都有一个编号。
4. 跳到某项查找结果的文件中。:cc <编号>
命令可以跳到指定编号查找结果的文件中,<编号>是前面用:cc列出来的编号。
5. 返回到原来的位置。<ctrl> + o 可以返回原来的位置。

Makefile好助手:pkgconfig

你在Unix下开发过软件吗?写完一个程序,编译运行完全正常,在你本机上工作得好好的,你放到源代码管理系统中。然后,告诉你的同事说,你可以取下来用了。这时,你长长的出了一口气,几天的工作没有白费,多么清新的空气啊,你开始飘飘然了。

"Hi,怎么编译不过去?"你还沉浸在那种美妙的感觉之中,双臂充满着力量,似乎没有什么问题能难倒你的。正在此时,那个笨蛋已经冲着你嚷开了。

"不会吧,我这边好好的!"表面上你说得很客气,其实,你心里已经骂开了,真笨,不知道脑子干嘛用的。也许,你想的没错,上次,他犯了一个简单的错误,不是你一去就解决了吗。

他喊三次之后,你不得不放下你手上的工作,刚才那种美妙的感觉已经消失得无影无踪了,要不是你把情绪控制得很好,一肚子气就要撒在他身上了。你走到他的电脑前,键入make,优雅的按下回车。怎么可能出错呢?你信心十足。然而,屏幕上的结果多少有点让人脸红,该死的,libxxx.so怎么会让不到呢?

你在/usr目录中查找libxxx.so,一切都逃不过你的眼睛。奇怪,libxxx.so怎么在/usr/local/lib下,不是应该在/usr/lib下的吗?这你可不能怪别人,别人想安装在哪里都行,下次还可能安装到/lib目录下呢。

以上的场景并非虚构,我都经历过好几次,明明在本机上好好的,在别人的机器上连编译都过不去。可能两人的操作系统一模一样,需要的库都安装上,只是由于个人喜好不同,安装在不同的目录而已。遇到这种情况,每次都技巧性的绕过去了,用的补丁型的方法,心里老惦记其它地方能不能工作。

今天我们要介绍的pkgconfig,为解决以上问题提供了一个优美方案。从此,你再也不为此担忧了。Pkgconfig提供了下面几个功能:

1.
检查库的版本号。如果所需要的库的版本不满足要求,它会打印出错误信息,避免链接错误版本的库文件。
2. 获得编译预处理参数,如宏定义,头文件的位置。
3.
获得链接参数,如库及依赖的其它库的位置,文件名及其它一些连接参数。
4. 自动加入所依赖的其它库的设置。

这一切都自动的,库文件安装在哪里都没关系!

在使用前,我们说说pkgconfig的原理,pkgconfig并非精灵,可以凭空得到以上信息。事实上,为了让pkgconfig可以得到这些信息,要求库的提供者,提供一个.pc文件。比如gtk+-2.0的pc文件内容如下:

prefix=/usr
exec_prefix=/usr
libdir=/usr/lib
includedir=/usr/include
target=x11

gtk_binary_version=2.4.0
gtk_host=i386-redhat-linux-gnu

Name: GTK+
Description: GIMP Tool Kit (${target} target)
Version: 2.6.7
Requires: gdk-${target}-2.0 atk
Libs: -L${libdir} -lgtk-${target}-2.0
Cflags: -I${includedir}/gtk-2.0

这个文件一般放在/usr/lib/pkgconfig/或者/usr/local/lib/pkgconfig/里,当然也可以放在其它任何地方,如像X11相关的pc文件是放在/usr/X11R6/lib/pkgconfig下的。为了让pkgconfig可以找到你的pc文件,你要把pc文件所在的路径,设置在环境变量PKG_CONFIG_PATH里。

使用方法很简单,比如,我们要使用gtk+的库编译一个程序:
gcc -g arrow.c -o arrow `pkg-config "gtk+-2.0 > 2.0.0" --cflags --libs`

只要安装了gtk+2.0,不管它在哪里,编译都是正常的。这是不是简单很多了?

Linux下的调试工具

随着XP的流行,人们越来越注重软件的前期设计、后期的实现,以及贯穿于其中的测试工作,经过这个过程出来的自然是高质量的软件。甚至有人声称XP会淘汰调试器!这当然是有一定道理的,然而就目前的现实来看,这还是一种理想。在日常工作中,调试工具还是必不可少的。在Linux下,调试工具并非只有gdb,还有很多其它调试工具,它们都各有所长,侧重方面也有所不同。本文介绍几种笔者常用的调试工具:

1. mtrace
在linux下开发应用程序,用C/C++语言的居多。内存泄露和内存越界等内存错误,无疑是其中最头疼的问题之一。glibc为解决内存错误提供了两种方案:

一种是hook内存管理函数。hook内存管理函数后,你可以通过记下内存分配的历史记录,在程序终止时查看是否有内存泄露,这样就可以找出内存泄露的地方了。你也可以通过在所分配内存的首尾写入特殊的标志,在释放内存时检查该标志是否被破坏了,这样就可以达到检查内存越界问题的目的。

另外一种方法更简单,glibc已经为第一种方案提供了默认的实现,你要做的只是在特定的位置调用mtrace/muntrace两个函数,它们的函数原型如下:
#include <mcheck.h>
void mtrace(void);
void muntrace(void);
你可能会问,在哪里调这两种函数最好?这没有固定的答案,要视具体情况而定。对于小程序来说,在进入main时调用mtrace,在退出main函数时调用muntrace。对于大型软件,这样做可能会记录过多的信息,分析这些记录会比较慢,这时可以在你所怀疑代码的两端调用。

另外,还需要设置一个环境变量MALLOC_TRACE,它是一个文件名,要保证当前用户有权限创建和写入该文件。glibc的内存管理器会把内存分配的历史信息写入到MALLOC_TRACE指定的文件中。

程序运行完毕后,使用mtrace工具分析这些内存分配历史信息,可以查出内存错误的位置(mtrace在glibc-utils软件包里)。

2. strace
在编程时,检查函数的返回值是一种好习惯。对于像glibc等标准C的函数,光检查返回值是不够的,还需要检查errno的值。这样的程序往往显得冗长,不够简洁。同时也可能是出于偷懒的原因,大多数程序里并没有做这样的检查。

这样的程序,一旦出现错误,用调试器一步一步定位错误,然后想法查出错误的原因,也是可以的,不过比较麻烦,对调试器来说有些大材小用,不太可取。这时,用strace命令可能会更方便一点。它可以显示各个系统调用/信号的执行过程和结果。比如文件打开出错,一眼就看出来了,连错误的原因(errno)都知道。

3. binutil
binutil是一系列的工具,你可能根本不知道它们的存在,但是没有它们你却寸步难行。Binutil包括下列工具:
ld - the GNU linker.
as - the GNU assembler.
addr2line - Converts addresses into filenames and line numbers.
ar - A utility for creating, modifying and extracting from archives.
c++filt - Filter to demangle encoded C++ symbols.
gprof - Displays profiling information.
nlmconv - Converts object code into an NLM.
nm - Lists symbols from object files.
objcopy - Copys and translates object files.
objdump - Displays information from object files.
ranlib - Generates an index to the contents of an archive.
readelf - Displays information from any ELF format object file.
size - Lists the section sizes of an object or archive file.
strings - Lists printable strings from files.
strip - Discards symbols.
windres - A compiler for Windows resource files.
其中部分工具对调试极有帮助,如:
你可以用objdump反汇编,查看目标文件或可执行文件内部信息。
你可以用addr2line把机器地址转换到代码对应的位置。
你可以用nm查看目标文件或可执行文件中的各种符号。
你可以用gprof分析各个函数的使用情况,找出性能的瓶颈所在(这需要加编译选项)。

4. ld-linux
现在加载ELF可执行文件的工作,已经落到ld-linux.so.2头上了。你可能会问,这与有调试程序有关系吗?有的。比如,在linux中,共享库里所有非static的函数/全局变量都是export的,更糟的是C语言中没有名字空间这个概念,导致函数名极易冲突。在多个共享库中,名字冲突引起的BUG是比较难查的。这时,你可以通过设置LD_
DEBUG环境变量,来观察ld-linux.so加载可执行文件的过程,从中可以得到不少帮助信息。LD_
DEBUG的取值如下:
libs display library search paths
reloc display relocation processing
files display progress for input file
symbols display symbol table processing
bindings display information about symbol binding
versions display version dependencies
all all previous options combined
statistics display relocation statistics
unused determined unused DSOs
help display this help message and exit
5. gdb
对于真正意义的调试器来说,gdb在linux下是独一无二的。它有多种包装,有字符界面的,也有图形界面的,有单独运行的,也有集成到IDE中的。gdb功能强大,图形界面的gdb容易上手一点,但功能无疑受到了一些限制,相信大部分高手还是愿意使用字符界面的。Gdb太常用了,这里不再多说。

6. gcc/boundschecker
相信很多人用过win32下的BoundsChecker(Compuware公司)和Purify(IBM公司)两个工具吧。它们的功能实在太强大了,绝非能通过重载内存管理函数就可以做到,它们在编译时插入了自己的调试代码。

gcc也有个扩展,通过在编译时插入调试代码,来实现更强大的检查功能。当然这要求重新编译gcc,你可以到http://sourceforge.net/projects/boundschecking/
下载gcc的补丁。它的可移植性非常好,笔者曾一个ARM
平台项目里使用过,效果不错。

7. valgrind
最好的东西往往最后才见到。Valgrind是我的最爱,用习惯了,写的程序不在valgrind下跑一遍,就像没有写单元测试程序一样,有点放心不下。它有BoundsChecker/Purify的功能,而且速度更快。
有点遗憾的是valgrind目前只支持x86平台,当然,这对大多数情况已经足够了。
你可以到http://valgrind.org/ 下载最新版本。

Linux下共享库(SO)有关的几个环境变量

Linux支持共享库已经有悠久的历史了,不再是什么新概念了。大家都知道如何编译、连接以及动态加载(dlopen/dlsym/dlclose)
共享库。但是,可能很多人,甚至包括一些高手,对共享库相关的一些环境变量认识模糊。当然,不知道这些环境变量,也可以用共享库,但是,若知道它们,可能就会用得更好。下面介绍一些常用的环境变量,希望对家有所帮助:

LD_LIBRARY_PATH
这个环境变量是大家最为熟悉的,它告诉loader:在哪些目录中可以找到共享库。可以设置多个搜索目录,这些目录之间用冒号分隔开。在linux下,还提供了另外一种方式来完成同样的功能,你可以把这些目录加到/etc/ld.so.conf中,或则在/etc/ld.so.conf.d里创建一个文件,把目录加到这个文件里。当然,这是系统范围内全局有效的,而环境变量只对当前shell有效。按照惯例,除非你用上述方式指明,loader是不会在当前目录下去找共享库的,正如shell不会在当前目前找可执行文件一样。

LD_PRELOAD
这个环境变量对于程序员来说,也是特别有用的。它告诉loader:在解析函数地址时,优先使用LD_PRELOAD里指定的共享库中的函数。这为调试提供了方便,比如,对于C/C++程序来说,内存错误最难解决了。常见的做法就是重载malloc系列函数,但那样做要求重新编译程序,比较麻烦。使用LD_PRELOAD机制,就不用重新编译了,把包装函数库编译成共享库,并在LD_PRELOAD加入该共享库的名称,这些包装函数就会自动被调用了。在linux下,还提供了另外一种方式来完成同样的功能,你可以把要优先加载的共享库的文件名写在/etc/ld.so.preload里。当然,这是系统范围内全局有效的,而环境变量只对当前shell有效。

LD_ DEBUG
这个环境变量比较好玩,有时使用它,可以帮助你查找出一些共享库的疑难杂症(比如同名函数引起的问题)。同时,利用它,你也可以学到一些共享库加载过程的知识。它的参数如下:
libs display library search paths
reloc display relocation processing
files display progress for input file
symbols display symbol table processing
bindings display information about symbol binding
versions display version dependencies
all all previous options combined
statistics display relocation statistics
unused determined unused DSOs
help display this help message and exit
BIND_NOW
这个环境变量与dlopen中的flag的意义是一致,只是dlopen中的flag适用于显示加载的情况,而BIND_NOW/BIND_NOT适用于隐式加载。

LD_PROFILE/LD_PROFILE_OUTPUT:为指定的共享库产生profile数据,LD_PROFILE指定共享库的名称,LD_PROFILE_OUTPUT指定输出profile文件的位置,是一个目录,且必须存在,默认的目录为/var/tmp/或/var/profile。通过profile数据,你可以得到一些该共享库中函数的使用统计信息。
Linux下共享库(SO)有关的几个环境变量收藏
Linux支持共享库已经有悠久的历史了,不再是什么新概念了。大家都知道如何编译、连接以及动态加载(dlopen/dlsym/dlclose)
共享库。但是,可能很多人,甚至包括一些高手,对共享库相关的一些环境变量认识模糊。当然,不知道这些环境变量,也可以用共享库,但是,若知道它们,可能就会用得更好。下面介绍一些常用的环境变量,希望对家有所帮助:

LD_LIBRARY_PATH
这个环境变量是大家最为熟悉的,它告诉loader:在哪些目录中可以找到共享库。可以设置多个搜索目录,这些目录之间用冒号分隔开。在linux下,还提供了另外一种方式来完成同样的功能,你可以把这些目录加到/etc/ld.so.conf中,或则在/etc/ld.so.conf.d里创建一个文件,把目录加到这个文件里。当然,这是系统范围内全局有效的,而环境变量只对当前shell有效。按照惯例,除非你用上述方式指明,loader是不会在当前目录下去找共享库的,正如shell不会在当前目前找可执行文件一样。

LD_PRELOAD
这个环境变量对于程序员来说,也是特别有用的。它告诉loader:在解析函数地址时,优先使用LD_PRELOAD里指定的共享库中的函数。这为调试提供了方便,比如,对于C/C++程序来说,内存错误最难解决了。常见的做法就是重载malloc系列函数,但那样做要求重新编译程序,比较麻烦。使用LD_PRELOAD机制,就不用重新编译了,把包装函数库编译成共享库,并在LD_PRELOAD加入该共享库的名称,这些包装函数就会自动被调用了。在linux下,还提供了另外一种方式来完成同样的功能,你可以把要优先加载的共享库的文件名写在/etc/ld.so.preload里。当然,这是系统范围内全局有效的,而环境变量只对当前shell有效。

LD_ DEBUG
这个环境变量比较好玩,有时使用它,可以帮助你查找出一些共享库的疑难杂症(比如同名函数引起的问题)。同时,利用它,你也可以学到一些共享库加载过程的知识。它的参数如下:
libs display library search paths
reloc display relocation processing
files display progress for input file
symbols display symbol table processing
bindings display information about symbol binding
versions display version dependencies
all all previous options combined
statistics display relocation statistics
unused determined unused DSOs
help display this help message and exit
BIND_NOW
这个环境变量与dlopen中的flag的意义是一致,只是dlopen中的flag适用于显示加载的情况,而BIND_NOW/BIND_NOT适用于隐式加载。

LD_PROFILE/LD_PROFILE_OUTPUT:为指定的共享库产生profile数据,LD_PROFILE指定共享库的名称,LD_PROFILE_OUTPUT指定输出profile文件的位置,是一个目录,且必须存在,默认的目录为/var/tmp/或/var/profile。通过profile数据,你可以得到一些该共享库中函数的使用统计信息。

Linux下文件关联的实现原理

From:http://blog.csdn.net/absurd/

我们知道在Windows下,双击一个可执行文件,文件管理器会自动运行这个应用程序。而双击一个数据文件时,文件管理器会用与之关联的应用程序打开它。数据文件与应用程序之间的关联是通过注册表来实现的:文件管理器查询注册表,找到数据文件对应的应用程序,然后运行这个应用程序,并把数据文件的文件名作为命令行参数传给它。

这种文件关联的方式非常好用,省去了先起动应用程序再打开文件的麻烦。Linux下的桌面环境也有类似的功能,而且实现方式更合理。最近负责开发一个资源管理器,本来GNOME有一个功能强大的资源管理器Nautilus,只是它过于庞大,不但有超过10万行的代码,还依赖于libgnome、gnome-vfs和CORBA等,故不适合于嵌入式应用。最终我们决定自己开发一个简化的资源管理器,但又要尽量兼容现有的应用程序,这要了解相关标准,文件关联方式是其中之一。把这几天学到的知识做个笔记吧,供有兴趣的朋友参考:

首先让我们看看文件关联要做些什么。
1.
数据文件与应用程序的关联。一个应用程序通常只能打开一些特定的数据文件,比如图片浏览工具可以打开PNG、BMP和JPEG等图片文件。打开一词的意义比较宽泛,这里包括:打开、播放、安装、编辑和打印等等。

2.
文件类型信息。资源管理器把数据文件列出来时,通常会用一个图标来标识这类文件,同时也会加上一个简短的名称,以便用户可以很容易把它与其它类型的文件区分开来。

下面我们看看linux下是如何实现的。
1.
判断文件类型。文件的数量是无限的,我们只能按文件类型来处理。如何判断一个文件所属的文件类型呢?可能有人会说,很简单,用扩展名区分就行了。没错,用扩展名可以做到,但这种方法有两个缺陷:一方面它不是很精确,相同扩展名的文件的类型可能完全不同,比如dat文件,可能是一个视频文件,也可能是一个普通数据文件。另一方面它不是很准确,扩展名可以任何改动,为了某种目的,完全可以把exe扩展名改为htm扩展名。

而且在Linux下扩展名只是一个可选项,很多文件根本没有扩展名,所以纯粹采用文件扩展名的方式来判断肯定是不行的。为了更好的判断文件类型,在linux下同时采用两种方式:优先采用magic方式,其次才采用文件扩展名方式。所谓magic方式,就是根据文件内容来判断。绝大多数文件,内部都有一些特定的标记,这些标记称为magic,比如BMP图片文件以BM两个字符开头,BM就是一个magic。虽然即使采用了双保险机制也有误判的可能,但概率已经大大降低了。

2. 文件类型的表示。
文件类型如何表示呢?我们说JPEG是图片文件,说txt是文本文件,WML是XML文件。这种分类很直观,但也有几个问题:对JPEG文件来说,称它图片文件太笼统了。有的图片浏览工具虽然能够打开大部分图片文件,但不一定能打开所有图片文件,它需要更详细的文件类型信息。对txt和WML来说,它们其实都是文本文件,有的编辑器可能以同样的方式处理它们。为了避免分类太细或者太粗,linux采用了MIME(可以参考相关RFC)规范,它用一种层次型的方式来分类,如:
JPEG文件:image/jpeg
文本文件:text/plain
XML文件:text/xml
这种分类方式就可以粗细兼顾了。

3. 文件类型的数据信息。
在linux下,关于文件类型的信息通常放在/usr/share/mime、/usr/local/share/mime和用户目录下,所有应用程序可以共享这些信息。在该目录下,一般会有以下这些文件:
l aliases:文件类型的别名。比如application/pdf
有时也称为application/x-pdf 。
l
magic:各种文件的内部标识,用于从文件内容来判断文件类型。如BMP图片文件以BM开头。
l
globs:扩展名与文件类型的对应关系。如*.cpp文件是text/x-c++src类型的。
l packages目录:用于安装新文件类型用。
l
其它子目录及其下的文件:更详细的描述各种文件类型。比如image下的jpeg.xml文件描述了jpeg文件类型。为了方便国际化,这些描述信息有各种语言版本。

4. 图标文件与数据文件的关联。
在资源管理器中,通常用不同的图标来区分不同的文件类型。同时图标也是桌面主题相关的,主题不同,图标的大小和外观也不一样。图标文件通常存放在/usr/share/icons/主题/大小/mimetypes目录下。

文件类型与图标文件的对应关系是通过文件名来实现的。比如,JPEG文件对应的图标文件为gnome-mime-image-jpeg.png。
(这块不是很确定,有待进一步研究)

5. 应用程序与数据文件的关联。
应用程序与数据文件的关联是通过.desktop文件来实现的。应用程序要出现在开始菜单中或者桌面上,它要提供一个desktop文件才行。应用程序安装之后,desktop文件通常安装到/usr/share/applications下。

可以在desktop文件中,指明其可以操作的文件类型。如,软件包安装程序可以操作rpm文件,它的desktop文件(system-install-packages.desktop)内容为:
[Desktop Entry]
Name=Install Packages
GenericName=Install Packages
Comment=Install new packages on the system
MimeType=application/x-rpm;
Exec=/usr/bin/system-install-packages %F
Terminal=false
Type=Application
Icon=system-config-packages.png
Encoding=UTF-8
NoDisplay=true

MimeType项指明它可以操作rpm类型的文件。

Linux比windows的做法科学之处。
1. Linux采用了双保险机制,对文件类型的判断更正确,出错的概率更小。

2.
Linux分离文件类型判断信息和文件关联方式,这样文件类型信息可以被重用。比如file命令可以用这些信息来判断文件类型,而不必打开它。

参考资料:
http://standards.freedesktop.org/shared-mime-info-spec/shared-mime-info-spec-0.13.html
http://www.freedesktop.org/wiki/Standards_2fAddingMIMETutor

这四年来(以前写的,有点乱)

原帖:http://blog.csdn.net/absurd/archive/2006/02/17/601586.aspx

(刚到深圳时写的)
转眼就毕业快四年了,在一研究所呆了半年,在北京工作了三年,在深圳流浪了近三个月。
  
呆在研究所时,同从武汉过去毕业生共九个,无一不是感觉上当了。他们大多数人,要么一起谈论离职,与单位谈条件,要么就是怨天尤人。而我却只能把失望压在心里,老爸是中建七局公司的工人,凭着老爸高超的手艺和勤奋的工作,家里还算过得殷实。然而在我高考后几天,他为救一个同事,自己从三楼摔到二楼,腰部受伤,被迫病退了,加上弟弟同时上学,家里日趋困难。
  
去上班的路费还是向表哥借的,我还得等第一个月工资,以资助弟弟。再说我主修的是机电,辅修的计算机应用,没有工作经验,重新找份工作也很困难,下几个月的生活更无着落。
  
这时,女友打电话来说,大家工作在两地,生活在一起是不太可能的,你现在一个月几百块钱,自己生活都困难,还要资助你弟弟,你即使想来看我一次都不容易,还是分了吧。我握着电话,没有出声,任眼泪静静的流下。最后说了一句,等我一年,好吗?我发誓,一年后,月薪会超过三千的。她犹豫了一会儿,说,大家年龄都大了,家里人也着急,还是不用了吧。
  
性格本来就内行,我更加远离人群了,在宿舍里拼命的学英语,在办公室拼命的学编程。内心股强大的力量,不断的告诉我,一定要努力,一定离开这里。从不参与同事的抗议活动,也不参与他们的抱怨研讨会,只是默默的学习。有同事问我,不想离开吗?我笑着说,单位不倒,我是不走的,其实我比谁都想走。
  
渐渐的,凭着以前的数据库功底和PB的知识,不但可以轻松的应付工作,还可以指导同事。上司也是个爱学习的人,比我大两届,他见我如此爱学习,就让我专心的研究新技术,而不工作了。当时所谓的新技术无非是加密、反拷贝之类的。值得一提的是,他让我还要学习COM原理和OpenGL等知识,对此,他也仅懂一点,而我以前都没有听说过,他只是告诉我说,很有用的,同时给我买了十多本书(至今对他极为感谢,当时我根本没钱买书)。
  
现在想来真是搞笑,由于是全新的东西,很简单的概念都要想半天,翻译的也很烂,学习了半个月,还不清楚类工厂是做什么用的。直到看潘爱民老师的《COM原理》,才真正搞懂了。
  
终于到年底了,想到拿到那一千块的年终奖,就可以跑路了,不禁有些激动。为了谨慎起见,我装出一副平静的样子,只有在走了那一天,同室的同事才知道。
  
去北京之前,联系了在北航读研的好友老胡,同时发了五六封求职信。那时恰逢北京最冷的时候,零下十六度,又下很大的雪,只好花了三百块去买了羽绒服,又花了二百块去买了双鞋,还花了一百块买了个钱包,钱就用得差不多了,还好另外一个同学资助了一千块。
  
去了四家公司面试,都通过了。之所以后来选择那家公司,一是因待遇比较高,另一方面,在我发过简后,不到半小时就收到了他们的回复,真让我感动。老板让我去拿offer时,说,给你三天时间考虑,没问题的话,二月一号来上班,我当时兴奋的说,不用考虑了,一定来。
  
开始为了让我熟悉项目,让我写Linux平台的测试程序。旁边的同事,特别好,不厌其烦给我讲解(很想念他的,不知他现在过得好不好)。上司低估了我的能力,几个任务,我都只用比预期时间少得多的时间完成了,上司由此对我刮目相看。我完成了一套测试程序框架,工作量一下缩小到原来的五分之一,RD的人都无不惊异。
  
记得,给RD一个Linux高手讲了我的测试程序框架后,他看着我了老半天,才说,你太聪明了。我笑了笑了,说,惭愧,刚刚接触C++。其实心里高兴得不行了。
  
然而,后来的工作,却不是我想要的,前人留下的代码,写得也很差,本想成为编程高手的我,结果成了一个调试高手。工资也在长,职位也在升,然而我却日趋倦怠。感到在工作中学不到什么东西,有时你费尽心思找到一个严重的错误,不过是个简单的内存溢出,或者文件关闭后继续在使用。在上百万行代码中,而这种错误潜在的,不知有多少。我建议大家学习代码重构,设计模式,由于诸多原因无法实行。
  
真的累了,感觉就是在原地不断的奔跑,累了,然而还在原地。渐渐的萌生退意。后来公司组织一次活动去爬山,一件事让我很郁闷。
  
我一向很守时,说好早上九点半集合,我准时到了,而此时到的人不到五分之一,等到十点多,老板才过来。上山前,说好下午五点半集合,我计划好时间,估计五点可以下来,就和绿野的几个同伴一起走了,而最终五点十分回到集合地点,发现他们等了我一个小时间了,原来,他们只在半山腰转了一下,就回来了。晚上被老板训了一顿,说因为我而耽搁大家一个小时。真是郁闷极了,守时反而挨训,不守时反有理,真是天理何在。
  
每天在上地三街和七街间往返,早上在五街时,看见那班飞机从头顶飞过,在六街遇到几个中东的老外,还有一个总戴着红眼镜的女孩子。听着云飞的幽默集装箱或者CRI的Music。时间就这样溜走了。
  
和女友分手后,渐渐的我对周围的事充耳不闻,除了工作学习,个人外出旅游,偶尔和喜欢的网友聚一下。在公司里,我的脾气还是那样好,大家都喜欢和我来往,然而仅仅限于工作上的来往罢了。记得当时QA有个女孩,真的很喜欢她,看得出她对我也好感。然而另一个同事捷先登了,结果那女孩被吓跑了,一直很后悔没有早点行动。
  
年前给老妈打电话,老妈问起那个老问题,找对象没有。我说,没有。她说,二十六了,你小学同学的儿子都上小学了,该想想了。二十六了,我真的不敢面对时间流逝,年龄的增长。
  
二十六了,我还是一无所成!生活还是那样单调!我决定走了,决定去南方,我拒绝了亲友的忠告,拒绝了老板的加薪和升职,拒绝了同事的挽留(美国那边两个同事,一再劝我不要走,最后又劝我在外不好,就再回来)。
  
到了深圳,见到好友时,他第一句话就是,你还是像刚毕业时那样年轻。我苦涩的笑了笑,其实内心感觉好老了。他看了看我在背上的小包,说,就这么点东西,你是来玩的吧?我说就这点行李。他又说有钱就行。我又笑了,我存折上总共还不到八千块钱。
  
自信自己的能力,不在任何同龄人之下,尽管这几年IT不景气,还是相信可以找到一份新的工作。然而发出的数十信简历竟如沉大海。
  
我有些沮丧,这时一个好友拉我入伙,和他们一起做嵌入式系统,他们几个清华毕业的校友,软硬都很厉害。我答应做兼职,尽管没做过,凭着在学校时基础,很快上手了,开发出一些网络组件和图形组件。
  
渐渐的,觉得一个人在家里做,还是不如到公司做有感觉。这时另一个好友,介绍我去华为。记得,在面试那天,旁边一个小孩和我说话,他说,你也是去年毕业的吧,我笑了笑说,2000年毕业的。他惊讶了半天,说,不会吧,看起来这么年轻,怎么会那么老呢?呵,我只好苦笑。
  
面试很顺利,得到各方面的评价都比较高,不久就收到华为的offer了。一直羡慕华为的规范,所以铁了心要去了。然而,在腾讯的好友又介绍我去他们公司,说腾讯的待遇比华为高得多。今天又接到步步高和恒伟业面试的通知。我想凭自己的能力,都可以通过吧。
  
真是有些郁闷,想找工作时,竟没有人理我,找到工作时,又有很多选择了。我的性格内向,向管理方面发展,空间很小,做技术吧,空间本就不大,和朋友创业吧,自己没有创业头脑,也只能跟着他们混。

何去何从,真是有让人为难。

如何面对单调重复的任务

转载时请注明出处:http://blog.csdn.net/absurd/

我们每个人都喜欢做有挑战的,能学到新东西的任务,而不愿意去那些单调重复的,没有什么新意的事情。然而常常事与愿违,在软件开发中,前者并非主流,而后者占了大多数。前者未必每次都能轮到你,而后者也总是要人完成的。

面对后者,你可以选择拒绝接受任务,但那会让人觉得你工作态度不好,以后好的差事可能就轮流不到你了;你可以选择走人,换个地方去做,但那也只能祷告,祈求上帝保佑你在别的地方遇到好任务。其实这都不是好的做法,相反我们应该接受并搞掂它,应该想法转换它们,完成任务但又不必自虐,从中还能学到东西。下面是我的一些经验:

1.
让电脑去做单调重复的工作。Unix文化有一个原则:宁愿花机器一分,不花程序员一秒。单调重复的工作多数都是有规律可循的,有规律可循就可以让电脑来做。

实例一:在早些年代,那时还没有听说autobuild这个概念,发布版本是一件痛苦事情。你要从CVS上取出源代码,编译各个版本((英文版,
日文版) x(大企业版, 小企业版,
试用版)),再制定各个版本的安装包,最后上传到FTP服务器上。如果整个过程顺利,四个小时差不多了,但事实是从来没有顺利过,结果通常要花费两三天才能完成。编译出错,手工拷贝文件出错,上传时放错目录如此等等。那位负责做版本的大姐还算有耐心,坚持做了大半年时间,后来这事没费多大劲就推到我头上了(呵,大家都认为我好欺负)。我接手后,第一件事是花了两天用bash写了个脚本,把整个过程自动化了,在接下来一个版本中进行了验证,并修正几个脚本里的问题,后来发布版本时几乎不用人干预了。

实例二:如果有人问我写得最多的程序是什么。我一定会回答是代码产生器,前前后后、大大小小至少写过十几个代码产生器,小的可能是用bash+awk来做的,也就上百行代码,大的用C/C++来写,动则数千行代码,最大的竟达8000多行C++代码。大部分代码产生器都为我节省了不少时间,或者至少把单调重复的事情变得有趣一点了,而且得到的代码也更稳定可靠。

2.
换种思路,看有没有捷径。有些事情单调重复,本来也是可以让电脑去做的,但是开发相应的工具要费更多的时间,得不偿失,这时不换一种思路,或许别有洞天。

实例一:曾接到一个任务,要求找出一个公共函数库里的所有全局变量。那个库是个大杂烩,里面什么东西都有,凌乱而庞大。时间期限是一周,时间比较充足,即使一个文件一个文件的去找,时间也来得及,但那太痛苦了。更麻烦的是这个库是变化的,可能刚刚完成任务,又有人加了一个全局变量,这样就很难拿到一个最新的结果。怎么呢,我想,编译器肯定是知道哪些是全局变量的,所以第一反应是拿一个开源的编译器修改一下,让编译器告诉我结果。修改编译器可能也要一周时间,但利用它随时可以得到最新结果。有没有更简便的办法呢?猜想VC输出的map文件或许有些帮助,打开map文件一看,果然如此。让VC编译该库并输出map文件,取出全局变量列表,搞掂了。从接受任务到完成任务前后不到一个小时。

实例二:前几天,同事拿一个第三方库,编译时发现那个库是用带硬件浮点数的toolchain编译,而我们的toolchain用的是软浮点数。尽管反汇编出来,没有发现浮点指令,但编译器就是不让链接该库。我们的方案是,反汇编它再重新编译它。但反汇编出来的格式与as的输入格式有些差异,要花不少时间去修改,修改之后发现还是编译不过去。最后,这个任务又落到我头上了。我想既然没有用浮点指令,编译器不让编译可能是因为一个标志引起的,于是花了点时间去研究ELF(linux下的可执行文件格式)文件格式。果然是文件头中一个标志引起的,写了个小程序为该库加上这个标志位,编译就通过了。

3.
换种心情,坦然接受。如果面对一项任务,你别无选择时,那就坦然接受它吧。然后想法说服自己,让自己有个好的心情,这样的任务总是要有人做才行。还可以告诉自己,一定要从中学到点东西,即使从中学不到技术,也要从中学会忍耐。

当我从QA组进入RD组时,我任务是把Win32程序移植到linux下。这个任务比较重要,但绝不是什么好任务,工作本身单调不说,别人还瞧不起。他们认为这都是很简单的,不用动脑子的体力活。同组的同事很多都走了,新来的同事也呆不了多久。但我坚持下去了,移植的同时去研究那些代码,去研究Win32和linux在编程上的差异,
一年之后我成了少数几个了解整个系统架构的人,编程能力大有提高,对软件的可移植性也有了较深的理解。

~~end~~
如何面对单调重复的任务

转载时请注明出处:http://blog.csdn.net/absurd/

我们每个人都喜欢做有挑战的,能学到新东西的任务,而不愿意去那些单调重复的,没有什么新意的事情。然而常常事与愿违,在软件开发中,前者并非主流,而后者占了大多数。前者未必每次都能轮到你,而后者也总是要人完成的。

面对后者,你可以选择拒绝接受任务,但那会让人觉得你工作态度不好,以后好的差事可能就轮流不到你了;你可以选择走人,换个地方去做,但那也只能祷告,祈求上帝保佑你在别的地方遇到好任务。其实这都不是好的做法,相反我们应该接受并搞掂它,应该想法转换它们,完成任务但又不必自虐,从中还能学到东西。下面是我的一些经验:

1.
让电脑去做单调重复的工作。Unix文化有一个原则:宁愿花机器一分,不花程序员一秒。单调重复的工作多数都是有规律可循的,有规律可循就可以让电脑来做。

实例一:在早些年代,那时还没有听说autobuild这个概念,发布版本是一件痛苦事情。你要从CVS上取出源代码,编译各个版本((英文版,
日文版) x(大企业版, 小企业版,
试用版)),再制定各个版本的安装包,最后上传到FTP服务器上。如果整个过程顺利,四个小时差不多了,但事实是从来没有顺利过,结果通常要花费两三天才能完成。编译出错,手工拷贝文件出错,上传时放错目录如此等等。那位负责做版本的大姐还算有耐心,坚持做了大半年时间,后来这事没费多大劲就推到我头上了(呵,大家都认为我好欺负)。我接手后,第一件事是花了两天用bash写了个脚本,把整个过程自动化了,在接下来一个版本中进行了验证,并修正几个脚本里的问题,后来发布版本时几乎不用人干预了。

实例二:如果有人问我写得最多的程序是什么。我一定会回答是代码产生器,前前后后、大大小小至少写过十几个代码产生器,小的可能是用bash+awk来做的,也就上百行代码,大的用C/C++来写,动则数千行代码,最大的竟达8000多行C++代码。大部分代码产生器都为我节省了不少时间,或者至少把单调重复的事情变得有趣一点了,而且得到的代码也更稳定可靠。

2.
换种思路,看有没有捷径。有些事情单调重复,本来也是可以让电脑去做的,但是开发相应的工具要费更多的时间,得不偿失,这时不换一种思路,或许别有洞天。

实例一:曾接到一个任务,要求找出一个公共函数库里的所有全局变量。那个库是个大杂烩,里面什么东西都有,凌乱而庞大。时间期限是一周,时间比较充足,即使一个文件一个文件的去找,时间也来得及,但那太痛苦了。更麻烦的是这个库是变化的,可能刚刚完成任务,又有人加了一个全局变量,这样就很难拿到一个最新的结果。怎么呢,我想,编译器肯定是知道哪些是全局变量的,所以第一反应是拿一个开源的编译器修改一下,让编译器告诉我结果。修改编译器可能也要一周时间,但利用它随时可以得到最新结果。有没有更简便的办法呢?猜想VC输出的map文件或许有些帮助,打开map文件一看,果然如此。让VC编译该库并输出map文件,取出全局变量列表,搞掂了。从接受任务到完成任务前后不到一个小时。

实例二:前几天,同事拿一个第三方库,编译时发现那个库是用带硬件浮点数的toolchain编译,而我们的toolchain用的是软浮点数。尽管反汇编出来,没有发现浮点指令,但编译器就是不让链接该库。我们的方案是,反汇编它再重新编译它。但反汇编出来的格式与as的输入格式有些差异,要花不少时间去修改,修改之后发现还是编译不过去。最后,这个任务又落到我头上了。我想既然没有用浮点指令,编译器不让编译可能是因为一个标志引起的,于是花了点时间去研究ELF(linux下的可执行文件格式)文件格式。果然是文件头中一个标志引起的,写了个小程序为该库加上这个标志位,编译就通过了。

3.
换种心情,坦然接受。如果面对一项任务,你别无选择时,那就坦然接受它吧。然后想法说服自己,让自己有个好的心情,这样的任务总是要有人做才行。还可以告诉自己,一定要从中学到点东西,即使从中学不到技术,也要从中学会忍耐。

当我从QA组进入RD组时,我任务是把Win32程序移植到linux下。这个任务比较重要,但绝不是什么好任务,工作本身单调不说,别人还瞧不起。他们认为这都是很简单的,不用动脑子的体力活。同组的同事很多都走了,新来的同事也呆不了多久。但我坚持下去了,移植的同时去研究那些代码,去研究Win32和linux在编程上的差异,
一年之后我成了少数几个了解整个系统架构的人,编程能力大有提高,对软件的可移植性也有了较深的理解。

~~end~~

三种Cache写入方式原理简介

  
在386以上档次的微机中,为了提高系统效率,普遍采用Cache(高速缓冲存储器),现在的系统甚至可以拥有多级Cache。Cache实际上是位于CPU与DRAM主存储器之间少量超高速的静态存储器(SRAM),通常的大小为8KB~512KB。

  
对Cache的工作原理可以进行如下描述:具有Cache的计算机,当CPU需要进行存储器存取时,首先检查所需数据是否在Cache中。如果存在,则可以直接存取其中的数据而不必插入任何等待状态,这是最佳情况,称为高速命中。当CPU所需信息不在Cache中时,则需切换存取主储器,由于速度较慢,需要插入等待,这种情况称高速未命中。在CPU存取主存储器的时候,按照最优化原则将存储信息同时写入到Cache中以保证下次可能的高速命中。因此,同一数据可能同时存储在主存储器和Cache中;同样,按照优化算法,可以淘汰Cache中的一些不常使用的数据。

  所以,提高高速命中率的最好方法是尽量使Cache存放CPU最近一直在使用的指令与数据,当
Cache 装满后,可将相对长期不用的数据删除,提高 Cache 的使用效率。为保持
Cache
中数据与主存储器中数据的一致性,避免CPU在读写过程中,将Cache中的新数据遗失,造成错误的读数据,确保Cache
中更新过程的数据不会因覆盖而消失,必须将 Cache
中的数据更新及时准确地反映到主存储器中,这是一个写入过程,通常采用的处理方法有:直写式、缓冲直写式与回写式三种。

  1.直写式系统:
CPU对Cache写入时,将数据同时写入到主存储器中,这样可保证Cache中的内容与主存储器的内容完全一致。这种方式比较直观,而且简单、可靠,但由于每次对Cache更新时都要对主存储器进行写操作,而这必须通过系统总线来完成,因此总线工作频繁,系统运行速度就会受到影响。

  2.缓冲直写式系统:为解决直写式系统对总线速度的影响问题,在主存储器的数据写入时增加缓冲器区。当要写入主存储器的数据被缓冲器锁存后,CPU
便可执行下一个周期的操作,不必等待数据写入主存储器 。
这相对于给主存储器增加了一个单向单次高速缓存。比如,在写入周期之后可以紧接着一个数据已存在于Cache中的读取周期,这样就可避免直写式系统造成的操作延时。但这个缓冲器只能存储一次写入的数据,当连续两次写操作发生时,CPU仍需等待。

  3.回写式系统:以前的两种写入方式系统,都是在写Cache的同时,对主存储器进行写操作。实际上这不仅是对总线带宽的占用,浪费了宝贵的执行时间,而且对于有的情况是不必要的,可以通过增加额外的标准来判断是否有必要更新数据。回写式系统就是通过在Cache中的每一数据块的标志字段中加入一更新位,解决主存储器不必要的写操作。比如,若Cache中的数据曾被CPU更新过但还未同时更新主存储器,则该更新位被置1。每次CPU将一块新内容写入Cache时,首先,检查Cache中该数据块的更新位,若更新位为0,则将数据直接写入Cache;反之,若更新位为1,则先将
Cache 中的该项内容写入到主存储器中相应的位置,再将新数据写回到Cache中。

  与直写式系统相比,回写式系统可省下一些不必要的立即回写操作,而在许多情况下这是很频繁出现的。即使一个Cache被更新,若未被新的数据所取代,则没有必要立刻进行主存储器的写操作。也就是说,实际写入主存储器的次数,可能少于CPU实际所执行的写入周期的次数,但回写式系统的结构较复杂,Cache也必须用额外的容量用来存储标志。

  由于回写系统的高效率,现代的Cache大多采取这种方式进行操作。

PC架构之CPU/RAM/IO总线的发展历史

1. 从 IBM PC XT 架构开始...

一开始PC的设计中,CPU/RAM/IO都是被一条总线(BUS)连接起来,所有的部件都必须在同步的模式下面工作,由CPU来决定的其他设备工作在什么频率(Frequency)上。这样就带来一个"互锁"(locked to each other)效应,即大家都被限定在一个被所有设备所能承受的通用时钟频率(Clock Frequency)上面,系统的整体性能不高。

2. 总线的第一次切分

1987年,康柏(Compaq)公司想到一个办法,将系统总线与I/O总线切分开来,可以使得2个不同的总线工作在不同的时钟频率上面。CPU和内存仍旧工作在自己的公用总线上(the System Bus),独立于所有的I/O设备,可以使得高速的CPU/RAM组件摆脱低速I/O设备的束缚。

这里的Bridge,就是我们现在所说的南桥(South Bridge)芯片的前身,而且此处可以看到Bridge实际起到了一个降频的作用(类似AMD K8 CPU的分频机制)。

3. CPU倍频的出现

从80486开始,CPU的发展迅猛,频率大幅攀升,内存开始变得跟不上CPU的发展步伐了!Intel 于是决定在80486中引入倍频(Clock Doubler)的概念!内存依旧工作在系统总线上,与系统总线保持同样的工作频率,而CPU实际的内部工作频率(就是我们常说的CPU主频)是:

CPU 主频 = 外频(系统总线频率/System Bus Frequency)* 倍频 (Clock doubler)
CPU 主频 = 33 MHz * 2 = 66 MHz

用Intel P4 2.8C为例子则是:200 MHz * 14 = 2800 MHz = 2.8 GHz

4. 北桥芯片/前端总线的出现


从前面几点可以看到,PC结构变化的趋势是把速率慢的设备与速率快的设备用切割总线的方式,进行隔离。而这发展到后来,就终于演变出来了北桥(North Bridge)芯片!内存与北桥间的总线称为内存总线,把CPU与北桥间连接的这段总线成为前端总线(Front Side Bus,FSB),也就是系统总线(System Bus)!

得益于Intel的QDR (Quad Data Rate)技术,Pentium 4的CPU可以在每个时钟周期传输4次数据,所以当FSB的工作频率在200MHz的时候,FSB的等效频率为200MHz * 4 = 800 MHz !

注:常说的FSB 800,实际是FSB的等效频率,并不是FSB实际的工作频率!