6가지 좋은 코드 작성을 위한 원칙
- DRY(Don’t Repeat Yourself)
// 중복 제거된 문자열 사용
void printMessage(String message) {
print(message);
}
void main() {
printMessage('안녕하세요, 환영합니다!');
printMessage('안녕히 가세요, 다음에 또 만나요!');
}
- PIE(Program Intently and Expressively)
- 애매한 이름 사용 금지
- 누가 봐도 알기 쉬운 이름 사용
- 컨벤션 따르기
- 매직 넘버에 이름 붙이기
// 의도가 명확한 변수와 함수명
var width = 5;
var height = 3;
int calculateRectangleArea(int length, int breadth) {
return length * breadth;
}
- SRP(Single Responsibility Principle)
- 단일 책임 원칙
- 1개의 클래스는 1개의 일만 수행
- 한 부분의 에러를 수정하기 위해서는 그 클래스만 수정하면 됨
- 클래스 분리가 심해지면 오히려 관리가 어렵기도 함
// 단일 책임을 갖는 클래스
class UserService {
void saveUser(User user) {
// 사용자 저장 로직
}
}
class EmailService {
void sendEmail(User user) {
// 이메일 전송 로직
}
}
- OCP(Open Closed Principle)
- 확장에 대해서는 열려있고 (확장은 자유롭고), 변경에 대해서는 닫혀있다 (의존 부분의 변경은 불필요), 수정 없이 확장 가능하도록 하자
- Iterable, Comparator 등이 좋은 예
- String 의 경우는 상속 금지이므로 OCP에 반하는 클래스의 대표적인 예
- 인터페이스를 적극 활용하여 확장 가능하게 하자
확장 가능한 디자인 (다형성 활용)
abstract class Shape {
double calculateArea();
}
class Rectangle implements Shape {
double width;
double height;
@override
double calculateArea() {
return width * height;
}
}
class Circle implements Shape {
double radius;
@override
double calculateArea() {
return 3.14 * radius * radius;
}
}
- SDP(Stable Dependencies Principle)
- 안전한 것에 의존
- 암호 처리 같이 한번 완성되면 수정될 가능성이 없는 클래스에 의존할 만 함
- 가장 좋은 것은 특정 클래스가 아니라 인터페이스에 의존하는 것
- 클래스는 생성자가 변할 수 있으나 인터페이스는 거의 그대로
// 주문(Order) 모듈이 결제(Payment) 모듈에 의존하지 않음
class Order {
// 주문 로직
void processPayment(Payment payment) {
payment.makePayment();
}
}
class Payment {
// 결제 로직
void makePayment() {
// 결제 처리 로직
}
}
- ADP(Acyclic Dependencies Principle)
- 의존성 비순환 원칙
- 의존 관계에 사이클이 발생되지 않게 한다
// 주문(Order) 모듈과 결제(Payment) 모듈이 순환 의존성이 없음
class Order {
// 주문 로직
void processPayment(Payment payment) {
payment.makePayment();
}
}
class Payment {
// 결제 로직
void makePayment() {
// 결제 처리 로직
}
}
SOLID 원칙
- SRP(Single Responsiblity Principle, 단일 책임 원칙): 위 내용과 동일
- OCP(Open Closed Principle, 개방-폐쇄 원칙): 위 내용과 동일
- LSP(Liskov Substitution Principle, 리스코프 치환 원칙)
- 자식 클래스는 언제나 자신의 부모 클래스를 대체할 수 있다는 원칙
- 부모 클래스가 들어갈 자리에 자식 클래스를 넣어도 계획대로 잘 작동해야 한다
- 자식클래스는 부모 클래스의 책임을 무시하거나 재정의하지 않고 확장만 수행
class Bird {
void fly() {
print('새가 날고 있습니다.');
}
}
class Penguin extends Bird {
// 펭귄은 날지 못하므로 fly 메서드를 오버라이드하지 않음
}
void makeBirdFly(Bird bird) {
bird.fly();
}
void main() {
Bird bird = Bird();
Penguin penguin = Penguin();
makeBirdFly(bird); // "새가 날고 있습니다."
makeBirdFly(penguin); // 펭귄은 날지 못하므로 fly 메서드 호출은 일어나지 않음
}
- ISP(Interface Segregation Principle, 인터페이스 분리 원칙)
- 한 클래스는 자신이 사용하지않는 인터페이스는 구현하지 말아야 한다
- 하나의 일반적인 인터페이스보다 여러개의 구체적인 인터페이스가 낫다
// ISP를 준수한 추상 클래스
abstract interface class Animal {
void eat();
}
abstract class Flyable {
void fly();
}
class Bird implements Animal, Flyable {
@override
void eat() {
print('새가 먹이를 먹습니다.');
}
@override
void fly() {
print('새가 날고 있습니다.');
}
}
class Penguin implements Animal {
@override
void eat() {
print('펭귄이 먹이를 먹습니다.');
}
}
- DIP(Dependency Inversion Principle, 의존 역전 원칙)
- 의존 관계를 맺을 때 변화하기 쉬운 것 또는 자주 변화하는 것보다는 변화하기 어려운 것, 거의 변화가 없는 것에 의존하라는 것
- 구체적인 클래스보다 인터페이스나 추상 클래스와 관계를 맺으라는 것
abstract class Switchable {
void turnOn();
void turnOff();
}
class LightBulb implements Switchable {
@override
void turnOn() {
print('전등이 켜졌습니다.');
}
@override
void turnOff() {
print('전등이 꺼졌습니다.');
}
}
class Switch {
Switchable device;
Switch(this.device);
void operate() {
device.turnOn(); // 고수준 모듈(Switch)이 추상화(Switchable)에 의존
}
}