코딜리티(Codility) 첫 경험 - 데모 문제 1번

개인적으로 코딩테스트에 대한 공포감(?)이 조금 있다. 사실 코딩테스트에 대해서는 논쟁이 많은 편이다. 개인적으로 코딩테스트는 그냥 최소한의 자격 요건을 보는거지 이 결과로 당락을 결정하는건 아니라고 보는 편이다. 운동하는 사람들끼리 통용되는 말인 노가다에서 쓰는 근육과 헬스에서 쓰는 근육이 다르다이 프로그래밍 세계에서도 그대로 통용된다. 실무에서 쓰는 근육과 코딩테스트에서 쓰는 근육이 다르다 - 즉 코딩테스트 잘본다고 해서 실무를 잘하는게 아니다. 프로그래머라면 물론 코딩도 잘해야 하지만 직급에 맞는 시야와 상호간 커뮤니케이션 능력, 품질에 대한 고려 등등 코딩 외적인 능력이 있어야 훌륭한 개발자가 될 수 있다는게 내 생각이다.

아 근육 얘기 나오니까 하는 얘긴데 코딩테스트 본지 오래된 사람 갑자기 불러다가 코딩테스트 시키면 아무리 천하의 구글러라도 원하는 점수를 못얻을거다. 쓰는 근육이 다르니까.

암튼 말이 길어졌는데 코딩테스트에 대한 공포감을 해결하고자 코딜리티라는걸 이번에 처음 접해보고 한 번 도전해보았다. 코딜리티에는 데모 문제가 있는데 첫 번째 문제인 바이너리 갭(Binary Gap)을 한 번 풀어보았다.

이 문제는 함수를 짜는데 이진수 1과 1 사이에 있는 최대 0의 갯수를 구하는 문제다. 예를 들어 529는 이진수로 1000010001 이며, 함수는 4를 리턴한다. 9는 이진수로 1001이며 함수를 2를 리턴한다. 다만 32는 1000000 이라 갭이 없으므로 0을 리턴하게 된다.

처음에는 뭔가 이진수로 변환한 스트링을 순회하면서 Candidate를 배열에 추가하고 등등등 생각하다가 생각하다가…
갑자기 번뜩이는 split() 함수! 이걸 사용하면 굉장히 쉽게 문제를 해결 -ㅅ-;;;
뭔가 개꼼수 같지만 동작은 잘한다. 테스트케이스 몽땅 통과. 아래 코드는 3분만에 적었다.

알고리즘을 신성시하고 뭔가 대단한걸 기대한 사람들은 날더러 비정통파, 넌 프로그래머도 아냐라고 욕할지도 몰라.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function solution(N) {
// write your code in JavaScript (Node.js 8.9.4)
if (N === undefined || N < 0) {
throw new Error('invalid input');
}

// 10진수를 2진수로
const bin = N.toString(2);

// 1을 delimeter로 쪼갠다.
const sp = bin.split('1');
// 이진수 마지막이 0이면 배열의 마지막을 뺀다.
if (bin.endsWith('0')) {
sp.splice(sp.length - 1, 1);
}

const lens = sp.map((v) => {
return v.length;
});

return Math.max(...lens);
}

물론 이건 1번 문제니까 쉽겠지.

타입스크립트 전면 도입기

회사 개발팀에 사정이 있어서 지난 3월 말부터 개발팀을 이끌게 되었다. 첫 번째 임무는 기존 백엔드의 C#, CQRS, Event-Sourcing, DDD를 걷어내는 일이었다. 좋은 디자인 패턴과 개발방법론과 언어긴 하지만 현 개발팀 아니 한국에서는 좀 쉽지 않은 결정이다. 이유는 많지만 한 마디로 얘기하자면 현 개발팀에 안맞는 옷이었다.

