• 所遇不良设计(二)


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

    1 文件的层叠

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

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

      这是我用tree打印的工程文件目录,在这个工程.h和.cpp文件用include和src来分开。如果你要开发一个系统模块,你需要在include的dir1、dir2、dir3和src的dir1、dir2、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++编程规范,网上都有电子文档的。还有就是用脚本语言检测程序定期检查编写的代码,是否有不符合规定编码的规范,这些都需要项目经理去认真的执行的。

    3 宏的臃肿

    3.1 宏的好处

      宏的替换是无与伦比,没有其他可以替代的。我看到宏在好多地方合理有效的利用,其能有效的减少繁琐的重复的代码、解决跨平台系统系统API不兼容的问题(通过系统的宏定义,来判断是什么操作系统,从而来调用相应的系统API,这些事情都在编译的时候就完成了)

    3.1.1 抛出异常

      为了调试代码,我要抛出异常,异常的内容包括文件名、行号、错误码:

      throw new Exception(FILE, LINE, error);
    View Code

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

      #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

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

  • 相关阅读:
    【Mysql+shell】查询结果导出到文件,文件数据导入到数据库
    【mysql】IP地址整数int和varchar的转换
    【JVM】Class结构之常量池
    【Java】Java初始化过程总结
    【转】探索 ConcurrentHashMap 高并发性的实现机制
    【并发编程】使用BlockingQueue实现<多生产者,多消费者>
    【FTP】FTP文件上传下载-支持断点续传
    【SFTP】使用Jsch实现Sftp文件下载-支持断点续传和进程监控
    【大数据】基本概念--01(转)
    【Java】Java环境变量配置
  • 原文地址:https://www.cnblogs.com/wind-qu/p/3612665.html
Copyright © 2020-2023  润新知