Goの野暮ったさとどう付き合うか

135.8K Views

August 24, 25

スライド概要

profile-image

Fellow at Henry, Inc. Tech SaaSのPdM、スタートアップ取締役CTOや外資スタートアップのIC等を経て現任。好きな言語はGoとPerlと中国語で雑なOSSを200以上量産している。3 times ISUCON winner. 著書「みんなのGo言語」共著他。Podcast: https://oss4.fun/

シェア

またはPlayer版

埋め込む »CMSなどでJSが使えない場合

ダウンロード

関連スライド

各ページのテキスト
1.

Goの「野暮ったさ」と どう付き合うか 文化圏の思想の違いと技術選定

2.

Profile ● ● ● id: Songmu (ソンムー) Masayuki Matsuki / 松木雅幸 Blog: おそらくはそれさえも平凡な日々 ○ ● ● ● http://www.songmu.jp/riji/ 好きな言語は、PerlとGoと中国語 3 Times ISUCON Winner OSS作家 ○ ○ ○ 200+ OSS Developer 入門監視 付録C 執筆 「みんなのGo言語」共著者

3.

この資料について ● 技術アドバイザーとして関わっているRepro社の社内勉強会で話した内容 の公開版です ○ ○ ● Ruby開発者が多い組織なので、それを踏まえた内容になっています 加筆・修正をおこなっています この資料は k1LoW/deck で作られました

4.

The Go Programming Language ● ● 苦手な人も多いかも 今日話す話も散々聞いた話で聞き飽きた話かも知れない ○ ○ ● 耳にタコ 言い訳がましい話に感じるかも 嫌いになって欲しくない ○ Goがどういう言語で、どう付き合っていくと良いのか

5.

アジェンダ ● ● ● Go概論 Go開発に関する一般的なFAQ コードレビュー

6.

Go概論

7.

Goの名称 "Go"が正式名称 ● ● "golang"はあくまでドメイン名やSNSのタグ用途 "Go言語" という訳語を好まない人も ○ ○ ● 個人的には書籍やブログのタイトルでは使うのは良いと思っている 文中では「Go」と書くと良い Frequently Asked Questions (FAQ) - The Go Programming Language#go_ For instance, the social media tag for the language is “#golang”. The language’s name is just plain Go, regardless.

8.

Goはどういう言語か ● ● ベターC (だと考えるとめっちゃ良い) 悪くないパフォマンス ○ ○ ○ ● ● 単純なベンチマークではC, C++, Rustなどの最速組よりちょっと遅いくらい ■ 数倍オーダー (Javaとかと同等) ■ 他のスクリプト言語とかだと数十倍オーダー (V8は除く) コンパイルも速い(遅くない) ランタイムの小ささ・省メモリ実行 並行処理が得意 表現力はそこまで高くない 参考: 「Go言語らしさ」とは何か? Simplicityの哲学を理解し、Go Wayに沿った 開発を進めることの良さ|ハイクラス転職・求人情報サイト アンビ(AMBI)

9.

動的型と静的型の良いとこ取り ● ● 動的型付けのスクリプト言語における開発の効率性と容易さと、静的型付けの言 語の安全性を融合させる試みであり、ネットワークやマルチコアネイティブなモ ダンな言語であることを目標にしている Frequently Asked Questions (FAQ) - The Go Programming Language#creating_ Go is an attempt to combine the ease of programming of an interpreted, dynamically typed language with the efficiency and safety of a statically typed, compiled language. It also aims to be modern, with support for networked and multicore computing.

10.

Songmu個人の実感として ● ● 静的型の安全性がありながら、go run でスクリプト的に実行ができる テストも書きやすく、go test で簡単に実行できる ○ ● -run オプションで特定のテスト実行も簡単 「ちょっと書いて、ちょっとテストを動かす」というリズムで開発がしや すい ○ スクリプト言語開発者出自としてはありがたい

11.

GoがGoogleで作られた背景 開発の生産性とスケーラビリティ ● Go at Google: Language Design in the Service of Software Engineering - The Go Programming Language The goals of the Go project were to eliminate the slowness and clumsiness of software development at Google, and thereby to make the process more productive and scalable. The language was designed by and for people who write―and read and debug and maintain―large software systems.

12.

2種類の"Scale" ● ● 「システム」と「開発」それぞれのScalabilityが重要である 分散システム・マイクロサービス向きの言語 ○ ○ シンプルなインターフェースのマイクロサービスを協調動作させる モノリシックにはそれほど向いていない Toward Go 2 - The Go Programming Language

13.

Goの作者やメンテナ ● オリジナル作者 ○ ○ ○ ● Rob Pike氏 ■ UNIXやUTF-8の開発者 Ken Thompson氏 ■ UNIXやUTF-8の開発者 ■ B言語 (C言語の前身)の作者 Robert Griesemer氏 ■ JVMやV8などのコンパイラ開発・研究 テックリード (2012-2024) ○ Russ Cox氏

