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
Below Metrics are supported by Java, C, C++, C#, Objective C, Typescript, Javascript and Python.

Metrics
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

Below Metrics are supported by all languages

Metrics
1. Lines of Code (LOC)
2. Executable Lines Of Code (ELOC)
3. Lines Of Code Comments (LOC Comments)

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-

MetricsComponent LevelMethod Level
Lines of Code (LOC)1000100
Executable Lines Of Code (ELOC)1000NA
Lines Of Code Comments (LOC Comments)NANA
Comment Ratio (CR)>30NA
Number of Methods (NOM)10NA
Number of Attributes (NOA)5NA
Lack of Cohesion Of Methods (LCOM)77%NA
Number of Public Attributes (NOPA)0NA
Cyclomatic Complexity (CC)50NA
Coupling Between Objects (CBO)30NA
Depth of Inheritance Hierarchy (DOIH)5NA
Response for Class (RFC)50NA
Foreign Data Providers (FDP)NA0
Locality of Attribute Accesses (LAA)NA0.77
Number of Accessed Variables (NOAV)NA9
Access To Foreign Data (ATFD)24
Max Nesting (MN)NA5
Number of Parameters (NOP)NA4
Number of Public Methods (NOPM)10NA

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.

Example:
NOA is 7 for below component.

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.

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.

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.

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.

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.

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.

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).

Example:

NOAM is 2 for below code.

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.

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 (%).

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.

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.

Coupling Between Objects (CBO)

Coupling Between Objects (CBO) is the degree to 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 the reuse of components difficult. Also, changes in highly-coupled classes (components) often have a ripple effect on other dependent classes.

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.

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.

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.

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.

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.

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.

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;
  };
}
)