首页 > 代码库 > 将模块代码量精简为2%的实践

将模块代码量精简为2%的实践

 

说明

     本文通过目录和代码两个层面分析某产品xDsl驱动模块代码,将其精简为原始代码量的2%。

 

一  完整代码

     某产品xDsl驱动模块目录结构如下所示。其中,二级目录Lxx1通常为芯片厂家代码,Lxx2为自定义适配代码。

     ├─L010

     │  ├─include

     │  └─source

     ├─L020

     │  ├─include

     │  ├─source

     …… ……

     ├─L200

     │  ├─L210

     …… ……

     │  ├─L260

     │  │  ├─L261

     │  │  │  ├─include

     │  │  │  └─source

     │  │  └─L262

     │  │      ├─include

     │  │      └─source

     …… ……

     │  └─SELT_DELT

     │      ├─include

     │      └─source

    该产品xDsl模块存在大量已废弃的目录,而在用的目录中也存在大量无用的代码。

 

二  精简代码

     此处“精简”意指直接剔除无用代码,而非借助重构等手段来削减代码量。精简主要分为两个步骤:1. 分析makefile文件,清理不予编译的目录;2. 分析预编译宏,删除当前在用目录中无用的代码。

2.1 清理废弃目录

     lnxmkdep.ini文件中定义各编译目录(待编译代码所在目录),xDSL驱动目前用于编译的代码目录如下: 

 1 [DSLADDR]
 2 USE=YES
 3 PATH=../../../xDSL/L030
 4 COPT_EXTRA=
 5 DEPEND_MODULES= all
 6 
 7 
 8 [L261]
 9 USE=YES
10 PATH=../../../xDSL//L200/L260/L261
11 COPT_EXTRA=
12 DEPEND_MODULES= all
13 
14 [L262]
15 USE=YES
16 PATH=../../../xDSL//L200/L260/L262
17 COPT_EXTRA=
18 DEPEND_MODULES= all
19 
20 [L271]
21 USE=YES
22 PATH=../../../xDSL//L200/L270/L271
23 COPT_EXTRA=
24 DEPEND_MODULES= all
25 
26 [L272]
27 USE=YES
28 PATH=../../../xDSL//L200/L270/L272
29 COPT_EXTRA=
30 DEPEND_MODULES= all
31 
32 [L290]
33 USE=YES
34 PATH=../../../xDSL//L200/L290
35 COPT_EXTRA=
36 DEPEND_MODULES= all
37 COPT_EXTRA=
38 DEPEND_MODULES= all
39 
40 [SELTDELT]
41 USE=YES
42 PATH=../../../xDSL/L200/SELT_DELT
43 COPT_EXTRA=-Os
44 DEPEND_MODULES= all
Listed Directories

     结合头文件包含情况,可知编译需要L010、L030、L200(L210、L230、L260、L270、L290、SELT_DELT)目录。进一步分析得知,仅用到L210和L230目录的若干头文件,而L260目录存在同名头文件且版本更新,故可替代前两个目录。同时,L270目录用于提供30A功能,而当前产品不需要支持该功能,故可删除。

    此时,在用的目录已精简为L010、L030、L200(L260、L290、SELT_DELT)。

