[Day 30] Hackathon Comment + Node.js + HTTP + Iterable

Hackathon Comment

  1. 프로그래머들은 항상 복잡성(Complexity)과 싸워야한다.
    • 코드를 잘 정리해서 함수, 모듈, 파일 등으로 나눈다면 복잡성이 낮아지게 된다.
    • 큰 프로그램을 작성하고 유지보수를 편하게 하기 위해서 복잡성을 낮춰야 한다.
    • 협업을 잘 하기 위해서도 복잡성을 낮춰야 한다.
  2. 폭포수(Waterfall) 개발 방법론
  3. 애자일(Agile) 개발 방법론
  4. 일정을 잘 관리해야 한다.
    • 해야 하는 일하고 싶은 일을 잘 구분해서!
    • 어떤 일을 하는 데 어느 정도의 시간이 걸리겠다는 것을 측정할 때, 굉장히 보수적으로 측정해서 내가 생각한 것보다 2~3배 더 많이 걸릴 것으로 예상해야 한다. (실제로 온갖 버그와 문제점들 때문에 생각한 것보다 오래 걸린다.)
    • 작업하는 순서는 꼭 해야 하는 일 부터!!
      • MVP(Minimum Viable Product) 최소 기능 제품을 먼저 만들고 난 뒤 하고 싶은 일을 해야 한다.
      • MVP 조차 기한내에 맞추지 못한다면 Plan B를 세워서 어떻게든 기능을 만들어내야 한다.

Node.js + HTTP

Node.js

// Node.js module 사용하기
$ const os = require('os') // 급할땐 `os = ...`
undefined

// node.js 프로그램이 어떤 운영체제 위에서 실행되고 있는지
$ os.platform()
'linux'

// 가용한 메모리
$ os.freemem()
658300928
  • Node.js로 파일 실행시키기

    $ node (파일 경로)
    
  • Node.js

    • Chrome에서 사용되는 V8 JavaScript engine을 사용한다.
    • 통신도 이벤트 핸들러를 붙이는 방식(event-driven)으로 이루어진다.
    • 아주 빠른 속도로 많은 요청을 처리할 수 있는 non-blocking I/O moedl 을 사용한다.
    • Node.js의 package ecosystem인 npm 은 세계에서 가장 거대한 오픈소스 라이브러리 생태계를 갖고 있다.
  • Node.js Module

    // name.js
      
    // `module.exports`에 저장한 값은 다른 모듈에서 불러올 수 있음
    module.exports = {
      familyName: '김',
      givenName: '승하',
      fullName: function() {
        return this.familyName + this.givenName
      }
    }
    
    // calc.js
      
    // `exports`로도 참조 가능
    exports.add = (x, y) => x + y
    exports.sub = (x, y) => x - y
    
    // Node REPL에서 모듈 불러오기
      
    // Node.js 내장 모듈과는 다르게 경로를 지정해야 함
    $ const name = require('./name')
    undefined
    $ name
    { familyName: '김',
      givenName: '승하',
      fullName: [Function: fullName] }
    > name.familyName
    '김'
    > name.fullName()
    '김승하'
    > require('./calc').add(1, 2)
    3
    

npm

  • npm
    • Node.js 패키지 관리 도구 + 클라우드 패키지 저장소
    • 의존 패키지 관리
    • 스크립트 실행
    • 패키지 설정
    • NPM에 패키지 배포
    • Node.js 종합 작업 도구
  • package.json : 패키지 정보를 담고 있는 파일

    • dependencies : npm install --save 명령으로 설치한 패키지가 기록된다.

      • 어떤 라이브러리를 설치했는지 자동으로 기록된다.

      • package-lock.json에도 라이브러리 설치 기록이 남아있기 때문에 node_modules 폴더가 날라가도 npm install을 다시 하면 그대로 설치된다.
      • 협업을 위해 github에 올릴 때 용량을 줄이기 위해 node_modules 폴더는 올리지 않는다. (gitignore에 설정한다.)
        • package.json / package-lock.json 만 git에 올리고 필요시 npm install 해서 사용하도록 한다.
    • scripts : 원래 목적은 패키지 생명주기마다 자동으로 실행되는 명령을 등록하기 위함이나, 개발자 편의를 위해 자주 사용되는 명령을 등록하는 용도로 더 많이 사용된다.

      • package.json의 “scripts” 속성에 key/value 형태로 등록해서 사용한다.

        // package.json
              
        "scripts": {
            "test": "echo \"Error: no test specified\" && exit 1",
        	"start": "node index.js"
        },
              
        // 등록하고 나면 npm start  입력했을 경우 node index.js가 실행된다.
        
  • 과거 npm은 Node.js의 라이브러리들만 다운받아서 사용하는 클라우드 저장소였지만, 최근에는 FrontEnd 라이브러리들(jQuery, css reset 등등…)도 npm을 통해 다운받아서 사용할 수 있다.

    • 과거에는 FE 라이브러리를 다운받는 클라우드 저장소로 Bower를 많이 사용했었지만 2015년을 기점으로 거의 사장되었다.

