클린 코드 JavaScript

QQQ
nodejs backend
Published in
16 min readJan 18, 2021

같이 일하려면 이쁘게 써야죠.

원본: https://github.com/ryanmcdermott/clean-code-javascript

날짜 형식은 되도록 이렇게.

Bad:

#Bad:const yyyymmdstr = moment().format("YYYY/MM/DD");

Good:

Good:const currentDate = moment().format("YYYY/MM/DD");
dfdf
sdfdsf

인자에 수가 들어갈때는 숫자 통째로 넣지맙시다.

Bad:

// 8640000이 대체 뭔데?
setTimeout(blastOff, 86400000);

Good:

// 상수로 선언해줍시다.
const MILLISECONDS_IN_A_DAY = 60 * 60 * 24 * 10000; //8640000;
setTimeout(blastOff, MILLISECONDS_IN_A_DAY);

반복문의 인자로 i, j 와 같은 의미없는걸 쓰지맙시다.

Good:

const locations = ["Austin", "New York", "San Francisco"];
locations.forEach(location => { // 의미를 알아볼수 있는 걸로 이용하세요.
doStuff();
doSomeOtherStuff();
// ...
// ...
// ...
dispatch(location);
});

필요없는 표현은 자제!

Bad:

const Car = {
carMake: "Honda",
carModel: "Accord",
carColor: "Blue"
};

Good:

const Car = {
make: "Honda",
model: "Accord",
color: "Blue"
};
function paintCar(car) {
car.color = "Red";
}

인자에 디폴트 값을 넣으면 많은 도움이 될 것입니다. 특히 undefine, null 에러가 덜 나올 것입니다.

||(or)를 이용하여 인자가 없을 때를 처리할수도 있지만, 함수 이름을 짓고, 인자를 설정할 때 디폴트 값을 설정해두면 값이 안들어올 때의 예외 처리를 까먹을 확률이 줄어듭니다.

Bad:

function createMicrobrewery(name) {
const breweryName = name || "Hipster Brew Co.";
// ...
}

Good:

function createMicrobrewery(name = "Hipster Brew Co.") {
// ...
}

함수의 인자는 2개 혹은 적은 것이 이상적입니다.

인자의 갯수를 제한하는 것이 테스트를 진행할때 정말 큰 도움이 됩니다. 만약 3개가 넘는다면, 테스트를 진행해야 할때 상황이 너무나 많기 때문에 테스트 코드 설계에서부터 귀찮아질 수 있습니다. 3개 이상의 인자는 최대한 피해야 합니다. 3개가 넘는다면 그 함수는 너무나 많은 일을 한다는 걸 뜻합니다. 한 함수가 여러 일을 하고 있다면 분리시키는 걸 고려해보세요.

인자의 갯수가 많다면 객체로 넣읍시다.

Bad:

function createMenu(title, body, buttonText, cancellable) {
// ...
}
createMenu("Foo", "Bar", "Baz", true);

Good:

function createMenu({ title, body, buttonText, cancellable }) {
// ...
}
createMenu({
title: "Foo",
body: "Bar",
buttonText: "Baz",
cancellable: true
});

함수는 반드시 한가지 일만 해야합니다.

소프트웨어 엔지니어링에서 제일 중요한 것이 이것입니다. 한개의 함수가 한가지 이상의 일을 한다면 애초에 설계하기 힘들어지고, 테스트도 힘들어질 뿐더러, 다른 사람이 코드를 파악하기 힘들어집니다. 한가지 일만하게 된다면 리펙토링하기 정말 쉬워지고 가독성 또한 높아집니다. 이번 글에서 다른 것들은 안지켜도 되지만 이것만은 꼭 지키세요 !

Bad:

function emailClients(clients) {
clients.forEach(client => {
const clientRecord = database.lookup(client);
if (clientRecord.isActive()) {
email(client);
}
});
}

Good:

function emailActiveClients(clients) {
clients.filter(isActiveClient).forEach(email);
}
function isActiveClient(client) {
const clientRecord = database.lookup(client);
return clientRecord.isActive();
}

함수 이름은 함수가 뭘하는지 말해줘야합니다.

Bad:

function addToDate(date, month) {
// ...
}
const date = new Date();// 뭘 더하는지 알지 못하죠.
addToDate(date, 1);

Good:

function addMonthToDate(month, date) {
// ...
}
const date = new Date();
addMonthToDate(1, date);

코드의 중복을 최대한 피합시다.

같은 일지만 역할에 따라 보여주는 자료가 조금 다를 때 간혹 함수를 아에 두개 만들어버릴 때가 있습니다. 그럴 때는 한 함수내에서 결과를 분기 시키는게 좋습니다.

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();
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” 값으로 세팅하새요.

