阿里云主机折上折
  • 微信号
Current Site:Index > Defensive programming: How to write "robust" rather than "fragile" code?

Defensive programming: How to write "robust" rather than "fragile" code?

Author:Chuan Chen 阅读数:12974人阅读 分类: 前端综合

Defensive programming is a coding philosophy that anticipates and handles potential issues, with its core principle being the assumption that all external inputs may be erroneous and system dependencies may fail at any time. Through boundary checks, exception handling, default value processing, and other means, it ensures that code can remain stable or degrade gracefully under unexpected circumstances.

Core Principles of Defensive Programming

1. Trust No External Input

All data from users, APIs, or local storage should be treated as potential threats. Form inputs, URL parameters, and third-party API responses must undergo strict validation:

// Bad practice: Directly using URL parameters
const productId = window.location.search.split('=')[1]

// Defensive approach
function getSafeProductId() {
  const params = new URLSearchParams(window.location.search)
  const id = params.get('id')
  return /^\d+$/.test(id) ? parseInt(id) : null
}

2. Minimize the Impact of Failures

When a module fails, it should act like a circuit breaker rather than causing a chain reaction:

// Fragile design: A function handling multiple responsibilities
function processUserData(rawData) {
  const data = JSON.parse(rawData)
  updateDashboard(data.stats)
  saveToLocalStorage(data.prefs)
  renderUserProfile(data.user)
}

// Defensive improvement: Separation of concerns + error isolation
function safeParse(json) {
  try {
    return { success: true, data: JSON.parse(json) }
  } catch {
    return { success: false, error: 'INVALID_JSON' }
  }
}

function processUserData(rawData) {
  const { success, data, error } = safeParse(rawData)
  if (!success) return handleError(error)
  
  // Independent error handling for each module
  try { updateDashboard(data?.stats) } catch(e) { console.error(e) }
  try { saveToLocalStorage(data?.prefs) } catch(e) { console.error(e) }
  try { renderUserProfile(data?.user) } catch(e) { console.error(e) }
}

Frontend-Specific Defensive Strategies

1. DOM Operation Protection

The browser environment is highly unpredictable, and elements may be modified by other scripts:

// Traditional approach
document.getElementById('submit-btn').addEventListener('click', handler)

// Enhanced defensive approach
function safeAddListener(selector, eventType, handler, options) {
  const element = document.querySelector(selector)
  if (!element || !element.addEventListener) {
    return false
  }
  element.addEventListener(eventType, handler, options)
  return true
}

safeAddListener('#submit-btn', 'click', () => {
  // Event handling logic
}, { once: true })

2. API Communication Handling

Network requests must account for timeouts, interruptions, and malformed data:

async function fetchWithFallback(url, options = {}) {
  const controller = new AbortController()
  const timeoutId = setTimeout(() => controller.abort(), options.timeout || 10000)
  
  try {
    const response = await fetch(url, {
      ...options,
      signal: controller.signal
    })
    
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`)
    }
    
    const contentType = response.headers.get('content-type')
    if (!contentType?.includes('application/json')) {
      throw new Error('Invalid content type')
    }
    
    return await response.json()
  } catch (error) {
    if (error.name === 'AbortError') {
      console.warn('Request timed out')
      return cachedData || null
    }
    throw error
  } finally {
    clearTimeout(timeoutId)
  }
}

Type-Safety Defensive Techniques

1. Runtime Type Checking

Even with TypeScript, compile-time type checks may fail at runtime:

// User configuration validation
function validateConfig(config) {
  const schema = {
    theme: value => ['light', 'dark'].includes(value),
    fontSize: value => Number.isInteger(value) && value >= 12 && value <= 24,
    notifications: value => typeof value === 'boolean'
  }
  
  return Object.entries(schema).every(([key, validator]) => {
    return validator(config[key])
  })
}

// Usage example
const userConfig = JSON.parse(localStorage.getItem('config') || '{}')
if (!validateConfig(userConfig)) {
  resetToDefaultConfig()
}

2. Optional Chaining and Nullish Coalescing

Modern JavaScript syntax provides cleaner defensive patterns:

// Old defensive code
const street = user && user.address && user.address.street

// Modern equivalent
const street = user?.address?.street ?? 'Unknown'

// Function call protection
api.getUserInfo?.().then(...)

Immutable Data Practices

1. Avoid Direct State Mutations

Frontend framework state management requires special attention:

// Vue example
data() {
  return {
    user: {
      name: '',
      permissions: []
    }
  }
},
methods: {
  // Unsafe approach
  addPermission(perm) {
    this.user.permissions.push(perm)
  },
  
  // Defensive approach
  safeAddPermission(perm) {
    this.user = {
      ...this.user,
      permissions: [...this.user.permissions, perm]
    }
  }
}

2. Deep Freeze Critical Configurations

Prevent accidental modifications to core configuration objects:

function deepFreeze(obj) {
  Object.keys(obj).forEach(prop => {
    if (typeof obj[prop] === 'object' && !Object.isFrozen(obj[prop])) {
      deepFreeze(obj[prop])
    }
  })
  return Object.freeze(obj)
}

const config = deepFreeze({
  api: {
    baseURL: 'https://api.example.com',
    timeout: 5000
  }
})

// Any subsequent modification attempts will throw errors in strict mode
config.api.timeout = 10000 // TypeError

The Art of Error Handling

1. Categorized Error Handling

Different errors require different handling levels:

class NetworkError extends Error {
  constructor(message) {
    super(message)
    this.name = 'NetworkError'
    this.isRecoverable = true
  }
}

class AuthError extends Error {
  constructor(message) {
    super(message)
    this.name = 'AuthError'
    this.requiresRelogin = true
  }
}

async function fetchProtectedData() {
  try {
    const response = await fetch('/api/protected')
    if (response.status === 401) {
      throw new AuthError('Session expired')
    }
    return response.json()
  } catch (error) {
    if (error.name === 'AuthError') {
      showLoginModal()
    } else if (error.name === 'NetworkError') {
      retryAfterDelay()
    } else {
      logErrorToService(error)
      throw error
    }
  }
}

2. Error Boundary Design

React error boundary component example:

class ErrorBoundary extends React.Component {
  state = { hasError: false }
  
  static getDerivedStateFromError() {
    return { hasError: true }
  }
  
  componentDidCatch(error, info) {
    logErrorToService(error, info.componentStack)
  }
  
  render() {
    if (this.state.hasError) {
      return (
        <div className="fallback-ui">
          <h2>Something went wrong</h2>
          <button onClick={() => window.location.reload()}>
            Refresh Page
          </button>
        </div>
      )
    }
    return this.props.children
  }
}

// Usage
<ErrorBoundary>
  <UnstableComponent/>
</ErrorBoundary>

Performance Protection Measures

1. Debouncing and Throttling

Defensive strategies for high-frequency events:

function createDebouncer(delay = 300) {
  let timer = null
  return function(fn) {
    clearTimeout(timer)
    timer = setTimeout(() => {
      timer = null
      try {
        fn()
      } catch (e) {
        console.error('Debounced function error:', e)
      }
    }, delay)
  }
}

// Usage example
const searchDebouncer = createDebouncer(500)
inputElement.addEventListener('input', () => {
  searchDebouncer(() => searchAPI(inputElement.value))
})

2. Memory Leak Prevention

Common SPA issue defenses:

// Cleanup on component unmount
useEffect(() => {
  const controller = new AbortController()
  fetchData({ signal: controller.signal })
  
  return () => {
    controller.abort()
    clearAllTimeouts() // Custom cleanup function
    window.removeEventListener('resize', handleResize)
  }
}, [])

// HOC for memory leak detection
function withMemoryLeakDetection(WrappedComponent) {
  return function(props) {
    const [leakReport, setLeakReport] = useState(null)
    
    useEffect(() => {
      const initialMemory = performance.memory.usedJSHeapSize
      
      return () => {
        const delta = performance.memory.usedJSHeapSize - initialMemory
        if (delta > 1024 * 1024) { // Potential leak if over 1MB
          setLeakReport(`Possible leak: ${Math.round(delta/1024)}KB`)
        }
      }
    }, [])
    
    return (
      <>
        <WrappedComponent {...props} />
        {leakReport && <div className="leak-warning">{leakReport}</div>}
      </>
    )
  }
}

Environment Difference Handling

1. Browser Feature Detection

Avoid direct browser type checking:

// Not recommended
if (navigator.userAgent.includes('Chrome')) {
  useChromeSpecificAPI()
}

// Defensive approach
if ('IntersectionObserver' in window) {
  // Use modern API
} else {
  // Fallback solution
}

// Progressive enhancement loading strategy
function loadPolyfillIfNeeded(feature, polyfillUrl) {
  if (!feature in window) {
    return new Promise((resolve, reject) => {
      const script = document.createElement('script')
      script.src = polyfillUrl
      script.onload = resolve
      script.onerror = reject
      document.body.appendChild(script)
    })
  }
  return Promise.resolve()
}

await loadPolyfillIfNeeded('fetch', '/polyfills/fetch.js')

2. Environment Variable Protection

Handling build-time injected variables:

// Unsafe approach
const apiUrl = process.env.API_URL

// Defensive handling
function getEnvVar(key) {
  const value = process.env[key]
  if (value === undefined) {
    if (import.meta.env.PROD) {
      throw new Error(`Missing required env var: ${key}`)
    }
    return getDefaultValue(key)
  }
  return value
}

const apiUrl = getEnvVar('API_URL') || 'https://api.fallback.com'

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.