30歳からのプログラミング

30歳無職から独学でプログラミングを開始した人間の記録。

堅牢性と変更容易性

いろいろと考える機会があったので、備忘録としてまとめておく。


システムにおける堅牢性とは何か。
それは、壊れにくいこと、破綻しにくいことだと思う。

では、破綻しているとはどういう状態なのか。
システム全体の複雑さが増していって開発者がコントロールするのが難しくなること。
その結果として、いつの間にかバグが埋め込まれてしまって全く予期しない形でシステムが壊れてしまう、という現象が発生しやすい状態であること。
末期まで行くと、変更を加えること自体が困難になる。何かを修正すると別のところが壊れてしまうという状態になっており、身動きが取れない。

破綻を生み出す原因として何があるか。
頭のなかを整理しきれていないが、思いつくもの。主にフロントエンドの話。

  • 考慮しなければならない要素が多すぎる
    • 状態管理が難しく、複雑性を増している
      • UI(コンポーネントなど)が状態を持っている
      • 様々なコンポーネントが Store を直接操作するため、状態がどのように変化していくのか追いにくい
    • 密結合になっており、一つの変更が影響を及ぼす範囲がかなり広い
    • 依存関係が複雑なため、どこのコードがどこに影響を与えているのか、見通しが悪い
    • 共通化が行われていないため、変更漏れが生じる
    • 密結合なため、考慮しないといけないケースがたくさんある
      • 例えば、ある関数を使用するために、特定の状態や環境を前提にしていたり、前後に特定の処理が必要になっていたり
  • コードを読むのが難しい
    • 一貫性がなく、同じことをやるのに異なる方法を使っている
    • 使用していないモジュールをimportしているなど、明らかに可読性が悪いコードになっている
  • 一つの処理の影響範囲が大きすぎる
    • 関数が適切に分割されていない
    • 関数の役割や責務が曖昧なため、変な使われ方をしていたり、思ってもみないところから使われていたりする
  • テストがない
    • テストがないことで、バグが混入したときに気付けないし、バグの原因特定も難しくなる
    • テストがないと、リファクタリングも進まない
    • 上述した「密結合」「分割されていない」という特徴を持つ関数はテストを書きづらいことが多く、それによりますます悪い状態が維持されるというスパイラル

堅牢にするためには、これと逆のことをすればいい。
これと逆の状態を実現していることが、堅牢であるということ。

コードの可読性が高く、一つ一つの関数やコンポーネントの影響範囲は小さく、関数は疎結合であり副作用がなく粒度が小さいものになっており、テストも書かれているため壊れたことに事前に気付ける。

これらを実現して堅牢になることで、コードの修正や機能追加がやりやすくなる。
コードの見通しがよいことで、システムの全体像を理解しながら、コードを書いていける。依存関係がスッキリしており、データの流れや処理の流れが一目で分かることで、コードが書きやすくなる。

もちろんそれでもバグは生まれるが、コードの流れや依存関係が綺麗なので、原因を特定しやすい。テストの存在も原因特定を助ける。
そして、破綻していないわけだから、修正のためのコード変更も行いやすい。

堅牢性が低いと、こうはいかない。
恐る恐る、こわごわとコードを触る。不安を抱えながら神経質に開発することになる。
「生き生きとした状態」からは程遠い。

つまり、堅牢にすることで、変更容易性が高まる。
堅牢であるというのは、柔軟であるということでもあるのだ。

本当の意味での「柔軟さ」とは、「好き勝手にコードを書ける」ということではなく、デグレの心配を最低限にしてコードを書いていけるということであり、修正や機能追加あたってすぐに全体の構成や処理の流れを把握できるようになっているということ。

リリース優先のためにとにかく手を動かし「コードの品質は二の次で取り敢えず動くもの」を作るのが大事、という考え方がある。
現在のビジネスはスピード優先だから、とにかくすぐに動いてリリースするのが大事、という考え方。

それ自体は間違っていないと思うが、そのために設計の正しさやコードの品質を犠牲にしてはいけない。
拙速にリリースすることで嬉しいのは目の前の一瞬だけで、堅牢性はすぐに失われていく。「負債が積み上がる」というレベルではなく、一瞬で腐る。
そうすると「破綻」となり、「迅速なリリース」というのが難しくなっていく。

自分が気に入っている言葉に「ニコニコ動画は3日で作られたことによって、その後の3年を失った」というものがある。
ドワンゴの元従業員を名乗る方が、2015年にブログに書いた言葉。
「日に日に開発難易度が上がっていくばかりのコードを前に、僕は心がポッキリと折れました」とあるように、稚拙な開発がコードから堅牢性を奪う典型例だと思う。
一方の意見のみだから真偽の程は定かではないのだが、ニコニコ動画の現在の苦境を見ると、あながち間違っていないのかもしれない。

では、どうすれば堅牢になるのか。
大前提として、当たり前のことを当たり前にやること。サボらず。

  • 変数や関数に正しい名前をつける
  • 正しい名前であり続けるように気をつける。それが難しい場合それは、設計を見直すべきというサイン
  • 以下は、名前さえ正しければ自然と達成されるはず
    • 関数の責務や役割を明確にする
    • 単一責任の原則を守る
    • 疎結合にして依存性をなくす
    • 副作用のないピュアな関数にする
  • 処理の共通化を進める
  • 簡潔な記述を心がけて可読性に配慮する
  • テストを書く
  • 関数のインターフェイスを正しくする
    • 必要なデータのみを渡す
    • 本来の処理の前に行う前処理は、別の関数で行い、処理済みのデータを渡す
  • ドメインロジックがビューに漏れるのを防ぐ
  • ガード節を使うなどして、頭の中に必要な変数を少なくする

他にもあるだろうが、こういった地道なことをしっかり守る。
その上で技術選定や設計もしっかり行うことで、ようやく堅牢性は実現するのだろう。


一番言いたかったのは、「とにかくリリースしなければならない」というお題目のもとに、質の低いコードを肯定してしまうことへの違和感。
「現代のビジネスではスピードが大切」「素早くリリースして価値を届けるべき」ということには、何の異論もない。
但しそれは、質の低いコードを後先考えずに書いていくことへの免罪符にはならない。
現実は常に妥協の連続ではあるが、それでも、守るべき一線というのはあると思っている。
というか、リスクを把握した上で何を取って何を諦めるのか取捨選択しながら妥協するのと、拙速主義のもとに品質を二の次にするのは、全く別のものだ。

SPAのフルリニューアルをやることになり、どのように設計すべきか考えており、変更のしやすさが重要だという結論に至った。
仕様の変更は必ずついて回る。要件や仕様が曖昧なまま進んでしまったり、途中でひっくり返ることも、頻発すると思われる。仕様漏れも必ず起きる。
となれば、何よりもコードの捨てやすさ、変更しやすさが、非常に重要になる。

そして、変更しやすいコードというのは、堅牢なコードのことだという結論に至った。
壊れにくくデグレが起きにくいコードは柔軟さを持っており、手を入れやすい。変更を楽に、そして安全に行える。

柔軟なコードと堅牢なコードは同じだという結論に至ったことで、ずっと抱えていた「リリース優先」への違和感についても、自分なりに説明がついたように思えた。
堅牢な構成と、それを維持することを心がけながらコードを書くことは、スピードを犠牲にした我儘な行為ではないし、エンジニアの自己満足でもない。
むしろ、プロとしての責任感があるのならば、スピードに気を配りつつも堅牢性にも拘らなければならない。