diff --git a/README.md b/README.md index 3316bb54..15424472 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +Original repository: https://github.com/ryanmcdermott/clean-code-javascript + # clean-code-javascript ## Table of Contents @@ -985,199 +987,7 @@ account.setBalance(100); **[⬆ back to top](#table-of-contents)** -### Make objects have private members -This can be accomplished through closures (for ES5 and below). - -**Bad:** -```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 -``` - -**Good:** -```javascript -function makeEmployee(name) { - return { - 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 -``` -**[⬆ back to top](#table-of-contents)** - - ## **Classes** -### 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 ES2015/ES6 classes. However, prefer small functions over -classes until you find yourself needing larger and more complex objects. - -**Bad:** -```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() {}; -``` - -**Good:** -```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() { /* ... */ } -} -``` -**[⬆ back to top](#table-of-contents)** - - -### 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. - -**Bad:** -```javascript -class Car { - constructor(make, model, color) { - this.make = make; - this.model = model; - this.color = color; - } - - 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('Ford','F-150','red'); -car.setColor('pink'); -car.save(); -``` - -**Good:** -```javascript -class Car { - constructor(make, model, color) { - this.make = make; - this.model = model; - this.color = color; - } - - 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('Ford','F-150','red') - .setColor('pink') - .save(); -``` -**[⬆ back to top](#table-of-contents)** - ### 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 @@ -1288,17 +1098,20 @@ class UserAuth { class UserSettings { - constructor(user) { + constructor(user, auth) { this.user = user; - this.auth = new UserAuth(user); + this.auth = auth; } changeSettings(settings) { if (this.auth.verifyCredentials()) { - // ... + this.user.updateSettings(settings); } } } + +new UserSettings(user, new UserAuth(user)) + ``` **[⬆ back to top](#table-of-contents)** @@ -1403,6 +1216,13 @@ 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. +Note: This is a kind of a valid example but i would dive deeper into the topic. +Please read https://stackoverflow.com/questions/44442448/when-adhering-to-liskov-substitution-principle-lsp-can-a-child-class-implement best answer section. + +Note: Please also read this artical http://sergeyteplyakov.blogspot.com/2014/09/liskov-substitution-principle.html (RU) as more in-depth explanation. + +Note: If you want to know more about type variance, you can read this https://flow.org/en/docs/lang/variance/ explanation with examples in Flow. + **Bad:** ```javascript class Rectangle { @@ -1505,9 +1325,6 @@ renderLargeShapes(shapes); **[⬆ back to top](#table-of-contents)** ### 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 @@ -1519,6 +1336,8 @@ 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". +!IMPORTANT Yevhen's Note: !!!!!!! I don't think that it's a good example. I'm still thinking about a better example. !!!!! + **Bad:** ```javascript class DOMTraverser { @@ -1594,8 +1413,7 @@ 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 +The abstractions 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. @@ -2154,25 +1972,3 @@ const actions = function() { }; ``` **[⬆ back to top](#table-of-contents)** - -## Translation - -This is also available in other languages: - - - ![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) - - ![es](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/Uruguay.png) **Spanish**: [andersontr15/clean-code-javascript](https://github.com/andersontr15/clean-code-javascript-es) - - ![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) - - [beginor/clean-code-javascript](https://github.com/beginor/clean-code-javascript) - - ![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) - - ![kr](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/South-Korea.png) **Korean**: [qkraudghgh/clean-code-javascript-ko](https://github.com/qkraudghgh/clean-code-javascript-ko) - - ![pl](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/Poland.png) **Polish**: [greg-dev/clean-code-javascript-pl](https://github.com/greg-dev/clean-code-javascript-pl) - - ![ru](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/Russia.png) **Russian**: - - [BoryaMogila/clean-code-javascript-ru/](https://github.com/BoryaMogila/clean-code-javascript-ru/) - - [maksugr/clean-code-javascript](https://github.com/maksugr/clean-code-javascript) - - ![vi](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/Vietnam.png) **Vietnamese**: [hienvd/clean-code-javascript/](https://github.com/hienvd/clean-code-javascript/) - - ![ja](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/Japan.png) **Japanese**: [mitsuruog/clean-code-javascript/](https://github.com/mitsuruog/clean-code-javascript/) - - ![id](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/Indonesia.png) **Indonesia**: - [andirkh/clean-code-javascript/](https://github.com/andirkh/clean-code-javascript/) - -**[⬆ back to top](#table-of-contents)**