Почему люди так сильно противятся тегам #region в методах?

27

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

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

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

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

Мысли?

jjoelson
источник
4
В настоящее время я работаю с довольно большим классом. Он следует принципу единой ответственности; все в классе требуется функциональность. У него много частных методов, в основном из-за рефакторинга. Я использую #region для разделения этих методов на функциональные категории, что очень хорошо сработало для нас. В этом проекте есть много других классов, которые не нуждались в таком лечении. Правильный инструмент для правильной работы, говорю я.
Роберт Харви
4
@RobertHarvey, их использование в классе отличается от использования их внутри метода.
CaffGeek
18
@ Чед: Ах, не заметил этого. Использование их в методе кажется немного экстремальным. Если метод настолько длинный, что требует #regions, я реорганизую его в отдельные методы.
Роберт Харви
8
Не совсем ответ, но я не только ненавижу все #regionтеги, но и отключаю свертывание кода в Visual Studio. Мне не нравится код, который пытается скрыть от меня.
Даниэль Приден
2
«Кроме того, каждый из этих этапов имеет отношение только к вычислениям для этого метода, и поэтому извлечение их в новые методы не дает нам повторного использования кода». OTOH, я обнаружил, что если я разделю функцию на 3, я позже обнаружу, что отдельные части <i> являются </ i> полезными позже, например. если некоторые входные данные изменяют только одну фазу, вы можете проверить это изолированно и т. д.
Джек В.

Ответы:

65

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

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

  1. Вы не можете тестировать регионы, но вы можете тестировать истинные методы изолированно.
  2. Регионы - это комментарии, а не синтаксические элементы. В худшем случае вложенность ваших регионов и ваших блоков противоречит друг другу. Вы всегда должны стремиться представлять семантику своей структуры с помощью синтаксических элементов языка, который вы используете.
  3. После рефакторинга к методам больше не нужно складывать, чтобы прочитать текст. Кто-то может искать исходный код в любом инструменте, который не имеет свертывания, например в терминале, почтовом клиенте, веб-представлении для вашей VCS или diff-viewer.
  4. После рефакторинга к методам результирующий метод, по крайней мере, так же хорош, как и для регионов, плюс проще перемещать отдельные части.
  5. Принцип единой ответственности предполагает, что любое подразделение должно иметь одну задачу и только одну задачу. Задача метода main состоит в том, чтобы составить вспомогательные методы для получения желаемого результата. «Вспомогательные» методы решают дискретную, простую проблему изолированно. Класс, содержащий все эти методы, должен также выполнять только одну задачу и содержать только методы, связанные с этой задачей. Таким образом, вспомогательные методы либо входят в область действия класса, либо код не должен быть в классе в первую очередь, но, по крайней мере, в каком-то глобальном методе или, что еще лучше, должен быть введен .

Также актуально: мысли Джеффа Этвудса о свертывании кода

