IT Developer/Java

Java 기초 <19. 클린 코드 및 리팩토링 – SOLID 원칙 적용>

TEMA_ 2025. 3. 20. 09:28
반응형

자바 Java

19. 클린 코드 및 리팩토링 – SOLID 원칙 적용

 

안녕하세요! 태마입니다.

Java 기초 강좌입니다.

 

강좌의 경우 

1. 주제 간단 정리

2. 상세 주제 정리

으로 이루어져 있습니다.

 

자바 Java

포스팅 시작하겠습니다 :)

 

1. 주제 간단 정리

 

1. 클린 코드란?

**클린 코드(Clean Code)**는 읽기 쉽고, 유지보수하기 쉬운 코드를 의미합니다.
클린 코드직관적이고 명확하며, 버그가 적고, 변경이 용이합니다.
✔ 클린 코드는 개발자 간의 협업을 원활하게 하고, 오래된 코드를 다룰 때도 쉽게 수정할 수 있도록 해줍니다.

📌 클린 코드의 특징
명확한 변수 및 메서드 이름
간결한 코드 : 불필요한 중복을 피하고, 단순한 방식으로 구현
일관성 : 코드 스타일과 규칙을 일정하게 유지
잘 정의된 함수 : 함수는 하나의 작업만 하도록 설계


 

2. 리팩토링(Refactoring) 개념

리팩토링기능은 그대로 유지하면서 코드의 내부 구조를 개선하는 과정입니다.
✔ 리팩토링을 통해 코드의 가독성을 높이고, 유지보수성을 향상시킬 수 있습니다.
✔ 리팩토링은 점진적인 변경을 통해 이루어지며, 테스트가 필수적입니다.

📌 리팩토링의 주요 목적
코드의 가독성이해도 향상
중복 코드 제거
유지보수성 개선
성능 최적화


 

3. SOLID 원칙

SOLID 원칙은 객체 지향 프로그래밍에서 좋은 설계를 위한 다섯 가지 원칙을 말합니다.
S: Single Responsibility Principle (단일 책임 원칙)
O: Open/Closed Principle (개방/폐쇄 원칙)
L: Liskov Substitution Principle (리스코프 치환 원칙)
I: Interface Segregation Principle (인터페이스 분리 원칙)
D: Dependency Inversion Principle (의존성 역전 원칙)

📌 SOLID 원칙을 따르면 객체 지향 설계에서 유연하고 확장 가능한 구조를 만들 수 있습니다.

반응형

2. 상세 주제 정리

 

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

단일 책임 원칙클래스는 하나의 책임만 가져야 한다는 원칙입니다.
✔ 하나의 클래스가 여러 책임을 가지게 되면, 변경이 발생할 때마다 여러 부분을 수정해야 하므로 유지보수가 어려워집니다.
책임이 명확한 클래스는 이해하기 쉽고, 변경 시 다른 부분에 미치는 영향을 최소화할 수 있습니다.

단일 책임 원칙 예제

// 위반된 예제: 여러 책임을 가진 클래스
class User {
    public void saveToDatabase() {
        // DB 저장 로직
    }
    public void sendEmail() {
        // 이메일 발송 로직
    }
}

// 리팩토링 예제: 책임을 분리한 클래스
class User {
    public void saveToDatabase() {
        // DB 저장 로직
    }
}

class EmailService {
    public void sendEmail() {
        // 이메일 발송 로직
    }
}

📌 단일 책임 원칙을 지키면 각 클래스가 하나의 책임만 갖도록 설계되어 코드가 더 명확해짐.


 

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

개방/폐쇄 원칙소프트웨어 엔티티(클래스, 모듈 등)는 확장에는 열려 있어야 하고, 수정에는 닫혀 있어야 한다는 원칙입니다.
기존 코드를 수정하지 않고 새로운 기능을 추가할 수 있도록 설계하는 것이 중요합니다.
✔ 이를 통해 기존 코드의 버그를 수정하지 않고, 새로운 기능을 추가하는 것이 가능합니다.

개방/폐쇄 원칙 예제

// 위반된 예제: 기능이 추가될 때마다 기존 클래스를 수정
class Shape {
    public double area() {
        return 0;
    }
}

class Rectangle extends Shape {
    private double width, height;
    public double area() {
        return width * height;
    }
}

class Circle extends Shape {
    private double radius;
    public double area() {
        return Math.PI * radius * radius;
    }
}

// 리팩토링 예제: 새 기능을 추가할 때 기존 클래스 수정 없이 확장 가능
interface Shape {
    double area();
}

class Rectangle implements Shape {
    private double width, height;
    public double area() {
        return width * height;
    }
}

