Зарегистрируйтесь для доступа к 15+ бесплатным курсам по программированию с тренажером

Деревья JS: Обработка ошибок

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

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

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

  1. Работа с файловой структурой, как с некоторым деревом, в котором есть стандартные операции. Например, добавить детей, удалить детей, проверить их количество
  2. Работа с файловой системой, обеспечением инвариантов файловой системы, обработкой ошибок и прочее

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

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

Дизайн

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

Допустим, мы хотим создать следующую файловую структуру:

На верхнем уровне находятся папки home и tc, и внутри папки home находится файл config.

Чтобы передать туда название элемента, который является основным на текущем уровне, напишем функцию New Tree. Далее мы можем добавить детей этому элементу. Для этого мы пишем функцию tree.addChild и передаем имя home. Получается, функция tree.addChild возвращает ноду .addChild('home), которая является ребенком home.

Дальше мы продолжаем через точку вызывать снова addChild — рекурсивно производим добавление. Получается структура, где внутри home содержится config.

Далее ниже на самом дереве вызываем .addChild('etc'). В итоге внутри корневой директории у нас появляется на одном уровне несколько детей: home и etc.

Также ниже у нас есть тесты:

С их помощью мы проверяем, что у дерева hasChildren есть дети, один из которых home.

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

Теперь посмотрим уже построенную структуру, но при этом извлечем из нее некоторые данные.

Берем дерево и вызываем уже getChild, которому передаем ключ. Мы использовали его при создании ребенка:

В итоге getChild возвращает нам ребенка, так же как этот child, из которого мы также можем делать GetChild. В итоге мы получаем ноду.

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

Некоторые ноды содержат детей, некоторые нет. Причем эта библиотека не накладывает ограничений. Это мы делаем в библиотеках, которые построены поверх нее. То есть как мы хотим это использовать.

При этом все ноды обладают одинаковым интерфейсом. Также в ноде есть базовые методы: getKey и getMeta:

getMeta — это все что угодно, потому что у нас универсальная библиотека. То есть при getChild второй параметр был data.

То есть мы какие-то данные засунули в виде строки, в виде чего угодно, это не имеет никакого значения, просто будет храниться в ноде. И ключ это, в общем-то, то имя, которое мы указали при GetChild. Этого достаточно, чтобы построить файловую систему поверх этой системы.

Реализация

У реализации есть некоторая особенность. Мы будем создавать класс, который называется Tree — просто дерево:

По сути это и есть нода — рекурсивная структура, у которой дальше внутри есть деревья. Она рекурсивная по определению, поэтому так же и работает.

Еще у нас есть конструктор, который принимает на вход три параметра: key, meta и parent. Последний мы не всегда будем использовать. Если parent нет, он не передается.

Все это устанавливаем внутрь через this и дополнительно внутри устанавливаем свойство children, которому присваиваем new Map. То есть у нас будет некоторый ассоциативный массив — ключ значения, в котором содержится children.

Рассмотрим, как выглядит функция добавления ребенка:

Здесь мы передаем ключ и мета, который тоже не обязательно передавать. Если у нас достаточно ключа, то можно передать только его. И внутри мы создаем уже child — делаем new Tree, передаем туда key и meta, которые перешли к параметрам.

Также обязательно передаем this, так как если мы делаем addChild, значит, что текущий элемент, в котором мы находимся, — это parent. Поэтому нам обязательно передавать его как this, так как внутри он будет установлен как parent. То есть мы всегда из глубоких нод можем выйти наверх и обратиться к .parent.

Еще нам нужно не забыть поместить ребенка в список детей текущего элемента: делаем this.children.set, под ключом складываем туда текущего нового ребенка и возвращаем его наружу.

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

Ошибки

Предположим, мы создали такое дерево:

Мы записали ключ animals и добавили ребенка с ключом cats. При этом ниже делаем getChild('dogs'), но dogs внутри у нас нет. Но это не является ошибкой, потому что getChild выполнил свою работу: проверил, что там нет этого ребенка. По умолчанию вызов этой функции для ребенка, которого не существует, будет возвращать нам Undefined.

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

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

Чаще всего мы будем работать вот с такими путями:

Здесь у нас будет достаточно глубокая иерархия.

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

Чтобы это упростить, мы можем добавить в интерфейс библиотеки для работы с деревьями еще один метод — getDeepChild. Он выполняет эту рутинную работу — шаблонный код, который всё повторяет за нас.

Для этого мы передаем ему список ключей, по которым он внутри рекурсивно делает getChild, пока он не вернет последний элемент:

При этом если мы возьмем parent и посмотрим у него ключ, то это будет nginx. То есть nginx является parent для confd.

Если мы делаем обращение к элементу, которого не существует, то она должна вести себя так же, как getChild — должна возвращать undefined.

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


Аватары экспертов Хекслета

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят команда поддержки Хекслета или другие студенты.

Для полного доступа к курсу нужен базовый план

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

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

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

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff

Используйте Хекслет по-максимуму!

  • Задавайте вопросы по уроку
  • Проверяйте знания в квизах
  • Проходите практику прямо в браузере
  • Отслеживайте свой прогресс

Зарегистрируйтесь или войдите в свой аккаунт

Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»