Fourth Brother's AI Journey

Post-80s Breakthrough: From Programmer to AI's Fourth Dream

serious-products-vs-toys-eliminate-ai-try-try-try-fallback-mindset-do-real-product-development

The Difference Between Serious Products and Toys: Eliminate AI's "Try-Try-Try-Fallback" Mindset, Do Real Product Development

๐Ÿšจ The Most Dangerous Mentality in AI Programming: Developing Serious Products with a "Toy Mindset"

After observing numerous AI programming projects, I've discovered a worrying phenomenon: most developers are using "toy thinking" to develop serious products.

This "try-try-try-fallback" AI thinking pattern is becoming an invisible killer of product quality.

๐Ÿง  Toy Mindset vs Product Mindset: Fundamental Cognitive Differences

What is "Toy Thinking"?

AI's Typical Toy Thinking Pattern:

AI: This API call might fail, I'll add retry mechanism
AI: Database connection might have problems, I'll add fallback logic
AI: User input format might be wrong, I'll add multiple compatibility handling
AI: Third-party service might be down, I'll add degradation plan
AI: Configuration might be missing, I'll set default values

Result: Code filled with "try-try-try-fallback" defensive logic

AI's Inner Monologue:

  • "I want to ensure code runs under any circumstances"
  • "I want to consider all possible edge cases"
  • "I want to provide multiple degradation plans"
  • "I want to make the system robust enough"

Problems with This Thinking:

  • Masking real problems: Using fallback to cover up root causes
  • Increasing system complexity: Defensive code makes maintenance costs explode
  • Degrading user experience: Degraded functions often incomplete
  • Technical debt accumulation: Short-term "stability" exchanged for long-term chaos

What is "Product Thinking"?

Core Principles of Product Thinking:

Error unacceptable โ†’ Find root cause โ†’ Solve problem completely

Product Thinking's Practice Pattern:

API call failure โ†’ Immediately investigate failure cause โ†’ Fix API or adjust design
Database connection problem โ†’ Check configuration and network โ†’ Ensure connection stability
User input error โ†’ Improve frontend validation โ†’ Educate users on correct input
Third-party service unstable โ†’ Evaluate alternatives โ†’ Optimize service provider selection
Configuration missing โ†’ Validate at startup โ†’ Force complete configuration

Result: Problems completely solved, system stable and reliable

๐Ÿ’ก Three Disastrous Consequences of "Try-Try-Try-Fallback"

1. Problem Masking: Real Errors Never See the Light of Day

AI's Defensive Programming Trap:

// โŒ Toy thinking: Multi-layer degradation masks problems
async function fetchUserData(userId: string): Promise<UserData | null> {
  try {
    // Layer 1: Try primary API
    const response = await fetch(`/api/users/${userId}`)
    if (response.ok) {
      return await response.json()
    }
  } catch (error) {
    console.log('Primary API failed, trying fallback...')
  }

  try {
    // Layer 2: Degrade to cache
    const cachedData = await cache.get(`user:${userId}`)
    if (cachedData) {
      console.log('Using cached data')
      return JSON.parse(cachedData)
    }
  } catch (error) {
    console.log('Cache failed, using mock...')
  }

  try {
    // Layer 3: Degrade to mock data
    const mockUser = {
      id: userId,
      name: 'Unknown User',
      email: 'unknown@example.com',
      status: 'inactive'
    }
    console.log('Using mock data')
    return mockUser
  } catch (error) {
    console.log('All attempts failed')
    return null
  }
}

Problem Analysis:

  • API failures masked: Primary API problems never known
  • Users see fake data: Terrible user experience
  • Problems cannot be fixed: Operations staff can't see real errors
  • Technical debt accumulates: Layer upon layer of fallback makes code chaotic

โœ… Product Thinking: Let Problems Expose and Fix Immediately

