Сложность должна обитать где-то

Original author: Fred Hebert
  • Translation

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


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


Есть одна вещь из дисциплины построения устойчивых систем (resilience engineering), которая научила меня концепции необходимой вариативности (requisite variety) из кибернетики: только сложность может держать под контролем другую сложность.


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


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

И этого никак не избежать. Сложность должна обитать где-то. Она всегда сопровождает разработчиков в процессе решения проблем, независимо от того, осознаёте вы это или нет.


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


Они создают временные решения, когда видят несоответствия между парой конфликтующих концепций. Эта необходимая сложность может быть перемещена куда-то — назад в инструмент (или новый инструмент) — или выдавлена путём реконструкции частей. Каждое такое структурное изменение требует определённых усилий и дополнительных подгонок для того, чтобы люди увидели сложность, поняли сложность и научились справляться со сложностью. И в некоторых случаях сделанные изменения не упрощают сущности, они усложняют их создавая новые несоответствия между представлениями в головах разных людей, и это несоответствие порождает новые обёртки. Непреднамеренная сложность (accidental complexity) является просто необходимой сложностью (essential complexity), в которой проявился её возраст. Сложность невозможно избежать, она всё время меняется. Сложность должна обитать где-то.


В книге Дизайн привычных вещей, Дон Норман упоминает о концепциях "Внутренних знаний" (Knowledge in the head) и "Внешней информации" (Knowledge in the world) (подобные концепции в академическом исследовании представлены в работе Roesler & Woods Designing for Expertise). Внутренние знания — это факты и связи между ними, которые вы узнали, изучили и которые находятся в вашей памяти. Внешняя информация — это всё остальное: информация в виде текста, знаки в дизайне (вы узнаёте кнопку включения питания просто смотря на её символ и знаете, что она может быть нажата, поскольку выглядит как кнопка) и прочее. Одна нетривиальная вещь состоит в том, что интерпретация внешней информации зависит от культуры контекста, и, вдобавок, она основана на внутренних знаниях (вы знаете, что кнопка включения питания может быть нажата потому, что вы вообще знаете о кнопке в принципе).


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



Типичная ловушка, с которой мы сталкиваемся в процессе проектирования ПО, проявляется в фокусировании на том, насколько "простым" является для нас чтение и понимание конкретного фрагмента кода. Однако фокусирование на простоте чревато множеством затруднений, поскольку сложность не может быть удалена: она может быть только перемещена. Если вы её перемещаете из своего кода, куда она тогда денется?


Когда мы проектировали Rebar3, мы ощущали что инструмент может быть сделан простым в использовании. Отправной точкой для этой простоты являлось общее соглашение о предполагаемой структуре проектов на Erlang/OTP. Пока вы следуете этим правилам, всё отлично работает. Мы вынесли некоторую часть сложности на внешний уровень. Правила всегда были бы изучены (как мы думали), но теперь инструмент зависел от понимания и принятия всеми этих правил. В погоне за упрощением использования инструмента для разработчиков уже освоивших эти правила, мы усложнили использование для новичков и тех, кто ещё находился в процессе изучения. Другие инструменты самого разного назначения в других экосистемах — подобно нам — также выбирают свои компромиссы.


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


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


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


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

AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 82

    +6
    Если под «сложностью» понимать именно сложность присущую самой задаче (inherent complexity), то, безусловно, такую сложность надо распределять между абстракциями, где абстракции более высокого уровня полагаются на корректную реализацию соответствующего низкоуровнего API.
    Когда речь идёт об упрощении, часто имеют в виду так называемую accidental complexity — то есть искусственно привнесённая сложность в силу разного рода причини: недостаток знаний технологии, сжатые сроки и.т.д. Эти «сложности» могут накапливаться, не принося никакого нового функционала продукту. Их пытаются описать в терминах технического долга и убедить владельцев продукта на выделение времени на их устранение (рефакторинг).
      +2

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


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

        0

        Есть два способа борьбы со сложностью — инновации и переиспользвание. На днях переписал свой старый пет проект на современный лад и закрыл несколько ишью на гитхабе. Бандл по объему уменьшился раз в пять.


        Благодаря инновациям мир с каждым днём становится стандартнее и проще, превращая когда-то essential complexity в accidental. А с этим бороться все умеют. Нужно лишь периодически собираться силой воли и устраивать генеральную уборку.

          +5
          является обычной необходимой сложностью

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

            0

            Я правильно понимаю, что вы считаете, что римские числа обладали некоей сложностью, а при переходе к арабским позиционным числам часть сложности просто исчезла?

              +8

              Работа с римскими числами имела лишнюю сложность, потому что они неудачно сделаны.


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

                +3
                «Здесь не всё так однозначно»(с).
                Римские числа неудачно сделаны для одной задачи — для умножения, но зато удачно — для другой: для быстрой оценки порядка величины числа.
                В римской записи порядок виден сразу (если сначала идет M, то сразу понятно что порядок величины — тысячи), а в позиционной для надо посчитать цифры (понимаю, что нам до четырех посчитать несложно — мы привычные, но тем не менее).
                  0

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

                  +2

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


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


                  В итоге решение получилось более сложным, чем могло быть.

                    +5
                    если сначала идет M, то сразу понятно что порядок величины — тысячи

                    Верно. А если C — то сотни. Например, CMXXXVII — сразу видно, что порядок около сотни, не так ли?


                    На самом деле нет, это 937. То есть даже вашу удачную задачу римские цифры решают так себе, кое-как.

                      +1
                      Формально говоря, 937 — это сотни. Меньше одной тысячи ведь.
                        –1

                        Формально говоря, десятичный логарифм 937 округляется до трёх, а не до двух.

                          0
                          А кто говорит про логарифм и округление? Есть два слова: «сотни» и «тысячи». Вы считаете, что число 937 лучше характеризует второе?
                            +1

                            Да.

                              +1
                              Занятно.
                          +1

                          Если вам нужно узнать, число меньше тысячи или нет, то вы правы. 999 тоже меньше тысячи.


                          На практике, оно из частых использований чисел — это цены. Вы согласитесь с фразой "это стоит несколько сотен" при цене 937? Или если скажут "они примерно одинаковые по цене, CXXXVII и CMXXXVII". Так что для нормальной оценки вам совершенно точно не хватит первой цифры.

                            +1
                            Я смотрю, тут никто не заметил, что дается определение во множественном числе. Да, 999 — это ближе к тысяче, а не к сотне. Но это же число ближе к сотням, чем к тысячам. Ну да ладно, естественные языки никогда не были абсолютно точными, чтобы всерьез спорить об этом.
                              +1

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


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

                        0
                        Для арабских цифр придумали хак для быстрой оценки порядка — экспоненциальную форму записи числа. И тут вроде умножается хорошо и порядок виден, но чтобы сложить — числа нужно нормализовать.
                        Получается есть сохраняющаяся сложность — это полезная информация. А сложность, «которую невозможно оправдать», — это помеха. И методики чистки кода пытаются снизить уровень «помехи». Неудачное применение методик может привести не к снижению уровня помехи, а просто к преобразованию, в лучшем случае, «помехи» из одного вида в другой.
                        0

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


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


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


                        Но всегда есть нижняя граница сложности, ниже которой мы не опустимся. Поэтому её (сложность) нужно будет куда-то растолкать. Как-то так.

                          +2
                          школьники до 4-го класса учатся делать простые арифметические операции

                          Вы утверждаете, что третьекласснику научиться складывать римские цифры будет проще, чем арабские? Римские цифры — это не просто палочки пересчитать, там есть ещё пара правил, не таких уж простых для третьеклассника. XXIV + XXI простым подсчетом палочек не посчитаешь.


                          бесконечно упрощать мы всё равно не сможем

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


                          локальный минимум энтропии

                          +1. Если вы уверены, что текущий код более-менее близок к локальному минимуму, тогда пора распределять сложность. Но не ранее.

                        –4
                        Новички часто могут писать излишне сложно, например
                        if (some == true)

                        if (length(str) > 0)

                        вместо
                        if (some)

                        if (str != "")


                        Римские цифры из той же серии. Неудачное решение, которое можно заменить на простое, не перераспределяя сложность куда-то ещё.
                          +4
                          если в си кто-то напишет
                          if (str != "")

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

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

                              0
                              иногда напротив человека который слишком часто обжигался.
                              Ага, обжигался, и вместо того, чтобы разобраться с причинами, нашёл работающий костыль, более сложный.
                                +3
                                костыль понятный людям из любого языка, без оглядки на бэкграунд. Примерно как наследние си/си++ заставляет людей писать
                                if 2 == var 

                                вместо казалось бы более логичной
                                if var == 2

                                просто чтобы случайно в условии не совершить ошибку и не присвоить значение вместо сравнения.
                                  0
                                  Тем не менее, это ли не пример привнесённой сложности?
                                  0

                                  Length — это сложный костыль? Нет никакого преимущества в записи str != "". Есть желание, пишите так, не проблема. Но и пользы нет.

                                    0
                                    Синтаксическое дерево сложнее (больше операций) — значит, труднее читать код человеку. Умный компилятор, конечно, может считать, что это одно и то же.
                                      +1

                                      Вариативность больше. Первая мысль глядя на != "" — а сравнение тут по значению или по ссылке/указателю? Не явно как-то.

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

                                          Это если долго варишься только в нём, а не переключаешься только за день несколько раз.

                                        +1

                                        Вы мне, как человеку, говорите, что человеку труднее читать length? Ожидаете, что я могу согласиться даже несмотря на то, что мне легче?


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

                                          –3
                                          Сложнее, как минимум потому, что вопрос не только в вызове функции length, но и в значении, с которым сравниваем.
                                          Может, там написано
                                          if (length(str) > 10)
                                          и читатель, привыкнув к паттерну
                                          if (length(str) > 0)
                                          бегло бросив взгляд на начало строки решит, что это проверка на непустую строку
                                            0
                                            Поэтому лучше писать:
                                            if (!string.isEmpty(str))
                                            или даже:
                                            if (string.isNotEmpty(str))
                                            А еще лучше, если есть экстеншены/френдли функции/etc., и можно написать:
                                            if (str.isNotEmpty())
                        +2

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

                          +6

                          Зато в дереве сам элемент стал сложнее — ему уже нужно иметь два потомка (в случае BST).


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

                          Так что не видно понижения одновременно требуемой сложности. Она снова перераспределилась!

                            0
                            Да, ещё добавилась сложность понимания метафор ;-)
                              +1

                              Эээ, так это метафора была? :-)

                                +1

                                Ну да. Я сравнивал сложность для человека (кучу всего, что нужно держать в голове) со сложностью для компьютера (все эти O(n)). А вы восприняли в лоб, как сложность для человека (которая, очевидно, возросла).

                                  0

                                  Понял, принял.

                          0
                          Вроде как нашли костыль для сложности — микросервисы. Каждый микросервис делает свое дело (API вызов) и является объектом познания без изучения кода.
                          Таким образом сложность из кода (уровень программиста) вытекает выше на уровень бизнес-аналитика, который оперирует теми самыми API вызовами.
                            +11

                            Вся сложность разработки микросервисов уехала из отдельных сервисов на уровень "заставить всё это работать вместе".
                            Существенно усложнилось тестирование (теперь оно практически всё интеграционное), усложнились протоколы обмена, стало трудно получить целостность данных (см. raft/paxos и прочее веселье), усложнилось развёртывание. Что отлично подтверждает тезис о том, что сложность никуда не девается, а она просто перераспределяется

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

                                Это позволяет интерпретатору в лице человека при очевидных ограничениях на предел сложности в осознании объекта (т.н. «предел Миллера»), не уходить в leaky abstraction, то есть обходить затруднительный момент с difference в качестве мета-информации при навигации между слоями абстракции в системах. Иными словами равномерное распределение сложности становится свойством самой архитектуры с точки зрения интерпретатора — выделение сложности в отдельный домен (в структурный паттерн).
                                +4
                                А еще раньше:

                                Вроде как нашли костыль для сложности — функция. Каждая функция делает свое дело (API вызов) и является объектом познания без изучения кода.
                                Таким образом сложность из кода (уровень программиста) вытекает выше на уровень бизнес-аналитика, который оперирует теми самыми API вызовами.

                                И не благодарите ;-)
                                  0

                                  Спасибо, осталось только добавить, что в функциональных языках все проблемы решены, ведь там всё на функциях, поэтому все бегом туда!

                                    0
                                    Каждая функция делает свое дело (API вызов) и является объектом познания без изучения кода.

                                    А, может быть, она таковой является только потому, что мы когда-то изучили смысл слов на нашем разговорном языке?) По названию функции предполагаем что она делает. Сложность теперь там..)
                                      0
                                      А, может быть, она таковой является только потому, что мы когда-то изучили смысл слов на нашем разговорном языке?) По названию функции предполагаем что она делает. Сложность теперь там..)


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

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

                                      Kotlin
                                      fun <T : Comparable<T>> Iterable<T>.sorted(): List<T>
                                      

                                      Rust
                                      impl<T> [T]
                                      
                                      pub fn sort(&mut self) where T: Ord
                                      

                                      Swift (docs github)
                                      extension Sequence where Element: Comparable {
                                        public func sorted() -> [Element]
                                      }
                                      


                                      Все три языка явно указывают и проверяют, что для элементов последовательности должна быть реализована функция сравнения, иначе не получится их отсортировать.
                                  +1
                                  Да все просто. Делай так чтобы ты сам понял через n-лет.
                                  Но обычно делают «я самый крутой я помню как устроен проект»
                                    0

                                    А вот это, кстати, как раз отчасти перераспределение сложности. С себя завтрашнего на себя сегодняшнего.

                                    0
                                    Все правильно и все по делу, кроме главного — как с этим реально жить? Главное средство борьбы со сложностью — это архитектура. Уметь перемещаться на разные уровни рассмотрения со сложностью дает возможность ею управлять — так, в микросервисной архитектуре сложность уходит во взаимодействие сервисов — соответственно выстраивание взаимодействия соответственно домену позволяет схему эту копировать с уровня бизнес-требований — т.е. пользоваться уже готовой бизнес-моделью, сколь сложной она ни была бы.
                                      0

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

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

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

                                        +3

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


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


                                        То же самое относится к физике: до того, как Ньютон открыл «простейший» закон «F = m * a», любые расчеты траекторий движения тел (в основном, я полагаю, людей тогда интересовало движение ядер из пушек или требушетов :)) были очень сложными и силы вроде «силы инерции», «силы тяготения», «сила сопротивления воздуха» и прочих считались силами разной природы и комбинировать их и пытаться что-то посчитать было делом очень сложным и порождало много неточностей.


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


                                        Я на своем опыте видел очень мало случаев, когда сложность какой-то проблемы действительно была обусловлена естественной сложностью проблемы: почти всегда программисты/инженеры создают себе преграды для решения своих задач сами, тот самый «оверинжиниринг». Но даже в случаях, когда используются общепринятые подходы для решения какой-то задачи (например, деплой кода PHP-приложения — куда уж проще?), зачастую эти подходы вас ограничивают от нахождения решения, которое одновременно и проще и решает вашу задачу — и вот это как раз то место, «куда девается сложность» — это не сложность нельзя убрать, это от энтропии не избавиться :).


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


                                        Так что давайте прекращать говорить в терминах accidential complexity и essential complexity. Почти всегда систему можно упростить ещё дальше, чем, казалось бы, требует задача, и получить ещё больше гибкости и расширить границы применимости системы в итоге. Именно к этому нужно стремиться, когда вы создаете платформу для чего-либо, и не жалеть сил на работу над ней, потому что в итоге вы освободите ресурсы мозга тем, кто будет этой платформой пользоваться и они смогут решать более сложные задачи, чем до этого, причём ценой меньших усилий.

                                          0
                                          То есть, чтобы решить задачу «просто», нужно построить платформу для решения подобного класса задач (в примере с умножением — изобрести позиционную систему счисления).

                                          И когда на разработчика сваливается реальная задача, он должен сначала построить фреймворк и DSL (или найти подходящие), а потом решить задачу «просто», так? Я думаю, в сумме выйдет дороже, поэтому заказчики за это не платят.
                                            0

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


                                            Я скорее говорю про то, что зачастую сложности вообще не должно быть, и что она возникает лишь от нежелания упрощать систему / либо что упрощение является слишком трудоемким. Безусловно, упрощением системы скорее всего будут заниматься наиболее опытные коллеги. К сожалению, не могу найти источник, но мне нравится такое определение junior/middle/etc:


                                            Если у вас есть какая-то проблема и её поручили решить X, то вот, что будет на разных уровнях:


                                            • Junior: если проблема будет решена, то вероятно это будет переусложненное решение, полное багов
                                            • Middle: проблема решена в срок, всё работает, но не более того
                                            • Senior: проблема решена, и решение простое, быстро и элегантно
                                            • Godlike: проблема просто исчезла

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

                                              0
                                              В такой классификации, сеньоров не существует.
                                              Потому что есть задачи без быстрого и элегантного решения.
                                              Например, бек-энд компилятора. Или автоматизация бизнес-процесса с сотнями рабочих мест и десятками ролей.
                                              +1

                                              Поэтому стоит пользоватьс языками, где подобный фреймворк/DSL можно сделать попенсорсно, тогда если 100 Петь вложат половину времени, сколько потратили бы на задачу, каждый из них получит инструмент, который позволяет им всем решить их задачи "просто", да ещё и помогает неограниченному кругу других лиц.

                                                0

                                                LISP :)?

                                                  +1

                                                  Вы думаете, стоит открывать этот ящик Пандоры?

                                                  0
                                                  100 Петь вложат половину времени
                                                  Они друг с другом не договорятся. Каждый видит по-своему и будет пилить в свою сторону. А когда в репозиториях свободно лежит 100 решений, интереснее делать своё, чем изучать и выбирать.
                                                    0

                                                    Ну как-то же делают. Вон attoparsec/servant/diesel/r2d2/serde/… есть же, тысячи их.


                                                    Или более абстрактные: monix/zio/..., да хоть те же Akka/Spark

                                                      0
                                                      И что с этими тысячами делать разработчику? Потратить 10000 часов на изучение, чтобы в нужный момент вспомнить и применить? А за это заплатят? Заплатят за 1 час, «ну вы же на типовом решении сделали, написав 2 страницы кода».
                                                        0

                                                        Мм, чтобы использовать акку можно потратить десяток часов и сэкономить 100. То же и с другими либами.

                                                          +1
                                                          Я пытаюсь донести, что проблема в том, что существующих либ много, а применяться будет мало. Чтобы понять, что программисту нужно, и какая либа лучше, их придётся изучить все, а это впустую потраченное время.
                                                            0

                                                            мм, нет, обычно библиотеку можно найти просто по тегам или описанию в гугле, по совету в чатике… А в более крутых языках можно просто загуглить сгинатуру функции которая нужна и выдача предоставит нужные библиотеки. Например, пишем a -> ByteString и находим библиотеки по сериализации в JSON и другие текстовые форматы.

                                                              +2

                                                              И первую попавшуюся брать или всё же изучить каждую, сравнить и т. п.?

                                                                0

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


                                                                Тем более, что обычно основные либы все уже знают: чем хостить сервер, чем преобразовывать в JSON/протобаф, чем ходить в БД, чем писать логи… 99% зависимостй описано, остается специфика из разряда "Хотим хитрые ретраи" и "интегрируемся с редким сервисом".

                                                                  0
                                                                  Либы это хорошо. Но изначально речь шла о более высокоуровневых задачах. Те же связки GUI+CRUD переписывают по 100500 раз, возможно и на либах.
                                                                –2
                                                                Это похоже на SO-driven development.
                                                                Гуглим по вершкам, что нашли, то и тащим в проект.
                                                    +1
                                                    И когда на разработчика сваливается реальная задача, он должен сначала построить фреймворк и DSL (или найти подходящие), а потом решить задачу «просто», так?
                                                    Зависит от количества задач. Если подобные задачи ожидаются регулярно и в большом количестве, то вполне логично потратить время и силы на создание универсального инструмента для упрощения их последующего решения.
                                                    Тут как раз и будет уменьшение общей сложности, за счет того что она вынесена в инструмент, а множество однотипных задач решаются просто.
                                                • UFO just landed and posted this here
                                                    +3

                                                    М?


                                                    (Там первая строка является ссылкой)

                                                      +1
                                                      Пост содержит зерно истины, но написано не убедительно. И уж совсем невнятные примеры.
                                                      Сложности бывают разные. И от некоторых из них действительно можно избавиться. А некоторые из них являются следствием бизнес-логики.
                                                      Можно было бы начать с какого-нибудь всем известного примера. Например с такого языка
                                                      Целью разработки было создание простого непроцедурного языка, которым мог воспользоваться любой пользователь, даже не имеющий навыков программирования.
                                                        0

                                                        Это про тот, про который не стихают холивары унжно ли переносить логику в его процедуры? :)

                                                          +1

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


                                                          Например, мне довелось работать над data-flow языком, который выражается диаграммами:


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

                                                          И так далее. Всё в точности, как в статье.

                                                          0
                                                          Удивительно, что никто не упомянул индусский_код.

                                                        Only users with full accounts can post comments. Log in, please.