はじめに
MDNのドキュメントを眺めていたら、JavaScript のサンプルコードの作成ガイドラインという章を見つけました。第三者にコードを公開するような人向けにまとめられたものですが、正しい書き方の実践としてみることができるので、クローズドなプロジェクトの中でも使えるかもしれません。ただ、モノによってはチームで決めた慣習と食い違うこともあるかもしれないので、採用はケースバイケースになると思います。
上記のページが紹介された記事があまり見当たらなかったので、折角なので共有してみようと思います。あと、ところどころ補足を付け加えています。
対象読者
基本的には初心者向け
ただ、幾つかは中級者の方でもためになるものがあるかも?
規則の一般性
- ☆:よく見かける書き方。一般的
- ☆☆:あまり見かけないかもしれない書き方。あるいは、意識的には規則づけられてなかった書き方
- ☆☆☆:クローズドなプロジェクトの開発での慣行とは食い違う可能性のある書き方。好みで別れていたものを統一化する状況になるかも
(独断による分け方ですのでご承知おきください)
配列
配列の定義☆
配列の定義では、リテラルを用いるるようにしてください。
const list = []
わざわざコンストラクタを使わないようにしましょう。
const list = new Array(length)
new Arrayの挙動と使い所さん
const list = new Array(length)
で定義されたlistには、”空のスロット”が入っています。これはundefinedとは違い、このままではmapメソッドを使って配列の値をどうこう操作することができません。もし0で初期化したいならば、new Array(length).fill(0)
としてください。
これは例えば、Golangにおいてvar list [12]int
でlistを初期化したとき、listに12個の0が入るのに相当します。
コンストラクタを使って配列を定義するのは、0から1刻みで昇順の数字が入った配列を作るのに便利です。Pythonでは、lis = [i for i in range(11)]
とすれば楽に[0, 1, 2, 3, ...]
と言う値が入ったlisを定義できるのですが、JavaScriptではリスト内包表記がありません。
その代わり、new Arrayを使ってconst list = new Array(11).fill(null).map((_, index) => index)
とすれば同じような配列が作れます。
なお、nullをfillしているのは、配列の中身の値には興味がなく代わりにindex(添え字)を使うためです。別に0をfillしてもいいですが、初期化する値に意味があるわけではないことを明示するためにnullをfillしました。
コメント
単一コメント☆
スラッシュとコメントの間には空白を入れ、文末にはピリオド又は句点を付けません。
// これがよく書かれたコメントです。句点を付けないでください
インデントレベルの最初の行にコメントがつかないときは、空白行を設けてコードブロックを作ってください。
class Member {
// declaration without default values
private name: string;
private age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
// getter functions
getName(): string {
return this.name;
}
getAge(): number {
return this.age;
}
// setter functions
setName(name: string) {
this.name = name;
}
setAge(age: number) {
this.age = age;
}
// complement function
greeting(): string {
const greetWord = `My name is ${this.name}, I'm ${this.age} years old.`;
return greetWord;
}
}
複数行のコメント☆☆
コメントは短い方が理想的ですが、長いコメントではすべての文の先頭に//
を追加してください。/* … */
は使わないでください。
// This funciton generates lines of 'Commando'
// One sentence is returned from the function.
// You can change the name of an antagonist, Sully.
// Let's get it tried!
function qotesOfCommando(name: string): string {
const generalQuote1 = `Matrix: Remember, ${name}, when I promised to kill you last? \n`
const soldierQuote = `${name}: That's right, Matrix! You did! \n`
const generalQuote2 = `Matrix: I lied.`
return generalQuote1 + soldierQuote + generalQuote2
}
console.log(qotesOfCommando("Sully"))
これはダメ
/*
This funciton generates lines of 'Commando'
One sentence is returned from the function.
You can change the name of an antagonist, Sully.
Let's get it tried!
*/
function qotesOfCommando(name: string): string {
const generalQuote1 = `Matrix: Remember, ${name}, when I promised to kill you last? \n`
const soldierQuote = `${name}: That's right, Matrix! You did! \n`
const generalQuote2 = `Matrix: I lied.`
return generalQuote1 + soldierQuote + generalQuote2
}
console.log(qotesOfCommando("Sully"))
サンプルコードを書くにあたっては基本的に/* … */
を使わないようですが、引数の省略を表すときには使うこともあるようです。
取りえる引数を用いないことを表すために使うようです。
array.map((value /* , index, array */) => {
// …
});
関数
サンプルコードを書く際に可能な限り以下のような書き方にするとよいと紹介されてありました。
関数の書き方は三つほどもあるので、どの時に何を使うかはクローズドなプロジェクトでも統一しておいても良いかもしれません。
関数の宣言☆☆☆
関数の定義は関数宣言を用いて、関数式やアロー関数を用いるのは避けてください。
よい例
function declareFunction(): void {
console.log("function")
}
悪い例
const declareFunction = function(): void {
console.log("function")
}
悪い例
const declareFunction = () => console.log("function")
関数宣言の巻取り
JavaScriptは通常コードを上から読んでいくので、定義前の変数やクラスを用いようとするとエラーが起こります。
ただ、関数宣言は「巻き取り」が起こるため、後の方で宣言したものを宣言前の段階で使ってもエラーが起こらないという特徴があります。
これは、スクリプトにおいて、「よく編集される関数を上部に、そうではないヘルパー関数などは下部に置くことで、認知的負荷やスクロールの手間を減らせる」という点で有用です。
"use client"
import { react } from "React"
// UIを担当するこの関数はよく編集される
export default function Home() {
const path = getPath()
return (
<>
<p>{path}</p>
</>
)
}
// このヘルパー関数が修正されることはあまりない
function getPath(): string {
// ...
}
アロー関数の使い時と使い方☆
無名関数をコールバックとして用いる場合、アロー関数を使用して簡潔にしましょう。
また、アロー関数を用いる場合は、可能な限り暗黙の返り値(式本体とも呼ばれます)を使用してください。
const members = [
{name: "Kirby", age: 30},
{name: "Meta Knight", age: 30},
{name: "Magolor", age: 14},
];
const memberAges = members.map(member => member.age);
const youngestAge = Math.min(...memberAges);
const youngestMembers = members.filter(member => member.age === youngestAge).map(member => member.name);
console.log(youngestMembers)
// ["Magolor"]
悪い例1:わざわざ式宣言は用いない。
const memberAges = members.map(function(member) {
return member.age
})
悪い例2:一文で済むならわざわざ関数ブロックを作ったりしない。
const memberAges = members.map(member => {
return member.age
})
React:関数コンポーネントはどう定義するべきか?
この話をReactに拡張するならば、関数コンポーネントを定義するときは関数宣言でやるのがベターと言う結論になります。
人によってはアロー関数で変数に代入して定義していたりしますが、もしこれを揃えたいなら関数宣言に合わせる方が有力でしょう。
もう少しいうと、(これは個人の感想なのですが)export するのも宣言時でよいかもしれません。
関数コンポーネントの定義時にexport defaultをしていない場合、「その関数コンポーネントが子コンポーネントとして、下で定義された親コンポーネントに使われているかもしれない」と考えなければならないからです。感覚としては、letで変数宣言されたときと似たような感じでしょうか(後で変更されるかもしれないと身構える必要がある)。
exportするかどうかを一発で理解したいので、宣言時にexportする方が無難でしょう。
例
・最後にexport
function UI1() {
return <></>
}
function UI2() {
return (
<>
{<UI1 />}
</>
)
}
export default UI2 // 最後まで見ないと結局何がexportされるか分からない。
・最初にexport
export default function UI1() {
return (
<>
{<UI2 />}
</>
)
}
function UI2() {
return <></>
}
ループと制御
Pythonならイテラブルのループはfor in
で、Golangならコレクションのループはfor;;
ないしfor range
構文など使えるものが限定的です。
しかし、な ぜ か JavaScriptは割とたくさんあります。
古典的なfor(;;)
だけでなく、for in
やfor of
、メソッドとしてforEach
まで。
開発者の方が何を思ってこんなに実装したかは知りませんが、実務ではできる限り書き方は揃えることが望まれるでしょう。
ループの構文☆
コレクションの要素をすべて反復処理する場合は、for of
かforEach
を使うようにしてください。
const nintendoIPs = ["Mario", "Kirby", "Pokemon", "Fire Emblem", "Pikmin", "Zelda"]
for(const ip of nintendoIPs) {
console.log(nintendoIPs)
}
nintendoIPs.forEach(ip => console.log(ip))
for(;;)
を使うことは推奨されません。
インデックスの i を追加しなければならないだけでなく、配列の長さも指定しなければなりません。配列を回すだけなら冗長です。
for(let i = 0; i < nintendoIPs.length; i++) {
console.log(nintendoIPs[i])
}
もしインデックスを取り扱いたいならforEachを使ってください。コールバック関数の第二引数でインデックスを受け取れます。
for ofとforEachの違い
for ofは制御構文であるのに対して、forEachはArrayのプロトタイプメソッドです。これは、例えば関数の中でループする時に顕著に違いが現れます。
for ofのループブロックの中でreturnすれば、if文の制御ブロックでのreturnと同じように関数を終了させることができますが、forEachの場合単にその時のループで実行されているコールバック関数を終了するだけです。さらにその上の関数ブロックまでは終了できません。mapのコールバック関数でreturnしても早期リターンにならないことを考えると実感しやすいと思います。
このため、以下の二つの関数は同じような挙動になるわけではありません。
const nintendoIPs = ["Mario", "Kirby", "Pokemon", "Fire Emblem", "Pikmin", "Zelda"]
function loopList(list: string[]): boolean {
for(const v of list) {
if (v.length > 5) {
return false
}
}
return true
}
console.log(loopList(nintendoIPs)) // 期待通りfalseが出力
function loopList2(list: string[]): boolean {
list.forEach(v => {
if (v.length > 5) {
return false
}
})
return true
}
console.log(loopList2(nintendoIPs)) // 常にtrue
もう少しいえば、forEachは返り値が常に棄却されundefinedとなります。他のArrayプロトタイプメソッドで連鎖することはできませんし、元の配列が変わることもありません(即ち破壊的メソッドですらない)。
配列を回すことだけに特化した特殊なArrayメソッドであると解釈することが大切です。
for of
を使う場合は、変数をconstなどで定義してください。
これがないと暗黙の裡にグローバル変数を定義していることになり、意図しない変数の定義・上書きが起こる危険があります。
const nintendoIPs = ["Mario", "Kirby", "Pokemon", "Fire Emblem", "Pikmin", "Zelda"]
for(v of nintendoIPs) {
console.log(v)
// Kirby
// Pokemon
// Fire Emblem
// Pikmin
// Zelda
}
console.log(v)
// Zelda
// 最後に代入されたvがここでもアクセスできる
より意味の分かりやすいコードへ
forループをまったく使用しないようにすることを検討してください。 Arrayを使用している場合は、代わりにmap, every, some, findIndex, find, includesのようなより意味づけされた反復処理メソッドを使用することを検討してください。
制御文のブロック☆☆☆
もしif文がreturnで終わっている場合はelseを追加しないでください。ネストを浅くするためです。
ただし、if
, for
, while
などの制御フロー文は、単一の文の時は{}
の使用を要求されませんが、常に{}
を使用してください。文を追加する際に{}
を追加するのを防ぐためです。
if (flag) {
return "success" // {}の中でreturn
}
// else文は作らない
よくない例1
if (flag) return "success"
よくない例2
// 実はreturn文が直後になくてもjsは賢いので制御フローのreturn文だと認識してくれる
function func(flag: boolean):string {
if (flag)
// ただし、ここに何かを追加したらエラー
return "success"
return "failure"
}
例えば、Golangでは書き方が非常に限定されているため、頻繁にif文を丸ごと書かないといけません。三項演算子は使えませんし、{}
を省略することもできません。しかしそのおかげで条件分の書き方は個人の好みが入る余地はほとんどありません。
短文で終わるのにわざわざ{}を省略しないのは非効率に見えるかもしれませんが、複数行にしないといけなくなった時に{}
を追加し忘れる問題や、Golangのようにできるだけ記法は統一しておいた方がよいという話もあります。
switch文のブロック☆☆
breakとreturnは共存しません。return文を使う時はbreakしないようにしてください。
function monthStringToNumber(month: string): number {
switch (month) {
case "January":
return 1
case "Febrary":
return 2
case "March":
return 3
// ...
default:
return 0
}
}
console.log(monthStringToNumber("December")) // 12
caseブロックの中で変数を宣言したりする必要がある場合は、{}
で囲ってください。
switch (role) {
case "admin": {
const privilege = new Admin()
member.accessibility = privilege
break
}
case "alumni": {
const oldAccess = new Alumni()
member.accessibility = oldAccess
break
}
case "normal": {
const normalAccess = new Normal()
member.accessibility = normalAccess
break
}
default:
member.accessibility = null
break
}
変数
変数の宣言☆☆
基本的に変更する予定のないものはconstで宣言してください。
varはグローバルスコープを汚染するため避けてください。
const comsumptionTaxRate = 0.1
1 行で複数の変数を宣言したり、カンマで区切ったり、連鎖宣言を用いたりしないでください。
let var1, var2;
let var3 = var4 = "Apapou"; // var4 は暗黙にグローバル変数として作成され、厳格モードでは失敗する
おわりに
なじみ深いものもあれば、そういう書き方が推奨されていると初めて知ったものもあると思います。
繰り返すようで恐縮ですがこれはあくまでもサンプルコードを書くときにどういう風に書くべきかというものです。ただ、実にいろいろな書き方ができるJavaScriptにおいて、根拠をもって(即ち個人の好みでなく)書き方を統一するための一つの指標・ガイドラインになるかと思います。
実際に採用するかはともかく、目を通しておくのも意味はあるかなと思った次第です。
元サイト