14.

オピニオンリーダー ● 特にRob Pike氏やRuss Cox氏がGoの思想面を引っ張っていて、動画や記 事も多い ○ ● 「優しい終身の独裁者」的 去年の9月にテックリードの引継が行われた ○ ○ Russ Coxの声明: passing torches to Austin and Cherry 「優しい終身の独裁者」に関する言及も

15.

他のキーパーソン ● Dave Cheney氏 ○ ○ ○ ご存知 github.com/pkg/errors作者 Blogや登壇も多い ■ SOLID Go Design | Dave Cheney ■ The Zen of Go | Dave Cheney ■ Should methods be declared on T or *T | Dave Cheney コミュニティリーダーでオピニオンリーダー的存在だがGoコア開発者ではない ■ 時にコアチームとの見解の若干の相違が見られることも

16.

"Simplicity" ● ● 機能を絞り込んでいる 機能毎の直交性を重視 シンタックスシュガーや複数の機能にまたがる便利機能の提供を避ける ○ ● 参考 ○ ○ Simplicity is Complicated ■ Go is actually complex, but it feels simple. Less is exponentially more

17.

速くはないが変化し続けている ● 新しい機能を取り込んでいない訳ではない ○ ● 思想的に新機能追加をしないという事は実はあまりない ○ ● やらないと決めたことはGitHub issue上でばっさりcloseされてはいる とは言え、かなり慎重ではある ○ ● ● 最近だとジェネリクス、イテレータ、構造化ログ等 遅く見えるかもしれない 後方互換性への強い拘り 反面、入ったけどこの機能要らなかったね、みたいなのが少ない

18.

安定性の高さ ● Google社内で使われている信頼性 ○ ○ ● ● 新バージョンのRCが既にGoogleのプロダクションで使われている パッチバージョンが0でも安心 ランタイムやライブラリの更新が気軽に出来る GC・メモリ管理不要で安全・安心

19.

"Readability" ● 防御的なレイヤーを増やしすぎるより、読みやすさを重視 ○ ○ ● 暗黙的よりもの明示的なものを好む ○ ○ ● コード生成を活用し、生成された物もリポジトリ管理する マクロやモジュールインストール時のフック機構の様な物を設けていない AI支援は受けやすいのを個人的に感じる ○ ● 規約・フォーマットで守る linterやformatterを積極的に使う (後付けのメリットではある) 参考: Goに入ってはGoに従え

20.

Goと正しさ 正しさとGo - Qiita ● ● ● 正しさのために重厚にしすぎることを由としない 徒にレイヤーを増やすことは避ける 学習コストや認知負荷を逆に高めてしまうことも

21.

"Worse is Better" 「悪い方が良い」原則: The Rise of “Worse is Better” ● ● 正しいやり方の「MIT アプローチ」 悪い方が良い「New Jersey アプローチ」

22.

それぞれの「簡潔性」 The Rise of “Worse is Better” より ● MIT アプローチ ○ ● デザインは実装と使用法の両面において単純でなければならない。 このとき、使用法が 単純な方が、実装が単純なことより重要である。 New Jersey アプローチ ○ ○ デザインは実装と使用法の両面において単純でなければ ならない。このとき、実装が単 純な方が、使用法が単純なことより重要 である。 単純さがデザインにおいて最も重視されるべき事柄である。 「EasyかSimpleか」にも通じる話

23.

(参考)Ruiさんの話 ● 「悪い方が良い」原則と僕の体験談|Rui Ueyama 「賢いやり方」や〇〇原則といったものにこだ わりすぎないこと。実装の単純さはとてもとて も重要で、そのためにはレイヤ分けの一貫性や 完全性、コード重複の少なさを、リーズナブル な範囲で犠牲にしてもよい。自分が良いと思う デザインで小さくて実際に動くものを作り、そ れを次第に育てていくことが大切だ。

25.

フルスタック志向かコンポジット志向か フレームワーク選定等にも関わってくる話 ● フルスタック: 「全部入り」を目指すか ○ ● easy志向と親和性が高い コンポジット: 小さな小回りの聞くソフトウェアを組みわせるか ○ simple志向と親和性が高い このあたりはエコシステムの文化によっても好みが左右される。

27.

JSONとYAMLの例 ● ● JSONはsimple YAMLはeasy それぞれに良さがあり、それぞれの良さを他方に求めると、その価値を毀損す る。

28.

