Skip to main content
โšก Calmops

Type Systems and Static Analysis: Building Safer Code

Introduction

Static analysis and strong typing have revolutionized software development. By catching errors at compile time rather than runtime, teams can prevent bugs and build more maintainable systems. This guide covers type systems, static analysis tools, and best practices for leveraging types effectively.

Static analysis examines code without executing it, identifying potential errors, code smells, and security vulnerabilities early in development.

Type Systems

Static vs Dynamic Typing

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚               Static vs Dynamic Typing                        โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚                                                             โ”‚
โ”‚  Static (Java, TypeScript):                                 โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”               โ”‚
โ”‚  โ”‚ Compile time: Type checking            โ”‚               โ”‚
โ”‚  โ”‚ int x = 5;  // Type known              โ”‚               โ”‚
โ”‚  โ”‚ x = "hello"; // โŒ Compilation error   โ”‚               โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜               โ”‚
โ”‚                                                             โ”‚
โ”‚  Dynamic (Python, JavaScript):                              โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”               โ”‚
โ”‚  โ”‚ Runtime: Type checking                 โ”‚               โ”‚
โ”‚  โ”‚ x = 5                                  โ”‚               โ”‚
โ”‚  โ”‚ x = "hello"  // โœ“ Works                โ”‚               โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜               โ”‚
โ”‚                                                             โ”‚
โ”‚  Gradual (TypeScript, Python with hints):                   โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”               โ”‚
โ”‚  โ”‚ Optional static typing                 โ”‚               โ”‚
โ”‚  โ”‚ def foo(x: int) -> str: ...           โ”‚               โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜               โ”‚
โ”‚                                                             โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

TypeScript Configuration

{
  "compilerOptions": {
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "forceConsistentCasingInFileNames": true,
    "skipLibCheck": true,
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "bundler"
  }
}

Python Type Hints

from typing import Optional, List, Dict, Union, Any, Protocol
from dataclasses import dataclass

# Basic types
def greet(name: str) -> str:
    return f"Hello, {name}!"

# Optional types
def find_user(user_id: int) -> Optional[User]:
    """Returns User if found, None otherwise."""
    return None

# Complex types
def process_orders(orders: List[Dict[str, Any]]) -> Dict[str, float]:
    """Calculate totals by user."""
    totals: Dict[str, float] = {}
    for order in orders:
        user_id = order["user_id"]
        total = order["total"]
        totals[user_id] = totals.get(user_id, 0) + total
    return totals

# Union types
def parse_value(value: str) -> Union[int, float, str]:
    """Parse string to number or keep as string."""
    try:
        return int(value)
    except ValueError:
        try:
            return float(value)
        except ValueError:
            return value

# Protocol for duck typing
class Reader(Protocol):
    def read(self) -> str: ...

def process_reader(reader: Reader) -> int:
    """Process any reader-like object."""
    return len(reader.read())

# Type guards
from typing import TypeGuard

def is_string_list(val: List[Any]) -> TypeGuard[List[str]]:
    """Narrow to list of strings."""
    return all(isinstance(x, str) for x in val)

Static Analysis Tools

Python mypy

# Install mypy
pip install mypy

# Run type checking
mypy src/

# With strict mode
mypy --strict src/

# Configuration
# pyproject.toml or mypy.ini
[mypy]
python_version = 3.11
warn_return_any = True
warn_unused_configs = True
disallow_untyped_defs = True

TypeScript ESLint

{
  "extends": [
    "eslint:recommended",
    "plugin:@typescript-eslint/recommended",
    "plugin:@typescript-eslint/recommended-requiring-type-checking"
  ],
  "parser": "@typescript-eslint/parser",
  "parserOptions": {
    "project": "./tsconfig.json"
  },
  "rules": {
    "@typescript-eslint/no-unused-vars": "error",
    "@typescript-eslint/no-explicit-any": "error",
    "@typescript-eslint/explicit-function-return-type": "warn"
  }
}