// โœ… Clear error handling and quick fixes
async function fetchUserData(userId: string): Promise<UserData> {
  // Pre-validation: Verify API availability at startup
  if (!process.env.USER_API_URL) {
    throw new Error('USER_API_URL environment variable is required')
  }

  const response = await fetch(`${process.env.USER_API_URL}/users/${userId}`)

  if (!response.ok) {
    // Detailed error information for problem localization
    const errorBody = await response.text()
    throw new Error(
      `User API failed: ${response.status} ${response.statusText}. ` +
      `URL: ${process.env.USER_API_URL}/users/${userId}. ` +
      `Response: ${errorBody}`
    )
  }

  const userData = await response.json()

  // Runtime data validation
  if (!userData || typeof userData.id !== 'string') {
    throw new Error(`Invalid user data format received from API: ${JSON.stringify(userData)}`)
  }

  return userData
}

// Callers must handle all possible errors
try {
  const userData = await fetchUserData(userId)
  displayUserProfile(userData)
} catch (error) {
  // Immediately show user-friendly error messages
  showUserError('Unable to load user profile. Please try again later.')

  // Record detailed errors for troubleshooting
  logger.error('Failed to fetch user data', {
    userId,
    error: error instanceof Error ? error.message : 'Unknown error',
    timestamp: new Date().toISOString()
  })

  // If it's a system error, can trigger alerts
  if (error.message.includes('500')) {
    alertSystem('User API server error', error)
  }
}

Product Thinking's Advantages:

  • Problems immediately discovered: API failures immediately exposed
  • Quick fix response: Operations team can immediately handle
  • Consistent user experience: Either normal or clear error
  • System health transparent: Monitoring can see real status

2. Maintenance Cost Explosion: Cumulative Effect of Defensive Code

Toy Thinking's Maintenance Nightmare:

// โŒ AI-generated complex degradation logic
class RobustUserService {
  constructor(
    private primaryApiClient: PrimaryAPIClient,
    private fallbackApiClient: FallbackAPIClient,
    private cacheService: CacheService,
    private mockDataService: MockDataService,
    private configService: ConfigService
  ) {}

  async getUser(userId: string): Promise<User | null> {
    // Complex retry and degradation logic
    let lastError: Error | null = null

    // Strategy 1: Primary API + retry
    for (let attempt = 1; attempt <= 3; attempt++) {
      try {
        const user = await this.primaryApiClient.getUser(userId)
        if (user) {
          await this.cacheService.set(`user:${userId}`, user, 300)
          return user
        }
      } catch (error) {
        lastError = error as Error
        console.warn(`Primary API attempt ${attempt} failed:`, error)
        if (attempt < 3) {
          await this.delay(1000 * attempt) // Exponential backoff
        }
      }
    }

    // Strategy 2: Backup API
    try {
      const user = await this.fallbackApiClient.getUser(userId)
      if (user) {
        await this.cacheService.set(`user:${userId}`, user, 300)
        return user
      }
    } catch (error) {
      lastError = error as Error
      console.warn('Fallback API failed:', error)
    }

    // Strategy 3: Cached data
    try {
      const cachedUser = await this.cacheService.get(`user:${userId}`)
      if (cachedUser) {
        console.warn('Using stale cached data')
        return cachedUser
      }
    } catch (error) {
      lastError = error as Error
      console.warn('Cache fallback failed:', error)
    }

    // Strategy 4: Mock data
    try {
      const mockUser = await this.mockDataService.getMockUser(userId)
      console.warn('Using mock data - service degraded')
      return mockUser
    } catch (error) {
      lastError = error as Error
      console.error('All fallback strategies failed')
    }

    return null
  }

  private delay(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms))
  }
}

Maintenance Cost Analysis:

  • Code complexity: 150 lines of complex logic, hard to understand
  • Testing complexity: Need to test Nร—M combinations
  • Debugging difficulty: Don't know which layer has the problem
  • New feature addition: Need to modify all degradation branches

โœ… Product Thinking's Simple Design:

// โœ… Simple and direct, failure exposes immediately
class UserService {
  constructor(
    private apiClient: APIClient,
    private logger: Logger
  ) {}

