타입스크립트로 마이그레이션하며 깨달은 것들

여러분은 개발할 때 새로운 도구나 기술을 도입하는 것을 어떻게 생각하나요? 저는 새로운 기술이나 도구 도입을 굉장히 좋아합니다. 회사를 다닌 지 1년 정도 되었을 때, 운 좋게 구름IDE를 타입스크립트(TypeScript)로 마이그레이션 할 기회가 생겼습니다. 야근을 불사할 정도로 이 프로젝트에 빠졌죠. 그러나 프로젝트는 중단됐고 태스크는 곧 ICE BOX(우선순위가 높지않아 대기중인 상태)로 됐습니다. 이 글은 그때 경험을 회고하며 시행착오를 기록한 글입니다. 언젠가 타입스크립트를 도입하게 될 누군가 또는 미래의 저에게 이 글이 도움 되길 바랍니다.

💬 구름IDE는 클라우드 기반 통합 개발 환경 서비스이다. 쉽게 말하면 우리가 문서를 작성하는 MS Word처럼 개발자가 코드를 작성하며 개발하는 도구이다. 구름IDE의 가장 큰 장점은 PC에 직접 개발 환경을 구축하지 않고 클라우드에 개발 환경을 두고 인터넷 브라우저로 접속해 어디서나 동일한 환경에서 개발할 수 있다는 점이다. 무료로 사용할 수 있는 기본적인 기능 외에도 유료 서비스로 다양한 기능을 제공하고 있다. 

왜 타입스크립트인가?

일단 왜 제가 타입스크립트를 구름IDE에 새롭게 도입을 제안한 이유를 말해야겠네요. 단순히 새로운 기술을 쓰고 싶어서 시작한 일은 아니었습니다. 자바스크립트(JavaScript)는 느슨한 타입(Loose Typing)의 동적 언어입니다. 타입에 종속적이지 않아서 사용할 때마다 모든 변수에 타입과 상관없이 값을 할당할 수 있습니다. 이 특징은 개발을 빠르고 유연하게 하도록 돕습니다. 장점이기도 한 이 특징으로 인해 문제가 발생하는 몇몇 사건(?)을 겪게 되었습니다. 이 과정에서 타입을 잘못 생각하고 프로그래밍하고 있다는 걸 깨달았습니다.

😱 사건1 | 13레벨이 13,100레벨이 되다

구름IDE 기반으로 구름EXP 홈페이지가 생긴 지 얼마 안된 4월 1일 아침이었습니다(마침 만우절).

스탠드업을 작성하려고 보니 제 레벨이 13,100이었습니다. (본래는 13레벨이었습니다.) 만우절을 기념한 구름EXP의 장난으로 보였지만, 사실 이것은 자바스크립트의 장점이기도 한 느슨한 타입에 의해 발생한 버그였습니다. 본래는 레벨에 100을 더하는 이벤트였지만, 의도와 달리 기존 레벨 표시가 문자형 데이터라서 숫자 100이 문자형 변환되어 더해진 문제였습니다. 구르미에게는 잊지 못할 만우절(?) 이벤트가 되었습니다.

💬 구름EXP는 구름에서 측정할 수 있는 모든 데이터들을 최대한 수집하고 이 데이터들을 수치화하여 EXP(경험치)의 형태로 구르미들에게 부여하는 시스템입니다.

💬 구름EXP의 스탠드업은 매일 어제 할 일과 오늘 할 일을 정리하고 공유해 서로가 하는 업무를 파악하고, 같은 방향으로 나아가고 있음을 확인하는 방법입니다.
구름EXP, 스탠드업이란?

import PropTypes from 'prop-types';

class Greeting extends React.Component {
    render() {
        return (
            <h1>Hello, {this.props.name}</h1>
        );
    }
}

Greeting.propTypes = {
    name: PropTypes.string
};
JavaScript

이러한 느슨한 타입에 의한 문제를 막고자 구름IDE는 PropTypes라는 라이브러리를 사용합니다. 이 라이브러리는 리액트(React)의 컴포넌트(Component)가 부모로부터 전달받은 요소의 타입을 코드에 명시하고 실행 시 체크합니다. 만약 유효하지 않은 타입이 들어온다면 콘솔에 경고를 출력합니다.

