Category Archives: 0和1

找到BUG是好事

  找到BUG就好象发现孩子得病,你提醒他家人一样,人家只会感激你。

  1. 软件作者和你一样,都是追求完美注重细节的人,都希望自己的作品不断改进。

  2. 老板不会用BUG绝对数量衡量工作成绩。开发的代码越多,越重要,发现的BUG机会自然就越多,一行代码不写就没BUG了,没人用的代码也找不出BUG。

  3. 修正BUG会增加作者的工作量,这没那么郁闷,可以往后排进度或找人帮忙;就算实在没时间,至少可以明确“这里不完美”。

  4. 也许这不是BUG?没问题,你不会丢面子或者让别人感觉爱找麻烦,BUG等级里本来就设置了“可疑”,提出问题,说明你认真对待这个软件,我很高兴可以多交流。

  5. 真的很感谢你关心我的工作成果,这太好了,我宁愿和挑剔但负责任的客户打交道,也不愿意碰上没有进取心的家伙。请参考第1条。

  我就是负责整个系统的工程师,最终梦想是实现世界上最好的软件。我很乐意改进自己的代码,也很乐意帮同伴改进他的模块,但首先,请帮个忙,告诉我BUG在哪。

用template模拟虚函数多态性

  以前写过关于对象的多态技术的讨论,提到传统的C++虚函数的做法,像对话框这样的接口,会产生一个巨大的虚函数表,调用时,实际都要执行两步:先通过虚函数表找到函数指针,然后再运行函数。

  那么C++有什么其他解决方法呢?可以借助模板技术。

  首先,Framework实现一个template作为接口:

template<class T>
class FrameworkBase1
{
public:
//调用接口
void HelloWorld(void)
{
static_cast<T *>(this)->SayHello();
};
//实际实现,这里提供一个默认版本,也可以不提供,类似纯虚基类
void SayHello(void)
{
cout<<“Hello World! This is FrameworkBase1n”;
};
protected:
//下面都是扮演Framework调用HelloWorld接口
void call1(void)
{
HelloWorld();
}
void call2(void)
{
HelloWorld();
}
};

  当用户打算实现这个接口,提供给Framework回调的时候,就这样:

class Derived1 : public FrameworkBase1<Derived1>
{
public:
//重新实现
void SayHello(void)
{
cout<<“Hello World! This is Derived1n”;
};
};

  当然,也可以采用Base1类的默认动作:

class Derived2 : public FrameworkBase1<Derived2>
{
//啥也不做,直接采用FrameworkBase1默认的实现
};

  Alan Kay评价:“……所有喜欢和擅长C++的人都必须是严肃认真的铁人,有足够能力在事前规划一切,如果不适应这种风格,大量细节会让你疯狂……”

开始看PoEAA了

  去年夏天在书店里对着PoEAA琢磨了一下午,觉得消化不了,最终没买。昨天终于买回来开始看了。大概是因为做了97的项目,在luli搭建的框架下模仿,积累了一些经验,这次读起来就能找到门了。

  设计模式的书都像《红楼梦》一样,自己没有经历,看也白看。Gof的那本,本科第一次翻,像遇到天书一样。大四又看,对工厂模式产生共鸣,高兴得手舞足蹈。后来又经历了一个四处套用的阶段,写了不少蹩脚的尝试。就在自以为已经有谱的时候,有次在实验室讲座,翻到单例模式的章节(我以为这是最容易理解的模式之一),才发现精彩的后半部分以前被我忽略了……薄薄的一本,这么多年下来还是没有完全吃透。

  我承认,啃PoEAA就是为了赶潮流,用C++开发科学算法软件不一定用得上。嘿嘿,像“Domain Model”和“Unit of Work”这样的词在圈子里逐渐成了常识概念,和朋友聊天经常遇到,不好意思总让别人给我画UML图解释。

  模式就是招数。面向对象语言提供了一大堆兵器,比如封装、继承、重载、虚函数等等。遇到某些常见问题,怎么组织这些语言特点,设计系统的构架呢?高手把经验提炼出来,就形成设计模式,我们这些笨人 只要照着《九阴真经》出招就好了。当然,别指望三天就变成高手,太着急会走火入魔的,编程基础是修炼的第一必要条件,如果连虚函数基本原理都不明白,怎么 指望掌握工厂模式呢,只有不断实践才能体会经典的奥妙。

XPCOM和平台化

  XPCOM机制基本上是微软COM的翻版,提供业务层C++功能模块的组件模型;XUL文本用于描述用户界面;而XULRunner,一个类似JVM的底层平台,把两者结合起来。

  很不错的Rich Client技术。Firefox、Chatzilla和Thunderbird都基于这种体系结构。

  无论EclipseGeronimo还是FireFox,内核都很精练,思路不限于某种具体的功能,而是重点考虑体系结构的灵活性和拓展性,规范和简化组件的设计,从而吸引hacker开发各种出色的拓展插件。这种“平台化”思路很适合开源项目。

