Skip to main content

写更少的代码

你可能没有注意到的最重要的指标

所有的代码都有bug。因此可以推理得出,你写的代码越多,你的应用就会有越多的bug。

写更多的代码也需要更多的时间,留给其他事情的时间就更少了,比如优化、锦上添花的功能,或者不用驼背对着笔记本而是到户外活动。

事实上,人们普遍认为项目开发时间bug数量与代码库的大小呈_二次方_增长,而不是线性增长。这与我们的直觉相符:一个10行的pull request会得到仔细审查,而100行的却很少会得到这样的待遇。一旦某个模块变得太大以至于无法在一个屏幕上显示完整,理解它所需的认知努力就会显著增加。我们通过重构和添加注释来补救 - 这些活动几乎总是会导致_更多_的代码。这是一个恶性循环。

然而,当我们正确地关注性能数据、包大小和任何其他可以测量的指标时,我们很少关注我们正在编写的代码量。

可读性很重要

我当然不是说我们应该使用巧妙的技巧来牺牲可读性,把代码压缩成最紧凑的形式。我也不是说减少代码_行数_一定是个值得追求的目标,因为这会鼓励把这样可读的代码...

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版本实际上大了整整三倍!

这种差异如此明显的情况并不常见 - 根据我的经验,React组件通常比其Svelte等效组件大约40%。让我们来看看Svelte设计的哪些特性使你能够更简洁地表达想法:

顶级元素

在Svelte中,一个组件可以有任意数量的顶级元素。在React和Vue中,一个组件必须有一个单一的顶级元素 - 在React的情况下,试图从组件函数返回两个顶级元素会导致语法错误。(你可以使用片段 - <> - 代替 <div>,但基本思想是一样的,而且仍然会导致额外的缩进级别)。

在Vue中,你的标记必须包裹在一个<template>元素中,我认为这是多余的。

绑定

在React中,我们必须自己响应输入事件:

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

这不仅是占用屏幕空间的无聊管道代码,而且还为bug提供了额外的空间。从概念上讲,输入的值与a的值双向绑定,但这种关系并没有清晰地表达出来 - 相反,我们有两个紧密耦合但物理上分离的代码块(事件处理器和value={a}属性)。不仅如此,我们还必须记得用+运算符强制转换字符串值,否则2 + 2将等于22而不是4

像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特性的限制:我们可以_设计_一种组件开发体验,而不是必须围绕语言的语义来适应它。矛盾的是,这导致了_更多_符合习惯的代码 - 例如自然地使用变量而不是通过代理或钩子 - 同时提供显著更高性能的应用。