😱 사건2 | 지워도 끝이 없는 타입 경고

다음 그림에 있는 클릭업 링크들은 제가 스프린트가 끝나는 마지막 주에 각종 작은 버그와 경고 메시지들을 해결하고자 진행했던 태스크들입니다. 해당 태스크를 할 때면 꼭 앞서 설명한 PropTypes 라이브러리가 알려주는 잘못된 타입들을 고쳐야 했습니다. 15주 정도를 계속해서 3주 단위로 타입이 틀린 것을 수정했습니다. 15주의 시간을 들여 고쳐도 새로 고쳐야 할 것은 늘어났습니다. 

이 과정에서 우리는 자주 타입을 틀리며, 개발자가 예상한 대로 동작하지 않을 수 있다는 것을 눈으로 확인했습니다. 문제점을 개선하려면 타입이 잘못되면 바로 에러 메시지를 출력하는 타입스크립트가 필요하다고 생각했습니다. 단순한 경고가 아닌 에러로 인해 애플리케이션이 동작하지 않으면 사람들은 타입 지정에 좀 더 신경을 쓸 테니까요.

혼자서 마이그레이션을 시작하다

느슨한 타입으로 인한 문제를 해결하고 싶었습니다. 팀원도 자바스크립트의 이러한 문제점을 느끼고 있을거라 생각했습니다. 먼저 기술적으로도 설득하기 위해서 여러 타입스크립트 마이그레이션 글을 모으고 공부했습니다. 회사에서도 해당 연도의 기술 목표에 타입스크립트 도입이 고려되고 있던 차였습니다. 그래서 구름IDE에 타입스크립트를 적용하자고 팀원을 설득했습니다. 팀원들은 타입스크립트 도입에 반대하지는 않았지만, 현재 프로젝트에 도입하는 것에는 확신이 없었습니다. 그래서 혼자서라도 시작해 보기로 마음 먹었습니다. 타입스크립트 도입으로 제품 안전성이 개선되는 것을 본다면 팀원들의 마음을 돌릴 수 있을 테니까요.

// tsconfig
/* tsconfig에서는 JavaScript파일을 쓸 수 있게 수정하였다 */

...
       "allowJs": true,
       "skipLibCheck": true,
       "strict": false,
...

// eslintrc.client.js
/* eslint도 TypeScript를 동시에 쓸 수 있게 수정하였다 */

