受託開発エンジニアでプロジェクトリーダーという立場になるにあたって品質保証について考えるようになりました。
品質と一口に言っても「内部品質」と「外部品質」の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 === '';
}
配列利用
続いて配列を使って条件分岐を減らす方法もあります。
個人的にはswitch文を使おうかなというケースには特に有効な方法になるかと思います。
では、具体的な悪いコードを見ていきましょう。
public function hoge($x) {
$a = 0;
switch ($x) {
case 0:
$a = 1;
break;
case 1:
$a = 3;
break;
case 2:
$a = 5;
break;
}
return $a;
}
決して悪い書き方ではありませんが、配列を使って以下のように書き換えることができます。
public function hoge($x) {
$array = [
0 => 1,
1 => 3,
2 => 5,
];
return $array[$x];
}
こちらの方がシンプルでいいですね!
仮にswitch文に「defaultに入ったときは0を返す」といった条件があったときもnull合体演算子を使ってシンプルに書くことができます。
public function hoge($x) {
$array = [
0 => 1,
1 => 3,
2 => 5,
];
return $array[$x] ?? 0; // $xが0, 1, 2でないときは$array[$x]がnullのため0が返される
}
メソッド分割
続いて根本的な解決ではありませんがメソッドを分割して、1つのメソッドあたりの循環複雑度を減らす方法があります。
例えば以下のようなコードがあったとします。
public function hoge($array) {
$result = [];
foreach ($array as $item) {
if ($item) {
array_push($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($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_null($id)) {
throw new \Exception('id must not be null');
}
$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;
}
このように言語特性を生かすことによってネストを浅くする方法がないか?は一度探してみてもいいのではないかと思います。
他にもこんなテクニックあるよ!という方は、ぜひ追記したいのでコメントをお願いします!