Skip to main content

Understand the Acronym Spaghetti: Part 2 - The Quality Principles

Understand the Acronym Spaghetti: Part 2 - The Quality Principles

TLDR

  • Pure Functions: Same input always gives same output
  • Immutability: Create new objects instead of changing existing ones
  • Principle of Least Surprise: Code should behave as expected
  • Design by Contract: Define clear input/output rules
  • Explicit is better than Implicit: Be clear, not clever

In Part 1, we covered The Foundation - the core principles: SRP, DRY, KISS, YAGNI, Fail Fast, GIGO, and Separation of Concerns.

Code Quality & Safety principles take your code quality from "it works" to "it works well."

Code Quality & Safety

These are best practices that make your code more predictable, testable, and maintainable.

Pure Functions

Definition: A function that always returns the same output for the same input and has no side effects.

Why it matters: Predictable, testable, and easy to reason about. No hidden dependencies or surprises.

When to break this rule: When you need to interact with the outside world (APIs, files, databases) or when performance requires caching/memoization.

Simple calculation function

// ❌ Function with side effects and external dependencies
let counter = 0;
const addToCounter = (value: number) => {
  counter += value; // Side effect - modifies external state
  console.log('Added:', value); // Side effect - I/O operation
  return counter; // Output depends on external state
};

// ✅ Same functionality, but pure
const add = (a: number, b: number): number => {
  return a + b; // Same input always produces same output
};

React component with calculations

// ❌ Component with impure calculations
const ProductCard = ({ product }: { product: Product }) => {
  const [clickCount, setClickCount] = useState(0);
  
  const calculateDiscount = () => {
    const now = new Date(); // Non-deterministic - depends on current time
    const isWeekend = now.getDay() === 0 || now.getDay() === 6;
    setClickCount(prev => prev + 1); // Side effect - modifies state
    return isWeekend ? 0.2 : 0.1;
  };
  
  return <div>Price: ${product.price * (1 - calculateDiscount())}</div>;
};

// ✅ Same component, but with pure calculations
const ProductCard = ({ product, isWeekend }: { product: Product; isWeekend: boolean }) => {
  const calculateDiscount = (isWeekend: boolean) => {
    return isWeekend ? 0.2 : 0.1; // Pure - same input, same output
  };
  
  const discountedPrice = product.price * (1 - calculateDiscount(isWeekend));
  
  return <div>Price: ${discountedPrice}</div>;
};

User name formatting function

// ❌ Function with external dependencies and side effects
const formatUserName = (user: User) => {
  const settings = getGlobalSettings(); // External dependency
  localStorage.setItem('lastUser', user.id); // Side effect
  
  return settings.showFullName 
    ? `${user.firstName} ${user.lastName}`
    : user.firstName;
};

// ✅ Same functionality, but pure with explicit dependencies
const formatUserName = (user: User, showFullName: boolean): string => {
  return showFullName 
    ? `${user.firstName} ${user.lastName}`
    : user.firstName;
};

// Side effects handled separately
const saveUserAction = (userId: string) => {
  localStorage.setItem('lastUser', userId);
};

Common Pitfall: Mixing pure logic with I/O operations, making functions hard to test and unpredictable.


Immutability

Definition: Don't mutate objects — return new ones instead. Data structures should not be changed after creation.

Why it matters: Prevents bugs from unexpected changes, makes code easier to reason about, enables better performance optimizations.

When to break this rule: Performance-critical code where creating new objects is too expensive, or when working with large datasets where mutation is necessary.

Add item to cart function

// ❌ Function that mutates input array
const addItemToCart = (cart: CartItem[], item: CartItem) => {
  cart.push(item); // Mutates the original array
  return cart;
};

// ✅ Same functionality, but returns new array
const addItemToCart = (cart: CartItem[], item: CartItem): CartItem[] => {
  return [...cart, item]; // Creates new array
};

React shopping cart component

