首页 > 代码库 > 记一次网易云课堂MOOC课程学习经历——《软件工程(C编码实践篇)》
记一次网易云课堂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)最原始的代码
#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)引入链表,实现代码的业务逻辑和数据存储之间的分离
前文的代码十分简单,同时也存在严重的不足,就是一点用也没有。
我们的目的是写一个可重用的菜单程序,上面那个连边也摸不着。
于是,我们引入了一种数据结构:链表
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)中的链表很不完善,而且与源程序的耦合性也很强,继续改造之
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);
#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语言的库函数头文件中,存放的都只是函数和结构的声明,而不将实际的实现写在头文件中,这样做,可以防止其他人查看代码的内部实现,可以有效的隐藏信息。
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功能实现的很顺利,是时候将整个代码封装成模块了。
int MenuConfig(char *cmd, char *desc, void (*handler)(int argc, char *argv[])); int ExecuteMenu();
如果我们以后想使用menu模块,就要使用上面这两个接口。第一个用来增加menu项,第二个来启动menu页面。
接口的实现:
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);
#include "menu.h" int main() { ExecuteMenu(); return 0; }
大功告成!
- 四、结课总结
近两个月的学习已经结束了,本门课程的核心在于对软件开发思想的学习,而不是纠结于具体代码的一城一池,学习本课程的最大收获,就是在一个更高的理论层次上,总览全局,对软件的总体进行把握。只有学会了高屋建瓴,才能开发出高质量的软件。
除此之外,还对本课程抱有一丝遗憾:
(有一次课程作业忘记了互评……得分减半……好可惜……本句话删除)
希望可以在课程的学习中,实际动手做出一些更具有实用价值的软件。
感谢阅读!
记一次网易云课堂MOOC课程学习经历——《软件工程(C编码实践篇)》