見出し画像

プログラムの依存関係とは!?

みなさんこんにちは。ゼバ会です。
本日のテーマは「依存」です。

日本語だと、この依存というワードは正直ネガティブなイメージありますよね。
何者にも依存しないことが、真に自立した立派な社会人だ、みたいな。
あるいは、この言葉を聞いただけで「依存症」という病気が思い浮かぶ人もいるかもしれません。

でも辞書上の意味はそのイメージと少しだけ違ってて、もうちょっとだけ客観的な言葉です。
たとえば、他人に依存せず生きてる人であっても、社会インフラに依存せずに生きてる人はいません。
(仮にいたとしても、社会インフラに依存しなければゼバ会の記事を読むことはできませんし!)

生きていくのに日々の食事に依存してない人だっていないし、呼吸するために空気には依存してるわけだから、地球がなくたってチャラへっちゃら~なんて人もいないでしょう。
でも、だからといってそんな人でも、毎日の食事や量があまりにも量が多かったり、呼吸が24時間ずっと荒かったりすると、それはそれでそういう病気の可能性もあります。
つまり「依存」とは、少なめの方がいい反面、全くなくていいわけでもないのです。

今回は、その理屈がプログラミングにも当てはまるよ、という話。

〇プログラムにおける依存とは?

ベテランの方々には毎度おなじみの言葉である反面、新人の方だと、もしかするとプログラム同士の依存という概念にはピンと来ないかもしれません。
新人で依存を理解しているとしたら、あなたはとても勉強熱心な人です。
通常、初心者・中級者向けの教則本には登場しない言葉だからです。
だって、そんなもの気にしなくったってとりあえず動くだけのプログラムなら問題なく組めますからね。

ただ今回のコラムでは(というよりゼバ会では)、教則本でよく見かけるプログラム間の依存関係(dependency)とは少しだけ違った意味で言葉を使います。

当会における依存状態の定義は以下です。
(アーキテクチャー設計用語の「依存関係」との混乱を避けるため、なるだけ「依存状態」と呼ぶことにします)

1. ある関数(またはメソッド)が、
2. 自分以外の何らかの要素(他メソッド・他クラス・UI部品等)が、
3. 一定の動作をすることを前提としている

たとえば以下の JavaScript コードの場合、businessMethod() が様々な前提に依存していることになります。

function businessMethod(input) {
   const ret = systemMethod(input);
   if (ret.value == 0) {
      return false;
   }
   return true;
}
function systemMethod(input) {
   if (input == null) {
       return null;
   }
   return new Data(this.stringData.trim());
}

トレースに慣れた人ならすぐ気づくと思いますが、上記コードは非常に危険です。
まず businessMethod() が、引数に null は渡されないことを前提としており、それから systemMethod() が自クラスのメンバー変数 stringData が null ではないことも前提としています。
なので、結果的に businessMethod() には直接・間接合計で、バグを出す可能性のある場所が3つあることになります。

部下がいる人にとっては あるある話 だと思うんですが、若い子って、こういうコードをあんま深く考えずに気軽に組んじゃうんですよね。
でもそれは本人の意識不足・努力不足が原因ではなく、気にかけておくべき観点を教えられてないから分からないのです。

新人というものは、そもそも教えられなければ何も分からないものです。
たしかに、勉強熱心な人は教えなくても色んなことを勝手に学んでいきますが、とはいえ人間というものは自分が興味のある物事以外は本当に何も知らない、くらいが平均です。

とりわけ遠慮がちな子は「先輩を疑うなど失礼」といった意識があることもあり、ついうっかりで自分以外の人が組んだコードは完璧であるという前提でコードを書いてしまいがちなのです。
これが、質の悪いプログラムで依存状態が発生しがちになる大きな原因の1つです。

〇依存状態が発生しがちな要素

世の依存関係と呼ばれるものの中で、もっとも見て分かりやすいのはメソッド同士の依存関係でしょう。
上記の businessMethod() を見れば分かる通り、コードにデカデカと 私には systemMethod() が必要です。って書いてありますからね。

でも、systemMethod() がクラスを返さない可能性がある。 ことは、どこを見れば分かるでしょうか?
全体を俯瞰して見れば、慣れた人なら割とすぐ気づくでしょうが、それは全体を満遍なく見渡すクセがある人だけです。
コードを見るとき、1つ1つのロジックを順に追いかけるクセがある人は、複数個所に分断されたリスクにはまず気づきません。