// ❌ Component with direct state mutation
const ShoppingCart = () => {
  const [cart, setCart] = useState<CartItem[]>([]);
  
  const addItem = (item: CartItem) => {
    cart.push(item); // Mutates state directly
    setCart(cart); // React won't detect the change
  };
  
  const updateQuantity = (itemId: string, quantity: number) => {
    const item = cart.find(i => i.id === itemId);
    if (item) {
      item.quantity = quantity; // Mutates nested object
      setCart(cart);
    }
  };
  
  return <div>{cart.map(item => <CartItem key={item.id} item={item} />)}</div>;
};

// ✅ Same component, but with immutable state updates
const ShoppingCart = () => {
  const [cart, setCart] = useState<CartItem[]>([]);
  
  const addItem = (item: CartItem) => {
    setCart(prevCart => [...prevCart, item]); // Creates new array
  };
  
  const updateQuantity = (itemId: string, quantity: number) => {
    setCart(prevCart => 
      prevCart.map(item => 
        item.id === itemId 
          ? { ...item, quantity } // Creates new object
          : item
      )
    );
  };
  
  return <div>{cart.map(item => <CartItem key={item.id} item={item} />)}</div>;
};

User profile update function

// ❌ Function that mutates user object
const updateUserProfile = (user: User, updates: Partial<User>) => {
  user.name = updates.name || user.name; // Mutates original
  user.email = updates.email || user.email; // Mutates original
  user.updatedAt = new Date(); // Mutates original
  return user;
};

// ✅ Same functionality, but returns new user object
const updateUserProfile = (user: User, updates: Partial<User>): User => {
  return {
    ...user,
    ...updates,
    updatedAt: new Date()
  }; // Creates completely new object
};

Common Pitfall: Thinking immutability means you can't change data at all, instead of creating new versions of data structures.


Principle of Least Surprise

Definition: Code should behave as people intuitively expect. Functions, variables, interfaces, and folder structures should do what their names suggest.

Why it matters: Reduces cognitive load, makes code self-documenting, prevents bugs from misunderstanding.

When to break this rule: When following domain-specific conventions that might seem unusual but are standard in that field.

User data fetching function

// ❌ Function with misleading name and hidden side effects
const getUser = (id: string) => {
  const user = database.findUser(id);
  user.lastAccessed = new Date(); // Surprise! It modifies data
  analytics.track('user_accessed', { userId: id }); // Surprise! It tracks events
  return user;
};

// ✅ Same functionality, but explicit and separated
const getUser = (id: string): User => {
  return database.findUser(id); // Just gets the user
};

// Side effects are separate and explicit
const trackUserAccess = (userId: string) => {
  analytics.track('user_accessed', { userId });
};

const updateUserLastAccessed = (userId: string) => {
  database.updateUser(userId, { lastAccessed: new Date() });
};

React user list component

// ❌ Component with unexpected mutations during filtering
const UserList = ({ users }: { users: User[] }) => {
  const [searchTerm, setSearchTerm] = useState('');
  
  const filteredUsers = users.filter(user => {
    // Surprise! Filtering also sorts and modifies original data
    user.displayName = user.firstName + ' ' + user.lastName;
    return user.name.includes(searchTerm);
  }).sort((a, b) => a.name.localeCompare(b.name));
  
  return (
    <div>
      <input onChange={e => setSearchTerm(e.target.value)} />
      {filteredUsers.map(user => <div key={user.id}>{user.displayName}</div>)}
    </div>
  );
};

// ✅ Same component, but clear and predictable
const UserList = ({ users }: { users: User[] }) => {
  const [searchTerm, setSearchTerm] = useState('');
  
  const filteredUsers = users
    .filter(user => user.name.includes(searchTerm))
    .map(user => ({
      ...user,
      displayName: `${user.firstName} ${user.lastName}`
    }))
    .sort((a, b) => a.name.localeCompare(b.name));
  
  return (
    <div>
      <input onChange={e => setSearchTerm(e.target.value)} />
      {filteredUsers.map(user => <div key={user.id}>{user.displayName}</div>)}
    </div>
  );
};

