可以使用许多匿名函数吗?

时间:2018-05-30 05:56:35

标签: go coding-style

我喜欢golang的一件事是defer声明,但defer仅适用于func范围。

所以,我经常像这样使用它

func (s *Something) abc() error {
    func() {
        s.Lock()
        defer s.Unlock()
        // don't lock too long
    }()
    // do something else
    if err := func() error {
        resp, err := http.Get("https://example.com/api")
        if err != nil {
            return err
        }
        defer resp.Body.Close()
        if resp.StatusCode != 200 {
            return errors.New("Failed to download")
        }
        var tmp struct {
            Error bool    `json:"error"`
            Result string `json:"result"`
        }
        if err := json.NewDecoder(resp.Body).Decode(&tmp); err != nil {
            return err
        }
        if tmp.Error {
            return errors.New("API return error")
        }
        s.somedata = tmp.result
        return nil
    }(); err != nil {
        return err
    }
    func() {
        s.Lock()
        defer s.Unlock()
        // don't lock too long
    }()
    // do something else
}
基本上,我把它包装成匿名块func

使用这样的东西会很常见吗?任何其他地鼠都滥用这个事实吗?

编辑:澄清

好吧,看起来我没解释得很好, 我想实现两件事

  1. 我想要实现的是尽可能缩短互斥锁

    func() {
        s.Lock()
        defer s.Unlock()
        // don't lock too long
    
        // there is other code here, this func is not an empty func
    }()
    
  2. 在这个函数中有多个http.Get,让我们说在调用example.com/api后我需要调用example.com/api2。我们需要尽快关闭resp.Body,因此只有一个TCP连接到此服务器。 AFAIK,http.Get将创建另一个TCP连接,如果还有另一个尚未关闭的HTTP连接(在之前的响应中没有调用resp.Body.Close())。

  3. 编辑2:更多澄清

    第一个和最后一个匿名函数和锁是同步缓存。我基于map [string]字符串实现缓存,因此需要进行同步。

    我首先需要调用example.com/api,并根据我需要调用example.com/api2或example.com/api3的响应,当时必须关闭之前的http连接,它可以是代码像这样

    resp, err := http.Get("https://example.com/api")
    if err != nil {
        return err
    }
    if resp.StatusCode != 200 {
        resp.Body.Close()
        return errors.New("Failed to download")
    }
    // process the body
    resp.Body.Close()
    

    但你需要两次显式写resp.Body.Close()

2 个答案:

答案 0 :(得分:2)

您认为defer仅在函数范围内有效,我认为您遇到的问题与defer的行为无关。

一般来说,[插入意见]最好有更小的功能,那么你就不需要创建匿名函数来最大限度地使用defer

拆分你的例子,这样的事情可能会更好:

// Public method that does locking orchestration
func (s *Something) PublicDoABC() error {
    s.doWorkStart()
    if err := s.populateSomeData(); err != nil {
        return err
    }
    s.doWorkEnd()
    return nil
}

// setup function with locking
func (s *Something) doWorkStart() {
    s.Lock()
    defer s.Unlock()

    // do setup work here
}

// teardown function with locking
func (s *Something) doWorkEnd() {
    s.Lock()
    defer s.Unlock()

    // do teardown work here
}

// do the actual request
func (s *Something) populateSomeData() error {
    resp, err := http.Get("https://example.com/api")
    if err != nil {
        return err
    }
    defer resp.Body.Close()
    if resp.StatusCode != 200 {
        return errors.New("Failed to download")
    }
    var tmp struct {
        Error bool    `json:"error"`
        Result string `json:"result"`
    }
    if err := json.NewDecoder(resp.Body).Decode(&tmp); err != nil {
        return err
    }
    if tmp.Error {
        return errors.New("API return error")
    }
    s.somedata = tmp.Result
    return nil
}

我知道这会改变一些事情,你可能不想拆分函数的主体,你可以在Something上定义一个允许使用锁定执行任意函数的方法:

func (s *Something) PublicDoABC() error {
    s.executeLocked(func() {
        // do setup work
    })
    if err := s.populateSomeData(); err != nil {
        return err
    }

    s.executeLocked(func() {
        // do teardown work
    })

    return nil
}

// this function allows defer unlocks and unifies locking code
func (s *Something) executeLocked(f func()) {
    s.Lock()
    defer s.Unlock()
    f()
}

回答原始问题:不,我不相信这是常见的(具有多个内联功能)。如果感觉不对,那几乎可以肯定是一种更好的方法。

答案 1 :(得分:0)

由于您对LockUnlock的匿名功能,您将输入匿名函数,锁定,延迟解锁,让匿名函数触发延迟解锁,然后继续{{ 1}}。开头和结尾的匿名函数几乎没用。

您的中间匿名函数对于任何语言都是糟糕的风格。没有理由不做代码并回复任何返回的错误。

我会按如下方式重写此函数:

abc()

匿名功能有其自己的位置。我已将它们用于封闭以及非常简洁的常规操作。您可以在此处使用匿名函数的一个地方是记录func (s *Something) abc() error { s.Lock() defer s.Unlock() resp, err := http.Get("https://example.com/api") if err != nil { return err } defer resp.Body.Close() if resp.StatusCode != 200 { return errors.New("Failed to download") } // This struct should probably be global var tmp struct { Error bool `json:"error"` Result string `json:"result"` } err = json.NewDecoder(resp.Body).Decode(&tmp) if err != nil { return err } if tmp.Error { return errors.New("API return error") } s.somedata = tmp.result return nil } 返回的错误,如下所示:

resp.Body.Close()