[4기] 1주 차 발자국 제출_백엔드
SOLID
객체지향 설계를 이해하기 쉽고 유지보수하기 유연하게 형태로 할 수 있도록 도움을 주는 5가지 원칙
SRP : Single Responsibility Principle
OCP : Open-Closed Principle
LSP : Liskov Substitution Principle
ISP : Interface Segregation Principle
DIP : Dependenty Inversion Principle
단일책임원칙 SRP : Single Responsibility Principle
하나의 클래스가 하나의 책임만 갖도록 설계를 하라는 원칙
- 하나의 클래스가 변경 이유가 두가지라면, 해당 클래스의 책임은 2개 이상인 것.
- 객체가 가진 공개 메서드, 필드, 상수 등은 해당 객체의 단일 책임에 의해서만 변경되어야 함
이유 : SRP 를 지키게 되면 높은 응집도를 가지게 되고 낮은 결합도를 가지고 된다.
- 응집도 : 클래스나 모듈 내에 요소들이 긴밀하게 연관되어 있는 정도
- 결합도 : 두개 이상의 객체가 협력했을 때 한 객체가 변경되었을때 다른 객체들에 영향을 미치는 정도
개방-폐쇄 원칙 OCP : Open-Closed Principle
확장에는 열려있고, 수정에는 닫혀 있어야 한다.
- 새로운 요구사항이 추가되었을 때, 기존 코드의 변경 없이, 시스템의 기능을 확장할 수 있어야 한다.
어떻게 OCP 를 지킬 수 있나 ? >> 추상화와 다형성을 활용해서 OCP 를 지킬 수 있다.
리스코프 치환 원칙 LSP : Liskov Substitution Principle
리스코프 : 바바라 리스코프(사람 이름)
상속 구조에서 부모 클래스의 인스턴스를 자식 클래스의 인스턴스로 치환할 수 있어야 한다.
- 자식 클래스는 부모 클래스의 책임을 준수하며, 부모 클래스의 행동을 변경하지 않아야 한다.
이유 : LSP 를 위반하면, 상속 클래스를 사용할 때 예외가 발생하거나 이를 방지하기 위한 불필요한 타입 체크가 동반될 수 있다. 따라서 상속 구조를 사용하려면 LSP 를 잘 지키는 형태로 구현해야한다.
인터페이스 분리 원칙 ISP : Interface Segregation Principle
클라이언트는 자신이 사용하지 않는 인터페이스에 의존하면 안된다.
- 인터페이스를 잘게 쪼개는 것이 권장됨(=기능단위로 인터페이스를 잘게 나누어 사용해라)
이유 : 인터페이스 하나가 너무 무겁고 여러가지 기능이 합쳐져있게 되면 구현체에서 불필요한 기능까지 구현하게 되어버린다. 불필요한 의존성으로 인해 결합도가 높아지고, 특정 기능의 변경이 여러 클래스에 영향을 미칠 수 있다.
의존성 역전 원칙 : DIP : Dependenty Inversion Principle
상위 수준의 모듈은 하위 수준의 모듈에 의존해서는 안되고, 둘 모두 추상화에 의존해야 한다는 원칙 (= 추상화 레벨이 높은 쪽의 모듈이 구체 쪽에 가까운 모듈에 직접적으로 의존해서는 안된다. 모두 추상화에 의존하자)
- 의존성의 순방향 : 고수준 모듈이 저수준 모듈을 참조하는 것
- 의존성의 역방향 : 고수준, 저수준 모듈이 모두 추상화에 의존하는 것
- 의존성 : 하나의 모듈이 다른 하나의 모듈을 참조하거나, 직접적으로 생성하거나, 사용하는 것
- 저수준 모듈이 변경되어도, 고수준 모듈에는 영향이 가지 않는다.
이유 : 저수준 모듈은 자주 변경되는데, DIP 원칙을 지키면 저수준 모듈이 자유롭게 변경되어도 고수준 모듈에는 영향이 가지 않는다.
코드를 읽을 때 뇌 메모리 적게 쓰도록 도와주는(=가독성 좋은) 코드 작성
1. Early return
- Early return 의 사용으로 else 의 사용을 줄이자
- switch 문 지양
2. 중첩 분기문과 중첩 반복문의 리팩토링
중첩 반복문은 내가 기억해야하는 정보가 너무 많다.
for (let i = 0; i < 5; i++) {
for (let j = 0; j < 3; j++) {
if (i >= 2 && j < 1) {
doSomething(i, j);
}
}
}
doSomething 을 읽을때쯤에는 기억해야하는 정보가 너무 많다.
메서드로 다 쪼개고 동작의 의미를 부여해서 추출한다.
function shouldProcess(i, j) {
return i >= 2 && j < 1;
}
function processInnerLoop(i) {
for (let j = 0; j < 3; j++) {
if (shouldProcess(i, j)) {
doSomething(i, j);
}
}
}
for (let i = 0; i < 5; i++) {
processInnerLoop(i);
}
3. 공백라인을 대하는 자세
공백라인도 의미를 가진다.
- 복잡한 로직의 의미 단위를 나누어 보여줌으로써 읽는 사람에게 추가적인 정보를 전달할 수 있음
4. 부정어를 대하는 자세
복잡한 부정 조건 (읽기 비효율적이다)
관리자거나
밴 당했거나
활성화되지 않은 사용자이면
→ 이메일을 보내지 않는다
for (let user of users) {
if (!(user.isAdmin || user.isBanned || !user.isActive)) {
sendEmail(user);
}
}
function isEligibleForEmail(user) {
return !user.isAdmin && !user.isBanned && user.isActive;
}
for (let user of users) {
if (isEligibleForEmail(user)) {
sendEmail(user);
}
}
이메일을 보내도 되는 사람이면 > 이메일을 보낸다
코드를 읽었을 때 인식하기 쉬워졌다.
또한, 부정 조건이 꼭 필요하다면 부정 연산자를 사용하기 보다 메서드 자체에 부정한 의미(NOT)를 담는 것이 권장된다.
if (!(order.status !== 'CANCELLED' && !order.isLocked)) {
logWarning(order);
return;
}
function isNotProcessable(order) {
return order.status === 'CANCELLED' || order.isLocked;
}
if (isNotProcessable(order)) {
logWarning(order);
return;
}
isNotProcessable라는 이름으로 의미가 명확
조건 해석이 자연어처럼 읽힘: “처리 불가능하면 경고 로그를 남긴다”
4. 해피 케이스와 예외 처리
해피케이스 : 프로그램의 진행방향이 개발자가 원하는 방향으로 잘 흘러가는 것
- 예외 처리를 꼼꼼하는 개발자가 역량이 좋은 개발자로 평가됨
예외 처리를 잘하기
- 예외가 발생할 가능성 낮추기
- 어떤 값의 검증이 필요한 부분은 주로 외부 세계와의 접점
- 사용자 입력, 객체 생성자, 외부 서버의 요청 등
- 의도한 예외와 예상하지 못한 예외를 구분하기
- 사용자에게 보여줄 예외와, 개발자가 보고 처리해야 할 예외 구분
위 내용을 참고하여 읽기 좋은 코드로 리팩토링 하기
✔ 사용자가 생성한 '주문'이 유효한지를 검증하는 메서드.
✔ Order는 주문 객체이고, 필요하다면 Order에 추가적인 메서드를 만들어도 된다. (Order 내부의 구현을 구체적으로 할 필요는 없다.)
✔ 필요하다면 메서드를 추출할 수 있다.
🚨 before
public boolean validateOrder(Order order) {
if (order.getItems().size() == 0) {
log.info("주문 항목이 없습니다.");
return false;
} else {
if (order.getTotalPrice() > 0) {
if (!order.hasCustomerInfo()) {
log.info("사용자 정보가 없습니다.");
return false;
} else {
return true;
}
} else if (!(order.getTotalPrice() > 0)) {
log.info("올바르지 않은 총 가격입니다.");
return false;
}
}
return true;
}
✅ After
public boolean validateOrder(Order order) {
if (isEmptyItems(order)) {
log.info("주문 항목이 없습니다.");
return false;
}
if (isInvalidTotalPrice(order)) {
log.info("올바르지 않은 총 가격입니다.");
return false;
}
if (isMissingCustomerInfo(order)) {
log.info("사용자 정보가 없습니다.");
return false;
}
return true;
}
private boolean isEmptyItems(Order order) {
return order.getItems().isEmpty();
}
private boolean isInvalidTotalPrice(Order order) {
return order.getTotalPrice() <= 0;
}
private boolean isMissingCustomerInfo(Order order) {
return !order.hasCustomerInfo();
}
댓글을 작성해보세요.