ビゞネスで6幎のベストプラクティスを実践

2014幎、GopherConカンファレンスのオヌプニングで「Go Production Environmentsのベストプラクティス 」ず題したプレれンテヌションを行いたした。 SoundCloudで、私たちはGoの最初のナヌザヌの1人でしたが、それたでに2幎間で既に曞かれおおり、䜕らかの圢でGoを戊闘でサポヌトしおいたした。 この間、私たちは䜕かを孊び、この経隓の䞀郚を共有しようずしたした。

それ以来、運甚ずむンフラストラクチャを担圓するSoundCloudチヌムで1日䞭Goでプログラミングを続け、珟圚はWeaveworksでWeave ScopeずWeave Meshを䜿甚しおいたす。 たた、オヌプン゜ヌスのマむクロサヌビスツヌルキットであるGoキットにも熱心に取り組みたした。 そしおこの間ずっず、私はGoプログラマヌコミュニティの発展に積極的に参加し、ペヌロッパやアメリカでの䌚議や䌚議で倚くの開発者ず出䌚い、成功ず倱敗のストヌリヌを収集したした。

2015幎11月、Goリリヌスの6呚幎に 、最初のパフォヌマンスを思い出したした。 時の詊緎に合栌したベストプラクティスはどれですか それらのどれが時代遅れであるか、効果がありたせんか 新しいテクニックはありたすか 3月に、 QCon Londonカンファレンスで講挔する機䌚がありたした。そこでは、2014幎のベストプラクティスず2016幎たでのGoのさらなる開発に぀いお話したした。 この投皿は私のプレれンテヌションからの抜粋です。

私は、テキストの重芁なポむントを「トップヒント-最高のヒント」の圢匏で匷調したした。

そしお、ここにコンテンツがありたす

  1. 開発環境
  2. リポゞトリ構造
  3. 曞匏蚭定ずスタむル
  4. 構成
  5. プログラム開発
  6. ロギングずメトリック
  7. テスト䞭
  8. 䟝存関係管理
  9. 組み立おず展開
  10. おわりに

開発環境


Go開発環境の芏則は、GOPATHの䜿甚に基づいおいたす。 2014幎に、単䞀のグロヌバル倉数GOPATHが存圚する必芁があるずいう芋解を擁護したした。 それ以来、私の立堎は幟分和らいでいたす。 私はただ、他の条件が同じであれば、これが最良の遞択肢であるず考えおいたすが、プロゞェクト、チヌム、その他の機胜にも倧きく䟝存したす。

あなたたたはあなたの䌚瀟が䞻にバむナリを䜜成する堎合、プロゞェクトごずに別々のGOPATHを䜿甚するず特定の利点が埗られたす。 このような堎合、Dave Cheneyず寄皿者の新しいgbナヌティリティを䜿甚しお、この目的のために暙準のgoツヌルを眮き換えるこずができたす。 このナヌティリティはすでに倚くの肯定的なレビュヌを受けおいたす。

䞀郚の開発者は、2぀のディレクトリ2゚ントリでGOPATHを䜿甚しおいたす。たずえば、 $HOME/go/external:$HOME/go/internalです。 go-teamは垞にそのようなケヌスの凊理方法を知っおいたした go getは䟝存関係を最初のパスのディレクトリにダりンロヌドするため、この゜リュヌションは、内郚コヌドをサヌドパヌティから厳密に分離する必芁がある堎合に圹立ちたす。

䞀郚の開発者がGOPATH/binをPATHに入れるのを忘れおいるこずに気付きたした。 ただし、これにより、 go getで取埗した実行可胜ファむルの実行が簡単になり、掚奚 go installコヌドアセンブリメカニズムの操䜜も簡単になりたす。 しない理由はありたせん。

✪ 䞊のヒント - $PATHに$GOPATH/binず、むンストヌルされたプログラムぞのアクセスが容易になりたす。


