Listings Neugebauer/Defensives Programmieren mit TypeScript

Listing 1: Eine Fehlermeldung macht auf die Division durch 0 aufmerksam
function divide(a: number, b: number) {
    if (b === 0) {
        // Fail-fast
        throw new Error('Division by zero');
    }
    return a / b;
}

---

Listing 2: Bedingte Typzuweisung vermeidet Division durch 0
function divide<Divisor extends number>
(a: number, b: Divisor extends 0 ? never : Divisor) {
    if (b === 0) {
        throw new Error('Division by zero');
    }
    return a / b;
}

const result = divide(100, 0); // error TS2345

---

Listing 3: Defensive Abfragelogik verhindert zwar Eingabefehler, ist aber wartungsintensiv
import fs from 'node:fs';

// Type definition
type User = {
    age: number;
    email: string;
    name: string;
};

function saveUser(user: User): void {
    // Validation
    if (user.name.length < 1) {
        throw new Error('Name must have at least 1 character.');
    }

    if (user.age < 0) {
        throw new Error('Age cannot be negative.');
    }

    if (!user.email.includes('@')) {
        throw new Error('Email address must contain "@" symbol.');
    }

    // Example business logic
    fs.writeFileSync(`${user.name}.txt`, JSON.stringify(user));
}

saveUser({
    age: 36,
    email: 'benny@typescript.tv',
    name: 'Benny Code',
});

---

Listing 4: Sauberes Trennen von Validierung und Geschäftslogik durch Zod-Schema-Validierung
import fs from 'node:fs';
import {z} from 'zod';

// Schema definition
const userSchema = z.object({
    age: z.number().positive().int(),
    email: z.string().email(),
    name: z.string().min(1),
});

// Type definition
type User = z.infer<typeof userSchema>;

function saveUser(user: User): void {
    // Validation
    userSchema.parse(user);

    // Example business logic
    fs.writeFileSync(`${user.name}.txt`, JSON.stringify(user));
}

saveUser({
    age: 36,
    email: 'benny@typescript.tv',
    name: 'Benny Code',
});

---

Listing 5: Bekannte Zustände mit switch-case-Blöcken verwalten
const StreamStatus = {
    IDLE: 'IDLE',
    OFFLINE: 'OFFLINE',
    ONLINE: 'ONLINE',
} as const;

interface StreamResponse {
    status: keyof typeof StreamStatus;
}

function handleResponse(response: StreamResponse) {
    switch (response.status) {
        case StreamStatus.ONLINE:
            console.log('You are online.');
            break;
        case StreamStatus.OFFLINE:
            console.log('You are offline.');
            break;
    }
}

handleResponse({status: 'ONLINE'});

---

Listing 6: TypeScript-Typ never erzeugt einen Fehler
const StreamStatus = {
    IDLE: 'IDLE',
    OFFLINE: 'OFFLINE',
    ONLINE: 'ONLINE',
} as const;

interface StreamResponse {
    status: keyof typeof StreamStatus;
}

function handleResponse(response: StreamResponse) {
    switch (response.status) {
        case StreamStatus.ONLINE:
          console.log('You are online.');
          break;
        case StreamStatus.OFFLINE:
          console.log('You are offline.');
          break;
        default:
      // error TS2322
        const status: never = response.status;
        throw new Error(`Unhandled status "${status}"!`);
    }
}

---

Listing 7: JSON-Serialisierung findet erst nach Prüfen der Konfiguration statt
type Configuration = Partial<{
    format: {
        indent: string | number;
    };
}>;

function serialize(object: {}, config?: Configuration) {
    // Existence check
    if (config && config.format) {
        return JSON.stringify(object, null, config.format.indent);
    }
    return JSON.stringify(object);
}

const string = serialize({
    age: 36,
    name: 'Benny Code'
}, {format: {indent: 4}});

console.log(string);

---

Listing 8: Konfigurationsprüfung durch optionale Verkettung vereinfachen
type Configuration = Partial<{
    format: {
        indent: string | number;
    };
}>;

function serialize(object: {}, config?: Configuration) {
    // Existence check
    return JSON.stringify(object, null, config?.format?.indent);
}

const string = serialize({
    age: 36,
    name: 'Benny Code'
}, {format: {indent: 4}});

console.log(string);

---

Listing 9: Standardparameter discount führt Rabatt ein
function applyDiscount(price: number, discount = 0) {
    return price * (1 - discount);
}

console.log(applyDiscount(100)); // Returns: 100
console.log(applyDiscount(100, 0.5)); // Returns: 50

---

Listing 10: Prüfen, ob ein Wert geladen werden konnte
async function encodeMessage(message: string) {
    const encoder = new TextEncoder();
    const data = encoder.encode(message);
    return crypto.subtle.digest('SHA-256', data);
}

const message = window.localStorage.getItem('message');

if (message) {
    // TypeScript knows now that `message` is defined
    encodeMessage(message);
}

---

Listing 11: Saubere und wiederverwendbare Typsicherung mit der Assertion-Funktion isString
async function encodeMessage(message: string) {
    const encoder = new TextEncoder();
    const data = encoder.encode(message);
    return window.crypto.subtle.digest('SHA-256', data);
}

// Assertion Function
function isString(value: unknown): asserts value is string {
    if (typeof value !== 'string') {
        throw new Error(`${value} is not a string.`);
    }
}

const message = window.localStorage.getItem('message');
isString(message);
// TypeScript knows now that `message` is defined
await encodeMessage(message);


---

Listing 12: Type Guard hasErrorCode hilft beim Identifizieren von Fehlern
// Type Guard
const hasErrorCode = (error: unknown): error is { code: number } => {
    if (
        // Existence check
        error
        // Type check
        && typeof error === 'object'
        // Property check
        && 'code' in error
        // Property type check
        && typeof error.code === 'number') {
        return true;
    }
    return false;
};

try {
    throw {code: 72};
} catch (error: unknown) {
    if (hasErrorCode(error)) {
        console.error(`Failed with error code "${error.code}".`);
    }
}

---

Listing 13: Type Guards der Axios-Bibliothek sind in TypeScript importierbar
import axios, {isAxiosError} from 'axios';

try {
    await axios.get('https://typescript.tv/undefined');
} catch (error: unknown) {
    if (isAxiosError(error)) {
        console.log(`Failed with HTTP status "${error.response?.status}".`);
    }
}

---

Listing 14: Type Guard isEmail gibt Auskunft darüber, ob eine E-Mail-Adresse zu erkennen ist
type Email = string & { __brand: 'Email' };

// Type Guard with Type Predicate
function isEmail(email: string): email is Email {
    return email.includes('@');
}

function sendEmail(email: Email) {
    // Demo
    console.log(email);
}

const input = 'benny@typescript.tv';
if (isEmail(input)) {
    sendEmail(input);
}

---


