Некоторое время назад, вернувшись после полугодового отпуска в функциональном мире, назад в ООП, я в который раз наступил на привычные грабли: случайно изменил состояние.
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; }
Вместо заключения
Как думаете? В этом есть смысл или я парюсь?
Если вы уже делаете подобное или натягиваете другие части ФП на ООП, пожалуйста поделитесь.
ссылка на оригинал статьи https://habrahabr.ru/post/318908/
Добавить комментарий