📜 ⬆️ ⬇️

Pure javascript. Functions

Translation of the book by Ryan McDermott clean-code-javascript

Table of contents:





Function Arguments (ideally 2 or less)


Limiting the number of parameters of a function is incredibly important because it simplifies the testing of a function. The presence of more than three arguments leads to a combinatorial explosion, when you have to go through a lot of different cases with each separate argument.

The ideal situation is the absence of arguments. One or two arguments are good, but three should be avoided.
')
More arguments need to be consolidated. As a rule, if more than two arguments are passed, your function tries to do too much. In cases where this is not the case, it is better to use an object as an argument. Since JavaScript allows you to create objects on the fly, without a special description of classes, they can be used when you need to pass a lot of arguments.

Poorly:

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

Good:

 const menuConfig = { title: 'Foo', body: 'Bar', buttonText: 'Baz', cancellable: true }; function createMenu(config) { // ... } createMenu(menuConfig); 

The function should solve one problem.


This is by far the most important rule in software development. When functions solve more than one task, they are harder to combine, test and understand. As soon as you can reduce each function to performing only one action, it will become much easier to refactor them, and your code will become much more readable. Even if this rule is the only one that you’ve made from this tutorial, you will still be cooler than many developers.

Poorly:

 function emailClients(clients) { clients.forEach((client) => { const clientRecord = database.lookup(client); if (clientRecord.isActive()) { email(client); } }); } 

Good:

 function emailClients(clients) { clients .filter(isClientActive) .forEach(email); } function isClientActive(client) { const clientRecord = database.lookup(client); return clientRecord.isActive(); } 

Function names should describe their purpose.


Poorly:

 function addToDate(date, month) { // ... } const date = new Date(); //     ,    addToDate(date, 1); 

Good:

 function addMonthToDate(month, date) { // ... } const date = new Date(); addMonthToDate(1, date); 

Functions must represent only one level of abstraction.


If more than one level of abstraction is represented in a function, then, as a rule, it does too much. Separating such functions will result in reusability and ease of testing.

Poorly:

 function parseBetterJSAlternative(code) { const REGEXES = [ // ... ]; const statements = code.split(' '); const tokens = []; REGEXES.forEach((REGEX) => { statements.forEach((statement) => { // ... }); }); const ast = []; tokens.forEach((token) => { // ... }); ast.forEach((node) => { // ... }); } 

Good:

 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) => { // ... }); } 

Get rid of duplicate code


Try hard to avoid duplicate code. Duplicate code is harmful because it implies the presence of more than one place where you have to make edits if the logic of the actions changes.

Imagine running a restaurant and keeping records of all products — tomatoes, onions, garlic, spices, etc. If their records are kept in different lists, then the supply of any dish with tomatoes will require changes in each list. If there is only one list, then there will be only one edit!

Often, duplicate code occurs in cases where you need to implement two or more slightly different actions that are generally very similar, but their differences force you to have two or more functions that do almost the same thing. In this case, getting rid of duplicate code will mean creating an abstraction that can represent all the differences in the form of a single function, class or module.

Creating the right abstraction is a matter of incredible importance, and that is why you must follow the principles of SOLID. Bad abstractions can be worse than duplicate code, so be careful!

To summarize: if you can wrap a code with a good abstraction, do so! Do not duplicate the code, otherwise you will have to make many edits to every small change.

Poorly:

 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); }); } 

Good:

 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); }); } 

Set default objects with Object.assign


Poorly:

 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); 

