타입과 유추, 명명 규칙

타입을 유추해 코드를 파악하는 연습을 해봅니다. 이름을 짓는 관습에 대해 알아봅니다.
타입, 명명 규칙


타입

지금까지 이름은 주소를 가리키고, 그 주소가 가리키는 메모리에는 특정 값이 있으며, 그 값은 특정 타입(Type)에 속해있다고 했습니다. 타입은 메모리에 위치하고 있는 값이 구성되어 있는 방식이라고 할 수 있습니다. OOP적인 관점에서는 메모리에 위치한 값이 어떤 설계도, 즉, 어떤 클래스의 인스턴스인지 라고 생각 할 수 있겠습니다.

이름과 객체, 타입이름과 객체, 타입

코딩에 있어서 타입에 신경써야 하는 이유는, 우리는 코드를 읽으면서 단순히 변수의 이름 뿐만 아니라 그 이름이 가리키고 있는 숨겨진 문맥을 읽을 수 있어야하기 때문입니다. 타입을 읽을 수 있다면, 어떤 이름이 가리키는 객체가 어떤 속성들을 지녔는지를 알 수 있고, 어떤 이름이 함수라면 그 함수가 무슨 타입의 인자들을 원하는 지 알 수 있습니다. 이를 통해서 생산성을 높힐 수 있고, 주어진 함수를 잘못 사용하는 오류를 방지 할 수 있습니다.

언어별 타입의 특성언어별 타입의 특성

언어/플랫폼에 따라서 이러한 타입을 다루는 관점이 크게 다를 수 있습니다. 이는 크게 두가지 방식으로 구분 할 수 있습니다.

Strongly vs. Weakly Typed Language

-- Strongly
"1" + 1; // Error!
(Number)"1" + 1;


-- Weakly
"1" + 1;

먼저 특정 연산에서 형 변환이 암묵적으로(컴파일러가 알아서) 이루어 질 수 있는가의 여부에 따라서 강하게, 약하게 타입이 정해진 언어라고 구분 할 수 있습니다. Strongly Typed 경우에는 형 변환이 필요한 경우엔 항상 개발자가 명시적으로 형변환 코드를 작성해야 합니다.

Statically vs. Dynamically Typed Language

-- Statically
Number x = 10;
String y = "ten";
Array<Number> z = [1,2,3,4];
Number function sum(Number a, Number b) {
  return a + b;
}
Number result = sum(x, y); // Error!


-- Dynamically
var x = 10; // Number
var y = "ten";
var z = [1,2,3,4];
function sum(a, b) {
  return a + b;
}
var result = sum(x, y); // No Error!

또한 이름 자체에 타입이 고정되어 있는가에 따라서 정적, 동적 타입 언어로 구분 할 수 있습니다. 동적 타입 언어는 개발의 편리나 속도를 향상 시킬 수 있지만, 프로그램을 실행하기 이전에 컴파일러가 타입 오류를 완벽하게 찾아내지 못하기 때문에 잠재적인 런타임 오류의 위험이 있습니다. 반면에 정적 타입 언어는 컴파일 타임에서 타입 오류를 검증 할 수 있어 안정적이지만 코드량이 늘고, 로직이 유연하지 못하다는 단점이 있습니다.

이러한 관점에서 보면 JavaScript는 Weakly, Dynamically Typed Language 라고 할 수 있습니다. 또한 언어마다 타입을 다루는 차이는 언어의 좋고 나쁨, 쉽고 어려움을 의미하지 않습니다. 단순한 도구의 차이임을 이해하고, 필요에 따라 익히고 쓰시면 되겠습니다.

명명 규칙

명명 규칙(Naming Convention)은 영문 이름을 작성하는 관례를 말합니다. 크게 캐멀케이스(camelCase), 스네이크_케이스(snake_case), 파스칼케이스(PascalCase) 세가지 방식이 있습니다. 이 표기법은 여러 단어로 이루어진 이름의 경우 띄어쓰기 대신에 언더바(_)를 삽입하거나 단어의 첫글자를 대문자로 표기하는 방식입니다. 예를 들어 set color, append child 같은 단어를 setColor, appendChild로 표기합니다. JavaScript에서 함수나 메소드, 속성, 변수들의 이름은 이처럼 camelCase를 따르는 것이 관습적입니다. 그리고 클래스의 이름이나 단일 객체의 이름은 보통 PascalCase를 씁니다.

또한 함수의 이름은 setColor, appendChild 처럼 동사형을 쓰고, 값이나 객체의 이름은 color, children 처럼 적절한 명사형을 쓴다면 좀 더 코드를 유추하는데 도움이 됩니다.

관례에 맞게 이름 짓기

// 아래처럼 작성해도 문법상의 오류는 전혀 없습니다.
class user{
    constructor(Name){
        this.Name = Name;
    }

    PrintUserName(){
        console.log(this.Name);
    }
}
var User = new user("abc");
User.PrintUserName();

// 다만 위처럼 쓰기 보다는 아래처럼 씁니다.
// 이런 관습을 지킴으로써 타인의 코드를 유추하기가 더 쉬워집니다.
class User{
    constructor(name){
        this.name = name;
    }
    
    printUserName(){
        console.log(this.name);
    }
}

var user = new User("abc");
user.printUserName();

