2006/05/30

C++封装

巨硬的进度令人满意,不过在进入Debug之前,就进入了Refactoring阶段。Refactoring是软件工程的一个术语,我不知道怎么翻译,它的意思是在不改动需求和主体数据结构的情况下,对软件做优化,包括更清晰的组织代码,划分类功能,组织函数,重命名类和函数名等等。

Wikipedia的解释在这里:en.wikipedia.org/wiki/Refactoring (如果你能访问这篇Blog,那你也应该可以看到Wikipedia)。

之所以这样做是因为我并不着急launch这个程序,就像和newaa以及evan在网上聊天时所说到的,我本身并不把这个程序当成一个商品--确切的说是仅仅当成一个商品。巨硬带有很多实验主义的色彩,也倾注了我对手持计算设备的热爱,也是我不断学习软件开发乃至软件工程的一个课程,同时它还是WEB2.0时代的一个契机--虽然这只是一个输入法,但是它带来的更快捷的输入会改变人民对文字沟通的使用方式,而通过网络的文字沟通无疑是我们生活的这个时代发展最快的沟通手段之一。

一些软件工程高手常挂在嘴边的一句话是:软件工程从发布才开始。它指的是软件的功能维护,代码树维护,以及开发新的应用,拓展新的市场等等。至少到现在为止,我是打算接受这个挑战的,这也是我愿意重新打开CodeWarrior,重写巨硬II的最主要动因。从我的个性来说,我相当懒惰,而且不喜欢做重复的事情,如果只是用C重新写过巨硬并继续卖28元(或者一个美元)的话,我大概是不会做的。:)

Anyway,言归正传,如果你在看这个Blog的话,请不要催促我尽快发布巨硬II,也不要宣传巨硬II即将诞生。我会在适当的时候--当我觉得它很完美的时候,虽然事实上它不可能是完美的--发布它。这个时间不会很远,但是我不承诺一个严格的Deadline,这种压力只会使我在射门的时候动作变形,没什么其它的益处,我喜欢在最自由的空间里踢出最漂亮的弧线球--相信我,我会踢出来的。我能承诺的只是,我每天至少拿出3个小时在代码工作上,无论工作和生活多紧张,事实上在过去的2-3个星期我就是这样做的,我还会继续这样做下去,直到巨硬II的诞生。

好了,回到今天Blog的主题,说说我对C++封装的浅薄理解--你千万不要把它当作高手箴言,如果你想看高手箴言,我推荐你看豪杰解霸作者梁肇新撰写的《编程高手箴言》,或者10余年畅销不衰的Code Complete。我愿意写在这里,或者说写在这个几乎没人能看到的Blog里,就是颇有露怯之意,但是我还是愿意写下来,让明天的我看看昨天的我在想什么--这也是Blog的原本之意吧。

C++的结构非常灵活,你可以用很多种方式封装类,但是不幸的地方在于类是静态的,而实际的程序中要考虑对象的生存周期。所以第一个原则就是,生存周期不同的功能组,应该封装到不同的类里面去。

常常会遇到这样的情况,很多不同的功能都和一个资源有关,所以容易产生这样的想法,把所有和该资源有关的功能都统统放到这个类里面去,渐渐的,这个类就膨胀起来了,会出现很多的工作状态,这些状态之间彼此制约,结果不但是容易产生错误,而且对使用这个类的对象来说,要深刻理解这个类的对象的工作状态,这就违反了封装的本意--封装就是要只看到界面,而不必深入内部。

在一个类中留一个另一个类的指针并且在类初始化的时候赋值并不是什么坏主意,虽然这让人担心在指针实效的时候发生种种错误,但是在代码角度看,处理这些错误并不麻烦,而好处是,可以把对象的状态分开,容易理解。事实上那些被很多类调用的、用于封装排他资源的对象往往只有一个,在程序开始的时候初始化一下,然后传递给所有需要用到该资源的类,在这个类中可以很容易处理资源冲突情况,最后在程序退出前销毁;这是应付资源冲突的最好办法。资源冲突往往就像操作系统中的死锁问题一样,很多时候是不可避免的,用简单的失败处理代码来应付这种情况就可以了,这远比把事情搞的很复杂最终还是没有解决问题强。

C++中提供了虚函数和多态,这让代码看起来很优雅,但是虚函数往往需要实现该函数的类了解在基类中该函数被调用的具体过程,所以,不要写那些看起来很不易懂的虚函数,也不要让虚函数之间产生逻辑上的关联,不要为了让代码看起来更优雅一致或者更高深,而牺牲代码的易读性,易维护性,以及留下更多的犯错误的机会。通常在资源访问的问题上,虚函数是一个好主意,比如流,缓冲,或者数据库,它增加了代码的移植能力,因为大多数程序都必须和这些东西打交道。但是把程序自身的数据结构层层封装起来就不象是一个好主意,这需要使用者了解很多数据结构的含义和数据对象的状态--和状态打交道是程序设计中最复杂的事情,尽量不要处理组合状态,否则一定会死的很难看。

在参数的传递上,要避免传递复杂的数据结构,应该只传递三种基本的东西:(1)基本数据类型,含义和范围明确(2)简单的数据结构,它们封装了程序的核心数据,到处都用得到(3)对象指针,这些对象是作为一个占有资源的"服务器"存在的,很多时候要用到它们提供的服务,但是,不要试图了解对象的状态。这可能会破坏过程的原子性,在多线程或者多进程的环境下产生莫名其妙的错误。

总而言之,不要惧怕写很多的类,类越小越好;也不要惧怕类之间的依赖性,这是很常见的,而且它是由程序的核心逻辑决定的,不是说你少写了几个类就可以让问题变得简单;类之间的关系应该是松散集成的,彼此了解的越少越好。状态之间不要关联,不要在多个对象之中试图维护步调一致的数据。多画一些图来研究状态之间的关联性,直到你确定可以把每种状态都分离出来,再开始Coding,如果在Coding过程中发现了状态之间的耦合问题,那就应该回到设计上来解决这个问题,然后再开始Coding。

好了,该去写代码了。

No comments: