Goのインターフェイスを誤って使用しています!

このような大きな見出しで、私は最初に記事を書くことを考えました。 いいえ、実際には、あなたがうまくやっている可能性は完全にあり、この記事はあなたに関するものではありません。 しかし、非常に多くの場合、人々が他の言語から来たとき、彼らが慣れている言語から「耳を引っ張る」パターンをどのように試みているかを見ることができます。







この記事では、Goの初心者プログラマー(私を含む)が犯す典型的な間違いと、これらのエラーを回避する方法を集めたいと思います。



バギング



goのパッケージには、他のほとんどの言語にはない機能が1つあります。パッケージ間の循環依存関係は許可されていません。 したがって、大規模なアプリケーションを効率的に開発できるように、予期せずパッケージに分割することが非常に重要になります。



多くの場合、パッケージに分解するとき、人々はそれらを小さすぎるエンティティに分割しようとし、パッケージ間の「共通ロジック」を含む「common」、「domain」、「misc」などの抽象名を持つパッケージが表示されます。ループを作成しないようにします。 原則として、この事実に直接悪いことはありませんが、標準ライブラリを見ると、かなり複雑なエンティティで動作しますが、そのようなパッケージはありません。



彼女はどうやって? 標準ライブラリとプロジェクトの違いは何ですか? よく見ると、他の言語とは異なるいくつかの基本的なポイントを文字通り強調することができます。



1.パッケージは大きくなる可能性があります



たとえば、net / httpパッケージには40を超えるファイルが含まれ、20を超える異なるパブリックタイプが定義されています。 それらはすべてHTTPに関連しており、それらをいくつかのパケットに分割するのは非論理的です。 http.Header、http.Client、http.Serverなどの型はすべて論理的に見えるため、たとえば、より小さいモジュールを取得するためだけに、クライアント実装をサーバー実装から分離することを試みる必要はありません。



2.パッケージは、ほぼ排他的にインターフェイス、グローバル定数、および変数で構成できます



たとえば、「入出力」の非常に一般的な概念に関連するすべての必要なインターフェイスと定数を含むioパッケージがあります。 しかし、このパッケージには、これらのインターフェイスの実装が多数あるため、実装のヒントはまったくありません。そうしないと、リングの依存関係が判明します。



要するに、アイデアは次のとおりです。関連するエンティティが属するサブジェクト領域に基づいてパッケージに分割します。 異なるパッケージの一部のエンティティが相互に参照する場合、おそらくそれらを1つの大きなパッケージに入れるだけで、より論理的で理解しやすい型階層になります。



インターフェースの使用



インターフェイスは、実装とは別のパッケージで定義する必要があります。 実装は、インターフェースではなく特定のタイプを返します。 ほとんどの場合、インターフェイスは、依存関係取るパッケージで定義するか、完全に別個に定義する必要があります。 これにより、周期的な依存関係が同時に回避され、コードのテストと柔軟性が向上します。



最初のアプローチの例(インターフェースはレシーバーで定義されます)



あなたはすべてfmtパッケージを知っています。 そして彼らはまた、おそらく次のようなものを書いた:



//  habr.go package habr type Article struct { title string } func (a *Article) String() string { return a.title } //  main.go package main import ( "fmt" "habr" ) func main() { a := &habr.Article{title: "    Go !"} fmt.Printf("The article: %s\n", a) }
      
      





String()メソッドに注意してください。 fmt.Stringerインターフェースはfmtパッケージで宣言され、このインターフェースの実装は同じパッケージで受け入れられます(この場合も暗黙的に)。



一方、habrパッケージはfmtパッケージにまったく依存せず、fmtパッケージは必要に応じて自由にインポートできます。 これにより、コードをリファクタリングし、パッケージ構造全体を再構築する必要なく、循環依存関係を「ソフトに」「作成」できます。



より詳細な例(および根拠)は、次のリンク(英語)で見ることができます。





2番目のアプローチの例(個別のパッケージでのインターフェースの分離)



インターフェース(または何らかのタイプ)が複数の場所で必要であり、それ自体に値がある場合は、別のパッケージに割り当てる必要があります。 これがioパッケージの登場です-最も有用なインターフェース、定数、I / Oに何らかの関係がある変数が含まれています。 このパッケージをインポートするときに追加の依存関係を導入しないために、ioからのインターフェースを操作するための便利な機能を含む別のパッケージ、ioutilパッケージがあります。



ioパッケージのインターフェースは非常に成功していることがわかったので、私が知る限り、Goは標準ライブラリが「そのまま」ファイル、ソケット、HTTP応答、バイトバッファーなどに同時に印刷できる唯一の言語です。適切に設計された抽象化のおかげで、機能はほぼ「無料」で標準ライブラリに追加されました。



一般的な推奨事項



この(少し短く、少し厄介な)記事では、初心者がgoを使用する際の推奨事項で見落としがちな2つのことを引用しました。 この記事が、この美しい言語を使用する苦痛を回避する方法をよりよく理解するのに役立つことを願っています。



参照資料



ほとんどの場合、既にEffective Goを読んでいますが、そうでない場合は強くお勧めします:)。 また、goを使用したプログラミングの際の「グッドプラクティス」について説明する2つの優れた記事があります。






All Articles