Skip to content

Instantly share code, notes, and snippets.

@jung-han
Created March 11, 2020 06:03
Show Gist options
  • Save jung-han/13b158002d39fefb14e277e30ef00656 to your computer and use it in GitHub Desktop.
Save jung-han/13b158002d39fefb14e277e30ef00656 to your computer and use it in GitHub Desktop.

자바스크립트는 무엇으로 구성되어있을까?

원문: https://overreacted.io/what-is-javascript-made-of/

처음 몇 년 동안 자바스크립트를 사용하면서, 나는 사기꾼처럼 느껴졌었다. 프레임워크를 이용해 웹사이트를 만들 수 있었지만, 뭔가 빠진 느낌이었다. 기초에 대한 확실한 이해가 없었기 때문에 자바스크립트 구직 면접을 두려워했었다.

몇 년 동안, 나에게 자신감을 주는 자바스크립트의 멘탈 모델을 만들었다. 여기, 매우 압축된 버전의 멘탈 모델을 공유하려고 한다. 단어를 하나 제시하고, 각 주제에 맞게 몇 문장을 설명하는 방식으로 구성했다.

글을 읽으면서, 각 토픽에 대해 얼마나 자신감을 느끼는지 마음속으로 점수를 매겨봐라. 당신이 토픽 중 몇 가지를 놓쳤는지 판단하지 않을 것이다! 이 글의 끝에는 몇 가지 도움이 될만한 것을 작성했다.


  • 값(Value): 값의 개념은 약간 추상적이다. "것(thing)"이다. 자바스크립트에서 값은 수학의 숫자, 기하학의 점이다. 프로그램이 실행될 때 프로그램은 값으로 가득차 있다. 1, 2, 420 같은 숫자로 된 값 외에, "소는 음매" 같은 문장도 포함되어 있다. 하지만, 모든 것이 값은 아니다. 숫자는 값이지만 if문은 아니다. 아래에서 몇 가지 다른 값을 살펴보자.

    • 값의 타입: 값에는 몇 가지 "타입"이 존재한다. 예를 들어, 420 같은 숫자나,"소는 음매" 같은 문자열, 객체, 그리고 몇 가지 다른 타입이 존재한다. typeof를 값의 앞에 두면 값의 타입에 대해 알 수 있다. 예를 들어, console.log(typeof 2)를 실행한다면 "number"가 출력될 것이다.
    • 원시값(Primitive Values): 몇몇 값들의 타입은 "원시(Primitive)"다. 원시값은 숫자, 문자열, 그리고 몇 가지 타입을 포함한다. 원시값의 한 가지 독특한 점은 새롭게 원시값을 만들거나, 원시값을 변경하는 것이 불가능하다는 것이다. 예를 들어, 당신이 2를 쓸 때마다 항상 같은 2 값을 얻을 것이다. 당신의 프로그램에서 새로운 2를 "생성"할 수 없고, 23이 "되는" 것은 불가능하다. 문자열 또한 동일하다.
    • nullundefined: 두 가지 특별한 값이 있다. 이 두 값은 많은 것을 할 수 없게 만들며, 종종 에러의 원인이 된다. 일반적으로 null은 의도적으로 값이 없는 것을 의미하며, undefined는 의도하지 않게 값이 없는 것을 의미한다. 그러나, 언제 어떻게 사용될지는 프로그래머에게 맡겨진다. 두 값은 종종 누락된 값을 처리하는 것보다 연산이 실패하는 것이 괜찮기 때문에 존재한다.
  • 같음(Equality): "값" 처럼, "같음"은 자바스크립트의 기본 개념이다. 두 값이 같다고 말한다면(나는 절대 그렇게 말하지 않겠지만), 같은 값(value)이라는 것을 의미한다. 서로 다른 두 값(value)은 존재할 수 없다. 하나일 뿐이다! 예를 들어, "소는 음매"==="소는 음매"2===2인 것은 22이기 때문이다. JavaScript는 이 개념을 나타내기 위해 등호 세개를 사용한다.

    • 엄격한(strict) 같음: 위와 동일
    • 참조(Referential) 같음: 위와 동일
    • 느슨한(Loose) 같음: 이건 다르다! 느슨한 같음은 두 개의 등호(==)를 사용한다. 보기에는 비슷해 보이지만 다른 값을 참조하더라도 느슨하게 동등한 것으로 간주할 수 있다.(2와 "2"처럼) 일찍이 자바스크립트에 편의를 위해 추가되었지만, 이후로 끊임없는 혼란을 불러왔다. 이 개념은 기본 개념은 아니지만, 일반적인 실수의 원인이 된다. 난관에 부딪혔을 때 상황을 해결해 줄 수 있지만, 일반적으로는 사용을 지양한다.
  • 리터럴(Literal): 리터럴은 문자를 그대로 적음으로써 값을 나타내는 것이다. 2는 숫자 리터럴이고 "바나나"는 문자열 리터럴이다.

  • 변수: 변수를 통해 이름을 사용해 값에 참조할 수 있다. 예를 들어, let message = "소는 음매"로 작성하면 코드상에서 반복해서 문장을 적지 않고 message로 작성할 수 있게 된다. 또한, 이후에 message = "난 바다코끼리다"처럼 message가 다른 값을 가리키도록 바꿀 수 있다. 이것이 의미하는 것은 값 자체를 바꾸는 것이 아니다. "와이어"처럼 message가 어디 있는지 가리키는 위치만 변경된다. 변수는 "소는 음매"를 가리키다, 지금은 "나는 바다코끼리다"를 가리키고 있다.

    • 스코프: 프로그램 전체에서 하나의 message 변수를 사용할 수 있다면 별로일 것이다. 변수를 정의하면, 프로그램의 한 부분에서 사용할 수 있게 된다. 스코프가 어떻게 동작하는지 작동 방식에 대한 규칙이 있지만, 일반적으로 변수를 정의한 위치 근처의 {} 중괄호를 확인하면 찾을 수 있다. 이 코드 "블록"이 변수의 스코프다.
    • 할당: message = "나는 바다코끼리다"로 작성할 때, message 변수가 가리키는 값이 "나는 바다코끼리다"로 바뀐다. 이것을 할당, 쓰기, 혹은 변수 설정이라 한다.
    • let vs const vs var: 보통은 let을 사용하고 싶을 것이다. 만약 변수에 값 할당을 금지하려면 const를 사용하면 된다.(몇몇 코드베이스와 동료들은 한 번만 할당 할 경우 const를 사용하는 약속을 만들기도 한다.) 가능하다면 var 사용은 피하라. var는 스코프를 더럽힌다.
  • 객체: 객체는 자바스크립트에서 특별한 종류의 값이다. 객체의 멋진 점은 다른 값들과 연결을 가질 수 있다는 점이다. 예를 들어 {flavor: "vanilla"} 객체는 "vanilla"라는 값을 가리키는 flavor 속성이 존재한다. 객체를 "와이어"로 "자신의" 값을 갖는 값으로 생각하라.

    • 속성: 속성은 객체에서 어떤 값을 가리키는 "와이어" 역할을 한다. 앞에서 언급했던 변수가 떠오를 수 있는데(flavor같은 이름을 갖고 "vanilla"라는 값을 가리키는 것이), 변수와 다르게 요소는 코드(스코프)가 아닌 객체 내에 "존재"하게 된다. 요소는 객체의 일부로 간주하지만, 가리키는 값은 아니다.
    • 객체 리터럴: 객체 리터럴은 {}{flavor: "vanilla"}처럼 문자 그대로 작성해서 객체를 만드는 방법이다. {} 안에는 쉼표로 구분된 여러개의 {proverty: value} 쌍을 가질 수 있다. 이를 통해 객체의 속성이 어디에 "와이어"되는지 할당할 수 있다.
    • 객체 항등(Identity): 우리는 앞서 22와 같다(다시 말하면, 2 === 2)고 했다. 왜냐하면 2를 작성하는 어디에서나 항상 같은 값이 "소환"되기 때문이다. 하지만 {}를 작성한다면, 항상 다른 값이 될 것이다! 따라서 {}는 다른 {}와 다르다. 콘솔 창에 {} === {}를 실행해봐라.(false가 반환될 것이다) 컴퓨터가 코드에서 2를 만나면 항상 같은 2를 반환할 것이다. 하지만, 객체 리터럴은 다르다. 컴퓨터가 {}를 만나면 항상 새로운 객체를 생성한다. 그럼 객체 항등(identity)은 무엇일까? 그것은 여전히 동일하거나, 값의 동일성을 나타내는 또 다른 용어이다. 우리가 "ab가 항등이다"라고 말하는 것은, "ab가 같은 값을 가리키고 있다"는 것을 의미한다.(a === b) 만약 "ab가 항등이 아니다"는 것은 "ab가 다른 값을 가리킨다"(a !== b)는 것을 의미한다.
    • 점 표기법: 객체의 속성에 접근하거나 할당하고 싶을때 점(.) 표기법을 사용할 수 있다. 예를 들어 만약 변수 iceCreamflavor요소가 가리키는 값이 "chocolate"이라면, iceCream.flavor로 작성할 경우 "chocolate"가 반환된다.
    • 괄호 표기법: 접근하고 싶은 프로퍼티의 이름을 미리 알지 못하는 경우가 있다. 예를 들어 때로는 iceCream.flavor에 접근하고 싶을 수도 있고 iceCream.taste에 접근하고 싶을 수도 있다. 대괄호([]) 표기법은 변수를 이용해서 요소에 접근이 가능하게 한다. 예를 들어 let ourProperty = 'flavor'라고 선언해보자. iceCream[ourProperty]로 작성하면 "chocolate"를 반환한다. 신기하게도 { [ourProperty]: "vanilla" }처럼 객체를 생성할 때도 사용할 수 있다.
    • 뮤테이션(Mutation): 우리는 객체의 요소가 가리키는 부분이 달라졌을 때 변이(mutate) 되었다고 한다. 예를 들어 let iceCream = {flavor: "vanilla}라고 선언했을 때, iceCream.flavor = "chocolate"로 작성해 값을 변이 할 수 있다.비록 consticeCream을 선언하더라도 iceCream.flavor를 통해 변이할 수 있다. 왜냐하면 consticeCream 변수 그 자체만의 할당만을 막을 수 있기 때문에, 객체의 요소(flavor)가 가리키는 값을 바꿀 수 있다. 이런 오해의 소지 때문에 몇몇 사람들은 const를 맹목적으로 잘못 사용하기도 한다.
    • 배열: 배열은 무엇인가 나열 할 때 사용하는 객체이다. 배열 리터럴로 ["banana", "chocolate", "vanilla"]로 작성한다면, 본질적으로 0 요소가 "banana"에, 1 요소가 "chocolate"를, 2 요소가 "vanilla"를 가리키는 객체를 생성한다. {0: ..., 1: ..., 2: ...}처럼 작성하는 것은 매우 귀찮을 것이다. 또한 map, filter, reduce처럼 배열을 다루는 여러가지 메서드또한 제공된다. reduce가 혼란스럽다고 절망하지 마라 - 다른 사람들도 마찬가지로 혼란스러워하는 개념이다.
    • 프로토타입: 만약 존재하지 않는 요소에 접근하려 하면 어떤 일이 벌어질까? 예를 들어 iceCream.taste처럼 말이다.(우리가 선언한 요소는 flavor뿐이다.) 질문에 대한 단순한 답은 undefined이다. 좀 더 구체적으로 설명해보면, 대부분의 자바스크립트 객체는 "프로토타입"을 갖는다�. 당신은 프로토타입을 모든 객체에서 "다음에 어디를 봐야할 지" 결정하는 "숨겨진"요소로 생각할 수 있다. 따라서 iceCream객체에 taste라는 요소가 없다면, 자바스크립트는 객체의 프로토타입을 따라 taste요소를 찾아 거슬러 올라갈 것이다. 그리고 "프로토타입 체인"을 모두 찾았지만 .taste를 찾지 못한 경우에 undefined를 반환한다. 당신은 이 메커니즘을 직접적으로 사용할 일은 별로 없겠지만, iceCream객체에 우리가 정의하지 않은 toString 메서드가 존재하는 것을 설명할 수 있다. - 이 메서드는 프로토타입에서 왔다.
  • 함수: 함수는 프로그램에서 일부 코드를 나타내는 목적을 가진 특별한 값이다. 함수는 중복 코드를 여러번 작성하지 않으려는 경우 편리하게 쓸 수 있다. sayHi()같은 함수를 "호출"하면 컴퓨터에서 해당 코드를 내부적으로 실행한 뒤, 프로그램의 원래 위치로 돌아간다.

    • 매개 변수: sayHi("Amelie")처럼 매개변수를 이용하면 함수로 몇 가지 정보를 전달할 수 있다. 함수 내에서 매개변수는 변수처럼 동작한다. 이 값들은 "인자" 혹은 "매개변수"로 불린다.(함수를 정의하는 부분인지 호출하는 부분인지에 따라). 그러나 엄격하게 구분할 필요는 없고, 두 용어는 같이 사용될 수 있다.
    • 함수 표현식: 이전에 우리는 문자열 값을 let message = "나는 바다코끼리다"형태로 지정했었다. let sayHi = function { }처럼 함수를 변수에 할당하는 것 또한 가능하다. = 이후에 오는 함수를 함수 표현식이라 한다. 함수 표현식은 코드의 한 부분을 나타내는 특별한 값(함수)을 나타내기 때문에, 이후에 원하면 호출할 수 있다.
    • 함수 선언식: 함수를 let sayHi = function() {}처럼 매번 함수 표현식으로 선언하는 것은 귀찮은 일이다. 그래서 function sayHi() {} 같이 함수 표현식보다 짧은 형식을 사용한다. 이것을 함수 선언식이라 한다. 왼쪽에 변수 명을 지정하는 것 대신 function 키워드 뒤에 지정한다. 함수 표현식과 함수 선언식은 대부분 호환된다.
    • 함수 호이스팅: 일반적으로, let이나 const로 먼저 선언이 되어있어야 뒤에서 사용을 할 수 있다. 하지만 이런 특징 때문에 함수가 서로를 호출할 때, 어떤 함수가 다른 함수를 사용할 때, 어떤 함수가 우선적으로 선언되어야 하는지를 추적하는 것은 매우 성가시다. 이런 부분을 편리하게 하기 위해, 함수 선언 구문을 사용할 때만, "호이스팅"되어 순서에 상관없이 사용 가능해진다. 호이스팅은 개념적으로 스코프의 가장 위로 이동된다는 것을 의미한다. 이 경우에는 함수를 호출할 때, 이미 모두 정의되어 있다.
    • this: this는 아마 자바스크립트 개념 중 가장 오해받고 있는 함수의 특별한 인수다. 함수를 사용할 때 직접 this를 넘기지 않는다. 대신, 함수 호출 방식에 따라 자바스크립트 자체가 this를 넘겨줄 것이다. 예를 들어, 점 표기법 .을 사용하여 호출할 경우(iceCream.eat() 처럼) this.앞에 있는 값일 것이다. (예시에서는 iceCream) this는 함수가 어디서 정의되었는지가 아니라, 어디서 호출되었는지에 따라 달라진다. .bind, .call, .apply 같은 헬퍼를 이용하면 this를 좀 더 효과적으로 제어할 수 있을 것이다.
    • 화살표 함수: 화살표 함수는 함수 표현식과 비슷하다. let sayHi = () => { } 같이 선언할 수 있다. 화살표 함수들은 간결하고 한 줄로 표현될 수 있다. 화살표 함수는 일반 함수보다 제한적인 기능을 갖는다. 예를 들어, 화살표 함수는 this의 개념이 없다. 만약 화살표 함수에서 this를 사용한다면, 가장 가까운 "일반" 함수의 this를 사용한다. 이것은 매개변수나 함수에 존재하는 변수만 사용하는 것과 비슷하다. 실제로, 사람들이 화살표 함수를 사용하는 것은 코드상에서 둘러싸고 있는 것을 그대로 "보고" 싶을 때 사용한다는 것을 의미한다.
    • 함수 바인딩: 일반적으로 함수 fthis 값과 인수에 바인딩 하는 것은 사전 정의된 값으로 f를 호출하는 새로운 함수를 만드는 것을 의미한다 .자바스크립트는 .bind라는 빌트인 헬퍼가 존재하지만, 함수를 이용하지 않고 직접 바인딩을 할 수도 있다. 바인딩은 중첩된 함수가 외부 함수와 동일한 this를 "보게"하는 일반적인 방법이었다. 하지만 이런 부분들은 화살표 함수에서 처리되므로, 바인딩은 자주 사용되지 않는다.
    • 콜 스택: 함수를 호출하는 것은 방에 들어가는 것과 같다. 함수를 호출할 때마다, 함수 내부에 있는 변수들을 매번 초기화 한다. 따라서, 함수를 호출하는 것은 코드를 사용해 새로운 "방"을 구성하고 들어가는 것과 같다. 함수의 변수들은 방에 "살아 있게" 된다. 함수가 종료되면, "방"과 모든 변수는 사라진다. 당신은 이런 방을 수직 스택으로 시각화 할 수 있을 것이다. 이것이 콜 스택이다. 함수가 종료되면, 콜 스택의 "아래" 함수로 돌아가게 된다.
    • 재귀: 재귀는 함수가 내부에서 자기 자신을 호출하는 것을 의미한다. 재귀는 같은 동작을 다른 인수로 함수 안에서 다시 반복해야할 때 매우 유용하다. 예를 들어, 웹을 크롤링하는 검색 엔진을 만든다면, collectLinks(url) 함수는 먼저 페이지에서 방문 가능한 링크를 수집한 뒤, 모든 페이지를 방문할 때까지 자체적으로 함수를 호출할 것이다. 재귀의 함정은 계속 자기 자신을 호출하기 때문에 끝나지 않는 코드를 작성하기 쉽다는 것이다. 이 경우 자바스크립트는 "스택 오버플로우"로 불리는 에러와 함께 멈출 것이다. 이 에러가 스택 오버플로우라 불리는 이유는 콜스택에 호출해야 하는 함수가 너무 많이 쌓여 말그대로 넘쳤을 때 발생하기 때문이다.
    • 고차함수(Higher-Order Function): 고차 함수는 다른 함수를 인수로 사용하거나 그것을 반환하여 처리하는 함수이다. 처음에는 이상해 보일지 몰라도, 함수 또한 숫자, 문자열, 객체 처럼 전달 될 수 있는 값이다. 이 스타일은 과용될 수 있지만, 적당히 사용하면 매우 좋다.
    • 콜백: 콜백은 자바스크립트 용어는 아니다. 그것은 패턴에 가깝다. 콜백은 다른 함수의 매개변수로 함수를 넘겼을 때, 먼저 호출된 함수가 종료된 뒤에 넘긴 함수가 호출되는 것을 기대할 것이다. "뒤이어 호출" 될 것을 예상할 것이다. 예를 들어, setTimeout콜백함수를 갖는데.. 시간이 지나면 이 함수를 뒤이어 실행 시켜 준다. 그러나, 콜백 함수는 특별할 것이 없다. 일반적인 함수이며, "콜백" 함수에 대해서는 어떻게 동작할지에 대한 예상만 이야기한다.
    • 클로저: 일반적으로, 함수가 종료되면 모든 변수는 "사라진다". 왜냐하면 더는 선언된 변수가 필요가 없어지기 때문이다. 그러나 함수 내부에 함수를 선언한다면 어떻게 될까? 내부에 있는 함수는 이후에 여전히 호출될 수 있고, 외부함수의 변수에 접근할 수 있게 된다. 실제로 클로저는 매우 유용하다! 하지만 클로저가 동작하기 위해서는, 외부 함수의 변수는 어딘가 "그대로 놓여" 있어야 한다. 따라서 이때는, 자바스크립트는 변수를 "잊어버리지" 않고 "변수를 살려둔 채"로 유지하게 된다. 이것을 "클로저"라 부른다. 클로저는 종종 오해의 소지가 있는 자바스크립트의 기능으로 간주하지만, 당신은 아마 그것을 깨닫지 못한 채 자주 사용할 것이다!

자바스크립트는 이런 여러가지 개념들로 구성된다. 나는 정확한 멘탈 모델을 만들때까지 자바스크립트 지식이 매우 불안했고, 다음 세대 개발자들이 이 격차를 빠르게 따라잡기 위해 돕고 싶다.

이 각각의 주제들에 좀 더 깊게 공부하고 싶다면, 이 사이트를 살펴봐라. Just Javascript는 어떻게 자바스크립트가 동작하는지에 대한 나의 정제된 멘탈 모델이며, Maggie Appleton의 놀라운 시각적인 자료들과 함께한다. 이 글과 다르게 페이스가 느리기 때문에 모든 세부적인 내용을 살필 수 있을 것이다.

Just Javascript는 아직 매우 초기 단계이므로 가공하지 않거나, 수정중인 초안 정도의 시리즈들을 이메일로 받을 수 있다. 이 프로젝트가 흥미롭게 보인다면, 이메일로 무료 초안들을 받아봐라. 당신의 피드백에 매우 감사할 것이다. 땡큐!

Lerna로 mono-repo 만들어보기

1

시작하기 전에

이 글은 FE개발랩 오픈소스 프로젝트 mono-repo 도입을 위해 조사했던 내용을 조금 다듬어 공유용으로 작성한 글입니다.

이미 Mono-Repo를 적용해서 많은 꿀팁을 갖고 계시거나 잘못된 내용이 있다면 댓글 부탁드립니다.

Multi RepoPoly Repo는 같은 의미로 사용되는 단어입니다. 이 글에서는 좀 더 범용적으로 쓰이고 있는 Multi Repo로 통일 시켜 작성했습니다.

Mono-Repo와 Multi-Repo?

2

Mono단일의 의미를 나타내는 접두사입니다. 반대의 의미로는 Multi, 복수일 것입니다. 또한, Repo는 Repository의 줄임말로 저장소를 의미합니다. 두 단어를 합쳐보면 Mono-Repo의 범용적 의미를 알 수 있습니다.

Contains multiple packages or projects Multi-Repo는 여러 저장소에 프로젝트, 패키지를 분산해 두는 것을 의미합니다. 정반대로 Mono-Repo는 하나의 repository(저장소)에 여러 프로젝트, 패키지를 가진 것을 의미합니다.

Mono-Repo의 장점이자 Multi-Repo의 단점

Multi-Repo는 유연성, 배포, 확장이 쉽습니다. 또한, 정책을 정하거나 책임을 명확하게 팀원들에게 맞게 분배할 때 저장소를 분리하는 것이 좋습니다.

하지만, 새로운 프로젝트를 시작할 경우 중복된 설정을 매번 해줘야 하며 오픈소스를 운영할 때에는 이슈가 분산되고 중복되는 코드 배포가 잦을 수 있습니다.

그럼 이제 Mono-Repo의 장점을 하나하나 살펴보겠습니다.

Common config

Multi-repo는 모든 설정을 각각 프로젝트마다 갖게 됩니다. 하지만 Mono-repo에서는 ESLint, Prettier, Babel 등의 설정을 루트 레벨에 할 수 있어 여러 패키지에서 하나의 설정을 공유할 수 있습니다.

1111 <그림> babel 저장소입니다. eslint나 travis, flow 등 여러 설정을 루트에 두고 공유하며 사용하는 것을 볼 수 있습니다.

코드 재사용

또한, 패키지를 넘나들며 모듈화 시켜 사용할 수 있습니다.

3 <그림> babel의 패키지 모듈화입니다. babel의 패키지는 약 150개 정도 존재합니다.

여기서 babel/packages/babel-cli/src/babel/util.js 경로에 들어가 코드를 보면

// ... 다른 모듈 import
import * as babel from "@babel/core";

// ... 코드 코드

// babel 
export function transform(
  filename: string,
  code: string,
  opts: Object,
): Promise<Object> {
  opts = {
    ...opts,
    caller: CALLER,
    filename,
  };

  return new Promise((resolve, reject) => {
    babel.transform(code, opts, (err, result) => {
      if (err) reject(err);
      else resolve(result);
    });
  });
}

@babel/core 의존을 가져와 사용할 수 있습니다.(다음처럼 다른 패키지의 의존을 가져와 동작하게 하는 것은 yarnlerna를 사용한 workspaces로 쉽게 사용할 수 있습니다.)

4

간단하게 만들어본 Mono-Repo의 예시입니다. 공통으로 사용하는 core, util, routing 코드들을 각 패키지로 분리하고 이 패키지를 이용해 하나의 어플리케이션을 만들 수 있습니다. 또한, 이 core, util, routing은 이 자체로도 의미가 있어 외부에서 사용을 할 수 있도록 배포될 수 있습니다. 마치 TOAST UI의 tui-code-snippet, tui-dom 처럼요!

One PR

퀴즈를 하나 내보겠습니다.

TOAST UI Application + TOAST UI Component + React Wrapper + Vue Wrapper 의 갯수는 몇개일까요?

답은 41개입니다. 얼마 전 실제로 있었던 일입니다. 사명이 NHN Entertainment에서 NHN으로 변경되었을 때 사명이 변경된 것을 npm에 반영시키려면 41번의 배포가 필요했습니다.(repository의 변경은 조금 더 뒤에 있었으니 더 많은 횟수의 배포가 있었습니다.) 물론, 다른 오픈소스들에 비해 많은 개수는 아니지만 그래도 적지 않은 갯수이기는 합니다.(1000개 이상의 모듈을 npm에 등록하신 분도 존재합니다.) 적어도 TOAST UI의 각 어플리케이션에 래퍼들을 통합시켜 Mono-Repo 형태를 만들어준다면 10개의 저장소 관리 이슈가 줄어들고 하나의 PR로 많은 것을 한 번에 관리할 수 있게 될 것입니다.

TOAST UI Grid는 지금 어떻게 하고있나?

8 TOAST UI Application은 지금 multi-repo의 형태를 띄고 있습니다. TOAST UI Grid를 예로 들면(나머지 Application 또한 마찬가지입니다.) tui.grid, toast-ui.vue-grid, toast-ui.react-grid 세개의 저장소로 분리되어 있습니다.

관리의 불편함

  1. 🌟이슈의 분산🌟
    • 이슈가 분산되는 것은 가장 큰 문제였습니다. TOAST UI Grid를 vue-wrapper를 통해 사용할 때 문제가 생긴다면 어느 저정소에 이슈를 올려야 하는지 사용자는 충분히 헷갈릴 수 있습니다.
    • 또한 지켜봐야 할 저장소가 많아질 수록 빠른 대응이 힘들어지는 것 또한 현실이었습니다.
  2. 매번 프로젝트를 생성할 때마다 정적분석도구나 스타일 규칙을 매번 설정해줘야 했습니다.(Prettier, Eslint, Cypress, storybook...)
  3. 의존을 갖고있는 레포를 수정했을 때 모두 한꺼번에 수정하여 배포하기에 어려움이 있었습니다.

이런 불편한 점은 정확하게 mono-repo의 장점과 매칭되었습니다.

Mono-Repo의 필요성과 장점들을 간단하게 알아봤으니 저장소 구성을 도와주는 한 오픈소스를 소개해보겠습니다.

Lerna 란?

5 <그림> Lerna의 히드라. Lerna의 상징입니다. 🐉

Lerna는 러나 혹은 레르나로 읽힙니다. 한국에서는 레르나로 더 많이 읽히는데요. (로고의 히드라가 궁금하시면 위키를 참고해주세요.) Lerna는 단일 저장소(Repository)에서 다양한 packages를 구성하는 것을 도와주는 라이브러리입니다. 프로젝트 전체를 빌드하거나, 테스트 할 때 변경이 있는 패키지들을 배포할 때 도움을 줍니다.

Lerna로 무엇을 할 수 있나?

Lerna에서 공식사이트에서 언급하기로는 lerna publish, lerna bootstrap 두가지가 주요 기능이라고 합니다. 본격적으로 사용한 것은 아니지만 이외에 많은 기능을 한번에 제공하고 있습니다. 이제 여러 커맨드들도 같이 보도록 하겠습니다. 구체적인 커맨드 설명이나 여기 등장하지 않는 커맨드가 궁금하다면 다음 링크를 확인하세요.

lerna init

$ lerna init
$ lerna init --independent

해당 프로젝트에 lerna repo를 만들거나 최신 버전으로 업데이트를 할 때 사용합니다. --independent 옵션을 붙일 경우 패키지들의 버저닝 정책을 독립으로 가져갑니다.

lerna version

$ lerna version 1.0.1  # explicit
$ lerna version patch # semver keyword
$ lerna version           # select from prompt(s)

이전 배포 버전에서 변경이 존재하는 pacakge들의 버전을 변경시켜줍니다. semver 키워드를 입력시키거나 버전을 명시해주거나, 선택해서 적용시킬 수 있습니다.

lerna diff

$ lerna diff [package]

해당 패키지의 지난 배포 이후 변경점에 대한 내용을 보여주게 됩니다.

lerna bootstrap

$ lerna bootstrap

lerna bootstrap을 통해 각 패키지들의 의존을 설치하고 cross-dependencies를 연결, 재정비 해줍니다.

lerna run

$ lerna run <script>
$ lerna run test
$ lerna run build

# my-component 패키지만 test
$ lerna run --scope my-component test

각 패키지들의 npm 명령어를 실행합니다. scope를 지정할 수 있으며 scope를 지정하지 않을 경우 전체 패키지에 명령어를 수행합니다.

lerna publish

$ lerna publish

프로젝트에서 지난 릴리즈 이후 변경이 있었던 패키지를 배포합니다.

lerna clean

$ lerna clean

각 패키지들의 node_modules 폴더를 삭제합니다. workspaces를 사용하여 초기 설정을 구현할 때 사용하기 좋습니다.

lerna link convert

$ lerna link convert

lerna link convert를 통해 각 패키지의 devDependencies를 루트에서 관리할 수 있습니다.

6 <그림> lerna link convert 전 package.json

7 <그림> lerna link convert 후 package.json

Mono-Repo 적용 해보기

각 커맨드들을 간단하게 살펴봤습니다. 어떤 역할을 해주는 라이브러리인지 느낌이 오시나요? 그럼 한번 간단하게 적용해 보겠습니다.

독립버전

lerna를 설치하고 independent로 프로젝트를 구성했습니다.

$ lerna init --independent

lerna.json 파일은 lerna init을 한 상태 그대로 입니다.

{
  "packages": [
    "packages/*"
  ],
  "version": "independent"
}

각 패키지들의 경로가 어떻게 되는지, version은 어떻게 가져가는지를 나타내는 설정 파일입니다.

폴더 수정하기

9

packages 폴더에 각 패키지들을 위치 시킵니다. 이때 공통으로 묶을 수 있는 docs 파일이나 설정파일들을 root로 위치 시킵니다.

배포하기

10

npm publish를 하게되면 보게 되는 화면입니다. CLI를 통해 어떤 버전을 배포할 것인지 각각 패키지들 별로 버전을 지정해 줄 수 있습니다.

릴리즈 노트의 형태

11

독립버전 배포후 태그의 형태는 배포된 패키지의 이름@버전의 형태입니다. lerna publish를 이용하면 자동으로 태그를 만들어 줍니다.

bower 사용하기

bower를 제공하는 방식은 동일합니다.

12

설정 파일을 packages의 어플리케이션의 번들파일 위치로 변경해주면 됩니다. 다만 bower의 기준은 프로젝트의 루트이며 여러 패키지를 각각 bower로 배포하는 것은 어려워 보입니다.(방법이 존재한다면..댓글 부탁드립니다ㅠㅠ)

통합버전

independent 옵션을 사용하지 않으면 이전과 다르게 버전에 숫자가 생기게 됩니다. 이 형태는 기본적으로 semver를 따릅니다. 이 옵션이 전체 package들의 통합 버전을 나타내게 됩니다.

{
  "packages": [
    "packages/*"
  ],
  "version": "0.0.0"
}

배포하기

이전 과는 다르게 변화가 존재하는 전체 패키지들을 버전에 맞춰 한번에 배포합니다. 13

릴리즈 노트 또한 이전처럼 하나로 관리되게 됩니다. 14

만약 react wrapper에 변화를 주고 배포를 한다면 변화가 있는 패키지만 배포되게 됩니다.

15

yarn workspace를 통한 code sharing

환경 설정

  • lerna.json
{
  "packages": [
    "packages/*"
  ],
  "version": "0.0.0",
  "npmClient": "yarn",
  "useWorkspaces": true
}

각 pacakge의 코드를 공유하기 위해서는 yarn을 npm client로 지정해야 하며 useWorkspaces 옵션을 true로 지정합니다.

  • root packages.json
{
  "workspaces": [
    "packages/*"
  ]
}

최상단 packages.json에는 workspaces의 위치를 명시해줍니다. 이후 해당 프로젝트를 상단에서 사용해주기 위해 $ lerna bootstrap 으로 의존을 정리해 줍니다.

yarn workspace를 사용하게 되면 각 패키지에서 사용하는 노드 모듈들을 중복으로 설치하지 않고 상단에서 관리할 수 있게 됩니다.

16

중복으로 사용하는 모듈 외에 파일들은 각 패키지들에 위치하여 사용됩니다. 해당 의존을 package.json에 명시하고 사용하면 정상적으로 동작하는 것을 볼 수 있습니다.

오픈소스들의 버전 정책

만약 Mono-Repo를 도입한다면 가장 크게 고려할 점은 버전 정책일 것입니다. 각 오픈소스들은 각자의 서비스의 특성에 맞게 버전 정책을 정한 뒤 따르고 있습니다. 몇 가지 오픈소스들을 통해 사용할 수 있는 버전 정책을 검토해 볼 수 있을 것 같습니다. 각 오픈소스들의 버전 정책과 릴리즈 노트는 어떻게 작성되고 있는지, 그리고 어떤 정책을 선택해 볼 수 있을지 정리해봤습니다.

babel/babel

먼저, 모노레포를 가장 열심히 적용하고 있는 바벨입니다.

버전 정책은?

바벨은 한가지 메인버전을 두고 커밋 디프가 존재하는 패키지를 배포합니다. 메인 버전이 하나 존재하며 배포가 있는 패키지는 그 버전을 따라갑니다.

17

위 그림을 보면 babel-node7.4.5 버전이 나갔을 때의 diff 입니다. 버전이 바로 7.2.2에서 7.4.5 로 뛴 것을 볼 수 있습니다. 각 패키지들을 독립된 프로젝트로 본다면 semver를 지키지 못한다고 볼 수 있습니다. 하지만, 하나의 큰 프로젝트라 생각한다면 매우 편한 정책이라 볼 수 있습니다.

릴리즈 노트는?

18

변경이 되는 내용과 영향을 받은 패키지들을 나열합니다.

facebook/react

버전 정책은?

babel과 비슷한 정책을 추구합니다. 구체적인 정책은 알 수 없지만 주요한 기능이 되는 패키지들은 하나의 메인 버전을 따라갑니다. 독립적으로 버전을 주는 패키지들 또한 따로 존재 합니다.

릴리즈 노트는?

19

변경이 되는 내용과 영향을 받은 패키지들을 나열합니다.

facebook/jest

버전 정책은?

20

react, babel과 유사합니다. 아무래도 같은 계열의 프로젝트이기 때문이겠죠?

  1. 모든 패키지들이 버전을 따라가지는 않는다.
  2. 당시 업데이트가 있는(commit에 diff 가 있는) 패키지만 업데이트 하며 버전은 lerna.json을 따라간다.
  3. patch 버전이 올라가는 경우는 거의 없으며 주로 minor 버전이 증가합니다.
  4. major 버전이 빠르게 증가하는 이유도 이때문인 것 같습니다.

릴리즈 노트는?

깃헙에는 작성하지 않습니다. 마이너 버전, 패키지 단위의 업데이트는 따로 작성하지 않는다. 메이저 버전의 업데이트는 공식 사이트의 Blog 에 작성합니다.

airbnb/enzyme

react adapter를 react의 버전별로 관리하기 때문에 래퍼들을 관리하는 것과 비슷합니다.

버전 정책은?

independent 모드를 사용합니다. 각 패키지 별로 독립된 버전을 갖습니다. 21

릴리즈 노트는?

22

깃헙에는 작성하지 않습니다. 공식 사이트에 Change Log에 변동 사항을 각각 어떤 변동이 있는지 정리합니다.

가능한 버전 정책을 정리해보면?

23 <그림> 그림은 Application에 래퍼를 적용했을 때의 예시입니다. 각각을 패키지로 봐주시면 이해하기 좋을 것 같습니다.

세 가지 형태의 모델이 가능할 것 같습니다. 메인 버전을 두고 변화가 있는 패키지만 배포하는 모델, 전체를 모두 배포하는 모델, 각각의 패키지가 독립된 버전을 가져가는 모델입니다. 세 가지 모델은 프로젝트를 어떻게 구분하고 바라보는가에 따라 다를 것 같습니다.

첫 번째 모델은 lerna의 기본 모델입니다. 각 pacakge에서 변화가 있는 브랜치만 업데이트하는 모델입니다. 두번째 모델은 변화가 있는 브랜치에 맞춰 버전을 같이 올리는 경우인데 이 모델을 사용할때는 주로 의존을 갖고 있는 패키지의 package.json 파일에 업데이트 되는 버전의 패키지 버전을 명시해 변화를 줘 같이 업데이트 하는 방식을 따릅니다. 세번쨰 모델은 independent로 변화가 존재하더라도 각각 배포하게 되는 형태입니다.


준비한 내용은 여기까지 입니다. TOAST UI는 Mono-Repo 도입을 준비 중이고 결과적으로 어떤 장단점을 얻는지 다시 한번 정리하여 이후에 공유하도록 하겠습니다. 읽어주셔서 감사합니다. 😃

참고

2018년 NHN BASECAMP 회고

이 글은 2018년 NHN 신입 공채로 입사 후, 받은 기술 교육을 회고하는 글이다. 어떤 교육 과정을 거쳤고 이 교육이 내가 개발자로서 시작하는 데 어떻게 도움이 되었는지를 적었다. 1년이 지난 시점이라 기억이 명확하게 나지 않는 부분들이 있지만 그만큼 시간이 지났으니 교육이 정말로 지금의 나에게 도움이 되었는지 객관적으로 되돌아볼 수 있었다.

토스트 루키?

‘토스트 루키’는 NHN 신입 공채로 입사하게 된 신입 사원을 부르는 명칭이다. 2019년 현재 6기까지 진행되었으며 소프트웨어 개발, IT 보안, IT 인프라 분야로 선발한다. 기나긴 공채 과정을 거쳐 입사가 확정된 루키들은 사내 교육 과정인 ‘베이스캠프'를 거친 후 부서에 배치된다. 루키들은 각 부서에서 클라우드, 미디어, 검색, 서비스 개발뿐만 아니라 IT 인프라 구축과 운영, 보안 시스템 개발 및 자동화 등의 업무를 담당하게 된다.

베이스캠프?

루키본진.jpeg <그림1> 베이스캠프 입구

‘베이스캠프(base camp)’는 NHN 신입 사원을 위한 개발 교육 과정이다. 베이스캠프의 사전적 의미는 장거리 등산이나 탐험을 할 때 여행에 필요한 물자를 제공하거나 다음 여행을 준비할 수 있는 곳이다. 단어의 뜻처럼 교육 운영진들은 신입 사원들의 앞날을 높은 산에 올라가는 것으로 봤다고 한다. 교육생들이 앞으로 더 많은 개발 지식을 쉽게 익히는 데 도움이 되도록 준비해주는 교육이란 뜻에서 만든 이름이라고 한다. 이름처럼 회사 생활과 개발자로서 나아가는 데 필요한 준비물을 가방 가득 채워준다. 아주 꽉꽉 넣어준다.

2018년 1월 10일부터 3월 9일, 총 9주간의 교육이었다. 9주 교육? 처음에 들었을 때는 교육 기간이 길어 보일 수 있다. 나도 처음엔 그랬다. ‘여유롭게 적응하면서 진행하겠지….’ 하는 착각은 단 1주일이면 깨지게 된다. 9주는 여유롭지 않았고 길지 않은 시간이었다. 베이스캠프 교육에는 기승전결 구성이 존재하고 루키들을 모두 혼란에 빠뜨린 반전 또한 존재한다. (이 내용은 여기에 작성할 수 없었다. 궁금하다면 직접 들어와서 확인해 보기 바란다.)

멘토님 첫만남.JPG <그림2> 멘토님들과 첫 만남

교육은 처음부터 끝까지 루키 5명, 멘토 2명 총, 7명이 한 팀이 되어 진행한다. 멘토님들은 현업에서 일하고 계시는 시니어 개발자들이다. 함께 프로젝트를 진행하며 교육 방향과 개발 상황을 검토해 주신다. 멘토 제도의 가장 큰 특징은 멘토님들이 멘티들의 궁금증이나 어려움에 대한 정답을 내려 주지 않는다는 것이다. 대신 고민 해결을 위한 올바른 방향 정도만을 제공하여 루키들이 고민하고 문제를 해결하는 연습을 시킨다.

‘무엇을 했는가?’ 물어본다면 짧게 대답할 수 있다. 하나의 웹 서비스를 만들고 운영한다. 하지만 다시 무엇을 했는지 물어본다면 짧게 대답할 수 없다. 교육 과정에는 교육과 과제가 늘 함께했다. 우리가 개발하는 서비스 위에서 회사의 규칙을 배우고 적용과 적응을 해나갔다. 실제로 우리가 만나야 할, 만날 수 있는 상황들을 미리 체험해보고 대처했다. 또한, 개발뿐만 아니라 네트워크 기초, OAuth, Bash 활용 같은 관련 지식을 배우거나 기술 서적 읽기, 블로그 정리 등을 함께한다. 모든 것이 끝나고 되돌아봤을 때 우린 정말 많은 것을 했었고 정신없이 짧은 시간에 회사를 이해하게 된다.

다시, 무엇을 만들었는가?

주제는 ‘그룹웨어(메일 서비스)’ 만들기였다. SMTP 같은 프로토콜을 별도로 사용하지 않고 만든 서비스 내에서만 쓰고 읽기가 가능하도록 개발했다. 서비스를 기획하고 작동하도록 개발하는데 2주, 서비스를 확장, 운영하는 법을 배우는데 약 7주를 사용하게 된다.

기획하고 개발하기

약 2주간 서비스를 기획하고 디자인하고 개발한다. 한 명은 프런트엔드와 백엔드 개발 중 한 가지만을 담당할 수 없다. 두 일을 모두 해야 한다. 기본 기능을 제외한 모든 추가 기능은 우리가 결정하게 된다. 첫 주는 스펙을 정의하고 역할을 분배한다. 우리 손으로 만든 기획서를 가지고 약 10일 동안 서비스를 만들게 된다. 이 시기에 멘토님들의 관여는 거의 없다. 기획 내용을 공유드리고 들었던 조언은 ‘시간 내에 될까요?’가 주된 내용이었다. 우린 개발 서버를 세팅하고 DB 설계, 메일 CRUD, 회원관리와 추가로 기획했던 내용을 붙이게 된다. 이때 완성된 사이트와 교육이 종료되었을 때를 비교해보면 기능은 많이 다르지 않다. 하지만 UI와 서비스가 작동한다는 공통점 외에는 말 그대로 "모든 것이" 달라진다.

클린 코드 만들기

2주간 개발한 사이트를 시연한 후 코드 리뷰와 정적 분석 결과를 리뷰 받게 된다. 먼저, 우리의 코드를 하나의 사례로 안티 패턴들을 교육받게 된다. 사용하지 않는 코드, 좋지 못한 이름, 일관성 없는 코드 스타일, 잘못 사용한 예외처리, 한 메서드에 로직이 얽혀있는 등 유지 보수하기 어려우면서 읽기 힘든 코드들이 주로 언급된다.

코드리뷰.JPG <그림3> 코드 리뷰

동시에 한 조씩 차례대로 코드 리뷰를 받게 된다. 우리 조는 한 개의 컨트롤러 파일에 서비스의 모든 기능이 들어간 1000줄짜리 코드를 가지고 리뷰에 들어갔다. 사용하지 않는 방치된 코드, 쓸데 없이 구체적인 주석, 모든 기능이 한곳에 모인 결과였다. 사실 코드 리뷰에 들어간 시점에는 코드의 문제점을 인지하지 못했다. 누군가 좋은 코드란 무엇인가 물어봤을 때 ‘읽기 좋은 코드가 좋은 코드입니다' 라고 말해왔었던 자신이 무엇이 읽기 좋은 코드인지 몰랐던 것이다. 코드를 어떻게 작성하는 것이 간결하고 확장성 있는 코드인지, 코드 중복을 어떻게 해결할 수 있는지, 잘못 잡은 구조는 무엇이 있는지 타당한 결과를 받는다.

이 시기에 테스트 주도 개발(Test-Driven Development) 교육도 함께 받게 된다. 테스트 주도 개발은 짧은 개발 사이클을 반복하는 개발 프로세스이다. 먼저 개발자가 바라는 함수의 초기 결함을 증명하는 테스트 코드를 작성한다. 그다음 테스트를 통과하기 위한 최소한의 코드를 만든 뒤 표준에 맞춰 리팩토링을 한다. 우리는 JUnit을 이용했다.

이날 이후 우린 수치와의 싸움이 시작된다. 테스트 코드 라인 기준 커버리지 70% 이상, klocwork로 정적 분석을 했을 때 심각도를 나타내는 수치인 critical과 error를 모두 0으로 유지해야 했다. 시작할 때의 커버리지는 0이었고 정적 분석 결과는 매우 안 좋았다. 배웠던 그대로 테스트를 먼저 작성한 뒤 테스트를 통과하도록 기존 코드를 개선했다. ‘공격적인 리팩토링'을 경험할 수 있었다.

상받은거.jpeg <그림4> 가끔보면 흐뭇한 상

결과적으로 우리 조는 교육 동안 가장 높은 코드 커버리지와 좋은 정적 분석 수치를 유지하여 품질보증 상을 받았다. 당연하게도 높은 커버리지와 정적 분석의 좋은 수치가 좋은 코드를 의미하는 것은 아니다. 하지만 코드 개선을 통해 초기에 잘못 작성한 코드가 서비스를 확장해나가는 데 있어서 큰 걸림돌이 될 수 있다는 것을 배울 수 있었다.

데이터베이스 검수받기

서비스 성능에 가장 큰 영향을 미치는 부분이 무엇이냐고 묻는다면 데이터베이스 얘기를 빼놓을 수 없을 것이다. 데이터베이스 모델링을 잘못하게 되면 이후에 확장하거나 불가피하게 구조를 변경할 때 너무나도 많은 시간이 걸릴뿐더러 코드 모든 곳에 영향을 주게 된다. 잘못된 쿼리 하나로 서비스 전체에 악영향을 줄 수도 있다. 이처럼 중요함을 알지만 데이터베이스는 나에게 부족하고 어려운 분야였다.

첫 개발이 완료될 즈음 사내 데이터 운영팀을 통해 구축한 모델을 검수 받게 된다. 우리가 작성한 모델을 기준으로 데이터 모델링의 절차, 구조, 표준화 및 관리적 관점에서 무엇이 중점이 되어 검토되는지, 이유는 무엇인지 교육받는다. 또한, 제출했던 모델을 보완해서 어떻게 작성하는 게 좋을지 비교하면서 교육받는다. 교육 후반부에는 작성한 쿼리 중 비효율적으로 작성한 쿼리들을 뽑아 검수를 받게 된다. 검수 결과를 공유 받으면서 쿼리를 어떻게 작성하면 좋은지, 인덱스를 사용하는 기준, 주의사항 등 어떻게 작성하면 좋은지를 제출한 쿼리들을 기반으로 교육받게 된다. 전공 시간에 배웠던 내용에서 확장되어 현업에서 중요시되는 내용과 효율적인 모델링이 무엇인지 배우게 된다.

사내 서비스, 인프라 경험하기

NHN은 자체 IDC(Internet Data Center)를 운영하고 있으며 토스트라는 자체 클라우드 플랫폼 또한 갖고 있다. 현업에서도 이를 적극적으로 활용하여 개발하게 되는데 교육 또한 회사가 가진 인프라나 서비스를 적극적으로 활용하는 방향으로 진행된다. 토스트 클라우드의 Object Storage를 파일 저장 용도로, Deploy를 배포 자동화 목적으로 사용하면서 우리 회사의 서비스를 직접 경험한다. 그 외에 프런트엔드 서비스인 토스트 UI의 애플리케이션을 활용하여 서비스 UI를 구성했다. 처음 회사에 들어와 교육을 받을 시점에는 우리 회사가 어떤 기술, 인프라를 가졌는지 대부분 모르는 상태에서 시작한다. 우리가 가진 기술들을 사용자 관점에서 활용하여 서비스를 구축한다. 이 과정에서 좋았던 점, 불편했던 점은 부서에 배치된 뒤 서비스를 개발할 때 사용자 관점에서 개발할 수 있는 자세를 가지게 하여 더 나은 방향으로 서비스를 개선하는데 큰 도움이 된다.

갑자기 분위기 스케일 아웃?

코드 리팩토링을 마치고 한숨 돌릴 수 있겠지라고 생각할 무렵 서버를 2대로 늘려야 하는 과제가 등장했다. 스케일 아웃이 어떤 개념인지 알뿐 한 번도 직접 경험해보지 못했었다. 교육이 시작하기 전 우리는 ‘서버가 늘어날 경우에 두 서버 간 세션 관리는 어떻게 할까?’, '어떤 기준을 두고 두 서버를 나누고 관리할까?’ 같은 질문들을 나열하고 어떻게 해결할지 우리 나름의 답을 내린다. 물론 그게 정답일 가능성은 거의 없다.

교육1.JPG <그림5> 교육 시작 전 작성했던 스케일 아웃 방식

그림은 당시 교육 시작 전 우리가 생각했던 이슈와 해결책을 정리했던 내용이다. 당시에 로드 밸런서를 두어 어떻게 두 서버로 어떻게 분산을 할지, 세션을 어떻게 공유할지 등 고민했었다. 다른 조 또한 우리 조처럼 각자 조에서 개발할 때 발생할 수 있는 이슈와 그에 대한 나름의 정답들을 정리한다.

그리고 정리한 내용을 다른 조의 동기들에게 공유한다. 동기들은 우리들의 생각을 듣고 어떤 점이 좋았고 어떤 점이 맹점인지 같이 토론하는 시간을 가진다. 이 과정에서 어떤 점을 고려 못 했는지, 같은 이슈를 해결하는 다른 방법들을 듣게 된다. 공유하는 자리가 마무리된 뒤 교육은 시작된다.

교육2.JPG <그림6> 동기들과 작성한 내용을 공유

교육을 통해 우리의 해결책 중 잘못된 부분과, 정답에 가까웠던 부분을 확인할 수 있다. 교육을 끝난 뒤 우리는 리얼 서버를 추가한 뒤 로드 밸런서를 두고 Redis를 이용해서 세션 관리를 하였다. 서버에 장애가 생겼을 때 서비스를 이용할 수 없었던 과거와 달리 대비할 수 있는 고가용성을 갖게 했다.

웹 스케일 아웃 교육뿐만 아니라 다른 교육도 대부분 같은 과정을 거치게 된다. 이 과정에서 배우게 되는 지식 또한 큰 도움이 되지만 함께 고민해보고 해결책을 직접 내보는 과정은 큰 도움이 되었다고 생각한다. 일하는 동안 매일매일 새로운 문제점들을 만나고 문제점을 해결해 나간다. 교육 기간 동안 실전에 임하는 자세로 문제를 해결했던 경험은 현업에 들어가 문제를 해결하는 좋은 자세를 갖게 했다.

이후 DB 확장에 대한 교육을 받는다. 마스터-슬레이브 구조, 클러스터링과 샤딩의 특징을 각각 구분해서 무엇을 기준으로 분산하는지, 그 분산하는 기준이 무엇이 되는지 교육받는다.

안정적으로 운영하기

교육이 막바지로 갈수록 안정적으로 서비스하기 위한 작업을 시작한다. 성능 테스트의 목적과 목표를 어떻게 잡고 테스트하는지를 교육받는다. 사내 모니터링 시스템 또한 세팅했다. 문제가 발생한 뒤에 해결하는 것 또한 중요하지만, 전조 현상을 미리 보는 것 또한 중요하다. 서버 메모리, CPU 사용량, 로그 등을 보거나 문제가 발생했을 때 자동으로 알림을 주는 사내 시스템을 연동했다. 또한, 서버에 장애가 발생하여 종료 후 재구동을 해야 하는 상황에 있을 때 자동으로 재구동을 위한 스크립트 또한 이 시기에 작성한다. 이 이후 문제가 실제로 발생했을 때 사내 장애가 발생한 프로세스를 배우고 그대로 실행했다. 장애가 생기게 되면 전사 대상의 장애 관리 게시판에 언제 어떻게 발생했음을 알리고 이후에 문제가 해결된 뒤 원인을 공유한다. 이러한 과정은 개인 프로젝트를 통해 배울 수 없는 중요한 내용이었다.

이때부터 서비스는 무중단 배포를 하게 된다. 무중단 배포는 서비스를 중단하지 않고 배포하는 방식이다. 처음에는 꾸준히 중단되는 무중단 배포를 경험했지만, 이후에 여러 경험을 통해 적응되었을 때부터 서비스 사용에 영향이 가지 않게 무사히 배포할 수 있었다.

마지막 발표 그리고 서비스 보내기

9주간 만들었던 서비스를 전사를 대상으로 공개한다. 발표 전 전사 대상으로 우리가 만든 서비스와 개인 블로그가 함께 메일로 전달된다. 회사의 모든 직원들은 우리가 만든 서비스를 체험해 보기도 하고 9주간 정리했던 블로그 글들을 구경한다. 그리고 루키 선배, 부서 선배들을 대상으로 만든 서비스를 시연하면서 발표한다. 선배들은 현업을 하는 것처럼 서비스에 대한 피드백을 꼼꼼히 해주면서 어떤 부분이 좋았고 어떤 부분이 보강되었으면 좋겠는지 의견을 준다. 이런 부분들이 ‘교육은 이제 끝이 아니라 시작이다’라는 느낌을 줘서 매우 좋았다. 발표가 끝나면 실제로 서비스가 종료되었을 때처럼 서버를 반납 처리하고 사용하던 깃헙 저장소 등을 반납하게 된다. 그 뒤 우리는 부서로 배정받게 된다.

배웠던 것 4가지

첫 번째, 질문하는 법

대학교 때는 질문하는 법에 대해 크게 고민해본 적이 없을 것이다. 대부분 선배, 동기들에게 질문할 것이고 서로 어떻게 질문하는지는 크게 중요하지 않을 것이다. 궁금증을 해결하기 위해 계속 질문했었을 것이고 답했을 것이다. 하지만 회사에서는 상황이 조금 다르다. 질문하는 대상은 주로 선배 개발자분들이 될 것이고 대학교 때 보다 훨씬 더 상황에 따른 어려움일 것이다. 명료하지 않은 질문은 동료, 선배의 시간을 뺏는다.

베이스캠프를 진행하다 보면 어려움을 계속해서 만나게 된다. 모두 생소하고 처음 해보는 것 투성이기 때문이다. 어려운 상황들은 이어지고 문제는 계속 생기게 된다. 문제를 해결하기 위해 구글링을 하고 찾은 답이 맞는지 확인받고 싶어진다. 구글링을 통해 답을 알아내지 못했을 경우는 내가 뭘 모르는지도 모르는 상태가 되어 고구마를 백 개 정도 먹은 것처럼 답답하게 된다. 이럴 때 멘토님들을 찾아가 질문하게 된다면 질문이 아니라 하소연을 하게 되며 원하는 대답 또한 못 얻게 된다. (물론 질문할 때마다 멘토님들은 바보 같은 질문에도 꼼꼼하게 같이 고민해주셨다)

‘고무 오리 문제 해결법’이란 기법이 있다. 질문하기 전 고무 오리에게 내가 처한 문제에 관해 설명해 해결하는 기법이다. 누구나 그런 경험이 있을 것이다. 누군가에게 질문하다가 ‘아 이렇게 하면 되겠구나!’ 라고 깨닫는 것이다. 고무 오리 문제 해결법은 이런 바보 같은 실수를 줄인다. 하는 법은 단순하다. 문제를 고무 오리에게 설명한다. 이 과정에서 내 질문의 문제점을 파악하고 명료하게 한다. 문제점을 명료하게 노력하다 보면 대부분 엉뚱한 부분에서 접근하고 있다는 것을 깨닫고 이 단계에서 문제를 해결하게 된다. 그런데도 해결되지 않는다면 질문한다.

질문하기 전에 세 가지를 정리하는 습관을 지니려고 노력했다. 먼저 문제를 명료하게 한다. 그리고 내가 이해하지 못했던 부분, 궁금한 점을 명확하게 한다. 그 궁금한 점을 알기 위해 어떤 노력을 했고, 어떤 것을 알게 되었는지 정리한다.

이렇게 질문하면 내가 궁금했던 점을 더 빠르게 배울 수 있고 동료의 시간 또한 덜 빼앗게 된다. 현업에 투입되기 전 미리 질문하는 법에 대해 고민하고 연습하는 시간을 갖게 되어 좋았다. 아직 노력해야 하지만 지금도 세 가지를 정리하고 질문하는 습관을 지니려 노력하고 있다.

두 번째, 공유하는 법

기술 교육이 시작하기 전 나는 알고 있는 것을 열심히 공유하기로 마음먹었었다. 몇 가지 이유가 있었지만 가장 솔직한 이유는 내가 아는 것을 많이 공유하면 동기들도 알고 있는 것들을 많이 공유할 것 같았다. 9주라는 시간 동안 서로의 지식을 공유함으로써 최대한 많은 것을 빠르게 배우고 싶었다. 나는 사소한 것부터 공유를 시작했다. 내 에디터 설정 공유였다. 이후에는 과제를 하면서 사용하게 되는 사내 오픈소스 사용법이나 번들 파일 공유, gzip, minify 같은 성능 개선 등 내용을 공유했다. 문제를 해결하는 과정에서도 많은 것을 배우지만 내용을 공유하기 위해 준비하는 과정에서 더 많은 것을 알게 된다고 생각한다. 많은 것을 공유할수록 우리는 시행착오를 겪는 시간이 줄게 되고 많은 것들을 빠르게 배울 수 있게 된다. 동기들 또한 교육 과정에서 미리 해결했던 과제나 삽질했던 후기들을 정리해서 올렸다. 동기들이 블로그에 쓴 글과 공유 프로젝트에 올라온 글들은 교육이 종료될 때쯤에 100개 이상이 되었고 이 과정에서 정말 많은 것을 짧은 시간 내에 흡수할 수 있었다. 서로 알고 있는 것을 동기들과 서로 나누려고 하고 나눈 내용이 내 것이 되었고 이렇게 되면서 모두 다 같이 한 번에 성장할 수 있게 되었다.

9주간의 교육은 빠듯했고 교육받은 내용을 모두 이해하기는 힘들었다. 하지만 블로그에 정리하는 과정을 통해 배웠던 내용을 회고해 보면서 한 주를 정리하는 데 큰 도움이 되었다. 이렇게 작성한 내용 중에 현업에 들어갔을 때 다시 한 번 궁금한 내용은 정리한 내용을 보며 참고하고 있다. 또한, 다른 사람들이 정리한 글들을 보고 비교해 보면서 어떤 점을 놓쳤고 서로 어떤 점을 중요하게 생각하는지 공부하는 시간이 되었다.

공유하는 회사 문화

공유에 대해 관심을 두다 보니 회사 내에서도 공유할 수 있는 자리와 시스템이 많이 갖춰져 있다는 것을 알게 되었다. ‘NHN’이란 회사에서 공유라는 키워드는 매우 중요한 의미를 갖는 것 같다. 기술 공유 게시판이나 외부 사람들을 대상으로 하는 기술 교육, 콘퍼런스 등이 자리 잡혀있었고 기술 교육 또한 매우 활발하게 운영되고 있었다.

크게 봤을 때 블로그 작성이나 기술 공유 게시판에 글 작성하기 과제는 회사의 중요한 문화인 ‘공유’에 적응하는 훈련이 있었던 것이다. 1년이 지난 지금 동기들은 사내 게시판, 회사 기술 공유 블로그에 꾸준하게 시행착오나 배운 내용을 공유하고 있다. 루키만을 대상으로 하던 공유를 회사 모든 직원뿐만 아니라 대외로 공유한다. 이러한 노력을 통해 누군가는 쉽게 문제를 해결하고, 어려운 내용을 쉽게 배울 것이다. 공유하는 연습은 결국 나 자신뿐만 아니라 내가 속한 기술 생태계를 발전시키기 위한 연습이었다고 생각한다.

세 번째, 같이 일하는 법

우린 일주일 동안 얼마나 할 수 있을까?

대학교 때는 주로 혼자 개발을 하게 된다. 대부분 공부 목적의 개발이기 때문에 빠듯한 일정이라는 것을 생각해 보지 않는다. 하지만 회사에서는 이야기가 달라진다. 내가 오늘 하루 어느 정도의 일을 진행할 수 있는지 매일매일 생각해야 하고 일정을 지켜야 한다. 프로젝트를 처음 시작하고 나서 기획을 시작할 때 우리는 운영진에게 주제와 이 문장을 받게 된다. ‘5명이 7일간 개발할 분량으로 기획합니다.’ 이때의 난 1일간 개발할 분량이란 것을 생각해 본 적이 없었다. 처음부터 일정을 생각하고 기능을 제한하지는 않았다. 머릿속에 갖고 있던 서비스 의견을 나누고 기능들을 나열했다. 기능들을 나열해 봤을 때 전부 1주일 동안 개발할 수 있을 것 같았었다. 처음 느낌을 믿고 개발을 진행한다면 늘 그렇듯 예상은 틀릴 것이고 일정이 밀리거나 기능은 계속해서 제한될 것이다.

회의하고잇슴.JPG <그림8> 일정 회의하는 모습

어떻게 해야 효율적인 일정을 작성하는지 우리 TF는 생각할 필요가 있었고 몇 가지 노력을 했다.

반드시 들어가야 할 기능과 추가로 들어가야 할 기능들을 분류하고 우선순위를 먼저 나눴다. 욕심과 필수를 구분하려고 노력했고 그에 관해 서로의 의견에 대해 많이 이야기했다. 자신을 객관적으로 보려고 노력했다. 시간 내에 할 수 있을지를 한 발자국 뒤에서 생각했다. 일찍 마무리되면 다음 일을 시작하면 된다. 계획을 정하는데 많은 시간을 쏟지 않았다. 대신 하루에 한 번 멘토님을 포함하여 10분 15분 정도의 짧은 스크럼 시간을 가졌고 각자의 진행 상황과 어떤 어려움을 겪고 있는지 공유했다. 이 시간에 멘토님들에게 좋은 의견을 듣기도 하고 일을 재분배하거나 추가 투입하여 마무리하기도 했다.

이런 노력은 일정 내에 프로젝트를 진행하는 데 큰 도움이 되었다고 생각한다. 이런 방법들을 우리 스스로 고민해 봤던 것도 좋았다. 현재는 부서에서 오픈 소스를 운영하고 있고 일정을 내가 정하는 경우가 많다. 아직도 일정을 정하는 것은 너무 어렵다. 항상 변수가 등장한다. 하지만 베이스캠프 때 나 자신을 객관적으로 보려 노력했던 경험은 지금도 큰 도움이 되고 있다. 경험해보지 않은 상태에서 현업에 들어와 스스로 개발 일정을 처음 짰더라면, 지금에서야 여러 시행착오를 겪고 있었을 것이다.

브랜치 모델 적용하기

입사 전 깃헙은 말 그대로 ‘저장소' 목적으로 사용했다. 과제나 알고리즘 코드를 정리해 놓거나 이전에 했던 토이 프로젝트들을 저장해 놓는 용도로 사용했다. 그 자체로 깃헙은 저장소 역할을 하지만 저장소로만 사용하기엔 너무 아까운 도구이다. 우리는 교육 초반에 깃과 깃헙 기초교육을 받는다. 깃이 무엇인지, 깃헙 저장소를 만드는 것부터 실제 프로젝트에서 활용할 수 있는 여러 가지 브랜치 관리 모델들을 배운다. 또한, 깃에서 제공하는 훅들을 이용하여 사내 업무 툴과 연동시켜 효율적으로 관리하는 법까지 배운다.

회사에서 프로젝트를 혼자 개발하는 경우는 거의 없다. 각각 업무를 분담하고 동시에 개발하게 된다. 여러 명의 코드가 효과적으로 각각 관리되어야 한다. 개발할 때 서로의 코드에 영향을 주지 않으며 쉽게 합칠 수 있어야 한다. 각각 분리되어 개발될 경우 리뷰가 쉬워야 한다. 각각 개발한 부분들을 기능별로 서로 이해하기 쉬워져야 한다. 개발용 코드와 배포용 코드들이 분리되어 관리되어야 한다. 잘못된 배포가 나갔을 경우 쉽게 이전 버전으로 돌아오거나 이전 코드를 참조할 경우가 발생하면 쉽게 찾을 수 있다. 이때 깃헙과 브랜치 모델을 활용한다면 여러 가지 장점을 쉽게 누릴 수 있다.

gitflow.png <그림9> git-flow 모델

우린 교육시간에 배웠던 git-flow 모델을 활용했다. 개개인이 병렬로 기능을 개발할 수 있었고 출시 버전과 개발 버전을 관리하기에 쉬웠다. 물론 git-flow를 배운 뒤 바로 모든 장점을 누리는 것은 아니었다. 서로 같은 파일을 수정하는 경우가 빈번했고 PR 이후 병합할 때마다 충돌 해결은 늘 함께했다. 익숙하지 않았기 때문에 여러 실수 또한 함께했다.

하지만 점점 시행착오를 거치고 익숙해지면서 깃헙과 브랜치 모델을 사용하는 이유, 장점들을 알게 되었다. git-flow라는 브랜치 모델이 모든 서비스에서 항상 장점을 얻을 수 있는 모델은 아니다. 하지만 이런 브랜치 모델들을 프로젝트에서 오래 사용해 봄으로써 서비스의 특성에 따라 어떤 브랜치 모델을 가져가는 것이 효율적일지에 대해 고민할 수 있는 시간을 많이 가졌었다. 이러한 고민은 부서에 배치받아 새로운 프로젝트에 참여해 브랜치 모델을 이해할 때 쉽게 이해하는 데 큰 도움이 되었다.

코드 스타일 통일하기

// (1)
if(condition) a++;
else b++;

// (2)
if(condition) {
  a++;
} else {
  b++;
}

// (3)
if(condition)
{
  a++;
}
else
{
  b++;
}

<코드1> 어떤 코드를 선호하는가?

(1), (2), (3) 중 어느 코딩 스타일을 선호하는가? 지금의 나라면 당연히 (2)를 선택하겠지만 1년 반 전의 나는 라인이 적다는 이유로 (1)번을 선택했을 것이다. (2번이 아닌 1, 3번 코드를 선호하는 사람들 또한 많을 것이다) 사실 무엇이 더 좋은지는 선호 차이이다. 다수가 많이 쓰는 스타일과 아닌 스타일이 존재한다. 대학생 때는 알고리즘 문제를 주로 풀었기 때문에 짧은 라인의 코드를 선호했다. 변수명도 짧게 짧게 작성했다. 여러 명이 한 프로젝트를 개발한 경험이 많이 없었기 때문에 다른 사람의 코드가 내가 진행하고 있는 프로젝트에 들어왔을 때 느낌 또한 몰랐다.

현업에서는 코드를 작성하는 것보다 읽는 시간이 더 많이 필요하게 된다. 이전에 누군가 작성했던 코드들을 보면서 앞으로 개발할 내용이 어떻게 프로젝트에 들어가야 할지 방향을 잡아야 한다. 통일된 스타일의 코드를 작성하지 않는다면 코드를 읽는 시간은 배로 늘어날 것이고 업무의 효율 또한 많이 줄게 될 것이다. 코딩 스타일을 통일하고 유지하려는 노력은 선택이 아니라 필수라는 것을 알게 되었다.

처음에는 코딩 스타일을 정하지 않고 코드를 작성했다. 코드가 점점 많아짐에 따라 읽고 이해하기 힘들었다. 기능이 추가되거나 삭제될 때 수정할 부분들을 찾기 어려웠다. 우리는 이때부터 코드 스타일을 통일할 필요가 있겠다고 생각했다. 우리 조는 자바스크립트의 경우 많이 사용하는 Airbnb 규칙을 사용했고 자바의 경우 구글 가이드를 적용해 스타일을 통일했다. 내 코딩 습관을 버리고 다른 사람이 만든 규칙을 따라가는 것은 시간이 걸리고 어려운 일이었다. 하지만 시간이 지날수록 코드는 한 사람이 작성한 코드처럼 통일되어 갔고 코드를 읽고 이해하는 시간 또한 과거보다 빨라졌다.

네 번째, 책 읽는 습관 들기

교육 과정에는 총 3권의 기술 서적을 읽어야 했다. 자바스크립트 기초와 인프라 기초, 스프링에 관한 책을 읽었다. 사실 회사에 입사하기 전 기술을 공부할 때 책을 즐겨보는 성격은 아니었다. 변명을 해보자면 빠르게 변하는 웹을 공부하는데 책이 나오는 기간을 고려하면 이미 늦은 기술이라 생각했다. 물론 이 생각의 일부는 지금도 유효하다. 하지만 베이스캠프를 통해 책을 읽으면서 달라진 생각을 하게 되었다. 잘못된 지식은 인터넷상에서 더 쉽게 난립하게 된다. 오역이나 잘못 이해한 내용을 옮겨 정리하다 보니 쉽게 퍼져나간다. 한글뿐만 아니라 영어도 똑같다. 그에 비하면 책은 대체로 잘 정제되어 있는 검증된 내용이다. 책 대부분은 기술적 내용뿐만 아니라 작가들의 개발 철학들이 많이 담겨있게 된다. 그런 부분을 읽을 때 주니어 개발자인 나는 나에 대입해 배울 점을 고민하게 된다. 이런 고민은 단기적으로 코드를 개선하기 보다 쌓이고 쌓여서 먼 미래에 내 개발 철학을 갖는 데 큰 도움이 될 것으로 생각한다. 나는 베이스캠프가 끝난 후 지금까지 많은 양의 책을 보고 있고 지금도 어떤 새로운 책을 읽을지 고민하고 있다.

마무리

그림0 - Basecamp 단체사진.png <그림10> 토스트루키 5기 단체사진

취업 준비를 하던 시기에 신입 채용에 경력자와 같은 능력을 요구하는 분위기가 점점 만연해지는 것에 회의감을 가졌었다. 그런 분위기와는 정반대로 NHN은 채용 과정에서 전공과 기초과목을 중점적으로 봤다. 루키 중 웹 개발 경험이 한 번도 없었던 동기들 또한 많았다. 하지만 교육이 끝났을 때 어설프게 웹 개발을 해봤던 경험이 있는 사람들보다 훨씬 깊고 많은 것을 할 수 있는 개발자가 되어있었다고 자부한다. 이 자리를 빌려 루키들 보다 훨씬 더 열정적이게 교육을 준비해주신 운영진, 멘토님들, 선배 개발자님들에게 감사하다고 꼭 전하고 싶다.

아쉬웠던 점이 없을 수는 없다. 주 52시간 근무가 시작되기 전이었기에 우리는 가족보다 더 많은 시간을 함께 보냈다. 많은 것을 9주간 해내야 했기 때문에 주말 출근 또한 많았었다. (물론 나와서 하라고 하는 사람은 없었다.) 또한, 모바일이나 프런트엔드 분야의 개발을 하고 싶은 루키들에게 현업에서 사용할 수 있는 기술 측면에서 당장 쓸 수 있는 기술을 배울 것은 특히 적었다. 하지만, 궁극적으로는 기술을 학습하는데 가속이 붙는 연습을 시켜주는, 러닝 커브를 가파르게 하기 위한 준비를 시켜주는 교육이었다. 결론적으로 나는 이 9주 동안의 교육을 통해 도약할 준비를 했다. 1년이 지난 지금, 루키 5기는 한 명의 퇴사자 없이 베이스캠프 때 배웠던 내용을 기반으로 매일 배우고 공유하며 더 나은 개발자로 빠르게 성장하고 있다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment