douweiluo0600 2018-03-02 11:07
浏览 30
已采纳

如何重构语义重复

I have defined two funcs that do slightly different things but are syntactically the same.

Functions in question send POST requests to an api.

The duplication occurs in constructing the request, adding headers, etc.

How can I refactor the code to remove said duplication.

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
    "net/http/httputil"
)

type token struct {
    Token string
}

type config struct {
    Foo string
}

func main() {
    token, err := getAuthToken()
    if err != nil {
        log.Fatal(err)
    }

    config, err := getConfig("foo", token)
    if err != nil {
        log.Fatal(err)
    }

    _ = config
}

func getAuthToken() (string, error) {
    endpoint := "foo"

    body := struct {
        UserName string `json:"username"`
        Password string `json:"password"`
    }{
        UserName: "foo",
        Password: "bar",
    }

    jsnBytes, err := json.Marshal(body)
    if err != nil {
        return "", err

    }

    req, err := http.NewRequest("POST", endpoint, bytes.NewReader(jsnBytes))
    if err != nil {
        return "", fmt.Errorf("Unable to create request. %v", err)

    }

    req.Header.Add("Content-Type", "application/json")

    dump, err := httputil.DumpRequest(req, true)
    if err != nil {
        return "", fmt.Errorf("Could not dump request. ", err)
    }

    log.Println("Request: ", string(dump))

    client := http.Client{}
    log.Println("Initiating http request")

    resp, err := client.Do(req)
    if err != nil {
        return "", fmt.Errorf("HTTP Error: %v", err)
    }
    defer resp.Body.Close()

    bytes, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        return "", fmt.Errorf("Error reading response body: %v", err)
    }

    var token token

    err = json.Unmarshal(bytes, &token)
    if err != nil {
        return "", fmt.Errorf("Could not unamrshal json. ", err)
    }

    return token.Token, nil
}

func getConfig(id string, token string) (*config, error) {
    endpoint := "foo"

    body := struct {
        ID string `json:"id"`
    }{
        ID: id,
    }

    jsnBytes, err := json.Marshal(body)
    if err != nil {
        return nil, err
    }

    req, err := http.NewRequest("POST", endpoint, bytes.NewReader(jsnBytes))
    if err != nil {
        return nil, fmt.Errorf("Unable to create request. %v", err)
    }

    req.Header.Add("Authorization", "Bearer "+token)
    req.Header.Add("Content-Type", "application/json")

    dump, err := httputil.DumpRequest(req, true)
    if err != nil {
        return nil, fmt.Errorf("Could not dump request. ", err)
    }

    log.Println("Request: ", string(dump))

    client := http.Client{}
    log.Println("Initiating http request")

    resp, err := client.Do(req)
    if err != nil {
        return nil, fmt.Errorf("HTTP Error: %v", err)
    }
    defer resp.Body.Close()

    bytes, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        return nil, fmt.Errorf("Error reading response body: %v", err)
    }

    var config config

    err = json.Unmarshal(bytes, &config)
    if err != nil {
        return nil, fmt.Errorf("Could not unamrshal json. ", err)
    }

    return &config, nil
}
  • 写回答

2条回答 默认 最新

  • dqaxw44567 2018-03-02 15:54
    关注

    I would say the essence of sending the request is that you are sending a body to an endpoint and parsing a result. The headers are then optional options that you can add to the request along the way. With this in mind I would make a single common function for sending the request with this signature:

    type option func(*http.Request)
    func sendRequest(endpoint string, body interface{}, result interface{}, options ...option) error {
    

    Note this is using functional options which Dave Cheney did an excellent description of here:

    https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis

    The complete code then becomes:

    https://play.golang.org/p/GV6FeipIybA

    package main
    
    import (
        "bytes"
        "encoding/json"
        "fmt"
        "io/ioutil"
        "log"
        "net/http"
        "net/http/httputil"
    )
    
    type token struct {
        Token string
    }
    
    type config struct {
        Foo string
    }
    
    func main() {
        token, err := getAuthToken()
        if err != nil {
            log.Fatal(err)
        }
    
        config, err := getConfig("foo", token)
        if err != nil {
            log.Fatal(err)
        }
    
        _ = config
    }
    
    func getAuthToken() (string, error) {
        endpoint := "foo"
    
        body := struct {
            UserName string `json:"username"`
            Password string `json:"password"`
        }{
            UserName: "foo",
            Password: "bar",
        }
        var token token
    
        err := sendRequest(endpoint, body, &token)
        if err != nil {
            return "", err
        }
    
        return token.Token, nil
    }
    
    func getConfig(id string, token string) (*config, error) {
        endpoint := "foo"
    
        body := struct {
            ID string `json:"id"`
        }{
            ID: id,
        }
        var config config
    
        err := sendRequest(endpoint, body, &config, header("Content-Type", "application/json"))
        if err != nil {
            return nil, err
        }
    
        return &config, nil
    }
    
    type option func(*http.Request)
    func header(key, value string) func(*http.Request) {
        return func(req *http.Request) {
            req.Header.Add(key, value)
        }
    }
    
    func sendRequest(endpoint string, body interface{}, result interface{}, options ...option) error {
        jsnBytes, err := json.Marshal(body)
        if err != nil {
            return err
    
        }
    
        req, err := http.NewRequest("POST", endpoint, bytes.NewReader(jsnBytes))
        if err != nil {
            return fmt.Errorf("Unable to create request. %v", err)
    
        }
    
        req.Header.Add("Content-Type", "application/json")
        for _, option := range options {
            option(req)
        }
    
    
        dump, err := httputil.DumpRequest(req, true)
        if err != nil {
            return fmt.Errorf("Could not dump request. ", err)
        }
    
        log.Println("Request: ", string(dump))
    
        client := http.Client{}
        log.Println("Initiating http request")
    
        resp, err := client.Do(req)
        if err != nil {
            return fmt.Errorf("HTTP Error: %v", err)
        }
        defer resp.Body.Close()
    
        bytes, err := ioutil.ReadAll(resp.Body)
        if err != nil {
            return fmt.Errorf("Error reading response body: %v", err)
        }
    
        err = json.Unmarshal(bytes, result)
        if err != nil {
            return fmt.Errorf("Could not unamrshal json. ", err)
        }
        return nil
    }
    
    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
查看更多回答(1条)

报告相同问题?

悬赏问题

  • ¥15 用stata实现聚类的代码
  • ¥15 请问paddlehub能支持移动端开发吗?在Android studio上该如何部署?
  • ¥170 如图所示配置eNSP
  • ¥20 docker里部署springboot项目,访问不到扬声器
  • ¥15 netty整合springboot之后自动重连失效
  • ¥15 悬赏!微信开发者工具报错,求帮改
  • ¥20 wireshark抓不到vlan
  • ¥20 关于#stm32#的问题:需要指导自动酸碱滴定仪的原理图程序代码及仿真
  • ¥20 设计一款异域新娘的视频相亲软件需要哪些技术支持
  • ¥15 stata安慰剂检验作图但是真实值不出现在图上