  async getUser(userId: string): Promise<User> {
    // Pre-validation
    if (!userId || typeof userId !== 'string') {
      throw new Error('Invalid user ID: must be a non-empty string')
    }

    try {
      const user = await this.apiClient.getUser(userId)

      // Data validation
      if (!user || !user.id) {
        throw new Error(`Invalid user data received: ${JSON.stringify(user)}`)
      }

      this.logger.info(`User ${userId} retrieved successfully`)
      return user

    } catch (error) {
      this.logger.exception('Failed to retrieve user', error, { userId })
      throw new Error(`Failed to retrieve user ${userId}: ${error instanceof Error ? error.message : 'Unknown error'}`)
    }
  }
}

Product Thinking's Advantages:

  • Code simplicity: 20 lines of core logic, easy to understand
  • Testing simplicity: Only need to test normal and exception cases
  • Debugging ease: Clear error information, quick localization
  • Low maintenance cost: Low modification risk, clear impact scope

3. User Experience Disaster: "Semi-finished" Experience of Degraded Functions

Toy Thinking's User Experience Problems:

// โŒ Degradation leads to user experience disaster
async function loadUserProfile(userId: string) {
  const userProfile = await fetchUserData(userId) // Uses complex degradation logic above

  if (userProfile) {
    // Display user profile
    displayProfile(userProfile)
  } else {
    // Show empty page or error message
    showEmptyState()
  }
}

// Users might see:
// 1. Normal user profile โœ…
// 2. Stale cached data โš ๏ธ (Information outdated)
// 3. Mock fake data โŒ (Name is "Unknown User")
// 4. Blank page โŒ (Don't know where error occurred)
// 5. Loading state never disappears โŒ (Degradation logic stuck)

Disastrous Consequences for User Experience:

  • Data inconsistency: Users see wrong information
  • Unpredictable functionality: Same operation produces different results
  • Trust decline: Users don't believe system reliability
  • Support cost increase: Users report lots of "weird" problems

โœ… Product Thinking's User Experience:

// โœ… Clear states and error handling
async function loadUserProfile(userId: string) {
  try {
    // Show loading state
    showLoadingState('Loading user profile...')

    const userProfile = await fetchUserData(userId) // Simple direct call

    // Verify data integrity
    if (!userProfile.email || !userProfile.name) {
      throw new Error('User profile data incomplete')
    }

    hideLoadingState()
    displayProfile(userProfile)

  } catch (error) {
    hideLoadingState()

    // Show different prompts based on error type
    if (error.message.includes('404')) {
      showError('User not found', 'The user profile you requested does not exist.')
    } else if (error.message.includes('API failed')) {
      showError('Service temporarily unavailable', 'Our user service is experiencing issues. Please try again in a few minutes.')
    } else {
      showError('Unable to load profile', 'An unexpected error occurred. Please try again later.')
    }

    // Record user-friendly errors
    analytics.track('profile_load_failed', {
      userId,
      errorType: error.message.includes('404') ? 'not_found' : 'service_error',
      timestamp: new Date().toISOString()
    })
  }
}

Product Thinking's User Experience:

  • Clear states: Users know what system is doing
  • Friendly errors: Provide clear error information and suggestions
  • Consistent behavior: Same operation always produces same results
  • Predictability: Users know when it will succeed or fail

๐Ÿ›ก๏ธ Core Principles of Product Development: Let it Crash, Fix it Fast

1. Zero Tolerance Error Handling Philosophy

Traditional Thinking vs Product Thinking:

// โŒ Traditional thinking: Fault tolerance first
async function processPayment(amount: number, cardInfo: CardInfo): Promise<PaymentResult | null> {
  try {
    const result = await paymentService.charge(amount, cardInfo)
    return result
  } catch (error) {
    console.log('Payment failed, returning null...')
    return null // Silent failure, problem masked
  }
}

// Caller needs to guess null meaning
const result = await processPayment(100, card)
if (result) {
  showSuccess()
} else {
  // What kind of failure? Network issues? Invalid card? Amount error?
  // Users don't know, developers don't know
  showGenericError()
}
// โœ… Product thinking: Failure exposes immediately
async function processPayment(amount: number, cardInfo: CardInfo): Promise<PaymentResult> {
  // Input validation
  if (!amount || amount <= 0) {
    throw new ValidationError('Payment amount must be positive')
  }

  if (!cardInfo || !validateCardInfo(cardInfo)) {
    throw new ValidationError('Invalid card information')
  }

  try {
    const result = await paymentService.charge(amount, cardInfo)

    // Verify result
    if (!result || !result.transactionId) {
      throw new PaymentError('Invalid payment response from processor')
    }

    return result

  } catch (error) {
    // Record detailed error information
    logger.exception('Payment processing failed', error, {
      amount,
      cardLast4: cardInfo.last4,
      timestamp: new Date().toISOString()
    })

    // Re-throw to let caller know specific problem
    throw error
  }
}

// Callers can clearly handle each error
try {
  const result = await processPayment(100, card)
  showSuccess(`Payment successful! Transaction ID: ${result.transactionId}`)
} catch (error) {
  if (error instanceof ValidationError) {
    showFormError('Please check your payment information and try again.')
  } else if (error instanceof PaymentError) {
    showPaymentError('Payment processing failed. Please try a different payment method.')
  } else if (error.message.includes('declined')) {
    showDeclinedError('Your card was declined. Please contact your bank or try a different card.')
  } else {
    showGenericError('An unexpected error occurred. Please try again later.')
  }

  // Record user operations for analysis
  analytics.track('payment_failed', {
    amount,
    errorType: error.constructor.name,
    timestamp: new Date().toISOString()
  })
}

2. Startup Validation: No Runtime "Surprises"

Toy Thinking's Problems:

// โŒ Problems only discovered at runtime
class Application {
  private database: Database
  private cache: Cache
  private emailService: EmailService

  constructor() {
    // Configuration might only be validated at runtime
    this.database = new Database(process.env.DATABASE_URL || 'sqlite:memory')
    this.cache = new Cache(process.env.CACHE_URL || 'redis://localhost')
    this.emailService = new EmailService(process.env.SMTP_HOST || 'localhost')
  }

  async start() {
    // Configuration errors only discovered at runtime
    try {
      await this.database.connect()
      await this.cache.connect()
      await this.emailService.testConnection()

      console.log('Application started successfully')
    } catch (error) {
      console.error('Failed to start application:', error)
      // Application continues running, but functionality incomplete
    }
  }
}

Product Thinking's Solution:

// โœ… Validate at startup, configuration errors exposed immediately
interface AppConfig {
  database: {
    url: string
    maxConnections: number
    timeout: number
  }
  cache: {
    url: string
    maxRetries: number
  }
  email: {
    host: string
    port: number
    username: string
    password: string
  }
  server: {
    port: number
    host: string
  }
}

class ConfigValidator {
  static validate(config: AppConfig): void {
    // Database configuration validation
    if (!config.database.url) {
      throw new Error('DATABASE_URL is required')
    }

    if (config.database.maxConnections < 1 || config.database.maxConnections > 100) {
      throw new Error('Database maxConnections must be between 1 and 100')
    }

    // Cache configuration validation
    if (!config.cache.url) {
      throw new Error('CACHE_URL is required')
    }

    // Email configuration validation
    if (!config.email.host || !config.email.username || !config.email.password) {
      throw new Error('Email service configuration is incomplete')
    }

    if (config.email.port < 1 || config.email.port > 65535) {
      throw new Error('Email port must be between 1 and 65535')
    }

    // Server configuration validation
    if (config.server.port < 1024 || config.server.port > 65535) {
      throw new Error('Server port must be between 1024 and 65535')
    }
  }
}

class Application {
  private database: Database
  private cache: Cache
  private emailService: EmailService
  private config: AppConfig

  constructor(config: AppConfig) {
    // Configuration validation must be completed at startup
    ConfigValidator.validate(config)
    this.config = config

    // All dependencies based on validated configuration
    this.database = new Database(config.database)
    this.cache = new Cache(config.cache)
    this.emailService = new EmailService(config.email)
  }

  async start(): Promise<void> {
    console.log('Starting application...')

    // Start all services sequentially, any failure stops
    await this.database.connect()
    console.log('โœ… Database connected')

    await this.cache.connect()
    console.log('โœ… Cache connected')

    await this.emailService.testConnection()
    console.log('โœ… Email service ready')

    // Only start HTTP server after all services normal
    this.startHttpServer()

    console.log(`๐Ÿš€ Application started successfully on ${this.config.server.host}:${this.config.server.port}`)
  }

