回想自己刚初学习编程时,经常犯傻。
如:刚初窥门径,就立马苛求高大上,刚写了几行破程序,就想着成为编程高手,在论坛上四处发伸手贴,寻求经典书籍推荐,一些好心人不耐其烦,给推荐了四人帮的设计模式那本书。
然后,我惊喜的发现,这本书全是干货,不仅惜字如金,而且很便宜,果断入手一本,在成为编程高手的欲望驱动下,一头扎了进去。
但是,从一开始的兴致勃勃,到后来的耐着性子,再到最后寻找台阶,发现仅看了三分之一,该书就被抛弃一旁了。
又过了一段时间,朋友推荐看《HeadFirst设计模式》这一本,图文并茂,而且很厚,记起古训,读书要先薄到厚在到薄,虽然价格昂贵,也咬牙入手,又一次扎了进去。
很遗憾的是,这本书比上一本还惨,仅看完了四方之一就跑到角落去吃灰了。
数年后,在自己已经码过了数不清的代码,解决了N多技术问题,也在如何写出优雅软件这条路上探索了许久后,不经意间,重新拾起四人帮一书,发现如获至宝,竟然有一种惺惺相惜的感觉。
通读一遍后,暮然回首,自己竟然不是在看书,好似在玩味珍爱的玩物。
因此,我也有了一种顿悟,知识是分级的,和入门者谈架构会被认为装叉,如同在贫民窟中谈论品味生活找骂一样。
◇◇◇
但万事总要起步,如何让新人一点点的融入架构设计理念呢,好打下坚实基础呢。
痛定思痛,并结合自己的多年新人培训经历,我挖掘出了两个闪光点:抽象概念和注册机制。
先反复的、使劲的、刻意的锤炼这两个基本技能,在大脑中深深的烙下回沟,让其成为下意识的行为,后面的成长好像就信手拈来了。
抽象的概念我们已经多次提及,这里不在废言,该处拿注册机制开刀。
实际上,在例子三起始,已经讨论过注册机制了,但当时侧重从程序框架方面来描述的,目的是让大家体会架构给程序组织带来的妙处。
这儿不废话了,大家可以回忆阅读,先将相机这个软件中的注册机制用代码表达出来,便于大家加强体悟,如下:
/*注册函数定义*/
typedefvoid(*funRegister)(unsignedchar*pImage);
#defineMAX_REGISTER_COUNT16 /*最大允许的注册个数*/
structTRegister
{
LPCTSTRszMenuName; /*菜单名字*/
funRegisterpfn; /*注册函数*/
}register[MAX_REGISTER_COUNT];
intnIndex; /*索引兼注册个数*/
/*初始化*/
voidinit(void)
{
nIndex=0;
for(i=0;iMAX_REGISTER_COUNT;i++)
{
register[i].szMenuName=NULL;
register[i].pfn=NULL;
}
}
/*注册过程*/
intregisterProc(LPCTSRTlpszMenuName,funRegisterpfn)
{
if(nIndex=MAX_REGISTER_COUNT)
return0;
register[nIndex].szName=lpszMenuName;
register[nIndex].pfn=pfn;
nIndex++;
}
/*选择一副图片后,点击发送,弹出菜单列表*/
voidclickMsg(void)
{
inti;
for(i=0;inIndex;i++)
popMenu(register[i].szMenuName);
}
/*用户选择某一项菜单,执行消息函数*/
voidmenuMsg(inti,unsignedchar*pImage)
{
register[i].pfn(pImage);
}
/*应用层构建注册函数并注册的过程*/
voiduser1(unsignedchar*pImage)
{
……
}
register("user1Menu",user1);
通过注册机制,解耦了相机模块和应用模块,这是嵌入式产品中经常使用的一种策略。
所谓刻意训练,就是对自己要学习的内容刻意上心,然后反复寻找机会训练。前几天给一个小伙伴审核代码时,发现多处可以用注册机制优化的地方,信手拈来一处,便于大家举三反一,加深理解:
电度计算是仪表类产品的基本功能,原理很简单,在固定的时间间隔累加功率即可,当然我们要考虑有用无功,考虑正向负向,因此有了四象限电度。
很简单的一个软件模块,但好运就此戛然而止,用户要求增加分时电度功能,也就是将一天分为多个时段,分别统计尖峰平谷电度值。
最朴素的策略就是修改电度模块,但分时判断也是一个复杂的模块啊,因此我们的代码开始臃肿了。
霉运一般在你落魄的时候准时而来,不仅分时电度来了,XX电度,YY电度,ZZ电度也来了,作为程序员,你有没有内心想上去狠狠的踹用户几脚的感觉啊。
但用户是上帝,营销是上帝,领导是上帝,因此你只能苦逼的继续修改程序,原先短小精干的电度程序终于被你弄成了灭绝师太的裹脚布——又臭又长,你唯一期望的是以后永远不要在碰了,可惜……。
如果你还记得我啰啰嗦嗦反复提及的注册机制,是否会眼前一亮,这不正是注册机制发威的地方吗。
不管是分时电度,还是YY电度,基本都是在某条件下的电度统计功能,我们可以将电度模块提炼为定间隔累积和在特定条件累积两层,伪码示例如下:
/*电度累积注册函数*/
typedefvoid(*funRegister)(intnEnergy);
#defineMAX_REGISTER_COUNT16 /*最大允许的注册个数*/
funRegisterregister[MAX_REGISTER_COUNT];
intnIndex; /*索引兼注册个数*/
/*初始化*/
voidinit(void)
{
nIndex=0;
for(i=0;iMAX_REGISTER_COUNT;i++)
register[i]=NULL;
}
/*注册过程*/
intregisterProc(funRegisterpfn)
{
if(nIndex=MAX_REGISTER_COUNT)
return0;
register[nIndex++]=pfn;
}
/*定时间隔电度统计过程*/
voidclickMsg(void)
{
inti,energy;
energy=……; /*定时间隔电度累加*/
for(i=0;inIndex;i++)
register[i](energy);
}
/*分时电度统计过程*/
voidtimeEnergy(intnEnergy)
{
分时类型判断;
switch(当前分时类型)
{
case尖:
尖电度+=nEnergy;
break;
case峰:
峰电度+=nEnergy;
break;
……;
}
}
◇◇◇
在我带新人的训练过程中,指针这而还有一个非常经典的例子,也就是拿qsort函数开刀,其原型如下:
voidqsort(void*base,intnelem,intwidth,int(*fcmp)(constvoid*,constvoid*));
从函数原型可知,需要传递具体比较函数。
通过这个例子,不仅可以体会函数指针的一种典型用法,更主要的是需要小伙伴去分析程序的执行时间,这又是嵌入式编程中一个需要刻意注意的地方。
一谈起时间分析,好多人又要脑麻了,是否要研究数据结构上的O(nlogn)之类的东东啊,实际上,在工程上有很多巧妙的办法,而我正是想通过qsort让大家体会这种工程技巧。
但我发现,每个系列文章随着篇幅的递增,阅读量都是呈持续递减的状态,不想让这样关键的技能随意泯灭,打算给它找一个新的位置,让其绽放。
因此,这一节就是例子三的最后一篇了,我们下一篇进入入职训练的最后一部分,编程规范部分,期待你的光临。
我是小马儿,一个渴望良知与灵魂的工程师,欢迎您的陪伴与同行。
赞赏
人赞赏
北京中科医院曝光银川治白癜风最好的医院