文章目錄
- 警告提示(Warnings)
- 最佳化建議(Optimization)
- 記憶體建議(Memory)
- 代碼精簡建議(Code Reduction)
- 習慣約定建議(Convention Compliance)
- 大小寫不一致建議(Inconsistent Case)
概述
不管在那個Team Dev中每個人的編寫風格往往是千差萬別能力也有高低,如何讓別人快速看懂自己的代碼維護你的代碼。盡量避免不必要的簡單錯誤,為編寫代碼作一定的約束是必不可少的。
如果你說我一個人不需要規範,其實不然。個人的代碼風格能看出你的實際能力。猶如寫文章一樣雜亂有章,別人看都不願意看,再精彩的故事也沒用。日後自己維護也會頭大。
因此有一份簡單的代碼編寫約束是非常有必要的,不僅能提高可讀性而且能提高代碼品質。
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(警告方面):
Ambiguous unit references(重要)
Interfaces passed as parameters without "const" directive(重要)
Constructors/destructors without calls to inherited(重要)
Function result not set(重要)
Interfaced identifiers that are used, but not outside of unit(一般)
Interfaced class identifiers that are public/published, but not used outside of unit(一般)
Dangerous Exit-statements(一般)
Empty finally-block(一般)
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(一般)
Unbalanced Create/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分支下還有很多好的建議,我們日常工作中都能應用到,另外該工具的協助文檔雖然簡單,但各項建議的含義都有說明,大家不妨讀一下。