Повышение удобочитаемости и ремонтопригодности кода по трем аспектам

спецификация кода
Повышение удобочитаемости и ремонтопригодности кода по трем аспектам

история

Ведущий 💬: «Оказывается, есть изменение требований к проекту, и вам нужно его изменить. Изменений не много. Это должно быть очень скоро».

Ersheng💬: «Хорошо👌, я сначала посмотрю».

Эршэн, у которого было неспокойно на душе, открыл старый проект. Он не знал, не читал ли он его. Когда он увидел его, то был в шоке. Код проекта был, вероятно, таким:

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

Повторяющийся код аккуратен и красив
Нин пишет много раз и никогда не инкапсулирует
Глобальные переменные летают
Если вы хотите использовать его, используйте его с неба

Имена переменных все в порядке
a1 , a2 , a3 , a4
просто используйтеfor+ifутверждение
Реализовать все без страха

Умереть здесь и умереть там
Мультиплексирование — это прямое копирование
Берегите чернила, как золото, и не пишите заметок
Я не понимаю, это чужое дело

Перед лицом этих беспорядочных кодов Эршэн расплакалась😭.

Во взрослом мире нет простого слова, Эршэн вытер слезы и начал осторожно менять код.

наконец90%время, потраченное на чтение кода,10%Время, потраченное на исправления, похоже на завершение проекта домино, и чувство выполненного долга возникает спонтанно.

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

Когда руководитель видит отчет о работе, его внутренняя активность: «Небольшая функция так долго менялась?новичокЭто было написано, но было сказано, что это было нелегко прочитать и понять.Похоже, что способности этого сотрудника не хороши. "

предисловие

  • Цель статьи: Помочь каждому писать код с высокой читабельностью и ремонтопригодностью 😁
  • Подходит для людей: младшего персонала и друзей, которые хотят, чтобы их товарищи по команде хорошо писали код
  • Время чтения: зависит от человека, всего4000+Words, если не можете дочитать, нажмите на подборку 👍⭐

3 аспекта

Подготовить данные

// 鼠笼🐀🐀🐀
const mouseList = [
    { id: 'm01', name: '小白鼠', type: '0' },
    { id: 'm02', name: '小黑鼠', type: '4' },
    { id: 'm03', name: '小红鼠', type: '5' },
    { id: 'm04', name: '小橙鼠', type: '3' },
    { id: 'm05', name: '小黄鼠', type: '1' },
    { id: 'm06', name: '小青鼠', type: '1' },
    { id: 'm07', name: '小蓝鼠', type: '2' },
    { id: 'm08', name: '小绿鼠', type: '2', cap:{} },
    { id: 'm09', name: '小紫鼠', type: '5' },
]
// 类型对照表
const typeMap = {
    '0': '家鼠',
    '1': '田鼠',
    '2': '竹鼠',
    '3': '松鼠',
    '4': '米老鼠',
    '5': '快乐番薯'
}

Аспект 1: показать намерение

Четко скажите читателю, что вы делаете

Ниже приведен пример

📄Спрос

Ты иди 👉скажи мне вежливо小绿鼠, его жена изменяла.

✍ Реализация

var flag = false
for (var i = 0; i < mouseList.length; i++) {
    if (mouseList[i].name == '小绿鼠') {
        mouseList[i].cap.color = 'green'
        flag = true
        break
    }
}
if (flag) {
    console.log('我已经告诉他了')
} else {
    console.warn('没有找到小绿鼠')
}

🔍Анализ

Теперь давайте проанализируем, что делает приведенный выше код:

  1. определить переменнуюflag, значение по умолчаниюfalse
  2. правильноmouseListтраверс
  3. Если элемент массиваnameда小绿鼠
  4. данный этому элементу массиваcapатрибутcolorуступка имущества
  5. поместите переменнуюflagпереназначатьtrue
  6. завершить цикл
  7. пройти черезflagОпределить, найдено ли оно, дать подсказку

