電子產(chǎn)業(yè)一站式賦能平臺(tái)

PCB聯(lián)盟網(wǎng)

搜索
查看: 30|回復(fù): 0
收起左側(cè)

從混亂到清晰:嵌入式軟件重構(gòu)的實(shí)用技巧

[復(fù)制鏈接]

458

主題

458

帖子

2304

積分

三級(jí)會(huì)員

Rank: 3Rank: 3

積分
2304
跳轉(zhuǎn)到指定樓層
樓主
發(fā)表于 昨天 11:38 | 只看該作者 |只看大圖 回帖獎(jiǎng)勵(lì) |倒序?yàn)g覽 |閱讀模式


原文:https://www.cnblogs.com/clover-toeic/p/3842758.html
具體的重構(gòu)手段可參考《代碼大全2》或《重構(gòu):改善既有代碼的設(shè)計(jì)》,本文不再班門(mén)弄斧,而側(cè)重重構(gòu)時(shí)一些粗淺的“方法論”,旨在提高重構(gòu)效率。
作者未采用重量級(jí)的重構(gòu)工具,僅用到Source Insight的”Smart Rename”功能。也未使用CUnit等單元測(cè)試工具,而是通過(guò)在線調(diào)測(cè)和自動(dòng)化測(cè)試保證代碼的正確性。
一 背景 MDU系列產(chǎn)品從他處接手,OMCI模塊相關(guān)人員含作者在內(nèi)不過(guò)三五人。除新增功能的開(kāi)發(fā)外,大量時(shí)間花費(fèi)在處理遺留故障上。但該模塊代碼龐雜且可讀性差,導(dǎo)致大家僅了解其“大概輪廓”,難以放心地使用和維護(hù)。
此外,忙碌容易使人迷失方向。主要的時(shí)間精力花費(fèi)在故障處理上時(shí),自然無(wú)暇考慮整改代碼,從而陷入四處救火、疲于奔命的尷尬境地。
二 目標(biāo) 重構(gòu)的主要目的在于改善既有代碼的設(shè)計(jì),而不是修改缺陷、新增功能等。
重構(gòu)可以是修改變量名、重新安排目錄這樣簡(jiǎn)單的物理重構(gòu),也可以是抽取子函數(shù)、精簡(jiǎn)冗余設(shè)計(jì)這樣稍許復(fù)雜的邏輯重構(gòu)。但均不改變現(xiàn)有代碼的功能。
重構(gòu)可以將意大利面條式的雜亂代碼整理為千層餅式的整潔代碼。整潔的代碼更加健壯,因其便于建立完善的測(cè)試防護(hù)網(wǎng)。同時(shí),新手老人均可放心地修改。
期望重構(gòu)之后,代碼邏輯一目了然,擴(kuò)展和修改非常方便,出現(xiàn)故障時(shí)能迅速定位和修復(fù)。前人摔跤過(guò)的地方后人不再栽倒,前人思考出的成果后人可直接借用?傊,高度人性化,極大解放人力和腦力。
最初的想法是,通過(guò)重構(gòu)部分流程和代碼(代碼先行),建立測(cè)試防護(hù)體系,生成階段報(bào)告,展現(xiàn)代碼質(zhì)量(實(shí)例加數(shù)據(jù))和故障收斂曲線。借助這樣的報(bào)告,可望獲得領(lǐng)導(dǎo)層的支持和宣貫,也有利于績(jī)效考核。
三 實(shí)踐 具體實(shí)踐時(shí),作者并未進(jìn)行純粹的“重構(gòu)”,還兼做缺陷修改,并增加自動(dòng)化測(cè)試等輔助功能。原則上,對(duì)既有代碼注重重構(gòu),對(duì)新增代碼注重復(fù)用。
3.1 代碼研讀OMCI模塊代碼龐雜,分支眾多,上手困難(據(jù)稱(chēng)半年勉強(qiáng)入門(mén),一年才能熟練)。若不能有效掌握現(xiàn)有代碼,后續(xù)難免被迫付出時(shí)間健康而又得不到項(xiàng)目認(rèn)同(事實(shí)上,模塊內(nèi)發(fā)現(xiàn)的遺留故障源源不斷)。反之,若能全面掌握現(xiàn)有代碼,后續(xù)才可能通過(guò)反向工程、系統(tǒng)/代碼恢復(fù)和重構(gòu)等手段,將模塊改造得更易開(kāi)發(fā)和維護(hù),最終解放編碼者自己。
為提高代碼研讀效率,可采用分工閱讀和代碼注釋的方法。
“分工閱讀”是指將模塊分為若干塊子功能(如協(xié)議解析、告警、統(tǒng)計(jì)、二層、語(yǔ)音等),組內(nèi)每人負(fù)責(zé)一塊或幾塊,不定期地交流和輪值。
“代碼注釋”是指在學(xué)習(xí)代碼過(guò)程中,隨手注釋代碼(大至流程、函數(shù),小至代碼行),功能、意圖、技巧、缺陷、疑問(wèn)等均可(凡經(jīng)過(guò)思考的地方都是可加注釋之處)。其中“疑問(wèn)”既可咨詢兄弟產(chǎn)品同一模塊的同事再轉(zhuǎn)換為功能或意圖,也可由其他注釋者解答。
這樣做的好處是:避免重復(fù)鉆研;經(jīng)驗(yàn)積累;可供量化。
代碼可取產(chǎn)品最新版本,建立服務(wù)器公共代碼目錄(SVN管理更好)。注釋時(shí)不要覆蓋其他人的注釋即可。
建議注釋統(tǒng)一格式,便于識(shí)別和檢索,形如”//>”。以下示出一個(gè)代碼注釋實(shí)例:
case OMCI_ME_ATTRIBUTE_2: // Operational state
     if (attr.attr.ucOperationState != 0 && attr.attr.ucAdminState != 1) //xywang0618> BUG: should be ucOperationState!
     {
         return OMCI_FUNC_RETURN_OUT_OF_RANGE;
     }
     break;
