コードの記述量を減らす
見過ごされている最も重要な指標
すべてのコードにはバグが存在します。したがって、記述するコード量が多ければ多いほど、アプリケーションのバグも多くなるのは当然です。
コードの記述量が増えると、時間もかかります。最適化、便利な機能の追加、またはラップトップに頭を突っ込まずに屋外で過ごすなどの時間的な余裕が少なくなります。
実際、プロジェクト開発時間とバグ数は、コードベースのサイズに対して線形ではなく、*2乗*で増加することが広く認識されています。これは私たちの直感と一致しています。10行のプルリクエストは、100行のプルリクエストにはめったに適用されないレベルの精査を受けます。そして、特定のモジュールが1つの画面に収まらなくなると、それを理解するために必要な認知努力は大幅に増加します。私たちはリファクタリングやコメントの追加によって対応しますが、これらの作業はほとんどの場合、コードの*増加*につながります。これは悪循環です。
しかし、私たちはパフォーマンス数値、バンドルサイズ、その他測定可能なものについて熱心に考えていますが、記述するコード量にはほとんど注意を払っていません。
可読性は重要です
もちろん、可読性を犠牲にしてコードを可能な限りコンパクトな形式に圧縮するために巧妙なトリックを使用すべきだと言っているのではありません。また、可読性の高いコードを以下のように…
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>
つまり、Reactでは442文字、Vueでは263文字が必要ですが、Svelteでは145文字で実現できます。Reactのバージョンは文字数が3倍になっています!
この違いがこれほど明白になることは珍しいことです。私の経験では、Reactコンポーネントは通常、Svelteの同等物よりも約40%大きくなります。アイデアをより簡潔に表現できるようにするSvelteの設計機能を見てみましょう。
トップレベル要素
Svelteでは、コンポーネントに任意の数のトップレベル要素を含めることができます。ReactとVueでは、コンポーネントは単一のトップレベル要素を持つ必要があります。Reactの場合、コンポーネント関数から2つのトップレベル要素を返すことは、構文的に無効なコードになります。(<div>
の代わりにフラグメント(<>
)を使用できますが、基本的な考え方は同じであり、それでもインデントレベルが1つ増えます)。
Vueでは、マークアップは<template>
要素でラップする必要がありますが、これは冗長であると主張します。
バインディング
Reactでは、入力イベントに自分自身で対応する必要があります。
function function handleChangeA(event: any): void
handleChangeA(event: any
event) {
setA(+event: any
event.target.value);
}
これは、画面上の余分なスペースを占める退屈な配管作業だけでなく、バグが発生しやすい余分な領域でもあります。概念的には、入力の値は`a`の値に双方向にバインドされていますが、その関係は明確に表現されていません。代わりに、イベントハンドラーとvalue={a}
プロパティという、密接に関連しているが物理的に分離された2つのコードブロックがあります。それだけでなく、`+`演算子を使用して文字列値を強制変換することを覚えておく必要があります。そうでなければ、`2 + 2`は`4`ではなく`22`になります。
Svelteと同様に、Vueにはバインディングを表現する方法(v-model
属性)がありますが、数値入力であるにもかかわらず、v-model.number
を使用することに注意する必要があります。
状態
Svelteでは、代入演算子を使用してローカルコンポーネントの状態を更新します。
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);
}
これははるかに*ノイズが多い*です。まったく同じ概念を表していますが、文字数が60%以上多くなっています。コードを読んでいるとき、著者の意図を理解するために、その分多くの作業を行う必要があります。
一方、Vueでは、ローカル状態に対応するプロパティを持つオブジェクトリテラルを返すdata
関数を持つデフォルトエクスポートがあります。ヘルパー関数や子コンポーネントは、単にインポートしてテンプレートで使用できるわけではなく、デフォルトエクスポートの正しい部分にアタッチすることで「登録」する必要があります。
定型コードの死滅
これらは、Svelteが最小限の手間でユーザーインターフェースの構築を支援する方法の一部です。他にもたくさんあります。たとえば、リアクティブ宣言($:
ステートメント)は、本質的にReactのuseMemo
、useCallback
、useEffect
の作業を、定型コード(または状態の変更ごとにインライン関数と配列を作成することによるガベージコレクションのオーバーヘッド)なしで行います。
どのように?異なる制約のセットを選択することによって。Svelteはコンパイラであるため、JavaScriptの特性に縛られることはありません。言語のセマンティクスに合わせて調整するのではなく、コンポーネントの作成エクスペリエンスを*設計*できます。これは逆説的に、より*慣用的*なコード(たとえば、プロキシやフックではなく、自然な方法で変数を使用する)をもたらし、同時に大幅にパフォーマンスの高いアプリケーションを提供します。