国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

go最佳實踐:如何舒適地編碼

這篇具有很好參考價值的文章主要介紹了go最佳實踐:如何舒適地編碼。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

什么是 "最佳 "做法?

有很多做法:你可以自己想出來,在互聯(lián)網上找到,或者從其他語言中拿來,但由于其主觀性,并不總是容易說哪一個比另一個好?!弊罴选钡暮x因人而異,也取決于其背景,例如網絡應用的最佳實踐可能與中間件的最佳實踐不一樣。
為了寫這篇文章,我?guī)е粋€問題看了go的實踐,那就是 “它在多大程度上讓我對寫Go感到舒服?”,當我說"語言的最佳實踐是什么?"時,那是在我剛接觸這門語言,還沒有完全適應寫這門語言的時候。
當然,還有更多的做法,我在這里不做介紹,但如果你在寫go時知道這些做法,就會非常有用,但這三個做法對我在go中的信心影響最大。
這就是我選擇"最佳"做法的原因。現(xiàn)在是該上手的時候了。

實踐1:package布局

當我開始學習go時,最令人驚訝的事情之一是,go沒有像Laravel對PHP,Express對Node那樣的網絡框架。這意味著在編寫網絡應用時,如何組織你的代碼和包,完全取決于你。雖然在如何組織代碼方面擁有自由是一件好事,但如果沒有指導原則,很容易迷失方向。
另外,這也是最難達成一致的話題之一;"最佳 "的含義很容易改變,這取決于程序處理的業(yè)務邏輯或代碼庫的大小/成熟度。即使是同一個代碼庫,當前的軟件包組織在6個月后也可能不是最好的。
雖然沒有單一的做法可以統(tǒng)治一切,但為了補救這種情況,我將介紹一些準則,希望它們能使決策過程更容易。

準則1:從平面布局開始

除非你知道代碼庫會很大,并且需要某種預先的包布局,否則最好從平面布局開始,簡單地將所有的go文件放在根文件夾中。
這是一個來自github.com/patrickmn/g…軟件包的文件結構。

? tree
.
├── CONTRIBUTORS
├── LICENSE
├── README.md
├── cache.go
├── cache_test.go
├── sharded.go
└── sharded_test.go

它只有一個領域的關注:對數(shù)據緩存,對于像這樣的包,甚至不需要包的布局。扁平結構在這種情況下最適合。
但隨著代碼庫的增長,根文件夾會變得很忙,你會開始覺得扁平結構不再是最好的了。是時候把一些文件移到它們自己的包里了。

準則2:創(chuàng)建子包

據我所知,主要有三種模式:直接在根部,在pkg文件夾下,以及在internal文件夾下。

在根部

在根目錄下創(chuàng)建一個帶有軟件包名稱的文件夾,并將所有相關文件移到該文件夾下。這樣做的好處是:

  • 沒有深層次/嵌套的目錄
  • 導入路徑不雜亂

缺點是根文件夾會變得有點亂,特別是當有其他文件夾如scripts、bin和docs時。

在pkg包下

創(chuàng)建一個名為pkg的目錄,把子包放在它下面。好的方面是:

  • 這個名字清楚地表明這個目錄包含了子包
  • 你可以保持頂層的清潔

而不好的方面是你需要在導入路徑中有pkg,這并不意味著什么,因為很明顯你在導入包。
然而,這種模式有一個更大的問題,也是前一種模式的問題:有可能從版本庫外部訪問子包。
這對私人倉庫來說是可以接受的,因為如果發(fā)生這種情況,在審查過程中會被注意到,但重要的是要注意什么是公開的,特別是在開放源碼的背景下,向后兼容性很重要。一旦你把它公開,你就不能輕易改變它。
有第三個選擇來處理這種情況。

在internal包下