  private startHttpServer(): void {
    // HTTP server startup logic
  }
}

// Application startup
async function main() {
  try {
    const config: AppConfig = {
      database: {
        url: process.env.DATABASE_URL!,
        maxConnections: parseInt(process.env.DB_MAX_CONNECTIONS || '10'),
        timeout: parseInt(process.env.DB_TIMEOUT || '30000')
      },
      cache: {
        url: process.env.CACHE_URL!,
        maxRetries: parseInt(process.env.CACHE_MAX_RETRIES || '3')
      },
      email: {
        host: process.env.SMTP_HOST!,
        port: parseInt(process.env.SMTP_PORT || '587'),
        username: process.env.SMTP_USERNAME!,
        password: process.env.SMTP_PASSWORD!
      },
      server: {
        port: parseInt(process.env.PORT || '3000'),
        host: process.env.HOST || 'localhost'
      }
    }

    const app = new Application(config)
    await app.start()

  } catch (error) {
    console.error('โŒ Application startup failed:', error instanceof Error ? error.message : error)

    // Provide specific fix suggestions for configuration errors
    if (error.message.includes('DATABASE_URL')) {
      console.error('๐Ÿ’ก Fix: Set DATABASE_URL environment variable')
      console.error('๐Ÿ’ก Example: export DATABASE_URL="postgresql://user:pass@localhost:5432/dbname"')
    }

    if (error.message.includes('CACHE_URL')) {
      console.error('๐Ÿ’ก Fix: Set CACHE_URL environment variable')
      console.error('๐Ÿ’ก Example: export CACHE_URL="redis://localhost:6379"')
    }

    // Exit process to avoid running on wrong configuration
    process.exit(1)
  }
}

// Start application
main()

3. Error Classification and Standardized Handling

Establish Clear Error Classification System:

// Base error types
export abstract class ApplicationError extends Error {
  abstract readonly code: string
  abstract readonly httpStatus: number
  abstract readonly userMessage: string

  constructor(message: string, public readonly context?: Record<string, any>) {
    super(message)
    this.name = this.constructor.name
  }
}

// User input errors
export class ValidationError extends ApplicationError {
  readonly code = 'VALIDATION_ERROR'
  readonly httpStatus = 400
  readonly userMessage = 'Please check input information and try again'
}

export class AuthenticationError extends ApplicationError {
  readonly code = 'AUTHENTICATION_ERROR'
  readonly httpStatus = 401
  readonly userMessage = 'Please login first and try again'
}

export class AuthorizationError extends ApplicationError {
  readonly code = 'AUTHORIZATION_ERROR'
  readonly httpStatus = 403
  readonly userMessage = 'You do not have permission to perform this operation'
}

// Business logic errors
export class BusinessError extends ApplicationError {
  readonly code = 'BUSINESS_ERROR'
  readonly httpStatus = 422
  readonly userMessage = 'Operation cannot be completed, please check related conditions'
}

// System errors
export class DatabaseError extends ApplicationError {
  readonly code = 'DATABASE_ERROR'
  readonly httpStatus = 500
  readonly userMessage = 'System temporarily busy, please try again later'
}

export class ExternalServiceError extends ApplicationError {
  readonly code = 'EXTERNAL_SERVICE_ERROR'
  readonly httpStatus = 503
  readonly userMessage = 'External service temporarily unavailable, please try again later'
}

// Error handling middleware
export function errorHandler(error: Error, req: Request, res: Response, next: NextFunction): void {
  // Record detailed error information
  logger.exception('Request error', error, {
    method: req.method,
    url: req.url,
    userAgent: req.get('User-Agent'),
    ip: req.ip,
    userId: req.user?.id,
    timestamp: new Date().toISOString()
  })

  // Return different responses based on error type
  if (error instanceof ApplicationError) {
    res.status(error.httpStatus).json({
      success: false,
      error: {
        code: error.code,
        message: error.userMessage,
        timestamp: new Date().toISOString(),
        requestId: req.id
      }
    })
  } else {
    // Unknown error
    logger.error('Unknown error occurred', error)
    res.status(500).json({
      success: false,
      error: {
        code: 'INTERNAL_SERVER_ERROR',
        message: 'Server internal error, please contact technical support',
        timestamp: new Date().toISOString(),
        requestId: req.id
      }
    })
  }
}

