CleanCode for JavaScript -2

2018-03-25

클린코드 for 자바스크립트 -2

  • 객체와 자료구조(Objects and Data Structures)
  • 클래스(Classes)
  • 테스트(Testing)
  • 동시성(Concurrency)
  • 에러 처리(Error Handling)
  • 포맷팅(Formatting)
  • 주석(Comments)

객체와 자료구조(Objects and Data Structures)

  • getter와 setter를 사용해라.
    • 자바스크립트는 인터페이스와 타입을 가지고 있지 않기 때문에 이러한 패턴을 적용하기 힘들다.
    • public과 private 같은 키워드가 없기 때문
    • 때문에 getter및 setter를 사용하여 객체의 데이터의 접근하는 것이 객체의 속성을 찾는 것 보다 훨씬 낫다.
  1. 객체의 속성을 얻는 것 이상 많은 것을 하고 싶을 때, 코드에서 모든 접근자를 찾아 바꾸고 할 필요가 없다.
  2. set 할때 검증로직을 추가하는 것이 코드가 더 간단하다.
  3. 내부용 API를 캡슐화 할 수 있다.
  4. getting과 setting할 때 로그를 찾거나 에러처리 하기가 쉽다.
  5. 클래스를 상속해서 디폴트 동작을 재정의할 수 있다.
  6. 서버에서 객체 속성을 받아올 때 lazy load할 수 있다.
// BAD
class BankAccount {
  constructor() {
    this.balance = 1000;
  }
}

const bankAccount = new BankAccount();

// 신발을 구매할 때...
bankAccount.balance -= 100;

// GOOD
class BankAccount {
  constructor(balance = 1000) {
	   this._balance = balance;
  }

  // getter/setter를 정의할 때 `get`, `set` 같은 접두사가 필요하지 않다.
  set balance(amount) {
      if (this.verifyIfAmountCanBeSetted(amount)) {
        this._balance = amount;
      }
    }
  
  get balance() {
    return this._balance;
  }

  verifyIfAmountCanBeSetted(val) {
    // ...
  }
}

const bankAccount = new BankAccount();
    
// 신발을 구매할 때...
bankAccount.balance -= shoesPrice;

// balance 값을 얻을 때
let balance = bankAccount.balance;
  • 객체에 비공개 멤버를 만들어라.
    • 클로저를 이용하면 es5이하에서도 가능하다.
// BAD
const Employee = function(name) {
  this.name = name;
};

Employee.prototype.getName = function getName() {
  return this.name;
};

const employee = new Employee('John Doe');
console.log(`Employee name: ${employee.getName()}`); // Employee name: John Doe
delete employee.name;
console.log(`Employee name: ${employee.getName()}`); // Employee name: undefined

// GOOD
function makeEmployee(name) {
  // private 멤버는 함수 안에
  return { // public멤버는 리턴에
    getName() {
      return name;
    },
  };
}

const employee = makeEmployee('John Doe');
console.log(`Employee name: ${employee.getName()}`); // Employee name: John Doe
delete employee.name;
console.log(`Employee name: ${employee.getName()}`); // Employee name: John Doe

클래스 (Class)

  • 단일 책임 원칙 (Single Responsibility Prinsiple, SRP)
    • “클래스를 수정할때 수정해야 하는 이유가 2가지 이상이면 안된다” 라고 클린코드는 말한다.
    • 이 문제는 클래스가 개념적으로 응집되어 있지 않다는 것이다.
// BAD
class 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()) {
      // ...
    }
  }
}
  • 개방 / 폐쇄 원칙 (Open/Closed Principle, OCP)
    • “확장엔 개방적이고 수정엔 폐쇄적이어야한다.” - Bertrand Meyer
// BAD
class AjaxAdapter extends Adapter {
  constructor() {
    super();
    this.name = 'ajaxAdapter';
  }
}

class NodeAdapter extends Adapter {
  constructor() {
    super();
    this.name = 'nodeAdapter';
  }
}

