인프런 워밍업 클럽 스터디 2기 - 백엔드 클린코드, 테스트코드 1주차 발자국
Section2) 추상
Clean Code를 추구해야 하는 이유
나를 포함해 다른 개발자가 코드를 읽고 이해하는데 드는 시간이 절약된다.
프로그램의 정의
프로그램 = (데이터가 담긴) 변수 + (변수를 사용하는) 메서드의 집합
추상
중요한 정보는 가려내어 남기고, 덜 중요한 정보는 생략하여 버리는 것
적절한 추상화 : 도메인 안에서, 정말 중요한 핵심 개념만 남겨서 표현하는 것이다.
추상화의 가장 대표적인 행위는 이름을 짓는 것이다.
(변수) 이름 짓기 Tip
1) 단수와 복수 구분
- 끝에 '(e)s'를 붙여 구분
2) 이름 줄이지 않기
- 일반적으로 무엇이든 이름을 줄여서 사용하는 것은 가독성을 제물로 바쳐 효율성을 얻는 것인데,
유지보수 관점에서 득보다 실이 크다.
- 관용어 처럼 자주 사용하는 것은 줄여도 괜찮다.
=> ex, column -> col, latitude -> lat
=> count -> cnt (추천X)
3) 은어/방언 사용X
- 현재의 팀만 아는 용어 사용 금지
4) 좋은 코드를 보고 습득하기
메서드와 추상화
한 메서드는 반드시 한가지 일만 해야 한다.
2가지 일을 하게된다면 추상화된 내용(==메서드 선언부)을 보고 구체적인 내용(메서드 구현부)의 유추가 어렵다.
추상화 레벨
외부 세계(==추상화 레벨이 높은 세게)와 내부세계(==추상화 레벨이 낮은 구체)를 나누었을때 추상화 레벨이 달라지는데, 하나의 세계 안에서 각 로직의 추상화 레벨은 동등해야 한다.
매직 넘버, 매직 스트링
매직 넘버(스트링) : 의미를 갖고 있으나, 상수로 추출되지 않은 숫자, 문자열 등을 말한다.
Mission 1) 생각나는 추상과 구체의 예시
Execute Login
1) 클라이언트가 서버에 암호화된 ID와 Password를 HTTP Body에 담아 Request를 보낸다.
2) 서버는 ID와 Password를 복호화하고 각 필드에 대한 유효성 검사를 한다.
3) 유효하다면, 복호화된 ID와 Password를 DB에 보내 가입된 유저인지 확인한다.
4) 가입된 유저라면, Session Cookie 혹은 JWT 등에 사용자 정보를 담아 클라이언트에 Response로 보낸다.
Section3) 논리, 사고의 흐름
뇌 메모리 적게 쓰기
최소의 인지적 노력으로 (뇌 메모리를 줄여) 최대의 정보를 제공해야 한다,
Early return
else (if) 대신 return을 사용하는 것을 권장한다,.
사고의 Depth 줄이기
1) 중첩 반복문을 메서드 혹은 Stream을 통해 개선하면 좋다.
2) 사용할 변수는 가깝게 선언하기
공백라인도 의미를 가진다.
부정어
if문에서 부정어(!) 사용시 메서드화하여 분리하기
해피케이스와 예외처리
1) 예외가 발생할 가능성을 낮추는게 좋다. (ex, 사용자 입력, 객체 생성자, 외부 서버의 요청 등)
2) 의도한 예외(ex, Custom Exception)와 예상하지 못한 예외 구분하기
3) NullPointException은 항상 발생하지 않게 해야 한다.
- 메서드 설계시 return null을 자제하고, Optional 사용을 고려하기
- Optional의 orElse(), orElsGet(), orElseThrow() 메서드의 차이 이해 필요.
Section4) 객체지향 패러다임
객체 설계
1) 새로운 객체 생성시 주의사항
- 1개의 관심사로 명확하게 책임이 정의되었는지 확인 필요
- 생성자, 정적 팩토리 메서드에서 유효성 검증이 가능함을 인지
- setter 사용 자제
- getter도 사용 자제하고, 반드시 필요한 경우에만 추가
- 필드의 수는 적을수록 좋다
2) 도메인 지식은 만드는 것이 아니라 발견하는 것
Mission 2-1) 읽기 좋은 코드로 리팩토링
#중요하게 생각한 점
1) order 객체 Null 체크 필요
2) if-else문들 if문으로 개선
3) 부정어구(!) 없애기
4) getter 제거 → 객체에 의미가 담긴 메서드를 별도 생성
5) 공백라인 사용
public boolean validateOrder(Order order) {
//1) Null Check
if (order == null) {
log.info("주문을 확인할 수 없습니다.");
return false;
}
//2) remove (getter)
if (order.hasNoItems()) {
log.info("주문 항목이 없습니다.");
return false;
}
//3) remove (! + if-else + getter)
if (order.isInvalidTotalPrice()) {
log.info("올바르지 않은 총 가격입니다.");
return false;
}
//4) remove (! + if-else)
if (order.hasNoCustomerInfo()) {
log.info("사용자 정보가 없습니다.");
return false
}
return true;
}
public class Order {
private List<Item> items;
private double totalPrice;
private CustomerInfo customerInfo;
public boolean hasNoItems() {
return items == null || items.isEmpty();
}
public boolean isInvalidTotalPrice() {
return totalPrice <= 0;
}
public boolean hasNoCustomerInfo() {
return customerInfo == null;
}
}
Mission 2-2) 자기만의 언어로 정리한 SOLID
1) SRP
- Single Responsibility Principle (단일 책임의 원칙)
- “하나의 클래스에 변경이 발생한다면 그 이유(==책임)는 반드시 하나여야 한다”는 원칙
- ex) 프로그램 실행 부와 실제 실행 로직은 나누어져 있어야 한다.
- 높은 응집도, 낮은 결합도와 관련 있음.
2) OCP
- Open-Closed Principle (개방-폐쇄 원칙)
- “기존 코드의 변경 없이, 기능을 확장할 수 있어야 한다”는 원칙
3) LSP
- Liskov Substitution Principle (리스코프 치환 원칙)
- 상속 구조에서, “부모 클래스의 인스턴스는 자식 클래스의 인스턴스로 치환될수 있어야 한다”는 원칙
4) ISP
- Interrface Segregation Principle (인터페이스 분리 원칙)
- ”하나의 구체 클래스는 자신이 사용하지 않는 인터페이스에 의존해서는 안된다”는 원칙
(이때는, 인터페이스를 2개로 분리해야 한다)
5) DIP
- Dependency Inversion Principle (의존성 역전 법칙)
- “레벨이 높은 모듈(ex, Lv2 카페)은 구체 모듈(ex, Lv0 커피)에 바로 의존해서는 안되고 추상화(ex, Lv1 음료)에 의존해야 한다”는 원칙
미션을 통해 SOLID 원칙을 다시 한번 상기시킬수 있었는데, 실제 업무에 활용하기 위해서는 스스로 좀더 깊은 학습이 필요할것 같다. 그리고 클린코드의 방법론을 미션을 통해 적용해 보면서 코드가 좀 더 잘 읽히고 이해하기 쉬워지는 것을 직접 느낄수 있었다.
Section5) 객체지향 적용하기
상속과 조합
상속은 시멘트처럼 굳어지는 구조이기 때문에 수정이 어려우므로, 상속보다 조합을 사용하는게 좋다.
Value Object
도메인의 어떤 개념을 추상화하여 표현한 값 객체로, 불변성, 동등성, 유효성 검증 등을 보장해야 한다.
VO (Value Object)는 내부의 모든 값이 다 같아야 동등한 객체로 취급한다. 이에 반해 Entity는 식별자만 같으면 동등한 객체로 취급한다.
일급 컬렉션
컬렉션(List, Set, Map 등)을 포장하면서, 컬렉션만을 유일하게 필드로 가지는 객체로, 단 하나의 컬렉션 필드만을 가진다.
만약, getter로 컬렉션을 반환할 일이 생긴다면 외부 조작을 피하기 위해 꼭 새로운 컬렉션(List<Object타입>)으로 만들어 반환하는게 좋다.
스터디를 진행하면서, 처음에는 완벽하게 다 이해하려고 생각해 학습에 시간이 오래걸렸는데, 이제는 내가 당장 적용해 볼수 있는 부분들을 Target으로 하여 배워나가야 겠다는 깨달음을 얻었다.
출처
댓글을 작성해보세요.