はじめに
「リーダブルコード」第10章 無関係の下位問題を抽出する
で、紹介されていた関数(findClosestLocation
)をTypeScriptにしたので、そのメモです。
コード
Before
const radians = (degree: number) => degree * (Math.PI / 180);
interface Place {
lat: number;
lng: number;
}
// 与えられた緯度経度に最も近い 配列 "placeList" の要素を返す
const findClosestLocation = (lat: number, lng: number, placeList: Place[]) => {
let closestDist = Number.MAX_VALUE;
let closestPlace;
placeList.forEach((place: Place) => {
const latRad = radians(lat);
const lngRad = radians(lng);
const lat2Rad = radians(place.lat);
const lng2Rad = radians(place.lng);
const dist = Math.acos(
Math.sin(latRad) * Math.sin(lat2Rad) +
Math.cos(latRad) * Math.cos(lat2Rad) * Math.cos(lng2Rad - lngRad)
);
// 算出した距離が他の一番近い距離より近ければ更新
if (dist < closestDist) {
closestPlace = place;
closestDist = dist;
}
});
return closestPlace;
};
// ちなみに返り値は名古屋駅の緯度経度
console.log(
// 第一第二引数は福岡駅の緯度経度
findClosestLocation(33.590188, 130.420685, [
{ lat: 35.1706431, lng: 136.8816945 }, // 名古屋駅の緯度経度
{ lat: 35.6809591, lng: 139.7673068 } // 東京駅の緯度経度
])
);
Number.MAX_VALUE プロパティは、 JavaScript において表すことが可能な最大の数値です。
After(リファクタ後)
下位問題として切り出された関数(sphericalDistance
)の幾何学計算は、球面余弦定理で球面上の距離を計算しています。
参考
const radians = (degree: number) => degree * (Math.PI / 180);
interface Place {
lat: number;
lng: number;
}
const sphericalDistance = (
lat1: number,
lng1: number,
lat2: number,
lng2: number
) => {
const lat1Rad = radians(lat1);
const lng1Rad = radians(lng1);
const lat2Rad = radians(lat2);
const lng2Rad = radians(lng2);
const dist = Math.acos(
Math.sin(lat1Rad) * Math.sin(lat2Rad) +
Math.cos(lat1Rad) * Math.cos(lat2Rad) * Math.cos(lng2Rad - lng1Rad)
);
return dist;
};
// 与えられた緯度経度に最も近い 配列 "placeList" の要素を返す
const findClosestLocation = (lat: number, lng: number, placeList: Place[]) => {
let closestDist = Number.MAX_VALUE;
let closestPlace;
placeList.forEach((place: Place) => {
const dist = sphericalDistance(lat, lng, place.lat, place.lng);
// 算出した距離が他の一番近い距離より近ければ更新
if (dist < closestDist) {
closestPlace = place;
closestDist = dist;
}
});
return closestPlace;
};
// ちなみに返り値は名古屋駅の緯度経度
console.log(
// 第一第二引数は福岡駅の緯度経度
findClosestLocation(33.590188, 130.420685, [
{ lat: 35.1706431, lng: 136.8816945 }, // 名古屋駅の緯度経度
{ lat: 35.6809591, lng: 139.7673068 } // 東京駅の緯度経度
])
);
感想
つまり、「関数に直接関係ない部分を別の関数にする」ということですね。
そうすることで、可読性が上がり、再利用もでき、単体テストもできるのでメンテナンスしやすいですね。
共通部品を作る時は、「こんな引数を受け取るのもあったら他にも別件で誰かが使うかも」と考えて作れるとより汎用性が上がるなと思いました。
無関係の下位問題を汎用的な関数として切り出して残ったのが、その機能固有の本質的なコード、上位問題です。なので、上位問題と下位問題を切り分けてコードを書き分けるようになりたい。
要約版
参考
これの第10章