Dead Code Detection

Research [Strei2014] and other sources (e.g. [Pizz2013]) have shown that typical software code bases contain 5-10% “dead code”, i.e. code that can be removed without reducing the functionality.
Streamlining the code base by identifying and removing dead code has several benefits:

  • Less maintenance cost: Whilst dead code is less likely to be changed frequently, it still has to be understood and might be effected by refactorings.
  • Smaller footprint: Less code makes the development environment faster, the build and deployment processes are more efficient, and the size of runtime artifacts are smaller.
  • Better precision for calculated metrics: Dead code contributes to software metrics, e.g. “average test coverage” might be improved by tests for unused code and therefore creating false confidence.

Dead code grows in projects for the following reasons:

  • Only few developers check in their IDE if some element is still in use, when they remove a reference to it.
  • Identifying reliably that a public class or method is “dead code” is not a trivial task and requires deep knowledge about the code base.
  • Removing seemingly dead code can easily lead to new bugs therefore developers are usually reluctant to remove them.

It is likely that more dead code exists in large and long running projects with a high fluctuation of developers.

Detecting dead code is a good use case to illustrate Sonargraph Explorer’s powerful scripting API and to demonstrate how it can be used to efficiently detect dead code within a Java project including public classes, methods and fields.

Introduction of Concepts

Before I describe the development of the script in detail, I would like to introduce and differentiate the following concepts:

  • Unused code: Code that contributes to functionality that is not used by the end user. Detection of unused code requires monitoring the application at runtime and collecting usage information about classes that are instantiated and the methods that are invoked. The classes/methods that don’t appear in the logs are only candidates for “unused code”, because they might be used in exceptional cases that did not happen during the time of monitoring.
  • Useless (or unnecessary) code: Code that does not contribute anything to a functionality (no-operations, writing to a file that is never read, etc). To my knowledge it is not possible to detect useless code automatically and reliably on class and method level. Tools like Eclipse, Findbugs and PMD can detect useless (or “unnecessary”) code on the micro-level by analyzing the AST (abstract syntax tree) [PMD].
  • Statically unreachable code: Code that is not statically referenced is a superset of “dead code”. It is easy to detect with Sonargraph Explorer: We just need to check if a type/method/field has no incoming dependencies. Note that there is code that is statically unreachable, but is nevertheless in use as it is dynamically invoked at runtime.
  • Dead code: Code that is nowhere used within the application.

The following figure shows how these concepts relate (click to enlarge). Note that the sizes of the ellipses have been chosen arbitrarily and do not reflect the actual percentage of the overall code base.

Concepts
Concepts

Since Sonargraph Explorer does static code analysis, the focus of this blog post is to first determine “statically unreachable code” and then to adjuste the configuration iteratively to narrow it down to “dead code”.

Structure of the Script

The full script is shown at the end of this post and I will refer to individual sections while I explain its functionality. It is provided as part of the Java Quality Model within Sonargraph Explorer.
The main “action” is executed by the IJavaVisitor and the onType(), onMethod() and onField() visitor methods.
Statically unreachable code can easily be determined by using the API method ElementAccess.getReferencingElementsRecursively(Aggregator, boolean, boolean, IDependencyKind…), see lines 166-175 in the script and check the online JavaDoc [ScriptAPI] for details.
Without additional configuration, this leads to a high number of false positives and I will demonstrate, how those false positives can be eliminated. Unfortunately, this process cannot be automated as it requires deep knowledge about the code base.

The following types need to be tolerated:

  1. Tolerate top-level types: Top-level types that are the entry points to the application usually are nowhere referenced but are of course necessary and cannot be removed. The names of those types are configured in the list of “toleratedClassNames”. I use regular expression matching in the “onType” closure so I don’t have to list each class individually. Identified tolerated types are added to a specific result node for easier verification of the findings (see lines 15-22, 155-164).
  2. Tolerate types used via reflection: A lot of classes are also instantiated dynamically via reflection and are referenced in configuration files (web.xml, plugin.xml, beans.xml, properties files, etc). Again, those types are added to the “toleratedClassNames” as regular expressions (see lines 15-22, 155-164).
  3. Tolerate types based on inheritance: If you know that all your top-level types extend certain base classes or implement specific interfaces, you can test for this inheritance, too. This probably results in fewer patterns than listing each type individually (see lines 29, 125-145).
  4. Tolerate generated code: Some code is generated and should be excluded from the analysis, e.g. classes generated by Java to XML Binding (JAXB). The information about unused generated classes should be used to reduce the generated code.

