Натягиваем ФП на ООП

в 13:22, , рубрики: как сделать жизнь проще, ооп, отладка, Программирование, Семантика, функциональное программирование, метки:

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

private double fBm(Vector2D v, int y)
{
    double result = 0f;
    double freq = Frequency;

    for (int i = 0; i < Octaves; ++i)
    {
        result += NoiseFn(permutation, v * freq) * Amplitude;
        freq *= Lacunarity;
        Amplitude *= Gain; // <-- Вот тут.
    }

    return result;
}

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

Во-первых нужно сделать this обязательным. Поставить поле не с той стороны выражения слишком легко. Если бы я везде явно указал контекст, скорее всего сразу бы заметил, что ступил. К сожалению, компилятор C# нельзя заставить требовать this, а в статических проверках студии есть только правило которое работает наоборот, то есть заставляет убирать this. Так что, видимо придется писать свое.

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

public void DoAThing(int input)
{
    // Первый абзац
    int a = this.A;
    int b = this.B;
    int result = 0;

    // Второй абзац
    for (int i = 0; i < input; ++i)
        result += (a + b) * i;

    // Третий абзац
    this.Thing = result;
}

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

return можно добавить в любое место, на поведение метода он не повлияет.

Плюсы

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

Минусы

  • Дисциплина. Фу-фу-фу. Без автоматических проверок, легко все испортить.
  • В некоторых случаях этот подход будет работать медленней.
  • Может выглядеть странно или глупо. Вот к примеру:
    {
        int a = this.A;
        int b = this.B;
        return a + b;
    }

Вместо заключения
Как думаете? В этом есть смысл или я парюсь?
Если вы уже делаете подобное или натягиваете другие части ФП на ООП, пожалуйста поделитесь.

Автор: aikixd

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js