我有一些验证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中不鼓励这样做。
答案 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
,否则我不会尝试从中进行改进。