Claude Code Plugins

Community-maintained marketplace

Feedback

elixir-no-placeholders

@mkreyman/bmad-elixir
0
0

PROHIBITS placeholder code, default values that mask missing data, and silent failures. Enforces fail-fast with loud errors. Use when implementing ANY function or data structure.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name elixir-no-placeholders
description PROHIBITS placeholder code, default values that mask missing data, and silent failures. Enforces fail-fast with loud errors. Use when implementing ANY function or data structure.

Elixir No Placeholders: Fail Loud, Fail Fast

THE IRON LAW

NEVER create placeholder code or provide defaults where there shouldn't be any.

Silent failures are debugging nightmares. Loud failures save hours of troubleshooting.

FAIL LOUD. FAIL FAST. FAIL OBVIOUSLY.

ABSOLUTE PROHIBITIONS

You are NEVER allowed to:

1. Create Placeholder Code

# BAD: Placeholder implementations
def process_payment(_user_id, _amount) do
  # TODO: Implement this
  {:ok, %{}} # WRONG! Silent success with empty data
end

def send_email(_to, _subject, _body) do
  :ok  # WRONG! Pretends to work but does nothing
end

def validate_user(_attrs) do
  {:ok, attrs}  # WRONG! Bypasses validation
end

# GOOD: Explicit not implemented
def process_payment(_user_id, _amount) do
  raise "process_payment/2 not yet implemented"
end

# OR use @impl with proper error
@impl true
def handle_call({:process_payment, user_id, amount}, _from, state) do
  {:stop, {:error, :not_implemented}, state}
end

2. Provide Default Values That Hide Missing Data

# BAD: Default values masking missing required data
defmodule User do
  schema "users" do
    field :email, :string, default: "unknown@example.com"  # WRONG!
    field :name, :string, default: "Unknown User"          # WRONG!
    field :role, :string, default: "user"                  # Maybe OK if truly optional
  end
end

# GOOD: No defaults for required fields
defmodule User do
  schema "users" do
    field :email, :string  # Required - no default
    field :name, :string   # Required - no default
    field :role, :string, default: "user"  # OK - has sensible default meaning
  end

  def changeset(user, attrs) do
    user
    |> cast(attrs, [:email, :name, :role])
    |> validate_required([:email, :name])  # Explicit requirements
  end
end

3. Silent Fallbacks in Pattern Matching

# BAD: Catch-all that hides problems
def handle_result({:ok, data}), do: process(data)
def handle_result({:error, reason}), do: log_error(reason)
def handle_result(_anything_else), do: :ok  # WRONG! Silent success

# GOOD: Explicit handling, crash on unexpected
def handle_result({:ok, data}), do: process(data)
def handle_result({:error, reason}), do: {:error, reason}
# No catch-all - crashes loudly if unexpected input

# OR explicit error if you must handle it
def handle_result(unexpected) do
  raise ArgumentError, "Expected {:ok, data} or {:error, reason}, got: #{inspect(unexpected)}"
end

4. Empty Data Structures as Fallbacks

# BAD: Return empty instead of error
def get_user_posts(user_id) do
  case Repo.get(User, user_id) do
    nil -> []  # WRONG! Silent "no posts" vs "user doesn't exist"
    user -> Repo.preload(user, :posts).posts
  end
end

# GOOD: Explicit error for missing user
def get_user_posts(user_id) do
  user = Repo.get!(User, user_id)  # Crashes if user missing
  Repo.preload(user, :posts).posts
end

# OR return proper error tuple
def get_user_posts(user_id) do
  case Repo.get(User, user_id) do
    nil -> {:error, :user_not_found}
    user -> {:ok, Repo.preload(user, :posts).posts}
  end
end

5. Try/Rescue That Silences Errors

# BAD: Catch and return default
def parse_date(date_string) do
  try do
    Date.from_iso8601!(date_string)
  rescue
    _ -> ~D[2000-01-01]  # WRONG! Why this date? Masks parsing errors
  end
end

# GOOD: Let it crash or return error
def parse_date(date_string) do
  Date.from_iso8601!(date_string)  # Crashes with clear error
end

# OR return explicit error
def parse_date(date_string) do
  case Date.from_iso8601(date_string) do
    {:ok, date} -> {:ok, date}
    {:error, reason} -> {:error, {:invalid_date, reason}}
  end
end

6. Map.get/3 With Default for Required Keys

# BAD: Default hides missing required keys
def create_user(attrs) do
  email = Map.get(attrs, :email, "unknown@example.com")  # WRONG!
  name = Map.get(attrs, :name, "Unknown")                # WRONG!
  User.changeset(%User{}, %{email: email, name: name})
