Skip to content

SOLID Principles & Code Quality

A class should have only one reason to change. Each class owns one job, making it easier to test, reuse, and modify.

Bad:

public class Report {
public void GenerateReport() { /* ... */ }
public void SaveToFile() { /* ... */ }
public void SendEmail() { /* ... */ }
}

Good:

public class ReportGenerator { public Report Generate() { /* ... */ } }
public class FileSaver { public void Save(Report r) { /* ... */ } }
public class EmailSender { public void Send(Report r) { /* ... */ } }

Open for extension, closed for modification. Add new behavior without changing existing code—extend, don’t edit.

Bad:

public class PaymentProcessor {
public void Process(string method) {
if (method == "Credit") { /* credit logic */ }
else if (method == "PayPal") { /* paypal logic */ }
}
}

Good:

public interface IPaymentMethod { void Process(); }
public class CreditCard : IPaymentMethod { public void Process() { /* ... */ } }
public class PayPalMethod : IPaymentMethod { public void Process() { /* ... */ } }
public class PaymentProcessor { public void Process(IPaymentMethod method) { method.Process(); } }

Subclasses must be substitutable for their base type. If Bird flies, all birds should fly—no surprises.

Bad:

public class Rectangle { public int Width { get; set; } public int Height { get; set; } }
public class Square : Rectangle { /* forces Width == Height */ }

Good:

public interface IShape { int Area(); }
public class Rectangle : IShape { public int Area() { return Width * Height; } }
public class Square : IShape { public int Area() { return Side * Side; } }

Don’t force clients to depend on methods they don’t use. Small, focused interfaces beat one fat interface.

Bad:

public interface IMultiFunctionDevice { void Print(); void Scan(); void Fax(); }
public class SimplePrinter : IMultiFunctionDevice {
public void Print() { /* ... */ }
public void Scan() { throw new NotImplementedException(); }
public void Fax() { throw new NotImplementedException(); }
}

Good:

public interface IPrinter { void Print(); }
public interface IScanner { void Scan(); }
public interface IFaxer { void Fax(); }
public class SimplePrinter : IPrinter { public void Print() { /* ... */ } }

Depend on abstractions, not concretions. High-level modules shouldn’t care how low-level modules work—inject the abstraction.

Bad:

public class UserService {
private EmailSender _sender = new EmailSender();
public void NotifyUser(User u) { _sender.Send(u.Email); }
}

Good:

public interface IMessageSender { void Send(string recipient); }
public class UserService {
private IMessageSender _sender;
public UserService(IMessageSender sender) { _sender = sender; }
public void NotifyUser(User u) { _sender.Send(u.Email); }
}

PrincipleDefinitionWhen It Matters
DRY (Don’t Repeat Yourself)One source of truth per concept. Duplicate code = future bugs.Refactoring duplicates is your first red flag.
KISS (Keep It Simple, Stupid)Simple code beats clever code. Readability > cleverness.Over-engineering often hides under “design patterns.”
YAGNI (You Aren’t Gonna Need It)Don’t build features you might use later.Speculative generality is a code smell—cut it.
Composition over InheritanceFavor “has-a” over “is-a”. Inheritance chains = rigid, fragile.Mixins, traits, and interfaces are your friends.

Code smells aren’t bugs—they’re warning signs. They cluster around SOLID violations.

SmellDefinitionInterview Q
BloatersLong Method, Large Class, Primitive Obsession, Long Parameter List”How would you reduce a 500-line method?” (Decompose.)
OO AbusersSwitch Statements, Temp Fields, Refused Bequest”Why is a switch statement a smell?” (Usually violates OCP.)
Change PreventersDivergent Change, Shotgun Surgery”What’s Shotgun Surgery?” (One change, many files.)
DispensablesDuplicate Code, Dead Code, Lazy Class”How do you find dead code?” (Coverage reports + linters.)
CouplersFeature Envy, Message Chains, Middle Man”What’s Feature Envy?” (Method uses another class’s data.)

BOCS-D: Bloaters, OO Abusers, Change preventers, Dispensables, Couplers.


  1. Write tests FIRST. Establish a baseline; don’t refactor blind.
  2. Eliminate duplicated code. Extract common logic into helper methods or base classes.
  3. Improve naming. Rename variables/methods to clarify intent (takes courage, massive payoff).
  4. Simplify conditionals. Replace nested if/else with guards, polymorphism, or strategy patterns.
  5. Decompose large methods. Extract logical chunks into separate methods (aim for <20 lines).
  6. Remove dead code. Delete unused variables, methods, branches—no sentimental code.
  7. Apply SOLID & patterns. Use these principles to guide bigger refactors.
  8. Check security. Ensure no new injection points, data leaks, or validation gaps introduced.
  9. Run unit tests. Green = safe. Red = you broke something; fix it before continuing.
  10. Code review. Fresh eyes catch things you miss. Discuss trade-offs.
  11. Performance test. Refactored code should not regress speed; profile before/after if performance matters.