JavaScript – чистый код

JavaScript – чистый код

Front-end 15.01.2018

Написание чистого кода – это то, что делают на автомате профессиональные разработчики.

В этой статье мы обсудим основные понятия принципа чистоты кода, как называть и использовать переменные и функции, а также рассмотрим некоторые конкретные примеры применения “чистого кода” в javascript.

Что такое Чистый код и с чем его едят?

Понятие Чистый код значит, что вы пишете код для повторного использования собой и коллегами, а уже потом для машин. Ваш код должен быть легок в понимании.

Если каждая рутина, которую Вы читаете кажется Вам более менее предсказуема – вы работаете с хорошим кодом.

JavaScript. Чистый код. Best Practices

Сейчас, когда мы имеем общее понятие, что от нас требуется, перейдем к более конкретным моментам.

Как называть переменные?

Имя переменной должно указывать на то, зачем она нужна. Не переживайте если Ваши названия будут не совсем короткими.

Если вы будете этому следовать – Ваши переменные можно будет отыскать в коде, что очень поможет в рефакторинге или когда вы просто будете сами что-то искать.

// DON'T
let d
let elapsed
const ages = arr.map((i) => i.age)

// DO
let daysSinceModification
const agesOfUsers = users.map((user) => user.age)

Кроме того не перегружайте свои переменные.

// DON'T
let nameString
let theUsers

// DO
let name
let users

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

// DON'T
let fName, lName
let cntr

let full = false
if (cart.size > 100) {
  full = true
}

// DO
let firstName, lastName
let counter

const MAX_CART_SIZE = 100
// ...
const isFull = cart.size > MAX_CART_SIZE

Как писать функции?

Ваши функции должны делать только 1 вещь только на одном уровне абстракции, но делать её хорошо!

// DON'T
function getUserRouteHandler (req, res) {
  const { userId } = req.params
  // inline SQL query
  knex('user')
    .where({ id: userId })
    .first()
    .then((user) => res.json(user))
}

// DO
// User model (eg. models/user.js)
const tableName = 'user'
const User = {
  getOne (userId) {
    return knex(tableName)
      .where({ id: userId })
      .first()
  }
}

// route handler (eg. server/routes/user/get.js)
function getUserRouteHandler (req, res) {
  const { userId } = req.params
  User.getOne(userId)
    .then((user) => res.json(user))
}
  • Используйте более длинные, но понятные названия.

Имя функции должно описывать ее назначение. Название существительной лучше делать глаголом или фразой.

// DON'T
/**
 * Invite a new user with its email address
 * @param {String} user email address
 */
function inv (user) { /* implementation */ }

// DO
function inviteUser (emailAddress) { /* implementation */ }
  • Избегайте длинного списка аргументов.

Лучше использовать обьект, чем слишком длинный список аргументов.

// DON'T
function getRegisteredUsers (fields, include, fromDate, toDate) { /* implementation */ }
getRegisteredUsers(['firstName', 'lastName', 'email'], ['invitedUsers'], '2016-09-26', '2016-12-13')

// DO
function getRegisteredUsers ({ fields, include, fromDate, toDate }) { /* implementation */ }
getRegisteredUsers({
  fields: ['firstName', 'lastName', 'email'],
  include: ['invitedUsers'],
  fromDate: '2016-09-26',
  toDate: '2016-12-13'
})
  • Уменьшайте побочные эффекты

Пишите чистые функции без побочных эффектов, пока это возможно. Они реально упрощают использование и тестирование.

// DON'T
function addItemToCart (cart, item, quantity = 1) {
  const alreadyInCart = cart.get(item.id) || 0
  cart.set(item.id, alreadyInCart + quantity)
  return cart
}

// DO
// not modifying the original cart
function addItemToCart (cart, item, quantity = 1) {
  const cartCopy = new Map(cart)
  const alreadyInCart = cartCopy.get(item.id) || 0
  cartCopy.set(item.id, alreadyInCart + quantity)
  return cartCopy
}

// or by invert the method location
// you can expect that the original object will be mutated
// addItemToCart(cart, item, quantity) -> cart.addItem(item, quantity)
const cart = new Map()
Object.assign(cart, {
  addItem (item, quantity = 1) {
    const alreadyInCart = this.get(item.id) || 0
    this.set(item.id, alreadyInCart + quantity)
    return this
  }
})
  • Организовывайте свои функции в файле в соответствии с правилам перехода.

