Go 如何重构语义重复

Go 如何重构语义重复,go,refactoring,Go,Refactoring,我定义了两个函数,它们做的事情稍有不同,但语法相同 相关函数向api发送POST请求 重复发生在构造请求、添加头等过程中 我如何重构代码以消除上述重复 package main import ( "bytes" "encoding/json" "fmt" "io/ioutil" "log" "net/http" "net/http/httputil" ) type token struct { Token string } t

我定义了两个函数,它们做的事情稍有不同,但语法相同

相关函数向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
}

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

以httpbin为例

创建请求包括设置端点、头和将请求正文封送到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
}
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
}
如果没有额外的头,可以在此处将
nil
作为第三个参数传递

要提取的第二部分实际上是执行请求并解组数据。以下是executeRequest的外观:

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
}
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)执行请求

以httpbin为例

创建请求包括设置端点、头和将请求正文封送到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
}
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
}
如果没有额外的头,可以在此处将
nil
作为第三个参数传递

要提取的第二部分实际上是执行请求并解组数据。以下是executeRequest的外观:

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
}
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
}

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

type option func(*http.Request)
func sendRequest(endpoint string, body interface{}, result interface{}, options ...option) error {
请注意,这是使用功能选项,Dave Cheney在此处对其进行了出色的描述:

然后,完整的代码变为:


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

type option func(*http.Request)
func sendRequest(endpoint string, body interface{}, result interface{}, options ...option) error {
请注意,这是使用功能选项,Dave Cheney在此处对其进行了出色的描述:

然后,完整的代码变为:


一种可能的方法可能是一种可能的方法可能是