<acronym id="s8ci2"><small id="s8ci2"></small></acronym>
<rt id="s8ci2"></rt><rt id="s8ci2"><optgroup id="s8ci2"></optgroup></rt>
<acronym id="s8ci2"></acronym>
<acronym id="s8ci2"><center id="s8ci2"></center></acronym>
0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

代碼重構的經驗總結

工程師進階筆記 ? 來源:博客園-clover_toeic ? 2023-08-23 10:10 ? 次閱讀

具體的重構手段可參考《代碼大全2》或《重構:改善既有代碼的設計》,本文不再班門弄斧,而側重重構時一些粗淺的“方法論”,旨在提高重構效率。

作者未采用重量級的重構工具,僅用到Source Insight的”Smart Rename”功能。也未使用CUnit等單元測試工具,而是通過在線調測和自動化測試保證代碼的正確性。

一 背景

MDU系列產品從他處接手,OMCI模塊相關人員含作者在內不過三五人。除新增功能的開發外,大量時間花費在處理遺留故障上。但該模塊代碼龐雜且可讀性差,導致大家僅了解其“大概輪廓”,難以放心地使用和維護。

此外,忙碌容易使人迷失方向。主要的時間精力花費在故障處理上時,自然無暇考慮整改代碼,從而陷入四處救火、疲于奔命的尷尬境地。

二 目標

重構的主要目的在于改善既有代碼的設計,而不是修改缺陷、新增功能等。

重構可以是修改變量名、重新安排目錄這樣簡單的物理重構,也可以是抽取子函數、精簡冗余設計這樣稍許復雜的邏輯重構。但均不改變現有代碼的功能。

重構可以將意大利面條式的雜亂代碼整理為千層餅式的整潔代碼。整潔的代碼更加健壯,因其便于建立完善的測試防護網。同時,新手老人均可放心地修改。

期望重構之后,代碼邏輯一目了然,擴展和修改非常方便,出現故障時能迅速定位和修復。前人摔跤過的地方后人不再栽倒,前人思考出的成果后人可直接借用??傊?,高度人性化,極大解放人力和腦力。

最初的想法是,通過重構部分流程和代碼(代碼先行),建立測試防護體系,生成階段報告,展現代碼質量(實例加數據)和故障收斂曲線。借助這樣的報告,可望獲得領導層的支持和宣貫,也有利于績效考核。

三 實踐

具體實踐時,作者并未進行純粹的“重構”,還兼做缺陷修改,并增加自動化測試等輔助功能。原則上,對既有代碼注重重構,對新增代碼注重復用。

3.1 代碼研讀

OMCI模塊代碼龐雜,分支眾多,上手困難(據稱半年勉強入門,一年才能熟練)。若不能有效掌握現有代碼,后續難免被迫付出時間健康而又得不到項目認同(事實上,模塊內發現的遺留故障源源不斷)。反之,若能全面掌握現有代碼,后續才可能通過反向工程、系統/代碼恢復和重構等手段,將模塊改造得更易開發和維護,最終解放編碼者自己。

為提高代碼研讀效率,可采用分工閱讀和代碼注釋的方法。

“分工閱讀”是指將模塊分為若干塊子功能(如協議解析、告警、統計、二層、語音等),組內每人負責一塊或幾塊,不定期地交流和輪值。

“代碼注釋”是指在學習代碼過程中,隨手注釋代碼(大至流程、函數,小至代碼行),功能、意圖、技巧、缺陷、疑問等均可(凡經過思考的地方都是可加注釋之處)。其中“疑問”既可咨詢兄弟產品同一模塊的同事再轉換為功能或意圖,也可由其他注釋者解答。

這樣做的好處是:避免重復鉆研;經驗積累;可供量化。

代碼可取產品最新版本,建立服務器公共代碼目錄(SVN管理更好)。注釋時不要覆蓋其他人的注釋即可。

建議注釋統一格式,便于識別和檢索,形如”//>”。以下示出一個代碼注釋實例:

caseOMCI_ME_ATTRIBUTE_2://Operationalstate
if(attr.attr.ucOperationState!=0&&attr.attr.ucAdminState!=1)//xywang0618>BUG:shouldbeucOperationState!
{
returnOMCI_FUNC_RETURN_OUT_OF_RANGE;
}
break;