あらゆる皮類の゚ディタヌずIDEのおかげで、開発環境は継続的に改善されおいたす。 あなたがvimファンなら、すべおが完璧に機胜したしたFatih Arslanの疲れ知らずで信じられないほど効果的な仕事のおかげで、 vim-goプラグむンはクラスの最高のツヌルである本圓の芞術䜜品に倉わりたした。 私はEmacsにはあたり銎染みがありたせんが、goinmodel Dominik Honnefがこの分野で䟝然ずしお支配しおいたす。

匕き続き、倚くの人がSublime Text + GoSublimeバンドルを匕き続き䜿甚しおいたす。 圌女ずスピヌドで競うのは難しいです。 しかし、明らかに、最近、 Electronに基づいた線集者により倚くの泚意が払われたした。 Atom + go-plusには倚くのファンがいたす。特に、ある蚀語からJavaScriptに頻繁に切り替える必芁のある開発者の間で特にそうです。 Visual Studio Code + vscode-goバンチはダヌクホヌスでしたSublime Textよりも遅く実行されたすが、Atomよりも著しく高速であり、同時にクリックしお定矩するクリックしおオブゞェクトの堎所に移動するなど、私にずっお重芁な重芁な機胜を完党にサポヌトしたす Thomas Adamから玹介されお以来、私はこのバンドルを6か月間毎日䜿甚しおいたす。 玠晎らしいこず。

本栌的なIDEに぀いおは、特別に䜜成されたLiteIDEを挙げるこずができたす 。これは定期的に曎新され、独自のファンがいたす。 Go Intellijの興味深いプラグむンもあり、垞に改善されおいたす。

リポゞトリ構造


プロゞェクトがより成熟するのに十分な時間があり、その結果、倚くの明確なアプロヌチが開発されたした。 プロゞェクトが䜕であるかは、リポゞトリの構造によっお異なりたす。 閉じたプロゞェクトたたは䌚瀟の内郚プロゞェクトに぀いお話しおいる堎合は、先に進むこずができたす独自のGOPATHを持たせ、カスタムビルドツヌルを䜿甚し、喜びをもたらし、生産性を向䞊させる堎合は䜕でもしたす。

ただし、パブリックプロゞェクトたずえば、オヌプン゜ヌスの堎合、ルヌルはより厳栌になりたす。 コヌドはgo getず互換性がある必芁がありたす。これは、ほずんどのGo開発者があなたの䜜業を掻甚したい方法だからです。
理想的なリポゞトリ構造は、゚ンティティのタむプによっお異なりたす。 これらが排他的に実行可胜なバむナリファむルたたはラむブラリである堎合、コンシュヌマがベヌスパスに沿っおgo getたたはimportを䜿甚できるこずを確認する必芁がありたす。 したがっお、パッケヌゞのメむンコヌドたたはメむンコヌドをgithub.com/name/repoにむンポヌトし、サブパッケヌゞにはサブフォルダヌを䜿甚したす。

リポゞトリがバむナリファむルずラむブラリの組み合わせである堎合、 メむン゚ンティティを定矩しお、リポゞトリのルヌトに配眮する必芁がありたす。 たずえば、リポゞトリの倧郚分が実行可胜ファむルで構成されおいるが、ラむブラリずしおも䜿甚できる堎合、おそらく次のように構造化するこずをお勧めしたす。

 github.com/peterbourgon/foo/ main.go // package main main_test.go // package main lib/ foo.go // package foo foo_test.go // package foo 

有甚なアドバむス lib/サブフォルダヌでは、フォルダヌ自䜓ではなく、ラむブラリヌの名前に埓っおパッケヌゞに名前を付ける方が良いです。 ぀たり、この䟋では、 package libではなくpackage fooです。 これはかなり厳密なGoむディオムの䟋倖ですが、実際にはナヌザヌにずっお非垞に䟿利です。 HTTPサヌビスのストレステスト甚ツヌルである玠晎らしいtsenart / vegetaリポゞトリも同様に配眮されおいたす。

✪ トップヒント -fooリポゞトリが䞻に実行可胜なバむナリファむルで構成されおいる堎合は、ラむブラリコヌドをlib/サブフォルダヌに入れ、 package foo名前を付けpackage foo 。


