Published
- 14 min read
제품 문제 인식
문제 인식
우리 제품은 Window Application이다. 웹을 염두해둔 제품이 아니다. 프론트 개발자가 없어서 팀 막내 or 잉여 인력이 급하게 하루이틀 javascript 배우고 개발하다 보니, 전체적으로 문제가 많았다.
- 불필요한 Static File 요청을 서버에게 수백개 날리고 있다. 근데 그 중에 사용되는 파일은 몇개 없다.
- static file 요청이 너무 많아서 사용자가 한명만 붙어도 트래픽이 스파이크를 찍고 웹서버가 일부 파일에 대해 응답을 못한다.
- 예외 처리가 없기 때문에 런타임 에러 발생시 전체 WebApp이 중단된다. If 분기처리로 Case-By-Case 처리하는 건 한계가 있고 그 Context를 아무도 모른다.
- 동적으로 만들어지는 모든 Node와 하위의 HTMLElement가 소멸하지 않는데 Heap Memory를 계속 차지하고 있다.
- 이벤트 리스너가 사라지지 않고 누적만 된다. 그래서 몇 분동안 검색, 초기화 등 상호작용하다 보면 뭐 하나 클릭해도 수백개의 이벤트가 Bubble되고 있었다.
근본적인 원인
- 간단히 요약하면
- 표준을 안쓴다, Javascript 문법을 따르지 않는다. 자신만 아는 규칙과 동작하지 않는 문법으로 자신만 아는 메서드를 특정 파일에 몰래 숨겨놓았다가 런타임에 삽입한다.
- 그 규칙과 함수는 그 사람이 퇴사하면 아무도 못찾는다.
- 모든 변수와 함수가 전역에 선언되어 있다.
- 근데 그게 상당히 많이 덮어써진다.
foo(); get(); set(); getset() setget();
이런 식으로 일반적인 네이밍의 함수가 각각 오천만개씩 정의되어있다. 모두 전역에 선언했으므로 그 중 어떤 함수가 언제 실행 될지 아는 사람은 이미 퇴사했다.- 같은 파일을 2~3번 서버에게 요청한 후 실행해버리는 특성 탓에 어떤 파일은 모든 변수가
const
로 선언을 하면 안된다. 어떤 파일이 그런지는 ‘그들’만 알고 있다.
- Event 핸들링
- 일단 Javascript의 Event 클래스 사용하지 않는다.
- 이벤트 핸들러와 HTMLElement는 생성만되고 소멸이 되지 않는다.
- 자체 정의 Event 클래스는 ClassName을 이용해서 Catch를 하고 있었고 당연히 ClassName은 유일하지 않기 때문에 자기만 아는 ClassName 규칙을 만들어서 ClassName으로 이벤트를 구분하는데 그 규칙 아는 유일한 사람 퇴사했다.
- Event 처리하는 메서드는 또 Prototype이용한 Monkey Patching을 이용해서 구현한다. 따라서 모든 파일이 신뢰할 수 없으며 내가 이 이벤트를 처리하는 메서드를
Ctrl + 좌클릭
하면 아무것도 안뜨는데 분명 어딘가에서 Monkey Patching해서 지만 아는 방법으로 처리하고 있기 때문에 항상 모든 파일을 전수조사해야 한다.
- CSS와 Javascript 코드를 import하기 위해 특정 함수로 런타임 중 index.html에 script 태그를 삽입한다.
- script태그때문에 반응이 느려질까봐 비동기로 가져오겠다고 의도를 한 것 까지는 OK.
- 근데 그걸 초기화 과정이 아니라 각 스크립트 파일에 산포해 있고 그 기준이 존재하지 않아서 어떤 스크립트에는 있고 어떤 스크립트에는 없다.
- 그래서 기능 동작 중에 script 태그 붙이다가 이미 선언된 변수와 이름이 겹쳤을 때 기존에 의도한 동작이 아니라 다른 동작이 실행되거나 const로 인해서 런타임 에러가 발생하고
- 예외 처리가 없다. case by case로 일일히 if 분기처리하고 있다.
- 네이밍은 중요하다, 허나 전체적으로 엉망이다. 예시는 다음과 같다.
include_js(), include_css()
#include <sth.h>
이걸 JS 버전으로 만든거라고 추측하고 있는 중이다. 일부 파일 중 헤더파일을 따라한 흔적이 있었기 때문.strlen(), strcpy(), strcat()
바이트 단위 조작으로 length구하고 copy하고 concat하는거 만드셨던데 그거 안만들어도 된다.- 그 외, C의 stdio나 C++의 string 라이브러리와 동일한 네이밍의 수많은 전역함수들이 있었다. 그리고 역시, 필요없거나 잘못구현했으나 우연히 의도한 동작과 일치하는 함수들이었다.
- 메서드명으로 getSet, setGet, getFunc, getFoo 이러는데 의도를 알 수 없다.
- 모든 전역함수는 호출 직후 isValid(sth)를 호출하고 그 결과를 if 문으로 분기처리하고 있었다.
- 일단 Valid의 기준을 명확히 정의하지 않았다
- Array나 Map에 속하면 True를 반환한다.
- 문제는 Array가 아니라도 Valid한 파라미터인데 모든 전역함수에 대해 이 로직을 적용하고 있었다.
- 퇴사자 중 누군가 isValid를 만들었고 이후에 들어온 사람은 Javascript의 기본 메서드인 줄 알고 원리를 모른체 일단 쓰고 있었다.
- 모든 메서드가 Monkey Patching을 통해 만들어져있다, 모든 파일이 신뢰 불가능하다.
- 특정 클래스의 메서드 사용법을 알기 위해 수많은 파일을 뒤져야 하며, 내가 보고 있는 이 메서드가 실존하는 메서드인지, 이 파일에서 몽키패칭한 메서드인지, 혹은 런타임에 덮어써지는 메서드인지 알 수 없다.
- CSS 관리방식
- 버튼에 대한 CSS가 버튼 파일에 없다. jQuery와 DOM API를 동시에 사용하고 있었는데 버튼 CSS 조작을 Table과 관련된 파일에서 하고 Table CSS 조작을 index.js에서 한다.
- 일관되지 않은 방식: CSS-in-JS, CSS 파일, 동적인 Style태그 삽입 등 모든 방법을 총동원해서 CSS 관리 방식을 파편화시켰다.
- 일관성 없는 코드 스타일
- ES6 문법을 쓰다말다 한다. 이게 무슨 말이냐면 Class 정의해놓고 메서드는 prototype으로 넣는다.
- 사소한 문제이기는 하지만 2 space랑 4 space를 쓰다말다한다. ESLint나 Prettier같은 도구는 존재를 모르고 있어서 그렇다치자. Visual Studio로 개발을 하던데 VS에 Auto Format 기능이 있지 않나?
- 라이브러리 관리를 안함
- 나는 lib라는 폴더가 있길래 거기다 다 몰아넣는 줄 알았는데 그게 아님. 지만 아는 특정 폴더에 멀웨어 마냥 숨겨놓는다. 그리고 퇴사를 했고 다음 사람은 js 하루이틀 배우고 투입되다보니 그게 Javascript 기본 함수인줄 알고 쓴다는게 문제임.
- 예를 들어,
moment()
이건 moment.js라는 외부 라이브러리의 함수이다. 당연히 ‘그들’중 누군가 moment를 사용하기 위해 프로젝트 폴더 중 어딘가에 moment.js를 다운로드 받아서 넣어 둔 후, 수백개 파일 중 어느 곳에include_js('<path>')
를 넣어두고 퇴사를 했을 것이다. - 실제로 date time picker 라이브러리와 excel.js 라이브러리의 경우 그 라이브러리에서만 존재하는 메서드를 써놓고 그게 Javascript의 기본기능인줄 알고 있더라.
- 패키지 매니저, 번들링, 빌드 없음. 웹 프레임워크를 각 프로젝트 폴더에다 통째로
CTRL + C
,CTRL + V
로 사용함.- 적어도 Grunt 같이 태스크 자동화 도구정도는 쓰자. 빌드 프로세스 이런거 안바란다. 적어도 웹 프레임워크를 각 폴더에다 통째로 복사를 할거면 그것정도는 자동화 할 수 있잖아.
- 안할거면 최소한 각 폴더마다 package.json에 version 명시라도 해야 하는데 각 프로젝트의 상태를 표현하는 무언가가 존재하지 않는다.
- 빌드 프로세스 자동화 안해서 각 프로젝트 마다 웹 프레임워크의 버전이 다르다. 한마디로 웹 프레임워크의 버전 관리를 안한다.
- 웹 프레임워크를 어떻게 가져오냐고 물어보니 그 용량 큰 폴더를 통째로 복사해서 쓰더라. 그럼 Production용으로 배포할 때는 어떻게 하냐고 물어보니 복붙하고 index.js 수정하래. 이러니까 실제 배포된 웹 페이지가 그 용량 큰 폴더의 모든 파일들을 부르게 되고 페이지 로딩에 10초가 걸리게 되었다.
리팩터링 진행 방향
일단 규칙을 정하고 프로세스를 정립했다. 지금은 다른걸로 바꿨지만 내가 처음 투입되고 과도기 단계에는
- 적절한 툴을 사용하여 생산성을 높였다.
- Lerna & Nx 사용하여 모노레포 구조를 만들었다.
- Grunt와 Parcel 사용한 빌드 프로세스 만들었다.
- 퇴사자 컴퓨터 받아와서 거기다가 NPM 레지스트리 구축했다. 거기에는 내가 만든 디자인 시스템 라이브러리 및 라우터 라이브러리를 배포했다.
- 코드 스타일을 강제했다.
- 이제는 아래 규칙을 안따랐을 때 빌드 스크립트가 실패한다.
- ES6 문법 쓰기로 했다. 하지만 당장 레거시 코드도 써야 하기 때문에
- 추상구문 트리를 파싱하여 Global Scope js를 ESM으로 자동 변환하는 툴을 직접 만들었다.
- 저거 정말 가슴아픈게 오픈소스로 만들었으면 진짜 사람들 많이 쓸텐데 우리 팀은 웹 개발자가 없고, 웹을 하는 팀은 당연히 AMD를 쓰기 때문에 알아 주는 사람이 없다.
- 그러다가 전임자 코드 날릴때 실수로 저것까지 같이 날려서(전임자는 Git을 쓸 줄 몰라서 안쓰기 때문에 진짜 없어짐) 이젠 정말 나도 내 업적을 증명 못한다. 저것만 생각하면 밤에 잠이 안온다.
- 모노레포 루트에 ESLint와 Prettier 및 내가 직접 만든 플러그인(강제로 UTF-8인코딩 변환, CRLF 강제로 LF로 변환 등)을 설정했다.
- 생산성을 높였다.
- 디자인 시스템을 만들었다. 이건 나중에 따로 설명