Metastatic.Analysis.Cohesion (Metastatic v0.10.4)

View Source

Cohesion analysis for containers (modules/classes).

Measures how well the members (methods/functions) of a container work together. High cohesion indicates that members are closely related and work toward a common purpose, which is a desirable property in object-oriented design.

Supported Metrics

LCOM (Lack of Cohesion of Methods)

Measures the number of disjoint sets of methods. Lower is better.

  • LCOM = 0: Perfect cohesion (all methods share state)
  • LCOM > 0: Poor cohesion (methods form disconnected groups)

TCC (Tight Class Cohesion)

Ratio of directly connected method pairs. Range: 0.0-1.0, higher is better.

  • TCC = 1.0: All methods directly share state
  • TCC > 0.5: Good cohesion
  • TCC < 0.3: Poor cohesion

LCC (Loose Class Cohesion)

Ratio of directly or indirectly connected method pairs. Range: 0.0-1.0.

  • LCC >= TCC always
  • High LCC but low TCC: Methods connected through intermediaries

Algorithm

  1. Extract all methods/functions from container
  2. For each method, identify which instance variables it accesses
  3. Build a connection graph between methods based on shared variables
  4. Calculate LCOM (number of disconnected components)
  5. Calculate TCC (direct connections / total possible pairs)
  6. Calculate LCC (transitive closure / total possible pairs)

Examples

# High cohesion - all methods use shared state
ast = {:container, :class, "BankAccount", %{}, [
  {:function_def, :public, "deposit", ["amount"], %{},
   {:augmented_assignment, :+, {:attribute_access, {:variable, "self"}, "balance"}, {:variable, "amount"}}},
  {:function_def, :public, "withdraw", ["amount"], %{},
   {:augmented_assignment, :-, {:attribute_access, {:variable, "self"}, "balance"}, {:variable, "amount"}}},
  {:function_def, :public, "get_balance", [], %{},
   {:attribute_access, {:variable, "self"}, "balance"}}
]}

doc = Document.new(ast, :python)
{:ok, result} = Cohesion.analyze(doc)

result.lcom           # => 0 (perfect cohesion)
result.tcc            # => 1.0 (all methods connected)
result.assessment     # => :excellent

# Low cohesion - methods don't share state
ast = {:container, :class, "Utilities", %{}, [
  {:function_def, :public, "format_date", ["date"], %{}, ...},
  {:function_def, :public, "calculate_tax", ["amount"], %{}, ...},
  {:function_def, :public, "send_email", ["to", "msg"], %{}, ...}
]}

{:ok, result} = Cohesion.analyze(doc)
result.lcom           # => 3 (three disjoint methods)
result.tcc            # => 0.0 (no shared state)
result.assessment     # => :very_poor

Summary

Functions

Analyze cohesion of a container (module/class/namespace).

Analyze cohesion of a container (module/class/namespace).

Functions

analyze(language_or_doc, source_or_ast_or_opts \\ [], opts \\ [])

@spec analyze(Metastatic.language(), term(), keyword()) ::
  {:ok, map()} | {:error, term()}

Analyze cohesion of a container (module/class/namespace).

Returns {:ok, result} if the AST contains a container, or {:error, reason} otherwise.

Examples

iex> ast = {:container, :class, "Calculator", %{}, [
...>   {:function_def, :public, "add", ["x"], %{},
...>    {:augmented_assignment, :+, {:attribute_access, {:variable, "self"}, "total"}, {:variable, "x"}}},
...>   {:function_def, :public, "get_total", [], %{},
...>    {:attribute_access, {:variable, "self"}, "total"}}
...> ]}
iex> doc = Metastatic.Document.new(ast, :python)
iex> {:ok, result} = Metastatic.Analysis.Cohesion.analyze(doc)
iex> result.lcom
0
iex> result.tcc
1.0

analyze!(language_or_doc, source_or_ast_or_opts \\ [], opts \\ [])

@spec analyze!(Metastatic.language(), term(), keyword()) :: map()

Analyze cohesion of a container (module/class/namespace).

Returns {:ok, result} if the AST contains a container, or {:error, reason} otherwise.

Examples

iex> ast = {:container, :class, "Calculator", %{}, [
...>   {:function_def, :public, "add", ["x"], %{},
...>    {:augmented_assignment, :+, {:attribute_access, {:variable, "self"}, "total"}, {:variable, "x"}}},
...>   {:function_def, :public, "get_total", [], %{},
...>    {:attribute_access, {:variable, "self"}, "total"}}
...> ]}
iex> doc = Metastatic.Document.new(ast, :python)
iex> {:ok, result} = Metastatic.Analysis.Cohesion.analyze(doc)
iex> result.lcom
0
iex> result.tcc
1.0

Unlike not-banged version, this one either returns a result or raises