본문 바로가기
Programming/4. JavaScript & React

015_자바스크립트에서의 클린 코드 (변수, 함수 편) ✏️

by @sangseophwang 2022. 4. 4.
본 글은 clean-code-javascript 레포지토리를 참고해 작성했습니다.

출처 : https://github.com/qkraudghgh/clean-code-javascript-ko

1. 변수

의미있고 발음하기 쉬운 변수 이름을 사용할 것
// 안 좋은 예
const yyyymmdstr = moment().format('YYYY/MM/DD');

// 좋은 예
const currentDate = moment().format('YYYY/MM/DD');

 

동일한 유형의 변수에 동일한 어휘를 사용할 것
// 안 좋은 예
getUserInfo();
getClientData();
getCustomerRecord();

// 좋은 예
getUser();

 

검색가능한 이름을 사용할 것
// 안 좋은 예
// 대체 86400000 무엇을 의미하는 걸까요?
setTimeout(blastOff, 86400000);


// 좋은 예
// 대문자로 `const` 전역 변수를 선언하세요
const MILLISECONDS_IN_A_DAY = 86400000;
setTimeout(blastOff, MILLISECONDS_IN_A_DAY);

 

의도를 나타내는 변수들을 사용할 것
// 안 좋은 예
const address = 'One Infinite Loop, Cupertino 95014';
const cityZipCodeRegex = /^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/;
saveCityZipCode(address.match(cityZipCodeRegex)[1], address.match(cityZipCodeRegex)[2]);

// 좋은 예
const address = 'One Infinite Loop, Cupertino 95014';
const cityZipCodeRegex = /^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/;
const [, city, zipCode] = address.match(cityZipCodeRegex) || [];
saveCityZipCode(city, zipCode);

 

명시적으로 작성할 것
// l과 location의 차이를 확인해보세요.

// 안 좋은 예
const locations = ['서울', '인천', '수원'];
locations.forEach(l => {
  doStuff();
  doSomeOtherStuff();
  // ...
  // ...
  // ...
  // 잠깐, `l`은 또 뭘까요?
  dispatch(l);
});

// 좋은 예
const locations = ['서울', '인천', '수원'];
locations.forEach(location => {
  doStuff();
  doSomeOtherStuff();
  // ...
  // ...
  // ...
  dispatch(location);
});

 

문맥상 필요없는 것들은 쓰지 말 것
// carMake와 make의 차이를 확인해보세요.

// 안 좋은 예
const Car = {
  carMake: 'BMW',
  carModel: 'M3',
  carColor: '파란색'
};

function paintCar(car) {
  car.carColor = '빨간색';
}

// 좋은 예
const Car = {
  make: 'BMW',
  model: 'M3',
  color: '파란색'
};

function paintCar(car) {
  car.color = '빨간색';
}

 

기본 매개변수를 잘 활용할 것
// 안 좋은 예
function createMicrobrewery(name) {
  const breweryName = name || 'Hipster Brew Co.';
  // ...
}

// 좋은 예
function createMicrobrewery(name = 'Hipster Brew Co.') {
  // ...
}

 

2. 함수

함수 인자는 최대 2개까지만 사용할 것

매개변수의 개수를 제한 하는 것은 함수 테스팅을 쉽게 만들어 주기 때문에 중요하다.
만약 매개변수가 3개 이상일 경우엔 테스트 해야하는 경우의 수가 많아지고 여러 사례들을 테스트 해야하기 때문이다.
함수가 기대하는 속성을 좀더 명확히 하기 위해서 es6의 비구조화(destructuring) 구문을 사용할 수 있다.

비구조화 구문의 장점은 다음과 같다.
1. 어떤 사람이 그 함수의 시그니쳐(인자의 타입, 반환되는 값의 타입 등)를 볼 때 어떤 속성이 사용되는지 즉시 알 수 있다.
2. 또한 비구조화는 함수에 전달된 인수 객체의 지정된 기본 타입 값을 복제하며 이는 사이드 이펙트가 일어나는 것을 방지한다. 참고로 인수 객체로부터 비구조화된 객체와 배열은 복제되지 않는다.
3. Linter를 사용하면 사용하지 않는 인자에 대해 경고해주거나 비구조화 없이 코드를 짤 수 없게 할 수 있다.
// 안 좋은 예
function createMenu(title, body, buttonText, cancellable) {
  // ...
}

// 좋은 예
function createMenu({ title, body, buttonText, cancellable }) {
  // ...
}

createMenu({
  title: 'Foo',
  body: 'Bar',
  buttonText: 'Baz',
  cancellable: true
});

 

