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

106

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

Я следовал некоторым рекомендациям, рекомендованным в книге «Чистый код» Роберта Мартина, особенно тем, которые относятся к типу программного обеспечения, с которым я работаю, и тем, которые имеют смысл для меня (я не придерживаюсь этого как догма) ,

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

  • Инкапсулирующие условия

Так что вместо

if(contact.email != null && contact.emails.contains('@')

Я мог бы написать небольшой метод, как это

private Boolean isEmailValid(String email){...}
  • Замена встроенного комментария другим закрытым методом, чтобы имя метода описывало себя, а не добавляло встроенный комментарий поверх него
  • У класса должна быть только одна причина для изменения

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

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

Конкретный вопрос, на который я ищу ответ:

Является ли это приемлемым побочным продуктом написания чистого кода? Если да, то какие аргументы я могу использовать, чтобы оправдать тот факт, что было написано больше LOC?

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

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

Это особая проблема ... эти классы могут быть единым классом, который все еще достигает "одной вещи", без помощи множества маленьких методов. Это может быть один класс с 3 или 4 методами и некоторыми комментариями.

CommonCoreTawan
источник
98
Если ваша организация использует только LOC в качестве метрики для баз кода, то оправдание чистого кода для начала безнадежно.
Килиан Фот
24
Если ваша цель - ремонтопригодность, то LOC - не лучший показатель для оценки - это один из них, но нужно рассмотреть гораздо больше, чем просто сократить его.
Зиббобз
29
Это не ответ, но необходимо сделать следующее замечание. Существует целое сообщество людей, пишущих код с минимальным количеством строк / символов. codegolf.stackexchange.com Можно утверждать, что большинство ответов там не так читабельно, как они могли бы быть.
Антифеос
14
Узнайте причины каждой лучшей практики, а не только сами правила. Следовать правилам без причины - это Грузовой культ. У каждого правила есть своя причина.
Герман
9
Помимо всего прочего, и, используя ваш пример, иногда проталкивание вещей в методы заставит вас задуматься: «Может быть, есть библиотечная функция, которая может сделать это». Например, чтобы проверить адрес электронной почты, вы можете создать System.Net.Mail.MailAddress, который будет проверять его для вас. Вы можете (надеюсь) доверять автору этой библиотеки, чтобы понять это правильно. Это означает, что ваша кодовая база будет иметь больше абстракций и уменьшаться в размере.
Грегори Керри

Ответы:

130

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

Эти люди правильно определили что-то: они хотят, чтобы код был легче поддерживать. Хотя они ошиблись, предполагая, что чем меньше кода, тем легче его поддерживать.

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

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

Дэвид Арно
источник
27
«Безусловно, самый простой способ получить простой в изменении код - это иметь полный набор автоматических тестов для него, которые не пройдут, если ваши изменения будут критическими». Это просто неправда. Тесты требуют дополнительной работы для каждого поведенческого изменения, потому что тесты также должны меняться , это из-за замысла, и многие утверждают, что делает изменения более безопасными, но это также обязательно усложняет изменения.
Джек Эйдли
63
Конечно, но время, потраченное на поддержание этих тестов, уменьшается по сравнению с тем временем, когда вы потеряли бы диагностику и исправление ошибок, предотвращаемых тестами.
MetaFight
29
@JackAidley, необходимость изменения тестов вместе с кодом может создать видимость дополнительной работы, но только если игнорировать трудно обнаруживаемые ошибки, которые внесут изменения в непроверенный код и которые часто не будут обнаружены до тех пор, пока не будет отправлено , Последний просто предлагает иллюзию меньшего количества работы.
Дэвид Арно
31
@JackAidley, я с тобой полностью не согласен. Тесты облегчают изменение кода. Я признаю, что плохо спроектированный код, который слишком тесно связан и, следовательно, тесно связан с тестами, может быть трудно изменить, но хорошо структурированный, хорошо протестированный код легко изменить в моем опыте.
Дэвид Арно
22
@JackAidley Вы можете много реорганизовать, не меняя API или интерфейс. Это означает, что вы можете сойти с ума при изменении кода без необходимости изменения одной строки в модульных или функциональных тестах. То есть, если ваши тесты не тестируют конкретную реализацию.
Эрик Думинил
155

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

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

А затем подумайте, что нужно сделать, если когда-нибудь потребуется изменить правила проверки электронной почты, что вы бы предпочли: одно известное местоположение; или много мест, возможно, пропустив несколько?

Карл Билефельдт
источник
10
гораздо лучше ответить, чем утомительное «модульное тестирование решит все ваши проблемы»
Дирк Бур
13
Этот ответ затрагивает ключевой момент, которого дяде Бобу и его друзьям всегда не хватает - рефакторинг на маленькие методы помогает, только если вам не нужно читать все маленькие методы, чтобы выяснить, что делает ваш код. Создание отдельного класса для проверки адресов электронной почты целесообразно. Натяжение код iterations < _maxIterationsв метод называется ShouldContinueToIterateэто глупо .
Би Джей Майерс
4
@DavidArno: «быть полезным»! = «Решает все ваши проблемы»
Кристиан Хакл
2
@DavidArno: Когда кто-то жалуется на людей, подразумевающих, что модульное тестирование «решает все ваши проблемы», они, очевидно, подразумевают людей, которые подразумевают, что модульное тестирование решает или, по крайней мере, способствует решению практически всех проблем в разработке программного обеспечения. Я думаю, что никто не обвиняет никого в предложении юнит-тестирования как способа положить конец войне, нищете и болезням. Иными словами, крайняя переоценка модульного тестирования во многих ответах, не только на этот вопрос, но и на SE в целом, подвергается (по праву) критике.
Кристиан Хакл
2
Привет @DavidArno, мой комментарий был явно гиперболой, а не трубочником;) Для меня это так: я спрашиваю, как починить мою машину, и религиозные люди приходят и говорят мне, что я должен жить менее греховной жизнью. В теории кое-что стоит обсудить, но на самом деле это не помогает мне стать лучше в ремонте автомобилей.
Дирк Бур
34

