반응형

가독성 높이는 습관 - 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의 정의

- 사이드 이펙트를 줄이는 방법들

  • 나누어서 생각하기 (로직 분해하기)
  • 순수 함수로 만들기
  • 사이드 이펙트 모아두기
  • 전역변수 제거하기
  • 불필요한 변수 제거하기
  • 불변객체 사용하기

 

 


반응형

+ Recent posts