JSONはsimple ● ● 覚えることが少ない反面、表現力に乏しい 一つのことを上手くやる ○ ● 人間が読めるシリアライゼーションフォーマットとして優れている ■ 必ず一行にできる強み ● ログフォーマットとしても使いやすい 仕様が簡潔 ○ ○ RFC 8259 / ECMA-404 で定義され、パーズもそこまで難しくない ■ サーバーサイドでのリソース消費も高くない ■ もちろんバイナリフォーマットに比べると効率は悪いが 融通の効かない仕様が、堅牢性や一貫性、相互運用性に寄与している ■ キーのクオート必須、ケツカンマやコメントを許容しないこと等 ■ システムを跨いだAPI等で使いやすい ● 任意のサーバーが返したJSONを任意の言語のクライアントで読める

29.

YAMLはeasy ● 覚えることが多いが表現力が豊かで読み書きしやすい(ように一見思える) ○ ● ● 設定ファイルなどで利用される 仕様が複雑で難解 ○ ○ ● 多くのシンタックスシュガー 実は完璧なYAMLパーザーはこの世におそらく存在しない ■ 実用的には十分なケースが多いのだろうが ■ 例えばYAMLは文字列以外もキーにできる 😨 ある言語のライブラリで書かれたYAMLが他のライブラリで読めるとは限らない 個人的にYAMLには色々思うところはある ○ ○ 受け入れざるを得ないものになっているとは思う 類似の物を作ろうとしても普及などの点で難しいのではないかと思っている ■ 頑張ろうとしている人は沢山いるが ■ 新たな「オレオレ」が出来るだけという感じはある

30.

Unix哲学 ● Small is Beautiful ○ ● Make each program do one thing well ○ ● 小さいことは良いことだ 一つのことを上手くやる KISS ○ Keep It Simple, Stupid simpleでコンポジット志向であると言える。Unix系システムを触ってきたベテ ランのエンジニアはその志向の人が多い。

31.

文化圏による違い(私見) その文化圏・エコシステムにそぐわないとなかなか使われない。どういうキ ラーソフトウェアが作られたかによっても左右されるので鶏卵ではある。 ● ● フルスタック文化圏では、小回りの聞くユーティリティが育ちづらい コンポジット文化圏では、フルスタックフレームワークが育ちづらい 「なぜ、フルスタックフレームワークが少ないのか / 小回りの聞くユーティリ ティが少ないのか」というのは、文化圏の違い。 → 郷に入りては郷に従う

32.

具体例 Web開発の文脈において覇権を取ったWebフレームワークがあるかどうかで文 化圏の色が変わる。 ● Ruby, PHP はフルスタック志向 ○ ● Rails, Laravel Perl, Go はコンポジット志向 ○ 正確には、私がPerlを良く書いていた頃は、easyでマジカル(complicated)なコードを書 くことへの反省が界隈的にあった気がする 私はsimple志向かつコンポジット志向寄り。

33.

フルスタック志向のメリット ● セオリーが定まっており余計なことを考えずに済む ○ ○ ● ● ● Rails is omakase Batteries Included Philosophy ネット上のドキュメントで十分なことが多く、社内ドキュメントを簡略化 できる 開発思想を通底させることが簡単 迷いが少なく、新人の立ち上がりも早い

34.

フルスタック志向のデメリット ● 融通が利かず、レールから外れると大変 ○ ○ ● マイクロサービス分割など 一部差し替えるなどがやりづらい ■ 差し替えられる場合も依存の注入が複雑化して認知負荷が高くなることも 乗り換えが難しくロックインされるリスクも高め ○ 一蓮托生

35.

フルスタック志向のデメリット(続き) ● 多くのユーティリティを内包することでメンテナンス難易度が高い ○ ● 強力なリーダーシップへの依存 ○ ○ ● 外部からのコントリビュートのハードルも高い 超人的な「優しい終身の独裁者」 もしくは強力なガバナンス体制 複雑度が高く結果として寿命が短め ○ ○ メンテナンスが止まったり賞味期限が過ぎたライブラリへの依存の危険性 ■ これまで多くのフレームワークが廃れていった ➡ Railsはめちゃくちゃすごい!!!

36.

batteries includedの良し悪し ● Pythonの大事なモットー ○ ○ ○ ● ちゃんと実現されれば便利 しかし標準ライブラリのメンテナンスは時に大変 言語コアのバージョンアップ時にメンテナンスの足並みを揃えるのが大変 ■ お互い互換を壊しづらい Rustの場合 ○ ○ ○ The Rust Libz Blitz | Rust Blog batteries includedにしない思想 Cargo(パッケージシステム)を上手く作ることでカバー

37.

Goの場合 ● 標準ライブラリはbatteries included的な部分がある ○ ○ ● ● ● 充実している 同時に追加にかなり慎重 日付ライブラリ、TLSが標準ライブラリに含まれているのはデカイ 開発体制が信頼できる コミュニティライブラリの標準ライブラリ昇格が無い ○ ○ これは昔は微妙に思っていたが統一感の観点でメリットだと思うようになった 既存のライブラリを参考にしても全て提案ベースで新たに作られる

