编程低手箴言(三)


三、代码的组织
我遇到过无数利用面向对象的方法,结果代码乱成一团的例子,其中有不少就出自我本人。
许多初学者感到困惑,他们刚开始写程序,程序代码非常优美,程序运行,编译,调试无不得心应手;待3个月后,发现手头的代码是一个泥潭;半年之后,每次要修改点东西都如跌入深渊;一年以后,他们开始酝酿重写整个代码。
由上我们知道合理组织代码的重要性。写好代码的根本原因在于需要维护和复用。这一点不难理解,如果你的代码再也不需要维护或者再也不会被用于别处,那写的再乱又有什么关系呢?
熟练掌握设计模式有助于将代码组织的更好,不过这不是一件能速成的事情。我们需要另辟蹊径,注意到设计模式很大程度上是伴随着java和C#渐渐被我们所熟知。在此之前,许多优秀的C++程序员并没有听过设计模式,他们一样能写出便于维护和复用的代码来。因为他们有自己的秘密武器:接口与实现分离。在现在主流的编程语言中(C++,java,vb,c#,delphi),C++是仅有的将接口(h)和实现(cpp)分开的语言。我们应该好好利用这一先天优势。
接口与实现分离,这一点许多人都听过,可是能坚持照做的却不多。有时候是因为懒惰,有时候是因为习惯,其实还是懒惰的表现。罗马不是一天建成的,代码也不是一天弄乱的。下面我会教大家如何开始行动。

原则一:不要在头文件中随便包含头文件
虽然我们看到的很多代码都在头文件中包含无数头文件,包括C++标准库,MFC库以及许多开源库,在商业软件中,这种情况会更多。但这并不意味着他们全是对的。
因为在头文件中包含别的头文件是将代码弄乱的根源,这跟疾病的传染源一样,在cpp文件中无论怎么写,不会对其他的代码造成影响,因为C++是分开编译的。可是头文件写来就是被cpp文件包含的,头文件写的好坏,对整个代码的影响是显而易见的。
如何判断应不应该在头文件中包含其他头文件呢?假设你写了一个fooA.h,里面应不应该包含fooB.h呢?你只需要问自己两个问题:
问题1,我需要它吗?
这好像是废话,不需要我包含它干嘛,可是实际上,许多人的头文件都在包含不需要的头文件,包括刚开始用到了,后来不用的。要整理代码,第一步就是将不需要包含的头文件从头文件中删掉。
问题2,我的使用者需要它吗?
这个问题比第一个要难一些。通常有两类cpp文件需要包含一个头文件,分别是实现者和用户,如果是前者,即使它需要,它可以直接去包含它要的头文件,根本就无需在头文件中再包含一个头文件。对于后者这种情况的回答,才是这个问题的关键。
如果你对1、2都回答是,那么恭喜你,你在头文件中包含另一个头文件的做法无论于己于人,都是好的。
如果你对1回答否,对2回答是,那么,请确认你的头文件是那种做公益事业的头文件。(有这样一种惯用法,如果某种用途需要包含许多头文件,例如a1.h,a2.h,a3.h等等,为了方便,写一个头文件a.h将它们都包含进来,使用者只用包含a.h就行了。)
如果你对1回答是,对2回答否,那么这将是我们讨论的重点。抛开技术问题不谈,单从道德上来看,相比前两种的高风亮节,这种做法简直是太损了。
千言万语,对于程序员来说,不如几行代码直观:
// foo.h
#include "afx.h"

class Foo
{
public:
 void Method(const char* filename);
private:
 CFile m_file;
};
上面这样的代码应该许多人都写过,现在让我们用上面的标准来衡量一下,显然,foo.h需要包含afx.h,因为其中有CFile的定义,可是对于foo.h的使用者,它需要了解CFile吗?打开后的文件是用MFC的CFile或是C库函数的FILE*或是WindowsAPI中的HANDLE或者是标准库中的fstream来表示,对于它的使用者来说,一点都不重要,也不需要知道,虽然Foo中用private关键字来保证了m_file不会被用户直接访问。这在一定程度上迷惑了初学者,以为用private就是封装了。其实private的重要作用是使类能保持其不变式,虽然m_file是private成员,可是一旦实现需要改变,例如改用fstream,所有直接或间接包含了foo.h的文件都将面临重新编译。有些时候,重新编译是很可怕的,例如你的工程非常大,而老板给你的机器配置太低,你就可以体会到了(这种方式用来要挟老板为你升级机器倒是很管用)。
如果重新编译你不在乎,我这还有更重要的理由,你看看afx.h中有许多个好用的类,它们都充满诱惑,难道你没想过在用Foo时顺便再用afx.h中的东西吗?那个private看着挺不爽的,想加点什么功能都不方便,添加一个数据还得再加两函数,难道你不想把东西放几个到public中吗?一旦你开始这么做,你等着,不出两个月,如果代码还在维护,它一定已经千疮百孔、盘根错节,牵一发而动全身。
有几个简单的方法来改进这一点,先介绍使用前向声明,对于上面的例子,可以改写如下:
// foo.h
class CFile;
class Foo
{
public:
 void Method(const char* filename);
private:
 CFile* m_file;
};
OK,现在清爽多了。用户不用知道什么是CFile,而你可以在foo.cpp中再包含afx.h。这种方法还是有局限性,如果以后需要增加数据成员了,用户还是需要重新编译,另外对每一个数据成员都不厌其烦的加上前向声明也挺烦的。下面我再介绍一个更加彻底的版本。
// foo.h
class Imp;
class Foo
{
public:
 void Method(const char* filename);
private:
 Imp* _Imp;
};
这下无敌了。在foo.cpp中你可以随心所欲
// foo.cpp
class Imp
{
public:
 CFile m_file;
 string filename;
 // ...
};
除了前向声明,还有一个方法是采用接口继承,这个我想留在COM那一章再讲。


原则二:不要拷贝代码
虽然说天下代码一大抄,可也得看会抄不会抄。我前面说过在cpp文件中随便怎么写也影响不大的话,现在我认错,我忽略了拷贝代码这一点。设想一下,一个程序员把一段有问题的代码在数十个cpp文件中拷贝,然后再找个程序员来改一改其中的Bug,他只发现了两处,分别修改了。软件发布,用户抱怨,第三个程序员开始修改Bug,他也很容易的找到了几处并且修改了,之后软件开始升级,再后面的程序员开始犯困了,为什么到处都是相似的代码,仔细一看还都有些不同,改着改着就不知道在改哪了。
没有好的做法可以防止拷贝代码,也没有什么标准来判断应不应该拷贝代码。我只说一句,希望大家牢记在心:以复用代码为荣,以复制代码为耻!

<未完待续> 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值