변화가 생긴 개발팀은 Javascript 기반 프론트 1명, 풀스택 2명이었고 C#/Java Entry Level 1명 그리고 나까지 5명이었다. 마음 먹은대로 하자면 백엔드는 Python 혹은 Go로 하고 싶었지만 작은 회사일수록 테크 스택을 통일해야 한다는게 나의 지론이기 때문에 백엔드는 Express를 도입하기로 했다.

Express 도입과 함께 한 가지 도입한 테크 스택은 Typescript다. 현재 웹 프론트엔드인 vue.js와 백오피스 프론트엔드인 react.js는 이미 Typescript였다. 이 참에 Express도 Typescript를 도입하기로 했고 최종적으로 Typescript Rest을 선택했다.

프론트엔드와 백엔드를 모두 Typescript로 통일하고 그 장점을 극대화 하기 위해서 양쪽 사이의 API에서 사용하는 인터페이스나 Enum을 모아놓은 프로젝트를 만들었다. 예를 들어 Item 정보를 만드는 POST API가 있다고 가정하자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { POST, Path } from 'typescript-rest';

interface ItemBody {
name: string;
quantity: number;
}

@Path('/item')
export class ItemController {
/**
* 아이템을 만든다.
*/
@Path('')
@POST
public postItem(body: ItemBody){
// .....
}
}

그리고 프론트엔드에서 axios.js로 해당 API를 쓴다면 아래와 같겠다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
interface ItemBody {
name: string;
quantity: number;
}

//// 생략
class APIs {
public async doSend(item: ItemBody) {
try {
await axios.post(`${BASE_URL}/item`, item);
} catch (e) {
// ....
}
}
}
//// 후략

두 프로젝트에서 공통으로 쓰는 인터페이스인 ItemBody를 별도의 프로젝트로 분리, Private NPM 저장소를 만들고 이걸 import를 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { POST, Path } from 'typescript-rest';
import { ItemBody } from '@mycompany/interface';

/* 이 부분을 없앨 수 있다.
interface ItemBody {
name: string;
quantity: number;
}*/