class HttpRequester {
  constructor(adapter) {
    this.adapter = adapter;
  }

  fetch(url) {
    if (this.adapter.name === 'ajaxAdapter') {
      return makeAjaxCall(url).then((response) => {
        // transform response and return
      });
    } else if (this.adapter.name === 'httpNodeAdapter') {
      return makeHttpCall(url).then((response) => {
        // transform response and return
      });
    }
  }
}

function makeAjaxCall(url) {
  // request and return promise
}

function makeHttpCall(url) {
  // request and return promise
}

// GOOD
class AjaxAdapter extends Adapter {
  constructor() {
    super();
    this.name = 'ajaxAdapter';
  }

  request(url) {
    // request and return promise
  }
}

class NodeAdapter extends Adapter {
  constructor() {
    super();
    this.name = 'nodeAdapter';
  }

  request(url) {
    // request and return promise
  }
}

class HttpRequester {
  constructor(adapter) {
    this.adapter = adapter;
  }

  fetch(url) {
    return this.adapter.request(url).then((response) => {
      // transform response and return
    });
  }
}
  • 리스코프 치환 원칙 (Liskov Substitution Principle, LSP)
    • 리스코프 원칙이란 자료형 S가 자료형 T의 하위형이라면, 프로그램이 갖추어야 할 속성들(정확성, 수행되는 작업 등)의 변경사항 없이, 자료형 T의 객체를 자료형 S의 객체로 교체(치환)할 수 있어야 한다는 원칙이다.
    • 이해가 안간다면 정사각형-직사각형 관계를 생각해보자. 수학적으로 정사각형은 직사각형이지만 상속을 통해 “is-a” 관계를 사용하여 모델링한다면 문제가 발생한다.

BAD

// BAD
class Rectangle {
  constructor() {
    this.width = 0;
    this.height = 0;
  }

  setColor(color) {
    // ...
  }

  render(area) {
    // ...
  }

  setWidth(width) {
    this.width = width;
  }

  setHeight(height) {
    this.height = height;
  }

  getArea() {
    return this.width * this.height;
  }
}

class Square extends Rectangle {
  setWidth(width) {
    this.width = width;
    this.height = width;
  }

  setHeight(height) {
    this.width = height;
    this.height = height;
  }
}

function renderLargeRectangles(rectangles) {
  rectangles.forEach((rectangle) => {
    rectangle.setWidth(4);
    rectangle.setHeight(5);
    const area = rectangle.getArea(); // 정사각형일때 25를 리턴합니다. 하지만 20이어야 하는게 맞습니다.
    rectangle.render(area);
  });
}

const rectangles = [new Rectangle(), new Rectangle(), new Square()];
renderLargeRectangles(rectangles);

GOOD

// GOOD
class Shape {
  setColor(color) {
    // ...
  }

  render(area) {
    // ...
  }
}

class Rectangle extends Shape {
  constructor(width, height) {
    super();
    this.width = width;
    this.height = height;
  }

  getArea() {
    return this.width * this.height;
  }
}

class Square extends Shape {
  constructor(length) {
    super();
    this.length = length;
  }

  getArea() {
    return this.length * this.length;
  }
}

function renderLargeShapes(shapes) {
  shapes.forEach((shape) => {
      const area = shape.getArea();
      shape.render(area);
    });
  }

const shapes = [new Rectangle(4, 5), new Rectangle(4, 5), new Square(5)];
renderLargeShapes(shapes);
  • 인터페이스 분리 원칙 (Interface Segregation Principle, ISP)

    • “클라이언트는 사용하지 않는 인터페이스에 의존하도록 강요 받으면 안된다.”

    • 자바스크립트는 인터페이스가 없기 대문에 다른 원칙들 처럼 완벽하게 적용할 수 없다. 하지만 중요하고 관계있는 원칙이다.

    • 자바스크립트에서 이것을 보여주는 가장 좋은 예는 방대한 양의 설정 객체가 필요한 클래스이다.

    • 대부분의 경우 설정들이 전부 다 필요한 건 아니기 때문에 방대한 양의 옵션을 설정하지 않는것이 좋다.

    • 설정을 선택적으로 할 수 있다면 “무거운 인터페이스”를 만드는것을 방지할 수 있다.

