系统模块化设计

将系统中有关联的部分组合在一起,构成具有特定功能的子系统。划分

模块的内部组成具有较强的耦合性,模块本身具有一定的通用性。

不同的模块间可以进行相互组合与依赖,进而构成不同的产品。

模块化设计:

结构化设计    面向对象设计

示例:

Module Demo
interface123  int function();   class0  class1 class2

接口比模块先实现,用来各个模块交互。

各个模块间需要相互依赖,进而完成产品功能。

根据依赖关系能够将模块分为不同的层。

模块间的分层:硬件层,系统层,平台层,框架层,应用层

平台层  heap module timer mudule memory module

框架层 fsm module communication module

应用层 session(汇画) module privilege module  user module

模块的分级(更细的设计粒度):

同一层中的模块根据依赖关系能够继续分级

平台层:两级

框架层

应用层:两级

分层和分级的意义:

模块间的依赖关系决定了初始化的前后顺序。

被依赖的模块必须先初始化(底层先于上层初始化)

如:硬件层模块先于系统层模块初始化

框架层模块先于应用层模块初始化。

系统架构示例一:图

依赖关系

系统内核--平台层(android runtime)--框架层--应用层

示例2

硬件层(显示器,键盘,收银箱,POS机,打印机)--操作系统(驱动)--平台层(java虚拟机)--框架层(数据库模块,网络通信模块,设备控制模块)--应用层(用户界面,扩展模块)

设计时需要思考的问题:

如何在代码中定义模块?如何定义的层级关系(依赖关系)?如何确定模块的初始化顺序?

模块的定义:

typedef enum{
    MODULE_MODULE, //for module management
    MODULE_INTERRUPT, //for interrupt management
    MODULE_DEVICE,  //for device management
...}module_t;

模块的描述及组织方式

type struct{
    dll_node_t node_;  //链表结点
    const char *p_name_;  //模块名
    module_callback_t callback_;    //模块回调函数:接收系统消息
    bool is_registered_; //注册标记
}module_init_t;

注册到数据结构中:

level0-->module1->module2
level1-->module3->module4->module5
level2->module6

第一级依赖于第二级.....

层级关系的定义:每一层分为七级

typename enum{
    LEVEL_FIRST,
    //...
    //for platform layer
    PLATFORM_LEVEL0,
    //...
    PLATFORM_LEVEL7,
    //for framework layer
    FRAMEWORK_LEVEL0,
    //...
    FRAMEWORK_LEVEL7
    ...
    LEVEL_COUNT,
    LEVEL_LAST=(LEVEL_COUNT-1)
}init_level_t;

状态设计: 用回调函数改状态

-system_up()->initialzing->up--system_down()->down->destroying
typedef enum{
    STARE_INITIALIZING,
    STATE_UP,
    STATE_DOWN,    
    STATE_DESTROYING
}system_state_t;

模块初始化:

modele1--module_register()-->module manager
modele2--module_register()-->module manager
modele2<---callback()--module manager回调

模块的销毁:

modele2--module_register()-->module manager(system_down())

最后初始化的最先终止,最先初始化的最后终止。

实现要点:

每一个模块对应一个ID和一个结构体变量(module_init_t)

模块需要注册后才能被初始化(module_register)

模块提供了一个回调函数(module_callback_t)用于接收事件。

所有的模块根据层级关系组织与不同链表中:同一个链表中的模块没有依赖关系,整个系统从底层(最上层)的模块开始进行初始化(销毁)。

系统功能模块图和总体架构图的区别 系统功能模块的设计_错误码

启动-模块注册-发送消息-system_down()

system up:如何在代码中反映层级关系:将模块对应的变量放到相应的链表中去,然后遍历的时候从上到下遍历链表中的结点,遍历之后,对结点中的回调函数进行调用,回调函数收到系统初始化以及启动的消息后,就进行具体的模块初始化工作了。

system down:返过来遍历:

模块设计是要遵循强内聚弱耦合的原则。

模块之间可以相互依赖,并进行模块层级的划分。

模块管理是为了系统中各个模块的有序启动和停。

模块设计时需要考虑资源的分配和释放问题。

模块代码优化22-1.

22:异常设计

在开发中,不可避免的需要进行异常处理。

函数调用时的异常:并不是函数设计上的逻辑错误。而是可预见的非正常功能的执行分支。

异常处理的意义:

软件开发过程中,大多数情况下都在处理异常情况。

异常不是错误,但是可能导致程序无法正常执行。