リポゞトリが基本的にラむブラリであるが、1぀たたは2぀の実行可胜プログラムも含たれおいる堎合、構造は次のようになりたす。

 github.com/peterbourgon/foo foo.go // package foo foo_test.go // package foo cmd/ foo/ main.go // package main main_test.go // package main 

ラむブラリコヌドがルヌトに配眮され、実行可胜プログラムのコヌドがサブディレクトリcmd/foo/栌玍されるず、逆構造になりたす。 䞭間のcmd/レベルは、次の2぀の理由で䟿利です。


✪ トップヒント -リポゞトリの䞻な目的がラむブラリの堎合、実行可胜プログラムのコヌドをcmd/内のサブフォルダヌに配眮したす。


ここでの䞻なアむデアナヌザヌの䞖話-プロゞェクトの基本機胜の䜿甚を簡玠化したす。 ナヌザヌのニヌズに焊点を圓おたこの抜象的なアむデアは、Goの粟神そのものに合っおいるように思えたす。

曞匏蚭定ずスタむル


ここで倧きな倉化はありたせん。 これはGoが正しい道を歩んだ堎所の1぀であり、コミュニティの合意ずそれによる蚀語の安定性に本圓に感謝しおいたす。 コヌドレビュヌコメントは優れおおり、コヌド改蚂䞭に基準を満たすために必芁な最䜎限の基準セットである必芁がありたす。 たた、タむトルに異議を唱える状況や矛盟がある堎合は、Andrew Gerrandの優れた慣甚的な呜名芏則を䜿甚できたす。

✪ ヒント -Andrew Gerrandの呜名芏則を掻甚しおください。


ツヌルに関しおは、すべおが良くなりたした。 ゚ディタヌを構成しお、保存時にgofmtがトリガヌされるようにするか、goimportを改善したす ここにオブゞェクトが1぀もないこずを願っおいたす。 go vetナヌティリティを䜿甚しおも誀怜知はほずんど発生しないため、事前コミットフックの䞀郚ずしお䜿甚できたす。 たた、 gometalinterの優れたコヌド品質管理ナヌティリティに泚意しおください 。 誀怜知が発生する可胜性があるため、䜕らかの圢で独自の契玄を指定するこずは理にかなっおいたす。

構成


構成は、ランタむム環境ずプロセスの間にありたす。 明瀺的で十分に文曞化されおいる必芁がありたす。 私は今でもflagパッケヌゞを䜿甚し、䜿甚するこずをお勧めしたすが、それでも蚭定がより銎染みのあるこずを奜みたす。 匕数の詳现で簡朔な圢匏があるように、匕数の暙準構文をgetoptsスタむルで取埗したいず思いたす。 たた、䜿甚法のテキストをもっずコンパクトにしたいです。

Twelve-Factor Appの芏則に埓うアプリケヌションは、環境倉数を構成に䜿甚するように動機付けたす。 各倉数もflagずしお定矩されおいれば 、これは正垞だず思いたす。 ここでは自明性が重芁です。アプリケヌションの実行時の動䜜の倉曎は、簡単に怜出および文曞化された方法で行われる必芁がありたす。

すでに2014幎に蚀ったが、繰り返す必芁があるず考えおいる。func main内でフラグを定矩しお解析する 。 func main()のみが、ナヌザヌが䜿甚できるフラグを決定する暩利を持っおいたす。 ラむブラリで動䜜を構成できる堎合、構成パラメヌタヌは型コンストラクタヌの䞀郚である必芁がありたす。 構成をパッケヌゞのグロヌバルスコヌプに移動するず、利点の錯芚が生じたすが、節玄は誀りです。コヌドのモゞュヌル性を壊すため、他の開発者が䟝存関係の関係を理解するのが難しくなり、独立した䞊列化されたテストを曞くこずもはるかに難しくなりたす。

Tipヒント -func func main()のみが、ナヌザヌが䜿甚できるフラグを決定する暩限を持ちたす。


コミュニティは、これらのすべおのプロパティが組み合わされるフラグの包括的なパッケヌゞを非垞にうたく䜜成できるず思いたす。 既に存圚する可胜性がありたす。 もしそうなら、 私に知らせおください 。 私は間違いなくそれを䜿甚したす。

プログラム開発


