所遇不良设计(二)

  软件设计就比如造房子。当一个软件bug重重,扩展很困难时,还不如推到了,再从新作一个。在博客上,看到陌陌的劲舞手游,由于在设计上不到位,致使整个手游从新开发; 同时负责这个游戏的项目经理也随之离职。java

1 文件的层叠

  如今咱们在用eclipse开发软件,在切换文件的时候很麻烦; 由于一个系统模块的文件分布到不一样的文件目录里面去,就像珍宝,用层层的布裹着,你须要一层层的打开,才能知其庐山真面目。如下是用tree工具打印的目录树:linux

  ├── project
  │   ├── include
  │   │   ├── module1
  │   │   │   └── submod1
  │   │   ├── module2
  │   │   └── module3
  │   └── src
  │       ├── module1
  │       ├── module2
  │       └── module3
View Code

  这是我用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
View Code

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);
View Code

  每次我都要加入__FILE__、__LINE__ 比较啰嗦, 使用宏更加方便:windows

  #define THROW_EXCEPTION(error) \
      throw new Exception(__FILE__, __LINE__, error)
View Code

  这里不能用函数来替代的,要是那样,_FILE__, _LINE_就显示的是所用函数的文件和行,形成异常的错误提示有误设计模式

3.1.2 解决系统的跨平台问题。

1 #ifdef _WIN32
2      xxxx
3 #elif _LINUX
4      xxxx
5 6 #else
7      xxxx
8  #endif
View Code

  Poco库是一个优秀的C++库,跨平台。其结构也很清晰,能够去了解,里面有不少这样设计的。数组

3.1.3 定义静态的数组

  int a[10],可能a的数组长度常常发生变化,并且咱们常用10这个常量。咱们能够这样作 :网络

  #define LEN 10
  int a[LEN];
View Code

   当咱们之后要改变长度时,咱们只须要改变LEN这个宏的值就好了,防止大量的替换。

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()
View Code

  以上使用##宏来链接类名,根据名字定义一个以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;
}
View Code

  工厂模式能够根据类名来取出对象来,我以为已经够用了。虽然实际上没有根据类名来自由的建立类,你还得手动的去建立各个类,咱们只是在作存和取的工做。工厂模式通常被用来总领整个框架,就像是写做文的提纲似的。 使用宏不够好,同时也可能形成代码难以阅读; 应当尽可能避免宏。

相关文章
相关标签/搜索