Как написать более элегантные условные суждения

JavaScript

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

Если кто-то еще хочет пересмотреть или добавить условие, продолжайте добавлять к нему условия. Если этот порочный круг продолжится, то лишь немногие из них могут в конечном итоге стать десятками или даже десятками.
Не говорите, что это невозможно, я видел, как люди часто используют if else в компонентах React, а читабельность и ремонтопригодность очень плохие. (Конечно, это не горшок если еще, это в основном проблема дизайна компонентов)

Эта статья в основном взята из "Энциклопедии кода 2". В оригинальной книге для реализации используются vb и java. Здесь я основываюсь на реализации TypeScript и добавляю к содержанию книги кое-что из своего собственного понимания.

Начните с примера

календарь

Если мы хотим сделать компонент календаря, то мы должны знать, сколько дней в каждом месяце из 12 месяцев в году, как мы об этом судим?
Самый глупый способ, конечно, использовать if else.

if (month === 1) {
    return 31;
}
if (month === 2) {
    return 28;
}
...
if (month === 12) {
    return 31;
}

Таким образом, необходимо написать 12 ifs сразу, что тратит много времени и неэффективно.
В настоящее время некоторые люди будут думать об использовании switch/case для этого, но switch/case будет не намного проще, чем if, и все равно нужно будет написать 12 case! ! ! Что, если бы учитывались високосные годы? Не будет ли это более хлопотным?
Мы могли бы также изменить наше мышление.Каждый месяц соответствует числу, а месяцы идут по порядку.Можем ли мы использовать массив для хранения количества дней? Когда он будет доступен по индексу?

const month: number = new Date().getMonth(),
    year: number = new Date().getFullYear(),
    isLeapYear: boolean = year % 4 == 0 && year % 100 != 0 || year % 400 == 0;

const monthDays: number[] = [31, isLeapYear ? 29 : 28, 31, ... , 31];
const days: number = monthDays[month];

концепция

Я полагаю, что после прочтения приведенного выше примера у вас есть определенное представление о табличном методе. Вот краткое изложение «Энциклопедии кода».

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

Прежде чем использовать табличный метод, вам нужно подумать над двумя вопросами. Один из них — как делать запросы из таблицы. В конце концов, не все сценарии так просты, как описанные выше. Если оценка if находится в другом диапазоне, как Проверь это?
Другой вопрос, что вам нужно запрашивать в таблице, это данные? или действие? Или индекс?
На основе этих двух вопросов запросы делятся на следующие три типа:

  1. прямое интервью
  2. доступ к индексу
  3. Лестничный доступ

таблица прямого доступа

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

Статистика страховых тарифов

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

if (gender === 'female') {
    if (hasMarried) {
        if (age < 18) {
            //
        } else {
            // 
        }
    } else if (age < 18) {
        //
    } else {
        // 
    }
} else {
    ...
}

Но из приведенного выше примера с календарем этот возраст представляет собой диапазон, а не фиксированное значение, и его нельзя сопоставить с массивом или объектом, так что же нам делать? Это связано с проблемой, упомянутой выше, как сделать запрос из таблицы?
Эта проблема может быть решена двумя методами: таблицей доступа к релейной диаграмме и таблицей прямого доступа, доступ к релейной диаграмме будет представлен позже, и здесь упоминается только таблица прямого доступа.
Есть два обходных пути:
1. Скопируйте информацию, чтобы иметь возможность напрямую использовать значение ключа.
Мы можем скопировать часть информации для каждого возраста в диапазоне от 1 до 17 лет, а затем использовать возраст для прямого доступа к ней, и то же самое верно для других возрастных групп. Этот метод заключается в том, что операция очень проста, и структура таблицы также очень проста. Но есть недостаток в том, что это будет занимать место впустую, ведь генерируется много избыточной информации.
2. Преобразование пары "ключ-значение"
Давайте подумаем об этом с другой стороны, что, если мы преобразуем возрастной диапазон в ключ? Таким образом, к нему можно получить прямой доступ.Единственная проблема, которую следует рассмотреть, — это как преобразовать возраст в значение ключа.
Конечно, мы можем продолжить это преобразование с помощью if else. Как упоминалось ранее, с простым if else проблем не возникает, а управление таблицами предназначено только для оптимизации сложных логических суждений, что делает их более гибкими и легко расширяемыми.

