Overview
Examples
Screenshots
Comparisons
Applications
Download
Documentation
Bazaar
Status & Roadmap
FAQ
Authors & License
Forums
Funding Ultimate++
Search on this site













SourceForge.net Logo

CodeMetric

 

Investigating C++ cyclometric complexity

 

 

 

AnalyseGui.h

 

#ifndef _CppAnalyse_AnalyseGui_h

#define _CppAnalyse_AnalyseGui_h

 

#include <CtrlLib/CtrlLib.h>

 

using namespace Upp;

 

class WarningDisplay : public Display

{

    int limit;

    Color warningColor;

public:

    WarningDisplay(int limit, Color warningColor = Color(255, 128, 128));

    void PaintBackground(Draw& w, const Rect& r, const Value& q,

                         Color ink, Color paper, dword style) const;

};

 

class AnalyseGui : public TopWindow

{

public:

    typedef AnalyseGui CLASSNAME;

    MenuBar      menu;

    Splitter     splitter;

    LineEdit     source;

    ArrayCtrl    chart;

    LineEdit     textMetric;

    WarningDisplay ccDisplay, llocDisplay, depthDisplay;

 

    AnalyseGui();

    void MainMenu(Bar& menu);

    void Open();

    void UpdateMetric();

    void GotoFunction();

};

 

#endif

 

 

 

Analyse.h

 

#ifndef _CppAnalyse_Analyze_h_

#define _CppAnalyse_Analyze_h_

 

#include <CppBase/CppBase.h>

 

using namespace Upp;

 

struct CodeMetric

{

public:

    struct FunctionEntry : public Moveable<FunctionEntry>

    {

        String name;

        int pos;

        int cyclomaticComplexity1;

        int cyclomaticComplexity2;

        int logicalLinesOfCode;

        int scopeDepth;

    };

 

    int orphanLines, blankLines, commentLines;

    int totalLLOC, sumCC1, sumCC2, sumDepth;

    Vector<FunctionEntry> functions;

    String errors;

 

    explicit CodeMetric(const String &fileContent);

    String ToString() const;

 

private:

    typedef CodeMetric CLASSNAME;

    void StoreError(int line, const String &msg);

    void StoreMetric(const Parser::FunctionStat & functionStat);

    int LogicalLinesOfCode(const LexSymbolStat &symbolStat);

};

 

#endif

 

 

 

main.cpp

 

#include "AnalyseGui.h"

#include "Analyse.h"

 

#define IMAGECLASS Images

#define IMAGEFILE  <CodeMetric/AnalyseGui.iml>

#include <Draw/iml.h>

 

String defaultCode =

"/*"

"  This tool measures certain source code metrics.\n"

"  You can type, paste, or load the code you would like measure into this text area.\n"

"  Metrics provided are:\n"

"  - Cyclomatic complexity 1: counts the decision points per method, thus estimating the\n"

"  number of testcases needed for each method.\n"

"  - Cyclomatic complexity 2: CC1 extended with the implicit decisions created by the\n"

"  && || and ?: operators. Evaluation:\n"

"  CC2  1-10  simple, low risk method\n"

"  CC2  10-20 moderate complexity & risk method\n"

"  CC2  21-50 high complexity & risk method\n"

"  CC2  >50   too complex, untestable method, should be refactored\n"

"  - Depth: measures deepest scope embedding level per method. This estimates the human\n"

"  memory needed to keep in mind the current context. Any methods with depth > 5 is\n"

"  considered too complex, and candidate for refactoring\n"

"  - Logical Lines Of Code: estimates the amount of source code per method in a way\n"

"  which is mostly independent from code formatting style. Methods longer than 80 LLOC are\n"

"  too long, and should be refactored.\n"

"*/\n"

"int main()\n"

"{\n"

"    return 0;\n"

"}\n";

 

String defaultTitle = "CodeMetric GUI";

 

 

WarningDisplay::WarningDisplay(int limit, Color warningColor) :

 limit(limit), warningColor(warningColor)

{

}

 

void WarningDisplay::PaintBackground(Draw& w, const Rect& r, const Value& q,

                                     Color ink, Color paper, dword style) const

{

    int v = (int)q;

    if(v >= limit)

        paper = warningColor;

    Display::PaintBackground(w, r, q, ink, paper, style);

}

 

void AnalyseGui::MainMenu(Bar &bar)

{

    bar.Add("Load file", THISBACK(Open));

}

 

void AnalyseGui::Open()

{

    FileSelector fsel;

    fsel.ExecuteOpen("Select file");

    String fileName = fsel.Get();

    if(fileName == "")

        return;

    source <<= LoadFile(fileName);

    UpdateMetric();

    Title(defaultTitle + "  [" + fileName + "]");

}

 

void AnalyseGui::UpdateMetric()

{

    String s = ~source;

    CodeMetric metric(s);

    chart.Clear();

    for(int i = 0; i < metric.functions.GetCount(); i++)

    {

        CodeMetric::FunctionEntry & entry =

          metric.functions[i];

        chart.Add(entry.name,

                  entry.pos,

                  entry.cyclomaticComplexity1,

                  entry.cyclomaticComplexity2,

                  entry.logicalLinesOfCode,

                  entry.scopeDepth);

    }

 

    textMetric.SetData(metric.ToString());

}

 

void AnalyseGui::GotoFunction()

{

    int cursor = chart.GetCursor();

    if(cursor >= 0 && cursor < chart.GetCount())

    {

        int pos = (int)chart.Get(cursor, 1);

        source.SetCursor( source.GetPos(pos) );

        source.CenterCursor();

    }

}

 

