#104, add option cat. for the metrics + put ana in separate unit

This commit is contained in:
Basile Burg 2016-11-18 12:11:10 +01:00
parent 736c4bb5b2
commit 6e10f889ee
No known key found for this signature in database
GPG Key ID: 1868039F415CB8CF
6 changed files with 342 additions and 85 deletions

View File

@ -9,6 +9,11 @@ import
version(unittest){} else import version(unittest){} else import
common; common;
/**
* Retrieves the count and unique count of the operands and operators of
* each function (inc. methods) of a module. After the call the results are
* serialized as JSON in te standard output.
*/
void performHalsteadMetrics(const(Module) mod) void performHalsteadMetrics(const(Module) mod)
{ {
HalsteadMetric hm = construct!(HalsteadMetric); HalsteadMetric hm = construct!(HalsteadMetric);
@ -91,7 +96,7 @@ private final class HalsteadMetric: ASTVisitor
version(unittest) version(unittest)
{ {
import std.stdio; import std.stdio: writeln;
writeln(functions[$-1]); writeln(functions[$-1]);
writeln('\t',operators); writeln('\t',operators);
writeln('\t',operands); writeln('\t',operands);
@ -479,7 +484,6 @@ version(unittest)
Function test(string source) Function test(string source)
{ {
import std.typecons;
HalsteadMetric hm = parseAndVisit!(HalsteadMetric)(source); HalsteadMetric hm = parseAndVisit!(HalsteadMetric)(source);
scope(exit) destruct(hm); scope(exit) destruct(hm);
return hm.functions[$-1]; return hm.functions[$-1];

View File

@ -244,7 +244,7 @@
<PackageName Value="LCL"/> <PackageName Value="LCL"/>
</Item7> </Item7>
</RequiredPackages> </RequiredPackages>
<Units Count="55"> <Units Count="56">
<Unit0> <Unit0>
<Filename Value="coedit.lpr"/> <Filename Value="coedit.lpr"/>
<IsPartOfProject Value="True"/> <IsPartOfProject Value="True"/>
@ -529,6 +529,10 @@
<HasResources Value="True"/> <HasResources Value="True"/>
<ResourceBaseClass Value="Form"/> <ResourceBaseClass Value="Form"/>
</Unit54> </Unit54>
<Unit55>
<Filename Value="..\src\ce_halstead.pas"/>
<IsPartOfProject Value="True"/>
</Unit55>
</Units> </Units>
</ProjectOptions> </ProjectOptions>
<CompilerOptions> <CompilerOptions>

View File

@ -12,7 +12,7 @@ uses
ce_dockoptions, ce_shortcutseditor, ce_mru, ce_processes, ce_dockoptions, ce_shortcutseditor, ce_mru, ce_processes,
ce_dialogs, ce_dubprojeditor, ce_controls, ce_dfmt, ce_lcldragdrop, ce_dialogs, ce_dubprojeditor, ce_controls, ce_dfmt, ce_lcldragdrop,
ce_stringrange, ce_dlangmaps, ce_projgroup, ce_projutils, ce_d2synpresets, ce_stringrange, ce_dlangmaps, ce_projgroup, ce_projutils, ce_d2synpresets,
ce_dastworx, ce_dbgitf, ce_ddemangle, ce_dubproject; ce_dastworx, ce_dbgitf, ce_ddemangle, ce_dubproject, ce_halstead;
{$R *.res} {$R *.res}

288
src/ce_halstead.pas Normal file
View File

@ -0,0 +1,288 @@
unit ce_halstead;
{$I ce_defines.inc}
interface
uses
Classes, SysUtils, xfpjson, math,
ce_common, ce_observer, ce_interfaces, ce_dastworx, ce_writableComponent,
ce_synmemo;
type
TBugCountMethod = (pow23div3000, div3000);
THalsteadMetricsBase = class(TWritableLfmTextComponent)
private
fMaxBugsPerFunction: single;
fMaxBugsPerModule: single;
fMaxVolumePerFunction: integer;
fShowAllResults: boolean;
fBugCountMethod: TBugCountMethod;
procedure setMaxBugsPerFunction(value: single);
procedure setMaxBugsPerModule(value: single);
procedure setMaxVolumePerFunction(value: integer);
published
property maxBugsPerFunction: single read fMaxBugsPerFunction write setMaxBugsPerFunction;
property maxBugsPerModule: single read fMaxBugsPerModule write setMaxBugsPerModule;
property maxVolumePerFunction: integer read fMaxVolumePerFunction write setMaxVolumePerFunction;
property showAllResults: boolean read fShowAllResults write fShowAllResults default false;
property bugCountMethod: TBugCountMethod read fBugCountMethod write fBugCountMethod default pow23div3000;
public
constructor create(aOwner: TComponent); override;
procedure assign(value: TPersistent); override;
end;
THalsteadMetrics = class(THalsteadMetricsBase, ICEEditableOptions)
private
fBackup: THalsteadMetricsBase;
fMsgs: ICEMessagesDisplay;
function optionedWantCategory(): string;
function optionedWantEditorKind: TOptionEditorKind;
function optionedWantContainer: TPersistent;
procedure optionedEvent(event: TOptionEditorEvent);
function optionedOptionsModified: boolean;
public
constructor create(aOwner: TComponent); override;
destructor destroy; override;
procedure measure(document: TCESynMemo);
end;
function metrics: THalsteadMetrics;
implementation
var
fMetrics: THalsteadMetrics = nil;
const
optFname = 'metrics.txt';
function metrics: THalsteadMetrics;
begin
if fMetrics.isNil then
fMetrics := THalsteadMetrics.create(nil);
result := fMetrics;
end;
constructor THalsteadMetricsBase.create(aOwner: TComponent);
begin
inherited;
fMaxBugsPerFunction:= 0.5;
fMaxBugsPerModule:= 2;
fMaxVolumePerFunction:= 1000;
end;
procedure THalsteadMetricsBase.assign(value: TPersistent);
var
s: THalsteadMetricsBase;
begin
if value is THalsteadMetricsBase then
begin
s := THalsteadMetricsBase(value);
fMaxBugsPerFunction:=s.fMaxBugsPerFunction;
fMaxBugsPerModule:=s.fMaxBugsPerModule;
fMaxVolumePerFunction:=s.fMaxVolumePerFunction;
fShowAllResults:=s.fShowAllResults;
fBugCountMethod:=s.fBugCountMethod;
end
else inherited;
end;
procedure THalsteadMetricsBase.setMaxBugsPerFunction(value: single);
begin
if value < 0 then
value := 0;
fMaxBugsPerFunction:=value;
end;
procedure THalsteadMetricsBase.setMaxBugsPerModule(value: single);
begin
if value < 0 then
value := 0;
fMaxBugsPerModule:=value;
end;
procedure THalsteadMetricsBase.setMaxVolumePerFunction(value: integer);
begin
if value < 0 then
value := 0;
fMaxVolumePerFunction:=value;
end;
constructor THalsteadMetrics.create(aOwner: TComponent);
var
f: string;
begin
inherited;
fBackup:= THalsteadMetricsBase.create(self);
f := getCoeditDocPath + optFname;
if f.fileExists then
loadFromFile(f);
fBackup.assign(self);
EntitiesConnector.addObserver(self);
end;
destructor THalsteadMetrics.destroy;
begin
EntitiesConnector.removeObserver(self);
saveTofile(getCoeditDocPath + optFname);
inherited;
end;
function THalsteadMetrics.optionedWantCategory(): string;
begin
exit('Code metrics');
end;
function THalsteadMetrics.optionedWantEditorKind: TOptionEditorKind;
begin
exit(oekGeneric);
end;
function THalsteadMetrics.optionedWantContainer: TPersistent;
begin
fBackup.assign(self);
exit(self);
end;
procedure THalsteadMetrics.optionedEvent(event: TOptionEditorEvent);
begin
case event of
oeeAccept: fBackup.assign(self);
oeeCancel: assign(fBackup);
end;
end;
function THalsteadMetrics.optionedOptionsModified: boolean;
begin
exit(false);
end;
procedure THalsteadMetrics.Measure(document: TCESynMemo);
function checkFunction(const obj: TJSONObject; var bugsSum: single): boolean;
var
n1, sn1, n2, sn2: integer;
val: TJSONData;
voc, len, line: integer;
vol, dif, eff: single;
cpl, bgs: single;
vwn: boolean;
bwn: boolean;
const
bgt: array[boolean] of TCEAppMessageKind = (amkInf, amkWarn);
begin
result := true;
val := obj.Find('n1Count');
if val.isNil then
exit;
n1 := val.AsInteger;
val := obj.Find('n1Sum');
if val.isNil then
exit;
sn1 := val.AsInteger;
val := obj.Find('n2Count');
if val.isNil then
exit;
n2 := val.AsInteger;
val := obj.Find('n2Sum');
if val.isNil then
exit;
sn2 := val.AsInteger;
val := obj.Find('line');
if val.isNil then
exit;
line := val.AsInteger;
val := obj.Find('name');
if val.isNil then
exit;
voc := max(1, n1 + n2);
len := max(1, sn1 + sn2);
cpl := n1*log2(n1) + n2*log2(n2);
vol := len * log2(voc);
dif := n1 * 0.5 * (sn2 / n2);
eff := dif * vol;
case fBugCountMethod of
pow23div3000: bgs := power(eff, 0.666667) / 3000;
div3000: bgs := eff / 3000;
end;
bugsSum += bgs;
vwn := (fMaxVolumePerFunction <> 0) and not IsNan(vol) and (vol >= fMaxVolumePerFunction);
bwn := (fMaxBugsPerFunction <> 0) and not IsNan(bgs) and (bgs >= fMaxBugsPerFunction);
result := not vwn and not bwn;
if fShowAllResults or not result then
begin
fMsgs.message(format('%s(%d): metrics for "%s"',
[document.fileName, line, val.AsString]), document, amcEdit, amkInf);
fMsgs.message(format(' Vocabulary: %d', [voc]), document, amcEdit, amkInf);
fMsgs.message(format(' Length: %d', [len]), document, amcEdit, amkInf);
fMsgs.message(format(' Calculated program length: %f', [cpl]), document, amcEdit, amkInf);
fMsgs.message(format(' Volume: %.2f', [vol]), document, amcEdit, bgt[vwn]);
fMsgs.message(format(' Error proneness: %.2f', [dif]), document, amcEdit, amkInf);
fMsgs.message(format(' Implementation effort: %.2f', [eff]), document, amcEdit, amkInf);
fMsgs.message(format(' Implementation Time: %.2f secs.', [eff / 18]), document, amcEdit, amkInf);
fMsgs.message(format(' Estimated bugs: %.2f', [bgs]), document, amcEdit, bgt[bwn]);
end;
end;
var
jsn: TJSONObject = nil;
fnc: TJSONObject = nil;
val: TJSONData;
arr: TJSONArray;
bgS: single = 0;
noW: boolean = true;
fnW: boolean;
i: integer;
begin
if not fShowAllResults
and ((maxBugsPerFunction = 0) and (maxBugsPerModule = 0) and (maxVolumePerFunction = 0)) then
exit;
if not assigned(fMsgs) then
fMSgs := getMessageDisplay;
getHalsteadMetrics(document.Lines, jsn);
if jsn.isNil then
exit;
val := jsn.Find('functions');
if val.isNil or (val.JSONType <> jtArray) then
exit;
arr := TJSONArray(val);
for i := 0 to arr.Count-1 do
begin
fnc := TJSONObject(arr.Objects[i]);
if fnc.isNotNil then
begin
fnW := checkFunction(fnc, bgS);
noW := noW and fnW;
end;
end;
if (fMaxBugsPerModule <> 0) and (bgS >= fMaxBugsPerModule) then
fMsgs.message(format('The estimated number of bugs (%.2f) in this module exceeds the limit', [bgS]),
document, amcEdit, amkWarn)
else if noW then
fMsgs.message('No abnormal values in the code metrics',
document, amcEdit, amkInf);
jsn.Free;
end;
initialization
fMetrics := THalsteadMetrics.create(nil);
finalization
fMetrics.Free;
end.

View File

@ -1468,7 +1468,7 @@ object CEMainForm: TCEMainForm
OnCloseQuery = FormCloseQuery OnCloseQuery = FormCloseQuery
OnDropFiles = FormDropFiles OnDropFiles = FormDropFiles
ShowHint = True ShowHint = True
LCLVersion = '1.6.0.4' LCLVersion = '1.6.2.0'
object mainMenu: TMainMenu object mainMenu: TMainMenu
Images = imgList Images = imgList
top = 1 top = 1
@ -2389,6 +2389,42 @@ object CEMainForm: TCEMainForm
end end
object MenuItem77: TMenuItem object MenuItem77: TMenuItem
Action = actFileMetricsHalstead Action = actFileMetricsHalstead
Bitmap.Data = {
36040000424D3604000000000000360000002800000010000000100000000100
2000000000000004000064000000640000000000000000000000000000000000
0000000000000000000000000000000000000000000000000033000000000000
00330000003300000000000000000000000000000000000000002178AD002178
AD002178AD002178AD002178AD002177AC00000000332270B3FF00000033008A
48FF008A4AFF00000033008E4E00008D4D0000A3620000A160002178AD002178
AD002178AD002178AD002177AD00000000331E6EAAFF6FF0FFFF00843CFF00E2
9BFF00E39CFF008647FF00000033008C4D0000A3620000A160002178AD002178
AD002178AD002177AC00000000331D6FA8FF6DEDFFFF008234FF00E096FF00DE
97FF00DE97FF00E29BFF008646FF0000003300A2610000A160002178AD002178
AD002177AC00000000331C6EA5FF51C7F9FF00822CFF00E394FF00E196FF007F
38FF007F38FF00E096FF00E19BFF008445FF0000003300A160002178AD002177
AC00000000331B6DA3FF6BE6FFFF4DD6FFFF52D8FFFF008536FF008334FF55D7
FFFF71E1FFFF007E32FF00DF95FF00E09AFF009C5BFF000000332077AC000000
00331B6DA3FF4EC1F0FF45D1FFFF45CEFFFF47D4FFFF31351CFF33C1DAFF48D0
FFFF47CFFFFF48C2D9FF009648FF00E095FF00E49CFF009F5DFF00000033196D
A3FF6FE2FFFF3ECCFFFF3FC9FFFF3FC9FFFF3FCEFFFF3FD3FFFF40CEFFFF3FCA
FFFF3FC8FFFF40CAFFFF73DCFFFF008231FF00883CFF00A457001C73A8FF9BF5
FFFF36C8FFFF39C4FFFF3BC4FFFF3BC6FFFF39CCFFFF643F30FF39CCFFFF3BC6
FFFF3BC4FFFF39C4FFFF38C7FFFF9EF2FFFF206FB1FF2473B5001E75AA00196D
A3FF78E0FFFF32C1FFFF34C0FFFF35C2FFFF34CAFFFF563529FF34CAFFFF35C2
FFFF34C0FFFF32C1FFFF78E0FFFF196DA4FF1F75AB002177AE002077AC001E74
A9001A6DA2FF7CDFFFFF2CBDFFFF2FBEFFFF2FC6FFFF492A1CFF2FC6FFFF2EBE
FFFF2CBDFFFF7CDFFFFF1A6DA2FF1E74A9002077AC002178AD002178AD002177
AC001E74A9001A6EA2FF7EDFFFFF29BBFFFF2AC0FFFF3C1808FF2AC0FFFF27BA
FFFF7DDEFFFF1A6DA2FF1E74A9002177AC002178AD002178AD002178AD002178
AD002177AC001E75AA001B6FA3FF52B8F1FF22B9FFFF22BAFFFF21B8FFFF81DD
FFFF1A6DA2FF1E74A9002177AC002178AD002178AD002178AD002178AD002178
AD002178AD002178AC001F75AA001B6FA3FF83DCFFFF16B2FFFF82DBFFFF1A6E
A2FF1E74A9002177AC002178AD002178AD002178AD002178AD002178AD002178
AD002178AD002178AD002177AC001E75AA00186EA4FFD0F9FFFF186EA3FF1E75
A9002177AC002178AD002178AD002178AD002178AD002178AD002178AD002178
AD002178AD002178AD002178AD002077AC001E75AA001B73A8FF1E75AA002077
AC002178AD002178AD002178AD002178AD002178AD002178AD00
}
end end
object MenuItem60: TMenuItem object MenuItem60: TMenuItem
Action = actFileOpenContFold Action = actFileOpenContFold

View File

@ -7,7 +7,7 @@ interface
uses uses
Classes, SysUtils, LazFileUtils, SynEditKeyCmds, SynHighlighterLFM, Forms, Classes, SysUtils, LazFileUtils, SynEditKeyCmds, SynHighlighterLFM, Forms,
StdCtrls, AnchorDocking, AnchorDockStorage, AnchorDockOptionsDlg, Controls, StdCtrls, AnchorDocking, AnchorDockStorage, AnchorDockOptionsDlg, Controls,
Graphics, strutils, Dialogs, Menus, ActnList, ExtCtrls, process, math, Graphics, strutils, Dialogs, Menus, ActnList, ExtCtrls, process,
{$IFDEF WINDOWS}Windows, {$ENDIF} XMLPropStorage, SynExportHTML, fphttpclient, {$IFDEF WINDOWS}Windows, {$ENDIF} XMLPropStorage, SynExportHTML, fphttpclient,
xfpjson, xjsonparser, xjsonscanner, xfpjson, xjsonparser, xjsonscanner,
ce_common, ce_dmdwrap, ce_ceproject, ce_synmemo, ce_writableComponent, ce_common, ce_dmdwrap, ce_ceproject, ce_synmemo, ce_writableComponent,
@ -15,7 +15,8 @@ uses
ce_search, ce_miniexplorer, ce_libman, ce_libmaneditor, ce_todolist, ce_observer, ce_search, ce_miniexplorer, ce_libman, ce_libmaneditor, ce_todolist, ce_observer,
ce_toolseditor, ce_procinput, ce_optionseditor, ce_symlist, ce_mru, ce_processes, ce_toolseditor, ce_procinput, ce_optionseditor, ce_symlist, ce_mru, ce_processes,
ce_infos, ce_dubproject, ce_dialogs, ce_dubprojeditor,{$IFDEF UNIX} ce_gdb,{$ENDIF} ce_infos, ce_dubproject, ce_dialogs, ce_dubprojeditor,{$IFDEF UNIX} ce_gdb,{$ENDIF}
ce_dfmt, ce_lcldragdrop, ce_projgroup, ce_projutils, ce_stringrange, ce_dastworx; ce_dfmt, ce_lcldragdrop, ce_projgroup, ce_projutils, ce_stringrange, ce_dastworx,
ce_halstead;
type type
@ -2876,86 +2877,10 @@ begin
end; end;
procedure TCEMainForm.actFileMetricsHalsteadExecute(Sender: TObject); procedure TCEMainForm.actFileMetricsHalsteadExecute(Sender: TObject);
procedure computeMetrics(const obj: TJSONObject);
var
n1, sn1, n2, sn2: integer;
val: TJSONData;
voc, len, line: integer;
vol, dif, eff: single;
bgs: single;
const
bgt: array[boolean] of TCEAppMessageKind = (amkInf, amkWarn);
begin begin
val := obj.Find('n1Count'); if fDoc.isNil or not fDoc.isDSource then
if val.isNil then
exit; exit;
n1 := val.AsInteger; metrics.measure(fDoc);
val := obj.Find('n1Sum');
if val.isNil then
exit;
sn1 := val.AsInteger;
val := obj.Find('n2Count');
if val.isNil then
exit;
n2 := val.AsInteger;
val := obj.Find('n2Sum');
if val.isNil then
exit;
sn2 := val.AsInteger;
val := obj.Find('line');
if val.isNil then
exit;
line := val.AsInteger;
val := obj.Find('name');
if val.isNil then
exit;
fMsgs.message(format('%s(%d): Halstead metrics for "%s"',
[fDoc.fileName, line, val.AsString]), fDoc, amcEdit, amkInf);
voc := n1 + n2;
len := sn1 + sn2;
vol := len * log2(voc);
dif := n1 * 0.5 * (sn2 / n2);
eff := dif * vol;
bgs := power(eff, 0.666667) / 3000;
fMsgs.message(format(' Vocabulary: %d', [voc]), fDoc, amcEdit, amkInf);
fMsgs.message(format(' Length: %d', [len]), fDoc, amcEdit, amkInf);
fMsgs.message(format(' Calculated program length: %f', [n1*log2(n1) + n2*log2(n2)]), fDoc, amcEdit, amkInf);
fMsgs.message(format(' Volume: %.2f', [vol]), fDoc, amcEdit, amkInf);
fMsgs.message(format(' Difficulty to review: %.2f', [dif]), fDoc, amcEdit, amkInf);
fMsgs.message(format(' Effort: %.2f', [eff]), fDoc, amcEdit, amkInf);
fMsgs.message(format(' Time required: %.2f secs.', [eff / 18]), fDoc, amcEdit, amkInf);
fMsgs.message(format(' Estimated bugs: %.2f', [bgs]), fDoc, amcEdit, bgt[bgs >= 0.25]);
end;
var
jsn: TJSONObject = nil;
fnc: TJSONObject = nil;
val: TJSONData;
arr: TJSONArray;
i: integer;
begin
if fDoc.isNil then
exit;
getHalsteadMetrics(fDoc.Lines, jsn);
if jsn.isNil then
exit;
val := jsn.Find('functions');
if val.isNil or (val.JSONType <> jtArray) then
exit;
arr := TJSONArray(val);
for i := 0 to arr.Count-1 do
begin
fnc := TJSONObject(arr.Objects[i]);
if fnc.isNotNil then
computeMetrics(fnc);
end;
jsn.Free;
end; end;
procedure TCEMainForm.actFileNewDubScriptExecute(Sender: TObject); procedure TCEMainForm.actFileNewDubScriptExecute(Sender: TObject);