// Unified error handling decorator
export function withErrorHandling<T extends any[], R>(
  fn: (...args: T) => Promise<R>
): (...args: T) => Promise<R> {
  return async (...args: T): Promise<R> => {
    try {
      return await fn(...args)
    } catch (error) {
      // If it's known ApplicationError, re-throw directly
      if (error instanceof ApplicationError) {
        throw error
      }

      // Unknown error converted to ApplicationError
      throw new ExternalServiceError(
        `Unexpected error in ${fn.name}: ${error instanceof Error ? error.message : 'Unknown error'}`,
        { originalError: error, functionName: fn.name, args: args.length }
      )
    }
  }
}

// Usage example
class UserService {
  @withErrorHandling
  async createUser(userData: CreateUserInput): Promise<User> {
    // Input validation
    if (!userData.email || !userData.name) {
      throw new ValidationError('Email and name are required', { userData })
    }

    // Business logic check
    if (await this.emailExists(userData.email)) {
      throw new BusinessError('Email already registered', { email: userData.email })
    }

    // Database operation
    try {
      const user = await this.userRepository.create(userData)
      return user
    } catch (dbError) {
      throw new DatabaseError('Failed to create user', { userData, dbError })
    }
  }
}

๐ŸŽฏ Transformation Path: From Toy Thinking to Product Thinking

Step 1: Cognitive Transformation - Redefine "Good Code"

Toy Thinking's "Good Code" Standards:

  • โœ… Doesn't crash
  • โœ… Has fallback solutions
  • โœ… Can handle various exceptions
  • โœ… Looks very "robust"

Product Thinking's "Good Code" Standards:

  • โœ… Problems expose immediately
  • โœ… Has clear error messages
  • โœ… Easy to debug and maintain
  • โœ… User experience consistent

Step 2: Establish Error Classification System

Create Clear Error Classifications:

// Error classification decision tree
function classifyError(error: Error): ErrorType {
  if (error instanceof ValidationError) {
    return 'USER_ERROR'
  }

  if (error instanceof DatabaseError) {
    return 'SYSTEM_ERROR'
  }

  if (error instanceof ExternalServiceError) {
    return 'DEPENDENCY_ERROR'
  }

  return 'UNKNOWN_ERROR'
}

Handling Strategies for Different Error Types:

  • User errors: Prompt users to fix immediately
  • System errors: Record detailed information, fix quickly
  • Dependency errors: Evaluate alternatives, optimize service providers
  • Unknown errors: Emergency investigation, establish monitoring

Step 3: Refactor Existing Code

Refactoring Principles:

  1. Remove unnecessary degradation logic
  2. Add pre-validation
  3. Standardize error handling
  4. Improve error messages

Refactoring Example:

// Refactoring before: Toy thinking
async function processData(data: any): Promise<any> {
  try {
    const result = await processWithService(data)
    return result
  } catch (error) {
    try {
      const fallbackResult = await processWithFallback(data)
      return fallbackResult
    } catch (fallbackError) {
      return null
    }
  }
}

// Refactoring after: Product thinking
async function processData(inputData: ProcessDataInput): Promise<ProcessDataOutput> {
  // Pre-validation
  validateInputData(inputData)

  try {
    const result = await processWithService(inputData)
    validateOutputData(result)
    return result
  } catch (error) {
    logger.exception('Data processing failed', error, { inputData })
    throw new ProcessingError('Unable to process data', { inputData, error })
  }
}

Step 4: Establish Monitoring and Alert Mechanisms

Key Monitoring Metrics:

  • Error rate: Classified by error type
  • Response time: Identify performance regression
  • Success rate: Core functionality availability
  • User impact: Error impact on user experience

Alert Strategies:

  • Immediate alerts: Core function failures
  • 5-minute alerts: Error rate exceeds threshold
  • 1-hour alerts: Performance metric anomalies
  • 1-day reports: Trend analysis and summary

