Некоторые способы повышения надежности внешнего кода

JavaScript

Эта статья синхронизирована в личном блогеshymean.comвверх, добро пожаловать, чтобы следовать

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

Ранее скомпилированные статьи о надежности кода

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

Более безопасный доступ к объектам

Не доверяйте данным интерфейса

Не доверяйте параметрам, передаваемым внешним интерфейсом, и не доверяйте данным, возвращаемым серверной частью.

напримерapi/xxx/listинтерфейс, согласно условным обозначениям документации

{
    code: 0,
    msg: "",
    data: [
     // ... 具体数据
    ],
};

Интерфейсный код может быть записан как

const {code, msg, data} = await fetchList()
data.forEach(()=>{})

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

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

В это время используйтеdata.forEachсообщит об ошибке,

Uncaught TypeError: data.forEach is not a function

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

Array.isArray(data) && data.forEach(()=>{})

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

Нулевой оператор объединения

Из-за динамической природы JavaScript, когда мы запрашиваем свойство объекта, напримерx.y.z, лучше проверитьxиyон существует

let z = x && x.y && x.y.z

Часто очень неприятно написать это, а атрибут объекта доступа к безопасности в DART гораздо просто.

var z = a?.y?.z;

Представлено в ES2020Нулевой оператор объединенияпроект, в том числе??и?.оператор, который может выполнять ту же функцию безопасного доступа к свойствам объекта, что и dart. Теперь откройте последнюю версию Chrome для тестирования.

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

function getObjectValueByKeyStr(obj, key, defaultVal = undefined) {
    if (!key) return defaultVal;
    let namespace = key.toString().split(".");
    let value,
        i = 0,
        len = namespace.length;
    for (; i < len; i++) {
        value = obj[namespace[i]];
        if (value === undefined || value === null) return defaultVal;
        obj = value;
    }
    return value;
}
var x = { y: { z: 100,},};

var val = getObjectValueByKeyStr(x, "y.z");
// var val = getObjectValueByKeyStr(x, "zz");
console.log(val);

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

Помните об обработке исключений

Ссылаться на:

Обработка исключений является основной гарантией надежности кода.Есть два аспекта обработки исключений.

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

объект ошибки

Пользовательский объект ошибки может быть выброшен с помощью оператора throw

// Create an object type UserException
function UserException (message){
  // 包含message和name两个属性
  this.message=message;
  this.name="UserException";
}
// 覆盖默认[object Object]的toString
UserException.prototype.toString = function (){
  return this.name + ': "' + this.message + '"';
}

// 抛出自定义错误
function f(){
    try {
        throw new UserException("Value too high");
    }catch(e){
        if(e instanceof UserException){
            console.log('catch UserException')
            console.log(e)
        }else{
            console.log('unknown error')
            throw e
        }
    }finally{
        // 可以做一些退出操作,如关闭文件、关闭loading等状态重置
        console.log('done')
        return 1000 // 如果finally中return了值,那么会覆盖前面try或catch中的返回值或异常
    }
}
f()

Синхронный код

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

function a(){
    throw 'error b'
}

// 当b能够处理异常时,则不再向上抛出
function b(){
    try{
        a()
    }catch(e){
        if(e === 'error b'){
            console.log('由b处理')
        }else {
            throw e
        }
    }
}

function main(){
    try {
        b()
    }catch(e){
        console.log('顶层catch')
    }
}

асинхронный код

Так как catch не может получить исключения, генерируемые в асинхронном коде, для реализации цепочки ответственности необходимо передать обработку исключений асинхронным задачам через callback-функции

function a(errorHandler) {
    let error = new Error("error a");
    if (errorHandler) {
        errorHandler(error);
    } else {
        throw error;
    }
}

function b(errorHandler) {
    let handler = e => {
        if (e === "error b") {
            console.log("由b处理");
        } else {
            errorHandler(e);
        }
    };

    setTimeout(() => {
        a(handler);
    });
}

let globalHandler = e => {
    console.log(e);
};
b(globalHandler);

Обработка исключений промисов

Обещания содержат только три состояния:pending,rejectedиfulfilled

let promise2 = promise1.then(onFulfilled, onRejected)

Ниже приведены несколько правил для обещаний генерировать исключения.

function case1(){
    // 如果promise1是rejected态的,但是onRejected返回了一个值(包括undifined),那么promise2还是fulfilled态的,这个过程相当于catch到异常,并将它处理掉,所以不需要向上抛出。
    var p1 = new Promise((resolve, reject)=>{
        throw 'p1 error'
    })

    p1.then((res)=>{
        return 1
    }, (e)=>{
        console.log(e)
        return 2
    }).then((a)=>{
        // 如果注册了onReject,则不会影响后面Promise执行
        console.log(a) // 收到的是2
    })
}
function case2(){
    //  在promise1的onRejected中处理了p1的异常,但是又抛出了一个新异常,,那么promise2的onRejected会抛出这个异常
    var p1 = new Promise((resolve, reject)=>{
        throw 'p1 error'
    })
    p1.then((res)=>{
        return 1
    }, (e)=>{
        console.log(e)
        throw 'error in p1 onReject'
    }).then((a)=>{}, (e)=>{
        // 如果p1的 onReject 抛出了异常
        console.log(e)
    })
}

