The Rules of Programming読書メモ
モチベーション
関わっているコードベースがだんだんと複雑かつ大きくなってきた。
よりシンプルに良いコードを書くことを自分ができるようになりたいと感じたため。
リーダブルコードはすでに読んでいるため更に踏み込んだ良いコードを書くための当たり前をインプットしたいと考えたため。
方針
気になった箇所、自分はこうしてきたが違った点、考察などを列挙する。
TypeScriptがメインで使っているのでTypeScriptならどう書くかなどを書く。
ルールにまつわる物語
新人プログラマーたちが書いたコードがもっとでかい問題を解こうとしていること。
そういうでかい問題は、単純で具体的な問題を、小さな部分問題として内包する。
これやってしまっているなぁと反省した。具体的な問題には愚直に具体的で単純な解決策で挑むのが一番で、自分からわざわざ大きな問題を相手にすることはない。
困難は分割せよとはよく言ったもので、スマートで一般的なコードが書けるほうがいいみたいな思い込みをまずは捨てて問題と向き合わないといけないなと背筋が伸びた。
ルールにが功を奏した理由は、「ルールを覚えるのが簡単」「ルールが適用される状況を簡単に認識できる」という2点だ。
これはプログラミングに関わらず一般的にも同じことが言えるなと思った。
チームでの決め事やプロジェクト運営をしていく中でルールがたくさんできてしまうことは往々にしてあるが、わかりにくいルールや適用される状況が見極めずらいルールは実質機能していないのと大差ない。
なるべくコスト低く(認知付加も含めて)運用できるルールというのが必要なんだな。
できるだけ単純であるべきたが、単純化してはいけない
最も優れたコードは単純なコードのことだ
すぐになんとか汎用性や一般化を考えてしまうのは小手先なのかもなと思った。
一方でこの言説にはやや疑問がある。単純なコードというのは一体どんなもんなんやということ。
書き手のレベルが上がるとこれは単純というのの基準も上がって来るのでは??と言う疑問はある。
単純さの計測
疑問の回答が出ていた。
いくつか単純さを計測可能な指標が上げられている。
- 書かれているコードの量
- 持ち込まれている概念の数
- 説明するのにかかる時間の量
とは言え完璧な単純さを計測する指標はなく、ある指標では複雑とされていることが別の指標では単純とされるときもある。
なのでどの指標で単純なのかを選ぶ必要がある。
【持論】
コードレビューを依頼するときにこの視点は大切かもなと思った。
レビュイーとレビュワーが違う指標を見ているとしたら下手をすると殴り合いになってしまう。(そういう経験がある)
なのでプルリクエストにはどういう指標を選んでいて、指標に沿っているのかを見てほしいと書き残すことが良さそうだ。
また、レビュワー側も違う指標で見たときにここはこう書いてもいいかもねと言うのは【IMO】みたいなプレフィックスをつけることで下手な衝突を回避できると思った。
ちなみに私はIMOがついたものはすべて修正されなくていいと思っているのでIMOしかコメントツケてないプルリクエストはApproveしている。(好みはある)
解法よりも問題を単純化したほうがいい場合もある
問題に単純な解法が存在しないんだったら、複雑な解法を受け入れる前に問題に対し、問を投げかけて検討してみるべきだ
大事なこと何だけど意外と納期やスケジュールに押されていたり、関係者が多いときに面倒がってしまうことがあるなと思って反省。
PMがいて、フロントエンドエンジニアがいて、バックエンドエンジニアがいてみたいな状況のときにフロントエンドエンジニア的にはこれは複雑な問題がきたなってときにPMとそもそもこの問題はもっと単純にできるのではないか? そもそもで課題は何なのか、制約はなにか?みたいなコミュニケーションが取れないと結構きついかもなと思った。
お客さんがそう言っているからを盾にされてしまうともうそれ以上なにも言いたくないなーと思ってしまうときもありそうだが、粘り強く問題の単純化や問題の分割をしないといけないんだろうな。
本音をいうと問題の分割はPMがやってくれよと思うときもある。
そのへんはプロダクトマネージャーのしごとで書かれているので読むといいかもしれない。
筋書きを見失うな
単純なコードは実行される順番を読み下して理解できるものとある。
認知付加が少ないコードを書くと言い換えられるかもしれない。プログラマー脳に書いてあった内容を思い出してみる必要がありそうだと思った。
すべてを統べる、一つのルール
複雑性こそ究極の敵
これは間違いなくそうだ。複雑なことを常に避けようと努力することを心がけたい。
その過程で単純な解を良しとしたり、解法ではなく問題の方を単純にできないか画策したり、筋書きを見失わないようにするのだ。
結局のところ単純化と言うのは複雑性を見ないふりをしてしまうということなのかもしれない。
おきている事実や解決の方法で思考として楽なものに飛びついてしまい、よく見ないとわからない複雑性を置き去りにしてはいけないという教訓なのではないかと考えた。
ルール2 バグは伝染する
バグを見つけるのが後になればなるほど直すのがうざくなる
これは間違いない。一発目から設計が良いコード、見通しの良いコード、的確な命名をしたほうがいい。
もっと言えばコードレビューについても同じだ。
出したプルリクエストは放置されずにすぐに見てくれたほうが修正の心理的なコストは低い。
コードレビューには多くのがここでは2つの側面に注目したい。
- 自分自身の成長の機会
- バグを未然に防ぐ機会
これは正論だと思うが、できるなら修正の箇所なしでApproveされてくれるに越したことはないのではないかと思う。
とすると、そもそも論で後になればなるほど直すのが面倒でうざくなるのだと思う。
コードが動作する、あるいは動作しているように思い込む、自然な習性がある。
耳が痛い。まさに自分だと思った。
動いている理由やうまく動いている理由を知ろうとすることはあまりないし、そもそも興味を持っていないかもと思った。
バグがバグを呼ぶというのは間違い無い事実だと思う。
なぜこれが正常に動いているとされているのか、自分のコードをが間違いを犯していないだろうかともっと注意深くなる必要がありそうだ。
だが、言うのは簡単なんだけどこれを実行するのが難しい。それを実践する方法を教えてくれ。
ユーザーを当てにしてちゃいけない
ユーザーはたとえ機能にバグが含まれていたとしてもそれを正常な挙動だと受け取る。
なのでユーザーからのバグ報告は防衛ラインにならない。
バグを防ぐ施策としてテストがあげられるが、テストのコストは依然として高い。
継続した自動テストの導入をすることは望ましい結果を得るための方法の一つとしてありなんだろうが、それを実践できるのかは別問題だと思う。
プロダクトマネージャーのしごとの中で出てくる教訓に
『ベストプラクティスはよくできたフィクションである』というのが登場する。これをまさに思い出していた。
継続的自動テストはベストプラクティスだが、そのプロセスに潜むコストを無視して取り入れるのはフィクションに踊らされていると言っていいのかもしれない。
継続的な自動テストは、個人的なプラクティスの形で取りれられるようなものじゃない
やはりチームの課題として認識され、継続のための施策を作らないといけない。そして継続した自動テストを実行するためのチームがいてもいいくらいなのではないかと思う。
自動テストは扱いにくい場合がある
成功度合いの計測が難しいために、テストするのが性質上難しいコードがある
自分の担当しているプロダクトでもこういうのが結構あるのでテストを書くとき悩ましい。
特にリグレッションテストやE2Eでテストは通っているんだけどQAしたらだめでしたみたいなのはよくある。
こういう問題とどう向き合うのか。
ステートレスなコードはテストしやすい
これはまあそうだよねという感じがする。
そもそもテストを書き始めるときってステートレスなところから書く気がするがそんなこともない?
このセクションではあんまり新しい発見はなかった。概ね自分の考えてることはズレていないんだなという確認ができた。
呼び出し側を信用するな
これは自分が気をつけている(昨日よりも前の自分をあんまり信用していない)ので自分の考えは間違っていないのだなと思った。
フロントエンドの開発をしているとこのHooksの詳細が知らなくて呼び出されたときどうすんねん的なことを考えることが多々ある。
ex) ユーザーの投稿を削除するHooksがある。このHooksは管理者ロールの管理画面のユーザーが管理画面のみで使用できる。このHooksではRoleを常に検証するべきか?
個人的には仕様を知らない人が書いたときに管理者のみが削除を行えるという仕様を満たせることが大事なのではないかと思っているので、Roleを検証するがやり過ぎと言われることもしばしば。。。
まあそのへんはレビューする側の好みもあるしバトってもいみがないポイントだと思った。
コードを健全に保つ
コードをテストしやすくするテクニックはたいてい、コードを書きやすくする
なるほど。諦めずにテストを書き続けたいもんですな。
優れた命名こそ最高のドキュメントである
ここで書かれている内容はリーダブルコードやプログラマー脳に出てくる内容とほぼ変わらない。
ユニークな命名をすることで認知負荷を下げることができる。また、ドキュメントを読まなくてもその実態を推測し使うことができる。
自己文書化されたコードや命名が大切とう言う話。
昨今だとAIの進化のお陰で命名にかけるコストは低くなったと思う。
『XXXという処理をしたい関数の命名を考えてください。候補をいくつかだして。』とプロンプトを入れれば大体いい感じの命名になってくれるからだ。
逆に言えばチーム固有の変な規則があるとこういう考え事が増えてしまうとも思った。
例えばInterfaceの命名には接頭にIをつけようとか。
こういう公になっていないルールがある方が今のプログラミングで良い命名ができるのではないかと思う。
一般化には3つのルールが必要
知りたい内容が書かれた章。
すぐに何でも一般化、汎用化したくなってしまうのでやり過ぎを防ぎたい。
シンプルな問題解決を心がけたいと思っているので自分の中で判断軸がある方がいいからだ。
3つが当てはまったとき初めてチームに『この関数は一般化しませんか』と根拠を持って提案できる。
議論のときも何が根拠なのかがベースにあるから空中戦の殴り合いをしなくていい。
YAGNI
よくプログラマが覚えるべき心構としてでてくる。必要になるまで書くなというやつ。
この精神に則り、まずはユースケースを洗い出すのが大切。
そしてユースケースをどうやって見つけるかだが、実際にコードが動いているところを見るしかない。
現実の問題をよく見るということだ。
また主観に頼るとバイアスがかかるので、数字としてのトラッキングも大切だ。
フロントエンドならGAやDatadogRUM、Sentryなど。
これらのツールを駆使することで可視化と監視をしてユースケースを発見できる。
YAGNIよりもまずいこと
実行されないコードをかき、管理対象にすること。
実行されないコードを管理する意味は微塵もないし、認知不可を増やすことになる。
また一般化を過度に行い拡張を続けた結果、文脈を見失ってしまっては本末転倒だ。
最適化に関する教訓その1 『最適化するな』
教訓その1
最適化するな。最近のプログラミング言語のコンパイラはかなりちゃんとしているので間違ったロジックを与えない限りはある程度の速度が出る。
早まった最適化が逆にパフォーマンスを落とすことになる。
教訓その2
最適化されていないコードは何をすべきか明確なのがよいところである。
教訓その3
そんなものはない。らしい。
プログラマはパファーマンスに対して気にし過ぎだ。というのが主張される。
わかるところもある。Webの開発をしていてある程度大きく開発が落ち着いてきたサービスに関わるとこの手のパフォーマンス最適化をしたくなる。
やることが悪いわけではない。だがたいていこの手のパフォーマンス改善は仕事のための仕事であることがおおい。
パフォーマンスを改善してクラウドリソースをN%削減したというのは確かに良さそうに聞こえる。が、全体を見渡したとき(サービス自体の売上や販管費の構成を見渡したとき)にそもそも販管費がユーザー数に対して比例しているとしたらそちらを改善できるかチャレンジしたほうがいいのだ。
一見成果に見えるが仕事のための仕事というか、パフォーマンスを改善したという成果で評価されるための仕事でしかない。
ここであげた例は過激だけどなんのためのプロダクトで、なんのためのコードなんだ?というのを忘れてはいけない。と考えがずれていった。