如果/internal在導入路徑中,go處理包的方式有點不同。如果軟件包被放在/internal文件夾下,只有共享/internal之前的路徑的軟件包才能訪問里面的軟件包。
例如,如果軟件包路徑是/a/b/c/internal/d/e/f,只有/a/b/c目錄下的軟件包可以訪問/internal目錄下的軟件包。這意味著如果你把internal放在根目錄下,只有該倉庫內的包可以使用子包,而其他倉庫不能訪問。如果你想擁有子包,同時保持它們的API在內部,這很有用。

準則3:將main移至cmd目錄下

把主包放在cmd/<命令名稱>目錄下也是一種常見的做法。
假設我們有一個用go編寫的管理個人筆記的API服務器,用這種模式看起來會是這樣。

$ tree
.
├── cmd
│    └── personal-note-api
│        └── main.go
...
├── Makefile
├── go.mod
└── go.sum

要考慮使用這種模式的情況是:

  • 你可能想在一個資源庫中擁有多個二進制文件。你可以在cmd下創(chuàng)建任意多的文件夾,只要你想。
  • 有時需要將主包移到其他地方,以避免循環(huán)依賴。

準則4:按其責任組織包裝

我們已經研究了何時以及如何制作子包,但還有一個大問題:它們應該如何分組?我認為這是最棘手的部分,需要一些時間來適應,主要是因為它在很大程度上受應用程序的領域關注和功能影響。深入了解代碼的作用是做出決定的必要條件。
對此,最常見的建議是按照責任來組織。
對于那些熟悉MVC框架的人來說,擁有"model"、“controller”、“service"等包可能感覺很自然。建議不要在go中使用它們。
相反,我們建議使用更多的責任/領域導向的包名,如"用戶"或"事務”。

準則5:按依賴關系對子包進行分組

根據它們的依賴關系來命名包,例如"redis"、“kafka"或"pubsub”,在某些情況下提供了明確的抽象性。
想象一下,你有一個這樣的接口:

package bestpractice

type User struct {}

type UserService interface {
        User(context.Context, string) (*User, error)
}

而你在redis子包里有一個服務,它是這樣實現(xiàn)的:

package redis

import (
        "github.com/thirdfort/go-bestpractice"
        "github.com/thirdfort/go-redis"
)

type UserService struct {
        ...
}

func (s *UserService) User(ctx context.Context, id string) (*bestpractice.User, error) {
        ...
        err := redis.RetrieveHash(ctx, k)
        ...
}

如果消費者(大概是主函數(shù))只依賴于接口,它可以很容易地被替代的實現(xiàn)所取代,如postgres或inmemory。

附加提示1:給包起一個簡短的名字

關于命名包的幾個要點。

  • 短而有代表性的名稱
  • 使用一個詞
  • 使用縮略語,但不要讓它變得神秘莫測

如果你想使用多個詞(如billing_account)怎么辦?我能想到的選項是:

  • 為每個詞設置一個嵌套包:billing/account
  • 如果沒有混淆,就簡單地命名為帳戶
  • 使用縮略語:billacc

補充提示2:避免重復

這是關于如何命名包內的內容(結構/界面/函數(shù))。go的建議是,在消費包的時候盡量避免重復。例如,如果我們有一個包,內容是這樣的:

package user

func GetUser(ctx context.Context, id string) (*User, error) {
        ...
}

這個包的消費者要這樣調用這個函數(shù):user.GetUser(ctx, u.ID)
在函數(shù)調用中出現(xiàn)了兩次user這個詞。即使我們把user這個詞從函數(shù)中去掉:user.Get,仍然可以看出它返回了一個用戶,因為從包的名稱中可以看出。go更傾向于簡單的名字。
我希望這些準則在決定包的布局時能有所幫助。
讓我們來看看關于上下文的第二個實踐。

實踐2:熟悉context.Context

在95%的情況下,你唯一需要做的就是將調用者提供的上下文傳遞給需要上下文作為參數(shù)的子程序調用。

func (u *User) Store(ctx context.Context) error {
        ...
        if err := u.Hash.Store(ctx, k, u); err != nil {
                return err
        }
        ...
}