软件工程相关转载三则

  Ai92翻译的这个版本的Martin Fowler的《设计已死?》很不错。其中总结的XP设计技巧是:

  

  • 持续保持代码干净、简单。
  • 重构,使你觉得任何有必要的时候都可以大胆的改进。对模式加深认识:不死搬硬套,要知道该何时用,以及如何逐步引入。
  • 着眼于应付未来变化的设计,知道现在做出的决策可能要过后进行修改。
  • 知道如何将设计传达给必要的人,用代码、图表和最重要的:交谈。

  摘录《可以不当工作狂吗》的一段内容

  “……哈佛商学院的Leslie Perlow,作为人种学者,对多个国家的几支软件工程师团队进行了一项研究。这几个团队在做类似工作时的工作效率几乎一样。但Perlow发现,这些团队工作方式不同,对成员的影响也截然不同。”

  “在印度,工程师们遇到问题时会直接找团队里的其他专家。他们之间相互的承诺关系使每个人的工作时间往往很长,因为每个人都觉得必须让同事找得到自己。”

  “在中国,工程师们工作时很少互相说话。所有的帮助请求都汇总到项目经理那里。这使得每个人都严重依赖项目经理。”

  “在匈牙利……”

  “Perlow说,这三个团队都相信没有其他的工作方式,他们只是按照全球市场的要求来行动。然而印度团队的做法会让人筋疲力尽;中国团队受制于上司;只有匈牙利团队的做法使员工拥有自己的生活……”

  梦想风暴在他的《乱弹设计》里写道:

  “……曾经的我就是一个为技术而技术的人,当一个项目启动的时候,我首先考虑的就是如何展现自己的功力,如何编写优雅的程序,而忽视了根本性的问题:需求。真正让一个软件产生价值的就是需求。少了需求,软件的意义也就荡然无存了。当一个人做事连自己的目标都搞不清楚的时候,不做错事,已经要感谢上天的眷顾了。没有正确的目标,其它的努力只是让自己在未卜之途上越走越远。有许多部影片为我们展示了邪恶科学家的威力,他们不是不努力,只是弄错了方向……”

  “……谈到设计,最好的设计标准还是那条经典的“高内聚,低耦合”。了解了这么多的设计手法,体会那么多的设计原则,到最后,基本上都能归结到这条标准上来。设计的过程是一个分分合合的过程,先是把系统分拆,再把功能相近的东西合并,这样就形成了一个模块,有人叫服务、子系统、类、组件、函数、方面……,都是一回事。设计者需要考虑的就是让(各个模块实现者)怎么去做这个游戏,以便让各人能够独立工作而不致于相互影响,这就需要请出“高内聚,低耦合”作为衡量标准。模块之间的合同就是我们常说的“接口”,它可能是函数调用,可能是参数传递,可能是共享数据,也可能是远程调用。总之,有了合同好办事,谁的问题谁负责……”

关于Rails框架

  很多国内技术人员对Ruby感情复杂,除了技术因素,还搀杂了一点别的东西:Ruby的创始人是个日本黑客

  其实技术就是技术,机器不会说谎,先进的东西你不吸收,只能更加落后。再说自由软件项目从来都是超越国界的团队成果,例如大红大紫的Ruby On Rails来自美国。技术上,Ruby和Java或.net不是替代关系,但Ruby On Rails的确是目前开发WEB2.0效率最高的工具,大大影响到了java和.net阵营。

  看到calvin的各系Rails大点兵,很不错,推荐。里面提到出色Rails框架的特征:

  1. 动态语言
  2. extreme simple to use的ORM框架
  3. extreme simple to use的MVC框架
  4. 自动生成代码的命令、模版
  5. AJAX、WEB Service、i18n等特性自由扩展

蛋白质数据索引

  阅读代码,画图,然后重构,单元测试……搞定了数据库部分。

  被数据结构的细节所纠缠。由于性能原因,无法使用通用数据库平台,只能自行开发一个数据服务。用各种酶在不同修饰条件下,对几十G 蛋白质数据进行酶切,得到肽离子,建立索引(即使最小的库,都有将近九千万条,而且每新增一种酶或修饰,数量还要翻倍),然后用文件映射方式,通过共享内存提供服务。

  其实我很欣赏系统最初的体系结构设计。但是由于1.5版deadline很紧张,老板施加了巨大的压力。为赶进度,工程师编程顾不上接口和重构,产生了大量的耦合和拷贝,弄得整个架构动弹不得。我接手后,只好回过头用几倍的时间阅读和重构代码,何苦呢?

  彻底重新设计了数据访问类,用一个纯虚父类做接口,隔开其他部分。每种具体实现方式作为一个子类,比如目前读取共享内存的代码。还打算把1.0版不通过索引的数据访问方式代码也移植过来,实现另外一个子类,提供给系统内存小于1.5G的用户。

  固定了接口,新版本就可以再实现其他数据访问方式,比如通过关系型数据库和Web Service提供数据服务。用关系型数据库的话,为保证效率满足Web应用,就必须用集群或网格了。这种情况一般都考虑Oracle,但涉及到老板的银子……听说Google采用MySQL,很希望知道他们的方案。

  今天跑通了单元测试案例,用的是马(Horse)的蛋白质库,不加任何修饰。Debug版访问所有肽链一遍,2.266秒。以前还真没对付过这种级别的海量数据。

