メインコンテンツへスキップ

コードの記述量を減らす

見過ごされている最も重要な指標

すべてのコードにはバグが存在します。したがって、記述するコード量が多ければ多いほど、アプリケーションのバグも多くなるのは当然です。

コードの記述量が増えると、時間もかかります。最適化、便利な機能の追加、またはラップトップに頭を突っ込まずに屋外で過ごすなどの時間的な余裕が少なくなります。

実際、プロジェクト開発時間バグ数は、コードベースのサイズに対して線形ではなく、*2乗*で増加することが広く認識されています。これは私たちの直感と一致しています。10行のプルリクエストは、100行のプルリクエストにはめったに適用されないレベルの精査を受けます。そして、特定のモジュールが1つの画面に収まらなくなると、それを理解するために必要な認知努力は大幅に増加します。私たちはリファクタリングやコメントの追加によって対応しますが、これらの作業はほとんどの場合、コードの*増加*につながります。これは悪循環です。

しかし、私たちはパフォーマンス数値、バンドルサイズ、その他測定可能なものについて熱心に考えていますが、記述するコード量にはほとんど注意を払っていません。

可読性は重要です

もちろん、可読性を犠牲にしてコードを可能な限りコンパクトな形式に圧縮するために巧妙なトリックを使用すべきだと言っているのではありません。また、可読性の高いコードを以下のように…

for (let let i: numberi = 0; let i: numberi <= 100; let i: numberi += 1) {
	if (let i: numberi % 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
@seesource
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.

@sincev0.1.100
log
(`${let i: numberi} is even`);
} }

…解析がはるかに困難なものに変えることを奨励するため、コードの*行数*を減らすことが必ずしも価値のある目標だと言っているわけではありません。

for (let let i: numberi = 0; let i: numberi <= 100; let i: numberi += 1) if (let i: numberi % 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
@seesource
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.

@sincev0.1.100
log
(`${let i: numberi} is even`);

代わりに、自然にコードの記述量を減らせる言語とパターンを優先すべきだと主張しています。

そうです、Svelteについて話しています

記述するコードの量を減らすことは、Svelteの明示的な目標です。例として、React、Vue、Svelteで実装された非常にシンプルなコンポーネントを見てみましょう。まず、Svelteのバージョンです。

これをReactでどのように構築しますか?おそらく、このようなものになるでしょう。

import import ReactReact, { import useStateuseState } from 'react';

export default () => {
	const [const a: anya, const setA: anysetA] = import useStateuseState(1);
	const [const b: anyb, const setB: anysetB] = import useStateuseState(2);

	function function (local function) handleChangeA(event: any): voidhandleChangeA(event: anyevent) {
		const setA: anysetA(+event: anyevent.target.value);
	}

	function function (local function) handleChangeB(event: any): voidhandleChangeB(event: anyevent) {
		const setB: anysetB(+event: anyevent.target.value);
	}

	return (
		<type div = /*unresolved*/ anydiv>
			<type input = /*unresolved*/ anyinput type="number" value={a: anya} onChange={handleChangeA: (event: any) => voidhandleChangeA} />
			<type input = /*unresolved*/ anyinput type="number" value={b: anyb} onChange={handleChangeB: (event: any) => voidhandleChangeB} />

			<type p = /*unresolved*/ anyp>
				{a: anya} + {b: anyb} = {const a: anya + const b: anyb}
			</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): voidhandleChangeA(event: anyevent) {
	setA(+event: anyevent.target.value);
}

これは、画面上の余分なスペースを占める退屈な配管作業だけでなく、バグが発生しやすい余分な領域でもあります。概念的には、入力の値は`a`の値に双方向にバインドされていますが、その関係は明確に表現されていません。代わりに、イベントハンドラーとvalue={a}プロパティという、密接に関連しているが物理的に分離された2つのコードブロックがあります。それだけでなく、`+`演算子を使用して文字列値を強制変換することを覚えておく必要があります。そうでなければ、`2 + 2`は`4`ではなく`22`になります。

Svelteと同様に、Vueにはバインディングを表現する方法(v-model属性)がありますが、数値入力であるにもかかわらず、v-model.numberを使用することに注意する必要があります。

状態

Svelteでは、代入演算子を使用してローカルコンポーネントの状態を更新します。

let let count: numbercount = 0;

function function increment(): voidincrement() {
	let count: numbercount += 1;
}

Reactでは、useStateフックを使用します。

const [const count: anycount, const setCount: anysetCount] = useState(0);

function function increment(): voidincrement() {
	const setCount: anysetCount(const count: anycount + 1);
}

これははるかに*ノイズが多い*です。まったく同じ概念を表していますが、文字数が60%以上多くなっています。コードを読んでいるとき、著者の意図を理解するために、その分多くの作業を行う必要があります。

一方、Vueでは、ローカル状態に対応するプロパティを持つオブジェクトリテラルを返すdata関数を持つデフォルトエクスポートがあります。ヘルパー関数や子コンポーネントは、単にインポートしてテンプレートで使用できるわけではなく、デフォルトエクスポートの正しい部分にアタッチすることで「登録」する必要があります。

定型コードの死滅

これらは、Svelteが最小限の手間でユーザーインターフェースの構築を支援する方法の一部です。他にもたくさんあります。たとえば、リアクティブ宣言($:ステートメント)は、本質的にReactのuseMemouseCallbackuseEffectの作業を、定型コード(または状態の変更ごとにインライン関数と配列を作成することによるガベージコレクションのオーバーヘッド)なしで行います。

どのように?異なる制約のセットを選択することによって。Svelteはコンパイラであるため、JavaScriptの特性に縛られることはありません。言語のセマンティクスに合わせて調整するのではなく、コンポーネントの作成エクスペリエンスを*設計*できます。これは逆説的に、より*慣用的*なコード(たとえば、プロキシやフックではなく、自然な方法で変数を使用する)をもたらし、同時に大幅にパフォーマンスの高いアプリケーションを提供します。