Skip to main content

コード量を減らす(Write less code)

注意が払われていない最も重要な指標について

全てのコードにはバグが存在する可能性があります(All code is buggy)。したがって、書かなければいけないコードが多ければ多いほど、アプリケーションがバグだらけになるのは理にかなっています。

多くのコードを書くには多くの時間がかかるので、他のこと(例えば最適化や、良い機能を開発すること、またはPCの前に座らずに外出することなど)に充てられる時間は少なくなります。

実際、プロジェクトの開発時間バグ件数 はコードベースのサイズに対して直線的ではなく 二次関数的 に増加することが広く知られています。これは私たちの直感と一致しています : 10行のプルリクエストは、100行のプルリクエストだと有り得ないレベルで精査されるでしょう。また、モジュールが1つの画面に収まりきらないほど大きくなると、それを理解するために必要な認知的努力が著しく増大します。リファクタリングし、コメントを追加することでこの問題に対応しようとしますが、ほとんどの場合コードがもっと増加してしまいます。悪循環です。

しかし、私たちはパフォーマンスの数値やバンドルサイズ、その他計測できるものはなんでも気にかける一方で、書いているコードの量に注意を払うことはほとんどありません。

重要なのは読みやすさ(Readability is important)

巧妙なトリックを使って読みやすさを犠牲にしてでもできるだけコードをコンパクトにするべきだ、と主張しているわけではありません。また、コードの 行数 を減らすことこそが価値のある目標であると主張しているわけでもありません、このような主張は、例えば次のような読みやすいコードを…

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>

つまり、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): voidhandleChangeA(event: anyevent) {
	setA(+event: anyevent.target.value);
}

これは退屈で画面上に余分なスペースを取るだけでなく、余分なバグを生みかねません。概念的には、入力の値は a の値と結びついており、その逆も同様ですが、この関係が綺麗に表現されていません - その代わり、緊密に結合しているものの物理的には分離しているコードのチャンク(イベントハンドラと value={a} prop) になります。それだけでなく、+ 演算子で文字列の値が強制(coerce)されることも忘れないようにしないといけません( 訳注 : JavaScriptでは暗黙的に型が変換されるケースがあります。詳しくは Type coercion (型強制) - MDN Web Docs をご参照ください )。さもないと、2 + 24 ではなく 22 になってしまいます。

Svelteと同様、Vueにはバインディングを表現する方法があります - v-model属性です。ただし、数値入力であっても v-model.number を使うように気をつけなければなりません。

State

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

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);
}

これは かなりノイジー(much noisier) です。全く同じ概念を表現していますが文字が60%も多いです。コードを読む際に、その意図を理解するためにより多くの労力をかける必要があります。

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

ボイラープレートに終焉を(Death to boilerplate)

これらは、最小限の手間でユーザーインタフェースを構築するのにSvelteが役立つことの、ほんの一部に過ぎません。他には、例えばreactive declarations はReactのuseMemouseCallbackuseEffectにあたる動作をボイラープレート(あるいは、stateが変化するたびにインライン関数と配列を作成するのに伴うガベージコレクションのオーバーヘッド)なしで本質的に行います。

どうやって?別の制約セットを選択します。Svelteはコンパイラなので、JavaScriptの特性に縛られません : 言語のセマンティクスに合わせるのではなく、コンポーネントのオーサリングエクスペリエンスを設計できます。逆説的に言えば、結果としてよりイディオムなコードが可能になります - 例えば、プロキシやフックを経由するのではなく、自然に変数を使用することができます - しかも、よりパフォーマンスの高いアプリを提供することができます。