Comments 182
К сожалению, в последнее время встречаю много огромных «простыней» за авторством именно бывалых (5+ лет разработки), уверенных в себе людей
А преимущества и недостатки «волшебных однострочников» можно обсудить за дружеской кружкой пива :)
Комментарии любят лгать. Кто-то потом обязательно изменит часть метода и конечно напрочь забьет на написание обновленного коммента в замен старому. Потом читаешь такие комменты, которые вообще не соответствуют реальности, и дуаешь: "Это ошибка и должно быть так как в комментарии, или комментарий устарел?".
Просто писать надо понятно. Называть метод и его параметры так, чтобы из одной только этой информации было понятно что этот метод делает, как он это делает, и зачем он вообще нужен. А уж если всё-таки придется заглянуть внутрь, то каждая строка метода должна быть самодокументируемой, что опять таки происходит благодаря осмысленным названиям переменных, функций, вызываемых в методе и тд.
Такой код сложно писать (ох как тяжко иногда придумать короткое, понятное и выразительное название методу), он сильно выростает в ширину, но не в высоту, и благодаря этому читается, как обычный текст. Язык программирования на то и язык, чтобы на нем писать, обрекая лексические конструкции в понятный и человеку и машине текст программы.
Вообще рекомендую прочитать книгу "Чистый код: создание, анализ" за авторством Роберта С. Мартина, там очень много полезного написано, в том числе и то что в статье перечислено.
Комментарий, который говорит «что» делает код — действительно почти бесполезен при правильной организации оного кода
Комментарий должен отвечать на вопрос «зачем» здесь этот код, какая бизнес-необходимость привела к его написанию. Тогда он не устареет вне зависимости от смены реализации.
Как правило такие комментарии называются документацией. И пишутся соответственно стандарту доументирования, как заведено в языке, например тот же Javadoc, или как заведено стандартами в компании, если имеются. Обычных болтающихся комментариев в коде стоит избегать, так как обычно единственное что можно понять из комментария, так это то, что у автора не получается писать достаточно выразительный код.
Ещё комментарий может говорить о хаках и трюках, которые (зачем то) понадобились.
Справедливое замечание, хоть я и редко встречаю такие хаки в своей практике, но в целом согласен ибо сам пишу некоторые разъяснения, но опять же, обычно, в документации, за редким исключением, когда хак можно описать одним-двумя предложениями. А вот когда натыкаюсь на чужие хаки без комментариев или с бесполезными комментариями из разряда:
i = 0x5f3759df - ( i >> 1 ); // what the fuck?
каждый раз вспоминаю быстрое извлечение обратного квадратного корня из квейка (собственно оттуда и пример).
Обычных болтающихся комментариев в коде стоит избегать, так как обычно единственное что можно понять из комментария, так это то, что у автора не получается писать достаточно выразительный код.Мои комментарии часто бывают в стиле «сделано так, потому что другой способ, очевидный, является неправильным или неэффективным».
К примеру, если в delphi создаёшь форму через Application.CreateForm(TForm, formReference), то нужно обязательно вызвать Application.Run, даже если приложение запускается не в интерактивном режиме (можно в Run послать сразу Terminate), другого способа корректно освободить ресурсы этих форм нет.
То есть, комментарии типа «да, тут некрасиво и напрашивается переписать короче и понятнее, но короче работать не будет».
Так что «сделано так, потому что другой способ, очевидный, является неправильным или неэффективным» вполне нормальная ситуация если всё написано так что можно понять без комментариев.
На самом деле иногда поражает на сколько люди ленятся читать код без комментариев, даже если он сам себя комментирует через имена его компонентов. Даешь человеку прочитать свой код, чтобы обсудить с ним какую-то его часть — он возвращается с упрёком, мол «где комментарии». Я отвечаю: «а зачем, тут же всё достаточно хорошо расписано, и всё есть в документации», на что обычно я получаю ответ в стиле: «код без комментариев не может быть понятным», хотя на деле это просто человеческая лень, ведь очень демотивирует когда ты видишь исходник на 3 тысячи строк кода (отформатированных по стандарту, определенному проектом, написаным с максимальной выразительностью, и с функциями, тела которых не превышают 20 строк, и обладают единственной ответственностью) без единого комментария. Хотя если приглядеться и начать таки читать, всё читается как текст. Сам читал такой код не раз, сам пишу такой же код на работе.
3 тысячи строк кода с функциями, тела которых не превышают 20 строк
150 функций на одном уровне вложенности, каждая из которых может потенциально вызываться из остальных? Я бы наверно тоже так сказал.
не, ну если в блокноте читать, то понятное дело. Никто же не запрещает использовать Ctags, встроенные в IDE фичи, такие как поиск символа и так далее (мазохизм конечно никто не отменял).
Такой файл как правило читается с верху в низ — если функция что-то вызывает, то оно обязательно определено ниже, так что в целом за один проход от верха до низа можно проследить одну ветвь вполне легко, а уж тем более когда можно просто прыгнуть по тегу. Функции одного уровня определяются рядом, что тоже не вносит беспорядка в файл, так что, мне кажется, эти претензии не обоснованы, просто читать и разбираться в чужом всегда лень, а особенно без комментариев. При этом я понимаю что другой человек никак не может узнать степень выразительности кода, не начав его читать, и понимаю что никто не хочет тратить время на это. Но деньги то получают за работу, значит читать надо.
на одном уровне вложенности
Что не так с одним уровнем вложенности я не понял, вы функции в глобальном пространстве видимости на разных уровнях вложенности пишете? Функции в любом случае будут на одном уровне вложенности (если конечно не определять их в классе, вложенном в класс, в класс, в класс).
не, ну если в блокноте читать, то понятное дело
Так количество функций что в блокноте что в IDE одинаковое) Чтобы понять, что тут делается, надо все их проанализировать.
Функции одного уровня определяются рядом, что тоже не вносит беспорядка в файл
Ага, разобьешь одну функцию на несколько, они все рядом, потом еще их разобьешь на несколько, и вот уже главные функции кроме первой уехали вниз. А потом еще и вызываются из разных мест. Вот и прыгаешь туда-сюда, а контекст надо держать в голове.
Что не так с одним уровнем вложенности я не понял, вы функции в глобальном пространстве видимости на разных уровнях вложенности пишете?
Я намекаю на то, что вложенным может быть код, который вынесен в эти функции. В пределах одного метода сразу видно, что вот эти действия вызываются только при вот этих условиях или в этом цикле. Я к тому, что разбивать надо не по количеству строк, а по логической связанности.
Так количество функций что в блокноте что в IDE одинаковое) Чтобы понять, что тут делается, надо все их проанализировать.Чаще всего достаточно прочесть название функции. Если вдруг понадобятся детали одной из десятка — лезть глубже в конкретный блок, а не пытаться искать ориентиры в простыне.
Куча мелких функций ничем не лучше кучи строк в одной функции. Названиями переменных тоже можно действия документировать. В данном примере 3000 строк лучше разделить на несколько классов, а не пытаться прикрыть сложность функциями.
Фридрих Ницше
«Совершенство достигается не тогда, когда нечего добавить, а тогда, когда нечего отнять».
Угу, любимое занятие: "фиксил багу… стёр 200 строк кода, теперь вроде работает правильно".
А насчёт лямбд — очень неуниверсальный совет. Порой лямбда именно что на своём месте, унести её в метод — значит, отодвинуть на 100 строк от места, где её удобно читать, да ещё и передать пачку параметров. Но, конечно, когда лямбда разрастается — на это приходится идти, а то и вообще выносить её в отдельный объект (или делить на коротенькую лямбду и большой метод, который она вызывает)
Runnable
внутри которого ещё и дёргает map
по коллекции, становится очень печально. Эдакий callback hell на стероидах.на универсальность не претендую :)
В качестве альтернативы лямбда-функциями в C#, например, можно использовать локальные фукции.
Ну прямо-таки старый добрый Паскаль, аж на ностальгию пробило :-)
Самое забавное по ссылке: "Во многих случаях выбор между использованием лямбда-выражений и локальных функций определяется стилем и личными предпочтениями".
Структура — это тоже код. Декларации — это, внезапно, тоже код. И даже когда перед нами обычный ООП без «закидонов» — уменьшение количества функционального кода всегда выливается в увеличение объема структур (это помимо случаев изобретения велосипедов, когда надо просто использовать уже где-то имеющийся код).
Когда слишком много структур — это ничем не лучше, чем когда слишком много кода. Когда видишь перед собой простую редко выполняющуюся задачу категоризации (а-ля «разложить общую кучу элементов по нескольким по определенному признаку»), решаемую с помощью пары интерфейсов, десятка классов, и «умно» примененных паттернов — схватиться за пистолет хочется нисколько не меньше, чем при чтении метода на 1000 строк.
Посудите сами, если у вас есть однородная задача на 10 000 действий, то это задача на 10 000 действий. Вы можете её разбить на 1*10000, 10*1000, 100*100, 1000*10 и тд структурных единиц, но от этого она меньше не станет. Она останется задачей на 10000. Ваши разбиения лишь добавляют в неё энтропию. Да, разбиение работает в качестве оглавления, но, в целом, оно ни лучше и не хуже простого комментирования кода.
Вкусовщина. Одним людям проще кушать слона по частям. Другим проще прочесть код, как книгу, — от корки до корки. Третьи будут ныть в любом случае, а четвёртые вообще не будут читать, позвонят разработчику и вынут всю душу вместе с содержимым кишечника.
Я не имею в виду программу с ГУИ, обработкой командной строки, работой с БД и расчётным блоком. Это разные задачи — структурные единицы, которые должны быть разнесены. А бывают иные задачи, разбиение которых ничего не даёт, кроме эстетического удовлетворения и проблем с передачей параметров. Стоит оно того или нет? Я не знаю.
Посудите сами, если у вас есть однородная задача на 10 000 действий, то это задача на 10 000 действий. Вы можете её разбить на 1*10000, 10*1000, 100*100, 1000*10 и тд структурных единиц, но от этого она меньше не станет. Она останется задачей на 10000.
вот только для среднего изменения в одном случае надо будет прочитать 10-15 блоков по 10 действий, а в другом — 3-4 блока по тысяче
1. читается код намного больше раз, чем пишется. Монолитный код приходится читать полностью, в то время как модульный — только интересующими нас кусками.
2. человеческая память ограничена определенным количеством действий. Один раз прочитав метод и зная, что он вычисляет какой-нибудь f(x,y,z) и не имеет побочных эффектов, повторно можно его не анализировать. Большие методы, однако, держать в голове полностью невозможно.
3. поведение маленьких модулей проще, значит проще составить модель их ожидаемого поведения, протестировать и гарантировать корректность
4. маленькие методы легко переиспользовать, слишком большие — невозможно. Поэтому в плохо разделенном коде чаще встречается дублирование, которое само по себе ведет к нескольким другим проблемам.
А теперь еще раз вспомните, что все эти действия над кодом надо делать каждый раз при попытке внесения изменений.
1) я и писал про читаемость кода
2) а этот пункт как раз и был предметом сомнений: я совсем даже не уверен, что держать в голове 15 коротких блоков проще чем 4 длинных, и даже более того, сталкивался с ситуациями, когда эти самые короткие блоки, поделённые на отдельные функции и вложенные друг в друга, меня ужасно бесили при чтении кода — гораздо приятнее было бы прочитать одну простыню, чем ворох мелких фрагментов; если у вас такого никогда не было — это не значит что всем так же.
3) опять та же проблема: вам надо сделать кучу мелких проверок или одну большую: усложнение графа связей между функциями само по себе добавляет проблем, и, может быть, оно добавит меньше проблем, чем решит благодаря маленькому размеру модулей, а может и нет
4) ну, да, но переиспользование не везде будет требоваться; если вы пишете какое-то апи общего назначения — то да, вы не можете заранее полностью знать контекст его применения, а если вы пишете конкретный продукт под конкретную задачу — то уже можете знать, что какая-то функция, хоть она и могла бы быть переиспользованной в другом случае, но конкретно в вашем проекте она используется всего 1 раз и больше не будет; у многих есть склонность создавать абстракции и апи общего назначения там, где их «не заказывали» — это далеко не всегда хорошо.
Сложность теста пропорциональна сложности метода. Проще метод — проще тест.
Проект редко (никогда не) заканчивается сразу после реализации первично обговоренного функционала. Лучше сделать код расширяемым чем рефакторить на каждую хотелку.
Я сравниваю два варианта, которые были предложены вами же выше. Причём тут код LLVM непонятно. ПРо тесты это отдельная тема но про них в начале речи тоже не шло.
Проект редко (никогда не) заканчивается сразу после реализации первично обговоренного функционала. Лучше сделать код расширяемым чем рефакторить на каждую хотелку.
Во-первых, это просто не так. Во-вторых, добавление функционала не означает, что из этого нового функционала может быть вызвана произвольная функция. В-третьих, такие преждевременные абстракции имеют свою цену, и если вы заранее сделаете 100 ненужных апи функций, а потом, в процессе доработки, 10 их них пригодятся — то весьма сомнительно, что вы в итоге выиграли. Я не говорю, что делать апи заранее ненужно (более того, есть места, где оно понадобится потом с вероятностью >70%), но делать его везде — определённо лишнее, и есть места, которые в вашем проекте его потребуют с вероятностью, например, не больше 5%, но вы, забыв о рабочей задаче, начинаете вместо специализированного инструмента делать софт общего назначения "для всех".
Так что всё по своей мере.
Но правильно выбраное разбиение на абстракции может разбить эту задачу на (условно) 10 * 10 * 10 * 10 структурных единиц (не только методов, но может быть и классов). В зависимости от того, на сколько глубоко нужно изучить механизм решения задачи, можно будет прочесть (условно) один метод на 10 строк. Или ещё 2-3 метода по 10 строк и так далее. Гораздо легче сфокусироваться, погружаясь в детали.
Я столкнулся с таким, казалось бы излишним, разбиением в open source проекте, к которому готовил pull request, и оно меня спасло. Оно того стоит.
Я такое видел лишь единожды, и мне очень понравилось.
Я не собираюсь спорить, какой вариант лучше. Разбиение на структурные единицы — инструмент. И им тоже нужно уметь пользоваться.
Когда видишь перед собой простую редко выполняющуюся задачу категоризации (а-ля «разложить общую кучу элементов по нескольким по определенному признаку»), решаемую с помощью пары интерфейсов, десятка классов, и «умно» примененных паттернов
А если такая задача решалась бы с помощью пары интерфейсов, ДВА десятка классов, НО БЕЗ «умно» примененных паттернов — это было бы лучше 1000 строк?
1. Это вольный пересказ нескольких глав из книг Фаулера и Йордона?
2. Статья про код, про ООП и про Java, но ни строчки кода на Java?
И маленькое дополнение: Хотелось бы увидеть как автор доказывает и показывает , что вот этот код ( здесь пример кода) был таким, а потом стал таким и что из этого вышло и к чему привело. Или так писать плохо(тут пример плохого кода), а так писать хорошо (тут пример хорошего...) Как говорится — всё познается в сравнении.
2. код должен быть разделен на логические блоки, а не параграфы. Ветку if “на 20+ строк” имеет смысл выносить в отдельную функцию только если она будет являться самостоятельной логической единицей.
Хорошо работает только если нет костылей. Если есть reflection, или дыры в тестах, т.п. — любой рефакторинг выливается в рефакторинг зависимых частей, и такой «проект» растет, как снежный ком. И даже хуже — если не довести его до конца, приходится добавлять еще костылей.
а вот теперь по остальным выводам:
Сэкономите кучу времени себе (на написание) и другим (на чтение).
Есть метод XXZZ12YYu(int a)
Он вызывается во всем проекте несколько и в разных местах.
вызов занимает 1 строчку, а вот что он делает, анализ метода занимает огромное кол-во времени, плюс написание тестов (если их нет) что бы понять конкретный результат.
Уменьшите количество ошибок в системе.
Выше косвенно влияет, но только при условии, что в системе остальное работает безупречно.
Упростите поддержку и развитие системы.
Ситуация стандартная, когда не понимаешь как это работает и посмотреть негде, а есть только 2 строчки кода. Большие затраты времени на анализ «маленького» кода.
Получите эстетическое удовольствие от результата.
Удовольствие получит тот, кто этот код написал, а тот кто будет его читать через 3 месяца будет комментировать его, используя громкие эпитеты.
Код должен быть лаконичным, что не эквивалентно понятию «Короткий»,
что означает — каждый следующий элемент кода должен следовать логически из предыдущего.
Присоединяюсь, только у меня фраза звучит так: "Идеальный код — это не написанный".
Или что-то из
Вот как раз перед праздниками я случайно нашел причину лагов веб-интерфейса. Она была в коде который должен был бороться с лагами. Удалил его и лаги исчезли…
А потом возможно придется покупать аппарат для гемодиализа.
А зачем тогда кто-то этот код туда вставил?
Нет, есть вероятность, что это какой-то атавизм, который забыли вырезать после вырезание его связей. Нет смысла спорить.
— уговорить клиента вообще отказаться от фичи (уровень Бог);
— переиспользовать готовые фичи;
— что-то поправить или настроить в конфигах;
— взять готовую библиотеку.
И только если ничего из вышеперечисленного невозможно — писать код. Причем писать как можно меньше (минимально возможное количество кода, позволяющее реализовать фичу).
Это помогает:
— сократить время разработки;
— упростить саппорт.
Вот прихожу я к вам и говорю — «мне нужно вот с этих пяти сайтов собирать информацию об их товарах, чтоб как только что новое появляется — у меня сразу вся инфа с ценой.»
Достаю и держу в руках пачку денег
Давайте по своим четырем пунктам попробуйте пройтись. Начните с конфигов)
- Нет ли у этих сайтов фидов с ценами? Может можно добавить продукты в избранное и получать цены о них на почту? Может уже есть сервис-агрегатор и вам нужно купить подписку на него вместо разработки своей системы?
- Если у меня уже есть какой-то парсер, я могу добавить ваши сайты в него и дописать только необходимую часть.
- Если у меня есть парсер (допустим я уже делал такие задачи) — возможно в нем есть вариант конфигурировать правила разбора разметки, пользуясь например XML или YAML.
- Если все с нуля — поищу какие есть готовые библиотеки для парсинга, для отправки HTTP запросов, для отправки писем.
И только потом напишу код-склейку.
2. нету, конечно
3. см. 2
4. О каком парсинге речь? html-парсинге? Это подножный стандартный инструмент. Всё остальное всё равно придется ручками писать. А любое готовое типа универсальное решение которое вам (или вы) будут впаривать в итоге выйдет боком и намного дороже пары сотен своих строк.
Я даже не беру в расчет время, необходимое на изучение и настройки этих готовых решений, и их ошибки. И вообще, зависание на чужой код.
2. нет так нет
3. см. 2
4. Я подумал, что вы хотите со страницу интернет-магазина цену брать, поэтому html-парсер. Возможно, есть другие способы. Что плохого в зависимости от чужого кода, если он решает задачу? Опять же, я не имею в виду первую попавшуюся поделку с гитхаба, есть решения с большим сообществом и долгой историей. Я предпочитаю, чтобы баги фиксил кто-то другой.
По поводу фида было — висел я на фидах, и в один момент один перестал работать. А хозяин даже не заметил — кому сейчас rss этот нужен. Пришлось аврально переписывать на парсинг сайта, который всегда должен быть онлайн
Так что все зависит от задачи. Иногда можно и библиотеки поискать, но в большинстве несложных случаев проще самому.
Переиспользуйте код, выделяйте абстракции, не стесняйтесь.Опасный совет.
Зачастую, выделенные абстракции — неправильные, и приводят к серьёзному усложнению программы и большему числу багов, чем лишний раз скопипасченный кусок кода.
Выделять абстракции можно потом, во время рефакторинга, когда очевидно что этот кусок кода используется 3 или больше раз в неизменном виде, а не на этапе написания нового функционала.
— в зависимости от проекта бывает очень тяжело предсказать что будет дальше, хотя, в отличие от тупых продакт менеджеров разработчик уже знает
— есть большая вероятность написать что-то лишнее, а все лишнее нужно поддерживать (об этом статья)
— преждевременно написанный код: если написать код, который в общем виде понадобится в будущем релизе, а не текущем. такое явно нужно согласовывать, т.к. может быть приоритеты другие.
Преждевременно написанный код возникает при попытке выделить абстракции в момент первого написания функционала. Тут я соглашусь.
Но когда тот же функционал используется уже во второй раз — абстракции уже стоит выделять, не доводя до рефакторинга, поскольку
- дальше будет как обычно — нехватка времени на поддержку, исправление похожих багов в разных похожих участках кода и никто не готов выделить время на рефакторинг.
- а не надо писать лишнее. Выделяются только те абстракциии, которые нужны. Если когда этот функционал будет использован в третий раз — абстракции можно будет доработать.
- качество кода это не предмет согласования, а прямая обязанность программиста. А если проект планируется дольше чем на полгода — еще и головная боль.
нехватка времени на исправление похожих багов в разных похожих участках кода и никто не готов выделить время на рефакторинг.Рефакторинг проводится тогда, когда ты возвращаешься к этому коду — при исправлении багов или реализации новой функциональности. Поэтому специально выделять на него время нужно лишь в крайне редких, сложных и запущенных случаях, когда были допущены существенные ошибки в архитектуре приложения (а чаще это даже вредно, так как есть риск попасть в ловушку «рефакторинг ради рефакторинга», сжирающую любой выделенный на неё объём времени без каких-либо реальных выгод).
а не надо писать лишнее. Выделяются только те абстракции, которые нужныСложновато сделать вывод о том, нужна ли абстракция (и в такой ли форме?), если она встречается только дважды, причём второе применение — новое, для которого всегда может последовать команда «Отставить!».
Если когда этот функционал будет использован в третий раз — абстракции можно будет доработать.В результате чего придётся вносить изменения (и рисковать багами) в первых двух применениях. Зацепленность не просто так считается одной из важнейших проблем при программировании.
даже единожды скопированные без изменения несколько строк кода — абсолютное зло.Часто встречаются ложно-одинаковые куски кода, которые ни в коем случае нельзя выделять в абстракции, потому что хоть они и «скопированы без изменения» — но изменения в них точно будут, просто не на данном этапе.
Например, инициализация элементов UI: несколько диалогов в руках у разработчика могут быть идентичными — но стоит им попасть в руки дизайнеров, и каждый получит индивидуальный цвет, вкус и запах.
Необходимость копирования можно проверить представлением в динамике — «если у нас логика изменится здесь, надо будет менять там?», «может ли быть, что там должно работать не так, как здесь?».
Мне стоило некоторого труда научиться задавать себе вопрос, а не говнокод ли я написал только что?
К сожалению, многие разработчики не задают себе такой вопрос вообще никогда.
И сферический идеальный код в вакууме почти никому в реальном мире не нужен (а тем очень немногим, кому нужен — как правило, не за большие деньги, если вообще хоть за какие-то). В реальном мире вообще код не нужен, а нужны инструменты для решения проблем клиента (которые он толком описать вам не сможет, во всяком случае не с первого раза), и желательно чтоб еще вчера, и чтоб максимально недорого.
Я, конечно, люблю идеальный код, но не являюсь его неуёмным фанатом. Должны существовать измеримые критерии качества кода, и никакой код в проекте не должен быть хуже, чем предусмотрено этими критериями. Критерии, конечно, у каждого проекта могут быть свои.
Очень хорошо, если заказчик понимает, что качество кода непосредственно влияет на качество продукта, на стоимость его развития и поддержки. Если не понимает — лучше инвестировать время в разъяснение, окупится.
А так — да. Никому не нужна корова, всем нужно молоко, шкура, мясо. Никому не нужен бетон, всем нужны мосты и здания. Никому не нужен Х, всем нужен У.
Должны существовать измеримые критерии качества кода
Так они есть, просто от самого кода они удалены довольно далеко.
Критерий сиюминутного качества — это стоимость написания того, что нужно вотпрямщас. Критерий долговременного качества — стоимость поддержки и развития последующие N лет.
В обоих случаях первичны, опять же, деньги. А не «сколько нам нужно грамотных проектировщиков, чтоб всё было красиво», и тем более не «сколько в среднем строк на метод» и прочее, имеющее непосредственное отношение к коду. А дальше кто как разумеет, тот так и идёт от денег к конкретным инструкциям для тех же джунов. Кто-то эмпирическим путем определяет, как «строки на метод» относятся к стоимости поддержки, кто-то пользуется своим (и чужим) прошлым неудачным и удачным опытом, чтоб понять, что некий набор методик и рекомендаций скорее всего позволит вписаться в сроки и бюджеты, и так далее. Путей очень много.
С основным посылом полностью согласен: довольно много некачественного кода пишется как новичками, так и «бывалыми» (всегда так работал и было норм). А вот про рецепты… Все-таки главное читабельность, а не количество кода (видел прилично компактых нечитаемых примеров в жизни). Хотя метрика «кол-во кода» проще автоматизируется. Просто, этого недостаточно.
Да и не будут это читать «бывалые». А если прочитают, то не согласятся. Иначе бы уже давно исправились. Нужны организационные изменения. Какие — зависит от конкретной организации. Где-то достаточно обязательных code review. Где-то можно внедрить роботов, которые блокируют PR, если там слишком плохой с формальной точки зрения код. Где-то еще что-то.
Код заслуживает быть вынесенным в отдельную функцию не только тогда, когда его планируется переиспользовать, но и в целях улучшения читаемости.
Маленькая проблема, называется "области видимости". В результате в вашу "отдельную функцию" постоянно придётся носить кучу параметров, а если она ещё и в цикле (вложенных циклах) вызывается — так уж вообще проще застрелиться, сколько времени сожрёт её вызов чисто на запуливание-выпуливание параметров в/из стека.
Нет, конечно, бывают тривиальные случаи, но как правило, если написана "длинная простыня", то случай не тривиальный.
Области видимости — это не проблема, а благословение, позволяющее экономить сложность.
Если вы выносите в функцию вроде бы обособленный кусок кода, а он тащит с собой 5-10 параметров, значит что-то тут не так. Например, математические расчеты перемешаны с логгированием, получением/записью данных и пр. Другой случай — у вас есть блок данных, которые везде приходится таскать вместе (скажем, длина, ширина и кисть рабочей области рисования), тогда имеет смысл выделить класс-контекст.
Именно выделение функции позволяет такие проблемы обнаружить, и почти всегда после рефакторинга понимаешь, что новый код стал более понятным и чистым.
Еще лучше с длинными простынями. Часто при распутывании таких "нетривиальных" случаев оказывается, что покрыты не все результаты ветвления и в некоторых случаях получатся странные результаты либо явные расчетные или инфраструктурные (забыли считать/модифицировать общие данные) ошибки.
Сhart of how Slack decides to send a notification
1) Это не код, а блок-схема. Вероятно, она была нарисована, когда авторы совсем запутались в написанном коде;)
2) В этой блок-схеме есть два очевидно отдельных куска с разделением в "What's the user's channel notification pref for this device". При этом нижнюю часть скорее всего можно упростить, т.к. есть пересечение у channel и global notifications (например Mentions). Также если делать не единственные точки выхода (YES/NO), то из схемы пропадёт куча объединяющих стрелочек и она станет не такой страшной.
3) Главное, эта схема описывает реальную сложную бизнес-логику. Ее тоже не стоит переусложнять (пользователю будет непонятно), но это другой случай. Описывающий эту логику код поместится на 1-2 страницах, и с учетом хорошей документации в этом нет проблем. Но есть еще код, который вытаскивает из внутренних структур значения переменных, которые тут в if'е. Если он подмешан к коду бизнес-логики, то именно тогда и получается простыня, требующая рефакторинга.
сколько времени сожрёт её вызов чисто на запуливание-выпуливание параметров в/из стека.
Ого! Вот с таким уровнем оптимизации я никогда не сталкивался. Простите уж Java-программисту его наивность :)
Что касается большого количества параметров выделяемой функции, то, если все они действительно нужны, можно задуматься о выделении структуры или класса. Но только если не приходится экономить на виртуальных вызовах и количестве параметров на стеке :)
Большие куски кода трудно тестировать. Большие методы, использующие кучу данных, практически нереально покрыть Unit тестами, а интеграционные тесты оказываются огромными и очень сложными.
Если код приходится "покрывать" тестами, лучше забыть про Unit-тесты, иначе и в тестировании разочаруетесь, и руководителям нервы испортите. Напишите интеграционные или приемочные тесты. Главное, чтобы они позволили без страха рефакторить и добавлять новый функционал. А новый код пишите по TDD.
Большие куски кода сложно ревьюить.
Ревью — вообще проблема и один из самых больших bottleneck'ов в процессе доставки продукта конечному пользователю. И на больших, и на маленьких кусках кода. Человек, который делает ревью, не может адекватно оценить решение, потому что он не в контексте задачи. Обычно на ревью находят всякую стилистическую шелуху, которую при наличии тестов можно поправить в любой момент.
Быть в контексте задачи помогает парное программирование, но продать его руководству очень сложно.
Переиспользуйте код, выделяйте абстракции, не стесняйтесь.
Пожалуйста, не выделяйте абстракции, когда пишите код. Они будут дерьмом, всегда. Решите сначала свою задачу, а в фазе рефакторинга, если код действительно будет "кричать" о необходимости абстракции, выделяйте.
Чтобы писать меньше кода, нужно:
- перестать реализовывать бесполезные фичи
- решать с помощью кода только текущие проблемы, а не мечтать "когда мы будем как Facebook, такое простое решение работать не будет"
- сначала написать падающий тест
- потом заставить тест проходить
- закончить рефакторингом
- если рефакторинг не идет — оставьте код в покое, позже все равно поймете как сделать лучше
2. Искать библиотечки тоже не всегда нужно. Иногда проще свелосипедить чем тащить зависимость ради одной тривиальной задачи. Тоже самое с переиспользованием. Часто лучше скопипастить, если это что-то что 100% не будет меняться, чем зависеть от других классов, проектов.
За 10+ опыта вывел для себя следующие приоритеты:
1. Простота, читабельность, понятность кода;
2. По-меньше всяких абстракций и ООП, только тогда когда они реально нужны;
3. Слабая связность и изорилорваность, даже если придется копипастить (без фанатазима), так, чтобы в случае чего код можно было безопасно выкинуть или переписать не затрагивая другого функционала системы;
4. Линейный, императивный код, в идеале такой чтобы определенную логику можно было проанализировать прочитав один файл сверху вниз один раз а не прыгать по разным файлам в разных проектах;
5. Код дружелюбен к тестам и дебагингу.
Кодинг — это как лингвистический язык (русский, английски). Должна быть культура его разговора, его обучения. Правила, стилистика, синтаксис, орфография и всё остальное, а не феня гопника.
А сосед написал тетрис на ассемблере, самый быстрый, который я когда либо видел — фигурки падали с ускорением свободного падения на 486. Да еще и с алгоритмом, когда он сам собирался.
А другой накодил сетевой 3д-шутер на VAX-VMS на псевдографике.
В той атмосфере код любой программы старались довести до совершенства. Это было как личная калиграфическая роспись.
Раньше кодинг был искусством. Помню, еще на первом курсе давали задачки: написать код на любом языке, который в качестве вывода выдаст сам себя. Сейчас так могут?
Я думаю могут если поставят себе такую цель. Особенно в университетах. Кодинг не перестал быть искусством, просто сейчас гораздо больше программистов разных уровней.
Раньше кодинг был искусством.
Так он и сейчас. Именно в этом и заключаются основные проблемы. Кодинг должен быть инженерной дисциплиной, а является — искусством.
И дело в том, что поляроид — это не сам ремесленник, а его инструмент. Нам дали полароид на время. РКН недавно заблокировал что-то, и полрунета перестало работать. Потому что полароид — ихний. Почему у нас аналогов этих гугловских сервисов нет? Свифта нет? Потому что все радостно бегают с полароидами и думают, что крутые мастера.
Полароиды делают те самые художники, ремесленник его не сделает. И Гугл и Майкрософт сразу же выдергивают из России любого начинающего художника, как увидят, на 100К плюс полный соцпакет и переезд для всей семьи.
Так что до поры до времени — да, полароид удобней.
Рено логан штампуют ремесленники, но создавали его не ремесленники.
Художник — тот, кто знает и любит свой предмет теоретически и практически, для кого важно внутреннее качество.
Ремесленник — кто использует его преимущественно для зарабатывания денег, не заботясь о внутренней красоте того, что делает.
«Внутренний» — это, в том числе, следование парадигме ООП (если берем java) и лучшим стандартам кодинга.
на первом курсе давали задачки: написать код на любом языке, который в качестве вывода выдаст сам себя. Сейчас так могут?
Довольно-таки бесполезная вещь, имхо.
В той атмосфере код любой программы старались довести до совершенства. Это было как личная калиграфическая роспись.
И сейчас стараются. Но только если данные объёмом в десятки ГБ ВНЕЗАПНО не влезают в лимит памяти 2ГБ, или бездарно написанные алгоритмы имеют сложность О(N^6) при N порядка 3000. Суровая коммерческая разработка — это вам не тетрис на PHP.
Не люблю ностальгию по системам с 640КБ. Особенно когда сравнивают "вот тогда" и "а сейчас". Сравнивать не совсем корректно: объёмы расчётов на порядки больше; компиляторы генерируют сопоставимый по скорости с ассемблером код, этого кода иногда получается аж гигабайты — столько вручную не написать; и т.д.
ООП оно и в африке ООП, к языку не привязано вроде
Во-вторых, не программеры, а языки программирования больше или меньше «тяготеют» к ООП, поэтому реализовать одну и ту же ООП структуру можно и на php, и на java, но для опытных программеров на java это естественно, а на php возможно проще процедурно решить задачу.
Тем не менее реализация ООП в php (классы, объекты, наследование) вполне себе полная. В java просто более строгая, академическая.
В общес, про ООП головного мозга не понятно. То же самое, что сказать, что у java программеров — main головного мозга. Ну да, есть немного, но это фича, а не баг.
Систему можно разбивать более чем одним способом. А еще разный способ подходит под разные задачи.
Но. Когда систему бьют, чтобы бить; когда абстракции выделяют бездумно и в огромном количестве; когда количество смысла на строку текста приближается к нулю. Вот тогда мы получаем ООП головного мозга.
Это не знаю…
У людей, долго пиливших на яве в кровавом энтерпрайзе, вырабатывается характерный стиль: очень много слоёв абстракции. Думаю, причина этого — не сам язык, а особенности процесса разработки (вероятно, обоснованные в той отрасли), они потом на любом языке так пишут.
И везде там было ООП. Не помню особо проблем с разбором чужого кода…
ООП — это образ мышления. Структурирование данных, вычленение сущностей и поиск зависимостей. Одно и то же техзадание можно перевести в архитектуру будущего ПО очень по-разному — удачно и неудачно.
Поэтому архитекторы и дизайнеры получают в несколько раз больше простых программеров.
Как например (извините, наболело) этот их «серьезный, популярный, эффективный» ангуляр, разработчики которого в один прекрасный момент просто заявляют: извините, но сделать наследование наших деклараций и инъекций слишком сложно, поэтому мы этого делать не будем. Нет, нас не волнует, что мы типа косим (туториалами, модульностью, и тэ дэ) под ООП, но не имеем реализации базовых принципов ООП. Вас волнует? Ну, ваши проблемы.
Или наоборот, создание блок-схемы приводит к автоматическому написанию кода?
А есть такой редактор кода, в котором параллельно коду автоматически рисуется блок-схема алгоритма?
Параллельно — не знаю. Но по определенной команде построить точно можно (Visual Studio).
Или наоборот, создание блок-схемы приводит к автоматическому написанию кода?
Есть. Но сгенерированный код не отличается хорошим качеством.
Не пишите лишнего
ООП с документацией и тестами
heh, classic
Опять поиски серебряной пули. Не существует универсального метода написания кода и разбивки на части. Всегда любым гайдлайнам можно найти логичное опровержение.
Проверьте, не решал ли кто-то до вас в этом проекте такую же (или сильно похожую) задачу.
Скорей всего он решал конкретную задачу без рассчета на то, что потом его код будет кто-то переиспользовать. И пытаясь переиспользовать код, вы для разных функциональных кусков вносите общую зависимость, которая потом усложнит поддержку и эволюцию каждого из них. Закопипейстить менее болезненное мероприятие.
Знайте и любите утилиты и API собственного проекта. Особенно, если ему больше полугода. Там могут найтись жемчужины, которые сэкономят вам десятки строк кода.
Там скорей всего не жемчужины, а сплошные перлы. Если чья-то утилита работала в чьем-то коде, совсем не значит, что она также заработает в вашем. Кроме того, название запросто может не соответствовать содержимому.
Ещё знайте и любите популярные библиотеки для вашего языка или платформы
Иногда только из-за одной функции подтягивают кучу зависимостей.
Код заслуживает быть вынесенным в отдельную функцию
То есть, когда была объемная, но линейная функция, отражающая какой-то процесс или алгоритм, теперь она раздраконена и раскидана по всему файлу. Это улучшит читаемость? А еще входные и выходные параметры нужно везде мепить...
Все ваши замечания очень даже по существу, и их стоит применять, организуя свой код и проект. Хотя публика может и не оценить предложение использовать копипейст :)
Отличной иллюстрацией проблем с использованием библиотек в индустрии стала истоия «левого отступа» в JavaScript, так что о ней нескоро забудут.
А вот с «обёмной, но линейной функцией» я бы поспорил. Сколь линейной бы она не была, если она не лезет в экран, её пора разделить на «шаги».
А вот с «обёмной, но линейной функцией» я бы поспорил. Сколь линейной бы она не была, если она не лезет в экран, её пора разделить на «шаги».
Вам какую книгу удобно читать — если текст последовательный (пусть и не влазит на одну страницу) или разбит по отдельным кускам-абзацам, которые хаотично и бессистемно раскиданы по всему тому?
Допускаю, что «бессистемная раскиданность» не доставляет мне проблем благодаря используемому инструменту (IDE). Читать код с большой степенью косвенности в Vim/Emacs наверное так же сложно, как читать распечатанную википедию (без браузера).
пишите лаконичный минимальный идеальный в вакууме и сразу правильный на все случаи код.
все.
— Этот метод будет вызываться один раз, значит в коде будут плюс две-четыре лишние строки на сигнатуру функции, скобки, разделяющую пустую строку и т.п., зачем мне лишние строки?
Ну и, несмотря на то, что я считаю это ужасным подходом с точки зрения качества кода, стоит признать, что формально-то он прав, кода действительно становится больше. Поэтому я удивился, когда увидел в статье
Код заслуживает быть вынесенным в отдельную функцию не только тогда, когда его планируется переиспользовать, но и в целях улучшения читаемости.
Так что я не понял посыл. С чем предлагается бороться: с количеством или низким качеством? Или под словом «код» здесь понимается только содержание методов?
Моя основная боль начинается от ООПшного комка грязи, разбросанного по разным файлам, между которыми нужно скакать, пытаясь угадать зависимости. Когда непонятно даже, куда в принципе стоит смотреть. А усиливается боль от пучка зависимостей между огромными модулями (пускай даже не слишком плохими внутри), с которыми уже вообще фиг знает, что можно сделать, и любая правка подобна магии. А ещё больнее, когда все остальные модули — не твоя зона ответственности, и ничего менять в них по регламенту тебе не положено. Ещё хлеще, если часть этих модулей вообще проприетарное ПО с закрытым кодом, и ничего там нельзя изменить в принципе.
Есть объект, его свойства, есть необходимые методы — паблик, протектед — интерфейс к нему, Остальное разбивается на прайвет.
Во главе — объект, его свойства и методы. А что написано в методе волновать должно только тех, кто этот метод будет дополнять/переписывать.
Если ваш программер выдает рабочие готовые библиотеки, нечитаемость его кода терпима. Если это команда — обязательны правила для всех. В противном случае — желательны.(премируйте грамотно пишущих)
Если программист (1)выдаёт идеально работающий продукт, который (2)не нужно будет дорабатывать, тогда конечно не так важно, как оно написано. Первое происходит обычно никогда, а второе — обычно когда продукт никому не нужен.
Если же так все экономить фанатично, то даже, казалось бы, линейная и длинная простыня в методе, которую сверху вниз прочитать можно, становится расплывчитым набором символов и ситуация еще больше усугубляется.
Я вот большую часть времени вообще думаю о том, чем являются вещи и как они работают в реальном мире. Рисую диаграмки там всякие… наверное я не программист )
Очень важным, критическим является момент, когда программист (не важно, молодой или старый) приходит к осознанию, что код пишется не для машины, а для других людей. Тогда начинается выделение в логические блоки, отдельные методы, появляются комментарии, документация, переиспользование классов и тому подобное. Программист перестаёт щеголять синтаксическими хуками и способностями к скорописанию в одну строку, а наоборот, старается избежать самых очевидных граблей, когда этот код будут читать через год-два (возможно, даже он будет читать, позабыв подробности и помня только общую канву).
Они подразумевают идеальное качество всего предыдущего кода проекта, законченности внутренних библиотек, справедливости их документации и близкого к 100% покрытия тестами. Иначе, вместо того, чтобы написать лишние 200-500 строчек, мы можем залезть не в «жемчужину», а в ведро червей, где выяснится столько проблем, о которых умалчивали (и забыли), что время на их исправление вырастет в десятки раз.
Особенно часто это на моей практике случалось в следующем сценарии: в результате рефакторинга, несколько лет назад, из проекта вынесли определенный логически свзяанный функционал в отдельную библиотеку. Ее оттестировали, задокументировали, порадовались, забыли. И теперь возникла задача, которая в теории относится именно к этой библиотеке, решается частью ее функционала. Так вот во всех случаях это приведет к тому, что библиотека-то работала не правильно. Придется исправлять, переделывать тесты, документацию, заново перепроверять вручную, и вроде бы все это об улучшении общего качества кода, только вот:
Бизнесу это не нужно. Сданные задачи считаются сданными. Как бы мы не верили в чудодейственную силу agile, но убедить тех, кто был счастливым свидетелем выполненной работы, в том, что ее необходимо существенно доделать — плохая идея.
Вам это не нужно. Ведро червей — это ведро червей. В своем велосипеде на 200-500 строчек вы бы могли применить несколько изящных идей, которые вам правда нравятся, а вот в чужом коде вы в любом случае будете считать wtf/minute.
Продукту это не нужно. Он не проживет столько времени, чтобы разница в поддерживаемости лишних 200 строчек стала по нему бить. Зато старый его функционал будет гарантированно делать ровно то же, что и раньше, даже если вы не знали, что именно (а пользователи вот знали, и любили).
Большая часть проблем энтерпрайза — это как раз подобные проблемы и холивары на эту тему. Я правда, по той причине его и покинул, ибо принцип write it once,
Возьмем суровую бизнес-логику расчета зарплаты. Желающие могут прочитать статью налогового кодекса про НДФЛ. В 1С Зарплате 2.5 были гигантские запросы на 10 килобайт текста, в которых вычислялось что нужно. В текущей 1С Зарплате 3.0 от гигантских запросов ушли и текст запроса строится горкой процедур из разных подзапросов, но тут есть проблемы что построение запроса это глубина стека в 26 вызовов и 60 подзапросов, причем многие из подзапросов это один и тот же подзапрос с разным именем (например, определение списка сотрудников, к которому потом присоединяются данные из других таблиц). С точки зрения исправления ошибок путь 2.5 проще, ты вдумчиво изучил запрос и наступило счастье. В 3.0 тебе приходится в отладчике идти по всему стеку вызовов, чтобы найти ту самую процедуру, в которой появляются некорректные данные. А когда по кругу вызывается одна и та же процедура, то мозг теряется. )
Трассировка выявила, что было вызвано 384 процедуры с максимальной глубиной вложенности 64. Как в этом графе вызовов искать, что пошло не так?
Не знаю как в SAP R3, а в 1с есть визуальный конструктор запросов, который умеет превращать простыню текста в объекты и генерировать текст запроса взад.
Не пишите лишнего