end

# GOOD: Let it crash if key missing
def create_user(attrs) do
  # Will raise KeyError if :email or :name missing - GOOD!
  %{email: email, name: name} = attrs
  User.changeset(%User{}, %{email: email, name: name})
end

# OR explicit error
def create_user(attrs) do
  with {:ok, email} <- Map.fetch(attrs, :email),
       {:ok, name} <- Map.fetch(attrs, :name) do
    User.changeset(%User{}, %{email: email, name: name})
  else
    :error -> {:error, :missing_required_fields}
  end
end

7. Config With Silent Fallbacks

# BAD: Default config hides missing env vars
def api_key do
  System.get_env("API_KEY") || "default_key_12345"  # WRONG!
end

def database_url do
  System.get_env("DATABASE_URL") || "localhost"  # WRONG!
end

# GOOD: Crash if required env var missing
def api_key do
  System.fetch_env!("API_KEY")  # Crashes if missing
end

def database_url do
  System.get_env("DATABASE_URL") ||
    raise "DATABASE_URL environment variable is required"
end

WHEN DEFAULTS ARE ACCEPTABLE

Defaults are OK when they have semantic meaning, not just placeholders:

Acceptable Defaults

# OK: Default has actual business meaning
defmodule Post do
  schema "posts" do
    field :status, :string, default: "draft"        # OK: New posts are drafts
    field :published, :boolean, default: false      # OK: Unpublished by default
    field :view_count, :integer, default: 0         # OK: No views initially
    field :featured, :boolean, default: false       # OK: Not featured by default
  end
end

# OK: Optional fields with sensible defaults
def create_user(email, name, opts \\ []) do
  role = Keyword.get(opts, :role, "user")          # OK: "user" is sensible default
  locale = Keyword.get(opts, :locale, "en")        # OK: "en" is sensible default
  %User{email: email, name: name, role: role, locale: locale}
end

# OK: Pagination defaults
def list_users(opts \\ []) do
  page = Keyword.get(opts, :page, 1)               # OK: Page 1 is sensible start
  per_page = Keyword.get(opts, :per_page, 20)      # OK: 20 is sensible page size

  User
  |> limit(^per_page)
  |> offset(^((page - 1) * per_page))
  |> Repo.all()
end

Unacceptable Defaults (Placeholders)

# WRONG: Default hides missing required data
field :email, :string, default: "unknown@example.com"     # User email is required!
field :stripe_customer_id, :string, default: "cus_xxxxx"  # Payment ID required!
field :api_token, :string, default: "token123"            # Security credential!

# WRONG: Default bypasses validation
def validate_amount(amount) do
  amount || 0  # If amount is nil, use 0 - WRONG!
end

# WRONG: Default hides configuration errors
api_endpoint = System.get_env("API_ENDPOINT") || "http://localhost"  # Production will break!

DETECTION CHECKLIST

Before writing ANY default value, ask:

  1. Is this data actually optional? → If no, don't provide default
  2. Does this default have semantic meaning? → If no, don't provide default
  3. Would I rather know immediately if this is missing? → If yes, don't provide default
  4. Could this default hide a bug? → If yes, don't provide default
  5. Is this a configuration value? → If yes, crash if missing

If in doubt, NO DEFAULT. Let it crash.

FAIL LOUD PATTERNS

Pattern 1: Let It Crash

# Prefer this
def process_order(order_id) do
  order = Repo.get!(Order, order_id)  # ! version crashes if not found
  Repo.preload(order, :items)
end

# Over this
def process_order(order_id) do
  case Repo.get(Order, order_id) do
    nil -> %Order{}  # WRONG! Fake order with no data
    order -> Repo.preload(order, :items)
  end
end

Pattern 2: Explicit Errors

# When you need to handle missing data
def find_user(id) do
  case Repo.get(User, id) do
    nil -> {:error, :user_not_found}       # Explicit error
    user -> {:ok, user}                     # Explicit success
  end
end

# Not this
def find_user(id) do
  Repo.get(User, id) || %User{}  # WRONG! Fake user
end

Pattern 3: Required Keys

# Use pattern matching to enforce required keys
def create_notification(%{user_id: user_id, message: message} = attrs) do
  # Will crash with clear error if user_id or message missing
  %Notification{user_id: user_id, message: message}
end

# Not this
def create_notification(attrs) do
  user_id = attrs[:user_id] || 1       # WRONG! Who is user 1?
  message = attrs[:message] || "N/A"   # WRONG! Useless notification
  %Notification{user_id: user_id, message: message}
