Ragex.Analysis.Impact (Ragex v0.11.0)

View Source

Change impact analysis using graph traversal and metrics.

Predicts the impact of code changes by analyzing:

  • Call graph (who calls this function?)
  • Betweenness centrality (how critical is this node?)
  • PageRank (how important is this node?)
  • Complexity metrics (how complex is the code?)

Usage

alias Ragex.Analysis.Impact

# Analyze impact of changing a function
{:ok, analysis} = Impact.analyze_change({:function, MyModule, :process, 2})

# Find tests affected by a change
{:ok, tests} = Impact.find_affected_tests({:function, MyModule, :process, 2})

# Estimate refactoring effort
{:ok, estimate} = Impact.estimate_effort(:rename_function, {:function, MyModule, :old, 2})

# Calculate risk score
{:ok, risk} = Impact.risk_score({:function, MyModule, :critical, 1})

Summary

Functions

Analyzes the impact of changing a function or module.

Estimates the effort required for a refactoring operation.

Finds tests that would be affected by changing a function.

Calculates a risk score for changing a function or module.

Types

effort_estimate()

@type effort_estimate() :: %{
  operation: atom(),
  target: node_ref(),
  estimated_changes: non_neg_integer(),
  complexity: :low | :medium | :high | :very_high,
  estimated_time: String.t(),
  risks: [String.t()],
  recommendations: [String.t()]
}

impact_analysis()

@type impact_analysis() :: %{
  target: node_ref(),
  direct_callers: [node_ref()],
  all_affected: [node_ref()],
  affected_count: non_neg_integer(),
  depth: non_neg_integer(),
  risk_score: float(),
  importance: float(),
  recommendations: [String.t()]
}

node_ref()

@type node_ref() ::
  {:module, module()} | {:function, module(), atom(), non_neg_integer()}

risk_analysis()

@type risk_analysis() :: %{
  target: node_ref(),
  importance: float(),
  coupling: float(),
  complexity: float(),
  overall: float(),
  level: :low | :medium | :high | :critical,
  factors: map()
}

test_ref()

@type test_ref() :: {:function, module(), atom(), non_neg_integer()}

Functions

analyze_change(target, opts \\ [])

@spec analyze_change(
  node_ref(),
  keyword()
) :: {:ok, impact_analysis()} | {:error, term()}

Analyzes the impact of changing a function or module.

Uses graph traversal to find all code that would be affected by changing the target node. Calculates risk scores and provides recommendations.

Parameters

  • target - Node reference (function or module tuple)
  • opts - Keyword list of options
    • :depth - Maximum traversal depth (default: 5)
    • :include_tests - Include test files in analysis (default: true)
    • :exclude_modules - List of modules to exclude

Returns

  • {:ok, impact_analysis} - Impact analysis result
  • {:error, reason} - Error if analysis fails

Examples

{:ok, analysis} = Impact.analyze_change({:function, MyModule, :process, 2})
IO.puts("Functions affected: " <> Integer.to_string(analysis.affected_count))
IO.puts("Risk score: " <> Float.to_string(analysis.risk_score))

estimate_effort(operation, target, opts \\ [])

@spec estimate_effort(atom(), node_ref(), keyword()) ::
  {:ok, effort_estimate()} | {:error, term()}

Estimates the effort required for a refactoring operation.

Parameters

  • operation - Refactoring operation atom
    • :rename_function - Rename a function
    • :rename_module - Rename a module
    • :extract_function - Extract code into new function
    • :inline_function - Inline a function
    • :move_function - Move function to another module
    • :change_signature - Change function signature
  • target - Target node reference
  • opts - Additional options for specific operations

Returns

  • {:ok, effort_estimate} - Effort estimation
  • {:error, reason} - Error if estimation fails

Examples

{:ok, estimate} = Impact.estimate_effort(:rename_function, {:function, MyModule, :old, 2})
IO.puts("Estimated changes: " <> Integer.to_string(estimate.estimated_changes))
IO.puts("Complexity: " <> Atom.to_string(estimate.complexity))
IO.puts("Time: " <> estimate.estimated_time)

find_affected_tests(target, opts \\ [])

@spec find_affected_tests(
  node_ref(),
  keyword()
) :: {:ok, [test_ref()]} | {:error, term()}

Finds tests that would be affected by changing a function.

Traverses the call graph to find test functions that directly or indirectly call the target function.

Parameters

  • target - Function reference tuple
  • opts - Keyword list of options
    • :depth - Maximum traversal depth (default: 10)
    • :test_patterns - Patterns to identify test modules (default: ["Test", "_test"])

Returns

  • {:ok, [test_ref]} - List of affected test functions
  • {:error, reason} - Error if analysis fails

Examples

{:ok, tests} = Impact.find_affected_tests({:function, MyModule, :process, 2})
IO.puts("Tests affected: " <> Integer.to_string(length(tests)))

risk_score(target, opts \\ [])

@spec risk_score(
  node_ref(),
  keyword()
) :: {:ok, risk_analysis()} | {:error, term()}

Calculates a risk score for changing a function or module.

Combines multiple factors:

  • Importance (PageRank score)
  • Coupling (number of dependencies)
  • Complexity (if available from quality metrics)

Parameters

  • target - Node reference
  • opts - Keyword list of options
    • :weights - Custom weights for factors (default: equal)

Returns

  • {:ok, risk_analysis} - Risk analysis with score 0.0-1.0
  • {:error, reason} - Error if scoring fails

Examples

{:ok, risk} = Impact.risk_score({:function, MyModule, :critical, 1})
IO.puts("Overall risk: " <> Float.to_string(risk.overall) <> " (" <> Atom.to_string(risk.level) <> ")")