3.2 可讀性

首先,規范變量、函數等命名。具體方法不再贅述。

其次,注釋到位,尤其是全局變量和通用函數。舉例如下:

/******************************************************************************
*函數名稱:ByteArray2StrSeq
*功能說明:掩碼字節數組字符串化
該數組元素為掩碼字節,將其所有值為1的比特位置轉換為指定格式的字符串
*輸入參數:pucByteArray:掩碼字節數組
ucByteNum:掩碼字節數組待轉換的有效字節數目
ucBaseVal:掩碼字符串起始字節對應的數值
*輸出參數: pStrSeq :掩碼字符串,以','、'-'間隔
形如0xD7(0b'11010111)--->"0-1,3,5-7"
*返回值: pStr :pStrSeq的指針備份,可用于strlen等鏈式表達式
*用法示例:INT8UaucByteArray[8]={0xD7,0x8F,0xF5,0x73};
CHARszSeq[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"
*注意事項:因本函數內含strcat,故調用前應按需初始化pStrSeq
******************************************************************************/
CHAR*ByteArray2StrSeq(INT8U*pucByteArray,INT8UucByteNum,INT8UucBaseVal,CHAR*pStrSeq);

最后,整改晦澀難懂的代碼。主要有兩種手段:

1) 改寫方法

以PON光路檢測為例,底層接口提供的光功率單位為0.1uW,OMCI協議Test消息上報的光功率單位為0.002dBuW,而Ani-G功率屬性單位則為0.002dBmW。

原有代碼轉換如下(為突出重點有所改編):

INT16SwRxPower=GetRxPowerInDot1uW();//接收光功率
if(wRxPower

可見,原實現中轉換關系非?;逎y懂。其實借助1dBuW=10*lg(1uW)和1dBuW-1dBmW=30dB兩個公式,經過簡單的數學推導即可得到更簡潔易懂的表達(為突出重點有所改編):

INT16SwRxPower=GetRxPowerInDot1uW();//接收光功率
//Test單位0.002dBuW,底層單位0.1uW,轉換關系T=(10*lg(B*0.1))/0.002=5000*(lgB-1)
wRxPower=(INT16S)(5000*(log10((DOUBLE)wRxPower)-1));

//Ani-G功率屬性單位0.002dBmW,Test結果單位0.002dBuW
//轉換關系A(dBmW)*0.002+30=T(dBuW)*0.002,即A=T-15000
INT16SwAniRxPwr=wRxPower-15000;

注意,原實現中誤認為Ani-G功率屬性與Test結果的單位相同,新實現已修正該錯誤。

2) 封裝函數

以實體屬性的掩碼校驗為例,原有代碼如下:

/*掩碼初校驗*/
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<omci_header.wmeclass,vpIn->omci_header.wmeid,vpIn->omci_header.ucmsgtype,wMask,usSupportMask);
}
}

對usSupportMask賦值及判斷的語句(第6~7行),用于校驗掩碼是否越界。為更具可讀性,將其封裝為如下函數:

