Pull to refresh

Comments 26

В проектировании вывел для себя следующее правило:

Любая абстракция – это инструмент, применение которого имеет свою цену, выраженную в увеличении когнитивной нагрузки.

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

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

И да, завуалированные ответы, просто повторяющие определение паттерна проектирования («Я использую декоратор, чтобы динамически добавлять объекту новую функциональность, оборачивая его в обёртку») не считаются. Нужна конкретная ситуация и предложенное решение.

Иначе можно быстро скатиться в энтерпрайз-ориентированную разработку, с 2 проектами, полиморфной фабрикой, декоратором и инверсией контроля для проверки является ли строка палиндромом.
> Любая абстракция – это инструмент, применение которого имеет свою цену, выраженную в увеличении когнитивной нагрузки.

По идее ровно наоборот, пока нет цели разобраться во всей структуре.

Да нет, именно так.


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

вероятность ошибки в строке кода крайне мала, пусть 0.01%. Перемножив вероятности ошибки на большом приложении с 10 000 строк кода, получаем 100%, что там есть ошибка

В этом случае вероятность ошибки будет не 100%, а примерно 63%
UFO just landed and posted this here
А если 20 000 строк кода, то 200%? Шанс 100% не будет никогда по определению вероятности.
UFO just landed and posted this here

63% ну никак не попадает в категорию "практически 100%", так что ваше "во-первых" мимо.


Вот с "в-третьих" стоило бы начать и закончить.

Вероятность отсутствия ошибки в одной строке кода: 99.99%.
Вероятность отсутствия ошибок во всех строках кода: 99.99%^10000=36.8%.
Вероятность наличия хотя бы одной ошибки во всех строках кода: 100%-36.8%=63.2%.
141433315, defuz

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

P(A+B)=P(A)+P(B)−P(A⋅B).

Вероятность наличия ошибки в одной строке кода: 0.01%.
Вероятность наличия ошибки во всех строках кода: 0.01%^10000 = очень мало.
Вероятность наличия хотя бы одной ошибки в любой строке кода: 0.01%*10000 — 0.01%^10000 ~= 100%.
У вас неверное представление о том, как работает теория вероятности.

Формула, которую вы привели, для трех событий будет выглядеть следующим образом:

P(A+B+С) = P(A) + P(B) + P(C) − P(A⋅B) − P(B⋅C) − P(C⋅A) + P(A⋅B⋅C)

Для 10000 строк эта формула будет иметь 2^10000-1 слагаемых. Вряд ли вам захочется считать вероятность таким образом.

У нас есть два взаимоисключающих события, одно из которых гарантированно наступает:

  1. Ни в одной строке кода нет ошибки.
  2. Хотя бы в одной строке кода есть ошибка.

Посчитайте вероятность первого события, а вероятность второго будет простым его отрицанием.
Вероятность наличия ошибки в одной строке кода: 0.01%.
Вероятность наличия ошибки во всех строках кода: 0.01%^10000 = очень мало.
Вероятность наличия хотя бы одной ошибки в любой строке кода: 0.01%*10000 — 0.01%^10000 ~= 100%.

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

UFO just landed and posted this here

Интересная тема, спасибо. В общем чуть более чем полностью согласен. Моё скромное мнение заключается в том, что сложность бывает двух типов: естественная сложность автоматизируемого бизнес процесса и искусственная сложность привнесенных абстракций, фоеймверков и прочего, прочего, прочего. Задача проектирования — реализовать естественную сложность в некотором управляемом виде при этом, по возможности, не привнести искусственную сложность. Эту идею можно выразить в простом ритуале — каждый раз добавляя в дизайн приложения новый элимент, нужно спросить себя позволяет ли этот элемент упростить работу с бизнес логикой и если да, то можно ли добиться этого же результата проще? Конечно бывают крайние случаи, когда нам нужно выживать под нагрузкой например. Но тем не менее.


И не много критики.