同じように、よく見過ごされがちな依存関係としては、以下のようなものがあります。

- (HTMLフォームやWindowオブジェクト等の)U/I部品からの値が正常であることを前提としてしまう
- DBテーブルのレコードにマスターレコードがセットされていることを前提としてしまう
- クラス変数やグローバル変数の初期化が完了していることを前提としてしまう

個人的に知る範囲でいえば、「ファイルの読み書き」を行う場合の依存状態に油断する人はほとんどいません。
たとえベンダーが自動的に作成するフォルダであろうとも、存在チェックをきちんとやったり、ファイルのフォーマットチェックをしっかりやる人は多いです。
なぜなら、ファイルに対しては「ユーザーが自由にいじれるもの」という意識をみんな持ってるからです。

ですが反面、データベーステーブルにマスターレコードが正常にセットされていない可能性を考慮する人は、多少ですが少なくなるでしょう。(まぁ、さすがに膨大な数がいるとは思いたくないですが)
個人的にも経験があるのであんま人に強くもいえないのですが、ユーザーから弄れない部分は、やはりどうしても油断しがちになります。

ですので、自分(自メソッド)が依存してしまう可能性がある部品は、以下のパーツに分けて考えるのがおすすめです。

- クラス変数/グローバル変数
- U/I部品
- DB
- ファイル
- 他メソッド
- 他クラス
- 他モジュール

こいつら↑からの入力があるときや、格納された値を受け取って利用する場合には、たとえ引数経由での入力ではなくても、メソッドの外にあるあらゆるものは入力であると考えると、設計が少し楽になります。
たとえば、自クラス内のメソッドがクラスメンバー変数を参照するとき、これは「メソッドへのデータ入力」と見做せます。
なぜなら、自メソッド以外の何者かが書き換える可能性があるからです。

もちろん、あらゆる部品が自分以外の何もかもを本当に一切合切なにも信用しないとなると、それはそれで問題が出るでしょう。
というより、自分以外の全てが信じられない人だらけのチームなんて、そもそも正常にワークするはずありません。
(悪の秘密結社じゃないんだから、、、)

つまりあまりにも何でもかんでもアヤシみすぎても、本来は自分達が楽をするためにシステムを堅牢にしようとしているのに、そのために手間が増えたら本末転倒というわけです。

〇依存のさせ方: 疎結合と密結合

そこで、メソッドとメソッド、あるいは部品と部品の依存状態に、結合度合いという考え方を取り入れると、どこにどれくらい注意したらいいか分かりやすくなります。

世の中的には、依存状態がときと場合によって違うことを「密結合」「疎結合」と呼んだりします。
「密結合」とは、完全に依存しきっていて、片方がコケたらほぼ100%一緒にもう片方もコケてしまう状態。
対して「疎結合」は、依存してるはしてるんだけど、常にどこかしら疑いの目は残している状態を呼びます。

まぁ、人間でいえば親密度ですかね。
カノジョとカレシみたいな関係だったら密結合してよくて、結婚したら収入は旦那に頼り切り、旦那が倒れる可能性なんて考えない。だって自分が健康管理するから!
みたいな関係もありでしょう。
メソッドとメソッドの関係も、本当に一蓮托生のメソッド同士が密結合してるのはいいかもしれません。

でも旦那の考え方がフワフワしてて、仕事だっていつなくなるか分からない、自分がしっかりしなきゃ! みたいな人は、旦那に密結合するのはちょっと無理でしょう。怖いしね。

プログラムも同じです。
いつコケるか分からないメソッドに密結合するのは怖いのです。

たとえ非常に近い距離に存在していて、同じビジネスロジックを処理する仲良しのメソッド同士であろうとも、信用できない部分は信用しないという割り切りは必要です。
親しき中にも礼儀あり。
うちの嫁さんは割といい嫁だとは思うんだけど、それでも地雷はどこに埋まってるか分からないって、20年一緒にいてもやっぱ思うもん。
(最近はとみに更年k.......ゲフンゲフン)

基本的にメソッド同士の結合度合いは、「やり取りするデータ量を制限する」「入力値のチェックを厳密化する」の2つでコントロールします。
密結合の場合はデータを大量にやり取りしあっていいし、入力値のチェックも最低限でかまいません。
でも疎結合の場合はデータを絞んないとダメだし、その絞られたデータにしっかり検問をつけてチェックせんといかんとです。

