クイック エンジニアリングブログ

株式会社クイック Web事業企画開発本部のエンジニアリングチームが運営する技術ブログです。

文字列結合スニペット

こんにちは!Czです!

僕はよく科学館にいくのですが、行く理由の一つとしてプラネタリウムがあります。
ある科学館では夏限定で公開しているプラネタリウムがあり、そのプラネタリウムのウリが何と「12K」と超高解像度!

4年前に初めて体験したのですが、その没入感に魅了されて今では夏の恒例行事として家族で月3回観に行くほどです!(家族はあまり乗り気ではないですが...)

ユーザの声としては

「まるで宇宙船に乗って宇宙を旅しているみたい」

「えっ?座席動いてる??」

「あ〜涼しい」

とのこと。※個人の感想です

科学館に許可を取っていないので名前は伏せておきますが、興味がある方は「12K プラネタリウム」で検索!

ふぅ...プラネタリウムの宣伝できたし、これでブログ完了でいいよね?えっ、ダメ?


え〜今回のテーマは「文字列結合スニペット

まぁ大したことないです。今開発中の仕様で以下のようなものがあるのでそれをネタにしようと思います。

  • 選択された項目を「/」区切りで表示
  • 値が選択されていなければ表示しない

今回はこういったあるあるな処理を効率よく書きたいなぁと言う話です。

プログラムを始めたばかりの人の参考になってくれればと思います。

※本プログラムはPHP+Laravelを使用しています。ですが他の言語やフレームワークでも応用が効くと思います。

あまり良くないパターン(sample1)

# 入力値
$isNurse = true;
$isManager = false;
$isCertifiedNurse = true;

# 期待値
'看護師/認定看護師'
<?php
# 処理
public function sample1($isNurse, $isManager, $isCertifiedNurse)
{
    $value = '';

    if ($isNurse) {
        if ($value !== '') {
            $value .= '/';
        }
        $value .= '看護師';
    } else {
        if ($value !== '') {
            $value .= '/';
        }
        $value .= 'その他';
    }

    if ($isManager) {
        if ($value !== '') {
            $value .= '/';
        }
        $value .= '管理職';
    }

    if ($isCertifiedNurse) {
        if ($value !== '') {
            $value .= '/';
        }
        $value .= '認定看護師';
    }

    return $value;
}

上記サンプルではif文を使い、条件に当てはまったら文字列を追加すると言う処理をしています。

よく見ると「/」を入れるかどうかの判定はどれも同じなのですが、文字列を順次結合するやり方をとっているため都度判定が必要になっています。

もう少し短くしたいですね。

悪くはないパターン(sample2)

# 入力値
$isNurse = true;
$isManager = false;
$isCertifiedNurse = true;

# 期待値
'看護師/認定看護師'
<?php
# 処理
public function sample2($isNurse, $isManager, $isCertifiedNurse)
{
    $values = [];

    if ($isNurse) {
        $values[] = '看護師';
    } else {
        $values[] = 'その他';
    }

    if ($isManager) {
        $value[] = '管理職';
    }

    if ($isCertifiedNurse) {
        $value[] = '認定看護師';
    }

    return implode('/', $values);
}

sample1の時に気になっていた「/」を入れる処理を、PHP標準のimplode()と言うメソッドを使って実現しています。

$valuesに入れるかどうかの判定をif文で行なっている為、ちょっと長くなりがちですね。

値が存在するものだけにフィルタリングするパターン(sample3)

# 入力値
$isNurse = true;
$isManager = false;
$isCertifiedNurse = true;

# 期待値
'看護師/認定看護師'
<?php
# 処理
public function sample3($isNurse, $isManager, $isCertifiedNurse)
{
    $values = [
        $isNurse ? '看護師' : 'その他',
        $isManager ? '管理職' : '',
        $isCertifiedNurse ? '認定看護師' : '',
    ];

    return implode('/', array_filter($values, function ($it) {
        return $it !== '';
    }));
}

$valuesに入れる処理を一括で行い、if文を三項演算子に置き換え、値がない場合は空文字を代入しています。

そして、array_filterを使って空文字を排除することで目的を達成しています。

割とスッキリしているかと思います。

一時変数を使わないパターン(sample4)

# 入力値
$isNurse = true;
$isManager = false;
$isCertifiedNurse = true;

# 期待値
'看護師/認定看護師'
<?php
# 処理
public function sample4($isNurse, $isManager, $isCertifiedNurse)
{
    return implode('/', array_filter([
        $isNurse ? '看護師' : 'その他',
        $isManager ? '管理職' : '',
        $isCertifiedNurse ? '認定看護師' : '',
    ], function ($it) {
        return $it !== '';
    })); // 「看護師/認定看護師」
}

すごく短くなりましたが、implodeとarray_filterの中で値を作っているので可読性があまり良くありません。

