Ошибка дороже, чем null

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

Это функция.

В частности, это интерфейс, который мы предоставляем, который обычно представляет собой набор функций.

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

Почему?

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

Нет, это не так. Это не устраивает.

Любой разработчик может писать повторно используемый код независимо от опыта. Такие языки, как JavaScript, Python, Ruby и Go, построены на миллионах небольших общих модулей, которые показывают, насколько легко писать повторно используемый исходный код. Написать повторно используемый код легко.

Давайте проведем рефакторинг.

I̶t̶ ̶i̶s̶ ̶d̶i̶f̶f̶i̶c̶u̶l̶t̶ ̶t̶o̶ ̶w̶r̶i̶t̶e̶ ̶r̶e̶u̶s̶a̶b̶l̶e̶ ̶c̶o̶d̶e̶.̶
Трудно повторно использовать код.

Ну это тоже не то. Взгляните на библиотеку node.js repeat-string на npm. Он просто повторяет строку, и разработчики загружают ее более семнадцати миллионов раз в неделю.

Семнадцать миллионов - это только количество скачиваний. Он даже не показывает, сколько раз эта функция фактически повторно используется в исходном коде. Это должно быть астрономически!

Так к чему я?

Как бы вы нашли модуль типа repeat-string для своего проекта node.js? Вы бы искали « повторить строку в npm», верно? Возможно, вы будете искать повтор строки, но результаты будут аналогичными. Вы можете увидеть описанную выше проблему во втором результате поиска. И четвертый. И девятый. И десятый. И одиннадцатое.

Взгляните на эти примеры. Каждая библиотека обеспечивает одинаковое точное поведение.

Ты видишь проблему?

Нет, дело не в том, что один из них асинхронный по какой-то странной причине. Я также не говорю о том, что повторение строк было частью языка JavaScript более 6 лет ("A".repeat(5)). Дело в том, что у каждой библиотеки есть различия:

  1. Их входная подпись. Некоторые принимают (string, int), другие - (int, string). Один фактически принимает числа с плавающей запятой, а другой требует обратного вызова.
  2. Их выходная сигнатура. Каждый выходной поток представляет собой строку, кроме той, которая ничего не возвращает и передает свой результат в виде обратного вызова. Даже не рассказывай мне об их ошибках.
  3. Их поведение при выполнении. Одно - асинхронное, другие - синхронное.
  4. Как они представлены. Некоторые предоставляют одну экспортированную функцию, другие - как метод объекта.

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

Подумаешь?

Проблема в том, что эти варианты добавляют нулевое значение, только трение. Они не влияют на «бизнес-логику» (то есть на то, что имеет значение). Это детали реализации. Иногда этот выбор компенсирует недостатки языка или структуры, а иногда - просто результат мысленного подбрасывания монеты. Мы стараемся выбрать путь, который улучшит опыт разработчиков сейчас или в будущем, но предсказать будущее невозможно. Эти решения дьявольские в том смысле, что каждый выбор сначала кажется хорошим, а все работает. Мы получаем дофамин и чувствуем себя королями и королевами. Мастера компьютерного дела! Однако когда нам нужно что-то добавить, заменить или изменить, мы понимаем, что не совсем гении, о которых думали.

Как часто нам нужно что-то менять? Каждый день. Это все, что мы делаем. Как часто программное обеспечение ломается из-за этого? Буквально все время. Иногда поломка незначительная, и мы ее исправляем, не задумываясь. Мы согласны с тем, что сломанное программное обеспечение является нормальным явлением. Мы даже пишем автоматизированные тесты, которые ничего не делают, кроме как указывают на то, что что-то сломалось.

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

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

Давайте проведем рефакторинг постановки задачи в третий раз.

Сложно WRITE Многоразовых ̵c̵o̵d̵e̵.̵
Трудно REUSE ̵c̵o̵d̵e̵.̵
Трудно писать сменный код.

Это не цепляет, но приближается.

Без кода plug-and-play нам нужно сгладить каждую морщину в интерфейсе, прежде чем мы сможем его использовать. Мы должны настроить каждый бит кода, который входит в приложение. Каждый вход. Каждый выход. Каждый API. Ставим строителей на переходники и заворачиваем их на фабриках за сервисами. Никакое количество помады не может сделать красивый узор.

Это похоже на создание программного обеспечения в 18 веке. Обрезаем деревья вручную до досок произвольных размеров. Мы изготавливаем молотки с нуля и вдавливаем железо в гвозди, чтобы построить дом, идентичный соседнему. Это стоит слишком дорого и занимает слишком много времени. Даже после завершения мы все еще обременены индивидуальным обслуживанием. У нас нестандартные размеры, проводка все время отправляет электриков в больницу, а строителям, только что окончившим профтехучилище, не нравится, как мы гвозди гвозди. Нам уже пора приобрести программное обеспечение Home Depot и цифровые 2x4.

Нет ничего удивительного в том, что у нас могут быть стандарты, которые работают достаточно хорошо для всех и где все подходит. Microsoft представила COM в 90-х годах, чтобы разделять логику между приложениями, написанными на любом языке. JVM почти такая же старая и показала, как несколько языков могут ориентироваться на один и тот же байт-код и взаимодействовать друг с другом. На протяжении десятилетий мир открывал и заново открывал для себя Flow и Linda как способ соединения распределенных черных ящиков, и Docker пересмотрел то, чем может быть современный черный ящик.

В этой проблеме много возможностей, и многие пытаются ее решить. Dapr и WasmCloud обещают следующие итерации. Оба они по-разному решают часть проблемы. Каким бы печальным ни было состояние программного обеспечения сейчас, я теперь полон надежд, чем в последние 10 лет. JavaScript предоставил краткий период обнадеживающих обещаний - когда универсальная платформа могла позволить аморфным приложениям распространяться по всему миру - прежде чем превратиться в груду Electron, React и потраченной RAM. Веб-сборка - мой источник нового оптимизма. Это далеко не идеально, но вот что интересно. Идеальное никогда не побеждает.