Pull to refresh

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

Reading time2 min
Views12K

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


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;
}

Вместо заключения


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

Tags:
Hubs:
Total votes 24: ↑14 and ↓10+4
Comments34

Articles