On the next level, the usage of methods is checked. If a method is tolerated, it is checked if its parent (the enclosing type) needs to be removed from the list of “dead” types.

  1. Tolerate API methods and methods used by frameworks: Public methods might not be referenced anywhere, but are still being used dynamically by a framework (e.g. public “bean” methods of an Ant task) or even by other projects (see lines 241-255). This is straight forward and also based on checking specific naming patterns.
  2. Tolerate overriding methods: Inheritance / virtual method calls make it impossible to know which methods can be considered as “dead”. Therefore all “overriding” methods are tolerated (see line 188). Note that this is not based on the @Override annotation!
  3. Tolerate main methods: Main methods need to be considered as entry points and need to be tolerated (see line 227).
  4. Tolerate methods based on inheritance: It can be efficient to specify that all methods must be tolerated that are contained in classes extending certain base classes. Of course, that is possible, too (see lines 210-225).

Processing of fields:

  1. Tolerate serialVersionUID fields: Those constants are used by the Java serialization functionality (see lines 295-305).
  2. Check if a field is being read: It is not enough to check if a field has no incoming dependency. The field’s value must be read somewhere, otherwise it is a dead store (see lines 337-348).
  3. Tolerate fields based on naming convention: As with types and methods, it is possible to tolerate fields explicitly based on a naming pattern (see lines 307-322).

On all levels it is possible to tolerate elements with certain annotations: Some classes, methods and fields have annotations that mark them to be used by a framework, e.g. @Resource. This is done via a method defined within the script (see lines 81-98) and used for typees in lines 147-153, for methods in lines 195-207 and for fields in lines 280-293.

It is obvious that a consistent naming convention makes the configuration of the script easier. As a positive side-effect of configuring the script, you might want to move all classes that are instantiated via reflection or a framework into a separate package, so that developers can easily recognize their nature.

Recommended Approach

I recommend the following procedure to actually eliminate dead code:

  1. Never take the result of the script for granted. If a false positive is detected, adjust the script’s configuration.
  2. Get a second opinion and never delete code on your own.
  3. Check for comments in the code or in its test method.
  4. Check your SCM to identify the “owner of the code”. This might be the person who created the artifact or modified it most frequently. This person might be able to provide more information.
  5. Work incrementally and test frequently.

Results

The percentage of dead code is given at the end of the console output and also provided as a metric. The default upper threshold is set to 5%, but you can adjust that in the script configuration. An issue is created for each “potentially dead” element.
The following screenshot shows the results found for my little sample test project (click to enlarge).

screenshot
The script helped us to identify ~2 % of dead code within the code base of Sonargraph Explorer and takes less than 30 seconds to process a ~300 KLOC project running on a standard laptop (Intel i7-4712HQ @2.3 GHz, 16 GB RAM).
Let us know how much dead code you find!

Known limitations:

  • Since the script only tests for incoming dependencies locally on each element, isolated groups of circular dependent elements won’t be detected. This will be implemented in the future.
  • Sonargraph Explorer is not aware of the structure of external code, including the JDK. For example Sonargraph Explorer does not know that java.lang.Integer implements indirectly java.io.Serializable interface.

Resources:

[Strei2014]: “Dead Code Detection On Class Level”, by Fabian Streitel, Daniela Steidl, Elmar J├╝rgens, 2014
[Pizz2013]: “Static analysis: Leveraging source code analysis to reign in application maintenance cost”, by Pete Pizzutillo, 2013
[PMD]: PMD rules category “unnecessary”
[ScriptAPI]: JavaDoc for Sonargraph Explorer Script API

 

