현재 직장에서 담당하고 있는 부분(웹&데스크탑)의 이슈 중, 보안 이슈를 핸들링하던 와중, 다소 특이하게 토큰(Access Token
, Authentication Token
)을 특이하게 다루고 있는 것을 확인했다.
다소 특이하게 관리하는 큰 틀 두가지가 있었는데(코드는 5~3년전까지 활발하게 개발되었고, 그 이후로는 많은 부분이 건드려지지 않았다...),
첫 번째로 Auth Token
의 사용이 이루어지지 않는다는 점이다.
사실 Auth Token
은 사용 될 것이다. 웹 서비스와 마찬가지로, 공통 부분들이 실 서버로 부터 정보를 받아오기 위한 헤더로 쓰이기 때문이다. 하지만, 적어도 Javascript
로 이루어진 코드(뷰&내부 서버)는 저장만 할 뿐, 다루지 않았다...
두 번째로 Access Token
으로 정의 된 소셜 로그인의 토큰들이 그나마 활발하게 사용되었다. 소셜 인증을 통해 받아온 토큰을 바탕으로 사용자 정보를 저장하고, 로그인 유지를 시키기 때문에 이 정보들은 로컬과 Javascript
서버내에서도 활발하게 사용되었다.
이 두가지를 언급한 이유는 사실 간단하다... 위의 동작들을 이루기 위한 코드 + 그 외의 handler
들이 모두 Class
로 작성된 점이다.
Java
와 그 외의 OOP
를 지향하는 언어들을 다루어 봤기 때문에, Class
자체를 이해하지 못하진 않았지만, 너무 많은 this
키워드와, 내부 메소드들의 지속적인 활용 덕분에, 구조를 파악하고 원래 하려던 작업을 진행하는데까지 소요된 시간이 다소 많았다... (30%는 왜 이렇게 짯을까의 고민... 30%는 그래서 이렇게 한게 어디에 쓰이는지에 대한 고민..., 30%는 결과적으로 짜인 부분에 대한 이슈 적용 방법 고민... 나머지 10% 밖에 걸리지 않은 이슈 해결...)
개인적으로 Class
로 작성 된 코드를 보며 큰 장점 한가지와 큰 단점 한가지를 느끼기도 했다.
- 잘 만든 코드 같다.
- 가독성이 현저히 떨어지는 코드 같다.
사실 2번의 부분이 1번의 부분을 침해하긴 하지만, 그 이유를 차차 개념을 살펴가며 다시 잡아보고자 했다. (내가 틀렸을 수 도 있으니)
Class
사실 Javascript
에서의 Class
는 엄밀히 말하면 Class
가 아니다. 더 정확하게 말하면, 기존 OOP
의 Class
를 흉내내기 위한 특별한 함수로, 객체를 생성하기 위한 템플릿이다.
Constructor (생성자)
Class
는 반드시 하나의 생성자 함수만 갖는다. 생성자 함수는 클래스 객체를 초기화하고 생성하기 위한 특수한 메소드이다.OOP
의 Class
와 같이 상속을 할 수 있는데, 이때 super
키워드를 활용하여 부모 클래스의 생성자 함수를 호출 할 수 있다.
class Mom {
constructor(wealth){
this.wealth = 'rich';
}
}
// undefined
class Son extends Mom{
constructor(wealth){
super(wealth)
}
get money() {
return this.wealth
}
}
// undefined
son.money
// 'rich'
기본 문법
Class
는 다음과 같은 기본 문법을 갖는다.
class myClass {
constructor(){...}
method_1(){...}
method_2(){...}
...
}
생성된 class
는 new 키워드로 호출 할 수 있고, 이때 자동으로 constructor()
가 불러와진다.
이때, myClass.prototype
에 constructor()
와 method()
들이 들어있고, prototype
프로퍼티를 통해서 메소드들을 가져오게 된다.
Factory Function
Factory function
은 new
키워드를 사용하여 생성하는 class
객체와는 다르게, 단순히 함수가 객체를 만들고, 리턴해준다.
function myProfile(name) {
return {
name: name,
tellMyProfile: function () {
console.log('My name is ' + name);
}
};
}
Class vs Factory Function ?
어떻게 보면 큰 차이점이 있어보이지만, 근본적으로 둘다 함수 이며, 내부 속성과 메소드를 갖는다는 점에서 유사해 보인다.
차이점은 무엇이 있을까?
- Encapsulation (캡슐화)
기본적으로 class
는 캡슐화를 제공하지 않는다. 반면 Factory Function
은 제공이 된다. 하지만 이 부분도, nodejs 12 부터는 private class field
의 사용으로 class
를 캡슐화 할 수 있다.
class TodoModel {
#todos;
constructor(data) {
this.#todos = [];
this.data = data;
}
addData() {
console.log(`${data} addData`);
}
#add() { console.log('add'); }
}
const todoModel = new TodoModel('inputData');
console.log(todoModel.todos); // undefined
todoModel.add(); // todoModel.add is not a function
- Immutable (불변성)
상태를 많이 다루는 컴포넌트에서 불변성은 중요한 속성이다.
class
함수는 메소드를 변경 할 수 있으나, Factory Function
에서는 변경되지 않는다.
static
키워드를 통해 이를 해결 할 수 있지만, 인스턴스화 되지 않으므로 조심해야하며, 권장하지 않는다.
- Composition and inheritance (상속과 구성)
class
는 기존 상속과 같이 모든 메소드를 상속받아 사용하지만, Factory Function
은 선별적으로 구성을 통해 사용된다.
- Class
class Person {
eat() {
console.log('I am eating');
}
breathe() {
console.log('I am breathing');
}
swim() {
console.log('I am swimming');
}
}
class Wizard extends Person {
trick() {
console.log('I am doing a trick');
}
}
const harry = new Wizard();
const ron = new Wizard();
// Harry can:
harry.eat(); // I am eating
harry.breathe(); // I am breathing
harry.swim(); // I am swimming
harry.trick(); // I am doing a trick
// Ron can:
ron.eat(); // I am eating
ron.breathe(); // I am breathing
ron.swim(); // I am swimming
ron.trick(); // I am doing a trick
- Factory Function
const Person = () => {
return {
eat: () => {
console.log('I am eating');
},
breathe: () => {
console.log('I am breathing');
},
swim: () => {
console.log('I am swimming');
},
};
};
const Trick = () => {
return {
trick: () => {
console.log('I am doing a trick');
},
};
};
const Wizard = () => {
return {
eat: Person().eat,
breathe: Person().breathe,
trick: Trick().trick,
};
};
const Muggle = () => {
return {
eat: Person().eat,
breathe: Person().breathe,
swim: Person().swim,
};
};
// Harry can:
const harry = Wizard();
harry.eat(); // I am eating
harry.breathe(); // I am breathing
harry.trick(); // I am doing a trick
// Ron can:
const ron = Muggle();
ron.eat(); // I am eating
ron.breathe(); // I am breathing
ron.swim(); // I am swimming
- this 키워드의 사용
class
는 this
키워드의 사용이 가능하나, Factory Function
은 불가능하다.
- 동일 객체 생성에 대한 메모리 cost
class
의 모든 메소드는 프로토타입 객체에서 생성 된 이후, 모든 인스턴스에서 공유된다.
하지만 Factory Function
은 동일한 객체를 만들 때, 메모리 비용이 많이 든다.
The memory cost (in Chrome)
+-----------+------------+------------+
| Instances | 10 methods | 20 methods |
+-----------+---------------+---------+
| 10 | 0 | 0 |
| 100 | 0.1Mb | 0.1Mb |
| 1000 | 0.7Mb | 1.4Mb |
| 10000 | 7.3Mb | 14.2Mb |
+-----------+------------+------------+
그래서 결론은?
사실 기존 리엑트 라이브러리도, class
로 컴포넌트를 생성하여 사용되었지만, 현재는 hooks
의 도입과 함께, functional
하게 바뀌었다.
안전성에 있어서 Factory Function
이 우위를 갖으며, 메모리적으로는 손해일 수 있지만, 인스턴스에 올라가 재사용이 빈번하게 일어나지 않는 이상 요즘 클라이언트의 메모리가 감당하지 못할 수준은 아닌 것 같다.
개인적으로는 this
키워드의 사용을 싫어한다... 5년된 레거시 코드를 다루며 정말 많은 this
키워드를 보고 있는데 항상 볼 때 마다 this
의 scope
에 머리가 빠질 것 같다...
Redux
의 state
를 connect
함수로 받아 쓸 때, this.props
로 접근하게 되는데, 이때 이 prop
이 부모 컴포넌트로 부터 온 것인지, Redux
의 state
를 표현한 것인지 확인하는 작업을 한다는 것 자체에 정말 진절머리나긴한다...
물론 요즘은 바로 dispatch
를 통해 상태를 저장하고, selector
를 통해 가져와 사용해서 고민 할 것이 안되지만, 레거시를 다루는 입장에서 제한된 인원으로 (데스크탑은 혼자다...) 전부 리팩토링 하기 전까지는 다룰 수 밖에 없다는 점이 아쉽다...
그렇다고 단점만 보이는 것은 아니다. 사실 어플리케이션 자체는 로그인을 해야 움직일 수 있다. 결국 Login Command
가 class
로 존재하여, 관련 메소드들을 모두 정의한다면, 어플 시작과 동시에 인스턴스에 존재하고, 로그인 및 auth 관련 api
를 다루어준다면, 그만큼 편하게 작업 할 수도 있긴 하다.
현 레거시를 보면, 근본적인 틀은 꽤나 잘 만들었다고 생각했다. 이렇게 지속적으로 쓰여야만 하는 일부 핸들러들이 인스턴스에 떠있고, 바로 활용할 수 있게끔 되있다. 하지만 코드 상으로 봤을 때, 기존 기획과는 다르게 다양한 추가 기능들이 붙으면서, 리팩토링이 아닌 그때그때의 기능 및 코드 추가로, 나같은 이어 받은 개발자가 개발하기 힘든 환경을 안겨준 것은 사실이다...
참고 사이트
'Software Engineering' 카테고리의 다른 글
[Web App] Frontend Developer로 Web App 개발하기 (0) | 2023.01.14 |
---|---|
[Design Pattern] 의존성 (Dependency) (0) | 2022.10.09 |
Git 브랜치 전략, Git-flow (0) | 2022.09.03 |
성능을 위한 프론트엔드 설계와 코드 - 1 (0) | 2022.08.28 |
Quality Assurance (QA: 품질보증) (0) | 2022.08.26 |