Как видите, нам нужно прочитать весь фрагмент кода, чтобы понять, что он делает, потому что первое, что мы видим, этоfor+if, мы знаем только отсюда, что遍历+判断, без более четкого намерения.

🛠Оптимизировано

Вот 3 способа сделать цель вашего кода более явной:

  1. пишите комментарии напрямую

Добавьте комментарии к двум небольшим фрагментам кода соответственно:

  • Найдите одну из мышей по имени小绿鼠мышь, помоги ему покрасить шляпу в зеленый цвет
  • Напоминание, когда все сделано

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

  1. имя внутри сообщения

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

Исходный код написан какflag

var flag = false

Теперь мы меняем его на это

let isFound = false

У такой записи есть 3 преимущества:

  1. использоватьES6изletвместоconst, указывая на то, что в будущем я будупереназначать,а такжеvarпросто простое заявление
  2. isНачало переменной равноBooleanТип, если группа разных переменных смешивается вместе в следующем, ее можно распознать с первого взгляда.
  3. isFoundозначает是否找到,этоfoundКак только она выходит, читатели сразу узнают авторанамерение найти что-либо

found это причастие прошедшего времени от find

Итак, просто посмотрите наlet isFound = false, не глядя на приведенный ниже код, мы можем сделать вывод, что автор ищет цель,isFoundОн используется как индикатор того, найдена ли цель.Если цель найдена, должно бытьisFound = trueКод появляется

  1. использовать функцию

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

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

  • Область применения (где найти)
  • Объективное описание (что искать)
  • Количество (найдите несколько)
  • результат (не найдено)

Используйте это, чтобы инкапсулировать функцию

/**
 * 数组里面找元素
 * @param {array} array 范围
 * @param {function} callback 目标描述
 * @param {number} count 数量
 * @return {array} 结果
 */
function arrayFindItem(array, callback, count) {
    const result = []
    let _count = 0
    for (let i = 0; i < array.length; i++) {
        if (callback(array[i])) {
            _count++
            result.push(array[i])
            if (_count === count) {
                return result
            }
        }
    }
    return result
}

затем используйте его

const result = arrayFindItem(mouseList, function(mouse) {
    return mouse.name === '小绿鼠'
}, 1)

Таким образом, остальная информация в коде понятна:

  • поведение:arrayFindItemнайти элемент в массиве
  • Где найти:mouseList
  • Что искать:function(mouse) { return mouse.name === '小绿鼠' }
  • Найдите несколько: 1
  • Вы нашли:result

Хотя это гораздо яснее, предпосылка состоит в том, чтоarrayFindItemПараметры и возвращаемые значения хорошо понятны, и в этом случае есть лучшее решение:Array.prototype.find

const result = mouseList.find(function(mouse) {
    return mouse.name === '小绿鼠'
})

Так как этоes6Метод массива в спецификации, так что все уже очень хорошо знают его поведение, и дополнительных затрат на чтение нет.


Аспект 2: Разделение кода

Все нужно делать по частям, а код писать по частям.

📄Спрос

  1. Возьмите сырую крысу в клетку и приготовьте ее следующим образом:
    • Бамбуковая крыса -> жареная
    • домовые мыши и полевки -> вареные
    • Сладкий картофель -> Гриль на углях🔥
  2. Приготовь на ужин, сегодня вечером хочу съесть 🍽

✍ Реализация

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

var dinnerList = []
for (var i = 0; i < mouseList.length; i++) {
    if (mouseList[i].isRaw != true) {
        if (mouseList[i].type === '2') {
            // 宽油竹鼠
            mouseList[i].recipe = '油炸配方'
            mouseList[i].newName = '油炸' + mouseList[i].name
            // ...被省略的油炸的其他操作
            mouseList[i].isRaw = true
            dinnerList.push(mouseList[i])
        } else if (mouseList[i].type === '0' || mouseList[i].type === '1') {
            // 水煮家鼠 + 田鼠
            mouseList[i].recipe = '水煮配方'
            mouseList[i].newName = '水煮' + mouseList[i].name
            // ...被省略的水煮的其他操作
            mouseList[i].isRaw = true
            dinnerList.push(mouseList[i])
        } else if (mouseList[i].type === '5') {
            // 烤番薯
            mouseList[i].recipe = '碳烤配方'
            mouseList[i].newName = '碳烤' + mouseList[i].name
            // ...被省略的碳烤的其他操作
            mouseList[i].isRaw = true
            dinnerList.push(mouseList[i])
        }
    }
}
console.log(dinnerList)

