mcpField
Z
zig-tiger-style

zig-tiger-style

github.com/zigcc開発5258 回インストール2 週間前に更新
リポジトリ

zig-tiger-style について

TigerBeetle の TIGER_STYLE.md を基にまとめた Zig コーディングガイドラインスキルです。安全性と性能を重視する Zig 開発者向けに、構成、命名、書式、レビューの指針を示します。

主な機能

  1. 01明示的な制御フローとアサーション
  2. 02上限付きループと完全なエラー処理
  3. 03メモリと性能設計の指針
  4. 04命名、コメント、ファイル構成の規則
  5. 05フォーマットとコミット前チェックリスト

活用シーン

  1. 01Zig コードの安全性の確認
  2. 02Zig プロジェクトのチーム規約策定
  3. 03コミット前のコード確認

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/zigcc/skills --skill zig-tiger-style

インストール後、Claude Code で/zig-tiger-styleを入力して読み込まれたか確認します。

詳細

TigerStyle:Zig コーディングガイドライン

TigerBeetle の TIGER_STYLE.md を要約したものです。 設計目標の優先順位:安全性 > パフォーマンス > 開発者エクスペリエンス。


1. 安全性

制御フロー
  • 単純で明示的な制御フローのみを使用します。上限が証明できる場合を除き、再帰は使いません。

  • 複合条件は入れ子の if/else 分岐に分割し、肯定側と否定側の両方を処理するか、アサートします。

  • 不変条件は肯定形で記述します。

    // preferred
    if (index < length) { ... } else { ... }
    
    // avoid
    if (index >= length) { ... }
    
  • if の各分岐で、対応する else も処理する必要があるかを検討します。

アサーション

アサーションはプログラマーのミスを検出するためのもので、予期される実行時エラーを検出するものではありません。破損した状態に対する唯一の正しい対応は、クラッシュさせることです。アサーションによって、壊滅的な正確性のバグを活性のバグへと格下げできます。

  • 確認していないデータを関数が盲目的に処理してはいけません。関数の入口で引数をアサートします。

  • ペアアサーション:適用したいプロパティごとに、異なるコードパスを少なくとも2つ選び、そこでアサーションを追加します(例:ディスクへの書き込み直前と、読み戻し直後)。

  • 複合アサーションは分割します。

    // preferred
    assert(a);
    assert(b);
    
    // avoid
    assert(a and b);
    
  • 含意をアサートするには、単一行の if を使います:if (a) assert(b);

  • コンパイル時定数間の関係をアサートし、プログラムの実行前に設計の整合性を検証します。

    comptime assert(@sizeOf(Header) == 128);
    comptime assert(config.pipeline_max <= config.batch_max);
    
  • 肯定側(真であると期待すること)と否定側(偽であると期待すること)の両方をアサートします。有効と無効の境界にはバグが潜んでいます。

メモリ
  • 大きな構造体はアウトポインター経由でその場で初期化し、中間コピーをなくしてポインターの安定性を保証します。

    // preferred
    fn init(target: *LargeStruct) !void {
        target.* = .{ ... };
    }
    
    // avoid
    fn init() !LargeStruct {
        return LargeStruct{ ... };
    }
    
変数のスコープ
  • 誤用の可能性を減らすため、変数は可能な限り狭いスコープで宣言します。
  • 変数は使用箇所の近くで宣言し、必要になる前に導入しないでください。これにより、TOCTOU の遠縁にあたる POCPOU バグを防げます。
ループとキュー
  • 無限ループやテールレイテンシーの急増を防ぐため、すべてのループとキューには固定の上限を設けます。フェイルファストの原則に従います。
  • イベントループなど、実際に終了しないループは、そのように明示的にアサートします。
エラー処理
  • すべてのエラーを処理する必要があります。 本番環境で発生する壊滅的な障害の多くは、致命的ではないエラーの処理を誤ることが原因です。
  • _ を使ってエラーの戻り値を決して破棄しないでください。
その他
  • サイズが明示された整数型(u32、i64 など)を使用し、可能な限りアーキテクチャーに依存する usize を避けます。
  • コンパイラーの最も厳格な警告設定を有効にして遵守し、警告は一切許容しません。
  • 外部イベントにインラインで直接反応しないでください。プログラム自身のペースで処理することで、バッチ処理が可能になり、制御フローの所有権を維持できます。
  • 関数はできるだけ小さく保ちます。 分割する際は、意味的に適切な分割点を選びます。
    • すべての if/switch を「親」関数に集約し、純粋なロジックをヘルパーに切り出します。
    • すべての可変状態は親に持たせます。ヘルパーは何を変更するかを計算しますが、変更を適用しません。
    • 目安:「if を上へ、for を下へ」。

