Architectural analysis suite for Choreo.UML class/struct diagrams.
Provides static analysis tools optimized for functional and OOP software design:
cycles/1— identifies circular dependency loops.broken_contracts/1— flags incomplete behavior/protocol realizations.coupling_metrics/1— computes Afferent/Efferent coupling and Instability.law_of_demeter_violations/1— identifies structural Law of Demeter violations.
Summary
Functions
Identifies components which have declared a :realizes or :inherits relationship
to a :behavior, :protocol, or :interface component but do not implement all
the functions specified by that contract.
Computes coupling and stability metrics for all components in the UML diagram.
Identifies all circular dependency paths in the UML diagram.
Identifies all Law of Demeter violations in the UML class diagram.
Functions
@spec broken_contracts(Choreo.UML.t()) :: [ {Choreo.UML.class_id(), Choreo.UML.class_id(), [map()]} ]
Identifies components which have declared a :realizes or :inherits relationship
to a :behavior, :protocol, or :interface component but do not implement all
the functions specified by that contract.
Returns a list of tuples [{component_id, contract_id, [missing_function_maps]}].
Examples
iex> contract = Choreo.UML.new()
...> |> Choreo.UML.add_class(:auth, type: :behavior, functions: [%{name: "verify", arity: 1}])
...> # Struct missing the arity 1 verify function:
...> |> Choreo.UML.add_class(:provider, type: :struct, functions: [%{name: "verify", arity: 2}])
...> |> Choreo.UML.add_relationship(:provider, :auth, type: :realizes)
iex> [{:provider, :auth, [missing]}] = Choreo.UML.Analysis.broken_contracts(contract)
iex> missing[:name]
"verify"
iex> missing[:arity]
1
@spec coupling_metrics(Choreo.UML.t()) :: %{ optional(Choreo.UML.class_id()) => %{ afferent: non_neg_integer(), efferent: non_neg_integer(), instability: float() } }
Computes coupling and stability metrics for all components in the UML diagram.
Returns a map of component IDs to:
:afferent— number of incoming dependencies (who depends on me):efferent— number of outgoing dependencies (who do I depend on):instability— Ce / (Ca + Ce). Stable core modules approach 0.0, unstable leaf modules approach 1.0.
Examples
iex> uml =
...> Choreo.UML.new()
...> |> Choreo.UML.add_class(:a)
...> |> Choreo.UML.add_class(:b)
...> |> Choreo.UML.add_relationship(:a, :b, type: :associates)
iex> metrics = Choreo.UML.Analysis.coupling_metrics(uml)
iex> metrics[:a]
%{afferent: 0, efferent: 1, instability: 1.0}
iex> metrics[:b]
%{afferent: 1, efferent: 0, instability: 0.0}
@spec cycles(Choreo.UML.t()) :: [[Choreo.UML.class_id()]]
Identifies all circular dependency paths in the UML diagram.
Cycles in structural diagrams indicate high coupling and are a major source of compilation cascades in Elixir applications.
Returns a list of cycles, where each cycle is a list of node IDs starting at the canonical smallest node and listing each member once.
Examples
iex> uml =
...> Choreo.UML.new()
...> |> Choreo.UML.add_class(:a)
...> |> Choreo.UML.add_class(:b)
...> |> Choreo.UML.add_relationship(:a, :b, type: :associates)
...> |> Choreo.UML.add_relationship(:b, :a, type: :depends)
iex> Choreo.UML.Analysis.cycles(uml)
[[:a, :b]]
@spec law_of_demeter_violations(Choreo.UML.t()) :: [ {Choreo.UML.class_id(), Choreo.UML.class_id(), Choreo.UML.class_id()} ]
Identifies all Law of Demeter violations in the UML class diagram.
A violation occurs when a class A has a direct relationship to a class C,
but also has a path through an intermediate class B (i.e., A -> B, B -> C,
and A -> C).
Returns a list of triplets [{class_a, class_b, class_c}] representing the violations.
Examples
iex> uml =
...> Choreo.UML.new()
...> |> Choreo.UML.add_class(:a)
...> |> Choreo.UML.add_class(:b)
...> |> Choreo.UML.add_class(:c)
...> |> Choreo.UML.add_relationship(:a, :b, type: :associates)
...> |> Choreo.UML.add_relationship(:b, :c, type: :associates)
...> |> Choreo.UML.add_relationship(:a, :c, type: :associates)
iex> Choreo.UML.Analysis.law_of_demeter_violations(uml)
[{:a, :b, :c}]