プロジェクトリーダーという役割をやるにあたって品質保証について考えるようになりました。
品質と一口に言っても「内部品質」と「外部品質」の2点がありますが、エンジニアである以上、内部品質の向上が外部品質を上げる最適解だと思います。
そして内部品質を上げるためにはアーキテクチャを考えることが大切ですが、既存プロジェクトの場合はアーキテクチャが 存在しないレベルでぐっちゃぐちゃ 十分に検討されていないケースも多いです。
そこで、一番手っ取り早くプログラムの可読性を上げる方法は、新規に書くコードをできるだけシンプルに実装することです。
今回はその中でも個人的に使っている条件分岐を少なくする or 見やすくするテクニックについてまとめてみました。
なお、今回の例は「こういうやり方がある」という説明のために利用しているだけで、必ずしもベストな例ではないことをご了承ください。
条件分岐を減らす方法7個
今回の分岐を減らす方法ですが、基準としては
- 1メソッドあたりの循環的複雑度を下げる
- ネストを1段階浅くできる
のいずれかを満たしているという条件で考えています。
ガード節
まず、ネストを浅くする最もメジャーな方法としてガード節を思い浮かべた方も多いかと思います。
ガード節とは、処理の対象外と条件を関数の先頭で return/continue する方法のことで、その後の処理条件を限定することができるメリットがあります。
まず、悪い例から見ていくと、
public function hoge($a, $b) {
$result = 0;
if (isset($a)) {
$result = 1;
} else {
if (isset($b)) {
$result = 2;
}
}
return $result;
}
あまりいい例ではありませんが、このようにifを重ねることによってネストが深くなるケースがあります。これをガード節を書き加えることによって以下のようになります。
public function hoge($a, $b) {
if (isset($a)) return 1;
if (isset($b)) return 2;
return 0;
}
最初に$a, $b
に値が入っていないかを判定することによって、不要なelseを書く必要がなくなります。
条件分岐を減らしたいと考えたときに真っ先に検討する方法の1つです。
bool判定
続いて true/false判定をする際にも余計な条件分岐が書かれているケースも少なくありません。
具体的な悪いコードは以下の通りです。
public function hoge($a) {
if ($a === '') {
return true;
}
return false;
}
基本的に true/false を取得したい場合は、その条件を返すだけで事足ります。
public function hoge($a) {
return $a === '';
}
配列利用
配列を使って条件分岐を減らす方法もあります。
enumやconfigで定義するほどでもないけど、DBから値を取得して画面でテキストを表示したいといったときに便利です。
では、具体的なコードを見ていきましょう。
public function hoge($x) {
$a = 0;
switch ($x) {
case 0:
$a = '駅名1';
break;
case 1:
$a = '駅名2';
break;
case 2:
$a = '駅名3';
break;
}
return $a;
}
決して悪い書き方ではありませんが、配列を使って以下のように書き換えることができます。
public function hoge($x) {
$array = [
0 => '駅名1',
1 => '駅名2',
2 => '駅名3',
];
return $array[$x];
}
こちらの方がシンプルでいいですね!
仮にswitch文に「defaultに入ったときは0を返す」といった条件があったときもnull合体演算子を使ってシンプルに書くことができます。
こちらはあくまでもPHPの例なので他の言語では上手く動作しない可能性があります。
public function hoge($x) {
$array = [
0 => '駅名1',
1 => '駅名2',
2 => '駅名3',
];
// $xが'駅名1', '駅名2', '駅名3'でないときは$array[$x]がnullのため'駅名0'が返される
return $array[$x] ?? '駅名0';
}
メソッド分割
根本的な解決ではありませんがメソッドを分割して、1つのメソッドあたりの循環複雑度を減らす方法があります。
例えば以下のようなコードがあったとします。
public function hoge($array) {
$result = [];
foreach ($array as $item) {
if ($item) {
array_push($result, $item);
}
}
return $result;
}
このif文を別メソッドに切り分けることでコードの可読性を上げることができます。
public function hoge($array) {
$result = [];
foreach ($array as $item) {
$this->fuga($result, $item);
}
return $result;
}
private function fuga(&$result, $item) {
if ($item) {
array_push($result, $item);
}
}
正直この例ではあまりメリットが分かりづらいですが、例えば「ある特定の条件結果がtrue/falseによって処理を分ける」といったケースでは可読性が上がるケースがあります。
// 変更前
$result = '';
if ($condition) {
if (...) {
foreach (...) { // TODO }
}
} else {
foreach ($array as $item) {
// TODO
}
}
// 変更後
$result = $condition ? $this->hoge() : $this->fuga();
条件分岐を減らすテクニックというよりは、きちんとモジュール化しましょうという話なので少し毛色は違いますが立派なテクニックと言えるかと思います。
三項演算子の活用
人によって意見が分かれますが三項演算子を使うのもネストを浅くする1つの選択肢になります。
具体的にコードを見ていきましょう。
$result = 0;
if ($condition) {
$result = 1;
} else {
$result = 2;
}
三項演算子を使えば以下のように書くことが可能です。
$result = $condition ? 1 : 2;
ただし三項演算子は読み手によってはわかりにくいと言われることも少なくありません。
したがって、三項演算子を使う場合にも複雑な処理を書かないことは頭に入れておく必要があるかと思います。
型定義の実装
型定義が行える言語であるにも関わらず型を定義していないという場合もあるかもしれません。
例えば、PHPは7系から型定義ができますし、TypeScriptなどでもanyをできるだけ使わないようにすることで分岐を減らすことができます。
public function hoge($id) {
if (!is_int($id)) {
throw new \Exception('id must be type int');
}
$sql = "SELECT * FROM user WHERE user_id = {$id}";
...
}
型定義は以下のように実装することができます。
public function hoge(int $id) {
$sql = "SELECT * FROM user WHERE user_id = {$id}";
...
}
型定義によってどれだけエラー発生が減らせるか?については以下のスライドも参考になります。
» PHP7 で堅牢なコードを書く – 例外処理、表明プログラミング、契約による設計
言語特有の演算子活用
言語特有の演算子を活用したり言語特性を生かすことによって条件分岐を減らすことができます。
例えばPHPであれば、null合体演算子を活用して以下のようにコードを変更することができます。
// 変更前
public function hoge($a) {
$result = 0;
if (!is_null($a)) {
$result = $a;
}
return $result;
}
// 変更後
public function hoge($a) {
return $a ?? 0;
}
他にも、JavaScriptでは以下のように書くことができます。
// 変更前
const hoge = value => {
let result = 0;
if (!!value) {
result = value;
}
return result;
}
// 変更後
const hoge = value => {
return value || 0;
}
追記
正しくnull/undefined判定を行うためには以下が正しいコードになります。
// 変更後
const hoge = value => {
return value ?? 0;
}
avaScriptは0やfalseなどもfalsyであり、それらも含めて判定を行いたい場合には上記の論理OR演算子||
を活用してください。
このように言語特性を生かすことによってネストを浅くする方法がないか?は一度探してみてもいいのではないかと思います。