🔍Анализ

  1. определить массивdinnerList
  2. траверсmouseList
  3. найти все свойстваisRawдаtrueэлемент массива
  4. На основании 3, по признакуtypeразные, выполнять разные операции
    • typeда2==> жарка
    • typeда0или1==> Операция кипячения
    • typeда5==> Угольный гриль

Очевидно, процесс приготовления пишите прямо вforЦикл сделает цикл слишком длинным, что не способствует чтению, поэтому его следует разделить.

🛠Оптимизировано

Итак, давайте сделаем первый шаг и разделим способ приготовления

/* ****** 这里是烹饪的方法们 ****** */

function fry(mouse) {
    mouse.recipe = '油炸配方'
    mouse.newName = '油炸' + mouse.name
    // ...被省略的油炸的其他操作
    mouse.isRaw = true
}
function boil(mouse) {
    mouse.recipe = '水煮配方'
    mouse.newName = '水煮' + mouse.name
    // ...被省略的水煮的其他操作
    mouse.isRaw = true
}
function roast(mouse) {
    mouse.recipe = '碳烤配方'
    mouse.newName = '碳烤' + mouse.name
    // ...被省略的碳烤的其他操作
    mouse.isRaw = true
}

Итак, у нас есть 3 метода приготовления, соедините их, чтобы аннотировать, это понятно и легко поддерживать.

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

Не закончено, затем завершите код

var dinnerList = []
for (var i = 0; i < mouseList.length; i++) {
    if (mouseList[i].isRaw != true) {
        if (mouseList[i].type === '2') {
            fry(mouseList[i]) // 宽油竹鼠
            dinnerList.push(mouseList[i])
        } else if (mouseList[i].type === '0' || mouseList[i].type === '1') {
            boil(mouseList[i]) // 水煮家鼠 + 田鼠
            dinnerList.push(mouseList[i])
        } else if (mouseList[i].type === '5') {
            roast(mouseList[i]) // 烤番薯
            dinnerList.push(mouseList[i])
        }
    }
}
console.log(dinnerList)

Этот цикл появляетсяif... else if... else if..., а объектом суждения являетсяtype, что доказывает, что есть логика, которую можно разделить.

Ознакомьтесь с нашими первоначальными требованиями

  • Бамбуковая крыса -> Широкая масляная бамбуковая крыса
  • домовые мыши и полевки -> вареные
  • Сладкий картофель -> жареный на углях

Пусть логика куска кода близка к требованиям, тогда либочитаемость кодаещеРеагировать на меняющиеся требованияспособность поднимется до уровня.

Следующим шагом является преобразование требований в код: Левая часть заменена на тип, правая часть заменена на функцию, предварительный вариант:

условие действовать
typeда2 операция жарки
typeда0или1 Операция кипячения
typeда5 Работа на угольном гриле

Затем замените его кодовой записью:

type cookFn
2 fry()
0 || 1 boil()
5 roast()

В этот момент вы обнаружите, что эта корреспонденцияkey => value,keyэто мышьtype, valueэто способ приготовленияcookFn, поэтому мы должны использовать объекты для хранения корреспонденции

// 老鼠烹饪方法映射表
const mouseCookFnMap = {
    // type: cookFn 
    '0': boil,
    '1': boil,
    '2': fry,
    '5': roast
}

Идеально! Если у вас обсессивно-компульсивное расстройство, вы также можете заполнить все типы, как это

