Clean Code In JavaScript — 1편
11 min readJul 26, 2021
모두가 처음부터 완벽한 코드를 작성할 순 없을 것입니다.
유지보수 하기 쉽고 더 읽기 좋은 코드를 작성하기 위해서 Robert C. Martin’s의 책 Clean Code에 담긴 내용을 자바스크립트 버전으로 정리하려고 합니다.
이 글은 이와 관련된 영상(https://www.youtube.com/watch?v=tz3eC9JbxQA)을 보고 작성되었음을 알립니다.
변수
- 의미 있고 발음하기 쉬운 변수명을 짓는다
// Bad
const yyyymmdstr = moment().format('YYYY/MM/DD');// Good
const currentDate = moment().format('YYYY/MM/DD');
- 유형이 동일하면 어휘도 동일하게 사용한다
// Bad
getUserInfo();
getClientData();
getCustomerRecord();// Good
getUser();
- 상수를 활용해 검색 가능한 이름을 만든다
// Bad
setTimeout(blastOff, 86400000); // 대체 86400000은 무엇을 의미하는 걸까요?// Good
const MILLISECONDS_IN_A_DAY = 86400000; // 대문자로 'const' 전역 변수를 선언하세요
setTimeout(blastOff, MILLISECONDS_IN_A_DAY);
- 의도를 드러내는 변수를 사용한다
// Bad
const address = 'One Infinite Loop, Cupertino 95014';
const cityZipCodeRegex = /^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/;
saveCityZipCode(address.match(cityZipCodeRegex)[1], address.match(cityZipCodeRegex)[2]);// Good
const address = 'One Infinite Loop, Cupertino 95014';
const cityZipCodeRegex = /^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/;
const [, city, zipCode] = address.match(cityZipCodeRegex) || [];
saveCityZipCode(city, zipCode);
함수
- 매개변수는 2개 이하가 좋다
- 함수명에는 동사를 쓸 것
- 하나의 함수는 한가지 행동만 수행해야 한다
// Bad
function emailClients(clients) {
clients.forEach(client => {
const clientRecord = database.lookup(client);
if (clientRecord.isActive()) {
email(client);
}
});
}// Good
function emailClients(clients) {
clients.filter(isClientActive).forEach(email);
}function isClientActive(client) {
const clientRecord = database.lookup(client);
return clientRecord.isActive();
}
- 명확한 이름을 짓는다
// Bad
function AddToDate(date, month) {}
const date = new Date();
AddToDate(date, 1); // 뭘 추가하는 건지 이름만 보고 알아내기 힘듭니다// Good
function AddMonthToDate(date, month) {}
const date = new Date();
AddMonthToDate(date, 1);
- 함수는 단일 행동을 추상화해야 한다 (이름이 여러 의미를 내포하면, 너무 많은 일을 수행하므로 함수를 분해해서 테스트를 쉽게 만들어야 한다)
- 중복된 코드를 제거해야 한다
// Bad
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);
});
}// Good
function showEmployeeList(employees) {
employees.forEach(employee => {
const expectedSalary = employee.calculateExpectedSalary();
const experience = employee.getExperience();
let portfolio = employee.getGithubLink();
if (employee.type === 'manager') {
portfolio = employee.getMBAProjects();
}
const data = {
expectedSalary,
experience,
portfolio
};
render(data);
});
}
- 매개변수로 플래그를 사용하면 안된다
// Bad
function createFile(name, temp) {
if (temp) {
fs.create(`./temp/${name}`);
} else {
fs.create(name);
}
}// Good
function createFile(name) {
fs.create(name);
}function createTempFile(name) {
createFile(`./temp/${name}`);
}
- 부작용을 피해야 한다
// Bad
// 아래 함수에 의해 참조되는 전역 변수입니다.
// 이 전역 변수를 사용하는 또 하나의 함수가 있다고 생각해보세요.
let name = 'Jack Ryan';
function splitIntoFirstAndLastName() {
name = name.split(' '); // 사이드 이펙트에 의해 원본이 망가짐
}
splitIntoFirstAndLastName();
console.log(name); // ['Jack', 'Ryan'];// Good
function splitIntoFirstAndLastName(name) {
return name.split(' ');
}
const name = 'Jack Ryan';
const newName = splitIntoFirstAndLastName(name);
console.log(name); // 'Jack Ryan'
console.log(newName); // ['Jack', 'Ryan'];
- prototype으로 전역 함수를 추가하지 않는다
// Bad
Array.prototype.diff = function diff(comparisonArray) {
const hash = new Set(comparisonArray);
return this.filter(elem => !hash.has(elem));
}// Good
class SuperArray extends Array {
diff(comparisonArray) {
cosnt hash = new Set(comparisonArray);
return this.filter(elem => !hash.has(elem));
}
}
- 조건문은 캡슐화 한다
// Bad
if (fsm.state === 'fetching' && isEmpty(listNode)) {}// Good
function shouldShowSpinner(fsm, listNode) {
return fsm.state === 'fetching' && isEmpty(listNode);
}if (shouldShowSpinner(fsmInstance, listNodeInstance)) {}
- 다형성 (객체지향 vs 절차지향)
// Bad
// 절차지향은 타입이 늘어날 때마다 기존 내용에 추가하는 작업을 해야한다
// 이는 새로운 것을 만드는 작업보다 리스크가 있다
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();
}
}
}// Good
// 객체지향은 메서드를 추가하려면 상속 대상도 전부 고쳐야한다
// 절차지향과 객체지향 방식은 서로 트레이드 오프가 있다는 점을 명심하자
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();
}
}
- 타입 점검을 하지 않는다(대신, TypeScript 사용을 권장)
// Bad
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'));
}
}// Good
function travelToTexas(vehicle) {
vehicle.move(this.currentLocation, new Location('texas'));
}
// Bad
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');
}// Good
function combine(val1, val2) {
return val1 + val2;
}
나머지는 2편에서 계속…