JavaScript

Clean JavaScript(클린코드)

단점이없어지고싶은개발자 2022. 7. 31. 15:08
반응형

이미 자바스크립트를 공부하면서 한 번은 읽었고, 코드를 작성하면서 적용하고 있는 주제지만, 클린코드라는 책을 가지고 스터디를 하면서 전체적인 내용을 요약해놓은 블로그 글이 있어서 번역하면서 재차 정리하는 글이다.


클린 코드는 단순하게 작동하는 코드가 아니라 다른 사람들이 코드를 쉽게 읽고, 재사용하고, 리팩토링할 수 있는 코드다.

협업을 하면서 깨끗하게 코드를 작성하는 것은 매우 중요하다. 

아래 예제들은 JavaScript 코드로 되어있지만, 거의 모든 다른 프로그래밍 언어에 적용할 수 있다. 일반적인 개념들은 Robert C. Martin의 Clean Code에서 주로 채택한 권장사항이기 때문에 따르지 않아도 된다. 권장사항이다.

 

1. Variables - 의미있는 변수 이름 짓기

변수의 이름은 설명적이어야 한다. 경험상 대부분의 JavaScript 변수는 CamelCase로 짓는다.

// Don't ❌
const foo = "JDoe@example.com";
const bar = "John";
const age = 23;
const qux = true;

// Do ✅
const email = "John@example.com";
const firstName = "John";
const age = 23;
const isActive = true

일반적으로 boolean의 이름은 아래와 같이 짓는다

isActive
didSubscribe
hasLinkedAccount

불필요한 컨텍스트를 추가하지 말자. 이미 객체 이름으로, 클래스 이름에 제공한 경우 변수 이름에 중복적인 컨텍스트를 피해야한다.

// Don't ❌
const user = {
  userId: "296e2589-7b33-400a-b762-007b730c8e6d",
  userEmail: "JDoe@example.com",
  userFirstName: "John",
  userLastName: "Doe",
  userAge: 23,
};

user.userId;

// Do ✅
const user = {
  id: "296e2589-7b33-400a-b762-007b730c8e6d",
  email: "JDoe@example.com",
  firstName: "John",
  lastName: "Doe",
  age: 23,
};

user.id;

하드코딩된 값을 피하기 위해서 상수(const)를 사용하자. 대신 의미 있고 검색 가능한 상수를 선언해야 한다.

전역 상수는 보통 Screaming Snake Case로 작성한다

// Don't ❌
setTimeout(clearSessionData, 900000);

// Do ✅
const SESSION_DURATION_MS = 15 * 60 * 1000;

setTimeout(clearSessionData, SESSION_DURATION_MS);

 

2. Functions - 설명이 포함된 이름을 사용

함수 이름은 함수가 실제로 수행하는 작업을 나타내는 이름으로 짓는다. 이름은 일반적으로 동사의 형태로 짓는다.

// Don't ❌
function toggle() {
  // ...
}

function agreed(user) {
  // ...
}

// Do ✅
function toggleThemeSwitcher() {
  // ...
}

function didAgreeToAllTerms(user) {
  // ...
}

매개변수의 기본인수를 함수 내부안에서 추가 조건문을 사용하는것보다 매개변수의 default 값으로 설정을 해놓으면 훨씬 깔끔하다. 그러나 여기서 기본 인수는 undefined만 대체한다는 것을 기억하자. 

// Don't ❌
function printAllFilesInDirectory(dir) {
  const directory = dir || "./";
  //   ...
}

// Do ✅
function printAllFilesInDirectory(dir = "./") {
  // ...
}

함수 안에 인자를 넘겨줄 때, 객체구조화를 사용해 인자를 넘겨주자.

// Don't ❌
function sendPushNotification(title, message, image, isSilent, delayMs) {
  // ...
}

sendPushNotification("New Message", "...", "http://...", false, 1000);

// Do ✅
function sendPushNotification({ title, message, image, isSilent, delayMs }) {
  // ...
}

const notificationConfig = {
  title: "New Message",
  message: "...",
  image: "http://...",
  isSilent: false,
  delayMs: 1000,
};

sendPushNotification(notificationConfig);

한 함수 안에서 한 가지 일만하자. 이 규칙으로 함수의 크기와 복잡성을 줄이는데 도움이 되므로 추후 테스트, 디버깅 및 리팩토링이 더 쉬워진다. 그래서 일반적으로 20-30줄 미만의 코드를 목표로 삼자.

// Don't ❌
function pingUsers(users) {
  users.forEach((user) => {
    const userRecord = database.lookup(user);
    if (!userRecord.isActive()) {
      ping(user);
    }
  });
}