// BAD
class DOMTraverser {
  constructor(settings) {
    this.settings = settings;
    this.setup();
  }

  setup() {
    this.rootNode = this.settings.rootNode;
    this.animationModule.setup();
  }

  traverse() {
    // ...
  }
}

const $ = new DOMTraverser({
  rootNode: document.getElementsByTagName('body'),
  animationModule() {} // 우리는 대부분의 경우 DOM을 탐색할 때 애니메이션이 필요하지 않습니다.
  // ...
});

// GOOD
class DOMTraverser {
  constructor(settings) {
    this.settings = settings;
    this.options = settings.options;
    this.setup();
  }

  setup() {
    this.rootNode = this.settings.rootNode;
    this.setupOptions();
  }

  setupOptions() {
    if (this.options.animationModule) {
      // ...
    }
  }

  traverse() {
    // ...
  }
}

const $ = new DOMTraverser({
  rootNode: document.getElementsByTagName('body'),
  options: {
    animationModule() {}
  }
});
  • 의존성 역전 원칙 (Dependency Inversion Principle, DIP)
    • 이 원칙은 두가지 중요한 요소를 가지고 있다.
      • 상위 모듈은 하위 모듈에 종속되어서는 안된다. 둘 다 추상화에 의존해야 한다.
      • 추상화는 세부사항에 의존하지 않는다. 세부사항은 추상화에 의해 달라저야 한다.
    • Angular.js의 의존성 주입(Dependency Injection)의 구현체이다.
    • DI의 장점은 모듈 간의 의존성을 감소시킨다. 모듈간의 의존성이 높을수록 리팩토링이 어려워진다.
    • 자바스크립트는 인터페이스가 없으므로 추상화에 의존하는 것은 암시적인 약속이다.
    • 즉, 다른 객체나 클래스에 노출되는 메소드와 속성이 바로 암시적인 약속(추상화)가 된다.
    • 예제는 암시적인 약속은 InventoryTracker에대한 모든 요청 모듈이 requestItems 메소드를 가질 것이라는 점이다.
// BAD
class InventoryRequester {
  constructor() {
    this.REQ_METHODS = ['HTTP'];
  }

  requestItem(item) {
    // ...
  }
}

class InventoryTracker {
  constructor(items) {
    this.items = items;

    // 안좋은 이유: 특정 요청방법 구현에 대한 의존성을 만들었습니다.
    // requestItems는 한가지 요청방법을 필요로 합니다.
    this.requester = new InventoryRequester();
  }

  requestItems() {
    this.items.forEach(item => {
      this.requester.requestItem(item);
    });
  }
}

const inventoryTracker = new InventoryTracker(['apples', 'bananas']);
inventoryTracker.requestItems();

// GOOD
class InventoryTracker {
  constructor(items, requester) {
    this.items = items;
    this.requester = requester;
  }

  requestItems() {
    this.items.forEach(item => {
      this.requester.requestItem(item);
    });
  }
}

class InventoryRequesterV1 {
  constructor() {
    this.REQ_METHODS = ['HTTP'];
  }

  requestItem(item) {
    // ...
  }
}

class InventoryRequesterV2 {
  constructor() {
    this.REQ_METHODS = ['WS'];
  }

  requestItem(item) {
    // ...
  }
}

// 의존성을 외부에서 만들어 주입해줌으로써,
// 요청 모듈을 새롭게 만든 웹소켓 사용 모듈로 쉽게 바꿔 끼울 수 있게 되었습니다.
const inventoryTracker = new InventoryTracker(['apples', 'bananas'], new InventoryRequesterV2());
inventoryTracker.requestItems();
  • ES5 함수보다 ES6의 클래스를 사용해라.
    • ES5의 클래스에서 상속, 구성 및 메소드 정의는 어렵다. 상속이 필요한 경우라면 클래스를 사용하는 것이 좋다.
    • 하지만 크고 복잡한 객체가 필요한 경우가 아니라면 함수를 사용해라.
