记一次网易云课堂MOOC课程学习经历——《软件工程(C编码实践篇)》

刘东晓 + 原创作品转载请注明出处 + 《软件工程(C编码实践篇)》MOOC课程http://mooc.study.163.com/course/USTC-1000002006

  • 一、对课程的简要理解

正如计算机业内的一个非常经典的等式所言:

程序      =算法+数据结构

软件      =程序+软件工程

软件企业    =软件+商业模式

现代软件企业的成功离不开优秀的软件以及杰出的商业模式,同时,作为企业运营核心的软件亦离不开软件工程的指导。

作为商业软件而言,程序是软件的“内功”,“内功”分算法和数据结构两个方面,它们共同决定了程序运行的效率,但无关乎程序的正确与否。除了程序本身之外,软件工程才真正决定了软件的命运。软件工程是指导人们如何构建和开发软件的科学,是最优的组织软件开发的方法。

总的说来,有了软件工程设计思想的“武装”,以后就为开发规范、成功的软件打下了坚实的基础。

  • 二、课程中的实践

本课程有相对应的实验课程,在实验楼网站中运行虚拟的Linux环境来进行C语言的编程。

以下是实验的心得与体会,共六篇:

实验一:写一个hello world小程序

实验二:命令行菜单小程序V1.0

实验三:内部模块化的命令行菜单小程序V2.0

实验四:用可重用的链表模块来实现命令行菜单小程序V2.5

实验五:用callback增强链表模块来实现命令行菜单小程序V2.8

实验七:将menu设计为可重用的子系统

也欢迎大家到我的代码仓库查看源代码:http://git.shiyanlou.com/dustcenix/shiyanlou_cs122

  • 三、对代码的简要分析

在这个十分简单的“菜单”功能C程序的开发过程中,将会逐渐重现下列软件工程开发思想对软件开发的指导:

  1. 模块化
  2. 接口
  3. 信息隐藏
  4. 增量开发
  5. 抽象
  6. 代码重用

在开始的开始,还有一个十分重要的事情,那就是程序的代码风格。

什么是代码风格?什么是好的代码风格?

通常来说,代码风格就是,缩进、命名、注释等代码编排的风格规范。

好的代码风格的原则是:简明、易读、无二义性。

对于编译器来说,代码风格确实无关紧要,甚至对于一些追求极限性能的程序来说,有时候,考虑代码风格问题甚至会带来程序性能的下降(代码难以理解)。

但是,在实际开发的绝大多数场合,开发者所遇到的性能瓶颈问题,都基本不会是依靠打乱代码风格来解决的。

正相反,良好的代码风格,使程序代码简洁清晰,容易维护,也是开发人员之间的写作具有更高的效率。

(1)最原始的代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
int main()
{
    char command[128];
    time_t t;
    char Name[64]="NULL";
    while(1)
    {
        printf("command>>");
        scanf("%s",command);
        if(strcmp(command,"help") == 0)
        {
            printf("commandlist:help,info,echo,exit,time,setName,getName,clearName\n");
        }
        else if(strcmp(command,"info") == 0)
        {
            printf("author:Liu Dongxiao Version:1.0\n");
        }
        else if(strcmp(command,"echo") == 0)
        {
            scanf("%s",command);
            printf("%s\n",command);
        }
        else if(strcmp(command,"exit") == 0)
        {
            exit(0);
        }
        else if(strcmp(command,"time") == 0)
        {
            printf("Now:%s",ctime(&t));
        }
        else if(strcmp(command,"setName") == 0)
        {
            printf("Please insert your name!\n");
            scanf("%s",Name);
        }
        else if(strcmp(command,"getName") == 0)
        {
            printf("%s\n",Name);
        }
        else if(strcmp(command,"clearName") == 0)
        {
            strcpy(Name,"NULL");
        }
        else printf("Wrong Command\n");
    }
    return 0;
}

程序十分简单!学过C语言的都能看懂!  

(2)引入链表,实现代码的业务逻辑和数据存储之间的分离

前文的代码十分简单,同时也存在严重的不足,就是一点用也没有。

我们的目的是写一个可重用的菜单程序,上面那个连边也摸不着。

于是,我们引入了一种数据结构:链表

  • Linklist.h
