[워밍업 클럽 스터디 2기] 1주차 발자국 🐾
1주차 진도
섹션 2. 추상
섹션 3. 논리, 사고의 흐름
섹션 4. 객체 지향 패러다임 (SOLID)
SRP(Single Responsibility Principle) : 단일 책임 원칙
클래스는 단 하나의 책임을 가져야 한다.
변경하는 이유는 단 하나여야 한다.
SRP 원칙을 잘 지키면, 유지보수가 필요한 상황이 생겼을 때 수정할 대상이 명확해진다.
클래스를 분리할 때 주의점
어떤 파라미터를 전달할지 객체 입장에서 고민하기
예) 익셉션 핸들러에게 e를 줄까? e.getMessage를 줄까? ⇒ e ( ∵ e를 가지고 뭘 할지는 핸들러가 알아서 해 ~ )
클래스에게서 get으로 필드를 가져오는 건 무례하다구! Rude! ⇒ 메서드로 필요한 정보만을 요청하자
예) 지뢰찾기 게임
‘게임의 진입점’과 ‘지뢰찾기 실행’ 부분 분리
입출력 관련한 것 분리
게임보드 분리
OCP(Open Closed Principle) : 개방 폐쇄 원칙
확장에는 열려 있고, 수정에는 닫혀 있어야 한다.
새로운 요구사항이 생겼을 때, 기존 코드의 수정은 최소화 되고 새로운 기능 확장은 손쉽게 되도록 해야 한다.
인터페이스를 활용하면 새로운 기능을 추가할 때
기존 코드를 수정하지 않고
새로운 구현체 클래스를 생성하면 된다.
예) 지뢰찾기 게임 : 난이도에 따라 보드의 크기를 달리 하는 기능 추가
상수로 박아둔 크기를 변수 처리
GameLevel 인터페이스 사용
각 난이도는 각 구현체로
LSP(Listov Substitution Principle) : 리스코프 치환 원칙
부모 클래스의 인스턴스를 자식 클래스의 인스턴스로 치환할 수 있어야 한다.
상속 구조로 리팩토링할 때, 타입 체크를 하는 방향은 좋은 설계가 아니다.
기존 패턴을 유지하면서 통합하는 방식으로 인터페이스를 만들면 타입 체크가 생기게 돼 ⇒ 더 추상화된 관점에서 새로운 설계를 떠올릴 수 있어야 해
예) 지뢰찾기 게임 : 지뢰 Cell, 숫자 Cell, 빈 Cell을 Cell을 상속 받는 구조로 리팩토링
지뢰 Cell을 구현체로 만들었으므로 플래그를 세우는 turnOn 메서드 삭제
숫자 Cell을 구현체로 만들었으므로 주변부 지뢰 개수를 세서 update하는 메서드 삭제 ⇒ 생성자로 넣기
ISP(Interface Segregation Principle) : 인터페이스 분리 원칙
클라이언트는 자신이 사용하지 않는 인터페이스에 의존해서는 안 된다.
예) 지뢰찾기 말고 다른 게임이 추가되는 상황
새로운 게임은 initialize 메서드 사용하지 않아 ⇒ Game 인터페이스를 GameInitial과 GameRunnable로 분리
DIP(Dependency Inversion Principle) : 의존 역전 원칙
상위 수준의 모듈은 하위 수준의 모듈에 의존해서는 안 된다. ⇒ 둘 다 추상화에 의존해야 한다.
의존성의 순방향 : 고수준 모듈이 저수준 모듈을 참조하는 것
의존을 역전시킨다 = 고수준, 저수준 모듈 모두 추상화에 의존한다.
저수준은 구체 쪽에 가깝기 때문에 수정이 잦으므로 고수준이 추상화 된 스펙만 참조하도록 한다.
저수준 모듈이 자유롭게 변경되어도 고수준 모듈에는 영향이 가지 않게 설계해야 한다.
예) 콘솔 외의 다른 I/O가 추가되는 상황
현재 코드에서 수정하려 한다면, 마인스위퍼의 수정 불가피 해 (저수준 모듈의 변경이 고수준 모듈에 영향을 미치는 것)
콘솔 핸들러를 구현체로 가지는 인풋,아웃풋 핸들러 인터페이스 생성해서 마인스위퍼에서 사용
기존 메서드명이 어색해진다면 이것도 더 추상화 된 메서드 명으로 수정 : printXXX → showXXX
회고
칭찬하고 싶은 점 : 워밍업 클럽의 미션을 성실히 수행했다.
아쉬웠던 점 : 진도를 따라잡기 위해 리팩토링을 직접 고민해보는 과정은 생략했다.
보완하고 싶은 점 : 강의에서 질문거리가 나오면 잠시 멈춰두고 혼자 고민하는 시간을 가지면 좋을 것 같다.
미션
As Is
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; }
To Be
if - else 문은 else 대신 return을 사용해서 Early return
중첩 조건문은 메서드로 추출해서 사고의 depth 줄이기
의미 단위로 공백 주기
부정어 줄이기
public boolean validateOrder(Order order) {
if (order.getItems().size() == 0) {
log.info("주문 항목이 없습니다.");
return false;
}
if (order.getTotalPrice() > 0) {
return checkCustomerInfo(order);
}
log.info("올바르지 않은 총 가격입니다.");
return false;
}
public boolean checkCustomerInfo(Order order) {
if (order.hasCustomerInfo()) return true;
log.info("사용자 정보가 없습니다.");
return false;
}
댓글을 작성해보세요.