티스토리 뷰
인프런 클린코드 워밍업클럽을 시작하며, 미션과 함께 클린코드와 SOLID 원칙을 정리한다.
Readable Code: 읽기 좋은 코드를 작성하는 사고법 강의 | 박우빈 - 인프런
박우빈 | , [사진]저 사람은 코드를 되게 잘 짜네. 어떻게 저런 코드를 작성하는 걸까? 🤔어떤 사람의 코드를 보고 '와 잘 짰다' 라고 느낄 때가 있습니다.우리가 '코드를 잘 짠다' 라고 표현하는
www.inflearn.com
클린코드 리팩토링
package cleancode.mission;
public class mission4 {
public boolean validateOrder(Order order) {
if(order.getItems().size == 0) {
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;
}
}
눈에 띄는 리팩토링 요소
- Getter 사용 : Order의 정보를 무작정 get을 요구하고 있다. validateOrder 가 Order 안에 어떤 정보들이 있는지 하나씩 알고 있어야 할까? 메서드를 통하여 Order와 validateOrder 가 소통해야 한다.
- 중첩된 if-else : 반복된 if 문으로 가독성이 매우 떨어진다 early Return을 적용할 수 있는 부분을 찾거나 중첩된 조건을 메서드화 하여 분리하면 좋을 듯하다.
- 복잡한 부정문의 사용 :! 부정문은 무조건 사고를 2회 하게 한다. 거기에 get호출을 하여 조건을 확인하고 부정하는 조건까지 너무 복잡하다. 단순화시킬 필요가 있다.
- NPE를 포함한 예외 관리 : 예외를 발생하는 조건이 너무 부족하다. order의 반환값이 null 일 경우? , 상황별 예외를 적절히 잡을 수 있도록 구조화가 필요하다.
1. 이중 중첩 해결
큰 그림을 잡기 위해 구조를 먼저 수정해 본다.
else를 쓴다면 이전의 if 조건들의 내용을 기억해야 한다. else 를 최대한 쓰지 않도록 분리해 본다.
package cleancode.mission;
public class mission4 {
public boolean validateOrder(Order order) {
// 아이템이 없는 경우
if(order.getItems().size == 0) {
return false;
}
// 아이템이 있지만 금액이 정상적이지 않은 경우
if (!(order.getTotalPrice()) > 0) {
log.info("올바르지 않은 총 가격입니다.");
return false;
}
// 아이템도 금액도 있지만, 사용자 정보가 없는 경우
if(!order.hasCustomerInfo()) {
log.info("사용자 정보가 없습니다.");
return false;
}
return true;
}
}
2. 이해하기 어려운 부정문
괄호까지 활용하는 이중의 삼중 부정문을 수정한다.
package cleancode.mission;
public class mission4 {
public boolean validateOrder(Order order) {
// 아이템이 없는 경우
if(order.getItems().size == 0) {
return false;
}
// 아이템이 있지만 금액이 0보다 작거나 같은 경우
if (order.getTotalPrice() <= 0) {
log.info("올바르지 않은 총 가격입니다.");
return false;
}
// 아이템도 금액도 있지만, 사용자 정보가 없는 경우
if(!order.hasCustomerInfo()) {
log.info("사용자 정보가 없습니다.");
return false;
}
return true;
}
}
3. Getter?
꼭 getter 가 필요할까? validateOrder 가 Order의 특정 필드 값을 알고 싶어 하는 건가?
Setter는 물론이고 Getter도 굳이 사용하지 말자,
사용하기 전에 Order에게 물어볼 수 없는지 생각해 보자. 메시지와 메서드로 소통하는 게 객체니까.
package cleancode.mission;
public class mission4 {
public boolean validateOrder(Order order) {
// 아이템이 없는 경우 (아이템의 숫자는 중요하지 않다. 유무 판단)
if(order.doesNotHaveItems()) {
return false;
}
// 아이템이 있지만 금액이 0보다 작거나 같은 경우 (정확한 금액은 필요하지 않다)
if (order.doesNotHavePrice()) {
log.info("올바르지 않은 총 가격입니다.");
return false;
}
// 아이템도 금액도 있지만, 사용자 정보가 없는 경우
if(order.isEmptyCustomerInfo()) {
log.info("사용자 정보가 없습니다.");
return false;
}
return true;
}
}
3. MyException!
정해진 방식은 의도한 예외로 나타나도록 한다.
예상하지 못한 예외가 발생할 경우 다른 방식 출력하여 로그를 관리할 수 있도록 한다.
package cleancode.mission;
public class mission4 {
public boolean validateOrder(Order order) {
try {
// 아이템이 없는 경우 (아이템의 숫자는 중요하지 않다. 유무 판단)
if(order.doesNotHaveItems()) {
return false;
}
// 아이템이 있지만 금액이 0보다 작거나 같은 경우 (정확한 금액은 필요하지 않다)
if (order.doesNotHavePrice()) {
return false;
}
// 아이템도 금액도 있지만, 사용자 정보가 없는 경우
if(order.isEmptyCustomerInfo()) {
return false;
return true;
} catch (OrderValidateException e) {
log.info("주문 확인에 문제가 발생했습니다.");
} catch (Exception e) {
log.info("주문 확인에 문제가 발생했습니다.");
}
}
}
정리하며,
SOLID 원칙 이전에 리팩토링 관점의 클린코드를 배워 정리해 보았다.
프로젝트를 진행하며 느꼈던 불편함이 생각나기도 했고, 부정문을 수정하는 부분은 단기적으로 체감하기에는 어려운 부분이기도 했다. 다른 코드들을 많이 읽어봐 좋은 코드에 대한 경험을 늘려야 할 필요를 느꼈다.
SOLID 원칙
객체 지향 프로그래밍을 하기 위한 기본 틀이다.
자바를 활용한다면 당연히 지켜져야 하는 부분이고, 모르고 짜는 코드와는 다른 결과가 나올 것이다.
SRP(Single Responsibility Principle) 단일 책임 원칙
객체는 하나의 책임만 가진다.
객체와 객체는 공개 메서드, 필드, 상수를 활용하여 소통한다.
하나의 책임만 가지기 때문에 변경될 때도 오직 그 책임에 관련된 이유에만 변경될 수 있다.
책임을 분리하면 클래스가 분리된다.
피겨를 하는 김연아와 연기를 하는 김태희의 책임은 다르다.
이 둘이 방송에 나온다면, 피겨 질문은 김연아에게 연기 질문은 김태희에게 하지 않겠는가?
책임을 나누면 객체 간의 결합도는 낮아지고, 응집도는 높아진다.
하나의 책임 영역을 수정해도 다른 객체는 영향받지 않는다. 결국 유지보수성이 좋아진다.
OCP(Open Closed Principle) 개방 폐쇄 원칙
기존 코드의 변경 없이 새로운 요구사항을 반영할 수 있어야 한다.
이 부분도 책임에 연관된다. 게임에 새로운 레벨이 추가된다고, 게임 로고가 바뀌지는 않는다.
게임에 새로운 스테이지가 추가된다고, 튜토리얼이 바뀌지는 않는다.
확장에는 열리고 수정에는 닫혀야 한다. 튜토리얼을 수정하려면 튜토리얼 코드를 고쳐야 한다.
추상과 다형성을 활용하여 이 원칙을 잘 지킬 수 있다.
LSP(Liskov Substitution Principle) 리스코프 치환 원칙
자식은 부모의 책임을 준수하지만 부모의 책임과 행동에 영향을 줄 수는 없다.
이것이 결국 상속이다.
부모 클래스가 가진 역할은 자식에게 내려 줄 수 있다. 또한 자식을 스스로 새로운 메서드를 생성할 수 있다.
하지만 그 메서드가 부모에게 동작할 필요는 없다.
부모님 집에는 내가 태어나서 살고 있다. 이 집 쌀과 물은 내 거다. 하지만 내가 독립하면?
ISP(Interface Segregation Principle) 인터페이스 분리 원칙
클라이언트는 자신이 사용하지 않는 인터페이스에 의존할 필요 없다.
게임이라는 인터페이스 내에 ‘게임 초기화’, ‘실행’ 두 개의 역할을 넣어놨다.
모든 게임이 늘 초기화가 필요할 거라 생각했는데, 초기화 없이 실행하는 게임을 개발해 버렸다.
하나의 인터페이스에 두 개가 항상 같이 있다면? 위 요구사항을 반영할 수 없다.
인터페이스는 최대한 쪼개서 설계한다.
DIP(Dependency Inversion Principle) 의존 역전 원칙
상위 수준은 하위 수준에 의존하지 않는다. 추상화가 높은 객체는 구체적인 객체에 의존하지 않는다.
저수준의 구현체가 변경되더라도, 연관된 고수준은 영향받지 않는다.
게임 레벨이 바뀐다고, 다른 스테이지의 코드가 바뀌는 것은 아니다.
인프런 워밍업 클럽 3기 - 인프런 | 학습하기
지금 인프런에서 학습해 보세요! - 학습하기 | 인프런
www.inflearn.com
- Total
- Today
- Yesterday
- 스파르타
- mock
- Database
- java
- datalock
- MYSQL
- 더티체킹
- 트랜잭션
- 객체지향
- 코딩테스트
- feignclient
- http
- TDD
- Lazy
- queryDSL
- proxy
- JPA
- 트러블슈팅
- 동시성
- 프로젝트기획
- 마이크로서비스
- 클린코드
- JPQL
- Spring
- Article
- 리팩토링
- 기술도서
- 인프런
- MSA
- Solid
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |