함수
함수 인자(가급적 2개 이하)
함수 매개변수 개수 제한은 테스트 용이성을 위해 매우 중요합니다. 3개 이상의 매개변수가 존재할 경우 각 인자의 조합적 폭발이 발생하여 다양한 테스트 케이스를 모두 검증해야 합니다.
1-2개의 인자가 이상적이며 3개 이상은 가능한 피해야 합니다. 더 많은 매개변수가 필요할 경우 일반적으로 상위 레벨 객체를 인자로 전달하는 것이 효과적입니다. 이는 함수가 여러 책임을 지고 있음을 의미할 수 있습니다.
JavaScript의 동적 객체 생성 기능을 활용하면 다수 인자 대신 단일 객체를 사용할 수 있습니다.
ES2015/ES6 구조 분해 할당 문법을 사용하면 함수가 기대하는 속성을 명확히 할 수 있습니다. 이 접근법의 장점은 다음과 같습니다:
- 함수 시그니처를 확인하는 즉시 사용되는 속성을 파악 가능
- 명명된 매개변수(named parameters) 시뮬레이션 가능
- 구조 분해는 인자 객체의 기본형 값을 복제하므로 부수 효과 방지에 도움(※ 객체/배열은 복제되지 않음)
- 사용되지 않은 속성에 대한 린터 경고 활성화 가능
나쁜 예:
function createMenu(title, body, buttonText, cancellable) {
// ...
}
createMenu("Foo", "Bar", "Baz", true);좋은 예:
function createMenu({ title, body, buttonText, cancellable }) {
// ...
}
createMenu({
title: "Foo",
body: "Bar",
buttonText: "Baz",
cancellable: true
});단일 책임 원칙 준수
이 규칙은 소프트웨어 공학에서 가장 중요한 원칙입니다. 다중 기능을 수행하는 함수는 조합, 테스트, 이해가 어려워지며, 단일 액션으로 격리된 함수는 리팩토링과 가독성이 우수합니다. 본 가이드 중 이 원칙만 적용해도 개발 역량이 향상됩니다.
나쁜 예:
function emailClients(clients) {
clients.forEach(client => {
const clientRecord = database.lookup(client);
if (clientRecord.isActive()) {
email(client);
}
});
}좋은 예:
function emailActiveClients(clients) {
clients.filter(isActiveClient).forEach(email);
}
function isActiveClient(client) {
const clientRecord = database.lookup(client);
return clientRecord.isActive();
}함수명은 기능을 명시해야 함
나쁜 예:
function addToDate(date, month) {
// ...
}
const date = new Date();
// It's hard to tell from the function name what is added
addToDate(date, 1);좋은 예:
function addMonthToDate(month, date) {
// ...
}
const date = new Date();
addMonthToDate(1, date);단일 추상화 수준 유지
다중 추상화 수준은 함수의 과도한 복잡성을 나타냅니다. 함수 분할을 통해 재사용성과 테스트 용이성을 확보하세요.
나쁜 예:
function parseBetterJSAlternative(code) {
const REGEXES = [
// ...
];
const statements = code.split(" ");
const tokens = [];
REGEXES.forEach(REGEX => {
statements.forEach(statement => {
// ...
});
});
const ast = [];
tokens.forEach(token => {
// lex...
});
ast.forEach(node => {
// parse...
});
}좋은 예:
function parseBetterJSAlternative(code) {
const tokens = tokenize(code);
const syntaxTree = parse(tokens);
syntaxTree.forEach(node => {
// parse...
});
}
function tokenize(code) {
const REGEXES = [
// ...
];
const statements = code.split(" ");
const tokens = [];
REGEXES.forEach(REGEX => {
statements.forEach(statement => {
tokens.push(/* ... */);
});
});
return tokens;
}
function parse(tokens) {
const syntaxTree = [];
tokens.forEach(token => {
syntaxTree.push(/* ... */);
});
return syntaxTree;
}중복 코드 제거
중복 코드는 로직 변경 시 다수 위치를 수정해야 하므로 철저히 방지해야 합니다.
레스토랑 재고 관리 사례로 설명: 토마토 재고를 여러 목록에서 관리하면 모든 목록을 업데이트해야 하지만, 단일 목록 사용 시 한 번만 수정하면 됩니다.
유사하지만 약간 다른 기능들로 인한 중복 코드 발생 시 추상화를 통해 통합 구현해야 합니다. SOLID 원칙에 기반한 적절한 추상화 설계가 핵심입니다.
잘못된 추상화는 중복 코드보다 해로울 수 있으므로 주의해야 합니다. 효과적인 추상화 구현 시 변경 관리가 용이해집니다.
나쁜 예:
function showDeveloperList(developers) {
developers.forEach(developer => {
const expectedSalary = developer.calculateExpectedSalary();
const experience = developer.getExperience();
const githubLink = developer.getGithubLink();
const data = {
expectedSalary,
experience,
githubLink
};
render(data);
});
}
function showManagerList(managers) {
managers.forEach(manager => {
const expectedSalary = manager.calculateExpectedSalary();
const experience = manager.getExperience();
const portfolio = manager.getMBAProjects();
const data = {
expectedSalary,
experience,
portfolio
};
render(data);
});
}좋은 예:
function showEmployeeList(employees) {
employees.forEach(employee => {
const expectedSalary = employee.calculateExpectedSalary();
const experience = employee.getExperience();
const data = {
expectedSalary,
experience
};
switch (employee.type) {
case "manager":
data.portfolio = employee.getMBAProjects();
break;
case "developer":
data.githubLink = employee.getGithubLink();
break;
}
render(data);
});
}Object.assign으로 기본 객체 설정
나쁜 예:
const menuConfig = {
title: null,
body: "Bar",
buttonText: null,
cancellable: true
};
function createMenu(config) {
config.title = config.title || "Foo";
config.body = config.body || "Bar";
config.buttonText = config.buttonText || "Baz";
config.cancellable =
config.cancellable !== undefined ? config.cancellable : true;
}
createMenu(menuConfig);좋은 예:
const menuConfig = {
title: "Order",
// User did not include 'body' key
buttonText: "Send",
cancellable: true
};
function createMenu(config) {
let finalConfig = Object.assign(
{
title: "Foo",
body: "Bar",
buttonText: "Baz",
cancellable: true
},
config
);
return finalConfig
// config now equals: {title: "Order", body: "Bar", buttonText: "Send", cancellable: true}
// ...
}
createMenu(menuConfig);플래그 매개변수 사용 금지
플래그는 함수가 다중 기능을 수행함을 의미합니다. 부울 값에 따른 분기 처리가 필요한 경우 함수를 분리해야 합니다.
나쁜 예:
function createFile(name, temp) {
if (temp) {
fs.create(`./temp/${name}`);
} else {
fs.create(name);
}
}좋은 예:
function createFile(name) {
fs.create(name);
}
function createTempFile(name) {
createFile(`./temp/${name}`);
}부수 효과 방지 (1부)
부수 효과는 파일 기록/전역 변수 변경 등 입출력 외 시스템 상태 변경을 의미합니다. 필요한 경우 서비스 계층에서 중앙 집중 관리해야 합니다.
공유 상태/가변 데이터 타입 사용을 피하고 부수 효과 발생 지점을 통제해야 합니다. 체계적인 관리가 가능하면 유지보수성이 크게 향상됩니다.
나쁜 예:
좋은 예:
// Global variable referenced by following function.
// If we had another function that used this name, now it'd be an array and it could break it.
let name = "Ryan McDermott";
function splitIntoFirstAndLastName() {
name = name.split(" ");
}
splitIntoFirstAndLastName();
console.log(name); // ['Ryan', 'McDermott'];function splitIntoFirstAndLastName(name) {
return name.split(" ");
}
const name = "Ryan McDermott";
const newName = splitIntoFirstAndLastName(name);
console.log(name); // 'Ryan McDermott';
console.log(newName); // ['Ryan', 'McDermott'];부수 효과 방지 (2부)
JavaScript 객체/배열은 변경 가능하므로 함수 매개변수로 전달 시 주의가 필요합니다. 예시 쇼핑 카트 수정 시 의도치 않은 부수 효과 발생 가능성을 설명합니다.
네트워크 요청 중 사용자가 실수로 상품 추가 시 예상치 못한 데이터 전송 문제 발생 사례를 제시합니다.
클론 생성 후 수정 반영 방식으로 해결책 제시: addItemToCart 함수가 cart 복제본을 반환하도록 구현합니다.
주의 사항 2가지: 입력 객체 수정이 필요한 드문 경우 고려, 대형 객체 복제 시 성능 문제(Immutable.js 등의 라이브러리 사용 권장)
나쁜 예:
좋은 예:
전역 함수 수정 금지
전역 공간 오염은 라이브러리 충돌 위험을 초래합니다. Array.prototype 확장 대신 ES6 클래스 상속을 사용해야 합니다.
const addItemToCart = (cart, item) => {
cart.push({ item, date: Date.now() });
};나쁜 예:
const addItemToCart = (cart, item) => {
return [...cart, { item, date: Date.now() }];
};좋은 예:
⬆ 상단으로
명령형보다 함수형 프로그래밍 지향
JavaScript는 함수형 패러다임을 지원하므로 더 깔끔하고 테스트 가능한 코드 작성을 위해 함수형 스타일을 권장합니다.
Array.prototype.diff = function diff(comparisonArray) {
const hash = new Set(comparisonArray);
return this.filter(elem => !hash.has(elem));
};나쁜 예:
class SuperArray extends Array {
diff(comparisonArray) {
const hash = new Set(comparisonArray);
return this.filter(elem => !hash.has(elem));
}
}좋은 예:
⬆ 상단으로
조건문 캡슐화
나쁜 예:
const programmerOutput = [
{
name: "Uncle Bobby",
linesOfCode: 500
},
{
name: "Suzie Q",
linesOfCode: 1500
},
{
name: "Jimmy Gosling",
linesOfCode: 150
},
{
name: "Gracie Hopper",
linesOfCode: 1000
}
];
let totalOutput = 0;
for (let i = 0; i < programmerOutput.length; i++) {
totalOutput += programmerOutput[i].linesOfCode;
}좋은 예:
const programmerOutput = [
{
name: "Uncle Bobby",
linesOfCode: 500
},
{
name: "Suzie Q",
linesOfCode: 1500
},
{
name: "Jimmy Gosling",
linesOfCode: 150
},
{
name: "Gracie Hopper",
linesOfCode: 1000
}
];
const totalOutput = programmerOutput.reduce(
(totalLines, output) => totalLines + output.linesOfCode,
0
);부정 조건문 회피
나쁜 예:
if (fsm.state === "fetching" && isEmpty(listNode)) {
// ...
}좋은 예:
function shouldShowSpinner(fsm, listNode) {
return fsm.state === "fetching" && isEmpty(listNode);
}
if (shouldShowSpinner(fsmInstance, listNodeInstance)) {
// ...
}조건문 최소화
다형성을 활용해 조건문을 대체할 수 있습니다. 함수 단일 책임 원칙을 준수하면 if 문 사용을 크게 줄일 수 있습니다.
function isDOMNodeNotPresent(node) {
// ...
}
if (!isDOMNodeNotPresent(node)) {
// ...
}나쁜 예:
function isDOMNodePresent(node) {
// ...
}
if (isDOMNodePresent(node)) {
// ...
}좋은 예:
⬆ 상단으로
타입 체크 회피 (1부)
JavaScript의 동적 타이핑 특성상 타입 체크를 유혹받을 수 있지만 일관된 API 설계로 이를 방지해야 합니다.
class Airplane {
// ...
getCruisingAltitude() {
switch (this.type) {
case "777":
return this.getMaxAltitude() - this.getPassengerCount();
case "Air Force One":
return this.getMaxAltitude();
case "Cessna":
return this.getMaxAltitude() - this.getFuelExpenditure();
}
}
}나쁜 예:
class Airplane {
// ...
}
class Boeing777 extends Airplane {
// ...
getCruisingAltitude() {
return this.getMaxAltitude() - this.getPassengerCount();
}
}
class AirForceOne extends Airplane {
// ...
getCruisingAltitude() {
return this.getMaxAltitude();
}
}
class Cessna extends Airplane {
// ...
getCruisingAltitude() {
return this.getMaxAltitude() - this.getFuelExpenditure();
}
}좋은 예:
⬆ 상단으로
타입 체크 회피 (2부)
기본형 값 다룰 때 TypeScript 사용을 고려해야 합니다. 수동 타입 체크는 가독성을 저해하므로 깨끗한 코드/테스트/리뷰 프로세스를 유지해야 합니다.
function travelToTexas(vehicle) {
if (vehicle instanceof Bicycle) {
vehicle.pedal(this.currentLocation, new Location("texas"));
} else if (vehicle instanceof Car) {
vehicle.drive(this.currentLocation, new Location("texas"));
}
}나쁜 예:
function travelToTexas(vehicle) {
vehicle.move(this.currentLocation, new Location("texas"));
}좋은 예:
⬆ 상단으로
과도한 최적화 방지
현대 브라우저는 자체 최적화 기능을 보유하므로 최적화 병목 지점 분석 자료를 참고해 목표를 명확히 해야 합니다.
function combine(val1, val2) {
if (
(typeof val1 === "number" && typeof val2 === "number") ||
(typeof val1 === "string" && typeof val2 === "string")
) {
return val1 + val2;
}
throw new Error("Must be of type String or Number");
}나쁜 예:
function combine(val1, val2) {
return val1 + val2;
}좋은 예:
⬆ 상단으로
사용되지 않는 코드 제거
데드 코드는 중복 코드와 동일한 문제를 유발합니다. 버전 관리 시스템에 기록이 남으므로 즉시 삭제해야 합니다.
// On old browsers, each iteration with uncached `list.length` would be costly
// because of `list.length` recomputation. In modern browsers, this is optimized.
for (let i = 0, len = list.length; i < len; i++) {
// ...
}나쁜 예:
for (let i = 0; i < list.length; i++) {
// ...
}좋은 예:
⬆ 상단으로
$$101$$
$$102$$
function oldRequestModule(url) {
// ...
}
function newRequestModule(url) {
// ...
}
const req = newRequestModule;
inventoryTracker("apples", req, "www.inventory-awesome.io");$$103$$
function newRequestModule(url) {
// ...
}
const req = newRequestModule;
inventoryTracker("apples", req, "www.inventory-awesome.io");$$104$$