Claude-skill-registry elixir-no-placeholders
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.
git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/elixir-no-placeholders" ~/.claude/skills/majiayu000-claude-skill-registry-elixir-no-placeholders && rm -rf "$T"
skills/data/elixir-no-placeholders/SKILL.mdElixir 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:
- Is this data actually optional? → If no, don't provide default
- Does this default have semantic meaning? → If no, don't provide default
- Would I rather know immediately if this is missing? → If yes, don't provide default
- Could this default hide a bug? → If yes, don't provide default
- 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.