异常处理直接决定软件产品的鲁棒性和稳定性。

项目中的异常设计:

实现表达异常的通用方法(异常如何表示?)

设定异常报告的方法(发生什么异常,如何知道)

指定统一处理异常的原则(怎么处理异常)

在c语言中通过错误码对异常进行表示:

优势:错误码的 定义简单,使用方便。

劣势:同一个错误码可能表示不同的含义

异常表示的通用设计方法:采用整数分区域的方式对异常进行表示。

32位整数:最高为标识,1为错误,15位模块标识,16位错误标识。

注意:一般而言,错误码最高位为1.即:所有错误码为负数。

错误码类型操作:

ERROR_MARK  //0x8000000 错误码最高位为1
ERROR_BEGIN(_module_id) //根据模块ID计算起始错误号
ERROR_T(module_error) //根据错误号生成错误号
MODULE_ERROR(_error_t)// 获取错误码中的模块内错误号(底16位)
MODULE_ID(_error_t) //获取错误码中的模块ID(高15位)

代码:

异常的报告:

通常情况下,系统日志的是异常报告的主要方式。

注意:异常报告并不是异常处理。

异常报告用于记录异常的发生,异常处理用于阻止导致的程序奔溃。

异常报告与异常处理示例(整体框架)

异常报告与异常处理示例(异常报告)

异常报告与异常处理示例(异常处理)

工程开发时的一些建议:

尽量在异常发生的地方报告异常:有助于事后找到发生时的函数调用路径。

尽量在上层函数中统一处理异常:集中处理异常有助于提高代码的维护性。

小结:

c项目中通常采用整数分区域的方式对异常进行表示。

异常号包含了模块信息以及模块相关的具体异常信息。

通常情况下,系统日志是异常报告的主要方式

尽量就近报告异常,尽量统一处理异常。

23、异常中

问题:当前的设计中,直接输出异常错误码的方式易于问题的定位吗?是否有更好的异常输出方式?

使用c++独立程序,通过c++程序自动生成我们想要的c代码,c++独立程序编译过后就可以将定义异常的头文件作为输入产生输出,输出是我们想要的c代码,而这个c代码作用是异常错误码到对应的异常名字之间的映射。方便日志的打印。

期望的异常输出方式:

直接将错误码所对应的枚举常量名输出:

枚举常量名是精心设计的有意义的名字。

枚举常量名包含了模块名和模块内错误名。

error_t err=ERROR_T(ERROR_TIMER_ALLOC_NOTIMER);
LOG(sub_func,err);
=>sub_func[main.c:41]=>8007000A
=>ERROR_TIMER_ALLOC_NOTIMER

思路:

简历枚举常量名到字符串数组的映射。

通过错误号查找字符串数组得到对应的名字。

维护性上的考虑:

每个模块有自己的异常枚举定义(数量不同)

异常的类型无法一次性设计完善(后期可能增加后减少)

当异常类型改动时,必须正确改动对应的字符串数组。

自动产生?如何产生?

解决方案设计:

1、每个模块的异常枚举定义于独立的文件

2、异常枚举的定义遵循固有的编码规范

3、编写独立程序处理异常枚举定义文件,生成对应的字符串数组。

4、当项目中出现异常时:根据错误码中的模块名定位字符串数组。根据错误码中的内部错误号定位字符串。

解决方案设计:

errtmr.h errheap.h errmod.h err2str.cpp-->err2str--out->errstr.def

错误码到枚举常量名的映射:

g_errsr_array(包含指针)--module_id-->g_error_MODULE_TIMER--error_id-->
"ERROR_TIMER_ALLOC_INVCB"
"ERROR_TIMER_ALLOC_NOTIMER"
"ERROR_TIMER_FREE_INVHANDLE"

数据结构设计:

static struct errstr_t{
    int available_;  (标记是否存在错误名数组)
    int last_error_; (模块内的最后一个错误号)
    const char **error_array_; //错误名数组
}g_errstr_array[MODULE_COUNT];

错误名查找函数的设计:

const char *errstr(error_t _error)
{
    static bool initialized=false;
    module_t module_id=MODULE_ID (_error);
    int error_id=MODULE_ERROR (_error);
    if(!initialized){
        errstr_init();  //将自动生成的错误名数组映射到对应的模块ID
        initialized=true;
    }
    //...
    return g_errstr_array[module_id].error_array_[error_id];
}

makefile:

.PHONY : all clean rebuild
ERR_INC := errheap.h errtmr.h
APP := app.out
APP_SRC := $(wildcard *.c)
APP_INC := $(wildcard *.h)
ERR2STR := err2str
ERR2STR_SRC := err2str.cpp
ERRSTR_DEF := errstr.def
all : $(APP)
 @echo "BUILD SUCCESS => $^"
$(APP) : $(APP_SRC) $(APP_INC) $(ERRSTR_DEF)
 gcc -o $@ $(filter %.c, $^)
$(ERRSTR_DEF) : $(ERR2STR) $(ERR_INC)
 ./$(ERR2STR) $(ERR_INC) > $@
$(ERR2STR) : $(ERR2STR_SRC)
 g++ -o $@ $^
clean :
 rm -fr $(APP) $(ERR2STR) $(ERRSTR_DEF) rebuild : clean all

小结:异常输出时,期望的是可读性强的描述,而不是错误码。

使用独立程序自动建立错误码到字符信息的映射。

代码自动生成技术建立于项目中的代码规范之上。

当项目中有固定规则编写的代码时,可以考虑代码自动生成。

24、异常处理下

使用c++独立程序,通过c++程序自动生成我们想要的c代码,c++独立程序编译过后就可以将定义异常的头文件作为输入产生输出,输出是我们想要的c代码,而这个c代码作用是异常错误码到对应的异常名字之间的映射。方便日志的打印。

自动代码生成需要考虑的问题:

输入:如果是数据(非代码)->数据是否被格式化组织(XML,JSon)

如果是代码->代码是否遵循严格的编码规范(有规律可寻)

输出:

是否需要基于代码模板完成?

是否需要动态“组装”生成代码?

问题分析:

常量名所在行均以ERROR_开头--》第一个常量名所在行包含模块ID->常量名在目标数组中的下标可计算-》MODULE_ERROR(常量名)

处理流程设计:

开始->输入文件是否合法->文件处理结束?->寻找枚举常量->生成错误码到常量名的映射代码->生成模块错误名初始化函数->生成全局错误名初始化函数->结束

分析err2str.cpp

小结:代码自动生成--静态->基于格式化参数(输入为数据)->数据描述方式 / 代码模板

          代码自动生成--动态-->基于编码规范(输入为代码)

25、项目中需要思考的问题:

开发流程的定义

平台与框架的选择

目录结构及源码管理

嵌入式产品的开发效率

什么是软件开发流程?

通过一系列步骤保证软件产品的顺利完成。软件产品在生命期内的管理方法学。

软件开发流程的本质:开发流程与具体技术无关,开发流程是开发团队必须遵守的规则。

瀑布模型?  不是qt中讲的吗?

增量模型

螺旋模型

敏捷模型

2平台与框架的选择:

软件开发平台:开发平台是位于操作系统之上的软件层。开发平台提供更多模块化的功能,简化(加速)软件开发

软件开发框架:开发框架是位于开发平台之上的软件层,开发框架是为特定应用所设计的更抽象的软件模块。

平台与框架的示例->spring/java

0S-java-sprint-web应用程序

平台与框架示例2->qt

os--qt core(平台层)--状态机框架,模型视图框架,动画框架--框架层--桌面应用程序

3目录结构及源码管理

项目中每个模块的代码用一个文件夹进行管理。

文件夹由inc,src,makefile构成

项目中每个模块的对外函数声明统一放置同一目录中。

common.h  xxxfunc.h

common  对外 module 具体功能  main 产品入口

目录设计的意义:

书架功能:反映项目中代码的层次感和模块化。

意识向导:引导对于新增文件功能,命名以及位置的思考。

增强维护性:加快开发人员对于项目整体架构的理解。

4嵌入式产品的开发效率

常规开发方式:

代码编写-代码修改-交叉编译-程序烧写-功能验证-结束

存在的问题:

开发工程师必须人手一台设备(项目早期可能无法满足)

每次代码改动必须到设备进行验证(效率低下)

反复多次烧写可能导致设备损坏(不稳定的早期设备)

嵌入式开发的调试问题:

需要基于额外硬件(JTAG)链接设备进行断点调试。

常规日志只能写于文件中,无法实时查看。

几乎无法进行现场调试(客户环境调试)

嵌入式基础设施的建设:

架构设计时,模块之间遵循强内聚,若耦合原则:模块能够基于pc环境编译,并进行单元测试。

开发pc环境中的设备模拟器:产品代码能够在pc环境完整编译并运行与pc环境。

开发产品中可实时查看输出的日志系统:设备运行日志输出可以实时传输到pc环境。