JavaScript предоставляет нам такую ​​гибкость, что мы можем набросать программу за считанные минуты. Наброски отлично подходят для опробования новых концепций или экспериментов с новыми техниками. Однако когда мы пытаемся создать масштабируемое и надежное программное обеспечение, наброски часто приводят к созданию неподдающегося сопровождению спагетти-кода. Я предлагаю простую структуру под названием Систематическая разработка, состоящую из трех этапов: Планирование, Создание и Тестирование для создания надежного программного обеспечения. . Систематическая разработка применима ко всем языкам программирования, но я сосредоточусь на том, как использовать эту структуру в JavaScript.

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

Эскизный подход

Вектор имеет компоненты x и y

Мы можем добавить векторы

Мы можем отрицать вектор

Мы можем вычитать векторы

Пока все хорошо. Однако теперь мы хотим обрабатывать не только 2D-векторы, но и 1D- и 3D-векторы. Нам нужен полный рефакторинг всех трех функций, потому что они предполагают, что векторы равны 2d. Чтобы определить, в каком измерении находится входной вектор, мы создадим три вспомогательные функции — is1d, is2d и is3d.

Теперь addVectors выглядит так:

Выглядит немного противно со всем повторяющимся литеральным кодом объекта.

Нам нужно применить ту же гадость к negateVector :

Мы копируем функцию addVectors и меняем все + на -, чтобы получить subtractVectors:

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

Системный подход к развитию

Прежде чем писать какой-либо код, нам нужно составить план.

Этап планирования

Задайте себе три вопроса:

1. Что мы хотим, чтобы программа достигла?

Нам нужна векторная библиотека, которая

  • поддерживает 1d, 2d и 3d векторы
  • поддерживает две векторные формы — компонентную и угловую (величина и направление)
  • может складывать, вычитать и инвертировать векторы

Постановка цели программы очень важна. Например, если мы решили поддерживать n-мерные векторы, программа будет совсем другой.

2. Как мы можем представить данные в программе?

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

Теперь мы переводим наше английское описание данных (в данном случае векторов) из вопроса 1 в типы на TypeScript. Во-первых, вектор может быть либо компонентом, либо угловым вектором. Поэтому мы создаем новый пользовательский тип с именем Vector, который имеет два варианта — VectorCom для компонентных векторов и VectorAng для угловых векторов.

Как мы можем представить VectorCom?

Давайте рассмотрим 1d, 2d и 3d векторы в каждом конкретном случае:

1d вектор в компонентах

  • х: действительное число

2d вектор в компонентах

  • х: действительное число
  • у: действительное число

3d вектор в компонентах

  • х: действительное число
  • у: действительное число
  • г: действительное число

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

Как мы можем представить VectorAng?

Давайте рассмотрим 1d, 2d и 3d векторы в каждом конкретном случае:

1d вектор по направлению и величине

  • величина: неотрицательное действительное число (величина ≥ 0)
  • направление: влево или вправо

2d вектор по направлению и величине

  • величина: неотрицательное действительное число (величина ≥ 0)
  • направление: угол между 0 и 360

3d вектор по направлению и величине

  • величина: неотрицательное действительное число (величина ≥ 0)
  • направление: два угла (αи β) от 0 до 360

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

1d угловой вектор

Мы используем -1 и 1 для представления левого и правого направления соответственно. Вы также можете использовать логическое значение, но -1 и 1 имеют для меня больше смысла, поскольку они фиксируют отрицательное (левое) и положительное (правое) направления, обычно используемые в физике. В дополнение к mag (величина) и dir (направление) мы также добавляем свойство dim (размер), чтобы мы могли обрабатывать каждое измерение по-разному при расчете.

2d угловой вектор

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

3d угловой вектор

Добавьте еще один угол, чтобы показать, насколько высоко/низко расположен вектор по вертикальной оси Z.

3. Как мы можем определить функциональные возможности программы?

Нам нужны три функции (в нашем случае векторные операции): сложение, инвертирование и вычитание векторов.

Давайте рассмотрим каждую функциональность более подробно и подумаем:

  • ввод функциональности
  • вывод функции

Добавлять

  • Вход

Поскольку добавление серии векторов от кончика к хвосту является относительно распространенным явлением, мы примем список векторов в качестве входных данных.

  • вывод

Мы вернем результирующий вектор в качестве вывода. Поскольку вектор может быть в компонентной или угловой форме, мы добавим входной параметр с именем form, чтобы пользователь мог указать, какую форму он хочет. Мы определим тип VectorForm, который может быть либо "ang" (угловой), либо "com" (компонентный).

Напишем сигнатуру функции для добавления:

отрицать

  • Вход

Примем на вход один вектор v.

  • вывод

Мы вернем инвертированный вектор в качестве вывода в form, указанном пользователем.

Напишем сигнатуру функции для отрицания:

Вычесть

  • Вход

Мы примем на вход два вектора v1 и v2.

  • вывод

Мы получим результат v1 — v2 в качестве вывода в form, указанном пользователем.

Напишем сигнатуру функции для вычитания:

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

Стадия сборки

Мы построим три функции последовательно.

Добавлять

Следуя схеме, которую мы создали на этапе планирования, мы поместим детали реализации в функцию addVectors.

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

ОСТАНОВИТЕСЬ и ИСПЫТАЙТЕ!

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

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

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

ОСТАНОВИТЕСЬ и ИСПЫТАЙТЕ!

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

Перед определением функции formVector мы укажем, что form по умолчанию, используемый всеми функциями (сложение, инвертирование и вычитание), будет "com"(компонент), поскольку все наши векторные операции по умолчанию создают вектор компонента.

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

ОСТАНОВИТЕСЬ и ИСПЫТАЙТЕ!

Давайте определим formVector, используя функции decomposeVector и angularizeVector helper:

ОСТАНОВИТЕСЬ и ИСПЫТАЙТЕ!

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

Наконец, давайте объединим все вспомогательные функции, чтобы создать функцию addVectors:

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

ОСТАНОВИТЕСЬ и ИСПЫТАЙТЕ!

Этот тест завершен? Чего не хватает?

Сделайте паузу и внимательно просмотрите тесты и сравните их с фактическими входными и выходными данными addVectors.

Ответ:

  • Вход

addVectors принимает список векторов в качестве входных данных, но мы протестировали только списки, содержащие 2 вектора. Мы должны добавить на вход пустые списки и списки, содержащие более 2 векторов.

  • вывод

addVectors может возвращать как компонентные, так и угловые векторы в качестве выходных данных, но мы протестировали только компонентные векторы в качестве выходных данных. Мы должны добавить выходные данные углового вектора, указав для параметра form значение "ang".

🎉🎉🎉 Мы успешно построили наш первый функционал — добавляем!

Мы можем следовать аналогичному процессу сборки и тестирования для двух других функций. Из-за большого объема статьи я не буду рассматривать их пошагово. Вы можете проверить мой репозиторий GitHub для всего кода.

Надеюсь, вы понимаете три этапа систематической разработки — планирование, сборка и тестирование — и можете применять их для создания собственных надежных проектов. 🚀