如何重构语义重复

时间:2018-03-02 11:07:02

标签: go refactoring

我已经定义了两个功能略有不同但在语法上相同的功能。

有问题的函数向api发送POST个请求。

在构造请求,添加标题等时会发生重复。

如何重构代码以删除所述重复。

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 个答案:

答案 0 :(得分:2)

我这样做的方法是提取两个请求执行共同的部分:1)创建请求和2)执行请求。

Gist with new code使用HTTP Bin作为示例

创建请求包括设置端点,标头并将请求主体封送到JSON。在您的情况下,您还要将请求转储到日志中,该日志也可以放在那里。这就是它的样子:

func buildRequest(endpoint string, body interface{}, extraHeaders map[string]string) (*http.Request, error) {
  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, err
  }

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

  for name, value := range extraHeaders {
    req.Header.Add(name, value)
  }

  dump, err := httputil.DumpRequest(req, true)
  if err != nil {
    return nil, err
  }

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

  return req, nil
}

如果您没有额外的标题,可以在此处传递nil作为第三个参数。

要提取的第二部分实际上是执行请求并解组数据。这就是executeRequest的样子:

func executeRequest(req *http.Request, responseBody interface{}) error {
    client := http.Client{}
    log.Println("Initiating http request")

    resp, err := client.Do(req)
    if err != nil {
        return err
    }
    defer resp.Body.Close()

    bytes, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        return err
    }

    log.Printf("Response is: %s\n", string(bytes))
    err = json.Unmarshal(bytes, &responseBody)
    return err
}

答案 1 :(得分:2)

我想说发送请求的本质是你将一个主体发送到一个端点并解析一个结果。然后,标题是可选的选项,您可以在此过程中添加到请求中。考虑到这一点,我将使用此签名发送一个通用函数来发送请求:

public void add(Sword sword){
    //nope
}

请注意,这是使用功能选项,Dave Cheney在这里做了很好的描述:

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

然后完整的代码变为:

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

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