Home

Published

- 14 min read

제품 문제 인식

img of 제품 문제 인식

문제 인식

우리 제품은 Window Application이다. 웹을 염두해둔 제품이 아니다. 프론트 개발자가 없어서 팀 막내 or 잉여 인력이 급하게 하루이틀 javascript 배우고 개발하다 보니, 전체적으로 문제가 많았다.

  1. 불필요한 Static File 요청을 서버에게 수백개 날리고 있다. 근데 그 중에 사용되는 파일은 몇개 없다.
  2. static file 요청이 너무 많아서 사용자가 한명만 붙어도 트래픽이 스파이크를 찍고 웹서버가 일부 파일에 대해 응답을 못한다.
  3. 예외 처리가 없기 때문에 런타임 에러 발생시 전체 WebApp이 중단된다. If 분기처리로 Case-By-Case 처리하는 건 한계가 있고 그 Context를 아무도 모른다.
  4. 동적으로 만들어지는 모든 Node와 하위의 HTMLElement가 소멸하지 않는데 Heap Memory를 계속 차지하고 있다.
  5. 이벤트 리스너가 사라지지 않고 누적만 된다. 그래서 몇 분동안 검색, 초기화 등 상호작용하다 보면 뭐 하나 클릭해도 수백개의 이벤트가 Bubble되고 있었다.

근본적인 원인

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

리팩터링 진행 방향

일단 규칙을 정하고 프로세스를 정립했다. 지금은 다른걸로 바꿨지만 내가 처음 투입되고 과도기 단계에는

  1. 적절한 툴을 사용하여 생산성을 높였다.
    • Lerna & Nx 사용하여 모노레포 구조를 만들었다.
    • Grunt와 Parcel 사용한 빌드 프로세스 만들었다.
    • 퇴사자 컴퓨터 받아와서 거기다가 NPM 레지스트리 구축했다. 거기에는 내가 만든 디자인 시스템 라이브러리 및 라우터 라이브러리를 배포했다.
  2. 코드 스타일을 강제했다.
    • 이제는 아래 규칙을 안따랐을 때 빌드 스크립트가 실패한다.
    • ES6 문법 쓰기로 했다. 하지만 당장 레거시 코드도 써야 하기 때문에
      • 추상구문 트리를 파싱하여 Global Scope js를 ESM으로 자동 변환하는 툴을 직접 만들었다.
      • 저거 정말 가슴아픈게 오픈소스로 만들었으면 진짜 사람들 많이 쓸텐데 우리 팀은 웹 개발자가 없고, 웹을 하는 팀은 당연히 AMD를 쓰기 때문에 알아 주는 사람이 없다.
      • 그러다가 전임자 코드 날릴때 실수로 저것까지 같이 날려서(전임자는 Git을 쓸 줄 몰라서 안쓰기 때문에 진짜 없어짐) 이젠 정말 나도 내 업적을 증명 못한다. 저것만 생각하면 밤에 잠이 안온다.
    • 모노레포 루트에 ESLint와 Prettier 및 내가 직접 만든 플러그인(강제로 UTF-8인코딩 변환, CRLF 강제로 LF로 변환 등)을 설정했다.
  3. 생산성을 높였다.
    • 디자인 시스템을 만들었다. 이건 나중에 따로 설명