38.

コンポジット志向のメリット ● ● ● ● 差し替えや切り出すことが比較的容易 コントローラブル OSSライブラリに変更を取り込んでもらいやすい 小さいものは長生きしやすい

39.

コンポジット志向のデメリット ● 思想の通底が難しく、統制が難しい ○ ○ ● ドキュメント整備など 新人の立ち上がりが遅くなりがち 結局グルーコードが肥大化する ○ ○ ○ easyっぽいオレオレの仕組みを作り上げて、混乱を生む ガラパゴス化 組み合わせ方にセンスが問われる

40.

ガラパゴスを避けることの大事さ ● ● トレンドに乗る・作る オレオレを社内に閉じ込めない ○ ○ ● 世の中に問う 敲かれた技術やプラクティスの方が良いものになる 良いモノよりも使われているモノの方が生き残る可能性が高い

41.

ライブラリ選定力の重要性 ● ● Goだとフレームワークが全部用意して「くれない」 標準・準標準パッケージは充実している ○ ● 準標準: golang.org/x/ から始まるパッケージ ■ 最近は準標準でも非推奨なものも ■ golang.org/x/exp/ に実験的な物や非推奨なものが置かれる流れに サードパーティOSSを選定する必要がある

42.

OSSの開発速度と互換性問題 ● ● ● 「自分たちが追随できるか」が大事 速すぎないか、適度に枯れているか、腐っていないか 後方互換をどれくらい大事にしているか ○ ○ ○ semverに期待しすぎない ソフトウェアによって文化や風土、塩梅がある バージョンアップデート後にどれくらい気軽に更新できるか ■ Goはパッチバージョンが0でも安心 ■ 様子見が必要なソフトウェアも

43.

Tip: 依存OSSのリポジトリを一括で高速に ゲットする ● 【宣伝】 ghq handbookに載ってます ○ ○ ● ghq handbook | Zenn ghq handbook | leanpub モジュールとリポジトリの対応が明確ならではの旨味

44.

Go開発に関する一般的なFAQ

45.

インターフェースか 具象型(構造体)か 使い分け・インターフェースを使いすぎていないか

46.

Accept Interfaces Return Concrete Types ● 「インターフォースを受取り、具象型(構造体)を返せ」 ○ ● ● 良く言われる格言だが、実はGo公式ではない What “accept interfaces, return structs” means in Go SOLID Go Design | Dave Cheney パッケージは具体的な型を返し利用者側でインターフェースを定義・利用する

47.

標準osパッケージの例 ● f, err := os.Open("example.txt") os.Open は具象型としての *os.File を返すが、利用者側が、それを、 io.Reader, io.Writer, io.Writer, io.Seeker として必要に応じて使え ば良い。

48.

具象型を返す方が利用者に優しい ● ● 具象側にメソッドを増やしても非互換変更にならない interface の場合はメソッド追加が非互換になる ○ ○ 利用者側が追加されたメソッドを実装しないといけないとコンパイルエラー ■ (テストにお毛売るinterface埋め込みというテクニックはあるが) 利用者側に負担を強いさせる

49.

最初から完璧なインターフェース設計は難 しい ● ● 「そのインターフェースが使われる前に定義するな。リアルな実例がない 限り、そのインターフェースが妥当かどうかを判断するのは難しい」 現実主義的な考え方 Do not define interfaces before they are used: without a realistic example of usage, it is too difficult to see whether an interface is even necessary, let alone what methods it ought to contain. -- https://go.dev/wiki/CodeReviewComments#interfaces

50.

まずは構造体から ● 内部のデータ構造を見せることに寛容 ○ ● 受け手に自由度を与える 理想的なインターフェース設計が最初から出来ると考えていない ○ だから「小さなinterface」とその組み合わせ(コンポジション)を好む

51.

"Accept Interfaces Return Concrete Types" に関する議論 ● 実はRob Pike氏はこの言葉自体は「余り好みではない」とも述べている ○ 適用範囲が不明確で、必ずしもそうとは限らないため I could not find the proverb “Accept Interfaces Return Struct in Go” here · Issue #37 · go-proverbs/go-proverbs.github.io

52.

開発者内での見解の相違や進展 「枯れた」インターフェースはインターフェース化してもよいのではない かという雰囲気にもなってきている ● ● ● proposal: os/v2: File should be an interface · Issue #14106 · golang/go proposal: os/v2: Stdin, Stdout and Stderr should be interfaces · Issue #13473 · golang/go logパッケージはinterfaceでも良かったかも知れないという議論も ○ 実際、log/slog はinterfaceになっている

53.

