Skip to content

Instantly share code, notes, and snippets.

@jordangarcia
Created June 25, 2026 19:41
Show Gist options
  • Select an option

  • Save jordangarcia/7efc8bd4ec0ca826ff1b83fdf5f061cb to your computer and use it in GitHub Desktop.

Select an option

Save jordangarcia/7efc8bd4ec0ca826ff1b83fdf5f061cb to your computer and use it in GitHub Desktop.

Coding Style Guide for Al

Core Programming Philosophy

Functional Programming Preference

  • Strong preference for functional programming patterns
  • Use map, filter, reduce extensively rather than imperative loops
  • Favor immutable data transformations over mutation
  • Create pure functions where possible
  • Use function composition and higher-order functions
  • Prefer building the "piping" or "system" that data can flow through, making it easy to test and reason about

Error Handling Strategy

  • Implement robust error handling with multiple fallback strategies
  • Always include comprehensive try-catch blocks in async operations
  • Log meaningful error messages with context
  • Prefer graceful degradation over hard failures

Code Organization Principles

  • Create small, focused functions with single responsibilities
  • Extract reusable utilities into generic functions
  • Use descriptive variable names but keep them concise
  • Prioritize readability and maintainability over cleverness
  • Dont overuse barrel imports

Language-Specific Patterns

TypeScript/JavaScript

  • Use type annotations but rely on inference when types are obvious

React Development

  • Prefer to keep things out of react and hooks unless absolutely necesssary
  • I despise useEffect, try to only use with empty or very little dependencies. useUeffect usage should not depend on dependencies mutating
  • Use conditional rendering patterns effectively

Async/Await Patterns

  • Implement proper error handling for all async operations

Code Style

Readable, staged TypeScript. Dense but visually paragraphed, with comments used as operational memory, not decoration.

Code should feel like data moving through a small system: gather inputs, normalize, transform, execute, finalize.

Shape

  • Use one blank line to separate ideas.
  • Do not use multiple blank lines as padding.
  • Group code into visible phases: setup, derived values, validation, execution, cleanup.
  • Prefer named intermediate values over dense inline expressions.
  • Long functions are acceptable when each phase is visually obvious.
  • Extract helpers when they create a real boundary or reusable transformation. Do not extract tiny one-off helpers just to hide two lines.
const messages = await buildMessages(children, {
  includeImages: true,
})

const request = {
  model,
  messages,
  timeoutMs,
}

const requestToLog = cleanRequest(request)
logger.info('Request started', requestToLog)

const response = await client.send(request)

return normalizeResponse(response)

Conditions

  • Conditions should read like predicates.
  • Always use braces.
  • Put the body on its own lines.
  • Prefer multiline conditions when any part is non-trivial.
  • Never assign or do meaningful work inside a condition.
  • Compute named values first, then branch on them.
const hasContent = message.parts.some((part) => {
  if (part.type === 'text') {
    return part.text.length > 0
  }

  return part.type === 'image'
})

if (!hasContent) {
  return null
}
if (event.type === 'content' && event.delta.type === 'text') {
  output += event.delta.text
  yield event.delta.text
}

Avoid this shape:

if ((chunk = event.next()) && chunk.text) {
  yield chunk.text
}

Prefer:

const chunk = event.next()
const hasText = chunk && chunk.text

if (hasText) {
  yield chunk.text
}

Control Flow

  • Prefer early returns over nested conditionals.
  • Keep immediate guards attached to their setup variables; no blank line is needed between initialization and an early return.
  • Put the blank line after the guard, before the next phase.
  • Put one blank line before the final success return when it follows setup or derived variables.
  • Use continue when it keeps the main path flat.
  • Use switch when branching over a small tagged domain.
  • Use loops when order, async iteration, cleanup, or short-circuiting matter.
  • Use map, filter, and reduce for data conversion boundaries.
if (!client) {
  throw new Error('Missing client')
}

if (!items.length) {
  return []
}

return items
  .filter((item) => item.enabled)
  .map((item) => ({
    id: item.id,
    label: item.name.trim(),
  }))
