20.2 모듈
모듈(module)은 패키지를 만들고 코드를 네임스페이스(namespace)로 구분하는 메커니즘입니다.
네임스페이스는 이름 충돌을 방지하는 방법입니다.
예를 들어 아만다와 타일러가 모두 caculate
함수를 만들었는데, 두 함수를 그냥 복사해서 당신의 프로그램에 붙여 넣는다면 나중에 붙여 넣은 함수가 처음에 붙여 넣은 함수를 덮어쓸 겁니다.
네임스페이스는 ‘아만다가 만든 calculate’, ‘타일러가 만든 calculate’하는 식으로 구분할 수 있는 방법을 제공합니다.
노드 모듈이 이런 문제를 어떻게 해결하는지 살펴봅시다.
다음과 같이 amanda.js
파일을 만드십시오.
function calculate(a, x, n) {
if (x === 1) return a*n;
return a*(1 - Math.pow(x, n)) / (1-x);
}
module.exports = calculate;
tyler.js
파일도 만듭니다.
function calculate(r) {
return 4/3 * Math.PI * Math.pow(r, 3);
}
module.exports = calculate;
아만다와 타일러가 함수 이름을 무책임하게 만들었다고 비난해도 그들은 아무 할 말이 없지만, 최소한 이 예제에서만큼은 아무 문제도 없습니다.
이들 파일에서 중요한 부분은 module.exports = calculate
입니다.
Module은 노드에서 모듈을 구현하기 위해 만든 특별한 객체입니다.
exports
프로퍼티에 무엇을 할당하든, 모듈은 그것을 내보냅니다(export).
이제 모듈 두 개를 만들었으니 다른 프로그램에서 그 모듈을 어떻게 사용할 수 있는지 봅시다.
app.js
파일을 만들고 그 파일에서 이들 모듈을 임포트합시다.
const amanda_calculate = require('./amanda.js');
const tyler_calculate = require('./tyler.js');
console.log(amanda_calculate(1, 2, 5)); // 31
console.log(tyler_calculate(2)); // 33.510321638291124
예제에서는 amanda_calculate와 tyler_calculate라는 이름을 썼지만, 무슨 이름을 쓰든 아무 상관도 없습니다.
이들은 그저 변수일 뿐입니다.
각 변수에 할당되는 값은 노드가 require
함수를 호출한 결과입니다.
수학을 좋아하는 독자라면 아만다와 타일러가 무슨 계산을 하고 있는지 알아챘을 겁니다.
아만다는 등비급수(geometric series)의 합인
a + ax + ax<sup>2</sup> + ... + ax<sup>n-1</sup>
을 계산하고 있고, 타일러는 반지름이 r
인 구체의 체적(volume of a sphere)을 구하고 있습니다.
그들이 뭘 계산하는지 알았으니, 아만다와 타일러의 함수 작명 센스에 어깨를 한 번 으쓱하고는 app.js
에서 더 적당한 이름을 정할 수 있습니다.
const geometricSum = require('./amanda.js');
const sphereVolume = require('./tyler.js');
console.log(geometricSum(1, 2, 5)); // 31
console.log(sphereVolume(2)); // 33.510321638291124
모듈은 어떤 타입의 값이든 내보낼 수 있습니다.
그럴 이유는 별로 없지만, 원한다면 문자열이나 숫자 같은 원시 값을 내보낼 수도 있습니다.
보통은 모듈 하나에 여러 함수를 저장하고, 그 함수를 메서드로 포함하는 객체를 내보내는 것이 일반적입니다.
아만다가 기하학자이고, 등비급수의 합 외에도 여러 가지 유용한 기하학 함수를 모듈로 내보내려 한다고 생각해 봅시다.
module.exports = {
geometricSum(a, x, n) {
if (x === 1) return a*n;
return a * (1 - Math.pow(x, n)) / (1 - x);
},
arithmeticSum(n) {
return (n + 1) * n / 2;
},
quadraticFormula(a, b, c) {
const D = Math.sqrt(b * b - 4 * a * c);
return [(-b + D)/(2*a), (-b -D)/(2*a)];
},
}
이제 좀 더 전통적인 네임스페이스 형태를 쓸 수 있습니다.
반환받은 것(객체)에만 이름을 붙이면 그 안에 포함된 것들에는 이미 이름이 정해져 있습니다.
const amanda = require('./amanda.js');
console.log(amanda.geometricSum(1, 2, 5)); // logs 31
console.log(amanda.quadraticFormula(1, 2, -15)); // logs [3, -5]
여기에 어려운 것은 아무것도 없습니다.
모듈은 단순히 일반적인 객체를 내보낼 뿐이고, 그 객체에 함수 프로퍼티가 있을 뿐입니다(ES6의 단축 문법을 쓰긴 했지만 단순한 함수입니다).
이 방식은 널리 쓰이므로, 특별한 변수 exports
를 사용하는 단축 문법이 따로 있습니다.
다음과 같이 아만다의 모듈을 더 간결하게 고쳐 쓸 수 있습니다.
exports.geometricSum = function(a, x, n) {
if (x === 1) return a*n;
return a * (1 - Math.pow(x, n)) / (1 - x);
};
exports.arithmeticSum = function(n) {
return (n + 1) * n / 2;
};
exports.quadraticFormula = function(a, b, c) {
const D = Math.sqrt(b * b - 4 * a * c);
return [(-b + D)/(2*a), (-b -D)/(2*a)];
}
NOTE_
exports
를 사용한 단축 문법은 객체를 내보낼 때만 쓸 수 있습니다.
함수나 기타 다른 값을 내보낼 때는 반드시module.exports
를 써야 합니다. (뭐야 위 예제에선 이 단축문법으로 함수 내보냈잖아…나중에 확인해보지뭐…)
또한, 두 문법을 섞어 쓸 수도 없습니다.
모듈 하나에 한 가지 문법만 써야 합니다.