// Do ✅
function pingInactiveUsers(users) {
  users.filter(!isUserActive).forEach(ping);
}

function isUserActive(user) {
  const userRecord = database.lookup(user);
  return userRecord.isActive();
}

플레그를 인수로 사용하지 말자. 플래그는 함수를 단순화할 수 있다. 위와 같다. 한 함수는 하나의 일만.

// Don't ❌
function createFile(name, isPublic) {
  if (isPublic) {
    fs.create(`./public/${name}`);
  } else {
    fs.create(name);
  }
}

// Do ✅
function createFile(name) {
  fs.create(name);
}

function createPublicFile(name) {
  createFile(`./public/${name}`);
}

중복된 코드는 결코 좋지 않다. 

// Don't ❌
function renderCarsList(cars) {
  cars.forEach((car) => {
    const price = car.getPrice();
    const make = car.getMake();
    const brand = car.getBrand();
    const nbOfDoors = car.getNbOfDoors();

    render({ price, make, brand, nbOfDoors });
  });
}

function renderMotorcyclesList(motorcycles) {
  motorcycles.forEach((motorcycle) => {
    const price = motorcycle.getPrice();
    const make = motorcycle.getMake();
    const brand = motorcycle.getBrand();
    const seatHeight = motorcycle.getSeatHeight();

    render({ price, make, brand, seatHeight });
  });
}

// Do ✅
function renderVehiclesList(vehicles) {
  vehicles.forEach((vehicle) => {
    const price = vehicle.getPrice();
    const make = vehicle.getMake();
    const brand = vehicle.getBrand();

    const data = { price, make, brand };

    switch (vehicle.type) {
      case "car":
        data.nbOfDoors = vehicle.getNbOfDoors();
        break;
      case "motorcycle":
        data.seatHeight = vehicle.getSeatHeight();
        break;
    }

    render(data);
  });
}

자바스크립트는 명령형 패턴보다 기능적 패턴을 선호해야 한다. 즉, 필요없는 함수를 순수하게 유지해야한다. 그래서 어떤 한 함수를 사용하면 동일하게 작동해야한다. 

// Don't ❌
let date = "21-8-2021";

function splitIntoDayMonthYear() {
  date = date.split("-");
}

splitIntoDayMonthYear();

// Another function could be expecting date as a string
console.log(date); // ['21', '8', '2021'];

// Do ✅
function splitIntoDayMonthYear(date) {
  return date.split("-");
}

const date = "21-8-2021";
const newDate = splitIntoDayMonthYear(date);

// Original vlaue is intact
console.log(date); // '21-8-2021';
console.log(newDate); // ['21', '8', '2021'];

또한, 변경 가능한 값이 함수에 전달되는 경우 값을 직접 변경하고 반환시키지 말고, 변경된 새로 복제시켜 반환시킨다. spread 연산자를 사용하자

// Don't ❌
function enrollStudentInCourse(course, student) {
  course.push({ student, enrollmentDate: Date.now() });
}

// Do ✅
function enrollStudentInCourse(course, student) {
  return [...course, { student, enrollmentDate: Date.now() }];
}

 

3. Conditionals - 음수가 아닌 조건문 사용

// Don't ❌
function isUserNotVerified(user) {
  // ...
}

if (!isUserNotVerified(user)) {
  // ...
}

// Do ✅
function isUserVerified(user) {
  // ...
}

if (isUserVerified(user)) {
  // ...
}

가능하면 조건부 안은 줄여서 작성하자

// Don't ❌
if (isActive === true) {
  // ...
}

if (firstName !== "" && firstName !== null && firstName !== undefined) {
  // ...
}

const isUserEligible = user.isVerified() && user.didSubscribe() ? true : false;

// Do ✅
if (isActive) {
  // ...
}

if (!!firstName) {
  // ...
}

const isUserEligible = user.isVerified() && user.didSubscribe();

early return을 사용해서 일찍 반환시켜 코드를 선형적이고 읽기 쉽고 덜 복잡하게 만들자

// Don't ❌
function addUserService(db, user) {
  if (!db) {
    if (!db.isConnected()) {
      if (!user) {
        return db.insert("users", user);
      } else {
        throw new Error("No user");
      }
    } else {
      throw new Error("No database connection");
    }
  } else {
    throw new Error("No database");
  }
}

// Do ✅
function addUserService(db, user) {
  if (!db) throw new Error("No database");
  if (!db.isConnected()) throw new Error("No database connection");
  if (!user) throw new Error("No user");

  return db.insert("users", user);
}

switch문보다 객체 리터럴 또는 map등을 적용시켜서 코드를 줄이고, 성능을 향상시키자.