䌚話では、プログラム開発の他の倚くの偎面を議論するための出発点ずしお構成を䜿甚したした2014幎にこのトピックを取り䞊げたせんでした。 たず、コンストラクタヌを芋おみたしょう。 すべおの䟝存関係を正しくパラメヌタヌ化するず、コンストラクタヌが非垞に倧きくなる可胜性がありたす。

 foo, err := newFoo( *fooKey, bar, 100 * time.Millisecond, nil, ) if err != nil { log.Fatal(err) } defer foo.close() 

構成オブゞェクトを䜿甚しお、そのような構成を衚珟した方がよい堎合がありたす。構成オブゞェクトは、構成されたオブゞェクトの動䜜を決定するオプションのパラメヌタヌを取る構造です 。 fooKeyパラメヌタヌfooKey必須であり、他のすべおが劥圓なデフォルト倀を持っおいるか、 fooKeyず仮定したす。

私はしばしば、構成オブゞェクトがいくらか断片化された方法で構築されるプロゞェクトに出くわしたす

 //    cfg := fooConfig{} cfg.Bar = bar cfg.Period = 100 * time.Millisecond cfg.Output = nil foo, err := newFoo(*fooKey, cfg) if err != nil { log.Fatal(err) } defer foo.close() 

ただし、いわゆる構造初期化構文を䜿甚しお、単䞀の匏を䜿甚しお䞀床にオブゞェクトを構築する方がはるかに優れおいたす。

 //    cfg := fooConfig{ Bar: bar, Period: 100 * time.Millisecond, Output: nil, } foo, err := newFoo(*fooKey, cfg) if err != nil { log.Fatal(err) } defer foo.close() 

オブゞェクトが䞭間の誀った状態にある堎合、ここには匏はありたせん。 同時に、 fooConfigの定矩を反映しお、すべおのフィヌルドが矎しく区切られ、むンデントされたす。

cfgオブゞェクトを䜜成し、すぐに䜿甚するこずに泚意しおください。 この堎合、 newFooコンストラクタヌに構造䜓宣蚀を盎接埋め蟌むこずで、䞭間状態のもう1぀のステップを回避し、別のコヌド行を保存できたす。

 //    foo, err := newFoo(*fooKey, fooConfig{ Bar: bar, Period: 100 * time.Millisecond, Output: nil, }) if err != nil { log.Fatal(err) } defer foo.close() 

玠晎らしい。

✪ 䞊のヒント -誀った䞭間状態を回避するには、構造リテラルの初期化を䜿甚したす。 可胜な限り、構造宣蚀を埋め蟌みたす。


次に、劥圓なデフォルトのトピックに移りたしょう。 Outputパラメヌタヌはnilできるこずに泚意しおください。

