Clean Code In JavaScript — 1편

DongHyun Gu
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편에서 계속…

--

--