// BAD
const Animal = function(age) {
  if (!(this instanceof Animal)) {
    throw new Error("Instantiate Animal with `new`");
  }
    
  this.age = age;
};

Animal.prototype.move = function() {};

const Mammal = function(age, furColor) {
  if (!(this instanceof Mammal)) {
    throw new Error("Instantiate Mammal with `new`");
  }

  Animal.call(this, age);
  this.furColor = furColor;
};

Mammal.prototype = Object.create(Animal.prototype);
Mammal.prototype.constructor = Mammal;
Mammal.prototype.liveBirth = function liveBirth() {};

const Human = function(age, furColor, languageSpoken) {
  if (!(this instanceof Human)) {
    throw new Error("Instantiate Human with `new`");
  }

  Mammal.call(this, age, furColor);
  this.languageSpoken = languageSpoken;
};

Human.prototype = Object.create(Mammal.prototype);
Human.prototype.constructor = Human;
Human.prototype.speak = function speak() {};

// GOOD
class Animal {
  constructor(age) {
    this.age = age;
  }

  move() { /* ... */ }
}

class Mammal extends Animal {
  constructor(age, furColor) {
    super(age);
    this.furColor = furColor;
  }

  liveBirth() { /* ... */ }
}

class Human extends Mammal {
  constructor(age, furColor, languageSpoken) {
    super(age, furColor);
    this.languageSpoken = languageSpoken;
  }

  speak() { /* ... */ }
}
  • 메소드 체이닝을 사용해라.
    • 자바스크립트에서 메소드 체이닝은 매우 유용한 패턴이며 jQuery나 Lodash같은 많은 라이브러리에서도 이 패턴을 볼 수 있다.
    • 클래스 함수에서 단순히 모든 함수의 끝에 ‘this’를 리턴해주는 것으로 클래스 메소드를 추가로 연결할 수 있다.
// BAD
class Car {
  constructor() {
    this.make = 'Honda';
    this.model = 'Accord';
    this.color = 'white';
  }

  setMake(make) {
    this.make = make;
  }

  setModel(model) {
    this.model = model;
  }

  setColor(color) {
    this.color = color;
  }

  save() {
    console.log(this.make, this.model, this.color);
  }
}

const car = new Car();
car.setColor('pink');
car.setMake('Ford');
car.setModel('F-150');
car.save();


// GOOD
class Car {
  constructor() {
    this.make = 'Honda';
    this.model = 'Accord';
    this.color = 'white';
  }

  setMake(make) {
    this.make = make;
    // 메모: 체이닝을 위해 this를 리턴합니다.
    return this;
  }

  setModel(model) {
    this.model = model;
    // 메모: 체이닝을 위해 this를 리턴합니다.
    return this;
  }

  setColor(color) {
    this.color = color;
    // 메모: 체이닝을 위해 this를 리턴합니다.
    return this;
  }

  save() {
    console.log(this.make, this.model, this.color);
    // 메모: 체이닝을 위해 this를 리턴합니다.
    return this;
  }
}

const car = new Car()
  .setColor('pink')
  .setMake('Ford')
  .setModel('F-150')
  .save();
  • 상속보단 조합(composition)을 사용해라.
    • GOF의 디자인패턴에서 유명한 전략으로서 상속보다는 조합을 사용해야 한다.
    • 상속을 사용해서 얻는 이득보다 조합을 사용했을때 얻을 수 있는 이득이 더 많다.
    • 조합보다 상속을 쓰는게 더 좋은 상황
      • 상속관계가 “is-a”관계가 아니라 “has-a”관계 일때 (사람-> 동물 vs. 유저->유저정보)
      • 기반 클래스의 코드를 다시 사용할 수 있을 때 (인간은 모든 동물처럼 움직일 수 있다.)
      • 기반 클래스를 수정하여 파생된 클래스를 모두 수정하고 싶을 때 (이동시 모든 동물이 소비하는 칼로리를 변경하고 싶을 때)
