Пример структурированного решения проблем

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

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

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

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

Примечание: только потому, что это мой любимый ресторан быстрого питания, и у них много мест, я решил использовать Chipotle для заполнения данных в моем примере для этой статьи и демонстрации. Они не имели к этому никакого отношения :-).

Описание проблемы

Компания клиента имеет несколько офисов в нескольких штатах США. У них уже была карта Google на своем веб-сайте, на которой были маркеры, представляющие каждое местоположение, и они попросили нас добавить некоторые функции, которые позволили бы пользователю «находить местоположения рядом со мной».

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

Понимание проблемы

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

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

API JavaScript Карт Google предоставляет способ достичь этого простым способом, позволяя передавать функцию обратного вызова в качестве параметра, добавляемого к запросу API. Я уже создал простую настраиваемую функцию карты для клиента с маркерами, информационными окнами, связанными с каждым маркером, и границами, так что эта часть проблемы уже была решена (примечание: вы можете прочитать отличный учебник по API здесь, см. превосходную и обширную документацию по API Google здесь и см. краткое руководство по добавлению маркеров на пользовательские карты Google здесь).

Вот базовый пример, который Google предоставляет для встраивания пользовательской карты в документ HTML с помощью запроса API:

Маркеры

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

Маркеры на карте Google - это объекты типа Marker, которые включают свойство position, значением которого должен быть другой объект со свойствами Latitude и Longitude - либо буквальный объект с этими свойствами, либо объект latLng ». Этот объект latLng позволяет точно отобразить маркер относительно самой карты. Карта, на которой должен быть размещен маркер, затем предоставляется в качестве другого аргумента при создании маркера.

Вот простой пример функции, которую можно добавить к запросу API Карт Google для создания карты с одним маркером - вы можете увидеть, как создается маркер в строке 9 (пример кода предоставлен Google):

В этот момент я знал, что общее описание подхода было следующим: я собирался создать функцию JavaScript, которая будет передавать какие-то данные маркеров в Google Maps API, который затем будет возвращать визуализированную карту Google в указанную HTML-элемент (#map). Сложной частью будет обеспечение того, чтобы правильные данные о местоположении были сгенерированы и правильно отфильтрованы с учетом ввода пользователя.

Типы ввода, вывода и данных

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

Входы:

  • Местоположение пользователя: строка, например «1350 West Street, Los Angeles, CA» или даже просто «Los Angeles».
  • Желаемый радиус пользователя в милях: число.
  • Список всех местоположений: массив объектов, каждый из которых содержит данные о местоположении компании, такие как Адрес, Номер телефона, а также Широта и Долгота (вы можете см. данные о местоположении, которые я использовал для этой демонстрации здесь)

Вывод:

  • Отфильтрованный список местоположений: массив объектов. Это будет использоваться для 1) вывода списка местоположений в виде текста и 2) создания вызова API в Google для повторного рендеринга карты с указанными маркерами.

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

Углубляясь в проблему

Следующим шагом было сделать сами требования намного более конкретными - например, что значит для местоположения быть на расстоянии X миль от другого местоположения? Что ж, если мое значение latLng равно {latY, lngY}, а latLng предприятия равно {latZ, lngZ}, это должно просто означать, что расстояние от {latY, lngY} до {latZ, lngZ} меньше или равно X.

Поэтому, если бы я мог найти способ надежно вычислить расстояние между двумя объектами latLng и проверить, был ли результат меньше или равен введенному пользователем радиусу, остальная часть проблемы заключалась бы в просто фильтруя список местоположений, чтобы включить только те, которые соответствуют этим критериям (подсказка, что я, вероятно, буду использовать абстракции Array).

Но как на самом деле найти расстояние между любыми двумя точками на поверхности Земли, даже если бы у меня были широта и долгота?

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

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

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

Поскольку у меня уже были данные о широте и долготе, доступные для каждого местоположения (я нашел их вручную в начале этого процесса), все, что мне нужно, это способ преобразовать адрес пользователя в широту / долготу, а затем объект Google latLng, также.

И снова ленивые разработчики повсюду будут радоваться вместе со мной, узнав, что Google предоставил способ сделать это легко с помощью другого бесплатного инструмента - Geocoding API.

Отправка запроса в API геокодирования с параметром строки адреса возвращает объект JSON, который включает данные о широте и долготе местоположения - вот пример использования Oriole Park at Camden Yards, который расположен по адресу 333 W Camden Street, Baltimore MD :