๐ŸŒŸ Real Cases: Tidepool Notes' Product Thinking Practice

Case 1: AI Dialogue Interface Refactoring

Toy Thinking Period Problems:

// โŒ Complex degradation logic
async function getAIResponse(message: string): Promise<string | null> {
  try {
    const response = await openai.chat.completions.create({
      model: 'gpt-4',
      messages: [{ role: 'user', content: message }]
    })
    return response.choices[0]?.message?.content || null
  } catch (error) {
    console.log('OpenAI failed, trying local model...')

    try {
      const localResponse = await localAIModel.process(message)
      return localResponse
    } catch (localError) {
      console.log('Local model failed, using preset response...')
      return presetResponses[Math.floor(Math.random() * presetResponses.length)]
    }
  }
}

Product Thinking Refactoring:

// โœ… Clear error handling and quick fixes
class AIService {
  async generateResponse(message: string): Promise<string> {
    // Input validation
    if (!message || message.trim().length === 0) {
      throw new ValidationError('Message cannot be empty')
    }

    if (message.length > 10000) {
      throw new ValidationError('Message too long (max 10000 characters)')
    }

    try {
      const response = await this.openaiClient.chat.completions.create({
        model: 'gpt-4',
        messages: [{ role: 'user', content: message.trim() }],
        temperature: 0.7,
        max_tokens: 1000
      })

      const content = response.choices[0]?.message?.content
      if (!content) {
        throw new ExternalServiceError('AI service returned empty response')
      }

      return content

    } catch (error) {
      this.logger.exception('AI response generation failed', error, {
        messageLength: message.length,
        timestamp: new Date().toISOString()
      })

      // Provide specific prompts based on error type
      if (error.message.includes('rate_limit_exceeded')) {
        throw new ExternalServiceError('AI service is busy, please try again in a moment')
      }

      if (error.message.includes('insufficient_quota')) {
        throw new ExternalServiceError('AI service quota exceeded, please contact support')
      }

      throw new ExternalServiceError('Unable to generate AI response at this time')
    }
  }
}

Refactoring Effect:

  • User experience improved: Clear error prompts, no more strange replies
  • Problem discovery speed: OpenAI API problems immediately exposed, quick fixes
  • Maintenance cost reduced: Code from 50 lines to 30 lines, clear logic
  • Monitoring improved: Can accurately track AI service availability

Case 2: Data Synchronization Service Transformation

Toy Thinking Problems:

// โŒ Silent failures, data inconsistency
async function syncUserData(userId: string): Promise<void> {
  try {
    await localDB.updateUser(userId)
    await remoteDB.updateUser(userId)
    await cache.invalidateUser(userId)
  } catch (error) {
    console.log('Sync failed, but continuing...')
    // Silent failure, data inconsistency
  }
}

Product Thinking Solution:

// โœ… Transactional operations, all succeed or all fail
class UserDataSyncService {
  async syncUser(userId: string): Promise<void> {
    // Get current data snapshot
    const currentUserData = await this.localDB.getUser(userId)
    if (!currentUserData) {
      throw new ValidationError(`User ${userId} not found in local database`)
    }

    try {
      // Start distributed transaction
      const transactionId = await this.transactionManager.begin()

      try {
        // Step 1: Update remote database
        await this.remoteDB.updateUser(userId, currentUserData)

        // Step 2: Update cache
        await this.cache.set(`user:${userId}`, currentUserData, 3600)

        // Step 3: Commit transaction
        await this.transactionManager.commit(transactionId)

        this.logger.info(`User ${userId} synced successfully`, {
          transactionId,
          timestamp: new Date().toISOString()
        })

      } catch (stepError) {
        // Any step fails, rollback transaction
        await this.transactionManager.rollback(transactionId)
        throw stepError
      }

    } catch (error) {
      this.logger.exception('User sync failed', error, {
        userId,
        userDataKeys: Object.keys(currentUserData)
      })

      // Decide retry strategy based on error type
      if (error instanceof NetworkError) {
        throw new ExternalServiceError('Network connectivity issues prevented sync')
      }

      if (error instanceof DatabaseError) {
        throw new DatabaseError('Database operation failed during sync')
      }

      throw new SyncError('Unable to sync user data', { userId, error })
    }
  }
}