function case3(){
    // 如果promise1是rejected态的,并且没有定义onRejected,则promise2也会是rejected态的。
    var p1 = new Promise((resolve, reject)=>{
        throw 'p1 error'
    })

    p1.then((res)=>{
        return 1
    }).then((a)=>{
        console.log('not run:', a)
    }, (e)=>{
        // 如果p1的 onReject 抛出了异常
        console.log('handle p2:', e)
    })
}
function case4(){
    // // 如果promise1是fulfilled态但是onFulfilled和onRejected出现了异常,promise2也会是rejected态的,并且会获得promise1的被拒绝原因或异常。
    var p1 = new Promise((resolve, reject)=>{
        resolve(1)
    })
    p1.then((res)=>{
        console.log(res)
        throw 'p1 onFull error'
    }).then(()=>{}, (e)=>{
        console.log('handle p2:', e)
        return 123
    })
}

Поэтому мы можемonRejectedОбработать ошибку текущего промиса в , если нет, скинуть на следующийpromise

async

async/awaitПо сути, синтаксический сахар для промисов, поэтому его также можно использоватьpromise.catchАналогичный механизм захвата

function sleep(cb, cb2 =()=>{},ms = 100) {
    cb2()
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            try {
                cb();
                resolve();
            }catch(e){
                reject(e)
            }
        }, ms);
    });
}
// 通过promise.catch来捕获
async function case1() {
    await sleep(() => {
        throw "sleep reject error";
    }).catch(e => {
        console.log(e);
    });
}
// 通过try...catch捕获
async function case2() {
    try {
        await sleep(() => {
            throw "sleep reject error";
        })
    } catch (e) {
        console.log("catch:", e);
    }
}
// 如果是未被reject抛出的错误,则无法被捕获
async function case3() {
    try {
        await sleep(()=>{}, () => {
            // 抛出一个未被promise reject的错误
            throw 'no reject error'
        }).catch((e)=>{
            console.log('cannot catch:', e)
        })
    } catch (e) {
        console.log("catch:", e);
    }
}

Более стабильные сторонние модули

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

Это также некоторые очень маленькие модули, которые часто появляются в npm, такие как этот пакет, который определяет, является ли это нечетным числом:isOdd, еженедельный объем загрузок на самом деле составляет 600 000.

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

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

export function getOSType() {
  const ua = navigator.userAgent

  const isWindowsPhone = /(?:Windows Phone)/.test(ua)
  const isSymbian = /(?:SymbianOS)/.test(ua) || isWindowsPhone
  const isAndroid = /(?:Android)/.test(ua)

  // 判断是否是平板
  const isTablet =
    /(?:iPad|PlayBook)/.test(ua) ||
    (isAndroid && !/(?:Mobile)/.test(ua)) ||
    (/(?:Firefox)/.test(ua) && /(?:Tablet)/.test(ua))

  // 是否是iphone
  const isIPhone = /(?:iPhone)/.test(ua) && !isTablet

  // 是否是pc
  const isPc = !isIPhone && !isAndroid && !isSymbian && !isTablet
  return {
    isIPhone,
    isAndroid,
    isSymbian,
    isTablet,
    isPc
  }
}

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

"Mozilla/5.0 (Linux; U; Android 8.1.0; zh-CN; MI PAD 4 Build/OPM1.171019.019) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/57.0.2987.108 Quark/3.8.5.129 Mobile Safari/537.36

даже поставитьMI PADДобавьте это в обычное решение, чтобы временно исправить это.Что, если позже будут специальные UA других устройств? Поэтому сложно учесть все проблемы в письменной форме на основе собственного опыта, а потом заменить наmobile-detectэта библиотека.

Недостатком использования модулей является то, что

  • Это может увеличить объем зависимости файла, увеличить время упаковки и т. д. Эту проблему можно решить путем настройки упаковки, упаковки сторонних модулей, которые не часто меняются, в файл поставщика для настройки кеша.
  • В некоторых проектах может быть необходимо сократить использование сторонних модулей из соображений безопасности или потребовать сначала просмотреть исходный код.

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

локальный файл конфигурации

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

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

Распространенной практикой является следующее: создайте новый локальный файл конфигурацииconfig.local.js, а затем экспортируйте соответствующую информацию о конфигурации

// config.local.js
module.exports = {
  needMock: true
}

Не забудьте игнорировать файл в .gitignore

config.local.js

затем пройтиtry...catch...Загрузите этот модуль, поскольку файл не входит в базу кода, он войдет в процесс перехвата при извлечении обновления кода из другого места, а локальная разработка войдет в обычный процесс импорта модуля.

// mock/entry.js
try {
  const { needMock } = require('./config.local')
  if (needMock) {
    require('./index') // 对应的mock入口
    console.log('====start mock api===')
  }
} catch (e) {
  console.log('未引入mock,如需要,请创建/mock/config.local并导出 {needMock: true}')
}

Наконец, определите среду разработки во входном файле всего приложения и внедрите ее.

if (process.env.NODE_ENV === 'development') {
  require('../mock/entry')
}

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

Code Review

Ссылаться на:

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

  • Уметь подтверждать наличие отклонений в понимании требований и избегать споров

  • Оптимизируйте качество кода, включая избыточный код, имена переменных и чрезмерную инкапсуляцию.

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

резюме

В этой статье в основном организованы некоторые методы повышения надежности кода JavaScript, в основном систематизированы

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

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