Clean JavaScript

이기훈·2021년 11월 2일
0

Today-I-Learned

목록 보기
1/9

Writing Clean JavaScript -  ES6 Edition

알아보기 쉬운 Code

작업을 하다보면 어떤 기준을 잡고 작업을 해야하나 항상 헷갈릴때가 있다.
내 생각으로는 코드를 보면서 코드를 짠 사람과 대화하듯이 자연스럽고 읽기 쉬운 코드로 작업을 하려고 하나 항상 중간쯤 가다보면 처음에 지켰던 원칙도 조금 지켜지지않는 경우들도 있고 코드 짜기에 급급해 동작만 하는 코드로 짜여지는 경우가 있었다.
그래서 가장 기초적인 것들 부터 지키자는 생각으로 변수명, 함수, 에러핸들링, 분기처리등을 잘 정리해놓은 블로그가 있어 간단히 정리해보았다.

1. 변수

주위에서 작업을 할때 변수명을 'a'나 'first'등 의미 없는 변수명으로 코드를 작성하는 경우를 종종 보았다.
작업량이 점점 많아지고 프로젝트가 점점 커지다 보면 의미 없는 변수명으로 작성해 놓은 코드는 오류나 추후 리팩토링시 작업량이 거대해지고 혹시나 작업 도중에 기획이나 디자인이 수정되어 코드를 수정해야 하는 경우 난감한 상황을 몇번 보았다.

의미 있는 이름 사용
본인은 웬만하면 함수명을 작성 할 때는 동사를 앞에 두고 뒤에 명사가 오도록 지어 일반 변수와 구분을 둔다.

const foo = 'name' // X

const userName = 'name'

불필요한 문맥 추가 금지

const user = {
	userId: 'abcd'
} // X

const user = {
	id: 'abcd'
}

하드코딩 값 금지

setTimeout(clearSessionData, 900000); // X

const SESSION_DURATION_MS = 15 * 60 * 1000
setTimeout(clearSessionData, SESSION_DURATION_MS);

2. 함수

설명적인 이름

function toggle(){...
} // X

function toggleThemeSwitcher(){...
}

기본 인자 사용

function printAllFilesDirectory(dir){
	const directory = dir || "./";
} // X

function printAllFilesDirectory(dir = "./"){
}

인자 개수 제한

function sendPushNotification(title, message, image, isSilent){
} // X

function sendPushNotification({title, message, image, isSilent}){
}

하나의 함수에서 여러가지 액션

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

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

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

조건문 피하기

function createFile(name, isPublic) {
  if (isPublic) {
    fs.create(`./public/${name}`);
  } else {
    fs.create(name);
  }
} // X

function createFile(name) {
  fs.create(name);
}

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

스스로 반복하지 않기 (Do not repeat yourself)

  • 자신의 코드를 복제해서 반복해서 사용 금지
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 });
  });
} // X

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

사이드 이펙트 피하기

  • 자바스크립트에서는 명령형을 기반으로한 함수 사용
  • 함수를 순수하게 유지하기
  • 모든 사이드 이펙트는 중앙화 되어야 한다.
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'];
// X

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'];
function enrollStudentInCourse(course, student) {
  course.push({ student, enrollmentDate: Date.now() });
} //  X

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

3. 상태

if-else, switch 같은 조건문을 사용하지 않는 것인데 아직 익숙하지가 않아서 항상 조건문으로 작성하곤 한다.

  • 불필요한 부정문 사용 금지
function isUserNotVerified(user) {
  // ...
}

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

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

if (isUserVerified(user)) {
  // ...
}
  • 속기 사용
if (isActive === true) {
  // ...
}

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

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

//////////////////////////// X

if (isActive) {
  // ...
}

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

const isUserEligible = user.isVerified() && user.didSubscribe();
  • 가지치기와 바로 리턴하는것 피하기
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");
  }
}
// ------------------------------X---------------------

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대신 객체 사용
const getColorByStatus = (status) => {
  switch (status) {
    case "success":
      return "green";
    case "failure":
      return "red";
    case "warning":
      return "yellow";
    case "loading":
    default:
      return "blue";
  }
};
// ------------------------X------------------------

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

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";
// ---------------------X----------------------

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

4. 동시성

  • 콜백 피하기 - await/async

5. 에러핸들링

// 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. 주석

  • 비지니스 로직에만 주석
// 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;
}
  • 버전 컨트롤 사용
// Don't ❌
/**
 * 2021-7-21: Fixed corner case
 * 2021-7-15: Improved performance
 * 2021-7-10: Handled mutliple user types
 */
function generateCanonicalLink(user) {
  // const session = getUserSession(user)
  const session = user.getSession();
  // ...
}

// Do ✅
function generateCanonicalLink(user) {
  const session = user.getSession();
  // ...
}
  • 명세서 작성
profile
Beyond Code

0개의 댓글