形散神聚-适用于非计算机专业学术团队的架构、工具链方案设计

2018-02-27 10:53:53来源:http://blog.csdn.net/goldenhawking/article/details/79338956作者:CSDN博客人点击

分享
1.非计算机专业学术团队的业务特点

非计算机专业学术团队是一个泛泛的概念,即没有受过专业化的软件工程化训练,又精通某个非计算机行业知识的学术团队。诸如需要频繁自行开发小工具进行算法验证的高校教研团队,以及深入某一领域(化工、机械、通信、电子等)进行数据分析,需要长期从事非消费类工具软件开发的工程师团队。他们具备这些特点:


- 计算机专业人才引进少,且经验不足;


- 专业能力突出,开发水平停留在毕业论文水平;


- 对高校而言,还存在人员流动很快等问题。


笔者曾作为行业学术团队的一员,尝试为团队内部构造一个能被广泛接受的生产力工具链。目的是尽可能多地保留各届毕业生们实现的算法逻辑,使其能够用于后续的合作项目开发。经过了十几年的尝试,先后尝试了4套方案,均效果不佳。


2 四个失败方案

从这四个失败案例所在时代来看,本身技术先进性都不是很差。在尝试这四个案例之前,教研室的项目都是靠直接嵌入代码的方法来引用现有技术的。


方案一-动态链接库

设计实现于2004年左右。当时,本硕教学、项目开发采用的均为Visual C++ ,按照导师的要求,学生们的所有算法模块需要封装为动态链接库,以便可以很快的嵌入到新的项目中使用。作为对计算机稍微熟悉一点的助教,我和几个同事一起规范了函数名称等规则,便直接试用了。当时我们老师的水平也是停留在工具、架构搅和在一起,糊里糊涂的地步。这个方案试用了2年,发现了很多问题:


1. 要求不够细 。对动态链接库封装技术的掌握各人并不相同。用过MFC的老人们应该记得,VC平台导出DLL就有好多种不同的形式,各自对依赖项的引用也分静态链接、动态链接、动态加载等方式。很多学生被要求重新封装,非常不高兴,抗拒这个规则。


2. 错误难定位 。学生们个性很强,每个人习惯千差万别,代码中BUG千奇百怪。作为教研室,很难找到专业的测试人员,最终两个项目平台运行起来,不是这里报错就是那里报错,且很难定位是谁的库有问题;开发者本人毕业后,根本没有人愿意去碰他的代码。


3. 跨语言集成难 。与某公司合作,该公司使用的是当时最为新颖的.Net技术,使用C#调用不安全的DLL逻辑,出现的内存泄漏(估计是学生在malloc之后没有free)、野指针等问题使得项目几乎失败。


方案二-COM

2006年左右,当时与计算机系合作,要来了两个对COM组件技术很熟悉的博士生。那一阵子被COM、ACE的设计模式洗脑了,总觉得应该在教研室内部实现一套类似计算机系“基于COM组件”的啥啥自动处理系统云云。两位博士很尽责,很快新的框架、接口就完成了。“二进制兼容、语言无关、,这太好了!”




由于实验室最熟悉的还是MFC、C#,于是乎,一场轰轰烈烈的学习运动开始了。但是,热乎劲只持续了几周,大多数人就打了退堂鼓,方案不了了之。原因:


1. 计算机水平差 。让大多数只学过“C语言编程”这门课的非计算机专业学生,猛然一下尝试这种复杂的技术,是很失败的决策。


2. 学习曲线陡峭 。学生们不愿意投入这么大的精力,去把Matlab的逻辑变成C语言的算法。毕竟毕业压力太大。


3. 错误调试难 。人员计算机专业能力差,使得调试更麻烦了。没办法跟踪COM组件内的错误,压力山大。


方案三-Web Service

2008年以后,基于浏览器的Web Service架构流行起来!而且,这种架构好处很多,尤其在系统发布、版本升级方面,有先天优势。实验室尝试把较为稳健的算法通过fcgi接口向Web架构迁移,赶上时代的发展。


然而,事与愿违——我们很快发现,依靠当下的团队无法驾驭Web工具链。学生们把算法移植为后台进程,并不难。但是,作为非消费类的行业应用,无论从硬件架构还是用户群来说,和消费类的框架差异都很大。放弃的原因就一个:


