概要
の続きです。TypeScriptで記述しています。
目次
- 導入
- 命名規則
- より綺麗にする
- ロジック
- 大きなコード作成(<-ここ)
- 最後に(<-ここ)
無関係の問題を抽出
下位問題を抽出し、小さなメソッドにすることで以下のメリットがある
- メソッドが小さくなるため可読性が上がる
- 単体でテスト可能
- 他の関数でも利用可
下位問題の抽出:例題 1
「与えられた地点から最も近い場所を見つける」 findClosestLocation メソッドについて考える
// 与えられた軽度緯度に最も近い"placeList"の要素を返す
const findClosestLocation = (lat: number, lng: number, placeList: Place[]) => {
let closestDist = Number.MAX_VALUE;
let closedPlace;
placeList.forEach((place: Place) => {
const latRad = radians(lat);
const lngRad = raians(lng);
const lat2Rad = radians(place.lat);
const lng2Rad = raians(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;
};
「2つの地点の球面距離を算出する」コードが下位問題
// 与えられた軽度緯度に最も近い"placeList"の要素を返す
const findClosestLocation = (lat: number, lng: number, placeList: Place[]) => {
let closestDist = Number.MAX_VALUE;
let closedPlace;
placeList.forEach((place: Place) => {
// ここから
const latRad = radians(lat);
const lngRad = raians(lng);
const lat2Rad = radians(place.lat);
const lng2Rad = raians(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;
};
「2つの地点の球面距離を算出する」コードを関数に抽出
const sphericalDistance = (
lat1: number,
lng1: number,
lat2: number,
lng2: number
) => {
const lat1Rad = radians(lat1);
const lng1Rad = raians(lng1);
const lat2Rad = radians(lat3);
const lng2Rad = raians(lng3);
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 closedPlace;
placeList.forEach((place: Place) => {
const dist = sphericalDistance(lat, lng, place.lat, place.lng);
if (dist < closestDist) {
closestPlace = place;
closestDist = dist;
}
});
return closestplace;
};
下位問題の抽出:例題 2
「API よりユーザ情報のレスポンス取得」する fetchUser を考える
async function fetchUser(url: Url) {
// { resisterData: "date 20211221 userId 8898" }
const result = await axios.get(url);
const dataList = result.data.split(" ");
// "{resisterDate: 20211221,userId: 8898,}"に整形
let str = "{\n";
dataList.forEach((data: string, index: number) => {
str += ` ${data}`;
if (index % 2) {
str += ",\n";
}
if (!(index % 2)) {
str += ":";
}
});
str += "}";
return str;
}
「コードを整形する」コードが下位問題
async function fetchUser(url: Url) {
// { resisterData: "date 20211221 userId 8898" }
const result = await axios.get(url);
// ここから
const dataList = result.data.split(" ");
// "{resisterDate: 20211221,userId: 8898,}"に整形
let str = "{\n";
dataList.forEach((data: string, index: number) => {
str += ` ${data}`;
if (index % 2) {
str += ",\n";
}
if (!(index % 2)) {
str += ":";
}
});
str += "}";
// ここまで
return str;
}
「コードを整形する」コードを関数に抽出
function formatPretty(result) {
const dataList = result.data.split(" ");
let str = "{\n";
dataList.forEach((data: string, index: number) => {
str += ` ${data}`;
if (index % 2) {
str += ",\n";
}
if (!(index % 2)) {
str += ":";
}
});
str += "}";
return str;
}
元のコードはより簡潔に表現できるようになった
async function fetchUser(url: Url) {
const result = await axios.get(url);
return formatPretty(result);
}
タスクを小さくする
一度に複数のことをすると理解しにくいため、1 つにするための方法をみる
以下の例を考える
投票用ウィジェットを考える。ユーザは「賛成」、「反対」、「無投票」の 3 つの選べる。
選択したら、以下の関数が呼ばれて合計投票数を計算することを考える。
- 無投票 - total: 4
- 賛成 - total: 5
- 反対 - total: 3
function voteChanged(oldVote: string, newVote: string) {
let score = getScore();
if(newVote !== oldVote) {
if(newVote === 'Up'){
score += (oldVote === 'Down' ? 2 : 1)
} else if(newVote === 'Down') {
score -= (oldVote === 'Up' ? 2 : 1)
} else if(newVote === '') {
score += (oldVote === 'Up' ? -1 : 1)
}
}
setScore(score);
}
タスクを分解する
-
oldVote
とnewVote
を解析 -
score
を更新
function voteChanged(oldVote: string, newVote: string) {
let score = getScore();
if(newVote !== oldVote) {
if(newVote === 'Up'){
score += (oldVote === 'Down' ? 2 : 1)
} else if(newVote === 'Down') {
score -= (oldVote === 'Up' ? 2 : 1)
} else if(newVote === '') {
score += (oldVote === 'Up' ? -1 : 1)
}
}
setScore(score);
}
-
oldVote
とnewVote
を解析 について考える
解析をして対応する数値を返す関数を作成する
// voteの値を解析し、適切な値を返す
function parseVoteValue(vote: string) {
if (vote === "Up") {
return 1;
}
if (vote === "Down") {
return -1;
}
return 0;
}
元のコードをparseVoteValue
を利用し手書き換える
元のコードは 2. score
を更新 を担う
function voteChanged(oldVote: string, newVote: string) {
let score = getScore();
// 前の値を削除
score -= parseVoteValue(oldVote);
// 新しい値を追加
socre += parseVoteValue(newVote);
setScore(score);
}
オブジェクトから値を抽出
locationInformation
のオブジェクトから「prefecrue(県), country(国)」の値を返す関数getPlace
を考える
ルール
- 県を選ぶ時、値がなければ市 → 区町の順番で選ぶ
- 県、市、区町がない場合、なんでもない場所をデフォルトとして代入
- 国がない場合、「地球」をデフォルトとして代入
type LocationInformation = {
prefecrure: string; //"大阪府";
city: string; //"大阪市";
town: string; //"北区";
country: string; //"日本";
};
悪い例
function getPlace(location: LocationInformation) {
let place = location.prefecture;
if (!place) {
place = location.city;
}
if (!place) {
place = location.town;
}
if (!place) {
place = "どこでもない場所";
}
if (location.country) {
return place + "," + location.country;
}
return place + ", 地球";
}
タスクを分解
- location から値を抽出
- 県の優先准尉を調べ、なければデフォルト値を代入
- 国を取得し、なければデフォルト値を代入
- 値を更新
このように処理の順序を文字で起こすことで整理できる
1.location から値を抽出
// 値を抽出
const country = location.country;
const prefecture = location.prefecture;
const city = location.city;
const town = location.town;
2.県の優先准尉を調べ、なければデフォルト値を代入
const firstHalf = prefecture || city || town || "なんでもない場所";
3.国を取得し、なければデフォルト値を代入
const secondHalf = country || "地球";
優先順位を求めるタスクは短絡評価を使うとすっきり!
4.値を更新
const place = firstHalf + secondHalf;
まとめる
function getPlace(location: LocationInformation) {
// location から値を抽出
const country = location.country;
const prefecture = location.prefecture;
const city = location.city;
const town = location.town;
// 県の優先准尉を調べ、なければデフォルト値を代入
const firstHalf = prefecture || city || town || "なんでもない場所";
// 国を取得し、なければデフォルト値を代入
const secondHalf = country || "地球";
// 値を更新
const place = firstHalf + secondHalf;
return place;
}
- ここで国が日本の場合は、国のところは区町を入れるとの仕様変更があった
- 容易に対応できることがわかる
function getPlace(location: LocationInformation) {
// location から値を抽出
const country = location.country;
const prefecture = location.prefecture;
const city = location.city;
const town = location.town;
if (country === "日本") {
// 県の優先順位を調べ、なければデフォルト値を代入
const firstHalf = prefecture || city || "なんでもない場所";
// 国を取得し、なければデフォルト値を代入
const secondHalf = town || country;
// 値を更新
const place = firstHalf + secondHalf;
return place;
}
// 県の優先順位を調べ、なければデフォルト値を代入
const firstHalf = prefecture || city || town || "なんでもない場所";
// 国を取得し、なければデフォルト値を代入
const secondHalf = country || "地球";
// 値を更新
const place = firstHalf + secondHalf;
return place;
}
短いコードを書く
コードを悪化ないことを知り、小さく保つことで、可読性があり、シンプルなコードとなる
不要なコメントアウトは含めない
後で使うかともとコメントアウトでコードで残すのは NG
/* 後で使用するかもしれないから残そう! */
// function getUser(url: string) {
// try {
// const result = await axios.get(url);
// return result;
// } catch {
// throw new Error("失敗");
// }
// }
標準メソッド・ライブラリ・オープンソースを利用
作りたい機能がオープンソースにないか調べ、あれば使う
標準メソッド
- 配列
- forEach
- map
- include
- length
- オブジェクト
- Object.assign
- Object.create
- Object.defineProperty()
標準ライブラリ
- Math
- Date
オープンソース
-
Moment.js
- 時間を扱うライブラリ
-
date-fns
- Moment.js をさらに使いやすくしたライブラリ
-
is.js
- フラグ等を扱うライブラリ
-
underscore.js
- 大量のデータ処理をする際に、よく使うロジックをとても簡単に書くことができるライブラリ
-
highcharts
- 図表が作れるライブラリ
-
Flatpickr
- 日時入力カレンダーライブラリ
オープンソースの利用
Moment.js
2012/10/20 15:30:50 という日時の文字列から 2012 年 10 月 20 日 15 時 30 分のような形式で出力したい
// bad標準ライブらりの場合
const d = new Date("2012/10/20 15:30:50");
const formatDate =
d.getFullYear() +
"年" +
(d.getMonth() + 1) +
"月" +
d.getDate() +
"日 " +
d.getHours() +
"時" +
d.getMinutes() +
"分";
// good (Moment.jfを利用した場合)
const m = moment("2012/10/20 15:30:50");
const formatDate = m.format("YYYY年MM月DD日 HH時mm分");
まとめ
- 複雑なコードの場合、タスクを文字で列挙することが大事
- コードはクラス、メソッド、段落のいずれかには分けることができる
最後に
自然に読みやすいコードを書けるようになるための 3 ステップ
- 実際にやる
- 当たり前にする
- コードで伝える
実際にやる
実際にやるとぶつかる?
自分の書いているコードにどのように適用したらいいのかがわからない・・・
- 解決策
- この資料を見直す
- MR する前に全体を俯瞰する
- 第三者にレビューしてもらう
当たり前にする
当たり前にするには?
自分だけきれいに書いても他の人が汚いコードだと、そのプロジェクトのコードは変わらない
- どうする?
他の人のコードを積極的にレビューし、新しく入ってくるコードはすべてきれいにすることが大事
コードで伝える
実際にレビューする際はコード例を書こう
- 何がいいのか?
- 言葉で言われるよりより実感がわき、理解しやすい
- 人に伝えるには自然とわかりやすいコードを書く
- レビューを意識してコミット
- フォーマットと修正を 1 つのコミットにまとめない
1 つのことを 1 つだけ