자바 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와의 연계 방법에 대해 다뤄봅시다!
'IT Developer > Java' 카테고리의 다른 글
Java 기초 <20. Java와 Spring을 함께 사용할 때 유용한 팁 (Spring Boot 연계)> (0) | 2025.03.21 |
---|---|
Java 기초 <18. Java에서의 네트워크 프로그래밍> (0) | 2025.03.19 |
Java 기초 <17. Java 디자인 패턴 – 싱글톤, 팩토리, 전략 패턴 등> (1) | 2025.03.18 |
Java 기초 <16. Java 성능 최적화 및 메모리 관리 (Heap, Stack, GC 튜닝)> (2) | 2025.03.17 |
Java 기초 <15. Java의 리플렉션(Reflection)과 애노테이션(Annotation) 활용> (1) | 2025.03.16 |
Java 기초 <14. JVM 내부 구조 및 GC 원리 (G1 GC, ZGC, Shenandoah)> (1) | 2025.03.15 |
Java 기초 <13. Java 18~현재 최신 트렌드 – Virtual Threads, GraalVM, CRaC> (1) | 2025.03.14 |
Java 기초 <12. Java 5~17 주요 기능 변화 (제네릭, 스트림, 람다, 레코드 등)> (1) | 2025.03.13 |