// 老鼠烹饪方法映射表(强迫症版)
const mouseCookFnMapIllVer = {
    // type: cookFn 
    '0': boil,
    '1': boil,
    '2': fry,
    '3': undefined, // 未指定烹饪方法
    '4': undefined,
    '5': roast
}

Используйте его, как только вы его напишете

var dinnerList = []
for (var i = 0; i < mouseList.length; i++) {
    if (mouseList[i].isRaw != true) {
        var cookFn = mouseCookFnMap[mouseList[i].type]
        if (cookFn) { // cookFn !== undefined
            cookFn(mouseList[i])
            dinnerList.push(mouseList[i])
        }
    }
}
console.log(dinnerList)

В это время вы получаете изменение спроса: «Бамбуковая крыса с широким маслом слишком дорога, поменяйте ее на гриль на углях для меня».

просто поставь'2': fry,изменить на'2': roast,Просто 🆗.

и, используяТаблица отношений сопоставленияНекоторые сценарии с более сложными требованиями могут быть легко обработаны.

Например宽油竹鼠, На самом деле, это не может быть сделано путем жарки.Шкура и мясо бамбуковой крысы относительно толстые, и она долго варится, поэтому для竹鼠, нужно сначала油炸Опять таки焖煮.

На данный момент таблица сопоставленияvalueЭто не просто один метод, это должно быть несколько методов, и они должны быть упорядочены, очевидно, их можно хранить в массиве:

// 加一个`焖煮方法`
function braise(mouse) { 
    // ... 省略的焖煮方法具体实现 
}

// 老鼠烹饪方法映射表加强版
const mouseCookFnMapPlus = {
    // type: cookFnArray
    '0': [boil],
    '1': [boil],
    '2': [fry, braise],
    '5': [roast]
}

При использовании метод будет вызываться напрямую

cookFn(mouseList[i])

Изменить для обхода массива и последовательного вызова

cookFnArray.forEach(cookFn => cookFn(mouseList(i)))

Я действительно не хочу писать здесьforзацикленный, б/уforEach, следующее посоветует вам не писать как можно большеforцикл


Аспект 3: Устранение избыточности

Этот аспект в основном обсуждает, как удалить избыточность в коде на грамматическом уровне.Конкретный метод заключается в том, чтобы найти части (в основном переменные) в коде, которые не имеют отношения к теме, и попытаться удалить их.

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

Стрелочные функции ➡

ES6стрелочная функцияЕсть два преимущества:

  1. изменить функциюthisнаправление

В прошлом для преобразования внутреннегоthisУкажите на внешнюю область, основным методом является

var that = this
// or
var self = this

Серьезно, см.thatУ меня большая голова 😰, определите функцию перед запуском каждой функцииthatТы не устал?

И в функции стрелкиthisОн указывает на внешний слой, полностью удаляя избыточный код выше! 💯

могу написать=>, не пишиfunction. Вместо использования стрелочных функций дляthisуказывают на внешний слой, аfunctionключевые слова используются дляthisОн будет использоваться только в том случае, если указывает на этот слой.

  1. Упрощенное написание

почувствуй это первым

mouseList.find(function(mouse) {
    return mouse.name === '小绿鼠'
})
// 箭头函数写法
mouseList.find(mouse => mouse.name === '小绿鼠')

Нужно ли писать намного меньше, со сравнением способов написания

написание function (параметр) => { автоматический возврат тело функции }
оригинальное письмо function (параметр) { тело функции }
Стрелка 1 (параметр) => { тело функции }
Стрелка 2 (параметр) => // имеют одна строка кода

Функции стрелок потрясающие! Писатели могут меньше писать, а зрители меньше читать. См. конкретное использованиеДокументация, посмотрим вниз

цикл

посмотри это первымforпетля, она длинная и широкая

for (var i = 0; i < mouseList.length; i++) {
    if (mouseList[i].name == '小绿鼠') {
        mouseList[i].cap.color = 'green'
    }
}