Известно, что Биллу Гейтсу приписывают: «Измерение прогресса в программировании с помощью строк кода похоже на измерение прогресса в самолетостроении по весу»

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

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

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

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

Нил
источник
1
Вес самолета - важный показатель, все же. И во время проектирования ожидаемый вес тщательно контролируется. Не как признак прогресса, а как ограничение. Контрольные строки кода показывают, что чем больше, тем лучше, в то время как в конструкции самолета меньший вес лучше Поэтому я думаю, что мистер Гейтс мог бы выбрать лучшую иллюстрацию для своей точки зрения.
Jos
21
@jos в конкретной команде, с которой работает OP, кажется, что меньшее количество LOC считается «лучшим». Дело в том, что Билл Гейтс говорил о том, что LOC не имеет никакого отношения к прогрессу каким-либо значимым образом, точно так же, как вес самолета не связан с прогрессом значимым образом. Строящийся самолет может сравнительно быстро набрать 95% своего конечного веса, но это будет просто пустой корпус без систем управления, он не завершен на 95%. То же самое в программном обеспечении, если программа имеет 100 000 строк кода, это не означает, что каждая 1000 строк обеспечивает 1% функциональности.
Мистер Миндор
7
Мониторинг прогресса - сложная работа, не так ли? Бедные менеджеры.
Jos
@jos: в коде также лучше иметь меньше строк для той же функциональности, если все остальное равно.
RemcoGerlich
@jos Читайте внимательно. Гейтс ничего не говорит о том, является ли вес важной мерой для самого самолета. Он говорит, что вес - ужасная мера для прогресса в создании самолета. В конце концов, с помощью этой меры, как только вы бросили весь корпус на землю, вы в основном сделали это, так как это, предположительно, составляет 9x% от веса всего самолета.
Во
23

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

Это вопрос потери зрения на действительную цель.

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

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


логирование

Возьмите приложение (A), которое не имеет регистрации. Теперь создайте приложение B, которое является тем же приложением A, но с регистрацией. В B всегда будет больше строк кода, и поэтому вам нужно написать больше кода.

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

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

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

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


