This rule set helps teams consistently apply Python coding principles, reducing technical debt and improving maintainability.
-
Context-Based Organization: Use Phoenix contexts to define domain boundaries
lib/my_app/
accounts/ # User management domain
billing/ # Payment processing domain
catalog/ # Product catalog domain
-
API/Implementation Separation: Public API modules delegate to implementation modules
In MyApp.Accounts (API module)
defdelegate create_user(attrs), to: MyApp.Accounts.UserCreator
-
Boundary Enforcement: Use tools like NimbleOptions to validate inputs at boundaries
-
Pattern Matching: Use pattern matching in function heads for control flow
-
Railway-Oriented Programming: Chain operations with 'with' for elegant error handling
with {:ok, user} <- find_user(id),
{:ok, updated} <- update_user(user, attrs) do
{:ok, updated}
end
-
Type Specifications: Add typespecs to all public functions
@spec create_user(user_attrs()) :: {:ok, User.t()} | {:error, Changeset.t()}
-
Immutable Data Transformations: Return new state rather than modifying existing state
-
Data Validation: Validate data at boundaries using Ecto.Changeset even outside of database contexts
def validate_attrs(attrs) do
{%{}, %{name: :string, email: :string}}
|> Ecto.Changeset.cast(attrs, [:name, :email])
|> Ecto.Changeset.validate_required([:name, :email])
|> Ecto.Changeset.validate_format(:email, ~r/@/)
end
-
Result Tuples: Return tagged tuples like '{:ok, result}' or '{:error, reason}' for operations that can fail