Transformation Effect:

  • Data consistency: No more partial synchronization issues
  • Failure traceability: Each sync has clear success/failure status
  • Error recoverable: Network issues can be retried, database issues can be fixed
  • Monitoring visualization: Can see sync success rate and failure reasons

๐Ÿ’ก Practical Suggestions for AI Developers

1. Establish "Product Thinking" Check List

Ask Yourself After Each AI Code Generation:

## Product Thinking Check List

### Error Handling Check
- [ ] Will errors expose immediately?
- [ ] Are error messages clear and specific?
- [ ] Is necessary logging included?
- [ ] Do users know how to handle errors?

### User Experience Check
- [ ] Is functionality always consistent?
- [ ] Do users know system status?
- [ ] Are there friendly prompts when errors occur?
- [ ] Is "magical" degradation behavior avoided?

### Maintainability Check
- [ ] Is code logic simple and direct?
- [ ] Is it easy to debug and troubleshoot problems?
- [ ] Is there complete test coverage?
- [ ] Does it follow single responsibility principle?

### Monitoring and Operations Check
- [ ] Do key operations have logs?
- [ ] Is there error alert mechanism?
- [ ] Is there performance monitoring?
- [ ] Is there health check endpoint?

2. Establish AI Output Quality Standards

Prohibited AI Generation Code Patterns:

// โŒ Prohibited patterns
try {
  result = riskyOperation()
} catch {
  result = fallbackValue()  // Silent degradation
}

// โŒ Prohibited patterns
if (data === undefined || data === null) {
  data = defaultValue  // Mask data problems
}

// โŒ Prohibited patterns
async function robustOperation() {
  for (let i = 0; i < 3; i++) {
    try {
      return await operation()
    } catch {
      continue  // Infinite retry, don't expose problems
    }
  }
}

Required AI Generation Code Patterns:

// โœ… Required patterns
async function operation(): Promise<Result> {
  const input = validateInput()  // Pre-validation
  const result = await performOperation(input)
  return validateOutput(result)  // Result validation
}

// โœ… Required patterns
try {
  const result = await operation()
  return result
} catch (error) {
  logger.exception('Operation failed', error)
  throw new OperationError('Unable to complete operation', { error })
}

3. Establish Code Review Standards

Product Thinking Code Review Points:

  • Error handling appropriateness: Should not have silent failures
  • User experience consistency: Same operation should produce same results
  • Failure fast exposure: Problems should be discovered immediately
  • Maintenance simplicity: Code should be easy to understand and modify

๐ŸŒŸ Summary: Transformation from Toy Developer to Product Engineer

Core Cognitive Transformation

Toy Developer Thinking:

  • "I want to make code run under any circumstances"
  • "I want to provide multiple degradation plans"
  • "I want to defend against all possible errors"
  • "System stability is more important than user experience"

Product Engineer Thinking:

  • "I want to let problems expose immediately and fix quickly"
  • "I want to provide consistent user experience"
  • "I want to ensure problem traceability"
  • "User experience is first priority"

Practice Suggestions

  1. Reject degradation thinking: Don't use fallback to mask problems
  2. Embrace fast failure: Let it Crash, Fix it Fast
  3. Establish monitoring systems: Real-time understanding of system health
  4. Improve error handling: Provide clear error messages and user guidance

Ultimate Goal

True product development isn't writing code that "doesn't crash," but building systems that "quickly discover problems, quickly fix problems."

Remember: Users would rather see a clear error message than accept a functionally incomplete degraded product.


Next Article Preview: ใ€ŠAI Programming's Ultimate Challenge: How to Build Truly Enterprise-Level Applicationsใ€‹


Search "ๅ››ๅ“ฅ่ฟ˜ไผš่ŠAI" on WeChat to learn more product-level AI development practical experience

Search "ๆฑๅฑฟ็ฌ”่ฎฐ" on WeChat to see how these architectural principles are applied in real projects

โ† Back to Home