Script source for DeadCode.scr (as contained within Sonargraph Explorer)

  1. /**
  2. * Script that detects potentially dead types, methods and fields.
  3. * It does not detect isolated cycles of dead code. It could be improved by visiting the model several times and check if there are elements left that are only referenced by previously detected dead code.
  4. *
  5. * IMPORTANT!
  6. * The code identified as "potentially dead" is only a hint - only YOU as the architect / developer of the system know if the code is referenced via dependency injection / reflection.
  7. * Delete the identified code with great care.
  8. */
  9.  
  10. /////////////////// Start of configuration ////////////////////////////////////////
  11. toleratedClassNames = new ArrayList<>();
  12. toleratedClassNames.add("\\S+\\.package-info");
  13.  
  14. //Application classes (examples)
  15. toleratedClassNames.add("com\\.hello2morrow\\.sonargraph\\.build\\.application\\.SonargraphBuildApplication");
  16. toleratedClassNames.add("com\\.hello2morrow\\.sonargraph\\.build\\.client\\.ant\\.SonargraphReportTask");
  17.  
  18. //UI classes referenced by plugin.xml (examples)
  19. toleratedClassNames.add("com\\.hello2morrow\\.sonargraph\\.ui\\.standalone\\.application\\.\\S+");
  20. toleratedClassNames.add("com\\.hello2morrow\\.sonargraph\\.ui\\.\\S+\\.commandhandler\\.\\S+");
  21. toleratedClassNames.add("com\\.hello2morrow\\.sonargraph\\.ui.standalone.wizard.\\S+");
  22. toleratedClassNames.add("com\\.hello2morrow\\.sonargraph\\.ui.\\S+CommandHandler");
  23.  
  24. //Instantiated via reflection
  25. //toleratedClassNames.add("x\\.y\\.z\\.ByReflectionClassNamePattern");
  26.  
  27. //Tolerate classes that extend certain base classes
  28. toleratedIfExtends = new ArrayList<>();
  29. //toleratedIfExtends.add("x\\.y\\.z\\.SuperClassNamePattern");
  30.  
  31. //Tolerate classes with specific annotations
  32. toleratedTypeAnnotations = new ArrayList<>();
  33. toleratedTypeAnnotations.add("javax.annotation.Resource");
  34.  
  35. //Method names
  36. toleratedMethodNames = new ArrayList<>(); 
  37. //toleratedMethodNames.add('\\S+\\$_\\S+_closure\\d+.\\S+()'); //groovy closures 
  38.  
  39. mainMethodPattern = "\\S+\\.main\\(String\\[\\]\\)"; 
  40.  
  41. //Tolerate methods with specific annotations
  42. toleratedMethodAnnotations = new ArrayList<>();
  43. toleratedMethodAnnotations.add("org.junit.Test");
  44. toleratedMethodAnnotations.add("javax.annotation.Resource");
  45.  
  46. List<String> toleratedMethodsOfSubClasses = new ArrayList<>();
  47. toleratedMethodsOfSubClasses.add('com.hello2morrow.foundation.propertyreader.BeanPropertyReader\$BeanAdapter');
  48.  
  49. //Field names
  50. toleratedFieldNames = new ArrayList<>();
  51. //toleratedFieldNames.add("x\\.y\\.z\\.ClassNamePattern\\.FieldNamePattern"
  52.  
  53. //Tolerate fields with specific annotations
  54. toleratedFieldAnnotations = new ArrayList<>();
  55. toleratedFieldAnnotations.add("javax.annotation.Resource");
  56.  
  57. //Tolerate fields required for serialization
  58. serializableClassList = new ArrayList<>();
  59. serializableClassList.add("java.lang.Exception");
  60. serializableClassList.add("java.io.Serializable");
  61. serialVersionUIDFieldName = "serialVersionUID";
  62.  
  63. /////////////////// End of configuration ////////////////////////////////////////
  64.  
  65. deadTypes = new ArrayList<>();
  66. deadMethods = new ArrayList<>();
  67. deadFields = new ArrayList<>();
  68. toleratedTypes = new ArrayList<>();
  69. toleratedMethods = new ArrayList<>();
  70. toleratedFields = new ArrayList<>();
  71.  
  72. //Create nodes for the "tree tab"
  73. NodeAccess deadTypeNode = result.addNode("Potentially dead types");
  74. NodeAccess deadMethodsNode = result.addNode("Potentially dead methods");
  75. NodeAccess deadFieldsNode = result.addNode("Potentially dead fields");
  76. NodeAccess toleratedTypesNode = result.addNode("Tolerated types");
  77. NodeAccess toleratedMethodsNode = result.addNode("Tolerated methods");
  78. NodeAccess toleratedFieldsNode = result.addNode("Tolerated fields");
  79.  
  80. //function to check if a dependency to an annotation exists
  81. def hasAnnotation(ProgrammingElementAccess element, List toleratedAnnotations) 
  82. {
  83.     // println "Checking for annotations on element: " + element;
  84.     boolean annotationPresent = element.getOutgoingDependencies(Aggregator.TYPE, true, JavaDependencyKind.HAS_ANNOTATION).find
  85.     {
  86.         AggregatedDependencyAccess dep ->
  87.         // println "   outgoing annotation dependency: " + dep;
  88.         for (String annotationClass : toleratedAnnotations)
  89.         {
  90.             if (dep.getTo().getName().equals(annotationClass))
  91.             {
  92.                 return true;
  93.             }
  94.         }
  95.         return false;
  96.     }    
  97.     return annotationPresent;
  98. }
  99.  
  100. IJavaVisitor v = javaAccess.createVisitor();
  101.  
  102. //Check on source files and skip all external and Groovy source files since too much reflection is going on...
  103. v.onSourceFile {
  104.     SourceFileAccess sourceFileAccess ->
  105.       if (sourceFileAccess.isExternal() || sourceFileAccess.getFile() == null || sourceFileAccess.getFile().getName().endsWith(".groovy"))
  106.       {
  107.           //println "Skipping source file: " + sourceFileAccess.getName();
  108.           return;
  109.       }
  110.       v.visitChildren(sourceFileAccess);
  111. }
  112.  
  113. //Check for dead types
  114. v.onType
  115. {
  116.     JavaTypeAccess type ->
  117.     if(type.isExternal() || type.isExcluded())
  118.     {
  119.         return;
  120.     }
  121.  
  122.     //Add elements so they show up in the elements tab, so we know what has been processed.
  123.     result.addElement(type)
  124.  
  125.     boolean isToleratedExtends = type.getOutgoingDependencies(Aggregator.TYPE, true, JavaDependencyKind.EXTENDS).find
  126.     {
  127.         AggregatedDependencyAccess dep ->
  128.         //println "   outgoing dependency: " + dep;
  129.         for (String pattern : toleratedIfExtends)
  130.         {
  131.             if (dep.getTo().getName().matches(pattern))
  132.             {
  133.                 return true;
  134.             }
  135.         }
  136.         return false;
  137.     }    
  138.  
  139.     if (isToleratedExtends)
  140.     {
  141.         println "Tolerated type (extends): " + type 
  142.         toleratedTypes.add(type);
  143.         v.visitChildren(type)
  144.         return;
  145.     }
  146.  
  147.     if (hasAnnotation(type, toleratedTypeAnnotations))
  148.     {
  149.         println "Tolerated type (annotation): " + type 
  150.         toleratedTypes.add(type);
  151.         v.visitChildren(type)
  152.         return;
  153.     }
  154.  
  155.     for (String pattern : toleratedClassNames)
  156.     {
  157.         if (type.getName().matches(pattern))
  158.         {
  159.             println "Tolerated type (pattern): " + type
  160.             toleratedTypes.add(type);
  161.             v.visitChildren(type);
  162.             return;
  163.         }
  164.   }
  165.  
  166.     List usingTypes = type.getReferencingElementsRecursively(Aggregator.TYPE, true, false)
  167.     int numberOfIncomingDependencies = usingTypes.size()
  168.     if(numberOfIncomingDependencies == 0)
  169.     {
  170.         //println "Dead type detected: " + type
  171.         deadTypes.add(type);
  172.         //we continue checking on methods and fields, because classes with main(String[]) methods will be removed from the dead types list and we want to find
  173.         //unused methods and fields in those classes as well.
  174.     }
  175.     v.visitChildren(type);
  176. }
  177.  
  178. //Check for dead methods
  179. v.onMethod
  180. {
  181.     JavaMethodAccess method ->
  182.  
  183.     if (method.isExternal() || method.isExcluded() || method.isInitializer() || !method.isDefinedInEnclosingElement())
  184.     {
  185.         return;
  186.     }
  187.  
  188.     if (method.isOverriding())
  189.     {
  190.         //println "  method " + method + " is overriding";
  191.         return;    
  192.     }
  193.  
  194.     JavaTypeAccess type = method.getParent();    
  195.     if (hasAnnotation(method, toleratedMethodAnnotations))
  196.     {
  197.         println "Tolerated method (annotation): " + method 
  198.         toleratedMethods.add(method);
  199.         if(deadTypes.remove(type) != null)
  200.         {
  201.             println "Type " + type + " contains tolerated method (annotation) " + method + " and is therefore not dead code."
  202.             if (!toleratedTypes.contains(type))
  203.             {
  204.                 toleratedTypes.add(type);
  205.             }
  206.         }
  207.         return;
  208.     }
  209.  
  210.     for (String toleratedSuperClass : toleratedMethodsOfSubClasses)
  211.     {
  212.         if (type.typeOf(toleratedSuperClass))
  213.         {
  214.             toleratedMethods.add(method);
  215.             if (deadTypes.remove(type) != null)
  216.             {
  217.                 println "Type " + type + " is a  tolerated subclass of " + toleratedSuperClass + " and is therefore not dead code";
  218.                 if (!toleratedTypes.contains(type))
  219.                 {
  220.                     toleratedTypes.add(type);
  221.                 }
  222.             }
  223.             return;
  224.         }
  225.     }
  226.  
  227.     if (method.toString().matches(mainMethodPattern))
  228.     {
  229.         toleratedMethods.add(method);
  230.         if (deadTypes.remove(type) != null)
  231.         {
  232.             println "Type " + type + " has a main method and is therefore not dead code";
  233.             if (!toleratedTypes.contains(type))
  234.             {
  235.                 toleratedTypes.add(type);
  236.             }
  237.             return;
  238.         }
  239.     }
  240.  
  241.     for (String pattern : toleratedMethodNames)
  242.     {    
  243.         if (method.toString().matches(pattern))
  244.         {
  245.             toleratedMethods.add(method);
  246.              //println "    method is tolerated as it matches pattern " + pattern + ", " + method.getName();
  247.             if(deadTypes.remove(type) != null)
  248.             {
  249.                 println "Type " + type + " contains tolerated method (pattern) " + method + " and is therefore not dead code."
  250.                 if (!toleratedTypes.contains(type))
  251.                 {
  252.                     toleratedTypes.add(type);
  253.                 }
  254.             }
  255.             return;    
  256.         }
  257.     }
  258.  
  259.     List usingTypes = method.getReferencingElementsRecursively(Aggregator.TYPE, false, false); 
  260.     int numberOfIncomingDependencies = usingTypes.size()
  261.     if(numberOfIncomingDependencies > 0)
  262.     {
  263.         //println "    method " + method + " has " + numberOfIncomingDependencies + " incoming dependencies" ;
  264.         return;
  265.     }
  266.  
  267.     deadMethods.add(method);    
  268. }
  269.  
  270. //check for dead fields
  271. v.onField
  272. {
  273.     JavaFieldAccess field ->     
  274.     if (!field.isDefinedInEnclosingElement())
  275.     {
  276.         return;
  277.     }
  278.  
  279.     JavaTypeAccess type = (JavaTypeAccess) field.getParent();
  280.     if (hasAnnotation(field, toleratedFieldAnnotations))
  281.     {
  282.         //println "Tolerated field (annotation): " + field
  283.         toleratedFields.add(field);
  284.         if(deadTypes.remove(type) != null)
  285.         {
  286.             println "Type " + type + " contains tolerated field (annotation) " + field + " and is therefore not dead code."
  287.             if (!toleratedTypes.contains(type))
  288.             {
  289.                 toleratedTypes.add(type);
  290.             }
  291.         }
  292.         return;
  293.     }
  294.  
  295.     for(String serializableClass : serializableClassList) 
  296.     {
  297.         if (type.typeOf(serializableClass)) 
  298.         { 
  299.             if (field.getShortName().equals(serialVersionUIDFieldName) && field.isStatic()) 
  300.             {
  301.                 toleratedFields.add(field);
  302.                 return;
  303.             }
  304.         }
  305.     }
  306.  
  307.     for (String pattern : toleratedFieldNames)
  308.     {
  309.         if (field.toString().matches(pattern))
  310.         {
  311.             toleratedFields.add(field);
  312.             //println "Tolerated field (pattern): " + field
  313.             if(deadTypes.remove(type) != null)
  314.             {
  315.                 println "Type " + type + " contains tolerated field (pattern) " + field + " and is therefore not dead code."
  316.                 if (!toleratedTypes.contains(type))
  317.                 {
  318.                     toleratedTypes.add(type);
  319.                 }
  320.             }
  321.             return;
  322.         }
  323.     }
  324.  
  325.     if (type.isEnum() && field.isPublic())
  326.     {
  327.         List using = field.getReferencingElementsRecursively(Aggregator.ELEMENT, true, false);
  328.         using.remove(type); //if enum constant needs to override method, we have an incoming dependency
  329.         if (!using.isEmpty())
  330.         {
  331.             //println "Enum constant $field.name is used within enum class $type.name";
  332.             return;
  333.         }
  334.     }
  335.     else
  336.     {
  337.         List using = field.getReferencingElementsRecursively(Aggregator.ELEMENT, false, false, JavaDependencyKind.READ_FIELD, JavaDependencyKind.READ_FIELD_INLINE);
  338.         if (!using.isEmpty())
  339.         {
  340.             return;
  341.         }
  342.         for (JavaFieldAccess subclassField : field.getReferencingElementsRecursively(Aggregator.FIELD, false, false, JavaDependencyKind.VIA_SUBTYPE))
  343.         {
  344.             //println "detected field used by subclass: " + subclassField.getName()
  345.             using = subclassField.getReferencingElementsRecursively(Aggregator.ELEMENT, false, false, JavaDependencyKind.READ_FIELD, JavaDependencyKind.READ_FIELD_INLINE);
  346.             if (!using.isEmpty())
  347.             {
  348.                 return;
  349.             }
  350.         }
  351.     }
  352.     //println "  dead field: " + field;
  353.     deadFields.add(field);
  354. }
  355.  
  356. //Traverse the model
  357. coreAccess.visitModel(v)
  358.  
  359. //Sort
  360. deadTypes.sort{it.getNameWithSignature()};
  361. deadMethods.sort{it.getNameWithSignature()};
  362. deadFields.sort{it.getNameWithSignature()};
  363. toleratedTypes.sort{it.getNameWithSignature()};
  364. toleratedMethods.sort{it.getNameWithSignature()};
  365. toleratedFields.sort{it.getNameWithSignature()};
  366.  
  367. long numberOfStatementsInDeadCode = 0;
  368. for(TypeAccess type : deadTypes)
  369. {
  370.     //Add child node for the detected type
  371.     result.addNode(deadTypeNode, type);
  372.  
  373.     numberOfStatementsInDeadCode += type.getNumberOfStatementsMetric().intValue();
  374.     println "Unused type: " + type.getName();
  375.     //Create warning type issue
  376.     result.addWarningIssue(type, "Potentially dead type", "Type has no incoming dependencies")
  377. }    
  378.  
  379. for(MethodAccess method : deadMethods)
  380. {
  381.     if (deadTypes.contains(method.getParent()))
  382.     {
  383.         //We don't want to add methods for unused types -> this will screw up the "% of dead code" metric
  384.         continue;
  385.     }
  386.     //Add child node for the detected method
  387.     result.addNode(deadMethodsNode, method);
  388.      numberOfStatementsInDeadCode += method.getNumberOfStatementsMetric().intValue();
  389.     //println "Unused method: " + next;     
  390.     //Create warning type issue
  391.     result.addWarningIssue(method, "Potentially dead method", "Method has no incoming dependencies")
  392. }
  393.  
  394. for (JavaFieldAccess next : deadFields)
  395. {
  396.     if(deadTypes.contains(next.getParent()))
  397.     {
  398.         continue;
  399.     }
  400.     result.addNode(deadFieldsNode, next);
  401.     result.addWarningIssue(next, "Potentially dead field", "Field has no incoming dependencies");
  402.     //We simply assume that a field declaration is one statement 
  403.     numberOfStatementsInDeadCode++;
  404. }
  405.  
  406. for (JavaTypeAccess type : toleratedTypes)
  407. {
  408.     result.addNode(toleratedTypesNode, type);        
  409. }
  410.  
  411. for (JavaMethodAccess method : toleratedMethods)
  412. {
  413.     result.addNode(toleratedMethodsNode, method);
  414. }
  415.  
  416. for (JavaFieldAccess field : toleratedFields)
  417. {
  418.     result.addNode(toleratedFieldsNode, field);
  419. }
  420.  
  421. float percentageOfDeadCode = numberOfStatementsInDeadCode * 100.0 / coreAccess.getNumberOfStatementsMetric();
  422.  
  423. def metricId = coreAccess.getOrCreateMetricId("Percentage of dead code", "Potentially dead code (%)", "Percentage of potentially dead code", true, 0.0, new Float(parameterUpperThreshold));
  424. result.addMetricValue(metricId, coreAccess, percentageOfDeadCode);
  425.  
  426. println "\nNumber of statements (total): " + coreAccess.getNumberOfStatementsMetric();
  427. println "Number of statements (dead code): " + numberOfStatementsInDeadCode
  428. println "Percentage of (potentially) dead code: " + percentageOfDeadCode + "%"
  429. println "\nNOTE: Check the result carefully and edit the configuration inside the script to avoid false positives!"

Leave a Reply

Your email address will not be published. Required fields are marked *