盡管如此,由于context在go程序中隨處可見,因此了解何時需要它,以及如何使用它是非常重要的。

context的三種用途

首先,也是最重要的一點是,要意識到上下文可以有三種不同的用途:

  • 發(fā)送取消信號
  • 設置超時
  • 存儲/檢索請求的相關值

發(fā)送取消信號

context.Context提供了一種機制,可以發(fā)送一個信號,告訴收到context的進程停止。
例如,優(yōu)雅關機
當一個服務器收到關閉信號時,它需要"優(yōu)雅地"停止;如果它正在處理一個請求,它需要在關閉之前為其提供服務。context包提供了context.WithCancel API,它返回一個配置了cancel的新上下文和一個取消它的函數(shù)。如果你調用cancel函數(shù),信號會被發(fā)送到接收該上下文的進程中。
在下面的例子中,它調用context.WithCancel后,在啟動服務器時將其傳遞給服務器。當程序收到OS信號時,會調用cancel:

func main() {
        ctx, cancel := context.WithCancel(context.Background())
        defer cancel()
  
        go func() {
                sigchan := make(chan os.Signal, 1)
                signal.Notify(sigchan, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM)
                <-sigchan
                cancel()
        }()
  
        svr := &ctxpkg.Server{}
        svr.Run(ctx) // ← long running process
        log.Println("graceful stop")
}

讓我們看看"偽"服務器的實現(xiàn);它實際上什么也沒做,但為了演示,它有足夠的功能:

type Server struct{}

func (s *Server) Run(ctx context.Context) {
        for {
                select {
                case <-ctx.Done():
                        log.Println("cancel received, attempting graceful stop...")
                        // clean up process
                        return
                default:
                        handleRequest()
                }
        }
}

func handleRequest() {
        time.Sleep(time.Duration(rand.Intn(10)) * time.Second)
}

它首先進入一個無限的循環(huán)。在這個循環(huán)中,它檢查上下文是否已經在ctx.Done()通道上使用select取消了。如果取消了,它就清理進程并返回。如果沒有,它就處理一個請求。一旦請求被處理,它就回到循環(huán)中,再次檢查上下文。
這里的重點是通過使用context.Context,你可以允許進程在他們準備好的時候返回。

設置超時

第二種用法是為操作設置超時。想象一下,你正在向第三方發(fā)送HTTP請求。如果由于某些原因,如網絡中斷,請求的時間超過預期,你可能想取消請求,以防止整個過程掛起。通過context.WithTimeout,你可以為這些情況設置超時。

func main() {
        ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
        defer cancel() // ← cancel should be called even if timeout didn't happen

        SendRequest(ctx) // ← subroutine that can get stuck
}

在SendRequest方法中,在不同的goroutine中發(fā)送請求后,它同時在ctx.Done()通道和響應通道中等待。當超時發(fā)生時,你會從ctx.Done()通道得到一個信號,這樣你就可以從該函數(shù)中退出,而不用等待響應。

func SendRequest(ctx context.Context) {
        respCh := make(chan interface{}, 1)
        go sendRequest(respCh)

        select {
        case <-ctx.Done():
                log.Println("operation timed out!")
        case <-respCh:
                log.Println("response received")
        }
}

func sendRequest(ch chan<- interface{}) {
        time.Sleep(60 * time.Second)
        ch <- struct{}{}
}

context包也有context.WithDeadline();不同的是,context.WithTimeout需要time.Duration,而context.WithDeadline()需要time.Time。

存儲/檢索請求的相關值

上下文的最后一種用法是在上下文中存儲和檢索與請求相關的值。例如,如果服務器收到一個請求,你可能希望在請求過程中產生的所有日志行都有請求信息,如路徑和方法。在這種情況下,你可以創(chuàng)建一個日志記錄器,設置請求相關的信息,并使用context.WithValue將其存儲在上下文中。

var logCtxKey = &struct{}{}