User status checking function

// ❌ Function with confusing boolean logic
const isUserActive = (user: User): boolean => {
  // Surprise! Returns false for active users
  return user.status === 'inactive' || user.lastLogin < Date.now() - 30 * 24 * 60 * 60 * 1000;
};

// ✅ Same functionality, but clear boolean logic
const isUserActive = (user: User): boolean => {
  return user.status === 'active' && user.lastLogin > Date.now() - 30 * 24 * 60 * 60 * 1000;
};

Folder structure organization

❌ Misleading folder organization
src/
├── helpers/
│   ├── userValidation.ts  // Actually business logic, not helpers
│   ├── paymentService.ts  // Actually a service, not a helper
│   └── Button.tsx         // Surprise! UI component in helpers
├── utils/
│   ├── ApiClient.ts       // Actually a service, not a utility
│   ├── formatDate.ts      // Actually a utility ✓
│   └── UserModel.ts       // Actually a model, not a utility
├── app/
│   ├── LoginPage.tsx      // Surprise! Page component in app folder
│   └── database.ts        // Surprise! Infrastructure in app folder
└── pages/
    ├── userHelpers.ts     // Surprise! Helper functions in pages
    └── api.ts             // Surprise! API client in pages

✅ Clear folder organization
src/
├── components/
│   ├── UserList.tsx
│   └── Button.tsx
├── services/
│   ├── paymentService.ts
│   ├── userService.ts
│   └── apiClient.ts
├── utils/
│   ├── dateHelpers.ts
│   └── formatters.ts
├── models/
│   └── UserModel.ts
├── business/
│   └── userValidation.ts
└── pages/
    └── LoginPage.tsx

Common Pitfall: Using names that don't accurately describe what the code actually does.


Design by Contract

Definition: Define input/output conditions clearly. Specify what a function expects (preconditions) and what it guarantees (postconditions).

Why it matters: Makes function behavior explicit, catches bugs early, improves documentation and testing.

When to break this rule: Simple, obvious functions where the contract is self-evident from the name and types.

Price discount calculation function

// ❌ Function with unclear contract
const calculateDiscount = (price: any, userType: any) => {
  // What happens if price is negative? What userTypes are valid?
  if (userType === 'premium') return price * 0.8;
  if (userType === 'regular') return price * 0.9;
  return price; // Silent fallback - no indication this can happen
};

// ✅ Same functionality, but explicit contract
const calculateDiscount = (price: number, userType: 'premium' | 'regular'): number => {
  // Preconditions: price >= 0, userType must be 'premium' or 'regular'
  if (price < 0) throw new Error('Price must be non-negative');
  
  // Postconditions: returns discounted price (0.8x for premium, 0.9x for regular)
  if (userType === 'premium') return price * 0.8;
  if (userType === 'regular') return price * 0.9;
  
  // This line should never be reached due to TypeScript types
  throw new Error(`Invalid user type: ${userType}`);
};

React user profile component

// ❌ Component with unclear expectations
const UserProfile = ({ user }: { user: any }) => {
  // What if user is null? What properties are required?
  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
      <p>Member since: {user.joinDate.toLocaleDateString()}</p>
    </div>
  );
};

// ✅ Same component, but clear interface
interface UserProfileProps {
  user: {
    name: string;
    email: string;
    joinDate: Date;
  };
}

const UserProfile = ({ user }: UserProfileProps) => {
  // Contract: user object must have name, email, and joinDate
  // Guarantees: renders user information or throws error
  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
      <p>Member since: {user.joinDate.toLocaleDateString()}</p>
    </div>
  );
};

API update function