工具链爆炸。举个例子,原本从一块采集芯片上读取的波形,只要C++一种语言,经过有限几步处理,就能直接显示。但是用了Web后,要有后台采集进程(C)、要有处理进程(C)、webService(ASP.net)、网页控件(JS,当时还有Flash或者ActiveX)、网页(牵扯美工)。要么和外包公司合作,要么就引进人才。


在2008年左右,软件外包的成本还是比较高的。而且,作为一个不是很有名的穷学校,这些成本都是非常高昂的。


方案四-Qt插件

有了这些前车之鉴后,教学团队的老师们已经有些皮条了。直到2012年左右,与一个外包公司合作的项目因为采用了C++ Qt 的插件技术,无论是开发过程、调试体验都很棒,实验室才决定引入Qt作为标准开发框架来规范教研室内的工具链。


在这个阶段,很多可重用的代码被直接封装,采用Qt-plugins的方式嵌入到框架中去。



但是,这不代表没有问题,近几年,寻求改变的呼声越来越大了。主要原因:


1. 二进制依赖性太强 。Qt的库不是二进制兼容的。虽然Qt号称跨平台,那都是需要重新编译的。Qt必须要保证1级、2级版本号相同(最近5.8后,似乎C++部分的改造很小,这个结论也过时了)、编译器相同,其插件才是兼容的。因此,很多项目的Qt版本被镶死,落后主版本4-5年。不同届的学生留下的库,版本号参差不齐,问题多多。


2. 不利于个性发展 。2014年以后,很多高中都有较深的计算机教学课程,特别是近几年,熟悉脚本语言的年轻人越来越多。一些原本很复杂的工作,年轻人用python+Numpy等工具,轻而易举就解决了。这些学生没有必要再被要求去为了迎合Qt,学习C++。实验室不是软件公司,强迫别人采用自己厌恶的语言,是非常暴力并令人讨厌的。


3 工具链考虑的因素

有了上面4个失败的教训,我们这些一把年纪的家伙聚起来,好好考虑了一下工具链应该具备的特点。所有难易都是相对只学过一门基础语言、没有任何工程化开发经验的非计算机专业本科生、具备一定计算机软件开发经验,熟悉Matlab的硕士生、博士生而言。


讨论的结果是下面这个表。



尝试过的工具链+架构
学习曲线
错误定位调试难度
开发语言绑定
编译器绑定
学生的抗拒程度


C++动态链接库
★★
★★★★★



COM/OLE
★★★★★
★★★


★★★★★
.Net WebService
★★★
★★


★★★★
Qt插件
★★
★★★
★★★★★
★★★★★
★★★
理想方案?






可是,即使是这个表,都得不到学生们的认可。因为, 难度因人而异 。一些人认为C++很难,但是Web很简单,一些人反之。是时候从头思考了。十几年了,老哥们。


4 理想方案——弃形求神

我们认为,经历了如此多的失败,究其原因,就是总是想选取一种大家都满意的特定语言、特定架构——这可能根本就是不存在的。打个比方,武林中,谁会去规定每个门派必须使用什么兵器?记得扫地僧拿着扫把也很厉害啊!



但是,不规定使用什么兵器,不代表放任不管。兵器这个“形”是外在的,不重要。一个门派内在的应该是“神”。武当、峨眉都用宝剑,但剑法不同。一些门派都是壮汉,则可练习纯阳武功,一些门派本来就是女子,于是选择诸如九阴白骨爪之类的东东。回到实验室这样的团队,只有根据自身师生能力、学科构成来考虑,把什么“先进性”之类的念头彻底抛弃,琢磨适合自己的东西。放回到我们这里,好的理想方案,应该具备下面的特点。


进程切割、语言无关、编译器无关、架构无关


4.1 方案特点
1.进程切割

从老师角度,每个单元绝对不能混在一个进程中。因为每个人不保证自己的代码不会崩溃。


从学生角度,应该可以像本科编写作业题一样编写程序,比如直接使用printf/scanf吞吐数据。


任何逾越四年制本科教学计算机基础课程以外的特性,都是不值得提倡的。避免使用DLL、避免使用网络套接字、避免使用COM,避免使用特定环境。


2.语言无关、编译器无关

不规定使用的语言、编译器,不规定运行的环境。


3.架构无关

学生不需要考虑自己的这部分东西究竟是独立运行、嵌入一个EXE桌面程序运行、嵌入Web后台运行。 学生的精力应该投入到主干学科,让硕士与博士们真正搞研究 ,而不是打工。


