diff --git a/translation-stopped/README-ko.md b/translation-stopped/README-ko.md new file mode 100644 index 00000000..428e18f3 --- /dev/null +++ b/translation-stopped/README-ko.md @@ -0,0 +1,2166 @@ +# clean-code-javascript + +## 목차 + 1. [소개](#소개) + 2. [변수](#변수) + 3. [함수](#함수) + 4. [오브젝트 및 자료구조](#오브젝트-및-자료구조) + 5. [클래스](#클래스) + 6. [테스팅](#테스팅) + 7. [동시성](#동시성) + 8. [오류 처리](#오류-처리) + 9. [포맷팅](#포맷팅) + 10. [주석](#주석) + 11. [번역](#번역) + +## 소개 +![Humorous image of software quality estimation as a count of how many expletives +you shout when reading code](http://www.osnews.com/images/comics/wtfm.jpg) + +로버트 C. 마틴이 저서 +[*Clean Code*](https://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882) +에서 주장한 소프트웨어 공학의 원칙을 자바스크립트에 적용했습니다. 이 문서는 스타일가이드가 아닙니다. 이 문서는 +자바스크립트로 된 읽기 좋고, 재사용하기 쉬우며, 리팩토링하기에 용이한 소프트웨어를 만들기 위한 가이드입니다. + +여기 있는 모든 원칙이 반드시 엄격히 지켜져야 하는 것은 아니며, 심지어는 합의되지 않을 수도 있습니다. +이것은 가이드라인일 뿐이며, +*Clean Code* +의 저자들이 수년간 공동 경험을 통해 축적한 것을 집대성한 것입니다. + +소프트웨어 엔지니어링 기술은 이제 막 50년이 넘었으며 여전히 배워가고 있는 것이 많습니다. +시간이 지나 소프트웨어 아키텍처가 아키텍처만큼 성숙한다면 +아마도 우리는 더욱 엄격한 규칙들을 따라야 할 것입니다. 일단 지금 +우리가 제안하는 가이드라인을 당신과 당신의 팀이 작성하는 자바스크립트 코드의 품질을 평가하는 +시금석으로 삼으세요. + +한 가지 더: 이것들을 안다고 곧바로 당신이 더 나은 개발자가 되는 것은 아니며, +이를 수년간 실무에 적용해왔다고 해서 실수를 안 하게 되는 것도 아닙니다. +우리는 모든 코드의 조각들을 초안에서 시작해서 마치 젖은 점토의 형태가 완성형으로 변해가듯 바꿔나갈 것입니다. +마지막으로 우리는 동료들과의 리뷰를 통해 불완전한 부분들을 보완하고 있습니다. +개선이 필요한 초안 때문에 자신을 괴롭히지 맙시다. +대신 코드와 싸워 이깁시다! + +## **변수** +### 의미 있고 발음할 수 있는 변수 이름을 사용하라 + +**나쁜 예:** +```javascript +const yyyymmdstr = moment().format('YYYY/MM/DD'); +``` + +**좋은 예:** +```javascript +const currentDate = moment().format('YYYY/MM/DD'); +``` +**[⬆ 맨 위로](#목차)** + +### 동일한 유형의 변수에는 동일한 단어를 사용하라 + +**나쁜 예:** +```javascript +getUserInfo(); +getClientData(); +getCustomerRecord(); +``` + +**좋은 예:** +```javascript +getUser(); +``` +**[⬆ 맨 위로](#목차)** + +### 검색하기 쉬운 이름을 사용하라 +우리는 직접 코드를 작성하는 것보다 더 오랜 시간 동안 다른 사람이 쓴 코드를 읽습니다. +따라서 코드는 읽기 쉽고 검색이 용이하게 작성되어야 합니다. 만약 우리가 프로그램을 이해하는 데 +의미가 있는 변수의 이름을 짓지 못했다면, 그 코드를 읽는 사람은 고통에 빠질 것입니다. +검색하기 쉽게 변수의 이름을 지으세요. +[buddy.js](https://github.com/danielstjules/buddy.js)와 +[ESLint](https://github.com/eslint/eslint/blob/660e0918933e6e7fede26bc675a0763a6b357c94/docs/rules/no-magic-numbers.md) +같은 도구는 당신의 코드에서 이름 없는 상수를 찾는 데 도움을 줄 것입니다. + +**나쁜 예:** +```javascript +// What the heck is 86400000 for? +setTimeout(blastOff, 86400000); + +``` + +**좋은 예:** +```javascript +// Declare them as capitalized `const` globals. +const MILLISECONDS_IN_A_DAY = 86400000; + +setTimeout(blastOff, MILLISECONDS_IN_A_DAY); + +``` +**[⬆ 맨 위로](#목차)** + +### 설명을 위한 변수를 사용하라 +**나쁜 예:** +```javascript +const address = 'One Infinite Loop, Cupertino 95014'; +const cityZipCodeRegex = /^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/; +saveCityZipCode(address.match(cityZipCodeRegex)[1], address.match(cityZipCodeRegex)[2]); +``` + +**좋은 예:** +```javascript +const address = 'One Infinite Loop, Cupertino 95014'; +const cityZipCodeRegex = /^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/; +const [, city, zipCode] = address.match(cityZipCodeRegex) || []; +saveCityZipCode(city, zipCode); +``` +**[⬆ 맨 위로](#목차)** + +### 함축적인 이름 피하기 +함축적인 것보다 명백한 것이 낫습니다. + +**나쁜 예:** +```javascript +const locations = ['Austin', 'New York', 'San Francisco']; +locations.forEach((l) => { + doStuff(); + doSomeOtherStuff(); + // ... + // ... + // ... + // Wait, what is `l` for again? + dispatch(l); +}); +``` + +**좋은 예:** +```javascript +const locations = ['Austin', 'New York', 'San Francisco']; +locations.forEach((location) => { + doStuff(); + doSomeOtherStuff(); + // ... + // ... + // ... + dispatch(location); +}); +``` +**[⬆ 맨 위로](#목차)** + +### 불필요한 전후 관계를 더하지 마라 +클래스나 오브젝트의 이름이 설명해주는 것을 +변수의 이름에서 반복하지 마세요. + +**나쁜 예:** +```javascript +const Car = { + carMake: 'Honda', + carModel: 'Accord', + carColor: 'Blue' +}; + +function paintCar(car) { + car.carColor = 'Red'; +} +``` + +**좋은 예:** +```javascript +const Car = { + make: 'Honda', + model: 'Accord', + color: 'Blue' +}; + +function paintCar(car) { + car.color = 'Red'; +} +``` +**[⬆ 맨 위로](#목차)** + +### 단락 또는 조건문을 사용하는 대신 기본 아규먼트를 사용하라 + +**나쁜 예:** +```javascript +function createMicrobrewery(name) { + const breweryName = name || 'Hipster Brew Co.'; + // ... +} + +``` + +**좋은 예:** +```javascript +function createMicrobrewery(breweryName = 'Hipster Brew Co.') { + // ... +} + +``` +**[⬆ 맨 위로](#목차)** + +## **함수** +### 함수에서의 인수 사용 (2개, 이상적으로 더 적게) +함수를 사용할 땐 매개변수의 양을 제한하는 것이 매우 중요합니다. +왜냐하면 그것이 함수에 대한 테스트를 보다 쉽게 만들어주기 때문입니다. +매개변수를 세 개이상 사용하게 되면 조합확산의 문제가 생기며, +그로인해 당신은 각각의 인수에 대한 다양한 경우의 수를 테스트 해야만 합니다. + +인수가 0개인 것이 이상적인 경우입니다. 인수가 1개 또는 2개인 것도 괜찮으며, 3개이면 피해야 합니다. +인수가 3개 이상이라면 그 인수들은 통합되어야 합니다. +보통 2개 이상의 인수가 있는 경우, 그 함수는 너무 많은 일을 하고 있습니다. +그게 아니라면 대부분의 경우 상위 오브젝트가 인수로서 +충분할 것입니다. + +자바 스크립트는 다른 프로그래밍 언어와 달리 여러 상용 클래스를 덧붙일 필요 없이 +즉석에서 오브젝트를 만들 수 있게 해줍니다. 그래서 많은 수의 인수가 필요한 경우 +그냥 오브젝트를 사용하면 됩니다. + +**나쁜 예:** +```javascript +function createMenu(title, body, buttonText, cancellable) { + // ... +} +``` + +**좋은 예:** +```javascript +function createMenu(config) { + // ... +} + +createMenu({ + title: 'Foo', + body: 'Bar', + buttonText: 'Baz', + cancellable: true +}); +``` +**[⬆ 맨 위로](#목차)** + + +### 함수는 하나의 일만 해야한다 +이것은 소프트웨어 엔지니어링에 있어서 가장 중요한 규칙입니다. +함수가 두 개이상의 일을 할 때, 함수에 대한 작성, 테스트, 추론이 어려워집니다. +함수를 하나의 행동 단위로 작성하면 훨씬 쉽게 리팩토링 할 수 있으며, +코드 또한 훨씬 더 명확하게 읽히게 됩니다. +만약 당신이 이 가이드에서 이것만 배워간다고 하더라도, 당신은 다른 많은 개발자들보다 앞서 나갈 수 있습니다. + +**나쁜 예:** +```javascript +function emailClients(clients) { + clients.forEach((client) => { + const clientRecord = database.lookup(client); + if (clientRecord.isActive()) { + email(client); + } + }); +} +``` + +**좋은 예:** +```javascript +function emailClients(clients) { + clients + .filter(isClientActive) + .forEach(email); +} + +function isClientActive(client) { + const clientRecord = database.lookup(client); + return clientRecord.isActive(); +} +``` +**[⬆ 맨 위로](#목차)** + +### 함수의 이름에는 그 함수가 하는 일이 표현되어야 한다 + +**나쁜 예:** +```javascript +function addToDate(date, month) { + // ... +} + +const date = new Date(); + +// It's hard to to tell from the function name what is added +addToDate(date, 1); +``` + +**좋은 예:** +```javascript +function addMonthToDate(month, date) { + // ... +} + +const date = new Date(); +addMonthToDate(1, date); +``` +**[⬆ 맨 위로](#목차)** + +### 함수는 추상화의 한 수준이어야한다 +보통 하나의 함수에 한 수준 이상의 추상화가 있는 경우, +그 함수는 너무 많은 일을 하고 있습니다. +함수를 분리하면 재사용성이 높아지고 테스트가 쉬워집니다. + +**나쁜 예:** +```javascript +function parseBetterJSAlternative(code) { + const REGEXES = [ + // ... + ]; + + const statements = code.split(' '); + const tokens = []; + REGEXES.forEach((REGEX) => { + statements.forEach((statement) => { + // ... + }); + }); + + const ast = []; + tokens.forEach((token) => { + // lex... + }); + + ast.forEach((node) => { + // parse... + }); +} +``` + +**좋은 예:** +```javascript +function tokenize(code) { + const REGEXES = [ + // ... + ]; + + const statements = code.split(' '); + const tokens = []; + REGEXES.forEach((REGEX) => { + statements.forEach((statement) => { + tokens.push( /* ... */ ); + }); + }); + + return tokens; +} + +function lexer(tokens) { + const ast = []; + tokens.forEach((token) => { + ast.push( /* ... */ ); + }); + + return ast; +} + +function parseBetterJSAlternative(code) { + const tokens = tokenize(code); + const ast = lexer(tokens); + ast.forEach((node) => { + // parse... + }); +} +``` +**[⬆ 맨 위로](#목차)** + +### 중복 되는 코드를 제거하라 +중복 되는 코드를 피하기 위해 최선을 다하십시오. +중복 되는 코드가 있다면 로직을 변경해야 할 일이 생겼을 때 +두 곳 이상의 부분을 수정해야합니다. + +당신이 레스토랑을 운영하고 있으며, 재고를 추적하고 있다고 상상해 보십시오: 토마토, 양파, 마늘 향신료 등 +만약 당신이 이에 대한 여러 개의 리스트를 가지고 있다면, +토마토를 접시에 담을 때마다 각각의 리스트를 업데이트 해야 합니다. +만약 당신이 오직 하나의 리스트만 가지고 있다면, 업데이트 할 것도 하나 뿐입니다! + +중복되는 코드를 가지게 되는 흔한 경우는 많은 부분에서 공통점을 가지고 있고 비슷한 일을 하지만, +작은 차이가 있는 함수를 만들 때다. 실제로 두 함수는 많은 공통점을 가지지만 +이로 인해 두 개 이상의 함수로 나뉘어 중복되는 코드가 된다. +중복 코드를 제거한다는 것은 하나의 함수/모듈/클래스를 가지고 +여러 가지 집합을 처리 할 수 있는 추상화를 만드는 것을 의미합니다. + +올바른 추상화를 취하는 것이 중요합니다. +이를 위해선 *클래스* 섹션에서 제시된 원칙을 따라야만 합니다. +나쁜 추상화는 중복되는 코드보다 나쁠 수 있으니 꼭 조심하세요! +만약 당신이 좋은 추상화를 만들 수 있다면 그러세요! 반복하지 마세요. +그렇지 않으면 단 하나의 요소만 변경 했는데도 여러 부분을 업데이트하고 있는 자신을 발견할 것입니다. + +**나쁜 예:** +```javascript +function showDeveloperList(developers) { + developers.forEach((developer) => { + 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); + }); +} +``` + +**좋은 예:** +```javascript +function showList(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); + }); +} +``` +**[⬆ 맨 위로](#목차)** + +### Object.assign을 사용하여 기본 오브젝트를 설정하라 + +**나쁜 예:** +```javascript +const menuConfig = { + title: null, + body: 'Bar', + buttonText: null, + cancellable: true +}; + +function createMenu(config) { + config.title = config.title || 'Foo'; + config.body = config.body || 'Bar'; + config.buttonText = config.buttonText || 'Baz'; + config.cancellable = config.cancellable === undefined ? config.cancellable : true; +} + +createMenu(menuConfig); +``` + +**좋은 예:** +```javascript +const menuConfig = { + title: 'Order', + // User did not include 'body' key + buttonText: 'Send', + cancellable: true +}; + +function createMenu(config) { + config = Object.assign({ + title: 'Foo', + body: 'Bar', + buttonText: 'Baz', + cancellable: true + }, config); + + // config now equals: {title: "Order", body: "Bar", buttonText: "Send", cancellable: true} + // ... +} + +createMenu(menuConfig); +``` +**[⬆ 맨 위로](#목차)** + + +### 함수의 매개변수로 플래그를 사용하지 마라 +플래그는 당신의 함수가 두 가지 이상의 일을 하고 있다는 것을 보여주는 지표입니다. 함수는 한 가지 일만 해야합니다. 만약 불리언에 따라 코드의 경로가 달라진다면 함수를 분할해야합니다. + +**나쁜 예:** +```javascript +function createFile(name, temp) { + if (temp) { + fs.create(`./temp/${name}`); + } else { + fs.create(name); + } +} +``` + +**좋은 예:** +```javascript +function createFile(name) { + fs.create(name); +} + +function createTempFile(name) { + createFile(`./temp/${name}`); +} +``` +**[⬆ 맨 위로](#목차)** + +### 부작용 피하기 (1부) +값을 가져오거나 값을 리턴하는 것 외의 일을 하는 함수는 부작용을 일으킵니다. +이 부작용은 파일을 덮여쓰거나, +전역변수를 수정하거나, +심지어는 모든 돈을 낯선 사람에게 송금하는 것일 수 있습니다. + +하지만 때때로 당신은 프로그램에 부작용을 일으킬 필요가 있습니다. +앞에서 예로 들었듯 파일을 작성하는 함수가 필요할 때가 있기 때문입니다. +그러기 위해선 부작용이 일어난 곳을 중앙집중화하여야 합니다. 특정 파일을 작성하는 여러가지 함수와 클래스를 +만들지 마십시오. 오직 한가지, 한가지의 서비스만 만들어야 합니다. + +요점은 함정들을 피하는 데 있습니다. 예컨대 정해진 구조 없이 오브젝트 간에 상태를 공유한다거나, +무엇으로든 변경 가능한 데이터 타입을 사용한다거나, +혹은 부작용이 일어나는 곳을 중앙집중화하지 않는다거나 하는 함정들 말입니다. +만약 당신이 이렇게 할 수 있다면, 당신은 대부분의 다른 프로그래머보다 더 행복해질 수 있을 것 입니다. + +**나쁜 예:** +```javascript +// Global variable referenced by following function. +// If we had another function that used this name, now it'd be an array and it could break it. +let name = 'Ryan McDermott'; + +function splitIntoFirstAndLastName() { + name = name.split(' '); +} + +splitIntoFirstAndLastName(); + +console.log(name); // ['Ryan', 'McDermott']; +``` + +**좋은 예:** +```javascript +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부) +자바 스크립트에서 프리미티브는 값으로 전달되며, 오브젝트/배열은 참조로 전달됩니다. +말인즉 함수가 구매할 항목을 추가한다거나 하는 이유로 장바구니 배열(또는 오브젝트)을 변경하면 +해당 `cart` 배열을 사용하는 다른 모든 함수가 변경에 영향을 받게 된다는 것입니다. +이것은 정말 좋지만, +나쁠 때도 있습니다. +최악의 상황을 가정해봅시다. + +한 쇼핑몰을 이용하는 사용자가 "구매"버튼을 클릭합니다. +곧이어 네트워크 요청을 생성하는`purchase` 함수가 호출되고 `cart` 배열이 서버에 전송됩니다. +그런데 네트워크 연결이 좋지 않아 `purchase` 함수의 요청이 다시 시도 됩니다. +만약 재전송이 시도 되기 전 사용자가 실수로 원하지 않는 항목을 +"장바구니에 추가" 했다면 어떻게 될까요? +이 경우, addItemToCart 함수에 의해 장바구니에 원치 않는 항목이 추가된 뒤 +수정 된 장바구니 배열에 대한 참조가 발생하고 +그 결과 원치 않는 항목이 추가 된 리스트를 서버에 보내게 +됩니다. + +가장 좋은 해결책은 `addItemToCart` 함수가 항상 `cart`를 복제하고, +편집하고, 복제본을 리턴하도록 하는 것입니다. +이렇게 하면 장바구니를 참조하는 함수들이 어떤 변화에도 영향을 받지 않게 됩니다. + +이 접근법에 대한 두 가지 주의사항: + 1. 실제로 입력 오브젝트를 수정하고자 하는 경우가 있을 수 있습니다. +하지만 이 프로그래밍 습관을 도입하면 그러한 사례들이 상당히 드물다는 것을 알게 될 것입니다. +또한 대부분의 프로그램은 부작용을 일으키지 않도록 리팩토링 될 수 있습니다! + + 2. 큰 오브젝트를 복제하는 것은 성능 측면에서 매우 비쌉니다. +하지만 운이 좋게도 이것은 큰 문제가 아닙니다. 왜냐하면 우리에겐 +[훌륭한 라이브러리](https://facebook.github.io/immutable-js/)가 있기 때문입니다. +이것이 수동으로 오브젝트와 배열을 복제할 수 있게 도와주며, +오브젝트의 복제를 빠르고 메모리 집약적이지 않게 만들어줍니다. + +**Bad:** +```javascript +const addItemToCart = (cart, item) => { + cart.push({ item, date: Date.now() }); +}; +``` + +**Good:** +```javascript +const addItemToCart = (cart, item) => { + return [...cart, { item, date : Date.now() }]; +}; +``` + +**[⬆ 맨 위로](#목차)** + +### Don't write to global functions +Polluting globals is a bad practice in JavaScript because you could clash with another +library and the user of your API would be none-the-wiser until they get an +exception in production. Let's think about an example: what if you wanted to +extend JavaScript's native Array method to have a `diff` method that could +show the difference between two arrays? You could write your new function +to the `Array.prototype`, but it could clash with another library that tried +to do the same thing. What if that other library was just using `diff` to find +the difference between the first and last elements of an array? This is why it +would be much better to just use ES2015/ES6 classes and simply extend the `Array` global. + +**나쁜 예:** +```javascript +Array.prototype.diff = function diff(comparisonArray) { + const hash = new Set(comparisonArray); + return this.filter(elem => !hash.has(elem)); +}; +``` + +**좋은 예:** +```javascript +class SuperArray extends Array { + diff(comparisonArray) { + const hash = new Set(comparisonArray); + return this.filter(elem => !hash.has(elem)); + } +} +``` +**[⬆ 맨 위로](#목차)** + +### Favor functional programming over imperative programming +JavaScript isn't a functional language in the way that Haskell is, but it has +a functional flavor to it. Functional languages are cleaner and easier to test. +Favor this style of programming when you can. + +**나쁜 예:** +```javascript +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; +} +``` + +**좋은 예:** +```javascript +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, 0); +``` +**[⬆ 맨 위로](#목차)** + +### Encapsulate conditionals + +**나쁜 예:** +```javascript +if (fsm.state === 'fetching' && isEmpty(listNode)) { + // ... +} +``` + +**좋은 예:** +```javascript +function shouldShowSpinner(fsm, listNode) { + return fsm.state === 'fetching' && isEmpty(listNode); +} + +if (shouldShowSpinner(fsmInstance, listNodeInstance)) { + // ... +} +``` +**[⬆ 맨 위로](#목차)** + +### Avoid negative conditionals + +**나쁜 예:** +```javascript +function isDOMNodeNotPresent(node) { + // ... +} + +if (!isDOMNodeNotPresent(node)) { + // ... +} +``` + +**좋은 예:** +```javascript +function isDOMNodePresent(node) { + // ... +} + +if (isDOMNodePresent(node)) { + // ... +} +``` +**[⬆ 맨 위로](#목차)** + +### Avoid conditionals +This seems like an impossible task. Upon first hearing this, most people say, +"how am I supposed to do anything without an `if` statement?" The answer is that +you can use polymorphism to achieve the same task in many cases. The second +question is usually, "well that's great but why would I want to do that?" The +answer is a previous clean code concept we learned: a function should only do +one thing. When you have classes and functions that have `if` statements, you +are telling your user that your function does more than one thing. Remember, +just do one thing. + +**나쁜 예:** +```javascript +class Airplane { + // ... + getCruisingAltitude() { + switch (this.type) { + case '777': + return this.getMaxAltitude() - this.getPassengerCount(); + case 'Air Force One': + return this.getMaxAltitude(); + case 'Cessna': + return this.getMaxAltitude() - this.getFuelExpenditure(); + } + } +} +``` + +**좋은 예:** +```javascript +class Airplane { + // ... +} + +class Boeing777 extends Airplane { + // ... + getCruisingAltitude() { + return this.getMaxAltitude() - this.getPassengerCount(); + } +} + +class AirForceOne extends Airplane { + // ... + getCruisingAltitude() { + return this.getMaxAltitude(); + } +} + +class Cessna extends Airplane { + // ... + getCruisingAltitude() { + return this.getMaxAltitude() - this.getFuelExpenditure(); + } +} +``` +**[⬆ 맨 위로](#목차)** + +### Avoid type-checking (part 1) +JavaScript is untyped, which means your functions can take any type of argument. +Sometimes you are bitten by this freedom and it becomes tempting to do +type-checking in your functions. There are many ways to avoid having to do this. +The first thing to consider is consistent APIs. + +**나쁜 예:** +```javascript +function travelToTexas(vehicle) { + if (vehicle instanceof Bicycle) { + vehicle.peddle(this.currentLocation, new Location('texas')); + } else if (vehicle instanceof Car) { + vehicle.drive(this.currentLocation, new Location('texas')); + } +} +``` + +**좋은 예:** +```javascript +function travelToTexas(vehicle) { + vehicle.move(this.currentLocation, new Location('texas')); +} +``` +**[⬆ 맨 위로](#목차)** + +### Avoid type-checking (part 2) +If you are working with basic primitive values like strings, integers, and arrays, +and you can't use polymorphism but you still feel the need to type-check, +you should consider using TypeScript. It is an excellent alternative to normal +JavaScript, as it provides you with static typing on top of standard JavaScript +syntax. The problem with manually type-checking normal JavaScript is that +doing it well requires so much extra verbiage that the faux "type-safety" you get +doesn't make up for the lost readability. Keep your JavaScript clean, write +good tests, and have good code reviews. Otherwise, do all of that but with +TypeScript (which, like I said, is a great alternative!). + +**나쁜 예:** +```javascript +function combine(val1, val2) { + if (typeof val1 === 'number' && typeof val2 === 'number' || + typeof val1 === 'string' && typeof val2 === 'string') { + return val1 + val2; + } + + throw new Error('Must be of type String or Number'); +} +``` + +**좋은 예:** +```javascript +function combine(val1, val2) { + return val1 + val2; +} +``` +**[⬆ 맨 위로](#목차)** + +### Don't over-optimize +Modern browsers do a lot of optimization under-the-hood at runtime. A lot of +times, if you are optimizing then you are just wasting your time. [There are good +resources](https://github.com/petkaantonov/bluebird/wiki/Optimization-killers) +for seeing where optimization is lacking. Target those in the meantime, until +they are fixed if they can be. + +**나쁜 예:** +```javascript + +// On old browsers, each iteration with uncached `list.length` would be costly +// because of `list.length` recomputation. In modern browsers, this is optimized. +for (let i = 0, len = list.length; i < len; i++) { + // ... +} +``` + +**좋은 예:** +```javascript +for (let i = 0; i < list.length; i++) { + // ... +} +``` +**[⬆ 맨 위로](#목차)** + +### Remove dead code +Dead code is just as bad as duplicate code. There's no reason to keep it in +your codebase. If it's not being called, get rid of it! It will still be safe +in your version history if you still need it. + +**나쁜 예:** +```javascript +function oldRequestModule(url) { + // ... +} + +function newRequestModule(url) { + // ... +} + +const req = newRequestModule; +inventoryTracker('apples', req, 'www.inventory-awesome.io'); + +``` + +**좋은 예:** +```javascript +function newRequestModule(url) { + // ... +} + +const req = newRequestModule; +inventoryTracker('apples', req, 'www.inventory-awesome.io'); +``` +**[⬆ 맨 위로](#목차)** + +## **오브젝트 및 자료구조** +### Use getters and setters +JavaScript doesn't have interfaces or types so it is very hard to enforce this +pattern, because we don't have keywords like `public` and `private`. As it is, +using getters and setters to access data on objects is far better than simply +looking for a property on an object. "Why?" you might ask. Well, here's an +unorganized list of reasons why: + +* When you want to do more beyond getting an object property, you don't have +to look up and change every accessor in your codebase. +* Makes adding validation simple when doing a `set`. +* Encapsulates the internal representation. +* Easy to add logging and error handling when getting and setting. +* Inheriting this class, you can override default functionality. +* You can lazy load your object's properties, let's say getting it from a +server. + + +**나쁜 예:** +```javascript +class BankAccount { + constructor() { + this.balance = 1000; + } +} + +const bankAccount = new BankAccount(); + +// Buy shoes... +bankAccount.balance -= 100; +``` + +**좋은 예:** +```javascript +class BankAccount { + constructor(balance = 1000) { + this._balance = balance; + } + + // It doesn't have to be prefixed with `get` or `set` to be a getter/setter + set balance(amount) { + if (verifyIfAmountCanBeSetted(amount)) { + this._balance = amount; + } + } + + get balance() { + return this._balance; + } + + verifyIfAmountCanBeSetted(val) { + // ... + } +} + +const bankAccount = new BankAccount(); + +// Buy shoes... +bankAccount.balance -= shoesPrice; + +// Get balance +let balance = bankAccount.balance; + +``` +**[⬆ 맨 위로](#목차)** + + +### Make objects have private members +This can be accomplished through closures (for ES5 and below). + +**나쁜 예:** +```javascript + +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 +``` + +**좋은 예:** +```javascript +const Employee = function (name) { + this.getName = function getName() { + return 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: John Doe +``` +**[⬆ 맨 위로](#목차)** + + +## **클래스** +### Single Responsibility Principle (SRP) +As stated in Clean Code, "There should never be more than one reason for a class +to change". It's tempting to jam-pack a class with a lot of functionality, like +when you can only take one suitcase on your flight. The issue with this is +that your class won't be conceptually cohesive and it will give it many reasons +to change. Minimizing the amount of times you need to change a class is important. +It's important because if too much functionality is in one class and you modify a piece of it, +it can be difficult to understand how that will affect other dependent modules in +your codebase. + +**나쁜 예:** +```javascript +class UserSettings { + constructor(user) { + this.user = user; + } + + changeSettings(settings) { + if (this.verifyCredentials()) { + // ... + } + } + + verifyCredentials() { + // ... + } +} +``` + +**좋은 예:** +```javascript +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) +As stated by Bertrand Meyer, "software entities (classes, modules, functions, +etc.) should be open for extension, but closed for modification." What does that +mean though? This principle basically states that you should allow users to +add new functionalities without changing existing code. + +**나쁜 예:** +```javascript +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 +} +``` + +**좋은 예:** +```javascript +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) +This is a scary term for a very simple concept. It's formally defined as "If S +is a subtype of T, then objects of type T may be replaced with objects of type S +(i.e., objects of type S may substitute objects of type T) without altering any +of the desirable properties of that program (correctness, task performed, +etc.)." That's an even scarier definition. + +The best explanation for this is if you have a parent class and a child class, +then the base class and child class can be used interchangeably without getting +incorrect results. This might still be confusing, so let's take a look at the +classic Square-Rectangle example. Mathematically, a square is a rectangle, but +if you model it using the "is-a" relationship via inheritance, you quickly +get into trouble. + +**나쁜 예:** +```javascript +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(); // 나쁜 예: Will return 25 for Square. Should be 20. + rectangle.render(area); + }); +} + +const rectangles = [new Rectangle(), new Rectangle(), new Square()]; +renderLargeRectangles(rectangles); +``` + +**좋은 예:** +```javascript +class Shape { + setColor(color) { + // ... + } + + render(area) { + // ... + } +} + +class Rectangle extends Shape { + constructor() { + super(); + this.width = 0; + this.height = 0; + } + + setWidth(width) { + this.width = width; + } + + setHeight(height) { + this.height = height; + } + + getArea() { + return this.width * this.height; + } +} + +class Square extends Shape { + constructor() { + super(); + this.length = 0; + } + + setLength(length) { + this.length = length; + } + + getArea() { + return this.length * this.length; + } +} + +function renderLargeShapes(shapes) { + shapes.forEach((shape) => { + switch (shape.constructor.name) { + case 'Square': + shape.setLength(5); + break; + case 'Rectangle': + shape.setWidth(4); + shape.setHeight(5); + } + + const area = shape.getArea(); + shape.render(area); + }); +} + +const shapes = [new Rectangle(), new Rectangle(), new Square()]; +renderLargeShapes(shapes); +``` +**[⬆ 맨 위로](#목차)** + +### Interface Segregation Principle (ISP) +JavaScript doesn't have interfaces so this principle doesn't apply as strictly +as others. However, it's important and relevant even with JavaScript's lack of +type system. + +ISP states that "Clients should not be forced to depend upon interfaces that +they do not use." Interfaces are implicit contracts in JavaScript because of +duck typing. + +A good example to look at that demonstrates this principle in JavaScript is for +classes that require large settings objects. Not requiring clients to setup +huge amounts of options is beneficial, because most of the time they won't need +all of the settings. Making them optional helps prevent having a "fat interface". + +**나쁜 예:** +```javascript +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() {} // Most of the time, we won't need to animate when traversing. + // ... +}); + +``` + +**좋은 예:** +```javascript +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) +This principle states two essential things: +1. High-level modules should not depend on low-level modules. Both should +depend on abstractions. +2. Abstractions should not depend upon details. Details should depend on +abstractions. + +This can be hard to understand at first, but if you've worked with Angular.js, +you've seen an implementation of this principle in the form of Dependency +Injection (DI). While they are not identical concepts, DIP keeps high-level +modules from knowing the details of its low-level modules and setting them up. +It can accomplish this through DI. A huge benefit of this is that it reduces +the coupling between modules. Coupling is a very bad development pattern because +it makes your code hard to refactor. + +As stated previously, JavaScript doesn't have interfaces so the abstractions +that are depended upon are implicit contracts. That is to say, the methods +and properties that an object/class exposes to another object/class. In the +example below, the implicit contract is that any Request module for an +`InventoryTracker` will have a `requestItems` method. + +**나쁜 예:** +```javascript +class InventoryRequester { + constructor() { + this.REQ_METHODS = ['HTTP']; + } + + requestItem(item) { + // ... + } +} + +class InventoryTracker { + constructor(items) { + this.items = items; + + // 나쁜 예: We have created a dependency on a specific request implementation. + // We should just have requestItems depend on a request method: `request` + this.requester = new InventoryRequester(); + } + + requestItems() { + this.items.forEach((item) => { + this.requester.requestItem(item); + }); + } +} + +const inventoryTracker = new InventoryTracker(['apples', 'bananas']); +inventoryTracker.requestItems(); +``` + +**좋은 예:** +```javascript +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) { + // ... + } +} + +// By constructing our dependencies externally and injecting them, we can easily +// substitute our request module for a fancy new one that uses WebSockets. +const inventoryTracker = new InventoryTracker(['apples', 'bananas'], new InventoryRequesterV2()); +inventoryTracker.requestItems(); +``` +**[⬆ 맨 위로](#목차)** + +### Prefer ES2015/ES6 classes over ES5 plain functions +It's very difficult to get readable class inheritance, construction, and method +definitions for classical ES5 classes. If you need inheritance (and be aware +that you might not), then prefer classes. However, prefer small functions over +classes until you find yourself needing larger and more complex objects. + +**나쁜 예:** +```javascript +const Animal = function(age) { + if (!(this instanceof Animal)) { + throw new Error('Instantiate Animal with `new`'); + } + + this.age = age; +}; + +Animal.prototype.move = function move() {}; + +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() {}; +``` + +**좋은 예:** +```javascript +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() { /* ... */ } +} +``` +**[⬆ 맨 위로](#목차)** + + +### Use method chaining +This pattern is very useful in JavaScript and you see it in many libraries such +as jQuery and Lodash. It allows your code to be expressive, and less verbose. +For that reason, I say, use method chaining and take a look at how clean your code +will be. In your class functions, simply return `this` at the end of every function, +and you can chain further class methods onto it. + +**나쁜 예:** +```javascript +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(); +``` + +**좋은 예:** +```javascript +class Car { + constructor() { + this.make = 'Honda'; + this.model = 'Accord'; + this.color = 'white'; + } + + setMake(make) { + this.make = make; + // NOTE: Returning this for chaining + return this; + } + + setModel(model) { + this.model = model; + // NOTE: Returning this for chaining + return this; + } + + setColor(color) { + this.color = color; + // NOTE: Returning this for chaining + return this; + } + + save() { + console.log(this.make, this.model, this.color); + // NOTE: Returning this for chaining + return this; + } +} + +const car = new Car() + .setColor('pink') + .setMake('Ford') + .setModel('F-150') + .save(); +``` +**[⬆ 맨 위로](#목차)** + +### Prefer composition over inheritance +As stated famously in [*Design Patterns*](https://en.wikipedia.org/wiki/Design_Patterns) by the Gang of Four, +you should prefer composition over inheritance where you can. There are lots of +good reasons to use inheritance and lots of good reasons to use composition. +The main point for this maxim is that if your mind instinctively goes for +inheritance, try to think if composition could model your problem better. In some +cases it can. + +You might be wondering then, "when should I use inheritance?" It +depends on your problem at hand, but this is a decent list of when inheritance +makes more sense than composition: + +1. Your inheritance represents an "is-a" relationship and not a "has-a" +relationship (Human->Animal vs. User->UserDetails). +2. You can reuse code from the base classes (Humans can move like all animals). +3. You want to make global changes to derived classes by changing a base class. +(Change the caloric expenditure of all animals when they move). + +**나쁜 예:** +```javascript +class Employee { + constructor(name, email) { + this.name = name; + this.email = email; + } + + // ... +} + +// Bad because Employees "have" tax data. EmployeeTaxData is not a type of Employee +class EmployeeTaxData extends Employee { + constructor(ssn, salary) { + super(); + this.ssn = ssn; + this.salary = salary; + } + + // ... +} +``` + +**좋은 예:** +```javascript +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 is more important than shipping. If you have no tests or an +inadequate amount, then every time you ship code you won't be sure that you +didn't break anything. Deciding on what constitutes an adequate amount is up +to your team, but having 100% coverage (all statements and branches) is how +you achieve very high confidence and developer peace of mind. This means that +in addition to having a great testing framework, you also need to use a +[good coverage tool](http://gotwarlost.github.io/istanbul/). + +There's no excuse to not write tests. There's [plenty of good JS test frameworks] +(http://jstherightway.org/#testing-tools), so find one that your team prefers. +When you find one that works for your team, then aim to always write tests +for every new feature/module you introduce. If your preferred method is +Test Driven Development (TDD), that is great, but the main point is to just +make sure you are reaching your coverage goals before launching any feature, +or refactoring an existing one. + +### Single concept per test + +**나쁜 예:** +```javascript +const assert = require('assert'); + +describe('MakeMomentJSGreatAgain', () => { + it('handles date boundaries', () => { + let date; + + date = new MakeMomentJSGreatAgain('1/1/2015'); + date.addDays(30); + date.shouldEqual('1/31/2015'); + + 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); + }); +}); +``` + +**좋은 예:** +```javascript +const assert = require('assert'); + +describe('MakeMomentJSGreatAgain', () => { + it('handles 30-day months', () => { + const date = new MakeMomentJSGreatAgain('1/1/2015'); + date.addDays(30); + date.shouldEqual('1/31/2015'); + }); + + 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); + }); +}); +``` +**[⬆ 맨 위로](#목차)** + +## **동시성** +### Use Promises, not callbacks +Callbacks aren't clean, and they cause excessive amounts of nesting. With ES2015/ES6, +Promises are a built-in global type. Use them! + +**나쁜 예:** +```javascript +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'); + } + }); + } +}); + +``` + +**좋은 예:** +```javascript +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); + }); + +``` +**[⬆ 맨 위로](#목차)** + +### Async/Await are even cleaner than Promises +Promises are a very clean alternative to callbacks, but ES2017/ES8 brings async and await +which offer an even cleaner solution. All you need is a function that is prefixed +in an `async` keyword, and then you can write your logic imperatively without +a `then` chain of functions. Use this if you can take advantage of ES2017/ES8 features +today! + +**나쁜 예:** +```javascript +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); + }); + +``` + +**좋은 예:** +```javascript +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); + } +} +``` +**[⬆ 맨 위로](#목차)** + + +## **오류 처리** +Thrown errors are a good thing! They mean the runtime has successfully +identified when something in your program has gone wrong and it's letting +you know by stopping function execution on the current stack, killing the +process (in Node), and notifying you in the console with a stack trace. + +### Don't ignore caught errors +Doing nothing with a caught error doesn't give you the ability to ever fix +or react to said error. Logging the error to the console (`console.log`) +isn't much better as often times it can get lost in a sea of things printed +to the console. If you wrap any bit of code in a `try/catch` it means you +think an error may occur there and therefore you should have a plan, +or create a code path, for when it occurs. + +**나쁜 예:** +```javascript +try { + functionThatMightThrow(); +} catch (error) { + console.log(error); +} +``` + +**좋은 예:** +```javascript +try { + functionThatMightThrow(); +} catch (error) { + // One option (more noisy than console.log): + console.error(error); + // Another option: + notifyUserOfError(error); + // Another option: + reportErrorToService(error); + // OR do all three! +} +``` + +### Don't ignore rejected promises +For the same reason you shouldn't ignore caught errors +from `try/catch`. + +**나쁜 예:** +```javascript +getdata() +.then((data) => { + functionThatMightThrow(data); +}) +.catch((error) => { + console.log(error); +}); +``` + +**좋은 예:** +```javascript +getdata() +.then((data) => { + functionThatMightThrow(data); +}) +.catch((error) => { + // One option (more noisy than console.log): + console.error(error); + // Another option: + notifyUserOfError(error); + // Another option: + reportErrorToService(error); + // OR do all three! +}); +``` + +**[⬆ 맨 위로](#목차)** + + +## **포맷팅** +Formatting is subjective. Like many rules herein, there is no hard and fast +rule that you must follow. The main point is DO NOT ARGUE over formatting. +There are [tons of tools](http://standardjs.com/rules.html) to automate this. +Use one! It's a waste of time and money for engineers to argue over formatting. + +For things that don't fall under the purview of automatic formatting +(indentation, tabs vs. spaces, double vs. single quotes, etc.) look here +for some guidance. + +### Use consistent capitalization +JavaScript is untyped, so capitalization tells you a lot about your variables, +functions, etc. These rules are subjective, so your team can choose whatever +they want. The point is, no matter what you all choose, just be consistent. + +**나쁜 예:** +```javascript +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 {} +``` + +**좋은 예:** +```javascript +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 {} +``` +**[⬆ 맨 위로](#목차)** + + +### Function callers and callees should be close +If a function calls another, keep those functions vertically close in the source +file. Ideally, keep the caller right above the callee. We tend to read code from +top-to-bottom, like a newspaper. Because of this, make your code read that way. + +**나쁜 예:** +```javascript +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(); +``` + +**좋은 예:** +```javascript +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(); +``` + +**[⬆ 맨 위로](#목차)** + +## **주석** +### 비지니스 로직의 복잡성에 대해서만 주석을 작성하라. +주석은 선택 사항일 뿐 필수적인 것은 아니다. 좋은 코드는 *대개* 그 자체로 좋다. + +**나쁜 예:** +```javascript +function hashIt(data) { + // The hash + let hash = 0; + + // Length of string + const length = data.length; + + // Loop through every character in data + for (let i = 0; i < length; i++) { + // Get character code. + const char = data.charCodeAt(i); + // Make the hash + hash = ((hash << 5) - hash) + char; + // Convert to 32-bit integer + hash &= hash; + } +} +``` + +**좋은 예:** +```javascript + +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; + + // Convert to 32-bit integer + hash &= hash; + } +} + +``` +**[⬆ 맨 위로](#목차)** + +### 코드베이스에 주석 처리 된 코드를 남기지 말라 +버전 관리가 이런 이유 때문에 존재합니다. 오래된 코드는 히스토리에 남겨둡시다. + +**나쁜 예:** +```javascript +doStuff(); +// doOtherStuff(); +// doSomeMoreStuff(); +// doSoMuchStuff(); +``` + +**좋은 예:** +```javascript +doStuff(); +``` +**[⬆ 맨 위로](#목차)** + +### 저널 주석을 하지 말라 +반드시 버전 관리를 사용하세요! 데드 코드, 주석처리 된 코드, 특히 저널 주석은 필요 없게 됩니다. +`git log`를 사용하면 지금까지의 히스토리를 살펴볼 수 있습니다! + +**나쁜 예:** +```javascript +/** + * 2016-12-20: Removed monads, didn't understand them (RM) + * 2016-10-01: Improved using special monads (JP) + * 2016-02-03: Removed type-checking (LI) + * 2015-03-14: Added combine with type-checking (JR) + */ +function combine(a, b) { + return a + b; +} +``` + +**좋은 예:** +```javascript +function combine(a, b) { + return a + b; +} +``` +**[⬆ 맨 위로](#목차)** + +### 위치 마커를 피하라 +위치 표시는 종종 문제를 일으킵니다. 위치 표시를 하지 않더라도 +함수와 변수의 이름에 적절한 들여쓰기와 포맷팅을 적용하면 코드에 시각적 구조가 생깁니다. + +**나쁜 예:** +```javascript +//////////////////////////////////////////////////////////////////////////////// +// Scope Model Instantiation +//////////////////////////////////////////////////////////////////////////////// +$scope.model = { + menu: 'foo', + nav: 'bar' +}; + +//////////////////////////////////////////////////////////////////////////////// +// Action setup +//////////////////////////////////////////////////////////////////////////////// +const actions = function() { + // ... +}; +``` + +**좋은 예:** +```javascript +$scope.model = { + menu: 'foo', + nav: 'bar' +}; + +const actions = function() { + // ... +}; +``` +**[⬆ 맨 위로](#목차)** + +## 번역 + +이 문서는 다른 언어로도 이용 가능합니다: + + - ![br](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/Brazil.png) **Brazilian Portuguese**: [fesnt/clean-code-javascript](https://github.com/fesnt/clean-code-javascript) + - ![cn](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/China.png) **Chinese**: [alivebao/clean-code-js](https://github.com/alivebao/clean-code-js) + - ![de](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/Germany.png) **German**: [marcbruederlin/clean-code-javascript](https://github.com/marcbruederlin/clean-code-javascript) + +**[⬆ 맨 위로](#목차)** +