说明
本文通过目录和代码两个层面分析某产品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后续可能更新,为便于同步相应代码未做精简)。编译后经验证,可正常配置和查询。