@Path('/item')
export class ItemController {
/**
* 아이템을 만든다.
*/
@Path('')
@POST
public postItem(body: ItemBody){
// .....
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { ItemBody } from '@mycompany/interface';
/* 아래 코드를 없앨 수 있다.
interface ItemBody {
name: string;
quantity: number;
}*/

//// 생략
class APIs {
public async doSend(item: ItemBody) {
try {
await axios.post(`${BASE_URL}/item`, item);
} catch (e) {
// ....
}
}
}
//// 후략

여기서는 프로퍼티가 단순히 2개인 인터페이스만 대상으로 했지만, 프로퍼티가 많아질 수록, 그 인터페이스가 많아질 수록 강력한 효과를 발휘했다. 프론트엔드에서 힘들게 Swagger를 보고 인터페이스를 정의할 필요가 없었고,만들다가 생기는 휴먼 에러도 방지할 수 있었다. 단순히 npm을 업데이트함으로서 API와 싱크를 맞추기도 쉬웠다. 무엇보다 각각의 프로젝트에서 각각 관리하던 인터페이스 코드를 통합관리 할 수 있어서 굉장히 생산성이 높아졌다.

필요성을 느껴서 만들었기 때문에 다른 프로젝트에서는 이렇게 쓰는지는 잘 모르겠지만 프론트엔드와 백엔드 모두 타입스크립트를 쓴다면 한 번 도입해보면 좋겠다.

P.S 두 달 반만에 성공적으로 C#에서 자바스크립트로 이전했다.

ECMAScript use strict 사용하기

“use strict” 은 자바스크립트 코드를 좀 더 엄격한(strict) 모드에서 실행하도록 인터프리터에게 명령하는 자바스크립트의 지시자(directive)다.

좋은 점 3가지

  1. 코딩 실수 대신 에러를 낸다. 따라서 프로그래머는 자신의 실수를 쉽게 알아차릴 수 있다.
  2. 보통 엄격 모드에서 실행속도가 빠르다라고 알려져 있다. 직접 테스트를 안해봐서 알 수는 없지만, 엄격 모드에서는 인터프리터의 최적화 작업을 어렵게 만드는 문법을 금지시키기 때문에 빠르다.
  3. ECMAScript의 차기 버전들에서 정의 될 문법을 사용하지 못한다.

‘엄격 모드’를 사용하면 생기는 제한

이 ‘엄격 모드’로 자바스크립트 코드를 실행하고자 하면 은은하게 많은 제한이 생긴다. 주석은 크롬 콘솔에서 실행했을 때 발생하는 에러다.

  1. 생성하지 않은 변수를 사용할 수 없다. 자바스크립트에서는 실수로 변수명을 잘못 적었다면 에러를 내지 않고 그냥 새로운 전역 변수가 생성되는데, strict 모드를 사용하면 이를 막을 수 있다.

    1
    2
    "use strict";
    pi = 3.1415; // Uncaught ReferenceError: pi is not defined
  2. 변수나 함수, 삭제할 수 없는 속성을 삭제할 수 없다.

    1
    2
    3
    4
    5
    6
    "use strict";
    var pi = 3.14;
    delete pi;
    function f(p1, p2) {};
    delete f; // Uncaught SyntaxError: Delete of an unqualified identifier in strict mode.
    delete Object.prototype; // TypeError: property "prototype" is non-configurable and can't be deleted
  3. 파라미터 이름을 중복으로 쓸 수 없다.

    1
    2
    "use strict";
    function f(p1, p1) {}; // SyntaxError: duplicate formal argument p1
  4. 8진수 리터럴, 8진수 이스케이프 문자를 사용할 수 없다.

    1
    2
    3
    "use strict";
    var oct = 010; // Uncaught SyntaxError: Octal literals are not allowed in strict mode
    var oct1 = "\010"; // Uncaught SyntaxError: Octal escape sequences are not allowed in strict mode.
  5. 읽기 전용 속성이나 GET 전용 속성을 다시 쓸 수 없다.

    1
    2
    3
    4
    5
    6
    "use strict";
    var obj = {get z() {return 0} };
    Object.defineProperty(obj, "x1", {value:123, writable:false});

    obj.x1 = "hello"; // Uncaught TypeError: Cannot assign to read only property 'x1' of object
    obj.z = "world"; // Uncaught TypeError: Cannot set property z of #<Object> which has only a getter
  6. 몇 가지 키워드를 변수명으로 사용할 수 없다.

    1
    2
    3
    "use strict";
    var eval = 'hello'; // SyntaxError: 'eval' can't be defined or assigned to in strict mode code
    var arguments = 'world'; // SyntaxError: 'arguments' can't be defined or assigned to in strict mode code

브라우저 지원

2009년 ECMAScript 5에서 처음 나온 기능으로 IE9과 그 이전 IE에서는 strict 모드를 지원하지 않는다. 다만 이전 브라우져에서 큰 문제는 되지 않는데, 이전 브라우져에서는 문법적(BNF)으로 보면 “use strict”를 그저 리터럴 표현으로 인식하기 때문에 side effect 를 발생시키지 않고 따라서 그냥 무시될 뿐이다.

strict 모드를 지원하는 브라우져는 아래와 같다.

  • 크롬 버전 10 이상
  • 파이어폭스 버전 4 이상
  • 사파리 버전 5.1 이상
  • IE 버전 10 이상

ECMAScript(Javascript) 호이스팅

호이스팅(Hosting)은 자바스크립트 인터프리터가 변수 및 함수의 선언을 항상 코드 상단으로 올리는 행위를 말한다. 엄밀하게 얘기하자면 컴파일 단계에서 변수 및 함수의 선언을 먼저 읽어서 메모리 상에 저장해두는 행위에 가깝다. 예제 코드를 한 번 보자.

1
2
3
a = 1;
console.log('Result:', a);
var a;

이 코드의 실행 결과는 아래와 같다.

1
Result: 1

알다시피 자바스크립트는 변수를 선언하기 전에 사용할 수 있다. 코드 1행에서 선언하지도 않았던 a에 1을 할당했고 2행에서 콘솔에 출력한 결과가 제대로 출력되었다. 정작 변수 a는 3행에서야 나타난다.

이를 호이스팅(hoisting)이라 한다. 호이스팅의 원형 hoist는 사전적 의미로 밧줄이나 장비를 이용하여 끌어올리다는 뜻이 있다. 이렇게 자바스크립트 인터프리터는 컴파일 단계에서 변수 및 함수의 선언을 항상 컨텍스트의 상단으로 끌려올린다. 인터프리터가 선언 코드를 물리적으로 상단으로 올리는 건 아니다. 다만 변수와 함수 선언을 컴파일 단계에서 메모리에 올려두고 undefined 값을 할당한다. 그 후에 코드를 실행하기 때문에 선언이 뒤에 있더라도 선언 앞에서 해당 변수를 사용할 수 있다.

다만 선언에 강조를 한 이유는 초기화 코드는 호이스팅의 대상이 아니기 때문이다.

1
2
3
var a = 5;
console.log('a: %d b: %d', a, b);
var b = 7;

위 코드를 크롬 콘솔에서 실행하면 아래의 결과가 나온다.

1
a: 5 b: NaN

자바스크립트 인터프리터가 위 코드를 호이스팅하면 아래와 같은 형태로 코드를 읽어들인다.

1
2
3
4
5
var a;
var b;
a = 5;
console.log('a: %d b: %d', a, b)
b = 7;

즉 인터프리터는 컴파일 단계에서 변수 a와 b를 메모리 상에 올려두고 undefined 값을 할당해 둔다. 다만 호이스팅은 선언만을 대상으로 하기 때문에 초기화 구문은 해당되지 않는다. 따라서 console로 값을 찍을 무렵에는 a에는 5만 할당되어 있고 b는 undefined 상태가 된다.

함수도 호이스팅의 영향을 받는다. 함수 선언식이 호이스팅의 영향을 받는다. 자바스크립트에서 함수를 사용하는 방식을 잠깐 설명하자면 자바스크립트에서 함수를 사용하는 방식은 함수 선언식(Function Declaration)과 함수 표현식(Function Expression)으로 구분할 수 있다.

함수 선언식은 다른 프로그래밍 언어에서 사용하는 함수 선언식과 동일하다.

1
2
3
function functionDeclaration() {
// 함수 내용
}

함수 표현식은 아래와 같다. Go언어같은 최신 언어에서는 자바스크립트와 같은 함수 표현식을 많이 지원하기도 한다.

1
2
3
var functionExpr = function() {
// 함수 내용
}

예를 들어 아래와 같은 코드가 있다면,

1
2
3
4
5
6
7
8
9
10
funcDecl();
funcExpr();

function funcDecl() {
return 'decl!';
}

var funcExpr = function() {
return 'expr!';
}

호이스팅때문에 인터프리터는 아래와 같이 해석한다.

1
2
3
4
5
6
7
8
9
10
function funcDecl() {
return 'decl!';
}
var funcExpr;
funcDecl();
funcExpr();

funcExpr = function() {
return 'expr!';
}

따라서 funcExpr()를 실행할 시점에는 funcExpr()이 정의되지 않았기 때문에 크롬에서 실행하면 아래와 같은 결과가 나온다.

1
Uncaught TypeError: funcExpr is not a function

이 호이스팅 때문에 의도하지 않은 버그를 내기도 한다. 문제는 자바스크립트 인터프리터의 동작을 이해하지 못하는 사람이 코드를 짜면 버그가 잘 날 수 있다는 사실, 그리고 그 버그가 나오면 찾기 어렵다는 문제가 있다. ~C계열의 undefined behavior의 향기가…
따라서 호이스팅을 막으려면

  1. 선언을 함수나 전역 상단에 적기
  2. let 사용
  3. strict 모드 사용 - use strict 을 사용 시 선언하지 않은 변수를 사용할 수 없다.
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×