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:
- Remove unnecessary degradation logic
- Add pre-validation
- Standardize error handling
- 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
- Reject degradation thinking: Don't use fallback to mask problems
- Embrace fast failure: Let it Crash, Fix it Fast
- Establish monitoring systems: Real-time understanding of system health
- 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