12 советов по написанию масштабируемого JavaScript

0
569
views

Перевод статьи «12 tips for writing clean and scalable JavaScript».

Как писать чистый и поддерживаемый JavaScript

JavaScript имеет длинную историю. Он зародился как скриптовый язык, а к настоящему времени развился в полноценный язык программирования, подходящий и для фронтенда, и для бэкенда.

Современные веб-приложения очень зависимы от JavaScript, особенно это касается одностраничных приложений (SPAs). С появлением таких фреймворков как React, AngularJS и Vue.js, веб-приложения начали создаваться по большей части с использованием JavaScript.

Масштабирование этих приложений (и фронтенда, и бэкенда), является довольно сложной задачей. С обычными настройками вы быстро упретесь в ограничения и потеряетесь в море путаницы. В этой статье я хочу поделиться с вами несколькими советами, следуя которым вы сможете эффективно писать чистый код.

Эта статья ориентирована на JavaScript-разработчиков с любым уровнем навыков. Но наибольшую пользу из нее извлекут те, кто уже знает этот язык на среднем уровне.

1. Изолируйте свой код

Самое главное, что я могу посоветовать для поддержки кодовой базы в чистом и читаемом виде, это разделение отдельных кусков логики (обычно функций) по темам. Если вы пишете функцию, она по определению должна иметь только одно назначение и не делать нескольких вещей одновременно.

Также следует избегать действий, способных вызывать побочные эффекты. Т. е., в большинстве случаев не следует изменять что-то, объявленное вне вашей функции. Данные в функцию вы передаете с помощью параметров, а все остальное должно быть недоступно. Если вы хотите убрать что-то из функции, return новые значения.

2. Модуляризация

Конечно, если ваши функции (и/или классы) используются сходным образом и делают похожие вещи, вы можете их объединить в один модуль. Например, если вам нужно производить много различных вычислений, разделите их на изолированные шаги (функции), которые вы сможете связать. Несмотря на изолированность, все эти функции могут быть объявлены в одном файле (модуле). Вот пример на JavaScript:

function add(a, b) {
    return a + b   
}

function subtract(a, b) {
    return a - b   
}

module.exports = {
    add,
    subtract
}
const { add, subtract } = require('./calculations')

