Некоторое время назад мне понадобилось процитировать известное письмо Дейкстры 1968 года, и я воспользовался случаем, чтобы таки внимательно прочитать его. В наши дни "споры о goto" уже неактуальны, поскольку в большинстве современных языков команды goto либо нет вообще, либо используется она редко, и стало быть, обсуждать особо нечего. Однако мне была интересна аргументация. В нашей области масса "фольклорного знания", которое на поверку не всегда оказывается точным (что хорошо показано в книге Боссавита), так что оценить логику Дейкстры с позиции сегодняшнего дня не помешает. Надо сказать, что его формулировки не всегда легко понять, поэтому я решил изложить их несколько более простым языком, потратив немного больше места.

Письма в редакцию

Для начала отметим, что жанр "Go To Statement Considered Harmful" — это письмо в редакцию журнала Communications of the ACM. То есть перед нами не научная статья, и особых требований к строгости здесь не предъявляется. Большинство писем — это краткие сообщения об интересных находках (в том числе фрагменты кода), реакция на другие письма, сообщения об ошибках в статьях и тому подобное. Некоторые из них читать довольно забавно. Например, один автор в том же 1968 году выражает недовольство шестнадцатеричными цифрами. Дескать, если в числе нет символов A-F, то непонятно, десятичное это число или шестнадцатеричное. В качестве решения он предлагает свой собственный набор цифр, который выглядит так:

Думаю, я ещё полистаю секцию писем на досуге, но пока что давайте вернёмся к основной теме.

Вопросы пространства и времени

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

О трассировке

Теперь обсудим небольшой фрагмент кода, выполняющийся ровно в том порядке, в котором он изначально записан ("в пространстве"):

a = 5
b = 15
c = a + b # мы здесь
print(c)

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

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

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

a = 5
b = 15
c = a + b
if c == 20:
    print(c) # мы здесь
else:
    pass

Ситуация меняется, как только в программе появляются вызовы функций. Рассмотрим следующий фрагмент:

def f(a):
    c = 5 + a
    print(c) # мы здесь

f(5)
f(7)

Здесь уже недостаточно знать, что текущей инструкцией является print(). Нам нужно ещё понять, какой из двух вызовов f() сейчас выполняется. Таким образом, нам нужно располагать полным стеком вызовов.

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

Почему использовать go to плохо

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

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

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

