👀

プログラムの可読性について

2024/03/08に公開

概要

プログラマになって早数年、日々良いプログラムとは何かを考えながらプログラミングをしていました。
一概には言えませんが、良いプログラムとは、究極 バグがない プログラムを指すと思います。(要件を満たしているかなどはさておき)
もちろん開発経験のある方ならそれが無理難題だとお気付きでしょう。

では、もう少し現実的に良いプログラムにするためには何が必要か、具体的に良いプログラムとは以下のような事を指すことが多いです。

  • 可読性の高いプログラム
  • 再利用性の高いプログラム
  • エラー処理に対処したプログラム
  • テスト可能なプログラム
  • etc

今回はそれらの中で 可読性 に焦点を当てて説明してみたいと思います。
※言語はPHPで説明します。

可読性の高いプログラムとは

プログラムの可読性とは、そのプログラムコードが人間にとってどれだけ理解しやすいか、つまり読みやすく、理解しやすい状態にあるかを指す概念です。

コードの可読性が高い場合、他の開発者がそのコードを読んで理解するのが容易になり、バグの特定、修正、機能の追加が簡単になります。

可読性を高めるためには、適切な命名規則の使用、コードの整理、コメントの追加、そして一貫したコーディングスタイルの適用などがあります。

名前に意味を持たせる

初心者のプログラムを見ていると多いのですが、変数名関数名 に意味を持っていないことが多々見られます。

🙅 悪い例

$a = 1000;
$b = 10;
$c = $a + ($a * $b / 100);
echo $c;

🙆 良い例

$basePrice = 1000;
$taxRate = 10;
$totalPrice = $basePrice + ($basePrice * $taxRate / 100);

echo $totalPrice;

悪い例を見ると計算しているのは分かるけど、具体的に何をしているのか理解しづらいですよね。

逆に良い例を見てみると変数名に意味を持たせているので、消費税を含めた合計を出すプログラムというのが何となく理解しやすくなりました。

コメントと関数を使用して、もう少し可読性を上げてみましょう。

🙆 さらに良い例

/**
 * 指定された電話番号にSMS認証コードを送信する
 */
function getTotalPrice($basePrice, $taxRate)
{
    return $basePrice + ($basePrice * $taxRate / 100);
}

echo getTotalPrice(1000, 10);

コメントと関数を使用することで、関数を見るだけで何が出来るかさらに分かりやすくなりました。
関数名に意味を持たせることは何も可読性だけではなく、プログラム全体で 良いコード になることが多いです。

ちょっと抽象的すぎたので、先ほどの関数 getTotalPrice() にさらに500円以下なら送料を200円付与し、5,000円以上なら500円引きにしてみましょう。

/**
 * 消費税を含めた合計金額を取得する
 */
function getTotalPrice($basePrice, $taxRate)
{
    $totalPrice = $basePrice + ($basePrice * $taxRate / 100);
    
    // 500円以下なら送料を付与する
    if ($totalPrice <= 500) {
        $totalPrice = $totalPrice + 200;
    }

    // 合計金額が5,000円以上なら500円引きにする
    if ($totalPrice >= 5000) {
        $totalPrice = $totalPrice - 500;
    }

    return $totalPrice;
}

echo getTotalPrice(1000, 10);

どうでしょうか。 getTotalPrice() という関数名なのにプログラムに意味を持たせすぎて、名前以上の事をしている様に思えませんか?

そうです。命名規則をしっかりすることでこのような違和感に気付き、最終的にはこの様なコードになると思います。

/**
 * 消費税を含めた合計金額を取得する
 */
function getTotalPrice($basePrice, $taxRate)
{
    $totalPrice = $basePrice + ($basePrice * $taxRate / 100);

    return $totalPrice;
}

/**
 * 金額が一定以上なら送料を無料にし、一定以下であれば送料を付与する
 */
function addShippingForFreeDeliveryEligible($totalPrice)
{
    if ($totalPrice <= 500) {
        return $totalPrice = $totalPrice + 200;
    }

    return $totalPrice;
}

/**
 * 金額が一定以上なら500円引きにする
 */
function applyDiscountIfEligible($totalPrice)
{
    if ($totalPrice >= 5000) {
        return $totalPrice = $totalPrice - 500;
    }

    return $totalPrice;
}

$totalPrice = getTotalPrice(10000, 10);
$totalPriceWithShipping = addShippingForFreeDeliveryEligible($totalPrice);
$allTotalPrice = applyDiscountIfEligible($totalPrice);

echo $allTotalPrice;

命名にしっかり意味を持たせることで、可読性があるだけでなく、関数の責務を上手く切り分けることが分かったと思います。

読みやすい順序を考える

PHPには特別、プロパティやアクセス修飾子の順序に決まりはありません。
ですが、以下の例を見てみましょう。

🙅 悪い例

class PriceService
{
    private function calculationPrice()
    {
        // 処理
    }

    protected $tax;

    public function getPrice()
    {
        // 処理
    }

    protected function getTax()
    {
        // 処理
    }

    public $price;

    public function __construct(int $price)
    {
        $this->price = $price;
    }

}

どうでしょうか、関数やプロパティの順序にルールがなく、パッと見でこのクラスがどういうものか分かりづらいと思います。

では良い例を見てみましょう。

🙆 良い例

class PriceService
{
    public $price;
    
    protected $tax;
    
    public function __construct(int $price)
    {
        $this->price = $price;
    }

    public function getPrice()
    {
        // 処理
    }

    protected function getTax()
    {
        // 処理
    }