함수는 하나의 행동만 할 것
// 안 좋은 예
function emailClients(clients) {
  clients.forEach(client => {
    const clientRecord = database.lookup(client);
    if (clientRecord.isActive()) {
      email(client);
    }
  });
}

// 좋은 예
function emailClients(clients) {
  clients
    .filter(isClientActive)
    .forEach(email);
}

function isClientActive(client) {
  const clientRecord = database.lookup(client);
  return clientRecord.isActive();
}

 

함수명은 함수가 무엇을 하는지 알 수 있도록 작성할 것
// 안 좋은 예
function AddToDate(date, month) {
  // ...
}

const date = new Date();

// 뭘 추가하는 건지 이름만 보고 알아내기 힘듭니다.
AddToDate(date, 1);


// 좋은 예
function AddMonthToDate(date, month) {
  // ...
}

const date = new Date();
AddMonthToDate(date, 1);

 

중복된 코드 금지
// 안 좋은 예
function showDeveloperList(developers) {
  developers.forEach(developers => {
    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);
  });
}


// 좋은 예
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);
  });
}

 

매개변수로 플래그를 사용하지 말 것 (플래그를 사용하는 것 자체가 그 함수가 한가지 이상의 역할을 하고 있다는 것)
// 안 좋은 예
// temp를 플래그로 사용하고 있다.
function createFile(name, temp) {
  if (temp) {
    fs.create(`./temp/${name}`);
  } else {
    fs.create(name);
  }
}

// 좋은 예
function createFile(name) {
  fs.create(name);
}

function createTempFile(name) {
  createFile(`./temp/${name}`);
}

 

사이드 이펙트는 피할 것 - 1

어떠한 구조체도 없이 객체 사이의 상태를 공유하거나, 무엇이든 쓸 수 있는 변경 가능한 데이터 유형을 사용하거나, 같은 사이드 이펙트를 만들어내는 것을 여러개 만들거나하면 안된다.
// 아래 함수에 의해 참조되는 전역 변수입니다.
// 이 전역 변수를 사용하는 또 하나의 함수가 있다고 생각해보세요. 이제 이 변수는 배열이 될 것이고, 프로그램을 망가뜨리겠죠.
let name = 'Ryan McDermott';

function splitIntoFirstAndLastName() {
  name = name.split(' ');
}

splitIntoFirstAndLastName();

console.log(name); // ['Ryan', 'McDermott'];


// 좋은 예
// 기존 배열은 유지하면서 새로운 변수에 값을 담아 안정성을 유지할 수 있습니다.
function splitIntoFirstAndLastName(name) {
  return name.split(' ');
}

const name = 'Ryan McDermott';
const newName = splitIntoFirstAndLastName(name);

console.log(name); // 'Ryan McDermott';
console.log(newName); // ['Ryan', 'McDermott'];

 

사이드 이펙트는 피할 것 - 2

유저가 구매하기 버튼을 눌러 구매 함수를 호출한다.
이는 네트워크 요청을 생성하고 서버에 장바구니 배열을 보낸다.
하지만 네트워크 연결이 좋지 않아서 구매 함수는 다시한번 네트워크 요청을 보내야 하는 상황이 생겼다.
이 때, 사용자가 네트워크 요청이 시작되기 전에 실수로 원하지 않는 상품의 "장바구니에 추가" 버튼을 실수로 클릭하면 어떻게 될까?
실수가 있고 난 뒤, 네트워크 요청이 시작되면 장바구니에 추가 함수 때문에 실수로 변경된 장바구니 배열을 서버에 보내게 된다.
가장 좋은 방법은 장바구니에 추가는 항상 장바구니 배열을 복제하여 수정하고 복제본을 반환하는 것이다.
이렇게 하면 장바구니 참조를 보유하고 있는 다른 함수가 다른 변경 사항의 영향을 받지 않게 된다.
// 안 좋은 예
const addItemToCart = (cart, item) => {
  cart.push({ item, date: Date.now() });
};

// 좋은 예
const addItemToCart = (cart, item) => {
  return [...cart, { item, date : Date.now() }];
};

 

명령형 프로그래밍보다 함수형 프로그래밍을 지향할 것
// 안 좋은 예
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;
}


// 좋은 예
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
  .map(programmer => programmer.linesOfCode)
  .reduce((acc, linesOfCode) => acc + linesOfCode, INITIAL_VALUE);

 

조건문을 캡슐화할 것
// 안 좋은 예
if (fsm.state === 'fetching' && isEmpty(listNode)) {
  // ...
}

// 좋은 예
function shouldShowSpinner(fsm, listNode) {
  return fsm.state === 'fetching' && isEmpty(listNode);
}

if (shouldShowSpinner(fsmInstance, listNodeInstance)) {
  // ...
}
반응형

댓글