在你的情况下,如何正确组织 Golang 代码?

huangapple go评论73阅读模式
英文:

How can I orginize code in the right way in my case? Golang

问题

有两个几乎相同的函数,它们执行的功能大致相同。在这种情况下,如何组织代码以避免重复?httpGetter()函数访问云平台API并获取JSON响应,然后我在另一个函数中对其进行解析,并根据解析结果从模板中生成terraform清单。getToken()函数执行的几乎相同的操作,只是获取一个令牌,然后在httpGetter()函数中使用该令牌。

var accessToken = getToken()

func httpGetter(method, url string) (*http.Response, []byte) {
    client := &http.Client{}
    req, err := http.NewRequest(method, url, nil)
    if err != nil {
        fmt.Println(err)
    }
    req.Header.Add("Accept", "application/json;version=35.0")
    req.Header.Add("Authorization", "Bearer "+accessToken)
    res, err := client.Do(req)
    if err != nil {
        fmt.Println(err)
    }
    defer res.Body.Close()
    body, err := ioutil.ReadAll(res.Body)
    if err != nil {
        fmt.Println(err)
    }
    return res, body
}

func getToken() string {
    url := "https://cloud-platform-api.com/api/sessions"
    method := "POST"
    client := &http.Client{}
    req, err := http.NewRequest(method, url, nil)
    if err != nil {
        fmt.Println(err)
    }
    req.Header.Add("Accept", "application/*+xml;version=35.0")
    req.Header.Add("Authorization", "Basic <auth-hash>")
    res, err := client.Do(req)
    if err != nil {
        fmt.Println(err)
    }
    defer res.Body.Close()
    if err != nil {
        fmt.Println(err)
    }
    accessToken := res.Header.Get("x-vmware-vcloud-access-token")
    return accessToken
}

为了避免重复,你可以将这两个函数合并为一个函数,并将需要传递的参数作为函数的参数。这样,你可以根据不同的情况来调用这个函数,而不需要重复编写相同的代码。例如:

func makeRequest(method, url, accessToken string) (*http.Response, []byte) {
    client := &http.Client{}
    req, err := http.NewRequest(method, url, nil)
    if err != nil {
        fmt.Println(err)
    }
    req.Header.Add("Accept", "application/json;version=35.0")
    req.Header.Add("Authorization", "Bearer "+accessToken)
    res, err := client.Do(req)
    if err != nil {
        fmt.Println(err)
    }
    defer res.Body.Close()
    body, err := ioutil.ReadAll(res.Body)
    if err != nil {
        fmt.Println(err)
    }
    return res, body
}

然后,你可以根据需要调用这个函数,传递不同的参数:

accessToken := getToken()
res, body := makeRequest("GET", "https://cloud-platform-api.com/api/endpoint", accessToken)
// 根据需要处理响应和返回的数据

这样,你就可以避免重复编写相同的代码,并且可以根据需要调用这个函数来执行不同的操作。

英文:

There are almost two identical functions that do approximately the same thing. What would be the right way to organize the code to avoid repetition in this case? The httpGetter() function accesses cloud platform API and gets JSON response, which I then parsed in another function and based on it I form terraform manifest from the template. The getToken() function does almost the same thing, just gets a token, which is then used in the httpGetter() function.