// BAD
class Employee {
  constructor(name, email) {
    this.name = name;
    this.email = email;
  }

  // ...
}

// 이 코드가 안좋은 이유는 Employees가 tax data를 "가지고" 있기 때문입니다.
// EmployeeTaxData는 Employee 타입이 아닙니다.
class EmployeeTaxData extends Employee {
  constructor(ssn, salary) {
    super();
    this.ssn = ssn;
    this.salary = salary;
  }

  // ...
}


// GOOD
class EmployeeTaxData {
  constructor(ssn, salary) {
    this.ssn = ssn;
    this.salary = salary;
  }
  
  // ...
}

class Employee {
  constructor(name, email) {
    this.name = name;
    this.email = email;
  }

  setTaxData(ssn, salary) {
    this.taxData = new EmployeeTaxData(ssn, salary);
  }
  // ...
}

테스트 (Testing)

테스트는 배포하는 것보다 중요하다. 테스트코드를 작성하지 않는다는 것은 그 무엇도 변명이 될 수 없다.

  • 테스트 컨셉
// BAD
const assert = require('assert');

describe('MakeMomentJSGreatAgain', () => {
  it('handles date boundaries', () => {
    let date;

    date = new MakeMomentJSGreatAgain('1/1/2015');
    date.addDays(30);
    assert.equal('1/31/2015', date);

    date = new MakeMomentJSGreatAgain('2/1/2016');
    date.addDays(28);
    assert.equal('02/29/2016', date);

    date = new MakeMomentJSGreatAgain('2/1/2015');
    date.addDays(28);
    assert.equal('03/01/2015', date);
  });
});

// GOOD
const assert = require('assert');

describe('MakeMomentJSGreatAgain', () => {
  it('handles 30-day months', () => {
    const date = new MakeMomentJSGreatAgain('1/1/2015');
    date.addDays(30);
    assert.equal('1/31/2015', date);
  });

  it('handles leap year', () => {
    const date = new MakeMomentJSGreatAgain('2/1/2016');
    date.addDays(28);
    assert.equal('02/29/2016', date);
  });

  it('handles non-leap year', () => {
    const date = new MakeMomentJSGreatAgain('2/1/2015');
    date.addDays(28);
    assert.equal('03/01/2015', date);
  });
});

동시성 (Concurrency)

  • Callback 대신 Promise를 사용해라.
    • 콜백은 깔끔하지 않다. 그리고 많은 중괄호 중첩을 만들어낸다.
    • ES6에선 Promise가 내장되어 있다.
// BAD
require('request').get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin', (requestErr, response) => {
  if (requestErr) {
    console.error(requestErr);
  } else {
    require('fs').writeFile('article.html', response.body, (writeErr) => {
      if (writeErr) {
        console.error(writeErr);
      } else {
        console.log('File written');
      }
    });
  }
});

// GOOD
require('request-promise').get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin')
  .then((response) => {
    return require('fs-promise').writeFile('article.html', response);
  })
  .then(() => {
    console.log('File written');
  })
  .catch((err) => {
    console.error(err);
  });
  • Asnyc/Await는 Promise보다 더 깔끔하다.
    • ES7에선 async와 await가 있다. 이들은 Callback에대한 더 깔끔한 해결책을 제시해준다.
    • 단지 함수앞에 async를 붙임으로서 더이상 논리적으로 연결하기 위해 then을 사용하지 않아도 된다.
// BAD
require('request-promise').get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin')
  .then(response => {
    return require('fs-promise').writeFile('article.html', response);
  })
  .then(() => {
    console.log('File written');
  })
  .catch(err => {
    console.error(err);
  })