Функция самого высокого уровня должна быть сверху. Это значительно облегчает прочтение кода.

// DON'T
// "I need the full name for something..."
function getFullName (user) {
  return `${user.firstName} ${user.lastName}`
}

function renderEmailTemplate (user) {
  // "oh, here"
  const fullName = getFullName(user)
  return `Dear ${fullName}, ...`
}

// DO
function renderEmailTemplate (user) {
  // "I need the full name of the user"
  const fullName = getFullName(user)
  return `Dear ${fullName}, ...`
}

// "I use this for the email template rendering"
function getFullName (user) {
  return `${user.firstName} ${user.lastName}`
}
  • Запрос или изменения.

Функции должны иметь простую возможность модификовываться или отвечать на запросы, но не одновременно.

Все любят писать JavaScript по-разному, что делать?

Поскольку JavaScript является динамическим и слабо типизированным, он особенно подвержен ошибкам программиста.

Используйте правила проекта или компании по написанию и форматированию кода.

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

Как писать красивый асинхронный код?

Вместо написания вложенных callbacks, вы можете использовать Promises доступные в node js.

// AVOID
asyncFunc1((err, result1) => {
  asyncFunc2(result1, (err, result2) => {
    asyncFunc3(result2, (err, result3) => {
      console.lor(result3)
    })
  })
})

// PREFER
asyncFuncPromise1()
  .then(asyncFuncPromise2)
  .then(asyncFuncPromise3)
  .then((result) => console.log(result))
  .catch((err) => console.error(err))

Большинство библиотек, которые имеют интерфейсы callbacks и Promises, предпочитают использовать последний. Вы даже можете конвертировать API callbacks, чтоб использовать Promises, обернув их, используя пакеты, такие как es6-promisify.

// AVOID
const fs = require('fs')

function readJSON (filePath, callback) {
  fs.readFile(filePath, (err, data) => {
    if (err) {
      return callback(err)
    }

    try {
      callback(null, JSON.parse(data))
    } catch (ex) {
      callback(ex)
    }
  })
}

readJSON('./package.json', (err, pkg) => { console.log(err, pkg) })

// PREFER
const fs = require('fs')
const promisify = require('es6-promisify')

const readFile = promisify(fs.readFile)
function readJSON (filePath) {
  return readFile(filePath)
    .then((data) => JSON.parse(data))
}

readJSON('./package.json')
  .then((pkg) => console.log(pkg))
  .catch((err) => console.error(err))

Следующим шагом будет использование async/await (≥ Node 7) или генераторов с co (≥ Node 4) чтоб добиться синхронных потоков управления для вашего асинхронного кода.

const request = require('request-promise-native')

function getExtractFromWikipedia (title) {
  return request({
    uri: 'https://en.wikipedia.org/w/api.php',
    qs: {
      titles: title,
      action: 'query',
      format: 'json',
      prop: 'extracts',
      exintro: true,
      explaintext: true
    },
    method: 'GET',
    json: true
  })
    .then((body) => Object.keys(body.query.pages).map((key) => body.query.pages[key].extract))
    .then((extracts) => extracts[0])
    .catch((err) => {
      console.error('getExtractFromWikipedia() error:', err)
      throw err
    })
} 

// PREFER
async function getExtractFromWikipedia (title) {
  let body
  try {
    body = await request({ /* same parameters as above */ })
  } catch (err) {
    console.error('getExtractFromWikipedia() error:', err)
    throw err
  }

  const extracts = Object.keys(body.query.pages).map((key) => body.query.pages[key].extract)
  return extracts[0]
}

// or
const co = require('co')

const getExtractFromWikipedia = co.wrap(function * (title) {
  let body
  try {
    body = yield request({ /* same parameters as above */ })
  } catch (err) {
    console.error('getExtractFromWikipedia() error:', err)
    throw err
  }

  const extracts = Object.keys(body.query.pages).map((key) => body.query.pages[key].extract)
  return extracts[0]
})

getExtractFromWikipedia('Robert Cecil Martin')
  .then((robert) => console.log(robert))

Источник: Blog risingstack.

Поделиться:

Отправить ответ

avatar
  Subscribe  
Notify of