All Stories

幸亏我不是做网页的

  看到一篇义愤填膺的博文,正气浩然。又看了一下文中的链接,一篇广告帖引起的口水仗。不过倒真是看不惯那作者的作法,死不认账。又看到其他人的跟帖,我不禁庆幸,幸亏我不是做网页的!  不过说到底,我的专业领域C++方面,应该也是有这种问题的,不过我自己没怎么接触到而已。在我准备大举进入本行业之时,有候捷在那里做了不少出色的教育和科普工作,从此弯路就极少走了,想看的书,基本都是挑公认的最顶尖的专家写的最优秀的书籍。而过了那么一小段时间后,自己也基本上具备了识别并挑选出优秀书籍的能力。  不过今天看到那些讲什么ASP、AJAX、JS的书,我还是很庆幸,那个行业似乎比C++的混乱多了啊!

图形程序设计续之自动调整连接点

  本来计划是明天才开始做这个功能的,不过因为着急着要尽快走流程办手续,只好把这最主要的任务尽早实现了。  总的说来,基本算法是完成了,不过还是有些小bug。大体的算法是这样的:三类连接线,包括折线,样本曲线和贝塞尔曲线,其中两种曲线要都Flatten一下,最后变成一组前后相连的线段,然后将这组线段分别与矩形的4条边计算交点,把离交点最近的跟踪点作为新的连接线的一个端点。  这里要注意的是计算交点的顺序问题。也就是说,如果是从连在矩形上的端点开始遍历那组线段来计算交点,那么应该以最后一次的交点作为最终结果。而如果是从另一个端点开始遍历计算交点,那么应该以第一次的交点作为最终结果。  其次是中间点的调整。像折线,一个比较简单的做法,把交点与端点间的所有连接点都删掉就可以了。而样本曲线则应该调整中间的切点位置,贝塞尔曲线则是调整两个控制点的位置,以免新生成的曲线因为中间那些点的牵引而仍然横穿矩形。

图形程序设计续之添加子对象

  今天大概花了两个小时吧,加了右键菜单添加子对象的功能。这个功能的实现,有几个部分需要关注。  首先是图形绘制部分。这是需要修改最少的部分,因为之前的工作已经实现了子图,子对象就是在子图中添加的对象,只需要在相应的响应函数中实现添加子图,在对应的图中添加对象即可。  其次是右键菜单动态生成。这部分也不是很麻烦,只要识别出当前点击位置是什么图形,该图形在业务意义上可以拥有哪些子对象,就可以拼装出一个完整的右键菜单。  最后是花了不少精神的配置信息读取部分。本来配置信息都是从配置文件中读取的,因为并没有对配置文件与配置项的对应关系做严格的限制,所以原先的设计中,把配置项的信息基本上都是做成实时读取的,今天又要拿来用一次,就显得原来的设计似乎不是很方便。不过我还是没做更多的改进,仍然沿用原来的做法,实时读取某个对象可以拥有的子对象的类型信息和其他描述信息,以便于添加到右键菜单和后面的创建子对象 。  今天的任务基本完成。数了一下代码量,这个项目总共才4.7Kloc,挺少的,很出乎我自己的意料之外。这么算来,假设我在这个项目中已经投入20天,那么有效产量大约是230行每天,比自己订的目的500行每天少了一半多啊!