Про Дашу и бизнес логику. Тут есть обратная сторона в картинке. 80% стека вызовов это фреймверк, то есть общий код, который ты по идее не должен трогать совсем. А вот бизнес логика это 20% которые должны быть довольно простые. Если отказаться от этих 80%, то придётся добавить к бизнес логике ручную работу с транзакциями, безопасность, работу с бд. Не факт, что получится проще.


Ну и ещё один момент. Простые решения требуют непрерывного рефакторинга. В случае, если мы написали код в методе контроллера и наше приложение осталось простым, то мы выиграли, сэкономили кучу времени. Но если приложение начало расти? Где та грань, когда нужно начинать переходить от простых решений к сложными? Когда контроллера два? Когда их 10? Проблема в том, что обычно тебя просят дописать один метод, потом второй. И каждый раз ты думаешь, что это всего то ещё одна фича, ничего страшного. А потом приходится в какой-то момент рефакторить монстра из лапши на тысячи строк кода. Собственно по этому часто и делают оверинжиниринг на будущее, потому что боятся, что потом не будет времени на рефакторинг и надо брать это время пока дают. Не сказал бы, что это хорошо. Лучше когда на каждую фичу закладывается время на рефакторинг, тогда можно идти от простых решений к сложными по необходимости и без проектирования на будущее.

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

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

Поручают молодому «гению» что-то бесконтрольно сочинять. Кто виноват? Тот кто поручил.

Требуют по быстрому в бой. Кто виноват? Кто требует по быстрому.

Веруют в зазнавшегося архитектора. Кто виноват? Верующий.

В общем случае — понятия не имеют о способах организации разработки. Кто виноват? Ответ очевиден.

Программист всегда крайний, поэтому на него и валят. А на самом-то деле…
Была бы у меня возможность показать вам код проекта которому уже лет 6 как, на ноде, с колбэками, без абстракций… когда в контроллере функции на (я не шучу) 2k строк… и все это еще завязано на ивент емитере…

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

Мы как в том анекдоте, едим кактус и плачем. Ооочень медленно вводим абстракции вида

1. Validation layer (валидаци? какая валидация? =))
2. Thin Controller (сейчас это просто куча кода с колбек хелами)
3. Service Layer (отсутствует как класс. вся логика в контроллере)
4. DAO/Repository (сейчас монгуси прямо в контроллерах)
5. Mappers (работаем с внешними апишками)
6. Resources (http clients)

Да, файлов много. Но уже не так потеешь когда открываешь контроллер. И этих файлов будет еще больше. Но альтернатива в виде огромных функций это ад…

Ну и нужно понимать, абстракции потекут если их не поддерживать… и превратятся в гомно, кучу кода с ифами… я думаю все мы это проходили =))

з.ы.
Естественно мы не пытаемся впихнуть невпихуемое типа абстрактной фабрики и т.д. Да и 80% разработчиков и не слышали о них, может это и к лучшему =))

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

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


  1. Множество простых решений похоже всегда сильно меньше множества всех возможных решений. Поэтому найти простое решение сложно.
  2. Задачу, если смотреть на неё, как на математический объект, типа вектора, можно по разному раскладывать на компоненты и от этого зависит сложность. Возможно вы встречали ситуацию, когда после рефакторинга удалялось 90% кода, а система продолжала работать. Это и есть выбор правильного базиса для решения задачи.

В математике есть масса примеров. Матрица может быть «сложной», без какой-либо структуры, но ее можно свести к Жордановой форме. Сигнал может быть сложным, но раскладывается в ряд единообразных компонент — sin/cos, вейвлеты и т.п.


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

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

Есть одна структура, рядом есть другая. В каждой по 1000 отличающихся от соседа элементов. Эта 1000 = производная. Дайте несложный алгоритм, устраняющий эту сложность.

Кажется вы не поняли моего комментария и задаёт вопрос с ним не связанный...


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

Давайте скажем ещё проще — нужно сесть и подумать. И результат обязательно будет.