typedef struct dataNode
{
    char *cmd;
    char *desc;
    int (*handler)();
    struct dataNode *next;
}tDataNode;
tDataNode * findCMD(tDataNode *head,char *cmd);
void showAllCMD(tDataNode *head);

具体实现请参见代码库。

有了链表,再对源程序改造一番!

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "Linklist.h"
#define CMD_MAX_LEN 128
#define DESC_LEN    1024
#define CMD_NUM     10
static time_t t;
void help();
void info();
void echo();
void Exit();
void Time();
static tDataNode menu[]=
{
        {"help","List all command in this program",help,&menu[1]},
        {"info","Show information",info,&menu[2]},
        {"echo","Repeat your input",echo,&menu[3]},
        {"exit","Exit this program",Exit,&menu[4]},
        {"time","Show time now",Time,NULL},
};
int main()
{
    char cmd[CMD_MAX_LEN];
    tDataNode *command;
    printf("Program is running\n");
    while (1)
    {
        printf("Command>>");
        scanf("%s",cmd);
        command=findCMD(menu,cmd);
        if(command == NULL)
        {
            printf("Command Not found\n");
        } else if(command->handler != NULL)
        {
            command->handler();
        }
    }
}
void help()
{
    showAllCMD(menu);
}
void info()
{
    printf("Author:Liu Dongxiao\nProgram Version:1.0\n");
}
void echo()
{
    char command[CMD_MAX_LEN];
    scanf("%s",command);
    printf("%s\n",command);
}
void Exit()
{
    printf("Program exited\n");
    exit(0);
}
void Time()
{
    printf("Now:%s",ctime(&t));
}

这样就将功能与存储分离了开来。同时,也应用了模块化的思想。

(3)改造链表,提升重用性

(2)中的链表很不完善,而且与源程序的耦合性也很强,继续改造之

  • LinkList.h
typedef struct LinkListNode
{
    struct LinkListNode * pNext;
}tLinkListNode;
typedef struct LinkList
{
    tLinkListNode *pHead;
    tLinkListNode *pTail;
    int         SumOfNode;
}tLinkList;
tLinkList * CreateLinkList();
int DeleteLinkList(tLinkList *pLinkList);
int AddLinkListNode(tLinkList *pLinkList,tLinkListNode * pNode);
int DelLinkListNode(tLinkList *pLinkList,tLinkListNode * pNode);
tLinkListNode * GetLinkListHead(tLinkList *pLinkList);
tLinkListNode * GetNextLinkListNode(tLinkList *pLinkList,tLinkListNode * pNode);
  • menu.c
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include "LinkList.h"
#define CMD_MAX_LEN 128
#define DESC_LEN    1024
#define CMD_NUM     10
void help();
void info();
void echo();
void Exit();
void Time();
typedef struct DataNode
{
    tLinkListNode *pNext;
    char* cmd;
    char* desc;
    void   (*handler)();
    struct DataNode *next;
}tDataNode;
tDataNode *FindCmd(tLinkList *head, char *cmd)
{
    tDataNode* pNode=(tDataNode*)GetLinkListHead(head);
    while(pNode !=NULL)
    {
        if(!strcmp(pNode->cmd,cmd))
        {
            return pNode;
        }
        pNode=(tDataNode*)GetNextLinkListNode(head,(tLinkListNode*)pNode);
    }
    return NULL;
}
int showAllCMD(tLinkList* head)
{
    tDataNode *pNode=(tDataNode*)GetLinkListHead(head);
    while(pNode !=NULL)
    {
        printf("%s - %s\n",pNode->cmd,pNode->desc);
        pNode =(tDataNode*)GetNextLinkListNode(head,(tLinkListNode*)pNode);
    }
    return 0;
}
int InitMenuData(tLinkList **ppLinkList)
{
    *ppLinkList=CreateLinkList();
    tDataNode* pNode=(tDataNode*)malloc(sizeof(tDataNode));
    pNode->cmd="help";
    pNode->desc="List all command in this program";
    pNode->handler=help;
    AddLinkListNode(*ppLinkList,(tLinkListNode *)pNode);
    pNode=(tDataNode*)malloc(sizeof(tDataNode));
    pNode->cmd="info";
    pNode->desc="Show information";
    pNode->handler=info;
    AddLinkListNode(*ppLinkList,(tLinkListNode *)pNode);
    pNode=(tDataNode*)malloc(sizeof(tDataNode));
    pNode->cmd="echo";
    pNode->desc="Repeat your input";
    pNode->handler=echo;
    AddLinkListNode(*ppLinkList,(tLinkListNode *)pNode);
    pNode=(tDataNode*)malloc(sizeof(tDataNode));
    pNode->cmd="exit";
    pNode->desc="Exit this program";
    pNode->handler=Exit;
    AddLinkListNode(*ppLinkList,(tLinkListNode *)pNode);
    pNode=(tDataNode*)malloc(sizeof(tDataNode));
    pNode->cmd="time";
    pNode->desc="Show time now";
    pNode->handler=Time;
    AddLinkListNode(*ppLinkList,(tLinkListNode *)pNode);
    return 0;
}
tLinkList* head=NULL;
int main()
{
    char cmd[CMD_MAX_LEN];
    InitMenuData(&head);
    tLinkListNode *command;
    printf("Program is running\n");
    while (1)
    {
        printf("Command>>");
        scanf("%s",cmd);
        tDataNode *p = FindCmd(head,cmd);
        if(p == NULL)
        {
            printf("Command Not found\n");
        } else if(p->handler!= NULL)
        {
            p->handler();
        }
    }
}
void help()
{
    showAllCMD(head);
}
void info()
{
    printf("Author:Liu Dongxiao\nProgram Version:1.0\n");
}
void echo()
{
    char command[CMD_MAX_LEN];
    scanf("%s",command);
    printf("%s\n",command);
}
void Exit()
{
    printf("Program exited\n");
    exit(0);
}
void Time()
{
    time_t t;
    printf("Now:%s",ctime(&t));
}