type assertionとinteroperability ● 利用者側が期待する振る舞い(interface)を定義して良い ○ ● 依存ライブラリ側で定義されている interface を利用する為にそのパッケージを importする必要は無い 同じ振る舞いのインターフェースは共存可能である

54.

ExitCoder インターフェースの例 github.com/Songmu/ecschedule のmainより。任意のcliライブラリが返すerror がExitCoderインターフェースを備える場合にその終了コードを返す。

55.

ポインタか値(生の構造体)か

56.

比較 ポインタ (*T) 値型 (T) コピーコスト ポインタのみ 構造体が大きいとコスト高 副作用 有 無 nil 有 無

57.

私見: ポインタに寄せるのが今は無難 ● 世の中の既存のコードベースはポインタの割合が多い ○ ○ ● 副作用の有無 ○ ● 一貫性が重要 但し、基本型 (int等) はポインタを避ける 構造体の内部状態を変化させたい場合もある nil ○ ○ nil pointer exceptionの可能性はある 「意味がない」値であることを明示的に示せるメリットも ■ 値だと逆にゼロ値チェックが難しい 今後関数型的な設計が主流になるにつれて、値型が主流になっていく可能性も

58.

イミュータビリティの実現方法

59.

イミュータビリティは必要か ● ● ● ● 単に副作用を避けたいだけならポインタを使わない手もある とは言え欲しい気持ちは分かる 現実的にはしばらくは入らなさそう ちゃんと作るのは難しい。以下のエントリーが面白い ○ ● 不変性とエイリアス参照と意味論と - Qiita 個人的には別に今のままでも大きな問題はないと思っている ○ ○ カスタムlinterで再代入検知などはできる Goに慣れすぎてしまった人間の考えかも知れないとは思う

60.

immutable type提案 ● オープンされたまま残ってはいる ○ ● 可能性はあるかも prkoposal: spec: immutable type qualifier · Issue #27975 · golang/go

61.

暫定的な実現方法 ● ● 変数自体の再代入禁止は諦めるとして、フィールドを変更できないように する方法はある → 全てメソッド呼び出しにする ○ ○ ● 例えばインターフェースにする 例えば構造体のフィールドをprivateにしてgetter経由でのみアクセス可能にする ■ コンストラクタでフィールドをセットする そこまでやる必要がある局面は少ないと思っているが

62.

コンストラクタ関数設計が必要になる ● privateフィールドに値をセットするためのコンストラクタ設計 ○ ○ ● h := &hoge.Hoge{} の様に直接組み立てできない h := hoge.New() 的なのを作る ■ その中で初期値をセットする "Functional Option Pattern" の発明 ○ ○ ○ コンストラクタの引数が長大になる事を防ぐ コンストラクタ用の構造体を別に定義する様なことを避ける ref. Functional options for friendly APIs | Dave Cheney

63.

やり過ぎ注意 ● ● ● 基本はコンストラクタを作らずともゼロ値でちゃんと動くようにする "Functional Option Pattern" も面白い発想だし、私も使うこともある が、濫用しない方が良い 余りprivateフィールドにこだわりすぎずに、内部構造を見せても良いか もしれない

64.

(余談) 名前付きでの構造体初期化を強制する方法 ● aws-sdk-go等がやっていたテクニック ○ ○ ● v2では削除されている 余り見なくなった 極力名前付き初期化をした方が良いとは思う ○ レビューやコーディング規約、linter等でコントロールするのが良いと言うのが私見

65.

(余談) シングルトン実現方法 ● プライベート関数をinterface内に定義することで外部からはそのinterfaceを満 たせなくする ○ ○ dummyメソッド方式はシングルトンのために使われていることは見たことはないが go/ast で同 一インターフェースを満たす別の型を定義する為に使われている 参考: ■ GoでSingletonぽいことを実現する、とある方法 | おそらくはそれさえも平凡な日々 ■ Sum/Union/Variant Type in Go and Static Check Tool of switch-case handling

66.

エラー処理

68.

標準error にスタックトレースが入っていない理由 ● 出力方法の仕様が定められていないことが大きそう ○ ● 出力方法が決まっていないのにコストがかかるスタックトレースを取得しても意 味がない ○ ○ ● ● ● Error Printing - Draft Design 逆にちゃんとしたエラー出力仕様がAcceptされれば進む可能性はある 優先度は今はあまり高くはなさそうだが ■ 意欲的に進める人が出てくれば別 この辺りの議論がGoはめちゃくちゃ慎重 現状利用者側としてはスタックトレースを取る3rdパーティライブラリを使うこ とになる 参考: Goのerrorがスタックトレースを含まない理由 - methaneのブログ

69.

コレクション操作

70.

