KISS Principle + Let It Crash: The Golden Rules of AI Programming
๐ช The Biggest Trap in AI Programming: The Temptation of Complexity
AI has a natural flaw: it loves to show off. When you give it a simple requirement, it always tries to showcase its "talents" by making simple problems complex.
During the development of Tidepool Notes, I discovered that the KISS Principle and Let it Crash are the two most effective rules for constraining AI and keeping code simple.
Today, I'll teach you how to apply these two golden rules in AI programming.
๐ฏ KISS Principle: Simplicity is the Ultimate Complexity
What is KISS?
KISS = Keep It Simple, Stupid
- Keep it simple, don't be clever
- If you can solve it with 1 line of code, never use 10 lines
- If you can use a simple solution, never use a complex one
Why AI Violates the KISS Principle?
1. Show-off Mentality
AI wants to prove it's smart, so it tends to use complex technical solutions.
2. Excessive Safety Considerations
AI anticipates various edge cases and future needs, leading to over-engineering.
3. Lack of Context
AI doesn't know your actual business scenario and user scale, so it can only provide "universal" complex solutions.
KISS Practice in Tidepool Notes
Principle 1: 1500-line limit per file
// โ Complex code that AI might generate
class AbstractFactoryProviderProxy {
private decoratorChain: DecoratorPattern[]
private middlewareStack: MiddlewareComposite[]
private configurationManager: ConfigurationManager
private dependencyInjector: DependencyInjector
constructor() {
// 200 lines of complex initialization logic
}
public createProvider<T>(type: ProviderType): Promise<T> {
// Complex factory pattern implementation
// Contains multiple layers of abstraction and indirect calls
}
}
// โ
Simple and direct implementation
function createUser(userData: UserData): Promise<User> {
return prisma.user.create({ data: userData })
}
Principle 2: Single Responsibility Principle
// โ "All-purpose function" that AI might write
async function processUserAction(data: any) {
// Validate user
// Process data
// Send notifications
// Log activities
// Update statistics
// Clear cache
// ... 100 lines of code
}
// โ
Each function does only one thing
async function validateUser(data: UserData): Promise<User> {
return await userValidator.validate(data)
}
async function saveUserData(data: UserData): Promise<User> {
return await userRepository.save(data)
}
async function sendNotification(user: User): Promise<void> {
return await notificationService.send(user)
}
Principle 3: Avoid Over-abstraction
// โ Over-abstracted configuration management
interface ConfigurationProvider {
getValue<T>(key: string): T
setValue<T>(key: string, value: T): void
hasKey(key: string): boolean
clear(): void
reload(): Promise<void>
}
class EnvironmentConfigurationProvider implements ConfigurationProvider {
// Complex implementation
}
class DatabaseConfigurationProvider implements ConfigurationProvider {
// Complex implementation
}
// โ
Simple configuration object
const config = {
apiUrl: process.env.API_URL || 'http://localhost:3001',
databaseUrl: process.env.DATABASE_URL,
jwtSecret: process.env.JWT_SECRET,
}
export function getConfig(key: keyof typeof config) {
return config[key]
}
๐ฅ Let It Crash: Let Errors Expose Early
What is Let It Crash?
Let it Crash = Let the program crash
- When encountering unhandleable errors, crash directly
- Don't try to mask errors or provide fallback solutions
- Crashing is the best error message because it exposes the root cause
Misconceptions in Traditional Error Handling
โ Traditional "Defensive Programming":
async function getUser(id: string): Promise<User | null> {
try {
const user = await prisma.user.findUnique({ where: { id } })
if (!user) {
logger.warn(`User not found: ${id}`)
return null
}
return user
} catch (error) {
logger.error(`Error fetching user: ${error.message}`)
return null
}
}
// Caller needs to handle null
const user = await getUser(userId)
if (user) {
// Process user
} else {
// Handle null, but don't know why it's null
}
Problems:
- Caller doesn't know why null is returned
- Errors are masked, making debugging difficult
- Business logic becomes complex
โ Let it Crash approach:
async function getUser(id: string): Promise<User> {
const user = await prisma.user.findUnique({ where: { id } })
if (!user) {
throw new Error(`User not found: ${id}`)
}
return user
}
// Caller uses directly, let it crash on error
const user = await getUser(userId) // If error occurs, program crashes, immediately exposing problem
// Process user, no need to check null
Applying Let it Crash in AI Programming
1. Input Validation: Reject Invalid Data
// โ "Lenient" validation that AI might write
function parseAge(input: any): number {
if (typeof input === 'number') return input
if (typeof input === 'string') {
const parsed = parseInt(input)
if (!isNaN(parsed)) return parsed
}
return 0 // Default value, masks the problem
// โ
Strict validation, crash if not valid
function parseAge(input: unknown): number {
if (typeof input === 'number' && input >= 0 && input <= 150) {
return input
}
throw new Error(`Invalid age: ${input}`)
}
2. Environment Configuration: Crash if Required Config is Missing
// โ Overly fault-tolerant
const dbUrl = process.env.DATABASE_URL || 'sqlite://default.db'
const jwtSecret = process.env.JWT_SECRET || 'default-secret'
// โ
Crash if required config is missing
if (!process.env.DATABASE_URL) {
throw new Error('DATABASE_URL is required')
}
if (!process.env.JWT_SECRET) {
throw new Error('JWT_SECRET is required')
}
3. API Calls: Fail if Failed, No Fallback
// โ Complex retry logic that AI might write
async function callAPI(url: string): Promise<any> {
try {
const response = await fetch(url)
return await response.json()
} catch (error) {
// Retry once
try {
const response = await fetch(url)
return await response.json()
} catch (retryError) {
// Return empty data
return {}
}
}
}
// โ
Simple and direct, crash if failed
async function callAPI(url: string): Promise<any> {
const response = await fetch(url)
if (!response.ok) {
throw new Error(`API call failed: ${response.status}`)
}
return await response.json()
}
๐ก๏ธ Type Safety: The Most Effective Way to Prevent AI Errors
Although AI is powerful, it's prone to making mistakes in type inference. Using type-safe languages is an effective way to constrain AI.
TypeScript Application in Tidepool Notes
1. Clear Data Type Definitions
interface UserSession {
id: string
userId: string
status: 'active' | 'completed' | 'paused'
createdAt: Date
updatedAt: Date
}
interface CreateSessionData {
userId: string
initialMood?: 'happy' | 'sad' | 'neutral'
notes?: string
}
2. API Response Type Constraints
type ApiResponse<T> = {
success: boolean
data?: T
error?: {
code: string
message: string
}
}
// Usage example
async function createSession(data: CreateSessionData): Promise<ApiResponse<UserSession>> {
try {
const session = await sessionRepository.create(data)
return { success: true, data: session }
} catch (error) {
return {
success: false,
error: {
code: 'SESSION_CREATE_FAILED',
message: error.message
}
}
}
}
3. Avoid any Type
// โ AI might use any
function processUserData(data: any): any {
// Unsafe processing
}
// โ
Clear type definitions
interface UserData {
name: string
email: string
age: number
}
function processUserData(data: UserData): ProcessedData {
// Type-safe processing
}
๐ Claude.md: The Soul Document of the Project
Why is Claude.md so Important?
Claude.md is the most important file in AI programming projects. It defines your core requirements and development principles.
Core Content of Tidepool Notes Claude.md:
- Project Overview: Positioning as an intelligent emotional companion application
- Three-Layer Playbook Architecture: L1 Core Portrait, L2 Recent Battle Report, L3 Current Briefing
- Technology Stack Selection: Taro + React + Fastify + PostgreSQL
- Development Principles: KISS Principle, Let it Crash, Type Safety
- API Modes: Mock/Real/Local three modes
- Error Handling Standards: logger.exception, structured error responses
Claude.md Best Practices
1. Keep it Simple and Highlight Key Points
# Project Overview
Tidepool Notes: Providing AI emotional companionship for young people having late-night emotional moments
# Core Principles
- KISS Principle: Simplicity first, single file not exceeding 1500 lines
- Let it Crash: Expose errors directly, don't mask them
- Type Safety: Strict TypeScript usage, avoid any
# Technology Stack
- Frontend: Taro + React + TypeScript
- Backend: Fastify + TypeScript + Prisma
- Database: PostgreSQL
- AI: OpenAI API
2. Include Specific Code Examples and Best Practices
# Error Handling Examples
// โ Don't do this
try {
// some code
} catch (error) {
return null // Mask errors
}
// โ
Should do this
try {
// some code
} catch (error) {
logger.exception('Error details', error)
throw error // Let errors expose
}
3. Update Regularly to Reflect New Project Requirements
- New feature requirements
- Technology stack adjustments
- Development principle updates
- Lessons learned summaries
๐ฏ Practical Application: Specific Cases in Tidepool Notes
Case 1: Simplification of Message Storage System
AI Initial Solution:
- Support multiple message formats (text, images, voice, video)
- Implement message version control
- Add message search functionality
- Support message recall and editing
- Implement message sync and conflict resolution
Simplification after Applying KISS Principle:
- Only support text messages
- Simple CRUD operations
- Display by time order
- Basic message status (sent, read)
Case 2: User Authentication System Refactoring
Traditional Complex Solution:
- Multiple login methods (phone, email, WeChat, QQ)
- Complex permission management system
- Operation audit logs
- Password strength validation and reset flow
Simplification after Applying Let it Crash:
- Only support WeChat login
- Simple user authentication
- Login failure crashes directly, no fallback provided
- Startup fails if necessary configuration is missing
Case 3: Standardization of Error Handling
Complex Error Handling Recommended by AI:
- Custom error type system
- Error code mapping table
- Multi-layer error capture and handling
- Error recovery and retry mechanisms
Applying Type Safety and Let it Crash:
- Use strict TypeScript type checking
- Throw errors directly, log with logger.exception
- No error compatibility solutions provided
- Direct crash in development, unified error page in production
๐ Summary: Golden Rules of AI Programming
KISS Principle Key Points
- 1500-line limit per file: Force keeping things simple
- Single Responsibility Principle: Each function does only one thing
- Avoid Over-abstraction: Don't add complexity for "future needs"
- Reject Show-off: Working code is good code
Let it Crash Key Points
- Expose Errors Directly: Don't try to mask or make compatible
- Crash if Required Configuration Missing: Don't use default values
- Throw Exception if Validation Fails: Don't return special values
- Crash if API Call Fails: Don't use complex retry logic
Type Safety Key Points
- Strict TypeScript Usage: Avoid any types
- Clear Interface Definitions: Make AI-generated code more controllable
- Runtime Type Validation: Don't trust input data
- Compile-time Error Checking: Eliminate problems at the bud stage
Claude.md Key Points
- Clearly Define Project Principles: Constrain AI's behavior boundaries
- Include Specific Examples: Give AI clear reference templates
- Regular Updates and Maintenance: Reflect project's latest requirements
- Keep Simple and Clear: Highlight the most important constraints
Remember: AI programming is not about letting AI do what it wants, but letting AI do what you need. The KISS principle and Let it Crash are the best tools to constrain AI.
Next Article Preview: Token Economics in Practice: How to Save 90% of AI Programming Costs
Search "ๅๅฅ่ฟไผ่AI" on WeChat to see how these principles are applied in actual projects