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를 사용하여 객체의 데이터의 접근하는 것이 객체의 속성을 찾는 것 보다 훨씬 낫다.
- 객체의 속성을 얻는 것 이상 많은 것을 하고 싶을 때, 코드에서 모든 접근자를 찾아 바꾸고 할 필요가 없다.
- set 할때 검증로직을 추가하는 것이 코드가 더 간단하다.
- 내부용 API를 캡슐화 할 수 있다.
- getting과 setting할 때 로그를 찾거나 에러처리 하기가 쉽다.
- 클래스를 상속해서 디폴트 동작을 재정의할 수 있다.
- 서버에서 객체 속성을 받아올 때 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() {
// ...
};