通过改造,就能在编码时方便的添加menu项啦!

(4)信息的隐藏与封装以及回调函数

可以看出,之前的代码中,我们将链表的结构体定义在LinkList.h中,而总所周知的是,C语言的库函数头文件中,存放的都只是函数和结构的声明,而不将实际的实现写在头文件中,这样做,可以防止其他人查看代码的内部实现,可以有效的隐藏信息。

  • LinkList.h
typedef struct LinkListNode tLinkListNode;
typedef struct LinkList tLinkList;
tLinkList * CreateLinkList();
int DeleteLinkList(tLinkList *pLinkList);
int AddLinkListNode(tLinkList *pLinkList,tLinkListNode * pNode);
int DelLinkListNode(tLinkList *pLinkList,tLinkListNode * pNode);
tLinkListNode * GetLinkListHead(tLinkList *pLinkList);
tLinkListNode * GetNextLinkListNode(tLinkList *pLinkList,tLinkListNode * pNode);
tLinkListNode *SearchLinkListNode(tLinkList *pLinkList,int Condition(tLinkListNode * pNode, void * args),void *args);

所谓回调,就是调用者调用被调用者,在被调用者执行的过程中,又去执行调用者代码段的过程逻辑。

我们在本程序中引入回调函数,是为了进一步增强链表的功能。

int SearchCondition(tLinkListNode * pNode, void * args){
    return (strcmp(((tDataNode*)pNode)->cmd,(char*)args)?0:1);
}
tDataNode *FindCmd(tLinkList *head, char *cmd)
{
        return (tDataNode*)SearchLinkListNode(head,SearchCondition,cmd);
}
tLinkListNode *SearchLinkListNode(tLinkList *pLinkList,int Condition(tLinkListNode * pNode, void * args),void *args)
{
    if(pLinkList == NULL)
    {
        return NULL;
    }
    tLinkListNode *tmp = pLinkList->pHead;
    while(tmp != NULL)
    {
        if(Condition(tmp,args) == 1)
        {
            return tmp;
        }
        tmp = tmp->pNext;
    }
    return NULL;
}

可以看出,在上述两段代码的过程逻辑中,主函数中的FindCMD调用了LinkList中的SearchLinkListNode,而在SearchLinkListNode中又使用了主函数传递过来的SearchCondition函数,这样可以做到,在主程序中定义了搜索条件,而不是预先定义在LinkList中。

