Required Setup/Teardown Code Smell
The Required Setup/Teardown code smell occurs when callers must manually execute mandatory setup or cleanup code before and after using a particular class or method. Instead of the class managing its own lifecycle, the responsibility is pushed onto the caller. The result is that correct usage depends on convention and memory rather than structure, making it easy to introduce bugs by simply forgetting a step.
This smell often appears in classes that manage resources (database connections, file handles, locks), classes that must be initialized before use, or test helpers that require explicit reset between runs. It is a sign that the class is not fully encapsulating its own behavior.
Problems Caused by Required Setup/Teardown Code
Easy to Forget
When setup or teardown is mandatory but optional to write, it will be forgotten. Missing teardown can cause resource leaks, corrupted state, or flaky tests. Missing setup can cause cryptic errors that are far from the root cause. These bugs can be difficult to reproduce and trace.
Coupling Callers to Implementation Details
When every caller must know the correct initialization sequence, changes to that sequence ripple through every call site. The class’s internal requirements become part of the public contract in a way that is difficult to communicate and enforce.
Reduced Encapsulation
A core principle of object-oriented design is that a class owns its own state and behavior. When callers must reach in to set up or tear down that state, encapsulation is broken. The class is no longer a self-contained unit.
Brittle Tests
Test code is especially vulnerable to this smell. Test fixtures that require careful setup and teardown in every test method tend to become fragile. A single forgotten reset leaves state leaking between tests, causing failures that are order-dependent and hard to diagnose.
Example
Consider a service that wraps a database connection but requires callers to open and close it explicitly:
public class ReportService
{
private SqlConnection _connection;
public void Initialize(string connectionString)
{
_connection = new SqlConnection(connectionString);
_connection.Open();
}
public Report GetReport(int id)
{
// uses _connection
}
public void Shutdown()
{
_connection?.Close();
_connection?.Dispose();
}
}
// Caller
var service = new ReportService();
service.Initialize(connectionString); // easy to forget
var report = service.GetReport(42);
service.Shutdown(); // easy to forgetIf Initialize or Shutdown is omitted, the service either fails with a null reference or leaks a connection. The caller must know — and remember — the required ceremony.
A better design moves connection management inside the class’s constructor, a factory method, or a using-compatible IDisposable pattern:
public class ReportService : IDisposable
{
private readonly SqlConnection _connection;
public ReportService(string connectionString)
{
_connection = new SqlConnection(connectionString);
_connection.Open();
}
public Report GetReport(int id)
{
// uses _connection
}
public void Dispose()
{
_connection?.Dispose();
}
}
// Caller
using var service = new ReportService(connectionString);
var report = service.GetReport(42);
// Dispose is called automatically at end of using blockThe lifecycle is now managed by the class and enforced by the language runtime. The caller cannot forget because there is nothing to remember.
Addressing Required Setup/Teardown Code
Several design techniques eliminate this smell:
Constructor Initialization
Move setup into the constructor. If a class cannot function without a certain initialization step, that step should happen during construction. A fully initialized object is always ready to use.
IDisposable / using
For teardown involving resource release, implement IDisposable (or its equivalent in your language) and use language-level using / try-finally constructs to guarantee cleanup. The runtime enforces teardown rather than relying on callers.
Factory Methods
When construction is complex, use a factory method or builder pattern that produces a fully ready object. The factory encapsulates the ceremony; callers receive an object that is already in a valid state.
RAII (Resource Acquisition Is Initialization)
In languages like C++, the RAII idiom ties resource acquisition to object construction and release to destruction, so the object’s lifetime controls resource lifetime automatically.
Template Method / Hooks
For test setup and teardown, testing frameworks provide lifecycle hooks ([SetUp] / [TearDown] in NUnit, BeforeEach / AfterEach in Jest) that guarantee execution order. Centralizing test initialization in these hooks rather than in each test method reduces duplication and eliminates the risk of forgetting.
Identifying Required Setup/Teardown Code
Look for:
- Classes with methods named
Initialize,Open,Start,Setup,Close,Reset, orShutdownthat must be called by the caller. - Documentation or comments warning callers to call a specific method before or after use.
- Tests that fail intermittently due to shared state not being reset between runs.
- Repeated boilerplate at the top or bottom of methods that use a particular class.