func handleRequest(w http.ResponseWriter, r *http.Request) {
        method, path := r.Method, r.URL.Path
        logger := log.With().
                Str("method", method).
                Str("path", path).
                Logger()
        ctxWithLogger := context.WithValue(r.Context(), logCtxKey, logger)
        ...
        accessDatabase(ctxWithLogger)
}

在某個地方,你可以用同樣的鍵把記錄器從上下文中取出來。例如,如果你想在數(shù)據庫訪問層留下一個日志,你可以這樣做:

func accessDatabase(ctx context.Context) {
        logger := ctx.Value(logCtxKey).(zerolog.Logger)
        logger.Debug().Msg("accessing database")
}

這產生了以下包含請求方法和路徑的日志行。

{"level":"debug","method":"GET","path":"/v1/todo","time":"2022-11-15T15:44:53Z","message":"accessing database"}

就像我說的,你需要使用這些上下文API的情況并不常見,但了解它的作用真的很重要,這樣你就知道在哪種情況下你真的需要注意它。
讓我們進入最后一個實踐。

實踐3:了解 Table Driven Test(表格驅動方法)

表驅動測試是一種組織測試的技術,更多地關注輸入數(shù)據/模擬/存根和預期輸出,而不是斷言,這有時可能是重復的。
我選擇這種方法的原因不僅是因為這是一種常用的做法,而且這也使我在編寫測試時更有樂趣。在編寫測試時有一個良好的動機,對于有一個快樂的編碼生活是非常重要的,不用說編寫可靠的代碼。
讓我們來看看一個例子。
假設我們有一個餐廳的數(shù)據類型,它有一個方法,如果它在某一特定時間開放,則返回真。

type Restaurant struct {
	openAt  time.Time
	closeAt time.Time
}

func (r Restaurant) IsOpen(at time.Time) bool {
	return (at.Equal(r.openAt) || at.After(r.openAt)) &&
		(at.Equal(r.closeAt) || at.Before(r.closeAt))
}

讓我們?yōu)檫@個方法寫一些測試。

如果我們在餐廳開門的時候訪問了它,我們期望它是開放的。

func TestRestaurantJustOpened(t *testing.T) {
	r := Restaurant{
		openAt:  time.Date(2022, time.January, 17, 12, 0, 0, 0, time.UTC),
		closeAt: time.Date(2022, time.January, 17, 22, 0, 0, 0, time.UTC),
	}

	input := r.openAt
	got := r.IsOpen(input)
	assert.True(t, got)
}

到目前為止還不錯。讓我為邊界條件添加更多測試:

func TestRestaurantBeforeOpen(t *testing.T) {
	r := Restaurant{
		openAt:  time.Date(2022, time.January, 17, 12, 0, 0, 0, time.UTC),
		closeAt: time.Date(2022, time.January, 17, 22, 0, 0, 0, time.UTC),
	}
	
	input := r.openAt.Add(-1 * time.Second)
	got := r.IsOpen(input)
	assert.False(t, got)
}

func TestRestaurantBeforeClose(t *testing.T) {
	r := Restaurant{
		openAt:  time.Date(2022, time.January, 17, 12, 0, 0, 0, time.UTC),
		closeAt: time.Date(2022, time.January, 17, 22, 0, 0, 0, time.UTC),
	}

	input := r.closeAt
	got := r.IsOpen(input)
	assert.True(t, got)
}

你可能已經注意到,這些測試之間的差異非常小,我認為這是表驅動測試的一個典型用例。

表驅動測試的介紹

現(xiàn)在讓我們看看,如果用表驅動的方式來寫,會是什么樣子:

func TestRestaurantTableDriven(t *testing.T) {
	r := Restaurant{
		openAt:  time.Date(2022, time.January, 17, 12, 0, 0, 0, time.UTC),
		closeAt: time.Date(2022, time.January, 17, 22, 0, 0, 0, time.UTC),
	}

	// test cases
	cases := map[string]struct {
		input time.Time
		want  bool
	}{
		"before open": {
			input: r.openAt.Add(-1 * time.Second),
			want:  false,
		},
		"just opened": {
			input: r.openAt,
			want:  true,
		},
		"before close": {
			input: r.closeAt,
			want:  true,
		},
		"just closed": {
			input: r.closeAt.Add(1 * time.Second),
			want:  false,
		},
	}

	for name, c := range cases {
		t.Run(name, func(t *testing.T) {
			got := r.IsOpen(c.input)
			assert.Equal(t, c.want, got)
		})
	}
}

首先,我聲明了測試目標。根據情況,它可以在每個測試案例里面。
接下來,我定義了測試用例。我在這里使用了map,所以我可以使用測試名稱作為map鍵。測試用例結構包含每個情況下的輸入和預期輸出。
最后,我對測試用例進行了循環(huán),并對每個測試用例運行了子測試。斷言與之前的例子相同,但這里我從測試用例結構中獲取輸入和預期值。
以表格驅動方式編寫的測試很緊湊,重復性較低,如果你想添加更多的測試,你只需要添加一個新的測試用例,無需更多的斷言。

去嘗試吧!

一方面,了解社區(qū)中共享的實踐很重要。go社區(qū)足夠大,很容易找到它們。你可以找到博客文章、講座、YouTube視頻等等。另外,說到go,很多實踐都來自go的標準庫。表驅動測試就是一個很好的例子。go是一種開源語言。閱讀標準包代碼是個好主意。
另一方面,僅僅知道它們并不能讓你感到舒服。到目前為止,學習最佳實踐的最好方法是在你現(xiàn)在工作的真實代碼庫中使用它們,看看它們有多合適,這實際上是我學習go實踐的方式。所以,多寫go,不要害怕犯錯。文章來源地址http://www.zghlxwxcb.cn/news/detail-785850.html

到了這里,關于go最佳實踐:如何舒適地編碼的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網!

本文來自互聯(lián)網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。如若轉載,請注明出處: 如若內容造成侵權/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經查實,立即刪除!

領支付寶紅包贊助服務器費用

