7
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

駆け出しプログラマ向け、実務で使える「変更容易性」ってなに?

Last updated at Posted at 2021-08-22

前置き

こちらの記事は筆者が備忘録的にまとめたものです。
せっかく来てくれた読者の皆様にとっても役に立つように意識して書いてみましたので、
是非最後まで読んでみてください。では、Let us begin!

変更容易性(EoC: Ease of Changing | Modifiability)

へんこうよういせい とは

これは特に辞書など引っ張ってくる必要もなさそうですね。
意味は、変更のしやすさ になります。変更のしやすさと一口に言っても、

  • 変更を加える際に必要な変更箇所の数
  • 変更を加える際の他の実装への影響度
  • 変更に要する時間

等々いろいろあるかと思います。

変更容易性をコードに適用した例

適用前(変更容易性 改善前のコード)

まずは変更容易性があまりないコードを見てみましょう。(下の問題点から見ると早いです)
タスクの進捗ステータスに応じた処理を実行する関数があるとしましょう。

javascript
// task は以下ステータス(status) のどれかを持つことにします。(以下 3つのみ です)
// - open 未着手
// - inprogress 進行中
// - close 終了

function funcOne(task){
  // ステータスが`未着手`なら
  if(task.status === "open"){ /*  ... do something */ }
  // ステータスが`進行中`なら
  if(task.status === "inprogress"){ /*  ... do something */ }
  // ステータスが`終了`なら
  if(task.status === "close"){ /*  ... do something */ }
}
function funcTwo(task){
  // ステータスが`未着手`か`進行中`なら
  if(task.status === "start" || task.status === "inpRogress"){
    task.status = "finish"; // ステータスを終了に変更する
  }
  return task;
}
function funcThree(task){
  // ステータスが`終了`か`進行中`なら
  if(task.status === "close" || task.status === "inprogress"){  /*  ... do something */  }
}

うぅ、ひどいコードですね。
先に上記コードの問題点をいくつか列挙してみます。

問題点

  • 存在しないはずのステータス(start, finish)が存在します。open,closeと間違えたという設定にしておきます。
  • 小文字であるべき文字が大文字になっています(funcTwoifinpRogress)
  • 全てのステータスを文字列で扱っています
    • 文字列で管理しようとすると、すでに列挙している問題点のようなバグを生み出す温床になります
  • 全て文字列として定義されているせいで、今回のタイトルである変更容易性も失われています(詳しくは後述)

今はあくまで、一つの例として上記の3つの関数しか定義していませんが、アプリの規模によってはこのようなコードが

  • 多数のファイルにまたがって、しかも
  • 複数箇所で定義されているかもしれません

(流石にこんなバグだらけで肥大することはないと思いますが・・・)

鬼上司現る

さて、上司があなたに言いました。
「この文字列のせいで発生してるバグ、あと3分で全部修正して」

あなたから、

「無茶でしょ」
「一体何ファイル、何行分のコードがこんな状態なんだ・・・」
「本来存在しないステータスがあったらエディタの全ファイル検索使っても見つけるの無理だよ・・・」

なんて悲痛の声が聞こえてきそうです。

ごもっともな嘆きですよね。もしこのバグの状態が正解という前提に更に何か実装されていたらどうしましょう。
たとえばどこかで、タスクのステータスが start だったら通知を送るとかしていたら・・・

javascript
if(task.status === "start"){   // 'start' なんてステータスは存在しないのに・・・・
  sendNotification()
}

修正するとしたらこれら全てのことを考えないといけないわけです。とても3分じゃ足りませんね。

やや修正後

さて、とりあえず修正しようと思ったらまずは正しく動くようにしないといけません。
とりあえず頑張って大文字小文字の間違え、存在しないステータス部分の修正も終えたとしましょう。

javascript
function funcOne(task){
  if(task.status === "open"){ /*  ... do something */ }
  if(task.status === "inprogress"){ /*  ... do something */ }
  if(task.status === "close"){ /*  ... do something */ }
}
function funcTwo(task){
  if(task.status === "open" || task.status === "inprogress"){
    task.status = "close"; // ステータスを終了に変更する
  }
  return task;
}
function funcThree(task){
  if(task.status === "close" || task.status === "inprogress"){  /*  ... do something */  }
}

お疲れ様でした。