通过一系列的改进,我们编写的程序做到了,在主函数中定义menu项,加入队列,自定义查询条件等一系列功能。

任务接近完成!

(5)最终的改造,接口设计

之前一系列的改造之后,menu功能实现的很顺利,是时候将整个代码封装成模块了。

  • menu.h
int MenuConfig(char *cmd, char *desc, void (*handler)(int argc, char *argv[]));
int ExecuteMenu();

如果我们以后想使用menu模块,就要使用上面这两个接口。第一个用来增加menu项,第二个来启动menu页面。

接口的实现:

  • menu.c
int MenuConfig(char *cmd, char *desc, void (*handler)(int argc, char *argv[]))
{
    if(head == NULL)
    {
        head = CreateLinkList();
        tDataNode *pNode = (tDataNode*) malloc(sizeof(tDataNode));
        pNode->cmd = "help";
        pNode->desc = "List all command in this program";
        pNode->handler = help;
        AddLinkListNode(head,(tLinkListNode *)pNode);
    }
    tDataNode* pNode=(tDataNode*)malloc(sizeof(tDataNode));
    pNode->cmd=cmd;
    pNode->desc=desc;
    pNode->handler=handler;
    AddLinkListNode(head,(tLinkListNode *)pNode);
    return 0;
};
int ExecuteMenu()
{
    int argc = 0;
    char cmd[CMD_MAX_LEN];
    char *argv[CMD_MAX_ARGV_NUM];
    char *command = NULL;
    MenuConfig("info","Show information\n\t‘-author‘ for author information\n\t‘-version‘ for version information",info);
    MenuConfig("exit","Exit this program",Exit);
    MenuConfig("time","Show time now",Time);
    printf("Program is running\n");
    while (1)
    {
        argc = 0;
        command = NULL;
        printf("Command>>");
        command=fgets(cmd,CMD_MAX_LEN,stdin);
        if (command == NULL)
        {
            continue;
        }
        command = strtok(command," ");
        while (command != NULL && argc < CMD_MAX_ARGV_NUM)
        {
            argv[argc] = command;
            argc++;
            command = strtok(NULL," ");
        }
        if(argc == 1)
        {
            int len = strlen(argv[0]);
            *(argv[0] + len -1) = ‘\0‘;
        }
        tDataNode *p = FindCmd(head,argv[0]);
        if(p == NULL)
        {
            printf("Command Not found\n");
        } else if(p->handler!= NULL)
        {
            p->handler(argc,argv);
        }
    }
}

menu.c中原来的其他代码也有删改,详情请参见源码及实验报告。本实现中又新增了一个功能,即实现带参数的menu项。

使用示例:

    MenuConfig("info","Show information\n\t‘-author‘ for author information\n\t‘-version‘ for version information",info);
    MenuConfig("exit","Exit this program",Exit);
    MenuConfig("time","Show time now",Time);
  • test.c
#include "menu.h"
int main() {
    ExecuteMenu();
    return 0;
}

大功告成!

  • 四、结课总结

近两个月的学习已经结束了,本门课程的核心在于对软件开发思想的学习,而不是纠结于具体代码的一城一池,学习本课程的最大收获,就是在一个更高的理论层次上,总览全局,对软件的总体进行把握。只有学会了高屋建瓴,才能开发出高质量的软件。

除此之外,还对本课程抱有一丝遗憾:

(有一次课程作业忘记了互评……得分减半……好可惜……本句话删除)

希望可以在课程的学习中,实际动手做出一些更具有实用价值的软件。

感谢阅读!

时间: 2024-08-26 12:35:15

记一次网易云课堂MOOC课程学习经历——《软件工程(C编码实践篇)》的相关文章

网易云课堂微专业 学习列表

页面制作 课程学习指南 <web前端开发实践>的大作业题目 Photoshop切图 工具.面板.视图 测量.取色 切图 保存 修改.维护 图片优化与合并 Photoshop切图的单元测试(作业) 开发.调试工具 开发调试工具 HTML HTML简介 标签 实体字符 HTML的单元测试(作业) HTML的单元作业(作业) CSS CSS简介 选择器 文本 盒模型 背景 布局 变形 动画 CSS的单元测试 CSS的单元作业 JavaScript程序设计 课程学习指南 <web前端开发实践&g