// GOOD
async function getCleanCodeArticle() {
  try {
    const response = await require('request-promise').get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin');
    await require('fs-promise').writeFile('article.html', response);
    console.log('File written');
  } catch(err) {
    console.error(err);
  }
}

에러 처리(Error Handling)

  • 단순히 에러를 확인만 하지 마라.
    • 확인하는 것만으로 에러가 해결되지 않는다.
    • console.log를 통해 로그를 기록하는 것은 에러로그를 잃어버리기 쉽기 때문에 좋은 방법이 아니다.
    • try/catch로 어떤 코드를 감쌌다면 그 코드에 어떤 에러가 날지도 모르기 때문에 감싼 것이므로 그에 대한 장치를 해야한다.
// BAD
try {
  functionThatMightThrow();
} catch (error) {
  console.log(error);
}

// GOOD
try {
  functionThatMightThrow();
} catch (error) {
  // 첫번째 방법은 console.error를 이용하는 것입니다. 이건 console.log보다 조금 더 알아채기 쉽습니다.
  console.error(error);
  // 다른 방법은 유저에게 알리는 방법입니다.
  notifyUserOfError(error);
  // 또 다른 방법은 서비스 자체에 에러를 기록하는 방법입니다.
  reportErrorToService(error);
  // 혹은 그 어떤 방법이 될 수 있습니다.
}
  • Promise가 실패된 것을 무시하지 마라.
// BAD
getdata()
.then(data => {
  functionThatMightThrow(data);
})
.catch(error => {
  console.log(error);
});

// GOOD
getdata()
.then(data => {
  functionThatMightThrow(data);
})
.catch(error => {
  // 첫번째 방법은 console.error를 이용하는 것입니다. 이건 console.log보다 조금 더 알아채기 쉽습니다.
  console.error(error);
  // 다른 방법은 유저에게 알리는 방법입니다.
  notifyUserOfError(error);
  // 또 다른 방법은 서비스 자체에 에러를 기록하는 방법입니다.
  reportErrorToService(error);
  // 혹은 그 어떤 방법이 될 수 있습니다.
});

포맷팅(Formatting)

포맷팅은 주관적이다. 때문에 과도하게 신경쓰는 것은 의미없다.

포맷팅 체크를 자동으로 해주는 많은 도구들중 하나를 선택해서 사용해라.

  • 일관된 대소문자를 사용해라.
    • 자바스크립트는 정해진 타입이 없기 때문에 대소문자를 구분하는 것으로 변수나 함수명 등에서 많은것을 알 수 있다.
    • 이 규칙 또한 주관적이기 때문에 일관성 있게 사용해야 한다.
// BAD
const DAYS_IN_WEEK = 7;
const daysInMonth = 30;

const songs = ['Back In Black', 'Stairway to Heaven', 'Hey Jude'];
const Artists = ['ACDC', 'Led Zeppelin', 'The Beatles'];

function eraseDatabase() {}
function restore_database() {}

class animal {}
class Alpaca {}

// GOOD
const DAYS_IN_WEEK = 7;
const DAYS_IN_MONTH = 30;

const songs = ['Back In Black', 'Stairway to Heaven', 'Hey Jude'];
const artists = ['ACDC', 'Led Zeppelin', 'The Beatles'];

function eraseDatabase() {}
function restoreDatabase() {}

class Animal {}
class Alpaca {}
  • 함수 호출자와 함수 피호출자는 가깝게 위치시켜라.
    • 어떤 함수가 다른 함수를 호출하면 그 함수들은 파일 안에서 서로 수직으로 근접해 있어야 한다.
    • 이상적으로는 함수 호출자를 함수 피호출자 바로 위에 위치시켜야 한다.
    • 코드를 읽을때 위에서 아래로 읽기 때문에 코드를 작성할 때도 이를 고려해서 작성해야 한다.
// BAD
class PerformanceReview {
  constructor(employee) {
    this.employee = employee;
  }