...
overrides: [{
files: ['**/*.ts','**/*.tsx'],
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
...
JavaScript

시작은 타입스크립트와 자바스크립트를 같이 이용할 수 있는 환경을 만드는 일이었습니다. 한꺼번에 거대한 프로젝트의 코드 전부를 바꾼다는 것은 불가능한 일이기 때문입니다. 둘을 병행할 수 있는 환경을 만드는 것이 중요했습니다. 환경을 다 만든 다음에는 적용을 고민했습니다. 모든 부분을 설계하고 고려하는 데에는 오랜 시간이 걸릴 것 같아서 데이터 속성의 개수가 적은 것부터 바꿔나갔습니다.

export interface Container {
    always: boolean;
    authorId: string;
    collaborationSummary: {
        extraCollaboratorCount: number;
        representationCollaborators: Array<any>; // userInfo
        rootUserIds: Array<string>;
    };
    createDate: string;
    description: string;
    domains: Array<string>;
    gpu: boolean;
    imagingState: number;
    lastAccessDate: string;
    lastRunDate: string;
    lastStopDate: string;
    name: string;
    owner: UserInfo;
    portforward: Array<any>; // portInfo
    projectPath: string;
    shareLinkPermission: string;
    shareLinkType: string;
    spaceId?: string;
    stack: StackInfo;
    status: string;
    storageSize: number;
    terminalAccessLink: string;
    uid: string;
    workspaceAccessLink: string;
    customImageIndex?: string;
    launchingStep?: string;
    stopping?: boolean;
    alwaysOnDate?: Date;
    errCode?: any;
    stepCount?: number;
    creatingStep?: string;
}
JavaScript

😱 사건3 | 구성요소가 많은 데이터

속성이 적은 데이터를 고치고 나니 그다음이 문제였습니다. 속성이 많은 데이터를 바꾸려면 거기에 사용된 모든 데이터의 속성 타입을 인지해야 해서 작업이 어려웠습니다. 적은 속성을 가진 데이터부터 바꾸다 보면 속성이 많은 데이터의 타입도 모두 추론할 수 있을 거라 생각했는데 예상은 빗나갔습니다. 속성이 많은 데이터의 타입을 알아내고 새로 정의하는 것은 많은 시행착오와 시간이 필요한 일이었습니다.

또한 작성해 두었던 데이터 속성의 타입이 다른 파일들에서 쓰일 때에는 타입이 맞지 않아서 다시 작성해야 하는 문제도 있었습니다. 제가 앞에서 일어났던 타입 오류를 반복하고 있었던 것이죠. 

마이그레이션에 대한 정의와 설계가 제대로 갖추어져 있지 않은 점도 문제였습니다. 그러다 보니 타입스크립트를 고려하면서 고치기보다는 안전하고 빠르게 코드를 바꾸고자 마이그레이션 이전 버전의 자바스크립트 파일을 불러와서 진행하는 일이 빈번했습니다.

타입스크립트 마이그레이션에서 내가 깨달은 것

제가 타입스크립트로 수정했던 코드와 자바스크립트 코드가 충돌하면서 팀 내에는 타입스크립트에 대한 부정적인 의견이 늘었습니다. 저 또한 다른 급한 업무 등에 투입되며 타입스크립트 도입은 잠정 중단되었습니다.

이 일을 계기로 두 가지를 깨닫게 되었습니다. 첫째, 기술을 옮기기 위해서 충분히 많은 설계나 준비가 되어 있어야 한다는 것입니다. 빠르게 적용해보고 싶은 나머지 이를 소홀히 함으로써 같은 실수를 반복하고 기술적인 장점을 살리지 못했습니다. 둘째, 그것을 사용하는 사람들을 설득하지 못하면 이점보다는 불편함을 안겨줍니다. 제대로 동작하지 못하면 이점은 살리지 못하고, 문제만 더 일으킬 수 있습니다.

// JSDoc
/* JavaScript에서 타입 추론을 도와주는 도구입니다. */
/** 

 * @param {string} name 
 * @param {string} phoneNumber 
 * @param {string} email 
 * @param {string} companyName 
 * @param {string} companyRegistrationNumber 
 * @param {{ 
 *  isEtc: boolean, 
 *  value: string, 
 *  etcDescription: string 
 * }} funnel 
 * @returns {Promise<boolean>} 
 */
JavaScript

마이그레이션뿐 아니라 여러 업무에서 이 둘이 얼마나 중요한지 잘 이해하고 있지만, 제게 설계와 설득은 여전히 힘듭니다. 그래서 요즘은 진행한 작업은 문서로 남기고 팀원의 이야기도 경청하려 노력하고 있습니다. 타입스크립트 마이그레이션은 실패했지만, 자바스크립트로 타입을 확인할 수 있는 도구를 틈틈이 찾고 개발자들이 타입스크립트와 친해질 수 있도록 ‘챌린지’도 공유했습니다.

이런 도전을 하면서 저만 무언가를 배운 게 아닌 것 같았습니다. 제가 이 글을 쓰면서 찾아보니 생각보다 많은 분이 제가 타입스크립트를 시작했을 때 작성했던 노션 글과 멈추었을 때 쓴 을 많이 보셨더라고요. 몇몇 분들은 발표나 의사 결정을 할 때 참고했다고 들었습니다. 

지금 돌이켜보면, 부족한 점이 많았던 내용들이지만 저 스스로에게나 여러분께 반면교사가 될 수 있었던 좋은 기회였습니다.

Posted by
mono.kim

언제나 좋은 동료가 되는 것을 꿈꾸는 개발자입니다.