Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
Tags
- microservices
- OOP
- AWS
- 서버 베이스 컴퓨팅
- eks
- kubernetes
- jenkins
- openstack
- 젠킨스
- 오픈스택
- 가상 데스크탑 환경
- no-param-reassign
- IaaS
- sagemaker
- centos7
- 쿠버네티스
- 구축
- eslint
- 객체지향
- rocky
- 로키
- 설치
- 마이크로서비스
- 프로젝트
- 머신러닝
- fargate
- app&desk
- server base computing
- serverless
- xenserver app&desk
Archives
- Today
- Total
IT
[Javascript / 리팩토링] 객체 프로퍼티 재할당 (feat. ESLint : no-param-reassign 에러 발생) 본문
개발
[Javascript / 리팩토링] 객체 프로퍼티 재할당 (feat. ESLint : no-param-reassign 에러 발생)
abcee 2022. 11. 16. 14:04Smell Code
함수의 매개변수로 보내진 객체의 프로퍼티를 해당 함수에서 재할당하는 경우
- AirBnb Eslint를 사용하는 경우
no-param-reassign
에러가 발생한다. - 객체 지향 프로그래밍 관점에서의 문제점
- Javascript의 경우 Class 선언없이 Object가 생성되는 레거시 코드가 많기 때문에 객체의 프로퍼티에 setter/getter에 준하는 메소드 없이 직접적으로 할당하는 경우가 많은데 이 경우 객체 지향 프로그래밍 측면에서 문제가 발생한다.
- 객체지향 프로그래밍에서는 객체의 정보를 캡슐화하여 객체의 정보 변경을 내부에서 하도록 책임을 부여하고 외부에서 해당 객체의 정보를 변경하는 것을 제한하도록 설계하여 중복을 방지하고 Side Effect 를 최소화하는 코드 작성을 권장한다.
- 그런데 함수 내부에서 외부 데이터를 변경하게되면(캡슐화 없이 dot notation 을 통한 직접적인 데이터 변경) 해당 객체의 데이터 변경 책임이 외부 객체로 분산되어 코드 중복과 예상치 못한 Side Effect 발생 등 유지보수에 어려움이 생긴다.
- 함수형 프로그래밍 관점에서의 문제점
- Javascript의 경우
Dot notation
과Bracket notation
을 사용하여 함수의 매개변수로 보내진 객체의 데이터를 동적으로 생성하는 레거시 코드가 많은데 이 경우 함수형 프로그래밍 측면에서의 문제가 발생한다. - 함수형 프로그래밍에서는 함수가 Side Effect 없이 동일한 매개변수에 대해 동일한 동작과 결과을 보장하도록
순수함수
를 설계하여 예측 가능하고 테스트하기 쉬운 코드 작성을 권장한다. - 그런데 함수 내부에서 외부 데이터를 변경하게되면(dot notation 과 setter 등 모든 프로퍼티 재할당) 변경된 데이터가 함수 동작에 영향을 끼칠 가능성이 존재하므로 Side Effect 발생 가능성이 생기고 예측이 어려워져
순수함수
를 만족하지 않게 된다.
- Javascript의 경우
- Data Code
// ./main.js file
const jsonString = `
{
"school" : {
"nm" : "b",
"students": [
{
"nm" : "s1",
"age" : 10,
"checkCards" : [
{ "balance": 10, "validDate": "2022-01-01 GMT" },
{ "balance": 20, "validDate": "2022-02-01 GMT" },
{ "balance": 30, "validDate": "2022-03-01 GMT" }
],
"house" : { "nm" : "my_home" }
}
]
}
}
`;
- Smell Source Code
// ./main.js file
const { school: schoolObj } = JSON.parse(jsonString);
function useCard(student) {
student.checkCards.forEach((checkCard) => {
// this syntax occur "ESLint: Assignment to property of function parameter 'checkCard'.(no-param-reassign)"
// From an object-oriented programming perspective, there is a problem of lack of encapsulation.
// ESLint 에러가 발생한다.
// 객체 지향 관점에서 캡슐화가 되지 않은 문제가 있다.
checkCard.balance -= 5;
});
}
function reissuedCard(cards) {
cards.forEach((checkCard) => {
// this syntax occur "ESLint: Assignment to property of function parameter 'checkCard'.(no-param-reassign)"
// ESLint 에러가 발생한다.
checkCard.validDate = new Date(`${checkCard.validDate.getFullYear() + 1}-${checkCard.validDate.getMonth() + 1}-${checkCard.validDate.getDate()} GMT`);
// You can solve the ESLint error by using the setFullYear function as below to increment the year,
// but it still has side effects, which is problematic from a functional programming point of view.
// Date 객체의 setFullYear() 함수를 사용해 ESLint 에러를 해결할 수는 있지만,
// 여전히 함수형 관점에서 reissuedCard() 함수 내 외부 cards의 데이터를 변조하는 Side Effect가 존재하는 문제가 있다.
checkCard.validDate.setFullYear(checkCard.validDate.getFullYear() + 1);
});
}
useCard(schoolObj.students[0]);
reissuedCard(schoolObj.students[0].checkCards);
console.log(JSON.stringify(schoolObj, null, 2));
Refactoring
- AirBnb Eslint의
no-param-reassign
에러를 해결하기 위해선 현재 소프트웨어 설계구조와 로직에 적절한 방안을 적용해야 한다.- 예제의
useCard
함수처럼 기능의 의미상 기존 객체 인스턴스의 데이터만 변경하는 게 맞는다면 객체 지향 프로그래밍 관점에서 리팩토링을 해야한다. - 예제의
reissuedCard
함수처럼 기능의 의미상 새로운 객체 인스턴스를 생성하는 게 맞는다면 함수형 프로그래밍 관점에서 리팩토링을 해야한다.
- 예제의
- 객체 지향 프로그래밍 관점에서의 문제점 해결
- 해당 프로퍼티 할당의 책임을 프로퍼티를 소유하고 있는 객체에 부여한다.
- 함수 외부에서 setter 메소드가 존재하는 Class를 선언하고 해당 Class 의 prototype으로 object를 재할당 한다.
- 그리고 함수 내부에서는 setter 메소드를 통해서 object의 프로퍼티를 변경한다.
useCard
함수에 존재하던 학생이 가지고 있는 체크카드 잔액 변경 책임을CheckCard
객체에 부여한다.
- 해당 프로퍼티 할당의 책임을 프로퍼티를 소유하고 있는 객체에 부여한다.
- 함수형 프로그래밍 관점에서의 문제점 해결
- 순수함수를 만들기 위해 함수 내부에서 객체를 shallow copy 후 데이터를 변경하고 새로 생성된 객체를 return 한다.
reissuedCard
함수에서cards
의 유효일을 변경하던 구조를reissuedCard
함수에서 변경된 유효일로 새로운 카드를 만들어서 return 하고 객체가 생성된 곳에서.checkCards
프로퍼티를 재할당 하도록 구조를 변경한다.
// ./main.js file
const Student = require('./vo/Student');
const CheckCard = require('./vo/CheckCard');
const House = require('./vo/House');
const { ValueObject, JsonParseAndAssignPrototype, VoReassignPrototype } = require('./util');
const VO = {
Students: new ValueObject(
'students',
true,
Student,
),
CheckCards: new ValueObject(
'checkCards',
true,
CheckCard,
),
House: new ValueObject(
'house',
false,
House,
),
};
const voList = [VO.Students, VO.CheckCards, VO.House];
// jsonString 을 Object로 만들어서 parameter passing 할때
const { school: schoolObj } = JsonParseAndAssignPrototype(jsonString, voList);
// 기생성된 object를 parameter passing 할때
// const schoolObj = VoReassignPrototype(JSON.parse(jsonString).school, voList);
// 객체지향 프로그래밍 관점에서의 문제점 해결
function useCard(student) {
student.checkCards.forEach((checkCard) => {
// `CheckCard` 객체에 부여된 체크카드 잔액 변경 책임이 동작하도록 변경됨
checkCard.updateBalance(-5);
});
}
// 함수지향 프로그래밍 관점에서의 문제점 해결
function reissuedCard(cards) {
// 변경된 유효일로 새로운 카드를 만들어서 return 하도록 변경됨
return cards.map((checkCard) => ({
...checkCard,
validDate: new Date(`${checkCard.validDate.getFullYear() + 2}-${checkCard.validDate.getMonth() + 1}-${checkCard.validDate.getDate()} GMT`),
}));
}
useCard(schoolObj.students[0]);
// 변경된 유효일의 새로운 카드를 schoolObj.students[0].checkCards 에 재할당
schoolObj.students[0].checkCards = reissuedCard(schoolObj.students[0].checkCards);
console.log(JSON.stringify(schoolObj, null, 2));
// ./vo/Student.js file
class Student {
setNM(nm) {
this.nm = nm;
}
setAge(age) {
this.age = age;
}
}
module.exports = Student;
// ./vo/CheckCard.js file
class CheckCard {
updateBalance(diff) {
this.balance += diff;
}
}
module.exports = CheckCard;
// ./vo/House.js file
class House {
setNM(nm) {
this.nm = nm;
}
}
module.exports = House;
// ./util.js file
const { isEmpty } = require('lodash');
class ValueObject {
constructor(jsonKey, isArray, classInstance) {
this.jsonKey = jsonKey;
this.isArray = isArray;
this.classInstance = classInstance;
}
}
const reassignPrototypeOfElement = (prototype, element) => (element ? Object.create(prototype, Object.getOwnPropertyDescriptors(element)) : null);
const reassignPrototypeOnArrayElement = (prototype, arr) => (arr instanceof Array ? arr.map((item) => reassignPrototypeOfElement(prototype, item)) : null);
const reassignPrototype = (vo, obj) => (vo.isArray ? reassignPrototypeOnArrayElement(vo.classInstance.prototype, obj) : reassignPrototypeOfElement(vo.classInstance.prototype, obj));
const reassignPrototypeReviver = (voList, reviverWrapper) => (key, value) => {
const vo = voList.find((item) => key === item.jsonKey);
const newValue = reviverWrapper ? reviverWrapper(key, value) : value;
return vo ? reassignPrototype(vo, newValue) : newValue;
};
/**
* jsonString 을 Object로 변경할때 Prototype 할당
* @param {String} jsonString
* @param {Array} voList
* @param {function(key, value)} reviver (optional)
* @returns {Object} parsing result
*/
const jsonParseAndAssignPrototype = (jsonString, voList, reviver = null) => JSON.parse(jsonString, reassignPrototypeReviver(voList, reviver));
const createNodes = (obj) => (obj instanceof Object ? Object.entries(obj).map(([key, value]) => ({ key, value, parent: obj })) : []);
/**
* 기생성된 Object를 그대로 사용할때 Prototype 재할당
* @param {Object} rootVO
* @param {Array} voList
* @returns {Object} rootVO
*/
const voReassignPrototype = (rootVO, voList) => {
const stack = createNodes(rootVO);
while (!isEmpty(stack)) {
const { key, value, parent } = stack.pop();
const vo = voList.find((item) => key === item.jsonKey);
if (vo) {
parent[key] = reassignPrototype(vo, value);
}
stack.push(...createNodes(parent[key]));
}
return rootVO;
};
module.exports = {
ValueObject,
JsonParseAndAssignPrototype: jsonParseAndAssignPrototype,
VoReassignPrototype: voReassignPrototype,
};
'개발' 카테고리의 다른 글
[Javascript / 리팩토링] 불필요한 매개변수 프로퍼티 재할당 (feat. ESLint : no-param-reassign 에러 발생) (0) | 2022.11.17 |
---|---|
[git] SVN to GIT 마이그레이션 - 1 (0) | 2020.02.09 |
[Jquery] check box 제어 (feat. jqeury, event, toggle, select) (0) | 2019.12.31 |
JAVA 업캐스팅 (3) | 2019.02.25 |
c언어 배열, 포인터, 구조체 (0) | 2019.02.25 |
Comments