Lua Style Guide

このスタイルガイドは、Roblox内のLuaコードを、可能な限り同一のスタイルと規約で統一することを目的としています。

このガイドは、GoogleのC++スタイルガイド を元に設計されています。Luaは非常に異なる言語ですが、このガイドの原則は依然として有効です。

指針

  • スタイルガイドの目的は、議論を避けることにあります。

    • コードの整形方法に正解は一つではありませんが、一貫性が重要です。従って、このやや恣意的な基準を採用することで、コードを書く時間を増やし、フォーマットの詳細に関する議論の時間を削減します。

  • コードは書くためではなく、読むために最適化してください。

    • コードは一度だけ書かれます。レビュー担当者や、後からコードに触れる他の開発者、さらには数ヶ月後の自分自身がこれを読む必要があります。

    • 差分がどのように見えるかを考慮し、一行をまたぐ変更がないようにすると、レビューが容易になります。

  • 驚くような、または危険なLuaの機能(魔法的な機能)は避けてください。

    • 魔法のようなコードは便利ですが、問題が発生するとその理由や修正方法が不明瞭になります。

    • Metatables は、注意深く使用すべき強力な機能の一例です。

  • 適切な場合、慣習的なLuaの書き方に一貫性を持たせてください。

ファイル構造

ファイルは(存在する場合)次の要素で構成されるべきです(順番通り):

  1. このファイルが存在する理由についての任意のブロックコメント(オプション)

    • ファイル名、作者、日付は含めない(これらはバージョン管理システムで確認可能)。

  2. ファイルで使用されるサービス(GetService を使用)

  3. モジュールのインポート(require を使用)

  4. モジュールレベルの定数

  5. モジュールレベルの変数と関数

  6. モジュールが返すオブジェクト

  7. return

リクワイア(require)の使用

一般

  • すべての require 呼び出しはファイルの先頭で行い、依存関係を静的にしてください。

  • require 文はモジュール名のアルファベット順に並べ替えてください。

リクワイアの構造

可読性を維持するため、require 文はブロックに分けることができます。これらのブロックはプロジェクトの内部構造を模倣し、以下の要素(存在する場合)で順序立てて構成されます:

  1. 共通の祖先の定義

  2. インポートされたすべてのパッケージのブロック

  3. パッケージから派生した定義のブロック(サブフォルダごとに再帰可能)

  4. 同一プロジェクト内からインポートされたモジュールのブロック(サブフォルダごとに再帰可能)

もし require 文をブロックに分ける場合:

  • ブロックはまずグループ化に使用するサブフォルダ名、その後モジュール名のアルファベット順に並べ替えてください。

  • 複数の require 文が共通のパスを共有する場合は、別のブロックにまとめることを推奨します。

ライブラリの読み込み

ライブラリとは、外部利用者向けにAPIを定義するプロジェクトで、通常は他のモジュールを require するためのトップレベルのテーブルを提供します。ライブラリは内部モジュールから構成される構造化された公開APIを提供し、内部の詳細が変更されても安定したインターフェースを維持できます。これにより、コードの共有だけでなく、自身のコード整理にも利用できます。

  • ライブラリ内部では、公開モジュールおよび非公開モジュールを直接 require してください。例:

  • ライブラリを利用する際は、まずAPI定義を読み込み、その後公開モジュールのパスを require してください。例:

以下のようなプロジェクト構造の場合:

MyClass は次のインポートブロックを定義するべきです:

メタテーブルの使用

メタテーブルは Lua の非常に強力な機能であり、演算子のオーバーロード、プロトタイプベースの継承、オブジェクトのライフサイクル管理に用いられます。 Roblox では、以下のような場合にメタテーブルの使用を推奨します:

  • プロトタイプベースのクラスの実装

  • タイプミスの防止

プロトタイプベースのクラス

クラスの定義方法はいくつかありますが、以下の方法を推奨します。これは Luau の型システムを活用するためです。強い型付けにより、クラスの使用方法が明確になり、型チェッカーや IDE が不整合を検出して警告を表示できます。