const id = request.param('id')
const session = await getSession(id)
if (!session) {
  return jsonResponse({ error: 'session not found' }, 404)
}

const payload = await buildPayload(session)

return jsonResponse(payload)
for await (const event of stream) {
  if (event.type === 'metadata') {
    metadata = event.value
    continue
  }

  if (event.type === 'content') {
    output += event.text
    yield event.text
  }
}

Comments

Comments explain hidden context, not obvious code.

Use comments for:

  • provider/API quirks
  • async lifecycle traps
  • TypeScript limitations
  • intentional deviations
  • retry/fallback reasoning
  • examples of external payloads
  • uncertainty worth preserving
// The remote API returns 200 with an empty payload for filtered content.
// Treat that as a non-retryable refusal so callers can handle it explicitly.
if (text.length === 0 && finishReason === 'filtered') {
  throw new CompletionError('Filtered response', false)
}
// TypeScript can't infer that this value is initialized by attachStream().
// Keep the runtime check so misuse fails at the boundary.
if (!this.stream) {
  throw new Error('Stream not attached')
}

Rough internal notes are fine when they encode useful history:

// TODO(al): this probably wants a typed span later
// maybe split request metadata from response metadata

Avoid comments that only narrate syntax:

// Increment count by one
count += 1

Error Handling

  • Wrap async/provider boundaries with explicit error handling.
  • Preserve original errors when useful.
  • Prefer graceful degradation when the caller can continue.
  • Log with enough context to debug without re-running locally.
try {
  const result = await fetchImage(url)
  return {
    success: true,
    data: result,
  }
} catch (error) {
  logger.warn(`Failed to fetch image (url=${url})`, error)

  if (failOnError) {
    throw error
  }

  return {
    success: false,
    fallbackText: `[Image unavailable: ${url}]`,
  }
}

Types

  • Let types document the shape of the system.
  • Use tagged unions for state and results.
  • Put short comments on union variants when they clarify meaning.
  • Avoid any unless the boundary truly requires it.
type ImageData =
  // from URL, provider can fetch directly
  | {
      url: string
      data: null
      mediaType: null
    }
  // downloaded locally
  | {
      url: string
      data: string
      mediaType: ImageMediaType
    }
  // caller supplied base64
  | {
      url: null
      data: string
      mediaType: ImageMediaType
    }

Formatting Details

  • Top-level imports only.
  • Keep object literals and function calls vertically readable.
  • Prefer explicit object construction over clever spreading when behavior would be unclear.
const payload = {
  id,
  status,
  metadata: {
    source,
    retryCount,
  },
}

Review Checklist

  • Can the reader see the phases from blank lines alone?
  • Do conditions read as predicates?
  • Are all branches brace-delimited and multiline?
  • Is any assignment hidden inside a condition?
  • Are comments explaining context instead of syntax?
  • Are async/provider boundaries wrapped with useful errors?
  • Is data conversion mostly functional and control flow mostly flat?

Problem-Solving Approach

Debugging Strategy

  • Add logging at key points to trace execution flow
  • Use descriptive console.log messages with context
  • Implement debugging utilities when working with complex data flows
  • Create helper functions for common debugging tasks

Code Simplification

  • Always look for opportunities to reduce complexity

  • Extract common patterns into reusable utilities

  • Prefer declarative over imperative code

  • Remove unnecessary nesting and conditionals Abstraction is acceptable, even preferable, when it makes the system more:

  • understandable

  • predictable

  • reusable/composable

  • testable

  • easier to reason about as data flowing through a system

Not abstraction for abstraction’s sake, and not tiny helpers for one call site. More like: build a small, coherent system boundary/pipeline if it makes behavior easier to verify and extend.

Development Practices

  • Write code that is easy to test and debug
  • Create modular, composable functions
  • Use consistent patterns across the codebase
  • Document complex logic with clear comments

When Helping with Code

  • Always suggest the most maintainable solution
  • Provide functional alternatives when appropriate
  • Include proper error handling in all async operations
  • Explain trade-offs between different approaches
  • Focus on creating reusable, composable solutions
  • Consider performance implications of suggested solutions

Git

  • When creating branches, prefix them with al/ to indicate they came from me.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment