コード量を減らす(Write less code)
注意が払われていない最も重要な指標について
全てのコードにはバグが存在する可能性があります(All code is buggy)。したがって、書かなければいけないコードが多ければ多いほど、アプリケーションがバグだらけになるのは理にかなっています。
多くのコードを書くには多くの時間がかかるので、他のこと(例えば最適化や、良い機能を開発すること、またはPCの前に座らずに外出することなど)に充てられる時間は少なくなります。
実際、プロジェクトの開発時間 と バグ件数 はコードベースのサイズに対して直線的ではなく 二次関数的 に増加することが広く知られています。これは私たちの直感と一致しています : 10行のプルリクエストは、100行のプルリクエストだと有り得ないレベルで精査されるでしょう。また、モジュールが1つの画面に収まりきらないほど大きくなると、それを理解するために必要な認知的努力が著しく増大します。リファクタリングし、コメントを追加することでこの問題に対応しようとしますが、ほとんどの場合コードがもっと増加してしまいます。悪循環です。
しかし、私たちはパフォーマンスの数値やバンドルサイズ、その他計測できるものはなんでも気にかける一方で、書いているコードの量に注意を払うことはほとんどありません。
重要なのは読みやすさ(Readability is important)
巧妙なトリックを使って読みやすさを犠牲にしてでもできるだけコードをコンパクトにするべきだ、と主張しているわけではありません。また、コードの 行数 を減らすことこそが価値のある目標であると主張しているわけでもありません、このような主張は、例えば次のような読みやすいコードを…
for (let let i: number
i = 0; let i: number
i <= 100; let i: number
i += 1) {
if (let i: number
i % 2 === 0) {
var console: Console
The console
module provides a simple debugging console that is similar to the
JavaScript console mechanism provided by web browsers.
The module exports two specific components:
- A
Console
class with methods such as console.log()
, console.error()
and console.warn()
that can be used to write to any Node.js stream.
- A global
console
instance configured to write to process.stdout
and
process.stderr
. The global console
can be used without calling require('console')
.
Warning: The global console object’s methods are neither consistently
synchronous like the browser APIs they resemble, nor are they consistently
asynchronous like all other Node.js streams. See the note on process I/O
for
more information.
Example using the global console
:
console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr
Example using the Console
class:
const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err
console.Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)
Prints to stdout
with newline. Multiple arguments can be passed, with the
first used as the primary message and all additional used as substitution
values similar to printf(3)
(the arguments are all passed to util.format()
).
const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout
See util.format()
for more information.
log(`${let i: number
i} is even`);
}
}
…次のように解析がより難しいものに変えるよう促してしまいます:
for (let let i: number
i = 0; let i: number
i <= 100; let i: number
i += 1) if (let i: number
i % 2 === 0) var console: Console
The console
module provides a simple debugging console that is similar to the
JavaScript console mechanism provided by web browsers.
The module exports two specific components:
- A
Console
class with methods such as console.log()
, console.error()
and console.warn()
that can be used to write to any Node.js stream.
- A global
console
instance configured to write to process.stdout
and
process.stderr
. The global console
can be used without calling require('console')
.
Warning: The global console object’s methods are neither consistently
synchronous like the browser APIs they resemble, nor are they consistently
asynchronous like all other Node.js streams. See the note on process I/O
for
more information.
Example using the global console
:
console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr
Example using the Console
class:
const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err
console.Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)
Prints to stdout
with newline. Multiple arguments can be passed, with the
first used as the primary message and all additional used as substitution
values similar to printf(3)
(the arguments are all passed to util.format()
).
const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout
See util.format()
for more information.
log(`${let i: number
i} is even`);
代わりに、自然とコードが少なくなるような言語とパターンを好むべきだと主張します。
はい、Svelteについて話しています
書かなければいけないコードの量を減らすことは、Svelteの明確な目標です。説明のために、React、Vue、Svelteでそれぞれ実装されたシンプルなコンポーネントを見てみましょう。まずはSvelteのバージョンです:
これをReactで構築するには? おそらく次のようになるでしょう:
import import React
React, { import useState
useState } from 'react';
export default () => {
const [const a: any
a, const setA: any
setA] = import useState
useState(1);
const [const b: any
b, const setB: any
setB] = import useState
useState(2);
function function (local function) handleChangeA(event: any): void
handleChangeA(event: any
event) {
const setA: any
setA(+event: any
event.target.value);
}
function function (local function) handleChangeB(event: any): void
handleChangeB(event: any
event) {
const setB: any
setB(+event: any
event.target.value);
}
return (
<type div = /*unresolved*/ any
div>
<type input = /*unresolved*/ any
input type="number" value={a: any
a} onChange={handleChangeA: (event: any) => void
handleChangeA} />
<type input = /*unresolved*/ any
input type="number" value={b: any
b} onChange={handleChangeB: (event: any) => void
handleChangeB} />
<type p = /*unresolved*/ any
p>
{a: any
a} + {b: any
b} = {const a: any
a + const b: any
b}
</p>
</div>
);
};
Vueで同等のことをやると次のようになります:
<template>
<div>
<input type="number" v-model.number="a">
<input type="number" v-model.number="b">
<p>{{a}} + {{b}} = {{a + b}}</p>
</div>
</template>
<script>
export default {
data: function() {
return {
a: 1,
b: 2
};
}
};
</script>
つまり、Svelteだと145文字でできることが、Reactだと442文字、Vueだと263文字かかります。Reactバージョンは文字通り3倍大きいです!
ここまで差がつくのは中々珍しいケースです - 私の経験では、ReactコンポーネントはSvelteの同等のコンポーネントより大体約40%ほど大きいです。では、アイデアを簡潔に表現することを可能にするSvelteの設計の特徴を見ていきましょう。
トップレベル要素(Top-level elements)
Svelteでは、1つのコンポーネントに好きなだけトップレベル要素を含めることができます。ReactやVueは、1つのコンポーネントには単一のトップレベル要素しか含めることができません - Reactの場合、コンポーネント関数から2つのトップレベル要素を返そうとすると構文的に誤ったコードになります。(<div>
の代わりに <>
を使用できますが、基本的な考え方は同じで、インデントが余分に発生します)
Vueの場合、<template>
要素でラップしなければなりません、これは冗長です。
バインディング(Bindings)
Reactでは、自分で入力イベントに対応しなければなりません。
function function handleChangeA(event: any): void
handleChangeA(event: any
event) {
setA(+event: any
event.target.value);
}
これは退屈で画面上に余分なスペースを取るだけでなく、余分なバグを生みかねません。概念的には、入力の値は a
の値と結びついており、その逆も同様ですが、この関係が綺麗に表現されていません - その代わり、緊密に結合しているものの物理的には分離しているコードのチャンク(イベントハンドラと value={a}
prop) になります。それだけでなく、+
演算子で文字列の値が強制(coerce)されることも忘れないようにしないといけません( 訳注 : JavaScriptでは暗黙的に型が変換されるケースがあります。詳しくは Type coercion (型強制) - MDN Web Docs をご参照ください )。さもないと、2 + 2
が 4
ではなく 22
になってしまいます。
Svelteと同様、Vueにはバインディングを表現する方法があります - v-model
属性です。ただし、数値入力であっても v-model.number
を使うように気をつけなければなりません。
State
Svelteでは、ローカルコンポーネントのstateを代入演算子で更新します。
let let count: number
count = 0;
function function increment(): void
increment() {
let count: number
count += 1;
}
Reactでは、useState
フックを使用します。
const [const count: any
count, const setCount: any
setCount] = useState(0);
function function increment(): void
increment() {
const setCount: any
setCount(const count: any
count + 1);
}
これは かなりノイジー(much noisier) です。全く同じ概念を表現していますが文字が60%も多いです。コードを読む際に、その意図を理解するためにより多くの労力をかける必要があります。
一方Vueでは、デフォルトのエクスポートに data
関数があり、ローカルのstateに対応するプロパティを持つオブジェクトリテラルを返します。ヘルパー関数や子コンポーネントなどは単純にインポートしてテンプレートで使用することはできません、デフォルトのエクスポートの正しい部分にアタッチして’登録’する必要があります。
ボイラープレートに終焉を(Death to boilerplate)
これらは、最小限の手間でユーザーインタフェースを構築するのにSvelteが役立つことの、ほんの一部に過ぎません。他には、例えばreactive declarations はReactのuseMemo
、useCallback
、useEffect
にあたる動作をボイラープレート(あるいは、stateが変化するたびにインライン関数と配列を作成するのに伴うガベージコレクションのオーバーヘッド)なしで本質的に行います。
どうやって?別の制約セットを選択します。Svelteはコンパイラなので、JavaScriptの特性に縛られません : 言語のセマンティクスに合わせるのではなく、コンポーネントのオーサリングエクスペリエンスを設計できます。逆説的に言えば、結果としてよりイディオムなコードが可能になります - 例えば、プロキシやフックを経由するのではなく、自然に変数を使用することができます - しかも、よりパフォーマンスの高いアプリを提供することができます。