BTW:早上去了趟所里,玩了玩曙光3000,酷。

对象的多态技术

  C++的虚函数机制,是通过一个虚函数表存放函数指针实现的,一旦子类重载了虚函数,就替换掉表里的指针。COM的原理与此很类似,其中Interface可以看作纯虚类。C++是静态语言,函数表没有自描述性,调用代码完全是编译器由指针偏移量编译得到的硬编码。也就是说,虚函数表是定长的,即使是子类没有实现的函数,也需要父类的函数指针来占位。

  而Ruby等新一代动态语言,因为包含元数据,可以根据方法的名称进行查找。子类实现了几个虚函数,它的查找表就有多长,找不到再扔给父类继续查找。

  思路上,两者都是插入一个中间层,拦截外界对方法的调用,按照具体的类信息指定实际执行的代码。两者也有差别:动态语言因为涉及到字符串比较,查找效率相对差,而C++运行效率更高;但如果一个C++的父类提供成千上万的虚方法接口,即使子类只实现其中一小部分,也必须带着一个巨大的函数表,这显然造成了空间浪费。

  典型的,GUI架构如果采用虚函数机制,即使再小的对话框,例如‘确认取消’,甚至是只需要实现OnClick()的一个按钮类,由于是从Window类继承下来的,都必须带着一个存放几百甚至上千函数指针的函数表。

  为了对付这种情况,MFC没有使用虚函数,而定义了一大堆宏进行事件消息分发,虽然语法风格很丑陋,但保证了效率;而.net框架,发展出delegate机制,呵呵,兜了一圈,我们又回到纯C的回调函数风格了。

  参考资料很多:元数据、反射和delegate机制的资料都源于网上;C++和COM技术参考这两本:《Inside The C++ Object Model》和《COM原理与应用》。

里程碑、系统分析、提问和倾听

  士气很高,可能因为pFind开发进度顺利吧。算法层的单元测试案例完成后,就可以放心把各个模块留给博士们独立开发了。开始画应用层SDK的业务流程。周四下午拖着别人开会。傻乎乎的外行问题、写写擦擦的白板、乱七八糟的稿纸。周五整天都在修改Visio图。每30分钟拿着修改稿跑去访问(或者说骚扰更合适)一圈,好累,总算把系统业务流程严谨地定义到白纸黑字上了。下周被其他项目拖住的工程师也能过来了,要启动新的里程碑了,嘿嘿。

  交流真是一种技能。很多时候,你以为懂了,达成一致了,其实没有。必须把细节清楚列在纸上,拿给人家看,被认同,这才算数。

  例子太多了。比如我找几个博士聊过很多次后,仍然以为蛋白质片段匹配的误差分析,是各种质谱类型对应不同的统计图;直到在业务流程上写注释,才意识到可能有误解。跑去一问,原来所有质谱都应该提供多种误差分析工具,只不过生物学家针对每种质谱类型有不同侧重。实际上,调查对象已经多次明显表达出这个意思了,但我的耳朵一直在按软件工程师的思路去听。系统分析像是深入陌生的专业领域签合同,应该始终提醒自己,与领域专家的交流要虚心,提问要细致再细致,总结文档要明确再明确。

  BTW:老板原则很“完美”,不仅要求牛paper,还要实现真正可用的商业级系统。纸上漂亮的模型遇到实际数据不一定管用,实验室的博士生真比别处辛苦。

项目管理

  管理中什么最重要?是那些甘特图,那些文档模版,那些例会制度,那些考勤吗?

  人,只有人,项目管理的唯一内容:怎样面试,怎样确定角色,怎样管理需求,怎样识别风险,怎样争夺资源,怎样交流,怎样激励,怎样授权,甚至怎样写邮件……

  匆忙从Engineer提拔到PM的可怜虫,一遍又一遍重复的错误,一个又一个焦油坑……

  在Amazon软件开发图书排行里发现了Software Project Survival Guide。97年出版的老书,名气一直不大,我那本还是从打折旧书堆里翻来的,没想到如今又能成为畅销书。