
워밍업 클럽 3기 BE 클린코드&테스트 - 1주차 발자국
강의 수강
Readable Code: 읽기 좋은 코드를 작성하는 사고법
학습 내용 요약
섹션2. 추상
추상화: 구체에서 정보를 제거하고 함축하여 중요한 것만 남기는 과정
구체화: 추상을 보고 유추를 통해 생략된 정보를 재현해내서 이해하는 과정
추상화 레벨: 얼마나 추상화했는지를 나타내는 단계
고수준: 추상화 레벨이 높다 (추상적)
저수준: 추상화 레벨이 낮다 (구체적)
적절한 추상화: 해당 도메인의 문맥 안에서 핵심 개념만 남겨서 표현하는 것
-> 적절한 추상화는 복잡한 데이터/로직을 단순화하여 코드를 이해하기 쉽게 한다.
이름 짓기, 메서드로 추출 + 메서드 선언부, 매직 넘버/매직 스트링 상수로 추출
섹션3. 논리, 사고의 흐름
뇌 메모리 적게 쓰기 (인지적 경제성)
코드를 작성할 때 읽는 사람의 뇌 메모리를 최대한 적게 사용하도록 작성할 것
Early return
early return: 메서드를 분리해 끝낼 수 있는 케이스들은 빨리 return 해버려 아래쪽 케이스를 읽을 때 위쪽 케이스를 신경쓰지 않아도 되도록 하는 것
사고의 depth 줄이기
코드를 읽는 사람의 사고가 적당한 수준으로(추상화된 정도로) 이해할 수 있도록 메서드 분리
공백 라인으로 의미 단위 표현
부정 연산자 제거
예외 처리
개발자가 의도한 예외
개발자가 의도하지 않은 예외
섹션4. 객체 지향 패러다임
관심사의 분리: 관심사에 따라 기능과 책임을 나누어 객체 생성
높은 응집도
낮은 결합도
SOLID
SRP(단일 책임 원칙)
하나의 클래스는 하나의 책임(관심사)만을 가져야 한다.
SRP를 지킴으로써 각 클래스의 응집도를 높이고, 클래스 간의 결합도를 낮출 수 있다.
OCP(개방-폐쇄 원칙)
확장에는 열려 있고, 변경에는 닫혀 있어야 한다.
기존 코드의 변경 없이도 시스템의 기능을 확장할 수 있어야 한다.
추상화와 다형성을 활용해서 OCP를 지킬 수 있다.
LSP(리스코프 치환 원칙)
부모 클래스의 인스턴스를 자식 클래스의 인스턴스로 치환해도 정상 작동해야 한다.
상속 구조에서 자식 클래스는 부모 클래스의 책임을 준수하고 행동을 변경하지 않아야 한다.
ISP(인터페이스 분리 원칙)
클라이언트는 자신이 사용하지 않는 인터페이스에 의존하면 안된다.
인터페이스를 필요한 기능 단위로 잘게 쪼개서 사용해야 한다.
DIP(의존성 역전 원칙)
상위 수준의 모듈은 하위 수준의 모듈에 의존해서는 안 된다.
둘 모두 추상화에 의존해야 한다.
구체적인 구현 클래스가 아닌 추상화(인터페이스, 추상 클래스)에 의존해야 한다.
섹션5. 객체 지향 적용하기
회고
평소에 코드를 짤 때 좋은 코드 작성에 대해서 깊게 생각하지 않았다. 일단 코드가 제기능을 하는지가 가장 큰 관심사였고, 개인 프로젝트를 주로 해왔기 때문에 내가 이해할 수 있으면 가독성에 대해 크게 신경쓰지 않았기 때문이다. 그래서 읽는 사람보다는 코드를 작성하는 나의 입장에서 편한 방향으로 코드를 작성해왔다.
강의를 왜 좋은 코드를 작성해야 하는지 명확하게 이해하고, 지금껏 생각하지 못한 관점을 배울 수 있었다. 또한 강사님을 따라 코드를 리팩토링하며 앞으로 코드를 짤 때 배운 내용을 적용하여 읽는 이가 이해하기 쉬운 코드를 짤 수 있도록 노력해야 겠다는 다짐을 하게 되었다.
칭찬하고 싶은점: 바쁜 한주였지만 매일 학습 진도표에 맞춰서 미루지 않고 강의를 들었다.
아쉬웠던 점: 강의 내용을 정리하며 학습하는 것은 조금 밀렸고, 미션 수행만으로는 학습한 내용을 내가 온전히 이해하고 있는지 확인하기 어려웠다.
보완할 점: 이론적인 내용을 정리하는 것 외에도 배운 내용을 실제 코드에 적용해보는 연습이 더 필요할 것 같다.
다음 주 목표: 강의와 미션 외에도 기존에 내가 짠 코드를 리팩토링 해봐야겠다.
미션
코드 리팩토링
리팩토링 전
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;
}
리팩토링 후
public static final String EMPTY_ORDER_MESSAGE = "주문 항목이 없습니다.";
public static final String INVALID_TOTAL_PRICE_MESSAGE = "올바르지 않은 총 가격입니다.";
public static final String NO_USER_INFO_MESSAGE = "사용자 정보가 없습니다.";
public boolean validateOrder(Order order) {
if (order.hasNoItem()) {
log.info(EMPTY_ORDER_MESSAGE);
return false;
}
if (order.hasInvalidTotalPrice()) {
log.info(INVALID_TOTAL_PRICE_MESSAGE);
return false;
}
if (order.hasNoCustomerInfo()) {
log.info(NO_USER_INFO_MESSAGE);
return false;
}
return true;
}
미션 해결 과정
코드를 살펴보며 강의에서 배운 내용을 하나씩 적용해보았다.
중첩되어 복잡한 분기문 -> Early return을 적용해 불필요한 else와 중첩된 분기 제거
if문의 조건이 복잡함 -> if문의 조건을 메서드로 추출 분기 조건이 한번에 이해되도록 함 (사고의 depth 줄임)
또한
order
의 정보를 객체 외부에서 getter로 빼와 조작하는 대신order
가 처리하게 하여 캡슐화를
if문 조건에 부정 연산자 존재 -> 별도의 메서드(hasNoItem())를 만들어 if문 조건의 부정 연산자(!) 제거
매직 스트링이었던 로그 메시지를 상수로 추출해 가독성을 높이고 유지보수를 용이하게 함
읽는 이가 의미 단위로 이해할 수 있도록 공백 라인 추가
회고
if
문의 조건을order
의 메서드로 추출하는 과정에서 메서드의 이름을 짓는 것이 생각보다 고민되었다. 좀 더 많은 코드를 보며 이런 기능의 메서드의 이름을 관용적으로 어떻게 짓는지 공부해볼 필요가 있어 보인다.다시 보니
log.info()
로 처리되고 있는 예외 처리 부분을thrwo
와try-catch
로 관리하는 것이 유지보수에 좋았을 것 같다는 생각이 든다.
댓글을 작성해보세요.