3.2 可讀性首先,規(guī)范變量、函數(shù)等命名。具體方法不再贅述。
其次,注釋到位,尤其是全局變量和通用函數(shù)。舉例如下:
/******************************************************************************
* 函數(shù)名稱(chēng):  ByteArray2StrSeq
* 功能說(shuō)明:  掩碼字節(jié)數(shù)組字符串化
            該數(shù)組元素為掩碼字節(jié),將其所有值為1的比特位置轉(zhuǎn)換為指定格式的字符串
* 輸入?yún)?shù):  pucByteArray: 掩碼字節(jié)數(shù)組
            ucByteNum   : 掩碼字節(jié)數(shù)組待轉(zhuǎn)換的有效字節(jié)數(shù)目
            ucBaseVal   : 掩碼字符串起始字節(jié)對(duì)應(yīng)的數(shù)值
* 輸出參數(shù):  pStrSeq     :掩碼字符串,以','、'-'間隔
            形如0xD7(0b'11010111)  ---> "0-1,3,5-7"
* 返 回 值:  pStr        :pStrSeq的指針備份,可用于strlen等鏈?zhǔn)奖磉_(dá)式
* 用法示例:  INT8U aucByteArray[8] = {0xD7, 0x8F, 0xF5, 0x73};
            CHAR szSeq[64] = {0};
            ByteArray2StrSeq(aucByteArray, 4, 0, szSeq);
               ----> "0-1,3,5-8,12-19,21,23,25-27,30-31"
            memset(szSeq, 0, sizeof(szSeq));
            ByteArray2StrSeq(aucByteArray, 4, 1, szSeq);
               ----> "1-2,4,6-9,13-20,22,24,26-28,31-32"
* 注意事項(xiàng):  因本函數(shù)內(nèi)含strcat,故調(diào)用前應(yīng)按需初始化pStrSeq
******************************************************************************/
CHAR *ByteArray2StrSeq(INT8U *pucByteArray, INT8U ucByteNum, INT8U ucBaseVal, CHAR *pStrSeq);
最后,整改晦澀難懂的代碼。主要有兩種手段:
1) 改寫(xiě)方法
以PON光路檢測(cè)為例,底層接口提供的光功率單位為0.1uW,OMCI協(xié)議Test消息上報(bào)的光功率單位為0.002dBuW,而Ani-G功率屬性單位則為0.002dBmW。
原有代碼轉(zhuǎn)換如下(為突出重點(diǎn)有所改編):
INT16S wRxPower = GetRxPowerInDot1uW(); //接收光功率
if(wRxPower 1){
    wRxPower = 1;
}
/*0.1uw to 0.002dbm*/
dblVal = 10 * log10(wRxPower) - 40;
dblVal = dblVal * 500;
wRxPower = (INT16U)dblVal;
wRxPower  = (int)wRxPower*100;
/*opt pwr  0.00002db      X  * 0.00002*/
wRxPower = wRxPower + (30 * 500) * 100;
if(wRxPower 0){
    val = (INT16U)((0 - wRxPower) / 100);
    val = (((~val) & 0x7fff) + 1) | 0x8000;
    wRxPower = val;
}
else{
    wRxPower = wRxPower / 100;
}
可見(jiàn),原實(shí)現(xiàn)中轉(zhuǎn)換關(guān)系非常晦澀難懂。其實(shí)借助1dBuW=10*lg(1uW)和1dBuW-1dBmW=30dB兩個(gè)公式,經(jīng)過(guò)簡(jiǎn)單的數(shù)學(xué)推導(dǎo)即可得到更簡(jiǎn)潔易懂的表達(dá)(為突出重點(diǎn)有所改編):
INT16S wRxPower = GetRxPowerInDot1uW(); //接收光功率
//Test單位0.002dBuW,底層單位0.1uW,轉(zhuǎn)換關(guān)系T=(10*lg(B*0.1))/0.002=5000*(lgB-1)
wRxPower = (INT16S)(5000 * (log10((DOUBLE)wRxPower)-1));
//Ani-G功率屬性單位0.002dBmW,Test結(jié)果單位0.002dBuW
//轉(zhuǎn)換關(guān)系A(chǔ)(dBmW)*0.002 + 30 = T(dBuW)*0.002,即A=T-15000
INT16S wAniRxPwr = wRxPower - 15000;
注意,原實(shí)現(xiàn)中誤認(rèn)為Ani-G功率屬性與Test結(jié)果的單位相同,新實(shí)現(xiàn)已修正該錯(cuò)誤。
2) 封裝函數(shù)
以實(shí)體屬性的掩碼校驗(yàn)為例,原有代碼如下:
/*掩碼初校驗(yàn)*/
if ((OMCIMETYPE_SET == vpIn->omci_header.ucmsgtype)
|| (OMCIMETYPE_GET == vpIn->omci_header.ucmsgtype))
{
    wMask = W(response.omcimsg.auccontent[0],response.omcimsg.auccontent[1]);
    usSupportMask = (1 map.num))-1;
    if( 0 != (wMask & usSupportMask))
    {
        OmciPrint_warn("[%s] check mask warning: (meclass[%u], meid[%u], msgtype[%u], mask[0x%x], unsupport mask[0x%x])!
\r",
                       FUNCTION_NAME, vpIn->omci_header.wmeclass, vpIn->omci_header.wmeid, vpIn->omci_header.ucmsgtype, wMask, usSupportMask);
    }
}
對(duì)usSupportMask賦值及判斷的語(yǔ)句(第6~7行),用于校驗(yàn)掩碼是否越界。為更具可讀性,將其封裝為如下函數(shù):
/******************************************************************************
* 函數(shù)名稱(chēng):  OmciIsMaskOutOfLimit
* 功能說(shuō)明:  判斷實(shí)體屬性掩碼是否越界(比特1數(shù)目超過(guò)屬性數(shù)目)
* 輸入?yún)?shù):  INT16U wMeMask  :實(shí)體掩碼
*           INT8U ucAttrNum :屬性數(shù)目
* 輸出參數(shù):  NA
* 返 回 值:  BOOL
******************************************************************************/
BOOL OmciIsMaskOutOfLimit(INT16U wMeMask, INT8U ucAttrNum)
{
    //wMeMask     :mmmm mmmm mmm0 m000
    //wInvertMask :0000 0000 000i iiii
    INT8U wInvertMask = (1 1;
    return (0 != (wMeMask & wInvertMask));
}
封裝后的函數(shù)名恰當(dāng)?shù)仄鸬健白悦枋觥钡淖饔谩?br /> 3.3 在線調(diào)測(cè)工程該產(chǎn)品作為嵌入式終端,需要在Linux系統(tǒng)中編譯打包版本,然后將其下載到目標(biāo)單板上運(yùn)行。這種交叉編譯方式對(duì)于單個(gè)模塊的調(diào)試而言,效率無(wú)疑比較低下。
為提高調(diào)測(cè)效率,在Linux服務(wù)器搭建在線調(diào)測(cè)工程。即提取OMCI模塊代碼,稍作改造后直接在服務(wù)器上編譯和運(yùn)行。這樣就可避免每次修改代碼都要重啟單板升級(jí)大版本,調(diào)測(cè)效率極高。
為使模塊可獨(dú)立運(yùn)行,需要編寫(xiě)模擬接口以屏蔽底層調(diào)用,并裁減暫不必要的特性(如線程和通信)等。
3.4 模擬數(shù)據(jù)庫(kù)OMCI模塊使用某內(nèi)存數(shù)據(jù)庫(kù)來(lái)管理需要持久化的實(shí)體信息,但該數(shù)據(jù)庫(kù)代碼內(nèi)調(diào)用了大量平臺(tái)相關(guān)的接口,不利于實(shí)現(xiàn)模塊的在線調(diào)測(cè)。因此,作者研讀源代碼后編寫(xiě)了一個(gè)模擬數(shù)據(jù)庫(kù)。該庫(kù)仿照模塊使用的幾個(gè)原庫(kù)接口及行為,模擬接口內(nèi)部校驗(yàn)均增加錯(cuò)誤信息打印,以便于排障。
此外,在數(shù)據(jù)庫(kù)接口原語(yǔ)的基礎(chǔ)上二次封裝統(tǒng)一接口,一舉消除模塊內(nèi)數(shù)據(jù)庫(kù)操作代碼的凌亂和重復(fù)。
3.5 自動(dòng)化測(cè)試沒(méi)有測(cè)試保護(hù)網(wǎng)的重構(gòu),無(wú)異于沒(méi)有血源的外科手術(shù)。
首先,公共接口和函數(shù)均提供有相應(yīng)的測(cè)試函數(shù),兼做示例和用例。如:
//Start of ByteArray2StrSeqTest//
VOID ByteArray2StrSeqTest(VOID)
{
    //ByteArray2StrSeq函數(shù)算法不甚優(yōu)美和嚴(yán)謹(jǐn),應(yīng)多加測(cè)試驗(yàn)證,如有可能盡量?jī)?yōu)化。
    INT8U ucTestIndex = 1;
    INT8U pucByteArray[] = {0xD7, 0x8F, 0xF5, 0x73, 0xB7, 0xF0, 0x00, 0xE8, 0x2C, 0x3B};
    CHAR pStrSeq[50] = {0};
    //Time Consumed(x86_gcc3.2.3_glibc2.2.5): 72us
    memset(pStrSeq, 0, sizeof(pStrSeq));
    ByteArray2StrSeq(pucByteArray, 4, 1, pStrSeq);
    printf("[%s] Result: %s, pStrSeq = %s!
", __FUNCTION__, ucTestIndex++,
           strcmp(pStrSeq, "1-2,4,6-9,13-20,22,24,26-28,31-32") ? "ERROR" : "OK", pStrSeq);
    //Time Consumed(x86_gcc3.2.3_glibc2.2.5): 7us
    memset(pStrSeq, 0, sizeof(pStrSeq));
    ByteArray2StrSeq(pucByteArray, 4, 0, pStrSeq);
    printf("[%s] Result: %s, pStrSeq = %s!!!
", __FUNCTION__, ucTestIndex++,
           strcmp(pStrSeq, "0-1,3,5-8,12-19,21,23,25-27,30-31") ? "ERROR" : "OK", pStrSeq);
    //Time Consumed(x86_gcc3.2.3_glibc2.2.5): 4us
    memset(pStrSeq, 0, sizeof(pStrSeq));
    ByteArray2StrSeq(&pucByteArray[4], 2, 1, pStrSeq);
    printf("[%s] Result: %s, pStrSeq = %s!
", __FUNCTION__, ucTestIndex++,
           strcmp(pStrSeq, "1,3-4,6-12") ? "ERROR" : "OK", pStrSeq);
    //Time Consumed(x86_gcc3.2.3_glibc2.2.5): 4us
    memset(pStrSeq, 0, sizeof(pStrSeq));
    ByteArray2StrSeq(&pucByteArray[6], 2, 1, pStrSeq);
    printf("[%s] Result: %s, pStrSeq = %s!
", __FUNCTION__, ucTestIndex++,
           strcmp(pStrSeq, "9-11,13") ? "ERROR" : "OK", pStrSeq);
    //Time Consumed(x86_gcc3.2.3_glibc2.2.5): 5us
    memset(pStrSeq, 0, sizeof(pStrSeq));
    ByteArray2StrSeq(&pucByteArray[8], 2, 1, pStrSeq);
    printf("[%s] Result: %s, pStrSeq = %s!
", __FUNCTION__, ucTestIndex++,
           strcmp(pStrSeq, "3,5-6,11-13,15-16") ? "ERROR" : "OK", pStrSeq);
}
//End of ByteArray2StrSeqTest//
此外,模塊內(nèi)還增加自動(dòng)化測(cè)試功能(TestSuite),可用來(lái)驗(yàn)證批量或單個(gè)實(shí)體的配置和查詢操作。批量測(cè)試結(jié)果統(tǒng)計(jì)如下(省略各實(shí)體的具體測(cè)試結(jié)果):

發(fā)表回復(fù)

本版積分規(guī)則


聯(lián)系客服 關(guān)注微信 下載APP 返回頂部 返回列表