Table of Contents
软件设计就比如造房子。当一个软件bug重重,扩展很困难时,还不如推到了,再从新作一个。在博客上,看到陌陌的劲舞手游,由于在设计上不到位,致使整个手游从新开发; 同时负责这个游戏的项目经理也随之离职。java
1 文件的层叠
如今咱们在用eclipse开发软件,在切换文件的时候很麻烦; 由于一个系统模块的文件分布到不一样的文件目录里面去,就像珍宝,用层层的布裹着,你须要一层层的打开,才能知其庐山真面目。如下是用tree工具打印的目录树:linux

├── project
│ ├── include
│ │ ├── module1
│ │ │ └── submod1
│ │ ├── module2
│ │ └── module3
│ └── src
│ ├── module1
│ ├── module2
│ └── module3
这是我用tree打印的工程文件目录,在这个工程.h和.cpp文件用include和src来分开。若是你要开发一个系统模块,你须要在include的dir一、dir二、dir3和src的dir一、dir二、dir3里面分别新建.h和.cpp文件,由于一个模块又分为了几个子模块。若是你使用一种可视化开发工具中的project explorer来浏览文件,都要把头切换晕了; 要是可能的话module1下面可能还有submodule1文件夹。我以为系统的模块的层级结构并非特别须要用文件目录来表现,并且这样子也让Makefile写起来很麻烦; 可能有人会说能够不须要写,自动生成的,用automake。可是当要写一些跨平台的工程的时候,不只仅在linux下面,还包括windows以及mac,我更加倾向于用CMake。曾经我下载过redis的源码,这是一个用C语言开发的NOSQL数据库,其全部的.c、.h文件都放到了src目录下,简单极了,切换文件很方便; 并且其就只有一个Makefile文件。 在这里,我并非批评那些在一个工程里面放有目录的人,我是但愿尽可能能减小目录,尽可能把头文件和源文件放到一块儿; 由于咱们在开发项目的时候,同时要兼顾头文件和源文件。咱们能够这样子: 程序员

├── project │ ├── mod1 │ │ ├── submod1.cpp │ │ ├── submod1.h │ │ ├── submod2.cpp │ │ └── submod2.h │ ├── mod2 │ └── mod3
2 命名的规范
我见过好多搞C/C++的人,比较讨厌Java。首先Java隐藏了指针了,搞得这些同胞们,对人生失去了乐趣; 其次Java的效率问题,以为始终没有C/C++高; 再者以为搞Java有点脑残,太简单,垃圾什么的都不用管,有点乱扔垃圾的意思,不文明。可是我要说的时,Java有许多好的设计模式。在文件命名方面,Java作得比较好。java文件名必需要和里面的类必须同名,我以为C++也应该保持这样的方式,虽然编译器没有强制你这样作,可是颇有必要,这样子经过文件名称就能看到里面装的是什么类。其次Java用到了包,在Java编程思想里面包名和你的网络名称同样,这样便于网络传输,不会出现包的冲突,由于网络地址是惟一的。C++里面有namespace,用namespace封装本身的类库,能够防止本身的类库,和第三方的库冲突,不过namespace的名称必须是标识符。 我见过的工程里面,每一个程序员都有本身的编程规范,公司的编程规范没有很好的去执行。首先,我以为好多公司的编程规范不够详细,模棱两可; 其次好多开发人员可能以前在其余公司呆过,保留了上一家公司的编程习惯。若是公司可以肯定了好的编码规范,你们仍是愿意遵照的。这里我比较推荐google的C/C++编程规范,网上都有电子文档的。还有就是用脚本语言检测程序按期检查编写的代码,是否有不符合规定编码的规范,这些都须要项目经理去认真的执行的。redis
3 宏的臃肿
3.1 宏的好处
宏的替换是无与伦比,没有其余能够替代的。我看到宏在好多地方合理有效的利用,其能有效的减小繁琐的重复的代码、解决跨平台系统系统API不兼容的问题(经过系统的宏定义,来判断是什么操做系统,从而来调用相应的系统API,这些事情都在编译的时候就完成了)数据库
3.1.1 抛出异常
为了调试代码,我要抛出异常,异常的内容包括文件名、行号、错误码:编程

throw new Exception(FILE, LINE, error);
每次我都要加入__FILE__、__LINE__ 比较啰嗦, 使用宏更加方便:windows

#define THROW_EXCEPTION(error) \ throw new Exception(__FILE__, __LINE__, error)
这里不能用函数来替代的,要是那样,_FILE__, _LINE_就显示的是所用函数的文件和行,形成异常的错误提示有误。设计模式
3.1.2 解决系统的跨平台问题。

1 #ifdef _WIN32 2 xxxx 3 #elif _LINUX 4 xxxx 5 … 6 #else 7 xxxx 8 #endif
Poco库是一个优秀的C++库,跨平台。其结构也很清晰,能够去了解,里面有不少这样设计的。数组
3.2 宏的坏处
可是宏在编译的时候要替换代码,会生成不少重复的代码。若是你的宏要是很长,会很糟糕。有人可能说宏很快,我以为为了追求那点速度,而违背代码的简约设计,是没有说服力的。并且宏也带来调试的不方便(在宏里面下断点无效)。在代码中尽可能减小宏的使用比较好。大多时候,咱们能够用函数来代替宏,这样可以复用,即便宏很短。
3.2.1 使用带有const类型的常量
当咱们定义常量的时候而且常量不是在编译的时候使用,可使用带const的类型变量声明宏,例如const int len = 10。由于这样便于咱们肯定常量的类型。可能宏定义整形的时候,默认就是整形。要是宏定义一个小于255的数字,依旧使用整型,若是是一个浮点型,其默认是单精度,除非你把小数点加长,让float不能识别,这时候编译器才将其当作double了。其实用define肯定常量,让常量的类型产生了歧义,让类型不够明了。
3.2.2 使用工厂模式来经过名字获取对象
在C++里面没有反射机制(即经过类名来建立类),用宏是很容易作到的。以下: //根据名字来定义一个类:

1 //根据名字来定义一个类: 2 #define DEF_CLASS(class_name) \ 3 class class_name##Mgr {\ 4 public:\ 5 static class_name##Mgr* CreateClass() {\ 6 return new class_name##Mgr;\ 7 }\ 8 ....\ 9 } 10 11 //建立类 12 #define CREATE_CLASS(class_name) \ 13 class_name##Mgr::CreateClass()
以上使用##宏来链接类名,根据名字定义一个以Mgr结尾的类,以及建立对象。要实现这样的方式,咱们只能用宏。其实咱们能够其余的方法,即利用多态的特性来模拟这种特性。

class Base { public: .... }; class Derive:public Base{ public: .... }; class Factory { public: //注册各个类 void RegisterClass(const string& classname, Base* pBase) { classes[classname] = pBase; } //建立 Base* CreateClass(const string& classname) { if find return classes[classname]; retur NULL; } .... private: hash_map<std::string, Base*> classes; }
工厂模式能够根据类名来取出对象来,我以为已经够用了。虽然实际上没有根据类名来自由的建立类,你还得手动的去建立各个类,咱们只是在作存和取的工做。工厂模式通常被用来总领整个框架,就像是写做文的提纲似的。 使用宏不够好,同时也可能形成代码难以阅读; 应当尽可能避免宏。