Scala中最干净的方法是在转换集合和检查每个步骤中的错误条件时避免嵌套ifs

时间:2014-08-21 22:12:57

标签: scala

我有一些验证IP地址的代码,如下所示:

sealed abstract class Result
case object Valid extends Result
case class Malformatted(val invalid: Iterable[IpConfig]) extends Result
case class Duplicates(val dups: Iterable[Inet4Address]) extends Result
case class Unavailable(val taken: Iterable[Inet4Address]) extends Result

def result(ipConfigs: Iterable[IpConfig]): Result = {
  val invalidIpConfigs: Iterable[IpConfig] =
    ipConfigs.filterNot(ipConfig => {
      (isValidIpv4(ipConfig.address)
        && isValidIpv4(ipConfig.gateway))
    })
  if (!invalidIpConfigs.isEmpty) {
    Malformatted(invalidIpConfigs)
  } else {
    val ipv4it: Iterable[Inet4Address] = ipConfigs.map { ipConfig =>
      InetAddress.getByName(ipConfig.address).asInstanceOf[Inet4Address]
    }
    val dups = ipv4it.groupBy(identity).filter(_._2.size != 1).keys
    if (!dups.isEmpty) {
      Duplicates(dups)
    } else {
      val ipAvailability: Map[Inet4Address, Boolean] =
        ipv4it.map(ip => (ip, isIpAvailable(ip)))
      val taken: Iterable[Inet4Address] = ipAvailability.filter(!_._2).keys
      if (!taken.isEmpty) {
        Unavailable(taken)
      } else {
        Valid
      }
    }
  }
}

我不喜欢嵌套的ifs,因为它会降低代码的可读性。是否有一种很好的方法来线性化这段代码?在java中,我可能会使用return语句,但在scala中不鼓励这样做。

3 个答案:

答案 0 :(得分:1)

我个人主张在任何地方使用匹配,因为我认为通常会使代码非常易读

def result(ipConfigs: Iterable[IpConfig]): Result =
  ipConfigs.filterNot(ipc => isValidIpv4(ipc.address) && isValidIpv4(ipc.gateway)) match {
    case Nil => 
      val ipv4it = ipConfigs.map { ipc =>
        InetAddress.getByName(ipc.address).asInstanceOf[Inet4Address]
      }
      ipv4it.groupBy(identity).filter(_._2.size != 1).keys match {
        case Nil =>
          val taken = ipv4it.map(ip => (ip, isIpAvailable(ip))).filter(!_._2).keys
          if (taken.nonEmpty) Unavailable(taken) else Valid
        case dups => Duplicates(dups)
      }
    case invalid => Malformatted(invalid)
  }

请注意,我首先选择匹配else部分,因为您通常会在匹配中从特定匹配到通用,因为Nil is a subclass of Iterable我将其作为第一种情况,因此无需另一个i if i.nonEmpty中的case,因为如果它与Nil

不匹配,它将是给定的

此外需要注意的是,您的所有val都不需要明确定义的类型,如果您编写类似

的内容,它会显着淡化代码
val ipAvailability: Map[Inet4Address, Boolean] =
  ipv4it.map(ip => (ip, isIpAvailable(ip)))

简单地

val ipAvailability = ipv4it.map(ip => (ip, isIpAvailable(ip)))

我也冒昧地删除了许多我没有远程找到的一次性变量,因为他们所做的就是为代码添加更多行

此处需要注意的是,在嵌套match上使用if,就是添加新case比添加新else if更容易99%的时间,从而使其更加模块化,模块化总是一件好事。

或者,正如纳撒尼尔·福特所建议的那样,你可以把它分解成几种较小的方法,在这种情况下,上面的代码就是这样:

def result(ipConfigs: Iterable[IpConfig]): Result =
  ipConfigs.filterNot(ipc => isValidIpv4(ipc.address) && isValidIpv4(ipc.gateway)) match {
    case Nil => wellFormatted(ipConfigs)
    case i => Malformatted(i)
  }

def wellFormatted(ipConfigs: Iterable[IpConfig]): Result = {
  val ipv4it = ipConfigs.map(ipc => InetAddress.getByName(ipc.address).asInstanceOf[Inet4Address])
  ipv4it.groupBy(identity).filter(_._2.size != 1).keys match {
    case Nil => noDuplicates(ipv4it)          
    case dups => Duplicates(dups)
  }
}

def noDuplicates(ipv4it: Iterable[IpConfig]): Result = 
  ipv4it.map(ip => (ip, isIpAvailable(ip))).filter(!_._2).keys match {
    case Nil => Valid
    case taken => Unavailable(taken)
  }

这有利于将其分解为更小的更易于管理的块,同时保持FP理想的功能只做一件事,但做好一件事,而不是拥有做所有事情的上帝方法。 / p>

您喜欢哪种风格,当然取决于您。

答案 1 :(得分:1)

现在有一段时间,但我会加2美分。处理此问题的正确方法是使用Either。您可以创建一个方法:

public int ecall_test_1(
    [out]                 uint32_t* data_size
);

public int ecall_test_2(
                          uint32_t  data_size,
    [out, size=data_size] uint8_t*  data,
);

所以你可以使用理解语法

def checkErrors[T](errorList: Iterable[T], onError: Result) : Either[Result, Unit] = if(errorList.isEmpty) Right() else Left(onError)

如果您不想返回使用

val invalidIpConfigs = getFormatErrors(ipConfigs)

val result = for {
  _ <- checkErrors(invalidIpConfigs, Malformatted(invalidIpConfigs))
  dups = getDuplicates(ipConfigs)
  _ <- checkErrors(dups, Duplicates(dups))
  taken = getAvailability(ipConfigs)
  _ <- checkErrors(taken, Unavailable(taken))
} yield Valid

如果检查方法使用Futures(例如,可能是getAvailability的情况),您可以使用cats库以清晰的方式使用它:https://typelevel.org/cats/datatypes/eithert.html

答案 2 :(得分:0)

我认为它非常具有可读性,除非!isEmpty等于nonEmpty,否则我不会尝试从中进行改进。