Pascal Analyzer 4 代码分析使用简要说明
概述
不管在那个开发团队中每个人的编写风格往往是千差万别能力也有高低,如何让别人快速看懂自己的代码维护你的代码。尽量避免不必要的简单错误,为编写代码作一定的约束是必不可少的。
如果你说我一个人不需要规范,其实不然。个人的代码风格能看出你的实际能力。犹如写文章一样杂乱有章,别人看都不愿意看,再精彩的故事也没用。日后自己维护也会头大。
因此有一份简单的代码编写约束是非常有必要的,不仅能提高可读性而且能提高代码质量。
1. 创建分析工程
打开软件后,有显示向导界面(默认关闭的,可以通过File菜单下的Run Wizard打开)在工程选择界面上选要分析的工程(如下图所示)
(图1)
选择工程后下一步,选择Delphi版本,选择后,在分析设置界面,我们可以选(Main File And Used Units),不用包括DFM文件,路径和条件设置界面中,可以把所有的选项都不选(这些可以在后面调整),选项确认后,会提示保存,可以选择保存(也可以不保存),选择后,工具进入分析阶段(需等一会儿),分析完,生成分析报告(如下图所示)
(图2)
也可以在进入界面后,直接新建工程(File菜单下的New Project 或工具条新建),新建后点击分析中的运行。
分析的工程文件(. dpr)不需要编译通过的,可以根据分析需要创建只包括需要分析的单元,这里要注意最好有工程同名的配置文件(.dof)。
2. 工程选项设置
在打开已有工程或新建工程后,选择工程属性(或通过New Project方式打开)
在这里需要关注的主要有:
a) 工程版本(Target version),该选项按实际情况选择(暂不确认对分析有什么影响)
b) 包含文件必须存在(Include Files must Exist),该选项可以不选(特别是有某些目录被排除的情况下,选择会导致分析报错并中断)
c) 使用工程设置(Use Delphi Project options if found),如果进行全工程分析时,可以选择此选择,选择此选项后,分析工具会使用工程中的搜索路径,分析的单元会很多。
d) Delphi环境路径(Append Delphi library path 和Browsing path)该选项建议不选。
e) 分析排除路径(Exclude search path),有设置时,分析工具将不分析排除路径下的单元,因工程经常会携带一下第3方的控件,这种情况下,把这些控件目录及子目录排除,会减少不必要的分析(注意排除路径可能不支持相对路径,最好使用绝对路径,或直接通过向导设置)。
3. 分析报告解读
该工具的分析报告内容比较多,根据一段时间的使用和分析,感觉以下分析项目意义较大:
Warnings(警告方面):
Interfaces passed as parameters without "const" directive(重要)
Constructors/destructors without calls to inherited(重要)
Interfaced identifiers that are used, but not outside of unit(一般)
Interfaced class identifiers that are public/published, but not used outside of unit(一般)
Possible bad object creation(一般)
Ambiguous references in with-blocks(一般)
Optimization(优化方面):
Missing "const" for unmodified string parameter(重要)
Local subprograms with references to outer local variables(一般)
Memory(内存方面):
Local objects with unprotected calls to Free(一般)
Code Reduction(代码精简方面):
Local identifiers that are set and referenced once(一般)
Local long strings that are initialized to empty string(一般)
Boolean assignment can be shortened(一般)
Convention Compliance(习惯约定方面)
Local identifiers that "shadow" outer scope identifiers(一般)
Inconsistent Case(大小写不一致方面)
Inconsistent case for same identifier(一般)
下面对这些关注方面进行简要说明,方便理解。
警告提示(Warnings)
重要关注方面
a) 不明确的引用关系(Ambiguous unit references)
该提示说明,使用的常量、函数、过程有重名现象,这些需要分析,尽量消除。
b) 接口类型参数未加const 声明(Interfaces passed as parameters without "const" directive)
function Save(ADB: IBaseDBIntf): Boolean; virtual;
接口作为参数传递,未加const 声明时,编译器在调用时 会加上 _AddRef 调用结束后会加上 _Release,这在频繁调用时会降低效率。
c) 解构/析构未继承调用(Constructors/destructors without calls to inherited)
如果类继承父类,解构时未继承调用可能会导致父类未初始化;析构时未继承调用可能会导致内存泄漏。类似的还包括(Destructors without override 未声明覆盖)。
d) 函数未设置返回值(Function result not set)
function TdlgInputHuanSuanLv.ActOkNotify: Boolean; begin DoOk; end;
该情况Delphi编译时,通常也有警告提示,函数未设置返回值可能会导致不可预料的结果。
一般关注方面
a) 对外定义(包括类、函数等声明)被使用了,但只在本单元内(Interfaced identifiers that are used, but not outside of unit)
简单示例如function BoolToInt
function BoolToInt(ABoolValue: Boolean): Integer; implementation {$R *.dfm} function BoolToInt(ABoolValue: Boolean): Integer; begin Result := Integer(ABoolValue); end; procedure TForm1.Button2Click(Sender: TObject); begin ShowMessage(IntToStr(BoolToInt(False))); end;
这个提示可以列出一些不必要公布的函数或类,对这些函数或类进行一些分析后,可能会有新的归类和设计。
b) 对外公布类的公共属性和方法,只在本单元使用了(Interfaced class identifiers that are public/published, but not used outside of unit)
这个提示可以用来帮助精简对外公用的属性和方法
c) 危险的退出(Dangerous Exit-statements)
在一个过程、函数中,如果开头有Exit 就会提示该信息,这通常有两种情况:一是该过程不使用了,建议删除该过程;另一种是调试时增加的代码,但忘记删除了,这个需要确认核实。
d) 空的强制执行模块(Empty finally-block)
有该提示的 Try … finally结构可以删除,提高代码效率。
e) 可能是错误类创建过程(Possible bad object creation)
procedure Proc; var Bmp : TBitmap; begin Bmp.Create; //错误 end; procedure Proc2; begin TNode.Create(Parent);//内存泄漏 end;
有这样提示的代码可能或导致错误或内存泄漏。
f) 危险的块声明引用(Ambiguous references in with-blocks)
procedure TForm1.Button1Click(Sender: TObject); begin with Button1 do begin Caption := 'test'; end; end;
这种提示通常发生在 块定义的类实例和过程所属的类有同名的属性或过程(如上图代码 Caption),这在复杂逻辑中往往会导致问题。
优化建议(Optimization)
重要关注方面
a) 未发生修改的字符串参数未声明为const (Missing "const" for unmodified string parameter)
function LoadSQL(ACondition: string; AOnlyStruct: Boolean = False):
因字符串有引用计数,在明确知道不需要修改的字符串参数前,请加上 const,这样可以提高效率。类似的参数还有 record、array、Interface等。
一般关注方面
a) 子过程使用过程外的变量(Local subprograms with references to outer local variables)
procedure TSelectTextHelper.InitFields(AFields: TSerializableFields; AItem: TSerializeItem); var i: Integer; aField: TSerializableField; procedure InnerAddField(AIdx: Integer); begin aField := TSerializableField(AFields.Append); aField.FieldName := aField.FieldItem.ClassFieldName; aField.DataType := TypeKindToFieldType(aField.FieldItem.DataType); end; begin AFields.Clear; if AItem <> nil then begin i := AItem.ObjFieldsMap.IndexByName(AItem.KeyField); InnerAddField(i); end; end;
如上面的代码所示,子过程使用外面的 aField,这种情况需要尽量避免(特别是影响效率的代码段),建议子过程尽量放在 变量声明前面。
内存建议(Memory)
一般关注方面
a) 过程内的对象未在保护结构内释放(Local objects with unprotected calls to Free)
var aList: TList; begin aList := TList.Create; ;//省略逻辑过程 aList.Free;
对象释放过程没有在保护结构时,如果释放前有异常抛出或提前退出时,会导致内存泄漏,建议释放过程尽量使用 try … finall结构。
b) 不对称的创建释放(Unbalanced Create/Free)
procedure LocalProc; var Obj: TMyClass; begin Obj := TmyClass.Create; Obj.DoSomething; end;
不对称释放,往往会导致内存泄漏,但分析工具对一些加入列表的、全局对象等判断不够,导致很多误报。另外还有下面这种情况也不提倡使用,最好把返回对象作为参数传入,这样可能避免疏忽引起的泄漏
function TForm1.GetList: TStringList; begin ;//省略逻辑 Result := TStringList.Create; end; //建议改成 procedure TForm1.GetList(Alist: TstringList); begin ;//省略逻辑 end;
代码精简建议(Code Reduction)
一般关注方面
a) 过程内本地变量只使用一次(Local identifiers that are set and referenced once)
var aData: TSearchResult_Data; begin if FDataList <> nil then begin aData := TSearchResult_Data(FDataList.Items[AIndex]); if not aData.Valid then Value := cst_Invalid_Price; end; end; //可精简为 begin if FDataList <> nil then begin if not TSearchResult_Data(FDataList.Items[AIndex]).Valid then Value := cst_Invalid_Price; end; end;
不在循环体内的只使用一次的变量往往可以精简,如上图,这种情况要具体情况具体分析,有很多时候,分析报告这方面的提示是不正确的。类似的还有Local identifiers that possibly are set and referenced once,这里不另外列举。
b) 过程内字符串变量初始化为空(Local long strings that are initialized to empty string)
var s: string; begin s := ''; ;//省略逻辑
如上例所示,s设置为空是没有必要的,编译器已经自动初始化了。
c) 布尔赋值可以更精简(Boolean assignment can be shortened)
if CheckedCount > 0 then NewState := True else NewState := False; //可以精简为 NewState := CheckedCount > 0;
该类提示往往是代码使用了条件判断再赋值的方式,该类代码可以优化另外也可以关注(Unneeded boolean comparisons)。
习惯约定建议(Convention Compliance)
一般关注方面
a) 参数重名情况Local identifiers that "shadow" outer scope identifiers
procedure TfrmMessageDlg.showButton(flags, timeCount: Integer); function GetButtons(Flags: integer): TMsgDlgButtons; begin ;//省略逻辑 end; begin ;//省略逻辑 end;
该情况一般发生在过程下有子过程中,如上面的代码所示,建议不要重名现象,重名会导致易读性下降。
大小写不一致建议(Inconsistent Case)
一般关注方面
a) 相同定义大小写不一致Inconsistent case for same identifier
procedure DoSaveEvent(ATotal, ACur: Integer); begin if Assigned(FSaveEvent) then FSaveEvent(aTotal, aCur); end;
如上面的代码所示,参数位大写,在使用时为小写,这个不符合编码规范,建议关注这方面的提示,使代码更规范。
4. 结束语
在团队合作中统一的代码规范是非常有必要的。提高代码质量、提高代码可读性等起到一定作用,上面只是列举了部分内容,分析报告中的General分支下还有很多好的建议,我们日常工作中都能应用到,另外该工具的帮助文档虽然简单,但各项建议的含义都有说明,大家不妨读一下。