これがio.Writerたす。 䜕も特別なこずを行わない堎合、 fooオブゞェクトでそれを䜿甚したい堎合は、たずnilをチェックする必芁がありたす。

 func (f *foo) process() { if f.Output != nil { fmt.Fprintf(f.Output, "start\n") } // ... } 

これは玠晎らしいこずではありたせん。 出力倀の存圚を確認せずに䜿甚できる方がはるかに安党です。

 func (f *foo) process() { fmt.Fprintf(f.Output, "start\n") // ... } 

そのため、ここではデフォルトで有甚なものを提䟛する必芁がありたす。 むンタヌフェむスの皮類のおかげで、むンタヌフェむスのノヌオペレヌション実装぀たり、操䜜を実行しない実装、スタブ。翻蚳者のメモを提䟛するものを枡すこずができたす。 そのため、stdlib ioutilパッケヌゞにはio.Writerずいうioutil.Discardオペレヌションio.Writerが付属しおいたす。

✪ トップヒント -デフォルトのノヌオペレヌション実装では、nilチェックを避けたす。


これをfooConfigオブゞェクトに枡すこずもできたすが、これはかなり脆匱な゜リュヌションです。 呌び出し元のコヌドが呌び出しの堎所でこれを行うのを忘れた堎合、再びnilパラメヌタヌを取埗したす。 代わりに、コンストラクタヌ内で保護できたす。

 func newFoo(..., cfg fooConfig) *foo { if cfg.Output == nil { cfg.Output = ioutil.Discard } // ... } 

これは、Go Make Zero倀のむディオムの単なるアプリケヌションです。 ぀たり、null倀 nil を䜿甚しお、適切なデフォルトの動䜜no-opを提䟛できたす。

✪ 先端のヒント -特に構成オブゞェクトでは、null倀を有甚にしたす。


コンストラクタヌに戻りたしょう。 パラメヌタヌfooKey 、 bar 、 periodおよびoutputは䟝存関係です。 fooオブゞェクトの起動ず操䜜の成功は、それらのそれぞれに䟝存したす。 Goでの6幎間の毎日のプログラミングず倧芏暡プロゞェクトの芳察で正確に孊んだこずは、 䟝存関係を明瀺的にする必芁があるずいうこずです。

✪ 先端のヒント -䟝存関係を明瀺的に


あいたいな䟝存関係たたは暗黙的な䟝存関係が、技術サポヌト、混乱、バグ、未払いの技術的負債のための人件費の信じられないほどの量の理由だず思いたす。 foo型のprocessメ゜ッドを考えおみたしょう

 func (f *foo) process() { fmt.Fprintf(f.Output, "start\n") result := f.Bar.compute() log.Printf("bar: %v", result) // Whoops! // ... } 

fmt.Printf自埋的で、グロヌバル状態に圱響を䞎えず、䟝存したせん。 機胜面では、䞀皮の参照透過性がありたす。 したがっお、これは䞭毒ではありたせん。 明らかに、それはf.Barです。 log.Printfがグロヌバルパッケヌゞ内ロガヌオブゞェクトに圱響を䞎えるのは䞍思議です。これは、 Printf関数が無料であるため、単玔に明らかではありたせん。 したがっお、これも䞭毒です。

これらすべおの䟝存関係で䜕をしたすか それらを明瀺的にしたしょう 。 processメ゜ッドは操䜜䞭にログに曞き蟌むため、メ゜ッドたたはfooオブゞェクト自䜓は、ロギングオブゞェクトを䟝存関係ずしお受け入れる必芁がありたす。 たずえば、 log.Printfはf.Logger.Printfなりf.Logger.Printf 。

 func (f *foo) process() { fmt.Fprintf(f.Output, "start\n") result := f.Bar.compute() f.Logger.Printf("bar: %v", result) // . // ... } 

以前は、特定の皮類の䜜業を担保ずしおのロギングず芋なしおいたした。 したがっお、グロヌバルロガヌなどのサポヌトラむブラリを䜿甚しお、負担を軜枛できるこずを嬉しく思いたす。 ただし、メトリックスず同様に、ロギングはサヌビスの機胜においお決定的な圹割を果たすこずがよくありたす。 そしお、可芖性のグロヌバル空間で䟝存関係を非衚瀺にするこずで、ロギングなどの䞀芋無害な圢で、たたはパラメヌタヌ化を気にしない他のより重芁な䞻題コンポヌネントの圢で、私たちに打撃を䞎えるこずができたす。 。 厳栌なルヌルで将来の痛みから身を守りたしょうすべおの䞭毒を明瀺的にしたしょう。

✪ トップヒント -ロガヌは䟝存関係であり、他のコンポヌネント、デヌタベヌスクラむアント、コマンドラむン匕数などぞのリンクも同様です。


もちろん、ロガヌの劥圓なデフォルトを取埗する必芁がありたす。
 func newFoo(..., cfg fooConfig) *foo { // ... if cfg.Logger == nil { cfg.Logger = log.New(ioutil.Discard, ...) } // ... } 

ロギングずメトリック


党䜓ずしおの問題に぀いお話すず、ログを蚘録するこずで、戊闘での経隓がはるかに倚くなり、問題に察する敬意が匷くなりたした。 ロギングは、予想よりもはるかに高䟡であり、システムのボトルネックにすぐに倉わる可胜性がありたす。 このトピックに぀いおは、別の投皿で詳しく説明したしたが、簡単に蚀えば


ロギングが高䟡な堎合、メトリックは安䟡です。 コヌドベヌスの重芁なコンポヌネントからメトリックを削陀したす。 キュヌなどのリ゜ヌスの堎合は、 Brendan GreggのUSEメ゜ッドを䜿甚しお枬定したすUtilization、Saturation、Error countrate。 これが䜕らかの゚ンドポむントである堎合、トムりィルキヌのREDメ゜ッドに埓っお枬定したす芁求カりント率、゚ラヌカりント率、期間。

この問題で遞択する機䌚があれば、 プロメテりスを枬定システムずしお䜿甚するこずをお勧めしたす。 そしおもちろん、メトリックも䟝存関係です

ロガヌずメトリックから脱線しお、グロヌバル状態を盎接芋おみたしょう。 Goに関するいく぀かの事実を次に瀺したす。


これらの事実は個別に蚱容されたすが、䞀般的には困難です。 ぀たり、固定グロヌバルロガヌを䜿甚しお、コンポヌネントによっおログに送信された出力をどのようにテストできたすか このデヌタをリダむレクトする必芁がありたすが、䞊行しおテストする方法は たさか 答えは䞍十分です。 たたは、たずえば、異なる芁件を持぀HTTPリク゚ストを生成する2぀の独立したコンポヌネントがありたすが、これをどのように管理したすか 暙準のグロヌバルhttp.Clientを䜿甚するのは非垞に困難です。 䟋を参照しおください。

 func foo() { resp, err := http.Get("http://zombo.com") // ... } 

http.Getは、httpパケットでグロヌバルを呌び出したす。 暗黙的なグロヌバル䟝存関係がありたすが、これは簡単に削陀できたす。

 func foo(client *http.Client) { resp, err := client.Get("http://zombo.com") // ... } 

http.Clientをパラメヌタヌずしお枡すhttp.Client 。 しかし、これは具䜓的なタむプであるため、この関数をテストする堎合は、特定のhttp.Clientを提䟛する必芁がありたす。これにより、HTTPを介した実際の接続の確立が匷制されたす。 これは良くありたせん。 より良い方法HTTP芁求を実行実行できるむンタヌフェヌスを枡したす。

 type Doer interface { Do(*http.Request) (*http.Response, error) } func foo(d Doer) { req, _ := http.NewRequest("GET", "http://zombo.com", nil) resp, err := d.Do(req) // ... } 

http.Client Doer , Doer . : foo foo , , http.Client , .




テスト䞭


2014 , - . (stdlib) . . Go , . , . testing .

TDD/BDD , DSL , , . , . , , , , . When in Go, do as Gophers do ( Go, , ) : — Go, , , .

, . GOPATH, , , DSL . , , . , .

. (Mitchell Hashimoto) ( SpeakerDeck , YouTube ), .

: , Go , . , , , .

✪ Top Tip — .


http.Client , , - , . - -, HTTP-, , , . - - .

✪ Top Tip — , .



. 2014 , (vendor). - : . , Go 1.6 GO15VENDOREXPERIMENT vendor/ . . , , . :


✪ Top Tip — .


. Go . , . , 1.5 , . , : 1 , 2 . , : .

✪ Top Tip — .


, () API. , .

open source , , . , , . GO15VENDOREXPERIMENT , .

, . Etcd , , , Go Windows. , , , . , , , - .


, ( ) go install go build . install $GOPATH/pkg , . $GOPATH/bin , .

✪ Top Tip — go install go build .


, , gb . . , Go 1.5 - « ». GOOS GOARCH, go-. .

, , , , Ruby, Python, JVM. : , — FROM scratch. Go , .

: , , — -. . , AMI EC2, . .

おわりに


Top Tips:

  1. $GOPATH/bin $PATH , .
  2. foo , lib/ package foo .
  3. — , cmd/.
  4. .
  5. func main() , .
  6. , . , , .
  7. nil no-op- .
  8. , .
  9. !
  10. , , , . .
  11. .
  12. , .
  13. .
  14. .
  15. go install go build .

Go , , - . — — . ( Go Proverbs ), , « » (up the stack) , , Go.

Go.

Source: https://habr.com/ru/post/J301036/


All Articles