ajur58

ajur58

Coding with LLMs: CONVENTIONS.md for Elixir - do you have your own? What does it contain?

Hey everyone!

For the past weeks I’ve been using Claude Sonnet 3.5 with Aider as a “pair programming” buddy. It works fairly well, but some things have been constantly nagging me.

I’ve noticed that people are using a doc called conventions to fine-tune the code that gets written by the AI.

Inspired by this, I decided to bite the bullet and write one for myself.

To make things easier - and because I really liked PragDave’s Elixir course - I checked out the repo from the course, and told Claude to generate a conventions doc based on that. After a couple of iterations & adjustments I ended up with this version.

I’m curios if someone has their own conventions doc, what it contains, and also happy to hear your feedback.

Thanks!

# Elixir and Phoenix Best Practices
*Based on Dave Thomas' (PragDave) coding philosophy*

## Core Principles

- **Domain-Driven Design**: Organize code around business domains, not technical layers
- **Functional Core, Imperative Shell**: Pure domain logic with side effects at boundaries
- **Explicit Over Implicit**: Prefer clarity over magic
- **Composition Over Inheritance**: Build systems from small, focused components
- **Single Responsibility**: Each module and function should do one thing well
- **Easy to Change**: Design for maintainability and future change
- **Fail Fast**: Detect and handle errors as early as possible
- **YAGNI**: Don't build features until they're needed

## Project Structure

- **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

## Coding Patterns

- **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

## Process Design

- **GenServer for State**: Use GenServers for stateful processes
- **Supervision Trees**: Design proper supervision hierarchies
- **Registry Pattern**: Use Registry for dynamic process lookup
- **Task.Supervisor**: Use for concurrent, potentially failing operations
- **Process Isolation**: Design processes to crash independently without affecting the whole system
- **Let It Crash**: Embrace the "let it crash" philosophy with proper supervision

## Phoenix Best Practices

- **LiveView-First**: Use LiveView as the primary UI technology
- **Function Components**: Use function components for reusable UI elements
- **PubSub for Real-time**: Use Phoenix PubSub for real-time features
- **Context Boundaries**: Respect context boundaries in controllers and LiveViews
- **Thin Controllers**: Keep controllers thin, delegating business logic to contexts
- **Security First**: Always consider security implications (CSRF, XSS, etc.)

## Testing Strategies

- **Test Public APIs**: Focus on testing public context APIs
- **Mox for Dependencies**: Use Mox for mocking external dependencies
- **Property-Based Testing**: Use StreamData for property-based tests
- **Test Factories**: Use ExMachina for test data creation
- **Test Readability**: Write tests that serve as documentation
- **Arrange-Act-Assert**: Structure tests with clear setup, action, and verification phases

## HTTP and API Integration

- **Req for HTTP Clients**: Use Req instead of HTTPoison or Tesla
- **Behaviours for API Clients**: Define behaviours for API clients to allow easy mocking
- **Error Handling**: Handle network failures and unexpected responses gracefully
- **Timeouts**: Always set appropriate timeouts for external calls
- **Circuit Breakers**: Use circuit breakers for critical external services

## Naming Conventions

- **Snake Case**: For variables and functions (`create_user`)
- **Verb-First Functions**: Start function names with verbs (`create_user`, not `user_create`)
- **Plural for Collections**: Use plural for collections (`users`, not `user`)
- **Consistent Terminology**: Use consistent terms throughout the codebase
- **Intention-Revealing Names**: Choose names that reveal intent, not implementation

## Documentation and Quality

- **Document Public Functions**: Add `@doc` to all public functions
- **Examples in Docs**: Include examples in documentation
- **Credo and Dialyzer**: Use for static analysis and type checking
- **Consistent Formatting**: Use `mix format` to maintain consistent code style
- **Continuous Refactoring**: Regularly improve code structure without changing behavior
- **Comments**: Write comments only when necessary. Describe why, not what it does.

## Performance Considerations

- **Avoid N+1 Queries**: Use Ecto's preloading and joins
- **Pagination**: Paginate large result sets
- **Background Jobs**: Use Oban for background processing
- **Measure First**: Profile before optimizing
- **Caching**: Apply strategic caching where appropriate

Most Liked

ajur58

ajur58

Here is the file on GitHub for better readability: llm-conventions/conventions_ex.md at main · ajur58/llm-conventions · GitHub

dimitarvp

dimitarvp

Same, and always has.

But I understand it’s a noble intention that was hijacked by a lot of wannabes who poisoned the well for the well-intentioned people so I try to suppress my instinctive reaction.

We actually had a friendly disagreement with Renato on this and we both agreed it’s ultimately a matter of taste. At least I maintain that having a plethora of super small files does not improve anything measurably compared to, say, a Phoenix context file (where you have list, get_*, delete etc. helpers).

To me the important thing is the single responsibility principle and clean boundaries. How are these achieved is a technical detail that’s often not very important.

Pal

Pal

It was PragDaves approach that led me to Ash :slight_smile:

Where Next?

Popular in Discussions Top

blackode
Elixir Upgrading is so Simple in Ubuntu and It worked for me Ubuntu 16.04 git clone https://github.com/elixir-lang/elixir.git cd elixir...
New
MarioFlach
Hello, I want to share a project I’ve been working on for a while: https://github.com/almightycouch/gitgud Background Some time ago I ...
New
AngeloChecked
What learn first? Rust or Elixir Hi Elixir community! I’m here because i want learn a new language. I’m a junior developer and mainly i ...
New
AstonJ
If a newbie asked you about Phoenix Contexts, how would you explain the basics to them? Feel free to be as concise or in-depth as you li...
New
Ankhers
Just a little information upfront. Generally speaking, if I feel like I need to either break a pipe chain or use an anonymous function in...
New
New
rms.mrcs
A couple of days ago I was discussing with a friend about different approaches to write microservices. He said that if he was going to w...
New
100phlecs
Are there any downsides, like perf issues, to putting all functional components in CoreComponents as long as you prefix it with the conte...
New
wmnnd
The Go vs Elixir thread got me thinking: Would it be too hard to implement a simple mechanism for creating Go-style static app binaries f...
New
pdgonzalez872
If this has been asked here before, please point me to where it was asked as I didn’t find it when I searched the forum. Maybe a mailing ...
New

Other popular topics Top

danschultzer
None of the current solutions worked well for me, so I went ahead and built a user management system from scratch. This project took far...
548 29377 241
New
electic
Hi, I am new to Elixir. I am trying to use the DateTime component to insert a date into MySQL however the there seems to be no way to fo...
New
ovidiubadita
Hey all, I discovered Elixir and I love it. I always wanted to learn a functional programming and I intended to go for Haskell, but afte...
New
minhajuddin
I have seen a lot of code which picks the first element from a list using Enum.at(0) instead of List.first. Is there a reason why people ...
New
chrismccord
This release brings a number of exciting features, including integration with the new Phoenix LiveDashboard and Phoenix LiveView. There h...
New
josevalim
Hi everyone, One of the features added to Elixir early on to help integration with Erlang code was the idea of overridable function defi...
New
stefanluptak
Hello everybody, usually, I use a 29" ultra-wide monitor for VSCode which can easily accomodate explorer (files panel) + file with code ...
New
ashish173
I am using Ecto timestamps with postgres, I can see the timestamps() use the :naive_dateime but for my use case I wanted to store the ti...
New
axelson
This post is a wiki (feel free to hit the edit button near the bottom right of this post to add your own changes!) This post collects co...
239 47930 226
New
lanycrost
Hi everyone! I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
New

Latest on Elixir Forum

We're in Beta

About us Mission Statement