如何使用嵌套的“if”语句减少代码重复?

Tho*_*mas 5 f#

让我们考虑一下这段代码:

let getBuildDate (assembly: Assembly) : DateTime option =

    let buildVersionMetadataPrefix = "+build"
    let attribute = assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()

    if attribute <> null && attribute.InformationalVersion <> null then
        let value = attribute.InformationalVersion
        let index = value.IndexOf(buildVersionMetadataPrefix)
        if index > 0 then
            let value = value.Substring(index + buildVersionMetadataPrefix.Length)
            let success, timestamp = DateTime.TryParseExact(value, "yyyyMMddHHmmss", CultureInfo.InvariantCulture, DateTimeStyles.None)
            if success then
                Some timestamp
            else
                None
        else
            None
    else
        None
Run Code Online (Sandbox Code Playgroud)

有没有办法摆脱所有的“else None”语句而只有一个?

一方面,我可以想象,对于某些人来说,代码更清晰,所有 None 语句都拼出来了,但另一方面,来自 C 世界,我认为它是混乱的,降低了可读性。

在很多情况下,您需要满足一系列条件,并且所有失败的案例都集中在一个地方。

如果我有一个依赖于彼此成功的条件列表,我怎样才能做到简洁的短退出而不重复。

Cha*_*ger 8

另一种方法可能是使用这些Option函数 - 如果上一步的输入是 ,那么这些步骤中的每一步都会有效地短路None

let getBuildDate (assembly: Assembly) : DateTime option =    
    let tryDate value =
         match DateTime.TryParseExact(value, "yyyyMMddHHmmss", CultureInfo.InvariantCulture, DateTimeStyles.None) with
         | true, date -> Some date
         | false, _ -> None

    let buildVersionMetadataPrefix = "+build"
    let attribute = assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()

    Option.ofObj attribute
    |> Option.bind (fun attr -> Option.ofObj attr.InformationalVersion)
    |> Option.map (fun infVer -> infVer, infVer.IndexOf buildVersionMetadataPrefix)
    |> Option.filter (fun (_, index) -> index > 0)
    |> Option.map (fun (infVer, index) -> infVer.Substring(index + buildVersionMetadataPrefix.Length))
    |> Option.bind tryDate
Run Code Online (Sandbox Code Playgroud)

这是否“更好”是有争议的——而且绝对是一个见仁见智的问题!


Tom*_*cek 7

其他答案展示了如何使用更复杂的函数式编程方法来做到这一点,比如使用计算表达式或option值。如果您在整个系统的许多地方都这样做,这些绝对有用并且有意义。

但是,如果您只是想要一种简单的方法来更改代码,从而使控制流更加清晰(而不是让它更聪明),我会否定这些条件。以前,您拥有:

if something then 
  moreStuff()
  Some result
else
  None
Run Code Online (Sandbox Code Playgroud)

您可以通过返回Noneif来重写它not something。我认为这种情况下的 F# 编码约定还允许您删除缩进,因此它看起来更像是命令式提前返回:

if not something then None else
moreStuff()
Some result
Run Code Online (Sandbox Code Playgroud)

有了这个,您可以按如下方式编写原始函数 - 无需任何额外的巧妙技巧:

let getBuildDate (assembly: Assembly) : DateTime option =

    let buildVersionMetadataPrefix = "+build"
    let attribute = assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()

    if attribute = null || attribute.InformationalVersion = null then None else
    let value = attribute.InformationalVersion
    let index = value.IndexOf(buildVersionMetadataPrefix)
    if index <= 0 then None else
    let value = value.Substring(index + buildVersionMetadataPrefix.Length)
    let success, timestamp = DateTime.TryParseExact(value, "yyyyMMddHHmmss", CultureInfo.InvariantCulture, DateTimeStyles.None)
    if not success then None else
    Some timestamp
Run Code Online (Sandbox Code Playgroud)