あなたが住んでいる場所を知っている暴力的なサイコパスが同伴するかのようにコードを書く

...プロジェクトが開発される予定がなく、ソースコードを共有しない場合でも、20年後にマニアが製品のマシンコードを調査および変更し、彼があなたを見つけたいと思うかもしれません。



開発のスピードIIIモダンパッチの必要性



一般的に、私はめったにコンピューターゲームをプレイしません。 数年連続でプレイしなかったことがあります。 しかし、時には小さなリバースエンジニアが私の中に目を覚まし、過去のお気に入りのおもちゃのマシンコードに興味を持ちました。 昨年、私はニードフォースピードIII:Hot Pursuit(1998)改良に取り組みました。 これはこのジャンルでの私のお気に入りのゲームですが、残念ながら今では、それがどれほどうんざりしているのかを知っています。 最も予想外の場所にある多数の小さなバグは、コードの質の悪さの直接的な結果です。



どれくらい悪いの?



実行可能ファイルには、ゲームの前の部分から継承されて使用されていない膨大な量のコードがあります。つまり、古いコードは開発者によって削除されていません。それらはもはや必要ありませんでした。 ゲームでは、固定サイズの静的配列をあらゆる場所で使用します。多くの場合、配列の制限を超えるチェックは行われず、予約されたメモリ量が十分でない場合にクラッシュします。 ゲームでは、多数のダーティハックが使用されます。 たとえば、ルートの反映されたバージョンのレース機能は、ロードされたときにルートのモデルを反映することではなく、左と右のボタンの逆、および開発者が適切なコードを追加することを忘れなかった多くの同様の置換により、レンダリング中に各フレームを反転することによって実現されます。 このため、車の碑文(たとえば、警官)が反映され、レース中、警官はトランシーバーの交渉で「右」と「左」を混同します。 開発プロセス中に定数値が変更されたときに、名前付き定数の代わりにコードでマジックナンバーを使用することに関連するエラーがありますが、古い値を使用して正しく動作しないコードがありました。 しかし、あるケースが非常に面白いことが判明したため、短いメモの一部としてそれを共有したいと考えました。



同じコードの4つのバリアントにいくつのエラーがありますか?



ゲームには1つの小さなバグがあり、ドロップダウンメニューのインデントに関連していました。 たとえば、「Hot Pursuit」モードが選択された場合、すべての上部のドロップダウンリストの左マージンは、明らかな必要なしに大幅に増加しました。



画像画像



これは、コードのエラーが原因であると想定しました。これにより、トレースのドロップダウンリストにパッセージアイコンを表示するためのスペースが追加されます。



画像画像



通常のドロップダウンリストには、モードによってはインデントの問題もありましたが、そのような明示的な形式ではありませんでした。 対応するコードと一緒に掘っていたので、この奇妙な動作を同時に修正することにしました。



ゲームコードには継承などの痕跡がないため、純粋なCで記述されている可能性が高いことに注意してください。ゲームは、メニュー項目とそのプロパティを外部テキストファイルから読み取るコードを実装しましたが、何か新しいプロパティを追加し、単に要素の名前に依存して追加のコードを作成しました。 たとえば、ドロップダウンリストの出力コード自体が現在の要素の名前と他のいくつかの変数をチェックし、これに応じて、追加のロジックを適用できます。



それでここで判明しました。 メニューは2種類のドロップダウンリスト(通常とトップ)を使用するため、それらを操作するためのコード全体が完全に複製されました。 さらに、ドロップダウンリストの左右の境界線を計算するときに、異なるコードフラグメントによって左インデントが選択されることが判明しました。 合計-理論的には、同じコードの4つのコピーが必要です。 どんなに!



オプション1(通常のドローダウンの左ボーダーのインデントを計算する場合):

画像

Cでは、次のようになりました。

dw_padding = (stricmp(str_element_name, "tracks") == 0 && dw_cfg_race_type == 3) ? 35 : 15;
      
      





オプション2(通常のドローダウンの右境界線のインデントを計算する場合):

画像

Cでは、次のようになりました。

 dw_padding = (stricmp(str_element_name, "tracks") == 0 || stricmp(str_element_name, "rectrk") == 0) ? 35 : 15;
      
      





オプション3および4(見出しの下のドロップダウンの左右の境界のインデントを計算する場合):

画像画像

Cでは、次のようになりました。

 dw_padding = (dw_cfg_race_type == 3) ? 35 : 15;
      
      





すべてのオプションを一緒に(わかりやすくするために):

 dw_padding = (stricmp(str_element_name, "tracks") == 0 && dw_cfg_race_type == 3) ? 35 : 15; dw_padding = (stricmp(str_element_name, "tracks") == 0 || stricmp(str_element_name, "rectrk") == 0) ? 35 : 15; dw_padding = (dw_cfg_race_type == 3) ? 35 : 15;
      
      





どのオプションが正しいですか? 答え:1つではありません! すべてのチェックをまとめた場合にのみ、正しいバージョンのコードを取得できます。これは次のようになります。

 dw_padding = (dw_cfg_race_type == 3 && (stricmp(str_element_name, "tracks") == 0 || stricmp(str_element_name, "rectrk") == 0)) ? 35 : 15;
      
      





合計で、同じコードの4つのバリアントがありますが、異なるバージョンで3つの異なるエラーが発生しました! ただのユニークなケースであり、コピー&ペーストが悪い考えである理由の素晴らしいデモンストレーションです。



これはどのように修正されましたか?



マシンコードの変更方法については、前書きました 。 問題を修正するために、1つの関数を作成しました。

画像

上記のコードスニペットはすべて、この関数の呼び出しに置き換えられています。 これでリスト内のインデントが正しく選択されました:)そして、これはパッチで行われた200以上の変更のうちの1つにすぎません。 記載された変更は実際には最小の変更の1つですが、エラー自体は興味深いものでした(対応するアンチパターンの害のデモンストレーションとして)。



結論は?



今考えて。 ソースコードを使用せずに、他の人のバグを修正せずにソースコードなしで修正しようとする詐欺師がいた場合、開発者または彼の親relativeを探したいという結果になるほど怒っている人もいるかもしれません。 必要ですか? 品質コードを書く:)



All Articles