IT

[Javascript / 리팩토링] 불필요한 매개변수 프로퍼티 재할당 (feat. ESLint : no-param-reassign 에러 발생) 본문

개발

[Javascript / 리팩토링] 불필요한 매개변수 프로퍼티 재할당 (feat. ESLint : no-param-reassign 에러 발생)

abcee 2022. 11. 17. 01:07

Smell Code

기전달된 매개변수에 동적으로 프로퍼티를 추가하고 해당 변수를 다른 함수의 매개변수로 다시 전달하는 경우

  • AirBnb Eslint를 사용하는 경우 no-param-reassign 에러가 발생한다.
  • 객체 지향 프로그래밍 관점에서의 문제점
    • 객체 지향 프로그래밍에서는 함수의 정의를 명확하고 하나의 책임만 담당하도록 설계하여 코드 변경의 목적을 제한하는 것을 권장한다.
    • 아래 예시처럼 함수는 매개변수를 포함해서 정의된다.
    • 함수의 정의 예시
      • ObjectA.save(arg1)와 같이 객체의 메소드로서의 함수 의미
        • 메소드 의미 : ObjectA 가 arg1를 save 한다.
        • 메소드 호출 시 메시지 : ObjectA 가 arg1를 save 하도록 요청한다.
      • parse(arg1)와 같이 1급객체로서의 함수 의미
        • 함수 의미 : arg1를 parse 한다.
        • 메소드 호출 시 메시지 : arg1를 parse 하도록 요청한다.
    • 그런데 매개변수에 불필요한 데이터가 포함될 경우 함수의 정의가 모호해지거나 책임이 늘어나 유지보수에 어려움이 생긴다.
  • 함수형 프로그래밍 관점에서의 문제점
    • 함수형 프로그래밍에서도 객체 지향 프로그래밍에서와 마찬가지로 매개변수에 불필요한 데이터가 포함될 경우 함수 혹은 메소드의 정의가 모호해지거나 책임이 늘어나 유지보수에 어려움이 생긴다.
    • 또한 함수형 프로그래밍에서는 같은 매개변수에 대해 같은 동작과 결과를 보장하도록 순수함수를 설계하여 예측할 수 있고 테스트하기 쉬운 코드 작성을 권장한다.
    • 그런데 함수 호출자는 불필요한 데이터를 모두 포함하여 함수의 입력으로 상정하므로 실제 필요한 데이터가 같아 실행 결과가 일치하더라도 동일 결과를 예측하기 어려워져 순수함수의 목적을 상실하게 된다.
  • Common Code
class Request {
  constructor(header, queryString, body) {
    this.header = header;
    this.queryString = queryString;
    this.body = body;
  }
}
  • Smell Source Code
async function apiRequestHandler(request) {
  // this syntax occur "ESLint: Assignment to property of function parameter 'apiRequestHandler'.(no-param-reassign)"
  // ESLint 에러가 발생한다.
  request.userId = "user1";
  await saveData(request);
}

/**
 * 'saveData(request)' 함수는 내부에서 사용하지 않는 'header', 'queryString' 데이터가 포함되어 있다.
 *  1. 따라서 함수 호출 시 외부로부터 어떤 데이터가 전달돼야 하는지 정확히 알 수 없어 가독성이 떨어진다.
 *  2. 'request' 매개변수에 'userId' 와 'body' 데이터가 모두 종속되어 변경에 유연하지 않다.
 *  3. 함수형 프로그래밍 관점에서 함수 호출자는 'userId'와 'body' 데이터가 같아 실제 결과가 동일하더라도 'header', 'queryString' 데이터가 다르다면 동일 결과를 예측하기 어렵다.
 * 
 * @param request { 'userId', 'body', 'header', 'queryString' }
 * @returns {Promise<void>}
 */
async function saveData(request) {
  DTO.saveData(request.userId, JSON.parse(request.body));
} 

Refactoring

  • AirBnb Eslint의 no-param-reassign 에러를 해결하기 위해선 현재 소프트웨어 설계구조와 로직에 적절한 방안을 적용해야 한다.
    • 함수를 정의할 때 외부로부터 어떤 데이터가 매개변수로 전달돼야 하는지 명확히 한다.
    • 함수의 매개변수 개수가 너무 많아 줄이고 싶다면 필요한 데이터만으로 구성된 객체를 만들어서 해당 객체를 매개변수로 받도록 구성한다.
async function apiRequestHandler(request) {
  // 함수 호출에 필요한 데이터만 매개변수로 전달한다.
  await saveData("user1", JSON.parse(request.body));

  // 함수 호출에 필요한 데이터가 호출 후 재사용되는 데이터라면 변수 선언 후 사용한다. 
  const userId = "user1";
  const data = JSON.parse(request.body);
  await saveData(userId, data);
  console.log(userId);
  console.log(data);
}

// 'saveData' 함수 호출 시 외부로부터 'userId', 'data' 가 매개변수로 전달돼야 함을 명확히 한다.
async function saveData(userId, data) {
  DTO.saveData(userId, data);
} 
class Entity {
  constructor(userId, data) {
    this.userId = userId;
    this.data = data;
  }
}
async function apiRequestHandler(request) {
  // 매개변수 개수를 줄이기 위해 Entity 객체를 만들어 매개변수로 전달한다.
  const entity = new Entity("user1", JSON.parse(request.body))
  await saveData(entity);
  console.log(entity);
}

// 'saveData' 함수 호출 시 외부로부터 'userId', 'data' 데이터가 묶인 Entity 가 매개변수로 전달된다.
async function saveData(entity) {
  DTO.saveData(entity.userId, entity.data);
} 

객체 프로퍼티 재할당이 필요한 아키텍처에서의 리팩토링 방법은 [Javascript / 리팩토링] 객체 프로퍼티 재할당 (feat. ESLint : no-param-reassign 에러 발생) 포스팅에서 설명한다.


Comments