// Don't ❌
const getColorByStatus = (status) => {
  switch (status) {
    case "success":
      return "green";
    case "failure":
      return "red";
    case "warning":
      return "yellow";
    case "loading":
    default:
      return "blue";
  }
};

// Do ✅
const statusColors = {
  success: "green",
  failure: "red",
  warning: "yellow",
  loading: "blue",
};

const getColorByStatus = (status) => statusColors[status] || "blue";

옵셔널 체이닝 사용하기

const user = {
  email: "JDoe@example.com",
  billing: {
    iban: "...",
    swift: "...",
    address: {
      street: "Some Street Name",
      state: "CA",
    },
  },
};

// Don't ❌
const email = (user && user.email) || "N/A";
const street =
  (user &&
    user.billing &&
    user.billing.address &&
    user.billing.address.street) ||
  "N/A";
const state =
  (user &&
    user.billing &&
    user.billing.address &&
    user.billing.address.state) ||
  "N/A";

// Do ✅
const email = user?.email || "N/A";
const street = user?.billing?.address?.street || "N/A";
const state = user?.billing?.address?.state || "N/A";

4. Concurrency - 콜백을 피하자

콜백은 지저분하고 중첩된 코드를 생성한다. ES6는 콜백되신 깔끔하게 코드를 만드는 Promise를 제공한다. 그리고 ECMAScript 2017은 Async/Await구문을 통해 더 간결하게 동시성을 처리할 수 있게 됐다. 그리고 곧 Async를 쓰지 않고, Await만 사용해 동시성을 사용할 수 있게 된다.

// Don't ❌
getUser(function (err, user) {
  getProfile(user, function (err, profile) {
    getAccount(profile, function (err, account) {
      getReports(account, function (err, reports) {
        sendStatistics(reports, function (err) {
          console.error(err);
        });
      });
    });
  });
});

// Do ✅
getUser()
  .then(getProfile)
  .then(getAccount)
  .then(getReports)
  .then(sendStatistics)
  .catch((err) => console.error(err));

// or using Async/Await ✅✅

async function sendUserStatistics() {
  try {
    const user = await getUser();
    const profile = await getProfile(user);
    const account = await getAccount(profile);
    const reports = await getReports(account);
    return sendStatistics(reports);
  } catch (e) {
    console.error(err);
  }
}

5. Error Handling 

Error Handling은 매우 중요하다. 오류를 올바르게 처리하는데 시간을 할애하면 코드가 추후 버그를 다시 재차 찾을 시간을 줄일 수 있다.

// Don't ❌
try {
  // Possible erronous code
} catch (e) {
  console.log(e);
}

// Do ✅
try {
  // Possible erronous code
} catch (e) {
  // Follow the most applicable (or all):
  // 1- More suitable than console.log
  console.error(e);

  // 2- Notify user if applicable
  alertUserOfError(e);

  // 3- Report to server
  reportErrorToServer(e);

  // 4- Use a custom error handler
  throw new CustomError(e);
}

6. Comments

과도하게 주석을 달지 말자. 복잡한 논리에만 주석을 달자. 하지만 개인적인 의견으로는 주석이 없는 코드가 가장 깔끔하고, 진정한 클린코드가 아닐까..? 

// Don't ❌
function generateHash(str) {
  // Hash variable
  let hash = 0;

  // Get the length of the string
  let length = str.length;

  // If the string is empty return
  if (!length) {
    return hash;
  }

  // Loop through every character in the string
  for (let i = 0; i < length; i++) {
    // Get character code.
    const char = str.charCodeAt(i);

    // Make the hash
    hash = (hash << 5) - hash + char;

    // Convert to 32-bit integer
    hash &= hash;
  }
}

// Do ✅
function generateHash(str) {
  let hash = 0;
  let length = str.length;
  if (!length) {
    return hash;
  }

  for (let i = 0; i < length; i++) {
    const char = str.charCodeAt(i);
    hash = (hash << 5) - hash + char;
    hash = hash & hash; // Convert to 32bit integer
  }
  return hash;
}

가능한 경우 문서화를 시키자. 문서화는 코드와 안정성을 높이는데 도움이 된다. 누구나 코드의 모든 측면을 이해할 수 있는 코드베이스의 역할을 한다.

/**  
 * Returns x raised to the n-th power.  
 *  
 * @param {number} x The number to raise.  
 * @param {number} n The power, should be a natural number.  
 * @return {number} x raised to the n-th power.  
 */ 
function pow(x, n) {   
    // ...
}

 

https://medium.com/geekculture/writing-clean-javascript-es6-edition-834e83abc746

 

Writing Clean JavaScript —  ES6 Edition

Clean code means you are not just writing code that works, but rather code that can be easily read, reused, and refactored by others…

medium.com

 

반응형