Upgrade to Pro — share decks privately, control downloads, hide ads and more …

その nil、本当にありえませんか ? Go に制約を書く静的解析

その nil、本当にありえませんか ? Go に制約を書く静的解析

munetoshi / @mntsh / 株式会社ナレッジワーク / ソフトウェアエンジニア

※2026/9/18開催「Go Night Talks2026 – After Conference」での登壇資料です
https://upsider.connpass.com/event/403598/

<セッション概要>
「この引数に nil を渡してよいか」「この戻り値に nil はありえないか」。Go はこれをコードとして明示する手段を持たず、答えは実装詳細と書いた人の記憶、コメントの中にしかありません。書かれていない制約は、破られても気づけません。

本発表では、制約をコメントとして書き、言語もコンパイラも変えずに CI で検証する仕組みをお話しします。制約は関数の引数や戻り値、構造体のフィールドに対して書き、違反があればリントエラーとして直すべき場所が示されます。

宣言の検証は、基本的に SSA (Static Single Assignment) でコードのフローを追うことで実施されます。それをパッケージを越えて伝播させるために go/analysis の Fact を使います。ただし全ファイルを検証するのではなく、対象は変更されたファイルとその呼び出し元だけに絞ります。

<プロフィール>
株式会社ナレッジワークのソフトウェアエンジニア。バックエンドが主務だが得意なのは Android App。コードの可読性と堅牢性にちょっとうるさい。著書に「読みやすいコードのガイドライン ―持続可能なソフトウェア開発のために」と「エンジニアチームの生産性の高め方 〜開発効率を向上させて、人を育てる仕組みを作る」がある。

More Decks by KNOWLEDGE WORK / 株式会社ナレッジワーク

Other Decks in Technology

Transcript

  1. to B nil, or not to B nil func GetBox()

    (*Box, error) { ... } b, err := GetBox() if err != nil { return } b.Field b は nil になりえる? 「多くの場合は」nil でないことを前提にしている 4
  2. nil かどうかを「知る」方法 - 実装を読む - GoDoc を読む: // ... *Box

    と error はどちらか一方だけが nil - ベストプラクティスから予想する : Go 標準ライブラリ 途中で仕様が変わったら...? 違反しているコードがあったら...? 5
  3. 例外の存在 net/http の Client.Do func (c *Client) Do(req *Request) (*Response,

    error) 部分的な失敗 (リダイレクト処理中の失敗) で 両方 non-nil 開発中のリポジトリで例外が存在しない保証は...? 6
  4. nil を検出する方法 Uber の NilAway https://github.com/uber-go/nilaway: nil の可能性を検査する linter -

    コードを辿って静的に検査 - 制約のアノテーションを書かない func GetBox() (*Box, error) { if ... { return nil, errors.New(...) } ... } b, err := GetBox() b.Field // REPORTED! 7
  5. 制約を書く vs 書かない 1/2 書かない方式 の利点: uber-go/nilaway - 既存プロジェクトに導入しやすい 書く方式

    の利点: Android Java + @NonNull/@Nullable - API の契約を明確にできる → 実装を読まずに理解できる - (理論上は) 解析が早い 8
  6. 制約を書く vs 書かない 2/2 AI を使うなら「制約を書く」を採用したい - AI にとっても「認知可能」なコードが重要 -

    AI でアノテーションの追加コストは低下 Go 向けの「制約を書いて静的に nil を検査する」linter は無さそう 9
  7. go-vow の使い方 1/2 1. 引数/戻り値/フィールド の 制約を誓約する 例: 戻り値 *Box

    と error は一方が排他的に 非 nil // vow:cond $1 == nil <=> $2 != nil func GetBox() (*Box, error) { ... } 12
  8. go-vow の使い方 2/2 2. 誓約を使う b1, err1 := GetBox() if

    err1 != nil { return } b1.Field // <= OK b2, err2 := GetBox() b2.Field // <= REPORTED! 13
  9. go-vow の評価戦略 誓約を利用して局所的に評価する 1. 引数 + 依存先の誓約を取得 2. 実装中に違反がないかチェック 3.

    実装が自身の誓約に沿っているかチェック // vow:cond $1 == nil <=> $2 != nil func Caller() (*Value, error) { b, err := GetBox() if err != nil { return nil, wrapErr(...) } return b.Field, nil CHECK!! } // vow:cond $1 == nil <=> $2 != nil func GetBox() (*Box, error) { ... } 14
  10. 評価戦略の応用 nil の検査以外にも応用している must-consume // vow:emit - sentinel error が無視されていないか

    - 閉じるべきリソースが放置されていないか readonly - // vow:readonly 構造体を構築後に変更していないか 15