DEV Community

Cover image for Junior Survival Guide for Simplicity
Sara A.
Sara A. Subscriber

Posted on

Junior Survival Guide for Simplicity

Use Patterns Thoughtfully

  • Apply design patterns (e.g., Strategy, Factory, Builder) to solve recurring problems, but avoid over-engineering.
  • Avoid "pattern obsession" — only use a pattern when it adds clarity and solves a real problem.

Introduce abstractions (interfaces/protocols/traits) only when you need

  • multiple implementations now, or
  • a clear seam for testing/integration boundaries, or
  • a genuine contract used by multiple consumers.

Leverage Existing Tools and Libraries

  • Prefer well-maintained, widely-used libraries for common problems (validation, HTTP, auth, persistence, scheduling).
  • Only build your own when requirements truly don’t fit existing tools.

Favour Convention Over Configuration

  • Prefer established conventions and defaults in your stack.
  • Avoid excessive custom configuration unless you can explain the concrete benefit and cost.
  • Keep configuration minimal and predictable.

Solve Problems, Not Hypotheticals

  • Avoid implementing solutions for problems that do not exist yet.
  • Design for current requirements, and extend when needed.
  • Start with a single, clean implementation and refactor if multiple implementations are required.
  • However, do not forget the SOLID principles → objects or entities should be open for extension but closed for modification.

Design for Readability Over Cleverness

  • Write code that is easy to read and understand for others (and your future self).
  • Avoid overly clever tricks or shortcuts that obscure the intent of the code.

Follow DRY (Don’t Repeat Yourself)

  • Extract common logic into reusable methods or classes when multiple areas of the codebase share functionality.
  • Ensure the logic is truly universal and not overly specific to a particular use case.

Avoid Over-Generalisation

  • Resist the temptation to generalise different business cases into a single method or class just to reduce duplication.
  • Over-generalisation can lead to bloated or overly complex code that is harder to understand, maintain, or adapt.

DTO Specialisation Per Module/Domain

  • Keep transfer models local to the boundary/context that uses them (API layer, messaging, persistence, internal domain).
  • Don’t be afraid to duplicate boundary models if it keeps each context clear and reduces coupling.

Make State and Side Effects Explicit

  • Hidden state is the root of most bugs.
  • Prefer immutable objects where possible.
  • Avoid methods that secretly mutate shared state.
  • If a method has side effects (DB writes, HTTP calls, file I/O), make it obvious in the name or structure.

Validate at the Boundaries

  • Validate inputs at system edges (controllers, message consumers).
  • Fail fast with clear errors.
  • Inside the core logic, assume data is already valid.

Prefer Composition Over Inheritance

  • Use inheritance only for true “is-a” relationships.
  • Prefer composing behaviour via small collaborators.
  • Deep inheritance trees are almost always a smell.

Keep Methods Small and Single-Purpose

  • One method should do one thing.
  • If a method has “and” in its description → split it.
  • Long methods hide bugs and make testing painful.

Name Things Like You’re Teaching a Human

  • Naming is design.
  • Avoid abbreviations unless they are universally known.
  • Good names remove the need for comments.

Write Tests That Explain Behaviour

  • Tests are executable documentation.
  • Test names should read like sentences describing behaviour.
  • Test outcomes, not internal implementation details.

Log for Humans, Not for Debuggers

  • Logs should explain what happened and why.
  • Avoid noise logs like “entered method X”.
  • Log business events, not internal plumbing.

Errors Are Part of the API

Treat failures as part of the contract:

  • make error cases explicit and meaningful,
  • avoid silent failures (null/empty/default),
  • choose the idiom of your language (exceptions, result types, error values) consistently.

Make the Happy Path Obvious

  • Structure code so the main flow is immediately visible.
  • Handle validation and edge cases early.
  • Avoid deeply nested logic.

Delete Code Aggressively

  • Dead code is technical debt.
  • If it’s unused, remove it.
  • Version control already remembers it.
  • Commented-out code is a red flag.

The Golden Meta Rule

If something feels complicated, it probably is.

Stop and simplify before adding more code.


Top comments (0)