class Circle implements Shape {
    private double radius;
    public double area() {
        return Math.PI * radius * radius;
    }
}

class AreaCalculator {
    public double calculateArea(Shape shape) {
        return shape.area();
    }
}

📌 개방/폐쇄 원칙을 따르는 설계는 새로운 기능을 추가하는 데 용이하고, 기존 코드를 수정하지 않으므로 안전함.


 

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

리스코프 치환 원칙서브클래스는 언제나 부모 클래스로 대체 가능해야 한다는 원칙입니다.
서브클래스가 부모 클래스의 동작을 완전히 대체해야 하며, 기능이 변경되거나 기대한 대로 동작하지 않으면 안 됩니다.
✔ 이를 통해 상속 계층 구조에서 객체의 교체가 원활하게 이루어집니다.

리스코프 치환 원칙 예제

class Bird {
    public void fly() {
        // 비행 로직
    }
}

class Sparrow extends Bird {
    public void fly() {
        // 참새 비행 로직
    }
}

class Penguin extends Bird { // 위반된 예시
    public void fly() {
        throw new UnsupportedOperationException("펭귄은 날 수 없습니다.");
    }
}

// 리팩토링 예제: 비행할 수 있는 새만 상속받도록 설계
interface Flyable {
    void fly();
}

class Sparrow implements Flyable {
    public void fly() {
        // 참새 비행 로직
    }
}

class Penguin { // 더 이상 비행할 수 없는 새는 Flyable 인터페이스를 구현하지 않음
}

📌 리스코프 치환 원칙을 지키면 부모 클래스와 서브클래스를 교체할 때, 예기치 않은 동작을 방지할 수 있음.


 

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

인터페이스 분리 원칙클라이언트는 자신이 사용하지 않는 메서드에 의존하지 않아야 한다는 원칙입니다.
인터페이스는 그 기능에 맞는 메서드만 제공해야 하며, 불필요한 메서드는 포함하지 않도록 설계해야 합니다.
✔ 이를 통해 인터페이스를 사용하는 클래스가 불필요한 의존성을 가지지 않도록 할 수 있습니다.

인터페이스 분리 원칙 예제

// 위반된 예제: 하나의 인터페이스에 너무 많은 메서드가 포함됨
interface Worker {
    void work();
    void eat();
}

// 리팩토링 예제: 인터페이스를 분리하여 클라이언트가 필요한 기능만 사용
interface Workable {
    void work();
}

interface Eatable {
    void eat();
}

class WorkerImpl implements Workable, Eatable {
    public void work() {
        // 작업 수행
    }

    public void eat() {
        // 식사 수행
    }
}

📌 인터페이스 분리 원칙을 적용하면 각 인터페이스가 작고 명확하여, 클라이언트가 필요한 메서드만 사용할 수 있음.


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

의존성 역전 원칙상위 모듈이 하위 모듈에 의존하지 않도록 하고, 둘 다 추상화된 인터페이스에 의존하도록 하는 원칙입니다.
구체적인 구현 클래스가 아니라 인터페이스나 추상 클래스에 의존하여 유연한 설계를 할 수 있습니다.

의존성 역전 원칙 예제

// 위반된 예제: 상위 모듈이 하위 모듈에 의존
class LightBulb {
    public void turnOn() {
        System.out.println("Lightbulb turned on");
    }

    public void turnOff() {
        System.out.println("Lightbulb turned off");
    }
}

class Switch {
    private LightBulb bulb;

    public Switch(LightBulb bulb) {
        this.bulb = bulb;
    }

    public void operate() {
        bulb.turnOn();
    }
}

// 리팩토링 예제: 인터페이스를 통해 의존성 역전
interface Switchable {
    void turnOn();
    void turnOff();
}

class LightBulb implements Switchable {
    public void turnOn() {
        System.out.println("Lightbulb turned on");
    }

    public void turnOff() {
        System.out.println("Lightbulb turned off");
    }
}

class Switch {
    private Switchable device;

    public Switch(Switchable device) {
        this.device = device;
    }

    public void operate() {
        device.turnOn();
    }
}

📌 의존성 역전 원칙을 사용하면 상위 모듈이 구체적인 하위 모듈에 의존하지 않게 되어 시스템이 유연하고 확장 가능해짐.


지금까지 SOLID 원칙과 리팩토링에 대해 배웠어요!
👉 "이제 Spring과의 연계를 통해 Java 개발을 더욱 효율적으로 만드는 방법을 배우고 싶다면?"
다음 회차에서 Spring Boot와의 연계 방법에 대해 다뤄봅시다!

반응형