AnalyseGui::AnalyseGui() :

 ccDisplay(50), llocDisplay(80), depthDisplay(5)

{

    Title(defaultTitle);

    Icon(Images::GaugeIcon);

    AddFrame(menu);

    menu.Set( THISBACK(MainMenu) );

    source <<= THISBACK(UpdateMetric);

    source <<= defaultCode;

    textMetric.SetEditable(false);

    chart.AddColumn("Function", 80);

    chart.AddColumn("Pos", 15);

    chart.AddColumn("CC1", 15).SetDisplay(ccDisplay);

    chart.AddColumn("CC2", 15).SetDisplay(ccDisplay);

    chart.AddColumn("LLOC", 15).SetDisplay(llocDisplay);

    chart.AddColumn("Depth", 15).SetDisplay(depthDisplay);

    chart.WhenLeftDouble = THISBACK(GotoFunction);

    UpdateMetric();

    splitter.Vert() << source << chart << textMetric;

    splitter.SetPos(9000, 1);

    splitter.SetPos(6500, 0);

    Add(splitter.SizePos());

    Sizeable().Zoomable();

}

 

 

GUI_APP_MAIN

{

    AnalyseGui().Run();

}

 

 

 

Analyse.cpp

 

#include <CppBase/CppBase.h>

#include "Analyse.h"

 

 

bool ContainsAt(const String &source, const String &pattern, int pos = 0)

{

    return    pos >= 0

           && pos + pattern.GetLength() <= source.GetLength()

           && 0 == memcmp(source.Begin() + pos, pattern.Begin(), pattern.GetLength());

}

 

bool StartsWith(const String &source, const String &pattern)

{

    return ContainsAt(source, pattern, 0);

}

 

bool EndsWith(const String &source, const String &pattern)

{

    return ContainsAt(source, pattern, source.GetLength() - pattern.GetLength());

}

 

String InsertNestingToSignature(String natural, String nesting)

{

    if(StartsWith(nesting, "::"))

        nesting.Remove(0, 2);

    if(nesting.GetCount() && !EndsWith(nesting, "::"))

        nesting << "::";

 

    int pos = natural.Find('('); // find the first opening parenthesis

    pos--;

    while(pos >= 0 && !iscid(natural[pos])) // skip over non-id chars before paren.

        pos--;

    if(pos < 0) return "";

    while(pos >= 0 && iscid(natural[pos])) // skip over last id before paren

        pos--;

    natural.Insert(pos+1, nesting);

    return natural;

}

 

CodeMetric::CodeMetric(const String &fileContent) :

 orphanLines(0), blankLines(0), commentLines(0)

{

    StringStream stream(fileContent);

    CppBase base;

    Parser parser;

    parser.whenFnEnd = THISBACK(StoreMetric);

 

    parser.Do(stream, base, 0, 0,

              "file", CNULL,

              Vector<String>(),

              Vector<String>(),

              Index<String>());

 

    const SrcFile &srcFile = parser.getPreprocessedFile();

 

    commentLines = srcFile.commentLinesRemoved;

    blankLines   = srcFile.blankLinesRemoved;

    orphanLines  = parser.symbolsOutsideFunctions.GetStat(';');

    totalLLOC    = orphanLines;

    sumCC1 = sumCC2 = sumDepth = 0;

 

    for(int i = 0; i < functions.GetCount(); i++) {

        totalLLOC += functions[i].logicalLinesOfCode;

        sumCC1    += functions[i].cyclomaticComplexity1;

        sumCC2    += functions[i].cyclomaticComplexity2;

        sumDepth  += functions[i].scopeDepth;

    }

}

 

 

String CodeMetric::ToString() const

{

    String s;

    s << "LLOC: " << totalLLOC

      << ", Blank: " << blankLines

      << ", Comments: " << commentLines;

    if(errors != "")

        s << "\nErrors:\n" << errors;

    return s;

}

 

void CodeMetric::StoreError(int line, const String &msg)

{

    errors << "line " << line << ": " << msg << "\n";

}

 

int CodeMetric::LogicalLinesOfCode(const LexSymbolStat &symbolStat)

{

    static Vector<int> oneLiners(pick(

      Vector<int>() << tk_if << tk_else << tk_switch << tk_case

                    << tk_for << tk_do << tk_while << tk_try << tk_catch

                    << tk_struct << tk_class << tk_namespace

                    << tk_public << tk_private << tk_protected

                    << ';'));

    return symbolStat.SumStat( oneLiners );

}

 

void CodeMetric::StoreMetric(const Parser::FunctionStat & functionStat)

{

    static Vector<int> cc1_symbols(pick(

      Vector<int>() << tk_if << tk_case << tk_for << tk_while << tk_catch));

 

    static Vector<int> cc2_symbols(pick(

      Vector<int>() << t_and << t_or << '?'));

 

    if(!functionStat.cppItem.impl)

        return;

    

    FunctionEntry &entry = functions.Add();

 

    entry.pos                   = functionStat.cppItem.line;

    entry.name                  = InsertNestingToSignature(functionStat.cppItem.natural,

                                                           functionStat.scope);

    int cc1 = 1 + functionStat.symbolStat.SumStat( cc1_symbols );

    entry.cyclomaticComplexity1 = cc1;

    entry.cyclomaticComplexity2 = cc1 + functionStat.symbolStat.SumStat( cc2_symbols );

    entry.logicalLinesOfCode    = 2 + LogicalLinesOfCode(functionStat.symbolStat);

    entry.scopeDepth            = functionStat.maxScopeDepth;

}

 

 

 

 

Do you want to contribute?