코드 작성시 지켜야한다는 SOLID 원칙이란 대체 무엇인가?

코딩과 교육/전문코딩 2025. 2. 4. 09:27

소프트웨어 개발에서 코드의 품질은 단순히 작동 여부를 넘어 유지보수성과 확장성에 달려 있습니다. SOLID 원칙은 객체 지향 설계에서 소프트웨어를 더 이해하기 쉽고, 유지보수 가능하며, 확장 가능하게 만드는 다섯 가지 핵심 원칙을 나타냅니다. 이 원칙들은 로버트 C. 마틴(Robert C. Martin)에 의해 제안되었으며, 유지보수성과 확장성을 높이기 위한 지침으로 널리 사용됩니다. 이 글에서는 SOLID 원칙이 무엇인지, 각 원칙이 왜 중요한지, 그리고 이를 실제 프로젝트에서 어떻게 적용할 수 있는지 알아보겠습니다.

SOLID는 다음 다섯 가지 원칙의 약어입니다.

  1. 단일 책임 원칙 (Single Responsibility Principle, SRP)
  2. 개방-폐쇄 원칙 (Open/Closed Principle, OCP)
  3. 리스코프 치환 원칙 (Liskov Substitution Principle, LSP)
  4. 인터페이스 분리 원칙 (Interface Segregation Principle, ISP)
  5. 의존관계 역전 원칙 (Dependency Inversion Principle, DIP)

위 5가지 원칙들에 대해서 조금 더 자세히 살펴보겠습니다.

1. 단일 책임 원칙 (Single Responsibility Principle, SRP)

  • 클래스는 단 하나의 책임만 가져야 하며, 변경의 이유가 하나뿐이어야 한다.
  • 사용자 데이터를 관리하는 클래스가 있다고 가정해 봅시다. 이 클래스가 사용자 인증, 프로필 관리, 이메일 알림까지 모두 처리한다면 SRP를 위반한 것입니다. 각 기능을 별도의 클래스로 분리하면 코드의 가독성과 이후 코드 사용의 용이성이 크게 향상됩니다.

 

// SRP 위반 
class UserManager {
    void authenticateUser() { ... }
    void manageProfile() { ... }
    void sendEmailNotification() { ... }
}

// SRP 준수
class UserAuthenticator { void authenticateUser() { ... } }
class UserProfileManager { void manageProfile() { ... } }
class EmailNotifier { void sendEmailNotification() { ... } }

 

2. 개방-폐쇄 원칙 (Open/Closed Principle, OCP)

 

  • 소프트웨어 엔티티(클래스, 모듈 등)는 확장에는 열려 있어야 하지만 변경에는 닫혀 있어야 한다.
  • 새로운 기능을 추가할 때 기존 코드를 수정하지 않고 확장할 수 있어야 합니다. 예를 들어, 결제 시스템에서 새로운 결제 방식을 추가하려면 기존 클래스 대신 인터페이스와 확장을 활용합니다.

 

interface Payment {
    void processPayment();
}

class CreditCardPayment implements Payment {
    public void processPayment() { ... }
}

class PayPalPayment implements Payment {
    public void processPayment() { ... }
}

 

3. 리스코프 치환 원칙 (Liskov Substitution Principle, LSP)

 

  • 서브클래스는 언제나 부모 클래스 타입으로 대체 가능해야 한다.
  • Bird라는 부모 클래스가 fly() 메서드를 가지고 있고, Penguin 서브클래스가 이를 오버라이드하여 예외를 던진다면 LSP를 위반한 것입니다. 이 문제는 인터페이스로 해결할 수 있습니다.

 

interface Flyable {
    void fly();
}

class Sparrow implements Flyable {
    public void fly() { ... }
}

class Penguin {
    // Penguins do not implement Flyable
}

 

4. 인터페이스 분리 원칙 (Interface Segregation Principle, ISP)

 

  • 클라이언트는 자신이 사용하지 않는 메서드에 의존하지 않아야 한다.
  • 하나의 거대한 인터페이스 대신 작은 인터페이스로 나누어 각 클래스가 필요한 기능만 구현하도록 합니다.

 

// ISP 위반
interface Animal {
    void fly();
    void swim();
}

class Fish implements Animal {
    public void swim() { ... }
    public void fly() { throw new UnsupportedOperationException(); }
}

// ISP 준수
interface Swimmable {
    void swim();
}

interface Flyable {
    void fly();
}

class Fish implements Swimmable {
    public void swim() { ... }
}

 

5. 의존관계 역전 원칙 (Dependency Inversion Principle, DIP)

 

  • 고수준 모듈은 저수준 모듈에 의존해서는 안 되며, 둘 다 추상화에 의존해야 한다.
  • 객체 생성 시 구체적인 클래스 대신 인터페이스에 의존하고, 의존성 주입(Dependency Injection)을 활용합니다.

 

// DIP 준수
interface Database {
    void saveData(String data);
}

class MySQLDatabase implements Database {
    public void saveData(String data) { ... }
}

class DataService {
    private Database database;

    public DataService(Database database) {
        this.database = database;
    }

    public void save(String data) {
        database.saveData(data);
    }
}

 

왜 SOLID 원칙을 따라야 할까?

 

- "유지보수성 향상": 코드가 더 읽기 쉽고 수정하기 쉬워집니다.
- "확장성 증가": 새로운 기능 추가 시 기존 코드를 수정할 필요가 없습니다.
- "결합도 감소 및 응집도 증가": 각 클래스와 모듈이 독립적으로 동작할 수 있습니다.
- "테스트 용이성 개선": 단일 책임과 낮은 결합도 덕분에 테스트가 간단해집니다.