Как стать автором
Поиск
Написать публикацию
Обновить

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

Если есть возможность попробуйте заинлайнить все эти 5-10 функций и рассказать нам насколько это проще.

Там не будет функции на 500 строк. Там будет функция на те же 5 строк, где будут нарушены все 33 слоя абстрактной абстракции.

Но возможности проверить у меня нет - это всё было очень давно

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

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

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

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

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

Но это всё хорошо, когда все нормально с архитектурой и в конкретной функции нет большого контекста, общего для ее частей (слабая cohesion). А если большой контекст есть, то большая функция удобнее: контекст в ней можно хранить в локальных переменных, для которых несложно найти, где они устанавливаются и используются, и про которые a priori очевидно, что за пределами функции они не используются. А вот при разбиении такой функции на части возникает проблема - как передавать контекст. Портянкой параметров - потом замучаешься в вызываемых функциях разбираться, где какой. Создать объект, содержащий контекст и сделать функции его методами - здравствуйте, God Object, давно с вами не встречались, да и сборщику мусора будет чем заняться. Но худший IMHO вариант - это использовать функциональщину: передавать подфункции части логики как параметры-функции с контекстом в их замыканиях. К сожалению, и такое делать приходится, например - при существенном изменении общей логики: к примеру MS в ASP.NET Core именно так была вынуждена обеспечивать совместимость с предыдущим шаблоном приложения Web Host, после того как перешла к шаблону Generic Host - я разбирал этот великолепный костыль в одной из предыдущих своих статей.

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

Если "большая" функция написана "хорошо", то она читается ровно так же.

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

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

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

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

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

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

Кстати, и переписать такую функцию, выделив ее части в отдельные функции, тоже несложно.

Я не раз так делал. Только вот есть проблема, некоторые моменты поведения которые были спрятанны в складках большой функции, становяться видны. Функция творит какую-то дичь если чуть отступить от "happy path" и ты не знаешь почему это так. Толи это странная бизнес логика, толи так вышло.

Самая частая проблема, которая мне встречается это использование переменной из другого контекста. Это специфично для языков (Python), где нет неизменяемых перменных (final, val) или где от их использования отказались.

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

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

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

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

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

Сейчас мы говорим об архитектуре на уровне модуля. В обоих сучаях наружу торчит одна функция. Разбить всё внутри на маленькие функции или нет это уже детали.

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

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

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

Всегда было интересно, как такие функции тестируются.

Вас как это интересует - по теории или по жизни?

Если по теории, то вопрос вообще бессмысленный, потому что по теории таких функций быть не должно :-)

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

PS Тесты быть читаемыми не обязаны: их код для изменения не предназначен.

А кого мы считаем теоретиками? Дядюшка Боб - практик.

Программирование - это такое ремесло, где невозможно быть теоретиком.

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

Дядюшка Боб - практик.

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

Совсем отказывать ему в опыте тоже не правильно. Я смотрел разные ведео с ним, он понимает о чем говорит и рассказывает вполне технические вещи.

У него можно очень много чему научится. Да, не все советы подходят для всех случаев жизни.

PS. В это дискусии очень мало примеров реального кода и описания достижений от участников. Сложно определить являются ли участники дискуссии практиками или теоретиками.

Он спокойно спорит с "Чистым кодом", не надо соломенное чучело избивать. Это место* в книжке воспринимают буквально из-за авторитета; чтобы люди "приходили к такому же отношению", надо подобные статьи поощрять.

* Вот это место:

  • The first rule of functions is that they should be small. The second rule of functions is that they should be smaller than that.

  • Functions should not be 100 lines long. Functions should hardly ever be 20 lines long.

  • Every function in this program was just two, or three, or four lines long. Each was transparently obvious. Each told a story. And each led you to the next in a compelling order. That’s how short your functions should be!

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

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

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

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

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

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

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

Подписываюсь. Я точно так же , как автор, исхожу не из размера функции, а из логики программы. И иногда пишу функции и на 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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Я начал карьеру в 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. Но это было! Да, не было еще общепринятой терминологии для каких-то штук. Но сами практики не были никаким откровением ни для кого в профессиональной разработке и были повсеместно распространенны.

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

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

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

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

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

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

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

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

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

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

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

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

Например:

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

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

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

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

    Где-то так.

Всё так.

И ещё:

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

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

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

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

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

Ну например:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

На какие? Вы же их сами перечислили. Естественно все приватные и не доступные снаружи.

Зачем?

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

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

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

  • Тестировать становится проще. Эту функциональность без тестовых двойников не протестировать. Согласитесь будет странно писать мок на склад, для расчёта налогов.

  • Когда тесты падают то можно точнее установить кто виновник.

  • Ошибки типа случайно использовал не ту переменную сводятся к нулю.

  • Не автор кода может быстрее начать работу с кодом.

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

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

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

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

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

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

case x: return fx();

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

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

`case x: return fx();` это хорошая декомпозиция.

switch иногда можно поиском по словарю заместить. Фабрики и полиморфизм иногда могут помочь.

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

Задачки с литкода вполне решаются с разбиением на подфункции. Хотя они в среднем пожирнее получаются.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Знаете, вот пожалуй один вариант полезности юнит-тестов могу припомнить.
Когда есть очень большая и сложная система, когда состыковано такое количество сервисов, что получается "микросервисный ужас собранный из МАКРОсервисов" :))) ,
и нужно переписать один модуль(может класс, а может просто какой-то кусок файла), и к этому модулю УЖЕ ЕСТЬ ГОТОВЫЙ юнит тест! :))))))))))))))

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Во всяком случае, ни разу не было в практике, чтобы юнит-тест оказался полезен(выловил бы баг).

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

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

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

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

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

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

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

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

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

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

Тоесть не:

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

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

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

- иначе: 0

А:

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

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

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

- иначе:

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

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

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

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

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

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

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

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

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

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

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

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

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

Ну

,

это

такой

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

ом вооб

ще нет никак

ой структ

уры
.

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

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

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

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

А зачем вы слова то раздробили? Разве кто-то советует делить семантические единицы?

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

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

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

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

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

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

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

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

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

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

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

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

Посмеялся. После пары редактирований ваши комментарии разделители уедут чёрти знает куда или станут не актуальными.

А что вам мешает комментарии превратить в функции?

Давайте пофантазируем. СкачайДаные
использует HTTP, ПровалидируйДанные, ТрансформируйДанные
кастомный код, с последующим расширением, ЗагрузиДанные
использует AWS.

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

После пары редактирований ваши комментарии разделители уедут чёрти знает куда или станут не актуальными.

Уехавшие и неактуальные комментарии не пройдут код-ревью.

Давайте пофантазируем.

...

Слишком много разных уровней абстаркции

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

Уехавшие и неактуальные комментарии не пройдут код-ревью.

Это хорошо. Но не везде это так.

Я полагаю, совет Мартина разбивать функции на короткие относится не к такому случаю.

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

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

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

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

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

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

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

Зарегистрируйтесь на Хабре, чтобы оставить комментарий