🍋

【JavaScript】コードの見通しを良くする為に意識している事10選

2023/02/19に公開
9

普段コード書いている中で見通し(可読性)を良くするために意識していることを10個列挙しました

JavaScript(TypeScript)のお話です

1.変数宣言はconstを利用する

変数を宣言するときはconstを使います。varは使わない様にします

letを使うときは限定的で、実行する関数の中で変数を再代入する必要がある時に使います

// 関数の外でのlet宣言はアンチパターン
let strValue = ""; 

const method1 = () => {
  strValue = "method1";
};
const method2 = () => {
  // 関数内、再代入する処理で宣言する
 let setValue = "ほげ";
};
// 以下、変数strValueを利用するかもしれない
// どこかで再代入されるかも分からない..

letは処理の途中で変数の値が変わる事を暗に示しているので、読む人によっては苦痛になります

再代入する必要がない場合は不必要にletを使う理由はないで、まずconstを書く様に意識しています

2.省略して書く

true,falseは省略して書きます

// よくない
if(checkBoolean === true)
if(checkBoolean === false)

// 省略できる
if(checkBoolean) 
if(!checkBoolean) 

比較する対象がundefinednull""の場合は!!に省略できます

省略しない場合↓

const hoge = "example";

const hogeFunc = (hoge) => {
  return hogehoge !== undefined && 
         hogehoge !== null && 
	 hogehoge !== ""
};

console.log(hogeFunc(hoge))

省略した場合↓

const hoge = "example";

const hogeFunc = (hoge) => {
  return !!hoge
};

console.log(hogeFunc(hoge))

!!をつけると、あらゆる型がbooleanになります(キャストされる)

特にundefinedについてはhoge !== undefinedでは誤った判定をされる可能性もあり推奨されていないので、!!を使うようにします

3.三項演算子

以下の書き方(三項演算子)でifを省略して書くことができます

条件式 ? 真の場合の値 : 偽の場合の値;

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Conditional_Operator

もちろん、三項演算子で評価した結果を代入する事もできます

const 変数  = 条件式 ? 真の場合の値 : 偽の場合の値;

この三項演算子ですが、書き方によっては2種類の書き方ができます

1つ目は、評価した結果をそれぞれ代入するパターン↓

const TRUE_MESSAGE = "trueです";
const FALSE_MESSAGE = "falseです";
const isCheckBoolean = true;
const resultList = [];

isCheckBoolean ? resultList.push(TRUE_MESSAGE) : resultList.push(FALSE_MESSAGE);

console.log(resultList); // ['trueです']

2つ目は、評価した結果を左辺に代入するパターン↓

const TRUE_MESSAGE = "trueです";
const FALSE_MESSAGE = "falseです";
const isCheckBoolean = true;
const resultList = [];

resultList.push(isCheckBoolean ? TRUE_MESSAGE : FALSE_MESSAGE);

console.log(resultList); // ['trueです']

どちらも結果は全く同じですが、可読性という意味では、宣言した変数を一度書けば済むパターン2の方が個人的には読みやすいです

4.Boolean()で書く

下記の様に、処理の中で何もせずに単純にreturn tureまたはreturn falseで返している書き方は冗長です

// よくない
(() => {
  const checkString = ""
  
  if(checkString) {
    return true
  } else {
    return false
  }
})();

// 省略できる
(() => {
  const checkString = ""
  return Boolean(checkString)
})();

三項演算子も評価値がBooelanであれば〜 ? true : falseと書くのは冗長です

const checkString = ""

// よくない
const checkHoge = checkString ? true : false

// Booleanで書く
const checkHoge = Boolean(checkString)

5.return

関数の処理の中でreturnすると、それ以降は実行されません

ifで条件を満たしていない時などに後続を実行させたくない時によく使います

(() => {
  const checkBoolean = true
  
  if(checkBoolean) return // tureの場合は後続の処理は実行されない
  
  // falseの場合だけ実行
  console.log(checkBoolean);
})();

else 〜と書く必要がないので、記述量が減ります

6.オプショナルチェーン

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Optional_chaining

オプショナルチェーン演算子はES2020で追加されました

