Эдсгер Дейкстра: «Грязно и быстро — мне это не понравится»

«Чтобы иметь право называть себя профессионалом, вы должны писать чистый код. Нет никаких разумных оправданий тому, чтобы не стремиться к лучшему». Clean Code

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

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

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

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

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

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

TLDR


В этом эссе я расскажу о трёх «грязных» практиках кодинга:

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

Я люблю большие функции


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

Это противоречит принципу чистого кода, который гласит:

«Первое правило функций — они должны быть маленькими. Второе правило функций — они должны быть ещё меньше».

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

  • Несколько крупных «базовых» функций, настоящее «мясо» модуля. Я не ограничиваю количество строк кода (LOC) этих функций, но начинаю ощущать дискомфорт, когда они становятся длиннее примерно 200-300 LOC.
  • Приличное количество «поддерживающих» функций в пределах 10-20 LOC.
  • Приличное количество «вспомогательных» функций в пределах 5-10 LOC.

В качестве примера «базовой» функции рассмотрим issueAjaxRequest() из проекта htmx. Эта функция состоит почти из 400 строк!

Её точно нельзя считать «чистой»!

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

▍ Важные вещи должны быть большими


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

Давайте взглянем на визуальную схему сравнения «чистого» и «грязного» кода:

Три категории функций: важные (красные), средней важности (оранжевые) и неважные (зелёные)

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

Всё начинает выглядеть одинаково: определение сигнатуры функции, за которым следует конструкция if или цикл for, возможно, вызовы одной-двух функций, и возврат.

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

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

Если мне нужно знакомиться с новым модулем, я предпочитаю, чтобы он был «грязным»: я быстрее пойму его, и мне проще будет запоминать важные части.

▍ Эмпирическое доказательство


А как насчёт эмпирических (пугающее слово в разработке ПО!) доказательств идеального размера функций?

В разделе 4 главы 7 книги «Совершенный код» Стив Макконнелл приводит доводы за и против больших функций. Результаты спорные, но во многих из цитируемых им исследований метрика «ошибки на строку» лучше для больших функций, нежели для маленьких.

Есть и более новые исследования, свидетельствующие в пользу маленьких функций (<24 LOC), но упор в них делается на то, что исследователи называют «предрасположенность к изменениям». Что же касается багов, то исследователи говорят следующее:

Корреляции между SLOC и предрасположенностью к багам (то есть #BuggyCommits) существенно ниже, чем четыре индикатора предрасположенности к изменениям.

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

▍ Реальные примеры


А как насчёт примеров из реального сложного и успешного ПО?

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

Или рассмотрим функцию ChromeContentRendererClient::RenderFrameCreated()
из веб-браузера Google Chrome. Похоже, в ней тоже больше 200 LOC. Покопавшись в кодовой базе, мы тоже найдём множество других длинных функций. Chrome решает одну из самых сложных задач в мире ПО: он стал хорошим гипермедиа-клиентом общего назначения. Тем не менее, его код не кажется мне особо «чистым».

Далее взглянем на функцию kvstoreScan() из Redis. Она меньше, порядка 40 LOC, но всё равно намного больше, чем рекомендует чистый код. Бегло просмотрев кодовую базу Redis, мы сможем найти множество других «грязных» примеров.

Всё это проекты на C, так что, возможно, правило маленьких функций применимо только к объектно-ориентированным языкам наподобие Java?

Что ж, давайте взглянем на функцию update() из класса CompilerAction IntelliJ, состоящую примерно из 90 LOC. Поискав в её кодовой базе, мы снова найдём множество больших функций длиной более 50 LOC.

SQLite, Chrome, Redis и IntelliJ…

Всё это важные, сложные, успешные и хорошо поддерживаемые проекты; тем не менее во всех них можно найти большие функции.

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

Я предпочитаю интеграционные тесты юнит-тестам


Я огромный фанат тестирования и крайне рекомендую тестирование ПО в качестве ключевого компонента создания удобных в поддержке систем.

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

Если взглянуть на набор тестов, можно заметить почти полное отсутствие юнит-тестов. У нас очень мало тестов, напрямую вызывающих функции объекта htmx. Вместо этого в основном применяются интеграционные тесты: они подготавливают конкретную конфигурацию DOM с некими атрибутами htmx, а затем, например, нажимают кнопку и проверяют разные аспекты состояния DOM.

Это противоречит рекомендации «Чистого кода» по обширному применению юнит-тестирования в сочетании с Test-First Development:

Первый закон Нельзя писать код продакшена, пока вы не написали проваливающийся юнит-тест.

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

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

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

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

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

Весь этот код и вся эта сложность обычно привязывают вас к конкретной реализации.

▍ Грязное тестирование


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

После этого я тщательно тестирую API интеграционными тестами.

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

Кроме того, я выяснил, что после создания нескольких высокоуровневых интеграционных тестов можно заниматься Test-Driven development, но на более высоком уровне: вы думаете не о юнитах кода, а об API, который хотите получить, пишете тесты для этого API, а затем реализуете его так, как вам это кажется уместным.

Поэтому я считаю, что мы должны откладывать написание больших наборов тестов на более поздние этапы проектов, и что этот набор тестов должен работать на более высоком уровне, чем рекомендует Test-First Development.

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

Я предпочитаю минимизировать классы


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

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

  • «Предпочитайте полиморфизм конструкциям If/Else или Switch/Case»
  • «Первое правило классов — они должны быть маленькими. Второе правило классов — они должны быть даже меньше».
  • «Принцип единственной ответственности (SRP) гласит, что класс или модуль должен иметь одну и только одну причину для изменений».
  • «Первое, что вы можете заметить — программа стала намного длиннее. Вместо одной страницы её длина составляет три».

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

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

▍ «Божественные» объекты


Люди часто критикуют идею «божественных объектов» и я, разумеется, понимаю причины этой критики: несогласованный класс или модуль с трясиной несвязанных друг с другом функций — это очевидно плохо.

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

Чтобы сбалансировать эту боязнь, давайте взглянем на один из моих самых любимых программных пакетов — Active Record.

Active Record позволяет отображать объекты Ruby в базу данных, это так называемый инструмент объектно-реляционного отображения.

И на мой взгляд, он прекрасно справляется с этой задачей: простые задачи в нём выполняются просто, средние по сложности достаточно просты, а когда возникают трудности, можно без особых проблем перейти к сырому SQL.

(Это отличный пример того, что я называю «слоистостью» API.)

Но объекты Active Record хороши не только этим: они также предоставляют превосходную функциональность для создания HTML в слое представления Rails. Они не содержат специфичной для HTML функциональности, однако предоставляют функциональность, полезную на стороне представления, например, обеспечивают API для получения сообщений об ошибках даже на уровне полей.

При написании приложений Ruby on Rails мы просто передаём экземпляры Active Record наружу представлению/шаблонам.

Сравним это с более разбитой на части реализацией, в которой ошибки валидации обрабатываются как отдельные аспекты. Теперь для надлежащей генерации HTML необходимо передать две разные вещи (или хотя бы получить к ним доступ). В сообществе разработчиков на Java часто происходит так, что используется паттерн DTO и есть отдельное множество объектов, полностью отличающееся от слоя ORM, который передаётся представлению.

Мне нравится подход, реализованный в Active Record. Пусть с точки зрения пуриста он и не разделяет ответственность, но меня чаще всего заботит ответственность передачи данных из базы данных в документ HTML, и Active Record прекрасно справляется с этой работой, не заставляя меня по пути иметь дело с кучей других объектов.

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

Может ли в модель вкрасться функциональность, которая связана с «представлением»?

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

Заключение


Я привёл три примера моей методологии «грязного кода»:

  • На самом деле большие функции (некоторые) — это хорошо
  • Следует предпочитать интеграционные тесты юнит-тестам.
  • Не стоит завышать количество классов/интерфейсов/концепций.

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

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

Не нужно пугаться, если кто-то назовёт ваш код «грязным»: многие успешные примеры ПО были написаны таким способом, и если вы сосредоточитесь на ключевых идеях разработки ПО, то, вероятно, обретёте успех, каким бы «грязным» ни был код, а может, и благодаря этому!

Telegram-канал со скидками, розыгрышами призов и новостями IT ?

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


  1. panzerfaust
    09.12.2024 13:11

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

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


    1. evgenyk
      09.12.2024 13:11

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


    1. mvv-rus
      09.12.2024 13:11

      Да. "Но есть нюанс."(с)
      Книги теоретиков от программирования - они не только (и, подозреваю, не столько) для программистов. Их ещё и менеджеры читают - у которых зачастую нет своего опыта разработки, и им остается только доверять чужому.
      В менеджерской науке, как известно, господствует постулат "управлять можно только тем, что можно измерить" И в таких книжках менеджеры могут найти для себя метрики, которые они могут измерять.

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

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


      1. Ritan
        09.12.2024 13:11

        Как известно: заставь дурака богу молиться...

        Если такой менеджер введёт KPI не на числе строк в функции/методов в классе, то введёт какую-нибудь другую хрень. Проблема не в рекомендациях, а в идиотах.


        1. mvv-rus
          09.12.2024 13:11

          Проблема не только в идиотах. Менеджер, как агент нанимателя, объективно заинтересован в том, чтобы с кодом мог работать программист как можно более низкой квалификации - на их оплате можно сэкономить.
          И с этой точки зрения метрика размера функции, возможно, не так уж глупа: менее квалифицированный программист помимо прочего отличается ещё и тем, что он хуже читает код, удерживает в голове меньший контекст. Так что для такого, более дешевого программиста ограничение размера метода может оказаться полезным. Так это или нет в реальности - я сказать не могу, ибо таких иссследований не видел, но из общих соображений это может оказаться так.
          А то, что тут в комментариях коллеги считают ограничение размера метода неоправданным - это понятно: тут всё больше люди квалифицированные отписались.
          PS Мне самому ограничение на размер метода для работы не нужно - в конце концов, я воспитывался на примерах настоящих программистов, которые "могут без смущения написать цикл DO на пяти страницах" (в более привычных единицах это 300-350 строк).


          1. Andrey_Solomatin
            09.12.2024 13:11

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


            Или чтобы высоооплачиваемый специались мог сделать больше за единицу времени.


            1. mvv-rus
              09.12.2024 13:11

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


  1. Gmugra
    09.12.2024 13:11

    Описываемый автором подход полностью согласуется с моим личным опытом. Спасибо ему что хорошо сформулировал.

    Когда люди обсуждают Clean Code, часто забывают что это по сути субъективное мнение/опыт конкретных персонажей.
    Нету в мире ничего чисто белого и чисто черного.
    Нельзя Clean Code брать как методичку "делать всегда так и только так".
    Это полезно принимать во внимание в виде "ок, посыл понял, буду учитывать", но не более того.

    Да, очень большие методы - это как правило не хорошо.

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

    Нету никакого общего правила как быть в конкретной ситуации. И быть не может.

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


    1. adeshere
      09.12.2024 13:11

      Описываемый автором подход полностью согласуется с моим личным опытом. Спасибо ему что хорошо сформулировал.

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

      Вот реальный пример из довольно большой программы (кстати, этот проект поддерживается и развивается, включая регулярный рефакторинг, уже больше 30 лет), где в одном блоке родственных процедур есть как небольшие функции, так и монстр на 400 строк. Можно ли его разбить на кусочки поменьше? Да, безусловно. Нужно ли? Лично я совсем не уверен ;-)

      Блок функций, формирующих контекстное меню в разных экранах программы. Осторожно, тут ядреный фортран!

      Код написан лет примерно 20+ назад, в момент переписывания DOS-программы под Windows. Я тогда еще вообще не умел в контекстные меню, поэтому описание функций может сейчас кого-то повеселить ;-) Но, этот код родом из DOS до сих пор работает в проде. Изредка (по мере расширения функционала программы) я что-то туда добавляю, а вот рефакторить предпочитаю совсем другие куски, где этот вопрос, имхо, гораздо более актуален ;-)

      Мегафункция PPMenu_make на 400 строк - в самом конце. Еще поясню, что все КМ у меня создаются динамически, так как список доступных опций может зависеть от текущего состояния проги (количество открытых рядов данных и др.). Поэтому заменить эту функцию таблицей нельзя, - это именно исполняемый код. Да, если кто-то не знает фортран: все функции используют набор общих глобальных (для них) переменных, структур и констант, которые описаны во вставляемом заголовочном файле.

      Даже интересно, скажет ли кто-то из адептов "чистого кода", что эту самую функцию PPMenu_make срочно надо разбить на части (например, можно вынести каждый CASE-случай в отдельную процедуру)? И, главное, что после этого разбиения (и соответствующего роста количества суб-функций) представленный блок кода станет гораздо понятнее и читабельнее?

      c=======================================================================c
      c=======================================================================c
      C     4. Создание и использование контекстных (всплывающих) меню КМ     c
      c=======================================================================c
      c=======================================================================c
      C.......................................................................c
      C           SUBR PPMENU_INIT()                                          C
      C           SUBR PPMENU_REG()                                           C
      C           SUBR PPMENU_UNREG()                                         C
      C           SUBR PPMENU_CALL       (ID)                                 C
      C           I*4 FUNC PPMENU_Find   (ID)                                 C
      C           SUBR PPMENU_AddRegion  (ID,   COORD)                        C
      C           SUBR PPMenu_AddItem    (ID,                 ITEM)           C
      c           SUBR PPMENU_MAKE       (ID)                                 C
      C                                  i*4 /SCREEN_RECT/ /POPUP_MENU_ITEM/  C
      C.......................................................................c
      c     КМ представляет собой стандартный элемент управления Windows,     c
      c  обычно активируемый нажатием правой клавиши мыши. Структура "КМ"     c
      c  включает номер меню, регион действия меню и список альтернатив.      c
      c  Чтобы активировать КМ, клиент передает серверу специальную таблицу   c
      c  PPMenu_Regions(PPMenu_N_Regions), содержащую сведения об одном       c
      c  или нескольких КМ.  При повторной передаче таблицы PPMenu_Regions    c
      c  северу новая таблица ЗАМЕНЯЕТ ранее установленную. Соответственно,   c
      c  для дезактивации КМ клиент передает пустую таблицу PPMENU_Regions(0).c
      c     Если клиент одновремено установил несколько КМ с неодинаковыми    c
      c  регионами действия, обработка и таблицы и запуск КМ осуществляются   c
      c  аналогично обработке регионов мыши (т.е. таблица просматривается     c
      c  от конца к началу до первого КМ с "подходящим" регионом).            c
      C.......................................................................c
      c     Таблица PPMENU_Regions состоит из записей типа POPUP_MENU         c
      c  Каждая такая запись описывает одно КМ, и включает следующие поля:    c
      c     - уникальный номер меню                                           c
      c     - регион, в котором меню активно                                  c
      c     - количество альтернатив меню SIZE                                c
      c     - массив PPMI=PP_MENU_ITEMS - список альтернатив меню             c
      c  Каждая альтернатива состоит из текста (название пункта меню; для     c
      c  указания горячих клавиш используется знак &) и клавиатурного кода.   c
      c  Когда пользователь выбирает в меню какую-то альтернативу, сервер     c
      c  помещает этот код в буфер клавиатуры клиента. При отмене выбора в    c
      c  буфер клавиатуры помещается ноль-код:  $Zero_Code=KEY_CODE(0,0).     c
      c     Чтобы добавить в меню полоску-разделитель на i-й позиции,         c
      c  задайте PPMI(i).key=$Zero_Code                                       c
      C.......................................................................c
      c      Клиент может инициировать запуск КМ, используя функцию           c
      c  PPMENU_CALL(ID). В этом случае сервер проверяет, находится ли мышь   c
      c  внутри региона, соответствующего этому меню. Если да, то меню        c
      c  визуализируется в положении мыши, если нет - то в центре региона.    c
      C.......................................................................c
      C  Константы и типы для работы с КМ, описаны в WinABD_inc.for           c
      C.......................................................................C
      C.......................................................................C
      C  PPMENU_INIT()        Очищает таблицу КМ для последующего заполнения  С
      C  PPMENU_ADDREGION(..)     Добавляет в таблицу КМ новое меню           C
      C  PPMenu_AddItem(..)   Добавляет в КМ новый элемент                C
      C  PPMENU_REG()         Регистрирует (передает серверу) таблицу КМ      C 
      C  PPMENU_CALL(ID)      Инициирует запуск одного из установленных КМ    C
      C  PPMENU_UNREG()       Очищает таблицу КМ и передает ее серверу        C
      C  PPMENU_Find(ID)      Ищет в таблице КМ меню с запрошенным ID.        C
      C                       Возвращает порядковый номер КМ в таблице КМ,    C
      C                       или 0, если в таблице нет КМ с таким ID.        C
      C...............Входные параметры:......................................C
      C  ID       - идентификатор меню (см. ID-константы в WinABD_inc.for)    C
      C  COORD    - регион действия меню (структура типа SCREEN_RECT)         C
      C  ITEM     - элемент меню (структура типа POPUP_MENU_ITEM)             C
      C.......................................................................C
      C.......................................................................C
      c  PPMENU_MAKE(PPMENU_ID)   готовит и регистрирует таблицу КМ согласно  C
      c  полученному значению PPMENU_ID.  Каждое меню должно иметь уникальный c
      c  номер PPMENU_ID. Если надо задать два КМ для двух разных регионов,   c
      c  то у каждого должен быть свой PPMENU_ID.  Список предопределенных    c
      c  констант PPMENU_ID для разных режимов см.в WinABD_inc.for.           c
      c.......................................................................c
      c     В зависимости от диапазона значений ID, создаваемое КМ может      c
      c  открывать новую таблицу КМ либо дополнять существующую таблицу.      c
      c     Если ID < $PPMENU_ADDITIONAL_ID_LEVEL, то ранее существовавшая    c
      c  таблица КМ очищается, и создается новая таблица, в которой данное КМ c
      c  будет первым.   Если ID >= $PPMENU_ADDITIONAL_ID_LEVEL, то новое КМ  c
      c  добавляется к существующей таблице КМ.                               c
      C.......................................................................C
            SUBROUTINE  PPMENU_INIT()
            USE HEADERS
            PPMenu_N_Regions = 0
            Active_PPMenu = 0
            END
      C.......................................................................C
            SUBROUTINE  PPMENU_REG()
            USE HEADERS
            integer*4 res
            res = isenddata(51,PPMenu_Regions,sizeof(PPMenu_Regions(1))*PPMenu_N_Regions)
            end
      C.......................................................................C
            SUBROUTINE  PPMENU_UNREG()
            USE HEADERS
            PPMenu_N_Regions=0
            call PPMenu_REG()
            Active_PPMenu=0
            END
      C.......................................................................C
            SUBROUTINE  PPMENU_CALL(ID)
            USE HEADERS
            integer*4 res,ID
            call kbdclr()
            res = isenddata(52,ID,4)
            END
      C.......................................................................C
            SUBROUTINE  PPMENU_ADDREGION(ID, COORD)
            USE HEADERS
            INTEGER*4 ID
            TYPE(SCREEN_RECT) :: COORD
            if (PPMenu_N_Regions >= $PPMENU_MAX_REGIONS) call error(-411)
            PPMenu_N_Regions = PPMenu_N_Regions+1
            PPMenu_Regions(PPMenu_N_Regions).id = id 
            PPMenu_Regions(PPMenu_N_Regions).coord = coord
            PPMenu_Regions(PPMenu_N_Regions).size = 0
            end
      C.......................................................................C
            SUBROUTINE  PPMenu_AddItem(ID, ITEM)
            USE HEADERS
            TYPE(POPUP_MENU_ITEM) :: ITEM
            integer*4 ID, N_Menu, N_Item
      c
      c     Проверить наличие КМ с запрошенным ID, и не превышен ли его размер:
            N_Menu=PPMenu_Find(ID)
            if (N_Menu == 0) call error(-413)
            N_Item= PPMenu_Regions(N_Menu).SIZE + 1 
            if (N_Item >= $PPMENU_MAXSIZE) call error(-412)
      c      
            PPMenu_Regions(N_Menu).SIZE=N_Item
            PPMenu_Regions(N_Menu).PPMI(N_Item) = ITEM
            end
      C.......................................................................C
            INTEGER*4 FUNCTION PPMenu_Find(ID)
            USE HEADERS, Dummy_PPMenu_Find => PPMenu_Find
            integer*4 ID      
            do PPMenu_Find=1,PPMenu_N_Regions 
              IF (PPMenu_Regions(PPMenu_Find).ID == ID) return
            end do
            PPMenu_Find=0
            end
      c
      c.......................................................................c
            SUBROUTINE PPMenu_make(PPMENU_ID)
            USE ABD_INC;  USE HEADERS
            integer*4     PPMenu_ID
      c
      c.....Проверка, не установлено ли уже только что именно это меню?
            if (Active_PPMenu == PPMenu_ID) return
            Active_PPMenu = PPMenu_ID
      c
      c.....Реинициализация таблицы КМ если ID < $PPMENU_ADDITIONAL_ID_LEVEL:
            if (PPMenu_ID < $PPMENU_ADDITIONAL_ID_LEVEL) call PPMENU_Init()
      c
      c
      c=======================================================================c
      c     Формируем КМ для разных режимов:                                  c
      c=======================================================================c
      c
            SELECT CASE (PPMenu_ID)
      c
      c.......Графики:     
      c
      c       КМ строки заголовка графиков: вызов других экранов и переключение числа боксов:
              case($PPMENU_ID_ABDPLOT_NAMES)
                call PPMenu_AddRegion(PPMenu_ID, Series_Names_Zone)
                
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Histo)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Struct)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Acorr)
                if (Visual_Series >= 2) call PPMenu_AddItem(PPMenu_ID,$PPMI_Dcorr)
                if (Visual_Series >= 2) call PPMenu_AddItem(PPMenu_ID,$PPMI_Resp)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Filter)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Save)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Separator)
      
                call PPMenu_AddItem(PPMenu_ID,$PPMI_FontUp)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_FontDoun)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_ReBold)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_ShowMRegs)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Separator)          
      
                if (Visual_Series >= 2) call PPMenu_AddItem(PPMenu_ID,$PPMI_SwichBoxes)
                if (Visual_Series >= 2) call PPMenu_AddItem(PPMenu_ID,$PPMI_Separator)          
      
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Gradient)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_NamesList)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_EditHistory)  ! Alt+F6
                call PPMenu_AddItem(PPMenu_ID,$PPMI_ConfigEdit)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Exit)
      c
      c       КМ основного экрана (поле графиков):        
              case($PPMENU_ID_ABDPLOT)
                call PPMenu_AddRegion(PPMenu_ID, Plot_Screen)
      
                call PPMenu_AddItem(PPMenu_ID,$PPMI_ScreenPgUp32)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Window)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_RboxManual)          
                if (int_len(screen) > 32) call PPMenu_AddItem(PPMenu_ID,$PPMI_ScreenManual)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_ReScale)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_ReGeneralize)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_View)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_OnOff_Left_Part)                    
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Separator)
      
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Text)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_CbText)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_CurveNumb)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_OnOffCursor)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_MarkPoint)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_SeriesInfo)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_CtrlIns)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Separator)
      
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Equake)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_EqCfgEdit)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_EqDataEdit)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Separator)
      
                call PPMenu_AddItem(PPMenu_ID,$PPMI_EditHistory)  ! Alt+F6
                call PPMenu_AddItem(PPMenu_ID,$PPMI_ConfigEdit)          
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Exit)
      c          
      c.......Графики с дополнительными командами рамки:     
              case($PPMENU_ID_RBOX_ACTIVE)
      c
      c         Сформировать основное КМ поля графиков с рамкой Zoom:
                call PPMenu_AddRegion(PPMenu_ID, Plot_Screen)
      c          
                call PPMenu_AddItem(PPMenu_ID,$PPMI_RBoxPgUp04)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_WWindow)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_RboxManual)          
                if (int_len(screen) > 32) call PPMenu_AddItem(PPMenu_ID,$PPMI_ScreenManual)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_ReScale)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_ReGeneralize)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_View)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_OnOff_Left_Part)          
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Separator)
                
                call PPMenu_AddItem(PPMenu_ID,$PPMI_EraseZoom)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Subst)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_LinTrZoom)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_ShiftInZoom)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Separator)
      
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Text)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_CbText)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_CurveNumb)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_OnOffCursor)          
                call PPMenu_AddItem(PPMenu_ID,$PPMI_MarkPoint)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_SeriesInfo)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_CtrlIns)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Separator)
      
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Equake)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_EqCfgEdit)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_EqDataEdit)          
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Separator)          
      
                call PPMenu_AddItem(PPMenu_ID,$PPMI_EditHistory)  ! Alt+F6
                call PPMenu_AddItem(PPMenu_ID,$PPMI_ConfigEdit)          
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Exit)
      c
      c.......Гистограмма: рамка не активна:     
              case($PPMENU_ID_HISTO)
      c
      c         Сформировать основное КМ гистограммы:
                call PPMenu_AddRegion(PPMenu_ID, Plot_Screen)
      
                call PPMenu_AddItem(PPMenu_ID,$PPMI_FontUp)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_FontDoun)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_ReBold)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_ShowMRegs)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Separator)          
      
                call PPMenu_AddItem(PPMenu_ID,$PPMI_CtrlIns)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Toggle_Text)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Text)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_CbText)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_MarkPoint)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Separator)
                          
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Zoom)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Separator)
                
                call PPMenu_AddItem(PPMenu_ID,$PPMI_EditHistory)  ! Alt+F6
                call PPMenu_AddItem(PPMenu_ID,$PPMI_EditXHistory) ! Ctrl+F6
                call PPMenu_AddItem(PPMenu_ID,$PPMI_New)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Exit)
      c
      c.......Гистограмма: рамка активна:     
              case($PPMENU_ID_HISTO_ZOOM)
      c
      c         Сформировать основное КМ гистограммы с рамкой:        
                call PPMenu_AddRegion(PPMenu_ID, Plot_Screen)
      
                call PPMenu_AddItem(PPMenu_ID,$PPMI_FontUp)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_FontDoun)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_ReBold)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_ShowMRegs)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Separator)          
                
                call PPMenu_AddItem(PPMenu_ID,$PPMI_CtrlIns)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Toggle_Text)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Text)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_CbText)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_MarkPoint)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Separator)
      
                call PPMenu_AddItem(PPMenu_ID,$PPMI_EraseOut)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_EraseRight)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_EraseLeft)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_EraseIn)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Delete_Zoom)          
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Separator)
                
                call PPMenu_AddItem(PPMenu_ID,$PPMI_EditHistory)  ! Alt+F6
                call PPMenu_AddItem(PPMenu_ID,$PPMI_EditXHistory) ! Ctrl+F6
                call PPMenu_AddItem(PPMenu_ID,$PPMI_ConfigEdit)          
                call PPMenu_AddItem(PPMenu_ID,$PPMI_New)          
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Exit)          
      c
      c.......СФ, АКФ, ВКФ:     
              case($PPMENU_ID_CORR)
      c
      c         Сформировать основное КМ:        
                call PPMenu_AddRegion(PPMenu_ID, Plot_Screen)
      
                call PPMenu_AddItem(PPMenu_ID,$PPMI_FontUp)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_FontDoun)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_ReBold)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Separator)
                
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Text)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_CbText)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_MarkPoint)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_CtrlIns)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Separator)
                          
                select case((Plot_Mode))
                  case ($PM_Acorr);   call PPMenu_AddItem(PPMenu_ID,$PPMI_SF_Save) 
                  case ($PM_Struct);  call PPMenu_AddItem(PPMenu_ID,$PPMI_AKF_Save)
                  case ($PM_Dcorr);   call PPMenu_AddItem(PPMenu_ID,$PPMI_VKF_Save) 
                end select
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Separator)
                
                call PPMenu_AddItem(PPMenu_ID,$PPMI_EditHistory)  ! Alt+F6
                call PPMenu_AddItem(PPMenu_ID,$PPMI_EditXHistory) ! Ctrl+F6
                call PPMenu_AddItem(PPMenu_ID,$PPMI_ConfigEdit)          
                call PPMenu_AddItem(PPMenu_ID,$PPMI_New)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Exit)
      c
      c.......Resp:     
              case($PPMENU_ID_RESP)
      c
      c         Сформировать основное КМ:
                call PPMenu_AddRegion(PPMenu_ID, Plot_Screen)
      
                call PPMenu_AddItem(PPMenu_ID,$PPMI_ReScale)      ! Ctrl+F7
                call PPMenu_AddItem(PPMenu_ID,$PPMI_FontUp)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_FontDoun)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_ReBold)       ! Ctrl+B
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Gradient)     ! F7          
                call PPMenu_AddItem(PPMenu_ID,$PPMI_ShowMRegs)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Separator)
      
                call PPMenu_AddItem(PPMenu_ID,$PPMI_RespL)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_RespO)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_RespD)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_RespN)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Separator)
      
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Animate)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_SearchNext)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_SearchAll)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Separator)
      
                call PPMenu_AddItem(PPMenu_ID,$PPMI_GotoDate)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_FindDate)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Separator)
      c
      c         Эти пункты показываем только в режиме "карта эпицентров" или всегда?
      !          if (map_components()) then
                  call PPMenu_AddItem(PPMenu_ID,$PPMI_EQ_MAP)
                  call PPMenu_AddItem(PPMenu_ID,$PPMI_EQ_MAP_EDIT)
                  call PPMenu_AddItem(PPMenu_ID,$PPMI_Separator)
      !          end if
      
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Text)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_CbText)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_MarkPoint)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_CtrlIns)
      
                call PPMenu_AddItem(PPMenu_ID,$PPMI_EditHistory)  ! Alt+F6
                call PPMenu_AddItem(PPMenu_ID,$PPMI_EditXHistory) ! Ctrl+F6
                call PPMenu_AddItem(PPMenu_ID,$PPMI_ConfigEdit)          
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Exit)
      c
      c.......Spectr:     
              case($PPMENU_ID_SPECTR)
      c
      c         Сформировать основное КМ экрана спектра: 
                call PPMenu_AddRegion(PPMenu_ID, Plot_Screen)
      c          
                call PPMenu_AddItem(PPMenu_ID,$PPMI_WWindow)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_SpScale)  ! ReScale Y
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Shift)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Smooth)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Separator)
      
                call PPMenu_AddItem(PPMenu_ID,$PPMI_SSave)    ! Spectr Save
                call PPMenu_AddItem(PPMenu_ID,$PPMI_SDump)    ! Smoosed Spectr Dump
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Separator)          
                          
                call PPMenu_AddItem(PPMenu_ID,$PPMI_FontUp)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_FontDoun)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_ReBold)   ! Ctrl+B
                call PPMenu_AddItem(PPMenu_ID,$PPMI_ShowMRegs)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Separator)
      
                call PPMenu_AddItem(PPMenu_ID,$PPMI_View)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Text)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_CbText)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_CurveNumb)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_MarkPoint)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_CtrlIns)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Separator)
      
                call PPMenu_AddItem(PPMenu_ID,$PPMI_EditHistory)      ! Alt+F6
                call PPMenu_AddItem(PPMenu_ID,$PPMI_EditXHistory)     ! Ctrl+F6
                call PPMenu_AddItem(PPMenu_ID,$PPMI_ConfigEdit)          
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Exit)
      c
      c
      c         Сформировать КМ строки заголовка спектра:
              case($PPMENU_ID_SPECTR_HEADER)
                call PPMenu_AddRegion(PPMenu_ID, Series_Names_Zone)
      c          
                call PPMenu_AddItem(PPMenu_ID,$PPMI_SpScale)  ! ReScale Y
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Shift)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Smooth)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Separator)
      
                call PPMenu_AddItem(PPMenu_ID,$PPMI_SSave)    ! Spectr Save
                call PPMenu_AddItem(PPMenu_ID,$PPMI_SDump)    ! Smoosed Spectr Dump
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Separator)          
      
                call PPMenu_AddItem(PPMenu_ID,$PPMI_FontUp)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_FontDoun)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_ReBold)   ! Ctrl+B
                call PPMenu_AddItem(PPMenu_ID,$PPMI_ShowMRegs)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Separator)
      
                call PPMenu_AddItem(PPMenu_ID,$PPMI_CurveNumb)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_CtrlIns)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Separator)
      
                call PPMenu_AddItem(PPMenu_ID,$PPMI_EditHistory)      ! Alt+F6
                call PPMenu_AddItem(PPMenu_ID,$PPMI_EditXHistory)     ! Ctrl+F6
                call PPMenu_AddItem(PPMenu_ID,$PPMI_ConfigEdit)                    
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Exit)
      c
      c.......Fractlen:     
              case($PPMENU_ID_FRACTLEN)
      c
      c         Сформировать основное КМ:        
                call PPMenu_AddRegion(PPMenu_ID, Plot_Screen)
      c          
                call PPMenu_AddItem(PPMenu_ID,$PPMI_New)              ! N
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Text)             ! T
                call PPMenu_AddItem(PPMenu_ID,$PPMI_CbText)           ! C
                call PPMenu_AddItem(PPMenu_ID,$PPMI_MarkPoint)        ! F4
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Toggle_Text)      ! F3
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Separator)
      
                call PPMenu_AddItem(PPMenu_ID,$PPMI_FontUp)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_FontDoun)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_ReBold)           ! Ctrl+B          
                call PPMenu_AddItem(PPMenu_ID,$PPMI_ShowMRegs)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_CtrlIns)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Separator)
      
                call PPMenu_AddItem(PPMenu_ID,$PPMI_EditHistory)      ! Alt+F6
                call PPMenu_AddItem(PPMenu_ID,$PPMI_EditXHistory)     ! Ctrl+F6
                call PPMenu_AddItem(PPMenu_ID,$PPMI_ConfigEdit)          
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Exit)             ! E
      cc
              case($PPMENU_ID_FRACTLEN_ZOOM)
      c
      c         Сформировать основное КМ:        
                call PPMenu_AddRegion(PPMenu_ID, Plot_Screen)
      c          
                call PPMenu_AddItem(PPMenu_ID,$PPMI_FL_Regr)          ! R
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Delete_Zoom)      ! D
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Separator)
      
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Text)             ! T
                call PPMenu_AddItem(PPMenu_ID,$PPMI_CbText)           ! C
                call PPMenu_AddItem(PPMenu_ID,$PPMI_MarkPoint)        ! F4
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Toggle_Text)      ! F3
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Separator)
      
                call PPMenu_AddItem(PPMenu_ID,$PPMI_FontUp)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_FontDoun)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_ReBold)           ! Ctrl+B          
                call PPMenu_AddItem(PPMenu_ID,$PPMI_ShowMRegs)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_CtrlIns)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Separator)
      
                call PPMenu_AddItem(PPMenu_ID,$PPMI_EditHistory)      ! Alt+F6
                call PPMenu_AddItem(PPMenu_ID,$PPMI_EditXHistory)     ! Ctrl+F6
                call PPMenu_AddItem(PPMenu_ID,$PPMI_ConfigEdit)          
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Exit)             ! E
      c
      c.......Hurst:     
              case($PPMENU_ID_HURST)
      c
      c         Сформировать основное КМ:        
                call PPMenu_AddRegion(PPMenu_ID, Plot_Screen)
      c          
                call PPMenu_AddItem(PPMenu_ID,$PPMI_New)              ! N
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Text)             ! T
                call PPMenu_AddItem(PPMenu_ID,$PPMI_CbText)           ! C
                call PPMenu_AddItem(PPMenu_ID,$PPMI_MarkPoint)        ! F4
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Toggle_Text)      ! F3
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Separator)
                          
                call PPMenu_AddItem(PPMenu_ID,$PPMI_FontUp)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_FontDoun)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_ReBold)           ! Ctrl+B
                call PPMenu_AddItem(PPMenu_ID,$PPMI_ShowMRegs)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_CtrlIns)
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Separator)
                
                call PPMenu_AddItem(PPMenu_ID,$PPMI_EditHistory)      ! Alt+F6
                call PPMenu_AddItem(PPMenu_ID,$PPMI_EditXHistory)     ! Ctrl+F6
                call PPMenu_AddItem(PPMenu_ID,$PPMI_ConfigEdit)          
                call PPMenu_AddItem(PPMenu_ID,$PPMI_Exit)             ! E
            END SELECT          
      c
      c.....Зарегистрировать получившуюся таблицу КМ как единствненную либо дополнительную,
      c     в зависимости от PPMenu_ID > $PPMENU_ADDITIONAL_ID_LEVEL или нет:
            call ppmenu_reg()
            end


    1. mvv-rus
      09.12.2024 13:11

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

      По опыту разбора исходного кода ASP.NET Core (есть у меня такое развлечение) я понял, что разбиение кода на множество мелких классов и запихивание их в разные папки и файлы - это ничуть не худшее средство для усложнения понимания кода, чем широко известный "спагетти-код" с GOTO, с которым активно боролись теоретики времен Дейкстры и Вирта. А если ещё приправить всё это функциональщиной - в C# это делегаты экземплярных методов, которые, в отличие от чистых функций из классического ФП, могут, не привлекая внимания компилятора, иметь почти неограниченные побочные эффекты - то получается просто адская смесь, вполне достойное наследия Настоящих Программистов!


  1. kenomimi
    09.12.2024 13:11

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

    А всякие методологии в отрыве от реалий конкретного бизнеса - чистой воды диверсионная деятельность. Я припоминаю, как у нас на предприятии (западная ТНК) вводили 5С - приклеивали мониторы к столам, меряли линейкой расстояние между ярлыками на рабочем столе, подписывали очевидные вещи (на столе наклейка "стол", на двери "дверь", ...) - угарали с клоунады все, включая ответственных. Или вот, внедрение канбана поддержке, когда ответственный - камнеголовый: надо было, чтобы у тебя всегда было три задачи, так прописано в священных догмах. Больше? Плохо, и пофиг, что ты ждешь подрядчика или ответ пользователя. Три, и точка. Нет задач? Найди, хоть рожай. Ну и рожали, отбирали группы у юзеров, потом по обращениям снова выдавали - а как еще KPI нагнать? Или как вам установка плана отделу RnD на N задач в жире, не глядя, что там делают люди. Даже мемчик локальный появился такой, муляжировать, создавать бессмысленные задачи самому себе или коллеге, чтобы выполнить план. "Что делаешь? Да вот муляжирую 10 задач, а то недельный план не будет закрыт"

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


    1. panzerfaust
      09.12.2024 13:11

      В одной конторе тоже приходил скрам-мастер и учил плохому. Потом иногда до скандалов доходило, когда задачу из In progress пытались положить назад в ToDo. Ну мало ли: приоритеты изменились или данных пока не хватает. Нет, взял - двигай только вперед. И это говорил не какой-нибудь смузихлеб после универа, а 40-летний начальник отдела - так ему мозги промыли "правильным" скрамом.


      1. Aggle
        09.12.2024 13:11

        У Вас ошибка в слове "Scum".


      1. bondeg
        09.12.2024 13:11

        Срам-то какой.


    1. flancer
      09.12.2024 13:11

      он дурак или эффективный менеджер

      Спасибо, занёс в личный цитатник :)))


      1. LordCarCar
        09.12.2024 13:11

        Предпочитаю называть "эффектный менеджер".


  1. event1
    09.12.2024 13:11

    Гражданин Мартин последний раз писал код за деньги лет 30 назад. Но его советы по организации кодовой базы почему-то кого-то волнуют.

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


    1. Andrey_Solomatin
      09.12.2024 13:11

      А что такого поменялось за 30 лет?


      1. event1
        09.12.2024 13:11

        Примерно всё. Вы где-то в бункере жили?


        1. Andrey_Solomatin
          09.12.2024 13:11

          Новые парадигмы? Прорывы в ООП? Архитектурные паттерны? Новые структуры данных и алгоритмы для повседневного пользования?

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

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

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

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


          1. Gmugra
            09.12.2024 13:11

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

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

            Нам доступно радикально другое железо (например многоядерности 30 лет назад не было в принципе, не было смартфонов, не было ультраскоросного интернета). Это все оказывает огромное влияние в том числе и на то как мы разрабатываем и что мы разрабатываем.

            Если вы посмотрите, например, на первые версии Java (как раз 30 лет назад появилась) и то что мы имеем сейчас: это выглядит почти как другой язык.

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

            Короче я мог бы многое написать о том, что сдохло а что появилось потому как раз лет примерно 30 в этой профессии. У меня есть много лет опыта в технологиях, методологиях и даже ЯП, которые сейчас почти нифиг никому и не для чего не сдались.

            Но моя мысль в том что как раз в разработке софта мир меняется настолько быстро, что опыт 10-летней давности это на половину уже бесполезный опыт. А 30-летней давности - на 90%. IMHO.


          1. event1
            09.12.2024 13:11

            Концептуально, нужно отметить проникновение функциональных концепций везде и всюду, как справедливо отметил коллега выше. Но это не столь важно, в контексте организации кода. Много важнее, широкое распространение систем контроля версий, и множество процессов вокруг них: систематические обзоры кода, последовательная интеграция и автоматическое тестирование всего и вся. И ещё, как вы сами и отметили, множество библиотек для любых целей. Да, просто, подсветка синтаксиса в редакторах была не везде. Да и в остальном, редакторы 94-го года (на пример, турбо паскаль версии 7.0) даже близко нельзя сравнивать с, каким-нибудь vscode.

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

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

            Про гражданина Мартина, важно не то, что поменялось в профессии за 30 лет, а то, что он не программировал 30+ лет. И до этого программировал не много.

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


            1. Gmugra
              09.12.2024 13:11

              Вы в целом правы. Но давайте будем честны: все эти практики активно применялись очень давно и задолго до Мартина.

              Я начал карьеру в 1996 году и на той, самой первой своей работе в совсем небольшой фирме применялся VSS, существовал style gгide и требования к именованиям в коде, были автоматические тесты (в кустарном виде, да. Но были) и даже то, что сейчас называется code review было. Lotus Notes использовался в качестве, вы не поверите, системы с тикетами для раздачи и контроля выполнения задач. (что-то типа Jira на минималках)

              Да и не все было так уже примитивно. Вторая поливина 90x это куча IDE c GUI которые много чего могли, это инструменты RAD вроде Delphi или Gupta SqlWindows. Инструментарий был, его было не мало и оно все активно развивалось и улучшалось.

              СVS и предок VSS существовали уже в самом начале 90x. Концепция юнит-тестов корнями вообще уходит куда-то в конец 70x и активно пропагандировалась Кентом нашим Беком чуть ли не с конца 80x.

              Где-то в самом начале 2000x я попал в мир Java и сразу: CVS, Ant, JUnit (да, он уже существовал), стандарт форматирования прямо от разработчиков ЯП, дизайн паттерны (знаменитая книжка тогда уже вышла несколько лет как, ee даже на русский перевили уже в 1999 году) и вот это вот все.

              Т.е. все практически упомянутые практики были широко известны черт знает с каких времен.

              Да инструменты, были гораздо примитивнее. Hudson/Jenkins появился позже: в моих первых проектах, то что сейчас называется CI/CD выглядело как набор самописанных bash-скриптов которые гонялись по cron. Но это было! Да, не было еще общепринятой терминологии для каких-то штук. Но сами практики не были никаким откровением ни для кого в профессиональной разработке и были повсеместно распространенны.


              1. UnclShura
                09.12.2024 13:11

                А вот кстати из того, что именно изменилось за 30 лет: почти полный отказ от собственно ООП на классах и замена его интерфейсами и фасадами/включением. Провал (да да!) множественного наследования. Как правильно сказано, функциональные элементы в языке. Гигантский прогресс в инструментарии, когда без IDE код никто вообще уже не читает (ну за исключением, может, С/С++). И уж не пишет точно. Кроме того межъязыковое проникновение (C# вызывает JS, Python зовет библиотеки на C++) да и смешение - многие элементы одного языка пришли в другие и обратно. Появились языковые конструкции, которых тогда не было (pattern matching, properties). Изменился стиль (паттерны).

                ЗЫ: за Gupta отдельное спасибо! Я 2 года убил на то чтоб ее больше никогда не видеть :)


                1. Andrey_Solomatin
                  09.12.2024 13:11

                  Появились языковые конструкции, которых тогда не было (pattern matching, properties).

                  В про паттерн матчинг в каком-то конретном языке или вообще?

                  Изменился стиль (паттерны).

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


      1. whoisking
        09.12.2024 13:11

        На мой взгляд, они и 30 лет назад были бы не особо практичными)


        1. warkid
          09.12.2024 13:11

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


          1. vvbob
            09.12.2024 13:11

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


            1. evgenyk
              09.12.2024 13:11

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


  1. DasMeister
    09.12.2024 13:11

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


    1. evgenyk
      09.12.2024 13:11

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

      А дальше разделы по предметным областям, такой структуры: предметная область - подход - примеры.

      Например:

      • Предметная область: Графический интерфейс. Подход: ООП. Примеры структуры программ.

      • Предметная область: Обработка больших объемов данных. Подход: Пайплайн. Примеры . Примеры структуры программ.

      • Ну и так далее.

        В подходе и примерах и нужно изложить рекомендации к длине функций, названиям функций и переменных и так далее.

        Где-то так.


      1. Andrey_Solomatin
        09.12.2024 13:11

        Всё так.

        И ещё:

        • Для разных стадий проекта.

        • Для разных уровней синьёрности комманд.

        • Для поддержки авторами или аутсорсерами (каждый раз новыми).


      1. DasMeister
        09.12.2024 13:11

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


        1. evgenyk
          09.12.2024 13:11

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


          1. DasMeister
            09.12.2024 13:11

            Ну например:

            "Микросервисы. Паттерны разработки и рефакторинга" - которая весь текст будет рассказывать о том, где применимы микросервисы и паттерны. А где нет. И как DDD здорово подходит для микросервисов и в какие моменты.

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

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

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

            Вот пример - книга Кента Бека "Разработка через тестирование", маленькая, компактная и содержит и примеры кода, практики и идей и их обоснования и выгоды от использования.

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

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


  1. geher
    09.12.2024 13:11

    Чистый код, грязный код...

    Функции не должны быть длинныии или кортткими. Они должны заключать в себе законченную осмысленную единицу кода. А если дробить функции только ради достижения требуемого размера в n строк или объединять код в жирную функцию только ради соответствия размера ее важности, ерунда какая-то получается.

    Примерно то же можно сказать и о классах, а их количество в проекте само по себе ни о че м не говорит.


    1. Iwanowsky
      09.12.2024 13:11

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


      1. Andrey_Solomatin
        09.12.2024 13:11

        А сколько из них публичных и сколько приватных?


    1. kekoz
      09.12.2024 13:11

      Но пределы должны быть у всего. И разумные, желательно.

      Автор считает, что 200 строк делают функцию большой и “грязной”? Так он просто больших функций не видел :) В одном чертовски популярном open source продукте (не уверен, что вспомнил точно, а уточнять прямо сейчас лениво, позже повспоминаю, но кажется это ffmpeg. Или нет? Но что-то про мультимедиа. Найду потом) есть функция, в которой почти шесть тысяч строк.


    1. sswwssww
      09.12.2024 13:11

      Они должны заключать в себе законченную осмысленную единицу кода

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


      1. uranik
        09.12.2024 13:11

        А пример большой атомарной функции, содержимое которой не надо разбивать на несколько маленьких атомарных?


        1. sswwssww
          09.12.2024 13:11

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


          1. Andrey_Solomatin
            09.12.2024 13:11

            Хороший пример как разбить большую функцию на подфункции.


            1. sswwssww
              09.12.2024 13:11

              На какие подфункции вы разобъете бизнес процесс order и зачем?


        1. geher
          09.12.2024 13:11

          Несколько сотен строк у мегя не было, а чуть больше сотни (с комментариями и пустыми строками) случалось.

          Это обычно одно из двух.

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

          Во-вторых, огромный switch, каждая строчка которого имеет вид

          case x: return fx();

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

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


  1. Andrey_Solomatin
    09.12.2024 13:11

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

    Зачем так делать? Не знаю, но делают.

    Большие функции требуют большей культуры разработки. Автоматизация качества кода будет работать хуже.

    Если в проекте нет сильной культуры разработки, то лучше запретить большие функции.

    Для себя я не на SLOC смотрю, а на цикломатисескую сложность. Высокая сложность всегда много строк.


  1. vagon333
    09.12.2024 13:11

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

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

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


    1. Noah1
      09.12.2024 13:11

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

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


      1. Gromilo
        09.12.2024 13:11

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


  1. Andrey_Solomatin
    09.12.2024 13:11

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

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

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

    Подход автора будет работать, если закладывать это подход в архитектуру проекта. Если вы присоединитесь к проекту типа аморфный монолит, то не взлетит.


  1. Andrey_Solomatin
    09.12.2024 13:11

    Был опыт работы на проекте, где данные доставались из базы и прямо отсылались на фронтенд. На фронтенде они просто показывались.

    Надо переименовать поле на фронте? Меняем имя колонки в базе данных.

    Меняем структуру базы, фронтенд переписываем.

    Экономия на объектах не всегда хорошо. Хотя на ранних стадиях такая связанность неплохо экономит время.


  1. Notactic
    09.12.2024 13:11

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

    P.S. Главное не размер функции, а умение пользоваться :D


    1. Gmugra
      09.12.2024 13:11

      Я вам больше скажу: все книжки Мартина это популизм чистой воды. Субъектившина. А если он вдруг пишет книжку более практическую (как евоная книжка по C# например) то совсем ерунда выходит.

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


  1. iroln
    09.12.2024 13:11

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


    1. Gromilo
      09.12.2024 13:11

      Мне кажется это оптимизация усилий при том же качестве.


  1. DenSigma
    09.12.2024 13:11

    А это верно, что чем хуже и грязнее пишешь, тем быстрее работаешь и выше твоя производительность?


    1. Andrey_Solomatin
      09.12.2024 13:11

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


    1. qeeveex
      09.12.2024 13:11

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


      1. i86com
        09.12.2024 13:11

        Когда как. По моему опыту на больших проектах как раз таки бывает наоборот.

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

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

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

        Я бы тут привёл аналогию с книгами.

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

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

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


        1. Andrey_Solomatin
          09.12.2024 13:11

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

          Если программист может в 500 строках сразу увидить контекст, то значит он написал эту функцию в одно лицо, пару недель назад.

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

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

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

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


  1. DenSigma
    09.12.2024 13:11

    1. Хотелось бы уточнения, что такое "Важные" и
      "неважные" функции.

    2. «Первое правило классов — они должны быть маленькими. Второе правило классов — они должны быть даже меньше». Простите, где это сказано?


    1. Andrey_Solomatin
      09.12.2024 13:11

      Хотелось бы уточнения, что такое "Важные" и "неважные" функции.

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

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


    1. Chanser
      09.12.2024 13:11

      2. «Первое правило классов — они должны быть маленькими. Второе правило классов — они должны быть даже меньше». Простите, где это сказано?

      В статье же вроде книга "Чистый код" обсуждается, вот прямо в ней и сказано. Глава 10 "Классы", раздел "Строение класса", подраздел "Классы должны быть компактными!".

      P.S. Интересно что в этом подразделе, в принципе как и в остальных частях книги, Мартин ударяется в крайности: уменьшив в примере размер класса до 5 методов, говорит что и этого слишком много.


  1. orefkov
    09.12.2024 13:11

    Пишите хорошо, а плохо не пишите.
    Имхо, самый важный принцип - KISS.


    1. Gmugra
      09.12.2024 13:11

      Проблема со всеми этими модными акронимами в том что они бессмыслены в отрыве от контекста.

      Ну вот "делай просто". А что значит "просто"?

      У нас куча подходов, техник и т.п. которые сложны просто в силу своей природы.

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

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


      1. evgenyk
        09.12.2024 13:11

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


        1. vvbob
          09.12.2024 13:11

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


      1. vvbob
        09.12.2024 13:11

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


      1. orefkov
        09.12.2024 13:11

        Делай просто, насколько возможно, но не проще :)


  1. qeeveex
    09.12.2024 13:11

    Главная фишка чистой архитектуры и unit тестов, это возможность моментальной проверки корректности твоих изменений. Буквально за секунды.

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

    Так же требуется время на поддержку и настройку различных mock сервисов и стабов. Которые к тому же чувствительны к среде выполнения (привет docker).

    В итоге уходит много времени на проверку, что правки корректны и не сломали старый функционал. Во многих компаниях сейчас целые QA отделы, но пока они все это проверят.

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


    1. qeeveex
      09.12.2024 13:11

      На прошлой работе, например, чтоб проверить фичу в QA, нужно было выкатить в master! Так как на актуальность dev окружение давно забили и проще все обкатать на стейдже.

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

      Все нормально - фигачьте парни!

      А смысл в том что разработчикам которые пишут чистый код и шарят в архитектуре надо платить больше денег! ПРостым кодерам, которые фигак-фигак и на прод - зарпаты будут существенней ниже.

      QA тоже стоит меньше.


    1. Gromilo
      09.12.2024 13:11

      Я делаю апи и использую интеграционники чаще чем юниты. 1000 тестов где-то за минуту проходит. Это никак не мешает их запускать при доработках.


  1. vvbob
    09.12.2024 13:11

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

    С другой стороны - видел и противоположный полюс, когда все упорно пихалось в God Object, и проект выглядел как несколько классов с простынями кода (по 4-5 тысяч строк).

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


  1. lexus1990
    09.12.2024 13:11

    Хотел бы написать то, что стало для меня открытием год назад, не смотря что я 10 лет программирую. Есть такое понятие - как глобальная сложность системы. Ее часто рассматривают в контексте микросервисов. Если вы сделаете систему из 20 очень маленьких микросервисов - то с каждым микросервисом будет легко разбираться, но сложно будет понять что в целом происходит с системой. Так же я предлагаю относиться и к количеству классов / интерфейсов в приложении (компоненте). Учитывать не только локальную сложность, но и сложность самого компонента


  1. esisl
    09.12.2024 13:11

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

    Про полиморфизм vs if/else
    Тут интересный такой психиатрический выверт.
    Помните ругань в адрес "клипового мышления"?
    Я на своих студентах, с удивлением, обнаружил когда-то, что они здорово тупят на длинных логических цепочках, за то отлично оперируют массивом неявных связей!!
    Это именно про это...


  1. Oldju
    09.12.2024 13:11

    Код должен быть таким, что бы вы могли легко найти в нем ошибку. Все.


  1. TeslaTechFan
    09.12.2024 13:11

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


  1. infitum
    09.12.2024 13:11

    если вам помогает в разработке какойто шаблон "чистого" кода, используйте его, а если не помогает, то какая разница, кто его предложил )


  1. Prizrakileni
    09.12.2024 13:11

    Если использовать методику «сначала тесты», то у вас появится куча тестов, которые будут ломаться в процессе исследования пространства задачи и попыток найти подходящие абстракции.”

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

    Тоесть не:

    - взять айди юзера из базы

    - сверить роли с полем access конфига

    - вернуть ‘2’ если роль есть в группе rdctrs

    - иначе: 0

    А:

    - если юзер залогинен

    - и его роль есть в группе уровня «редакторы» и выше:

    - разрешить редактировние

    - иначе:

    - показать тултип «доступ ограничен»

    Тогда и апи тестируемого модуля будет адекватней и понятней, и «валиться в ходе исследования» тесты будут «валидно» - помогая, а не мешая

    Не могу сказать, что сам всегда сначала пишу тесты: если задача на «рисеч» чего-то на что в команде нет ни экспертизы ни точных указаний чего мы хотим - то и не понятно чего именно надо тестировать.

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


    1. Gromilo
      09.12.2024 13:11

      Но это же не юнит, а вполне себе интеграционник


      1. Prizrakileni
        09.12.2024 13:11

        Это спека: а юнит это будет или интеграционник - это как реализовать тест)

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

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


        1. Gromilo
          09.12.2024 13:11

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

          писать спеки в терминах решаемой задачи

          Это база. Вообще код нужно писать в терминах решаемой задачи.


  1. mrozov
    09.12.2024 13:11

    Что такое код, состоящий из огромных функций?
    Полныманалогомэтогоявялетсятекстбезпроблеловипереводовстрок.

    Что такое код, состоящий из функций, большинство которых состоят из одной-двух строк?

    Ну

    ,

    это

    такой

    текст , в котор

    ом вооб

    ще нет никак

    ой структ

    уры
    .

    Что из них хуже? Так оба хуже. Оба подхода друг-друга стоят. Ни один из них не нужно использовать.


    1. Andrey_Solomatin
      09.12.2024 13:11

      Оба подхода друг-друга стоят

      Второй вариант я прочитал без проблем, не возвращаясь к уже прочитанному. Он значительно лучше первого.

      У вас метрики сбиты, потому, что это вы написали первый пример. И можете его "прочитать" вообще не глядя на экран.


  1. gen1lee
    09.12.2024 13:11

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

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


    1. Gromilo
      09.12.2024 13:11

      В книге есть полезные мысли, а есть не очень.

      Новичкам советую читать Стива Макконнелла — Совершенный код с 10-й главы


    1. Andrey_Solomatin
      09.12.2024 13:11

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

      Мне кажется что вы о каком-то другом коде говорите. Разбиение на фукции должно делать код плоским и последовательным.

      Вот пример разбиения:

      данные = СкачайДаные()
      ПровалидируйДанные(данные)
      новыеДанные = ТрансформируйДанные(данные)
      ЗагрузиДанные(новыеДанные)

      Каждая из этих функций состоит из других маленьких функций.

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

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


      1. gen1lee
        09.12.2024 13:11

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

        Так что этот перефрагментированный говнокод и есть «грязный».