コードをより分かりやすくする「コード・ニートネス」の極意
はじめに
コードが動くかどうかだけでなく、コードの書き方そのものの質にもこだわるのであれば、コードに対する整頓されたアプローチを追求していると言っていいでしょう。
以上の議論に基づき、すっきりとしたコードの書き方とは、自明で、理解しやすく、修正や拡張が容易な方法でコードを書くことと定義されます。
.
この記事では、コードをきれいにする方法を紹介するためにJavascriptから始めますが、この理論は他のプログラミング言語にも適用できます。
強力な型チェック
の代わりに === を使用します。
0 == false // true
0 === false // false
2 == "2" // true
2 === "2" // false
// example
const val = "123";
if (val === 123) {
console.log(val);
// it cannot not be reached
}
if (val === "123") {
console.log(val);
// it can be reached
}
変数の命名
変数の名前は、その使用目的を表すように付けましょう。
このようにして、これらの変数を検索可能にし、可読性を高めることができます。
お勧めしません。
let daysSLV = 10;
let y = new Date().getFullYear();
let ok;
if (user.age > 30) {
ok = true;
}
推奨される。
const MAX_AGE = 30;
let daysSinceLastVisit = 10;
let currentYear = new Date().getFullYear();
...
const isUserOlderThanAllowed = user.age > MAX_AGE;
変数名に余計な単語や不要な単語を追加しないでください。
お勧めしません。
let nameValue;
let theProduct;
推奨される。
let name;
let product;
変数に関するコンテキスト情報を保存する必要性を無視しないでください。
お勧めしません。
const products = ["T-Shirt", "Shoes", "Watches", "Bags"];
products.forEach(p => {
doSomething();
doSomethingElse();
// ...
// ...
// ...
// ...
// `p` それは何を意味するのか?
register(p);
});
推奨される。
const products = ["T-Shirt", "Shoes", "Watches", "Bags"];
products.forEach(product => {
doSomething();
doSomethingElse();
// ...
// ...
// ...
register(product);
});
変数に不要な情報を追加しない
お勧めしません。
const product = {
productId: 1,
productName: "T-Shirt",
productPrice: 8.99,
productUnits: 12
};
...
product.productName;
推奨される。
const product = {
id: 1,
name: "T-Shirt",
price: 8.99,
units: 12
};
...
product.name;
同じ型の変数には同じ語彙を使用
お勧めしません。
getUserInfo();
getClientData();
getCustomerRecord();
推奨される。
getProduct();
機能
説明的な関数名を使用してください。
通常、関数は特定の振る舞いを表すものであることから、関数名は、関数の背後にある意図や引数の意味を適切に伝える動詞や語句であるべきです。
お勧めしません。
function email(user) {
// implementation
}
推奨される。
function sendEmailUser(emailAddress) {
// implementation
}
多すぎるパラメータは避けてください
関数の引数は2つ以下が理想的です。引数の数が少なければ少ないほど、関数のテストが容易になります。
お勧めしません。
function getProducts(fields, fromDate, toDate) {
// implementation
}
推奨される。
function getProducts({ fields, fromDate, toDate }) {
// implementation
}
getProducts({
fields: ['id', 'name', 'price', 'units],
fromDate: '',
toDate: ''
});
条件判定の代わりにデフォルト・パラメータを使用
お勧めしません。
function createShape(type) {
const shapeType = type || "circle";
// ...
}
推奨される。
function createShape(type = "circle") {
// ...
}
つの関数本体で複数の操作を行わないようにします。
お勧めしません。
function notifyUsers(users) {
users.forEach(user => {
const userRecord = database.lookup(user);
if (userRecord.isVerified()) {
notify(user);
}
});
}
推奨される。
function notifyVerifiedUsers(users) {
users.filter(isUserVerified).forEach(notify);
}
function isUserVerified(user) {
const userRecord = database.lookup(user);
return userRecord.isVerified();
}
Object.assignを使ったデフォルト・オブジェクトの設定
お勧めしません。
const shapeConfig = {
type: "circle",
width: 150,
height: null
};
function createShape(config) {
config.type = config.type || "circle";
config.width = config.width || 300;
config.height = config.height || 300;
}
createShape(shapeConfig);
推奨される。
const shapeConfig = {
type: "circle",
width: 150
// Exclude the 'height' key
};
function createShape(config) {
config = Object.assign(
{
type: "circle",
width: 300,
height: 300
},
config
);
...
}
createShape(shapeConfig);
フラグを引数として取らないようにしましょう。フラグは、関数を定義済みのスコープ外で動作させるからです。
お勧めしません。
function createFile(name, isPublic) {
if (isPublic) {
fs.create(`./public/${name}`);
} else {
fs.create(name);
}
}
推奨される。
function createFile(name) {
fs.create(name);
}
function createPublicFile(name) {
createFile(`./public/${name}`);
}
グローバル変数を汚染しない
既存のオブジェクトを拡張したい場合は、ネイティブ・オブジェクトのプロトタイプ・チェインで拡張関数を作成する代わりに、ES6のクラスと継承構文を使用します。
お勧めしません。
Array.prototype.myFunction = function myFunction() {
// implementation
};
推奨される。
class SuperArray extends Array {
myFunc() {
// implementation
}
}
条件判定
ネガティブな状況を避ける
お勧めしません。
function isPostNotPublished(post) {
// implementation
}
if (!isPostNotPublished(post)) {
// implementation
}
推奨される。
function isPostPublished(user) {
// implementation
}
if (isPostPublished(user)) {
// implementation
}
簡略化された条件判断の使用
このルールは些細なことに思えるかもしれませんが、特筆に値します。このルールは、値がundefinedやnullでないことが明らかで、かつ値がboolean型である場合に、コードをより簡潔にするために使うことができます。
お勧めしません。
if (isValid === true) {
// do something...
}
if (isValid === false) {
// do something...
}
推奨される。
if (isValid) {
// do something...
}
if (!isValid) {
// do something...
}
どのような場合でも、条件判断の使用は避けてください。
ポリモーフィズムと継承の使用
お勧めしません。
class Dog {
// ...
getBreed() {
switch (this.type) {
case "GermanShepherd":
return this.getStandardSize("GermanShepherd");
case "JackRussellTerrier":
return this.getStandardSize("JackRussellTerrier");
case "ShibaInu":
return this.getStandardSize("ShibaInu");
}
}
}
推奨される。
class Dog {
// ...
}
class GermanShepherd extends Dog {
// ...
getStandardSize() {
return this.standardSize;
}
}
class JackRussellTerrier extends Dog {
// ...
getSize() {
return this.standardSize;
}
}
class ShibaInu extends Dog {
// ...
getSize() {
return this.standardSize;
}
}
ES Classes
クラス構文は、Javascriptの新しい構文糖です。書き方が異なるだけでなく、クラス構文はプロトタイプを使用するのと同じ効果を実現し、コードをより簡潔にします。
お勧めしません。
const Product = function(name) {
if (!(this instanceof Product)) {
throw new Error("Instantiate Product with `new` keyword");
}
this.name = name;
};
Product.prototype.getSize = function getSize() { /**/ };
const Tshirt = function(name, color) {
if (!(this instanceof Tshirt)) {
throw new Error("Instantiate Tshirt with `new` keyword");
}
Product.call(this, name);
this.color = color;
};
Tshirt.prototype = Object.create(Product.prototype);
Tshirt.prototype.constructor = Tshirt;
Tshirt.prototype.printColor = function printColor() { /**/ };
推奨される。
class Product {
constructor(name) {
this.name = name;
}
getDiscount() {
/* ... */
}
}
class Tshirt extends Product {
constructor(name, color) {
super(name);
this.color = color;
}
getSize() {
/* ... */
}
}
連鎖メソッドの使用
jQueryやLodashのような人気のあるライブラリの多くは、このパターンを使っています。
定義したクラスの各関数の末尾にthisを返すことで、後でさらにクラス関数を連鎖させることができます。
お勧めしません。
class Product {
constructor(name) {
this.name = name;
}
setPrice(price) {
this.price = price;
}
setUnits(units) {
this.units = units;
}
save() {
console.log(this.name, this.price, this.units);
}
}
const product = new Product("Bag");
person.setPrice(23.99);
person.setUnits(12);
person.save();
推奨される。
class Product {
constructor(name) {
this.name = name;
}
setName(name) {
this.name = name;
// Return this for chaining
return this;
}
setPrice(price) {
this.price = price;
// Return this for chaining
return this;
}
save() {
console.log(this.name, this.price, this.units);
// Return this for chaining
return this;
}
}
const product = new Product("T-Shirt")
.setName("Jeans")
.setAge(31.99)
.save();
eval
eval関数は文字列をJavascriptコンパイラに渡し、Javascriptステートメントとして実行します。
要するに、実行時に渡したものはすべて、設計時に追加されたかのように動作します。
eval("alert('Hi');");
上記のコードを実行すると、「こんにちは」と書かれたメッセージボックスが表示されます。
JSLintの使用
JSLintのようなコードフォーマット検証ツールは、標準化されたコードを書くのに最適です。このようなツールの使用については後述します