Регрессия

Возьмите приложение А, которое совсем не СУХОЙ.
Возьмите приложение B, которое СУХОЕ, но в итоге потребовалось больше строк из-за дополнительных абстракций.

Запрос на изменение подан, что требует изменения в логике.

Для приложения B разработчик изменяет (уникальную совместно используемую) логику в соответствии с запросом на изменение.

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

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

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


Взаимозаменяемость разработчиков

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

Разработчик А отсутствует в течение месяца из-за выходных. Запрос на экстренное изменение подан. Это не может ждать еще три недели, пока Dev A вернется.

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

В то же время приложение B (созданное разработчиком B) имеет чрезвычайную ситуацию. Dev B занят, но Dev C доступен, хотя он не знает кодовой базы. Что мы делаем?

  • Если мы продолжим работу B над A и включим C в работу над B, то у нас есть два разработчика, которые не знают, что делают, и работа выполняется недостаточно оптимально.
  • Если мы отодвинем B от A и попросим его выполнить B, а теперь мы поставим C на A, то вся работа разработчика B (или значительная ее часть) может в итоге быть отброшена. Это потенциально потерянные дни / недели усилий.

Dev A возвращается из отпуска и видит, что B не понимает код и поэтому плохо его реализовал. Это не вина Б, потому что он использовал все доступные ресурсы, исходный код просто не был адекватно читаемым. А теперь нужно потратить время на исправление читабельности кода?


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

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

Если да, то какие аргументы я могу использовать, чтобы оправдать тот факт, что было написано больше LOC?

Мое объяснение состоит в том, чтобы спросить руководство, что бы они предпочли: приложение с базой кода 100KLOC, которое может быть разработано за три месяца, или базой кода 50KLOC, которое может быть разработано за шесть месяцев.

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

Flater
источник
23

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

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

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

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

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

Я бы сказал, если сомневаешься, всегда выбирай самый простой и понятный код.

JacquesB
источник
7
Я должен согласиться, что превращение каждого условного выражения в метод валидации может привести к более нежелательной сложности, когда речь заходит об обслуживании и проверке кода. Теперь вам нужно переключаться в коде, чтобы убедиться, что ваши условные методы верны. А что происходит, когда у вас разные условия для одного и того же значения? Теперь у вас может возникнуть кошмар именования с несколькими небольшими методами, которые вызываются только один раз и выглядят в основном одинаково.
pboss3010
7
Легко лучший ответ здесь. Особенно замечание (в третьем абзаце), что сложность - это не просто свойство всего кода в целом, а нечто, что существует и отличается одновременно на нескольких уровнях абстракции.
Кристиан Хакл
2
Я думаю, что один из способов выразить это заключается в том, что в общем случае извлечение условия должно выполняться только в том случае, если для этого условия есть значимое, не запутанное имя. Это необходимое, но не достаточное условие.
JimmyJames
Re "... потому что теперь у вас есть функция, которая видна из большего количества точек в программе" : в Паскале можно иметь локальные функции - "... Каждая процедура или функция может иметь свои собственные объявления меток goto, констант , типы, переменные и другие процедуры и функции, ... "
Питер Мортенсен
2
@PeterMortensen: это также возможно в C # и JavaScript. И это здорово! Но дело в том, что функция, даже локальная функция, видна в большем объеме, чем фрагмент встроенного кода.
JacquesB
9

Я бы отметил, что в этом нет ничего плохого:

if(contact.email != null && contact.email.contains('@')

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

У меня могут быть проблемы с этим очень легко:

private Boolean isEmailValid(String email){
   return email != null && email.contains('@');
}

Несколько вещей, за которыми я бы следил:

  1. Почему это личное? Это похоже на потенциально полезную заглушку. Достаточно ли это полезно, чтобы быть частным методом, и нет никаких шансов его более широкого использования?
  2. Я бы не назвал метод IsValidEmail лично, возможно, ContainsAtSign или LooksVaguelyLikeEmailAddress, потому что он практически не выполняет реальную проверку, что, возможно, хорошо, а может, и не то, что ожидается.
  3. Используется ли он более одного раза?

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

С другой стороны, я видел, как методы делают что-то вроде этого:

if (contact.email != null && contact.email.contains('@')) { ... }
else if (contact.email != null && contact.email.contains('@') && contact.email.contains("@mydomain.com")) { //headquarters email }
else if (contact.email != null && contact.email.contains('@') && (contact.email.contains("@news.mydomain.com") || contact.email.contains("@design.mydomain.com") ) { //internal contract teams }

Этот пример явно не СУХОЙ.

Или даже только это последнее утверждение может привести другой пример:

if (contact.email != null && contact.email.contains('@') && (contact.email.contains("@news.mydomain.com") || contact.email.contains("@design.mydomain.com") )

Цель должна состоять в том, чтобы сделать код более читабельным:

if (LooksSortaLikeAnEmail(contact.Email)) { ... }
else if (LooksLikeFromHeadquarters(contact.Email)) { ... }
else if (LooksLikeInternalEmail(contact.Email)) { ... }

Другой сценарий:

У вас может быть такой метод:

public void SaveContact(Contact contact){
   if (contact.email != null && contact.email.contains('@'))
   {
       contacts.Add(contact);
       contacts.Save();
   }
}

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

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

Вы будете рады, что сделали это, когда вам также нужно будет создать учетную запись второй электронной почты президентов Pr3 $ sid3nt @ h0m3! @ Mydomain.com и решить просто приложить все усилия и попытаться поддержать RFC 2822.

По читабельности:

// If there is an email property and it contains an @ sign then process
if (contact.email != null && contact.email.contains('@'))

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

// The UI passes '@' by default, the DBA's made this column non-nullable but 
// marketing is currently more concerned with other fields and '@' default is OK
if (contact.email != null && contact.email.contains('@'))

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

В итоге: не измеряйте эти вещи; Сосредоточьтесь на принципах, из которых построен текст (СУХОЙ, ТВЕРДЫЙ, ПОЦЕЛУЙ)

// A valid class that does nothing
public class Nothing 
{

}
AthomSfere
источник
3
Whether the comments above an if statement or inside a tiny method is to me, pedantic.Это проблема «соломы, которая сломала спину верблюду». Вы правы, что эту вещь не особенно трудно прочитать прямо. Но если у вас есть большой метод (например , большой импорт) , который имеет десятки этих мелких оценок, имея эти инкапсулированные в читаемых имен методов ( IsUserActive, GetAverageIncome, MustBeDeleted...) будет заметное улучшение при чтении кода. Проблема с примером состоит в том, что он наблюдает только одну соломинку, а не весь пакет, который ломает спину верблюда.
Флатер
@Flater, и я надеюсь, что это тот дух, который читатель берет из этого.
AthomSfere
1
Эта «инкапсуляция» является анти-паттерном, и ответ на самом деле демонстрирует это. Мы возвращаемся, чтобы прочитать код в целях отладки и в целях расширения кода. В обоих случаях понимание того, что на самом деле делает код, имеет решающее значение. Старт блока кода if (contact.email != null && contact.email.contains('@'))глючит. Если if ложно, ни одна из остальных строк if не может быть истиной. Это совсем не видно в LooksSortaLikeAnEmailблоке. Функция, содержащая одну строку кода, не намного лучше, чем комментарий, объясняющий, как работает эта строка.
Причуды
1
В лучшем случае, другой уровень косвенности скрывает действительную механику и усложняет отладку. В худшем случае имя функции стало ложью, точно так же, как комментарии превратились в ложь - содержимое обновляется, а имя - нет. Это не удар по инкапсуляции в целом, но эта идиома является симптомом большой современной проблемы с «корпоративной» разработкой программного обеспечения - слоями и уровнями абстракции и склеиванием соответствующей логики.
Причуд
@quirk Я думаю, ты согласен с моим общим мнением? А с клеем у тебя совсем другая проблема. Я на самом деле использую карты кодов, когда смотрю на новый код команд. Это ужасно плохо, что я видел, что сделал для некоторых больших методов, вызывающих серию больших методов даже на уровне шаблона mvc.
AthomSfere
6

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

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

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

boolean isEmailValid = (contact.email != null && contact.emails.contains('@');

if (isEmailValid) {
...

Это помогает легко следовать коду без необходимости много перемещаться по файлу.

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

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

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

Строки кода как метрика бессмысленны. Важными вопросами в разработке программного обеспечения не являются:

  • У тебя слишком много кода?
  • У вас слишком мало кода?

Важные вопросы:

  • Правильно ли оформлено приложение в целом?
  • Правильно ли реализован код?
  • Поддерживается ли код?
  • Код проверяем?
  • Код адекватно протестирован?

Я никогда не думал о LoC, когда писал код для каких-либо целей, кроме Code Golf. Я спрашивал себя: «Могу ли я написать это более кратко?», Но в целях удобства чтения, удобства обслуживания и эффективности, а не просто длины.

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

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

Клемент Черлин
источник
3

На одном уровне они правы - чем меньше кода, тем лучше. Еще один ответ процитировал Gate, я предпочитаю:

«Если отладка - это процесс устранения программных ошибок, то программирование должно быть процессом их исправления». - Эдсгер Дейкстра

«При отладке новички вставляют корректирующий код; эксперты удаляют дефектный код ». - Ричард Паттис

Самые дешевые, быстрые и надежные компоненты - это те, которых нет. - Гордон Белл

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

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

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

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

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

Baldrickk
источник
2

В существующих ответах много мудрости, но я хотел бы добавить еще один фактор: язык .

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

Например, в Java может легко потребоваться 50 строк, чтобы написать новый класс с тремя свойствами, каждое с геттером и сеттером, и одним или несколькими конструкторами, в то время как вы можете сделать то же самое в одной строке Kotlin * или Scala. (Еще больше экономии , если вы также хотели подходящие equals(), hashCode()и toString()методы.)

Результатом является то, что в Java дополнительная работа означает, что вы с большей вероятностью будете повторно использовать общий объект, который на самом деле не подходит, втиснуть свойства в существующие объекты или передать кучу «голых» свойств по отдельности; в то время как в кратком, выразительном языке, вы с большей вероятностью будете писать лучший код.

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

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

(* На самом деле это не место для вилки, но Котлин стоит посмотреть ИМХО.)

gidds
источник
1

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

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


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

private void LargeMethod() {
    //A lot of code which modifies a lot of values. You do all sorts of tricks here.
    //Code.
    //Code..
    //Code...

    //Email validation code becoming very difficult to test as it will be difficult to ensure 
    //that you have the right data till you reach here in the method
    ValidateEmail();

    //Another whole lot of code that modifies all sorts of values.
    //Extra work to preserve the result of ValidateEmail() for your asserts later.
}

С другой стороны, если у вас был отдельный класс электронной почты с методом для проверки электронной почты, то для модульного тестирования вашего кода проверки вы просто сделали бы один простой вызов Email.Validation()с вашими тестовыми данными.


Бонусный контент: разговор MFeather о глубокой синергии между тестируемостью и хорошим дизайном.

отображаемое имя
источник
1

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

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

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

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

JimmyJames
источник
1

Вы определили действительный компромисс

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

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

В этих случаях жадный алгоритм может потерпеть неудачу

Сложность также сделала код в определенном смысле менее читабельным, чем более, В предыдущей работе я имел дело с HTTP API, который был очень аккуратно и точно структурирован в несколько уровней, каждая конечная точка определяется контроллером, который проверяет форму входящего сообщения, а затем передает его какому-то менеджеру «уровня бизнес-логики» , который затем сделал некоторый запрос некоторого «уровня данных», который отвечал за выполнение нескольких запросов к некоторому уровню «объекта доступа к данным», который отвечал за создание нескольких делегатов SQL, которые фактически ответили бы на ваш вопрос. Первое, что я могу сказать по этому поводу, было то, что примерно 90% кода было шаблоном копирования и вставки, другими словами, это не было никаких операций. Поэтому во многих случаях чтение любого фрагмента кода было очень «легким», потому что «о, этот менеджер просто перенаправляет запрос к этому объекту доступа к данным».много переключения контекста и поиска файлов и попытки отследить информацию, которую вы никогда не должны были отслеживать, "это называется X на этом уровне, он становится называемым X" на этом другом уровне, затем он называется X "на этом другом другой слой. "

Я думаю, что когда я остановился, этот простой CRUD API был на той стадии, когда, если вы напечатали его по 30 строк на страницу, на полке потребовалось бы 10-20 учебников по пятьсот страниц: это была целая энциклопедия повторяющихся код. Что касается существенной сложности, я не уверен, что там была даже половина учебника существенной сложности; у нас было всего 5-6 диаграмм базы данных, чтобы справиться с этим. Внесение в него каких-либо незначительных изменений было гигантским начинанием, изучение того, что это было гигантским начинанием, добавление новых функций стало настолько болезненным, что у нас фактически были шаблоны шаблонных файлов, которые мы будем использовать для добавления новых функций.

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

Лучшая практика: используйте СУХОЙ и длины, чтобы сделать звонок

(Примечание. Заголовок этого раздела в некотором роде шутка; я часто говорю своим друзьям, что когда кто-то говорит: «Мы должны сделать X, потому что лучшие практики так говорят », они в 90% случаев не говорят о чем-то вроде внедрения SQL или хеширования пароля. или что-то еще - односторонние лучшие практики - и таким образом утверждение может быть переведено в эти 90% времени на «мы должны сделать X, потому что я так говорю ». Как будто у них может быть какая-то статья в блоге от какого-то бизнеса, который сделал лучшую работу с X, а не с X ', но, как правило, нет гарантии, что ваш бизнес похож на этот бизнес, и, как правило, есть какая-то другая статья из другого бизнеса, которая лучше справилась с X', чем с X. Поэтому, пожалуйста, не берите название слишком шутки в сторону.)

То , что я рекомендовал бы основан на разговоре Джек Дидерих называется Классы Stop Пишущие (youtube.com) . В этом выступлении он сделал несколько важных замечаний: например, вы можете знать, что класс на самом деле является просто функцией, когда у него есть только два открытых метода, и один из них - конструктор / инициализатор. Но в одном случае он говорит о том, что гипотетическая библиотека, которую он заменил строкой для разговора как «Маффин», объявила свой собственный класс «MuffinHash», который был подклассом встроенного dictтипа, который есть в Python. Реализация была совершенно пустой - кто-то только что подумал: «Возможно, нам понадобится добавить пользовательские функции в словари Python позже, давайте на всякий случай представим абстракцию прямо сейчас».

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

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

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

  1. Реорганизовать проверку в функцию / метод, когда вы хотите скопировать и вставить его. Например, иногда есть веские причины для копирования-вставки, но вы всегда должны чувствовать себя грязно. Настоящие авторы не заставляют вас перечитывать большое длинное предложение 50 раз на протяжении всего повествования, если только они действительно не пытаются выделить тему.
  2. Функция / метод в идеале должна быть «абзацем». Большинство функций должно занимать около половины страницы или 1-15 строк кода, и только 10% ваших функций должны иметь диапазон от полутора страниц до 45 строк или более. Когда вы наберете более 120 строк кода и комментариев, эту вещь нужно разбить на части.
  3. Файл в идеале должен быть "главой". Большинство файлов должно быть 12 страниц или меньше, так что 360 строк кода и комментариев. Только 10% ваших файлов должны иметь длину до 50 страниц или 1500 строк кода и комментариев.
  4. В идеале большая часть вашего кода должна иметь отступ базовой линии функции или один уровень глубины. Исходя из некоторой эвристики относительно дерева исходных текстов Linux, если вы не сомневаетесь в этом, только 10% вашего кода должны иметь отступ с 2 уровнями или более в пределах базовой линии, менее 5% с отступом 3 уровня или более. В частности, это означает, что вещи, которые должны «обернуть» некоторые другие проблемы, такие как обработка ошибок в большой попытке / уловке, должны быть извлечены из реальной логики.

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

ЧР Дрост
источник