コードや設計で「シンプル」が良いのはわかるが実際にどうすれば良いだろう?
先日、IaC (Infrastructure as Code) のコードを書いている時に自分が体験したシンプルさの威力の体験と、どうやったら「シンプル」に設計・コーディングができるか?ということについて調べてみたので整理したい。
「はじめて」の人に敗北
先日、私がSLA Site というアプリのデプロイスクリプトを terraform で書いていた時のこと。この Site は内部で機能に何か問題が起きた時にお客さんより先に気づくための仕組みで、私は、Azure Functions の VNet インテグレーション、Managed Indentity、KeyVault Reference といった機能が正常に動作さするか確認するためのアプリを作っていた。
Azure Functions は対応する Event Source の数が多いため、私は、沢山のサイトを作り、Vnet インテグレーション、Managed Identity、KeyVault reference を設定する必要がある。めんどくさい。
手で作ってもよいのだが、元DevOps 野郎なので、インフラのコードを手で設定するのはいごごちが悪いので、昔から使っている terraform で作ることにした。
terraform 初めての Alexey
IaCのスクリプトというのは単純だがこのように複雑なものになると結構時間がかかる。何より「試す」ための時間がかかる。クラウドインフラのコードを書いてちゃんと動いているか確認するためには、30分ぐらい待たないといけないこともよくある。ずっとこれに時間を使っているわけではないが、わけのわからないエラーを解決しながらやっていたので数週間かかっている。
そして、ついにそろそろ最終段階で、ようやく、私がすべてのトリガーの種類をカバーし、VNet, Managed Identity, KeyVault がすべてテストできるスクリプトをテストし書き終えた頃、Alexey という同僚が同じようなことをやることになった。
私とは違うSKUで同じことをする必要があったので私に聞いてきたのだ。私はコードをシェアして、terraform のクイックスタートのページを「これやったら大体わかるよ」と伝えて、躓いたら私に聞いてと伝えた。そう。彼はプログラミングはめっちゃできるが、terraform はたぶん初めてなのだ。
シンプルな解決策
そうして、数日たったあと、彼が私を呼び出して、こういう構造にしたよという彼の書いたスクリプトを見せてもらった。彼は terraform の module を使って、私のスクリプトをリファクタリングしたものを作って、それからこう私に聞いた。
VNet や、KeyVault リファレンスって仕組み的にすべてのトリガーでテストする必要ある?
私は、「すべてのトリガー」ですべてのシナリオと考えていたので全部を実現するものを作っていたが、彼はそういう風に聞いてきた。確かによく考えると、必要ない。ただし、Managed Identity だけは必要だねという話になった。そして、彼は 私のスクリプトをきれいにモジュール化していて、Event Source のデプロイが module で行われるようにしたので、その部分が再利用できるようになっていた。私の考えていた複雑なものの構造よりそっちの方が良いから彼の構造を採用した。
シンプルさの威力
自分の書いていたスクリプトはすべてスパイクソリューションと化したので、自分も一からManaged Identity, VNet, KeyVault リファレンスのアプリを一から作成しなおすことになった。
ところが驚いたことに、私は一日もたたないうちに、Managed Identity のものが終了できて、VNet も次の日に終わった。それまで数週間ついやしてたのに。なぜって、「シンプル」になったから。
リソースのデプロイで同じコードを書かなくてよくなったから。私のつくってた「全部できる」アプリはデプロイ全部成功させるにはいろいろ難しいことを学ぶ必要があったしそれに気づくにも時間がかかった。
例えば、Role Assignment をどのような依存関係で設定するか、どのようにVNetを設計して、パブリックな回線を通さずに Event Source にアクセスさせるか、そして、それをどうやって terraform からデプロイさせるか、どうやって、private preview のリソースをデプロイするかなど、たぶん普段からやっている人だったら当然しってるのだろうが、最近はプログラマなので terraform は私も久々だ。結構エラーにかかっては解決策をログから探るったり terraform 師匠に聞いたりしてゆっくりと、サイドプロジェクトとして時間をかけてやっていた。
これは、複雑性なアプリによって、複雑さが一気に押し寄せてその掛け算になっているので、私も結構時間がかかったのだろう。だが、terraform 初めてのはずの、Alexeyがシンプルにしてくれたから、私は0から作り直しなのに、1日ずつで小さなトピックを終えることができた。掛け算だった部分がなくなるだけでもなんと簡単になることよ。
今まで トリガー種類 x Managed Identity x VNet x KeyVault Reference だったのが、トリガー種類 x Managed Identity + VNet x トリガー種1 + KeyVault Refernece x トリガー種1 になったので、複雑性がずいぶん緩和された。私が作った module は Alexey が使えるし、逆もしかり。
もちろん私が苦労していろいろ地雷踏んでいるからスムーズというのもちろんあるかもしれないが、やっぱり「シンプル」であるのは破壊的な価値があるようだ。
敗北したので、三流としては学ぶしかない
つまり、私は、結構 terraform を書いたりツールを作ったり terratest とかに貢献していたのに、センスのある素人に敗北したw さすが三流である。さて、三流のアティチュードとしてはここから学ばなければいけないということで、どうやったらコードや設計をシンプルにできるかを調べることにした。
Alexey のことば
最初にすることは当然 Alexey がどういう思考回路でその結論にたどり着いたかだ。私が彼の立場なら、全然しらないものだし、私の構造をきっとマネするだろう。それに、私ももちろん module は知っていたが、「重複するまでは、リファクタリング」しないというポリシーを使っていたのだが、「重複している」にもかかわらずそのポリシーを忘れていた。ここは本来リファクタリングすべき場所だったのだろう。正直 module として整理するのが面倒だったのもある。
彼に聞いてみると彼はこんなことを言っていた。
今回は自分がめんどくさがったが、そのせいで結局沢山時間がかかることになったのだ。めんどくさいから、重複部分が出てきたら、DRYの原則を適用して、リファクタリングして、コード個所を1か所にする…ってもう20年前から習ってるやん。って感じやけど、それをまず実施するべきだった。
また、当日はたまたまクリスとのメンタリングセッションがあったので、彼にも聞いてみた。
クリスのことば
と言った。彼は "obsessed" という言葉を使っていたので、相当シンプルには気を使いまくっている様子だ。彼は続けて
私は「一番」とは思っていなかったが、「知って」いたはずだった。eXtreme Programming でも、4つの価値のうちの一つは「Simple」だし、「KISS の原則 (Keep It Simple Stupid)」(訳:シンプルにせなあかんで、あほやのぉ)そして、「YAGNI (You ain't gonna need it)」(訳:それは将来もたぶんいらんで)というのも知っていた。
でも具体的にどうするん?というのは気になるところだ。「シンプル」って明確な基準は無い。
だからクリスにどうやったら具体的に「シンプルになれるのか」というのを聞くと彼は次のようなことを答えてくれた。
静的解析ツールを使って、コードの複雑度を調べる。結構これで僕はシンプルにすることを学んだよ。
コードを書いたときに「このコードを他人に説明するときに簡単に説明できるか?」ということを考えるんだ。簡単に説明できそうにもなかったらたぶん君は複雑なことをしている
Copilot や GPT に聞く。彼らが示してくるコードはシンプルなことが多い
私の悪い癖は、複雑なことをしがちなのは自覚しているので、このあたりのことを実施してみたい。最後に、ChatGPT 先生にも聞いてみた。
ChatGPT のお答え
Chat GPT 先生もいいことを言っていたので、日本語にしてシェアしておこう。
KISSの原則を守る
Keep It Simple, Stupid (KISS): この原則は、複雑さよりもシンプルさを推奨する。コードを書くときに不必要な複雑性を避けて、単純にする。シンプルなコードはテストも簡単で、理解しやすくメンテしやすい。(著者注:ほんまそのとおりでございます。すんません)YAGNIに従う
You Aren't Gonna Need It(YAGNI)は、それが必要になるまで、機能を追加することを避ける。この原則はオーバーエンジニアリングを防ぎ、デザインを最小限にする。デザインパターンを賢く使う
デザインパターンはパワフルだが、使いすぎると不要な複雑さを導入する。パターンをかしこく、あなたのデザインをシンプルにするために使おう。シンプルさのためのリファクタリングする
シンプルにするために継続的にリファクタリングしよう(あー、今回のわしだ)複雑性を低くするための機会を見つけて、読みやすさやパフォーマンスを向上させよう読みやすいコードを書く
クリアな命名規則:わかりやすい名前を変数やファンクションやクラスにつけること。よい名前は余計なコメントを不要にするし、コード自体が語れるようになる。
ロジックをシンプルにする:複雑な条件を分解したり、ネストしたループをシンプルにしたり、小さなファンクションにしたいりする。一つの関数が1つのことだけを「上手に」するようにする。モジュラー性に気を配る
小さなパーツに分解する:システムを小さな独立したモジュールとして設計する。このアプローチは、システムを簡単に理解したり、テストしたり、メンテナスすることができるようになる。
凝集度を下げる:モジュール間の依存性を下げる。このことは各コンポーネントをより再利用しやすく、柔軟にさせる。コードレビューとペアプログラミングを強調する
相互レビュー:定期的にコードレビューを「シンプルさ」にフォーカスをして実施する。相手からのフィードバックは、複雑なコードを見つけるのに役立つ。
ペアプログラミング:ペアプログラミングも、シンプルなソリューションをみつけるのに役立つ継続的に学び、適用する
学習と適用:ソフトウェアアーキテクチャやデザインを継続的に学びアップデートする。学んだことを適用してよりシンプルにしていく。
実験:新しいアプローチを恐れず、既存のコードをリファクタリングするSOLIDの原則を理解し適用する
SOLIDの原則は、オブジェクト指向のデザインの原則で、保守しやすくスケーラブルなシステムを作れるようになるパフォーマンスとシンプルさを優先させる
シンプルさは重要だが、パフォーマンスも考慮しよう。パフォーマンス的な理由で少しだけシンプルじゃないデザインが必要なケースもある。
ChatGPT先生の回答以外にそもそも「要件・仕様」のレベルからシンプルにするという考えもある。あまり発生頻度の高くないバグをつぶすために、複雑な実装を導入する必要があるのなら、あまり重要でないバグを受け入れてそういう場合は、問題が起こりうるが、それは既知の問題で、それを踏まないためにこうしようと決めてしまうのもありだろう。
シンプルさは、「繰り返し」をしていくなかで達成される。継続的に見直してシンプルなコードであり、デザインにしよう。シンプルさを重視するマインドセットにしよう。こういうマインドセットは、君のチームや組織にそのマインドセットを広げていこう。
すべて「知って」いた
あああ、全部「知ってる」だけど出来てないんだな。まずは、「マインドセット」を「シンプル重視」に切り替えて、クリスや、Alexeyが言ったことを実践してみよう。GPT先生が言ってるように、これは繰り返しやらないとなかなか身につかないのだろう。ちなみに、SOLIDは長くなるので解説していないが、興味がある人は次の本に詳しく載っています。
まとめ
「シンプル」は簡単ではないが、今回整理したような「知識」をもっと実践に適用して、改善していこう。説明可能にすること、ツールによって複雑さを定義して、DRYや、GRASP,SOLID,KISS,YAGNIの習ったことある原則を思い出して当てはめてみよう。最後にクリスが書いてくれた図をシェアしたい。
こんなインターネットミームを知ってる?初心者は最初複雑なことができないからシンプルなコードなんだけど、いろいろ技を覚えると複雑にしがちで、達人になったらシンプルに戻るんだよ。
あと、最近本出して、ありがたくも結構読んでいただいてるみたい。よかったらどうぞ!
この記事が気に入ったらサポートをしてみませんか?