Являются ли частные методы с единственным справочным плохим стилем?

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

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

138 голосов | спросил Jordak 5 J0000006Europe/Moscow 2017, 17:46:06

6 ответов


200

Нет, это не плохой стиль. На самом деле это очень хороший стиль.

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

Рассмотрим функцию, которая делает слишком много. Это ста строк длиннее и невозможно рассуждать.

Если вы разделите эту функцию на более мелкие кусочки, она все равно «делает» столько же, сколько раньше, а в меньших частях. Он вызывает другие функции, которые должны иметь описательные имена. Основная функция читается почти как книга: делать A, затем делать B, затем делать C и т. Д. Функции, которые она вызывает, могут вызываться только в одном месте, но теперь они меньше. Любая конкретная функция обязательно изолирована от других функций: у них разные области.

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

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

  • Локальность ссылки. Теперь становится невозможно объявить и использовать переменную, затем закрепить ее и снова использовать 100 строк позже. Эти функции имеют разные области.

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

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

ответил 5 J0000006Europe/Moscow 2017, 18:04:00
36

Это, наверное, отличная идея!

Я занимаюсь проблемой с разделением длинных линейных последовательностей действий на отдельные функции исключительно для уменьшения средней длины функции в вашей кодовой базе:

function step1(){
  // ...
  step2(zarb, foo, biz);
}

function step2(zarb, foo, biz){
  // ...
  step3(zarb, foo, biz, gleep);
}

function step3(zarb, foo, biz, gleep){
  // ...
}

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

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

function foo(){
  f = getFrambulation();
  g = deglorbFramb(f);
  r = reglorbulate(g);
}

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

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

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

ответил DaveGauer 5 J0000006Europe/Moscow 2017, 19:41:27
19

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

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

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

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

  • Сильно разложенный код всегда ясен для автора, чем для других. Это не означает, что он не может быть понятен другим, но легко сказать, что имеет смысл, что bar() необходимо вызвать до foo() на момент написания.

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

Разложите функции на основе их внутренней сплоченности и сцепления, а не абсолютной длины.

ответил dlasalle 5 J0000006Europe/Moscow 2017, 20:05:44
2

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

  1. Полное и точное человекоязычное описание того, что делает код, , включая способы обработки угловых случаев и

  2. Сам код.

Если код намного сложнее, чем описание, замена встроенного кода вызовом функции может облегчить понимание окружающего кода. С другой стороны, некоторые понятия могут быть более выражены на компьютерном языке, чем на человеческом языке. Я бы рассмотрел w=x+y+z;, например, как более читаемый, чем w=addThreeNumbersAssumingSumOfFirstTwoDoesntOverflow(x,y,z);.

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

ответил supercat 5 J0000006Europe/Moscow 2017, 19:45:11
2

Это балансирующий вызов.

Лучше лучше

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

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

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

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

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

Пока он не станет слишком тонким

Можно ли переусердствовать с именами небольших фрагментов кода и в итоге слишком много слишком маленьких частных методов? Конечно. Один или несколько из следующих симптомов указывают на то, что методы становятся слишком маленькими, чтобы быть полезными:

  • Слишком много перегрузок, которые на самом деле не представляют собой альтернативные подписи для одной и той же важной логики, а представляют собой один фиксированный стек вызовов.
  • Синонимы, используемые только для обозначения той же концепции неоднократно («развлекательное именование»)
  • Множество партитурных наименований, таких как XxxInternal или DoXxx, особенно если нет единой схемы представления этих файлов.
  • Неуклюжие имена почти дольше, чем сама реализация, например LogDiskSpaceConsumptionUnlessNoUpdateNeeded
ответил Jirka Hanika 8 J0000006Europe/Moscow 2017, 10:26:25
1

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

  

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

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

Почему бы не разложить на частные методы? Вот несколько причин:

  • Плотная связь и проверяемость. Уменьшая размер вашего общедоступного метода, вы улучшили его читабельность, но весь код все еще тесно связан. Вы можете тестировать отдельные частные методы (используя расширенные функции структуры тестирования), но вы не можете легко протестировать общедоступный метод независимо от частных методов. Это противоречит принципам модульного тестирования.
  • Размер и сложность класса. Вы уменьшили сложность метода, но увеличили сложность класса. Открытый метод легче читать, но теперь класс становится более трудным для чтения, поскольку он имеет больше функций, определяющих его поведение. Мои предпочтения касаются небольших классов с одной ответственностью, поэтому длинный метод является признаком того, что класс делает слишком много.
  • Нельзя использовать повторно. Часто бывает так, что, поскольку тело кода пригодно для повторного использования, полезно. Если ваши шаги находятся в частных методах, их нельзя использовать повторно нигде, не извлекая их каким-либо образом. Кроме того, он может поощрять копирование, когда требуется какой-либо шаг в другом месте.
  • Разделение таким образом, вероятно, будет произвольным. Я бы сказал, что разделение длинного общедоступного метода не требует большого внимания или дизайна, как будто вы должны разбить ответственность на классы. Каждый класс должен быть оправдан с собственным именем, документацией и тестами, тогда как частный метод не получает столько внимания.
  • Скрывает проблему. Таким образом, вы решили разделить свой общедоступный метод на небольшие частные методы. Теперь нет проблем! Вы можете добавлять все больше и больше шагов, добавляя все больше и больше частных методов! Напротив, я думаю, что это серьезная проблема. Он устанавливает шаблон для добавления сложности к классу, за которым последуют последующие исправления ошибок и реализации функций. Скоро ваши частные методы будут расти и они должны быть разделены.
  

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

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

ответил Samuel 7 J0000006Europe/Moscow 2017, 06:40:30

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132