2012-10-04 17 views
4

下面是一个很常见的游戏框架2控制器:阶:改善这段代码的可读性和风格

def save(ideaId : Long) = CORSAction { request => 
    Idea.findById(ideaId).map { idea => 
    request.body.asJson.map { json => 
     json.asOpt[Comment].map { comment => 
     comment.copy(idea = idea).save.fold(
      errors => JsonBadRequest(errors), 
      comment => Ok(toJson(comment).toString) 
     ) 
     }.getOrElse  (JsonBadRequest("Invalid Comment entity")) 
    }.getOrElse  (JsonBadRequest("Expecting JSON data")) 
    }.getOrElse   (JsonBadRequest("Could not find idea with id '%s'".format(ideaId))) 
} 

我觉得有点讨厌所有这些嵌套.maps,我也找了一下单调乏味,每个错误处理是在底部

你会如何改进它,使更多的可读性,同时保持作为功能惯用斯卡拉代码?

我在想也许这样的事情(这是seudo代码,仍然不能编译)

def save(ideaId : Long) = CORSAction { request => 

    val idea = Idea.findById(ideaId).getOrElse(
    return JsonBadRequest("Could not find idea with id '%s'".format(ideaId))) 

    val json = request.body.asJson.getOrElse(
    return JsonBadRequest("Expecting JSON data")) 

    val comment = json.asOpt[Comment].getOrElse(
    return JsonBadRequest("Invalid Comment entity")) 

    comment.copy(idea = idea).save.fold(
    errors => JsonBadRequest(errors), 
    comment => Ok(toJson(comment).toString) 
) 

} 

PS:我知道这将是更好的避免return语句...

+8

恭喜,你刚刚发明了monads! –

+0

删除'返回'作为首发。哦,等等,他们是否应该打破“save”方法的流程? – pedrofurla

回答

7

第一简化。假设我有采取String并返回三种方法的Option[String]

def foo(s: String): Option[String] = if (s.size >= 4) Some(s + "1") else None 
def bar(s: String): Option[String] = if (s(0) != 'A') Some(s + "2") else None 
def baz(s: String): Option[String] = if (s toSet ' ') Some(s + "3") else None 

我想,如果我得到沿途None该管道通过这些字符串并返回相应的错误信息的方法。我可以这样写:

def all(s: String): Either[String, String] = 
    foo(s).map { x => 
    bar(x).map { y => 
     baz(y).map { z => 
     Right(z) 
     } getOrElse Left("Doesn't contain a space!") 
    } getOrElse Left("Starts with an A!") 
    } getOrElse  Left("Too short!") 

但是对,这不是很漂亮。我们可以用一个for -comprehension和OptiontoRight方法写一个更清晰的版本:上Option

def all(s: String): Either[String, String] = for { 
    x <- (foo(s) toRight "Too short!"    ).right 
    y <- (bar(x) toRight "Starts with an A!"  ).right 
    z <- (baz(y) toRight "Doesn't contain a space!").right 
} yield z 

调用toRight(msg)给了我们一个Left(msg),如果它是空的,和Right(whatever)否则。然后,我们必须采取Either.right的正确预测,因为斯卡拉的Either不是正确的偏见。

你的情况相当于将是这样的:

def save(ideaId: Long) = CORSAction { request => 
    val saveResult = for { 
    idea <- (Idea.findById(ideaId) toRight "Could not find id" ).right 
    json <- (request.body.asJson toRight "Invalid Comment entity").right 
    comment <- (json.asOpt[Comment] toRight "Expecting JSON data" ).right 
    result <- comment.copy(idea = idea).save().right 
    } yield result 

    saveResult.fold(
    error => JsonBadRequest(error), 
    comment => Ok(toJson(comment).toString) 
) 
} 

不太尽量精简所需的语法,但错误消息出现在一个更合乎逻辑的地方,我们已经摆脱了的丑陋的嵌套。

+0

我认为嵌套地图实际上是flatMaps,否则你最终与任一[任一[...],任一[...]],不是吗? – pedrofurla

+1

@pedrofurla:在嵌套版本中,每个级别的'getOrElse'已经在进行扁平化。 –

+1

@ om-non-nom:感谢编辑!我发誓我从来没有在Scala中输入那个单词*! –