ゴーストオブツシマとは
ゴーストオブツシマは、PS4・PS5で発売されている、かつての文永の役がモデルとなったゲームです。オープンワールドで景色が美しい上ストーリーも面白いです。ゼルダの伝説のようにどこに行ったら良いんだっけ?ということにはならず行き先の案内もあるのでゲームに慣れてなくても十分楽しめます。
ただし、戦がテーマなだけあってバトルが大変なのと、年齢制限が18歳以上のためかなりグロテスクな要素は多いです。
The Rules of Programmingとは
オライリー社から出版された本で、著者がゴーストオブツシマの開発メンバーとなっております。
「The Rules of Programming」を読むにあたりプレイをしてみた結果
ゲームは面白かったです。ただ書籍の内容には全く影響ありませんでした...。
でも面白かったので良いです!
The Rules of Programmingの21のルール
ルール1 できるだけ単純であるべきだが、単純化してはいけない
「単純っていいことじゃん」と思うことは間違いではありません。単純なコードは他の開発メンバーやこれから参画するメンバーの助けになります。
書籍の中で、「単純なコード」について3つの基準が設けられています。
1. 書かれているコードの量がより少ない
2. 持ち込んでいる概念の数がより少ない
3. 説明するのにかかる時間の量がより少ない
書籍の中で、「単純」と「単純化」はしっかり線引きされています。
「単純」は上記の3つの概念のことを示しますが、「単純化」は「単純の概念をクリアしていないのに単純かのように振舞ってしまうコード」のことを指します。
例えば短いコードだが複雑で可読性が著しく低い再帰関数のようなものは「単純化」の見本と言えるでしょう。
「コードが簡潔」が全てではなく、特に2, 3番の「理解・説明に時間を要しない」という項目が「単純なコード」には必要とされています。
ルール2 バグは伝染する
「バグが感染する」とは文字通りです。
とある箇所にバグがあった場合、依存するコードもバグを含んでいることがあります。
また、稀ですがバグがあるが良い振る舞いをしてしまっているコードがあるとしたら、一度バグが見つかったら関連箇所も影響を受けるでしょう。
バグに対しての向き合い方の中で、書籍では以下の要点を挙げています。
ユーザー(実装したコードの呼び出し側)に頼ってはならない
- 例えばこれは未来の自分になるかもしれないが、数週間〜数ヶ月が経過してしまうと時に別人のように全く当時のおコードの意図が読み取れなくなってしまう場合がある
- 第三者についても、「前任がそうしたのなら正しいだろう」というバイアスの元実装してしまう場合があるのでそうなるとバグがそのまま流れていってしまう
よりステートレスなコードを記載することでテストがしやすくなる
コードを健全に保つ
やはり「コードの品質を保つ」というワードには「自動テスト」が付随してきます。
テスト環境はCICDが主になりますがやはりセットアップに労力がかかります。
しかし、一度できてしまえば安全な状態を維持しやすくなります。
テストドリブンという手法もありますが、書籍の中では比較的推奨されています。
ルール3 優れた名前こそ最高のドキュメントである
命名は、「とにかく読みやすく」が最重要事項です。
書籍の中にも、「いちいち頭を使わせるな」という強めのセクションがあります。
「頭を使わせない」ためには、全メンバーが統一ルールのもと命名ができることが理想です。
命名ルールとして定番なものは、少し古いかもしれませんがハンガリアン記法などが挙げられますね。
なかなか「命名ルールを統一する」ということはしてないチームが多いかもしれませんが、全員が好き勝手な命名ルールでコーディングをしてしまうととても読みづらくなってしまいます。
ルール4 一般化には3つの例が必要
ユースケースが3つ未満の場合に一般化をしてしまうと、ときに偏った実装となってしまう。
後から3, 4, 5つ目のユースケースが登場したときに対応できない or 対応しても苦しい実装になってしまうケースがあるため一般化に踏み切るには3つ以上のユースケースを得てから取り組むべきだということです。
この例についてはその他の書籍でも「早すぎる抽象化は失敗する」というような表現で書かれていたりしています。
ルール5 最適化に関する教訓その1は、「最適化するな」
ルール4にも紐づきますが、早まった最適化は諸悪の根源であると主張されています。
最適化の教訓その1:最適化するな
「最適化」の概念がやや怪しいところではありますが、いわゆる我々が普段言う「共通化」をすれば全て解決するわけではないです。全てロジックがまとまっているものが美しいわけではなく、「単調であること」がより好まれます。
最適化の教訓その2:単純なコードを早くするのは簡単だ
教訓1の続きですが、「共通化された複雑なコード」より「共通化されていない単調なコード」の方が実行速度の向上をしやすいとのことです。
ゴーストオブツシマのコードは200万行あるようですが、意外にもこういった高速化の検討はすくかなったようです(20~30箇所)。「共通化」を考える前にまずは「単純化」そして「コードがやることを極力少なくする」と言ったことの方が重要視されるようです。
ルール6 コードレビューが役に立つ3つの理由
コードレビューの効果は様々ありますが、書籍では主に以下の3つがポイントとしています。
- バグを見つけることができる
- 誰もがコードの理解を深められる
- 人に進んで見せたくなるようなコードをみんなが書くようになる
1,2ももちろん好ましい効果ですが、3はルール1~5で言う「単純化」をする助けになるでしょう。
ルール7 失敗が起こる場合をなくす
少し難しい概念かもしれませんが、「誤りを検出できるといい、ただ誤りをコードとして表現することが不可能ならもっといい」と記載があります。
少し?となってしまいそうですが、続いて以下の表現があります
完全にフールプルーフなものを設計しようとする場合にやってしまいがちな間違いは、完全に愚かなものが行う創意工夫を過小評価することだ。
「ユーザーがこう言う振る舞いをするだろうからこう言うエラーハンドリングをしよう」ではなく、「基本的にこのシステムはこうしか使えない」「ただ振る舞いをミスしたらエラー(コンパイルエラー)が出る」と言った感じに、そもそも難しいハンドリングを考えるのではなく難しい使い方をできなくさせる(操作がほぼ1択に近い状態)と理想系だそうです。
ルール8 実行されていないコードは動作しない
実装をして機能変更をする中で使われなくなったコードを放置してしまった場合、いつかの未来でまたそのコードが発見されて新たに使用されたとします。
レビューの中でもなかなか差分以外で未使用になったコードの検知はしにくいです。
現在で言うと未使用コードに対してアラートを出してくれるlintもあるのでぜひ活用してみてください。
ではテストは??というと、全てのプロジェクトが満足のいくテストができているわけではありません。テストにも当然リソースが必要なので優先度によっては書ききれない場合もあります。
ルール9 集約可能なコードを書け
コードを書く、抽象化する際にはメンバーたちの「長期記憶」と「短期記憶」が連携する仕組みを用いる。
命名は共通な用語、もしくはプロジェクトとして一般化しているものを用いたり、チーム全体の規則に則ったものを用いることで新たな認知負荷の軽減になる。そう言った記憶は保存がきくし出し入れがしやすいです。
しかし、コードの変数名が特殊であったり、個性ある命名をした場合「これはなんだろう?」と短期記憶としてストックが必要です。また、その変数名を理解した場合その情報を抱えながら次のコードを読む必要があります。
なのでここで言う「集約可能」とは、新しい概念がプロジェクトに入ってきた場合でも学びやすく読みやすい状態であり、新しい考えの数々を単純な抽象概念群に集約しやすくなるような状態を目指すようです。
ルール10 複雑性を局所化せよ
いずれプロジェクトがスケールしていくと、どこかで複雑なものは必ず出てきます。
問題なのは、「インターフェイスと相互作用が単純ではない」ことにあります。
複雑性が局所化されつつある中でインターフェイスと相互作用が単純であれば複雑な状態は許容されるとのことです。
ルール11 2倍良くなるか?
主にリプレイスに関するセクションですが、皆様はリプレイス経験はありますか?
結果、どうでしょう?
書籍の中では、経験則として「2倍以上良くなるものはリプレイスの価値あり」としています。
ただの作る直しや多くのリファクタをするためであればインクリメンタルに解決することを推奨されていました。
「2倍」をなかなか数値化しにくいところではありますが、機能やアプリそのもののリプレイスをする際は「2倍良くなる?」を頭の片隅に入れておいてください。
ルール12 大きなチームには強い規則が必要
やはりチームが大きくなると個性が溢れてしまいます。
特にエンジニアのような技術職は人格も技術も様々なため、雑に仕様を投げただけであれば十人十色な結果が返ってきてしまうでしょう。
効果的なチームは「皆が同じように振る舞える」状態を作っていることです。
- あらゆるものの命名法
- コードのフォーマット形式
- 言語機能の利用法
- 一般化されたコードの書き方(Controller, Service, Repositoryなど)
- ファイルを作成する境界線や1ファイルの中での記述の順序
- 定数化する基準や手法
ルール13 雪崩を起こした小石を探せ
1つのバグが見つかり、修正した場合
- 実はまだ関連箇所があり連鎖的に発見される
- 修正がさらに副作用を生み出しバグが連鎖する
と言ったことがあり得ます。
では、どのように根源へ素早くアクセスできるでしょうか?
- 症状を原因に近づける
- 原因がソースコードの近くにあるか発生したのがつい最近である場合発見をしやすい - 因果関係の連鎖の長さを短くすることでデバッグのプロセスの短縮ができる
- 時間を遡って特定の地点へ簡単に飛べるようにすると辿りやすくなる
やはりデバッグのしやすさも「単純なコード」に返ってくるのですができる限り「純粋関数」(副作用がなく入力のみに依存する関数)で完結できると好ましいです。
純粋関数のデバッグやテスト・バグ探しはかなり容易です。
ルール14 コードには種類が4つある
4つの問題は以下のマトリクスです
「やさしい」問題 | 「難しい」問題 | |
---|---|---|
「単純」な解法 | 期待通り | 野心的 |
「複雑」な解法 | 実にひどい | 容認されている |
ただし、「やさしい」「難しい」は誰が基準なのでしょう?
エンジニアの世界では、同じプロジェクトでも歴15年のリーダーもいれば歴2年のジュニアもいます。
その2人にとって「やさしい」の基準は絶対に異なります。
プログラマーの種類 | 「やさしい」問題 | 「難しい」問題 |
---|---|---|
平凡 | 「複雑」な解法を書く | 「複雑」な解法を書く |
優秀 | 「単純」な解法を書く | 「複雑」な解法を書く |
偉大 | 「単純」な解法を書く | 「単純」な解法を書く |
ルール15 雑草は抜け
ここで言う雑草はNintendoの「あつまれどうぶつの森」の雑草に例えられています。
「抜いたとて大した影響もない。放置してもあまり影響はない。ただそう言うものは抜いておけ。」という考えです。
少しズレるかもしれませんが「ボーイスカウトルール」という方針もあります。
ボーイスカウトが自分のいた跡は綺麗にして帰るというストーリーから派生したルールですが、
そこを通ったついでに雑草を抜くといいでしょう。
「雑草」の概念については、「安全に修正できるか」という観点です。
typo修正やdocコメントの修正がいい例です。
ルール16 コードから先に進むのではなく、結果から後ろ向きに辿れ
実は意外と不具合は使い手側の近いところにありがちという方針から導き出されたルールです。
書籍の例では設定ファイルを解析するツールの例題でしたが、
前向きに進むやり方は設定ファイルの形式チェックから進めていく手法で、
後ろ向きというのはツールの利用者側からスタートする方法です。
こうすると結果単純で優れた解法になりやすいようです。
(今度試してみようと思います)
確かに我々はエラーがあった場合、DBの中身を見て、ロジックの破綻がないか徐々に見ていき、最後にユーザーの操作を見てしまう場合があるかもしれません。
そうではなく、ユーザーの操作から逆算していく手法をいつか試してみてください。
ルール17 大きな問題ほど解決しやすいこともある
スポーツや整体などの経験がある方はわかるかもしれませんが、大きなトラブルの要因は案外別の場所にあったりしませんか?腰が痛いのは実は首が原因だったり...ゴルフで調子が悪いのは右手のせいだと思っていたら足の使い方のせいだったり...
コーディングも同じ場合があるようです。
意外と大きいトラブルほど、別観点からアクセスしてみたらサラッと解決する場合があります。
ルール18 コードに自らの物語を語らせろ
「未来の自分は他人と思え」はいくつかの書籍でも言われていると思います。
コメントを読んで理解できればまだマシです。ただコメントを読んでも、「あれ、なんでだっけ?」となる方もいるはずです。
そのために、「良いコメント」を残す努力をすべきです。
書籍でいう「いいコメント」は以下の項目です
- コードについて明白じゃないものを読者に伝える
- コードを適切に区切る
- あくまでコードの複製ではなくコードの保管をすべき
- what, whyを明確にする
ルール19 作り直しは並列で行うこと
リプレイスに関連する部分かもしれないが、作り直しをする際に一気にAとBを差し替えるとやはりトラブルが発生する。可能なものであれば旧システムと新システムどちらにもB機能を入れておく。そしてfeatureフラグやbuildフラグで切り替える。
そうすると一括切り替えより遥かにバグやトラブルが軽減するというルールです。しかし、本当にバグがあった場合は2重の習性が必要になったりするので銀の弾丸とは言い切れないです。
ルール20 計算をやっておけ
ここでいう計算とは、「かかるコスト」と「便益性」の比較です。
CICDだけではなく、例えばtoBサービスで「この決済処理が手間だから自動化して欲しい」となった場合、
コスト
- 開発工数 * 時給
便益性
- システム化することにより軽減する時間 * 担当者の時給 + 軽減したことにより他の作業の生産量
これを天秤にかける計算となります。
やはりエンジニアの方が単価は高めである上、思った以上に工数がかかる場合は「コスパが悪い」という判断になってしまうでしょう。
今一度「自動化」システムを導入する際はこの辺の判断は冷静にしても良いでしょう。
時代の流れでCICDだ!自動化だ!となりがちなので脳死でやった結果意外と損でした。ということにならないよう計算はやっておけ。ということらしいです。
ルール21 とにかく釘を打たなきゃいけないこともある
最後はエンジニアとしてのルールです。
やはり実装をしていて、頭を抱えたりものすごい達成感を得る作業は少なかったりします。
「自動化」「AI」と表ではなっていても実態はエンジニアたちのものすごい地道な人力で成り立っている場合もあります。
解析ツールも進化しているかもしれませんが、地道なバグ解析も時には必要です。
面倒な表に書き込む作業があればすぐ自動化したくなりますがルール20にもあるとおり意外とコストの方が高くなってしまうこともあります。
木に100本の釘があった場合、見て見ぬ振りをするでも誰かがハンマーで打ってくれたり釘打ち機が発売されることを待つのではなく、自分でハンマーを取って釘を打った方がいい場面もあります。
「とにかく取り組む」ということも時には必要です。
以上です!
全体をかいつまんで書いてしまいました。
中には今必要としないルールもあるかもしれませんが、ぜひ教訓として取り入れてみてください。