コーディングガイドライン
これらのガイドラインはKafka® コードベースに従事している人々の間での一貫性とベストプラクティスを促進することを意図しています。それらを無視するやむを得ない理由がない限りは守られるべきです。
基本的な事柄
- 暗号略語の回避。1文字の変数名は変数が少ないとても短いメソッドでは良いですが、そうでなければ変数名を参考になるものにしましょう。
- 明確なコードはコメントしやすいです。良い命名であればコメントが必要ないかも知れません。それができない場合は、コメントは必須だと思うべきです。読まれるように書きましょう。
- ログ、設定、公開されるAPIは"UI”です。感じがよく、一貫性があり、使い勝手の良いものにしましょう。
- 行の長さに上限はありません(もちろん80文字ではありません。もうパンチカードで動かしていません)が、適度なものにします。
- だらしなくしないでください。コメントアウトされたコードをチェックインしないでください: バージョン管理を使っています。履歴にそれが残っています。手伝えない場合はコード内にTODOあるいはFIXMEを残さないでください。コード内にprintln文を残さないでください。望むらくはこれはわかりきったことです。
- みんなに私たちのものを使って欲しいです。このことは明確な正しいドキュメントが必要なことを意味します。ユーザ ドキュメントはユニットテストあるいはパフォーマンスの結果のような、ユーザ向けの機能の部分を考慮すべきです。
- コードを複製しない (duh)。
- Kafkaはシステムソフトウェアであり、他の場所では適切ではないものがシステムソフトウェアでは適切なことがあります。Sockets, bytes, concurrency, and distribution are our core competency which means we will have a more "from scratch" implementation of some of these things then would be appropriate for software elsewhere in the stack. これは私たちが例外的にこれらのことに上手でなければならなかったからです。This does not excuse fiddly low-level code, but it does excuse spending a little extra time to make sure that our filesystem structures, networking code, threading model, are all done perfectly right for our application rather than just trying to glue together ill-fitting off-the-shelf pieces (well-fitting off-the-shelf pieces are great though).
Scala
ここで定められたスタイルガイドに従っています (ですが完全ではありません)。以下は注意する価値のある幾つかの項目です:- Scala はとても柔軟な言語です。使用制限魔法暗号のワンライナーは私たちに感銘を与えませが、読み易さは感銘を与えます。
- 可能であれば
val
を使ってください。 - 可能であればメンバ変数のためにprivateを使ってください。
- メソッドおよびメンバー変数名は、
aMethodName
のように最初に小文字を持つキャメルケースでなければなりません。 - 定数は
LIKE_THIS
ではなくLikeThis
のように最初に大文字を持つキャメルケースでなければなりません。 - 見つけやすいようにファイルごとに1つのトップレベルのクラスが望ましいです。
- 必要でない限りはセミコロンを使わないでください。
- ゲッターとセッターは避けてください - 代わりに明確な
val
あるいはvar
に固持します。(後で)myVar
という名前のvar
のために独自のセッター(あるいはゲッター)を必要とする場合、シャドーvar myVar_underlying
を追加し、セッター (def myVar_=
) とゲッター (def myVar = myVar_underlying
) を上書きします。 - scala API内では
null
よりOption
が望ましいです。 - 意味が完全には明確ではない場合には、文字列の値を渡す時に名前付きの引数を使います。例えば
Utils.delete(true)
の代わりにUtils.delete(recursive=true)
が望ましいです。 - インデントは2つの空白文字でタブではありません。正しいインデントの数について議論する人もいるかもしれませんが、scalaでは2が標準のようで、明らかに"正しい"方法はないため、一貫性がここでは最善です。
- メソッドに副作用が無い場合のみ、引数の無いメソッドでの任意の括弧を含みます。そうでなければそれらを省略します。例えば、
fileChannel.force()
とfileChannel.size
。これは副作用のためのメソッドを呼び出していることを強調することに役立ちます。それは返り値を取得するだけでなく、なんらかの状態を変更します。 - 意図した内容が何であるかを明確にするために、重要なAPI内ではタプルよりcaseクラスが望ましいです。
ログ
- ログは "UI" の3つのうちの1つ目で、慎重に扱われるべきです。重要な事が記録されゴミがそこに無い事を確実にするために変更をする場合は、ログにアクセスする時間を取ってください。
- ログの構文は、ソースコードに慣れている必要が無い人が読めるように適切な大文字を使用した完全な文章でなければなりません。デバッグの時には大幅に削減した小さなログ構文にするのも良いですが、チェック インする前にそれらをきれいにするか削除してください。つまり "INFO: entering SyncProducer send()" のようなログは適切ではありません。
- ログはクラス名あるいは内部的な値を言及するべきではありません。
- ログには6つのレベル
TRACE
,DEBUG
,INFO
,WARN
,ERROR
およびFATAL
があり、それらは以下のように使われるべきです。INFO
はソフトウェアが実行されるだろうとみなすべきレベルです。INFO メッセージは、悪いものでは無いが、それらが起きる時にユーザが明確に知りたいだろうものです。TRACE
とDEBUG
は両方とも、何かがおかしく、何が起きているかを見つけ出したい時にオンにするものです。DEBUG
はサーバのパフォーマンスに深刻な影響を与えるだろうため、細かくするべきではありません。TRACE
は何でもありです。DEBUG
とTRACE
の構文は、常に大きな文字列を貼り付けることを避けるために、if(logger.isDebugEnabled)
のチェックで囲まれるべきです。WARN
とERROR
は何か悪いことを示します。悪いこととは、完全には確信を持てない場合はWARN
を、確信を持っている場合はERROR
を使ってください。FATAL
はSystem.exit()
の呼び出しの直前にのみ使ってください。
監視
- 監視は"UI"の3つのうちの2つ目で、これも慎重に扱われるべきです。
- 監視のためにJMXを使います。
- どのような新しい機能も正しく動作しているかを知るために適切な監視が付属している必要があります。それはプロダクションを検証するのでユニットテストのように少なくとも重要です。
ユニットテスト
- 新しいパッチは機能が追加されたことを検証するためにユニットテストを付属していなければなりません。
- ユニットテストは素晴らしいコードであり、そのように扱われなければなりません。それらはコードの重複、神秘的なハッキング、あるいはそのようなものの全てを含むべきではありません。
kafka.utils.TestUtils
内のメソッドに注意してください。それらは以下の多くのことを理解しやすくします。- ユニットテストは可能な限り最小のコードをテストすべきです。1つのクラスあるいは孤立したクラスの小さなグループをテストことができる場合はサーバ全体から始めないでください。
- テストは外部のリソースに依存してはいけません。それらは自身のものでセットアップおよびティアダウンする必要があります。このことはもしzookeeperが必要であれば開始および停止される必要があることを意味します。すでにあることに依存することはできません。同様にデータが含まれるファイルが必要であれば、テストの開始時に書き、削除する必要があります(通過あるいは失敗)。
- ファイルシステムとネットワークは範疇なのでテストで使っても問題ありません。ですが、後で自分自身できれいにする必要があります。
TestUtils
内にこのためのヘルパーがあります。 - テストにおいてsleepあるいは他のタイミングの仮定を使わないでください。それは常に、常に、常に間違っており、遅延を起こす他のことをやっているテストサーバ上では断続的に失敗するでしょう。時間に依存しないような方法でテストを書いてください。本当に。一つ役に立つだろうこととして、コード内で直接システムクロック(つまり
System.currentTimeMillis
) を使わずに代わりにkafka.utils.Time
を使ってください。これは、システムクロックの代わりにこのモック クロックを注入した時に、時間の経過をプログラム的および決定論的に起こすことができるモック実装を持つ特性です。 - 衝突することなく、並行してテストを実行できるべきです。1つのCIサーバ上で複数のブランチをCIすることができることは役に立ちます。このことは、2つのインスタンスがお互いを踏みつけるため、テスト内でディレクトリあるいはポート、あるいはそのようなものをハードコードできないことを意味します。この場合もやはり、
TestUtils
はこのためのヘルパーを持ちます (例えば、TestUtils.choosePort
は開いているポートを見つけるでしょう)。
設定
- 設定は"UI"の3つのうちの最後です。
- 名前は設定を使用する人の観点から考えるべきですが、しばしばプログラマはコードを読む誰かに合った設定名を選択します。
- 設定内で最も意味のある値はしばしばプログラムで使う時に役に立ちません。例えば、全てのI/O帯域幅を使い果たすことを避けるためにI/Oを絞りたいとしましょう。実装で最も簡単なのはI/Oを設定したレートに落ちるまでプログラムをsleepさせる "sleep time" 設定をすることです。しかし、そうするためにこの設定パラメータを正しく使うことは難しいことに注意してください。ユーザはマシーン上でI/Oレートを見積もる必要があり、システム上で期待するI/Oレートを与えるための正しいsleep時間を計算するために一連の計算をする必要があります。単にユーザ設定を許可したいI/Oレート(例えば 5MB/sec)にし、それから適切なsleep時間と正確なI/Oレートを計算した方が、もっともっと良いでしょう。別の言い方をすると、設定は常に、あなたがしたい数量ではなく、ユーザが知っている数量の観点から行うべきです。
- 設定は何らかの理由で正面から解決できない問題に対する解決策です -- 最良の価値を単純に選ぶ方法がある場合は、代わりにそれを行います。
- 設定はインスタンスレベルのプロパティファイルからもたらされるべきです。追加の設定(環境変数、システムプロパティなど)は1つのマシーン上で複数のブローカーのインスタンスの実行を妨げるため、これらは追加されるべきではありません。
並行処理制御
- 同期をカプセル化します。つまり、ロックはクラス内のプライベートメンバー変数でなければならず、同期ストラテジの正確さを検証するために1つのクラスまたはメソッドだけが調べられなければなりません。
- スレッドセーフと考えられている時は
@threadsafe
として注釈を付け、このようなものを追跡するのに役に立たない時は@notthreadsafe
として注釈を付けます。 - スレッドとスレッドプールには幾らかの問題があります: スレッドに適切にデーモンフラグが設定されていますか?スレッドダンプでそれらの目的が区別できるような方法でスレッドに名前が付けられていますか?キューにされたタスクの数が上限にぶつかった時に何がおこるか (詰めますか?阻止しますか?)。
- 低レベルのwait-notify、独自のロック/同期、あるいは高レベルのscala固有のプリミティブのいずれかに対して java.util.concurrent パッケージを優先します。util.concurrent は良く考えられていて実際正しく動作します。様々な良く知られている弱点のため、スレッドとロックは並行処理制御のプリミティブにならないだろうという一般的な気持ちがあります。これは恐らく正しいでしょうが、現在では高パフォーマンスのソフトウェアのために使うのに現実に十分成熟しているという利点があります; それらのよく知られた欠点は同じくよく知られたベストプラクティスによって容易に対処されます。ですので、actor、ソフトウェアのトランザクションメモリ、タプルのスペース、あるいはDoug Leaに書かれておらず、少なくとも他の100万のプロダクションシステムで使われていないものを、避けてください。:-)
後方互換性
- 私たちのポリシーは、Kafkaのプロトコルとデータ形式はダウンタイム無しのアップグレードを可能にするために1つのリリースについて後方互換性をもつべきだということです。(あまり反論を無理に出すこと無しに)。このことは、サーバは同時に古いおよび新しいクライアントの両方からのリクエストをサポートできなければならないことを意味します。この互換性は1つのリリースについてのみ維持される必要があります (例えば、0.7 は 0.6 クライアントからのリクエストを受け付けなければならないが、これはいつまでも維持される必要は無い)。"古い" パスを扱っているコードは次のリリースで削除することができるか、いつでも全てのクライアントを最新にすることができます。バイナリ形式の代表的なアップグレードの手順は、(1) 新しいメッセージ形式を処理するためにコンシューマをアップグレード、(2) サーバをアップグレード、(3) クライアント、でしょう。
- このバイナリ互換性を必要とする3つのものがあります: リクエストオブジェクト、永続データ構造 (メッセージおよびメッセージセット)、および zookeeper構造とプロトコル。メッセージ バイナリ構造には増やすことができる"magic" バイトがあります。この数字はフォーマットが変更された時に増加させられなければならず、その数値は正しいロジックを適用し適切にデフォルトを与えるためにチェックすることができます。ネットワークリクエストは同じような目的を提供するリクエストidを持ち、リクエストオブジェクトへのどのような変更もリクエストidの変更を伴わなければなりません。ここでの変更は、新しいコードとの互換性のためにテストされたバイナリファイルとして、古い形式でのリクエストあるいはメッセージを保持する互換性テストを伴わなければなりません。
クライアントコード
サーバ側では大きな影響が無いクライアントコードで考慮される必要がある2,3のことがあります。
- クライアントに必要とされるライブラリは出来る限りいつでも避けられるべきです。クライアントは別の人のコード内で実行されます。同じライブラリだが異なり互換性が無いバージョンの可能性があります。これはクライアントを使うことができないことを意味するでしょう。このため、クライアントは厳密に必要ではないライブラリを使うべきではありません。
- 私たちは可能な限りAPIの互換性を維持しようとしなければなりませんが、プロジェクトのライフサイクルのこの時点では破損より物事を良くすることがより重要です。
ストリーム API
KafkaのストリームAPI (別名 Kafka Streams) は更に幾つかの追加のコードのガイドラインを使います。全ての貢献者は高品質で統一されたコードベースを持つためにこれらに従うべきです。幾つかのルールはPRレビューを単純化するのに役立ち、従って全ての貢献者の人生をより楽にするでしょう。
- 可能であれば
final
を使ってください。これは全てのクラス メンバー、ローカル変数、ループ変数 およびメソッドのパラメータに適用できます。 - モジュラー、そしてテスト可能なコードを書いてください。必要であればリファクタしてください!
- 大きなPRを避けてください (推奨はPRごとに 500 行未満です)。多くのJIRAは大きなコード変更を必要とします; 従って複数のPRに作業を分割し、作業を追跡できるようにJIRA上で一致するサブタスクを生成します。
- 全てのpublic APIはJavaDocsを持つべきです。
- JavaDocsがまだ最新であるか、更新されるべきであるかを検証します。
- JavaDocs: 文法的に正しい文章を書き句読点を適切に使います。
- 適切なマークアップを使ってください (例えば
{@code null}
)。 - Kafka webページ上のドキュメントを更新してください (例えば、
docs/
フォルダ内。ドキュメントの変更は付加的な作業(つまり、PRの付け加えではありません)ではなく、実際のPRの一部です (サブタスクにも成り得ます)。 - テスト:
- 自己説明的なメソッド名を使ってください (例えば
shouldNotAcceptNullAsTopicName()
)。 - 各テストは1つのケースのみをカバーすべきではありません。
- 可能な限りテストを短くしてください (つまり、パリッとしたコードを書いてください)。
- 全ての可能性のあるパラメータ値についてテストを書いてください。
- リフレクションを使わず、コードを書き直してください (リフレクションはそもそも悪い設計を暗示します)。
- 1行だけのテストについては、
@Test(expected = SomeException.class)
のようなアノテーションを使ってください。
- 自己説明的なメソッド名を使ってください (例えば
- コードの整形 (それによりGithubのdiffが読み易くなり、従ってコードレビューを単純化します):
- 120文字以上の行はあるべきではありません。
- メソッドを定義する時は、"1行あたりに1つのパラメータ" 形式を使ってください (2つのパラメータのみを持つメソッドについても)。
- メソッドの呼び出しが120文字以上の場合、1行あたりに1つのパラメータ形式に切り替えてください (単純に2行に分割するだけでは無く)。
- JavaDocsについては、新しい文章ごとに新しい行を始めてください。
- 不必要な再整形は避けてください。
- 再整形が必要であれば、minor PRをしてください (直接あるいはフォローアップのどちらか)。
- PRを開始/更新する前に
./gradlew clean checkstyleMain checkstyleTest
を実行してください。 - レビューを手伝ってください!尻込みする必要はありません; コードに貢献することができるなら、他の人の作業にコメントできるくらい良く知っています。