JavaScript, от которого не тошнит
- воскресенье, 19 ноября 2017 г. в 03:12:14
Здравствуйте, меня зовут Сережа и я JavaScript программист. Когда я вижу плохой код, я расстраиваюсь. А я не люблю расстраиваться. И поэтому я иногда пытаюсь объяснить автору, что собственно я вижу плохого в его коде. И чтобы не повторяться, я решил написать небольшой набор истин, на который я смог бы сслылаться на то, как писать хороший код, и как не писать плохой. И дабы все желающие также могли ознакомиться с материалом я решил оформить это все в виде статьи на Хабрахабре.
Для начала, стоит определиться с тем, что такое хороший код, а что такое плохой код. К счастью, нам не придется углубляться в философские рассуждения о понятии качества. Так можно и с катушек слететь, проведя всю оставшуюся жизнь ухаживая за мотоциклом. А у нас все достаточно банально. Так как большую часть времени любой программист занимается чтением кода и его изменением, то хороший код легко читается, легко понимается и так же легко изменяется. Следовательно, плохой код медленно читается, трудно понимается и очень тяжело изменяется. Поэтому наш код должен быть простым, лаконичным и изящным. Чтобы он получался таким, нужно следовать несложным правилам.
Были времена, когда по земле ходили динозавры, а компьютеры были большими, памяти у них было мало, а автокомплита в редакторах не было. Дабы экономить эту самую память и уберечь свои пальцы от излишних нажатий клавиш, хитрые программоцерапторсы записывали переменные в сокращенном виде. Например, в слове image целых пять букв, а в сокращении img всего-навсего три. На эти два байта разницы они и жили. Времена те давно прошли, история стала легендой, легенда превратилась в миф, динозавры вымерли, но традиция сохранилась и прошла сквозь века. Сейчас никакой причины экономить на буквах не осталось, но в коде очень часто можно встретить нечто вроде img.getSrc()
. Казалось бы, невелика беда, мозг же легко расшифровывает это до полноценного image.getSource()
. Но если в коде на пять строчек десяток переменных с такими названиями, то скорость парсинга кода нашим мозгом резко падает. И это еще не говоря о том, что эти сокращения более-менее распространены, а что такое cls.setCfc(0.5)
незнакомый с кодобазой человек в жизни не догадается.
Бойлерплейт — это плохо, это все знают. И мы не будем писать бойлерплейт. Верно? Но чтобы этого не делать, надо четко определить, что же этот самый бойлерплейт такое. То, что любые повторения кода, являются бойлерплейтом, это и так понятно. Но что еще?
Работа разработчика — это переносить смыслы из неосязаемого ментального вида в строго структурированную запись на конкретном языке программирования. Поэтому все, что не несет какой-либо смысловой нагрузки и не является структурным элементом, является лишним.
Например, в JavaScript лишними являются точки с запятой. Они не несут смысла, и не имеют структурной роли. Они лишь занимают лишнее место на экране и замедляют чтение кода.
const number = 100
// чище чем
const number = 100;
Когда точек с запятыми на экране много, то они очень сильно мешают восприятию кода. Также в JavaScript в некоторых случаях лишними являются круглые и фигурные скобки.
const addOne = number => number + 1
// чище чем
const addOne = (number) => { return number + 1 }
Операторы if-else генерируют очень много грязи. Лучше не использовать else вовсе, а с if, если выполняется всего одна строка не писать скобок. Вообще идеально заменять то, что можно на тернарный оператор.
const abs = number => {
if (number >= 0) return number
return -number
}
// лучше чем
const abs = number => {
if (number >= 0) {
return number
} else {
return -number
}
}
// но в идеале
const abs = number =>
number >= 0 ? number : -number
В общем и целом, есть простое правило: располагайте как можно больше смысловой нагрузки на каждой строчке кода, но ни в коем случае не жертвуйте читабельностью и легкостью восприятия.
Когда я вижу в коде цикл for, я поперхиваюсь своим смузи. Писать императивно в 2017 году — это моветон. Императивный код по большей части — это просто бойлерплейт. Очень мало ситуаций действительно требуют его применения. В прикладных задачах таких ситуаций почти не бывает.
const numbers = [1,2,3]
// Бойлерплейт
const doubleNumbers = []
for (var i = 0; i < numbers.length; i++) {
doubleNumbers.push(2 * numbers[i])
}
// Нормальный код
const doubleNumbers = numbers.map(number => number * 2)
Хороший JavaScript код разделен на хорошо абстрагированные модули. Чтобы их связность была минимальна, стоит придерживаться нескольких простых правил.
Каждый модуль должен лежать в директории с файлом index.js
внутри
Именнованные .js файлы вроде button.js
создавать не стоит
Директорий без index.js
тоже создавать не стоит
Если нужно иметь несколько модулей в одной директории создайте index.js
файл, импортируйте в него все модули
# Плохо
component/
├── button.js
└── panel.js
# Тоже плохо
component/
├── button/
│ └── index.js
└── panel/
└── index.js
# Хорошо
component/
├── button/
│ └── index.js
├── panel/
│ └── index.js
└── index.js
Импортировать вы должны только модули, находящиеся на одном уровне с тем, в который происходит импорт. Импортировать что-либо из детей или родителей не стоит.
// Плохо
import {Button} from '../../component'
// Тоже плохо
import {Button} from './component/button'
// Хорошо
import {Button} from './component'
Чтобы сделать модуль, который можно импортировать откуда угодно, положите его в source/shared
и добавьте алиас. Так вы сможете импортировать этот модуль как обычную npm зависимость.
source/
└── shared/
└── component
├── button/
│ └── index.js
└── index.js
import {Button} from 'component'
Таким образом создается очень простой и понятный top-down flow, который очень легко рефакторить и изменять. Если же не следовать данным правилам, то код превращается в кашу.
Ничего не засоряет код больше, чем комментарии. Если комментарии используются для обозначения типов, то для решения этой задачи гораздо лучше подойдут Flow или PropTypes. Если комментарии поясняют смысл написанного, то видимо стоит отрефакторить этот кусок кода и сделать его более понятным для читателя. JavaScript высокоуровневый язык, на нем можно решать только высокоуровневые задачи. Понятно, зачем нужно объяснять оперирование байтами на Си. Это может быть действительно неочевидно. Но если вам нужно объяснять что делает высокоуровневый код, значит проблема в самом коде.
Если есть список элементов, чей порядок не имеет никакого значения, то лучше расположить их по алфавиту. К примеру, список импортов лучше сортировать сначала по алфавиту вертикально и горизонтально.
import {ApolloClient, ApolloProvider, createNetworkInterface} from 'react-apollo'
import Application from './application'
import {BrowserRouter} from 'react-router-dom'
import {createStore} from 'react-redux-boil'
import React from 'react'
import ReactDom from 'react-dom'
Тут были перечислены не все правила, которые я использую, но даже следование этим нескольким очень сильно улучшает читаемость и изменяемость программы. Когда-нибудь в будущем, я оформлю их в большой стайлгайд с кучей примеров плохого и хорошего кода.