Очевидно, переменная здесьiбессмысленно, так как удалитьiШерстяная ткань? 🤔

Первый заключается в использованииES6изfor..of

for (const mouse of mouseList) {
    if (mouse.name == '小绿鼠') {
        mouse.cap.color = 'green'
    }
}

Но используйтеfor...of, даже если вы хотите подписатьсяiДать тоже не может.Рекомендуется, в общем-то можно пользоваться.forEachиспользуется всякий раз, когдаArray.prototype.forEach()

mouseList.forEach((mouse) => {
    if (mouse.name == '小绿鼠') {
        mouse.cap.color = 'green'
    }
})

проиллюстрировать:for...ofМожет проходить через все развертыванияiterator(итератор) данные, в то время какforEachПросто метод на прототипе массива. Но если вы решили использоватьforEach, пригодный для использованияспред оператор(...) для преобразования итерации в массив:[...iterableValue].forEach()

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

Я знаю, что есть письменный метод, но я не могу вспомнить, как его использовать?

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

Цель означает возвращаемое значение
найти предмет find элемент массива (не найденundefined)
найти индекс findIndex number(Не найдено-1)
найти несколько (фильтр) filter array
скопируй все и трансформируй map array
Некоторые? some boolean
Все они? every boolean
... ... ...

Разрушение (разрушение)

Деструктурирование присваивания переменных (деструктурирование)

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

пример

Предположим, мы хотим удалить несколько свойств мыши, не разрушая присваивание, как это

const whiteMouse = { id: 'm01', name: '小白鼠', type: '0' }
const id = whiteMouse.id
const name = whiteMouse.name
const type = whiteMouse.type

Присваивание с деструктурированием выглядит так

const whiteMouse = { id: 'm01', name: '小白鼠', type: '0' }
const { id, name, type } = whiteMouse

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

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

const greenMouse = mouseList.find(mouse => mouse.name === '小绿鼠')
if (greenMouse) {
    greenMouse.cap.color = 'green'
    greenMouse.cap.size = 'big'
    greenMouse.cap.brightness = 'high'
}

Обеспокоенные тем, что это слишком эвфемистично для маленькой зеленой мышки, мы увеличили размер шляпы 🎩 и сделали ее более ослепительной.

При встрече с таким свойством объектаиспользовать много разВ случае , лучше всего использовать переменную для его хранения, чтобы избежать многократного对象.属性.属性...Написание делает код раздутым и влияет на чтение.

const greenMouse = mouseList.find(({ name }) => name === '小绿鼠')
if (greenMouse) {
    const { cap } = greenMouse
    cap.color = 'green'
    cap.size = 'big'
    cap.brightness = 'high'
}

Его легко понять и легко использовать, но если вы будете осторожны, вы обнаружите, что есть еще одно место, где также используется деконструкция, а именно.find()Часть параметров функции обратного вызова.

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

Объясните эту деконструкцию

(mouse) => mouse.name === '小绿鼠'

.find()входящая функция обратного вызова(mouse) => mouse.name === '小绿鼠', его первый параметрmouse,ДаmouseListэлементы в , т.е.

   { id: 'm01', name: '小白鼠', type: '0' } // 第一次回调运行时`mouse`的值
   { id: 'm02', name: '小黑鼠', type: '4' } // 第二次回调运行时`mouse`的值
   // ...

теперь, когдаmouseэто объект, нам просто нужно егоnameсвойства, то({ name })просто поставь

const mouse = { id: 'm01', name: '小白鼠', type: '0' }

изменился на

const { name } = { id: 'm01', name: '小白鼠', type: '0' }

Деструктурирование действительно распространено, когда интерфейс запроса возвращается, он часто пишется так

async function getData() {
    const { code, msg, data } = await requestFn()
    // ...
}

Так вот такое предложение, после запроса интерфейса поставить回调函数а также.then()Убери это, посмотри на этоasync + await, разве это не ясно, разве это не читабельно?

наконец

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