【问题标题】:Is there any way to use immutable collections here + make the code look better?有什么方法可以在这里使用不可变集合+使代码看起来更好吗?
【发布时间】:2014-03-08 21:13:17
【问题描述】:

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

  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!"

  //.....

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

【问题讨论】:

  • 你在找香草斯卡拉吗?

标签: scala


【解决方案1】:

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

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"))

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

【讨论】:

  • 这些是可变的映射和列表吗?
  • 如果key fieldToValidate1 不存在会不会引发异常?
  • @Alex 地图和列表是不可变的。 withDefaultValue 方法防止异常。
  • 我想知道,为什么会这样:errors = errors updated ("fieldToValidate1", "it must not be empty!" :: errors("fieldToValidate1")) -- cons operator?
  • 看不到使用 var 的不可变集合优于使用 var 的可变集合的意义
【解决方案2】:

那么什么是适合您支票的类型呢?我在考虑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])

【讨论】:

    【解决方案3】:

    虽然 [相对] 广泛使用的正确做事方式是使用 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,我认为您无法比当前方法做得更好。

    【讨论】:

      【解决方案4】:

      如果您可以使用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!)))
      

      【讨论】:

        猜你喜欢
        • 2018-04-03
        • 2020-03-05
        • 1970-01-01
        • 1970-01-01
        • 2018-10-30
        • 1970-01-01
        • 1970-01-01
        • 2021-11-04
        • 2021-06-14
        相关资源
        最近更新 更多