Контекст проблемы

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

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

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

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

Цели решения

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

Я думаю, что все это возможно, если следовать принципам потока. Он отличается от объектно-ориентированного программирования и радикально отличается от того, как нас учат писать программное обеспечение. Так что ожидаю много критики. Моя единственная надежда состоит в том, что эта критика будет объективной, а не просто сопротивлением изменениям из-за боязни попробовать что-то новое.

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

Предлагаемая идея — поток

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

  1. Какие вещи я уже знаю
  2. Какие следующие вещи я могу знать, учитывая то, что я знаю.

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

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

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

Принципы потока

1. Два вида

Все единицы кода могут быть одного из двух типов. Данные или Алгоритм. Они называются Модели и Функции.

2. Модель

Модель будет иметь базовые типы данных и другие модели, но не методы.

Модель [Модель, базовый тип]

3. Функция

Функции будут принимать модели и функции в качестве входных данных, а функцию или модель - в качестве выходных данных.

функция ( [список моделей], [правила доступа к моделям], [список функций])

4. Доступ к ясности

Единственное, что функциям разрешено читать и писать, будет определяться ее аргументами. Это означает:

  1. Все модели, которые он использует, и вызовы функций, которые он делает, явно указаны в его сигнатуре.
  2. Функции не имеют доступа к какой-либо постоянной области действия, кроме моделей, которые они принимают в качестве входных данных. Таким образом, единственные постоянные изменения, которые он может внести, находятся в классах моделей.

5. Доступ к минимуму

Функции должны иметь доступ к как можно меньшему количеству информации. Из этого следует:

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

Отличие от ООП

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

Связь с функциональным программированием

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

Например, Flow не требует, чтобы какие-либо функции были математически чистыми. Хотя наличие такого свойства часто поощряет принцип «Минимального доступа». Flow не требует таких чистых функций, поскольку могут быть другие способы минимизировать доступ.

Чем не процедурное программирование?

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

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

В-третьих, он вводит несколько довольно новых идей по управлению уровнем доступа к моделям и их членам. Идея достаточно проста, но пока я не видел ни одного крупного языка, поддерживающего ее. В существующих языках есть такие понятия, как private и public, которые одинаково применимы ко всем функциям другого класса или ко всем сущностям в пространстве имен. Что предлагает Flow, так это доступ для чтения и записи для каждой функции. То, что доступно для записи одной функции, не обязательно для другой. И один член модели может быть доступен для записи, а другой — нет в одной и той же функции, что, например, не так, как работает ключевое слово const для константных функций C++. В процедурном программировании этого нет.

Я также хочу сказать, что если какие-то идеи в процедурном программировании приносят пользу, мы должны их принять. У процедурного программирования были проблемы с неконтролируемым уровнем глобальной области видимости, из-за чего было очень трудно следить за вещами. В Flow все должно быть наоборот. Глядя на функцию, вы будете точно знать, куда смотреть дальше, чтобы понять ее путь. Неважно, сколько миллионов строк кода в проекте, если вы начнете с любой функции глубоко внутри потока, вы сразу узнаете, что ей требуется и что она может изменить, а что нет. Конечно, вам нужно будет знать предметную область, и этого ни в коем случае нельзя избегать. Вам понадобится человек, чтобы объяснить вам бизнес-логику. Но разница будет заключаться в том, что вы будете точно знать, какие вопросы задавать. Вы будете точно знать, какие предыдущие функции или модели вам нужно понять. Таким образом, поток понимания будет самоуправляемым, и вы никогда не потеряетесь, даже не зная, о чем спросить. И это было бы возможно, потому что топологический граф зависимостей понятий будет сгенерирован тривиально. Единственной оставшейся задачей будет понимание этих моделей и функций с точки зрения бизнес-логики.

Идеальный ООП с внедрением зависимостей

Если мы начнем с типичного класса ООП и спросим, ​​почему он должен иметь более одного метода, если он действительно делает одну вещь? В идеале не должно. Таким образом, мы уменьшаем количество методов до одного. В этот момент мы также можем убить класс. Затем мы добавляем все зависимости как внедрение зависимостей через аргумент. То, что у нас осталось, — это душа Потока.

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

Преимущества

  1. Поскольку между функциями, работающими с одной и той же моделью, не сохраняется никакого контекста, кроме самой модели, у функций не может быть необычных переменных состояния, которые делают их хрупкими и менее читабельными.
  2. Поскольку он явно указывает, какие другие функции он будет вызывать, модульное тестирование становится возможным по замыслу.
  3. Поскольку каждая функция существует сама по себе, каждая задача действительно модульная.
  4. Из-за этого явного списка моделей и функций в его сигнатуре мы можем легко построить топологический порядок моделей и функций, которые необходимо понять, чтобы понять эту функцию. Это делает вещи намного более читабельными.
  5. Если кто-то хочет исправить функцию, он точно знает, какие пути существующего кода нужно понять.

Пример

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

В ООП-подходе должен быть класс Queue с этими двумя методами. В то время как моя идея предлагает создать две функции, которые ничего не знают друг о друге, за исключением того, что они обе работают с одним и тем же классом Model, называемым Queue. А Queue — это просто модель без каких-либо методов.

Таким образом, реализация постановки в очередь не может зависеть от деталей реализации метода удаления из очереди. Они имеют только один и тот же класс модели. Это немедленно освобождает нас от необходимости понимать весь класс (в мире ООП), чтобы понять любой из методов. Если подумать, в хорошей реализации не должно быть никаких зависимостей между этими двумя функциями.

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

Я думаю, что истинная сила этого подхода будет более ясна на очень сложных реальных примерах с сотнями классов моделей и тысячами методов. В ООП, чтобы понять любой блок кода, вам часто нужно понять, как на самом деле работает весь класс. На самом деле я нахожу немного странным, что ООП не следует тому, что оно само проповедует, в том смысле, что каждый модуль должен делать одну и одну вещь действительно хорошо. Почему мы тогда говорим, что Enqueue и Dequeue должны быть частью одного и того же объекта? Это действительно две разные задачи. Тот факт, что они используют общую модель, не должен означать каких-либо других зависимостей.