HTTP(HyperText Transfer Protocol)

  • 웹 브라우저와 웹 서버 간의 통신을 위해 개발된 통신규약

  • 최근에는 REST API의 부상과 함께 다른 용도로도 널리 사용된다.

    • 모바일 앱 - 서버 간 통신
    • 서버 - 서버 간 통신
  • 80번 포트를 기본으로 사용

    • 컴퓨터에 통신을 위한 수많은 콘센트가 있고, 거기에 번호가 붙어 있을 때 그 번호를 포트(Port)라고 한다.
  • 클라이언트의 요청(request)과 서버의 응답(response)으로 이루어짐

    • 요청 한번 보내고, 응답 한번 받으면 연결이 끊어진다. (하나의 사이클)
  • HTTPS (HTTP over SSL(secure socket layer))

    • HTTP 통신을 암호화해 주고받는 내용을 중간에서 가로챌 수 없도록 함
    • 443번 포트를 기본으로 사용
  • HTTP/2

    • 구글의 SPDY 프로토콜을 기반으로 2015년에 확정된 새로운 HTTP 표준
    • 속도 개선에 중점을 두고 개발됨
    • 반드시 HTTPS를 사용해야 함
    • 현재 전체 웹사이트 중 26% 이상이 사용 중
  • Request & Response

    • 웹 브라우저(또는 다른 클라이언트)는 웹 서버에 요청(request)을 보낸다.
    • 그에 따라 서버는 클라이언트에 응답(response)을 보냄
    • 웹 브라우저의 경우, HTML 문서 형태의 응답이 오면 해당 문서를 분석한 후, 문서에 포함된 모든 자원에 대한 요청을 각각 추가로 보낸다. (이미지, 동영상, 오디오, CSS, JS, 폰트, …)
  • Request Methods (요청 메소드)

    • HTTP 명세에는 8종류가 등록되어 있고, 각각의 역할과 충족해야 하는 성질이 명시되어 있다.
    • 웹 브라우저는 특정 상황에서 특정 메소드로 요청을 보내도록 만들어져 있다.
    • Ajax와 같이 요청을 보내는 코드를 직접 짤 때는 요청 메소드를 선택할 수 있다.
    • 자료의 본문을 요청하는 GET 메소드와 새로운 자료를 등록하는 POST 메소드가 가장 많이 쓰인다.
    • Method Definitions
  • URL

    HTTP URL Anatomy

    • Percent Encoding

      • URL은 ASCII 문자 밖에 사용하지 못하기 때문에, non-ASCII 문자를 위한 표현방법이 필요하다.

      • Percent Ecoding은 non-ASCII 문자를 위한 웹 표준 인코딩 방법으로, Javascript에 관련 기능이 포함되어 있다.

        $ encodeURIComponent("한글")
        "%ED%95%9C%EA%B8%80"
        $ decodeURIComponent("%ED%95%9C%EA%B8%80")
        "한글"
        
  • Response Status

    • HTTP Status Codes
    • Status Category
      • 2xx : 성공
        • 200 OK : 성공
        • 201 Created : 자료가 성공적으로 생성됨
      • 3xx : 추가 작업이 필요함
        • 301 Moved Permanently (Redirection) : 자료가 완전히 다른 곳으로 이동했음
        • 302 Found (Redirection) : 자료가 일시적으로 다른 곳에 있음
        • 304 Not Modified (Cache) : 클라이언트가 이미 가지고 있던 자료가 수정되지 않았음 (그대로 사용하면 됨)
      • 4xx : 실패 - 클라이언트 책임
        • 400 Bad Request : 요청의 형태가 잘못되어 응답할 수 없음
        • 403 Forbidden : 요청한 자료에 접근할 권한이 없음
        • 404 Not Found : 요청한 자료가 없음
      • 5xx : 실패 - 서버 책임
        • 500 Internal Server Error : 요청을 처리하던 중에 예상치 못한 오류가 발생함
        • 503 Service Unavailable : 서버가 일시적으로 응답을 할 수 없음
  • Header

    • 요청과 응답에 대한 추가 정보를 표현하는 데 사용된다.
    • 인증, 캐싱, 쿠키, 보안, 프록시 등 웹 표준에 정의된 많은 기능을 제어하는데 사용된다.
    • Authorization : 요청의 인증 정보
    • User-Agent : 요청 중인 클라이언트의 정보
    • Location : 301, 302 응답에서 자료의 위치
    • Accept : 요청이 어떤 형태의 자료를 원하는지 나타냄
    • Content-Type :요청 혹은 응답이 어떤 형태의 자료인지 나타냄