var accessToken = getToken()
func httpGetter(method, url string) (*http.Response, []byte) {
client := &amp;http.Client{}
req, err := http.NewRequest(method, url, nil)
if err != nil {
fmt.Println(err)
}
req.Header.Add(&quot;Accept&quot;, &quot;application/json;version=35.0&quot;)
req.Header.Add(&quot;Authorization&quot;, &quot;Bearer &quot;+accessToken)
res, err := client.Do(req)
if err != nil {
fmt.Println(err)
}
defer res.Body.Close()
body, err := ioutil.ReadAll(res.Body)
if err != nil {
fmt.Println(err)
}
return res, body
}
func getToken() string {
url := &quot;https://cloud-platform-api.com/api/sessions&quot;
method := &quot;POST&quot;
client := &amp;http.Client{}
req, err := http.NewRequest(method, url, nil)
if err != nil {
fmt.Println(err)
}
req.Header.Add(&quot;Accept&quot;, &quot;application/*+xml;version=35.0&quot;)
req.Header.Add(&quot;Authorization&quot;, &quot;Basic &lt;auth-hash&gt;&quot;)
res, err := client.Do(req)
if err != nil {
fmt.Println(err)
}
defer res.Body.Close()
if err != nil {
fmt.Println(err)
}
accessToken := res.Header.Get(&quot;x-vmware-vcloud-access-token&quot;)
return accessToken
}

答案1

得分: 2

首先,如果你知道这个方法是一个getter方法,那么你就不需要传递方法参数,所以签名将变成以下形式。此外,你已经在将*http.Response返回给调用者,现在由调用者决定如何处理响应+调用者应该决定在HTTP调用失败的情况下该怎么做,所以返回错误并让调用者决定。

func HttpGet(url string) (*http.Response, error)

现在你还想要一个带有请求体的POST方法(在某些情况下),所以再创建一个函数

func HttpPost(URL string, body []byte) (*http.Response, error)

现在为了管理这两个签名并拥有共同的代码,你可以有一个私有方法,它只在这个文件中使用,或者你也可以公开该方法(由你决定)

type Headers map[string]string

func http(method, URL string, body []byte, headers Headers) (*http.Response, error) { // 我们在这里传递headers,以便调用者可以传递自定义的请求头
    client := &http.Client{}
    req, err := http.NewRequest(method, url, body)
    if err != nil {
        return nil, err
    }

    req.Header.Add("Accept", "application/json;version=35.0") // 你可以保留这个作为常量的静态请求头

    for key, value := range headers {
       req.Header.Add(key, value)
    }
    
    return client.Do(req)
}

使用这个方法,你的两个方法的调用将如下所示

func HttpGet(url string, headers Headers) (*http.Response, error) {
  return http(http.MethodGet, URL, nil, headers)
}

func HttpPost(url string, body []byte, headers Headers) (*http.Response, error) {
  return http(http.MethodPost, url, body, headers)
}

现在你可以使用这个方法从调用者传递认证令牌,例如:

func getToken() {
  res, err := httpPost("https://cloud-platform-api.com/api/sessions", nil, 
    map[string]string{
      "Authorization": "Basic <auth-hash>",
    }

  if err != nil {
    // 处理错误
  }

  if res.StatusCode == http.StatusCreated {
     // 对成功响应进行处理,比如解析为JSON
  }
}

对于不需要传递请求头的情况,你可以这样做

 res, err := httpGet("some-url", nil) // 将请求头传递为nil
英文:

First thing first if you know the method is a getter then you don't need to pass the method param so the signature would become something like below. Also, you are already returning a *http.Response back to the caller now it will be the callers decision on what to do with the response + the caller should decide what to do in case of if the HTTP call fails so return error and let the caller decide.

func HttpGet(url string) (*http.Response, error)

Now you also want POST method with body (in some cases) so have another function

func HttpPost(URL string, body []byte) (*http.Response, error)

Now to manage both signature and have a common code you could have a private method that will be just used in this file or you could also expose that method (it is up to you)

type Headers map[string]string
func http(method, URL string, body []byte, headers Headers) (*http.Response, error) { // we pass headers here so that the caller can pass custom headers for request
client := &amp;http.Client{}
req, err := http.NewRequest(method, url, body)
if err != nil {
return nil, err
}
req.Header.Add(&quot;Accept&quot;, &quot;application/json;version=35.0&quot;) // common static header you can keep as it is
for key, value := range headers {
req.Header.Add(key, value)
}
return client.Do(req)
}

Using this your call from the two methods would look like

func HttpGet(url string, headers Headers) (*http.Response, error) {
return http(http.MethodGet, URL, nil, headers)
}
func HttpPost(url string, body []byte, headers Headers) (*http.Response, error) {
return http(http.MethodPost, url, body, headers)
}

Now you can use this to pass the auth token from the caller like:

func getToken() {
res, err := httpPost(&quot;https://cloud-platform-api.com/api/sessions&quot;, nil, 
map[string]string{
&quot;Authorization&quot;: &quot;Basic &lt;auth-hash&gt;&quot;,
}
if err != nil {
// do something with error
}
if res.StatusCode == http.StatusCreated {
// do what you want with the success response like unmarshalling to JSON
}
}

and for cases where you don't need to pass header, you can do

 res, err := httpGet(&quot;some-url&quot;, nil) // you pass header as nil

huangapple
  • 本文由 发表于 2022年6月30日 17:22:32
  • 转载请务必保留本文链接:https://go.coder-hub.com/72813334.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定