Обычно я использую закрытые методы для инкапсуляции функциональности, которая повторно используется в нескольких местах в классе. Но иногда у меня есть большой публичный метод, который можно разбить на более мелкие этапы, каждый из которых имеет свой собственный приватный метод. Это сделало бы открытый метод короче, но я обеспокоен тем, что принуждение любого, кто читает метод, переключаться на другие частные методы, повредит читабельности.
Есть ли консенсус по этому вопросу? Лучше ли иметь длинные публичные методы или разбивать их на более мелкие части, даже если каждая часть не может быть повторно использована?
coding-style
readability
Jordak
источник
источник
Ответы:
Нет, это не плохой стиль. На самом деле это очень хороший стиль.
Частные функции не должны существовать просто из-за возможности повторного использования. Это, безусловно, одна из веских причин для их создания, но есть и другая причина: разложение.
Рассмотрим функцию, которая делает слишком много. Это сто строк, и рассуждать невозможно.
Если вы разделите эту функцию на более мелкие части, она все равно «выполняет» такую же работу, как и раньше, но на более мелкие части. Он вызывает другие функции, которые должны иметь описательные имена. Основная функция читается почти как книга: выполните A, затем B, затем C и т. Д. Функции, которые она вызывает, могут вызываться только в одном месте, но теперь они меньше. Любая конкретная функция обязательно изолирована от других функций: они имеют разные области действия.
Когда вы разбиваете большую проблему на более мелкие, даже если эти меньшие проблемы (функции) используются / решаются только один раз, вы получаете несколько преимуществ:
Читаемость. Никто не может прочитать монолитную функцию и понять, что она делает полностью. Вы можете либо продолжать лгать себе, либо разбить его на куски размером с укус, которые имеют смысл.
Местность ссылки. Теперь становится невозможным объявить и использовать переменную, затем оставить ее и использовать снова 100 строк спустя. Эти функции имеют разные области применения.
Тестирование. Несмотря на то, что необходимо только провести модульное тестирование открытых членов класса, может быть также желательно протестировать и некоторых частных членов. Если есть критический раздел длинной функции, который может выиграть от тестирования, невозможно протестировать его независимо, не извлекая его в отдельную функцию.
Модульность. Теперь, когда у вас есть частные функции, вы можете найти одну или несколько из них, которые можно извлечь в отдельный класс, независимо от того, используется ли он только здесь или может использоваться повторно. К предыдущему пункту этот отдельный класс, вероятно, также будет легче тестировать, так как для него потребуется открытый интерфейс.
Идея разбить большой код на более мелкие части, которые легче понять и протестировать, является ключевым моментом книги Дяди Боба «Чистый код» . На момент написания этого ответа книге было девять лет, но сегодня она так же актуальна, как и тогда.
источник
Это, наверное, отличная идея!
Я не согласен с разделением длинных линейных последовательностей действий на отдельные функции исключительно для уменьшения средней длины функции в вашей кодовой базе:
Теперь вы на самом деле добавили строки исходного текста и уменьшить общую читаемость значительно. Особенно, если вы сейчас передаете много параметров между каждой функцией, чтобы отслеживать состояние. Хлоп!
Однако , если вам удалось извлечь одну или несколько строк в чистую функцию, которая служит единственной ясной цели ( даже если вызывается только один раз ), то вы улучшили читабельность:
Это, вероятно, будет нелегко в реальных ситуациях, но части чистой функциональности часто можно выявить, если вы думаете об этом достаточно долго.
Вы будете знать, что находитесь на правильном пути, когда у вас есть функции с хорошими именами глаголов и когда ваша родительская функция вызывает их, и все это практически читается как абзац прозы.
Затем, когда вы вернетесь через несколько недель, чтобы добавить больше функциональности, и обнаружите, что действительно можете повторно использовать одну из этих функций, тогда, о, восторженная радость! Какой чудесный лучезарный восторг!
источник
foo = compose(reglorbulate, deglorbFramb, getFrambulation);
Ответ очень зависит от ситуации. @Snowman рассказывает о положительных сторонах распада широкой публичной функции, но важно помнить, что могут быть и отрицательные последствия, о чем вы справедливо беспокоитесь.
Множество частных функций с побочными эффектами может сделать код трудным для чтения и довольно хрупким. Это особенно верно, когда эти частные функции зависят от побочных эффектов друг друга. Избегайте тесно связанных функций.
Абстракции негерметичны . Хотя приятно представлять, как выполняется операция или как хранятся данные, не имеет значения, есть случаи, когда это происходит, и важно их идентифицировать.
Семантика и контекст имеют значение. Если вам не удастся четко определить, что функция делает в своем названии, вы снова можете уменьшить читабельность и увеличить хрупкость публичной функции. Это особенно верно, когда вы начинаете создавать частные функции с большим количеством входных и выходных параметров. И хотя из общедоступной функции вы видите, какие частные функции она вызывает, из закрытой функции вы не видите, какие публичные функции ее вызывают. Это может привести к «исправлению ошибок» в частной функции, которая нарушает общедоступную функцию.
Сильно разложенный код всегда понятнее автору, чем другим. Это не означает, что это все еще не может быть ясно для других, но легко сказать, что это имеет смысл, что
bar()
должно быть вызвано раньшеfoo()
во время написания.Опасное повторное использование. Ваша общедоступная функция, вероятно, ограничивала, какой ввод был возможен для каждой частной функции Если эти входные предположения не собраны должным образом (документировать ВСЕ предположения сложно), кто-то может неправильно повторно использовать одну из ваших личных функций, внося ошибки в базу кода.
Разложите функции, основываясь на их внутренней связности и связи, а не на абсолютной длине.
источник
MainMethod
(достаточно легко получить, обычно символы включены, и у вас должен быть файл разыменования символов), то есть 2 000 строк (номера строк никогда не включаются в отчеты об ошибках), и это NRE, которое может произойти на 1k этих строк, ну, будь я проклят, если буду вникать в это. Но если мне скажут, что это произошло,SomeSubMethodA
и в нем 8 строк, а исключением было NRE, то я, вероятно, найду и исправлю это достаточно просто.ИМХО, ценность извлечения блоков кода исключительно как средство разрушения сложности, часто связана с различием в сложности между:
Полное и точное описание того, что делает код на человеческом языке, включая то, как он обрабатывает угловые случаи , и
Сам код.
Если код намного сложнее, чем описание, замена встроенного кода вызовом функции может облегчить понимание окружающего кода. С другой стороны, некоторые понятия могут быть выражены более легко на компьютерном языке, чем на человеческом языке. Я бы посчитал
w=x+y+z;
, например, более читабельным, чемw=addThreeNumbersAssumingSumOfFirstTwoDoesntOverflow(x,y,z);
.По мере разделения большой функции различие между сложностью подфункций и их описаниями будет все меньше и меньше, и преимущество дальнейших подразделений будет уменьшаться. Если все разделится до такой степени, что описания будут более сложными, чем код, дальнейшие разделения сделают код хуже.
источник
int average3(int n1, int n2, int n3)
. Разделение «средней» функции будет означать, что тот, кто хочет узнать, подходит ли она для требований, должен искать в двух местах.Это балансирующая задача.
Тоньше лучше
Закрытый метод эффективно обеспечивает имя для содержащегося кода и иногда значащую сигнатуру (если половина его параметров не является полностью специальными данными бродяги с неясными, недокументированными взаимозависимостями).
Предоставление имен для конструкций кода, как правило, хорошо, если имена предполагают значимый контракт для вызывающей стороны, а контракт частного метода точно соответствует тому, что предлагает имя.
Заставляя себя думать о значимых контрактах для небольших частей кода, первоначальный разработчик может обнаружить некоторые ошибки и безболезненно их избежать даже перед любым тестированием разработчика. Это работает только в том случае, если разработчик стремится к краткому именованию (простое звучание, но точное) и желает адаптировать границы частного метода так, чтобы краткое именование было даже возможно.
Последующее обслуживание также помогает, потому что дополнительное именование помогает сделать самодокументирование кода .
Пока не станет слишком хорошо
Можно ли переусердствовать в присвоении имен маленьким частям кода и в результате получить слишком много, слишком маленьких частных методов? Конечно. Один или несколько из следующих симптомов указывают, что методы становятся слишком маленькими, чтобы быть полезными:
XxxInternal
илиDoXxx
, особенно если нет единой схемы их введения.LogDiskSpaceConsumptionUnlessNoUpdateNeeded
источник
Вопреки тому, что говорили другие, я бы сказал, что длинный публичный метод - это запах дизайна, который не устраняется путем разложения на частные методы.
Если это так, то я бы поспорил, что на каждом этапе должен быть свой первоклассный гражданин с единственной ответственностью за каждого. В объектно-ориентированной парадигме я бы предложил создать интерфейс и реализацию для каждого шага таким образом, чтобы каждый из них имел одну, легко определяемую ответственность и мог быть назван так, чтобы ответственность была ясной. Это позволяет вам провести модульное тестирование (ранее) большого открытого метода, а также каждого отдельного шага независимо друг от друга. Все они должны быть задокументированы.
Почему бы не разложить на частные методы? Вот несколько причин:
Это аргумент, который я недавно имел с одним из моих коллег. Он утверждает, что наличие всего поведения модуля в одном файле / методе улучшает читаемость. Я согласен, что код легче следовать, когда все вместе, но код становится все труднее рассуждать по мере роста сложности. По мере роста системы становится трудно рассуждать обо всем модуле в целом. Когда вы разбиваете сложную логику на несколько классов, каждый из которых имеет одну ответственность, становится намного проще рассуждать о каждой части.
источник