zoukankan      html  css  js  c++  java
  • 代码中的软件工程

    编译与调试环境

    在 Windows 下用 GNU ToolChain 真的伤不起。课程 ppt 里的链接下不了,搞了一大堆无用操作,还是不行。最后(全局)科*上网用 MinGW.org 的 mingw-get 下载成功。lab5.2 有了 Makefile 之后,要 link 到 pthread,所以还要下。按理说装好 gcc,make 和 pthread 应该就好了。然而这个下载器,直接连接的话除了 gcc/g++ 都下不了,也没有搜索功能,找个包找到海枯石烂。(然后 Makefile 里也没给 $(CC_PTHREAD_FLAGS)main() 也没有返回值,宏 debug("xxxx") 也不知道在干嘛......)

    MinGW Installer 要装的包如下

    MinGW Installer 要装的包

    MinGW Installer pthread

    环境变量设置

    路径里的 msys1.0in 包含了 make 在内的一票命令行工具,之前下过 GNU Make 貌似也可以。

    命令行编译每个 lab 里的代码的话,大概格式如下:

    > gcc menu.c linktable.c -o menu.exe -lpthread
    

    调试的话,在 vscode 可以选择 gdb 来调试。

    vscode

    代码成长轨迹

    这个命令行菜单小项目演示了一个代码不断成长的过程。各个版本都根据软件开发中的一些原则加入新的东西。

    版本 内容变化 涉及
    lab1 +hello.c +menu.c 运行测试、伪代码
    lab2 -hello.c 源文件信息头、主函数框架
    lab3.1 命令链表遍历代替分支语句
    lab3.2 隐藏命令链表的遍历操作
    lab3.3 +linklist.h +linklist.c 将命令链表操作独立成另一个模块
    lab4 -linklist.h -linklist.c +linktable.h +linktable.c +test.c +testlinktable.c 把链表与命令分离、动态创建命令、可重入函数与线程安全、模块测试
    lab5.1 -test.c call-back 函数
    lab5.2 -testlinktable.c +Makefile 增加 Mackefile
    lab7.1 +test.c +menu.h 把菜单独立成模块
    lab7.2 +readme.txt 项目说明

    代码分析与感悟

    模块化与接口

    软件工程这件事,说白了就是为了人。计算机科学和自然科学不同,它是个人造的东西。人干什么事情都是有目的,人发明的技术都是为了解决问题。为什么要有模块化?因为我没必要重新写以前写过的东西,因为我想用别人写好的我想要的东西。要重用代码,这个代码如果乱成一坨,那我还不如写个新的。所以代码要有规范才能好理解、要容易往里加东西但又不容易被破坏原本的结构……用正式的话总结出来就变成一条条的原则写在教科书上,再也没人看得懂了。这很矛盾。前人总结的经验没有经验你看不懂,你看不懂硬要用的话,就会过度设计;你自己遇到问题之后,你再去看这些,除了发出点“噢,我也跳过这个坑,你们还给他起了个名字”之类的感慨,也没啥了。

    接口和抽象紧密相连。计算机本身就是从电路一层层抽象上来的,各个层次之间都要定义好接口,这样才能接得上。作为上层软件的程序员,不可能理解完所有的细节才去编程,否则日子没法过了。所以我不关心这一层层的黑盒里具体干了什么,我只关心他们提供的功能,而这些功能通过接口获取。代码里,把 linktable 变成模块以后,我只需要调用其接口函数,就能用到他的功能了。

    那可重用的接口该如何设计?应该脱离依赖,低耦合。打个比方,lab5.1 的 linktable.c 的 SearchLinkTableNode 函数,他的搜索命中判断条件,为什么不硬编码到 if 里,而是给一个谓词参数传进来?因为要脱离依赖。不同的人用链表查找的事情不同,你都直接规定了查找条件,谁还能重用你这个模块?

    tLinkTableNode * SearchLinkTableNode(tLinkTable *pLinkTable, int Conditon(tLinkTableNode * pNode))
    {
        if(pLinkTable == NULL || Conditon == NULL)
        {
            return NULL;
        }
        tLinkTableNode * pNode = pLinkTable->pHead;
        while(pNode != pLinkTable->pTail)
        {    
            if(Conditon(pNode) == SUCCESS)
            {
                return pNode;				    
            }
            pNode = pNode->pNext;
        }
        return NULL;
    }
    

    再看 lab5.2 的 linktable.c 里的 SearchLinkTableNode,增加了一个参数。而 call-back 函数也增加了这个参数。这里的主要目的是,把 SearchCondition 里的判断参数 cmd 独立出来(他本来是全局的)。

    tLinkTableNode * SearchLinkTableNode(tLinkTable *pLinkTable, int Conditon(tLinkTableNode * pNode, void * args), void * args)
    {
        if(pLinkTable == NULL || Conditon == NULL)
        {
            return NULL;
        }
        tLinkTableNode * pNode = pLinkTable->pHead;
        while(pNode != NULL)
        {    
            if(Conditon(pNode,args) == SUCCESS)
            {
                return pNode;				    
            }
            pNode = pNode->pNext;
        }
        return NULL;
    }
    
    // menu.c
    int SearchCondition(tLinkTableNode * pLinkTableNode, void * args)
    {
        char * cmd = (char*) args;
        tDataNode * pNode = (tDataNode *)pLinkTableNode;
        if(strcmp(pNode->cmd, cmd) == 0)
        {
            return  SUCCESS;  
        }
        return FAILURE;	       
    }
    

    个人感觉,这样并没啥特别的改变。因为这个 Condition 函数, 他的参数是随着 SearchLinkTableNode 函数参数的改变而改变的。比起 lab5.1,SearchLinkTableNode 甚至还知道了关于判断条件更多的信息(Condition 除了 pNode 还需要 args)。要我想个解决方案的话,干脆不要 Condition 函数,把他换成类(如果在支持类机制的语言里)。也许可以这么写:

    class Conditon()
    {
        Condition(char *args) { ... }
        // 需要可变长参数的话?
        // Condition(char *args, int len) { ... }
        
        int evaluate(tLinkTableNode * pNode)
        {
            // 任何判断条件都可以
            if (pNode ... args)
            {
               ...
            }
        }
        
        char* args;
    }
    
    tLinkTableNode * SearchLinkTableNode(tLinkTable *pLinkTable, const Conditon* condition)
    {
        if(pLinkTable == NULL || Conditon == NULL)
        {
            return NULL;
        }
        tLinkTableNode * pNode = pLinkTable->pHead;
        while(pNode != pLinkTable->pTail)
        {    
            if(conditon.evaluate(pNode) == SUCCESS)
            {
                return pNode;				    
            }
            pNode = pNode->pNext;
        }
        return NULL;
    }
    

    继承与多态

    我以前貌似在某设计模式的书里看到过 “不要 if-else 要类” 的说法。如下代码片段所示,通过给所有的命令套进一个 Node 里,Node 里封装一个统一的函数指针 int (*handler)(),这样就可以通过这个统一的指针,调用到不同的函数。再看下下面,LinkTableNode 作为一个链表结点,他逻辑上只需要能指向下一个结点就行了,至于他里面放什么数据,不是他非得关心的问题。所以把他作为父类,不同的结点继承他。这些不同的结点作为子类,放不同的具体数据。

    // lab2/menu.c
     while(1)
        {
            scanf("%s", cmd);
            if(strcmp(cmd, "help") == 0)
            {
                printf("This is help cmd!
    ");
            }
            else if(strcmp(cmd, "quit") == 0)
            {
                exit(0);
            }
            else
            {
                printf("Wrong cmd!
    ");
            }
        }
    
    // lab3.1/menu.c
        while(1)
        {
            char cmd[CMD_MAX_LEN];
            printf("Input a cmd number > ");
            scanf("%s", cmd);
            tDataNode *p = head;
            while(p != NULL)
            {
                if(strcmp(p->cmd, cmd) == 0)
                {
                    printf("%s - %s
    ", p->cmd, p->desc);
                    if(p->handler != NULL)
                    {
                        p->handler();
                    }
                    break;
                }
                p = p->next;
            }
            if(p == NULL) 
            {
                printf("This is a wrong cmd!
     ");
            }
        }
    
    // lab4
    // linktable.c
    typedef struct LinkTableNode
    {
        struct LinkTableNode * pNext;
    }tLinkTableNode
    
    // menu.c
    typedef struct DataNode
    {
        tLinkTableNode * pNext;
        char*   cmd;
        char*   desc;
        int     (*handler)();
    } tDataNode
    

    可重入函数与线程安全

    若一个程序或子程序可以“在任意时刻被中断然后操作系统调度执行另外一段代码,这段代码又调用了该子程序不会出错”,则称其为可重入的(reentrant)。可重入和线程安全密切相关。可重入函数可以由多于一个任务并发使用,而不必担心数据错误。相反,不可重入函数不能由超过一个任务所共享,除非能确保函数的互斥(或者使用信号量,或者在代码的关键部分禁用中断)。操作系统说,信号量用来保护临界资源,而临界资源是一段可共享的代码。在 linktable.c 里可以看到很多类似的代码:

           
            pthread_mutex_lock(&(pLinkTable->mutex));
            pLinkTable->pHead = pLinkTable->pHead->pNext;
            pLinkTable->SumOfNode -= 1 ;
            pthread_mutex_unlock(&(pLinkTable->mutex));
    
    // 简化一下
    
            P(mutex)
            // do something...
            V(mutex)
                
    

    信号量的初始化和销毁:

       
       pthread_mutex_init(&(pLinkTable->mutex), NULL);
       // ...
       pthread_mutex_destroy(&(pLinkTable->mutex))
           
    

    工程化

    当程序员多了起来,代码多了起来,工程复杂了起来,就必须要使用一定的方法来管理项目了。c/c++ 项目的构建系统,在 Linux / Unix 下有 make,Windows 下 MSVC 的 proj 和 sln,跨平台的 CMake。这些工具是通过编写一些规定好语法的配置文件,来指导编译链接甚至安装的过程(说白了就是把一行行敲的命令和编译链接选项,写进了文件里)。项目里展示的 make 工具的 Makefile :

    #
    # Makefile for Menu Program
    #
    
    CC_PTHREAD_FLAGS			 = -lpthread
    CC_FLAGS                     = -c 
    CC_OUTPUT_FLAGS				 = -o
    CC                           = gcc
    RM                           = rm
    RM_FLAGS                     = -f
    
    TARGET  =   test
    OBJS    =   linktable.o  menu.o test.o
    
    all:	$(OBJS)
    	$(CC) $(CC_OUTPUT_FLAGS) $(TARGET) $(OBJS) $(CC_PTHREAD_FLAGS)
    	
    # CC_PTHREAD_FLAGS 不要忘记加进来
    
    .c.o:
    	$(CC) $(CC_FLAGS) $<
    
    clean:
    	$(RM) $(RM_FLAGS)  $(OBJS) $(TARGET) *.bak
    
    

    Makefile有三个非常有用的变量。分别是(@、)^和$<,代表的意义分别是: (@ 表示目标文件;)^ 表示所有的依赖文件;(< 表示第一个依赖文件。 目标.c.o和)<结合起来是精简写法,表示要生成一个.o目标文件就自动使用对应的.c文件来编译生成。

    项目里展示的工程化的流程还有,开发前先测试环境啦(helloworld)、写完一个模块先写测试试一下啦(testlinktable)、增加 readme 啦……总的来说,还是值得学习的一个小项目。

    参考

    [1] https://gitee.com/mengning997/se/blob/master/README.md#代码中的软件工程

  • 相关阅读:
    Mysql5.7主主互备安装配置
    一个简单有效的kubernetes部署案例
    kubernetes应用部署原理
    在线电路编程 (ICP)
    N76E003系统时钟
    说说UART(转)
    串行通信中 同步通信和异步通信的区别及使用情况(转)
    串行通讯与并行通讯区别
    定时器2及输入捕获
    N76E003之定时器3
  • 原文地址:https://www.cnblogs.com/tandandan/p/13896466.html
Copyright © 2011-2022 走看看