/******************************************************************************
*函數名稱:OmciIsMaskOutOfLimit
*功能說明:判斷實體屬性掩碼是否越界(比特1數目超過屬性數目)
*輸入參數:INT16UwMeMask:實體掩碼
*INT8UucAttrNum:屬性數目
*輸出參數:NA
*返回值:BOOL
******************************************************************************/
BOOLOmciIsMaskOutOfLimit(INT16UwMeMask,INT8UucAttrNum)
{
//wMeMask:mmmmmmmmmmm0m000
//wInvertMask:00000000000iiiii
INT8UwInvertMask=(1<

封裝后的函數名恰當地起到“自描述”的作用。

3.3 在線調測工程

該產品作為嵌入式終端,需要在Linux系統中編譯打包版本,然后將其下載到目標單板上運行。這種交叉編譯方式對于單個模塊的調試而言,效率無疑比較低下。

為提高調測效率,在Linux服務器搭建在線調測工程。即提取OMCI模塊代碼,稍作改造后直接在服務器上編譯和運行。這樣就可避免每次修改代碼都要重啟單板升級大版本,調測效率極高。

為使模塊可獨立運行,需要編寫模擬接口以屏蔽底層調用,并裁減暫不必要的特性(如線程和通信)等。

3.4 模擬數據庫

OMCI模塊使用某內存數據庫來管理需要持久化的實體信息,但該數據庫代碼內調用了大量平臺相關的接口,不利于實現模塊的在線調測。因此,作者研讀源代碼后編寫了一個模擬數據庫。該庫仿照模塊使用的幾個原庫接口及行為,模擬接口內部校驗均增加錯誤信息打印,以便于排障。

此外,在數據庫接口原語的基礎上二次封裝統一接口,一舉消除模塊內數據庫操作代碼的凌亂和重復。

3.5 自動化測試

沒有測試保護網的重構,無異于沒有血源的外科手術。

首先,公共接口和函數均提供有相應的測試函數,兼做示例和用例。如:

//StartofByteArray2StrSeqTest//
VOIDByteArray2StrSeqTest(VOID)
{
//ByteArray2StrSeq函數算法不甚優美和嚴謹,應多加測試驗證,如有可能盡量優化。
INT8UucTestIndex=1;
INT8UpucByteArray[]={0xD7,0x8F,0xF5,0x73,0xB7,0xF0,0x00,0xE8,0x2C,0x3B};
CHARpStrSeq[50]={0};

//TimeConsumed(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);

//TimeConsumed(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);

//TimeConsumed(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);

//TimeConsumed(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);

//TimeConsumed(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);
}
//EndofByteArray2StrSeqTest//

此外,模塊內還增加自動化測試功能(TestSuite),可用來驗證批量或單個實體的配置和查詢操作。批量測試結果統計如下(省略各實體的具體測試結果):

28a19f96-40de-11ee-a2ef-92fbcf53809c.jpg

在上述測試結果中,Failed TestCase(s)最為關鍵,表示失敗的用例數目。此外,UnCompared TestCase(s)表示未做比較的條目數,如獲取時間等易變屬性的實體,無法預置恰當的期望結果,因此未做比較。測試過程中的打印信息可保存為日志文件,然后在打印日志中搜索Failure關鍵字,即可獲知哪些配置失敗。

當大量修改當前代碼時,借助上述自動化測試功能,可迅速獲知修改結果的影響。在開發新功能時,可先設計好測試用例和期望結果,然后按照“測試驅動開發”的模式來編碼,提高編碼效率和正確率。

3.6 直搗核心

傳統的重構步驟是先容易后困難,先外圍后核心。而作者反其道而行之,首先重構核心公共的代碼。這樣做的好處是:

1) 便于梳理頭文件包含關系

在線調測工程中最初只保留最為公共的代碼文件(如日志功能),重構并調測通過后再逐步添加其他單一功能的目標代碼。該過程中會按需拆分和/或組合文件,減少頭文件的嵌套和交叉引用。

2) 避免重復工作甚至返工

公共代碼重構后并封裝后,對較外圍的應用代碼重構時會更容易消除冗余。若先重構好外圍代碼,很可能發現某些邏輯可以統一到公共代碼內,從而導致大面積返工;而若先著手重構公共代碼,則通過研讀外圍代碼對其的使用方式,很容易及早甄別這些冗余性。

3) 迭代驗證

在重構后的公共代碼基礎上逐步疊加外圍代碼時,也在反復測試公共代碼的正確性和易用性。

4) 增強信心

先核心后外圍、逐步疊加驗證的過程可控,可增強大規模重構時的信心,緩解壓力。反之,若先重構好外圍代碼,等觸及核心時牽一發而動全身,壓力極大。

四 效果

在某產品代碼基礎上,進行OMCI模塊DB/LOG/實體存取/消息處理/性能統計等重構。經過三個多月的重構后,模塊代碼復雜度大幅下降(某核心源文件平均復雜度降為原先1/4),代碼顯著精簡(據不完全統計已精簡萬余行),同時更具可讀性。新增代碼的過程中,編寫大量工具類宏和函數,并增加OMCI自動化測試、內存檢測等實用功能。

通過LineCount和Source Monitor度量某功能代碼重構效果,如下表所示:

28b972b0-40de-11ee-a2ef-92fbcf53809c.png

