<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          『每周譯Go』淺談如何組織Go代碼結(jié)構(gòu)

          共 6868字,需瀏覽 14分鐘

           ·

          2021-05-24 16:15

          原文地址:https://changelog.com/posts/on-go-application-structure

          原文作者:Jon Calhoun

          本文永久鏈接:https://github.com/gocn/translator/blob/master/2021/w20_Thoughts_on_how_to_structure_Go_code.md

          譯者:Fivezh


          應(yīng)用程序的結(jié)構(gòu)很是困擾開發(fā)者。

          好的程序結(jié)構(gòu)可以改善開發(fā)者的體驗(yàn)。它可以幫助你隔離正在進(jìn)行中的內(nèi)容,而不必將整個(gè)代碼庫(kù)放在腦中。一個(gè)結(jié)構(gòu)良好的應(yīng)用程序可通過解耦和易于編寫的測(cè)試來幫助避免錯(cuò)誤。

          一個(gè)結(jié)構(gòu)不佳的應(yīng)用程序卻適得其反。它使得測(cè)試更加困難,找到相關(guān)代碼也異常復(fù)雜,并可能引入非必要的復(fù)雜性和冗長(zhǎng)的代碼,這些將拖慢開發(fā)速度卻沒有一點(diǎn)好處。

          最后一點(diǎn)很重要:使用一個(gè)遠(yuǎn)比需要復(fù)雜的程序結(jié)構(gòu),實(shí)際上對(duì)項(xiàng)目的傷害比幫助更大。

          我正在寫的東西可能對(duì)任何人來說都不是新聞。程序員很早就被告知合理組織代碼的重要性。無論是變量和函數(shù)命名,還是文件的命名和組織,這幾乎是每一個(gè)編程課中早期涉及的主題。

          所有這些都引出了一個(gè)問題:搞清楚如何構(gòu)建 Go 代碼為何如此困難?

          通過上下文來組織

          在過去的 Q&A 中,我們被問及 Go 應(yīng)用的結(jié)構(gòu)問題,Peter Bourgon 的回答是這樣的:

          很多語(yǔ)言都有這樣的慣例(我猜),對(duì)于同一類型的項(xiàng)目,所有項(xiàng)目的結(jié)構(gòu)都大致相同...如果你用 Ruby 做一個(gè) web 服務(wù),你會(huì)有這樣的布局,程序包會(huì)以你使用的架構(gòu)模式來命名。以 MVC 為例,控制器等等。但是在 Go 中,這并不是我們真正要做的。我們的程序包和項(xiàng)目結(jié)構(gòu)基本能反映出我們正在實(shí)施事務(wù)所在的領(lǐng)域。不是所使用的模式,也不是腳手架,而是取決于當(dāng)前項(xiàng)目所在領(lǐng)域中的特定類型和實(shí)體。**因此,從定義上講,不同項(xiàng)目在習(xí)慣上總是有所不同。**在一個(gè)項(xiàng)目中有意義的,在另一個(gè)項(xiàng)目中可能就沒有意義。不是說這里是唯一的方法,但這是我們傾向于的一種選擇......因此,是的,這個(gè)問題沒有答案,那種關(guān)于語(yǔ)言中約定俗成讓很多人非常困惑,但結(jié)果可能是錯(cuò)誤的選擇......我不知道,但我想這是主要的一點(diǎn)。Peter Bourgon 在 Go Time #147 中的回答。其中的加粗是我標(biāo)注的。

          總的來說,大多數(shù)成功的 Go 應(yīng)用程序的結(jié)構(gòu)并不能從一個(gè)項(xiàng)目復(fù)制/粘貼到另一個(gè)項(xiàng)目。也就是說,我們不能把一般的文件夾結(jié)構(gòu)復(fù)制到一個(gè)新的應(yīng)用程序,并期望它能正常工作,因?yàn)樾碌膽?yīng)用程序很可能有一套獨(dú)特的上下文來工作。

          與其尋找一個(gè)可以復(fù)制的模板,不如讓我們從思考應(yīng)用程序的上下文來開始。為了幫助你能理解我的意思,讓我們來一起看下,我是如何構(gòu)建用于托管 Go 課程的網(wǎng)絡(luò)應(yīng)用程序的。

          背景信息:這個(gè) Go 課程應(yīng)用程序是一個(gè)網(wǎng)站,學(xué)生在這里注冊(cè)課程并查看課程內(nèi)容。大多數(shù)課程都有視頻、鏈接(課程中使用的代碼)、以及其他相關(guān)信息。如果你曾經(jīng)使用過任何視頻課程網(wǎng)站,你應(yīng)該對(duì)它的外觀有一個(gè)大致的了解,但如果你想進(jìn)一步挖掘,你可以免費(fèi)注冊(cè)Gophercises。。

          在這一點(diǎn)上,我對(duì)應(yīng)用程序的需求相當(dāng)熟悉,但我要試著帶領(lǐng)你了解最初開始創(chuàng)建程序時(shí)的思考過程,因?yàn)槟鞘墙?jīng)常要開始的狀態(tài)。

          開始的時(shí)候,有兩個(gè)主要內(nèi)容上下文需要考慮:

          學(xué)生上下文 管理員/老師上下文 學(xué)生上下文是大多數(shù)人所熟悉的。在這種情況下,用戶登錄到一個(gè)賬戶,查看他們可以訪問的課程儀表板,然后向下導(dǎo)航到課程內(nèi)容。

          管理員的上下文有點(diǎn)不同,大多數(shù)人不會(huì)看到它。作為管理員,不用擔(dān)心消費(fèi)課程,而更關(guān)心如何管理它們。我們需要能添加新的課程,更新現(xiàn)有課程視頻,以及其他。除了能夠管理課程之外,管理員還需要管理用戶、購(gòu)買和退款。

          為了創(chuàng)建這種分離,我的倉(cāng)庫(kù)將從兩個(gè)包開始:

          admin/
            ... (some go files here)
          student/
            ... (some go files here)

          通過分離這兩個(gè)包,我能夠在每種情況下以不同的方式定義實(shí)體。例如,從學(xué)生的角度來看,Lesson類型主要由指向不同資源的一些 URL 組成,它有用戶相關(guān)信息,如 CompletedAt 字段表明該特定用戶何時(shí)/是否完成課程。

          package student

          type Lesson struct {
            Name         string // 課程名, 比如: "如何寫測(cè)試"
            Video        string // 課程視頻url,空則用戶無權(quán)訪問
            SourceCode   string // 課程源碼url
            CompletedAt  *time.Time // 表示該用戶是否完成課程的布爾值或完成時(shí)間
            // + 更多字段
          }

          同時(shí),管理員的 Lesson 類型沒有 CompletedAt 字段,因?yàn)樵谶@種上下文情況下是沒有意義。這些信息只對(duì)登錄用戶查看課程有關(guān),而不是管理員管理課程的內(nèi)容。

          相反,管理員 Lesson 類型將提供對(duì) Requirement 等字段的訪問,這些字段被用來確定用戶是否可以訪問此內(nèi)容。其他字段看起來也會(huì)有些不同;Video 字段不是視頻的URL,而是視頻托管地點(diǎn)的信息,因?yàn)檫@是管理員更新內(nèi)容的方式。

          Instead, the admin Lesson type will provide access to fields like Requirement, which will be used to determine if a user has access to content. Other fields will look a bit different as well; rather than a URL to the video, the Video field might instead be information about where the video is hosted, as this is how admins will update the content.

          package admin

          // 為了簡(jiǎn)潔起見,本例使用內(nèi)聯(lián)結(jié)構(gòu)
          type Lesson struct {
            Name string
            // 視頻URL可以使用這些信息動(dòng)態(tài)構(gòu)建(在某些情況下,使用時(shí)間限制的訪問令牌)
            Video struct {
              Provider string // Youtube, Vimeo, 等
              ExternalID string
            }
            // 決定源碼資源URL的有關(guān)信息,如 `repo/branch`
            SourceCode struct {
              Provider string // Github, Gitlab, 等
              Repo     string // 比如 "gophercises/quiz"
              Branch   string // 比如 "solution-p1"
            }

            // 用來確定用戶是否有權(quán)限學(xué)習(xí)本課。
            // 通常是類似于 "twg-base "的字符串,當(dāng)用戶購(gòu)買課程許可證時(shí),將有這些權(quán)限字符串鏈接到他們的賬戶。這可能不是最有效的方法,但現(xiàn)在已經(jīng)足夠用了,而且可以很容易地制作提供多個(gè)課程訪問權(quán)限的程序包。
            Requirement string
          }

          我采用這種方式是因?yàn)橄嘈胚@兩種情況會(huì)有足夠的差異,以證明這種分離是合理的,但我也懷疑這兩種情況都不會(huì)發(fā)展到足夠大,以證明未來任何進(jìn)一步的組織。

          我可以用不同的方式組織這些代碼嗎?當(dāng)然可以。

          我可能改變結(jié)構(gòu)的一個(gè)方法是進(jìn)一步分離它。例如, admin包的一些代碼與管理用戶有關(guān),而另一些代碼與管理課程有關(guān)。把它分成兩個(gè)部分是很容易的。另外,可以把所有與認(rèn)證有關(guān)的代碼(注冊(cè)、修改密碼等)放到一個(gè) auth 包中。

          與其過度思考,挑選看起來合適的方案并按需進(jìn)行調(diào)整更有意義。

          以層的方式組織包結(jié)構(gòu)

          另一種分割程序的方法是通過依賴關(guān)系。Ben Johnson在gobeyond.dev,特別是在Packages as layers, not groups一文中對(duì)此進(jìn)行了很好的討論。這個(gè)概念與Kat Zien在 GopherCon 演講中提到的六邊形架構(gòu)非常相似,"你如何組織Go應(yīng)用程序的結(jié)構(gòu)"。

          從較高的角度來看,我們的想法是我們擁有一個(gè)核心域,在其中定義資源和與之交互所使用的服務(wù)。

          package app

          type Lesson struct {
            ID string
            Name string
            // ...
          }

          type LessonStore interface {
            Create(*Lesson) error
            QueryByPermissions(...Permission) ([]Lesson, error)
            // ...
          }

          使用像 Lesson 這樣的類型和 LessonStore 這樣的接口,我們可以編寫一個(gè)完整的應(yīng)用程序。如果沒有 LessonStore 的實(shí)現(xiàn),我們就不能運(yùn)行程序,但可以編寫所有的核心邏輯,而不必?fù)?dān)心如何實(shí)現(xiàn)。

          當(dāng)我們準(zhǔn)備好實(shí)現(xiàn)像 LessonStore 接口時(shí),我們會(huì)給程序添加一個(gè)新的層。在這種情況下,它可能是以 sql 包的形式出現(xiàn)。

          package sql

          type LessonStore struct {
            db *sql.DB
          }

          func Create(l *Lesson) error {
            // ...
          }

          func QueryByPermissions(perms ...Permission) ([]Lesson, error) {
            // ...
          }

          想更多了解這一策略,建議去看看 Ben 的文章,網(wǎng)址是 https://www.gobeyond.dev/

          譯者注:該文章的中文譯稿 以層的方式而不是組的方式進(jìn)行包管理

          分層打包的方式似乎與上述 Go 課程中選擇的方法大相徑庭,但實(shí)際上,混合這些策略要比最初看起來容易得多。例如,如果把 adminstudent 分別作為一個(gè)定義了資源和服務(wù)的域,就可以使用分層打包的方法來實(shí)現(xiàn)這些服務(wù)。下面是使用 admin 包域和 sql 包的例子,其中 sql 包有一個(gè) admin.LessonStore 的實(shí)現(xiàn)。

          package admin

          type Lesson struct {
            // ... same as before
          }

          type LessonStore interface {
            Create(*Lesson) error
            // ...
          }
          package sql

          import "github.com/joncalhoun/my-app/admin"

          type AdminLessonStore struct { ... }

          func (ls *AdminLessonStore) Create(lesson *admin.Lesson) error { ... }

          上面這些是對(duì)該應(yīng)用的正確選擇嗎?我不知道。

          使用這樣的接口會(huì)使測(cè)試代碼片斷變得容易,但這只有在它真正有用時(shí)才重要。否則,我們寫接口,解耦代碼,并創(chuàng)建新的包,但卻沒有看到真正的好處。這樣一來,這種方案只會(huì)讓自己更加忙碌。

          唯一錯(cuò)誤的決定是沒有決定

          除了以上這些結(jié)構(gòu)之外,還有無數(shù)種(或無結(jié)構(gòu))組織代碼的方法,根據(jù)不同的上下文這些方法也是有意義的。我曾在一些項(xiàng)目中嘗試扁平結(jié)構(gòu)(單一的包),我仍然對(duì)這種方式的效果感到震驚。當(dāng)剛開始寫 Go 代碼時(shí),我?guī)缀踔皇褂?MVC。這不僅比整個(gè)社區(qū)引導(dǎo)的更好,而且擺脫了因不知道如何布局程序結(jié)構(gòu)的困境,避免了不知道從哪里開始的難題。

          在同一 Q&A 中,我們被問到如何組織 Go 代碼,Mat Ryer表達(dá)了沒有一個(gè)固定方式來組織代碼的好處:

          我認(rèn)為,這里是非常自由的,雖然說沒有真正的方法,但這也意味著你不會(huì)做錯(cuò)。適合你的情況才是好的選擇。Mat Ryer 在 Go Time #147 中發(fā)表的觀點(diǎn)

          現(xiàn)在我有很多使用 Go 的經(jīng)驗(yàn),我完全同意 Mat 的觀點(diǎn)。決定一個(gè)應(yīng)用適合什么樣的結(jié)構(gòu),這是一種自有。我喜歡沒有一個(gè)固定的方法,也沒有一個(gè)錯(cuò)誤的方法。盡管現(xiàn)在有這種感覺,但也記得在我經(jīng)驗(yàn)不足的時(shí)候,因?yàn)闆]有具體的例子可以參考而感到非常沮喪。

          事實(shí)是,如果沒有一些經(jīng)驗(yàn),決定什么結(jié)構(gòu)適合你的情況幾乎是不可能的,但現(xiàn)實(shí)往往迫使我們?cè)讷@得任何經(jīng)驗(yàn)之前就得選擇。這是一個(gè)《第22條軍規(guī)》陷阱,在還沒有開始的時(shí)候就阻止了我們。

          然而我并沒有放棄,而是選擇了使用所知道的東西:MVC。這使我能夠編寫代碼,獲得一些工作,并從這些錯(cuò)誤中學(xué)習(xí)。隨著時(shí)間的推移,開始理解其他的代碼結(jié)構(gòu)方式,我的應(yīng)用程序與 MVC 的相似度越來越低,但這是一個(gè)漸進(jìn)的過程。我甚至懷疑,如果一開始就強(qiáng)迫自己立即弄好程序的結(jié)構(gòu),這根本就不會(huì)成功。最多只能在經(jīng)歷了大量的挫折之后獲得成功。

          絕對(duì)正確的是,MVC 永遠(yuǎn)不會(huì)像為項(xiàng)目量身定做的應(yīng)用結(jié)構(gòu)那樣提供清晰的信息。同樣正確的是,對(duì)于一個(gè)幾乎沒有 Go 代碼經(jīng)驗(yàn)的人來說,發(fā)掘項(xiàng)目的理想結(jié)構(gòu)并不是一個(gè)現(xiàn)實(shí)的目標(biāo)。它需要實(shí)踐、實(shí)驗(yàn)和重構(gòu)來獲得正確的結(jié)果。MVC 是簡(jiǎn)單而平易近人的。當(dāng)我們沒有足夠的經(jīng)驗(yàn)或上下文來想出更好的選擇時(shí),它會(huì)是一個(gè)合理的開始。

          總結(jié)

          正如在本文開頭所說,好的程序結(jié)構(gòu)是為了改善開發(fā)者的體驗(yàn)。它是為了幫助你以一種有意義的方式來組織代碼。它并不是要讓新手們陷入癱瘓、不知道該如何繼續(xù)。

          如果你發(fā)現(xiàn)自己被卡住了,不知道如何繼續(xù),問問自己什么是更有效的:繼續(xù)卡住,還是挑選一個(gè)結(jié)構(gòu)并加以嘗試?

          對(duì)于前者,什么都做不了。對(duì)于后者,即使你做錯(cuò)了,也可以從中學(xué)習(xí)經(jīng)驗(yàn),并在下一次做得更好。這聽起來比永遠(yuǎn)不開始要好得多。

          瀏覽 55
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧美一级AA大片免费看视频 | 国产特级毛片AAAAAA喷潮 | 在线观看亚洲视频网站 | 苍井空最新一区二区三区电影 | 操逼入口 |