douweiluo0600 2018-03-02 11:07 采纳率: 0%
浏览 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 matlab在安装时报错 无法找到入口 无法定位程序输入点
  • ¥15 收益高的广告联盟有哪些
  • ¥15 Android Studio webview 的使用问题, 播放器横屏全屏
  • ¥15 删掉jdk后重新下载,Java web所需要的eclipse无法使用
  • ¥15 uniapp正式环境中通过webapi将本地数据推送到设备出现的跨域问题
  • ¥15 xui建立节点,显示错误
  • ¥15 关于#单片机#的问题:开始、复位、十进制的功能可以实现,但是切换八进制的功能无法实现(按下按键也没有效果),把初始状态调成八进制,也是八进制可以实现但是切换到十进制不行(相关搜索:汇编语言|计数器)
  • ¥15 VINS-Mono或Fusion中feature_manager中estimated_depth是特征的深度还是逆深度?
  • ¥15 谷歌浏览器如何备份抖音网页数据
  • ¥15 分别有什么商家下面需要非常多的骑手为它工作?