プログラミングに慣れてくるとついついこう書きがちですが、忘れた頃に見直すと何でこんな書き方したんだーとなっちゃうかもしれません。

応用編(sample5)

以下の条件を追加

  • 入力値が配列になり、期待値も配列となる
  • マスターテーブルからidをキーにnameを取得する
# 入力値
$params = [
    [
        'label' => 'A',
        'isNurse' => true,
        'isManager' => false,
        'isCertifiedNurse' => true,
        'categoryId' => 1,
    ],
    [
        'label' => 'B',
        'isNurse' => false,
        'isManager' => true,
        'isCertifiedNurse' => false,
        'categoryId' => 0,
    ],
    [
        'label' => 'C',
        'isNurse' => true,
        'isManager' => true,
        'isCertifiedNurse' => true,
        'categoryId' => 2,
    ],
];

# CategoryMaster::all()の取得結果
[
    [
        'id' => 1,
        'name' => 'ほげ'
    ],
    [
        'id' => 2,
        'name' => 'ふが'
    ],
];

# 期待値
[
    'A/看護師/認定看護師/ほげ',
    'B/その他/管理職',
    'C/看護師/管理職/認定看護師/ふが',
 ]
<?php
# 処理
public function sample5($params)
{
    $categoryMasterById = CategoryMaster::all()->keyBy('id'); // LaravelのEloquentモデルを使ってマスタデータを取得し、idごとの値に変換している

    return array_map(function ($param) use ($categoryMasterById) {
         $categoryMaster = array_get($categoryMasterById, $param['categoryId']);
         $categoryMasterName = (is_null($categoryMaster)) ? '' : $categoryMaster['name'];

        $values = [
            $param['label'],
            $param['isNurse'] ? '看護師' : 'その他',
            $param['isManager'] ? '管理職' : '',
            $param['isCertifiedNurse'] ? '認定看護師' : '',
            $categoryMasterName,
        ];
        implode('/', array_filter($values, function ($it) {
            return $it !== '';
        }));
    }, $params);
}

マスタデータの参照が増えたり、入出力が配列になってもそこまで複雑ではないですね。

if文を使わなくても割とシンプルにかけることがわかると思います。

こういうスニペットを多く覚えておくことで実装速度も上がりそうですね!(実行速度が上がるかどうかはまた別の問題)

ただ、個人的にはメモに貼ったスニペットをコピペして実装するのはおすすめしないですね。常に頭で考えるようにしていただきたいです。

そうすることによりどんどんブラッシュアップされ、よりよいコードが書けるようになると思います。

おまけ

応用編をsample1のように記述して見るとこうなります。

<?php
# 処理
public function sample5($params)
{
    $categoryMasters = CategoryMaster::all();
    $categoryMasterById = [];
    foreach ($categoryMasters as $categoryMaster) {
        $categoryMasterById[$categoryMaster->id] = $categoryMaster;
    }

    $values = [];
    foreach ($params as $param) {
        $value = '';
        if ($param['label'] !== '') {
            if ($value !== '') {
                $value .= '/';
            }
            $values .= $param['name']
        }

        if ($isNurse) {
            if ($value !== '') {
                $value .= '/';
            }
            $value .= '看護師';
        } else {
            if ($value !== '') {
                $value .= '/';
            }
            $value .= 'その他';
        }

        if ($isM) {
            if ($value !== '') {
                $value .= '/';
            }
            $value .= '管理職';
        }

        if ($isCertifiedNurse) {
            if ($value !== '') {
                $value .= '/';
            }
            $value .= '認定看護師';
        }

        if (isset(categoryMasterById[$param['categoryId']]) {
            $categoryMaster = categoryMasterById[$param['categoryId']]
            if (!is_null($categoryMaster)) {
                if ($value !== '') {
                    $value .= '/';
                }
                $value .= $categoryMaster['name'];
            }
        }

        $values[] = $value;
    }
}

ながいですねー。応用編が20行に対し、このメソッドは57行もあります。

短い方が良いということではないですが、1メソッドあたりの行数が多いと読む人のコストが高くなってしまうのでそこも考慮して実装したいですね。

まとめ

  • ただ短くするのではなく、後から読む人のことを考え、読みやすさも考慮しつつ実装しましょう。
  • スニペットを使う際はどこからか拾ってきたコードをコピペで使うのではなく、なぜこう書くのかを理解しながら使いましょう。
  • スニペットは時間とともに風化します。常にどうあるべきかを自分自身の頭で考え実装しましょう。

以上!

※ここにあるプログラムは動作確認をしていないのでコピペで動かない可能性があります。ご自分で考え、動作をきちんと確認しましょう。



\\一緒に良いサービスを作って成長したい、そんなメンバーを募集中です! //
919.jp