Write less code

All code is buggy. It stands to reason, therefore, that the more code you have to write the buggier your apps will be. Writing more code also takes more time, leaving less time for other things like

All code is buggy. It stands to reason, therefore, that the more code you have to write the buggier your apps will be.

Writing more code also takes more time, leaving less time for other things like optimisation, nice-to-have features, or being outdoors instead of hunched over a laptop.

In fact it’s widely acknowledged that project development time (https://blog.codinghorror.com/diseconomies-of-scale-and-lines-of-code/) and bug count (https://www.mayerdan.com/ruby/2012/11/11/bugs-per-line-of-code-ratio) grow quadratically, not linearly, with the size of a codebase. That tracks with our intuitions: a ten-line pull request will get a level of scrutiny rarely applied to a 100-line one. And once a given module becomes too big to fit on a single screen, the cognitive effort required to understand it increases significantly. We compensate by refactoring and adding comments — activities that almost always result in more code. It’s a vicious cycle.

Yet while we obsess — rightly! — over performance numbers, bundle size and anything else we can measure, we rarely pay attention to the amount of code we’re writing.

Readability is important (#Readability-is-important)I’m certainly not claiming that we should use clever tricks to scrunch our code into the most compact form possible at the expense of readability. Nor am I claiming that reducing lines of code is necessarily a worthwhile goal, since it encourages turning readable code like this…

for (let let i: numberi = 0; let i: numberi <= 100; let i: numberi += 1) { if (let i: numberi % 2 === 0) { var console: ConsoleThe 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 importing the node:console module.

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 stderrExample 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 (https://github.com/nodejs/node/blob/v20.11.1/lib/console.js)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 stdoutSee util.format() for more information.

@sincev0.1.100log(${let i: numberi} is even); } }…into something much harder to parse:

for (let let i: numberi = 0; let i: numberi <= 100; let i: numberi += 1) if (let i: numberi % 2 === 0) var console: ConsoleThe 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 importing the node:console module.

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 stderrExample 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 (https://github.com/nodejs/node/blob/v20.11.1/lib/console.js)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 stdoutSee util.format() for more information.

@sincev0.1.100log(${let i: numberi} is even);Instead, I’m claiming that we should favour languages and patterns that allow us to naturally write less code.

Yes, I’m talking about Svelte (#Yes-I’m-talking-about-Svelte)Reducing the amount of code you have to write is an explicit goal of Svelte. To illustrate, let’s look at a very simple component implemented in React, Vue and Svelte. First, the Svelte version:

How would we build this in React? It would probably look something like this:

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 (
	<div>
		<input type: stringtype="number" value: anyvalue={const a: anya} onChange: (event: any) => voidonChange={function (local function) handleChangeA(event: any): voidhandleChangeA} />
		<input type: stringtype="number" value: anyvalue={const b: anyb} onChange: (event: any) => voidonChange={function (local function) handleChangeB(event: any): voidhandleChangeB} />

		<p>
			{const a: anya} + {const b: anyb} = {const a: anya + const b: anyb}
		</p>
	</div>
);

};Here’s an equivalent component in Vue:

I'm counting by copying the source code to the clipboard and running pbpaste | wc -c in my terminal

In other words, it takes 442 characters in React, and 263 characters in Vue, to achieve something that takes 145 characters in Svelte. The React version is literally three times larger!

It’s unusual for the difference to be quite so obvious — in my experience, a React component is typically around 40% larger than its Svelte equivalent. Let’s look at the features of Svelte’s design that enable you to express ideas more concisely:

Top-level elements (#Yes-I’m-talking-about-Svelte-Top-level-elements)In Svelte, a component can have as many top-level elements as you like. In React and Vue, a component must have a single top-level element — in React’s case, trying to return two top-level elements from a component function would result in syntactically invalid code. (You can use a fragment — <> — instead of a

, but it’s the same basic idea, and still results in an extra level of indentation).

In Vue, your markup must be wrapped in a