[워밍업 클럽 스터디 2기 :: BE 클린코드 & 테스트] 1주차 발자국
뇌 메모리 적게 쓰기
프로그램 = 데이터 + 코드
정리하는 뇌
정리 시스템에서 중요한 과제는 최소의 인지적 노력으로 최대의 정보를 제공하는 것이다.
도둑맞은 집중력
뇌는 한 번에 한 가지 일 밖에 하지 못한다.
인지적 경제성을 추가하자
Early return
Early return 으로 else의 사용을 지양하자
Early return 을 통해서 끊어서 볼 수 있도록 하여 이후 불필요한 로직을 수행하지 않도록 처리
사고의 depth 줄이기
중첩 분기분, 중첩 반복문
‘무조건 1 depth로 만들어라’ 가 아니다. → 보이는 depth를 줄이는 데에 급급한 것이 아니라, 추상화를 통한 사고 과정의 depth를 줄이는 것이 중요 → 2중 중첩 구조로 표현하는 것이 사고하는 데에 더 도움이 된다고 판단한다면, 메서드 분리보다 그대로 놔두는 것이 더 나은 선택일 수 있다. 때로는 메서드를 분리하는 것이 더 혼선을 줄 수도 있다.
무조건 strame 이 인지하기 좋은 것은 아니다. for i, foreach 가 더 좋은 경우도 있으니 상황에 맞게 사용하자
사용할 변수는 가깝게 선언하기
Before
int i = 10; // 코드 20줄..... int j = i + 10;
After
// 코드 20줄..... int i = 10; int j = i + 10;
파라미터를 줄일 수 있다면 줄이는게 좋다.
공백 라인을 대하는 자세
공백 라인도 의미를 가진다. → 복잡한 로직의 의미 단위를 나우어 보여줌으로써 읽는 사람에게 추가적인 정보를 전달할 수 있다.
부정어를 대하는 자세
부정어구를 쓰지 않아도 되는 상황인지 체크
부정의 의미를 담은 다른 단어가 존재하는지 고민 or 부정어구로 메서드명 구성
※ 부정 연산자(!)의 가동성 ↓
if(!isLeftDirection()){
}
// 반대대는 상황이 있다면
if(!isRightirection()){
}
// 메서드명에 Not 을 추가하는 방법
if(isNotLeftDirection()){
}
해피 케이스와 예외 처리
사람은 해피 케이스에 몰두하는 경향이 있다.
예외가 발생할 가능성 낮추기
어떤 값의 검증이 필요한 부분은 주로 외부 세계와의 접점
의도한 예외와 예상하지 못한 예외를 구분하기
Null을 대하는 자세
항상 NullPointException을 방지하는 방향으로 경각심 가지기
메서드 설계 시 return null을 자제한다. → 만약 어렵다면, Optional 사용을 고민해 본다.
Optional에 관해
Optional은 비싼 객체다. 꼭 필요한 상황에서 반환 타입에 사용한다.
Optional을 파라미터로 받지 않도록 한다. 분기 케이스가 3개나 된다.
(Optional이 가진 데이터가 null인지 아닌지 + Optional 그 자체가 null인지)Optional을 반환받았다면 최대한 빠르게 해소한다.
잘못 사용된 Optional은 안티패턴이다.
(JPA에서의 Optional 은 잘 사용된 패턴)
Optional을 해소하는 방법
분기문을 만드는 isPresent()-get() 대신 풍부한 API 사용
orElse(), orElseGet(), orElseThrow()의 차이 숙지
orElseThrow() : 그냥 쓰면 됨
orElse() : 항상 실행, 괄호안의 값이 확정된 값일때 활용
orElseGet() : null인 경우 실행, 값을 제공하는 동작(Supplier) 저의
public T orElse(T other){
return value != null ? value : other;
}
Intger result = somethingOptional.orElse(methodHeavy()); // 호출할 필요가 없는 경우에도 항상 실행
Intger result3 = somethingOptional.orElse(0); // 정해진 값일 때 사
public T orElseGet(Supplier<? extends T> supplier){
return value != null ? value : supplier.get();
}
integer result = somethingOptional.orElseGet(() -> methodHeavy()); // null인 경우에만 실행
예외를 던지고 그 안에 메세지를 넣어서 무슨 오류인지 인지할 수 있게 도와주자
비교를 할 때 상수와 변수를 비교해야 한다면 상수를 앞에 넣어서 NullPointException 이 안 일어나게 할 수도 있다
추상의 관점으로 바라보는 객체 지향
객체 : 추상화된 데이터 + 코드
관심사의 분리
유지 보수가 원활해진다.
높은 응집도, 낮은 결합도
객체 설계하기
객체 : 데이터 + 코드
객체(Object)로 추상화 하기
비공개 필드(데이터), 비공개 로직(코드, 기능 구현부)
공개 메서드 선언부를 통해 외부 세계와 소통 → 각 메서드의 기능은 객체의 책임을 드러내는 창구
객체의 책임이 나뉨에 따라 객체 간의 협력이 발생
객체가 제공하는 것
절차 지향에서 잘 보이지 않았던 개념을 가시화
관심사가 한 군데로 모이기 때문에, 유지보수성 ↑ →
ex) 객체 내부에서 객체가 가진 데이터의 유효성 검증 책임을 가질 수 있다.여러 객체를 사용하는 입장에서는 구체적인 구현에 신경 쓰지 않고 보다 높은 추상화레벨에서 도메인 로직을 다룰 수 있다.
새로운 객체를 만들때 주의할 점
1개의 관심사로 명확하게 책임이 정의되었는지 확인하기 → 메서드를 추상화 할 때와 비슷하다. → 객체를 만듦으로써 외부 세계와 어떤 소통을 하려고 하는지 생각해보자.
생성자, 정적 팩토리 메서드에서 유효성 검증이 가능하다. → 도메인에 특화된 검증 로직이 들어갈 수 있다.
Setter 사용 자제
→ 데이터는 불변이 최고다. 변하는 데이터라도 객체가 핸들링 할 수 있어야 한다.
→ 객체 내부에서 외부 세계의 개입 없이 자체적인 변경/가공으로 처리할 수 있는지를 확인
→ 만약 외부에서 가지고 있는 데이터로 변경 요청을 해야하는 경우, ‘set~’이라는 단순한 이름 보다는 ‘update~’ 같이 의도를 드러내는 네이밍을 고려하자.
Getter도 처음에는 사용 자제. 반드시 필요한 경우에 추가하기
→ 외부에서 객체 내 데이터가 필요하다고 getter를 남발하는 것은 무례한 행동이다.
객체에 메시지를 보내라
단일 책임의 원칙 :
SRP : Single Responsibility Principle
하나의 객체는 각자 맞는 책임을 가지게
OCP : Open-Closed Principle
추상화와 다형성을 이용!
객체 자체는 무엇이 들어와도 처리가능 하게
인터페이스를 통해 다양한 값이 들어 올 수 있도록 처리
LSP : Liskov Subsitution Principle
부모에서의 기능을 자식에서도 동일하게
ISP : Interface Segregation Principle
클라이언트는 자신이 사용하지 않는 인터페이스에 의존하면 안된다. → 인터페이스를 잘게 쪼개라
ISP를 위반하면, 불필요한 의존성으로 인해 결합도가 높아지고, 특정 기능의 변경이 여러 클래스에 영향을 미칠 수 있다.
※ 기능단위로 인터페이스를 작게 사용해라!
DIP : Dependency Inversion Principle
의존성 : 하나의 모듈이 다른 하나의 모듈을 생성하거나 사용하는 모든 것, 참조하는 것
의존성 순방향 : 고수준 모듈이 저수준 모듈을 참조
댓글을 작성해보세요.