Чем такой вариант хуже вашего? Он точно так же не показывает сложность промежуточных шагов. И точно так же выглядит «просто».

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

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

1. Сложность как следствие предметной области. Это самая честная сложность.

2. Сложность как следствие привычки. В эту ловушку я попал на своем пет проекте. Я его писал прям с нуля и со временем он оброс всякими вспомогательными штуками: системой локализации, тестами АПИ, системой валидации и др. И в какой-то момент мне нужно было сделать другой пет проект так же с нуля за ограниченное время. Ты уже привык к своей среде, а главная ценность время и нужно следовать ей.

А еще меня всегда поражали туториалы уровня Hello world!, где первая часть это настройка IDE, окружения, деплоя, прогона тестов. А вторая часть это Hello world! и все что настроено в первой части по большому счету не используется. Это можно рационально объяснить тем, что «хорошие привычки нужно прививать сразу». Но это не всегда возможно. В конце концов «На ошибках учатся. А некоторым вещам учатся только на ошибках.»

3. Сложность как следствие пессимизации задачи. По своему парадоксальное явление. Люди исходят из того, что не бывает простых задач. Бывают только сложные. И выбирают инструменты для сложных задач. Если бы это был осознанный выбор «на вырост» я бы понял. Но до «выроста» еще дожить надо. Это как: «Нам поручили спроектировать сортир на даче. Мы исходим из концепции хайлоад, поэтому будем дорабатывать проект туалета для молла в центре Москвы».

4. Сложность как следствие плохой структурированности. По большому счету нет разницы засунули ли вы 20 классов в один мегакласс или разложили по одному классу в 21 файл. Задача решается одна и та же тем же способом. Но развивать и поддерживать мегакласс сильно сложнее.

Борьба со сложностью начинается с вопросов «А зачем X нам? Что X дает?» Оказывается это очень непросто выбирать инструмент соразмерно задаче.
А еще меня всегда поражали туториалы уровня Hello world!, где первая часть это настройка IDE, окружения, деплоя, прогона тестов. А вторая часть это Hello world! и все что настроено в первой части по большому счету не используется.

Просто это туториал не по написанию hello world, а по настройке инфраструктуры. Hello world в таком туториале — это лишь способ продемонстрировать, что вся инфраструктура настроена и работает.

2. Можно сказать как следствие привычки, а можно как уменьшение сложности вхождения в проект или переключения между проектами. Вот есть у нас с пяток UI приложений разной сложности. Для каждого поддерживать уникальную структуру (и инфраструктуру, кстати) или всё же привести к одному унифицированному виду? Мы пошли по второму варианту.

Или есть фреймворки, с довольно сложной структурой файлов. При этом допускающие гибкую кастомизацию. Задача у нас простая и стандартная структура избыточна. Можем её упростить, потратив на это время. Но когда новый человек придёт на проект, нам надо будет тратить его и своё время на объяснение нашей структуры, её отличия от стандартной. Ну и если всё будет хорошо, будем тратить время на обратное усложнение. Есть ли в этом смысл?
Никита, спасибо за статью! Оно заставляет подумать, крепко подумать. Я обычно жалуюсь на наличие или чрезмерное количество фреймворков. Но ведь и в своем коде может быть больше абстракций, чем нужно конкретной задаче.

Пару месяцев назад к нам приходил программист собеседоваться. Невероятно умный. вот просто гений. он ответил на 100% каверзных вопросов про Javascript. но когда я попросил его обрисовать общую схему, как бы он простой бекенд на ноде делал, он сказал — ну как, один файл, там роутер и сразу обработку — достали из базы и вернули. я такой — а как же слои там, все дела? Он — так задачи не стояло сделать что-то расширяемое. Надо было просто решить задачу. Если там появится еще что-то — тогда типа и буду делать слои. а без необходимости — какой смысл?

Я не могу прям 100% с этим согласиться, но некоторое рациональное зерно в этом есть
Sign up to leave a comment.

Articles