가독성 높이는 습관 - Side effect 줄이기
Side effect 란?
- 부작용 - 함수 절차의 실행에 의해서 발생되는 외부적인 작용이고,
- 함수의 결과 값을 발생시키는 작용과는 다른 것을 말한다.
JSF AV C++ 코딩 표준
<Joint Strike Fighter Air Vehicle>
AV Rule 187 (MISRA Rule 53, Revised)
All non-null statements shall potencially have a side-effect.
https://www.stroustrup.com/JSF-AV-rules.pdf
Accessing an object designated by a volatile lvaule, modifying an object, calling a library I/O function, or calling a function that does any of thoes operations are all side effects, which ard changes in th state of the execution enviroment.
Example: Potential side effect
if (flag) // Has side effect only if flag is true.
{
foo();
}
Example: The following expression has no side effects
3 + 4; // Bad: statement has zero side effects
Example: The following expressions has side effects
x = 3 + 4; // Statement has one side effect: x is set to 7.
y = x++; // Statement two side effects: y is set to x and x is incremented.
- 따라서, 값을 할당하거나 어떤 함수를 호출하거나 어떤 할당된 값의 상태를 변경시키는 경우 'Side effect'라고 표현 가능
foo(파라미터) {
변수 = 함수호출();
//... 어떠한 연산
변수 = 함수호출(변수, 변수, ...);
//... 어떠한 연산
return 결과;
}
- Side effect의 숫자가 적다면 더 빠르게 문제를 찾을 수 있고 버그도 줄어듬
- Side effect를 줄인다 = 한번에 다뤄야할 문제의 크기를 줄인다
한번에 다뤄야할 문제의 크기를 줄이기
나누어서 생각하기 (로직 분해하기)
public void order(User user, long bookId) {
Book book = bookRepository.findByBookId(bookId)
.orElseThrow(() -> new RuntimeException("도서가 존재하지 않습니다."));
BookStock bookStock = bookStockRepository.findByBookId(bookId)
.orElseThrow(() -> new RuntimeException("재고 정보가 존재하지 않습니다."));
UserInfo userInfo = userInfoAdaptor.findUserInfo(user)
.orElseThrow(() -> new RuntimeException("재고 정보가 존재하지 않습니다."));
if (!book.onSale(LocalDateTime.now()))
throw new RuntimeException("판매 중이지 않은 도서입니다.");
if (!userInfo.isDormant())
throw new RuntimeException("휴면 고객은 구매할 수 없습니다.");
if (!book.enoughAge(userInfo.getAge()))
throw new RuntimeException("도서를 구매할 수 없는 나이입니다.");
if (!bookStock.enoughStock())
throw new RuntimeException("도서 재고가 부족합니다.");
// 기타 조건체크
BookOrder bookOrder = BookOrder
.builder()
.bookId(book.getId())
.userId(user.getId())
.build();
bookStock.decreaseStock();
bookStockRepository.save(bookStock);
bookOrderRepository.save(bookOrder);
}
public void order(User user, long bookId) {
Book book = findBook(bookId);
UserInfo userInfo = findUserInfo(user);
BookStock bookStock = findBookStock(bookId);
chackOnSale(book);
checkDormant(userInfo);
checkEnoughAge(book, userInfo);
checkEnoughStock(bookStock);
// 기타 조건체크
bookStock.decreaseStock();
bookStockRepository.save(bookStock);
bookOrderRepository.save(createBookOrder(user, book));
}
(가능하다면) 순수함수로 만들기
// 순수 함수
int add(int x, int y) {
return x + y;
}
- 동일한 입력에 대해서 항상 같은 값을 반환
- 같을 값을 반환한다는 것은 입력값에 대해서 결과값을 예측하기 쉬움
- 함수의 출력값이 입력값에 의존적이므로 테스트가 쉬워짐
// 일반 함수
int add(int x, int y) {
this.total = this.total + x + y;
return this.total;
}
// 일반 함수
int add(int x, int y) {
incAddFunCount();
return x + y;
}
순수함수로 만들 수 없다면, 사이드 이펙트 모아두기
유저가입자_기본정보_조회() {
Response response = new Response();
유저 = 유저조회();
response.set(유저);
유저커스텀정보 = 유저_커스텀_정보조회();
response.set(커스텀정보);
set(response, 기타_추가_정보1);
set(response, 기타_추가_정보2);
...
return reponse;
}
유저가입자_기본정보_조회() {
Response response = new Response();
유저 = 유저조회();
유저커스텀정보 = 유저_커스텀_정보조회();
기타_추가_정보1 = 기타_추가_정보1_조회();
기타_추가_정보2 = 기타_추가_정보2_조회();
return Response.builder()
.user(user)
.커스텀정보(커스텀정보)
.기타_추가_정보1(기타_추가_정보1)
.기타_추가_정보2(기타_추가_정보2)
...
.build();
}
전역 변수 제거하기 - 전역변수의 문제점
- 1. 코드 어디서든 참조하므로, 참조가능한 모든지점에서 사이드 이펙트 발생 가능
- 2. 변수의 LifeCycle이 길어짐 (언제, 어디서, 어떻게 사용하는지 추적하기 어려움)
- 3. 전역변수에 엮이는 모든 곳에, 보이지 않는 커플링이 발생 (테스트 어려움)
- 4. 멀티쓰레드 상황에서 자원 획득을 위한 경합 발생
불필요한 변수 제거하기 (가능하다면 변수 없애기)
int add(int x, int y) {
int result = x + y;
return result;
}
- 굳이 임시 지역 변수(result)를 넣을 이유가 있을까?
불변(immutable) 객체 사용하기
@Getter
@AllArgsConstructor
public class ImmutableUserDto {
private final Long id;
private final Long age;
private final String name;
}
public static void main(String[] args) {
// .. 무언가 조회
ImmutableUserDto dto = new ImmutableUserDto();
// .. 복잡한 로직수행
// 아무리 복잡한 로직을 수행해도 dto 값은 바뀌지 않음
}
정리
- Side effect의 정의
- 사이드 이펙트를 줄이는 방법들
- 나누어서 생각하기 (로직 분해하기)
- 순수 함수로 만들기
- 사이드 이펙트 모아두기
- 전역변수 제거하기
- 불필요한 변수 제거하기
- 불변객체 사용하기
'cs > java-spring-boot' 카테고리의 다른 글
[Zero-base] 9-14. 심미적 방법들 (0) | 2022.03.17 |
---|---|
[Zero-base] 9-13. 심플하게 구성하기 (0) | 2022.03.17 |
[Zero-base] 9-11. Optional 제대로 사용하기 (0) | 2022.03.17 |
[Zero-base] 9-10. Optional 살펴보기 + Java 8 : functional interface (0) | 2022.03.16 |
[Zero-base] 9-9. null 핸들링 (0) | 2022.03.16 |