代码的坏味道

  最近一直在学习Martin Fowler的《重构》,并且对照我参与的一个已经投入至少15人年,历时3年,约20万行,目前仍然在继续开发维护的项目,让我觉得触目惊心,其中的代码,到处充斥着Martin Fowler所谓的坏味道,而又困惑重重,不知道别的项目代码质量是如何的。  下面就都只是随便举一下项目中的实际情况为例,项目是用MFC开发,使用了Codejock的Xtreme Toolkit Pro界面扩展库。  重复代码。有3处计算MD5的实现,分别由3个开发人员完成,大概实在是这种实现的代码在网上太容易找到了。另外有一个特性,可以与另一个服务进行文件的上传、下载、更新、同步,而文件因为类型不同,做这些操作时某些细节有细小的差别,但实现中却是为每一类文件具体而完整都实现了一遍这些操作。  过长的函数。有的开发人员就是习惯性地写出长函数。整个项目中,圈复杂度超过100的有4个函数,超过20的不知道是几十还是上百个。  过大的类。有一个类的cpp文件,是18000行,另外有一个类的cpp文件是10000行。还有CMainFrame类的cpp文件,用Source Insight打开后,在列出函数列表的窗口中显示“Too complex to parse”。  过长的函数列表。有一个cpp文件中共9个函数实现,每个函数的参数都超过7个,而且含义晦涩,自从原创人员两年前离职后,没人敢去动那块代码。  发散式变化。前面提到的一个18000行的cpp文件,是一个视图的实现。如果要给该视图的右键菜单中新增加一个菜单项,并进行响应,需要修改不知多少个函数,记得曾经有个开发人员,花了一周时间都在为了一个新增的菜单项。添加代码没花多少时间,时间花在添加后,因此引发的问题上。  霰弹式修改。有两个模块都需要一个高亮显示语法关键字的编辑功能。有一个基本的控件封装类,但要修改一些代码时,总是要很小心地去从头检查一遍另一模块的实现是否受影响。我的理解是,这个控件封装类的抽象不够通用,或者两个模块的相似度并不高。  基本型别偏执。这样的代码在项目中不好找,不过有类似的。项目中使用MSXML操作xml数据,在各个模块的实现中,都直接聚合了一堆MSXML的接口指针,操作xml的方法,和业务逻辑、界面响应完全混合在一起。  Switch结构。很多处又大又长的switch结构。  冗余累赘类。有两个(派生)类过于考虑以后的扩展性,而那种扩展性的需求至少在未来2、3年内是遇不上的。  夸夸其谈未来性。有一个快捷键处理模块,从项目刚开始就已经实现完成,但后来一直没被用过。项目没有开始实际编码前,超过5个人,花了2个月制订了各个模块需要暴露的COM接口,结果到现在3年了,真正实现的接口也才10个左右。  中间转手人。CMainFrame类已经成了各个模块用来转发消息的场所。一个重要的原因是界面与业务逻辑耦合,很多业务处理需要MainFrame转发到相应的界面实现类中进行处理。  狎昵关系。无论是各个Pane还是MDIClient,都与CMainFrame存在着这种双向依赖关系。  异曲同工的类。两个有交互的模块,居然各自定义了一组数据结构,用来描述现实世界中的同一种事物,中间又由CMainFrame来完成这两组数组结构之间的转换。  纯数据的类。很多时候,为了向线程函数传递一些数据(超过一个DWORD的量),就专门定义一个纯数据的类。  被拒绝的遗赠。两个平行的模块,一个类是从另一个类继承过来的,而明明有很多那被继承的类的功能,在派生类中是不需要的。呃,被继承的就是那个18000行的类。另外还有那两个需要编辑功能的模块,曾经居然也是一个类从另一个类直接继承,导致在派生类中变成不需要什么功能,就加些代码,把那部分功能屏蔽掉。  过多的注释,有一个开发人员,喜欢在自己编写的函数开头部分写上几十行注释,呃,全是算法描述和伪代码。  在公司4年,我参与过的略有规模的项目,除了这个外,另外有一个,基本是独自一人完成,代码量最高峰是7万行,后来路过不断的重构,在仍然有新特性增加的前提下,代码量缩减到4万多,现在回头看来,这个项目中代码的坏味道似乎少一些,但质量却也不行,崩溃经常发生,其他业务逻辑有问题的也不少。  所以,我就很是困惑啊,别人的项目是怎么样的情况?

图形程序设计续之子图

  这次的需求中提到,能为一个图形关联一个子图,用该子图来描述该图形的内部结构信息。展现给用户的语意是,当双击一个图形时,则展示该图形关联的子图。  今天投入去实现该特性,基本没有遇到什么阻碍,因为原来的架构设计可以满足这个需要。当初把用户界面视图绘画层,图形管理器,图形对象三部分独立开来实现,所有用户操作都从视图绘画层接受,将操作信息传递给图形管理器,再由图形管理器转发给各个图形对象进行响应。在那之后,我曾经考虑过,这么做是不是有点儿过度设计,这个图形管理器的操作响应转发功能是不是多余的。现在却发现,这样的实现才刚刚好,一个子图,对应一个图形管理器,视图绘画层要始终跟踪当前起作用的图形管理器对象,任何时刻都只跟一个图形管理器进行交互。这样,没花费多少力气,只是增加了创建图形管理器,维护图形对象与图形管理器的对应关系以及销毁图形对象和图形管理器的功能,就可以实现子图功能了。  在实现这个功能时,有一个小心得,及早加入dump文件捕获功能很有用的,因为即使是开发阶段,也不是每次都在调试器中运行,所以当在自测试时偶然出现的崩溃现象,可以及早通过dump文件进行定位,而且因为是Debug版本,所以通过dump文件分析出的结果往往比较精准和明确。  除此之外,还发现一个以前没想到的现象,原来MFC中,CDocument类的OnCloseDocument方法,居然会销毁自己,及其派生类对象,如果要做些扫尾功能,放在派生类的OnCloseDocument方法中时,一定要把调用CDocument的OnCloseDocument方法放在最后。

