Golang新手如何进行代码审查

Golang新手如何进行代码审查 最近我开始学习Go语言,正在编写一个管理联系人信息的小型Web应用程序。如果您能查看以下代码并给我一些改进建议,我将不胜感激。这是一个提供基本CRUD操作的mux路由器。例如,让我困扰的一点是重复的身份验证检查。

package server

import (
	"log"
	"encoding/json"
	"errors"
	"net/http"
	"github.com/gorilla/mux"
	"github.com/cretzel/dopi-contacts/pkg"
)

type contactRouter struct {
	contactService root.ContactService
}

func NewContactRouter(contactService root.ContactService, router *mux.Router) *mux.Router {
	contactRouter := contactRouter{contactService}
	router.HandleFunc("/contacts", contactRouter.getContacts).Methods("GET")
	router.HandleFunc("/contacts", contactRouter.createContact).Methods("POST")
	router.HandleFunc("/contacts/{id}", contactRouter.updateContact).Methods("PUT")
	router.HandleFunc("/contacts/{id}", contactRouter.deleteContact).Methods("DELETE")
	return router
}

func (router *contactRouter) getContacts(response http.ResponseWriter, request *http.Request) {
	username, err := getCurrentUser(request)
	if err != nil {
		Error(response, http.StatusForbidden, err.Error())
		return
	}

	contacts, err := router.contactService.GetContacts(username)
	if err != nil {
		Error(response, http.StatusInternalServerError, err.Error())
		return
	}

	Json(response, http.StatusOK, contacts)
}

func (router *contactRouter) createContact(response http.ResponseWriter, request *http.Request) {
	username, err := getCurrentUser(request)
	if err != nil {
		Error(response, http.StatusForbidden, err.Error())
		return
	}

	contact, err := decodeContact(request)
	if err != nil {
		Error(response, http.StatusBadRequest, "Invalid request payload")
		return
	}

	responseContact, err := router.contactService.CreateContact(username, &contact)
	if err != nil {
		Error(response, http.StatusInternalServerError, err.Error())
		return
	}

	Json(response, http.StatusCreated, responseContact)
}

func (router *contactRouter) updateContact(response http.ResponseWriter, request *http.Request) {
	username, err := getCurrentUser(request)
	if err != nil {
		Error(response, http.StatusForbidden, err.Error())
		return
	}

	vars := mux.Vars(request)
	id := vars["id"]

	contact, err := decodeContact(request)
	if err != nil {
		Error(response, http.StatusBadRequest, "Invalid request payload")
		return
	}

	err = router.contactService.UpdateContact(username, id, &contact)
	if err != nil {
		Error(response, http.StatusInternalServerError, err.Error())
		return
	}

	Json(response, http.StatusOK, nil)
}

func (router *contactRouter) deleteContact(response http.ResponseWriter, request *http.Request) {
	username, err := getCurrentUser(request)
	if err != nil {
		Error(response, http.StatusForbidden, err.Error())
		return
	}

	vars := mux.Vars(request)
	id := vars["id"]

	err = router.contactService.DeleteContact(username, id)
	if err != nil {
		Error(response, http.StatusInternalServerError, err.Error())
		return
	}

	Json(response, http.StatusOK, "")
}

func decodeContact(request *http.Request) (root.Contact, error) {
	var contact root.Contact
	if request.Body == nil {
		return contact, errors.New("no request body")
	}
	decoder := json.NewDecoder(request.Body)
	err := decoder.Decode(&contact)
	return contact, err
}

func getCurrentUser(request *http.Request) (string, error) {
	username := request.Header.Get("X-User")
	log.Println("Username", username)
	if (username == "") {
		return username, errors.New("no username found")
	}
	return username, nil
}

更多关于Golang新手如何进行代码审查的实战教程也可以访问 https://www.itying.com/category-94-b0.html

2 回复

你可以使用中间件来处理身份验证。例如,可以使用中间件验证用户是否已正确认证,然后将其保存到"上下文"中:

			httpError(w, unauthorizedError)
			return
		}

		u := &manager.User{AccessToken: token}
		if !u.ReadUserByToken(env.DB) {
			httpError(w, unauthorizedError)
			return
		}

		ctx := context.WithValue(r.Context(), contextAuthUser, u)
		next.ServeHTTP(w, r.WithContext(ctx))
	})
}

之后,你可以从处理程序中获取该用户:

	"net/http"
	"strconv"
	"strings"

	"github.com/gchumillas/photomanager/manager"
	"github.com/gorilla/mux"
)

