Metrics Overview
Metrics are measurements of software source code that helps us to characterize quality. Each metric has a predefined threshold. To keep the source code base healthy, we should keep metrics within thresholds.
Language-wise Metrics table
The below table illustrates the languages and metrics supported by BrowserStack Code Quality.
Metrics | Language Supported |
---|---|
1. Lines of Code (LOC) 2. Executable Lines Of Code (ELOC) 3. Lines Of Code Comments (LOC Comments) 4. Comment Ratio (CR) 5. Number of Methods (NOM) 6. Number of Attributes (NOA) 7. Lack of Cohesion Of Methods (LCOM) 8. Number of Public Attributes (NOPA) 9. Cyclomatic Complexity (CC) 10. Coupling Between Objects (CBO) 11. Depth of Inheritance Hierarchy (DOIH) 12. Response for Class (RFC) 13. Foreign Data Providers (FDP) 14. Locality of Attribute Accesses (LAA) 15. Number of Accessed Variables (NOAV) 16. Access To Foreign Data (ATFD) 17. Max Nesting (MN) 18. Number of Parameters (NOP) 19. Number of Public Methods (NOPM) | 1. Java 2. C 3. C++ 4. C# 5. Objective-C 6. TypeScript 7. JavaScript |
1. Lines of Code (LOC) 2. Executable Lines Of Code (ELOC) 3. Lines Of Code Comments (LOC Comments) | 1. Phyton 2. PHP 3. Go 4. Kotlin 5. Solidity 6. SQL |
Metric Thresholds
Metric can have different thresholds at the component (class) level and method level. Some metrics can be applicable only at one level while some can be applicable at both class and method levels.
Before going into the details of each metric, tables that show the threshold for each metric is given below-
Metrics | Component Level | Method Level |
---|---|---|
Lines of Code (LOC) | 1000 | 100 |
Executable Lines Of Code (ELOC) | 1000 | NA |
Lines Of Code Comments (LOC Comments) | NA | NA |
Comment Ratio (CR) | >30 | NA |
Number of Methods (NOM) | 10 | NA |
Number of Attributes (NOA) | 5 | NA |
Lack of Cohesion Of Methods (LCOM) | 77% | NA |
Number of Public Attributes (NOPA) | 0 | NA |
Cyclomatic Complexity (CC) | 50 | 10 |
Coupling Between Objects (CBO) | 30 | NA |
Depth of Inheritance Hierarchy (DOIH) | 5 | NA |
Response for Class (RFC) | 50 | NA |
Foreign Data Providers (FDP) | NA | 0 |
Locality of Attribute Accesses (LAA) | NA | 0.77 |
Number of Accessed Variables (NOAV) | NA | 9 |
Access To Foreign Data (ATFD) | 2 | 4 |
Max Nesting (MN) | NA | 5 |
Number of Parameters (NOP) | NA | 4 |
Number of Public Methods (NOPM) | 10 | NA |
Number of Attributes (NOA)
NOA is the number of attributes of a component (or class). An attribute or field of a class is typically a constant or variable.
Default Threshold5
Example:
NOA is 7 for below component.
/** * Autogenerated by Thrift Compiler (0.9.3) * * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING * @generated */ package org.apache.hive.service.rpc.thrift; import java.util.Map; import java.util.HashMap; import org.apache.thrift.TEnum; public enum TFetchOrientation implements org.apache.thrift.TEnum { FETCH_NEXT(0), FETCH_PRIOR(1), FETCH_RELATIVE(2), FETCH_ABSOLUTE(3), FETCH_FIRST(4), FETCH_LAST(5); private final int value; private TFetchOrientation(int value) { this.value = value; } /** * Get the integer value of this enum value, as defined in the Thrift IDL. */ public int getValue() { return value; } /** * Find a the enum type by its integer value, as defined in the Thrift IDL. * @return null if the value is not found. */ public static TFetchOrientation findByValue(int value) { switch (value) { case 0: return FETCH_NEXT; case 1: return FETCH_PRIOR; case 2: return FETCH_RELATIVE; case 3: return FETCH_ABSOLUTE; case 4: return FETCH_FIRST; case 5: return FETCH_LAST; default: return null; } } }
Lines of Code Comments (LOC Comments)
LOC Comments is the number of comment lines in a component or class. Very few LOC Comments will affect understandability. New developers will find it hard to work with such components. Too many LOC Comments may be a result of un-intuitive implementation.
Default ThresholdNA
Example:
LOC Comments is 5 in below code.
package org.apache.hive.hcatalog.pig; import java.io.IOException; import java.util.Properties; import org.apache.hadoop.mapreduce.Job; import org.apache.hive.hcatalog.common.HCatConstants; import org.apache.pig.impl.util.UDFContext; /** * This class is used to test the HCAT_PIG_STORER_EXTERNAL_LOCATION property used in HCatStorer. * When this property is set, HCatStorer writes the output to the location it specifies. Since * the property can only be set in the UDFContext, we need this simpler wrapper to do three things: *
Number of Methods (NOM)
The number of methods (NOM) is the total number of methods (or functions) in a component (or class) or file. High NOM indicates a high complexity of the class.
Default Threshold10
Example:
NOM is 2 for below component.
package org.apache.hive.hcatalog.pig; import com.google.common.collect.Lists; import junit.framework.Assert; import org.apache.hive.hcatalog.common.HCatConstants; import org.apache.hive.hcatalog.data.schema.HCatFieldSchema; import org.apache.hive.hcatalog.data.schema.HCatSchema; import org.apache.pig.ResourceSchema; import org.apache.pig.ResourceSchema.ResourceFieldSchema; import org.apache.pig.data.DataType; import org.apache.pig.impl.util.UDFContext; import org.junit.Test; public class TestPigHCatUtil { @Test public void testGetBagSubSchema() throws Exception { // Define the expected schema. ResourceFieldSchema[] bagSubFieldSchemas = new ResourceFieldSchema[1]; bagSubFieldSchemas[0] = new ResourceFieldSchema().setName("innertuple") .setDescription("The tuple in the bag").setType(DataType.TUPLE); ResourceFieldSchema[] innerTupleFieldSchemas = new ResourceFieldSchema[1]; innerTupleFieldSchemas[0] = new ResourceFieldSchema().setName("innerfield").setType(DataType.CHARARRAY); bagSubFieldSchemas[0].setSchema(new ResourceSchema().setFields(innerTupleFieldSchemas)); ResourceSchema expected = new ResourceSchema().setFields(bagSubFieldSchemas); // Get the actual converted schema. HCatSchema hCatSchema = new HCatSchema(Lists.newArrayList( new HCatFieldSchema("innerLlama", HCatFieldSchema.Type.STRING, null))); HCatFieldSchema hCatFieldSchema = new HCatFieldSchema("llama", HCatFieldSchema.Type.ARRAY, hCatSchema, null); ResourceSchema actual = PigHCatUtil.getBagSubSchema(hCatFieldSchema); Assert.assertEquals(expected.toString(), actual.toString()); } @Test public void testGetBagSubSchemaConfigured() throws Exception { // NOTE: pig-0.8 sets client system properties by actually getting the client // system properties. Starting in pig-0.9 you must pass the properties in. // When updating our pig dependency this will need updated. System.setProperty(HCatConstants.HCAT_PIG_INNER_TUPLE_NAME, "t"); System.setProperty(HCatConstants.HCAT_PIG_INNER_FIELD_NAME, "FIELDNAME_tuple"); UDFContext.getUDFContext().setClientSystemProps(System.getProperties()); // Define the expected schema. ResourceFieldSchema[] bagSubFieldSchemas = new ResourceFieldSchema[1]; bagSubFieldSchemas[0] = new ResourceFieldSchema().setName("t") .setDescription("The tuple in the bag").setType(DataType.TUPLE); ResourceFieldSchema[] innerTupleFieldSchemas = new ResourceFieldSchema[1]; innerTupleFieldSchemas[0] = new ResourceFieldSchema().setName("llama_tuple").setType(DataType.CHARARRAY); bagSubFieldSchemas[0].setSchema(new ResourceSchema().setFields(innerTupleFieldSchemas)); ResourceSchema expected = new ResourceSchema().setFields(bagSubFieldSchemas); // Get the actual converted schema. HCatSchema actualHCatSchema = new HCatSchema(Lists.newArrayList( new HCatFieldSchema("innerLlama", HCatFieldSchema.Type.STRING, null))); HCatFieldSchema actualHCatFieldSchema = new HCatFieldSchema("llama", HCatFieldSchema.Type.ARRAY, actualHCatSchema, null); ResourceSchema actual = PigHCatUtil.getBagSubSchema(actualHCatFieldSchema); Assert.assertEquals(expected.toString(), actual.toString()); // Clean up System properties that were set by this test System.clearProperty(HCatConstants.HCAT_PIG_INNER_TUPLE_NAME); System.clearProperty(HCatConstants.HCAT_PIG_INNER_FIELD_NAME); } }
Foreign Data Providers (FDP)
Foreign Data Providers (FDP) is the total number of external components (or classes) from which foreign attributes are accessed. Here, these external classes are provisioning data. Ideally, all of the data should be locally available within the class to promote cohesion.
Default Threshold0
Example:
Consider method ‘M’ in class ‘A’ is accessing attributes from classes ‘B’, ‘C’ and ‘D’.
In this case, FDP of method ‘M’ is 3.
Locality of Attribute Accesses (LAA)
The Locality of Attribute Access (LAA) is the ratio of the number of attributes accessed from a method’s (or function’s) owner class to the total number of attributes accessed in the method. This is a subcomponent (method) level metric.
Default Threshold> 0.77
Example:
Consider method ‘M’ in class ‘A’ is accessing 2 attributes from class ‘A’. Method ‘M’ is also accessing 1 attribute from foreign class ‘B’ and 3 attributes from another foreign class ‘C’.
Thus LAA for the method ‘M’ = 2/ (2+1+3) = 2/6 = 0.33
Number of Accessed Variables (NOAV)
The number of Accessed variables (NOAV) is the total number of variables accessed in a method (or function). local attributes, global attributes, parameters and object variables are counted. This is a subcomponent level metric.
Default Threshold9
Executable Lines Of Code (ELOC)
Executable Lines Of Code (ELOC) is the number of executable lines of code in a class (component) or a function. This metric is also referred to as ‘Number Of Statements’ (NOS). The comment lines and empty lines are not counted. Components with high ELOC are often complex and tough to maintain.
Default Threshold : Component Level1000
Example:
ELOC for below component is 15.
public static class CsvUnescaper extends SinglePassTranslator { @Override void translateWhole(final CharSequence input, final Writer out) throws IOException { // is input not quoted? if (input.charAt(0) != CSV_QUOTE || input.charAt(input.length() - 1) != CSV_QUOTE) { out.write(input.toString()); return; } // strip quotes final String quoteless = input.subSequence(1, input.length() - 1).toString(); if (StringUtils.containsAny(quoteless, CSV_SEARCH_CHARS)) { // deal with escaped quotes; ie) "" out.write(StringUtils.replace(quoteless, CSV_ESCAPED_QUOTE_STR, CSV_QUOTE_STR)); } else { out.write(input.toString()); } } }
Number of Accessor Methods (NOAM)
The Number of Accessor Methods (NOAM) is the total number of accessor methods (getters and setters) of a class (or component).
Default ThresholdNA
Example:
NOAM is 2 for below code.
public class Employee { private int number; public int getNumber() { return number; } public void setNumber(int newNumber) { number = newNumber; } }
Access To Foreign Data (ATFD)
The Access To Foreign Data (ATFD) is the number of attributes from unrelated classes that are accessed directly or by invoking accessor methods. High ATFD is usually associated with low cohesion and high coupling.
Default Threshold2
Example:
ATFD is 2 for below class.
private static class KeySelector3 implements KeySelector<tuple3<integer, long,="" string="">, Tuple2<integer, long="">> { private static final long serialVersionUID = 1L; @Override public Tuple2<integer, long=""> getKey(Tuple3<integer, long,="" string=""> t) { return new Tuple2<>(t.f0, t.f1); } }</integer,></integer,></integer,></tuple3<integer,>
Lack of Cohesion Of Methods (LCOM)
Lack of Cohesion of Methods (LCOM) is a measure of cohesiveness of a class. The low LCOM value means the methods in a class is authored to achieve a common goal. Thus, a low LCOM value is desirable as it indicates good encapsulation. LCOM is measured in percent (%).
Default Threshold77%
Example:
LCOM for class ‘ClusterInformation’ in below code is 50 which is within the default threshold.
package org.apache.flink.runtime.entrypoint; import org.apache.flink.util.Preconditions; import java.io.Serializable; /** * Information about the cluster which is shared with the cluster components. */ public class ClusterInformation implements Serializable { private static final long serialVersionUID = 316958921518479205L; private final String blobServerHostname; private final int blobServerPort; public ClusterInformation(String blobServerHostname, int blobServerPort) { this.blobServerHostname = Preconditions.checkNotNull(blobServerHostname); Preconditions.checkArgument( 0 < blobServerPort && blobServerPort < 65_536, "The blob port must between 0 and 65_536. However, it was " + blobServerPort + '.'); this.blobServerPort = blobServerPort; } public String getBlobServerHostname() { return blobServerHostname; } public int getBlobServerPort() { return blobServerPort; } @Override public String toString() { return "ClusterInformation{" + "blobServerHostname='" + blobServerHostname + ''' + ", blobServerPort=" + blobServerPort + '}'; } }
Number of Public Attributes (NOPA)
The number of Public Attributes (NOPA) is the measure of publicly exposed data of a class. It is the count of public fields.
Default Threshold0
Example:
Class ‘SimplePojo’ have a NOPA of 19 which is high.
/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.api.java.typeutils; import static org.junit.Assert.assertTrue; import org.apache.flink.api.common.typeinfo.TypeInformation; import org.apache.flink.api.common.typeutils.CompositeType; import org.apache.flink.api.java.typeutils.TypeExtractor; import org.junit.Test; public class PojoTypeInformationTest { public static class SimplePojo { public String str; public Boolean Bl; public boolean bl; public Byte Bt; public byte bt; public Short Shrt; public short shrt; public Integer Intgr; public int intgr; public Long Lng; public long lng; public Float Flt; public float flt; public Double Dbl; public double dbl; public Character Ch; public char ch; public int[] primIntArray; public Integer[] intWrapperArray; } @Test public void testSimplePojoTypeExtraction() { TypeInformation type = TypeExtractor.getForClass(SimplePojo.class); assertTrue("Extracted type is not a composite/pojo type but should be.", type instanceof CompositeType); } public static class NestedPojoInner { public String field; } public static class NestedPojoOuter { public Integer intField; public NestedPojoInner inner; } @Test public void testNestedPojoTypeExtraction() { TypeInformation type = TypeExtractor.getForClass(NestedPojoOuter.class); assertTrue("Extracted type is not a Pojo type but should be.", type instanceof CompositeType); } public static class Recursive1Pojo { public Integer intField; public Recursive2Pojo rec; } public static class Recursive2Pojo { public String strField; public Recursive1Pojo rec; } @Test public void testRecursivePojoTypeExtraction() { // This one tests whether a recursive pojo is detected using the set of visited // types in the type extractor. The recursive field will be handled using the generic serializer. TypeInformation type = TypeExtractor.getForClass(Recursive1Pojo.class); assertTrue("Extracted type is not a Pojo type but should be.", type instanceof CompositeType); } @Test public void testRecursivePojoObjectTypeExtraction() { TypeInformation type = TypeExtractor.getForObject(new Recursive1Pojo()); assertTrue("Extracted type is not a Pojo type but should be.", type instanceof CompositeType); } }
Cyclomatic Complexity (CC)
Cyclomatic Complexity (CC) is a measure of the program’s complexity achieved by measuring the number of linearly independent paths through a program’s source code. This measure needs to be applied to sections of source like methods of each class. Presence of IF-ELSE statements or SWITCH statements and FOR loops increases the number of paths in a method. The number of linearly independent paths also means the minimum number of paths that should be tested. The more paths, the higher the number of test cases that need to be implemented. McCabe’s method is used to calculate CC.
Default Threshold50
Coupling Between Objects (CBO)
Coupling Between Objects (CBO) is the degree by which one object depends on each of the other objects. The coupling can occur through method calls, field accesses, inheritance, arguments, return types, etc. The degree of coupling should be low. A high value represents poor encapsulation. This makes reuse of components difficult. Also, changes in highly-coupled classes (components) often have a ripple effect on other dependent classes.
Default Threshold30
Number of Parameters (NOP)
NOP is the number of parameters used in a method (or function). If a method has too many parameters, it is difficult to call and also difficult to change if it is called from many different clients.
Default Threshold4
Comment Ratio (CR)
Comment Ratio (CR) is simply the ratio of the line of comments to the total number of lines of code. A good comment ratio makes the code easier to understand, maintain and expand. If the comment ratio is too low, the file will be hard to maintain. A new developer will struggle to understand such a file. If CR is too high, the file is more appropriately a document rather than a source code file. If CR is too high, it may also mean that the implementation/ structure/naming is unintuitive.
Default Threshold>30
Lines of Code (LOC)
Lines of Code (LOC) as the name suggests is the total number of lines in a class or method. The comment lines and the blank lines are also counted. A longer class is often difficult to maintain. LOC is the most basic metric.
Default Threshold1000
Example:
LOC is 14 in below code :
package org.apache.hive.hcatalog.pig; import java.io.IOException; import java.util.Properties; import org.apache.hadoop.mapreduce.Job; import org.apache.hive.hcatalog.common.HCatConstants; import org.apache.pig.impl.util.UDFContext; /** * This class is used to test the HCAT_PIG_STORER_EXTERNAL_LOCATION property used in HCatStorer. * When this property is set, HCatStorer writes the output to the location it specifies. Since * the property can only be set in the UDFContext, we need this simpler wrapper to do three things: *
Depth of Inheritance Hierarchy (DOIH)
Depth of Inheritance Hierarchy (DOIH) is the inheritance level of a class from its topmost class in the hierarchy. It is the maximum length of the path from a class to its root class in the inheritance structure. High DOIH indicates high reuse. However, a high DOIH makes the behaviour unpredictable.
Default Threshold5
Response for Class (RFC)
Response for Class (RFC) is the number of methods that will be executed when any method of the class is invoked. In other words, it as a count of methods implemented by this class (including inherited methods, if any) plus a number of methods called on other classes (excluding base classes). Higher RFC will make the class more complex, difficult to understand, maintain and test the code.
Default Threshold50
Maximum Nesting
Maximum nesting is a number of nested blocks that are present inside a class/method. This helps to identify how the class is deeply nested.
The blocks with if
, else
, else if
, do
, while
, for
,foreach
, switch
, catch
, etc statements are generally the part of nested loops.
Default Component Level ThresholdNA
Default Method Level Threshold5
Example:
Maximum Nesting is 5 for the below component.
(function() { var something = function(deps) { var result = []; for (var i = 0, len = deps.length; i < len; i++) { result[i] = __m[deps[i]]; } var otherthing = function(ids) { var output = []; for (var j = 0, len = ids.length; j < len; j++) { output[j] = __m[ids[j]]; } var anotherthing = function(criticality) { var volume = []; for (var k = 0, len = criticality.length; k < len; k++) { volume[k] = __m[criticality[k]]; } var thing = function(resources) { var resource = []; for (var l = 0, len = resources.length; l < len; l++) { resource[l] = __m[resources[l]]; } var uid = function(repos) { var repo = []; for (var m = 0, len = repos.length; m < len; m++) { repo[m] = __m[repos[l]]; } return repo; }; return resource; }; return volume; }; return output; }; return result; }; } )