そのあたりのメリハリをつけることにより、常にフルフル頭使いっぱなしの状態で気を張ってプログラムを組まなくてよくなるのです。

〇密結合しているメソッドがツリー状に連なることを意識する

では、どのメソッド同士だったら密結合に信用していいものでしょうか。

これも人間関係と同じです。
業務上、立場に近い者同士は信頼しあえる可能性が高いといえます。
でも立場の遠い者同士は、たとえ信頼はできても、その度合いは限られるでしょう。
たとえどんなに超得意様であろうとも、ある日突然アポなしで訪ねてきた他社の人であれば、無条件に手放しでは信用はできません。

なので同じクラス同士・1つの業務に属する同士など、処理に共通点のあるメソッド同士は、どちらかといえば密結合しても大丈夫な可能性は高まることになります。

なおかつ、通常多くは子メソッドは(兄弟メソッドではなく)親メソッドからコールされるわけですから、ゆえに結果的に密結合しているメソッド同士は最終的に「ツリー図」のように連なることになります。
(このツリー図は実際にはドキュメントに落とされることもないでしょうし、「どこまでを密と呼ぶか」によっても変わってくるものだとは思いますが)

つまり、もっとも信頼できる人が「上司」である状態がビジネスにおいてもっとも望ましく、実際にそうである組織は品質が高いといえる、というわけです。

〇疎結合のサンプル

では、今日のしめくくりとして、先ほどのコードサンプルを疎結合の状態に変更してみましょう。
要はこうなるわけです。
(JavaScriptらしからぬ記述もありますが、そのへんは要点じゃないので広い心でお許しを汗)

function businessMethod(input) {
   if (input !== null && type(input) !== Integer) {
       return false;
   }
   const ret = systemMethod(input);
   if (!ret || ret.value == 0) {
      return false;
   }
   return true;
}
function systemMethod(input) {
   if (input == null || type(this.stringData) !== String) {
       return null;
   }
   return new Data(this.stringData.trim());
}

businessMethod() と systemMethod() の冒頭部分に、入力データに対するバリデーションをガッツリ追加しました。
要は、信じられない可能性があるから信じてないわけです。

もちろん、私のサンプルコードではバリデーションを if で行っていますが、引数や戻り値に型指定があって、それをもってバリデーションとできるならそうしてもかまいません。
当たり前ですが、以下のような書き方をすればメソッドには数値以外の値が入ってこなくなります。(これはJavaScriptではなくTypeScriptの記法)

function businessMethod(input: number) {

初期バージョンでは型指定がないような簡易言語も、バージョンアップするにつれ徐々に型指定に対応していく傾向があります。
これも、引数に型指定ができれば、それが簡易的なバリデーションとして作用するからです。

またこのサンプルコードのポイントは、自クラスのメンバー変数(stringData)に値が入っている可能性を信じていない点です。
systemMethod() から見てメンバー変数は「外部」に当たるわけですから、これも「メソッドへの入力」と見做すべきです。
初期化されていることが何らかの方法で完全に保証されているならいいのですが、常に必ず絶対100%完璧に保証されていないのなら、入っていない可能性や、誤った値が入っている可能性を疑うべきでしょう。

もちろん、もし systemMethod() にデータベースやファイルからの入力があるなら、それらも「外部」ですので、少なくともバリデーションの対象とすべきかどうかの吟味だけは意識的に行う必要があります。
それがていねいに作るということだし、そういうときこそ「神は細部に宿る」という言葉を思い出すといいと思います。

〇次回予告

「ていねいに作る」いい言葉ですよね。
もちろん、ていねいを意識しすぎて精神すり減らしちゃったらそれはそれで意味ないけど、ていねいなモノづくりができるというのはそれだけで1つの才能です。
というより、「ていねいに作りたい」という欲求は、モノづくりを仕事にする人達の根源的欲求といっても過言ではありません!

てなわけで次回はプログラミングの話題からはいったん離れ、その才能を伸ばすお話をしましょう!

「ていねい」

言うのは簡単だけど、そもそもていねいの定義って何? ってお話。
具体的に何をやったら「ていねいな仕事をした」ことになるの?

何をどこまでやったら怒られないの?
何がどうなったらみんな納得してくれるの?
何をどうすれば俺のことみんな認めてくれるの?

その辺フワフワした状態のまま生きてる人の多いこのご時世、ズバッと定義いたしやしょう!
お楽しみに!

この記事が気に入ったらサポートをしてみませんか?