В большинстве случаев с растущей неопределенностью код идет вправо. Вложенные условия трудно читать и подвержены ошибкам. Настоящая цель - проверить путь успеха / неудачи, так почему бы не прояснить его?

Вот пример:

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/, и они были достаточно любезны, чтобы позволить мне повторно использовать иллюстрации;)