Skip to content

Measuring The Structure

Measuring the structural properties of software artifacts (requirements, design documents, source code) provides valuable insights into quality attributes like complexity, maintainability, testability, and potential effort estimation. Analyzing structure helps identify potential problem areas early in the lifecycle.

Software structure can be analyzed from different perspectives:

  • Control-Flow Structure: Examines the sequence of execution paths within a program or module. It helps understand logical complexity.
  • Data-Flow Structure: Tracks how data is defined, used, and modified as it moves through different parts of the program or between modules. It helps understand data dependencies and coupling.
  • Data Structure: Refers to the organization and complexity of data elements themselves (e.g., arrays, records, objects, databases schemas).
  • Architecture/Module Structure: Examines the high-level organization of the system into components, modules, or services and their interconnections (coupling and cohesion).

Control-flow is often modeled using Control Flow Graphs (CFGs), where nodes represent code statements or blocks, and directed edges represent possible transfers of control.

  • Cyclomatic Complexity (McCabe’s Metric): A widely used metric calculated from the CFG (Number of Edges - Number of Nodes + 2). It represents the number of linearly independent paths through the code, providing a quantitative measure of logical complexity. Higher values generally indicate more complex code that is potentially harder to test and maintain.
  • Nodes and Edges Count: Simple counts of basic blocks (nodes) or transfers of control (edges) in the CFG.
  • Nesting Depth: Measures the maximum depth of nested control structures (if, while, for statements). Deep nesting can make code harder to understand.
  • Cognitive Complexity: A more recent metric (developed by SonarSource) that aims to measure the effort required for a human to understand the control flow, penalizing structures that break the linear flow or require mental stacking (e.g., deep nesting, certain logical operators).

Tools: Static analysis tools (e.g., SonarQube, PMD, Checkstyle, linters integrated into IDEs) often calculate these metrics automatically.

Data-flow analysis focuses on the life cycle of variables (definition, usage, destruction) and the flow of information between different parts of the system.

  • Coupling Metrics: Measure the degree of interdependence between modules. High coupling suggests changes in one module are likely to impact others, reducing maintainability. Types include data coupling, stamp coupling, control coupling, common coupling, and content coupling (from best to worst). Tools can often measure fan-in (number of modules calling a given module) and fan-out (number of modules called by a given module) as indicators.
  • Information Flow Complexity (e.g., Henry and Kafura): Classic metrics attempted to quantify complexity based on fan-in, fan-out, and potentially the complexity of data structures passed. While foundational, simpler coupling metrics are often used today.
  • Span of Variables: The number of lines of code between successive references to a variable. Large spans can make tracking variable usage difficult.

Application: Understanding data flow is crucial for impact analysis (assessing the ripple effect of changes) and identifying areas prone to errors due to complex data interactions.

This involves assessing the complexity of the data being manipulated by the software.

  • Number of Attributes/Fields: Simple counts of data elements within classes, records, or database tables.
  • Complexity of Data Types: Assessing the use of complex nested structures, pointers, or intricate object relationships.
  • Database Schema Complexity: Metrics related to the number of tables, relationships (foreign keys), indexes, and normalization levels in a database schema.

Application: Complex data structures can increase the difficulty of coding algorithms that manipulate them and can be harder to test thoroughly.

These metrics assess the quality of the high-level design.

  • Cohesion Metrics: Measure the degree to which elements within a single module belong together, focusing on a single task. High cohesion is desirable (e.g., LCOM - Lack of Cohesion in Methods).
  • Coupling Metrics (at Module Level): As described under Data-Flow, but applied to the interactions between high-level components or modules.
  • Dependency Analysis: Identifying dependencies between modules or packages. Tools can visualize dependency graphs and detect cyclic dependencies, which hinder maintainability and testability.
  • Size Metrics: Number of modules, classes, or services in the system.

While structural metrics provide valuable quantitative data, it’s important to remember:

  • Correlation, Not Causation: High complexity doesn’t guarantee low maintainability, but it’s often strongly correlated.
  • Context is Key: Acceptable complexity thresholds vary depending on the application domain, team expertise, and criticality.
  • Not the Whole Picture: Structural metrics don’t capture aspects like algorithm efficiency, usability, or functional correctness directly.
  • Tool Dependence: Different tools might calculate the same metric slightly differently.
  • Potential for Misuse: Don’t set arbitrary targets for metrics without understanding their meaning and potential side effects (Goodhart’s Law).

Structural metrics are most effective when used as indicators to guide investigation, track trends over time, and support discussions about code quality and design improvements, rather than as absolute judgments.