Iterable

  • 반복 가능한 객체(iterable object)는 ES2015에서 도입되었다.
  • 이터러블 객체가 다른 객체와 다른 점은 객체의 Symbol.iterator 속성에 특별한 형태의 함수가 들어 있다는 것이다.
  • 이터러블이 가능할 경우 해당 객체는 iterable protocol을 만족한다고 표현한다.
  • iterable 객체를 만들어내는 생성자
    • String
    • Array
    • TypedArray
    • Map
    • Set

Iterable의 사용

  • 어떤 객체가 Iterable이라면, 그 객체에 대해 이러한 기능들을 사용할 수 있다.

    • for...of 루프
    • spread 연산자(...)
    • 분해대입(destructuring assignment)
    • 기타 iterable을 인수로 받는 함수
    // `for...of`
    for (let c of 'hello') {
      console.log(c);
    }
      
    // spread 연산자
    const characters = [...'hello'];
      
    // 분해대입
    const [c1, c2] = 'hello';
      
    // `Array.from`은 iterable 혹은 array-like 객체를 인수로 받습니다.
    Array.from('hello');
    

Generator 함수

  • iterable protocol을 구현하기만 하면 어떤 객체든 iterable이 될 수 있다.

    • 너무 어려운 방법이다 ㅠㅠ
  • 대신, Generator 함수(ES2015)를 사용하면 쉽게 iterable 객체를 만들 수 있다.
  • Generator 함수는 iterable 객체를 return하는 특별한 형태의 함수이다.

  • generator 함수 정의

    // generator 함수 선언하기
    function* gen1() {
      // ...
    }
      
    // 표현식으로 사용하기
    const gen2 = function* () {
      // ...
    }
      
    // 메소드 문법으로 사용하기
    const obj = {
      * gen3() {
        // ...
      }
    }
    
  • Generator 함수를 호출하면 객체가 생성되는데, 이 객체는 iterable protocol을 만족한다. 즉, Symbol.iterator 속성을 갖는다.

    function* gen1() {
      // ...
    }
      
    // `gen1`를 호출하면 iterable이 반환됩니다.
    const iterable = gen1();
      
    iterable[Symbol.iterator]; // [Function]
    
  • Generator 함수 안에서는 yield라는 키워드를 사용할 수 있다. yield 키워드는 return과 유사한 역할을 하며, iterable 기능을 사용할 때 yield 키워드 뒤에 있는 값들을 순서대로 넘겨준다.

    function* numberGen() {
      yield 1;
      yield 2;
      yield 3;
    }
      
    // 1, 2, 3이 순서대로 출력됩니다.
    for (let n of numberGen()) {
      console.log(n);
    }
    
  • yield* 표현식을 사용하면, 다른 Generator 함수에서 넘겨준 값을 대신 넘겨줄 수도 있다.

    function* numberGen() {
      yield 1;
      yield 2;
      yield 3;
    }
      
    function* numberGen2() {
      yield* numberGen();
      yield* numberGen();
    }
      
    // 1, 2, 3, 1, 2, 3이 순서대로 출력됩니다.
    for (let n of numberGen2()) {
      console.log(n);
    }
    
  • Generator 함수의 활용 예시

    • Generator 함수를 사용하면 좋은 점
      • 무한 배열을 생성할 수 있다.
      • 루프(loop)를 값으로 사용할 수 있다. (값의 변경, 조합 등 추가적인 작업이 가능해진다.)
      • 값으로 만들어진 것들은 조합성이 좋기 때문에 나중에 변경을 쉽게 할 수 있다.
      • 루프를 기능으로 분리해낼 수 있다.
    // 등차수열 생성하기
    function* range(start = 0, end = Infinity, step = 1) {
      for (let i = start; i < end; i += step) {
        yield i;
      }
    }
      
    // 피보나치 수열 생성하기
    function* fibonacci(count = Infinity) {
      let x = 1;
      let y = 1;
      for (let i = 0; i < count; i++) {
        yield x;
        [x, y] = [y, x + y];
      }
    }
      
    // 하나의 항목을 계속 넘겨주기
    function* repeat(item, count = Infinity) {
      for (let i = 0; i < count; i++) {
        yield item;
      }
    }
      
    // 여러 요소를 반복해서 넘겨주기
    function* repeatMany(array) {
      while (true) {
        for (let item of array) {
          yield item;
        }
      }
    }
    
  • Generator 함수를 사용할 때 주의할 점!!

    1. Generator 함수로부터 생성된 iterable 객체는 한 번만 사용될 수 있다.

      • 모든 iterable 객체에 해당되는 것은 아니다. 문자열이나 배열도 iterable 객체이지만 횟수에 제한 없이 사용할 수 있다.
      // Generator 함수로부터 생성된 iterable은 한 번만 사용될 수 있습니다.
      function* gen() {
        yield 1;
        yield 2;
        yield 3;
      }
           
      const iter = gen();
           
      for (let n of iter) {
        // 잘 출력됩니다.
        console.log(n);
      }
      for (let n of iter) {
        // `iter`는 한 번 사용되었으므로, 이 코드는 실행되지 않습니다.
        console.log(n);
      }
      
    2. Generator 함수 내부에서 정의된 일반 함수에서는 yield 키워드를 사용할 수 없다.

      // Generator 함수 내부에서 정의된 일반 함수에서는 `yield` 키워드를 사용할 수 없습니다.
      function* gen2() {
        // 아예 문법 오류가 납니다. (Unexpected token)
        function fakeGen() {
          yield 1;
          yield 2;
          yield 3;
        }
        fakeGen();
      }
      
  • Generator란 무엇인가?

    • Generator는 값을 여러번 뱉어낼 수 있는 함수이다. (조합성이 좋아진다.)

    • 일시정지도 할 수 있다.

      function* myGen() {
        console.log('hello')
        yield 1
        console.log('hello 2')
        yield 2
        console.log('bye')
      }
          
      const iterable = myGen()
          
      iterable.next()
      iterable.next()
      iterable.next()
          
      // next() 메소드를 실행하면 해당 generator를 실행하는데, yield를 만나면 일시정지 한다.
      // yield 를 만나면 일시정지 하면서 { value: (...), done: (false/true) } 를 return 한다.
      // iterable 객체의 value는 yield가 return하는 값이고, done은 함수의 종료 여부를 return 한다.
      // 함수의 맨 마지막 까지 진행됐다면 done: true를 return 한다.
      
    • Generator 사용 예제

      • 제너레이터와 실행기를 이용한 Delay
      // 제너레이터 정의
      function* genCoroutine() {
        console.log('hello')
        yield {
          type: 'delay',
          delay: 1000
        }
        console.log('bye')
        yield {
          type: 'delay',
          delay: 2000
        }
        console.log('last')
      }
          
      // 제너레이터 실행기 정의
      function runCoroutine(co) {
        const value = co.next().value
        if (value.type === 'delay') {
          setTimeout(() => {
            runCoroutine(co)
          }, value.delay)
        }
      }
          
      // 제너레이터 실행
      runCoroutine(genCoroutine())
      

댓글남기기