std::copy_if?

  几天没有正儿八经写代码了,今天又咬牙写了一会儿。在完成一个新功能后,开始重构原来的代码,把其中用于BOOST_FOREACH的地方都检查了一遍,看能不能重构掉。在刚有BOOST_FOREACH的时候,我还是觉得它很简单很方便很易用的,也许是当时觉得写for循环来迭代遍历容器步骤繁琐了一点儿。自从用上了boost::bind和boost::lambda以后,渐渐习惯了作用STL中的算法来操作容器,到现在,看BOOST_FOREACH都觉得很不舒服。  今天看到的几个BOOST_FOREACH,最后被我改得只剩下一处。其中有一处,需要用copy_if算法,而STL中明显是没有这个东东的,看《C++标准程序库》一书说的,如果要这样的功能,得用remove_copy_if,它是copy和remove_if的结合体。结果我开始用的时候没有仔细看它的使用说明,拿来便用,运行结果总是和我预期的刚好相反。我一开始总以为remove_copy_if,是先像remove_if那样,把满足op为true的元素都移动到容器的最后,然后把这些元素都copy到另一个容器中。实际上是我想差了,应该是它会把源区间内的所有元素都尝试copy到另一个容器中,在copy过程中会把满足op为true的元素剔除掉。我大汗,这个算法的设计实在不好,我就觉得很纳闷,当年那帮大佬们怎么硬是不提供copy_if呢?  回到家看了看boost sandbox中的algorithm库,里面已经实现了copy_if,以及其他几个很实用的算法,看注释似乎是作为TR2存在的,可能会加到C++0x中,但那实在是遥遥无期啊!

几根白头发

  中午去吃午饭,楼梯上遇到王同学,依然是一脸茫然困顿的样子,问我去不去吃KFC,我大汗,这个时候选择去吃KFC还真是意外,问她为什么,说是要去洗头,只好去那旁边的KFC店里解决午饭了。而我原本是去食堂的,根本没带钱出来,两手空空,王同学只带了30元,外加几张KFC的优惠券,两个人就这么冲过去了。  花光那30元钱,外加一些她车上的几元零钱,基本心满意足地吃完KFC,又让王同学带我去洗头。洗完头,吹干,看起来舒服多了,这几天台风来,气温降,早上都懒得洗头洗澡,确实脏了点。猛然发现自己头顶好几根白头发,恐慌,恐惧,恐怖!

热替换不成功的bug

  升级程序中有一个热替换功能,呃,这个热替换,其实是我自己发明的词,意思是如果EXE、DLL之类的文件正在执行,文件是不能删除的,那么升级时也要能被替换成新版本的文件,而不需要相应的进程退出,这就是所谓的热替换。  之前也一直陆陆续续有bug报上来,说热替换不成功,但有时候又是成功的,于是也一直没放在心上,把它归结于Windows这个API可能有问题,当然更可能是自己用得不对。直到昨天自己再一次调试时,发现一个诡异的现象。升级程序的可执行文件叫updater.exe,其中加载了dbghelp.dll,当我升级这两个文件时,必然需要热替换了,结果发现,在文件下载完后,替换确实成功了,dbghelp.dll确实已经是新版本的了,可是当升级程序在弹出提示框,提示用户升级完成后,用户点击确定,提示框消失,那个dbghelp.dll文件又诡异地变回旧版本了!一连试了几次,都是如此,简直就是灵异事件了。  当然,不可能真是灵异,这种单线程的逻辑调试起来还是比较简单的,单步跟踪了一会儿,就发现,原来在在提示框消失后,会调用一个结束处理过程,这个过程中首先会判断当前是否正在升级,如果是,则中断升级过程,并把已经替换掉的文件还原回去。而刚好这段代码写得有问题,把是否正在升级的标志置位,放在调用这个结束处理过程后了,于是总是会发生回滚还原的操作。问题的解决很容易,只是调整一下两个过程的调用顺序就可以了。

修改升级程序

  今天花了点时间在修改升级程序。这个程序完成后,一直没有进行过有效的测试,而是直接给别人用了,呃,这里的别人指的是部门内的其他有项目升级需求的同事。实际上,至少有3个其他项目中使用了这个升级程序,但我自己却没有真实地体验过。总的说来,这个程序在功能上,基本能满足当前的绝大部分需求,从当初的质量部的地个项目中独立出来后,一次大规模重构,代码结构作了不小的调整,当然这调整的原因是原来的架构已经不能适应后面增加新特性的需求了。  曾经倒也是规划过一个比较长远的版本计划,呃,也许不能称之为版本计划,称为需求实现计划更合适一些。不过后来还是剩下几条没有实在,因为没有工作量分配过来了。今天的投入则是现在发现,在某些情况下,它会崩溃,这是最不能容忍的致使问题。不过很无奈的是,原来没有使用google breakpad来生成dump文件,现在加入了这功能后,暂时也没能把它弄崩溃,于是也压根不知道问题出在哪里。只能听天由命了!