end

Pattern 4: Config Required

# In config/runtime.exs
config :my_app, MyApp.Mailer,
  adapter: Swoosh.Adapters.Sendgrid,
  api_key: System.fetch_env!("SENDGRID_API_KEY")  # Crashes if missing

# Not this
config :my_app, MyApp.Mailer,
  adapter: Swoosh.Adapters.Sendgrid,
  api_key: System.get_env("SENDGRID_API_KEY") || "default"  # WRONG!

DEBUGGING BENEFITS

With placeholders and defaults:

User registration succeeds ✓
Email notification "sent" ✓
Database shows: user.email = "unknown@example.com"
Customer: "I never received my confirmation email!"
Developer: "Oh, the email was actually 'unknown@example.com' all along..."
Debugging time: 2 hours to trace through logs

Without placeholders (fail loud):

User registration fails ✗
Error: "Required key :email not found in params"
Developer: "Email field is missing from the form"
Debugging time: 2 minutes to add email field

EXAMPLES FROM REAL DEBUGGING NIGHTMARES

Example 1: Silent Payment Failure

# BAD: Silent failure with placeholder
def charge_customer(amount) do
  stripe_customer_id = get_stripe_id() || "cus_placeholder"  # WRONG!

  case Stripe.charge(stripe_customer_id, amount) do
    {:ok, charge} -> {:ok, charge}
    {:error, _} -> {:ok, %{id: "ch_placeholder", status: "succeeded"}}  # WRONG!
  end
end

# Result: Database shows successful charge, customer never charged, debugging takes days

# GOOD: Fail loud
def charge_customer(amount) do
  stripe_customer_id = get_stripe_id!()  # Crashes if missing

  case Stripe.charge(stripe_customer_id, amount) do
    {:ok, charge} -> {:ok, charge}
    {:error, reason} -> {:error, reason}  # Explicit error
  end
end

# Result: Error appears immediately, fix in 5 minutes

Example 2: Default Hiding Configuration Error

# BAD: Default hides missing config
defmodule MyApp.EmailClient do
  def send(to, subject, body) do
    api_key = System.get_env("EMAIL_API_KEY") || "test_key_123"  # WRONG!
    # Works in development, fails silently in production
    ThirdPartyMailer.send(api_key, to, subject, body)
  end
end

# GOOD: Crash early
defmodule MyApp.EmailClient do
  def send(to, subject, body) do
    api_key = System.fetch_env!("EMAIL_API_KEY")  # Crashes at startup
    ThirdPartyMailer.send(api_key, to, subject, body)
  end
end

Example 3: Empty List Hiding Database Issue

# BAD: Empty list hides query error
def user_orders(user_id) do
  try do
    Repo.all(from o in Order, where: o.user_id == ^user_id)
  rescue
    _ -> []  # WRONG! Query error looks like "no orders"
  end
end

# GOOD: Let database errors surface
def user_orders(user_id) do
  Repo.all(from o in Order, where: o.user_id == ^user_id)
  # If query fails, error is obvious and immediate
end

RATIONALIZATIONS THAT ARE WRONG

"I'll add a TODO and fix it later"

WRONG. TODOs with placeholder code never get fixed. Write raise "not implemented" instead.

"This is just for development/testing"

WRONG. Development placeholders leak to production. Be explicit from the start.

"I need something to make the tests pass"

WRONG. Tests passing with placeholder data proves nothing. Write proper fixtures.

"The default value is harmless"

WRONG. Default values mask bugs. There's no such thing as a harmless default for required data.

"It's easier to provide a default than handle the error"

WRONG. Easier now = debugging nightmare later. Fail loud, fix fast.

"This makes the API more flexible"

WRONG. Required data that's "optional" isn't flexibility, it's ambiguity.

THE RULE

Required data should be required. Missing data should crash.

If it's optional, document WHY and what the default MEANS.

Placeholders are lies. Defaults without meaning are bugs waiting to happen.

ENFORCEMENT CHECKLIST

Before providing ANY default value:

  • Is this data truly optional in the business domain?
  • Does this default have clear semantic meaning?
  • Have I documented what this default represents?
  • Would failing loudly here save debugging time?
  • Could this default hide a bug or misconfiguration?

If you can't clearly explain WHY a default exists and WHAT it means, DON'T USE IT.

REMEMBER

"Silent failures waste hours. Loud failures save hours."

"A crash in development prevents a bug in production."

"Defaults should have meaning, not just placeholders to avoid errors."

"If data is required, make it required. If it's missing, crash."

FAIL LOUD. FAIL FAST. FAIL OBVIOUSLY.