2.2 删除无用代码

    xDsl驱动代码中包含对于其他各种单板和功能的支持,而多数单板已不再使用,某些功能也并未使用。这些支持在代码中主要通过预处理宏(即#if、#ifdef、#ifndef、#elif等含"#+if"关键字的条件编译语句,统称为#if语句)来控制,例如fsap_prj.h文件定义功能宏(INSTALL_MAP_BONDING等),config.mak文件定义编译宏(_INSTALL_VSLC等),其他宏定义则分散于xDsl目录下各文件中。

     删除无用代码,即寻找哪些控制编译的宏未定义,并将其控制的代码删除。但鉴于宏定义的分散性,人工查找和删除条件编译分支显然不现实,必须借助自动化工具。

     通过工具剔除未使用的条件编译分支,其原理如下:

     1. 在待处理代码(*.c、*.h)中#if语句句首插入gcc扩展的预编译头#warning。

     2. 编译待处理代码获取gcc编译输出并进行分析。

     3. 编译结果中”#warning”警告所对应的#if语句为TRUE,即所控制的代码段正在使用,应予保留;反之可删除。

     开源工具stripcc可较好地完成上述工作。在小工程上试用效果符合期望,但应用到本产品时似乎出现死锁,无法正常工作。该问题在研读和调试其源代码后仍未解决。

     以下将基于相同工作原理,借助Python脚本处理,分析预编译宏,标记在用的#if代码段。处理后的源代码示例如下(剔除/*TRUE*/ 标记后即为原始代码):

 1 #define BCM_BONDING_ENABLED
 2 #define BCM_ENABLED
 3 
 4 /*TRUE*/ #ifdef BCM_BONDING_ENABLED
 5          CodeLine1;
 6 #endif
 7 
 8 #ifdef BCM_DISABLED
 9           CodeLine2;
10 /*TRUE*/ #elif defined BCM_ENABLED
11           CodeLine3;
12 #else
13         #error Defination of BCM_DISABLED or BCM_ENABLED is Required!
14 #endif
15 
16 #ifdef BCM_TEST
17       CodeLine4;
18 /*TRUE*/ #else
19       CodeLine5;
20 #endif
Processed Code

     其中,若#if句前出现:

  • 一个/*TRUE*/:表示该#if句为逻辑真;
  • 多个/*TRUE*/:多出现于被广泛引用的头文件内,每次引用对为真的#if处增加一个/*TRUE*/;
  • 没有/*TRUE*/:表示该#if句为逻辑假。

     注意,若某文件“期望”出现/*TRUE*/ 标记(如#ifndef <头文件宏>)但未出现,则该文件很可能并未编译——可用于甄别无用的文件。

 

    【脚本示例】文件布局如下:

 

     其中,AddWarnsEx.py对源代码添加预编译头#warning。

 1 #!/usr/bin/python
 2 # -*- coding: utf-8 -*-
 3 
 4 import os, re
 5 
 6 
 7 CodeDirName = r"E:\ValidMacroExample"
 8 
 9 def AddWarnToFile(strPath):
10     OrigFd = open(strPath)
11     BackFd = open(strPath+"b", w+)
12 
13     OrigLineNo = 0;
14     while(1):
15         CurCodeLine = OrigFd.readline()
16         OrigLineNo = OrigLineNo + 1
17         if(len(CurCodeLine) == 0):
18             break;
19 
20         BackFd.write(CurCodeLine)
21         MacthRes = re.compile(".*#\s*(el|if).*").match(CurCodeLine)
22         if MacthRes != None:
23             InsertCodeLine = "#warning Reach code " + <File:+strPath +>+ <Line:+str(OrigLineNo)+> + "\n";
24             BackFd.write(InsertCodeLine)
25         
26     OrigFd.close()
27     BackFd.close()
28     return
29 
30     
31 def SwapFileStatus(strPath):
32     os.rename(strPath, strPath+t)
33     os.rename(strPath+b, strPath)
34     os.rename(strPath+t, strPath+b)
35     return
36 
37 
38 def DirTravel(DirPath):
39 
40     #遍历目录中的文件
41     if os.path.isdir(DirPath) == True:
42         FileList = os.listdir(DirPath)
43     else:
44         FileList = [os.path.basename(DirPath)]
45 
46     if FileList != []:
47         for File in FileList:
48             #检查目录名或文件名
49             if os.path.isdir(DirPath) == True:
50                 FilePath = DirPath + os.sep + File
51             else:
52                 FilePath = DirPath
53 
54             #文件类型为目录,递归
55             if os.path.isdir(FilePath) == True:
56                 DirTravel(FilePath)
57                 continue
58 
59             #识别C文件和H文件
60             SplitList = File.split(.)
61             #忽略无后缀名的文件
62             if len(SplitList) < 2:
63                 continue
64             FileType = SplitList[-1]
65             if FileType == c or FileType == h:
66                 AddWarnToFile(FilePath)
67                 SwapFileStatus(FilePath)
68     return
69 
70 
71 DirTravel(CodeDirName)
AddWarnsEx

     ChkMacrosEx.py分析编译结果并标记为真的#if语句。

 1 #!/usr/bin/python
 2 # -*- coding: utf-8 -*-
 3 
 4 
 5 import os, re
 6 
 7 CodeDirName = r"E:\ValidMacroExample"
 8 WarnFileName = CodeDirName + r"\Warns.txt"
 9 
10 
11 def RestoreFileStatus(strPath):
12     os.remove(strPath)
13     os.rename(strPath+b, strPath)
14     return
15 
16 
17 def ValidMacroInFile():
18     Fd = open(WarnFileName, r)
19 
20     while(1):
21         CurCodeLine = Fd.readline()
22         if(len(CurCodeLine) == 0):
23             break;
24 
25         MacthRes = re.compile(".*#warning.*<File:(.*)><Line:(\d+)>").match(CurCodeLine)
26         if MacthRes != None:
27             #根据编译警告信息打开相应的源文件(MacthRes.group(1)),修改相应行(MacthRes.group(2))
28             #全文读入,修改一行,全文写入。同一文件内多行#warning时,效率较低
29             SrcFd = open(MacthRes.group(1), r)
30             FileLines = SrcFd.readlines()
31             ModLineNo = int(MacthRes.group(2))-1
32             FileLines[ModLineNo] = "/*TRUE*/ " + FileLines[ModLineNo]
33             SrcFd.close()
34 
35             SrcFd = open(MacthRes.group(1), w)
36             SrcFd.writelines(FileLines)
37             SrcFd.close()
38         
39     Fd.close()
40     return
41 
42 
43 def RemoveBackFile(DirPath):
44 
45     #遍历目录中的文件
46     if os.path.isdir(DirPath) == True:
47         FileList = os.listdir(DirPath)
48     else:
49         FileList = [os.path.basename(DirPath)]
50 
51     if FileList != []:
52         for File in FileList:
53             #检查目录名或文件名
54             if os.path.isdir(DirPath) == True:
55                 FilePath = DirPath + os.sep + File
56             else:
57                 FilePath = DirPath
58 
59             #文件类型为目录,递归
60             if os.path.isdir(FilePath) == True:
61                 RemoveBackFile(FilePath)
62                 continue
63 
64             #识别C文件和H文件
65             SplitList = File.split(.)
66             #忽略无后缀名的文件
67             if len(SplitList) < 2:
68                 continue
69             FileType = SplitList[-1]
70             if FileType == c or FileType == h:
71                 RestoreFileStatus(FilePath)
72                 #os.remove(FilePath)
73     return
74 
75 
76 RemoveBackFile(CodeDirName)
77 ValidMacroInFile()
ChkMacrosEx

     Macro.c等为待处理的C源文件。

 1 //Macro.c(dir1)
 2 #define BCM_BONDING_ENABLED
 3 #define BCM_ENABLED
 4 
 5 #ifdef BCM_BONDING_ENABLED
 6       CodeLine1;
 7 #endif
 8 
 9 #ifdef BCM_DISABLED
10       CodeLine2;
11 #elif defined BCM_ENABLED
12       CodeLine3;
13 #else
14       #error Defination of BCM_DISABLED or BCM_ENABLED is Required!
15 #endif
16 
17 #ifdef BCM_TEST
18       CodeLine4;
19 #else
20       CodeLine5;
21 #endif
22 
23 
24 //Macro1.c(dir2)
25 #define BCM_BONDING_ENABLED
26 #define BCM_ENABLED
27 
28 #ifdef BCM_BONDING_ENABLED
29       CodeLine4;
30 #endif
31 
32 #ifdef BCM_ENABLED
33       CodeLine8;
34 #endif
35 
36 
37 //Macro2.c(dir2)
38 #define BCM_ENABLED
39 
40 #ifdef BCM_VECTOR_ENABLED
41       CodeLine4;
42 #endif
43 
44 #ifdef BCM_ENABLED
45       CodeLine8;
46 #endif
Macros

     Warns.txt为编译结果(暂以模拟内容代替)。

1 #warning Reach code <File:E:\ValidMacroExample\dir1\Macro.c><Line:4>
2 #warning Reach code <File:E:\ValidMacroExample\dir1\Macro.c><Line:10>
3 #warning Reach code <File:E:\ValidMacroExample\dir1\Macro.c><Line:18>
4 #warning Reach code <File:E:\ValidMacroExample\dir2\Macro1.c><Line:4>
5 #warning Reach code <File:E:\ValidMacroExample\dir2\Macro1.c><Line:8>
6 #warning Reach code <File:E:\ValidMacroExample\dir2\Macro2.c><Line:7>
Warns

     根据实际情况调整代码路径(当前为E:\ValidMacroExample)后,按如下步骤运行:

     1. 执行AddWarnsEx.py,生成添加#warning后的代码文件f.c(h)及其备份f.cb(hb)。

     2. 编译处理后的代码文件f.c(h),将编译结果重定向到Warns.txt内。

     3. 执行ChkMacrosEx.py,生成添加/*TRUE*/的代码文件,并自动删除备份文件。

     将Python脚本内待处理代码路径修改为xDsl模块路径后,即可用于实际工程代码的精简。经过处理的实际代码片段截图如下:

     更进一步,可分析处理后的/*TRUE*/标记,自动删除未编译的代码段,但需要严密的语法分析。此外,目前的脚本实现未考虑执行效率。因时间精力有限,暂时不予改进。     

 

三  效果评价

     清理目录和代码后,比较完整代码(Full)和精简代码(Lite)的规模如下:

版本

代码量()

系数

Full

9,024,746

1

Lite

221,964

0.0246

     可见,Lite代码行数约为Full代码的2%(考虑到BCM芯片SDK后续可能更新,为便于同步相应代码未做精简)。编译后经验证,可正常配置和查询。