back2dos
источник
9
1. Большинство программистов не тестируют все частные методы. 2. Имя метода не может передать все, что нужно знать об этом методе; какие исключения могут быть выброшены? Является ли значение NULL допустимым? Иногда можно использовать несинтаксические конструкции, чтобы сделать ваш код более понятным. Во многих языках файлы являются примером несинтаксического элемента, который довольно широко принят в качестве средства для организации кода. 3. Погружение во внутреннюю работу метода лучше всего предпринимать в IDE по ряду причин; ситуации, когда регионы могут укусить вас в вашем VCS или почтовом клиенте, довольно редки.
Джоэлсон
3
4. Но вы только что экспортировали некоторую серьезную сложность в остальную часть вашего класса. Это того стоит? Кроме того, регионы можно перемещать так же легко, как вызов метода. 5. Теперь вы говорите о загрязнении вашего пространства имен большим количеством классов, которые будут использоваться только в одном месте. Почему классы и методы являются единственными единицами инкапсуляции, которые вы принимаете? Вы всегда можете создать этот новый класс позже, когда (или если) возникнет необходимость иметь этот отдельный класс.
Джоэлсон
7
@jjoelson: 1. И что? 2. В точности моя точка зрения: вполне допустимо использовать несинтаксические конструкции, если (и только если) одного синтаксиса недостаточно. 3. "Редкий?" - Я полагаю, у вас есть цифры, чтобы показать это. Я могу с уверенностью сказать, что инструмент diff (или обвинение, или что-то еще) в комплекте с TortoiseSVN не имеет сворачивания. 4. Как это связано с тем, что я высказал? 5. Именно поэтому большинство современных языков имеют вложенные пространства имен. Вы прибегаете к искусству ASCII, а не используете широкую палитру доступных языковых элементов для структурирования вашего кода.
back2dos
4
@jjoelson: 1. Это ваше мнение и выходит за рамки этого вопроса. 2. По расширению моего аргумента, вложенные функции лучше, чем регионы, да. 3. Потому что иногда мне нужно просмотреть несколько ревизий, чтобы отследить проблему. В любом случае, исходный код написан для людей, а не для IDE. 4. С одной стороны, аргумент не связан с моей точкой зрения, во-вторых, это опять-таки только ваше мнение, в-третьих, это выходит за рамки этого вопроса. 5. C # имеет другие средства структурирования кода, чем вложенная функция. Вы должны либо использовать их, либо язык, который позволяет вложения функций.
back2dos
13
Вы, ребята, должны взять это в чат, если вы все еще хотите спорить ... У меня нет ничего, что стоило бы сказать ОП, он типичный юный разработчик, убежденный в своих убеждениях. Ничто не изменит его мнение, но больше опыта ИМО.
maple_shaft
15

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

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

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

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

