В большинстве случаев с растущей неопределенностью код идет вправо. Вложенные условия трудно читать и подвержены ошибкам. Настоящая цель - проверить путь успеха / неудачи, так почему бы не прояснить его?
Вот пример:
if user_allowed? if user.save if profile.save render_success else render_errors(profile.errors) end else render_errors(user.errors) end else render_errors(errors) end
Или, если вы используете исключения, это может быть:
begin raise StandardError unless user_allowed? user.save! profile.save! render_success rescue StandardError => e # retrieving formatted errors is tricky there. # maybe with conditionals? render_errors end
Конечно, вы можете утверждать, что быстрая окупаемость может:
return render_errors(errors) unless user_allowed? return render_errors(user.errors) unless user.save return render_errors(profile.errors) unless profile.save? render_success
Но мы все еще можем добиться большего.
Железнодорожное программирование (да, не Rails) - это сделать очевидными пути успеха и ошибок в вашем коде. В этом замысел жемчужины водопада.
Поток один, идет он или перекрывается:
- после перекрытия поток попадает на трек ошибок
- пока вы остаетесь на дорожке успеха
Это представлено так:
Приведенный выше пример может выглядеть следующим образом:
Flow.new.tap do |flow| flow .chain { flow.dam(errors) unless user_allowed? } .chain { flow.dam(user.errors) unless user.save } .chain { flow.dam(profile.errors) unless profile.save } .chain { render_success } .on_dam { render_errors(flow.error_pool) } end
После блокировки в потоке выполняются только on_dam
блоков.
Поскольку логические проверки настолько распространены, есть синтаксический сахар:
Flow.new .when_falsy { user_allowed? }.dam { errors } .when_falsy { user.save }.dam { user.errors } .when_falsy { profile.save }.dam { profile.errors } .chain { render_success } .on_dam {|error_pool| render_errors(error_pool) }
Где потоки действительно сияют, так это тогда, когда вам нужно объединить логические блоки в цепочку. Вы действительно можете связать один поток с другим, здесь это становится особенно интересно.
Flow.new .chain do Flow.new .chain { ... } .chain { ... } end .chain { ... } .on_dam { ... }
Было бы здорово:
- передавать данные из одного потока в другой
- обернуть логику в соответствующие классы
- есть стратегии отката при ошибке
Что ж, это определенно возможно!
Если вы хотите увидеть больше, отметьте Объединить служебные объекты как босс.
Вы также можете проверить мою готовящуюся к выпуску книгу:
Как сообщается, ни исходный принцип, ни иллюстрация не принадлежат мне напрямую. Оба взяты с https://fsharpforfunandprofit.com/rop/, и они были достаточно любезны, чтобы позволить мне повторно использовать иллюстрации;)