// 고정된 값(상수)은 모두 다 대문자인 SNAKE_CASE로 쓰는 경우도 있습니다.
var SOME_CONSTANT = "./files/path/fixed";

타입 유추

협업을 할 때나, 라이브러리의 API 문서를 읽는 등 타인의 코드를 읽고 이해해야 하는 경우는 적지 않습니다. 그렇기 때문에 남이 작성한 코드를 읽고 이해하는 능력은 중요합니다.

또한 수 많은 프로그래밍 언어가 있지만 언어별로 문법(Syntax)의 차이는 크지 않습니다. 이름과 값, 객체, 함수, 제어문 등 기본 부품을 근거로 처음보는 코드도 해석 할 수 있어야 합니다.

  • 이름이 대문자면 클래스이거나 단일 객체일 확률이 높다.
  • 이름이 소문자면 함수, 값이거나 인스턴스일 확률이 높다.
  • 이름( … ) 꼴은 어떤 함수다.
  • new 이름( … ) 꼴은 어떤 클래스다.
  • 이름 뒤에 .someThing 또는 [“someThing”]이 붙으면 어떤 객체다.
  • 이름 뒤에 [Number]이 붙으면 Array 또는 String이다.
  • …등등

코드 유추하기

var obj1 = new Something("a","b");
// Something은 클래스고, obj1은 Something 타입의 객체다.
// 또 Something의 생성자는 인자로 String 2개를 받을 수 있다.

obj1.doThing();
// Something 타입 객체는 doThing이라는 인자를 받지 않는 인스턴스 메소드를 갖는다.

obj1.asyncTask(function(data){
  console.log(data + 1);
});
// Something 타입 객체는 asyncTask라는 인스턴스 메소드를 갖는다.
// 또 asyncTask 메소드는 인자로 함수 하나를 받고, 그 함수는 Number와 + 연산이 가능한 인자 하나를 받는다.

obj1.doSomething().doThing().attr = 12;
// Something의 doSomething 인스턴스 메소드는 doThing이라는 함수를 속성으로 갖는 객체를 리턴한다.
// 그런데 5번 줄에서 doThing이 obj1의 메소드였으니 doSometing은 아마도 호출한 인스턴스(obj1)을 다시 리턴하는 함수다.
// 또 doThing이라는 함수는 attr라는 Number를 속성으로 갖는 객체를 리턴한다.

메소드 체이닝

위 14번줄 코드처럼 메소드를 실행하고 다시 바로 메소드를 실행하는 패턴을 메소드 체이닝(Method Chaining)이라고 합니다. 어떤 객체에 대해 연속적인 처리를 할 때, 가독성을 높히기 위해서 구현 할 수 있는 방식입니다.

class Ball{
  constructor(x, y){
    this.x = x;
    this.y = y;
  }

  moveRight(amount){
    this.x += amount || 1; // amount가 undefined거나 0이면 1 아니면 amount
  }

  moveLeft(amount){
    this.x -= amount || 1;
  }

  moveUp(amount){
    this.y += amount || 1;
  }

  moveDown(amount){
    this.y -= amount || 1;
  }
}

// 이 때 공을 왼쪽으로 10, 위로 10 옮기려면
var ball = new Ball(0,0);
ball.moveLeft(10);
ball.moveUp(10);


// 메소드 체이닝을 구현하려면
class Ball2{
  constructor(x, y){
    this.x = x;
    this.y = y;
  }

  moveRight(amount){
    this.x += amount || 1;
    return this; // 인스턴스를 다시 리턴한다.
  }

  moveLeft(amount){
    this.x -= amount || 1;
    return this; // 인스턴스를 다시 리턴한다.
  }

  moveUp(amount){
    this.y += amount || 1;
    return this; // 인스턴스를 다시 리턴한다.
  }

  moveDown(amount){
    this.y -= amount || 1;
    return this; // 인스턴스를 다시 리턴한다.
  }
}

// 다시 공을 왼쪽으로 10, 위로 10 옮기려면
var ball2 = new Ball2(0,0);
ball2.moveLeft(10).moveUp(10);

사전 지식 없이 코드 읽기

var express = require('express');
var router = express.Router();
var fs = require('fs');
var path = require('path');
var bodyParser = require('body-parser');

router.get('/', (req, res) => {
  res.render('bbs/list', {
    posts: loadPosts()
  });
});

// GET /bbs/write
router.get('/write', (req, res) => {
  res.render('bbs/write', {
  });
});

// POST /bbs/write
router.post('/write', bodyParser.urlencoded(), (req, res) => {
  if (req.body.title == "") throw new Error("No Title");
  else if (req.body.password != "1234") throw new Error("Invalid Password");

  var id = savePost({
    title: req.body.title,
    subtitle: req.body.subtitle,
    contents: req.body.contents,
    created: new Date().toString()
  });

  res.redirect('/bbs/'+id);
});

module.exports = router;
이 강의를 포함한 커리큘럼
저자

김동욱

개발 경력 약 10년, 전문 분야는 웹 및 각종 응용 소프트웨어 플랫폼입니다. Codeflow를 운영하고 있습니다.

2018년 05월 11일 업데이트

지원되지 않는 웹 브라우저거나 예기치 않은 오류가 발생했습니다.