Это плохая практика иметь длинный метод инициализации? - PullRequest
8 голосов
/ 12 апреля 2010

Многие люди спорили о размере функции. Говорят, что функции в целом должны быть довольно короткими. Мнения варьируются от примерно 15 строк до "примерно одного экрана", которое сегодня, вероятно, составляет около 40-80 строк.
Кроме того, функции всегда должны выполнять только одну задачу.

Однако в моем коде есть одна функция, которая часто не работает по обоим критериям: функции инициализации.

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

Особенность init-функций заключается в том, что они обычно вызываются только один раз, поэтому нет необходимости повторно использовать какие-либо компоненты. Вы все еще разбили бы их на несколько более мелких функций? Считаете ли вы, что большие функции инициализации будут в порядке?

Ответы [ 9 ]

12 голосов
/ 12 апреля 2010

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

void _init_hardware() { }
void _convert_format() { }
void _setup_state() { }

void initialize_audio() {
    _init_hardware();
    _convert_format();
    _setup_state();
}

Написание кратких функций так же важно, как изоляция ошибок и изменений, так и сохранение читабельности. Если вы знаете, что ошибка в _convert_format(), вы можете отследить ~ 40 строк, ответственных за ошибку, немного быстрее. То же самое применимо, если вы фиксируете изменения, которые касаются только одной функции.

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

5 голосов
/ 12 апреля 2010

Если разбиение на более мелкие части делает код лучше структурированным и / или более читаемым - делайте это независимо от того, что делает функция. Дело не в количестве строк, а в качестве кода.

3 голосов
/ 12 апреля 2010

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

SetupAudioHardware();
ConvertAudioData();
SetupState();

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

2 голосов
/ 12 апреля 2010

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

1 голос
/ 12 апреля 2010

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

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

Надеюсь, это поможет,
Карлос Нуньес

1 голос
/ 12 апреля 2010

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

В зависимости от структуры кода, возможно, стоит подумать об абстрагировании ваших больших функций инициализации. Я по-прежнему согласен с точкой зрения Мегар, хотя разделение функций на _init_X(), _init_Y() и т. Д. - хороший путь. Даже если вы не собираетесь повторно использовать комментарии в этом коде для своего следующего проекта, когда вы говорите себе: «Как я инициализировал этот X-компонент?», Вам будет гораздо проще вернуться назад и выбрать его. функции _init_X() меньшего размера, чем было бы для выделения ее из функции большего размера, особенно если X-инициализация разбросана по ней.

1 голос
/ 12 апреля 2010

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

1 голос
/ 12 апреля 2010

Во-первых, вместо функции инициализации должна использоваться фабрика. То есть вместо initialize_audio() у вас есть new AudioObjectFactory (здесь вы можете найти более подходящее имя). Это поддерживает разделение интересов.

Однако будьте осторожны и не абстрагируйтесь слишком рано. Очевидно, у вас уже есть две проблемы: 1) инициализация аудио и 2) использование этого аудио. Например, до тех пор, пока вы не абстрагируете аудиоустройство, которое нужно инициализировать, или до того, как конкретное устройство может быть сконфигурировано во время инициализации, ваш фабричный метод (audioObjectFactory.Create() или любой другой) действительно должен быть ограничен одним большим методом. Ранняя абстракция служит только для запутывания дизайна.

Обратите внимание, что audioObjectFactory.Create() не может быть проверено модулем. Тестирование это интеграционный тест, и до тех пор, пока его части не будут абстрагированы, он останется интеграционным тестом. Позже вы можете обнаружить, что у вас есть несколько разных фабрик для разных конфигураций; в этот момент может быть полезно абстрагировать аппаратные вызовы в интерфейс, чтобы вы могли создавать модульные тесты, чтобы гарантировать, что различные фабрики правильно сконфигурируют оборудование.

1 голос
/ 12 апреля 2010

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

Одной альтернативой этому является использование структуры внедрения зависимостей (например, Spring, Castle Windsor, Guice и т. Д.). У этого есть определенные плюсы и минусы ... хотя работа с одним большим методом может быть довольно болезненной, у вас, по крайней мере, есть хорошее представление о том, где все инициализируется, и нет необходимости беспокоиться о том, что может произойти «волшебство». , С другой стороны, инициализация не может быть изменена после развертывания (как, например, в случае XML-файла для Spring).

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

...