[워밍업 클럽 3기 BE 클린코드&테스트] - 1주차 발자국
회고
1주차에 정말 많은 것을 학습하였습니다. 추상부터 객체지향까지 클린코드에 대해 자세하고 이해가기 쉬운 강의였습니다.
클린코드에 대해 관심을 갖고는 있었지만, 어떻게 적용하고, 어디까지 적용해야하는지가 어려웠었는데, 이제는 감이 조금은 오는것 같습니다. 남은 주차와 강의도 열심히 듣고 참여하겠습니다!
강의 내용 요약
1. 추상
우리가 클린 코드를 추구해야 하는 이유
가독성이 높아지면 코드가 잘 읽히고 이해가 쉬워짐.
유지보수가 용이해져 시간과 자원이 절약됨.
코드의 의도를 명확히 전달하여 협업과 확장성이 향상됨.
추상과 구체
추상화는 불필요한 정보를 제거하고 중요한 정보만 남기는 과정.
추상화 수준이 높을수록 중요한 부분만 남고 불필요한 디테일이 제거됨.
메서드 이름 짓기
이름은 코드의 가독성과 직결됨.
명확하고 직관적인 이름 사용 (줄임말 지양, 도메인 용어 활용).
단수/복수를 명확히 구분하여 의미 전달.
메서드와 추상화
하나의 메서드는 하나의 역할만 수행해야 함.
추상화 수준을 맞춰 메서드를 설계해야 함.
의미를 드러내는 적절한 이름, 파라미터, 반환값을 고려해야 함.
메서드 선언부
메서드명: 추상화된 구체를 유추할 수 있도록 의미 있는 이름 사용.
파라미터: 타입, 개수, 순서를 통해 의미를 명확히 전달.
반환 타입: void 사용을 최소화하고 가급적 값을 반환하도록 설계.
추상화 레벨
하나의 코드 블록 내에서 동일한 추상화 레벨을 유지해야 함.
코드의 가독성과 유지보수성을 높이기 위해 상위 개념과 하위 개념을 분리.
매직 넘버, 매직 스트링
의미 없이 사용된 숫자(매직 넘버)와 문자열(매직 스트링)은 가독성을 해침.
상수로 추출하여 의미를 부여함으로써 가독성과 유지보수성을 확보.
2. 논리, 사고 흐름
Early Return
else if
나else
사용을 최소화하여 흐름을 단순하게 유지.빠르게 반환할 수 있는 경우
return
을 이용해 코드의 복잡도를 줄임.switch
문도 가능하면 피하는 것이 좋음.
사고의 Depth 줄이기
중첩 분기문과 반복문을 줄여 가독성을 높임.
복잡한 로직은 별도의 메서드로 분리하여 한눈에 이해할 수 있도록 설계.
가독성 있는 공백 라인
공백 라인은 로직을 구분하는 역할을 함.
의미 있는 단위로 공백을 활용하여 코드의 흐름을 명확히 표현.
부정어를 대하는 자세 → 긍정으로 처리
부정형(
!isValid
) 대신 긍정형(isInvalid
)으로 변경하여 가독성 향상.독자가 사고를 반전시키지 않도록 명확한 표현 사용.
해피 케이스와 예외 처리
예외는 최소화하고, 발생 시 명확하게 구분하여 처리.
예상하지 못한 예외와 의도된 예외를 구별.
null
반환을 피하고Optional
을 활용하되, 불필요한 사용을 방지.
3. 객체지향 패러다임
객체 설계하기
캡슐화: 객체의 내부 로직을 숨기고, 공개된 메서드를 통해 외부와 소통.
객체 간 협력: 객체의 책임을 분리하여 협업을 유도.
높은 응집도: 관련 기능을 하나의 객체에 모아 유지보수성을 증가시킴.
낮은 결합도: 객체 간 의존성을 최소화하여 확장성을 확보.
객체 생성 시 주의사항
단일 책임 원칙 준수: 하나의 객체는 하나의 명확한 역할만 수행해야 함.
Setter 사용 최소화: 내부 상태를 직접 변경하지 않도록 관리.
불가피한 경우
setX()
대신updateX()
같은 의미 있는 이름 사용.
Getter 사용 지양: 데이터를 직접 가져오는 대신, 객체 내에서 해결하도록 설계
지양하라는거지, 안쓰라는 말이 아님 (꼭 필요하면 써야한다)
필드 최소화: 불필요한 데이터를 줄이고, 계산 가능한 값은 메서드로 제공.
단, 성능 이점이 있다면 필드로 저장 가능.
SOLID 원칙
SRP (단일 책임 원칙): 하나의 클래스는 하나의 책임만 가져야 함.
OCP (개방-폐쇄 원칙): 확장에는 열려 있고, 변경에는 닫혀 있어야 함.
LSP (리스코프 치환 원칙): 하위 클래스는 상위 클래스를 대체할 수 있어야 함.
ISP (인터페이스 분리 원칙): 클라이언트에 맞는 인터페이스를 제공해야 함.
DIP (의존성 역전 원칙): 추상화에 의존하고, 구체적인 구현체에 의존하지 않아야 함.
4. 객체지향 적용하기
상속과 조합
상속(Inheritance)은 공통 기능을 재사용할 때 활용.
조합(Composition)은 유연성과 확장성을 고려하여 객체 간 관계를 구성할 때 활용.
무분별한 상속은 피하고, 조합을 적극 활용하여 결합도를 낮춤.
Value Object (값 객체)
값 자체로 불변성을 가지는 객체.
equals()
와hashCode()
를 오버라이딩하여 동등성 비교.예:
Money
,Address
와 같은 값 중심의 개념.
일급 컬렉션
컬렉션을 감싸는 클래스로, 컬렉션과 관련된 로직을 하나의 객체에서 관리.
컬렉션을 직접 노출하지 않고 캡슐화하여 무결성 유지.
Enum의 특성과 활용
상수 값 그룹화: 코드의 가독성과 유지보수성을 높임.
행위 추가 가능: 인터페이스 구현 및 메서드 정의 가능.
스위치 문 대체:
enum
메서드를 활용하여 분기 처리 가능.만약 변경이 잦은 개념은 Enum보다 DB로 관리하는 것이 나
다형성 활용
인터페이스와 추상 클래스를 활용하여 유연한 설계.
if-else
대신 enum이나, 전략 패턴(Strategy Pattern)등 을 사용하여 유지보수성 향상.의존성 주입(DI)을 활용하여 객체 간 결합도를 낮추고 테스트 용이성 확보.
숨겨져 있는 도메인 개념 도출하기
도메인 지식은 만드는 것이 아니라 발견하는 것이므로, 현재의 최선을 다하자.
미션
Day 2 - 추상과 구체
미션 답변
추상적 개념: "사용자가 선착순 쿠폰을 발급받는다."
구체적 과정:
사용자가 특정 쿠폰 발급 요청을 보냄.
Redis의 Set 자료구조에서 남은 쿠폰 개수를 확인.
쿠폰 수량이 남아 있다면 Redis에서 하나를 제거하고 발급 처리.
발급된 쿠폰 정보는 DB에 저장되고, 사용자는 쿠폰을 발급받음.
트랜잭션을 통해 동시성 제어를 수행하여 중복 발급 방지.
요약
서비스 제공 입장에서는 추상적 개념으로 인식.
실제 개발 시에는 구체적인 과정을 설계해야 함.
개발 과정에서도 다양한 추상적 사고가 필요.
Day 4
1. 읽기 좋은 코드로 리팩토링
public class Day4Mission {
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;
}
}
기존 코드 문제점
중첩된
if-else
구조로 인해 가독성이 떨어짐.불필요한 조건 체크가 반복됨.
논리 흐름을 이해하기 어렵고 유지보수가 어려움.
리팩토링 방식
Early Return
을 적용하여 중첩 구조 제거.의미 있는 메서드 (
isOrderEmpty
,hasNoCustomerInfo
,isTotalPriceInvalid
)를 생성하여 가독성 향상.메서드를 활용하여 논리 흐름을 단순화.
리팩토링 후 코드
private boolean validateOrder(Order order) {
if (isOrderEmpty(order)) {
log.info("주문 항목이 없습니다.");
return false;
}
if (hasNoCustomerInfo(order)) {
log.info("사용자 정보가 없습니다.");
return false;
}
if (isTotalPriceInvalid(order)) {
log.info("올바르지 않은 총 가격입니다.");
return false;
}
return true;
}
private boolean hasNoCustomerInfo(Order order) {
return !order.hasCustomerInfo();
}
private boolean isOrderEmpty(Order order) {
return order.getItems() == null || order.getItems().isEmpty();
}
private boolean isTotalPriceInvalid(Order order) {
return order.getTotalPrice() <= 0;
}
1. SOLID에 대하여 자기만의 언어로 정리
SOLID원칙의 정의와 예시, 그리고 스프링에서 적용되는지에 대해 작성해보았습니다.
댓글을 작성해보세요.