Обычная тенденция, которую я наблюдал в проектах Elm, заключается в том, что Elm направляет вас к простым решениям. Я обнаружил, что это верно даже для новичков в Elm. Я тренировал команду в корпоративной среде, используя Elm для нового проекта. Они создали предыдущий проект с AngularJS и действительно боролись с ним. Вернее, они не боролись с этим. Фреймворк примет любое решение, которое они придумают. Когда они начали работать с Elm, им, возможно, потребовалось немного больше времени, чтобы выяснить, «как мне сохранить ввод из поля ввода в моей модели». Но поскольку Элм не разрешал им использовать временные хаки, их подход был совершенно другим. Вместо того, чтобы копировать код из StackOverflow, который использует глобальную переменную или настраивает DOM, «просто для того, чтобы заставить его работать на данный момент», Элм заставил их найти решение, не полагаясь на какие-либо хаки. В результате несколько месяцев спустя вместо борьбы с нестабильной кодовой базой, которую страшно менять, они работали с исключительно надежным кодом. Они могли вносить изменения легко и без страха. Разница была днем ​​и ночью.

Недавно у меня был подобный опыт, который показывает, как Elm ведет вас к простоте. Я являюсь автором пакета Elm для типобезопасных запросов GraphQL, dillonkearns/elm-graphql (см. Мой недавний разговор о Elm Conf, чтобы узнать больше). Я начал получать один и тот же запрос функции от нескольких разных пользователей. Это был разумный запрос, но когда он возник, я объяснил технические проблемы и насколько значительным будет изменение дизайна для его поддержки.

Задача: уникальные ключи

Запросы GraphQL получают ответы в формате JSON, причем ответы имеют ту же «форму», что и запрос. Итак, этот запрос:

{
  currentUser {
    avatar
  }
}

вернет JSON следующим образом:

{
  "currentUser": {
    "avatar": "https://avatar.com/user.jpg"
  }
}

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

{
  currentUser {
    avatar(size: 12)
    avatar(size: 48)
  }
}
# ERROR - Field 'avatar' has an argument conflict: is size 12 or 48?

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

dillonkearns/elm-graphql обрабатывает низкоуровневые детали построения и десериализации запросов GraphQL, так что 1) вы можете использовать высокоуровневые функции языка Elm вместо сложной библиотеки и 2) это может гарантировать, что любой созданный вами запрос является действительным. Таким образом, библиотеке нужен способ избежать создания таких неоднозначных и недействительных запросов.

Оригинальное решение

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

Используя этот подход, моя библиотека сгенерирует такой запрос:

{
  currentUser {
    avatar(size: 12)
    avatar2: avatar(size: 48)  # this alias tells GraphQL to return this data under the key "avatar2"
  }
}

Это сработало довольно хорошо. Но когда вы создали Selection Set, как указано выше, захватив малые и средние аватары, ему нужно было создать соответствующий Json Decoder на основе того, что он знал в тот момент времени (Json Decoders - это то, как Elm десериализует JSON в типизированные данные Elm) . Поэтому я не мог предоставить API для объединения двух таких наборов выбора вместе, потому что он создал бы декодер для извлечения данных из своего псевдонима avatar2. Но если мы объединим другой набор полей, включающий средний и полноразмерный аватар, их Json-декодеры попытаются получить эти данные из поля с именем avatar и avatar2, тогда как вместо этого они должны искать их в avatar3 и avatar4. Я мог бы решить эту проблему, переработав код для генерации десериализаторов (Json Decoders) в последнюю минуту, но это означало бы создание настраиваемой структуры данных, чтобы я мог отложить это. Это добавило бы намного больше сложности и точек отказа, где я мог бы внести ошибку.

В поисках простого подхода

Я действительно боялся сделать этот редизайн. Конечно, это было выполнимо, но было неправильно вводить столько сложностей для решения такой простой проблемы. Я хотел продумать дизайн библиотеки как с точки зрения общедоступного API, так и с точки зрения низкоуровневой реализации. Я часто делаю это в Elm. Если бы я решал эту проблему на другом языке, таком как TypeScript, было бы легко добавить какое-то глобальное состояние, чтобы подсчитывать, сколько раз создавались поля с определенным именем. Исходя из прошлого опыта использования этого подхода, это может привести к сообщениям об ошибках, тестам с ложными срабатываниями и отрицательными результатами, а также к тому, что другие люди будут трогать код и не понимать, откуда взялось это странное поведение или что им нужно было сделать, чтобы сохранить его должным образом. В этом проблема неявного состояния. Это действительно удобно, чтобы быстро решать проблемы. Но в конечном итоге вы начинаете ощущать реальную цену. Состояние - это действительно то, что затрудняет понимание и изменение кода, а чтение / изменение кода - это то, на что мы тратим большую часть наших часов кодирования. Так что, если мы сможем минимизировать состояние, это будет огромная победа!

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

Более простое решение

В конце концов мне пришла в голову идея использовать псевдонимы полей на основе хешей:

{
  currentUser {
    avatarABC: avatar(size: 12)
    avatarXYZ: avatar(size: 48)
  }
}

Создавая уникальный хэш на основе аргументов поля (представьте, что ABC является хеш-представлением (size: 12)), вы гарантированно избежите неоднозначных запросов. И вам не нужно знать, какие поля используются в окружающем запросе! Это решение намного проще и менее подвержено ошибкам, потому что все, что вам нужно, это заданное поле для определения его псевдонима. Не нужно подглядывать за его братьями и сестрами или беспокоиться о том, обзаведется ли он еще братьями и сестрами позже. Подробнее о деталях финальной реализации вы можете прочитать в этом выпуске Github.

Суть

Так почему это так важно? Одно из ключевых качеств, которые я ищу в языке, фреймворке, технике кодирования и т. Д., Заключается в том, имеет ли он тенденцию к простоте. Например, если рефакторинг с помощью языка или фреймворка утомителен или подвержен ошибкам, вы будете делать это реже. Если он будет казаться пуленепробиваемым и легким, как в Elm, то код станет более удобным в обслуживании. Со временем кодовые базы Elm стремятся к минимальному состоянию. А меньшее состояние означает меньше движущихся частей, менее дорогие изменения и меньше ошибок.

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