  lookupPeers() {
    return db.lookup(this.employee, 'peers');
  }

  lookupManager() {
    return db.lookup(this.employee, 'manager');
  }

  getPeerReviews() {
    const peers = this.lookupPeers();
    // ...
  }

  perfReview() {
    this.getPeerReviews();
    this.getManagerReview();
    this.getSelfReview();
  }

  getManagerReview() {
    const manager = this.lookupManager();
  }

  getSelfReview() {
    // ...
  }
}

const review = new PerformanceReview(user);
review.perfReview();

// GOOD
class PerformanceReview {
  constructor(employee) {
    this.employee = employee;
  }

  perfReview() {
    this.getPeerReviews();
    this.getManagerReview();
    this.getSelfReview();
  }

  getPeerReviews() {
    const peers = this.lookupPeers();
    // ...
  }

  lookupPeers() {
    return db.lookup(this.employee, 'peers');
  }

  getManagerReview() {
    const manager = this.lookupManager();
  }

  lookupManager() {
    return db.lookup(this.employee, 'manager');
  }

  getSelfReview() {
    // ...
  }
}

const review = new PerformanceReview(employee);
review.perfReview();

주석 (Comments)

  • 주석은 단지 그 로직이 복잡하다는 것을 말 할 뿐이다.
    • 주석을 다는것은 사과해야할 일이며 필수가 아니다.
    • 좋은 코드는 코드 자체로 말한다.
// BAD
function hashIt(data) {
  // 이건 해쉬입니다.
  let hash = 0;

  // lengh는 data의 길이입니다.
  const length = data.length;

  // 데이터의 문자열 개수만큼 반복문을 실행합니다.
  for (let i = 0; i < length; i++) {
    // 문자열 코드를 얻습니다.
    const char = data.charCodeAt(i);
    // 해쉬를 만듭니다.
    hash = ((hash << 5) - hash) + char;
    // 32-bit 정수로 바꿉니다.
    hash &= hash;
  }
}

// GOOD
function hashIt(data) {
  let hash = 0;
  const length = data.length;

  for (let i = 0; i < length; i++) {
    const char = data.charCodeAt(i);
    hash = ((hash << 5) - hash) + char;

    // 32-bit 정수로 바꿉니다.
    hash &= hash;
  }
}
  • 주석으로 된 코드를 남기지 마라.
    • 버전 관리 도구가 존재하기 때문에 남길 이유가 없다.
// BAD
doStuff();
// doOtherStuff();
// doSomeMoreStuff();
// doSoMuchStuff();

// GOOD
doStuff();
  • 코드 기록을 주석으로 남기지 마라.
    • 죽은 코드도, 불필요한 설명도, 특히 코드의 기록에 대한 주석은 필요하지 않다.
    • 코드의 기록을 보고싶으면 버전 관리 시스템의 로그를 사용해라.
// BAD
/**
 * 2016-12-20: 모나드 제거했음, 이해는 되지 않음 (RM)
 * 2016-10-01: 모나드 쓰는 로직 개선 (JP)
 * 2016-02-03: 타입체킹 하는부분 제거 (LI)
 * 2015-03-14: 버그 수정 (JR)
 */
function combine(a, b) {
  return a + b;
}

// GOOD
function combine(a, b) {
  return a + b;
}
  • 코드의 위치를 설명하지 마라.
    • 적절한 들여쓰기와 포맷팅을 하고 함수와 변수의 이름에 의미를 부여하는것에 더 집중해라.
// BAD
////////////////////////////////////////////////////////////////////////////////
// 스코프 모델 정의
////////////////////////////////////////////////////////////////////////////////
$scope.model = {
  menu: 'foo',
  nav: 'bar'
};

////////////////////////////////////////////////////////////////////////////////
// actions 설정
////////////////////////////////////////////////////////////////////////////////
const actions = function() {
  // ...
};

// GOOD
$scope.model = {
  menu: 'foo',
  nav: 'bar'
};

const actions = function() {
  // ...
};