Skip to content
:tech
← Articles

AI-translated. Read the original in Korean

Keep Principles in Mind

Keep Principles in Mind

Principles matter. Even if you want to declare, “I won’t live by principles!” that sounds like little more than teenage rebellion unless you know what those principles are and understand them well. Admittedly, most people saying this probably mean rules rather than principles, but let’s leave that distinction aside.

Software development has many principles too. You may have encountered them briefly at university or in articles, only to forget them while rushing to add features and fix bugs in a real service. If you pause amid that rush, look at your code, and wonder, “Why is this here?” perhaps that’s a signal to revisit those principles.

This post discusses the S.O.L.I.D. principles described by Uncle Bob—Robert C. Martin, author of Clean Architecture and Clean Code. SOLID takes its name from five principles he proposed for object-oriented design in his 2000 paper Design Principles and Design Patterns. Although proposed for OOD, these ideas also apply to the core philosophy of development approaches such as Agile.

S.O.L.I.D. Principles

Single Responsibility Principle

A class should have only one responsibility and only one reason to change. Consider a module that edits and prints reports. It may change for two reasons: a change in the report’s content, or a change in its format. Modifying it for editing can easily affect printing. It should therefore be split into a module responsible for content and a module responsible for output formatting.

“Just because you can doesn’t mean you should.”

Open / Closed Principle

Software entities—classes, modules, functions, and so on—should be open for extension but closed for modification. A module is open for extension if we can extend it, for example by adding a field to a data structure or adding a function. It is closed when other modules can use it without requiring modifications.

public class CreditCard {
    private int cardType;
 
    public int getCardType() { return cardType; }
 
    public void setCardType(int cardType) { this.cardType = cardType; }
    
    public double getDiscount(double monthlyCost){
         if (cardType == 1) {
             return monthlyCost * 0.02;
         } else {
             return monthlyCost * 0.01;
         }
    }
}

Adding a new card type to this CreditCard class inevitably requires changing getDiscount. That violates the Open/Closed Principle.

“You shouldn’t need abdominal surgery to put on a coat.”

Liskov Substitution Principle

Objects in a program should be replaceable with instances of their subtypes without compromising correctness. Requirements include contravariance of method arguments, covariance of return types, and no new exceptions that do not derive from exceptions allowed by the parent type.

When learning inheritance in OOP, you probably saw examples intended to make it intuitive. Ironically, many examples that seem perfectly reasonable violate this principle. The classic case is squares and rectangles. A square is a kind of rectangle, so having Square inherit from Rectangle seems sensible. But is it? Consider this test for a rectangle’s area:

Rectangle rect = new Rectangle();
rect.setWidth(10);
rect.setHeight(20);
assertEquals(200, rect.getArea());

What happens if we assign new Square() to rect instead of new Rectangle()? The area becomes 400 and the test fails. In this case, making Square inherit from Rectangle violates the Liskov Substitution Principle. Inheritance is an appealing way to solve problems, but it is easy to misuse.

“If it looks like a duck and quacks like a duck, but needs batteries, it isn’t a duck.”

Interface Segregation Principle

Several interfaces designed for particular clients are better than one broad, general-purpose interface. While developing software for a new printer with features such as stapling—yes, stapling—and faxing, Xerox found its program had become too tangled to continue developing and asked Uncle Bob for help.

The problem was that a single Job class implemented every feature. This bloated class exposed all its functions to clients, even functions they would never use. Uncle Bob applied the Interface Segregation Principle, creating separate interfaces that exposed only the functions each client needed. He then used the next principle, Dependency Inversion, to implement the functionality and resolve the problem.

Dependency Inversion Principle

“Depend on abstractions, not on concretions.”

Higher-level modules should depend on abstractions rather than lower-level implementations. Reversing the traditional dependency—from high-level modules to low-level implementations—makes the higher-level modules independent of those implementation details. Dependency injection is one way to follow this principle.

Conclusion

Bad programs do exist. Two programs are not equivalent simply because they offer the same visible features. More code than we might expect ends up somewhere without careful thought, merely because “it can go there” or “it works there.” You might get applause for adding a feature faster today. But as these decisions accumulate, the code can become impossible to change. You recognize the problem, commit to refactoring, tear everything down—and then build the same kind of code again.

Simple code is the hardest code to write, and good code grows from good principles. Building programs that adapt to change, express intent clearly, minimize opportunities for problems, and extend easily is essential to achieving what we actually want. Experience and small tweaks alone won’t get us there. If you’ve been broadening your horizons and gaining experience with new technologies and frameworks, why not stop occasionally and revisit the principles?

Buzzvil is hiring energetic engineers, including candidates for Korea’s specialized research personnel program.

All articles