console.log(subtract(5, add(3, 2))

Если вы пишете фронтенд приложения на JavaScript, определенно стоит использовать экспорт по умолчанию для наиболее важных элементов и именованный экспорт для вторичных элементов.

3. Отдавайте предпочтение нескольким параметрам, а не объекту параметров

При объявлении функции всегда следует предпочитать несколько параметров, а не объект параметров.

// GOOD
function displayUser(firstName, lastName, age) {
    console.log(`This is ${firstName} ${lastName}. She is ${age} years old.`)
}

// BAD
function displayUser(user) {
    console.log(`This is ${user.firstName} ${user.lastName}. She is ${user.age} years old.`)
}

Причина этого в том, что просто взглянув на первую строчку объявления функции, вы всегда будете знать, что именно вы передаете в эту функцию.

Даже несмотря на то, что функции должны быть ограничены в размерах – и выполнять лишь одно действие – может случиться, что они разрастутся. Просмотр тела функции в поисках переменных, которые вам нужно передать (и которые вложены внутри объекта), будет отнимать у вас больше времени. Порой может казаться, что проще взять целый объект и передать его в функцию, но указанный мною способ определенно поможет вам, когда дело дойдет до масштабирования вашего приложения.

Однако, этот подход хорош до определенного предела, после которого объявление отдельных параметров не имеет смысла. Для меня предел это ситуация, когда нужно передавать больше 4-5 параметров. Если ваша функция настолько разрослась, стоит склониться к использованию объекта параметров.

Основная причина для этого в том, что параметры должны передаваться в определенном порядке. Если у вас есть опциональные параметры, вам необходимо передавать undefined или null. А если вы используете объект параметров, вы можете просто передать весь объект, где порядок и undefined-значения не играют роли.

4. Деструктуризация

Деструктуризация это отличный инструмент, который был представлен в ES6. Она позволяет вам взять отдельные поля из объекта и немедленно присвоить их переменной. Это можно использовать с любыми типами объектов или модулей.

// EXAMPLE FOR MODULES
const { add, subtract } = require('./calculations')

Смысл в том, что вы можете импортировать только функции, которые вам нужно использовать в вашем файле, вместо того чтобы использовать весь модуль. Если вы решите, что вам определенно нужен объект в качестве параметра функции, вы также можете использовать деструктуризацию. Это по-прежнему даст вам представление о том, что необходимо внутри функции:

function logCountry({name, code, language, currency, population, continent}) {
    let msg = `The official language of ${name} `
    if(code) msg += `(${code}) `
    msg += `is ${language}. ${population} inhabitants pay in ${currency}.`
    if(contintent) msg += ` The country is located in ${continent}`
}

logCountry({
    name: 'Germany',
    code: 'DE',
    language 'german',
    currency: 'Euro',
    population: '82 Million',
})


logCountry({
    name: 'China',
    language 'mandarin',
    currency: 'Renminbi',
    population: '1.4 Billion',
    continent: 'Asia',
})
view raw

Как видите, я по-прежнему знаю, что мне нужно передать в функцию, даже несмотря на то, что это завернуто в объект.

(Кстати, это также работает с функциональными компонентами React).

5. Используйте значения по умолчанию

Дефолтные значения очень полезны для деструктуризированных и даже для базовых функций. Во-первых, они дают вам пример того, какое значение вы можете передавать в функцию. Во-вторых, вы можете обозначить, какие значения являются обязательными, а какие нет. Если использовать предыдущий пример, полная настройка функции может выглядеть так:

function logCountry({
    name = 'United States', 
    code, 
    language = 'English', 
    currency = 'USD', 
    population = '327 Million', 
    continent,
}) {
    let msg = `The official language of ${name} `
    if(code) msg += `(${code}) `
    msg += `is ${language}. ${population} inhabitants pay in ${currency}.`
    if(contintent) msg += ` The country is located in ${continent}`
}

logCountry({
    name: 'Germany',
    code: 'DE',
    language 'german',
    currency: 'Euro',
    population: '82 Million',
})


logCountry({
    name: 'China',
    language 'mandarin',
    currency: 'Renminbi',
    population: '1.4 Billion',
    continent: 'Asia',
})

Разумеется, иногда вы можете не хотеть использовать значения по умолчанию, а вместо этого выдавать ошибку в случае отсутствия переданного значения. Но чаще это удобный прием.

6. Недостаток данных

Предыдущий совет подводит нас к одному умозаключению: не следует просто пропускать данные, которые вам не нужны. Опять же, это может потребовать больше работы для настройки ваших функций. Но в долгосрочной перспективе вы получите определенно более читаемую кодовую базу. Знать совершенно точно, какие значения используются в определенном отрывке кода, – бесценно.

7. Ограничение количества строк и отступов

Мне доводилось видеть большие, очень большие файлы. Больше 3 тысяч строк кода. В подобных файлах невероятно трудно найти нужный кусок логики.

Поэтому стоит ограничивать размеры своих файлов до определенного количества строк. Я стараюсь делать так, чтобы мои файлы не превышали лимит в 100 строк кода. Иногда их сложно разбивать, и тогда в файле может оказаться и 200-300 строк, а в редких случаях и до 400.

Но, преодолев этот предел, файлы слишком загромождаются, их становится тяжело поддерживать. Не стесняйтесь создавать новые модули и папки. Ваш проект должен выглядеть, как лес, в котором есть стволы (разделы модулей) и ветви (группы модулей и файлы с модулями). Старайтесь не создавать «горы», накапливая код в отдельных местах.

Ваши файлы должны скорее напоминать долину: с холмами (небольшими уровнями отступов), но по большей части относительно ровную. Старайтесь, чтобы уровень отступов был меньше четырех.

Для поддержки такого стиля может быть полезным использование ESLint-правил!

8. Используйте prettier

Работа в команде требует четких правил относительно стиля и форматирования кода. ESLint предлагает огромный набор правил, который вы можете подогнать под свои нужды. Есть также eslint —fix, который может исправлять некоторые ошибки, но не все.

Я же рекомендую для форматирования кода использовать Prettier. Благодаря этому инструменту разработчик может вообще не беспокоиться о форматировании и думать только о написании высококачественного кода. При этом его код будет выглядеть последовательно и форматироваться автоматически.

Форматирование кода

9. Используйте осмысленные имена переменных

В идеале, имена переменных должны отражать их содержимое. Вот несколько правил, которые помогут вам объявлять хорошие, продуманные имена переменных.

Функции

Функции обычно осуществляют какое-то действие. Для обозначения действий люди используют глаголы, например, convert или display. Будет хорошей идеей начинать имена ваших функций с глаголов, например, convertCurrency или displayUserName.

Массивы

Массивы обычно содержат списки позиций, поэтому в конце имени массива стоит добавлять «s» (окончание множественного числа). Например:

const students = ['Eddie', 'Julia', 'Nathan', 'Theresa']

Логические выражения

В этом случае имя просто должно начинаться с is или has, чтобы быть ближе к естественному языку. Как если бы вы спрашивали: «Is that person a teacher?» («Этот человек – учитель?»), и получали в ответ «да» или «нет». Примерно так:

const isTeacher = true // OR false

Функции для работы с массивами

forEach, map, reduce, filter это отличные собственные функции JavaScript для работы с массивами и осуществления некоторых действий. Мне часто случается видеть, что многие люди передают в функции обратного вызова в качестве параметров просто el или element. И хотя это просто и быстро, их тоже следует именовать соответственно их значениям. Например:

const cities = ['Berlin', 'San Francisco', 'Tel Aviv', 'Seoul']
cities.forEach(function(city) {

})

Идентификаторы (ID)

Часто приходится отслеживать идентификаторы определенных наборов данных или объектов. Когда идентификаторы вложены, просто оставляйте их как «id». Например, я предпочитаю отображать «_id» MongoDB как просто «id» перед возвратом объекта во фронтенд. При извлечении идентификаторов из объекта предварительно добавляйте тип объекта. Например:

const studentId = student.id
// OR
const { id: studentId } = student // destructuring with renaming

Исключение из этого правила – ссылки на MongoDB в моделях. Здесь нужно просто называть поле по названию модели, на которую идет ссылка. Это позволит сохранить ясность, когда количество ссылок возрастет.

const StudentSchema = new Schema({
    teacher: {
        type: Schema.Types.ObjectId,
        ref: 'Teacher',
        required: true,
    },
    name: String,
    ...
})

10. Используйте async / await, где это возможно

Нет ничего хуже обратных вызовов, когда речь идет о читаемости кода, особенно, если они вложенные. Promises были значительным улучшением, но, по моему мнению, лучшая читаемость достигается при использовании async/await. Это также очень помогает начинающим разработчикам или людям, знающим другие языки и переходящим на JS. Однако следует непременно сначала разобраться в самой концепции, а не просто бездумно использовать повсюду async/await.

11. Порядок импорта модулей

Как мы знаем из первого и второго советов, важно держать логику в правильных местах – это имеет ключевое значение для поддерживаемости кода. Точно так же порядок импорта различных модулей может способствовать уменьшению неразберихи в ваших файлах. В том, что касается импорта, я придерживаюсь простой структуры:

// 3rd party packages
import React from 'react'
import styled from 'styled-components'

// Stores
import Store from '~/Store

// reusable components
import Button from '~/components/Button'

// utility functions
import { add, subtract } from '~/utils/calculate'

// submodules
import Intro from './Intro'
import Selector from './Selector'

В этом примере я использовал компонент React, поскольку здесь больше типов импорта.

12. Избавьтесь от console

console.log это отличный способ отладки – очень простой, быстрый и хорошо выполняющий свою работу. Очевидно, есть и более изощренные инструменты, но я думаю, что именно console.log по-прежнему пользуются все разработчики.

Однако, если вы будете забывать чистить логи, ваш console окажется в ужасном беспорядке. А ведь есть логи, которые вы хотели бы сохранить в вашей кодовой базе, например, логи предупреждений и ошибок.

Для решения этой проблемы вы можете продолжать использовать console.log в отладочных целях, но для логов, которые должны вестись постоянно, используйте библиотеку вроде loglevel или winston.

Следование этим правилам очень помогло мне поддерживать свой код в чистом и масштабируемом состоянии. Вам тоже есть что посоветовать коллегам? Поделитесь в комментариях!

ОСТАВЬТЕ ОТВЕТ

Please enter your comment!
Please enter your name here