软件工程 C 实践二 课程总结笔记

作者 Marlous 日期 2019-02-17
软件工程 C 实践二 课程总结笔记

本博文内容是课程最后的总结。

参考:
1、 软件工程(C语言实践篇)学习心得总结
2、 软件工程(C编码实践篇)学习总结

一 软件设计方法论(原则)

  1. 概述:
  • 模块化
  • 接口
  • 信息隐藏
  • 增量开发
  • 抽象
  • 一般化
  1. 模块化:
    将一个系统拆分成多个模块,要求低耦合、高内聚,减少出错提高可维护性。

  2. 接口:
    信息隐藏通过接口的定义来完成。信息隐藏越多,耦合性越低。

  3. 增量开发:
    以模块化为基础;明确的接口定义为增量开发提供可能。
    补充,拆分模块,增量开发时打破依赖关系:
    拆分模块

  4. 抽象:
    同层级放在函数调用前,子层级放在函数调用内部。

二 软件设计具体内容

1 代码设计规范

  • 用控制结构、数据结构来简化代码。
  • 一定要有错误处理。
  • 一个函数或方法、一个系统、子系统、模块、类等只做一件事情。
  • goto 常用来做错误处理(C 语言)。

2 可重用模块的接口设计

  1. 接口定义内容:
  • 函数
  • 参数
  • 返回值
  1. 接口的五个要素:
  • 目的:如函数名。
  • 前置条件:如接口函数调用前已经做好哪些准备工作(插入链表元素前需要先创建链表)。
  • 协议:格式方式。如参数、返回值的类型,指针指向的数据格式。
  • 后置条件:如返回值、printf 函数接口的效果是屏幕显示了字符串。
  • 质量属性:如接口函数执行时间限制在 1 秒内。
  1. 接口的分类:
  • 共享数据或变量名(间接的接口)
  • call-in functions(最常见,函数调用的方式)
  • callback functions(用函数指针作为卧底,调用上层模块的函数)
  • 同步调用接口(阻塞式的函数调用,专门等待要使用的函数)
  • 异步调用接口(调用后继续做自己的事情,直到被调用的函数过来。用信号量、消息队列等方式实现)
  1. 可重用模块的接口设计:
  • 一般方法定义的接口:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
     #ifndef LINKTABLE_H
    #define LINKTABLE_H


    #define FAIL -1
    #define SUCCESS 0


    typedef struct linkTableNode
    {
    struct linkTableNode *pNext;
    }tLinkTableNode;

    typedef struct linkTable
    {
    tLinkTableNode *pHead;
    tLinkTableNode *pTail;
    int sumOfNode;
    }tLinkTable;


    tLinkTable *createLinkTable();

    int deleteLinkTable(tLinkTable *pLinkTable);

    int addLinkTableNode(tLinkTable *pLinkTable,tLinkTableNode *pNode);

    int delLinkTableNode(tLinkTable *pLinkTable,tLinkTableNode *pNode);

    tLinkTableNode *getLinkTableHead(tLinkTable *pLinkTable);

    tLinkTableNode *getNextLinkTableNode(tLinkTable *pLinkTable,tLinkTableNode *pNode);


    #endif
  • 用 callback 方式定义的接口:
    使 Linktable 的查询接口更加通用,隐藏接口信息。将数据的定义放到 linktable.c 文件中。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    #ifndef _LINK_TABLE_H_
    #define _LINK_TABLE_H_


    #include <pthread.h>


    #define SUCCESS 0
    #define FAILURE (-1)


    typedef struct LinkTableNode
    {
    struct LinkTableNode pNext;
    }tLinkTableNode;


    typedef struct LinkTable tLinkTable;


    tLinkTable * CreateLinkTable();

    int DeleteLinkTable(tLinkTable *pLinkTable);

    int AddLinkTableNode(tLinkTable *pLinkTable,tLinkTableNode * pNode);

    int DelLinkTableNode(tLinkTable *pLinkTable,tLinkTableNode * pNode);

    tLinkTableNode * SearchLinkTableNode(tLinkTable *pLinkTable, int Conditon(tLinkTableNode * pNode, void * args),void *args);

    tLinkTableNode * GetLinkTableHead(tLinkTable *pLinkTable);

    tLinkTableNode * GetNextLinkTableNode(tLinkTable *pLinkTable,tLinkTableNode * pNode);


    #endif /* _LINK_TABLE_H_ */
  1. 接口定义内容:
    补充:不同部分隔两行区分。参考 Nginx 头文件
  • 版权等信息
  • #ifndef、#define
  • 包含的头文件
  • 宏定义
  • 结构体定义
  • 变量定义
  • 函数声明

3 函数的可重入性、线程安全

4 子系统可重用设计

给整个命令行菜单程序定义一套调用接口。参考代码

  1. 概念:
  • 对于菜单程序,不会像链表模块那样很多地方要用到,需要考虑通用性、线程安全。不需要太具体,也不要太通用。够用就好。
  • MenuConfig 函数相当于初始化一个链表,向内添加结点等。
  • ExecuteMenu 函数就是菜单程序的主程序。
  1. 子系统接口示例:

    1
    2
    3
    4
    5
    .h 文件:

    int MenuConfig(char *cmd, char *desc, int (*handler)());

    int ExcuteMenu();
  2. 调用子系统示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    .c 文件:

    int main(int argc, char **argv)
    {
    MenuConfig("version", "xxx v1.0(Menu program v3.0.0 inside)", NULL);
    MenuConfig("argtest", "test arg option", argtest);
    MenuConfig("quit", "quit from xxx", Quit);
    ExcuteMenu();
    return 0;
    }

5 代码背后的设计思想