Quake III Arenaは、1999年12月にリリースされたマルチプレイヤー中心の一人称シューティングゲームです。このゲームはid Softwareによって開発されました。Quake IIIは非常に人気のあるゲームであり、現在も多くのゲーマーに支持されています。
Quake3のオリジナルソースコードは こちら 。
Artem Kharytoniuk氏は独自の バージョンを作成することを決め、その目標はリポジトリで次のように説明されています:
このリポジトリには、コード構造を再編成し、互換性を修正し、最新のVisual Studio向けのビルド設定を行い、コア技術を更新しながらも、オリジナルのゲームプレイ、見た目、感触を維持する変更を加えた、オリジナルQ3コードベースの更新版が含まれています。
2つのプロジェクトのソースコードを比較し、Kenny Editionでどのような改善が行われたのかを見てみましょう。
全体像
オリジナルコードのいくつかの指標の概要は次のとおりです。

変更版の概要は次のとおりです。

それらの概要を比較して分かることは次のとおりです:
- 変更版の方がコード量が少ない。
- 型、メソッド、変数が少ない。
- ソリューション全体の評価と技術的負債は非常に似ている。
- メソッドの複雑さも似ている。
これらの指標をプロジェクトごとに確認してみましょう:
オリジナルコード。

変更コード。

さらに進んで、2つのバージョンを詳しく比較してみましょう。
モジュール性
モジュール性は、ソフトウェアが独立した部分で構成される度合いを高め、モジュール化されたコードの管理と保守を容易にするソフトウェア設計技法です。
名前空間、コンポーネント、クラスなどの論理的な仕組みが存在しないCのような手続き型言語では、ディレクトリとファイルを使用してモジュール性を実現できます。
変更版では、フォルダ構造がリファクタリングされています。以下は、エンジンに関連するフォルダがengineディレクトリに分離されている例です。

カプセル化
カプセル化は、実装内部の関数とデータを隠蔽することです。Cでは、staticキーワードを使用してカプセル化を行います。これらのエンティティは、ファイルスコープの関数と変数と呼ばれます。
オリジナルコードで、すべての静的関数を検索してみましょう:

多くの関数が、カプセル化を強制するためにstaticとして宣言されています。一方、staticとして宣言されていない関数も多数あります。
メトリクスビューを使うと、どれほど多くの関数がstaticかをよく把握できます。メトリクスビューでは、コードベースがツリーマップで表現されます。ツリーマッピングは、入れ子になった長方形を使ってツリー構造のデータを表示する方法です。CppDependのツリーマップで使用されるツリー構造は、通常のコード階層です:
- プロジェクトにはディレクトリが含まれる。
- ディレクトリにはファイルが含まれる。
- ファイルには構造体、関数、変数が含まれる。
ツリーマップビューは、CQLinqクエリの結果を表現する便利な方法を提供し、クエリ対象の型を視覚的に確認できます。

ご覧のとおり、rendererプロジェクトとcgameプロジェクトのほぼすべての関数がstaticとして宣言されていますが、他のプロジェクトではそうではなく、staticとして宣言されている関数はごく少数です。
変更版では、カプセル化がより効果的に強制されているでしょうか?
静的関数クエリのCQLinq結果は次のとおりです:

オリジナルコードよりも静的関数が少なくなっています。主な理由は、リファクタリング中に多くの関数が削除されたためであり、この新バージョンがオリジナルよりもカプセル化を強制しているわけではありません。
データモデルを保存するための構造体の使用
Cプログラミングでは、関数は変数を使用して処理を実行します。これらの変数には次のようなものがあります:
- 静的変数。
- グローバル変数。
- ローカル変数。
- 構造体の変数。
各プロジェクトにはデータモデルがあり、それが多くのソースファイルで使用される場合があります。グローバル変数を使用するのも1つの解決策ですが、良い方法ではありません。一般的には、構造体を使ってデータをグループ化することが推奨されます。

9,084個のフィールドのうち、constまたはstaticにするか、構造体へ埋め込むことでリファクタリングできる変数は166個だけです。
変更コードに対する同じクエリは次のとおりです。

新バージョンでは、リファクタリング候補となる新しいグローバル変数がいくつか追加されています。それらを見つけるために、staticではなく、constでもなく、ソースコード内で変更されないプリミティブなグローバル変数を検索できます:

コードに追加されたmulti_texture_add_frag_spv_sizeやmulti_texture_clipping_plane_vert_spv_sizeなどの一部は、constとして宣言できます。
2つのプロジェクトにおけるコードスメル
フィールドが多すぎる型
30個を超えるフィールドを持つ構造体を検索してみましょう:

変更版の結果は次のとおりです。

いくつかの大きな構造体がリファクタリングされています。たとえば、cgMedia_tのフィールド数は258から199へ減少しました。
大きすぎる関数
以下は、 LinuxコーディングスタイルのWebページにある、関数の長さに関するアドバイスです:
Functions should be short and sweet, and do just one thing. They should
fit on one or two screenfuls of text (the ISO/ANSI screen size is 80x24,
as we all know), and do one thing and do that well.
The maximum length of a function is inversely proportional to the
complexity and indentation level of that function. So, if you have a
conceptually simple function that is just one long (but simple)
case-statement, where you have to do lots of small things for a lot of
different cases, it's OK to have a longer function.オリジナルコードで、コード行数が50行を超える関数を検索してみましょう:

変更版での同じ結果は次のとおりです:

多くの大きな関数がなくなっています。完全に削除されたものもあれば、リファクタリングされたものもあります。
パラメータが多すぎる関数
NbParameters > 8となる関数は、呼び出しにくく、パフォーマンスを低下させる可能性があります。もう1つの選択肢は、引数渡しを処理する専用の構造体を提供することです。
オリジナルコードで該当する関数は次のとおりです:

変更版の結果は次のとおりです:

どちらのバージョンでも、8個を超えるパラメータを持つ関数はごく少数です。
ローカル変数が多すぎる関数
NbVariablesが8を超えるメソッドは、理解・保守が困難になる可能性があります。NbVariablesが15を超えるメソッドは非常に複雑であり、より小さなメソッドへ分割すべきです(ツールによって自動生成されている場合を除く)。
オリジナルコードの結果は次のとおりです:

変更版の結果は次のとおりです。

新しい変更版で追加された関数の一部には、15個を超える変数があります。vk_initialize()は、73個の変数を持つ追加関数です。
複雑すぎる関数
複雑な関数を検出する指標は多数存在します。NBLinesOfCode、パラメータ数、ローカル変数の数は基本的なものです。
複雑な関数を検出するための、他の興味深い指標もあります:
- 循環的複雑度は、手続き内で行われ得る決定の数に等しい、一般的な手続き型ソフトウェア指標です。
- Nesting Depthは、メソッド本体内の入れ子になったスコープの最大深度を表すメソッド指標です。
- Max Nested Loopは、関数内のループの最大入れ子レベルです。
これらの指標で許容される最大値は、主にチームの選択によって決まります。標準的な値はありません。
オリジナルコードで、リファクタリング候補となる関数を検索してみましょう:

変更版は次のとおりです:

多くの複雑な関数が削除またはリファクタリングされていることが分かります。
結論
オリジナルのQuake IIIソースコードは適切に実装されており、検出されるコードスメルはわずかです。しかし変更版では大規模なクリーンアップが行われ、多くの関数と変数が削除され、いくつかの構造体がリファクタリングされ、物理構造もわずかに変更されています。
