top of page

Code Rules: #06: Keep Constructors Boring

  • Writer: Mateusz Roguski
    Mateusz Roguski
  • May 3
  • 5 min read
A flat, minimalist illustration showing a calm, solid gray cube labeled “Boring Constructor” on the left, and a tangled, chaotic machine labeled “Logical Constructor” on the right. The contrast visually represents the dangers of placing logic inside object constructors.
Solid foundations start with boring constructors.

Table of Contents




Introduction


We’ve all been there.


You open a class expecting to see its dependencies.

Instead, it’s doing everything the moment it’s born:

  • It talks to services.

  • It checks the file system.

  • It validates, transforms — even sends emails.


All of that, inside the constructor.


Here’s the problem: constructors like that don’t just build objects — they build trouble.

A constructor isn’t a to-do list.

It’s a handshake. It says, “Here’s what I need — now I’m ready.”


But the moment it starts thinking, deciding, or reaching into the outside world — it stops being a constructor and starts becoming a liability.


In this article, we’ll look at the telltale signs of a misused constructor, the design principles it breaks, the patterns it undermines — and how to keep yours as boring, reliable, and testable as they were always meant to be.


Signs Your Constructor Needs Refactoring


  • It throws anything other than an InvalidArgumentException: Constructors should only validate their own inputs. Anything beyond simple argument checks (like throwing from external logic or I/O operations) breaks the guarantee that construction is cheap and safe.

  • It connects to a database, file system, or third-party service: Object creation should not trigger side effects. Doing so ties object lifecycle to external state and makes testing and predictability fragile.

  • It runs real logic instead of just assigning values: Constructors should prepare state, not compute or transform it. Business logic belongs in methods or services, not in the act of creation.

  • It includes conditionals, loops, or switches: Flow control suggests decision-making — which means behavior, not construction. Constructors should configure, not decide.

  • It requires mocking services just to create the object: If you have to inject test doubles just to instantiate a class, the constructor is doing too much.


If any of this sounds familiar — it's a sign your constructor isn't just building an object; it's trying to run the program. Time to refactor. — it’s time to rethink what your constructor is really doing.


Design Principles


Before we get into which principles are promoted or broken, it’s worth remembering what good architecture is built on: clarity, separation of responsibilities, and predictability. Constructors that contain logic blur these lines.


Let’s look at the core principles at stake — and how keeping constructors boring helps you uphold them.


Broken by Logical Constructors


  • Single Responsibility Principle (SRP): Constructors that perform validations, calculations, or external calls take on multiple responsibilities.

  • Command-Query Separation (CQS): Constructors that mutate external state (sending emails, writing files) blend creation and execution.

  • Liskov Substitution Principle (LSP): Subclass constructors with unexpected behavior undermine substitutability.

  • Explicit over Implicit: Logic hidden inside constructors leads to surprising and hard-to-trace behaviors.


Promoted by Boring Constructors


  • Single Responsibility Principle (SRP): A constructor that only assigns values keeps the class focused on its primary purpose.

  • Command-Query Separation (CQS): Constructors merely create objects (queries), not trigger side effects (commands).

  • Dependency Inversion Principle (DIP): Simple constructors encourage injecting abstractions without forcing immediate usage.

  • Explicit over Implicit: Assignments are visible and predictable, avoiding hidden behaviors.


Design Patterns


Design patterns exist to give us reusable solutions to common problems — especially around object creation, behavior, and responsibility. But when constructors are overloaded with logic, they start stepping on the toes of these patterns. They make builders redundant, factories irrelevant, and services harder to isolate.


Let’s break down which patterns suffer when constructors try to do too much — and which ones thrive when constructors stay simple.


Broken by Logical Constructors


  • Builder Pattern: Complex constructors compete with or duplicate builder responsibilities.

  • Factory Pattern: Factories lose meaning if constructors already handle complex decisions.

  • Dependency Injection: Logic in constructors makes object graphs fragile, slow, and error-prone.

  • Domain Services: Logic that belongs in service layers gets trapped inside domain objects, leading to bloated models.


Promoted by Boring Constructors


  • Builder Pattern: Gradual, explicit assembly of objects becomes possible.

  • Factory Pattern: Encapsulation of complex creation logic outside objects remains clean.

  • Dependency Injection: Containers can instantiate objects without unexpected runtime behavior.

  • Domain Services (DDD) / Pure Fabrication (GRASP): External coordination of complex tasks outside core domain objects.


What to Do Instead


  • Assign Only: Constructors should only set fields, enforce invariants minimally, and prepare the object for immediate use.

  • Use Factories: Place any complex creation or validation logic into dedicated factory classes.

  • Adopt Builders: For objects requiring optional or staged construction, use a Builder to gradually assemble parts.

  • Service Coordination: Perform side effects (email sending, file writing, database interactions) inside Service classes, after object creation.

  • Lazy Initialization: If costly computations are required, delay them until explicitly needed, not at construction.

  • Static Factory Methods: Instead of overloaded constructors for different scenarios, expose static create() methods that orchestrate setup outside the constructor.


Special Case: Validation in Constructors


While constructors should avoid business logic, basic validation of input parameters is acceptable and encouraged, especially in Value Objects.


Here are some common examples of valid constructor validation:

  • Ensuring a string is non-empty for a Username.

  • Verifying an integer is positive for a Quantity.

  • Checking email format validity for an EmailAddress.


These validations enforce class invariants, ensuring that an object is either fully valid or does not exist at all. Such validation does not violate the 'keep constructors boring' rule — it strengthens it by guaranteeing that once an object is created, it is always in a valid state.


Important distinction:

  • Allowed: Local, pure validation (e.g., asserting parameters, throwing simple exceptions like InvalidArgumentException).

  • Forbidden: External calls, heavy computations, complex workflows, side effects.


Impact on AI-Driven Development


As AI becomes an integral part of software development (code generation, static analysis, auto-refactoring), clean architectural practices become even more critical.


  • Predictability: AI systems infer patterns. Predictable constructor behavior makes AI code assistance more accurate and safer.

  • Structure Recognition: Boring constructors clarify class boundaries, helping AI understand relationships and intentions.

  • Error Reduction: AI relies on assumptions about instantiation. Hidden constructor logic increases hallucination risks and faulty suggestions.

  • Test Automation: Lightweight constructors simplify AI-generated test cases, enabling more reliable automation.


When code adheres to clear, strict boundaries, AI tools can reason better, generate more meaningful code, and reduce errors in your systems.


Summary: Keep Constructors Boring


A constructor should be a contract, not a command center.


When you keep it clean — no logic, no decisions, no side effects — your objects become easier to test, easier to trust, and easier to extend.


You stop building accidental complexity. You start building confidence.


This isn't just about tidiness — it's about design that scales. Code that's predictable for your teammates. And readable by your AI assistant.


So the next time you're in a constructor and feel tempted to be clever, ask yourself:

Am I building an object — or starting a process?

If it’s the latter, you’re in the wrong place.


Because boring constructors don’t just make good code — they make great architecture.


Boring constructors build brilliant systems.

Sign up to get the latest coding tips and architecture insights.

  • LinkedIn
  • X

Code, architecture, and the future of software — one post at a time.

Contact me: 

bottom of page