让装置回来是不对的?

问题描述 投票:2回答:2

我有以下功能:

  private def constraintToJson(req: => Request[IO])
  : EitherT[IO, Throwable, Unit]
  = {
    val err: EitherT[IO, Throwable, Unit] = EitherT.fromEither[IO](Left(new Exception("Not JSON format request.")))
    req.contentType match {
      case Some(s) =>
        if (s != `Content-Type`(MediaType.`application/json`))
          err
        else
          EitherT.fromEither[IO](Right(()))
      case None =>
        err
    }
  }

问题是,如果Unit是正确的还是有另一种选择,那么返回它是错误的吗?

scala scala-cats
2个回答
3
投票

我认为返回Unit(包装到Either / EitherT)可能没问题,如果这是计算的最后一步,它实际上不会产生任何输出。在其他情况下(最有可能包括你的)你应该为成功案例返回一些值,以便你可以进一步链接它。所以主要的问题是:如何使用constraintToJson?如果Request与JSON匹配,那么对你的案例的明显怀疑是返回Content-Type或它的身体,因为这很可能是下一步将使用的数据。


2
投票

简化问题

让我首先通过删除IO来抽象你的问题。问题是具有如下签名的方法是否有用:

def validate[A](a: Request[A]): Either[Error,()] = 
    if(isOk(a.someProperty)) Left(()) else Right("invalid")

注意到Either[A,()] =:= Option[A],此方法检查请求值中是否存在任何Error,如果检测到则返回此Error

用这种方法可以做些什么?

Check and exit

我们可以编写一个检查Request的程序:

val someRequest: Request[X] = getRequestFrom("somewhere")

validate(someRequest).toLeft.foreach{error => 
  println("invalid request: " + error
  System.exit(-1)
}
System.exit(0)

在这种情况下,validate有一个有用的签名。

Check and go on

我们也可以validate一个请求,然后继续执行它:

val someRequest: Request[X] = getRequestFrom("somewhere")

validate(someRequest) match {
  case Left(error) => 
    println("invalid request: " + error)
    System.exit(-1)
  case Right(_)    =>
    println("executing request: " + someRequest.execute)
    System.exit(0)
}

虽然这段代码运行得很好,但它会在第二个_子句中丢弃一个参数(match)。这是一些糟糕设计的指标。 case Right(_)中的代码从外部获取值someRequest,并将其视为有效请求,因为您编写代码的方式。

如果我们在someReuqest.execute案例中调用Left,我们可能会在执行无效的Request时遇到运行时异常。根据我们所需的严格程度,这可能是代码味道。

我们可以通过以下方式改进代码。

使用非单位返回类型

我们可以通过简单地返回检查的参数来规避返回的Unit。这看起来有点多余,我们稍后会看到如何将返回的值转换为有用的东西。但我们先来看看这个选项。

def validateTyped[A](a: Request[A]): Either[Error,Request[A]] = 
    if(isOk(a.someProperty)) Left(a) else Right("invalid")

然后我们可以编写Check并继续编写代码

val someRequest: Request[X] = getRequestFrom("somewhere")

validate(someRequestTyped) match {
  case Left(error) => 
    println("invalid request: " + error)
    System.exit(-1)
  case Right(validatedRequest) =>
    //we now execute the validated request
    println("executing request: " + validatedRequest.execute)
    System.exit(0)
}

现在这有点改进了代码。我们不会在第二个match条款中丢弃返回的值。我们可以执行validatedRequest,因为我们已经在validateRequest的文档中读到返回的Right值已经很好地形成,并且它可以被执行而没有错误。

看起来不错吧?但我们可以做得更好。

防止执行格式错误的请求

我们仍然可以通过改变我们的Request类型完全阻止执行格式错误的Request来进一步改进它。

case class Request[A](query: String){
  def validate: Either[Error,ValidRequest[A]] = ???
}

case class ValidRequest[A](query: String){
    def execute: A
}

使用此代码,不再可以调用Request.execute,因为验证现在变为强制性的。否则代码将无法编译。

我们还注意到现在需要Right返回的Request.validate值。它已成为获得ValidRequest的唯一来源。

现在不可能将execute称为未经验证的Request,但是:

val someRequest: Request[X] = getRequestFrom("somewhere")

someRequest.validate match {
  case Left(error) => 
    println("invalid request: " + error)
    System.exit(-1)
  case Right(validatedRequest) =>
    println("executing request: " + validatedRequest.execute)
    System.exit(0)
}

结论

通过这种方式,我们变成了一个奇怪的方法,将Unit返回到一个返回有意义且需要的值的方法。此外,execute已成为一个尚未成功验证的Request变得不可能。

© www.soinside.com 2019 - 2024. All rights reserved.