?《Python自动化测试修炼宝典》线上课程已经成功入驻网易云课堂......

<Python自动化测试修炼宝典>线上课程已经成功入驻网易云课堂...... IT测试老兵利用工作之余,亲自录制的<Python自动化测试修炼宝典>线上课程已经成功入驻网易云课堂,想要提高测试技术的小伙伴可以线上购买课程进行学习. 课程背景 测试人员进阶实战课程.本套课程以作者多年测试实战经验为背景,结合大量测试案例深入浅出讲解目前主流web端.app端自动化测试框架以及使用Python如何从0到1实现接口测试框架的搭建. 课程特色 系统教学+实战案例+开放源码.涵盖Python3

聊一聊【网易云课堂】

(首先说明,这不是广告,知识我个人的一点感受而已) 若干天之前注册了网易云课堂,并填写.邮寄申请讲师的材料. 若干天之后的今天,终于收到通知,我通过了讲师认证,给我名字上加了个大大的"V".以及<petshop4.0源码解读>教程通过了审核,正式发布了.下班之前看了看,竟然已经有6名学习者了,就是不知道他们有没有真的学.有想学的直接去那里看就行了,免费的. 课堂教程的网址是:http://study.163.com/course/introduction/655003.ht

Android 网易云课堂第一周笔记16/5/10

Android 网易云课堂第一周笔记 首先第一周的主要任务回顾,第一是Android环境的搭建和Android studio软件的安装使用.第二是android的hello word项目的构建,其他的项目的功能包括button的功能基本使用,比如页面的跳转,Toast,Intent等等,还有我认为最为重要的是Activity的生命活动的讲解,虽然老师在课堂上只是简单展示一下生命周期的几个内容,但我认为如果想玩好Activity的话,生命周期这一块一定要理解和掌握的,废话不多说直接进入正题. An

网易云课堂-软件工程(C编码实践篇)

网易云课堂-软件工程(C编程实践篇) 识别代码工程质量: 代码风格: 封装接口: 可重用代码: 可重入函数和线程安全: 代码风格的原则:简明.易读.无二义性:代码风格原则在团队交流过程中非常重要.

网易云课堂_C++程序设计入门(上)_第2单元:丹青画松石– EGE图形库_第2节:一个简单的EGE程序

网易云课堂_C++程序设计入门(上)_第2单元:丹青画松石– EGE图形库_第2节:一个简单的EGE程序 #ifndef _GRAPHICS_H_ #define _GRAPHICS_H_ #ifndef __cplusplus #error You must use C++ compiler, or you need filename with '.cpp' suffix #endif #include "ege.h" using namespace ege; #endif #inc

网易云课堂程序设计入门--C语言第七周:指针与字符串学习笔记

====================================== 第七周:指针与字符串学习笔记 网易云课堂 程序设计入门–C语言 指针与字符串 学习笔记 ====================================== 7-1指针初步 7-1-1类型大小与取地址运算 sizeof是一个运算符 给出某个变量货类型在内存中所占据的字节数 sizeof(int); sizeif(i); double变量在内存中占据的空间是int变量的两倍 { int a; a =6; prin

Python实例之抓取网易云课堂搜索数据(post方式json型数据)并保存为TXT

本实例实现了抓取网易云课堂中以'java'为关键字的搜索结果,经详细查看请求的方式为post,请求的结果为JSON数据 具体实现代码如下: import requests import json finalstr = '' #初始化字符串 totlePage = 0 #初始化总页数 test = 0 #初始化数据总条数 url = 'http://study.163.com/p/search/studycourse.json' headers = {'content-type': 'applic

网易云课堂-----Linux内核分析-----期末主观题

王康 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 下面是对8个课题的总结 1. 图解分析汇编代码以及理解计算机是如何工作的 冯诺依曼体系结构的计算机,又叫存储程序计算机,从硬件的角度来看,其工作模型是CPU依次读取内存中的指令来完成工作.这节课详细介绍了CPU计算模块.寄存器和内存是如何配合工作的. 2. 基于mykernel的一个简单的时间片轮转多道程序内核代码分析 myk