Имитация Сложности — Антиномия Простого и Сложного

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



    День добрый. Меня зовут Никита Данилов, я ведущий .NET разработчик, при этом старательно интересуюсь как техническими нюансами .NET платформы, так и философскими аспектами ремесла программиста. Некоторое время назад я выступал на встрече SarDotNet с докладом «Имитация сложности» (Саратовское сообщество всероссийского DotNetRu), вольной расшифровкой которого хочу поделиться.



    Вступление


    Сегодня мы поговорим об имитации. Вынужден предупредить, всё что вы сейчас увидите и услышите – мысли на обдумывание, а не точные советы.


    Сложное


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


    Простое → Сложное

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


    Простое ← Имитация Сложности ← Сложное

    Малое и Большое, Простое и… Хаос


    Если говорить про сложное и сложность, то вроде и много всего написано, а всё равно я ощущаю нехватку материалов. Размышляя про код, модули или архитектуру, мы интуитивно понимаем что такое маленькое и большое. Можем примерно отличить нечто простое (как кирпич) и интуитивно понимаем когда творится хаос (с лапшой). Так или иначе, все эти термины связаны с понятием сложности.



    Вопрос: Попробуйте подумать, повспоминать и ответить на следующие вопросы:


    • Какие существуют метрики сложности кода, архитектуры или программного обеспечения?
    • Что такое «сложность» именно для вас?


    Обозначений сложности много, она многогранна, перечислим лишь наиболее известные:


    • Метрики размера программ:
      • SLOC (Source Lines of Code) – анализ количества строк кода;
      • Метрика Холстеда – анализ числа операторов и операндов программы;
    • Метрики сложности потока управления:
      • Метрика Маккейба – цикломатическая сложность (англ. Cyclomatic), или структурная (топологическая);
      • Метрика Майерса – расширение метрики Маккейба с интервальным представлением значения метрики;
      • Метрика Вудварда – Хенела и Хидлея, анализ точек пересечения передачи управления;
      • Метрика Джилба – анализ насыщенности выражениями типа IF-THEN-ELSE;
    • Метрики сложности потока данных:
      • Метрика Чепина и её модификации – анализ характера использования переменных ввода-вывода;
      • Метрика Спена – анализ обращений к данным внутри каждой программной секции;
    • Метрики специфичные для объектно-ориентированного программирования:
      • NOC (Number of Children) – анализ количества непосредственных потомков;
      • DIT (Depth of Inheritance) – анализ глубины дерева наследования;
      • WMC (Weighted Methods per Class) – анализ суммарной сложности всех методов класса;
      • CBO (Coupling Between Classes) – анализ связности классов и методов;
      • LCOM (Lack of Cohesion of Methods) – анализ сцепленности классов и методов;
    • и многие-многие другие.

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


    Словари определяют, что само слово «сложный» является антонимом к слову «простой» и имеет несколько устоявшихся значений:


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

    Если пристально вглядеться, в нашем мире и правда много сложного. Как нам думается. Навскидку, выписал слова, которые крутились в голове – ГОСТ, финансы, LESS, физика, Docker, ООП, Шаблоны проектирования, Опционы, АЭС и т.д. (см. КДПВ).
    Хотя зачастую мы сами явно не упрощаем. Именно в IT-индустрии многие могут знать не понаслышке про такие проекты, где собиралась огромная бригада аналитиков и менеджеров, приносились фреймворки и Blue-Green развертывание, event-driven микросервисы и реактивные микрофронтенды. И вот спустя полгода человек жонглирует 30 серверами пытаясь понять в каком месте оно не работает. А нужна была лишь система обработки заказов, с пиковой нагрузкой в 100 пользователей.


    Имитация Сложности


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


    На мой взгляд, для описания подобной ситуации подходит слово «имитация» имеющее древние корни:


    • imitari (праиндоевр.) — подражать, воспроизводить.
    • imitatio (лат.) — подражание.

    Когда мы имитируем сложность, у нас её еще нет, но мы решили попробовать ей подражать. Это промежуточная серая область, когда разрабатываемое решение уже нельзя считать простым, однако и сложность пока выглядит сомнительно либо неуместно. Позвольте объяснить точнее, рассмотрим на условном примере.


    Простое ← Имитация Сложности ← Сложное

    Вам передают приложение, разработанное кем то иным, допустим REST API. Вы открываете и видите N проектов: контракты, ядро, данные, веб, фасады и т.д. Богато выглядит, думаете вы, наверняка оно решает нечто важное.



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


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


    Постепенно вы добираетесь до проекта Web, думается там вся соль! Ан нет, там только один контроллер и стандартная конфигурация.



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


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


    Универсальный ответ — «зависит», ведь это зависит от контекста, от поставленной задачи и чего хотели достичь. Возможно это всё будет ещё расширяться, возможно нет, но если вдруг будет, приложение готово к расширению. Разумеется, я выступаю за продуманную организацию кода, а с другой стороны, вроде всё это упаковывается в 2 проекта и несколько файлов, на первое время.


    Рассмотрим другой пример.


    Вы создаете новое приложение, допустим, REST API. Решив начать всё с контроллера, вы описали контроллер, описали сущности контракта.



    И ощущаете, вроде маловато. Надо еще ведь модульные тесты написать — создаете аксессор (и/или: репозиторий, UnitOfWork, DB-контекст, ...).



    Потом идете на кухню попить чайку, открываете книгу про шаблоны проектирования, посещаете конференцию где вам рассказывают еще 3 истории успеха внедрения DDD и CQRS, т.е. вы где-то что-то зачем-то узнаете. Не успеваете оглянуться, вжух – кембрийский взрыв. В приложении появляются останки всех известных слов для выражения зависимостей:



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


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

    Вопрос: Насколько оно обосновано?


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


    Мне доводилось самому делать такие приложения как показал на предыдущих слайдах – сразу всё по полочкам, с горой терминов. Потом вернулся через 3 года и выкинул много лишнего, что так и не пригодилось.


    Серая область имитации сложности довольно велика и относительна. Но чтобы её лучше понять надо сначала осознать простое и сравнить сложное с простым.


    Простое


    Простое ← Имитация Сложности ← Сложное

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



    1. Проще → Быстрее Разработка


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


    Безусловно, выбор решения во многом зависит от решаемой проблемы и поставленной задачи.
    Сани, машина, ракета — всё из этого позволяет добраться из пункта А в пункт Б.



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


    Другой пример это когда вам необходимо доплыть до пункта Б.



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


    2. Проще → Надежнее Работа


    Во-вторых, простое решение обеспечивает более надежную работу. Это заявление может выглядеть более спорным, а потому позвольте объяснить подробнее. Обратим наше внимание на названые ранее метрики сложности. Значение любой из них так или иначе прямо пропорционально числу «подвижных» частей нашего решения. Если мы входим в зону имитации сложности, мы начинаем добавлять код, вряд ли убавлять.


    Вопрос: Какова вероятность ошибки или опечатки в строке кода? 1% или 0.1% или 0.01%?


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



    Причем, исправление одной ошибки уменьшит вероятность следующей лишь незначительно, ведь эти события являются практически независимыми. Допустим мы пишем код идеально. Уменьшим вероятность еще на 2 знака, получим вероятность ошибки 1% на 10 000 строк кода. А потом вспоминаем, что у нас минимум 100 взаимосвязей или разных зависимостей между классами...



    Продолжать можно бесконечно, хоть и к точности подсчета можно придраться, суть останется прежней: «Больше последовательных звеньев — ниже надежность». Сложность проверки превосходит количество строк кода в миллионы раз. Поэтому, чем меньше кода в вашем решении, тем выше вероятность надежной работы. Обычно.


    3. Проще → Понятнее для Поддержки


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


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


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



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


        class Controller
        {
            Controller(IManager, ILogger, ISettings) { … }
    
            Load()
            {
                …
                var data = _manager.Load(...);
                …
            }
        }

    Идем дальше. Менеджер вроде тоже вполне прозрачный, зависимости какие-то, но параметры перекомпоновывает, поэтому надо разобраться как именно. Разобрались, идём дальше.


        class Manager
        {
            Manager(IFacade, ILogger, IChecker, IWrapper) { … }
    
            Load()
            {
                …
                var data = _facade.Load(...);
                …
            }
        }

    Открываем фасад, там еще порция зависимостей. Перегруппировка параметров и вызов дальше аксессора.


        class Facade
        {
            Facade(IAccessor, ILogger, IPermission, IPhone) { … }
    
            Load()
            {
                …
                var data = _accessor.Load(...);
                …
            }
        }

    Открываем аксессор, нет зависимостей – ура! Подождите ка...


        class Accessor
        {
            Accessor() { }
    
            Load(...)
            {
                NLog.Instance.Write(…);
                var context = new DbContext("connectionString");
                var data = context.Entities.ToList();
            }
        }

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


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


    Так почему не делать всё Просто?


    Итого, простота ведет к Скорости (уменьшению затрачиваемого времени), к Надежности, к Поддерживаемости. Казалось бы,


    «Обалдеть, дайте две!» ©

    Замечательно, давайте делать всё максимально просто, чего я вообще тут про имитацию распинаюсь. Почему мы не делаем всё Просто? Разумеется, указанные параметры здесь встают в позу знаменитой тройки: лебедь, рак и щука.



    (источник, автор: Анна Тимакова, Город: Москва, возраст: 7 лет)


    К сожалению, реально полноценно совместить лишь 2 аспекта, а 3-й обязательно будет страдать. Примерно как CAP теорема, или Тройственная ограниченность проекта, только про другое (очень уж людям нравится треугольники). Нам придется делать выбор, а выбор обусловлен целью и задачами, как мы помним. Решаемая задача каждый раз особая, где для одной допустимо простое решение, для другой придется усложнять. В одном случае топорное решение обеспечит страдания при поддержке, а в другом наоборот позволит быстрее переписать при необходимости.


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


    Кто виноват? Что делать?


    Попробуем подумать, зачем мы имитируем?


    • Крутизна, хотим чтобы выглядело круто, посложнее, солиднее, богаче, изучить новый подход.
    • Изучение, пробуем сделать посложнее дабы изучить новый подсмотренный подход.
    • Демонстрация, показать что мы это умеем.
    • Вдохновение, попробовать нечто новое и крышесносящее, подслушанное на конференции.
    • Опасения, желаем чтобы приняли диплом.
    • Предусмотрительность, стараемся добиться слабой связанности модулей для последующих изменений.
    • Гибкость, волшебное слово «гибкость», чтобы было гибко, конфигурируемо, мы же сейчас всё сделаем так, что потом поменяем два контракта и всё будет работать как раньше.
    • Поддерживаемость, стараемся избежать технического долга.
    • и т.д. и т.п.

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


    Мне знакомы ситуации, когда люди привносили сложность т.к. им было стыдно показать простое решение. Они выходили на кухню, там все рассказывали про сложные шаблоны и рождалась мысль: «Как же я свои 2 класса покажу, это же всё не по книгам».


    Часто можно услышать и другую универсальную причину — сделаем гибче, еще гибче, на будущее. Мы многое можем спрятать под общими словами, не желая признать собственное непонимание решаемой задачи.


    Не призываю совсем избегать сложности, ведь она бывает оправдана, да и зачастую она оправдана. Всё-таки мы проблемы Бизнеса решаем, а не лямбдами балуемся.
    Не призываю создавать только примитивные решения, ведь действительно с ними потом можно получить множество проблем при необходимости внести изменение. Если строить лишь телеги, то в космос не полететь. Хотя если строить лишь космические корабли, то и саночек не останется.


    Многое в этом мире автоматизировано и успешно функционирует с использованием относительно простых решений. Признаю проблематичность подтверждения пруфами данного утверждения, оно основано скорее на личном общении. Можно вспомнить доклад Дилана Битти «Ctrl-Alt-Del: учимся любить легаси-код», где Excel упоминается как самая популярная в истории платформа для разработки коммерческих продуктов, а рядом бродит призрак Visual Basic. Знаю небольшие магазины, где долгие годы люди успешно работают с примитивнейшим полу-консольным интерфейсом кассовых аппаратов.


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


    «Всё следует упрощать до тех пор, пока это возможно, но не более того» © Альберт Эйнштейн

    Упростив постановку проблемы и предлагаемое решение, вы получите пространство для маневра.
    Если вы осознаете необходимость восхитительно гибкого механизма — прекрасно, вы эксперт, дерзайте.
    Если вы хотите именно здесь опробовать новый подход и он не принесёт страданий — замечательно, пробуйте.
    Если же причин усложнять нет, а вас смущают лишь субъективные нерабочие факторы, разве это повод обеспечить трудности будущему себе?


    Простое <—> Имитация Сложности <—> Сложное

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


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


    Благодарю за внимание, до новых встреч.


    Ссылки



    DotNetRu


    DotNetRu — группа независимых сообществ .NET разработчиков со всей России. Мы объединяем людей вокруг .NET платформы, чтобы способствовать обмену опытом и знания. Проводим регулярные встречи, чтобы делиться новостями и лучшими практиками в разработке программных продуктов.


    Мы стараемся объединить всех .NET разработчиков России, стать тем метасообществом, где можно посмотреть видео всех докладов, почитать новости, поделиться своими знаниями или мнением на ту или иную тему. В 2018 году нас было 4 города, а через два года уже 13! Это настоящее объединение русскоговорящих .NET сообществ.


    DotNetRu ставит перед собой следующие цели:


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

    Мы в телеграмме: новостной канал DotNetRu и обсуждение насущных вопросов DotNetRuChat.


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

    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 26

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

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

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

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

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

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

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

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


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

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

        В этом случае вероятность ошибки будет не 100%, а примерно 63%
          –5

          Нет, шанс ошибки в таком коде будет практически 100%, если сделать предположение, что вероятность ошибки на строке независима от каких-либо факторов.

            +4
            А если 20 000 строк кода, то 200%? Шанс 100% не будет никогда по определению вероятности.
              –3

              Во-первых, я никогда не говорил, что вероятность будет в точности 100%; я сказал, что будет практически 100%. А, во-вторых, это по какому такому определению не может быть 100% вероятности?


              А, в-третьих, я ошибся в своих расчетах. Но ошибка была бы около 100% для кода длинной в 100,000 строк.

                +2

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


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

              +5
              Вероятность отсутствия ошибки в одной строке кода: 99.99%.
              Вероятность отсутствия ошибок во всех строках кода: 99.99%^10000=36.8%.
              Вероятность наличия хотя бы одной ошибки во всех строках кода: 100%-36.8%=63.2%.
                –6
                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%.
                  +2
                  У вас неверное представление о том, как работает теория вероятности.

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

                  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. Хотя бы в одной строке кода есть ошибка.

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

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

                      +3

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

                +1

                Просто написать сложно.

                  +1

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


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


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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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


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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

                              Я не могу прям 100% с этим согласиться, но некоторое рациональное зерно в этом есть

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

                              Самое читаемое