Pre-commit Hooks

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v4.5.0
    hooks:
      - id: trailing-whitespace
      - id: end-of-file-fixer
      - id: check-yaml
      
  - repo: https://github.com/pycqa/flake8
    rev: 7.0.0
    hooks:
      - id: flake8
        args: [--max-line-length=100]
        
  - repo: https://github.com/pre-commit/mirrors-mypy
    rev: v1.7.0
    hooks:
      - id: mypy
        args: [--strict, --ignore-missing-imports]
        
  - repo: https://github.com/typescript-eslint/typescript-eslint
    rev: v6.21.0
    hooks:
      - id: eslint
        args: [--fix]

Advanced Type Patterns

Discriminated Unions

// TypeScript discriminated unions
type Result<T, E> = 
  | { success: true; data: T }
  | { success: false; error: E };

function handleResult<T, E>(result: Result<T, E>): string {
  if (result.success) {
    return `Data: ${result.data}`;
  }
  return `Error: ${result.error}`;
}

// Match pattern
type Shape = 
  | { kind: 'circle'; radius: number }
  | { kind: 'rectangle'; width: number; height: number }
  | { kind: 'triangle'; base: number; height: number };

function area(shape: Shape): number {
  switch (shape.kind) {
    case 'circle':
      return Math.PI * shape.radius ** 2;
    case 'rectangle':
      return shape.width * shape.height;
    case 'triangle':
      return 0.5 * shape.base * shape.height;
  }
}

Generic Constraints

// Generic constraints
function getProperty<T, K extends keyof T>(
  obj: T, 
  key: K
): T[K] {
  return obj[key];
}

// Usage
const user = { name: "Alice", age: 30 };
const name = getProperty(user, "name"); // string
const age = getProperty(user, "age");    // number

// Constraint with interface
interface HasId {
  id: string;
}

function findById<T extends HasId>(items: T[], id: string): T | undefined {
  return items.find(item => item.id === id);
}

Type-safe Error Handling

// Result pattern in TypeScript
type Result<T, E = Error> = 
  | { ok: true; value: T }
  | { ok: false; error: E };

async function fetchUser(id: string): Promise<Result<User>> {
  try {
    const response = await fetch(`/api/users/${id}`);
    if (!response.ok) {
      return { ok: false, error: new Error(`HTTP ${response.status}`) };
    }
    const user = await response.json();
    return { ok: true, value: user };
  } catch (e) {
    return { ok: false, error: e as Error };
  }
}

// Using the Result type
const result = await fetchUser("123");
if (result.ok) {
  console.log(result.value.name); // TypeScript knows this is safe
} else {
  console.error(result.error.message);
}

Runtime Type Checking

Runtime Validation

# Pydantic for runtime validation
from pydantic import BaseModel, Field, validator
from typing import Optional, List
from datetime import datetime

class User(BaseModel):
    id: int
    email: str
    name: str = Field(..., min_length=1)
    age: Optional[int] = Field(None, ge=0, le=150)
    created_at: datetime = Field(default_factory=datetime.now)
    
    @validator('email')
    def email_must_be_valid(cls, v):
        if '@' not in v:
            raise ValueError('Invalid email')
        return v
    
    class Config:
        orm_mode = True

# Usage
user = User(id=1, email="[email protected]", name="Alice")
print(user.email)  # Type-safe at runtime too

Zod Schema Validation

// Zod for TypeScript runtime validation
import { z } from 'zod';

const UserSchema = z.object({
  id: z.number(),
  email: z.string().email(),
  name: z.string().min(1),
  age: z.number().min(0).optional(),
});

type User = z.infer<typeof UserSchema>;

// Validate at runtime
function createUser(data: unknown): User {
  return UserSchema.parse(data);
}

// Try parsing (no throw)
const result = UserSchema.safeParse(data);
if (result.success) {
  console.log(result.data.email); // TypeScript knows this is safe
} else {
  console.error(result.error);
}

Best Practices

  1. Enable strict mode: Catch more errors
  2. Type function signatures: Always define input/output types
  3. Use Type Guards: Narrow types conditionally
  4. Prefer explicit types: Don’t overuse any
  5. Document complex types: Use comments for complex logic
  6. Run checks in CI: Enforce types in pipeline

Conclusion

Static analysis and strong typing significantly reduce runtime errors and improve code maintainability. By leveraging modern type systems and analysis tools, teams can catch bugs early and build more reliable software.

Comments