以前产品应用是用串口做控制台,写了一个带简单命令历史和命令补全功能的控制台Shell,用作程序的调试,包括查看系统状态和调试修改设定等等。确实非常好用,对很多现场简单问题的快速定位起到了很好的作用。系统移到 Linux 以后,由于对如何在 Linux 下,在应用程序中如何嵌入控制台 Shell 用作原先的调试功能,不太熟悉,先前想用 Modbus Server,通过改 Modbus 寄存器方式做调试修改,后来发现根本要用的时候记不住,也不直观,不好用,写完了以后,自己就没用过。后来想集成一个 httpd,使用 CGI 接口做程序状态的相识和调试修改设定,于是着手寻找简单的 httpd 的实现。经过一番比较,我认为 bhttpd 非常小,c 文件只有3个,行数只有 104+375+90 行。而且带 CGI 功能,十分适合拿来用。
下来代码以后测试发现,这个代码有问题,不能正常工作。沉下心来,把代码看了一遍,做了一番调试,代码终于正常了。修改后的代码更新在我的 bhttpd 上。其中大概修改如下:
这个功能基本就是 C# 的 split 方法,原实现类似于:
char buf[BUFFER_SIZE];
char basic_request[3][BUFFER_SIZE];
// other codes
read_line(buf, sockfd);
sscanf(buf, "%s %s %s", basic_request[METHOD], basic_request[PATH], basic_request[PROC]);
这个方法很简单,就是比较浪费内存。当然似乎在系统上,这点内存可能不算啥。但是对于嵌入的人来说,不能忍受,做了一个原地切割为多个字符串的函数来处理:
int str_split(char *str, char split_char, char **ret, int max_ret)
{
int ret_num = 0, i;
memset(ret, 0, max_ret*sizeof(*ret));
while (ret_num < max_ret) {
*ret++ = str;
ret_num++;
while ((*str != split_char) && (*str != '\0')) str++;
if (*str == '\0') break;
*str = '\0'; str++;
while (*str == split_char) str++;
if (*str == '\0') break;
}
for (i = ret_num; i < max_ret; i++) // all other pointers set to NULL string
*ret++ = str;
return ret_num;
}
由此,独立出来一个单独的 strlibs 模块,专门放置字符串处理相关的工具函数。
头文件包含:原先的实现,头文件主要包含在 .h 文件中。我始终认为,.h 中应该包含最少的头文件,如果实现需要某个头文件,那么包含应该是在 .c 中。所以大量的包含做了修改。
mime 3级加速表,改动态分配为全静态分配。因为不管如何,他的元素始终是这么多的,那就不如静态分配,对调试更有利,也更易理解。
tcp 连接 close() 调用问题
其实,这个问题才是这个库不能工作的最终原因。其解释可以见 The ultimate SO_LINGER page, or: why is my tcp not reliable 代码修改为:
if(fds[i].fd!=-1&&(fds[i].revents & POLLRDNORM)) {
handle_request(mime_tbl, cgi_tbl, conf.pub_dir, conf.default_page, fds[i].fd);
close(fds[i].fd);
fds[i].fd = -1;
--polled;
}
修改为:
if (fds[i].fd != -1 && (fds[i].revents & POLLRDNORM)) {
handle_request(mime_tbl, cgi_tbl, conf.pub_dir, conf.default_page, fds[i].fd);
shutdown(fds[i].fd, SHUT_WR);
while ((len = recv(fds[i].fd, buff, sizeof(buff), 0)) > 0) fprintf(stderr, "recv before close get %d.\n", len);
close(fds[i].fd);
fds[i].fd = -1;
--polled;
}
注意 shutdown()
函数以后后面的 recv()
函数循环。
原先的128GB SSD,给Windows用是够了,最近虚拟机用得多,靠以前的SSD外挂着用,实在有点不爽,就入手一个256GB的,重装系统是个令人头疼的事情,当然不能干。想起来以前另一个机器操作的时候,查过直接复制分区就可以。所以直接启动到另一个临时系统,做整盘ghost。替换以后,发现系统启动不了了。想了想,启动到Linux,dd,然后手动调整分区,再安装好,启动,成功。移动系统就是这么简单,哈。
关于C的头文件包含,我认为体现了软件的模块设计以及包含关系,对于头文件包含,我个人看法如下:
这本书其实买了有两年了,还去参加了潘老师的公开课,限于能力,当时上课时领悟有限,最近因为Scanning打印系统做代码重构,要做代码框架设计,想借助于UML,以严谨一些,就翻出了这本书,重新看了一遍。
这本书其实并没涉及到具体软件架构设计要用的UML操作,诚如书名,侧重于需求分析。
以下是一些笔记,比较杂乱:
这里的意思是,现在已经过了粗放经营的阶段了,企业需要搞清需求(产品好卖)和做好良好设计(降低成本),才能在激烈竞争中赢得优势。对于软件,我们更要注重代码重用带来的效率提高。
针对不少开发人员并不喜欢用UML,而是自造草图的情况,书里一针见血地指出是想通过”形式上的丑陋来遮掩内容上的丑陋”。大家对一些基本技能形成共识,可以“大大减少思考中的浪费”。
和涉众的交流内容应该聚焦涉众利益,而不是需求。
缺乏清晰、共享的愿景往往是项目失败的主要原因。(我们公司当前也缺乏愿景)
要避免无用的废话,例如PS可乐的目标客户是“卖给想喝可乐的人”。
系统的愿景应该是“买了这个系统,对组织有什么好处”。
由于编程语言的表达能力越来越强,针对某一段代码画流程图,变得没有必要。业务流程的描述,建议使用序列图。
许多伟大的创新,其实就是用廉价的替代方案来山寨过去富豪和高官的生活。
软件工程更接近于经济学,而非计算机科学。
需求是不考虑“复用”的,用同样的材料,变出更多可卖的功能,这是设计能力的体现。
大家可以尝试用“不这样行吗”这个标准去过滤我们的“需求”。
首先说一下,这个培训还是非常好的,基本上是我碰到的收益最大的一个培训。
考核一定是从上到下,企业一定要形成目标,然后层层分解目标,考核就是各级对目标达成情况进行打分。
我们都知道,对于一线研发人员,是不适合用KPI进行考核的,这里建议采用PBC(个人绩效承诺)方式对员工进行考核。即首先员工进行个人业绩承诺,制定个人对于部门、项目以及个人成长上的目标,同时制定如何达成该目标的计划。最终,通过评价业绩达成情况以及对于团队的贡献,来评价一线研发人员。另外,对于研发人员的考评,应该采取ABCD等级化方式(比例约为1:4:4:1)来评定研发人员。因为评价一个研发人员是70分,还是75分,非常困难,而且你很难就70分和75分的差别和研发人员达成一致。考评一定要沟通。研发考评,对于竞争性公司,一定要强制排序,以传递市场压力。 中小型企业文化建设比考评重要,更要侧重建立文化引导(要肯定“假”积极)。考评的结果,不光是绩效,更要有价值观。 另外研发人员的奖金还需要和部门考评挂钩,部门考评也要按等级化方式评定。即形成等高线考评法。
研发人员考评一般不采用里程碑方式。一般采取固定周期考评。一般认为,月度的时间太短,年度时间太长。所以一般建议采用月度跟踪,季度考评,年度评定方式进行。对于我们实际来说,我觉得我们也应该增加月度和季度的考评活动。
研发分为预研和开发,预研人员的奖金不与销售金额挂钩,开发人员的奖金与销售金额相关,但是不是强相关。研发的奖金要形成奖金池。即按当年的销售额度比例固定提取到研发奖金池中。然后开发人员的奖金从奖金池中提取。收益好的年份奖金高一些,收益低的年份奖金低一些,但不是严格和公司当年盈利情况成正比。这样保证研发活动的持续性。研发人员的奖金不以项目收益作为主要决定因素,避免对公司长远产品形成资源挤出。
奖金构成考虑的因素有:1. 新产品销售占比(S1);2. 老产品毛利额(S2);3. 人均利润率增加(M1); 4. 管理指标(千行代码BUG率,人员培养指标等) 5. 因研发而造成的不可回收的服务费用(M2)。
奖金=S1K1+S2K2+M1K3-M2K4。
必须要进行预算管理,一般销售的10~15%用于研发支出,研发的10%用于预研,预研的10%用于对外合作。研发的支出要保证,并且用于研发的预算一定要花完(并且要监管资金用途),保证研发的持续性。
研发职能部门,一定要建立起研发积累,即建立货架技术,让做产品像搭积木一样。
去年开始在周会贯彻宣讲我的一些开发的理念,以下是对去年主要念叨点的总结:
素养,我觉得就是一个群体对于有利于群体整体发展的一些规则。对于社会,就可能体现为道德。人生活在不同的圈子里,就有不同的素养要求。素养是有利于群体,从而有利于个人,但是对于具体某事而言,可能是不利于当事个人的。对于开发人员而言,我能想到的:1. 对代码负责,不用过客心态对待代码;2. 代码风格和代码注释,代码风格和代码注释一定要统一。对于应用,注释量可以略少,对于基础平台和库,注释一定要严格和严谨;3. Review,大家帮助大家,形成统一的行事准则;4. 团队意识和主动性,为团队创造更好的业绩。5. 积极主动的交流,包括工作情况交流和知识的交流。把知识握在自己的手里,你永远停留在那个位置。自己的接班人要自己培养。技术交流,应该不能停留于这样是不是目前没问题,而要考虑扩展性,多问问那样是不是更好。
代码注释和命名,就是一个程序的外貌。代码的架构,就是一个程序的气质。气质,大家可能都认为比较重要,但是外貌一样也很重要。漂亮的外貌,可以让人愉快工作,良好的命名同样。良好的命名,不仅可以令人愉快,更可以帮助大家更容易理解程序,同时,良好的命名,有助于你更好地思考你怎么样去实现程序。如果你能清晰明确地命名,那通常意味着你的程序结构存在问题。
应该做有效的注释,注释应该去说明你的功能,说明你的结构,即说明你的想法。不要去指导别人,别人或许有更好的实现方式和思路,要尊重别人。模块的结构和接口,别人使用模块的方法,代码算法部分,临时变通部分,需要特别的注释。
代码架构很难一概而论,但是我觉得有几点可以作为参考:1. 程序的一致性,就同一个应用里,有统一的参数校验原则和返回值系统。2. 清晰的层级结构,一个设计的架构图应该是很容易画出来的。3. 封装,理想的封装应该上层只需要知道下一层的结构信息,如果有长长的成员操作,通常意味着坏的架构设计。只有对代码了然于胸,才可能做出一个良好的结构。
调试信息,要十分注意调试信息的合理安排,调试信息应该归属于什么,是否有必要,应该仔细考虑。