OeditusCredo.Check.Refactoring.SuggestFSM (OeditusCredo v0.6.0)

View Source

Basics

This check is disabled by default.

Learn how to enable it via .credo.exs.

This check has a base priority of low and works with any version of Elixir.

Explanation

Modules that manage entity lifecycle through plain imperative code -- branching on a status/state field, manually setting it to new values, scattering guards across multiple function heads -- should consider using a proper finite state machine instead.

Suggested replacements:

  • Finitomata -- an Elixir FSM library with PlantUML/Mermaid diagram input, callbacks, supervision, history, and telemetry.
  • :gen_statem -- the OTP built-in state machine behaviour.

Bad:

defmodule MyApp.Order do
  use Ecto.Schema

  schema "orders" do
    field :status, Ecto.Enum, values: [:draft, :pending, :paid, :shipped, :delivered]
  end

  def pay(order) do
    case order.status do
      :draft -> {:error, :not_ready}
      :pending -> Ecto.Changeset.change(order, status: :paid)
      :paid -> {:error, :already_paid}
      _ -> {:error, :invalid}
    end
  end
end

Good:

defmodule MyApp.OrderFSM do
  @fsm """
  draft --> |submit| pending
  pending --> |pay| paid
  paid --> |ship| shipped
  shipped --> |deliver| delivered
  """

  use Finitomata, fsm: @fsm, syntax: :flowchart

  @impl Finitomata
  def on_transition(:pending, :pay, _payload, state),
    do: {:ok, :paid, state}
end

Check-Specific Parameters

Use the following parameters to configure this check:

:exclude_test_files

Set to true to skip test files (default: false)

This parameter defaults to nil.

:status_field_names

Field names to watch (default: [:status, :state])

This parameter defaults to nil.

:min_states

Minimum distinct status values to trigger (default: 3)

This parameter defaults to nil.

General Parameters

Like with all checks, general params can be applied.

Parameters can be configured via the .credo.exs config file.