JavaScriptでは、プロパティの値がないものにアクセスしようとするとエラーになってしまいます

(よく見るUncaught ReferenceError: 変数名or関数名 is not defined

このエラーに当たると、プログラムが止まってしまい非常に厄介です

対処法としてifでプロパティがあることを確認してからアクセスするのが定石ですが、冗長になってしまいます

const freamWork = {
  name: "react",
  version: "18",
}

// オプショナルチェーンを使わない場合
if (freamWork && freamWork.version) {
  const getVersion = freamWork.version;
  console.log(getVersion);
}

オプショナルチェーンを使うと、たとえ存在しないプロパティにアクセスしてもエラーにならず、undifinedを返してくれます

const freamWork = {
  name: "react",
  version: "18",
}

// オプショナルチェーンを使う場合
const check = freamWork?.manual
console.log(check) // undifined

利用場面としてはサーバサイドからfetchしたデータに対してよく使います

7.スプレッド構文

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Spread_syntax

スプレッド構文はES2015(ES6)に追加された構文で、配列やオブジェクトを展開してくれます

下の例では、スプレッド構文を使ってオブジェクトを展開することで、一つのまとまったオブジェクトを作成しています

const userInfo = {
  user_id: 1234,
  user_name: 'naru',
  user_email: 'naru@gmail.com',
}

const brouserInfo = {
  browser: 'chrome',
  version: '110'
}

const add = "example"

// 一つのオブジェクトにまとめています
const sumArray = {add, ...userInfo, ...brouserInfo}

console.log(sumArray)
// {add: 'example', user_id: 1234, user_name: 'naru', user_email: 'naru@gmail.com', browser: 'chrome', …}

上記の様に処理の中でオブジェクトを1つにしたい時や、バックエンドへ送るパラメータを一つにまとめたい時によく使います

8.Setオブジェクト

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Set

Setオブジェクトは、ES2015(ES6)で追加されました

new Set()を使うと、値が被らないオブジェクトを作成してくれます

const numArray = ["2","1","3","2","1","5","6","7","8"]
const result = new Set(numArray)
console.log(result) 

// 結果
// Set(7) {'2', '1', '3', '5', '6', …}

また、スプレッド構文と組み合わせる事で、配列も作れます

const numArray = ["2","1","3","2","1","5","6","7","8"]
const result =  [...new Set(numArray)]
console.log(result) 

// 結果
// (7) ['2', '1', '3', '5', '6', '7', '8']

new Setしたオブジェクトに対しては、add()メソッドで追加する事ができます

重複しているかの確認のifを書く必要がない、同じ値を追加してもエラーにならずに一意にしてくれるのでかなり使い勝手がいいです

const labels = new Set();
        
labels.add("apple");

labels.add("apple");

const labelsList = labels; // Set(1) {'apple'}

// 配列にしたい場合はスプレッド構文を使えばOK
const labelsArrayList = [...labels];  // ['apple']

また、応用して2つの配列を比較したりするときに便利です

const list_A = [3,5,7];
const list_B = [2,6,9];

// スプレッド構文を利用して、配列を一つにまとめる
const checkSumList = [...list_A, ...list_B];

// 一意にする(被りをなくす)
const resultSumList = [...new Set(checkSumList)];

// 重複がある場合は配列の要素の数は減るのでこれでチェックできる
if (resultSumList.length === checkSumList.length) {
  // 重複がない時の処理
  console.log("重複はありません")
} else {
  // 重複があった時の処理
  console.log("重複しています")
}

9.includes()

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/includes

includes()は指定された要素が配列に含まれているかどうかを調べて、あればtrueなければfalseを返すメソッドです

ECMAScript 2016 (ES7)で追加されました

例えば、"iPhone"の時はtrueそれ以外の時はfalseに判定したい処理があるとします

よくある書き方↓

const DEVICE = "iPhone"

if(DEVICE === "iPhone"){
 // trueの時の処理
}
// falseの時の処理

このような場合はincludesを使うのが便利です

まず"iPhone"を配列に収めます

1個しかない場合でも配列に入れるのがポイントです

定数化して/util等にまとめておきます

// exportでモジュール化
export const DEVICE_LIST = ['iPhone'];

使う時はインポートします

下記の様にincludesを使って変数に定数の値があるかないかチェックすることができます

// 上記をインポート
import DEVICE_LIST from "上記のファイルパス";

// includesが便利
const check_device = FRUITS_LIST.includes("iPhone");

if(check_device){
 // trueの時の処理
}

includesの良いところは、配列で何をtrue判定させたいのかの管理ができるところです

要は後で追加・削除がしやすくなります

例えば、追加で"mac"もtrue処理したい場合は、以下の様に配列に入れればOKです

export const DEVICE_LIST = ['iPhone', 'mac'];

こんな感じで、決められたものに対してチェックをしたい場合にincludes()は使いやすいです

10.map()、filter()

  • map() ECMAScript 5 (ES5)で追加

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/map

  • filter() ECMAScript 5 (ES5)で追加

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/filter

map()filter()を使うことでforEachを使わなくても簡潔にコードを書く事ができます

forEachでは、ぱっと見だと何をしているコードか分かりづらくなりがちです

// forEachを使った場合
const numbers = [1, 2, 3, 4, 5];
const doubledNumbers = [];
numbers.forEach((num) => {
  doubledNumbers.push(num * 2);
});
console.log(doubledNumbers); // [2, 4, 6, 8, 10]

// mapを使った場合
const numbers = [1, 2, 3, 4, 5];
const doubledNumbers = numbers.map((num) => {
  return num * 2;
});
console.log(doubledNumbers); // [2, 4, 6, 8, 10]

map()で実現できるのであればforEachより優先して使っています

なおfilter()の便利な使い方として、値が""など空の値があるときに.filter(v => v);とすることで、空白を取り除く事ができます

filter()を使わない場合↓

const numbers = [1, 2, "", 4];
const doubledNumbers = numbers.map((num) => {
  return num * 2;
});
console.log(doubledNumbers); // [2, 4, 0, 8] 空白の処理で0が入ってしまう

filter()を使った場合↓

const numbers = [1, 2, "", 4];
const doubledNumbers = numbers.filter(v => v).map((num) => {
  return num * 2;
});
console.log(doubledNumbers); // [2, 4, 8]

またmap()filter()で作成した配列は、元の配列とは別の配列として作成されるため、元の配列に影響を及ぼしません。forEachよりかは扱いやすいです

ただし、注意点としてfilter()で1回、map()で2回ループしている事になるので、ある意味無駄に処理しているとも言えます

Setなど他のやり方を検討しつつ、使い所では(処理量が少ないとか)積極的に使っていいと思います

最後に

注意を払っておりますが、間違い等ありましたら随時修正します

Discussion

ちゃちゃ

1つ目は、評価した結果をそれぞれ代入するパターン↓

const TRUE_MESSAGE = "trueです"
const FALSE_MESSAGE = "falseです"
const isCheckBoolean = true;
const resultList = [];

isCheckBoolean ? (resultList = TRUE_MESSAGE) : (resultList = FALSE_MESSAGE); 

2つ目は、評価した結果を左辺に代入するパターン↓

const TRUE_MESSAGE = "trueです"
const FALSE_MESSAGE = "falseです"
const isCheckBoolean = true;
const resultList = [];

resultList = isCheckBoolean ? TRUE_MESSAG : FALSE_MESSAGE;

どちらも結果は全く同じですが、可読性という意味では、宣言した変数を一度書けば済むパターン2の方が個人的には読みやすいです

constで宣言した値に直接代入してはエラーが出ると思われます。
配列なのでpushや引数を使って代入する方が良いかなと思います。

katakata

CH21様

ご指摘ありがとうございます。
おっしゃる通り、constで宣言した配列に直で値を入れようとしてますね...

push()で配列に値を追加する形に修正しました

const TRUE_MESSAGE = "trueです";
const FALSE_MESSAGE = "falseです";
const isCheckBoolean = true;
const resultList = [];

isCheckBoolean ? resultList.push(TRUE_MESSAGE) : resultList.push(FALSE_MESSAGE);

console.log(resultList); // ['trueです']
const TRUE_MESSAGE = "trueです";
const FALSE_MESSAGE = "falseです";
const isCheckBoolean = true;
const resultList = [];

resultList.push(isCheckBoolean ? TRUE_MESSAGE : FALSE_MESSAGE);

console.log(resultList); // ['trueです']
Yuhei FUJITAYuhei FUJITA

Boolean(foo) は二重否定を使って !!foo とすることが多いですね

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Logical_NOT

katakata

Yuhei FUJITA様

コメントありがとうございます
ご指摘頂くまで二重否定を意識的に使った事がなく、Booleanを多用しておりました
こちら大変勉強になりました(MDNのリンクも添付頂きありがとうございます)

本文にも追記しました

Takahiro KatoTakahiro Kato

特にundefinedについてはhoge !== undefinedでは誤った判定をされる可能性もあり推奨されていないので、!!を使うようにします

後学のために教えて頂きたいのですが、hoge !== undefined で誤った判定をされるのはどのような場合なのでしょうか??

katakata

Takahiro Kato様

コメントありがとうございます
私の認識ですが、以下の認識で記述しています

JavaScriptではundefinedをグローバルな"変数"として扱います
(変数というのがポイントです)

変数である以上、数字(100)や文字列("hoge")等のfalseと判定されない値も代入ができる訳です
なので、undefinedと比較するつもりでhoge !== undefinedと書いても、数字(100)や文字列("hoge")と比較することになり、true判定されてしまう....
という事もあり得る。という意味で書きました

ただ上記の事を言いながら、最近のブラウザではundefinedを代入したり上書きする事を禁止しているので(上記の話はJavaScriptの言語仕様のお話です)
あまり気にする必要もないのが正直な所です

Takahiro KatoTakahiro Kato

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/undefined

最近のブラウザー (JavaScript 1.8.5 / Firefox 4 以降) での undefined は、 ECMAScript 5 仕様により、設定不可、書込不可のプロパティとなります。

ご丁寧に返信頂きありがとうございます。↑の仕様ですね、私は気にしたことなかったです…
kata様が言及されているケースについて、理解できました、ありがとうございます。

kaiyu(migratoryfish)kaiyu(migratoryfish)

自分も以前にfilterしてからmapするという処理をしたときに
別の観点から違う書き方を知ったのでここにも記載します

const numbers = [1, 2, "", 4];
const doubledNumbers2 = numbers.flatMap((num) => (num ? num * 2 : []));
console.log(doubledNumbers2); // [2, 4, 8]

ES2019から追加されたflatMapを使用することで記事の以下の記載

ただし、注意点としてfilter()で1回、map()で2回ループしている事になるので、ある意味無駄に処理しているとも言えます

を解決してループ1回で処理ができます。
しかし本記事の主題である可読性から言うと慣れてないと??となる点、フィルターしてからマップする!のほうが圧倒的にわかりやすさと可読性が両立してるのでケースバイケースかなと思いました。

あとfilter()を使わない場合↓filter()を使った場合↓のコメントで
[2, 4, 0, 10] 及び[2, 4, 10] となっているところは[2, 4, 0, 8] 及び[2, 4, 8]かなと思います

Boolean()で書くや二重否定は全く知らなかったので大変勉強になりました!

katakata

kaiyu(migratoryfish)様

コメントありがとうございます
仰る通り、分かりやすさの重視で書きました

しかしflatMap()

  • 捜査が1回である
  • ワンライナーで書ける

と考えるとflatMap()でも十分に可読性があると認識しております

以上の事を考えると、両方のパターンを書くべきでした

ご指摘を頂いた事を明記し、追記させて頂きました。ありがとうございます

なお別論点での気づき、ですが

ES2019から追加されたflatMapを使用することで記事の以下の記載

この様な形で、いつからメソッドが使える様になった点も明記して頂き大変参考になりました

記事全体を見直し追記しようと思います

あとfilter()を使わない場合↓とfilter()を使った場合↓のコメントで
[2, 4, 0, 10] 及び[2, 4, 10] となっているところは[2, 4, 0, 8] 及び[2, 4, 8]かなと思います

こちらも修正しました、ありがとうございます