enum ages {
    unAdult = 0
    adult = 1
}
enum genders {
    female = 0,
    male = 1
}
enum marry = {
    unmarried = 0,
    married = 1
}
const age2key = (age: number): string => {
    if (age < 18) {
        return ages.unAdult
    }
    return ages.adult
}
type premiumRateType = {
    [ages: string]: {
        [genders: string]: {
            [marry: string]: {
                rate: number
            }
        }
    }
}
const premiumRate: premiumRateType = {
    [ages.unAdult]: {
        [genders.female]: {
            [marry.unmarried]: {
                rate: 0.1
            },
            [marry.married]: {
                rate: 0.2
            }
        },
        [genders.male]: {
            [marry.unmarried]: {
                rate: 0.3
            },
            [marry.married]: {
                rate: 0.4
            }
        }
    },
    [ages.adult]: {
        [genders.female]: {
            [marry.unmarried]: {
                rate: 0.5
            },
            [marry.married]: {
                rate: 0.6
            }
        },
        [genders.male]: {
            [marry.unmarried]: {
                rate: 0.7
            },
            [marry.married]: {
                rate: 0.8
            }
        }
    }
}
const getRate = (age: number, hasMarried: 0 | 1, gender: 0 | 1) => {
     const ageKey: string = age2key(age);
     return premiumRate[ageKey]
        && premiumRate[ageKey][gender]
        && premiumRate[ageKey][gender][hasMarried]
}

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

enum ages {
    unAdult = 0,
    adult = 1
}
enum genders {
    female = 0,
    male = 1
}
enum marry = {
    unmarried = 0,
    married = 1
}

const age2key = (age: number): string => {
    if (age < 18) {
        return ages.unAdult
    }
    return ages.adult
}
const rates: number[] = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8]
const premiumRate = {
    0: [age.unAdult, genders.female, marry.unmarried],
    1: [age.unAdult, genders.female, marry.married],
    2: [age.unAdult, genders.male, marry.unmarried],
    3: [age.unAdult, genders.male, marry.unmarried],
    4: [age.adult, genders.female, marry.unmarried],
    5: [age.adult, genders.female, marry.married],
    6: [age.adult, genders.male, marry.unmarried],
    7: [age.adult, genders.male, marry.unmarried]
}
type BoolCode = 0 | 1
const getRate = (age: number, hasMarried: BoolCode, gender: BoolCode) => {
    const ageKey: BoolCode = age2key(age)
    let index: string = ''
    Object.keys(premiumRate).forEach((key, i) => {
        const condition: BoolCode[] = premiumRate[key]
        if (condition[0] === ageKey 
            && condition[1] === gender
            && condition[2] === hasMarried
        ) {
            index = key;
        }
    })
    return rates[index];
}

После этой модификации структура становится более четкой и легкой в ​​обслуживании.

Индекс таблицы доступа

Стоящий перед нами вопрос страхового тарифа — это головная боль при работе с возрастным диапазоном, который часто не так легко получить, как ключ выше.
Мы упомянули копирование информации в то время, чтобы иметь возможность напрямую использовать значение ключа, но этот метод тратит много места, потому что каждый возраст будет сохранять часть данных, но что, если мы просто сохраним индекс и запросим данные через этот индекс?
Предполагая, что человек родился в 0 лет и может прожить до 100 лет, тогда нам нужно создать массив длиной 101, а нижний индекс массива соответствует возрасту человека, чтобы мы хранили '65' выше 65.
Таким образом, мы можем получить соответствующий индекс по возрасту, а затем запросить соответствующие данные по индексу.
Кажется, что этот метод сложнее, чем приведенная выше таблица прямого доступа, но в некоторых сценариях, когда сложно преобразовать ключевые значения, а данные занимают много места, можно попробовать доступ по индексу.

const ages: string[] = ['<18', '<18', '<18', '<18', ... , '18-65', '18-65', '18-65', '18-65', ... , '>65', '>65', '>65', '>65']
const ageKey: string = ages[age];

Форма доступа к лестнице

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

const ageRanges: number[] = [17, 65, 100],
  keys: string[] = ['<18', '18-65', '>65'],
  len: number = keys.length;
const getKey = (age: number): string => {
  for (let i = 0; i < len; i++) {
    console.log('i', i)
    console.log('ageRanges', ageRanges[i])
    if (age <= ageRanges[i]) {
      return keys[i]
    }
  }
  return keys[len-1];
}

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

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

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

Цитируя отрывок из Zhihu Da V Ivony:

При анализе и чтении фрагмента кода он часто фокусируется на аспектах, иногда на данных, иногда на логике. Предположим, у нас есть такое требование, когда значение такого-то меньше 100, как оно. Тогда 100 в этом — это данные.Что происходит, когда спрос изменяется до определенного значения меньше 200, то наше внимание сосредоточено на модификации этих данных. Вместо модификации всей логики удаление данных помогает нам найти точки модификации и быстрее модифицировать код.

Использованная литература:

  1. Энциклопедия кода (2-е издание)
  2. Следует ли реорганизовать if else в этом примере?

PS: Приглашаю всех обратить внимание на мой публичный аккаунт [Front-end Pavilion], и давайте вместе обсудим технологии.