📖

【リーダブルコード】関係ない下位問題を抽出してコードを読みやすくする

に公開

🎯 上位レベルの目的に集中するために、関係のない処理を分離する

以下のJavaコードでは、「指定された緯度経度に最も近い地点を探す」という主目的があるものの、ループ内部の処理が複雑な数式により埋もれていて読みづらくなっています。

public static Location findClosestLocation(double lat, double lng, List<Location> locations) {
    Location closest = null;
    double closestDistance = Double.MAX_VALUE;

    for (Location location : locations) {
        double latRad = Math.toRadians(lat);
        double lngRad = Math.toRadians(lng);
        double lat2Rad = Math.toRadians(location.getLatitude());
        double lng2Rad = Math.toRadians(location.getLongitude());

        double distance = Math.acos(
            Math.sin(latRad) * Math.sin(lat2Rad) +
            Math.cos(latRad) * Math.cos(lat2Rad) * 
            Math.cos(lng2Rad - lngRad)
        );

        if (distance < closestDistance) {
            closest = location;
            closestDistance = distance;
        }
    }

    return closest;
}

このコードは確かに機能しますが、本質的なロジック(最も近い場所を見つける)を理解する上でのノイズが多く含まれています。


sphericalDistance() という関数へ切り出す

距離の計算は上位目的とは無関係な**「下位問題」です。これを関数として切り出すことで、コード全体の可読性・再利用性・テスト容易性**が向上します。

public static double sphericalDistance(double lat1, double lng1, double lat2, double lng2) {
    double latRad1 = Math.toRadians(lat1);
    double lngRad1 = Math.toRadians(lng1);
    double latRad2 = Math.toRadians(lat2);
    double lngRad2 = Math.toRadians(lng2);

    return Math.acos(
        Math.sin(latRad1) * Math.sin(latRad2) +
        Math.cos(latRad1) * Math.cos(latRad2) *
        Math.cos(lngRad2 - lngRad1)
    );
}

public static Location findClosestLocation(double lat, double lng, List<Location> locations) {
    Location closest = null;
    double closestDistance = Double.MAX_VALUE;

    for (Location location : locations) {
        double distance = sphericalDistance(lat, lng, location.getLatitude(), location.getLongitude());

        if (distance < closestDistance) {
            closest = location;
            closestDistance = distance;
        }
    }

    return closest;
}

このように関数を分けることで、「何をしているか」よりも「なぜやっているか」に集中できるようになり、コードの読み手にとって非常に親切です。また、sphericalDistance()はユニットテストも容易で、他の場面での再利用も可能です。


💡 コードの抽象化は「読みやすさ」と「テスト性」を高める

例えば、暗号化・復号化、認証、複雑な数式などは、最初は理解しながら書く必要があるものの、将来的には抽象化しておくことで以下の利点があります:

  • 再利用しやすい
  • ユニットテストしやすい
  • インプットとアウトプットだけを意識すればよくなる

例えばログイン処理を関数で分離していなければ、HTTP経由でしかテストできず、ユニットテストでは対応が難しくなります。


⚠️ やりすぎには注意!

『リーダブルコード』では、「関係のない下位問題を抽出すべきだが、過剰に分けすぎると逆に読みにくくなる」とも述べています。

極端に細かく分割された関数を追いかけていくうちに、かえって理解が困難になることもあります。抽出する際には、「その関数が本当に再利用可能か?」「インプットとアウトプットが明確か?」といった点を意識することが大切です。


📘 第10章のまとめ

  • 無関係な処理(下位問題)は別の関数として抽出する
  • 関数化によってテストしやすく、再利用可能なコードになる
  • ただし過剰な関数化は逆効果。バランスが大事
  • コードを書く際は「上位目的に集中できるか」を意識する

この章は、ただ動くコードを書くのではなく、「読みやすく、保守しやすく、バグが少ないコード」を書くためにどう設計すべきかを考える良いきっかけになりました。次回からは関数を作るとき、「これは下位問題か?」と常に問いながら設計していきたいと思います。

Discussion