2012-10-04 21 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中輸入那個單詞*! –