コレクション操作 ● ダルイのは認める ○ ● MapやFilter的な処理を手軽にやりたいのは同意 ○ ● とは言え、詰め替えをするとメモリ割り当てに意識的にはなれる こういうMap的な処理は内部的に別のメモリ確保がされている事は意識した方が良い ■ 上手く再利用しているケースもあるが ジェネリクスやイテレーターが標準に入って手軽にやりやすくなってきた ○ それらを使ってコレクション操作処理を簡単にする標準ライブラリはまだ無い

71.

x/exp/xiter へ期待が高まっていたが ● 公式でのユーティリティ関数群の提供検討 ○ ● 結構議論が進んでいたが、一旦却下されて白紙に ○ ● xiter.Map, xiter.Filter などの関数を実装 proposal: x/exp/xiter: new package with iterator adapters · Issue #61898 · golang/go Goの慎重さが伺える出来事でもある

72.

github.com/basemachina/lo サードパーティライブラリ選定 ● 程良さそう ○ ○ ● GitHub - basemachina/lo: A Lodash-style Go library based on samber/lo GoのGenericsによるslice操作との付き合い方 好き嫌いあるけど、使って良いんじゃないの?と最近は個人的には思って います

73.

パッケージ名やパッケージ構成

74.

パッケージ名 ● ● ● 単数系の一語であることが望ましい 標準パッケージとかぶる場合などに複数形を使うことも 参考: Package names - The Go Programming Language

75.

プロジェクトの推奨パッケージ構成 色々議論があった(無駄に重厚なパッケージ構成をさもベストプラクティスの ように標榜する人が後を絶たなかった)が、去年Go公式見解が発表された。 ● ● ● Organizing a Go module - The Go Programming Language 11 tips for structuring your Go projects - Alex Edwards 少しずつ育てるGo言語のプロジェクト構成 基本的には単純なところから始めて育てていくことが推奨されている。自分で 考える必要がある。元々そういう思想だった。

76.

私見: 以下のような分類が良いのでは無いか ● 機能群としてのパッケージ群 ○ ○ ● レイヤーとしてのパッケージ群 ○ ● ex. internal/auth, internal/notification... 外部ライブラリのような汎用的なモノ ex. app/service, app/repository... データベースが関わる機能群をどう整理するかは悩ましい

77.

パッケージの依存関係 ● ● 「良く設計されたGoプログラムのインポートグラフは、階層が深すぎ ず、幅広く比較的平坦であるべき」 SOLID Go Design | Dave Cheney All things being equal the import graph of a well designed Go program should be a wide, and relatively flat, rather than tall and narrow. If you have a package whose functions cannot operate without enlisting the aid of another package, that is perhaps a sign that code is not well factored along package boundaries.

78.

Goのパッケージ ● 旧来のClassの役割は名前空間とインターフェース(オブジェクト)で蜜結 合していた ○ ○ ● そういう言語だと細かく分割する必要が出てくる そのメンタルモデルを引きずっている人が多いのでは Goは名前空間とインターフェースが直交している ○ そこに逆に不慣れな感じを受けるのかも

79.

レビュー編

80.

言語処理系やライブラリ更新 ● Goは特にランタイム自体のバージョンアップはほぼ安全なのですぐにや ると良い ○ ○ ● パフォーマンス的にもお得な場合も多い 逆にマイナーバージョンは1.5年しかサポートされない ■ セキュリティアップデートもされない ライブラリも非互換修正が入ることも少ない ○ メジャーバージョンが上がるときは大変なことも

81.

Webフレームワーク選定 ● ● 標準のnet/httpか、それをラップした物なら何でも良いと思う 標準のnet/httpがmiddleware的な構造も持っている ○ ● WSGI, Rack, PSGI的な物が標準であるのは嬉しい フレームワーク側に独自の作り込みが無い方が良い

82.

linterやformatter, language server ● staticcheckでのlintやgoimportsによるフォーマット ○ ● ● reviewdogを使ってCIに組み込むと良い gopls (公式language server) をエディタに組み込む ○ ● 基本機能に加えてmodernizer機能が新しい書き方を提案してくれるのが嬉しい 協力なツールチェインを公式が提供してくれるメリットは大きい ○ ● これは最低限やっておきたい エディタ拡張入れたら自動的に使ってくれる golangci-lint もメジャーだが把握しきれないルールセットをいきなり入 れない方が良い ○ まずはstaticcheckで十分

83.

オブザーバビリティ ● ● ● 標準の expvar パッケージでプログラム自体のメトリクスを取得できる goroutine数やメモリ使用量などが重要 各種observavilityベンダーのSDKなどはここから値を取っている ○ ● runtime パッケージが直接使われることもある log/slog により構造化ログが標準に入ったのは大きい ○ 今後の OTel との連携も期待

84.