Запрос: «https://maps.googleapis.com/maps/api/geocode/json?address=333+W+Camden+St,+Baltimore,+MD+21201&key=My_API_KEY

Ответ:

Это много полезных данных, но, что наиболее важно, они включают в себя «lat» и «lng» как свойства, к которым можно получить доступ в разделе «geometry: location».

Тестовые примеры и примеры

Теперь у меня было хорошее представление о вспомогательных функциях, которые будут задействованы в реализации моей функции обратного вызова - вместе они должны будут принимать ввод пользователя, делать запросы API по мере необходимости, анализировать ответ (ы) и выводить обновленную карту. .

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

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

  • Функция создать карту с возможностью поиска должна генерировать карту Google, отображающую правильные маркеры (и связанные с ними i nfoWindows). Маркеры должны быть переданы в качестве аргумента.
  • Информация об адресе пользователя (строка) должна быть объединена с ключом API и частичным URL-адресом, чтобы вернуть правильно отформатированный запрос API геокодирования (который, по сути, является просто запросом HTTP GET с данными адреса, добавленными в качестве параметра).
  • Запрос API геокодирования должен возвращать ответ в виде объекта JSON, который включает правильную широту и долготу для адреса, о котором он был запрошен, или статус «ZERO_RESULTS», если адрес не существует / плохо отформатирован.
  • Функция «distance between» из библиотеки сферической геометрии должна возвращать правильное расстояние в метрах между двумя объектами latLng, переданными в качестве аргументов.
  • Вспомогательная функция «конвертировать метры в мили» должна возвращать правильное количество миль, указанное в метрах в качестве аргумента.
  • Вспомогательная функция «находится в пределах радиуса» должна возвращать true для двух объектов latLng и числа радиуса в качестве аргументов, если расстояние между двумя объектами latLng меньше или равно радиусу.
  • Вспомогательная функция «фильтровать местоположения» должна возвращать подмножество массива «местоположений», включая только элементы, для которых функция «находится в пределах радиуса» вернула истину.
  • Второй вызов функции «создать карту» при передаче массива отфильтрованных местоположений должен правильно отображать маркеры только для этих местоположений.

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

Как только это сработало, я мог спокойно перейти к проблемам более высокого уровня, таким как обеспечение / проверка пользовательского ввода, создание пользовательского интерфейса с помощью HTML и CSS, а затем обеспечение того, чтобы взаимодействия между моим JavaScript и DOM работали должным образом.

Крайние случаи

Чтобы справиться с крайними случаями при вводе данных пользователем, я решил полагаться на простую дезинфекцию строк ввода пользователя, поскольку это будет клиентское приложение, а API Google довольно прощают плохой ввод. Насколько я мог определить, если что-то действительно странное или пустое было помещено в запрос API, ответом было бы просто сообщение «ZERO_RESULTS» ​​или ошибка, оба из которых я мог бы учесть с помощью защитного предложения.

Алгоритм

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

Это был мой алгоритм реализации решения как клиентского приложения:

  1. Возьмите радиус (число) и адрес (строку) от пользователя через HTML-форму
  2. Если оба ввода действительны, преобразуйте адрес в набор координат широты / долготы.
  3. Отфильтровать массив местоположений, возвращая новый массив, включающий только те местоположения, где расстояние между координатами пользователя и координатами местоположения не превышает радиуса пользователя.
  4. Выведите новую версию карты Google, передав отфильтрованный список местоположений и используя его для создания маркеров.
  5. Вывести список отфильтрованных местоположений в виде HTML-элементов под картой.

Код

Если вы зашли так далеко, или если у вас есть TL; DR, оказались здесь после беглого просмотра вступления, вам повезло! Наконец-то пришло время для решения.

Вот подход, который я придумал для реализации моего алгоритма Создать карту с возможностью поиска, используя простой JavaScript с небольшим количеством jQuery: https://github.com/Dchyk/locations-near-me/blob/master/createSearchableMap.js .

Примечание. Вы можете поиграть с живой демонстрацией этого проекта на моем веб-сайте и просмотреть полное репо проекта на моем Github.

Демо-данные локаций Chipotle находятся здесь и представляют собой массив объектов.

Пример:

Спасибо за прочтение

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

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