Если вы взломаете методы, вы получите много преимуществ:

  • Более четкое понимание того, что происходит, где, даже если только через название метода. И вы неправильно , что метод не может быть полностью документированы - добавить блок документации (HIT ///) и введите описание, параметры, тип возвращаемого значения, а также exception, example, remarksузлы подробно эти сценарии. Вот куда это идет, вот для чего оно!
  • Там нет никаких побочных эффектов или проблем с ограничением объема. Вы можете сказать, что объявляете все свои переменные в области действия с помощью {}, но должен ли я проверять каждый метод, чтобы убедиться, что вы не «обманули»? Это dodgyObjectя использую здесь только потому, что используется в этом регионе, или это откуда-то еще? Если бы я был по своему собственному методу, я бы легко мог увидеть, был ли он передан мне или я сам его создал (или, скорее, вы его создали).
  • Мой журнал событий сообщает мне, в каком методе произошло исключение. Да, я могу найти код с ошибкой с номером строки, но знание имени метода (особенно, если я их правильно назвал) дает мне очень хорошее представление о что случилось и что пошло не так. «О, TurnThingsUpsideDownметод не удался - возможно, он проваливается, но плохо получается» - это гораздо лучше, чем «О, DoEverythingметод не удался - это могло быть 50 разных вещей, и мне придется поискать его в течение часа» ,

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

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

Кирк Бродхерст
источник
Синтаксический анализ комментариев Visual Studio вместе с тегами #region подпадает под категорию функций Visual Studio. Весь мой смысл в том, что нельзя полагаться на эти функции, если все в вашем проекте используют Visual Studio. Что касается побочных эффектов и проблем с областями видимости, вы все равно можете облажаться с глобальными полями. В любом случае вы должны полагаться на программистов, чтобы они не делали глупостей с побочными эффектами.
Джоэлсон
5
@jjoelson Вы все еще можете читать комментарии XML без визуальной студии, но теги региона почти полностью бесполезны за пределами VS. Логическим продолжением вашего аргумента является один гигантский метод, который запускает все ваше приложение, если вы разделите его на регионы!
Кирк Бродхерст
2
@jjoelson, пожалуйста, не начинайте нас о том, насколько плохи глобальные поля. Сказать что-то бесполезно, потому что у вас есть «обходной путь», чтобы все испортить - это просто глупо. Просто держите ваши методы короткими и переменные ограничены
OliverS
@OliverS, Кирк был тем, кто поднял общее состояние как проблему с моей схемой регионов в методе. По сути, я говорил именно то, что вы говорите: тот факт, что программист может злоупотреблять состоянием, разделяя слишком много переменных между регионами, не является обвинительным заключением для всей схемы, так как возможность испортить состояние, разделяемое между методами через глобальные переменные, не существует. Обвинение в схеме с несколькими методами.
Джоэлсон
7

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

«Но тогда у моего класса будет только один публичный метод!»

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

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

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

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

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

Какая? Вы не используете инъекцию зависимости? Ну, вы, вероятно, еще не видели необходимости, потому что вы не следуете принципу единой ответственности. Как только вы начнете, вы обнаружите, что самый простой способ дать каждому классу свои зависимости - это использовать DI-контейнер.

РЕДАКТИРОВАТЬ :

Хорошо, давайте отложим вопрос рефакторинга. Цель #regionсостоит в том, чтобы скрыть код , который в основном означает код, который не нужно редактировать ни при каких обычных условиях. Сгенерированный код является лучшим примером. Он должен быть скрыт в регионах, потому что обычно вам не нужно его редактировать. Если вам это нужно, то расширение области дает вам небольшое предупреждение в стиле «Здесь будут драконы», чтобы убедиться, что вы знаете, что делаете.

Поэтому я бы сказал, #regionчто не следует использовать в середине метода. Если я открою метод, я хочу увидеть код. Использование #region дает мне другой уровень сокрытия, который мне не нужен, и это становится раздражением: я раскрываю метод ... и все еще не вижу никакого кода.

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

//
// ---------- COMPUTE SOMETHING OR OTHER ----------
//

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

Kyralessa
источник
2
Все это для метода, который вызывается в одном месте? Даже большинство хардкорных Лисперсов не будут абстрагироваться от функции более высокого порядка, пока не определят как минимум два места, где ее можно использовать для более простого определения функции.
Джоэлсон
1
@jjoelson Так сложно создать новый класс? Что это, одна строка, одна открывающая скобка и одна закрывающая скобка. Ох, и вы должны нажатьctrl+n
Кирк Бродхерст
2
Дело не в том, что создать новый класс сложно , но есть дополнительные издержки при наличии дополнительного класса. Это еще один уровень косвенности, который усложняет разработчику кода найти то, что он ищет. Это не что большой сделки, но он также не купить вам что - нибудь в том случае , когда этот бит кода вряд ли будет вызываться из где - либо еще. И, как вы упомянули, это довольно легко сделать , что новый класс и поместить код там , если выясняется, вы делаете необходимость вызывать этот бит кода в нескольких местах.
Джоэлсон
2
@jjoelson, это довольно распространенная реакция. Это было мое, когда я впервые узнал о таких вещах, как SRP и DI, и это было реакцией моих коллег, когда мы начали разрывать зависимости. Если вы смотрите на один отдельный случай , это не стоит того. Преимущество в совокупности. Как только вы начнете выполнять SRP со всеми вашими классами, вы обнаружите, что гораздо проще изменить часть кода без изменений, затрагивающих половину вашего приложения.
Kyralessa
@Kyralessa, это методы, которые используются только в одном месте. Изменение такого метода не повлияет на половину вашего приложения. Если такой код реализован как область в методе, который его использует, то у вас будет идеальная инкапсуляция; только содержащий метод использует код, и вы можете изменять его по своему усмотрению, не беспокоясь о его влиянии, кроме как в этом методе.
Джоэлсон
4

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

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

Во-вторых, если я напишу методы A, B и C, которые используются только в методе D, тогда мне будет проще проводить модульное тестирование AB и C независимо от D, что позволяет проводить более надежные модульные тесты во всех 4 методах.

maple_shaft
источник
Модульное тестирование, безусловно, справедливо, но я не уверен, что оно оправдывает загрязнение границ класса в каждом случае. Реально говоря, кто-нибудь на самом деле тестирует каждый маленький частный метод, который они пишут? Я бы сказал, что люди склонны к юнит-тестированию частных методов, которые часто используются, то есть частных методов, которые в любом случае были бы кандидатами на повторное использование кода.
Джоэлсон
@jjoelson Если метод имеет значимую логику и не содержит каких-либо других функций, я всегда пишу для него модульный тест. Методы модульного тестирования не имеют ничего общего с повторным использованием кода. Вы должны использовать методы модульного тестирования, чтобы убедиться, что для заданных входов вы получаете ожидаемые результаты в согласованной и воспроизводимой форме. Если вы заметили, что область действия класса загрязняется, как вы ее называете, то, возможно, ваш класс нарушает принцип единой ответственности.
maple_shaft
2
Большинство программистов, с которыми я встречался, не согласны с идеей модульного тестирования каждого частного метода. Это просто не стоит времени, которое требуется для внедрения и поддержки тестов для всех частных методов.
Джоэлсон
3

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

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

Мейсон Уилер
источник
1
ctrl-f10- беги к курсору
Стивен Джеурис
2

Мое личное правило: если метод длиннее одного экрана, скажем, 40 строк, он слишком длинный. Если класс длиннее 500 строк, он слишком большой. Я иногда нарушаю эти правила, иногда даже большим числом, но обычно я сожалею об этом в течение года.

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

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

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

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

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

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

PeterAllenWebb
источник
2

Здесь мы снова ... Есть много других тем на программистов, которые оказались в той же дискуссии.

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

Тем не мение ...

Решение, на мой взгляд, определенно не охватывает «фазы» в регионах. Сворачивание кода используется, чтобы смести код под ковриком. Оставьте код там, как он есть, это может напомнить вам, что вы можете реорганизовать его позже! Чтобы связать это с аргументом в вашем вопросе: как вы сможете увидеть возможность повторного использования кода, если вы не видите кода? Длинные сегменты кода должны быть именно таким, шипом в глазу, который заставляет вас захотеть его реорганизовать.

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

Стивен Джеурис
источник
Ну, я, по сути, использую регионы для разграничения абзацев кода (у регионов могут быть комментарии, которые видны, даже когда код свернут). Мне нравится использовать регионы для этого, потому что это дает сопровождающему возможность увидеть код, если он хочет изучить реализацию, или свернуть ее, если она хочет только увидеть «общую картину».
Джоэлсон
1

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

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

class Foo : IComparable
{
  private String name;
  private int foo;
  private Bar bar;

  public int CompareTo(Foo other)
  {
#region Ascending on name
     if (this.name < other.name) return -1;
     if (this.name > other.name) return 1;
#endregion
#region Usual order on bar
     int compBar = bar.compareTo(other.bar);
     if (compBar != 0) return compBar;
#endregion
#region Descending on foo
     if (this.foo > other.foo) return -1;
     if (this.foo < other.foo) return 1;
#endregion
     return 0; // equal
  }

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

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

Сьерд
источник
0

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

  • Имена и порядок регионов делают более явным упорядочение и организацию кода, который обеспечивает особый стиль, который может стать источником разногласий и упущения.
  • Большинство концепций не вписываются в небольшое количество регионов. Как правило, вы начинаете с регионов с помощью модификаторов доступа public , private, а затем, возможно, по типу члена (например, метод, свойство, поле), но затем насчет конструкторов, которые охватывают эти модификаторы, статических разновидностей всех этих объектов, защищенных членов, внутренних членов, защищенных внутренних члены, неявные члены интерфейса, события и т. д. В итоге у вас будут наиболее хорошо разработанные классы, в которых у вас есть отдельный класс или метод в каждом регионе из-за гранулярной категоризации.
  • Если вы способны сохранять прагматичность и группировать вещи, скажем, в три-четыре последовательных типа регионов, то это может сработать, но какую ценность это действительно добавляет? Если вы разрабатываете классы хорошо, вам действительно не нужно просеивать страницы кода.
  • Как указывалось выше, регионы могут скрывать уродливый код, который может сделать его менее проблематичным, чем он есть. Хранение этого как глазная рана может помочь в обращении к этому.
jpierson
источник