tool管理 (go get -tool) ● Go 1.24 以降、 go get -tool が使えるようになって依存ツールのバー ジョンを go.mod に記述できるようになった How to manage tool dependencies in Go 1.24+ - Alex Edwards

85.

非同期処理の扱い

87.

sync.Mutex か channel か ● ● sync.Mutex|RMutex は内部的にchannelを使い排他制御を簡単にしてくれる 慣れないうちは sync パッケージを積極的に使うと良い ○ ● ● ● channel を使った方がよりすっきり書ける場合もあるが メッセージパッシングする場合には channel を使う 参考: Go Wiki: Use a sync.Mutex or a channel? - The Go Programming Language Don't communicate by sharing memory, share memory by communicating. ○ ○ メモリを持ち合うことでコミュニケーションするのではなく、メッセージングで情報を共有する https://go-proverbs.github.io/

88.

🚨「同時アクセス」に敏感になる ● goroutineを使っていると容易に起こる ○ ● シングルプロセス・シングルスレッドなアプリケーションとは異なる スレッドセーフかどうかの確認を怠らない ○ ○ 変数、mapやスライスへの追加、上書きや読み込み処理 競合するとパニックする

89.

sync.Mutex を使う 書き込みのためのロックだけであれば、sync.RMutex を使う必要はない。 関数の 冒頭にロック処理とdefer のアンロック処理を入れるだけなのでお手軽。

90.

発展編: channel を使う ● 変数を持ち合うのではなく、チャンネルを通して情報を受け渡す ○ don't communicate by sharing memory, share memory by communicating.

91.

channelを使った例

92.

初期化パターン ● sync.Once の活用

93.

if分岐による初期化チェックによるクリティカルセクションの発生

94.

改善案 ● sync.Map を使う ○ ○ ● sync.Map はスレッドセーフなマップ 操作時の競合も避けたい場合 sync.Once を使う ○ ○ sync.Once は一度だけ実行されることを保証する 実行中は他の同一 sync.Once をブロックする

95.

sync.Once を使った初期化処理例

96.

sync.Once ファミリー ● ● 遅延初期化パターンはGo 1.21で入った sync.OnceValue が便利 sync.OnceValues, sync.OnceFunc も1.21で入った

97.

sync.Once 再実行の裏技とテスタビリティ ● ● sync.Once は(当然)再実行できない テストを動かすときも1回しか実行されない ○ ● テスト内で再実行させたい場合がある 変数を上書きすれば再実行可能 ❗

98.

初期化処理失敗時の再実行 ● ● 乱暴気味なワークアラウンドではある 厳密に言うとレースコンディションが存在する ○ 濫用注意

99.

sync.WaitGroup や errgroup を用いた お手軽並行処理 ● errgroup.Group がよりお手軽 ○ ● ただし errgroup はfail fastになっていて、最初にエラーが起きた時点 で終了する ○ ○ ● context も渡せるし、SetLimit で同時制限数も設定できる contextを使っている場合、他のgoroutineへのcancelは行われる 後続のエラーは拾われない この挙動に困る場合は自分で組み立てる必要がある

100.

自分で組み立てる場合 以下の部材が有用。 ● ● ● sync.WaitGroup で並行処理 x/sync/semaphore.Weighted で同時実行数を制限 errors.Join を用いて複数エラーを1つのエラーに集約 ○ Go 1.20 から標準に入った

101.

自分で実装する上の参考 ● ● ● sync.WaitGroup は内部的にはchannelで実現されている errgroup は sync.WaitGroup をラップしている これらのソースを読むのはめっちゃ勉強になる

102.

エントリーポイント(main()) の 設計と初期化処理

103.

main() 関数 ● 「正しく」終了できるように作りたい ○ ○ context を伝播させる defer などの後片づけがちゃんと走るように

104.

main() は小さくする ● os.Exit が呼ばれたら「直ちに」終了する ○ ○ 必要な defer が呼ばれないという落とし穴がある log.Fatal は内部的に os.Exit(1) を呼ぶ ■ 呼ぶなら一番外側でのみ

105.

signal.NotifyContext シグナルハンドリングのお手軽実現 シグナルで正常終了したか異常終了したかは判別したい場合は一工夫必要

106.

明示的な初期化 ● import順に依存する処理を書かない ○ ● ● 何かの拍子で壊れる可能性も 「明示的」であることの重要性 init() の使いすぎも注意 ○ ○ ○ 同一パッケージ内に複数書けるが呼び出し順は言語として未定義 guide/style.md#avoid-init · uber-go/guide ■ Uber社のスタイルガイドに"Avoid init()"とある Understanding init in Go | DigitalOcean

107.

セキュリティ: SSRF対策 Server Side Request Forgery

108.

