Refactoring Bad Code Using SOLID Principles
A step-by-step refactoring walkthrough that turns a tangled Java service into clean, testable code by applying each SOLID principle.
Starting point
public class CheckoutService {
public void checkout(Cart cart, String paymentType) {
double total = 0;
for (var item : cart.items()) total += item.price * item.qty;
if (paymentType.equals("CARD")) total *= 1.029;
else if (paymentType.equals("PP")) total *= 1.034;
var conn = DriverManager.getConnection("jdbc:mysql://...");
var ps = conn.prepareStatement("INSERT INTO orders VALUES (?,?)");
ps.setLong(1, cart.userId()); ps.setDouble(2, total); ps.execute();
// raw SMTP send...
}
}
It violates every SOLID principle.
Step 1 — SRP
class TotalCalculator { double total(Cart c) { /* ... */ } }
class FeeCalculator { double withFees(double total, String type) { /* ... */ } }
class OrderRepository { void save(Order o) { /* ... */ } }
class OrderMailer { void confirm(Order o) { /* ... */ } }
Step 2 — OCP via polymorphism
interface FeeStrategy { String type(); double apply(double amount); }
Step 3 — DIP via constructor injection
@Service
class CheckoutService {
private final TotalCalculator totals;
private final FeeCalculator fees;
private final OrderRepository orders;
private final OrderMailer mailer;
}
Step 4 — ISP
Split OrderMailer so a future SMS service doesn't inherit email-only methods.
Step 5 — LSP check
A CachedOrderRepository must still save orders — not "save sometimes".
End state
```java
@Service
@RequiredArgsConstructor
class CheckoutService {
private final TotalCalculator totals;
private final FeeCalculator fees;
private final OrderRepository orders;
private final OrderMailer mailer;public void checkout(Cart cart, String paymentType) { var raw = totals.total(cart); var total = fees.withFees(raw, paymentType); var order = orders.save(new Order(cart.userId(), total)); mailer.confirm(order); } } ```
The class reads like English, is fully testable with mocks, and accepts new payment methods without modification.
Related tutorials
TL;DR
Key takeaways
- Understand the core concepts behind Refactoring Bad Code Using SOLID Principles in a production context.
- Apply the patterns to real Software Engineering Fundamentals systems, not just toy examples.
- Recognize the trade-offs, failure modes, and operational concerns before adopting them.
- Get a clear path to the next step — related tutorials, tools, and reference architectures.
Avoid these
Common mistakes
1. Copy-pasting code without understanding the trade-offs
It's tempting to ship a snippet from a blog post into production, but Software Engineering Fundamentals patterns only work when the failure modes are understood. Always reason about timeouts, retries, and consistency.
2. Skipping observability from day one
Structured logs, metrics, and traces are not optional. Wire them in before you ship — debugging Software Engineering Fundamentals systems without them is painful and expensive.
3. Optimizing too early
Premature caching, sharding, or microservice extraction adds operational cost. Validate the bottleneck with real measurements first.
4. Ignoring security defaults
Secrets in env files, open management ports, missing RBAC — these are the most common production incidents. Treat security as part of the definition of done.
Ship it safely
Production best practices
Apply these before promoting Refactoring Bad Code Using SOLID Principles to a real production environment.
Scalability
Design Software Engineering Fundamentals services to scale horizontally. Keep request handlers stateless, push session and cache state to external stores (Redis, the database), and benchmark p95/p99 latency under realistic load before tuning.
Monitoring & Observability
Emit metrics (RED/USE), structured JSON logs, and distributed traces from day one. Wire dashboards and alerts to SLOs you actually care about — error rate, latency, saturation — not vanity metrics.
Logging
Log with correlation IDs, never log secrets or PII, and centralize logs (ELK, Loki, CloudWatch). Use levels deliberately: INFO for state changes, WARN for recoverable issues, ERROR for incidents.
Security
Apply least-privilege IAM, rotate secrets through a vault, validate every input, and patch dependencies on a schedule. For HTTP services, enable TLS everywhere and set sensible security headers.
Testing
Layer unit, integration, and contract tests. Run them in CI on every PR, and add smoke tests post-deploy. For Software Engineering Fundamentals systems, also run chaos and load tests before a major release.
Reliability & Rollouts
Ship with health checks, readiness probes, graceful shutdown, and a rollback strategy. Prefer canary or blue/green deploys over big-bang releases.
Questions
Frequently asked questions
Is this tutorial up to date?
Yes. This tutorial was last reviewed and updated on April 7, 2026. We revisit popular Software Engineering Fundamentals tutorials regularly to keep them aligned with current best practices.
What level is this tutorial aimed at?
It is written for working developers with some backend experience. Beginners can still follow along, and senior engineers will find production-grade patterns and trade-off discussions.
Do I need to follow every step in order?
The walkthrough is sequential because each step depends on the previous one. If you only need a specific concept, the table of contents at the top of the article lets you jump straight to that section.
Where can I find the source code?
Code samples are inlined in the tutorial. When a companion repository is published it will be linked at the top of this page.
Go deeper
Further reading
More From the Channel
Follow the full tutorial series on YouTube
The MasterLabSystems channel publishes in-depth, project-based tutorials on Java, Spring Boot, microservices, Docker, Kubernetes, AWS and DevOps — the same topics covered on this site, with full code walkthroughs.
Stay in the Loop
Get the next tutorial in your inbox
next tutorial →
Essential Software Design Principles Every Developer Should Know
Related tutorials
Understanding SOLID Principles in Object-Oriented Design
A complete introduction to the five SOLID principles with Java examples and Spring Boot context — the foundation of clean object-oriented code.
Single Responsibility Principle (SRP) Explained with Java Examples
Master the Single Responsibility Principle with Java and Spring Boot examples — why classes with one reason to change are easier to test and evolve.
Open/Closed Principle (OCP) in Real-World Spring Boot Applications
Use the Open/Closed Principle to extend behavior without rewriting tested code — practical Spring Boot examples with polymorphism and strategies.