此外,重構過程中積累的通用框架、代碼及經驗,可進一步應用到新的項目中。

審核編輯:湯梓紅

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 模塊
    +關注

    關注

    7

    文章

    2504

    瀏覽量

    46637
  • 服務器
    +關注

    關注

    12

    文章

    8185

    瀏覽量

    82742
  • 代碼
    +關注

    關注

    30

    文章

    4566

    瀏覽量

    66981
  • MDU
    MDU
    +關注

    關注

    0

    文章

    3

    瀏覽量

    1370
  • 單元測試
    +關注

    關注

    0

    文章

    33

    瀏覽量

    3090

原文標題:四 效果

文章出處:【微信號:工程師進階筆記,微信公眾號:工程師進階筆記】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    Altera SOPC專題競賽-經驗總結

    Altera SOPC專題競賽-經驗總結Altera SOPC專題競賽-經驗總結.docx
    發表于 08-10 18:19

    電源制作高手經驗總結

    電源制作高手經驗總結電源制作高手經驗總結 28頁.pdf (2.9 MB )
    發表于 06-05 02:05

    SOPC Builder/Nios 學習經驗總結

    SOPC Builder/Nios 學習經驗總結
    發表于 07-22 15:32 ?0次下載
    SOPC Builder/Nios 學習<b class='flag-5'>經驗總結</b>

    PCB板繪制經驗總結

    PCB板的繪制經驗總結:(1):畫原理圖的時候管腳的標注一定要用網絡 NET不要用文本TEXT否則導PCB設計的時候會出問題(2):畫完原理圖的時候一
    發表于 09-19 23:52 ?3733次閱讀

    線圈天線設計經驗總結

    線圈天線設計經驗總結
    發表于 09-12 17:21 ?224次下載

    做四軸飛行器的經驗總結

    做四軸飛行器的經驗總結,請各位大神共同學習,里面是一位大神做四軸飛行器的經驗總結
    發表于 11-11 16:52 ?0次下載

    開關電源測量的經驗總結

    開關電源測量的經驗總結,感興趣的小伙伴們可以瞧一瞧。
    發表于 09-18 17:34 ?0次下載

    模擬電路設計經驗總結

    模擬電子的相關知識學習教材資料——模擬電路設計經驗總結
    發表于 09-27 15:19 ?0次下載

    指針經驗總結

    指針經驗總結
    發表于 10-27 15:44 ?19次下載
    指針<b class='flag-5'>經驗總結</b>

    老電工20年經驗總結的接線技巧分享

    老電工20年經驗總結的接線技巧分享,具體的跟隨小編一起來了解一下。
    的頭像 發表于 07-23 10:54 ?1.7w次閱讀
    老電工20年<b class='flag-5'>經驗總結</b>的接線技巧分享

    scikit-learn K近鄰法類庫使用的經驗總結

    本文對scikit-learn中KNN相關的類庫使用做了一個總結,主要關注于類庫調參時的一個經驗總結,且非常詳細地介紹了類庫的參數含義。
    的頭像 發表于 01-13 11:49 ?2949次閱讀
    scikit-learn K近鄰法類庫使用的<b class='flag-5'>經驗總結</b>

    電路設計的一些經驗總結

    電路設計的一些經驗總結
    發表于 12-02 13:57 ?41次下載

    EMI整改經驗總結

    EMI整改經驗總結
    發表于 12-20 15:55 ?45次下載

    富士變頻器維修經驗總結

    富士變頻器維修經驗總結
    發表于 10-07 10:55 ?0次下載

    選擇燒結銀的經驗總結

    選擇燒結銀的經驗總結
    的頭像 發表于 12-17 15:46 ?500次閱讀
    選擇燒結銀的<b class='flag-5'>經驗總結</b>
    亚洲欧美日韩精品久久_久久精品AⅤ无码中文_日本中文字幕有码在线播放_亚洲视频高清不卡在线观看
    <acronym id="s8ci2"><small id="s8ci2"></small></acronym>
    <rt id="s8ci2"></rt><rt id="s8ci2"><optgroup id="s8ci2"></optgroup></rt>
    <acronym id="s8ci2"></acronym>
    <acronym id="s8ci2"><center id="s8ci2"></center></acronym>