// ❌ API function with vague contract
const updateUser = async (id: string, data: any) => {
  // What fields can be updated? What errors can occur?
  const response = await fetch(`/api/users/${id}`, {
    method: 'PUT',
    body: JSON.stringify(data)
  });
  return response.json();
};

// ✅ Same functionality, but explicit contract
interface UpdateUserData {
  name?: string;
  email?: string;
}

const updateUser = async (id: string, data: UpdateUserData): Promise<User> => {
  if (!id) throw new Error('User ID is required');
  
  const response = await fetch(`/api/users/${id}`, {
    method: 'PUT',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(data)
  });
  
  if (!response.ok) throw new Error('Failed to update user');
  return response.json();
};

Common Pitfall: Assuming callers will know what inputs are valid and what outputs to expect without explicit documentation.


Explicit is better than implicit

Definition: Be clear rather than clever. Make your intentions obvious instead of relying on hidden assumptions or magic.

Why it matters: Code is read more often than it's written. Explicit code is easier to understand, debug, and maintain.

When to break this rule: When well-established conventions make implicit behavior clearer than explicit (like map or filter operations).

User processing function

// ❌ Function with implicit business logic
const processUsers = (users: User[]) => {
  return users
    .filter(u => u.active && u.level > 2) // What does level > 2 mean?
    .map(u => ({ ...u, bonus: u.sales * 0.1 })) // Why 0.1? What's the logic?
    .sort((a, b) => b.bonus - a.bonus); // Implicit descending sort
};

// ✅ Same functionality, but explicit business logic
const SENIOR_USER_LEVEL = 2;
const COMMISSION_RATE = 0.1;

const processUsers = (users: User[]) => {
  return users
    .filter(user => user.active && user.level > SENIOR_USER_LEVEL)
    .map(user => ({ ...user, bonus: user.sales * COMMISSION_RATE }))
    .sort((a, b) => b.bonus - a.bonus);
};

React component with magic numbers

// ❌ Component with magic numbers and implicit logic
const ProductList = ({ products }: { products: Product[] }) => {
  return (
    <div>
      {products
        .filter(p => p.price > 0) // Implicit business rule
        .map(p => (
          <div key={p.id}>
            <h3>{p.name}</h3>
            <p>${p.price}</p>
            {p.stock < 5 && <span>Low stock!</span>} {/* Magic number */}
          </div>
        ))}
    </div>
  );
};

// ✅ Same component, but explicit constants
const LOW_STOCK_THRESHOLD = 5;

const ProductList = ({ products }: { products: Product[] }) => {
  const availableProducts = products.filter(p => p.price > 0);
  
  return (
    <div>
      {availableProducts.map(product => (
        <div key={product.id}>
          <h3>{product.name}</h3>
          <p>${product.price}</p>
          {product.stock < LOW_STOCK_THRESHOLD && (
            <span>Low stock!</span>
          )}
        </div>
      ))}
    </div>
  );
};

API configuration

// ❌ Configuration with magic numbers
const API_CONFIG = {
  baseUrl: process.env.API_URL || 'https://api.example.com',
  timeout: 5000, // Magic number - why 5000?
  retries: 3, // Magic number - why 3?
};

// ✅ Same configuration, but explicit with documentation
const API_CONFIG = {
  baseUrl: process.env.API_URL || 'https://api.example.com',
  timeout: 5000, // 5 seconds - balance between user experience and server load
  retries: 3, // 3 attempts - enough to handle transient failures without overloading
  retryDelay: 1000, // 1 second between retries
} as const;

Common Pitfall: Using magic numbers, unexplained business rules, or clever one-liners that require mental parsing to understand.


Summary

Code Quality & Safety principles help you write code that's not just functional, but robust and reliable. They're the difference between code that works today and code that works in six months when you (or someone else) needs to modify it.

Coming next: Part 3 covers SOLID Architecture principles, and Part 4 explores System Architecture patterns.

Remember: good code is not about showing off how smart you are - it's about making life easier for the next person who reads it (including future you).