有没有办法在这里使用不可变集合+使代码看起来更好?

时间:2014-02-11 07:38:45

标签: scala

由于某种原因,我必须手动验证一些变量,并返回一个映射,其中包含每个变量的错误消息的序列。我决定使用可变集合,因为我认为没有其他选择了:

  val errors = collection.mutable.Map[String, ListBuffer[String]]()
  //field1
  val fieldToValidate1 = getData1()
  if (fieldToValidate1 = "")
    errors("fieldToValidate1") += "it must not be empty!"

  if (validate2(fieldToValidate1))
    errors("fieldToValidate1") += "validation2!"

  if (validate3(fieldToValidate1))
    errors("fieldToValidate1") += "validation3!"

  //field2
  val fieldToValidate2 = getData1()
  //approximately the same steps
  if (fieldToValidate2 = "")
    errors("fieldToValidate2") += "it must not be empty!"

  //.....

在我看来,它看起来有点笨拙,应该有其他优雅的解决方案。如果可能的话,我也不想使用可变集合。你的想法?

4 个答案:

答案 0 :(得分:2)

您可以使用errors定义var,而不是使用可变集合,并以这种方式更新它。

var errors = Map[String, List[String]]().withDefaultValue(Nil)
errors = errors updated ("fieldToValidate1", errors("fieldToValidate1") ++ List("it must not be empty!"))
errors = errors updated ("fieldToValidate1", errors("fieldToValidate1") ++ List("validation2"))

代码看起来更乏味,但它来自可变集合。

答案 1 :(得分:1)

那么什么是支票的好类型?我正在考虑A => Option[String]如果A是您所测试对象的类型。如果您的错误消息不依赖于测试对象的值,(A => Boolean, String)可能会更方便。

//for constructing checks from boolean test and an error message
def checkMsg[A](check: A => Boolean, msg: => String): A => Option[String] = 
  x => if(check(x)) Some(msg) else None

val checks = Seq[String => Option[String]](
  checkMsg((_ == ""), "it must not be empty"),
  //example of using the object under test in the error message
  x => Some(x).filterNot(_ startsWith "ab").map(x => x + " does not begin with ab")
)

val objectUnderTest = "acvw"
val errors = checks.flatMap(c => c(objectUnderTest))

错误标签

正如我刚才所说,您要求每张支票都带有标签的地图。在这种情况下,您需要提供检查标签。然后,您的支票类型为(String, A => Option[String])

答案 2 :(得分:1)

虽然[相对]广泛使用scalaz的验证方法(正如@senia所示),但我认为这是一种有点压倒性的方法(如果你把scalaz带到你的项目中)你必须是一个经验丰富的scala开发人员,否则它可能会给你带来更多弊大于利。

很好的替代方法可能是ScalaUtils使用了Or and Every专门用于此目的,实际上如果您使用的是ScalaTest,您已经看到了它们的一个示例(它使用下面的scalautils) 。我羞耻地从他们的文档中复制粘贴的例子:

import org.scalautils._

def parseName(input: String): String Or One[ErrorMessage] = {
  val trimmed = input.trim
  if (!trimmed.isEmpty) Good(trimmed) else Bad(One(s""""${input}" is not a valid name"""))
}

def parseAge(input: String): Int Or One[ErrorMessage] = {
  try {
    val age = input.trim.toInt
    if (age >= 0) Good(age) else Bad(One(s""""${age}" is not a valid age"""))
  }
  catch {
    case _: NumberFormatException => Bad(One(s""""${input}" is not a valid integer"""))
  }
}

import Accumulation._

def parsePerson(inputName: String, inputAge: String): Person Or Every[ErrorMessage] = {
  val name = parseName(inputName)
  val age = parseAge(inputAge)
  withGood(name, age) { Person(_, _) }
}

parsePerson("Bridget Jones", "29")
// Result: Good(Person(Bridget Jones,29))

parsePerson("Bridget Jones", "")
// Result: Bad(One("" is not a valid integer))

parsePerson("Bridget Jones", "-29")
// Result: Bad(One("-29" is not a valid age))

parsePerson("", "")
// Result: Bad(Many("" is not a valid name, "" is not a valid integer))

话虽如此,如果你想坚持使用没有任何外部依赖性的核心scala,我认为你不能比你当前的方法做得更好。

答案 3 :(得分:0)

如果您可以使用scalaz汇总错误的最佳解决方案是Validation

def validate1(value: String) =
  if (value == "") "it must not be empty!".failNel else value.success

def validate2(value: String) =
  if (value.length > 10) "it must not be longer than 10!".failNel else value.success

def validate3(value: String) =
  if (value == "error") "it must not be equal to 'error'!".failNel else value.success

def validateField(name: String, value: String): ValidationNel[(String, String), String] =
  (
    validate1(value) |@|
    validate2(value) |@|
    validate3(value)
  ).tupled >| value leftMap { _.map{ name -> _ } }

val result = (
  validateField("fieldToValidate1", getData1()) |@|
  validateField("fieldToValidate2", getData2())
).tupled

然后你可以得到这样的可选错误Map

val errors =
  result.swap.toOption.map{
    _.toList.groupBy(_._1).map{ case (k, v) => k -> v.map(_._2) }
  }
// Some(Map(fieldToValidate2 -> List(it must not be equal to 'error'!), fieldToValidate1 -> List(it must not be empty!)))