2. パフォーマンス

  • パフォーマンスの問題は設計段階で解決します。最も大きな改善(1000倍)は、事後のプロファイリングではなく、アーキテクチャから生まれます。

  • 4つのリソース(ネットワーク、ディスク、メモリ、CPU)と、それぞれの2つの特性(帯域幅、レイテンシ)について、概算のスケッチを作成します。

  • アクセス頻度を考慮し、遅いリソースから最適化します。ネットワーク → ディスク → メモリ → CPU の順です。

  • 主な手段はバッチ処理です。ネットワーク、ディスク、メモリ、CPU のコストを償却できます。

  • コントロールプレーンとデータプレーンを区別します。バッチ処理により、両者を安全かつ高速に共存させられます。

  • ホットパスのループを、プリミティブ型の引数を取るスタンドアロン関数(self なし)に切り出します。これにより、コンパイラーはフィールドをレジスターにキャッシュでき、人間も重複した処理を見つけやすくなります。

    // hot loop extracted, no self
    fn process_batch(items: []const Item, result: []Output) void { ... }
    
  • 明示的に記述します。コンパイラーが適切に処理してくれることを前提にしないでください。

  • ライブラリー呼び出しでは、常にオプションを明示的に渡します。デフォルト値に依存しないでください。

    // preferred
    @prefetch(a, .{ .cache = .data, .rw = .read, .locality = 3 });
    
    // avoid
    @prefetch(a, .{});
    

3. 命名

  • 一般に、関数はcamelCase、型はPascalCase、変数はlowercase_with_underscoresにします。ただし、型を返す関数は例外で、PascalCaseにします。

    pub fn ArrayList(comptime T: type) type {
      return ArrayListAligned(T, null);
    }
    
  • 通常、ファイル名はlowercase_with_underscoreにします。ただし、型を直接公開するファイルはPascalCaseにします。

  • 変数名を省略しないでください(ソートや行列で使うプリミティブ型の整数ループインデックスを除きます)。

  • 頭字語はすべて大文字にします。VsrStateではなくVSRStateとします。

  • 単位や修飾語を名前に付けます。重要度の高い順に並べ、最も重要な単語を先頭にします。

    latency_ms_max    // not max_latency_ms
    latency_ms_min    // aligns nicely with the above
    message_size_max
    
  • 関連する名前は、同じ文字数にして視覚的に揃えます。

    source         // same length as target
    target
    source_offset
    target_offset
    
  • ヘルパー関数やコールバック関数の名前には、呼び出し元の名前を接頭辞として付けます。 read_sector() → read_sector_callback()

  • コールバックはパラメーターリストの最後に置きます(呼び出し順と一致させます)。

  • 名前に意味を持たせます。gpa: Allocatorやarena: Allocatorは、allocator: Allocatorよりもはるかに情報量があります。

  • 同じ型の引数を2つ以上取る関数では、引数の取り違えを防ぐため、名前付きのoptions: structパラメーターを使用します。

構造体とファイルのレイアウト
// Struct order: fields → type definitions → methods
time: Time,
process_id: ProcessID,

const ProcessID = struct { cluster: u128, replica: u8 };
const Tracer = @This();

pub fn init(gpa: std.mem.Allocator, time: Time) !Tracer { ... }
  • main関数はファイルの先頭に置きます。読み手が最も重要な内容を最初に確認できます。
  • 複雑なネスト型は、トップレベルの構造体に昇格させます。

4. コメント

  • コメントは完全な文にします。//の後にスペースを入れ、大文字で始め、ピリオドで終えます(何かを導入する場合はコロンで終えます)。行末のインラインコメントは、句読点のない語句でもかまいません。
  • **理由を必ず説明します。**コードは何をどのように行うかを示し、コメントは判断の根拠を説明します。
  • テストの先頭に、目的と手法を説明する記述を追加します。
  • 場合によっては、コメントの代わりに、明らかに真であるアサーションを使って重要かつ意外な不変条件を文書化します。アサーションのほうが、より強い説明になります。

5. 書式設定

  • 必ずzig fmtを実行します。

  • インデントには4つのスペースを使います(離れた位置から見たとき、2つよりも視認性が高いためです)。

  • 1行あたり100カラムを上限とし、例外は認めません。末尾にカンマを付けて、折り返しはzig fmtに任せます。

  • 行全体が1行に収まる場合を除き、if文には必ず波括弧を付けます。

    // single-line ok without braces
    if (ok) return;
    
    // multi-line always needs braces
    if (condition) {
        do_something();
    }
    
除算 — 丸めの意図を明示する
@divExact(a, b)   // asserts no remainder
@divFloor(a, b)   // rounds toward negative infinity
div_ceil(a, b)    // rounds toward positive infinity

6. 1つずれるエラー

index(0始まり)、count(1始まり)、size(=count × 単位サイズ)は、変換規則が明確に定められた別個の型です。

  • index → count:1を加算
  • count → size:単位サイズを乗算
  • 変数名に単位や修飾語を含め(「命名」を参照)、これらの変換が見てわかるようにします。

7. 依存関係とツール

  • 依存関係ゼロの方針:Zigツールチェーン以外の外部依存関係を持ちません。
  • スクリプトは*.shではなくscripts/*.zigとして記述します。クロスプラットフォームで、型安全性が高く、信頼性も向上します。
  • チームの成長に伴う複雑さを抑えるため、ツールにはZigを標準採用します。

コミット前のチェックリスト

提出する前に、以下を確認してください。

  • すべての行が 100 列以内であり、zig fmt を実行済みである
  • すべてのエラーが処理されている(_ による破棄がない)
  • 変数名に単位や修飾情報が含まれ、省略形が使われていない
  • 複合条件が入れ子の if/else に分割されている
  • すべてのループに明示的な上限が設定されている
  • コメントで単に「何を」ではなく「なぜ」を説明している
  • コンパイル時定数間の関係が comptime assert で検証されている

コメント