まず、通常の空のテーブルを作成します:

次に、__index メンバーにクラス自身を設定します。これにより、インスタンスのメタテーブルとしてクラスのテーブルを利用できます。

厳密な型推論を支援するため、クラスの形状を記述してください。これにより、クラスメンバーを2度指定する冗長性が生じますが、不整合があれば警告されます。

次に、クラスのデフォルトコンストラクタを作成し、上記の型定義を返り値(self)に割り当てます。

インスタンスに作用するメソッドも定義できます。Luau の型解析機能導入前はコロン(:)を用いた定義が一般的でしたが、ここでは self の型を明示するためにドット(.)スタイルを使用します。これらのメソッドは、呼び出し時には通常通りコロンで呼び出すことができます。

これでクラスは使用する準備が整いました! インスタンスを生成して動作を確認してみましょう:

さらに、必要に応じて以下の追加が可能です:

  • デバッグを容易にするために __tostring メタメソッドを導入する

  • プライベートに近いメンバーを定義するため、接頭辞にアンダースコア2つを使用する

  • インスタンスの型をチェックするメソッドを追加する:

タイプミス防止

Lua では、存在しないキーにアクセスすると nil が返され、追跡が困難なエラーが発生する場合があります。 メタテーブルのもう一つの用途は、この問題を防ぐことです。列挙型のような性質を持つテーブルでは、以下のように __index メタメソッドでエラーを発生させることができます:

__index はキーが存在しない場合にのみ呼び出されるため、MyEnum.AMyEnum.B は通常通りの値を返しますが、MyEnum.FROB はエラーとなり、バグの特定が容易になります。

一般的な句読点

  • セミコロン ; は使用しないでください。一行に複数の文を書く必要はありません。

一般的な空白と整形

  • インデントにはタブを使用してください。

  • タブ幅を4と仮定し、行の長さは100桁以内に保ってください。

  • タブ文字は Luacheck で正確に解析されないため、StyLua の使用を推奨します。

  • コメントはタブ幅4として80桁以内に折り返してください。コードとは異なり、短い行の方がコメントの可読性が向上します。

  • 行末の余計な空白は残さないでください。(エディタの自動トリム機能を有効にしてください)

  • ファイルの最後には必ず改行を入れてください。

  • 垂直方向の整列(縦並びの整列)は避けてください。後の編集で乱れやすく、編集が困難になるためです。

良い例:

悪い例:

  • グループ間は単一の空行で区切ってください。ブロックの開始時に空行を入れるのは避けてください。余分な空行は全体の可読性を低下させます。

  • 1行には1つの文を記述し、関数本体は新しい行に配置してください。

良い例:

悪い例:

  • 複数の戻り値を返す関数では、関数が1行に収まっていない方がミスを見つけやすいです。

  • if 文も、たとえ本体が return のみの場合でも、改行することで可読性が向上します。

良い例:

悪い例:

このパターンは、入力や条件の検証の場合に特に有用で、複数行に分けることでログの追加や条件の拡張が容易になります。

  • 演算子の前後にはスペースを入れてください(ただし、優先順位が明確な場合を除きます)。

良い例:

悪い例:

  • テーブルや関数呼び出しのカンマの後にはスペースを入れてください。

良い例:

悪い例:

  • 大きなインラインテーブルや関数呼び出しの場合、ブレースや関数を新たな行に配置すると可読性が向上します。

良い例:

悪い例:

長い式における改行

  • 長い式は、論理的な単位ごとに改行してください。括弧内のサブ式も新しい行に分けると良いでしょう。

    • 改行する際は、行頭に演算子を配置して前行との連続性を示してください。

    • 必要であれば、一時変数を使用することを検討してください。

  • 長い if 文の条件も、各条件を改行して記述してください。

良い例:

悪い例:

  • if 式での else 分岐も必ず記述し、改行して構造を明確にしてください。

良い例:

悪い例:

  • 条件が長い場合は、一時変数を用いて可読性を高めてください。

if-then-else 式について

  • x and y or z のパターンよりも、if-then-else 式を用いる方が安全かつ高速で読みやすくなります。

