Как разбить большую функцию на несколько мелких?

Прочитал статью "Искусство написания простых и коротких функций" https://habrahabr.ru/post/310590/
Эта тема постоянно мусолится, и постоянно у меня в голове возникает один и тот же вопрос.

Очевидно, что нужно разбивать сложные функции на части. Но у меня часто возникает проблема из-за того, что внутренние функции почти наверняка не будут где-либо повторно использованы. Довольно часто получается специфическая функция со специфическим набором параметров и специфическим результатом. И как её тогда называть? Использовать имя родительской функции в качестве префикса? Более того, если мы выносим код в отдельную функцию, то появляется вопрос проверки входящих параметров, который наверняка выполнялся в родительской функции. В родительской функции мы точно знали, что параметры верные, а тут получается мы или ничего не проверяем, что плохо для самостоятельности новоиспеченной функции, или в очередной раз проверяем то, что уже проверяли раньше, что негативно сказывается на быстродействии. В основном я программирую на php и там у меня с этим совсем беда, так как в языке нет локальных функций. Их наличие могло исправить ситуацию за счет того, что внутренние функции имеют локальную область видимости, а значит не должны иметь уникальные понятные имена, плюс не могут быть вызваны из вне, а значит нет смысла по несколько раз проверять параметры.

Что я делаю не так? Бывают ли случаи, когда на самом деле лучше не разбивать? Или наличие подобных случаев однозначно говорит о плохой архитектуре?
  • Вопрос задан
  • 1837 просмотров
Пригласить эксперта
Ответы на вопрос 4
Adamos
@Adamos
Архитектура измеряется не длиной функций, а их назначением. Функция, выполняющая ровно одну задачу, обычно и не будет длинной.
Подойдите к вопросу со стороны тестирования. Вас устроит тест этой функции как единого черного ящика? Или у нее есть конкретные части, которые выполняют полноценные подзадачи, под которые хорошо бы иметь отдельный тест на случай, если в функции будут изменения? Если нет и у вас просто однородная простыня - разбивать незачем. Если есть - почему нет?
Ну, а если вас смущает накопление никому больше не нужных функций - вспомните, что в РНР есть ООП и все это может быть собрано в класс, а нигде больше не используемые функции - сделаны приватными.
Ответ написан
Комментировать
zoonman
@zoonman
⋆⋆⋆⋆⋆
Откройте для себя namespace.

Никогда не делайте префиксы функций, а то будет вот такое говно `syn_whitelabel_form_whitelabel_partner_node_form_alter()` (это реальный код).

Освойте ООП в конце концов. Собирайте свои функции внутри одного функционального домена в один класс.
Если функция внутренняя объявляйте ее приватной и вызывайте через `self::`.

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

Разбивать большие функции на мелкие нужно тогда, когда действие повторяется хотя бы один раз или может быть использовано другой частью проекта.
Типичный пример - валидаторы, они могут быть использованы повсеместно.
Ответ написан
Комментировать
@abcd0x00
У тебя неправильный подход. Ты сначала делаешь какую-то большую функцию, а потом хочешь её разбивать. Так вот эта большая функция твоя - это полное фуфло. Вот в чём дело. Потому что ты её уже неправильно сделал.

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

Например, ты хочешь вывести строку "hello" в C.
Код
#include <stdio.h>

int main(void)
{
    printf("hello\n");
    puts("hello");
    fputs("hello\n", stdout);
    fprintf(stdout, "hello\n");
    fwrite("hello\n", 1, 6, stdout);
    return 0;
}

Вывод
[guest@localhost c]$ .ansi t.c -o t
[guest@localhost c]$ ./t
hello
hello
hello
hello
hello
[guest@localhost c]$


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

А вот та же задача, но перечисленные функции использовать запрещено. Что делать?
Можно написать функцию вместо них.
Код
#include <stdio.h>

void f1(void)
{
    putchar('h');
    putchar('e');
    putchar('l');
    putchar('l');
    putchar('o');
    putchar('\n');
}

void f2(char c)
{
    putchar(c);
    putchar('e');
    putchar('l');
    putchar('l');
    putchar('o');
    putchar('\n');
}

void f3(char c)
{
    int i;
    
    putchar('h');
    putchar('e');
    for (i = 0; i < 2; i++)
        putchar(c);
    putchar('o');
    putchar('\n');
}

int main(void)
{
    f1();
    f2('h');
    f3('l');
    return 0;
}

Вывод
[guest@localhost c]$ .ansi t.c -o t
[guest@localhost c]$ ./t
hello
hello
hello
[guest@localhost c]$


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

А вот теперь давай подумаем, как так получилось, что функция printf(), написанная десятки лет назад, решила нашу задачу и тысячи других задач, а свои функции f1(), f2() и f3() могут решить только нашу задачу, но кроме неё не могут решить и десятка других задач?

В чём разница между printf() и f1()? В том, что в printf() строка "hello" передаётся через параметр (то есть выводимый текст параметризован), тогда как в f1() строки вообще нет, она образуется из символов, которые даже не параметризованы. А что у нас с f2(), ведь там есть параметр? А в f2() выводимый текст параметризован недостаточно, так же как и в f3().

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

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

В примере выше ты решаешь задачу вывода строки "hello" функцией f1(), а внутри функции f1() ты решаешь задачу вывода символа на экран функцией putchar(). (Благо putchar() готова уже и хорошо параметризована. Но если не готова, то ты так же её вызываешь, а потом дописываешь её внутренность, в которой всё повторяется - задача и вызов.)
Ответ написан
Комментировать
trevoga_su
@trevoga_su
ООП
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

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