Комментарии (32)


  1. rubinstein
    30.11.2021 12:32
    -1

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


    1. KongEnGe
      30.11.2021 12:42
      +1

      А не надо писать такие вложенные циклы, из которых требуется выход по goto.


      1. haqreu
        30.11.2021 19:22
        +3

        А как делать? Скажем, у меня чёрно-белое изображение, я могу к нему обращаться get(i,j), и мне нужно найти координаты любого белого пикселя. Чем плоха такая конструкция, и чем мне её заменить?

        for i in range(w):
          for j in range(h):
            if get(i,j): goto label
        label:


        1. poxvuibr
          30.11.2021 20:28
          +4

          Завернуть в метод и написать return


        1. rg_software Автор
          30.11.2021 20:50
          +6

          Обратите внимание, что в ответе "завернуть метод и написать return" заключается больше, чем желание просто "победить недостаток языка", который не позволяет сделать break из вложенного цикла.

          Вы хотите выйти из цикла, чтобы делать что-то ещё. А зачем?

          Functions should do one thing. They should do it well. They should do it only.

          (R. Martin. Clean Code, Ch. 3)

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


          1. haqreu
            30.11.2021 21:30
            -1

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


            1. poxvuibr
              30.11.2021 23:07
              +1

              Но не сказали, чем плоха предложенная мной конструкция.

              С моей точки зрения, ответ простой. Для того, чтобы её использовать придётся затащить в язык goto, который в большинстве случаев оказывается вредным. И по итогу легче вообще убрать goto, чем следить за тем, как его использовать.


              У goto я лично знаю два кейса удачного применения. Переход к концу метода в случае возникновения ошики, и тот, что продемонстрировали вы.


              Первое решается введением в язык эксепшнов.


              Ваш кейс, по крайней мере в джаве, будет выглядеть вот так


              label:
              for (int i = 0; i < w; i++) {
                  for (int j = 0; j < h;  j++) {
                      if (get(i,j)) {
                         break label;
                      }
                  }
              }

              goto убрали, а полезные функции раздали другим конструкциям


              1. haqreu
                30.11.2021 23:38
                +1

                Не поймите меня неправильно, я не за повсеместное использование goto. Я за то, чтобы включать голову, и писать понятный код. Любые догмы имеют пределы применения, поэтому голову и нужно включать. Я пишу почти исключтельно на C++, где goto уже есть, но за последние десять лет мне довелось его применить считанное количество раз. Тем не менее, я считаю, что в этих нескольких случаях применение было оправданным с точки зрения написания читаемого кода.


                1. charypopper
                  01.12.2021 00:11
                  +2

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


                  1. haqreu
                    01.12.2021 00:24

                    Как-то вы пессимистично настроены. Вот наш текущий проект насчитывает 850 тысяч строк кода, в нём goto применён два раза. Никто не торопится этого делать, люди умеют включать голову.

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


                1. poxvuibr
                  01.12.2021 00:24

                  Не поймите меня неправильно, я не за повсеместное использование goto.

                  Да мне кажется мы друг друга отлично поняли ))


                  Я за то, чтобы включать голову, и писать понятный код.

                  А я ещё за то, чтобы делать как сделано в джаве. То есть выделить полезные применения и сделать отдельными фичами, убрав заведомо вредные варинаты использования.


                  В случае с С++, я бы наверное стал искать какой-нибудь линтер, который разрешает делать только хорошо. Если конечно в случае с С++ его получится написать, тут я плохо разбираюсь.


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

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


                  1. haqreu
                    01.12.2021 00:39

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


              1. Tangeman
                01.12.2021 04:39

                Первое решается введением в язык эксепшнов.

                Это будет иметь смысл если только они не будут замедлять выполнение. Обработка исключений — это дорого, очень дорого, в любом языке который это поддерживает — помню я нарвался на это первый раз в .Net когда работа с сокетами выбрасывала исключения на "connection refused" (которое совсем не исключение, если подумать) — это был кошмар, и с тех пор ситуация не поменялась, даже в C++.


                Если у нас есть конструкция типа:


                как-то-так() {
                  выделить-ресурсы
                  пролог
                  цикл1() {
                    if (если-что-то-не-так) goto oops;
                  }
                  цикл2() {
                    if (если-что-то-не-так) goto oops;
                  }
                  эпилог
                oops:
                  освободить ресурсы
                }

                Причём, весь этот метод вполне может отвечать принципу "делать что-то одно" и даже быть достаточно коротким, но при этом в 50% случаев будет вылетать в oops (обрабатывая данные извне, к примеру) — если он вызывается сотни миллионов раз то исключения напрочь убьют производительность.


                Гугль хорошо описал большинство "за" и "против" и в итоге отказался от них даже в C++, ну а ядро Линукса не то чтобы напичкано goto, но совсем ими не гнушается — представьте там исключения и прочие "правильные" меры (выделение метода — это тоже может влиять на производительность).


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


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


                1. rg_software Автор
                  01.12.2021 05:07
                  +1

                  Я сам склоняюсь к "тащить и не пущать". Особенно в случае C++, где даже если вы очень грамотно ставите переход на конец функции (и в этом случае вроде бы это просто "хорошо структурированный break"), остаются проблемы, например, с выделением ресурсов, где конструктор чего-то сделал, а деструктор не сработал из-за goto и тому подобные приколы (иными словами, даже невинная конструкция типа a = b; может выделять ресурс где-то за кулисами).

                  Наверно, проще всего воспринимать ситуацию, описанную выше, как code smell. Грубо говоря, с какой радости у нас два цикла в рамках одной функции, да ещё с прыжками. Можно ведь, например, так:

                  f1() {
                      цикл1() {     
                          if (если-что-то-не-так) return false;  
                      }
                      return true;
                  }
                  
                  f2() {
                      цикл2() {     
                          if (если-что-то-не-так) return false;  
                      }
                      return true;
                  }
                  
                  как-то-так() {
                    выделить-ресурсы
                    пролог
                   
                    if (f1() && f2())
                        эпилог
                  
                     освободить ресурсы
                  }

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


                1. poxvuibr
                  01.12.2021 10:29

                  Это будет иметь смысл если только они не будут замедлять выполнение. Обработка исключений — это дорого

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


                  весь этот метод [… ] в 50% случаев будет вылетать в oops [… ] — если он вызывается сотни миллионов раз то исключения напрочь убьют производительность.

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


                  Гугль хорошо описал большинство "за" и "против" и в итоге отказался от них даже в C++

                  По ссылке, которую вы дали в явном виде озвучен вывод гугла — в случае эксепшнов "за" перевешивают "против", то есть эксепшны это скорее хорошо, чем плохо. Гугл отказался от них из-за большого количества легаси кода.


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

                  Кстати, если бы пожары и затопления случались часто — давно бы уже запретили держать дома что утюги, что стиральные машины ))


            1. rg_software Автор
              01.12.2021 04:18

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

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

              Ну вот представьте себе что-то вроде библиотеки классов Java или Qt: вас очень волнует, сколько там реально функций? Их всё равно слишком много. Что вас должно беспокоить -- так это насколько удобно в системе ориентироваться.

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


    1. amarao
      30.11.2021 13:01
      +16

      Это не тот goto, который ругал Дейкстра. Вот если у вас будет goto в середину цикла (потому что так проще), или в середину кода под if'ом вот тогда ой.


  1. saurterrs
    30.11.2021 14:32
    +3

    Не бывает плохих инструкций или паттернов. Бывает их неуместное использование =\

    Никого ведь не смущает, например, ранний выход из функции в питоне в стиле

    ```
    def hello(input):
    if input == 'no':
    return
    blah-blah-blah
    ```

    Хотя по сути это goto в конец функции


    1. rg_software Автор
      30.11.2021 14:35
      +1

      Сорри, случайно в минус ткнул. Коммент выше релевантен: это не тот goto, о котором писал Дейкстра. Ранний выход из функции не нарушает структуры. Проблема с "goto в конец" состоит в том, что никто не запретит сделать вам "не в конец", а вот с return такое не пройдёт.


      1. saurterrs
        30.11.2021 15:14
        +1

        Ну, заголовок и текст про то, что goto плохой. А не про то что, вот тут можно, а тут нельзя :)


  1. Yaris
    30.11.2021 16:28
    +2

    Перед тем, как говорить что-то про goto, стоит учитывать контекст. 1968 год - это год, когда Pascal и С ещё не было, как и многих best practices.


    1. rg_software Автор
      30.11.2021 16:40
      +2

      Да, но с другой стороны, существовал уже ALGOL 60, и велась работа над проектом ALGOL X (который станет ALGOL 68). Так что идеи о том, в какую сторону двигаться, активно обсуждались, а членами группы ALGOL были, в частности, Дейкстра и Вирт.


  1. Dr_Dash
    30.11.2021 20:28
    +1

    https://habr.com/ru/post/332664/

    Если программа с выделенными состояниями то goto в начало состояния - это ещё одна синтаксическая клнструкция


  1. mn3m0n1c_3n3m1
    30.11.2021 21:09
    +2

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

    • ограничением применимости;

    • узкой областью действия;

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

    Возьмём, к примеру return, как верно отметили в комментариях выше, это тот же goto, однако конкретнее:

    • прыжки возможны лишь в конец функции;

    • места переходов достаются с вершины стека вызовов;

    for и for-of, определяет правила игры для goto: либо счётчик, либо границы массива.

    Функции, промисы, конструкторы и методы классов: goto формирующие стек-вызовов.

    Таким образом все можно выразить с помощью goto, это такой себе jmp из мира ассемблера (где тоже есть, более мощные работающие со стеком вызовов, альтернативы:call и ret).

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

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


  1. shashurup
    01.12.2021 20:53

    Вот мне бы более интересно было бы про современные антипаттерны потереть. Например, когда относительно простую задачу, под предлогом тестабельности, или еще чего, решают размазывая логику по нескольким классам с довольно невнятной ответственностью и, от того с еще более невнятными именами, где все сделано в маленьких функциях, которые, чаще всего, делегируют свою работу кому-то еще. Распутывая этот клубок чаще всего, с удивлением, обнаруживаешь, что этого "кого-то еще" просто нет. Я понимаю, что в голове у автора была какая-то конструкция изо всех этих построений, только вот лучше бы она там и осталась :( Если пытаться беспристрастно рассуждать, то, лично для себя, я думаю, что "ужас ужас" в виде goto внутрь if'а в код размером с экран не так уж и страшен.

    А что до goto, как-то я упер у Грега Кроа-Хартмана прототип модуля ядра для управления вентилятором самсунговских ноутов через их проприетарный интерфейс SABI. Я честно решил выпилить там goto и, с немалым удивлением обнаружил, что у меня все получает более громоздко и не сказать, что более просто и понятно.

    Вообще, кстати, в линуксовом ядре goto довольно часто используется.


    1. rg_software Автор
      02.12.2021 05:14
      +1

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

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

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


      1. shashurup
        02.12.2021 10:10
        +1

        Фраза "под предлогом тестабельности" мне не близка, потому что это не "предлог", а вполне себе внятно определённое и полезное свойство архитектуры.

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

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

        Я так думаю, что "в живую" никто из здесь присутствующих такого кода и не видел. Самое старое, с чем мне доводилось работать относилось к концу 80ых и там ничего такого уже и близко не было, хотя, возможно, от реально написанного в 80ые годы кода там оставалось пару тысяч строк. Также, я думаю, несмотря на свою известность, вряд ли это высказывание характеризовало весь код своего времени. На самом деле, Дейкстре, скорее всего, не повезло оказаться среди "хороших программистов на фортране которые могут писать на фортране на любом языке", где фортран = ассемблер в котором циклы делаются с помощью тех самых goto (только условных, как правило). В том же Lisp сообществе эта проблема не могла иметь место, причем не только потому, что goto там нет. Современному разработчику, для того, чтобы начать писать "спагетти код" с goto нужно будет специально прилагать усилия. Примерно как если бы нужно было переключиться с ООП на ФП.

        А с архитектурой вида "прокладка на прокладке сидит и прокладкой погоняет" непонятно, что делать, потому что тут и решения простого нет.

        Я здесь, скорее, имею ввиду проблему, которую иллюстрирует https://github.com/EnterpriseQualityCoding/FizzBuzzEnterpriseEdition В нашей сфере и так достаточно essential complexity, чтобы не плодить еще и accidential complexity на ровном месте. (Не получается это на русский перевести адекватно)

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

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

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


        1. rg_software Автор
          02.12.2021 14:12

           На самом деле, Дейкстре, скорее всего, не повезло оказаться среди "хороших программистов на фортране которые могут писать на фортране на любом языке"

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

          (Не получается это на русский перевести адекватно)

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

           чтобы эти слои плодить, вместо того, чтобы думать в обратном направлении.

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

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

          Искусственные объекты реального мира инкапсулированы гораздо лучше. В микросхеме может быть масса транзисторов, но у меня не просто нет способа до них достучаться, но я даже могу не знать об их наличии -- чистый black box. Или какая-нибудь кофеварка: внутри масса сообщающихся механических частей, но для меня есть только отсек для зёрен, бак для воды и кнопки на панели. Даже если я залезу вовнутрь, то увижу, например, мельницу для зёрен, которая точно так же будет black-box'ом, и её внутренние части не засорят моё ментальное поле, пока я физически её не раскручу.

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


          1. shashurup
            02.12.2021 15:20

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

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

            Вот мне кажется, что в идеале софт можно было бы устроить так. Однако на практике всё равно всё торчит наружу

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

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


            1. rg_software Автор
              02.12.2021 17:33

              Именно это ограничение заставляет нас заниматься логической декомпозицией системы так, чтобы на каждом уровне все укладывалось в те самые 7 объектов 

              Да, конечно, согласен.

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

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

              Можно написать программу именно так, как мы обсуждаем: каждый каталог на диске -- это "компонент", причём в одной папке будет не более 5-7 компонентов, и каждый компонент будет общаться только с непосредственными "родителями", "потомками" и "соседями". Надо попробовать сделать что-нибудь хотя бы среднего размера в таком стиле и посмотреть, что выйдет.

              Но если я вижу подобного рода чужую программу, то начинается паника, т.к. уже догадываюсь, что буду постоянно нырять в самые глубины глубин. А про "сложно" -- ну да, the hardest problem is naming things.


  1. ladle
    03.12.2021 00:29

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

    Когда мы пишем код, мы видим его как текст, занимающий некое пространство экрана. 

    Вы уж извините, о тексте на каком таком экране могла идти речь в 1968 году?


    1. rg_software Автор
      03.12.2021 03:31
      +1

      Ну то есть листинги на Питоне вас не смутили :) Аргументы я стараюсь воспроизводить аккуратно, формулировки могут отличаться. В оригинале -- "spread out in text space".