外部から受け取ったURLへのアクセス resp, err := http.Get(user.config.WebhookURL) ● ● 外部からのユーザー入力的な者を直接HTTPリクエストに使うのは怖い SSRF攻撃 ○ ○ URLに「プライベートURL/IPアドレス」を入れられてしまうと、内部情報を読み取られ てしまう可能性がある A10 サーバーサイドリクエストフォージェリ (SSRF) - OWASP Top 10:2021

109.

SSRF対策ライブラリの利用 ● ● クローラー的なヤツは対策ライブラリを被せるのが無難 github.com/hakobe/paranoidhttp 他にありそうだけど見つけられなかった これはMackerelの外形監視システムの内部で使われていたライブラリ ■ 今は違う物を使っているとのこと ○ ○ ● http.(*Client).Transport を差し替えられるようになっている Goの標準httpライブラリの面白くて有用なところ ○ ● 参考 ○ ○ Goによるプライベートネットワークへのアクセスを禁止するHTTPクライアントの実装 - はこべ にっき ♨ Goでプライベートネットワークへのアクセスを制限する - 詩と創作・思索のひろば

110.

パフォーマンスチューニングの 心構え

111.

細かいテクニックは余り気にしない 例えば以下のような物。知っておいて損はないが、そこまで厳密にならなくて も良い。 ● sliceの移し替え時に添え字を使うテクニック ○ ● ● やると良いし、そういうテクニックがあることは知っておく 正規表現の仕様は控え、使うときは極力初期化時にコンパイルしておく structのフィールド順によるメモリアラインメント最適化 ○ やらなくても良い

112.

極端に「遅くなる」ようなことをしない ● ● 無駄な重複処理を避け、計算量や効率的なデータ格納を意識するので十分 Goは「遅くなりづらい」言語である ○ ● ● ラフに書いてもそこまで遅くならない良さ 遅さが気になる時は観察や計測をしてボトルネックを潰していく 細かいところ含めてカリカリにチューニングにしたい(速くしたい)んだっ たらCやRustにすれば良い

113.

「推測するな計測せよ」はRob Pike氏の言葉 Rob Pike's 5 Rules of Programming Rule .1内の「ボトルネックを特定する前に、当て推量でスピードハックをするな」 と、Rule 2.内の「計測する前にチューニングをするな」を合わせて意訳がされてい る。 (ちなみに、計測する前に観察せよ ~ Observe. Don't measure for monitoring until you've observedというのが個人的に最近考えていること。) 後半の3項目では「アルゴリズムよりもデータ構造が重要。複雑なアルゴリズムはバ グりやすいので、シンプルなアルゴリズムとデータ構造を使うべき」という主張がさ れている。 → この辺りの思想がGoに反映されているように感じる。

114.

GoとAIの相性の良さ

115.

AIはGoを学習しやすい ● 全体的なコードの統一感 ○ ○ ● ● Goで良く使われる愚直なコード生成もAIにとって学習しやすい 言語機能も素朴なのでAIが出力するコードの揺らぎが少ない ○ ○ ● 言語リリースの2012年以降に書かれたコードしかほぼ世の中に存在しない ■ コードレビューやCI、フォーマッターが当たり前になった時代以降 ■ 学習ソースとなるOSSも多い 標準ライブラリとそのドキュメントの品質も統一感がある 人間がレビューしやすい Goの "Readability" が一役買っている Songmuは一時期趣味コードでRustを書いていたが、Goに戻ってきた ○ 慣れも大いにあるが、AIとの協業が捗る

116.

まとめ Happy Go Hacking!

117.

スポンサー募集 Goで色々OSSを開発・メンテナンスしています。$1のワンショットワンコイ ンでも嬉しいので、良ければOSS活動を支援してくれると嬉しいです。 ● GitHub Sponsor ○ ● https://github.com/sponsors/Songmu ghq handbook ○ ○ https://leanpub.com/ghq-handbook https://zenn.dev/songmu/books/ghq-handbook

118.

参考資料

119.

公式に近い資料 ● ● ● ● ● ● ● Simplicity is Complicated Less is exponentially more Effective Go - The Go Programming Language Package names - The Go Programming Language passing torches to Austin and Cherry [ On | No ] syntactic support for error handling - The Go Programming Language Goに入ってはGoに従え

121.

その他 ● ● ● ● ● ● ● ● 「Go言語らしさ」とは何か? Simplicityの哲学を理解し、Go Wayに沿った開 発を進めることの良さ|ハイクラス転職・求人情報サイト アンビ(AMBI) 正しさとGo - Qiita The Rise of “Worse is Better” 「悪い方が良い」原則と僕の体験談|Rui Ueyama 不変性とエイリアス参照と意味論と - Qiita Goのなぜ問答 Goでイミュータブルを実現させたい人向けの方法論 GoのGenericsによるslice操作との付き合い方