Bad:

const menuConfig = {
title: null,
body: "Bar",
buttonText: null,
cancellable: true
};
function createMenu(config) {
confit.title = config.title || "Foo";
config.body = config.body || "Bar";
config.buttonText = config.buttonText || "Baz"
config.cancellabel =
config.cancellabel !== undefined ? config.cancellable : true;
}
createMenu(menuConfig);

Good:

cont menuConfig = {
title: "Order",
// body를 입력하지 않은 상황.
buttonText: "Send",
cancellable: true
};
function createMenu(config) {
let finalConfig = Object.assign( // assing으로 알아서 값이 비지않게 합니다. !
{
title: "Foo",
body: "Bar",
buttonText: "Baz",
cancellable: true
},
config
);
return finalConfig
// config == { title: "Order", body: "Bar", buttonText: "Send", cancellabel: true }
}
createMenu(menuConfig);

분기에 따라 다른 함수를 실행시키게 하면 안됩니다.(번역을 잘 못하겠습니다. 코드로 봐주세요 !);

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:

// 아래 함수에서 글로벌 변수를 참조하고 있습니다.
// 만약에 name 변수를 다른 함수에서도 사용하고 있다면 name 변수의 변화를 예측할 수 없게 됩니다.
let name = "Ryan McDermott";
function splitIntoFirstAndLastName() {
name = name.split(" ");
}
splitIntoFirstAndLastName();console.log(name); // ['Ryan', 'McDermott'];

Good:

function splitIntoFirstAndLastName(name) {
return name.split(" ");
}
const name = "Ryan McDermott";
const newName = splitIntoFirstAndLastName(name);
console.log(name); // 'Ryan McDermott';
console.log(newName); // ['Ryan', 'McDermott'];

되도록 명령형 보다는 함수형 프로그래밍을 하세요.

Bad:

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;
}

Good:

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
);

조건문을 캡슐화하세요. !

Bad:

if (fsm.state === "fetching" && isEmpty(listNode)) {
// ...
}

Good:

function shouldShowSpinner(fsm, listNode) {
return fsm.state === "fetching" && isEmpty(listNode);
}
if (shouldShowSpinner(fsmInstance, listNodeInstance)) {
// ...
}

부정적인 조건문은 피하세요(isFalse() 와 같은 것들은 피하세요)

Bad:

function isDOMNodeNotPresent(node) {
// ...
}
if (!isDOMNodeNotPresent(node)) {
// ...
}

Good:

function isDOMNodePresent(node) {
// ...
}
if (isDOMNodePresent(node)) {
// ...
}

조건문을 피합시다 !

“어떻게 if문을 쓰지않고 프로그래밍을 합니까?”라고 말할수 있습니다. 그런데 아마도 “다형성”을 이용하시면 꽤 많이 피하실 수 있을 것입니다.
“왜 다형성을 이용해야 합니까?”라고 묻는다면, 지금까지 우리가 알아본 “함수는 한가지 일만을 해야한다” 이 문장 하나만으로도 설명이 됩니다. 코드를 보죠

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();
}
}

Objects and Data Structures

게터와 세터를 씁시다.

Bad:

function makeBankAccount() {
// ...
return {
balance: 0
// ...
};
}
const account = makeBankAccount();
account.balance = 100;

Good:

function makeBankAccount() {
// this one is private
let balance = 0;
// a "getter", made public via the returned object below
function getBalance() {
return balance;
}
// a "setter", made public via the returned object below
function setBalance(amount) {
// ... validate before updating the balance
balance = amount;
}
return {
// ...
getBalance,
setBalance
};
}
const account = makeBankAccount();
account.setBalance(100);

Classes

SOLID — Single Responsibility Principle (SRP)

클래스가 너무 커져버리면 클래스의 역할이 무엇인지 알아차리기도 힘들고 변경이 필요할 때 쉽게 건들이지 못합니다. 용도에 맞게 나누어 최소한의 크기로 유지하는 것이 좋습니다.

Bac:

const UserSettings {
constructor(user) {
this.user = user;
}
changeSettings(settings) {
if (this.verifyCredentials()) {
// ...
}
}
verifyCredentials() {
// ...
}
}

Good:

class UserAuth {
constructor(user) {
this.user = user;
}
verifyCredentials() {
// ...
}
}
class UserSettings {
constructor(user) {
this.user = user;
this.auth = new UserAuth(user);
}
changeSettings(settings) {
if (this.auth.verifyCredentials()) {
// ...
}
}
}

너무 유익한 내용입니다. 글로 정리하긴 했지만 몇번씩 봐가면서 제 코드 습관으로 만들어야겠네요.

--

--