良い例:

悪い例:

  • if 式には必ず else を付けてください。

  • 複数行に渡る場合、thenelse は新たな行に配置し、適切にインデントしてください。

良い例:

悪い例:

ブロック

  • ifwhilerepeat などの条件式では、括弧は不要です。

  • 変数のスコープを限定するため、do ... end ブロックを使用してください。

リテラル

  • 文字列リテラルにはダブルクォーツを使用してください。

    • シングルクォーツを使用すると、アポストロフィのエスケープが必要になります。

    • 空文字列はダブルクォーツの方が認識しやすいです。

良い例:

悪い例:

  • 両方のクォーツを含む場合は、外側にダブルクォーツを使用するのが望ましいです。

テーブル

  • 配列的なキーと連想配列的なキーが混在するテーブルは避けてください。混在すると、反復処理が困難になります。

  • 配列的なテーブルは ipairs、連想配列的なテーブルは pairs を用いて反復してください。

  • 複数行に渡るテーブルでは、末尾にカンマを追加すると、行の追加や並べ替えが容易になります。

良い例:

関数

  • 関数の引数は可能な限り少なく(1つまたは2つ)してください。

  • 関数呼び出し時は必ず括弧を使用してください。Luaでは括弧を省略できますが、可読性が低下します。

良い例:

悪い例:

  • グローバル関数の定義は避け、関数はローカルで定義してください。

良い例:

悪い例:

  • テーブル内で関数を定義する際は、.: の使い分けに注意してください。

良い例:

悪い例:

コメント

  • インラインコメントは一行で記述し、複数行の場合は各行にコメント記号を入れてください。

  • 関数やオブジェクトの意図を説明するためにブロックコメントを使用してください。

  • コメントは「なぜ」そのようにコードが書かれているかに焦点を当て、「何を」しているかの説明だけに終始しないようにしてください。

    • 良い例:

    • 悪い例:

  • セクション分けのためだけのコメントは避け、ファイル分割やモジュール化を検討してください。

命名規則

  • 単語は省略せず完全に記述してください。略語は可読性を低下させる原因となります。

  • クラスや列挙型のオブジェクトには PascalCase を使用してください。

  • Roblox の API では PascalCase、ローカル変数、メンバー、関数には camelCase を使用してください。

  • 定数には大文字のスネークケース(LOUD_SNAKE_CASE)を、プライベートメンバーには先頭にアンダースコアを付けてください(例: _camelCase)。

  • ファイル名は、そのモジュールがエクスポートするオブジェクト名と一致させてください。たとえば、単一の関数 doSomething をエクスポートするモジュールなら、ファイル名は doSomething.lua とします。

非同期処理と Yield

  • yield する(非同期の)関数はメインタスク上で直接呼び出さないでください。coroutine.wrapdelay を使用し、Promise や類似の非同期プリミティブを利用することを検討してください。

    • 長所:

      • Roblox の非同期モデルにより、複雑な関数をコルーチンやその他の非同期手法を理解せずに利用できます。

    • 短所:

      • 意図しない yield により、データ競合が発生する可能性があります。

エラーハンドリング

  • 失敗する可能性のある関数は、success, result を返すか、Result 型や非同期プリミティブを使用してください。

  • 関数は、正しくない使い方の場合にのみエラーを投げるようにしてください。

  • 長所:

    • 例外を利用すると、未処理のエラーが自動的に呼び出し元へ伝播します。

    • スタックトレースが自動付加されます。

  • 短所:

    • Lua は文字列しか例外として投げられないため、区別が難しいです。

    • 例外は関数の契約に明示されないため、呼び出し側でエラー処理を考慮する必要があります。

  • 例外を投げる関数を呼び出す際には、pcall を使用し、期待されるエラーをコメントで明記してください。

一般的な Roblox のベストプラクティス

  • すべてのサービスは、ファイルの先頭で game:GetService を用いて参照してください。

  • モジュールをインポートする際は、まず API 定義を読み込み、その後パブリックモジュールを参照してください。

最終更新