    private function calculationPrice()
    {
        // 処理
    }
}

順序をプロパティ→コンストラクタ→メソッドにしてみました。
さらにアクセス修飾子は public→protected→private にしています。

こうすることで、クラスに必要な引数やプロパティが一目で分かるようになりました。
もちろん厳密なルールはPHPでは存在しないですが、さまざまなプロジェクトを見てもこのような順序が多く見受けられます。

アクセス修飾子を public→protected→private の順にする理由はこの順序に従うことで、開発者は最も公開されている部分(public)から始まり、より制限されたアクセスレベル(protected、private)へと順に読み進めることができます。

また最も開発者がこのクラスで一番関心のあるのは、そのクラスが提供するpublicインターフェースです。
したがって、これらをクラスの上部に配置することで、他のクラスからアクセスされる可能性が高いメソッドやプロパティをすぐに見つけることができます。

早期リターン

これを理解しているしていないで、コードの品質(可読性)が大きく違ってきます。
早速、ユーザーのアクセス制御の処理で悪い例から見てみましょう。

🙅 悪い例

/**
 * ユーザーのアクセスチェック
 */
public function checkUserAccess($user) {
    $message = '';
    
    if ($user->hasAccount()) {
        if ($user->isEmailVerified()) {
            if (!$user->isAccountLocked()) {
                $message = 'アクセス成功!';
            } else {
                $message = 'アクセス失敗! アカウントがロックされています。';
            }
        } else {
            $message = 'アクセス失敗! Eメールが重複しています。';
        }
    } else {
        $message = 'アクセス失敗! アカウントが存在しません。';
    }
    return $message;
}

かなり、ifのネストが深くなっています。ここにもう一つ処理を追加する...を繰り返す波動拳が打ててしまいます。

D7NAUJeUIAAQgYt.jpeg

こうなるとわざわざ全てのifに目を通す必要があり、可読性が良いとは言えません。
そうならないためにも、早期リターンをして可読性を上げてみましょう。

🙆 良い例

/**
 * ユーザーのアクセスチェック
 */
public function checkUserAccess($user) {
    if (!$user->hasAccount()) {
        return 'アクセス失敗! アカウントがロックされています。';
    }

    if (!$user->isEmailVerified()) {
        return 'アクセス失敗! Eメールが重複しています。';
    }

    if ($user->isAccountLocked()) {
        return 'アクセス失敗! アカウントが存在しません。';
    }

    return 'アクセス成功!';
}

どうでしょうか。
これなら全ての処理を見る必要がなく、目的のメッセージをすぐに見つけられそうです。
上記の例の様に if文を書くときすぐに else や else if を利用するのではなく、
まず最初に早期リターンができないかを考えて実装してみましょう。

複雑な処理

可読性は記述量が少なければ、良いというわけではありません。
複雑な処理の場合は、無理やりワンライナーに拘らず、if文やswitch文、コメントなどで書くようにして読み手に伝わりやすい処理を書くようにしましょう。

ここでは悪い例として、三項演算子やnull合体演算子で複雑な処理をワンライナーで書いてみましょう。

🙅 悪い例

// 権限を取得し、権限が存在しないとき開発者アカウントの場合は開発者権限を付与し、存在しない場合はユーザー権限を付与する
$role = $this->getRole($user) ?? $this->isDeveloper($user) : $this->getRoleForDeveloper() ? $this->getRoleForUser();

return $role;

一目で $role に何が入るか分かりません。
ではif文を用いた書き方で見てみましょう。

🙆 良い例

$role = $this->getRole($user);

// 権限が存在しない場合
if (!$role) {
    // 開発者アカウントの場合は開発者権限を付与し、存在しない場合はユーザー権限を付与する
    if ($this->isDeveloper($user)) {
        $role = $this->getRoleForDeveloper();
    } else {
        $role = $this->getRoleForUser();
    }
}

return $role;

ネストが増えてしまいましたが、(この場合は早期リターンが使えないため仕方ない)こちらの方が $role が存在しない時の処理やコメントが読みやすくなったと思います。
上記のように複雑な処理や、ワンライナーとして長すぎる場合はif文にすることを考えてみましょう。

型宣言

静的言語の方は飛ばして大丈夫です。

PHPは動的言語ですが、型宣言が可能です。
型宣言は値が特定の型であることを保証することだけではなく、可読性向上にもつながります。
例によって、悪い例から見てみましょう。

🙅 悪い例

getVerificationCode($userId)
{
    $user = getUser($userId);

    return $this->createCode($user);
}

上記の例では引数と戻り値はintかstringが入るのかが不鮮明です。
これではDBのカラムを見たり、呼び出し元からどういう値が入るのかを見る必要があります。

では、良い例では型宣言を使ってみましょう。

🙆 良い例

getVerificationCode(int $userId): ?int
{
    $user = getUser($userId);

    return $this->createCode($user);
}

型宣言をすることで、引数が int 戻り値が int|null を求めていることが分かります。
これでDBのカラムや呼び出し元を見る必要がなくなりました。

しかし、PHPを使用していると型宣言が難しいこともあるでしょう。
その場合はPHPDocに書いてあげましょう。

/**
 * @param int $userId
 * 
 * @return int|null
 */
getVerificationCode($userId)
{
    $user = getUser($userId);

    return $this->createCode($user);
}

最後に

ここまで可読性について、焦点を当ててお話ししました。
初学者向けに書いてみましたが、あなたのプログラムのお役に立てたら幸いです。

では、良いコーディングライフを!

Discussion