全ファイル一つ一つのステータスを全てチェックし、システムが完全に問題なく動くように修正を終えたあなた。

一息つこうとオフィスの外に出ると、美しい木々が生い茂り、新鮮な空気に包まれる、まるで一切の汚れのない森の中にいる気分です。

そう、あなたが全ファイルの全ステータスのバグ修正を終えた今、
すでに自分以外の人類(鬼上司含む)は全て消滅していました、なんてことには(なりません)。

さて、バグ修正だけでこんな状態です。もし鬼上司が蘇ってinprogressinProgress にしてくれ、って言ってきたらどうしましょう?
まぁ修正内容としては別に変ではありませんが、人類が消滅できる時間があるくらいそこら中のコードでタスクのステータスが使われています。

人類もう一回消滅できそうですよね。

ここで真面目に戻りますが、これまでの状態は変更容易と言えたでしょうか?
答えはNOですよね。

上司に頼まれたステータスの名称変更も、また全てのステータスを見直す必要があるし、今後、新人が新しくステータスを扱う際に同じバグを入れてしまうかもしれません。

では inprogressinProgress に変更する前に、本題の変更容易性を改善しましょう。

適用後(変更容易性 改善後のコード)

↓新しく追加 | (taskStatus.OPEN"open" という文字列が取得できる)

javascript
// これ↓をあらゆる箇所で使う。 javascript なら export/import とか。
const taskStatus = { OPEN: "open", IN_PROGRESS: "inprogress", CLOSE: "close" }

↓文字列で扱っていたステータスを全て↑で定義したオブジェクトで置き換える

javascript
function funcOne(task){
  if(task.status === taskStatus.OPEN){ /*  ... do something */ }
  if(task.status === taskStatus.IN_PROGRESS){ /*  ... do something */ }
  if(task.status === taskStatus.CLOSE){ /*  ... do something */ }
}
function funcTwo(task){
  if(task.status === taskStatus.OPEN || task.status === taskStatus.IN_PROGRESS){
    task.status = taskStatus.CLOSE; // ステータスを終了に変更する
  }
  return task;
}
function funcThree(task){
  if(task.status === taskStatus.CLOSE || task.status === taskStatus.IN_PROGRESS){  /*  ... do something */  }
}

解説

値が決まった固定のもの(今回で言えばステータス3つ)をあらかじめ taskStatus という形で定義しておき、その変数の特定のプロパティにアクセスすることで、各ステータス("open","inprogress","close") を取得できるようにしました。

あとはステータスを必要とする箇所で、taskStatus.〇〇と呼び出すだけです。

では、inprogressinProgress に変更してみましょう。

javascript
const taskStatus = { OPEN: "open", IN_PROGRESS: "inProgress", CLOSE: "close" }
                                               // ↑ここだけ↑

これで終わりです。
忌々しい上司がカップラーメンを作る隙もありませんでした。

なぜなら、他の場所は全て taskStatus.IN_PROGRESS としているからです。中にどんな値が入っているかは気にしなくて良いのです。

最初からこうしていれば人類は消滅しなくても良かったかもしれませんね。

今回の変更容易性改善によるメリット

  • 一つの変更のために、つられて変更しなければならない箇所が大幅に減りました。
    • 本来は、Aを修正したらBも、Bを修正したらCもというようにお互いに依存しまくっている状態は避けるのが好ましいです。(疎結合)
  • 変更のために気にしないといけない箇所が減りました。
  • 人類が救われました

まとめ

メリットでほとんどまとめていますが、今回のような文字列で扱わないようにするというのは、変更容易性改善の話におけるほんの一部になります。変更容易性の話には先ほどチラッと出た 疎結合 とかそういう話も出てきます。

要するに、AというものとBというものがお互いに依存しまくっていると、Aだけに変更を加えたくてもBも変更が必要になるかもしれない、なんてことがあるので、できるだけお互いに結合していない、独立した状態(疎結合)にしておこうね、という感じです。

つまり小池〇〇子氏に「疎です」と言わせたら勝ちです。


重ねてお伝えしますが、この記事の内容はほんの一部にすぎません。
ぜひ、みなさんも変更容易性とはなんぞや、疎結合とか密結合ってなんや、という疑問をご自身でも積極的に調べてみてください!
ここまで読んでくださりありがとうございました!

参考

7
12
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?