相關文章

  • 【Golang】go編程語言適合哪些項目開發(fā)?

    【Golang】go編程語言適合哪些項目開發(fā)?

    前言 在當今數(shù)字化時代,軟件開發(fā)已成為各行各業(yè)的核心需求之一。 而選擇適合的編程語言對于項目的成功開發(fā)至關重要。 本文將重點探討Go編程語言適合哪些項目開發(fā),以幫助讀者在選擇合適的編程語言時做出明智的決策。 Go 編程語言適合哪些項目開發(fā)? Go是由Google開發(fā)

    2024年02月04日
    瀏覽(29)
  • 【Golang】VsCode下開發(fā)Go語言的環(huán)境配置(超詳細圖文詳解)

    【Golang】VsCode下開發(fā)Go語言的環(huán)境配置(超詳細圖文詳解)

    ??推薦網站(不斷完善中):個人博客 ??個人主頁:個人主頁 ??相關專欄:CSDN專欄、個人專欄 ??立志賺錢,干活想躺,瞎分享的摸魚工程師一枚 ? 話說在前,Go語言的編碼方式是 UTF-8 ,理論上你直接使用文本進行編輯也是可以的,當然為了提升我們的開發(fā)效率我們還是需

    2024年02月07日
    瀏覽(27)
  • Flutter 最佳實踐和編碼準則

    Flutter 最佳實踐和編碼準則

    最佳實踐是一套既定的準則,可以提高代碼質量、可讀性和可靠性。它們確保遵循行業(yè)標準,鼓勵一致性,并促進開發(fā)人員之間的合作。通過遵循最佳實踐,代碼變得更容易理解、修改和調試,從而提高整體軟件質量。 原文 https://ducafecat.com/blog/flutter-best-practices-and-coding-gui

    2024年02月15日
    瀏覽(31)
  • Golang中接口類型詳解與最佳實踐(二)

    之前的文章《Golang中的interface(接口)詳解與最佳實踐》詳細介紹了接口類型的定義、使用方法和最佳實踐。接口類型使得編寫可擴展、可維護和可復用的高質量代碼變得更加容易。 還是使用之前文章的例子,例如聲明了如下一個接口MyInterface: 這個接口定義了兩個方法Method

    2024年02月03日
    瀏覽(19)
  • 探索 Awesome Guidelines:編碼規(guī)范與最佳實踐的寶庫

    項目地址:https://gitcode.com/Kristories/awesome-guidelines 在軟件開發(fā)的世界中,遵循一致和高效的編碼標準是至關重要的。它不僅提高了代碼質量,還能讓團隊協(xié)作更加順暢。Awesome Guidelines 是一個精心整理的資源庫,收集了各種編程語言、工具和技術的最佳實踐和指導原則,幫助開發(fā)

    2024年04月11日
    瀏覽(35)
  • Go之流程控制大全: 細節(jié)、示例與最佳實踐

    Go之流程控制大全: 細節(jié)、示例與最佳實踐

    本文深入探討Go語言中的流程控制語法,包括基本的 if-else 條件分支、 for 循環(huán)、 switch-case 多條件分支,以及與特定數(shù)據類型相關的流程控制,如 for-range 循環(huán)和 type-switch 。文章還詳細描述了 goto 、 fallthrough 等跳轉語句的使用方法,通過清晰的代碼示例為讀者提供了直觀的指

    2024年02月08日
    瀏覽(17)
  • Go 項目依賴注入wire工具最佳實踐介紹與使用

    Go 項目依賴注入wire工具最佳實踐介紹與使用

    目錄 一、引入 二、控制反轉與依賴注入 三、為什么需要依賴注入工具 3.1 示例 3.2 依賴注入寫法與非依賴注入寫法 四、wire 工具介紹與安裝 4.1 wire 基本介紹 4.2 安裝 五、Wire 的基本使用 5.1 前置代碼準備 5.2 使用 Wire 工具生成代碼 六、Wire 核心技術 5.1 抽象語法樹分析 5.2 模板

    2024年04月08日
    瀏覽(19)
  • Go-Zero微服務快速入門和最佳實踐(一)

    并發(fā)編程和分布式微服務 是我們Gopher升職加薪的關鍵。 畢竟Go基礎很容易搞定,不管你是否有編程經驗,都可以比較快速的入門Go語言進行簡單項目的開發(fā)。 雖說好上手,但是想和別人拉開差距,提高自己的競爭力, 搞懂分布式微服務和并發(fā)編程還是灰常重要的,這也是我

    2024年04月28日
    瀏覽(21)
  • 深入解析Go非類型安全指針:技術全解與最佳實踐

    深入解析Go非類型安全指針:技術全解與最佳實踐

    本文全面深入地探討了Go非類型安全指針,特別是在Go語言環(huán)境下的應用。從基本概念、使用場景,到潛在風險和挑戰(zhàn),文章提供了一系列具體的代碼示例和最佳實踐。目的是幫助讀者在保證代碼安全和效率的同時,更加精通非類型安全指針的使用。 關注【TechLeadCloud】,分享

    2024年02月08日
    瀏覽(22)
  • SHA-512在Go中的實戰(zhàn)應用: 性能優(yōu)化和安全最佳實踐

    SHA-512在Go中的實戰(zhàn)應用: 性能優(yōu)化和安全最佳實踐

    在當今數(shù)字化的世界中,數(shù)據安全已成為軟件開發(fā)的核心議題之一。特別是在數(shù)據傳輸和存儲過程中,保護數(shù)據不被未經授權的訪問和篡改是至關重要的。為了達到這一目的,開發(fā)者們經常依賴于強大的哈希算法來加強數(shù)據的安全性。SHA-512,作為一種被廣泛認可和使用的安全

    2024年02月20日
    瀏覽(23)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領取紅包,優(yōu)惠每天領

二維碼1

領取紅包

二維碼2

領紅包