はじめに
javascriptを利用して開発している方は多くいるかと思いますが、その中でクリーンなコードを意識して作業している人はどれくらいいるでしょうか?
クリーンなコードとは可読性を重視し、再利用やリファクタリングが可能なコードのことを指しています。
どのような業務でも1度書いて終わりということはほとんどなく、PDCA サイクルのようにより良くしていくためにコードを編集することがほとんどです。
本記事では、javascriptにおいて、より可読性や保守性が高くなるコードの書き方についてまとめます。
変数
変数の宣言
変数の名前は基本的に意味のある名前を使用します。
javascriptでも基本はキャメルケース(文を一語に繋げて表記する際に、各構成語の先頭を大文字にする方式)が無難です。
// × 良くない例
const a = "Taro";
const b = "test@example.com";
const c = 30;
const d = true;
// ○ 良い例
const firstName = "Taro";
const email = "test@example.com";
const age = 30;
const isAllow = true;
※ bool名については基本的に「is」や「did」、「has」などを先頭に持ってきます。
const isAllow
const didSubscribe
const hasLinkedText
無駄なコンテキストを追加しない
定義しているオブジェクト名に対して、すでにコンテキストが定義されている場合は、その配下のオブジェクト名に既にわかっている情報を追加しない方が良いとされています。
下記例では、userという変数オブジェクトに対して、良くない例では毎回userというコンテキストを利用しています。
// × 良くない例
const user = {
userId: "A000001",
userName: "KUNI Taro",
userEmail: "test@example.com",
};
console.log(user.userName);
// ○ 良い例
const user = {
id : "A000001",
name: "KUNI Taro",
email: "test@example.com"
}
console.log(user.name)
定数値を直接関数などに対してハードコーディングしない
いざコードを見直すと、設定されている値がなんの値なのかわからないケースがあります。
そういったケースを避けるため定数値についてはあらかじめ意味のある変数名で宣言しておき、関数等で再利用する場合はその変数名を利用するようにします。
// × 良くない例
setTimeout(clearSession, 30000);
// ○ 良い例
const SESSION_TIMEOUT_MS = 30 * 1000; // 30分をmsに変換
setTimeout(clearSession, SESSION_TIMEOUT_MS);
関数
関数名には理解しやすい名前を使用する
各関数名には処理の名前を使うと思いますが、通常は動作を表す「動詞」の形式なります。
ただし、ブール値については例外で「はい」または「いいえ」のヶ意識で関数名を指定します。
// × 良くない例
function calendar() {
// ...
}
function phone() {
// ...
}
// ○ 良い例
function calendarCalculater() {
// ...
}
function isCorrectData(value) {
// ...
}
関数の引数に対して、デフォルト値を設定する
デフォルトの引数は関数内部で定義するのではなく、関数の引数に指定する方が良いとされています。
// × 良くない例
function calendarCalculater(dir) {
const baseDirectory = dir || "./";
// ...
}
// ○ 良い例
function calendarCalculater(dir = "./") {
// ...
}
関数の引数に設定する数を制限する
引数の数が多いと、それだけで可読性が下がります。
大体0、1、2個程度を持つのが良いとされており、3つ以上あると過剰な引数と判断されます。
基本的に複数の引数を指定したい場合は、関連する情報を構造体として渡してあげるのが良いかと思います。
// × 良くない例
function calendarCalculater(title, name, value, id, No) {
// ...
}
calendarCalculater("test", "name1", "value1", "A000001", 1);
// ○ 良い例
function calendarCalculater({title, name, value, id, No}) {
// ...
}
const dataConfig = {
title: "test",
name: "name1",
value: "value1",
id: "A000001",
No: 1
};
calendarCalculater(dataConfig);
一つの関数内で複数の別の処理を実行しない
関数は、1つの関数に対して実行する処理は1つというルールにより、複雑さが軽減されて可読性が向上します。
大体1関数20行から30行程度に納めておくと良いかと思います。
// × 良くない例
function pingUsers(users) {
users.forEach((user) => {
const userRecord = database.lookup(user);
if (!userRecord.isActive()) {
ping(user);
}
});
}
// ○ 良い例
function pingInactiveUsers(users) {
users.filter(!isUserActive).forEach(ping);
}
function isUserActive(user) {
const userRecord = database.lookup(user);
return userRecord.isActive();
}
フラグ値を引数に指定しない
引数にフラグを指定し、関数内で場合分けするケースがありますが、それぞれの場合ごとに関数化すると、とても綺麗なコードになります。
// × 良くない例
function createFile(name, isFlg) {
if (isFlg) {
fs.create("./public/${name}");
} else {
fs.create(name);
}
}
// ○ 良い例
function createBaseFile(name) {
fs.create(name);
}
function createPublicFile(name) {
fs.create("./public/${name}");
}
同じような処理の関数を繰り返し作らない
同じような処理コードを書くのは可読性や保守性に良くありません。ロジックに変更があるたびに複数箇所を修正しないといけない手間が発生してしまいます。
// × 良くない例
fuction renderPrivateUsersList(privateUsers) {
privateUsers.forEach((privateUser) => {
const id = privateUser.getUserId();
const name = privateUser.GetUserName();
const privateDetail = privateUser.GetPrivateDetail();
render({id, name, detail});
});
}
function renderPublicUsersList(publicUsers){
publicUsers.forEach((publicUser) => {
const id = publicUser.getUserId();
const name = publicUser.GetUserName();
const publicDetail = publicUser.GetPublicDetail();
render({id, name, publicDetail});
});
}
// ○ 良い例
function renderAllUsersList(allUsers) {
allUsers.forEach((allUser) => {
const id = allUser.getUserId();
const name = allUser.GetUserName();
const data = { id, name };
switch (allUser.type) {
case "public":
data.publicDetail = allUser.GetPublicDetail();
break;
case "private":
data.privateDetail = allUser.GetPrivateDetail();
break;
}
render(data);
});
}
データを上書きせず元のデータを残しておく
変数のデータがころころ変わってしまうと、変わった情報に対して修正漏れが発生したりするケースがあります。
そのようなケースを回避するために、元のデータは残し、変更があった場合は新しく変数を設定すると良いかと思います。
// × 良くない例
let date = "2022-04-18";
function splitDate() {
date = date.split("-");
}
splitDate();
console.log(date); // ['2022', '04', '18']
// ○ 良い例
function splitDate(date){
return date.split("-");
}
const baseDate = "2022-04-18";
const editDate = splitDate(baseDate);
console.log(baseDate); // '2022-04-18'
console.log(editDate); // ['2022', '04', '18']
条件
条件指定の場合は否定条件を利用しない
たとえば、Aではない場合に〜を処理するという条件を記載するのではなく、Bの場合に〜を処理するのように肯定から入る条件の方が、理解しやすいと考えます。
// × 良くない例
function isCorrectData(data){
// ...
}
if(!isCorrectData(data)) {
// ...
}
// ○ 良い例
function isCorrectData(data){
// ...
}
if(isCorrectData(data)) {
// ...
}
省略形を利用する
条件に「true」などを書かなくても、基本的にわかる場合はコード数が増えるだけなので無意味と考えています。
// × 良くない例
if(isCollect === true) {
// ...
}
if(name !== "" && id !== undefined) {
// ...
}
const isActiveUser = user.isVerified() && user.isActive() ? true : false;
// ○ 良い例
if(isCollect){
// ...
}
if(!!name) {
// ...
}
const isActiveUser = user.isVerified() && user.isActive();
条件を入れ子にしないようにする
条件の中に条件がはいるような入れ子状態の場合、可動性がかなり下がります。また、副作用などが発生するケースもあります。
// × 良くない例
function editUser(type) {
if(type) {
if(type.isActive()) {
return "a";
} else {
throw new Error("Not active.")
}
} else {
throw new Error("No type");
}
return "";
}
// ○ 良い例
function editUser(type){
if(!type) throw new Error("No type");
if(!type.isActive()) throw new Error("Not active");
return "a";
}
switchの利用よりObjectリテラルやマップを利用した方が見やすい
// × 良くない例
const getColorByStatus = (status) => {
switch (status) {
case "success":
return "green";
case "failure":
return "red";
case "warning":
return "yellow";
case "loading":
default:
return "blue";
}
};
// ○ 良い例
const statusColors = {
success: "green",
failure: "red",
warning: "yellow",
loading: "blue",
};
const getColorByStatus = (status) => statusColors[status] || "blue";
エラーハンドリング
エラーハンドリングはとても重要です。どこでどのようなエラーが発生したのかわかりやすくするために、必ずハンドリング処理は必要となります。
基本的に例外エラーをキャッチするために「try-catch」は書くようにしましょう。
try {
// ...
} catch (e) {
// 一般的な例外エラーはコンソール出力
console.error(e)
// 通知などユーザに見える形でのエラー出力
alertError(e);
// サーバ側へのレポート出力
reportError(e);
// 最終的にカスタムエラーを出力
throw new CustomError(e);
}
コメント
関数や変数、クラスに対しては必ずコメントを記載するようにしましょう。
特にクラスや関数については、どのような処理なのか、引数や返り値についてを記載します。
/**
* ユーザ情報を取得する
*
* @param {boolean} type: ユーザタイプ
* @return {map} users: 取得したユーザ情報マップ.
*/
function getUsers(type) {
// ...
}
最後に
本記事では、javascript開発においての可読性や保守性を担保するための書き方についてまとめてみました、
長期的に管理することを考慮し、是非参考にしてみてください。