|

我是老温,一名热爱学习的嵌入式工程师9 K$ u2 A2 X& v. q) P; ?
关注我,一起变得更加优秀!具体的重构手段可参考《代码大全2》或《重构:改善既有代码的设计》,本文不再班门弄斧,而侧重重构时一些粗浅的“方法论”,旨在提高重构效率。
2 v# p( d* B- ?, [# Q/ n作者未采用重量级的重构工具,仅用到Source Insight的”Smart Rename”功能。也未使用CUnit等单元测试工具,而是通过在线调测和自动化测试保证代码的正确性。
) Q, _- c4 L! x1 w一 背景MDU系列产品从他处接手,OMCI模块相关人员含作者在内不过三五人。除新增功能的开发外,大量时间花费在处理遗留故障上。但该模块代码庞杂且可读性差,导致大家仅了解其“大概轮廓”,难以放心地使用和维护。& K, o/ f# u: z
此外,忙碌容易使人迷失方向。主要的时间精力花费在故障处理上时,自然无暇考虑整改代码,从而陷入四处救火、疲于奔命的尴尬境地。
3 n$ V' i1 s/ j2 W; k二 目标重构的主要目的在于改善既有代码的设计,而不是修改缺陷、新增功能等。
4 f0 N: L; u( g% B' W6 |% W重构可以是修改变量名、重新安排目录这样简单的物理重构,也可以是抽取子函数、精简冗余设计这样稍许复杂的逻辑重构。但均不改变现有代码的功能。
8 p$ s/ b0 ~( u+ y% C重构可以将意大利面条式的杂乱代码整理为千层饼式的整洁代码。整洁的代码更加健壮,因其便于建立完善的测试防护网。同时,新手老人均可放心地修改。- { v0 Y/ c9 v; d/ ^+ C* W0 ]
期望重构之后,代码逻辑一目了然,扩展和修改非常方便,出现故障时能迅速定位和修复。前人摔跤过的地方后人不再栽倒,前人思考出的成果后人可直接借用。总之,高度人性化,极大解放人力和脑力。+ c v( | ?# [8 F1 S% ]
最初的想法是,通过重构部分流程和代码(代码先行),建立测试防护体系,生成阶段报告,展现代码质量(实例加数据)和故障收敛曲线。借助这样的报告,可望获得领导层的支持和宣贯,也有利于绩效考核。
/ \4 h ~# }% r* S三 实践具体实践时,作者并未进行纯粹的“重构”,还兼做缺陷修改,并增加自动化测试等辅助功能。原则上,对既有代码注重重构,对新增代码注重复用。
; N% p9 E' r) o5 R3.1 代码研读OMCI模块代码庞杂,分支众多,上手困难(据称半年勉强入门,一年才能熟练)。若不能有效掌握现有代码,后续难免被迫付出时间健康而又得不到项目认同(事实上,模块内发现的遗留故障源源不断)。反之,若能全面掌握现有代码,后续才可能通过反向工程、系统/代码恢复和重构等手段,将模块改造得更易开发和维护,最终解放编码者自己。
/ `, O; S) u( ^; g' o; s# a为提高代码研读效率,可采用分工阅读和代码注释的方法。
: {0 Q8 A* g7 \# h“分工阅读”是指将模块分为若干块子功能(如协议解析、告警、统计、二层、语音等),组内每人负责一块或几块,不定期地交流和轮值。
) Q% h/ H' H0 W7 E“代码注释”是指在学习代码过程中,随手注释代码(大至流程、函数,小至代码行),功能、意图、技巧、缺陷、疑问等均可(凡经过思考的地方都是可加注释之处)。其中“疑问”既可咨询兄弟产品同一模块的同事再转换为功能或意图,也可由其他注释者解答。
7 s. J( N5 h2 i9 ~2 }这样做的好处是:避免重复钻研;经验积累;可供量化。! Y/ d; L; E' r( V+ n2 ? c
代码可取产品最新版本,建立服务器公共代码目录(SVN管理更好)。注释时不要覆盖其他人的注释即可。- c$ E) q# y: e2 d
建议注释统一格式,便于识别和检索,形如”//>”。以下示出一个代码注释实例:% j* J) Q# Y. @% m
case OMCI_ME_ATTRIBUTE_2: // Operational state
$ D$ F0 H: _0 d. u if (attr.attr.ucOperationState != 0 && attr.attr.ucAdminState != 1) //xywang0618> BUG: should be ucOperationState!
8 A! r" V: X& |, {; i5 i! q* Y3 ` s {/ D' B% ^% k% U# }- V- p
return OMCI_FUNC_RETURN_OUT_OF_RANGE;
% A: l& J8 F4 t5 P/ k) h7 x }0 M9 l; J0 R3 f, ]$ S( H
break;
1 ^+ Y% X6 I/ h2 V9 c7 S. u3.2 可读性首先,规范变量、函数等命名。具体方法不再赘述。
; }' n. M2 K4 v/ Q1 ^5 R) r. p其次,注释到位,尤其是全局变量和通用函数。举例如下:
4 |5 N: \9 D* b/ `; Z/******************************************************************************
' ^5 V) r! B% d: p0 h5 j5 O9 w+ c* 函数名称: ByteArray2StrSeq% ^. Y- ~; O; @; L- n7 C0 m
* 功能说明: 掩码字节数组字符串化- T+ Y7 o9 J2 V( n- t
该数组元素为掩码字节,将其所有值为1的比特位置转换为指定格式的字符串
0 F, m) i9 ]9 c1 y2 K9 X* 输入参数: pucByteArray: 掩码字节数组
) V$ s1 \! i1 v ucByteNum : 掩码字节数组待转换的有效字节数目" t$ f7 c2 E: v+ p; `7 q+ F' {
ucBaseVal : 掩码字符串起始字节对应的数值; }4 d: o) Y) o7 w- X9 j
* 输出参数: pStrSeq :掩码字符串,以','、'-'间隔
* U7 [! F( p1 d; w m0 }4 D 形如0xD7(0b'11010111) ---> "0-1,3,5-7"
- g# O/ f: j) y9 f* 返 回 值: pStr :pStrSeq的指针备份,可用于strlen等链式表达式, p6 Z. S# T& G
* 用法示例: INT8U aucByteArray[8] = {0xD7, 0x8F, 0xF5, 0x73};
. l* Z2 w' v! r: p4 s$ V CHAR szSeq[64] = {0};4 g$ a' h* W. ` ~" U
ByteArray2StrSeq(aucByteArray, 4, 0, szSeq);6 k; r/ v' c3 y q: y( x3 o% M
----> "0-1,3,5-8,12-19,21,23,25-27,30-31"8 q& P, ^9 b- f' N9 ]- N/ I
memset(szSeq, 0, sizeof(szSeq));
/ p( A8 h+ Y/ n- j! o- g ByteArray2StrSeq(aucByteArray, 4, 1, szSeq);4 f* P. E# Y" d( \: W
----> "1-2,4,6-9,13-20,22,24,26-28,31-32"
, _6 g& a6 C+ k7 y8 x- q9 \* 注意事项: 因本函数内含strcat,故调用前应按需初始化pStrSeq. G1 x, K% D3 Z5 F
******************************************************************************/
9 I# p' n j1 [+ h6 ~( GCHAR *ByteArray2StrSeq(INT8U *pucByteArray, INT8U ucByteNum, INT8U ucBaseVal, CHAR *pStrSeq);% s4 n1 s" c9 ~2 }8 K6 \3 ?. _
最后,整改晦涩难懂的代码。主要有两种手段:
6 j' t L% O, L" i9 D8 E1) 改写方法
0 U4 }, ~ s' n: t以PON光路检测为例,底层接口提供的光功率单位为0.1uW,OMCI协议Test消息上报的光功率单位为0.002dBuW,而Ani-G功率属性单位则为0.002dBmW。
0 K7 k; [- C- _; S) q) X原有代码转换如下(为突出重点有所改编):
r3 C7 Y3 j# BINT16S wRxPower = GetRxPowerInDot1uW(); //接收光功率* ?- y9 [8 Y! X9 O* P
if(wRxPower 1){
\- P0 {+ `" i! D wRxPower = 1;
2 C. c) C* M2 ~}& ~* X, a, n3 D
/*0.1uw to 0.002dbm*/3 F6 }1 F/ z; u
dblVal = 10 * log10(wRxPower) - 40;" v( I3 U5 h$ G
dblVal = dblVal * 500;( k' A# ], W" u! L3 \
wRxPower = (INT16U)dblVal;4 B% {8 Q7 F/ ^$ k! H
wRxPower = (int)wRxPower*100;
3 _' w" @2 ^9 v2 m9 A$ ]) N3 W/*opt pwr 0.00002db X * 0.00002*/5 {# k6 m1 b0 G/ X V: L+ |
wRxPower = wRxPower + (30 * 500) * 100; J* |- Z( L) w) N
if(wRxPower 0){
/ M8 {9 s2 s( R9 s- i& E, o val = (INT16U)((0 - wRxPower) / 100);
8 _7 N: C* P% h: s val = (((~val) & 0x7fff) + 1) | 0x8000;: S8 }* W! b ?. s
wRxPower = val;
9 X% r/ j$ v+ s8 v7 N& \2 F}
5 D* k3 }* O" l2 N! v2 Celse{' O- p6 @2 S* X) Y; w: {
wRxPower = wRxPower / 100;
. m. i5 Y) B+ M}
$ L. S2 d& N: B( Q+ p9 `可见,原实现中转换关系非常晦涩难懂。其实借助1dBuW=10*lg(1uW)和1dBuW-1dBmW=30dB两个公式,经过简单的数学推导即可得到更简洁易懂的表达(为突出重点有所改编): ]( L4 w. W/ i* J' Z7 x0 \
INT16S wRxPower = GetRxPowerInDot1uW(); //接收光功率$ L. w' b1 G, q' H
//Test单位0.002dBuW,底层单位0.1uW,转换关系T=(10*lg(B*0.1))/0.002=5000*(lgB-1)
# V2 S* U" Z: ~: S l" S, I/ }9 jwRxPower = (INT16S)(5000 * (log10((DOUBLE)wRxPower)-1));
: Z. w8 ^, R' B# y; k0 j4 }% a//Ani-G功率属性单位0.002dBmW,Test结果单位0.002dBuW
. c. n, S4 N, o& U# F- m6 ~//转换关系A(dBmW)*0.002 + 30 = T(dBuW)*0.002,即A=T-15000) V3 m. _) ^% _& G% i5 R7 V: l0 E
INT16S wAniRxPwr = wRxPower - 15000;( W& k& t! O; G7 a \! M
注意,原实现中误认为Ani-G功率属性与Test结果的单位相同,新实现已修正该错误。
7 ]3 G/ f: w$ ~3 R- Q2) 封装函数
% B% G% [& @* S) D; e8 u: j以实体属性的掩码校验为例,原有代码如下:' m' U4 K" D4 S, k N
/*掩码初校验*/
! j# K2 l) `9 Nif ((OMCIMETYPE_SET == vpIn->omci_header.ucmsgtype)
( L! @9 W: H- b7 J+ k || (OMCIMETYPE_GET == vpIn->omci_header.ucmsgtype))
) I7 z2 `7 G! j9 \{# U2 o, q% a/ }2 h
wMask = W(response.omcimsg.auccontent[0],response.omcimsg.auccontent[1]);
# [2 k1 p6 D7 m4 O" e: t; o2 d usSupportMask = (1 map.num))-1;
/ q% J5 F/ H) E if( 0 != (wMask & usSupportMask))* c1 D1 P. m/ A5 u
{% `1 O7 K5 @% Y; S5 H
OmciPrint_warn("[%s] check mask warning: (meclass[%u], meid[%u], msgtype[%u], mask[0x%x], unsupport mask[0x%x])!
2 Z: X0 s0 u! E9 y; u8 {7 O\r",1 g- F) E; V& m& D/ L; d
FUNCTION_NAME, vpIn->omci_header.wmeclass, vpIn->omci_header.wmeid, vpIn->omci_header.ucmsgtype, wMask, usSupportMask);% F$ r9 E2 j& q/ u
}8 M9 I% i) ]; o1 O& A. p) l
}; _4 c' R" O7 G& ~3 E! H/ n
对usSupportMask赋值及判断的语句(第6~7行),用于校验掩码是否越界。为更具可读性,将其封装为如下函数:, [& ` I, K% d5 y. M% H
/******************************************************************************& m3 G( o) D! ~, C% f5 B5 m
* 函数名称: OmciIsMaskOutOfLimit3 s. R) B' E2 c& S1 ^+ b7 v6 } U
* 功能说明: 判断实体属性掩码是否越界(比特1数目超过属性数目) c; h0 N( G' w/ g% c* Q; N. h
* 输入参数: INT16U wMeMask :实体掩码2 E2 V; D+ ?. S" }- _# S" Q
* INT8U ucAttrNum :属性数目1 N$ y, L) _& g4 I2 p
* 输出参数: NA
) z; _! a( q6 Z# V$ y* 返 回 值: BOOL$ R, l' \6 X! L% f$ v' v6 u
******************************************************************************/
; `: }6 @1 }3 X; h- ~1 N) _BOOL OmciIsMaskOutOfLimit(INT16U wMeMask, INT8U ucAttrNum); g& H& C4 S) ?8 k
{% B: L$ R9 W2 M9 o, U
//wMeMask :mmmm mmmm mmm0 m0006 ^4 f) W' I9 z9 E" o
//wInvertMask :0000 0000 000i iiii$ P) t% k7 K# D
INT8U wInvertMask = (1 1;; l$ ^/ m1 Y1 T# H+ Y' T
return (0 != (wMeMask & wInvertMask));
- o8 G2 ~ s1 z1 U( |}
1 R- S1 l" m E( x; e. g封装后的函数名恰当地起到“自描述”的作用。2 Q4 a" G+ t) \6 g/ I
3.3 在线调测工程该产品作为嵌入式终端,需要在Linux系统中编译打包版本,然后将其下载到目标单板上运行。这种交叉编译方式对于单个模块的调试而言,效率无疑比较低下。6 t# B0 e9 P% l# ~3 }+ p) m
为提高调测效率,在Linux服务器搭建在线调测工程。即提取OMCI模块代码,稍作改造后直接在服务器上编译和运行。这样就可避免每次修改代码都要重启单板升级大版本,调测效率极高。
$ _5 W9 V5 u1 b为使模块可独立运行,需要编写模拟接口以屏蔽底层调用,并裁减暂不必要的特性(如线程和通信)等。
' T! w" T8 M0 I3 R$ Z% i3.4 模拟数据库OMCI模块使用某内存数据库来管理需要持久化的实体信息,但该数据库代码内调用了大量平台相关的接口,不利于实现模块的在线调测。因此,作者研读源代码后编写了一个模拟数据库。该库仿照模块使用的几个原库接口及行为,模拟接口内部校验均增加错误信息打印,以便于排障。
2 t- `& \+ O. D) @. Z/ D此外,在数据库接口原语的基础上二次封装统一接口,一举消除模块内数据库操作代码的凌乱和重复。
/ U5 n1 z" X' T, `0 \3.5 自动化测试没有测试保护网的重构,无异于没有血源的外科手术。
9 V2 r" ?* e, @6 T$ y首先,公共接口和函数均提供有相应的测试函数,兼做示例和用例。如:
5 [" ?$ E* |( W/ K//Start of ByteArray2StrSeqTest//
) t B% V! N4 B/ q2 N- wVOID ByteArray2StrSeqTest(VOID)
) X8 }8 U4 C- C G{) A, S6 E" [0 T1 E! g& ]: }3 q
//ByteArray2StrSeq函数算法不甚优美和严谨,应多加测试验证,如有可能尽量优化。
6 [2 p( d" s$ ~ INT8U ucTestIndex = 1;6 T9 c* Z* B8 J( | a
INT8U pucByteArray[] = {0xD7, 0x8F, 0xF5, 0x73, 0xB7, 0xF0, 0x00, 0xE8, 0x2C, 0x3B};) Q, Q/ L8 J7 @- P; a; ^" ] j
CHAR pStrSeq[50] = {0};
, T3 i$ b5 D% s! q! v6 O1 n //Time Consumed(x86_gcc3.2.3_glibc2.2.5): 72us! m% m# u% ~6 m; ]# C
memset(pStrSeq, 0, sizeof(pStrSeq));
; p# ~- p) X, }; w ByteArray2StrSeq(pucByteArray, 4, 1, pStrSeq);
0 z' g. h. a# f& a1 j printf("[%s] Result: %s, pStrSeq = %s!
% L- F. @7 N' `8 E", __FUNCTION__, ucTestIndex++,8 L' Q& t2 ]' t1 q
strcmp(pStrSeq, "1-2,4,6-9,13-20,22,24,26-28,31-32") ? "ERROR" : "OK", pStrSeq);
3 M" o! v$ K7 X. m //Time Consumed(x86_gcc3.2.3_glibc2.2.5): 7us, l% c* d" n- p0 h' i# t
memset(pStrSeq, 0, sizeof(pStrSeq));
8 J! E1 D( i# O) j ByteArray2StrSeq(pucByteArray, 4, 0, pStrSeq);. z/ X1 D$ Z, s2 z/ Z% |8 I
printf("[%s] Result: %s, pStrSeq = %s!!!
! \: V( d) b$ l) [0 w# k ^) I", __FUNCTION__, ucTestIndex++,; W5 p- P1 f" s$ B3 C B- @
strcmp(pStrSeq, "0-1,3,5-8,12-19,21,23,25-27,30-31") ? "ERROR" : "OK", pStrSeq);8 A' [5 S+ N' s5 R/ r& R
//Time Consumed(x86_gcc3.2.3_glibc2.2.5): 4us% | d0 }. j' f: C7 T/ [
memset(pStrSeq, 0, sizeof(pStrSeq));7 c$ l( v+ u! N' \: |# m$ M- i
ByteArray2StrSeq(&pucByteArray[4], 2, 1, pStrSeq);& m! I' r( ~. w& L7 ~2 `8 n0 I2 w
printf("[%s] Result: %s, pStrSeq = %s!
0 I+ s! _9 U$ ~% |", __FUNCTION__, ucTestIndex++,
9 O7 w9 h4 A1 \/ L2 v4 g strcmp(pStrSeq, "1,3-4,6-12") ? "ERROR" : "OK", pStrSeq);- F1 [; |! W5 M+ Z$ |* c5 P! Q
//Time Consumed(x86_gcc3.2.3_glibc2.2.5): 4us7 V/ K* v7 w1 W% L
memset(pStrSeq, 0, sizeof(pStrSeq));
" i% r: f& Q/ @7 o& b: o* C# k ByteArray2StrSeq(&pucByteArray[6], 2, 1, pStrSeq);
* {7 P. {! T' Z7 D printf("[%s] Result: %s, pStrSeq = %s! Q( C8 b% ?4 |, U
", __FUNCTION__, ucTestIndex++,+ p ?# x% s" g( q" a* m9 v
strcmp(pStrSeq, "9-11,13") ? "ERROR" : "OK", pStrSeq);
' m$ ?' F/ O1 l: f' O7 n W4 ?( T //Time Consumed(x86_gcc3.2.3_glibc2.2.5): 5us* f5 Z/ q- i; {! b1 I
memset(pStrSeq, 0, sizeof(pStrSeq));
, |$ R$ h" h& x( a' \ ByteArray2StrSeq(&pucByteArray[8], 2, 1, pStrSeq);+ b y0 S) `. N- y$ Q
printf("[%s] Result: %s, pStrSeq = %s!
) u- w. R& n! d* p", __FUNCTION__, ucTestIndex++,
8 F4 M. Y! H+ y4 X+ X strcmp(pStrSeq, "3,5-6,11-13,15-16") ? "ERROR" : "OK", pStrSeq);
% {! V8 y) T0 x4 q$ K}
w% \- x$ e: e+ I9 d( i//End of ByteArray2StrSeqTest//
* P0 @) r# v/ O! Z4 P' t3 p此外,模块内还增加自动化测试功能(TestSuite),可用来验证批量或单个实体的配置和查询操作。批量测试结果统计如下(省略各实体的具体测试结果): |
|