// GetCategories获取所有分类
func (env *Env) GetCategories(w http.ResponseWriter, r *http.Request) {
	u := getAuthUser(r)

	params := mux.Vars(r)
	parentCatID := params["catID"]

	sortCols := strings.Split(getParam(r, "sort", "name"), ",")
	for _, col := range sortCols {
		pos := strings.IndexRune(col, '-')
		str := col
		if pos > -1 {
			str = col[pos+1:]
	unauthorizedError   = httpStatus{401, "Not authorized."}
	docNotFoundError    = httpStatus{404, "Document not found."}
)

// Env包含公共变量,例如数据库访问等
type Env struct {
	DB              *mgo.Database
	MaxItemsPerPage int
}

func getAuthUser(r *http.Request) *manager.User {
	return r.Context().Value(contextAuthUser).(*manager.User)
}

func parsePayload(w http.ResponseWriter, r *http.Request, payload interface{}) {
	dec := json.NewDecoder(r.Body)

	if err := dec.Decode(payload); err != nil {
		httpError(w, payloadError)
		return
	}

我之前做这个项目也是为了学习GO 🙂

更多关于Golang新手如何进行代码审查的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


以下是针对您提供的Go代码的审查意见,重点关注重复的身份验证检查问题以及其他潜在改进点。我会直接给出代码示例来展示如何优化。

1. 消除重复的身份验证检查

您在每个处理函数中都调用了getCurrentUser来验证用户身份,这违反了DRY(Don’t Repeat Yourself)原则。可以通过中间件来统一处理身份验证。

示例代码:身份验证中间件

func authMiddleware(next http.HandlerFunc) http.HandlerFunc {
    return func(response http.ResponseWriter, request *http.Request) {
        username, err := getCurrentUser(request)
        if err != nil {
            Error(response, http.StatusForbidden, err.Error())
            return
        }
        // 将用户名存储在请求上下文中,供后续处理函数使用
        ctx := context.WithValue(request.Context(), "username", username)
        next.ServeHTTP(response, request.WithContext(ctx))
    }
}

// 修改路由注册,应用中间件
func NewContactRouter(contactService root.ContactService, router *mux.Router) *mux.Router {
    contactRouter := contactRouter{contactService}
    router.HandleFunc("/contacts", authMiddleware(contactRouter.getContacts)).Methods("GET")
    router.HandleFunc("/contacts", authMiddleware(contactRouter.createContact)).Methods("POST")
    router.HandleFunc("/contacts/{id}", authMiddleware(contactRouter.updateContact)).Methods("PUT")
    router.HandleFunc("/contacts/{id}", authMiddleware(contactRouter.deleteContact)).Methods("DELETE")
    return router
}

// 在处理函数中从上下文中获取用户名
func (router *contactRouter) getContacts(response http.ResponseWriter, request *http.Request) {
    username := request.Context().Value("username").(string)
    contacts, err := router.contactService.GetContacts(username)
    if err != nil {
        Error(response, http.StatusInternalServerError, err.Error())
        return
    }
    Json(response, http.StatusOK, contacts)
}

2. 统一错误处理

当前代码中每个处理函数都重复进行错误检查,可以提取一个辅助函数来简化。

示例代码:错误处理辅助函数

func handleError(response http.ResponseWriter, err error, defaultMessage string, statusCode int) {
    if err != nil {
        Error(response, statusCode, err.Error())
    } else {
        Error(response, statusCode, defaultMessage)
    }
}

// 在处理函数中使用
func (router *contactRouter) getContacts(response http.ResponseWriter, request *http.Request) {
    username := request.Context().Value("username").(string)
    contacts, err := router.contactService.GetContacts(username)
    if err != nil {
        handleError(response, err, "Failed to retrieve contacts", http.StatusInternalServerError)
        return
    }
    Json(response, http.StatusOK, contacts)
}

3. 改进JSON解码和响应

decodeContact函数可以更健壮地处理JSON解码错误,并统一响应格式。

示例代码:增强的JSON解码

func decodeContact(request *http.Request) (root.Contact, error) {
    var contact root.Contact
    if request.Body == nil {
        return contact, errors.New("no request body")
    }
    defer request.Body.Close()
    decoder := json.NewDecoder(request.Body)
    decoder.DisallowUnknownFields() // 防止未知字段导致错误
    err := decoder.Decode(&contact)
    if err != nil {
        return contact, fmt.Errorf("invalid JSON: %v", err)
    }
    return contact, nil
}

4. 使用常量代替硬编码字符串

将硬编码的字符串(如状态消息)提取为常量,提高可维护性。

示例代码:定义常量

const (
    ErrNoUsername    = "no username found"
    ErrInvalidPayload = "Invalid request payload"
)

func getCurrentUser(request *http.Request) (string, error) {
    username := request.Header.Get("X-User")
    log.Println("Username", username)
    if username == "" {
        return username, errors.New(ErrNoUsername)
    }
    return username, nil
}

5. 优化日志记录

当前代码使用log.Println记录用户名,建议使用结构化的日志库(如logrus或zap)以便于生产环境调试。

示例代码:使用logrus

import "github.com/sirupsen/logrus"

var logger = logrus.New()

func getCurrentUser(request *http.Request) (string, error) {
    username := request.Header.Get("X-User")
    logger.WithField("username", username).Info("User authentication attempt")
    if username == "" {
        return username, errors.New(ErrNoUsername)
    }
    return username, nil
}

这些修改将减少代码重复,提高可读性和可维护性,同时保持功能不变。

回到顶部