Good:

 const menuConfig = { title: 'Order', //     'body' buttonText: 'Send', cancellable: true }; function createMenu(config) { config = Object.assign({ title: 'Foo', body: 'Bar', buttonText: 'Baz', cancellable: true }, config); //  config = {title: "Order", body: "Bar", buttonText: "Send", cancellable: true} // ... } createMenu(menuConfig); 

Do not use flags as function parameters.


Flags tell the user that the function performs more than one action. The function should solve one problem. Separate functions if they execute different code variants based on a logical value.

Poorly:

 function createFile(name, temp) { if (temp) { fs.create(`./temp/${name}`); } else { fs.create(name); } } 

Good:

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

Avoid side effects (Part 1)


The function produces a side effect if it performs any action besides getting the value and returning another value or values. A side effect may be writing to a file, changing some global variables, or randomly transferring all your money to unknown persons.

However, side effects in the program are necessary. Suppose, as in the previous example, you need to write to the file. Describe what you want to do, strictly in one place.

Do not create multiple functions and classes that write something to a specific file. Create one service that does all this. One and only one.

The point is to avoid common errors, such as, for example, the transfer of state between objects without any structure, using variable data that anyone can overwrite, bypassing the centralized location of side effects.

If you learn to do this, you will become happier than the vast majority of other programmers.

Poorly:

 //  ,     . //        ,      name   , //   ,     let name = 'Ryan McDermott'; function splitIntoFirstAndLastName() { name = name.split(' '); } splitIntoFirstAndLastName(); console.log(name); // ['Ryan', 'McDermott']; 

Good:

 function splitIntoFirstAndLastName(name) { return name.split(' '); } const name = 'Ryan McDermott'; const newName = splitIntoFirstAndLastName(name); console.log(name); // 'Ryan McDermott'; console.log(newName); // ['Ryan', 'McDermott']; 


Avoid Side Effects (Part 2)
In JavaScript, primitives are passed by value, and objects and arrays are passed by
link. In the case of objects and arrays, if our function makes changes
to the cart (array), for example, by adding an item to the array,
then any other function that uses this basket (array) will depend on it
additions This can be both good and bad in different cases. Let's imagine a bad situation:

The user clicks on the “Purchase” button, which calls the `purchase` function, which sends data from the recycle bin (array) to the server. In case of a bad connection to the network, the purchase` function must send a second request. Now, what if at the same time the user accidentally presses the “Add to cart” button, but does not want to buy the product yet?
If this happens, and the network request begins, then the `purchase` function
will send a randomly added item, since it has a link to the previous cart (array), modified by the `addItemToCart` function. A great solution would be for `addItemToCart` to always clone the cart, edit and return the clone. This ensures that no other functions that depend on the cart will be affected by any changes.

Two warnings about this approach:
  1. There are cases when you actually want to change an object by reference, but such cases are extremely rare. Most functions can be declared without side effects!
  2. Cloning large objects can be very stressful and affect performance. Fortunately, this is not a big problem in practice, because there are excellent libraries that allow you to clone objects with a smaller memory load, unlike manual cloning.


Poorly:

 const addItemToCart = (cart, item) => { cart.push({ item, date: Date.now() }); }; 

Good:

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


Do not override global functions.


Contamination of global variables is a bad practice in JavaScript, as it can cause conflicts with another library, and the user of your API will not see errors until it gets an exception in production. Let's look at an example: what to do if you want to extend the standard Array functionality from JavaScript by adding the diff method that calculates the difference between two arrays? You would have to write a new function to Array.prototype, but then it may come into conflict with another library that tried to do the same. And if another library used the diff method to find the difference between the first and last elements of the array? That is why it is much better to use the ES2015 / ES6 classes and simply inherit our implementation from the Array class.

Poorly:

 Array.prototype.diff = function diff(comparisonArray) { const hash = new Set(comparisonArray); return this.filter(elem => !hash.has(elem)); }; 

Good:

 class SuperArray extends Array { diff(comparisonArray) { const hash = new Set(comparisonArray); return this.filter(elem => !hash.has(elem)); } } 

Prefer functional programming over imperative


JavaScript is not as functional as Haskell, but it is not without a certain amount of functionality. Functional languages ​​are cleaner and easier to test. Use a functional programming style when possible.

Poorly:

 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; } 

Good:

 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 conditions


Poorly:

 if (fsm.state === 'fetching' && isEmpty(listNode)) { // ... } 

Good:

 function shouldShowSpinner(fsm, listNode) { return fsm.state === 'fetching' && isEmpty(listNode); } if (shouldShowSpinner(fsmInstance, listNodeInstance)) { // ... } 

Avoid negative conditions


Poorly:

 function isDOMNodeNotPresent(node) { // ... } if (!isDOMNodeNotPresent(node)) { // ... } 

Good:

 function isDOMNodePresent(node) { // ... } if (isDOMNodePresent(node)) { // ... } 

Avoid conditional constructions.


Such a task seems impossible. Hearing this, most people say, "How should I do something without an if expression?" The answer is that in many cases polymorphism can be used to achieve the same goals. The second question, as a rule, is: “Well, wonderful, but why should I avoid them?”. The answer is the previous concept of clean code that we learned: a function should perform only one task. If you have classes and functions that contain an 'if' construct, you seem to be telling your user that your function performs more than one task. Remember: one function - one task.

Poorly:

 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(); } } } 

Good:

 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 an untyped language, which means that your functions can take arguments of any type. Sometimes you were burned by this freedom, which prompted you to perform type checking on your functions. There are many ways to avoid it. The first thing to think about is a consistent API.

Poorly:

 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')); } } 

Good:

 function travelToTexas(vehicle) { vehicle.move(this.currentLocation, new Location('texas')); } 

Avoid type checking (part 2)


If you are working with basic primitives, such as strings, integers, and arrays, and cannot use polymorphism, although you still feel the need for type checks, you should consider using TypeScript. This is a great alternative to regular JavaScript, providing the ability to static typing over standard JavaScript syntax. The problem with manual type checking in regular JavaScript is that the security illusion it creates is not compensated for by the loss of readability due to code verbosity. Keep your code clean, write good tests, and do effective code revisions. Or do the same thing, but with TypeScript (which, as I said, is a great alternative!).

Poorly:

 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'); } 

Good:

 function combine(val1, val2) { return val1 + val2; } 

Do not optimize beyond measure


Modern browsers make many optimizations under the hood during code execution. By optimizing the code manually, you often just waste your time. There are excellent resources describing situations where optimization is really lame. Look at them in your free time until these problems are corrected, if at all, of course.

Poorly:

 //        `list.length`   // -  `list.length`.     . for (let i = 0, len = list.length; i < len; i++) { // ... } 

Good:

 for (let i = 0; i < list.length; i++) { // ... } 

Delete dead code


Dead code is as bad as duplicate code. There is no reason to keep it in the repository. If the code is not called, get rid of it!

It will still be in the version control system, if ever you need it.

Poorly:

 function oldRequestModule(url) { // ... } function newRequestModule(url) { // ... } const req = newRequestModule; inventoryTracker('apples', req, 'www.inventory-awesome.io'); 

Good:

 function newRequestModule(url) { // ... } const req = newRequestModule; inventoryTracker('apples', req, 'www.inventory-awesome.io'); 

Source: https://habr.com/ru/post/319480/


All Articles