4.2 方案:基于进程管道重定向的任务总线

本方案从进程通信的标准为入口,定义一个团队内的协作开发方案。


1. 进程管道

只要学过一门基础语言,就会遇到输入输出。一般来说,再小的一个通用操作系统,也会提供至少1对输入输出管道用于进程间通信。以C为例子,就是标准输入输出(stdio)中定义的stdin, stdout.


这两个管道直接用于类似printf、scanf等函数,或使用 fread(…,stdin), fwrite(…,stdout)来进行通信。


2. 任务总线

如果仅通过管道,进程间其实只具备单路双工通信能力。任务总线通过一定的数据结构,在单路双工线路中,承载多个逻辑通道。如可区分专题(subject)、目的、来源地址等。


3. 分布式任务总线环境示意图


4.3 学生的工作

学生的工作,就是实现上图中绿色部分所示的各个模块、文档。


这些模块采用的实现方法不限制,但只要能够实现下面三个功能,即可完成任务。


1.撰写一个JSON功能描述文档

这个JSON文档用来描述模块具备什么功能。如,一个负责采集土壤温度的模块:


temp_sp.exe.json


{
"mud_tp_sampler":{
"paras":
{
"sprate":{
"label":"采样间隔(秒)",
"type":"int",
"range":{
"min":1,
"max":120
}
},
"mod":{
"label":"工作模式",
"type":"enum",
"range":{
"1":"定时采集",
"2":"持续采集"
}
}
},
"in_subjects":{
"gps":{
"label":"GPS专题输入",
"type":"text"
}
},
"out_subjects":{
"data":{
"label":"采集温度",
"type":"bytes"
},
"log":{
"label":"工况",
"type":"text"
}
}
}
}

将在平台的管理界面中体现为这样的图标:



2. 接收命令行参数

在上述模块中,所有paras 在模块进程被启动时,会通过–name=value的方式传递。


如(为版面换行):


root@localhost$ temp_sp
--instance=2421
--func=mud_tp_sampler
--sprate=23
--mod=2
--gps=1837462
--data=8274611
--log=343649

学生只要具备基础的能力,即可获得:


int main(int argc, char * argv[])
{
//...
{
if (strcmp(argv[i],"--sprate")==0)
{
//...
}
}
}

一些解释:


1. 平台会通过–instance送进来一个唯一的ID用于区分本进程。


2. 所有参数(paras单元)都能够被平台配置,比如采样间隔–sprate


3. 所有接口(subjects单元)都是系统根据连接关系分配的整数。这些整数都是用于区分数据的专题,用于吞吐数据。


3.吞吐数据

学生直接吞吐数据。


比如,在本例里,可以不断接受GPS,并判断时间、输出


struct tbheader{
int subject;
int channel;
int from;
int to;
int len;
};
int main(...)
{
/*
已经定义一个能够承载最大长度包的缓存buffer
命令行获得的gps专题的号码存储在n_gps里,
命令行获得的data专题的号码存储在n_data里,
命令行获得的进程实例(--instance)号码存储在n_ins里,
*/
structtbheader header;
while (!finished())
{
fread(&header,sizeof(header),1,stdin);
fread(buffer,1,header.len,stdin);
if (subject==n_gps)
{
//...
if (time_hit())
{
float * ftemp = get_card_temp_buffer();
struct tbheader out_header;
out_header.subject = n_data;
out_header.channel = header.channel;
out_header.from = n_ins;
out_header.to = header.from;
out_header.len = sizeof(float)*OUTLEN;
fwrite(ftemp,sizeof(fload),OUTLEN,stdout);
fflush(stdout);
}
}
}
//...
}
4. 调试

通过freopen等函数,可以改变 stdin,stdout的指向。因此,学生只要通过文件输入输出来调试自己的EXE,便可以在接入平台后,顺利处理真实的输入输出。


5 后记

使用这种架构,充分给学生选择工具链的自由。同时,彻底消除了语言、编译器的耦合关系。对水平差的学生,可以使用单线程操作;水平高的,模块里面完全又是一个复杂的系统。一些独立的功能还可以有自己的界面,不同的工作模式。


而平台部分,整体外包给软件公司,是最好的选择。


最新文章

123

最新摄影

闪念基因

微信扫一扫

第七城市微信公众平台