一个人自娱自乐的写个小程序,跟一帮人一起写个大程序。真的是不一样。 自己一个人,根本就不存在交流,相互理解的问题。人越多,理解他人意图,向他人解释意图就越来越花时间
自己一个人,根本就不存在交流,相互理解的问题。人越多,理解他人意图,向他人解释意图就越来越花时间。只要是需要交流的任务,并非是人越多越好。有人加入,为了使加入的人有事做,原来的事就要重新划分,而分开之后要配合,又要花时间交流。发觉很多重要的软件开始都是几个人做出来的。而漫画中,进行任务也采用小组模式,好像<幽游>, <猎人>都是四人小组。
这里,最基本的问题就是任务的划分,最理想的划分是相互独立。而要做到这种独立,正交的划分,是很困难的,更困难的是你会发现那最初的任务会随着时间变动。传统的软件工程会说,首先是定义需求,跟着大体设计,详细设计,编码,单元测试,整体测试等等。但真正实施起来,发觉没有那样理想,很多项目都不是重新编写,而是在原有的代码上加强,很可能原来的根本就是沙地,很是流沙,而却要在上面起高楼。假如一个项目规定是半年完成,花上一个月去定义需求和设计,跟着去编码了,就会发现很多事情是想不到的,有些看来很简单的事一卡就卡上一个月,毫无进展。发觉这本来是很简单的问题牵涉到项目的结构,而之前结构很难修改,又或者不舍得去修改,为解决这问题,就使出一些歪招,看起来好像很巧妙,却打乱了原来的结构,跟着这些古古怪怪,想不到的问题一个个冒出,原来的设计渐渐偏离,又没有去修改文档。时间越来越紧,项目拖后,公司上层发觉不对路了,就加人,新人来,拿起最初的设计文档看,发觉根本对不上。旧人就又花时间会帮他理解项目。到原定的发布限期,程序却一运行就死机,最终结果迟上两三个月发布,发布之后很多bug, 再花两三个月改错,发个补丁包。一年就此过去。
软件中常说的任务划分,其实就是要确立边界条件。比如划分出组件,组件中划分出类,组件与组件,类和类交互都要通过一些接口。边界条件最容易出问题。本来我这个类好好的,一和另一个类交互,就出问题了,又比如这个函数好好的,一到线程切换那一瞬间就出问题。接口的定义很重要。软件还没有完全做到硬件那样即插即用。先确立接口,跟着找不同的人做实现,最后嵌起来就可以用,这也是很理想的一种情况。很可能最初那样划分接口就错了,另一种可能是实现者理解错了接口的含义。很多事,做完了才知道是做错了。比如考试,通常考完出来就知道答案,想着下次好好准备,考好点,却发觉下次还是有题目是不会做。
我现在自己的意见(以后可能也会变), 项目初期是无论如何都想不出具体的细节的,所以只要有个大方向就可以了, 要尽快动手做。既然之前没有想出所有细节,就强调要可以很容易的调整代码的结构,添加更多的细节,也就是重构。注意,重构并非是添加新功能,而是在不改变程序表现的情况下整理代码,使原来的代码更合理,之后再添新功能就容易了。而所谓的添个if来判断空指针,再对话框上面加个按钮等等,就不算是重构了。
为修改代码更容易,应该注意某些小细节。特别是工程比较大的时候。有些习惯要一开始就养成,不要想着只是做些小玩意玩玩,没有关系啦。一开始就应向着这行业最top的那批人看齐,这样才有可能做到专业。
1. 减少编译连接时间,特别尽量不要在头文件上加头文件。
以前也很随意的乱加头文件,反正小工程,一下就编译完了。现在发觉只要我改某个头文件,等它编译完够看一章书了,经过编译折磨,才发觉这很重要。因为头文件一修改,包含或者间接包含的cpp文件就要被编译。乱包含,会发现底层的文件一改,几乎整个工程都编译,是很费时间的。另外就不要将所有类定义都写在一个地方,这样包含依赖会减少一些。
如果编译时间很长,明明知道有些地方不妥,也不会去修改的。好像写程序的都很怕麻烦。
2. 变量用到才定义,不要一开始就定义。
很多人还保留C的习惯,将所有用到的变量都定义在函数开头。这习惯其实很不好,一方面没必要的构造析构会被调用。更重要的是,你会发觉以后想将原来的函数分解成一些小函数时候,会很麻烦,因为定义在开头,作用域是整个函数的,你想提出一段代码,很难确定那些变量要用到,那些不用。另外变量定义一定要附上初始值,这个错误看起来很弱智,但很多人会犯,特别是喜欢将所有变量就放在开头的那种C风格的人。
3. 用类管理资源,获取资源跟释放资源尽量在同一个地方,不要分开在两处。特别是不要在同一个函数中不要new, delete同一对象或者数组。
资源是很广义的,比如取gdi对象和释放,常说的是内存。要复制一个字串,或要暂时读一内存,开始又不知道长度,很多人会new 一个char数组,跟着函数末尾在释放。这样就有问题,比如中间有一个return, 跳过了释放的语句,就有资源泄露。对此很多人坚持一个函数一个出口,但是这样往往有一些变量标记性着是否结束循环等等,远不如一个return直接。另外换另一个人来修改代码,他很可能不信奉一函数一出口。随着函数修改变长,对应的获取释放相隔越来越远。怕麻烦,为了不在每个return之前添上释放代码,有人就用goto out: 替代return, out:之后做释放。
只要出现goto, 资源跟释放隔太远,以后想将一些代码提出来,做成另一个函数,就很麻烦。
所以C++有个惯用法RAII, 简单理念是用类来管理资源,在构造函数中获取,在析构释放。因为标准保证每个出口,已生成的对象其析构会被调用,包括发生异常。如果要分配char数组,可以用std::vector<char> mem; mem.resize(memSize)来替代, new char[memSize];, 之后的指针可以写成&mem[0]。 有人可能会说,如果对象太大,可能直接定义会引起栈溢出,所以要new. 如果真的是这样,很可能是那个对象的设计本身就有问题。要是老是写这样一些释放的辅助类很烦人,看看Loki::ScopeGuaid.
4. 风格保持一致
风格看起来是很个人,很细微的事情,但对于一帮人保持协调也是很重要,最忌的是团队每个人,或者个人在不同时候的风格都不一样。因为风格不同,在从一种风格逃到另一种风格时,思维上会卡一卡。另外接口命名的风格不一样,就很容易的将接口用错。
比如,有个迭代器类,要判断前进是否合理,有千奇百怪的名字,hasMove, hasMoveElements, isCurValie, isValie, isOk isLegal。又或者判断有几个元素,有名字getLength, length, size, numElemnts, totalElements。你用那些类,大概会骂最初写代码的那批人太白痴。但想想,有几个人是会将风格保持一致的。
其实风格本身没有好坏,最怕的是不统一,选择一种,跟着用就是了。但现实种往往是一批人同做一项目,各人都有自己写法,还鄙视他人的写法。假如A是取名成stl那风格,小写加下划线,B取命成MFC那种,C取名成java那种,看起来会很累。这个问题是很普遍的,写代码的很多人都很聪明,也很自傲,往往觉得自己做的才是最好的。
另一点是,相同功能类的那些函数接口,如果有同样的函数,应该将名字取成一致,不要搞得很乱。
一帮人一起会做一件事,会确立规矩,比如一起会玩游戏啊,出去玩啊,会商议定个时间。有类人当别人商量规矩时候,他不出声,或者是做别的事,比如玩手机发短信之类。规矩定好了,自己不清楚,就老是问,或者犯规了就怪规矩定得不合理。这类人是很影响士气的,观察一下,你身边应该会有类似的人。
5. 最后一条,老原则,Keep it simple and stupid.
程序首先是给人看的,之后才是被计算机看的。一定要简单。比如接口设计开始要最简化,不要想着这接口以后会用到就加上,要想着以后可能没有用就去掉。如果你自我陶醉,觉得自己很聪明,这样巧妙的代码也可以写得出,将来还有什么软件写不出来,注意,这样想会有问题。因为越是巧妙的代码,以后修改的人会越难理解,越容易出错。其实简单的代码才难写。最好的设计应是理所当然,顺理成章的,感觉不到有多巧妙。<孙子>中有句话,善战者,无功名,无勇功。因为他们去打仗,打之前就赢定了,根本就不需要很勇敢,很激烈才能打赢,一切顺理成章,好像没有什么难度,自然不会被人去歌颂。同样,需要很巧妙的方法才能解决某问题,本身就落下乘。