From 0b50f29d47ce760c4494376cfe7d44fbc0aab809 Mon Sep 17 00:00:00 2001 From: sunguangbiao <2991552132@qq.com> Date: Sat, 27 Dec 2025 00:03:25 +0800 Subject: [PATCH 1/7] docs: add CLAUDE.md for guidance on using code components and update js2java module files for consistency in language and structure --- CLAUDE.md | 346 +++++++++++++ components/code-examples/en/code-examples.ts | 462 +++++++++++++++++- .../code-examples/zh-cn/code-examples.ts | 462 +++++++++++++++++- .../code-examples/zh-tw/code-examples.ts | 458 ++++++++++++++++- components/header.tsx | 8 + content/docs/js2java/meta.json | 52 +- .../js2java/module-00-java-introduction.mdx | 360 +++++++------- 7 files changed, 1910 insertions(+), 238 deletions(-) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..acd80bd --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,346 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## 🎯 CRITICAL: Code Component Usage Priority + +### ⭐ UniversalEditor Component (HIGHEST PRIORITY) + +**ALWAYS use the `` component for ALL code comparisons in learning module content.** + +The UniversalEditor component provides: +- Interactive code editing in the browser +- Side-by-side language comparison +- Real-time code execution (where supported) +- Consistent user experience across all modules + +#### When to Use UniversalEditor: +✅ **ALWAYS USE** for: +- Side-by-side language comparisons +- Interactive code examples +- Code pattern demonstrations +- Before/after code transformations +- Language feature comparisons + +❌ **NEVER USE** (unless specifically required): +- Static code blocks without comparison +- Single-language examples without comparison context +- Non-interactive code displays + +#### UniversalEditor Syntax: +```md + +```language !! syntax-highlighter +// Your code here +``` + +```other-language !! other-syntax-highlighter +// Your comparison code here +``` + +``` + +#### Example: +```md + +```python !! py +# Python - Dynamic typing +name = "Alice" +age = 25 +``` + +```rust !! rust +// Rust - Static typing +let name: &str = "Alice"; +let age: i32 = 25; +``` + +``` + +**IMPORTANT: UniversalEditor is the DEFAULT and PREFERRED component for all code examples in learning modules.** + +### Other Code Components (Use Only When Appropriate): + +#### `` Component +For simple syntax highlighting without interactivity: +```tsx +import { Code } from '@/components/code' + +{`code here`} +``` + +Use cases: +- Documentation sections +- Configuration files +- Non-interactive code snippets + +#### `` Component +For single-language interactive editing: +```tsx +import { VirtualizedEditor } from '@/components/virtualized-editor' + + setCode(value)} + height={300} +/> +``` + +Use cases: +- Single-language code editors +- Exercise/practice areas +- When only one language needs to be shown + +## Project Overview + +LangShift.dev is a programming language conversion learning platform built with Next.js 15.5.9 and Fumadocs 15.6.1. It helps developers learn new programming languages through comparative learning, starting from their existing language knowledge. + +**Core Philosophy:** Teach new languages by comparing them to a known language, showing syntax mappings, concept translations, and performance differences. + +### Supported Language Conversions +- JavaScript → Python (13 modules) ✅ +- JavaScript → Rust (14 modules) ✅ +- JavaScript → Go (14 modules) ✅ +- JavaScript → Kotlin (14 modules) ✅ +- JavaScript → C++ (15 modules) ✅ +- JavaScript → Swift (15 modules) ✅ +- JavaScript → C (15 modules) ✅ +- JavaScript → Java (20 modules) ✅ +- Python → JavaScript ✅ +- Python → Rust (planned) + +## Development Commands + +```bash +# Development server (runs on port 8000) +pnpm dev + +# Production build +pnpm build + +# Start production server +pnpm start + +# Type checking +pnpm type-check + +# Linting +pnpm lint + +# SEO checking (builds and validates sitemap/robots.txt) +pnpm seo-check + +# Bundle analysis +pnpm analyze +``` + +**Important:** Always use `pnpm` as the package manager. The project uses Turbopack for faster development builds. + +## Architecture Overview + +### Tech Stack +- **Framework:** Next.js 15.5.9 with App Router +- **Documentation:** Fumadocs 15.6.1 + MDX +- **Styling:** Tailwind CSS 4.0.9 +- **Editor:** Monaco Editor 4.7.0 +- **Type Safety:** TypeScript 5.8.2 (strict mode) +- **Search:** Orama 3.1.1 full-text search +- **i18n:** Support for English, Simplified Chinese, Traditional Chinese + +### Content Creation Guidelines + +#### 1. Module Structure + +Each language conversion path follows this structure: +``` +content/docs/{source}2{target}/ +├── meta.json # Module metadata (keep simple!) +├── index.mdx # English introduction +├── index.zh-cn.mdx # Simplified Chinese introduction +├── index.zh-tw.mdx # Traditional Chinese introduction +├── module-00-{topic}.mdx +├── module-00-{topic}.zh-cn.mdx +├── module-00-{topic}.zh-tw.mdx +└── ... (more modules) +``` + +#### 2. meta.json Format + +**KEEP IT SIMPLE** - Only include title and root: +```json +{ + "title": "Python → Rust", + "root": true +} +``` + +Do NOT add extra fields like pages, language, modules, etc. + +#### 3. Language Versioning + +**File naming convention:** +- Main file: `module-xx-topic.mdx` (English) +- Chinese simplified: `module-xx-topic.zh-cn.mdx` +- Chinese traditional: `module-xx-topic.zh-tw.mdx` + +**CRITICAL:** The main file MUST be in English. Chinese versions use the appropriate suffix. + +#### 4. Module Content Guidelines + +**When creating learning content:** + +1. **Always use UniversalEditor for code comparisons** + - This is the highest priority rule + - Every code comparison should use UniversalEditor + - Provide meaningful titles for each comparison + +2. **Start with the source language** + - Explain new concepts by comparing them to the source language + - Show "before" (source) and "after" (target) code + - Highlight key differences + +3. **Provide runnable code examples** + - Code should be syntactically correct + - Include comments explaining key concepts + - Show realistic, production-relevant examples + +4. **Include performance analysis** + - Compare performance characteristics + - Explain memory management differences + - Discuss optimization strategies + +5. **Show common pitfalls** + - Highlight mistakes developers make when transitioning + - Provide solutions and best practices + - Include "gotcha" warnings + +6. **Use progressive difficulty** + - Start with basic concepts + - Build complexity gradually + - Each module should build on previous knowledge + +#### 5. MDX Frontmatter + +Each module file must have frontmatter: +```md +--- +title: "Module XX: Topic Name" +description: "Brief description of what this module covers" +--- +``` + +#### 6. UniversalEditor Best Practices + +**DO:** +- Use descriptive titles that explain the comparison +- Include helpful comments in code +- Show realistic, practical examples +- Keep code snippets focused and concise +- Demonstrate idiomatic code in both languages + +**DON'T:** +- Use overly simplistic "hello world" examples (unless it's module 0) +- Show contrived or unrealistic code +- Make code snippets too long (>50 lines preferred) +- Skip error handling or edge cases + +#### 7. Module Planning + +When creating a new language conversion path: + +1. **Analyze the source and target languages** + - Identify key conceptual differences + - Map syntax patterns between languages + - Note unique features of each language + +2. **Plan 15-20 modules covering:** + - Module 0: Introduction and environment setup + - Modules 1-5: Basic syntax and concepts + - Modules 6-10: Intermediate features and OOP + - Modules 11-15: Advanced features and ecosystem + - Modules 16-20: Best practices and real-world projects + +3. **Create consistent structure** + - Each module should follow a similar pattern + - Include learning objectives at the start + - Provide exercises at the end + - Add summary sections + +## File Naming Conventions + +- **Learning modules:** `module-{number:02d}-{topic}.mdx` (e.g., `module-01-basics.mdx`) +- **Language conversion directories:** `{source}2{target}` (e.g., `py2rust`) +- **Components:** PascalCase (e.g., `VirtualizedEditor.tsx`) +- **Utilities:** camelCase (e.g., `monaco-manager.tsx`) + +## Header Navigation + +When adding a new language conversion path, update `components/header.tsx`: + +```typescript +const SOURCE_LANGUAGES = [ + { + id: 'python', + name: 'Python', + icon: '🐍', + gradient: 'from-green-500 to-emerald-500', + targets: [ + { + id: 'rust', + name: 'Rust', + icon: '🦀', + gradient: 'from-orange-500 to-red-500', + path: 'py2rust', + status: 'completed' as const, + } + ] + } +] +``` + +## Common Patterns + +### Component Structure +```tsx +'use client' // Required for interactive components + +import { useState, useEffect } from 'react' +import { useMonacoManager } from '@/components/monaco-manager' + +export function MyComponent() { + const { isReady, isLoading } = useMonacoManager() + // Component logic +} +``` + +### Error Handling +- Always handle network errors for API calls +- Provide user-friendly error messages +- Log errors to console for debugging +- Implement retry logic where appropriate + +### Internationalization +- Use `useTranslations` from Fumadocs UI +- Add translation keys to `/messages/*.json` +- Test all three languages (en, zh-cn, zh-tw) +- Ensure language detection works correctly + +## Testing Before Deployment + +1. **Type checking:** `pnpm type-check` must pass +2. **Linting:** `pnpm lint` must pass +3. **Build:** `pnpm build` must succeed +4. **SEO:** `pnpm seo-check` validates sitemap and robots.txt +5. **Manual testing:** Test UniversalEditor components for each language +6. **Performance:** Run `pnpm analyze` to check bundle size + +## Summary of Key Rules + +1. ⭐ **ALWAYS use UniversalEditor for code comparisons** (Highest Priority) +2. Main module files MUST be in English +3. Keep meta.json simple (only title and root) +4. Use consistent file naming conventions +5. Provide practical, realistic code examples +6. Include progressive difficulty levels +7. Test all three language versions diff --git a/components/code-examples/en/code-examples.ts b/components/code-examples/en/code-examples.ts index 8805849..8136877 100644 --- a/components/code-examples/en/code-examples.ts +++ b/components/code-examples/en/code-examples.ts @@ -425,10 +425,10 @@ int main() { std::cout << "5 + 10 = " << add(5, 10) << std::endl; // 2. Used for string concatenation (T is deduced as std::string) - std::cout << "\"Hello, \" + \"World!\" = " - << add(std::string("Hello, "), std::string("World!")) + std::cout << "\"Hello, \" + \"World!\" = " + << add(std::string("Hello, "), std::string("World!")) << std::endl; - + return 0; }`, leftLanguage: 'javascript', @@ -439,5 +439,461 @@ int main() { tags: ['Type System', 'Functions', 'Templates', 'Dynamic vs Static'], difficulty: 'beginner', category: 'Core Concepts' + }, + 'js-go': { + leftCode: `// JavaScript async programming: Promise and async/await +// Simulate async operation +function fetchData() { + return new Promise((resolve) => { + setTimeout(() => { + resolve({ id: 1, name: 'Alice' }); + }, 1000); + }); +} + +// Using async/await +async function getUser() { + console.log('Fetching user data...'); + const user = await fetchData(); + console.log('User:', user); + return user; +} + +// Using Promise.then() +fetchData().then(user => { + console.log('User:', user); +}); + +// Parallel requests +Promise.all([ + fetchData(), + fetchData(), +]).then(users => { + console.log('Multiple users:', users); +});`, + rightCode: `// Go concurrent programming: Goroutines and Channels +package main + +import ( + "fmt" + "time" +) + +// Simulate async operation +func fetchData(userChan chan<- map[string]interface{}) { + time.Sleep(time.Second) + userChan <- map[string]interface{}{ + "id": 1, + "name": "Alice", + } +} + +// Using goroutine and channel +func getUser() { + fmt.Println("Fetching user data...") + userChan := make(chan map[string]interface{}) + go fetchData(userChan) + user := <-userChan + fmt.Println("User:", user) +} + +// Using goroutine and channel +func main() { + // Single request + userChan := make(chan map[string]interface{}) + go fetchData(userChan) + user := <-userChan + fmt.Println("User:", user) + + // Parallel requests (similar to Promise.all) + userChan1 := make(chan map[string]interface{}) + userChan2 := make(chan map[string]interface{}) + go fetchData(userChan1) + go fetchData(userChan2) + user1 := <-userChan1 + user2 := <-userChan2 + fmt.Println("Multiple users:", []map[string]interface{}{user1, user2}) +}`, + leftLanguage: 'javascript', + rightLanguage: 'go', + titleLeft: 'JavaScript', + titleRight: 'Go', + description: 'Async programming patterns comparison - Promise/async-await vs Goroutines/Channels', + tags: ['Async', 'Concurrency', 'Promise', 'Goroutines'], + difficulty: 'intermediate', + category: 'Async Programming' + }, + 'js-swift': { + leftCode: `// JavaScript arrays and higher-order functions +const numbers = [1, 2, 3, 4, 5]; + +// Map - transform array +const doubled = numbers.map(x => x * 2); +console.log(doubled); // [2, 4, 6, 8, 10] + +// Filter - filter array +const evens = numbers.filter(x => x % 2 === 0); +console.log(evens); // [2, 4] + +// Reduce - reduce array +const sum = numbers.reduce((acc, x) => acc + x, 0); +console.log(sum); // 15 + +// Chaining +const result = numbers + .filter(x => x % 2 === 0) + .map(x => x * 2) + .reduce((acc, x) => acc + x, 0); +console.log(result); // 12 + +// Find - find element +const found = numbers.find(x => x > 3); +console.log(found); // 4`, + rightCode: `// Swift arrays and higher-order functions +import Foundation + +let numbers = [1, 2, 3, 4, 5] + +// Map - transform array +let doubled = numbers.map { $0 * 2 } +print(doubled) // [2, 4, 6, 8, 10] + +// Filter - filter array +let evens = numbers.filter { $0 % 2 == 0 } +print(evens) // [2, 4] + +// Reduce - reduce array +let sum = numbers.reduce(0) { $0 + $1 } +print(sum) // 15 + +// Chaining +let result = numbers + .filter { $0 % 2 == 0 } + .map { $0 * 2 } + .reduce(0) { $0 + $1 } +print(result) // 12 + +// Find - find element +let found = numbers.first { $0 > 3 } +print(found as Any) // Optional(4) + +// CompactMap - handle optionals +let strings = ["1", "2", "abc", "4"] +let numbersFromStrings = strings.compactMap { Int($0) } +print(numbersFromStrings) // [1, 2, 4]`, + leftLanguage: 'javascript', + rightLanguage: 'swift', + titleLeft: 'JavaScript', + titleRight: 'Swift', + description: 'Arrays and functional programming - higher-order functions and chaining', + tags: ['Arrays', 'Functional Programming', 'Higher-Order Functions', 'Map/Filter/Reduce'], + difficulty: 'beginner', + category: 'Data Structures' + }, + 'js-c': { + leftCode: `// JavaScript: Dynamic arrays and objects +// Arrays can grow dynamically +const arr = [1, 2, 3]; +arr.push(4); +arr.push(5); +console.log(arr); // [1, 2, 3, 4, 5] + +// Arrays can store any type +arr.push("hello"); +arr.push({ name: "Alice" }); +console.log(arr); // [1, 2, 3, 4, 5, "hello", { name: "Alice" }] + +// Objects can add properties dynamically +const obj = {}; +obj.name = "Alice"; +obj.age = 30; +obj.greet = function() { + return \`Hello, I'm \${this.name}\`; +}; +console.log(obj.greet()); // "Hello, I'm Alice" + +// Automatic memory management +let data = []; +for (let i = 0; i < 1000; i++) { + data.push({ id: i, value: Math.random() }); +} +// Garbage collector automatically cleans memory`, + rightCode: `#include +#include +#include + +// C: Manual memory management and fixed-size arrays +// Array size must be fixed +int arr[5] = {1, 2, 3, 4, 5}; + +// Dynamic arrays require manual memory management +int* create_dynamic_array(int size) { + int* arr = (int*)malloc(size * sizeof(int)); + if (arr == NULL) { + fprintf(stderr, "Memory allocation failed\\n"); + exit(1); + } + return arr; +} + +// Struct definition +struct Person { + char name[50]; + int age; +}; + +// Struct array +struct Person* create_person_array(int size) { + struct Person* people = (struct Person*)malloc(size * sizeof(struct Person)); + if (people == NULL) { + fprintf(stderr, "Memory allocation failed\\n"); + exit(1); + } + return people; +} + +int main() { + // Dynamic array example + int size = 1000; + int* data = create_dynamic_array(size); + + for (int i = 0; i < size; i++) { + data[i] = i; + } + + // Must manually free memory after use + free(data); + + // Struct array example + struct Person* people = create_person_array(10); + strcpy(people[0].name, "Alice"); + people[0].age = 30; + + printf("Name: %s, Age: %d\\n", people[0].name, people[0].age); + + // Manually free memory + free(people); + + return 0; +}`, + leftLanguage: 'javascript', + rightLanguage: 'c', + titleLeft: 'JavaScript', + titleRight: 'C', + description: 'Memory management comparison - garbage collection vs manual memory management', + tags: ['Memory Management', 'Arrays', 'Dynamic vs Static', 'Pointers'], + difficulty: 'advanced', + category: 'Memory Management' + }, + 'js-kt': { + leftCode: `// JavaScript classes and object-oriented programming +class Person { + constructor(name, age) { + this.name = name; + this.age = age; + } + + greet() { + return \`Hello, I'm \${this.name}\`; + } + + introduce() { + return \`I'm \${this.age} years old\`; + } +} + +// Inheritance +class Student extends Person { + constructor(name, age, studentId) { + super(name, age); + this.studentId = studentId; + } + + greet() { + return \`Hi, I'm \${this.name} (ID: \${this.studentId})\`; + } +} + +// Usage +const person = new Person("Alice", 25); +console.log(person.greet()); // "Hello, I'm Alice" + +const student = new Student("Bob", 20, "S12345"); +console.log(student.greet()); // "Hi, I'm Bob (ID: S12345)"`, + rightCode: `// Kotlin classes and object-oriented programming +// Data class - auto-generates equals, hashCode, toString, copy +data class Person( + val name: String, + val age: Int +) { + fun greet() = "Hello, I'm $name" + + fun introduce() = "I'm $age years old" +} + +// Sealed class - restricted class hierarchy +sealed class StudentStatus { + data class Active(val gpa: Double) : StudentStatus() + data class OnProbation(val reason: String) : StudentStatus() + object Graduated : StudentStatus() +} + +// Inheritance +open class Animal(val name: String) { + open fun makeSound() = "Some sound" +} + +class Dog(name: String) : Animal(name) { + override fun makeSound() = "Woof!" +} + +// Usage +fun main() { + val person = Person("Alice", 25) + println(person.greet()) // "Hello, I'm Alice" + + // Data class copy feature + val olderPerson = person.copy(age = 26) + println(olderPerson) // "Person(name=Alice, age=26)" + + val dog = Dog("Buddy") + println(dog.makeSound()) // "Woof!" + + // Smart type casting + fun checkStatus(status: StudentStatus) = when (status) { + is StudentStatus.Active -> "Active with GPA ${status.gpa}" + is StudentStatus.OnProbation -> "On probation: ${status.reason}" + is StudentStatus.Graduated -> "Graduated" + } +}`, + leftLanguage: 'javascript', + rightLanguage: 'kotlin', + titleLeft: 'JavaScript', + titleRight: 'Kotlin', + description: 'Object-oriented programming comparison - class inheritance vs data classes and sealed classes', + tags: ['OOP', 'Classes', 'Inheritance', 'Data Classes'], + difficulty: 'intermediate', + category: 'Object-Oriented Programming' + }, + 'js-java': { + leftCode: `// JavaScript: Flexible function definitions +// Function declaration +function add(a, b) { + return a + b; +} + +// Function expression +const multiply = function(a, b) { + return a * b; +}; + +// Arrow function +const subtract = (a, b) => a - b; + +// Default parameters +function greet(name = "World", greeting = "Hello") { + return \`\${greeting}, \${name}!\`; +} + +// Destructuring parameters +function createUser({ name, age, email }) { + return { name, age, email }; +} + +const user = createUser({ + name: "Alice", + age: 30, + email: "alice@example.com" +}); + +// Rest parameters +function sum(...numbers) { + return numbers.reduce((acc, n) => acc + n, 0); +} + +console.log(sum(1, 2, 3, 4, 5)); // 15`, + rightCode: `// Java: Strict method definitions +public class Main { + // Method overloading - same method name, different parameters + public static int add(int a, int b) { + return a + b; + } + + public static double add(double a, double b) { + return a + b; + } + + // Varargs + public static int sum(int... numbers) { + int total = 0; + for (int num : numbers) { + total += num; + } + return total; + } + + // Using class to encapsulate parameters (similar to destructuring) + static class User { + String name; + int age; + String email; + + User(String name, int age, String email) { + this.name = name; + this.age = age; + this.email = email; + } + } + + public static User createUser(User user) { + return user; + } + + // Using Builder pattern (more flexible parameter passing) + static class UserBuilder { + private String name; + private int age; + private String email; + + public UserBuilder setName(String name) { + this.name = name; + return this; + } + + public UserBuilder setAge(int age) { + this.age = age; + return this; + } + + public UserBuilder setEmail(String email) { + this.email = email; + return this; + } + + public User build() { + return new User(name, age, email); + } + } + + public static void main(String[] args) { + System.out.println(add(5, 3)); // 8 + System.out.println(add(5.5, 3.2)); // 8.7 + System.out.println(sum(1, 2, 3, 4, 5)); // 15 + + User user = new UserBuilder() + .setName("Alice") + .setAge(30) + .setEmail("alice@example.com") + .build(); + } +}`, + leftLanguage: 'javascript', + rightLanguage: 'java', + titleLeft: 'JavaScript', + titleRight: 'Java', + description: 'Function and method definition comparison - flexibility vs strictness', + tags: ['Functions', 'Methods', 'Parameters', 'Overloading'], + difficulty: 'intermediate', + category: 'Functional Programming' } }; \ No newline at end of file diff --git a/components/code-examples/zh-cn/code-examples.ts b/components/code-examples/zh-cn/code-examples.ts index d23deb6..736b9eb 100644 --- a/components/code-examples/zh-cn/code-examples.ts +++ b/components/code-examples/zh-cn/code-examples.ts @@ -425,10 +425,10 @@ int main() { std::cout << "5 + 10 = " << add(5, 10) << std::endl; // 2. 用于字符串拼接 (T 被推导为 std::string) - std::cout << "\"Hello, \" + \"World!\" = " - << add(std::string("Hello, "), std::string("World!")) + std::cout << "\"Hello, \" + \"World!\" = " + << add(std::string("Hello, "), std::string("World!")) << std::endl; - + return 0; }`, leftLanguage: 'javascript', @@ -439,5 +439,461 @@ int main() { tags: ['类型系统', '函数', '模板', '动态 vs 静态'], difficulty: 'beginner', category: '核心概念' + }, + 'js-go': { + leftCode: `// JavaScript 异步编程:Promise 和 async/await +// 模拟异步操作 +function fetchData() { + return new Promise((resolve) => { + setTimeout(() => { + resolve({ id: 1, name: 'Alice' }); + }, 1000); + }); +} + +// 使用 async/await +async function getUser() { + console.log('获取用户数据...'); + const user = await fetchData(); + console.log('用户:', user); + return user; +} + +// 使用 Promise.then() +fetchData().then(user => { + console.log('用户:', user); +}); + +// 并发请求 +Promise.all([ + fetchData(), + fetchData(), +]).then(users => { + console.log('多个用户:', users); +});`, + rightCode: `// Go 并发编程:Goroutines 和 Channels +package main + +import ( + "fmt" + "time" +) + +// 模拟异步操作 +func fetchData(userChan chan<- map[string]interface{}) { + time.Sleep(time.Second) + userChan <- map[string]interface{}{ + "id": 1, + "name": "Alice", + } +} + +// 使用 goroutine 和 channel +func getUser() { + fmt.Println("获取用户数据...") + userChan := make(chan map[string]interface{}) + go fetchData(userChan) + user := <-userChan + fmt.Println("用户:", user) +} + +// 使用 goroutine 和 channel +func main() { + // 单个请求 + userChan := make(chan map[string]interface{}) + go fetchData(userChan) + user := <-userChan + fmt.Println("用户:", user) + + // 并发请求(类似 Promise.all) + userChan1 := make(chan map[string]interface{}) + userChan2 := make(chan map[string]interface{}) + go fetchData(userChan1) + go fetchData(userChan2) + user1 := <-userChan1 + user2 := <-userChan2 + fmt.Println("多个用户:", []map[string]interface{}{user1, user2}) +}`, + leftLanguage: 'javascript', + rightLanguage: 'go', + titleLeft: 'JavaScript', + titleRight: 'Go', + description: '异步编程模式对比 - Promise/async-await vs Goroutines/Channels', + tags: ['异步编程', '并发', 'Promise', 'Goroutines'], + difficulty: 'intermediate', + category: '异步编程' + }, + 'js-swift': { + leftCode: `// JavaScript 数组和高阶函数 +const numbers = [1, 2, 3, 4, 5]; + +// Map - 转换数组 +const doubled = numbers.map(x => x * 2); +console.log(doubled); // [2, 4, 6, 8, 10] + +// Filter - 过滤数组 +const evens = numbers.filter(x => x % 2 === 0); +console.log(evens); // [2, 4] + +// Reduce - 归约数组 +const sum = numbers.reduce((acc, x) => acc + x, 0); +console.log(sum); // 15 + +// 链式调用 +const result = numbers + .filter(x => x % 2 === 0) + .map(x => x * 2) + .reduce((acc, x) => acc + x, 0); +console.log(result); // 12 + +// Find - 查找元素 +const found = numbers.find(x => x > 3); +console.log(found); // 4`, + rightCode: `// Swift 数组和高阶函数 +import Foundation + +let numbers = [1, 2, 3, 4, 5] + +// Map - 转换数组 +let doubled = numbers.map { $0 * 2 } +print(doubled) // [2, 4, 6, 8, 10] + +// Filter - 过滤数组 +let evens = numbers.filter { $0 % 2 == 0 } +print(evens) // [2, 4] + +// Reduce - 归约数组 +let sum = numbers.reduce(0) { $0 + $1 } +print(sum) // 15 + +// 链式调用 +let result = numbers + .filter { $0 % 2 == 0 } + .map { $0 * 2 } + .reduce(0) { $0 + $1 } +print(result) // 12 + +// Find - 查找元素 +let found = numbers.first { $0 > 3 } +print(found as Any) // Optional(4) + +// CompactMap - 处理可选值 +let strings = ["1", "2", "abc", "4"] +let numbersFromStrings = strings.compactMap { Int($0) } +print(numbersFromStrings) // [1, 2, 4]`, + leftLanguage: 'javascript', + rightLanguage: 'swift', + titleLeft: 'JavaScript', + titleRight: 'Swift', + description: '数组和函数式编程 - 高阶函数和链式调用', + tags: ['数组', '函数式编程', '高阶函数', 'Map/Filter/Reduce'], + difficulty: 'beginner', + category: '数据结构' + }, + 'js-c': { + leftCode: `// JavaScript: 动态数组和对象 +// 数组可以动态增长 +const arr = [1, 2, 3]; +arr.push(4); +arr.push(5); +console.log(arr); // [1, 2, 3, 4, 5] + +// 数组可以存储任意类型 +arr.push("hello"); +arr.push({ name: "Alice" }); +console.log(arr); // [1, 2, 3, 4, 5, "hello", { name: "Alice" }] + +// 对象动态添加属性 +const obj = {}; +obj.name = "Alice"; +obj.age = 30; +obj.greet = function() { + return \`Hello, I'm \${this.name}\`; +}; +console.log(obj.greet()); // "Hello, I'm Alice" + +// 自动内存管理 +let data = []; +for (let i = 0; i < 1000; i++) { + data.push({ id: i, value: Math.random() }); +} +// 垃圾回收器会自动清理内存`, + rightCode: `#include +#include +#include + +// C: 手动内存管理和固定大小数组 +// 数组大小必须固定 +int arr[5] = {1, 2, 3, 4, 5}; + +// 动态数组需要手动管理内存 +int* create_dynamic_array(int size) { + int* arr = (int*)malloc(size * sizeof(int)); + if (arr == NULL) { + fprintf(stderr, "内存分配失败\\n"); + exit(1); + } + return arr; +} + +// 结构体定义 +struct Person { + char name[50]; + int age; +}; + +// 结构体数组 +struct Person* create_person_array(int size) { + struct Person* people = (struct Person*)malloc(size * sizeof(struct Person)); + if (people == NULL) { + fprintf(stderr, "内存分配失败\\n"); + exit(1); + } + return people; +} + +int main() { + // 动态数组示例 + int size = 1000; + int* data = create_dynamic_array(size); + + for (int i = 0; i < size; i++) { + data[i] = i; + } + + // 使用完必须手动释放内存 + free(data); + + // 结构体数组示例 + struct Person* people = create_person_array(10); + strcpy(people[0].name, "Alice"); + people[0].age = 30; + + printf("Name: %s, Age: %d\\n", people[0].name, people[0].age); + + // 手动释放内存 + free(people); + + return 0; +}`, + leftLanguage: 'javascript', + rightLanguage: 'c', + titleLeft: 'JavaScript', + titleRight: 'C', + description: '内存管理对比 - 垃圾回收 vs 手动内存管理', + tags: ['内存管理', '数组', '动态 vs 静态', '指针'], + difficulty: 'advanced', + category: '内存管理' + }, + 'js-kt': { + leftCode: `// JavaScript 类和面向对象编程 +class Person { + constructor(name, age) { + this.name = name; + this.age = age; + } + + greet() { + return \`Hello, I'm \${this.name}\`; + } + + introduce() { + return \`I'm \${this.age} years old\`; + } +} + +// 继承 +class Student extends Person { + constructor(name, age, studentId) { + super(name, age); + this.studentId = studentId; + } + + greet() { + return \`Hi, I'm \${this.name} (ID: \${this.studentId})\`; + } +} + +// 使用 +const person = new Person("Alice", 25); +console.log(person.greet()); // "Hello, I'm Alice" + +const student = new Student("Bob", 20, "S12345"); +console.log(student.greet()); // "Hi, I'm Bob (ID: S12345)"`, + rightCode: `// Kotlin 类和面向对象编程 +// 数据类 - 自动生成 equals, hashCode, toString, copy +data class Person( + val name: String, + val age: Int +) { + fun greet() = "Hello, I'm $name" + + fun introduce() = "I'm $age years old" +} + +// 密封类 - 受限的类继承结构 +sealed class StudentStatus { + data class Active(val gpa: Double) : StudentStatus() + data class OnProbation(val reason: String) : StudentStatus() + object Graduated : StudentStatus() +} + +// 继承 +open class Animal(val name: String) { + open fun makeSound() = "Some sound" +} + +class Dog(name: String) : Animal(name) { + override fun makeSound() = "Woof!" +} + +// 使用 +fun main() { + val person = Person("Alice", 25) + println(person.greet()) // "Hello, I'm Alice" + + // 数据类的 copy 功能 + val olderPerson = person.copy(age = 26) + println(olderPerson) // "Person(name=Alice, age=26)" + + val dog = Dog("Buddy") + println(dog.makeSound()) // "Woof!" + + // 智能类型转换 + fun checkStatus(status: StudentStatus) = when (status) { + is StudentStatus.Active -> "Active with GPA ${status.gpa}" + is StudentStatus.OnProbation -> "On probation: ${status.reason}" + is StudentStatus.Graduated -> "Graduated" + } +}`, + leftLanguage: 'javascript', + rightLanguage: 'kotlin', + titleLeft: 'JavaScript', + titleRight: 'Kotlin', + description: '面向对象编程对比 - 类继承 vs 数据类和密封类', + tags: ['面向对象', '类', '继承', '数据类'], + difficulty: 'intermediate', + category: '面向对象编程' + }, + 'js-java': { + leftCode: `// JavaScript: 灵活的函数定义 +// 函数声明 +function add(a, b) { + return a + b; +} + +// 函数表达式 +const multiply = function(a, b) { + return a * b; +}; + +// 箭头函数 +const subtract = (a, b) => a - b; + +// 默认参数 +function greet(name = "World", greeting = "Hello") { + return \`\${greeting}, \${name}!\`; +} + +// 解构参数 +function createUser({ name, age, email }) { + return { name, age, email }; +} + +const user = createUser({ + name: "Alice", + age: 30, + email: "alice@example.com" +}); + +// 剩余参数 +function sum(...numbers) { + return numbers.reduce((acc, n) => acc + n, 0); +} + +console.log(sum(1, 2, 3, 4, 5)); // 15`, + rightCode: `// Java: 严格的方法定义 +public class Main { + // 方法重载 - 相同方法名,不同参数 + public static int add(int a, int b) { + return a + b; + } + + public static double add(double a, double b) { + return a + b; + } + + // 可变参数 + public static int sum(int... numbers) { + int total = 0; + for (int num : numbers) { + total += num; + } + return total; + } + + // 使用类封装参数(类似解构) + static class User { + String name; + int age; + String email; + + User(String name, int age, String email) { + this.name = name; + this.age = age; + this.email = email; + } + } + + public static User createUser(User user) { + return user; + } + + // 使用 Builder 模式(更灵活的参数传递) + static class UserBuilder { + private String name; + private int age; + private String email; + + public UserBuilder setName(String name) { + this.name = name; + return this; + } + + public UserBuilder setAge(int age) { + this.age = age; + return this; + } + + public UserBuilder setEmail(String email) { + this.email = email; + return this; + } + + public User build() { + return new User(name, age, email); + } + } + + public static void main(String[] args) { + System.out.println(add(5, 3)); // 8 + System.out.println(add(5.5, 3.2)); // 8.7 + System.out.println(sum(1, 2, 3, 4, 5)); // 15 + + User user = new UserBuilder() + .setName("Alice") + .setAge(30) + .setEmail("alice@example.com") + .build(); + } +}`, + leftLanguage: 'javascript', + rightLanguage: 'java', + titleLeft: 'JavaScript', + titleRight: 'Java', + description: '函数和方法定义对比 - 灵活性 vs 严格性', + tags: ['函数', '方法', '参数', '重载'], + difficulty: 'intermediate', + category: '函数编程' } }; \ No newline at end of file diff --git a/components/code-examples/zh-tw/code-examples.ts b/components/code-examples/zh-tw/code-examples.ts index 985403b..6034b40 100644 --- a/components/code-examples/zh-tw/code-examples.ts +++ b/components/code-examples/zh-tw/code-examples.ts @@ -428,7 +428,7 @@ int main() { std::cout << "\"Hello, \" + \"World!\" = " << add(std::string("Hello, "), std::string("World!")) << std::endl; - + return 0; }`, leftLanguage: 'javascript', @@ -439,5 +439,461 @@ int main() { tags: ['類型系統', '函數', '模板', '動態 vs 靜態'], difficulty: 'beginner', category: '核心概念' + }, + 'js-go': { + leftCode: `// JavaScript 異步編程:Promise 和 async/await +// 模擬異步操作 +function fetchData() { + return new Promise((resolve) => { + setTimeout(() => { + resolve({ id: 1, name: 'Alice' }); + }, 1000); + }); +} + +// 使用 async/await +async function getUser() { + console.log('獲取用戶數據...'); + const user = await fetchData(); + console.log('用戶:', user); + return user; +} + +// 使用 Promise.then() +fetchData().then(user => { + console.log('用戶:', user); +}); + +// 並發請求 +Promise.all([ + fetchData(), + fetchData(), +]).then(users => { + console.log('多個用戶:', users); +});`, + rightCode: `// Go 並發編程:Goroutines 和 Channels +package main + +import ( + "fmt" + "time" +) + +// 模擬異步操作 +func fetchData(userChan chan<- map[string]interface{}) { + time.Sleep(time.Second) + userChan <- map[string]interface{}{ + "id": 1, + "name": "Alice", + } +} + +// 使用 goroutine 和 channel +func getUser() { + fmt.Println("獲取用戶數據...") + userChan := make(chan map[string]interface{}) + go fetchData(userChan) + user := <-userChan + fmt.Println("用戶:", user) +} + +// 使用 goroutine 和 channel +func main() { + // 單個請求 + userChan := make(chan map[string]interface{}) + go fetchData(userChan) + user := <-userChan + fmt.Println("用戶:", user) + + // 並發請求(類似 Promise.all) + userChan1 := make(chan map[string]interface{}) + userChan2 := make(chan map[string]interface{}) + go fetchData(userChan1) + go fetchData(userChan2) + user1 := <-userChan1 + user2 := <-userChan2 + fmt.Println("多個用戶:", []map[string]interface{}{user1, user2}) +}`, + leftLanguage: 'javascript', + rightLanguage: 'go', + titleLeft: 'JavaScript', + titleRight: 'Go', + description: '異步編程模式對比 - Promise/async-await vs Goroutines/Channels', + tags: ['異步編程', '並發', 'Promise', 'Goroutines'], + difficulty: 'intermediate', + category: '異步編程' + }, + 'js-swift': { + leftCode: `// JavaScript 數組和高階函數 +const numbers = [1, 2, 3, 4, 5]; + +// Map - 轉換數組 +const doubled = numbers.map(x => x * 2); +console.log(doubled); // [2, 4, 6, 8, 10] + +// Filter - 過濾數組 +const evens = numbers.filter(x => x % 2 === 0); +console.log(evens); // [2, 4] + +// Reduce - 歸約數組 +const sum = numbers.reduce((acc, x) => acc + x, 0); +console.log(sum); // 15 + +// 鏈式調用 +const result = numbers + .filter(x => x % 2 === 0) + .map(x => x * 2) + .reduce((acc, x) => acc + x, 0); +console.log(result); // 12 + +// Find - 查找元素 +const found = numbers.find(x => x > 3); +console.log(found); // 4`, + rightCode: `// Swift 數組和高階函數 +import Foundation + +let numbers = [1, 2, 3, 4, 5] + +// Map - 轉換數組 +let doubled = numbers.map { $0 * 2 } +print(doubled) // [2, 4, 6, 8, 10] + +// Filter - 過濾數組 +let evens = numbers.filter { $0 % 2 == 0 } +print(evens) // [2, 4] + +// Reduce - 歸約數組 +let sum = numbers.reduce(0) { $0 + $1 } +print(sum) // 15 + +// 鏈式調用 +let result = numbers + .filter { $0 % 2 == 0 } + .map { $0 * 2 } + .reduce(0) { $0 + $1 } +print(result) // 12 + +// Find - 查找元素 +let found = numbers.first { $0 > 3 } +print(found as Any) // Optional(4) + +// CompactMap - 處理可選值 +let strings = ["1", "2", "abc", "4"] +let numbersFromStrings = strings.compactMap { Int($0) } +print(numbersFromStrings) // [1, 2, 4]`, + leftLanguage: 'javascript', + rightLanguage: 'swift', + titleLeft: 'JavaScript', + titleRight: 'Swift', + description: '數組和函數式編程 - 高階函數和鏈式調用', + tags: ['數組', '函數式編程', '高階函數', 'Map/Filter/Reduce'], + difficulty: 'beginner', + category: '數據結構' + }, + 'js-c': { + leftCode: `// JavaScript: 動態數組和對象 +// 數組可以動態增長 +const arr = [1, 2, 3]; +arr.push(4); +arr.push(5); +console.log(arr); // [1, 2, 3, 4, 5] + +// 數組可以存儲任意類型 +arr.push("hello"); +arr.push({ name: "Alice" }); +console.log(arr); // [1, 2, 3, 4, 5, "hello", { name: "Alice" }] + +// 對象動態添加屬性 +const obj = {}; +obj.name = "Alice"; +obj.age = 30; +obj.greet = function() { + return \`Hello, I'm \${this.name}\`; +}; +console.log(obj.greet()); // "Hello, I'm Alice" + +// 自動記憶體管理 +let data = []; +for (let i = 0; i < 1000; i++) { + data.push({ id: i, value: Math.random() }); +} +// 垃圾回收器會自動清理記憶體`, + rightCode: `#include +#include +#include + +// C: 手動記憶體管理和固定大小數組 +// 數組大小必須固定 +int arr[5] = {1, 2, 3, 4, 5}; + +// 動態數組需要手動管理記憶體 +int* create_dynamic_array(int size) { + int* arr = (int*)malloc(size * sizeof(int)); + if (arr == NULL) { + fprintf(stderr, "記憶體分配失敗\\n"); + exit(1); + } + return arr; +} + +// 結構體定義 +struct Person { + char name[50]; + int age; +}; + +// 結構體數組 +struct Person* create_person_array(int size) { + struct Person* people = (struct Person*)malloc(size * sizeof(struct Person)); + if (people == NULL) { + fprintf(stderr, "記憶體分配失敗\\n"); + exit(1); + } + return people; +} + +int main() { + // 動態數組示例 + int size = 1000; + int* data = create_dynamic_array(size); + + for (int i = 0; i < size; i++) { + data[i] = i; + } + + // 使用完必須手動釋放記憶體 + free(data); + + // 結構體數組示例 + struct Person* people = create_person_array(10); + strcpy(people[0].name, "Alice"); + people[0].age = 30; + + printf("Name: %s, Age: %d\\n", people[0].name, people[0].age); + + // 手動釋放記憶體 + free(people); + + return 0; +}`, + leftLanguage: 'javascript', + rightLanguage: 'c', + titleLeft: 'JavaScript', + titleRight: 'C', + description: '記憶體管理對比 - 垃圾回收 vs 手動記憶體管理', + tags: ['記憶體管理', '數組', '動態 vs 靜態', '指針'], + difficulty: 'advanced', + category: '記憶體管理' + }, + 'js-kt': { + leftCode: `// JavaScript 類和面向對象編程 +class Person { + constructor(name, age) { + this.name = name; + this.age = age; + } + + greet() { + return \`Hello, I'm \${this.name}\`; + } + + introduce() { + return \`I'm \${this.age} years old\`; + } +} + +// 繼承 +class Student extends Person { + constructor(name, age, studentId) { + super(name, age); + this.studentId = studentId; + } + + greet() { + return \`Hi, I'm \${this.name} (ID: \${this.studentId})\`; + } +} + +// 使用 +const person = new Person("Alice", 25); +console.log(person.greet()); // "Hello, I'm Alice" + +const student = new Student("Bob", 20, "S12345"); +console.log(student.greet()); // "Hi, I'm Bob (ID: S12345)"`, + rightCode: `// Kotlin 類和面向對象編程 +// 數據類 - 自動生成 equals, hashCode, toString, copy +data class Person( + val name: String, + val age: Int +) { + fun greet() = "Hello, I'm $name" + + fun introduce() = "I'm $age years old" +} + +// 密封類 - 受限的類繼承結構 +sealed class StudentStatus { + data class Active(val gpa: Double) : StudentStatus() + data class OnProbation(val reason: String) : StudentStatus() + object Graduated : StudentStatus() +} + +// 繼承 +open class Animal(val name: String) { + open fun makeSound() = "Some sound" +} + +class Dog(name: String) : Animal(name) { + override fun makeSound() = "Woof!" +} + +// 使用 +fun main() { + val person = Person("Alice", 25) + println(person.greet()) // "Hello, I'm Alice" + + // 數據類的 copy 功能 + val olderPerson = person.copy(age = 26) + println(olderPerson) // "Person(name=Alice, age=26)" + + val dog = Dog("Buddy") + println(dog.makeSound()) // "Woof!" + + // 智能類型轉換 + fun checkStatus(status: StudentStatus) = when (status) { + is StudentStatus.Active -> "Active with GPA ${status.gpa}" + is StudentStatus.OnProbation -> "On probation: ${status.reason}" + is StudentStatus.Graduated -> "Graduated" + } +}`, + leftLanguage: 'javascript', + rightLanguage: 'kotlin', + titleLeft: 'JavaScript', + titleRight: 'Kotlin', + description: '面向對象編程對比 - 類繼承 vs 數據類和密封類', + tags: ['面向對象', '類', '繼承', '數據類'], + difficulty: 'intermediate', + category: '面向對象編程' + }, + 'js-java': { + leftCode: `// JavaScript: 靈活的函數定義 +// 函數聲明 +function add(a, b) { + return a + b; +} + +// 函數表達式 +const multiply = function(a, b) { + return a * b; +}; + +// 箭頭函數 +const subtract = (a, b) => a - b; + +// 默認參數 +function greet(name = "World", greeting = "Hello") { + return \`\${greeting}, \${name}!\`; +} + +// 解構參數 +function createUser({ name, age, email }) { + return { name, age, email }; +} + +const user = createUser({ + name: "Alice", + age: 30, + email: "alice@example.com" +}); + +// 剩餘參數 +function sum(...numbers) { + return numbers.reduce((acc, n) => acc + n, 0); +} + +console.log(sum(1, 2, 3, 4, 5)); // 15`, + rightCode: `// Java: 嚴格的方法定義 +public class Main { + // 方法重載 - 相同方法名,不同參數 + public static int add(int a, int b) { + return a + b; + } + + public static double add(double a, double b) { + return a + b; + } + + // 可變參數 + public static int sum(int... numbers) { + int total = 0; + for (int num : numbers) { + total += num; + } + return total; + } + + // 使用類封裝參數(類似解構) + static class User { + String name; + int age; + String email; + + User(String name, int age, String email) { + this.name = name; + this.age = age; + this.email = email; + } + } + + public static User createUser(User user) { + return user; + } + + // 使用 Builder 模式(更靈活的參數傳遞) + static class UserBuilder { + private String name; + private int age; + private String email; + + public UserBuilder setName(String name) { + this.name = name; + return this; + } + + public UserBuilder setAge(int age) { + this.age = age; + return this; + } + + public UserBuilder setEmail(String email) { + this.email = email; + return this; + } + + public User build() { + return new User(name, age, email); + } + } + + public static void main(String[] args) { + System.out.println(add(5, 3)); // 8 + System.out.println(add(5.5, 3.2)); // 8.7 + System.out.println(sum(1, 2, 3, 4, 5)); // 15 + + User user = new UserBuilder() + .setName("Alice") + .setAge(30) + .setEmail("alice@example.com") + .build(); + } +}`, + leftLanguage: 'javascript', + rightLanguage: 'java', + titleLeft: 'JavaScript', + titleRight: 'Java', + description: '函數和方法定義對比 - 靈活性 vs 嚴格性', + tags: ['函數', '方法', '參數', '重載'], + difficulty: 'intermediate', + category: '函數編程' } }; \ No newline at end of file diff --git a/components/header.tsx b/components/header.tsx index 4b69977..43d651c 100644 --- a/components/header.tsx +++ b/components/header.tsx @@ -73,6 +73,14 @@ const SOURCE_LANGUAGES = [ path: 'js2kotlin', status: 'completed' as const, }, + { + id: 'java', + name: 'Java', + icon: '☕', + gradient: 'from-red-500 to-rose-500', + path: 'js2java', + status: 'completed' as const, + }, ] }, { diff --git a/content/docs/js2java/meta.json b/content/docs/js2java/meta.json index 4cfc5ec..1a1b2e2 100644 --- a/content/docs/js2java/meta.json +++ b/content/docs/js2java/meta.json @@ -1,54 +1,4 @@ { "title": "Js2Java", - "root": true, - "pages": [ - "index", - "module-00-java-introduction", - "module-01-syntax-comparison", - "module-02-types-variables", - "module-03-control-flow", - "module-04-arrays-collections", - "module-05-functions-methods", - "module-06-classes-objects", - "module-07-inheritance-polymorphism", - "module-08-interfaces-abstract", - "module-09-exceptions-handling", - "module-10-generics", - "module-11-collections-framework", - "module-12-concurrency", - "module-13-spring-framework", - "module-14-web-development", - "module-15-projects", - "module-16-common-pitfalls", - "module-17-idiomatic-java", - "module-18-advanced-topics", - "module-19-performance-optimization" - ], - "language": "java", - "sourceLanguage": "javascript", - "targetLanguage": "java", - "modules": 20, - "difficulty": "intermediate", - "estimatedHours": 40, - "tags": [ - "java", - "javascript", - "面向对象编程", - "企业级开发", - "spring框架", - "并发编程", - "jvm", - "web开发", - "微服务" - ], - "features": [ - "面向对象编程深度解析", - "Spring 框架实战", - "企业级开发最佳实践", - "并发编程详解", - "JVM 性能优化", - "现代 Java 特性", - "Web 开发实战", - "微服务架构" - ] + "root": true } \ No newline at end of file diff --git a/content/docs/js2java/module-00-java-introduction.mdx b/content/docs/js2java/module-00-java-introduction.mdx index e679a7e..4c586c4 100644 --- a/content/docs/js2java/module-00-java-introduction.mdx +++ b/content/docs/js2java/module-00-java-introduction.mdx @@ -1,92 +1,92 @@ --- -title: "Java 语言介绍" -description: "了解 Java 语言的历史、设计哲学和开发环境搭建,为 JavaScript 开发者提供 Java 学习的基础" +title: "Introduction to Java & Environment Setup" +description: "Understand Java's history, design philosophy, and set up the development environment for JavaScript developers transitioning to Java" --- -# Java 语言介绍 +# Introduction to Java & Environment Setup -欢迎来到 Java 编程语言的世界!作为 JavaScript 开发者,你将发现 Java 提供了一个全新的编程范式,特别是在面向对象编程和企业级开发方面。 +Welcome to the world of Java programming! As a JavaScript developer, you'll discover that Java offers a completely new programming paradigm, especially in object-oriented programming and enterprise development. -## Java 语言历史 +## Java Language History -Java 由 Sun Microsystems 的 James Gosling 团队在 1995 年创建,最初被称为 Oak。它的设计目标是: +Java was created by James Gosling's team at Sun Microsystems in 1995, originally called Oak. Its design goals were: -- **Write Once, Run Anywhere (WORA)**: 一次编写,到处运行 -- **面向对象**: 完全基于对象的设计 -- **安全性**: 内置的安全机制 -- **网络编程**: 原生支持网络应用开发 +- **Write Once, Run Anywhere (WORA)**: Code written once runs everywhere +- **Object-Oriented**: Completely object-based design +- **Security**: Built-in security mechanisms +- **Network Programming**: Native support for network application development - + ```javascript !! js -// JavaScript - 简单的 Hello World +// JavaScript - Simple Hello World console.log("Hello, World!"); -// 在浏览器中直接运行 -// 无需编译,解释执行 +// Runs directly in browser +// No compilation needed, interpreted execution ``` ```java !! java -// Java - Hello World 程序 +// Java - Hello World Program public class HelloWorld { public static void main(String[] args) { System.out.println("Hello, World!"); } } -// 需要编译为字节码,然后在 JVM 上运行 +// Needs to be compiled to bytecode, then run on JVM // javac HelloWorld.java // java HelloWorld ``` -## Java vs JavaScript 设计哲学对比 +## Java vs JavaScript Design Philosophy Comparison -### JavaScript 的设计哲学 -- **动态类型**: 变量类型在运行时确定 -- **原型继承**: 基于原型的对象系统 -- **函数式编程**: 函数是一等公民 -- **解释执行**: 直接解释执行,无需编译 +### JavaScript Design Philosophy +- **Dynamic Typing**: Variable types determined at runtime +- **Prototype Inheritance**: Prototype-based object system +- **Functional Programming**: Functions are first-class citizens +- **Interpreted Execution**: Direct interpretation, no compilation -### Java 的设计哲学 -- **静态类型**: 编译时确定类型 -- **类继承**: 基于类的面向对象系统 -- **强类型**: 严格的类型检查 -- **编译执行**: 编译为字节码,在 JVM 上运行 +### Java Design Philosophy +- **Static Typing**: Types determined at compile time +- **Class Inheritance**: Class-based object-oriented system +- **Strong Typing**: Strict type checking +- **Compiled Execution**: Compiled to bytecode, runs on JVM - + ```javascript !! js -// JavaScript - 动态类型 -let name = "John"; // 字符串 -name = 42; // 数字 -name = { age: 25 }; // 对象 -name = [1, 2, 3]; // 数组 -name = function() {}; // 函数 - -// 类型在运行时确定,灵活但可能导致错误 +// JavaScript - Dynamic Typing +let name = "John"; // String +name = 42; // Number +name = { age: 25 }; // Object +name = [1, 2, 3]; // Array +name = function() {}; // Function + +// Types determined at runtime, flexible but can lead to errors ``` ```java !! java -// Java - 静态类型 -String name = "John"; // 字符串 -// name = 42; // 编译错误! -// name = new Object(); // 编译错误! +// Java - Static Typing +String name = "John"; // String +// name = 42; // Compilation error! +// name = new Object(); // Compilation error! -int age = 25; // 整数 -double price = 19.99; // 浮点数 -boolean isActive = true; // 布尔值 +int age = 25; // Integer +double price = 19.99; // Floating point +boolean isActive = true; // Boolean -// 类型在编译时确定,安全但需要显式声明 +// Types determined at compile time, safe but requires explicit declaration ``` -## Java 的优势 +## Java's Advantages -### 1. 企业级开发 -Java 在企业级应用开发中占据主导地位: +### 1. Enterprise Development +Java dominates enterprise application development: - + ```javascript !! js -// JavaScript - 简单的 Web 服务 +// JavaScript - Simple Web Service const express = require('express'); const app = express(); @@ -100,56 +100,56 @@ app.listen(3000, () => { ``` ```java !! java -// Java - Spring Boot 企业级应用 +// Java - Spring Boot Enterprise Application @SpringBootApplication @RestController public class UserController { - + @Autowired private UserService userService; - + @GetMapping("/api/users") public ResponseEntity> getUsers() { List users = userService.getAllUsers(); return ResponseEntity.ok(users); } - + public static void main(String[] args) { SpringApplication.run(UserController.class, args); } } -// 包含依赖注入、事务管理、安全控制等企业级特性 +// Includes enterprise features like dependency injection, transaction management, security control ``` -### 2. 性能优势 -- **JIT 编译**: 即时编译优化 -- **垃圾回收**: 自动内存管理 -- **多线程**: 原生并发支持 -- **优化**: 成熟的性能优化工具 +### 2. Performance Advantages +- **JIT Compilation**: Just-in-time compilation optimization +- **Garbage Collection**: Automatic memory management +- **Multi-threading**: Native concurrency support +- **Optimization**: Mature performance optimization tools -### 3. 生态系统 -- **Spring 框架**: 企业级开发框架 -- **Maven/Gradle**: 构建工具 -- **JUnit**: 测试框架 -- **IDE 支持**: IntelliJ IDEA, Eclipse +### 3. Ecosystem +- **Spring Framework**: Enterprise development framework +- **Maven/Gradle**: Build tools +- **JUnit**: Testing framework +- **IDE Support**: IntelliJ IDEA, Eclipse -## 开发环境搭建 +## Development Environment Setup -### 1. 安装 JDK (Java Development Kit) +### 1. Install JDK (Java Development Kit) #### Windows -1. 下载 OpenJDK 或 Oracle JDK -2. 运行安装程序 -3. 设置环境变量 `JAVA_HOME` +1. Download OpenJDK or Oracle JDK +2. Run the installer +3. Set environment variable `JAVA_HOME` #### macOS ```bash -# 使用 Homebrew +# Using Homebrew brew install openjdk@17 -# 或使用 SDKMAN +# Or use SDKMAN curl -s "https://get.sdkman.io" | bash sdk install java 17.0.2-open ``` @@ -164,35 +164,35 @@ sudo apt install openjdk-17-jdk sudo yum install java-17-openjdk-devel ``` -### 2. 验证安装 +### 2. Verify Installation - + ```bash !! bash -# 检查 Java 版本 +# Check Java version java -version -# 检查编译器版本 +# Check compiler version javac -version -# 应该看到类似输出: +# You should see output like: # openjdk version "17.0.2" 2022-01-18 # OpenJDK Runtime Environment (build 17.0.2+8-86) # OpenJDK 64-Bit Server VM (build 17.0.2+8-86, mixed mode, sharing) ``` -### 3. 安装 IDE +### 3. Install IDE -推荐使用 IntelliJ IDEA: +We recommend using IntelliJ IDEA: - + ```java !! java // HelloWorld.java public class HelloWorld { public static void main(String[] args) { System.out.println("Hello from Java!"); - - // 使用 Java 8+ 的新特性 + + // Using Java 8+ new features String message = "Welcome to Java programming"; message.lines() .forEach(System.out::println); @@ -201,166 +201,166 @@ public class HelloWorld { ``` ```bash !! bash -# 编译和运行 +# Compile and run javac HelloWorld.java java HelloWorld ``` -## Java 平台架构 +## Java Platform Architecture ### JVM (Java Virtual Machine) -- **字节码执行**: 将 Java 字节码转换为机器码 -- **跨平台**: 同一份字节码在不同平台运行 -- **内存管理**: 自动垃圾回收 -- **性能优化**: JIT 编译和优化 +- **Bytecode Execution**: Converts Java bytecode to machine code +- **Cross-Platform**: Same bytecode runs on different platforms +- **Memory Management**: Automatic garbage collection +- **Performance Optimization**: JIT compilation and optimization - + ```java !! java -// Java 源代码 +// Java source code public class Calculator { public int add(int a, int b) { return a + b; } } -// 编译后生成字节码 -// javac Calculator.java 生成 Calculator.class -// 字节码可以在任何有 JVM 的平台上运行 +// After compilation generates bytecode +// javac Calculator.java generates Calculator.class +// Bytecode can run on any platform with JVM ``` ```javascript !! js -// JavaScript 直接解释执行 +// JavaScript is directly interpreted function add(a, b) { return a + b; } -// 不同浏览器的 JavaScript 引擎实现不同 +// Different browsers have different JavaScript engine implementations // V8 (Chrome), SpiderMonkey (Firefox), JavaScriptCore (Safari) ``` -## Java 版本和特性 +## Java Versions and Features ### Java 8 (LTS) -- Lambda 表达式 +- Lambda expressions - Stream API -- Optional 类 -- 新的日期时间 API +- Optional class +- New date/time API ### Java 11 (LTS) - HTTP Client -- 局部变量类型推断 -- 字符串方法增强 +- Local variable type inference +- Enhanced string methods ### Java 17 (LTS) -- 密封类 -- 模式匹配 -- 文本块 -- 记录类 +- Sealed classes +- Pattern matching +- Text blocks +- Records - + ```java !! java -// Java 8+ Lambda 表达式 +// Java 8+ Lambda expressions List names = Arrays.asList("Alice", "Bob", "Charlie"); names.forEach(name -> System.out.println("Hello, " + name)); -// Java 10+ 局部变量类型推断 +// Java 10+ Local variable type inference var message = "Hello, World!"; var numbers = List.of(1, 2, 3, 4, 5); -// Java 14+ 记录类 +// Java 14+ Records record Person(String name, int age) {} -// Java 15+ 文本块 +// Java 15+ Text blocks String sql = """ - SELECT id, name, email - FROM users + SELECT id, name, email + FROM users WHERE active = true """; ``` ```javascript !! js -// JavaScript 对应特性 +// JavaScript equivalent features const names = ["Alice", "Bob", "Charlie"]; names.forEach(name => console.log(`Hello, ${name}`)); -// 动态类型,无需声明 +// Dynamic typing, no declaration needed const message = "Hello, World!"; const numbers = [1, 2, 3, 4, 5]; -// 对象字面量 +// Object literal const person = { name: "John", age: 30 }; -// 模板字符串 +// Template strings const sql = ` - SELECT id, name, email - FROM users + SELECT id, name, email + FROM users WHERE active = true `; ``` -## 学习路径建议 - -### 第一阶段:基础语法 (模块 1-5) -- 语法对比和基本概念 -- 类型系统和变量 -- 控制流和循环 -- 数组和集合基础 -- 函数和方法 - -### 第二阶段:面向对象 (模块 6-8) -- 类和对象 -- 继承和多态 -- 接口和抽象类 - -### 第三阶段:高级特性 (模块 9-12) -- 异常处理 -- 泛型 -- 集合框架 -- 并发编程 - -### 第四阶段:企业级开发 (模块 13-15) -- Spring 框架 -- Web 开发 -- 实战项目 - -### 第五阶段:最佳实践 (模块 16-19) -- 常见陷阱 -- Java 惯用法 -- 高级主题 -- 性能优化 - -## 练习题 - -### 练习 1: 环境搭建 -1. 安装 JDK 17 -2. 配置环境变量 -3. 创建第一个 Java 程序 -4. 使用 IDE 创建项目 - -### 练习 2: 理解 JVM -1. 编写简单的 Java 程序 -2. 编译生成字节码 -3. 在不同平台上运行 -4. 观察 JVM 的工作过程 - -### 练习 3: 对比分析 -1. 对比 JavaScript 和 Java 的类型系统 -2. 分析两种语言的设计哲学差异 -3. 理解编译执行 vs 解释执行的区别 - -## 总结 - -Java 为 JavaScript 开发者提供了一个全新的编程范式: - -- **静态类型系统**: 提供编译时类型安全 -- **面向对象编程**: 完整的 OOP 支持 -- **企业级开发**: 丰富的框架和工具 -- **性能优化**: JVM 的优化能力 -- **跨平台**: Write Once, Run Anywhere - -在接下来的模块中,我们将深入学习 Java 的各个方面,从基础语法到企业级开发,帮助你成为 Java 开发者。 - -准备好开始你的 Java 之旅了吗?让我们从基础语法对比开始! \ No newline at end of file +## Learning Path Recommendations + +### Phase 1: Basic Syntax (Modules 1-5) +- Syntax comparison and basic concepts +- Type system and variables +- Control flow and loops +- Arrays and collections basics +- Functions and methods + +### Phase 2: Object-Oriented (Modules 6-8) +- Classes and objects +- Inheritance and polymorphism +- Interfaces and abstract classes + +### Phase 3: Advanced Features (Modules 9-12) +- Exception handling +- Generics +- Collections framework +- Concurrent programming + +### Phase 4: Enterprise Development (Modules 13-15) +- Spring framework +- Web development +- Practical projects + +### Phase 5: Best Practices (Modules 16-19) +- Common pitfalls +- Java idioms +- Advanced topics +- Performance optimization + +## Exercises + +### Exercise 1: Environment Setup +1. Install JDK 17 +2. Configure environment variables +3. Create your first Java program +4. Use IDE to create a project + +### Exercise 2: Understanding JVM +1. Write a simple Java program +2. Compile to generate bytecode +3. Run on different platforms +4. Observe JVM's working process + +### Exercise 3: Comparative Analysis +1. Compare JavaScript and Java type systems +2. Analyze design philosophy differences +3. Understand compiled vs interpreted execution differences + +## Summary + +Java offers JavaScript developers a completely new programming paradigm: + +- **Static Type System**: Provides compile-time type safety +- **Object-Oriented Programming**: Complete OOP support +- **Enterprise Development**: Rich frameworks and tools +- **Performance Optimization**: JVM's optimization capabilities +- **Cross-Platform**: Write Once, Run Anywhere + +In the following modules, we'll dive deep into Java's aspects, from basic syntax to enterprise development, helping you become a Java developer. + +Ready to start your Java journey? Let's begin with basic syntax comparison! From c0ad1be9cdaecd5719b1b440d07d471750cca5c7 Mon Sep 17 00:00:00 2001 From: sunguangbiao <2991552132@qq.com> Date: Sat, 27 Dec 2025 01:37:37 +0800 Subject: [PATCH 2/7] feat: add Python to Rust learning path with 20 comprehensive modules covering syntax, memory management, error handling, and more, including translations in Chinese and Taiwanese --- CLAUDE.md | 2 +- components/header.tsx | 10 +- content/docs/py2rust/index.mdx | 245 ++++ content/docs/py2rust/index.zh-cn.mdx | 244 ++++ content/docs/py2rust/index.zh-tw.mdx | 244 ++++ content/docs/py2rust/meta.json | 4 + .../docs/py2rust/module-00-introduction.mdx | 725 +++++++++++ .../py2rust/module-00-introduction.zh-cn.mdx | 725 +++++++++++ .../py2rust/module-00-introduction.zh-tw.mdx | 725 +++++++++++ .../docs/py2rust/module-01-syntax-basics.mdx | 828 +++++++++++++ .../py2rust/module-01-syntax-basics.zh-cn.mdx | 828 +++++++++++++ content/docs/py2rust/module-03-borrowing.mdx | 1001 ++++++++++++++++ .../py2rust/module-03-borrowing.zh-cn.mdx | 1001 ++++++++++++++++ .../py2rust/module-03-borrowing.zh-tw.mdx | 1001 ++++++++++++++++ .../docs/py2rust/module-04-control-flow.mdx | 1002 ++++++++++++++++ .../py2rust/module-04-control-flow.zh-cn.mdx | 1002 ++++++++++++++++ .../py2rust/module-04-control-flow.zh-tw.mdx | 1002 ++++++++++++++++ content/docs/py2rust/module-05-functions.mdx | 973 +++++++++++++++ .../py2rust/module-05-functions.zh-cn.mdx | 973 +++++++++++++++ .../py2rust/module-05-functions.zh-tw.mdx | 561 +++++++++ .../py2rust/module-06-data-structures.mdx | 999 ++++++++++++++++ .../module-06-data-structures.zh-cn.mdx | 958 +++++++++++++++ .../module-06-data-structures.zh-tw.mdx | 818 +++++++++++++ .../docs/py2rust/module-07-collections.mdx | 726 +++++++++++ .../py2rust/module-07-collections.zh-cn.mdx | 726 +++++++++++ .../py2rust/module-07-collections.zh-tw.mdx | 726 +++++++++++ content/docs/py2rust/module-08-enums.mdx | 915 ++++++++++++++ .../docs/py2rust/module-08-enums.zh-cn.mdx | 915 ++++++++++++++ .../docs/py2rust/module-08-enums.zh-tw.mdx | 455 +++++++ .../docs/py2rust/module-09-error-handling.mdx | 823 +++++++++++++ .../module-09-error-handling.zh-cn.mdx | 353 ++++++ .../module-09-error-handling.zh-tw.mdx | 315 +++++ .../py2rust/module-10-traits-generics.mdx | 781 ++++++++++++ .../module-10-traits-generics.zh-cn.mdx | 572 +++++++++ .../module-10-traits-generics.zh-tw.mdx | 572 +++++++++ content/docs/py2rust/module-11-lifetimes.mdx | 887 ++++++++++++++ .../py2rust/module-11-lifetimes.zh-cn.mdx | 889 ++++++++++++++ .../py2rust/module-11-lifetimes.zh-tw.mdx | 889 ++++++++++++++ .../docs/py2rust/module-12-smart-pointers.mdx | 879 ++++++++++++++ .../module-12-smart-pointers.zh-cn.mdx | 879 ++++++++++++++ .../module-12-smart-pointers.zh-tw.mdx | 320 +++++ .../py2rust/module-13-modules-packages.mdx | 723 +++++++++++ .../module-13-modules-packages.zh-cn.mdx | 194 +++ .../module-13-modules-packages.zh-tw.mdx | 144 +++ content/docs/py2rust/module-14-file-io.mdx | 485 ++++++++ .../docs/py2rust/module-14-file-io.zh-cn.mdx | 187 +++ .../docs/py2rust/module-14-file-io.zh-tw.mdx | 117 ++ content/docs/py2rust/module-15-threads.mdx | 711 +++++++++++ .../docs/py2rust/module-15-threads.zh-cn.mdx | 296 +++++ .../docs/py2rust/module-15-threads.zh-tw.mdx | 189 +++ content/docs/py2rust/module-16-async.mdx | 682 +++++++++++ .../docs/py2rust/module-16-async.zh-cn.mdx | 682 +++++++++++ .../docs/py2rust/module-16-async.zh-tw.mdx | 682 +++++++++++ content/docs/py2rust/module-17-testing.mdx | 920 ++++++++++++++ .../docs/py2rust/module-17-testing.zh-cn.mdx | 200 ++++ .../docs/py2rust/module-17-testing.zh-tw.mdx | 166 +++ content/docs/py2rust/module-18-macros.mdx | 848 +++++++++++++ .../docs/py2rust/module-18-macros.zh-cn.mdx | 205 ++++ .../docs/py2rust/module-18-macros.zh-tw.mdx | 182 +++ .../docs/py2rust/module-19-performance.mdx | 716 +++++++++++ .../py2rust/module-19-performance.zh-cn.mdx | 190 +++ .../py2rust/module-19-performance.zh-tw.mdx | 117 ++ .../py2rust/module-20-web-api-project.mdx | 1058 +++++++++++++++++ .../module-20-web-api-project.zh-cn.mdx | 311 +++++ .../module-20-web-api-project.zh-tw.mdx | 274 +++++ 65 files changed, 38770 insertions(+), 2 deletions(-) create mode 100644 content/docs/py2rust/index.mdx create mode 100644 content/docs/py2rust/index.zh-cn.mdx create mode 100644 content/docs/py2rust/index.zh-tw.mdx create mode 100644 content/docs/py2rust/meta.json create mode 100644 content/docs/py2rust/module-00-introduction.mdx create mode 100644 content/docs/py2rust/module-00-introduction.zh-cn.mdx create mode 100644 content/docs/py2rust/module-00-introduction.zh-tw.mdx create mode 100644 content/docs/py2rust/module-01-syntax-basics.mdx create mode 100644 content/docs/py2rust/module-01-syntax-basics.zh-cn.mdx create mode 100644 content/docs/py2rust/module-03-borrowing.mdx create mode 100644 content/docs/py2rust/module-03-borrowing.zh-cn.mdx create mode 100644 content/docs/py2rust/module-03-borrowing.zh-tw.mdx create mode 100644 content/docs/py2rust/module-04-control-flow.mdx create mode 100644 content/docs/py2rust/module-04-control-flow.zh-cn.mdx create mode 100644 content/docs/py2rust/module-04-control-flow.zh-tw.mdx create mode 100644 content/docs/py2rust/module-05-functions.mdx create mode 100644 content/docs/py2rust/module-05-functions.zh-cn.mdx create mode 100644 content/docs/py2rust/module-05-functions.zh-tw.mdx create mode 100644 content/docs/py2rust/module-06-data-structures.mdx create mode 100644 content/docs/py2rust/module-06-data-structures.zh-cn.mdx create mode 100644 content/docs/py2rust/module-06-data-structures.zh-tw.mdx create mode 100644 content/docs/py2rust/module-07-collections.mdx create mode 100644 content/docs/py2rust/module-07-collections.zh-cn.mdx create mode 100644 content/docs/py2rust/module-07-collections.zh-tw.mdx create mode 100644 content/docs/py2rust/module-08-enums.mdx create mode 100644 content/docs/py2rust/module-08-enums.zh-cn.mdx create mode 100644 content/docs/py2rust/module-08-enums.zh-tw.mdx create mode 100644 content/docs/py2rust/module-09-error-handling.mdx create mode 100644 content/docs/py2rust/module-09-error-handling.zh-cn.mdx create mode 100644 content/docs/py2rust/module-09-error-handling.zh-tw.mdx create mode 100644 content/docs/py2rust/module-10-traits-generics.mdx create mode 100644 content/docs/py2rust/module-10-traits-generics.zh-cn.mdx create mode 100644 content/docs/py2rust/module-10-traits-generics.zh-tw.mdx create mode 100644 content/docs/py2rust/module-11-lifetimes.mdx create mode 100644 content/docs/py2rust/module-11-lifetimes.zh-cn.mdx create mode 100644 content/docs/py2rust/module-11-lifetimes.zh-tw.mdx create mode 100644 content/docs/py2rust/module-12-smart-pointers.mdx create mode 100644 content/docs/py2rust/module-12-smart-pointers.zh-cn.mdx create mode 100644 content/docs/py2rust/module-12-smart-pointers.zh-tw.mdx create mode 100644 content/docs/py2rust/module-13-modules-packages.mdx create mode 100644 content/docs/py2rust/module-13-modules-packages.zh-cn.mdx create mode 100644 content/docs/py2rust/module-13-modules-packages.zh-tw.mdx create mode 100644 content/docs/py2rust/module-14-file-io.mdx create mode 100644 content/docs/py2rust/module-14-file-io.zh-cn.mdx create mode 100644 content/docs/py2rust/module-14-file-io.zh-tw.mdx create mode 100644 content/docs/py2rust/module-15-threads.mdx create mode 100644 content/docs/py2rust/module-15-threads.zh-cn.mdx create mode 100644 content/docs/py2rust/module-15-threads.zh-tw.mdx create mode 100644 content/docs/py2rust/module-16-async.mdx create mode 100644 content/docs/py2rust/module-16-async.zh-cn.mdx create mode 100644 content/docs/py2rust/module-16-async.zh-tw.mdx create mode 100644 content/docs/py2rust/module-17-testing.mdx create mode 100644 content/docs/py2rust/module-17-testing.zh-cn.mdx create mode 100644 content/docs/py2rust/module-17-testing.zh-tw.mdx create mode 100644 content/docs/py2rust/module-18-macros.mdx create mode 100644 content/docs/py2rust/module-18-macros.zh-cn.mdx create mode 100644 content/docs/py2rust/module-18-macros.zh-tw.mdx create mode 100644 content/docs/py2rust/module-19-performance.mdx create mode 100644 content/docs/py2rust/module-19-performance.zh-cn.mdx create mode 100644 content/docs/py2rust/module-19-performance.zh-tw.mdx create mode 100644 content/docs/py2rust/module-20-web-api-project.mdx create mode 100644 content/docs/py2rust/module-20-web-api-project.zh-cn.mdx create mode 100644 content/docs/py2rust/module-20-web-api-project.zh-tw.mdx diff --git a/CLAUDE.md b/CLAUDE.md index acd80bd..22cb343 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -49,7 +49,7 @@ name = "Alice" age = 25 ``` -```rust !! rust +```rust !! rs // Rust - Static typing let name: &str = "Alice"; let age: i32 = 25; diff --git a/components/header.tsx b/components/header.tsx index 43d651c..192fc5e 100644 --- a/components/header.tsx +++ b/components/header.tsx @@ -98,7 +98,15 @@ const SOURCE_LANGUAGES = [ gradient: 'from-yellow-500 to-orange-500', path: 'py2js', status: 'completed' as const, - } + }, + { + id: 'rust', + name: 'Rust', + icon: '🦀', + gradient: 'from-orange-500 to-red-500', + path: 'py2rust', + status: 'completed' as const, + }, ] } // 未来可以添加其他源语言 diff --git a/content/docs/py2rust/index.mdx b/content/docs/py2rust/index.mdx new file mode 100644 index 0000000..739734a --- /dev/null +++ b/content/docs/py2rust/index.mdx @@ -0,0 +1,245 @@ +--- +title: "Python → Rust: Complete Learning Path" +description: "Master Rust programming starting from your Python knowledge. Learn memory safety, concurrency, and performance through comparative examples." +--- + +# Python → Rust: Complete Learning Path + +Welcome to the **Python → Rust** learning path! This comprehensive curriculum is designed specifically for Python developers who want to master Rust programming. + +## Why Learn Rust as a Python Developer? + +As a Python developer, you already understand programming fundamentals, algorithms, and software design principles. Rust builds on this knowledge while introducing powerful concepts that will make you a better programmer overall. + +### Key Advantages of Rust + + +```python !! py +# Python: Interpreted, dynamically typed +def fibonacci(n): + if n <= 1: + return n + return fibonacci(n - 1) + fibonacci(n - 2) + +# Slow for large n due to interpretation overhead +``` + +```rust !! rs +// Rust: Compiled, statically typed, optimized +fn fibonacci(n: u64) -> u64 { + match n { + 0 => 0, + 1 => 1, + _ => fibonacci(n - 1) + fibonacci(n - 2), + } +} + +// Compiles to highly optimized machine code +``` + + +### What Makes Rust Different? + +1. **Memory Safety without Garbage Collection**: Rust's ownership system ensures memory safety at compile time, eliminating entire classes of bugs while maintaining performance. + +2. **Zero-Cost Abstractions**: High-level features like iterators and pattern matching compile down to efficient machine code. + +3. **Fearless Concurrency**: Rust's type system prevents data races, making concurrent programming safe and approachable. + +4. **Modern Tooling**: Cargo, Rust's package manager, provides best-in-class dependency management, building, testing, and documentation. + +## What You'll Learn + +This learning path consists of **20 comprehensive modules** that take you from Rust beginner to confident practitioner. + +### Module Structure + +1. **Modules 0-1**: Getting Started + - Introduction to Rust and environment setup + - Syntax basics and key differences from Python + +2. **Modules 2-5**: Core Concepts + - **Memory Safety**: Ownership, borrowing, and lifetimes (Rust's unique approach) + - Control flow and functions + - Understanding Rust's memory model + +3. **Modules 6-10**: Type System + - Data structures (structs, enums) + - Collections (Vec, HashMap, etc.) + - Pattern matching + - Error handling with Result and Option + - Traits and generics + +4. **Modules 11-14**: Advanced Features + - Deep dive into lifetimes + - Smart pointers (Box, Rc, Arc, etc.) + - Module system and package management + - File I/O and standard library + +5. **Modules 15-16**: Concurrency + - Multi-threaded programming + - Async/await and asynchronous runtimes + - Message passing and shared state + +6. **Modules 17-19**: Production Readiness + - Testing strategies + - Macros and metaprogramming + - Performance optimization + - Benchmarking and profiling + +7. **Module 20**: Final Project + - Build a complete RESTful API service + - Apply all concepts learned + - Real-world patterns and best practices + +## Learning Approach + +### Python-First Teaching + +Every concept in this curriculum is introduced by: +1. **Starting with familiar Python code** - See how you'd solve it in Python +2. **Introducing the Rust equivalent** - Understand the mapping between languages +3. **Explaining key differences** - Learn why Rust approaches things differently +4. **Practical examples** - Work through real-world scenarios + + +```python !! py +# Python: Exceptions are the default +def divide(a, b): + if b == 0: + raise ValueError("Cannot divide by zero") + return a / b + +# Callers must remember to handle exceptions +try: + result = divide(10, 0) +except ValueError as e: + print(f"Error: {e}") +``` + +```rust !! rs +// Rust: Result types make errors explicit +fn divide(a: f64, b: f64) -> Result { + if b == 0.0 { + Err(String::from("Cannot divide by zero")) + } else { + Ok(a / b) + } +} + +// Compiler forces you to handle errors +match divide(10.0, 0.0) { + Ok(result) => println!("Result: {}", result), + Err(e) => println!("Error: {}", e), +} +``` + + +### Hands-On Practice + +Each module includes: +- **Multiple code comparisons** using our interactive UniversalEditor +- **Practical exercises** to reinforce learning +- **Common pitfalls** and how to avoid them +- **Real-world examples** from production Rust code + +## Prerequisites + +Before starting this learning path, you should have: + +- ✅ **Intermediate Python knowledge**: Comfortable with functions, classes, and basic data structures +- ✅ **Basic programming concepts**: Understanding of variables, loops, and control flow +- ✅ **Terminal familiarity**: Comfortable running commands in a shell +- ✅ **Willingness to learn**: Rust has a steep initial learning curve, but it's worth it! + +No prior systems programming experience required - we'll cover everything you need to know. + +## What Makes This Different from Other Rust Tutorials? + +### 1. Comparative Learning Approach +Most Rust tutorials assume you're coming from C++ or systems programming. This path is specifically designed for **Python developers**, leveraging your existing knowledge while explaining Rust's unique features. + +### 2. Emphasis on Memory Safety +We dedicate significant time to understanding **ownership, borrowing, and lifetimes** - Rust's most distinctive features. These concepts are challenging but crucial for writing effective Rust code. + +### 3. Concurrency & Performance +Special focus on **async programming and performance optimization**, areas where Rust shines compared to Python. + +### 4. Production-Ready Patterns +Learn not just syntax, but **real-world patterns** and best practices used in production Rust applications. + +## Time Commitment + +Each module is designed to take approximately **2-4 hours** to complete thoroughly, including: +- Reading and understanding concepts +- Working through code examples +- Completing exercises +- Experimenting with the code + +Total time: **40-80 hours** for the complete learning path. + +## Getting the Most Out of This Course + +### Active Learning Strategy + +1. **Don't just read - code along**: Type out the examples yourself +2. **Experiment**: Modify the code, see what breaks, understand why +3. **Build things**: Apply concepts to small projects as you learn +4. **Join the community**: Rust has an incredibly welcoming community + +### Recommended Tools + +- **Rust Installation**: We'll cover this in Module 0 +- **IDE**: VS Code with rust-analyzer (recommended) +- **Playground**: [Rust Playground](https://play.rust-lang.org/) for quick experiments + +## Community & Resources + +Rust has one of the most welcoming programming communities. Don't hesitate to ask questions! + +- **Official Rust Book**: [doc.rust-lang.org/book](https://doc.rust-lang.org/book/) +- **Rust by Example**: [doc.rust-lang.org/rust-by-example](https://doc.rust-lang.org/rust-by-example/) +- **Rust Users Forum**: [users.rust-lang.org](https://users.rust-lang.org/) +- **Rust Discord**: Join the conversation at [discord.gg/rust-lang](https://discord.gg/rust-lang) + +## What You'll Be Able to Build + +By the end of this learning path, you'll be able to: + +- ✅ Write **safe, concurrent Rust code** with confidence +- ✅ Build **high-performance applications** that rival C/C++ in speed +- ✅ Create **web services** using frameworks like Actix-web or Axum +- ✅ Develop **CLI tools** with great user experiences +- ✅ Contribute to **Rust open-source projects** +- ✅ Apply Rust concepts to **become a better programmer** in any language + +## Common Concerns + +### "Isn't Rust too difficult?" + +Rust has a reputation for being difficult, primarily due to its ownership system. However: +- **The learning curve is front-loaded**: Once you understand ownership, everything else becomes easier +- **The compiler is your teacher**: Rust's error messages are incredibly helpful +- **The payoff is huge**: Memory safety without garbage collection is worth the effort + +### "Will I still use Python after learning Rust?" + +Absolutely! Rust and Python serve different purposes: +- **Python**: Rapid development, scripting, data science, prototyping +- **Rust**: Performance-critical code, systems programming, WebAssembly + +Many companies use **both** together - Python for the application layer, Rust for performance-critical modules. + +### "Do I need systems programming experience?" + +No! This course assumes only Python programming experience. We'll teach you everything you need to know about memory management, pointers, and low-level concepts. + +## Let's Get Started! + +Ready to begin your Rust journey? Head over to **[Module 0: Introduction & Environment Setup](./module-00-introduction)** to install Rust and write your first Rust program. + +Remember: **Every Rust expert was once a beginner who didn't give up.** The learning curve is steep, but the view from the top is spectacular. + +--- + +**Next: [Module 0 - Introduction & Environment Setup](./module-00-introduction)** → diff --git a/content/docs/py2rust/index.zh-cn.mdx b/content/docs/py2rust/index.zh-cn.mdx new file mode 100644 index 0000000..7a9d4de --- /dev/null +++ b/content/docs/py2rust/index.zh-cn.mdx @@ -0,0 +1,244 @@ +--- +title: "Python → Rust: 完整学习路径" +description: "基于你的 Python 知识掌握 Rust 编程。通过对比示例学习内存安全、并发和性能优化。" +--- + +# Python → Rust: 完整学习路径 + +欢迎来到 **Python → Rust** 学习路径!这套全面课程专为希望掌握 Rust 编程的 Python 开发者设计。 + +## 为什么 Python 开发者应该学习 Rust? + +作为一名 Python 开发者,你已经理解编程基础、算法和软件设计原则。Rust 在此基础上为你引入强大的概念,让你整体上成为更优秀的程序员。 + +### Rust 的核心优势 + + +```python !! py +# Python: 解释型,动态类型 +def fibonacci(n): + if n <= 1: + return n + return fibonacci(n - 1) + fibonacci(n - 2) + +# 由于解释开销,大数计算很慢 +``` + +```rust !! rs +// Rust: 编译型,静态类型,优化后 +fn fibonacci(n: u64) -> u64 { + match n { + 0 => 0, + 1 => 1, + _ => fibonacci(n - 1) + fibonacci(n - 2), + } +} + +// 编译为高度优化的机器码 +``` + + +### Rust 的独特之处 + +1. **无垃圾回收的内存安全**:Rust 的所有权系统在编译时确保内存安全,消除了整类错误,同时保持高性能。 + +2. **零成本抽象**:迭代器和模式匹配等高级特性编译为高效的机器码。 + +3. **无畏并发**:Rust 的类型系统防止数据竞争,使并发编程既安全又易于实现。 + +4. **现代工具链**:Cargo 包管理器提供一流的依赖管理、构建、测试和文档生成。 + +## 你将学到什么 + +本学习路径包含 **20 个综合模块**,带你从 Rust 初学者成长为自信的实践者。 + +### 模块结构 + +1. **模块 0-1**:入门 + - Rust 介绍与环境搭建 + - 语法基础和与 Python 的主要差异 + +2. **模块 2-5**:核心概念 + - **内存安全**:所有权、借用和生命周期(Rust 独特的方法) + - 控制流和函数 + - 理解 Rust 的内存模型 + +3. **模块 6-10**:类型系统 + - 数据结构(结构体、枚举) + - 集合类型(Vec、HashMap 等) + - 模式匹配 + - Result 和 Option 错误处理 + - Trait 和泛型 + +4. **模块 11-14**:高级特性 + - 深入生命周期 + - 智能指针(Box、Rc、Arc 等) + - 模块系统和包管理 + - 文件 I/O 和标准库 + +5. **模块 15-16**:并发 + - 多线程编程 + - 异步/等待和异步运行时 + - 消息传递和共享状态 + +6. **模块 17-19**:生产就绪 + - 测试策略 + - 宏和元编程 + - 性能优化 + - 基准测试和性能分析 + +7. **模块 20**:综合项目 + - 构建完整的 RESTful API 服务 + - 应用所学所有概念 + - 真实世界的模式和最佳实践 + +## 学习方法 + +### Python 优先教学 + +本课程中的每个概念都通过以下方式引入: +1. **从熟悉的 Python 代码开始** - 了解你在 Python 中如何解决 +2. **引入 Rust 等价代码** - 理解语言之间的映射关系 +3. **解释关键差异** - 学习为什么 Rust 采用不同的方法 +4. **实践示例** - 通过真实场景演练 + + +```python !! py +# Python: 异常是默认方式 +def divide(a, b): + if b == 0: + raise ValueError("不能除以零") + return a / b + +# 调用者必须记住处理异常 +try: + result = divide(10, 0) +except ValueError as e: + print(f"错误: {e}") +``` + +```rust !! rs +// Rust: Result 类型使错误显式化 +fn divide(a: f64, b: f64) -> Result { + if b == 0.0 { + Err(String::from("不能除以零")) + } else { + Ok(a / b) + } +} + +// 编译器强制你处理错误 +match divide(10.0, 0.0) { + Ok(result) => println!("结果: {}", result), + Err(e) => println!("错误: {}", e), +} +``` + + +### 动手实践 + +每个模块包括: +- **多个代码对比**使用我们的交互式 UniversalEditor +- **实践练习**以巩固学习 +- **常见陷阱**及如何避免 +- **真实示例**来自生产级 Rust 代码 + +## 前置要求 + +在开始本学习路径之前,你应该具备: + +- ✅ **中级 Python 知识**:熟悉函数、类和基本数据结构 +- ✅ **基本编程概念**:理解变量、循环和控制流 +- ✅ **终端使用经验**:能在 shell 中运行命令 +- ✅ **学习意愿**:Rust 的学习曲线较陡,但绝对值得! + +无需系统编程经验 - 我们将涵盖你需要知道的一切。 + +## 与其他 Rust 教程的区别 + +### 1. 对比学习方法 +大多数 Rust 教程假设你来自 C++ 或有系统编程背景。本路径专为 **Python 开发者**设计,利用你现有的知识,同时解释 Rust 的独特特性。 + +### 2. 强调内存安全 +我们投入大量时间讲解 **所有权、借用和生命周期** - Rust 最显著的特征。这些概念虽然具有挑战性,但对于编写有效的 Rust 代码至关重要。 + +### 3. 并发与性能 +特别关注 **异步编程和性能优化**,这些是 Rust 相比 Python 的优势领域。 + +### 4. 生产就绪模式 +不仅学习语法,还学习生产级 Rust 应用中使用的 **真实世界模式和最佳实践**。 + +## 时间投入 + +每个模块设计为大约 **2-4 小时**彻底完成,包括: +- 阅读和理解概念 +- 学习代码示例 +- 完成练习 +- 实验代码 + +总时间:完整学习路径 **40-80 小时**。 + +## 充分利用本课程 + +### 主动学习策略 + +1. **不只是阅读 - 一起编码**:自己输入示例代码 +2. **实验**:修改代码,看看什么会出错,理解为什么 +3. **构建东西**:边学边将概念应用到小项目中 +4. **加入社区**:Rust 有非常热情友好的社区 + +### 推荐工具 + +- **Rust 安装**:我们将在模块 0 中介绍 +- **IDE**:VS Code + rust-analyzer(推荐) +- **Playground**:[Rust Playground](https://play.rust-lang.org/) 用于快速实验 + +## 社区与资源 + +Rust 拥有最友好的编程社区之一。不要犹豫提问! + +- **Rust 官方教程**:[doc.rust-lang.org/book](https://doc.rust-lang.org/book/) +- **Rust by Example**:[doc.rust-lang.org/rust-by-example](https://doc.rust-lang.org/rust-by-example/) +- **Rust 用户论坛**:[users.rust-lang.org](https://users.rust-lang.org/) +- **Rust Discord**:加入讨论 [discord.gg/rust-lang](https://discord.gg/rust-lang) + +## 学完之后你能做什么 + +完成本学习路径后,你将能够: +- ✅ 自信地编写 **安全的并发 Rust 代码** +- ✅ 构建 **高性能应用程序**,速度媲美 C/C++ +- ✅ 使用 Actix-web 或 Axum 等框架创建 **Web 服务** +- ✅ 开发具有出色用户体验的 **CLI 工具** +- ✅ 为 **Rust 开源项目** 做贡献 +- ✅ 应用 Rust 概念 **成为任何语言的更好的程序员** + +## 常见担忧 + +### "Rust 是否太难?" + +Rust 因其所有权系统而难以学习。但是: +- **学习曲线前置**:一旦理解了所有权,其他一切都变得更容易 +- **编译器是你的老师**:Rust 的错误信息非常有帮助 +- **回报巨大**:无垃圾回收的内存安全绝对值得努力 + +### "学习 Rust 后我还会使用 Python 吗?" + +绝对会!Rust 和 Python 服务于不同目的: +- **Python**:快速开发、脚本、数据科学、原型设计 +- **Rust**:性能关键代码、系统编程、WebAssembly + +许多公司 **同时使用**两者 - Python 用于应用层,Rust 用于性能关键模块。 + +### "我需要系统编程经验吗?" + +不需要!本课程假设你只有 Python 编程经验。我们将教你关于内存管理、指针和底层概念所需的一切。 + +## 开始学习吧! + +准备好开始你的 Rust 之旅了吗?前往 **[模块 0:介绍与环境搭建](./module-00-introduction)** 安装 Rust 并编写你的第一个 Rust 程序。 + +记住:**每个 Rust 专家都曾是一个不放弃的初学者。** 学习曲线很陡,但山顶的风景绝美。 + +--- + +**下一步:[模块 0 - 介绍与环境搭建](./module-00-introduction)** → diff --git a/content/docs/py2rust/index.zh-tw.mdx b/content/docs/py2rust/index.zh-tw.mdx new file mode 100644 index 0000000..52403dc --- /dev/null +++ b/content/docs/py2rust/index.zh-tw.mdx @@ -0,0 +1,244 @@ +--- +title: "Python → Rust: 完整學習路徑" +description: "基於你的 Python 知識掌握 Rust 程式設計。透過對比範例學習記憶體安全、並行和效能優化。" +--- + +# Python → Rust: 完整學習路徑 + +歡迎來到 **Python → Rust** 學習路徑!這套全面課程專為希望掌握 Rust 程式設計的 Python 開發者設計。 + +## 為什麼 Python 開發者應該學習 Rust? + +作為一名 Python 開發者,你已經理解程式設計基礎、演算法和軟體設計原則。Rust 在此基礎上為你引入強大的概念,讓你整體上成為更優秀的程式設計師。 + +### Rust 的核心優勢 + + +```python !! py +# Python: 直譯型,動態類型 +def fibonacci(n): + if n <= 1: + return n + return fibonacci(n - 1) + fibonacci(n - 2) + +# 由於直譯開銷,大數計算很慢 +``` + +```rust !! rs +// Rust: 編譯型,靜態類型,最佳化後 +fn fibonacci(n: u64) -> u64 { + match n { + 0 => 0, + 1 => 1, + _ => fibonacci(n - 1) + fibonacci(n - 2), + } +} + +// 編譯為高度最佳化的機器碼 +``` + + +### Rust 的獨特之處 + +1. **無垃圾回收的記憶體安全**:Rust 的所有權系統在編譯時確保記憶體安全,消除了整類錯誤,同時保持高效能。 + +2. **零成本抽象**:迭代器和模式匹配等高級特性編譯為高效能的機器碼。 + +3. **無畏並行**:Rust 的類型系統防止資料競爭,使並行程式設計既安全又易於實現。 + +4. **現代工具鏈**:Cargo 套件管理器提供一流的依賴管理、建構、測試和文件生成。 + +## 你將學到什麼 + +本學習路徑包含 **20 個綜合模組**,帶你從 Rust 初學者成長為自信的實踐者。 + +### 模組結構 + +1. **模組 0-1**:入門 + - Rust 介紹與環境建置 + - 語法基礎和與 Python 的主要差異 + +2. **模組 2-5**:核心概念 + - **記憶體安全**:所有權、借用和生命週期(Rust 獨特的方法) + - 控制流程和函式 + - 理解 Rust 的記憶體模型 + +3. **模組 6-10**:類型系統 + - 資料結構(結構體、列舉) + - 集合類型(Vec、HashMap 等) + - 模式匹配 + - Result 和 Option 錯誤處理 + - Trait 和泛型 + +4. **模組 11-14**:進階特性 + - 深入生命週期 + - 智慧指標(Box、Rc、Arc 等) + - 模組系統和套件管理 + - 檔案 I/O 和標準函式庫 + +5. **模組 15-16**:並行 + - 多執行緒程式設計 + - 非同步/等待和非同步執行時 + - 訊息傳遞和共享狀態 + +6. **模組 17-19**:生產就緒 + - 測試策略 + - 巨集和元程式設計 + - 效能最佳化 + - 基準測試和效能分析 + +7. **模組 20**:綜合專案 + - 建構完整的 RESTful API 服務 + - 應用所學所有概念 + - 真實世界的模式和最佳實踐 + +## 學習方法 + +### Python 優先教學 + +本課程中的每個概念都透過以下方式引入: +1. **從熟悉的 Python 程式碼開始** - 了解你在 Python 中如何解決 +2. **引入 Rust 等價程式碼** - 理解語言之間的映射關係 +3. **解釋關鍵差異** - 學習為什麼 Rust 採用不同的方法 +4. **實踐範例** - 透過真實場景演練 + + +```python !! py +# Python: 例外是預設方式 +def divide(a, b): + if b == 0: + raise ValueError("不能除以零") + return a / b + +# 呼叫者必須記住處理例外 +try: + result = divide(10, 0) +except ValueError as e: + print(f"錯誤: {e}") +``` + +```rust !! rs +// Rust: Result 類型使錯誤顯式化 +fn divide(a: f64, b: f64) -> Result { + if b == 0.0 { + Err(String::from("不能除以零")) + } else { + Ok(a / b) + } +} + +// 編譯器強制你處理錯誤 +match divide(10.0, 0.0) { + Ok(result) => println!("結果: {}", result), + Err(e) => println!("錯誤: {}", e), +} +``` + + +### 動手實踐 + +每個模組包括: +- **多個程式碼對比**使用我們的互動式 UniversalEditor +- **實踐練習**以鞏固學習 +- **常見陷阱**及如何避免 +- **真實範例**來自生產級 Rust 程式碼 + +## 前置要求 + +在開始本學習路徑之前,你應該具備: + +- ✅ **中級 Python 知識**:熟悉函式、類別和基本資料結構 +- ✅ **基本程式設計概念**:理解變數、迴圈和控制流程 +- ✅ **終端使用經驗**:能在 shell 中執行指令 +- ✅ **學習意願**:Rust 的學習曲線較陡,但絕對值得! + +無需系統程式設計經驗 - 我們將涵蓋你需要知道的一切。 + +## 與其他 Rust 教程的區別 + +### 1. 對比學習方法 +大多數 Rust 教程假設你來自 C++ 或有系統程式設計背景。本路徑專為 **Python 開發者**設計,利用你現有的知識,同時解釋 Rust 的獨特特性。 + +### 2. 強調記憶體安全 +我們投入大量時間講解 **所有權、借用和生命週期** - Rust 最顯著的特徵。這些概念雖然具有挑戰性,但對於編寫有效的 Rust 程式碼至關重要。 + +### 3. 並行與效能 +特別關注 **非同步程式設計和效能最佳化**,這些是 Rust 相比 Python 的優勢領域。 + +### 4. 生產就緒模式 +不只學習語法,還學習生產級 Rust 應用中使用的 **真實世界模式和最佳實踐**。 + +## 時間投入 + +每個模組設計為大約 **2-4 小時**徹底完成,包括: +- 閱讀和理解概念 +- 學習程式碼範例 +- 完成練習 +- 實驗程式碼 + +總時間:完整學習路徑 **40-80 小時**。 + +## 充分利用本課程 + +### 主動學習策略 + +1. **不只是閱讀 - 一起編碼**:自己輸入範例程式碼 +2. **實驗**:修改程式碼,看看什麼會出錯,理解為什麼 +3. **建構東西**:邊學邊將概念應用到小專案中 +4. **加入社群**:Rust 有非常熱情友好的社群 + +### 推薦工具 + +- **Rust 安裝**:我們將在模組 0 中介紹 +- **IDE**:VS Code + rust-analyzer(推薦) +- **Playground**:[Rust Playground](https://play.rust-lang.org/) 用於快速實驗 + +## 社群與資源 + +Rust 擁有最友好的程式設計社群之一。不要猶豫提問! + +- **Rust 官方教程**:[doc.rust-lang.org/book](https://doc.rust-lang.org/book/) +- **Rust by Example**:[doc.rust-lang.org/rust-by-example](https://doc.rust-lang.org/rust-by-example/) +- **Rust 使用者論壇**:[users.rust-lang.org](https://users.rust-lang.org/) +- **Rust Discord**:加入討論 [discord.gg/rust-lang](https://discord.gg/rust-lang) + +## 學完之後你能做什麼 + +完成本學習路徑後,你將能夠: +- ✅ 自信地編寫 **安全的並行 Rust 程式碼** +- ✅ 建構 **高效能應用程式**,速度媲美 C/C++ +- ✅ 使用 Actix-web 或 Axum 等框架建立 **Web 服務** +- ✅ 開發具有出色使用者體驗的 **CLI 工具** +- ✅ 為 **Rust 開源專案** 做貢獻 +- ✅ 應用 Rust 概念 **成為任何語言的更好的程式設計師** + +## 常見擔憂 + +### "Rust 是否太難?" + +Rust 因其所有權系統而難以學習。但是: +- **學習曲線前置**:一旦理解了所有權,其他一切都變得更容易 +- **編譯器是你的老師**:Rust 的錯誤訊息非常有幫助 +- **回報巨大**:無垃圾回收的記憶體安全絕對值得努力 + +### "學習 Rust 後我還會使用 Python 嗎?" + +絕對會!Rust 和 Python 服務於不同目的: +- **Python**:快速開發、腳本、資料科學、原型設計 +- **Rust**:效能關鍵程式碼、系統程式設計、WebAssembly + +許多公司 **同時使用**兩者 - Python 用於應用層,Rust 用於效能關鍵模組。 + +### "我需要系統程式設計經驗嗎?" + +不需要!本課程假設你只有 Python 程式設計經驗。我們將教導你關於記憶體管理、指標和底層概念所需的一切。 + +## 開始學習吧! + +準備好開始你的 Rust 之旅了嗎?前往 **[模組 0:介紹與環境建置](./module-00-introduction)** 安裝 Rust 並編寫你的第一個 Rust 程式。 + +記住:**每個 Rust 專家都曾是一個不放棄的初學者。** 學習曲線很陡,但山頂的風景絕美。 + +--- + +**下一步:[模組 0 - 介紹與環境建置](./module-00-introduction)** → diff --git a/content/docs/py2rust/meta.json b/content/docs/py2rust/meta.json new file mode 100644 index 0000000..f350ec2 --- /dev/null +++ b/content/docs/py2rust/meta.json @@ -0,0 +1,4 @@ +{ + "title": "Python → Rust", + "root": true +} diff --git a/content/docs/py2rust/module-00-introduction.mdx b/content/docs/py2rust/module-00-introduction.mdx new file mode 100644 index 0000000..16e518f --- /dev/null +++ b/content/docs/py2rust/module-00-introduction.mdx @@ -0,0 +1,725 @@ +--- +title: "Module 0: Introduction & Environment Setup" +description: "Get started with Rust by understanding why it matters, installing the toolchain, and writing your first Rust program." +--- + +# Module 0: Introduction & Environment Setup + +Welcome to your Rust journey! In this module, you'll learn why Rust is worth your time, install the Rust toolchain, and write your first Rust program. + +## Learning Objectives + +By the end of this module, you'll be able to: +- ✅ Understand what makes Rust unique and valuable +- ✅ Install Rust and set up your development environment +- ✅ Use Cargo, Rust's package manager and build tool +- ✅ Write and run your first Rust program +- ✅ Navigate the Rust ecosystem and resources + +## Why Rust? A Python Developer's Perspective + +As a Python developer, you might wonder: "Why learn another language?" Let's explore what Rust brings to the table. + + +```python !! py +# Python: Quick to write, slower to run +def process_data(items): + result = [] + for item in items: + result.append(item * 2) + return result + +# Runs ~100x slower than optimized Rust +# But took 5 seconds to write +``` + +```rust !! rs +// Rust: More verbose, blazingly fast +fn process_data(items: &[i32]) -> Vec { + items.iter().map(|x| x * 2).collect() +} + +// Runs at C/C++ speed +// Takes a bit longer to write initially +// But compiler catches bugs before runtime +``` + + +### The Rust Value Proposition + +#### 1. Performance Without Sacrificing Safety + +Python uses a Global Interpreter Lock (GIL) and garbage collection, which limit performance and make true parallelism challenging. Rust gives you: +- **C/C++ performance** without the memory safety risks +- **True parallelism** through fearless concurrency +- **Predictable performance** with no garbage collection pauses + + +```python !! py +# Python: ~2.5 seconds for fib(35) +import time + +def fib(n): + if n <= 1: + return n + return fib(n-1) + fib(n-2) + +start = time.time() +print(fib(35)) +print(f"Time: {time.time() - start:.2f}s") +``` + +```rust !! rs +// Rust: ~0.7 seconds for fib(35) (3.5x faster) +use std::time::Instant; + +fn fib(n: u64) -> u64 { + match n { + 0 => 0, + 1 => 1, + _ => fib(n-1) + fib(n-2), + } +} + +fn main() { + let start = Instant::now(); + println!("{}", fib(35)); + println!("Time: {:?}", start.elapsed()); +} +``` + + +#### 2. Memory Safety at Compile Time + +This is Rust's superpower. The Rust compiler prevents entire classes of bugs: +- **Null pointer dereferences** → Rust has no null +- **Dangling pointers** → Borrow checker prevents invalid references +- **Data races** → Type system prevents concurrent data access issues +- **Buffer overflows** → Bounds checking by default + +In Python, these would be runtime errors. In Rust, they're **compile-time errors** - you find them before your code ever runs. + +#### 3. Modern Tooling + +Cargo is one of the best package managers in existence: + + +```python !! py +# Python: Multiple tools, scattered configuration +# Create project +mkdir myproject +cd myproject +python -m venv venv +source venv/bin/activate + +# Add dependencies (manual editing) +echo "numpy==1.24.0" >> requirements.txt +pip install -r requirements.txt + +# Run tests (needs separate test framework) +pytest + +# Building (for distribution) +python setup.py sdist bdist_wheel +``` + +```rust !! rs +// Rust: Single unified tool +// Create project +cargo new myproject +cd myproject + +// Add dependencies +cargo add serde + +// Run tests (built-in) +cargo test + +// Build optimized binary +cargo build --release +``` + + +#### 4. Growing Ecosystem + +Rust has a rapidly growing ecosystem with excellent crates: +- **Web**: Actix-web, Axum, Rocket +- **Async**: Tokio, async-std +- **Serialization**: Serde (JSON, YAML, etc.) +- **Database**: Diesel, SQLx +- **CLI**: Clap, structopt + +Many Python libraries now have Rust acceleration (e.g., PyO3 for Python bindings). + +## Installing Rust + +### Step 1: Install rustup (Rust Toolchain Installer) + +**macOS / Linux:** +```bash +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +``` + +**Windows:** +Download and run `rustup-init.exe` from [rustup.rs](https://rustup.rs/) + +The installer will: +1. Install Rust compiler (`rustc`) +2. Install Cargo (package manager and build tool) +3. Add Rust to your system PATH +4. Install stable Rust toolchain + +**Verify installation:** +```bash +rustc --version +cargo --version +``` + +You should see something like: +``` +rustc 1.75.0 (82e1608df 2023-12-21) +cargo 1.75.0 (1d8b05cdd 2023-11-20) +``` + +### Step 2: Install a Code Editor + +**Recommended: VS Code + rust-analyzer** + +1. Install [VS Code](https://code.visualstudio.com/) +2. Install the `rust-analyzer` extension (NOT "Rust" - use rust-analyzer) +3. Install `CodeLLDB` for debugging support + +**Alternative editors:** +- IntelliJ IDEA with Rust plugin +- Vim/Neovim with rust-tools.nvim +- Emacs with rust-mode + +rust-analyzer provides: +- ✅ Autocompletion +- ✅ Inline type hints +- ✅ Go to definition +- ✅ Inline error messages +- ✅ Code actions (quick fixes) + +## Your First Rust Program + +Let's create and run your first Rust project! + +### Creating a New Project + +```bash +cargo new hello_rust +cd hello_rust +``` + +Cargo creates: +``` +hello_rust/ +├── Cargo.toml # Project metadata and dependencies +└── src/ + └── main.rs # Your source code +``` + +### Understanding the Project Structure + +**`Cargo.toml`** - Project manifest: +```toml +[package] +name = "hello_rust" +version = "0.1.0" +edition = "2021" + +[dependencies] +# External crates go here +``` + +**`src/main.rs`** - Entry point: +```rust +fn main() { + println!("Hello, world!"); +} +``` + +### Running Your Project + +```bash +cargo run +``` + +You should see: +``` +Compiling hello_rust v0.1.0 +Finished dev profile [unoptimized + debuginfo] target(s) +Running `target/debug/hello_rust` +Hello, world! +``` + +## Understanding the Code + +Let's break down what's happening: + + +```python !! py +# Python: Script-style execution +# No main function required - just execute top to bottom + +def main(): + print("Hello, world!") + +if __name__ == "__main__": + main() + +# Actually, in Python you'd usually just: +print("Hello, world!") +``` + +```rust !! rs +// Rust: Compiled, with explicit entry point +// Every Rust program needs a main() function + +fn main() { // fn declares a function + println!("Hello, world!"); // ! indicates a macro +} + +// Key points: +// - fn: function keyword +// - main(): entry point (like if __name__ == "__main__") +// - {}: function body +// - println!: macro call (note the !) +// - ;: statement terminator +``` + + +### Key Rust Concepts (Preview) + +1. **`fn main()`**: Entry point function +2. **`println!`**: It's a **macro** (note the `!`), not a function +3. **`;`**: Statements must end with semicolons +4. **`{}`**: Blocks for scoping + +## Cargo Commands Deep Dive + +Cargo is your Swiss Army knife for Rust development: + +### Build Commands + +```bash +# Build for development (unoptimized, fast compile) +cargo build + +# Build for release (optimized, slow compile, fast binary) +cargo build --release + +# Run immediately (builds if necessary) +cargo run + +# Run release version +cargo run --release +``` + +### Development Commands + +```bash +# Check code without building (fastest) +cargo check + +# Run tests +cargo test + +# Generate documentation +cargo doc --open + +# Start new project (binary) +cargo new project_name + +# Start new project (library) +cargo new --lib project_name + +# Update dependencies +cargo update +``` + +### Useful Cargo Flags + +```bash +# Show verbose output +cargo build --verbose + +# Compile only specific target +cargo build --bin hello_rust + +# Compile specific example +cargo build --example my_example + +# Run with custom arguments +cargo run -- --some-arg +``` + +## Comparing Python and Rust Workflows + + +```python !! py +# Python project structure +myproject/ +├── src/ +│ └── mymodule.py +├── tests/ +│ └── test_mymodule.py +├── requirements.txt +├── setup.py or pyproject.toml +└── README.md + +# Install dependencies +pip install -r requirements.txt + +# Run module +python -m src.mymodule + +# Run tests +pytest + +# Package +python setup.py sdist +``` + +```rust !! rs +// Rust project structure +myproject/ +├── src/ +│ ├── main.rs # Binary entry point +│ └── lib.rs # Library entry point (if --lib) +├── tests/ +│ └── integration_test.rs +├── examples/ +│ └── example.rs +├── Cargo.toml # All dependencies here +└── README.md + +// Build and run +cargo build +cargo run +cargo test + +// Everything managed by Cargo +``` + + +## Setting Up Development Environment + +### Recommended VS Code Extensions + +1. **rust-analyzer** (Required) + - Official language server + - Provides IDE features + +2. **CodeLLDB** + - Debugging support + +3. **Even Better TOML** + - Syntax highlighting for Cargo.toml + +4. **Error Lens** + - Inline error display + +### VS Code Settings + +Create `.vscode/settings.json`: +```json +{ + "rust-analyzer.checkOnSave.command": "clippy", + "rust-analyzer.cargo.features": "all", + "editor.formatOnSave": true, + "rust-analyzer.rustfmt.extraArgs": ["+nightly"] +} +``` + +## The Rust Compilation Model + +Understanding how Rust compiles helps you work with it effectively. + + +```python !! py +# Python: Interpreted +# Source → Bytecode (.pyc) → Python VM → Execute + +def add(a, b): + return a + b + +# Executes line by line +# Type errors happen at runtime +# No compilation step (technically bytecode compilation) +``` + +```rust !! rs +// Rust: Compiled ahead-of-time +// Source → LLVM IR → Machine Code → Execute + +fn add(a: i32, b: i32) -> i32 { + a + b +} + +// Fully compiled before execution +// Type errors caught at compile time +// Produces native binary executable +``` + + +### The Compilation Process + +1. **Parsing**: Rust source → AST (Abstract Syntax Tree) +2. **HIR lowering**: AST → HIR (High-level IR) +3. **MIR lowering**: HIR → MIR (Mid-level IR) +4. **LLVM**: MIR → LLVM IR → Machine code +5. **Linking**: Machine code + libraries → Executable + +This is why `cargo build` takes longer than `python script.py`, but the resulting binary runs much faster. + +## Basic Rust Syntax (Quick Preview) + +Let's look at some basic Rust syntax compared to Python: + +### Variables + + +```python !! py +# Python: Dynamic typing, mutable by default +name = "Alice" # string +age = 25 # int +height = 5.6 # float + +# Can reassign with different type +name = 42 # This works! + +# Everything is mutable +age = 26 +``` + +```rust !! rs +// Rust: Static typing, immutable by default +let name = "Alice"; // &str +let age: i32 = 25; // i32 (explicit type) +let height = 5.6; // f64 (inferred) + +// Cannot reassign with different type +// name = 42; // ERROR: type mismatch + +// Variables are immutable by default +// age = 26; // ERROR: cannot assign twice + +// Use mut for mutable variables +let mut age = 25; +age = 26; // OK +``` + + +### Functions + + +```python !! py +# Python: Dynamic typing, optional return +def greet(name, age): + message = f"Hello, {name}!" + print(message) + return age * 2 + +# Call +result = greet("Alice", 25) +``` + +```rust !! rs +// Rust: Static typing, explicit return type +fn greet(name: &str, age: i32) -> i32 { + let message = format!("Hello, {}!", name); + println!("{}", message); + age * 2 // No semicolon = return value +} + +// Call +let result = greet("Alice", 25); +``` + + +## Rust Editions + +Rust has "editions" that allow incremental improvements: + +- **Rust 2015**: Original edition +- **Rust 2018**: Major improvements (modules, async prep) +- **Rust 2021**: Current edition (default) + +New projects should use Rust 2021. All editions work together - you can use Rust 2015 crates in a Rust 2021 project. + +## The Rust Community + +Rust has one of the friendliest programming communities: + +### Official Resources +- **The Rust Programming Language**: [doc.rust-lang.org/book](https://doc.rust-lang.org/book/) (The "Book") +- **Rust by Example**: [doc.rust-lang.org/rust-by-example](https://doc.rust-lang.org/rust-by-example/) +- **Rust Standard Library**: [doc.rust-lang.org/std](https://doc.rust-lang.org/std/) + +### Community Resources +- **Rust Users Forum**: [users.rust-lang.org](https://users.rust-lang.org/) +- **Rust Discord**: [discord.gg/rust-lang](https://discord.gg/rust-lang) +- **Reddit**: r/rust +- **Crates.io**: [crates.io](https://crates.io/) (Package registry) + +## Common First-Time Challenges + +### 1. The Borrow Checker + +You'll encounter this error often: +```rust +let s1 = String::from("hello"); +let s2 = s1; +// println!("{}", s1); // ERROR: value borrowed after move +``` + +This is Rust's ownership system preventing use-after-move. We'll cover this in detail in Module 2. + +### 2. Verbosity + +Rust is more verbose than Python: +```rust +// Rust: Explicit types everywhere +fn process(items: Vec) -> Vec { + items.into_iter().map(|x| x * 2).collect() +} +``` + +This verbosity buys you safety and performance. You'll learn to appreciate it. + +### 3. Compilation Time + +Rust compiles slower than Python runs. Tips: +- Use `cargo check` for quick validation +- Use `cargo build` only when needed +- The compiler is catching bugs you'd find at runtime in Python + +## Your Second Rust Program + +Let's write something more interesting: + + +```python !! py +def fibonacci(n): + """Calculate the nth Fibonacci number.""" + if n <= 1: + return n + a, b = 0, 1 + for _ in range(2, n + 1): + a, b = b, a + b + return b + +if __name__ == "__main__": + n = 10 + result = fibonacci(n) + print(f"Fibonacci({n}) = {result}") +``` + +```rust !! rs +fn fibonacci(n: u64) -> u64 { + /// Calculate the nth Fibonacci number + if n <= 1 { + return n; + } + let (mut a, mut b) = (0, 1); + for _ in 2..=n { + let temp = a + b; + a = b; + b = temp; + } + b +} + +fn main() { + let n = 10; + let result = fibonacci(n); + println!("Fibonacci({}) = {}", n, result); +} +``` + + +Create this in `src/main.rs` and run with `cargo run`. + +## Testing Your Installation + +Verify everything works: + +```bash +# Check Rust version +rustc --version + +# Check Cargo +cargo --version + +# Create test project +cargo new test_project && cd test_project + +# Build it +cargo build + +# Run it +cargo run + +# Run tests +cargo test +``` + +If all commands succeed, you're ready to learn Rust! + +## Summary + +In this module, you learned: +- ✅ Why Rust is valuable for Python developers +- ✅ How to install Rust and Cargo +- ✅ How to create and run a Rust project +- ✅ Basic Cargo commands +- ✅ The difference between Python's interpretation and Rust's compilation +- ✅ How to set up your development environment + +## Next Steps + +Now that your environment is set up: +1. **[Module 1: Syntax Basics](./module-01-syntax-basics)** - Learn Rust syntax by comparing with Python +2. Practice with small programs +3. Explore the [Rust Playground](https://play.rust-lang.org/) for quick experiments + +## Exercises + +### Exercise 1: Create a Project + +Create a new Cargo project called `greeting` that: +- Asks for the user's name +- Prints a personalized greeting +- Uses variables and functions + +### Exercise 2: Temperature Converter + +Write a program that converts Fahrenheit to Celsius: +- Formula: `C = (F - 32) * 5/9` +- Create a function for the conversion +- Test with a few values + +
+Solution + +```rust +fn fahrenheit_to_celsius(f: f64) -> f64 { + (f - 32.0) * 5.0 / 9.0 +} + +fn main() { + let fahrenheit = 98.6; + let celsius = fahrenheit_to_celsius(fahrenheit); + println!("{}°F = {}°C", fahrenheit, celsius); +} +``` + +
+ +--- + +**Remember**: Rust has a learning curve, but the compiler is your friend. Read error messages carefully - they're incredibly helpful! + +**Next:** [Module 1 - Syntax Basics](./module-01-syntax-basics) → diff --git a/content/docs/py2rust/module-00-introduction.zh-cn.mdx b/content/docs/py2rust/module-00-introduction.zh-cn.mdx new file mode 100644 index 0000000..bb48326 --- /dev/null +++ b/content/docs/py2rust/module-00-introduction.zh-cn.mdx @@ -0,0 +1,725 @@ +--- +title: "模块 0:介绍与环境搭建" +description: "通过了解 Rust 的重要性、安装工具链和编写第一个 Rust 程序来开始你的 Rust 之旅。" +--- + +# 模块 0:介绍与环境搭建 + +欢迎来到你的 Rust 之旅!在本模块中,你将学习为什么 Rust 值得你投入时间,安装 Rust 工具链,并编写你的第一个 Rust 程序。 + +## 学习目标 + +完成本模块后,你将能够: +- ✅ 理解 Rust 的独特和价值所在 +- ✅ 安装 Rust 并设置开发环境 +- ✅ 使用 Cargo(Rust 的包管理器和构建工具) +- ✅ 编写并运行第一个 Rust 程序 +- ✅ 导航 Rust 生态系统和资源 + +## 为什么选择 Rust?Python 开发者的视角 + +作为 Python 开发者,你可能会想:"为什么要学习另一种语言?" 让我们探索 Rust 带来的价值。 + + +```python !! py +# Python: 编写快,运行慢 +def process_data(items): + result = [] + for item in items: + result.append(item * 2) + return result + +# 运行速度比优化后的 Rust 慢约 100 倍 +# 但只需 5 秒钟编写 +``` + +```rust !! rs +// Rust: 更冗长,但速度极快 +fn process_data(items: &[i32]) -> Vec { + items.iter().map(|x| x * 2).collect() +} + +// 运行速度达到 C/C++ 级别 +// 初始编写时间稍长 +// 但编译器在运行前就能捕获错误 +``` + + +### Rust 的价值主张 + +#### 1. 不牺牲安全性的性能 + +Python 使用全局解释器锁(GIL)和垃圾回收,这限制了性能并使真正的并行性变得具有挑战性。Rust 为你提供: +- **C/C++ 性能** 而没有内存安全风险 +- **真正的并行性** 通过无畏并发 +- **可预测的性能** 没有垃圾回收暂停 + + +```python !! py +# Python: fib(35) 约 2.5 秒 +import time + +def fib(n): + if n <= 1: + return n + return fib(n-1) + fib(n-2) + +start = time.time() +print(fib(35)) +print(f"耗时: {time.time() - start:.2f}秒") +``` + +```rust !! rs +// Rust: fib(35) 约 0.7 秒(快 3.5 倍) +use std::time::Instant; + +fn fib(n: u64) -> u64 { + match n { + 0 => 0, + 1 => 1, + _ => fib(n-1) + fib(n-2), + } +} + +fn main() { + let start = Instant::now(); + println!("{}", fib(35)); + println!("耗时: {:?}", start.elapsed()); +} +``` + + +#### 2. 编译时的内存安全 + +这是 Rust 的超能力。Rust 编译器可以防止整类错误: +- **空指针解引用** → Rust 没有null +- **悬垂指针** → 借用检查器防止无效引用 +- **数据竞争** → 类型系统防止并发数据访问问题 +- **缓冲区溢出** → 默认边界检查 + +在 Python 中,这些将是运行时错误。在 Rust 中,它们是 **编译时错误** - 你在代码运行之前就能发现它们。 + +#### 3. 现代工具链 + +Cargo 是现有的最好的包管理器之一: + + +```python !! py +# Python: 多个工具,配置分散 +# 创建项目 +mkdir myproject +cd myproject +python -m venv venv +source venv/bin/activate + +# 添加依赖(手动编辑) +echo "numpy==1.24.0" >> requirements.txt +pip install -r requirements.txt + +# 运行测试(需要单独的测试框架) +pytest + +# 构建(用于分发) +python setup.py sdist bdist_wheel +``` + +```rust !! rs +// Rust: 统一的工具 +// 创建项目 +cargo new myproject +cd myproject + +// 添加依赖 +cargo add serde + +// 运行测试(内置) +cargo test + +// 构建优化后的二进制文件 +cargo build --release +``` + + +#### 4. 成长的生态系统 + +Rust 拥有快速增长的生态系统,提供出色的 crate: +- **Web**: Actix-web, Axum, Rocket +- **异步**: Tokio, async-std +- **序列化**: Serde (JSON, YAML 等) +- **数据库**: Diesel, SQLx +- **CLI**: Clap, structopt + +许多 Python 库现在都有 Rust 加速(例如,用于 Python 绑定的 PyO3)。 + +## 安装 Rust + +### 步骤 1:安装 rustup(Rust 工具链安装程序) + +**macOS / Linux:** +```bash +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +``` + +**Windows:** +从 [rustup.rs](https://rustup.rs/) 下载并运行 `rustup-init.exe` + +安装程序将: +1. 安装 Rust 编译器(`rustc`) +2. 安装 Cargo(包管理器和构建工具) +3. 将 Rust 添加到系统 PATH +4. 安装稳定版 Rust 工具链 + +**验证安装:** +```bash +rustc --version +cargo --version +``` + +你应该看到类似这样的输出: +``` +rustc 1.75.0 (82e1608df 2023-12-21) +cargo 1.75.0 (1d8b05cdd 2023-11-20) +``` + +### 步骤 2:安装代码编辑器 + +**推荐:VS Code + rust-analyzer** + +1. 安装 [VS Code](https://code.visualstudio.com/) +2. 安装 `rust-analyzer` 扩展(注意不是 "Rust" - 要用 rust-analyzer) +3. 安装 `CodeLLDB` 用于调试支持 + +**其他编辑器:** +- IntelliJ IDEA 配合 Rust 插件 +- Vim/Neovim 配合 rust-tools.nvim +- Emacs 配合 rust-mode + +rust-analyzer 提供: +- ✅ 自动补全 +- ✅ 内联类型提示 +- ✅ 转到定义 +- ✅ 内联错误消息 +- ✅ 代码操作(快速修复) + +## 你的第一个 Rust 程序 + +让我们创建并运行你的第一个 Rust 项目! + +### 创建新项目 + +```bash +cargo new hello_rust +cd hello_rust +``` + +Cargo 创建: +``` +hello_rust/ +├── Cargo.toml # 项目元数据和依赖 +└── src/ + └── main.rs # 你的源代码 +``` + +### 理解项目结构 + +**`Cargo.toml`** - 项目清单: +```toml +[package] +name = "hello_rust" +version = "0.1.0" +edition = "2021" + +[dependencies] +# 外部 crate 放在这里 +``` + +**`src/main.rs`** - 入口点: +```rust +fn main() { + println!("Hello, world!"); +} +``` + +### 运行你的项目 + +```bash +cargo run +``` + +你应该看到: +``` +Compiling hello_rust v0.1.0 +Finished dev profile [unoptimized + debuginfo] target(s) +Running `target/debug/hello_rust` +Hello, world! +``` + +## 理解代码 + +让我们分解发生了什么: + + +```python !! py +# Python: 脚本式执行 +# 不需要 main 函数 - 只是从上到下执行 + +def main(): + print("Hello, world!") + +if __name__ == "__main__": + main() + +# 实际上,在 Python 中通常只需: +print("Hello, world!") +``` + +```rust !! rs +// Rust: 编译型,有显式入口点 +// 每个 Rust 程序都需要 main() 函数 + +fn main() { // fn 声明函数 + println!("Hello, world!"); // ! 表示宏 +} + +// 关键点: +// - fn: 函数关键字 +// - main(): 入口点(类似 if __name__ == "__main__") +// - {}: 函数体 +// - println!: 宏调用(注意 !) +// - ;: 语句终止符 +``` + + +### 关键 Rust 概念(预览) + +1. **`fn main()`**: 入口点函数 +2. **`println!`**: 它是 **宏**(注意 `!`),不是函数 +3. **`;`**: 语句必须以分号结尾 +4. **`{}`**: 用于作用域的代码块 + +## Cargo 命令深入 + +Cargo 是 Rust 开发的瑞士军刀: + +### 构建命令 + +```bash +# 开发版本构建(未优化,快速编译) +cargo build + +# 发布版本构建(优化后,慢速编译,快速二进制) +cargo build --release + +# 立即运行(必要时构建) +cargo run + +# 运行发布版本 +cargo run --release +``` + +### 开发命令 + +```bash +# 检查代码而不构建(最快) +cargo check + +# 运行测试 +cargo test + +# 生成文档 +cargo doc --open + +# 创建新项目(二进制) +cargo new project_name + +# 创建新项目(库) +cargo new --lib project_name + +# 更新依赖 +cargo update +``` + +### 有用的 Cargo 标志 + +```bash +# 显示详细输出 +cargo build --verbose + +# 仅编译特定目标 +cargo build --bin hello_rust + +# 编译特定示例 +cargo build --example my_example + +# 使用自定义参数运行 +cargo run -- --some-arg +``` + +## Python 和 Rust 工作流对比 + + +```python !! py +# Python 项目结构 +myproject/ +├── src/ +│ └── mymodule.py +├── tests/ +│ └── test_mymodule.py +├── requirements.txt +├── setup.py 或 pyproject.toml +└── README.md + +# 安装依赖 +pip install -r requirements.txt + +# 运行模块 +python -m src.mymodule + +# 运行测试 +pytest + +# 打包 +python setup.py sdist +``` + +```rust !! rs +// Rust 项目结构 +myproject/ +├── src/ +│ ├── main.rs # 二进制入口点 +│ └── lib.rs # 库入口点(如果是 --lib) +├── tests/ +│ └── integration_test.rs +├── examples/ +│ └── example.rs +├── Cargo.toml # 所有依赖在这里 +└── README.md + +// 构建和运行 +cargo build +cargo run +cargo test + +// 一切都由 Cargo 管理 +``` + + +## 设置开发环境 + +### 推荐的 VS Code 扩展 + +1. **rust-analyzer**(必需) + - 官方语言服务器 + - 提供 IDE 功能 + +2. **CodeLLDB** + - 调试支持 + +3. **Even Better TOML** + - Cargo.toml 的语法高亮 + +4. **Error Lens** + - 内联错误显示 + +### VS Code 设置 + +创建 `.vscode/settings.json`: +```json +{ + "rust-analyzer.checkOnSave.command": "clippy", + "rust-analyzer.cargo.features": "all", + "editor.formatOnSave": true, + "rust-analyzer.rustfmt.extraArgs": ["+nightly"] +} +``` + +## Rust 编译模型 + +理解 Rust 如何编译有助于你有效地使用它。 + + +```python !! py +# Python: 解释型 +# 源代码 → 字节码 (.pyc) → Python VM → 执行 + +def add(a, b): + return a + b + +# 逐行执行 +# 类型错误发生在运行时 +# 无编译步骤(技术上有字节码编译) +``` + +```rust !! rs +// Rust: 提前编译 +// 源代码 → LLVM IR → 机器码 → 执行 + +fn add(a: i32, b: i32) -> i32 { + a + b +} + +// 执行前完全编译 +// 编译时捕获类型错误 +// 生成原生可执行文件 +``` + + +### 编译过程 + +1. **解析**: Rust 源代码 → AST(抽象语法树) +2. **HIR 降低**: AST → HIR(高级中间表示) +3. **MIR 降低**: HIR → MIR(中级中间表示) +4. **LLVM**: MIR → LLVM IR → 机器码 +5. **链接**: 机器码 + 库 → 可执行文件 + +这就是为什么 `cargo build` 比 `python script.py` 花费更长时间,但生成的二进制文件运行速度快得多。 + +## 基本 Rust 语法(快速预览) + +让我们看一下与 Python 对比的一些基本 Rust 语法: + +### 变量 + + +```python !! py +# Python: 动态类型,默认可变 +name = "Alice" # string +age = 25 # int +height = 5.6 # float + +# 可以重新赋值为不同类型 +name = 42 # 这可以! + +# 一切都可变 +age = 26 +``` + +```rust !! rs +// Rust: 静态类型,默认不可变 +let name = "Alice"; // &str +let age: i32 = 25; // i32(显式类型) +let height = 5.6; // f64(推断) + +// 不能重新赋值为不同类型 +// name = 42; // 错误:类型不匹配 + +// 变量默认不可变 +// age = 26; // 错误:不能赋值两次 + +// 使用 mut 表示可变变量 +let mut age = 25; +age = 26; // 可以 +``` + + +### 函数 + + +```python !! py +# Python: 动态类型,可选返回类型 +def greet(name, age): + message = f"Hello, {name}!" + print(message) + return age * 2 + +# 调用 +result = greet("Alice", 25) +``` + +```rust !! rs +// Rust: 静态类型,显式返回类型 +fn greet(name: &str, age: i32) -> i32 { + let message = format!("Hello, {}!", name); + println!("{}", message); + age * 2 // 无分号 = 返回值 +} + +// 调用 +let result = greet("Alice", 25); +``` + + +## Rust 版本 + +Rust 有"版本"(editions),允许渐进式改进: + +- **Rust 2015**: 原始版本 +- **Rust 2018**: 重大改进(模块、异步准备) +- **Rust 2021**: 当前版本(默认) + +新项目应该使用 Rust 2021。所有版本可以一起工作 - 你可以在 Rust 2021 项目中使用 Rust 2015 crate。 + +## Rust 社区 + +Rust 拥有最友好的编程社区之一: + +### 官方资源 +- **Rust 程序设计语言**: [doc.rust-lang.org/book](https://doc.rust-lang.org/book/) ("Rust 书") +- **Rust by Example**: [doc.rust-lang.org/rust-by-example](https://doc.rust-lang.org/rust-by-example/) +- **Rust 标准库**: [doc.rust-lang.org/std](https://doc.rust-lang.org/std/) + +### 社区资源 +- **Rust 用户论坛**: [users.rust-lang.org](https://users.rust-lang.org/) +- **Rust Discord**: [discord.gg/rust-lang](https://discord.gg/rust-lang) +- **Reddit**: r/rust +- **Crates.io**: [crates.io](https://crates.io/)(包注册表) + +## 常见的初次挑战 + +### 1. 借用检查器 + +你会经常遇到这个错误: +```rust +let s1 = String::from("hello"); +let s2 = s1; +// println!("{}", s1); // 错误:值移动后被借用 +``` + +这是 Rust 的所有权系统防止移动后使用。我们将在模块 2 中详细讲解。 + +### 2. 冗长性 + +Rust 比 Python 更冗长: +```rust +// Rust: 到处都是显式类型 +fn process(items: Vec) -> Vec { + items.into_iter().map(|x| x * 2).collect() +} +``` + +这种冗长性换来的是安全性和性能。你会学会欣赏它。 + +### 3. 编译时间 + +Rust 编译比 Python 运行慢。提示: +- 使用 `cargo check` 进行快速验证 +- 仅在需要时使用 `cargo build` +- 编译器正在捕获你在 Python 中运行时才会发现的错误 + +## 你的第二个 Rust 程序 + +让我们写一些更有趣的东西: + + +```python !! py +def fibonacci(n): + """计算第 n 个斐波那契数。""" + if n <= 1: + return n + a, b = 0, 1 + for _ in range(2, n + 1): + a, b = b, a + b + return b + +if __name__ == "__main__": + n = 10 + result = fibonacci(n) + print(f"斐波那契({n}) = {result}") +``` + +```rust !! rs +fn fibonacci(n: u64) -> u64 { + /// 计算第 n 个斐波那契数 + if n <= 1 { + return n; + } + let (mut a, mut b) = (0, 1); + for _ in 2..=n { + let temp = a + b; + a = b; + b = temp; + } + b +} + +fn main() { + let n = 10; + let result = fibonacci(n); + println!("斐波那契({}) = {}", n, result); +} +``` + + +在 `src/main.rs` 中创建此文件并使用 `cargo run` 运行。 + +## 测试你的安装 + +验证一切正常: + +```bash +# 检查 Rust 版本 +rustc --version + +# 检查 Cargo +cargo --version + +# 创建测试项目 +cargo new test_project && cd test_project + +# 构建它 +cargo build + +# 运行它 +cargo run + +# 运行测试 +cargo test +``` + +如果所有命令都成功,你就准备好学习 Rust 了! + +## 总结 + +在本模块中,你学习了: +- ✅ 为什么 Rust 对 Python 开发者有价值 +- ✅ 如何安装 Rust 和 Cargo +- ✅ 如何创建和运行 Rust 项目 +- ✅ 基本 Cargo 命令 +- ✅ Python 解释和 Rust 编译的区别 +- ✅ 如何设置开发环境 + +## 下一步 + +现在环境已设置好: +1. **[模块 1:语法基础](./module-01-syntax-basics)** - 通过与 Python 对比学习 Rust 语法 +2. 用小程序练习 +3. 探索 [Rust Playground](https://play.rust-lang.org/) 进行快速实验 + +## 练习 + +### 练习 1:创建项目 + +创建一个名为 `greeting` 的 Cargo 项目,要求: +- 询问用户的名字 +- 打印个性化问候 +- 使用变量和函数 + +### 练习 2:温度转换器 + +编写一个将华氏度转换为摄氏度的程序: +- 公式:`C = (F - 32) * 5/9` +- 为转换创建函数 +- 用几个值测试 + +
+解决方案 + +```rust +fn fahrenheit_to_celsius(f: f64) -> f64 { + (f - 32.0) * 5.0 / 9.0 +} + +fn main() { + let fahrenheit = 98.6; + let celsius = fahrenheit_to_celsius(fahrenheit); + println!("{}°F = {}°C", fahrenheit, celsius); +} +``` + +
+ +--- + +**记住**:Rust 有学习曲线,但编译器是你的朋友。仔细阅读错误消息 - 它们非常有帮助! + +**下一步**:[模块 1 - 语法基础](./module-01-syntax-basics) → diff --git a/content/docs/py2rust/module-00-introduction.zh-tw.mdx b/content/docs/py2rust/module-00-introduction.zh-tw.mdx new file mode 100644 index 0000000..746b284 --- /dev/null +++ b/content/docs/py2rust/module-00-introduction.zh-tw.mdx @@ -0,0 +1,725 @@ +--- +title: "模組 0:介紹與環境建置" +description: "透過了解 Rust 的重要性、安裝工具鏈和編寫第一個 Rust 程式來開始你的 Rust 之旅。" +--- + +# 模組 0:介紹與環境建置 + +歡迎來到你的 Rust 之旅!在本模組中,你將學習為什麼 Rust 值得你投入時間,安裝 Rust 工具鏈,並編寫你的第一個 Rust 程式。 + +## 學習目標 + +完成本模組後,你將能夠: +- ✅ 理解 Rust 的獨特和價值所在 +- ✅ 安裝 Rust 並設定開發環境 +- ✅ 使用 Cargo(Rust 的套件管理器和建構工具) +- ✅ 編寫並執行第一個 Rust 程式 +- ✅ 導航 Rust 生態系統和資源 + +## 為什麼選擇 Rust?Python 開發者的視角 + +作為 Python 開發者,你可能會想:「為什麼要學習另一種語言?」讓我們探索 Rust 帶來的價值。 + + +```python !! py +# Python: 編寫快,執行慢 +def process_data(items): + result = [] + for item in items: + result.append(item * 2) + return result + +# 執行速度比最佳化後的 Rust 慢約 100 倍 +# 但只需 5 秒鐘編寫 +``` + +```rust !! rs +// Rust: 更冗長,但速度極快 +fn process_data(items: &[i32]) -> Vec { + items.iter().map(|x| x * 2).collect() +} + +// 執行速度達到 C/C++ 級別 +// 初始編寫時間稍長 +// 但編譯器在執行前就能捕獲錯誤 +``` + + +### Rust 的價值主張 + +#### 1. 不犧牲安全性的效能 + +Python 使用全域解釋器鎖(GIL)和垃圾回收,這限制了效能並使真正的平行性變得具有挑戰性。Rust 為你提供: +- **C/C++ 效能** 而沒有記憶體安全風險 +- **真正的平行性** 透過無畏並行 +- **可預測的效能** 沒有垃圾回收暫停 + + +```python !! py +# Python: fib(35) 約 2.5 秒 +import time + +def fib(n): + if n <= 1: + return n + return fib(n-1) + fib(n-2) + +start = time.time() +print(fib(35)) +print(f"耗時: {time.time() - start:.2f}秒") +``` + +```rust !! rs +// Rust: fib(35) 約 0.7 秒(快 3.5 倍) +use std::time::Instant; + +fn fib(n: u64) -> u64 { + match n { + 0 => 0, + 1 => 1, + _ => fib(n-1) + fib(n-2), + } +} + +fn main() { + let start = Instant::now(); + println!("{}", fib(35)); + println!("耗時: {:?}", start.elapsed()); +} +``` + + +#### 2. 編譯時的記憶體安全 + +這是 Rust 的超能力。Rust 編譯器可以防止整類錯誤: +- **空指標解引用** → Rust 沒有 null +- **懸垂指標** → 借用檢查器防止無效引用 +- **資料競爭** → 型別系統防止並行資料存取問題 +- **緩衝區溢位** → 預設邊界檢查 + +在 Python 中,這些將是執行時錯誤。在 Rust 中,它們是 **編譯時錯誤** - 你在程式碼執行之前就能發現它們。 + +#### 3. 現代工具鏈 + +Cargo 是現有的最好的套件管理器之一: + + +```python !! py +# Python: 多個工具,設定分散 +# 建立專案 +mkdir myproject +cd myproject +python -m venv venv +source venv/bin/activate + +# 新增依賴(手動編輯) +echo "numpy==1.24.0" >> requirements.txt +pip install -r requirements.txt + +# 執行測試(需要獨立的測試框架) +pytest + +# 建構(用於發佈) +python setup.py sdist bdist_wheel +``` + +```rust !! rs +// Rust: 統一的工具 +// 建立專案 +cargo new myproject +cd myproject + +// 新增依賴 +cargo add serde + +// 執行測試(內建) +cargo test + +// 建構最佳化後的二進位檔案 +cargo build --release +``` + + +#### 4. 成長的生態系統 + +Rust 擁有快速成長的生態系統,提供出色的 crate: +- **Web**: Actix-web, Axum, Rocket +- **非同步**: Tokio, async-std +- **序列化**: Serde (JSON, YAML 等) +- **資料庫**: Diesel, SQLx +- **CLI**: Clap, structopt + +許多 Python 函式庫現在都有 Rust 加速(例如,用於 Python 綁定的 PyO3)。 + +## 安裝 Rust + +### 步驟 1:安裝 rustup(Rust 工具鏈安裝程式) + +**macOS / Linux:** +```bash +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +``` + +**Windows:** +從 [rustup.rs](https://rustup.rs/) 下載並執行 `rustup-init.exe` + +安裝程式將: +1. 安裝 Rust 編譯器(`rustc`) +2. 安裝 Cargo(套件管理器和建構工具) +3. 將 Rust 新增到系統 PATH +4. 安裝穩定版 Rust 工具鏈 + +**驗證安裝:** +```bash +rustc --version +cargo --version +``` + +你應該看到類似這樣的輸出: +``` +rustc 1.75.0 (82e1608df 2023-12-21) +cargo 1.75.0 (1d8b05cdd 2023-11-20) +``` + +### 步驟 2:安裝程式碼編輯器 + +**推薦:VS Code + rust-analyzer** + +1. 安裝 [VS Code](https://code.visualstudio.com/) +2. 安裝 `rust-analyzer` 擴充功能(注意不是 "Rust" - 要用 rust-analyzer) +3. 安裝 `CodeLLDB` 用於除錯支援 + +**其他編輯器:** +- IntelliJ IDEA 配合 Rust 外掛 +- Vim/Neovim 配合 rust-tools.nvim +- Emacs 配合 rust-mode + +rust-analyzer 提供: +- ✅ 自動補全 +- ✅ 內聯型別提示 +- ✅ 轉到定義 +- ✅ 內聯錯誤訊息 +- ✅ 程式碼操作(快速修復) + +## 你的第一個 Rust 程式 + +讓我們建立並執行你的第一個 Rust 專案! + +### 建立新專案 + +```bash +cargo new hello_rust +cd hello_rust +``` + +Cargo 建立: +``` +hello_rust/ +├── Cargo.toml # 專案元資料和依賴 +└── src/ + └── main.rs # 你的原始碼 +``` + +### 理解專案結構 + +**`Cargo.toml`** - 專案清單: +```toml +[package] +name = "hello_rust" +version = "0.1.0" +edition = "2021" + +[dependencies] +# 外部 crate 放在這裡 +``` + +**`src/main.rs`** - 進入點: +```rust +fn main() { + println!("Hello, world!"); +} +``` + +### 執行你的專案 + +```bash +cargo run +``` + +你應該看到: +``` +Compiling hello_rust v0.1.0 +Finished dev profile [unoptimized + debuginfo] target(s) +Running `target/debug/hello_rust` +Hello, world! +``` + +## 理解程式碼 + +讓我們分解發生了什麼: + + +```python !! py +# Python: 腳本式執行 +# 不需要 main 函式 - 只是從上到下執行 + +def main(): + print("Hello, world!") + +if __name__ == "__main__": + main() + +# 實際上,在 Python 中通常只需: +print("Hello, world!") +``` + +```rust !! rs +// Rust: 編譯型,有顯式進入點 +// 每個 Rust 程式都需要 main() 函式 + +fn main() { // fn 宣告函式 + println!("Hello, world!"); // ! 表示巨集 +} + +// 關鍵點: +// - fn: 函式關鍵字 +// - main(): 進入點(類似 if __name__ == "__main__") +// - {}: 函式主體 +// - println!: 巨集呼叫(注意 !) +// - ;: 陳述式終止符 +``` + + +### 關鍵 Rust 概念(預覽) + +1. **`fn main()`**: 進入點函式 +2. **`println!`**: 它是 **巨集**(注意 `!`),不是函式 +3. **`;`**: 陳述式必須以分號結尾 +4. **`{}`**: 用於作用域的程式碼塊 + +## Cargo 命令深入 + +Cargo 是 Rust 開發的瑞士軍刀: + +### 建構命令 + +```bash +# 開發版本建構(未最佳化,快速編譯) +cargo build + +# 發佈版本建構(最佳化後,慢速編譯,快速二進位) +cargo build --release + +# 立即執行(必要時建構) +cargo run + +# 執行發佈版本 +cargo run --release +``` + +### 開發命令 + +```bash +# 檢查程式碼而不建構(最快) +cargo check + +# 執行測試 +cargo test + +# 產生文件 +cargo doc --open + +# 建立新專案(二進位) +cargo new project_name + +# 建立新專案(函式庫) +cargo new --lib project_name + +# 更新依賴 +cargo update +``` + +### 有用的 Cargo 標誌 + +```bash +# 顯示詳細輸出 +cargo build --verbose + +# 僅編譯特定目標 +cargo build --bin hello_rust + +# 編譯特定範例 +cargo build --example my_example + +# 使用自訂參數執行 +cargo run -- --some-arg +``` + +## Python 和 Rust 工作流程對比 + + +```python !! py +# Python 專案結構 +myproject/ +├── src/ +│ └── mymodule.py +├── tests/ +│ └── test_mymodule.py +├── requirements.txt +├── setup.py 或 pyproject.toml +└── README.md + +# 安裝依賴 +pip install -r requirements.txt + +# 執行模組 +python -m src.mymodule + +# 執行測試 +pytest + +# 打包 +python setup.py sdist +``` + +```rust !! rs +// Rust 專案結構 +myproject/ +├── src/ +│ ├── main.rs # 二進位進入點 +│ └── lib.rs # 函式庫進入點(如果是 --lib) +├── tests/ +│ └── integration_test.rs +├── examples/ +│ └── example.rs +├── Cargo.toml # 所有依賴在這裡 +└── README.md + +// 建構和執行 +cargo build +cargo run +cargo test + +// 一切都由 Cargo 管理 +``` + + +## 設定開發環境 + +### 推薦的 VS Code 擴充功能 + +1. **rust-analyzer**(必需) + - 官方語言伺服器 + - 提供 IDE 功能 + +2. **CodeLLDB** + - 除錯支援 + +3. **Even Better TOML** + - Cargo.toml 的語法高亮 + +4. **Error Lens** + - 內聯錯誤顯示 + +### VS Code 設定 + +建立 `.vscode/settings.json`: +```json +{ + "rust-analyzer.checkOnSave.command": "clippy", + "rust-analyzer.cargo.features": "all", + "editor.formatOnSave": true, + "rust-analyzer.rustfmt.extraArgs": ["+nightly"] +} +``` + +## Rust 編譯模型 + +理解 Rust 如何編譯有助於你有效地使用它。 + + +```python !! py +# Python: 直譯型 +# 原始碼 → 位元組碼 (.pyc) → Python VM → 執行 + +def add(a, b): + return a + b + +# 逐行執行 +# 型別錯誤發生在執行時 +# 無編譯步驟(技術上有位元組碼編譯) +``` + +```rust !! rs +// Rust: 提前編譯 +// 原始碼 → LLVM IR → 機器碼 → 執行 + +fn add(a: i32, b: i32) -> i32 { + a + b +} + +// 執行前完全編譯 +// 編譯時捕獲型別錯誤 +// 產生原生可執行檔 +``` + + +### 編譯過程 + +1. **解析**: Rust 原始碼 → AST(抽象語法樹) +2. **HIR 降低**: AST → HIR(高階中間表示) +3. **MIR 降低**: HIR → MIR(中階中間表示) +4. **LLVM**: MIR → LLVM IR → 機器碼 +5. **鏈接**: 機器碼 + 函式庫 → 可執行檔 + +這就是為什麼 `cargo build` 比 `python script.py` 花費更長時間,但產生的二進位檔執行速度快得多。 + +## 基本 Rust 語法(快速預覽) + +讓我們看一下與 Python 對比的一些基本 Rust 語法: + +### 變數 + + +```python !! py +# Python: 動態型別,預設可變 +name = "Alice" # string +age = 25 # int +height = 5.6 # float + +# 可以重新賦值為不同型別 +name = 42 # 這可以! + +# 一切都可變 +age = 26 +``` + +```rust !! rs +// Rust: 靜態型別,預設不可變 +let name = "Alice"; // &str +let age: i32 = 25; // i32(顯式型別) +let height = 5.6; // f64(推斷) + +// 不能重新賦值為不同型別 +// name = 42; // 錯誤:型別不匹配 + +// 變數預設不可變 +// age = 26; // 錯誤:不能賦值兩次 + +// 使用 mut 表示可變變數 +let mut age = 25; +age = 26; // 可以 +``` + + +### 函式 + + +```python !! py +# Python: 動態型別,可選回傳型別 +def greet(name, age): + message = f"Hello, {name}!" + print(message) + return age * 2 + +# 呼叫 +result = greet("Alice", 25) +``` + +```rust !! rs +// Rust: 靜態型別,顯式回傳型別 +fn greet(name: &str, age: i32) -> i32 { + let message = format!("Hello, {}!", name); + println!("{}", message); + age * 2 // 無分號 = 回傳值 +} + +// 呼叫 +let result = greet("Alice", 25); +``` + + +## Rust 版本 + +Rust 有"版本"(editions),允許漸進式改進: + +- **Rust 2015**: 原始版本 +- **Rust 2018**: 重大改進(模組、非同步準備) +- **Rust 2021**: 當前版本(預設) + +新專案應該使用 Rust 2021。所有版本可以一起工作 - 你可以在 Rust 2021 專案中使用 Rust 2015 crate。 + +## Rust 社群 + +Rust 擁有最友善的程式設計社群之一: + +### 官方資源 +- **Rust 程式設計語言**: [doc.rust-lang.org/book](https://doc.rust-lang.org/book/) ("Rust 書") +- **Rust by Example**: [doc.rust-lang.org/rust-by-example](https://doc.rust-lang.org/rust-by-example/) +- **Rust 標準函式庫**: [doc.rust-lang.org/std](https://doc.rust-lang.org/std/) + +### 社群資源 +- **Rust 使用者論壇**: [users.rust-lang.org](https://users.rust-lang.org/) +- **Rust Discord**: [discord.gg/rust-lang](https://discord.gg/rust-lang) +- **Reddit**: r/rust +- **Crates.io**: [crates.io](https://crates.io/)(套件註冊表) + +## 常見的初次挑戰 + +### 1. 借用檢查器 + +你會經常遇到這個錯誤: +```rust +let s1 = String::from("hello"); +let s2 = s1; +// println!("{}", s1); // 錯誤:值移動後被借用 +``` + +這是 Rust 的所有權系統防止移動後使用。我們將在模組 2 中詳細講解。 + +### 2. 冗長性 + +Rust 比 Python 更冗長: +```rust +// Rust: 到處都是顯式型別 +fn process(items: Vec) -> Vec { + items.into_iter().map(|x| x * 2).collect() +} +``` + +這種冗長性換來的是安全性和效能。你會學會欣賞它。 + +### 3. 編譯時間 + +Rust 編譯比 Python 執行慢。提示: +- 使用 `cargo check` 進行快速驗證 +- 僅在需要時使用 `cargo build` +- 編譯器正在捕獲你在 Python 中執行時才會發現的錯誤 + +## 你的第二個 Rust 程式 + +讓我們寫一些更有趣的東西: + + +```python !! py +def fibonacci(n): + """計算第 n 個斐波那契數。""" + if n <= 1: + return n + a, b = 0, 1 + for _ in range(2, n + 1): + a, b = b, a + b + return b + +if __name__ == "__main__": + n = 10 + result = fibonacci(n) + print(f"斐波那契({n}) = {result}") +``` + +```rust !! rs +fn fibonacci(n: u64) -> u64 { + /// 計算第 n 個斐波那契數 + if n <= 1 { + return n; + } + let (mut a, mut b) = (0, 1); + for _ in 2..=n { + let temp = a + b; + a = b; + b = temp; + } + b +} + +fn main() { + let n = 10; + let result = fibonacci(n); + println!("斐波那契({}) = {}", n, result); +} +``` + + +在 `src/main.rs` 中建立此檔案並使用 `cargo run` 執行。 + +## 測試你的安裝 + +驗證一切正常: + +```bash +# 檢查 Rust 版本 +rustc --version + +# 檢查 Cargo +cargo --version + +# 建立測試專案 +cargo new test_project && cd test_project + +# 建構它 +cargo build + +# 執行它 +cargo run + +# 執行測試 +cargo test +``` + +如果所有指令都成功,你就準備好學習 Rust 了! + +## 總結 + +在本模組中,你學習了: +- ✅ 為什麼 Rust 對 Python 開發者有價值 +- ✅ 如何安裝 Rust 和 Cargo +- ✅ 如何建立和執行 Rust 專案 +- ✅ 基本 Cargo 命令 +- ✅ Python 直譯和 Rust 編譯的區別 +- ✅ 如何設定開發環境 + +## 下一步 + +現在環境已設定好: +1. **[模組 1:語法基礎](./module-01-syntax-basics)** - 透過與 Python 對比學習 Rust 語法 +2. 用小程式練習 +3. 探索 [Rust Playground](https://play.rust-lang.org/) 進行快速實驗 + +## 練習 + +### 練習 1:建立專案 + +建立一個名為 `greeting` 的 Cargo 專案,要求: +- 詢問使用者的名字 +- 列印個人化問候 +- 使用變數和函式 + +### 練習 2:溫度轉換器 + +編寫一個將華氏度轉換為攝氏度的程式: +- 公式:`C = (F - 32) * 5/9` +- 為轉換建立函式 +- 用幾個值測試 + +
+解答 + +```rust +fn fahrenheit_to_celsius(f: f64) -> f64 { + (f - 32.0) * 5.0 / 9.0 +} + +fn main() { + let fahrenheit = 98.6; + let celsius = fahrenheit_to_celsius(fahrenheit); + println!("{}°F = {}°C", fahrenheit, celsius); +} +``` + +
+ +--- + +**記住**:Rust 有學習曲線,但編譯器是你的朋友。仔細閱讀錯誤訊息 - 它們非常有幫助! + +**下一步**:[模組 1 - 語法基礎](./module-01-syntax-basics) → diff --git a/content/docs/py2rust/module-01-syntax-basics.mdx b/content/docs/py2rust/module-01-syntax-basics.mdx new file mode 100644 index 0000000..e3ba993 --- /dev/null +++ b/content/docs/py2rust/module-01-syntax-basics.mdx @@ -0,0 +1,828 @@ +--- +title: "Module 1: Syntax Basics" +description: "Learn Rust syntax by comparing it with Python. Understand variables, types, functions, and basic expressions." +--- + +# Module 1: Syntax Basics + +Now that your Rust environment is set up, let's dive into Rust syntax! This module will be your bridge from Python to Rust, showing you how familiar concepts map to Rust. + +## Learning Objectives + +By the end of this module, you'll understand: +- ✅ Variable declarations and mutability +- ✅ Basic data types and type annotations +- ✅ Functions and expressions vs statements +- ✅ Comments and code organization +- ✅ Printing and output +- ✅ Basic operators and precedence + +## Variables and Mutability + +The first major difference you'll notice is how Rust handles variables. + + +```python !! py +# Python: Variables are references, mutable by default +name = "Alice" +age = 25 +height = 5.6 + +# Can reassign anytime +age = 26 + +# Can even change type +age = "twenty-six" # This works! +``` + +```rust !! rs +// Rust: Variables are bindings, immutable by default +let name = "Alice"; +let age: i32 = 25; // Explicit type +let height = 5.6; // Type inferred + +// Cannot reassign immutable variables +// age = 26; // ERROR: cannot assign twice to immutable variable + +// Use mut for mutable variables +let mut age = 25; +age = 26; // OK + +// Cannot change type +// age = "twenty-six"; // ERROR: type mismatch +``` + + +### Why Immutability by Default? + +Rust's default immutability prevents bugs: +- **Clearer code**: You know variables won't change +- **Compiler optimizations**: Easier to optimize +- **Concurrency**: Safer sharing between threads + + +```python !! py +# Python: Everything can change +def process(items): + result = [] + for item in items: + result.append(item * 2) + return result + +data = [1, 2, 3] +processed = process(data) +print(data) # Still [1, 2, 3] - not modified +``` + +```rust !! rs +// Rust: Explicit control over mutations +fn process(items: &[i32]) -> Vec { + items.iter().map(|x| x * 2).collect() +} + +let data = vec![1, 2, 3]; +let processed = process(&data); +println!("{:?}", data); // Still [1, 2, 3] - guaranteed! +``` + + +### Variable Naming + + +```python !! py +# Python: snake_case for variables and functions +my_variable = 42 +def calculate_sum(): + pass + +# CONSTANT-LIKE for constants +MAX_SIZE = 100 +``` + +```rust !! rs +// Rust: snake_case for variables and functions +let my_variable = 42; +fn calculate_sum() {} + +// SCREAMING_SNAKE_CASE for constants +const MAX_SIZE: usize = 100; + +// Types are PascalCase +struct MyStruct {} +enum MyEnum {} +``` + + +## Basic Data Types + +Rust has a static type system, but the compiler is good at type inference. + +### Integer Types + + +```python !! py +# Python: Arbitrary precision integers +x = 42 +x = 2**100 # Works! Python handles big integers +``` + +```rust !! rs +// Rust: Fixed-size integers +let x: i32 = 42; // 32-bit signed +let y: u64 = 100; // 64-bit unsigned +let z: i8 = -5; // 8-bit signed + +// Integer literals +let decimal = 98_222; // Underscore for readability +let hex = 0xff; // Prefix 0x +let octal = 0o77; // Prefix 0o +let binary = 0b1111_0000; // Prefix 0b +let byte = b'A'; // u8 only +``` + + +**Integer types in Rust:** +- `i8, i16, i32, i64, i128, isize` (signed) +- `u8, u16, u32, u64, u128, usize` (unsigned) +- Default is `i32` +- `isize`/`usize` are pointer-sized + +### Floating-Point Types + + +```python !! py +# Python: float is usually double precision +x = 3.14 +y = 2.0 # Also a float +``` + +```rust !! rs +// Rust: f32 and f64 +let x: f32 = 3.14; // 32-bit +let y: f64 = 2.0; // 64-bit (default) + +let z = 3.14; // Inferred as f64 +``` + + +### Boolean Type + + +```python !! py +# Python: bool type +is_active = True +is_deleted = False + +# Truthy and falsy values +if 0: # Falsy + pass +if "": # Falsy + pass +``` + +```rust !! rs +// Rust: bool type (no truthy/falsy) +let is_active: bool = true; +let is_deleted: bool = false; + +// Must use explicit boolean in conditionals +if x != 0 { // Cannot just use `if x` + // ... +} + +// Explicit comparison for strings +if !s.is_empty() { // Cannot just use `if s` + // ... +} +``` + + +### Character Type + + +```python !! py +# Python: str for strings, individual chars are just strings +c = 'A' +s = "Hello" +``` + +```rust !! rs +// Rust: char is a Unicode scalar value (4 bytes) +let c: char = 'A'; // Note single quotes +let emoji = '😀'; // Works! + +// String uses double quotes +let s: &str = "Hello"; + +// char is NOT a byte - it's Unicode! +let heart = '❤'; // Valid char +``` + + +### Type Inference + + +```python !! py +# Python: Dynamic typing - type determined at runtime +x = 42 # int +x = "hello" # Now str - type can change +``` + +```rust !! rs +// Rust: Static typing with inference +let x = 42; // Type inferred as i32 +let y: i32 = x; // Explicit type annotation + +// Type is set at declaration and cannot change +// x = "hello"; // ERROR: expected i32, found &str + +// Compiler infers from usage +let mut x = 5; // i32 +x = 10; // Still i32 +``` + + +## Functions + +Functions are where you'll see more significant differences. + + +```python !! py +# Python: Dynamic typing, no return type annotation required +def add(a, b): + return a + b + +# With type hints (optional) +def add_with_hints(a: int, b: int) -> int: + return a + b + +# Can return different types +def flexible(x): + if x > 0: + return 42 + return "negative" +``` + +```rust !! rs +// Rust: Static typing, explicit return type +fn add(a: i32, b: i32) -> i32 { + a + b // No semicolon = return value +} + +// Must declare return type +// fn add(a: i32, b: i32) { // ERROR: missing return type +// a + b +// } + +// Cannot return different types +// fn flexible(x: i32) -> ??? { // What type? +// if x > 0 { +// return 42; +// } +// return "negative"; // ERROR: type mismatch +// } +``` + + +### Statements vs Expressions + +This is a crucial Rust concept! + + +```python !! py +# Python: Everything is a statement (returns None) +x = 5 # Statement +y = (x := 10) # Assignment expression (Python 3.8+) + +# if is a statement +if x > 0: + result = "positive" +else: + result = "negative" +``` + +```rust !! rs +// Rust: Statements don't return values, expressions do +let x = 5; // Statement (note the semicolon) + +// Expressions return values +let y = { + let x = 3; + x + 1 // Expression (no semicolon) +}; // y = 4 + +// if is an expression! +let result = if x > 0 { + "positive" +} else { + "negative" +}; // No ternary operator needed +``` + + +**Key rule**: In Rust, expressions end without a semicolon, statements end with a semicolon. + + +```python !! py +# Python: Statements and expressions mixed +def calculate(x): + if x > 0: + return x * 2 + return x * 3 + +result = calculate(5) +``` + +```rust !! rs +// Rust: Everything can be an expression +fn calculate(x: i32) -> i32 { + if x > 0 { + x * 2 // Expression value returned + } else { + x * 3 + } // No semicolon for the block +} + +// Can use match for cleaner code +fn calculate_match(x: i32) -> i32 { + match x.cmp(&0) { + std::cmp::Ordering::Greater => x * 2, + _ => x * 3, + } +} +``` + + +### Return Values + + +```python !! py +# Python: Explicit return or implicit None +def add(a, b): + return a + b + +def no_return(): + x = 5 + # Implicitly returns None +``` + +```rust !! rs +// Rust: Last expression is return value +fn add(a: i32, b: i32) -> i32 { + a + b // No semicolon = returns this +} + +// Can use return keyword (but uncommon) +fn add_explicit(a: i32, b: i32) -> i32 { + return a + b; // Semicolon OK with return +} + +// Functions with no return type return () +fn no_return() { + let x = 5; + // Implicitly returns the unit type () +} +``` + + +## Comments + + +```python !! py +# Single-line comment + +""" +Multi-line docstring +Used for documentation +""" + +def my_function(): + """Function docstring.""" + pass +``` + +```rust !! rs +// Single-line comment + +/// Outer documentation (for users) +/// This is a doc comment - it becomes documentation! +/// +/// # Examples +/// ``` +/// let x = add(1, 2); +/// ``` +fn add(a: i32, b: i32) -> i32 { + a + b +} + +//! Inner documentation (for crate authors) +//! Rarely used outside of library crates + +/* Multi-line comment + Less common in Rust */ +``` + + +## Printing and Output + + +```python !! py +# Python: print function +print("Hello, world!") +print("Value:", x) +print(f"Formatted: {x}") + +# Multiple ways to format +name = "Alice" +age = 25 +print(f"{name} is {age} years old") +print("{} is {} years old".format(name, age)) +``` + +```rust !! rs +// Rust: Macros for printing +println!("Hello, world!"); +println!("Value: {}", x); +println!("Formatted: {}", x); + +// Multiple placeholders +let name = "Alice"; +let age = 25; +println!("{} is {} years old", name, age); + +// Named parameters +println!("{name} is {age} years old", name=name, age=age); + +// Debug output (use {:?}) +println!("{:?}", vec![1, 2, 3]); + +// Pretty debug (use {:#?}) +println!("{:#?}", vec![1, 2, 3]); + +// Print without newline +print!("No newline"); +``` + + +### Format Specifiers + + +```python !! py +# Python: Format specifications +x = 42 +y = 3.14159 + +print(f"{x:5}") # Width 5 +print(f"{y:.2}") # 2 decimal places +print(f"{x:#x}") # Hexadecimal +``` + +```rust !! rs +// Rust: Format specifications +let x: i32 = 42; +let y: f64 = 3.14159; + +println!("{:5}", x); // Width 5 +println!("{:.2}", y); // 2 decimal places +println!("{:#x}", x); // Hexadecimal +println!("{:?}", x); // Debug +println!("{:b}", x); // Binary +``` + + +## Operators + +Most operators are similar, but there are some differences. + + +```python !! py +# Python +a = 10 + 5 # 15 +b = 10 - 5 # 5 +c = 10 * 5 # 50 +d = 10 / 3 # 3.333... +e = 10 // 3 # 3 (floor division) +f = 10 % 3 # 1 +g = 2 ** 3 # 8 (exponentiation) +``` + +```rust !! rs +// Rust +let a = 10 + 5; // 15 +let b = 10 - 5; // 5 +let c = 10 * 5; // 50 +let d = 10 / 3; // 3 (integer division!) +let e = 10 % 3; // 1 +let f = 2_i32.pow(3); // 8 (method, not operator) + +// For floating point +let d = 10.0 / 3.0; // 3.333... +``` + + + +```python !! py +# Python +a = 5 +b = 10 + +a == b # False +a != b # True +a < b # True +a <= b # True +a > b # False +a >= b # False +``` + +```rust !! rs +// Rust (same as Python) +let a = 5; +let b = 10; + +a == b // False +a != b // True +a < b // True +a <= b // True +a > b // False +a >= b // False +``` + + + +```python !! py +# Python: words, not symbols +a = True +b = False + +a and b # False +a or b # True +not a # False +``` + +```rust !! rs +// Rust: symbols, not words +let a: bool = true; +let b: bool = false; + +a && b // False (AND) +a || b // True (OR) +!a // False (NOT) +``` + + +## String Types + +Rust has multiple string types - a common source of confusion! + + +```python !! py +# Python: Just str +s1 = "Hello" +s2 = "World" +s3 = s1 + s2 # "HelloWorld" +``` + +```rust !! rs +// Rust: &str (string slice) and String (owned) +let s1: &str = "Hello"; // &str - immutable, fixed size +let s2: String = String::from("World"); // String - growable + +let s3: String = s1.to_string() + &s2; // "HelloWorld" + +// More on strings in Module 7! +``` + + +**Quick summary:** +- `&str`: String slice, immutable, borrowed +- `String`: Heap-allocated, mutable, owned + +## Code Blocks and Scope + + +```python !! py +# Python: Function-level scope +x = 10 + +def my_function(): + # x is accessible here + print(x) + y = 5 + # y is also accessible outside in Python! + +# print(y) # This would fail, but only because y isn't defined yet +``` + +```rust !! rs +// Rust: Block-level scope +let x = 10; + +{ + // x is accessible here + println!("{}", x); + let y = 5; + println!("{}", y); +} // y is dropped here + +// println!("{}", y); // ERROR: y not found +``` + + +## Shadowing + +Rust allows shadowing - declaring a new variable with the same name. + + +```python !! py +# Python: No shadowing, just reassignment +x = 5 +x = "hello" # Same variable, different type +``` + +```rust !! rs +// Rust: Shadowing creates a new variable +let x = 5; +let x = x + 1; // Shadow x +let x = "hello"; // Shadow with different type! + +// This is different from mut +let mut x = 5; +x = x + 1; // Same variable, new value +// x = "hello"; // ERROR: type mismatch +``` + + +**When to use shadowing:** +- Transforming a value through steps +- Changing types +- Keeping the name meaningful + +## Common Patterns + + +```python !! py +# Python: Variables can be uninitialized +x = None +if condition: + x = calculate() + +print(x) # Might be None +``` + +```rust !! rs +// Rust: Variables must be initialized +let x; // Declared but not initialized +// println!("{}", x); // ERROR: use of possibly uninitialized variable + +if condition { + x = calculate(); +} + +// Must be initialized before use +if condition { + println!("{}", x); // OK +} + +// Better: use Option +let x = if condition { + Some(calculate()) +} else { + None +}; +``` + + +## Putting It All Together + +Let's write a more complex example: + + +```python !! py +def calculate_stats(numbers): + """Calculate sum and average.""" + if not numbers: + return 0, 0.0 + + total = sum(numbers) + average = total / len(numbers) + + return total, average + +def main(): + data = [1, 2, 3, 4, 5] + total, avg = calculate_stats(data) + + print(f"Total: {total}") + print(f"Average: {avg:.2}") + +if __name__ == "__main__": + main() +``` + +```rust !! rs +fn calculate_stats(numbers: &[i32]) -> (i32, f64) { + if numbers.is_empty() { + return (0, 0.0); + } + + let total: i32 = numbers.iter().sum(); + let average: f64 = total as f64 / numbers.len() as f64; + + (total, average) +} + +fn main() { + let data = vec![1, 2, 3, 4, 5]; + let (total, avg) = calculate_stats(&data); + + println!("Total: {}", total); + println!("Average: {:.2}", avg); +} +``` + + +## Summary + +In this module, you learned: +- ✅ Variables are immutable by default (`let` vs `let mut`) +- ✅ Rust has static types with inference +- ✅ Functions require explicit types +- ✅ Expressions (no semicolon) vs statements (semicolon) +- ✅ `println!` macro for output +- ✅ Block-level scope +- ✅ Shadowing for transforming values + +## Key Differences from Python + +1. **Immutability**: Rust defaults to immutable variables +2. **Static typing**: Types checked at compile time +3. **Explicit return**: Last expression is returned, not `return` keyword +4. **Block scope**: Variables limited to their block +5. **No truthy/falsy**: Must use explicit boolean conditions +6. **String types**: Multiple string types (`&str` vs `String`) + +## Exercises + +### Exercise 1: Temperature Conversion + +Write a function that converts Celsius to Fahrenheit: +- Formula: `F = C * 9/5 + 32` +- Use proper types and annotations +- Print the result with 1 decimal place + +
+Solution + +```rust +fn celsius_to_fahrenheit(celsius: f64) -> f64 { + celsius * 9.0 / 5.0 + 32.0 +} + +fn main() { + let celsius = 25.0; + let fahrenheit = celsius_to_fahrenheit(celsius); + println!("{:.1}°C = {:.1}°F", celsius, fahrenheit); +} +``` + +
+ +### Exercise 2: Circle Properties + +Calculate the area and circumference of a circle: +- Use `f64` for calculations +- Return a tuple `(area, circumference)` +- π = 3.14159 + +
+Solution + +```rust +fn circle_properties(radius: f64) -> (f64, f64) { + let pi = 3.14159; + let area = pi * radius * radius; + let circumference = 2.0 * pi * radius; + (area, circumference) +} + +fn main() { + let radius = 5.0; + let (area, circumference) = circle_properties(radius); + println!("Radius: {}", radius); + println!("Area: {:.2}", area); + println!("Circumference: {:.2}", circumference); +} +``` + +
+ +## Next Steps + +Now that you understand basic syntax: +1. **[Module 2: Ownership](./module-02-ownership)** - Learn Rust's unique memory management +2. Practice with small programs +3. Experiment with different types + +--- + +**Next:** [Module 2 - Ownership & Memory Management](./module-02-ownership) → diff --git a/content/docs/py2rust/module-01-syntax-basics.zh-cn.mdx b/content/docs/py2rust/module-01-syntax-basics.zh-cn.mdx new file mode 100644 index 0000000..c987ec9 --- /dev/null +++ b/content/docs/py2rust/module-01-syntax-basics.zh-cn.mdx @@ -0,0 +1,828 @@ +--- +title: "模块 1:语法基础" +description: "通过与 Python 对比来学习 Rust 语法。理解变量、类型、函数和基本表达式。" +--- + +# 模块 1:语法基础 + +现在你的 Rust 环境已经设置好了,让我们深入学习 Rust 语法!本模块将是你从 Python 到 Rust 的桥梁,向你展示熟悉的概念如何在 Rust 中实现。 + +## 学习目标 + +完成本模块后,你将理解: +- ✅ 变量声明和可变性 +- ✅ 基本数据类型和类型注解 +- ✅ 函数和表达式 vs 语句 +- ✅ 注释和代码组织 +- ✅ 打印和输出 +- ✅ 基本运算符和优先级 + +## 变量和可变性 + +你注意到的第一个主要区别是 Rust 如何处理变量。 + + +```python !! py +# Python:变量是引用,默认可变 +name = "Alice" +age = 25 +height = 5.6 + +# 可以随时重新赋值 +age = 26 + +# 甚至可以改变类型 +age = "twenty-six" # 这可以工作! +``` + +```rust !! rs +// Rust:变量是绑定,默认不可变 +let name = "Alice"; +let age: i32 = 25; // 显式类型 +let height = 5.6; // 类型推断 + +// 不能重新赋值不可变变量 +// age = 26; // 错误:不能两次赋值给不可变变量 + +// 使用 mut 声明可变变量 +let mut age = 25; +age = 26; // 正确 + +// 不能改变类型 +// age = "twenty-six"; // 错误:类型不匹配 +``` + + +### 为什么默认不可变? + +Rust 的默认不可变性可以防止 bug: +- **更清晰的代码**:你知道变量不会改变 +- **编译器优化**:更容易优化 +- **并发性**:在线程间更安全地共享 + + +```python !! py +# Python:一切都可以改变 +def process(items): + result = [] + for item in items: + result.append(item * 2) + return result + +data = [1, 2, 3] +processed = process(data) +print(data) # 仍然是 [1, 2, 3] - 未修改 +``` + +```rust !! rs +// Rust:对变化的显式控制 +fn process(items: &[i32]) -> Vec { + items.iter().map(|x| x * 2).collect() +} + +let data = vec![1, 2, 3]; +let processed = process(&data); +println!("{:?}", data); // 仍然是 [1, 2, 3] - 有保证! +``` + + +### 变量命名 + + +```python !! py +# Python:变量和函数使用 snake_case +my_variable = 42 +def calculate_sum(): + pass + +# 常量使用 CONSTANT-LIKE +MAX_SIZE = 100 +``` + +```rust !! rs +// Rust:变量和函数使用 snake_case +let my_variable = 42; +fn calculate_sum() {} + +// 常量使用 SCREAMING_SNAKE_CASE +const MAX_SIZE: usize = 100; + +// 类型使用 PascalCase +struct MyStruct {} +enum MyEnum {} +``` + + +## 基本数据类型 + +Rust 有静态类型系统,但编译器擅长类型推断。 + +### 整数类型 + + +```python !! py +# Python:任意精度整数 +x = 42 +x = 2**100 # 可以工作!Python 处理大整数 +``` + +```rust !! rs +// Rust:固定大小整数 +let x: i32 = 42; // 32位有符号 +let y: u64 = 100; // 64位无符号 +let z: i8 = -5; // 8位有符号 + +// 整数字面量 +let decimal = 98_222; // 下划线用于可读性 +let hex = 0xff; // 前缀 0x +let octal = 0o77; // 前缀 0o +let binary = 0b1111_0000; // 前缀 0b +let byte = b'A'; // 仅 u8 +``` + + +**Rust 中的整数类型:** +- `i8, i16, i32, i64, i128, isize` (有符号) +- `u8, u16, u32, u64, u128, usize` (无符号) +- 默认是 `i32` +- `isize`/`usize` 是指针大小的 + +### 浮点类型 + + +```python !! py +# Python:float 通常是双精度 +x = 3.14 +y = 2.0 # 也是 float +``` + +```rust !! rs +// Rust:f32 和 f64 +let x: f32 = 3.14; // 32位 +let y: f64 = 2.0; // 64位(默认) + +let z = 3.14; // 推断为 f64 +``` + + +### 布尔类型 + + +```python !! py +# Python:bool 类型 +is_active = True +is_deleted = False + +# 真值和假值 +if 0: # 假值 + pass +if "": # 假值 + pass +``` + +```rust !! rs +// Rust:bool 类型(没有真值/假值) +let is_active: bool = true; +let is_deleted: bool = false; + +// 必须在条件中使用显式布尔值 +if x != 0 { // 不能只使用 `if x` + // ... +} + +// 字符串的显式比较 +if !s.is_empty() { // 不能只使用 `if s` + // ... +} +``` + + +### 字符类型 + + +```python !! py +# Python:str 用于字符串,单个字符也只是字符串 +c = 'A' +s = "Hello" +``` + +```rust !! rs +// Rust:char 是 Unicode 标量值(4 字节) +let c: char = 'A'; // 注意单引号 +let emoji = '😀'; // 可以工作! + +// String 使用双引号 +let s: &str = "Hello"; + +// char 不是字节 - 它是 Unicode! +let heart = '❤'; // 有效的 char +``` + + +### 类型推断 + + +```python !! py +# Python:动态类型 - 类型在运行时确定 +x = 42 # int +x = "hello" # 现在是 str - 类型可以改变 +``` + +```rust !! rs +// Rust:带推断的静态类型 +let x = 42; // 类型推断为 i32 +let y: i32 = x; // 显式类型注解 + +// 类型在声明时设置,不能改变 +// x = "hello"; // 错误:期望 i32,发现 &str + +// 编译器从使用中推断 +let mut x = 5; // i32 +x = 10; // 仍然是 i32 +``` + + +## 函数 + +函数是你将看到更显著差异的地方。 + + +```python !! py +# Python:动态类型,不需要返回类型注解 +def add(a, b): + return a + b + +# 带类型提示(可选) +def add_with_hints(a: int, b: int) -> int: + return a + b + +# 可以返回不同类型 +def flexible(x): + if x > 0: + return 42 + return "negative" +``` + +```rust !! rs +// Rust:静态类型,显式返回类型 +fn add(a: i32, b: i32) -> i32 { + a + b // 没有分号 = 返回值 +} + +// 必须声明返回类型 +// fn add(a: i32, b: i32) { // 错误:缺少返回类型 +// a + b +// } + +// 不能返回不同类型 +// fn flexible(x: i32) -> ??? { // 什么类型? +// if x > 0 { +// return 42; +// } +// return "negative"; // 错误:类型不匹配 +// } +``` + + +### 语句 vs 表达式 + +这是一个关键的 Rust 概念! + + +```python !! py +# Python:一切都是语句(返回 None) +x = 5 # 语句 +y = (x := 10) # 赋值表达式(Python 3.8+) + +# if 是语句 +if x > 0: + result = "positive" +else: + result = "negative" +``` + +```rust !! rs +// Rust:语句不返回值,表达式返回值 +let x = 5; // 语句(注意分号) + +// 表达式返回值 +let y = { + let x = 3; + x + 1 // 表达式(没有分号) +}; // y = 4 + +// if 是表达式! +let result = if x > 0 { + "positive" +} else { + "negative" +}; // 不需要三元运算符 +``` + + +**关键规则**:在 Rust 中,表达式结束没有分号,语句结束有分号。 + + +```python !! py +# Python:语句和表达式混合 +def calculate(x): + if x > 0: + return x * 2 + return x * 3 + +result = calculate(5) +``` + +```rust !! rs +// Rust:一切都可以是表达式 +fn calculate(x: i32) -> i32 { + if x > 0 { + x * 2 // 表达式值被返回 + } else { + x * 3 + } // 块没有分号 +} + +// 可以使用 match 使代码更清晰 +fn calculate_match(x: i32) -> i32 { + match x.cmp(&0) { + std::cmp::Ordering::Greater => x * 2, + _ => x * 3, + } +} +``` + + +### 返回值 + + +```python !! py +# Python:显式 return 或隐式 None +def add(a, b): + return a + b + +def no_return(): + x = 5 + # 隐式返回 None +``` + +```rust !! rs +// Rust:最后一个表达式是返回值 +fn add(a: i32, b: i32) -> i32 { + a + b // 没有分号 = 返回这个 +} + +// 可以使用 return 关键字(但不常见) +fn add_explicit(a: i32, b: i32) -> i32 { + return a + b; // 使用 return 时分号可以 +} + +// 没有返回类型的函数返回 () +fn no_return() { + let x = 5; + // 隐式返回单元类型 () +} +``` + + +## 注释 + + +```python !! py +# 单行注释 + +""" +多行文档字符串 +用于文档 +""" + +def my_function(): + """函数文档字符串。""" + pass +``` + +```rust !! rs +// 单行注释 + +/// 外部文档(给用户看的) +/// 这是文档注释 - 它会变成文档! +/// +/// # 示例 +/// ``` +/// let x = add(1, 2); +/// ``` +fn add(a: i32, b: i32) -> i32 { + a + b +} + +//! 内部文档(给 crate 作者看的) +//! 除了 library crate 外很少使用 + +/* 多行注释 + 在 Rust 中不常见 */ +``` + + +## 打印和输出 + + +```python !! py +# Python:print 函数 +print("Hello, world!") +print("Value:", x) +print(f"Formatted: {x}") + +# 多种格式化方式 +name = "Alice" +age = 25 +print(f"{name} is {age} years old") +print("{} is {} years old".format(name, age)) +``` + +```rust !! rs +// Rust:用于打印的宏 +println!("Hello, world!"); +println!("Value: {}", x); +println!("Formatted: {}", x); + +// 多个占位符 +let name = "Alice"; +let age = 25; +println!("{} is {} years old", name, age); + +// 命名参数 +println!("{name} is {age} years old", name=name, age=age); + +// 调试输出(使用 {:?}) +println!("{:?}", vec![1, 2, 3]); + +// 漂亮调试(使用 {:#?}) +println!("{:#?}", vec![1, 2, 3]); + +// 打印不带换行 +print!("No newline"); +``` + + +### 格式说明符 + + +```python !! py +# Python:格式规范 +x = 42 +y = 3.14159 + +print(f"{x:5}") # 宽度 5 +print(f"{y:.2}") # 2 位小数 +print(f"{x:#x}") # 十六进制 +``` + +```rust !! rs +// Rust:格式规范 +let x: i32 = 42; +let y: f64 = 3.14159; + +println!("{:5}", x); // 宽度 5 +println!("{:.2}", y); // 2 位小数 +println!("{:#x}", x); // 十六进制 +println!("{:?}", x); // 调试 +println!("{:b}", x); // 二进制 +``` + + +## 运算符 + +大多数运算符是相似的,但有一些差异。 + + +```python !! py +# Python +a = 10 + 5 # 15 +b = 10 - 5 # 5 +c = 10 * 5 # 50 +d = 10 / 3 # 3.333... +e = 10 // 3 # 3(整除) +f = 10 % 3 # 1 +g = 2 ** 3 # 8(幂运算) +``` + +```rust !! rs +// Rust +let a = 10 + 5; // 15 +let b = 10 - 5; // 5 +let c = 10 * 5; // 50 +let d = 10 / 3; // 3(整数除法!) +let e = 10 % 3; // 1 +let f = 2_i32.pow(3); // 8(方法,不是运算符) + +// 对于浮点数 +let d = 10.0 / 3.0; // 3.333... +``` + + + +```python !! py +# Python +a = 5 +b = 10 + +a == b # False +a != b # True +a < b # True +a <= b # True +a > b # False +a >= b # False +``` + +```rust !! rs +// Rust(与 Python 相同) +let a = 5; +let b = 10; + +a == b // False +a != b // True +a < b // True +a <= b // True +a > b // False +a >= b // False +``` + + + +```python !! py +# Python:单词,不是符号 +a = True +b = False + +a and b # False +a or b # True +not a # False +``` + +```rust !! rs +// Rust:符号,不是单词 +let a: bool = true; +let b: bool = false; + +a && b // False (AND) +a || b // True (OR) +!a // False (NOT) +``` + + +## 字符串类型 + +Rust 有多种字符串类型 - 这是一个常见的困惑源! + + +```python !! py +# Python:只有 str +s1 = "Hello" +s2 = "World" +s3 = s1 + s2 # "HelloWorld" +``` + +```rust !! rs +// Rust:&str(字符串切片)和 String(拥有的) +let s1: &str = "Hello"; // &str - 不可变,固定大小 +let s2: String = String::from("World"); // String - 可增长 + +let s3: String = s1.to_string() + &s2; // "HelloWorld" + +// 更多关于字符串的内容在模块 7! +``` + + +**快速总结:** +- `&str`:字符串切片,不可变,借用 +- `String`:堆分配,可变,拥有 + +## 代码块和作用域 + + +```python !! py +# Python:函数级作用域 +x = 10 + +def my_function(): + # x 在这里可访问 + print(x) + y = 5 + # y 在 Python 中也可以在外部访问! + +# print(y) # 这会失败,但只是因为 y 还没有定义 +``` + +```rust !! rs +// Rust:块级作用域 +let x = 10; + +{ + // x 在这里可访问 + println!("{}", x); + let y = 5; + println!("{}", y); +} // y 在这里被丢弃 + +// println!("{}", y); // 错误:找不到 y +``` + + +## 遮蔽 + +Rust 允许遮蔽 - 用相同名称声明新变量。 + + +```python !! py +# Python:没有遮蔽,只是重新赋值 +x = 5 +x = "hello" # 同一个变量,不同类型 +``` + +```rust !! rs +// Rust:遮蔽创建新变量 +let x = 5; +let x = x + 1; // 遮蔽 x +let x = "hello"; // 用不同类型遮蔽! + +// 这与 mut 不同 +let mut x = 5; +x = x + 1; // 同一个变量,新值 +// x = "hello"; // 错误:类型不匹配 +``` + + +**何时使用遮蔽:** +- 通过步骤转换值 +- 改变类型 +- 保持名称有意义 + +## 常见模式 + + +```python !! py +# Python:变量可以未初始化 +x = None +if condition: + x = calculate() + +print(x) # 可能是 None +``` + +```rust !! rs +// Rust:变量必须初始化 +let x; // 声明但未初始化 +// println!("{}", x); // 错误:使用了可能未初始化的变量 + +if condition { + x = calculate(); +} + +// 必须在使用前初始化 +if condition { + println!("{}", x); // 正确 +} + +// 更好:使用 Option +let x = if condition { + Some(calculate()) +} else { + None +}; +``` + + +## 综合实例 + +让我们写一个更复杂的例子: + + +```python !! py +def calculate_stats(numbers): + """计算总和和平均值。""" + if not numbers: + return 0, 0.0 + + total = sum(numbers) + average = total / len(numbers) + + return total, average + +def main(): + data = [1, 2, 3, 4, 5] + total, avg = calculate_stats(data) + + print(f"Total: {total}") + print(f"Average: {avg:.2}") + +if __name__ == "__main__": + main() +``` + +```rust !! rs +fn calculate_stats(numbers: &[i32]) -> (i32, f64) { + if numbers.is_empty() { + return (0, 0.0); + } + + let total: i32 = numbers.iter().sum(); + let average: f64 = total as f64 / numbers.len() as f64; + + (total, average) +} + +fn main() { + let data = vec![1, 2, 3, 4, 5]; + let (total, avg) = calculate_stats(&data); + + println!("Total: {}", total); + println!("Average: {:.2}", avg); +} +``` + + +## 总结 + +在本模块中,你学习了: +- ✅ 变量默认不可变(`let` vs `let mut`) +- ✅ Rust 有带推断的静态类型 +- ✅ 函数需要显式类型 +- ✅ 表达式(无分号)vs 语句(有分号) +- ✅ `println!` 宏用于输出 +- ✅ 块级作用域 +- ✅ 用于转换值的遮蔽 + +## 与 Python 的主要区别 + +1. **不可变性**:Rust 默认为不可变变量 +2. **静态类型**:类型在编译时检查 +3. **显式返回**:最后一个表达式被返回,不是 `return` 关键字 +4. **块作用域**:变量限制在其块中 +5. **没有真值/假值**:必须使用显式布尔条件 +6. **字符串类型**:多种字符串类型(`&str` vs `String`) + +## 练习 + +### 练习 1:温度转换 + +编写一个将摄氏度转换为华氏度的函数: +- 公式:`F = C * 9/5 + 32` +- 使用适当的类型和注解 +- 打印结果,保留 1 位小数 + +
+解决方案 + +```rust +fn celsius_to_fahrenheit(celsius: f64) -> f64 { + celsius * 9.0 / 5.0 + 32.0 +} + +fn main() { + let celsius = 25.0; + let fahrenheit = celsius_to_fahrenheit(celsius); + println!("{:.1}°C = {:.1}°F", celsius, fahrenheit); +} +``` + +
+ +### 练习 2:圆的属性 + +计算圆的面积和周长: +- 使用 `f64` 进行计算 +- 返回元组 `(area, circumference)` +- π = 3.14159 + +
+解决方案 + +```rust +fn circle_properties(radius: f64) -> (f64, f64) { + let pi = 3.14159; + let area = pi * radius * radius; + let circumference = 2.0 * pi * radius; + (area, circumference) +} + +fn main() { + let radius = 5.0; + let (area, circumference) = circle_properties(radius); + println!("Radius: {}", radius); + println!("Area: {:.2}", area); + println!("Circumference: {:.2}", circumference); +} +``` + +
+ +## 下一步 + +既然你已经理解了基本语法: +1. **[模块 2:所有权](./module-02-ownership)** - 学习 Rust 独特的内存管理 +2. 用小程序练习 +3. 尝试不同的类型 + +--- + +**下一步:** [模块 2 - 所有权与内存管理](./module-02-ownership) → diff --git a/content/docs/py2rust/module-03-borrowing.mdx b/content/docs/py2rust/module-03-borrowing.mdx new file mode 100644 index 0000000..c841e21 --- /dev/null +++ b/content/docs/py2rust/module-03-borrowing.mdx @@ -0,0 +1,1001 @@ +--- +title: "Module 3: Borrowing & References" +description: "Understanding Rust's ownership system through borrowing and references, with comparisons to Python's reference semantics" +--- + +# Module 3: Borrowing & References + +## Learning Objectives + +By the end of this module, you'll understand: +- How Rust's borrowing system works compared to Python's references +- The difference between immutable and mutable references +- Rust's borrowing rules at compile time +- How the borrow checker prevents memory safety issues +- Common borrowing patterns and idioms + +## Introduction: Python vs Rust Reference Models + +In Python, you're used to passing references to objects around freely. Python's garbage collector handles memory management automatically, and you rarely need to think about who "owns" a piece of data. + +Rust takes a different approach with its **borrowing system**, which enforces memory safety at compile time without a garbage collector. This is one of Rust's most unique and powerful features. + +### Python: Everything is a Reference + + +```python !! py +# Python: References work everywhere +data = [1, 2, 3] + +# Multiple references to the same data +ref1 = data +ref2 = data + +# All references work independently +ref1.append(4) +print(ref2) # [1, 2, 3, 4] - sees the change! + +# Can pass to functions freely +def modify_list(lst): + lst.append(5) + +modify_list(data) +print(data) # [1, 2, 3, 4, 5] + +# Can have multiple mutable references +a = data +b = data +c = data +a.append(6) +b.append(7) +print(c) # All work fine! +``` + + +### Rust: Controlled Borrowing + + +```rust !! rs +// Rust: Borrowing follows strict rules +fn main() { + let data = vec![1, 2, 3]; + + // Immutable references - can have many! + let ref1 = &data; + let ref2 = &data; + let ref3 = &data; + + println!("{:?} {:?} {:?}", ref1, ref2, ref3); + + // Mutable reference - exclusive access + let mut data_mut = vec![1, 2, 3]; + let mref = &mut data_mut; + mref.push(4); + + // Can't use data_mut while mref exists! + // data_mut.push(5); // ERROR: data_mut was borrowed + + println!("{:?}", mref); +} +``` + + +## Rust's Borrowing Rules + +Rust enforces these rules at **compile time**: + +1. **You can have multiple immutable references** (`&T`) to a value +2. **You can have one mutable reference** (`&mut T`) to a value +3. **References must always be valid** (no dangling pointers) + +These rules prevent data races and use-after-free bugs at compile time! + +### Rule 1: Multiple Immutable References + + +```python !! py +# Python: Many references, one object +numbers = [1, 2, 3, 4, 5] + +# Multiple "immutable" reads (Python doesn't enforce) +def read_first(lst): + return lst[0] + +def read_last(lst): + return lst[-1] + +print(read_first(numbers)) # 1 +print(read_last(numbers)) # 5 +print(numbers[0]) # 1 +# All safe because we're only reading +``` + +```rust !! rs +// Rust: Multiple immutable references are safe +fn main() { + let numbers = vec![1, 2, 3, 4, 5]; + + // Many immutable references at once + let first = &numbers[0]; + let last = &numbers[numbers.len() - 1]; + let middle = &numbers[2]; + + // All can coexist safely + println!("First: {}, Last: {}, Middle: {}", first, last, middle); + + // Can pass to functions + print_length(&numbers); + print_length(&numbers); // Again! + + // Original still accessible + println!("{:?}", numbers); +} + +fn print_length(vec: &Vec) { + println!("Length: {}", vec.len()); +} +``` + + +### Rule 2: One Mutable Reference (Exclusive Access) + + +```python !! py +# Python: Multiple mutable references allowed +data = {"count": 0} + +ref_a = data +ref_b = data + +# Both can modify - potential data race in threads! +ref_a["count"] += 1 +ref_b["count"] += 1 + +print(data["count"]) # 2 + +# Python doesn't prevent this - you must be careful! +``` + +```rust !! rs +// Rust: Only ONE mutable reference at a time +fn main() { + let mut data = vec![1, 2, 3]; + + // ONE mutable reference + let ref1 = &mut data; + ref1.push(4); + + // Can't create another mutable reference + // let ref2 = &mut data; // ERROR! + // Cannot borrow `data` as mutable more than once + + // Can't even use the original name + // data.push(5); // ERROR! + // Cannot borrow `data` as mutable + // because it is also borrowed as mutable + + println!("{:?}", ref1); + + // After ref1 is no longer used, we can borrow again + let ref2 = &mut data; + ref2.push(5); + println!("{:?}", ref2); +} +``` + + +### Rule 3: Cannot Mix Mutable and Immutable + + +```python !! py +# Python: Can mix reads and writes +data = [1, 2, 3] + +read_ref = data +write_ref = data + +# Read through read_ref while write_ref modifies +print(read_ref[0]) # 1 +write_ref.append(4) +print(read_ref) # [1, 2, 3, 4] - sees the change! + +# Python allows this - can lead to bugs +``` + +```rust !! rs +// Rust: Cannot mix mutable and immutable +fn main() { + let mut data = vec![1, 2, 3]; + + // Immutable reference + let read_ref = &data; + println!("Read: {:?}", read_ref); + + // Can't create mutable while immutable exists + // let write_ref = &mut data; // ERROR! + // Cannot borrow `data` as mutable + // because it is also borrowed as immutable + + // After read_ref is no longer used + let write_ref = &mut data; + write_ref.push(4); + println!("Write: {:?}", write_ref); + + // Now immutable again + let new_read = &data; + println!("New read: {:?}", new_read); +} +``` + + +## Borrow Checker Examples + +The Rust compiler (borrow checker) ensures all borrowing rules are followed. Let's see common scenarios. + +### Example 1: Borrowing in Functions + + +```python !! py +# Python: References passed implicitly +def double_items(items): + for i in range(len(items)): + items[i] *= 2 + +def print_items(items): + print(items) + +numbers = [1, 2, 3] +print_items(numbers) # Read +double_items(numbers) # Write +print_items(numbers) # Read again - [2, 4, 6] +``` + +```rust !! rs +// Rust: Explicit borrowing in functions +fn main() { + let mut numbers = vec![1, 2, 3]; + + // Immutable borrow for reading + print_items(&numbers); + + // Mutable borrow for writing + double_items(&mut numbers); + + // Immutable borrow again + print_items(&numbers); + + // Original still owned by main + println!("Owned: {:?}", numbers); +} + +fn double_items(items: &mut Vec) { + for item in items.iter_mut() { + *item *= 2; + } +} + +fn print_items(items: &Vec) { + println!("{:?}", items); +} +``` + + +### Example 2: Borrowing in Loops + + +```python !! py +# Python: Can collect and modify freely +data = ["hello", "world", "rust"] + +# Collect references +first_letters = [word[0] for word in data] + +# Modify original +data[0] = data[0].upper() + +print(first_letters) # ['h', 'w', 'r'] +print(data) # ['HELLO', 'world', 'rust'] +``` + +```rust !! rs +// Rust: Careful with borrows in loops +fn main() { + let mut data = vec!["hello", "world", "rust"]; + + // Collect immutable references + let first_letters: Vec<&str> = data.iter() + .map(|s| &s[0..1]) + .collect(); + + println!("{:?}", first_letters); + + // Can't modify while references exist + // data[0] = "HELLO"; // ERROR! + // Cannot borrow `data` as mutable + // because it is also borrowed as immutable + + // After first_letters is used, we can modify + drop(first_letters); // Explicitly drop + data[0] = "HELLO"; + + println!("{:?}", data); +} +``` + + +### Example 3: Returning References + + +```python !! py +# Python: Can return references freely +def get_first(items): + return items[0] + +data = [10, 20, 30] +first = get_first(data) +print(first) # 10 + +# Even after function returns +data[0] = 100 +print(first) # Still 10 (not a reference to data[0] anymore) +``` + +```rust !! rs +// Rust: Lifetime ensures validity +fn main() { + let data = vec![10, 20, 30]; + + // Borrow from data + let first = get_first(&data); + println!("First: {}", first); + + // Can't modify while borrowed + // data[0] = 100; // ERROR! + + println!("Data: {:?}", data); +} + +// Lifetime annotation: returned reference +// lives at least as long as the input +fn get_first<'a>(items: &'a Vec) -> &'a i32 { + &items[0] +} + +// This won't compile - dangling reference! +// fn get_first_bad() -> &i32 { +// let value = 42; +// &value // ERROR: returns a reference to dropped data +// } +``` + + +## Dangling References Prevention + +Rust's borrow checker ensures references never point to invalid memory. + +### What is a Dangling Reference? + + +```python !! py +# Python: Garbage collector prevents dangling refs +def create_ref(): + data = [1, 2, 3] + return data # Returns reference, object stays alive + +ref = create_ref() +print(ref) # [1, 2, 3] - Python keeps it alive + +# Even with explicit reference +import weakref + +class MyClass: + def __init__(self, value): + self.value = value + +obj = MyClass(42) +weak_ref = weakref.ref(obj) + +del obj # Delete original +print(weak_ref()) # None - object was deleted +``` + +```rust !! rs +// Rust: Compile-time prevention of dangling refs +fn main() { + // This works - reference to valid data + let data = vec![1, 2, 3]; + let first = &data[0]; + println!("First: {}", first); + + // This won't compile - dangling reference! + // let bad_ref = create_dangling(); + // println!("{:?}", bad_ref); +} + +// This function won't compile! +// fn create_dangling() -> &i32 { +// let value = 42; +// &value // ERROR: returns a reference to dropped data +// } + +// Correct version: return owned value +fn create_owned() -> i32 { + let value = 42; + value // Ownership transferred +} + +// Or return reference to something that exists +fn create_valid<'a>(data: &'a Vec) -> &'a i32 { + &data[0] +} +``` + + +### Scope-Based Validity + + +```python !! py +# Python: References work until GC collects +def process(): + local_data = [1, 2, 3] + return local_data[:] # Return copy + +result = process() +print(result) # [1, 2, 3] - works fine + +# Original local_data is gone, but we have a copy +``` + +```rust !! rs +// Rust: References tied to scope +fn main() { + // Reference must live shorter than owner + let result; + { + let data = vec![1, 2, 3]; + result = data.len(); // Copy value, not reference + } // data is dropped here + + println!("Length: {}", result); // Works fine + + // This wouldn't work: + // let ref_result; + // { + // let data = vec![1, 2, 3]; + // ref_result = &data[0]; // ERROR! + // } // data dropped, ref_result would be dangling + // println!("{:?}", ref_result); +} +``` + + +## Multiple Borrowing Scenarios + +Let's explore common borrowing patterns and pitfalls. + +### Scenario 1: Iterator Invalidaton + + +```python !! py +# Python: Modifying while iterating (buggy!) +numbers = [1, 2, 3, 4, 5] + +# This skips elements! +for i, num in enumerate(numbers): + if num % 2 == 0: + numbers.pop(i) # Modifying while iterating + +print(numbers) # [1, 3, 5] - 2 was removed, but 4 stayed! +``` + +```rust !! rs +// Rust: Compile-time protection +fn main() { + let mut numbers = vec![1, 2, 3, 4, 5]; + + // Can't borrow mutably while iterating + // for num in numbers.iter() { + // if num % 2 == 0 { + // numbers.push(10); // ERROR! + // Cannot borrow `numbers` as mutable + // because it is also borrowed as immutable + // } + // } + + // Correct approach: collect indices first + let to_remove: Vec = numbers.iter() + .enumerate() + .filter(|(_, &num)| num % 2 == 0) + .map(|(i, _)| i) + .collect(); + + // Then modify (in reverse order to keep indices valid) + for i in to_remove.into_iter().rev() { + numbers.remove(i); + } + + println!("{:?}", numbers); // [1, 3, 5] + + // Or use retain() + let mut numbers = vec![1, 2, 3, 4, 5]; + numbers.retain(|&x| x % 2 != 0); + println!("{:?}", numbers); // [1, 3, 5] +} +``` + + +### Scenario 2: Struct Field Borrowing + + +```python !! py +# Python: Can access multiple fields +class Point: + def __init__(self, x, y): + self.x = x + self.y = y + +p = Point(10, 20) +x_ref = p.x +y_ref = p.y + +print(x_ref, y_ref) # 10 20 +p.x = 30 +print(x_ref) # 10 (not updated) +``` + +```rust !! rs +// Rust: Borrowing struct fields +struct Point { + x: i32, + y: i32, +} + +fn main() { + let mut p = Point { x: 10, y: 20 }; + + // Multiple immutable borrows of different fields + let x_ref = &p.x; + let y_ref = &p.y; + println!("X: {}, Y: {}", x_ref, y_ref); + + // Can't borrow mutably while borrows exist + // p.x = 30; // ERROR! + + // After borrows end, can modify + let x_mut = &mut p.x; + *x_mut = 30; + println!("X: {}", p.x); + + // Can borrow multiple fields mutably (non-overlapping) + { + let x_ref = &mut p.x; + *x_ref += 1; + } // x_ref ends here + { + let y_ref = &mut p.y; + *y_ref += 1; + } // y_ref ends here + + println!("Point: ({}, {})", p.x, p.y); +} +``` + + +### Scenario 3: Conditional Borrowing + + +```python !! py +# Python: Conditional modifications +data = [1, 2, 3, 4, 5] + +def maybe_modify(items, should_modify): + if should_modify: + items.append(6) + return items + +result = maybe_modify(data, True) +print(result) # [1, 2, 3, 4, 5, 6] + +result = maybe_modify(data, False) +print(result) # [1, 2, 3, 4, 5, 6] +``` + +```rust !! rs +// Rust: Conditional borrowing +fn main() { + let mut data = vec![1, 2, 3, 4, 5]; + + // Conditional modification + let result = maybe_modify(&mut data, true); + println!("{:?}", result); // [1, 2, 3, 4, 5, 6] + + let result = maybe_modify(&mut data, false); + println!("{:?}", result); // [1, 2, 3, 4, 5, 6] +} + +fn maybe_modify(items: &mut Vec, should_modify: bool) -> &Vec { + if should_modify { + items.push(6); + } + items // Return reference +} +``` + + +## Comparison: Python References vs Rust Borrowing + +### Conceptual Differences + +| Aspect | Python | Rust | +|--------|--------|------| +| **Memory Safety** | Runtime (GC) | Compile-time (borrow checker) | +| **Mutable References** | Unlimited | Only one at a time | +| **Mixing Read/Write** | Allowed | Not allowed simultaneously | +| **Dangling References** | Possible (weakref) | Prevented at compile time | +| **Performance** | GC overhead | Zero-cost | +| **Thread Safety** | Developer's responsibility | Guaranteed by compiler | + +### Practical Implications + + +```python !! py +# Python: Everything is a reference +def modify_shared_data(data1, data2): + # Both can modify shared state + data1.append(1) + data2.append(2) + +shared = [] +modify_shared_data(shared, shared) +print(shared) # [1, 2] + +# In concurrent code, this causes data races! +# Python relies on GIL and developer discipline +``` + + + +```rust !! rs +// Rust: Compile-time prevention of issues +fn main() { + let mut shared = vec![]; + + // Can't pass as mutable twice + // modify_shared_data(&mut shared, &mut shared); + // ERROR: use of moved value: `shared` + + // Must pass once, or use interior mutability + modify_once(&mut shared); + modify_once(&mut shared); + + println!("{:?}", shared); // [1, 2] +} + +fn modify_once(data: &mut Vec) { + data.push(1); +} +``` + + +## Best Practices + +### DO: Use References for Read-Only Access + + +```rust !! rs +fn main() { + let data = vec![1, 2, 3, 4, 5]; + + // Use & for read-only + calculate_sum(&data); + calculate_sum(&data); // Can call multiple times + print_first(&data); +} + +fn calculate_sum(numbers: &Vec) -> i32 { + numbers.iter().sum() +} + +fn print_first(numbers: &Vec) { + println!("First: {}", numbers[0]); +} +``` + + +### DO: Borrow for the Minimum Necessary Scope + + +```rust !! rs +fn main() { + let mut data = vec![1, 2, 3, 4, 5]; + + // Borrow only when needed + { + let first = &data[0]; + println!("First: {}", first); + } // Borrow ends here + + // Can now modify + data.push(6); + + // Another borrow + { + let last = &data[data.len() - 1]; + println!("Last: {}", last); + } +} +``` + + +### DON'T: Create Unnecessary Mutable Borrows + + +```rust !! rs +fn main() { + let data = vec![1, 2, 3, 4, 5]; + + // DON'T: Use mutable when immutable suffices + // print_items(&mut data); // Unnecessary! + + // DO: Use immutable for read-only + print_items(&data); +} + +fn print_items(items: &Vec) { + println!("{:?}", items); +} +``` + + +## Common Borrowing Pitfalls + +### Pitfall 1: Forgetting to Drop Borrows + + +```python !! py +# Python: No issue +data = [1, 2, 3] + +ref = data +print(ref) + +data.append(4) # Works fine +print(ref) +``` + +```rust !! rs +// Rust: Borrow persists until last use +fn main() { + let mut data = vec![1, 2, 3]; + + let ref_vec = &data; + println!("{:?}", ref_vec); + + // Can't modify while ref_vec is "alive" + // data.push(4); // ERROR! + + // Even though we printed it, compiler thinks it's still used + // Solution: explicitly drop or use smaller scope + + // Explicit drop + drop(ref_vec); + data.push(4); // Now works! + + println!("{:?}", data); +} +``` + + +### Pitfall 2: Collecting References + + +```rust !! rs +fn main() { + let mut data = vec![1, 2, 3, 4, 5]; + + // Collect references + let evens: Vec<&i32> = data.iter() + .filter(|&&x| x % 2 == 0) + .collect(); + + println!("{:?}", evens); + + // Can't modify while evens holds references + // data.push(6); // ERROR! + + // Must drop evens first + drop(evens); + data.push(6); + + println!("{:?}", data); +} +``` + + +### Pitfall 3: Closure Borrowing + + +```rust !! rs +fn main() { + let data = vec![1, 2, 3, 4, 5]; + + // Closure borrows data + let sum = || { + data.iter().sum::() + }; + + println!("Sum: {}", sum()); + + // Can't modify while closure exists + // let mut data_mut = data; + // data_mut.push(6); // ERROR! + + // After closure is used, can consume + let mut data_mut = data; + data_mut.push(6); + + println!("{:?}", data_mut); +} +``` + + +## Advanced: Smart Pointers and Borrowing + +For more complex scenarios, Rust provides smart pointers with interior mutability. + + +```python !! py +# Python: Shared mutable state everywhere +counter = [0] + +def increment(): + counter[0] += 1 + +increment() +increment() +print(counter[0]) # 2 +``` + +```rust !! rs +use std::rc::Rc; +use std::cell::RefCell; + +fn main() { + // Runtime borrow checking with RefCell + let counter = Rc::new(RefCell::new(0)); + + // Clone the Rc (reference count increases) + let counter_ref1 = Rc::clone(&counter); + let counter_ref2 = Rc::clone(&counter); + + // Multiple mutable references at runtime! + // Will panic if there's a conflict + *counter_ref1.borrow_mut() += 1; + *counter_ref2.borrow_mut() += 1; + + println!("Count: {}", counter.borrow()); // 2 + + // Use for shared ownership with runtime checking + // Less efficient but more flexible +} +``` + + +## Exercises + +### Exercise 1: Fix the Borrow Checker + + +```rust !! rs +// FIX THE ERRORS in this code + +fn main() { + let mut numbers = vec![1, 2, 3, 4, 5]; + + let first = &numbers[0]; + let last = &numbers[numbers.len() - 1]; + + numbers.push(6); // ERROR: Why? + + println!("First: {}, Last: {}", first, last); + println!("{:?}", numbers); +} + +// HINT: Adjust the order or use scopes +``` + + +### Exercise 2: Implement a Function + + +```rust !! rs +// IMPLEMENT this function using borrowing + +fn main() { + let numbers = vec![10, 20, 30, 40, 50]; + let largest = find_largest(&numbers); + println!("Largest: {}", largest); // Should print 50 +} + +// TODO: Implement find_largest +// It should: +// 1. Take a reference to a Vec +// 2. Return the largest value +// 3. Use borrowing (not take ownership) +fn find_largest(numbers: &Vec) -> i32 { + // Your code here + unimplemented!() +} +``` + + +### Exercise 3: Borrowing in Structs + + +```rust !! rs +// IMPLEMENT a struct that holds a reference + +fn main() { + let data = vec![1, 2, 3, 4, 5]; + + // Create a DataView that references the data + let view = create_view(&data); + + println!("First: {}", view.first()); // 1 + println!("Last: {}", view.last()); // 5 + + println!("{:?}", data); // Still accessible +} + +// TODO: Define DataView struct with a reference +// HINT: You'll need lifetime annotations +struct DataView { + // Your fields here +} + +fn create_view(data: &Vec) -> DataView { + // Your code here + unimplemented!() +} + +// Implement methods for DataView +// impl DataView { +// fn first(&self) -> i32 { ... } +// fn last(&self) -> i32 { ... } +// } +``` + + +## Summary + +In this module, you learned: + +### Key Concepts +- **Borrowing**: Rust's system for temporary access without ownership transfer +- **Immutable References (`&T`)**: Multiple readers allowed simultaneously +- **Mutable References (`&mut T`)**: Exclusive access, only one at a time +- **Borrow Checker**: Compile-time enforcement of memory safety + +### Borrowing Rules +1. Multiple immutable references are OK +2. One mutable reference is OK +3. Cannot mix mutable and immutable references +4. References must always be valid + +### Python vs Rust +- **Python**: Runtime memory management with GC, flexible references +- **Rust**: Compile-time guarantees, zero-cost safety + +### Best Practices +- Use `&T` for read-only access +- Use `&mut T` only when mutation is needed +- Minimize borrow scope +- Drop references explicitly when needed +- Use smart pointers (Rc, RefCell) for complex sharing scenarios + +### Next Steps +In [Module 4: Structs and Enums](/docs/py2rust/module-04-structs-enums), we'll explore Rust's type system and how to define custom data types with structs and enums, comparing them to Python's classes and enums. + +--- + +**Practice**: Try rewriting some Python code that uses references in Rust, paying attention to the borrowing rules. The borrow checker will guide you toward safe patterns! diff --git a/content/docs/py2rust/module-03-borrowing.zh-cn.mdx b/content/docs/py2rust/module-03-borrowing.zh-cn.mdx new file mode 100644 index 0000000..50a0fa0 --- /dev/null +++ b/content/docs/py2rust/module-03-borrowing.zh-cn.mdx @@ -0,0 +1,1001 @@ +--- +title: "模块 3:借用与引用" +description: "通过借用和引用理解 Rust 的所有权系统,并与 Python 的引用语义进行对比" +--- + +# 模块 3:借用与引用 + +## 学习目标 + +完成本模块后,你将理解: +- Rust 的借用系统如何与 Python 的引用进行比较 +- 不可变引用和可变引用的区别 +- Rust 在编译时的借用规则 +- 借用检查器如何防止内存安全问题 +- 常见的借用模式和惯用法 + +## 简介:Python vs Rust 引用模型 + +在 Python 中,你已经习惯了自由地传递对象的引用。Python 的垃圾回收器自动管理内存,你很少需要考虑谁"拥有"某块数据。 + +Rust 采用不同的方法,使用**借用系统**,在编译时强制执行内存安全,而不需要垃圾回收器。这是 Rust 最独特和最强大的特性之一。 + +### Python:一切都是引用 + + +```python !! py +# Python:引用在任何地方都可以使用 +data = [1, 2, 3] + +# 同一个数据的多个引用 +ref1 = data +ref2 = data + +# 所有引用都可以独立工作 +ref1.append(4) +print(ref2) # [1, 2, 3, 4] - 看到了变化! + +# 可以自由地传递给函数 +def modify_list(lst): + lst.append(5) + +modify_list(data) +print(data) # [1, 2, 3, 4, 5] + +# 可以有多个可变引用 +a = data +b = data +c = data +a.append(6) +b.append(7) +print(c) # 都能正常工作! +``` + + +### Rust:受控的借用 + + +```rust !! rs +// Rust:借用遵循严格的规则 +fn main() { + let data = vec![1, 2, 3]; + + // 不可变引用 - 可以有多个! + let ref1 = &data; + let ref2 = &data; + let ref3 = &data; + + println!("{:?} {:?} {:?}", ref1, ref2, ref3); + + // 可变引用 - 独占访问 + let mut data_mut = vec![1, 2, 3]; + let mref = &mut data_mut; + mref.push(4); + + // mref 存在时不能使用 data_mut! + // data_mut.push(5); // 错误:data_mut 已被借用 + + println!("{:?}", mref); +} +``` + + +## Rust 的借用规则 + +Rust 在**编译时**强制执行这些规则: + +1. **可以拥有多个不可变引用**(`&T`)指向同一个值 +2. **可以拥有一个可变引用**(`&mut T`)指向一个值 +3. **引用必须始终有效**(没有悬垂指针) + +这些规则在编译时防止数据竞争和释放后使用错误! + +### 规则 1:多个不可变引用 + + +```python !! py +# Python:多个引用,一个对象 +numbers = [1, 2, 3, 4, 5] + +# 多个"不可变"读取(Python 不强制) +def read_first(lst): + return lst[0] + +def read_last(lst): + return lst[-1] + +print(read_first(numbers)) # 1 +print(read_last(numbers)) # 5 +print(numbers[0]) # 1 +# 都是安全的,因为我们只是读取 +``` + +```rust !! rs +// Rust:多个不可变引用是安全的 +fn main() { + let numbers = vec![1, 2, 3, 4, 5]; + + // 一次有多个不可变引用 + let first = &numbers[0]; + let last = &numbers[numbers.len() - 1]; + let middle = &numbers[2]; + + // 所有引用都可以安全共存 + println!("First: {}, Last: {}, Middle: {}", first, last, middle); + + // 可以传递给函数 + print_length(&numbers); + print_length(&numbers); // 再次调用! + + // 原始数据仍然可访问 + println!("{:?}", numbers); +} + +fn print_length(vec: &Vec) { + println!("Length: {}", vec.len()); +} +``` + + +### 规则 2:一个可变引用(独占访问) + + +```python !! py +# Python:允许多个可变引用 +data = {"count": 0} + +ref_a = data +ref_b = data + +# 两者都可以修改 - 在线程中可能发生数据竞争! +ref_a["count"] += 1 +ref_b["count"] += 1 + +print(data["count"]) # 2 + +# Python 不会阻止这种情况 - 你必须小心! +``` + +```rust !! rs +// Rust:同一时间只能有一个可变引用 +fn main() { + let mut data = vec![1, 2, 3]; + + // 一个可变引用 + let ref1 = &mut data; + ref1.push(4); + + // 不能创建另一个可变引用 + // let ref2 = &mut data; // 错误! + // 不能多次可变借用 `data` + + // 甚至不能使用原始名称 + // data.push(5); // 错误! + // 不能可变借用 `data` + // 因为它已经被可变借用 + + println!("{:?}", ref1); + + // ref1 不再使用后,可以再次借用 + let ref2 = &mut data; + ref2.push(5); + println!("{:?}", ref2); +} +``` + + +### 规则 3:不能混合可变和不可变 + + +```python !! py +# Python:可以混合读写 +data = [1, 2, 3] + +read_ref = data +write_ref = data + +# 通过 read_ref 读取,同时 write_ref 修改 +print(read_ref[0]) # 1 +write_ref.append(4) +print(read_ref) # [1, 2, 3, 4] - 看到了变化! + +# Python 允许这样做 - 可能导致错误 +``` + +```rust !! rs +// Rust:不能混合可变和不可变 +fn main() { + let mut data = vec![1, 2, 3]; + + // 不可变引用 + let read_ref = &data; + println!("Read: {:?}", read_ref); + + // 不可变引用存在时不能创建可变引用 + // let write_ref = &mut data; // 错误! + // 不能可变借用 `data` + // 因为它已经被不可变借用 + + // read_ref 不再使用后 + let write_ref = &mut data; + write_ref.push(4); + println!("Write: {:?}", write_ref); + + // 现在可以再次不可变借用 + let new_read = &data; + println!("New read: {:?}", new_read); +} +``` + + +## 借用检查器示例 + +Rust 编译器(借用检查器)确保所有借用规则都被遵循。让我们看看常见场景。 + +### 示例 1:函数中的借用 + + +```python !! py +# Python:隐式传递引用 +def double_items(items): + for i in range(len(items)): + items[i] *= 2 + +def print_items(items): + print(items) + +numbers = [1, 2, 3] +print_items(numbers) # 读取 +double_items(numbers) # 写入 +print_items(numbers) # 再次读取 - [2, 4, 6] +``` + +```rust !! rs +// Rust:函数中的显式借用 +fn main() { + let mut numbers = vec![1, 2, 3]; + + // 不可变借用用于读取 + print_items(&numbers); + + // 可变借用用于写入 + double_items(&mut numbers); + + // 再次不可变借用 + print_items(&numbers); + + // main 仍然拥有原始数据 + println!("Owned: {:?}", numbers); +} + +fn double_items(items: &mut Vec) { + for item in items.iter_mut() { + *item *= 2; + } +} + +fn print_items(items: &Vec) { + println!("{:?}", items); +} +``` + + +### 示例 2:循环中的借用 + + +```python !! py +# Python:可以自由收集和修改 +data = ["hello", "world", "rust"] + +# 收集引用 +first_letters = [word[0] for word in data] + +# 修改原始数据 +data[0] = data[0].upper() + +print(first_letters) # ['h', 'w', 'r'] +print(data) # ['HELLO', 'world', 'rust'] +``` + +```rust !! rs +// Rust:循环中借用需要小心 +fn main() { + let mut data = vec!["hello", "world", "rust"]; + + // 收集不可变引用 + let first_letters: Vec<&str> = data.iter() + .map(|s| &s[0..1]) + .collect(); + + println!("{:?}", first_letters); + + // 引用存在时不能修改 + // data[0] = "HELLO"; // 错误! + // 不能可变借用 `data` + // 因为它已经被不可变借用 + + // first_letters 被使用后,可以修改 + drop(first_letters); // 显式丢弃 + data[0] = "HELLO"; + + println!("{:?}", data); +} +``` + + +### 示例 3:返回引用 + + +```python !! py +# Python:可以自由返回引用 +def get_first(items): + return items[0] + +data = [10, 20, 30] +first = get_first(data) +print(first) # 10 + +# 函数返回后 +data[0] = 100 +print(first) # 仍然是 10(不再是 data[0] 的引用) +``` + +```rust !! rs +// Rust:生命周期确保有效性 +fn main() { + let data = vec![10, 20, 30]; + + // 从 data 借用 + let first = get_first(&data); + println!("First: {}", first); + + // 借用时不能修改 + // data[0] = 100; // 错误! + + println!("Data: {:?}", data); +} + +// 生命周期注解:返回的引用 +// 至少与输入参数一样长 +fn get_first<'a>(items: &'a Vec) -> &'a i32 { + &items[0] +} + +// 这不会编译 - 悬垂引用! +// fn get_first_bad() -> &i32 { +// let value = 42; +// &value // 错误:返回指向已释放数据的引用 +// } +``` + + +## 悬垂引用预防 + +Rust 的借用检查器确保引用永远不会指向无效的内存。 + +### 什么是悬垂引用? + + +```python !! py +# Python:垃圾回收器防止悬垂引用 +def create_ref(): + data = [1, 2, 3] + return data # 返回引用,对象保持存活 + +ref = create_ref() +print(ref) # [1, 2, 3] - Python 保持它存活 + +# 即使使用显式引用 +import weakref + +class MyClass: + def __init__(self, value): + self.value = value + +obj = MyClass(42) +weak_ref = weakref.ref(obj) + +del obj # 删除原始对象 +print(weak_ref()) # None - 对象已被删除 +``` + +```rust !! rs +// Rust:编译时防止悬垂引用 +fn main() { + // 这可行 - 引用有效数据 + let data = vec![1, 2, 3]; + let first = &data[0]; + println!("First: {}", first); + + // 这不会编译 - 悬垂引用! + // let bad_ref = create_dangling(); + // println!("{:?}", bad_ref); +} + +// 这个函数不会编译! +// fn create_dangling() -> &i32 { +// let value = 42; +// &value // 错误:返回指向已释放数据的引用 +// } + +// 正确版本:返回拥有的值 +fn create_owned() -> i32 { + let value = 42; + value // 所有权转移 +} + +// 或者返回对存在事物的引用 +fn create_valid<'a>(data: &'a Vec) -> &'a i32 { + &data[0] +} +``` + + +### 基于作用域的有效性 + + +```python !! py +# Python:引用一直工作直到 GC 回收 +def process(): + local_data = [1, 2, 3] + return local_data[:] # 返回副本 + +result = process() +print(result) # [1, 2, 3] - 正常工作 + +# 原始 local_data 消失了,但我们有副本 +``` + +```rust !! rs +// Rust:引用与作用域绑定 +fn main() { + // 引用的生命周期必须短于所有者 + let result; + { + let data = vec![1, 2, 3]; + result = data.len(); // 复制值,不是引用 + } // data 在这里被丢弃 + + println!("Length: {}", result); // 正常工作 + + // 这样不行: + // let ref_result; + // { + // let data = vec![1, 2, 3]; + // ref_result = &data[0]; // 错误! + // } // data 被丢弃,ref_result 将悬垂 + // println!("{:?}", ref_result); +} +``` + + +## 多种借用场景 + +让我们探索常见的借用模式和陷阱。 + +### 场景 1:迭代器失效 + + +```python !! py +# Python:迭代时修改(有 bug!) +numbers = [1, 2, 3, 4, 5] + +# 这会跳过元素! +for i, num in enumerate(numbers): + if num % 2 == 0: + numbers.pop(i) # 迭代时修改 + +print(numbers) # [1, 3, 5] - 2 被删除了,但 4 还在! +``` + +```rust !! rs +// Rust:编译时保护 +fn main() { + let mut numbers = vec![1, 2, 3, 4, 5]; + + // 迭代时不能可变借用 + // for num in numbers.iter() { + // if num % 2 == 0 { + // numbers.push(10); // 错误! + // 不能可变借用 `numbers` + // 因为它已经被不可变借用 + // } + // } + + // 正确方法:先收集索引 + let to_remove: Vec = numbers.iter() + .enumerate() + .filter(|(_, &num)| num % 2 == 0) + .map(|(i, _)| i) + .collect(); + + // 然后修改(反向以保持索引有效) + for i in to_remove.into_iter().rev() { + numbers.remove(i); + } + + println!("{:?}", numbers); // [1, 3, 5] + + // 或使用 retain() + let mut numbers = vec![1, 2, 3, 4, 5]; + numbers.retain(|&x| x % 2 != 0); + println!("{:?}", numbers); // [1, 3, 5] +} +``` + + +### 场景 2:结构体字段借用 + + +```python !! py +# Python:可以访问多个字段 +class Point: + def __init__(self, x, y): + self.x = x + self.y = y + +p = Point(10, 20) +x_ref = p.x +y_ref = p.y + +print(x_ref, y_ref) # 10 20 +p.x = 30 +print(x_ref) # 10(未更新) +``` + +```rust !! rs +// Rust:借用结构体字段 +struct Point { + x: i32, + y: i32, +} + +fn main() { + let mut p = Point { x: 10, y: 20 }; + + // 多个不可变借用不同字段 + let x_ref = &p.x; + let y_ref = &p.y; + println!("X: {}, Y: {}", x_ref, y_ref); + + // 借用存在时不能可变借用 + // p.x = 30; // 错误! + + // 借用结束后可以修改 + let x_mut = &mut p.x; + *x_mut = 30; + println!("X: {}", p.x); + + // 可以可变借用多个字段(不重叠) + { + let x_ref = &mut p.x; + *x_ref += 1; + } // x_ref 在这里结束 + { + let y_ref = &mut p.y; + *y_ref += 1; + } // y_ref 在这里结束 + + println!("Point: ({}, {})", p.x, p.y); +} +``` + + +### 场景 3:条件借用 + + +```python !! py +# Python:条件修改 +data = [1, 2, 3, 4, 5] + +def maybe_modify(items, should_modify): + if should_modify: + items.append(6) + return items + +result = maybe_modify(data, True) +print(result) # [1, 2, 3, 4, 5, 6] + +result = maybe_modify(data, False) +print(result) # [1, 2, 3, 4, 5, 6] +``` + +```rust !! rs +// Rust:条件借用 +fn main() { + let mut data = vec![1, 2, 3, 4, 5]; + + // 条件修改 + let result = maybe_modify(&mut data, true); + println!("{:?}", result); // [1, 2, 3, 4, 5, 6] + + let result = maybe_modify(&mut data, false); + println!("{:?}", result); // [1, 2, 3, 4, 5, 6] +} + +fn maybe_modify(items: &mut Vec, should_modify: bool) -> &Vec { + if should_modify { + items.push(6); + } + items // 返回引用 +} +``` + + +## 比较:Python 引用 vs Rust 借用 + +### 概念差异 + +| 方面 | Python | Rust | +|--------|--------|------| +| **内存安全** | 运行时(GC) | 编译时(借用检查器) | +| **可变引用** | 无限 | 同一时间只能有一个 | +| **混合读/写** | 允许 | 不允许同时进行 | +| **悬垂引用** | 可能(weakref) | 编译时预防 | +| **性能** | GC 开销 | 零成本 | +| **线程安全** | 开发者责任 | 编译器保证 | + +### 实际影响 + + +```python !! py +# Python:一切都是引用 +def modify_shared_data(data1, data2): + # 两者都可以修改共享状态 + data1.append(1) + data2.append(2) + +shared = [] +modify_shared_data(shared, shared) +print(shared) # [1, 2] + +# 在并发代码中,这会导致数据竞争! +# Python 依赖 GIL 和开发者纪律 +``` + + + +```rust !! rs +// Rust:编译时预防问题 +fn main() { + let mut shared = vec![]; + + // 不能作为可变引用传递两次 + // modify_shared_data(&mut shared, &mut shared); + // 错误:使用了已移动的值:`shared` + + // 必须传递一次,或使用内部可变性 + modify_once(&mut shared); + modify_once(&mut shared); + + println!("{:?}", shared); // [1, 2] +} + +fn modify_once(data: &mut Vec) { + data.push(1); +} +``` + + +## 最佳实践 + +### DO:使用引用进行只读访问 + + +```rust !! rs +fn main() { + let data = vec![1, 2, 3, 4, 5]; + + // 使用 & 进行只读 + calculate_sum(&data); + calculate_sum(&data); // 可以多次调用 + print_first(&data); +} + +fn calculate_sum(numbers: &Vec) -> i32 { + numbers.iter().sum() +} + +fn print_first(numbers: &Vec) { + println!("First: {}", numbers[0]); +} +``` + + +### DO:在最小必要范围内借用 + + +```rust !! rs +fn main() { + let mut data = vec![1, 2, 3, 4, 5]; + + // 只在需要时借用 + { + let first = &data[0]; + println!("First: {}", first); + } // 借用在这里结束 + + // 现在可以修改 + data.push(6); + + // 另一个借用 + { + let last = &data[data.len() - 1]; + println!("Last: {}", last); + } +} +``` + + +### DON'T:创建不必要的可变借用 + + +```rust !! rs +fn main() { + let data = vec![1, 2, 3, 4, 5]; + + // 不要:不可变足够时使用可变 + // print_items(&mut data); // 不必要! + + // 应该:只读使用不可变 + print_items(&data); +} + +fn print_items(items: &Vec) { + println!("{:?}", items); +} +``` + + +## 常见借用陷阱 + +### 陷阱 1:忘记丢弃借用 + + +```python !! py +# Python:没有问题 +data = [1, 2, 3] + +ref = data +print(ref) + +data.append(4) # 正常工作 +print(ref) +``` + +```rust !! rs +// Rust:借用持续到最后一次使用 +fn main() { + let mut data = vec![1, 2, 3]; + + let ref_vec = &data; + println!("{:?}", ref_vec); + + // ref_vec "活着"时不能修改 + // data.push(4); // 错误! + + // 尽管我们打印了它,编译器认为它仍然被使用 + // 解决方案:显式丢弃或使用更小的作用域 + + // 显式丢弃 + drop(ref_vec); + data.push(4); // 现在可以工作! + + println!("{:?}", data); +} +``` + + +### 陷阱 2:收集引用 + + +```rust !! rs +fn main() { + let mut data = vec![1, 2, 3, 4, 5]; + + // 收集引用 + let evens: Vec<&i32> = data.iter() + .filter(|&&x| x % 2 == 0) + .collect(); + + println!("{:?}", evens); + + // evens 持有引用时不能修改 + // data.push(6); // 错误! + + // 必须先丢弃 evens + drop(evens); + data.push(6); + + println!("{:?}", data); +} +``` + + +### 陷阱 3:闭包借用 + + +```rust !! rs +fn main() { + let data = vec![1, 2, 3, 4, 5]; + + // 闭包借用 data + let sum = || { + data.iter().sum::() + }; + + println!("Sum: {}", sum()); + + // 闭包存在时不能修改 + // let mut data_mut = data; + // data_mut.push(6); // 错误! + + // 闭包使用后可以消费 + let mut data_mut = data; + data_mut.push(6); + + println!("{:?}", data_mut); +} +``` + + +## 高级:智能指针和借用 + +对于更复杂的场景,Rust 提供具有内部可变性的智能指针。 + + +```python !! py +# Python:到处都是共享可变状态 +counter = [0] + +def increment(): + counter[0] += 1 + +increment() +increment() +print(counter[0]) # 2 +``` + +```rust !! rs +use std::rc::Rc; +use std::cell::RefCell; + +fn main() { + // 使用 RefCell 进行运行时借用检查 + let counter = Rc::new(RefCell::new(0)); + + // 克隆 Rc(引用计数增加) + let counter_ref1 = Rc::clone(&counter); + let counter_ref2 = Rc::clone(&counter); + + // 运行时多个可变引用! + // 如果有冲突会 panic + *counter_ref1.borrow_mut() += 1; + *counter_ref2.borrow_mut() += 1; + + println!("Count: {}", counter.borrow()); // 2 + + // 用于具有运行时检查的共享所有权 + // 效率较低但更灵活 +} +``` + + +## 练习 + +### 练习 1:修复借用检查器 + + +```rust !! rs +// 修复这段代码中的错误 + +fn main() { + let mut numbers = vec![1, 2, 3, 4, 5]; + + let first = &numbers[0]; + let last = &numbers[numbers.len() - 1]; + + numbers.push(6); // 错误:为什么? + + println!("First: {}, Last: {}", first, last); + println!("{:?}", numbers); +} + +// 提示:调整顺序或使用作用域 +``` + + +### 练习 2:实现函数 + + +```rust !! rs +// 使用借用实现这个函数 + +fn main() { + let numbers = vec![10, 20, 30, 40, 50]; + let largest = find_largest(&numbers); + println!("Largest: {}", largest); // 应该打印 50 +} + +// TODO:实现 find_largest +// 它应该: +// 1. 接受对 Vec 的引用 +// 2. 返回最大值 +// 3. 使用借用(不获取所有权) +fn find_largest(numbers: &Vec) -> i32 { + // 你的代码 + unimplemented!() +} +``` + + +### 练习 3:结构体中的借用 + + +```rust !! rs +// 实现一个持有引用的结构体 + +fn main() { + let data = vec![1, 2, 3, 4, 5]; + + // 创建一个引用 data 的 DataView + let view = create_view(&data); + + println!("First: {}", view.first()); // 1 + println!("Last: {}", view.last()); // 5 + + println!("{:?}", data); // 仍然可访问 +} + +// TODO:定义带有引用的 DataView 结构体 +// 提示:你需要生命周期注解 +struct DataView { + // 你的字段 +} + +fn create_view(data: &Vec) -> DataView { + // 你的代码 + unimplemented!() +} + +// 为 DataView 实现方法 +// impl DataView { +// fn first(&self) -> i32 { ... } +// fn last(&self) -> i32 { ... } +// } +``` + + +## 总结 + +在本模块中,你学习了: + +### 核心概念 +- **借用**:Rust 在不转移所有权的情况下进行临时访问的系统 +- **不可变引用(`&T`)**:同时允许多个读取者 +- **可变引用(`&mut T`)**:独占访问,同一时间只能有一个 +- **借用检查器**:编译时强制执行内存安全 + +### 借用规则 +1. 多个不可变引用可以 +2. 一个可变引用可以 +3. 不能混合可变和不可变引用 +4. 引用必须始终有效 + +### Python vs Rust +- **Python**:带有 GC 的运行时内存管理,灵活的引用 +- **Rust**:编译时保证,零成本安全 + +### 最佳实践 +- 使用 `&T` 进行只读访问 +- 只在需要变异时使用 `&mut T` +- 最小化借用范围 +- 需要时显式丢弃引用 +- 对复杂共享场景使用智能指针(Rc、RefCell) + +### 下一步 +在[模块 4:结构体和枚举](/docs/py2rust/module-04-structs-enums)中,我们将探索 Rust 的类型系统以及如何使用结构体和枚举定义自定义数据类型,并与 Python 的类和枚举进行比较。 + +--- + +**练习**:尝试用 Rust 重写一些使用引用的 Python 代码,注意借用规则。借用检查器会引导你采用安全的模式! diff --git a/content/docs/py2rust/module-03-borrowing.zh-tw.mdx b/content/docs/py2rust/module-03-borrowing.zh-tw.mdx new file mode 100644 index 0000000..832b9ab --- /dev/null +++ b/content/docs/py2rust/module-03-borrowing.zh-tw.mdx @@ -0,0 +1,1001 @@ +--- +title: "模組 3:借用與引用" +description: "透過借用與引用理解 Rust 的所有權系統,並與 Python 的引用語義進行對比" +--- + +# 模組 3:借用與引用 + +## 學習目標 + +完成本模組後,你將理解: +- Rust 的借用系統如何與 Python 的引用進行比較 +- 不可變引用和可變引用的區別 +- Rust 在編譯時的借用規則 +- 借用檢查器如何防止記憶體安全問題 +- 常見的借用模式和慣用法 + +## 簡介:Python vs Rust 引用模型 + +在 Python 中,你已經習慣了自由地傳遞物件的引用。Python 的垃圾回收器自動管理記憶體,你很少需要考慮誰"擁有"某塊資料。 + +Rust 採用不同的方法,使用**借用系統**,在編譯時強制執行記憶體安全,而不需要垃圾回收器。這是 Rust 最獨特和最強大的特性之一。 + +### Python:一切都是引用 + + +```python !! py +# Python:引用在任何地方都可以使用 +data = [1, 2, 3] + +# 同一個資料的多個引用 +ref1 = data +ref2 = data + +# 所有引用都可以獨立工作 +ref1.append(4) +print(ref2) # [1, 2, 3, 4] - 看到了變化! + +# 可以自由地傳遞給函式 +def modify_list(lst): + lst.append(5) + +modify_list(data) +print(data) # [1, 2, 3, 4, 5] + +# 可以有多個可變引用 +a = data +b = data +c = data +a.append(6) +b.append(7) +print(c) # 都能正常工作! +``` + + +### Rust:受控的借用 + + +```rust !! rs +// Rust:借用遵循嚴格的規則 +fn main() { + let data = vec![1, 2, 3]; + + // 不可變引用 - 可以有多個! + let ref1 = &data; + let ref2 = &data; + let ref3 = &data; + + println!("{:?} {:?} {:?}", ref1, ref2, ref3); + + // 可變引用 - 獨占存取 + let mut data_mut = vec![1, 2, 3]; + let mref = &mut data_mut; + mref.push(4); + + // mref 存在時不能使用 data_mut! + // data_mut.push(5); // 錯誤:data_mut 已被借用 + + println!("{:?}", mref); +} +``` + + +## Rust 的借用規則 + +Rust 在**編譯時**強制執行這些規則: + +1. **可以擁有多個不可變引用**(`&T`)指向同一個值 +2. **可以擁有一個可變引用**(`&mut T`)指向一個值 +3. **引用必須始終有效**(沒有懸垂指標) + +這些規則在編譯時防止資料競爭和釋放後使用錯誤! + +### 規則 1:多個不可變引用 + + +```python !! py +# Python:多個引用,一個物件 +numbers = [1, 2, 3, 4, 5] + +# 多個"不可變"讀取(Python 不強制) +def read_first(lst): + return lst[0] + +def read_last(lst): + return lst[-1] + +print(read_first(numbers)) # 1 +print(read_last(numbers)) # 5 +print(numbers[0]) # 1 +# 都是安全的,因為我們只是讀取 +``` + +```rust !! rs +// Rust:多個不可變引用是安全的 +fn main() { + let numbers = vec![1, 2, 3, 4, 5]; + + // 一次有多個不可變引用 + let first = &numbers[0]; + let last = &numbers[numbers.len() - 1]; + let middle = &numbers[2]; + + // 所有引用都可以安全共存 + println!("First: {}, Last: {}, Middle: {}", first, last, middle); + + // 可以傳遞給函式 + print_length(&numbers); + print_length(&numbers); // 再次呼叫! + + // 原始資料仍然可存取 + println!("{:?}", numbers); +} + +fn print_length(vec: &Vec) { + println!("Length: {}", vec.len()); +} +``` + + +### 規則 2:一個可變引用(獨占存取) + + +```python !! py +# Python:允許多個可變引用 +data = {"count": 0} + +ref_a = data +ref_b = data + +# 兩者都可以修改 - 在執行緒中可能發生資料競爭! +ref_a["count"] += 1 +ref_b["count"] += 1 + +print(data["count"]) # 2 + +# Python 不會阻止這種情況 - 你必須小心! +``` + +```rust !! rs +// Rust:同一時間只能有一個可變引用 +fn main() { + let mut data = vec![1, 2, 3]; + + // 一個可變引用 + let ref1 = &mut data; + ref1.push(4); + + // 不能建立另一個可變引用 + // let ref2 = &mut data; // 錯誤! + // 不能多次可變借用 `data` + + // 甚至不能使用原始名稱 + // data.push(5); // 錯誤! + // 不能可變借用 `data` + // 因為它已經被可變借用 + + println!("{:?}", ref1); + + // ref1 不再使用後,可以再次借用 + let ref2 = &mut data; + ref2.push(5); + println!("{:?}", ref2); +} +``` + + +### 規則 3:不能混合可變和不可變 + + +```python !! py +# Python:可以混合讀寫 +data = [1, 2, 3] + +read_ref = data +write_ref = data + +# 透過 read_ref 讀取,同時 write_ref 修改 +print(read_ref[0]) # 1 +write_ref.append(4) +print(read_ref) # [1, 2, 3, 4] - 看到了變化! + +# Python 允許這樣做 - 可能導致錯誤 +``` + +```rust !! rs +// Rust:不能混合可變和不可變 +fn main() { + let mut data = vec![1, 2, 3]; + + // 不可變引用 + let read_ref = &data; + println!("Read: {:?}", read_ref); + + // 不可變引用存在時不能建立可變引用 + // let write_ref = &mut data; // 錯誤! + // 不能可變借用 `data` + // 因為它已經被不可變借用 + + // read_ref 不再使用後 + let write_ref = &mut data; + write_ref.push(4); + println!("Write: {:?}", write_ref); + + // 現在可以再次不可變借用 + let new_read = &data; + println!("New read: {:?}", new_read); +} +``` + + +## 借用檢查器範例 + +Rust 編譯器(借用檢查器)確保所有借用規則都被遵循。讓我們看看常見場景。 + +### 範例 1:函式中的借用 + + +```python !! py +# Python:隱式傳遞引用 +def double_items(items): + for i in range(len(items)): + items[i] *= 2 + +def print_items(items): + print(items) + +numbers = [1, 2, 3] +print_items(numbers) # 讀取 +double_items(numbers) # 寫入 +print_items(numbers) # 再次讀取 - [2, 4, 6] +``` + +```rust !! rs +// Rust:函式中的顯式借用 +fn main() { + let mut numbers = vec![1, 2, 3]; + + // 不可變借用用於讀取 + print_items(&numbers); + + // 可變借用用於寫入 + double_items(&mut numbers); + + // 再次不可變借用 + print_items(&numbers); + + // main 仍然擁有原始資料 + println!("Owned: {:?}", numbers); +} + +fn double_items(items: &mut Vec) { + for item in items.iter_mut() { + *item *= 2; + } +} + +fn print_items(items: &Vec) { + println!("{:?}", items); +} +``` + + +### 範例 2:迴圈中的借用 + + +```python !! py +# Python:可以自由收集和修改 +data = ["hello", "world", "rust"] + +# 收集引用 +first_letters = [word[0] for word in data] + +# 修改原始資料 +data[0] = data[0].upper() + +print(first_letters) # ['h', 'w', 'r'] +print(data) # ['HELLO', 'world', 'rust'] +``` + +```rust !! rs +// Rust:迴圈中借用需要小心 +fn main() { + let mut data = vec!["hello", "world", "rust"]; + + // 收集不可變引用 + let first_letters: Vec<&str> = data.iter() + .map(|s| &s[0..1]) + .collect(); + + println!("{:?}", first_letters); + + // 引用存在時不能修改 + // data[0] = "HELLO"; // 錯誤! + // 不能可變借用 `data` + // 因為它已經被不可變借用 + + // first_letters 被使用後,可以修改 + drop(first_letters); // 顯式丟棄 + data[0] = "HELLO"; + + println!("{:?}", data); +} +``` + + +### 範例 3:返回引用 + + +```python !! py +# Python:可以自由返回引用 +def get_first(items): + return items[0] + +data = [10, 20, 30] +first = get_first(data) +print(first) # 10 + +# 函式返回後 +data[0] = 100 +print(first) # 仍然是 10(不再是 data[0] 的引用) +``` + +```rust !! rs +// Rust:生命週期確保有效性 +fn main() { + let data = vec![10, 20, 30]; + + // 從 data 借用 + let first = get_first(&data); + println!("First: {}", first); + + // 借用時不能修改 + // data[0] = 100; // 錯誤! + + println!("Data: {:?}", data); +} + +// 生命週期註解:返回的引用 +// 至少與輸入參數一樣長 +fn get_first<'a>(items: &'a Vec) -> &'a i32 { + &items[0] +} + +// 這不會編譯 - 懸垂引用! +// fn get_first_bad() -> &i32 { +// let value = 42; +// &value // 錯誤:返回指向已釋放資料的引用 +// } +``` + + +## 懸垂引用預防 + +Rust 的借用檢查器確保引用永遠不會指向無效的記憶體。 + +### 什麼是懸垂引用? + + +```python !! py +# Python:垃圾回收器防止懸垂引用 +def create_ref(): + data = [1, 2, 3] + return data # 返回引用,物件保持存活 + +ref = create_ref() +print(ref) # [1, 2, 3] - Python 保持它存活 + +# 即使使用顯式引用 +import weakref + +class MyClass: + def __init__(self, value): + self.value = value + +obj = MyClass(42) +weak_ref = weakref.ref(obj) + +del obj # 刪除原始物件 +print(weak_ref()) # None - 物件已被刪除 +``` + +```rust !! rs +// Rust:編譯時防止懸垂引用 +fn main() { + // 這可行 - 引用有效資料 + let data = vec![1, 2, 3]; + let first = &data[0]; + println!("First: {}", first); + + // 這不會編譯 - 懸垂引用! + // let bad_ref = create_dangling(); + // println!("{:?}", bad_ref); +} + +// 這個函式不會編譯! +// fn create_dangling() -> &i32 { +// let value = 42; +// &value // 錯誤:返回指向已釋放資料的引用 +// } + +// 正確版本:返回擁有的值 +fn create_owned() -> i32 { + let value = 42; + value // 所有权轉移 +} + +// 或者返回對存在事物的引用 +fn create_valid<'a>(data: &'a Vec) -> &'a i32 { + &data[0] +} +``` + + +### 基於作用域的有效性 + + +```python !! py +# Python:引用一直工作直到 GC 回收 +def process(): + local_data = [1, 2, 3] + return local_data[:] # 返回副本 + +result = process() +print(result) # [1, 2, 3] - 正常工作 + +# 原始 local_data 消失了,但我們有副本 +``` + +```rust !! rs +// Rust:引用與作用域綁定 +fn main() { + // 引用的生命週期必須短於所有者 + let result; + { + let data = vec![1, 2, 3]; + result = data.len(); // 複製值,不是引用 + } // data 在這裡被丟棄 + + println!("Length: {}", result); // 正常工作 + + // 這樣不行: + // let ref_result; + // { + // let data = vec![1, 2, 3]; + // ref_result = &data[0]; // 錯誤! + // } // data 被丟棄,ref_result 將懸垂 + // println!("{:?}", ref_result); +} +``` + + +## 多種借用場景 + +讓我們探索常見的借用模式和陷阱。 + +### 場景 1:迭代器失效 + + +```python !! py +# Python:迭代時修改(有 bug!) +numbers = [1, 2, 3, 4, 5] + +# 這會跳過元素! +for i, num in enumerate(numbers): + if num % 2 == 0: + numbers.pop(i) # 迭代時修改 + +print(numbers) # [1, 3, 5] - 2 被刪除了,但 4 還在! +``` + +```rust !! rs +// Rust:編譯時保護 +fn main() { + let mut numbers = vec![1, 2, 3, 4, 5]; + + // 迭代時不能可變借用 + // for num in numbers.iter() { + // if num % 2 == 0 { + // numbers.push(10); // 錯誤! + // 不能可變借用 `numbers` + // 因為它已經被不可變借用 + // } + // } + + // 正確方法:先收集索引 + let to_remove: Vec = numbers.iter() + .enumerate() + .filter(|(_, &num)| num % 2 == 0) + .map(|(i, _)| i) + .collect(); + + // 然後修改(反向以保持索引有效) + for i in to_remove.into_iter().rev() { + numbers.remove(i); + } + + println!("{:?}", numbers); // [1, 3, 5] + + // 或使用 retain() + let mut numbers = vec![1, 2, 3, 4, 5]; + numbers.retain(|&x| x % 2 != 0); + println!("{:?}", numbers); // [1, 3, 5] +} +``` + + +### 場景 2:結構體字段借用 + + +```python !! py +# Python:可以存取多個字段 +class Point: + def __init__(self, x, y): + self.x = x + self.y = y + +p = Point(10, 20) +x_ref = p.x +y_ref = p.y + +print(x_ref, y_ref) # 10 20 +p.x = 30 +print(x_ref) # 10(未更新) +``` + +```rust !! rs +// Rust:借用結構體字段 +struct Point { + x: i32, + y: i32, +} + +fn main() { + let mut p = Point { x: 10, y: 20 }; + + // 多個不可變借用不同字段 + let x_ref = &p.x; + let y_ref = &p.y; + println!("X: {}, Y: {}", x_ref, y_ref); + + // 借用存在時不能可變借用 + // p.x = 30; // 錯誤! + + // 借用結束後可以修改 + let x_mut = &mut p.x; + *x_mut = 30; + println!("X: {}", p.x); + + // 可以可變借用多個字段(不重疊) + { + let x_ref = &mut p.x; + *x_ref += 1; + } // x_ref 在這裡結束 + { + let y_ref = &mut p.y; + *y_ref += 1; + } // y_ref 在這裡結束 + + println!("Point: ({}, {})", p.x, p.y); +} +``` + + +### 場景 3:條件借用 + + +```python !! py +# Python:條件修改 +data = [1, 2, 3, 4, 5] + +def maybe_modify(items, should_modify): + if should_modify: + items.append(6) + return items + +result = maybe_modify(data, True) +print(result) # [1, 2, 3, 4, 5, 6] + +result = maybe_modify(data, False) +print(result) # [1, 2, 3, 4, 5, 6] +``` + +```rust !! rs +// Rust:條件借用 +fn main() { + let mut data = vec![1, 2, 3, 4, 5]; + + // 條件修改 + let result = maybe_modify(&mut data, true); + println!("{:?}", result); // [1, 2, 3, 4, 5, 6] + + let result = maybe_modify(&mut data, false); + println!("{:?}", result); // [1, 2, 3, 4, 5, 6] +} + +fn maybe_modify(items: &mut Vec, should_modify: bool) -> &Vec { + if should_modify { + items.push(6); + } + items // 返回引用 +} +``` + + +## 比較:Python 引用 vs Rust 借用 + +### 概念差異 + +| 方面 | Python | Rust | +|--------|--------|------| +| **記憶體安全** | 執行時(GC) | 編譯時(借用檢查器) | +| **可變引用** | 無限 | 同一時間只能有一個 | +| **混合讀/寫** | 允許 | 不允許同時進行 | +| **懸垂引用** | 可能(weakref) | 編譯時預防 | +| **效能** | GC 開銷 | 零成本 | +| **執行緒安全** | 開發者責任 | 編譯器保證 | + +### 實際影響 + + +```python !! py +# Python:一切都是引用 +def modify_shared_data(data1, data2): + # 兩者都可以修改共享狀態 + data1.append(1) + data2.append(2) + +shared = [] +modify_shared_data(shared, shared) +print(shared) # [1, 2] + +# 在並發程式碼中,這會導致資料競爭! +# Python 依賴 GIL 和開發者紀律 +``` + + + +```rust !! rs +// Rust:編譯時預防問題 +fn main() { + let mut shared = vec![]; + + // 不能作為可變引用傳遞兩次 + // modify_shared_data(&mut shared, &mut shared); + // 錯誤:使用了已移動的值:`shared` + + // 必須傳遞一次,或使用內部可變性 + modify_once(&mut shared); + modify_once(&mut shared); + + println!("{:?}", shared); // [1, 2] +} + +fn modify_once(data: &mut Vec) { + data.push(1); +} +``` + + +## 最佳實踐 + +### DO:使用引用進行唯讀存取 + + +```rust !! rs +fn main() { + let data = vec![1, 2, 3, 4, 5]; + + // 使用 & 進行唯讀 + calculate_sum(&data); + calculate_sum(&data); // 可以多次呼叫 + print_first(&data); +} + +fn calculate_sum(numbers: &Vec) -> i32 { + numbers.iter().sum() +} + +fn print_first(numbers: &Vec) { + println!("First: {}", numbers[0]); +} +``` + + +### DO:在最小必要範圍內借用 + + +```rust !! rs +fn main() { + let mut data = vec![1, 2, 3, 4, 5]; + + // 只在需要時借用 + { + let first = &data[0]; + println!("First: {}", first); + } // 借用在這裡結束 + + // 現在可以修改 + data.push(6); + + // 另一個借用 + { + let last = &data[data.len() - 1]; + println!("Last: {}", last); + } +} +``` + + +### DON'T:建立不必要的可變借用 + + +```rust !! rs +fn main() { + let data = vec![1, 2, 3, 4, 5]; + + // 不要:不可變足夠時使用可變 + // print_items(&mut data); // 不必要! + + // 應該:唯讀使用不可變 + print_items(&data); +} + +fn print_items(items: &Vec) { + println!("{:?}", items); +} +``` + + +## 常見借用陷阱 + +### 陷阱 1:忘記丟棄借用 + + +```python !! py +# Python:沒有問題 +data = [1, 2, 3] + +ref = data +print(ref) + +data.append(4) # 正常工作 +print(ref) +``` + +```rust !! rs +// Rust:借用持續到最後一次使用 +fn main() { + let mut data = vec![1, 2, 3]; + + let ref_vec = &data; + println!("{:?}", ref_vec); + + // ref_vec "活著"時不能修改 + // data.push(4); // 錯誤! + + // 儘管我們打印了它,編譯器認為它仍然被使用 + // 解決方案:顯式丟棄或使用更小的作用域 + + // 顯式丟棄 + drop(ref_vec); + data.push(4); // 現在可以工作! + + println!("{:?}", data); +} +``` + + +### 陷阱 2:收集引用 + + +```rust !! rs +fn main() { + let mut data = vec![1, 2, 3, 4, 5]; + + // 收集引用 + let evens: Vec<&i32> = data.iter() + .filter(|&&x| x % 2 == 0) + .collect(); + + println!("{:?}", evens); + + // evens 持有引用時不能修改 + // data.push(6); // 錯誤! + + // 必須先丟棄 evens + drop(evens); + data.push(6); + + println!("{:?}", data); +} +``` + + +### 陷阱 3:閉包借用 + + +```rust !! rs +fn main() { + let data = vec![1, 2, 3, 4, 5]; + + // 閉包借用 data + let sum = || { + data.iter().sum::() + }; + + println!("Sum: {}", sum()); + + // 閉包存在時不能修改 + // let mut data_mut = data; + // data_mut.push(6); // 錯誤! + + // 閉包使用後可以消費 + let mut data_mut = data; + data_mut.push(6); + + println!("{:?}", data_mut); +} +``` + + +## 進階:智慧指標和借用 + +對於更複雜的場景,Rust 提供具有內部可變性的智慧指標。 + + +```python !! py +# Python:到處都是共享可變狀態 +counter = [0] + +def increment(): + counter[0] += 1 + +increment() +increment() +print(counter[0]) # 2 +``` + +```rust !! rs +use std::rc::Rc; +use std::cell::RefCell; + +fn main() { + // 使用 RefCell 進行執行時借用檢查 + let counter = Rc::new(RefCell::new(0)); + + // 複製 Rc(引用計數增加) + let counter_ref1 = Rc::clone(&counter); + let counter_ref2 = Rc::clone(&counter); + + // 執行時多個可變引用! + // 如果有衝突會 panic + *counter_ref1.borrow_mut() += 1; + *counter_ref2.borrow_mut() += 1; + + println!("Count: {}", counter.borrow()); // 2 + + // 用於具有執行時檢查的共享所有權 + // 效率較低但更靈活 +} +``` + + +## 練習 + +### 練習 1:修復借用檢查器 + + +```rust !! rs +// 修復這段程式碼中的錯誤 + +fn main() { + let mut numbers = vec![1, 2, 3, 4, 5]; + + let first = &numbers[0]; + let last = &numbers[numbers.len() - 1]; + + numbers.push(6); // 錯誤:為什麼? + + println!("First: {}, Last: {}", first, last); + println!("{:?}", numbers); +} + +// 提示:調整順序或使用作用域 +``` + + +### 練習 2:實作函式 + + +```rust !! rs +// 使用借用實作這個函式 + +fn main() { + let numbers = vec![10, 20, 30, 40, 50]; + let largest = find_largest(&numbers); + println!("Largest: {}", largest); // 應該列印 50 +} + +// TODO:實作 find_largest +// 它應該: +// 1. 接受對 Vec 的引用 +// 2. 返回最大值 +// 3. 使用借用(不獲取所有權) +fn find_largest(numbers: &Vec) -> i32 { + // 你的程式碼 + unimplemented!() +} +``` + + +### 練習 3:結構體中的借用 + + +```rust !! rs +// 實作一個持有引用的結構體 + +fn main() { + let data = vec![1, 2, 3, 4, 5]; + + // 建立一個引用 data 的 DataView + let view = create_view(&data); + + println!("First: {}", view.first()); // 1 + println!("Last: {}", view.last()); // 5 + + println!("{:?}", data); // 仍然可存取 +} + +// TODO:定義帶有引用的 DataView 結構體 +// 提示:你需要生命週期註解 +struct DataView { + // 你的字段 +} + +fn create_view(data: &Vec) -> DataView { + // 你的程式碼 + unimplemented!() +} + +// 為 DataView 實作方法 +// impl DataView { +// fn first(&self) -> i32 { ... } +// fn last(&self) -> i32 { ... } +// } +``` + + +## 總結 + +在本模組中,你學習了: + +### 核心概念 +- **借用**:Rust 在不轉移所有權的情況下進行暫時存取的系統 +- **不可變引用(`&T`)**:同時允許多個讀取者 +- **可變引用(`&mut T`)**:獨占存取,同一時間只能有一個 +- **借用檢查器**:編譯時強制執行記憶體安全 + +### 借用規則 +1. 多個不可變引用可以 +2. 一個可變引用可以 +3. 不能混合可變和不可變引用 +4. 引用必須始終有效 + +### Python vs Rust +- **Python**:帶有 GC 的執行時記憶體管理,靈活的引用 +- **Rust**:編譯時保證,零成本安全 + +### 最佳實踐 +- 使用 `&T` 進行唯讀存取 +- 只在需要變異時使用 `&mut T` +- 最小化借用範圍 +- 需要時顯式丟棄引用 +- 對複雜共享場景使用智慧指標(Rc、RefCell) + +### 下一步 +在[模組 4:結構體和枚舉](/docs/py2rust/module-04-structs-enums)中,我們將探索 Rust 的型別系統以及如何使用結構體和枚舉定義自訂資料型別,並與 Python 的類別和枚舉進行比較。 + +--- + +**練習**:嘗試用 Rust 重寫一些使用引用的 Python 程式碼,注意借用規則。借用檢查器會引導你採用安全的模式! diff --git a/content/docs/py2rust/module-04-control-flow.mdx b/content/docs/py2rust/module-04-control-flow.mdx new file mode 100644 index 0000000..f58fc76 --- /dev/null +++ b/content/docs/py2rust/module-04-control-flow.mdx @@ -0,0 +1,1002 @@ +--- +title: "Module 4: Control Flow" +description: "Master Rust's control flow constructs by comparing them with Python. Learn about if expressions, loops, pattern matching, and more." +--- + +# Module 4: Control Flow + +Control flow is where Rust really shines compared to Python. While Python focuses on readability with simple constructs, Rust provides powerful, expressive control flow that prevents entire classes of bugs at compile time. + +## Learning Objectives + +By the end of this module, you'll understand: +- ✅ `if` as expressions, not statements +- ✅ Loop constructs: `loop`, `while`, and `for` +- ✅ Pattern matching with `match` (beyond what you saw in Module 3) +- ✅ `if let` and `while let` patterns +- ✅ `break` and `continue` with values +- ✅ Loop labels for nested loops +- ✅ Common control flow patterns + +## if Expressions + +In Python, `if` is a statement. In Rust, `if` is an expression that returns a value! + + +```python !! py +# Python: if is a statement +x = 10 + +if x > 0: + result = "positive" +elif x < 0: + result = "negative" +else: + result = "zero" + +# Cannot use if directly in expressions +# value = "positive" if x > 0 else "negative" # Ternary, but limited +``` + +```rust !! rs +// Rust: if is an expression +let x = 10; + +let result = if x > 0 { + "positive" +} else if x < 0 { + "negative" +} else { + "zero" +}; // Note the semicolon + +// Can use anywhere a value is expected +println!("Result: {}", result); + +// No ternary operator needed - if is the ternary! +let status = if x >= 18 { "adult" } else { "minor" }; +``` + + +### Type Consistency + + +```python !! py +# Python: Can return different types +x = 5 +result = x if x > 0 else "error" # int or str +``` + +```rust !! rs +// Rust: All branches must return same type +let x = 5; + +// This works - all &str +let result = if x > 0 { + "positive" +} else { + "zero" +}; + +// ERROR: mixed types +// let result = if x > 0 { +// 42 +// } else { +// "error" // ERROR: expected integer, found &str +// }; + +// Must use enum for different types +enum MaybeNumber { + Number(i32), + Error(&'static str), +} + +let result = if x > 0 { + MaybeNumber::Number(42) +} else { + MaybeNumber::Error("error") +}; +``` + + +### No Truthy/Falsy Values + + +```python !! py +# Python: Truthy and falsy values +x = 5 +if x: # Truthy + pass + +s = "hello" +if s: # Truthy (non-empty string) + pass + +items = [] +if items: # Falsy (empty list) + pass + +numbers = [0, 1, 2] +if numbers: # Truthy (non-empty list, even with 0s) + pass +``` + +```rust !! rs +// Rust: Must use explicit boolean +let x = 5; +if x != 0 { // Must compare explicitly + // ... +} + +let s = "hello"; +if !s.is_empty() { // Must check explicitly + // ... +} + +let items: Vec = vec![]; +if !items.is_empty() { // Must check explicitly + // ... +} + +// No implicit truthiness +// if x { // ERROR: expected bool, found integer +// } +``` + + +## Loops + +Rust has three loop constructs: `loop`, `while`, and `for`. Each has specific use cases. + +### loop - The Infinite Loop + + +```python !! py +# Python: while True for infinite loops +counter = 0 +while True: + counter += 1 + if counter >= 10: + break + print(counter) + +# With value extraction +result = None +while True: + value = get_value() + if value is not None: + result = value + break +``` + +```rust !! rs +// Rust: loop for infinite loops (explicit and powerful) +let mut counter = 0; +loop { + counter += 1; + if counter >= 10 { + break; + } + println!("{}", counter); +} + +// Can break with a value! +let result = loop { + let value = get_value(); + if value.is_some() { + break value; + } +}; // result is Option + +// loop can return values +let x = loop { + break 5; // break with value +}; // x = 5 + +// Can use break in any expression +let y = loop { + counter += 1; + if counter > 5 { + break counter * 2; + } +}; // y = 12 +``` + + +### while Loops + + +```python !! py +# Python: while loop +counter = 0 +while counter < 10: + print(counter) + counter += 1 + +# Can't return values +# result = while counter < 10: # SyntaxError +# counter += 1 +``` + +```rust !! rs +// Rust: while loop +let mut counter = 0; +while counter < 10 { + println!("{}", counter); + counter += 1; +} + +// while cannot return values (use loop or for) +// while is just syntactic sugar for loop with if + +// Desugaring of while: +// loop { +// if condition { +// // body +// } else { +// break; +// } +// } +``` + + +### for Loops + + +```python !! py +# Python: for loop over iterables +items = [1, 2, 3, 4, 5] + +for item in items: + print(item) + +# With enumerate +for index, item in enumerate(items): + print(f"{index}: {item}") + +# Range +for i in range(10): + print(i) + +for i in range(5, 10): + print(i) + +# Reversed +for item in reversed(items): + print(item) +``` + +```rust !! rs +// Rust: for loop over iterators +let items = vec![1, 2, 3, 4, 5]; + +for item in &items { + println!("{}", item); +} + +// With enumerate +for (index, item) in items.iter().enumerate() { + println!("{}: {}", index, item); +} + +// Ranges +for i in 0..10 { + println!("{}", i); +} + +for i in 5..10 { + println!("{}", i); +} + +// Inclusive range +for i in 0..=10 { + println!("{}", i); +} + +// Reversed (requires rev()) +for item in items.iter().rev() { + println!("{}", item); +} +``` + + +### Iterators and Ownership + + +```python !! py +# Python: Iterators don't affect ownership +items = [1, 2, 3] + +for item in items: + print(item) + +# items is still accessible +print(items) # [1, 2, 3] +``` + +```rust !! rs +// Rust: Careful with ownership +let items = vec![1, 2, 3]; + +// Borrow each item +for item in &items { + println!("{}", item); +} +// items is still accessible + +// Consume the collection +for item in items { + println!("{}", item); +} +// items is no longer accessible - moved! + +// To iterate and keep collection, use &items +``` + + +## Loop Control + +### break and continue + + +```python !! py +# Python: break and continue +for i in range(10): + if i == 3: + continue + if i == 7: + break + print(i) +# Prints: 0, 1, 2, 4, 5, 6 +``` + +```rust !! rs +// Rust: break and continue +for i in 0..10 { + if i == 3 { + continue; + } + if i == 7 { + break; + } + println!("{}", i); +} +// Prints: 0, 1, 2, 4, 5, 6 +``` + + +### Loop Labels + + +```python !! py +# Python: Breaking out of nested loops requires flags +found = False +for i in range(5): + for j in range(5): + if i == 2 and j == 3: + found = True + break + if found: + break + +# Or use exceptions (not recommended) +try: + for i in range(5): + for j in range(5): + if i == 2 and j == 3: + raise StopIteration +except StopIteration: + pass +``` + +```rust !! rs +// Rust: Loop labels for breaking out of nested loops +'outer: for i in 0..5 { + for j in 0..5 { + if i == 2 && j == 3 { + break 'outer; // Break out of outer loop + } + } +} + +// Can continue with labels too +'outer: for i in 0..5 { + for j in 0..5 { + if i == 2 && j == 3 { + continue 'outer; // Continue outer loop + } + println!("{} {}", i, j); + } +} + +// Can break loop with values +let result = 'search: loop { + for i in 0..10 { + for j in 0..10 { + if i * j > 50 { + break 'search (i, j); + } + } + } +}; +println!("Found: {:?}", result); +``` + + +### break with Values + + +```python !! py +# Python: Can't break with values directly +# result = None +# while True: +# value = calculate() +# if value > 100: +# result = value # Must use variable +# break +``` + +```rust !! rs +// Rust: Can break with values +let result = loop { + let value = calculate(); + if value > 100 { + break value; // Break with the value + } +}; + +// Works with labeled loops too +let (x, y) = 'outer: loop { + for i in 0..10 { + for j in 0..10 { + if i * j > 50 { + break 'outer (i, j); + } + } + } +}; + +// Common pattern: searching +let found = items.iter().enumerate().find(|&(i, &item)| { + item == target +}); +``` + + +## Pattern Matching Recap + +You learned about `match` in Module 3, but let's review with more control flow context. + + +```python !! py +# Python: Multiple if-elif-else +value = 5 + +if value == 1: + result = "one" +elif value == 2: + result = "two" +elif value == 3: + result = "three" +elif value > 3 and value < 10: + result = "small" +else: + result = "other" +``` + +```rust !! rs +// Rust: Pattern matching with match +let value = 5; + +let result = match value { + 1 => "one", + 2 => "two", + 3 => "three", + 4..=9 => "small", // Range pattern + _ => "other", +}; + +// Match with guards +let result = match value { + x if x < 0 => "negative", + x if x > 100 => "large", + x => "normal", +}; +``` + + +## if let and while let + +These are convenient patterns for single-match scenarios. + + +```python !! py +# Python: Check and extract +value = get_optional() + +if value is not None: + result = value + # Use result +else: + # Handle None + pass + +# Or more verbose +try: + result = value.unwrap() +except AttributeError: + # Handle None + pass +``` + +```rust !! rs +// Rust: if let for single pattern matching +let value: Option = Some(5); + +// if let - matches one pattern, ignores others +if let Some(x) = value { + println!("Got: {}", x); +} else { + println!("Got None"); +} + +// Equivalent to: +// match value { +// Some(x) => { println!("Got: {}", x); } +// None => { println!("Got None"); } +// } + +// Can use if let in expressions +let result = if let Some(x) = value { + x * 2 +} else { + 0 +}; +``` + + + +```python !! py +# Python: Pattern matching in loops +values = [Some(1), None, Some(2), None, Some(3)] + +for value in values: + if value is not None: + print(value) # Only process Some values +``` + +```rust !! rs +// Rust: while let for repeated pattern matching +let values = vec![Some(1), None, Some(2), None, Some(3)]; + +// while let - loops while pattern matches +let mut iter = values.into_iter(); +while let Some(value) = iter.next() { + println!("{}", value); +} + +// More realistic example: processing queue +let mut queue = vec![1, 2, 3, 4, 5]; + +while let Some(item) = queue.pop() { + println!("Processing: {}", item); +} +``` + + +## Common Control Flow Patterns + +### Early Returns + + +```python !! py +# Python: Early returns for clarity +def process(value): + if value is None: + return None + + if value < 0: + return None + + # Process value + return value * 2 +``` + +```rust !! rs +// Rust: Early returns are idiomatic +fn process(value: Option) -> Option { + let value = value?; // Early return if None + + if value < 0 { + return None; + } + + // Process value + Some(value * 2) +} + +// Or with guards +fn process_guard(value: Option) -> Option { + match value { + Some(v) if v >= 0 => Some(v * 2), + _ => None, + } +} +``` + + +### Iteration Patterns + + +```python !! py +# Python: Common iteration patterns + +# Filter +items = [1, 2, 3, 4, 5] +filtered = [x for x in items if x % 2 == 0] + +# Map +doubled = [x * 2 for x in items] + +# Find +found = next((x for x in items if x > 3), None) + +# All/Any +all_positive = all(x > 0 for x in items) +any_large = any(x > 100 for x in items) + +# Sum/Count +total = sum(items) +count = len([x for x in items if x > 2]) +``` + +```rust !! rs +// Rust: Iterator methods are powerful +let items = vec![1, 2, 3, 4, 5]; + +// Filter +let filtered: Vec = items.iter() + .filter(|&&x| x % 2 == 0) + .collect(); + +// Map +let doubled: Vec = items.iter() + .map(|&x| x * 2) + .collect(); + +// Find +let found = items.iter().find(|&&x| x > 3); + +// All/Any +let all_positive = items.iter().all(|&x| x > 0); +let any_large = items.iter().any(|&x| x > 100); + +// Sum/Count +let total: i32 = items.iter().sum(); +let count = items.iter().filter(|&&x| x > 2).count(); + +// Chaining +let result: Vec = items.iter() + .filter(|&&x| x % 2 == 0) + .map(|&x| x * 2) + .take(3) + .collect(); +``` + + +### Error Handling with Control Flow + + +```python !! py +# Python: Exception-based error handling +def divide(a, b): + try: + return a / b + except ZeroDivisionError: + return None + +# Using +result = divide(10, 2) +if result is not None: + print(result) +``` + +```rust !! rs +// Rust: Result-based error handling +fn divide(a: f64, b: f64) -> Option { + if b == 0.0 { + None + } else { + Some(a / b) + } +} + +// Using +if let Some(result) = divide(10.0, 2.0) { + println!("{}", result); +} + +// Or with ? +fn divide_safe(a: f64, b: f64) -> Result { + if b == 0.0 { + Err("Division by zero".to_string()) + } else { + Ok(a / b) + } +} + +fn process_division(a: f64, b: f64) -> Result { + let result = divide_safe(a, b)?; // Early return on Err + Ok(result * 2.0) +} +``` + + +## Putting It All Together + +Let's build a complete example: + + +```python !! py +def process_numbers(numbers): + """Process numbers with various control flow.""" + if not numbers: + return None + + results = [] + + for num in numbers: + # Skip negatives + if num < 0: + continue + + # Process positive numbers + if num == 0: + results.append("zero") + elif num % 2 == 0: + results.append(f"even: {num}") + else: + results.append(f"odd: {num}") + + # Stop if we find a large number + if num > 100: + results.append("found large") + break + + return results + +# Main execution +data = [1, 2, -3, 4, 0, 5, 150, 6] +output = process_numbers(data) +print(output) +``` + +```rust !! rs +fn process_numbers(numbers: &[i32]) -> Option> { + if numbers.is_empty() { + return None; + } + + let mut results = Vec::new(); + + for &num in numbers { + // Skip negatives + if num < 0 { + continue; + } + + // Process positive numbers + let result = match num { + 0 => "zero".to_string(), + n if n % 2 == 0 => format!("even: {}", n), + n => format!("odd: {}", n), + }; + + results.push(result); + + // Stop if we find a large number + if num > 100 { + results.push("found large".to_string()); + break; + } + } + + Some(results) +} + +fn main() { + let data = vec![1, 2, -3, 4, 0, 5, 150, 6]; + + if let Some(output) = process_numbers(&data) { + println!("{:?}", output); + } + + // Using iterators + let data2 = vec![1, 2, 3, 4, 5]; + + let sum: i32 = data2.iter() + .filter(|&&x| x > 2) + .map(|&x| x * 2) + .sum(); + + println!("Sum: {}", sum); + + // Loop with value + let counter = 0; + let result = loop { + if counter >= 10 { + break counter * 2; + } + }; + println!("Result: {}", result); +} +``` + + +## Performance Considerations + + +```python !! py +# Python: For loops have overhead +# Each iteration involves Python interpreter + +# List comprehension is faster +result = [x * 2 for x in range(1000000)] + +# Generator is memory efficient +result = (x * 2 for x in range(100000000)) +``` + +```rust !! rs +// Rust: Loops compile to efficient machine code +// Iterators are zero-cost abstractions + +// All compile to similar efficient code +let mut sum = 0; +for i in 0..1000000 { + sum += i; +} + +// Iterator version (often as fast or faster) +let sum: i32 = (0..1000000).sum(); + +// Both compile to very efficient assembly +// No runtime overhead for iterators +``` + + +## Summary + +In this module, you learned: +- ✅ `if` is an expression that returns values +- ✅ `loop` for infinite loops with `break` values +- ✅ `while` for conditional loops +- ✅ `for` for iteration with iterators +- ✅ Loop labels for nested control flow +- ✅ `if let` and `while let` for pattern matching +- ✅ No truthy/falsy - explicit conditions only +- ✅ Powerful iterator methods + +## Key Differences from Python + +1. **if is an expression**: Returns values, not just statements +2. **No truthy/falsy**: Must use explicit boolean conditions +3. **loop construct**: More explicit than `while True` +4. **break with values**: Loops can return values +5. **Loop labels**: Control nested loops explicitly +6. **Pattern matching**: `match` is more powerful than `if-elif` +7. **Iterator methods**: Rich functional programming capabilities + +## Exercises + +### Exercise 1: FizzBuzz + +Write FizzBuzz using Rust control flow: +- Print "Fizz" for multiples of 3 +- Print "Buzz" for multiples of 5 +- Print "FizzBuzz" for multiples of both +- Use `match` for the logic + +
+Solution + +```rust +fn fizzbuzz(n: i32) -> String { + match (n % 3, n % 5) { + (0, 0) => "FizzBuzz".to_string(), + (0, _) => "Fizz".to_string(), + (_, 0) => "Buzz".to_string(), + _ => n.to_string(), + } +} + +fn main() { + for i in 1..=20 { + println!("{}", fizzbuzz(i)); + } +} +``` + +
+ +### Exercise 2: Find Maximum + +Find the maximum value in a vector: +- Use a `loop` instead of `for` +- Return `None` for empty vectors +- Use `break` with a value + +
+Solution + +```rust +fn find_max(numbers: &[i32]) -> Option { + if numbers.is_empty() { + return None; + } + + let mut iter = numbers.iter(); + let mut max = *iter.next()?; + + loop { + match iter.next() { + Some(&num) if num > max => max = num, + Some(_) => {} + None => break max, + } + } +} + +fn main() { + let numbers = vec![3, 1, 4, 1, 5, 9, 2, 6]; + if let Some(max) = find_max(&numbers) { + println!("Maximum: {}", max); + } +} +``` + +
+ +### Exercise 3: Process Until + +Process items until a condition is met: +- Use `while let` to pop from a vector +- Stop when you find a number > 100 +- Return the sum of processed numbers + +
+Solution + +```rust +fn process_until(mut numbers: Vec) -> i32 { + let mut sum = 0; + + while let Some(num) = numbers.pop() { + if num > 100 { + break; + } + sum += num; + } + + sum +} + +fn main() { + let numbers = vec![1, 2, 3, 150, 4, 5]; + let sum = process_until(numbers); + println!("Sum: {}", sum); // 9 (1+2+3+3) +} +``` + +
+ +## Next Steps + +Now that you understand control flow: +1. **[Module 5: Functions & Closures](./module-05-functions)** - Learn about functions as first-class citizens +2. Practice control flow patterns +3. Explore iterator methods + +--- + +**Next:** [Module 5 - Functions & Closures](./module-05-functions) → diff --git a/content/docs/py2rust/module-04-control-flow.zh-cn.mdx b/content/docs/py2rust/module-04-control-flow.zh-cn.mdx new file mode 100644 index 0000000..72ce1b0 --- /dev/null +++ b/content/docs/py2rust/module-04-control-flow.zh-cn.mdx @@ -0,0 +1,1002 @@ +--- +title: "模块 4:控制流" +description: "通过与 Python 对比来掌握 Rust 的控制流结构。学习 if 表达式、循环、模式匹配等。" +--- + +# 模块 4:控制流 + +在控制流方面,Rust 相比 Python 展现出了真正的优势。Python 专注于简单的可读性结构,而 Rust 提供了强大、表达力强的控制流,能够在编译时防止整类错误。 + +## 学习目标 + +完成本模块后,你将理解: +- ✅ `if` 作为表达式而非语句 +- ✅ 循环结构:`loop`、`while` 和 `for` +- ✅ 使用 `match` 进行模式匹配(超越模块 3 的内容) +- ✅ `if let` 和 `while let` 模式 +- ✅ 带值的 `break` 和 `continue` +- ✅ 嵌套循环的标签 +- ✅ 常见的控制流模式 + +## if 表达式 + +在 Python 中,`if` 是语句。在 Rust 中,`if` 是可以返回值的表达式! + + +```python !! py +# Python: if 是语句 +x = 10 + +if x > 0: + result = "positive" +elif x < 0: + result = "negative" +else: + result = "zero" + +# 不能在表达式中直接使用 if +# value = "positive" if x > 0 else "negative" # 三元表达式,但功能有限 +``` + +```rust !! rs +// Rust: if 是表达式 +let x = 10; + +let result = if x > 0 { + "positive" +} else if x < 0 { + "negative" +} else { + "zero" +}; // 注意分号 + +// 可以在任何需要值的地方使用 +println!("Result: {}", result); + +// 不需要三元运算符 - if 本身就是三元表达式! +let status = if x >= 18 { "adult" } else { "minor" }; +``` + + +### 类型一致性 + + +```python !! py +# Python: 可以返回不同的类型 +x = 5 +result = x if x > 0 else "error" # int 或 str +``` + +```rust !! rs +// Rust: 所有分支必须返回相同类型 +let x = 5; + +// 这是可以的 - 都是 &str +let result = if x > 0 { + "positive" +} else { + "zero" +}; + +// 错误:混合类型 +// let result = if x > 0 { +// 42 +// } else { +// "error" // 错误:期望整数,发现 &str +// }; + +// 对于不同类型必须使用枚举 +enum MaybeNumber { + Number(i32), + Error(&'static str), +} + +let result = if x > 0 { + MaybeNumber::Number(42) +} else { + MaybeNumber::Error("error") +}; +``` + + +### 没有真值/假值 + + +```python !! py +# Python: 真值和假值 +x = 5 +if x: # 真值 + pass + +s = "hello" +if s: # 真值(非空字符串) + pass + +items = [] +if items: # 假值(空列表) + pass + +numbers = [0, 1, 2] +if numbers: # 真值(非空列表,即使包含 0) + pass +``` + +```rust !! rs +// Rust: 必须使用显式的布尔值 +let x = 5; +if x != 0 { // 必须显式比较 + // ... +} + +let s = "hello"; +if !s.is_empty() { // 必须显式检查 + // ... +} + +let items: Vec = vec![]; +if !items.is_empty() { // 必须显式检查 + // ... +} + +// 没有隐式的真值 +// if x { // 错误:期望 bool,发现整数 +// } +``` + + +## 循环 + +Rust 有三种循环结构:`loop`、`while` 和 `for`。每种都有特定的用例。 + +### loop - 无限循环 + + +```python !! py +# Python: 使用 while True 进行无限循环 +counter = 0 +while True: + counter += 1 + if counter >= 10: + break + print(counter) + +# 带值提取 +result = None +while True: + value = get_value() + if value is not None: + result = value + break +``` + +```rust !! rs +// Rust: loop 用于无限循环(明确且强大) +let mut counter = 0; +loop { + counter += 1; + if counter >= 10 { + break; + } + println!("{}", counter); +} + +// 可以用 break 返回值! +let result = loop { + let value = get_value(); + if value.is_some() { + break value; + } +}; // result 是 Option + +// loop 可以返回值 +let x = loop { + break 5; // 带值 break +}; // x = 5 + +// 可以在任何表达式中使用 break +let y = loop { + counter += 1; + if counter > 5 { + break counter * 2; + } +}; // y = 12 +``` + + +### while 循环 + + +```python !! py +# Python: while 循环 +counter = 0 +while counter < 10: + print(counter) + counter += 1 + +# 不能返回值 +# result = while counter < 10: # SyntaxError +# counter += 1 +``` + +```rust !! rs +// Rust: while 循环 +let mut counter = 0; +while counter < 10 { + println!("{}", counter); + counter += 1; +} + +// while 不能返回值(使用 loop 或 for) +// while 只是带 if 的 loop 的语法糖 + +// while 的脱糖形式: +// loop { +// if condition { +// // body +// } else { +// break; +// } +// } +``` + + +### for 循环 + + +```python !! py +# Python: for 循环遍历可迭代对象 +items = [1, 2, 3, 4, 5] + +for item in items: + print(item) + +# 使用 enumerate +for index, item in enumerate(items): + print(f"{index}: {item}") + +# 范围 +for i in range(10): + print(i) + +for i in range(5, 10): + print(i) + +# 反向 +for item in reversed(items): + print(item) +``` + +```rust !! rs +// Rust: for 循环遍历迭代器 +let items = vec![1, 2, 3, 4, 5]; + +for item in &items { + println!("{}", item); +} + +// 使用 enumerate +for (index, item) in items.iter().enumerate() { + println!("{}: {}", index, item); +} + +// 范围 +for i in 0..10 { + println!("{}", i); +} + +for i in 5..10 { + println!("{}", i); +} + +// 包含范围 +for i in 0..=10 { + println!("{}", i); +} + +// 反向(需要 rev()) +for item in items.iter().rev() { + println!("{}", item); +} +``` + + +### 迭代器和所有权 + + +```python !! py +# Python: 迭代器不影响所有权 +items = [1, 2, 3] + +for item in items: + print(item) + +# items 仍然可访问 +print(items) # [1, 2, 3] +``` + +```rust !! rs +// Rust: 注意所有权 +let items = vec![1, 2, 3]; + +// 借用每个元素 +for item in &items { + println!("{}", item); +} +// items 仍然可访问 + +// 消耗集合 +for item in items { + println!("{}", item); +} +// items 不再可访问 - 已移动! + +// 要迭代并保留集合,使用 &items +``` + + +## 循环控制 + +### break 和 continue + + +```python !! py +# Python: break 和 continue +for i in range(10): + if i == 3: + continue + if i == 7: + break + print(i) +# 打印: 0, 1, 2, 4, 5, 6 +``` + +```rust !! rs +// Rust: break 和 continue +for i in 0..10 { + if i == 3 { + continue; + } + if i == 7 { + break; + } + println!("{}", i); +} +// 打印: 0, 1, 2, 4, 5, 6 +``` + + +### 循环标签 + + +```python !! py +# Python: 跳出嵌套循环需要标志 +found = False +for i in range(5): + for j in range(5): + if i == 2 and j == 3: + found = True + break + if found: + break + +# 或使用异常(不推荐) +try: + for i in range(5): + for j in range(5): + if i == 2 and j == 3: + raise StopIteration +except StopIteration: + pass +``` + +```rust !! rs +// Rust: 循环标签用于跳出嵌套循环 +'outer: for i in 0..5 { + for j in 0..5 { + if i == 2 && j == 3 { + break 'outer; // 跳出外层循环 + } + } +} + +// 也可以使用 continue 标签 +'outer: for i in 0..5 { + for j in 0..5 { + if i == 2 && j == 3 { + continue 'outer; // 继续外层循环 + } + println!("{} {}", i, j); + } +} + +// 可以带值 break 循环 +let result = 'search: loop { + for i in 0..10 { + for j in 0..10 { + if i * j > 50 { + break 'search (i, j); + } + } + } +}; +println!("Found: {:?}", result); +``` + + +### 带值的 break + + +```python !! py +# Python: 不能直接带值 break +# result = None +# while True: +# value = calculate() +# if value > 100: +# result = value # 必须使用变量 +# break +``` + +```rust !! rs +// Rust: 可以带值 break +let result = loop { + let value = calculate(); + if value > 100 { + break value; // 带值 break + } +}; + +// 也适用于带标签的循环 +let (x, y) = 'outer: loop { + for i in 0..10 { + for j in 0..10 { + if i * j > 50 { + break 'outer (i, j); + } + } + } +}; + +// 常见模式:搜索 +let found = items.iter().enumerate().find(|&(i, &item)| { + item == target +}); +``` + + +## 模式匹配回顾 + +你在模块 3 中学习了 `match`,但让我们在更多控制流上下文中回顾。 + + +```python !! py +# Python: 多个 if-elif-else +value = 5 + +if value == 1: + result = "one" +elif value == 2: + result = "two" +elif value == 3: + result = "three" +elif value > 3 and value < 10: + result = "small" +else: + result = "other" +``` + +```rust !! rs +// Rust: 使用 match 进行模式匹配 +let value = 5; + +let result = match value { + 1 => "one", + 2 => "two", + 3 => "three", + 4..=9 => "small", // 范围模式 + _ => "other", +}; + +// 带守卫的 match +let result = match value { + x if x < 0 => "negative", + x if x > 100 => "large", + x => "normal", +}; +``` + + +## if let 和 while let + +这些是单模式匹配的便捷方式。 + + +```python !! py +# Python: 检查并提取 +value = get_optional() + +if value is not None: + result = value + # 使用 result +else: + # 处理 None + pass + +# 或更冗长的方式 +try: + result = value.unwrap() +except AttributeError: + # 处理 None + pass +``` + +```rust !! rs +// Rust: if let 用于单模式匹配 +let value: Option = Some(5); + +// if let - 匹配一个模式,忽略其他 +if let Some(x) = value { + println!("Got: {}", x); +} else { + println!("Got None"); +} + +// 等价于: +// match value { +// Some(x) => { println!("Got: {}", x); } +// None => { println!("Got None"); } +// } + +// 可以在表达式中使用 if let +let result = if let Some(x) = value { + x * 2 +} else { + 0 +}; +``` + + + +```python !! py +# Python: 循环中的模式匹配 +values = [Some(1), None, Some(2), None, Some(3)] + +for value in values: + if value is not None: + print(value) # 只处理 Some 值 +``` + +```rust !! rs +// Rust: while let 用于重复模式匹配 +let values = vec![Some(1), None, Some(2), None, Some(3)]; + +// while let - 在模式匹配时循环 +let mut iter = values.into_iter(); +while let Some(value) = iter.next() { + println!("{}", value); +} + +// 更实际的例子:处理队列 +let mut queue = vec![1, 2, 3, 4, 5]; + +while let Some(item) = queue.pop() { + println!("Processing: {}", item); +} +``` + + +## 常见控制流模式 + +### 提前返回 + + +```python !! py +# Python: 提前返回以提高清晰度 +def process(value): + if value is None: + return None + + if value < 0: + return None + + # 处理 value + return value * 2 +``` + +```rust !! rs +// Rust: 提前返回是惯用的 +fn process(value: Option) -> Option { + let value = value?; // 如果是 None 则提前返回 + + if value < 0 { + return None; + } + + // 处理 value + Some(value * 2) +} + +// 或使用守卫 +fn process_guard(value: Option) -> Option { + match value { + Some(v) if v >= 0 => Some(v * 2), + _ => None, + } +} +``` + + +### 迭代模式 + + +```python !! py +# Python: 常见迭代模式 + +# 过滤 +items = [1, 2, 3, 4, 5] +filtered = [x for x in items if x % 2 == 0] + +# 映射 +doubled = [x * 2 for x in items] + +# 查找 +found = next((x for x in items if x > 3), None) + +# 全部/任意 +all_positive = all(x > 0 for x in items) +any_large = any(x > 100 for x in items) + +# 求和/计数 +total = sum(items) +count = len([x for x in items if x > 2]) +``` + +```rust !! rs +// Rust: 迭代器方法很强大 +let items = vec![1, 2, 3, 4, 5]; + +// 过滤 +let filtered: Vec = items.iter() + .filter(|&&x| x % 2 == 0) + .collect(); + +// 映射 +let doubled: Vec = items.iter() + .map(|&x| x * 2) + .collect(); + +// 查找 +let found = items.iter().find(|&&x| x > 3); + +// 全部/任意 +let all_positive = items.iter().all(|&x| x > 0); +let any_large = items.iter().any(|&x| x > 100); + +// 求和/计数 +let total: i32 = items.iter().sum(); +let count = items.iter().filter(|&&x| x > 2).count(); + +// 链式调用 +let result: Vec = items.iter() + .filter(|&&x| x % 2 == 0) + .map(|&x| x * 2) + .take(3) + .collect(); +``` + + +### 控制流处理错误 + + +```python !! py +# Python: 基于异常的错误处理 +def divide(a, b): + try: + return a / b + except ZeroDivisionError: + return None + +# 使用 +result = divide(10, 2) +if result is not None: + print(result) +``` + +```rust !! rs +// Rust: 基于 Result 的错误处理 +fn divide(a: f64, b: f64) -> Option { + if b == 0.0 { + None + } else { + Some(a / b) + } +} + +// 使用 +if let Some(result) = divide(10.0, 2.0) { + println!("{}", result); +} + +// 或使用 ? +fn divide_safe(a: f64, b: f64) -> Result { + if b == 0.0 { + Err("Division by zero".to_string()) + } else { + Ok(a / b) + } +} + +fn process_division(a: f64, b: f64) -> Result { + let result = divide_safe(a, b)?; // 出错时提前返回 + Ok(result * 2.0) +} +``` + + +## 综合运用 + +让我们构建一个完整的例子: + + +```python !! py +def process_numbers(numbers): + """使用各种控制流处理数字。""" + if not numbers: + return None + + results = [] + + for num in numbers: + # 跳过负数 + if num < 0: + continue + + # 处理正数 + if num == 0: + results.append("zero") + elif num % 2 == 0: + results.append(f"even: {num}") + else: + results.append(f"odd: {num}") + + # 如果发现大数字则停止 + if num > 100: + results.append("found large") + break + + return results + +# 主执行 +data = [1, 2, -3, 4, 0, 5, 150, 6] +output = process_numbers(data) +print(output) +``` + +```rust !! rs +fn process_numbers(numbers: &[i32]) -> Option> { + if numbers.is_empty() { + return None; + } + + let mut results = Vec::new(); + + for &num in numbers { + // 跳过负数 + if num < 0 { + continue; + } + + // 处理正数 + let result = match num { + 0 => "zero".to_string(), + n if n % 2 == 0 => format!("even: {}", n), + n => format!("odd: {}", n), + }; + + results.push(result); + + // 如果发现大数字则停止 + if num > 100 { + results.push("found large".to_string()); + break; + } + } + + Some(results) +} + +fn main() { + let data = vec![1, 2, -3, 4, 0, 5, 150, 6]; + + if let Some(output) = process_numbers(&data) { + println!("{:?}", output); + } + + // 使用迭代器 + let data2 = vec![1, 2, 3, 4, 5]; + + let sum: i32 = data2.iter() + .filter(|&&x| x > 2) + .map(|&x| x * 2) + .sum(); + + println!("Sum: {}", sum); + + // 带值的循环 + let counter = 0; + let result = loop { + if counter >= 10 { + break counter * 2; + } + }; + println!("Result: {}", result); +} +``` + + +## 性能考虑 + + +```python !! py +# Python: for 循环有开销 +# 每次迭代都涉及 Python 解释器 + +# 列表推导式更快 +result = [x * 2 for x in range(1000000)] + +# 生成器内存效率高 +result = (x * 2 for x in range(100000000)) +``` + +```rust !! rs +// Rust: 循环编译为高效的机器码 +// 迭代器是零成本抽象 + +// 所有都编译为类似的高效代码 +let mut sum = 0; +for i in 0..1000000 { + sum += i; +} + +// 迭代器版本(通常同样快或更快) +let sum: i32 = (0..1000000).sum(); + +// 两者都编译为非常高效的汇编 +// 迭代器没有运行时开销 +``` + + +## 总结 + +在本模块中,你学习了: +- ✅ `if` 是可以返回值的表达式 +- ✅ `loop` 用于带 `break` 值的无限循环 +- ✅ `while` 用于条件循环 +- ✅ `for` 用于迭代器迭代 +- ✅ 循环标签用于嵌套控制流 +- ✅ `if let` 和 `while let` 用于模式匹配 +- ✅ 没有真值/假值 - 只有显式条件 +- ✅ 强大的迭代器方法 + +## 与 Python 的主要区别 + +1. **if 是表达式**: 返回值,不仅仅是语句 +2. **没有真值/假值**: 必须使用显式布尔条件 +3. **loop 结构**: 比 `while True` 更明确 +4. **带值的 break**: 循环可以返回值 +5. **循环标签**: 显式控制嵌套循环 +6. **模式匹配**: `match` 比 `if-elif` 更强大 +7. **迭代器方法**: 丰富的函数式编程能力 + +## 练习 + +### 练习 1: FizzBuzz + +使用 Rust 控制流编写 FizzBuzz: +- 3 的倍数打印 "Fizz" +- 5 的倍数打印 "Buzz" +- 同时是两者倍数打印 "FizzBuzz" +- 使用 `match` 实现逻辑 + +
+解决方案 + +```rust +fn fizzbuzz(n: i32) -> String { + match (n % 3, n % 5) { + (0, 0) => "FizzBuzz".to_string(), + (0, _) => "Fizz".to_string(), + (_, 0) => "Buzz".to_string(), + _ => n.to_string(), + } +} + +fn main() { + for i in 1..=20 { + println!("{}", fizzbuzz(i)); + } +} +``` + +
+ +### 练习 2: 查找最大值 + +在向量中查找最大值: +- 使用 `loop` 而不是 `for` +- 空向量返回 `None` +- 使用带值的 `break` + +
+解决方案 + +```rust +fn find_max(numbers: &[i32]) -> Option { + if numbers.is_empty() { + return None; + } + + let mut iter = numbers.iter(); + let mut max = *iter.next()?; + + loop { + match iter.next() { + Some(&num) if num > max => max = num, + Some(_) => {} + None => break max, + } + } +} + +fn main() { + let numbers = vec![3, 1, 4, 1, 5, 9, 2, 6]; + if let Some(max) = find_max(&numbers) { + println!("Maximum: {}", max); + } +} +``` + +
+ +### 练习 3: 处理直到满足条件 + +处理项目直到满足条件: +- 使用 `while let` 从向量中弹出 +- 发现大于 100 的数字时停止 +- 返回处理过的数字之和 + +
+解决方案 + +```rust +fn process_until(mut numbers: Vec) -> i32 { + let mut sum = 0; + + while let Some(num) = numbers.pop() { + if num > 100 { + break; + } + sum += num; + } + + sum +} + +fn main() { + let numbers = vec![1, 2, 3, 150, 4, 5]; + let sum = process_until(numbers); + println!("Sum: {}", sum); // 9 (1+2+3+3) +} +``` + +
+ +## 下一步 + +现在你已经理解了控制流: +1. **[模块 5: 函数与闭包](./module-05-functions)** - 学习作为一等公民的函数 +2. 练习控制流模式 +3. 探索迭代器方法 + +--- + +**下一节:** [模块 5 - 函数与闭包](./module-05-functions) → diff --git a/content/docs/py2rust/module-04-control-flow.zh-tw.mdx b/content/docs/py2rust/module-04-control-flow.zh-tw.mdx new file mode 100644 index 0000000..f13a1ff --- /dev/null +++ b/content/docs/py2rust/module-04-control-flow.zh-tw.mdx @@ -0,0 +1,1002 @@ +--- +title: "模組 4:控制流" +description: "透過與 Python 比較來掌握 Rust 的控制流結構。學習 if 表達式、迴圈、模式匹配等。" +--- + +# 模組 4:控制流 + +在控制流方面,Rust 相比 Python 展現出了真正的優勢。Python 專注於簡單的可讀性結構,而 Rust 提供了強大、表達力強的控制流,能夠在編譯時防止整類錯誤。 + +## 學習目標 + +完成本模組後,你將理解: +- ✅ `if` 作為表達式而非語句 +- ✅ 迴圈結構:`loop`、`while` 和 `for` +- ✅ 使用 `match` 進行模式匹配(超越模組 3 的內容) +- ✅ `if let` 和 `while let` 模式 +- ✅ 帶值的 `break` 和 `continue` +- ✅ 巢狀迴圈的標籤 +- ✅ 常見的控制流模式 + +## if 表達式 + +在 Python 中,`if` 是語句。在 Rust 中,`if` 是可以傳回值的表達式! + + +```python !! py +# Python: if 是語句 +x = 10 + +if x > 0: + result = "positive" +elif x < 0: + result = "negative" +else: + result = "zero" + +# 不能在表達式中直接使用 if +# value = "positive" if x > 0 else "negative" # 三元運算式,但功能有限 +``` + +```rust !! rs +// Rust: if 是表達式 +let x = 10; + +let result = if x > 0 { + "positive" +} else if x < 0 { + "negative" +} else { + "zero" +}; // 注意分號 + +// 可以在任何需要值的地方使用 +println!("Result: {}", result); + +// 不需要三元運算符 - if 本身就是三元表達式! +let status = if x >= 18 { "adult" } else { "minor" }; +``` + + +### 型別一致性 + + +```python !! py +# Python: 可以傳回不同的型別 +x = 5 +result = x if x > 0 else "error" # int 或 str +``` + +```rust !! rs +// Rust: 所有分支必須傳回相同型別 +let x = 5; + +// 這是可以的 - 都是 &str +let result = if x > 0 { + "positive" +} else { + "zero" +}; + +// 錯誤:混合型別 +// let result = if x > 0 { +// 42 +// } else { +// "error" // 錯誤:期望整數,發現 &str +// }; + +// 對於不同型別必須使用枚舉 +enum MaybeNumber { + Number(i32), + Error(&'static str), +} + +let result = if x > 0 { + MaybeNumber::Number(42) +} else { + MaybeNumber::Error("error") +}; +``` + + +### 沒有真值/假值 + + +```python !! py +# Python: 真值和假值 +x = 5 +if x: # 真值 + pass + +s = "hello" +if s: # 真值(非空字串) + pass + +items = [] +if items: # 假值(空列表) + pass + +numbers = [0, 1, 2] +if numbers: # 真值(非空列表,即使包含 0) + pass +``` + +```rust !! rs +// Rust: 必須使用顯式的布林值 +let x = 5; +if x != 0 { // 必須顯式比較 + // ... +} + +let s = "hello"; +if !s.is_empty() { // 必須顯式檢查 + // ... +} + +let items: Vec = vec![]; +if !items.is_empty() { // 必須顯式檢查 + // ... +} + +// 沒有隱式的真值 +// if x { // 錯誤:期望 bool,發現整數 +// } +``` + + +## 迴圈 + +Rust 有三種迴圈結構:`loop`、`while` 和 `for`。每種都有特定的用例。 + +### loop - 無限迴圈 + + +```python !! py +# Python: 使用 while True 進行無限迴圈 +counter = 0 +while True: + counter += 1 + if counter >= 10: + break + print(counter) + +# 帶值提取 +result = None +while True: + value = get_value() + if value is not None: + result = value + break +``` + +```rust !! rs +// Rust: loop 用於無限迴圈(明確且強大) +let mut counter = 0; +loop { + counter += 1; + if counter >= 10 { + break; + } + println!("{}", counter); +} + +// 可以用 break 傳回值! +let result = loop { + let value = get_value(); + if value.is_some() { + break value; + } +}; // result 是 Option + +// loop 可以傳回值 +let x = loop { + break 5; // 帶值 break +}; // x = 5 + +// 可以在任何表達式中使用 break +let y = loop { + counter += 1; + if counter > 5 { + break counter * 2; + } +}; // y = 12 +``` + + +### while 迴圈 + + +```python !! py +# Python: while 迴圈 +counter = 0 +while counter < 10: + print(counter) + counter += 1 + +# 不能傳回值 +# result = while counter < 10: # SyntaxError +# counter += 1 +``` + +```rust !! rs +// Rust: while 迴圈 +let mut counter = 0; +while counter < 10 { + println!("{}", counter); + counter += 1; +} + +// while 不能傳回值(使用 loop 或 for) +// while 只是帶 if 的 loop 的語法糖 + +// while 的脫糖形式: +// loop { +// if condition { +// // body +// } else { +// break; +// } +// } +``` + + +### for 迴圈 + + +```python !! py +# Python: for 迴圈遍歷可迭代物件 +items = [1, 2, 3, 4, 5] + +for item in items: + print(item) + +# 使用 enumerate +for index, item in enumerate(items): + print(f"{index}: {item}") + +# 範圍 +for i in range(10): + print(i) + +for i in range(5, 10): + print(i) + +# 反向 +for item in reversed(items): + print(item) +``` + +```rust !! rs +// Rust: for 迴圈遍歷迭代器 +let items = vec![1, 2, 3, 4, 5]; + +for item in &items { + println!("{}", item); +} + +// 使用 enumerate +for (index, item) in items.iter().enumerate() { + println!("{}: {}", index, item); +} + +// 範圍 +for i in 0..10 { + println!("{}", i); +} + +for i in 5..10 { + println!("{}", i); +} + +// 包含範圍 +for i in 0..=10 { + println!("{}", i); +} + +// 反向(需要 rev()) +for item in items.iter().rev() { + println!("{}", item); +} +``` + + +### 迭代器和所有權 + + +```python !! py +# Python: 迭代器不影響所有權 +items = [1, 2, 3] + +for item in items: + print(item) + +# items 仍然可訪問 +print(items) # [1, 2, 3] +``` + +```rust !! rs +// Rust: 注意所有權 +let items = vec![1, 2, 3]; + +// 借用每個元素 +for item in &items { + println!("{}", item); +} +// items 仍然可訪問 + +// 消耗集合 +for item in items { + println!("{}", item); +} +// items 不再可訪問 - 已移動! + +// 要迭代並保留集合,使用 &items +``` + + +## 迴圈控制 + +### break 和 continue + + +```python !! py +# Python: break 和 continue +for i in range(10): + if i == 3: + continue + if i == 7: + break + print(i) +# 打印: 0, 1, 2, 4, 5, 6 +``` + +```rust !! rs +// Rust: break 和 continue +for i in 0..10 { + if i == 3 { + continue; + } + if i == 7 { + break; + } + println!("{}", i); +} +// 打印: 0, 1, 2, 4, 5, 6 +``` + + +### 迴圈標籤 + + +```python !! py +# Python: 跳出巢狀迴圈需要標誌 +found = False +for i in range(5): + for j in range(5): + if i == 2 and j == 3: + found = True + break + if found: + break + +# 或使用異常(不推薦) +try: + for i in range(5): + for j in range(5): + if i == 2 and j == 3: + raise StopIteration +except StopIteration: + pass +``` + +```rust !! rs +// Rust: 迴圈標籤用於跳出巢狀迴圈 +'outer: for i in 0..5 { + for j in 0..5 { + if i == 2 && j == 3 { + break 'outer; // 跳出外層迴圈 + } + } +} + +// 也可以使用 continue 標籤 +'outer: for i in 0..5 { + for j in 0..5 { + if i == 2 && j == 3 { + continue 'outer; // 繼續外層迴圈 + } + println!("{} {}", i, j); + } +} + +// 可以帶值 break 迴圈 +let result = 'search: loop { + for i in 0..10 { + for j in 0..10 { + if i * j > 50 { + break 'search (i, j); + } + } + } +}; +println!("Found: {:?}", result); +``` + + +### 帶值的 break + + +```python !! py +# Python: 不能直接帶值 break +# result = None +# while True: +# value = calculate() +# if value > 100: +# result = value # 必須使用變數 +# break +``` + +```rust !! rs +// Rust: 可以帶值 break +let result = loop { + let value = calculate(); + if value > 100 { + break value; // 帶值 break + } +}; + +// 也適用於帶標籤的迴圈 +let (x, y) = 'outer: loop { + for i in 0..10 { + for j in 0..10 { + if i * j > 50 { + break 'outer (i, j); + } + } + } +}; + +// 常見模式:搜尋 +let found = items.iter().enumerate().find(|&(i, &item)| { + item == target +}); +``` + + +## 模式匹配回顧 + +你在模組 3 中學習了 `match`,但讓我們在更多控制流上下文中回顧。 + + +```python !! py +# Python: 多個 if-elif-else +value = 5 + +if value == 1: + result = "one" +elif value == 2: + result = "two" +elif value == 3: + result = "three" +elif value > 3 and value < 10: + result = "small" +else: + result = "other" +``` + +```rust !! rs +// Rust: 使用 match 進行模式匹配 +let value = 5; + +let result = match value { + 1 => "one", + 2 => "two", + 3 => "three", + 4..=9 => "small", // 範圍模式 + _ => "other", +}; + +// 帶守衛的 match +let result = match value { + x if x < 0 => "negative", + x if x > 100 => "large", + x => "normal", +}; +``` + + +## if let 和 while let + +這些是單模式匹配的便捷方式。 + + +```python !! py +# Python: 檢查並提取 +value = get_optional() + +if value is not None: + result = value + # 使用 result +else: + # 處理 None + pass + +# 或更冗長的方式 +try: + result = value.unwrap() +except AttributeError: + # 處理 None + pass +``` + +```rust !! rs +// Rust: if let 用於單模式匹配 +let value: Option = Some(5); + +// if let - 匹配一個模式,忽略其他 +if let Some(x) = value { + println!("Got: {}", x); +} else { + println!("Got None"); +} + +// 等價於: +// match value { +// Some(x) => { println!("Got: {}", x); } +// None => { println!("Got None"); } +// } + +// 可以在表達式中使用 if let +let result = if let Some(x) = value { + x * 2 +} else { + 0 +}; +``` + + + +```python !! py +# Python: 迴圈中的模式匹配 +values = [Some(1), None, Some(2), None, Some(3)] + +for value in values: + if value is not None: + print(value) # 只處理 Some 值 +``` + +```rust !! rs +// Rust: while let 用於重複模式匹配 +let values = vec![Some(1), None, Some(2), None, Some(3)]; + +// while let - 在模式匹配時迴圈 +let mut iter = values.into_iter(); +while let Some(value) = iter.next() { + println!("{}", value); +} + +// 更實際的例子:處理佇列 +let mut queue = vec![1, 2, 3, 4, 5]; + +while let Some(item) = queue.pop() { + println!("Processing: {}", item); +} +``` + + +## 常見控制流模式 + +### 提早返回 + + +```python !! py +# Python: 提早返回以提高清晰度 +def process(value): + if value is None: + return None + + if value < 0: + return None + + # 處理 value + return value * 2 +``` + +```rust !! rs +// Rust: 提早返回是慣用的 +fn process(value: Option) -> Option { + let value = value?; // 如果是 None 則提早返回 + + if value < 0 { + return None; + } + + // 處理 value + Some(value * 2) +} + +// 或使用守衛 +fn process_guard(value: Option) -> Option { + match value { + Some(v) if v >= 0 => Some(v * 2), + _ => None, + } +} +``` + + +### 迭代模式 + + +```python !! py +# Python: 常見迭代模式 + +# 過濾 +items = [1, 2, 3, 4, 5] +filtered = [x for x in items if x % 2 == 0] + +# 映射 +doubled = [x * 2 for x in items] + +# 查找 +found = next((x for x in items if x > 3), None) + +# 全部/任意 +all_positive = all(x > 0 for x in items) +any_large = any(x > 100 for x in items) + +# 求和/計數 +total = sum(items) +count = len([x for x in items if x > 2]) +``` + +```rust !! rs +// Rust: 迭代器方法很強大 +let items = vec![1, 2, 3, 4, 5]; + +// 過濾 +let filtered: Vec = items.iter() + .filter(|&&x| x % 2 == 0) + .collect(); + +// 映射 +let doubled: Vec = items.iter() + .map(|&x| x * 2) + .collect(); + +// 查找 +let found = items.iter().find(|&&x| x > 3); + +// 全部/任意 +let all_positive = items.iter().all(|&x| x > 0); +let any_large = items.iter().any(|&x| x > 100); + +// 求和/計數 +let total: i32 = items.iter().sum(); +let count = items.iter().filter(|&&x| x > 2).count(); + +// 鏈式調用 +let result: Vec = items.iter() + .filter(|&&x| x % 2 == 0) + .map(|&x| x * 2) + .take(3) + .collect(); +``` + + +### 控制流處理錯誤 + + +```python !! py +# Python: 基於異常的錯誤處理 +def divide(a, b): + try: + return a / b + except ZeroDivisionError: + return None + +# 使用 +result = divide(10, 2) +if result is not None: + print(result) +``` + +```rust !! rs +// Rust: 基於 Result 的錯誤處理 +fn divide(a: f64, b: f64) -> Option { + if b == 0.0 { + None + } else { + Some(a / b) + } +} + +// 使用 +if let Some(result) = divide(10.0, 2.0) { + println!("{}", result); +} + +// 或使用 ? +fn divide_safe(a: f64, b: f64) -> Result { + if b == 0.0 { + Err("Division by zero".to_string()) + } else { + Ok(a / b) + } +} + +fn process_division(a: f64, b: f64) -> Result { + let result = divide_safe(a, b)?; // 出錯時提早返回 + Ok(result * 2.0) +} +``` + + +## 綜合運用 + +讓我們構建一個完整的例子: + + +```python !! py +def process_numbers(numbers): + """使用各種控制流處理數字。""" + if not numbers: + return None + + results = [] + + for num in numbers: + # 跳過負數 + if num < 0: + continue + + # 處理正數 + if num == 0: + results.append("zero") + elif num % 2 == 0: + results.append(f"even: {num}") + else: + results.append(f"odd: {num}") + + # 如果發現大數字則停止 + if num > 100: + results.append("found large") + break + + return results + +# 主執行 +data = [1, 2, -3, 4, 0, 5, 150, 6] +output = process_numbers(data) +print(output) +``` + +```rust !! rs +fn process_numbers(numbers: &[i32]) -> Option> { + if numbers.is_empty() { + return None; + } + + let mut results = Vec::new(); + + for &num in numbers { + // 跳過負數 + if num < 0 { + continue; + } + + // 處理正數 + let result = match num { + 0 => "zero".to_string(), + n if n % 2 == 0 => format!("even: {}", n), + n => format!("odd: {}", n), + }; + + results.push(result); + + // 如果發現大數字則停止 + if num > 100 { + results.push("found large".to_string()); + break; + } + } + + Some(results) +} + +fn main() { + let data = vec![1, 2, -3, 4, 0, 5, 150, 6]; + + if let Some(output) = process_numbers(&data) { + println!("{:?}", output); + } + + // 使用迭代器 + let data2 = vec![1, 2, 3, 4, 5]; + + let sum: i32 = data2.iter() + .filter(|&&x| x > 2) + .map(|&x| x * 2) + .sum(); + + println!("Sum: {}", sum); + + // 帶值的迴圈 + let counter = 0; + let result = loop { + if counter >= 10 { + break counter * 2; + } + }; + println!("Result: {}", result); +} +``` + + +## 效能考慮 + + +```python !! py +# Python: for 迴圈有開銷 +# 每次迭代都涉及 Python 解釋器 + +# 列表推導式更快 +result = [x * 2 for x in range(1000000)] + +# 生成器記憶體效率高 +result = (x * 2 for x in range(100000000)) +``` + +```rust !! rs +// Rust: 迴圈編譯為高效的機器碼 +// 迭代器是零成本抽象 + +// 所有都編譯為類似的高效代碼 +let mut sum = 0; +for i in 0..1000000 { + sum += i; +} + +// 迭代器版本(通常同樣快或更快) +let sum: i32 = (0..1000000).sum(); + +// 兩者都編譯為非常高效的組合語言 +// 迭代器沒有運行時開銷 +``` + + +## 總結 + +在本模組中,你學習了: +- ✅ `if` 是可以傳回值的表達式 +- ✅ `loop` 用於帶 `break` 值的無限迴圈 +- ✅ `while` 用於條件迴圈 +- ✅ `for` 用於迭代器迭代 +- ✅ 迴圈標籤用於巢狀控制流 +- ✅ `if let` 和 `while let` 用於模式匹配 +- ✅ 沒有真值/假值 - 只有顯式條件 +- ✅ 強大的迭代器方法 + +## 與 Python 的主要差別 + +1. **if 是表達式**: 傳回值,不僅僅是語句 +2. **沒有真值/假值**: 必須使用顯式布林條件 +3. **loop 結構**: 比 `while True` 更明確 +4. **帶值的 break**: 迴圈可以傳回值 +5. **迴圈標籤**: 顯式控制巢狀迴圈 +6. **模式匹配**: `match` 比 `if-elif` 更強大 +7. **迭代器方法**: 豐富的函數式編程能力 + +## 練習 + +### 練習 1: FizzBuzz + +使用 Rust 控制流編寫 FizzBuzz: +- 3 的倍數打印 "Fizz" +- 5 的倍數打印 "Buzz" +- 同時是兩者倍數打印 "FizzBuzz" +- 使用 `match` 實現邏輯 + +
+解決方案 + +```rust +fn fizzbuzz(n: i32) -> String { + match (n % 3, n % 5) { + (0, 0) => "FizzBuzz".to_string(), + (0, _) => "Fizz".to_string(), + (_, 0) => "Buzz".to_string(), + _ => n.to_string(), + } +} + +fn main() { + for i in 1..=20 { + println!("{}", fizzbuzz(i)); + } +} +``` + +
+ +### 練習 2: 查找最大值 + +在向量中查找最大值: +- 使用 `loop` 而不是 `for` +- 空向量返回 `None` +- 使用帶值的 `break` + +
+解決方案 + +```rust +fn find_max(numbers: &[i32]) -> Option { + if numbers.is_empty() { + return None; + } + + let mut iter = numbers.iter(); + let mut max = *iter.next()?; + + loop { + match iter.next() { + Some(&num) if num > max => max = num, + Some(_) => {} + None => break max, + } + } +} + +fn main() { + let numbers = vec![3, 1, 4, 1, 5, 9, 2, 6]; + if let Some(max) = find_max(&numbers) { + println!("Maximum: {}", max); + } +} +``` + +
+ +### 練習 3: 處理直到滿足條件 + +處理項目直到滿足條件: +- 使用 `while let` 從向量中彈出 +- 發現大於 100 的數字時停止 +- 返回處理過的數字之和 + +
+解決方案 + +```rust +fn process_until(mut numbers: Vec) -> i32 { + let mut sum = 0; + + while let Some(num) = numbers.pop() { + if num > 100 { + break; + } + sum += num; + } + + sum +} + +fn main() { + let numbers = vec![1, 2, 3, 150, 4, 5]; + let sum = process_until(numbers); + println!("Sum: {}", sum); // 9 (1+2+3+3) +} +``` + +
+ +## 下一步 + +現在你已經理解了控制流: +1. **[模組 5: 函數與閉包](./module-05-functions)** - 學習作為一等公民的函數 +2. 練習控制流模式 +3. 探索迭代器方法 + +--- + +**下一節:** [模組 5 - 函數與閉包](./module-05-functions) → diff --git a/content/docs/py2rust/module-05-functions.mdx b/content/docs/py2rust/module-05-functions.mdx new file mode 100644 index 0000000..90b0185 --- /dev/null +++ b/content/docs/py2rust/module-05-functions.mdx @@ -0,0 +1,973 @@ +--- +title: "Module 5: Functions & Closures" +description: "Master Rust's function system, closures, and functional programming features by comparing them with Python." +--- + +# Module 5: Functions & Closures + +Functions are first-class citizens in both Python and Rust, but Rust takes them to a new level with its type system, ownership, and closure features. This module will show you how to leverage Rust's powerful function system. + +## Learning Objectives + +By the end of this module, you'll understand: +- ✅ Function definitions and signatures +- ✅ Parameters, arguments, and passing +- ✅ Return types and the `return` keyword +- ✅ Closures and their capture modes +- ✅ Function pointers and closures as values +- ✅ Higher-order functions +- ✅ Iterators and functional programming patterns +- ✅ Closures vs functions performance + +## Function Definitions + +### Basic Function Syntax + + +```python !! py +# Python: Dynamic, flexible functions +def greet(name): + return f"Hello, {name}!" + +def add(a, b): + return a + b + +def no_return(): + print("Side effect") + # Returns None implicitly +``` + +```rust !! rs +// Rust: Static types, explicit signatures +fn greet(name: &str) -> String { + format!("Hello, {}!", name) +} + +fn add(a: i32, b: i32) -> i32 { + a + b // No semicolon = return value +} + +fn no_return() { + println!("Side effect"); + // Returns () implicitly +} +``` + + +### Type Annotations Are Required + + +```python !! py +# Python: Type hints optional +def add(a: int, b: int) -> int: + return a + b + +def add_no_hints(a, b): + return a + b # Works fine! + +def flexible(x): + if x > 0: + return 42 + return "negative" # Different types OK +``` + +```rust !! rs +// Rust: Type annotations required for params and return +fn add(a: i32, b: i32) -> i32 { + a + b +} + +// fn add_no_hints(a, b) { // ERROR: types required +// a + b +// } + +// Cannot return different types +// fn flexible(x: i32) -> ??? { // What type? +// if x > 0 { +// return 42; +// } +// return "negative"; // ERROR: type mismatch +// } + +// Use enums for different return types +enum Either { + Number(i32), + Text(&'static str), +} + +fn flexible(x: i32) -> Either { + if x > 0 { + Either::Number(42) + } else { + Either::Text("negative") + } +} +``` + + +### Multiple Return Values + + +```python !! py +# Python: Tuples for multiple returns +def divide_and_remainder(a, b): + quotient = a // b + remainder = a % b + return quotient, remainder + +q, r = divide_and_remainder(10, 3) + +# Can return any number of values +def many_returns(): + return 1, 2, 3, 4, 5 + +a, b, c, d, e = many_returns() +``` + +```rust !! rs +// Rust: Tuples for multiple returns +fn divide_and_remainder(a: i32, b: i32) -> (i32, i32) { + (a / b, a % b) +} + +let (q, r) = divide_and_remainder(10, 3); + +// Can return any number of values +fn many_returns() -> (i32, i32, i32, i32, i32) { + (1, 2, 3, 4, 5) +} + +let (a, b, c, d, e) = many_returns(); + +// Named fields for clarity +struct DivisionResult { + quotient: i32, + remainder: i32, +} + +fn divide_struct(a: i32, b: i32) -> DivisionResult { + DivisionResult { + quotient: a / b, + remainder: a % b, + } +} +``` + + +## Parameters and Arguments + +### Pass by Value vs Reference + + +```python !! py +# Python: Everything is reference (object) +def modify_list(items): + items.append(4) # Modifies original! + print(items) + +my_list = [1, 2, 3] +modify_list(my_list) +print(my_list) # [1, 2, 3, 4] + +# But reassignment doesn't affect caller +def reassign_list(items): + items = [4, 5, 6] # Only affects local variable + +my_list = [1, 2, 3] +reassign_list(my_list) +print(my_list) # [1, 2, 3] +``` + +```rust !! rs +// Rust: Explicit ownership and borrowing +fn modify_list(items: &mut Vec) { + items.push(4); // Modifies original + println!("{:?}", items); +} + +let mut my_list = vec![1, 2, 3]; +modify_list(&mut my_list); +println!("{:?}", my_list); // [1, 2, 3, 4] + +// Takes ownership (consumes the vector) +fn consume_list(items: Vec) { + println!("{:?}", items); +} // items is dropped here + +let my_list = vec![1, 2, 3]; +consume_list(my_list); +// println!("{:?}", my_list); // ERROR: value moved + +// Borrow without modifying +fn read_list(items: &[i32]) { + println!("{:?}", items); +} + +let my_list = vec![1, 2, 3]; +read_list(&my_list); +println!("{:?}", my_list); // Still accessible +``` + + +### Default Arguments + + +```python !! py +# Python: Default arguments +def greet(name, greeting="Hello"): + return f"{greeting}, {name}!" + +print(greet("Alice")) # Hello, Alice! +print(greet("Bob", "Hi")) # Hi, Bob! + +# Can have multiple defaults +def func(a, b=1, c=2): + return a + b + c + +print(func(1)) # 4 +print(func(1, 10)) # 13 +print(func(1, 10, 20)) # 31 +``` + +```rust !! rs +// Rust: No default arguments (use methods or builders) +fn greet(name: &str, greeting: &str) -> String { + format!("{}, {}!", greeting, name) +} + +println!("{}", greet("Alice", "Hello")); +println!("{}", greet("Bob", "Hi")); + +// Pattern: Use Option for defaults +fn greet_optional(name: &str, greeting: Option<&str>) -> String { + let greeting = greeting.unwrap_or("Hello"); + format!("{}, {}!", greeting, name) +} + +println!("{}", greet_optional("Alice", None)); +println!("{}", greet_optional("Bob", Some("Hi"))); + +// Pattern: Use builder pattern +struct GreetingBuilder { + name: String, + greeting: Option, +} + +impl GreetingBuilder { + fn new(name: &str) -> Self { + GreetingBuilder { + name: name.to_string(), + greeting: None, + } + } + + fn greeting(mut self, greeting: &str) -> Self { + self.greeting = Some(greeting.to_string()); + self + } + + fn build(self) -> String { + let greeting = self.greeting.as_deref().unwrap_or("Hello"); + format!("{}, {}!", greeting, self.name) + } +} + +println!("{}", GreetingBuilder::new("Alice").build()); +println!("{}", GreetingBuilder::new("Bob").greeting("Hi").build()); +``` + + +### Variable Arguments + + +```python !! py +# Python: *args and **kwargs +def sum_all(*args): + return sum(args) + +print(sum_all(1, 2, 3)) # 6 +print(sum_all(1, 2, 3, 4, 5)) # 15 + +def print_info(**kwargs): + for key, value in kwargs.items(): + print(f"{key}: {value}") + +print_info(name="Alice", age=25) + +def combined(a, b, *args, **kwargs): + print(f"a={a}, b={b}") + print(f"args={args}") + print(f"kwargs={kwargs}") +``` + +```rust !! rs +// Rust: Use iterators, slices, or macros +fn sum_all(numbers: &[i32]) -> i32 { + numbers.iter().sum() +} + +println!("{}", sum_all(&[1, 2, 3])); // 6 +println!("{}", sum_all(&[1, 2, 3, 4, 5])); // 15 + +// For truly variable arguments, use macros +macro_rules! sum_all_macro { + ($($x:expr),*) => {{ + let mut sum = 0; + $(sum += $x;)* + sum + }}; +} + +println!("{}", sum_all_macro!(1, 2, 3)); // 6 + +// For heterogeneous data, use structs or enums +struct Info { + data: std::collections::HashMap, +} + +fn print_info(info: &Info) { + for (key, value) in &info.data { + println!("{}: {}", key, value); + } +} +``` + + +## Return Values + +### Expression-Based Returns + + +```python !! py +# Python: Explicit return required +def add(a, b): + return a + b + +def multiple_returns(x): + if x > 0: + return "positive" + return "non-positive" + +def no_return(): + pass # Returns None +``` + +```rust !! rs +// Rust: Last expression is return value +fn add(a: i32, b: i32) -> i32 { + a + b // No semicolon = return value +} + +fn multiple_returns(x: i32) -> &'static str { + if x > 0 { + "positive" // No semicolon + } else { + "non-positive" // No semicolon + } +} + +fn no_return() { + // Returns () implicitly +} + +// Can use return keyword for early returns +fn early_return(x: i32) -> i32 { + if x < 0 { + return 0; // Semicolon OK with return + } + x * 2 +} +``` + + +### Never Return Type + + +```python !! py +# Python: Functions that never return +def forever(): + while True: + pass + +def error_out(): + raise ValueError("Error") +``` + +```rust !! rs +// Rust: The ! never type +fn forever() -> ! { + loop {} // Never returns +} + +fn error_out() -> ! { + panic!("Error"); // Never returns +} + +// Can use ! where any type is expected +fn always_errors() -> ! { + panic!("Always errors"); +} + +let x: i32 = always_errors(); // OK, ! coerces to any type + +// In match branches +fn get_number(opt: Option) -> i32 { + match opt { + Some(num) => num, + None => panic!("No number"), // ! is compatible with i32 + } +} +``` + + +## Closures + +Closures are anonymous functions that can capture their environment. + + +```python !! py +# Python: Lambda functions +add = lambda x, y: x + y +print(add(1, 2)) # 3 + +# Can also use def +def make_adder(n): + def adder(x): + return x + n + return adder + +add_5 = make_adder(5) +print(add_5(10)) # 15 +``` + +```rust !! rs +// Rust: Closures with type inference +let add = |x: i32, y: i32| -> i32 { x + y }; +println!("{}", add(1, 2)); // 3 + +// Types can often be inferred +let add = |x, y| x + y; +println!("{}", add(1i32, 2)); // 3 + +// Capturing environment +fn make_adder(n: i32) -> impl Fn(i32) -> i32 { + move |x| x + n // move keyword transfers ownership +} + +let add_5 = make_adder(5); +println!("{}", add_5(10)); // 15 +``` + + +### Closure Capture Modes + + +```python !! py +# Python: Captures by reference +def make_counter(): + count = 0 + def increment(): + nonlocal count + count += 1 + return count + return increment + +counter = make_counter() +print(counter()) # 1 +print(counter()) # 2 +``` + +```rust !! rs +// Rust: Three capture modes +// 1. Borrowing (Fn) +let data = vec![1, 2, 3]; +let borrow = || { + println!("{:?}", data); // Borrows data +}; +borrow(); +println!("{:?}", data); // Still accessible + +// 2. Mutable borrow (FnMut) +let mut data = vec![1, 2, 3]; +let mut borrow_mut = || { + data.push(4); // Mutably borrows data +}; +borrow_mut(); +println!("{:?}", data); // [1, 2, 3, 4] + +// 3. Taking ownership (FnOnce with move) +let data = vec![1, 2, 3]; +let take_ownership = move || { + println!("{:?}", data); // Moves data +}; +take_ownership(); +// println!("{:?}", data); // ERROR: data was moved + +// Function traits +// Fn: can call multiple times, borrows +// FnMut: can call multiple times, mutably borrows +// FnOnce: can call once, consumes captured values +``` + + +### Closure Type Inference + + +```python !! py +# Python: Dynamic types +def apply(func, value): + return func(value) + +double = lambda x: x * 2 +print(apply(double, 5)) # 10 + +to_upper = lambda s: s.upper() +print(apply(to_upper, "hello")) # HELLO +``` + +```rust !! rs +// Rust: Closure types are inferred +fn apply(func: F, value: T) -> R +where + F: Fn(T) -> R, +{ + func(value) +} + +let double = |x: i32| x * 2; +println!("{}", apply(double, 5)); // 10 + +// Cannot use apply with different closure types +// let to_upper = |s: &str| s.to_uppercase(); +// println!("{}", apply(to_upper, "hello")); // ERROR: type mismatch + +// But closures with same signature are compatible +let add = |x: i32| x + 10; +let sub = |x: i32| x - 10; +fn use_func(f: impl Fn(i32) -> i32, x: i32) -> i32 { + f(x) +} +println!("{}", use_func(add, 5)); // 15 +println!("{}", use_func(sub, 5)); // -5 +``` + + +### Closure Performance + + +```python !! py +# Python: Function call overhead +def apply_function(func, items): + return [func(x) for x in items] + +items = list(range(1000000)) +result = apply_function(lambda x: x * 2, items) +``` + +```rust !! rs +// Rust: Closures are often zero-cost abstractions +fn apply_function(func: F, items: &[i32]) -> Vec +where + F: Fn(i32) -> i32, +{ + items.iter().map(|&x| func(x)).collect() +} + +let items: Vec = (0..1000000).collect(); +let result = apply_function(|x| x * 2, &items); + +// Closures compile to regular functions +// Compiler can inline them +// No runtime overhead compared to functions +``` + + +## Higher-Order Functions + +Functions that take or return other functions. + + +```python !! py +# Python: Functions as values +def apply_operation(x, y, operation): + return operation(x, y) + +def add(a, b): + return a + b + +def multiply(a, b): + return a * b + +print(apply_operation(5, 3, add)) # 8 +print(apply_operation(5, 3, multiply)) # 15 + +# Returning functions +def make_operation(operation): + if operation == "add": + return lambda x, y: x + y + elif operation == "multiply": + return lambda x, y: x * y + +add_func = make_operation("add") +print(add_func(5, 3)) # 8 +``` + +```rust !! rs +// Rust: Function pointers and closures +type BinaryOp = fn(i32, i32) -> i32; + +fn apply_operation(x: i32, y: i32, operation: BinaryOp) -> i32 { + operation(x, y) +} + +fn add(a: i32, b: i32) -> i32 { + a + b +} + +fn multiply(a: i32, b: i32) -> i32 { + a * b +} + +println!("{}", apply_operation(5, 3, add)); // 8 +println!("{}", apply_operation(5, 3, multiply)); // 15 + +// Returning closures (requires impl Fn) +fn make_operation(operation: &str) -> impl Fn(i32, i32) -> i32 { + move |x, y| match operation { + "add" => x + y, + "multiply" => x * y, + _ => panic!("Unknown operation"), + } +} + +let add_func = make_operation("add"); +println!("{}", add_func(5, 3)); // 8 +``` + + +## Iterators and Closures + +Rust's iterator methods work seamlessly with closures. + + +```python !! py +# Python: List comprehensions and functions +items = [1, 2, 3, 4, 5] + +# Map +doubled = [x * 2 for x in items] + +# Filter +evens = [x for x in items if x % 2 == 0] + +# Custom operation +def process(x): + return x * 2 if x % 2 == 0 else x + +processed = [process(x) for x in items] +``` + +```rust !! rs +// Rust: Iterator methods with closures +let items = vec![1, 2, 3, 4, 5]; + +// Map +let doubled: Vec = items.iter().map(|x| x * 2).collect(); + +// Filter +let evens: Vec<&i32> = items.iter().filter(|&x| x % 2 == 0).collect(); + +// Custom operation +let processed: Vec = items.iter() + .map(|&x| if x % 2 == 0 { x * 2 } else { x }) + .collect(); + +// Chaining +let result: Vec = items.iter() + .filter(|&&x| x % 2 == 0) + .map(|&x| x * 2) + .take(2) + .collect(); +``` + + +### Common Iterator Patterns + + +```python !! py +# Python: Common patterns +items = [1, 2, 3, 4, 5] + +# Sum of squares +sum_squares = sum(x * x for x in items) + +# First matching +first_large = next((x for x in items if x > 3), None) + +# All/Any +all_positive = all(x > 0 for x in items) +any_large = any(x > 10 for x in items) + +# Fold/reduce +total = sum(items) +product = 1 +for x in items: + product *= x +``` + +```rust !! rs +// Rust: Iterator patterns +let items = vec![1, 2, 3, 4, 5]; + +// Sum of squares +let sum_squares: i32 = items.iter().map(|&x| x * x).sum(); + +// First matching +let first_large = items.iter().find(|&&x| x > 3); + +// All/Any +let all_positive = items.iter().all(|&x| x > 0); +let any_large = items.iter().any(|&x| x > 10); + +// Fold/reduce +let total: i32 = items.iter().sum(); +let product: i32 = items.iter().product(); +``` + + +## Putting It All Together + +Let's build a complete example: + + +```python !! py +from typing import List, Callable, Optional + +def process_numbers( + numbers: List[int], + transform: Callable[[int], int], + predicate: Callable[[int], bool] +) -> List[int]: + """Process numbers with transform and filter by predicate.""" + result = [] + for num in numbers: + if predicate(num): + result.append(transform(num)) + return result + +def double(x: int) -> int: + return x * 2 + +def is_positive(x: int) -> bool: + return x > 0 + +# Using +numbers = [1, -2, 3, -4, 5] +result = process_numbers(numbers, double, is_positive) +print(result) # [2, 6, 10] + +# With lambda +result = process_numbers(numbers, lambda x: x * 3, is_positive) +print(result) # [3, 9, 15] + +# Composing operations +def compose(f, g): + return lambda x: f(g(x)) + +double_then_square = compose(lambda x: x * x, double) +print(double_then_square(5)) # 100 +``` + +```rust !! rs +use std::collections::HashMap; + +fn process_numbers( + numbers: &[i32], + transform: F, + predicate: P, +) -> Vec +where + F: Fn(i32) -> i32, + P: Fn(i32) -> bool, +{ + numbers + .iter() + .filter(|&&x| predicate(x)) + .map(|&x| transform(x)) + .collect() +} + +fn double(x: i32) -> i32 { + x * 2 +} + +fn is_positive(x: i32) -> bool { + x > 0 +} + +fn main() { + // Using + let numbers = vec![1, -2, 3, -4, 5]; + let result = process_numbers(&numbers, double, is_positive); + println!("{:?}", result); // [2, 6, 10] + + // With closure + let result = process_numbers(&numbers, |x| x * 3, is_positive); + println!("{:?}", result); // [3, 9, 15] + + // Composing operations + fn compose(f: F, g: G) -> impl Fn(A) -> C + where + F: Fn(B) -> C, + G: Fn(A) -> B, + { + move |x| f(g(x)) + } + + let double_then_square = compose(|x: i32| x * x, double); + println!("{}", double_then_square(5)); // 100 + + // Practical example: counting words + let text = "hello world hello rust world"; + let mut counts = HashMap::new(); + + for word in text.split_whitespace() { + *counts.entry(word).or_insert(0) += 1; + } + + println!("{:?}", counts); // {"hello": 2, "world": 2, "rust": 1} +} +``` + + +## Summary + +In this module, you learned: +- ✅ Function signatures require explicit types +- ✅ Return values are the last expression +- ✅ Closures capture environment in three ways +- ✅ Function traits: Fn, FnMut, FnOnce +- ✅ Higher-order functions with function pointers +- ✅ Iterator methods use closures extensively +- ✅ Closures are zero-cost abstractions + +## Key Differences from Python + +1. **Type annotations**: Required for all parameters and returns +2. **Return values**: Last expression, not explicit `return` +3. **Closure captures**: Explicit ownership (move vs borrow) +4. **No default arguments**: Use builders or Option +5. **Function traits**: Three traits for different capture modes +6. **Closures are efficient**: Often compile to inline code +7. **No varargs**: Use slices, iterators, or macros + +## Exercises + +### Exercise 1: Custom Iterator + +Create a function that returns a closure: +- The closure should maintain a counter +- Each call returns the next number +- Uses `FnMut` trait + +
+Solution + +```rust +fn make_counter(start: i32) -> impl FnMut() -> i32 { + let mut counter = start; + move || { + let current = counter; + counter += 1; + current + } +} + +fn main() { + let mut count = make_counter(0); + println!("{}", count()); // 0 + println!("{}", count()); // 1 + println!("{}", count()); // 2 +} +``` + +
+ +### Exercise 2: Higher-Order Map + +Implement a generic map function: +- Takes a slice and a transformation function +- Returns a new vector with transformed values +- Use generics and impl Fn + +
+Solution + +```rust +fn my_map(items: &[T], f: F) -> Vec +where + F: Fn(&T) -> U, +{ + items.iter().map(f).collect() +} + +fn main() { + let numbers = vec![1, 2, 3, 4, 5]; + let doubled: Vec = my_map(&numbers, |&x| x * 2); + println!("{:?}", doubled); // [2, 4, 6, 8, 10] + + let words = vec!["hello", "world"]; + let upper: Vec = my_map(&words, |&s| s.to_uppercase()); + println!("{:?}", upper); // ["HELLO", "WORLD"] +} +``` + +
+ +### Exercise 3: Filter with Closure + +Create a flexible filter function: +- Takes a slice and predicate closure +- Returns items matching predicate +- Show usage with multiple predicate types + +
+Solution + +```rust +fn my_filter(items: &[T], predicate: F) -> Vec<&T> +where + F: Fn(&T) -> bool, +{ + items.iter().filter(|x| predicate(x)).collect() +} + +fn main() { + let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + + // Filter evens + let evens = my_filter(&numbers, |&x| x % 2 == 0); + println!("{:?}", evens); // [2, 4, 6, 8, 10] + + // Filter greater than 5 + let large = my_filter(&numbers, |&x| x > 5); + println!("{:?}", large); // [6, 7, 8, 9, 10] + + // Filter range + let range = my_filter(&numbers, |&x| x >= 3 && x <= 7); + println!("{:?}", range); // [3, 4, 5, 6, 7] +} +``` + +
+ +## Next Steps + +Now that you understand functions and closures: +1. **[Module 6: Data Structures](./module-06-data-structures)** - Learn about structs, enums, and collections +2. Practice with functional patterns +3. Explore more iterator methods + +--- + +**Next:** [Module 6 - Data Structures](./module-06-data-structures) → diff --git a/content/docs/py2rust/module-05-functions.zh-cn.mdx b/content/docs/py2rust/module-05-functions.zh-cn.mdx new file mode 100644 index 0000000..cde5fb1 --- /dev/null +++ b/content/docs/py2rust/module-05-functions.zh-cn.mdx @@ -0,0 +1,973 @@ +--- +title: "模块 5:函数与闭包" +description: "通过与 Python 对比来掌握 Rust 的函数系统、闭包和函数式编程特性。" +--- + +# 模块 5:函数与闭包 + +函数在 Python 和 Rust 中都是一等公民,但 Rust 通过其类型系统、所有权和闭包特性将其提升到了新的高度。本模块将向你展示如何利用 Rust 强大的函数系统。 + +## 学习目标 + +完成本模块后,你将理解: +- ✅ 函数定义和签名 +- ✅ 参数、参数传递 +- ✅ 返回类型和 `return` 关键字 +- ✅ 闭包及其捕获模式 +- ✅ 作为值的函数指针和闭包 +- ✅ 高阶函数 +- ✅ 迭代器和函数式编程模式 +- ✅ 闭包 vs 函数的性能 + +## 函数定义 + +### 基本函数语法 + + +```python !! py +# Python: 动态、灵活的函数 +def greet(name): + return f"Hello, {name}!" + +def add(a, b): + return a + b + +def no_return(): + print("Side effect") + # 隐式返回 None +``` + +```rust !! rs +// Rust: 静态类型,显式签名 +fn greet(name: &str) -> String { + format!("Hello, {}!", name) +} + +fn add(a: i32, b: i32) -> i32 { + a + b // 无分号 = 返回值 +} + +fn no_return() { + println!("Side effect"); + // 隐式返回 () +} +``` + + +### 类型注解是必需的 + + +```python !! py +# Python: 类型提示可选 +def add(a: int, b: int) -> int: + return a + b + +def add_no_hints(a, b): + return a + b # 可以正常工作! + +def flexible(x): + if x > 0: + return 42 + return "negative" # 不同类型也可以 +``` + +```rust !! rs +// Rust: 参数和返回值需要类型注解 +fn add(a: i32, b: i32) -> i32 { + a + b +} + +// fn add_no_hints(a, b) { // 错误:需要类型 +// a + b +// } + +// 不能返回不同的类型 +// fn flexible(x: i32) -> ??? { // 什么类型? +// if x > 0 { +// return 42; +// } +// return "negative"; // 错误:类型不匹配 +// } + +// 使用枚举处理不同返回类型 +enum Either { + Number(i32), + Text(&'static str), +} + +fn flexible(x: i32) -> Either { + if x > 0 { + Either::Number(42) + } else { + Either::Text("negative") + } +} +``` + + +### 多返回值 + + +```python !! py +# Python: 使用元组返回多个值 +def divide_and_remainder(a, b): + quotient = a // b + remainder = a % b + return quotient, remainder + +q, r = divide_and_remainder(10, 3) + +# 可以返回任意数量的值 +def many_returns(): + return 1, 2, 3, 4, 5 + +a, b, c, d, e = many_returns() +``` + +```rust !! rs +// Rust: 使用元组返回多个值 +fn divide_and_remainder(a: i32, b: i32) -> (i32, i32) { + (a / b, a % b) +} + +let (q, r) = divide_and_remainder(10, 3); + +// 可以返回任意数量的值 +fn many_returns() -> (i32, i32, i32, i32, i32) { + (1, 2, 3, 4, 5) +} + +let (a, b, c, d, e) = many_returns(); + +// 使用命名字段提高清晰度 +struct DivisionResult { + quotient: i32, + remainder: i32, +} + +fn divide_struct(a: i32, b: i32) -> DivisionResult { + DivisionResult { + quotient: a / b, + remainder: a % b, + } +} +``` + + +## 参数和参数传递 + +### 值传递 vs 引用传递 + + +```python !! py +# Python: 一切都是引用(对象) +def modify_list(items): + items.append(4) # 修改原始列表! + print(items) + +my_list = [1, 2, 3] +modify_list(my_list) +print(my_list) # [1, 2, 3, 4] + +# 但重新赋值不影响调用者 +def reassign_list(items): + items = [4, 5, 6] # 只影响局部变量 + +my_list = [1, 2, 3] +reassign_list(my_list) +print(my_list) # [1, 2, 3] +``` + +```rust !! rs +// Rust: 显式的所有权和借用 +fn modify_list(items: &mut Vec) { + items.push(4); // 修改原始列表 + println!("{:?}", items); +} + +let mut my_list = vec![1, 2, 3]; +modify_list(&mut my_list); +println!("{:?}", my_list); // [1, 2, 3, 4] + +// 获取所有权(消耗向量) +fn consume_list(items: Vec) { + println!("{:?}", items); +} // items 在此处被丢弃 + +let my_list = vec![1, 2, 3]; +consume_list(my_list); +// println!("{:?}", my_list); // 错误:值已被移动 + +// 不修改地借用 +fn read_list(items: &[i32]) { + println!("{:?}", items); +} + +let my_list = vec![1, 2, 3]; +read_list(&my_list); +println!("{:?}", my_list); // 仍然可访问 +``` + + +### 默认参数 + + +```python !! py +# Python: 默认参数 +def greet(name, greeting="Hello"): + return f"{greeting}, {name}!" + +print(greet("Alice")) # Hello, Alice! +print(greet("Bob", "Hi")) # Hi, Bob! + +# 可以有多个默认值 +def func(a, b=1, c=2): + return a + b + c + +print(func(1)) # 4 +print(func(1, 10)) # 13 +print(func(1, 10, 20)) # 31 +``` + +```rust !! rs +// Rust: 没有默认参数(使用方法或构建器) +fn greet(name: &str, greeting: &str) -> String { + format!("{}, {}!", greeting, name) +} + +println!("{}", greet("Alice", "Hello")); +println!("{}", greet("Bob", "Hi")); + +// 模式:使用 Option 表示默认值 +fn greet_optional(name: &str, greeting: Option<&str>) -> String { + let greeting = greeting.unwrap_or("Hello"); + format!("{}, {}!", greeting, name) +} + +println!("{}", greet_optional("Alice", None)); +println!("{}", greet_optional("Bob", Some("Hi"))); + +// 模式:使用构建器模式 +struct GreetingBuilder { + name: String, + greeting: Option, +} + +impl GreetingBuilder { + fn new(name: &str) -> Self { + GreetingBuilder { + name: name.to_string(), + greeting: None, + } + } + + fn greeting(mut self, greeting: &str) -> Self { + self.greeting = Some(greeting.to_string()); + self + } + + fn build(self) -> String { + let greeting = self.greeting.as_deref().unwrap_or("Hello"); + format!("{}, {}!", greeting, self.name) + } +} + +println!("{}", GreetingBuilder::new("Alice").build()); +println!("{}", GreetingBuilder::new("Bob").greeting("Hi").build()); +``` + + +### 可变参数 + + +```python !! py +# Python: *args 和 **kwargs +def sum_all(*args): + return sum(args) + +print(sum_all(1, 2, 3)) # 6 +print(sum_all(1, 2, 3, 4, 5)) # 15 + +def print_info(**kwargs): + for key, value in kwargs.items(): + print(f"{key}: {value}") + +print_info(name="Alice", age=25) + +def combined(a, b, *args, **kwargs): + print(f"a={a}, b={b}") + print(f"args={args}") + print(f"kwargs={kwargs}") +``` + +```rust !! rs +// Rust: 使用迭代器、切片或宏 +fn sum_all(numbers: &[i32]) -> i32 { + numbers.iter().sum() +} + +println!("{}", sum_all(&[1, 2, 3])); // 6 +println!("{}", sum_all(&[1, 2, 3, 4, 5])); // 15 + +// 对于真正的可变参数,使用宏 +macro_rules! sum_all_macro { + ($($x:expr),*) => {{ + let mut sum = 0; + $(sum += $x;)* + sum + }}; +} + +println!("{}", sum_all_macro!(1, 2, 3)); // 6 + +// 对于异构数据,使用结构体或枚举 +struct Info { + data: std::collections::HashMap, +} + +fn print_info(info: &Info) { + for (key, value) in &info.data { + println!("{}: {}", key, value); + } +} +``` + + +## 返回值 + +### 基于表达式的返回 + + +```python !! py +# Python: 需要显式 return +def add(a, b): + return a + b + +def multiple_returns(x): + if x > 0: + return "positive" + return "non-positive" + +def no_return(): + pass # 返回 None +``` + +```rust !! rs +// Rust: 最后一个表达式是返回值 +fn add(a: i32, b: i32) -> i32 { + a + b // 无分号 = 返回值 +} + +fn multiple_returns(x: i32) -> &'static str { + if x > 0 { + "positive" // 无分号 + } else { + "non-positive" // 无分号 + } +} + +fn no_return() { + // 隐式返回 () +} + +// 可以使用 return 关键字进行提前返回 +fn early_return(x: i32) -> i32 { + if x < 0 { + return 0; // return 可以有分号 + } + x * 2 +} +``` + + +### 永不返回类型 + + +```python !! py +# Python: 永不返回的函数 +def forever(): + while True: + pass + +def error_out(): + raise ValueError("Error") +``` + +```rust !! rs +// Rust: ! 永不类型 +fn forever() -> ! { + loop {} // 永不返回 +} + +fn error_out() -> ! { + panic!("Error"); // 永不返回 +} + +// 可以在任何需要类型的地方使用 ! +fn always_errors() -> ! { + panic!("Always errors"); +} + +let x: i32 = always_errors(); // OK,! 可以强制转换为任何类型 + +// 在 match 分支中 +fn get_number(opt: Option) -> i32 { + match opt { + Some(num) => num, + None => panic!("No number"), // ! 与 i32 兼容 + } +} +``` + + +## 闭包 + +闭包是可以捕获其环境的匿名函数。 + + +```python !! py +# Python: Lambda 函数 +add = lambda x, y: x + y +print(add(1, 2)) # 3 + +# 也可以使用 def +def make_adder(n): + def adder(x): + return x + n + return adder + +add_5 = make_adder(5) +print(add_5(10)) # 15 +``` + +```rust !! rs +// Rust: 类型推断的闭包 +let add = |x: i32, y: i32| -> i32 { x + y }; +println!("{}", add(1, 2)); // 3 + +// 类型通常可以推断 +let add = |x, y| x + y; +println!("{}", add(1i32, 2)); // 3 + +// 捕获环境 +fn make_adder(n: i32) -> impl Fn(i32) -> i32 { + move |x| x + n // move 关键字转移所有权 +} + +let add_5 = make_adder(5); +println!("{}", add_5(10)); // 15 +``` + + +### 闭包捕获模式 + + +```python !! py +# Python: 通过引用捕获 +def make_counter(): + count = 0 + def increment(): + nonlocal count + count += 1 + return count + return increment + +counter = make_counter() +print(counter()) # 1 +print(counter()) # 2 +``` + +```rust !! rs +// Rust: 三种捕获模式 +// 1. 借用 (Fn) +let data = vec![1, 2, 3]; +let borrow = || { + println!("{:?}", data); // 借用 data +}; +borrow(); +println!("{:?}", data); // 仍然可访问 + +// 2. 可变借用 (FnMut) +let mut data = vec![1, 2, 3]; +let mut borrow_mut = || { + data.push(4); // 可变借用 data +}; +borrow_mut(); +println!("{:?}", data); // [1, 2, 3, 4] + +// 3. 获取所有权 (使用 move 的 FnOnce) +let data = vec![1, 2, 3]; +let take_ownership = move || { + println!("{:?}", data); // 移动 data +}; +take_ownership(); +// println!("{:?}", data); // 错误:data 已被移动 + +// 函数特质 +// Fn: 可以多次调用,借用 +// FnMut: 可以多次调用,可变借用 +// FnOnce: 只能调用一次,消耗捕获的值 +``` + + +### 闭包类型推断 + + +```python !! py +# Python: 动态类型 +def apply(func, value): + return func(value) + +double = lambda x: x * 2 +print(apply(double, 5)) # 10 + +to_upper = lambda s: s.upper() +print(apply(to_upper, "hello")) # HELLO +``` + +```rust !! rs +// Rust: 闭包类型是推断的 +fn apply(func: F, value: T) -> R +where + F: Fn(T) -> R, +{ + func(value) +} + +let double = |x: i32| x * 2; +println!("{}", apply(double, 5)); // 10 + +// 不能对不同的闭包类型使用 apply +// let to_upper = |s: &str| s.to_uppercase(); +// println!("{}", apply(to_upper, "hello")); // 错误:类型不匹配 + +// 但具有相同签名的闭包是兼容的 +let add = |x: i32| x + 10; +let sub = |x: i32| x - 10; +fn use_func(f: impl Fn(i32) -> i32, x: i32) -> i32 { + f(x) +} +println!("{}", use_func(add, 5)); // 15 +println!("{}", use_func(sub, 5)); // -5 +``` + + +### 闭包性能 + + +```python !! py +# Python: 函数调用开销 +def apply_function(func, items): + return [func(x) for x in items] + +items = list(range(1000000)) +result = apply_function(lambda x: x * 2, items) +``` + +```rust !! rs +// Rust: 闭包通常是零成本抽象 +fn apply_function(func: F, items: &[i32]) -> Vec +where + F: Fn(i32) -> i32, +{ + items.iter().map(|&x| func(x)).collect() +} + +let items: Vec = (0..1000000).collect(); +let result = apply_function(|x| x * 2, &items); + +// 闭包编译为常规函数 +// 编译器可以内联它们 +// 相比函数没有运行时开销 +``` + + +## 高阶函数 + +接受或返回其他函数的函数。 + + +```python !! py +# Python: 函数作为值 +def apply_operation(x, y, operation): + return operation(x, y) + +def add(a, b): + return a + b + +def multiply(a, b): + return a * b + +print(apply_operation(5, 3, add)) # 8 +print(apply_operation(5, 3, multiply)) # 15 + +# 返回函数 +def make_operation(operation): + if operation == "add": + return lambda x, y: x + y + elif operation == "multiply": + return lambda x, y: x * y + +add_func = make_operation("add") +print(add_func(5, 3)) # 8 +``` + +```rust !! rs +// Rust: 函数指针和闭包 +type BinaryOp = fn(i32, i32) -> i32; + +fn apply_operation(x: i32, y: i32, operation: BinaryOp) -> i32 { + operation(x, y) +} + +fn add(a: i32, b: i32) -> i32 { + a + b +} + +fn multiply(a: i32, b: i32) -> i32 { + a * b +} + +println!("{}", apply_operation(5, 3, add)); // 8 +println!("{}", apply_operation(5, 3, multiply)); // 15 + +// 返回闭包(需要 impl Fn) +fn make_operation(operation: &str) -> impl Fn(i32, i32) -> i32 { + move |x, y| match operation { + "add" => x + y, + "multiply" => x * y, + _ => panic!("Unknown operation"), + } +} + +let add_func = make_operation("add"); +println!("{}", add_func(5, 3)); // 8 +``` + + +## 迭代器和闭包 + +Rust 的迭代器方法与闭包无缝协作。 + + +```python !! py +# Python: 列表推导式和函数 +items = [1, 2, 3, 4, 5] + +# 映射 +doubled = [x * 2 for x in items] + +# 过滤 +evens = [x for x in items if x % 2 == 0] + +# 自定义操作 +def process(x): + return x * 2 if x % 2 == 0 else x + +processed = [process(x) for x in items] +``` + +```rust !! rs +// Rust: 使用闭包的迭代器方法 +let items = vec![1, 2, 3, 4, 5]; + +// 映射 +let doubled: Vec = items.iter().map(|x| x * 2).collect(); + +// 过滤 +let evens: Vec<&i32> = items.iter().filter(|&x| x % 2 == 0).collect(); + +// 自定义操作 +let processed: Vec = items.iter() + .map(|&x| if x % 2 == 0 { x * 2 } else { x }) + .collect(); + +// 链式调用 +let result: Vec = items.iter() + .filter(|&&x| x % 2 == 0) + .map(|&x| x * 2) + .take(2) + .collect(); +``` + + +### 常见迭代器模式 + + +```python !! py +# Python: 常见模式 +items = [1, 2, 3, 4, 5] + +# 平方和 +sum_squares = sum(x * x for x in items) + +# 第一个匹配项 +first_large = next((x for x in items if x > 3), None) + +# 全部/任意 +all_positive = all(x > 0 for x in items) +any_large = any(x > 10 for x in items) + +# 折叠/归约 +total = sum(items) +product = 1 +for x in items: + product *= x +``` + +```rust !! rs +// Rust: 迭代器模式 +let items = vec![1, 2, 3, 4, 5]; + +// 平方和 +let sum_squares: i32 = items.iter().map(|&x| x * x).sum(); + +// 第一个匹配项 +let first_large = items.iter().find(|&&x| x > 3); + +// 全部/任意 +let all_positive = items.iter().all(|&x| x > 0); +let any_large = items.iter().any(|&x| x > 10); + +// 折叠/归约 +let total: i32 = items.iter().sum(); +let product: i32 = items.iter().product(); +``` + + +## 综合运用 + +让我们构建一个完整的例子: + + +```python !! py +from typing import List, Callable, Optional + +def process_numbers( + numbers: List[int], + transform: Callable[[int], int], + predicate: Callable[[int], bool] +) -> List[int]: + """使用转换处理数字并按谓词过滤。""" + result = [] + for num in numbers: + if predicate(num): + result.append(transform(num)) + return result + +def double(x: int) -> int: + return x * 2 + +def is_positive(x: int) -> bool: + return x > 0 + +# 使用 +numbers = [1, -2, 3, -4, 5] +result = process_numbers(numbers, double, is_positive) +print(result) # [2, 6, 10] + +# 使用 lambda +result = process_numbers(numbers, lambda x: x * 3, is_positive) +print(result) # [3, 9, 15] + +# 组合操作 +def compose(f, g): + return lambda x: f(g(x)) + +double_then_square = compose(lambda x: x * x, double) +print(double_then_square(5)) # 100 +``` + +```rust !! rs +use std::collections::HashMap; + +fn process_numbers( + numbers: &[i32], + transform: F, + predicate: P, +) -> Vec +where + F: Fn(i32) -> i32, + P: Fn(i32) -> bool, +{ + numbers + .iter() + .filter(|&&x| predicate(x)) + .map(|&x| transform(x)) + .collect() +} + +fn double(x: i32) -> i32 { + x * 2 +} + +fn is_positive(x: i32) -> bool { + x > 0 +} + +fn main() { + // 使用 + let numbers = vec![1, -2, 3, -4, 5]; + let result = process_numbers(&numbers, double, is_positive); + println!("{:?}", result); // [2, 6, 10] + + // 使用闭包 + let result = process_numbers(&numbers, |x| x * 3, is_positive); + println!("{:?}", result); // [3, 9, 15] + + // 组合操作 + fn compose(f: F, g: G) -> impl Fn(A) -> C + where + F: Fn(B) -> C, + G: Fn(A) -> B, + { + move |x| f(g(x)) + } + + let double_then_square = compose(|x: i32| x * x, double); + println!("{}", double_then_square(5)); // 100 + + // 实际例子:计数单词 + let text = "hello world hello rust world"; + let mut counts = HashMap::new(); + + for word in text.split_whitespace() { + *counts.entry(word).or_insert(0) += 1; + } + + println!("{:?}", counts); // {"hello": 2, "world": 2, "rust": 1} +} +``` + + +## 总结 + +在本模块中,你学习了: +- ✅ 函数签名需要显式类型 +- ✅ 返回值是最后一个表达式 +- ✅ 闭包有三种捕获环境的方式 +- ✅ 函数特质:Fn、FnMut、FnOnce +- ✅ 使用函数指针的高阶函数 +- ✅ 迭代器方法广泛使用闭包 +- ✅ 闭包是零成本抽象 + +## 与 Python 的主要区别 + +1. **类型注解**: 所有参数和返回值都需要 +2. **返回值**: 最后一个表达式,不是显式的 `return` +3. **闭包捕获**: 显式的所有权(move vs 借用) +4. **没有默认参数**: 使用构建器或 Option +5. **函数特质**: 三种特质用于不同捕获模式 +6. **闭包高效**: 通常编译为内联代码 +7. **没有可变参数**: 使用切片、迭代器或宏 + +## 练习 + +### 练习 1: 自定义迭代器 + +创建一个返回闭包的函数: +- 闭包应该维护计数器 +- 每次调用返回下一个数字 +- 使用 `FnMut` 特质 + +
+解决方案 + +```rust +fn make_counter(start: i32) -> impl FnMut() -> i32 { + let mut counter = start; + move || { + let current = counter; + counter += 1; + current + } +} + +fn main() { + let mut count = make_counter(0); + println!("{}", count()); // 0 + println!("{}", count()); // 1 + println!("{}", count()); // 2 +} +``` + +
+ +### 练习 2: 高阶映射 + +实现一个通用的映射函数: +- 接受一个切片和转换函数 +- 返回包含转换后值的新向量 +- 使用泛型和 impl Fn + +
+解决方案 + +```rust +fn my_map(items: &[T], f: F) -> Vec +where + F: Fn(&T) -> U, +{ + items.iter().map(f).collect() +} + +fn main() { + let numbers = vec![1, 2, 3, 4, 5]; + let doubled: Vec = my_map(&numbers, |&x| x * 2); + println!("{:?}", doubled); // [2, 4, 6, 8, 10] + + let words = vec!["hello", "world"]; + let upper: Vec = my_map(&words, |&s| s.to_uppercase()); + println!("{:?}", upper); // ["HELLO", "WORLD"] +} +``` + +
+ +### 练习 3: 使用闭包过滤 + +创建一个灵活的过滤函数: +- 接受一个切片和谓词闭包 +- 返回匹配谓词的项目 +- 展示多种谓词类型的使用 + +
+解决方案 + +```rust +fn my_filter(items: &[T], predicate: F) -> Vec<&T> +where + F: Fn(&T) -> bool, +{ + items.iter().filter(|x| predicate(x)).collect() +} + +fn main() { + let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + + // 过滤偶数 + let evens = my_filter(&numbers, |&x| x % 2 == 0); + println!("{:?}", evens); // [2, 4, 6, 8, 10] + + // 过滤大于 5 的数 + let large = my_filter(&numbers, |&x| x > 5); + println!("{:?}", large); // [6, 7, 8, 9, 10] + + // 过滤范围 + let range = my_filter(&numbers, |&x| x >= 3 && x <= 7); + println!("{:?}", range); // [3, 4, 5, 6, 7] +} +``` + +
+ +## 下一步 + +现在你已经理解了函数和闭包: +1. **[模块 6: 数据结构](./module-06-data-structures)** - 学习结构体、枚举和集合 +2. 练习函数式模式 +3. 探索更多迭代器方法 + +--- + +**下一节:** [模块 6 - 数据结构](./module-06-data-structures) → diff --git a/content/docs/py2rust/module-05-functions.zh-tw.mdx b/content/docs/py2rust/module-05-functions.zh-tw.mdx new file mode 100644 index 0000000..0662077 --- /dev/null +++ b/content/docs/py2rust/module-05-functions.zh-tw.mdx @@ -0,0 +1,561 @@ +--- +title: "模組 5:函數與閉包" +description: "透過與 Python 比較來掌握 Rust 的函數系統、閉包和函數式程式設計特性。" +--- + +# 模組 5:函數與閉包 + +函數在 Python 和 Rust 中都是一等公民,但 Rust 透過其類型系統、所有權和閉包特性將其提升到了新的高度。本模組將向你展示如何利用 Rust 強大的函數系統。 + +## 學習目標 + +完成本模組後,你將理解: +- ✅ 函數定義和簽名 +- ✅ 參數、參數傳遞 +- ✅ 返回類型和 `return` 關鍵字 +- ✅ 閉包及其捕獲模式 +- ✅ 作為值的函數指標和閉包 +- ✅ 高階函數 +- ✅ 迭代器和函數式程式設計模式 +- ✅ 閉包 vs 函數的效能 + +## 函數定義 + +### 基本函數語法 + + +```python !! py +# Python: 動態、靈活的函數 +def greet(name): + return f"Hello, {name}!" + +def add(a, b): + return a + b + +def no_return(): + print("Side effect") + # 隱式返回 None +``` + +```rust !! rs +// Rust: 靜態類型,顯式簽名 +fn greet(name: &str) -> String { + format!("Hello, {}!", name) +} + +fn add(a: i32, b: i32) -> i32 { + a + b // 無分號 = 返回值 +} + +fn no_return() { + println!("Side effect"); + // 隱式返回 () +} +``` + + +### 類型註解是必需的 + + +```python !! py +# Python: 類型提示可選 +def add(a: int, b: int) -> int: + return a + b + +def add_no_hints(a, b): + return a + b # 可以正常工作! + +def flexible(x): + if x > 0: + return 42 + return "negative" # 不同類型也可以 +``` + +```rust !! rs +// Rust: 參數和返回值需要類型註解 +fn add(a: i32, b: i32) -> i32 { + a + b +} + +// fn add_no_hints(a, b) { // 錯誤:需要類型 +// a + b +// } + +// 不能返回不同的類型 +// fn flexible(x: i32) -> ??? { // 什麼類型? +// if x > 0 { +// return 42; +// } +// return "negative"; // 錯誤:類型不匹配 +// } + +// 使用枚舉處理不同返回類型 +enum Either { + Number(i32), + Text(&'static str), +} + +fn flexible(x: i32) -> Either { + if x > 0 { + Either::Number(42) + } else { + Either::Text("negative") + } +} +``` + + +### 多返回值 + + +```python !! py +# Python: 使用元組返回多個值 +def divide_and_remainder(a, b): + quotient = a // b + remainder = a % b + return quotient, remainder + +q, r = divide_and_remainder(10, 3) + +# 可以返回任意數量的值 +def many_returns(): + return 1, 2, 3, 4, 5 + +a, b, c, d, e = many_returns() +``` + +```rust !! rs +// Rust: 使用元組返回多個值 +fn divide_and_remainder(a: i32, b: i32) -> (i32, i32) { + (a / b, a % b) +} + +let (q, r) = divide_and_remainder(10, 3); + +// 可以返回任意數量的值 +fn many_returns() -> (i32, i32, i32, i32, i32) { + (1, 2, 3, 4, 5) +} + +let (a, b, c, d, e) = many_returns(); + +// 使用命名字段提高清晰度 +struct DivisionResult { + quotient: i32, + remainder: i32, +} + +fn divide_struct(a: i32, b: i32) -> DivisionResult { + DivisionResult { + quotient: a / b, + remainder: a % b, + } +} +``` + + +## 參數和參數傳遞 + +### 值傳遞 vs 引用傳遞 + + +```python !! py +# Python: 一切都是引用(物件) +def modify_list(items): + items.append(4) # 修改原始列表! + print(items) + +my_list = [1, 2, 3] +modify_list(my_list) +print(my_list) # [1, 2, 3, 4] + +# 但重新賦值不影響調用者 +def reassign_list(items): + items = [4, 5, 6] # 只影響局部變數 + +my_list = [1, 2, 3] +reassign_list(my_list) +print(my_list) # [1, 2, 3] +``` + +```rust !! rs +// Rust: 顯式的所有權和借用 +fn modify_list(items: &mut Vec) { + items.push(4); // 修改原始列表 + println!("{:?}", items); +} + +let mut my_list = vec![1, 2, 3]; +modify_list(&mut my_list); +println!("{:?}", my_list); // [1, 2, 3, 4] + +// 獲取所有權(消耗向量) +fn consume_list(items: Vec) { + println!("{:?}", items); +} // items 在此處被丟棄 + +let my_list = vec![1, 2, 3]; +consume_list(my_list); +// println!("{:?}", my_list); // 錯誤:值已被移動 + +// 不修改地借用 +fn read_list(items: &[i32]) { + println!("{:?}", items); +} + +let my_list = vec![1, 2, 3]; +read_list(&my_list); +println!("{:?}", my_list); // 仍然可訪問 +``` + + +### 預設參數 + + +```python !! py +# Python: 預設參數 +def greet(name, greeting="Hello"): + return f"{greeting}, {name}!" + +print(greet("Alice")) # Hello, Alice! +print(greet("Bob", "Hi")) # Hi, Bob! +``` + +```rust !! rs +// Rust: 沒有預設參數(使用方法或構建器) +fn greet(name: &str, greeting: &str) -> String { + format!("{}, {}!", greeting, name) +} + +// 使用 Option 表示預設值 +fn greet_optional(name: &str, greeting: Option<&str>) -> String { + let greeting = greeting.unwrap_or("Hello"); + format!("{}, {}!", greeting, name) +} +``` + + +## 返回值 + +### 基於表達式的返回 + + +```python !! py +# Python: 需要顯式 return +def add(a, b): + return a + b + +def multiple_returns(x): + if x > 0: + return "positive" + return "non-positive" +``` + +```rust !! rs +// Rust: 最後一個表達式是返回值 +fn add(a: i32, b: i32) -> i32 { + a + b // 無分號 = 返回值 +} + +fn multiple_returns(x: i32) -> &'static str { + if x > 0 { + "positive" // 無分號 + } else { + "non-positive" // 無分號 + } +} + +// 可以使用 return 關鍵字進行提前返回 +fn early_return(x: i32) -> i32 { + if x < 0 { + return 0; // return 可以有分號 + } + x * 2 +} +``` + + +## 閉包 + +閉包是可以捕獲其環境的匿名函數。 + + +```python !! py +# Python: Lambda 函數 +add = lambda x, y: x + y +print(add(1, 2)) # 3 + +# 也可以使用 def +def make_adder(n): + def adder(x): + return x + n + return adder + +add_5 = make_adder(5) +print(add_5(10)) # 15 +``` + +```rust !! rs +// Rust: 類型推斷的閉包 +let add = |x: i32, y: i32| -> i32 { x + y }; +println!("{}", add(1, 2)); // 3 + +// 類型通常可以推斷 +let add = |x, y| x + y; + +// 捕獲環境 +fn make_adder(n: i32) -> impl Fn(i32) -> i32 { + move |x| x + n // move 關鍵字轉移所有權 +} + +let add_5 = make_adder(5); +println!("{}", add_5(10)); // 15 +``` + + +### 閉包捕獲模式 + + +```python !! py +# Python: 透過引用捕獲 +def make_counter(): + count = 0 + def increment(): + nonlocal count + count += 1 + return count + return increment + +counter = make_counter() +print(counter()) # 1 +print(counter()) # 2 +``` + +```rust !! rs +// Rust: 三種捕獲模式 +// 1. 借用 (Fn) +let data = vec![1, 2, 3]; +let borrow = || { + println!("{:?}", data); // 借用 data +}; +borrow(); +println!("{:?}", data); // 仍然可訪問 + +// 2. 可變借用 (FnMut) +let mut data = vec![1, 2, 3]; +let mut borrow_mut = || { + data.push(4); // 可變借用 data +}; +borrow_mut(); +println!("{:?}", data); // [1, 2, 3, 4] + +// 3. 獲取所有權 (使用 move 的 FnOnce) +let data = vec![1, 2, 3]; +let take_ownership = move || { + println!("{:?}", data); // 移動 data +}; +take_ownership(); +// println!("{:?}", data); // 錯誤:data 已被移動 + +// 函數特質 +// Fn: 可以多次調用,借用 +// FnMut: 可以多次調用,可變借用 +// FnOnce: 只能調用一次,消耗捕獲的值 +``` + + +### 閉包效能 + + +```python !! py +# Python: 函數調用開銷 +def apply_function(func, items): + return [func(x) for x in items] + +items = list(range(1000000)) +result = apply_function(lambda x: x * 2, items) +``` + +```rust !! rs +// Rust: 閉包通常是零成本抽象 +fn apply_function(func: F, items: &[i32]) -> Vec +where + F: Fn(i32) -> i32, +{ + items.iter().map(|&x| func(x)).collect() +} + +let items: Vec = (0..1000000).collect(); +let result = apply_function(|x| x * 2, &items); + +// 閉包編譯為常規函數 +// 編譯器可以內聯它們 +// 相比函數沒有運行時開銷 +``` + + +## 高階函數 + +接受或返回其他函數的函數。 + + +```python !! py +# Python: 函數作為值 +def apply_operation(x, y, operation): + return operation(x, y) + +def add(a, b): + return a + b + +def multiply(a, b): + return a * b + +print(apply_operation(5, 3, add)) # 8 +print(apply_operation(5, 3, multiply)) # 15 +``` + +```rust !! rs +// Rust: 函數指標和閉包 +type BinaryOp = fn(i32, i32) -> i32; + +fn apply_operation(x: i32, y: i32, operation: BinaryOp) -> i32 { + operation(x, y) +} + +fn add(a: i32, b: i32) -> i32 { + a + b +} + +fn multiply(a: i32, b: i32) -> i32 { + a * b +} + +println!("{}", apply_operation(5, 3, add)); // 8 +println!("{}", apply_operation(5, 3, multiply)); // 15 +``` + + +## 迭代器和閉包 + +Rust 的迭代器方法與閉包無縫協作。 + + +```python !! py +# Python: 列表推導式和函數 +items = [1, 2, 3, 4, 5] + +# 映射 +doubled = [x * 2 for x in items] + +# 過濾 +evens = [x for x in items if x % 2 == 0] +``` + +```rust !! rs +// Rust: 使用閉包的迭代器方法 +let items = vec![1, 2, 3, 4, 5]; + +// 映射 +let doubled: Vec = items.iter().map(|x| x * 2).collect(); + +// 過濾 +let evens: Vec<&i32> = items.iter().filter(|&x| x % 2 == 0).collect(); + +// 鏈式調用 +let result: Vec = items.iter() + .filter(|&&x| x % 2 == 0) + .map(|&x| x * 2) + .take(2) + .collect(); +``` + + +## 總結 + +在本模組中,你學習了: +- ✅ 函數簽名需要顯式類型 +- ✅ 返回值是最後一個表達式 +- ✅ 閉包有三種捕獲環境的方式 +- ✅ 函數特質:Fn、FnMut、FnOnce +- ✅ 使用函數指標的高階函數 +- ✅ 迭代器方法廣泛使用閉包 +- ✅ 閉包是零成本抽象 + +## 與 Python 的主要差別 + +1. **類型註解**: 所有參數和返回值都需要 +2. **返回值**: 最後一個表達式,不是顯式的 `return` +3. **閉包捕獲**: 顯式的所有權(move vs 借用) +4. **沒有預設參數**: 使用構建器或 Option +5. **函數特質**: 三種特質用於不同捕獲模式 +6. **閉包高效**: 通常編譯為內聯代碼 + +## 練習 + +### 練習 1: 自定義迭代器 + +創建一個返回閉包的函數: +- 閉包應該維護計數器 +- 每次調用返回下一個數字 +- 使用 `FnMut` 特質 + +
+解決方案 + +```rust +fn make_counter(start: i32) -> impl FnMut() -> i32 { + let mut counter = start; + move || { + let current = counter; + counter += 1; + current + } +} + +fn main() { + let mut count = make_counter(0); + println!("{}", count()); // 0 + println!("{}", count()); // 1 + println!("{}", count()); // 2 +} +``` + +
+ +### 練習 2: 高階映射 + +實現一個通用的映射函數: +- 接受一個切片和轉換函數 +- 返回包含轉換後值的新向量 +- 使用泛型和 impl Fn + +
+解決方案 + +```rust +fn my_map(items: &[T], f: F) -> Vec +where + F: Fn(&T) -> U, +{ + items.iter().map(f).collect() +} + +fn main() { + let numbers = vec![1, 2, 3, 4, 5]; + let doubled: Vec = my_map(&numbers, |&x| x * 2); + println!("{:?}", doubled); // [2, 4, 6, 8, 10] +} +``` + +
+ +## 下一步 + +現在你已經理解了函數和閉包: +1. **[模組 6: 數據結構](./module-06-data-structures)** - 學習結構體、枚舉和集合 +2. 練習函數式模式 +3. 探索更多迭代器方法 + +--- + +**下一節:** [模組 6 - 數據結構](./module-06-data-structures) → diff --git a/content/docs/py2rust/module-06-data-structures.mdx b/content/docs/py2rust/module-06-data-structures.mdx new file mode 100644 index 0000000..ea0d1d9 --- /dev/null +++ b/content/docs/py2rust/module-06-data-structures.mdx @@ -0,0 +1,999 @@ +--- +title: "Module 6: Data Structures" +description: "Master Rust's data structures - structs, enums, and collections - by comparing them with Python's classes and data structures." +--- + +# Module 6: Data Structures + +Data structures are the building blocks of any program. While Python uses classes for everything, Rust provides specialized tools for different use cases: structs for data, enums for variants, and powerful collection types. + +## Learning Objectives + +By the end of this module, you'll understand: +- ✅ Structs: Named, Tuple, and Unit structs +- ✅ Enums: Data-carrying enums and Option/Result +- ✅ Methods: impl blocks and self +- ✅ Collections: Vec, HashMap, HashSet, and more +- ✅ Strings: String vs &str +- ✅ Arrays and Slices +- ✅ Pattern matching on data structures +- ✅ Deriving traits and custom implementations + +## Structs + +Structs are Rust's way of grouping related data together. + +### Named Field Structs + + +```python !! py +# Python: Using dataclass or class +from dataclasses import dataclass + +@dataclass +class Person: + name: str + age: int + email: str + +# Create instance +person = Person("Alice", 25, "alice@example.com") + +# Access fields +print(person.name) +print(person.age) + +# Update (creates new instance if frozen) +person.age = 26 +``` + +```rust !! rs +// Rust: Named field structs +struct Person { + name: String, + age: u32, + email: String, +} + +// Create instance +let person = Person { + name: String::from("Alice"), + age: 25, + email: String::from("alice@example.com"), +}; + +// Access fields +println!("{}", person.name); +println!("{}", person.age); + +// Update (requires mut) +let mut person = person; +person.age = 26; + +// Or create new instance with update syntax +let person2 = Person { + age: 27, + ..person // Move remaining fields +}; +``` + + +### Tuple Structs + + +```python !! py +# Python: Using namedtuple or tuples +from collections import namedtuple +Color = namedtuple('Color', ['red', 'green', 'blue']) + +black = Color(0, 0, 0) +print(black.red) + +# Or just use tuples +color = (255, 0, 0) +print(color[0]) # 255 +``` + +```rust !! rs +// Rust: Tuple structs +struct Color(u8, u8, u8); + +let black = Color(0, 0, 0); +println!("{}", black.0); // Access by index + +// Can also destructure +let Color(r, g, b) = black; +println!("R: {}, G: {}, B: {}", r, g, b); + +// Useful for single-field wrappers (newtype pattern) +struct Meters(u32); +struct Kilometers(u32); + +let distance = Meters(1000); +// Meters and Kilometers are different types! +``` + + +### Unit-Like Structs + + +```python !! py +# Python: Singleton or marker class +class Singleton: + instance = None + + def __new__(cls): + if cls.instance is None: + cls.instance = super().__new__(cls) + return cls.instance + +# Or just use a class for namespacing +class Marker: + pass +``` + +```rust !! rs +// Rust: Unit-like struct (no fields) +struct Singleton; + +impl Singleton { + fn instance() -> &'static Singleton { + // Implementation would use OnceLock or lazy_static + &Singleton + } +} + +// Marker types +struct Marker; + +// Useful for phantom types and markers +struct PhantomData; +``` + + +## Enums + +Enums are powerful in Rust - they can carry data! + + +```python !! py +# Python: Enum (from enum module) +from enum import Enum + +class Color(Enum): + RED = 1 + GREEN = 2 + BLUE = 3 + +color = Color.RED +print(color) # Color.RED +``` + +```rust !! rs +// Rust: Enums can carry data! +enum Color { + Red, + Green, + Blue, + RGB(u8, u8, u8), // Can carry data! + CMYK { c: u8, m: u8, y: u8, k: u8 }, // Or named fields +} + +let color = Color::Red; + +// Match on enum +match color { + Color::Red => println!("Red"), + Color::Green => println!("Green"), + Color::Blue => println!("Blue"), + Color::RGB(r, g, b) => println!("RGB: {},{},{}", r, g, b), + Color::CMYK { c, m, y, k } => println!("CMYK: {},{},{},{}", c, m, y, k), +} +``` + + +### Option and Result + + +```python !! py +# Python: None represents absence +def find_user(user_id): + if user_id == 1: + return {"name": "Alice"} + return None + +user = find_user(1) +if user is not None: + print(user["name"]) + +# Or use Optional type hints +from typing import Optional + +def find_user_typed(user_id: int) -> Optional[dict]: + if user_id == 1: + return {"name": "Alice"} + return None +``` + +```rust !! rs +// Rust: Option enum for absent values +fn find_user(user_id: i32) -> Option<&'static str> { + if user_id == 1 { + Some("Alice") + } else { + None + } +} + +let user = find_user(1); +if let Some(name) = user { + println!("{}", name); +} + +// Must handle None explicitly +let user = find_user(999); +match user { + Some(name) => println!("Found: {}", name), + None => println!("Not found"), +} + +// Use ? for early return +fn get_user_name(user_id: i32) -> Option<&'static str> { + let name = find_user(user_id)?; + Some(name) +} +``` + + + +```python !! py +# Python: Exceptions for errors +def divide(a, b): + if b == 0: + raise ValueError("Division by zero") + return a / b + +try: + result = divide(10, 0) +except ValueError as e: + print(f"Error: {e}") +``` + +```rust !! rs +// Rust: Result enum for errors +fn divide(a: f64, b: f64) -> Result { + if b == 0.0 { + Err(String::from("Division by zero")) + } else { + Ok(a / b) + } +} + +match divide(10.0, 0.0) { + Ok(result) => println!("Result: {}", result), + Err(e) => println!("Error: {}", e), +} + +// Use ? for error propagation +fn calculate() -> Result { + let result = divide(10.0, 2.0)?; + Ok(result * 2.0) +} +``` + + +## Methods + +Methods are functions defined within an `impl` block. + + +```python !! py +# Python: Methods in class +class Rectangle: + def __init__(self, width, height): + self.width = width + self.height = height + + def area(self): + return self.width * self.height + + def scale(self, factor): + return Rectangle(self.width * factor, self.height * factor) + +rect = Rectangle(10, 20) +print(rect.area()) # 200 +``` + +```rust !! rs +// Rust: Methods in impl block +struct Rectangle { + width: u32, + height: u32, +} + +impl Rectangle { + // Associated function (like static method) + fn new(width: u32, height: u32) -> Self { + Rectangle { width, height } + } + + // Method taking &self + fn area(&self) -> u32 { + self.width * self.height + } + + // Method taking &mut self + fn scale(&mut self, factor: u32) { + self.width *= factor; + self.height *= factor; + } + + // Method consuming self + fn into_square(self) -> Rectangle { + let size = self.width.max(self.height); + Rectangle { width: size, height: size } + } +} + +let rect = Rectangle::new(10, 20); +println!("{}", rect.area()); // 200 +``` + + +## Collections + +Rust provides powerful collection types in the standard library. + +### Vec (Dynamic Array) + + +```python !! py +# Python: List +items = [1, 2, 3, 4, 5] + +# Add elements +items.append(6) + +# Access +print(items[0]) # 1 + +# Iterate +for item in items: + print(item) + +# List comprehension +doubled = [x * 2 for x in items] +``` + +```rust !! rs +// Rust: Vec +let mut items = vec![1, 2, 3, 4, 5]; + +// Add elements +items.push(6); + +// Access +println!("{}", items[0]); // 1 + +// Iterate +for item in &items { + println!("{}", item); +} + +// With closure (map) +let doubled: Vec = items.iter().map(|x| x * 2).collect(); +``` + + +### HashMap + + +```python !! py +# Python: Dictionary +scores = {"Alice": 100, "Bob": 95} + +# Access +print(scores["Alice"]) + +# Add/Update +scores["Charlie"] = 90 + +# Check existence +if "Bob" in scores: + print(scores["Bob"]) + +# Get with default +print(scores.get("David", 0)) +``` + +```rust !! rs +// Rust: HashMap +use std::collections::HashMap; + +let mut scores = HashMap::new(); + +// Insert +scores.insert("Alice", 100); +scores.insert("Bob", 95); + +// Access +println!("{}", scores.get("Alice").unwrap_or(&0)); + +// Add/Update +scores.insert("Charlie", 90); + +// Check existence +if scores.contains_key("Bob") { + println!("{}", scores["Bob"]); +} + +// Entry API for complex operations +scores.entry("David").or_insert(0); +``` + + +### HashSet + + +```python !! py +# Python: Set +unique_numbers = {1, 2, 3, 4, 5} + +# Add +unique_numbers.add(6) + +# Check membership +if 3 in unique_numbers: + print("Found 3") + +# Set operations +set1 = {1, 2, 3} +set2 = {3, 4, 5} +print(set1 & set2) # Intersection: {3} +print(set1 | set2) # Union: {1, 2, 3, 4, 5} +``` + +```rust !! rs +// Rust: HashSet +use std::collections::HashSet; + +let mut unique_numbers: HashSet = [1, 2, 3, 4, 5].iter().cloned().collect(); + +// Add +unique_numbers.insert(6); + +// Check membership +if unique_numbers.contains(&3) { + println!("Found 3"); +} + +// Set operations +let set1: HashSet = [1, 2, 3].iter().cloned().collect(); +let set2: HashSet = [3, 4, 5].iter().cloned().collect(); + +// Intersection +let intersection: HashSet<&i32> = set1.intersection(&set2).collect(); +println!("{:?}", intersection); // {3} + +// Union +let union: HashSet = set1.union(&set2).cloned().collect(); +println!("{:?}", union); // {1, 2, 3, 4, 5} +``` + + +## Strings + +Rust has multiple string types, which can be confusing. + + +```python !! py +# Python: Just str +s1 = "Hello" +s2 = "World" +s3 = s1 + " " + s2 + +# Strings are immutable +# s1[0] = "h" # TypeError +``` + +```rust !! rs +// Rust: &str and String +let s1: &str = "Hello"; // String slice, immutable +let s2: String = String::from("World"); // Owned, growable + +let s3: String = s1.to_string() + " " + &s2; + +// Strings are immutable +// s1[0] = "h"; // ERROR: cannot mutate &str + +// But String can be modified +let mut s2 = String::from("World"); +s2.push_str("!"); +``` + + +### String Operations + + +```python !! py +# Python: String methods +s = "hello world" + +# Length +print(len(s)) # 11 + +# Substring +print(s[0:5]) # "hello" + +# Split +words = s.split() # ["hello", "world"] + +# Replace +new_s = s.replace("hello", "hi") + +# Trim +s2 = " text " +print(s2.strip()) +``` + +```rust !! rs +// Rust: String operations +let s = "hello world"; + +// Length (bytes, not chars!) +println!("{}", s.len()); // 11 + +// Substring (needs careful indexing) +println!("{}", &s[0..5]); // "hello" + +// Split +let words: Vec<&str> = s.split_whitespace().collect(); + +// Replace +let new_s = s.replace("hello", "hi"); + +// Trim +let s2 = " text "; +println!("{}", s2.trim()); + +// Iterate over characters +for c in s.chars() { + println!("{}", c); +} +``` + + +## Arrays and Slices + + +```python !! py +# Python: List (dynamic) +items = [1, 2, 3, 4, 5] + +# Can grow +items.append(6) + +# Slicing +subset = items[1:4] # [2, 3, 4] +``` + +```rust !! rs +// Rust: Arrays (fixed-size) +let items: [i32; 5] = [1, 2, 3, 4, 5]; + +// Cannot grow +// items.push(6); // ERROR: no method named `push` + +// Slices (views into arrays/Vecs) +let subset = &items[1..4]; // &[2, 3, 4] + +// Vec (dynamic) +let mut items_vec = vec![1, 2, 3, 4, 5]; +items_vec.push(6); + +// Slice of Vec +let subset_vec: &[i32] = &items_vec[1..4]; +``` + + +## Pattern Matching + +Pattern matching works great with data structures. + + +```python !! py +# Python: if-elif chains or isinstance +def process(value): + if isinstance(value, dict): + return "dict" + elif isinstance(value, list): + return "list" + elif isinstance(value, str): + return "str" + return "other" + +# Or match (Python 3.10+) +match value: + case {"name": name, "age": age}: + print(f"Person: {name}, {age}") + case [x, y, z]: + print(f"Three items: {x}, {y}, {z}") + case _: + print("Other") +``` + +```rust !! rs +// Rust: Powerful pattern matching +enum Value { + Number(i32), + Text(String), + Pair(i32, i32), +} + +fn process(value: &Value) -> &str { + match value { + Value::Number(n) if *n > 0 => "positive number", + Value::Number(_) => "number", + Value::Text(s) if !s.is_empty() => "non-empty text", + Value::Text(_) => "text", + Value::Pair(x, y) if x == y => "equal pair", + Value::Pair(_, _) => "pair", + } +} + +// Destructuring structs +struct Point { x: i32, y: i32 } + +let p = Point { x: 10, y: 20 }; +match p { + Point { x: 0, y } => println!("On y-axis at {}", y), + Point { x, y: 0 } => println!("On x-axis at {}", x), + Point { x, y } => println!("At ({}, {})", x, y), +} +``` + + +## Deriving Traits + +Rust can automatically implement common traits. + + +```python !! py +# Python: dataclasses with features +from dataclasses import dataclass, field + +@dataclass +class Person: + name: str + age: int + + def __str__(self): + return f"Person(name={self.name}, age={self.age})" + + def __eq__(self, other): + if not isinstance(other, Person): + return False + return self.name == other.name and self.age == other.age + +person1 = Person("Alice", 25) +person2 = Person("Alice", 25) +print(person1 == person2) # True +``` + +```rust !! rs +// Rust: Derive common traits +#[derive(Debug, Clone, PartialEq, Eq)] +struct Person { + name: String, + age: u32, +} + +let person1 = Person { + name: String::from("Alice"), + age: 25, +}; +let person2 = Person { + name: String::from("Alice"), + age: 25, +}; + +println!("{:?}", person1); // Debug output +println!("{}", person1 == person2); // true + +// Or use manual impl +impl std::fmt::Display for Person { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "Person(name={}, age={})", self.name, self.age) + } +} + +println!("{}", person1); // Uses Display +``` + + +## Putting It All Together + + +```python !! py +from dataclasses import dataclass +from typing import Optional +from enum import Enum + +class Status(Enum): + ACTIVE = "active" + INACTIVE = "inactive" + PENDING = "pending" + +@dataclass +class User: + id: int + name: str + email: str + status: Status + friends: list[int] + + def add_friend(self, friend_id: int) -> None: + if friend_id not in self.friends: + self.friends.append(friend_id) + + def is_active(self) -> bool: + return self.status == Status.ACTIVE + +def find_user(users: list[User], user_id: int) -> Optional[User]: + for user in users: + if user.id == user_id: + return user + return None + +# Usage +users = [ + User(1, "Alice", "alice@example.com", Status.ACTIVE, []), + User(2, "Bob", "bob@example.com", Status.INACTIVE, [1]), +] + +user = find_user(users, 1) +if user: + print(f"Found: {user.name}") + user.add_friend(2) +``` + +```rust !! rs +use std::collections::{HashMap, HashSet}; + +#[derive(Debug, Clone, PartialEq)] +enum Status { + Active, + Inactive, + Pending, +} + +#[derive(Debug, Clone)] +struct User { + id: u32, + name: String, + email: String, + status: Status, + friends: HashSet, +} + +impl User { + fn new(id: u32, name: &str, email: &str, status: Status) -> Self { + User { + id, + name: String::from(name), + email: String::from(email), + status, + friends: HashSet::new(), + } + } + + fn add_friend(&mut self, friend_id: u32) { + self.friends.insert(friend_id); + } + + fn is_active(&self) -> bool { + self.status == Status::Active + } +} + +fn find_user(users: &[User], user_id: u32) -> Option<&User> { + users.iter().find(|u| u.id == user_id) +} + +fn main() { + let mut users = vec![ + User::new(1, "Alice", "alice@example.com", Status::Active), + User::new(2, "Bob", "bob@example.com", Status::Inactive), + ]; + + users[1].add_friend(1); + + if let Some(user) = find_user(&users, 1) { + println!("Found: {}", user.name); + } + + // Using HashMap for faster lookups + let mut user_map: HashMap = users + .into_iter() + .map(|u| (u.id, u)) + .collect(); + + if let Some(user) = user_map.get(&1) { + println!("Found: {}", user.name); + } +} +``` + + +## Summary + +In this module, you learned: +- ✅ Structs group related data +- ✅ Enums represent variants with optional data +- ✅ Methods are defined in impl blocks +- ✅ Vec, HashMap, HashSet for collections +- ✅ String vs &str for strings +- ✅ Arrays (fixed) vs slices (views) +- ✅ Pattern matching on data structures +- ✅ Derive common traits automatically + +## Key Differences from Python + +1. **Structs not classes**: Data only, behavior in impl blocks +2. **No inheritance**: Use composition and traits +3. **Enum variants**: Can carry data +4. **Multiple string types**: String (owned) vs &str (borrowed) +5. **Fixed arrays**: [T; N] vs dynamic `Vec` +6. **Explicit mutability**: mut keyword required +7. **Pattern matching**: More powerful than if-elif + +## Exercises + +### Exercise 1: Implement a Stack + +Create a Stack data structure: +- Use generics for element type +- Implement push, pop, and is_empty methods +- Use Vec internally + +
+Solution + +```rust +#[derive(Debug)] +struct Stack { + items: Vec, +} + +impl Stack { + fn new() -> Self { + Stack { items: Vec::new() } + } + + fn push(&mut self, item: T) { + self.items.push(item); + } + + fn pop(&mut self) -> Option { + self.items.pop() + } + + fn is_empty(&self) -> bool { + self.items.is_empty() + } +} + +fn main() { + let mut stack = Stack::new(); + stack.push(1); + stack.push(2); + stack.push(3); + + while let Some(item) = stack.pop() { + println!("{}", item); + } +} +``` + +
+ +### Exercise 2: Generic Key-Value Store + +Create a generic key-value store: +- Store key-value pairs +- Implement get, set, and remove methods +- Use HashMap internally + +
+Solution + +```rust +use std::collections::HashMap; + +struct Store { + data: HashMap, +} + +impl Store +where + K: std::hash::Hash + Eq, +{ + fn new() -> Self { + Store { data: HashMap::new() } + } + + fn set(&mut self, key: K, value: V) { + self.data.insert(key, value); + } + + fn get(&self, key: &K) -> Option<&V> { + self.data.get(key) + } + + fn remove(&mut self, key: &K) -> Option { + self.data.remove(key) + } +} + +fn main() { + let mut store = Store::new(); + store.set("name", "Alice"); + store.set("age", 25); + + println!("{:?}", store.get(&"name")); +} +``` + +
+ +### Exercise 3: Result Wrapper + +Create an enum that wraps values with metadata: +- Variants for Success and Error +- Include timestamp and status code +- Implement Display trait + +
+Solution + +```rust +#[derive(Debug)] +enum Result { + Success { value: T, timestamp: u64 }, + Error { code: u32, message: String }, +} + +impl std::fmt::Display for Result { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Result::Success { value, timestamp } => { + write!(f, "[{}] Success: {}", timestamp, value) + } + Result::Error { code, message } => { + write!(f, "[{}] Error: {}", code, message) + } + } + } +} + +fn main() { + let success = Result::Success { value: 42, timestamp: 1234567890 }; + let error = Result::Error { code: 404, message: String::from("Not found") }; + + println!("{}", success); + println!("{}", error); +} +``` + +
+ +## Next Steps + +Now that you understand data structures: +1. **[Module 7: Error Handling](./module-07-error-handling)** - Learn Rust's error handling approach +2. Practice with structs and enums +3. Explore more collection types + +--- + +**Next:** [Module 7 - Error Handling](./module-07-error-handling) → diff --git a/content/docs/py2rust/module-06-data-structures.zh-cn.mdx b/content/docs/py2rust/module-06-data-structures.zh-cn.mdx new file mode 100644 index 0000000..5ac5349 --- /dev/null +++ b/content/docs/py2rust/module-06-data-structures.zh-cn.mdx @@ -0,0 +1,958 @@ +--- +title: "模块 6:数据结构" +description: "通过与 Python 的类和数据结构对比来掌握 Rust 的数据结构——结构体、枚举和集合。" +--- + +# 模块 6:数据结构 + +数据结构是任何程序的构建块。Python 使用类来处理一切,而 Rust 为不同用例提供了专门的工具:结构体用于数据,枚举用于变体,以及强大的集合类型。 + +## 学习目标 + +完成本模块后,你将理解: +- ✅ 结构体:命名结构体、元组结构体和单元结构体 +- ✅ 枚举:携带数据的枚举和 Option/Result +- ✅ 方法:impl 块和 self +- ✅ 集合:Vec、HashMap、HashSet 等 +- ✅ 字符串:String vs &str +- ✅ 数组和切片 +- ✅ 数据结构的模式匹配 +- ✅ 派生特质和自定义实现 + +## 结构体 + +结构体是 Rust 将相关数据分组在一起的方式。 + +### 命名字段结构体 + + +```python !! py +# Python: 使用 dataclass 或类 +from dataclasses import dataclass + +@dataclass +class Person: + name: str + age: int + email: str + +# 创建实例 +person = Person("Alice", 25, "alice@example.com") + +# 访问字段 +print(person.name) +print(person.age) + +# 更新(如果冻结则创建新实例) +person.age = 26 +``` + +```rust !! rs +// Rust: 命名字段结构体 +struct Person { + name: String, + age: u32, + email: String, +} + +// 创建实例 +let person = Person { + name: String::from("Alice"), + age: 25, + email: String::from("alice@example.com"), +}; + +// 访问字段 +println!("{}", person.name); +println!("{}", person.age); + +// 更新(需要 mut) +let mut person = person; +person.age = 26; + +// 或使用更新语法创建新实例 +let person2 = Person { + age: 27, + ..person // 移动剩余字段 +}; +``` + + +### 元组结构体 + + +```python !! py +# Python: 使用 namedtuple 或元组 +from collections import namedtuple +Color = namedtuple('Color', ['red', 'green', 'blue']) + +black = Color(0, 0, 0) +print(black.red) + +# 或直接使用元组 +color = (255, 0, 0) +print(color[0]) # 255 +``` + +```rust !! rs +// Rust: 元组结构体 +struct Color(u8, u8, u8); + +let black = Color(0, 0, 0); +println!("{}", black.0); // 通过索引访问 + +// 也可以解构 +let Color(r, g, b) = black; +println!("R: {}, G: {}, B: {}", r, g, b); + +// 用于单字段包装器(newtype 模式) +struct Meters(u32); +struct Kilometers(u32); + +let distance = Meters(1000); +// Meters 和 Kilometers 是不同的类型! +``` + + +### 类单元结构体 + + +```python !! py +# Python: 单例或标记类 +class Singleton: + instance = None + + def __new__(cls): + if cls.instance is None: + cls.instance = super().__new__(cls) + return cls.instance + +# 或仅使用类进行命名空间 +class Marker: + pass +``` + +```rust !! rs +// Rust: 类单元结构体(无字段) +struct Singleton; + +impl Singleton { + fn instance() -> &'static Singleton { + // 实现会使用 OnceLock 或 lazy_static + &Singleton + } +} + +// 标记类型 +struct Marker; + +// 用于幻影类型和标记 +struct PhantomData; +``` + + +## 枚举 + +Rust 中的枚举很强大——它们可以携带数据! + + +```python !! py +# Python: Enum(来自 enum 模块) +from enum import Enum + +class Color(Enum): + RED = 1 + GREEN = 2 + BLUE = 3 + +color = Color.RED +print(color) # Color.RED +``` + +```rust !! rs +// Rust: 枚举可以携带数据! +enum Color { + Red, + Green, + Blue, + RGB(u8, u8, u8), // 可以携带数据! + CMYK { c: u8, m: u8, y: u8, k: u8 }, // 或命名字段 +} + +let color = Color::Red; + +// 在枚举上匹配 +match color { + Color::Red => println!("Red"), + Color::Green => println!("Green"), + Color::Blue => println!("Blue"), + Color::RGB(r, g, b) => println!("RGB: {},{},{}", r, g, b), + Color::CMYK { c, m, y, k } => println!("CMYK: {},{},{},{}", c, m, y, k), +} +``` + + +### Option 和 Result + + +```python !! py +# Python: None 表示缺失 +def find_user(user_id): + if user_id == 1: + return {"name": "Alice"} + return None + +user = find_user(1) +if user is not None: + print(user["name"]) + +# 或使用 Optional 类型提示 +from typing import Optional + +def find_user_typed(user_id: int) -> Optional[dict]: + if user_id == 1: + return {"name": "Alice"} + return None +``` + +```rust !! rs +// Rust: Option 枚举用于缺失值 +fn find_user(user_id: i32) -> Option<&'static str> { + if user_id == 1 { + Some("Alice") + } else { + None + } +} + +let user = find_user(1); +if let Some(name) = user { + println!("{}", name); +} + +// 必须显式处理 None +let user = find_user(999); +match user { + Some(name) => println!("Found: {}", name), + None => println!("Not found"), +} + +// 使用 ? 进行提前返回 +fn get_user_name(user_id: i32) -> Option<&'static str> { + let name = find_user(user_id)?; + Some(name) +} +``` + + + +```python !! py +# Python: 使用异常处理错误 +def divide(a, b): + if b == 0: + raise ValueError("Division by zero") + return a / b + +try: + result = divide(10, 0) +except ValueError as e: + print(f"Error: {e}") +``` + +```rust !! rs +// Rust: Result 枚举用于错误 +fn divide(a: f64, b: f64) -> Result { + if b == 0.0 { + Err(String::from("Division by zero")) + } else { + Ok(a / b) + } +} + +match divide(10.0, 0.0) { + Ok(result) => println!("Result: {}", result), + Err(e) => println!("Error: {}", e), +} + +// 使用 ? 进行错误传播 +fn calculate() -> Result { + let result = divide(10.0, 2.0)?; + Ok(result * 2.0) +} +``` + + +## 方法 + +方法是在 `impl` 块中定义的函数。 + + +```python !! py +# Python: 类中的方法 +class Rectangle: + def __init__(self, width, height): + self.width = width + self.height = height + + def area(self): + return self.width * self.height + + def scale(self, factor): + return Rectangle(self.width * factor, self.height * factor) + +rect = Rectangle(10, 20) +print(rect.area()) # 200 +``` + +```rust !! rs +// Rust: impl 块中的方法 +struct Rectangle { + width: u32, + height: u32, +} + +impl Rectangle { + // 关联函数(类似静态方法) + fn new(width: u32, height: u32) -> Self { + Rectangle { width, height } + } + + // 接收 &self 的方法 + fn area(&self) -> u32 { + self.width * self.height + } + + // 接收 &mut self 的方法 + fn scale(&mut self, factor: u32) { + self.width *= factor; + self.height *= factor; + } + + // 消耗 self 的方法 + fn into_square(self) -> Rectangle { + let size = self.width.max(self.height); + Rectangle { width: size, height: size } + } +} + +let rect = Rectangle::new(10, 20); +println!("{}", rect.area()); // 200 +``` + + +## 集合 + +Rust 在标准库中提供了强大的集合类型。 + +### Vec (动态数组) + + +```python !! py +# Python: 列表 +items = [1, 2, 3, 4, 5] + +# 添加元素 +items.append(6) + +# 访问 +print(items[0]) # 1 + +# 迭代 +for item in items: + print(item) + +# 列表推导式 +doubled = [x * 2 for x in items] +``` + +```rust !! rs +// Rust: Vec +let mut items = vec![1, 2, 3, 4, 5]; + +// 添加元素 +items.push(6); + +// 访问 +println!("{}", items[0]); // 1 + +// 迭代 +for item in &items { + println!("{}", item); +} + +// 使用闭包(map) +let doubled: Vec = items.iter().map(|x| x * 2).collect(); +``` + + +### HashMap + + +```python !! py +# Python: 字典 +scores = {"Alice": 100, "Bob": 95} + +# 访问 +print(scores["Alice"]) + +# 添加/更新 +scores["Charlie"] = 90 + +# 检查存在性 +if "Bob" in scores: + print(scores["Bob"]) + +# 使用默认值获取 +print(scores.get("David", 0)) +``` + +```rust !! rs +// Rust: HashMap +use std::collections::HashMap; + +let mut scores = HashMap::new(); + +// 插入 +scores.insert("Alice", 100); +scores.insert("Bob", 95); + +// 访问 +println!("{}", scores.get("Alice").unwrap_or(&0)); + +// 添加/更新 +scores.insert("Charlie", 90); + +// 检查存在性 +if scores.contains_key("Bob") { + println!("{}", scores["Bob"]); +} + +// Entry API 用于复杂操作 +scores.entry("David").or_insert(0); +``` + + +### HashSet + + +```python !! py +# Python: 集合 +unique_numbers = {1, 2, 3, 4, 5} + +# 添加 +unique_numbers.add(6) + +# 检查成员资格 +if 3 in unique_numbers: + print("Found 3") + +# 集合操作 +set1 = {1, 2, 3} +set2 = {3, 4, 5} +print(set1 & set2) # 交集: {3} +print(set1 | set2) # 并集: {1, 2, 3, 4, 5} +``` + +```rust !! rs +// Rust: HashSet +use std::collections::HashSet; + +let mut unique_numbers: HashSet = [1, 2, 3, 4, 5].iter().cloned().collect(); + +// 添加 +unique_numbers.insert(6); + +// 检查成员资格 +if unique_numbers.contains(&3) { + println!("Found 3"); +} + +// 集合操作 +let set1: HashSet = [1, 2, 3].iter().cloned().collect(); +let set2: HashSet = [3, 4, 5].iter().cloned().collect(); + +// 交集 +let intersection: HashSet<&i32> = set1.intersection(&set2).collect(); +println!("{:?}", intersection); // {3} + +// 并集 +let union: HashSet = set1.union(&set2).cloned().collect(); +println!("{:?}", union); // {1, 2, 3, 4, 5} +``` + + +## 字符串 + +Rust 有多种字符串类型,这可能令人困惑。 + + +```python !! py +# Python: 只有 str +s1 = "Hello" +s2 = "World" +s3 = s1 + " " + s2 + +# 字符串不可变 +# s1[0] = "h" # TypeError +``` + +```rust !! rs +// Rust: &str 和 String +let s1: &str = "Hello"; // 字符串切片,不可变 +let s2: String = String::from("World"); // 拥有,可增长 + +let s3: String = s1.to_string() + " " + &s2; + +// 字符串不可变 +// s1[0] = "h"; // 错误:不能修改 &str + +// 但 String 可以修改 +let mut s2 = String::from("World"); +s2.push_str("!"); +``` + + +### 字符串操作 + + +```python !! py +# Python: 字符串方法 +s = "hello world" + +# 长度 +print(len(s)) # 11 + +# 子字符串 +print(s[0:5]) # "hello" + +# 分割 +words = s.split() # ["hello", "world"] + +# 替换 +new_s = s.replace("hello", "hi") + +# 去除空格 +s2 = " text " +print(s2.strip()) +``` + +```rust !! rs +// Rust: 字符串操作 +let s = "hello world"; + +// 长度(字节,不是字符!) +println!("{}", s.len()); // 11 + +// 子字符串(需要小心索引) +println!("{}", &s[0..5]); // "hello" + +// 分割 +let words: Vec<&str> = s.split_whitespace().collect(); + +// 替换 +let new_s = s.replace("hello", "hi"); + +// 去除空格 +let s2 = " text "; +println!("{}", s2.trim()); + +// 遍历字符 +for c in s.chars() { + println!("{}", c); +} +``` + + +## 数组和切片 + + +```python !! py +# Python: 列表(动态) +items = [1, 2, 3, 4, 5] + +# 可以增长 +items.append(6) + +# 切片 +subset = items[1:4] # [2, 3, 4] +``` + +```rust !! rs +// Rust: 数组(固定大小) +let items: [i32; 5] = [1, 2, 3, 4, 5]; + +// 不能增长 +// items.push(6); // 错误:没有名为 `push` 的方法 + +// 切片(数组/Vec 的视图) +let subset = &items[1..4]; // &[2, 3, 4] + +// Vec(动态) +let mut items_vec = vec![1, 2, 3, 4, 5]; +items_vec.push(6); + +// Vec 的切片 +let subset_vec: &[i32] = &items_vec[1..4]; +``` + + +## 模式匹配 + +模式匹配与数据结构配合得很好。 + + +```python !! py +# Python: if-elif 链或 isinstance +def process(value): + if isinstance(value, dict): + return "dict" + elif isinstance(value, list): + return "list" + elif isinstance(value, str): + return "str" + return "other" + +# 或 match(Python 3.10+) +match value: + case {"name": name, "age": age}: + print(f"Person: {name}, {age}") + case [x, y, z]: + print(f"Three items: {x}, {y}, {z}") + case _: + print("Other") +``` + +```rust !! rs +// Rust: 强大的模式匹配 +enum Value { + Number(i32), + Text(String), + Pair(i32, i32), +} + +fn process(value: &Value) -> &str { + match value { + Value::Number(n) if *n > 0 => "positive number", + Value::Number(_) => "number", + Value::Text(s) if !s.is_empty() => "non-empty text", + Value::Text(_) => "text", + Value::Pair(x, y) if x == y => "equal pair", + Value::Pair(_, _) => "pair", + } +} + +// 解构结构体 +struct Point { x: i32, y: i32 } + +let p = Point { x: 10, y: 20 }; +match p { + Point { x: 0, y } => println!("On y-axis at {}", y), + Point { x, y: 0 } => println!("On x-axis at {}", x), + Point { x, y } => println!("At ({}, {})", x, y), +} +``` + + +## 派生特质 + +Rust 可以自动实现常见特质。 + + +```python !! py +# Python: 带功能的 dataclasses +from dataclasses import dataclass, field + +@dataclass +class Person: + name: str + age: int + + def __str__(self): + return f"Person(name={self.name}, age={self.age})" + + def __eq__(self, other): + if not isinstance(other, Person): + return False + return self.name == other.name and self.age == other.age + +person1 = Person("Alice", 25) +person2 = Person("Alice", 25) +print(person1 == person2) # True +``` + +```rust !! rs +// Rust: 派生常见特质 +#[derive(Debug, Clone, PartialEq, Eq)] +struct Person { + name: String, + age: u32, +} + +let person1 = Person { + name: String::from("Alice"), + age: 25, +}; +let person2 = Person { + name: String::from("Alice"), + age: 25, +}; + +println!("{:?}", person1); // Debug 输出 +println!("{}", person1 == person2); // true + +// 或使用手动 impl +impl std::fmt::Display for Person { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "Person(name={}, age={})", self.name, self.age) + } +} + +println!("{}", person1); // 使用 Display +``` + + +## 综合运用 + + +```python !! py +from dataclasses import dataclass +from typing import Optional +from enum import Enum + +class Status(Enum): + ACTIVE = "active" + INACTIVE = "inactive" + PENDING = "pending" + +@dataclass +class User: + id: int + name: str + email: str + status: Status + friends: list[int] + + def add_friend(self, friend_id: int) -> None: + if friend_id not in self.friends: + self.friends.append(friend_id) + + def is_active(self) -> bool: + return self.status == Status.ACTIVE + +def find_user(users: list[User], user_id: int) -> Optional[User]: + for user in users: + if user.id == user_id: + return user + return None + +# 使用 +users = [ + User(1, "Alice", "alice@example.com", Status.ACTIVE, []), + User(2, "Bob", "bob@example.com", Status.INACTIVE, [1]), +] + +user = find_user(users, 1) +if user: + print(f"Found: {user.name}") + user.add_friend(2) +``` + +```rust !! rs +use std::collections::{HashMap, HashSet}; + +#[derive(Debug, Clone, PartialEq)] +enum Status { + Active, + Inactive, + Pending, +} + +#[derive(Debug, Clone)] +struct User { + id: u32, + name: String, + email: String, + status: Status, + friends: HashSet, +} + +impl User { + fn new(id: u32, name: &str, email: &str, status: Status) -> Self { + User { + id, + name: String::from(name), + email: String::from(email), + status, + friends: HashSet::new(), + } + } + + fn add_friend(&mut self, friend_id: u32) { + self.friends.insert(friend_id); + } + + fn is_active(&self) -> bool { + self.status == Status::Active + } +} + +fn find_user(users: &[User], user_id: u32) -> Option<&User> { + users.iter().find(|u| u.id == user_id) +} + +fn main() { + let mut users = vec![ + User::new(1, "Alice", "alice@example.com", Status::Active), + User::new(2, "Bob", "bob@example.com", Status::Inactive), + ]; + + users[1].add_friend(1); + + if let Some(user) = find_user(&users, 1) { + println!("Found: {}", user.name); + } + + // 使用 HashMap 进行更快的查找 + let mut user_map: HashMap = users + .into_iter() + .map(|u| (u.id, u)) + .collect(); + + if let Some(user) = user_map.get(&1) { + println!("Found: {}", user.name); + } +} +``` + + +## 总结 + +在本模块中,你学习了: +- ✅ 结构体将相关数据分组 +- ✅ 枚举表示带可选数据的变体 +- ✅ 方法在 impl 块中定义 +- ✅ Vec、HashMap、HashSet 用于集合 +- ✅ String vs &str 用于字符串 +- ✅ 数组(固定)vs 切片(视图) +- ✅ 数据结构的模式匹配 +- ✅ 自动派生常见特质 + +## 与 Python 的主要区别 + +1. **结构体不是类**: 仅数据,行为在 impl 块中 +2. **没有继承**: 使用组合和特质 +3. **枚举变体**: 可以携带数据 +4. **多种字符串类型**: String(拥有)vs &str(借用) +5. **固定数组**: [T; N] vs 动态 `Vec` +6. **显式可变性**: 需要 mut 关键字 +7. **模式匹配**: 比 if-elif 更强大 + +## 练习 + +### 练习 1: 实现栈 + +创建一个栈数据结构: +- 使用泛型表示元素类型 +- 实现 push、pop 和 is_empty 方法 +- 内部使用 Vec + +
+解决方案 + +```rust +#[derive(Debug)] +struct Stack { + items: Vec, +} + +impl Stack { + fn new() -> Self { + Stack { items: Vec::new() } + } + + fn push(&mut self, item: T) { + self.items.push(item); + } + + fn pop(&mut self) -> Option { + self.items.pop() + } + + fn is_empty(&self) -> bool { + self.items.is_empty() + } +} + +fn main() { + let mut stack = Stack::new(); + stack.push(1); + stack.push(2); + stack.push(3); + + while let Some(item) = stack.pop() { + println!("{}", item); + } +} +``` + +
+ +### 练习 2: 泛型键值存储 + +创建一个泛型键值存储: +- 存储键值对 +- 实现 get、set 和 remove 方法 +- 内部使用 HashMap + +
+解决方案 + +```rust +use std::collections::HashMap; + +struct Store { + data: HashMap, +} + +impl Store +where + K: std::hash::Hash + Eq, +{ + fn new() -> Self { + Store { data: HashMap::new() } + } + + fn set(&mut self, key: K, value: V) { + self.data.insert(key, value); + } + + fn get(&self, key: &K) -> Option<&V> { + self.data.get(key) + } + + fn remove(&mut self, key: &K) -> Option { + self.data.remove(key) + } +} + +fn main() { + let mut store = Store::new(); + store.set("name", "Alice"); + store.set("age", 25); + + println!("{:?}", store.get(&"name")); +} +``` + +
+ +## 下一步 + +现在你已经理解了数据结构: +1. **[模块 7: 错误处理](./module-07-error-handling)** - 学习 Rust 的错误处理方法 +2. 练习使用结构体和枚举 +3. 探索更多集合类型 + +--- + +**下一节:** [模块 7 - 错误处理](./module-07-error-handling) → diff --git a/content/docs/py2rust/module-06-data-structures.zh-tw.mdx b/content/docs/py2rust/module-06-data-structures.zh-tw.mdx new file mode 100644 index 0000000..c6cceac --- /dev/null +++ b/content/docs/py2rust/module-06-data-structures.zh-tw.mdx @@ -0,0 +1,818 @@ +--- +title: "模組 6:數據結構" +description: "透過與 Python 的類和數據結構比較來掌握 Rust 的數據結構——結構體、枚舉和集合。" +--- + +# 模組 6:數據結構 + +數據結構是任何程式的構建塊。Python 使用類來處理一切,而 Rust 為不同用例提供了專門的工具:結構體用於數據,枚舉用於變體,以及強大的集合類型。 + +## 學習目標 + +完成本模組後,你將理解: +- ✅ 結構體:命名結構體、元組結構體和單元結構體 +- ✅ 枚舉:攜帶數據的枚舉和 Option/Result +- ✅ 方法:impl 塊和 self +- ✅ 集合:Vec、HashMap、HashSet 等 +- ✅ 字符串:String vs &str +- ✅ 數組和切片 +- ✅ 數據結構的模式匹配 +- ✅ 派生特質和自定義實現 + +## 結構體 + +結構體是 Rust 將相關數據分組在一起的方式。 + +### 命名字段結構體 + + +```python !! py +# Python: 使用 dataclass 或類 +from dataclasses import dataclass + +@dataclass +class Person: + name: str + age: int + email: str + +# 創建實例 +person = Person("Alice", 25, "alice@example.com") + +# 訪問字段 +print(person.name) +print(person.age) + +# 更新(如果凍結則創建新實例) +person.age = 26 +``` + +```rust !! rs +// Rust: 命名字段結構體 +struct Person { + name: String, + age: u32, + email: String, +} + +// 創建實例 +let person = Person { + name: String::from("Alice"), + age: 25, + email: String::from("alice@example.com"), +}; + +// 訪問字段 +println!("{}", person.name); +println!("{}", person.age); + +// 更新(需要 mut) +let mut person = person; +person.age = 26; + +// 或使用更新語法創建新實例 +let person2 = Person { + age: 27, + ..person // 移動剩餘字段 +}; +``` + + +### 元組結構體 + + +```python !! py +# Python: 使用 namedtuple 或元組 +from collections import namedtuple +Color = namedtuple('Color', ['red', 'green', 'blue']) + +black = Color(0, 0, 0) +print(black.red) + +# 或直接使用元組 +color = (255, 0, 0) +print(color[0]) # 255 +``` + +```rust !! rs +// Rust: 元組結構體 +struct Color(u8, u8, u8); + +let black = Color(0, 0, 0); +println!("{}", black.0); // 通過索引訪問 + +// 也可以解構 +let Color(r, g, b) = black; +println!("R: {}, G: {}, B: {}", r, g, b); + +// 用於單字段包裝器(newtype 模式) +struct Meters(u32); +struct Kilometers(u32); + +let distance = Meters(1000); +// Meters 和 Kilometers 是不同的類型! +``` + + +### 類單元結構體 + + +```python !! py +# Python: 單例或標記類 +class Singleton: + instance = None + + def __new__(cls): + if cls.instance is None: + cls.instance = super().__new__(cls) + return cls.instance +``` + +```rust !! rs +// Rust: 類單元結構體(無字段) +struct Singleton; + +impl Singleton { + fn instance() -> &'static Singleton { + // 實現會使用 OnceLock 或 lazy_static + &Singleton + } +} + +// 標記類型 +struct Marker; +``` + + +## 枚舉 + +Rust 中的枚舉很強大——它們可以攜帶數據! + + +```python !! py +# Python: Enum(來自 enum 模組) +from enum import Enum + +class Color(Enum): + RED = 1 + GREEN = 2 + BLUE = 3 + +color = Color.RED +print(color) # Color.RED +``` + +```rust !! rs +// Rust: 枚舉可以攜帶數據! +enum Color { + Red, + Green, + Blue, + RGB(u8, u8, u8), // 可以攜帶數據! + CMYK { c: u8, m: u8, y: u8, k: u8 }, // 或命名字段 +} + +let color = Color::Red; + +// 在枚舉上匹配 +match color { + Color::Red => println!("Red"), + Color::Green => println!("Green"), + Color::Blue => println!("Blue"), + Color::RGB(r, g, b) => println!("RGB: {},{},{}", r, g, b), + Color::CMYK { c, m, y, k } => println!("CMYK: {},{},{},{}", c, m, y, k), +} +``` + + +### Option 和 Result + + +```python !! py +# Python: None 表示缺失 +def find_user(user_id): + if user_id == 1: + return {"name": "Alice"} + return None + +user = find_user(1) +if user is not None: + print(user["name"]) +``` + +```rust !! rs +// Rust: Option 枚舉用於缺失值 +fn find_user(user_id: i32) -> Option<&'static str> { + if user_id == 1 { + Some("Alice") + } else { + None + } +} + +let user = find_user(1); +if let Some(name) = user { + println!("{}", name); +} + +// 必須顯式處理 None +let user = find_user(999); +match user { + Some(name) => println!("Found: {}", name), + None => println!("Not found"), +} + +// 使用 ? 進行提前返回 +fn get_user_name(user_id: i32) -> Option<&'static str> { + let name = find_user(user_id)?; + Some(name) +} +``` + + + +```python !! py +# Python: 使用異常處理錯誤 +def divide(a, b): + if b == 0: + raise ValueError("Division by zero") + return a / b + +try: + result = divide(10, 0) +except ValueError as e: + print(f"Error: {e}") +``` + +```rust !! rs +// Rust: Result 枚舉用於錯誤 +fn divide(a: f64, b: f64) -> Result { + if b == 0.0 { + Err(String::from("Division by zero")) + } else { + Ok(a / b) + } +} + +match divide(10.0, 0.0) { + Ok(result) => println!("Result: {}", result), + Err(e) => println!("Error: {}", e), +} + +// 使用 ? 進行錯誤傳播 +fn calculate() -> Result { + let result = divide(10.0, 2.0)?; + Ok(result * 2.0) +} +``` + + +## 方法 + +方法是在 `impl` 塊中定義的函數。 + + +```python !! py +# Python: 類中的方法 +class Rectangle: + def __init__(self, width, height): + self.width = width + self.height = height + + def area(self): + return self.width * self.height + +rect = Rectangle(10, 20) +print(rect.area()) # 200 +``` + +```rust !! rs +// Rust: impl 塊中的方法 +struct Rectangle { + width: u32, + height: u32, +} + +impl Rectangle { + // 關聯函數(類似靜態方法) + fn new(width: u32, height: u32) -> Self { + Rectangle { width, height } + } + + // 接收 &self 的方法 + fn area(&self) -> u32 { + self.width * self.height + } + + // 接收 &mut self 的方法 + fn scale(&mut self, factor: u32) { + self.width *= factor; + self.height *= factor; + } +} + +let rect = Rectangle::new(10, 20); +println!("{}", rect.area()); // 200 +``` + + +## 集合 + +Rust 在標準庫中提供了強大的集合類型。 + +### Vec (動態數組) + + +```python !! py +# Python: 列表 +items = [1, 2, 3, 4, 5] + +# 添加元素 +items.append(6) + +# 訪問 +print(items[0]) # 1 + +# 迭代 +for item in items: + print(item) +``` + +```rust !! rs +// Rust: Vec +let mut items = vec![1, 2, 3, 4, 5]; + +// 添加元素 +items.push(6); + +// 訪問 +println!("{}", items[0]); // 1 + +// 迭代 +for item in &items { + println!("{}", item); +} + +// 使用閉包(map) +let doubled: Vec = items.iter().map(|x| x * 2).collect(); +``` + + +### HashMap + + +```python !! py +# Python: 字典 +scores = {"Alice": 100, "Bob": 95} + +# 訪問 +print(scores["Alice"]) + +# 添加/更新 +scores["Charlie"] = 90 + +# 檢查存在性 +if "Bob" in scores: + print(scores["Bob"]) +``` + +```rust !! rs +// Rust: HashMap +use std::collections::HashMap; + +let mut scores = HashMap::new(); + +// 插入 +scores.insert("Alice", 100); +scores.insert("Bob", 95); + +// 訪問 +println!("{}", scores.get("Alice").unwrap_or(&0)); + +// 添加/更新 +scores.insert("Charlie", 90); + +// 檢查存在性 +if scores.contains_key("Bob") { + println!("{}", scores["Bob"]); +} +``` + + +### HashSet + + +```python !! py +# Python: 集合 +unique_numbers = {1, 2, 3, 4, 5} + +# 添加 +unique_numbers.add(6) + +# 檢查成員資格 +if 3 in unique_numbers: + print("Found 3") + +# 集合操作 +set1 = {1, 2, 3} +set2 = {3, 4, 5} +print(set1 & set2) # 交集: {3} +``` + +```rust !! rs +// Rust: HashSet +use std::collections::HashSet; + +let mut unique_numbers: HashSet = [1, 2, 3, 4, 5].iter().cloned().collect(); + +// 添加 +unique_numbers.insert(6); + +// 檢查成員資格 +if unique_numbers.contains(&3) { + println!("Found 3"); +} + +// 集合操作 +let set1: HashSet = [1, 2, 3].iter().cloned().collect(); +let set2: HashSet = [3, 4, 5].iter().cloned().collect(); + +// 交集 +let intersection: HashSet<&i32> = set1.intersection(&set2).collect(); +println!("{:?}", intersection); // {3} +``` + + +## 字符串 + +Rust 有多種字符串類型。 + + +```python !! py +# Python: 只有 str +s1 = "Hello" +s2 = "World" +s3 = s1 + " " + s2 + +# 字符串不可變 +# s1[0] = "h" # TypeError +``` + +```rust !! rs +// Rust: &str 和 String +let s1: &str = "Hello"; // 字符串切片,不可變 +let s2: String = String::from("World"); // 擁有,可增長 + +let s3: String = s1.to_string() + " " + &s2; + +// 字符串不可變 +// s1[0] = "h"; // 錯誤:不能修改 &str + +// 但 String 可以修改 +let mut s2 = String::from("World"); +s2.push_str("!"); +``` + + +### 字符串操作 + + +```python !! py +# Python: 字符串方法 +s = "hello world" + +# 長度 +print(len(s)) # 11 + +# 子字符串 +print(s[0:5]) # "hello" + +# 分割 +words = s.split() # ["hello", "world"] +``` + +```rust !! rs +// Rust: 字符串操作 +let s = "hello world"; + +// 長�(字节,不是字符!) +println!("{}", s.len()); // 11 + +// 子字符串(需要小心索引) +println!("{}", &s[0..5]); // "hello" + +// 分割 +let words: Vec<&str> = s.split_whitespace().collect(); + +// 替換 +let new_s = s.replace("hello", "hi"); +``` + + +## 數組和切片 + + +```python !! py +# Python: 列表(動態) +items = [1, 2, 3, 4, 5] + +# 可以增長 +items.append(6) + +# 切片 +subset = items[1:4] # [2, 3, 4] +``` + +```rust !! rs +// Rust: 數組(固定大小) +let items: [i32; 5] = [1, 2, 3, 4, 5]; + +// 不能增長 +// items.push(6); // 錯誤 + +// 切片(數組/Vec 的視圖) +let subset = &items[1..4]; // &[2, 3, 4] + +// Vec(動態) +let mut items_vec = vec![1, 2, 3, 4, 5]; +items_vec.push(6); +``` + + +## 模式匹配 + +模式匹配與數據結構配合得很好。 + + +```python !! py +# Python: if-elif 鏈 +def process(value): + if isinstance(value, dict): + return "dict" + elif isinstance(value, list): + return "list" + return "other" +``` + +```rust !! rs +// Rust: 強大的模式匹配 +enum Value { + Number(i32), + Text(String), + Pair(i32, i32), +} + +fn process(value: &Value) -> &str { + match value { + Value::Number(n) if *n > 0 => "positive number", + Value::Number(_) => "number", + Value::Text(s) if !s.is_empty() => "non-empty text", + Value::Text(_) => "text", + Value::Pair(x, y) if x == y => "equal pair", + Value::Pair(_, _) => "pair", + } +} + +// 解構結構體 +struct Point { x: i32, y: i32 } + +let p = Point { x: 10, y: 20 }; +match p { + Point { x: 0, y } => println!("On y-axis at {}", y), + Point { x, y: 0 } => println!("On x-axis at {}", x), + Point { x, y } => println!("At ({}, {})", x, y), +} +``` + + +## 派生特質 + +Rust 可以自動實現常見特質。 + + +```python !! py +# Python: dataclasses +@dataclass +class Person: + name: str + age: int + +person1 = Person("Alice", 25) +person2 = Person("Alice", 25) +print(person1 == person2) # True +``` + +```rust !! rs +// Rust: 派生常見特質 +#[derive(Debug, Clone, PartialEq, Eq)] +struct Person { + name: String, + age: u32, +} + +let person1 = Person { + name: String::from("Alice"), + age: 25, +}; +let person2 = Person { + name: String::from("Alice"), + age: 25, +}; + +println!("{:?}", person1); // Debug 輸出 +println!("{}", person1 == person2); // true +``` + + +## 綜合運用 + + +```python !! py +from dataclasses import dataclass +from enum import Enum + +class Status(Enum): + ACTIVE = "active" + INACTIVE = "inactive" + +@dataclass +class User: + id: int + name: str + status: Status + + def is_active(self) -> bool: + return self.status == Status.ACTIVE +``` + +```rust !! rs +use std::collections::HashSet; + +#[derive(Debug, Clone, PartialEq)] +enum Status { + Active, + Inactive, +} + +#[derive(Debug, Clone)] +struct User { + id: u32, + name: String, + status: Status, + friends: HashSet, +} + +impl User { + fn new(id: u32, name: &str, status: Status) -> Self { + User { + id, + name: String::from(name), + status, + friends: HashSet::new(), + } + } + + fn is_active(&self) -> bool { + self.status == Status::Active + } +} + +fn main() { + let users = vec![ + User::new(1, "Alice", Status::Active), + User::new(2, "Bob", Status::Inactive), + ]; + + if let Some(user) = users.iter().find(|u| u.id == 1) { + println!("Found: {}", user.name); + } +} +``` + + +## 總結 + +在本模組中,你學習了: +- ✅ 結構體將相關數據分組 +- ✅ 枚舉表示帶可選數據的變體 +- ✅ 方法在 impl 塊中定義 +- ✅ Vec、HashMap、HashSet 用於集合 +- ✅ String vs &str 用於字符串 +- ✅ 數組(固定)vs 切片(視圖) +- ✅ 數據結構的模式匹配 +- ✅ 自動派生常見特質 + +## 與 Python 的主要區別 + +1. **結構體不是類**: 僅數據,行為在 impl 塊中 +2. **沒有繼承**: 使用組合和特質 +3. **枚舉變體**: 可以攜帶數據 +4. **多種字符串類型**: String(擁有)vs &str(借用) +5. **固定數組**: [T; N] vs 動態 `Vec` +6. **顯式可變性**: 需要 mut 關鍵字 +7. **模式匹配**: 比 if-elif 更強大 + +## 練習 + +### 練習 1: 實現棧 + +創建一個棧數據結構: +- 使用泛型表示元素類型 +- 實現 push、pop 和 is_empty 方法 +- 內部使用 Vec + +
+解決方案 + +```rust +#[derive(Debug)] +struct Stack { + items: Vec, +} + +impl Stack { + fn new() -> Self { + Stack { items: Vec::new() } + } + + fn push(&mut self, item: T) { + self.items.push(item); + } + + fn pop(&mut self) -> Option { + self.items.pop() + } + + fn is_empty(&self) -> bool { + self.items.is_empty() + } +} + +fn main() { + let mut stack = Stack::new(); + stack.push(1); + stack.push(2); + stack.push(3); + + while let Some(item) = stack.pop() { + println!("{}", item); + } +} +``` + +
+ +### 練習 2: 泛型鍵值存儲 + +創建一個泛型鍵值存儲: +- 存儲鍵值對 +- 實現 get、set 和 remove 方法 +- 內部使用 HashMap + +
+解決方案 + +```rust +use std::collections::HashMap; + +struct Store { + data: HashMap, +} + +impl Store +where + K: std::hash::Hash + Eq, +{ + fn new() -> Self { + Store { data: HashMap::new() } + } + + fn set(&mut self, key: K, value: V) { + self.data.insert(key, value); + } + + fn get(&self, key: &K) -> Option<&V> { + self.data.get(key) + } +} + +fn main() { + let mut store = Store::new(); + store.set("name", "Alice"); + + println!("{:?}", store.get(&"name")); +} +``` + +
+ +## 下一步 + +現在你已經理解了數據結構: +1. **[模組 7: 錯誤處理](./module-07-error-handling)** - 學習 Rust 的錯誤處理方法 +2. 練習使用結構體和枚舉 +3. 探索更多集合類型 + +--- + +**下一節:** [模組 7 - 錯誤處理](./module-07-error-handling) → diff --git a/content/docs/py2rust/module-07-collections.mdx b/content/docs/py2rust/module-07-collections.mdx new file mode 100644 index 0000000..89cd1a2 --- /dev/null +++ b/content/docs/py2rust/module-07-collections.mdx @@ -0,0 +1,726 @@ +--- +title: "Module 7: Collections and Data Structures" +description: "Master Rust's collection types including Vec, String, HashMap, and iterators through comparison with Python's built-in collections" +--- + +# Module 7: Collections and Data Structures + +Welcome to Module 7! In this module, we'll explore Rust's collection types and how they compare to Python's built-in collections. Understanding these differences is crucial for writing idiomatic Rust code. + +## Learning Objectives + +By the end of this module, you'll be able to: +- Use Rust's `Vec` type compared to Python's `list` +- Distinguish between `String` and `&str` types +- Work with `HashMap` for key-value storage +- Leverage Rust's powerful iterator system +- Choose the right collection for your use case + +## 7.1 `Vec` vs Python's list + +### Python Lists: Flexible and Dynamic + +In Python, lists are dynamically typed and can hold any type: + +```python +# Python - Dynamic typing +numbers = [1, 2, 3, 4, 5] +mixed = [1, "hello", 3.14, True] + +# Append and remove +numbers.append(6) +numbers.pop() # Removes and returns last element + +# List comprehension +squares = [x**2 for x in range(10)] +``` + +### Rust `Vec`: Type-Safe and Contiguous + +Rust's `Vec` is a growable array type with contiguous memory: + + +```python !! py +# Python - Dynamic lists +numbers = [1, 2, 3, 4, 5] +numbers.append(6) + +# List comprehension +squares = [x**2 for x in range(10)] +print(squares) # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] + +# Mixed types (allowed in Python) +mixed = [1, "hello", 3.14] +``` + +```rust !! rs +// Rust - Typed vectors +let mut numbers: Vec = vec![1, 2, 3, 4, 5]; +numbers.push(6); + +// Iterator equivalent to list comprehension +let squares: Vec = (0..10).map(|x| x * x).collect(); +println!("{:?}", squares); // [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] + +// Mixed types? Use enums or trait objects! +#[derive(Debug)] +enum Mixed { + Int(i32), + String(String), + Float(f64), +} + +let mixed: Vec = vec![ + Mixed::Int(1), + Mixed::String(String::from("hello")), + Mixed::Float(3.14), +]; +println!("{:?}", mixed); +``` + + +### Key Differences + +1. **Type Safety**: Rust vectors are homogeneous (same type `T`) +2. **Memory Layout**: Contiguous memory like Python's list +3. **Capacity Management**: Rust vectors have explicit capacity control + + +```python !! py +# Python - Automatic memory management +numbers = [] +for i in range(1000): + numbers.append(i) +# Python handles all memory allocation automatically +``` + +```rust !! rs +// Rust - Explicit capacity control +let mut numbers = Vec::with_capacity(1000); +for i in 0..1000 { + numbers.push(i); +} +// Pre-allocating capacity is more efficient than repeated reallocation + +// Check capacity and length +println!("Capacity: {}, Length: {}", numbers.capacity(), numbers.len()); + +// Shrink to fit if needed +numbers.shrink_to_fit(); +``` + + +## 7.2 String vs &str: The String Type System + +One of the most confusing aspects for Python developers is Rust's string types. + +### String Types Overview + + +```python !! py +# Python - Only one string type +s1 = "Hello" +s2 = s1 # Reference, no copy +s3 = s1 + " World" # New string + +# Strings are immutable +# s1[0] = 'h' # Error! +``` + +```rust !! rs +// Rust - Two main string types +// String: Owned, growable, heap-allocated +let mut s1: String = String::from("Hello"); +s1.push_str(" World"); + +// &str: String slice, immutable view +let s2: &str = "Hello"; +let s3: &str = &s1[0..5]; // Slice of String + +// &str can't be modified +// s2.push('!'); // Error! + +// String to &str (cheap conversion) +let s4: &str = &s1; +``` + + +### String Creation Methods + + +```python !! py +# Python - String creation +s1 = "Hello" +s2 = str(42) +s3 = f"Number: {42}" +s4 = " ".join(["Hello", "World"]) +``` + +```rust !! rs +// Rust - Multiple ways to create strings +let s1: String = String::from("Hello"); +let s2: String = "World".to_string(); +let s3: String = format!("Number: {}", 42); +let s4: String = vec!["Hello", "World"].join(" "); + +// From characters +let s5: String = ['H', 'e', 'l', 'l', 'o'].iter().collect(); + +// With capacity +let mut s6 = String::with_capacity(10); +s6.push_str("Hello"); +``` + + +### String Manipulation + + +```python !! py +# Python - String manipulation +text = "Hello, World!" + +# Length (characters) +print(len(text)) # 13 + +# Slicing (bytes, not characters!) +print(text[0:5]) # "Hello" + +# Splitting +words = text.split(", ") +print(list(words)) # ['Hello', 'World!'] + +# Trimming +trimmed = " hello ".strip() +``` + +```rust !! rs +// Rust - String manipulation +let text = String::from("Hello, World!"); + +// Length (bytes, not characters!) +println!("Length: {}", text.len()); // 13 bytes + +// Character count (different from len()) +println!("Chars: {}", text.chars().count()); // 13 chars + +// Slicing (must be at valid UTF-8 boundary) +let hello = &text[0..5]; // "Hello" + +// Splitting +let words: Vec<&str> = text.split(", ").collect(); +println!("{:?}", words); // ["Hello", "World!"] + +// Trimming +let trimmed = " hello ".trim(); +println!("{}", trimmed); // "hello" + +// Character iteration +for c in text.chars() { + println!("{}", c); +} +``` + + +### Common Pitfall: UTF-8 Encoding + + +```python !! py +# Python - Unicode handled automatically +emoji = "Hello 😊" +print(len(emoji)) # 8 (characters) +print(emoji[6]) # 😊 +``` + +```rust !! rs +// Rust - UTF-8 awareness required +let emoji = String::from("Hello 😊"); + +// Byte length vs character count +println!("Bytes: {}", emoji.len()); // 10 bytes (😊 is 4 bytes) +println!("Chars: {}", emoji.chars().count()); // 8 chars + +// Can't index by position (might split multi-byte char) +// let c = emoji[6]; // Panic! Not at character boundary + +// Correct way to get nth character +let c = emoji.chars().nth(6).unwrap(); +println!("{}", c); // 😊 +``` + + +## 7.3 `HashMap` + +### Python Dictionaries vs Rust HashMap + + +```python !! py +# Python - Dictionary +scores = { + "Alice": 95, + "Bob": 87, + "Charlie": 92, +} + +# Access with default +print(scores.get("David", 0)) # 0 + +# Add/Update +scores["David"] = 88 + +# Check existence +if "Alice" in scores: + print(f"Alice scored {scores['Alice']}") +``` + +```rust !! rs +// Rust - HashMap +use std::collections::HashMap; + +let mut scores: HashMap<&str, i32> = HashMap::new(); + +// Insert +scores.insert("Alice", 95); +scores.insert("Bob", 87); +scores.insert("Charlie", 92); + +// Access with default +println!("{:?}", scores.get("David").unwrap_or(&0)); // 0 + +// Add/Update +scores.insert("David", 88); + +// Check existence +if let Some(score) = scores.get("Alice") { + println!("Alice scored {}", score); +} + +// Entry API for conditional insert +scores.entry("Eve").or_insert(90); +``` + + +### HashMap Ownership + + +```python !! py +# Python - References work naturally +key = "name" +data = {key: "Alice"} # Works fine +``` + +```rust !! rs +// Rust - Ownership matters +use std::collections::HashMap; + +let key = String::from("name"); +let mut map: HashMap = HashMap::new(); + +// Error: key was moved +// map.insert(key, "Alice"); + +// Solution: Clone the key +map.insert(key.clone(), "Alice"); +println!("{}", key); // Still accessible + +// Or use references +let key_ref = &key; +map.insert(key_ref.clone(), "Bob"); +``` + + +### HashMap Iteration + + +```python !! py +# Python - Dict iteration +scores = {"Alice": 95, "Bob": 87} + +# Keys +for name in scores.keys(): + print(name) + +# Values +for score in scores.values(): + print(score) + +# Both +for name, score in scores.items(): + print(f"{name}: {score}") +``` + +```rust !! rs +// Rust - HashMap iteration +use std::collections::HashMap; + +let scores = HashMap::from([ + ("Alice", 95), + ("Bob", 87), +]); + +// Keys +for name in scores.keys() { + println!("{}", name); +} + +// Values +for score in scores.values() { + println!("{}", score); +} + +// Both +for (name, score) in &scores { + println!("{}: {}", name, score); +} +``` + + +## 7.4 Iterators: Lazy and Powerful + +Rust's iterator system is more powerful and explicit than Python's. + +### Iterator Basics + + +```python !! py +# Python - Iterators everywhere +numbers = [1, 2, 3, 4, 5] + +# Iterator (implicit) +for n in numbers: + print(n) + +# Explicit iterator +it = iter(numbers) +print(next(it)) # 1 +print(next(it)) # 2 + +# List comprehension (eager) +squares = [x**2 for x in numbers] +``` + +```rust !! rs +// Rust - Explicit iterator types +let numbers = vec![1, 2, 3, 4, 5]; + +// For loop uses iterator implicitly +for n in &numbers { + println!("{}", n); +} + +// Explicit iterator +let mut it = numbers.iter(); +println!("{:?}", it.next()); // Some(1) +println!("{:?}", it.next()); // Some(2) + +// collect() is eager like list comprehension +let squares: Vec = numbers.iter().map(|x| x * x).collect(); +println!("{:?}", squares); +``` + + +### Iterator Chains + + +```python !! py +# Python - Method chaining with generators +numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + +# Multiple operations +result = [ + x * 2 + for x in numbers + if x % 2 == 0 +] +print(result) # [4, 8, 12, 16, 20] + +# With filter and map (using generators) +result2 = list( + map(lambda x: x * 2, + filter(lambda x: x % 2 == 0, numbers)) +) +``` + +```rust !! rs +// Rust - Fluent iterator chains +let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + +// Fluent chain of methods +let result: Vec = numbers + .iter() + .filter(|&&x| x % 2 == 0) // Keep evens + .map(|x| x * 2) // Double them + .collect(); +println!("{:?}", result); // [4, 8, 12, 16, 20] + +// Laziness: nothing happens until collect() +let chain = numbers.iter() + .filter(|&&x| x % 2 == 0) + .map(|x| x * 2); +// No computation yet! +let result: Vec = chain.collect(); // Now it runs +``` + + +### Iterator Consumption + + +```python !! py +# Python - Consume iterators +numbers = [1, 2, 3, 4, 5] + +# Sum +total = sum(numbers) # 15 + +# Any/All +has_even = any(n % 2 == 0 for n in numbers) # True +all_positive = all(n > 0 for n in numbers) # True + +# Find +first_even = next((n for n in numbers if n % 2 == 0), None) +print(first_even) # 2 +``` + +```rust !! rs +// Rust - Consuming iterators +let numbers = vec![1, 2, 3, 4, 5]; + +// Sum +let total: i32 = numbers.iter().sum(); // 15 + +// Any/All +let has_even = numbers.iter().any(|&x| x % 2 == 0); // true +let all_positive = numbers.iter().all(|&x| x > 0); // true + +// Find +let first_even = numbers.iter().find(|&&x| x % 2 == 0); +println!("{:?}", first_even); // Some(2) + +// Fold (like reduce) +let sum = numbers.iter().fold(0, |acc, &x| acc + x); +println!("{}", sum); // 15 +``` + + +### IntoIterator: Different Iteration Types + + +```python !! py +# Python - Different iteration patterns +numbers = [1, 2, 3] + +# Read elements +for n in numbers: + print(n) + +# Modify elements (in-place) +for i in range(len(numbers)): + numbers[i] *= 2 +``` + +```rust !! rs +// Rust - Three types of iteration +let numbers = vec![1, 2, 3]; + +// iter() - Borrowed elements (read-only) +for n in numbers.iter() { + println!("{}", n); +} + +// into_iter() - Owned elements (consumes vector) +for n in numbers.into_iter() { + println!("{}", n); +} +// numbers is no longer available! + +// iter_mut() - Mutable borrows (modify in-place) +let mut numbers = vec![1, 2, 3]; +for n in numbers.iter_mut() { + *n *= 2; +} +println!("{:?}", numbers); // [2, 4, 6] +``` + + +## 7.5 Performance Comparison + +Let's compare the performance characteristics of these collections. + + +```python !! py +# Python - Performance considerations +# List append: O(1) amortized +# List access: O(1) +# List insert at beginning: O(n) + +# Dict lookup: O(1) average +# Dict insert: O(1) average + +# String concatenation in loop: O(n²) if done wrong +# (Use ''.join() for O(n)) +``` + +```rust !! rs +// Rust - Performance characteristics +// Vec push: O(1) amortized +// Vec access: O(1) +// Vec insert at beginning: O(n) + +// HashMap lookup: O(1) average +// HashMap insert: O(1) average + +// String concatenation: Use with_capacity() for efficiency +let mut result = String::with_capacity(100); +for i in 0..10 { + result.push_str(&format!("Number {}", i)); +} +// Much faster than repeated string concatenation +``` + + +## 7.6 Choosing the Right Collection + + +```python !! py +# Python - Collection choices +# list: Ordered sequence, dynamic size +# dict: Key-value mapping, fast lookup +# set: Unique elements, fast membership test +# tuple: Immutable sequence +# frozenset: Immutable set +``` + +```rust !! rs +// Rust - Collection choices +// Vec: Default choice for sequences +// [T; N]: Fixed-size array (known at compile time) +// String: Owned, growable string +// &str: String slice (view into String) +// HashMap: Key-value mapping (default) +// BTreeMap: Ordered key-value mapping +// HashSet: Unique elements, fast membership +// BTreeSet: Ordered unique elements +// VecDeque: Double-ended queue +// LinkedList: Doubly linked list (rarely needed!) +``` + + +## Common Pitfalls + +### Pitfall 1: String Indexing + +```rust +// DON'T: Index into strings +let s = String::from("hello"); +// let c = s[0]; // Panic! + +// DO: Use chars() or bytes() +let c = s.chars().next().unwrap(); +``` + +### Pitfall 2: forgetting to Clone + +```rust +// DON'T: Move values into HashMap unexpectedly +use std::collections::HashMap; + +let name = String::from("Alice"); +let mut map = HashMap::new(); +map.insert(name, 95); +// println!("{}", name); // Error: name was moved! + +// DO: Clone when needed +let name = String::from("Alice"); +map.insert(name.clone(), 95); +println!("{}", name); // Works! +``` + +### Pitfall 3: Unnecessary Collect + +```rust +// DON'T: Collect unnecessarily +let result: Vec = numbers.iter() + .map(|x| x * 2) + .collect(); +let sum: i32 = result.iter().sum(); + +// DO: Chain operations +let sum: i32 = numbers.iter() + .map(|x| x * 2) + .sum(); +``` + +## Best Practices + +1. **Pre-allocate capacity** when you know the size +2. **Use `String::with_capacity()`** for building strings +3. **Leverage iterator chains** instead of intermediate collections +4. **Use `&str` for function parameters** (more flexible) +5. **Prefer `Vec` over `LinkedList`** (better cache locality) +6. **Use `entry()` API** for conditional HashMap operations + +## Summary + +In this module, we covered: + +- **`Vec`**: Type-safe, growable arrays with capacity control +- **String vs &str**: Owned strings vs string slices +- **`HashMap`**: Key-value storage with ownership semantics +- **Iterators**: Lazy, composable, and powerful + +Key takeaways: +- Rust's collections are type-safe and explicit about ownership +- String types require understanding UTF-8 encoding +- Iterators are lazy and chainable for efficient data processing +- Choose collections based on your performance needs + +## Exercise + +Create a word frequency counter that: +1. Reads text and splits into words +2. Counts word occurrences using a HashMap +3. Returns the top 5 most common words +4. Handle Unicode text properly + +
+View Solution + +```rust +use std::collections::HashMap; + +fn word_frequency(text: &str) -> Vec<(String, usize)> { + let mut freq = HashMap::new(); + + // Split on whitespace and count + for word in text.split_whitespace() { + // Clean word: remove punctuation, lowercase + let cleaned = word + .to_lowercase() + .chars() + .filter(|c| c.is_alphabetic()) + .collect::(); + + if !cleaned.is_empty() { + *freq.entry(cleaned).or_insert(0) += 1; + } + } + + // Convert to vector and sort + let mut counts: Vec<(String, usize)> = freq.into_iter().collect(); + counts.sort_by(|a, b| b.1.cmp(&a.1)); // Descending by count + + counts +} + +fn main() { + let text = "Hello world hello RUST world rust World"; + let top_words = word_frequency(text); + + for (word, count) in top_words.iter().take(5) { + println!("{}: {}", word, count); + } +} +``` + +
+ +Next up: **Module 8 - Enums & Pattern Matching**, where we'll explore Rust's powerful enum system and pattern matching capabilities! diff --git a/content/docs/py2rust/module-07-collections.zh-cn.mdx b/content/docs/py2rust/module-07-collections.zh-cn.mdx new file mode 100644 index 0000000..c7acc0a --- /dev/null +++ b/content/docs/py2rust/module-07-collections.zh-cn.mdx @@ -0,0 +1,726 @@ +--- +title: "模块 7:集合与数据结构" +description: "通过与 Python 内置集合对比,掌握 Rust 的集合类型,包括 Vec、String、HashMap 和迭代器" +--- + +# 模块 7:集合与数据结构 + +欢迎来到模块 7!在本模块中,我们将探索 Rust 的集合类型,以及它们与 Python 内置集合的对比。理解这些差异对于编写地道的 Rust 代码至关重要。 + +## 学习目标 + +完成本模块后,你将能够: +- 使用 Rust 的 `Vec` 类型,并与 Python 的 `list` 进行对比 +- 区分 `String` 和 `&str` 类型 +- 使用 `HashMap` 进行键值存储 +- 利用 Rust 强大的迭代器系统 +- 为你的用例选择合适的集合 + +## 7.1 `Vec` vs Python 的 list + +### Python 列表:灵活且动态 + +在 Python 中,列表是动态类型的,可以保存任何类型: + +```python +# Python - 动态类型 +numbers = [1, 2, 3, 4, 5] +mixed = [1, "hello", 3.14, True] + +# 追加和删除 +numbers.append(6) +numbers.pop() # 删除并返回最后一个元素 + +# 列表推导式 +squares = [x**2 for x in range(10)] +``` + +### Rust `Vec`:类型安全且连续 + +Rust 的 `Vec` 是具有连续内存的可增长数组类型: + + +```python !! py +# Python - 动态列表 +numbers = [1, 2, 3, 4, 5] +numbers.append(6) + +# 列表推导式 +squares = [x**2 for x in range(10)] +print(squares) # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] + +# 混合类型(Python 中允许) +mixed = [1, "hello", 3.14] +``` + +```rust !! rs +// Rust - 类型化向量 +let mut numbers: Vec = vec![1, 2, 3, 4, 5]; +numbers.push(6); + +// 迭代器等价于列表推导式 +let squares: Vec = (0..10).map(|x| x * x).collect(); +println!("{:?}", squares); // [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] + +// 混合类型?使用枚举或 trait 对象! +#[derive(Debug)] +enum Mixed { + Int(i32), + String(String), + Float(f64), +} + +let mixed: Vec = vec![ + Mixed::Int(1), + Mixed::String(String::from("hello")), + Mixed::Float(3.14), +]; +println!("{:?}", mixed); +``` + + +### 主要差异 + +1. **类型安全**:Rust 向量是同构的(相同类型 `T`) +2. **内存布局**:像 Python 列表一样的连续内存 +3. **容量管理**:Rust 向量具有显式的容量控制 + + +```python !! py +# Python - 自动内存管理 +numbers = [] +for i in range(1000): + numbers.append(i) +# Python 自动处理所有内存分配 +``` + +```rust !! rs +// Rust - 显式容量控制 +let mut numbers = Vec::with_capacity(1000); +for i in 0..1000 { + numbers.push(i); +} +// 预分配容量比重复重新分配更高效 + +// 检查容量和长度 +println!("Capacity: {}, Length: {}", numbers.capacity(), numbers.len()); + +// 如果需要,收缩以适应 +numbers.shrink_to_fit(); +``` + + +## 7.2 String vs &str:字符串类型系统 + +对于 Python 开发者来说,最令人困惑的方面之一是 Rust 的字符串类型。 + +### 字符串类型概述 + + +```python !! py +# Python - 只有一种字符串类型 +s1 = "Hello" +s2 = s1 # 引用,不复制 +s3 = s1 + " World" # 新字符串 + +# 字符串是不可变的 +# s1[0] = 'h' # 错误! +``` + +```rust !! rs +// Rust - 两种主要字符串类型 +// String: 拥有的、可增长的、堆分配的 +let mut s1: String = String::from("Hello"); +s1.push_str(" World"); + +// &str: 字符串切片,不可变视图 +let s2: &str = "Hello"; +let s3: &str = &s1[0..5]; // String 的切片 + +// &str 不能被修改 +// s2.push('!'); // 错误! + +// String 到 &str(廉价转换) +let s4: &str = &s1; +``` + + +### 字符串创建方法 + + +```python !! py +# Python - 字符串创建 +s1 = "Hello" +s2 = str(42) +s3 = f"Number: {42}" +s4 = " ".join(["Hello", "World"]) +``` + +```rust !! rs +// Rust - 多种创建字符串的方式 +let s1: String = String::from("Hello"); +let s2: String = "World".to_string(); +let s3: String = format!("Number: {}", 42); +let s4: String = vec!["Hello", "World"].join(" "); + +// 从字符创建 +let s5: String = ['H', 'e', 'l', 'l', 'o'].iter().collect(); + +// 带容量 +let mut s6 = String::with_capacity(10); +s6.push_str("Hello"); +``` + + +### 字符串操作 + + +```python !! py +# Python - 字符串操作 +text = "Hello, World!" + +# 长度(字符) +print(len(text)) # 13 + +# 切片(字节,不是字符!) +print(text[0:5]) # "Hello" + +# 分割 +words = text.split(", ") +print(list(words)) # ['Hello', 'World!'] + +# 去除空格 +trimmed = " hello ".strip() +``` + +```rust !! rs +// Rust - 字符串操作 +let text = String::from("Hello, World!"); + +// 长度(字节,不是字符!) +println!("Length: {}", text.len()); // 13 字节 + +// 字符计数(与 len() 不同) +println!("Chars: {}", text.chars().count()); // 13 字符 + +// 切片(必须在有效的 UTF-8 边界上) +let hello = &text[0..5]; // "Hello" + +// 分割 +let words: Vec<&str> = text.split(", ").collect(); +println!("{:?}", words); // ["Hello", "World!"] + +// 去除空格 +let trimmed = " hello ".trim(); +println!("{}", trimmed); // "hello" + +// 字符迭代 +for c in text.chars() { + println!("{}", c); +} +``` + + +### 常见陷阱:UTF-8 编码 + + +```python !! py +# Python - 自动处理 Unicode +emoji = "Hello 😊" +print(len(emoji)) # 8(字符) +print(emoji[6]) # 😊 +``` + +```rust !! rs +// Rust - 需要 UTF-8 意识 +let emoji = String::from("Hello 😊"); + +// 字节长度 vs 字符计数 +println!("Bytes: {}", emoji.len()); // 10 字节(😊 是 4 字节) +println!("Chars: {}", emoji.chars().count()); // 8 字符 + +// 不能通过位置索引(可能分割多字节字符) +// let c = emoji[6]; // 恐慌!不在字符边界上 + +// 获取第 n 个字符的正确方法 +let c = emoji.chars().nth(6).unwrap(); +println!("{}", c); // 😊 +``` + + +## 7.3 `HashMap` + +### Python 字典 vs Rust HashMap + + +```python !! py +# Python - 字典 +scores = { + "Alice": 95, + "Bob": 87, + "Charlie": 92, +} + +# 使用默认值访问 +print(scores.get("David", 0)) # 0 + +# 添加/更新 +scores["David"] = 88 + +# 检查存在性 +if "Alice" in scores: + print(f"Alice scored {scores['Alice']}") +``` + +```rust !! rs +// Rust - HashMap +use std::collections::HashMap; + +let mut scores: HashMap<&str, i32> = HashMap::new(); + +// 插入 +scores.insert("Alice", 95); +scores.insert("Bob", 87); +scores.insert("Charlie", 92); + +// 使用默认值访问 +println!("{:?}", scores.get("David").unwrap_or(&0)); // 0 + +// 添加/更新 +scores.insert("David", 88); + +// 检查存在性 +if let Some(score) = scores.get("Alice") { + println!("Alice scored {}", score); +} + +// Entry API 用于条件插入 +scores.entry("Eve").or_insert(90); +``` + + +### HashMap 所有权 + + +```python !! py +# Python - 引用自然工作 +key = "name" +data = {key: "Alice"} # 工作正常 +``` + +```rust !! rs +// Rust - 所有权很重要 +use std::collections::HashMap; + +let key = String::from("name"); +let mut map: HashMap = HashMap::new(); + +// 错误:key 被移动了 +// map.insert(key, "Alice"); + +// 解决方案:克隆 key +map.insert(key.clone(), "Alice"); +println!("{}", key); // 仍然可访问 + +// 或使用引用 +let key_ref = &key; +map.insert(key_ref.clone(), "Bob"); +``` + + +### HashMap 迭代 + + +```python !! py +# Python - 字典迭代 +scores = {"Alice": 95, "Bob": 87} + +# 键 +for name in scores.keys(): + print(name) + +# 值 +for score in scores.values(): + print(score) + +# 两者 +for name, score in scores.items(): + print(f"{name}: {score}") +``` + +```rust !! rs +// Rust - HashMap 迭代 +use std::collections::HashMap; + +let scores = HashMap::from([ + ("Alice", 95), + ("Bob", 87), +]); + +// 键 +for name in scores.keys() { + println!("{}", name); +} + +// 值 +for score in scores.values() { + println!("{}", score); +} + +// 两者 +for (name, score) in &scores { + println!("{}: {}", name, score); +} +``` + + +## 7.4 迭代器:惰性和强大 + +Rust 的迭代器系统比 Python 的更强大和显式。 + +### 迭代器基础 + + +```python !! py +# Python - 迭代器无处不在 +numbers = [1, 2, 3, 4, 5] + +# 迭代器(隐式) +for n in numbers: + print(n) + +# 显式迭代器 +it = iter(numbers) +print(next(it)) # 1 +print(next(it)) # 2 + +# 列表推导式(急切) +squares = [x**2 for x in numbers] +``` + +```rust !! rs +// Rust - 显式迭代器类型 +let numbers = vec![1, 2, 3, 4, 5]; + +// for 循环隐式使用迭代器 +for n in &numbers { + println!("{}", n); +} + +// 显式迭代器 +let mut it = numbers.iter(); +println!("{:?}", it.next()); // Some(1) +println!("{:?}", it.next()); // Some(2) + +// collect() 是急切的,像列表推导式 +let squares: Vec = numbers.iter().map(|x| x * x).collect(); +println!("{:?}", squares); +``` + + +### 迭代器链 + + +```python !! py +# Python - 使用生成器的方法链 +numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + +# 多个操作 +result = [ + x * 2 + for x in numbers + if x % 2 == 0 +] +print(result) # [4, 8, 12, 16, 20] + +# 使用 filter 和 map(使用生成器) +result2 = list( + map(lambda x: x * 2, + filter(lambda x: x % 2 == 0, numbers)) +) +``` + +```rust !! rs +// Rust - 流畅的迭代器链 +let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + +// 方法的流畅链 +let result: Vec = numbers + .iter() + .filter(|&&x| x % 2 == 0) // 保留偶数 + .map(|x| x * 2) // 加倍 + .collect(); +println!("{:?}", result); // [4, 8, 12, 16, 20] + +// 惰性:直到 collect() 才会发生任何事情 +let chain = numbers.iter() + .filter(|&&x| x % 2 == 0) + .map(|x| x * 2); +// 还没有计算! +let result: Vec = chain.collect(); // 现在运行 +``` + + +### 迭代器消费 + + +```python !! py +# Python - 消费迭代器 +numbers = [1, 2, 3, 4, 5] + +# 求和 +total = sum(numbers) # 15 + +# Any/All +has_even = any(n % 2 == 0 for n in numbers) # True +all_positive = all(n > 0 for n in numbers) # True + +# 查找 +first_even = next((n for n in numbers if n % 2 == 0), None) +print(first_even) # 2 +``` + +```rust !! rs +// Rust - 消费迭代器 +let numbers = vec![1, 2, 3, 4, 5]; + +// 求和 +let total: i32 = numbers.iter().sum(); // 15 + +// Any/All +let has_even = numbers.iter().any(|&x| x % 2 == 0); // true +let all_positive = numbers.iter().all(|&x| x > 0); // true + +// 查找 +let first_even = numbers.iter().find(|&&x| x % 2 == 0); +println!("{:?}", first_even); // Some(2) + +// Fold(像 reduce) +let sum = numbers.iter().fold(0, |acc, &x| acc + x); +println!("{}", sum); // 15 +``` + + +### IntoIterator:不同的迭代类型 + + +```python !! py +# Python - 不同的迭代模式 +numbers = [1, 2, 3] + +# 读取元素 +for n in numbers: + print(n) + +# 修改元素(就地) +for i in range(len(numbers)): + numbers[i] *= 2 +``` + +```rust !! rs +// Rust - 三种迭代类型 +let numbers = vec![1, 2, 3]; + +// iter() - 借用的元素(只读) +for n in numbers.iter() { + println!("{}", n); +} + +// into_iter() - 拥有的元素(消耗向量) +for n in numbers.into_iter() { + println!("{}", n); +} +// numbers 不再可用! + +// iter_mut() - 可变借用(就地修改) +let mut numbers = vec![1, 2, 3]; +for n in numbers.iter_mut() { + *n *= 2; +} +println!("{:?}", numbers); // [2, 4, 6] +``` + + +## 7.5 性能比较 + +让我们比较这些集合的性能特征。 + + +```python !! py +# Python - 性能考虑 +# 列表追加:O(1) 摊销 +# 列表访问:O(1) +# 列表在开头插入:O(n) + +# 字典查找:O(1) 平均 +# 字典插入:O(1) 平均 + +# 循环中的字符串连接:如果做错是 O(n²) +# (使用 ''.join() 实现 O(n)) +``` + +```rust !! rs +// Rust - 性能特征 +// Vec push: O(1) 摊销 +// Vec 访问: O(1) +// Vec 在开头插入: O(n) + +// HashMap 查找: O(1) 平均 +// HashMap 插入: O(1) 平均 + +// 字符串连接:使用 with_capacity() 提高效率 +let mut result = String::with_capacity(100); +for i in 0..10 { + result.push_str(&format!("Number {}", i)); +} +// 比重复的字符串连接快得多 +``` + + +## 7.6 选择合适的集合 + + +```python !! py +# Python - 集合选择 +# list: 有序序列,动态大小 +# dict: 键值映射,快速查找 +# set: 唯一元素,快速成员测试 +# tuple: 不可变序列 +# frozenset: 不可变集合 +``` + +```rust !! rs +// Rust - 集合选择 +// Vec: 序列的默认选择 +// [T; N]: 固定大小数组(编译时已知) +// String: 拥有的、可增长的字符串 +// &str: 字符串切片(String 的视图) +// HashMap: 键值映射(默认) +// BTreeMap: 有序键值映射 +// HashSet: 唯一元素,快速成员测试 +// BTreeSet: 有序唯一元素 +// VecDeque: 双端队列 +// LinkedList: 双向链表(很少需要!) +``` + + +## 常见陷阱 + +### 陷阱 1:字符串索引 + +```rust +// 不要:对字符串进行索引 +let s = String::from("hello"); +// let c = s[0]; // 恐慌! + +// 应该:使用 chars() 或 bytes() +let c = s.chars().next().unwrap(); +``` + +### 陷阱 2:忘记克隆 + +```rust +// 不要:意外地将值移动到 HashMap +use std::collections::HashMap; + +let name = String::from("Alice"); +let mut map = HashMap::new(); +map.insert(name, 95); +// println!("{}", name); // 错误:name 被移动了! + +// 应该:需要时克隆 +let name = String::from("Alice"); +map.insert(name.clone(), 95); +println!("{}", name); // 工作! +``` + +### 陷阱 3:不必要的 collect + +```rust +// 不要:不必要地收集 +let result: Vec = numbers.iter() + .map(|x| x * 2) + .collect(); +let sum: i32 = result.iter().sum(); + +// 应该:链接操作 +let sum: i32 = numbers.iter() + .map(|x| x * 2) + .sum(); +``` + +## 最佳实践 + +1. 当你知道大小时,**预分配容量** +2. 使用 `String::with_capacity()` **构建字符串** +3. **利用迭代器链** 而不是中间集合 +4. **使用 `&str` 作为函数参数**(更灵活) +5. **优先选择 `Vec` 而不是 `LinkedList`**(更好的缓存局部性) +6. **使用 `entry()` API** 进行条件 HashMap 操作 + +## 总结 + +在本模块中,我们涵盖了: + +- **`Vec`**:类型安全、可增长的数组,具有容量控制 +- **String vs &str**:拥有的字符串 vs 字符串切片 +- **`HashMap`**:具有所有权语义的键值存储 +- **迭代器**:惰性、可组合且强大 + +关键要点: +- Rust 的集合是类型安全的,并且显式处理所有权 +- 字符串类型需要理解 UTF-8 编码 +- 迭代器是惰性且可链式的,用于高效的数据处理 +- 根据你的性能需求选择集合 + +## 练习 + +创建一个词频计数器,它: +1. 读取文本并分割成单词 +2. 使用 HashMap 计算单词出现次数 +3. 返回前 5 个最常见的单词 +4. 正确处理 Unicode 文本 + +
+查看解决方案 + +```rust +use std::collections::HashMap; + +fn word_frequency(text: &str) -> Vec<(String, usize)> { + let mut freq = HashMap::new(); + + // 分割空格并计数 + for word in text.split_whitespace() { + // 清理单词:删除标点符号,小写 + let cleaned = word + .to_lowercase() + .chars() + .filter(|c| c.is_alphabetic()) + .collect::(); + + if !cleaned.is_empty() { + *freq.entry(cleaned).or_insert(0) += 1; + } + } + + // 转换为向量并排序 + let mut counts: Vec<(String, usize)> = freq.into_iter().collect(); + counts.sort_by(|a, b| b.1.cmp(&a.1)); // 按计数降序 + + counts +} + +fn main() { + let text = "Hello world hello RUST world rust World"; + let top_words = word_frequency(text); + + for (word, count) in top_words.iter().take(5) { + println!("{}: {}", word, count); + } +} +``` + +
+ +接下来:**模块 8 - 枚举和模式匹配**,我们将探索 Rust 强大的枚举系统和模式匹配功能! diff --git a/content/docs/py2rust/module-07-collections.zh-tw.mdx b/content/docs/py2rust/module-07-collections.zh-tw.mdx new file mode 100644 index 0000000..f3d2144 --- /dev/null +++ b/content/docs/py2rust/module-07-collections.zh-tw.mdx @@ -0,0 +1,726 @@ +--- +title: "模組 7:集合與資料結構" +description: "透過與 Python 內建集合對比,掌握 Rust 的集合型別,包括 Vec、String、HashMap 和迭代器" +--- + +# 模組 7:集合與資料結構 + +歡迎來到模組 7!在本模組中,我們將探索 Rust 的集合型別,以及它們與 Python 內建集合的對比。理解這些差異對於編寫地道的 Rust 程式碼至關重要。 + +## 學習目標 + +完成本模組後,你將能夠: +- 使用 Rust 的 `Vec` 型別,並與 Python 的 `list` 進行對比 +- 區分 `String` 和 `&str` 型別 +- 使用 `HashMap` 進行鍵值儲存 +- 利用 Rust 強大的迭代器系統 +- 為你的用例選擇合適的集合 + +## 7.1 `Vec` vs Python 的 list + +### Python 列表:靈活且動態 + +在 Python 中,列表是動態型別的,可以儲存任何型別: + +```python +# Python - 動態型別 +numbers = [1, 2, 3, 4, 5] +mixed = [1, "hello", 3.14, True] + +# 追加和刪除 +numbers.append(6) +numbers.pop() # 刪除並返回最後一個元素 + +# 列表推導式 +squares = [x**2 for x in range(10)] +``` + +### Rust `Vec`:型別安全且連續 + +Rust 的 `Vec` 是具有連續記憶體的可增長陣列型別: + + +```python !! py +# Python - 動態列表 +numbers = [1, 2, 3, 4, 5] +numbers.append(6) + +# 列表推導式 +squares = [x**2 for x in range(10)] +print(squares) # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] + +# 混合型別(Python 中允許) +mixed = [1, "hello", 3.14] +``` + +```rust !! rs +// Rust - 型別化向量 +let mut numbers: Vec = vec![1, 2, 3, 4, 5]; +numbers.push(6); + +// 迭代器等價於列表推導式 +let squares: Vec = (0..10).map(|x| x * x).collect(); +println!("{:?}", squares); // [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] + +// 混合型別?使用枚舉或 trait 物件! +#[derive(Debug)] +enum Mixed { + Int(i32), + String(String), + Float(f64), +} + +let mixed: Vec = vec![ + Mixed::Int(1), + Mixed::String(String::from("hello")), + Mixed::Float(3.14), +]; +println!("{:?}", mixed); +``` + + +### 主要差異 + +1. **型別安全**:Rust 向量是同構的(相同型別 `T`) +2. **記憶體佈局**:像 Python 列表一樣的連續記憶體 +3. **容量管理**:Rust 向量具有顯式的容量控制 + + +```python !! py +# Python - 自動記憶體管理 +numbers = [] +for i in range(1000): + numbers.append(i) +# Python 自動處理所有記憶體分配 +``` + +```rust !! rs +// Rust - 顯式容量控制 +let mut numbers = Vec::with_capacity(1000); +for i in 0..1000 { + numbers.push(i); +} +// 預分配容量比重複重新分配更高效 + +// 檢查容量和長度 +println!("Capacity: {}, Length: {}", numbers.capacity(), numbers.len()); + +// 如果需要,收縮以適應 +numbers.shrink_to_fit(); +``` + + +## 7.2 String vs &str:字串型別系統 + +對於 Python 開發者來說,最令人困惑的方面之一是 Rust 的字串型別。 + +### 字串型別概述 + + +```python !! py +# Python - 只有一種字串型別 +s1 = "Hello" +s2 = s1 # 引用,不複製 +s3 = s1 + " World" # 新字串 + +# 字串是不可變的 +# s1[0] = 'h' # 錯誤! +``` + +```rust !! rs +// Rust - 兩種主要字串型別 +// String: 擁有的、可增長的、堆分配的 +let mut s1: String = String::from("Hello"); +s1.push_str(" World"); + +// &str: 字串切片,不可變檢視 +let s2: &str = "Hello"; +let s3: &str = &s1[0..5]; // String 的切片 + +// &str 不能被修改 +// s2.push('!'); // 錯誤! + +// String 到 &str(廉價轉換) +let s4: &str = &s1; +``` + + +### 字串建立方法 + + +```python !! py +# Python - 字串建立 +s1 = "Hello" +s2 = str(42) +s3 = f"Number: {42}" +s4 = " ".join(["Hello", "World"]) +``` + +```rust !! rs +// Rust - 多種建立字串的方式 +let s1: String = String::from("Hello"); +let s2: String = "World".to_string(); +let s3: String = format!("Number: {}", 42); +let s4: String = vec!["Hello", "World"].join(" "); + +// 從字元建立 +let s5: String = ['H', 'e', 'l', 'l', 'o'].iter().collect(); + +// 帶容量 +let mut s6 = String::with_capacity(10); +s6.push_str("Hello"); +``` + + +### 字串操作 + + +```python !! py +# Python - 字串操作 +text = "Hello, World!" + +# 長度(字元) +print(len(text)) # 13 + +# 切片(位元組,不是字元!) +print(text[0:5]) # "Hello" + +# 分割 +words = text.split(", ") +print(list(words)) # ['Hello', 'World!'] + +# 去除空格 +trimmed = " hello ".strip() +``` + +```rust !! rs +// Rust - 字串操作 +let text = String::from("Hello, World!"); + +// 長度(位元組,不是字元!) +println!("Length: {}", text.len()); // 13 位元組 + +// 字元計數(與 len() 不同) +println!("Chars: {}", text.chars().count()); // 13 字元 + +// 切片(必須在有效的 UTF-8 邊界上) +let hello = &text[0..5]; // "Hello" + +// 分割 +let words: Vec<&str> = text.split(", ").collect(); +println!("{:?}", words); // ["Hello", "World!"] + +// 去除空格 +let trimmed = " hello ".trim(); +println!("{}", trimmed); // "hello" + +// 字元迭代 +for c in text.chars() { + println!("{}", c); +} +``` + + +### 常見陷阱:UTF-8 編碼 + + +```python !! py +# Python - 自動處理 Unicode +emoji = "Hello 😊" +print(len(emoji)) # 8(字元) +print(emoji[6]) # 😊 +``` + +```rust !! rs +// Rust - 需要 UTF-8 意識 +let emoji = String::from("Hello 😊"); + +// 位元組長度 vs 字元計數 +println!("Bytes: {}", emoji.len()); // 10 位元組(😊 是 4 位元組) +println!("Chars: {}", emoji.chars().count()); // 8 字元 + +// 不能通過位置索引(可能分割多位元組字元) +// let c = emoji[6]; // 恐慌!不在字元邊界上 + +// 取得第 n 個字元的正確方法 +let c = emoji.chars().nth(6).unwrap(); +println!("{}", c); // 😊 +``` + + +## 7.3 `HashMap` + +### Python 字典 vs Rust HashMap + + +```python !! py +# Python - 字典 +scores = { + "Alice": 95, + "Bob": 87, + "Charlie": 92, +} + +# 使用預設值存取 +print(scores.get("David", 0)) # 0 + +# 新增/更新 +scores["David"] = 88 + +# 檢查存在性 +if "Alice" in scores: + print(f"Alice scored {scores['Alice']}") +``` + +```rust !! rs +// Rust - HashMap +use std::collections::HashMap; + +let mut scores: HashMap<&str, i32> = HashMap::new(); + +// 插入 +scores.insert("Alice", 95); +scores.insert("Bob", 87); +scores.insert("Charlie", 92); + +// 使用預設值存取 +println!("{:?}", scores.get("David").unwrap_or(&0)); // 0 + +// 新增/更新 +scores.insert("David", 88); + +// 檢查存在性 +if let Some(score) = scores.get("Alice") { + println!("Alice scored {}", score); +} + +// Entry API 用於條件插入 +scores.entry("Eve").or_insert(90); +``` + + +### HashMap 所有权 + + +```python !! py +# Python - 引用自然工作 +key = "name" +data = {key: "Alice"} # 工作正常 +``` + +```rust !! rs +// Rust - 所有權很重要 +use std::collections::HashMap; + +let key = String::from("name"); +let mut map: HashMap = HashMap::new(); + +// 錯誤:key 被移動了 +// map.insert(key, "Alice"); + +// 解決方案:複製 key +map.insert(key.clone(), "Alice"); +println!("{}", key); // 仍然可存取 + +// 或使用引用 +let key_ref = &key; +map.insert(key_ref.clone(), "Bob"); +``` + + +### HashMap 迭代 + + +```python !! py +# Python - 字典迭代 +scores = {"Alice": 95, "Bob": 87} + +# 鍵 +for name in scores.keys(): + print(name) + +# 值 +for score in scores.values(): + print(score) + +# 两者 +for name, score in scores.items(): + print(f"{name}: {score}") +``` + +```rust !! rs +// Rust - HashMap 迭代 +use std::collections::HashMap; + +let scores = HashMap::from([ + ("Alice", 95), + ("Bob", 87), +]); + +// 鍵 +for name in scores.keys() { + println!("{}", name); +} + +// 值 +for score in scores.values() { + println!("{}", score); +} + +// 两者 +for (name, score) in &scores { + println!("{}: {}", name, score); +} +``` + + +## 7.4 迭代器:惰性和強大 + +Rust 的迭代器系統比 Python 的更強大和顯式。 + +### 迭代器基礎 + + +```python !! py +# Python - 迭代器無處不在 +numbers = [1, 2, 3, 4, 5] + +# 迭代器(隱式) +for n in numbers: + print(n) + +# 顯式迭代器 +it = iter(numbers) +print(next(it)) # 1 +print(next(it)) # 2 + +# 列表推導式(急切) +squares = [x**2 for x in numbers] +``` + +```rust !! rs +// Rust - 顯式迭代器型別 +let numbers = vec![1, 2, 3, 4, 5]; + +// for 迴圈隱式使用迭代器 +for n in &numbers { + println!("{}", n); +} + +// 顯式迭代器 +let mut it = numbers.iter(); +println!("{:?}", it.next()); // Some(1) +println!("{:?}", it.next()); // Some(2) + +// collect() 是急切的,像列表推導式 +let squares: Vec = numbers.iter().map(|x| x * x).collect(); +println!("{:?}", squares); +``` + + +### 迭代器鏈 + + +```python !! py +# Python - 使用生成器的方法鏈 +numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + +# 多個操作 +result = [ + x * 2 + for x in numbers + if x % 2 == 0 +] +print(result) # [4, 8, 12, 16, 20] + +# 使用 filter 和 map(使用生成器) +result2 = list( + map(lambda x: x * 2, + filter(lambda x: x % 2 == 0, numbers)) +) +``` + +```rust !! rs +// Rust - 流暢的迭代器鏈 +let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + +// 方法的流暢鏈 +let result: Vec = numbers + .iter() + .filter(|&&x| x % 2 == 0) // 保留偶數 + .map(|x| x * 2) // 加倍 + .collect(); +println!("{:?}", result); // [4, 8, 12, 16, 20] + +// 惰性:直到 collect() 才會發生任何事情 +let chain = numbers.iter() + .filter(|&&x| x % 2 == 0) + .map(|x| x * 2); +// 還沒有計算! +let result: Vec = chain.collect(); // 現在執行 +``` + + +### 迭代器消費 + + +```python !! py +# Python - 消費迭代器 +numbers = [1, 2, 3, 4, 5] + +# 求和 +total = sum(numbers) # 15 + +# Any/All +has_even = any(n % 2 == 0 for n in numbers) # True +all_positive = all(n > 0 for n in numbers) # True + +# 查找 +first_even = next((n for n in numbers if n % 2 == 0), None) +print(first_even) # 2 +``` + +```rust !! rs +// Rust - 消費迭代器 +let numbers = vec![1, 2, 3, 4, 5]; + +// 求和 +let total: i32 = numbers.iter().sum(); // 15 + +// Any/All +let has_even = numbers.iter().any(|&x| x % 2 == 0); // true +let all_positive = numbers.iter().all(|&x| x > 0); // true + +// 查找 +let first_even = numbers.iter().find(|&&x| x % 2 == 0); +println!("{:?}", first_even); // Some(2) + +// Fold(像 reduce) +let sum = numbers.iter().fold(0, |acc, &x| acc + x); +println!("{}", sum); // 15 +``` + + +### IntoIterator:不同的迭代型別 + + +```python !! py +# Python - 不同的迭代模式 +numbers = [1, 2, 3] + +# 讀取元素 +for n in numbers: + print(n) + +# 修改元素(就地) +for i in range(len(numbers)): + numbers[i] *= 2 +``` + +```rust !! rs +// Rust - 三種迭代型別 +let numbers = vec![1, 2, 3]; + +// iter() - 借用的元素(唯讀) +for n in numbers.iter() { + println!("{}", n); +} + +// into_iter() - 擁有的元素(消耗向量) +for n in numbers.into_iter() { + println!("{}", n); +} +// numbers 不再可用! + +// iter_mut() - 可變借用(就地修改) +let mut numbers = vec![1, 2, 3]; +for n in numbers.iter_mut() { + *n *= 2; +} +println!("{:?}", numbers); // [2, 4, 6] +``` + + +## 7.5 效能比較 + +讓我們比較這些集合的效能特徵。 + + +```python !! py +# Python - 效能考慮 +# 列表追加:O(1) 攤銷 +# 列表存取:O(1) +# 列表在開頭插入:O(n) + +# 字典查找:O(1) 平均 +# 字典插入:O(1) 平均 + +# 迴圈中的字串連接:如果做錯是 O(n²) +# (使用 ''.join() 實現 O(n)) +``` + +```rust !! rs +// Rust - 效能特徵 +// Vec push: O(1) 攤銷 +// Vec 存取: O(1) +// Vec 在開頭插入: O(n) + +// HashMap 查找: O(1) 平均 +// HashMap 插入: O(1) 平均 + +// 字串連接:使用 with_capacity() 提高效率 +let mut result = String::with_capacity(100); +for i in 0..10 { + result.push_str(&format!("Number {}", i)); +} +// 比重複的字串連接快得多 +``` + + +## 7.6 選擇合適的集合 + + +```python !! py +# Python - 集合選擇 +# list: 有序序列,動態大小 +# dict: 鍵值映射,快速查找 +# set: 唯一元素,快速成員測試 +# tuple: 不可變序列 +# frozenset: 不可變集合 +``` + +```rust !! rs +// Rust - 集合選擇 +// Vec: 序列的預設選擇 +// [T; N]: 固定大小陣列(編譯時已知) +// String: 擁有的、可增長的字串 +// &str: 字串切片(String 的檢視) +// HashMap: 鍵值映射(預設) +// BTreeMap: 有序鍵值映射 +// HashSet: 唯一元素,快速成員測試 +// BTreeSet: 有序唯一元素 +// VecDeque: 雙端佇列 +// LinkedList: 雙向鏈結串列(很少需要!) +``` + + +## 常見陷阱 + +### 陷阱 1:字串索引 + +```rust +// 不要:對字串進行索引 +let s = String::from("hello"); +// let c = s[0]; // 恐慌! + +// 應該:使用 chars() 或 bytes() +let c = s.chars().next().unwrap(); +``` + +### 陷阱 2:忘記複製 + +```rust +// 不要:意外地將值移動到 HashMap +use std::collections::HashMap; + +let name = String::from("Alice"); +let mut map = HashMap::new(); +map.insert(name, 95); +// println!("{}", name); // 錯誤:name 被移動了! + +// 應該:需要時複製 +let name = String::from("Alice"); +map.insert(name.clone(), 95); +println!("{}", name); // 工作! +``` + +### 陷阱 3:不必要的 collect + +```rust +// 不要:不必要地收集 +let result: Vec = numbers.iter() + .map(|x| x * 2) + .collect(); +let sum: i32 = result.iter().sum(); + +// 應該:鏈接操作 +let sum: i32 = numbers.iter() + .map(|x| x * 2) + .sum(); +``` + +## 最佳實踐 + +1. 當你知道大小時,**預分配容量** +2. 使用 `String::with_capacity()` **建構字串** +3. **利用迭代器鏈** 而不是中間集合 +4. **使用 `&str` 作為函數參數**(更靈活) +5. **優先選擇 `Vec` 而不是 `LinkedList`**(更好的快取局部性) +6. **使用 `entry()` API** 進行條件 HashMap 操作 + +## 總結 + +在本模組中,我們涵蓋了: + +- **`Vec`**:型別安全、可增長的陣列,具有容量控制 +- **String vs &str**:擁有的字串 vs 字串切片 +- **`HashMap`**:具有所有權語義的鍵值儲存 +- **迭代器**:惰性、可組合且強大 + +關鍵要點: +- Rust 的集合是型別安全的,並且顯式處理所有權 +- 字串型別需要理解 UTF-8 編碼 +- 迭代器是惰性且可鏈式的,用於高效的資料處理 +- 根據你的效能需求選擇集合 + +## 練習 + +建立一個詞頻計數器,它: +1. 讀取文字並分割成單詞 +2. 使用 HashMap 計算單詞出現次數 +3. 返回前 5 個最常見的單詞 +4. 正確處理 Unicode 文字 + +
+檢視解決方案 + +```rust +use std::collections::HashMap; + +fn word_frequency(text: &str) -> Vec<(String, usize)> { + let mut freq = HashMap::new(); + + // 分割空格並計數 + for word in text.split_whitespace() { + // 清理單詞:刪除標點符號,小寫 + let cleaned = word + .to_lowercase() + .chars() + .filter(|c| c.is_alphabetic()) + .collect::(); + + if !cleaned.is_empty() { + *freq.entry(cleaned).or_insert(0) += 1; + } + } + + // 轉換為向量並排序 + let mut counts: Vec<(String, usize)> = freq.into_iter().collect(); + counts.sort_by(|a, b| b.1.cmp(&a.1)); // 按計數降序 + + counts +} + +fn main() { + let text = "Hello world hello RUST world rust World"; + let top_words = word_frequency(text); + + for (word, count) in top_words.iter().take(5) { + println!("{}: {}", word, count); + } +} +``` + +
+ +接下來:**模組 8 - 枚舉和模式匹配**,我們將探索 Rust 強大的枚舉系統和模式匹配功能! diff --git a/content/docs/py2rust/module-08-enums.mdx b/content/docs/py2rust/module-08-enums.mdx new file mode 100644 index 0000000..8ca4f11 --- /dev/null +++ b/content/docs/py2rust/module-08-enums.mdx @@ -0,0 +1,915 @@ +--- +title: "Module 8: Enums and Pattern Matching" +description: "Explore Rust's powerful enum system and pattern matching capabilities, comparing with Python's class hierarchies and control flow" +--- + +# Module 8: Enums and Pattern Matching + +Welcome to Module 8! This is where Rust truly shines. Rust's enum system and pattern matching are far more powerful than anything in Python, enabling type-safe, expressive code that's impossible to replicate in dynamic languages. + +## Learning Objectives + +By the end of this module, you'll be able to: +- Define and use enums with data variants +- Leverage `Option` for null-safe programming +- Use `Result` for error handling +- Write powerful pattern matches with `match` expressions +- Use `if let` and `while let` for concise pattern matching +- Understand exhaustive matching and compiler guarantees + +## 8.1 Enums: More Than Just Constants + +### Python: Limited Enum Support + +Python's enums are relatively simple: + +```python +# Python - Basic enums +from enum import Enum + +class Color(Enum): + RED = 1 + GREEN = 2 + BLUE = 3 + +# Usage +color = Color.RED +print(color.value) # 1 +``` + +### Rust: Data-Carrying Enums + +Rust enums are far more powerful - they can carry data: + + +```python !! py +# Python - Classes to simulate data variants +class Color: + def __init__(self, value): + self.value = value + +class RGB(Color): + def __init__(self, r, g, b): + self.r = r + self.g = g + self.b = b + +class HSV(Color): + def __init__(self, h, s, v): + self.h = h + self.s = s + self.v = v + +# Type checking is manual +color = RGB(255, 0, 0) +if isinstance(color, RGB): + print(f"R={color.r}, G={color.g}, B={color.b}") +``` + +```rust !! rs +// Rust - Algebraic data types +enum Color { + RGB(u8, u8, u8), + HSV(u8, u8, u8), +} + +// Usage +let color = Color::RGB(255, 0, 0); + +// Pattern matching to extract data +match color { + Color::RGB(r, g, b) => { + println!("R={}, G={}, B={}", r, g, b); + } + Color::HSV(h, s, v) => { + println!("H={}, S={}, V={}", h, s, v); + } +} + +// With named fields +enum Message { + Quit, + Move { x: i32, y: i32 }, + Write(String), + ChangeColor(u8, u8, u8), +} + +let msg = Message::Move { x: 10, y: 20 }; +``` + + +### The Power of Enums + + +```python !! py +# Python - Manual type checking and errors +class Shape: + pass + +class Circle(Shape): + def __init__(self, radius): + self.radius = radius + +class Rectangle(Shape): + def __init__(self, width, height): + self.width = width + self.height = height + +def area(shape): + if isinstance(shape, Circle): + return 3.14 * shape.radius ** 2 + elif isinstance(shape, Rectangle): + return shape.width * shape.height + else: + raise ValueError("Unknown shape") +``` + +```rust !! rs +// Rust - Type-safe with exhaustive matching +enum Shape { + Circle(f64), + Rectangle { width: f64, height: f64 }, +} + +fn area(shape: &Shape) -> f64 { + match shape { + Shape::Circle(radius) => 3.14 * radius * radius, + Shape::Rectangle { width, height } => width * height, + } +} + +// Compiler ensures all cases are covered! +let circle = Shape::Circle(5.0); +println!("Area: {}", area(&circle)); +``` + + +## 8.2 `Option`: Eliminating Null + +### Python: None and Runtime Errors + +Python uses `None` to represent absence, but this can lead to runtime errors: + + +```python !! py +# Python - None is everywhere +def find_user(user_id): + if user_id == 1: + return {"name": "Alice", "email": "alice@example.com"} + return None + +# Runtime error if we forget to check! +user = find_user(2) +print(user["name"]) # TypeError: 'NoneType' is not subscriptable + +# Must check manually +user = find_user(2) +if user is not None: + print(user["name"]) +else: + print("User not found") +``` + +```rust !! rs +// Rust - Option forces handling +fn find_user(user_id: u32) -> Option<&'static str> { + if user_id == 1 { + Some("Alice") + } else { + None + } +} + +// Compiler forces you to handle None case +let user = find_user(2); +match user { + Some(name) => println!("User: {}", name), + None => println!("User not found"), +} + +// Or use helper methods +let user = find_user(1); +println!("User: {}", user.unwrap_or("Unknown")); + +// Chaining operations +let user = find_user(2) + .map(|name| name.to_uppercase()) + .unwrap_or_else(|| "DEFAULT".to_string()); +``` + + +### Option Methods + + +```python !! py +# Python - Manual None handling +def get_score(name): + scores = {"Alice": 95, "Bob": 87} + return scores.get(name) + +# Manual checking +score = get_score("Charlie") +if score is not None: + print(f"Score: {score}") +else: + print("No score") + +# Provide default +score = get_score("Charlie") or 0 +print(f"Score: {score}") +``` + +```rust !! rs +// Rust - Rich Option API +fn get_score(name: &str) -> Option { + let scores = [("Alice", 95), ("Bob", 87)]; + scores.iter() + .find(|(n, _)| *n == name) + .map(|(_, score)| *score) +} + +// Pattern matching +let score = get_score("Charlie"); +match score { + Some(s) => println!("Score: {}", s), + None => println!("No score"), +} + +// Provide default +let score = get_score("Charlie").unwrap_or(0); +println!("Score: {}", score); + +// Transform with map +let doubled = get_score("Alice").map(|s| s * 2); +println!("Doubled: {:?}", doubled); + +// Chain operations +let result = get_score("Charlie") + .or(Some(0)) + .map(|s| s * 2) + .unwrap_or(0); +``` + + +## 8.3 `Result`: Proper Error Handling + +### Python: Exceptions and Try/Catch + +Python uses exceptions for error handling: + + +```python !! py +# Python - Exceptions +def divide(a, b): + if b == 0: + raise ValueError("Cannot divide by zero") + return a / b + +# Must use try/catch +try: + result = divide(10, 0) +except ValueError as e: + print(f"Error: {e}") +else: + print(f"Result: {result}") + +# Multiple exception types +try: + result = divide(10, "2") +except ValueError as e: + print(f"ValueError: {e}") +except TypeError as e: + print(f"TypeError: {e}") +``` + +```rust !! rs +// Rust - Result for explicit errors +fn divide(a: f64, b: f64) -> Result { + if b == 0.0 { + Err(String::from("Cannot divide by zero")) + } else { + Ok(a / b) + } +} + +// Must handle both cases +let result = divide(10.0, 0.0); +match result { + Ok(value) => println!("Result: {}", value), + Err(e) => println!("Error: {}", e), +} + +// Or use ? operator (in functions that return Result) +fn calculate() -> Result<(), String> { + let result = divide(10.0, 2.0)?; + println!("Result: {}", result); + Ok(()) +} + +// Multiple error types with custom error enum +#[derive(Debug)] +enum MathError { + DivisionByZero, + InvalidInput(String), +} + +fn divide_safe(a: f64, b: f64) -> Result { + if b == 0.0 { + Err(MathError::DivisionByZero) + } else if a.is_nan() || b.is_nan() { + Err(MathError::InvalidInput("NaN detected".to_string())) + } else { + Ok(a / b) + } +} +``` + + +### Combining Results + + +```python !! py +# Python - Manual error propagation +def step1(): + return "data" + +def step2(data): + if data == "error": + raise ValueError("Step 2 failed") + return data + "_processed" + +def pipeline(): + try: + data = step1() + result = step2(data) + return result + except ValueError as e: + print(f"Pipeline failed: {e}") + return None +``` + +```rust !! rs +// Rust - Composable Result operations +fn step1() -> Result<&'static str, String> { + Ok("data") +} + +fn step2(data: &str) -> Result { + if data == "error" { + Err("Step 2 failed".to_string()) + } else { + Ok(format!("{}_processed", data)) + } +} + +// Manual propagation with match +fn pipeline() -> Result { + let data = step1()?; + let result = step2(&data)?; + Ok(result) +} + +// Or use and_then for chaining +fn_pipeline() -> Result { + step1() + .and_then(|data| step2(data)) +} + +// Compose multiple operations +fn pipeline_complex() -> Result { + step1() + .and_then(|data| step2(data)) + .map(|result| result.to_uppercase()) + .map_err(|e| format!("Pipeline failed: {}", e)) +} +``` + + +## 8.4 Pattern Matching with match + +### Python: if/elif/else Chains + +Python relies on if/elif/else chains: + +```python +# Python - if/elif/else +def describe_number(n): + if n < 0: + return "negative" + elif n == 0: + return "zero" + elif n < 10: + return "small" + else: + return "large" +``` + +### Rust: Exhaustive Pattern Matching + + +```python !! py +# Python - Multiple conditions +def get_day_name(n): + if n == 1: + return "Monday" + elif n == 2: + return "Tuesday" + elif n == 3: + return "Wednesday" + elif n == 4: + return "Thursday" + elif n == 5: + return "Friday" + elif n == 6: + return "Saturday" + elif n == 7: + return "Sunday" + else: + return "Invalid day" +``` + +```rust !! rs +// Rust - Exhaustive match +fn get_day_name(n: u32) -> &'static str { + match n { + 1 => "Monday", + 2 => "Tuesday", + 3 => "Wednesday", + 4 => "Thursday", + 5 => "Friday", + 6 => "Saturday", + 7 => "Sunday", + _ => "Invalid day", // Catch-all + } +} + +// Or use ranges +fn classify_number(n: i32) -> &'static str { + match n { + 0 => "zero", + 1..=9 => "single digit", // Inclusive range + 10..=99 => "double digit", + _ => "other", + } +} +``` + + +### Destructuring in Patterns + + +```python !! py +# Python - Manual field access +class Point: + def __init__(self, x, y): + self.x = x + self.y = y + +def describe_point(point): + if point.x == 0 and point.y == 0: + return "origin" + elif point.x == 0: + return "on y-axis" + elif point.y == 0: + return "on x-axis" + else: + return f"at ({point.x}, {point.y})" +``` + +```rust !! rs +// Rust - Destructuring in match +struct Point { + x: i32, + y: i32, +} + +fn describe_point(point: &Point) -> String { + match point { + Point { x: 0, y: 0 } => String::from("origin"), + Point { x: 0, y } => format!("on y-axis at y={}", y), + Point { x, y: 0 } => format!("on x-axis at x={}", x), + Point { x, y } => format!("at ({}, {})", x, y), + } +} + +// Or with shorthand +fn describe_point_short(point: &Point) -> String { + match point { + Point { x: 0, y: 0 } => String::from("origin"), + Point { x: 0, y } => format!("y={}", y), + Point { x, y: 0 } => format!("x={}", x), + Point { x, y } => format!("({}, {})", x, y), + } +} +``` + + +### Matching Enums + + +```python !! py +# Python - Instance checking +class Message: + pass + +class Quit(Message): + pass + +class Move(Message): + def __init__(self, x, y): + self.x = x + self.y = y + +def handle_message(msg): + if isinstance(msg, Quit): + print("Quitting...") + elif isinstance(msg, Move): + print(f"Moving to ({msg.x}, {msg.y})") + else: + print("Unknown message") +``` + +```rust !! rs +// Rust - Pattern matching on enums +enum Message { + Quit, + Move { x: i32, y: i32 }, + Write(String), + ChangeColor(i32, i32, i32), +} + +fn handle_message(msg: &Message) { + match msg { + Message::Quit => { + println!("Quitting..."); + } + Message::Move { x, y } => { + println!("Moving to ({}, {})", x, y); + } + Message::Write(text) => { + println!("Writing: {}", text); + } + Message::ChangeColor(r, g, b) => { + println!("Color: {}, {}, {}", r, g, b); + } + } +} + +// Guards in match arms +fn handle_message_with_guard(msg: &Message) { + match msg { + Message::Move { x, y } if x < 0 || y < 0 => { + println!("Invalid move!"); + } + Message::Move { x, y } => { + println!("Moving to ({}, {})", x, y); + } + _ => println!("Other message"), + } +} +``` + + +## 8.5 Concise Patterns: if let and while let + +### if let: Match One Pattern + + +```python !! py +# Python - Check and extract +def process_option(value): + if value is not None: + # Work with value + print(f"Got: {value}") + else: + # Handle None + print("Got nothing") +``` + +```rust !! rs +// Rust - if let for single pattern matching +fn process_option(value: Option<&str>) { + // Match only Some, ignore None + if let Some(v) = value { + println!("Got: {}", v); + } else { + println!("Got nothing"); + } +} + +// Or even more concise +fn process_option_concise(value: Option<&str>) { + if let Some(v) = value { + println!("Got: {}", v); + } + // else is optional! +} + +// if let with else +fn process_option_else(value: Option<&str>) { + if let Some(v) = value { + println!("Processing: {}", v); + } else { + println!("No value provided"); + } +} +``` + + +### while let: Match Repeatedly + + +```python !! py +# Python - Loop until condition +def pop_until_empty(stack): + results = [] + while stack: + results.append(stack.pop()) + return results +``` + +```rust !! rs +// Rust - while let for repeated matching +fn pop_until_empty(stack: &mut Vec) -> Vec { + let mut results = Vec::new(); + + // Keep popping while there are elements + while let Some(val) = stack.pop() { + results.push(val); + } + + results +} + +// More complex example +fn process_results(values: Vec>) -> Vec { + let mut results = Vec::new(); + + for mut opt in values { + // Keep taking values until None + while let Some(val) = opt { + results.push(val); + opt = None; // Simulated state change + } + } + + results +} +``` + + +## 8.6 Advanced Patterns + +### Matching Multiple Patterns + + +```python !! py +# Python - Multiple conditions +def describe_value(x): + if x == 1 or x == 3 or x == 5 or x == 7 or x == 9: + return "odd digit" + elif x == 0 or x == 2 or x == 4 or x == 6 or x == 8: + return "even digit" + else: + return "other" +``` + +```rust !! rs +// Rust - Multiple patterns with | +fn describe_value(x: i32) -> &'static str { + match x { + 1 | 3 | 5 | 7 | 9 => "odd digit", + 0 | 2 | 4 | 6 | 8 => "even digit", + _ => "other", + } +} + +// Ranges with multiple patterns +fn classify_number_advanced(x: i32) -> &'static str { + match x { + 1..=10 => "1-10", + 20 | 30 | 40 => "20, 30, or 40", + _ => "other", + } +} +``` + + +### Ignoring Values + + +```python !! py +# Python - Ignore with _ +result, _ = some_function() # Ignore second value +``` + +```rust !! rs +// Rust - Multiple ways to ignore +// Ignore single value +let (x, _) = (1, 2); // x = 1, ignore 2 + +// Ignore multiple values +let _ = (1, 2, 3); // Ignore entire tuple + +// Ignoring in struct patterns +struct Point3D { + x: i32, + y: i32, + z: i32, +} + +fn get_x(point: Point3D) -> i32 { + match point { + Point3D { x, .. } => x, // Only care about x + } +} + +// Ignore with _ prefix (unused variable warning suppression) +let _x = 5; // Prefix with _ to suppress warning +``` + + +### @ Binding + + +```python !! py +# Python - Need to both test and capture +class Message: + pass + +class Move(Message): + def __init__(self, x, y): + self.x = x + self.y = y + +msg = Move(10, 20) +if isinstance(msg, Move) and msg.x > 5: + print(f"Large move: {msg.x}, {msg.y}") +``` + +```rust !! rs +// Rust - @ binding for match and capture +enum Message { + Move { x: i32, y: i32 }, +} + +fn describe_message(msg: &Message) -> String { + match msg { + // Match and bind with @ + Message::Move { x: x_val @ (10 | 20 | 30), y } => { + format!("Special move at x={}, y={}", x_val, y) + } + Message::Move { x, y } => { + format!("Normal move at x={}, y={}", x, y) + } + } +} + +// Another example +fn classify_range(n: i32) -> &'static str { + match n { + m @ 0..=10 => "small", + m @ 11..=100 => "medium", + m @ 101..=1000 => "large", + _ => "huge", + } +} +``` + + +## Common Pitfalls + +### Pitfall 1: Forgetting to Handle None + +```rust +// DON'T: Forget to handle None +let opt: Option = None; +let val = opt.unwrap(); // Panic! + +// DO: Handle None properly +let opt: Option = None; +let val = opt.unwrap_or(0); +``` + +### Pitfall 2: Non-Exhaustive Matches + +```rust +// DON'T: Forget match cases (won't compile) +enum Color { Red, Blue, Green } +let color = Color::Red; +match color { + Color::Red => println!("Red"), + Color::Blue => println!("Blue"), + // Missing Green! Compiler error! +} + +// DO: Handle all cases +match color { + Color::Red => println!("Red"), + Color::Blue => println!("Blue"), + Color::Green => println!("Green"), +} +``` + +### Pitfall 3: Expect Instead of Unwrap + +```rust +// DON'T: Use unwrap in production +let val = opt.unwrap(); // Panics with generic message + +// DO: Use expect for better error messages +let val = opt.expect("Failed to get value from config"); +``` + +## Best Practices + +1. **Prefer `Option` over null values** - Never use null pointer values +2. **Use `Result` for recoverable errors** - Save `panic!` for unrecoverable errors +3. **Leverage `match` for exhaustive handling** - Let the compiler ensure correctness +4. **Use `if let` for single patterns** - More concise than full match +5. **Combine with `?` operator** - Clean error propagation +6. **Create custom error types** - Type-safe error handling + +## Summary + +In this module, we covered: + +- **Enums**: Data-carrying variants for type-safe modeling +- **`Option`**: Null-safe programming without runtime errors +- **`Result`**: Explicit error handling without exceptions +- **Pattern Matching**: Exhaustive, compiler-checked control flow +- **Concise Patterns**: `if let` and `while let` for readability + +Key takeaways: +- Rust's enums are algebraic data types, far more powerful than Python's +- Option and Result eliminate entire classes of runtime errors +- Pattern matching is exhaustive and compiler-verified +- The ? operator provides clean error propagation +- Pattern matching enables impossible-to-replicate type safety + +## Exercise + +Create a simple expression evaluator that: +1. Defines an enum for expressions (Number, Add, Multiply, etc.) +2. Implements an eval function using pattern matching +3. Handles errors gracefully with Result +4. Demonstrates nested pattern matching + +
+View Solution + +```rust +#[derive(Debug)] +enum Expr { + Number(f64), + Add(Box, Box), + Multiply(Box, Box), + Variable(String), +} + +#[derive(Debug)] +enum EvalError { + DivisionByZero, + UnknownVariable(String), +} + +fn eval(expr: &Expr, vars: &std::collections::HashMap) -> Result { + match expr { + Expr::Number(n) => Ok(*n), + Expr::Add(left, right) => { + let l = eval(left, vars)?; + let r = eval(right, vars)?; + Ok(l + r) + } + Expr::Multiply(left, right) => { + let l = eval(left, vars)?; + let r = eval(right, vars)?; + Ok(l * r) + } + Expr::Variable(name) => { + vars.get(name) + .copied() + .ok_or(EvalError::UnknownVariable(name.clone())) + } + } +} + +fn main() { + let expr = Expr::Add( + Box::new(Expr::Number(5.0)), + Box::new(Expr::Multiply( + Box::new(Expr::Number(3.0)), + Box::new(Expr::Number(2.0)), + )), + ); + + match eval(&expr, &std::collections::HashMap::new()) { + Ok(result) => println!("Result: {}", result), // 11.0 + Err(e) => println!("Error: {:?}", e), + } +} +``` + +
+ +Next up: **Module 9 - Error Handling**, where we'll dive deep into Rust's error handling patterns and best practices! diff --git a/content/docs/py2rust/module-08-enums.zh-cn.mdx b/content/docs/py2rust/module-08-enums.zh-cn.mdx new file mode 100644 index 0000000..d80ac82 --- /dev/null +++ b/content/docs/py2rust/module-08-enums.zh-cn.mdx @@ -0,0 +1,915 @@ +--- +title: "模块 8:枚举和模式匹配" +description: "探索 Rust 强大的枚举系统和模式匹配功能,与 Python 的类层次结构和控制流进行对比" +--- + +# 模块 8:枚举和模式匹配 + +欢迎来到模块 8!这是 Rust 真正闪耀的地方。Rust 的枚举系统和模式匹配远比 Python 中的任何功能都强大,能够实现类型安全、表达性强的代码,这在动态语言中是无法复制的。 + +## 学习目标 + +完成本模块后,你将能够: +- 定义和使用带有数据变体的枚举 +- 利用 `Option` 实现空值安全编程 +- 使用 `Result` 进行错误处理 +- 使用 `match` 表达式编写强大的模式匹配 +- 使用 `if let` 和 `while let` 进行简洁的模式匹配 +- 理解穷尽匹配和编译器保证 + +## 8.1 枚举:不仅仅是常量 + +### Python:有限的枚举支持 + +Python 的枚举相对简单: + +```python +# Python - 基本枚举 +from enum import Enum + +class Color(Enum): + RED = 1 + GREEN = 2 + BLUE = 3 + +# 使用 +color = Color.RED +print(color.value) # 1 +``` + +### Rust:携带数据的枚举 + +Rust 枚举要强大得多 - 它们可以携带数据: + + +```python !! py +# Python - 使用类模拟数据变体 +class Color: + def __init__(self, value): + self.value = value + +class RGB(Color): + def __init__(self, r, g, b): + self.r = r + self.g = g + self.b = b + +class HSV(Color): + def __init__(self, h, s, v): + self.h = h + self.s = s + self.v = v + +# 类型检查是手动的 +color = RGB(255, 0, 0) +if isinstance(color, RGB): + print(f"R={color.r}, G={color.g}, B={color.b}") +``` + +```rust !! rs +// Rust - 代数数据类型 +enum Color { + RGB(u8, u8, u8), + HSV(u8, u8, u8), +} + +// 使用 +let color = Color::RGB(255, 0, 0); + +// 模式匹配提取数据 +match color { + Color::RGB(r, g, b) => { + println!("R={}, G={}, B={}", r, g, b); + } + Color::HSV(h, s, v) => { + println!("H={}, S={}, V={}", h, s, v); + } +} + +// 使用命名字段 +enum Message { + Quit, + Move { x: i32, y: i32 }, + Write(String), + ChangeColor(u8, u8, u8), +} + +let msg = Message::Move { x: 10, y: 20 }; +``` + + +### 枚举的威力 + + +```python !! py +# Python - 手动类型检查和错误 +class Shape: + pass + +class Circle(Shape): + def __init__(self, radius): + self.radius = radius + +class Rectangle(Shape): + def __init__(self, width, height): + self.width = width + self.height = height + +def area(shape): + if isinstance(shape, Circle): + return 3.14 * shape.radius ** 2 + elif isinstance(shape, Rectangle): + return shape.width * shape.height + else: + raise ValueError("Unknown shape") +``` + +```rust !! rs +// Rust - 使用穷尽匹配的类型安全 +enum Shape { + Circle(f64), + Rectangle { width: f64, height: f64 }, +} + +fn area(shape: &Shape) -> f64 { + match shape { + Shape::Circle(radius) => 3.14 * radius * radius, + Shape::Rectangle { width, height } => width * height, + } +} + +// 编译器确保所有情况都被覆盖! +let circle = Shape::Circle(5.0); +println!("Area: {}", area(&circle)); +``` + + +## 8.2 `Option`:消除 Null + +### Python:None 和运行时错误 + +Python 使用 `None` 表示不存在,但这可能导致运行时错误: + + +```python !! py +# Python - None 无处不在 +def find_user(user_id): + if user_id == 1: + return {"name": "Alice", "email": "alice@example.com"} + return None + +# 如果忘记检查会出现运行时错误! +user = find_user(2) +print(user["name"]) # TypeError: 'NoneType' is not subscriptable + +# 必须手动检查 +user = find_user(2) +if user is not None: + print(user["name"]) +else: + print("User not found") +``` + +```rust !! rs +// Rust - Option 强制处理 +fn find_user(user_id: u32) -> Option<&'static str> { + if user_id == 1 { + Some("Alice") + } else { + None + } +} + +// 编译器强制你处理 None 情况 +let user = find_user(2); +match user { + Some(name) => println!("User: {}", name), + None => println!("User not found"), +} + +// 或使用辅助方法 +let user = find_user(1); +println!("User: {}", user.unwrap_or("Unknown")); + +// 链式操作 +let user = find_user(2) + .map(|name| name.to_uppercase()) + .unwrap_or_else(|| "DEFAULT".to_string()); +``` + + +### Option 方法 + + +```python !! py +# Python - 手动 None 处理 +def get_score(name): + scores = {"Alice": 95, "Bob": 87} + return scores.get(name) + +# 手动检查 +score = get_score("Charlie") +if score is not None: + print(f"Score: {score}") +else: + print("No score") + +# 提供默认值 +score = get_score("Charlie") or 0 +print(f"Score: {score}") +``` + +```rust !! rs +// Rust - 丰富的 Option API +fn get_score(name: &str) -> Option { + let scores = [("Alice", 95), ("Bob", 87)]; + scores.iter() + .find(|(n, _)| *n == name) + .map(|(_, score)| *score) +} + +// 模式匹配 +let score = get_score("Charlie"); +match score { + Some(s) => println!("Score: {}", s), + None => println!("No score"), +} + +// 提供默认值 +let score = get_score("Charlie").unwrap_or(0); +println!("Score: {}", score); + +// 使用 map 转换 +let doubled = get_score("Alice").map(|s| s * 2); +println!("Doubled: {:?}", doubled); + +// 链式操作 +let result = get_score("Charlie") + .or(Some(0)) + .map(|s| s * 2) + .unwrap_or(0); +``` + + +## 8.3 `Result`:正确的错误处理 + +### Python:异常和 Try/Catch + +Python 使用异常进行错误处理: + + +```python !! py +# Python - 异常 +def divide(a, b): + if b == 0: + raise ValueError("Cannot divide by zero") + return a / b + +# 必须使用 try/catch +try: + result = divide(10, 0) +except ValueError as e: + print(f"Error: {e}") +else: + print(f"Result: {result}") + +# 多种异常类型 +try: + result = divide(10, "2") +except ValueError as e: + print(f"ValueError: {e}") +except TypeError as e: + print(f"TypeError: {e}") +``` + +```rust !! rs +// Rust - Result 用于显式错误 +fn divide(a: f64, b: f64) -> Result { + if b == 0.0 { + Err(String::from("Cannot divide by zero")) + } else { + Ok(a / b) + } +} + +// 必须处理两种情况 +let result = divide(10.0, 0.0); +match result { + Ok(value) => println!("Result: {}", value), + Err(e) => println!("Error: {}", e), +} + +// 或在返回 Result 的函数中使用 ? 运算符 +fn calculate() -> Result<(), String> { + let result = divide(10.0, 2.0)?; + println!("Result: {}", result); + Ok(()) +} + +// 使用自定义错误枚举处理多种错误类型 +#[derive(Debug)] +enum MathError { + DivisionByZero, + InvalidInput(String), +} + +fn divide_safe(a: f64, b: f64) -> Result { + if b == 0.0 { + Err(MathError::DivisionByZero) + } else if a.is_nan() || b.is_nan() { + Err(MathError::InvalidInput("NaN detected".to_string())) + } else { + Ok(a / b) + } +} +``` + + +### 组合 Result + + +```python !! py +# Python - 手动错误传播 +def step1(): + return "data" + +def step2(data): + if data == "error": + raise ValueError("Step 2 failed") + return data + "_processed" + +def pipeline(): + try: + data = step1() + result = step2(data) + return result + except ValueError as e: + print(f"Pipeline failed: {e}") + return None +``` + +```rust !! rs +// Rust - 可组合的 Result 操作 +fn step1() -> Result<&'static str, String> { + Ok("data") +} + +fn step2(data: &str) -> Result { + if data == "error" { + Err("Step 2 failed".to_string()) + } else { + Ok(format!("{}_processed", data)) + } +} + +// 使用 match 手动传播 +fn pipeline() -> Result { + let data = step1()?; + let result = step2(&data)?; + Ok(result) +} + +// 或使用 and_then 进行链式调用 +fn pipeline() -> Result { + step1() + .and_then(|data| step2(data)) +} + +// 组合多个操作 +fn pipeline_complex() -> Result { + step1() + .and_then(|data| step2(data)) + .map(|result| result.to_uppercase()) + .map_err(|e| format!("Pipeline failed: {}", e)) +} +``` + + +## 8.4 使用 match 进行模式匹配 + +### Python:if/elif/else 链 + +Python 依赖 if/elif/else 链: + +```python +# Python - if/elif/else +def describe_number(n): + if n < 0: + return "negative" + elif n == 0: + return "zero" + elif n < 10: + return "small" + else: + return "large" +``` + +### Rust:穷尽模式匹配 + + +```python !! py +# Python - 多个条件 +def get_day_name(n): + if n == 1: + return "Monday" + elif n == 2: + return "Tuesday" + elif n == 3: + return "Wednesday" + elif n == 4: + return "Thursday" + elif n == 5: + return "Friday" + elif n == 6: + return "Saturday" + elif n == 7: + return "Sunday" + else: + return "Invalid day" +``` + +```rust !! rs +// Rust - 穷尽 match +fn get_day_name(n: u32) -> &'static str { + match n { + 1 => "Monday", + 2 => "Tuesday", + 3 => "Wednesday", + 4 => "Thursday", + 5 => "Friday", + 6 => "Saturday", + 7 => "Sunday", + _ => "Invalid day", // 捕获所有 + } +} + +// 或使用范围 +fn classify_number(n: i32) -> &'static str { + match n { + 0 => "zero", + 1..=9 => "single digit", // 包含范围 + 10..=99 => "double digit", + _ => "other", + } +} +``` + + +### 模式中的解构 + + +```python !! py +# Python - 手动字段访问 +class Point: + def __init__(self, x, y): + self.x = x + self.y = y + +def describe_point(point): + if point.x == 0 and point.y == 0: + return "origin" + elif point.x == 0: + return "on y-axis" + elif point.y == 0: + return "on x-axis" + else: + return f"at ({point.x}, {point.y})" +``` + +```rust !! rs +// Rust - match 中的解构 +struct Point { + x: i32, + y: i32, +} + +fn describe_point(point: &Point) -> String { + match point { + Point { x: 0, y: 0 } => String::from("origin"), + Point { x: 0, y } => format!("on y-axis at y={}", y), + Point { x, y: 0 } => format!("on x-axis at x={}", x), + Point { x, y } => format!("at ({}, {})", x, y), + } +} + +// 或使用简写 +fn describe_point_short(point: &Point) -> String { + match point { + Point { x: 0, y: 0 } => String::from("origin"), + Point { x: 0, y } => format!("y={}", y), + Point { x, y: 0 } => format!("x={}", x), + Point { x, y } => format!("({}, {})", x, y), + } +} +``` + + +### 匹配枚举 + + +```python !! py +# Python - 实例检查 +class Message: + pass + +class Quit(Message): + pass + +class Move(Message): + def __init__(self, x, y): + self.x = x + self.y = y + +def handle_message(msg): + if isinstance(msg, Quit): + print("Quitting...") + elif isinstance(msg, Move): + print(f"Moving to ({msg.x}, {msg.y})") + else: + print("Unknown message") +``` + +```rust !! rs +// Rust - 枚举上的模式匹配 +enum Message { + Quit, + Move { x: i32, y: i32 }, + Write(String), + ChangeColor(i32, i32, i32), +} + +fn handle_message(msg: &Message) { + match msg { + Message::Quit => { + println!("Quitting..."); + } + Message::Move { x, y } => { + println!("Moving to ({}, {})", x, y); + } + Message::Write(text) => { + println!("Writing: {}", text); + } + Message::ChangeColor(r, g, b) => { + println!("Color: {}, {}, {}", r, g, b); + } + } +} + +// match 分支中的守卫 +fn handle_message_with_guard(msg: &Message) { + match msg { + Message::Move { x, y } if x < 0 || y < 0 => { + println!("Invalid move!"); + } + Message::Move { x, y } => { + println!("Moving to ({}, {})", x, y); + } + _ => println!("Other message"), + } +} +``` + + +## 8.5 简洁模式:if let 和 while let + +### if let:匹配单个模式 + + +```python !! py +# Python - 检查和提取 +def process_option(value): + if value is not None: + # 使用 value 工作 + print(f"Got: {value}") + else: + # 处理 None + print("Got nothing") +``` + +```rust !! rs +// Rust - if let 用于单模式匹配 +fn process_option(value: Option<&str>) { + // 只匹配 Some,忽略 None + if let Some(v) = value { + println!("Got: {}", v); + } else { + println!("Got nothing"); + } +} + +// 或更简洁 +fn process_option_concise(value: Option<&str>) { + if let Some(v) = value { + println!("Got: {}", v); + } + // else 是可选的! +} + +// if let with else +fn process_option_else(value: Option<&str>) { + if let Some(v) = value { + println!("Processing: {}", v); + } else { + println!("No value provided"); + } +} +``` + + +### while let:重复匹配 + + +```python !! py +# Python - 循环直到条件满足 +def pop_until_empty(stack): + results = [] + while stack: + results.append(stack.pop()) + return results +``` + +```rust !! rs +// Rust - while let 用于重复匹配 +fn pop_until_empty(stack: &mut Vec) -> Vec { + let mut results = Vec::new(); + + // 保持弹出直到没有元素 + while let Some(val) = stack.pop() { + results.push(val); + } + + results +} + +// 更复杂的例子 +fn process_results(values: Vec>) -> Vec { + let mut results = Vec::new(); + + for mut opt in values { + // 持续获取值直到 None + while let Some(val) = opt { + results.push(val); + opt = None; // 模拟状态变化 + } + } + + results +} +``` + + +## 8.6 高级模式 + +### 匹配多个模式 + + +```python !! py +# Python - 多个条件 +def describe_value(x): + if x == 1 or x == 3 or x == 5 or x == 7 or x == 9: + return "odd digit" + elif x == 0 or x == 2 or x == 4 or x == 6 or x == 8: + return "even digit" + else: + return "other" +``` + +```rust !! rs +// Rust - 使用 | 的多个模式 +fn describe_value(x: i32) -> &'static str { + match x { + 1 | 3 | 5 | 7 | 9 => "odd digit", + 0 | 2 | 4 | 6 | 8 => "even digit", + _ => "other", + } +} + +// 范围和多个模式 +fn classify_number_advanced(x: i32) -> &'static str { + match x { + 1..=10 => "1-10", + 20 | 30 | 40 => "20, 30, or 40", + _ => "other", + } +} +``` + + +### 忽略值 + + +```python !! py +# Python - 使用 _ 忽略 +result, _ = some_function() # 忽略第二个值 +``` + +```rust !! rs +// Rust - 多种忽略方式 +// 忽略单个值 +let (x, _) = (1, 2); // x = 1,忽略 2 + +// 忽略多个值 +let _ = (1, 2, 3); // 忽略整个元组 + +// 结构体模式中的忽略 +struct Point3D { + x: i32, + y: i32, + z: i32, +} + +fn get_x(point: Point3D) -> i32 { + match point { + Point3D { x, .. } => x, // 只关心 x + } +} + +// 使用 _ 前缀忽略(抑制未使用变量警告) +let _x = 5; // 使用 _ 前缀抑制警告 +``` + + +### @ 绑定 + + +```python !! py +# Python - 需要同时测试和捕获 +class Message: + pass + +class Move(Message): + def __init__(self, x, y): + self.x = x + self.y = y + +msg = Move(10, 20) +if isinstance(msg, Move) and msg.x > 5: + print(f"Large move: {msg.x}, {msg.y}") +``` + +```rust !! rs +// Rust - @ 绑定用于匹配和捕获 +enum Message { + Move { x: i32, y: i32 }, +} + +fn describe_message(msg: &Message) -> String { + match msg { + // 使用 @ 匹配和绑定 + Message::Move { x: x_val @ (10 | 20 | 30), y } => { + format!("Special move at x={}, y={}", x_val, y) + } + Message::Move { x, y } => { + format!("Normal move at x={}, y={}", x, y) + } + } +} + +// 另一个例子 +fn classify_range(n: i32) -> &'static str { + match n { + m @ 0..=10 => "small", + m @ 11..=100 => "medium", + m @ 101..=1000 => "large", + _ => "huge", + } +} +``` + + +## 常见陷阱 + +### 陷阱 1:忘记处理 None + +```rust +// 不要:忘记处理 None +let opt: Option = None; +let val = opt.unwrap(); // 恐慌! + +// 应该:正确处理 None +let opt: Option = None; +let val = opt.unwrap_or(0); +``` + +### 陷阱 2:非穷尽匹配 + +```rust +// 不要:忘记 match 情况(不会编译) +enum Color { Red, Blue, Green } +let color = Color::Red; +match color { + Color::Red => println!("Red"), + Color::Blue => println!("Blue"), + // 缺少 Green!编译器错误! +} + +// 应该:处理所有情况 +match color { + Color::Red => println!("Red"), + Color::Blue => println!("Blue"), + Color::Green => println!("Green"), +} +``` + +### 陷阱 3:使用 Expect 而不是 Unwrap + +```rust +// 不要:在生产中使用 unwrap +let val = opt.unwrap(); // 恐慌,带有通用消息 + +// 应该:使用 expect 获得更好的错误消息 +let val = opt.expect("Failed to get value from config"); +``` + +## 最佳实践 + +1. **优先使用 `Option` 而不是 null 值** - 永远不要使用空指针值 +2. **对可恢复错误使用 `Result`** - 将 `panic!` 用于不可恢复的错误 +3. **利用 `match` 进行穷尽处理** - 让编译器确保正确性 +4. **对单个模式使用 `if let`** - 比完整的 match 更简洁 +5. **与 `?` 运算符结合** - 清晰的错误传播 +6. **创建自定义错误类型** - 类型安全的错误处理 + +## 总结 + +在本模块中,我们涵盖了: + +- **枚举**:用于类型安全建模的数据携带变体 +- **`Option`**:没有运行时错误的空值安全编程 +- **`Result`**:没有异常的显式错误处理 +- **模式匹配**:穷尽的、编译器检查的控制流 +- **简洁模式**:`if let` 和 `while let` 提高可读性 + +关键要点: +- Rust 的枚举是代数数据类型,远比 Python 的强大 +- Option 和 Result 消除了整类运行时错误 +- 模式匹配是穷尽的且经过编译器验证 +- ? 运算符提供清晰的错误传播 +- 模式匹配实现了无法复制的类型安全性 + +## 练习 + +创建一个简单的表达式求值器: +1. 定义表达式枚举(Number、Add、Multiply 等) +2. 使用模式匹配实现 eval 函数 +3. 使用 Result 优雅地处理错误 +4. 展示嵌套模式匹配 + +
+查看解决方案 + +```rust +#[derive(Debug)] +enum Expr { + Number(f64), + Add(Box, Box), + Multiply(Box, Box), + Variable(String), +} + +#[derive(Debug)] +enum EvalError { + DivisionByZero, + UnknownVariable(String), +} + +fn eval(expr: &Expr, vars: &std::collections::HashMap) -> Result { + match expr { + Expr::Number(n) => Ok(*n), + Expr::Add(left, right) => { + let l = eval(left, vars)?; + let r = eval(right, vars)?; + Ok(l + r) + } + Expr::Multiply(left, right) => { + let l = eval(left, vars)?; + let r = eval(right, vars)?; + Ok(l * r) + } + Expr::Variable(name) => { + vars.get(name) + .copied() + .ok_or(EvalError::UnknownVariable(name.clone())) + } + } +} + +fn main() { + let expr = Expr::Add( + Box::new(Expr::Number(5.0)), + Box::new(Expr::Multiply( + Box::new(Expr::Number(3.0)), + Box::new(Expr::Number(2.0)), + )), + ); + + match eval(&expr, &std::collections::HashMap::new()) { + Ok(result) => println!("Result: {}", result), // 11.0 + Err(e) => println!("Error: {:?}", e), + } +} +``` + +
+ +接下来:**模块 9 - 错误处理**,我们将深入研究 Rust 的错误处理模式和最佳实践! diff --git a/content/docs/py2rust/module-08-enums.zh-tw.mdx b/content/docs/py2rust/module-08-enums.zh-tw.mdx new file mode 100644 index 0000000..359cffe --- /dev/null +++ b/content/docs/py2rust/module-08-enums.zh-tw.mdx @@ -0,0 +1,455 @@ +--- +title: "模組 8:列舉和模式匹配" +description: "探索 Rust 強大的列舉系統和模式匹配功能,與 Python 的類層次結構和控制流進行對比" +--- + +# 模組 8:列舉和模式匹配 + +歡迎來到模組 8!這是 Rust 真正閃耀的地方。Rust 的列舉系統和模式匹配遠比 Python 中的任何功能都強大,能夠實現型別安全、表達性強的程式碼,這在動態語言中是無法複製的。 + +## 學習目標 + +完成本模組後,你將能夠: +- 定義和使用帶有資料變體的列舉 +- 利用 `Option` 實現空值安全程式設計 +- 使用 `Result` 進行錯誤處理 +- 使用 `match` 表達式編寫強大的模式匹配 +- 使用 `if let` 和 `while let` 進行簡潔的模式匹配 +- 理解窮盡匹配和編譯器保證 + +## 8.1 列舉:不僅僅是常量 + +### Python:有限的列舉支援 + +Python 的列舉相對簡單: + +```python +# Python - 基本列舉 +from enum import Enum + +class Color(Enum): + RED = 1 + GREEN = 2 + BLUE = 3 + +# 使用 +color = Color.RED +print(color.value) # 1 +``` + +### Rust:攜帶資料的列舉 + +Rust 列舉要強大得多 - 它們可以攜帶資料: + + +```python !! py +# Python - 使用類模擬資料變體 +class Color: + def __init__(self, value): + self.value = value + +class RGB(Color): + def __init__(self, r, g, b): + self.r = r + self.g = g + self.b = b + +class HSV(Color): + def __init__(self, h, s, v): + self.h = h + self.s = s + self.v = v + +# 型別檢查是手動的 +color = RGB(255, 0, 0) +if isinstance(color, RGB): + print(f"R={color.r}, G={color.g}, B={color.b}") +``` + +```rust !! rs +// Rust - 代數資料型別 +enum Color { + RGB(u8, u8, u8), + HSV(u8, u8, u8), +} + +// 使用 +let color = Color::RGB(255, 0, 0); + +// 模式匹配提取資料 +match color { + Color::RGB(r, g, b) => { + println!("R={}, G={}, B={}", r, g, b); + } + Color::HSV(h, s, v) => { + println!("H={}, S={}, V={}", h, s, v); + } +} + +// 使用命名字段 +enum Message { + Quit, + Move { x: i32, y: i32 }, + Write(String), + ChangeColor(u8, u8, u8), +} + +let msg = Message::Move { x: 10, y: 20 }; +``` + + +## 8.2 `Option`:消除 Null + +### Python:None 和執行時錯誤 + +Python 使用 `None` 表示不存在,但這可能導致執行時錯誤: + + +```python !! py +# Python - None 無處不在 +def find_user(user_id): + if user_id == 1: + return {"name": "Alice", "email": "alice@example.com"} + return None + +# 如果忘記檢查會出現執行時錯誤! +user = find_user(2) +print(user["name"]) # TypeError: 'NoneType' is not subscriptable +``` + +```rust !! rs +// Rust - Option 強制處理 +fn find_user(user_id: u32) -> Option<&'static str> { + if user_id == 1 { + Some("Alice") + } else { + None + } +} + +// 編譯器強制你處理 None 情況 +let user = find_user(2); +match user { + Some(name) => println!("User: {}", name), + None => println!("User not found"), +} +``` + + +## 8.3 `Result`:正確的錯誤處理 + +### Python:異常和 Try/Catch + +Python 使用異常進行錯誤處理: + + +```python !! py +# Python - 異常 +def divide(a, b): + if b == 0: + raise ValueError("Cannot divide by zero") + return a / b + +# 必須使用 try/catch +try: + result = divide(10, 0) +except ValueError as e: + print(f"Error: {e}") +``` + +```rust !! rs +// Rust - Result 用於顯式錯誤 +fn divide(a: f64, b: f64) -> Result { + if b == 0.0 { + Err(String::from("Cannot divide by zero")) + } else { + Ok(a / b) + } +} + +// 必須處理兩種情況 +let result = divide(10.0, 0.0); +match result { + Ok(value) => println!("Result: {}", value), + Err(e) => println!("Error: {}", e), +} +``` + + +## 8.4 使用 match 進行模式匹配 + +### Rust:窮盡模式匹配 + + +```python !! py +# Python - 多個條件 +def get_day_name(n): + if n == 1: + return "Monday" + elif n == 2: + return "Tuesday" + # ... 更多條件 + else: + return "Invalid day" +``` + +```rust !! rs +// Rust - 窮盡 match +fn get_day_name(n: u32) -> &'static str { + match n { + 1 => "Monday", + 2 => "Tuesday", + 3 => "Wednesday", + 4 => "Thursday", + 5 => "Friday", + 6 => "Saturday", + 7 => "Sunday", + _ => "Invalid day", // 捕獲所有 + } +} + +// 或使用範圍 +fn classify_number(n: i32) -> &'static str { + match n { + 0 => "zero", + 1..=9 => "single digit", + 10..=99 => "double digit", + _ => "other", + } +} +``` + + +### 列舉的模式匹配 + + +```rust !! rs +// Rust - 列舉上的模式匹配 +enum Message { + Quit, + Move { x: i32, y: i32 }, + Write(String), + ChangeColor(i32, i32, i32), +} + +fn handle_message(msg: &Message) { + match msg { + Message::Quit => { + println!("Quitting..."); + } + Message::Move { x, y } => { + println!("Moving to ({}, {})", x, y); + } + Message::Write(text) => { + println!("Writing: {}", text); + } + Message::ChangeColor(r, g, b) => { + println!("Color: {}, {}, {}", r, g, b); + } + } +} +``` + + +## 8.5 簡潔模式:if let 和 while let + +### if let:匹配單個模式 + + +```rust !! rs +// Rust - if let 用於單模式匹配 +fn process_option(value: Option<&str>) { + // 只匹配 Some,忽略 None + if let Some(v) = value { + println!("Got: {}", v); + } else { + println!("Got nothing"); + } +} + +// 或更簡潔(else 是可選的) +fn process_option_concise(value: Option<&str>) { + if let Some(v) = value { + println!("Got: {}", v); + } +} +``` + + +### while let:重複匹配 + + +```rust !! rs +// Rust - while let 用於重複匹配 +fn pop_until_empty(stack: &mut Vec) -> Vec { + let mut results = Vec::new(); + + // 保持彈出直到沒有元素 + while let Some(val) = stack.pop() { + results.push(val); + } + + results +} +``` + + +## 8.6 高級模式 + +### 匹配多個模式 + + +```rust !! rs +// Rust - 使用 | 的多個模式 +fn describe_value(x: i32) -> &'static str { + match x { + 1 | 3 | 5 | 7 | 9 => "odd digit", + 0 | 2 | 4 | 6 | 8 => "even digit", + _ => "other", + } +} + +// 範圍和多個模式 +fn classify_number_advanced(x: i32) -> &'static str { + match x { + 1..=10 => "1-10", + 20 | 30 | 40 => "20, 30, or 40", + _ => "other", + } +} +``` + + +### @ 綁定 + + +```rust !! rs +// Rust - @ 綁定用於匹配和捕獲 +enum Message { + Move { x: i32, y: i32 }, +} + +fn describe_message(msg: &Message) -> String { + match msg { + // 使用 @ 匹配和綁定 + Message::Move { x: x_val @ (10 | 20 | 30), y } => { + format!("Special move at x={}, y={}", x_val, y) + } + Message::Move { x, y } => { + format!("Normal move at x={}, y={}", x, y) + } + } +} +``` + + +## 常見陷阱 + +### 陷阱 1:忘記處理 None + +```rust +// 不要:忘記處理 None +let opt: Option = None; +let val = opt.unwrap(); // 恐慌! + +// 應該:正確處理 None +let opt: Option = None; +let val = opt.unwrap_or(0); +``` + +### 陷阱 2:非窮盡匹配 + +```rust +// 不要:忘記 match 情況(不會編譯) +enum Color { Red, Blue, Green } +let color = Color::Red; +match color { + Color::Red => println!("Red"), + Color::Blue => println!("Blue"), + // 缺少 Green!編譯器錯誤! +} + +// 應該:處理所有情況 +match color { + Color::Red => println!("Red"), + Color::Blue => println!("Blue"), + Color::Green => println!("Green"), +} +``` + +## 最佳實踐 + +1. **優先使用 `Option` 而不是 null 值** - 永遠不要使用空指標值 +2. **對可恢復錯誤使用 `Result`** - 將 `panic!` 用於不可恢復的錯誤 +3. **利用 `match` 進行窮盡處理** - 讓編譯器確保正確性 +4. **對單個模式使用 `if let`** - 比完整的 match 更簡潔 +5. **與 `?` 運算符結合** - 清晰的錯誤傳播 + +## 總結 + +在本模組中,我們涵蓋了: + +- **列舉**:用於型別安全建模的資料攜帶變體 +- **`Option`**:沒有執行時錯誤的空值安全程式設計 +- **`Result`**:沒有異常的顯式錯誤處理 +- **模式匹配**:窮盡的、編譯器檢查的控制流 +- **簡潔模式**:`if let` 和 `while let` 提高可讀性 + +關鍵要點: +- Rust 的列舉是代數資料型別,遠比 Python 的強大 +- Option 和 Result 消除了整類執行時錯誤 +- 模式匹配是窮盡的且經過編譯器驗證 +- ? 運算符提供清晰的錯誤傳播 +- 模式匹配實現了無法複製的型別安全性 + +## 練習 + +創建一個簡單的表達式求值器: +1. 定義表達式列舉(Number、Add、Multiply 等) +2. 使用模式匹配實現 eval 函數 +3. 使用 Result 優雅地處理錯誤 +4. 展示嵌套模式匹配 + +
+檢視解決方案 + +```rust +#[derive(Debug)] +enum Expr { + Number(f64), + Add(Box, Box), + Multiply(Box, Box), + Variable(String), +} + +#[derive(Debug)] +enum EvalError { + DivisionByZero, + UnknownVariable(String), +} + +fn eval(expr: &Expr, vars: &std::collections::HashMap) -> Result { + match expr { + Expr::Number(n) => Ok(*n), + Expr::Add(left, right) => { + let l = eval(left, vars)?; + let r = eval(right, vars)?; + Ok(l + r) + } + Expr::Multiply(left, right) => { + let l = eval(left, vars)?; + let r = eval(right, vars)?; + Ok(l * r) + } + Expr::Variable(name) => { + vars.get(name) + .copied() + .ok_or(EvalError::UnknownVariable(name.clone())) + } + } +} +``` + +
+ +接下來:**模組 9 - 錯誤處理**,我們將深入研究 Rust 的錯誤處理模式和最佳實踐! diff --git a/content/docs/py2rust/module-09-error-handling.mdx b/content/docs/py2rust/module-09-error-handling.mdx new file mode 100644 index 0000000..8a6ee56 --- /dev/null +++ b/content/docs/py2rust/module-09-error-handling.mdx @@ -0,0 +1,823 @@ +--- +title: "Module 9: Error Handling" +description: "Master Rust's error handling philosophy including panic!, Result, the ? operator, and best practices for robust error management" +--- + +# Module 9: Error Handling + +Welcome to Module 9! Error handling is one of Rust's most distinctive features. Unlike Python's exception-based approach, Rust uses a type-safe, explicit system that catches entire classes of errors at compile time. + +## Learning Objectives + +By the end of this module, you'll be able to: +- Understand Rust's two error handling categories: recoverable and unrecoverable +- Use `panic!` for unrecoverable errors +- Work with `Result` for recoverable errors +- Leverage the `?` operator for clean error propagation +- Create custom error types +- Apply error handling best practices + +## 9.1 Two Categories of Errors + +### Python: Exceptions for Everything + +Python uses exceptions for all errors: + +```python +# Python - Exceptions for everything +# Recoverable errors +try: + file = open("nonexistent.txt") +except FileNotFoundError: + print("File not found") + +# Unrecoverable errors (rare in Python) +raise RuntimeError("Something went wrong") +``` + +### Rust: Explicit Categories + +Rust distinguishes between recoverable and unrecoverable errors: + + +```python !! py +# Python - No distinction at language level +def divide(a, b): + if b == 0: + raise ValueError("Division by zero") + return a / b + +# Caller must handle exception +try: + result = divide(10, 0) +except ValueError as e: + print(f"Error: {e}") +``` + +```rust !! rs +// Rust - Explicit categories +// Recoverable: Result +fn divide(a: f64, b: f64) -> Result { + if b == 0.0 { + return Err(String::from("Division by zero")); + } + Ok(a / b) +} + +// Unrecoverable: panic! +fn critical_error() { + panic!("Critical system failure!"); +} + +// Usage +match divide(10.0, 0.0) { + Ok(result) => println!("Result: {}", result), + Err(e) => println!("Error: {}", e), +} +``` + + +## 9.2 Unrecoverable Errors with panic! + +### Python: Raising Exceptions + +```python +# Python - Raise exceptions +def critical_function(): + raise RuntimeError("Critical failure!") + +# Program continues if caught +try: + critical_function() +except RuntimeError: + print("Caught") +``` + +### Rust: Panicking + + +```python !! py +# Python - Stack trace on exception +def bad_function(): + raise ValueError("Bad value!") + +# Traceback shows call stack +bad_function() +``` + +```rust !! rs +// Rust - Panic with stack trace +fn bad_function() { + panic!("Bad value!"); +} + +// Stack unwinding (default) +// bad_function(); // Prints stack trace + +// Abort immediately (no cleanup) +// std::panic::set_hook(Box::new(|_| { +// std::process::exit(1); +// })); + +// Panic in debug vs release +#[cfg(debug_assertions)] +fn debug_only_panic() { + panic!("Debug only error!"); +} +``` + + +### Common Panics + + +```python !! py +# Python - Runtime errors +# Index out of range +nums = [1, 2, 3] +print(nums[10]) # IndexError + +# None access +x = None +print(x.value) # AttributeError + +# Divide by zero +x = 10 / 0 # ZeroDivisionError +``` + +```rust !! rs +// Rust - Panics on invalid operations +// Index out of range +let nums = vec![1, 2, 3]; +// let val = nums[10]; // Panic! + +// None access (if using unwrap) +let opt: Option = None; +// let val = opt.unwrap(); // Panic! + +// Better alternatives +let val = nums.get(10); // Returns None +let val = opt.unwrap_or(0); // Returns 0 + +// Assert with custom message +let x = 10; +assert_eq!(x, 10, "x should be 10"); +assert!(x > 5, "x must be greater than 5"); + +// Debug-only checks +debug_assert!(x > 0); // Only checked in debug builds +``` + + +### Recovering from Panics + + +```python !! py +# Python - Catch exceptions +try: + raise ValueError("Error") +except ValueError: + print("Caught") +finally: + print("Cleanup") +``` + +```rust !! rs +// Rust - Catch panics (use sparingly!) +use std::panic; + +fn might_panic() { + panic!("Oops!"); +} + +fn catch_panic() { + let result = panic::catch_unwind(|| { + might_panic(); + }); + + match result { + Ok(_) => println!("No panic"), + Err(_) => println!("Panic was caught"), + } +} + +// Warning: Don't use catch_unwind for normal error handling! +// It's mainly for: +// 1. FFI boundaries +// 2. Testing panic behavior +// 3. Wrapping unsafe code +``` + + +## 9.3 Recoverable Errors with `Result` + +### Python: Try/Except Pattern + +```python +# Python - Try/except for error handling +def read_file(filename): + try: + with open(filename) as f: + return f.read() + except FileNotFoundError: + return None + except IOError as e: + print(f"IO Error: {e}") + return None +``` + +### Rust: Result Type + + +```python !! py +# Python - Manual error handling +def parse_number(s): + try: + return int(s) + except ValueError: + return None + +# Usage +num = parse_number("42") +if num is not None: + print(f"Got: {num}") +``` + +```rust !! rs +// Rust - Type-safe error handling +fn parse_number(s: &str) -> Result { + s.parse::() +} + +// Usage with match +match parse_number("42") { + Ok(num) => println!("Got: {}", num), + Err(e) => println!("Parse error: {}", e), +} + +// Provide default +let num = parse_number("invalid").unwrap_or(0); + +// Provide default with function +let num = parse_number("invalid") + .unwrap_or_else(|_| 0); + +// Convert error type +let num: Result = parse_number("42") + .map_err(|e| e.to_string()); +``` + + +### Propagating Errors + + +```python !! py +# Python - Manual propagation +def step1(): + return 42 + +def step2(value): + if value < 0: + raise ValueError("Value must be positive") + return value * 2 + +def process(): + try: + v = step1() + result = step2(v) + return result + except ValueError as e: + print(f"Error: {e}") + return None +``` + +```rust !! rs +// Rust - Clean propagation with ? +fn step1() -> Result { + Ok(42) +} + +fn step2(value: i32) -> Result { + if value < 0 { + return Err("Value must be positive".to_string()); + } + Ok(value * 2) +} + +// Manual propagation +fn process_manual() -> Result { + let v = step1()?; + let result = step2(v)?; + Ok(result) +} + +// More concise +fn process() -> Result { + Ok(step2(step1()?)?) +} + +// Even more operations +fn process_complex() -> Result { + let v1 = step1()?; + let v2 = step2(v1)?; + let v3 = step2(v2)?; + Ok(v3) +} +``` + + +## 9.4 The ? Operator Deep Dive + +### Understanding ? + +The `?` operator is syntactic sugar for error propagation: + + +```python !! py +# Python - Manual error handling +def operation(): + result = risky_function() + if isinstance(result, Error): + return result + return process(result) +``` + +```rust !! rs +// Rust - ? operator does this: +fn operation() -> Result { + // Using ? + let value = risky_function()?; + + // Expands to: + // match risky_function() { + // Ok(v) => v, + // Err(e) => return Err(e), + // } + + process(value) +} +``` + + +### ? with Different Types + + +```rust !! rs +// ? can convert error types with From trait +use std::fs::File; +use std::io::{self, Read}; + +#[derive(Debug)] +enum AppError { + Io(io::Error), + Parse(String), +} + +// Implement From for automatic conversion +impl From for AppError { + fn from(error: io::Error) -> Self { + AppError::Io(error) + } +} + +fn read_config(path: &str) -> Result { + let mut file = File::open(path)?; // io::Error -> AppError + let mut contents = String::new(); + file.read_to_string(&mut contents)?; + Ok(contents) +} + +// Using ? with Option +fn parse_optional(input: Option<&str>) -> Result { + let s = input.ok_or("No input provided")?; + s.parse().map_err(|e| format!("Parse error: {}", e)) +} +``` + + +### Early Returns + + +```python !! py +# Python - Early returns on error +def validate_and_process(data): + if not data: + return None, "No data" + if len(data) > 100: + return None, "Data too large" + # Process data + return process(data), None +``` + +```rust !! rs +// Rust - Clean early returns with ? +fn validate_and_process(data: &str) -> Result { + if data.is_empty() { + return Err("No data".to_string()); + } + if data.len() > 100 { + return Err("Data too large".to_string()); + } + // Process data + Ok(process(data)) +} + +// Even cleaner with validation functions +fn validate(data: &str) -> Result<(), String> { + if data.is_empty() { + return Err("No data".to_string()); + } + if data.len() > 100 { + return Err("Data too large".to_string()); + } + Ok(()) +} + +fn validate_and_process_clean(data: &str) -> Result { + validate(data)?; + Ok(process(data)) +} +``` + + +## 9.5 Custom Error Types + +### Python: Custom Exceptions + +```python +# Python - Custom exception classes +class ValidationError(Exception): + pass + +class NetworkError(Exception): + def __init__(self, message, status_code): + super().__init__(message) + self.status_code = status_code + +def validate(value): + if not value: + raise ValidationError("Value is required") +``` + +### Rust: Custom Error Enums + + +```python !! py +# Python - Exception hierarchy +class AppError(Exception): + pass + +class ValidationError(AppError): + pass + +class NetworkError(AppError): + pass + +# Use with try/except +try: + validate(data) +except ValidationError as e: + print(f"Validation: {e}") +except NetworkError as e: + print(f"Network: {e}") +``` + +```rust !! rs +// Rust - Algebraic error types +#[derive(Debug)] +enum AppError { + ValidationError(String), + NetworkError { status_code: u16, message: String }, + DatabaseError(String), +} + +// Implement Display for user-friendly output +impl std::fmt::Display for AppError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + AppError::ValidationError(msg) => { + write!(f, "Validation error: {}", msg) + } + AppError::NetworkError { status_code, message } => { + write!(f, "Network error ({}): {}", status_code, message) + } + AppError::DatabaseError(msg) => { + write!(f, "Database error: {}", msg) + } + } + } +} + +// Implement Error for std compatibility +impl std::error::Error for AppError {} + +// Usage +fn validate(value: &str) -> Result<(), AppError> { + if value.is_empty() { + return Err(AppError::ValidationError("Value is required".to_string())); + } + Ok(()) +} + +// Convert to AppError using From +impl From for AppError { + fn from(error: std::io::Error) -> Self { + AppError::DatabaseError(error.to_string()) + } +} +``` + + +### Using thiserror and anyhow + + +```rust !! rs +// For library code: use thiserror +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum LibError { + #[error("Validation failed: {0}")] + ValidationError(String), + + #[error("IO error: {0}")] + Io(#[from] std::io::Error), + + #[error("Parse error: {0}")] + Parse(String), +} + +// For application code: use anyhow +use anyhow::{Result, Context}; + +fn read_config() -> Result { + let content = std::fs::read_to_string("config.toml") + .context("Failed to read config file")?; + Ok(content) +} + +fn main() -> Result<()> { + let config = read_config()?; + println!("Config: {}", config); + Ok(()) +} +``` + + +## 9.6 Error Handling Patterns + +### Pattern 1: Contextual Errors + + +```python !! py +# Python - Chain exceptions +def load_config(): + try: + return read_file("config.toml") + except IOError as e: + raise ConfigError(f"Failed to load config: {e}") from e +``` + +```rust !! rs +// Rust - Add context with map_err +fn load_config() -> Result { + std::fs::read_to_string("config.toml") + .map_err(|e| format!("Failed to load config: {}", e)) +} + +// Or with anyhow +use anyhow::{Context, Result}; + +fn load_config() -> Result { + std::fs::read_to_string("config.toml") + .context("Failed to load config file") +} +``` + + +### Pattern 2: Multiple Errors + + +```python !! py +# Python - Collect multiple errors +def validate_all(data): + errors = [] + if not data.get('name'): + errors.append("Name is required") + if not data.get('email'): + errors.append("Email is required") + if errors: + raise ValidationError(errors) +``` + +```rust !! rs +// Rust - Collect validation errors +#[derive(Debug)] +struct ValidationErrors { + errors: Vec, +} + +fn validate_all(data: &serde_json::Value) -> Result<(), ValidationErrors> { + let mut errors = Vec::new(); + + if data.get("name").is_none() { + errors.push("Name is required".to_string()); + } + if data.get("email").is_none() { + errors.push("Email is required".to_string()); + } + + if !errors.is_empty() { + return Err(ValidationErrors { errors }); + } + + Ok(()) +} +``` + + +### Pattern 3: Retry Logic + + +```python !! py +# Python - Retry logic +import time + +def fetch_with_retry(url, max_retries=3): + for attempt in range(max_retries): + try: + return fetch(url) + except NetworkError as e: + if attempt == max_retries - 1: + raise + time.sleep(2 ** attempt) # Exponential backoff +``` + +```rust !! rs +// Rust - Retry with backoff +use std::time::Duration; +use std::thread; + +fn fetch_with_retry(url: &str, max_retries: u32) -> Result { + for attempt in 0..max_retries { + match fetch(url) { + Ok(data) => return Ok(data), + Err(e) if attempt < max_retries - 1 => { + let backoff = Duration::from_secs(2_u64.pow(attempt)); + thread::sleep(backoff); + } + Err(e) => return Err(e), + } + } + unreachable!() +} +``` + + +## Common Pitfalls + +### Pitfall 1: Using panic! in Libraries + +```rust +// DON'T: Panic in library code +pub fn parse_config(s: &str) -> Config { + if s.is_empty() { + panic!("Config cannot be empty"); // Wrong! + } + // ... +} + +// DO: Return Result +pub fn parse_config(s: &str) -> Result { + if s.is_empty() { + return Err(Error::EmptyConfig); + } + // ... +} +``` + +### Pitfall 2: Ignoring Errors + +```rust +// DON'T: Ignore errors +let _ = File::open("config.txt"); // Error ignored! + +// DO: Handle errors explicitly +let file = File::open("config.txt")?; +// or +let file = File::open("config.txt") + .expect("Failed to open config.txt"); +``` + +### Pitfall 3: Overusing unwrap + +```rust +// DON'T: Excessive unwrap +let val = some_operation().unwrap(); // Might panic! + +// DO: Handle gracefully +let val = some_operation() + .unwrap_or_else(|e| { + eprintln!("Error: {}", e); + std::process::exit(1); + }); +``` + +## Best Practices + +1. **Use Result for recoverable errors** - Save panic! for truly unrecoverable situations +2. **Implement custom error types** - Use enums for structured error handling +3. **Provide context** - Add information about what operation failed +4. **Use libraries** - Leverage thiserror for libraries, anyhow for apps +5. **Document errors** - Document which errors can be returned +6. **Test error paths** - Write tests for error conditions + +## Summary + +In this module, we covered: + +- **Two error categories**: Recoverable (Result) vs unrecoverable (panic!) +- **panic!**: For unrecoverable errors with stack unwinding +- **`Result`**: Type-safe error handling +- **? operator**: Clean error propagation +- **Custom errors**: Structured error types with Display/Error traits +- **Error patterns**: Context, collection, retry logic + +Key takeaways: +- Rust makes errors explicit and type-safe +- Result catches errors at compile time +- The ? operator provides clean error propagation +- Custom error types enable structured error handling +- Choose the right error handling strategy for your context + +## Exercise + +Create a file processing application that: +1. Reads a file (handling IO errors) +2. Parses the content (handling parse errors) +3. Validates the data (handling validation errors) +4. Uses custom error types +5. Provides helpful error messages + +
+View Solution + +```rust +use std::fs; +use std::io; +use std::num::ParseIntError; + +#[derive(Debug)] +enum ProcessError { + Io(io::Error), + Parse(ParseIntError), + Validation(String), +} + +impl std::fmt::Display for ProcessError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + ProcessError::Io(e) => write!(f, "IO error: {}", e), + ProcessError::Parse(e) => write!(f, "Parse error: {}", e), + ProcessError::Validation(msg) => write!(f, "Validation error: {}", msg), + } + } +} + +impl std::error::Error for ProcessError {} + +impl From for ProcessError { + fn from(e: io::Error) -> Self { + ProcessError::Io(e) + } +} + +impl From for ProcessError { + fn from(e: ParseIntError) -> Self { + ProcessError::Parse(e) + } +} + +fn process_file(path: &str) -> Result { + let content = fs::read_to_string(path)?; + let number: i32 = content.trim().parse()?; + + if number < 0 { + return Err(ProcessError::Validation( + "Number must be positive".to_string() + )); + } + + Ok(number * 2) +} + +fn main() { + match process_file("data.txt") { + Ok(result) => println!("Result: {}", result), + Err(e) => eprintln!("Error: {}", e), + } +} +``` + +
+ +Next up: **Module 10 - Traits and Generics**, where we'll explore Rust's powerful type system for writing flexible, reusable code! diff --git a/content/docs/py2rust/module-09-error-handling.zh-cn.mdx b/content/docs/py2rust/module-09-error-handling.zh-cn.mdx new file mode 100644 index 0000000..c8cd986 --- /dev/null +++ b/content/docs/py2rust/module-09-error-handling.zh-cn.mdx @@ -0,0 +1,353 @@ +--- +title: "模块 9:错误处理" +description: "掌握 Rust 的错误处理哲学,包括 panic!、Result、? 运算符和健壮错误管理的最佳实践" +--- + +# 模块 9:错误处理 + +欢迎来到模块 9!错误处理是 Rust 最独特的功能之一。与 Python 基于异常的方法不同,Rust 使用类型安全的显式系统,在编译时捕获整类错误。 + +## 学习目标 + +完成本模块后,你将能够: +- 理解 Rust 的两种错误处理类别:可恢复和不可恢复 +- 对不可恢复错误使用 `panic!` +- 对可恢复错误使用 `Result` +- 利用 `?` 运算符进行清晰的错误传播 +- 创建自定义错误类型 +- 应用错误处理最佳实践 + +## 9.1 两种错误类别 + +### Python:所有东西都用异常 + +Python 对所有错误使用异常: + +```python +# Python - 所有东西都用异常 +try: + file = open("nonexistent.txt") +except FileNotFoundError: + print("File not found") +``` + +### Rust:显式类别 + +Rust 区分可恢复和不可恢复错误: + + +```python !! py +# Python - 语言级别没有区分 +def divide(a, b): + if b == 0: + raise ValueError("Division by zero") + return a / b +``` + +```rust !! rs +// Rust - 显式类别 +// 可恢复:Result +fn divide(a: f64, b: f64) -> Result { + if b == 0.0 { + return Err(String::from("Division by zero")); + } + Ok(a / b) +} + +// 不可恢复:panic! +fn critical_error() { + panic!("Critical system failure!"); +} +``` + + +## 9.2 使用 panic! 的不可恢复错误 + + +```python !! py +# Python - 异常的堆栈跟踪 +def bad_function(): + raise ValueError("Bad value!") +``` + +```rust !! rs +// Rust - 带堆栈跟踪的 panic +fn bad_function() { + panic!("Bad value!"); +} + +// 常见的 panic 场景 +let nums = vec![1, 2, 3]; +// let val = nums[10]; // Panic! + +// 更好的替代方案 +let val = nums.get(10); // 返回 None + +// 断言 +let x = 10; +assert_eq!(x, 10, "x should be 10"); +``` + + +## 9.3 使用 `Result` 的可恢复错误 + + +```python !! py +# Python - 手动错误处理 +def parse_number(s): + try: + return int(s) + except ValueError: + return None +``` + +```rust !! rs +// Rust - 类型安全的错误处理 +fn parse_number(s: &str) -> Result { + s.parse::() +} + +// 使用 match +match parse_number("42") { + Ok(num) => println!("Got: {}", num), + Err(e) => println!("Parse error: {}", e), +} + +// 提供默认值 +let num = parse_number("invalid").unwrap_or(0); +``` + + +## 9.4 ? 运算符深入 + +### 理解 ? + +`?` 运算符是错误传播的语法糖: + + +```rust !! rs +// Rust - ? 运算符执行以下操作: +fn operation() -> Result { + let value = risky_function()?; + + // 展开为: + // match risky_function() { + // Ok(v) => v, + // Err(e) => return Err(e), + // } + + process(value) +} +``` + + +## 9.5 自定义错误类型 + + +```rust !! rs +// Rust - 代数错误类型 +#[derive(Debug)] +enum AppError { + ValidationError(String), + NetworkError { status_code: u16, message: String }, + DatabaseError(String), +} + +impl std::fmt::Display for AppError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + AppError::ValidationError(msg) => { + write!(f, "Validation error: {}", msg) + } + AppError::NetworkError { status_code, message } => { + write!(f, "Network error ({}): {}", status_code, message) + } + AppError::DatabaseError(msg) => { + write!(f, "Database error: {}", msg) + } + } + } +} + +impl std::error::Error for AppError {} +``` + + +## 9.6 错误处理模式 + +### 模式 1:上下文错误 + + +```python !! py +# Python - 链式异常 +def load_config(): + try: + return read_file("config.toml") + except IOError as e: + raise ConfigError(f"Failed to load config: {e}") from e +``` + +```rust !! rs +// Rust - 使用 map_err 添加上下文 +fn load_config() -> Result { + std::fs::read_to_string("config.toml") + .map_err(|e| format!("Failed to load config: {}", e)) +} +``` + + +### 模式 2:重试逻辑 + + +```python !! py +# Python - 重试逻辑 +import time + +def fetch_with_retry(url, max_retries=3): + for attempt in range(max_retries): + try: + return fetch(url) + except NetworkError as e: + if attempt == max_retries - 1: + raise + time.sleep(2 ** attempt) +``` + +```rust !! rs +// Rust - 带退避的重试 +use std::time::Duration; +use std::thread; + +fn fetch_with_retry(url: &str, max_retries: u32) -> Result { + for attempt in 0..max_retries { + match fetch(url) { + Ok(data) => return Ok(data), + Err(e) if attempt < max_retries - 1 => { + let backoff = Duration::from_secs(2_u64.pow(attempt)); + thread::sleep(backoff); + } + Err(e) => return Err(e), + } + } + unreachable!() +} +``` + + +## 常见陷阱 + +### 陷阱 1:在库中使用 panic! + +```rust +// 不要:在库代码中 panic +pub fn parse_config(s: &str) -> Config { + if s.is_empty() { + panic!("Config cannot be empty"); // 错误! + } +} + +// 应该:返回 Result +pub fn parse_config(s: &str) -> Result { + if s.is_empty() { + return Err(Error::EmptyConfig); + } + // ... +} +``` + +### 陷阱 2:忽略错误 + +```rust +// 不要:忽略错误 +let _ = File::open("config.txt"); // 错误被忽略! + +// 应该:显式处理错误 +let file = File::open("config.txt")?; +``` + +## 最佳实践 + +1. **对可恢复错误使用 Result** - 将 panic! 用于真正的不可恢复情况 +2. **实现自定义错误类型** - 使用枚举进行结构化错误处理 +3. **提供上下文** - 添加关于哪个操作失败的信息 +4. **使用库** - 在库中使用 thiserror,在应用中使用 anyhow +5. **记录错误** - 记录哪些错误可以被返回 +6. **测试错误路径** - 为错误条件编写测试 + +## 总结 + +在本模块中,我们涵盖了: + +- **两种错误类别**:可恢复(Result)vs 不可恢复(panic!) +- **panic!**:用于不可恢复错误,带堆栈展开 +- **`Result`**:类型安全的错误处理 +- **? 运算符**:清晰的错误传播 +- **自定义错误**:具有 Display/Error trait 的结构化错误类型 +- **错误模式**:上下文、收集、重试逻辑 + +关键要点: +- Rust 使错误显式和类型安全 +- Result 在编译时捕获错误 +- ? 运算符提供清晰的错误传播 +- 自定义错误类型实现结构化错误处理 +- 为你的上下文选择正确的错误处理策略 + +## 练习 + +创建一个文件处理应用程序,它: +1. 读取文件(处理 IO 错误) +2. 解析内容(处理解析错误) +3. 验证数据(处理验证错误) +4. 使用自定义错误类型 +5. 提供有用的错误消息 + +
+查看解决方案 + +```rust +use std::fs; +use std::io; +use std::num::ParseIntError; + +#[derive(Debug)] +enum ProcessError { + Io(io::Error), + Parse(ParseIntError), + Validation(String), +} + +impl std::fmt::Display for ProcessError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + ProcessError::Io(e) => write!(f, "IO error: {}", e), + ProcessError::Parse(e) => write!(f, "Parse error: {}", e), + ProcessError::Validation(msg) => write!(f, "Validation error: {}", msg), + } + } +} + +impl std::error::Error for ProcessError {} + +impl From for ProcessError { + fn from(e: io::Error) -> Self { + ProcessError::Io(e) + } +} + +fn process_file(path: &str) -> Result { + let content = fs::read_to_string(path)?; + let number: i32 = content.trim().parse()?; + + if number < 0 { + return Err(ProcessError::Validation( + "Number must be positive".to_string() + )); + } + + Ok(number * 2) +} +``` + +
+ +接下来:**模块 10 - 特征和泛型**,我们将探索 Rust 强大的类型系统,用于编写灵活、可重用的代码! diff --git a/content/docs/py2rust/module-09-error-handling.zh-tw.mdx b/content/docs/py2rust/module-09-error-handling.zh-tw.mdx new file mode 100644 index 0000000..239dc49 --- /dev/null +++ b/content/docs/py2rust/module-09-error-handling.zh-tw.mdx @@ -0,0 +1,315 @@ +--- +title: "模組 9:錯誤處理" +description: "掌握 Rust 的錯誤處理哲學,包括 panic!、Result、? 運算符和健壯錯誤管理的最佳實踐" +--- + +# 模組 9:錯誤處理 + +歡迎來到模組 9!錯誤處理是 Rust 最獨特的功能之一。與 Python 基於異常的方法不同,Rust 使用型別安全的顯式系統,在編譯時捕獲整類錯誤。 + +## 學習目標 + +完成本模組後,你將能夠: +- 理解 Rust 的兩種錯誤處理類別:可恢復和不可恢復 +- 對不可恢復錯誤使用 `panic!` +- 對可恢復錯誤使用 `Result` +- 利用 `?` 運算符進行清晰的錯誤傳播 +- 建立自訂錯誤型別 +- 應用錯誤處理最佳實踐 + +## 9.1 兩種錯誤類別 + +### Python:所有東西都用異常 + +Python 對所有錯誤使用異常: + +```python +# Python - 所有東西都用異常 +try: + file = open("nonexistent.txt") +except FileNotFoundError: + print("File not found") +``` + +### Rust:顯式類別 + +Rust 區分可恢復和不可恢復錯誤: + + +```python !! py +# Python - 語言層級沒有區分 +def divide(a, b): + if b == 0: + raise ValueError("Division by zero") + return a / b +``` + +```rust !! rs +// Rust - 顯式類別 +// 可恢復:Result +fn divide(a: f64, b: f64) -> Result { + if b == 0.0 { + return Err(String::from("Division by zero")); + } + Ok(a / b) +} + +// 不可恢復:panic! +fn critical_error() { + panic!("Critical system failure!"); +} +``` + + +## 9.2 使用 panic! 的不可恢復錯誤 + + +```rust !! rs +// Rust - 帶堆疊追蹤的 panic +fn bad_function() { + panic!("Bad value!"); +} + +// 常見的 panic 場景 +let nums = vec![1, 2, 3]; +// let val = nums[10]; // Panic! + +// 更好的替代方案 +let val = nums.get(10); // 返回 None + +// 斷言 +let x = 10; +assert_eq!(x, 10, "x should be 10"); +``` + + +## 9.3 使用 `Result` 的可恢復錯誤 + + +```python !! py +# Python - 手動錯誤處理 +def parse_number(s): + try: + return int(s) + except ValueError: + return None +``` + +```rust !! rs +// Rust - 型別安全的錯誤處理 +fn parse_number(s: &str) -> Result { + s.parse::() +} + +// 使用 match +match parse_number("42") { + Ok(num) => println!("Got: {}", num), + Err(e) => println!("Parse error: {}", e), +} + +// 提供預設值 +let num = parse_number("invalid").unwrap_or(0); +``` + + +## 9.4 ? 運算符深入 + +### 理解 ? + +`?` 運算符是錯誤傳播的語法糖: + + +```rust !! rs +// Rust - ? 運算符執行以下操作: +fn operation() -> Result { + let value = risky_function()?; + + // 展開為: + // match risky_function() { + // Ok(v) => v, + // Err(e) => return Err(e), + // } + + process(value) +} +``` + + +## 9.5 自訂錯誤型別 + + +```rust !! rs +// Rust - 代數錯誤型別 +#[derive(Debug)] +enum AppError { + ValidationError(String), + NetworkError { status_code: u16, message: String }, + DatabaseError(String), +} + +impl std::fmt::Display for AppError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + AppError::ValidationError(msg) => { + write!(f, "Validation error: {}", msg) + } + AppError::NetworkError { status_code, message } => { + write!(f, "Network error ({}): {}", status_code, message) + } + AppError::DatabaseError(msg) => { + write!(f, "Database error: {}", msg) + } + } + } +} + +impl std::error::Error for AppError {} +``` + + +## 9.6 錯誤處理模式 + +### 模式 1:上下文錯誤 + + +```rust !! rs +// Rust - 使用 map_err 添加上下文 +fn load_config() -> Result { + std::fs::read_to_string("config.toml") + .map_err(|e| format!("Failed to load config: {}", e)) +} +``` + + +### 模式 2:重試邏輯 + + +```rust !! rs +// Rust - 帶退避的重試 +use std::time::Duration; +use std::thread; + +fn fetch_with_retry(url: &str, max_retries: u32) -> Result { + for attempt in 0..max_retries { + match fetch(url) { + Ok(data) => return Ok(data), + Err(e) if attempt < max_retries - 1 => { + let backoff = Duration::from_secs(2_u64.pow(attempt)); + thread::sleep(backoff); + } + Err(e) => return Err(e), + } + } + unreachable!() +} +``` + + +## 常見陷阱 + +### 陷阱 1:在庫中使用 panic! + +```rust +// 不要:在庫程式碼中 panic +pub fn parse_config(s: &str) -> Config { + if s.is_empty() { + panic!("Config cannot be empty"); // 錯誤! + } +} + +// 應該:返回 Result +pub fn parse_config(s: &str) -> Result { + if s.is_empty() { + return Err(Error::EmptyConfig); + } +} +``` + +### 陷阱 2:忽略錯誤 + +```rust +// 不要:忽略錯誤 +let _ = File::open("config.txt"); // 錯誤被忽略! + +// 應該:顯式處理錯誤 +let file = File::open("config.txt")?; +``` + +## 最佳實踐 + +1. **對可恢復錯誤使用 Result** - 將 panic! 用於真正的不可恢復情況 +2. **實現自訂錯誤型別** - 使用枚舉進行結構化錯誤處理 +3. **提供上下文** - 添加關於哪個操作失敗的資訊 +4. **使用庫** - 在庫中使用 thiserror,在應用中使用 anyhow +5. **記錄錯誤** - 記錄哪些錯誤可以被返回 +6. **測試錯誤路徑** - 為錯誤條件編寫測試 + +## 總結 + +在本模組中,我們涵蓋了: + +- **兩種錯誤類別**:可恢復(Result)vs 不可恢復(panic!) +- **panic!**:用於不可恢復錯誤,帶堆疊展開 +- **`Result`**:型別安全的錯誤處理 +- **? 運算符**:清晰的錯誤傳播 +- **自訂錯誤**:具有 Display/Error trait 的結構化錯誤型別 +- **錯誤模式**:上下文、收集、重試邏輯 + +關鍵要點: +- Rust 使錯誤顯式和型別安全 +- Result 在編譯時捕獲錯誤 +- ? 運算符提供清晰的錯誤傳播 +- 自訂錯誤型別實現結構化錯誤處理 +- 為你的上下文選擇正確的錯誤處理策略 + +## 練習 + +創建一個文件處理應用程式,它: +1. 讀取文件(處理 IO 錯誤) +2. 解析內容(處理解析錯誤) +3. 驗證資料(處理驗證錯誤) +4. 使用自訂錯誤型別 +5. 提供有用的錯誤訊息 + +
+檢視解決方案 + +```rust +use std::fs; +use std::io; +use std::num::ParseIntError; + +#[derive(Debug)] +enum ProcessError { + Io(io::Error), + Parse(ParseIntError), + Validation(String), +} + +impl std::fmt::Display for ProcessError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + ProcessError::Io(e) => write!(f, "IO error: {}", e), + ProcessError::Parse(e) => write!(f, "Parse error: {}", e), + ProcessError::Validation(msg) => write!(f, "Validation error: {}", msg), + } + } +} + +fn process_file(path: &str) -> Result { + let content = fs::read_to_string(path)?; + let number: i32 = content.trim().parse()?; + + if number < 0 { + return Err(ProcessError::Validation( + "Number must be positive".to_string() + )); + } + + Ok(number * 2) +} +``` + +
+ +接下來:**模組 10 - 特徵和泛型**,我們將探索 Rust 強大的型別系統,用於編寫靈活、可重用的程式碼! diff --git a/content/docs/py2rust/module-10-traits-generics.mdx b/content/docs/py2rust/module-10-traits-generics.mdx new file mode 100644 index 0000000..7d559c9 --- /dev/null +++ b/content/docs/py2rust/module-10-traits-generics.mdx @@ -0,0 +1,781 @@ +--- +title: "Module 10: Traits and Generics" +description: "Master Rust's trait system and generics for writing flexible, reusable, and type-safe code" +--- + +# Module 10: Traits and Generics + +Welcome to Module 10! This is where Rust's type system truly shines. Traits and generics enable you to write flexible, reusable code without sacrificing type safety or performance. + +## Learning Objectives + +By the end of this module, you'll be able to: +- Define and implement traits for shared behavior +- Use generics for type-agnostic code +- Understand trait bounds and constraints +- Leverage standard library traits +- Use trait objects for dynamic dispatch +- Apply advanced trait patterns + +## 10.1 Traits: Shared Behavior + +### Python: Duck Typing + +Python uses duck typing ("if it walks like a duck..."): + +```python +# Python - Duck typing +def describe(obj): + if hasattr(obj, 'name'): + return obj.name + return str(obj) + +class Dog: + def __init__(self, name): + self.name = name + +class Person: + def __init__(self, name): + self.name = name + +describe(Dog("Fido")) # "Fido" +describe(Person("Alice")) # "Alice" +``` + +### Rust: Traits for Shared Behavior + + +```python !! py +# Python - Informal interfaces +class Dog: + def speak(self): + return "Woof!" + +class Cat: + def speak(self): + return "Meow!" + +def make_sound(animal): + return animal.speak() +``` + +```rust !! rs +// Rust - Formal trait definitions +trait Speak { + fn speak(&self) -> String; +} + +struct Dog; +impl Speak for Dog { + fn speak(&self) -> String { + String::from("Woof!") + } +} + +struct Cat; +impl Speak for Cat { + fn speak(&self) -> String { + String::from("Meow!") + } +} + +fn make_sound(animal: &impl Speak) -> String { + animal.speak() +} + +// Usage +println!("{}", make_sound(&Dog)); // Woof! +println!("{}", make_sound(&Cat)); // Meow! +``` + + +### Trait as Parameters + + +```rust !! rs +// Three ways to write trait bounds + +// 1. impl Trait syntax (simple, concise) +fn print_speak(speaker: &impl Speak) { + println!("{}", speaker.speak()); +} + +// 2. Trait bound syntax (more explicit) +fn print_speak(speaker: &T) { + println!("{}", speaker.speak()); +} + +// 3. where clause (for complex bounds) +fn print_speak(speaker: &T) +where + T: Speak, +{ + println!("{}", speaker.speak()); +} + +// Multiple trait bounds +fn compare_speakers(a: &T, b: &T) -> bool +where + T: Speak + PartialEq, +{ + a.speak() == b.speak() +} +``` + + +## 10.2 Standard Library Traits + +Rust's standard library provides many useful traits: + + +```python !! py +# Python - Special methods +class Point: + def __init__(self, x, y): + self.x = x + self.y = y + + def __str__(self): + return f"Point({self.x}, {self.y})" + + def __repr__(self): + return f"Point(x={self.x}, y={self.y})" + + def __eq__(self, other): + return self.x == other.x and self.y == other.y +``` + +```rust !! rs +// Rust - Derive and implement traits +#[derive(Debug, PartialEq)] // Auto-implement these traits +struct Point { + x: i32, + y: i32, +} + +// Custom Display implementation +impl std::fmt::Display for Point { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "Point({}, {})", self.x, self.y) + } +} + +// Clone trait +impl Clone for Point { + fn clone(&self) -> Self { + Point { x: self.x, y: self.y } + } +} + +// Copy trait (requires Clone) +impl Copy for Point {} + +// Usage +let p1 = Point { x: 1, y: 2 }; +let p2 = p1; // Copy, not move! +println!("{}", p1); // Still valid +println!("{:?}", p1); // Debug output +``` + + +### Iterator Trait + + +```python !! py +# Python - Iterator protocol +class Counter: + def __init__(self, max): + self.max = max + self.current = 0 + + def __iter__(self): + return self + + def __next__(self): + if self.current < self.max: + value = self.current + self.current += 1 + return value + raise StopIteration + +for i in Counter(5): + print(i) +``` + +```rust !! rs +// Rust - Iterator trait +struct Counter { + max: u32, + current: u32, +} + +impl Counter { + fn new(max: u32) -> Counter { + Counter { max, current: 0 } + } +} + +impl Iterator for Counter { + type Item = u32; + + fn next(&mut self) -> Option { + if self.current < self.max { + let value = self.current; + self.current += 1; + Some(value) + } else { + None + } + } +} + +// Usage +for i in Counter::new(5) { + println!("{}", i); +} + +// Iterator methods available automatically +let sum: u32 = Counter::new(5).sum(); +println!("Sum: {}", sum); // 10 +``` + + +## 10.3 Generics: Type-Agnostic Code + +### Python: Dynamic Typing + +Python handles different types dynamically: + +```python +# Python - Works with any type +def first(items): + return items[0] if items else None + +first([1, 2, 3]) # 1 +first(["a", "b"]) # "a" +``` + +### Rust: Generic Functions + + +```python !! py +# Python - Dynamic typing +def get_first(items): + if not items: + return None + return items[0] +``` + +```rust !! rs +// Rust - Generic function +fn get_first(items: &[T]) -> Option<&T> { + if items.is_empty() { + None + } else { + Some(&items[0]) + } +} + +// Usage with different types +let numbers = vec![1, 2, 3]; +let strings = vec!["a", "b", "c"]; + +println!("{:?}", get_first(&numbers)); // Some(1) +println!("{:?}", get_first(&strings)); // Some("a") + +// Multiple generic parameters +fn pair(a: A, b: B) -> (A, B) { + (a, b) +} +``` + + +### Generic Structs + + +```python !! py +# Python - Works with any type +class Container: + def __init__(self, value): + self.value = value + +container1 = Container(42) +container2 = Container("hello") +``` + +```rust !! rs +// Rust - Generic structs +struct Container { + value: T, +} + +impl Container { + fn new(value: T) -> Self { + Container { value } + } + + fn get(&self) -> &T { + &self.value + } +} + +// Usage with different types +let container1: Container = Container::new(42); +let container2: Container<&str> = Container::new("hello"); + +// Multiple type parameters +struct Pair { + first: A, + second: B, +} + +impl Pair { + fn new(first: A, second: B) -> Self { + Pair { first, second } + } +} +``` + + +## 10.4 Trait Bounds and Constraints + +### Basic Trait Bounds + + +```python !! py +# Python - Duck typing (runtime checks) +def process(items): + # Assumes items supports iteration and len() + for item in items: + print(item) + return len(items) +``` + +```rust !! rs +// Rust - Trait bounds (compile-time checks) +use std::fmt::Debug; + +fn process(items: &[T]) -> usize { + for item in items { + println!("{}", item); // Requires Display + } + items.len() +} + +// Multiple bounds +fn clone_and_compare(item: &T) -> T +where + T: Clone + PartialEq, +{ + let cloned = item.clone(); + cloned +} + +// Bounded by lifetime +fn longest<'a, T>(x: &'a T, y: &'a T) -> &'a T +where + T: PartialOrd, +{ + if x > y { x } else { y } +} +``` + + +### Conditional Implementation + + +```rust !! rs +// Implement methods only when trait bounds are met +struct Wrapper(T); + +impl Wrapper { + fn new(value: T) -> Self { + Wrapper(value) + } +} + +// Only implement display for types that implement Display +impl Wrapper { + fn display(&self) { + println!("{}", self.0); + } +} + +// Only implement comparison for types that implement PartialEq +impl Wrapper { + fn equals(&self, other: &Self) -> bool { + self.0 == other.0 + } +} + +// Usage +let w1 = Wrapper::new(42); +w1.display(); // Works: i32 implements Display + +let w2 = Wrapper::new(vec![1, 2, 3]); +// w2.display(); // Error: Vec doesn't implement Display +``` + + +## 10.5 Trait Objects: Dynamic Dispatch + +### Static vs Dynamic Dispatch + + +```python !! py +# Python - Always dynamic dispatch +def process(obj): + obj.speak() # Resolved at runtime +``` + +```rust !! rs +// Rust - Static dispatch (default, faster) +fn process_static(speaker: T) { + speaker.speak(); // Resolved at compile time +} + +// Dynamic dispatch (trait objects) +fn process_dynamic(speaker: &dyn Speak) { + speaker.speak(); // Resolved at runtime via vtable +} + +// When to use which +// Static dispatch: When you know the type at compile time (faster) +// Dynamic dispatch: When you need a collection of different types +``` + + +### Trait Objects in Practice + + +```python !! py +# Python - Heterogeneous collections +animals = [Dog("Fido"), Cat("Whiskers")] +for animal in animals: + print(animal.speak()) +``` + +```rust !! rs +// Rust - Trait objects for heterogeneous collections +trait Speak { + fn speak(&self) -> String; +} + +struct Dog { name: String } +impl Speak for Dog { + fn speak(&self) -> String { + format!("{} says Woof!", self.name) + } +} + +struct Cat { name: String } +impl Speak for Cat { + fn speak(&self) -> String { + format!("{} says Meow!", self.name) + } +} + +// Collection of trait objects +fn demo_trait_objects() { + let dog = Dog { name: String::from("Fido") }; + let cat = Cat { name: String::from("Whiskers") }; + + // Must use references (&dyn Speak) or Box + let animals: Vec> = vec![ + Box::new(dog), + Box::new(cat), + ]; + + for animal in animals.iter() { + println!("{}", animal.speak()); + } +} +``` + + +### Object Safety + + +```rust !! rs +// Not all traits can be trait objects (must be "object safe") +trait NotObjectSafe { + // Generic methods not allowed + fn generic_method(&self, item: T); + + // Self return type not allowed + fn clone_self(&self) -> Self; +} + +// Object-safe trait +trait ObjectSafe { + // No generic methods + fn speak(&self) -> String; + + // No Self return + fn get_name(&self) -> &str; +} + +// This works +fn use_trait_object(obj: &dyn ObjectSafe) { + println!("{}", obj.speak()); +} +``` + + +## 10.6 Advanced Trait Patterns + +### Associated Types + + +```python !! py +# Python - Associated type via convention +class Iterator: + # Type of items is implicit + def next(self): + pass +``` + +```rust !! rs +// Rust - Associated types (more precise than generics) +trait Iterator { + type Item; // Associated type + + fn next(&mut self) -> Option; +} + +struct Counter; +impl Iterator for Counter { + type Item = i32; // Specify the associated type + + fn next(&mut self) -> Option { + Some(42) + } +} + +// Why associated types instead of generics? +// trait Iterator { ... } // Would be overly flexible +// Associated types make the relationship clearer +``` + + +### Default Implementations + + +```rust !! rs +// Traits can provide default implementations +trait Animal { + fn speak(&self) -> String { + String::from("Unknown sound") + } + + fn intro(&self) -> String { + format!("I say: {}", self.speak()) + } +} + +struct Dog; +impl Animal for Dog { + fn speak(&self) -> String { + String::from("Woof!") + } + // intro() uses default implementation +} + +struct Cat; +impl Animal for Cat { + fn speak(&self) -> String { + String::from("Meow!") + } + + fn intro(&self) -> String { + format!("The cat says: {}", self.speak()) // Override default + } +} + +// Usage +let dog = Dog; +println!("{}", dog.intro()); // I say: Woof! + +let cat = Cat; +println!("{}", cat.intro()); // The cat says: Meow! +``` + + +### Supertraits + + +```python !! py +# Python - Inheritance +class Animal: + def name(self): + pass + +class Pet(Animal): + def cuddle(self): + pass +``` + +```rust !! rs +// Rust - Supertraits (trait inheritance) +trait Animal { + fn name(&self) -> &str; +} + +trait Pet: Animal { // Pet requires Animal + fn cuddle(&self) { + println!("{} is being cuddled", self.name()); + } +} + +struct Dog { name: String } + +impl Animal for Dog { + fn name(&self) -> &str { + &self.name + } +} + +impl Pet for Dog { + // cuddle() uses default, can also override +} + +// Usage +let dog = Dog { name: String::from("Buddy") }; +dog.cuddle(); // Has access to both traits +``` + + +## Common Pitfalls + +### Pitfall 1: Orphan Rule + +```rust +// DON'T: Implement external trait on external type +// impl Display for Vec { } // Error! + +// DO: Use newtype pattern +struct MyVec(Vec); + +impl std::fmt::Display for MyVec { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{:?}", self.0) + } +} +``` + +### Pitfall 2: Overusing Trait Objects + +```rust +// DON'T: Use trait objects when static dispatch works +fn process(items: &[&dyn Speak]) { } // Slower + +// DO: Use generics when possible +fn process(items: &[T]) { } // Faster +``` + +### Pitfall 3: Complex Trait Bounds + +```rust +// DON'T: Too many bounds (hard to read) +fn process(x: T) { } + +// DO: Use where clause for clarity +fn process(x: T) +where + T: Clone + Display + Debug + PartialEq, +{ +} +``` + +## Best Practices + +1. **Prefer static dispatch** - Use generics over trait objects when possible +2. **Keep traits focused** - Small, cohesive traits are better than large ones +3. **Provide default implementations** - Make traits easier to implement +4. **Use standard library traits** - Don't reinvent Display, Debug, Clone, etc. +5. **Consider object safety** - Decide if trait should be usable as trait object +6. **Document trait contracts** - Clearly document what implementers must provide + +## Summary + +In this module, we covered: + +- **Traits**: Shared behavior with formal contracts +- **Generics**: Type-agnostic code with type safety +- **Trait Bounds**: Compile-time constraints on generic types +- **Standard Library Traits**: Display, Debug, Clone, Iterator, etc. +- **Trait Objects**: Dynamic dispatch for runtime polymorphism +- **Advanced Patterns**: Associated types, default implementations, supertraits + +Key takeaways: +- Traits provide shared behavior with zero-cost abstraction +- Generics enable flexible, type-safe code +- Static dispatch is preferred, trait objects when needed +- The standard library provides many useful traits +- Trait bounds enable powerful compile-time polymorphism +- Rust's type system enables both flexibility and performance + +## Exercise + +Create a generic data processing library that: +1. Defines a `Processable` trait with `process` method +2. Implements it for multiple types +3. Uses generics to process collections +4. Demonstrates both static and dynamic dispatch +5. Uses trait bounds to constrain functionality + +
+View Solution + +```rust +use std::fmt::Display; + +trait Processable { + type Output; + fn process(&self) -> Self::Output; +} + +#[derive(Debug)] +struct Number(i32); + +impl Processable for Number { + type Output = String; + + fn process(&self) -> Self::Output { + format!("Doubled: {}", self.0 * 2) + } +} + +#[derive(Debug)] +struct Text(String); + +impl Processable for Text { + type Output = usize; + + fn process(&self) -> Self::Output { + self.0.len() + } +} + +// Generic function using trait +fn process_item(item: &T) -> String { + format!("{:?}", item.process()) +} + +// Trait object version +fn process_dynamic(item: &dyn Processable) -> String { + item.process() +} + +fn main() { + let num = Number(42); + let text = Text("Hello"); + + println!("{}", process_item(&num)); // "Doubled: 84" + println!("{}", process_item(&text)); // "5" +} +``` + +
+ +Congratulations on completing Module 10! You now have a solid understanding of Rust's trait and generics system. These features enable you to write flexible, reusable, and type-safe code that's also performant. Continue practicing with real-world projects to master these concepts! diff --git a/content/docs/py2rust/module-10-traits-generics.zh-cn.mdx b/content/docs/py2rust/module-10-traits-generics.zh-cn.mdx new file mode 100644 index 0000000..e7cd745 --- /dev/null +++ b/content/docs/py2rust/module-10-traits-generics.zh-cn.mdx @@ -0,0 +1,572 @@ +--- +title: "模块 10:特征和泛型" +description: "掌握 Rust 的特征系统和泛型,用于编写灵活、可重用和类型安全的代码" +--- + +# 模块 10:特征和泛型 + +欢迎来到模块 10!这是 Rust 类型系统真正闪耀的地方。特征和泛型使你能够编写灵活、可重用的代码,而不牺牲类型安全或性能。 + +## 学习目标 + +完成本模块后,你将能够: +- 定义和实现特征以共享行为 +- 使用泛型编写类型无关代码 +- 理解特征边界和约束 +- 利用标准库特征 +- 使用特征对象进行动态分发 +- 应用高级特征模式 + +## 10.1 特征:共享行为 + +### Python:鸭子类型 + +Python 使用鸭子类型("如果它走路像鸭子..."): + +```python +# Python - 鸭子类型 +class Dog: + def speak(self): + return "Woof!" + +class Cat: + def speak(self): + return "Meow!" + +def make_sound(animal): + return animal.speak() +``` + +### Rust:用于共享行为的特征 + + +```python !! py +# Python - 非正式接口 +class Dog: + def speak(self): + return "Woof!" + +class Cat: + def speak(self): + return "Meow!" +``` + +```rust !! rs +// Rust - 形式化特征定义 +trait Speak { + fn speak(&self) -> String; +} + +struct Dog; +impl Speak for Dog { + fn speak(&self) -> String { + String::from("Woof!") + } +} + +struct Cat; +impl Speak for Cat { + fn speak(&self) -> String { + String::from("Meow!") + } +} + +fn make_sound(animal: &impl Speak) -> String { + animal.speak() +} + +// 使用 +println!("{}", make_sound(&Dog)); // Woof! +println!("{}", make_sound(&Cat)); // Meow! +``` + + +### 特征作为参数 + + +```rust !! rs +// 编写特征边界的三种方式 + +// 1. impl Trait 语法(简单,简洁) +fn print_speak(speaker: &impl Speak) { + println!("{}", speaker.speak()); +} + +// 2. 特征边界语法(更明确) +fn print_speak(speaker: &T) { + println!("{}", speaker.speak()); +} + +// 3. where 子句(用于复杂边界) +fn print_speak(speaker: &T) +where + T: Speak, +{ + println!("{}", speaker.speak()); +} + +// 多个特征边界 +fn compare_speakers(a: &T, b: &T) -> bool +where + T: Speak + PartialEq, +{ + a.speak() == b.speak() +} +``` + + +## 10.2 标准库特征 + +Rust 的标准库提供了许多有用的特征: + + +```python !! py +# Python - 特殊方法 +class Point: + def __init__(self, x, y): + self.x = x + self.y = y + + def __str__(self): + return f"Point({self.x}, {self.y})" +``` + +```rust !! rs +// Rust - 派生和实现特征 +#[derive(Debug, PartialEq)] // 自动实现这些特征 +struct Point { + x: i32, + y: i32, +} + +// 自定义 Display 实现 +impl std::fmt::Display for Point { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "Point({}, {})", self.x, self.y) + } +} + +// 使用 +let p1 = Point { x: 1, y: 2 }; +let p2 = p1; // 拷贝,不是移动! +println!("{}", p1); // 仍然有效 +println!("{:?}", p1); // Debug 输出 +``` + + +### Iterator 特征 + + +```python !! py +# Python - 迭代器协议 +class Counter: + def __init__(self, max): + self.max = max + self.current = 0 + + def __iter__(self): + return self + + def __next__(self): + if self.current < self.max: + value = self.current + self.current += 1 + return value + raise StopIteration +``` + +```rust !! rs +// Rust - Iterator 特征 +struct Counter { + max: u32, + current: u32, +} + +impl Iterator for Counter { + type Item = u32; + + fn next(&mut self) -> Option { + if self.current < self.max { + let value = self.current; + self.current += 1; + Some(value) + } else { + None + } + } +} + +// 迭代器方法自动可用 +let sum: u32 = Counter::new(5).sum(); +println!("Sum: {}", sum); // 10 +``` + + +## 10.3 泛型:类型无关代码 + +### Python:动态类型 + +Python 动态处理不同类型: + +```python +# Python - 适用于任何类型 +def first(items): + return items[0] if items else None +``` + +### Rust:泛型函数 + + +```python !! py +# Python - 动态类型 +def get_first(items): + if not items: + return None + return items[0] +``` + +```rust !! rs +// Rust - 泛型函数 +fn get_first(items: &[T]) -> Option<&T> { + if items.is_empty() { + None + } else { + Some(&items[0]) + } +} + +// 使用不同类型 +let numbers = vec![1, 2, 3]; +let strings = vec!["a", "b", "c"]; + +println!("{:?}", get_first(&numbers)); // Some(1) +println!("{:?}", get_first(&strings)); // Some("a") + +// 多个泛型参数 +fn pair(a: A, b: B) -> (A, B) { + (a, b) +} +``` + + +### 泛型结构体 + + +```python !! py +# Python - 适用于任何类型 +class Container: + def __init__(self, value): + self.value = value +``` + +```rust !! rs +// Rust - 泛型结构体 +struct Container { + value: T, +} + +impl Container { + fn new(value: T) -> Self { + Container { value } + } + + fn get(&self) -> &T { + &self.value + } +} + +// 使用不同类型 +let container1: Container = Container::new(42); +let container2: Container<&str> = Container::new("hello"); +``` + + +## 10.4 特征边界和约束 + +### 基本特征边界 + + +```python !! py +# Python - 鸭子类型(运行时检查) +def process(items): + for item in items: + print(item) + return len(items) +``` + +```rust !! rs +// Rust - 特征边界(编译时检查) +fn process(items: &[T]) -> usize { + for item in items { + println!("{}", item); // 需要 Display + } + items.len() +} + +// 多个边界 +fn clone_and_compare(item: &T) -> T +where + T: Clone + PartialEq, +{ + let cloned = item.clone(); + cloned +} +``` + + +## 10.5 特征对象:动态分发 + +### 静态 vs 动态分发 + + +```python !! py +# Python - 始终动态分发 +def process(obj): + obj.speak() # 运行时解析 +``` + +```rust !! rs +// Rust - 静态分发(默认,更快) +fn process_static(speaker: T) { + speaker.speak(); // 编译时解析 +} + +// 动态分发(特征对象) +fn process_dynamic(speaker: &dyn Speak) { + speaker.speak(); // 运行时通过 vtable 解析 +} + +// 何时使用哪个 +// 静态分发:编译时知道类型时(更快) +// 动态分发:需要不同类型的集合时 +``` + + +### 特征对象实践 + + +```python !! py +# Python - 异构集合 +animals = [Dog("Fido"), Cat("Whiskers")] +for animal in animals: + print(animal.speak()) +``` + +```rust !! rs +// Rust - 异构集合的特征对象 +trait Speak { + fn speak(&self) -> String; +} + +struct Dog { name: String } +impl Speak for Dog { + fn speak(&self) -> String { + format!("{} says Woof!", self.name) + } +} + +struct Cat { name: String } +impl Speak for Cat { + fn speak(&self) -> String { + format!("{} says Meow!", self.name) + } +} + +// 特征对象集合 +let dog = Dog { name: String::from("Fido") }; +let cat = Cat { name: String::from("Whiskers") }; + +// 必须使用引用 (&dyn Speak) 或 Box +let animals: Vec> = vec![ + Box::new(dog), + Box::new(cat), +]; + +for animal in animals.iter() { + println!("{}", animal.speak()); +} +``` + + +## 10.6 高级特征模式 + +### 关联类型 + + +```rust !! rs +// Rust - 关联类型(比泛型更精确) +trait Iterator { + type Item; // 关联类型 + + fn next(&mut self) -> Option; +} + +struct Counter; +impl Iterator for Counter { + type Item = i32; // 指定关联类型 + + fn next(&mut self) -> Option { + Some(42) + } +} +``` + + +### 默认实现 + + +```rust !! rs +// 特征可以提供默认实现 +trait Animal { + fn speak(&self) -> String { + String::from("Unknown sound") + } + + fn intro(&self) -> String { + format!("I say: {}", self.speak()) + } +} + +struct Dog; +impl Animal for Dog { + fn speak(&self) -> String { + String::from("Woof!") + } + // intro() 使用默认实现 +} + +// 使用 +let dog = Dog; +println!("{}", dog.intro()); // I say: Woof! +``` + + +### 超特征 + + +```rust !! rs +// Rust - 超特征(特征继承) +trait Animal { + fn name(&self) -> &str; +} + +trait Pet: Animal { // Pet 要求 Animal + fn cuddle(&self) { + println!("{} is being cuddled", self.name()); + } +} + +struct Dog { name: String } + +impl Animal for Dog { + fn name(&self) -> &str { + &self.name + } +} + +impl Pet for Dog { + // cuddle() 使用默认,也可以覆盖 +} +``` + + +## 常见陷阱 + +### 陷阱 1:孤儿规则 + +```rust +// 不要:在外部类型上实现外部特征 +// impl Display for Vec { } // 错误! + +// 应该:使用 newtype 模式 +struct MyVec(Vec); + +impl std::fmt::Display for MyVec { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{:?}", self.0) + } +} +``` + +### 陷阱 2:过度使用特征对象 + +```rust +// 不要:静态分发可行时使用特征对象 +fn process(items: &[&dyn Speak]) { } // 更慢 + +// 应该:尽可能使用泛型 +fn process(items: &[T]) { } // 更快 +``` + +## 最佳实践 + +1. **优先使用静态分发** - 尽可能使用泛型而不是特征对象 +2. **保持特征聚焦** - 小型、内聚的特征比大型特征更好 +3. **提供默认实现** - 使特征更容易实现 +4. **使用标准库特征** - 不要重新发明 Display、Debug、Clone 等 +5. **考虑对象安全** - 决定特征是否应该可用作特征对象 +6. **记录特征契约** - 清楚地记录实现者必须提供什么 + +## 总结 + +在本模块中,我们涵盖了: + +- **特征**:具有形式化契约的共享行为 +- **泛型**:类型安全的类型无关代码 +- **特征边界**:泛型类型的编译时约束 +- **标准库特征**:Display、Debug、Clone、Iterator 等 +- **特征对象**:运行时多态的动态分发 +- **高级模式**:关联类型、默认实现、超特征 + +关键要点: +- 特征提供零成本抽象的共享行为 +- 泛型实现灵活、类型安全的代码 +- 优先静态分发,需要时使用特征对象 +- 标准库提供许多有用的特征 +- 特征边界实现强大的编译时多态 +- Rust 的类型系统同时实现灵活性和性能 + +## 练习 + +创建一个通用数据处理库: +1. 定义带有 `process` 方法的 `Processable` 特征 +2. 为多个类型实现它 +3. 使用泛型处理集合 +4. 演示静态和动态分发 +5. 使用特征边界约束功能 + +
+查看解决方案 + +```rust +use std::fmt::Display; + +trait Processable { + type Output; + fn process(&self) -> Self::Output; +} + +#[derive(Debug)] +struct Number(i32); + +impl Processable for Number { + type Output = String; + + fn process(&self) -> Self::Output { + format!("Doubled: {}", self.0 * 2) + } +} + +fn process_item(item: &T) -> String { + format!("{:?}", item.process()) +} + +fn main() { + let num = Number(42); + println!("{}", process_item(&num)); // "Doubled: 84" +} +``` + +
+ +恭喜完成模块 10!你现在对 Rust 的特征和泛型系统有了扎实的理解。这些功能使你能够编写灵活、可重用和高性能的类型安全代码。继续通过实际项目练习以掌握这些概念! diff --git a/content/docs/py2rust/module-10-traits-generics.zh-tw.mdx b/content/docs/py2rust/module-10-traits-generics.zh-tw.mdx new file mode 100644 index 0000000..c4477d1 --- /dev/null +++ b/content/docs/py2rust/module-10-traits-generics.zh-tw.mdx @@ -0,0 +1,572 @@ +--- +title: "模組 10:特徵和泛型" +description: "掌握 Rust 的特徵系統和泛型,用於編寫靈活、可重用和型別安全的程式碼" +--- + +# 模組 10:特徵和泛型 + +歡迎來到模組 10!這是 Rust 型別系統真正閃耀的地方。特徵和泛型使你能夠編寫靈活、可重用的程式碼,而不犧牲型別安全或效能。 + +## 學習目標 + +完成本模組後,你將能夠: +- 定義和實現特徵以共享行為 +- 使用泛型編寫型別無關程式碼 +- 理解特徵邊界和約束 +- 利用標準庫特徵 +- 使用特徵物件進行動態分發 +- 應用高級特徵模式 + +## 10.1 特徵:共享行為 + +### Python:鴨子型別 + +Python 使用鴨子型別("如果它走路像鴨子..."): + +```python +# Python - 鴨子型別 +class Dog: + def speak(self): + return "Woof!" + +class Cat: + def speak(self): + return "Meow!" + +def make_sound(animal): + return animal.speak() +``` + +### Rust:用於共享行為的特徵 + + +```python !! py +# Python - 非正式介面 +class Dog: + def speak(self): + return "Woof!" + +class Cat: + def speak(self): + return "Meow!" +``` + +```rust !! rs +// Rust - 形式化特徵定義 +trait Speak { + fn speak(&self) -> String; +} + +struct Dog; +impl Speak for Dog { + fn speak(&self) -> String { + String::from("Woof!") + } +} + +struct Cat; +impl Speak for Cat { + fn speak(&self) -> String { + String::from("Meow!") + } +} + +fn make_sound(animal: &impl Speak) -> String { + animal.speak() +} + +// 使用 +println!("{}", make_sound(&Dog)); // Woof! +println!("{}", make_sound(&Cat)); // Meow! +``` + + +### 特徵作為參數 + + +```rust !! rs +// 編寫特徵邊界的三種方式 + +// 1. impl Trait 語法(簡單,簡潔) +fn print_speak(speaker: &impl Speak) { + println!("{}", speaker.speak()); +} + +// 2. 特徵邊界語法(更明確) +fn print_speak(speaker: &T) { + println!("{}", speaker.speak()); +} + +// 3. where 子句(用於複雜邊界) +fn print_speak(speaker: &T) +where + T: Speak, +{ + println!("{}", speaker.speak()); +} + +// 多個特徵邊界 +fn compare_speakers(a: &T, b: &T) -> bool +where + T: Speak + PartialEq, +{ + a.speak() == b.speak() +} +``` + + +## 10.2 標準庫特徵 + +Rust 的標準庫提供了許多有用的特徵: + + +```python !! py +# Python - 特殊方法 +class Point: + def __init__(self, x, y): + self.x = x + self.y = y + + def __str__(self): + return f"Point({self.x}, {self.y})" +``` + +```rust !! rs +// Rust - 派生和實現特徵 +#[derive(Debug, PartialEq)] // 自動實現這些特徵 +struct Point { + x: i32, + y: i32, +} + +// 自定義 Display 實現 +impl std::fmt::Display for Point { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "Point({}, {})", self.x, self.y) + } +} + +// 使用 +let p1 = Point { x: 1, y: 2 }; +let p2 = p1; // 拷貝,不是移動! +println!("{}", p1); // 仍然有效 +println!("{:?}", p1); // Debug 輸出 +``` + + +### Iterator 特徵 + + +```python !! py +# Python - 迭代器協議 +class Counter: + def __init__(self, max): + self.max = max + self.current = 0 + + def __iter__(self): + return self + + def __next__(self): + if self.current < self.max: + value = self.current + self.current += 1 + return value + raise StopIteration +``` + +```rust !! rs +// Rust - Iterator 特徵 +struct Counter { + max: u32, + current: u32, +} + +impl Iterator for Counter { + type Item = u32; + + fn next(&mut self) -> Option { + if self.current < self.max { + let value = self.current; + self.current += 1; + Some(value) + } else { + None + } + } +} + +// 迭代器方法自動可用 +let sum: u32 = Counter::new(5).sum(); +println!("Sum: {}", sum); // 10 +``` + + +## 10.3 泛型:型別無關程式碼 + +### Python:動態型別 + +Python 動態處理不同型別: + +```python +# Python - 適用於任何型別 +def first(items): + return items[0] if items else None +``` + +### Rust:泛型函數 + + +```python !! py +# Python - 動態型別 +def get_first(items): + if not items: + return None + return items[0] +``` + +```rust !! rs +// Rust - 泛型函數 +fn get_first(items: &[T]) -> Option<&T> { + if items.is_empty() { + None + } else { + Some(&items[0]) + } +} + +// 使用不同型別 +let numbers = vec![1, 2, 3]; +let strings = vec!["a", "b", "c"]; + +println!("{:?}", get_first(&numbers)); // Some(1) +println!("{:?}", get_first(&strings)); // Some("a") + +// 多個泛型參數 +fn pair(a: A, b: B) -> (A, B) { + (a, b) +} +``` + + +### 泛型結構體 + + +```python !! py +# Python - 適用於任何型別 +class Container: + def __init__(self, value): + self.value = value +``` + +```rust !! rs +// Rust - 泛型結構體 +struct Container { + value: T, +} + +impl Container { + fn new(value: T) -> Self { + Container { value } + } + + fn get(&self) -> &T { + &self.value + } +} + +// 使用不同型別 +let container1: Container = Container::new(42); +let container2: Container<&str> = Container::new("hello"); +``` + + +## 10.4 特徵邊界和約束 + +### 基本特徵邊界 + + +```python !! py +# Python - 鴨子型別(執行時檢查) +def process(items): + for item in items: + print(item) + return len(items) +``` + +```rust !! rs +// Rust - 特徵邊界(編譯時檢查) +fn process(items: &[T]) -> usize { + for item in items { + println!("{}", item); // 需要 Display + } + items.len() +} + +// 多個邊界 +fn clone_and_compare(item: &T) -> T +where + T: Clone + PartialEq, +{ + let cloned = item.clone(); + cloned +} +``` + + +## 10.5 特徵物件:動態分發 + +### 靜態 vs 動態分發 + + +```python !! py +# Python - 始終動態分發 +def process(obj): + obj.speak() # 執行時解析 +``` + +```rust !! rs +// Rust - 靜態分發(預設,更快) +fn process_static(speaker: T) { + speaker.speak(); // 編譯時解析 +} + +// 動態分發(特徵物件) +fn process_dynamic(speaker: &dyn Speak) { + speaker.speak(); // 執行時通過 vtable 解析 +} + +// 何時使用哪個 +// 靜態分發:編譯時知道型別時(更快) +// 動態分發:需要不同型別的集合時 +``` + + +### 特徵物件實踐 + + +```python !! py +# Python - 異質集合 +animals = [Dog("Fido"), Cat("Whiskers")] +for animal in animals: + print(animal.speak()) +``` + +```rust !! rs +// Rust - 異質集合的特徵物件 +trait Speak { + fn speak(&self) -> String; +} + +struct Dog { name: String } +impl Speak for Dog { + fn speak(&self) -> String { + format!("{} says Woof!", self.name) + } +} + +struct Cat { name: String } +impl Speak for Cat { + fn speak(&self) -> String { + format!("{} says Meow!", self.name) + } +} + +// 特徵物件集合 +let dog = Dog { name: String::from("Fido") }; +let cat = Cat { name: String::from("Whiskers") }; + +// 必須使用引用 (&dyn Speak) 或 Box +let animals: Vec> = vec![ + Box::new(dog), + Box::new(cat), +]; + +for animal in animals.iter() { + println!("{}", animal.speak()); +} +``` + + +## 10.6 高級特徵模式 + +### 關聯型別 + + +```rust !! rs +// Rust - 關聯型別(比泛型更精確) +trait Iterator { + type Item; // 關聯型別 + + fn next(&mut self) -> Option; +} + +struct Counter; +impl Iterator for Counter { + type Item = i32; // 指定關聯型別 + + fn next(&mut self) -> Option { + Some(42) + } +} +``` + + +### 預設實現 + + +```rust !! rs +// 特徵可以提供預設實現 +trait Animal { + fn speak(&self) -> String { + String::from("Unknown sound") + } + + fn intro(&self) -> String { + format!("I say: {}", self.speak()) + } +} + +struct Dog; +impl Animal for Dog { + fn speak(&self) -> String { + String::from("Woof!") + } + // intro() 使用預設實現 +} + +// 使用 +let dog = Dog; +println!("{}", dog.intro()); // I say: Woof! +``` + + +### 超特徵 + + +```rust !! rs +// Rust - 超特徵(特徵繼承) +trait Animal { + fn name(&self) -> &str; +} + +trait Pet: Animal { // Pet 要求 Animal + fn cuddle(&self) { + println!("{} is being cuddled", self.name()); + } +} + +struct Dog { name: String } + +impl Animal for Dog { + fn name(&self) -> &str { + &self.name + } +} + +impl Pet for Dog { + // cuddle() 使用預設,也可以覆蓋 +} +``` + + +## 常見陷阱 + +### 陷阱 1:孤兒規則 + +```rust +// 不要:在外部型別上實現外部特徵 +// impl Display for Vec { } // 錯誤! + +// 應該:使用 newtype 模式 +struct MyVec(Vec); + +impl std::fmt::Display for MyVec { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{:?}", self.0) + } +} +``` + +### 陷阱 2:過度使用特徵物件 + +```rust +// 不要:靜態分發可行時使用特徵物件 +fn process(items: &[&dyn Speak]) { } // 更慢 + +// 應該:盡可能使用泛型 +fn process(items: &[T]) { } // 更快 +``` + +## 最佳實踐 + +1. **優先使用靜態分發** - 盡可能使用泛型而不是特徵物件 +2. **保持特徵聚焦** - 小型、內聚的特徵比大型特徵更好 +3. **提供預設實現** - 使特徵更容易實現 +4. **使用標準庫特徵** - 不要重新發明 Display、Debug、Clone 等 +5. **考慮物件安全** - 決定特徵是否應該可用作特徵物件 +6. **記錄特徵契約** - 清楚地記錄實現者必須提供什麼 + +## 總結 + +在本模組中,我們涵蓋了: + +- **特徵**:具有形式化契約的共享行為 +- **泛型**:型別安全的型別無關程式碼 +- **特徵邊界**:泛型型別的編譯時約束 +- **標準庫特徵**:Display、Debug、Clone、Iterator 等 +- **特徵物件**:執行時多態的動態分發 +- **高級模式**:關聯型別、預設實現、超特徵 + +關鍵要點: +- 特徵提供零成本抽象的共享行為 +- 泛型實現靈活、型別安全的程式碼 +- 優先靜態分發,需要時使用特徵物件 +- 標準庫提供許多有用的特徵 +- 特徵邊界實現強大的編譯時多態 +- Rust 的型別系統同時實現靈活性和效能 + +## 練習 + +創建一個通用資料處理庫: +1. 定義帶有 `process` 方法的 `Processable` 特徵 +2. 為多個型別實現它 +3. 使用泛型處理集合 +4. 演示靜態和動態分發 +5. 使用特徵邊界約束功能 + +
+檢視解決方案 + +```rust +use std::fmt::Display; + +trait Processable { + type Output; + fn process(&self) -> Self::Output; +} + +#[derive(Debug)] +struct Number(i32); + +impl Processable for Number { + type Output = String; + + fn process(&self) -> Self::Output { + format!("Doubled: {}", self.0 * 2) + } +} + +fn process_item(item: &T) -> String { + format!("{:?}", item.process()) +} + +fn main() { + let num = Number(42); + println!("{}", process_item(&num)); // "Doubled: 84" +} +``` + +
+ +恭喜完成模組 10!你現在對 Rust 的特徵和泛型系統有了紮實的理解。這些功能使你能夠編寫靈活、可重用和高效能的型別安全程式碼。繼續通過實際項目練習以掌握這些概念! diff --git a/content/docs/py2rust/module-11-lifetimes.mdx b/content/docs/py2rust/module-11-lifetimes.mdx new file mode 100644 index 0000000..c0a82de --- /dev/null +++ b/content/docs/py2rust/module-11-lifetimes.mdx @@ -0,0 +1,887 @@ +--- +title: "Module 11: Lifetimes - Memory Safety Without Garbage Collection" +description: "Understand Rust's lifetime system, how it ensures memory safety without a garbage collector, and how it differs from Python's reference counting." +--- + +# Module 11: Lifetimes - Memory Safety Without Garbage Collection + +In this module, you'll learn about one of Rust's most unique features: **lifetimes**. Lifetimes are how Rust achieves memory safety without a garbage collector, which is fundamentally different from Python's reference counting approach. + +## Learning Objectives + +By the end of this module, you'll be able to: +- Understand what lifetimes are and why Rust needs them +- Use lifetime annotations in function signatures +- Understand lifetime elision rules +- Work with `'static` lifetime +- Compare Rust's ownership model with Python's reference counting + +## Background: Python vs Rust Memory Management + +### Python's Memory Model + +Python uses **reference counting** with a cycle-detecting garbage collector: + +```python +# Python: Automatic memory management via reference counting +def process_data(data): + # data is a reference, refcount increases + result = transform(data) + # When function returns, refs are decremented + return result + +# Objects are freed when refcount reaches 0 +# Garbage collector handles reference cycles +``` + +### Rust's Memory Model + +Rust uses **ownership** with **lifetimes** for compile-time memory management: + +```rust +// Rust: Compile-time memory management with lifetimes +fn process_data<'a>(data: &'a str) -> &'a str { + // The compiler verifies that the returned reference + // lives as long as the input reference + let result = transform(data); + result +} +// Memory is freed when owners go out of scope +// No garbage collector needed! +``` + +## What Are Lifetimes? + +Lifetimes are how the Rust compiler tracks **how long references are valid**. They ensure that you never have a dangling reference - a reference that points to memory that has been freed. + +### The Problem Lifetimes Solve + +Consider this example that would be dangerous in languages without lifetime checks: + + +```python !! py +# Python: No dangling references (GC prevents this) +def get_reference(): + local = "temporary data" + return local # Returns the object, not a reference to memory + +# In Python, objects live until refcount is 0 +ref = get_reference() +print(ref) # Works fine, object is still alive +``` + +```rust !! rs +// Rust: This won't compile - dangling reference prevented +// fn get_reference() -> &str { // Error: missing lifetime +// let local = String::from("temporary data"); +// &local // Error: returns a reference to dropped data +// } // local is dropped here! + +// Rust compiler prevents this at compile time! +``` + + +## Lifetime Annotations + +Lifetime annotations tell the compiler **how references relate to each other**. They don't change how long references live - they just describe the relationships. + +### Basic Lifetime Syntax + + +```python !! py +# Python: No lifetime annotations needed +# The garbage collector handles everything + +def first_word(text: str) -> str: + words = text.split() + return words[0] if words else "" + +# Works fine - Python keeps the string alive +s = "hello world" +word = first_word(s) +``` + +```rust !! rs +// Rust: Explicit lifetime annotations +// The 'a lifetime parameter connects input and output +fn first_word<'a>(text: &'a str) -> &'a str { + // The returned reference is valid for as long + // as the input reference is valid + match text.split(' ').next() { + Some(word) => word, + None => "", + } +} + +// The compiler verifies that 'a makes sense +let s = String::from("hello world"); +let word = first_word(&s); +// word is valid as long as s is valid +``` + + +### Lifetime Parameters in Structs + +When a struct holds references, you must specify lifetimes: + + +```python !! py +# Python: Classes can hold references freely +class Processor: + def __init__(self, data): + self.data = data # Reference to data + +data = [1, 2, 3, 4, 5] +processor = Processor(data) +# The list stays alive as long as processor needs it +print(processor.data) +``` + +```rust !! rs +// Rust: Structs with references need lifetime annotations +struct Processor<'a> { + // This reference is valid for 'a lifetime + data: &'a [i32], +} + +impl<'a> Processor<'a> { + fn new(data: &'a [i32]) -> Self { + Processor { data } + } + + fn process(&self) -> i32 { + self.data.iter().sum() + } +} + +let data = vec![1, 2, 3, 4, 5]; +let processor = Processor::new(&data); +// processor is valid as long as data is valid +println!("{}", processor.process()); +``` + + +### Multiple Lifetime Parameters + +You can have multiple lifetime parameters to show different relationships: + + +```python !! py +# Python: References can have independent lifetimes +class Pair: + def __init__(self, first, second): + self.first = first + self.second = second + +a = "first" +b = "second" +pair = Pair(a, b) +# Both strings stay alive +``` + +```rust !! rs +// Rust: Different lifetimes for different references +struct Pair<'a, 'b> { + first: &'a str, + second: &'b str, +} + +impl<'a, 'b> Pair<'a, 'b> { + fn new(first: &'a str, second: &'b str) -> Self { + Pair { first, second } + } + + fn compare(&self) -> bool { + self.first == self.second + } +} + +let a = String::from("first"); +let b = String::from("second"); + +{ + let b_ref = &b; + let pair = Pair::new(&a, b_ref); + // a and b_ref can have different lifetimes + println!("{}", pair.compare()); +} +``` + + +## Lifetime Elision Rules + +Rust has **lifetime elision rules** that let you omit lifetime annotations in common cases. The compiler infers the lifetimes for you. + +### Rule 1: Input Lifetimes + +Each reference parameter gets its own lifetime parameter: + + +```python !! py +# Python: No annotations needed +def single_arg(text: str) -> str: + return text.upper() + +def two_args(a: str, b: str) -> str: + return a + b +``` + +```rust !! rs +// Rust: Rule 1 - Each reference gets a lifetime +fn single_arg(text: &str) -> &str { // Elided + // Compiler infers: fn single_arg<'a>(text: &'a str) -> &'a str + text.to_uppercase().leak() // For demonstration only! +} + +fn two_args(a: &str, b: &str) -> &str { // Elided + // Compiler infers: fn two_args<'a, 'b>(a: &'a str, b: &'b str) -> &_ + // But this won't compile without explicit lifetime on return + "not implementable without explicit lifetime" +} +``` + + +### Rule 2: Single Input Lifetime + +If there's exactly one input lifetime, it's assigned to all output lifetimes: + + +```python !! py +# Python: Simple reference handling +def get_first(data: list) -> any: + return data[0] if data else None +``` + +```rust !! rs +// Rust: Rule 2 - Single input lifetime applies to output +fn get_first(data: &[i32]) -> Option<&i32> { // Elided + // Compiler infers: fn get_first<'a>(data: &'a [i32]) -> Option<&'a i32> + data.first() +} + +// Fully annotated version: +fn get_first_explicit<'a>(data: &'a [i32]) -> Option<&'a i32> { + data.first() +} +``` + + +### Rule 3: Methods with &self or &mut self + +If there's a &self or &mut self parameter, its lifetime is assigned to all output lifetimes: + + +```python !! py +# Python: Methods don't need lifetime annotations +class Container: + def __init__(self, items): + self.items = items + + def get_first(self): + return self.items[0] if self.items else None + + def get_last(self): + return self.items[-1] if self.items else None +``` + +```rust !! rs +// Rust: Rule 3 - &self lifetime applies to output +struct Container { + items: Vec, +} + +impl Container { + fn get_first(&self) -> Option<&i32> { // Elided + // Compiler infers: fn get_first<'a>(&'a self) -> Option<&'a i32> + self.items.first() + } + + fn get_last(&self) -> Option<&i32> { // Elided + self.items.last() + } +} + +// Fully annotated versions: +impl Container { + fn get_first_explicit<'a>(&'a self) -> Option<&'a i32> { + self.items.first() + } + + fn get_last_explicit<'a>(&'a self) -> Option<&'a i32> { + self.items.last() + } +} +``` + + +## When You Need Explicit Lifetimes + +You need explicit lifetime annotations when elision rules don't apply, especially with multiple input references: + + +```python !! py +# Python: No issues with multiple references +def choose_longer(a: str, b: str) -> str: + return a if len(a) > len(b) else b +``` + +```rust !! rs +// Rust: Multiple input references need explicit lifetimes +fn choose_longer<'a>(a: &'a str, b: &'a str) -> &'a str { + // Both inputs must live at least as long as the output + if a.len() > b.len() { + a + } else { + b + } +} + +// Using it: +let s1 = String::from("short"); +let s2 = String::from("much longer string"); +let result = choose_longer(&s1, &s2); +println!("{}", result); +``` + + +## The 'static Lifetime + +The `'static` lifetime is a special lifetime that lasts for the **entire duration of the program**. + + +```python !! py +# Python: Module-level constants live forever +CONFIG = { + "timeout": 30, + "retries": 3 +} + +def get_config(): + return CONFIG # Always valid + +# Also: String literals are interned and live forever +message = "hello" # This string stays in memory +``` + +```rust !! rs +// Rust: 'static lifetime - lives for the entire program +// String literals have 'static lifetime +const CONFIG: &str = "default config"; + +fn get_config() -> &'static str { + CONFIG // OK: CONFIG has 'static lifetime +} + +// Static variables +static TIMEOUT: u32 = 30; + +fn get_timeout() -> u32 { + TIMEOUT // Always valid +} + +// String literals are 'static +fn get_message() -> &'static str { + "hello" // This string is stored in the binary +} + +// Comparison with non-static +fn temporary_string() -> String { + String::from("temporary") +} +``` + + +### When to Use 'static + + +```python !! py +# Python: Global cache (lives forever) +cache = {} + +def memoize(func): + def wrapper(key): + if key not in cache: + cache[key] = func(key) + return cache[key] + return wrapper + +@memoize +def expensive_computation(n): + return sum(range(n)) +``` + +```rust !! rs +// Rust: Using 'static for global data +use std::collections::HashMap; +use std::sync::Mutex; + +// Static mutable data requires Mutex for thread safety +static CACHE: Mutex> = Mutex::new(HashMap::new()); + +fn memoize computation(key: &'static str) -> String { + let mut cache = CACHE.lock().unwrap(); + if let Some(value) = cache.get(key) { + value.clone() + } else { + let computed = format!("computed for {}", key); + cache.insert(key, computed.clone()); + computed + } +} + +// Static strings are common +fn get_static_str() -> &'static str { + "This lives forever" +} + +// Be careful: don't overuse 'static +fn example() { + // Don't write this: + // fn unnecessary_static(s: &'static str) -> &'static str { s } + + // Better - let the compiler infer the shortest lifetime + fn better(s: &str) -> &str { s } +} +``` + + +## Lifetime Subtyping + +Lifetimes support subtyping - a longer lifetime can be used where a shorter one is expected: + + +```python !! py +# Python: No explicit subtyping needed +def with_string(data: str): + print(data) + +s = "hello" +with_string(s) # Works fine +``` + +```rust !! rs +// Rust: Longer lifetime can be used as shorter lifetime +fn with_short<'a>(s: &'a str) { + println!("{}", s); +} + +fn with_long<'b>(s: &'b str) where 'b: 'a { + // 'b outlives 'a, so we can pass to with_short + with_short(s); +} + +let long_lived = String::from("I live a long time"); +with_long(&long_lived); +``` + + +## Common Lifetime Patterns + +### Returning References from Functions + + +```python !! py +# Python: Return reference to part of input +def find_item(items, target): + for item in items: + if item == target: + return item + return None + +items = ["apple", "banana", "cherry"] +found = find_item(items, "banana") +``` + +```rust !! rs +// Rust: Return reference to input data +fn find_item<'a>(items: &'a [String], target: &str) -> Option<&'a String> { + items.iter().find(|item| item == target) +} + +let items = vec![ + String::from("apple"), + String::from("banana"), + String::from("cherry"), +]; + +let found = find_item(&items, "banana"); +// found is valid as long as items is valid +``` + + +### Structs with Multiple References + + +```python !! py +# Python: Context object with multiple references +class ProcessingContext: + def __init__(self, config, data): + self.config = config + self.data = data + self.timestamp = time.time() + +config = {"timeout": 30} +data = [1, 2, 3] +ctx = ProcessingContext(config, data) +``` + +```rust !! rs +// Rust: Context with different lifetimes +struct ProcessingContext<'a, 'b> { + config: &'a Config, + data: &'b [i32], + timestamp: u64, +} + +struct Config { + timeout: u32, +} + +impl<'a, 'b> ProcessingContext<'a, 'b> { + fn new(config: &'a Config, data: &'b [i32]) -> Self { + ProcessingContext { + config, + data, + timestamp: 0, // Would get actual time + } + } + + fn process(&self) -> u32 { + self.data.iter().sum::() as u32 * self.config.timeout + } +} + +let config = Config { timeout: 30 }; +let data = vec![1, 2, 3]; +let ctx = ProcessingContext::new(&config, &data); +``` + + +## Lifetime Bounds + +You can add lifetime bounds to generic types: + + +```python !! py +# Python: No explicit bounds needed +class Container: + def __init__(self, value): + self.value = value + +def extract(container): + return container.value +``` + +```rust !! rs +// Rust: Lifetime bounds on generics +struct Container<'a, T: 'a> { + // T must outlive 'a + value: &'a T, +} + +impl<'a, T: 'a> Container<'a, T> { + fn new(value: &'a T) -> Self { + Container { value } + } + + fn get_value(&self) -> &'a T { + self.value + } +} + +let x = 42; +let container = Container::new(&x); +println!("{}", container.get_value()); +``` + + +## Comparison: Python Reference Counting vs Rust Lifetimes + +### Memory Management Comparison + + +```python !! py +# Python: Runtime reference counting +import sys + +class Data: + def __init__(self, value): + self.value = value + print(f"Created Data({value}), refcount: {sys.getrefcount(self)}") + + def __del__(self): + print(f"Destroyed Data({self.value})") + +# Reference counting happens at runtime +def process(): + data = Data(10) # refcount increases + print(f"In function: {sys.getrefcount(data)}") + return data # refcount transferred + +result = process() +print(f"After call: {sys.getrefcount(result)}") +# Automatic cleanup when refcount reaches 0 +``` + +```rust !! rs +// Rust: Compile-time lifetime checking +struct Data { + value: i32, +} + +impl Data { + fn new(value: i32) -> Self { + println!("Created Data({})", value); + Data { value } + } +} + +impl Drop for Data { + fn drop(&mut self) { + println!("Destroyed Data({})", self.value); + } +} + +fn process() -> Data { + let data = Data::new(10); + data // Ownership moved +} + +let result = process(); +// result is dropped automatically at end of scope +``` + + +### Performance Implications + + +```python !! py +# Python: Reference counting overhead +def process_many(): + # Every assignment changes refcount + items = [] + for i in range(1_000_000): + items.append(i) # refcount operations + temp = items[-1] # refcount operations + # More refcount operations... + +process_many() # Millions of refcount operations +``` + +```rust !! rs +// Rust: Zero-cost lifetime checking +fn process_many() { + let mut items = Vec::new(); + for i in 0..1_000_000 { + items.push(i); + let temp = items.last(); + // No runtime overhead - lifetimes checked at compile time + } +} + +process_many(); // No runtime cost for lifetimes! +``` + + +### Safety Comparison + + +```python !! py +# Python: Protected by reference counting +def dangerous(): + local = [1, 2, 3] + return local + +reference = dangerous() +# reference is valid because the list is still alive +print(reference) # Safe! +``` + +```rust !! rs +// Rust: Protected by compile-time checking +// fn dangerous() -> &Vec { +// let local = vec![1, 2, 3]; +// &local // ERROR: returns reference to dropped value +// } // local is dropped here + +// Correct version - move ownership +fn safe() -> Vec { + let local = vec![1, 2, 3]; + local // Ownership moved out +} + +let reference = safe(); +// reference owns the data, no dangling reference possible +println!("{:?}", reference); +``` + + +## Common Pitfalls and How to Avoid Them + +### Pitfall 1: Trying to Store Temporary References + + +```python !! py +# Python: Storing references is fine +class Cache: + def __init__(self): + self.cache = {} + + def get_or_compute(self, key, compute_fn): + if key not in self.cache: + self.cache[key] = compute_fn() + return self.cache[key] + +cache = Cache() +result = cache.get_or_compute("expensive", lambda: sum(range(1000))) +``` + +```rust !! rs +// Rust: Don't store references to temporary data +struct Cache<'a> { + cache: std::collections::HashMap, +} + +impl<'a> Cache<'a> { + fn get_or_compute(&mut self, key: &str, compute_fn: F) -> &'a str + where + F: FnOnce() -> &'a str, + { + if !self.cache.contains_key(key) { + self.cache.insert(key.to_string(), compute_fn()); + } + self.cache.get(key).unwrap() + } +} + +// Better: Use owned data +struct Cache { + cache: std::collections::HashMap, +} + +impl Cache { + fn get_or_compute(&mut self, key: &str, compute_fn: F) -> String + where + F: FnOnce() -> String, + { + if !self.cache.contains_key(key) { + self.cache.insert(key.to_string(), compute_fn()); + } + self.cache.get(key).unwrap().clone() + } +} +``` + + +### Pitfall 2: Overusing 'static + + +```python !! py +# Python: Normal references work fine +def process(data: str) -> str: + return data.upper() +``` + +```rust !! rs +// Rust: Don't use 'static unless necessary +// Too restrictive: +// fn process_static(data: &'static str) -> &'static str { +// data.to_uppercase().leak() // BAD: memory leak! +// } + +// Better - let compiler infer lifetimes: +fn process(data: &str) -> String { + data.to_uppercase() // Returns owned String +} + +// Best - use lifetime elision: +fn process_ref(data: &str) -> String { + data.to_uppercase() +} +``` + + +### Pitfall 3: Self-Referential Structs + + +```python !! py +# Python: Self-references are easy +class TreeNode: + def __init__(self, value): + self.value = value + self.children = [] + + def add_child(self, child): + self.children.append(child) +``` + +```rust !! rs +// Rust: Self-references require special handling +// This won't work: +// struct TreeNode<'a> { +// value: String, +// children: Vec<&'a TreeNode<'a>>, // Problematic! +// } + +// Better approach - use indices or Rc +use std::rc::Rc; + +struct TreeNode { + value: String, + children: Vec>, +} + +impl TreeNode { + fn new(value: String) -> Self { + TreeNode { + value, + children: Vec::new(), + } + } + + fn add_child(&mut self, child: Rc) { + self.children.push(child); + } +} +``` + + +## Best Practices + +### DO: + +1. **Let the compiler help** - Trust Rust's lifetime inference +2. **Start with elided lifetimes** - Only add explicit annotations when needed +3. **Keep lifetimes short** - Minimize the scope of references +4. **Use owned data when uncertain** - Move instead of borrow when appropriate +5. **Read the error messages** - Rust's compiler gives excellent lifetime diagnostics + +### DON'T: + +1. **Add unnecessary 'static** - Only use when data truly lives forever +2. **Create self-references with plain references** - Use Rc, Arc, or indices +3. **Try to outsmart the borrow checker** - It's preventing real bugs +4. **Use unsafe to bypass lifetimes** - Unless absolutely necessary +5. **Ignore lifetime warnings** - They're trying to help you + +## Summary + +- **Lifetimes** are compile-time checks that ensure references are valid +- **Lifetime annotations** describe relationships between references +- **Elision rules** let you omit annotations in common cases +- **'static lifetime** means "valid for the entire program" +- **Rust's lifetimes** provide memory safety without runtime overhead +- **Python's reference counting** provides safety at runtime cost +- **Rust trades complexity for performance and safety** + +## Practice Exercises + +1. Write a function that takes two string slices and returns the longer one +2. Create a struct that holds a reference to a configuration and data +3. Implement a caching system with proper lifetime management +4. Refactor code to use lifetime elision where possible +5. Compare the performance of a recursive function with owned data vs references + +## Next Module + +In the next module, we'll explore **smart pointers** (Box, Rc, Arc, RefCell) and how they compare to Python's memory model. You'll learn when to use each type and how they enable patterns like shared ownership and interior mutability. diff --git a/content/docs/py2rust/module-11-lifetimes.zh-cn.mdx b/content/docs/py2rust/module-11-lifetimes.zh-cn.mdx new file mode 100644 index 0000000..062bdd0 --- /dev/null +++ b/content/docs/py2rust/module-11-lifetimes.zh-cn.mdx @@ -0,0 +1,889 @@ +--- +title: "模块 11: 生命周期 - 无需垃圾回收的内存安全" +description: "理解 Rust 的生命周期系统,它如何在没有垃圾回收器的情况下确保内存安全,以及它与 Python 引用计数的区别。" +--- + +# 模块 11: 生命周期 - 无需垃圾回收的内存安全 + +在本模块中,你将学习 Rust 最独特的功能之一:**生命周期**。生命周期是 Rust 在没有垃圾回收器的情况下实现内存安全的方式,这与 Python 的引用计数方法有本质区别。 + +## 学习目标 + +完成本模块后,你将能够: +- 理解什么是生命周期以及为什么 Rust 需要它们 +- 在函数签名中使用生命周期注解 +- 理解生命周期省略规则 +- 使用 `'static` 生命周期 +- 比较 Rust 的所有权模型与 Python 的引用计数 + +## 背景:Python vs Rust 内存管理 + +### Python 的内存模型 + +Python 使用**引用计数**和循环检测垃圾回收器: + +```python +# Python: 通过引用计数进行自动内存管理 +def process_data(data): + # data 是一个引用,引用计数增加 + result = transform(data) + # 当函数返回时,引用计数减少 + return result + +# 当引用计数达到 0 时,对象被释放 +# 垃圾回收器处理引用循环 +``` + +### Rust 的内存模型 + +Rust 使用**所有权**和**生命周期**进行编译时内存管理: + +```rust +// Rust: 使用生命周期的编译时内存管理 +fn process_data<'a>(data: &'a str) -> &'a str { + // 编译器验证返回的引用 + // 与输入引用一样有效 + let result = transform(data); + result +} +// 内存在所有者离开作用域时被释放 +// 不需要垃圾回收器! +``` + +## 什么是生命周期? + +生命周期是 Rust 编译器跟踪**引用有效时间**的方式。它们确保你永远不会拥有悬垂引用 - 指向已释放内存的引用。 + +### 生命周期解决的问题 + +考虑这个在没有生命周期检查的语言中会有危险的例子: + + +```python !! py +# Python: 没有悬垂引用(GC 防止了这个问题) +def get_reference(): + local = "临时数据" + return local # 返回对象,而不是内存引用 + +# 在 Python 中,对象存活到引用计数为 0 +ref = get_reference() +print(ref) # 工作正常,对象仍然存活 +``` + +```rust !! rs +// Rust: 这不会编译 - 悬垂引用被阻止 +// fn get_reference() -> &str { // 错误: 缺少生命周期 +// let local = String::from("临时数据"); +// &local // 错误: 返回对已释放数据的引用 +// } // local 在这里被释放! + +// Rust 编译器在编译时阻止这种情况! +``` + + +## 生命周期注解 + +生命周期注解告诉编译器**引用之间如何关联**。它们不会改变引用存活多长时间 - 它们只是描述关系。 + +### 基本生命周期语法 + + +```python !! py +# Python: 不需要生命周期注解 +# 垃圾回收器处理一切 + +def first_word(text: str) -> str: + words = text.split() + return words[0] if words else "" + +# 工作正常 - Python 保持字符串存活 +s = "hello world" +word = first_word(s) +``` + +```rust !! rs +// Rust: 显式生命周期注解 +// 'a 生命周期参数连接输入和输出 +fn first_word<'a>(text: &'a str) -> &'a str { + // 返回的引用只要输入引用有效就有效 + match text.split(' ').next() { + Some(word) => word, + None => "", + } +} + +// 编译器验证 'a 是否有意义 +let s = String::from("hello world"); +let word = first_word(&s); +// word 只要 s 有效就有效 +``` + + +### 结构体中的生命周期参数 + +当结构体持有引用时,必须指定生命周期: + + +```python !! py +# Python: 类可以自由持有引用 +class Processor: + def __init__(self, data): + self.data = data # 对数据的引用 + +data = [1, 2, 3, 4, 5] +processor = Processor(data) +# 列表在 processor 需要时保持存活 +print(processor.data) +``` + +```rust !! rs +// Rust: 带有引用的结构体需要生命周期注解 +struct Processor<'a> { + // 这个引用对 'a 生命周期有效 + data: &'a [i32], +} + +impl<'a> Processor<'a> { + fn new(data: &'a [i32]) -> Self { + Processor { data } + } + + fn process(&self) -> i32 { + self.data.iter().sum() + } +} + +let data = vec![1, 2, 3, 4, 5]; +let processor = Processor::new(&data); +// processor 只要 data 有效就有效 +println!("{}", processor.process()); +``` + + +### 多个生命周期参数 + +你可以有多个生命周期参数来显示不同的关系: + + +```python !! py +# Python: 引用可以有不同的生命周期 +class Pair: + def __init__(self, first, second): + self.first = first + self.second = second + +a = "first" +b = "second" +pair = Pair(a, b) +# 两个字符串都保持存活 +``` + +```rust !! rs +// Rust: 不同引用的不同生命周期 +struct Pair<'a, 'b> { + first: &'a str, + second: &'b str, +} + +impl<'a, 'b> Pair<'a, 'b> { + fn new(first: &'a str, second: &'b str) -> Self { + Pair { first, second } + } + + fn compare(&self) -> bool { + self.first == self.second + } +} + +let a = String::from("first"); +let b = String::from("second"); + +{ + let b_ref = &b; + let pair = Pair::new(&a, b_ref); + // a 和 b_ref 可以有不同的生命周期 + println!("{}", pair.compare()); +} +``` + + +## 生命周期省略规则 + +Rust 有**生命周期省略规则**,让你在常见情况下省略生命周期注解。编译器会为你推断生命周期。 + +### 规则 1: 输入生命周期 + +每个引用参数获得自己的生命周期参数: + + +```python !! py +# Python: 不需要注解 +def single_arg(text: str) -> str: + return text.upper() + +def two_args(a: str, b: str) -> str: + return a + b +``` + +```rust !! rs +// Rust: 规则 1 - 每个引用获得一个生命周期 +fn single_arg(text: &str) -> &str { // 省略 + // 编译器推断: fn single_arg<'a>(text: &'a str) -> &'a str + text.to_uppercase().leak() // 仅用于演示! +} + +fn two_args(a: &str, b: &str) -> &str { // 省略 + // 编译器推断: fn two_args<'a, 'b>(a: &'a str, b: &'b str) -> &_ + // 但这无法编译,返回值需要显式生命周期 + "没有显式生命周期无法实现" +} +``` + + +### 规则 2: 单个输入生命周期 + +如果只有一个输入生命周期,它被分配给所有输出生命周期: + + +```python !! py +# Python: 简单的引用处理 +def get_first(data: list) -> any: + return data[0] if data else None +``` + +```rust !! rs +// Rust: 规则 2 - 单个输入生命周期应用于输出 +fn get_first(data: &[i32]) -> Option<&i32> { // 省略 + // 编译器推断: fn get_first<'a>(data: &'a [i32]) -> Option<&'a i32> + data.first() +} + +// 完全注解版本: +fn get_first_explicit<'a>(data: &'a [i32]) -> Option<&'a i32> { + data.first() +} +``` + + +### 规则 3: 带 &self 或 &mut self 的方法 + +如果有 &self 或 &mut self 参数,它的生命周期被分配给所有输出生命周期: + + +```python !! py +# Python: 方法不需要生命周期注解 +class Container: + def __init__(self, items): + self.items = items + + def get_first(self): + return self.items[0] if self.items else None + + def get_last(self): + return self.items[-1] if self.items else None +``` + +```rust !! rs +// Rust: 规则 3 - &self 生命周期应用于输出 +struct Container { + items: Vec, +} + +impl Container { + fn get_first(&self) -> Option<&i32> { // 省略 + // 编译器推断: fn get_first<'a>(&'a self) -> Option<&'a i32> + self.items.first() + } + + fn get_last(&self) -> Option<&i32> { // 省略 + self.items.last() + } +} + +// 完全注解版本: +impl Container { + fn get_first_explicit<'a>(&'a self) -> Option<&'a i32> { + self.items.first() + } + + fn get_last_explicit<'a>(&'a self) -> Option<&'a i32> { + self.items.last() + } +} +``` + + +## 何时需要显式生命周期 + +当省略规则不适用时,你需要显式生命周期注解,特别是在有多个输入引用时: + + +```python !! py +# Python: 多个引用没有问题 +def choose_longer(a: str, b: str) -> str: + return a if len(a) > len(b) else b +``` + +```rust !! rs +// Rust: 多个输入引用需要显式生命周期 +fn choose_longer<'a>(a: &'a str, b: &'a str) -> &'a str { + // 两个输入必须至少与输出一样长 + if a.len() > b.len() { + a + } else { + b + } +} + +// 使用它: +let s1 = String::from("short"); +let s2 = String::from("much longer string"); +let result = choose_longer(&s1, &s2); +println!("{}", result); +``` + + +## 'static 生命周期 + +`'static` 生命周期是一个特殊的生命周期,持续**程序的整个持续时间**。 + + +```python !! py +# Python: 模块级常量永远存在 +CONFIG = { + "timeout": 30, + "retries": 3 +} + +def get_config(): + return CONFIG # 始终有效 + +# 此外: 字符串字面量被驻留并永远存在 +message = "hello" # 这个字符串保留在内存中 +``` + +```rust !! rs +// Rust: 'static 生命周期 - 存在于整个程序 +// 字符串字面量具有 'static 生命周期 +const CONFIG: &str = "default config"; + +fn get_config() -> &'static str { + CONFIG // OK: CONFIG 具有 'static 生命周期 +} + +// 静态变量 +static TIMEOUT: u32 = 30; + +fn get_timeout() -> u32 { + TIMEOUT // 始终有效 +} + +// 字符串字面量是 'static +fn get_message() -> &'static str { + "hello" // 这个字符串存储在二进制文件中 +} + +// 与非静态的比较 +fn temporary_string() -> String { + String::from("temporary") +} +``` + + +### 何时使用 'static + + +```python !! py +# Python: 全局缓存(永远存在) +cache = {} + +def memoize(func): + def wrapper(key): + if key not in cache: + cache[key] = func(key) + return cache[key] + return wrapper + +@memoize +def expensive_computation(n): + return sum(range(n)) +``` + +```rust !! rs +// Rust: 使用 'static 进行全局数据 +use std::collections::HashMap; +use std::sync::Mutex; + +// 静态可变数据需要 Mutex 以确保线程安全 +static CACHE: Mutex> = Mutex::new(HashMap::new()); + +fn memoize_computation(key: &'static str) -> String { + let mut cache = CACHE.lock().unwrap(); + if let Some(value) = cache.get(key) { + value.clone() + } else { + let computed = format!("computed for {}", key); + cache.insert(key, computed.clone()); + computed + } +} + +// 静态字符串很常见 +fn get_static_str() -> &'static str { + "This lives forever" +} + +// 小心: 不要过度使用 'static +fn example() { + // 不要这样写: + // fn unnecessary_static(s: &'static str) -> &'static str { s } + + // 更好 - 让编译器推断最短生命周期 + fn better(s: &str) -> &str { s } +} +``` + + +## 生命周期子类型 + +生命周期支持子类型 - 更长的生命周期可以在需要较短生命周期的地方使用: + + +```python !! py +# Python: 不需要显式子类型 +def with_string(data: str): + print(data) + +s = "hello" +with_string(s) # 工作正常 +``` + +```rust !! rs +// Rust: 更长的生命周期可以用作更短的生命周期 +fn with_short<'a>(s: &'a str) { + println!("{}", s); +} + +fn with_long<'b>(s: &'b str) +where + 'b: 'a, +{ + // 'b 比 'a 长,所以我们可以传递给 with_short + with_short(s); +} + +let long_lived = String::from("I live a long time"); +with_long(&long_lived); +``` + + +## 常见生命周期模式 + +### 从函数返回引用 + + +```python !! py +# Python: 返回对输入部分的引用 +def find_item(items, target): + for item in items: + if item == target: + return item + return None + +items = ["apple", "banana", "cherry"] +found = find_item(items, "banana") +``` + +```rust !! rs +// Rust: 返回对输入数据的引用 +fn find_item<'a>(items: &'a [String], target: &str) -> Option<&'a String> { + items.iter().find(|item| item == target) +} + +let items = vec![ + String::from("apple"), + String::from("banana"), + String::from("cherry"), +]; + +let found = find_item(&items, "banana"); +// found 只要 items 有效就有效 +``` + + +### 带有多个引用的结构体 + + +```python !! py +# Python: 带有多个引用的上下文对象 +class ProcessingContext: + def __init__(self, config, data): + self.config = config + self.data = data + self.timestamp = time.time() + +config = {"timeout": 30} +data = [1, 2, 3] +ctx = ProcessingContext(config, data) +``` + +```rust !! rs +// Rust: 带有不同生命周期的上下文 +struct ProcessingContext<'a, 'b> { + config: &'a Config, + data: &'b [i32], + timestamp: u64, +} + +struct Config { + timeout: u32, +} + +impl<'a, 'b> ProcessingContext<'a, 'b> { + fn new(config: &'a Config, data: &'b [i32]) -> Self { + ProcessingContext { + config, + data, + timestamp: 0, // 会获取实际时间 + } + } + + fn process(&self) -> u32 { + self.data.iter().sum::() as u32 * self.config.timeout + } +} + +let config = Config { timeout: 30 }; +let data = vec![1, 2, 3]; +let ctx = ProcessingContext::new(&config, &data); +``` + + +## 生命周期边界 + +你可以为泛型类型添加生命周期边界: + + +```python !! py +# Python: 不需要显式边界 +class Container: + def __init__(self, value): + self.value = value + +def extract(container): + return container.value +``` + +```rust !! rs +// Rust: 泛型上的生命周期边界 +struct Container<'a, T: 'a> { + // T 必须比 'a 长命 + value: &'a T, +} + +impl<'a, T: 'a> Container<'a, T> { + fn new(value: &'a T) -> Self { + Container { value } + } + + fn get_value(&self) -> &'a T { + self.value + } +} + +let x = 42; +let container = Container::new(&x); +println!("{}", container.get_value()); +``` + + +## 比较:Python 引用计数 vs Rust 生命周期 + +### 内存管理比较 + + +```python !! py +# Python: 运行时引用计数 +import sys + +class Data: + def __init__(self, value): + self.value = value + print(f"Created Data({value}), refcount: {sys.getrefcount(self)}") + + def __del__(self): + print(f"Destroyed Data({self.value})") + +# 引用计数在运行时发生 +def process(): + data = Data(10) # 引用计数增加 + print(f"In function: {sys.getrefcount(data)}") + return data # 引用计数转移 + +result = process() +print(f"After call: {sys.getrefcount(result)}") +# 当引用计数达到 0 时自动清理 +``` + +```rust !! rs +// Rust: 编译时生命周期检查 +struct Data { + value: i32, +} + +impl Data { + fn new(value: i32) -> Self { + println!("Created Data({})", value); + Data { value } + } +} + +impl Drop for Data { + fn drop(&mut self) { + println!("Destroyed Data({})", self.value); + } +} + +fn process() -> Data { + let data = Data::new(10); + data // 所有权移动 +} + +let result = process(); +// result 在作用域结束时自动被释放 +``` + + +### 性能影响 + + +```python !! py +# Python: 引用计数开销 +def process_many(): + # 每次赋值都会改变引用计数 + items = [] + for i in range(1_000_000): + items.append(i) # 引用计数操作 + temp = items[-1] # 引用计数操作 + # 更多引用计数操作... + +process_many() # 数百万次引用计数操作 +``` + +```rust !! rs +// Rust: 零成本生命周期检查 +fn process_many() { + let mut items = Vec::new(); + for i in 0..1_000_000 { + items.push(i); + let temp = items.last(); + // 没有运行时开销 - 生命周期在编译时检查 + } +} + +process_many(); // 生命周期没有运行时成本! +``` + + +### 安全性比较 + + +```python !! py +# Python: 受引用计数保护 +def dangerous(): + local = [1, 2, 3] + return local + +reference = dangerous() +# reference 有效,因为列表仍然存活 +print(reference) # 安全! +``` + +```rust !! rs +// Rust: 受编译时检查保护 +// fn dangerous() -> &Vec { +// let local = vec![1, 2, 3]; +// &local // 错误: 返回对已释放值的引用 +// } // local 在这里被释放 + +// 正确版本 - 移动所有权 +fn safe() -> Vec { + let local = vec![1, 2, 3]; + local // 所有权移出 +} + +let reference = safe(); +// reference 拥有数据,不可能有悬垂引用 +println!("{:?}", reference); +``` + + +## 常见陷阱及如何避免它们 + +### 陷阱 1: 尝试存储临时引用 + + +```python !! py +# Python: 存储引用没问题 +class Cache: + def __init__(self): + self.cache = {} + + def get_or_compute(self, key, compute_fn): + if key not in self.cache: + self.cache[key] = compute_fn() + return self.cache[key] + +cache = Cache() +result = cache.get_or_compute("expensive", lambda: sum(range(1000))) +``` + +```rust !! rs +// Rust: 不要存储对临时数据的引用 +struct Cache<'a> { + cache: std::collections::HashMap, +} + +impl<'a> Cache<'a> { + fn get_or_compute(&mut self, key: &str, compute_fn: F) -> &'a str + where + F: FnOnce() -> &'a str, + { + if !self.cache.contains_key(key) { + self.cache.insert(key.to_string(), compute_fn()); + } + self.cache.get(key).unwrap() + } +} + +// 更好: 使用拥有的数据 +struct Cache { + cache: std::collections::HashMap, +} + +impl Cache { + fn get_or_compute(&mut self, key: &str, compute_fn: F) -> String + where + F: FnOnce() -> String, + { + if !self.cache.contains_key(key) { + self.cache.insert(key.to_string(), compute_fn()); + } + self.cache.get(key).unwrap().clone() + } +} +``` + + +### 陷阱 2: 过度使用 'static + + +```python !! py +# Python: 普通引用工作正常 +def process(data: str) -> str: + return data.upper() +``` + +```rust !! rs +// Rust: 除非必要,否则不要使用 'static +// 太 restrictive: +// fn process_static(data: &'static str) -> &'static str { +// data.to_uppercase().leak() // 坏: 内存泄漏! +// } + +// 更好 - 让编译器推断生命周期: +fn process(data: &str) -> String { + data.to_uppercase() // 返回拥有的 String +} + +// 最好 - 使用生命周期省略: +fn process_ref(data: &str) -> String { + data.to_uppercase() +} +``` + + +### 陷阱 3: 自引用结构体 + + +```python !! py +# Python: 自引用很容易 +class TreeNode: + def __init__(self, value): + self.value = value + self.children = [] + + def add_child(self, child): + self.children.append(child) +``` + +```rust !! rs +// Rust: 自引用需要特殊处理 +// 这行不通: +// struct TreeNode<'a> { +// value: String, +// children: Vec<&'a TreeNode<'a>>, // 有问题! +// } + +// 更好的方法 - 使用索引或 Rc +use std::rc::Rc; + +struct TreeNode { + value: String, + children: Vec>, +} + +impl TreeNode { + fn new(value: String) -> Self { + TreeNode { + value, + children: Vec::new(), + } + } + + fn add_child(&mut self, child: Rc) { + self.children.push(child); + } +} +``` + + +## 最佳实践 + +### 应该做: + +1. **让编译器帮助你** - 信任 Rust 的生命周期推断 +2. **从省略的生命周期开始** - 只在需要时添加显式注解 +3. **保持生命周期简短** - 最小化引用的范围 +4. **不确定时使用拥有的数据** - 适当时移动而不是借用 +5. **阅读错误消息** - Rust 的编译器提供出色的生命周期诊断 + +### 不应该做: + +1. **添加不必要的 'static** - 仅在数据真正永远存在时使用 +2. **使用普通引用创建自引用** - 使用 Rc、Arc 或索引 +3. **试图智胜借用检查器** - 它正在防止真正的错误 +4. **使用 unsafe 绕过生命周期** - 除非绝对必要 +5. **忽略生命周期警告** - 它们试图帮助你 + +## 总结 + +- **生命周期**是确保引用有效的编译时检查 +- **生命周期注解**描述引用之间的关系 +- **省略规则**让你在常见情况下省略注解 +- **'static 生命周期**意味着"在整个程序期间有效" +- **Rust 的生命周期**在没有运行时开销的情况下提供内存安全 +- **Python 的引用计数**以运行时成本提供安全性 +- **Rust 以复杂性换取性能和安全性** + +## 练习 + +1. 编写一个函数,接受两个字符串切片并返回较长的一个 +2. 创建一个结构体,持有对配置和数据的引用 +3. 实现一个具有适当生命周期管理的缓存系统 +4. 重构代码,在可能的情况下使用生命周期省略 +5. 比较使用拥有数据的递归函数与引用的性能 + +## 下一模块 + +在下一个模块中,我们将探索**智能指针**(Box、Rc、Arc、RefCell)以及它们如何与 Python 的内存模型进行比较。你将学习何时使用每种类型,以及它们如何启用共享所有权和内部可变性等模式。 diff --git a/content/docs/py2rust/module-11-lifetimes.zh-tw.mdx b/content/docs/py2rust/module-11-lifetimes.zh-tw.mdx new file mode 100644 index 0000000..f8a45a8 --- /dev/null +++ b/content/docs/py2rust/module-11-lifetimes.zh-tw.mdx @@ -0,0 +1,889 @@ +--- +title: "模組 11: 生命週期 - 無需垃圾回收的記憶體安全" +description: "理解 Rust 的生命週期系統,它如何在不需垃圾回收器的情況下確保記憶體安全,以及它與 Python 引用計數的區別。" +--- + +# 模組 11: 生命週期 - 無需垃圾回收的記憶體安全 + +在本模組中,你將學習 Rust 最獨特的功能之一:**生命週期**。生命週期是 Rust 在沒有垃圾回收器的情況下實現記憶體安全的方式,這與 Python 的引用計數方法有本質區別。 + +## 學習目標 + +完成本模組後,你將能夠: +- 理解什麼是生命週期以及為什麼 Rust 需要它們 +- 在函數簽名中使用生命週期註解 +- 理解生命週期省略規則 +- 使用 `'static` 生命週期 +- 比較 Rust 的所有權模型與 Python 的引用計數 + +## 背景:Python vs Rust 記憶體管理 + +### Python 的記憶體模型 + +Python 使用**引用計數**和循環檢測垃圾回收器: + +```python +# Python: 通過引用計數進行自動記憶體管理 +def process_data(data): + # data 是一個引用,引用計數增加 + result = transform(data) + # 當函數返回時,引用計數減少 + return result + +# 當引用計數達到 0 時,對象被釋放 +# 垃圾回收器處理引用循環 +``` + +### Rust 的記憶體模型 + +Rust 使用**所有權**和**生命週期**進行編譯時記憶體管理: + +```rust +// Rust: 使用生命週期的編譯時記憶體管理 +fn process_data<'a>(data: &'a str) -> &'a str { + // 編譯器驗證返回的引用 + // 與輸入引用一樣有效 + let result = transform(data); + result +} +// 記憶體在所有者離開作用域時被釋放 +// 不需要垃圾回收器! +``` + +## 什麼是生命週期? + +生命週期是 Rust 編譯器追蹤**引用有效時間**的方式。它們確保你永遠不會擁有懸垂引用 - 指向已釋放記憶體的引用。 + +### 生命週期解決的問題 + +考慮這個在沒有生命週期檢查的語言中會有危險的例子: + + +```python !! py +# Python: 沒有懸垂引用(GC 防止了這個問題) +def get_reference(): + local = "臨時數據" + return local # 返回對象,而不是記憶體引用 + +# 在 Python 中,對象存活到引用計數為 0 +ref = get_reference() +print(ref) # 工作正常,對象仍然存活 +``` + +```rust !! rs +// Rust: 這不會編譯 - 懸垂引用被阻止 +// fn get_reference() -> &str { // 錯誤: 缺少生命週期 +// let local = String::from("臨時數據"); +// &local // 錯誤: 返回對已釋放數據的引用 +// } // local 在這裡被釋放! + +// Rust 編譯器在編譯時阻止這種情況! +``` + + +## 生命週期註解 + +生命週期註解告訴編譯器**引用之間如何關聯**。它們不會改變引用存活多長時間 - 它們只是描述關係。 + +### 基本生命週期語法 + + +```python !! py +# Python: 不需要生命週期註解 +# 垃圾回收器處理一切 + +def first_word(text: str) -> str: + words = text.split() + return words[0] if words else "" + +# 工作正常 - Python 保持字符串存活 +s = "hello world" +word = first_word(s) +``` + +```rust !! rs +// Rust: 顯式生命週期註解 +// 'a 生命週期參數連接輸入和輸出 +fn first_word<'a>(text: &'a str) -> &'a str { + // 返回的引用只要輸入引用有效就有效 + match text.split(' ').next() { + Some(word) => word, + None => "", + } +} + +// 編譯器驗證 'a 是否有意義 +let s = String::from("hello world"); +let word = first_word(&s); +// word 只要 s 有效就有效 +``` + + +### 結構體中的生命週期參數 + +當結構體持有引用時,必須指定生命週期: + + +```python !! py +# Python: 類可以自由持有引用 +class Processor: + def __init__(self, data): + self.data = data # 對數據的引用 + +data = [1, 2, 3, 4, 5] +processor = Processor(data) +# 列表在 processor 需要時保持存活 +print(processor.data) +``` + +```rust !! rs +// Rust: 帶有引用的結構體需要生命週期註解 +struct Processor<'a> { + // 這個引用對 'a 生命週期有效 + data: &'a [i32], +} + +impl<'a> Processor<'a> { + fn new(data: &'a [i32]) -> Self { + Processor { data } + } + + fn process(&self) -> i32 { + self.data.iter().sum() + } +} + +let data = vec![1, 2, 3, 4, 5]; +let processor = Processor::new(&data); +// processor 只要 data 有效就有效 +println!("{}", processor.process()); +``` + + +### 多個生命週期參數 + +你可以有多個生命週期參數來顯示不同的關係: + + +```python !! py +# Python: 引用可以有不同的生命週期 +class Pair: + def __init__(self, first, second): + self.first = first + self.second = second + +a = "first" +b = "second" +pair = Pair(a, b) +# 兩個字符串都保持存活 +``` + +```rust !! rs +// Rust: 不同引用的不同生命週期 +struct Pair<'a, 'b> { + first: &'a str, + second: &'b str, +} + +impl<'a, 'b> Pair<'a, 'b> { + fn new(first: &'a str, second: &'b str) -> Self { + Pair { first, second } + } + + fn compare(&self) -> bool { + self.first == self.second + } +} + +let a = String::from("first"); +let b = String::from("second"); + +{ + let b_ref = &b; + let pair = Pair::new(&a, b_ref); + // a 和 b_ref 可以有不同的生命週期 + println!("{}", pair.compare()); +} +``` + + +## 生命週期省略規則 + +Rust 有**生命週期省略規則**,讓你在常見情況下省略生命週期註解。編譯器會為你推斷生命週期。 + +### 規則 1: 輸入生命週期 + +每個引用參數獲得自己的生命週期參數: + + +```python !! py +# Python: 不需要註解 +def single_arg(text: str) -> str: + return text.upper() + +def two_args(a: str, b: str) -> str: + return a + b +``` + +```rust !! rs +// Rust: 規則 1 - 每個引用獲得一個生命週期 +fn single_arg(text: &str) -> &str { // 省略 + // 編譯器推斷: fn single_arg<'a>(text: &'a str) -> &'a str + text.to_uppercase().leak() // 僅用於演示! +} + +fn two_args(a: &str, b: &str) -> &str { // 省略 + // 編譯器推斷: fn two_args<'a, 'b>(a: &'a str, b: &'b str) -> &_ + // 但這無法編譯,返回值需要顯式生命週期 + "沒有顯式生命週期無法實現" +} +``` + + +### 規則 2: 單個輸入生命週期 + +如果只有一個輸入生命週期,它被分配給所有輸出生命週期: + + +```python !! py +# Python: 簡單的引用處理 +def get_first(data: list) -> any: + return data[0] if data else None +``` + +```rust !! rs +// Rust: 規則 2 - 單個輸入生命週期應用於輸出 +fn get_first(data: &[i32]) -> Option<&i32> { // 省略 + // 編譯器推斷: fn get_first<'a>(data: &'a [i32]) -> Option<&'a i32> + data.first() +} + +// 完全註解版本: +fn get_first_explicit<'a>(data: &'a [i32]) -> Option<&'a i32> { + data.first() +} +``` + + +### 規則 3: 帶 &self 或 &mut self 的方法 + +如果有 &self 或 &mut self 參數,它的生命週期被分配給所有輸出生命週期: + + +```python !! py +# Python: 方法不需要生命週期註解 +class Container: + def __init__(self, items): + self.items = items + + def get_first(self): + return self.items[0] if self.items else None + + def get_last(self): + return self.items[-1] if self.items else None +``` + +```rust !! rs +// Rust: 規則 3 - &self 生命週期應用於輸出 +struct Container { + items: Vec, +} + +impl Container { + fn get_first(&self) -> Option<&i32> { // 省略 + // 編譯器推斷: fn get_first<'a>(&'a self) -> Option<&'a i32> + self.items.first() + } + + fn get_last(&self) -> Option<&i32> { // 省略 + self.items.last() + } +} + +// 完全註解版本: +impl Container { + fn get_first_explicit<'a>(&'a self) -> Option<&'a i32> { + self.items.first() + } + + fn get_last_explicit<'a>(&'a self) -> Option<&'a i32> { + self.items.last() + } +} +``` + + +## 何時需要顯式生命週期 + +當省略規則不適用時,你需要顯式生命週期註解,特別是在有多個輸入引用時: + + +```python !! py +# Python: 多個引用沒有問題 +def choose_longer(a: str, b: str) -> str: + return a if len(a) > len(b) else b +``` + +```rust !! rs +// Rust: 多個輸入引用需要顯式生命週期 +fn choose_longer<'a>(a: &'a str, b: &'a str) -> &'a str { + // 兩個輸入必須至少與輸出一樣長 + if a.len() > b.len() { + a + } else { + b + } +} + +// 使用它: +let s1 = String::from("short"); +let s2 = String::from("much longer string"); +let result = choose_longer(&s1, &s2); +println!("{}", result); +``` + + +## 'static 生命週期 + +`'static` 生命週期是一個特殊的生命週期,持續**程式的整個持續時間**。 + + +```python !! py +# Python: 模組級常量永遠存在 +CONFIG = { + "timeout": 30, + "retries": 3 +} + +def get_config(): + return CONFIG # 始終有效 + +# 此外: 字符串字面量被駐留並永遠存在 +message = "hello" # 這個字符串保留在記憶體中 +``` + +```rust !! rs +// Rust: 'static 生命週期 - 存在於整個程式 +// 字符串字面量具有 'static 生命週期 +const CONFIG: &str = "default config"; + +fn get_config() -> &'static str { + CONFIG // OK: CONFIG 具有 'static 生命週期 +} + +// 靜態變量 +static TIMEOUT: u32 = 30; + +fn get_timeout() -> u32 { + TIMEOUT // 始終有效 +} + +// 字符串字面量是 'static +fn get_message() -> &'static str { + "hello" // 這個字符串存儲在二進制文件中 +} + +// 與非靜態的比較 +fn temporary_string() -> String { + String::from("temporary") +} +``` + + +### 何時使用 'static + + +```python !! py +# Python: 全局緩存(永遠存在) +cache = {} + +def memoize(func): + def wrapper(key): + if key not in cache: + cache[key] = func(key) + return cache[key] + return wrapper + +@memoize +def expensive_computation(n): + return sum(range(n)) +``` + +```rust !! rs +// Rust: 使用 'static 進行全局數據 +use std::collections::HashMap; +use std::sync::Mutex; + +// 靜態可變數據需要 Mutex 以確保線程安全 +static CACHE: Mutex> = Mutex::new(HashMap::new()); + +fn memoize_computation(key: &'static str) -> String { + let mut cache = CACHE.lock().unwrap(); + if let Some(value) = cache.get(key) { + value.clone() + } else { + let computed = format!("computed for {}", key); + cache.insert(key, computed.clone()); + computed + } +} + +// 靜態字符串很常見 +fn get_static_str() -> &'static str { + "This lives forever" +} + +// 小心: 不要過度使用 'static +fn example() { + // 不要這樣寫: + // fn unnecessary_static(s: &'static str) -> &'static str { s } + + // 更好 - 讓編譯器推斷最短生命週期 + fn better(s: &str) -> &str { s } +} +``` + + +## 生命週期子類型 + +生命週期支持子類型 - 更長的生命週期可以在需要較短生命週期的地方使用: + + +```python !! py +# Python: 不需要顯式子類型 +def with_string(data: str): + print(data) + +s = "hello" +with_string(s) # 工作正常 +``` + +```rust !! rs +// Rust: 更長的生命週期可用作更短的生命週期 +fn with_short<'a>(s: &'a str) { + println!("{}", s); +} + +fn with_long<'b>(s: &'b str) +where + 'b: 'a, +{ + // 'b 比 'a 長,所以我們可以傳遞給 with_short + with_short(s); +} + +let long_lived = String::from("I live a long time"); +with_long(&long_lived); +``` + + +## 常見生命週期模式 + +### 從函數返回引用 + + +```python !! py +# Python: 返回對輸入部分的引用 +def find_item(items, target): + for item in items: + if item == target: + return item + return None + +items = ["apple", "banana", "cherry"] +found = find_item(items, "banana") +``` + +```rust !! rs +// Rust: 返回對輸入數據的引用 +fn find_item<'a>(items: &'a [String], target: &str) -> Option<&'a String> { + items.iter().find(|item| item == target) +} + +let items = vec![ + String::from("apple"), + String::from("banana"), + String::from("cherry"), +]; + +let found = find_item(&items, "banana"); +// found 只要 items 有效就有效 +``` + + +### 帶有多個引用的結構體 + + +```python !! py +# Python: 帶有多個引用的上下文對象 +class ProcessingContext: + def __init__(self, config, data): + self.config = config + self.data = data + self.timestamp = time.time() + +config = {"timeout": 30} +data = [1, 2, 3] +ctx = ProcessingContext(config, data) +``` + +```rust !! rs +// Rust: 帶有不同生命週期的上下文 +struct ProcessingContext<'a, 'b> { + config: &'a Config, + data: &'b [i32], + timestamp: u64, +} + +struct Config { + timeout: u32, +} + +impl<'a, 'b> ProcessingContext<'a, 'b> { + fn new(config: &'a Config, data: &'b [i32]) -> Self { + ProcessingContext { + config, + data, + timestamp: 0, // 會獲取實際時間 + } + } + + fn process(&self) -> u32 { + self.data.iter().sum::() as u32 * self.config.timeout + } +} + +let config = Config { timeout: 30 }; +let data = vec![1, 2, 3]; +let ctx = ProcessingContext::new(&config, &data); +``` + + +## 生命週期邊界 + +你可以為泛型類型添加生命週期邊界: + + +```python !! py +# Python: 不需要顯式邊界 +class Container: + def __init__(self, value): + self.value = value + +def extract(container): + return container.value +``` + +```rust !! rs +// Rust: 泛型上的生命週期邊界 +struct Container<'a, T: 'a> { + // T 必須比 'a 長命 + value: &'a T, +} + +impl<'a, T: 'a> Container<'a, T> { + fn new(value: &'a T) -> Self { + Container { value } + } + + fn get_value(&self) -> &'a T { + self.value + } +} + +let x = 42; +let container = Container::new(&x); +println!("{}", container.get_value()); +``` + + +## 比較:Python 引用計數 vs Rust 生命週期 + +### 記憶體管理比較 + + +```python !! py +# Python: 運行時引用計數 +import sys + +class Data: + def __init__(self, value): + self.value = value + print(f"Created Data({value}), refcount: {sys.getrefcount(self)}") + + def __del__(self): + print(f"Destroyed Data({self.value})") + +# 引用計數在運行時發生 +def process(): + data = Data(10) # 引用計數增加 + print(f"In function: {sys.getrefcount(data)}") + return data # 引用計數轉移 + +result = process() +print(f"After call: {sys.getrefcount(result)}") +# 當引用計數達到 0 時自動清理 +``` + +```rust !! rs +// Rust: 編譯時生命週期檢查 +struct Data { + value: i32, +} + +impl Data { + fn new(value: i32) -> Self { + println!("Created Data({})", value); + Data { value } + } +} + +impl Drop for Data { + fn drop(&mut self) { + println!("Destroyed Data({})", self.value); + } +} + +fn process() -> Data { + let data = Data::new(10); + data // 所有權移動 +} + +let result = process(); +// result 在作用域結束時自動被釋放 +``` + + +### 性能影響 + + +```python !! py +# Python: 引用計數開銷 +def process_many(): + # 每次賦值都會改變引用計數 + items = [] + for i in range(1_000_000): + items.append(i) # 引用計數操作 + temp = items[-1] # 引用計數操作 + # 更多引用計數操作... + +process_many() # 數百萬次引用計數操作 +``` + +```rust !! rs +// Rust: 零成本生命週期檢查 +fn process_many() { + let mut items = Vec::new(); + for i in 0..1_000_000 { + items.push(i); + let temp = items.last(); + // 沒有運行時開銷 - 生命週期在編譯時檢查 + } +} + +process_many(); // 生命週期沒有運行時成本! +``` + + +### 安全性比較 + + +```python !! py +# Python: 受引用計數保護 +def dangerous(): + local = [1, 2, 3] + return local + +reference = dangerous() +# reference 有效,因為列表仍然存活 +print(reference) # 安全! +``` + +```rust !! rs +// Rust: 受編譯時檢查保護 +// fn dangerous() -> &Vec { +// let local = vec![1, 2, 3]; +// &local // 錯誤: 返回對已釋放值的引用 +// } // local 在這裡被釋放 + +// 正確版本 - 移動所有權 +fn safe() -> Vec { + let local = vec![1, 2, 3]; + local // 所有權移出 +} + +let reference = safe(); +// reference 擁有數據,不可能有懸垂引用 +println!("{:?}", reference); +``` + + +## 常見陷阱及如何避免它們 + +### 陷阱 1: 嘗試存儲臨時引用 + + +```python !! py +# Python: 存儲引用沒問題 +class Cache: + def __init__(self): + self.cache = {} + + def get_or_compute(self, key, compute_fn): + if key not in self.cache: + self.cache[key] = compute_fn() + return self.cache[key] + +cache = Cache() +result = cache.get_or_compute("expensive", lambda: sum(range(1000))) +``` + +```rust !! rs +// Rust: 不要存儲對臨時數據的引用 +struct Cache<'a> { + cache: std::collections::HashMap, +} + +impl<'a> Cache<'a> { + fn get_or_compute(&mut self, key: &str, compute_fn: F) -> &'a str + where + F: FnOnce() -> &'a str, + { + if !self.cache.contains_key(key) { + self.cache.insert(key.to_string(), compute_fn()); + } + self.cache.get(key).unwrap() + } +} + +// 更好: 使用擁有的數據 +struct Cache { + cache: std::collections::HashMap, +} + +impl Cache { + fn get_or_compute(&mut self, key: &str, compute_fn: F) -> String + where + F: FnOnce() -> String, + { + if !self.cache.contains_key(key) { + self.cache.insert(key.to_string(), compute_fn()); + } + self.cache.get(key).unwrap().clone() + } +} +``` + + +### 陷阱 2: 過度使用 'static + + +```python !! py +# Python: 普通引用工作正常 +def process(data: str) -> str: + return data.upper() +``` + +```rust !! rs +// Rust: 除非必要,否則不要使用 'static +// 太嚴格: +// fn process_static(data: &'static str) -> &'static str { +// data.to_uppercase().leak() // 壞: 記憶體洩漏! +// } + +// 更好 - 讓編譯器推斷生命週期: +fn process(data: &str) -> String { + data.to_uppercase() // 返回擁有的 String +} + +// 最好 - 使用生命週期省略: +fn process_ref(data: &str) -> String { + data.to_uppercase() +} +``` + + +### 陷阱 3: 自引用結構體 + + +```python !! py +# Python: 自引用很容易 +class TreeNode: + def __init__(self, value): + self.value = value + self.children = [] + + def add_child(self, child): + self.children.append(child) +``` + +```rust !! rs +// Rust: 自引用需要特殊處理 +// 這行不通: +// struct TreeNode<'a> { +// value: String, +// children: Vec<&'a TreeNode<'a>>, // 有問題! +// } + +// 更好的方法 - 使用索引或 Rc +use std::rc::Rc; + +struct TreeNode { + value: String, + children: Vec>, +} + +impl TreeNode { + fn new(value: String) -> Self { + TreeNode { + value, + children: Vec::new(), + } + } + + fn add_child(&mut self, child: Rc) { + self.children.push(child); + } +} +``` + + +## 最佳實踐 + +### 應該做: + +1. **讓編譯器幫助你** - 信任 Rust 的生命週期推斷 +2. **從省略的生命週期開始** - 只在需要時添加顯式註解 +3. **保持生命週期簡短** - 最小化引用的範圍 +4. **不確定時使用擁有的數據** - 適當時移動而不是借用 +5. **閱讀錯誤消息** - Rust 的編譯器提供出色的生命週期診斷 + +### 不應該做: + +1. **添加不必要的 'static** - 僅在數據真正永遠存在時使用 +2. **使用普通引用創建自引用** - 使用 Rc、Arc 或索引 +3. **試圖智勝借用檢查器** - 它正在防止真正的錯誤 +4. **使用 unsafe 繞過生命週期** - 除非絕對必要 +5. **忽略生命週期警告** - 它們試圖幫助你 + +## 總結 + +- **生命週期**是確保引用有效的編譯時檢查 +- **生命週期註解**描述引用之間的關係 +- **省略規則**讓你在常見情況下省略註解 +- **'static 生命週期**意味著"在整個程式期間有效" +- **Rust 的生命週期**在沒有運行時開銷的情況下提供記憶體安全 +- **Python 的引用計數**以運行時成本提供安全性 +- **Rust 以複雜性換取性能和安全性** + +## 練習 + +1. 編寫一個函數,接受兩個字符串切片並返回較長的一個 +2. 創建一個結構體,持有對配置和數據的引用 +3. 實現一個具有適當生命週期管理的緩存系統 +4. 重構代碼,在可能的情況下使用生命週期省略 +5. 比較使用擁有數據的遞歸函數與引用的性能 + +## 下一模組 + +在下一個模組中,我們將探索**智能指針**(Box、Rc、Arc、RefCell)以及它們如何與 Python 的記憶體模型進行比較。你將學習何時使用每種類型,以及它們如何啟用共享所有權和內部可變性等模式。 diff --git a/content/docs/py2rust/module-12-smart-pointers.mdx b/content/docs/py2rust/module-12-smart-pointers.mdx new file mode 100644 index 0000000..d1f0e2c --- /dev/null +++ b/content/docs/py2rust/module-12-smart-pointers.mdx @@ -0,0 +1,879 @@ +--- +title: "Module 12: Smart Pointers - Advanced Memory Management" +description: "Master Rust's smart pointers (Box, Rc, Arc, RefCell) and understand how they compare to Python's memory management model." +--- + +# Module 12: Smart Pointers - Advanced Memory Management + +In this module, you'll learn about **smart pointers** in Rust. Smart pointers are data structures that act like pointers but have additional metadata and capabilities. They're similar to Python's reference counting, but with explicit control and compile-time guarantees. + +## Learning Objectives + +By the end of this module, you'll be able to: +- Use `Box` for heap allocation +- Use `Rc` for single-threaded reference counting +- Use `Arc` for multi-threaded reference counting +- Use `RefCell` for interior mutability +- Understand when to use each smart pointer type +- Compare Rust's smart pointers with Python's memory model + +## Background: Pointers vs References + +In Rust, there's an important distinction between **references** and **smart pointers**: + + +```python !! py +# Python: All variables are references +x = [1, 2, 3] # x is a reference to a list +y = x # y is another reference to the same list +y.append(4) +print(x) # [1, 2, 3, 4] - both reference the same object +``` + +```rust !! rs +// Rust: References (&) vs Smart Pointers +// References: borrowing, no ownership +let x = vec![1, 2, 3]; +let y: &Vec = &x; // y borrows x +// y cannot modify x (unless &mut) + +// Smart pointers: ownership with extra capabilities +let z: Box> = Box::new(vec![1, 2, 3]); +// z owns the data on the heap +``` + + +## `Box`: Heap Allocation + +`Box` is the simplest smart pointer. It allows you to store data on the heap rather than the stack. + +### When to Use Box + + +```python !! py +# Python: Lists are always heap-allocated +large_data = list(range(1_000_000)) +# The list object is on the heap, items are heap-allocated + +# Recursive data structures work fine +class TreeNode: + def __init__(self, value, left=None, right=None): + self.value = value + self.left = left + self.right = right + +# Python handles this automatically +``` + +```rust !! rs +// Rust: Box for heap allocation +// Use Case 1: Large data on heap +let large_data: Box<[i32]> = (0..1_000_000).collect::>().into_boxed_slice(); +// Data is on the heap, large_data is a stack pointer + +// Use Case 2: Recursive types +enum TreeNode { + Leaf(i32), + Node { + value: i32, + left: Box, // Box required for recursion + right: Box, + }, +} + +let leaf = TreeNode::Leaf(5); +let node = TreeNode::Node { + value: 10, + left: Box::new(leaf), + right: Box::new(TreeNode::Leaf(15)), +}; + +// Use Case 3: Trait objects +trait Drawable { + fn draw(&self); +} + +struct Circle { radius: f64 } +struct Rectangle { width: f64, height: f64 } + +impl Drawable for Circle { + fn draw(&self) { println!("Drawing circle"); } +} + +impl Drawable for Rectangle { + fn draw(&self) { println!("Drawing rectangle"); } +} + +let shapes: Vec> = vec![ + Box::new(Circle { radius: 5.0 }), + Box::new(Rectangle { width: 10.0, height: 20.0 }), +]; + +for shape in shapes.iter() { + shape.draw(); +} +``` + + +### Box Performance + + +```python !! py +# Python: Everything is a reference +# Passing large lists is cheap (just copying the reference) +def process_list(lst): + return sum(lst) + +large = list(range(1_000_000)) +result = process_list(large) # Cheap - just a reference copy +``` + +```rust !! rs +// Rust: Box has a cost (heap allocation) but enables patterns +use std::time::Instant; + +fn process_vec(data: Vec) -> i32 { + data.iter().sum() +} + +fn process_box(data: Box<[i32]>) -> i32 { + data.iter().sum() +} + +let vec_data: Vec = (0..1_000_000).collect(); +let boxed_data: Box<[i32]> = vec_data.into_boxed_slice(); + +// Vec is stack-allocated with heap buffer +let start = Instant::now(); +let _ = process_vec(vec_data); +println!("Vec: {:?}", start.elapsed()); + +// Box is always heap-allocated +let start = Instant::now(); +let _ = process_box(boxed_data); +println!("Box: {:?}", start.elapsed()); +// Box has slight overhead but enables recursive types +``` + + +## `Rc`: Reference Counting + +`Rc` (Reference Counting) provides shared ownership similar to Python's memory model. It allows multiple owners of the same data. + + +```python !! py +# Python: Multiple references to the same object +data = [1, 2, 3] +ref1 = data +ref2 = data +ref3 = data + +# All reference the same list +ref1.append(4) +print(ref2) # [1, 2, 3, 4] +print(ref3) # [1, 2, 3, 4] + +# Python uses reference counting internally +import sys +print(f"refcount: {sys.getrefcount(data)}") # 4 (data + ref1 + ref2 + ref3) +``` + +```rust !! rs +// Rust: Rc for shared ownership (single-threaded) +use std::rc::Rc; + +let data = Rc::new(vec![1, 2, 3]); +let ref1 = Rc::clone(&data); +let ref2 = Rc::clone(&data); +let ref3 = Rc::clone(&data); + +// All reference the same data +println!("refcount: {}", Rc::strong_count(&data)); // 4 + +// Rc doesn't allow mutation through shared references +// For mutation, you need RefCell (covered later) +``` + + +### Rc Reference Counting + + +```python !! py +# Python: Reference counting happens automatically +class Data: + def __init__(self, value): + self.value = value + print(f"Created Data({value})") + + def __del__(self): + print(f"Destroyed Data({self.value})") + +def create_shared(): + local = Data(10) + return local + +# Reference counting tracks usage +shared = create_shared() +another = shared +print(f"refcount: {sys.getrefcount(shared)}") # 3 (shared + another + getrefcount arg) +del another +# Data destroyed when refcount reaches 0 +``` + +```rust !! rs +// Rust: Rc reference counting is explicit +use std::rc::Rc; + +struct Data { + value: i32, +} + +impl Drop for Data { + fn drop(&mut self) { + println!("Destroyed Data({})", self.value); + } +} + +fn create_shared() -> Rc { + let local = Rc::new(Data { value: 10 }); + println!("Created, refcount: {}", Rc::strong_count(&local)); + local +} + +let shared = create_shared(); +println!("After create, refcount: {}", Rc::strong_count(&shared)); + +let another = Rc::clone(&shared); +println!("After clone, refcount: {}", Rc::strong_count(&shared)); + +drop(another); +println!("After drop, refcount: {}", Rc::strong_count(&shared)); +// Data destroyed when refcount reaches 0 +``` + + +### Rc with Cycles + + +```python !! py +# Python: Reference cycles are handled by garbage collector +class Node: + def __init__(self, value): + self.value = value + self.parent = None + self.children = [] + +# Create a cycle +parent = Node(1) +child = Node(2) +parent.children.append(child) +child.parent = parent # Creates a cycle + +# Python's GC will detect and clean this up +``` + +```rust !! rs +// Rust: Rc creates memory leaks with cycles! +use std::rc::Rc; +use std::cell::RefCell; + +struct Node { + value: i32, + parent: RefCell>>, + children: RefCell>>, +} + +// WARNING: This creates a memory leak! +let parent = Rc::new(Node { + value: 1, + parent: RefCell::new(None), + children: RefCell::new(vec![]), +}); + +let child = Rc::new(Node { + value: 2, + parent: RefCell::new(None), + children: RefCell::new(vec![]), +}); + +// Create cycle +parent.children.borrow_mut().push(Rc::clone(&child)); +*child.parent.borrow_mut() = Some(Rc::clone(&parent)); + +// Memory leak! refcounts never reach 0 +// Solution: use Weak for back-references +``` + + +## `Arc`: Atomic Reference Counting + +`Arc` (Atomic Reference Counting) is like `Rc` but thread-safe. Use it when you need shared ownership across threads. + + +```python !! py +# Python: Reference counting is thread-safe (GIL protected) +import threading + +data = [1, 2, 3] + +def worker(): + print(f"Worker sees: {data}") + +thread = threading.Thread(target=worker) +thread.start() +thread.join() +# Python's GIL makes reference counting thread-safe +``` + +```rust !! rs +// Rust: Arc for thread-safe reference counting +use std::sync::{Arc, Mutex}; +use std::thread; + +let data = Arc::new(vec![1, 2, 3]); +let data_clone = Arc::clone(&data); + +let handle = thread::spawn(move || { + println!("Worker sees: {:?}", data_clone); +}); + +handle.join().unwrap(); +// Arc uses atomic operations for thread safety +``` + + +### Arc Performance + + +```python !! py +# Python: GIL makes operations thread-safe but slow +import time +import threading + +counter = [0] + +def increment(): + for _ in range(100_000): + counter[0] += 1 + +threads = [threading.Thread(target=increment) for _ in range(4)] +for t in threads: + t.start() +for t in threads: + t.join() + +print(f"Counter: {counter[0]}") # May not be 400,000 due to GIL contention +``` + +```rust !! rs +// Rust: Arc has overhead but enables true parallelism +use std::sync::{Arc, Mutex}; +use std::thread; + +let counter = Arc::new(Mutex::new(0)); +let mut handles = vec![]; + +for _ in 0..4 { + let counter_clone = Arc::clone(&counter); + let handle = thread::spawn(move || { + for _ in 0..100_000 { + let mut num = counter_clone.lock().unwrap(); + *num += 1; + } + }); + handles.push(handle); +} + +for handle in handles { + handle.join().unwrap(); +} + +println!("Counter: {}", *counter.lock().unwrap()); +// Correctly 400,000 - true parallelism +``` + + +## `RefCell`: Interior Mutability + +`RefCell` allows you to mutate data through immutable references. This is called **interior mutability**. + + +```python !! py +# Python: Objects are always mutable +class Container: + def __init__(self, value): + self.value = value + +container = Container(10) +container.value = 20 # Mutation is always allowed + +# Even through "immutable" references +def modify(cont): + cont.value = 30 # Works fine + +modify(container) +print(container.value) # 30 +``` + +```rust !! rs +// Rust: RefCell enables interior mutability +use std::cell::RefCell; + +struct Container { + value: RefCell, // Interior mutability +} + +let container = Container { + value: RefCell::new(10), +}; + +*container.value.borrow_mut() += 20; +println!("Value: {}", container.value.borrow()); + +// RefCell enforces borrowing rules at runtime +// Multiple immutable borrows OR one mutable borrow +let borrow1 = container.value.borrow(); +let borrow2 = container.value.borrow(); // OK: multiple immutable +println!("Borrow1: {}, Borrow2: {}", borrow1, borrow2); + +drop(borrow1); +drop(borrow2); + +let mut borrow3 = container.value.borrow_mut(); // OK: mutable after drops +*borrow3 += 10; +println!("After mutation: {}", borrow3); +``` + + +### RefCell Borrowing Rules + + +```python !! py +# Python: No borrowing rules +data = [1, 2, 3] + +iterator = iter(data) +data.append(4) # Modifying while iterating + +try: + for item in iterator: + print(item) +except RuntimeError as e: + print(f"Error: {e}") # Runtime error +``` + +```rust !! rs +// Rust: RefCell checks borrowing rules at runtime +use std::cell::RefCell; + +let data = RefCell::new(vec![1, 2, 3]); + +let borrow1 = data.borrow(); +// let borrow2 = data.borrow_mut(); // PANIC! Already immutably borrowed + +println!("First borrow: {:?}", borrow1); +drop(borrow1); + +let mut borrow2 = data.borrow_mut(); // OK: after drop +borrow2.push(4); +println!("After mutation: {:?}", borrow2); +``` + + +### Rc + RefCell Pattern + + +```python !! py +# Python: Shared mutable state is easy +class SharedData: + def __init__(self): + self.items = [] + +def add_item(shared, item): + shared.items.append(item) + +data = SharedData() +add_item(data, 1) +add_item(data, 2) +print(data.items) # [1, 2] +``` + +```rust !! rs +// Rust: Rc> for shared mutable state +use std::rc::Rc; +use std::cell::RefCell; + +struct SharedData { + items: RefCell>, +} + +fn add_item(shared: &Rc, item: i32) { + shared.items.borrow_mut().push(item); +} + +let data = Rc::new(SharedData { + items: RefCell::new(vec![]), +}); + +add_item(&data, 1); +add_item(&data, 2); +println!("Items: {:?}", data.items.borrow()); +``` + + +## Comparison Table: Smart Pointer Types + +| Smart Pointer | Thread Safe | Allows Mutation | Use Case | Python Equivalent | +|--------------|-------------|------------------|----------|-------------------| +| `Box` | Yes | Through `&mut` | Heap allocation, recursion | No direct equivalent | +| `Rc` | No | With `RefCell` | Shared ownership (single-threaded) | Standard references | +| `Arc` | Yes | With `Mutex` | Shared ownership (multi-threaded) | Thread-safe references | +| `RefCell` | N/A | Yes (runtime checks) | Interior mutability | All Python objects | +| `Mutex` | Yes | Yes (runtime checks) | Thread-safe mutable state | threading.Lock | + +## Common Patterns + +### Graph Structures + + +```python !! py +# Python: Graph is straightforward +class Node: + def __init__(self, value): + self.value = value + self.edges = [] + +node1 = Node(1) +node2 = Node(2) +node1.edges.append(node2) +node2.edges.append(node1) # Bidirectional +``` + +```rust !! rs +// Rust: Graph with Rc> +use std::rc::Rc; +use std::cell::RefCell; + +struct Node { + value: i32, + edges: RefCell>>, +} + +impl Node { + fn new(value: i32) -> Rc { + Rc::new(Node { + value, + edges: RefCell::new(vec![]), + }) + } + + fn add_edge(&self, other: &Rc) { + self.edges.borrow_mut().push(Rc::clone(other)); + } +} + +let node1 = Node::new(1); +let node2 = Node::new(2); + +node1.add_edge(&node2); +node2.add_edge(&node1); + +println!("Node1 has {} edges", node1.edges.borrow().len()); +println!("Node2 has {} edges", node2.edges.borrow().len()); +``` + + +### Immutable Data, Mutable View + + +```python !! py +# Python: All data is mutable by default +class Config: + def __init__(self): + self.settings = {} + self.cache = {} + +def update_cache(config): + config.cache["key"] = "value" + +config = Config() +update_cache(config) +``` + +```rust !! rs +// Rust: Immutable struct with mutable field +use std::cell::RefCell; + +struct Config { + settings: Vec, // Immutable + cache: RefCell>, // Mutable +} + +fn update_cache(config: &Config) { + config.cache.borrow_mut().insert("key".to_string(), "value".to_string()); +} + +let config = Config { + settings: vec!["setting1".to_string()], + cache: RefCell::new(std::collections::HashMap::new()), +}; + +update_cache(&config); +println!("Cache: {:?}", config.cache.borrow()); +``` + + +## Memory Management Comparison + + +```python !! py +# Python: Automatic memory management +import sys +import gc + +class Data: + def __init__(self, value): + self.value = value + +# Reference counting +a = Data(1) +b = a +print(f"refcount: {sys.getrefcount(a)}") # 3 + +# Cycle detection +x = Data(10) +x.self_ref = x # Create cycle +del x +gc.collect() # Clean up cycles + +# Memory is freed automatically +``` + +```rust !! rs +// Rust: Explicit ownership with smart pointers +use std::rc::{Rc, Weak}; +use std::cell::RefCell; + +struct Data { + value: i32, +} + +// Reference counting with Rc +let a = Rc::new(Data { value: 1 }); +let b = Rc::clone(&a); +println!("refcount: {}", Rc::strong_count(&a)); // 2 + +// Breaking cycles with Weak +struct Node { + value: i32, + parent: RefCell>, // Weak doesn't increment refcount + children: RefCell>>, +} + +let parent = Rc::new(Node { + value: 1, + parent: RefCell::new(Weak::new()), + children: RefCell::new(vec![]), +}); + +let child = Rc::new(Node { + value: 2, + parent: RefCell::new(Weak::new()), + children: RefCell::new(vec![]), +}); + +// Use Weak for back-references to avoid cycles +parent.children.borrow_mut().push(Rc::clone(&child)); +*child.parent.borrow_mut() = Rc::downgrade(&parent); // Weak reference + +// No memory leak! +``` + + +## Performance Considerations + + +```python !! py +# Python: Reference counting overhead +import time + +def many_references(): + data = list(range(1000)) + refs = [] + for _ in range(100_000): + refs.append(data) # Refcount changes each time + +start = time.time() +many_references() +print(f"Python: {time.time() - start:.3}s") +``` + +```rust !! rs +// Rust: Smart pointer overhead +use std::rc::Rc; +use std::time::Instant; + +fn many_references() { + let data = Rc::new((0..1000).collect::>()); + let mut refs = Vec::new(); + for _ in 0..100_000 { + refs.push(Rc::clone(&data)); // Atomic increment + } +} + +let start = Instant::now(); +many_references(); +println!("Rc: {:?}", start.elapsed()); +// Rust is faster but still has overhead +``` + + +## Common Pitfalls + +### Pitfall 1: Reference Cycles with Rc + + +```python !! py +# Python: GC handles cycles +class Node: + def __init__(self, value): + self.value = value + self.other = None + +a = Node(1) +b = Node(2) +a.other = b +b.other = a # Cycle +# GC will clean this up +``` + +```rust !! rs +// Rust: Use Weak to break cycles +use std::rc::{Rc, Weak}; +use std::cell::RefCell; + +struct Node { + value: i32, + other: RefCell>, // Use Weak for one direction +} + +let a = Rc::new(Node { + value: 1, + other: RefCell::new(Weak::new()), +}); + +let b = Rc::new(Node { + value: 2, + other: RefCell::new(Weak::new()), +}); + +*a.other.borrow_mut() = Rc::downgrade(&b); +*b.other.borrow_mut() = Rc::downgrade(&a); +// No cycle - can be cleaned up +``` + + +### Pitfall 2: RefCell Runtime Panics + + +```python !! py +# Python: Modifying during iteration causes errors +data = [1, 2, 3] +for item in data: + data.append(item) # RuntimeError: list changed size +``` + +```rust !! rs +// Rust: RefCell panics on borrow violations +use std::cell::RefCell; + +let data = RefCell::new(vec![1, 2, 3]); + +let borrow1 = data.borrow(); +// let mut borrow2 = data.borrow_mut(); // PANIC! + +// Always drop borrows before getting mutable borrow +drop(borrow1); +let mut borrow2 = data.borrow_mut(); // OK +borrow2.push(4); +``` + + +### Pitfall 3: Using Rc Across Threads + + +```python !! py +# Python: GIL makes everything thread-safe +import threading + +data = [1, 2, 3] + +def worker(): + print(data) # Safe + +thread = threading.Thread(target=worker) +thread.start() +``` + +```rust !! rs +// Rust: Rc is NOT thread-safe! +use std::rc::Rc; +use std::sync::Arc; +use std::thread; + +// This won't compile: +// let data = Rc::new(vec![1, 2, 3]); +// let handle = thread::spawn(move || { +// println!("{:?}", data); +// }); + +// Correct: Use Arc for thread safety +let data = Arc::new(vec![1, 2, 3]); +let handle = thread::spawn(move || { + println!("{:?}", data); +}); + +handle.join().unwrap(); +``` + + +## Best Practices + +### DO: + +1. **Use Box for recursion** - Required for recursive data structures +2. **Prefer Rc for single-threaded shared ownership** - Lower overhead than Arc +3. **Use Arc for multi-threaded code** - Required for thread safety +4. **Use RefCell sparingly** - Only when interior mutability is needed +5. **Break cycles with Weak** - Prevent memory leaks +6. **Drop borrows explicitly** - Avoid RefCell panics + +### DON'T: + +1. **Use Rc across threads** - Use Arc instead +2. **Create reference cycles** - Use Weak for back-references +3. **Overuse RefCell** - Prefer normal borrowing when possible +4. **Ignore RefCell panics** - They indicate logical errors +5. **Use Arc when Rc suffices** - Unnecessary overhead + +## Summary + +- **`Box`** provides heap allocation and enables recursion +- **`Rc`** provides shared ownership for single-threaded code +- **`Arc`** provides thread-safe shared ownership +- **`RefCell`** enables interior mutability with runtime checks +- **Rust's smart pointers** give explicit control over memory +- **Python's references** are automatic but have runtime overhead +- **Choose the right tool** based on ownership and threading needs + +## Practice Exercises + +1. Create a binary tree using Box for recursive structure +2. Implement a shared cache using `Rc>` +3. Build a thread-safe counter using `Arc>` +4. Create a graph structure using Rc and Weak to avoid cycles +5. Compare performance of Box vs Rc vs Arc for your use case + +## Next Module + +In the next module, we'll explore **modules and packages** in Rust, including the module system, use declarations, Cargo.toml, and publishing to crates.io. You'll learn how to organize code and manage dependencies. diff --git a/content/docs/py2rust/module-12-smart-pointers.zh-cn.mdx b/content/docs/py2rust/module-12-smart-pointers.zh-cn.mdx new file mode 100644 index 0000000..daee245 --- /dev/null +++ b/content/docs/py2rust/module-12-smart-pointers.zh-cn.mdx @@ -0,0 +1,879 @@ +--- +title: "模块 12: 智能指针 - 高级内存管理" +description: "掌握 Rust 的智能指针(Box、Rc、Arc、RefCell)并理解它们如何与 Python 的内存管理模型进行比较。" +--- + +# 模块 12: 智能指针 - 高级内存管理 + +在本模块中,你将学习 Rust 中的**智能指针**。智能指针是行为类似指针但具有额外元数据和功能的数据结构。它们类似于 Python 的引用计数,但具有显式控制和编译时保证。 + +## 学习目标 + +完成本模块后,你将能够: +- 使用 `Box` 进行堆分配 +- 使用 `Rc` 进行单线程引用计数 +- 使用 `Arc` 进行多线程引用计数 +- 使用 `RefCell` 实现内部可变性 +- 理解何时使用每种智能指针类型 +- 比较 Rust 的智能指针与 Python 的内存模型 + +## 背景:指针 vs 引用 + +在 Rust 中,**引用**和**智能指针**有重要区别: + + +```python !! py +# Python: 所有变量都是引用 +x = [1, 2, 3] # x 是对列表的引用 +y = x # y 是对同一列表的另一个引用 +y.append(4) +print(x) # [1, 2, 3, 4] - 两者引用同一个对象 +``` + +```rust !! rs +// Rust: 引用(&) vs 智能指针 +// 引用:借用,无所有权 +let x = vec![1, 2, 3]; +let y: &Vec = &x; // y 借用 x +// y 不能修改 x(除非是 &mut) + +// 智能指针:具有额外功能的所有权 +let z: Box> = Box::new(vec![1, 2, 3]); +// z 拥有堆上的数据 +``` + + +## `Box`: 堆分配 + +`Box` 是最简单的智能指针。它允许你在堆而不是栈上存储数据。 + +### 何时使用 Box + + +```python !! py +# Python: 列表总是在堆上分配 +large_data = list(range(1_000_000)) +# 列表对象在堆上,元素也是堆分配的 + +# 递归数据结构工作正常 +class TreeNode: + def __init__(self, value, left=None, right=None): + self.value = value + self.left = left + self.right = right + +# Python 自动处理这个问题 +``` + +```rust !! rs +// Rust: Box 用于堆分配 +// 使用场景 1: 堆上的大数据 +let large_data: Box<[i32]> = (0..1_000_000).collect::>().into_boxed_slice(); +// 数据在堆上,large_data 是栈指针 + +// 使用场景 2: 递归类型 +enum TreeNode { + Leaf(i32), + Node { + value: i32, + left: Box, // 递归需要 Box + right: Box, + }, +} + +let leaf = TreeNode::Leaf(5); +let node = TreeNode::Node { + value: 10, + left: Box::new(leaf), + right: Box::new(TreeNode::Leaf(15)), +}; + +// 使用场景 3: trait 对象 +trait Drawable { + fn draw(&self); +} + +struct Circle { radius: f64 } +struct Rectangle { width: f64, height: f64 } + +impl Drawable for Circle { + fn draw(&self) { println!("Drawing circle"); } +} + +impl Drawable for Rectangle { + fn draw(&self) { println!("Drawing rectangle"); } +} + +let shapes: Vec> = vec![ + Box::new(Circle { radius: 5.0 }), + Box::new(Rectangle { width: 10.0, height: 20.0 }), +]; + +for shape in shapes.iter() { + shape.draw(); +} +``` + + +### Box 性能 + + +```python !! py +# Python: 一切都是引用 +# 传递大列表很便宜(只是复制引用) +def process_list(lst): + return sum(lst) + +large = list(range(1_000_000)) +result = process_list(large) # 便宜 - 只是引用复制 +``` + +```rust !! rs +// Rust: Box 有成本(堆分配)但启用模式 +use std::time::Instant; + +fn process_vec(data: Vec) -> i32 { + data.iter().sum() +} + +fn process_box(data: Box<[i32]>) -> i32 { + data.iter().sum() +} + +let vec_data: Vec = (0..1_000_000).collect(); +let boxed_data: Box<[i32]> = vec_data.into_boxed_slice(); + +// Vec 是栈分配的,带有堆缓冲区 +let start = Instant::now(); +let _ = process_vec(vec_data); +println!("Vec: {:?}", start.elapsed()); + +// Box 总是堆分配的 +let start = Instant::now(); +let _ = process_box(boxed_data); +println!("Box: {:?}", start.elapsed()); +// Box 有轻微开销但启用递归类型 +``` + + +## `Rc`: 引用计数 + +`Rc`(Reference Counting)提供类似于 Python 内存模型的共享所有权。它允许多个所有者拥有相同的数据。 + + +```python !! py +# Python: 同一对象的多个引用 +data = [1, 2, 3] +ref1 = data +ref2 = data +ref3 = data + +# 都引用同一个列表 +ref1.append(4) +print(ref2) # [1, 2, 3, 4] +print(ref3) # [1, 2, 3, 4] + +# Python 内部使用引用计数 +import sys +print(f"refcount: {sys.getrefcount(data)}") # 4 (data + ref1 + ref2 + ref3) +``` + +```rust !! rs +// Rust: Rc 用于共享所有权(单线程) +use std::rc::Rc; + +let data = Rc::new(vec![1, 2, 3]); +let ref1 = Rc::clone(&data); +let ref2 = Rc::clone(&data); +let ref3 = Rc::clone(&data); + +// 都引用相同的数据 +println!("refcount: {}", Rc::strong_count(&data)); // 4 + +// Rc 不允许通过共享引用进行修改 +// 要修改,需要 RefCell(稍后介绍) +``` + + +### Rc 引用计数 + + +```python !! py +# Python: 引用计数自动发生 +class Data: + def __init__(self, value): + self.value = value + print(f"Created Data({value})") + + def __del__(self): + print(f"Destroyed Data({self.value})") + +def create_shared(): + local = Data(10) + return local + +# 引用计数跟踪使用情况 +shared = create_shared() +another = shared +print(f"refcount: {sys.getrefcount(shared)}") # 3 (shared + another + getrefcount 参数) +del another +# 引用计数达到 0 时数据被销毁 +``` + +```rust !! rs +// Rust: Rc 引用计数是显式的 +use std::rc::Rc; + +struct Data { + value: i32, +} + +impl Drop for Data { + fn drop(&mut self) { + println!("Destroyed Data({})", self.value); + } +} + +fn create_shared() -> Rc { + let local = Rc::new(Data { value: 10 }); + println!("Created, refcount: {}", Rc::strong_count(&local)); + local +} + +let shared = create_shared(); +println!("After create, refcount: {}", Rc::strong_count(&shared)); + +let another = Rc::clone(&shared); +println!("After clone, refcount: {}", Rc::strong_count(&shared)); + +drop(another); +println!("After drop, refcount: {}", Rc::strong_count(&shared)); +// 引用计数达到 0 时数据被销毁 +``` + + +### Rc 与循环 + + +```python !! py +# Python: 垃圾回收器处理引用循环 +class Node: + def __init__(self, value): + self.value = value + self.parent = None + self.children = [] + +# 创建循环 +parent = Node(1) +child = Node(2) +parent.children.append(child) +child.parent = parent # 创建循环 + +# Python 的 GC 会检测并清理这个 +``` + +```rust !! rs +// Rust: Rc 会因循环而造成内存泄漏! +use std::rc::Rc; +use std::cell::RefCell; + +struct Node { + value: i32, + parent: RefCell>>, + children: RefCell>>, +} + +// 警告: 这会造成内存泄漏! +let parent = Rc::new(Node { + value: 1, + parent: RefCell::new(None), + children: RefCell::new(vec![]), +}); + +let child = Rc::new(Node { + value: 2, + parent: RefCell::new(None), + children: RefCell::new(vec![]), +}); + +// 创建循环 +parent.children.borrow_mut().push(Rc::clone(&child)); +*child.parent.borrow_mut() = Some(Rc::clone(&parent)); + +// 内存泄漏! 引用计数永远不会达到 0 +// 解决方案: 使用 Weak 进行反向引用 +``` + + +## `Arc`: 原子引用计数 + +`Arc`(Atomic Reference Counting)类似于 `Rc`,但是是线程安全的。当你需要跨线程的共享所有权时使用它。 + + +```python !! py +# Python: 引用计数是线程安全的(GIL 保护) +import threading + +data = [1, 2, 3] + +def worker(): + print(f"Worker sees: {data}") + +thread = threading.Thread(target=worker) +thread.start() +thread.join() +# Python 的 GIL 使引用计数线程安全 +``` + +```rust !! rs +// Rust: Arc 用于线程安全的引用计数 +use std::sync::{Arc, Mutex}; +use std::thread; + +let data = Arc::new(vec![1, 2, 3]); +let data_clone = Arc::clone(&data); + +let handle = thread::spawn(move || { + println!("Worker sees: {:?}", data_clone); +}); + +handle.join().unwrap(); +// Arc 使用原子操作确保线程安全 +``` + + +### Arc 性能 + + +```python !! py +# Python: GIL 使操作线程安全但很慢 +import time +import threading + +counter = [0] + +def increment(): + for _ in range(100_000): + counter[0] += 1 + +threads = [threading.Thread(target=increment) for _ in range(4)] +for t in threads: + t.start() +for t in threads: + t.join() + +print(f"Counter: {counter[0]}") # 可能不是 400,000 由于 GIL 争用 +``` + +```rust !! rs +// Rust: Arc 有开销但启用真正的并行 +use std::sync::{Arc, Mutex}; +use std::thread; + +let counter = Arc::new(Mutex::new(0)); +let mut handles = vec![]; + +for _ in 0..4 { + let counter_clone = Arc::clone(&counter); + let handle = thread::spawn(move || { + for _ in 0..100_000 { + let mut num = counter_clone.lock().unwrap(); + *num += 1; + } + }); + handles.push(handle); +} + +for handle in handles { + handle.join().unwrap(); +} + +println!("Counter: {}", *counter.lock().unwrap()); +// 正确是 400,000 - 真正的并行 +``` + + +## `RefCell`: 内部可变性 + +`RefCell` 允许你通过不可变引用修改数据。这被称为**内部可变性**。 + + +```python !! py +# Python: 对象总是可变的 +class Container: + def __init__(self, value): + self.value = value + +container = Container(10) +container.value = 20 # 变更总是被允许的 + +# 即使通过"不可变"引用 +def modify(cont): + cont.value = 30 # 工作正常 + +modify(container) +print(container.value) # 30 +``` + +```rust !! rs +// Rust: RefCell 启用内部可变性 +use std::cell::RefCell; + +struct Container { + value: RefCell, // 内部可变性 +} + +let container = Container { + value: RefCell::new(10), +}; + +*container.value.borrow_mut() += 20; +println!("Value: {}", container.value.borrow()); + +// RefCell 在运行时强制执行借用规则 +// 多个不可变借用 或 一个可变借用 +let borrow1 = container.value.borrow(); +let borrow2 = container.value.borrow(); // OK: 多个不可变 +println!("Borrow1: {}, Borrow2: {}", borrow1, borrow2); + +drop(borrow1); +drop(borrow2); + +let mut borrow3 = container.value.borrow_mut(); // OK: 释放后可变 +*borrow3 += 10; +println!("After mutation: {}", borrow3); +``` + + +### RefCell 借用规则 + + +```python !! py +# Python: 没有借用规则 +data = [1, 2, 3] + +iterator = iter(data) +data.append(4) # 迭代时修改 + +try: + for item in iterator: + print(item) +except RuntimeError as e: + print(f"Error: {e}") # 运行时错误 +``` + +```rust !! rs +// Rust: RefCell 在运行时检查借用规则 +use std::cell::RefCell; + +let data = RefCell::new(vec![1, 2, 3]); + +let borrow1 = data.borrow(); +// let borrow2 = data.borrow_mut(); // 恐慌! 已经被不可变借用 + +println!("First borrow: {:?}", borrow1); +drop(borrow1); + +let mut borrow2 = data.borrow_mut(); // OK: 释放后 +borrow2.push(4); +println!("After mutation: {:?}", borrow2); +``` + + +### Rc + RefCell 模式 + + +```python !! py +# Python: 共享可变状态很容易 +class SharedData: + def __init__(self): + self.items = [] + +def add_item(shared, item): + shared.items.append(item) + +data = SharedData() +add_item(data, 1) +add_item(data, 2) +print(data.items) # [1, 2] +``` + +```rust !! rs +// Rust: Rc> 用于共享可变状态 +use std::rc::Rc; +use std::cell::RefCell; + +struct SharedData { + items: RefCell>, +} + +fn add_item(shared: &Rc, item: i32) { + shared.items.borrow_mut().push(item); +} + +let data = Rc::new(SharedData { + items: RefCell::new(vec![]), +}); + +add_item(&data, 1); +add_item(&data, 2); +println!("Items: {:?}", data.items.borrow()); +``` + + +## 比较表: 智能指针类型 + +| 智能指针 | 线程安全 | 允许变更 | 使用场景 | Python 等价物 | +|---------|---------|---------|---------|--------------| +| `Box` | 是 | 通过 `&mut` | 堆分配、递归 | 无直接等价物 | +| `Rc` | 否 | 与 `RefCell` | 共享所有权(单线程) | 标准引用 | +| `Arc` | 是 | 与 `Mutex` | 共享所有权(多线程) | 线程安全引用 | +| `RefCell` | N/A | 是(运行时检查) | 内部可变性 | 所有 Python 对象 | +| `Mutex` | 是 | 是(运行时检查) | 线程安全可变状态 | threading.Lock | + +## 常见模式 + +### 图结构 + + +```python !! py +# Python: 图很简单 +class Node: + def __init__(self, value): + self.value = value + self.edges = [] + +node1 = Node(1) +node2 = Node(2) +node1.edges.append(node2) +node2.edges.append(node1) # 双向 +``` + +```rust !! rs +// Rust: 使用 Rc> 的图 +use std::rc::Rc; +use std::cell::RefCell; + +struct Node { + value: i32, + edges: RefCell>>, +} + +impl Node { + fn new(value: i32) -> Rc { + Rc::new(Node { + value, + edges: RefCell::new(vec![]), + }) + } + + fn add_edge(&self, other: &Rc) { + self.edges.borrow_mut().push(Rc::clone(other)); + } +} + +let node1 = Node::new(1); +let node2 = Node::new(2); + +node1.add_edge(&node2); +node2.add_edge(&node1); + +println!("Node1 has {} edges", node1.edges.borrow().len()); +println!("Node2 has {} edges", node2.edges.borrow().len()); +``` + + +### 不可变数据,可变视图 + + +```python !! py +# Python: 所有数据默认是可变的 +class Config: + def __init__(self): + self.settings = {} + self.cache = {} + +def update_cache(config): + config.cache["key"] = "value" + +config = Config() +update_cache(config) +``` + +```rust !! rs +// Rust: 带有可变字段的不可变结构体 +use std::cell::RefCell; + +struct Config { + settings: Vec, // 不可变 + cache: RefCell>, // 可变 +} + +fn update_cache(config: &Config) { + config.cache.borrow_mut().insert("key".to_string(), "value".to_string()); +} + +let config = Config { + settings: vec!["setting1".to_string()], + cache: RefCell::new(std::collections::HashMap::new()), +}; + +update_cache(&config); +println!("Cache: {:?}", config.cache.borrow()); +``` + + +## 内存管理比较 + + +```python !! py +# Python: 自动内存管理 +import sys +import gc + +class Data: + def __init__(self, value): + self.value = value + +# 引用计数 +a = Data(1) +b = a +print(f"refcount: {sys.getrefcount(a)}") # 3 + +# 循环检测 +x = Data(10) +x.self_ref = x # 创建循环 +del x +gc.collect() # 清理循环 + +# 内存自动释放 +``` + +```rust !! rs +// Rust: 使用智能指针的显式所有权 +use std::rc::{Rc, Weak}; +use std::cell::RefCell; + +struct Data { + value: i32, +} + +// 使用 Rc 进行引用计数 +let a = Rc::new(Data { value: 1 }); +let b = Rc::clone(&a); +println!("refcount: {}", Rc::strong_count(&a)); // 2 + +// 使用 Weak 打破循环 +struct Node { + value: i32, + parent: RefCell>, // Weak 不增加引用计数 + children: RefCell>>, +} + +let parent = Rc::new(Node { + value: 1, + parent: RefCell::new(Weak::new()), + children: RefCell::new(vec![]), +}); + +let child = Rc::new(Node { + value: 2, + parent: RefCell::new(Weak::new()), + children: RefCell::new(vec![]), +}); + +// 使用 Weak 进行反向引用以避免循环 +parent.children.borrow_mut().push(Rc::clone(&child)); +*child.parent.borrow_mut() = Rc::downgrade(&parent); // 弱引用 + +// 没有内存泄漏! +``` + + +## 性能考虑 + + +```python !! py +# Python: 引用计数开销 +import time + +def many_references(): + data = list(range(1000)) + refs = [] + for _ in range(100_000): + refs.append(data) # 每次都改变引用计数 + +start = time.time() +many_references() +print(f"Python: {time.time() - start:.3}s") +``` + +```rust !! rs +// Rust: 智能指针开销 +use std::rc::Rc; +use std::time::Instant; + +fn many_references() { + let data = Rc::new((0..1000).collect::>()); + let mut refs = Vec::new(); + for _ in 0..100_000 { + refs.push(Rc::clone(&data)); // 原子递增 + } +} + +let start = Instant::now(); +many_references(); +println!("Rc: {:?}", start.elapsed()); +// Rust 更快但仍有开销 +``` + + +## 常见陷阱 + +### 陷阱 1: Rc 的引用循环 + + +```python !! py +# Python: GC 处理循环 +class Node: + def __init__(self, value): + self.value = value + self.other = None + +a = Node(1) +b = Node(2) +a.other = b +b.other = a # 循环 +# GC 会清理这个 +``` + +```rust !! rs +// Rust: 使用 Weak 打破循环 +use std::rc::{Rc, Weak}; +use std::cell::RefCell; + +struct Node { + value: i32, + other: RefCell>, // 一个方向使用 Weak +} + +let a = Rc::new(Node { + value: 1, + other: RefCell::new(Weak::new()), +}); + +let b = Rc::new(Node { + value: 2, + other: RefCell::new(Weak::new()), +}); + +*a.other.borrow_mut() = Rc::downgrade(&b); +*b.other.borrow_mut() = Rc::downgrade(&a); +// 没有循环 - 可以被清理 +``` + + +### 陷阱 2: RefCell 运行时恐慌 + + +```python !! py +# Python: 迭代时修改会导致错误 +data = [1, 2, 3] +for item in data: + data.append(item) # RuntimeError: 列表大小改变 +``` + +```rust !! rs +// Rust: RefCell 在借用违规时恐慌 +use std::cell::RefCell; + +let data = RefCell::new(vec![1, 2, 3]); + +let borrow1 = data.borrow(); +// let mut borrow2 = data.borrow_mut(); // 恐慌! + +// 在获取可变借用之前始终释放借用 +drop(borrow1); +let mut borrow2 = data.borrow_mut(); // OK +borrow2.push(4); +``` + + +### 陷阱 3: 跨线程使用 Rc + + +```python !! py +# Python: GIL 使一切都是线程安全的 +import threading + +data = [1, 2, 3] + +def worker(): + print(data) # 安全 + +thread = threading.Thread(target=worker) +thread.start() +``` + +```rust !! rs +// Rust: Rc 不是线程安全的! +use std::rc::Rc; +use std::sync::Arc; +use std::thread; + +// 这不会编译: +// let data = Rc::new(vec![1, 2, 3]); +// let handle = thread::spawn(move || { +// println!("{:?}", data); +// }); + +// 正确: 使用 Arc 确保线程安全 +let data = Arc::new(vec![1, 2, 3]); +let handle = thread::spawn(move || { + println!("{:?}", data); +}); + +handle.join().unwrap(); +``` + + +## 最佳实践 + +### 应该做: + +1. **使用 Box 进行递归** - 递归数据结构必需 +2. **单线程共享所有权首选 Rc** - 开销比 Arc 低 +3. **多线程代码使用 Arc** - 线程安全必需 +4. **谨慎使用 RefCell** - 仅在需要内部可变性时 +5. **使用 Weak 打破循环** - 防止内存泄漏 +6. **显式释放借用** - 避免 RefCell 恐慌 + +### 不应该做: + +1. **跨线程使用 Rc** - 改用 Arc +2. **创建引用循环** - 使用 Weak 进行反向引用 +3. **过度使用 RefCell** - 可能时优先使用普通借用 +4. **忽略 RefCell 恐慌** - 它们指示逻辑错误 +5. **Rc 足够时使用 Arc** - 不必要的开销 + +## 总结 + +- **`Box`** 提供堆分配并启用递归 +- **`Rc`** 为单线程代码提供共享所有权 +- **`Arc`** 提供线程安全的共享所有权 +- **`RefCell`** 启用具有运行时检查的内部可变性 +- **Rust 的智能指针**提供对内存的显式控制 +- **Python 的引用**是自动的但有运行时开销 +- **根据所有权和线程需求选择合适的工具** + +## 练习 + +1. 使用 Box 创建二叉树作为递归结构 +2. 使用 `Rc>` 实现共享缓存 +3. 使用 `Arc>` 构建线程安全计数器 +4. 使用 Rc 和 Weak 创建图结构以避免循环 +5. 比较你的用例中 Box vs Rc vs Arc 的性能 + +## 下一模块 + +在下一模块中,我们将探索 Rust 中的**模块和包**,包括模块系统、use 声明、Cargo.toml 和发布到 crates.io。你将学习如何组织代码和管理依赖。 diff --git a/content/docs/py2rust/module-12-smart-pointers.zh-tw.mdx b/content/docs/py2rust/module-12-smart-pointers.zh-tw.mdx new file mode 100644 index 0000000..70acf87 --- /dev/null +++ b/content/docs/py2rust/module-12-smart-pointers.zh-tw.mdx @@ -0,0 +1,320 @@ +--- +title: "模組 12: 智能指針 - 高級記憶體管理" +description: "掌握 Rust 的智能指針(Box、Rc、Arc、RefCell)並理解它們如何與 Python 的記憶體管理模型進行比較。" +--- + +# 模組 12: 智能指針 - 高級記憶體管理 + +在本模組中,你將學習 Rust 中的**智能指針**。智能指針是行為類似指針但具有額外元数据和功能的數據結構。它們類似於 Python 的引用計數,但具有顯式控制和編譯時保證。 + +## 學習目標 + +完成本模組後,你將能夠: +- 使用 `Box` 進行堆分配 +- 使用 `Rc` 進行單線程引用計數 +- 使用 `Arc` 進行多線程引用計數 +- 使用 `RefCell` 實現內部可變性 +- 理解何時使用每種智能指針類型 +- 比較 Rust 的智能指針與 Python 的記憶體模型 + +## 背景:指針 vs 引用 + +在 Rust 中,**引用**和**智能指針**有重要區別: + + +```python !! py +# Python: 所有變量都是引用 +x = [1, 2, 3] # x 是對列表的引用 +y = x # y 是對同一列表的另一個引用 +y.append(4) +print(x) # [1, 2, 3, 4] - 兩者引用同一個對象 +``` + +```rust !! rs +// Rust: 引用(&) vs 智能指針 +// 引用:借用,無所有權 +let x = vec![1, 2, 3]; +let y: &Vec = &x; // y 借用 x +// y 不能修改 x(除非是 &mut) + +// 智能指針:具有額外功能的所有權 +let z: Box> = Box::new(vec![1, 2, 3]); +// z 擁有堆上的數據 +``` + + +## `Box`: 堆分配 + +`Box` 是最簡單的智能指針。它允許你在堆而不是棧上存儲數據。 + +### 何時使用 Box + + +```python !! py +# Python: 列表總是在堆上分配 +large_data = list(range(1_000_000)) +# 列表對象在堆上,元素也是堆分配的 + +# 遞歸數據結構工作正常 +class TreeNode: + def __init__(self, value, left=None, right=None): + self.value = value + self.left = left + self.right = right + +# Python 自動處理這個問題 +``` + +```rust !! rs +// Rust: Box 用於堆分配 +// 使用場景 1: 堆上的大數據 +let large_data: Box<[i32]> = (0..1_000_000).collect::>().into_boxed_slice(); +// 數據在堆上, large_data 是棧指針 + +// 使用場景 2: 遞歸類型 +enum TreeNode { + Leaf(i32), + Node { + value: i32, + left: Box, // 遞歸需要 Box + right: Box, + }, +} + +let leaf = TreeNode::Leaf(5); +let node = TreeNode::Node { + value: 10, + left: Box::new(leaf), + right: Box::new(TreeNode::Leaf(15)), +}; + +// 使用場景 3: trait 對象 +trait Drawable { + fn draw(&self); +} + +struct Circle { radius: f64 } +struct Rectangle { width: f64, height: f64 } + +impl Drawable for Circle { + fn draw(&self) { println!("Drawing circle"); } +} + +impl Drawable for Rectangle { + fn draw(&self) { println!("Drawing rectangle"); } +} + +let shapes: Vec> = vec![ + Box::new(Circle { radius: 5.0 }), + Box::new(Rectangle { width: 10.0, height: 20.0 }), +]; + +for shape in shapes.iter() { + shape.draw(); +} +``` + + +## `Rc`: 引用計數 + +`Rc`(Reference Counting)提供類似於 Python 記憶體模型的共享所有權。它允許多個所有者擁有相同的數據。 + + +```python !! py +# Python: 同一對象的多個引用 +data = [1, 2, 3] +ref1 = data +ref2 = data +ref3 = data + +# 都引用同一個列表 +ref1.append(4) +print(ref2) # [1, 2, 3, 4] +print(ref3) # [1, 2, 3, 4] + +# Python 內部使用引用計數 +import sys +print(f"refcount: {sys.getrefcount(data)}") # 4 +``` + +```rust !! rs +// Rust: Rc 用於共享所有權(單線程) +use std::rc::Rc; + +let data = Rc::new(vec![1, 2, 3]); +let ref1 = Rc::clone(&data); +let ref2 = Rc::clone(&data); +let ref3 = Rc::clone(&data); + +println!("refcount: {}", Rc::strong_count(&data)); // 4 +``` + + +## `Arc`: 原子引用計數 + +`Arc` 是線程安全的引用計數智能指針,用於多線程環境。 + + +```python !! py +# Python: GIL 使引用計數線程安全 +import threading + +data = [1, 2, 3] + +def worker(): + print(f"Worker sees: {data}") + +thread = threading.Thread(target=worker) +thread.start() +thread.join() +``` + +```rust !! rs +// Rust: Arc 用於線程安全的引用計數 +use std::sync::{Arc, Mutex}; +use std::thread; + +let data = Arc::new(vec![1, 2, 3]); +let data_clone = Arc::clone(&data); + +let handle = thread::spawn(move || { + println!("Worker sees: {:?}", data_clone); +}); + +handle.join().unwrap(); +``` + + +## `RefCell`: 內部可變性 + +`RefCell` 允許通過不可變引用修改數據,稱為**內部可變性**。 + + +```python !! py +# Python: 對象總是可變的 +class Container: + def __init__(self, value): + self.value = value + +container = Container(10) +container.value = 20 # 變更總是被允許的 +``` + +```rust !! rs +// Rust: RefCell 啟用內部可變性 +use std::cell::RefCell; + +struct Container { + value: RefCell, +} + +let container = Container { + value: RefCell::new(10), +}; + +*container.value.borrow_mut() += 20; +println!("Value: {}", container.value.borrow()); +``` + + +## 比較表: 智能指針類型 + +| 智能指針 | 線程安全 | 允許變更 | 使用場景 | Python 等價物 | +|---------|---------|---------|---------|--------------| +| `Box` | 是 | 通過 `&mut` | 堆分配、遞歸 | 無直接等價物 | +| `Rc` | 否 | 與 `RefCell` | 共享所有權(單線程) | 標準引用 | +| `Arc` | 是 | 與 `Mutex` | 共享所有權(多線程) | 線程安全引用 | +| `RefCell` | N/A | 是(運行時檢查) | 內部可變性 | 所有 Python 對象 | +| `Mutex` | 是 | 是(運行時檢查) | 線程安全可變狀態 | threading.Lock | + +## 常見模式 + +### 圖結構 + + +```python !! py +# Python: 圖很簡單 +class Node: + def __init__(self, value): + self.value = value + self.edges = [] + +node1 = Node(1) +node2 = Node(2) +node1.edges.append(node2) +node2.edges.append(node1) +``` + +```rust !! rs +// Rust: 使用 Rc> 的圖 +use std::rc::Rc; +use std::cell::RefCell; + +struct Node { + value: i32, + edges: RefCell>>, +} + +impl Node { + fn new(value: i32) -> Rc { + Rc::new(Node { + value, + edges: RefCell::new(vec![]), + }) + } + + fn add_edge(&self, other: &Rc) { + self.edges.borrow_mut().push(Rc::clone(other)); + } +} + +let node1 = Node::new(1); +let node2 = Node::new(2); + +node1.add_edge(&node2); +node2.add_edge(&node1); + +println!("Node1 has {} edges", node1.edges.borrow().len()); +println!("Node2 has {} edges", node2.edges.borrow().len()); +``` + + +## 最佳實踐 + +### 應該做: + +1. **使用 Box 進行遞歸** - 遞歸數據結構必需 +2. **單線程共享所有權首選 Rc** - 開銷比 Arc 低 +3. **多線程代碼使用 Arc** - 線程安全必需 +4. **謹慎使用 RefCell** - 僅在需要內部可變性時 +5. **使用 Weak 打破循環** - 防止記憶體泄漏 + +### 不應該做: + +1. **跨線程使用 Rc** - 改用 Arc +2. **創建引用循環** - 使用 Weak 進行反向引用 +3. **過度使用 RefCell** - 可能時優先使用普通借用 +4. **忽略 RefCell 恐慌** - 它們指示邏輯錯誤 +5. **Rc 足夠時使用 Arc** - 不必要的開銷 + +## 總結 + +- **`Box`** 提供堆分配並啟用遞歸 +- **`Rc`** 為單線程代碼提供共享所有權 +- **`Arc`** 提供線程安全的共享所有權 +- **`RefCell`** 啟用具有運行時檢查的內部可變性 +- **Rust 的智能指針**提供對記憶體的顯式控制 +- **Python 的引用**是自動的但有運行時開銷 +- **根據所有權和線程需求選擇合適的工具** + +## 練習 + +1. 使用 Box 創建二叉樹作為遞歸結構 +2. 使用 `Rc>` 實現共享緩存 +3. 使用 `Arc>` 構建線程安全計數器 +4. 使用 Rc 和 Weak 創建圖結構以避免循環 +5. 比較你的用例中 Box vs Rc vs Arc 的性能 + +## 下一模組 + +在下一模組中,我們將探索 Rust 中的**模組和包**,包括模組系統、use 聲明、Cargo.toml 和發布到 crates.io。你將學習如何組織代碼和管理依賴。 diff --git a/content/docs/py2rust/module-13-modules-packages.mdx b/content/docs/py2rust/module-13-modules-packages.mdx new file mode 100644 index 0000000..39b2c37 --- /dev/null +++ b/content/docs/py2rust/module-13-modules-packages.mdx @@ -0,0 +1,723 @@ +--- +title: "Module 13: Modules & Packages - Code Organization" +description: "Learn Rust's module system, how to organize code with Cargo.toml, and publish packages to crates.io compared to Python's packaging ecosystem." +--- + +# Module 13: Modules & Packages - Code Organization + +In this module, you'll learn how Rust organizes code into **modules** and **packages**, and how this compares to Python's import system and package management. + +## Learning Objectives + +By the end of this module, you'll be able to: +- Understand Rust's module system +- Use `mod`, `use`, and `pub` keywords +- Organize code with file hierarchies +- Work with Cargo.toml for dependency management +- Publish and consume packages from crates.io +- Compare Rust's packaging with Python's setuptools/PyPI + +## Module System Basics + +Rust's module system provides **namespaces** and **access control**. Unlike Python's implicit module system, Rust's is explicit and file-based. + +### Defining Modules + + +```python !! py +# Python: Modules are files themselves +# math_utils.py +def add(a, b): + return a + b + +class Calculator: + def multiply(self, a, b): + return a * b + +PI = 3.14159 + +# Use it: +# from math_utils import add, Calculator, PI +# result = add(1, 2) +``` + +```rust !! rs +// Rust: Modules are explicitly declared +// math_utils.rs +pub fn add(a: i32, b: i32) -> i32 { + a + b +} + +pub struct Calculator { + pub value: i32, +} + +impl Calculator { + pub fn multiply(&self, a: i32, b: i32) -> i32 { + a * b + } +} + +pub const PI: f64 = 3.14159; + +// In main.rs or lib.rs: +mod math_utils; // Declare the module + +fn main() { + let result = math_utils::add(1, 2); + println!("Result: {}", result); +} +``` + + +### Inline Modules + + +```python !! py +# Python: No inline modules - must be separate files +# Everything is file-based +``` + +```rust !! rs +// Rust: Can define modules inline +mod utils { + pub fn helper() -> String { + String::from("helper function") + } + + mod internal { + pub fn internal_helper() -> String { + String::from("internal") + } + } +} + +fn main() { + println!("{}", utils::helper()); + // println!("{}", utils::internal::internal_helper()); // Error: not public +} +``` + + +## Use Declarations + +The `use` keyword brings items into scope, similar to Python's `import`. + + +```python !! py +# Python: Import statements +import os +from collections import defaultdict +from math import pi as PI +from mymodule import MyClass, my_function + +# Direct use +path = os.path.join("a", "b") +d = defaultdict(int) +``` + +```rust !! rs +// Rust: Use declarations +use std::fs; // Like: import fs +use std::collections::HashMap; // Like: from collections import HashMap +use std::io::{self, Write}; // Like: from io import io, Write +use std::fmt::Result as FmtResult; // Like: from fmt import Result as FmtResult + +// Nested paths +use std::{cmp::Ordering, collections::HashMap}; + +// Glob imports (use sparingly!) +use std::prelude::v1::*; // Rust's std library prelude + +fn main() { + let path = fs::File::create("test.txt").unwrap(); +} +``` + + +### Use Conventions + + +```python !! py +# Python: PEP 8 conventions +import os # Module +from collections import defaultdict # Specific import +from pandas import DataFrame as df # Alias + +# Avoid: from module import * (wildcard imports) +``` + +```rust !! rs +// Rust: Idiomatic use +use std::collections::HashMap; // Full path for functions + +use std::io::{self, Result}; // When using multiple items + +use crate::my_module::MyType; // For types and traits + +fn main() { + let map = HashMap::new(); // Can use HashMap directly + // Full path not needed +} +``` + + +## File Hierarchy Organization + +Rust organizes modules in a **file hierarchy** that mirrors the module structure. + + +```python !! py +# Python: Package structure +# myproject/ +# ├── __init__.py +# ├── utils.py +# ├── models/ +# │ ├── __init__.py +# │ ├── user.py +# │ └── post.py +# └── services/ +# ├── __init__.py +# └── auth.py + +# Usage: +# from models.user import User +# from services.auth import authenticate +``` + +```rust !! rs +// Rust: Crate structure +// myproject/ +// ├── Cargo.toml +// ├── src/ +// │ ├── main.rs +// │ ├── utils.rs +// │ ├── models/ +// │ │ ├── mod.rs +// │ │ ├── user.rs +// │ │ └── post.rs +// │ └── services/ +// │ ├── mod.rs +// │ └── auth.rs +// └── tests/ +// └── integration_test.rs + +// In src/main.rs: +mod utils; +mod models; +mod services; + +use models::user::User; +use services::auth::authenticate; + +fn main() { + let user = User::new("Alice"); + authenticate(&user); +} + +// In src/models/mod.rs: +pub mod user; +pub mod post; +``` + + +### Module Files vs mod.rs + + +```python !! py +# Python: Always __init__.py +# package/ +# ├── __init__.py # Makes it a package +# └── module.py +``` + +```rust !! rs +// Rust: Two styles (2018 edition prefers file per module) +// Style 1: Traditional mod.rs +// models/ +// ├── mod.rs (declare submodules here) +// ├── user.rs +// └── post.rs + +// In models/mod.rs: +pub mod user; +pub mod post; + +// Style 2: Modern (Rust 2018+) +// models.rs (file for models module) +// Or models/ (directory with mod.rs) +``` + + +## Visibility and Privacy + +Rust has **default private** visibility, unlike Python's "we're all adults here" philosophy. + + +```python !! py +# Python: No true privacy (convention-based) +class MyClass: + def __init__(self): + self.public = "visible" + self._protected = "convention: don't touch" + self.__private = "name mangled, still accessible" + +# All attributes are accessible +obj = MyClass() +print(obj.public) # OK +print(obj._protected) # OK but discouraged +print(obj._MyClass__private) # Still accessible! +``` + +```rust !! rs +// Rust: True privacy with visibility keywords +mod my_module { + pub struct PublicStruct { + pub public_field: i32, + private_field: i32, // Private by default + } + + impl PublicStruct { + pub fn new() -> Self { + PublicStruct { + public_field: 0, + private_field: 0, + } + } + + pub fn public_method(&self) { + println!("Public method"); + } + + fn private_method(&self) { + println!("Private method"); + } + } + + // Completely private struct + struct PrivateStruct { + value: i32, + } +} + +fn main() { + let obj = my_module::PublicStruct::new(); + obj.public_method(); + // obj.private_method(); // ERROR: private method + // println!("{}", obj.private_field); // ERROR: private field +} +``` + + +### Visibility Modifiers + + +```python !! py +# Python: Only convention-based privacy +class Parent: + def public_method(self): + pass + + def _protected_method(self): + pass + + def __private_method(self): + pass +``` + +```rust !! rs +// Rust: Explicit visibility modifiers +pub fn public_function() {} // Visible everywhere + +fn private_function() {} // Visible only in this module + +pub(crate) fn crate_public() {} // Visible in entire crate + +pub(super) fn visible_to_parent() {} // Visible in parent module + +pub(in crate::my_module) fn scoped_public() {} // Visible in specific path + +struct MyStruct { + pub x: i32, // Public field + y: i32, // Private field +} +``` + + +## Cargo.toml and Dependencies + +`Cargo.toml` is like Python's `setup.py` or `pyproject.toml`, but more opinionated. + + +```python !! py +# Python: pyproject.toml or setup.py +[build-system] +requires = ["setuptools>=45", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "mypackage" +version = "1.0.0" +description = "My Python package" +dependencies = [ + "requests>=2.28.0", + "pandas>=1.5.0", +] + +[project.optional-dependencies] +dev = ["pytest>=7.0", "black>=22.0"] +``` + +```rust !! rs +// Rust: Cargo.toml +[package] +name = "mycrate" +version = "0.1.0" +edition = "2021" +description = "My Rust crate" +authors = ["Your Name "] + +[dependencies] +serde = { version = "1.0", features = ["derive"] } +tokio = { version = "1.0", features = ["full"] } +regex = "1.8" + +[dev-dependencies] +criterion = "0.5" # Benchmarking +proptest = "1.0" # Property testing + +[[bin]] +name = "myapp" +path = "src/main.rs" + +[lib] +name = "mylib" +path = "src/lib.rs" +``` + + +## Dependency Management + + +```python !! py +# Python: Using pip and requirements.txt +# Install: pip install requests pandas + +# requirements.txt: +requests==2.28.0 +pandas>=1.5.0,<2.0.0 +numpy + +# Or in pyproject.toml: +[project.dependencies] +requests = ">=2.28.0" +pandas = ">=1.5.0" +``` + +```rust !! rs +// Rust: Using Cargo +// Command line: +// cargo add serde +// cargo add tokio --features full + +// Or edit Cargo.toml manually: +[dependencies] +serde = "1.0" # caret requirement (^1.0.0) +serde_json = "1.0" +rand = "0.8" // Means >=0.8.0,<0.9.0 + +// Cargo.lock pins exact versions (like Pipfile.lock) +``` + + +## Publishing to crates.io + + +```python !! py +# Python: Publishing to PyPI +# 1. Register account on PyPI +# 2. Build distribution +python -m build + +# 3. Upload +twine upload dist/* + +# Or use flit/poetry for publishing +poetry publish +``` + +```rust !! rs +// Rust: Publishing to crates.io +// 1. Login to crates.io +cargo login + +// 2. Ensure package documentation +// Add documentation to lib.rs! +//! # My Crate +//! +//! This crate does amazing things. + +/// This function does X +pub fn amazing_function() -> i32 { + 42 +} + +// 3. Publish +cargo publish + +// 4. Update version and publish again +// (bump version in Cargo.toml) +cargo publish +``` + + +## Workspaces: Multi-Package Projects + + +```python !! py +# Python: Monorepo structure +# project/ +# ├── services/ +# │ ├── auth/ +# │ │ ├── pyproject.toml +# │ │ └── src/ +# │ └── user/ +# │ ├── pyproject.toml +# │ └── src/ +# ├── pyproject.toml +# └── requirements.txt + +# Use Poetry workspaces or setup.py for linking +``` + +```rust !! rs +// Rust: Cargo workspaces +// workspace/ +// ├── Cargo.toml (workspace root) +// ├── Cargo.lock +// ├── service-auth/ (crate) +// │ ├── Cargo.toml +// │ └── src/ +// ├── service-user/ (crate) +// │ ├── Cargo.toml +// │ └── src/ +// └── shared/ (crate) +// ├── Cargo.toml +// └── src/ + +// In root Cargo.toml: +[workspace] +members = [ + "service-auth", + "service-user", + "shared", +] + +[workspace.dependencies] +serde = "1.0" +tokio = "1.0" + +// In member crate Cargo.toml: +[dependencies] +serde = { workspace = true } +tokio = { workspace = true } +shared = { path = "../shared" } +``` + + +## Testing Organization + + +```python !! py +# Python: test files alongside source +# myproject/ +# ├── mymodule.py +# └── test_mymodule.py + +# Or tests/ directory +# tests/ +# ├── __init__.py +# ├── test_auth.py +# └── test_user.py + +# Run with pytest +``` + +```rust !! rs +// Rust: Unit tests in same file, integration tests in tests/ +// In src/lib.rs or src/main.rs: + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_addition() { + assert_eq!(add(2, 2), 4); + } + + #[test] + fn test_multiplication() { + assert_eq!(multiply(2, 3), 6); + } +} + +// Integration tests in tests/ directory: +// tests/integration_test.rs + +#[test] +fn test_full_workflow() { + // Test multiple modules working together +} +``` + + +## Build Profiles and Optimization + + +```python !! py +# Python: No native compilation +# Performance comes from C extensions +# Optimization: code design, algorithms + +# For distribution: +python -m compileall # Bytecode compilation +``` + +```rust !! rs +// Rust: Cargo profiles +[profile.dev] +opt-level = 0 # No optimization for fast compilation + +[profile.release] +opt-level = 3 # Maximum optimization +lto = true # Link-time optimization +codegen-units = 1 # Better optimization at cost of compile time + +[profile.bench] +inherits = "release" + +[profile.test] +opt-level = 1 # Slight optimization for tests + +// Build commands: +// cargo build # Debug +// cargo build --release # Optimized +``` + + +## Common Patterns + +### Re-exports + + +```python !! py +# Python: Re-exporting in __init__.py +# mypackage/__init__.py +from .module1 import func1 +from .module2 import Class2 + +# Usage: +from mypackage import func1, Class2 +``` + +```rust !! rs +// Rust: pub use for re-exporting +// In lib.rs: +mod auth; +mod database; +mod utils; + +// Re-export for convenience +pub use auth::{login, logout}; +pub use database::{Database, Connection}; +pub use utils::*; + +// Users can now import directly: +use mycrate::{login, Database}; +``` + + +### Preloading Modules + + +```python !! py +# Python: No built-in prelude mechanism +# But you can create __init__.py that imports common items + +# mypackage/__init__.py +from .types import User, Post, Comment +from .utils import validate, sanitize +from .api import Client +``` + +```rust !! rs +// Rust: Create a prelude module +// src/prelude.rs +pub use crate::types::{User, Post, Comment}; +pub use crate::utils::{validate, sanitize}; +pub use crate::api::Client; + +// In lib.rs: +pub mod prelude; + +// Users can import everything at once: +use mycrate::prelude::*; +``` + + +## Comparison Table + +| Feature | Python | Rust | +|---------|--------|------| +| Module files | Any `.py` file | Explicit `mod` declaration | +| Import system | `import`, `from ... import` | `use`, `mod` | +| Default visibility | Public | Private | +| Package config | `pyproject.toml` | `Cargo.toml` | +| Dependency file | `requirements.txt` or `pyproject.toml` | `Cargo.toml` only | +| Version locking | `Pipfile.lock`, `poetry.lock` | `Cargo.lock` | +| Registry | PyPI | crates.io | +| Publishing | `twine upload`, `poetry publish` | `cargo publish` | +| Multi-package | Poetry workspaces, manual | Cargo workspaces | +| Test location | `tests/` or `test_*.py` | `tests/` + inline `#[cfg(test)]` | + +## Best Practices + +### DO: + +1. **Use modules to organize code** - Group related functionality +2. **Make APIs public, implementation private** - Encapsulation +3. **Use `pub use` for convenience** - Re-export common items +4. **Leverage workspaces** - For multi-package projects +5. **Document public APIs** - With `///` and `//!` comments +6. **Use semantic versioning** - Follow Cargo conventions + +### DON'T: + +1. **Overuse `use *`** - Be explicit about imports +2. **Make everything public** - Respect encapsulation +3. **Ignore Cargo warnings** - They catch real issues +4. **Publish without documentation** - Document your crate +5. **Mix release and debug** - Use profiles appropriately + +## Summary + +- **Modules** provide namespace organization +- **Use** declarations bring items into scope +- **Cargo.toml** is the single source of truth for dependencies +- **crates.io** is the official package registry +- **Workspaces** enable multi-package projects +- **Rust's system** is more explicit than Python's +- **Both ecosystems** provide dependency management and publishing + +## Practice Exercises + +1. Create a multi-module project with proper file hierarchy +2. Set up a workspace with multiple related crates +3. Publish a small crate to crates.io +4. Create a prelude module for convenient imports +5. Configure different build profiles for optimization + +## Next Module + +In the next module, we'll explore **file I/O and the standard library**, including reading/writing files, path manipulation, command-line arguments, and environment variables. You'll learn how Rust's approach compares to Python's file handling. diff --git a/content/docs/py2rust/module-13-modules-packages.zh-cn.mdx b/content/docs/py2rust/module-13-modules-packages.zh-cn.mdx new file mode 100644 index 0000000..63a7e3c --- /dev/null +++ b/content/docs/py2rust/module-13-modules-packages.zh-cn.mdx @@ -0,0 +1,194 @@ +--- +title: "模块 13: 模块与包 - 代码组织" +description: "学习 Rust 的模块系统、如何使用 Cargo.toml 组织代码,以及发布包到 crates.io,与 Python 的打包生态系统进行比较。" +--- + +# 模块 13: 模块与包 - 代码组织 + +在本模块中,你将学习 Rust 如何将代码组织成**模块**和**包**,以及这与 Python 的导入系统和包管理的比较。 + +## 学习目标 + +完成本模块后,你将能够: +- 理解 Rust 的模块系统 +- 使用 `mod`、`use` 和 `pub` 关键字 +- 使用文件层次结构组织代码 +- 使用 Cargo.toml 进行依赖管理 +- 从 crates.io 发布和消费包 +- 比较 Rust 的打包与 Python 的 setuptools/PyPI + +## 模块系统基础 + +Rust 的模块系统提供**命名空间**和**访问控制**。与 Python 的隐式模块系统不同,Rust 的是显式的和基于文件的。 + +### 定义模块 + + +```python !! py +# Python: 模块本身就是文件 +# math_utils.py +def add(a, b): + return a + b + +class Calculator: + def multiply(self, a, b): + return a * b + +PI = 3.14159 + +# 使用: +# from math_utils import add, Calculator, PI +# result = add(1, 2) +``` + +```rust !! rs +// Rust: 模块是显式声明的 +// math_utils.rs +pub fn add(a: i32, b: i32) -> i32 { + a + b +} + +pub struct Calculator { + pub value: i32, +} + +impl Calculator { + pub fn multiply(&self, a: i32, b: i32) -> i32 { + a * b + } +} + +pub const PI: f64 = 3.14159; + +// 在 main.rs 或 lib.rs 中: +mod math_utils; // 声明模块 + +fn main() { + let result = math_utils::add(1, 2); + println!("Result: {}", result); +} +``` + + +## Use 声明 + +`use` 关键字将项引入作用域,类似于 Python 的 `import`。 + + +```python !! py +# Python: 导入语句 +import os +from collections import defaultdict +from math import pi as PI +from mymodule import MyClass, my_function + +# 直接使用 +path = os.path.join("a", "b") +d = defaultdict(int) +``` + +```rust !! rs +// Rust: Use 声明 +use std::fs; // 类似于: import fs +use std::collections::HashMap; // 类似于: from collections import HashMap +use std::io::{self, Write}; // 类似于: from io import io, Write + +fn main() { + let path = fs::File::create("test.txt").unwrap(); +} +``` + + +## Cargo.toml 和依赖 + +`Cargo.toml` 类似于 Python 的 `setup.py` 或 `pyproject.toml`,但更具规范性。 + + +```python !! py +# Python: pyproject.toml 或 setup.py +[project] +name = "mypackage" +version = "1.0.0" +dependencies = [ + "requests>=2.28.0", + "pandas>=1.5.0", +] +``` + +```rust !! rs +// Rust: Cargo.toml +[package] +name = "mycrate" +version = "0.1.0" +edition = "2021" + +[dependencies] +serde = { version = "1.0", features = ["derive"] } +tokio = { version = "1.0", features = ["full"] } + +[dev-dependencies] +criterion = "0.5" +``` + + +## 发布到 crates.io + + +```python !! py +# Python: 发布到 PyPI +# 1. 在 PyPI 注册账户 +# 2. 构建发行版 +python -m build + +# 3. 上传 +twine upload dist/* +``` + +```rust !! rs +// Rust: 发布到 crates.io +// 1. 登录 crates.io +cargo login + +// 2. 确保包文档 +//! # My Crate +//! +//! This crate does amazing things. + +/// This function does X +pub fn amazing_function() -> i32 { + 42 +} + +// 3. 发布 +cargo publish +``` + + +## 最佳实践 + +### 应该做: + +1. **使用模块组织代码** - 分组相关功能 +2. **使 API 公开,实现私有** - 封装 +3. **使用 `pub use` 提供便利** - 重新导出常见项 +4. **利用工作空间** - 用于多包项目 +5. **记录公共 API** - 使用 `///` 和 `//!` 注释 + +### 不应该做: + +1. **过度使用 `use *`** - 明确导入 +2. **使所有内容公开** - 尊重封装 +3. **忽略 Cargo 警告** - 它们捕获真正的问题 + +## 总结 + +- **模块**提供命名空间组织 +- **Use** 声明将项引入作用域 +- **Cargo.toml** 是依赖的唯一真实来源 +- **crates.io** 是官方包注册表 +- **Rust 的系统**比 Python 的更显式 +- **两个生态系统**都提供依赖管理和发布 + +## 下一模块 + +在下一个模块中,我们将探索**文件 I/O 和标准库**,包括读/写文件、路径操作、命令行参数和环境变量。你将学习 Rust 的方法如何与 Python 的文件处理进行比较。 diff --git a/content/docs/py2rust/module-13-modules-packages.zh-tw.mdx b/content/docs/py2rust/module-13-modules-packages.zh-tw.mdx new file mode 100644 index 0000000..ba82599 --- /dev/null +++ b/content/docs/py2rust/module-13-modules-packages.zh-tw.mdx @@ -0,0 +1,144 @@ +--- +title: "模組 13: 模組與包 - 代碼組織" +description: "學習 Rust 的模組系統、如何使用 Cargo.toml 組織代碼,以及發布包到 crates.io,與 Python 的打包生態系統進行比較。" +--- + +# 模組 13: 模組與包 - 代碼組織 + +在本模組中,你將學習 Rust 如何將代碼組織成**模組**和**包**,以及這與 Python 的導入系統和包管理的比較。 + +## 學習目標 + +完成本模組後,你將能夠: +- 理解 Rust 的模組系統 +- 使用 `mod`、`use` 和 `pub` 關鍵字 +- 使用文件層次結構組織代碼 +- 使用 Cargo.toml 進行依賴管理 +- 從 crates.io 發布和消費包 +- 比較 Rust 的打包與 Python 的 setuptools/PyPI + +## 模組系統基礎 + +Rust 的模組系統提供**命名空間**和**訪問控制**。與 Python 的隱式模組系統不同,Rust 的是顯式的和基於文件的。 + +### 定義模組 + + +```python !! py +# Python: 模組本身就是文件 +# math_utils.py +def add(a, b): + return a + b + +# 使用: +# from math_utils import add +# result = add(1, 2) +``` + +```rust !! rs +// Rust: 模組是顯式聲明的 +// math_utils.rs +pub fn add(a: i32, b: i32) -> i32 { + a + b +} + +// 在 main.rs 或 lib.rs 中: +mod math_utils; // 聲明模組 + +fn main() { + let result = math_utils::add(1, 2); + println!("Result: {}", result); +} +``` + + +## Use 聲明 + +`use` 關鍵字將項引入作用域,類似於 Python 的 `import`。 + + +```python !! py +# Python: 導入語句 +import os +from collections import defaultdict +``` + +```rust !! rs +// Rust: Use 聲明 +use std::fs; // 類似於: import fs +use std::collections::HashMap; // 類似於: from collections import HashMap + +fn main() { + let path = fs::File::create("test.txt").unwrap(); +} +``` + + +## Cargo.toml 和依賴 + +`Cargo.toml` 類似於 Python 的 `pyproject.toml`,但更具規範性。 + + +```python !! py +# Python: pyproject.toml +[project] +name = "mypackage" +version = "1.0.0" +dependencies = [ + "requests>=2.28.0", +] +``` + +```rust !! rs +// Rust: Cargo.toml +[package] +name = "mycrate" +version = "0.1.0" +edition = "2021" + +[dependencies] +serde = { version = "1.0", features = ["derive"] } +tokio = { version = "1.0" } +``` + + +## 發布到 crates.io + + +```python !! py +# Python: 發布到 PyPI +python -m build +twine upload dist/* +``` + +```rust !! rs +// Rust: 發布到 crates.io +cargo login +cargo publish +``` + + +## 最佳實踐 + +### 應該做: + +1. **使用模組組織代碼** - 分組相關功能 +2. **使 API 公開,實現私有** - 封裝 +3. **使用 `pub use` 提供便利** - 重新導出常見項 + +### 不應該做: + +1. **過度使用 `use *`** - 明確導入 +2. **使所有內容公開** - 尊重封裝 + +## 總結 + +- **模組**提供命名空間組織 +- **Use** 聲明將項引入作用域 +- **Cargo.toml** 是依賴的唯一真實來源 +- **crates.io** 是官方包註冊表 +- **Rust 的系統**比 Python 的更顯式 + +## 下一模組 + +在下一個模組中,我們將探索**文件 I/O 和標準庫**,包括讀/寫文件、路徑操作、命令行參數和環境變量。你將學習 Rust 的方法如何與 Python 的文件處理進行比較。 diff --git a/content/docs/py2rust/module-14-file-io.mdx b/content/docs/py2rust/module-14-file-io.mdx new file mode 100644 index 0000000..b8c9bc1 --- /dev/null +++ b/content/docs/py2rust/module-14-file-io.mdx @@ -0,0 +1,485 @@ +--- +title: "Module 14: File I/O & Stdlib - Working with Files" +description: "Learn file input/output, path manipulation, command-line arguments, and environment variables in Rust compared to Python's approach." +--- + +# Module 14: File I/O & Stdlib - Working with Files + +In this module, you'll learn how Rust handles file I/O and system interactions, and how this compares to Python's approach. + +## Learning Objectives + +By the end of this module, you'll be able to: +- Read and write files in Rust +- Manipulate file system paths +- Handle command-line arguments +- Work with environment variables +- Use Rust's standard library effectively +- Compare Rust's I/O with Python's file handling + +## Reading Files + +### Reading Entire Files + + +```python !! py +# Python: Simple file reading +with open('file.txt', 'r') as f: + content = f.read() # Entire file as string + print(content) + +# Or read lines +with open('file.txt', 'r') as f: + lines = f.readlines() # List of lines +``` + +```rust !! rs +// Rust: Reading entire file +use std::fs; +use std::error::Error; + +fn read_file() -> Result> { + let content = fs::read_to_string("file.txt")?; + Ok(content) +} + +// Or read to bytes +fn read_bytes() -> Result, Box> { + let content = fs::read("file.txt")?; + Ok(content) +} + +fn main() { + match read_file() { + Ok(content) => println!("{}", content), + Err(e) => eprintln!("Error: {}", e), + } +} +``` + + +### Reading Line by Line + + +```python !! py +# Python: Line by line iteration +with open('file.txt', 'r') as f: + for line in f: + print(line.strip()) # Automatic memory management + +# Or with explicit line handling +with open('file.txt', 'r') as f: + while True: + line = f.readline() + if not line: + break + print(line.strip()) +``` + +```rust !! rs +// Rust: Buffered reading line by line +use std::fs::File; +use std::io::{self, BufRead, BufReader}; + +fn read_lines() -> io::Result<()> { + let file = File::open("file.txt")?; + let reader = BufReader::new(file); + + for line in reader.lines() { + let line = line?; + println!("{}", line); + } + + Ok(()) +} + +// Or with more control +fn read_lines_manual() -> io::Result<()> { + let file = File::open("file.txt")?; + let reader = BufReader::new(file); + + for line in reader.split(b'\n') { + let line = line?; + println!("{}", String::from_utf8_lossy(&line)); + } + + Ok(()) +} +``` + + +## Writing Files + + +```python !! py +# Python: Writing files +with open('output.txt', 'w') as f: + f.write("Hello, World!\n") + f.write("Second line\n") + +# Append mode +with open('output.txt', 'a') as f: + f.write("Appended line\n") + +# Write multiple lines +lines = ["Line 1\n", "Line 2\n", "Line 3\n"] +with open('output.txt', 'w') as f: + f.writelines(lines) +``` + +```rust !! rs +// Rust: Writing files +use std::fs::File; +use std::io::{self, Write}; + +fn write_file() -> io::Result<()> { + let mut file = File::create("output.txt")?; + file.write_all(b"Hello, World!\n")?; + file.write_all(b"Second line\n")?; + Ok(()) +} + +// Append mode +fn append_file() -> io::Result<()> { + let mut file = OpenOptions::new() + .append(true) + .open("output.txt")?; + file.write_all(b"Appended line\n")?; + Ok(()) +} + +// Use std::fs::write for simple cases +fn write_simple() -> io::Result<()> { + fs::write("output.txt", "Content\n")?; + Ok(()) +} +``` + + +## Path Manipulation + + +```python !! py +# Python: pathlib for cross-platform paths +from pathlib import Path + +path = Path("data") / "files" / "example.txt" +print(path) # data/files/example.txt + +# Path components +print(path.parent) # data/files +print(path.name) # example.txt +print(path.stem) # example +print(path.suffix) # .txt + +# Check if exists +if path.exists(): + print(f"Size: {path.stat().st_size} bytes") + +# Join paths +base = Path("/home/user") +full = base / "documents" / "file.txt" +``` + +```rust !! rs +// Rust: std::path::Path for cross-platform paths +use std::path::{Path, PathBuf}; + +fn path_operations() { + let path = Path::new("data/files/example.txt"); + + // Path components + println!("Parent: {:?}", path.parent()); + println!("File name: {:?}", path.file_name()); + println!("Stem: {:?}", path.file_stem()); + println!("Extension: {:?}", path.extension()); + + // Check if exists + if path.exists() { + let metadata = std::fs::metadata(path).unwrap(); + println!("Size: {} bytes", metadata.len()); + } + + // Build paths + let mut path_buf = PathBuf::new(); + path_buf.push("data"); + path_buf.push("files"); + path_buf.push("example.txt"); + + // Or use Path::join + let base = Path::new("/home/user"); + let full = base.join("documents").join("file.txt"); +} +``` + + +## Command-Line Arguments + + +```python !! py +# Python: sys.argv for command-line arguments +import sys + +def main(): + if len(sys.argv) < 2: + print(f"Usage: {sys.argv[0]} ") + sys.exit(1) + + filename = sys.argv[1] + print(f"Processing: {filename}") + + # Named arguments with argparse + import argparse + parser = argparse.ArgumentParser() + parser.add_argument("--verbose", action="store_true") + parser.add_argument("--output", default="out.txt") + args = parser.parse_args() + + if args.verbose: + print(f"Output to: {args.output}") + +if __name__ == "__main__": + main() +``` + +```rust !! rs +// Rust: std::env::args for command-line arguments +use std::env; + +fn main() { + let args: Vec = env::args().collect(); + + if args.len() < 2 { + eprintln!("Usage: {} ", args[0]); + std::process::exit(1); + } + + let filename = &args[1]; + println!("Processing: {}", filename); + + // For complex argument parsing, use crates like clap + // Cargo.toml: clap = { version = "4.0", features = ["derive"] } + + /* + use clap::Parser; + + #[derive(Parser)] + struct Cli { + #[arg(short, long)] + verbose: bool, + + #[arg(short, long, default_value = "out.txt")] + output: String, + } + + fn main_with_clap() { + let cli = Cli::parse(); + if cli.verbose { + println!("Output to: {}", cli.output); + } + } + */ +} +``` + + +## Environment Variables + + +```python !! py +# Python: os.environ for environment variables +import os + +# Get environment variable +home = os.environ.get("HOME", "/default/home") +print(f"Home: {home}") + +# Set environment variable +os.environ["MY_VAR"] = "value" + +# Check if variable exists +if "PATH" in os.environ: + print("PATH is set") + +# List all environment variables +for key, value in os.environ.items(): + print(f"{key}={value}") +``` + +```rust !! rs +// Rust: std::env for environment variables +use std::env; + +fn env_vars() { + // Get environment variable + let home = env::var("HOME").unwrap_or("/default/home".to_string()); + println!("Home: {}", home); + + // Set environment variable + env::set_var("MY_VAR", "value"); + + // Check if variable exists + if env::var("PATH").is_ok() { + println!("PATH is set"); + } + + // List all environment variables + for (key, value) in env::vars() { + println!("{}={}", key, value); + } +} +``` + + +## File System Operations + + +```python !! py +# Python: os and pathlib for file operations +import os +from pathlib import Path + +# Create directory +Path("new_dir").mkdir(exist_ok=True) + +# Create nested directories +Path("a/b/c").mkdir(parents=True, exist_ok=True) + +# Remove file +Path("file.txt").unlink(missing_ok=True) + +# Remove directory +Path("dir").rmdir() + +# List directory +for item in Path(".").iterdir(): + print(item) + +# Check file type +if Path("file.txt").is_file(): + print("It's a file") + +if Path("dir").is_dir(): + print("It's a directory") +``` + +```rust !! rs +// Rust: std::fs for file operations +use std::fs; +use std::path::Path; + +fn fs_operations() -> std::io::Result<()> { + // Create directory + fs::create_dir("new_dir")?; + + // Create nested directories + fs::create_dir_all("a/b/c")?; + + // Remove file + fs::remove_file("file.txt")?; + + // Remove directory (must be empty) + fs::remove_dir("dir")?; + + // Remove directory and contents + fs::remove_dir_all("dir_with_contents")?; + + // List directory + for entry in fs::read_dir(".")? { + let entry = entry?; + println!("{}", entry.path().display()); + } + + // Check file type + let path = Path::new("file.txt"); + if path.is_file() { + println!("It's a file"); + } + + if path.is_dir() { + println!("It's a directory"); + } + + Ok(()) +} +``` + + +## Error Handling Comparison + + +```python !! py +# Python: Exceptions for I/O errors +try: + with open('nonexistent.txt', 'r') as f: + content = f.read() +except FileNotFoundError: + print("File not found") +except PermissionError: + print("Permission denied") +except IOError as e: + print(f"I/O error: {e}") +``` + +```rust !! rs +// Rust: Result types for I/O errors +use std::fs; +use std::io; + +fn read_file_safe() -> Result { + let content = fs::read_to_string("nonexistent.txt")?; + Ok(content) +} + +fn main() { + match read_file_safe() { + Ok(content) => println!("{}", content), + Err(e) if e.kind() == io::ErrorKind::NotFound => { + eprintln!("File not found"); + } + Err(e) if e.kind() == io::ErrorKind::PermissionDenied => { + eprintln!("Permission denied"); + } + Err(e) => eprintln!("I/O error: {}", e), + } +} +``` + + +## Best Practices + +### DO: + +1. **Use `BufReader` for line-by-line reading** - More efficient +2. **Use `?` operator for error propagation** - Cleaner code +3. **Use `Path` and `PathBuf`** - Cross-platform compatibility +4. **Use `OpenOptions` for fine-grained control** - When needed +5. **Handle errors properly** - Don't use `.unwrap()` in production + +### DON'T: + +1. **Read entire files into memory unnecessarily** - Use streaming +2. **Ignore I/O errors** - Handle them properly +3. **Use string concatenation for paths** - Use Path methods +4. **Forget to flush buffers** - When manual buffering is used + +## Summary + +- **File I/O** in Rust uses `std::fs` and `std::io` +- **Paths** are handled with `std::path` for cross-platform support +- **Command-line args** via `std::env::args` +- **Environment variables** via `std::env` +- **Error handling** is explicit with `Result` types +- **Rust's approach** is more verbose but safer than Python's + +## Practice Exercises + +1. Create a program that reads a file and counts word frequencies +2. Build a simple file copy utility +3. Create a directory listing tool with recursive traversal +4. Implement a configuration file reader +5. Build a CLI tool with argument parsing + +## Next Module + +In the final module, we'll explore **threads and concurrency**, including spawning threads, channels, mutexes, and how Rust's fearless concurrency compares to Python's threading model. diff --git a/content/docs/py2rust/module-14-file-io.zh-cn.mdx b/content/docs/py2rust/module-14-file-io.zh-cn.mdx new file mode 100644 index 0000000..880785e --- /dev/null +++ b/content/docs/py2rust/module-14-file-io.zh-cn.mdx @@ -0,0 +1,187 @@ +--- +title: "模块 14: 文件 I/O 和标准库 - 文件操作" +description: "学习 Rust 的文件输入/输出、路径操作、命令行参数和环境变量,与 Python 的方法进行比较。" +--- + +# 模块 14: 文件 I/O 和标准库 - 文件操作 + +在本模块中,你将学习 Rust 如何处理文件 I/O 和系统交互,以及这与 Python 的方法的比较。 + +## 学习目标 + +完成本模块后,你将能够: +- 在 Rust 中读写文件 +- 操作文件系统路径 +- 处理命令行参数 +- 使用环境变量 +- 有效地使用 Rust 的标准库 +- 比较 Rust 的 I/O 与 Python 的文件处理 + +## 读取文件 + +### 读取整个文件 + + +```python !! py +# Python: 简单文件读取 +with open('file.txt', 'r') as f: + content = f.read() # 整个文件作为字符串 + print(content) +``` + +```rust !! rs +// Rust: 读取整个文件 +use std::fs; + +fn main() { + match fs::read_to_string("file.txt") { + Ok(content) => println!("{}", content), + Err(e) => eprintln!("错误: {}", e), + } +} +``` + + +## 写入文件 + + +```python !! py +# Python: 写入文件 +with open('output.txt', 'w') as f: + f.write("Hello, World!\n") + +# 追加模式 +with open('output.txt', 'a') as f: + f.write("追加的行\n") +``` + +```rust !! rs +// Rust: 写入文件 +use std::fs::File; +use std::io::Write; + +fn write_file() -> std::io::Result<()> { + let mut file = File::create("output.txt")?; + file.write_all(b"Hello, World!\n")?; + Ok(()) +} +``` + + +## 路径操作 + + +```python !! py +# Python: pathlib 用于跨平台路径 +from pathlib import Path + +path = Path("data") / "files" / "example.txt" +print(path.parent) # data/files +print(path.name) # example.txt +``` + +```rust !! rs +// Rust: std::path::Path 用于跨平台路径 +use std::path::Path; + +fn path_operations() { + let path = Path::new("data/files/example.txt"); + + println!("父目录: {:?}", path.parent()); + println!("文件名: {:?}", path.file_name()); +} +``` + + +## 命令行参数 + + +```python !! py +# Python: sys.argv 用于命令行参数 +import sys + +if len(sys.argv) < 2: + print(f"用法: {sys.argv[0]} <文件名>") + sys.exit(1) + +filename = sys.argv[1] +print(f"处理: {filename}") +``` + +```rust !! rs +// Rust: std::env::args 用于命令行参数 +use std::env; + +fn main() { + let args: Vec = env::args().collect(); + + if args.len() < 2 { + eprintln!("用法: {} <文件名>", args[0]); + std::process::exit(1); + } + + let filename = &args[1]; + println!("处理: {}", filename); +} +``` + + +## 环境变量 + + +```python !! py +# Python: os.environ 用于环境变量 +import os + +home = os.environ.get("HOME", "/default/home") +print(f"主目录: {home}") + +os.environ["MY_VAR"] = "value" +``` + +```rust !! rs +// Rust: std::env 用于环境变量 +use std::env; + +fn env_vars() { + let home = env::var("HOME").unwrap_or("/default/home".to_string()); + println!("主目录: {}", home); + + env::set_var("MY_VAR", "value"); +} +``` + + +## 最佳实践 + +### 应该做: + +1. **使用 `BufReader` 逐行读取** - 更高效 +2. **使用 `?` 操作符传播错误** - 代码更清晰 +3. **使用 `Path` 和 `PathBuf`** - 跨平台兼容 +4. **正确处理错误** - 生产环境中不要使用 `.unwrap()` + +### 不应该做: + +1. **不必要地将整个文件读入内存** - 使用流式处理 +2. **忽略 I/O 错误** - 正确处理它们 +3. **使用字符串拼接路径** - 使用 Path 方法 + +## 总结 + +- **文件 I/O** 使用 `std::fs` 和 `std::io` +- **路径** 使用 `std::path` 进行跨平台支持 +- **命令行参数** 通过 `std::env::args` +- **环境变量** 通过 `std::env` +- **错误处理** 使用 `Result` 类型显式处理 +- **Rust 的方法**比 Python 的更冗长但更安全 + +## 练习 + +1. 创建一个程序读取文件并统计词频 +2. 构建一个简单的文件复制工具 +3. 创建递归遍历的目录列表工具 + +## 下一模块 + +在最后一个模块中,我们将探索**线程和并发**,包括生成线程、通道、互斥锁,以及 Rust 的无畏并发如何与 Python 的线程模型进行比较。 diff --git a/content/docs/py2rust/module-14-file-io.zh-tw.mdx b/content/docs/py2rust/module-14-file-io.zh-tw.mdx new file mode 100644 index 0000000..0c84025 --- /dev/null +++ b/content/docs/py2rust/module-14-file-io.zh-tw.mdx @@ -0,0 +1,117 @@ +--- +title: "模組 14: 文件 I/O 和標準庫 - 文件操作" +description: "學習 Rust 的文件輸入/輸出、路徑操作、命令行參數和環境變量,與 Python 的方法進行比較。" +--- + +# 模組 14: 文件 I/O 和標準庫 - 文件操作 + +在本模組中,你將學習 Rust 如何處理文件 I/O 和系統交互,以及這與 Python 的方法的比較。 + +## 學習目標 + +完成本模組後,你將能夠: +- 在 Rust 中讀寫文件 +- 操作文件系統路徑 +- 處理命令行參數 +- 使用環境變量 +- 有效地使用 Rust 的標準庫 + +## 讀取文件 + + +```python !! py +# Python: 簡單文件讀取 +with open('file.txt', 'r') as f: + content = f.read() + print(content) +``` + +```rust !! rs +// Rust: 讀取整個文件 +use std::fs; + +fn main() { + match fs::read_to_string("file.txt") { + Ok(content) => println!("{}", content), + Err(e) => eprintln!("錯誤: {}", e), + } +} +``` + + +## 寫入文件 + + +```python !! py +# Python: 寫入文件 +with open('output.txt', 'w') as f: + f.write("Hello, World!\n") +``` + +```rust !! rs +// Rust: 寫入文件 +use std::fs::File; +use std::io::Write; + +fn write_file() -> std::io::Result<()> { + let mut file = File::create("output.txt")?; + file.write_all(b"Hello, World!\n")?; + Ok(()) +} +``` + + +## 路徑操作 + + +```python !! py +# Python: pathlib +from pathlib import Path + +path = Path("data") / "files" / "example.txt" +print(path.parent) +``` + +```rust !! rs +// Rust: std::path::Path +use std::path::Path; + +fn path_operations() { + let path = Path::new("data/files/example.txt"); + println!("父目錄: {:?}", path.parent()); +} +``` + + +## 命令行參數 + + +```python !! py +# Python: sys.argv +import sys + +filename = sys.argv[1] +print(f"處理: {filename}") +``` + +```rust !! rs +// Rust: std::env::args +use std::env; + +fn main() { + let args: Vec = env::args().collect(); + let filename = &args[1]; + println!("處理: {}", filename); +} +``` + + +## 總結 + +- **文件 I/O** 使用 `std::fs` 和 `std::io` +- **路徑** 使用 `std::path` 進行跨平台支持 +- **Rust 的方法**比 Python 的更冗長但更安全 + +## 下一模組 + +在最後一個模組中,我們將探索**線程和並發**。 diff --git a/content/docs/py2rust/module-15-threads.mdx b/content/docs/py2rust/module-15-threads.mdx new file mode 100644 index 0000000..31c0ed7 --- /dev/null +++ b/content/docs/py2rust/module-15-threads.mdx @@ -0,0 +1,711 @@ +--- +title: "Module 15: Threads & Concurrency - Fearless Parallelism" +description: "Master Rust's threading model, channels, mutexes, and fearless concurrency compared to Python's threading and multiprocessing." +--- + +# Module 15: Threads & Concurrency - Fearless Parallelism + +In this final module, you'll learn how Rust handles **threads and concurrency**, providing **fearless concurrency** through compile-time guarantees that prevent data races. + +## Learning Objectives + +By the end of this module, you'll be able to: +- Spawn threads in Rust +- Use channels for message passing +- Use Mutex for shared state +- Understand Rust's ownership model in concurrent contexts +- Compare Rust's concurrency with Python's threading +- Build safe concurrent programs + +## Background: Python vs Rust Concurrency + + +```python !! py +# Python: GIL (Global Interpreter Lock) limits true parallelism +import threading +import time + +def count_down(n): + while n > 0: + print(f"Count: {n}") + n -= 1 + time.sleep(0.1) + +# Threads don't run in parallel due to GIL +t1 = threading.Thread(target=count_down, args=(5,)) +t2 = threading.Thread(target=count_down, args=(5,)) + +t1.start() +t2.start() +t1.join() +t2.join() + +# For true parallelism, use multiprocessing +from multiprocessing import Process +p = Process(target=count_down, args=(5,)) +p.start() +p.join() +``` + +```rust !! rs +// Rust: True parallelism without GIL +use std::thread; +use std::time::Duration; + +fn count_down(n: i32) { + let mut n = n; + while n > 0 { + println!("Count: {}", n); + n -= 1; + thread::sleep(Duration::from_millis(100)); + } +} + +fn main() { + // Threads run in true parallel + let t1 = thread::spawn(|| count_down(5)); + let t2 = thread::spawn(|| count_down(5)); + + t1.join().unwrap(); + t2.join().unwrap(); + + // No GIL - full CPU utilization! +} +``` + + +## Spawning Threads + +### Basic Thread Creation + + +```python !! py +# Python: Basic threading +import threading +import time + +def worker(name): + print(f"Worker {name} starting") + time.sleep(1) + print(f"Worker {name} done") + +# Create and start thread +thread = threading.Thread(target=worker, args=("Alice",)) +thread.start() +thread.join() + +# With lambda +thread2 = threading.Thread(target=lambda: print("Quick task")) +thread2.start() +thread2.join() +``` + +```rust !! rs +// Rust: Spawning threads +use std::thread; +use std::time::Duration; + +fn worker(name: &str) { + println!("Worker {} starting", name); + thread::sleep(Duration::from_secs(1)); + println!("Worker {} done", name); +} + +fn main() { + // Create and start thread + let handle = thread::spawn(|| { + worker("Alice"); + }); + + handle.join().unwrap(); + + // With closure + let handle2 = thread::spawn(|| { + println!("Quick task"); + }); + + handle2.join().unwrap(); +} +``` + + +### Moving Data into Threads + + +```python !! py +# Python: Data shared by default (but GIL protects it) +import threading + +data = [1, 2, 3, 4, 5] + +def process(): + for item in data: + print(f"Processing {item}") + +thread = threading.Thread(target=process) +thread.start() +thread.join() + +# Data is accessible from all threads +print(data) +``` + +```rust !! rs +// Rust: Must move ownership into threads +use std::thread; + +fn main() { + let data = vec![1, 2, 3, 4, 5]; + + // Move ownership into thread + let handle = thread::spawn(move || { + for item in data { + println!("Processing {}", item); + } + // data is now owned by this thread + }); + + handle.join().unwrap(); + // println!("{:?}", data); // ERROR: data was moved +} +``` + + +### Returning Values from Threads + + +```python !! py +# Python: Return values via shared state +import threading + +result = [] + +def worker(): + result.append(42) + +thread = threading.Thread(target=worker) +thread.start() +thread.join() + +print(result[0]) # 42 + +# Or use concurrent.futures +from concurrent.futures import ThreadPoolExecutor + +def compute(): + return 42 + +with ThreadPoolExecutor() as executor: + future = executor.submit(compute) + print(future.result()) # 42 +``` + +```rust !! rs +// Rust: Return values via JoinHandle +use std::thread; + +fn main() { + let handle = thread::spawn(|| { + // Expensive computation + 42 + }); + + let result = handle.join().unwrap(); + println!("Result: {}", result); + + // With move closure + let data = vec![1, 2, 3, 4, 5]; + let handle = thread::spawn(move || { + data.iter().sum::() + }); + + let sum = handle.join().unwrap(); + println!("Sum: {}", sum); +} +``` + + +## Channels: Message Passing + +Rust channels provide **message passing concurrency** following the "do not communicate by sharing memory; instead, share memory by communicating" philosophy. + +### Multiple Producer, Single Consumer (mpsc) + + +```python !! py +# Python: Queue for thread communication +import threading +import queue + +q = queue.Queue() + +def producer(): + for i in range(5): + q.put(i) + q.put(None) # Sentinel + +def consumer(): + while True: + item = q.get() + if item is None: + break + print(f"Received: {item}") + +t1 = threading.Thread(target=producer) +t2 = threading.Thread(target=consumer) + +t1.start() +t2.start() + +t1.join() +t2.join() +``` + +```rust !! rs +// Rust: mpsc channels for message passing +use std::sync::mpsc; +use std::thread; + +fn main() { + let (tx, rx) = mpsc::channel(); + + let producer = thread::spawn(move || { + for i in 0..5 { + tx.send(i).unwrap(); + } + // Channel closes when tx is dropped + }); + + let consumer = thread::spawn(move || { + for received in rx { + println!("Received: {}", received); + } + }); + + producer.join().unwrap(); + consumer.join().unwrap(); +} +``` + + +### Multiple Producers + + +```python !! py +# Python: Multiple producers +import threading +import queue + +q = queue.Queue() + +def producer(id): + for i in range(3): + q.put(f"Producer {id}: {i}") + +def consumer(): + while True: + item = q.get() + if item == "DONE": + break + print(item) + +threads = [] +for i in range(3): + t = threading.Thread(target=producer, args=(i,)) + t.start() + threads.append(t) + +consumer_thread = threading.Thread(target=consumer) +consumer_thread.start() + +for t in threads: + t.join() + +q.put("DONE") +consumer_thread.join() +``` + +```rust !! rs +// Rust: Multiple producers with cloning +use std::sync::mpsc; +use std::thread; + +fn main() { + let (tx, rx) = mpsc::channel(); + + let mut producers = Vec::new(); + for id in 0..3 { + let tx_clone = tx.clone(); + let handle = thread::spawn(move || { + for i in 0..3 { + tx_clone.send(format!("Producer {}: {}", id, i)).unwrap(); + } + }); + producers.push(handle); + } + + // Drop original tx so rx knows when all producers are done + drop(tx); + + let consumer = thread::spawn(move || { + for received in rx { + println!("{}", received); + } + }); + + for handle in producers { + handle.join().unwrap(); + } + + consumer.join().unwrap(); +} +``` + + +## Shared State: Mutex + +When you need **shared mutable state**, use `Mutex` for thread-safe access. + + +```python !! py +# Python: threading.Lock for shared state +import threading + +counter = [0] +lock = threading.Lock() + +def increment(): + for _ in range(1000): + with lock: + counter[0] += 1 + +threads = [] +for _ in range(5): + t = threading.Thread(target=increment) + t.start() + threads.append(t) + +for t in threads: + t.join() + +print(f"Counter: {counter[0]}") # 5000 +``` + +```rust !! rs +// Rust: Mutex for shared mutable state +use std::sync::{Arc, Mutex}; +use std::thread; + +fn main() { + let counter = Arc::new(Mutex::new(0)); + let mut handles = vec![]; + + for _ in 0..5 { + let counter_clone = Arc::clone(&counter); + let handle = thread::spawn(move || { + for _ in 0..1000 { + let mut num = counter_clone.lock().unwrap(); + *num += 1; + } + }); + handles.push(handle); + } + + for handle in handles { + handle.join().unwrap(); + } + + println!("Counter: {}", *counter.lock().unwrap()); +} +``` + + +### Arc vs Rc + + +```python !! py +# Python: References are thread-safe due to GIL +import threading + +data = [1, 2, 3, 4, 5] + +def worker(): + print(sum(data)) + +threads = [threading.Thread(target=worker) for _ in range(5)] +for t in threads: + t.start() +for t in threads: + t.join() +``` + +```rust !! rs +// Rust: Use Arc for thread-safe reference counting +use std::sync::Arc; +use std::thread; + +fn main() { + let data = Arc::new(vec![1, 2, 3, 4, 5]); + let mut handles = vec![]; + + for _ in 0..5 { + let data_clone = Arc::clone(&data); + let handle = thread::spawn(move || { + let sum: i32 = data_clone.iter().sum(); + println!("Sum: {}", sum); + }); + handles.push(handle); + } + + for handle in handles { + handle.join().unwrap(); + } +} +``` + + +## Comparison: Rust vs Python Concurrency + +### Performance Comparison + + +```python !! py +# Python: GIL limits CPU-bound parallelism +import threading +import time + +def cpu_bound(): + count = 0 + for i in range(10_000_000): + count += i + return count + +start = time.time() +threads = [threading.Thread(target=cpu_bound) for _ in range(4)] +for t in threads: + t.start() +for t in threads: + t.join() +print(f"Time with threads: {time.time() - start:.2}s") + +# Faster with multiprocessing +from multiprocessing import Process + +start = time.time() +processes = [Process(target=cpu_bound) for _ in range(4)] +for p in processes: + p.start() +for p in processes: + p.join() +print(f"Time with processes: {time.time() - start:.2}s") +``` + +```rust !! rs +// Rust: True parallelism, no GIL +use std::thread; +use std::time::Instant; + +fn cpu_bound() -> i64 { + (0..10_000_000).fold(0i64, |acc, i| acc + i as i64) +} + +fn main() { + let start = Instant::now(); + + let mut handles = vec![]; + for _ in 0..4 { + let handle = thread::spawn(|| cpu_bound()); + handles.push(handle); + } + + for handle in handles { + handle.join().unwrap(); + } + + println!("Time: {:?}", start.elapsed()); + // Much faster than Python with threads! +} +``` + + +### Safety Comparison + + +```python !! py +# Python: Data races possible without locks +import threading + +counter = [0] + +def unsafe_increment(): + for _ in range(1000): + counter[0] += 1 # Potential race condition + +threads = [threading.Thread(target=unsafe_increment) for _ in range(10)] +for t in threads: + t.start() +for t in threads: + t.join() + +print(f"Counter: {counter[0]}") # May not be 10000! +``` + +```rust !! rs +// Rust: Compiler prevents data races! +use std::sync::{Arc, Mutex}; +use std::thread; + +fn main() { + let counter = Arc::new(Mutex::new(0)); + + // This won't compile without Mutex: + // let counter = Arc::new(0); + // let handle = thread::spawn(|| { + // *counter += 1; // ERROR: can't modify across threads + // }); + + let mut handles = vec![]; + for _ in 0..10 { + let counter_clone = Arc::clone(&counter); + let handle = thread::spawn(move || { + for _ in 0..1000 { + let mut num = counter_clone.lock().unwrap(); + *num += 1; + } + }); + handles.push(handle); + } + + for handle in handles { + handle.join().unwrap(); + } + + println!("Counter: {}", *counter.lock().unwrap()); + // Always 10000 - no data races! +} +``` + + +## Common Patterns + +### Work Stealing with Channels + + +```python !! py +# Python: Work queue pattern +import threading +import queue + +def worker(worker_id, q): + while True: + item = q.get() + if item is None: + break + print(f"Worker {worker_id}: processing {item}") + q.task_done() + +work_queue = queue.Queue() +for i in range(10): + work_queue.put(i) + +threads = [] +for i in range(3): + t = threading.Thread(target=worker, args=(i, work_queue)) + t.start() + threads.append(t) + +work_queue.join() + +for _ in threads: + work_queue.put(None) + +for t in threads: + t.join() +``` + +```rust !! rs +// Rust: Work queue with channels +use std::sync::mpsc; +use std::thread; + +fn worker(worker_id: i32, receiver: mpsc::Receiver) { + for item in receiver { + println!("Worker {}: processing {}", worker_id, item); + } +} + +fn main() { + let (tx, rx) = mpsc::channel(); + + // Send work items + for i in 0..10 { + tx.send(i).unwrap(); + } + // Drop tx to signal completion + drop(tx); + + // Split receiver for multiple workers + let (tx1, rx1) = mpsc::channel(); + let (tx2, rx2) = mpsc::channel(); + let (tx3, rx3) = mpsc::channel(); + + thread::spawn(|| worker(0, rx1)); + thread::spawn(|| worker(1, rx2)); + thread::spawn(|| worker(2, rx3)); +} +``` + + +## Best Practices + +### DO: + +1. **Use channels for message passing** - Prefer over shared state +2. **Use `Arc>` for shared state** - When necessary +3. **Move data into threads** - Use `move` closures +4. **Handle join errors** - Don't use `.unwrap()` in production +5. **Prefer scoped threads** - Use `crossbeam` when possible + +### DON'T: + +1. **Share state without synchronization** - Data races are undefined behavior +2. **Use Rc across threads** - Use Arc instead +3. **Forget to join threads** - Or they'll become detached +4. **Panic in threads without handling** - Can abort your program +5. **Ignore deadlocks** - Design lock hierarchies to avoid them + +## Summary + +- **Threads** in Rust provide true parallelism +- **Channels** enable safe message passing +- **Mutex** provides thread-safe shared state +- **Arc** enables shared ownership across threads +- **Rust prevents data races at compile time** +- **Python's GIL** limits true parallelism in threads +- **Rust's fearless concurrency** is safer and faster + +## Practice Exercises + +1. Build a parallel web scraper using threads +2. Implement a parallel map-reduce pattern +3. Create a thread-safe cache with `Arc>` +4. Build a work queue with multiple workers +5. Implement a producer-consumer system with backpressure + +## Congratulations! + +You've completed all 15 modules of the Python → Rust learning path! You now have: +- Understanding of Rust's ownership and type system +- Knowledge of pattern matching and error handling +- Experience with smart pointers and lifetimes +- Skills in concurrent programming +- Ability to write safe, performant Rust code + +Continue your journey by building real projects and exploring the Rust ecosystem! + +## Additional Resources + +- [The Rust Book](https://doc.rust-lang.org/book/) +- [Rust by Example](https://doc.rust-lang.org/rust-by-example/) +- [Rust Standard Library](https://doc.rust-lang.org/std/) +- [Crates.io](https://crates.io/) for packages diff --git a/content/docs/py2rust/module-15-threads.zh-cn.mdx b/content/docs/py2rust/module-15-threads.zh-cn.mdx new file mode 100644 index 0000000..6880d1f --- /dev/null +++ b/content/docs/py2rust/module-15-threads.zh-cn.mdx @@ -0,0 +1,296 @@ +--- +title: "模块 15: 线程与并发 - 无畏并发" +description: "掌握 Rust 的线程模型、通道、互斥锁和无畏并发,与 Python 的线程和多进程进行比较。" +--- + +# 模块 15: 线程与并发 - 无畏并发 + +在最后一个模块中,你将学习 Rust 如何处理**线程和并发**,通过编译时保证防止数据竞争,提供**无畏并发**。 + +## 学习目标 + +完成本模块后,你将能够: +- 在 Rust 中生成线程 +- 使用通道进行消息传递 +- 使用 Mutex 进行共享状态 +- 理解 Rust 在并发上下文中的所有权模型 +- 比较 Rust 的并发与 Python 的线程 +- 构建安全的并发程序 + +## 背景:Python vs Rust 并发 + + +```python !! py +# Python: GIL(全局解释器锁)限制真正的并行 +import threading + +def worker(): + print("Working...") + +# 由于 GIL,线程不能真正并行运行 +t = threading.Thread(target=worker) +t.start() +t.join() + +# 要获得真正的并行,使用多进程 +from multiprocessing import Process +p = Process(target=worker) +p.start() +p.join() +``` + +```rust !! rs +// Rust: 没有 GIL 的真正并行 +use std::thread; + +fn worker() { + println!("Working..."); +} + +fn main() { + // 线程真正并行运行 + let t = thread::spawn(|| worker()); + t.join().unwrap(); + + // 没有 GIL - 完全利用 CPU! +} +``` + + +## 生成线程 + + +```python !! py +# Python: 基本线程 +import threading + +def worker(name): + print(f"Worker {name}") + +thread = threading.Thread(target=worker, args=("Alice",)) +thread.start() +thread.join() +``` + +```rust !! rs +// Rust: 生成线程 +use std::thread; + +fn worker(name: &str) { + println!("Worker {}", name); +} + +fn main() { + let handle = thread::spawn(|| { + worker("Alice"); + }); + + handle.join().unwrap(); +} +``` + + +## 通道:消息传递 + + +```python !! py +# Python: Queue 用于线程通信 +import threading +import queue + +q = queue.Queue() + +def producer(): + for i in range(5): + q.put(i) + +def consumer(): + while True: + item = q.get() + print(f"Received: {item}") + +t1 = threading.Thread(target=producer) +t2 = threading.Thread(target=consumer) + +t1.start() +t2.start() +t1.join() +t2.join() +``` + +```rust !! rs +// Rust: mpsc 通道用于消息传递 +use std::sync::mpsc; +use std::thread; + +fn main() { + let (tx, rx) = mpsc::channel(); + + let producer = thread::spawn(move || { + for i in 0..5 { + tx.send(i).unwrap(); + } + }); + + let consumer = thread::spawn(move || { + for received in rx { + println!("Received: {}", received); + } + }); + + producer.join().unwrap(); + consumer.join().unwrap(); +} +``` + + +## 共享状态: Mutex + +当需要**共享可变状态**时,使用 `Mutex` 进行线程安全访问。 + + +```python !! py +# Python: threading.Lock 用于共享状态 +import threading + +counter = [0] +lock = threading.Lock() + +def increment(): + for _ in range(1000): + with lock: + counter[0] += 1 + +threads = [] +for _ in range(5): + t = threading.Thread(target=increment) + t.start() + threads.append(t) + +for t in threads: + t.join() + +print(f"Counter: {counter[0]}") +``` + +```rust !! rs +// Rust: Mutex 用于共享可变状态 +use std::sync::{Arc, Mutex}; +use std::thread; + +fn main() { + let counter = Arc::new(Mutex::new(0)); + let mut handles = vec![]; + + for _ in 0..5 { + let counter_clone = Arc::clone(&counter); + let handle = thread::spawn(move || { + for _ in 0..1000 { + let mut num = counter_clone.lock().unwrap(); + *num += 1; + } + }); + handles.push(handle); + } + + for handle in handles { + handle.join().unwrap(); + } + + println!("Counter: {}", *counter.lock().unwrap()); +} +``` + + +## 比较: Rust vs Python 并发 + + +```python !! py +# Python: GIL 限制 CPU 密集型并行 +import threading +import time + +def cpu_bound(): + count = 0 + for i in range(10_000_000): + count += i + return count + +start = time.time() +threads = [threading.Thread(target=cpu_bound) for _ in range(4)] +for t in threads: + t.start() +for t in threads: + t.join() +print(f"Time: {time.time() - start:.2}s") +``` + +```rust !! rs +// Rust: 真正并行,没有 GIL +use std::thread; +use std::time::Instant; + +fn cpu_bound() -> i64 { + (0..10_000_000).fold(0i64, |acc, i| acc + i as i64) +} + +fn main() { + let start = Instant::now(); + + let mut handles = vec![]; + for _ in 0..4 { + let handle = thread::spawn(|| cpu_bound()); + handles.push(handle); + } + + for handle in handles { + handle.join().unwrap(); + } + + println!("Time: {:?}", start.elapsed()); + // 比 Python 线程快得多! +} +``` + + +## 最佳实践 + +### 应该做: + +1. **使用通道进行消息传递** - 优先于共享状态 +2. **对共享状态使用 `Arc>`** - 必要时 +3. **将数据移入线程** - 使用 `move` 闭包 +4. **处理 join 错误** - 生产环境中不要使用 `.unwrap()` + +### 不应该做: + +1. **在没有同步的情况下共享状态** - 数据竞争是未定义行为 +2. **跨线程使用 Rc** - 改用 Arc +3. **忘记 join 线程** - 否则它们会变成分离的 + +## 总结 + +- **线程**在 Rust 中提供真正的并行 +- **通道**实现安全消息传递 +- **Mutex** 提供线程安全的共享状态 +- **Arc** 启用跨线程的共享所有权 +- **Rust 在编译时防止数据竞争** +- **Python 的 GIL** 限制线程中的真正并行 +- **Rust 的无畏并发**更安全更快 + +## 练习 + +1. 使用线程构建并行网页爬虫 +2. 实现并行 map-reduce 模式 +3. 创建线程安全的缓存 + +## 恭喜! + +你已完成 Python → Rust 学习路径的全部 15 个模块!你现在拥有: +- 对 Rust 所有权和类型系统的理解 +- 模式匹配和错误处理的知识 +- 智能指针和生命周期的经验 +- 并发编程技能 +- 编写安全、高性能 Rust 代码的能力 + +继续你的旅程,构建真实项目并探索 Rust 生态系统! diff --git a/content/docs/py2rust/module-15-threads.zh-tw.mdx b/content/docs/py2rust/module-15-threads.zh-tw.mdx new file mode 100644 index 0000000..cf57dbd --- /dev/null +++ b/content/docs/py2rust/module-15-threads.zh-tw.mdx @@ -0,0 +1,189 @@ +--- +title: "模組 15: 線程與並發 - 無畏並發" +description: "掌握 Rust 的線程模型、通道、互斥鎖和無畏並發,與 Python 的線程和多進程進行比較。" +--- + +# 模組 15: 線程與並發 - 無畏並發 + +在最後一個模組中,你將學習 Rust 如何處理**線程和並發**,通過編譯時保證防止數據競爭,提供**無畏並發**。 + +## 學習目標 + +完成本模組後,你將能夠: +- 在 Rust 中生成線程 +- 使用通道進行消息傳遞 +- 使用 Mutex 進行共享狀態 +- 理解 Rust 在並發上下文中的所有權模型 +- 比較 Rust 的並發與 Python 的線程 + +## 生成線程 + + +```python !! py +# Python: 基本線程 +import threading + +def worker(name): + print(f"Worker {name}") + +thread = threading.Thread(target=worker, args=("Alice",)) +thread.start() +thread.join() +``` + +```rust !! rs +// Rust: 生成線程 +use std::thread; + +fn worker(name: &str) { + println!("Worker {}", name); +} + +fn main() { + let handle = thread::spawn(|| { + worker("Alice"); + }); + + handle.join().unwrap(); +} +``` + + +## 通道:消息傳遞 + + +```python !! py +# Python: Queue 用於線程通信 +import threading +import queue + +q = queue.Queue() + +def producer(): + for i in range(5): + q.put(i) + +def consumer(): + while True: + item = q.get() + print(f"Received: {item}") + +t1 = threading.Thread(target=producer) +t2 = threading.Thread(target=consumer) +t1.start() +t2.start() +t1.join() +t2.join() +``` + +```rust !! rs +// Rust: mpsc 通道用於消息傳遞 +use std::sync::mpsc; +use std::thread; + +fn main() { + let (tx, rx) = mpsc::channel(); + + let producer = thread::spawn(move || { + for i in 0..5 { + tx.send(i).unwrap(); + } + }); + + let consumer = thread::spawn(move || { + for received in rx { + println!("Received: {}", received); + } + }); + + producer.join().unwrap(); + consumer.join().unwrap(); +} +``` + + +## 共享狀態: Mutex + + +```python !! py +# Python: threading.Lock 用於共享狀態 +import threading + +counter = [0] +lock = threading.Lock() + +def increment(): + for _ in range(1000): + with lock: + counter[0] += 1 + +threads = [] +for _ in range(5): + t = threading.Thread(target=increment) + t.start() + threads.append(t) + +for t in threads: + t.join() + +print(f"Counter: {counter[0]}") +``` + +```rust !! rs +// Rust: Mutex 用於共享可變狀態 +use std::sync::{Arc, Mutex}; +use std::thread; + +fn main() { + let counter = Arc::new(Mutex::new(0)); + let mut handles = vec![]; + + for _ in 0..5 { + let counter_clone = Arc::clone(&counter); + let handle = thread::spawn(move || { + for _ in 0..1000 { + let mut num = counter_clone.lock().unwrap(); + *num += 1; + } + }); + handles.push(handle); + } + + for handle in handles { + handle.join().unwrap(); + } + + println!("Counter: {}", *counter.lock().unwrap()); +} +``` + + +## 最佳實踐 + +### 應該做: + +1. **使用通道進行消息傳遞** - 優先於共享狀態 +2. **對共享狀態使用 `Arc>`** - 必要時 +3. **將數據移入線程** - 使用 `move` 閉包 + +### 不應該做: + +1. **在沒有同步的情況下共享狀態** - 數據競爭是未定義行為 +2. **跨線程使用 Rc** - 改用 Arc + +## 總結 + +- **線程**在 Rust 中提供真正的並行 +- **通道**實現安全消息傳遞 +- **Mutex** 提供線程安全的共享狀態 +- **Rust 在編譯時防止數據競爭** +- **Rust 的無畏並發**更安全更快 + +## 練習 + +1. 使用線程構建並行網頁爬蟲 +2. 實現並行 map-reduce 模式 + +## 恭喜! + +你已完成 Python → Rust 學習路徑的全部 15 個模組!繼續你的旅程,構建真實項目並探索 Rust 生態系統! diff --git a/content/docs/py2rust/module-16-async.mdx b/content/docs/py2rust/module-16-async.mdx new file mode 100644 index 0000000..15ad689 --- /dev/null +++ b/content/docs/py2rust/module-16-async.mdx @@ -0,0 +1,682 @@ +--- +title: "Module 16: Async Programming" +description: "Master asynchronous programming in Rust using async/await, tokio, and the Future trait, with comparisons to Python's asyncio" +--- + +# Module 16: Async Programming + +## Learning Objectives + +By the end of this module, you'll be able to: +- Understand Rust's async/await syntax and how it compares to Python's asyncio +- Work with the Future trait and async runtime +- Use Tokio for practical async applications +- Handle async streams and channels +- Avoid common async pitfalls + +## Introduction to Async in Rust + +Python's `asyncio` uses coroutines with `async`/`await` syntax. Rust has similar syntax but very different underlying mechanics. + +### Key Differences + + +```python !! py +# Python - Coroutines are implicit +import asyncio + +async def fetch_data(url: str) -> dict: + await asyncio.sleep(1) # Simulate network delay + return {"status": "ok", "data": [1, 2, 3]} + +# Python coroutines start paused and need the event loop +async def main(): + result = await fetch_data("https://api.example.com") + print(result) + +asyncio.run(main()) +``` + +```rust !! rs +// Rust - Futures are explicit and lazy +use tokio::time::{sleep, Duration}; + +async fn fetch_data(url: &str) -> serde_json::Value { + // Tokio provides async sleep + sleep(Duration::from_secs(1)).await; + json!({"status": "ok", "data": [1, 2, 3]}) +} + +// Rust needs a runtime at the entry point +#[tokio::main] +async fn main() { + let result = fetch_data("https://api.example.com").await; + println!("{}", result); +} +``` + + +**Critical Insight:** In Python, `async def` creates a coroutine object that starts paused. In Rust, `async fn` returns a `Future` that does nothing until polled. + +## The Future Trait + +Understanding the `Future` trait is crucial for async Rust. + + +```python !! py +# Python - Coroutines are managed by the event loop +import asyncio + +async def compute(): + print("Computing...") + await asyncio.sleep(0.1) + return 42 + +# Event loop handles everything +coro = compute() # Returns coroutine object +print(type(coro)) # + +result = asyncio.run(coro) +``` + +```rust !! rs +// Rust - Future trait requires explicit polling +use std::future::Future; +use std::pin::Pin; +use std::task::{Context, Poll}; + +// Simplified Future trait (actual std::future::Future) +pub trait SimpleFuture { + type Output; + fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll; +} + +// Async fn desugars to a type implementing Future +async fn compute() -> i32 { + println!("Computing..."); + tokio::time::sleep(Duration::from_millis(100)).await; + 42 +} + +// The runtime handles polling +#[tokio::main] +async fn main() { + let future = compute(); // Returns Future + let result = future.await; // Runtime polls until ready + println!("{}", result); +} +``` + + +## Async Runtime: Tokio + +Tokio is the most popular async runtime in Rust, similar to how asyncio is standard in Python. + +### Basic Tokio Usage + + +```python !! py +# Python - asyncio tasks +import asyncio + +async def worker(name: str, duration: int): + print(f"{name} started") + await asyncio.sleep(duration) + print(f"{name} completed after {duration}s") + +async def main(): + # Create tasks that run concurrently + task1 = asyncio.create_task(worker("Task 1", 2)) + task2 = asyncio.create_task(worker("Task 2", 1)) + + # Wait for both to complete + await asyncio.gather(task1, task2) + +asyncio.run(main()) +``` + +```rust !! rs +// Rust - Tokio tasks +use tokio::time::{sleep, Duration}; + +async fn worker(name: &str, duration: u64) { + println!("{} started", name); + sleep(Duration::from_secs(duration)).await; + println!("{} completed after {}s", name, duration); +} + +#[tokio::main] +async fn main() { + // Spawn tasks that run concurrently + let task1 = tokio::spawn(worker("Task 1", 2)); + let task2 = tokio::spawn(worker("Task 2", 1)); + + // Wait for both to complete + let (result1, result2) = tokio::join!(task1, task2); + + // Handle results (tasks return Result) + result1.unwrap(); + result2.unwrap(); +} +``` + + +### Async Channels + + +```python !! py +# Python - asyncio queues +import asyncio + +async def producer(queue: asyncio.Queue): + for i in range(5): + await asyncio.sleep(0.1) + await queue.put(i) + print(f"Produced: {i}") + await queue.put(None) # Signal end + +async def consumer(queue: asyncio.Queue): + while True: + item = await queue.get() + if item is None: + break + print(f"Consumed: {item}") + queue.task_done() + +async def main(): + queue = asyncio.Queue(maxsize=10) + producer_task = asyncio.create_task(producer(queue)) + consumer_task = asyncio.create_task(consumer(queue)) + await asyncio.gather(producer_task, consumer_task) + +asyncio.run(main()) +``` + +```rust !! rs +// Rust - Tokio channels +use tokio::sync::mpsc; +use tokio::time::{sleep, Duration}; + +async fn producer(mut tx: mpsc::Sender) { + for i in 0..5 { + sleep(Duration::from_millis(100)).await; + tx.send(i).await.unwrap(); + println!("Produced: {}", i); + } + // Drop sender to signal completion +} + +async fn consumer(mut rx: mpsc::Receiver) { + while let Some(item) = rx.recv().await { + println!("Consumed: {}", item); + } +} + +#[tokio::main] +async fn main() { + let (tx, rx) = mpsc::channel(10); + + let producer_task = tokio::spawn(producer(tx)); + let consumer_task = tokio::spawn(consumer(rx)); + + tokio::join!(producer_task, consumer_task); +} +``` + + +## Async Streams + +Rust's async streams are similar to Python's async generators. + + +```python !! py +# Python - Async generators +import asyncio + +async def countdown(n: int): + """Async generator yielding values""" + for i in range(n, 0, -1): + await asyncio.sleep(0.1) + yield i + +async def main(): + async for value in countdown(5): + print(f"Count: {value}") + +asyncio.run(main()) +``` + +```rust !! rs +// Rust - Async streams using futures::stream +use futures::stream::{self, StreamExt}; +use tokio::time::{sleep, Duration}; + +async fn countdown(n: i32) -> impl Stream { + // Create a stream from an iterator with delays + stream::iter((1..=n).rev()) + .then(|n| async move { + sleep(Duration::from_millis(100)).await; + n + }) +} + +#[tokio::main] +async fn main() { + let mut stream = countdown(5).await; + + while let Some(value) = stream.next().await { + println!("Count: {}", value); + } +} +``` + + +## Async HTTP Client + + +```python !! py +# Python - aiohttp client +import aiohttp +import asyncio + +async def fetch_url(session: aiohttp.ClientSession, url: str) -> str: + async with session.get(url) as response: + return await response.text() + +async def main(): + urls = [ + "https://httpbin.org/get", + "https://httpbin.org/delay/1", + "https://httpbin.org/json", + ] + + async with aiohttp.ClientSession() as session: + tasks = [fetch_url(session, url) for url in urls] + results = await asyncio.gather(*tasks) + for result in results: + print(f"Fetched {len(result)} bytes") + +asyncio.run(main()) +``` + +```rust !! rs +// Rust - Reqwest async client +use reqwest; +use futures::future::join_all; + +async fn fetch_url(url: &str) -> Result { + let response = reqwest::get(url).await?; + let text = response.text().await?; + Ok(text) +} + +#[tokio::main] +async fn main() { + let urls = [ + "https://httpbin.org/get", + "https://httpbin.org/delay/1", + "https://httpbin.org/json", + ]; + + let fetch_futures: Vec<_> = urls.iter() + .map(|&url| fetch_url(url)) + .collect(); + + let results = join_all(fetch_futures).await; + + for result in results { + match result { + Ok(text) => println!("Fetched {} bytes", text.len()), + Err(e) => println!("Error: {}", e), + } + } +} +``` + + +## Async File I/O + + +```python !! py +# Python - aiofiles for async file operations +import aiofiles +import asyncio + +async def process_file(filename: str): + async with aiofiles.open(filename, 'r') as f: + content = await f.read() + lines = content.splitlines() + print(f"Read {len(lines)} lines from {filename}") + +async def main(): + await asyncio.gather( + process_file('file1.txt'), + process_file('file2.txt'), + ) + +asyncio.run(main()) +``` + +```rust !! rs +// Rust - Tokio fs +use tokio::fs::File; +use tokio::io::{AsyncBufReadExt, BufReader}; + +async fn process_file(filename: &str) -> Result<(), Box> { + let file = File::open(filename).await?; + let reader = BufReader::new(file); + let mut lines = reader.lines(); + let mut count = 0; + + while let Some(_line) = lines.next_line().await? { + count += 1; + } + + println!("Read {} lines from {}", count, filename); + Ok(()) +} + +#[tokio::main] +async fn main() { + let results = tokio::join!( + process_file("file1.txt"), + process_file("file2.txt"), + ); + + results.0.unwrap(); + results.1.unwrap(); +} +``` + + +## Error Handling in Async + + +```python !! py +# Python - Exception handling in async +import asyncio + +async def fetch_data(url: str) -> dict: + if "error" in url: + raise ValueError("Invalid URL") + await asyncio.sleep(0.1) + return {"data": "success"} + +async def main(): + try: + result = await fetch_data("https://error.api") + except ValueError as e: + print(f"Caught error: {e}") + else: + print(result) + +asyncio.run(main()) +``` + +```rust !! rs +// Rust - Result types in async +use tokio::time::{sleep, Duration}; + +async fn fetch_data(url: &str) -> Result> { + if url.contains("error") { + Err("Invalid URL".into()) + } else { + sleep(Duration::from_millis(100)).await; + Ok(json!({"data": "success"})) + } +} + +#[tokio::main] +async fn main() { + match fetch_data("https://error.api").await { + Ok(result) => println!("{}", result), + Err(e) => println!("Caught error: {}", e), + } +} +``` + + +## Common Async Pitfalls + +### 1. Blocking in Async Context + + +```python !! py +# Python - Blocking in async (bad practice) +import asyncio +import time + +async def bad_blocking(): + time.sleep(2) # Blocks the entire event loop! + print("Done") + +async def good_nonblocking(): + await asyncio.sleep(2) # Yields control + print("Done") +``` + +```rust !! rs +// Rust - Use spawn_blocking for CPU-bound work +use tokio::task::spawn_blocking; +use tokio::time::{sleep, Duration}; + +async fn bad_blocking() { + std::thread::sleep(Duration::from_secs(2)); // Blocks runtime! + println!("Done"); +} + +async fn good_nonblocking() { + sleep(Duration::from_secs(2)).await; // Yields + println!("Done"); +} + +async fn good_cpu_bound() { + // Offload blocking work to thread pool + let result = spawn_blocking(|| { + // CPU-intensive work here + 42 + }).await.unwrap(); + + println!("Result: {}", result); +} + +#[tokio::main] +async fn main() { + good_nonblocking().await; + good_cpu_bound().await; +} +``` + + +### 2. Cancellation + + +```python !! py +# Python - Task cancellation +import asyncio + +async def cancellable_task(): + try: + print("Task started") + await asyncio.sleep(5) + print("Task completed") + except asyncio.CancelledError: + print("Task was cancelled") + raise + +async def main(): + task = asyncio.create_task(cancellable_task()) + await asyncio.sleep(1) + task.cancel() + try: + await task + except asyncio.CancelledError: + print("Main caught cancellation") + +asyncio.run(main()) +``` + +```rust !! rs +// Rust - Cooperative cancellation with tokio::select! +use tokio::time::{sleep, Duration, timeout}; +use tokio::signal::ctrl_c; + +async fn cancellable_task() -> Result<(), Box> { + println!("Task started"); + + // Use select! to wait for multiple futures + tokio::select! { + _ = sleep(Duration::from_secs(5)) => { + println!("Task completed"); + } + _ = ctrl_c() => { + println!("Task received Ctrl+C"); + return Err("Cancelled".into()); + } + } + + Ok(()) +} + +#[tokio::main] +async fn main() { + // Cancel after timeout + match timeout(Duration::from_secs(1), cancellable_task()).await { + Ok(Ok(())) => println!("Task succeeded"), + Ok(Err(e)) => println!("Task error: {}", e), + Err(_) => println!("Task timed out"), + } +} +``` + + +## Practical Example: Web Scraper + + +```python !! py +# Python - Async web scraper +import asyncio +import aiohttp +from bs4 import BeautifulSoup + +async def scrape_page(session: aiohttp.ClientSession, url: str) -> dict: + async with session.get(url) as response: + html = await response.text() + soup = BeautifulSoup(html, 'html.parser') + return { + 'url': url, + 'title': soup.title.string if soup.title else 'No title', + 'links': len(soup.find_all('a')) + } + +async def main(urls: list[str]): + async with aiohttp.ClientSession() as session: + tasks = [scrape_page(session, url) for url in urls] + results = await asyncio.gather(*tasks) + return results + +if __name__ == "__main__": + urls = [ + "https://example.com", + "https://example.org", + "https://example.net", + ] + results = asyncio.run(main(urls)) + for result in results: + print(f"{result['url']}: {result['title']} ({result['links']} links)") +``` + +```rust !! rs +// Rust - Concurrent web scraper +use reqwest::Client; +use scraper::{Html, Selector}; +use futures::future::join_all; + +struct ScrapedData { + url: String, + title: String, + links: usize, +} + +async fn scrape_page(client: &Client, url: &str) -> Result> { + let response = client.get(url).send().await?; + let html = response.text().await?; + + let document = Html::parse_document(&html); + let title_selector = Selector::parse("title").unwrap(); + let link_selector = Selector::parse("a").unwrap(); + + let title = document + .select(&title_selector) + .next() + .map(|t| t.text().collect::()) + .unwrap_or_else(|| "No title".to_string()); + + let links = document.select(&link_selector).count(); + + Ok(ScrapedData { + url: url.to_string(), + title, + links, + }) +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + let urls = [ + "https://example.com", + "https://example.org", + "https://example.net", + ]; + + let client = Client::new(); + let scrape_futures: Vec<_> = urls.iter() + .map(|&url| scrape_page(&client, url)) + .collect(); + + let results = join_all(scrape_futures).await; + + for result in results { + match result { + Ok(data) => println!("{}: {} ({} links)", data.url, data.title, data.links), + Err(e) => println!("Error scraping: {}", e), + } + } + + Ok(()) +} +``` + + +## Key Takeaways + +### Async in Python +- Built on coroutines and event loops +- Implicit scheduling by the runtime +- Easy to use, but runtime overhead +- Uses `async`/`await` syntax + +### Async in Rust +- Zero-cost abstractions with Futures +- Explicit runtime (Tokio) requirement +- Compiler enforces safe async code +- No runtime overhead when not using async +- Requires `#[tokio::main]` for async entry points + +### When to Use Async +- **Use async** for I/O-bound operations (network, files) +- **Use sync** for CPU-bound tasks (or `spawn_blocking`) +- **Avoid async** for simple sequential code +- **Prefer channels** over shared state + +## Exercises + +1. Implement a concurrent file downloader that downloads multiple files in parallel +2. Build a chat server using async channels +3. Create an async database connection pool +4. Implement retry logic with exponential backoff +5. Build a concurrent web scraper with rate limiting + +## Next Module + +In Module 17, we'll explore **Testing in Rust**, including unit tests, integration tests, property-based testing, and mocking strategies. diff --git a/content/docs/py2rust/module-16-async.zh-cn.mdx b/content/docs/py2rust/module-16-async.zh-cn.mdx new file mode 100644 index 0000000..0028f0d --- /dev/null +++ b/content/docs/py2rust/module-16-async.zh-cn.mdx @@ -0,0 +1,682 @@ +--- +title: "模块 16: 异步编程" +description: "掌握 Rust 中的异步编程,包括 async/await、tokio 和 Future trait,并与 Python 的 asyncio 进行对比" +--- + +# 模块 16: 异步编程 + +## 学习目标 + +完成本模块后,你将能够: +- 理解 Rust 的 async/await 语法及其与 Python asyncio 的区别 +- 使用 Future trait 和异步运行时 +- 使用 Tokio 构建实际异步应用 +- 处理异步流和通道 +- 避免常见的异步编程陷阱 + +## Rust 异步简介 + +Python 的 `asyncio` 使用带有 `async`/`await` 语法的协程。Rust 有类似的语法,但底层机制完全不同。 + +### 关键区别 + + +```python !! py +# Python - 协程是隐式的 +import asyncio + +async def fetch_data(url: str) -> dict: + await asyncio.sleep(1) # 模拟网络延迟 + return {"status": "ok", "data": [1, 2, 3]} + +# Python 协程启动时即暂停,需要事件循环 +async def main(): + result = await fetch_data("https://api.example.com") + print(result) + +asyncio.run(main()) +``` + +```rust !! rs +// Rust - Future 是显式的且是惰性的 +use tokio::time::{sleep, Duration}; + +async fn fetch_data(url: &str) -> serde_json::Value { + // Tokio 提供异步睡眠 + sleep(Duration::from_secs(1)).await; + json!({"status": "ok", "data": [1, 2, 3]}) +} + +// Rust 需要在入口点显式运行时 +#[tokio::main] +async fn main() { + let result = fetch_data("https://api.example.com").await; + println!("{}", result); +} +``` + + +**核心洞察:** 在 Python 中,`async def` 创建一个暂停的协程对象。在 Rust 中,`async fn` 返回一个 `Future`,在被轮询之前什么都不做。 + +## Future Trait + +理解 `Future` trait 对于掌握 Rust 异步编程至关重要。 + + +```python !! py +# Python - 事件循环管理协程 +import asyncio + +async def compute(): + print("Computing...") + await asyncio.sleep(0.1) + return 42 + +# 事件循环处理一切 +coro = compute() # 返回协程对象 +print(type(coro)) # + +result = asyncio.run(coro) +``` + +```rust !! rs +// Rust - Future trait 需要显式轮询 +use std::future::Future; +use std::pin::Pin; +use std::task::{Context, Poll}; + +// 简化的 Future trait(实际的 std::future::Future) +pub trait SimpleFuture { + type Output; + fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll; +} + +// async fn 脱糖为实现 Future 的类型 +async fn compute() -> i32 { + println!("Computing..."); + tokio::time::sleep(Duration::from_millis(100)).await; + 42 +} + +// 运行时处理轮询 +#[tokio::main] +async fn main() { + let future = compute(); // 返回 Future + let result = future.await; // 运行时轮询直到就绪 + println!("{}", result); +} +``` + + +## 异步运行时: Tokio + +Tokio 是 Rust 中最流行的异步运行时,类似于 asyncio 在 Python 中的地位。 + +### 基本 Tokio 使用 + + +```python !! py +# Python - asyncio 任务 +import asyncio + +async def worker(name: str, duration: int): + print(f"{name} started") + await asyncio.sleep(duration) + print(f"{name} completed after {duration}s") + +async def main(): + # 创建并发运行的任务 + task1 = asyncio.create_task(worker("Task 1", 2)) + task2 = asyncio.create_task(worker("Task 2", 1)) + + # 等待两者完成 + await asyncio.gather(task1, task2) + +asyncio.run(main()) +``` + +```rust !! rs +// Rust - Tokio 任务 +use tokio::time::{sleep, Duration}; + +async fn worker(name: &str, duration: u64) { + println!("{} started", name); + sleep(Duration::from_secs(duration)).await; + println!("{} completed after {}s", name, duration); +} + +#[tokio::main] +async fn main() { + // 生成并发运行的任务 + let task1 = tokio::spawn(worker("Task 1", 2)); + let task2 = tokio::spawn(worker("Task 2", 1)); + + // 等待两者完成 + let (result1, result2) = tokio::join!(task1, task2); + + // 处理结果(任务返回 Result) + result1.unwrap(); + result2.unwrap(); +} +``` + + +### 异步通道 + + +```python !! py +# Python - asyncio 队列 +import asyncio + +async def producer(queue: asyncio.Queue): + for i in range(5): + await asyncio.sleep(0.1) + await queue.put(i) + print(f"Produced: {i}") + await queue.put(None) # 发送结束信号 + +async def consumer(queue: asyncio.Queue): + while True: + item = await queue.get() + if item is None: + break + print(f"Consumed: {item}") + queue.task_done() + +async def main(): + queue = asyncio.Queue(maxsize=10) + producer_task = asyncio.create_task(producer(queue)) + consumer_task = asyncio.create_task(consumer(queue)) + await asyncio.gather(producer_task, consumer_task) + +asyncio.run(main()) +``` + +```rust !! rs +// Rust - Tokio 通道 +use tokio::sync::mpsc; +use tokio::time::{sleep, Duration}; + +async fn producer(mut tx: mpsc::Sender) { + for i in 0..5 { + sleep(Duration::from_millis(100)).await; + tx.send(i).await.unwrap(); + println!("Produced: {}", i); + } + // 丢弃 sender 以信号完成 +} + +async fn consumer(mut rx: mpsc::Receiver) { + while let Some(item) = rx.recv().await { + println!("Consumed: {}", item); + } +} + +#[tokio::main] +async fn main() { + let (tx, rx) = mpsc::channel(10); + + let producer_task = tokio::spawn(producer(tx)); + let consumer_task = tokio::spawn(consumer(rx)); + + tokio::join!(producer_task, consumer_task); +} +``` + + +## 异步流 + +Rust 的异步流类似于 Python 的异步生成器。 + + +```python !! py +# Python - 异步生成器 +import asyncio + +async def countdown(n: int): + """异步生成器产生值""" + for i in range(n, 0, -1): + await asyncio.sleep(0.1) + yield i + +async def main(): + async for value in countdown(5): + print(f"Count: {value}") + +asyncio.run(main()) +``` + +```rust !! rs +// Rust - 使用 futures::stream 的异步流 +use futures::stream::{self, StreamExt}; +use tokio::time::{sleep, Duration}; + +async fn countdown(n: i32) -> impl Stream { + // 从迭代器创建带延迟的流 + stream::iter((1..=n).rev()) + .then(|n| async move { + sleep(Duration::from_millis(100)).await; + n + }) +} + +#[tokio::main] +async fn main() { + let mut stream = countdown(5).await; + + while let Some(value) = stream.next().await { + println!("Count: {}", value); + } +} +``` + + +## 异步 HTTP 客户端 + + +```python !! py +# Python - aiohttp 客户端 +import aiohttp +import asyncio + +async def fetch_url(session: aiohttp.ClientSession, url: str) -> str: + async with session.get(url) as response: + return await response.text() + +async def main(): + urls = [ + "https://httpbin.org/get", + "https://httpbin.org/delay/1", + "https://httpbin.org/json", + ] + + async with aiohttp.ClientSession() as session: + tasks = [fetch_url(session, url) for url in urls] + results = await asyncio.gather(*tasks) + for result in results: + print(f"Fetched {len(result)} bytes") + +asyncio.run(main()) +``` + +```rust !! rs +// Rust - Reqwest 异步客户端 +use reqwest; +use futures::future::join_all; + +async fn fetch_url(url: &str) -> Result { + let response = reqwest::get(url).await?; + let text = response.text().await?; + Ok(text) +} + +#[tokio::main] +async fn main() { + let urls = [ + "https://httpbin.org/get", + "https://httpbin.org/delay/1", + "https://httpbin.org/json", + ]; + + let fetch_futures: Vec<_> = urls.iter() + .map(|&url| fetch_url(url)) + .collect(); + + let results = join_all(fetch_futures).await; + + for result in results { + match result { + Ok(text) => println!("Fetched {} bytes", text.len()), + Err(e) => println!("Error: {}", e), + } + } +} +``` + + +## 异步文件 I/O + + +```python !! py +# Python - aiofiles 异步文件操作 +import aiofiles +import asyncio + +async def process_file(filename: str): + async with aiofiles.open(filename, 'r') as f: + content = await f.read() + lines = content.splitlines() + print(f"Read {len(lines)} lines from {filename}") + +async def main(): + await asyncio.gather( + process_file('file1.txt'), + process_file('file2.txt'), + ) + +asyncio.run(main()) +``` + +```rust !! rs +// Rust - Tokio fs +use tokio::fs::File; +use tokio::io::{AsyncBufReadExt, BufReader}; + +async fn process_file(filename: &str) -> Result<(), Box> { + let file = File::open(filename).await?; + let reader = BufReader::new(file); + let mut lines = reader.lines(); + let mut count = 0; + + while let Some(_line) = lines.next_line().await? { + count += 1; + } + + println!("Read {} lines from {}", count, filename); + Ok(()) +} + +#[tokio::main] +async fn main() { + let results = tokio::join!( + process_file("file1.txt"), + process_file("file2.txt"), + ); + + results.0.unwrap(); + results.1.unwrap(); +} +``` + + +## 异步中的错误处理 + + +```python !! py +# Python - 异步中的异常处理 +import asyncio + +async def fetch_data(url: str) -> dict: + if "error" in url: + raise ValueError("Invalid URL") + await asyncio.sleep(0.1) + return {"data": "success"} + +async def main(): + try: + result = await fetch_data("https://error.api") + except ValueError as e: + print(f"Caught error: {e}") + else: + print(result) + +asyncio.run(main()) +``` + +```rust !! rs +// Rust - 异步中的 Result 类型 +use tokio::time::{sleep, Duration}; + +async fn fetch_data(url: &str) -> Result> { + if url.contains("error") { + Err("Invalid URL".into()) + } else { + sleep(Duration::from_millis(100)).await; + Ok(json!({"data": "success"})) + } +} + +#[tokio::main] +async fn main() { + match fetch_data("https://error.api").await { + Ok(result) => println!("{}", result), + Err(e) => println!("Caught error: {}", e), + } +} +``` + + +## 常见异步陷阱 + +### 1. 在异步上下文中阻塞 + + +```python !! py +# Python - 异步中的阻塞(不良实践) +import asyncio +import time + +async def bad_blocking(): + time.sleep(2) # 阻塞整个事件循环! + print("Done") + +async def good_nonblocking(): + await asyncio.sleep(2) # 让出控制权 + print("Done") +``` + +```rust !! rs +// Rust - 对 CPU 密集型工作使用 spawn_blocking +use tokio::task::spawn_blocking; +use tokio::time::{sleep, Duration}; + +async fn bad_blocking() { + std::thread::sleep(Duration::from_secs(2)); // 阻塞运行时! + println!("Done"); +} + +async fn good_nonblocking() { + sleep(Duration::from_secs(2)).await; // 让出控制 + println!("Done"); +} + +async fn good_cpu_bound() { + // 将阻塞工作卸载到线程池 + let result = spawn_blocking(|| { + // CPU 密集型工作在这里 + 42 + }).await.unwrap(); + + println!("Result: {}", result); +} + +#[tokio::main] +async fn main() { + good_nonblocking().await; + good_cpu_bound().await; +} +``` + + +### 2. 取消 + + +```python !! py +# Python - 任务取消 +import asyncio + +async def cancellable_task(): + try: + print("Task started") + await asyncio.sleep(5) + print("Task completed") + except asyncio.CancelledError: + print("Task was cancelled") + raise + +async def main(): + task = asyncio.create_task(cancellable_task()) + await asyncio.sleep(1) + task.cancel() + try: + await task + except asyncio.CancelledError: + print("Main caught cancellation") + +asyncio.run(main()) +``` + +```rust !! rs +// Rust - 使用 tokio::select! 的协作式取消 +use tokio::time::{sleep, Duration, timeout}; +use tokio::signal::ctrl_c; + +async fn cancellable_task() -> Result<(), Box> { + println!("Task started"); + + // 使用 select! 等待多个 future + tokio::select! { + _ = sleep(Duration::from_secs(5)) => { + println!("Task completed"); + } + _ = ctrl_c() => { + println!("Task received Ctrl+C"); + return Err("Cancelled".into()); + } + } + + Ok(()) +} + +#[tokio::main] +async fn main() { + // 超时后取消 + match timeout(Duration::from_secs(1), cancellable_task()).await { + Ok(Ok(())) => println!("Task succeeded"), + Ok(Err(e)) => println!("Task error: {}", e), + Err(_) => println!("Task timed out"), + } +} +``` + + +## 实战示例: 网页爬虫 + + +```python !! py +# Python - 异步网页爬虫 +import asyncio +import aiohttp +from bs4 import BeautifulSoup + +async def scrape_page(session: aiohttp.ClientSession, url: str) -> dict: + async with session.get(url) as response: + html = await response.text() + soup = BeautifulSoup(html, 'html.parser') + return { + 'url': url, + 'title': soup.title.string if soup.title else 'No title', + 'links': len(soup.find_all('a')) + } + +async def main(urls: list[str]): + async with aiohttp.ClientSession() as session: + tasks = [scrape_page(session, url) for url in urls] + results = await asyncio.gather(*tasks) + return results + +if __name__ == "__main__": + urls = [ + "https://example.com", + "https://example.org", + "https://example.net", + ] + results = asyncio.run(main(urls)) + for result in results: + print(f"{result['url']}: {result['title']} ({result['links']} links)") +``` + +```rust !! rs +// Rust - 并发网页爬虫 +use reqwest::Client; +use scraper::{Html, Selector}; +use futures::future::join_all; + +struct ScrapedData { + url: String, + title: String, + links: usize, +} + +async fn scrape_page(client: &Client, url: &str) -> Result> { + let response = client.get(url).send().await?; + let html = response.text().await?; + + let document = Html::parse_document(&html); + let title_selector = Selector::parse("title").unwrap(); + let link_selector = Selector::parse("a").unwrap(); + + let title = document + .select(&title_selector) + .next() + .map(|t| t.text().collect::()) + .unwrap_or_else(|| "No title".to_string()); + + let links = document.select(&link_selector).count(); + + Ok(ScrapedData { + url: url.to_string(), + title, + links, + }) +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + let urls = [ + "https://example.com", + "https://example.org", + "https://example.net", + ]; + + let client = Client::new(); + let scrape_futures: Vec<_> = urls.iter() + .map(|&url| scrape_page(&client, url)) + .collect(); + + let results = join_all(scrape_futures).await; + + for result in results { + match result { + Ok(data) => println!("{}: {} ({} links)", data.url, data.title, data.links), + Err(e) => println!("Error scraping: {}", e), + } + } + + Ok(()) +} +``` + + +## 关键要点 + +### Python 中的异步 +- 基于协程和事件循环 +- 运行时隐式调度 +- 易于使用但有运行时开销 +- 使用 `async`/`await` 语法 + +### Rust 中的异步 +- 基于 Future 的零成本抽象 +- 需要显式运行时(Tokio) +- 编译器强制异步代码安全 +- 不使用异步时无运行时开销 +- 异步入口点需要 `#[tokio::main]` + +### 何时使用异步 +- **使用异步** 处理 I/O 密集型操作(网络、文件) +- **使用同步** 处理 CPU 密集型任务(或使用 `spawn_blocking`) +- **避免异步** 用于简单的顺序代码 +- **优先使用通道** 而非共享状态 + +## 练习 + +1. 实现一个并发文件下载器,并行下载多个文件 +2. 使用异步通道构建聊天服务器 +3. 创建异步数据库连接池 +4. 实现指数退避的重试逻辑 +5. 构建带速率限制的并发网页爬虫 + +## 下一模块 + +在模块 17 中,我们将探索 **Rust 测试**,包括单元测试、集成测试、基于属性的测试和模拟策略。 diff --git a/content/docs/py2rust/module-16-async.zh-tw.mdx b/content/docs/py2rust/module-16-async.zh-tw.mdx new file mode 100644 index 0000000..efaccdd --- /dev/null +++ b/content/docs/py2rust/module-16-async.zh-tw.mdx @@ -0,0 +1,682 @@ +--- +title: "模組 16: 非同步程式設計" +description: "掌握 Rust 中的非同步程式設計,包括 async/await、tokio 和 Future trait,並與 Python 的 asyncio 進行對比" +--- + +# 模組 16: 非同步程式設計 + +## 學習目標 + +完成本模組後,你將能夠: +- 理解 Rust 的 async/await 語法及其與 Python asyncio 的區別 +- 使用 Future trait 和非同步執行時 +- 使用 Tokio 建構實際非同步應用 +- 處理非同步流和通道 +- 避免常見的非同步程式設計陷阱 + +## Rust 非同步簡介 + +Python 的 `asyncio` 使用帶有 `async`/`await` 語法的協程。Rust 有類似的語法,但底層機制完全不同。 + +### 關鍵區別 + + +```python !! py +# Python - 協程是隱式的 +import asyncio + +async def fetch_data(url: str) -> dict: + await asyncio.sleep(1) # 模擬網路延遲 + return {"status": "ok", "data": [1, 2, 3]} + +# Python 協程啟動時即暫停,需要事件循環 +async def main(): + result = await fetch_data("https://api.example.com") + print(result) + +asyncio.run(main()) +``` + +```rust !! rs +// Rust - Future 是顯式的且是惰性的 +use tokio::time::{sleep, Duration}; + +async fn fetch_data(url: &str) -> serde_json::Value { + // Tokio 提供非同步睡眠 + sleep(Duration::from_secs(1)).await; + json!({"status": "ok", "data": [1, 2, 3]}) +} + +// Rust 需要在入口點顯式執行時 +#[tokio::main] +async fn main() { + let result = fetch_data("https://api.example.com").await; + println!("{}", result); +} +``` + + +**核心洞察:** 在 Python 中,`async def` 建立一個暫停的協程物件。在 Rust 中,`async fn` 返回一個 `Future`,在被輪詢之前什麼都不做。 + +## Future Trait + +理解 `Future` trait 對於掌握 Rust 非同步程式設計至關重要。 + + +```python !! py +# Python - 事件循環管理協程 +import asyncio + +async def compute(): + print("Computing...") + await asyncio.sleep(0.1) + return 42 + +# 事件循環處理一切 +coro = compute() # 返回協程物件 +print(type(coro)) # + +result = asyncio.run(coro) +``` + +```rust !! rs +// Rust - Future trait 需要顯式輪詢 +use std::future::Future; +use std::pin::Pin; +use std::task::{Context, Poll}; + +// 簡化的 Future trait(實際的 std::future::Future) +pub trait SimpleFuture { + type Output; + fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll; +} + +// async fn 脫糖為實作 Future 的型別 +async fn compute() -> i32 { + println!("Computing..."); + tokio::time::sleep(Duration::from_millis(100)).await; + 42 +} + +// 執行時處理輪詢 +#[tokio::main] +async fn main() { + let future = compute(); // 返回 Future + let result = future.await; // 執行時輪詢直到就緒 + println!("{}", result); +} +``` + + +## 非同步執行時: Tokio + +Tokio 是 Rust 中最受歡迎的非同步執行時,類似於 asyncio 在 Python 中的地位。 + +### 基本 Tokio 使用 + + +```python !! py +# Python - asyncio 任務 +import asyncio + +async def worker(name: str, duration: int): + print(f"{name} started") + await asyncio.sleep(duration) + print(f"{name} completed after {duration}s") + +async def main(): + # 建立並發執行的任務 + task1 = asyncio.create_task(worker("Task 1", 2)) + task2 = asyncio.create_task(worker("Task 2", 1)) + + # 等待兩者完成 + await asyncio.gather(task1, task2) + +asyncio.run(main()) +``` + +```rust !! rs +// Rust - Tokio 任務 +use tokio::time::{sleep, Duration}; + +async fn worker(name: &str, duration: u64) { + println!("{} started", name); + sleep(Duration::from_secs(duration)).await; + println!("{} completed after {}s", name, duration); +} + +#[tokio::main] +async fn main() { + // 產生並發執行的任務 + let task1 = tokio::spawn(worker("Task 1", 2)); + let task2 = tokio::spawn(worker("Task 2", 1)); + + // 等待兩者完成 + let (result1, result2) = tokio::join!(task1, task2); + + // 處理結果(任務返回 Result) + result1.unwrap(); + result2.unwrap(); +} +``` + + +### 非同步通道 + + +```python !! py +# Python - asyncio 佇列 +import asyncio + +async def producer(queue: asyncio.Queue): + for i in range(5): + await asyncio.sleep(0.1) + await queue.put(i) + print(f"Produced: {i}") + await queue.put(None) # 發送結束信號 + +async def consumer(queue: asyncio.Queue): + while True: + item = await queue.get() + if item is None: + break + print(f"Consumed: {item}") + queue.task_done() + +async def main(): + queue = asyncio.Queue(maxsize=10) + producer_task = asyncio.create_task(producer(queue)) + consumer_task = asyncio.create_task(consumer(queue)) + await asyncio.gather(producer_task, consumer_task) + +asyncio.run(main()) +``` + +```rust !! rs +// Rust - Tokio 通道 +use tokio::sync::mpsc; +use tokio::time::{sleep, Duration}; + +async fn producer(mut tx: mpsc::Sender) { + for i in 0..5 { + sleep(Duration::from_millis(100)).await; + tx.send(i).await.unwrap(); + println!("Produced: {}", i); + } + // 丟棄 sender 以信號完成 +} + +async fn consumer(mut rx: mpsc::Receiver) { + while let Some(item) = rx.recv().await { + println!("Consumed: {}", item); + } +} + +#[tokio::main] +async fn main() { + let (tx, rx) = mpsc::channel(10); + + let producer_task = tokio::spawn(producer(tx)); + let consumer_task = tokio::spawn(consumer(rx)); + + tokio::join!(producer_task, consumer_task); +} +``` + + +## 非同步流 + +Rust 的非同步流類似於 Python 的非同步產生器。 + + +```python !! py +# Python - 非同步產生器 +import asyncio + +async def countdown(n: int): + """非同步產生器產生值""" + for i in range(n, 0, -1): + await asyncio.sleep(0.1) + yield i + +async def main(): + async for value in countdown(5): + print(f"Count: {value}") + +asyncio.run(main()) +``` + +```rust !! rs +// Rust - 使用 futures::stream 的非同步流 +use futures::stream::{self, StreamExt}; +use tokio::time::{sleep, Duration}; + +async fn countdown(n: i32) -> impl Stream { + // 從迭代器建立帶延遲的流 + stream::iter((1..=n).rev()) + .then(|n| async move { + sleep(Duration::from_millis(100)).await; + n + }) +} + +#[tokio::main] +async fn main() { + let mut stream = countdown(5).await; + + while let Some(value) = stream.next().await { + println!("Count: {}", value); + } +} +``` + + +## 非同步 HTTP 客戶端 + + +```python !! py +# Python - aiohttp 客戶端 +import aiohttp +import asyncio + +async def fetch_url(session: aiohttp.ClientSession, url: str) -> str: + async with session.get(url) as response: + return await response.text() + +async def main(): + urls = [ + "https://httpbin.org/get", + "https://httpbin.org/delay/1", + "https://httpbin.org/json", + ] + + async with aiohttp.ClientSession() as session: + tasks = [fetch_url(session, url) for url in urls] + results = await asyncio.gather(*tasks) + for result in results: + print(f"Fetched {len(result)} bytes") + +asyncio.run(main()) +``` + +```rust !! rs +// Rust - Reqwest 非同步客戶端 +use reqwest; +use futures::future::join_all; + +async fn fetch_url(url: &str) -> Result { + let response = reqwest::get(url).await?; + let text = response.text().await?; + Ok(text) +} + +#[tokio::main] +async fn main() { + let urls = [ + "https://httpbin.org/get", + "https://httpbin.org/delay/1", + "https://httpbin.org/json", + ]; + + let fetch_futures: Vec<_> = urls.iter() + .map(|&url| fetch_url(url)) + .collect(); + + let results = join_all(fetch_futures).await; + + for result in results { + match result { + Ok(text) => println!("Fetched {} bytes", text.len()), + Err(e) => println!("Error: {}", e), + } + } +} +``` + + +## 非同步檔案 I/O + + +```python !! py +# Python - aiofiles 非同步檔案操作 +import aiofiles +import asyncio + +async def process_file(filename: str): + async with aiofiles.open(filename, 'r') as f: + content = await f.read() + lines = content.splitlines() + print(f"Read {len(lines)} lines from {filename}") + +async def main(): + await asyncio.gather( + process_file('file1.txt'), + process_file('file2.txt'), + ) + +asyncio.run(main()) +``` + +```rust !! rs +// Rust - Tokio fs +use tokio::fs::File; +use tokio::io::{AsyncBufReadExt, BufReader}; + +async fn process_file(filename: &str) -> Result<(), Box> { + let file = File::open(filename).await?; + let reader = BufReader::new(file); + let mut lines = reader.lines(); + let mut count = 0; + + while let Some(_line) = lines.next_line().await? { + count += 1; + } + + println!("Read {} lines from {}", count, filename); + Ok(()) +} + +#[tokio::main] +async fn main() { + let results = tokio::join!( + process_file("file1.txt"), + process_file("file2.txt"), + ); + + results.0.unwrap(); + results.1.unwrap(); +} +``` + + +## 非同步中的錯誤處理 + + +```python !! py +# Python - 非同步中的例外處理 +import asyncio + +async def fetch_data(url: str) -> dict: + if "error" in url: + raise ValueError("Invalid URL") + await asyncio.sleep(0.1) + return {"data": "success"} + +async def main(): + try: + result = await fetch_data("https://error.api") + except ValueError as e: + print(f"Caught error: {e}") + else: + print(result) + +asyncio.run(main()) +``` + +```rust !! rs +// Rust - 非同步中的 Result 型別 +use tokio::time::{sleep, Duration}; + +async fn fetch_data(url: &str) -> Result> { + if url.contains("error") { + Err("Invalid URL".into()) + } else { + sleep(Duration::from_millis(100)).await; + Ok(json!({"data": "success"})) + } +} + +#[tokio::main] +async fn main() { + match fetch_data("https://error.api").await { + Ok(result) => println!("{}", result), + Err(e) => println!("Caught error: {}", e), + } +} +``` + + +## 常見非同步陷阱 + +### 1. 在非同步上下文中阻塞 + + +```python !! py +# Python - 非同步中的阻塞(不良實踐) +import asyncio +import time + +async def bad_blocking(): + time.sleep(2) # 阻塞整個事件循環! + print("Done") + +async def good_nonblocking(): + await asyncio.sleep(2) # 讓出控制權 + print("Done") +``` + +```rust !! rs +// Rust - 對 CPU 密集型工作使用 spawn_blocking +use tokio::task::spawn_blocking; +use tokio::time::{sleep, Duration}; + +async fn bad_blocking() { + std::thread::sleep(Duration::from_secs(2)); // 阻塞執行時! + println!("Done"); +} + +async fn good_nonblocking() { + sleep(Duration::from_secs(2)).await; // 讓出控制 + println!("Done"); +} + +async fn good_cpu_bound() { + // 將阻塞工作卸載到線程池 + let result = spawn_blocking(|| { + // CPU 密集型工作在這裡 + 42 + }).await.unwrap(); + + println!("Result: {}", result); +} + +#[tokio::main] +async fn main() { + good_nonblocking().await; + good_cpu_bound().await; +} +``` + + +### 2. 取消 + + +```python !! py +# Python - 任務取消 +import asyncio + +async def cancellable_task(): + try: + print("Task started") + await asyncio.sleep(5) + print("Task completed") + except asyncio.CancelledError: + print("Task was cancelled") + raise + +async def main(): + task = asyncio.create_task(cancellable_task()) + await asyncio.sleep(1) + task.cancel() + try: + await task + except asyncio.CancelledError: + print("Main caught cancellation") + +asyncio.run(main()) +``` + +```rust !! rs +// Rust - 使用 tokio::select! 的協作式取消 +use tokio::time::{sleep, Duration, timeout}; +use tokio::signal::ctrl_c; + +async fn cancellable_task() -> Result<(), Box> { + println!("Task started"); + + // 使用 select! 等待多個 future + tokio::select! { + _ = sleep(Duration::from_secs(5)) => { + println!("Task completed"); + } + _ = ctrl_c() => { + println!("Task received Ctrl+C"); + return Err("Cancelled".into()); + } + } + + Ok(()) +} + +#[tokio::main] +async fn main() { + // 超時後取消 + match timeout(Duration::from_secs(1), cancellable_task()).await { + Ok(Ok(())) => println!("Task succeeded"), + Ok(Err(e)) => println!("Task error: {}", e), + Err(_) => println!("Task timed out"), + } +} +``` + + +## 實戰示例: 網頁爬蟲 + + +```python !! py +# Python - 非同步網頁爬蟲 +import asyncio +import aiohttp +from bs4 import BeautifulSoup + +async def scrape_page(session: aiohttp.ClientSession, url: str) -> dict: + async with session.get(url) as response: + html = await response.text() + soup = BeautifulSoup(html, 'html.parser') + return { + 'url': url, + 'title': soup.title.string if soup.title else 'No title', + 'links': len(soup.find_all('a')) + } + +async def main(urls: list[str]): + async with aiohttp.ClientSession() as session: + tasks = [scrape_page(session, url) for url in urls] + results = await asyncio.gather(*tasks) + return results + +if __name__ == "__main__": + urls = [ + "https://example.com", + "https://example.org", + "https://example.net", + ] + results = asyncio.run(main(urls)) + for result in results: + print(f"{result['url']}: {result['title']} ({result['links']} links)") +``` + +```rust !! rs +// Rust - 並發網頁爬蟲 +use reqwest::Client; +use scraper::{Html, Selector}; +use futures::future::join_all; + +struct ScrapedData { + url: String, + title: String, + links: usize, +} + +async fn scrape_page(client: &Client, url: &str) -> Result> { + let response = client.get(url).send().await?; + let html = response.text().await?; + + let document = Html::parse_document(&html); + let title_selector = Selector::parse("title").unwrap(); + let link_selector = Selector::parse("a").unwrap(); + + let title = document + .select(&title_selector) + .next() + .map(|t| t.text().collect::()) + .unwrap_or_else(|| "No title".to_string()); + + let links = document.select(&link_selector).count(); + + Ok(ScrapedData { + url: url.to_string(), + title, + links, + }) +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + let urls = [ + "https://example.com", + "https://example.org", + "https://example.net", + ]; + + let client = Client::new(); + let scrape_futures: Vec<_> = urls.iter() + .map(|&url| scrape_page(&client, url)) + .collect(); + + let results = join_all(scrape_futures).await; + + for result in results { + match result { + Ok(data) => println!("{}: {} ({} links)", data.url, data.title, data.links), + Err(e) => println!("Error scraping: {}", e), + } + } + + Ok(()) +} +``` + + +## 關鍵要點 + +### Python 中的非同步 +- 基於協程和事件循環 +- 執行時隱式調度 +- 易於使用但有執行時開銷 +- 使用 `async`/`await` 語法 + +### Rust 中的非同步 +- 基於 Future 的零成本抽象 +- 需要顯式執行時(Tokio) +- 編譯器強制非同步程式碼安全 +- 不使用非同步時無執行時開銷 +- 非同步入口點需要 `#[tokio::main]` + +### 何時使用非同步 +- **使用非同步** 處理 I/O 密集型操作(網路、檔案) +- **使用同步** 處理 CPU 密集型任務(或使用 `spawn_blocking`) +- **避免非同步** 用於簡單的順序程式碼 +- **優先使用通道** 而非共享狀態 + +## 練習 + +1. 實作一個並發檔案下載器,並行下載多個檔案 +2. 使用非同步通道建構聊天伺服器 +3. 建立非同步資料庫連線池 +4. 實作指數退避的重試邏輯 +5. 建構帶速率限制的並發網頁爬蟲 + +## 下一模組 + +在模組 17 中,我們將探索 **Rust 測試**,包括單元測試、整合測試、基於屬性的測試和模擬策略。 diff --git a/content/docs/py2rust/module-17-testing.mdx b/content/docs/py2rust/module-17-testing.mdx new file mode 100644 index 0000000..7a06de4 --- /dev/null +++ b/content/docs/py2rust/module-17-testing.mdx @@ -0,0 +1,920 @@ +--- +title: "Module 17: Testing" +description: "Master testing in Rust including unit tests, integration tests, property-based testing, and mocking strategies" +--- + +# Module 17: Testing + +## Learning Objectives + +By the end of this module, you'll be able to: +- Write unit tests with the `#[test]` attribute +- Create integration tests in the `tests/` directory +- Use assertions and custom test output +- Test async code with Tokio +- Implement property-based testing with proptest +- Mock dependencies for isolated testing + +## Introduction to Testing in Rust + +Rust has built-in testing support, unlike Python which requires external frameworks like pytest (though pytest is very popular). + + +```python !! py +# Python - pytest structure +# test_calculator.py +import pytest + +def add(a: int, b: int) -> int: + return a + b + +def test_add_positive(): + assert add(2, 3) == 5 + +def test_add_negative(): + assert add(-1, 1) == 0 + +@pytest.mark.parametrize("a,b,expected", [ + (1, 2, 3), + (0, 0, 0), + (-1, 1, 0), +]) +def test_add_various(a, b, expected): + assert add(a, b) == expected + +# Run with: pytest test_calculator.py +``` + +```rust !! rs +// Rust - built-in testing +// calculator.rs or tests/calculator_test.rs + +fn add(a: i32, b: i32) -> i32 { + a + b +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_add_positive() { + assert_eq!(add(2, 3), 5); + } + + #[test] + fn test_add_negative() { + assert_eq!(add(-1, 1), 0); + } + + // No built-in parametrize, but we can iterate + #[test] + fn test_add_various() { + let cases = [(1, 2, 3), (0, 0, 0), (-1, 1, 0)]; + for (a, b, expected) in cases { + assert_eq!(add(a, b), expected); + } + } +} + +// Run with: cargo test +``` + + +## Unit Tests vs Integration Tests + +Rust has two types of tests with different locations: + + +```python !! py +# Python - Flat structure +# src/calculator.py +def add(a, b): + return a + b + +# tests/test_calculator.py +import pytest +from calculator import add + +def test_add(): + assert add(2, 3) == 5 +``` + +```rust !! rs +// Rust - Separated unit and integration tests +// src/lib.rs or src/main.rs +pub fn add(a: i32, b: i32) -> i32 { + a + b +} + +// Unit tests: in the same file +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_add() { + assert_eq!(add(2, 3), 5); + } +} + +// Integration tests: in tests/ directory +// tests/integration_test.rs +use my_crate::add; + +#[test] +fn test_integration_add() { + assert_eq!(add(10, 20), 30); +} +``` + + +**Key Point:** +- **Unit tests**: Test private functions, placed in the same module +- **Integration tests**: Test public API, placed in `tests/` directory + +## Assertions and Output + + +```python !! py +# Python - pytest assertions +import pytest + +def divide(a, b): + if b == 0: + raise ValueError("Cannot divide by zero") + return a / b + +def test_divide(): + assert divide(10, 2) == 5 + assert divide(10, 2) != 4 + + with pytest.raises(ValueError): + divide(10, 0) + + # Custom message + assert divide(10, 2) > 0, "Result should be positive" + + # Approximate equality + assert abs(0.1 + 0.2 - 0.3) < 1e-10 +``` + +```rust !! rs +// Rust - assertion macros +fn divide(a: f64, b: f64) -> Result { + if b == 0.0 { + Err("Cannot divide by zero".to_string()) + } else { + Ok(a / b) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_divide() { + assert_eq!(divide(10.0, 2.0).unwrap(), 5.0); + assert_ne!(divide(10.0, 2.0).unwrap(), 4.0); + + // Test for error + assert!(divide(10.0, 0.0).is_err()); + + // Custom message + assert!(divide(10.0, 2.0).unwrap() > 0.0, + "Result should be positive"); + + // Approximate equality + let result = 0.1_f64 + 0.2; + assert!((result - 0.3).abs() < 1e-10); + } + + #[test] + #[should_panic(expected = "Cannot divide")] + fn test_divide_panic() { + let _ = divide(10.0, 0.0).unwrap(); + } +} +``` + + +## Testing Async Code + + +```python !! py +# Python - pytest-asyncio +import pytest +import asyncio + +async def fetch_data(url: str) -> dict: + await asyncio.sleep(0.1) + return {"status": "ok", "data": [1, 2, 3]} + +@pytest.mark.asyncio +async def test_fetch_data(): + result = await fetch_data("https://api.example.com") + assert result["status"] == "ok" + assert len(result["data"]) == 3 + +# Or use pytest fixture +@pytest.fixture +async def async_fixture(): + await asyncio.sleep(0.01) + return {"initialized": True} + +@pytest.mark.asyncio +async def test_with_fixture(async_fixture): + assert async_fixture["initialized"] +``` + +```rust !! rs +// Rust - Tokio test macros +use tokio::time::{sleep, Duration}; + +async fn fetch_data(url: &str) -> serde_json::Value { + sleep(Duration::from_millis(100)).await; + json!({"status": "ok", "data": [1, 2, 3]}) +} + +#[cfg(test)] +mod tests { + use super::*; + + // Use tokio::test for async tests + #[tokio::test] + async fn test_fetch_data() { + let result = fetch_data("https://api.example.com").await; + assert_eq!(result["status"], "ok"); + assert_eq!(result["data"].as_array().unwrap().len(), 3); + } + + // Test timeout + #[tokio::test] + async fn test_with_timeout() { + let result = tokio::time::timeout( + Duration::from_millis(200), + fetch_data("https://api.example.com") + ).await; + + assert!(result.is_ok()); + } +} +``` + + +## Property-Based Testing + + +```python !! py +# Python - hypothesis +from hypothesis import given, strategies as st + +def reverse_string(s: str) -> str: + return s[::-1] + +@given(st.text()) +def test_reverse_double_reverse(s): + # Property: reversing twice gives original + assert reverse_string(reverse_string(s)) == s + +@given(st.text(), st.text()) +def test_reverse_concat(s1, s2): + # Property: reverse concatenation + combined = s1 + s2 + reversed_combined = reverse_string(combined) + assert reversed_combined == reverse_string(s2) + reverse_string(s1) +``` + +```rust !! rs +// Rust - proptest +use proptest::prelude::*; + +fn reverse_string(s: &str) -> String { + s.chars().rev().collect() +} + +proptest! { + #[test] + fn test_reverse_double_reverse(s in ".*") { + // Property: reversing twice gives original + prop_assert_eq!( + reverse_string(&reverse_string(&s)), + s + ); + } + + #[test] + fn test_reverse_concat(s1 in ".*", s2 in ".*") { + // Property: reverse concatenation + let combined = format!("{}{}", s1, s2); + let reversed_combined = reverse_string(&combined); + prop_assert_eq!( + reversed_combined, + format!("{}{}", reverse_string(&s2), reverse_string(&s1)) + ); + } +} +``` + + +## Custom Test Output + + +```python !! py +# Python - pytest output +import pytest + +def process_data(items): + results = [] + for item in items: + result = item * 2 + results.append(result) + return results + +def test_process_data(capsys): + data = [1, 2, 3] + print(f"Processing: {data}") + result = process_data(data) + print(f"Result: {result}") + + assert result == [2, 4, 6] + + # Capture output + captured = capsys.readouterr() + assert "Processing:" in captured.out +``` + +```rust !! rs +// Rust - custom test output +fn process_data(items: &[i32]) -> Vec { + items.iter().map(|&x| x * 2).collect() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_process_data() { + let data = vec![1, 2, 3]; + println!("Processing: {:?}", data); + + let result = process_data(&data); + println!("Result: {:?}", result); + + assert_eq!(result, vec![2, 4, 6]); + } + + // Show values even on panic + #[test] + #[should_panic] + fn test_with_debug_output() { + let data = vec![1, 2, 3]; + let result = process_data(&data); + // This will panic and show data + assert_eq!(result, vec![1, 2, 3]); + } +} + +// Run with: cargo test -- --nocapture (show print output) +// or: cargo test -- --show-output (show test output) +``` + + +## Test Organization and Fixtures + + +```python !! py +# Python - pytest fixtures +import pytest + +class Database: + def __init__(self): + self.data = {} + + def insert(self, key, value): + self.data[key] = value + + def get(self, key): + return self.data.get(key) + +@pytest.fixture +def db(): + database = Database() + database.insert("test", "value") + yield database + # Cleanup happens here + +def test_database(db): + assert db.get("test") == "value" + db.insert("new", "data") + assert db.get("new") == "data" +``` + +```rust !! rs +// Rust - manual setup/teardown +struct Database { + data: std::collections::HashMap, +} + +impl Database { + fn new() -> Self { + let mut db = Self { + data: std::collections::HashMap::new(), + }; + db.insert("test".to_string(), "value".to_string()); + db + } + + fn insert(&mut self, key: String, value: String) { + self.data.insert(key, value); + } + + fn get(&self, key: &str) -> Option<&String> { + self.data.get(key) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_database() { + // Manual setup + let mut db = Database::new(); + + assert_eq!(db.get("test"), Some(&"value".to_string())); + + db.insert("new".to_string(), "data".to_string()); + assert_eq!(db.get("new"), Some(&"data".to_string())); + + // Automatic cleanup when db goes out of scope + } +} +``` + + +## Mocking Dependencies + + +```python !! py +# Python - pytest monkeypatch or unittest.mock +from unittest.mock import Mock, patch + +class UserService: + def __init__(self, api_client): + self.api_client = api_client + + def get_user(self, user_id): + return self.api_client.fetch(f"/users/{user_id}") + +def test_user_service(): + mock_api = Mock() + mock_api.fetch.return_value = {"id": 1, "name": "Alice"} + + service = UserService(mock_api) + user = service.get_user(1) + + assert user["name"] == "Alice" + mock_api.fetch.assert_called_once_with("/users/1") +``` + +```rust !! rs +// Rust - trait-based mocking +trait ApiClient { + fn fetch(&self, path: &str) -> serde_json::Value; +} + +struct UserService { + api_client: T, +} + +impl UserService { + fn new(api_client: T) -> Self { + Self { api_client } + } + + fn get_user(&self, user_id: i32) -> serde_json::Value { + let path = format!("/users/{}", user_id); + self.api_client.fetch(&path) + } +} + +// Mock implementation for testing +struct MockApiClient { + response: serde_json::Value, +} + +impl ApiClient for MockApiClient { + fn fetch(&self, _path: &str) -> serde_json::Value { + self.response.clone() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_user_service() { + let mock_api = MockApiClient { + response: json!({"id": 1, "name": "Alice"}), + }; + + let service = UserService::new(mock_api); + let user = service.get_user(1); + + assert_eq!(user["name"], "Alice"); + assert_eq!(user["id"], 1); + } +} +``` + + +## Benchmarking + + +```python !! py +# Python - pytest-benchmark +import pytest + +def fibonacci(n): + if n <= 1: + return n + return fibonacci(n - 1) + fibonacci(n - 2) + +@pytest.mark.benchmark(group="fibonacci") +def test_fib_10(benchmark): + result = benchmark(fibonacci, 10) + assert result == 55 + +@pytest.mark.benchmark(group="fibonacci") +def test_fib_20(benchmark): + result = benchmark(fibonacci, 20) + assert result == 6765 +``` + +```rust !! rs +// Rust - Criterion (external crate) +// Add criterion to dev-dependencies +// [[bench]] +// name = "fibonacci" +// harness = false + +fn fibonacci(n: u64) -> u64 { + match n { + 0 => 0, + 1 => 1, + _ => fibonacci(n - 1) + fibonacci(n - 2), + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_fibonacci() { + assert_eq!(fibonacci(10), 55); + assert_eq!(fibonacci(20), 6765); + } +} + +// benches/fibonacci.rs +use criterion::{black_box, criterion_group, criterion_main, Criterion}; + +fn fibonacci_benchmark(c: &mut Criterion) { + c.bench_function("fib 10", |b| { + b.iter(|| fibonacci(black_box(10))) + }); + + c.bench_function("fib 20", |b| { + b.iter(|| fibonacci(black_box(20))) + }); +} + +criterion_group!(benches, fibonacci_benchmark); +criterion_main!(benches); + +// Run with: cargo bench +``` + + +## Testing Error Handling + + +```python !! py +# Python - Exception testing +import pytest + +class ValidationError(Exception): + pass + +def validate_email(email: str) -> str: + if "@" not in email: + raise ValidationError("Invalid email") + return email.lower() + +def test_valid_email(): + assert validate_email("USER@EXAMPLE.COM") == "user@example.com" + +def test_invalid_email(): + with pytest.raises(ValidationError) as exc: + validate_email("invalid") + assert "Invalid email" in str(exc.value) +``` + +```rust !! rs +// Rust - Result testing +#[derive(Debug, PartialEq)] +enum ValidationError { + InvalidEmail(String), +} + +fn validate_email(email: &str) -> Result { + if !email.contains('@') { + Err(ValidationError::InvalidEmail("Invalid email".to_string())) + } else { + Ok(email.to_lowercase()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_valid_email() { + let result = validate_email("USER@EXAMPLE.COM"); + assert_eq!(result, Ok("user@example.com".to_string())); + } + + #[test] + fn test_invalid_email() { + let result = validate_email("invalid"); + assert!(result.is_err()); + + if let Err(ValidationError::InvalidEmail(msg)) = result { + assert!(msg.contains("Invalid email")); + } else { + panic!("Expected InvalidEmail error"); + } + } + + #[test] + fn test_invalid_email_short() { + assert!(validate_email("invalid").is_err()); + } +} +``` + + +## Practical Example: Testing a REST Client + + +```python !! py +# Python - Testing HTTP client +import pytest +from unittest.mock import Mock, patch +import aiohttp + +class RestClient: + def __init__(self, base_url: str): + self.base_url = base_url + + async def get_user(self, user_id: int) -> dict: + async with aiohttp.ClientSession() as session: + url = f"{self.base_url}/users/{user_id}" + async with session.get(url) as response: + return await response.json() + +@pytest.mark.asyncio +async def test_get_user_success(): + client = RestClient("https://api.example.com") + + # Mock the HTTP request + with patch("aiohttp.ClientSession.get") as mock_get: + mock_response = Mock() + mock_response.json.return_value = {"id": 1, "name": "Alice"} + mock_get.return_value.__aenter__.return_value = mock_response + + user = await client.get_user(1) + assert user["name"] == "Alice" +``` + +```rust !! rs +// Rust - Testing HTTP client with mock +use reqwest::Client; + +pub struct RestClient { + base_url: String, + client: Client, +} + +impl RestClient { + pub fn new(base_url: &str) -> Self { + Self { + base_url: base_url.to_string(), + client: Client::new(), + } + } + + pub async fn get_user(&self, user_id: i32) -> Result { + let url = format!("{}/users/{}", self.base_url, user_id); + let response = self.client.get(&url).send().await?; + response.json().await + } +} + +#[cfg(test)] +mod tests { + use super::*; + use wiremock::{matchers, Mock, MockServer, ResponseTemplate}; + + #[tokio::test] + async fn test_get_user_success() { + // Create mock server + let mock_server = MockServer::start().await; + + // Setup mock response + Mock::given(matchers::method("GET")) + .and(matchers::path("/users/1")) + .respond_with(ResponseTemplate::new(200).set_body_json( + json!({"id": 1, "name": "Alice"}) + )) + .mount(&mock_server) + .await; + + // Create client with mock server URL + let client = RestClient::new(&mock_server.uri()); + let user = client.get_user(1).await.unwrap(); + + assert_eq!(user["name"], "Alice"); + assert_eq!(user["id"], 1); + } +} +``` + + +## Test-Driven Development Example + + +```python !! py +# Python - TDD approach +# 1. Write failing test first +def test_calculate_discount(): + customer = Customer(premium=True, years=5) + discount = calculate_discount(customer, 100) + assert discount == 20 # 20% discount + +# 2. Implement minimum code to pass +def calculate_discount(customer, amount): + if customer.premium: + return amount * 0.2 + return 0 + +# 3. Refactor with more rules +def calculate_discount(customer, amount): + discount = 0 + if customer.premium: + discount += 0.1 + if customer.years >= 5: + discount += 0.1 + return amount * discount +``` + +```rust !! rs +// Rust - TDD approach +// 1. Write failing test first +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_calculate_discount() { + let customer = Customer { + premium: true, + years: 5, + }; + let discount = calculate_discount(&customer, 100); + assert_eq!(discount, 20); // 20% discount + } +} + +// 2. Implement minimum code to pass +pub struct Customer { + pub premium: bool, + pub years: u32, +} + +pub fn calculate_discount(customer: &Customer, amount: u32) -> u32 { + if customer.premium { + amount * 20 / 100 + } else { + 0 + } +} + +// 3. Refactor with more rules +pub fn calculate_discount(customer: &Customer, amount: u32) -> u32 { + let mut discount_percent = 0; + + if customer.premium { + discount_percent += 10; + } + if customer.years >= 5 { + discount_percent += 10; + } + + amount * discount_percent / 100 +} +``` + + +## Running Tests + + +```bash !! bash +# Python - pytest commands +# Run all tests +pytest + +# Run specific file +pytest test_calculator.py + +# Run verbose +pytest -v + +# Run with coverage +pytest --cov=src --cov-report=html + +# Stop on first failure +pytest -x + +# Run matching pattern +pytest -k "test_add" +``` + +```rust !! rs +// Rust - cargo test commands +// Run all tests +cargo test + +// Run specific test +cargo test test_add + +// Run tests in specific file +cargo test --lib calculator + +// Run verbose (show print output) +cargo test -- --nocapture + +// Run integration tests only +cargo test --test integration_test + +// Run doc tests (examples in documentation) +cargo test --doc + +// Run tests with stdout +cargo test -- --show-output + +// Run tests in release mode (faster) +cargo test --release +``` + + +## Key Takeaways + +### Testing Philosophy +- **Python**: Emphasis on simplicity and expressiveness with pytest +- **Rust**: Built-in testing with focus on safety and performance + +### Test Organization +- **Python**: Tests in `test_*.py` files or `tests/` directory +- **Rust**: Unit tests in same file, integration tests in `tests/` directory + +### Async Testing +- **Python**: Requires pytest-asyncio plugin +- **Rust**: Requires `#[tokio::test]` macro + +### Property-Based Testing +- **Python**: Hypothesis framework +- **Rust**: Proptest crate + +### Mocking +- **Python**: unittest.mock or monkeypatch +- **Rust**: Trait-based mocking or dedicated mocking libraries + +## Exercises + +1. Write unit tests for a calculator with all basic operations +2. Create integration tests for a REST API client +3. Implement property-based tests for a sorting algorithm +4. Build a mock implementation for a database trait +5. Add benchmark tests for different sorting algorithms + +## Next Module + +In Module 18, we'll explore **Macros in Rust**, including declarative macros with `macro_rules!`, derive macros, and procedural macros. diff --git a/content/docs/py2rust/module-17-testing.zh-cn.mdx b/content/docs/py2rust/module-17-testing.zh-cn.mdx new file mode 100644 index 0000000..4cbf38b --- /dev/null +++ b/content/docs/py2rust/module-17-testing.zh-cn.mdx @@ -0,0 +1,200 @@ +--- +title: "模块 17: 测试" +description: "掌握 Rust 测试,包括单元测试、集成测试、基于属性的测试和模拟策略" +--- + +# 模块 17: 测试 + +## 学习目标 + +完成本模块后,你将能够: +- 使用 `#[test]` 属性编写单元测试 +- 在 `tests/` 目录中创建集成测试 +- 使用断言和自定义测试输出 +- 使用 Tokio 测试异步代码 +- 使用 proptest 实现基于属性的测试 +- 为隔离测试模拟依赖 + +## Rust 测试简介 + +Rust 有内置的测试支持,不像 Python 需要外部框架(虽然 pytest 非常流行)。 + + +```python !! py +# Python - pytest 结构 +# test_calculator.py +import pytest + +def add(a: int, b: int) -> int: + return a + b + +def test_add_positive(): + assert add(2, 3) == 5 + +def test_add_negative(): + assert add(-1, 1) == 0 + +@pytest.mark.parametrize("a,b,expected", [ + (1, 2, 3), + (0, 0, 0), + (-1, 1, 0), +]) +def test_add_various(a, b, expected): + assert add(a, b) == expected + +# 运行: pytest test_calculator.py +``` + +```rust !! rs +// Rust - 内置测试 +// calculator.rs 或 tests/calculator_test.rs + +fn add(a: i32, b: i32) -> i32 { + a + b +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_add_positive() { + assert_eq!(add(2, 3), 5); + } + + #[test] + fn test_add_negative() { + assert_eq!(add(-1, 1), 0); + } + + // 没有内置的参数化,但可以迭代 + #[test] + fn test_add_various() { + let cases = [(1, 2, 3), (0, 0, 0), (-1, 1, 0)]; + for (a, b, expected) in cases { + assert_eq!(add(a, b), expected); + } + } +} + +// 运行: cargo test +``` + + +## 单元测试 vs 集成测试 + +Rust 有两种不同位置的测试类型: + + +```python !! py +# Python - 平坦结构 +# src/calculator.py +def add(a, b): + return a + b + +# tests/test_calculator.py +import pytest +from calculator import add + +def test_add(): + assert add(2, 3) == 5 +``` + +```rust !! rs +// Rust - 分离的单元和集成测试 +// src/lib.rs 或 src/main.rs +pub fn add(a: i32, b: i32) -> i32 { + a + b +} + +// 单元测试:在同一文件中 +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_add() { + assert_eq!(add(2, 3), 5); + } +} + +// 集成测试:在 tests/ 目录中 +// tests/integration_test.rs +use my_crate::add; + +#[test] +fn test_integration_add() { + assert_eq!(add(10, 20), 30); +} +``` + + +**关键点:** +- **单元测试**: 测试私有函数,放在同一模块中 +- **集成测试**: 测试公共 API,放在 `tests/` 目录中 + +## 运行测试 + + +```bash !! bash +# Python - pytest 命令 +# 运行所有测试 +pytest + +# 运行特定文件 +pytest test_calculator.py + +# 详细输出 +pytest -v + +# 带覆盖率 +pytest --cov=src --cov-report=html +``` + +```rust !! rs +// Rust - cargo test 命令 +// 运行所有测试 +cargo test + +// 运行特定测试 +cargo test test_add + +// 详细输出(显示打印输出) +cargo test -- --nocapture + +// 只运行集成测试 +cargo test --test integration_test + +// 运行文档测试 +cargo test --doc + +// 发布模式运行测试(更快) +cargo test --release +``` + + +## 关键要点 + +### 测试哲学 +- **Python**: 强调简单性和表现力 +- **Rust**: 内置测试,注重安全性和性能 + +### 测试组织 +- **Python**: 测试在 `test_*.py` 文件中 +- **Rust**: 单元测试在同一文件,集成测试在 `tests/` 目录 + +### 异步测试 +- **Python**: 需要 pytest-asyncio 插件 +- **Rust**: 需要 `#[tokio::test]` 宏 + +## 练习 + +1. 为计算器的所有基本操作编写单元测试 +2. 为 REST API 客户端创建集成测试 +3. 为排序算法实现基于属性的测试 +4. 为数据库 trait 构建模拟实现 +5. 为不同排序算法添加基准测试 + +## 下一模块 + +在模块 18 中,我们将探索 **Rust 宏**,包括使用 `macro_rules!` 的声明式宏、派生宏和过程宏。 diff --git a/content/docs/py2rust/module-17-testing.zh-tw.mdx b/content/docs/py2rust/module-17-testing.zh-tw.mdx new file mode 100644 index 0000000..48cea5c --- /dev/null +++ b/content/docs/py2rust/module-17-testing.zh-tw.mdx @@ -0,0 +1,166 @@ +--- +title: "模組 17: 測試" +description: "掌握 Rust 測試,包括單元測試、整合測試、基於屬性的測試和模擬策略" +--- + +# 模組 17: 測試 + +## 學習目標 + +完成本模組後,你將能夠: +- 使用 `#[test]` 屬性編寫單元測試 +- 在 `tests/` 目錄中建立整合測試 +- 使用斷言和自訂測試輸出 +- 使用 Tokio 測試非同步程式碼 +- 使用 proptest 實作基於屬性的測試 +- 為隔離測試模擬依賴 + +## Rust 測試簡介 + +Rust 有內建的測試支援,不像 Python 需要外部框架(雖然 pytest 非常流行)。 + + +```python !! py +# Python - pytest 結構 +# test_calculator.py +import pytest + +def add(a: int, b: int) -> int: + return a + b + +def test_add_positive(): + assert add(2, 3) == 5 + +def test_add_negative(): + assert add(-1, 1) == 0 + +# 執行: pytest test_calculator.py +``` + +```rust !! rs +// Rust - 內建測試 +fn add(a: i32, b: i32) -> i32 { + a + b +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_add_positive() { + assert_eq!(add(2, 3), 5); + } + + #[test] + fn test_add_negative() { + assert_eq!(add(-1, 1), 0); + } +} + +// 執行: cargo test +``` + + +## 單元測試 vs 整合測試 + +Rust 有兩種不同位置的測試類型: + + +```python !! py +# Python - 平坦結構 +# src/calculator.py +def add(a, b): + return a + b + +# tests/test_calculator.py +from calculator import add + +def test_add(): + assert add(2, 3) == 5 +``` + +```rust !! rs +// Rust - 分離的單元和整合測試 +// src/lib.rs +pub fn add(a: i32, b: i32) -> i32 { + a + b +} + +// 單元測試:在同一檔案中 +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_add() { + assert_eq!(add(2, 3), 5); + } +} + +// 整合測試:在 tests/ 目錄中 +// tests/integration_test.rs +use my_crate::add; + +#[test] +fn test_integration_add() { + assert_eq!(add(10, 20), 30); +} +``` + + +**關鍵點:** +- **單元測試**: 測試私有函式,放在同一模組中 +- **整合測試**: 測試公共 API,放在 `tests/` 目錄中 + +## 執行測試 + + +```bash !! bash +# Python - pytest 指令 +# 執行所有測試 +pytest + +# 執行特定檔案 +pytest test_calculator.py + +# 詳細輸出 +pytest -v +``` + +```rust !! rs +// Rust - cargo test 指令 +// 執行所有測試 +cargo test + +// 執行特定測試 +cargo test test_add + +// 詳細輸出(顯示列印輸出) +cargo test -- --nocapture + +// 發布模式執行測試(更快) +cargo test --release +``` + + +## 關鍵要點 + +### 測試哲學 +- **Python**: 強調簡單性和表現力 +- **Rust**: 內建測試,注重安全性和效能 + +### 測試組織 +- **Python**: 測試在 `test_*.py` 檔案中 +- **Rust**: 單元測試在同一檔案,整合測試在 `tests/` 目錄 + +## 練習 + +1. 為計算器的所有基本操作編寫單元測試 +2. 為 REST API 客戶端建立整合測試 +3. 為排序演算法實作基於屬性的測試 +4. 為資料庫 trait 建構模擬實作 + +## 下一模組 + +在模組 18 中,我們將探索 **Rust 巨集**,包括使用 `macro_rules!` 的宣告式巨集、派生巨集和程序巨集。 diff --git a/content/docs/py2rust/module-18-macros.mdx b/content/docs/py2rust/module-18-macros.mdx new file mode 100644 index 0000000..28ce6ad --- /dev/null +++ b/content/docs/py2rust/module-18-macros.mdx @@ -0,0 +1,848 @@ +--- +title: "Module 18: Macros" +description: "Master Rust macros including declarative macros with macro_rules!, derive macros, and procedural macros" +--- + +# Module 18: Macros + +## Learning Objectives + +By the end of this module, you'll be able to: +- Write declarative macros using `macro_rules!` +- Create custom derive macros +- Understand procedural macros +- Generate code at compile time +- Use macros to reduce boilerplate + +## Introduction to Macros + +Python has decorators and metaclasses for metaprogramming. Rust has a more powerful macro system that operates at compile time. + + +```python !! py +# Python - Decorators for metaprogramming +def timing_decorator(func): + def wrapper(*args, **kwargs): + import time + start = time.time() + result = func(*args, **kwargs) + end = time.time() + print(f"{func.__name__} took {end - start:.4f}s") + return result + return wrapper + +@timing_decorator +def slow_function(): + import time + time.sleep(0.1) + return "done" + +# Use the decorated function +slow_function() +``` + +```rust !! rs +// Rust - Declarative macros for code generation +macro_rules! time_it { + ($func:expr) => {{ + let start = std::time::Instant::now(); + let result = $func; + let duration = start.elapsed(); + println!(" took {:?}", duration); + result + }}; +} + +fn slow_function() -> &'static str { + std::thread::sleep(std::time::Duration::from_millis(100)); + "done" +} + +fn main() { + time_it!(slow_function()); +} +``` + + +**Key Difference:** Python decorators modify functions at runtime, while Rust macros generate code at compile time with zero runtime cost. + +## Declarative Macros with `macro_rules!` + +### Pattern Matching + + +```python !! py +# Python - No direct equivalent +# Would use decorators or metaclasses +def create_getter_setter(field_name): + # This is runtime, not compile-time + pass + +class Person: + pass + +# Dynamically add attributes (runtime) +Person.name = property( + lambda self: self._name, + lambda self, value: setattr(self, '_name', value) +) +``` + +```rust !! rs +// Rust - Compile-time code generation +macro_rules! make_getter_setter { + ($field:ident, $type:ty) => { + fn get_$field(&self) -> &$type { + &self.$field + } + + fn set_$field(&mut self, value: $type) { + self.$field = value; + } + }; +} + +struct Person { + name: String, + age: u32, +} + +impl Person { + make_getter_setter!(name, String); + make_getter_setter!(age, u32); +} + +fn main() { + let mut p = Person { + name: "Alice".to_string(), + age: 30, + }; + + println!("Name: {}", p.get_name()); + p.set_age(31); +} +``` + + +### Multiple Patterns + + +```python !! py +# Python - Function overloading is not built-in +# Use type checking or multiple dispatch +from functools import singledispatch + +@singledispatch +def process(value): + raise TypeError("Unsupported type") + +@process.register +def _(value: int): + return f"Integer: {value}" + +@process.register +def _(value: str): + return f"String: {value}" + +print(process(42)) +print(process("hello")) +``` + +```rust !! rs +// Rust - Pattern matching in macros +macro_rules! process { + // Pattern 1: Integer + ($value:expr) => { + println!("Processing: {}", $value); + }; + + // Pattern 2: Key-value + (key $key:expr, value $value:expr) => { + println!("Key: {}, Value: {}", $key, $value); + }; + + // Pattern 3: Multiple values + ($($value:expr),*) => { + $( + println!("Item: {}", $value); + )* + }; +} + +fn main() { + process!(42); + process!(key "name", value "Alice"); + process!(1, 2, 3, 4); +} +``` + + +### Repetition + + +```python !! py +# Python - Runtime code generation with eval +def create_vector(*args): + # Not type-safe, runtime + return list(args) + +v = create_vector(1, 2, 3, 4, 5) +print(v) +``` + +```rust !! rs +// Rust - Compile-time repetition +macro_rules! vec { + ( $( $x:expr ),* $(,)? ) => { + { + let mut temp_vec = Vec::new(); + $( + temp_vec.push($x); + )* + temp_vec + } + }; +} + +fn main() { + let v = vec!(1, 2, 3, 4, 5); + println!("{:?}", v); +} +``` + + +## Practical Macro Examples + +### Building HashMap + + +```python !! py +# Python - Dictionary literal +data = { + "name": "Alice", + "age": 30, + "city": "New York", +} +``` + +```rust !! rs +// Rust - Create HashMap macro +macro_rules! map { + ( $($key:expr => $value:expr),* $(,)? ) => {{ + let mut m = std::collections::HashMap::new(); + $( + m.insert($key, $value); + )* + m + }}; +} + +fn main() { + let data = map!( + "name" => "Alice", + "age" => 30, + "city" => "New York", + ); + + println!("{:?}", data); +} +``` + + +### Token Trees and Hygiene + + +```python !! py +# Python - Variable capture issues +def wrapper(func): + def inner(): + value = 10 # May conflict with outer scope + return func() + return inner + +@wrapper +def my_func(): + value = 20 + return value + +# Rust macros are hygienic by default +``` + +```rust !! rs +// Rust - Hygienic macros +macro_rules! double { + ($x:expr) => {{ + let x = $x * 2; // Local variable, doesn't conflict + x + }}; +} + +fn main() { + let x = 10; + let doubled = double!(x + 5); + println!("Original: {}, Doubled: {}", x, doubled); +} +``` + + +## Derive Macros + +Derive macros automatically implement traits for structs and enums. + + +```python !! py +# Python - No direct equivalent +# Would use metaclasses or class decorators +from dataclasses import dataclass + +@dataclass +class Person: + name: str + age: int + + def to_dict(self): + return {"name": self.name, "age": self.age} +``` + +```rust !! rs +// Rust - Custom derive macro +// In: person_derive_macro/src/lib.rs +use proc_macro::TokenStream; +use quote::quote; +use syn::{parse_macro_input, DeriveInput}; + +#[proc_macro_derive(ToDict)] +pub fn to_dict_derive(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + let name = &input.ident; + + let expanded = quote! { + impl ToDict for #name { + fn to_dict(&self) -> std::collections::HashMap { + let mut map = std::collections::HashMap::new(); + map.insert(stringify!(#name).to_string(), format!("{:?}", self)); + map + } + } + }; + + TokenStream::from(expanded) +} + +// In: main.rs +use person_derive_macro::ToDict; + +#[derive(ToDict)] +struct Person { + name: String, + age: u32, +} + +trait ToDict { + fn to_dict(&self) -> std::collections::HashMap; +} + +fn main() { + let p = Person { + name: "Alice".to_string(), + age: 30, + }; + + let dict = p.to_dict(); + println!("{:?}", dict); +} +``` + + +## Attribute Macros + +Attribute macros modify items they're attached to. + + +```python !! py +# Python - Decorators as attribute macros +def route(method: str, path: str): + def decorator(func): + func.route_info = (method, path) + return func + return decorator + +class API: + @route("GET", "/users") + def get_users(self): + return [{"id": 1, "name": "Alice"}] + + @route("POST", "/users") + def create_user(self): + return {"id": 2, "name": "Bob"} +``` + +```rust !! rs +// Rust - Attribute-like procedural macro +// In: route_macro/src/lib.rs +use proc_macro::TokenStream; +use quote::quote; +use syn::{parse_macro_input, ItemFn}; + +#[proc_macro_attribute] +pub fn route(_attr: TokenStream, item: TokenStream) -> TokenStream { + let input = parse_macro_input!(item as ItemFn); + let name = &input.sig.ident; + + let expanded = quote! { + #[allow(non_camel_case_types)] + struct #name {} + + impl #name { + pub const ROUTE: &'static str = concat!( + "/", stringify!(#name) + ); + } + + #input + }; + + TokenStream::from(expanded) +} + +// In: main.rs +use route_macro::route; + +struct API; + +impl API { + #[route] + fn get_users() -> Vec { + vec![User { id: 1, name: "Alice" }] + } + + #[route] + fn create_user() -> User { + User { id: 2, name: "Bob" } + } +} + +#[derive(Debug)] +struct User { + id: u32, + name: &'static str, +} + +fn main() { + println!("Route: {}", get_users::ROUTE); +} +``` + + +## Function-like Macros + + +```python !! py +# Python - Function with dynamic code +import sqlparse + +def sql(query: str): + # Validate and format SQL at runtime + parsed = sqlparse.parse(query)[0] + return str(parsed) + +query = sql("SELECT * FROM users WHERE id = 1") +``` + +```rust !! rs +// Rust - Compile-time SQL validation +// In: sql_macro/src/lib.rs +use proc_macro::{TokenStream, TokenTree}; +use quote::quote; + +#[proc_macro] +pub fn sql(input: TokenStream) -> TokenStream { + // Simple validation (check for SELECT keyword) + let tokens: Vec = input.into_iter().collect(); + + if let Some(token) = tokens.first() { + let token_str = token.to_string(); + if token_str.to_uppercase().contains("SELECT") { + // Generate validated SQL string + let expanded = quote! { + #token_str + }; + return TokenStream::from(expanded); + } + } + + panic!("Invalid SQL: Must contain SELECT"); +} + +// In: main.rs +use sql_macro::sql; + +fn main() { + let query = sql!(SELECT * FROM users WHERE id = 1); + println!("Valid SQL: {}", query); +} +``` + + +## Advanced Macro Patterns + +### Builder Pattern Macro + + +```python !! py +# Python - Chained methods (runtime) +class QueryBuilder: + def __init__(self): + self._select = [] + self._where = [] + + def select(self, *fields): + self._select.extend(fields) + return self + + def where_clause(self, condition): + self._where.append(condition) + return self + + def build(self): + return f"SELECT {', '.join(self._select)} WHERE {' AND '.join(self._where)}" + +query = QueryBuilder().select("name", "age").where_clause("age > 18").build() +``` + +```rust !! rs +// Rust - Compile-time builder generation +macro_rules! builder { + ($struct_name:ident { $($field:ident: $field_type:ty),* $(,)? }) => { + struct $struct_name { + $($field: Option<$field_type>),* + } + + impl $struct_name { + fn new() -> Self { + $struct_name { + $($field: None),* + } + } + + $( + fn $field(mut self, value: $field_type) -> Self { + self.$field = Some(value); + self + } + )* + + fn build(self) -> Result { + Ok(stringify!($struct_name).to_string()) + } + } + }; +} + +builder!(QueryBuilder { + select: Vec, + where_clause: String, +}); + +fn main() { + let builder = QueryBuilder::new() + .select(vec!["name".to_string(), "age".to_string()]) + .where_clause("age > 18".to_string()); + + println!("{:?}", builder.build()); +} +``` + + +## Macro Best Practices + +### 1. Prefer Macros Over Code Copy-Paste + + +```python !! py +# Python - Use decorators to avoid repetition +def log_result(func): + def wrapper(*args, **kwargs): + result = func(*args, **kwargs) + print(f"{func.__name__} = {:?}", result) + return result + return wrapper + +@log_result +def add(x, y): + return x + y + +@log_result +def multiply(x, y): + return x * y +``` + +```rust !! rs +// Rust - Use macros to avoid boilerplate +macro_rules! impl_math_op { + ($func_name:ident, $op:tt) => { + fn $func_name(x: i32, y: i32) -> i32 { + let result = x $op y; + println!("{} = {:?}", stringify!($func_name), result); + result + } + }; +} + +impl_math_op!(add, +); +impl_math_op!(multiply, *); + +fn main() { + add(5, 3); + multiply(4, 7); +} +``` + + +### 2. Document Macros Clearly + + +```rust !! rs +/// Macro to create a HashMap with initial key-value pairs. +/// +/// # Examples +/// +/// ``` +/// use std::collections::HashMap; +/// +/// let map = hashmap!( +/// "name" => "Alice", +/// "age" => 30 +/// ); +/// ``` +/// +/// # Syntax +/// +/// `hashmap!(key1 => value1, key2 => value2, ...)` +macro_rules! hashmap { + ( $($key:expr => $value:expr),* $(,)? ) => {{ + let mut m = std::collections::HashMap::new(); + $( + m.insert($key, $value); + )* + m + }}; +} +``` + + +### 3. Test Macros Thoroughly + + +```rust !! rs +macro_rules! compute { + ($x:expr, $op:tt, $y:expr) => {{ + let x = $x; + let y = $y; + x $op y + }}; +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_compute_addition() { + assert_eq!(compute!(5, +, 3), 8); + } + + #[test] + fn test_compute_multiplication() { + assert_eq!(compute!(5, *, 3), 15); + } + + #[test] + fn test_compute_complex() { + assert_eq!(compute!(2 + 3, *, 4), 20); + } +} +``` + + +## Common Use Cases + +### 1. Serialization + + +```python !! py +# Python - dataclasses with asdict +from dataclasses import dataclass, asdict + +@dataclass +class User: + id: int + name: str + +user = User(id=1, name="Alice") +print(asdict(user)) +``` + +```rust !! rs +// Rust - Serialize derive (from serde) +use serde::{Serialize, Deserialize}; + +#[derive(Serialize, Deserialize)] +struct User { + id: u32, + name: String, +} + +fn main() -> Result<(), Box> { + let user = User { id: 1, name: "Alice".to_string() }; + let json = serde_json::to_string(&user)?; + println!("{}", json); + Ok(()) +} +``` + + +### 2. Error Handling + + +```python !! py +# Python - Custom exceptions +class ValidationError(Exception): + pass + +def validate_age(age: int): + if age < 0: + raise ValidationError("Age cannot be negative") + return age +``` + +```rust !! rs +// Rust - thiserror macro for error types +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum ValidationError { + #[error("Age cannot be negative: {0}")] + NegativeAge(i32), + + #[error("Invalid name: {0}")] + InvalidName(String), +} + +fn validate_age(age: i32) -> Result { + if age < 0 { + Err(ValidationError::NegativeAge(age)) + } else { + Ok(age) + } +} + +fn main() { + match validate_age(-5) { + Ok(age) => println!("Valid age: {}", age), + Err(e) => println!("Error: {}", e), + } +} +``` + + +### 3. Testing Utilities + + +```python !! py +# Python - pytest fixtures and parametrize +import pytest + +@pytest.mark.parametrize("input,expected", [ + (2, 4), + (3, 9), + (4, 16), +]) +def test_square(input, expected): + assert input * input == expected +``` + +```rust !! rs +// Rust - Custom test macro +macro_rules! test_cases { + ($test_name:ident, $func:expr, $(($input:expr, $expected:expr)),+) => { + #[test] + fn $test_name() { + $( + assert_eq!($func($input), $expected); + )+ + } + }; +} + +fn square(x: i32) -> i32 { + x * x +} + +test_cases!(test_square, square, + (2, 4), + (3, 9), + (4, 16) +); +``` + + +## Debugging Macros + + +```rust !! rs +// Use cargo expand to see macro expansion +// Install: cargo install cargo-expand +// Run: cargo expand + +macro_rules! debug_macro { + ($expr:expr) => { + println!("{} = {:?}", stringify!($expr), $expr); + }; +} + +fn main() { + let x = 42; + debug_macro!(x * 2); + // Output: x * 2 = 84 +} + +// To see expanded code: +// cargo expand --macro debug_macro +``` + + +## Key Takeaways + +### When to Use Macros +- **Use macros** to eliminate code duplication +- **Use macros** to create domain-specific languages (DSLs) +- **Use macros** to implement compile-time validation +- **Use derive macros** for automatic trait implementations + +### When NOT to Use Macros +- **Avoid macros** when a regular function works +- **Avoid macros** for simple type abstractions (use generics instead) +- **Avoid macros** that obscure code meaning + +### Macro Types +1. **Declarative macros** (`macro_rules!`): Pattern-based, simpler +2. **Derive macros**: Auto-implement traits +3. **Attribute macros**: Modify item behavior +4. **Function-like macros**: Custom syntax extensions + +### Python vs Rust Metaprogramming +- **Python decorators**: Runtime modification, flexible but slower +- **Rust macros**: Compile-time code generation, type-safe, zero-cost + +## Exercises + +1. Create a macro that generates enum variants with associated data +2. Build a derive macro that generates JSON serialization code +3. Implement an attribute macro that adds automatic logging to functions +4. Create a macro for building complex data structures +5. Build a domain-specific language using macros + +## Next Module + +In Module 19, we'll explore **Performance Optimization**, covering zero-cost abstractions, iterators, benchmarking, and profiling techniques. diff --git a/content/docs/py2rust/module-18-macros.zh-cn.mdx b/content/docs/py2rust/module-18-macros.zh-cn.mdx new file mode 100644 index 0000000..068c4a7 --- /dev/null +++ b/content/docs/py2rust/module-18-macros.zh-cn.mdx @@ -0,0 +1,205 @@ +--- +title: "模块 18: 宏" +description: "掌握 Rust 宏,包括使用 macro_rules! 的声明式宏、派生宏和过程宏" +--- + +# 模块 18: 宏 + +## 学习目标 + +完成本模块后,你将能够: +- 使用 `macro_rules!` 编写声明式宏 +- 创建自定义派生宏 +- 理解过程宏 +- 在编译时生成代码 +- 使用宏减少样板代码 + +## 宏简介 + +Python 有装饰器和元类用于元编程。Rust 有更强大的宏系统,在编译时运行。 + + +```python !! py +# Python - 用于元编程的装饰器 +def timing_decorator(func): + def wrapper(*args, **kwargs): + import time + start = time.time() + result = func(*args, **kwargs) + end = time.time() + print(f"{func.__name__} took {end - start:.4f}s") + return result + return wrapper + +@timing_decorator +def slow_function(): + import time + time.sleep(0.1) + return "done" +``` + +```rust !! rs +// Rust - 用于代码生成的声明式宏 +macro_rules! time_it { + ($func:expr) => {{ + let start = std::time::Instant::now(); + let result = $func; + let duration = start.elapsed(); + println!(" took {:?}", duration); + result + }}; +} + +fn slow_function() -> &'static str { + std::thread::sleep(std::time::Duration::from_millis(100)); + "done" +} + +fn main() { + time_it!(slow_function()); +} +``` + + +**关键区别:** Python 装饰器在运行时修改函数,而 Rust 宏在编译时生成代码,零运行时成本。 + +## 使用 `macro_rules!` 的声明式宏 + +### 模式匹配 + + +```python !! py +# Python - 没有直接等价物 +# 会使用装饰器或元类 +def create_getter_setter(field_name): + # 这是运行时的,不是编译时的 + pass +``` + +```rust !! rs +// Rust - 编译时代码生成 +macro_rules! make_getter_setter { + ($field:ident, $type:ty) => { + fn get_$field(&self) -> &$type { + &self.$field + } + + fn set_$field(&mut self, value: $type) { + self.$field = value; + } + }; +} + +struct Person { + name: String, + age: u32, +} + +impl Person { + make_getter_setter!(name, String); + make_getter_setter!(age, u32); +} +``` + + +## 派生宏 + +派生宏自动为结构体和枚举实现 trait。 + + +```python !! py +# Python - 没有直接等价物 +# 会使用元类或类装饰器 +from dataclasses import dataclass + +@dataclass +class Person: + name: str + age: int +``` + +```rust !! rs +// Rust - 自定义派生宏 +use proc_macro::TokenStream; +use quote::quote; +use syn::{parse_macro_input, DeriveInput}; + +#[proc_macro_derive(ToDict)] +pub fn to_dict_derive(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + let name = &input.ident; + + let expanded = quote! { + impl ToDict for #name { + fn to_dict(&self) -> std::collections::HashMap { + let mut map = std::collections::HashMap::new(); + map.insert(stringify!(#name).to_string(), format!("{:?}", self)); + map + } + } + }; + + TokenStream::from(expanded) +} +``` + + +## 宏的最佳实践 + +### 1. 优先使用宏而非复制粘贴代码 + + +```python !! py +# Python - 使用装饰器避免重复 +def log_result(func): + def wrapper(*args, **kwargs): + result = func(*args, **kwargs) + print(f"{func.__name__} = {:?}", result) + return result + return wrapper +``` + +```rust !! rs +// Rust - 使用宏避免样板代码 +macro_rules! impl_math_op { + ($func_name:ident, $op:tt) => { + fn $func_name(x: i32, y: i32) -> i32 { + let result = x $op y; + println!("{} = {:?}", stringify!($func_name), result); + result + } + }; +} + +impl_math_op!(add, +); +impl_math_op!(multiply, *); +``` + + +## 关键要点 + +### 何时使用宏 +- **使用宏** 消除代码重复 +- **使用宏** 创建领域特定语言 (DSL) +- **使用宏** 实现编译时验证 +- **使用派生宏** 自动实现 trait + +### 何时不使用宏 +- **避免宏** 当普通函数可以工作时 +- **避免宏** 用于简单的类型抽象(改用泛型) +- **避免宏** 使代码含义模糊 + +### Python vs Rust 元编程 +- **Python 装饰器**: 运行时修改,灵活但较慢 +- **Rust 宏**: 编译时代码生成,类型安全,零成本 + +## 练习 + +1. 创建一个生成带有关联数据的枚举变体的宏 +2. 构建一个派生宏来生成 JSON 序列化代码 +3. 实现一个为函数自动添加日志的属性宏 +4. 创建一个用于构建复杂数据结构的宏 + +## 下一模块 + +在模块 19 中,我们将探索 **性能优化**,涵盖零成本抽象、迭代器、基准测试和性能分析技术。 diff --git a/content/docs/py2rust/module-18-macros.zh-tw.mdx b/content/docs/py2rust/module-18-macros.zh-tw.mdx new file mode 100644 index 0000000..fbbb4c8 --- /dev/null +++ b/content/docs/py2rust/module-18-macros.zh-tw.mdx @@ -0,0 +1,182 @@ +--- +title: "模組 18: 巨集" +description: "掌握 Rust 巨集,包括使用 macro_rules! 的宣告式巨集、派生巨集和程序巨集" +--- + +# 模組 18: 巨集 + +## 學習目標 + +完成本模組後,你將能夠: +- 使用 `macro_rules!` 編寫宣告式巨集 +- 建立自訂派生巨集 +- 理解程序巨集 +- 在編譯時生成程式碼 +- 使用巨集減少樣板程式碼 + +## 巨集簡介 + +Python 有裝飾器和元類用於元程式設計。Rust 有更強大的巨集系統,在編譯時執行。 + + +```python !! py +# Python - 用於元程式設計的裝飾器 +def timing_decorator(func): + def wrapper(*args, **kwargs): + import time + start = time.time() + result = func(*args, **kwargs) + end = time.time() + print(f"{func.__name__} took {end - start:.4f}s") + return result + return wrapper +``` + +```rust !! rs +// Rust - 用於程式碼生成的宣告式巨集 +macro_rules! time_it { + ($func:expr) => {{ + let start = std::time::Instant::now(); + let result = $func; + let duration = start.elapsed(); + println!(" took {:?}", duration); + result + }}; +} +``` + + +**關鍵區別:** Python 裝飾器在執行時修改函式,而 Rust 巨集在編譯時生成程式碼,零執行時成本。 + +## 使用 `macro_rules!` 的宣告式巨集 + +### 模式匹配 + + +```python !! py +# Python - 沒有直接等價物 +# 會使用裝飾器或元類 +``` + +```rust !! rs +// Rust - 編譯時程式碼生成 +macro_rules! make_getter_setter { + ($field:ident, $type:ty) => { + fn get_$field(&self) -> &$type { + &self.$field + } + + fn set_$field(&mut self, value: $type) { + self.$field = value; + } + }; +} + +struct Person { + name: String, + age: u32, +} + +impl Person { + make_getter_setter!(name, String); + make_getter_setter!(age, u32); +} +``` + + +## 派生巨集 + +派生巨集自動為結構體和列舉實作 trait。 + + +```python !! py +# Python - 沒有直接等價物 +# 會使用元類或類裝飾器 +from dataclasses import dataclass + +@dataclass +class Person: + name: str + age: int +``` + +```rust !! rs +// Rust - 自訂派生巨集 +use proc_macro::TokenStream; +use quote::quote; +use syn::{parse_macro_input, DeriveInput}; + +#[proc_macro_derive(ToDict)] +pub fn to_dict_derive(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + let name = &input.ident; + + let expanded = quote! { + impl ToDict for #name { + fn to_dict(&self) -> std::collections::HashMap { + let mut map = std::collections::HashMap::new(); + map.insert(stringify!(#name).to_string(), format!("{:?}", self)); + map + } + } + }; + + TokenStream::from(expanded) +} +``` + + +## 巨集的最佳實踐 + +### 1. 優先使用巨集而非複製貼上程式碼 + + +```python !! py +# Python - 使用裝飾器避免重複 +def log_result(func): + def wrapper(*args, **kwargs): + result = func(*args, **kwargs) + print(f"{func.__name__} = {:?}", result) + return result + return wrapper +``` + +```rust !! rs +// Rust - 使用巨集避免樣板程式碼 +macro_rules! impl_math_op { + ($func_name:ident, $op:tt) => { + fn $func_name(x: i32, y: i32) -> i32 { + let result = x $op y; + println!("{} = {:?}", stringify!($func_name), result); + result + } + }; +} + +impl_math_op!(add, +); +impl_math_op!(multiply, *); +``` + + +## 關鍵要點 + +### 何時使用巨集 +- **使用巨集** 消除程式碼重複 +- **使用巨集** 建立領域特定語言 (DSL) +- **使用巨集** 實作編譯時驗證 +- **使用派生巨集** 自動實作 trait + +### 何時不使用巨集 +- **避免巨集** 當普通函式可以工作時 +- **避免巨集** 用於簡單的型別抽象(改用泛型) +- **避免巨集** 使程式碼含義模糊 + +## 練習 + +1. 建立一個生成帶有關聯資料的列舉變體的巨集 +2. 建構一個派生巨集來生成 JSON 序列化程式碼 +3. 實作一個為函式自動新增日誌的屬性巨集 + +## 下一模組 + +在模組 19 中,我們將探索 **效能優化**,涵蓋零成本抽象、迭代器、基準測試和效能分析技術。 diff --git a/content/docs/py2rust/module-19-performance.mdx b/content/docs/py2rust/module-19-performance.mdx new file mode 100644 index 0000000..7c32b3e --- /dev/null +++ b/content/docs/py2rust/module-19-performance.mdx @@ -0,0 +1,716 @@ +--- +title: "Module 19: Performance" +description: "Master performance optimization in Rust with zero-cost abstractions, iterators, benchmarking, and profiling" +--- + +# Module 19: Performance + +## Learning Objectives + +By the end of this module, you'll be able to: +- Understand zero-cost abstractions +- Use iterators for efficient data processing +- Write benchmark tests with Criterion +- Profile Rust applications +- Optimize memory allocations +- Apply performance optimization patterns + +## Zero-Cost Abstractions + +Rust's zero-cost abstractions mean you don't pay performance penalties for high-level constructs. + + +```python !! py +# Python - Abstractions have runtime overhead +def process_list(items): + # List comprehension is faster but still creates intermediate lists + return [x * 2 for x in items if x > 0] + +# Generator is more memory efficient +def process_gen(items): + for x in items: + if x > 0: + yield x * 2 + +# Both have interpreter overhead +``` + +```rust !! rs +// Rust - Abstractions compile to efficient machine code +fn process_list(items: &[i32]) -> Vec { + items.iter() + .filter(|&&x| x > 0) + .map(|&x| x * 2) + .collect() +} + +// This compiles to code as efficient as hand-written loops +// No allocations for intermediate results +``` + + +**Key Insight:** Rust compiler optimizes iterator chains to be as fast as hand-written loops. + +## Iterators vs Loops + + +```python !! py +# Python - Loop vs comprehension +import timeit + +def loop_sum(numbers): + total = 0 + for n in numbers: + total += n + return total + +def comprehension_sum(numbers): + return sum(numbers) + +# List comprehension is generally faster +numbers = range(1000) +print("Loop:", timeit.timeit(lambda: loop_sum(numbers), number=1000)) +print("Built-in:", timeit.timeit(lambda: comprehension_sum(numbers), number=1000)) +``` + +```rust !! rs +// Rust - Iterator vs for loop +use std::time::Instant; + +fn loop_sum(numbers: &[i32]) -> i32 { + let mut total = 0; + for &n in numbers { + total += n; + } + total +} + +fn iterator_sum(numbers: &[i32]) -> i32 { + numbers.iter().sum() +} + +// Both compile to identical machine code! +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_sums() { + let numbers: Vec = (0..1000).collect(); + assert_eq!(loop_sum(&numbers), iterator_sum(&numbers)); + } +} +``` + + +## Lazy Evaluation + + +```python !! py +# Python - Generators are lazy +import itertools + +def process_numbers(): + # Generator expressions are lazy + numbers = (x * 2 for x in range(1000000)) + filtered = (x for x in numbers if x % 3 == 0) + # Nothing computed until we consume + return sum(filtered) + +# Efficient memory usage +result = process_numbers() +``` + +```rust !! rs +// Rust - Iterators are lazy by default +fn process_numbers() -> i64 { + // Nothing is computed until collect() or similar + (0..1_000_000) + .map(|x| x * 2) + .filter(|&x| x % 3 == 0) + .sum() +} + +// Zero heap allocations, entirely stack-based +fn main() { + let result = process_numbers(); + println!("Sum: {}", result); +} +``` + + +## Benchmarking with Criterion + + +```python !! py +# Python - pytest-benchmark +import pytest + +def fibonacci(n): + if n <= 1: + return n + return fibonacci(n - 1) + fibonacci(n - 2) + +@pytest.mark.benchmark(group="fib") +def test_fib_10(benchmark): + result = benchmark(fibonacci, 10) + assert result == 55 + +@pytest.mark.benchmark(group="fib") +def test_fib_20(benchmark): + result = benchmark(fibonacci, 20) + assert result == 6765 +``` + +```rust !! rs +// Rust - Criterion benchmarks +// Cargo.toml: +// [dev-dependencies] +// criterion = "0.5" + +// [[bench]] +// name = "fibonacci" +// harness = false + +fn fibonacci(n: u64) -> u64 { + match n { + 0 => 0, + 1 => 1, + _ => fibonacci(n - 1) + fibonacci(n - 2), + } +} + +// benches/fibonacci.rs: +use criterion::{black_box, criterion_group, criterion_main, Criterion}; + +fn benchmark_fibonacci(c: &mut Criterion) { + c.bench_function("fib 10", |b| { + b.iter(|| fibonacci(black_box(10))) + }); + + c.bench_function("fib 20", |b| { + b.iter(|| fibonacci(black_box(20))) + }); +} + +criterion_group!(benches, benchmark_fibonacci); +criterion_main!(benches); + +// Run: cargo bench +``` + + +## Memory Optimization + +### Stack vs Heap Allocations + + +```python !! py +# Python - Mostly heap allocations +def process_small_data(): + # Small integers are interned (cached) + a = 256 + b = 256 + return a is b # True for small integers + +def process_large_data(): + # Large data always on heap + data = [x for x in range(1000000)] + return sum(data) +``` + +```rust !! rs +// Rust - Control over allocations +fn process_small_data() { + // Primitives on stack + let a: i32 = 256; + let b: i32 = 256; + // No heap allocation +} + +fn process_large_data() -> i64 { + // Vec controls heap allocation + let data: Vec = (0..1_000_000).collect(); + data.iter().sum() +} + +fn stack_allocated() -> [i32; 1000] { + // Fixed-size array on stack + [0; 1000] +} +``` + + +### String vs &str + + +```python !! py +# Python - String overhead +def process_strings(): + # All strings are heap-allocated + name = "Alice" + greeting = f"Hello, {name}" + return greeting +``` + +```rust !! rs +// Rust - Choose based on ownership needs +fn use_static_string() -> &'static str { + // No allocation, compile-time constant + "Hello" +} + +fn use_owned_string() -> String { + // Heap-allocated, owned data + "Alice".to_string() +} + +fn use_borrowed_string(s: &str) -> usize { + // Just a reference, no allocation + s.len() +} + +// Use &str for function parameters when possible +fn print_greeting(name: &str) { + println!("Hello, {}", name); +} +``` + + +## Profiling Tools + + +```bash !! bash +# Python - cProfile and py-spy +# Built-in profiling +python -m cProfile -o profile.stats script.py + +# py-spy for sampling profiler +py-spy record --output profile.svg -- python script.py + +# Memory profiling +python -m memory_profiler script.py +``` + +```rust !! rs +// Rust - Flamegraph and perf +// Install flamegraph: cargo install flamegraph + +// Run with flamegraph +cargo flamegraph + +// Or use perf (Linux) +perf record -g cargo run +perf script | flamegraph.pl > flamegraph.svg + +// Valgrind for detailed analysis +cargo install cargo-valgrind +cargo valgrind run +``` + + +## Optimization Techniques + +### 1. Allocations + + +```python !! py +# Python - Use generators to reduce memory +import sys + +def bad_memory(): + # Creates entire list in memory + return [x * 2 for x in range(1000000)] + +def good_memory(): + # Generator uses constant memory + return (x * 2 for x in range(1000000)) + +print("List:", sys.getsizeof(bad_memory())) +print("Generator:", sys.getsizeof(good_memory())) +``` + +```rust !! rs +// Rust - Pre-allocate when size is known +fn bad_allocations() -> Vec { + let mut vec = Vec::new(); + for i in 0..1_000_000 { + vec.push(i * 2); // May reallocate multiple times + } + vec +} + +fn good_allocations() -> Vec { + let mut vec = Vec::with_capacity(1_000_000); + for i in 0..1_000_000 { + vec.push(i * 2); // No reallocation + } + vec +} + +// Even better: use iterators +fn best_allocations() -> Vec { + (0..1_000_000) + .map(|i| i * 2) + .collect() // Allocates exact size once +} +``` + + +### 2. Algorithmic Optimization + + +```python !! py +# Python - Choose right data structure +import time + +def linear_search(items, target): + for item in items: + if item == target: + return True + return False + +def binary_search(items, target): + # Requires sorted list + import bisect + index = bisect.bisect_left(items, target) + return index < len(items) and items[index] == target + +items = list(range(1000000)) +start = time.time() +linear_search(items, 999999) +print(f"Linear: {time.time() - start:.6f}s") + +start = time.time() +binary_search(items, 999999) +print(f"Binary: {time.time() - start:.6f}s") +``` + +```rust !! rs +// Rust - Use appropriate collections +use std::collections::HashSet; + +fn linear_search(items: &[i32], target: i32) -> bool { + items.iter().any(|&x| x == target) +} + +fn hash_set_search(items: &HashSet, target: i32) -> bool { + items.contains(&target) // O(1) average +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_search() { + let items: Vec = (0..1_000_000).collect(); + let set: HashSet = items.iter().copied().collect(); + + assert!(linear_search(&items, 999_999)); + assert!(hash_set_search(&set, 999_999)); + } +} +``` + + +### 3. Parallel Processing + + +```python !! py +# Python - multiprocessing +from multiprocessing import Pool +import time + +def process_item(x): + return x * x + +def sequential(items): + return [process_item(x) for x in items] + +def parallel(items): + with Pool() as pool: + return pool.map(process_item, items) + +items = range(1000000) +start = time.time() +sequential(items) +print(f"Sequential: {time.time() - start:.2f}s") + +start = time.time() +parallel(items) +print(f"Parallel: {time.time() - start:.2f}s") +``` + +```rust !! rs +// Rust - Rayon for parallelism +use rayon::prelude::*; + +fn process_item(x: i32) -> i32 { + x * x +} + +fn sequential(items: &[i32]) -> Vec { + items.iter() + .map(|&x| process_item(x)) + .collect() +} + +fn parallel(items: &[i32]) -> Vec { + items.par_iter() // Parallel iterator + .map(|&x| process_item(x)) + .collect() +} + +fn main() { + let items: Vec = (0..1_000_000).collect(); + + let start = std::time::Instant::now(); + let _result = sequential(&items); + println!("Sequential: {:?}", start.elapsed()); + + let start = std::time::Instant::now(); + let _result = parallel(&items); + println!("Parallel: {:?}", start.elapsed()); +} +``` + + +## SIMD Optimization + + +```python !! py +# Python - NumPy for vectorized operations +import numpy as np +import time + +def python_loop(size): + data = list(range(size)) + return [x * 2 for x in data] + +def numpy_vectorized(size): + data = np.arange(size) + return data * 2 # SIMD operations + +size = 10_000_000 + +start = time.time() +python_loop(size) +print(f"Python loop: {time.time() - start:.3f}s") + +start = time.time() +numpy_vectorized(size) +print(f"NumPy: {time.time() - start:.3f}s") +``` + +```rust !! rs +// Rust - Packed_simd or automatic SIMD +use std::time::Instant; + +fn simple_loop(data: &[f32]) -> Vec { + data.iter().map(|&x| x * 2.0).collect() +} + +// Rust compiler may auto-vectorize this +fn potentially_simd(data: &[f32]) -> Vec { + data.iter() + .map(|&x| x * 2.0) + .collect() +} + +// Check assembly with: cargo rustc --release -- --emit asm +fn main() { + let data: Vec = (0..1_000_000).map(|i| i as f32).collect(); + + let start = Instant::now(); + let _result = simple_loop(&data); + println!("Simple: {:?}", start.elapsed()); + + let start = Instant::now(); + let _result = potentially_simd(&data); + println!("Potential SIMD: {:?}", start.elapsed()); +} +``` + + +## Memory Layout Optimization + + +```python !! py +# Python - No control over memory layout +class Data: + def __init__(self): + self.flag = True # bool + self.value = 42 # int + self.name = "test" # str +``` + +```rust !! rs +// Rust - Control memory layout for cache efficiency +// Bad: interleaved small and large fields +struct BadLayout { + flag: bool, // 1 byte + 7 padding + number: u64, // 8 bytes + another_flag: bool, // 1 byte + 7 padding + value: u64, // 8 bytes +} // Total: 32 bytes + +// Good: group by size +struct GoodLayout { + number: u64, // 8 bytes + value: u64, // 8 bytes + flag: bool, // 1 byte + another_flag: bool, // 1 byte + // 6 bytes padding +} // Total: 24 bytes + +// Even better: use #[repr(C)] for FFI or pack +#[repr(C)] +struct PackedLayout { + flag: bool, + number: u64, + another_flag: bool, + value: u64, +} + +fn main() { + println!("Bad: {}", std::mem::size_of::()); + println!("Good: {}", std::mem::size_of::()); +} +``` + + +## Zero-Cost abstractions in Practice + + +```python !! py +# Python - Multiple passes create temporary lists +def process(items): + # Each comprehension creates a new list + filtered = [x for x in items if x > 0] + doubled = [x * 2 for x in filtered] + return sum(doubled) + +# More efficient version +def process_gen(items): + return sum(x * 2 for x in items if x > 0) +``` + +```rust !! rs +// Rust - Single pass, no intermediate allocations +fn process(items: &[i32]) -> i32 { + items.iter() + .filter(|&&x| x > 0) + .map(|&x| x * 2) + .sum() +} + +// Compiles to code equivalent to: +fn process_optimized(items: &[i32]) -> i32 { + let mut total = 0; + for &x in items { + if x > 0 { + total += x * 2; + } + } + total +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_optimization() { + let items = vec![-1, 2, -3, 4, 5]; + assert_eq!(process(&items), 22); + assert_eq!(process_optimized(&items), 22); + } +} +``` + + +## Release Mode Optimization + + +```bash !! bash +# Python - Always interpreted, no release mode +python script.py + +# Use PyPy or Cython for performance +pypy script.py +``` + +```rust !! rs +// Rust - Debug vs Release builds + +// Debug: No optimization, fast compile, includes checks +// cargo build + +// Release: Full optimization, slower compile +// cargo build --release + +// Optimize for binary size: +// cargo build --release +// Add to Cargo.toml: +// [profile.release] +// opt-level = "z" # or "s" for size + +// Profile-guided optimization: +// 1. Build with profiling instrumentation +// cargo build --release +// 2. Run typical workload to generate profile data +// 3. Rebuild using profile data +// cargo build --release +``` + + +## Performance Tips Summary + +### Memory +- Use `Vec::with_capacity()` when size is known +- Prefer `&str` over `String` for function arguments +- Use stack arrays `[T; N]` for small, fixed-size data +- Avoid unnecessary clones + +### Algorithms +- Choose appropriate data structures (HashSet, HashMap, BTreeMap) +- Use iterators for lazy evaluation +- Profile before optimizing +- Consider parallelism with Rayon + +### Compilation +- Always benchmark in release mode (`--release`) +- Use `cargo flamegraph` for visualization +- Check assembly output: `cargo rustc -- --emit asm` +- Enable link-time optimization: `lto = true` in Cargo.toml + +## Key Takeaways + +### Performance Philosophy +- **Python**: "Premature optimization is the root of all evil" - optimize based on profiling +- **Rust**: Zero-cost abstractions - write idiomatic code, it's already fast + +### When to Optimize +1. **Profile first**: Measure before optimizing +2. **Optimize hot paths**: Focus on code that runs frequently +3. **Consider tradeoffs**: Readability vs performance + +### Rust Performance Advantages +- No garbage collector pauses +- Predictable performance +- Zero-cost abstractions +- Manual memory control +- Compile-time optimization + +## Exercises + +1. Benchmark different sorting algorithms +2. Optimize a data processing pipeline using iterators +3. Profile an application and optimize the bottleneck +4. Implement parallel processing with Rayon +5. Compare string handling strategies + +## Next Module + +In Module 20, our final module, we'll build a **Complete REST API Project** using Actix-web or Axum, including database integration, JWT authentication, testing, and deployment preparation. diff --git a/content/docs/py2rust/module-19-performance.zh-cn.mdx b/content/docs/py2rust/module-19-performance.zh-cn.mdx new file mode 100644 index 0000000..f8f05fd --- /dev/null +++ b/content/docs/py2rust/module-19-performance.zh-cn.mdx @@ -0,0 +1,190 @@ +--- +title: "模块 19: 性能优化" +description: "掌握 Rust 性能优化,包括零成本抽象、迭代器、基准测试和性能分析" +--- + +# 模块 19: 性能优化 + +## 学习目标 + +完成本模块后,你将能够: +- 理解零成本抽象 +- 使用迭代器高效处理数据 +- 使用 Criterion 编写基准测试 +- 分析 Rust 应用程序 +- 优化内存分配 +- 应用性能优化模式 + +## 零成本抽象 + +Rust 的零成本抽象意味着高级结构不会带来性能损失。 + + +```python !! py +# Python - 抽象有运行时开销 +def process_list(items): + # 列表推导比循环快但仍创建中间列表 + return [x * 2 for x in items if x > 0] + +# 生成器更节省内存 +def process_gen(items): + for x in items: + if x > 0: + yield x * 2 + +# 两者都有解释器开销 +``` + +```rust !! rs +// Rust - 抽象编译为高效的机器码 +fn process_list(items: &[i32]) -> Vec { + items.iter() + .filter(|&&x| x > 0) + .map(|&x| x * 2) + .collect() +} + +// 这编译成的代码与手写循环一样高效 +// 没有中间结果的分配 +``` + + +**核心洞察:** Rust 编译器优化迭代器链,使其与手写循环一样快。 + +## 迭代器 vs 循环 + + +```python !! py +# Python - 循环 vs 推导式 +import timeit + +def loop_sum(numbers): + total = 0 + for n in numbers: + total += n + return total + +def comprehension_sum(numbers): + return sum(numbers) + +# 列表推导通常更快 +``` + +```rust !! rs +// Rust - 迭代器 vs for 循环 +fn loop_sum(numbers: &[i32]) -> i32 { + let mut total = 0; + for &n in numbers { + total += n; + } + total +} + +fn iterator_sum(numbers: &[i32]) -> i32 { + numbers.iter().sum() +} + +// 两者编译成相同的机器码! +``` + + +## 使用 Criterion 基准测试 + + +```python !! py +# Python - pytest-benchmark +import pytest + +def fibonacci(n): + if n <= 1: + return n + return fibonacci(n - 1) + fibonacci(n - 2) + +@pytest.mark.benchmark(group="fib") +def test_fib_10(benchmark): + result = benchmark(fibonacci, 10) + assert result == 55 +``` + +```rust !! rs +// Rust - Criterion 基准测试 +// Cargo.toml: +// [dev-dependencies] +// criterion = "0.5" + +// [[bench]] +// name = "fibonacci" +// harness = false + +fn fibonacci(n: u64) -> u64 { + match n { + 0 => 0, + 1 => 1, + _ => fibonacci(n - 1) + fibonacci(n - 2), + } +} + +// benches/fibonacci.rs: +use criterion::{black_box, criterion_group, criterion_main, Criterion}; + +fn benchmark_fibonacci(c: &mut Criterion) { + c.bench_function("fib 10", |b| { + b.iter(|| fibonacci(black_box(10))) + }); +} + +criterion_group!(benches, benchmark_fibonacci); +criterion_main!(benches); + +// 运行: cargo bench +``` + + +## 性能技巧总结 + +### 内存 +- 大小已知时使用 `Vec::with_capacity()` +- 函数参数优先使用 `&str` 而非 `String` +- 小型固定大小数据使用栈数组 `[T; N]` +- 避免不必要的克隆 + +### 算法 +- 选择合适的数据结构 (HashSet, HashMap, BTreeMap) +- 使用迭代器进行惰性求值 +- 优化前先分析 +- 考虑使用 Rayon 并行处理 + +### 编译 +- 始终在 release 模式下进行基准测试 (`--release`) +- 使用 `cargo flamegraph` 可视化 +- 启用链接时优化: Cargo.toml 中设置 `lto = true` + +## 关键要点 + +### 性能哲学 +- **Python**: "过早优化是万恶之源" - 基于分析进行优化 +- **Rust**: 零成本抽象 - 编写惯用代码,它已经很快了 + +### 何时优化 +1. **先分析**: 优化前先测量 +2. **优化热路径**: 专注于频繁运行的代码 +3. **权衡考虑**: 可读性 vs 性能 + +### Rust 性能优势 +- 无垃圾回收暂停 +- 可预测的性能 +- 零成本抽象 +- 手动内存控制 +- 编译时优化 + +## 练习 + +1. 基准测试不同的排序算法 +2. 使用迭代器优化数据处理管道 +3. 分析应用程序并优化瓶颈 +4. 使用 Rayon 实现并行处理 +5. 比较字符串处理策略 + +## 下一模块 + +在模块 20(最后一个模块)中,我们将构建一个**完整的 REST API 项目**,使用 Actix-web 或 Axum,包括数据库集成、JWT 认证、测试和部署准备。 diff --git a/content/docs/py2rust/module-19-performance.zh-tw.mdx b/content/docs/py2rust/module-19-performance.zh-tw.mdx new file mode 100644 index 0000000..5829570 --- /dev/null +++ b/content/docs/py2rust/module-19-performance.zh-tw.mdx @@ -0,0 +1,117 @@ +--- +title: "模組 19: 效能優化" +description: "掌握 Rust 效能優化,包括零成本抽象、迭代器、基準測試和效能分析" +--- + +# 模組 19: 效能優化 + +## 學習目標 + +完成本模組後,你將能夠: +- 理解零成本抽象 +- 使用迭代器高效處理資料 +- 使用 Criterion 編寫基準測試 +- 分析 Rust 應用程式 +- 優化記憶體分配 + +## 零成本抽象 + +Rust 的零成本抽象意味著高級結構不會帶來效能損失。 + + +```python !! py +# Python - 抽象有執行時開銷 +def process_list(items): + return [x * 2 for x in items if x > 0] +``` + +```rust !! rs +// Rust - 抽象編譯為高效的機器碼 +fn process_list(items: &[i32]) -> Vec { + items.iter() + .filter(|&&x| x > 0) + .map(|&x| x * 2) + .collect() +} + +// 這編譯成的程式碼與手寫迴圈一樣高效 +``` + + +**核心洞察:** Rust 編譯器優化迭代器鏈,使其與手寫迴圈一樣快。 + +## 使用 Criterion 基準測試 + + +```python !! py +# Python - pytest-benchmark +import pytest + +def fibonacci(n): + if n <= 1: + return n + return fibonacci(n - 1) + fibonacci(n - 2) + +@pytest.mark.benchmark(group="fib") +def test_fib_10(benchmark): + result = benchmark(fibonacci, 10) + assert result == 55 +``` + +```rust !! rs +// Rust - Criterion 基準測試 +// Cargo.toml: +// [dev-dependencies] +// criterion = "0.5" + +fn fibonacci(n: u64) -> u64 { + match n { + 0 => 0, + 1 => 1, + _ => fibonacci(n - 1) + fibonacci(n - 2), + } +} + +// benches/fibonacci.rs: +use criterion::{black_box, criterion_group, criterion_main, Criterion}; + +fn benchmark_fibonacci(c: &mut Criterion) { + c.bench_function("fib 10", |b| { + b.iter(|| fibonacci(black_box(10))) + }); +} + +criterion_group!(benches, benchmark_fibonacci); +criterion_main!(benches); + +// 執行: cargo bench +``` + + +## 效能技巧總結 + +### 記憶體 +- 大小已知時使用 `Vec::with_capacity()` +- 函式參數優先使用 `&str` 而非 `String` +- 小型固定大小資料使用棧陣列 `[T; N]` + +### 演算法 +- 選擇合適的資料結構 +- 使用迭代器進行惰性求值 +- 考慮使用 Rayon 平行處理 + +### Rust 效能優勢 +- 無垃圾回收暫停 +- 可預測的效能 +- 零成本抽象 +- 手動記憶體控制 + +## 練習 + +1. 基準測試不同的排序演算法 +2. 使用迭代器優化資料處理管道 +3. 分析應用程式並優化瓶頸 + +## 下一模組 + +在模組 20(最後一個模組)中,我們將建構一個**完整的 REST API 專案**。 diff --git a/content/docs/py2rust/module-20-web-api-project.mdx b/content/docs/py2rust/module-20-web-api-project.mdx new file mode 100644 index 0000000..6ed5e01 --- /dev/null +++ b/content/docs/py2rust/module-20-web-api-project.mdx @@ -0,0 +1,1058 @@ +--- +title: "Module 20: REST API Project" +description: "Build a complete REST API with Axum, including database integration, JWT authentication, testing, and deployment" +--- + +# Module 20: REST API Project + +## Learning Objectives + +By the end of this module, you'll have built: +- A complete REST API using Axum +- Database integration with SQLx +- JWT authentication system +- Comprehensive test suite +- Production-ready deployment setup + +## Project Overview + +We'll build a Task Management API with: +- User registration and authentication +- CRUD operations for tasks +- JWT-based authentication +- PostgreSQL database +- Comprehensive testing + + +```python !! py +# Python - FastAPI project structure +task_api/ +├── app/ +│ ├── __init__.py +│ ├── main.py # FastAPI app +│ ├── models.py # SQLAlchemy models +│ ├── schemas.py # Pydantic schemas +│ ├── auth.py # Authentication +│ └── database.py # Database setup +├── tests/ +│ ├── test_auth.py +│ └── test_tasks.py +├── requirements.txt +└── .env +``` + +```rust !! rs +// Rust - Axum project structure +task_api/ +├── Cargo.toml +├── .env +├── src/ +│ ├── main.rs # Axum app setup +│ ├── models.rs # Database models +│ ├── handlers.rs # Request handlers +│ ├── auth.rs # JWT authentication +│ ├── db.rs # Database connection +│ └── error.rs # Error types +└── tests/ + ├── auth_tests.rs + └── task_tests.rs +``` + + +## Cargo.toml Setup + + +```python !! py +# Python - requirements.txt +fastapi==0.109.0 +uvicorn[standard]==0.27.0 +sqlalchemy==2.0.25 +pydantic==2.5.3 +pydantic-settings==2.1.0 +python-jose[cryptography]==3.3.0 +passlib[bcrypt]==1.7.4 +pytest==7.4.4 +httpx==0.26.0 +``` + +```rust !! rs +// Rust - Cargo.toml +[package] +name = "task_api" +version = "0.1.0" +edition = "2021" + +[dependencies] +axum = "0.7" +tokio = { version = "1", features = ["full"] } +sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "postgres", "uuid", "chrono"] } +serde = { version = "1", features = ["derive"] } +serde_json = "1" +uuid = { version = "1", features = ["serde", "v4"] } +chrono = { version = "0.4", features = ["serde"] } +jsonwebtoken = "9" +bcrypt = "0.15" +validator = { version = "0.16", features = ["derive"] } +tower-http = { version = "0.5", features = ["cors", "trace"] } +anyhow = "1" + +[dev-dependencies] +tower = "0.4" +http-body-util = "0.1" +``` + + +## Database Models + + +```python !! py +# Python - SQLAlchemy models +# app/models.py +from sqlalchemy import Column, String, Boolean, DateTime, ForeignKey +from sqlalchemy.dialects.postgresql import UUID +from sqlalchemy.ext.declarative import declarative_base +from datetime import datetime +import uuid + +Base = declarative_base() + +class User(Base): + __tablename__ = "users" + + id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) + email = Column(String, unique=True, nullable=False, index=True) + password_hash = Column(String, nullable=False) + created_at = Column(DateTime, default=datetime.utcnow) + +class Task(Base): + __tablename__ = "tasks" + + id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) + user_id = Column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=False) + title = Column(String, nullable=False) + description = Column(String) + completed = Column(Boolean, default=False) + created_at = Column(DateTime, default=datetime.utcnow) +``` + +```rust !! rs +// Rust - SQLx models +// src/models.rs +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; +use sqlx::FromRow; +use uuid::Uuid; + +#[derive(Debug, Serialize, Deserialize, FromRow)] +pub struct User { + pub id: Uuid, + pub email: String, + pub password_hash: String, + pub created_at: DateTime, +} + +#[derive(Debug, Serialize, Deserialize, FromRow)] +pub struct Task { + pub id: Uuid, + pub user_id: Uuid, + pub title: String, + pub description: Option, + pub completed: bool, + pub created_at: DateTime, +} + +// Input types +#[derive(Debug, Deserialize)] +pub struct CreateUser { + pub email: String, + pub password: String, +} + +#[derive(Debug, Deserialize)] +pub struct CreateTask { + pub title: String, + pub description: Option, +} + +#[derive(Debug, Deserialize)] +pub struct UpdateTask { + pub title: Option, + pub description: Option, + pub completed: Option, +} +``` + + +## Database Connection + + +```python !! py +# Python - Database connection +# app/database.py +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker +import os + +DATABASE_URL = os.getenv("DATABASE_URL", "postgresql://localhost/taskdb") + +engine = create_engine(DATABASE_URL) +SessionLocal = sessionmaker(bind=engine) + +def get_db(): + db = SessionLocal() + try: + yield db + finally: + db.close() +``` + +```rust !! rs +// Rust - Database connection +// src/db.rs +use sqlx::{PgPool, Postgres, Pool}; +use anyhow::Result; + +pub type DbPool = Pool; + +pub async fn create_pool(database_url: &str) -> Result { + let pool = PgPool::connect(database_url).await?; + // Run migrations + sqlx::migrate!("./migrations").run(&pool).await?; + Ok(pool) +} + +// Cloneable handle for the pool +#[derive(Clone)] +pub struct AppState { + pub db: DbPool, + pub jwt_secret: String, +} +``` + + +## JWT Authentication + + +```python !! py +# Python - JWT authentication +# app/auth.py +from datetime import datetime, timedelta +from jose import JWTError, jwt +from passlib.context import CryptContext +from fastapi import Depends, HTTPException, status +from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials + +SECRET_KEY = "your-secret-key" +ALGORITHM = "HS256" +ACCESS_TOKEN_EXPIRE_MINUTES = 30 + +pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") +security = HTTPBearer() + +def verify_password(plain_password, hashed_password): + return pwd_context.verify(plain_password, hashed_password) + +def get_password_hash(password): + return pwd_context.hash(password) + +def create_access_token(data: dict): + to_encode = data.copy() + expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) + to_encode.update({"exp": expire}) + return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) + +async def get_current_user( + credentials: HTTPAuthorizationCredentials = Depends(security), + db = Depends(get_db) +): + try: + payload = jwt.decode( + credentials.credentials, + SECRET_KEY, + algorithms=[ALGORITHM] + ) + user_id: str = payload.get("sub") + if user_id is None: + raise HTTPException(status_code=401, detail="Invalid token") + except JWTError: + raise HTTPException(status_code=401, detail="Invalid token") + + # Fetch user from database + user = db.query(User).filter(User.id == user_id).first() + if user is None: + raise HTTPException(status_code=401, detail="User not found") + return user +``` + +```rust !! rs +// Rust - JWT authentication +// src/auth.rs +use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation}; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; +use anyhow::Result; +use axum::{ + extract::Request, + http::HeaderMap, + middleware::Next, + response::Response, +}; + +#[derive(Debug, Serialize, Deserialize)] +pub struct Claims { + pub sub: String, + pub exp: usize, +} + +pub struct AuthUser { + pub id: Uuid, +} + +pub fn hash_password(password: &str) -> Result { + let cost = 12; + Ok(bcrypt::hash(password, cost)?) +} + +pub fn verify_password(password: &str, hash: &str) -> Result { + Ok(bcrypt::verify(password, hash)?) +} + +pub fn create_token(user_id: Uuid, secret: &str) -> Result { + let expiration = chrono::Utc::now() + .checked_add_signed(chrono::Duration::minutes(30)) + .expect("valid timestamp") + .timestamp(); + + let claims = Claims { + sub: user_id.to_string(), + exp: expiration as usize, + }; + + let token = encode( + &Header::default(), + &claims, + &EncodingKey::from_secret(secret.as_ref()), + )?; + + Ok(token) +} + +pub fn verify_token(token: &str, secret: &str) -> Result { + let token_data = decode::( + token, + &DecodingKey::from_secret(secret.as_ref()), + &Validation::default(), + )?; + Ok(token_data.claims) +} + +// Middleware to extract user from JWT +pub async fn auth_middleware( + headers: HeaderMap, + mut request: Request, + next: Next, +) -> Result { + let auth_header = headers + .get("authorization") + .ok_or_else(|| StatusCode::UNAUTHORIZED)? + .to_str() + .map_err(|_| StatusCode::UNAUTHORIZED)?; + + if !auth_header.starts_with("Bearer ") { + return Err(StatusCode::UNAUTHORIZED); + } + + let token = &auth_header[7..]; + let secret = std::env::var("JWT_SECRET").unwrap(); + + match verify_token(token, &secret) { + Ok(claims) => { + let user_id = Uuid::parse_str(&claims.sub)?; + request.extensions_mut().insert(AuthUser { id: user_id }); + Ok(next.run(request).await) + } + Err(_) => Err(StatusCode::UNAUTHORIZED), + } +} +``` + + +## Request Handlers + + +```python !! py +# Python - FastAPI handlers +# app/main.py +from fastapi import FastAPI, Depends, HTTPException, status +from sqlalchemy.orm import Session +from . import models, schemas, auth, database +from typing import List + +app = FastAPI(title="Task API") + +@app.post("/register", response_model=schemas.User) +async def register(user_data: schemas.CreateUser, db: Session = Depends(database.get_db)): + # Check if user exists + existing_user = db.query(models.User).filter( + models.User.email == user_data.email + ).first() + if existing_user: + raise HTTPException(status_code=400, detail="Email already registered") + + # Create user + hashed_password = auth.get_password_hash(user_data.password) + user = models.User( + email=user_data.email, + password_hash=hashed_password + ) + db.add(user) + db.commit() + db.refresh(user) + return user + +@app.post("/login") +async def login(credentials: schemas.Login, db: Session = Depends(database.get_db)): + user = db.query(models.User).filter( + models.User.email == credentials.email + ).first() + + if not user or not auth.verify_password(credentials.password, user.password_hash): + raise HTTPException(status_code=401, detail="Invalid credentials") + + access_token = auth.create_access_token(data={"sub": str(user.id)}) + return {"access_token": access_token, "token_type": "bearer"} + +@app.post("/tasks", response_model=schemas.Task) +async def create_task( + task_data: schemas.CreateTask, + current_user: models.User = Depends(auth.get_current_user), + db: Session = Depends(database.get_db) +): + task = models.Task( + user_id=current_user.id, + title=task_data.title, + description=task_data.description + ) + db.add(task) + db.commit() + db.refresh(task) + return task + +@app.get("/tasks", response_model=List[schemas.Task]) +async def list_tasks( + current_user: models.User = Depends(auth.get_current_user), + db: Session = Depends(database.get_db) +): + tasks = db.query(models.Task).filter( + models.Task.user_id == current_user.id + ).all() + return tasks +``` + +```rust !! rs +// Rust - Axum handlers +// src/handlers.rs +use axum::{ + extract::{Path, State}, + http::StatusCode, + response::IntoResponse, + Json, +}; +use uuid::Uuid; +use crate::{ + models::{CreateTask, Task, UpdateTask, CreateUser}, + auth::{AuthUser, create_token, hash_password, verify_password}, + db::AppState, +}; + +pub async fn register( + State(state): State, + Json(user_data): Json, +) -> Result { + // Check if user exists + let existing = sqlx::query_scalar::<_, Uuid>( + "SELECT id FROM users WHERE email = $1" + ) + .bind(&user_data.email) + .fetch_optional(&state.db) + .await + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + + if existing.is_some() { + return Err(StatusCode::CONFLICT); + } + + let password_hash = hash_password(&user_data.password) + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + + let user_id = Uuid::new_v4(); + sqlx::query( + "INSERT INTO users (id, email, password_hash) VALUES ($1, $2, $3)" + ) + .bind(user_id) + .bind(&user_data.email) + .bind(&password_hash) + .execute(&state.db) + .await + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + + let token = create_token(user_id, &state.jwt_secret) + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + + Ok(Json(serde_json::json!({ + "access_token": token, + "token_type": "bearer" + }))) +} + +pub async fn create_task( + State(state): State, + auth_user: axum::Extension, + Json(task_data): Json, +) -> Result { + let task_id = Uuid::new_v4(); + + sqlx::query( + "INSERT INTO tasks (id, user_id, title, description) VALUES ($1, $2, $3, $4)" + ) + .bind(task_id) + .bind(auth_user.id) + .bind(&task_data.title) + .bind(&task_data.description) + .execute(&state.db) + .await + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + + let task = sqlx::query_as::<_, Task>("SELECT * FROM tasks WHERE id = $1") + .bind(task_id) + .fetch_one(&state.db) + .await + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + + Ok(Json(task)) +} + +pub async fn list_tasks( + State(state): State, + auth_user: axum::Extension, +) -> Result { + let tasks = sqlx::query_as::<_, Task>( + "SELECT * FROM tasks WHERE user_id = $1 ORDER BY created_at DESC" + ) + .bind(auth_user.id) + .fetch_all(&state.db) + .await + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + + Ok(Json(tasks)) +} + +pub async fn get_task( + State(state): State, + auth_user: axum::Extension, + Path(id): Path, +) -> Result { + let task = sqlx::query_as::<_, Task>( + "SELECT * FROM tasks WHERE id = $1 AND user_id = $2" + ) + .bind(id) + .bind(auth_user.id) + .fetch_optional(&state.db) + .await + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + + match task { + Some(task) => Ok(Json(task)), + None => Err(StatusCode::NOT_FOUND), + } +} + +pub async fn update_task( + State(state): State, + auth_user: axum::Extension, + Path(id): Path, + Json(update): Json, +) -> Result { + let task = sqlx::query_as::<_, Task>( + "SELECT * FROM tasks WHERE id = $1 AND user_id = $2" + ) + .bind(id) + .bind(auth_user.id) + .fetch_optional(&state.db) + .await + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + + if task.is_none() { + return Err(StatusCode::NOT_FOUND); + } + + if let Some(title) = update.title { + sqlx::query("UPDATE tasks SET title = $1 WHERE id = $2") + .bind(&title) + .bind(id) + .execute(&state.db) + .await + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + } + + if let Some(completed) = update.completed { + sqlx::query("UPDATE tasks SET completed = $1 WHERE id = $2") + .bind(completed) + .bind(id) + .execute(&state.db) + .await + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + } + + let updated_task = sqlx::query_as::<_, Task>("SELECT * FROM tasks WHERE id = $1") + .bind(id) + .fetch_one(&state.db) + .await + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + + Ok(Json(updated_task)) +} + +pub async fn delete_task( + State(state): State, + auth_user: axum::Extension, + Path(id): Path, +) -> Result { + let result = sqlx::query("DELETE FROM tasks WHERE id = $1 AND user_id = $2") + .bind(id) + .bind(auth_user.id) + .execute(&state.db) + .await + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + + if result.rows_affected() == 0 { + return Err(StatusCode::NOT_FOUND); + } + + Ok(StatusCode::NO_CONTENT) +} +``` + + +## Main Application + + +```python !! py +# Python - FastAPI app +# app/main.py +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware + +app = FastAPI(title="Task API") + +app.add_middleware( + CORSMiddleware, + allow_origins=["http://localhost:3000"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +@app.get("/") +async def root(): + return {"message": "Task API"} + +@app.get("/health") +async def health(): + return {"status": "healthy"} + +if __name__ == "__main__": + import uvicorn + uvicorn.run(app, host="0.0.0.0", port=8000) +``` + +```rust !! rs +// Rust - Axum app +// src/main.rs +use axum::{ + routing::{get, post, put, delete}, + Router, +}; +use tower_http::cors::{CorsLayer, Any}; +use tokio::net::TcpListener; +use std::env; + +mod models; +mod handlers; +mod auth; +mod db; + +use handlers::{register, create_task, list_tasks, get_task, update_task, delete_task}; +use auth::auth_middleware; +use db::{create_pool, AppState}; + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + // Initialize dotenv + dotenv::dotenv().ok(); + + let database_url = env::var("DATABASE_URL") + .expect("DATABASE_URL must be set"); + let jwt_secret = env::var("JWT_SECRET") + .expect("JWT_SECRET must be set"); + + // Create database pool + let pool = create_pool(&database_url).await?; + + let app_state = AppState { + db: pool, + jwt_secret, + }; + + // Build router + let app = Router::new() + .route("/", get(root)) + .route("/health", get(health)) + .route("/register", post(register)) + .route("/tasks", get(list_tasks).post(create_task)) + .route("/tasks/:id", get(get_task).put(update_task).delete(delete_task)) + .layer(CorsLayer::new().allow_origin(Any).allow_methods(Any).allow_headers(Any)) + .with_state(app_state); + + // Start server + let listener = TcpListener::bind("0.0.0.0:8000").await?; + println!("Server running on http://0.0.0.0:8000"); + axum::serve(listener, app).await?; + + Ok(()) +} + +async fn root() -> &'static str { + "Task API" +} + +async fn health() -> &'static str { + "healthy" +} +``` + + +## Database Migrations + + +```sql !! sql +-- Python - Alembic migrations +-- migrations/versions/001_initial.py + +def upgrade(): + op.create_table( + 'users', + sa.Column('id', sa.UUID(), primary_key=True), + sa.Column('email', sa.String(), nullable=False), + sa.Column('password_hash', sa.String(), nullable=False), + sa.Column('created_at', sa.DateTime(), nullable=False), + ) + op.create_index('ix_users_email', 'users', ['email'], unique=True) + + op.create_table( + 'tasks', + sa.Column('id', sa.UUID(), primary_key=True), + sa.Column('user_id', sa.UUID(), nullable=False), + sa.Column('title', sa.String(), nullable=False), + sa.Column('description', sa.String()), + sa.Column('completed', sa.Boolean(), default=False), + sa.Column('created_at', sa.DateTime(), nullable=False), + sa.ForeignKeyConstraint(['user_id'], ['users.id']), + ) +``` + +```rust !! rs +// Rust - SQLx migrations +// migrations/001_initial.sql + +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; + +CREATE TABLE users ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + email VARCHAR(255) NOT NULL UNIQUE, + password_hash VARCHAR(255) NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE INDEX idx_users_email ON users(email); + +CREATE TABLE tasks ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, + title VARCHAR(255) NOT NULL, + description TEXT, + completed BOOLEAN NOT NULL DEFAULT FALSE, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE INDEX idx_tasks_user_id ON tasks(user_id); +``` + + +## Testing + + +```python !! py +# Python - pytest tests +# tests/test_tasks.py +import pytest +from httpx import AsyncClient +from sqlalchemy.ext.asyncio import AsyncSession + +@pytest.mark.asyncio +async def test_create_task(async_client: AsyncClient, auth_headers: dict): + response = await async_client.post( + "/tasks", + json={"title": "Test Task", "description": "Test Description"}, + headers=auth_headers + ) + assert response.status_code == 200 + data = response.json() + assert data["title"] == "Test Task" + assert data["completed"] == False + +@pytest.mark.asyncio +async def test_list_tasks(async_client: AsyncClient, auth_headers: dict): + response = await async_client.get("/tasks", headers=auth_headers) + assert response.status_code == 200 + tasks = response.json() + assert isinstance(tasks, list) + +@pytest.mark.asyncio +async def test_update_task(async_client: AsyncClient, auth_headers: dict): + # Create task first + create_response = await async_client.post( + "/tasks", + json={"title": "Original Title"}, + headers=auth_headers + ) + task_id = create_response.json()["id"] + + # Update task + response = await async_client.put( + f"/tasks/{task_id}", + json={"completed": True}, + headers=auth_headers + ) + assert response.status_code == 200 + assert response.json()["completed"] == True +``` + +```rust !! rs +// Rust - Integration tests +// tests/task_tests.rs +use axum::{ + body::Body, + http::{Request, StatusCode, header::AUTHORIZATION}, +}; +use http_body_util::BodyExt; +use serde_json::json; +use tower::ServiceExt; + +#[tokio::test] +async fn test_create_task() { + let app = create_test_app().await; + + // Register and login to get token + let token = register_and_login(&app).await; + + let response = app + .oneshot( + Request::builder() + .method("POST") + .uri("/tasks") + .header(AUTHORIZATION, format!("Bearer {}", token)) + .header("content-type", "application/json") + .body(Body::from(json!({ + "title": "Test Task", + "description": "Test Description" + }).to_string())) + .unwrap() + ) + .await + .unwrap(); + + assert_eq!(response.status(), StatusCode::OK); + + let body = response.into_body().collect().await.unwrap().to_bytes(); + let task: serde_json::Value = serde_json::from_slice(&body).unwrap(); + assert_eq!(task["title"], "Test Task"); + assert_eq!(task["completed"], false); +} + +#[tokio::test] +async fn test_list_tasks() { + let app = create_test_app().await; + let token = register_and_login(&app).await; + + let response = app + .oneshot( + Request::builder() + .method("GET") + .uri("/tasks") + .header(AUTHORIZATION, format!("Bearer {}", token)) + .body(Body::empty()) + .unwrap() + ) + .await + .unwrap(); + + assert_eq!(response.status(), StatusCode::OK); +} + +#[tokio::test] +async fn test_unauthorized_access() { + let app = create_test_app().await; + + let response = app + .oneshot( + Request::builder() + .method("GET") + .uri("/tasks") + .body(Body::empty()) + .unwrap() + ) + .await + .unwrap(); + + assert_eq!(response.status(), StatusCode::UNAUTHORIZED); +} +``` + + +## Deployment + + +```dockerfile !! dockerfile +# Python - Dockerfile +FROM python:3.11-slim + +WORKDIR /app + +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +COPY . . + +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"] +``` + +```dockerfile !! dockerfile +# Rust - Dockerfile +# Dockerfile +FROM rust:1.75 as builder + +WORKDIR /app +COPY Cargo.toml Cargo.lock ./ +COPY src ./src +COPY migrations ./migrations + +RUN cargo build --release + +FROM debian:bookworm-slim + +RUN apt-get update && apt-get install -y \ + ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /app + +COPY --from=builder /app/target/release/task_api /app/task_api +COPY migrations ./migrations + +EXPOSE 8000 + +CMD ["./task_api"] +``` + +```yaml !! yaml +# docker-compose.yml (same for both) +version: '3.8' + +services: + api: + build: . + ports: + - "8000:8000" + environment: + - DATABASE_URL=postgresql://user:pass@db:5432/taskdb + - JWT_SECRET=your-secret-key + depends_on: + - db + + db: + image: postgres:16 + environment: + - POSTGRES_DB=taskdb + - POSTGRES_USER=user + - POSTGRES_PASSWORD=pass + volumes: + - postgres_data:/var/lib/postgresql/data + +volumes: + postgres_data: +``` + + +## Project Summary + +### What We Built + +1. **REST API with Axum** + - User registration and authentication + - CRUD operations for tasks + - JWT-based security + +2. **Database Integration** + - PostgreSQL with SQLx + - Type-safe queries + - Migration support + +3. **Testing** + - Integration tests + - Test fixtures and helpers + +4. **Production Ready** + - Docker deployment + - Environment configuration + - Error handling + +### Key Takeaways + +**Python/FastAPI:** +- Rapid development +- Easy to read and modify +- Dynamic typing flexibility +- Good for prototyping + +**Rust/Axum:** +- Type-safe database queries +- Zero-cost abstractions +- Memory safety guarantees +- Excellent performance +- Compile-time error checking + +## Final Exercises + +1. Add task filtering and pagination +2. Implement task categories/tags +3. Add email notifications +4. Create a frontend client +5. Deploy to production (AWS/DigitalOcean/Render) + +## Conclusion + +Congratulations! You've completed the Python to Rust learning path. You now have: + +- Strong foundation in Rust syntax and concepts +- Experience with real-world Rust development +- Understanding of Rust's performance advantages +- Knowledge of testing and deployment practices + +Continue your Rust journey by: +- Contributing to open-source Rust projects +- Building more complex applications +- Exploring async Rust deeper +- Learning WebAssembly with Rust + +Happy coding in Rust! 🦀 diff --git a/content/docs/py2rust/module-20-web-api-project.zh-cn.mdx b/content/docs/py2rust/module-20-web-api-project.zh-cn.mdx new file mode 100644 index 0000000..ec6a7ad --- /dev/null +++ b/content/docs/py2rust/module-20-web-api-project.zh-cn.mdx @@ -0,0 +1,311 @@ +--- +title: "模块 20: REST API 项目" +description: "使用 Axum 构建完整的 REST API,包括数据库集成、JWT 认证、测试和部署" +--- + +# 模块 20: REST API 项目 + +## 学习目标 + +完成本模块后,你将构建: +- 使用 Axum 的完整 REST API +- 使用 SQLx 的数据库集成 +- JWT 认证系统 +- 全面的测试套件 +- 生产就绪的部署设置 + +## 项目概述 + +我们将构建一个任务管理 API,包含: +- 用户注册和认证 +- 任务的 CRUD 操作 +- 基于 JWT 的认证 +- PostgreSQL 数据库 +- 全面的测试 + + +```python !! py +# Python - FastAPI 项目结构 +task_api/ +├── app/ +│ ├── main.py # FastAPI 应用 +│ ├── models.py # SQLAlchemy 模型 +│ ├── auth.py # 认证 +│ └── database.py # 数据库设置 +├── tests/ +└── requirements.txt +``` + +```rust !! rs +// Rust - Axum 项目结构 +task_api/ +├── Cargo.toml +├── src/ +│ ├── main.rs # Axum 应用设置 +│ ├── models.rs # 数据库模型 +│ ├── handlers.rs # 请求处理 +│ ├── auth.rs # JWT 认证 +│ └── db.rs # 数据库连接 +└── tests/ +``` + + +## Cargo.toml 设置 + + +```python !! py +# Python - requirements.txt +fastapi==0.109.0 +uvicorn[standard]==0.27.0 +sqlalchemy==2.0.25 +pydantic==2.5.3 +python-jose[cryptography]==3.3.0 +passlib[bcrypt]==1.7.4 +``` + +```rust !! rs +// Rust - Cargo.toml +[dependencies] +axum = "0.7" +tokio = { version = "1", features = ["full"] } +sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "postgres", "uuid", "chrono"] } +serde = { version = "1", features = ["derive"] } +serde_json = "1" +uuid = { version = "1", features = ["serde", "v4"] } +chrono = { version = "0.4", features = ["serde"] } +jsonwebtoken = "9" +bcrypt = "0.15" +tower-http = { version = "0.5", features = ["cors", "trace"] } +anyhow = "1" +``` + + +## 数据库模型 + + +```python !! py +# Python - SQLAlchemy 模型 +from sqlalchemy import Column, String, Boolean, DateTime +from sqlalchemy.dialects.postgresql import UUID +import uuid + +class User(Base): + __tablename__ = "users" + + id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) + email = Column(String, unique=True, nullable=False, index=True) + password_hash = Column(String, nullable=False) + created_at = Column(DateTime, default=datetime.utcnow) +``` + +```rust !! rs +// Rust - SQLx 模型 +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; +use sqlx::FromRow; +use uuid::Uuid; + +#[derive(Debug, Serialize, Deserialize, FromRow)] +pub struct User { + pub id: Uuid, + pub email: String, + pub password_hash: String, + pub created_at: DateTime, +} + +#[derive(Debug, Deserialize)] +pub struct CreateUser { + pub email: String, + pub password: String, +} +``` + + +## 主应用程序 + + +```python !! py +# Python - FastAPI 应用 +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware + +app = FastAPI(title="Task API") + +app.add_middleware( + CORSMiddleware, + allow_origins=["http://localhost:3000"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +@app.get("/") +async def root(): + return {"message": "Task API"} +``` + +```rust !! rs +// Rust - Axum 应用 +use axum::{ + routing::{get, post}, + Router, +}; +use tower_http::cors::{CorsLayer, Any}; + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + let app = Router::new() + .route("/", get(root)) + .route("/register", post(register)) + .route("/tasks", get(list_tasks).post(create_task)) + .layer(CorsLayer::new().allow_origin(Any)) + .with_state(app_state); + + let listener = TcpListener::bind("0.0.0.0:8000").await?; + axum::serve(listener, app).await?; + + Ok(()) +} + +async fn root() -> &'static str { + "Task API" +} +``` + + +## 数据库迁移 + + +```sql !! sql +-- SQLx 迁移 +-- migrations/001_initial.sql + +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; + +CREATE TABLE users ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + email VARCHAR(255) NOT NULL UNIQUE, + password_hash VARCHAR(255) NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE TABLE tasks ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, + title VARCHAR(255) NOT NULL, + description TEXT, + completed BOOLEAN NOT NULL DEFAULT FALSE, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); +``` + + +## 部署 + + +```dockerfile !! dockerfile +# Rust - Dockerfile +FROM rust:1.75 as builder + +WORKDIR /app +COPY Cargo.toml Cargo.lock ./ +COPY src ./src + +RUN cargo build --release + +FROM debian:bookworm-slim + +WORKDIR /app +COPY --from=builder /app/target/release/task_api /app/task_api + +EXPOSE 8000 + +CMD ["./task_api"] +``` + +```yaml !! yaml +# docker-compose.yml +version: '3.8' + +services: + api: + build: . + ports: + - "8000:8000" + environment: + - DATABASE_URL=postgresql://user:pass@db:5432/taskdb + depends_on: + - db + + db: + image: postgres:16 + environment: + - POSTGRES_DB=taskdb + - POSTGRES_USER=user + - POSTGRES_PASSWORD=pass + volumes: + - postgres_data:/var/lib/postgresql/data + +volumes: + postgres_data: +``` + + +## 项目总结 + +### 我们构建了什么 + +1. **使用 Axum 的 REST API** + - 用户注册和认证 + - 任务的 CRUD 操作 + - 基于 JWT 的安全 + +2. **数据库集成** + - 使用 SQLx 的 PostgreSQL + - 类型安全的查询 + - 迁移支持 + +3. **生产就绪** + - Docker 部署 + - 环境配置 + - 错误处理 + +### 关键要点 + +**Python/FastAPI:** +- 快速开发 +- 易于阅读和修改 +- 动态类型灵活性 +- 适合原型开发 + +**Rust/Axum:** +- 类型安全的数据库查询 +- 零成本抽象 +- 内存安全保证 +- 卓越的性能 +- 编译时错误检查 + +## 最终练习 + +1. 添加任务过滤和分页 +2. 实现任务分类/标签 +3. 添加邮件通知 +4. 创建前端客户端 +5. 部署到生产环境 + +## 结语 + +恭喜!你已完成 Python 到 Rust 的学习路径。你现在拥有: + +- Rust 语法和概念的坚实基础 +- 实际 Rust 开发经验 +- 理解 Rust 的性能优势 +- 测试和部署实践知识 + +继续你的 Rust 之旅: +- 为开源 Rust 项目做贡献 +- 构建更复杂的应用 +- 深入探索异步 Rust +- 学习使用 Rust 的 WebAssembly + +愉快地使用 Rust 编码! 🦀 diff --git a/content/docs/py2rust/module-20-web-api-project.zh-tw.mdx b/content/docs/py2rust/module-20-web-api-project.zh-tw.mdx new file mode 100644 index 0000000..e94a0dd --- /dev/null +++ b/content/docs/py2rust/module-20-web-api-project.zh-tw.mdx @@ -0,0 +1,274 @@ +--- +title: "模組 20: REST API 專案" +description: "使用 Axum 建構完整的 REST API,包括資料庫整合、JWT 認證、測試和部署" +--- + +# 模組 20: REST API 專案 + +## 學習目標 + +完成本模組後,你將建構: +- 使用 Axum 的完整 REST API +- 使用 SQLx 的資料庫整合 +- JWT 認證系統 +- 全面的測試套件 +- 生產就緒的部署設定 + +## 專案概述 + +我們將建構一個任務管理 API,包含: +- 使用者註冊和認證 +- 任務的 CRUD 操作 +- 基於 JWT 的認證 +- PostgreSQL 資料庫 +- 全面的測試 + + +```python !! py +# Python - FastAPI 專案結構 +task_api/ +├── app/ +│ ├── main.py # FastAPI 應用 +│ ├── models.py # SQLAlchemy 模型 +│ ├── auth.py # 認證 +│ └── database.py # 資料庫設定 +├── tests/ +└── requirements.txt +``` + +```rust !! rs +// Rust - Axum 專案結構 +task_api/ +├── Cargo.toml +├── src/ +│ ├── main.rs # Axum 應用設定 +│ ├── models.rs # 資料庫模型 +│ ├── handlers.rs # 請求處理 +│ ├── auth.rs # JWT 認證 +│ └── db.rs # 資料庫連線 +└── tests/ +``` + + +## Cargo.toml 設定 + + +```python !! py +# Python - requirements.txt +fastapi==0.109.0 +uvicorn[standard]==0.27.0 +sqlalchemy==2.0.25 +pydantic==2.5.3 +python-jose[cryptography]==3.3.0 +passlib[bcrypt]==1.7.4 +``` + +```rust !! rs +// Rust - Cargo.toml +[dependencies] +axum = "0.7" +tokio = { version = "1", features = ["full"] } +sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "postgres", "uuid", "chrono"] } +serde = { version = "1", features = ["derive"] } +serde_json = "1" +uuid = { version = "1", features = ["serde", "v4"] } +chrono = { version = "0.4", features = ["serde"] } +jsonwebtoken = "9" +bcrypt = "0.15" +tower-http = { version = "0.5", features = ["cors", "trace"] } +anyhow = "1" +``` + + +## 資料庫模型 + + +```python !! py +# Python - SQLAlchemy 模型 +from sqlalchemy import Column, String, Boolean, DateTime +from sqlalchemy.dialects.postgresql import UUID +import uuid + +class User(Base): + __tablename__ = "users" + + id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) + email = Column(String, unique=True, nullable=False, index=True) + password_hash = Column(String, nullable=False) + created_at = Column(DateTime, default=datetime.utcnow) +``` + +```rust !! rs +// Rust - SQLx 模型 +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; +use sqlx::FromRow; +use uuid::Uuid; + +#[derive(Debug, Serialize, Deserialize, FromRow)] +pub struct User { + pub id: Uuid, + pub email: String, + pub password_hash: String, + pub created_at: DateTime, +} + +#[derive(Debug, Deserialize)] +pub struct CreateUser { + pub email: String, + pub password: String, +} +``` + + +## 主應用程式 + + +```python !! py +# Python - FastAPI 應用 +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware + +app = FastAPI(title="Task API") + +app.add_middleware( + CORSMiddleware, + allow_origins=["http://localhost:3000"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) +``` + +```rust !! rs +// Rust - Axum 應用 +use axum::{ + routing::{get, post}, + Router, +}; +use tower_http::cors::{CorsLayer, Any}; + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + let app = Router::new() + .route("/", get(root)) + .route("/register", post(register)) + .route("/tasks", get(list_tasks).post(create_task)) + .layer(CorsLayer::new().allow_origin(Any)) + .with_state(app_state); + + let listener = TcpListener::bind("0.0.0.0:8000").await?; + axum::serve(listener, app).await?; + + Ok(()) +} +``` + + +## 部署 + + +```dockerfile !! dockerfile +# Rust - Dockerfile +FROM rust:1.75 as builder + +WORKDIR /app +COPY Cargo.toml Cargo.lock ./ +COPY src ./src + +RUN cargo build --release + +FROM debian:bookworm-slim + +WORKDIR /app +COPY --from=builder /app/target/release/task_api /app/task_api + +EXPOSE 8000 + +CMD ["./task_api"] +``` + +```yaml !! yaml +# docker-compose.yml +version: '3.8' + +services: + api: + build: . + ports: + - "8000:8000" + environment: + - DATABASE_URL=postgresql://user:pass@db:5432/taskdb + depends_on: + - db + + db: + image: postgres:16 + environment: + - POSTGRES_DB=taskdb + volumes: + - postgres_data:/var/lib/postgresql/data + +volumes: + postgres_data: +``` + + +## 專案總結 + +### 我們建構了什麼 + +1. **使用 Axum 的 REST API** + - 使用者註冊和認證 + - 任務的 CRUD 操作 + - 基於 JWT 的安全 + +2. **資料庫整合** + - 使用 SQLx 的 PostgreSQL + - 型別安全的查詢 + - 遷移支援 + +3. **生產就緒** + - Docker 部署 + - 環境設定 + - 錯誤處理 + +### 關鍵要點 + +**Python/FastAPI:** +- 快速開發 +- 易於閱讀和修改 +- 動態型別靈活性 +- 適合原型開發 + +**Rust/Axum:** +- 型別安全的資料庫查詢 +- 零成本抽象 +- 記憶體安全保證 +- 卓越的效能 +- 編譯時錯誤檢查 + +## 最終練習 + +1. 新增任務過濾和分頁 +2. 實作任務分類/標籤 +3. 新增郵件通知 +4. 建立前端客戶端 +5. 部署到生產環境 + +## 結語 + +恭喜!你已完成 Python 到 Rust 的學習路徑。你現在擁有: + +- Rust 語法和概念的堅實基礎 +- 實際 Rust 開發經驗 +- 理解 Rust 的效能優勢 +- 測試和部署實踐知識 + +繼續你的 Rust 之旅: +- 為開源 Rust 專案貢獻 +- 建構更複雜的應用 +- 深入探索非同步 Rust +- 學習使用 Rust 的 WebAssembly + +愉快地使用 Rust 編碼! 🦀 From 407196ed05df96d3da47b5a53298b823aff9e8b0 Mon Sep 17 00:00:00 2001 From: sunguangbiao <2991552132@qq.com> Date: Sat, 27 Dec 2025 10:18:23 +0800 Subject: [PATCH 3/7] fix: escape dollar signs in Kotlin code examples for proper string interpolation in English, Chinese, and Taiwanese translations --- components/code-examples/en/code-examples.ts | 4 ++-- components/code-examples/zh-cn/code-examples.ts | 4 ++-- components/code-examples/zh-tw/code-examples.ts | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/components/code-examples/en/code-examples.ts b/components/code-examples/en/code-examples.ts index 8136877..a27afd2 100644 --- a/components/code-examples/en/code-examples.ts +++ b/components/code-examples/en/code-examples.ts @@ -761,8 +761,8 @@ fun main() { // Smart type casting fun checkStatus(status: StudentStatus) = when (status) { - is StudentStatus.Active -> "Active with GPA ${status.gpa}" - is StudentStatus.OnProbation -> "On probation: ${status.reason}" + is StudentStatus.Active -> "Active with GPA \${status.gpa}" + is StudentStatus.OnProbation -> "On probation: \${status.reason}" is StudentStatus.Graduated -> "Graduated" } }`, diff --git a/components/code-examples/zh-cn/code-examples.ts b/components/code-examples/zh-cn/code-examples.ts index 736b9eb..b5e27d8 100644 --- a/components/code-examples/zh-cn/code-examples.ts +++ b/components/code-examples/zh-cn/code-examples.ts @@ -761,8 +761,8 @@ fun main() { // 智能类型转换 fun checkStatus(status: StudentStatus) = when (status) { - is StudentStatus.Active -> "Active with GPA ${status.gpa}" - is StudentStatus.OnProbation -> "On probation: ${status.reason}" + is StudentStatus.Active -> "Active with GPA \${status.gpa}" + is StudentStatus.OnProbation -> "On probation: \${status.reason}" is StudentStatus.Graduated -> "Graduated" } }`, diff --git a/components/code-examples/zh-tw/code-examples.ts b/components/code-examples/zh-tw/code-examples.ts index 6034b40..0e11ef2 100644 --- a/components/code-examples/zh-tw/code-examples.ts +++ b/components/code-examples/zh-tw/code-examples.ts @@ -761,8 +761,8 @@ fun main() { // 智能類型轉換 fun checkStatus(status: StudentStatus) = when (status) { - is StudentStatus.Active -> "Active with GPA ${status.gpa}" - is StudentStatus.OnProbation -> "On probation: ${status.reason}" + is StudentStatus.Active -> "Active with GPA \${status.gpa}" + is StudentStatus.OnProbation -> "On probation: \${status.reason}" is StudentStatus.Graduated -> "Graduated" } }`, From 481ae9a8fee6f3beb97010c619bb77f5b29806d3 Mon Sep 17 00:00:00 2001 From: sunguangbiao <2991552132@qq.com> Date: Sat, 27 Dec 2025 12:28:33 +0800 Subject: [PATCH 4/7] feat: add Python to Go learning path with 14 comprehensive modules covering syntax, concurrency, web development, and more, including translations in Chinese and Taiwanese --- components/header.tsx | 8 + content/docs/py2go/index.mdx | 171 ++ content/docs/py2go/index.zh-cn.mdx | 171 ++ content/docs/py2go/index.zh-tw.mdx | 171 ++ content/docs/py2go/meta.json | 4 + .../docs/py2go/module-00-go-introduction.mdx | 959 ++++++++++ .../py2go/module-00-go-introduction.zh-cn.mdx | 959 ++++++++++ .../py2go/module-00-go-introduction.zh-tw.mdx | 959 ++++++++++ content/docs/py2go/module-01-basic-syntax.mdx | 666 +++++++ .../py2go/module-01-basic-syntax.zh-cn.mdx | 666 +++++++ .../py2go/module-01-basic-syntax.zh-tw.mdx | 666 +++++++ content/docs/py2go/module-02-control-flow.mdx | 604 ++++++ .../py2go/module-02-control-flow.zh-cn.mdx | 604 ++++++ .../py2go/module-02-control-flow.zh-tw.mdx | 604 ++++++ content/docs/py2go/module-03-functions.mdx | 1231 ++++++++++++ .../docs/py2go/module-03-functions.zh-cn.mdx | 1231 ++++++++++++ .../docs/py2go/module-03-functions.zh-tw.mdx | 1231 ++++++++++++ .../py2go/module-04-structs-interfaces.mdx | 1356 +++++++++++++ .../module-04-structs-interfaces.zh-cn.mdx | 1359 +++++++++++++ .../module-04-structs-interfaces.zh-tw.mdx | 1359 +++++++++++++ .../docs/py2go/module-05-packages-modules.mdx | 979 ++++++++++ .../module-05-packages-modules.zh-cn.mdx | 979 ++++++++++ .../module-05-packages-modules.zh-tw.mdx | 979 ++++++++++ .../docs/py2go/module-06-error-handling.mdx | 1687 +++++++++++++++++ .../py2go/module-06-error-handling.zh-cn.mdx | 1687 +++++++++++++++++ .../py2go/module-06-error-handling.zh-tw.mdx | 1687 +++++++++++++++++ content/docs/py2go/module-07-goroutines.mdx | 1619 ++++++++++++++++ .../docs/py2go/module-07-goroutines.zh-cn.mdx | 1619 ++++++++++++++++ .../docs/py2go/module-07-goroutines.zh-tw.mdx | 1619 ++++++++++++++++ content/docs/py2go/module-08-channels.mdx | 1523 +++++++++++++++ .../docs/py2go/module-08-channels.zh-cn.mdx | 1524 +++++++++++++++ .../docs/py2go/module-08-channels.zh-tw.mdx | 1524 +++++++++++++++ .../docs/py2go/module-09-select-patterns.mdx | 1477 +++++++++++++++ .../py2go/module-09-select-patterns.zh-cn.mdx | 1477 +++++++++++++++ .../py2go/module-09-select-patterns.zh-tw.mdx | 1477 +++++++++++++++ .../docs/py2go/module-10-web-development.mdx | 746 ++++++++ .../py2go/module-10-web-development.zh-cn.mdx | 746 ++++++++ .../py2go/module-10-web-development.zh-tw.mdx | 746 ++++++++ .../py2go/module-11-testing-debugging.mdx | 664 +++++++ .../module-11-testing-debugging.zh-cn.mdx | 664 +++++++ .../module-11-testing-debugging.zh-tw.mdx | 664 +++++++ .../module-12-performance-optimization.mdx | 715 +++++++ ...dule-12-performance-optimization.zh-cn.mdx | 715 +++++++ ...dule-12-performance-optimization.zh-tw.mdx | 715 +++++++ .../docs/py2go/module-13-microservices.mdx | 775 ++++++++ .../py2go/module-13-microservices.zh-cn.mdx | 775 ++++++++ .../py2go/module-13-microservices.zh-tw.mdx | 775 ++++++++ content/docs/py2go/module-14-cloud-native.mdx | 682 +++++++ .../py2go/module-14-cloud-native.zh-cn.mdx | 682 +++++++ .../py2go/module-14-cloud-native.zh-tw.mdx | 682 +++++++ .../docs/py2go/module-15-common-pitfalls.mdx | 533 ++++++ .../py2go/module-15-common-pitfalls.zh-cn.mdx | 533 ++++++ .../py2go/module-15-common-pitfalls.zh-tw.mdx | 533 ++++++ .../docs/py2go/module-16-real-projects.mdx | 798 ++++++++ .../py2go/module-16-real-projects.zh-cn.mdx | 798 ++++++++ .../py2go/module-16-real-projects.zh-tw.mdx | 798 ++++++++ 56 files changed, 51575 insertions(+) create mode 100644 content/docs/py2go/index.mdx create mode 100644 content/docs/py2go/index.zh-cn.mdx create mode 100644 content/docs/py2go/index.zh-tw.mdx create mode 100644 content/docs/py2go/meta.json create mode 100644 content/docs/py2go/module-00-go-introduction.mdx create mode 100644 content/docs/py2go/module-00-go-introduction.zh-cn.mdx create mode 100644 content/docs/py2go/module-00-go-introduction.zh-tw.mdx create mode 100644 content/docs/py2go/module-01-basic-syntax.mdx create mode 100644 content/docs/py2go/module-01-basic-syntax.zh-cn.mdx create mode 100644 content/docs/py2go/module-01-basic-syntax.zh-tw.mdx create mode 100644 content/docs/py2go/module-02-control-flow.mdx create mode 100644 content/docs/py2go/module-02-control-flow.zh-cn.mdx create mode 100644 content/docs/py2go/module-02-control-flow.zh-tw.mdx create mode 100644 content/docs/py2go/module-03-functions.mdx create mode 100644 content/docs/py2go/module-03-functions.zh-cn.mdx create mode 100644 content/docs/py2go/module-03-functions.zh-tw.mdx create mode 100644 content/docs/py2go/module-04-structs-interfaces.mdx create mode 100644 content/docs/py2go/module-04-structs-interfaces.zh-cn.mdx create mode 100644 content/docs/py2go/module-04-structs-interfaces.zh-tw.mdx create mode 100644 content/docs/py2go/module-05-packages-modules.mdx create mode 100644 content/docs/py2go/module-05-packages-modules.zh-cn.mdx create mode 100644 content/docs/py2go/module-05-packages-modules.zh-tw.mdx create mode 100644 content/docs/py2go/module-06-error-handling.mdx create mode 100644 content/docs/py2go/module-06-error-handling.zh-cn.mdx create mode 100644 content/docs/py2go/module-06-error-handling.zh-tw.mdx create mode 100644 content/docs/py2go/module-07-goroutines.mdx create mode 100644 content/docs/py2go/module-07-goroutines.zh-cn.mdx create mode 100644 content/docs/py2go/module-07-goroutines.zh-tw.mdx create mode 100644 content/docs/py2go/module-08-channels.mdx create mode 100644 content/docs/py2go/module-08-channels.zh-cn.mdx create mode 100644 content/docs/py2go/module-08-channels.zh-tw.mdx create mode 100644 content/docs/py2go/module-09-select-patterns.mdx create mode 100644 content/docs/py2go/module-09-select-patterns.zh-cn.mdx create mode 100644 content/docs/py2go/module-09-select-patterns.zh-tw.mdx create mode 100644 content/docs/py2go/module-10-web-development.mdx create mode 100644 content/docs/py2go/module-10-web-development.zh-cn.mdx create mode 100644 content/docs/py2go/module-10-web-development.zh-tw.mdx create mode 100644 content/docs/py2go/module-11-testing-debugging.mdx create mode 100644 content/docs/py2go/module-11-testing-debugging.zh-cn.mdx create mode 100644 content/docs/py2go/module-11-testing-debugging.zh-tw.mdx create mode 100644 content/docs/py2go/module-12-performance-optimization.mdx create mode 100644 content/docs/py2go/module-12-performance-optimization.zh-cn.mdx create mode 100644 content/docs/py2go/module-12-performance-optimization.zh-tw.mdx create mode 100644 content/docs/py2go/module-13-microservices.mdx create mode 100644 content/docs/py2go/module-13-microservices.zh-cn.mdx create mode 100644 content/docs/py2go/module-13-microservices.zh-tw.mdx create mode 100644 content/docs/py2go/module-14-cloud-native.mdx create mode 100644 content/docs/py2go/module-14-cloud-native.zh-cn.mdx create mode 100644 content/docs/py2go/module-14-cloud-native.zh-tw.mdx create mode 100644 content/docs/py2go/module-15-common-pitfalls.mdx create mode 100644 content/docs/py2go/module-15-common-pitfalls.zh-cn.mdx create mode 100644 content/docs/py2go/module-15-common-pitfalls.zh-tw.mdx create mode 100644 content/docs/py2go/module-16-real-projects.mdx create mode 100644 content/docs/py2go/module-16-real-projects.zh-cn.mdx create mode 100644 content/docs/py2go/module-16-real-projects.zh-tw.mdx diff --git a/components/header.tsx b/components/header.tsx index 192fc5e..6f544fc 100644 --- a/components/header.tsx +++ b/components/header.tsx @@ -107,6 +107,14 @@ const SOURCE_LANGUAGES = [ path: 'py2rust', status: 'completed' as const, }, + { + id: 'go', + name: 'Go', + icon: '🐹', + gradient: 'from-cyan-500 to-blue-500', + path: 'py2go', + status: 'completed' as const, + }, ] } // 未来可以添加其他源语言 diff --git a/content/docs/py2go/index.mdx b/content/docs/py2go/index.mdx new file mode 100644 index 0000000..052ec1b --- /dev/null +++ b/content/docs/py2go/index.mdx @@ -0,0 +1,171 @@ +--- +title: "Python → Go" +description: "Learn Go programming language from a Python developer's perspective, focusing on concurrency, performance, and cloud-native development" +--- + +## Why Learn Go? + +Go (Golang) is a modern programming language designed for simplicity, efficiency, and concurrency. As a Python developer, learning Go will expand your capabilities in: + +- **High Performance**: Achieve 10-100x faster execution than Python for CPU-bound tasks +- **True Concurrency**: Break free from Python's GIL with Go's lightweight goroutines +- **Single Binary Deployment**: Distribute your programs as a single executable file +- **System Programming**: Build low-level tools and utilities with ease +- **Microservices**: Create highly scalable and efficient microservices +- **Cloud-Native Development**: Join the ecosystem powering Kubernetes, Docker, and more + +## What You'll Learn + +### Core Concepts +- Go syntax and type system compared to Python +- Static typing and compilation +- Package management and module system +- Interfaces and composition + +### Concurrency & Performance +- Goroutines vs Python threading/multiprocessing +- Channel communication patterns +- Select statements for concurrent operations +- Memory management and garbage collection differences +- Performance optimization techniques + +### Web Development +- HTTP server development +- RESTful API design +- JSON handling and serialization +- WebSocket implementations + +### Production Ready +- Testing methodologies +- Error handling patterns +- Performance profiling +- Deployment strategies + +## Learning Path + +The learning path is organized into **18 progressive modules**: + +1. **Go Introduction**: Understanding Go's design philosophy and setting up your environment +2. **Syntax Comparison**: Mapping Python concepts to Go syntax +3. **Types and Variables**: Deep dive into Go's type system and variable declarations +4. **Control Flow**: Conditionals, loops, and branching in Go +5. **Functions and Methods**: Functions, methods, closures, and defer +6. **Structs and Interfaces**: Go's approach to OOP without classes +7. **Package Management**: Go modules, imports, and dependency management +8. **Error Handling**: Explicit error handling and error interfaces +9. **Goroutines and Concurrency**: Breaking free from the GIL +10. **Channels and Communication**: Safe concurrent data exchange +11. **Select and Patterns**: Advanced concurrency patterns +12. **Web Development**: Building web services and APIs +13. **Testing and Debugging**: Writing tests and debugging Go code +14. **Performance Optimization**: Profiling and optimizing Go applications +15. **Microservices Architecture**: Building distributed systems +16. **Cloud Native Development**: Containers, orchestration, and more +17. **Common Pitfalls**: Avoiding common mistakes when transitioning from Python +18. **Real-world Projects**: Complete projects to solidify your knowledge + +## Quick Start + +Let's begin with a simple comparison between Python and Go: + + +```python !! py +# Python - Dynamic typing, interpreted +def greet(name): + return f"Hello, {name}!" + +if __name__ == "__main__": + print(greet("World")) +``` + +```go !! go +// Go - Static typing, compiled +package main + +import "fmt" + +func greet(name string) string { + return fmt.Sprintf("Hello, %s!", name) +} + +func main() { + fmt.Println(greet("World")) +} +``` + + +## Key Differences + +| Aspect | Python | Go | +|--------|--------|-----| +| **Type System** | Dynamic typing | Static typing with type inference | +| **Execution** | Interpreted | Compiled to machine code | +| **Concurrency** | Limited by GIL | Native goroutines and channels | +| **Memory Management** | Reference counting + GC | Tracing garbage collector | +| **Performance** | Slower (interpreted) | Fast (compiled, similar to C/C++) | +| **Deployment** | Requires runtime | Single binary executable | +| **Package Management** | pip/PyPI/venv | go mod | +| **Error Handling** | Exceptions | Explicit error returns | + +## Performance Comparison + + +```python !! py +# Python - Slower due to interpreter overhead +def fibonacci(n): + if n <= 1: + return n + return fibonacci(n-1) + fibonacci(n-2) + +# This takes several seconds for n=40 +print(fibonacci(35)) +``` + +```go !! go +// Go - Much faster compiled execution +package main + +import "fmt" + +func fibonacci(n int) int { + if n <= 1 { + return n + } + return fibonacci(n-1) + fibonacci(n-2) +} + +func main() { + // This completes almost instantly for n=40 + fmt.Println(fibonacci(35)) +} +``` + + +## Who Should Learn Go? + +Python developers who want to: + +- **Scale beyond Python's limitations**: Overcome the GIL and performance bottlenecks +- **Build high-performance services**: Create fast, efficient backend systems +- **Work on cloud infrastructure**: Contribute to projects like Kubernetes, Docker, Terraform +- **Develop microservices**: Build distributed systems at scale +- **Improve job prospects**: Go is in high demand for infrastructure and backend roles + +## Prerequisites + +- Proficiency in Python programming +- Understanding of basic programming concepts +- Familiarity with command-line interface +- No prior systems programming experience required + +## Ready to Start? + +Begin your journey from Python to Go with our comprehensive learning path. Each module includes: + +- **Interactive code comparisons** side-by-side with Python +- **Performance benchmarks** showing real-world differences +- **Practical examples** from production systems +- **Progressive difficulty** building from basics to advanced topics +- **Real-world projects** to apply your knowledge + +[Start Learning →](./py2go/module-00-go-introduction) diff --git a/content/docs/py2go/index.zh-cn.mdx b/content/docs/py2go/index.zh-cn.mdx new file mode 100644 index 0000000..71d6a14 --- /dev/null +++ b/content/docs/py2go/index.zh-cn.mdx @@ -0,0 +1,171 @@ +--- +title: "Python → Go" +description: "从 Python 开发者的角度学习 Go 语言,重点关注并发、性能和云原生开发" +--- + +## 为什么要学习 Go? + +Go (Golang) 是一门为简洁性、效率和并发而设计的现代编程语言。作为 Python 开发者,学习 Go 将扩展你的能力: + +- **高性能**:在 CPU 密集型任务上实现比 Python 快 10-100 倍的执行速度 +- **真正的并发**:使用 Go 的轻量级 goroutines 摆脱 Python 的 GIL 限制 +- **单一二进制部署**:将程序分发为单个可执行文件 +- **系统编程**:轻松构建底层工具和实用程序 +- **微服务**:创建高度可扩展和高效的微服务 +- **云原生开发**:加入驱动 Kubernetes、Docker 等的生态系统 + +## 你将学到什么 + +### 核心概念 +- Go 语法和类型系统与 Python 的对比 +- 静态类型和编译 +- 包管理和模块系统 +- 接口和组合 + +### 并发与性能 +- Goroutines 与 Python 线程/多进程的对比 +- Channel 通信模式 +- Select 语句用于并发操作 +- 内存管理和垃圾回收的差异 +- 性能优化技巧 + +### Web 开发 +- HTTP 服务器开发 +- RESTful API 设计 +- JSON 处理和序列化 +- WebSocket 实现 + +### 生产就绪 +- 测试方法论 +- 错误处理模式 +- 性能分析 +- 部署策略 + +## 学习路径 + +学习路径包含 **18 个渐进式模块**: + +1. **Go 语言介绍**:理解 Go 的设计理念和环境设置 +2. **语法比较**:将 Python 概念映射到 Go 语法 +3. **类型和变量**:深入了解 Go 的类型系统和变量声明 +4. **控制流**:Go 中的条件、循环和分支 +5. **函数和方法**:函数、方法、闭包和 defer +6. **结构体和接口**:Go 没有 class 的 OOP 方法 +7. **包管理**:Go 模块、导入和依赖管理 +8. **错误处理**:显式错误处理和错误接口 +9. **Goroutines 和并发**:摆脱 GIL 的限制 +10. **Channels 和通信**:安全的并发数据交换 +11. **Select 和模式**:高级并发模式 +12. **Web 开发**:构建 Web 服务和 API +13. **测试和调试**:编写测试和调试 Go 代码 +14. **性能优化**:分析和优化 Go 应用程序 +15. **微服务架构**:构建分布式系统 +16. **云原生开发**:容器、编排等 +17. **常见陷阱**:避免从 Python 过渡时的常见错误 +18. **实战项目**:完整项目巩固你的知识 + +## 快速开始 + +让我们从一个简单的 Python 与 Go 对比开始: + + +```python !! py +# Python - 动态类型,解释执行 +def greet(name): + return f"Hello, {name}!" + +if __name__ == "__main__": + print(greet("World")) +``` + +```go !! go +// Go - 静态类型,编译执行 +package main + +import "fmt" + +func greet(name string) string { + return fmt.Sprintf("Hello, %s!", name) +} + +func main() { + fmt.Println(greet("World")) +} +``` + + +## 主要差异 + +| 方面 | Python | Go | +|------|--------|-----| +| **类型系统** | 动态类型 | 静态类型,带类型推断 | +| **执行方式** | 解释执行 | 编译为机器码 | +| **并发** | 受 GIL 限制 | 原生 goroutines 和 channels | +| **内存管理** | 引用计数 + GC | 追踪式垃圾回收器 | +| **性能** | 较慢(解释执行) | 快速(编译,类似 C/C++) | +| **部署** | 需要运行时 | 单个二进制可执行文件 | +| **包管理** | pip/PyPI/venv | go mod | +| **错误处理** | 异常 | 显式错误返回 | + +## 性能对比 + + +```python !! py +# Python - 由于解释器开销而较慢 +def fibonacci(n): + if n <= 1: + return n + return fibonacci(n-1) + fibonacci(n-2) + +# 计算 n=40 需要几秒钟 +print(fibonacci(35)) +``` + +```go !! go +// Go - 编译后执行速度快得多 +package main + +import "fmt" + +func fibonacci(n int) int { + if n <= 1 { + return n + } + return fibonacci(n-1) + fibonacci(n-2) +} + +func main() { + // 计算 n=40 几乎瞬间完成 + fmt.Println(fibonacci(35)) +} +``` + + +## 谁应该学习 Go? + +想要以下目标的 Python 开发者: + +- **超越 Python 的限制**:克服 GIL 和性能瓶颈 +- **构建高性能服务**:创建快速、高效的后端系统 +- **从事云基础设施**:为 Kubernetes、Docker、Terraform 等项目做贡献 +- **开发微服务**:大规模构建分布式系统 +- **提升就业前景**:Go 在基础设施和后端岗位中需求量很大 + +## 前置要求 + +- 熟练掌握 Python 编程 +- 理解基本编程概念 +- 熟悉命令行界面 +- 无需系统编程经验 + +## 准备开始了吗? + +通过我们全面的学习路径开始从 Python 到 Go 的旅程。每个模块包含: + +- **交互式代码对比**与 Python 并排展示 +- **性能基准测试**展示真实差异 +- **实战示例**来自生产系统 +- **渐进式难度**从基础到高级主题 +- **实战项目**应用你的知识 + +[开始学习 →](./py2go/module-00-go-introduction) diff --git a/content/docs/py2go/index.zh-tw.mdx b/content/docs/py2go/index.zh-tw.mdx new file mode 100644 index 0000000..309a9b5 --- /dev/null +++ b/content/docs/py2go/index.zh-tw.mdx @@ -0,0 +1,171 @@ +--- +title: "Python → Go" +description: "從 Python 開發者的角度學習 Go 語言,重點關注並發、效能和雲原生開發" +--- + +## 為什麼要學習 Go? + +Go (Golang) 是一門為簡潔性、效率和並發而設計的現代程式語言。作為 Python 開發者,學習 Go 將擴展你的能力: + +- **高效能**:在 CPU 密集型任務上實現比 Python 快 10-100 倍的執行速度 +- **真正的並發**:使用 Go 的輕量級 goroutines 擺脫 Python 的 GIL 限制 +- **單一二進位部署**:將程式分發為單個可執行檔案 +- **系統程式設計**:輕鬆構建底層工具和實用程式 +- **微服務**:建立高度可擴展和高效的微服務 +- **雲原生開發**:加入驅動 Kubernetes、Docker 等的生態系統 + +## 你將學到什麼 + +### 核心概念 +- Go 語法和類型系統與 Python 的對比 +- 靜態類型和編譯 +- 套件管理和模組系統 +- 介面和組合 + +### 並發與效能 +- Goroutines 與 Python 執行緒/多程序的對比 +- Channel 通訊模式 +- Select 陳述式用於並發操作 +- 記憶體管理和垃圾回收的差異 +- 效能優化技巧 + +### Web 開發 +- HTTP 伺服器開發 +- RESTful API 設計 +- JSON 處理和序列化 +- WebSocket 實作 + +### 生產就緒 +- 測試方法論 +- 錯誤處理模式 +- 效能分析 +- 部署策略 + +## 學習路徑 + +學習路徑包含 **18 個漸進式模組**: + +1. **Go 語言介紹**:理解 Go 的設計理念和環境設定 +2. **語法比較**:將 Python 概念對應到 Go 語法 +3. **類型和變數**:深入了解 Go 的類型系統和變數宣告 +4. **控制流程**:Go 中的條件、迴圈和分支 +5. **函式和方法**:函式、方法、閉包和 defer +6. **結構體和介面**:Go 沒有 class 的 OOP 方法 +7. **套件管理**:Go 模組、匯入和依賴管理 +8. **錯誤處理**:顯式錯誤處理和錯誤介面 +9. **Goroutines 和並發**:擺脫 GIL 的限制 +10. **Channels 和通訊**:安全的並發資料交換 +11. **Select 和模式**:高階並發模式 +12. **Web 開發**:建構 Web 服務和 API +13. **測試和除錯**:撰寫測試和除錯 Go 程式碼 +14. **效能優化**:分析和優化 Go 應用程式 +15. **微服務架構**:建構分散式系統 +16. **雲原生開發**:容器、編排等 +17. **常見陷阱**:避免從 Python 過渡時的常見錯誤 +18. **實戰專案**:完整專案鞏固你的知識 + +## 快速開始 + +讓我們從一個簡單的 Python 與 Go 對比開始: + + +```python !! py +# Python - 動態類型,直譯執行 +def greet(name): + return f"Hello, {name}!" + +if __name__ == "__main__": + print(greet("World")) +``` + +```go !! go +// Go - 靜態類型,編譯執行 +package main + +import "fmt" + +func greet(name string) string { + return fmt.Sprintf("Hello, %s!", name) +} + +func main() { + fmt.Println(greet("World")) +} +``` + + +## 主要差異 + +| 方面 | Python | Go | +|------|--------|-----| +| **類型系統** | 動態類型 | 靜態類型,帶類型推斷 | +| **執行方式** | 直譯執行 | 編譯為機器碼 | +| **並發** | 受 GIL 限制 | 原生 goroutines 和 channels | +| **記憶體管理** | 引用計數 + GC | 追蹤式垃圾回收器 | +| **效能** | 較慢(直譯執行) | 快速(編譯,類似 C/C++) | +| **部署** | 需要執行時 | 單個二進位可執行檔案 | +| **套件管理** | pip/PyPI/venv | go mod | +| **錯誤處理** | 例外 | 顯式錯誤傳回 | + +## 效能對比 + + +```python !! py +# Python - 由於直譯器開銷而較慢 +def fibonacci(n): + if n <= 1: + return n + return fibonacci(n-1) + fibonacci(n-2) + +# 計算 n=40 需要幾秒鐘 +print(fibonacci(35)) +``` + +```go !! go +// Go - 編譯後執行速度快得多 +package main + +import "fmt" + +func fibonacci(n int) int { + if n <= 1 { + return n + } + return fibonacci(n-1) + fibonacci(n-2) +} + +func main() { + // 計算 n=40 幾乎瞬間完成 + fmt.Println(fibonacci(35)) +} +``` + + +## 誰應該學習 Go? + +想要以下目標的 Python 開發者: + +- **超越 Python 的限制**:克服 GIL 和效能瓶頸 +- **建構高效能服務**:建立快速、高效的后端系統 +- **從事雲基礎設施**:為 Kubernetes、Docker、Terraform 等專案做貢獻 +- **開發微服務**:大規模建構分散式系統 +- **提升就業前景**:Go 在基礎設施和後端崗位中需求量很大 + +## 前置要求 + +- 熟練掌握 Python 程式設計 +- 理解基本程式設計概念 +- 熟悉命令列介面 +- 無需系統程式設計經驗 + +## 準備開始了嗎? + +透過我們全面的學習路徑開始從 Python 到 Go 的旅程。每個模組包含: + +- **互動式程式碼對比**與 Python 並排展示 +- **效能基準測試**展示真實差異 +- **實戰範例**來自生產系統 +- **漸進式難度**從基礎到高階主題 +- **實戰專案**應用你的知識 + +[開始學習 →](./py2go/module-00-go-introduction) diff --git a/content/docs/py2go/meta.json b/content/docs/py2go/meta.json new file mode 100644 index 0000000..cc159e1 --- /dev/null +++ b/content/docs/py2go/meta.json @@ -0,0 +1,4 @@ +{ + "title": "Python → Go", + "root": true +} diff --git a/content/docs/py2go/module-00-go-introduction.mdx b/content/docs/py2go/module-00-go-introduction.mdx new file mode 100644 index 0000000..747cb97 --- /dev/null +++ b/content/docs/py2go/module-00-go-introduction.mdx @@ -0,0 +1,959 @@ +--- +title: "Module 0: Go Language Introduction" +description: "Understanding Go's design philosophy, history, and setting up your development environment" +--- + +## Go Language History and Design Philosophy + +Go (also known as Golang) is a statically typed, compiled programming language designed at Google by Robert Griesemer, Rob Pike, and Ken Thompson. It was created in 2007 and officially announced in 2009. Go was designed to address the challenges of modern software development, particularly in the context of large-scale distributed systems and cloud computing. + +### Why Go Was Created + +Go emerged from frustration with existing languages at Google: + +- **C++** was too complex and slow to compile +- **Java** had verbose syntax and heavy memory footprint +- **Python** was easy to write but slow in execution and had poor concurrency support due to the GIL + +Go's creators wanted a language that: +- Compiled as fast as Python could start +- Ran as fast as C or C++ +- Provided excellent support for concurrency +- Had a simple, clean syntax +- Made it easy to build reliable software + +### Go's Design Philosophy + +Go's design is guided by these core principles: + +#### 1. Simplicity +- **Minimal keywords**: Only 25 keywords in the language +- **Clean syntax**: Easy to read and write +- **Orthogonal features**: Features work together consistently +- **No hidden complexity**: What you see is what you get + + +```python !! py +# Python - Multiple ways to declare variables +x = 5 +y: int = 10 +z = list([1, 2, 3]) +``` + +```go !! go +// Go - One consistent way +var x int = 5 +y := 10 // Short declaration (most common) +``` + + +#### 2. Concurrency +- **Goroutines**: Lightweight threads (thousands can run simultaneously) +- **Channels**: Safe communication between goroutines +- **Select**: Coordinate multiple operations +- **No GIL**: True parallel execution + + +```python !! py +# Python - Limited by GIL +import threading +import time + +def worker(): + time.sleep(1) + print("Done") + +# These don't run in parallel due to GIL +threads = [threading.Thread(target=worker) for _ in range(5)] +for t in threads: + t.start() +for t in threads: + t.join() +``` + +```go !! go +// Go - True parallelism +package main + +import ( + "fmt" + "time" +) + +func worker() { + time.Sleep(time.Second) + fmt.Println("Done") +} + +func main() { + // These run in parallel + for i := 0; i < 5; i++ { + go worker() + } + time.Sleep(2 * time.Second) +} +``` + + +#### 3. Performance +- **Compiled to machine code**: No interpreter overhead +- **Efficient garbage collector**: Low pause times +- **Static typing**: Optimizations at compile time +- **Zero-cost abstractions**: No runtime penalty for high-level constructs + +#### 4. Safety +- **Strong typing**: Catch errors at compile time +- **Memory safety**: No pointer arithmetic (except with unsafe package) +- **Garbage collection**: Automatic memory management +- **Explicit error handling**: Errors are values, not exceptions + + +```python !! py +# Python - Exceptions +try: + result = divide(10, 0) +except ZeroDivisionError as e: + print(f"Error: {e}") +``` + +```go !! go +// Go - Explicit error checking +result, err := divide(10, 0) +if err != nil { + fmt.Printf("Error: %v\n", err) + return +} +``` + + +## Comparison with Python + +| Feature | Python | Go | +|---------|--------|-----| +| **Paradigm** | Multi-paradigm (OO, functional, procedural) | Multi-paradigm (procedural, concurrent, with OO features) | +| **Typing** | Dynamic (duck typing) | Static (with type inference) | +| **Execution** | Interpreted (CPython), JIT (PyPy) | Compiled (AOT) to machine code | +| **Performance** | 10-100x slower than C | Similar to C/C++ (within 10-20%) | +| **Concurrency** | Limited by GIL (threading), parallel via multiprocessing | Native goroutines and channels, no GIL | +| **Memory** | Reference counting + cycle detector | Tracing garbage collector | +| **Startup Time** | Fast (interpreter starts quickly) | Medium (compilation is fast but not instant) | +| **Deployment** | Requires Python runtime, dependencies | Single binary, no dependencies | +| **Package Management** | pip + virtual environments | go mod (built-in) | +| **Standard Library** | Extensive ("batteries included") | Comprehensive but smaller | +| **Learning Curve** | Gentle | Moderate (static typing takes adjustment) | +| **Best For** | Data science, ML, scripting, web backends, automation | Microservices, cloud tools, system programming, high-performance services | + +## Compiled vs Interpreted + +### Python (Interpreted/JIT) + +Python code is executed by an interpreter: + +1. **Source code** (`.py` files) is parsed into bytecode +2. **Bytecode** is executed by the Python virtual machine +3. **Dynamic typing** means types are checked at runtime +4. **GIL** limits parallel execution + +**Pros:** +- Fast development cycle (no compilation step) +- Interactive shell (REPL) +- Dynamic typing offers flexibility +- Easy to prototype + +**Cons:** +- Slower execution +- Runtime type errors +- GIL limits true parallelism +- Requires runtime environment + +### Go (Compiled) + +Go code is compiled before execution: + +1. **Source code** (`.go` files) is parsed and type-checked +2. **Code is compiled** to machine code for the target platform +3. **Binary** is a standalone executable +4. **Static typing** catches errors at compile time + +**Pros:** +- Fast execution (similar to C/C++) +- No runtime dependencies +- Compile-time error checking +- True parallel execution +- Single binary deployment + +**Cons:** +- Compilation step (though very fast in Go) +- Less flexibility than dynamic typing +- Slower prototyping cycle + + +```python !! py +# Python - Run directly +$ python script.py +# No compilation step, but slower execution +``` + +```go !! go +// Go - Compile then run +$ go build -o myapp main.go +$ ./myapp +// Fast compilation, then fast execution +``` + + +## Go's Use Cases and Advantages + +### Primary Use Cases + +#### 1. Cloud-Native Infrastructure +Go is the language of the cloud: +- **Kubernetes**: Container orchestration +- **Docker**: Container platform +- **Terraform**: Infrastructure as Code +- **Prometheus**: Monitoring system + +#### 2. Microservices +- **High performance**: Fast execution and low memory footprint +- **Easy deployment**: Single binary per service +- **Built-in concurrency**: Handle many requests simultaneously +- **Fast compilation**: Quick iteration during development + + +```python !! py +# Python Flask microservice +# Typical: 500-2000 requests/second +from flask import Flask +app = Flask(__name__) + +@app.route('/') +def hello(): + return "Hello World" +``` + +```go !! go +// Go microservice +// Typical: 10,000-50,000+ requests/second +package main + +import ( + "fmt" + "net/http" +) + +func handler(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "Hello World") +} + +func main() { + http.HandleFunc("/", handler) + http.ListenAndServe(":8080", nil) +} +``` + + +#### 3. Networking Tools +- High-performance network servers +- Proxies and load balancers +- API gateways +- Real-time communication systems + +#### 4. DevOps Tools +- CI/CD tools (GitHub Actions runner, Drone) +- Monitoring agents +- Log aggregators +- Configuration management + +#### 5. Command-Line Tools +- Fast execution +- Single binary distribution +- Cross-platform support +- Easy to install and use + +### Key Advantages + +#### 1. Performance +- **Execution speed**: 10-100x faster than Python for CPU-bound tasks +- **Memory efficiency**: Lower memory footprint +- **Startup time**: Fast (though not instant) +- **Scalability**: Handle more concurrent operations + + +```python !! py +# Python - ~50ms for 10,000 records +import json + +data = [{"id": i, "name": f"User{i}"} for i in range(10000)] +json_str = json.dumps(data) +parsed = json.loads(json_str) +``` + +```go !! go +// Go - ~5ms for 10,000 records (10x faster) +package main + +import ( + "encoding/json" + "fmt" +) + +type User struct { + ID int `json:"id"` + Name string `json:"name"` +} + +func main() { + var data []User + for i := 0; i < 10000; i++ { + data = append(data, User{ID: i, Name: fmt.Sprintf("User%d", i)}) + } + + b, _ := json.Marshal(data) + var parsed []User + json.Unmarshal(b, &parsed) +} +``` + + +#### 2. Concurrency +- **Goroutines**: Start thousands of concurrent operations +- **Channels**: Safe communication +- **Select**: Wait for multiple operations +- **No GIL**: True parallel execution + +#### 3. Simplicity +- **Easy to learn**: Small language surface +- **Fast to write**: Less boilerplate than Java +- **Easy to read**: Clear, explicit code +- **Quick onboarding**: New developers become productive quickly + +#### 4. Tooling +- **Built-in tools**: `go fmt`, `go test`, `go vet`, `go doc` +- **Fast compilation**: Compile large projects in seconds +- **Cross-compilation**: Build for any platform from any machine +- **Standard library**: Comprehensive and well-designed + +#### 5. Deployment +- **Single binary**: No dependency hell +- **Cross-platform**: Run anywhere +- **Small binaries**: Typically 5-20 MB +- **Fast startup**: Suitable for serverless functions + + +```bash +# Python Deployment +$ pip install -r requirements.txt +$ python app.py +# Requires: Python runtime, all dependencies, correct versions +``` + +```bash +# Go Deployment +$ scp myapp server:/usr/local/bin/ +$ ssh server ./myapp +# Just: One binary file +``` + + +## Development Environment Setup + +### Prerequisites + +No special prerequisites - Go works on: +- **Linux**: Any modern distribution +- **macOS**: 10.15 Catalina or later +- **Windows**: Windows 10 or later +- **Architecture**: x86-64, ARM64, and more + +### Installation Methods + +#### Method 1: Official Installer (Recommended for Beginners) + +**macOS:** +```bash +# Download from https://golang.org/dl/ +# Or use Homebrew +brew install go +``` + +**Linux (Ubuntu/Debian):** +```bash +# Download and install +wget https://go.dev/dl/go1.21.5.linux-amd64.tar.gz +sudo rm -rf /usr/local/go +sudo tar -C /usr/local -xzf go1.21.5.linux-amd64.tar.gz + +# Add to PATH (add to ~/.bashrc or ~/.zshrc) +export PATH=$PATH:/usr/local/go/bin +export GOPATH=$HOME/go +``` + +**Windows:** +1. Download the MSI installer from https://golang.org/dl/ +2. Run the installer +3. Restart your terminal + +#### Method 2: Version Managers (Recommended for Advanced Users) + +**Using g (Go version manager):** +```bash +# Install g +curl -sSL https://raw.githubusercontent.com/voidint/g/master/install.sh | bash + +# Install and use specific Go version +g install 1.21.5 +g use 1.21.5 +``` + +### Verify Installation + +```bash +$ go version +go version go1.21.5 darwin/amd64 + +$ go env +GO111MODULE="on" +GOARCH="amd64" +GOBIN="" +GOCACHE="/Users/you/Library/Caches/go-build" +GOHOSTARCH="amd64" +GOHOSTOS="darwin" +GOOS="darwin" +... +``` + +### Understanding Go Environment + +#### GOPATH vs Go Modules + +**GOPATH (Old way, still relevant):** +- `GOPATH` is your workspace directory +- Contains three subdirectories: + - `src/`: Source code + - `pkg/`: Compiled package files + - `bin/`: Executable binaries + +**Go Modules (New way, recommended):** +- Module-aware mode (default since Go 1.16) +- `go.mod` file defines your module and dependencies +- No need for GOPATH +- Dependencies stored in module cache + + +```bash +# Python - Virtual environments +myproject/ +├── venv/ # Virtual environment +├── requirements.txt # Dependencies +└── src/ # Source code + └── main.py +``` + +```bash +# Go - Go modules +myproject/ +├── go.mod # Module definition +├── go.sum # Dependency checksums +└── main.go # Source code +``` + + +### Recommended IDE Setup + +#### 1. Visual Studio Code (Most Popular) + +**Install extensions:** +```bash +# Install Go extension +code --install-extension golang.go +``` + +**Features:** +- IntelliSense (autocomplete) +- Code navigation +- Refactoring tools +- Integrated debugging +- Test coverage visualization + +#### 2. GoLand (JetBrains) + +**Features:** +- Full-featured IDE +- Advanced refactoring +- Database tools +- Built-in debugger +- Version control integration + +**Price:** Paid, but free for students + +#### 3. Vim/Neovim + +**Install vim-go:** +```vim +" In .vimrc +Plug 'fatih/vim-go' +``` + +### Essential Go Tools + +These tools come with Go: + +```bash +# Format code automatically +go fmt ./... + +# Run tests +go test ./... + +# Check for issues +go vet ./... + +# List dependencies +go list -m all + +# Update dependencies +go get -u ./... + +# Tidy dependencies +go mod tidy + +# Build executable +go build -o myapp + +# Run directly +go run main.go + +# Download dependencies +go mod download + +# Verify dependencies +go mod verify +``` + + +```python !! py +# Python equivalent commands +# Format +black . + +# Run tests +pytest + +# Type check +mypy . + +# Install dependencies +pip install -r requirements.txt + +# Run application +python main.py +``` + +```go !! go +// Go equivalent commands +// Format +go fmt ./... + +// Run tests +go test ./... + +// Vet (check for issues) +go vet ./... + +// Download dependencies +go mod download + +// Run application +go run main.go +``` + + +## Your First Go Program + +Let's write a simple Go program to get started: + + +```python !! py +# Python - Hello World +def greet(name): + return f"Hello, {name}!" + +def main(): + names = ["Alice", "Bob", "Charlie"] + for name in names: + print(greet(name)) + +if __name__ == "__main__": + main() +``` + +```go !! go +// Go - Hello World +package main + +import "fmt" + +func greet(name string) string { + return fmt.Sprintf("Hello, %s!", name) +} + +func main() { + names := []string{"Alice", "Bob", "Charlie"} + for _, name := range names { + fmt.Println(greet(name)) + } +} +``` + + +### Anatomy of a Go Program + +```go +package main // 1. Package declaration (entry point) + +import "fmt" // 2. Import packages + +func greet(name string) string { // 3. Function definition + return fmt.Sprintf("Hello, %s!", name) // 4. Return statement +} + +func main() { // 5. Main function (entry point) + fmt.Println("Hello, World!") // 6. Function call +} +``` + +**Key differences from Python:** +1. **Semicolons**: Not required (inserted automatically) +2. **Types**: Explicit type annotations (`name string`) +3. **Package declaration**: Required at top +4. **Imports**: Must be in quotes +5. **Exported names**: Capitalized (e.g., `fmt.Printf`) +6. **Main function**: Must be in `package main`, named `main`, no parameters + +### Running Your First Program + +```bash +# Create a file +cat > main.go << 'EOF' +package main + +import "fmt" + +func main() { + fmt.Println("Hello, World!") +} +EOF + +# Run directly (compiles and runs in one step) +go run main.go +# Output: Hello, World! + +# Compile to binary +go build -o hello main.go + +# Run the binary +./hello +# Output: Hello, World! + +# See the generated binary +ls -lh hello +# Typically 1-2 MB for a simple program +``` + +## Setting Up a Go Project + +### Initialize a Go Module + +```bash +# Create project directory +mkdir myproject +cd myproject + +# Initialize Go module +go mod init github.com/username/myproject + +# This creates go.mod file: +cat go.mod +``` + +**go.mod contents:** +```go +module github.com/username/myproject + +go 1.21 +``` + +### Project Structure + + +```bash +# Python project +myproject/ +├── venv/ # Virtual environment +├── requirements.txt # Dependencies +├── src/ +│ ├── __init__.py +│ ├── main.py +│ └── utils.py +└── tests/ + ├── __init__.py + └── test_utils.py +``` + +```bash +# Go project +myproject/ +├── go.mod # Module definition +├── go.sum # Dependency checksums +├── main.go # Entry point +├── utils.go # Utility functions +└── utils_test.go # Tests (file_test.go convention) +``` + + +### Example Project + +Let's create a simple project: + +```bash +# Initialize +go mod init github.com/username/myproject + +# Create main.go +cat > main.go << 'EOF' +package main + +import ( + "fmt" + "strings" +) + +func greet(name string) string { + return fmt.Sprintf("Hello, %s!", strings.Title(name)) +} + +func main() { + names := []string{"alice", "bob", "charlie"} + for _, name := range names { + fmt.Println(greet(name)) + } +} +EOF + +# Run it +go run main.go +# Hello, Alice! +# Hello, Bob! +# Hello, Charlie! + +# Format the code +go fmt main.go + +# Build it +go build -o myproject + +# Run the binary +./myproject +``` + +## Performance Comparison: First Hand Experience + +Let's compare Python and Go performance with a practical example: + + +```python !! py +# Python - Count words in a large file +import time +from collections import Counter + +def count_words(filename): + with open(filename, 'r') as f: + words = f.read().lower().split() + return Counter(words) + +start = time.time() +result = count_words('large_file.txt') +print(f"Time: {time.time() - start:.2f}s") +``` + +```go !! go +// Go - Count words in a large file +package main + +import ( + "bufio" + "fmt" + "os" + "strings" + "time" +) + +func countWords(filename string) map[string]int { + file, _ := os.Open(filename) + defer file.Close() + + scanner := bufio.NewScanner(file) + counts := make(map[string]int) + + for scanner.Scan() { + words := strings.Fields(strings.ToLower(scanner.Text())) + for _, word := range words { + counts[word]++ + } + } + + return counts +} + +func main() { + start := time.Now() + counts := countWords("large_file.txt") + elapsed := time.Since(start) + + fmt.Printf("Time: %.2fs\n", elapsed.Seconds()) +} +``` + + +**Typical results:** +- **Python**: 2-5 seconds for 100MB file +- **Go**: 0.3-0.8 seconds for 100MB file (3-10x faster) + +## Common Questions from Python Developers + +### Q1: Is Go hard to learn? + +**A:** No, Go is easier to learn than most statically typed languages: +- Simple syntax with only 25 keywords +- No complex class hierarchies +- Minimal boilerplate +- Good error messages + +**Challenge for Python developers:** Static typing takes getting used to, but type inference helps. + +### Q2: Will Go replace Python? + +**A:** No, they serve different purposes: +- **Python**: Best for data science, ML, scripting, quick prototyping +- **Go**: Best for system programming, microservices, cloud infrastructure + +They're complementary, not competing. + +### Q3: Can I use Go with Python? + +**A:** Yes! Common patterns: +- Use Go for performance-critical services +- Use Python for data processing/ML +- Communicate via HTTP, gRPC, or message queues + + +```python !! py +# Python - ML Model Service +from flask import Flask, request +import my_ml_model + +app = Flask(__name__) + +@app.route('/predict', methods=['POST']) +def predict(): + data = request.json + result = my_ml_model.predict(data) + return {'prediction': result} +``` + +```go !! go +// Go - API Gateway +package main + +import "net/http" + +func proxyHandler(w http.ResponseWriter, r *http.Request) { + // Proxy to Python ML service + // Handle auth, rate limiting, caching +} + +func main() { + http.ListenAndServe(":8080", nil) +} +``` + + +### Q4: How do I handle dependencies in Go vs pip? + +**A:** Go uses Go Modules: + +```bash +# Python +pip install requests + +# Go +go get github.com/gin-gonic/gin +``` + +**Key differences:** +- Go: Vendor directory or module cache (no virtual environments needed) +- Python: Requires virtual environments for isolation + +### Q5: What about testing? + +**A:** Go has built-in testing: + + +```python !! py +# Python - pytest +def test_add(): + assert add(2, 3) == 5 + +# Run: pytest test_file.py +``` + +```go !! go +// Go - built-in testing +func TestAdd(t *testing.T) { + result := Add(2, 3) + if result != 5 { + t.Errorf("Add(2, 3) = %d; want 5", result) + } +} + +// Run: go test +``` + + +## Summary + +In this module, you learned: + +1. **Go's history and design philosophy**: Simplicity, concurrency, performance, safety +2. **Key differences from Python**: Static typing, compilation, no GIL, explicit error handling +3. **Go's use cases**: Cloud-native, microservices, DevOps tools, CLIs +4. **Development environment**: Installation, IDE setup, Go modules +5. **Your first Go program**: Structure, compilation, execution +6. **Performance characteristics**: 10-100x faster than Python for many tasks + +### Next Steps + +You're ready to dive into Go syntax! In the next module, you'll learn: +- Basic syntax differences from Python +- Variables, types, and declarations +- Control flow and functions +- How to translate Python idioms to Go + +## Exercises + +1. **Install Go** on your machine if you haven't already +2. **Create a "Hello, World!"** program in Go +3. **Initialize a Go module** for a new project +4. **Write a simple program** that: + - Creates a slice of strings + - Iterates over them + - Prints them in different formats +5. **Compare performance**: Implement the same simple algorithm in both Python and Go, then compare execution time using `time` command + +## Additional Resources + +- **Official Go website**: https://go.dev/ +- **Go Tour**: https://go.dev/tour/ +- **Effective Go**: https://go.dev/doc/effective_go +- **Go by Example**: https://gobyexample.com/ +- **Go Playground**: https://go.dev/play/ (run Go in browser) diff --git a/content/docs/py2go/module-00-go-introduction.zh-cn.mdx b/content/docs/py2go/module-00-go-introduction.zh-cn.mdx new file mode 100644 index 0000000..8664579 --- /dev/null +++ b/content/docs/py2go/module-00-go-introduction.zh-cn.mdx @@ -0,0 +1,959 @@ +--- +title: "模块 0:Go 语言介绍" +description: "理解 Go 的设计理念、历史以及设置开发环境" +--- + +## Go 语言历史和设计哲学 + +Go(也称为 Golang)是由 Google 的 Robert Griesemer、Rob Pike 和 Ken Thompson 设计的一门静态类型、编译型编程语言。它创建于 2007 年,并于 2009 年正式发布。Go 的设计旨在解决现代软件开发中的挑战,特别是在大规模分布式系统和云计算环境中。 + +### 为什么创建 Go + +Go 的诞生源于对 Google 现有语言的挫败感: + +- **C++** 太复杂,编译速度慢 +- **Java** 语法冗长,内存占用大 +- **Python** 易于编写但执行缓慢,由于 GIL 导致并发支持差 + +Go 的创造者想要一门语言: +- 像 Python 启动一样快地编译 +- 像 C 或 C++ 一样快地运行 +- 提供出色的并发支持 +- 简洁、清晰的语法 +- 使构建可靠软件变得容易 + +### Go 的设计哲学 + +Go 的设计由这些核心原则指导: + +#### 1. 简洁性 +- **最少关键字**:语言只有 25 个关键字 +- **清晰语法**:易于读写 +- **正交特性**:特性之间一致地协作 +- **无隐藏复杂性**:所见即所得 + + +```python !! py +# Python - 多种变量声明方式 +x = 5 +y: int = 10 +z = list([1, 2, 3]) +``` + +```go !! go +// Go - 一致的方式 +var x int = 5 +y := 10 // 短变量声明(最常用) +``` + + +#### 2. 并发 +- **Goroutines**:轻量级线程(数千个可以同时运行) +- **Channels**:goroutine 之间的安全通信 +- **Select**:协调多个操作 +- **无 GIL**:真正的并行执行 + + +```python !! py +# Python - 受 GIL 限制 +import threading +import time + +def worker(): + time.sleep(1) + print("Done") + +# 由于 GIL,这些不能并行运行 +threads = [threading.Thread(target=worker) for _ in range(5)] +for t in threads: + t.start() +for t in threads: + t.join() +``` + +```go !! go +// Go - 真正的并行 +package main + +import ( + "fmt" + "time" +) + +func worker() { + time.Sleep(time.Second) + fmt.Println("Done") +} + +func main() { + // 这些并行运行 + for i := 0; i < 5; i++ { + go worker() + } + time.Sleep(2 * time.Second) +} +``` + + +#### 3. 性能 +- **编译为机器码**:无解释器开销 +- **高效垃圾回收器**:低暂停时间 +- **静态类型**:编译时优化 +- **零成本抽象**:高级构造没有运行时开销 + +#### 4. 安全性 +- **强类型**:编译时捕获错误 +- **内存安全**:无指针运算(除非使用 unsafe 包) +- **垃圾回收**:自动内存管理 +- **显式错误处理**:错误是值,不是异常 + + +```python !! py +# Python - 异常 +try: + result = divide(10, 0) +except ZeroDivisionError as e: + print(f"Error: {e}") +``` + +```go !! go +// Go - 显式错误检查 +result, err := divide(10, 0) +if err != nil { + fmt.Printf("Error: %v\n", err) + return +} +``` + + +## 与 Python 的比较 + +| 特性 | Python | Go | +|------|--------|-----| +| **范式** | 多范式(OO、函数式、过程式) | 多范式(过程式、并发、带 OO 特性) | +| **类型** | 动态(鸭子类型) | 静态(带类型推断) | +| **执行** | 解释执行(CPython),JIT(PyPy) | 编译(AOT)为机器码 | +| **性能** | 比 C 慢 10-100 倍 | 类似 C/C++(10-20% 以内) | +| **并发** | 受 GIL 限制(线程),通过多进程并行 | 原生 goroutines 和 channels,无 GIL | +| **内存** | 引用计数 + 循环检测器 | 追踪式垃圾回收器 | +| **启动时间** | 快(解释器启动快) | 中等(编译快但不即时) | +| **部署** | 需要 Python 运行时、依赖 | 单个二进制,无依赖 | +| **包管理** | pip + 虚拟环境 | go mod(内置) | +| **标准库** | 丰富("自带电池") | 全面但较小 | +| **学习曲线** | 平缓 | 中等(静态类型需要适应) | +| **最适合** | 数据科学、ML、脚本、Web 后端、自动化 | 微服务、云工具、系统编程、高性能服务 | + +## 编译型 vs 解释型 + +### Python(解释型/JIT) + +Python 代码由解释器执行: + +1. **源代码**(`.py` 文件)被解析为字节码 +2. **字节码**由 Python 虚拟机执行 +3. **动态类型**意味着类型在运行时检查 +4. **GIL** 限制并行执行 + +**优点:** +- 快速开发周期(无编译步骤) +- 交互式 shell(REPL) +- 动态类型提供灵活性 +- 易于原型设计 + +**缺点:** +- 执行较慢 +- 运行时类型错误 +- GIL 限制真正的并行 +- 需要运行时环境 + +### Go(编译型) + +Go 代码在执行前编译: + +1. **源代码**(`.go` 文件)被解析和类型检查 +2. **代码被编译**为目标平台的机器码 +3. **二进制文件**是独立的可执行文件 +4. **静态类型**在编译时捕获错误 + +**优点:** +- 快速执行(类似 C/C++) +- 无运行时依赖 +- 编译时错误检查 +- 真正的并行执行 +- 单个二进制部署 + +**缺点:** +- 编译步骤(虽然 Go 很快) +- 比动态类型灵活性差 +- 原型设计周期较慢 + + +```python !! py +# Python - 直接运行 +$ python script.py +# 无编译步骤,但执行较慢 +``` + +```go !! go +// Go - 编译然后运行 +$ go build -o myapp main.go +$ ./myapp +// 快速编译,然后快速执行 +``` + + +## Go 的使用场景和优势 + +### 主要使用场景 + +#### 1. 云原生基础设施 +Go 是云的语言: +- **Kubernetes**:容器编排 +- **Docker**:容器平台 +- **Terraform**:基础设施即代码 +- **Prometheus**:监控系统 + +#### 2. 微服务 +- **高性能**:快速执行和低内存占用 +- **易于部署**:每个服务单个二进制 +- **内置并发**:同时处理多个请求 +- **快速编译**:开发期间快速迭代 + + +```python !! py +# Python Flask 微服务 +# 典型:500-2000 请求/秒 +from flask import Flask +app = Flask(__name__) + +@app.route('/') +def hello(): + return "Hello World" +``` + +```go !! go +// Go 微服务 +// 典型:10,000-50,000+ 请求/秒 +package main + +import ( + "fmt" + "net/http" +) + +func handler(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "Hello World") +} + +func main() { + http.HandleFunc("/", handler) + http.ListenAndServe(":8080", nil) +} +``` + + +#### 3. 网络工具 +- 高性能网络服务器 +- 代理和负载均衡器 +- API 网关 +- 实时通信系统 + +#### 4. DevOps 工具 +- CI/CD 工具(GitHub Actions runner、Drone) +- 监控代理 +- 日志聚合器 +- 配置管理 + +#### 5. 命令行工具 +- 快速执行 +- 单个二进制分发 +- 跨平台支持 +- 易于安装和使用 + +### 核心优势 + +#### 1. 性能 +- **执行速度**:对于 CPU 密集型任务比 Python 快 10-100 倍 +- **内存效率**:更低的内存占用 +- **启动时间**:快(虽然不是瞬间) +- **可扩展性**:处理更多并发操作 + + +```python !! py +# Python - 处理 10,000 条记录约 50ms +import json + +data = [{"id": i, "name": f"User{i}"} for i in range(10000)] +json_str = json.dumps(data) +parsed = json.loads(json_str) +``` + +```go !! go +// Go - 处理 10,000 条记录约 5ms(快 10 倍) +package main + +import ( + "encoding/json" + "fmt" +) + +type User struct { + ID int `json:"id"` + Name string `json:"name"` +} + +func main() { + var data []User + for i := 0; i < 10000; i++ { + data = append(data, User{ID: i, Name: fmt.Sprintf("User%d", i)}) + } + + b, _ := json.Marshal(data) + var parsed []User + json.Unmarshal(b, &parsed) +} +``` + + +#### 2. 并发 +- **Goroutines**:启动数千个并发操作 +- **Channels**:安全通信 +- **Select**:等待多个操作 +- **无 GIL**:真正的并行执行 + +#### 3. 简洁性 +- **易于学习**:语言表面小 +- **快速编写**:比 Java 更少的样板代码 +- **易于阅读**:清晰、显式的代码 +- **快速上手**:新开发者快速变得高效 + +#### 4. 工具 +- **内置工具**:`go fmt`、`go test`、`go vet`、`go doc` +- **快速编译**:几秒内编译大型项目 +- **交叉编译**:从任何机器为任何平台构建 +- **标准库**:全面且设计良好 + +#### 5. 部署 +- **单个二进制**:无依赖地狱 +- **跨平台**:随处运行 +- **小二进制**:通常 5-20 MB +- **快速启动**:适合无服务器函数 + + +```bash +# Python 部署 +$ pip install -r requirements.txt +$ python app.py +# 需要:Python 运行时、所有依赖、正确版本 +``` + +```bash +# Go 部署 +$ scp myapp server:/usr/local/bin/ +$ ssh server ./myapp +# 只需:一个二进制文件 +``` + + +## 开发环境设置 + +### 前置要求 + +无特殊前置要求 - Go 支持以下环境: +- **Linux**:任何现代发行版 +- **macOS**:10.15 Catalina 或更高版本 +- **Windows**:Windows 10 或更高版本 +- **架构**:x86-64、ARM64 等 + +### 安装方法 + +#### 方法 1:官方安装程序(推荐给初学者) + +**macOS:** +```bash +# 从 https://golang.org/dl/ 下载 +# 或使用 Homebrew +brew install go +``` + +**Linux (Ubuntu/Debian):** +```bash +# 下载并安装 +wget https://go.dev/dl/go1.21.5.linux-amd64.tar.gz +sudo rm -rf /usr/local/go +sudo tar -C /usr/local -xzf go1.21.5.linux-amd64.tar.gz + +# 添加到 PATH(添加到 ~/.bashrc 或 ~/.zshrc) +export PATH=$PATH:/usr/local/go/bin +export GOPATH=$HOME/go +``` + +**Windows:** +1. 从 https://golang.org/dl/ 下载 MSI 安装程序 +2. 运行安装程序 +3. 重启终端 + +#### 方法 2:版本管理器(推荐给高级用户) + +**使用 g(Go 版本管理器):** +```bash +# 安装 g +curl -sSL https://raw.githubusercontent.com/voidint/g/master/install.sh | bash + +# 安装并使用特定 Go 版本 +g install 1.21.5 +g use 1.21.5 +``` + +### 验证安装 + +```bash +$ go version +go version go1.21.5 darwin/amd64 + +$ go env +GO111MODULE="on" +GOARCH="amd64" +GOBIN="" +GOCACHE="/Users/you/Library/Caches/go-build" +GOHOSTARCH="amd64" +GOHOSTOS="darwin" +GOOS="darwin" +... +``` + +### 理解 Go 环境 + +#### GOPATH vs Go Modules + +**GOPATH(旧方式,仍然相关):** +- `GOPATH` 是你的工作区目录 +- 包含三个子目录: + - `src/`:源代码 + - `pkg/`:编译的包文件 + - `bin/`:可执行二进制文件 + +**Go Modules(新方式,推荐):** +- 模块感知模式(Go 1.16 起默认) +- `go.mod` 文件定义你的模块和依赖 +- 无需 GOPATH +- 依赖存储在模块缓存中 + + +```bash +# Python - 虚拟环境 +myproject/ +├── venv/ # 虚拟环境 +├── requirements.txt # 依赖 +└── src/ # 源代码 + └── main.py +``` + +```bash +# Go - Go 模块 +myproject/ +├── go.mod # 模块定义 +├── go.sum # 依赖校验和 +└── main.go # 源代码 +``` + + +### 推荐的 IDE 设置 + +#### 1. Visual Studio Code(最受欢迎) + +**安装扩展:** +```bash +# 安装 Go 扩展 +code --install-extension golang.go +``` + +**特性:** +- IntelliSense(自动完成) +- 代码导航 +- 重构工具 +- 集成调试 +- 测试覆盖率可视化 + +#### 2. GoLand(JetBrains) + +**特性:** +- 全功能 IDE +- 高级重构 +- 数据库工具 +- 内置调试器 +- 版本控制集成 + +**价格:**付费,但学生免费 + +#### 3. Vim/Neovim + +**安装 vim-go:** +```vim +# 在 .vimrc 中 +Plug 'fatih/vim-go' +``` + +### 必备的 Go 工具 + +这些工具随 Go 一起提供: + +```bash +# 自动格式化代码 +go fmt ./... + +# 运行测试 +go test ./... + +# 检查问题 +go vet ./... + +# 列出依赖 +go list -m all + +# 更新依赖 +go get -u ./... + +# 整理依赖 +go mod tidy + +# 构建可执行文件 +go build -o myapp + +# 直接运行 +go run main.go + +# 下载依赖 +go mod download + +# 验证依赖 +go mod verify +``` + + +```python !! py +# Python 等效命令 +# 格式化 +black . + +# 运行测试 +pytest + +# 类型检查 +mypy . + +# 安装依赖 +pip install -r requirements.txt + +# 运行应用 +python main.py +``` + +```go !! go +// Go 等效命令 +// 格式化 +go fmt ./... + +// 运行测试 +go test ./... + +// Vet(检查问题) +go vet ./... + +// 下载依赖 +go mod download + +// 运行应用 +go run main.go +``` + + +## 你的第一个 Go 程序 + +让我们编写一个简单的 Go 程序来开始: + + +```python !! py +# Python - Hello World +def greet(name): + return f"Hello, {name}!" + +def main(): + names = ["Alice", "Bob", "Charlie"] + for name in names: + print(greet(name)) + +if __name__ == "__main__": + main() +``` + +```go !! go +// Go - Hello World +package main + +import "fmt" + +func greet(name string) string { + return fmt.Sprintf("Hello, %s!", name) +} + +func main() { + names := []string{"Alice", "Bob", "Charlie"} + for _, name := range names { + fmt.Println(greet(name)) + } +} +``` + + +### Go 程序剖析 + +```go +package main // 1. 包声明(入口点) + +import "fmt" // 2. 导入包 + +func greet(name string) string { // 3. 函数定义 + return fmt.Sprintf("Hello, %s!", name) // 4. 返回语句 +} + +func main() { // 5. Main 函数(入口点) + fmt.Println("Hello, World!") // 6. 函数调用 +} +``` + +**与 Python 的主要区别:** +1. **分号**:不需要(自动插入) +2. **类型**:显式类型注解(`name string`) +3. **包声明**:顶部必需 +4. **导入**:必须在引号中 +5. **导出名称**:大写(例如 `fmt.Printf`) +6. **Main 函数**:必须在 `package main` 中,名为 `main`,无参数 + +### 运行你的第一个程序 + +```bash +# 创建文件 +cat > main.go << 'EOF' +package main + +import "fmt" + +func main() { + fmt.Println("Hello, World!") +} +EOF + +# 直接运行(一步编译和运行) +go run main.go +# 输出: Hello, World! + +# 编译为二进制 +go build -o hello main.go + +# 运行二进制 +./hello +# 输出: Hello, World! + +# 查看生成的二进制 +ls -lh hello +# 简单程序通常 1-2 MB +``` + +## 设置 Go 项目 + +### 初始化 Go 模块 + +```bash +# 创建项目目录 +mkdir myproject +cd myproject + +# 初始化 Go 模块 +go mod init github.com/username/myproject + +# 这会创建 go.mod 文件: +cat go.mod +``` + +**go.mod 内容:** +```go +module github.com/username/myproject + +go 1.21 +``` + +### 项目结构 + + +```bash +# Python 项目 +myproject/ +├── venv/ # 虚拟环境 +├── requirements.txt # 依赖 +├── src/ +│ ├── __init__.py +│ ├── main.py +│ └── utils.py +└── tests/ + ├── __init__.py + └── test_utils.py +``` + +```bash +# Go 项目 +myproject/ +├── go.mod # 模块定义 +├── go.sum # 依赖校验和 +├── main.go # 入口点 +├── utils.go # 工具函数 +└── utils_test.go # 测试(file_test.go 约定) +``` + + +### 示例项目 + +让我们创建一个简单的项目: + +```bash +# 初始化 +go mod init github.com/username/myproject + +# 创建 main.go +cat > main.go << 'EOF' +package main + +import ( + "fmt" + "strings" +) + +func greet(name string) string { + return fmt.Sprintf("Hello, %s!", strings.Title(name)) +} + +func main() { + names := []string{"alice", "bob", "charlie"} + for _, name := range names { + fmt.Println(greet(name)) + } +} +EOF + +# 运行它 +go run main.go +# Hello, Alice! +# Hello, Bob! +# Hello, Charlie! + +# 格式化代码 +go fmt main.go + +# 构建它 +go build -o myproject + +# 运行二进制 +./myproject +``` + +## 性能对比:亲身体验 + +让我们用实际例子比较 Python 和 Go 性能: + + +```python !! py +# Python - 统计大文件中的单词 +import time +from collections import Counter + +def count_words(filename): + with open(filename, 'r') as f: + words = f.read().lower().split() + return Counter(words) + +start = time.time() +result = count_words('large_file.txt') +print(f"Time: {time.time() - start:.2f}s") +``` + +```go !! go +// Go - 统计大文件中的单词 +package main + +import ( + "bufio" + "fmt" + "os" + "strings" + "time" +) + +func countWords(filename string) map[string]int { + file, _ := os.Open(filename) + defer file.Close() + + scanner := bufio.NewScanner(file) + counts := make(map[string]int) + + for scanner.Scan() { + words := strings.Fields(strings.ToLower(scanner.Text())) + for _, word := range words { + counts[word]++ + } + } + + return counts +} + +func main() { + start := time.Now() + counts := countWords("large_file.txt") + elapsed := time.Since(start) + + fmt.Printf("Time: %.2fs\n", elapsed.Seconds()) +} +``` + + +**典型结果:** +- **Python**:100MB 文件 2-5 秒 +- **Go**:100MB 文件 0.3-0.8 秒(快 3-10 倍) + +## Python 开发者常见问题 + +### Q1:Go 难学吗? + +**答:** 不,Go 比大多数静态类型语言更容易学: +- 简单语法,只有 25 个关键字 +- 无复杂的类层次结构 +- 最少的样板代码 +- 良好的错误消息 + +**Python 开发者的挑战:** 静态类型需要适应,但类型推断有帮助。 + +### Q2:Go 会取代 Python 吗? + +**答:** 不会,它们服务于不同目的: +- **Python**:最适合数据科学、ML、脚本、快速原型设计 +- **Go**:最适合系统编程、微服务、云基础设施 + +它们是互补的,不是竞争的。 + +### Q3:我可以将 Go 与 Python 一起使用吗? + +**答:** 可以!常见模式: +- 使用 Go 处理性能关键服务 +- 使用 Python 进行数据处理/ML +- 通过 HTTP、gRPC 或消息队列通信 + + +```python !! py +# Python - ML 模型服务 +from flask import Flask, request +import my_ml_model + +app = Flask(__name__) + +@app.route('/predict', methods=['POST']) +def predict(): + data = request.json + result = my_ml_model.predict(data) + return {'prediction': result} +``` + +```go !! go +// Go - API 网关 +package main + +import "net/http" + +func proxyHandler(w http.ResponseWriter, r *http.Request) { + // 代理到 Python ML 服务 + // 处理 auth、限流、缓存 +} + +func main() { + http.ListenAndServe(":8080", nil) +} +``` + + +### Q4:Go vs pip 如何处理依赖? + +**答:** Go 使用 Go Modules: + +```bash +# Python +pip install requests + +# Go +go get github.com/gin-gonic/gin +``` + +**主要区别:** +- Go:Vendor 目录或模块缓存(无需虚拟环境) +- Python:需要虚拟环境进行隔离 + +### Q5:测试呢? + +**答:** Go 有内置测试: + + +```python !! py +# Python - pytest +def test_add(): + assert add(2, 3) == 5 + +# 运行: pytest test_file.py +``` + +```go !! go +// Go - 内置测试 +func TestAdd(t *testing.T) { + result := Add(2, 3) + if result != 5 { + t.Errorf("Add(2, 3) = %d; want 5", result) + } +} + +// 运行: go test +``` + + +## 总结 + +在本模块中,你学到了: + +1. **Go 的历史和设计哲学**:简洁性、并发、性能、安全性 +2. **与 Python 的主要区别**:静态类型、编译、无 GIL、显式错误处理 +3. **Go 的使用场景**:云原生、微服务、DevOps 工具、CLI +4. **开发环境**:安装、IDE 设置、Go 模块 +5. **你的第一个 Go 程序**:结构、编译、执行 +6. **性能特征**:许多任务比 Python 快 10-100 倍 + +### 下一步 + +你准备好深入了解 Go 语法了!在下一模块中,你将学习: +- 与 Python 的基本语法差异 +- 变量、类型和声明 +- 控制流和函数 +- 如何将 Python 习语翻译为 Go + +## 练习 + +1. **安装 Go**(如果尚未安装) +2. **创建 "Hello, World!"** 程序 +3. **初始化 Go 模块**用于新项目 +4. **编写简单程序**,它: + - 创建字符串切片 + - 遍历它们 + - 以不同格式打印 +5. **比较性能**:用 Python 和 Go 实现相同的简单算法,然后使用 `time` 命令比较执行时间 + +## 其他资源 + +- **Go 官方网站**:https://go.dev/ +- **Go Tour**:https://go.dev/tour/welcome/1 +- **Effective Go**:https://go.dev/doc/effective_go +- **Go by Example**:https://gobyexample.com/ +- **Go Playground**:https://go.dev/play/(在浏览器中运行 Go) diff --git a/content/docs/py2go/module-00-go-introduction.zh-tw.mdx b/content/docs/py2go/module-00-go-introduction.zh-tw.mdx new file mode 100644 index 0000000..4d552f5 --- /dev/null +++ b/content/docs/py2go/module-00-go-introduction.zh-tw.mdx @@ -0,0 +1,959 @@ +--- +title: "模組 0:Go 語言介紹" +description: "理解 Go 的設計理念、歷史以及設定開發環境" +--- + +## Go 語言歷史和設計哲學 + +Go(也稱為 Golang)是由 Google 的 Robert Griesemer、Rob Pike 和 Ken Thompson 設計的一門靜態類型、編譯型程式語言。它創建於 2007 年,並於 2009 年正式發布。Go 的設計旨在解決現代軟體開發中的挑戰,特別是在大規模分散式系統和雲端運算環境中。 + +### 為什麼創建 Go + +Go 的誕生源於對 Google 現有語言的挫敗感: + +- **C++** 太複雜,編譯速度慢 +- **Java** 語法冗長,記憶體佔用大 +- **Python** 易於編寫但執行緩慢,由於 GIL 導致並發支援差 + +Go 的創造者想要一門語言: +- 像 Python 啟動一樣快地編譯 +- 像 C 或 C++ 一樣快地執行 +- 提供出色的並發支援 +- 簡潔、清晰的語法 +- 使建構可靠軟體變得容易 + +### Go 的設計哲學 + +Go 的設計由這些核心原則指導: + +#### 1. 簡潔性 +- **最少關鍵字**:語言只有 25 個關鍵字 +- **清晰語法**:易於讀寫 +- **正交特性**:特性之間一致地協作 +- **無隱藏複雜性**:所見即所得 + + +```python !! py +# Python - 多種變數宣告方式 +x = 5 +y: int = 10 +z = list([1, 2, 3]) +``` + +```go !! go +// Go - 一致的方式 +var x int = 5 +y := 10 // 短變數宣告(最常用) +``` + + +#### 2. 並發 +- **Goroutines**:輕量級執行緒(數千個可以同時執行) +- **Channels**:goroutine 之間的安全通訊 +- **Select**:協調多個操作 +- **無 GIL**:真正的平行執行 + + +```python !! py +# Python - 受 GIL 限制 +import threading +import time + +def worker(): + time.sleep(1) + print("Done") + +# 由於 GIL,這些不能平行執行 +threads = [threading.Thread(target=worker) for _ in range(5)] +for t in threads: + t.start() +for t in threads: + t.join() +``` + +```go !! go +// Go - 真正的平行 +package main + +import ( + "fmt" + "time" +) + +func worker() { + time.Sleep(time.Second) + fmt.Println("Done") +} + +func main() { + // 這些平行執行 + for i := 0; i < 5; i++ { + go worker() + } + time.Sleep(2 * time.Second) +} +``` + + +#### 3. 效能 +- **編譯為機器碼**:無直譯器開銷 +- **高效垃圾回收器**:低暫停時間 +- **靜態類型**:編譯時最佳化 +- **零成本抽象**:高階構造沒有執行時開銷 + +#### 4. 安全性 +- **強類型**:編譯時擷取錯誤 +- **記憶體安全**:無指標運算(除非使用 unsafe 套件) +- **垃圾回收**:自動記憶體管理 +- **顯式錯誤處理**:錯誤是值,不是異常 + + +```python !! py +# Python - 例外 +try: + result = divide(10, 0) +except ZeroDivisionError as e: + print(f"Error: {e}") +``` + +```go !! go +// Go - 顯式錯誤檢查 +result, err := divide(10, 0) +if err != nil { + fmt.Printf("Error: %v\n", err) + return +} +``` + + +## 與 Python 的比較 + +| 特性 | Python | Go | +|------|--------|-----| +| **範式** | 多範式(OO、函數式、程序式) | 多範式(程序式、並發、帶 OO 特性) | +| **類型** | 動態(鴨子類型) | 靜態(帶類型推斷) | +| **執行** | 直譯執行(CPython),JIT(PyPy) | 編譯(AOT)為機器碼 | +| **效能** | 比 C 慢 10-100 倍 | 類似 C/C++(10-20% 以內) | +| **並發** | 受 GIL 限制(執行緒),透過多程序平行 | 原生 goroutines 和 channels,無 GIL | +| **記憶體** | 引用計數 + 循環偵測器 | 追蹤式垃圾回收器 | +| **啟動時間** | 快(直譯器啟動快) | 中等(編譯快但不即時) | +| **部署** | 需要 Python 執行時、依賴 | 單個二進位,無依賴 | +| **套件管理** | pip + 虛擬環境 | go mod(內建) | +| **標準庫** | 豐富("自帶電池") | 全面但較小 | +| **學習曲線** | 平緩 | 中等(靜態類型需要適應) | +| **最適合** | 資料科學、ML、腳本、Web 後端、自動化 | 微服務、雲端工具、系統程式設計、高效能服務 | + +## 編譯型 vs 直譯型 + +### Python(直譯型/JIT) + +Python 程式碼由直譯器執行: + +1. **原始碼**(`.py` 檔案)被解析為位元組碼 +2. **位元組碼**由 Python 虛擬機執行 +3. **動態類型**意味著類型在執行時檢查 +4. **GIL** 限制平行執行 + +**優點:** +- 快速開發週期(無編譯步驟) +- 互動式 shell(REPL) +- 動態類型提供彈性 +- 易於原型設計 + +**缺點:** +- 執行較慢 +- 執行時類型錯誤 +- GIL 限制真正的平行 +- 需要執行時環境 + +### Go(編譯型) + +Go 程式碼在執行前編譯: + +1. **原始碼**(`.go` 檔案)被解析和類型檢查 +2. **程式碼被編譯**為目標平台的機器碼 +3. **二進位檔案**是獨立的可執行檔 +4. **靜態類型**在編譯時擷取錯誤 + +**優點:** +- 快速執行(類似 C/C++) +- 無執行時依賴 +- 編譯時錯誤檢查 +- 真正的平行執行 +- 單個二進位部署 + +**缺點:** +- 編譯步驟(雖然 Go 很快) +- 比動態類型彈性差 +- 原型設計週期較慢 + + +```python !! py +# Python - 直接執行 +$ python script.py +# 無編譯步驟,但執行較慢 +``` + +```go !! go +// Go - 編譯然後執行 +$ go build -o myapp main.go +$ ./myapp +// 快速編譯,然後快速執行 +``` + + +## Go 的使用場景和優勢 + +### 主要使用場景 + +#### 1. 雲原生基礎設施 +Go 是雲的語言: +- **Kubernetes**:容器編排 +- **Docker**:容器平台 +- **Terraform**:基礎設施即程式碼 +- **Prometheus**:監控系統 + +#### 2. 微服務 +- **高效能**:快速執行和低記憶體佔用 +- **易於部署**:每個服務單個二進位 +- **內建並發**:同時處理多個請求 +- **快速編譯**:開發期間快速迭代 + + +```python !! py +# Python Flask 微服務 +# 典型:500-2000 請求/秒 +from flask import Flask +app = Flask(__name__) + +@app.route('/') +def hello(): + return "Hello World" +``` + +```go !! go +// Go 微服務 +// 典型:10,000-50,000+ 請求/秒 +package main + +import ( + "fmt" + "net/http" +) + +func handler(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "Hello World") +} + +func main() { + http.HandleFunc("/", handler) + http.ListenAndServe(":8080", nil) +} +``` + + +#### 3. 網路工具 +- 高效能網路伺服器 +- 代理和負載平衡器 +- API 閘道 +- 即時通訊系統 + +#### 4. DevOps 工具 +- CI/CD 工具(GitHub Actions runner、Drone) +- 監控代理 +- 日誌聚合器 +- 組態管理 + +#### 5. 命令列工具 +- 快速執行 +- 單個二進位分發 +- 跨平台支援 +- 易於安裝和使用 + +### 核心優勢 + +#### 1. 效能 +- **執行速度**:對於 CPU 密集型任務比 Python 快 10-100 倍 +- **記憶體效率**:更低的記憶體佔用 +- **啟動時間**:快(雖然不是瞬間) +- **可擴展性**:處理更多並發操作 + + +```python !! py +# Python - 處理 10,000 筆記錄約 50ms +import json + +data = [{"id": i, "name": f"User{i}"} for i in range(10000)] +json_str = json.dumps(data) +parsed = json.loads(json_str) +``` + +```go !! go +// Go - 處理 10,000 筆記錄約 5ms(快 10 倍) +package main + +import ( + "encoding/json" + "fmt" +) + +type User struct { + ID int `json:"id"` + Name string `json:"name"` +} + +func main() { + var data []User + for i := 0; i < 10000; i++ { + data = append(data, User{ID: i, Name: fmt.Sprintf("User%d", i)}) + } + + b, _ := json.Marshal(data) + var parsed []User + json.Unmarshal(b, &parsed) +} +``` + + +#### 2. 並發 +- **Goroutines**:啟動數千個並發操作 +- **Channels**:安全通訊 +- **Select**:等待多個操作 +- **無 GIL**:真正的平行執行 + +#### 3. 簡潔性 +- **易於學習**:語言表面小 +- **快速編寫**:比 Java 更少的樣板程式碼 +- **易於閱讀**:清晰、顯式的程式碼 +- **快速上手**:新開發者快速變得高效 + +#### 4. 工具 +- **內建工具**:`go fmt`、`go test`、`go vet`、`go doc` +- **快速編譯**:幾秒內編譯大型專案 +- **交叉編譯**:從任何機器為任何平台建構 +- **標準庫**:全面且設計良好 + +#### 5. 部署 +- **單個二進位**:無依賴地獄 +- **跨平台**:隨處執行 +- **小二進位**:通常 5-20 MB +- **快速啟動**:適合無伺服器函式 + + +```bash +# Python 部署 +$ pip install -r requirements.txt +$ python app.py +# 需要:Python 執行時、所有依賴、正確版本 +``` + +```bash +# Go 部署 +$ scp myapp server:/usr/local/bin/ +$ ssh server ./myapp +# 只需:一個二進位檔案 +``` + + +## 開發環境設定 + +### 前置要求 + +無特殊前置要求 - Go 支援以下環境: +- **Linux**:任何現代發行版 +- **macOS**:10.15 Catalina 或更高版本 +- **Windows**:Windows 10 或更高版本 +- **架構**:x86-64、ARM64 等 + +### 安裝方法 + +#### 方法 1:官方安裝程式(推薦給初學者) + +**macOS:** +```bash +# 從 https://golang.org/dl/ 下載 +# 或使用 Homebrew +brew install go +``` + +**Linux (Ubuntu/Debian):** +```bash +# 下載並安裝 +wget https://go.dev/dl/go1.21.5.linux-amd64.tar.gz +sudo rm -rf /usr/local/go +sudo tar -C /usr/local -xzf go1.21.5.linux-amd64.tar.gz + +# 新增到 PATH(新增到 ~/.bashrc 或 ~/.zshrc) +export PATH=$PATH:/usr/local/go/bin +export GOPATH=$HOME/go +``` + +**Windows:** +1. 從 https://golang.org/dl/ 下載 MSI 安裝程式 +2. 執行安裝程式 +3. 重新啟動終端 + +#### 方法 2:版本管理器(推薦給高級使用者) + +**使用 g(Go 版本管理器):** +```bash +# 安裝 g +curl -sSL https://raw.githubusercontent.com/voidint/g/master/install.sh | bash + +# 安裝並使用特定 Go 版本 +g install 1.21.5 +g use 1.21.5 +``` + +### 驗證安裝 + +```bash +$ go version +go version go1.21.5 darwin/amd64 + +$ go env +GO111MODULE="on" +GOARCH="amd64" +GOBIN="" +GOCACHE="/Users/you/Library/Caches/go-build" +GOHOSTARCH="amd64" +GOHOSTOS="darwin" +GOOS="darwin" +... +``` + +### 理解 Go 環境 + +#### GOPATH vs Go Modules + +**GOPATH(舊方式,仍然相關):** +- `GOPATH` 是你的工作區目錄 +- 包含三個子目錄: + - `src/`:原始碼 + - `pkg/`:編譯的套件檔案 + - `bin/`:可執行二進位檔案 + +**Go Modules(新方式,推薦):** +- 模組感知模式(Go 1.16 起預設) +- `go.mod` 檔案定義你的模組和依賴 +- 無需 GOPATH +- 依賴儲存在模組快取中 + + +```bash +# Python - 虛擬環境 +myproject/ +├── venv/ # 虛擬環境 +├── requirements.txt # 依賴 +└── src/ # 原始碼 + └── main.py +``` + +```bash +# Go - Go 模組 +myproject/ +├── go.mod # 模組定義 +├── go.sum # 依賴校驗和 +└── main.go # 原始碼 +``` + + +### 推薦的 IDE 設定 + +#### 1. Visual Studio Code(最受歡迎) + +**安裝擴充功能:** +```bash +# 安裝 Go 擴充功能 +code --install-extension golang.go +``` + +**特性:** +- IntelliSense(自動完成) +- 程式碼導航 +- 重構工具 +- 整合除錯 +- 測試覆蓋率視覺化 + +#### 2. GoLand(JetBrains) + +**特性:** +- 全功能 IDE +- 進階重構 +- 資料庫工具 +- 內建除錯器 +- 版本控制整合 + +**價格:**付費,但學生免費 + +#### 3. Vim/Neovim + +**安裝 vim-go:** +```vim +# 在 .vimrc 中 +Plug 'fatih/vim-go' +``` + +### 必備的 Go 工具 + +這些工具隨 Go 一起提供: + +```bash +# 自動格式化程式碼 +go fmt ./... + +# 執行測試 +go test ./... + +# 檢查問題 +go vet ./... + +# 列出依賴 +go list -m all + +# 更新依賴 +go get -u ./... + +# 整理依賴 +go mod tidy + +# 建構可執行檔 +go build -o myapp + +# 直接執行 +go run main.go + +# 下載依賴 +go mod download + +# 驗證依賴 +go mod verify +``` + + +```python !! py +# Python 等效命令 +# 格式化 +black . + +# 執行測試 +pytest + +# 類型檢查 +mypy . + +# 安裝依賴 +pip install -r requirements.txt + +# 執行應用程式 +python main.py +``` + +```go !! go +// Go 等效命令 +// 格式化 +go fmt ./... + +// 執行測試 +go test ./... + +// Vet(檢查問題) +go vet ./... + +// 下載依賴 +go mod download + +// 執行應用程式 +go run main.go +``` + + +## 你的第一個 Go 程式 + +讓我們編寫一個簡單的 Go 程式來開始: + + +```python !! py +# Python - Hello World +def greet(name): + return f"Hello, {name}!" + +def main(): + names = ["Alice", "Bob", "Charlie"] + for name in names: + print(greet(name)) + +if __name__ == "__main__": + main() +``` + +```go !! go +// Go - Hello World +package main + +import "fmt" + +func greet(name string) string { + return fmt.Sprintf("Hello, %s!", name) +} + +func main() { + names := []string{"Alice", "Bob", "Charlie"} + for _, name := range names { + fmt.Println(greet(name)) + } +} +``` + + +### Go 程式剖析 + +```go +package main // 1. 套件宣告(入口點) + +import "fmt" // 2. 匯入套件 + +func greet(name string) string { // 3. 函式定義 + return fmt.Sprintf("Hello, %s!", name) // 4. 傳回陳述式 +} + +func main() { // 5. Main 函式(入口點) + fmt.Println("Hello, World!") // 6. 函式呼叫 +} +``` + +**與 Python 的主要區別:** +1. **分號**:不需要(自動插入) +2. **類型**:顯式類型註解(`name string`) +3. **套件宣告**:頂部必需 +4. **匯入**:必須在引號中 +5. **匯出名稱**:大寫(例如 `fmt.Printf`) +6. **Main 函式**:必須在 `package main` 中,名為 `main`,無參數 + +### 執行你的第一個程式 + +```bash +# 建立檔案 +cat > main.go << 'EOF' +package main + +import "fmt" + +func main() { + fmt.Println("Hello, World!") +} +EOF + +# 直接執行(一步編譯和執行) +go run main.go +# 輸出: Hello, World! + +# 編譯為二進位 +go build -o hello main.go + +# 執行二進位 +./hello +# 輸出: Hello, World! + +# 查看生成的二進位 +ls -lh hello +# 簡單程式通常 1-2 MB +``` + +## 設定 Go 專案 + +### 初始化 Go 模組 + +```bash +# 建立專案目錄 +mkdir myproject +cd myproject + +# 初始化 Go 模組 +go mod init github.com/username/myproject + +# 這會建立 go.mod 檔案: +cat go.mod +``` + +**go.mod 內容:** +```go +module github.com/username/myproject + +go 1.21 +``` + +### 專案結構 + + +```bash +# Python 專案 +myproject/ +├── venv/ # 虛擬環境 +├── requirements.txt # 依賴 +├── src/ +│ ├── __init__.py +│ ├── main.py +│ └── utils.py +└── tests/ + ├── __init__.py + └── test_utils.py +``` + +```bash +# Go 專案 +myproject/ +├── go.mod # 模組定義 +├── go.sum # 依賴校驗和 +├── main.go # 入口點 +├── utils.go # 工具函式 +└── utils_test.go # 測試(file_test.go 約定) +``` + + +### 示例專案 + +讓我們建立一個簡單的專案: + +```bash +# 初始化 +go mod init github.com/username/myproject + +# 建立main.go +cat > main.go << 'EOF' +package main + +import ( + "fmt" + "strings" +) + +func greet(name string) string { + return fmt.Sprintf("Hello, %s!", strings.Title(name)) +} + +func main() { + names := []string{"alice", "bob", "charlie"} + for _, name := range names { + fmt.Println(greet(name)) + } +} +EOF + +# 執行它 +go run main.go +# Hello, Alice! +# Hello, Bob! +# Hello, Charlie! + +# 格式化程式碼 +go fmt main.go + +# 建構它 +go build -o myproject + +# 執行二進位 +./myproject +``` + +## 效能對比:親身體驗 + +讓我們用實際例子比較 Python 和 Go 效能: + + +```python !! py +# Python - 統計大檔案中的單詞 +import time +from collections import Counter + +def count_words(filename): + with open(filename, 'r') as f: + words = f.read().lower().split() + return Counter(words) + +start = time.time() +result = count_words('large_file.txt') +print(f"Time: {time.time() - start:.2f}s") +``` + +```go !! go +// Go - 統計大檔案中的單詞 +package main + +import ( + "bufio" + "fmt" + "os" + "strings" + "time" +) + +func countWords(filename string) map[string]int { + file, _ := os.Open(filename) + defer file.Close() + + scanner := bufio.NewScanner(file) + counts := make(map[string]int) + + for scanner.Scan() { + words := strings.Fields(strings.ToLower(scanner.Text())) + for _, word := range words { + counts[word]++ + } + } + + return counts +} + +func main() { + start := time.Now() + counts := countWords("large_file.txt") + elapsed := time.Since(start) + + fmt.Printf("Time: %.2fs\n", elapsed.Seconds()) +} +``` + + +**典型結果:** +- **Python**:100MB 檔案 2-5 秒 +- **Go**:100MB 檔案 0.3-0.8 秒(快 3-10 倍) + +## Python 開發者常見問題 + +### Q1:Go 難學嗎? + +**答:** 不,Go 比大多數靜態類型語言更容易學: +- 簡單語法,只有 25 個關鍵字 +- 無複雜的類別階層 +- 最少的樣板程式碼 +- 良好的錯誤訊息 + +**Python 開發者的挑戰:** 靜態類型需要適應,但類型推斷有幫助。 + +### Q2:Go 會取代 Python 嗎? + +**答:** 不會,它們服務於不同目的: +- **Python**:最適合資料科學、ML、腳本、快速原型設計 +- **Go**:最適合系統程式設計、微服務、雲端基礎設施 + +它們是互補的,不是競爭的。 + +### Q3:我可以將 Go 與 Python 一起使用嗎? + +**答:** 可以!常見模式: +- 使用 Go 處理效能關鍵服務 +- 使用 Python 進行資料處理/ML +- 透過 HTTP、gRPC 或訊息佇列通訊 + + +```python !! py +# Python - ML 模型服務 +from flask import Flask, request +import my_ml_model + +app = Flask(__name__) + +@app.route('/predict', methods=['POST']) +def predict(): + data = request.json + result = my_ml_model.predict(data) + return {'prediction': result} +``` + +```go !! go +// Go - API 閘道 +package main + +import "net/http" + +func proxyHandler(w http.ResponseWriter, r *http.Request) { + // 代理到 Python ML 服務 + // 處理 auth、限流、快取 +} + +func main() { + http.ListenAndServe(":8080", nil) +} +``` + + +### Q4:Go vs pip 如何處理依賴? + +**答:** Go 使用 Go Modules: + +```bash +# Python +pip install requests + +# Go +go get github.com/gin-gonic/gin +``` + +**主要區別:** +- Go:Vendor 目錄或模組快取(無需虛擬環境) +- Python:需要虛擬環境進行隔離 + +### Q5:測試呢? + +**答:** Go 有內建測試: + + +```python !! py +# Python - pytest +def test_add(): + assert add(2, 3) == 5 + +# 執行: pytest test_file.py +``` + +```go !! go +// Go - 內建測試 +func TestAdd(t *testing.T) { + result := Add(2, 3) + if result != 5 { + t.Errorf("Add(2, 3) = %d; want 5", result) + } +} + +// 執行: go test +``` + + +## 總結 + +在本模組中,你學到了: + +1. **Go 的歷史和設計哲學**:簡潔性、並發、效能、安全性 +2. **與 Python 的主要區別**:靜態類型、編譯、無 GIL、顯式錯誤處理 +3. **Go 的使用場景**:雲原生、微服務、DevOps 工具、CLI +4. **開發環境**:安裝、IDE 設定、Go 模組 +5. **你的第一個 Go 程式**:結構、編譯、執行 +6. **效能特徵**:許多任務比 Python 快 10-100 倍 + +### 下一步 + +你準備好深入瞭解 Go 語法了!在下一模組中,你將學習: +- 與 Python 的基本語法差異 +- 變數、類型和宣告 +- 控制流程和函式 +- 如何將 Python 習語翻譯為 Go + +## 練習 + +1. **安裝 Go**(如果尚未安裝) +2. **建立 "Hello, World!"** 程式 +3. **初始化 Go 模組**用於新專案 +4. **編寫簡單程式**,它: + - 建立字串切片 + - 遍歷它們 + - 以不同格式列印 +5. **比較效能**:用 Python 和 Go 實作相同的簡單演算法,然後使用 `time` 命令比較執行時間 + +## 其他資源 + +- **Go 官方網站**:https://go.dev/ +- **Go Tour**:https://go.dev/tour/welcome/1 +- **Effective Go**:https://go.dev/doc/effective_go +- **Go by Example**:https://gobyexample.com/ +- **Go Playground**:https://go.dev/play/(在瀏覽器中執行 Go) diff --git a/content/docs/py2go/module-01-basic-syntax.mdx b/content/docs/py2go/module-01-basic-syntax.mdx new file mode 100644 index 0000000..336c306 --- /dev/null +++ b/content/docs/py2go/module-01-basic-syntax.mdx @@ -0,0 +1,666 @@ +--- +title: "Module 1: Basic Syntax Comparison" +description: "Learn the fundamental differences between Python and Go syntax" +--- + +## Introduction + +In this module, we'll compare the basic syntax of Python and Go. Understanding these differences is crucial for transitioning from Python's dynamic, interpreted nature to Go's static, compiled approach. + +## Variable Declaration + +### Python: Dynamic Typing + +Python uses dynamic typing - variables can hold any type: + +```python +# Python - No type declaration needed +name = "Alice" +age = 25 +price = 19.99 +is_active = True +items = [1, 2, 3] + +# Type can change +x = 10 +x = "now a string" # Perfectly valid in Python +``` + +### Go: Static Typing with Inference + +Go requires types, but can infer them: + + +```python !! py +# Python - Dynamic typing +name = "Alice" +age = 25 +price = 19.99 +is_active = True +items = [1, 2, 3] +``` + +```go !! go +// Go - Static typing with inference +var name string = "Alice" // Full declaration +var age = 25 // Type inferred (int) +price := 19.99 // Short declaration (most common) +is_active := true // Type inferred (bool) +var items []int = []int{1, 2, 3} // Slice + +// Cannot change type +// age = "now a string" // Compilation error! +``` + + +### Variable Declaration Styles + + +```python !! py +# Python - One way +x = 10 +y = "hello" +z = 3.14 +``` + +```go !! go +// Go - Three ways + +// 1. Full declaration +var x int = 10 + +// 2. Type inference +var y = "hello" + +// 3. Short declaration (most common, only inside functions) +z := 3.14 + +// Multiple variables +var a, b, c int = 1, 2, 3 +name, age := "Bob", 30 + +// Block declaration (for package level) +var ( + port int = 8080 + host string = "localhost" + debug bool = false +) +``` + + +## Constants + + +```python !! py +# Python - Constants (convention, not enforced) +MAX_CONNECTIONS = 100 +API_KEY = "secret" +DEBUG = True + +# Can actually be changed (but shouldn't) +MAX_CONNECTIONS = 200 # Works but violates convention +``` + +```go !! go +// Go - True constants (compile-time) +const MaxConnections = 100 +const APIKey = "secret" +const Debug = false + +// Cannot be changed +// MaxConnections = 200 // Compilation error! + +// Multiple constants +const ( + StatusOK = 200 + StatusNotFound = 404 + StatusError = 500 +) +``` + + +## Basic Types + + +```python !! py +# Python - Built-in types +int_num = 42 # int (unlimited precision) +float_num = 3.14 # float +text = "hello" # str +is_true = True # bool +byte_data = b"bytes" # bytes + +# Complex types +numbers = [1, 2, 3] # list (mutable) +coords = (1, 2) # tuple (immutable) +``` + +```go !! go +// Go - Basic types +var intNum int = 42 // int (platform-dependent, usually 64-bit) +var floatNum float64 = 3.14 // float64 +var text string = "hello" // string +var isTrue bool = true // bool +var byteData []byte = []byte("bytes") // byte slice + +// Complex types +var numbers []int = []int{1, 2, 3} // slice (dynamic array) +var coords [2]int = [2]int{1, 2} // array (fixed size) + +// Specific numeric types +var i8 int8 = 127 // 8-bit integer +var i16 int16 = 32767 // 16-bit integer +var i32 int32 = 2147483647 // 32-bit integer +var i64 int64 = 9223372036854775807 // 64-bit integer +var f32 float32 = 3.14 // 32-bit float +var u8 uint8 = 255 // 8-bit unsigned +``` + + +## Strings + + +```python !! py +# Python - String operations +text = "Hello, World!" + +# Length +print(len(text)) # 13 + +# Concatenation +greeting = text + " How are you?" +print(greeting) # Hello, World! How are you? + +# String formatting +name = "Alice" +age = 25 +print(f"Name: {name}, Age: {age}") +print("Name: {}, Age: {}".format(name, age)) + +# Substring (slicing) +print(text[0:5]) # Hello +print(text[7:]) # World! +print(text[-6:]) # World! + +# Multiline string +multiline = """ +This is a +multiline string +""" +``` + +```go !! go +// Go - String operations +package main + +import ( + "fmt" + "strings" +) + +func main() { + text := "Hello, World!" + + // Length + fmt.Println(len(text)) // 13 (number of bytes) + fmt.Println(len([]rune(text))) // 13 (number of characters/runes) + + // Concatenation + greeting := text + " How are you?" + fmt.Println(greeting) // Hello, World! How are you? + + // String formatting + name := "Alice" + age := 25 + fmt.Printf("Name: %s, Age: %d\n", name, age) + fmt.Printf("Name: %v, Age: %v\n", name, age) + + // Substring (slicing) + fmt.Println(text[0:5]) // Hello + fmt.Println(text[7:]) // World! + // No negative indices in Go + + // Multiline string + multiline := ` +This is a +multiline string +` + + // String methods + fmt.Println(strings.ToUpper(text)) // HELLO, WORLD! + fmt.Println(strings.Contains(text, "World")) // true + fmt.Println(strings.Replace(text, "World", "Go", 1)) // Hello, Go! +} +``` + + +## Zero Values + +In Go, every type has a "zero value" - the default value when not initialized: + + +```python !! py +# Python - Variables must be initialized +x = None # Explicit None +y = 0 # Initialize to 0 +z = "" # Initialize to empty string + +# No default values +def my_function(): + a # Error: local variable referenced before assignment +``` + +```go !! go +// Go - Zero values +var x int // 0 +var y float64 // 0 +var z string // "" (empty string) +var b bool // false +var s []int // nil (nil slice) + +// Works - variables have default values +func myFunction() { + var a int + fmt.Println(a) // Prints 0 +} +``` + + +## Comments + + +```python !! py +# Python - Comments +# Single line comment + +""" +Multi-line comment +(docstring) +""" + +# Function documentation +def add(a, b): + """Add two numbers together.""" + return a + b +``` + +```go !! go +// Go - Comments +// Single line comment + +/* Multi-line + comment */ + +// Package documentation (appears before package clause) +// Package main demonstrates basic Go syntax +package main + +// Function documentation +// Add adds two numbers together and returns the result. +func Add(a int, b int) int { + return a + b +} +``` + + +## Type Conversion + + +```python !! py +# Python - Automatic type conversion +x = 10 +y = 3.14 +result = x + y # 13.14 (float) +text = "Value: " + str(x) # Explicit conversion needed for strings +``` + +```go !! go +// Go - No automatic conversion (must be explicit) +package main + +import "fmt" + +func main() { + var x int = 10 + var y float64 = 3.14 + + // Error: cannot mix types + // result := x + y // Compilation error! + + // Must convert explicitly + result := float64(x) + y + fmt.Println(result) // 13.14 + + // String conversion + text := "Value: " + fmt.Sprintf("%d", x) + fmt.Println(text) // Value: 10 + + // Using strconv package + import "strconv" + numStr := strconv.Itoa(x) + fmt.Println(numStr) // "10" +} +``` + + +## Pointers (New Concept for Python Developers) + +Go has pointers - Python doesn't expose them: + + +```python !! py +# Python - Everything is a reference +def modify_list(lst): + lst.append(4) # Modifies original list + +numbers = [1, 2, 3] +modify_list(numbers) +print(numbers) # [1, 2, 3, 4] + +# But integers are immutable +def increment(x): + x += 1 # Creates new local variable + +num = 10 +increment(num) +print(num) # 10 (unchanged) +``` + +```go !! go +// Go - Explicit pointers +package main + +import "fmt" + +func modifySlice(slice []int) { + slice = append(slice, 4) // Modifies slice +} + +func incrementByPointer(x *int) { + *x++ // Dereference and increment +} + +func main() { + // Slice is already a reference + numbers := []int{1, 2, 3} + modifySlice(numbers) + fmt.Println(numbers) // [1, 2, 3, 4] + + // Use pointer for value types + num := 10 + incrementByPointer(&num) // Pass address + fmt.Println(num) // 11 (changed!) + + // Pointer syntax + x := 42 + p := &x // p is pointer to x (type *int) + fmt.Println(*p) // 42 (dereference) + *p = 100 // Modify x through pointer + fmt.Println(x) // 100 +} +``` + + +## Arrays and Slices + + +```python !! py +# Python - Lists (dynamic arrays) +numbers = [1, 2, 3, 4, 5] +numbers.append(6) # Add element +numbers.extend([7, 8]) # Add multiple +mixed = [1, "hello", 3.14] # Mixed types (allowed) +sliced = numbers[1:4] # Slice +``` + +```go !! go +// Go - Arrays (fixed) and Slices (dynamic) +package main + +import "fmt" + +func main() { + // Array - fixed size + var arr [5]int = [5]int{1, 2, 3, 4, 5} + // arr[5] = 6 // Error: index out of bounds + + // Slice - dynamic (use this mostly) + numbers := []int{1, 2, 3, 4, 5} + numbers = append(numbers, 6) // Add element + numbers = append(numbers, 7, 8) // Add multiple + + // Cannot mix types in Go + // mixed := []interface{}{1, "hello", 3.14} // Need interface{} + + // Slicing + sliced := numbers[1:4] // [2 3 4] + + // Make slice with capacity + nums := make([]int, 0, 10) // length=0, capacity=10 + nums = append(nums, 1, 2, 3) + + fmt.Println(nums) +} +``` + + +## Maps (Dictionaries) + + +```python !! py +# Python - Dictionary +person = { + "name": "Alice", + "age": 30, + "city": "NYC" +} + +# Access +print(person["name"]) # Alice +print(person.get("country", "Unknown")) # Unknown (default) + +# Modify +person["age"] = 31 +person["country"] = "USA" + +# Delete +del person["city"] + +# Iterate +for key, value in person.items(): + print(f"{key}: {value}") +``` + +```go !! go +// Go - Map +package main + +import "fmt" + +func main() { + // Create map + person := map[string]interface{}{ + "name": "Alice", + "age": 30, + "city": "NYC", + } + + // Access + fmt.Println(person["name"]) // Alice + country, exists := person["country"] + if !exists { + country = "Unknown" + } + fmt.Println(country) // Unknown + + // Modify + person["age"] = 31 + person["country"] = "USA" + + // Delete + delete(person, "city") + + // Iterate + for key, value := range person { + fmt.Printf("%v: %v\n", key, value) + } + + // Typed map (better) + ages := map[string]int{ + "Alice": 30, + "Bob": 25, + } +} +``` + + +## Operators + + +```python !! py +# Python - Operators +a, b = 10, 3 + +# Arithmetic +print(a + b) # 13 (addition) +print(a - b) # 7 (subtraction) +print(a * b) # 30 (multiplication) +print(a / b) # 3.333... (true division) +print(a // b) # 3 (floor division) +print(a % b) # 1 (modulo) +print(a ** b) # 1000 (power) + +# Comparison +print(a == b) # False +print(a != b) # True +print(a > b) # True +print(a >= b) # True + +# Logical +print(a > 5 and b < 5) # True +print(a > 5 or b > 5) # True +print(not (a == b)) # True +``` + +```go !! go +// Go - Operators +package main + +import ( + "fmt" + "math" +) + +func main() { + a, b := 10, 3 + + // Arithmetic + fmt.Println(a + b) // 13 (addition) + fmt.Println(a - b) // 7 (subtraction) + fmt.Println(a * b) // 30 (multiplication) + fmt.Println(a / b) // 3 (integer division) + fmt.Println(a % b) // 1 (modulo) + fmt.Println(math.Pow(float64(a), float64(b))) // 1000 (power) + + // Comparison (same as Python) + fmt.Println(a == b) // false + fmt.Println(a != b) // true + fmt.Println(a > b) // true + fmt.Println(a >= b) // true + + // Logical (different syntax) + fmt.Println(a > 5 && b < 5) // true (and) + fmt.Println(a > 5 || b > 5) // true (or) + fmt.Println(!(a == b)) // true (not) + + // Increment/decrement + i := 0 + i++ // i = 1 + i-- // i = 0 + // No ++i or --i in Go +} +``` + + +## Control Flow Basics + +### If Statements + + +```python !! py +# Python - If statement +age = 20 + +if age >= 18: + print("Adult") +elif age >= 13: + print("Teenager") +else: + print("Child") + +# With assignment expression (Python 3.8+) +if (n := len(items)) > 10: + print(f"Too many items: {n}") +``` + +```go !! go +// Go - If statement +package main + +import "fmt" + +func main() { + age := 20 + + if age >= 18 { + fmt.Println("Adult") + } else if age >= 13 { // Note: no elif + fmt.Println("Teenager") + } else { + fmt.Println("Child") + } + + // With initialization statement + if n := len(items); n > 10 { + fmt.Printf("Too many items: %d\n", n) + } + // n is not accessible outside the if block +} +``` + + +## Summary + +In this module, you learned: + +1. **Variable declarations**: Python (dynamic) vs Go (static with inference) +2. **Basic types**: Different type systems and conversions +3. **Strings**: Similar operations, different syntax +4. **Zero values**: Go's default values for uninitialized variables +5. **Pointers**: New concept for Python developers +6. **Collections**: Arrays/slices vs lists, maps vs dictionaries +7. **Operators**: Mostly similar with some syntax differences +8. **Control flow introduction**: If statements with initialization + +## Key Takeaways + +- **Go requires explicit types** but uses type inference with `:=` +- **No implicit type conversion** - must convert explicitly +- **Zero values** mean variables always have a default value +- **Pointers** give you explicit control over references +- **Slices** are dynamic arrays, **arrays** are fixed-size +- **Maps** are like Python dictionaries +- **Use `:=` for short declaration** inside functions (most common) + +## Exercises + +1. Create a program that declares variables of different types +2. Write a function that takes a pointer and modifies the value +3. Create a slice, append elements, and slice it +4. Build a map and demonstrate CRUD operations +5. Convert between different numeric types +6. Use if statements with initialization + +## Next Steps + +In the next module, we'll dive deeper into control flow, loops, and functions in Go. diff --git a/content/docs/py2go/module-01-basic-syntax.zh-cn.mdx b/content/docs/py2go/module-01-basic-syntax.zh-cn.mdx new file mode 100644 index 0000000..c2d357b --- /dev/null +++ b/content/docs/py2go/module-01-basic-syntax.zh-cn.mdx @@ -0,0 +1,666 @@ +--- +title: "模块 1:基础语法对比" +description: "学习 Python 和 Go 语法之间的基本差异" +--- + +## 简介 + +在本模块中,我们将对比 Python 和 Go 的基础语法。理解这些差异对于从 Python 的动态解释型特性转向 Go 的静态编译型方法至关重要。 + +## 变量声明 + +### Python: 动态类型 + +Python 使用动态类型 - 变量可以持有任何类型: + +```python +# Python - 无需类型声明 +name = "Alice" +age = 25 +price = 19.99 +is_active = True +items = [1, 2, 3] + +# 类型可以改变 +x = 10 +x = "now a string" # 在 Python 中完全有效 +``` + +### Go: 带类型推断的静态类型 + +Go 需要类型,但可以推断它们: + + +```python !! py +# Python - 动态类型 +name = "Alice" +age = 25 +price = 19.99 +is_active = True +items = [1, 2, 3] +``` + +```go !! go +// Go - 带类型推断的静态类型 +var name string = "Alice" // 完整声明 +var age = 25 // 类型推断 (int) +price := 19.99 // 短变量声明 (最常用) +is_active := true // 类型推断 (bool) +var items []int = []int{1, 2, 3} // slice + +// 不能改变类型 +// age = "now a string" // 编译错误! +``` + + +### 变量声明风格 + + +```python !! py +# Python - 一种方式 +x = 10 +y = "hello" +z = 3.14 +``` + +```go !! go +// Go - 三种方式 + +// 1. 完整声明 +var x int = 10 + +// 2. 类型推断 +var y = "hello" + +// 3. 短变量声明 (最常用,仅在函数内) +z := 3.14 + +// 多个变量 +var a, b, c int = 1, 2, 3 +name, age := "Bob", 30 + +// 块声明 (用于包级别) +var ( + port int = 8080 + host string = "localhost" + debug bool = false +) +``` + + +## 常量 + + +```python !! py +# Python - 常量 (约定,不强制) +MAX_CONNECTIONS = 100 +API_KEY = "secret" +DEBUG = True + +# 实际上可以修改 (但不应该) +MAX_CONNECTIONS = 200 # 可以工作但违反约定 +``` + +```go !! go +// Go - 真正的常量 (编译时) +const MaxConnections = 100 +const APIKey = "secret" +const Debug = false + +// 不能被修改 +// MaxConnections = 200 // 编译错误! + +// 多个常量 +const ( + StatusOK = 200 + StatusNotFound = 404 + StatusError = 500 +) +``` + + +## 基本类型 + + +```python !! py +# Python - 内置类型 +int_num = 42 # int (无限精度) +float_num = 3.14 # float +text = "hello" # str +is_true = True # bool +byte_data = b"bytes" # bytes + +# 复合类型 +numbers = [1, 2, 3] # list (可变) +coords = (1, 2) # tuple (不可变) +``` + +```go !! go +// Go - 基本类型 +var intNum int = 42 // int (平台相关,通常 64 位) +var floatNum float64 = 3.14 // float64 +var text string = "hello" // string +var isTrue bool = true // bool +var byteData []byte = []byte("bytes") // byte slice + +// 复合类型 +var numbers []int = []int{1, 2, 3} // slice (动态数组) +var coords [2]int = [2]int{1, 2} // array (固定大小) + +// 特定数值类型 +var i8 int8 = 127 // 8 位整数 +var i16 int16 = 32767 // 16 位整数 +var i32 int32 = 2147483647 // 32 位整数 +var i64 int64 = 9223372036854775807 // 64 位整数 +var f32 float32 = 3.14 // 32 位浮点 +var u8 uint8 = 255 // 8 位无符号 +``` + + +## 字符串 + + +```python !! py +# Python - 字符串操作 +text = "Hello, World!" + +# 长度 +print(len(text)) # 13 + +# 拼接 +greeting = text + " How are you?" +print(greeting) # Hello, World! How are you? + +# 字符串格式化 +name = "Alice" +age = 25 +print(f"Name: {name}, Age: {age}") +print("Name: {}, Age: {}".format(name, age)) + +# 子字符串 (切片) +print(text[0:5]) # Hello +print(text[7:]) # World! +print(text[-6:]) # World! + +# 多行字符串 +multiline = """ +This is a +multiline string +""" +``` + +```go !! go +// Go - 字符串操作 +package main + +import ( + "fmt" + "strings" +) + +func main() { + text := "Hello, World!" + + // 长度 + fmt.Println(len(text)) // 13 (字节数) + fmt.Println(len([]rune(text))) // 13 (字符/rune 数) + + // 拼接 + greeting := text + " How are you?" + fmt.Println(greeting) // Hello, World! How are you? + + // 字符串格式化 + name := "Alice" + age := 25 + fmt.Printf("Name: %s, Age: %d\n", name, age) + fmt.Printf("Name: %v, Age: %v\n", name, age) + + // 子字符串 (切片) + fmt.Println(text[0:5]) // Hello + fmt.Println(text[7:]) // World! + // Go 中没有负数索引 + + // 多行字符串 + multiline := ` +This is a +multiline string +` + + // 字符串方法 + fmt.Println(strings.ToUpper(text)) // HELLO, WORLD! + fmt.Println(strings.Contains(text, "World")) // true + fmt.Println(strings.Replace(text, "World", "Go", 1)) // Hello, Go! +} +``` + + +## 零值 + +在 Go 中,每个类型都有"零值" - 未初始化时的默认值: + + +```python !! py +# Python - 变量必须初始化 +x = None # 显式 None +y = 0 # 初始化为 0 +z = "" # 初始化为空字符串 + +# 没有默认值 +def my_function(): + a # 错误:局部变量在赋值前被引用 +``` + +```go !! go +// Go - 零值 +var x int // 0 +var y float64 // 0 +var z string // "" (空字符串) +var b bool // false +var s []int // nil (nil slice) + +// 可以工作 - 变量有默认值 +func myFunction() { + var a int + fmt.Println(a) // 打印 0 +} +``` + + +## 注释 + + +```python !! py +# Python - 注释 +# 单行注释 + +""" +多行注释 +(docstring) +""" + +# 函数文档 +def add(a, b): + """Add two numbers together.""" + return a + b +``` + +```go !! go +// Go - 注释 +// 单行注释 + +/* 多行 + 注释 */ + +// 包文档 (出现在 package 子句之前) +// Package main demonstrates basic Go syntax +package main + +// 函数文档 +// Add adds two numbers together and returns the result. +func Add(a int, b int) int { + return a + b +} +``` + + +## 类型转换 + + +```python !! py +# Python - 自动类型转换 +x = 10 +y = 3.14 +result = x + y # 13.14 (float) +text = "Value: " + str(x) # 字符串需要显式转换 +``` + +```go !! go +// Go - 无自动转换 (必须显式) +package main + +import "fmt" + +func main() { + var x int = 10 + var y float64 = 3.14 + + // 错误:不能混合类型 + // result := x + y // 编译错误! + + // 必须显式转换 + result := float64(x) + y + fmt.Println(result) // 13.14 + + // 字符串转换 + text := "Value: " + fmt.Sprintf("%d", x) + fmt.Println(text) // Value: 10 + + // 使用 strconv 包 + import "strconv" + numStr := strconv.Itoa(x) + fmt.Println(numStr) // "10" +} +``` + + +## 指针 (Python 开发者的新概念) + +Go 有指针 - Python 不暴露它们: + + +```python !! py +# Python - 一切都是引用 +def modify_list(lst): + lst.append(4) # 修改原始列表 + +numbers = [1, 2, 3] +modify_list(numbers) +print(numbers) # [1, 2, 3, 4] + +# 但整数是不可变的 +def increment(x): + x += 1 # 创建新的局部变量 + +num = 10 +increment(num) +print(num) # 10 (未改变) +``` + +```go !! go +// Go - 显式指针 +package main + +import "fmt" + +func modifySlice(slice []int) { + slice = append(slice, 4) // 修改 slice +} + +func incrementByPointer(x *int) { + *x++ // 解引用并递增 +} + +func main() { + // Slice 已经是引用 + numbers := []int{1, 2, 3} + modifySlice(numbers) + fmt.Println(numbers) // [1, 2, 3, 4] + + // 对值类型使用指针 + num := 10 + incrementByPointer(&num) // 传递地址 + fmt.Println(num) // 11 (已改变!) + + // 指针语法 + x := 42 + p := &x // p 是 x 的指针 (类型 *int) + fmt.Println(*p) // 42 (解引用) + *p = 100 // 通过指针修改 x + fmt.Println(x) // 100 +} +``` + + +## 数组和切片 + + +```python !! py +# Python - 列表 (动态数组) +numbers = [1, 2, 3, 4, 5] +numbers.append(6) # 添加元素 +numbers.extend([7, 8]) # 添加多个 +mixed = [1, "hello", 3.14] # 混合类型 (允许) +sliced = numbers[1:4] # 切片 +``` + +```go !! go +// Go - 数组 (固定) 和切片 (动态) +package main + +import "fmt" + +func main() { + // 数组 - 固定大小 + var arr [5]int = [5]int{1, 2, 3, 4, 5} + // arr[5] = 6 // 错误:索引越界 + + // 切片 - 动态 (主要使用这个) + numbers := []int{1, 2, 3, 4, 5} + numbers = append(numbers, 6) // 添加元素 + numbers = append(numbers, 7, 8) // 添加多个 + + // Go 中不能混合类型 + // mixed := []interface{}{1, "hello", 3.14} // 需要 interface{} + + // 切片 + sliced := numbers[1:4] // [2 3 4] + + // 创建带容量的切片 + nums := make([]int, 0, 10) // 长度=0, 容量=10 + nums = append(nums, 1, 2, 3) + + fmt.Println(nums) +} +``` + + +## 映射 (字典) + + +```python !! py +# Python - 字典 +person = { + "name": "Alice", + "age": 30, + "city": "NYC" +} + +# 访问 +print(person["name"]) # Alice +print(person.get("country", "Unknown")) # Unknown (默认值) + +# 修改 +person["age"] = 31 +person["country"] = "USA" + +# 删除 +del person["city"] + +# 迭代 +for key, value in person.items(): + print(f"{key}: {value}") +``` + +```go !! go +// Go - 映射 +package main + +import "fmt" + +func main() { + // 创建映射 + person := map[string]interface{}{ + "name": "Alice", + "age": 30, + "city": "NYC", + } + + // 访问 + fmt.Println(person["name"]) // Alice + country, exists := person["country"] + if !exists { + country = "Unknown" + } + fmt.Println(country) // Unknown + + // 修改 + person["age"] = 31 + person["country"] = "USA" + + // 删除 + delete(person, "city") + + // 迭代 + for key, value := range person { + fmt.Printf("%v: %v\n", key, value) + } + + // 类型化映射 (更好) + ages := map[string]int{ + "Alice": 30, + "Bob": 25, + } +} +``` + + +## 运算符 + + +```python !! py +# Python - 运算符 +a, b = 10, 3 + +# 算术 +print(a + b) # 13 (加法) +print(a - b) # 7 (减法) +print(a * b) # 30 (乘法) +print(a / b) # 3.333... (真除法) +print(a // b) # 3 (整除) +print(a % b) # 1 (取模) +print(a ** b) # 1000 (幂) + +# 比较 +print(a == b) # False +print(a != b) # True +print(a > b) # True +print(a >= b) # True + +# 逻辑 +print(a > 5 and b < 5) # True +print(a > 5 or b > 5) # True +print(not (a == b)) # True +``` + +```go !! go +// Go - 运算符 +package main + +import ( + "fmt" + "math" +) + +func main() { + a, b := 10, 3 + + // 算术 + fmt.Println(a + b) // 13 (加法) + fmt.Println(a - b) // 7 (减法) + fmt.Println(a * b) // 30 (乘法) + fmt.Println(a / b) // 3 (整数除法) + fmt.Println(a % b) // 1 (取模) + fmt.Println(math.Pow(float64(a), float64(b))) // 1000 (幂) + + // 比较 (与 Python 相同) + fmt.Println(a == b) // false + fmt.Println(a != b) // true + fmt.Println(a > b) // true + fmt.Println(a >= b) // true + + // 逻辑 (不同语法) + fmt.Println(a > 5 && b < 5) // true (and) + fmt.Println(a > 5 || b > 5) // true (or) + fmt.Println(!(a == b)) // true (not) + + // 递增/递减 + i := 0 + i++ // i = 1 + i-- // i = 0 + // Go 中没有 ++i 或 --i +} +``` + + +## 控制流基础 + +### If 语句 + + +```python !! py +# Python - If 语句 +age = 20 + +if age >= 18: + print("Adult") +elif age >= 13: + print("Teenager") +else: + print("Child") + +# 使用赋值表达式 (Python 3.8+) +if (n := len(items)) > 10: + print(f"Too many items: {n}") +``` + +```go !! go +// Go - If 语句 +package main + +import "fmt" + +func main() { + age := 20 + + if age >= 18 { + fmt.Println("Adult") + } else if age >= 13 { // 注意:没有 elif + fmt.Println("Teenager") + } else { + fmt.Println("Child") + } + + // 带初始化语句 + if n := len(items); n > 10 { + fmt.Printf("Too many items: %d\n", n) + } + // n 在 if 块外不可访问 +} +``` + + +## 总结 + +在本模块中,你学习了: + +1. **变量声明**: Python (动态) vs Go (带类型推断的静态) +2. **基本类型**: 不同的类型系统和转换 +3. **字符串**: 相似的操作,不同的语法 +4. **零值**: Go 中未初始化变量的默认值 +5. **指针**: Python 开发者的新概念 +6. **集合**: 数组/切片 vs 列表,映射 vs 字典 +7. **运算符**: 大部分相似,一些语法差异 +8. **控制流简介**: 带初始化的 If 语句 + +## 关键要点 + +- **Go 需要显式类型** 但使用 `:=` 进行类型推断 +- **无隐式类型转换** - 必须显式转换 +- **零值** 意味着变量总是有默认值 +- **指针** 让你对引用有显式控制 +- **切片** 是动态数组,**数组**是固定大小 +- **映射** 类似 Python 字典 +- **使用 `:=` 进行短变量声明** 在函数内部 (最常用) + +## 练习 + +1. 创建一个程序,声明不同类型的变量 +2. 编写一个函数,接收指针并修改值 +3. 创建一个切片,添加元素,并对其进行切片 +4. 构建一个映射并演示 CRUD 操作 +5. 在不同数值类型之间转换 +6. 使用带初始化的 if 语句 + +## 下一步 + +在下一个模块中,我们将深入学习 Go 的控制流、循环和函数。 diff --git a/content/docs/py2go/module-01-basic-syntax.zh-tw.mdx b/content/docs/py2go/module-01-basic-syntax.zh-tw.mdx new file mode 100644 index 0000000..ea17747 --- /dev/null +++ b/content/docs/py2go/module-01-basic-syntax.zh-tw.mdx @@ -0,0 +1,666 @@ +--- +title: "模組 1:基礎語法對比" +description: "學習 Python 和 Go 語法之間的基本差異" +--- + +## 簡介 + +在本模組中,我們將對比 Python 和 Go 的基礎語法。理解這些差異對於從 Python 的動態直譯型特性轉向 Go 的靜態編譯型方法至關重要。 + +## 變數宣告 + +### Python: 動態類型 + +Python 使用動態類型 - 變數可以持有任何類型: + +```python +# Python - 無需類型宣告 +name = "Alice" +age = 25 +price = 19.99 +is_active = True +items = [1, 2, 3] + +# 類型可以改變 +x = 10 +x = "now a string" # 在 Python 中完全有效 +``` + +### Go: 帶類型推斷的靜態類型 + +Go 需要類型,但可以推斷它們: + + +```python !! py +# Python - 動態類型 +name = "Alice" +age = 25 +price = 19.99 +is_active = True +items = [1, 2, 3] +``` + +```go !! go +// Go - 帶類型推斷的靜態類型 +var name string = "Alice" // 完整宣告 +var age = 25 // 類型推斷 (int) +price := 19.99 // 短變數宣告 (最常用) +is_active := true // 類型推斷 (bool) +var items []int = []int{1, 2, 3} // slice + +// 不能改變類型 +// age = "now a string" // 編譯錯誤! +``` + + +### 變數宣告風格 + + +```python !! py +# Python - 一種方式 +x = 10 +y = "hello" +z = 3.14 +``` + +```go !! go +// Go - 三種方式 + +// 1. 完整宣告 +var x int = 10 + +// 2. 類型推斷 +var y = "hello" + +// 3. 短變數宣告 (最常用,僅在函式內) +z := 3.14 + +// 多個變數 +var a, b, c int = 1, 2, 3 +name, age := "Bob", 30 + +// 區塊宣告 (用於套件級別) +var ( + port int = 8080 + host string = "localhost" + debug bool = false +) +``` + + +## 常數 + + +```python !! py +# Python - 常數 (約定,不強制) +MAX_CONNECTIONS = 100 +API_KEY = "secret" +DEBUG = True + +# 實際上可以修改 (但不應該) +MAX_CONNECTIONS = 200 # 可以工作但違反約定 +``` + +```go !! go +// Go - 真正的常數 (編譯時) +const MaxConnections = 100 +const APIKey = "secret" +const Debug = false + +// 不能被修改 +// MaxConnections = 200 // 編譯錯誤! + +// 多個常數 +const ( + StatusOK = 200 + StatusNotFound = 404 + StatusError = 500 +) +``` + + +## 基本類型 + + +```python !! py +# Python - 內建類型 +int_num = 42 # int (無限精度) +float_num = 3.14 # float +text = "hello" # str +is_true = True # bool +byte_data = b"bytes" # bytes + +# 複合類型 +numbers = [1, 2, 3] # list (可變) +coords = (1, 2) # tuple (不可變) +``` + +```go !! go +// Go - 基本類型 +var intNum int = 42 // int (平台相關,通常 64 位) +var floatNum float64 = 3.14 // float64 +var text string = "hello" // string +var isTrue bool = true // bool +var byteData []byte = []byte("bytes") // byte slice + +// 複合類型 +var numbers []int = []int{1, 2, 3} // slice (動態陣列) +var coords [2]int = [2]int{1, 2} // array (固定大小) + +// 特定數值類型 +var i8 int8 = 127 // 8 位元整數 +var i16 int16 = 32767 // 16 位元整數 +var i32 int32 = 2147483647 // 32 位元整數 +var i64 int64 = 9223372036854775807 // 64 位元整數 +var f32 float32 = 3.14 // 32 位元浮點 +var u8 uint8 = 255 // 8 位元無號 +``` + + +## 字串 + + +```python !! py +# Python - 字串操作 +text = "Hello, World!" + +# 長度 +print(len(text)) # 13 + +# 拼接 +greeting = text + " How are you?" +print(greeting) # Hello, World! How are you? + +# 字串格式化 +name = "Alice" +age = 25 +print(f"Name: {name}, Age: {age}") +print("Name: {}, Age: {}".format(name, age)) + +# 子字串 (切片) +print(text[0:5]) # Hello +print(text[7:]) # World! +print(text[-6:]) # World! + +# 多行字串 +multiline = """ +This is a +multiline string +""" +``` + +```go !! go +// Go - 字串操作 +package main + +import ( + "fmt" + "strings" +) + +func main() { + text := "Hello, World!" + + // 長度 + fmt.Println(len(text)) // 13 (位元組數) + fmt.Println(len([]rune(text))) // 13 (字元/rune 數) + + // 拼接 + greeting := text + " How are you?" + fmt.Println(greeting) // Hello, World! How are you? + + // 字串格式化 + name := "Alice" + age := 25 + fmt.Printf("Name: %s, Age: %d\n", name, age) + fmt.Printf("Name: %v, Age: %v\n", name, age) + + // 子字串 (切片) + fmt.Println(text[0:5]) // Hello + fmt.Println(text[7:]) // World! + // Go 中沒有負數索引 + + // 多行字串 + multiline := ` +This is a +multiline string +` + + // 字串方法 + fmt.Println(strings.ToUpper(text)) // HELLO, WORLD! + fmt.Println(strings.Contains(text, "World")) // true + fmt.Println(strings.Replace(text, "World", "Go", 1)) // Hello, Go! +} +``` + + +## 零值 + +在 Go 中,每個類型都有「零值」 - 未初始化時的預設值: + + +```python !! py +# Python - 變數必須初始化 +x = None # 顯式 None +y = 0 # 初始化為 0 +z = "" # 初始化為空字串 + +# 沒有預設值 +def my_function(): + a # 錯誤:區域變數在賦值前被引用 +``` + +```go !! go +// Go - 零值 +var x int // 0 +var y float64 // 0 +var z string // "" (空字串) +var b bool // false +var s []int // nil (nil slice) + +// 可以工作 - 變數有預設值 +func myFunction() { + var a int + fmt.Println(a) // 列印 0 +} +``` + + +## 註解 + + +```python !! py +# Python - 註解 +# 單行註解 + +""" +多行註解 +(docstring) +""" + +# 函式文件 +def add(a, b): + """Add two numbers together.""" + return a + b +``` + +```go !! go +// Go - 註解 +// 單行註解 + +/* 多行 + 註解 */ + +// 套件文件 (出現在 package 子句之前) +// Package main demonstrates basic Go syntax +package main + +// 函式文件 +// Add adds two numbers together and returns the result. +func Add(a int, b int) int { + return a + b +} +``` + + +## 類型轉換 + + +```python !! py +# Python - 自動類型轉換 +x = 10 +y = 3.14 +result = x + y # 13.14 (float) +text = "Value: " + str(x) # 字串需要顯式轉換 +``` + +```go !! go +// Go - 無自動轉換 (必須顯式) +package main + +import "fmt" + +func main() { + var x int = 10 + var y float64 = 3.14 + + // 錯誤:不能混合類型 + // result := x + y // 編譯錯誤! + + // 必須顯式轉換 + result := float64(x) + y + fmt.Println(result) // 13.14 + + // 字串轉換 + text := "Value: " + fmt.Sprintf("%d", x) + fmt.Println(text) // Value: 10 + + // 使用 strconv 套件 + import "strconv" + numStr := strconv.Itoa(x) + fmt.Println(numStr) // "10" +} +``` + + +## 指標 (Python 開發者的新概念) + +Go 有指標 - Python 不暴露它們: + + +```python !! py +# Python - 一切都是參考 +def modify_list(lst): + lst.append(4) # 修改原始列表 + +numbers = [1, 2, 3] +modify_list(numbers) +print(numbers) # [1, 2, 3, 4] + +# 但整數是不可變的 +def increment(x): + x += 1 # 建立新的區域變數 + +num = 10 +increment(num) +print(num) # 10 (未改變) +``` + +```go !! go +// Go - 顯式指標 +package main + +import "fmt" + +func modifySlice(slice []int) { + slice = append(slice, 4) // 修改 slice +} + +func incrementByPointer(x *int) { + *x++ // 解參考並遞增 +} + +func main() { + // Slice 已經是參考 + numbers := []int{1, 2, 3} + modifySlice(numbers) + fmt.Println(numbers) // [1, 2, 3, 4] + + // 對值類型使用指標 + num := 10 + incrementByPointer(&num) // 傳遞位址 + fmt.Println(num) // 11 (已改變!) + + // 指標語法 + x := 42 + p := &x // p 是 x 的指標 (類型 *int) + fmt.Println(*p) // 42 (解參考) + *p = 100 // 透過指標修改 x + fmt.Println(x) // 100 +} +``` + + +## 陣列和切片 + + +```python !! py +# Python - 列表 (動態陣列) +numbers = [1, 2, 3, 4, 5] +numbers.append(6) # 新增元素 +numbers.extend([7, 8]) # 新增多個 +mixed = [1, "hello", 3.14] # 混合類型 (允許) +sliced = numbers[1:4] # 切片 +``` + +```go !! go +// Go - 陣列 (固定) 和切片 (動態) +package main + +import "fmt" + +func main() { + // 陣列 - 固定大小 + var arr [5]int = [5]int{1, 2, 3, 4, 5} + // arr[5] = 6 // 錯誤:索引越界 + + // 切片 - 動態 (主要使用這個) + numbers := []int{1, 2, 3, 4, 5} + numbers = append(numbers, 6) // 新增元素 + numbers = append(numbers, 7, 8) // 新增多個 + + // Go 中不能混合類型 + // mixed := []interface{}{1, "hello", 3.14} // 需要 interface{} + + // 切片 + sliced := numbers[1:4] // [2 3 4] + + // 建立帶容量的切片 + nums := make([]int, 0, 10) // 長度=0, 容量=10 + nums = append(nums, 1, 2, 3) + + fmt.Println(nums) +} +``` + + +## 映射 (字典) + + +```python !! py +# Python - 字典 +person = { + "name": "Alice", + "age": 30, + "city": "NYC" +} + +# 存取 +print(person["name"]) # Alice +print(person.get("country", "Unknown")) # Unknown (預設值) + +# 修改 +person["age"] = 31 +person["country"] = "USA" + +# 刪除 +del person["city"] + +# 迭代 +for key, value in person.items(): + print(f"{key}: {value}") +``` + +```go !! go +// Go - 映射 +package main + +import "fmt" + +func main() { + // 建立映射 + person := map[string]interface{}{ + "name": "Alice", + "age": 30, + "city": "NYC", + } + + // 存取 + fmt.Println(person["name"]) // Alice + country, exists := person["country"] + if !exists { + country = "Unknown" + } + fmt.Println(country) // Unknown + + // 修改 + person["age"] = 31 + person["country"] = "USA" + + // 刪除 + delete(person, "city") + + // 迭代 + for key, value := range person { + fmt.Printf("%v: %v\n", key, value) + } + + // 類型化映射 (更好) + ages := map[string]int{ + "Alice": 30, + "Bob": 25, + } +} +``` + + +## 運算子 + + +```python !! py +# Python - 運算子 +a, b = 10, 3 + +# 算術 +print(a + b) # 13 (加法) +print(a - b) # 7 (減法) +print(a * b) # 30 (乘法) +print(a / b) # 3.333... (真除法) +print(a // b) # 3 (整除) +print(a % b) # 1 (取模) +print(a ** b) # 1000 (次方) + +# 比較 +print(a == b) # False +print(a != b) # True +print(a > b) # True +print(a >= b) # True + +# 邏輯 +print(a > 5 and b < 5) # True +print(a > 5 or b > 5) # True +print(not (a == b)) # True +``` + +```go !! go +// Go - 運算子 +package main + +import ( + "fmt" + "math" +) + +func main() { + a, b := 10, 3 + + // 算術 + fmt.Println(a + b) // 13 (加法) + fmt.Println(a - b) // 7 (減法) + fmt.Println(a * b) // 30 (乘法) + fmt.Println(a / b) // 3 (整數除法) + fmt.Println(a % b) // 1 (取模) + fmt.Println(math.Pow(float64(a), float64(b))) // 1000 (次方) + + // 比較 (與 Python 相同) + fmt.Println(a == b) // false + fmt.Println(a != b) // true + fmt.Println(a > b) // true + fmt.Println(a >= b) // true + + // 邏輯 (不同語法) + fmt.Println(a > 5 && b < 5) // true (and) + fmt.Println(a > 5 || b > 5) // true (or) + fmt.Println(!(a == b)) // true (not) + + // 遞增/遞減 + i := 0 + i++ // i = 1 + i-- // i = 0 + // Go 中沒有 ++i 或 --i +} +``` + + +## 控制流基礎 + +### If 語句 + + +```python !! py +# Python - If 語句 +age = 20 + +if age >= 18: + print("Adult") +elif age >= 13: + print("Teenager") +else: + print("Child") + +# 使用賦值運算式 (Python 3.8+) +if (n := len(items)) > 10: + print(f"Too many items: {n}") +``` + +```go !! go +// Go - If 語句 +package main + +import "fmt" + +func main() { + age := 20 + + if age >= 18 { + fmt.Println("Adult") + } else if age >= 13 { // 注意:沒有 elif + fmt.Println("Teenager") + } else { + fmt.Println("Child") + } + + // 帶初始化語句 + if n := len(items); n > 10 { + fmt.Printf("Too many items: %d\n", n) + } + // n 在 if 區塊外不可存取 +} +``` + + +## 總結 + +在本模組中,你學習了: + +1. **變數宣告**: Python (動態) vs Go (帶類型推斷的靜態) +2. **基本類型**: 不同的類型系統和轉換 +3. **字串**: 相似的操作,不同的語法 +4. **零值**: Go 中未初始化變數的預設值 +5. **指標**: Python 開發者的新概念 +6. **集合**: 陣列/切片 vs 列表,映射 vs 字典 +7. **運算子**: 大部分相似,一些語法差異 +8. **控制流簡介**: 帶初始化的 If 語句 + +## 關鍵要點 + +- **Go 需要顯式類型** 但使用 `:=` 進行類型推斷 +- **無隱式類型轉換** - 必須顯式轉換 +- **零值** 意味著變數總是有預設值 +- **指標** 讓你對參考有顯式控制 +- **切片** 是動態陣列,**陣列**是固定大小 +- **映射** 類似 Python 字典 +- **使用 `:=` 進行短變數宣告** 在函式內部 (最常用) + +## 練習 + +1. 建立一個程式,宣告不同類型的變數 +2. 撰寫一個函式,接收指標並修改值 +3. 建立一個切片,新增元素,並對其進行切片 +4. 建構一個映射並示範 CRUD 操作 +5. 在不同數值類型之間轉換 +6. 使用帶初始化的 if 語句 + +## 下一步 + +在下一個模組中,我們將深入學習 Go 的控制流、迴圈和函式。 diff --git a/content/docs/py2go/module-02-control-flow.mdx b/content/docs/py2go/module-02-control-flow.mdx new file mode 100644 index 0000000..c64e7a4 --- /dev/null +++ b/content/docs/py2go/module-02-control-flow.mdx @@ -0,0 +1,604 @@ +--- +title: "Module 2: Control Flow and Loops" +description: "Master Go's control flow constructs compared to Python" +--- + +## Introduction + +This module covers control flow in Go, including loops, conditionals, and switches. While Python focuses on "one obvious way to do it," Go provides explicit constructs that make code clear and predictable. + +## Loops: The Only Loop - For + +Python has `for` and `while` loops. **Go only has `for`**, but it's versatile enough to handle all looping scenarios. + +### Basic For Loop + + +```python !! py +# Python - While loop +i = 0 +while i < 5: + print(i) + i += 1 + +# Python - For loop with range +for i in range(5): + print(i) + +# Python - For loop over iterable +items = ["a", "b", "c"] +for item in items: + print(item) +``` + +```go !! go +// Go - All use for loop +package main + +import "fmt" + +func main() { + // While-style loop + i := 0 + for i < 5 { + fmt.Println(i) + i++ + } + + // Traditional for loop + for i := 0; i < 5; i++ { + fmt.Println(i) + } + + // Range over slice + items := []string{"a", "b", "c"} + for i, item := range items { + fmt.Printf("%d: %s\n", i, item) + } + + // Ignore index with _ + for _, item := range items { + fmt.Println(item) + } +} +``` + + +### Infinite Loops + + +```python !! py +# Python - Infinite loop +while True: + print("Running...") + if should_stop(): + break +``` + +```go !! go +// Go - Infinite loop +for { + fmt.Println("Running...") + if shouldStop() { + break + } +} +``` + + +### Iterating Over Maps + + +```python !! py +# Python - Dict iteration +person = {"name": "Alice", "age": 30, "city": "NYC"} + +# Keys +for key in person: + print(key) + +# Keys and values +for key, value in person.items(): + print(f"{key}: {value}") +``` + +```go !! go +// Go - Map iteration +package main + +import "fmt" + +func main() { + person := map[string]string{ + "name": "Alice", + "age": "30", + "city": "NYC", + } + + // Keys and values (only option in Go) + for key, value := range person { + fmt.Printf("%s: %s\n", key, value) + } + + // Only keys + for key := range person { + fmt.Println(key) + } +} +``` + + +### Iterating Over Strings + + +```python !! py +# Python - String iteration +text = "Hello" +for char in text: + print(char) + +for i, char in enumerate(text): + print(f"{i}: {char}") +``` + +```go !! go +// Go - String iteration (by runes) +package main + +import "fmt" + +func main() { + text := "Hello" + + // By rune (character) + for i, rune := range text { + fmt.Printf("%d: %c\n", i, rune) + } + + // Only runes + for _, rune := range text { + fmt.Printf("%c\n", rune) + } +} +``` + + +### Loop Control: Break and Continue + + +```python !! py +# Python - Break and continue +for i in range(10): + if i == 3: + continue # Skip 3 + if i == 7: + break # Stop at 7 + print(i) +# Output: 0, 1, 2, 4, 5, 6 +``` + +```go !! go +// Go - Break and continue (same as Python) +package main + +import "fmt" + +func main() { + for i := 0; i < 10; i++ { + if i == 3 { + continue // Skip 3 + } + if i == 7 { + break // Stop at 7 + } + fmt.Println(i) + } + // Output: 0, 1, 2, 4, 5, 6 +} +``` + + +## If Statements + +### Basic If + + +```python !! py +# Python - If statement +age = 20 + +if age >= 18: + print("Adult") +elif age >= 13: + print("Teenager") +else: + print("Child") +``` + +```go !! go +// Go - If statement +package main + +import "fmt" + +func main() { + age := 20 + + if age >= 18 { + fmt.Println("Adult") + } else if age >= 13 { + fmt.Println("Teenager") + } else { + fmt.Println("Child") + } +} +``` + + +### If with Initialization + + +```python !! py +# Python 3.8+ - Walrus operator +if (n := len(items)) > 10: + print(f"Too many: {n}") +``` + +```go !! go +// Go - If with initialization +package main + +import "fmt" + +func main() { + items := []int{1, 2, 3, 4, 5} + + // Variable scoped to if block + if n := len(items); n > 10 { + fmt.Printf("Too many: %d\n", n) + } else { + fmt.Printf("Count: %d\n", n) + } + // n not accessible here +} +``` + + +### If with Multiple Conditions + + +```python !! py +# Python - Logical operators +age = 25 +has_id = True + +if age >= 18 and has_id: + print("Can enter") + +if age < 18 or not has_id: + print("Cannot enter") +``` + +```go !! go +// Go - Logical operators (different syntax) +package main + +import "fmt" + +func main() { + age := 25 + hasID := true + + if age >= 18 && hasID { + fmt.Println("Can enter") + } + + if age < 18 || !hasID { + fmt.Println("Cannot enter") + } +} +``` + + +## Switch Statements + +Go's switch is more powerful than Python's if-elif chains: + + +```python !! py +# Python - No switch statement (before 3.10), use if-elif +grade = "B" + +if grade == "A": + print("Excellent") +elif grade == "B": + print("Good") +elif grade == "C": + print("Average") +else: + print("Need improvement") + +# Python 3.10+ - Match statement +match grade: + case "A": + print("Excellent") + case "B": + print("Good") + case "C": + print("Average") + case _: + print("Need improvement") +``` + +```go !! go +// Go - Switch statement (no break needed!) +package main + +import "fmt" + +func main() { + grade := "B" + + switch grade { + case "A": + fmt.Println("Excellent") + case "B": + fmt.Println("Good") + case "C": + fmt.Println("Average") + default: + fmt.Println("Need improvement") + } + + // Switch with multiple values + day := 3 + switch day { + case 1, 2, 3, 4, 5: + fmt.Println("Weekday") + case 6, 7: + fmt.Println("Weekend") + } + + // Switch without condition (like if-else) + switch { + case age < 13: + fmt.Println("Child") + case age < 18: + fmt.Println("Teenager") + default: + fmt.Println("Adult") + } +} +``` + + +### Switch with Initialization + + +```python !! py +# Python +num = 42 +if num % 2 == 0: + print("Even") +else: + print("Odd") +``` + +```go !! go +// Go - Switch with initialization +package main + +import "fmt" + +func main() { + switch num := 42; num % 2 { + case 0: + fmt.Println("Even") + case 1: + fmt.Println("Odd") + } +} +``` + + +## Defer (Unique to Go) + +`defer` executes a statement after the surrounding function completes. Useful for cleanup: + + +```python !! py +# Python - try-finally +file = None +try: + file = open("data.txt", "r") + data = file.read() + process(data) +finally: + if file: + file.close() + +# Python - Context manager (preferred) +with open("data.txt", "r") as file: + data = file.read() + process(data) +# Automatically closed +``` + +```go !! go +// Go - Defer +package main + +import "fmt" + +func processData() { + // Deferred until function returns + defer fmt.Println("Cleanup: Closing resources") + + fmt.Println("Step 1: Opening file") + fmt.Println("Step 2: Reading data") + fmt.Println("Step 3: Processing") + + // Output order: + // Step 1 + // Step 2 + // Step 3 + // Cleanup: Closing resources +} + +func main() { + // Multiple defers (LIFO order) + defer fmt.Println("Third") + defer fmt.Println("Second") + defer fmt.Println("First") + + processData() +} +``` + + +### Defer with File Operations + + +```python !! py +# Python - Context manager +def read_file(filename): + with open(filename, 'r') as f: + return f.read() +``` + +```go !! go +// Go - Defer for cleanup +package main + +import ( + "fmt" + "os" +) + +func readFile(filename string) ([]byte, error) { + file, err := os.Open(filename) + if err != nil { + return nil, err + } + // Always close, even if error occurs + defer file.Close() + + data := make([]byte, 100) + n, err := file.Read(data) + if err != nil { + return nil, err + } + + return data[:n], nil +} + +func main() { + data, err := readFile("data.txt") + if err != nil { + fmt.Println("Error:", err) + return + } + fmt.Println(string(data)) +} +``` + + +## Common Patterns + +### Loop Until Condition + + +```python !! py +# Python +while True: + line = read_line() + if not line: + break + process(line) +``` + +```go !! go +// Go +for { + line := readLine() + if line == "" { + break + } + process(line) +} +``` + + +### Early Returns + + +```python !! py +# Python +def validate(data): + if not data: + return False + + if len(data) < 10: + return False + + if "invalid" in data: + return False + + return True +``` + +```go !! go +// Go - Idiomatic with early returns +func validate(data string) bool { + if data == "" { + return false + } + + if len(data) < 10 { + return false + } + + if strings.Contains(data, "invalid") { + return false + } + + return true +} +``` + + +## Summary + +In this module, you learned: + +1. **Go only has `for` loops** - versatile enough for all scenarios +2. **No `while`** - use `for` with condition only +3. **Range-based iteration** over arrays, slices, maps, strings +4. **If with initialization** - scope-limited variables +5. **Switch statements** - no `break` needed, more powerful than Python's +6. **Defer** - executes after function completes (LIFO order) +7. **Early returns** - idiomatic pattern in Go + +## Key Differences from Python + +| Python | Go | +|--------|-----| +| `while`, `for` loops | Only `for` loop | +| `for i in range()` | `for i := 0; i < n; i++` | +| `for item in items` | `for _, item := range items` | +| `elif` | `else if` | +| `and`, `or`, `not` | `&&`, `\|\|`, `!` | +| `with` statement | `defer` keyword | +| No `switch` (before 3.10) | Powerful `switch` | + +## Exercises + +1. Write a function that uses `defer` to measure function execution time +2. Create a switch statement that handles multiple cases +3. Use `range` to iterate over a map and print key-value pairs +4. Implement early return pattern for validation +5. Use `for` with initialization, condition, and post statement + +## Next Steps + +Next module: **Functions and Methods** - Learn about Go's function declarations, multiple return values, closures, and methods. diff --git a/content/docs/py2go/module-02-control-flow.zh-cn.mdx b/content/docs/py2go/module-02-control-flow.zh-cn.mdx new file mode 100644 index 0000000..4b180fe --- /dev/null +++ b/content/docs/py2go/module-02-control-flow.zh-cn.mdx @@ -0,0 +1,604 @@ +--- +title: "模块 2:控制流和循环" +description: "掌握 Go 的控制流构造,与 Python 对比" +--- + +## 简介 + +本模块涵盖 Go 中的控制流,包括循环、条件语句和 switch。虽然 Python 强调"只有一种显而易见的方式",但 Go 提供了显式的构造,使代码清晰且可预测。 + +## 循环:唯一的循环 - For + +Python 有 `for` 和 `while` 循环。**Go 只有 `for`**,但它足够通用,可以处理所有循环场景。 + +### 基本 For 循环 + + +```python !! py +# Python - While 循环 +i = 0 +while i < 5: + print(i) + i += 1 + +# Python - 带 range 的 For 循环 +for i in range(5): + print(i) + +# Python - 遍历可迭代对象的 For 循环 +items = ["a", "b", "c"] +for item in items: + print(item) +``` + +```go !! go +// Go - 都使用 for 循环 +package main + +import "fmt" + +func main() { + // While 风格循环 + i := 0 + for i < 5 { + fmt.Println(i) + i++ + } + + // 传统 for 循环 + for i := 0; i < 5; i++ { + fmt.Println(i) + } + + // 遍历 slice + items := []string{"a", "b", "c"} + for i, item := range items { + fmt.Printf("%d: %s\n", i, item) + } + + // 使用 _ 忽略索引 + for _, item := range items { + fmt.Println(item) + } +} +``` + + +### 无限循环 + + +```python !! py +# Python - 无限循环 +while True: + print("Running...") + if should_stop(): + break +``` + +```go !! go +// Go - 无限循环 +for { + fmt.Println("Running...") + if shouldStop() { + break + } +} +``` + + +### 遍历映射 + + +```python !! py +# Python - 字典迭代 +person = {"name": "Alice", "age": 30, "city": "NYC"} + +# 键 +for key in person: + print(key) + +# 键和值 +for key, value in person.items(): + print(f"{key}: {value}") +``` + +```go !! go +// Go - 映射迭代 +package main + +import "fmt" + +func main() { + person := map[string]string{ + "name": "Alice", + "age": "30", + "city": "NYC", + } + + // 键和值 (Go 中唯一的方式) + for key, value := range person { + fmt.Printf("%s: %s\n", key, value) + } + + // 仅键 + for key := range person { + fmt.Println(key) + } +} +``` + + +### 遍历字符串 + + +```python !! py +# Python - 字符串迭代 +text = "Hello" +for char in text: + print(char) + +for i, char in enumerate(text): + print(f"{i}: {char}") +``` + +```go !! go +// Go - 字符串迭代 (按 rune) +package main + +import "fmt" + +func main() { + text := "Hello" + + // 按 rune (字符) + for i, rune := range text { + fmt.Printf("%d: %c\n", i, rune) + } + + // 仅 rune + for _, rune := range text { + fmt.Printf("%c\n", rune) + } +} +``` + + +### 循环控制:Break 和 Continue + + +```python !! py +# Python - Break 和 continue +for i in range(10): + if i == 3: + continue # 跳过 3 + if i == 7: + break # 在 7 处停止 + print(i) +# 输出: 0, 1, 2, 4, 5, 6 +``` + +```go !! go +// Go - Break 和 continue (与 Python 相同) +package main + +import "fmt" + +func main() { + for i := 0; i < 10; i++ { + if i == 3 { + continue // 跳过 3 + } + if i == 7 { + break // 在 7 处停止 + } + fmt.Println(i) + } + // 输出: 0, 1, 2, 4, 5, 6 +} +``` + + +## If 语句 + +### 基本 If + + +```python !! py +# Python - If 语句 +age = 20 + +if age >= 18: + print("Adult") +elif age >= 13: + print("Teenager") +else: + print("Child") +``` + +```go !! go +// Go - If 语句 +package main + +import "fmt" + +func main() { + age := 20 + + if age >= 18 { + fmt.Println("Adult") + } else if age >= 13 { + fmt.Println("Teenager") + } else { + fmt.Println("Child") + } +} +``` + + +### 带初始化的 If + + +```python !! py +# Python 3.8+ - 海象运算符 +if (n := len(items)) > 10: + print(f"Too many: {n}") +``` + +```go !! go +// Go - 带初始化的 If +package main + +import "fmt" + +func main() { + items := []int{1, 2, 3, 4, 5} + + // 变量作用域限定在 if 块 + if n := len(items); n > 10 { + fmt.Printf("Too many: %d\n", n) + } else { + fmt.Printf("Count: %d\n", n) + } + // n 在这里不可访问 +} +``` + + +### 带多个条件的 If + + +```python !! py +# Python - 逻辑运算符 +age = 25 +has_id = True + +if age >= 18 and has_id: + print("Can enter") + +if age < 18 or not has_id: + print("Cannot enter") +``` + +```go !! go +// Go - 逻辑运算符 (不同语法) +package main + +import "fmt" + +func main() { + age := 25 + hasID := true + + if age >= 18 && hasID { + fmt.Println("Can enter") + } + + if age < 18 || !hasID { + fmt.Println("Cannot enter") + } +} +``` + + +## Switch 语句 + +Go 的 switch 比 Python 的 if-elif 链更强大: + + +```python !! py +# Python - 无 switch 语句 (3.10 之前),使用 if-elif +grade = "B" + +if grade == "A": + print("Excellent") +elif grade == "B": + print("Good") +elif grade == "C": + print("Average") +else: + print("Need improvement") + +# Python 3.10+ - Match 语句 +match grade: + case "A": + print("Excellent") + case "B": + print("Good") + case "C": + print("Average") + case _: + print("Need improvement") +``` + +```go !! go +// Go - Switch 语句 (不需要 break!) +package main + +import "fmt" + +func main() { + grade := "B" + + switch grade { + case "A": + fmt.Println("Excellent") + case "B": + fmt.Println("Good") + case "C": + fmt.Println("Average") + default: + fmt.Println("Need improvement") + } + + // 带多个值的 switch + day := 3 + switch day { + case 1, 2, 3, 4, 5: + fmt.Println("Weekday") + case 6, 7: + fmt.Println("Weekend") + } + + // 无条件的 switch (类似 if-else) + switch { + case age < 13: + fmt.Println("Child") + case age < 18: + fmt.Println("Teenager") + default: + fmt.Println("Adult") + } +} +``` + + +### 带初始化的 Switch + + +```python !! py +# Python +num = 42 +if num % 2 == 0: + print("Even") +else: + print("Odd") +``` + +```go !! go +// Go - 带初始化的 Switch +package main + +import "fmt" + +func main() { + switch num := 42; num % 2 { + case 0: + fmt.Println("Even") + case 1: + fmt.Println("Odd") + } +} +``` + + +## Defer (Go 独有) + +`defer` 在包围函数完成后执行语句。用于清理: + + +```python !! py +# Python - try-finally +file = None +try: + file = open("data.txt", "r") + data = file.read() + process(data) +finally: + if file: + file.close() + +# Python - 上下文管理器 (首选) +with open("data.txt", "r") as file: + data = file.read() + process(data) +# 自动关闭 +``` + +```go !! go +// Go - Defer +package main + +import "fmt" + +func processData() { + // 延迟到函数返回时执行 + defer fmt.Println("Cleanup: Closing resources") + + fmt.Println("Step 1: Opening file") + fmt.Println("Step 2: Reading data") + fmt.Println("Step 3: Processing") + + // 输出顺序: + // Step 1 + // Step 2 + // Step 3 + // Cleanup: Closing resources +} + +func main() { + // 多个 defer (LIFO 顺序) + defer fmt.Println("Third") + defer fmt.Println("Second") + defer fmt.Println("First") + + processData() +} +``` + + +### 用于文件操作的 Defer + + +```python !! py +# Python - 上下文管理器 +def read_file(filename): + with open(filename, 'r') as f: + return f.read() +``` + +```go !! go +// Go - Defer 用于清理 +package main + +import ( + "fmt" + "os" +) + +func readFile(filename string) ([]byte, error) { + file, err := os.Open(filename) + if err != nil { + return nil, err + } + // 总是关闭,即使发生错误 + defer file.Close() + + data := make([]byte, 100) + n, err := file.Read(data) + if err != nil { + return nil, err + } + + return data[:n], nil +} + +func main() { + data, err := readFile("data.txt") + if err != nil { + fmt.Println("Error:", err) + return + } + fmt.Println(string(data)) +} +``` + + +## 常见模式 + +### 循环直到条件 + + +```python !! py +# Python +while True: + line = read_line() + if not line: + break + process(line) +``` + +```go !! go +// Go +for { + line := readLine() + if line == "" { + break + } + process(line) +} +``` + + +### 提前返回 + + +```python !! py +# Python +def validate(data): + if not data: + return False + + if len(data) < 10: + return False + + if "invalid" in data: + return False + + return True +``` + +```go !! go +// Go - 惯用的提前返回 +func validate(data string) bool { + if data == "" { + return false + } + + if len(data) < 10 { + return false + } + + if strings.Contains(data, "invalid") { + return false + } + + return true +} +``` + + +## 总结 + +在本模块中,你学习了: + +1. **Go 只有 `for` 循环** - 足够通用以应对所有场景 +2. **没有 `while`** - 使用仅带条件的 `for` +3. **基于 range 的迭代** 遍历数组、切片、映射、字符串 +4. **带初始化的 If** - 作用域受限的变量 +5. **Switch 语句** - 不需要 `break`,比 Python 的更强大 +6. **Defer** - 在函数完成后执行 (LIFO 顺序) +7. **提前返回** - Go 中的惯用模式 + +## 与 Python 的主要差异 + +| Python | Go | +|--------|-----| +| `while`, `for` 循环 | 只有 `for` 循环 | +| `for i in range()` | `for i := 0; i < n; i++` | +| `for item in items` | `for _, item := range items` | +| `elif` | `else if` | +| `and`, `or`, `not` | `&&`, `\|\|`, `!` | +| `with` 语句 | `defer` 关键字 | +| 无 `switch` (3.10 之前) | 强大的 `switch` | + +## 练习 + +1. 编写一个使用 `defer` 测量函数执行时间的函数 +2. 创建一个处理多个情况的 switch 语句 +3. 使用 `range` 遍历映射并打印键值对 +4. 实现验证的提前返回模式 +5. 使用带初始化、条件和后置语句的 `for` + +## 下一步 + +下一模块:**函数和方法** - 学习 Go 的函数声明、多返回值、闭包和方法。 diff --git a/content/docs/py2go/module-02-control-flow.zh-tw.mdx b/content/docs/py2go/module-02-control-flow.zh-tw.mdx new file mode 100644 index 0000000..81417e4 --- /dev/null +++ b/content/docs/py2go/module-02-control-flow.zh-tw.mdx @@ -0,0 +1,604 @@ +--- +title: "模組 2:控制流和迴圈" +description: "掌握 Go 的控制流構造,與 Python 對比" +--- + +## 簡介 + +本模組涵蓋 Go 中的控制流,包括迴圈、條件語句和 switch。雖然 Python 強調「只有一種顯而易見的方式」,但 Go 提供了顯式的構造,使程式碼清晰且可預測。 + +## 迴圈:唯一的迴圈 - For + +Python 有 `for` 和 `while` 迴圈。**Go 只有 `for`**,但它足夠通用,可以處理所有迴圈場景。 + +### 基本 For 迴圈 + + +```python !! py +# Python - While 迴圈 +i = 0 +while i < 5: + print(i) + i += 1 + +# Python - 帶 range 的 For 迴圈 +for i in range(5): + print(i) + +# Python - 遍歷可迭代物件的 For 迴圈 +items = ["a", "b", "c"] +for item in items: + print(item) +``` + +```go !! go +// Go - 都使用 for 迴圈 +package main + +import "fmt" + +func main() { + // While 風格迴圈 + i := 0 + for i < 5 { + fmt.Println(i) + i++ + } + + // 傳統 for 迴圈 + for i := 0; i < 5; i++ { + fmt.Println(i) + } + + // 遍歷 slice + items := []string{"a", "b", "c"} + for i, item := range items { + fmt.Printf("%d: %s\n", i, item) + } + + // 使用 _ 忽略索引 + for _, item := range items { + fmt.Println(item) + } +} +``` + + +### 無限迴圈 + + +```python !! py +# Python - 無限迴圈 +while True: + print("Running...") + if should_stop(): + break +``` + +```go !! go +// Go - 無限迴圈 +for { + fmt.Println("Running...") + if shouldStop() { + break + } +} +``` + + +### 遍歷映射 + + +```python !! py +# Python - 字典迭代 +person = {"name": "Alice", "age": 30, "city": "NYC"} + +# 鍵 +for key in person: + print(key) + +# 鍵和值 +for key, value in person.items(): + print(f"{key}: {value}") +``` + +```go !! go +// Go - 映射迭代 +package main + +import "fmt" + +func main() { + person := map[string]string{ + "name": "Alice", + "age": "30", + "city": "NYC", + } + + // 鍵和值 (Go 中唯一的方式) + for key, value := range person { + fmt.Printf("%s: %s\n", key, value) + } + + // 僅鍵 + for key := range person { + fmt.Println(key) + } +} +``` + + +### 遍歷字串 + + +```python !! py +# Python - 字串迭代 +text = "Hello" +for char in text: + print(char) + +for i, char in enumerate(text): + print(f"{i}: {char}") +``` + +```go !! go +// Go - 字串迭代 (按 rune) +package main + +import "fmt" + +func main() { + text := "Hello" + + // 按 rune (字元) + for i, rune := range text { + fmt.Printf("%d: %c\n", i, rune) + } + + // 僅 rune + for _, rune := range text { + fmt.Printf("%c\n", rune) + } +} +``` + + +### 迴圈控制:Break 和 Continue + + +```python !! py +# Python - Break 和 continue +for i in range(10): + if i == 3: + continue # 跳過 3 + if i == 7: + break # 在 7 處停止 + print(i) +# 輸出: 0, 1, 2, 4, 5, 6 +``` + +```go !! go +// Go - Break 和 continue (與 Python 相同) +package main + +import "fmt" + +func main() { + for i := 0; i < 10; i++ { + if i == 3 { + continue // 跳過 3 + } + if i == 7 { + break // 在 7 處停止 + } + fmt.Println(i) + } + // 輸出: 0, 1, 2, 4, 5, 6 +} +``` + + +## If 語句 + +### 基本 If + + +```python !! py +# Python - If 語句 +age = 20 + +if age >= 18: + print("Adult") +elif age >= 13: + print("Teenager") +else: + print("Child") +``` + +```go !! go +// Go - If 語句 +package main + +import "fmt" + +func main() { + age := 20 + + if age >= 18 { + fmt.Println("Adult") + } else if age >= 13 { + fmt.Println("Teenager") + } else { + fmt.Println("Child") + } +} +``` + + +### 帶初始化的 If + + +```python !! py +# Python 3.8+ - 海象運算子 +if (n := len(items)) > 10: + print(f"Too many: {n}") +``` + +```go !! go +// Go - 帶初始化的 If +package main + +import "fmt" + +func main() { + items := []int{1, 2, 3, 4, 5} + + // 變數作用域限定在 if 區塊 + if n := len(items); n > 10 { + fmt.Printf("Too many: %d\n", n) + } else { + fmt.Printf("Count: %d\n", n) + } + // n 在這裡不可存取 +} +``` + + +### 帶多個條件的 If + + +```python !! py +# Python - 邏輯運算子 +age = 25 +has_id = True + +if age >= 18 and has_id: + print("Can enter") + +if age < 18 or not has_id: + print("Cannot enter") +``` + +```go !! go +// Go - 邏輯運算子 (不同語法) +package main + +import "fmt" + +func main() { + age := 25 + hasID := true + + if age >= 18 && hasID { + fmt.Println("Can enter") + } + + if age < 18 || !hasID { + fmt.Println("Cannot enter") + } +} +``` + + +## Switch 語句 + +Go 的 switch 比 Python 的 if-elif 鏈更強大: + + +```python !! py +# Python - 無 switch 語句 (3.10 之前),使用 if-elif +grade = "B" + +if grade == "A": + print("Excellent") +elif grade == "B": + print("Good") +elif grade == "C": + print("Average") +else: + print("Need improvement") + +# Python 3.10+ - Match 語句 +match grade: + case "A": + print("Excellent") + case "B": + print("Good") + case "C": + print("Average") + case _: + print("Need improvement") +``` + +```go !! go +// Go - Switch 語句 (不需要 break!) +package main + +import "fmt" + +func main() { + grade := "B" + + switch grade { + case "A": + fmt.Println("Excellent") + case "B": + fmt.Println("Good") + case "C": + fmt.Println("Average") + default: + fmt.Println("Need improvement") + } + + // 帶多個值的 switch + day := 3 + switch day { + case 1, 2, 3, 4, 5: + fmt.Println("Weekday") + case 6, 7: + fmt.Println("Weekend") + } + + // 無條件的 switch (類似 if-else) + switch { + case age < 13: + fmt.Println("Child") + case age < 18: + fmt.Println("Teenager") + default: + fmt.Println("Adult") + } +} +``` + + +### 帶初始化的 Switch + + +```python !! py +# Python +num = 42 +if num % 2 == 0: + print("Even") +else: + print("Odd") +``` + +```go !! go +// Go - 帶初始化的 Switch +package main + +import "fmt" + +func main() { + switch num := 42; num % 2 { + case 0: + fmt.Println("Even") + case 1: + fmt.Println("Odd") + } +} +``` + + +## Defer (Go 獨有) + +`defer` 在包圍函式完成後執行語句。用於清理: + + +```python !! py +# Python - try-finally +file = None +try: + file = open("data.txt", "r") + data = file.read() + process(data) +finally: + if file: + file.close() + +# Python - 上下文管理器 (首選) +with open("data.txt", "r") as file: + data = file.read() + process(data) +# 自動關閉 +``` + +```go !! go +// Go - Defer +package main + +import "fmt" + +func processData() { + // 延遲到函式返回時執行 + defer fmt.Println("Cleanup: Closing resources") + + fmt.Println("Step 1: Opening file") + fmt.Println("Step 2: Reading data") + fmt.Println("Step 3: Processing") + + // 輸出順序: + // Step 1 + // Step 2 + // Step 3 + // Cleanup: Closing resources +} + +func main() { + // 多個 defer (LIFO 順序) + defer fmt.Println("Third") + defer fmt.Println("Second") + defer fmt.Println("First") + + processData() +} +``` + + +### 用於檔案操作的 Defer + + +```python !! py +# Python - 上下文管理器 +def read_file(filename): + with open(filename, 'r') as f: + return f.read() +``` + +```go !! go +// Go - Defer 用於清理 +package main + +import ( + "fmt" + "os" +) + +func readFile(filename string) ([]byte, error) { + file, err := os.Open(filename) + if err != nil { + return nil, err + } + // 總是關閉,即使發生錯誤 + defer file.Close() + + data := make([]byte, 100) + n, err := file.Read(data) + if err != nil { + return nil, err + } + + return data[:n], nil +} + +func main() { + data, err := readFile("data.txt") + if err != nil { + fmt.Println("Error:", err) + return + } + fmt.Println(string(data)) +} +``` + + +## 常見模式 + +### 迴圈直到條件 + + +```python !! py +# Python +while True: + line = read_line() + if not line: + break + process(line) +``` + +```go !! go +// Go +for { + line := readLine() + if line == "" { + break + } + process(line) +} +``` + + +### 提前返回 + + +```python !! py +# Python +def validate(data): + if not data: + return False + + if len(data) < 10: + return False + + if "invalid" in data: + return False + + return True +``` + +```go !! go +// Go - 慣用的提前返回 +func validate(data string) bool { + if data == "" { + return false + } + + if len(data) < 10 { + return false + } + + if strings.Contains(data, "invalid") { + return false + } + + return true +} +``` + + +## 總結 + +在本模組中,你學習了: + +1. **Go 只有 `for` 迴圈** - 足夠通用以應對所有場景 +2. **沒有 `while`** - 使用僅帶條件的 `for` +3. **基於 range 的迭代** 遍歷陣列、切片、映射、字串 +4. **帶初始化的 If** - 作用域受限的變數 +5. **Switch 語句** - 不需要 `break`,比 Python 的更強大 +6. **Defer** - 在函式完成後執行 (LIFO 順序) +7. **提前返回** - Go 中的慣用模式 + +## 與 Python 的主要差異 + +| Python | Go | +|--------|-----| +| `while`, `for` 迴圈 | 只有 `for` 迴圈 | +| `for i in range()` | `for i := 0; i < n; i++` | +| `for item in items` | `for _, item := range items` | +| `elif` | `else if` | +| `and`, `or`, `not` | `&&`, `\|\|`, `!` | +| `with` 語句 | `defer` 關鍵字 | +| 無 `switch` (3.10 之前) | 強大的 `switch` | + +## 練習 + +1. 撰寫一個使用 `defer` 測量函式執行時間的函式 +2. 建立一個處理多個情況的 switch 語句 +3. 使用 `range` 遍歷映射並列印鍵值對 +4. 實作驗證的提前返回模式 +5. 使用帶初始化、條件和後置語句的 `for` + +## 下一步 + +下一模組:**函式和方法** - 學習 Go 的函式宣告、多返回值、閉包和方法。 diff --git a/content/docs/py2go/module-03-functions.mdx b/content/docs/py2go/module-03-functions.mdx new file mode 100644 index 0000000..2431a26 --- /dev/null +++ b/content/docs/py2go/module-03-functions.mdx @@ -0,0 +1,1231 @@ +--- +title: "Module 3: Functions and Methods" +description: "Understanding Go's function system compared to Python" +--- + +## Introduction + +Go's function system is similar to Python but with key differences: static typing, multiple return values, and explicit error handling. This module covers functions, methods, closures, and more in depth. + +## Basic Functions + +### Function Declaration Syntax + + +```python !! py +# Python - Function definition +def add(a, b): + return a + b + +def greet(name, greeting="Hello"): + return f"{greeting}, {name}!" + +def calculate(x, y, z): + result = x + y * z + return result + +# Call +result = add(5, 3) +message = greet("Alice") +message = greet("Bob", "Hi") +calc_result = calculate(1, 2, 3) +``` + +```go !! go +// Go - Function definition +package main + +import "fmt" + +// Basic function +func add(a int, b int) int { + return a + b +} + +// When parameters share type, can shorten +func addShort(a, b int) int { + return a + b +} + +func greet(name string, greeting string) string { + return fmt.Sprintf("%s, %s!", greeting, name) +} + +func calculate(x, y, z int) int { + result := x + y*z + return result +} + +// Multiple parameters of same type +func sum(a, b, c, d int) int { + return a + b + c + d +} + +func main() { + result := add(5, 3) + message := greet("Alice", "Hello") + calcResult := calculate(1, 2, 3) + total := sum(1, 2, 3, 4) + + fmt.Println(result) + fmt.Println(message) + fmt.Println(calcResult) + fmt.Println(total) +} +``` + + +### No Default Parameters + +Go doesn't have default parameters. Use multiple functions instead: + + +```python !! py +# Python - Default parameters +def connect(host, port=5432, timeout=30): + return f"Connected to {host}:{port}" + +# Call with defaults +connect("localhost") + +# Override some +connect("localhost", port=3306) + +# Override all +connect("localhost", 5432, 60) +``` + +```go !! go +// Go - No default parameters, use multiple functions +package main + +import "fmt" + +// Approach 1: Multiple functions +func Connect(host string) string { + return ConnectPort(host, 5432) +} + +func ConnectPort(host string, port int) string { + return ConnectTimeout(host, port, 30) +} + +func ConnectTimeout(host string, port int, timeout int) string { + return fmt.Sprintf("Connected to %s:%d (timeout: %d)", host, port, timeout) +} + +// Approach 2: Configuration struct +type Config struct { + Host string + Port int + Timeout int +} + +func ConnectWithConfig(config Config) string { + // Provide defaults + if config.Port == 0 { + config.Port = 5432 + } + if config.Timeout == 0 { + config.Timeout = 30 + } + + return fmt.Sprintf("Connected to %s:%d (timeout: %d)", + config.Host, config.Port, config.Timeout) +} + +// Approach 3: Functional options pattern +type Server struct { + host string + port int + timeout int +} + +type Option func(*Server) + +func WithPort(port int) Option { + return func(s *Server) { + s.port = port + } +} + +func WithTimeout(timeout int) Option { + return func(s *Server) { + s.timeout = timeout + } +} + +func NewServer(host string, opts ...Option) *Server { + s := &Server{ + host: host, + port: 5432, // default + timeout: 30, // default + } + + for _, opt := range opts { + opt(s) + } + + return s +} + +func main() { + // Multiple functions approach + fmt.Println(Connect("localhost")) + fmt.Println(ConnectPort("localhost", 3306)) + + // Config struct approach + config := Config{Host: "localhost"} + fmt.Println(ConnectWithConfig(config)) + + // Functional options approach + server := NewServer("localhost", WithPort(8080), WithTimeout(60)) + fmt.Printf("Server: %+v\n", server) +} +``` + + +## Multiple Return Values + +This is one of Go's most powerful features: + + +```python !! py +# Python - Return tuple +def divide(a, b): + if b == 0: + return None, "Division by zero" + return a / b, None + +result, error = divide(10, 2) +if error: + print(f"Error: {error}") +else: + print(f"Result: {result}") + +# Can ignore values with _ +result, _ = divide(10, 2) +``` + +```go !! go +// Go - Multiple return values (idiomatic) +package main + +import "fmt" + +func divide(a int, b int) (int, error) { + if b == 0 { + return 0, fmt.Errorf("division by zero") + } + return a / b, nil +} + +func main() { + result, err := divide(10, 2) + if err != nil { + fmt.Printf("Error: %v\n", err) + return + } + fmt.Printf("Result: %d\n", result) + + // Ignore error with blank identifier + result2, _ := divide(20, 4) + fmt.Printf("Result2: %d\n", result2) + + // Ignore result + _, err2 := divide(10, 0) + if err2 != nil { + fmt.Printf("Expected error: %v\n", err2) + } +} +``` + + +### Real-World Multiple Returns + + +```python !! py +# Python - HTTP request with multiple returns +import requests + +def fetch_user(user_id): + try: + response = requests.get(f"/api/users/{user_id}") + return response.json(), None + except requests.RequestException as e: + return None, str(e) + +user, error = fetch_user(123) +if error: + print(f"Failed: {error}") +else: + print(f"User: {user['name']}") +``` + +```go !! go +// Go - HTTP request with multiple returns +package main + +import ( + "encoding/json" + "fmt" + "net/http" +) + +type User struct { + ID int `json:"id"` + Name string `json:"name"` + Email string `json:"email"` +} + +func fetchUser(userID int) (*User, error) { + resp, err := http.Get(fmt.Sprintf("http://api.example.com/users/%d", userID)) + if err != nil { + return nil, fmt.Errorf("request failed: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("unexpected status: %d", resp.StatusCode) + } + + var user User + if err := json.NewDecoder(resp.Body).Decode(&user); err != nil { + return nil, fmt.Errorf("decode failed: %w", err) + } + + return &user, nil +} + +func main() { + user, err := fetchUser(123) + if err != nil { + fmt.Printf("Failed: %v\n", err) + return + } + + fmt.Printf("User: %s\n", user.Name) +} +``` + + +## Named Return Values + +Named return values make code clearer but should be used judiciously: + + +```python !! py +# Python - No named returns (but can return dict) +def calculate(a, b): + sum_result = a + b + product = a * b + difference = a - b + return { + 'sum': sum_result, + 'product': product, + 'difference': difference + } + +result = calculate(5, 3) +print(result['sum'], result['product']) +``` + +```go !! go +// Go - Named return values +package main + +import "fmt" + +// Named return values (creates variables sum and product) +func calculate(a int, b int) (sum int, product int) { + sum = a + b // No need to declare + product = a * b // No need to declare + return // Naked return (returns sum, product) +} + +// Named returns with different types +func analyze(numbers []int) (count int, sum int, average float64) { + for _, n := range numbers { + sum += n + count++ + } + if count > 0 { + average = float64(sum) / float64(count) + } + return // Returns count, sum, average +} + +// Using named returns with defer +func readFile(filename string) (content string, err error) { + defer func() { + if err != nil { + err = fmt.Errorf("readFile(%s): %w", filename, err) + } + }() + + data, err := os.ReadFile(filename) + if err != nil { + return "", err + } + + content = string(data) + return content, nil +} + +func main() { + s, p := calculate(5, 3) + fmt.Printf("Sum: %d, Product: %d\n", s, p) + + count, sum, avg := analyze([]int{1, 2, 3, 4, 5}) + fmt.Printf("Count: %d, Sum: %d, Average: %.2f\n", count, sum, avg) +} +``` + + +### When to Use Named Returns + + +```go +// GOOD: Named returns for clarity in short functions +func calculateRectangle(width, height int) (area int, perimeter int) { + area = width * height + perimeter = 2 * (width + height) + return +} + +// GOOD: Named returns when you need to modify in defer +func transaction(db *DB) (result string, err error) { + defer func() { + if err != nil { + err = fmt.Errorf("transaction failed: %w", err) + } + }() + // ... transaction logic + return +} + +// AVOID: Named returns in long functions (hard to track) +func complexOperation(data []string) (output string, err error) { + // Many lines of code... + // Hard to track where 'output' is set + return +} + +// PREFER: Regular returns in complex functions +func complexOperation(data []string) (string, error) { + output := process(data) + return output, nil +} +``` + + +## Variadic Functions + + +```python !! py +# Python - *args and **kwargs +def func(*args, **kwargs): + print("Args:", args) + print("Kwargs:", kwargs) + +func(1, 2, 3, name="Alice", age=30) + +# Can also pass list/dict +numbers = [1, 2, 3] +func(*numbers) +``` + +```go !! go +// Go - Variadic functions +package main + +import "fmt" + +// Basic variadic function +func sumAll(numbers ...int) int { + total := 0 + for _, num := range numbers { + total += num + } + return total +} + +// Mixed parameters (variadic must be last) +func greet(prefix string, names ...string) { + for _, name := range names { + fmt.Printf("%s %s\n", prefix, name) + } +} + +// Variadic with different types +func printAny(items ...interface{}) { + for _, item := range items { + fmt.Println(item) + } +} + +func main() { + // Direct call + total := sumAll(1, 2, 3, 4, 5) + fmt.Println("Total:", total) + + // With slice (spread with ...) + numbers := []int{10, 20, 30} + total = sumAll(numbers...) + fmt.Println("Total from slice:", total) + + // Mixed parameters + greet("Hello:", "Alice", "Bob", "Charlie") + + // Empty variadic + total = sumAll() + fmt.Println("Empty sum:", total) +} +``` + + +### Real-World Variadic Examples + + +```go +// SQL query builder +func Query(db *sql.DB, query string, args ...interface{}) (*sql.Rows, error) { + return db.Query(query, args...) +} + +// Logging +func Log(level string, messages ...string) { + fmt.Printf("[%s] %s\n", level, strings.Join(messages, " ")) +} + +// Error wrapping +func Wrap(err error, messages ...string) error { + msg := strings.Join(messages, ": ") + return fmt.Errorf("%s: %w", msg, err) +} + +// Usage +rows, err := Query(db, "SELECT * FROM users WHERE id = $1", userID) +Log("ERROR", "Database", "Connection failed") +err = Wrap(err, "Failed to fetch user", userID) +``` + + +## Closures and Anonymous Functions + + +```python !! py +# Python - Closures +def make_multiplier(factor): + def multiply(x): + return x * factor + return multiply + +times3 = make_multiplier(3) +print(times3(5)) # 15 + +# Lambda +add = lambda x, y: x + y +print(add(3, 4)) # 7 + +# Closure with state +def counter(): + count = 0 + def increment(): + nonlocal count + count += 1 + return count + return increment + +c = counter() +print(c()) # 1 +print(c()) # 2 +``` + +```go !! go +// Go - Closures +package main + +import "fmt" + +// Basic closure +func makeMultiplier(factor int) func(int) int { + return func(x int) int { + return x * factor + } +} + +// Closure with state +func counter() func() int { + count := 0 + return func() int { + count++ + return count + } +} + +// Closure with multiple values +func accumulator() (func(int), func() int) { + sum := 0 + add := func(x int) { + sum += x + } + get := func() int { + return sum + } + return add, get +} + +func main() { + // Basic closure + times3 := makeMultiplier(3) + fmt.Println(times3(5)) // 15 + + // Anonymous function + add := func(x, y int) int { + return x + y + } + fmt.Println(add(3, 4)) // 7 + + // IIFE (Immediately Invoked Function Expression) + result := func(x int) int { + return x * 2 + }(5) + fmt.Println(result) // 10 + + // Counter with state + c := counter() + fmt.Println(c()) // 1 + fmt.Println(c()) // 2 + fmt.Println(c()) // 3 + + // Multiple closures sharing state + addVal, getSum := accumulator() + addVal(10) + addVal(20) + fmt.Println(getSum()) // 30 +} +``` + + +### Common Closure Patterns + + +```go +// 1. Iterator pattern +func iterate(numbers []int) func() (int, bool) { + index := 0 + return func() (int, bool) { + if index >= len(numbers) { + return 0, false + } + val := numbers[index] + index++ + return val, true + } +} + +// Usage +it := iterate([]int{1, 2, 3}) +for val, ok := it(); ok; val, ok = it() { + fmt.Println(val) +} + +// 2. Memoization +func memoize(fn func(int) int) func(int) int { + cache := make(map[int]int) + return func(x int) int { + if val, exists := cache[x]; exists { + return val + } + result := fn(x) + cache[x] = result + return result + } +} + +var fib = memoize(func(n int) int { + if n < 2 { + return n + } + return fib(n-1) + fib(n-2) +}) + +// 3. Defer with closure +func process() { + defer func() { + fmt.Println("Cleanup done") + }() + fmt.Println("Processing") +} +``` + + +## Methods + +Go doesn't have classes, but has methods on any type: + + +```python !! py +# Python - Class methods +class Rectangle: + def __init__(self, width, height): + self.width = width + self.height = height + + def area(self): + return self.width * self.height + + def perimeter(self): + return 2 * (self.width + self.height) + + def scale(self, factor): + self.width *= factor + self.height *= factor + +rect = Rectangle(5, 3) +print(rect.area()) # 15 +rect.scale(2) +print(rect.area()) # 60 +``` + +```go !! go +// Go - Methods on types +package main + +import "fmt" + +type Rectangle struct { + width int + height int +} + +// Value receiver (doesn't modify original) +func (r Rectangle) Area() int { + return r.width * r.height +} + +func (r Rectangle) Perimeter() int { + return 2 * (r.width + r.height) +} + +// Pointer receiver (can modify) +func (r *Rectangle) Scale(factor int) { + r.width *= factor + r.height *= factor +} + +// Methods on non-struct types +type MyInt int + +func (m MyInt) Double() MyInt { + return m * 2 +} + +func main() { + rect := Rectangle{width: 5, height: 3} + fmt.Println(rect.Area()) // 15 + fmt.Println(rect.Perimeter()) // 16 + + rect.Scale(2) + fmt.Println(rect.Area()) // 60 + + // Method on non-struct + num := MyInt(5) + fmt.Println(num.Double()) // 10 +} +``` + + +### Value vs Pointer Receivers + + +```python !! py +# Python - Methods can modify object +class Counter: + def __init__(self): + self.count = 0 + + def increment(self): + self.count += 1 + + def get_count(self): + return self.count + +counter = Counter() +counter.increment() +print(counter.get_count()) # 1 +``` + +```go !! go +// Go - Choose receiver type carefully +package main + +import "fmt" + +type Counter struct { + count int +} + +// Value receiver (cannot modify) +func (c Counter) GetCount() int { + return c.count +} + +// Pointer receiver (can modify) +func (c *Counter) Increment() { + c.count++ +} + +// When to use pointer receivers: +// 1. Method needs to modify the receiver +// 2. Struct is large (avoids copying) +// 3. Consistency (if some methods use pointers, all should) + +func main() { + counter := Counter{count: 0} + + // Both work + counter.Increment() + fmt.Println(counter.GetCount()) // 1 + + // Can call pointer receiver method on value + // (Go automatically takes address) + c := Counter{} + c.Increment() // Automatically converted to (&c).Increment() +} +``` + + +### Method Sets and Interfaces + + +```go +// Value receiver methods are in the value method set +type ValueInterface interface { + Method() // Can be called with value or pointer +} + +// Pointer receiver methods are only in pointer method set +type PointerInterface interface { + Method() // Can ONLY be called with pointer +} + +// Example +type User struct { + Name string +} + +func (u User) GetName() string { + return u.Name +} + +func (u *User) SetName(name string) { + u.Name = name +} + +func processUser(u User) { + // Can call GetName on value + fmt.Println(u.GetName()) + + // CANNOT call SetName on value + // u.SetName("Bob") // Compilation error! +} + +func processUserPtr(u *User) { + // Can call both methods on pointer + fmt.Println(u.GetName()) + u.SetName("Bob") // OK! +} +``` + + +## Higher-Order Functions + + +```python !! py +# Python - Map, filter, reduce +numbers = [1, 2, 3, 4, 5] + +# Map +doubled = list(map(lambda x: x * 2, numbers)) + +# Filter +evens = list(filter(lambda x: x % 2 == 0, numbers)) + +# Reduce +from functools import reduce +total = reduce(lambda x, y: x + y, numbers) + +# List comprehension (more Pythonic) +doubled = [x * 2 for x in numbers] +evens = [x for x in numbers if x % 2 == 0] +``` + +```go !! go +// Go - Higher-order functions +package main + +import "fmt" + +func mapInts(nums []int, f func(int) int) []int { + result := make([]int, len(nums)) + for i, num := range nums { + result[i] = f(num) + } + return result +} + +func filterInts(nums []int, f func(int) bool) []int { + result := []int{} + for _, num := range nums { + if f(num) { + result = append(result, num) + } + } + return result +} + +func reduceInts(nums []int, initial int, f func(int, int) int) int { + result := initial + for _, num := range nums { + result = f(result, num) + } + return result +} + +func main() { + numbers := []int{1, 2, 3, 4, 5} + + // Map + doubled := mapInts(numbers, func(x int) int { + return x * 2 + }) + fmt.Println("Doubled:", doubled) // [2 4 6 8 10] + + // Filter + evens := filterInts(numbers, func(x int) bool { + return x%2 == 0 + }) + fmt.Println("Evens:", evens) // [2 4] + + // Reduce + sum := reduceInts(numbers, 0, func(acc, val int) int { + return acc + val + }) + fmt.Println("Sum:", sum) // 15 + + // Chaining + result := reduceInts( + filterInts( + mapInts(numbers, func(x int) int { return x * 2 }), + func(x int) bool { return x > 4 }, + ), + 0, + func(acc, val int) int { return acc + val }, + ) + fmt.Println("Chained:", result) // 6 + 8 + 10 = 24 +} +``` + + +## Defer in Functions + + +```python !! py +# Python - Context managers +def process_file(filename): + with open(filename) as f: + data = f.read() + return process(data) + +# Multiple cleanup +def process(): + with open("file1.txt") as f1: + with open("file2.txt") as f2: + process_both(f1, f2) +``` + +```go !! go +// Go - Defer in functions +package main + +import ( + "fmt" + "os" +) + +// Basic defer +func processFile(filename string) error { + file, err := os.Open(filename) + if err != nil { + return err + } + defer file.Close() // Executed when function returns + + // Process file... + return nil +} + +// Multiple defers (LIFO order) +func processMultiple() { + defer fmt.Println("Third") + defer fmt.Println("Second") + defer fmt.Println("First") + // Output: First, Second, Third +} + +// Defer with arguments +func deferWithArgs() { + i := 0 + defer fmt.Println(i) // Prints 0 (evaluated now) + i = 10 +} + +// Defer for cleanup +func connectDatabase() (*sql.DB, error) { + db, err := sql.Open("driver", "dsn") + if err != nil { + return nil, err + } + + // Defer rollback if not committed + var tx *sql.Tx + defer func() { + if tx != nil { + tx.Rollback() + } + }() + + tx, err = db.Begin() + if err != nil { + return nil, err + } + + // ... use tx ... + + if err := tx.Commit(); err != nil { + return nil, err + } + tx = nil // Don't rollback + + return db, nil +} +``` + + +### Common Defer Patterns + + +```go +// 1. Unlock mutex +func process(data map[string]int) { + mu.Lock() + defer mu.Unlock() + + data["key"] = 42 + // Automatically unlocked +} + +// 2. Close response body +func fetch(url string) error { + resp, err := http.Get(url) + if err != nil { + return err + } + defer resp.Body.Close() + + // Process response... + return nil +} + +// 3. Recover from panic +func safeOperation() (err error) { + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf("panic: %v", r) + } + }() + + // Risky operation... + return nil +} + +// 4. Measure execution time +func timedOperation() { + defer func(start time.Time) { + fmt.Printf(" Took %v\n", time.Since(start)) + }(time.Now()) + + // Operation... +} + +// 5. Resource cleanup +func processFiles(filenames []string) error { + files := make([]*os.File, 0, len(filenames)) + + for _, filename := range filenames { + file, err := os.Open(filename) + if err != nil { + return err + } + files = append(files, file) + } + + // Close all files on return + defer func() { + for _, file := range files { + file.Close() + } + }() + + // Process files... + return nil +} +``` + + +## Panic and Recover + + +```python !! py +# Python - Try/except +def risky_operation(): + try: + result = divide(10, 0) + return result + except ZeroDivisionError as e: + print(f"Caught: {e}") + return None + finally: + print("Cleanup always runs") + +def divide(a, b): + if b == 0: + raise ZeroDivisionError("Cannot divide by zero") + return a / b +``` + +```go !! go +// Go - Panic/recover (rarely used, prefer errors) +package main + +import ( + "fmt" + "log" +) + +// Panic is like raise, recover is like except +func riskyOperation() (result int) { + // defer with recover + defer func() { + if r := recover(); r != nil { + log.Printf("Recovered from panic: %v", r) + result = 0 + } + }() + + result = divide(10, 0) + return result +} + +func divide(a, b int) int { + if b == 0 { + panic("cannot divide by zero") // Like raise + } + return a / b +} + +// Recovering in main +func main() { + defer func() { + if r := recover(); r != nil { + fmt.Printf("Main recovered: %v\n", r) + } + }() + + riskyOperation() +} +``` + + +### When to Use Panic + + +```go +// DON'T: Use panic for normal errors +func getUser(id int) (*User, error) { + if id < 0 { + panic("invalid id") // BAD! + } + // ... +} + +// DO: Return error +func getUser(id int) (*User, error) { + if id < 0 { + return nil, fmt.Errorf("invalid id: %d", id) + } + // ... +} + +// OK: Panic for truly unrecoverable conditions +func init() { + if apiKey == "" { + panic("API_KEY environment variable required") + } +} + +// OK: Panic in programmer error +func process(data []int) int { + if len(data) == 0 { + panic("process called with empty slice") // Programmer error + } + // ... +} +``` + + +## Summary + +In this module, you learned: + +1. **Function declarations** with typed parameters +2. **No default parameters** - use multiple functions or functional options +3. **Multiple return values** - Go's superpower for error handling +4. **Named return values** - use for clarity in simple functions +5. **Variadic functions** with `...` for flexible arguments +6. **Closures** - anonymous functions capturing context +7. **Methods** - functions on any type (no classes needed) +8. **Value vs pointer receivers** - when to use each +9. **Higher-order functions** - functions as parameters +10. **Defer** - guaranteed cleanup in LIFO order +11. **Panic/recover** - only for exceptional cases + +## Key Differences from Python + +| Python | Go | +|--------|-----| +| Dynamic parameters | Typed parameters | +| Default parameters | Multiple functions or options pattern | +| Single return (or tuple) | Multiple returns common | +| `lambda x: x+1` | `func(x int) int { return x+1 }` | +| Class methods | Methods on any type | +| `try/except/finally` | Error returns + panic/recover | +| List comprehensions | Higher-order functions (but loops are common) | +| Context managers | `defer` statements | + +## Exercises + +1. Write a function that returns both the quotient and remainder of division +2. Implement the functional options pattern for a Server configuration +3. Create a closure that maintains state (counter, accumulator) +4. Implement both value and pointer receivers on a custom struct +5. Write variadic functions for `min()`, `max()`, and `sum()` +6. Use `defer` to measure function execution time +7. Create a memoization helper with closures +8. Implement `map`, `filter`, and `reduce` functions + +## Next Steps + +Next module: **Structs and Interfaces** - Go's approach to object-oriented programming without classes. diff --git a/content/docs/py2go/module-03-functions.zh-cn.mdx b/content/docs/py2go/module-03-functions.zh-cn.mdx new file mode 100644 index 0000000..b8583ba --- /dev/null +++ b/content/docs/py2go/module-03-functions.zh-cn.mdx @@ -0,0 +1,1231 @@ +--- +title: "模块 3:函数和方法" +description: "理解 Go 的函数系统,与 Python 对比" +--- + +## 简介 + +Go 的函数系统与 Python 相似,但有关键差异:静态类型、多返回值和显式错误处理。本模块深入涵盖函数、方法、闭包等。 + +## 基本函数 + +### 函数声明语法 + + +```python !! py +# Python - 函数定义 +def add(a, b): + return a + b + +def greet(name, greeting="Hello"): + return f"{greeting}, {name}!" + +def calculate(x, y, z): + result = x + y * z + return result + +# 调用 +result = add(5, 3) +message = greet("Alice") +message = greet("Bob", "Hi") +calc_result = calculate(1, 2, 3) +``` + +```go !! go +// Go - 函数定义 +package main + +import "fmt" + +// 基本函数 +func add(a int, b int) int { + return a + b +} + +// 当参数共享类型时,可以简写 +func addShort(a, b int) int { + return a + b +} + +func greet(name string, greeting string) string { + return fmt.Sprintf("%s, %s!", greeting, name) +} + +func calculate(x, y, z int) int { + result := x + y*z + return result +} + +// 相同类型的多个参数 +func sum(a, b, c, d int) int { + return a + b + c + d +} + +func main() { + result := add(5, 3) + message := greet("Alice", "Hello") + calcResult := calculate(1, 2, 3) + total := sum(1, 2, 3, 4) + + fmt.Println(result) + fmt.Println(message) + fmt.Println(calcResult) + fmt.Println(total) +} +``` + + +### 无默认参数 + +Go 没有默认参数。使用多个函数代替: + + +```python !! py +# Python - 默认参数 +def connect(host, port=5432, timeout=30): + return f"Connected to {host}:{port}" + +# 使用默认值调用 +connect("localhost") + +# 覆盖部分 +connect("localhost", port=3306) + +# 覆盖全部 +connect("localhost", 5432, 60) +``` + +```go !! go +// Go - 无默认参数,使用多个函数 +package main + +import "fmt" + +// 方法 1: 多个函数 +func Connect(host string) string { + return ConnectPort(host, 5432) +} + +func ConnectPort(host string, port int) string { + return ConnectTimeout(host, port, 30) +} + +func ConnectTimeout(host string, port int, timeout int) string { + return fmt.Sprintf("Connected to %s:%d (timeout: %d)", host, port, timeout) +} + +// 方法 2: 配置结构体 +type Config struct { + Host string + Port int + Timeout int +} + +func ConnectWithConfig(config Config) string { + // 提供默认值 + if config.Port == 0 { + config.Port = 5432 + } + if config.Timeout == 0 { + config.Timeout = 30 + } + + return fmt.Sprintf("Connected to %s:%d (timeout: %d)", + config.Host, config.Port, config.Timeout) +} + +// 方法 3: 函数式选项模式 +type Server struct { + host string + port int + timeout int +} + +type Option func(*Server) + +func WithPort(port int) Option { + return func(s *Server) { + s.port = port + } +} + +func WithTimeout(timeout int) Option { + return func(s *Server) { + s.timeout = timeout + } +} + +func NewServer(host string, opts ...Option) *Server { + s := &Server{ + host: host, + port: 5432, // 默认值 + timeout: 30, // 默认值 + } + + for _, opt := range opts { + opt(s) + } + + return s +} + +func main() { + // 多个函数方法 + fmt.Println(Connect("localhost")) + fmt.Println(ConnectPort("localhost", 3306)) + + // 配置结构体方法 + config := Config{Host: "localhost"} + fmt.Println(ConnectWithConfig(config)) + + // 函数式选项方法 + server := NewServer("localhost", WithPort(8080), WithTimeout(60)) + fmt.Printf("Server: %+v\n", server) +} +``` + + +## 多返回值 + +这是 Go 最强大的特性之一: + + +```python !! py +# Python - 返回元组 +def divide(a, b): + if b == 0: + return None, "Division by zero" + return a / b, None + +result, error = divide(10, 2) +if error: + print(f"Error: {error}") +else: + print(f"Result: {result}") + +# 可以用 _ 忽略值 +result, _ = divide(10, 2) +``` + +```go !! go +// Go - 多返回值 (惯用) +package main + +import "fmt" + +func divide(a int, b int) (int, error) { + if b == 0 { + return 0, fmt.Errorf("division by zero") + } + return a / b, nil +} + +func main() { + result, err := divide(10, 2) + if err != nil { + fmt.Printf("Error: %v\n", err) + return + } + fmt.Printf("Result: %d\n", result) + + // 使用空白标识符忽略错误 + result2, _ := divide(20, 4) + fmt.Printf("Result2: %d\n", result2) + + // 忽略结果 + _, err2 := divide(10, 0) + if err2 != nil { + fmt.Printf("Expected error: %v\n", err2) + } +} +``` + + +### 真实世界的多返回值 + + +```python !! py +# Python - 带多返回值的 HTTP 请求 +import requests + +def fetch_user(user_id): + try: + response = requests.get(f"/api/users/{user_id}") + return response.json(), None + except requests.RequestException as e: + return None, str(e) + +user, error = fetch_user(123) +if error: + print(f"Failed: {error}") +else: + print(f"User: {user['name']}") +``` + +```go !! go +// Go - 带多返回值的 HTTP 请求 +package main + +import ( + "encoding/json" + "fmt" + "net/http" +) + +type User struct { + ID int `json:"id"` + Name string `json:"name"` + Email string `json:"email"` +} + +func fetchUser(userID int) (*User, error) { + resp, err := http.Get(fmt.Sprintf("http://api.example.com/users/%d", userID)) + if err != nil { + return nil, fmt.Errorf("request failed: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("unexpected status: %d", resp.StatusCode) + } + + var user User + if err := json.NewDecoder(resp.Body).Decode(&user); err != nil { + return nil, fmt.Errorf("decode failed: %w", err) + } + + return &user, nil +} + +func main() { + user, err := fetchUser(123) + if err != nil { + fmt.Printf("Failed: %v\n", err) + return + } + + fmt.Printf("User: %s\n", user.Name) +} +``` + + +## 命名返回值 + +命名返回值使代码更清晰,但应谨慎使用: + + +```python !! py +# Python - 无命名返回值 (但可以返回字典) +def calculate(a, b): + sum_result = a + b + product = a * b + difference = a - b + return { + 'sum': sum_result, + 'product': product, + 'difference': difference + } + +result = calculate(5, 3) +print(result['sum'], result['product']) +``` + +```go !! go +// Go - 命名返回值 +package main + +import "fmt" + +// 命名返回值 (创建变量 sum 和 product) +func calculate(a int, b int) (sum int, product int) { + sum = a + b // 无需声明 + product = a * b // 无需声明 + return // 裸返回 (返回 sum, product) +} + +// 不同类型的命名返回值 +func analyze(numbers []int) (count int, sum int, average float64) { + for _, n := range numbers { + sum += n + count++ + } + if count > 0 { + average = float64(sum) / float64(count) + } + return // 返回 count, sum, average +} + +// 在 defer 中使用命名返回值 +func readFile(filename string) (content string, err error) { + defer func() { + if err != nil { + err = fmt.Errorf("readFile(%s): %w", filename, err) + } + }() + + data, err := os.ReadFile(filename) + if err != nil { + return "", err + } + + content = string(data) + return content, nil +} + +func main() { + s, p := calculate(5, 3) + fmt.Printf("Sum: %d, Product: %d\n", s, p) + + count, sum, avg := analyze([]int{1, 2, 3, 4, 5}) + fmt.Printf("Count: %d, Sum: %d, Average: %.2f\n", count, sum, avg) +} +``` + + +### 何时使用命名返回值 + + +```go +// 好: 命名返回值用于简短函数的清晰性 +func calculateRectangle(width, height int) (area int, perimeter int) { + area = width * height + perimeter = 2 * (width + height) + return +} + +// 好: 命名返回值当需要在 defer 中修改时 +func transaction(db *DB) (result string, err error) { + defer func() { + if err != nil { + err = fmt.Errorf("transaction failed: %w", err) + } + }() + // ... 事务逻辑 + return +} + +// 避免: 在长函数中使用命名返回值 (难以跟踪) +func complexOperation(data []string) (output string, err error) { + // 许多行代码... + // 难以跟踪 'output' 在哪里设置 + return +} + +// 首选: 在复杂函数中使用常规返回值 +func complexOperation(data []string) (string, error) { + output := process(data) + return output, nil +} +``` + + +## 可变参数函数 + + +```python !! py +# Python - *args 和 **kwargs +def func(*args, **kwargs): + print("Args:", args) + print("Kwargs:", kwargs) + +func(1, 2, 3, name="Alice", age=30) + +# 也可以传递列表/字典 +numbers = [1, 2, 3] +func(*numbers) +``` + +```go !! go +// Go - 可变参数函数 +package main + +import "fmt" + +// 基本可变参数函数 +func sumAll(numbers ...int) int { + total := 0 + for _, num := range numbers { + total += num + } + return total +} + +// 混合参数 (可变参数必须在最后) +func greet(prefix string, names ...string) { + for _, name := range names { + fmt.Printf("%s %s\n", prefix, name) + } +} + +// 不同类型的可变参数 +func printAny(items ...interface{}) { + for _, item := range items { + fmt.Println(item) + } +} + +func main() { + // 直接调用 + total := sumAll(1, 2, 3, 4, 5) + fmt.Println("Total:", total) + + // 使用 slice (用 ... 展开) + numbers := []int{10, 20, 30} + total = sumAll(numbers...) + fmt.Println("Total from slice:", total) + + // 混合参数 + greet("Hello:", "Alice", "Bob", "Charlie") + + // 空可变参数 + total = sumAll() + fmt.Println("Empty sum:", total) +} +``` + + +### 真实世界的可变参数示例 + + +```go +// SQL 查询构建器 +func Query(db *sql.DB, query string, args ...interface{}) (*sql.Rows, error) { + return db.Query(query, args...) +} + +// 日志 +func Log(level string, messages ...string) { + fmt.Printf("[%s] %s\n", level, strings.Join(messages, " ")) +} + +// 错误包装 +func Wrap(err error, messages ...string) error { + msg := strings.Join(messages, ": ") + return fmt.Errorf("%s: %w", msg, err) +} + +// 使用 +rows, err := Query(db, "SELECT * FROM users WHERE id = $1", userID) +Log("ERROR", "Database", "Connection failed") +err = Wrap(err, "Failed to fetch user", userID) +``` + + +## 闭包和匿名函数 + + +```python !! py +# Python - 闭包 +def make_multiplier(factor): + def multiply(x): + return x * factor + return multiply + +times3 = make_multiplier(3) +print(times3(5)) # 15 + +# Lambda +add = lambda x, y: x + y +print(add(3, 4)) # 7 + +# 带状态的闭包 +def counter(): + count = 0 + def increment(): + nonlocal count + count += 1 + return count + return increment + +c = counter() +print(c()) # 1 +print(c()) # 2 +``` + +```go !! go +// Go - 闭包 +package main + +import "fmt" + +// 基本闭包 +func makeMultiplier(factor int) func(int) int { + return func(x int) int { + return x * factor + } +} + +// 带状态的闭包 +func counter() func() int { + count := 0 + return func() int { + count++ + return count + } +} + +// 带多个值的闭包 +func accumulator() (func(int), func() int) { + sum := 0 + add := func(x int) { + sum += x + } + get := func() int { + return sum + } + return add, get +} + +func main() { + // 基本闭包 + times3 := makeMultiplier(3) + fmt.Println(times3(5)) // 15 + + // 匿名函数 + add := func(x, y int) int { + return x + y + } + fmt.Println(add(3, 4)) // 7 + + // IIFE (立即调用函数表达式) + result := func(x int) int { + return x * 2 + }(5) + fmt.Println(result) // 10 + + // 带状态的计数器 + c := counter() + fmt.Println(c()) // 1 + fmt.Println(c()) // 2 + fmt.Println(c()) // 3 + + // 共享状态的多个闭包 + addVal, getSum := accumulator() + addVal(10) + addVal(20) + fmt.Println(getSum()) // 30 +} +``` + + +### 常见闭包模式 + + +```go +// 1. 迭代器模式 +func iterate(numbers []int) func() (int, bool) { + index := 0 + return func() (int, bool) { + if index >= len(numbers) { + return 0, false + } + val := numbers[index] + index++ + return val, true + } +} + +// 使用 +it := iterate([]int{1, 2, 3}) +for val, ok := it(); ok; val, ok = it() { + fmt.Println(val) +} + +// 2. 记忆化 +func memoize(fn func(int) int) func(int) int { + cache := make(map[int]int) + return func(x int) int { + if val, exists := cache[x]; exists { + return val + } + result := fn(x) + cache[x] = result + return result + } +} + +var fib = memoize(func(n int) int { + if n < 2 { + return n + } + return fib(n-1) + fib(n-2) +}) + +// 3. Defer 与闭包 +func process() { + defer func() { + fmt.Println("Cleanup done") + }() + fmt.Println("Processing") +} +``` + + +## 方法 + +Go 没有类,但在任何类型上都有方法: + + +```python !! py +# Python - 类方法 +class Rectangle: + def __init__(self, width, height): + self.width = width + self.height = height + + def area(self): + return self.width * self.height + + def perimeter(self): + return 2 * (self.width + self.height) + + def scale(self, factor): + self.width *= factor + self.height *= factor + +rect = Rectangle(5, 3) +print(rect.area()) # 15 +rect.scale(2) +print(rect.area()) # 60 +``` + +```go !! go +// Go - 类型上的方法 +package main + +import "fmt" + +type Rectangle struct { + width int + height int +} + +// 值接收者 (不修改原始值) +func (r Rectangle) Area() int { + return r.width * r.height +} + +func (r Rectangle) Perimeter() int { + return 2 * (r.width + r.height) +} + +// 指针接收者 (可以修改) +func (r *Rectangle) Scale(factor int) { + r.width *= factor + r.height *= factor +} + +// 非结构体类型上的方法 +type MyInt int + +func (m MyInt) Double() MyInt { + return m * 2 +} + +func main() { + rect := Rectangle{width: 5, height: 3} + fmt.Println(rect.Area()) // 15 + fmt.Println(rect.Perimeter()) // 16 + + rect.Scale(2) + fmt.Println(rect.Area()) // 60 + + // 非结构体上的方法 + num := MyInt(5) + fmt.Println(num.Double()) // 10 +} +``` + + +### 值 vs 指针接收者 + + +```python !! py +# Python - 方法可以修改对象 +class Counter: + def __init__(self): + self.count = 0 + + def increment(self): + self.count += 1 + + def get_count(self): + return self.count + +counter = Counter() +counter.increment() +print(counter.get_count()) # 1 +``` + +```go !! go +// Go - 仔细选择接收者类型 +package main + +import "fmt" + +type Counter struct { + count int +} + +// 值接收者 (不能修改) +func (c Counter) GetCount() int { + return c.count +} + +// 指针接收者 (可以修改) +func (c *Counter) Increment() { + c.count++ +} + +// 何时使用指针接收者: +// 1. 方法需要修改接收者 +// 2. 结构体很大 (避免复制) +// 3. 一致性 (如果某些方法使用指针,所有方法都应该使用) + +func main() { + counter := Counter{count: 0} + + // 两者都工作 + counter.Increment() + fmt.Println(counter.GetCount()) // 1 + + // 可以在值上调用指针接收者方法 + // (Go 自动获取地址) + c := Counter{} + c.Increment() // 自动转换为 (&c).Increment() +} +``` + + +### 方法集和接口 + + +```go +// 值接收者方法在值方法集中 +type ValueInterface interface { + Method() // 可以用值或指针调用 +} + +// 指针接收者方法仅在指针方法集中 +type PointerInterface interface { + Method() // 只能用指针调用 +} + +// 示例 +type User struct { + Name string +} + +func (u User) GetName() string { + return u.Name +} + +func (u *User) SetName(name string) { + u.Name = name +} + +func processUser(u User) { + // 可以在值上调用 GetName + fmt.Println(u.GetName()) + + // 不能在值上调用 SetName + // u.SetName("Bob") // 编译错误! +} + +func processUserPtr(u *User) { + // 可以在指针上调用两个方法 + fmt.Println(u.GetName()) + u.SetName("Bob") // OK! +} +``` + + +## 高阶函数 + + +```python !! py +# Python - Map, filter, reduce +numbers = [1, 2, 3, 4, 5] + +# Map +doubled = list(map(lambda x: x * 2, numbers)) + +# Filter +evens = list(filter(lambda x: x % 2 == 0, numbers)) + +# Reduce +from functools import reduce +total = reduce(lambda x, y: x + y, numbers) + +# 列表推导 (更 Pythonic) +doubled = [x * 2 for x in numbers] +evens = [x for x in numbers if x % 2 == 0] +``` + +```go !! go +// Go - 高阶函数 +package main + +import "fmt" + +func mapInts(nums []int, f func(int) int) []int { + result := make([]int, len(nums)) + for i, num := range nums { + result[i] = f(num) + } + return result +} + +func filterInts(nums []int, f func(int) bool) []int { + result := []int{} + for _, num := range nums { + if f(num) { + result = append(result, num) + } + } + return result +} + +func reduceInts(nums []int, initial int, f func(int, int) int) int { + result := initial + for _, num := range nums { + result = f(result, num) + } + return result +} + +func main() { + numbers := []int{1, 2, 3, 4, 5} + + // Map + doubled := mapInts(numbers, func(x int) int { + return x * 2 + }) + fmt.Println("Doubled:", doubled) // [2 4 6 8 10] + + // Filter + evens := filterInts(numbers, func(x int) bool { + return x%2 == 0 + }) + fmt.Println("Evens:", evens) // [2 4] + + // Reduce + sum := reduceInts(numbers, 0, func(acc, val int) int { + return acc + val + }) + fmt.Println("Sum:", sum) // 15 + + // 链式 + result := reduceInts( + filterInts( + mapInts(numbers, func(x int) int { return x * 2 }), + func(x int) bool { return x > 4 }, + ), + 0, + func(acc, val int) int { return acc + val }, + ) + fmt.Println("Chained:", result) // 6 + 8 + 10 = 24 +} +``` + + +## 函数中的 Defer + + +```python !! py +# Python - 上下文管理器 +def process_file(filename): + with open(filename) as f: + data = f.read() + return process(data) + +# 多个清理 +def process(): + with open("file1.txt") as f1: + with open("file2.txt") as f2: + process_both(f1, f2) +``` + +```go !! go +// Go - 函数中的 Defer +package main + +import ( + "fmt" + "os" +) + +// 基本 defer +func processFile(filename string) error { + file, err := os.Open(filename) + if err != nil { + return err + } + defer file.Close() // 函数返回时执行 + + // 处理文件... + return nil +} + +// 多个 defer (LIFO 顺序) +func processMultiple() { + defer fmt.Println("Third") + defer fmt.Println("Second") + defer fmt.Println("First") + // 输出: First, Second, Third +} + +// 带参数的 defer +func deferWithArgs() { + i := 0 + defer fmt.Println(i) // 打印 0 (现在求值) + i = 10 +} + +// Defer 用于清理 +func connectDatabase() (*sql.DB, error) { + db, err := sql.Open("driver", "dsn") + if err != nil { + return nil, err + } + + // Defer rollback 如果未提交 + var tx *sql.Tx + defer func() { + if tx != nil { + tx.Rollback() + } + }() + + tx, err = db.Begin() + if err != nil { + return nil, err + } + + // ... 使用 tx ... + + if err := tx.Commit(); err != nil { + return nil, err + } + tx = nil // 不要 rollback + + return db, nil +} +``` + + +### 常见 Defer 模式 + + +```go +// 1. 解锁互斥锁 +func process(data map[string]int) { + mu.Lock() + defer mu.Unlock() + + data["key"] = 42 + // 自动解锁 +} + +// 2. 关闭响应体 +func fetch(url string) error { + resp, err := http.Get(url) + if err != nil { + return err + } + defer resp.Body.Close() + + // 处理响应... + return nil +} + +// 3. 从 panic 恢复 +func safeOperation() (err error) { + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf("panic: %v", r) + } + }() + + // 有风险的操作... + return nil +} + +// 4. 测量执行时间 +func timedOperation() { + defer func(start time.Time) { + fmt.Printf(" Took %v\n", time.Since(start)) + }(time.Now()) + + // 操作... +} + +// 5. 资源清理 +func processFiles(filenames []string) error { + files := make([]*os.File, 0, len(filenames)) + + for _, filename := range filenames { + file, err := os.Open(filename) + if err != nil { + return err + } + files = append(files, file) + } + + // 返回时关闭所有文件 + defer func() { + for _, file := range files { + file.Close() + } + }() + + // 处理文件... + return nil +} +``` + + +## Panic 和 Recover + + +```python !! py +# Python - Try/except +def risky_operation(): + try: + result = divide(10, 0) + return result + except ZeroDivisionError as e: + print(f"Caught: {e}") + return None + finally: + print("Cleanup always runs") + +def divide(a, b): + if b == 0: + raise ZeroDivisionError("Cannot divide by zero") + return a / b +``` + +```go !! go +// Go - Panic/recover (很少使用,首选错误) +package main + +import ( + "fmt" + "log" +) + +// Panic 类似 raise,recover 类似 except +func riskyOperation() (result int) { + // defer with recover + defer func() { + if r := recover(); r != nil { + log.Printf("Recovered from panic: %v", r) + result = 0 + } + }() + + result = divide(10, 0) + return result +} + +func divide(a, b int) int { + if b == 0 { + panic("cannot divide by zero") // 类似 raise + } + return a / b +} + +// 在 main 中恢复 +func main() { + defer func() { + if r := recover(); r != nil { + fmt.Printf("Main recovered: %v\n", r) + } + }() + + riskyOperation() +} +``` + + +### 何时使用 Panic + + +```go +// 不要: 对正常错误使用 panic +func getUser(id int) (*User, error) { + if id < 0 { + panic("invalid id") // 不好! + } + // ... +} + +// 应该: 返回错误 +func getUser(id int) (*User, error) { + if id < 0 { + return nil, fmt.Errorf("invalid id: %d", id) + } + // ... +} + +// 可以: 对真正不可恢复的条件使用 panic +func init() { + if apiKey == "" { + panic("API_KEY environment variable required") + } +} + +// 可以: Panic 在程序员错误中 +func process(data []int) int { + if len(data) == 0 { + panic("process called with empty slice") // 程序员错误 + } + // ... +} +``` + + +## 总结 + +在本模块中,你学习了: + +1. **函数声明** 带类型参数 +2. **无默认参数** - 使用多个函数或函数式选项 +3. **多返回值** - Go 错误处理的超能力 +4. **命名返回值** - 在简单函数中用于清晰 +5. **可变参数函数** 使用 `...` 实现灵活参数 +6. **闭包** - 捕获上下文的匿名函数 +7. **方法** - 任何类型上的函数 (不需要类) +8. **值 vs 指针接收者** - 何时使用哪个 +9. **高阶函数** - 函数作为参数 +10. **Defer** - 保证按 LIFO 顺序清理 +11. **Panic/recover** - 仅用于异常情况 + +## 与 Python 的主要差异 + +| Python | Go | +|--------|-----| +| 动态参数 | 类型化参数 | +| 默认参数 | 多个函数或选项模式 | +| 单返回 (或元组) | 多返回值常见 | +| `lambda x: x+1` | `func(x int) int { return x+1 }` | +| 类方法 | 任何类型上的方法 | +| `try/except/finally` | 错误返回 + panic/recover | +| 列表推导 | 高阶函数 (但循环常见) | +| 上下文管理器 | `defer` 语句 | + +## 练习 + +1. 编写一个返回除法的商和余数的函数 +2. 为 Server 配置实现函数式选项模式 +3. 创建一个维护状态的闭包 (计数器、累加器) +4. 在自定义结构体上实现值和指针接收者 +5. 编写 `min()`、`max()` 和 `sum()` 的可变参数函数 +6. 使用 `defer` 测量函数执行时间 +7. 使用闭包创建记忆化助手 +8. 实现 `map`、`filter` 和 `reduce` 函数 + +## 下一步 + +下一模块:**结构体和接口** - Go 没有类的面向对象编程方法。 diff --git a/content/docs/py2go/module-03-functions.zh-tw.mdx b/content/docs/py2go/module-03-functions.zh-tw.mdx new file mode 100644 index 0000000..891e869 --- /dev/null +++ b/content/docs/py2go/module-03-functions.zh-tw.mdx @@ -0,0 +1,1231 @@ +--- +title: "模組 3:函式和方法" +description: "理解 Go 的函式系統,與 Python 對比" +--- + +## 簡介 + +Go 的函式系統與 Python 相似,但有關鍵差異:靜態類型、多返回值和顯式錯誤處理。本模組深入涵蓋函式、方法、閉包等。 + +## 基本函式 + +### 函式宣告語法 + + +```python !! py +# Python - 函式定義 +def add(a, b): + return a + b + +def greet(name, greeting="Hello"): + return f"{greeting}, {name}!" + +def calculate(x, y, z): + result = x + y * z + return result + +# 呼叫 +result = add(5, 3) +message = greet("Alice") +message = greet("Bob", "Hi") +calc_result = calculate(1, 2, 3) +``` + +```go !! go +// Go - 函式定義 +package main + +import "fmt" + +// 基本函式 +func add(a int, b int) int { + return a + b +} + +// 當參數共享類型時,可以簡寫 +func addShort(a, b int) int { + return a + b +} + +func greet(name string, greeting string) string { + return fmt.Sprintf("%s, %s!", greeting, name) +} + +func calculate(x, y, z int) int { + result := x + y*z + return result +} + +// 相同類型的多個參數 +func sum(a, b, c, d int) int { + return a + b + c + d +} + +func main() { + result := add(5, 3) + message := greet("Alice", "Hello") + calcResult := calculate(1, 2, 3) + total := sum(1, 2, 3, 4) + + fmt.Println(result) + fmt.Println(message) + fmt.Println(calcResult) + fmt.Println(total) +} +``` + + +### 無預設參數 + +Go 沒有預設參數。使用多個函式代替: + + +```python !! py +# Python - 預設參數 +def connect(host, port=5432, timeout=30): + return f"Connected to {host}:{port}" + +# 使用預設值呼叫 +connect("localhost") + +# 覆蓋部分 +connect("localhost", port=3306) + +# 覆蓋全部 +connect("localhost", 5432, 60) +``` + +```go !! go +// Go - 無預設參數,使用多個函式 +package main + +import "fmt" + +// 方法 1: 多個函式 +func Connect(host string) string { + return ConnectPort(host, 5432) +} + +func ConnectPort(host string, port int) string { + return ConnectTimeout(host, port, 30) +} + +func ConnectTimeout(host string, port int, timeout int) string { + return fmt.Sprintf("Connected to %s:%d (timeout: %d)", host, port, timeout) +} + +// 方法 2: 組態結構體 +type Config struct { + Host string + Port int + Timeout int +} + +func ConnectWithConfig(config Config) string { + // 提供預設值 + if config.Port == 0 { + config.Port = 5432 + } + if config.Timeout == 0 { + config.Timeout = 30 + } + + return fmt.Sprintf("Connected to %s:%d (timeout: %d)", + config.Host, config.Port, config.Timeout) +} + +// 方法 3: 函式選項模式 +type Server struct { + host string + port int + timeout int +} + +type Option func(*Server) + +func WithPort(port int) Option { + return func(s *Server) { + s.port = port + } +} + +func WithTimeout(timeout int) Option { + return func(s *Server) { + s.timeout = timeout + } +} + +func NewServer(host string, opts ...Option) *Server { + s := &Server{ + host: host, + port: 5432, // 預設值 + timeout: 30, // 預設值 + } + + for _, opt := range opts { + opt(s) + } + + return s +} + +func main() { + // 多個函式方法 + fmt.Println(Connect("localhost")) + fmt.Println(ConnectPort("localhost", 3306)) + + // 組態結構體方法 + config := Config{Host: "localhost"} + fmt.Println(ConnectWithConfig(config)) + + // 函式選項方法 + server := NewServer("localhost", WithPort(8080), WithTimeout(60)) + fmt.Printf("Server: %+v\n", server) +} +``` + + +## 多返回值 + +這是 Go 最強大的特性之一: + + +```python !! py +# Python - 返回元組 +def divide(a, b): + if b == 0: + return None, "Division by zero" + return a / b, None + +result, error = divide(10, 2) +if error: + print(f"Error: {error}") +else: + print(f"Result: {result}") + +# 可以用 _ 忽略值 +result, _ = divide(10, 2) +``` + +```go !! go +// Go - 多返回值 (慣用) +package main + +import "fmt" + +func divide(a int, b int) (int, error) { + if b == 0 { + return 0, fmt.Errorf("division by zero") + } + return a / b, nil +} + +func main() { + result, err := divide(10, 2) + if err != nil { + fmt.Printf("Error: %v\n", err) + return + } + fmt.Printf("Result: %d\n", result) + + // 使用空白識別符忽略錯誤 + result2, _ := divide(20, 4) + fmt.Printf("Result2: %d\n", result2) + + // 忽略結果 + _, err2 := divide(10, 0) + if err2 != nil { + fmt.Printf("Expected error: %v\n", err2) + } +} +``` + + +### 真實世界的多返回值 + + +```python !! py +# Python - 帶多返回值的 HTTP 請求 +import requests + +def fetch_user(user_id): + try: + response = requests.get(f"/api/users/{user_id}") + return response.json(), None + except requests.RequestException as e: + return None, str(e) + +user, error = fetch_user(123) +if error: + print(f"Failed: {error}") +else: + print(f"User: {user['name']}") +``` + +```go !! go +// Go - 帶多返回值的 HTTP 請求 +package main + +import ( + "encoding/json" + "fmt" + "net/http" +) + +type User struct { + ID int `json:"id"` + Name string `json:"name"` + Email string `json:"email"` +} + +func fetchUser(userID int) (*User, error) { + resp, err := http.Get(fmt.Sprintf("http://api.example.com/users/%d", userID)) + if err != nil { + return nil, fmt.Errorf("request failed: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("unexpected status: %d", resp.StatusCode) + } + + var user User + if err := json.NewDecoder(resp.Body).Decode(&user); err != nil { + return nil, fmt.Errorf("decode failed: %w", err) + } + + return &user, nil +} + +func main() { + user, err := fetchUser(123) + if err != nil { + fmt.Printf("Failed: %v\n", err) + return + } + + fmt.Printf("User: %s\n", user.Name) +} +``` + + +## 命名返回值 + +命名返回值使程式碼更清晰,但應謹慎使用: + + +```python !! py +# Python - 無命名返回值 (但可以返回字典) +def calculate(a, b): + sum_result = a + b + product = a * b + difference = a - b + return { + 'sum': sum_result, + 'product': product, + 'difference': difference + } + +result = calculate(5, 3) +print(result['sum'], result['product']) +``` + +```go !! go +// Go - 命名返回值 +package main + +import "fmt" + +// 命名返回值 (建立變數 sum 和 product) +func calculate(a int, b int) (sum int, product int) { + sum = a + b // 無需宣告 + product = a * b // 無需宣告 + return // 裸返回 (返回 sum, product) +} + +// 不同類型的命名返回值 +func analyze(numbers []int) (count int, sum int, average float64) { + for _, n := range numbers { + sum += n + count++ + } + if count > 0 { + average = float64(sum) / float64(count) + } + return // 返回 count, sum, average +} + +// 在 defer 中使用命名返回值 +func readFile(filename string) (content string, err error) { + defer func() { + if err != nil { + err = fmt.Errorf("readFile(%s): %w", filename, err) + } + }() + + data, err := os.ReadFile(filename) + if err != nil { + return "", err + } + + content = string(data) + return content, nil +} + +func main() { + s, p := calculate(5, 3) + fmt.Printf("Sum: %d, Product: %d\n", s, p) + + count, sum, avg := analyze([]int{1, 2, 3, 4, 5}) + fmt.Printf("Count: %d, Sum: %d, Average: %.2f\n", count, sum, avg) +} +``` + + +### 何時使用命名返回值 + + +```go +// 好: 命名返回值用於簡短函式的清晰性 +func calculateRectangle(width, height int) (area int, perimeter int) { + area = width * height + perimeter = 2 * (width + height) + return +} + +// 好: 命名返回值當需要在 defer 中修改時 +func transaction(db *DB) (result string, err error) { + defer func() { + if err != nil { + err = fmt.Errorf("transaction failed: %w", err) + } + }() + // ... 交易邏輯 + return +} + +// 避免: 在長函式中使用命名返回值 (難以追蹤) +func complexOperation(data []string) (output string, err error) { + // 許多行程式碼... + // 難以追蹤 'output' 在哪裡設定 + return +} + +// 首選: 在複雜函式中使用常規返回值 +func complexOperation(data []string) (string, error) { + output := process(data) + return output, nil +} +``` + + +## 可變參數函式 + + +```python !! py +# Python - *args 和 **kwargs +def func(*args, **kwargs): + print("Args:", args) + print("Kwargs:", kwargs) + +func(1, 2, 3, name="Alice", age=30) + +# 也可以傳遞列表/字典 +numbers = [1, 2, 3] +func(*numbers) +``` + +```go !! go +// Go - 可變參數函式 +package main + +import "fmt" + +// 基本可變參數函式 +func sumAll(numbers ...int) int { + total := 0 + for _, num := range numbers { + total += num + } + return total +} + +// 混合參數 (可變參數必須在最後) +func greet(prefix string, names ...string) { + for _, name := range names { + fmt.Printf("%s %s\n", prefix, name) + } +} + +// 不同類型的可變參數 +func printAny(items ...interface{}) { + for _, item := range items { + fmt.Println(item) + } +} + +func main() { + // 直接呼叫 + total := sumAll(1, 2, 3, 4, 5) + fmt.Println("Total:", total) + + // 使用 slice (用 ... 展開) + numbers := []int{10, 20, 30} + total = sumAll(numbers...) + fmt.Println("Total from slice:", total) + + // 混合參數 + greet("Hello:", "Alice", "Bob", "Charlie") + + // 空可變參數 + total = sumAll() + fmt.Println("Empty sum:", total) +} +``` + + +### 真實世界的可變參數範例 + + +```go +// SQL 查詢建構器 +func Query(db *sql.DB, query string, args ...interface{}) (*sql.Rows, error) { + return db.Query(query, args...) +} + +// 日誌 +func Log(level string, messages ...string) { + fmt.Printf("[%s] %s\n", level, strings.Join(messages, " ")) +} + +// 錯誤包裝 +func Wrap(err error, messages ...string) error { + msg := strings.Join(messages, ": ") + return fmt.Errorf("%s: %w", msg, err) +} + +// 使用 +rows, err := Query(db, "SELECT * FROM users WHERE id = $1", userID) +Log("ERROR", "Database", "Connection failed") +err = Wrap(err, "Failed to fetch user", userID) +``` + + +## 閉包和匿名函式 + + +```python !! py +# Python - 閉包 +def make_multiplier(factor): + def multiply(x): + return x * factor + return multiply + +times3 = make_multiplier(3) +print(times3(5)) # 15 + +# Lambda +add = lambda x, y: x + y +print(add(3, 4)) # 7 + +# 帶狀態的閉包 +def counter(): + count = 0 + def increment(): + nonlocal count + count += 1 + return count + return increment + +c = counter() +print(c()) # 1 +print(c()) # 2 +``` + +```go !! go +// Go - 閉包 +package main + +import "fmt" + +// 基本閉包 +func makeMultiplier(factor int) func(int) int { + return func(x int) int { + return x * factor + } +} + +// 帶狀態的閉包 +func counter() func() int { + count := 0 + return func() int { + count++ + return count + } +} + +// 帶多個值的閉包 +func accumulator() (func(int), func() int) { + sum := 0 + add := func(x int) { + sum += x + } + get := func() int { + return sum + } + return add, get +} + +func main() { + // 基本閉包 + times3 := makeMultiplier(3) + fmt.Println(times3(5)) // 15 + + // 匿名函式 + add := func(x, y int) int { + return x + y + } + fmt.Println(add(3, 4)) // 7 + + // IIFE (立即呼叫函式運算式) + result := func(x int) int { + return x * 2 + }(5) + fmt.Println(result) // 10 + + // 帶狀態的計數器 + c := counter() + fmt.Println(c()) // 1 + fmt.Println(c()) // 2 + fmt.Println(c()) // 3 + + // 共享狀態的多個閉包 + addVal, getSum := accumulator() + addVal(10) + addVal(20) + fmt.Println(getSum()) // 30 +} +``` + + +### 常見閉包模式 + + +```go +// 1. 迭代器模式 +func iterate(numbers []int) func() (int, bool) { + index := 0 + return func() (int, bool) { + if index >= len(numbers) { + return 0, false + } + val := numbers[index] + index++ + return val, true + } +} + +// 使用 +it := iterate([]int{1, 2, 3}) +for val, ok := it(); ok; val, ok = it() { + fmt.Println(val) +} + +// 2. 記憶化 +func memoize(fn func(int) int) func(int) int { + cache := make(map[int]int) + return func(x int) int { + if val, exists := cache[x]; exists { + return val + } + result := fn(x) + cache[x] = result + return result + } +} + +var fib = memoize(func(n int) int { + if n < 2 { + return n + } + return fib(n-1) + fib(n-2) +}) + +// 3. Defer 與閉包 +func process() { + defer func() { + fmt.Println("Cleanup done") + }() + fmt.Println("Processing") +} +``` + + +## 方法 + +Go 沒有類,但在任何類型上都有方法: + + +```python !! py +# Python - 類別方法 +class Rectangle: + def __init__(self, width, height): + self.width = width + self.height = height + + def area(self): + return self.width * self.height + + def perimeter(self): + return 2 * (self.width + self.height) + + def scale(self, factor): + self.width *= factor + self.height *= factor + +rect = Rectangle(5, 3) +print(rect.area()) # 15 +rect.scale(2) +print(rect.area()) # 60 +``` + +```go !! go +// Go - 類型上的方法 +package main + +import "fmt" + +type Rectangle struct { + width int + height int +} + +// 值接收者 (不修改原始值) +func (r Rectangle) Area() int { + return r.width * r.height +} + +func (r Rectangle) Perimeter() int { + return 2 * (r.width + r.height) +} + +// 指標接收者 (可以修改) +func (r *Rectangle) Scale(factor int) { + r.width *= factor + r.height *= factor +} + +// 非結構體類型上的方法 +type MyInt int + +func (m MyInt) Double() MyInt { + return m * 2 +} + +func main() { + rect := Rectangle{width: 5, height: 3} + fmt.Println(rect.Area()) // 15 + fmt.Println(rect.Perimeter()) // 16 + + rect.Scale(2) + fmt.Println(rect.Area()) // 60 + + // 非結構體上的方法 + num := MyInt(5) + fmt.Println(num.Double()) // 10 +} +``` + + +### 值 vs 指標接收者 + + +```python !! py +# Python - 方法可以修改物件 +class Counter: + def __init__(self): + self.count = 0 + + def increment(self): + self.count += 1 + + def get_count(self): + return self.count + +counter = Counter() +counter.increment() +print(counter.get_count()) # 1 +``` + +```go !! go +// Go - 仔細選擇接收者類型 +package main + +import "fmt" + +type Counter struct { + count int +} + +// 值接收者 (不能修改) +func (c Counter) GetCount() int { + return c.count +} + +// 指標接收者 (可以修改) +func (c *Counter) Increment() { + c.count++ +} + +// 何時使用指標接收者: +// 1. 方法需要修改接收者 +// 2. 結構體很大 (避免複製) +// 3. 一致性 (如果某些方法使用指標,所有方法都應該使用) + +func main() { + counter := Counter{count: 0} + + // 兩者都工作 + counter.Increment() + fmt.Println(counter.GetCount()) // 1 + + // 可以在值上呼叫指標接收者方法 + // (Go 自動取得位址) + c := Counter{} + c.Increment() // 自動轉換為 (&c).Increment() +} +``` + + +### 方法集和介面 + + +```go +// 值接收者方法在值方法集中 +type ValueInterface interface { + Method() // 可以用值或指標呼叫 +} + +// 指標接收者方法僅在指標方法集中 +type PointerInterface interface { + Method() // 只能用指標呼叫 +} + +// 範例 +type User struct { + Name string +} + +func (u User) GetName() string { + return u.Name +} + +func (u *User) SetName(name string) { + u.Name = name +} + +func processUser(u User) { + // 可以在值上呼叫 GetName + fmt.Println(u.GetName()) + + // 不能在值上呼叫 SetName + // u.SetName("Bob") // 編譯錯誤! +} + +func processUserPtr(u *User) { + // 可以在指標上呼叫兩個方法 + fmt.Println(u.GetName()) + u.SetName("Bob") // OK! +} +``` + + +## 高階函式 + + +```python !! py +# Python - Map, filter, reduce +numbers = [1, 2, 3, 4, 5] + +# Map +doubled = list(map(lambda x: x * 2, numbers)) + +# Filter +evens = list(filter(lambda x: x % 2 == 0, numbers)) + +# Reduce +from functools import reduce +total = reduce(lambda x, y: x + y, numbers) + +# 列表推導 (更 Pythonic) +doubled = [x * 2 for x in numbers] +evens = [x for x in numbers if x % 2 == 0] +``` + +```go !! go +// Go - 高階函式 +package main + +import "fmt" + +func mapInts(nums []int, f func(int) int) []int { + result := make([]int, len(nums)) + for i, num := range nums { + result[i] = f(num) + } + return result +} + +func filterInts(nums []int, f func(int) bool) []int { + result := []int{} + for _, num := range nums { + if f(num) { + result = append(result, num) + } + } + return result +} + +func reduceInts(nums []int, initial int, f func(int, int) int) int { + result := initial + for _, num := range nums { + result = f(result, num) + } + return result +} + +func main() { + numbers := []int{1, 2, 3, 4, 5} + + // Map + doubled := mapInts(numbers, func(x int) int { + return x * 2 + }) + fmt.Println("Doubled:", doubled) // [2 4 6 8 10] + + // Filter + evens := filterInts(numbers, func(x int) bool { + return x%2 == 0 + }) + fmt.Println("Evens:", evens) // [2 4] + + // Reduce + sum := reduceInts(numbers, 0, func(acc, val int) int { + return acc + val + }) + fmt.Println("Sum:", sum) // 15 + + // 鏈式 + result := reduceInts( + filterInts( + mapInts(numbers, func(x int) int { return x * 2 }), + func(x int) bool { return x > 4 }, + ), + 0, + func(acc, val int) int { return acc + val }, + ) + fmt.Println("Chained:", result) // 6 + 8 + 10 = 24 +} +``` + + +## 函式中的 Defer + + +```python !! py +# Python - 上下文管理器 +def process_file(filename): + with open(filename) as f: + data = f.read() + return process(data) + +# 多個清理 +def process(): + with open("file1.txt") as f1: + with open("file2.txt") as f2: + process_both(f1, f2) +``` + +```go !! go +// Go - 函式中的 Defer +package main + +import ( + "fmt" + "os" +) + +// 基本 defer +func processFile(filename string) error { + file, err := os.Open(filename) + if err != nil { + return err + } + defer file.Close() // 函式返回時執行 + + // 處理檔案... + return nil +} + +// 多個 defer (LIFO 順序) +func processMultiple() { + defer fmt.Println("Third") + defer fmt.Println("Second") + defer fmt.Println("First") + // 輸出: First, Second, Third +} + +// 帶參數的 defer +func deferWithArgs() { + i := 0 + defer fmt.Println(i) // 列印 0 (現在求值) + i = 10 +} + +// Defer 用於清理 +func connectDatabase() (*sql.DB, error) { + db, err := sql.Open("driver", "dsn") + if err != nil { + return nil, err + } + + // Defer rollback 如果未提交 + var tx *sql.Tx + defer func() { + if tx != nil { + tx.Rollback() + } + }() + + tx, err = db.Begin() + if err != nil { + return nil, err + } + + // ... 使用 tx ... + + if err := tx.Commit(); err != nil { + return nil, err + } + tx = nil // 不要 rollback + + return db, nil +} +``` + + +### 常見 Defer 模式 + + +```go +// 1. 解鎖互斥鎖 +func process(data map[string]int) { + mu.Lock() + defer mu.Unlock() + + data["key"] = 42 + // 自動解鎖 +} + +// 2. 關閉回應體 +func fetch(url string) error { + resp, err := http.Get(url) + if err != nil { + return err + } + defer resp.Body.Close() + + // 處理回應... + return nil +} + +// 3. 從 panic 恢復 +func safeOperation() (err error) { + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf("panic: %v", r) + } + }() + + // 有風險的操作... + return nil +} + +// 4. 測量執行時間 +func timedOperation() { + defer func(start time.Time) { + fmt.Printf(" Took %v\n", time.Since(start)) + }(time.Now()) + + // 操作... +} + +// 5. 資源清理 +func processFiles(filenames []string) error { + files := make([]*os.File, 0, len(filenames)) + + for _, filename := range filenames { + file, err := os.Open(filename) + if err != nil { + return err + } + files = append(files, file) + } + + // 返回時關閉所有檔案 + defer func() { + for _, file := range files { + file.Close() + } + }() + + // 處理檔案... + return nil +} +``` + + +## Panic 和 Recover + + +```python !! py +# Python - Try/except +def risky_operation(): + try: + result = divide(10, 0) + return result + except ZeroDivisionError as e: + print(f"Caught: {e}") + return None + finally: + print("Cleanup always runs") + +def divide(a, b): + if b == 0: + raise ZeroDivisionError("Cannot divide by zero") + return a / b +``` + +```go !! go +// Go - Panic/recover (很少使用,首選錯誤) +package main + +import ( + "fmt" + "log" +) + +// Panic 類似 raise,recover 類似 except +func riskyOperation() (result int) { + // defer with recover + defer func() { + if r := recover(); r != nil { + log.Printf("Recovered from panic: %v", r) + result = 0 + } + }() + + result = divide(10, 0) + return result +} + +func divide(a, b int) int { + if b == 0 { + panic("cannot divide by zero") // 類似 raise + } + return a / b +} + +// 在 main 中恢復 +func main() { + defer func() { + if r := recover(); r != nil { + fmt.Printf("Main recovered: %v\n", r) + } + }() + + riskyOperation() +} +``` + + +### 何時使用 Panic + + +```go +// 不要: 對正常錯誤使用 panic +func getUser(id int) (*User, error) { + if id < 0 { + panic("invalid id") // 不好! + } + // ... +} + +// 應該: 返回錯誤 +func getUser(id int) (*User, error) { + if id < 0 { + return nil, fmt.Errorf("invalid id: %d", id) + } + // ... +} + +// 可以: 對真正不可恢復的條件使用 panic +func init() { + if apiKey == "" { + panic("API_KEY environment variable required") + } +} + +// 可以: Panic 在程式設計師錯誤中 +func process(data []int) int { + if len(data) == 0 { + panic("process called with empty slice") // 程式設計師錯誤 + } + // ... +} +``` + + +## 總結 + +在本模組中,你學習了: + +1. **函式宣告** 帶類型參數 +2. **無預設參數** - 使用多個函式或函式選項 +3. **多返回值** - Go 錯誤處理的超能力 +4. **命名返回值** - 在簡單函式中用於清晰 +5. **可變參數函式** 使用 `...` 實現靈活參數 +6. **閉包** - 捕獲上下文的匿名函式 +7. **方法** - 任何類型上的函式 (不需要類) +8. **值 vs 指標接收者** - 何時使用哪個 +9. **高階函式** - 函式作為參數 +10. **Defer** - 保證按 LIFO 順序清理 +11. **Panic/recover** - 僅用於異常情況 + +## 與 Python 的主要差異 + +| Python | Go | +|--------|-----| +| 動態參數 | 類型化參數 | +| 預設參數 | 多個函式或選項模式 | +| 單返回 (或元組) | 多返回值常見 | +| `lambda x: x+1` | `func(x int) int { return x+1 }` | +| 類別方法 | 任何類型上的方法 | +| `try/except/finally` | 錯誤返回 + panic/recover | +| 列表推導 | 高階函式 (但迴圈常見) | +| 上下文管理器 | `defer` 語句 | + +## 練習 + +1. 撰寫一個返回除法的商和餘數的函式 +2. 為 Server 組態實作函式選項模式 +3. 建立一個維護狀態的閉包 (計數器、累加器) +4. 在自訂結構體上實作值和指標接收者 +5. 撰寫 `min()`、`max()` 和 `sum()` 的可變參數函式 +6. 使用 `defer` 測量函式執行時間 +7. 使用閉包建立記憶化助手 +8. 實作 `map`、`filter` 和 `reduce` 函式 + +## 下一步 + +下一模組:**結構體和介面** - Go 沒有類別的物件導向程式設計方法。 diff --git a/content/docs/py2go/module-04-structs-interfaces.mdx b/content/docs/py2go/module-04-structs-interfaces.mdx new file mode 100644 index 0000000..c1116f2 --- /dev/null +++ b/content/docs/py2go/module-04-structs-interfaces.mdx @@ -0,0 +1,1356 @@ +--- +title: "Module 4: Structs and Interfaces" +description: "Go's approach to object-oriented programming without classes" +--- + +## Introduction + +Go doesn't have classes or inheritance. Instead, it uses **structs** for data and **interfaces** for behavior. This is a simpler, more flexible approach to OOP that encourages composition over inheritance. + +## Structs Basics + +### Defining Structs + + +```python !! py +# Python - Class with __init__ +class Person: + def __init__(self, name, age): + self.name = name + self.age = age + + def greet(self): + return f"Hi, I'm {self.name}" + +person = Person("Alice", 30) +print(person.greet()) +``` + +```go !! go +// Go - Struct +package main + +import "fmt" + +type Person struct { + Name string + Age int +} + +func (p Person) Greet() string { + return fmt.Sprintf("Hi, I'm %s", p.Name) +} + +func main() { + person := Person{Name: "Alice", Age: 30} + fmt.Println(person.Greet()) +} +``` + + +### Struct Field Visibility + + +```python !! py +# Python - Public vs private (convention) +class Person: + def __init__(self, name, age, ssn): + self.name = name # Public + self._age = age # Protected (convention) + self.__ssn = ssn # Private (name mangling) + + def get_age(self): + return self._age +``` + +```go !! go +// Go - Exported vs unexported +package main + +import "fmt" + +type Person struct { + Name string // Exported (capitalized) + age int // Unexported (lowercase) + SSN string // Exported +} + +func (p Person) GetAge() int { + return p.age // Can access within package +} + +func NewPerson(name string, age int, ssn string) Person { + return Person{ + Name: name, + age: age, // Can set unexported field + SSN: ssn, + } +} + +func main() { + p := NewPerson("Alice", 30, "123-45-6789") + fmt.Println(p.Name) // OK - exported + fmt.Println(p.GetAge()) // OK - method + // fmt.Println(p.age) // Error: unexported +} +``` + + +### Creating and Initializing Structs + + +```python !! py +# Python - Class instantiation +class Point: + def __init__(self, x=0, y=0): + self.x = x + self.y = y + +p1 = Point(1, 2) +p2 = Point(x=3, y=4) +p3 = Point() # Default values +p4 = Point(5) # Partial (y=0) +``` + +```go !! go +// Go - Struct creation +package main + +import "fmt" + +type Point struct { + X int + Y int +} + +// Constructor function (idiomatic) +func NewPoint(x, y int) Point { + return Point{X: x, Y: y} +} + +// Constructor with defaults +func NewDefaultPoint() Point { + return Point{X: 0, Y: 0} +} + +func main() { + p1 := Point{1, 2} // Positional (order matters) + p2 := Point{X: 3, Y: 4} // Named fields + p3 := Point{} // Zero values + p4 := Point{X: 5} // Partial (Y=0) + p5 := NewPoint(6, 7) // Constructor + p6 := NewDefaultPoint() // Default constructor + + fmt.Println(p1, p2, p3, p4, p5, p6) +} +``` + + +### Struct Tags + + +```python !! py +# Python - No built-in struct tags +# Would use decorators or class attributes +class User: + def __init__(self, name, email): + self.name = name + self.email = email + + @classmethod + def from_dict(cls, data): + return cls(data['name'], data['email']) +``` + +```go !! go +// Go - Struct tags (metadata) +package main + +import ( + "encoding/json" + "fmt" +) + +type User struct { + ID int `json:"id"` // JSON key + Name string `json:"name"` // JSON key + Email string `json:"email,omitempty"` // Omit if empty + Password string `json:"-"` // Never in JSON + Internal string `json:"internal,internal"` // Multiple options + DBName string `db:"user_name" json:"name"` // Multiple tags +} + +func main() { + user := User{ + ID: 1, + Name: "Alice", + Email: "alice@example.com", + Password: "secret", + Internal: "internal_data", + DBName: "alice_db", + } + + // JSON encoding respects tags + jsonBytes, _ := json.Marshal(user) + fmt.Println(string(jsonBytes)) + // Output: {"id":1,"name":"Alice","email":"alice@example.com","internal":"internal_data"} +} +``` + + +### Anonymous Structs + + +```python !! py +# Python - No anonymous classes (use namedtuple or dict) +from collections import namedtuple +Point = namedtuple('Point', ['x', 'y']) +p = Point(1, 2) + +# Or use dict +p = {'x': 1, 'y': 2} +``` + +```go !! go +// Go - Anonymous structs +package main + +import "fmt" + +func main() { + // Anonymous struct literal + point := struct { + X int + Y int + }{ + X: 1, + Y: 2, + } + + fmt.Println(point) + + // Slice of anonymous structs + people := []struct { + Name string + Age int + }{ + {"Alice", 30}, + {"Bob", 25}, + } + + for _, p := range people { + fmt.Printf("%s is %d\n", p.Name, p.Age) + } +} +``` + + +## Composition over Inheritance + +Go uses composition instead of inheritance, which is more flexible: + +### Basic Composition + + +```python !! py +# Python - Inheritance +class Animal: + def __init__(self, name): + self.name = name + + def speak(self): + pass + +class Dog(Animal): + def __init__(self, name, breed): + super().__init__(name) + self.breed = breed + + def speak(self): + return f"{self.name} says Woof!" + +dog = Dog("Buddy", "Golden Retriever") +print(dog.speak()) +print(dog.name) # Inherited +``` + +```go !! go +// Go - Composition +package main + +import "fmt" + +type Animal struct { + Name string +} + +func (a Animal) Speak() string { + return fmt.Sprintf("%s makes a sound", a.Name) +} + +type Dog struct { + Animal // Embedded struct (composition) + Breed string +} + +func (d Dog) Speak() string { + return fmt.Sprintf("%s says Woof!", d.Name) +} + +func main() { + dog := Dog{ + Animal: Animal{Name: "Buddy"}, + Breed: "Golden Retriever", + } + + fmt.Println(dog.Speak()) + fmt.Println(dog.Name) // Access embedded field directly (promoted) + fmt.Println(dog.Breed) + + // Can also access through embedded type + fmt.Println(dog.Animal.Name) +} +``` + + +### Multiple Composition + + +```python !! py +# Python - Multiple inheritance +class Flyable: + def fly(self): + return "Flying" + +class Swimmable: + def swim(self): + return "Swimming" + +class Duck(Flyable, Swimmable): + def __init__(self, name): + self.name = name + +duck = Duck("Donald") +print(duck.fly()) +print(duck.swim()) +``` + +```go !! go +// Go - Multiple composition +package main + +import "fmt" + +type Flyable struct{} + +func (f Flyable) Fly() string { + return "Flying" +} + +type Swimmable struct{} + +func (s Swimmable) Swim() string { + return "Swimming" +} + +type Duck struct { + Flyable + Swimmable + Name string +} + +func main() { + duck := Duck{ + Name: "Donald", + } + + fmt.Println(duck.Fly()) + fmt.Println(duck.Swim()) + fmt.Println(duck.Name) +} +``` + + +### Method Resolution and Shadowing + + +```python !! py +# Python - MRO (Method Resolution Order) +class A: + def show(self): + print("A") + +class B(A): + def show(self): + print("B") + +class C(B): + pass + +obj = C() +obj.show() # Prints "B" (finds in B first) +``` + +```go !! go +// Go - Method promotion and shadowing +package main + +import "fmt" + +type Animal struct { + Name string +} + +func (a Animal) Speak() string { + return "Animal sound" +} + +type Dog struct { + Animal + Breed string +} + +// Dog's Speak shadows Animal's Speak +func (d Dog) Speak() string { + return "Woof!" +} + +func main() { + animal := Animal{Name: "Generic"} + dog := Dog{ + Animal: Animal{Name: "Buddy"}, + Breed: "Golden", + } + + fmt.Println(animal.Speak()) // "Animal sound" + fmt.Println(dog.Speak()) // "Woof!" (Dog's method) + + // Can still call Animal's method through the embedded field + fmt.Println(dog.Animal.Speak()) // "Animal sound" +} +``` + + +## Interfaces + +Interfaces are implicitly implemented - no explicit "implements" keyword needed. This is one of Go's most powerful features. + +### Basic Interfaces + + +```python !! py +# Python - Duck typing (implicit) +class Duck: + def quack(self): + return "Quack!" + +class Person: + def quack(self): + return "I'm quacking like a duck!" + +def make_it_quack(thing): + if hasattr(thing, 'quack'): + print(thing.quack()) + +duck = Duck() +person = Person() + +make_it_quack(duck) # Works +make_it_quack(person) # Also works! +``` + +```go !! go +// Go - Implicit interfaces +package main + +import "fmt" + +type Quacker interface { + Quack() string +} + +type Duck struct{} + +func (d Duck) Quack() string { + return "Quack!" +} + +type Person struct{} + +func (p Person) Quack() string { + return "I'm quacking like a duck!" +} + +func makeItQuack(q Quacker) { + fmt.Println(q.Quack()) +} + +func main() { + duck := Duck{} + person := Person{} + + makeItQuack(duck) // Works + makeItQuack(person) // Also works! + + // Types satisfy interface automatically + // No need to declare "type Duck implements Quacker" +} +``` + + +### Interface Composition + + +```python !! py +# Python - Multiple base classes +class Reader: + def read(self): + pass + +class Writer: + def write(self, data): + pass + +class ReadWriter(Reader, Writer): + pass +``` + +```go !! go +// Go - Interface composition +package main + +import "fmt" + +type Reader interface { + Read(p []byte) (n int, err error) +} + +type Writer interface { + Write(p []byte) (n int, err error) +} + +type ReadWriter interface { + Reader // Embedded interface + Writer // Embedded interface +} + +// File implements both Read and Write +type File struct { + name string +} + +func (f File) Read(p []byte) (n int, err error) { + // Implementation + return len(p), nil +} + +func (f File) Write(p []byte) (n int, err error) { + // Implementation + return len(p), nil +} + +func process(rw ReadWriter) { + buf := make([]byte, 1024) + rw.Read(buf) + rw.Write(buf) +} + +func main() { + file := File{name: "test.txt"} + process(file) // File satisfies ReadWriter +} +``` + + +### Empty Interface + + +```python !! py +# Python - Any type +def print_anything(value): + print(value) + print(type(value)) + +print_anything(42) +print_anything("hello") +print_anything([1, 2, 3]) +``` + +```go !! go +// Go - Empty interface (interface{}) +package main + +import "fmt" + +func printAnything(value interface{}) { + fmt.Println(value) + fmt.Printf("Type: %T\n", value) +} + +func main() { + printAnything(42) + printAnything("hello") + printAnything([]int{1, 2, 3}) + + // Empty interface can hold ANY value + var anything interface{} + anything = 42 + anything = "hello" + anything = []int{1, 2, 3} +} +``` + + +### Empty Interface - When to Use + + +```go +// AVOID: Empty interface when type is known +func process(data interface{}) { + // What type is data? Unclear! + // Type assertions needed everywhere +} + +// PREFER: Specific interface +func process(data []byte) { + // Type is clear +} + +// OK: Empty interface for truly heterogeneous data +func printAll(items ...interface{}) { + for _, item := range items { + fmt.Println(item) + } +} + +// OK: JSON unmarshaling +var data map[string]interface{} +json.Unmarshal(jsonBytes, &data) + +// PREFER: Specific struct +type Config struct { + Host string `json:"host"` + Port int `json:"port"` +} +var config Config +json.Unmarshal(jsonBytes, &config) +``` + + +## Type Assertions and Type Switches + +### Type Assertions + + +```python !! py +# Python - Type checking +def process(value): + if isinstance(value, int): + print(f"Integer: {value}") + return value * 2 + elif isinstance(value, str): + print(f"String: {value}") + return value.upper() + elif isinstance(value, list): + print(f"List: {value}") + return len(value) + else: + print(f"Unknown type: {type(value)}") + return None +``` + +```go !! go +// Go - Type assertions +package main + +import "fmt" + +func process(value interface{}) interface{} { + // Type assertion with comma-ok pattern + if i, ok := value.(int); ok { + fmt.Printf("Integer: %d\n", i) + return i * 2 + } + + if s, ok := value.(string); ok { + fmt.Printf("String: %s\n", s) + return strings.ToUpper(s) + } + + if lst, ok := value.([]int); ok { + fmt.Printf("Int slice: %v\n", lst) + return len(lst) + } + + fmt.Printf("Unknown type: %T\n", value) + return nil +} + +// Unsafe type assertion (panics if wrong) +func double(value interface{}) int { + return value.(int) * 2 // Panics if value is not int +} + +func main() { + process(42) + process("hello") + process([]int{1, 2, 3}) + + // Safe usage + if i, ok := interface{}(42).(int); ok { + fmt.Println("Double:", i*2) + } +} +``` + + +### Type Switches + + +```python !! py +# Python - Multiple type checks +def handle(value): + if isinstance(value, int): + return "integer" + elif isinstance(value, str): + return "string" + elif isinstance(value, list): + return "list" + elif isinstance(value, dict): + return "dict" + else: + return f"unknown: {type(value)}" +``` + +```go !! go +// Go - Type switch +package main + +import "fmt" + +func handle(value interface{}) string { + switch v := value.(type) { + case int: + return fmt.Sprintf("integer: %d", v) + case string: + return fmt.Sprintf("string: %s", v) + case []int: + return fmt.Sprintf("int slice: %v", v) + case map[string]interface{}: + return fmt.Sprintf("map: %d keys", len(v)) + case nil: + return "nil value" + default: + return fmt.Sprintf("unknown: %T", v) + } +} + +func main() { + fmt.Println(handle(42)) + fmt.Println(handle("hello")) + fmt.Println(handle([]int{1, 2, 3})) + fmt.Println(handle(map[string]interface{}{"a": 1})) + fmt.Println(handle(nil)) +} +``` + + +### Type Switch with Multiple Cases + + +```go +// Multiple types in one case +func process(value interface{}) string { + switch v := value.(type) { + case int, int8, int16, int32, int64: + return "integer type" + case uint, uint8, uint16, uint32, uint64: + return "unsigned integer type" + case float32, float64: + return "float type" + case string: + return "string" + case bool: + return "boolean" + default: + return "other type" + } +} +``` + + +## Standard Library Interfaces + +Go's standard library defines many useful interfaces: + +### Stringer Interface + + +```python !! py +# Python - __str__ and __repr__ +class Person: + def __init__(self, name, age): + self.name = name + self.age = age + + def __str__(self): + return f"Person(name={self.name})" + + def __repr__(self): + return f"Person(name={self.name}, age={self.age})" + +p = Person("Alice", 30) +print(p) # Uses __str__ +print(repr(p)) # Uses __repr__ +``` + +```go !! go +// Go - Stringer interface +package main + +import ( + "fmt" +) + +type Person struct { + Name string + Age int +} + +// Stringer interface (like __str__) +func (p Person) String() string { + return fmt.Sprintf("Person(name=%s)", p.Name) +} + +func main() { + person := Person{Name: "Alice", Age: 30} + + // fmt.Println calls String() automatically + fmt.Println(person) + + // String also uses String() + fmt.Sprintf("%s", person) +} +``` + + +### Error Interface + + +```python !! py +# Python - Exception +class ValidationError(Exception): + def __init__(self, message, field): + self.message = message + self.field = field + + def __str__(self): + return f"{self.field}: {self.message}" + +raise ValidationError("Invalid email", "email") +``` + +```go !! go +// Go - Error interface +package main + +import "fmt" + +// Error interface is built-in: +// type error interface { +// Error() string +// } + +type ValidationError struct { + Message string + Field string +} + +func (e ValidationError) Error() string { + return fmt.Sprintf("%s: %s", e.Field, e.Message) +} + +func validateEmail(email string) error { + if email == "" { + return ValidationError{ + Message: "cannot be empty", + Field: "email", + } + } + return nil +} + +func main() { + if err := validateEmail(""); err != nil { + fmt.Println(err) // Calls Error() automatically + } +} +``` + + +### Other Common Interfaces + + +```go +// Reader interface (io package) +type Reader interface { + Read(p []byte) (n int, err error) +} + +// Writer interface (io package) +type Writer interface { + Write(p []byte) (n int, err error) +} + +// Closer interface (io package) +type Closer interface { + Close() error +} + +// ReadWriter interface (composed) +type ReadWriter interface { + Reader + Writer +} + +// ReadWriteCloser interface +type ReadWriteCloser interface { + Reader + Writer + Closer +} +``` + + +## Interface Best Practices + +### Accept Interfaces, Return Structs + + +```python !! py +# Python - Return specific types +def get_user(): + return User("Alice", 30) + +# Accept duck-typed interface +def process(obj): + if hasattr(obj, 'process'): + obj.process() +``` + +```go !! go +// Go - Accept interfaces, return structs +package main + +// GOOD: Return concrete type +func NewUser(name string, age int) User { + return User{Name: name, Age: age} +} + +// GOOD: Accept interface +func processUser(u UserInterface) { + u.Process() +} + +// AVOID: Return interface (unless necessary) +func getUser() UserInterface { // Don't do this + return User{Name: "Alice"} +} + +// PREFER: Return concrete type +func getUser() User { // Do this + return User{Name: "Alice"} +} + +// Exception: Return interface when returning nil +func findUser(id int) (User, error) { + if id == 0 { + return User{}, fmt.Errorf("invalid id") + } + // ... +} +``` + + +### Small Interfaces + + +```go +// GOOD: Small, focused interfaces +type Reader interface { + Read(p []byte) (n int, err error) +} + +type Writer interface { + Write(p []byte) (n int, err error) +} + +// AVOID: Large, monolithic interfaces +type FileHandler interface { + Read(p []byte) (n int, err error) + Write(p []byte) (n int, err error) + Close() error + Sync() error + Stat() (os.FileInfo, error) + // ... many more methods +} + +// PREFER: Compose small interfaces +type ReadWriteCloser interface { + Reader + Writer + io.Closer +} +``` + + +### Interface Nil Gotcha + + +```go +// Watch out for nil interface values! +package main + +import "fmt" + +type MyError struct { + Message string +} + +func (e *MyError) Error() string { + return e.Message +} + +func returnsError() error { + var p *MyError = nil + return p // Returns nil interface, but non-nil interface! +} + +func returnsErrorCorrect() error { + var p *MyError = nil + return nil // Returns nil interface +} + +func main() { + err := returnsError() + if err != nil { + fmt.Println("Error is not nil!") + // This prints! Even though p is nil! + // The interface itself is not nil + } + + // Correct way + err2 := returnsErrorCorrect() + if err2 != nil { + fmt.Println("Error2 is not nil!") + } + // This doesn't print +} +``` + + +## Pointer Receivers vs Value Receivers + +### When to Use Each + + +```python !! py +# Python - Methods can modify +class Counter: + def __init__(self): + self.count = 0 + + def increment(self): + self.count += 1 # Modifies object + + def get_count(self): + return self.count + + def double(self): + self.count *= 2 # Modifies object +``` + +```go !! go +// Go - Choose receiver type carefully +package main + +import "fmt" + +type Counter struct { + count int +} + +// Value receiver - cannot modify +// Use when: +// - Method doesn't need to modify +// - Struct is small (cheap to copy) +// - Consistency (if all methods use value) +func (c Counter) GetCount() int { + return c.count +} + +// Pointer receiver - can modify +// Use when: +// - Method needs to modify +// - Struct is large (avoids copying) +// - Consistency (if any method uses pointer) +func (c *Counter) Increment() { + c.count++ +} + +// Even for read-only methods on large structs, +// use pointer to avoid copying +func (c *Counter) Double() { + c.count *= 2 +} + +// For small immutable values, value receiver is fine +type Point struct { + X, Y int +} + +func (p Point) Distance() int { + return p.X*p.X + p.Y*p.Y +} + +func main() { + counter := Counter{count: 0} + counter.Increment() + counter.Double() + fmt.Println(counter.GetCount()) // 2 +} +``` + + +### Receiver Type Guidelines + + +```go +// GUIDELINE 1: Use pointer receiver if method needs to modify +func (c *Counter) Increment() { + c.count++ +} + +// GUIDELINE 2: Use pointer receiver for large structs +type BigStruct struct { + data [1024]int +} + +func (b *BigStruct) Process() { + // Avoid copying 1024 ints +} + +// GUIDELINE 3: Use value receiver for small immutable values +type Point struct { + X, Y int +} + +func (p Point) String() string { + return fmt.Sprintf("(%d, %d)", p.X, p.Y) +} + +// GUIDELINE 4: Be consistent - don't mix receiver types +// BAD: Mixed receivers +type MyStruct struct { + value int +} + +func (m MyStruct) GetValue() int { + return m.value +} + +func (m *MyStruct) SetValue(v int) { + m.value = v +} + +// GOOD: All pointer receivers +func (m *MyStruct) GetValue() int { + return m.value +} + +func (m *MyStruct) SetValue(v int) { + m.value = v +} +``` + + +## Practical Examples + +### JSON Encoding/Decoding + + +```python !! py +# Python - JSON serialization +import json + +class User: + def __init__(self, name, email): + self.name = name + self.email = email + + def to_dict(self): + return { + 'name': self.name, + 'email': self.email + } + + @classmethod + def from_dict(cls, data): + return cls(data['name'], data['email']) + +user = User("Alice", "alice@example.com") +json_str = json.dumps(user.to_dict()) +parsed = User.from_dict(json.loads(json_str)) +``` + +```go !! go +// Go - JSON with struct tags +package main + +import ( + "encoding/json" + "fmt" +) + +type User struct { + Name string `json:"name"` + Email string `json:"email"` + Age int `json:"age,omitempty"` // Omit if zero +} + +func main() { + // Encoding + user := User{ + Name: "Alice", + Email: "alice@example.com", + } + + jsonBytes, err := json.Marshal(user) + if err != nil { + panic(err) + } + fmt.Println(string(jsonBytes)) + // {"name":"Alice","email":"alice@example.com"} + + // Decoding + var parsed User + if err := json.Unmarshal(jsonBytes, &parsed); err != nil { + panic(err) + } + fmt.Printf("%+v\n", parsed) +} +``` + + +### Database Models + + +```python !! py +# Python - SQLAlchemy model +from sqlalchemy import Column, Integer, String +from sqlalchemy.ext.declarative import declarative_base + +Base = declarative_base() + +class User(Base): + __tablename__ = 'users' + + id = Column(Integer, primary_key=True) + name = Column(String(100)) + email = Column(String(200)) +``` + +```go !! go +// Go - Database model with struct tags +package main + +type User struct { + ID int `json:"id" db:"id"` + Name string `json:"name" db:"name"` + Email string `json:"email" db:"email"` + CreatedAt time.Time `json:"created_at" db:"created_at"` + UpdatedAt time.Time `json:"updated_at" db:"updated_at"` +} + +// Table name mapping +func (User) TableName() string { + return "users" +} + +// Scanner and Valuer for custom types +type NullString struct { + String string + Valid bool +} + +func (ns *NullString) Scan(value interface{}) error { + if value == nil { + ns.String, ns.Valid = "", false + return nil + } + ns.Valid = true + return nil +} +``` + + +## Summary + +In this module, you learned: + +1. **Structs** - Go's replacement for classes +2. **Field visibility** - Exported vs unexported +3. **Struct tags** - Metadata for JSON, database, etc. +4. **Anonymous structs** - Quick, one-off structures +5. **Composition** - Preferred over inheritance +6. **Multiple composition** - Combine multiple types +7. **Method promotion** - Access embedded methods +8. **Interfaces** - Implicitly implemented, flexible +9. **Empty interface** - Holds any value (use carefully) +10. **Type assertions** - Check and convert types +11. **Type switches** - Switch on types +12. **Standard interfaces** - Stringer, Error, Reader, Writer +13. **Interface best practices** - Small, focused interfaces +14. **Receiver types** - Pointer vs value +15. **Nil interface gotcha** - Interface vs concrete nil + +## Key Differences from Python + +| Python | Go | +|--------|-----| +| Classes and inheritance | Structs and composition | +| `__init__` for initialization | Struct literals or constructor functions | +| Private via `__` prefix | Unexported (lowercase) | +| `self` parameter | Receiver parameter | +| Magic methods (`__str__`, `__eq__`) | Interfaces (Stringer, etc.) | +| Duck typing (runtime) | Interfaces (compile-time + duck typing) | +| Multiple inheritance | Composition + interface composition | +| `isinstance()` checks | Type assertions and type switches | + +## Best Practices + +1. **Prefer composition** over inheritance +2. **Keep interfaces small** - 1-2 methods ideal +3. **Accept interfaces, return structs** - Be specific on returns +4. **Use pointer receivers** when modifying or for large structs +5. **Be consistent** with receiver types +6. **Use struct tags** for serialization metadata +7. **Avoid empty interfaces** unless truly necessary +8. **Watch out for nil interfaces** - understand the gotcha +9. **Embed for composition** - Promote fields/methods +10. **Implement standard interfaces** - Stringer, Error, etc. + +## Exercises + +1. Create a struct with exported and unexported fields +2. Implement the Stringer interface on a custom type +3. Use composition to combine multiple structs +4. Write a type switch to handle different types +5. Create both pointer and value receivers and compare behavior +6. Implement custom JSON marshaling with tags +7. Create an interface and have multiple types satisfy it +8. Build a small database model with struct tags + +## Next Steps + +Next module: **Package Management** - Understanding Go modules and imports. diff --git a/content/docs/py2go/module-04-structs-interfaces.zh-cn.mdx b/content/docs/py2go/module-04-structs-interfaces.zh-cn.mdx new file mode 100644 index 0000000..4a9ae4e --- /dev/null +++ b/content/docs/py2go/module-04-structs-interfaces.zh-cn.mdx @@ -0,0 +1,1359 @@ +--- +title: "模块 4: 结构体和接口" +description: "Go 面向对象编程方法,无需类" +--- + +## 简介 + +Go 没有类或继承。相反,它使用**结构体(struct)**来存储数据,使用**接口(interface)**来定义行为。这是一种更简单、更灵活的 OOP 方法,鼓励组合优于继承。 + +## 结构体基础 + +### 定义结构体 + + +```python !! py +# Python - 带有 __init__ 的类 +class Person: + def __init__(self, name, age): + self.name = name + self.age = age + + def greet(self): + return f"Hi, I'm {self.name}" + +person = Person("Alice", 30) +print(person.greet()) +``` + +```go !! go +// Go - 结构体 +package main + +import "fmt" + +type Person struct { + Name string + Age int +} + +func (p Person) Greet() string { + return fmt.Sprintf("Hi, I'm %s", p.Name) +} + +func main() { + person := Person{Name: "Alice", Age: 30} + fmt.Println(person.Greet()) +} +``` + + +### 结构体字段可见性 + + +```python !! py +# Python - 公共 vs 私有(约定) +class Person: + def __init__(self, name, age, ssn): + self.name = name # 公共 + self._age = age # 受保护(约定) + self.__ssn = ssn # 私有(名称改写) + + def get_age(self): + return self._age +``` + +```go !! go +// Go - 导出 vs 未导出 +package main + +import "fmt" + +type Person struct { + Name string // 导出(首字母大写) + age int // 未导出(小写) + SSN string // 导出 +} + +func (p Person) GetAge() int { + return p.age // 可以在包内访问 +} + +func NewPerson(name string, age int, ssn string) Person { + return Person{ + Name: name, + age: age, // 可以设置未导出字段 + SSN: ssn, + } +} + +func main() { + p := NewPerson("Alice", 30, "123-45-6789") + fmt.Println(p.Name) // OK - 导出 + fmt.Println(p.GetAge()) // OK - 方法 + // fmt.Println(p.age) // 错误:未导出 +} +``` + + +### 创建和初始化结构体 + + +```python !! py +# Python - 类实例化 +class Point: + def __init__(self, x=0, y=0): + self.x = x + self.y = y + +p1 = Point(1, 2) +p2 = Point(x=3, y=4) +p3 = Point() # 默认值 +p4 = Point(5) # 部分(y=0) +``` + +```go !! go +// Go - 结构体创建 +package main + +import "fmt" + +type Point struct { + X int + Y int +} + +// 构造函数(惯用方式) +func NewPoint(x, y int) Point { + return Point{X: x, Y: y} +} + +// 带默认值的构造函数 +func NewDefaultPoint() Point { + return Point{X: 0, Y: 0} +} + +func main() { + p1 := Point{1, 2} // 位置参数(顺序很重要) + p2 := Point{X: 3, Y: 4} // 命名字段 + p3 := Point{} // 零值 + p4 := Point{X: 5} // 部分(Y=0) + p5 := NewPoint(6, 7) // 构造函数 + p6 := NewDefaultPoint() // 默认构造函数 + + fmt.Println(p1, p2, p3, p4, p5, p6) +} +``` + + +### 结构体标签 + + +```python !! py +# Python - 没有内置的结构体标签 +# 会使用装饰器或类属性 +class User: + def __init__(self, name, email): + self.name = name + self.email = email + + @classmethod + def from_dict(cls, data): + return cls(data['name'], data['email']) +``` + +```go !! go +// Go - 结构体标签(元数据) +package main + +import ( + "encoding/json" + "fmt" +) + +type User struct { + ID int `json:"id"` // JSON 键 + Name string `json:"name"` // JSON 键 + Email string `json:"email,omitempty"` // 如果为空则省略 + Password string `json:"-"` // 从不在 JSON 中 + Internal string `json:"internal,internal"` // 多个选项 + DBName string `db:"user_name" json:"name"` // 多个标签 +} + +func main() { + user := User{ + ID: 1, + Name: "Alice", + Email: "alice@example.com", + Password: "secret", + Internal: "internal_data", + DBName: "alice_db", + } + + // JSON 编码会遵守标签 + jsonBytes, _ := json.Marshal(user) + fmt.Println(string(jsonBytes)) + // 输出: {"id":1,"name":"Alice","email":"alice@example.com","internal":"internal_data"} +} +``` + + +### 匿名结构体 + + +```python !! py +# Python - 没有匿名类(使用 namedtuple 或 dict) +from collections import namedtuple +Point = namedtuple('Point', ['x', 'y']) +p = Point(1, 2) + +# 或者使用 dict +p = {'x': 1, 'y': 2} +``` + +```go !! go +// Go - 匿名结构体 +package main + +import "fmt" + +func main() { + // 匿名结构体字面量 + point := struct { + X int + Y int + }{ + X: 1, + Y: 2, + } + + fmt.Println(point) + + // 匿名结构体切片 + people := []struct { + Name string + Age int + }{ + {"Alice", 30}, + {"Bob", 25}, + } + + for _, p := range people { + fmt.Printf("%s is %d\n", p.Name, p.Age) + } +} +``` + + +## 组合优于继承 + +Go 使用组合而不是继承,这更加灵活: + +### 基本组合 + + +```python !! py +# Python - 继承 +class Animal: + def __init__(self, name): + self.name = name + + def speak(self): + pass + +class Dog(Animal): + def __init__(self, name, breed): + super().__init__(name) + self.breed = breed + + def speak(self): + return f"{self.name} says Woof!" + +dog = Dog("Buddy", "Golden Retriever") +print(dog.speak()) +print(dog.name) # 继承 +``` + +```go !! go +// Go - 组合 +package main + +import "fmt" + +type Animal struct { + Name string +} + +func (a Animal) Speak() string { + return fmt.Sprintf("%s makes a sound", a.Name) +} + +type Dog struct { + Animal // 嵌入结构体(组合) + Breed string +} + +func (d Dog) Speak() string { + return fmt.Sprintf("%s says Woof!", d.Name) +} + +func main() { + dog := Dog{ + Animal: Animal{Name: "Buddy"}, + Breed: "Golden Retriever", + } + + fmt.Println(dog.Speak()) + fmt.Println(dog.Name) // 直接访问嵌入字段(提升) + fmt.Println(dog.Breed) + + // 也可以通过嵌入类型访问 + fmt.Println(dog.Animal.Name) +} +``` + + +### 多重组合 + + +```python !! py +# Python - 多重继承 +class Flyable: + def fly(self): + return "Flying" + +class Swimmable: + def swim(self): + return "Swimming" + +class Duck(Flyable, Swimmable): + def __init__(self, name): + self.name = name + +duck = Duck("Donald") +print(duck.fly()) +print(duck.swim()) +``` + +```go !! go +// Go - 多重组合 +package main + +import "fmt" + +type Flyable struct{} + +func (f Flyable) Fly() string { + return "Flying" +} + +type Swimmable struct{} + +func (s Swimmable) Swim() string { + return "Swimming" +} + +type Duck struct { + Flyable + Swimmable + Name string +} + +func main() { + duck := Duck{ + Name: "Donald", + } + + fmt.Println(duck.Fly()) + fmt.Println(duck.Swim()) + fmt.Println(duck.Name) +} +``` + + +### 方法解析和遮蔽 + + +```python !! py +# Python - MRO(方法解析顺序) +class A: + def show(self): + print("A") + +class B(A): + def show(self): + print("B") + +class C(B): + pass + +obj = C() +obj.show() # 打印 "B"(先在 B 中找到) +``` + +```go !! go +// Go - 方法提升和遮蔽 +package main + +import "fmt" + +type Animal struct { + Name string +} + +func (a Animal) Speak() string { + return "Animal sound" +} + +type Dog struct { + Animal + Breed string +} + +// Dog 的 Speak 遮蔽了 Animal 的 Speak +func (d Dog) Speak() string { + return "Woof!" +} + +func main() { + animal := Animal{Name: "Generic"} + dog := Dog{ + Animal: Animal{Name: "Buddy"}, + Breed: "Golden", + } + + fmt.Println(animal.Speak()) // "Animal sound" + fmt.Println(dog.Speak()) // "Woof!"(Dog 的方法) + + // 仍然可以通过嵌入字段调用 Animal 的方法 + fmt.Println(dog.Animal.Speak()) // "Animal sound" +} +``` + + +## 接口 + +接口是隐式实现的——不需要显式的 "implements" 关键字。这是 Go 最强大的特性之一。 + +### 基本接口 + + +```python !! py +# Python - 鸭子类型(隐式) +class Duck: + def quack(self): + return "Quack!" + +class Person: + def quack(self): + return "I'm quacking like a duck!" + +def make_it_quack(thing): + if hasattr(thing, 'quack'): + print(thing.quack()) + +duck = Duck() +person = Person() + +make_it_quack(duck) # 可以工作 +make_it_quack(person) # 也可以工作! +``` + +```go !! go +// Go - 隐式接口 +package main + +import "fmt" + +type Quacker interface { + Quack() string +} + +type Duck struct{} + +func (d Duck) Quack() string { + return "Quack!" +} + +type Person struct{} + +func (p Person) Quack() string { + return "I'm quacking like a duck!" +} + +func makeItQuack(q Quacker) { + fmt.Println(q.Quack()) +} + +func main() { + duck := Duck{} + person := Person{} + + makeItQuack(duck) // 可以工作 + makeItQuack(person) // 也可以工作! + + // 类型自动满足接口 + // 不需要声明 "type Duck implements Quacker" +} +``` + + +### 接口组合 + + +```python !! py +# Python - 多个基类 +class Reader: + def read(self): + pass + +class Writer: + def write(self, data): + pass + +class ReadWriter(Reader, Writer): + pass +``` + +```go !! go +// Go - 接口组合 +package main + +import "fmt" + +type Reader interface { + Read(p []byte) (n int, err error) +} + +type Writer interface { + Write(p []byte) (n int, err error) +} + +type ReadWriter interface { + Reader // 嵌入接口 + Writer // 嵌入接口 +} + +// File 同时实现了 Read 和 Write +type File struct { + name string +} + +func (f File) Read(p []byte) (n int, err error) { + // 实现 + return len(p), nil +} + +func (f File) Write(p []byte) (n int, err error) { + // 实现 + return len(p), nil +} + +func process(rw ReadWriter) { + buf := make([]byte, 1024) + rw.Read(buf) + rw.Write(buf) +} + +func main() { + file := File{name: "test.txt"} + process(file) // File 满足 ReadWriter +} +``` + + +### 空接口 + + +```python !! py +# Python - 任意类型 +def print_anything(value): + print(value) + print(type(value)) + +print_anything(42) +print_anything("hello") +print_anything([1, 2, 3]) +``` + +```go !! go +// Go - 空接口(interface{}) +package main + +import "fmt" + +func printAnything(value interface{}) { + fmt.Println(value) + fmt.Printf("Type: %T\n", value) +} + +func main() { + printAnything(42) + printAnything("hello") + printAnything([]int{1, 2, 3}) + + // 空接口可以容纳任何值 + var anything interface{} + anything = 42 + anything = "hello" + anything = []int{1, 2, 3} +} +``` + + +### 空接口 - 使用时机 + + +```go +// 避免:空接口当类型已知时 +func process(data interface{}) { + // data 是什么类型?不清楚! + // 到处都需要类型断言 +} + +// 推荐:特定接口 +func process(data []byte) { + // 类型清晰 +} + +// 可以:空接口用于真正的异构数据 +func printAll(items ...interface{}) { + for _, item := range items { + fmt.Println(item) + } +} + +// 可以:JSON 反序列化 +var data map[string]interface{} +json.Unmarshal(jsonBytes, &data) + +// 推荐:特定结构体 +type Config struct { + Host string `json:"host"` + Port int `json:"port"` +} +var config Config +json.Unmarshal(jsonBytes, &config) +``` + + +## 类型断言和类型选择 + +### 类型断言 + + +```python !! py +# Python - 类型检查 +def process(value): + if isinstance(value, int): + print(f"Integer: {value}") + return value * 2 + elif isinstance(value, str): + print(f"String: {value}") + return value.upper() + elif isinstance(value, list): + print(f"List: {value}") + return len(value) + else: + print(f"Unknown type: {type(value)}") + return None +``` + +```go !! go +// Go - 类型断言 +package main + +import ( + "fmt" + "strings" +) + +func process(value interface{}) interface{} { + // 带逗号-ok 模式的类型断言 + if i, ok := value.(int); ok { + fmt.Printf("Integer: %d\n", i) + return i * 2 + } + + if s, ok := value.(string); ok { + fmt.Printf("String: %s\n", s) + return strings.ToUpper(s) + } + + if lst, ok := value.([]int); ok { + fmt.Printf("Int slice: %v\n", lst) + return len(lst) + } + + fmt.Printf("Unknown type: %T\n", value) + return nil +} + +// 不安全的类型断言(类型错误会 panic) +func double(value interface{}) int { + return value.(int) * 2 // 如果 value 不是 int 会 panic +} + +func main() { + process(42) + process("hello") + process([]int{1, 2, 3}) + + // 安全使用 + if i, ok := interface{}(42).(int); ok { + fmt.Println("Double:", i*2) + } +} +``` + + +### 类型选择 + + +```python !! py +# Python - 多个类型检查 +def handle(value): + if isinstance(value, int): + return "integer" + elif isinstance(value, str): + return "string" + elif isinstance(value, list): + return "list" + elif isinstance(value, dict): + return "dict" + else: + return f"unknown: {type(value)}" +``` + +```go !! go +// Go - 类型选择 +package main + +import "fmt" + +func handle(value interface{}) string { + switch v := value.(type) { + case int: + return fmt.Sprintf("integer: %d", v) + case string: + return fmt.Sprintf("string: %s", v) + case []int: + return fmt.Sprintf("int slice: %v", v) + case map[string]interface{}: + return fmt.Sprintf("map: %d keys", len(v)) + case nil: + return "nil value" + default: + return fmt.Sprintf("unknown: %T", v) + } +} + +func main() { + fmt.Println(handle(42)) + fmt.Println(handle("hello")) + fmt.Println(handle([]int{1, 2, 3})) + fmt.Println(handle(map[string]interface{}{"a": 1})) + fmt.Println(handle(nil)) +} +``` + + +### 多种情况的类型选择 + + +```go +// 一个 case 中多种类型 +func process(value interface{}) string { + switch v := value.(type) { + case int, int8, int16, int32, int64: + return "integer type" + case uint, uint8, uint16, uint32, uint64: + return "unsigned integer type" + case float32, float64: + return "float type" + case string: + return "string" + case bool: + return "boolean" + default: + return "other type" + } +} +``` + + +## 标准库接口 + +Go 标准库定义了许多有用的接口: + +### Stringer 接口 + + +```python !! py +# Python - __str__ 和 __repr__ +class Person: + def __init__(self, name, age): + self.name = name + self.age = age + + def __str__(self): + return f"Person(name={self.name})" + + def __repr__(self): + return f"Person(name={self.name}, age={self.age})" + +p = Person("Alice", 30) +print(p) # 使用 __str__ +print(repr(p)) # 使用 __repr__ +``` + +```go !! go +// Go - Stringer 接口 +package main + +import ( + "fmt" +) + +type Person struct { + Name string + Age int +} + +// Stringer 接口(类似 __str__) +func (p Person) String() string { + return fmt.Sprintf("Person(name=%s)", p.Name) +} + +func main() { + person := Person{Name: "Alice", Age: 30} + + // fmt.Println 自动调用 String() + fmt.Println(person) + + // String 也使用 String() + fmt.Sprintf("%s", person) +} +``` + + +### Error 接口 + + +```python !! py +# Python - Exception +class ValidationError(Exception): + def __init__(self, message, field): + self.message = message + self.field = field + + def __str__(self): + return f"{self.field}: {self.message}" + +raise ValidationError("Invalid email", "email") +``` + +```go !! go +// Go - Error 接口 +package main + +import "fmt" + +// Error 接口是内置的: +// type error interface { +// Error() string +// } + +type ValidationError struct { + Message string + Field string +} + +func (e ValidationError) Error() string { + return fmt.Sprintf("%s: %s", e.Field, e.Message) +} + +func validateEmail(email string) error { + if email == "" { + return ValidationError{ + Message: "cannot be empty", + Field: "email", + } + } + return nil +} + +func main() { + if err := validateEmail(""); err != nil { + fmt.Println(err) // 自动调用 Error() + } +} +``` + + +### 其他常用接口 + + +```go +// Reader 接口(io 包) +type Reader interface { + Read(p []byte) (n int, err error) +} + +// Writer 接口(io 包) +type Writer interface { + Write(p []byte) (n int, err error) +} + +// Closer 接口(io 包) +type Closer interface { + Close() error +} + +// ReadWriter 接口(组合) +type ReadWriter interface { + Reader + Writer +} + +// ReadWriteCloser 接口 +type ReadWriteCloser interface { + Reader + Writer + Closer +} +``` + + +## 接口最佳实践 + +### 接受接口,返回结构体 + + +```python !! py +# Python - 返回特定类型 +def get_user(): + return User("Alice", 30) + +# 接受鸭子类型接口 +def process(obj): + if hasattr(obj, 'process'): + obj.process() +``` + +```go !! go +// Go - 接受接口,返回结构体 +package main + +// 好:返回具体类型 +func NewUser(name string, age int) User { + return User{Name: name, Age: age} +} + +// 好:接受接口 +func processUser(u UserInterface) { + u.Process() +} + +// 避免:返回接口(除非必要) +func getUser() UserInterface { // 不要这样做 + return User{Name: "Alice"} +} + +// 推荐:返回具体类型 +func getUser() User { // 应该这样做 + return User{Name: "Alice"} +} + +// 例外:返回 nil 时返回接口 +func findUser(id int) (User, error) { + if id == 0 { + return User{}, fmt.Errorf("invalid id") + } + // ... +} +``` + + +### 小接口 + + +```go +// 好:小而专注的接口 +type Reader interface { + Read(p []byte) (n int, err error) +} + +type Writer interface { + Write(p []byte) (n int, err error) +} + +// 避免:大型、单体接口 +type FileHandler interface { + Read(p []byte) (n int, err error) + Write(p []byte) (n int, err error) + Close() error + Sync() error + Stat() (os.FileInfo, error) + // ... 更多方法 +} + +// 推荐:组合小接口 +type ReadWriteCloser interface { + Reader + Writer + io.Closer +} +``` + + +### 接口 Nil 陷阱 + + +```go +// 小心 nil 接口值! +package main + +import "fmt" + +type MyError struct { + Message string +} + +func (e *MyError) Error() string { + return e.Message +} + +func returnsError() error { + var p *MyError = nil + return p // 返回 nil 指针,但非 nil 接口! +} + +func returnsErrorCorrect() error { + var p *MyError = nil + return nil // 返回 nil 接口 +} + +func main() { + err := returnsError() + if err != nil { + fmt.Println("Error is not nil!") + // 这会打印!即使 p 是 nil! + // 接口本身不是 nil + } + + // 正确方式 + err2 := returnsErrorCorrect() + if err2 != nil { + fmt.Println("Error2 is not nil!") + } + // 这不会打印 +} +``` + + +## 指针接收者 vs 值接收者 + +### 何时使用每种 + + +```python !! py +# Python - 方法可以修改 +class Counter: + def __init__(self): + self.count = 0 + + def increment(self): + self.count += 1 # 修改对象 + + def get_count(self): + return self.count + + def double(self): + self.count *= 2 # 修改对象 +``` + +```go !! go +// Go - 仔细选择接收者类型 +package main + +import "fmt" + +type Counter struct { + count int +} + +// 值接收者 - 不能修改 +// 使用场景: +// - 方法不需要修改 +// - 结构体很小(复制成本低) +// - 一致性(如果所有方法都使用值) +func (c Counter) GetCount() int { + return c.count +} + +// 指针接收者 - 可以修改 +// 使用场景: +// - 方法需要修改 +// - 结构体很大(避免复制) +// - 一致性(如果任何方法使用指针) +func (c *Counter) Increment() { + c.count++ +} + +// 即使对于大型结构体的只读方法, +// 也使用指针来避免复制 +func (c *Counter) Double() { + c.count *= 2 +} + +// 对于小型不可变值,值接收者可以 +type Point struct { + X, Y int +} + +func (p Point) Distance() int { + return p.X*p.X + p.Y*p.Y +} + +func main() { + counter := Counter{count: 0} + counter.Increment() + counter.Double() + fmt.Println(counter.GetCount()) // 2 +} +``` + + +### 接收者类型指南 + + +```go +// 指南 1:如果方法需要修改则使用指针接收者 +func (c *Counter) Increment() { + c.count++ +} + +// 指南 2:大型结构体使用指针接收者 +type BigStruct struct { + data [1024]int +} + +func (b *BigStruct) Process() { + // 避免复制 1024 个 int +} + +// 指南 3:小型不可变值使用值接收者 +type Point struct { + X, Y int +} + +func (p Point) String() string { + return fmt.Sprintf("(%d, %d)", p.X, p.Y) +} + +// 指南 4:保持一致 - 不要混合接收者类型 +// 不好:混合接收者 +type MyStruct struct { + value int +} + +func (m MyStruct) GetValue() int { + return m.value +} + +func (m *MyStruct) SetValue(v int) { + m.value = v +} + +// 好:所有指针接收者 +func (m *MyStruct) GetValue() int { + return m.value +} + +func (m *MyStruct) SetValue(v int) { + m.value = v +} +``` + + +## 实际示例 + +### JSON 编码/解码 + + +```python !! py +# Python - JSON 序列化 +import json + +class User: + def __init__(self, name, email): + self.name = name + self.email = email + + def to_dict(self): + return { + 'name': self.name, + 'email': self.email + } + + @classmethod + def from_dict(cls, data): + return cls(data['name'], data['email']) + +user = User("Alice", "alice@example.com") +json_str = json.dumps(user.to_dict()) +parsed = User.from_dict(json.loads(json_str)) +``` + +```go !! go +// Go - JSON 和结构体标签 +package main + +import ( + "encoding/json" + "fmt" +) + +type User struct { + Name string `json:"name"` + Email string `json:"email"` + Age int `json:"age,omitempty"` // 如果为零值则省略 +} + +func main() { + // 编码 + user := User{ + Name: "Alice", + Email: "alice@example.com", + } + + jsonBytes, err := json.Marshal(user) + if err != nil { + panic(err) + } + fmt.Println(string(jsonBytes)) + // {"name":"Alice","email":"alice@example.com"} + + // 解码 + var parsed User + if err := json.Unmarshal(jsonBytes, &parsed); err != nil { + panic(err) + } + fmt.Printf("%+v\n", parsed) +} +``` + + +### 数据库模型 + + +```python !! py +# Python - SQLAlchemy 模型 +from sqlalchemy import Column, Integer, String +from sqlalchemy.ext.declarative import declarative_base + +Base = declarative_base() + +class User(Base): + __tablename__ = 'users' + + id = Column(Integer, primary_key=True) + name = Column(String(100)) + email = Column(String(200)) +``` + +```go !! go +// Go - 带结构体标签的数据库模型 +package main + +type User struct { + ID int `json:"id" db:"id"` + Name string `json:"name" db:"name"` + Email string `json:"email" db:"email"` + CreatedAt time.Time `json:"created_at" db:"created_at"` + UpdatedAt time.Time `json:"updated_at" db:"updated_at"` +} + +// 表名映射 +func (User) TableName() string { + return "users" +} + +// 自定义类型的 Scanner 和 Valuer +type NullString struct { + String string + Valid bool +} + +func (ns *NullString) Scan(value interface{}) error { + if value == nil { + ns.String, ns.Valid = "", false + return nil + } + ns.Valid = true + return nil +} +``` + + +## 总结 + +在本模块中,你学习了: + +1. **结构体** - Go 的类替代品 +2. **字段可见性** - 导出 vs 未导出 +3. **结构体标签** - JSON、数据库等元数据 +4. **匿名结构体** - 快速、一次性结构 +5. **组合** - 优于继承 +6. **多重组合** - 组合多个类型 +7. **方法提升** - 访问嵌入方法 +8. **接口** - 隐式实现,灵活 +9. **空接口** - 容纳任何值(小心使用) +10. **类型断言** - 检查和转换类型 +11. **类型选择** - 类型切换 +12. **标准接口** - Stringer、Error、Reader、Writer +13. **接口最佳实践** - 小而专注的接口 +14. **接收者类型** - 指针 vs 值 +15. **Nil 接口陷阱** - 接口 vs 具体 nil + +## 与 Python 的主要区别 + +| Python | Go | +|--------|-----| +| 类和继承 | 结构体和组合 | +| `__init__` 初始化 | 结构体字面量或构造函数 | +| 通过 `__` 前缀私有 | 未导出(小写) | +| `self` 参数 | 接收者参数 | +| 魔术方法(`__str__`、`__eq__`) | 接口(Stringer 等) | +| 鸭子类型(运行时) | 接口(编译时 + 鸭子类型) | +| 多重继承 | 组合 + 接口组合 | +| `isinstance()` 检查 | 类型断言和类型选择 | + +## 最佳实践 + +1. **优先使用组合**而非继承 +2. **保持接口小** - 1-2 个方法最理想 +3. **接受接口,返回结构体** - 返回值要具体 +4. **使用指针接收者**当需要修改或结构体较大时 +5. **保持一致**接收者类型 +6. **使用结构体标签**作为序列化元数据 +7. **避免空接口**除非真正必要 +8. **小心 nil 接口** - 理解陷阱 +9. **嵌入用于组合** - 提升字段/方法 +10. **实现标准接口** - Stringer、Error 等 + +## 练习 + +1. 创建一个带有导出和未导出字段的结构体 +2. 在自定义类型上实现 Stringer 接口 +3. 使用组合来组合多个结构体 +4. 编写类型选择来处理不同类型 +5. 创建指针接收者和值接收者并比较行为 +6. 使用标签实现自定义 JSON 编组 +7. 创建一个接口并让多个类型满足它 +8. 使用结构体标签构建小型数据库模型 + +## 下一步 + +下一模块:**包管理** - 理解 Go 模块和导入。 diff --git a/content/docs/py2go/module-04-structs-interfaces.zh-tw.mdx b/content/docs/py2go/module-04-structs-interfaces.zh-tw.mdx new file mode 100644 index 0000000..5fae904 --- /dev/null +++ b/content/docs/py2go/module-04-structs-interfaces.zh-tw.mdx @@ -0,0 +1,1359 @@ +--- +title: "模組 4: 結構體和介面" +description: "Go 物件導向程式設計方法,無需類別" +--- + +## 簡介 + +Go 沒有類別或繼承。相反,它使用**結構體(struct)**來儲存資料,使用**介面(interface)**來定義行為。這是一種更簡單、更靈活的 OOP 方法,鼓勵組合優於繼承。 + +## 結構體基礎 + +### 定義結構體 + + +```python !! py +# Python - 帶有 __init__ 的類別 +class Person: + def __init__(self, name, age): + self.name = name + self.age = age + + def greet(self): + return f"Hi, I'm {self.name}" + +person = Person("Alice", 30) +print(person.greet()) +``` + +```go !! go +// Go - 結構體 +package main + +import "fmt" + +type Person struct { + Name string + Age int +} + +func (p Person) Greet() string { + return fmt.Sprintf("Hi, I'm %s", p.Name) +} + +func main() { + person := Person{Name: "Alice", Age: 30} + fmt.Println(person.Greet()) +} +``` + + +### 結構體欄位可見性 + + +```python !! py +# Python - 公共 vs 私有(約定) +class Person: + def __init__(self, name, age, ssn): + self.name = name # 公共 + self._age = age # 受保護(約定) + self.__ssn = ssn # 私有(名稱改寫) + + def get_age(self): + return self._age +``` + +```go !! go +// Go - 匯出 vs 未匯出 +package main + +import "fmt" + +type Person struct { + Name string // 匯出(首字母大寫) + age int // 未匯出(小寫) + SSN string // 匯出 +} + +func (p Person) GetAge() int { + return p.age // 可以在套件內存取 +} + +func NewPerson(name string, age int, ssn string) Person { + return Person{ + Name: name, + age: age, // 可以設定未匯出欄位 + SSN: ssn, + } +} + +func main() { + p := NewPerson("Alice", 30, "123-45-6789") + fmt.Println(p.Name) // OK - 匯出 + fmt.Println(p.GetAge()) // OK - 方法 + // fmt.Println(p.age) // 錯誤:未匯出 +} +``` + + +### 建立和初始化結構體 + + +```python !! py +# Python - 類別實例化 +class Point: + def __init__(self, x=0, y=0): + self.x = x + self.y = y + +p1 = Point(1, 2) +p2 = Point(x=3, y=4) +p3 = Point() # 預設值 +p4 = Point(5) # 部分(y=0) +``` + +```go !! go +// Go - 結構體建立 +package main + +import "fmt" + +type Point struct { + X int + Y int +} + +// 建構函式(慣用方式) +func NewPoint(x, y int) Point { + return Point{X: x, Y: y} +} + +// 帶預設值的建構函式 +func NewDefaultPoint() Point { + return Point{X: 0, Y: 0} +} + +func main() { + p1 := Point{1, 2} // 位置參數(順序很重要) + p2 := Point{X: 3, Y: 4} // 命名欄位 + p3 := Point{} // 零值 + p4 := Point{X: 5} // 部分(Y=0) + p5 := NewPoint(6, 7) // 建構函式 + p6 := NewDefaultPoint() // 預設建構函式 + + fmt.Println(p1, p2, p3, p4, p5, p6) +} +``` + + +### 結構體標籤 + + +```python !! py +# Python - 沒有內建的結構體標籤 +# 會使用裝飾器或類別屬性 +class User: + def __init__(self, name, email): + self.name = name + self.email = email + + @classmethod + def from_dict(cls, data): + return cls(data['name'], data['email']) +``` + +```go !! go +// Go - 結構體標籤(元資料) +package main + +import ( + "encoding/json" + "fmt" +) + +type User struct { + ID int `json:"id"` // JSON 鍵 + Name string `json:"name"` // JSON 鍵 + Email string `json:"email,omitempty"` // 如果為空則省略 + Password string `json:"-"` // 從不在 JSON 中 + Internal string `json:"internal,internal"` // 多個選項 + DBName string `db:"user_name" json:"name"` // 多個標籤 +} + +func main() { + user := User{ + ID: 1, + Name: "Alice", + Email: "alice@example.com", + Password: "secret", + Internal: "internal_data", + DBName: "alice_db", + } + + // JSON 編碼會遵守標籤 + jsonBytes, _ := json.Marshal(user) + fmt.Println(string(jsonBytes)) + // 輸出: {"id":1,"name":"Alice","email":"alice@example.com","internal":"internal_data"} +} +``` + + +### 匿名結構體 + + +```python !! py +# Python - 沒有匿名類別(使用 namedtuple 或 dict) +from collections import namedtuple +Point = namedtuple('Point', ['x', 'y']) +p = Point(1, 2) + +# 或者使用 dict +p = {'x': 1, 'y': 2} +``` + +```go !! go +// Go - 匿名結構體 +package main + +import "fmt" + +func main() { + // 匿名結構體字面量 + point := struct { + X int + Y int + }{ + X: 1, + Y: 2, + } + + fmt.Println(point) + + // 匿名結構體切片 + people := []struct { + Name string + Age int + }{ + {"Alice", 30}, + {"Bob", 25}, + } + + for _, p := range people { + fmt.Printf("%s is %d\n", p.Name, p.Age) + } +} +``` + + +## 組合優於繼承 + +Go 使用組合而不是繼承,這更加靈活: + +### 基本組合 + + +```python !! py +# Python - 繼承 +class Animal: + def __init__(self, name): + self.name = name + + def speak(self): + pass + +class Dog(Animal): + def __init__(self, name, breed): + super().__init__(name) + self.breed = breed + + def speak(self): + return f"{self.name} says Woof!" + +dog = Dog("Buddy", "Golden Retriever") +print(dog.speak()) +print(dog.name) # 繼承 +``` + +```go !! go +// Go - 組合 +package main + +import "fmt" + +type Animal struct { + Name string +} + +func (a Animal) Speak() string { + return fmt.Sprintf("%s makes a sound", a.Name) +} + +type Dog struct { + Animal // 嵌入結構體(組合) + Breed string +} + +func (d Dog) Speak() string { + return fmt.Sprintf("%s says Woof!", d.Name) +} + +func main() { + dog := Dog{ + Animal: Animal{Name: "Buddy"}, + Breed: "Golden Retriever", + } + + fmt.Println(dog.Speak()) + fmt.Println(dog.Name) // 直接存取嵌入欄位(提升) + fmt.Println(dog.Breed) + + // 也可以透過嵌入類型存取 + fmt.Println(dog.Animal.Name) +} +``` + + +### 多重組合 + + +```python !! py +# Python - 多重繼承 +class Flyable: + def fly(self): + return "Flying" + +class Swimmable: + def swim(self): + return "Swimming" + +class Duck(Flyable, Swimmable): + def __init__(self, name): + self.name = name + +duck = Duck("Donald") +print(duck.fly()) +print(duck.swim()) +``` + +```go !! go +// Go - 多重组合 +package main + +import "fmt" + +type Flyable struct{} + +func (f Flyable) Fly() string { + return "Flying" +} + +type Swimmable struct{} + +func (s Swimmable) Swim() string { + return "Swimming" +} + +type Duck struct { + Flyable + Swimmable + Name string +} + +func main() { + duck := Duck{ + Name: "Donald", + } + + fmt.Println(duck.Fly()) + fmt.Println(duck.Swim()) + fmt.Println(duck.Name) +} +``` + + +### 方法解析和遮蔽 + + +```python !! py +# Python - MRO(方法解析順序) +class A: + def show(self): + print("A") + +class B(A): + def show(self): + print("B") + +class C(B): + pass + +obj = C() +obj.show() # 列印 "B"(先在 B 中找到) +``` + +```go !! go +// Go - 方法提升和遮蔽 +package main + +import "fmt" + +type Animal struct { + Name string +} + +func (a Animal) Speak() string { + return "Animal sound" +} + +type Dog struct { + Animal + Breed string +} + +// Dog 的 Speak 遮蔽了 Animal 的 Speak +func (d Dog) Speak() string { + return "Woof!" +} + +func main() { + animal := Animal{Name: "Generic"} + dog := Dog{ + Animal: Animal{Name: "Buddy"}, + Breed: "Golden", + } + + fmt.Println(animal.Speak()) // "Animal sound" + fmt.Println(dog.Speak()) // "Woof!"(Dog 的方法) + + // 仍然可以透過嵌入欄位呼叫 Animal 的方法 + fmt.Println(dog.Animal.Speak()) // "Animal sound" +} +``` + + +## 介面 + +介面是隱式實作的——不需要顯式的 "implements" 關鍵字。這是 Go 最強大的特性之一。 + +### 基本介面 + + +```python !! py +# Python - 鴨子類型(隱式) +class Duck: + def quack(self): + return "Quack!" + +class Person: + def quack(self): + return "I'm quacking like a duck!" + +def make_it_quack(thing): + if hasattr(thing, 'quack'): + print(thing.quack()) + +duck = Duck() +person = Person() + +make_it_quack(duck) # 可以工作 +make_it_quack(person) # 也可以工作! +``` + +```go !! go +// Go - 隱式介面 +package main + +import "fmt" + +type Quacker interface { + Quack() string +} + +type Duck struct{} + +func (d Duck) Quack() string { + return "Quack!" +} + +type Person struct{} + +func (p Person) Quack() string { + return "I'm quacking like a duck!" +} + +func makeItQuack(q Quacker) { + fmt.Println(q.Quack()) +} + +func main() { + duck := Duck{} + person := Person{} + + makeItQuack(duck) // 可以工作 + makeItQuack(person) // 也可以工作! + + // 類型自動滿足介面 + // 不需要宣告 "type Duck implements Quacker" +} +``` + + +### 介面組合 + + +```python !! py +# Python - 多個基底類別 +class Reader: + def read(self): + pass + +class Writer: + def write(self, data): + pass + +class ReadWriter(Reader, Writer): + pass +``` + +```go !! go +// Go - 介面組合 +package main + +import "fmt" + +type Reader interface { + Read(p []byte) (n int, err error) +} + +type Writer interface { + Write(p []byte) (n int, err error) +} + +type ReadWriter interface { + Reader // 嵌入介面 + Writer // 嵌入介面 +} + +// File 同時實作了 Read 和 Write +type File struct { + name string +} + +func (f File) Read(p []byte) (n int, err error) { + // 實作 + return len(p), nil +} + +func (f File) Write(p []byte) (n int, err error) { + // 實作 + return len(p), nil +} + +func process(rw ReadWriter) { + buf := make([]byte, 1024) + rw.Read(buf) + rw.Write(buf) +} + +func main() { + file := File{name: "test.txt"} + process(file) // File 滿足 ReadWriter +} +``` + + +### 空介面 + + +```python !! py +# Python - 任意類型 +def print_anything(value): + print(value) + print(type(value)) + +print_anything(42) +print_anything("hello") +print_anything([1, 2, 3]) +``` + +```go !! go +// Go - 空介面(interface{}) +package main + +import "fmt" + +func printAnything(value interface{}) { + fmt.Println(value) + fmt.Printf("Type: %T\n", value) +} + +func main() { + printAnything(42) + printAnything("hello") + printAnything([]int{1, 2, 3}) + + // 空介面可以容納任何值 + var anything interface{} + anything = 42 + anything = "hello" + anything = []int{1, 2, 3} +} +``` + + +### 空介面 - 使用時機 + + +```go +// 避免:空介面當類型已知時 +func process(data interface{}) { + // data 是什麼類型?不清楚! + // 到處都需要類型斷言 +} + +// 推薦:特定介面 +func process(data []byte) { + // 類型清晰 +} + +// 可以:空介面用於真正的異質資料 +func printAll(items ...interface{}) { + for _, item := range items { + fmt.Println(item) + } +} + +// 可以:JSON 反序列化 +var data map[string]interface{} +json.Unmarshal(jsonBytes, &data) + +// 推薦:特定結構體 +type Config struct { + Host string `json:"host"` + Port int `json:"port"` +} +var config Config +json.Unmarshal(jsonBytes, &config) +``` + + +## 類型斷言和類型選擇 + +### 類型斷言 + + +```python !! py +# Python - 類型檢查 +def process(value): + if isinstance(value, int): + print(f"Integer: {value}") + return value * 2 + elif isinstance(value, str): + print(f"String: {value}") + return value.upper() + elif isinstance(value, list): + print(f"List: {value}") + return len(value) + else: + print(f"Unknown type: {type(value)}") + return None +``` + +```go !! go +// Go - 類型斷言 +package main + +import ( + "fmt" + "strings" +) + +func process(value interface{}) interface{} { + // 帶逗號-ok 模式的類型斷言 + if i, ok := value.(int); ok { + fmt.Printf("Integer: %d\n", i) + return i * 2 + } + + if s, ok := value.(string); ok { + fmt.Printf("String: %s\n", s) + return strings.ToUpper(s) + } + + if lst, ok := value.([]int); ok { + fmt.Printf("Int slice: %v\n", lst) + return len(lst) + } + + fmt.Printf("Unknown type: %T\n", value) + return nil +} + +// 不安全的類型斷言(類型錯誤會 panic) +func double(value interface{}) int { + return value.(int) * 2 // 如果 value 不是 int 會 panic +} + +func main() { + process(42) + process("hello") + process([]int{1, 2, 3}) + + // 安全使用 + if i, ok := interface{}(42).(int); ok { + fmt.Println("Double:", i*2) + } +} +``` + + +### 類型選擇 + + +```python !! py +# Python - 多個類型檢查 +def handle(value): + if isinstance(value, int): + return "integer" + elif isinstance(value, str): + return "string" + elif isinstance(value, list): + return "list" + elif isinstance(value, dict): + return "dict" + else: + return f"unknown: {type(value)}" +``` + +```go !! go +// Go - 類型選擇 +package main + +import "fmt" + +func handle(value interface{}) string { + switch v := value.(type) { + case int: + return fmt.Sprintf("integer: %d", v) + case string: + return fmt.Sprintf("string: %s", v) + case []int: + return fmt.Sprintf("int slice: %v", v) + case map[string]interface{}: + return fmt.Sprintf("map: %d keys", len(v)) + case nil: + return "nil value" + default: + return fmt.Sprintf("unknown: %T", v) + } +} + +func main() { + fmt.Println(handle(42)) + fmt.Println(handle("hello")) + fmt.Println(handle([]int{1, 2, 3})) + fmt.Println(handle(map[string]interface{}{"a": 1})) + fmt.Println(handle(nil)) +} +``` + + +### 多種情況的類型選擇 + + +```go +// 一個 case 中多種類型 +func process(value interface{}) string { + switch v := value.(type) { + case int, int8, int16, int32, int64: + return "integer type" + case uint, uint8, uint16, uint32, uint64: + return "unsigned integer type" + case float32, float64: + return "float type" + case string: + return "string" + case bool: + return "boolean" + default: + return "other type" + } +} +``` + + +## 標準庫介面 + +Go 標準庫定義了許多有用的介面: + +### Stringer 介面 + + +```python !! py +# Python - __str__ 和 __repr__ +class Person: + def __init__(self, name, age): + self.name = name + self.age = age + + def __str__(self): + return f"Person(name={self.name})" + + def __repr__(self): + return f"Person(name={self.name}, age={self.age})" + +p = Person("Alice", 30) +print(p) # 使用 __str__ +print(repr(p)) # 使用 __repr__ +``` + +```go !! go +// Go - Stringer 介面 +package main + +import ( + "fmt" +) + +type Person struct { + Name string + Age int +} + +// Stringer 介面(類似 __str__) +func (p Person) String() string { + return fmt.Sprintf("Person(name=%s)", p.Name) +} + +func main() { + person := Person{Name: "Alice", Age: 30} + + // fmt.Println 自動呼叫 String() + fmt.Println(person) + + // String 也使用 String() + fmt.Sprintf("%s", person) +} +``` + + +### Error 介面 + + +```python !! py +# Python - Exception +class ValidationError(Exception): + def __init__(self, message, field): + self.message = message + self.field = field + + def __str__(self): + return f"{self.field}: {self.message}" + +raise ValidationError("Invalid email", "email") +``` + +```go !! go +// Go - Error 介面 +package main + +import "fmt" + +// Error 介面是內建的: +// type error interface { +// Error() string +// } + +type ValidationError struct { + Message string + Field string +} + +func (e ValidationError) Error() string { + return fmt.Sprintf("%s: %s", e.Field, e.Message) +} + +func validateEmail(email string) error { + if email == "" { + return ValidationError{ + Message: "cannot be empty", + Field: "email", + } + } + return nil +} + +func main() { + if err := validateEmail(""); err != nil { + fmt.Println(err) // 自動呼叫 Error() + } +} +``` + + +### 其他常用介面 + + +```go +// Reader 介面(io 套件) +type Reader interface { + Read(p []byte) (n int, err error) +} + +// Writer 介面(io 套件) +type Writer interface { + Write(p []byte) (n int, err error) +} + +// Closer 介面(io 套件) +type Closer interface { + Close() error +} + +// ReadWriter 介面(組合) +type ReadWriter interface { + Reader + Writer +} + +// ReadWriteCloser 介面 +type ReadWriteCloser interface { + Reader + Writer + Closer +} +``` + + +## 介面最佳實踐 + +### 接受介面,返回結構體 + + +```python !! py +# Python - 返回特定類型 +def get_user(): + return User("Alice", 30) + +# 接受鴨子類型介面 +def process(obj): + if hasattr(obj, 'process'): + obj.process() +``` + +```go !! go +// Go - 接受介面,返回結構體 +package main + +// 好:返回具體類型 +func NewUser(name string, age int) User { + return User{Name: name, Age: age} +} + +// 好:接受介面 +func processUser(u UserInterface) { + u.Process() +} + +// 避免:返回介面(除非必要) +func getUser() UserInterface { // 不要這樣做 + return User{Name: "Alice"} +} + +// 推薦:返回具體類型 +func getUser() User { // 應該這樣做 + return User{Name: "Alice"} +} + +// 例外:返回 nil 時返回介面 +func findUser(id int) (User, error) { + if id == 0 { + return User{}, fmt.Errorf("invalid id") + } + // ... +} +``` + + +### 小介面 + + +```go +// 好:小而專注的介面 +type Reader interface { + Read(p []byte) (n int, err error) +} + +type Writer interface { + Write(p []byte) (n int, err error) +} + +// 避免:大型、單體介面 +type FileHandler interface { + Read(p []byte) (n int, err error) + Write(p []byte) (n int, err error) + Close() error + Sync() error + Stat() (os.FileInfo, error) + // ... 更多方法 +} + +// 推薦:組合小介面 +type ReadWriteCloser interface { + Reader + Writer + io.Closer +} +``` + + +### 介面 Nil 陷阱 + + +```go +// 小心 nil 介面值! +package main + +import "fmt" + +type MyError struct { + Message string +} + +func (e *MyError) Error() string { + return e.Message +} + +func returnsError() error { + var p *MyError = nil + return p // 返回 nil 指標,但非 nil 介面! +} + +func returnsErrorCorrect() error { + var p *MyError = nil + return nil // 返回 nil 介面 +} + +func main() { + err := returnsError() + if err != nil { + fmt.Println("Error is not nil!") + // 這會列印!即使 p 是 nil! + // 介面本身不是 nil + } + + // 正確方式 + err2 := returnsErrorCorrect() + if err2 != nil { + fmt.Println("Error2 is not nil!") + } + // 這不會列印 +} +``` + + +## 指標接收者 vs 值接收者 + +### 何時使用每種 + + +```python !! py +# Python - 方法可以修改 +class Counter: + def __init__(self): + self.count = 0 + + def increment(self): + self.count += 1 # 修改物件 + + def get_count(self): + return self.count + + def double(self): + self.count *= 2 # 修改物件 +``` + +```go !! go +// Go - 仔細選擇接收者類型 +package main + +import "fmt" + +type Counter struct { + count int +} + +// 值接收者 - 不能修改 +// 使用場景: +// - 方法不需要修改 +// - 結構體很小(複製成本低) +// - 一致性(如果所有方法都使用值) +func (c Counter) GetCount() int { + return c.count +} + +// 指標接收者 - 可以修改 +// 使用場景: +// - 方法需要修改 +// - 結構體很大(避免複製) +// - 一致性(如果任何方法使用指標) +func (c *Counter) Increment() { + c.count++ +} + +// 即使對於大型結構體的唯讀方法, +// 也使用指標來避免複製 +func (c *Counter) Double() { + c.count *= 2 +} + +// 對於小型不可變值,值接收者可以 +type Point struct { + X, Y int +} + +func (p Point) Distance() int { + return p.X*p.X + p.Y*p.Y +} + +func main() { + counter := Counter{count: 0} + counter.Increment() + counter.Double() + fmt.Println(counter.GetCount()) // 2 +} +``` + + +### 接收者類型指南 + + +```go +// 指南 1:如果方法需要修改則使用指標接收者 +func (c *Counter) Increment() { + c.count++ +} + +// 指南 2:大型結構體使用指標接收者 +type BigStruct struct { + data [1024]int +} + +func (b *BigStruct) Process() { + // 避免複製 1024 個 int +} + +// 指南 3:小型不可變值使用值接收者 +type Point struct { + X, Y int +} + +func (p Point) String() string { + return fmt.Sprintf("(%d, %d)", p.X, p.Y) +} + +// 指南 4:保持一致 - 不要混合接收者類型 +// 不好:混合接收者 +type MyStruct struct { + value int +} + +func (m MyStruct) GetValue() int { + return m.value +} + +func (m *MyStruct) SetValue(v int) { + m.value = v +} + +// 好:所有指標接收者 +func (m *MyStruct) GetValue() int { + return m.value +} + +func (m *MyStruct) SetValue(v int) { + m.value = v +} +``` + + +## 實際範例 + +### JSON 編碼/解碼 + + +```python !! py +# Python - JSON 序列化 +import json + +class User: + def __init__(self, name, email): + self.name = name + self.email = email + + def to_dict(self): + return { + 'name': self.name, + 'email': self.email + } + + @classmethod + def from_dict(cls, data): + return cls(data['name'], data['email']) + +user = User("Alice", "alice@example.com") +json_str = json.dumps(user.to_dict()) +parsed = User.from_dict(json.loads(json_str)) +``` + +```go !! go +// Go - JSON 和結構體標籤 +package main + +import ( + "encoding/json" + "fmt" +) + +type User struct { + Name string `json:"name"` + Email string `json:"email"` + Age int `json:"age,omitempty"` // 如果為零值則省略 +} + +func main() { + // 編碼 + user := User{ + Name: "Alice", + Email: "alice@example.com", + } + + jsonBytes, err := json.Marshal(user) + if err != nil { + panic(err) + } + fmt.Println(string(jsonBytes)) + // {"name":"Alice","email":"alice@example.com"} + + // 解碼 + var parsed User + if err := json.Unmarshal(jsonBytes, &parsed); err != nil { + panic(err) + } + fmt.Printf("%+v\n", parsed) +} +``` + + +### 資料庫模型 + + +```python !! py +# Python - SQLAlchemy 模型 +from sqlalchemy import Column, Integer, String +from sqlalchemy.ext.declarative import declarative_base + +Base = declarative_base() + +class User(Base): + __tablename__ = 'users' + + id = Column(Integer, primary_key=True) + name = Column(String(100)) + email = Column(String(200)) +``` + +```go !! go +// Go - 帶結構體標籤的資料庫模型 +package main + +type User struct { + ID int `json:"id" db:"id"` + Name string `json:"name" db:"name"` + Email string `json:"email" db:"email"` + CreatedAt time.Time `json:"created_at" db:"created_at"` + UpdatedAt time.Time `json:"updated_at" db:"updated_at"` +} + +// 表名映射 +func (User) TableName() string { + return "users" +} + +// 自訂類型的 Scanner 和 Valuer +type NullString struct { + String string + Valid bool +} + +func (ns *NullString) Scan(value interface{}) error { + if value == nil { + ns.String, ns.Valid = "", false + return nil + } + ns.Valid = true + return nil +} +``` + + +## 總結 + +在本模組中,你學習了: + +1. **結構體** - Go 的類別替代品 +2. **欄位可見性** - 匯出 vs 未匯出 +3. **結構體標籤** - JSON、資料庫等元資料 +4. **匿名結構體** - 快速、一次性結構 +5. **組合** - 優於繼承 +6. **多重组合** - 組合多個類型 +7. **方法提升** - 存取嵌入方法 +8. **介面** - 隱式實作,靈活 +9. **空介面** - 容納任何值(小心使用) +10. **類型斷言** - 檢查和轉換類型 +11. **類型選擇** - 類型切換 +12. **標準介面** - Stringer、Error、Reader、Writer +13. **介面最佳實踐** - 小而專注的介面 +14. **接收者類型** - 指標 vs 值 +15. **Nil 介面陷阱** - 介面 vs 具體 nil + +## 與 Python 的主要區別 + +| Python | Go | +|--------|-----| +| 類別和繼承 | 結構體和組合 | +| `__init__` 初始化 | 結構體字面量或建構函式 | +| 透過 `__` 前綴私有 | 未匯出(小寫) | +| `self` 參數 | 接收者參數 | +| 魔術方法(`__str__`、`__eq__`) | 介面(Stringer 等) | +| 鴨子類型(執行時) | 介面(編譯時 + 鴨子類型) | +| 多重繼承 | 組合 + 介面組合 | +| `isinstance()` 檢查 | 類型斷言和類型選擇 | + +## 最佳實踐 + +1. **優先使用組合**而非繼承 +2. **保持介面小** - 1-2 個方法最理想 +3. **接受介面,返回結構體** - 返回值要具體 +4. **使用指標接收者**當需要修改或結構體較大時 +5. **保持一致**接收者類型 +6. **使用結構體標籤**作為序列化元資料 +7. **避免空介面**除非真正必要 +8. **小心 nil 介面** - 理解陷阱 +9. **嵌入用於組合** - 提升欄位/方法 +10. **實作標準介面** - Stringer、Error 等 + +## 練習 + +1. 建立一個帶有匯出和未匯出欄位的結構體 +2. 在自訂類型上實作 Stringer 介面 +3. 使用組合來組合多個結構體 +4. 撰寫類型選擇來處理不同類型 +5. 建立指標接收者和值接收者並比較行為 +6. 使用標籤實作自訂 JSON 編組 +7. 建立一個介面並讓多個類型滿足它 +8. 使用結構體標籤建構小型資料庫模型 + +## 下一步 + +下一模組:**套件管理** - 理解 Go 模組和匯入。 diff --git a/content/docs/py2go/module-05-packages-modules.mdx b/content/docs/py2go/module-05-packages-modules.mdx new file mode 100644 index 0000000..10dbe0a --- /dev/null +++ b/content/docs/py2go/module-05-packages-modules.mdx @@ -0,0 +1,979 @@ +--- +title: "Module 5: Package Management" +description: "Go modules and package system compared to Python" +--- + +## Introduction + +Go uses **Go Modules** for dependency management (since Go 1.11), replacing the old GOPATH system. This is similar to Python's pip/virtualenv but with some key differences and improvements. + +## Go Modules vs Python Tools + +### Basic Setup + + +```bash +# Python - Virtual environment setup +python -m venv venv +source venv/bin/activate # On Windows: venv\Scripts\activate +pip install requests flask +pip freeze > requirements.txt +``` + +```bash +# Go - Go module setup +mkdir myproject +cd myproject +go mod init github.com/username/myproject +# Creates go.mod file + +get github.com/gin-gonic/gin # Add dependency +go mod tidy # Clean up dependencies +# go.mod and go.sum are updated automatically +``` + + +### Project Structure + + +```bash +# Python project structure +myproject/ +├── venv/ # Virtual environment +│ ├── lib/ +│ └── bin/ +├── requirements.txt # Dependencies +├── src/ +│ ├── __init__.py +│ ├── main.py +│ └── utils.py +└── tests/ + ├── __init__.py + └── test_utils.py +``` + +```bash +# Go project structure +myproject/ +├── go.mod # Module definition +├── go.sum # Dependency checksums (auto-generated) +├── main.go # Entry point +├── utils.go # Utility code +└── utils_test.go # Test file (*_test.go convention) +``` + + +## The go.mod File + + +```python +# Python - requirements.txt +flask==2.3.0 +requests>=2.28.0 +django>=3.2,<4.0 +``` + +```go +// Go - go.mod +module github.com/username/myproject + +go 1.21 + +require ( + github.com/gin-gonic/gin v1.9.1 + github.com/stretchr/testify v1.8.4 +) + +// Direct dependencies +require github.com/go-redis/redis/v8 v8.11.5 + +// Indirect dependencies (shown in go mod why) +// indirect dependencies are listed in go.mod with // indirect +``` + + +## Importing Packages + +### Basic Imports + + +```python !! py +# Python - Imports +import math +import os.path +from collections import defaultdict, Counter +from . import local_module +from .utils import helper +from package import Class +import package as p +``` + +```go !! go +// Go - Imports +package main + +import ( + "fmt" // Standard library + "math" // Standard library + "os" // Standard library + "strings" // Standard library + + // Third-party packages (from go.mod) + "github.com/gin-gonic/gin" + "github.com/go-redis/redis/v8" + + // Local packages (relative to module root) + "myproject/utils" + "myproject/internal/app" + + // Import with alias + myfmt "myproject/format" +) + +func main() { + fmt.Println("Hello from main") + utils.Helper() +} +``` + + +### Package Organization + + +```python !! py +# Python - Package = directory +# myproject/utils/__init__.py +# myproject/utils/helper.py + +# Usage +from utils import helper +from utils.helper import process_data +``` + +```go !! go +// Go - Package declaration at top of file +// utils/helper.go +package utils + +func Helper() { + fmt.Println("Helper function") +} + +// utils/constants.go +package utils + +const MaxRetries = 3 + +// Usage (from main.go) +import "myproject/utils" + +func main() { + utils.Helper() + fmt.Println(utils.MaxRetries) +} +``` + + +## Package Visibility + + +```python !! py +# Python - Public vs private (convention-based) +# utils.py + +PUBLIC_VAR = "Anyone can access" + +_internal_var = "Private by convention" + +__private_var = "Name mangling" + +def public_function(): + pass + +def _internal_function(): + pass + +def __private_function(): + pass +``` + +```go !! go +// Go - Exported vs unexported (strict) +package utils + +const MaxRetries = 3 // Exported (capital letter) +const defaultTimeout = 30 // Unexported (lowercase) + +var GlobalVar string // Exported +var internalState int // Unexported + +func PublicFunction() {} // Exported +func internalHelper() {} // Unexported + +type PublicStruct struct {} // Exported +type privateStruct struct{} // Unexported + +// From another package +import "myproject/utils" + +func main() { + utils.MaxRetries // OK - exported + utils.defaultTimeout // Error: unexported +} +``` + + +## go.mod Commands + + +```bash +# Python - pip commands +pip install requests +pip list +pip freeze +pip install --upgrade requests +pip uninstall requests +pip show requests +``` + +```bash +# Go - module commands +go mod init github.com/username/project # Initialize +go mod tidy # Add missing, remove unused +go mod download # Download all dependencies +go mod verify # Verify dependencies (check checksums) +go mod graph # Display dependency graph +go mod why github.com/pkg/errors # Explain why a package is needed +go list -m all # List all dependencies +go get -u ./... # Update all dependencies +go get -u github.com/pkg/errors # Update specific package +``` + + +### Detailed Command Examples + + +```bash +# Initialize a new module +$ mkdir myproject +$ cd myproject +$ go mod init github.com/username/myproject +go: creating new go.mod: module github.com/username/myproject + +# Add a dependency +$ go get github.com/gin-gonic/gin +go: downloading github.com/gin-gonic/gin v1.9.1 +go: added github.com/gin-gonic/gin v1.9.1 + +# Tidy dependencies (remove unused, add missing) +$ go mod tidy + +# Check dependencies +$ cat go.mod +module github.com/username/myproject + +go 1.21 + +require github.com/gin-gonic/gin v1.9.1 + +require ( + github.com/stretchr/testify v1.8.4 // Indirect +) + +# See dependency graph +$ go mod graph +github.com/username/myproject github.com/gin-gonic/gin +github.com/gin-gonic/gin github.com/gin-contrib/sse +... + +# Why is a package needed? +$ go mod why github.com/stretchr/testify +github.com/username/myproject +└── github.com/gin-gonic/gin + └── github.com/stretchr/testify + +# Update all dependencies +$ go get -u ./... +``` + + +## Dependency Versions + + +```python +# Python - Version pinning +flask==2.3.0 # Exact version +requests>=2.28.0 # Minimum version +django~=3.2.0 # Compatible release (3.2.x, not 3.3+) +``` + +```go +// Go - Semantic versioning in go.mod +module github.com/username/myproject + +go 1.21 + +require ( + // Specific version + github.com/pkg/errors v0.9.1 + + // Version range with minimum + github.com/gin-gonic/gin v1.9.1 // v1.9.1 or later (with SemVer) + + // Indirect dependencies + github.com/stretchr/testify v1.8.4 // indirect +) + +// Go uses Semantic Versioning (SemVer): +// v1.2.3 = MAJOR.MINOR.PATCH +// v1.2 = 1.2.x (compatible, patches) +// v1 = 1.x.x (compatible, minor and patch) +``` + + +## go.sum File + + +```bash +# Python - No checksums by default +# Can use pip hash-checking mode +pip install --require-hashes -r requirements.txt + +# requirements.txt +flask==2.3.0 --hash=sha256:a8b... +``` + +```bash +# Go - go.sum (auto-generated, don't edit) +$ cat go.sum +github.com/gin-gonic/gin v1.9.1 h1:... +github.com/gin-gonic/gin v1.9.1/go.mod h1:... +github.com/stretchr/testify v1.8.4 h1:... +github.com/stretchr/testify v1.8.4/go.mod h1:... + +# go.sum contains: +# - Cryptographic hashes of all dependencies +# - Ensures dependency integrity +# - Auto-generated and managed by Go tools +# - Never edit manually +# - Commit to version control +``` + + +## Internal Packages + +Go reserves `internal` directories for private code: + + +```python +# Python - No concept of internal +# Use naming conventions or tools +myproject/ +├── utils.py +├── _private.py +└── tests/ + └── test_utils.py +``` + +```bash +# Go - Internal directory (private) +myproject/ +├── go.mod +├── main.go +├── internal/ +│ ├── app/ +│ │ └── app.go +│ └── database/ +│ └── db.go +└── pkg/ + └── utils/ + └── utils.go + +// internal/ is only accessible within myproject +// pkg/ is publicly importable +``` + +```go +// internal/app/app.go +package app + +// Can only be imported by code in myproject +// NOT accessible by: +// - github.com/other-project +// - github.com/username/myproject/internal (different path) + +// IS accessible by: +// - myproject/main.go +// - myproject/pkg/utils + +func Run() { + fmt.Println("App running") +} +``` + + +## Vendor Directory + + +```bash +# Python - Vendoring +pip install -t vendor/ -r requirements.txt + +# Or use pipenv +pipenv install +pipenv vendor +``` + +```bash +# Go - Vendoring dependencies +$ go mod vendor + +# Creates vendor/ directory with all source code +$ ls vendor/ +github.com/ + gin-gonic/ + stretchr/ + ... + +# Go will use vendor/ directory if present +# Commit vendor/ to version control for reproducible builds + +# Clean vendor if needed +$ rm -rf vendor/ +``` + + +## Import Strategies + +### Remote Imports + + +```python !! py +# Python - Install then import +# First: pip install pyyaml +# Then: +import yaml + +data = yaml.safe_load(""" + name: Alice + age: 30 +""") +``` + +```go !! go +// Go - Import and use +package main + +import ( + "fmt" + "gopkg.in/yaml.v3" // Domain prefix for non-GitHub repos +) + +func main() { + data := []byte(` + name: Alice + age: 30 + `) + + var result map[string]interface{} + yaml.Unmarshal(data, &result) + + fmt.Printf("Name: %v, Age: %v", result["name"], result["age"]) +} +``` + + +## Version Conflicts and Resolution + + +```python +# Python - Dependency conflicts +# requirements.txt +package-a==1.0.0 +package-b==2.0.0 + +# If package-a requires package-b==1.5.0 +# pip install -r requirements.txt +# ERROR: Requirement conflict +``` + +```go +// Go - Minimal conflicts (MVS - Minimal Version Selection) +// go.mod +module myproject + +go 1.21 + +require ( + package-a v1.0.0 + package-b v2.0.0 +) + +// If package-a requires package-b v1.5.0 +// Go uses MVS algorithm: +// - Both v1.5.0 and v2.0.0 satisfy package-b's requirement +// - Go chooses v1.5.0 (minimal version that satisfies all) +// - No version conflict! + +// This is a major advantage over Python's pip +``` + + +## Replacing Dependencies + + +```python +# Python - Override version +# requirements.txt +package-a==2.0.0 + +# Or use pip-tools +pip install package-a==2.0.0 +pip-compile # Creates requirements.txt from all packages +``` + +```go +// Go - replace directive +module myproject + +go 1.21 + +require ( + github.com/original/package v1.0.0 +) + +// Replace with local fork or different version +replace github.com/original/package => ../fork/package + +// Or use specific version +replace github.com/original/package => v1.1.0 + +// Or use different module +replace github.com/original/package => github.com/myfork/package v1.0.0 +``` + + +## Private Modules + + +```bash +# Python - Private PyPI repository +# Set up private PyPI server +pip install --index-url https://pypi.example.com/simple/ mypackage +``` + +```bash +# Go - Private modules +# Option 1: Direct Git repository +require ( + github.com/mycompany/privatepackage v1.0.0 +) + +# Configure git to authenticate +git config credential.helper store + +# Option 2: Private Go proxy +# Set GOPROXY +export GOPROXY=https://goproxy.example.com + +# Option 3: Replace with local path +replace github.com/mycompany/privatepackage => ../privatepackage + +# Option 4: GOPRIVATE setting +export GOPRIVATE=github.com/mycompany/* +# Private modules bypass proxy +``` + + +## Module Proxy + + +```bash +# Python - PyPI and package indexes +export PIP_INDEX_URL=https://pypi.org/simple +export PIP_EXTRA_INDEX_URL=https://pypi.example.com/simple +``` + +```bash +# Go - Module proxy +# By default, Go uses: +# - https://proxy.golang.org (public proxy) +# - Direct connection to VCS (Git) + +# Set module proxy +export GOPROXY=https://goproxy.cn,direct +export GONOSUMDB=github.com/* # Bypass proxy for these + +# Private module proxy +export GOPRIVATE=github.com/mycompany/* # Don't use proxy + +# Check proxy +go env GOPROXY + +# Turn off proxy (direct only) +export GOPROXY=direct +``` + + +## Common Package Patterns + +### Main Package + + +```python !! py +# Python - Entry point +if __name__ == "__main__": + main() +``` + +```go !! go +// Go - main package (entry point) +package main + +import "fmt" + +func main() { + fmt.Println("Hello, World!") +} + +// main() function in main package is the entry point +// Can have multiple main packages for different commands +``` + + +### Package Aliases + + +```python !! py +# Python - Import aliases +import numpy as np +import pandas as pd +from matplotlib import pyplot as plt +``` + +```go !! go +// Go - Import aliases +package main + +import ( + fmtpkg "fmt" // Alias + stdlib "github.com/mylib" // Alias +) + +func main() { + fmtpkg.Println("Using alias") + stdlib.Helper() +} +``` + + +### Dot Imports + + +```python !! py +# Python - From import +from math import sin, cos, pi +# Use directly: sin(x), cos(x), pi +``` + +```go !! go +// Go - Dot import (rare, use carefully) +package main + +import ( + . "fmt" // Dot import +) + +func main() { + // Can use Println directly without fmt prefix + Println("Hello, World!") + // WARNING: Can cause naming conflicts! +} +``` + + +## Init Functions + + +```python !! py +# Python - Module-level code runs on import +# config.py +CONFIG = load_config() +setup_database() + +# main.py +import config # CONFIG is initialized, DB is set up +``` + +```go !! go +// Go - init() function +package utils + +var config *Config + +func init() { + // Runs automatically when package is imported + // Can have multiple init() files (executed in order) + config = loadConfig() +} + +// OR (prefer explicit initialization) +var config *Config = loadConfig() // Init at package level + +func init() { + // Run before main() + setupDatabase() +} + +// main.go +package main + +import ( + "myproject/utils" + _ "myproject/database" // Import for side effects (init runs) +) +``` + + +## Third-Party Package Management + + +```bash +# Python - requirements.txt +# requirements.txt +flask==2.3.0 +requests>=2.28.0 +gunicorn>=20.1.0 + +# Development +pytest>=7.0.0 +black>=22.0.0 + +# Install all +pip install -r requirements.txt + +# Install production only +pip install -r requirements.txt --no-deps + +# Upgrade +pip install --upgrade -r requirements.txt +``` + +```bash +# Go - go.mod +module myproject + +go 1.21 + +// Build dependencies +require ( + github.com/gin-gonic/gin v1.9.1 + github.com/go-redis/redis/v8 v8.11.5 +) + +// Development dependencies +// Go doesn't separate dev deps +// All deps are in go.mod + +// Add dependency +$ go get github.com/pkg/errors + +// Update dependency +$ go get -u github.com/pkg/errors + +// Remove unused dependencies +$ go mod tidy + +// Update all +$ go get -u ./... +``` + + +## Workspace Mode (Go 1.18+) + + +```bash +# Python - No workspace concept +# Use monorepo tools or separate virtual envs +``` + +```bash +# Go - Workspaces (multi-module projects) +# workspace/go.work +go 1.21 + +use ( + ./app + ./utils + ./api +) + +# All modules share dependencies +# Can use code from one module in another +$ go work sync +``` + + +## Testing and Packages + + +```bash +# Python - test structure +myproject/ +├── src/ +│ └── calculator.py +└── tests/ + ├── __init__.py + ├── test_calculator.py + └── fixtures/ + └── test_data.json +``` + +```bash +# Go - test structure (same package) +myproject/ +├── calculator.go +├── calculator_test.go // *_test.go convention +└── calculator_example_test.go // *_example_test.go for examples + +// Test files can be in same package: +package myproject + +// Or in _test package (black-box testing): +package myproject_test +``` + + +## Tools and Best Practices + +### Dependency Management Best Practices + + +```go +// DO: Commit go.mod and go.sum +$ git add go.mod go.sum +$ git commit -m "Add gin-gonic dependency" + +// DO: Run go mod tidy before committing +$ go mod tidy +$ git add go.mod go.sum + +// DON'T: Manually edit go.sum +// Let Go tools manage it + +// DO: Use specific versions in go.mod +require github.com/pkg/errors v0.9.1 + +// DON'T: Use latest versions without testing +// Update dependencies when needed, not automatically + +// DO: Use go mod verify in CI +$ go mod verify +``` + + +### Common Patterns + + +```go +// 1. Repository pattern +package repository + +type UserRepository interface { + Find(id int) (*User, error) + Save(user *User) error +} + +type userRepository struct { + db *sql.DB +} + +func NewUserRepository(db *sql.DB) UserRepository { + return &userRepository{db: db} +} + +// 2. Service pattern +package service + +type UserService struct { + repo UserRepository +} + +func NewUserService(repo UserRepository) *UserService { + return &UserService{repo: repo} +} + +// 3. Factory pattern +package factory + +func NewServer(config *Config) *Server { + return &Server{ + config: config, + router: NewRouter(), + } +} +``` + + +## Summary + +In this module, you learned: + +1. **Go Modules** - Modern dependency management +2. **go.mod** - Module definition and dependencies +3. **go.sum** - Dependency checksums for security +4. **Package organization** - Directories and naming +5. **Import statements** - Standard, third-party, local +6. **Exported vs unexported** - Capitalization rules +7. **Internal packages** - Private code within module +8. **Vendor directory** - Vendored dependencies +9. **Version management** - Semantic versioning +10. **go mod commands** - tidy, download, verify, graph, why +11. **Private modules** - Authentication and proxies +12. **Module proxy** - GOPROXY and GOPRIVATE +13. **Workspaces** - Multi-module projects +14. **Init functions** - Package initialization +15. **Testing** - Test file conventions + +## Key Differences from Python + +| Python | Go | +|--------|-----| +| `pip install` | `go get` | +| `requirements.txt` | `go.mod` | +| Virtual environments | Go modules (no venv needed) | +| `__pycache__` | Module cache | +| `setup.py` | `go.mod` | +| `from X import Y` | `import "package"` | +| Private via `_` prefix | Private via lowercase | +| No internal concept | `internal/` directory | +| No checksums by default | `go.sum` (always) | +| Version conflicts common | MVS (minimal conflicts) | + +## Best Practices + +1. **Always commit go.mod and go.sum** together +2. **Run `go mod tidy`** before committing +3. **Use semantic versioning** for your modules +4. **Don't manually edit go.sum** +5. **Use `internal/`** for private code +6. **Be specific with dependencies** - avoid unnecessary packages +7. **Use `go mod verify`** in CI/CD +8. **Vendor dependencies** for reproducible builds if needed +9. **Use workspace mode** for multi-module projects +10. **Prefer direct imports** over dot imports + +## Exercises + +1. Create a new Go module with multiple packages +2. Add and update third-party dependencies +3. Create an internal package and test visibility +4. Use go mod commands to analyze dependencies +5. Set up a private module repository +6. Implement the repository pattern in a package +7. Create a workspace with multiple modules +8. Write init() functions for package setup + +## Next Steps + +Next module: **Error Handling** - Understanding Go's explicit error handling and error interfaces. diff --git a/content/docs/py2go/module-05-packages-modules.zh-cn.mdx b/content/docs/py2go/module-05-packages-modules.zh-cn.mdx new file mode 100644 index 0000000..d409043 --- /dev/null +++ b/content/docs/py2go/module-05-packages-modules.zh-cn.mdx @@ -0,0 +1,979 @@ +--- +title: "模块 5: 包管理" +description: "Go 模块和包系统与 Python 的对比" +--- + +## 简介 + +Go 使用 **Go Modules** 进行依赖管理(自 Go 1.11 起),取代了旧的 GOPATH 系统。这类似于 Python 的 pip/virtualenv,但有一些关键差异和改进。 + +## Go Modules vs Python 工具 + +### 基本设置 + + +```bash +# Python - 虚拟环境设置 +python -m venv venv +source venv/bin/activate # Windows: venv\Scripts\activate +pip install requests flask +pip freeze > requirements.txt +``` + +```bash +# Go - Go module 设置 +mkdir myproject +cd myproject +go mod init github.com/username/myproject +# 创建 go.mod 文件 + +go get github.com/gin-gonic/gin # 添加依赖 +go mod tidy # 清理依赖 +# go.mod 和 go.sum 自动更新 +``` + + +### 项目结构 + + +```bash +# Python 项目结构 +myproject/ +├── venv/ # 虚拟环境 +│ ├── lib/ +│ └── bin/ +├── requirements.txt # 依赖 +├── src/ +│ ├── __init__.py +│ ├── main.py +│ └── utils.py +└── tests/ + ├── __init__.py + └── test_utils.py +``` + +```bash +# Go 项目结构 +myproject/ +├── go.mod # 模块定义 +├── go.sum # 依赖校验和(自动生成) +├── main.go # 入口点 +├── utils.go # 工具代码 +└── utils_test.go # 测试文件(*_test.go 约定) +``` + + +## go.mod 文件 + + +```python +# Python - requirements.txt +flask==2.3.0 +requests>=2.28.0 +django>=3.2,<4.0 +``` + +```go +// Go - go.mod +module github.com/username/myproject + +go 1.21 + +require ( + github.com/gin-gonic/gin v1.9.1 + github.com/stretchr/testify v1.8.4 +) + +// 直接依赖 +require github.com/go-redis/redis/v8 v8.11.5 + +// 间接依赖(在 go mod why 中显示) +// 间接依赖在 go.mod 中用 // indirect 标记 +``` + + +## 导入包 + +### 基本导入 + + +```python !! py +# Python - 导入 +import math +import os.path +from collections import defaultdict, Counter +from . import local_module +from .utils import helper +from package import Class +import package as p +``` + +```go !! go +// Go - 导入 +package main + +import ( + "fmt" // 标准库 + "math" // 标准库 + "os" // 标准库 + "strings" // 标准库 + + // 第三方包(来自 go.mod) + "github.com/gin-gonic/gin" + "github.com/go-redis/redis/v8" + + // 本地包(相对于模块根目录) + "myproject/utils" + "myproject/internal/app" + + // 带别名导入 + myfmt "myproject/format" +) + +func main() { + fmt.Println("Hello from main") + utils.Helper() +} +``` + + +### 包组织 + + +```python !! py +# Python - 包 = 目录 +# myproject/utils/__init__.py +# myproject/utils/helper.py + +# 使用 +from utils import helper +from utils.helper import process_data +``` + +```go !! go +// Go - 包声明在文件顶部 +// utils/helper.go +package utils + +func Helper() { + fmt.Println("Helper function") +} + +// utils/constants.go +package utils + +const MaxRetries = 3 + +// 使用(从 main.go) +import "myproject/utils" + +func main() { + utils.Helper() + fmt.Println(utils.MaxRetries) +} +``` + + +## 包可见性 + + +```python !! py +# Python - 公共 vs 私有(基于约定) +# utils.py + +PUBLIC_VAR = "Anyone can access" + +_internal_var = "Private by convention" + +__private_var = "Name mangling" + +def public_function(): + pass + +def _internal_function(): + pass + +def __private_function(): + pass +``` + +```go !! go +// Go - 导出 vs 未导出(严格) +package utils + +const MaxRetries = 3 // 导出(首字母大写) +const defaultTimeout = 30 // 未导出(小写) + +var GlobalVar string // 导出 +var internalState int // 未导出 + +func PublicFunction() {} // 导出 +func internalHelper() {} // 未导出 + +type PublicStruct struct {} // 导出 +type privateStruct struct{} // 未导出 + +// 从另一个包 +import "myproject/utils" + +func main() { + utils.MaxRetries // OK - 导出 + utils.defaultTimeout // 错误:未导出 +} +``` + + +## go.mod 命令 + + +```bash +# Python - pip 命令 +pip install requests +pip list +pip freeze +pip install --upgrade requests +pip uninstall requests +pip show requests +``` + +```bash +# Go - module 命令 +go mod init github.com/username/project # 初始化 +go mod tidy # 添加缺失,移除未使用 +go mod download # 下载所有依赖 +go mod verify # 验证依赖(检查校验和) +go mod graph # 显示依赖图 +go mod why github.com/pkg/errors # 解释为什么需要包 +go list -m all # 列出所有依赖 +go get -u ./... # 更新所有依赖 +go get -u github.com/pkg/errors # 更新特定包 +``` + + +### 详细命令示例 + + +```bash +# 初始化新模块 +$ mkdir myproject +$ cd myproject +$ go mod init github.com/username/myproject +go: creating new go.mod: module github.com/username/myproject + +# 添加依赖 +$ go get github.com/gin-gonic/gin +go: downloading github.com/gin-gonic/gin v1.9.1 +go: added github.com/gin-gonic/gin v1.9.1 + +# 清理依赖(移除未使用的,添加缺失的) +$ go mod tidy + +# 检查依赖 +$ cat go.mod +module github.com/username/myproject + +go 1.21 + +require github.com/gin-gonic/gin v1.9.1 + +require ( + github.com/stretchr/testify v1.8.4 // Indirect +) + +# 查看依赖图 +$ go mod graph +github.com/username/myproject github.com/gin-gonic/gin +github.com/gin-gonic/gin github.com/gin-contrib/sse +... + +# 为什么需要这个包? +$ go mod why github.com/stretchr/testify +github.com/username/myproject +└── github.com/gin-gonic/gin + └── github.com/stretchr/testify + +# 更新所有依赖 +$ go get -u ./... +``` + + +## 依赖版本 + + +```python +# Python - 版本锁定 +flask==2.3.0 # 精确版本 +requests>=2.28.0 # 最低版本 +django~=3.2.0 # 兼容版本(3.2.x,不包括 3.3+) +``` + +```go +// Go - go.mod 中的语义化版本 +module github.com/username/myproject + +go 1.21 + +require ( + // 特定版本 + github.com/pkg/errors v0.9.1 + + // 带最低版本的版本范围 + github.com/gin-gonic/gin v1.9.1 // v1.9.1 或更高版本(使用 SemVer) + + // 间接依赖 + github.com/stretchr/testify v1.8.4 // indirect +) + +// Go 使用语义化版本(SemVer): +// v1.2.3 = MAJOR.MINOR.PATCH +// v1.2 = 1.2.x(兼容,补丁版本) +// v1 = 1.x.x(兼容,次版本和补丁版本) +``` + + +## go.sum 文件 + + +```bash +# Python - 默认没有校验和 +# 可以使用 pip hash-checking 模式 +pip install --require-hashes -r requirements.txt + +# requirements.txt +flask==2.3.0 --hash=sha256:a8b... +``` + +```bash +# Go - go.sum(自动生成,不要编辑) +$ cat go.sum +github.com/gin-gonic/gin v1.9.1 h1:... +github.com/gin-gonic/gin v1.9.1/go.mod h1:... +github.com/stretchr/testify v1.8.4 h1:... +github.com/stretchr/testify v1.8.4/go.mod h1:... + +# go.sum 包含: +# - 所有依赖的加密哈希 +# - 确保依赖完整性 +# - 由 Go 工具自动管理 +# - 永远不要手动编辑 +# - 提交到版本控制 +``` + + +## 内部包 + +Go 保留 `internal` 目录用于私有代码: + + +```python +# Python - 没有 internal 概念 +# 使用命名约定或工具 +myproject/ +├── utils.py +├── _private.py +└── tests/ + └── test_utils.py +``` + +```bash +# Go - internal 目录(私有) +myproject/ +├── go.mod +├── main.go +├── internal/ +│ ├── app/ +│ │ └── app.go +│ └── database/ +│ └── db.go +└── pkg/ + └── utils/ + └── utils.go + +// internal/ 只能在 myproject 内访问 +// pkg/ 可以公开导入 +``` + +```go +// internal/app/app.go +package app + +// 只能被 myproject 中的代码导入 +// 不能被以下访问: +// - github.com/other-project +// - github.com/username/myproject/internal(不同路径) + +// 可以被以下访问: +// - myproject/main.go +// - myproject/pkg/utils + +func Run() { + fmt.Println("App running") +} +``` + + +## Vendor 目录 + + +```bash +# Python - Vendor +pip install -t vendor/ -r requirements.txt + +# 或使用 pipenv +pipenv install +pipenv vendor +``` + +```bash +# Go - Vendor 依赖 +$ go mod vendor + +# 创建包含所有源代码的 vendor/ 目录 +$ ls vendor/ +github.com/ + gin-gonic/ + stretchr/ + ... + +# 如果存在 vendor/ 目录,Go 将使用它 +# 将 vendor/ 提交到版本控制以实现可重现构建 + +# 如需要清理 vendor +$ rm -rf vendor/ +``` + + +## 导入策略 + +### 远程导入 + + +```python !! py +# Python - 先安装再导入 +# 首先:pip install pyyaml +# 然后: +import yaml + +data = yaml.safe_load(""" + name: Alice + age: 30 +""") +``` + +```go !! go +// Go - 导入并使用 +package main + +import ( + "fmt" + "gopkg.in/yaml.v3" // 非 GitHub 仓库的域前缀 +) + +func main() { + data := []byte(` + name: Alice + age: 30 + `) + + var result map[string]interface{} + yaml.Unmarshal(data, &result) + + fmt.Printf("Name: %v, Age: %v", result["name"], result["age"]) +} +``` + + +## 版本冲突和解决 + + +```python +# Python - 依赖冲突 +# requirements.txt +package-a==1.0.0 +package-b==2.0.0 + +# 如果 package-a 需要 package-b==1.5.0 +# pip install -r requirements.txt +# 错误:需求冲突 +``` + +```go +// Go - 最小冲突(MVS - 最小版本选择) +// go.mod +module myproject + +go 1.21 + +require ( + package-a v1.0.0 + package-b v2.0.0 +) + +// 如果 package-a 需要 package-b v1.5.0 +// Go 使用 MVS 算法: +// - v1.5.0 和 v2.0.0 都满足 package-b 的需求 +// - Go 选择 v1.5.0(满足所有需求的最小版本) +// - 没有版本冲突! + +// 这是相比 Python pip 的主要优势 +``` + + +## 替换依赖 + + +```python +# Python - 覆盖版本 +# requirements.txt +package-a==2.0.0 + +# 或使用 pip-tools +pip install package-a==2.0.0 +pip-compile # 从所有包创建 requirements.txt +``` + +```go +// Go - replace 指令 +module myproject + +go 1.21 + +require ( + github.com/original/package v1.0.0 +) + +// 替换为本地 fork 或不同版本 +replace github.com/original/package => ../fork/package + +// 或使用特定版本 +replace github.com/original/package => v1.1.0 + +// 或使用不同模块 +replace github.com/original/package => github.com/myfork/package v1.0.0 +``` + + +## 私有模块 + + +```bash +# Python - 私有 PyPI 仓库 +# 设置私有 PyPI 服务器 +pip install --index-url https://pypi.example.com/simple/ mypackage +``` + +```bash +# Go - 私有模块 +# 选项 1:直接 Git 仓库 +require ( + github.com/mycompany/privatepackage v1.0.0 +) + +# 配置 git 进行身份验证 +git config credential.helper store + +# 选项 2:私有 Go proxy +# 设置 GOPROXY +export GOPROXY=https://goproxy.example.com + +# 选项 3:替换为本地路径 +replace github.com/mycompany/privatepackage => ../privatepackage + +# 选项 4:GOPRIVATE 设置 +export GOPRIVATE=github.com/mycompany/* +# 私有模块绕过代理 +``` + + +## 模块代理 + + +```bash +# Python - PyPI 和包索引 +export PIP_INDEX_URL=https://pypi.org/simple +export PIP_EXTRA_INDEX_URL=https://pypi.example.com/simple +``` + +```bash +# Go - 模块代理 +# 默认情况下,Go 使用: +# - https://proxy.golang.org(公共代理) +# - 直接连接到 VCS(Git) + +# 设置模块代理 +export GOPROXY=https://goproxy.cn,direct +export GONOSUMDB=github.com/* # 对这些绕过代理 + +# 私有模块代理 +export GOPRIVATE=github.com/mycompany/* # 不使用代理 + +# 检查代理 +go env GOPROXY + +# 关闭代理(仅直接连接) +export GOPROXY=direct +``` + + +## 常见包模式 + +### Main 包 + + +```python !! py +# Python - 入口点 +if __name__ == "__main__": + main() +``` + +```go !! go +// Go - main 包(入口点) +package main + +import "fmt" + +func main() { + fmt.Println("Hello, World!") +} + +// main 包中的 main() 函数是入口点 +// 可以有多个 main 包用于不同命令 +``` + + +### 包别名 + + +```python !! py +# Python - 导入别名 +import numpy as np +import pandas as pd +from matplotlib import pyplot as plt +``` + +```go !! go +// Go - 导入别名 +package main + +import ( + fmtpkg "fmt" // 别名 + stdlib "github.com/mylib" // 别名 +) + +func main() { + fmtpkg.Println("Using alias") + stdlib.Helper() +} +``` + + +### 点导入 + + +```python !! py +# Python - from 导入 +from math import sin, cos, pi +# 直接使用:sin(x)、cos(x)、pi +``` + +```go !! go +// Go - 点导入(罕见,小心使用) +package main + +import ( + . "fmt" // 点导入 +) + +func main() { + // 可以直接使用 Println,不需要 fmt 前缀 + Println("Hello, World!") + // 警告:可能导致命名冲突! +} +``` + + +## Init 函数 + + +```python !! py +# Python - 模块级代码在导入时运行 +# config.py +CONFIG = load_config() +setup_database() + +# main.py +import config # CONFIG 被初始化,DB 被设置 +``` + +```go !! go +// Go - init() 函数 +package utils + +var config *Config + +func init() { + // 在包导入时自动运行 + // 可以有多个 init() 文件(按顺序执行) + config = loadConfig() +} + +// 或者(优先显式初始化) +var config *Config = loadConfig() // 包级初始化 + +func init() { + // 在 main() 之前运行 + setupDatabase() +} + +// main.go +package main + +import ( + "myproject/utils" + _ "myproject/database" // 为副作用导入(init 运行) +) +``` + + +## 第三方包管理 + + +```bash +# Python - requirements.txt +# requirements.txt +flask==2.3.0 +requests>=2.28.0 +gunicorn>=20.1.0 + +# 开发 +pytest>=7.0.0 +black>=22.0.0 + +# 安装所有 +pip install -r requirements.txt + +# 仅安装生产环境 +pip install -r requirements.txt --no-deps + +# 升级 +pip install --upgrade -r requirements.txt +``` + +```bash +# Go - go.mod +module myproject + +go 1.21 + +// 构建依赖 +require ( + github.com/gin-gonic/gin v1.9.1 + github.com/go-redis/redis/v8 v8.11.5 +) + +// 开发依赖 +// Go 不分离开发依赖 +// 所有依赖都在 go.mod 中 + +// 添加依赖 +$ go get github.com/pkg/errors + +// 更新依赖 +$ go get -u github.com/pkg/errors + +// 移除未使用的依赖 +$ go mod tidy + +// 更新所有 +$ go get -u ./... +``` + + +## 工作区模式(Go 1.18+) + + +```bash +# Python - 没有工作区概念 +# 使用 monorepo 工具或独立的虚拟环境 +``` + +```bash +# Go - 工作区(多模块项目) +# workspace/go.work +go 1.21 + +use ( + ./app + ./utils + ./api +) + +# 所有模块共享依赖 +# 可以在一个模块中使用另一个模块的代码 +$ go work sync +``` + + +## 测试和包 + + +```bash +# Python - 测试结构 +myproject/ +├── src/ +│ └── calculator.py +└── tests/ + ├── __init__.py + ├── test_calculator.py + └── fixtures/ + └── test_data.json +``` + +```bash +# Go - 测试结构(相同包) +myproject/ +├── calculator.go +├── calculator_test.go // *_test.go 约定 +└── calculator_example_test.go // *_example_test.go 用于示例 + +// 测试文件可以在同一包中: +package myproject + +// 或在 _test 包中(黑盒测试): +package myproject_test +``` + + +## 工具和最佳实践 + +### 依赖管理最佳实践 + + +```go +// 做:提交 go.mod 和 go.sum +$ git add go.mod go.sum +$ git commit -m "Add gin-gonic dependency" + +// 做:提交前运行 go mod tidy +$ go mod tidy +$ git add go.mod go.sum + +// 不要:手动编辑 go.sum +// 让 Go 工具管理它 + +// 做:在 go.mod 中使用特定版本 +require github.com/pkg/errors v0.9.1 + +// 不要:未经测试就使用最新版本 +// 需要时更新依赖,不要自动更新 + +// 做:在 CI 中使用 go mod verify +$ go mod verify +``` + + +### 常见模式 + + +```go +// 1. 仓储模式 +package repository + +type UserRepository interface { + Find(id int) (*User, error) + Save(user *User) error +} + +type userRepository struct { + db *sql.DB +} + +func NewUserRepository(db *sql.DB) UserRepository { + return &userRepository{db: db} +} + +// 2. 服务模式 +package service + +type UserService struct { + repo UserRepository +} + +func NewUserService(repo UserRepository) *UserService { + return &UserService{repo: repo} +} + +// 3. 工厂模式 +package factory + +func NewServer(config *Config) *Server { + return &Server{ + config: config, + router: NewRouter(), + } +} +``` + + +## 总结 + +在本模块中,你学习了: + +1. **Go Modules** - 现代依赖管理 +2. **go.mod** - 模块定义和依赖 +3. **go.sum** - 依赖校验和以保障安全 +4. **包组织** - 目录和命名 +5. **导入语句** - 标准库、第三方、本地 +6. **导出 vs 未导出** - 首字母大写规则 +7. **内部包** - 模块内的私有代码 +8. **Vendor 目录** - Vendor 依赖 +9. **版本管理** - 语义化版本 +10. **go mod 命令** - tidy、download、verify、graph、why +11. **私有模块** - 身份验证和代理 +12. **模块代理** - GOPROXY 和 GOPRIVATE +13. **工作区** - 多模块项目 +14. **Init 函数** - 包初始化 +15. **测试** - 测试文件约定 + +## 与 Python 的主要区别 + +| Python | Go | +|--------|-----| +| `pip install` | `go get` | +| `requirements.txt` | `go.mod` | +| 虚拟环境 | Go 模块(不需要 venv) | +| `__pycache__` | 模块缓存 | +| `setup.py` | `go.mod` | +| `from X import Y` | `import "package"` | +| 通过 `_` 前缀私有 | 通过小写私有 | +| 没有 internal 概念 | `internal/` 目录 | +| 默认没有校验和 | `go.sum`(总是) | +| 版本冲突常见 | MVS(最小冲突) | + +## 最佳实践 + +1. **始终一起提交 go.mod 和 go.sum** +2. **提交前运行 `go mod tidy`** +3. **为你的模块使用语义化版本** +4. **不要手动编辑 go.sum** +5. **使用 `internal/`** 用于私有代码 +6. **明确依赖** - 避免不必要的包 +7. **在 CI/CD 中使用 `go mod verify`** +8. **Vendor 依赖**(如需要可重现构建) +9. **多模块项目使用工作区模式** +10. **优先使用直接导入**而非点导入 + +## 练习 + +1. 创建一个包含多个包的新 Go 模块 +2. 添加和更新第三方依赖 +3. 创建一个内部包并测试可见性 +4. 使用 go mod 命令分析依赖 +5. 设置私有模块仓库 +6. 在包中实现仓储模式 +7. 创建包含多个模块的工作区 +8. 编写 init() 函数进行包设置 + +## 下一步 + +下一模块:**错误处理** - 理解 Go 的显式错误处理和错误接口。 diff --git a/content/docs/py2go/module-05-packages-modules.zh-tw.mdx b/content/docs/py2go/module-05-packages-modules.zh-tw.mdx new file mode 100644 index 0000000..301f763 --- /dev/null +++ b/content/docs/py2go/module-05-packages-modules.zh-tw.mdx @@ -0,0 +1,979 @@ +--- +title: "模組 5: 套件管理" +description: "Go 模組和套件系統與 Python 的對比" +--- + +## 簡介 + +Go 使用 **Go Modules** 進行依賴管理(自 Go 1.11 起),取代了舊的 GOPATH 系統。這類似於 Python 的 pip/virtualenv,但有一些關鍵差異和改進。 + +## Go Modules vs Python 工具 + +### 基本設置 + + +```bash +# Python - 虛擬環境設置 +python -m venv venv +source venv/bin/activate # Windows: venv\Scripts\activate +pip install requests flask +pip freeze > requirements.txt +``` + +```bash +# Go - Go module 設置 +mkdir myproject +cd myproject +go mod init github.com/username/myproject +# 建立 go.mod 檔案 + +go get github.com/gin-gonic/gin # 新增依賴 +go mod tidy # 清理依賴 +# go.mod 和 go.sum 自動更新 +``` + + +### 專案結構 + + +```bash +# Python 專案結構 +myproject/ +├── venv/ # 虛擬環境 +│ ├── lib/ +│ └── bin/ +├── requirements.txt # 依賴 +├── src/ +│ ├── __init__.py +│ ├── main.py +│ └── utils.py +└── tests/ + ├── __init__.py + └── test_utils.py +``` + +```bash +# Go 專案結構 +myproject/ +├── go.mod # 模組定義 +├── go.sum # 依賴校驗和(自動生成) +├── main.go # 進入點 +├── utils.go # 工具程式碼 +└── utils_test.go # 測試檔案(*_test.go 約定) +``` + + +## go.mod 檔案 + + +```python +# Python - requirements.txt +flask==2.3.0 +requests>=2.28.0 +django>=3.2,<4.0 +``` + +```go +// Go - go.mod +module github.com/username/myproject + +go 1.21 + +require ( + github.com/gin-gonic/gin v1.9.1 + github.com/stretchr/testify v1.8.4 +) + +// 直接依賴 +require github.com/go-redis/redis/v8 v8.11.5 + +// 間接依賴(在 go mod why 中顯示) +// 間接依賴在 go.mod 中用 // indirect 標記 +``` + + +## 匯入套件 + +### 基本匯入 + + +```python !! py +# Python - 匯入 +import math +import os.path +from collections import defaultdict, Counter +from . import local_module +from .utils import helper +from package import Class +import package as p +``` + +```go !! go +// Go - 匯入 +package main + +import ( + "fmt" // 標準庫 + "math" // 標準庫 + "os" // 標準庫 + "strings" // 標準庫 + + // 第三方套件(來自 go.mod) + "github.com/gin-gonic/gin" + "github.com/go-redis/redis/v8" + + // 本地套件(相對於模組根目錄) + "myproject/utils" + "myproject/internal/app" + + // 帶別名匯入 + myfmt "myproject/format" +) + +func main() { + fmt.Println("Hello from main") + utils.Helper() +} +``` + + +### 套件組織 + + +```python !! py +# Python - 套件 = 目錄 +# myproject/utils/__init__.py +# myproject/utils/helper.py + +# 使用 +from utils import helper +from utils.helper import process_data +``` + +```go !! go +// Go - 套件宣告在檔案頂部 +// utils/helper.go +package utils + +func Helper() { + fmt.Println("Helper function") +} + +// utils/constants.go +package utils + +const MaxRetries = 3 + +// 使用(從 main.go) +import "myproject/utils" + +func main() { + utils.Helper() + fmt.Println(utils.MaxRetries) +} +``` + + +## 套件可見性 + + +```python !! py +# Python - 公共 vs 私有(基於約定) +# utils.py + +PUBLIC_VAR = "Anyone can access" + +_internal_var = "Private by convention" + +__private_var = "Name mangling" + +def public_function(): + pass + +def _internal_function(): + pass + +def __private_function(): + pass +``` + +```go !! go +// Go - 匯出 vs 未匯出(嚴格) +package utils + +const MaxRetries = 3 // 匯出(首字母大寫) +const defaultTimeout = 30 // 未匯出(小寫) + +var GlobalVar string // 匯出 +var internalState int // 未匯出 + +func PublicFunction() {} // 匯出 +func internalHelper() {} // 未匯出 + +type PublicStruct struct {} // 匯出 +type privateStruct struct{} // 未匯出 + +// 從另一個套件 +import "myproject/utils" + +func main() { + utils.MaxRetries // OK - 匯出 + utils.defaultTimeout // 錯誤:未匯出 +} +``` + + +## go.mod 指令 + + +```bash +# Python - pip 指令 +pip install requests +pip list +pip freeze +pip install --upgrade requests +pip uninstall requests +pip show requests +``` + +```bash +# Go - module 指令 +go mod init github.com/username/project # 初始化 +go mod tidy # 新增缺失,移除未使用 +go mod download # 下載所有依賴 +go mod verify # 驗證依賴(檢查校驗和) +go mod graph # 顯示依賴圖 +go mod why github.com/pkg/errors # 解釋為什麼需要套件 +go list -m all # 列出所有依賴 +go get -u ./... # 更新所有依賴 +go get -u github.com/pkg/errors # 更新特定套件 +``` + + +### 詳細指令範例 + + +```bash +# 初始化新模組 +$ mkdir myproject +$ cd myproject +$ go mod init github.com/username/myproject +go: creating new go.mod: module github.com/username/myproject + +# 新增依賴 +$ go get github.com/gin-gonic/gin +go: downloading github.com/gin-gonic/gin v1.9.1 +go: added github.com/gin-gonic/gin v1.9.1 + +# 清理依賴(移除未使用的,新增缺失的) +$ go mod tidy + +# 檢查依賴 +$ cat go.mod +module github.com/username/myproject + +go 1.21 + +require github.com/gin-gonic/gin v1.9.1 + +require ( + github.com/stretchr/testify v1.8.4 // Indirect +) + +# 查看依賴圖 +$ go mod graph +github.com/username/myproject github.com/gin-gonic/gin +github.com/gin-gonic/gin github.com/gin-contrib/sse +... + +# 為什麼需要這個套件? +$ go mod why github.com/stretchr/testify +github.com/username/myproject +└── github.com/gin-gonic/gin + └── github.com/stretchr/testify + +# 更新所有依賴 +$ go get -u ./... +``` + + +## 依賴版本 + + +```python +# Python - 版本鎖定 +flask==2.3.0 # 精確版本 +requests>=2.28.0 # 最低版本 +django~=3.2.0 # 相容版本(3.2.x,不包括 3.3+) +``` + +```go +// Go - go.mod 中的語義化版本 +module github.com/username/myproject + +go 1.21 + +require ( + // 特定版本 + github.com/pkg/errors v0.9.1 + + // 帶最低版本的版本範圍 + github.com/gin-gonic/gin v1.9.1 // v1.9.1 或更高版本(使用 SemVer) + + // 間接依賴 + github.com/stretchr/testify v1.8.4 // indirect +) + +// Go 使用語義化版本(SemVer): +// v1.2.3 = MAJOR.MINOR.PATCH +// v1.2 = 1.2.x(相容,補丁版本) +// v1 = 1.x.x(相容,次版本和補丁版本) +``` + + +## go.sum 檔案 + + +```bash +# Python - 預設沒有校驗和 +# 可以使用 pip hash-checking 模式 +pip install --require-hashes -r requirements.txt + +# requirements.txt +flask==2.3.0 --hash=sha256:a8b... +``` + +```bash +# Go - go.sum(自動生成,不要編輯) +$ cat go.sum +github.com/gin-gonic/gin v1.9.1 h1:... +github.com/gin-gonic/gin v1.9.1/go.mod h1:... +github.com/stretchr/testify v1.8.4 h1:... +github.com/stretchr/testify v1.8.4/go.mod h1:... + +# go.sum 包含: +# - 所有依賴的加密雜湊 +# - 確保依賴完整性 +# - 由 Go 工具自動管理 +# - 永遠不要手動編輯 +# - 提交到版本控制 +``` + + +## 內部套件 + +Go 保留 `internal` 目錄用於私有程式碼: + + +```python +# Python - 沒有 internal 概念 +# 使用命名約定或工具 +myproject/ +├── utils.py +├── _private.py +└── tests/ + └── test_utils.py +``` + +```bash +# Go - internal 目錄(私有) +myproject/ +├── go.mod +├── main.go +├── internal/ +│ ├── app/ +│ │ └── app.go +│ └── database/ +│ └── db.go +└── pkg/ + └── utils/ + └── utils.go + +// internal/ 只能在 myproject 內存取 +// pkg/ 可以公開匯入 +``` + +```go +// internal/app/app.go +package app + +// 只能被 myproject 中的程式碼匯入 +// 不能被以下存取: +// - github.com/other-project +// - github.com/username/myproject/internal(不同路徑) + +// 可以被以下存取: +// - myproject/main.go +// - myproject/pkg/utils + +func Run() { + fmt.Println("App running") +} +``` + + +## Vendor 目錄 + + +```bash +# Python - Vendor +pip install -t vendor/ -r requirements.txt + +# 或使用 pipenv +pipenv install +pipenv vendor +``` + +```bash +# Go - Vendor 依賴 +$ go mod vendor + +# 建立包含所有原始碼的 vendor/ 目錄 +$ ls vendor/ +github.com/ + gin-gonic/ + stretchr/ + ... + +# 如果存在 vendor/ 目錄,Go 將使用它 +# 將 vendor/ 提交到版本控制以實現可重現建構 + +# 如需要清理 vendor +$ rm -rf vendor/ +``` + + +## 匯入策略 + +### 遠端匯入 + + +```python !! py +# Python - 先安裝再匯入 +# 首先:pip install pyyaml +# 然後: +import yaml + +data = yaml.safe_load(""" + name: Alice + age: 30 +""") +``` + +```go !! go +// Go - 匯入並使用 +package main + +import ( + "fmt" + "gopkg.in/yaml.v3" // 非 GitHub 倉庫的域前綴 +) + +func main() { + data := []byte(` + name: Alice + age: 30 + `) + + var result map[string]interface{} + yaml.Unmarshal(data, &result) + + fmt.Printf("Name: %v, Age: %v", result["name"], result["age"]) +} +``` + + +## 版本衝突和解決 + + +```python +# Python - 依賴衝突 +# requirements.txt +package-a==1.0.0 +package-b==2.0.0 + +# 如果 package-a 需要 package-b==1.5.0 +# pip install -r requirements.txt +# 錯誤:需求衝突 +``` + +```go +// Go - 最小衝突(MVS - 最小版本選擇) +// go.mod +module myproject + +go 1.21 + +require ( + package-a v1.0.0 + package-b v2.0.0 +) + +// 如果 package-a 需要 package-b v1.5.0 +// Go 使用 MVS 演算法: +// - v1.5.0 和 v2.0.0 都滿足 package-b 的需求 +// - Go 選擇 v1.5.0(滿足所有需求的最小版本) +// - 沒有版本衝突! + +// 這是相比 Python pip 的主要優勢 +``` + + +## 替換依賴 + + +```python +# Python - 覆蓋版本 +# requirements.txt +package-a==2.0.0 + +# 或使用 pip-tools +pip install package-a==2.0.0 +pip-compile # 從所有套件建立 requirements.txt +``` + +```go +// Go - replace 指令 +module myproject + +go 1.21 + +require ( + github.com/original/package v1.0.0 +) + +// 替換為本地 fork 或不同版本 +replace github.com/original/package => ../fork/package + +// 或使用特定版本 +replace github.com/original/package => v1.1.0 + +// 或使用不同模組 +replace github.com/original/package => github.com/myfork/package v1.0.0 +``` + + +## 私有模組 + + +```bash +# Python - 私有 PyPI 倉庫 +# 設置私有 PyPI 伺服器 +pip install --index-url https://pypi.example.com/simple/ mypackage +``` + +```bash +# Go - 私有模組 +# 選項 1:直接 Git 倉庫 +require ( + github.com/mycompany/privatepackage v1.0.0 +) + +# 配置 git 進行身份驗證 +git config credential.helper store + +# 選項 2:私有 Go proxy +# 設置 GOPROXY +export GOPROXY=https://goproxy.example.com + +# 選項 3:替換為本地路徑 +replace github.com/mycompany/privatepackage => ../privatepackage + +# 選項 4:GOPRIVATE 設置 +export GOPRIVATE=github.com/mycompany/* +# 私有模組繞過代理 +``` + + +## 模組代理 + + +```bash +# Python - PyPI 和套件索引 +export PIP_INDEX_URL=https://pypi.org/simple +export PIP_EXTRA_INDEX_URL=https://pypi.example.com/simple +``` + +```bash +# Go - 模組代理 +# 預設情況下,Go 使用: +# - https://proxy.golang.org(公共代理) +# - 直接連接到 VCS(Git) + +# 設置模組代理 +export GOPROXY=https://goproxy.cn,direct +export GONOSUMDB=github.com/* # 對這些繞過代理 + +# 私有模組代理 +export GOPRIVATE=github.com/mycompany/* # 不使用代理 + +# 檢查代理 +go env GOPROXY + +# 關閉代理(僅直接連接) +export GOPROXY=direct +``` + + +## 常見套件模式 + +### Main 套件 + + +```python !! py +# Python - 進入點 +if __name__ == "__main__": + main() +``` + +```go !! go +// Go - main 套件(進入點) +package main + +import "fmt" + +func main() { + fmt.Println("Hello, World!") +} + +// main 套件中的 main() 函數是進入點 +// 可以有多個 main 套件用於不同指令 +``` + + +### 套件別名 + + +```python !! py +# Python - 匯入別名 +import numpy as np +import pandas as pd +from matplotlib import pyplot as plt +``` + +```go !! go +// Go - 匯入別名 +package main + +import ( + fmtpkg "fmt" // 別名 + stdlib "github.com/mylib" // 別名 +) + +func main() { + fmtpkg.Println("Using alias") + stdlib.Helper() +} +``` + + +### 點匯入 + + +```python !! py +# Python - from 匯入 +from math import sin, cos, pi +# 直接使用:sin(x)、cos(x)、pi +``` + +```go !! go +// Go - 點匯入(罕見,小心使用) +package main + +import ( + . "fmt" // 點匯入 +) + +func main() { + // 可以直接使用 Println,不需要 fmt 前綴 + Println("Hello, World!") + // 警告:可能導致命名衝突! +} +``` + + +## Init 函數 + + +```python !! py +# Python - 模組級程式碼在匯入時運行 +# config.py +CONFIG = load_config() +setup_database() + +# main.py +import config # CONFIG 被初始化,DB 被設置 +``` + +```go !! go +// Go - init() 函數 +package utils + +var config *Config + +func init() { + // 在套件匯入時自動運行 + // 可以有多個 init() 檔案(按順序執行) + config = loadConfig() +} + +// 或者(優先顯式初始化) +var config *Config = loadConfig() // 套件級初始化 + +func init() { + // 在 main() 之前運行 + setupDatabase() +} + +// main.go +package main + +import ( + "myproject/utils" + _ "myproject/database" // 為副作用匯入(init 運行) +) +``` + + +## 第三方套件管理 + + +```bash +# Python - requirements.txt +# requirements.txt +flask==2.3.0 +requests>=2.28.0 +gunicorn>=20.1.0 + +# 開發 +pytest>=7.0.0 +black>=22.0.0 + +# 安裝所有 +pip install -r requirements.txt + +# 僅安裝生產環境 +pip install -r requirements.txt --no-deps + +# 升級 +pip install --upgrade -r requirements.txt +``` + +```bash +# Go - go.mod +module myproject + +go 1.21 + +// 建構依賴 +require ( + github.com/gin-gonic/gin v1.9.1 + github.com/go-redis/redis/v8 v8.11.5 +) + +// 開發依賴 +// Go 不分離開發依賴 +// 所有依賴都在 go.mod 中 + +// 新增依賴 +$ go get github.com/pkg/errors + +// 更新依賴 +$ go get -u github.com/pkg/errors + +// 移除未使用的依賴 +$ go mod tidy + +// 更新所有 +$ go get -u ./... +``` + + +## 工作區模式(Go 1.18+) + + +```bash +# Python - 沒有工作區概念 +# 使用 monorepo 工具或獨立的虛擬環境 +``` + +```bash +# Go - 工作區(多模組專案) +# workspace/go.work +go 1.21 + +use ( + ./app + ./utils + ./api +) + +# 所有模組共用依賴 +# 可以在一個模組中使用另一個模組的程式碼 +$ go work sync +``` + + +## 測試和套件 + + +```bash +# Python - 測試結構 +myproject/ +├── src/ +│ └── calculator.py +└── tests/ + ├── __init__.py + ├── test_calculator.py + └── fixtures/ + └── test_data.json +``` + +```bash +# Go - 測試結構(相同套件) +myproject/ +├── calculator.go +├── calculator_test.go // *_test.go 約定 +└── calculator_example_test.go // *_example_test.go 用於範例 + +// 測試檔案可以在同一套件中: +package myproject + +// 或在 _test 套件中(黑盒測試): +package myproject_test +``` + + +## 工具和最佳實踐 + +### 依賴管理最佳實踐 + + +```go +// 做:提交 go.mod 和 go.sum +$ git add go.mod go.sum +$ git commit -m "Add gin-gonic dependency" + +// 做:提交前運行 go mod tidy +$ go mod tidy +$ git add go.mod go.sum + +// 不要:手動編輯 go.sum +// 讓 Go 工具管理它 + +// 做:在 go.mod 中使用特定版本 +require github.com/pkg/errors v0.9.1 + +// 不要:未經測試就使用最新版本 +// 需要時更新依賴,不要自動更新 + +// 做:在 CI 中使用 go mod verify +$ go mod verify +``` + + +### 常見模式 + + +```go +// 1. 倉儲模式 +package repository + +type UserRepository interface { + Find(id int) (*User, error) + Save(user *User) error +} + +type userRepository struct { + db *sql.DB +} + +func NewUserRepository(db *sql.DB) UserRepository { + return &userRepository{db: db} +} + +// 2. 服務模式 +package service + +type UserService struct { + repo UserRepository +} + +func NewUserService(repo UserRepository) *UserService { + return &UserService{repo: repo} +} + +// 3. 工廠模式 +package factory + +func NewServer(config *Config) *Server { + return &Server{ + config: config, + router: NewRouter(), + } +} +``` + + +## 總結 + +在本模組中,你學習了: + +1. **Go Modules** - 現代依賴管理 +2. **go.mod** - 模組定義和依賴 +3. **go.sum** - 依賴校驗和以保障安全 +4. **套件組織** - 目錄和命名 +5. **匯入語句** - 標準庫、第三方、本地 +6. **匯出 vs 未匯出** - 首字母大寫規則 +7. **內部套件** - 模組內的私有程式碼 +8. **Vendor 目錄** - Vendor 依賴 +9. **版本管理** - 語義化版本 +10. **go mod 指令** - tidy、download、verify、graph、why +11. **私有模組** - 身份驗證和代理 +12. **模組代理** - GOPROXY 和 GOPRIVATE +13. **工作區** - 多模組專案 +14. **Init 函數** - 套件初始化 +15. **測試** - 測試檔案約定 + +## 與 Python 的主要差異 + +| Python | Go | +|--------|-----| +| `pip install` | `go get` | +| `requirements.txt` | `go.mod` | +| 虛擬環境 | Go 模組(不需要 venv) | +| `__pycache__` | 模組快取 | +| `setup.py` | `go.mod` | +| `from X import Y` | `import "package"` | +| 通過 `_` 前綴私有 | 通過小寫私有 | +| 沒有 internal 概念 | `internal/` 目錄 | +| 預設沒有校驗和 | `go.sum`(總是) | +| 版本衝突常見 | MVS(最小衝突) | + +## 最佳實踐 + +1. **始終一起提交 go.mod 和 go.sum** +2. **提交前運行 `go mod tidy`** +3. **為你的模組使用語義化版本** +4. **不要手動編輯 go.sum** +5. **使用 `internal/`** 用於私有程式碼 +6. **明確依賴** - 避免不必要的套件 +7. **在 CI/CD 中使用 `go mod verify`** +8. **Vendor 依賴**(如需要可重現建構) +9. **多模組專案使用工作區模式** +10. **優先使用直接匯入**而非點匯入 + +## 練習 + +1. 建立一個包含多個套件的新 Go 模組 +2. 新增和更新第三方依賴 +3. 建立一個內部套件並測試可見性 +4. 使用 go mod 指令分析依賴 +5. 設置私有模組倉庫 +6. 在套件中實現倉儲模式 +7. 建立包含多個模組的工作區 +8. 撰寫 init() 函數進行套件設置 + +## 下一步 + +下一模組:**錯誤處理** - 理解 Go 的顯式錯誤處理和錯誤介面。 diff --git a/content/docs/py2go/module-06-error-handling.mdx b/content/docs/py2go/module-06-error-handling.mdx new file mode 100644 index 0000000..62f01c1 --- /dev/null +++ b/content/docs/py2go/module-06-error-handling.mdx @@ -0,0 +1,1687 @@ +--- +title: "Module 6: Error Handling" +description: "Go's explicit error handling compared to Python's exceptions" +--- + +## Introduction + +Go takes a fundamentally different approach to error handling than Python. Instead of exceptions, Go treats errors as values that are returned explicitly. This makes error handling visible, predictable, and force developers to handle errors explicitly. + +### Key Differences + +**Python Exceptions:** +- Errors "bubble up" the call stack automatically +- Can be caught at any level +- Invisible in function signatures +- Easy to ignore (until they crash your program) +- Control flow via exceptions + +**Go Errors:** +- Errors are returned as values +- Must be handled explicitly at each level +- Visible in function signatures +- Cannot be ignored without deliberate action +- Linear control flow + +## Basic Error Handling + +### The Error Interface + +In Go, `error` is an interface: + +```go +type error interface { + Error() string +} +``` + +Any type that implements `Error() string` can be used as an error. + + +```python !! py +# Python - Exceptions +def divide(a, b): + if b == 0: + raise ValueError("Cannot divide by zero") + return a / b + +try: + result = divide(10, 0) +except ValueError as e: + print(f"Error: {e}") + # Maybe log, maybe retry, maybe abort +``` + +```go !! go +// Go - Error as return value +package main + +import ( + "errors" + "fmt" +) + +func divide(a int, b int) (int, error) { + if b == 0 { + return 0, errors.New("cannot divide by zero") + } + return a / b, nil +} + +func main() { + result, err := divide(10, 0) + if err != nil { + fmt.Printf("Error: %v\n", err) + return + } + fmt.Printf("Result: %d\n", result) +} +``` + + +### The nil Error Convention + +In Go, `nil` represents "no error": + + +```python !! py +# Python - No exception means success +def get_user(id): + user = db.query(id) + if user: + return user + return None # Not an error, just no result +``` + +```go !! go +// Go - nil error means success +func getUser(id int) (*User, error) { + user, err := db.Query(id) + if err != nil { + return nil, err // Database error + } + if user == nil { + return nil, nil // No error, but no result either + } + return user, nil // Success +} + +// Usage +user, err := getUser(123) +if err != nil { + // Handle actual error + log.Fatal(err) +} +if user == nil { + // No user found, but no error + fmt.Println("User not found") +} +``` + + +## Custom Error Types + +### Sentinel Errors + +Sentinel errors are predefined error values that can be compared directly: + + +```python !! py +# Python - Custom exceptions +class ValidationError(Exception): + pass + +class NotFoundError(Exception): + pass + +def validate_age(age): + if age < 0: + raise ValidationError("Age cannot be negative") + if age > 120: + raise ValidationError("Age too high") + +def get_user(id): + user = db.find(id) + if not user: + raise NotFoundError(f"User {id} not found") + return user + +# Usage +try: + validate_age(-5) +except ValidationError as e: + print(f"Validation error: {e}") +except NotFoundError as e: + print(f"Not found: {e}") +``` + +```go !! go +// Go - Sentinel errors +package main + +import ( + "errors" + "fmt" +) + +var ( + ErrNegativeAge = errors.New("age cannot be negative") + ErrAgeTooHigh = errors.New("age too high") + ErrUserNotFound = errors.New("user not found") +) + +func validateAge(age int) error { + if age < 0 { + return ErrNegativeAge + } + if age > 120 { + return ErrAgeTooHigh + } + return nil +} + +func getUser(id int) (*User, error) { + user := db.Find(id) + if user == nil { + return nil, ErrUserNotFound + } + return user, nil +} + +func main() { + err := validateAge(-5) + if err != nil { + // Direct comparison + if errors.Is(err, ErrNegativeAge) { + fmt.Println("Age is negative") + } else if errors.Is(err, ErrAgeTooHigh) { + fmt.Println("Age is too high") + } else { + fmt.Println("Other error:", err) + } + } +} +``` + + +### Custom Error Structs + +For more complex error handling, create custom error types: + + +```python !! py +# Python - Exception with data +class ValidationError(Exception): + def __init__(self, field, message): + self.field = field + self.message = message + super().__init__(f"{field}: {message}") + +def validate_user(user): + if not user.email: + raise ValidationError("email", "is required") + if "@" not in user.email: + raise ValidationError("email", "is invalid") + +# Usage +try: + validate_user(user) +except ValidationError as e: + print(f"Field {e.field}: {e.message}") +``` + +```go !! go +// Go - Custom error struct +package main + +import "fmt" + +// ValidationError is a custom error type +type ValidationError struct { + Field string + Message string +} + +// Error implements the error interface +func (e *ValidationError) Error() string { + return fmt.Sprintf("%s: %s", e.Field, e.Message) +} + +// User represents a user +type User struct { + Email string + Age int +} + +func validateUser(user *User) error { + if user.Email == "" { + return &ValidationError{ + Field: "email", + Message: "is required", + } + } + // More validation... + return nil +} + +func main() { + user := &User{} + err := validateUser(user) + if err != nil { + // Type assertion to access custom fields + if validationErr, ok := err.(*ValidationError); ok { + fmt.Printf("Field %s: %s\n", validationErr.Field, validationErr.Message) + } else { + fmt.Println("Error:", err) + } + } +} +``` + + +### Error with Methods + +Your custom errors can have additional methods: + + +```python !! py +# Python - Rich exception +class PaymentError(Exception): + def __init__(self, code, message, retryable): + self.code = code + self.message = message + self.retryable = retryable + + def is_retryable(self): + return self.retryable + +# Usage +try: + process_payment() +except PaymentError as e: + if e.is_retryable(): + retry() +``` + +```go !! go +// Go - Error with methods +package main + +import "fmt" + +// PaymentError represents payment processing errors +type PaymentError struct { + Code int + Message string + Retryable bool +} + +func (e *PaymentError) Error() string { + return fmt.Sprintf("payment error [%d]: %s", e.Code, e.Message) +} + +func (e *PaymentError) IsRetryable() bool { + return e.Retryable +} + +func processPayment(amount float64) error { + if amount <= 0 { + return &PaymentError{ + Code: 1001, + Message: "invalid amount", + Retryable: false, + } + } + if amount > 10000 { + return &PaymentError{ + Code: 1002, + Message: "amount exceeds limit", + Retryable: true, + } + } + return nil +} + +func main() { + err := processPayment(15000) + if err != nil { + if paymentErr, ok := err.(*PaymentError); ok { + fmt.Println("Error:", paymentErr) + if paymentErr.IsRetryable() { + fmt.Println("This error is retryable") + } + } + } +} +``` + + +## Error Wrapping + +### Adding Context + +Go 1.13+ introduced error wrapping with `%w` verb: + + +```python !! py +# Python - Exception chaining +try: + data = load_data() +except ValueError as e: + raise ValueError("Failed to process data") from e + +# Or Python 3's implicit chaining +try: + data = load_data() +except ValueError: + raise ValueError("Failed to process data") +``` + +```go !! go +// Go - Error wrapping +package main + +import ( + "errors" + "fmt" +) + +func loadData() error { + return errors.New("file not found") +} + +func processData() error { + err := loadData() + if err != nil { + // Wrap error with context using %w + return fmt.Errorf("processData failed: %w", err) + } + return nil +} + +func main() { + err := processData() + if err != nil { + fmt.Println("Error:", err) + + // Unwrap to get original error + unwrapped := errors.Unwrap(err) + fmt.Println("Original:", unwrapped) + + // Check if specific error is in chain + if errors.Is(err, errors.New("file not found")) { + fmt.Println("File not found in error chain") + } + } +} +``` + + +### Multiple Wrapping Levels + +Errors can be wrapped multiple times: + + +```python !! py +# Python - Exception chain +def layer3(): + raise ValueError("Base error") + +def layer2(): + try: + layer3() + except ValueError as e: + raise RuntimeError("Layer 2 failed") from e + +def layer1(): + try: + layer2() + except RuntimeError as e: + raise RuntimeError("Layer 1 failed") from e + +# Traceback shows full chain +``` + +```go !! go +// Go - Multiple wrapping levels +package main + +import ( + "errors" + "fmt" +) + +func layer3() error { + return errors.New("base error") +} + +func layer2() error { + err := layer3() + if err != nil { + return fmt.Errorf("layer2: %w", err) + } + return nil +} + +func layer1() error { + err := layer2() + if err != nil { + return fmt.Errorf("layer1: %w", err) + } + return nil +} + +func main() { + err := layer1() + if err != nil { + fmt.Println("Full error:", err) + + // Unwrap multiple times + err = errors.Unwrap(err) // "layer2: base error" + err = errors.Unwrap(err) // "base error" + + // Or check if specific error is anywhere in chain + err = layer1() + if errors.Is(err, errors.New("base error")) { + fmt.Println("Base error found in chain") + } + } +} +``` + + +### Error Formatting Verbs + + +```go !! go +// Go - Formatting verbs +package main + +import ( + "errors" + "fmt" +) + +func main() { + baseErr := errors.New("base error") + + // %v - just the error text + fmt1 := fmt.Errorf("context: %v", baseErr) + fmt.Println(fmt1) // "context: base error" + + // %w - wrapped error (supports errors.Is/As) + fmt2 := fmt.Errorf("context: %w", baseErr) + fmt.Println(errors.Is(fmt2, baseErr)) // true + + // %+v - (if error supports it) detailed output + // Some error types implement Formatter interface + // for stack traces and additional details +} +``` + + +## Error Comparison and Type Assertion + +### errors.Is() + +Check if an error matches a specific value in the chain: + + +```python !! py +# Python - Exception type checking +try: + process() +except ValueError: + print("Value error") +except RuntimeError as e: + if "timeout" in str(e): + print("Timeout error") +``` + +```go !! go +// Go - errors.Is for comparison +package main + +import ( + "errors" + "fmt" +) + +var ( + ErrNotFound = errors.New("not found") + ErrTimeout = errors.New("timeout") +) + +func process() error { + return fmt.Errorf("process failed: %w", ErrNotFound) +} + +func main() { + err := process() + + // Check if specific error is in chain + if errors.Is(err, ErrNotFound) { + fmt.Println("Not found error occurred") + } + + if errors.Is(err, ErrTimeout) { + fmt.Println("Timeout error occurred") + } + + // Can check against new errors too + if errors.Is(err, errors.New("not found")) { + fmt.Println("Not found (new instance)") + } +} +``` + + +### errors.As() + +Type assertion for error chains: + + +```python !! py +# Python - Exception instance checking +try: + process() +except ValidationError as e: + print(f"Validation error: {e.field}") +except PaymentError as e: + print(f"Payment error: {e.code}") +``` + +```go !! go +// Go - errors.As for type assertion +package main + +import ( + "errors" + "fmt" +) + +type ValidationError struct { + Field string +} + +func (e *ValidationError) Error() string { + return fmt.Sprintf("validation error: %s", e.Field) +} + +type PaymentError struct { + Code int +} + +func (e *PaymentError) Error() string { + return fmt.Sprintf("payment error: %d", e.Code) +} + +func process() error { + return &ValidationError{Field: "email"} +} + +func main() { + err := process() + + // Check if error is of specific type + var validationErr *ValidationError + if errors.As(err, &validationErr) { + fmt.Printf("Validation error on field: %s\n", validationErr.Field) + } + + var paymentErr *PaymentError + if errors.As(err, &paymentErr) { + fmt.Printf("Payment error code: %d\n", paymentErr.Code) + } + + // Can also check directly + if _, ok := err.(*ValidationError); ok { + fmt.Println("This is a ValidationError") + } +} +``` + + +## Error Handling Patterns + +### Retry Logic + + +```python !! py +# Python - Retry decorator +import time + +def retry(max_attempts=3, delay=1): + def decorator(func): + def wrapper(*args, **kwargs): + for attempt in range(max_attempts): + try: + return func(*args, **kwargs) + except IOError as e: + if attempt == max_attempts - 1: + raise + time.sleep(delay) + return None + return wrapper + return decorator + +@retry(max_attempts=3) +def fetch_data(): + return api_call() +``` + +```go !! go +// Go - Retry function +package main + +import ( + "errors" + "fmt" + "time" +) + +var ErrTemporary = errors.New("temporary error") + +func fetch() error { + // Simulate failure + return ErrTemporary +} + +func retry(attempts int, delay time.Duration, fn func() error) error { + var err error + for i := 0; i < attempts; i++ { + err = fn() + if err == nil { + return nil + } + + // Check if error is retryable + if !errors.Is(err, ErrTemporary) { + return err + } + + if i < attempts-1 { + fmt.Printf("Attempt %d failed, retrying...\n", i+1) + time.Sleep(delay) + } + } + return fmt.Errorf("after %d attempts: %w", attempts, err) +} + +func main() { + err := retry(3, time.Second, fetch) + if err != nil { + fmt.Println("Final error:", err) + } +} +``` + + +### Error Aggregation + + +```python !! py +# Python - Collect multiple errors +class MultiError(Exception): + def __init__(self, errors): + self.errors = errors + super().__init__(f"{len(errors)} errors occurred") + +def process_all(items): + errors = [] + for item in items: + try: + process(item) + except Exception as e: + errors.append(e) + + if errors: + raise MultiError(errors) +``` + +```go !! go +// Go - Collect multiple errors +package main + +import ( + "fmt" + "strings" +) + +// MultiError collects multiple errors +type MultiError struct { + Errors []error +} + +func (e *MultiError) Error() string { + var sb strings.Builder + sb.WriteString(fmt.Sprintf("%d errors occurred:", len(e.Errors))) + for _, err := range e.Errors { + sb.WriteString("\n - ") + sb.WriteString(err.Error()) + } + return sb.String() +} + +func processAll(items []int) error { + var errs []error + for _, item := range items { + if err := process(item); err != nil { + errs = append(errs, err) + } + } + + if len(errs) > 0 { + return &MultiError{Errors: errs} + } + return nil +} + +func process(item int) error { + if item < 0 { + return fmt.Errorf("invalid item: %d", item) + } + return nil +} + +func main() { + items := []int{1, 2, -3, 4, -5} + err := processAll(items) + if err != nil { + fmt.Println(err) + } +} +``` + + +### Temporary vs Permanent Errors + + +```python !! py +# Python - Custom exceptions +class TemporaryError(Exception): + """Retry this operation""" + pass + +class PermanentError(Exception): + """Don't retry, give up""" + pass + +def handle_error(error): + if isinstance(error, TemporaryError): + return "retry" + elif isinstance(error, PermanentError): + return "give up" +``` + +```go !! go +// Go - Error interface for behavior +package main + +import ( + "errors" + "time" +) + +// Temporary indicates an error is temporary +type Temporary interface { + Temporary() bool +} + +// TemporaryError is retryable +type TemporaryError struct { + Msg string +} + +func (e *TemporaryError) Error() string { + return e.Msg +} + +func (e *TemporaryError) Temporary() bool { + return true +} + +// PermanentError is not retryable +type PermanentError struct { + Msg string +} + +func (e *PermanentError) Error() string { + return e.Msg +} + +func shouldRetry(err error) bool { + // Check if error implements Temporary interface + if te, ok := err.(Temporary); ok { + return te.Temporary() + } + return false +} + +func retryOperation(fn func() error) error { + for i := 0; i < 3; i++ { + err := fn() + if err == nil { + return nil + } + + if !shouldRetry(err) { + return err // Permanent error, don't retry + } + + // Temporary error, retry + time.Sleep(time.Second) + } + return errors.New("max retries exceeded") +} + +func main() { + tempErr := &TemporaryError{Msg: "connection timeout"} + permErr := &PermanentError{Msg: "authentication failed"} + + fmt.Println("Temporary retryable?", shouldRetry(tempErr)) // true + fmt.Println("Permanent retryable?", shouldRetry(permErr)) // false +} +``` + + +## Context and Errors + +### Context Cancellation + + +```python !! py +# Python - Cancellation with threading +import threading + +def worker(stop_event): + while not stop_event.is_set(): + # Do work + if stop_event.is_set(): + break + process() + +stop_event = threading.Event() +thread = threading.Thread(target=worker, args=(stop_event,)) +thread.start() + +# To cancel +stop_event.set() +``` + +```go !! go +// Go - Context for cancellation +package main + +import ( + "context" + "fmt" + "time" +) + +func worker(ctx context.Context) error { + ticker := time.NewTicker(100 * time.Millisecond) + defer ticker.Stop() + + for { + select { + case <-ctx.Done(): + // Context was cancelled + return ctx.Err() + case <-ticker.C: + // Do work + fmt.Println("Working...") + } + } +} + +func main() { + // Create cancellable context + ctx, cancel := context.WithCancel(context.Background()) + + // Start worker + go func() { + if err := worker(ctx); err != nil { + fmt.Println("Worker error:", err) + } + }() + + // Cancel after 500ms + time.Sleep(500 * time.Millisecond) + cancel() + + // Wait a bit for cleanup + time.Sleep(100 * time.Millisecond) +} +``` + + +### Context Timeout + + +```python !! py +# Python - Timeout with signal +import signal + +class TimeoutError(Exception): + pass + +def handler(signum, frame): + raise TimeoutError("Operation timed out") + +def fetch_with_timeout(url, timeout=5): + # Set alarm + signal.signal(signal.SIGALRM, handler) + signal.alarm(timeout) + + try: + result = fetch(url) + signal.alarm(0) # Cancel alarm + return result + except TimeoutError: + return None +``` + +```go !! go +// Go - Context with timeout +package main + +import ( + "context" + "fmt" + "time" +) + +func fetchData(ctx context.Context, url string) (string, error) { + // Simulate slow operation + select { + case <-time.After(3 * time.Second): + return "data", nil + case <-ctx.Done(): + return "", ctx.Err() // context.DeadlineExceeded + } +} + +func main() { + // Create context with timeout + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() // Always cancel to release resources + + data, err := fetchData(ctx, "http://example.com") + if err != nil { + fmt.Println("Error:", err) // context.DeadlineExceeded + return + } + + fmt.Println("Data:", data) +} +``` + + +## Defer for Cleanup + +### Resource Cleanup + + +```python !! py +# Python - try-finally +file = None +try: + file = open("data.txt") + process(file) +except IOError as e: + print(f"Error: {e}") +finally: + if file: + file.close() +``` + +```go !! go +// Go - defer always runs +package main + +import ( + "fmt" + "os" +) + +func processData() error { + file, err := os.Open("data.txt") + if err != nil { + return err + } + defer file.Close() // Always runs, even on error + + // Process file... + return nil +} + +func main() { + if err := processData(); err != nil { + fmt.Println("Error:", err) + } +} +``` + + +### Defer with Error Handling + + +```go !! go +// Go - Named return values with defer +package main + +import ( + "fmt" + "os" +) + +func processData() (err error) { + file, err := os.Open("data.txt") + if err != nil { + return err + } + + // Defer can modify named return value + defer func() { + closeErr := file.Close() + if closeErr != nil { + // If processing already failed, keep that error + if err == nil { + err = closeErr + } + } + }() + + // Process file... + return nil +} + +func main() { + if err := processData(); err != nil { + fmt.Println("Error:", err) + } +} +``` + + +## Panic and Recover + +### When to Use Panic + +Panic is NOT like Python exceptions. Use it ONLY for: +- Truly unrecoverable errors +- Programmer errors (should not happen in production) +- Initialization failures + + +```python !! py +# Python - Exceptions for control flow +def divide(a, b): + if b == 0: + raise ValueError("Cannot divide by zero") + return a / b + +# This is normal in Python +``` + +```go !! go +// Go - Panic for exceptional cases only +package main + +import "errors" + +// WRONG - Don't panic for expected errors +func divide(a, b int) int { + if b == 0 { + panic("division by zero") // WRONG! + } + return a / b +} + +// RIGHT - Return error +func divideCorrect(a, b int) (int, error) { + if b == 0 { + return 0, errors.New("division by zero") + } + return a / b, nil +} + +// OK - Panic for programmer errors +func MustGetUser(id int) *User { + user, err := db.GetUser(id) + if err != nil { + panic("database not initialized") // Programmer error + } + if user == nil { + panic("user must exist") // Programmer error + } + return user +} +``` + + +### Recover from Panic + + +```python !! py +# Python - Catch everything +try: + risky_operation() +except Exception as e: + print(f"Caught: {e}") +``` + +```go !! go +// Go - Recover from panic (rarely needed) +package main + +import ( + "fmt" + "log" +) + +func risky() (result string) { + // Defer + recover to catch panic + defer func() { + if r := recover(); r != nil { + // Convert panic to error + result = fmt.Sprintf("Recovered from: %v", r) + log.Printf("Panic recovered: %v", r) + } + }() + + panic("something went wrong") +} + +func main() { + fmt.Println(risky()) // "Recovered from: something went wrong" +} +``` + + +### Server Recover Middleware + + +```go !! go +// Go - Recover middleware for HTTP servers +package main + +import ( + "fmt" + "log" + "net/http" +) + +func recoveryMiddleware(next http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + defer func() { + if err := recover(); err != nil { + log.Printf("Panic recovered: %v", err) + http.Error(w, "Internal Server Error", 500) + } + }() + next(w, r) + } +} + +func handler(w http.ResponseWriter, r *http.Request) { + panic("oops!") +} + +func main() { + http.HandleFunc("/", recoveryMiddleware(handler)) + fmt.Println("Server starting on :8080") + log.Fatal(http.ListenAndServe(":8080", nil)) +} +``` + + +## Best Practices + +### 1. Always Check Errors + + +```python !! py +# Python - Exceptions force handling +try: + result = dangerous_operation() +except ValueError: + handle_error() +``` + +```go !! go +// Go - Explicit error checking +// WRONG - Ignoring error +file, _ := os.Open("data.txt") // Don't do this! + +// RIGHT - Always check error +file, err := os.Open("data.txt") +if err != nil { + return err +} +``` + + +### 2. Add Context to Errors + + +```python !! py +# Python - Exception with context +try: + user = get_user(id) +except ValueError as e: + raise ValueError(f"Failed to get user {id}: {e}") +``` + +```go !! go +// Go - Wrap with context +// WRONG - Lose original error +func processUser(id int) error { + user, err := getUser(id) + if err != nil { + return errors.New("user not found") // Lost context! + } + return nil +} + +// RIGHT - Wrap with context +func processUser(id int) error { + user, err := getUser(id) + if err != nil { + return fmt.Errorf("processUser(%d): %w", id, err) + } + return nil +} +``` + + +### 3. Handle Errors Immediately + + +```python !! py +# Python - Early return +def process(data): + if not data: + return None + + if len(data) < 10: + return None + + return transform(data) +``` + +```go !! go +// Go - Handle errors right away +func process(data string) (string, error) { + if data == "" { + return "", fmt.Errorf("empty data") + } + + if len(data) < 10 { + return "", fmt.Errorf("data too short: got %d, want >= 10", len(data)) + } + + return transform(data), nil +} +``` + + +### 4. Error Variables + + +```go !! go +// Go - Define error variables +package mypackage + +import "errors" + +var ( + ErrNotFound = errors.New("not found") + ErrInvalid = errors.New("invalid input") + ErrPermission = errors.New("permission denied") +) + +// Exported errors can be checked by callers +func FindItem(id int) (*Item, error) { + // ... + return nil, ErrNotFound +} + +// Caller can check +item, err := mypackage.FindItem(123) +if errors.Is(err, mypackage.ErrNotFound) { + // Handle not found +} +``` + + +### 5. Don't Panic in Libraries + + +```python !! py +# Python - Libraries raise exceptions +def process(data): + if not data: + raise ValueError("Invalid data") + return result +``` + +```go !! go +// Go - Libraries return errors +// WRONG - Library function panics +func Process(data string) string { + if data == "" { + panic("data is empty") // Don't panic in libraries! + } + return data +} + +// RIGHT - Library function returns error +func Process(data string) (string, error) { + if data == "" { + return "", errors.New("data is empty") + } + return data, nil +} +``` + + +## Real-World Examples + +### File Operations + + +```python !! py +# Python - File operations +def process_file(path): + try: + with open(path) as f: + data = f.read() + return parse(data) + except IOError as e: + print(f"Failed to read {path}: {e}") + return None +``` + +```go !! go +// Go - File operations +package main + +import ( + "fmt" + "os" +) + +func processFile(path string) error { + // Open file + file, err := os.Open(path) + if err != nil { + return fmt.Errorf("failed to open %s: %w", path, err) + } + defer file.Close() + + // Read file + data := make([]byte, 1024) + n, err := file.Read(data) + if err != nil { + return fmt.Errorf("failed to read %s: %w", path, err) + } + + // Process data + if err := parse(data[:n]); err != nil { + return fmt.Errorf("failed to parse %s: %w", path, err) + } + + return nil +} + +func main() { + if err := processFile("data.txt"); err != nil { + fmt.Println("Error:", err) + } +} +``` + + +### Database Operations + + +```python !! py +# Python - Database with exceptions +class UserNotFound(Exception): + pass + +class DatabaseError(Exception): + pass + +def get_user(db, id): + try: + cursor = db.cursor() + cursor.execute("SELECT * FROM users WHERE id = ?", (id,)) + result = cursor.fetchone() + if not result: + raise UserNotFound(f"User {id} not found") + return User(result) + except sqlite3.Error as e: + raise DatabaseError(f"Database error: {e}") +``` + +```go !! go +// Go - Database with errors +package main + +import ( + "database/sql" + "errors" + "fmt" +) + +var ( + ErrUserNotFound = errors.New("user not found") + ErrDatabase = errors.New("database error") +) + +func getUser(db *sql.DB, id int) (*User, error) { + var user User + + err := db.QueryRow( + "SELECT id, name, email FROM users WHERE id = $1", + id, + ).Scan(&user.ID, &user.Name, &user.Email) + + if err == sql.ErrNoRows { + return nil, fmt.Errorf("user %d: %w", id, ErrUserNotFound) + } + if err != nil { + return nil, fmt.Errorf("query user %d: %w", id, err) + } + + return &user, nil +} + +func main() { + db, err := sql.Open("postgres", connString) + if err != nil { + panic(err) + } + defer db.Close() + + user, err := getUser(db, 123) + if err != nil { + if errors.Is(err, ErrUserNotFound) { + fmt.Println("User not found") + } else { + fmt.Println("Database error:", err) + } + return + } + + fmt.Printf("User: %+v\n", user) +} +``` + + +### HTTP Client + + +```python !! py +# Python - HTTP with retries +import requests +from requests.adapters import HTTPAdapter +from urllib3.util.retry import Retry + +def fetch_with_retry(url, max_retries=3): + session = requests.Session() + retry = Retry(total=max_retries, backoff_factor=1) + adapter = HTTPAdapter(max_retries=retry) + session.mount('http://', adapter) + + try: + response = session.get(url, timeout=5) + response.raise_for_status() + return response.json() + except requests.RequestException as e: + print(f"Request failed: {e}") + return None +``` + +```go !! go +// Go - HTTP client with retries +package main + +import ( + "context" + "fmt" + "io" + "net/http" + "time" +) + +func fetchWithRetry(ctx context.Context, url string, maxRetries int) ([]byte, error) { + var lastErr error + + for attempt := 0; attempt < maxRetries; attempt++ { + req, err := http.NewRequestWithContext(ctx, "GET", url, nil) + if err != nil { + return nil, err + } + + resp, err := http.DefaultClient.Do(req) + if err != nil { + lastErr = err + time.Sleep(time.Duration(attempt+1) * time.Second) + continue + } + + defer resp.Body.Close() + + if resp.StatusCode == http.StatusOK { + return io.ReadAll(resp.Body) + } + + lastErr = fmt.Errorf("unexpected status: %d", resp.StatusCode) + + if resp.StatusCode >= 400 && resp.StatusCode < 500 { + // Client error, don't retry + return nil, lastErr + } + + // Server error, retry + time.Sleep(time.Duration(attempt+1) * time.Second) + } + + return nil, fmt.Errorf("after %d attempts: %w", maxRetries, lastErr) +} + +func main() { + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + data, err := fetchWithRetry(ctx, "http://example.com", 3) + if err != nil { + fmt.Println("Error:", err) + return + } + + fmt.Println("Data:", string(data)) +} +``` + + +## Summary + +### Key Concepts + +1. **Errors are values**: Returned explicitly, not thrown +2. **nil means success**: Non-nil error indicates failure +3. **Always check errors**: Handle immediately and explicitly +4. **Add context**: Wrap errors with fmt.Errorf and %w +5. **Sentinel errors**: Define error variables for comparison +6. **Custom types**: Create error structs for rich error data +7. **errors.Is/As**: Check and extract errors from chains +8. **Panic is rare**: Only for unrecoverable conditions + +### Common Patterns + +- **Return pattern**: `(result, error)` - error is last return value +- **Early return**: Check errors first, return early +- **Error wrapping**: `fmt.Errorf("context: %w", err)` +- **Error checking**: `errors.Is(err, ErrNotFound)` +- **Type assertion**: `errors.As(err, &customErr)` +- **Defer cleanup**: Always runs, even on panic + +### Best Practices + +1. Handle errors immediately +2. Add context when wrapping +3. Use sentinel errors for known cases +4. Don't ignore errors (use `_` sparingly) +5. Don't panic in libraries +6. Defer cleanup operations +7. Check errors in defer functions +8. Use context for cancellation/timeouts + +### Comparison with Python + +| Python | Go | +|--------|-----| +| Exceptions bubble up | Errors returned explicitly | +| try/except blocks | if err != nil checks | +| Exception types | Error values/types | +| raise Exception | return error | +| Invisible in signature | Visible in signature | +| Control flow via exceptions | Linear control flow | + +## Exercises + +1. Create a custom error type that includes: + - Error code + - Error message + - HTTP status code + - IsRetryable() method + +2. Implement retry logic with: + - Max retry attempts + - Exponential backoff + - Retry only on temporary errors + +3. Write a function that: + - Opens a file + - Reads its contents + - Parses the data + - Properly wraps errors at each step + +4. Create an error aggregation system: + - Collect multiple errors + - Format them nicely + - Check if specific error is in the collection + +5. Implement context-based cancellation: + - Create a long-running operation + - Support cancellation via context + - Clean up resources when cancelled + +## Next Steps + +Next module: **Goroutines and Concurrency** - Breaking free from the GIL and learning Go's powerful concurrency primitives. diff --git a/content/docs/py2go/module-06-error-handling.zh-cn.mdx b/content/docs/py2go/module-06-error-handling.zh-cn.mdx new file mode 100644 index 0000000..6923b6e --- /dev/null +++ b/content/docs/py2go/module-06-error-handling.zh-cn.mdx @@ -0,0 +1,1687 @@ +--- +title: "模块 6: 错误处理" +description: "Go 的显式错误处理与 Python 异常的对比" +--- + +## 简介 + +Go 采用与 Python 根本不同的错误处理方法。Go 不使用异常,而是将错误作为显式返回的值。这使得错误处理可见、可预测,并强制开发者显式处理错误。 + +### 关键差异 + +**Python 异常:** +- 错误自动沿调用栈向上冒泡 +- 可以在任何级别捕获 +- 在函数签名中不可见 +- 容易忽略(直到它们使程序崩溃) +- 通过异常进行控制流 + +**Go 错误:** +- 错误作为值返回 +- 必须在每一级显式处理 +- 在函数签名中可见 +- 不能在没有故意操作的情况下忽略 +- 线性控制流 + +## 基本错误处理 + +### Error 接口 + +在 Go 中,`error` 是一个接口: + +```go +type error interface { + Error() string +} +``` + +任何实现 `Error() string` 的类型都可以用作错误。 + + +```python !! py +# Python - 异常 +def divide(a, b): + if b == 0: + raise ValueError("Cannot divide by zero") + return a / b + +try: + result = divide(10, 0) +except ValueError as e: + print(f"Error: {e}") + # 可能记录、可能重试、可能中止 +``` + +```go !! go +// Go - 错误作为返回值 +package main + +import ( + "errors" + "fmt" +) + +func divide(a int, b int) (int, error) { + if b == 0 { + return 0, errors.New("cannot divide by zero") + } + return a / b, nil +} + +func main() { + result, err := divide(10, 0) + if err != nil { + fmt.Printf("Error: %v\n", err) + return + } + fmt.Printf("Result: %d\n", result) +} +``` + + +### Nil 错误约定 + +在 Go 中,`nil` 表示"没有错误": + + +```python !! py +# Python - 没有异常意味着成功 +def get_user(id): + user = db.query(id) + if user: + return user + return None # 不是错误,只是没有结果 +``` + +```go !! go +// Go - nil 错误意味着成功 +func getUser(id int) (*User, error) { + user, err := db.Query(id) + if err != nil { + return nil, err // 数据库错误 + } + if user == nil { + return nil, nil // 没有错误,但没有结果 + } + return user, nil // 成功 +} + +// 使用 +user, err := getUser(123) +if err != nil { + // 处理实际错误 + log.Fatal(err) +} +if user == nil { + // 没有找到用户,但没有错误 + fmt.Println("User not found") +} +``` + + +## 自定义错误类型 + +### 哨兵错误 + +哨兵错误是可以直接比较的预定义错误值: + + +```python !! py +# Python - 自定义异常 +class ValidationError(Exception): + pass + +class NotFoundError(Exception): + pass + +def validate_age(age): + if age < 0: + raise ValidationError("Age cannot be negative") + if age > 120: + raise ValidationError("Age too high") + +def get_user(id): + user = db.find(id) + if not user: + raise NotFoundError(f"User {id} not found") + return user + +# 使用 +try: + validate_age(-5) +except ValidationError as e: + print(f"Validation error: {e}") +except NotFoundError as e: + print(f"Not found: {e}") +``` + +```go !! go +// Go - 哨兵错误 +package main + +import ( + "errors" + "fmt" +) + +var ( + ErrNegativeAge = errors.New("age cannot be negative") + ErrAgeTooHigh = errors.New("age too high") + ErrUserNotFound = errors.New("user not found") +) + +func validateAge(age int) error { + if age < 0 { + return ErrNegativeAge + } + if age > 120 { + return ErrAgeTooHigh + } + return nil +} + +func getUser(id int) (*User, error) { + user := db.Find(id) + if user == nil { + return nil, ErrUserNotFound + } + return user, nil +} + +func main() { + err := validateAge(-5) + if err != nil { + // 直接比较 + if errors.Is(err, ErrNegativeAge) { + fmt.Println("Age is negative") + } else if errors.Is(err, ErrAgeTooHigh) { + fmt.Println("Age is too high") + } else { + fmt.Println("Other error:", err) + } + } +} +``` + + +### 自定义错误结构体 + +对于更复杂的错误处理,创建自定义错误类型: + + +```python !! py +# Python - 带数据的异常 +class ValidationError(Exception): + def __init__(self, field, message): + self.field = field + self.message = message + super().__init__(f"{field}: {message}") + +def validate_user(user): + if not user.email: + raise ValidationError("email", "is required") + if "@" not in user.email: + raise ValidationError("email", "is invalid") + +# 使用 +try: + validate_user(user) +except ValidationError as e: + print(f"Field {e.field}: {e.message}") +``` + +```go !! go +// Go - 自定义错误结构体 +package main + +import "fmt" + +// ValidationError 是自定义错误类型 +type ValidationError struct { + Field string + Message string +} + +// Error 实现错误接口 +func (e *ValidationError) Error() string { + return fmt.Sprintf("%s: %s", e.Field, e.Message) +} + +// User 表示用户 +type User struct { + Email string + Age int +} + +func validateUser(user *User) error { + if user.Email == "" { + return &ValidationError{ + Field: "email", + Message: "is required", + } + } + // 更多验证... + return nil +} + +func main() { + user := &User{} + err := validateUser(user) + if err != nil { + // 类型断言以访问自定义字段 + if validationErr, ok := err.(*ValidationError); ok { + fmt.Printf("Field %s: %s\n", validationErr.Field, validationErr.Message) + } else { + fmt.Println("Error:", err) + } + } +} +``` + + +### 带方法的错误 + +你的自定义错误可以有额外的方法: + + +```python !! py +# Python - 丰富异常 +class PaymentError(Exception): + def __init__(self, code, message, retryable): + self.code = code + self.message = message + self.retryable = retryable + + def is_retryable(self): + return self.retryable + +# 使用 +try: + process_payment() +except PaymentError as e: + if e.is_retryable(): + retry() +``` + +```go !! go +// Go - 带方法的错误 +package main + +import "fmt" + +// PaymentError 表示支付处理错误 +type PaymentError struct { + Code int + Message string + Retryable bool +} + +func (e *PaymentError) Error() string { + return fmt.Sprintf("payment error [%d]: %s", e.Code, e.Message) +} + +func (e *PaymentError) IsRetryable() bool { + return e.Retryable +} + +func processPayment(amount float64) error { + if amount <= 0 { + return &PaymentError{ + Code: 1001, + Message: "invalid amount", + Retryable: false, + } + } + if amount > 10000 { + return &PaymentError{ + Code: 1002, + Message: "amount exceeds limit", + Retryable: true, + } + } + return nil +} + +func main() { + err := processPayment(15000) + if err != nil { + if paymentErr, ok := err.(*PaymentError); ok { + fmt.Println("Error:", paymentErr) + if paymentErr.IsRetryable() { + fmt.Println("This error is retryable") + } + } + } +} +``` + + +## 错误包装 + +### 添加上下文 + +Go 1.13+ 引入了使用 `%w` 动词的错误包装: + + +```python !! py +# Python - 异常链 +try: + data = load_data() +except ValueError as e: + raise ValueError("Failed to process data") from e + +# 或 Python 3 的隐式链接 +try: + data = load_data() +except ValueError: + raise ValueError("Failed to process data") +``` + +```go !! go +// Go - 错误包装 +package main + +import ( + "errors" + "fmt" +) + +func loadData() error { + return errors.New("file not found") +} + +func processData() error { + err := loadData() + if err != nil { + // 使用 %w 包装错误并添加上下文 + return fmt.Errorf("processData failed: %w", err) + } + return nil +} + +func main() { + err := processData() + if err != nil { + fmt.Println("Error:", err) + + // 解包以获取原始错误 + unwrapped := errors.Unwrap(err) + fmt.Println("Original:", unwrapped) + + // 检查特定错误是否在链中 + if errors.Is(err, errors.New("file not found")) { + fmt.Println("File not found in error chain") + } + } +} +``` + + +### 多层包装 + +错误可以被包装多次: + + +```python !! py +# Python - 异常链 +def layer3(): + raise ValueError("Base error") + +def layer2(): + try: + layer3() + except ValueError as e: + raise RuntimeError("Layer 2 failed") from e + +def layer1(): + try: + layer2() + except RuntimeError as e: + raise RuntimeError("Layer 1 failed") from e + +# 回溯显示完整链 +``` + +```go !! go +// Go - 多层包装 +package main + +import ( + "errors" + "fmt" +) + +func layer3() error { + return errors.New("base error") +} + +func layer2() error { + err := layer3() + if err != nil { + return fmt.Errorf("layer2: %w", err) + } + return nil +} + +func layer1() error { + err := layer2() + if err != nil { + return fmt.Errorf("layer1: %w", err) + } + return nil +} + +func main() { + err := layer1() + if err != nil { + fmt.Println("Full error:", err) + + // 多次解包 + err = errors.Unwrap(err) // "layer2: base error" + err = errors.Unwrap(err) // "base error" + + // 或检查特定错误是否在链中的任何位置 + err = layer1() + if errors.Is(err, errors.New("base error")) { + fmt.Println("Base error found in chain") + } + } +} +``` + + +### 错误格式化动词 + + +```go !! go +// Go - 格式化动词 +package main + +import ( + "errors" + "fmt" +) + +func main() { + baseErr := errors.New("base error") + + // %v - 只是错误文本 + fmt1 := fmt.Errorf("context: %v", baseErr) + fmt.Println(fmt1) // "context: base error" + + // %w - 包装错误(支持 errors.Is/As) + fmt2 := fmt.Errorf("context: %w", baseErr) + fmt.Println(errors.Is(fmt2, baseErr)) // true + + // %+v - (如果错误支持)详细输出 + // 某些错误类型实现 Formatter 接口 + // 用于堆栈跟踪和额外细节 +} +``` + + +## 错误比较和类型断言 + +### errors.Is() + +检查错误是否与链中的特定值匹配: + + +```python !! py +# Python - 异常类型检查 +try: + process() +except ValueError: + print("Value error") +except RuntimeError as e: + if "timeout" in str(e): + print("Timeout error") +``` + +```go !! go +// Go - errors.Is 用于比较 +package main + +import ( + "errors" + "fmt" +) + +var ( + ErrNotFound = errors.New("not found") + ErrTimeout = errors.New("timeout") +) + +func process() error { + return fmt.Errorf("process failed: %w", ErrNotFound) +} + +func main() { + err := process() + + // 检查特定错误是否在链中 + if errors.Is(err, ErrNotFound) { + fmt.Println("Not found error occurred") + } + + if errors.Is(err, ErrTimeout) { + fmt.Println("Timeout error occurred") + } + + // 也可以检查新错误 + if errors.Is(err, errors.New("not found")) { + fmt.Println("Not found (new instance)") + } +} +``` + + +### errors.As() + +错误链的类型断言: + + +```python !! py +# Python - 异常实例检查 +try: + process() +except ValidationError as e: + print(f"Validation error: {e.field}") +except PaymentError as e: + print(f"Payment error: {e.code}") +``` + +```go !! go +// Go - errors.As 用于类型断言 +package main + +import ( + "errors" + "fmt" +) + +type ValidationError struct { + Field string +} + +func (e *ValidationError) Error() string { + return fmt.Sprintf("validation error: %s", e.Field) +} + +type PaymentError struct { + Code int +} + +func (e *PaymentError) Error() string { + return fmt.Sprintf("payment error: %d", e.Code) +} + +func process() error { + return &ValidationError{Field: "email"} +} + +func main() { + err := process() + + // 检查错误是否为特定类型 + var validationErr *ValidationError + if errors.As(err, &validationErr) { + fmt.Printf("Validation error on field: %s\n", validationErr.Field) + } + + var paymentErr *PaymentError + if errors.As(err, &paymentErr) { + fmt.Printf("Payment error code: %d\n", paymentErr.Code) + } + + // 也可以直接检查 + if _, ok := err.(*ValidationError); ok { + fmt.Println("This is a ValidationError") + } +} +``` + + +## 错误处理模式 + +### 重试逻辑 + + +```python !! py +# Python - 重试装饰器 +import time + +def retry(max_attempts=3, delay=1): + def decorator(func): + def wrapper(*args, **kwargs): + for attempt in range(max_attempts): + try: + return func(*args, **kwargs) + except IOError as e: + if attempt == max_attempts - 1: + raise + time.sleep(delay) + return None + return wrapper + return decorator + +@retry(max_attempts=3) +def fetch_data(): + return api_call() +``` + +```go !! go +// Go - 重试函数 +package main + +import ( + "errors" + "fmt" + "time" +) + +var ErrTemporary = errors.New("temporary error") + +func fetch() error { + // 模拟失败 + return ErrTemporary +} + +func retry(attempts int, delay time.Duration, fn func() error) error { + var err error + for i := 0; i < attempts; i++ { + err = fn() + if err == nil { + return nil + } + + // 检查错误是否可重试 + if !errors.Is(err, ErrTemporary) { + return err + } + + if i < attempts-1 { + fmt.Printf("Attempt %d failed, retrying...\n", i+1) + time.Sleep(delay) + } + } + return fmt.Errorf("after %d attempts: %w", attempts, err) +} + +func main() { + err := retry(3, time.Second, fetch) + if err != nil { + fmt.Println("Final error:", err) + } +} +``` + + +### 错误聚合 + + +```python !! py +# Python - 收集多个错误 +class MultiError(Exception): + def __init__(self, errors): + self.errors = errors + super().__init__(f"{len(errors)} errors occurred") + +def process_all(items): + errors = [] + for item in items: + try: + process(item) + except Exception as e: + errors.append(e) + + if errors: + raise MultiError(errors) +``` + +```go !! go +// Go - 收集多个错误 +package main + +import ( + "fmt" + "strings" +) + +// MultiError 收集多个错误 +type MultiError struct { + Errors []error +} + +func (e *MultiError) Error() string { + var sb strings.Builder + sb.WriteString(fmt.Sprintf("%d errors occurred:", len(e.Errors))) + for _, err := range e.Errors { + sb.WriteString("\n - ") + sb.WriteString(err.Error()) + } + return sb.String() +} + +func processAll(items []int) error { + var errs []error + for _, item := range items { + if err := process(item); err != nil { + errs = append(errs, err) + } + } + + if len(errs) > 0 { + return &MultiError{Errors: errs} + } + return nil +} + +func process(item int) error { + if item < 0 { + return fmt.Errorf("invalid item: %d", item) + } + return nil +} + +func main() { + items := []int{1, 2, -3, 4, -5} + err := processAll(items) + if err != nil { + fmt.Println(err) + } +} +``` + + +### 临时 vs 永久错误 + + +```python !! py +# Python - 自定义异常 +class TemporaryError(Exception): + """重试此操作""" + pass + +class PermanentError(Exception): + """不要重试,放弃""" + pass + +def handle_error(error): + if isinstance(error, TemporaryError): + return "retry" + elif isinstance(error, PermanentError): + return "give up" +``` + +```go !! go +// Go - 行为的错误接口 +package main + +import ( + "errors" + "time" +) + +// Temporary 指示错误是临时的 +type Temporary interface { + Temporary() bool +} + +// TemporaryError 可重试 +type TemporaryError struct { + Msg string +} + +func (e *TemporaryError) Error() string { + return e.Msg +} + +func (e *TemporaryError) Temporary() bool { + return true +} + +// PermanentError 不可重试 +type PermanentError struct { + Msg string +} + +func (e *PermanentError) Error() string { + return e.Msg +} + +func shouldRetry(err error) bool { + // 检查错误是否实现 Temporary 接口 + if te, ok := err.(Temporary); ok { + return te.Temporary() + } + return false +} + +func retryOperation(fn func() error) error { + for i := 0; i < 3; i++ { + err := fn() + if err == nil { + return nil + } + + if !shouldRetry(err) { + return err // 永久错误,不要重试 + } + + // 临时错误,重试 + time.Sleep(time.Second) + } + return errors.New("max retries exceeded") +} + +func main() { + tempErr := &TemporaryError{Msg: "connection timeout"} + permErr := &PermanentError{Msg: "authentication failed"} + + fmt.Println("Temporary retryable?", shouldRetry(tempErr)) // true + fmt.Println("Permanent retryable?", shouldRetry(permErr)) // false +} +``` + + +## Context 和错误 + +### Context 取消 + + +```python !! py +# Python - 使用线程取消 +import threading + +def worker(stop_event): + while not stop_event.is_set(): + # 做工作 + if stop_event.is_set(): + break + process() + +stop_event = threading.Event() +thread = threading.Thread(target=worker, args=(stop_event,)) +thread.start() + +# 取消 +stop_event.set() +``` + +```go !! go +// Go - Context 用于取消 +package main + +import ( + "context" + "fmt" + "time" +) + +func worker(ctx context.Context) error { + ticker := time.NewTicker(100 * time.Millisecond) + defer ticker.Stop() + + for { + select { + case <-ctx.Done(): + // Context 被取消 + return ctx.Err() + case <-ticker.C: + // 做工作 + fmt.Println("Working...") + } + } +} + +func main() { + // 创建可取消的 context + ctx, cancel := context.WithCancel(context.Background()) + + // 启动 worker + go func() { + if err := worker(ctx); err != nil { + fmt.Println("Worker error:", err) + } + }() + + // 500ms 后取消 + time.Sleep(500 * time.Millisecond) + cancel() + + // 等待一点时间以清理 + time.Sleep(100 * time.Millisecond) +} +``` + + +### Context 超时 + + +```python !! py +# Python - 使用信号超时 +import signal + +class TimeoutError(Exception): + pass + +def handler(signum, frame): + raise TimeoutError("Operation timed out") + +def fetch_with_timeout(url, timeout=5): + # 设置闹钟 + signal.signal(signal.SIGALRM, handler) + signal.alarm(timeout) + + try: + result = fetch(url) + signal.alarm(0) # 取消闹钟 + return result + except TimeoutError: + return None +``` + +```go !! go +// Go - 带超时的 Context +package main + +import ( + "context" + "fmt" + "time" +) + +func fetchData(ctx context.Context, url string) (string, error) { + // 模拟慢操作 + select { + case <-time.After(3 * time.Second): + return "data", nil + case <-ctx.Done(): + return "", ctx.Err() // context.DeadlineExceeded + } +} + +func main() { + // 创建带超时的 context + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() // 始终取消以释放资源 + + data, err := fetchData(ctx, "http://example.com") + if err != nil { + fmt.Println("Error:", err) // context.DeadlineExceeded + return + } + + fmt.Println("Data:", data) +} +``` + + +## Defer 用于清理 + +### 资源清理 + + +```python !! py +# Python - try-finally +file = None +try: + file = open("data.txt") + process(file) +except IOError as e: + print(f"Error: {e}") +finally: + if file: + file.close() +``` + +```go !! go +// Go - defer 始终运行 +package main + +import ( + "fmt" + "os" +) + +func processData() error { + file, err := os.Open("data.txt") + if err != nil { + return err + } + defer file.Close() // 始终运行,即使出错 + + // 处理文件... + return nil +} + +func main() { + if err := processData(); err != nil { + fmt.Println("Error:", err) + } +} +``` + + +### Defer 和错误处理 + + +```go !! go +// Go - 带命名返回值的 defer +package main + +import ( + "fmt" + "os" +) + +func processData() (err error) { + file, err := os.Open("data.txt") + if err != nil { + return err + } + + // Defer 可以修改命名返回值 + defer func() { + closeErr := file.Close() + if closeErr != nil { + // 如果处理已经失败,保留该错误 + if err == nil { + err = closeErr + } + } + }() + + // 处理文件... + return nil +} + +func main() { + if err := processData(); err != nil { + fmt.Println("Error:", err) + } +} +``` + + +## Panic 和 Recover + +### 何时使用 Panic + +Panic 不像 Python 异常。仅用于: +- 真正无法恢复的错误 +- 程序员错误(不应该在生产环境中发生) +- 初始化失败 + + +```python !! py +# Python - 异常用于控制流 +def divide(a, b): + if b == 0: + raise ValueError("Cannot divide by zero") + return a / b + +# 这在 Python 中是正常的 +``` + +```go !! go +// Go - Panic 仅用于异常情况 +package main + +import "errors" + +// 错误 - 不要对预期错误 panic +func divide(a, b int) int { + if b == 0 { + panic("division by zero") // 错误! + } + return a / b +} + +// 正确 - 返回错误 +func divideCorrect(a, b int) (int, error) { + if b == 0 { + return 0, errors.New("division by zero") + } + return a / b, nil +} + +// 可以 - Panic 用于程序员错误 +func MustGetUser(id int) *User { + user, err := db.GetUser(id) + if err != nil { + panic("database not initialized") // 程序员错误 + } + if user == nil { + panic("user must exist") // 程序员错误 + } + return user +} +``` + + +### 从 Panic 恢复 + + +```python !! py +# Python - 捕获所有 +try: + risky_operation() +except Exception as e: + print(f"Caught: {e}") +``` + +```go !! go +// Go - 从 panic 恢复(很少需要) +package main + +import ( + "fmt" + "log" +) + +func risky() (result string) { + // Defer + recover 捕获 panic + defer func() { + if r := recover(); r != nil { + // 将 panic 转换为错误 + result = fmt.Sprintf("Recovered from: %v", r) + log.Printf("Panic recovered: %v", r) + } + }() + + panic("something went wrong") +} + +func main() { + fmt.Println(risky()) // "Recovered from: something went wrong" +} +``` + + +### 服务器恢复中间件 + + +```go !! go +// Go - HTTP 服务器的恢复中间件 +package main + +import ( + "fmt" + "log" + "net/http" +) + +func recoveryMiddleware(next http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + defer func() { + if err := recover(); err != nil { + log.Printf("Panic recovered: %v", err) + http.Error(w, "Internal Server Error", 500) + } + }() + next(w, r) + } +} + +func handler(w http.ResponseWriter, r *http.Request) { + panic("oops!") +} + +func main() { + http.HandleFunc("/", recoveryMiddleware(handler)) + fmt.Println("Server starting on :8080") + log.Fatal(http.ListenAndServe(":8080", nil)) +} +``` + + +## 最佳实践 + +### 1. 始终检查错误 + + +```python !! py +# Python - 异常强制处理 +try: + result = dangerous_operation() +except ValueError: + handle_error() +``` + +```go !! go +// Go - 显式错误检查 +// 错误 - 忽略错误 +file, _ := os.Open("data.txt") // 不要这样做! + +// 正确 - 始终检查错误 +file, err := os.Open("data.txt") +if err != nil { + return err +} +``` + + +### 2. 为错误添加上下文 + + +```python !! py +# Python - 带上下文的异常 +try: + user = get_user(id) +except ValueError as e: + raise ValueError(f"Failed to get user {id}: {e}") +``` + +```go !! go +// Go - 包装上下文 +// 错误 - 丢失原始错误 +func processUser(id int) error { + user, err := getUser(id) + if err != nil { + return errors.New("user not found") // 丢失上下文! + } + return nil +} + +// 正确 - 包装上下文 +func processUser(id int) error { + user, err := getUser(id) + if err != nil { + return fmt.Errorf("processUser(%d): %w", id, err) + } + return nil +} +``` + + +### 3. 立即处理错误 + + +```python !! py +# Python - 早期返回 +def process(data): + if not data: + return None + + if len(data) < 10: + return None + + return transform(data) +``` + +```go !! go +// Go - 立即处理错误 +func process(data string) (string, error) { + if data == "" { + return "", fmt.Errorf("empty data") + } + + if len(data) < 10 { + return "", fmt.Errorf("data too short: got %d, want >= 10", len(data)) + } + + return transform(data), nil +} +``` + + +### 4. 错误变量 + + +```go !! go +// Go - 定义错误变量 +package mypackage + +import "errors" + +var ( + ErrNotFound = errors.New("not found") + ErrInvalid = errors.New("invalid input") + ErrPermission = errors.New("permission denied") +) + +// 导出的错误可以被调用者检查 +func FindItem(id int) (*Item, error) { + // ... + return nil, ErrNotFound +} + +// 调用者可以检查 +item, err := mypackage.FindItem(123) +if errors.Is(err, mypackage.ErrNotFound) { + // 处理未找到 +} +``` + + +### 5. 不要在库中 Panic + + +```python !! py +# Python - 库抛出异常 +def process(data): + if not data: + raise ValueError("Invalid data") + return result +``` + +```go !! go +// Go - 库返回错误 +// 错误 - 库函数 panic +func Process(data string) string { + if data == "" { + panic("data is empty") // 不要在库中 panic! + } + return data +} + +// 正确 - 库函数返回错误 +func Process(data string) (string, error) { + if data == "" { + return "", errors.New("data is empty") + } + return data, nil +} +``` + + +## 实际示例 + +### 文件操作 + + +```python !! py +# Python - 文件操作 +def process_file(path): + try: + with open(path) as f: + data = f.read() + return parse(data) + except IOError as e: + print(f"Failed to read {path}: {e}") + return None +``` + +```go !! go +// Go - 文件操作 +package main + +import ( + "fmt" + "os" +) + +func processFile(path string) error { + // 打开文件 + file, err := os.Open(path) + if err != nil { + return fmt.Errorf("failed to open %s: %w", path, err) + } + defer file.Close() + + // 读取文件 + data := make([]byte, 1024) + n, err := file.Read(data) + if err != nil { + return fmt.Errorf("failed to read %s: %w", path, err) + } + + // 处理数据 + if err := parse(data[:n]); err != nil { + return fmt.Errorf("failed to parse %s: %w", path, err) + } + + return nil +} + +func main() { + if err := processFile("data.txt"); err != nil { + fmt.Println("Error:", err) + } +} +``` + + +### 数据库操作 + + +```python !! py +# Python - 带异常的数据库 +class UserNotFound(Exception): + pass + +class DatabaseError(Exception): + pass + +def get_user(db, id): + try: + cursor = db.cursor() + cursor.execute("SELECT * FROM users WHERE id = ?", (id,)) + result = cursor.fetchone() + if not result: + raise UserNotFound(f"User {id} not found") + return User(result) + except sqlite3.Error as e: + raise DatabaseError(f"Database error: {e}") +``` + +```go !! go +// Go - 带错误的数据库 +package main + +import ( + "database/sql" + "errors" + "fmt" +) + +var ( + ErrUserNotFound = errors.New("user not found") + ErrDatabase = errors.New("database error") +) + +func getUser(db *sql.DB, id int) (*User, error) { + var user User + + err := db.QueryRow( + "SELECT id, name, email FROM users WHERE id = $1", + id, + ).Scan(&user.ID, &user.Name, &user.Email) + + if err == sql.ErrNoRows { + return nil, fmt.Errorf("user %d: %w", id, ErrUserNotFound) + } + if err != nil { + return nil, fmt.Errorf("query user %d: %w", id, err) + } + + return &user, nil +} + +func main() { + db, err := sql.Open("postgres", connString) + if err != nil { + panic(err) + } + defer db.Close() + + user, err := getUser(db, 123) + if err != nil { + if errors.Is(err, ErrUserNotFound) { + fmt.Println("User not found") + } else { + fmt.Println("Database error:", err) + } + return + } + + fmt.Printf("User: %+v\n", user) +} +``` + + +### HTTP 客户端 + + +```python !! py +# Python - 带重试的 HTTP +import requests +from requests.adapters import HTTPAdapter +from urllib3.util.retry import Retry + +def fetch_with_retry(url, max_retries=3): + session = requests.Session() + retry = Retry(total=max_retries, backoff_factor=1) + adapter = HTTPAdapter(max_retries=retry) + session.mount('http://', adapter) + + try: + response = session.get(url, timeout=5) + response.raise_for_status() + return response.json() + except requests.RequestException as e: + print(f"Request failed: {e}") + return None +``` + +```go !! go +// Go - 带重试的 HTTP 客户端 +package main + +import ( + "context" + "fmt" + "io" + "net/http" + "time" +) + +func fetchWithRetry(ctx context.Context, url string, maxRetries int) ([]byte, error) { + var lastErr error + + for attempt := 0; attempt < maxRetries; attempt++ { + req, err := http.NewRequestWithContext(ctx, "GET", url, nil) + if err != nil { + return nil, err + } + + resp, err := http.DefaultClient.Do(req) + if err != nil { + lastErr = err + time.Sleep(time.Duration(attempt+1) * time.Second) + continue + } + + defer resp.Body.Close() + + if resp.StatusCode == http.StatusOK { + return io.ReadAll(resp.Body) + } + + lastErr = fmt.Errorf("unexpected status: %d", resp.StatusCode) + + if resp.StatusCode >= 400 && resp.StatusCode < 500 { + // 客户端错误,不要重试 + return nil, lastErr + } + + // 服务器错误,重试 + time.Sleep(time.Duration(attempt+1) * time.Second) + } + + return nil, fmt.Errorf("after %d attempts: %w", maxRetries, lastErr) +} + +func main() { + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + data, err := fetchWithRetry(ctx, "http://example.com", 3) + if err != nil { + fmt.Println("Error:", err) + return + } + + fmt.Println("Data:", string(data)) +} +``` + + +## 总结 + +### 关键概念 + +1. **错误是值**:显式返回,不是抛出 +2. **nil 意味着成功**:非 nil 错误表示失败 +3. **始终检查错误**:立即显式处理 +4. **添加上下文**:使用 fmt.Errorf 和 %w 包装错误 +5. **哨兵错误**:定义错误变量用于比较 +6. **自定义类型**:创建错误结构体以获得丰富错误数据 +7. **errors.Is/As**:从链中检查和提取错误 +8. **Panic 很罕见**:仅用于无法恢复的情况 + +### 常见模式 + +- **返回模式**:`(result, error)` - 错误是最后一个返回值 +- **早期返回**:先检查错误,早期返回 +- **错误包装**:`fmt.Errorf("context: %w", err)` +- **错误检查**:`errors.Is(err, ErrNotFound)` +- **类型断言**:`errors.As(err, &customErr)` +- **Defer 清理**:始终运行,即使 panic + +### 最佳实践 + +1. 立即处理错误 +2. 包装时添加上下文 +3. 对已知情况使用哨兵错误 +4. 不要忽略错误(少用 `_`) +5. 不要在库中 panic +6. Defer 清理操作 +7. 检查 defer 函数中的错误 +8. 使用 context 进行取消/超时 + +### 与 Python 的对比 + +| Python | Go | +|--------|-----| +| 异常向上冒泡 | 错误显式返回 | +| try/except 块 | if err != nil 检查 | +| 异常类型 | 错误值/类型 | +| raise Exception | return error | +| 签名中不可见 | 签名中可见 | +| 通过异常进行控制流 | 线性控制流 | + +## 练习 + +1. 创建一个包含以下内容的自定义错误类型: + - 错误代码 + - 错误消息 + - HTTP 状态码 + - IsRetryable() 方法 + +2. 实现重试逻辑: + - 最大重试次数 + - 指数退避 + - 仅在临时错误时重试 + +3. 编写一个函数: + - 打开文件 + - 读取内容 + - 解析数据 + - 在每一步正确包装错误 + +4. 创建错误聚合系统: + - 收集多个错误 + - 很好地格式化它们 + - 检查特定错误是否在集合中 + +5. 实现基于 context 的取消: + - 创建长时间运行的操作 + - 支持通过 context 取消 + - 取消时清理资源 + +## 下一步 + +下一模块:**Goroutines 和并发** - 摆脱 GIL 并学习强大的 Go 并发原语。 diff --git a/content/docs/py2go/module-06-error-handling.zh-tw.mdx b/content/docs/py2go/module-06-error-handling.zh-tw.mdx new file mode 100644 index 0000000..0d8795e --- /dev/null +++ b/content/docs/py2go/module-06-error-handling.zh-tw.mdx @@ -0,0 +1,1687 @@ +--- +title: "模組 6: 錯誤處理" +description: "Go 的顯式錯誤處理與 Python 異常的對比" +--- + +## 簡介 + +Go 採用與 Python 根本不同的錯誤處理方法。Go 不使用異常,而是將錯誤作為顯式返回的值。這使得錯誤處理可見、可預測,並強制開發者顯式處理錯誤。 + +### 關鍵差異 + +**Python 異常:** +- 錯誤自動沿呼叫堆疊向上冒泡 +- 可以在任何層級捕獲 +- 在函數簽章中不可見 +- 容易忽略(直到它們使程式崩潰) +- 通過異常進行控制流 + +**Go 錯誤:** +- 錯誤作為值返回 +- 必須在每一層顯式處理 +- 在函數簽章中可見 +- 不能在沒有故意操作的情況下忽略 +- 線性控制流 + +## 基本錯誤處理 + +### Error 介面 + +在 Go 中,`error` 是一個介面: + +```go +type error interface { + Error() string +} +``` + +任何實現 `Error() string` 的型別都可以用作錯誤。 + + +```python !! py +# Python - 異常 +def divide(a, b): + if b == 0: + raise ValueError("Cannot divide by zero") + return a / b + +try: + result = divide(10, 0) +except ValueError as e: + print(f"Error: {e}") + # 可能記錄、可能重試、可能中止 +``` + +```go !! go +// Go - 錯誤作為返回值 +package main + +import ( + "errors" + "fmt" +) + +func divide(a int, b int) (int, error) { + if b == 0 { + return 0, errors.New("cannot divide by zero") + } + return a / b, nil +} + +func main() { + result, err := divide(10, 0) + if err != nil { + fmt.Printf("Error: %v\n", err) + return + } + fmt.Printf("Result: %d\n", result) +} +``` + + +### Nil 錯誤約定 + +在 Go 中,`nil` 表示"沒有錯誤": + + +```python !! py +# Python - 沒有異常意味著成功 +def get_user(id): + user = db.query(id) + if user: + return user + return None # 不是錯誤,只是沒有結果 +``` + +```go !! go +// Go - nil 錯誤意味著成功 +func getUser(id int) (*User, error) { + user, err := db.Query(id) + if err != nil { + return nil, err // 資料庫錯誤 + } + if user == nil { + return nil, nil // 沒有錯誤,但沒有結果 + } + return user, nil // 成功 +} + +// 使用 +user, err := getUser(123) +if err != nil { + // 處理實際錯誤 + log.Fatal(err) +} +if user == nil { + // 沒有找到使用者,但沒有錯誤 + fmt.Println("User not found") +} +``` + + +## 自訂錯誤型別 + +### 哨兵錯誤 + +哨兵錯誤是可以直接比較的預定義錯誤值: + + +```python !! py +# Python - 自訂異常 +class ValidationError(Exception): + pass + +class NotFoundError(Exception): + pass + +def validate_age(age): + if age < 0: + raise ValidationError("Age cannot be negative") + if age > 120: + raise ValidationError("Age too high") + +def get_user(id): + user = db.find(id) + if not user: + raise NotFoundError(f"User {id} not found") + return user + +# 使用 +try: + validate_age(-5) +except ValidationError as e: + print(f"Validation error: {e}") +except NotFoundError as e: + print(f"Not found: {e}") +``` + +```go !! go +// Go - 哨兵錯誤 +package main + +import ( + "errors" + "fmt" +) + +var ( + ErrNegativeAge = errors.New("age cannot be negative") + ErrAgeTooHigh = errors.New("age too high") + ErrUserNotFound = errors.New("user not found") +) + +func validateAge(age int) error { + if age < 0 { + return ErrNegativeAge + } + if age > 120 { + return ErrAgeTooHigh + } + return nil +} + +func getUser(id int) (*User, error) { + user := db.Find(id) + if user == nil { + return nil, ErrUserNotFound + } + return user, nil +} + +func main() { + err := validateAge(-5) + if err != nil { + // 直接比較 + if errors.Is(err, ErrNegativeAge) { + fmt.Println("Age is negative") + } else if errors.Is(err, ErrAgeTooHigh) { + fmt.Println("Age is too high") + } else { + fmt.Println("Other error:", err) + } + } +} +``` + + +### 自訂錯誤結構體 + +對於更複雜的錯誤處理,建立自訂錯誤型別: + + +```python !! py +# Python - 帶資料的異常 +class ValidationError(Exception): + def __init__(self, field, message): + self.field = field + self.message = message + super().__init__(f"{field}: {message}") + +def validate_user(user): + if not user.email: + raise ValidationError("email", "is required") + if "@" not in user.email: + raise ValidationError("email", "is invalid") + +# 使用 +try: + validate_user(user) +except ValidationError as e: + print(f"Field {e.field}: {e.message}") +``` + +```go !! go +// Go - 自訂錯誤結構體 +package main + +import "fmt" + +// ValidationError 是自訂錯誤型別 +type ValidationError struct { + Field string + Message string +} + +// Error 實作錯誤介面 +func (e *ValidationError) Error() string { + return fmt.Sprintf("%s: %s", e.Field, e.Message) +} + +// User 表示使用者 +type User struct { + Email string + Age int +} + +func validateUser(user *User) error { + if user.Email == "" { + return &ValidationError{ + Field: "email", + Message: "is required", + } + } + // 更多驗證... + return nil +} + +func main() { + user := &User{} + err := validateUser(user) + if err != nil { + // 型別斷言以存取自訂欄位 + if validationErr, ok := err.(*ValidationError); ok { + fmt.Printf("Field %s: %s\n", validationErr.Field, validationErr.Message) + } else { + fmt.Println("Error:", err) + } + } +} +``` + + +### 帶方法的錯誤 + +你的自訂錯誤可以有額外的方法: + + +```python !! py +# Python - 豐富異常 +class PaymentError(Exception): + def __init__(self, code, message, retryable): + self.code = code + self.message = message + self.retryable = retryable + + def is_retryable(self): + return self.retryable + +# 使用 +try: + process_payment() +except PaymentError as e: + if e.is_retryable(): + retry() +``` + +```go !! go +// Go - 帶方法的錯誤 +package main + +import "fmt" + +// PaymentError 表示支付處理錯誤 +type PaymentError struct { + Code int + Message string + Retryable bool +} + +func (e *PaymentError) Error() string { + return fmt.Sprintf("payment error [%d]: %s", e.Code, e.Message) +} + +func (e *PaymentError) IsRetryable() bool { + return e.Retryable +} + +func processPayment(amount float64) error { + if amount <= 0 { + return &PaymentError{ + Code: 1001, + Message: "invalid amount", + Retryable: false, + } + } + if amount > 10000 { + return &PaymentError{ + Code: 1002, + Message: "amount exceeds limit", + Retryable: true, + } + } + return nil +} + +func main() { + err := processPayment(15000) + if err != nil { + if paymentErr, ok := err.(*PaymentError); ok { + fmt.Println("Error:", paymentErr) + if paymentErr.IsRetryable() { + fmt.Println("This error is retryable") + } + } + } +} +``` + + +## 錯誤包裝 + +### 新增上下文 + +Go 1.13+ 引入了使用 `%w` 動詞的錯誤包裝: + + +```python !! py +# Python - 異常鏈 +try: + data = load_data() +except ValueError as e: + raise ValueError("Failed to process data") from e + +# 或 Python 3 的隱式連結 +try: + data = load_data() +except ValueError: + raise ValueError("Failed to process data") +``` + +```go !! go +// Go - 錯誤包裝 +package main + +import ( + "errors" + "fmt" +) + +func loadData() error { + return errors.New("file not found") +} + +func processData() error { + err := loadData() + if err != nil { + // 使用 %w 包裝錯誤並新增上下文 + return fmt.Errorf("processData failed: %w", err) + } + return nil +} + +func main() { + err := processData() + if err != nil { + fmt.Println("Error:", err) + + // 解包以獲取原始錯誤 + unwrapped := errors.Unwrap(err) + fmt.Println("Original:", unwrapped) + + // 檢查特定錯誤是否在鏈中 + if errors.Is(err, errors.New("file not found")) { + fmt.Println("File not found in error chain") + } + } +} +``` + + +### 多層包裝 + +錯誤可以被包裝多次: + + +```python !! py +# Python - 異常鏈 +def layer3(): + raise ValueError("Base error") + +def layer2(): + try: + layer3() + except ValueError as e: + raise RuntimeError("Layer 2 failed") from e + +def layer1(): + try: + layer2() + except RuntimeError as e: + raise RuntimeError("Layer 1 failed") from e + +# 回溯顯示完整鏈 +``` + +```go !! go +// Go - 多層包裝 +package main + +import ( + "errors" + "fmt" +) + +func layer3() error { + return errors.New("base error") +} + +func layer2() error { + err := layer3() + if err != nil { + return fmt.Errorf("layer2: %w", err) + } + return nil +} + +func layer1() error { + err := layer2() + if err != nil { + return fmt.Errorf("layer1: %w", err) + } + return nil +} + +func main() { + err := layer1() + if err != nil { + fmt.Println("Full error:", err) + + // 多次解包 + err = errors.Unwrap(err) // "layer2: base error" + err = errors.Unwrap(err) // "base error" + + // 或檢查特定錯誤是否在鏈中的任何位置 + err = layer1() + if errors.Is(err, errors.New("base error")) { + fmt.Println("Base error found in chain") + } + } +} +``` + + +### 錯誤格式化動詞 + + +```go !! go +// Go - 格式化動詞 +package main + +import ( + "errors" + "fmt" +) + +func main() { + baseErr := errors.New("base error") + + // %v - 只是錯誤文本 + fmt1 := fmt.Errorf("context: %v", baseErr) + fmt.Println(fmt1) // "context: base error" + + // %w - 包裝錯誤(支援 errors.Is/As) + fmt2 := fmt.Errorf("context: %w", baseErr) + fmt.Println(errors.Is(fmt2, baseErr)) // true + + // %+v - (如果錯誤支援)詳細輸出 + // 某些錯誤型別實作 Formatter 介面 + // 用於堆疊追蹤和額外細節 +} +``` + + +## 錯誤比較和型別斷言 + +### errors.Is() + +檢查錯誤是否與鏈中的特定值匹配: + + +```python !! py +# Python - 異常型別檢查 +try: + process() +except ValueError: + print("Value error") +except RuntimeError as e: + if "timeout" in str(e): + print("Timeout error") +``` + +```go !! go +// Go - errors.Is 用於比較 +package main + +import ( + "errors" + "fmt" +) + +var ( + ErrNotFound = errors.New("not found") + ErrTimeout = errors.New("timeout") +) + +func process() error { + return fmt.Errorf("process failed: %w", ErrNotFound) +} + +func main() { + err := process() + + // 檢查特定錯誤是否在鏈中 + if errors.Is(err, ErrNotFound) { + fmt.Println("Not found error occurred") + } + + if errors.Is(err, ErrTimeout) { + fmt.Println("Timeout error occurred") + } + + // 也可以檢查新錯誤 + if errors.Is(err, errors.New("not found")) { + fmt.Println("Not found (new instance)") + } +} +``` + + +### errors.As() + +錯誤鏈的型別斷言: + + +```python !! py +# Python - 異常實例檢查 +try: + process() +except ValidationError as e: + print(f"Validation error: {e.field}") +except PaymentError as e: + print(f"Payment error: {e.code}") +``` + +```go !! go +// Go - errors.As 用於型別斷言 +package main + +import ( + "errors" + "fmt" +) + +type ValidationError struct { + Field string +} + +func (e *ValidationError) Error() string { + return fmt.Sprintf("validation error: %s", e.Field) +} + +type PaymentError struct { + Code int +} + +func (e *PaymentError) Error() string { + return fmt.Sprintf("payment error: %d", e.Code) +} + +func process() error { + return &ValidationError{Field: "email"} +} + +func main() { + err := process() + + // 檢查錯誤是否為特定型別 + var validationErr *ValidationError + if errors.As(err, &validationErr) { + fmt.Printf("Validation error on field: %s\n", validationErr.Field) + } + + var paymentErr *PaymentError + if errors.As(err, &paymentErr) { + fmt.Printf("Payment error code: %d\n", paymentErr.Code) + } + + // 也可以直接檢查 + if _, ok := err.(*ValidationError); ok { + fmt.Println("This is a ValidationError") + } +} +``` + + +## 錯誤處理模式 + +### 重試邏輯 + + +```python !! py +# Python - 重試裝飾器 +import time + +def retry(max_attempts=3, delay=1): + def decorator(func): + def wrapper(*args, **kwargs): + for attempt in range(max_attempts): + try: + return func(*args, **kwargs) + except IOError as e: + if attempt == max_attempts - 1: + raise + time.sleep(delay) + return None + return wrapper + return decorator + +@retry(max_attempts=3) +def fetch_data(): + return api_call() +``` + +```go !! go +// Go - 重試函數 +package main + +import ( + "errors" + "fmt" + "time" +) + +var ErrTemporary = errors.New("temporary error") + +func fetch() error { + // 模擬失敗 + return ErrTemporary +} + +func retry(attempts int, delay time.Duration, fn func() error) error { + var err error + for i := 0; i < attempts; i++ { + err = fn() + if err == nil { + return nil + } + + // 檢查錯誤是否可重試 + if !errors.Is(err, ErrTemporary) { + return err + } + + if i < attempts-1 { + fmt.Printf("Attempt %d failed, retrying...\n", i+1) + time.Sleep(delay) + } + } + return fmt.Errorf("after %d attempts: %w", attempts, err) +} + +func main() { + err := retry(3, time.Second, fetch) + if err != nil { + fmt.Println("Final error:", err) + } +} +``` + + +### 錯誤聚合 + + +```python !! py +# Python - 收集多個錯誤 +class MultiError(Exception): + def __init__(self, errors): + self.errors = errors + super().__init__(f"{len(errors)} errors occurred") + +def process_all(items): + errors = [] + for item in items: + try: + process(item) + except Exception as e: + errors.append(e) + + if errors: + raise MultiError(errors) +``` + +```go !! go +// Go - 收集多個錯誤 +package main + +import ( + "fmt" + "strings" +) + +// MultiError 收集多個錯誤 +type MultiError struct { + Errors []error +} + +func (e *MultiError) Error() string { + var sb strings.Builder + sb.WriteString(fmt.Sprintf("%d errors occurred:", len(e.Errors))) + for _, err := range e.Errors { + sb.WriteString("\n - ") + sb.WriteString(err.Error()) + } + return sb.String() +} + +func processAll(items []int) error { + var errs []error + for _, item := range items { + if err := process(item); err != nil { + errs = append(errs, err) + } + } + + if len(errs) > 0 { + return &MultiError{Errors: errs} + } + return nil +} + +func process(item int) error { + if item < 0 { + return fmt.Errorf("invalid item: %d", item) + } + return nil +} + +func main() { + items := []int{1, 2, -3, 4, -5} + err := processAll(items) + if err != nil { + fmt.Println(err) + } +} +``` + + +### 臨時 vs 永久錯誤 + + +```python !! py +# Python - 自訂異常 +class TemporaryError(Exception): + """重試此操作""" + pass + +class PermanentError(Exception): + """不要重試,放棄""" + pass + +def handle_error(error): + if isinstance(error, TemporaryError): + return "retry" + elif isinstance(error, PermanentError): + return "give up" +``` + +```go !! go +// Go - 行為的錯誤介面 +package main + +import ( + "errors" + "time" +) + +// Temporary 指示錯誤是臨時的 +type Temporary interface { + Temporary() bool +} + +// TemporaryError 可重試 +type TemporaryError struct { + Msg string +} + +func (e *TemporaryError) Error() string { + return e.Msg +} + +func (e *TemporaryError) Temporary() bool { + return true +} + +// PermanentError 不可重試 +type PermanentError struct { + Msg string +} + +func (e *PermanentError) Error() string { + return e.Msg +} + +func shouldRetry(err error) bool { + // 檢查錯誤是否實作 Temporary 介面 + if te, ok := err.(Temporary); ok { + return te.Temporary() + } + return false +} + +func retryOperation(fn func() error) error { + for i := 0; i < 3; i++ { + err := fn() + if err == nil { + return nil + } + + if !shouldRetry(err) { + return err // 永久錯誤,不要重試 + } + + // 臨時錯誤,重試 + time.Sleep(time.Second) + } + return errors.New("max retries exceeded") +} + +func main() { + tempErr := &TemporaryError{Msg: "connection timeout"} + permErr := &PermanentError{Msg: "authentication failed"} + + fmt.Println("Temporary retryable?", shouldRetry(tempErr)) // true + fmt.Println("Permanent retryable?", shouldRetry(permErr)) // false +} +``` + + +## Context 和錯誤 + +### Context 取消 + + +```python !! py +# Python - 使用執行緒取消 +import threading + +def worker(stop_event): + while not stop_event.is_set(): + # 做工作 + if stop_event.is_set(): + break + process() + +stop_event = threading.Event() +thread = threading.Thread(target=worker, args=(stop_event,)) +thread.start() + +# 取消 +stop_event.set() +``` + +```go !! go +// Go - Context 用於取消 +package main + +import ( + "context" + "fmt" + "time" +) + +func worker(ctx context.Context) error { + ticker := time.NewTicker(100 * time.Millisecond) + defer ticker.Stop() + + for { + select { + case <-ctx.Done(): + // Context 被取消 + return ctx.Err() + case <-ticker.C: + // 做工作 + fmt.Println("Working...") + } + } +} + +func main() { + // 建立可取消的 context + ctx, cancel := context.WithCancel(context.Background()) + + // 啟動 worker + go func() { + if err := worker(ctx); err != nil { + fmt.Println("Worker error:", err) + } + }() + + // 500ms 後取消 + time.Sleep(500 * time.Millisecond) + cancel() + + // 等待一點時間以清理 + time.Sleep(100 * time.Millisecond) +} +``` + + +### Context 超時 + + +```python !! py +# Python - 使用信號超時 +import signal + +class TimeoutError(Exception): + pass + +def handler(signum, frame): + raise TimeoutError("Operation timed out") + +def fetch_with_timeout(url, timeout=5): + # 設置鬧鐘 + signal.signal(signal.SIGALRM, handler) + signal.alarm(timeout) + + try: + result = fetch(url) + signal.alarm(0) # 取消鬧鐘 + return result + except TimeoutError: + return None +``` + +```go !! go +// Go - 帶超時的 Context +package main + +import ( + "context" + "fmt" + "time" +) + +func fetchData(ctx context.Context, url string) (string, error) { + // 模擬慢操作 + select { + case <-time.After(3 * time.Second): + return "data", nil + case <-ctx.Done(): + return "", ctx.Err() // context.DeadlineExceeded + } +} + +func main() { + // 建立帶超時的 context + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() // 始終取消以釋放資源 + + data, err := fetchData(ctx, "http://example.com") + if err != nil { + fmt.Println("Error:", err) // context.DeadlineExceeded + return + } + + fmt.Println("Data:", data) +} +``` + + +## Defer 用於清理 + +### 資源清理 + + +```python !! py +# Python - try-finally +file = None +try: + file = open("data.txt") + process(file) +except IOError as e: + print(f"Error: {e}") +finally: + if file: + file.close() +``` + +```go !! go +// Go - defer 始終運行 +package main + +import ( + "fmt" + "os" +) + +func processData() error { + file, err := os.Open("data.txt") + if err != nil { + return err + } + defer file.Close() // 始終運行,即使出錯 + + // 處理檔案... + return nil +} + +func main() { + if err := processData(); err != nil { + fmt.Println("Error:", err) + } +} +``` + + +### Defer 和錯誤處理 + + +```go !! go +// Go - 帶命名返回值的 defer +package main + +import ( + "fmt" + "os" +) + +func processData() (err error) { + file, err := os.Open("data.txt") + if err != nil { + return err + } + + // Defer 可以修改命名返回值 + defer func() { + closeErr := file.Close() + if closeErr != nil { + // 如果處理已經失敗,保留該錯誤 + if err == nil { + err = closeErr + } + } + }() + + // 處理檔案... + return nil +} + +func main() { + if err := processData(); err != nil { + fmt.Println("Error:", err) + } +} +``` + + +## Panic 和 Recover + +### 何時使用 Panic + +Panic 不像 Python 異常。僅用於: +- 真正無法恢復的錯誤 +- 程式設計師錯誤(不應該在生產環境中發生) +- 初始化失敗 + + +```python !! py +# Python - 異常用於控制流 +def divide(a, b): + if b == 0: + raise ValueError("Cannot divide by zero") + return a / b + +# 這在 Python 中是正常的 +``` + +```go !! go +// Go - Panic 僅用於異常情況 +package main + +import "errors" + +// 錯誤 - 不要對預期錯誤 panic +func divide(a, b int) int { + if b == 0 { + panic("division by zero") // 錯誤! + } + return a / b +} + +// 正確 - 返回錯誤 +func divideCorrect(a, b int) (int, error) { + if b == 0 { + return 0, errors.New("division by zero") + } + return a / b, nil +} + +// 可以 - Panic 用於程式設計師錯誤 +func MustGetUser(id int) *User { + user, err := db.GetUser(id) + if err != nil { + panic("database not initialized") // 程式設計師錯誤 + } + if user == nil { + panic("user must exist") // 程式設計師錯誤 + } + return user +} +``` + + +### 從 Panic 恢復 + + +```python !! py +# Python - 捕獲所有 +try: + risky_operation() +except Exception as e: + print(f"Caught: {e}") +``` + +```go !! go +// Go - 從 panic 恢復(很少需要) +package main + +import ( + "fmt" + "log" +) + +func risky() (result string) { + // Defer + recover 捕獲 panic + defer func() { + if r := recover(); r != nil { + // 將 panic 轉換為錯誤 + result = fmt.Sprintf("Recovered from: %v", r) + log.Printf("Panic recovered: %v", r) + } + }() + + panic("something went wrong") +} + +func main() { + fmt.Println(risky()) // "Recovered from: something went wrong" +} +``` + + +### 伺服器恢復中介軟體 + + +```go !! go +// Go - HTTP 伺服器的恢復中介軟體 +package main + +import ( + "fmt" + "log" + "net/http" +) + +func recoveryMiddleware(next http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + defer func() { + if err := recover(); err != nil { + log.Printf("Panic recovered: %v", err) + http.Error(w, "Internal Server Error", 500) + } + }() + next(w, r) + } +} + +func handler(w http.ResponseWriter, r *http.Request) { + panic("oops!") +} + +func main() { + http.HandleFunc("/", recoveryMiddleware(handler)) + fmt.Println("Server starting on :8080") + log.Fatal(http.ListenAndServe(":8080", nil)) +} +``` + + +## 最佳實踐 + +### 1. 始終檢查錯誤 + + +```python !! py +# Python - 異常強制處理 +try: + result = dangerous_operation() +except ValueError: + handle_error() +``` + +```go !! go +// Go - 顯式錯誤檢查 +// 錯誤 - 忽略錯誤 +file, _ := os.Open("data.txt") // 不要這樣做! + +// 正確 - 始終檢查錯誤 +file, err := os.Open("data.txt") +if err != nil { + return err +} +``` + + +### 2. 為錯誤新增上下文 + + +```python !! py +# Python - 帶上下文的異常 +try: + user = get_user(id) +except ValueError as e: + raise ValueError(f"Failed to get user {id}: {e}") +``` + +```go !! go +// Go - 包裝上下文 +// 錯誤 - 丟失原始錯誤 +func processUser(id int) error { + user, err := getUser(id) + if err != nil { + return errors.New("user not found") // 丟失上下文! + } + return nil +} + +// 正確 - 包裝上下文 +func processUser(id int) error { + user, err := getUser(id) + if err != nil { + return fmt.Errorf("processUser(%d): %w", id, err) + } + return nil +} +``` + + +### 3. 立即處理錯誤 + + +```python !! py +# Python - 早期返回 +def process(data): + if not data: + return None + + if len(data) < 10: + return None + + return transform(data) +``` + +```go !! go +// Go - 立即處理錯誤 +func process(data string) (string, error) { + if data == "" { + return "", fmt.Errorf("empty data") + } + + if len(data) < 10 { + return "", fmt.Errorf("data too short: got %d, want >= 10", len(data)) + } + + return transform(data), nil +} +``` + + +### 4. 錯誤變數 + + +```go !! go +// Go - 定義錯誤變數 +package mypackage + +import "errors" + +var ( + ErrNotFound = errors.New("not found") + ErrInvalid = errors.New("invalid input") + ErrPermission = errors.New("permission denied") +) + +// 匯出的錯誤可以被呼叫者檢查 +func FindItem(id int) (*Item, error) { + // ... + return nil, ErrNotFound +} + +// 呼叫者可以檢查 +item, err := mypackage.FindItem(123) +if errors.Is(err, mypackage.ErrNotFound) { + // 處理未找到 +} +``` + + +### 5. 不要在程式庫中 Panic + + +```python !! py +# Python - 程式庫拋出異常 +def process(data): + if not data: + raise ValueError("Invalid data") + return result +``` + +```go !! go +// Go - 程式庫返回錯誤 +// 錯誤 - 程式庫函數 panic +func Process(data string) string { + if data == "" { + panic("data is empty") // 不要在程式庫中 panic! + } + return data +} + +// 正確 - 程式庫函數返回錯誤 +func Process(data string) (string, error) { + if data == "" { + return "", errors.New("data is empty") + } + return data, nil +} +``` + + +## 實際範例 + +### 檔案操作 + + +```python !! py +# Python - 檔案操作 +def process_file(path): + try: + with open(path) as f: + data = f.read() + return parse(data) + except IOError as e: + print(f"Failed to read {path}: {e}") + return None +``` + +```go !! go +// Go - 檔案操作 +package main + +import ( + "fmt" + "os" +) + +func processFile(path string) error { + // 開啟檔案 + file, err := os.Open(path) + if err != nil { + return fmt.Errorf("failed to open %s: %w", path, err) + } + defer file.Close() + + // 讀取檔案 + data := make([]byte, 1024) + n, err := file.Read(data) + if err != nil { + return fmt.Errorf("failed to read %s: %w", path, err) + } + + // 處理資料 + if err := parse(data[:n]); err != nil { + return fmt.Errorf("failed to parse %s: %w", path, err) + } + + return nil +} + +func main() { + if err := processFile("data.txt"); err != nil { + fmt.Println("Error:", err) + } +} +``` + + +### 資料庫操作 + + +```python !! py +# Python - 帶異常的資料庫 +class UserNotFound(Exception): + pass + +class DatabaseError(Exception): + pass + +def get_user(db, id): + try: + cursor = db.cursor() + cursor.execute("SELECT * FROM users WHERE id = ?", (id,)) + result = cursor.fetchone() + if not result: + raise UserNotFound(f"User {id} not found") + return User(result) + except sqlite3.Error as e: + raise DatabaseError(f"Database error: {e}") +``` + +```go !! go +// Go - 帶錯誤的資料庫 +package main + +import ( + "database/sql" + "errors" + "fmt" +) + +var ( + ErrUserNotFound = errors.New("user not found") + ErrDatabase = errors.New("database error") +) + +func getUser(db *sql.DB, id int) (*User, error) { + var user User + + err := db.QueryRow( + "SELECT id, name, email FROM users WHERE id = $1", + id, + ).Scan(&user.ID, &user.Name, &user.Email) + + if err == sql.ErrNoRows { + return nil, fmt.Errorf("user %d: %w", id, ErrUserNotFound) + } + if err != nil { + return nil, fmt.Errorf("query user %d: %w", id, err) + } + + return &user, nil +} + +func main() { + db, err := sql.Open("postgres", connString) + if err != nil { + panic(err) + } + defer db.Close() + + user, err := getUser(db, 123) + if err != nil { + if errors.Is(err, ErrUserNotFound) { + fmt.Println("User not found") + } else { + fmt.Println("Database error:", err) + } + return + } + + fmt.Printf("User: %+v\n", user) +} +``` + + +### HTTP 客戶端 + + +```python !! py +# Python - 帶重試的 HTTP +import requests +from requests.adapters import HTTPAdapter +from urllib3.util.retry import Retry + +def fetch_with_retry(url, max_retries=3): + session = requests.Session() + retry = Retry(total=max_retries, backoff_factor=1) + adapter = HTTPAdapter(max_retries=retry) + session.mount('http://', adapter) + + try: + response = session.get(url, timeout=5) + response.raise_for_status() + return response.json() + except requests.RequestException as e: + print(f"Request failed: {e}") + return None +``` + +```go !! go +// Go - 帶重試的 HTTP 客戶端 +package main + +import ( + "context" + "fmt" + "io" + "net/http" + "time" +) + +func fetchWithRetry(ctx context.Context, url string, maxRetries int) ([]byte, error) { + var lastErr error + + for attempt := 0; attempt < maxRetries; attempt++ { + req, err := http.NewRequestWithContext(ctx, "GET", url, nil) + if err != nil { + return nil, err + } + + resp, err := http.DefaultClient.Do(req) + if err != nil { + lastErr = err + time.Sleep(time.Duration(attempt+1) * time.Second) + continue + } + + defer resp.Body.Close() + + if resp.StatusCode == http.StatusOK { + return io.ReadAll(resp.Body) + } + + lastErr = fmt.Errorf("unexpected status: %d", resp.StatusCode) + + if resp.StatusCode >= 400 && resp.StatusCode < 500 { + // 客戶端錯誤,不要重試 + return nil, lastErr + } + + // 伺服器錯誤,重試 + time.Sleep(time.Duration(attempt+1) * time.Second) + } + + return nil, fmt.Errorf("after %d attempts: %w", maxRetries, lastErr) +} + +func main() { + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + data, err := fetchWithRetry(ctx, "http://example.com", 3) + if err != nil { + fmt.Println("Error:", err) + return + } + + fmt.Println("Data:", string(data)) +} +``` + + +## 總結 + +### 關鍵概念 + +1. **錯誤是值**:顯式返回,不是拋出 +2. **nil 意味著成功**:非 nil 錯誤表示失敗 +3. **始終檢查錯誤**:立即顯式處理 +4. **新增上下文**:使用 fmt.Errorf 和 %w 包裝錯誤 +5. **哨兵錯誤**:定義錯誤變數用於比較 +6. **自訂型別**:建立錯誤結構體以獲得豐富錯誤資料 +7. **errors.Is/As**:從鏈中檢查和提取錯誤 +8. **Panic 很罕見**:僅用於無法恢復的情況 + +### 常見模式 + +- **返回模式**:`(result, error)` - 錯誤是最後一個返回值 +- **早期返回**:先檢查錯誤,早期返回 +- **錯誤包裝**:`fmt.Errorf("context: %w", err)` +- **錯誤檢查**:`errors.Is(err, ErrNotFound)` +- **型別斷言**:`errors.As(err, &customErr)` +- **Defer 清理**:始終運行,即使 panic + +### 最佳實踐 + +1. 立即處理錯誤 +2. 包裝時新增上下文 +3. 對已知情況使用哨兵錯誤 +4. 不要忽略錯誤(少用 `_`) +5. 不要在程式庫中 panic +6. Defer 清理操作 +7. 檢查 defer 函數中的錯誤 +8. 使用 context 進行取消/超時 + +### 與 Python 的對比 + +| Python | Go | +|--------|-----| +| 異常向上冒泡 | 錯誤顯式返回 | +| try/except 區塊 | if err != nil 檢查 | +| 異常型別 | 錯誤值/型別 | +| raise Exception | return error | +| 簽章中不可見 | 簽章中可見 | +| 通過異常進行控制流 | 線性控制流 | + +## 練習 + +1. 建立一個包含以下內容的自訂錯誤型別: + - 錯誤代碼 + - 錯誤訊息 + - HTTP 狀態碼 + - IsRetryable() 方法 + +2. 實作重試邏輯: + - 最大重試次數 + - 指數退避 + - 僅在臨時錯誤時重試 + +3. 撰寫一個函數: + - 開啟檔案 + - 讀取內容 + - 解析資料 + - 在每一步正確包裝錯誤 + +4. 建立錯誤聚合系統: + - 收集多個錯誤 + - 很好地格式化它們 + - 檢查特定錯誤是否在集合中 + +5. 實作基於 context 的取消: + - 建立長時間運行的操作 + - 支援通過 context 取消 + - 取消時清理資源 + +## 下一步 + +下一模組:**Goroutines 和並發** - 擺脫 GIL 並學習強大的 Go 並發原語。 diff --git a/content/docs/py2go/module-07-goroutines.mdx b/content/docs/py2go/module-07-goroutines.mdx new file mode 100644 index 0000000..632cf11 --- /dev/null +++ b/content/docs/py2go/module-07-goroutines.mdx @@ -0,0 +1,1619 @@ +--- +title: "Module 7: Goroutines and Concurrency" +description: "Lightweight concurrency with goroutines" +--- + +## Introduction + +Python's Global Interpreter Lock (GIL) is one of the biggest limitations for CPU-bound concurrent tasks. Go was designed from the ground up with concurrency in mind, using goroutines - lightweight threads managed by the Go runtime. + +### What is the GIL? + +The **Global Interpreter Lock (GIL)** is a mutex that protects access to Python objects, preventing multiple native threads from executing Python bytecodes at once. This means: + +**In Python:** +- Only one thread executes Python bytecode at a time +- CPU-bound tasks see no performance benefit from threading +- I/O-bound tasks can benefit from threading (due to I/O waiting) +- Multiprocessing is needed for true parallelism + +**In Go:** +- No GIL limitation +- Multiple goroutines can execute truly in parallel +- Goroutines are multiplexed onto OS threads (M:N scheduling) +- Perfect for both CPU-bound and I/O-bound tasks + +## GIL vs Goroutines + + +```python !! py +# Python - Limited by GIL for CPU-bound tasks +import threading +import time + +def cpu_bound_task(n): + """CPU-intensive computation""" + total = 0 + for i in range(n): + total += i * i + return total + +def run_parallel(): + threads = [] + start = time.time() + + for _ in range(4): + t = threading.Thread(target=cpu_bound_task, args=(10_000_000,)) + threads.append(t) + t.start() + + for t in threads: + t.join() + + elapsed = time.time() - start + print(f"Threading time: {elapsed:.2f}s") + +# Result: ~4 seconds (threads run sequentially due to GIL) +# Same code with multiprocessing: ~1 second (true parallelism) +``` + +```go !! go +// Go - True parallelism with goroutines +package main + +import ( + "fmt" + "sync" + "time" +) + +func cpuBoundTask(n int) int { + total := 0 + for i := 0; i < n; i++ { + total += i * i + } + return total +} + +func runParallel() { + var wg sync.WaitGroup + start := time.Now() + + for i := 0; i < 4; i++ { + wg.Add(1) + go func() { + defer wg.Done() + cpuBoundTask(10_000_000) + }() + } + + wg.Wait() + elapsed := time.Since(start) + fmt.Printf("Goroutines time: %v\n", elapsed) +} + +// Result: ~1 second (goroutines run in parallel on all CPUs) +func main() { + runParallel() +} +``` + + +## Goroutine Internals + +### What are Goroutines? + +Goroutines are lightweight threads managed by the Go runtime: + + +```python !! py +# Python - OS Threads +import threading +import sys + +# Each Python thread uses an OS thread +# Typical memory: ~8 MB per thread +# Creation cost: High (OS system call) + +def thread_info(): + thread = threading.current_thread() + print(f"Thread: {thread.name}") + +# Creating 10,000 threads would likely crash your system +threads = [] +for i in range(100): # Even 100 is heavy + t = threading.Thread(target=thread_info) + threads.append(t) + t.start() + +for t in threads: + t.join() +``` + +```go !! go +// Go - Goroutines +package main + +import ( + "fmt" + "runtime" + "sync" + "time" +) + +// Goroutines are extremely lightweight: +// - Stack size: starts at 2KB, grows as needed +// - Creation cost: Very low +// - Scheduling: Go runtime (M:N model) + +func goroutineInfo(id int) { + fmt.Printf("Goroutine %d\n", id) +} + +func main() { + // Can easily create 10,000+ goroutines + var wg sync.WaitGroup + + for i := 0; i < 10000; i++ { + wg.Add(1) + go func(id int) { + defer wg.Done() + goroutineInfo(id) + }(i) + } + + wg.Wait() + fmt.Printf("Goroutines: %d\n", runtime.NumGoroutine()) + fmt.Printf("CPUs: %d\n", runtime.NumCPU()) +} + +// This works perfectly! Memory usage stays reasonable. +``` + + +### Goroutine Scheduling + +Go uses an **M:N scheduler**: +- **M** goroutines are multiplexed onto **N** OS threads +- The Go runtime handles scheduling, not the OS +- Goroutines are cooperatively scheduled (preemptive since Go 1.14) + + +```python !! py +# Python - OS Scheduling (1:1) +# +# Thread 1 -> OS Thread 1 -> CPU Core +# Thread 2 -> OS Thread 2 -> CPU Core +# Thread 3 -> OS Thread 3 -> CPU Core +# +# Issues: +# - OS manages scheduling (expensive context switches) +# - Limited by number of OS threads +# - High memory per thread (~8MB) + +import threading +import time + +def worker(): + time.sleep(0.001) # Context switch here + +# OS decides when to switch threads +for _ in range(100): + t = threading.Thread(target=worker) + t.start() + t.join() +``` + +```go !! go +// Go - Go Runtime Scheduling (M:N) +package main + +import ( + "fmt" + "runtime" + "time" +) + +// Goroutine 1 --\ /--> CPU Core 1 +// Goroutine 2 ---> [Go Scheduler] --[N OS Threads]---> CPU Core 2 +// Goroutine 3 --/ \--> CPU Core 3 +// +// Benefits: +// - Go runtime manages scheduling (cheap context switches) +// - Many goroutines per OS thread +// - Automatic load balancing +// - Grows and shrinks OS threads as needed + +func worker(id int) { + time.Sleep(time.Millisecond) + fmt.Printf("Worker %d\n", id) +} + +func main() { + // Set max number of OS threads (optional) + runtime.GOMAXPROCS(runtime.NumCPU()) + + for i := 0; i < 100; i++ { + go worker(i) + } + + time.Sleep(100 * time.Millisecond) +} +``` + + +## Starting Goroutines + +### Basic Goroutine Launch + + +```python !! py +# Python - Threading +import threading +import time + +def task(name, duration): + """Run a task""" + time.sleep(duration) + print(f"Task {name} completed") + +# Create and start thread +thread = threading.Thread(target=task, args=("A", 1)) +thread.start() # Start execution + +# Wait for completion +thread.join() +print("Main continues") +``` + +```go !! go +// Go - Goroutines +package main + +import ( + "fmt" + "time" +) + +func task(name string, duration time.Duration) { + time.Sleep(duration) + fmt.Printf("Task %s completed\n", name) +} + +func main() { + // Start goroutine + go task("A", time.Second) + + // Wait for goroutine to finish + // (In real code, use WaitGroup or channels) + time.Sleep(2 * time.Second) + fmt.Println("Main continues") +} +``` + + +### Multiple Goroutines + + +```python !! py +# Python - Multiple threads +import threading +import time + +def download(url, delay): + """Simulate download""" + time.sleep(delay) + print(f"Downloaded {url}") + +urls = [ + ("http://example.com/file1", 1), + ("http://example.com/file2", 2), + ("http://example.com/file3", 1), +] + +threads = [] +for url, delay in urls: + t = threading.Thread(target=download, args=(url, delay)) + threads.append(t) + t.start() + +for t in threads: + t.join() + +print("All downloads complete") +``` + +```go !! go +// Go - Multiple goroutines +package main + +import ( + "fmt" + "sync" + "time" +) + +type Download struct { + URL string + Delay time.Duration +} + +func download(d Download, wg *sync.WaitGroup) { + defer wg.Done() + time.Sleep(d.Delay) + fmt.Printf("Downloaded %s\n", d.URL) +} + +func main() { + downloads := []Download{ + {"http://example.com/file1", time.Second}, + {"http://example.com/file2", 2 * time.Second}, + {"http://example.com/file3", time.Second}, + } + + var wg sync.WaitGroup + + for _, d := range downloads { + wg.Add(1) + go download(d, &wg) + } + + wg.Wait() + fmt.Println("All downloads complete") +} +``` + + +## Anonymous Goroutines + +### Quick Inline Tasks + + +```python !! py +# Python - Lambda/thread +import threading +import time + +# Start anonymous task +thread = threading.Thread( + target=lambda: print("Anonymous task") +) +thread.start() +thread.join() + +# With parameters +thread = threading.Thread( + target=lambda x, y: print(f"Sum: {x + y}"), + args=(5, 3) +) +thread.start() +thread.join() +``` + +```go !! go +// Go - Anonymous goroutine +package main + +import ( + "fmt" + "time" +) + +func main() { + // Simple anonymous goroutine + go func() { + fmt.Println("Anonymous task") + }() + + time.Sleep(time.Millisecond) + + // With parameters + go func(x int, y int) { + fmt.Printf("Sum: %d\n", x+y) + }(5, 3) + + time.Sleep(time.Millisecond) +} +``` + + +## Goroutines with Closures + +### The Loop Variable Capture Gotcha + +This is one of the most common mistakes in Go! + + +```python !! py +# Python - Late binding closure issue +import threading + +funcs = [] +for i in range(3): + funcs.append(lambda: print(i)) + +for f in funcs: + f() # Prints 2, 2, 2 (all reference same i) + +# Fix - capture value with default argument +funcs = [] +for i in range(3): + funcs.append(lambda i=i: print(i)) + +for f in funcs: + f() # Prints 0, 1, 2 +``` + +```go !! go +// Go - Loop variable capture +package main + +import ( + "fmt" + "sync" + "time" +) + +func main() { + // WRONG - All goroutines capture same i variable + var wg sync.WaitGroup + for i := 0; i < 3; i++ { + wg.Add(1) + go func() { + defer wg.Done() + fmt.Println(i) // May print 3, 3, 3 or unpredictable + }() + } + wg.Wait() + time.Sleep(time.Millisecond) + + fmt.Println("---") + + // RIGHT - Pass i as parameter + var wg2 sync.WaitGroup + for i := 0; i < 3; i++ { + wg2.Add(1) + go func(n int) { + defer wg2.Done() + fmt.Println(n) // Prints 0, 1, 2 + }(i) // Pass current value of i + } + wg2.Wait() +} +``` + + +### Why Does This Happen? + +The closure captures the **variable**, not the **value**. All goroutines share the same loop variable, which may be modified before the goroutine runs. + +## WaitGroup: Proper Synchronization + +Using `time.Sleep()` to wait for goroutines is bad practice. Use `sync.WaitGroup` instead. + + +```python !! py +# Python - Join threads +import threading +import time + +def worker(id): + time.sleep(0.1) + print(f"Worker {id} done") + +threads = [] +for i in range(5): + t = threading.Thread(target=worker, args=(i,)) + threads.append(t) + t.start() + +# Wait for all threads +for t in threads: + t.join() + +print("All workers done") +``` + +```go !! go +// Go - WaitGroup +package main + +import ( + "fmt" + "sync" + "time" +) + +func worker(id int, wg *sync.WaitGroup) { + defer wg.Done() // Decrement counter when done + time.Sleep(100 * time.Millisecond) + fmt.Printf("Worker %d done\n", id) +} + +func main() { + var wg sync.WaitGroup + + for i := 0; i < 5; i++ { + wg.Add(1) // Increment counter + go worker(i, &wg) + } + + wg.Wait() // Wait until counter is 0 + fmt.Println("All workers done") +} +``` + + +### WaitGroup Best Practices + + +```go !! go +// Go - WaitGroup patterns +package main + +import ( + "fmt" + "sync" +) + +// Pattern 1: Add in caller, Done in goroutine +func worker1(id int, wg *sync.WaitGroup) { + defer wg.Done() + fmt.Printf("Worker %d\n", id) +} + +func pattern1() { + var wg sync.WaitGroup + + for i := 0; i < 3; i++ { + wg.Add(1) // Add before starting goroutine + go worker1(i, &wg) + } + + wg.Wait() +} + +// Pattern 2: Add inside goroutine +func worker2(id int) { + fmt.Printf("Worker %d\n", id) +} + +func pattern2() { + var wg sync.WaitGroup + + for i := 0; i < 3; i++ { + go func(id int) { + wg.Add(1) // Add at start of goroutine + defer wg.Done() + worker2(id) + }(i) + } + + wg.Wait() +} + +// Pattern 3: WaitGroup with return values +type Result struct { + ID int + Value int +} + +func workerWithResult(id int, results chan<- Result, wg *sync.WaitGroup) { + defer wg.Done() + results <- Result{ID: id, Value: id * 2} +} + +func pattern3() { + var wg sync.WaitGroup + results := make(chan Result, 3) + + for i := 0; i < 3; i++ { + wg.Add(1) + go workerWithResult(i, results, &wg) + } + + // Close channel when all workers done + go func() { + wg.Wait() + close(results) + }() + + // Collect results + for r := range results { + fmt.Printf("Worker %d: %d\n", r.ID, r.Value) + } +} + +func main() { + fmt.Println("Pattern 1:") + pattern1() + + fmt.Println("\nPattern 2:") + pattern2() + + fmt.Println("\nPattern 3:") + pattern3() +} +``` + + +## Mutual Exclusion (Mutex) + +When multiple goroutines access shared data, you need synchronization. + + +```python !! py +# Python - Lock +import threading + +counter = 0 +lock = threading.Lock() + +def increment(): + global counter + with lock: # Acquire lock + counter += 1 # Critical section + # Lock released automatically + +# Start 100 threads +threads = [] +for _ in range(100): + t = threading.Thread(target=increment) + threads.append(t) + t.start() + +for t in threads: + t.join() + +print(f"Counter: {counter}") # 100 +``` + +```go !! go +// Go - Mutex +package main + +import ( + "fmt" + "sync" +) + +var ( + counter int + mutex sync.Mutex +) + +func increment() { + mutex.Lock() // Acquire lock + counter++ // Critical section + mutex.Unlock() // Release lock +} + +func main() { + var wg sync.WaitGroup + + for i := 0; i < 100; i++ { + wg.Add(1) + go func() { + defer wg.Done() + increment() + }() + } + + wg.Wait() + fmt.Printf("Counter: %d\n", counter) // 100 +} +``` + + +### Using defer with Mutex + + +```go !! go +// Go - Defer for Unlock +package main + +import ( + "fmt" + "sync" +) + +var ( + counter int + mutex sync.Mutex +) + +func incrementGood() { + mutex.Lock() + defer mutex.Unlock() // Guaranteed to run + + counter++ + + // Even if panic occurs, Unlock will run + // if somethingBad() { panic("oops") } +} + +func incrementBad() { + mutex.Lock() + counter++ + mutex.Unlock() + + // If panic occurs here, mutex stays locked! + // if somethingBad() { panic("oops") } +} + +func main() { + var wg sync.WaitGroup + + for i := 0; i < 100; i++ { + wg.Add(1) + go func() { + defer wg.Done() + incrementGood() + }() + } + + wg.Wait() + fmt.Printf("Counter: %d\n", counter) +} +``` + + +## RWMutex: Read-Write Locks + +For read-heavy workloads, `sync.RWMutex` allows multiple readers or one writer. + + +```python !! py +# Python - Read-Write Lock +import threading + +rwlock = threading.RLock() + +def read_data(): + with rwlock: + # Multiple readers can hold this lock + data = get_data() + return data + +def write_data(new_data): + with rwlock: + # Writer gets exclusive access + set_data(new_data) +``` + +```go !! go +// Go - RWMutex +package main + +import ( + "fmt" + "sync" + "time" +) + +type DataStore struct { + mu sync.RWMutex + data map[string]string +} + +func NewDataStore() *DataStore { + return &DataStore{ + data: make(map[string]string), + } +} + +// Read - multiple goroutines can read simultaneously +func (ds *DataStore) Read(key string) (string, bool) { + ds.mu.RLock() // Read lock + defer ds.mu.RUnlock() + time.Sleep(time.Millisecond) // Simulate work + val, ok := ds.data[key] + return val, ok +} + +// Write - exclusive access +func (ds *DataStore) Write(key, value string) { + ds.mu.Lock() // Write lock (exclusive) + defer ds.mu.Unlock() + time.Sleep(10 * time.Millisecond) // Simulate work + ds.data[key] = value +} + +func main() { + ds := NewDataStore() + var wg sync.WaitGroup + + // Start 100 readers + for i := 0; i < 100; i++ { + wg.Add(1) + go func(id int) { + defer wg.Done() + ds.Read(fmt.Sprintf("key-%d", id)) + }(i) + } + + // Start 10 writers + for i := 0; i < 10; i++ { + wg.Add(1) + go func(id int) { + defer wg.Done() + ds.Write(fmt.Sprintf("key-%d", id), fmt.Sprintf("value-%d", id)) + }(i) + } + + wg.Wait() + fmt.Println("All operations complete") +} +``` + + +### When to Use RWMutex vs Mutex + + +```go !! go +// Go - When to use which +package main + +import "sync" + +// Use regular Mutex when: +// - Writes are frequent +// - Critical section is short +// - Don't need read optimization +type Counter struct { + mu sync.Mutex + value int +} + +// Use RWMutex when: +// - Reads greatly outnumber writes +// - Critical section is longer +// - Multiple concurrent readers are beneficial +type Cache struct { + mu sync.RWMutex + data map[string]interface{} +} + +func (c *Cache) Get(key string) (interface{}, bool) { + c.mu.RLock() // Allow concurrent reads + defer c.mu.RUnlock() + val, ok := c.data[key] + return val, ok +} + +func (c *Cache) Set(key string, val interface{}) { + c.mu.Lock() // Exclusive write + defer c.mu.Unlock() + c.data[key] = val +} +``` + + +## sync.Once: Single Initialization + +Ensure a function runs exactly once, even from multiple goroutines. + + +```python !! py +# Python - Singleton with threading +import threading + +class Singleton: + _instance = None + _lock = threading.Lock() + + def __new__(cls): + if cls._instance is None: + with cls._lock: + # Double-check + if cls._instance is None: + cls._instance = super().__new__(cls) + return cls._instance + +# Usage +s1 = Singleton() +s2 = Singleton() +assert s1 is s2 +``` + +```go !! go +// Go - sync.Once +package main + +import ( + "fmt" + "sync" +) + +type Singleton struct { + data string +} + +var ( + instance *Singleton + once sync.Once +) + +func getInstance() *Singleton { + once.Do(func() { + // This runs exactly once, ever + instance = &Singleton{data: "initialized"} + fmt.Println("Singleton initialized") + }) + return instance +} + +func main() { + var wg sync.WaitGroup + + // Multiple goroutines trying to initialize + for i := 0; i < 10; i++ { + wg.Add(1) + go func(id int) { + defer wg.Done() + inst := getInstance() + fmt.Printf("Goroutine %d: %p\n", id, inst) + }(i) + } + + wg.Wait() + // "Singleton initialized" prints only once +} +``` + + +### Once Patterns + + +```go !! go +// Go - Common sync.Once patterns +package main + +import ( + "fmt" + "sync" +) + +// Pattern 1: Lazy initialization +var ( + config map[string]string + configOnce sync.Once +) + +func getConfig() map[string]string { + configOnce.Do(func() { + // Expensive initialization + config = make(map[string]string) + config["host"] = "localhost" + config["port"] = "8080" + fmt.Println("Config initialized") + }) + return config +} + +// Pattern 2: Multiple Once instances +type ConnectionPool struct { + once sync.Once + pool []*Connection +} + +func (cp *ConnectionPool) Init() { + cp.once.Do(func() { + // Initialize pool + cp.pool = make([]*Connection, 10) + fmt.Println("Connection pool initialized") + }) +} + +// Pattern 3: Once with error handling +var ( + cache Cache + cacheOnce sync.Once + cacheErr error +) + +func getCache() (Cache, error) { + cacheOnce.Do(func() { + cache, cacheErr = initCache() + }) + return cache, cacheErr +} + +func initCache() (Cache, error) { + // Initialization that might fail + return Cache{}, nil +} + +type Cache struct{} + +func main() { + getConfig() + getConfig() + // "Config initialized" prints only once + + pool := &ConnectionPool{} + pool.Init() + pool.Init() + // Connection pool initializes only once +} +``` + + +## Atomic Operations + +For simple operations, use atomic package instead of mutex. + + +```python !! py +# Python - Atomic operations (thread-safe) +import threading + +counter = 0 + +# Increment is atomic in CPython due to GIL +# but not guaranteed in all implementations + +# For guaranteed atomicity, use locks +lock = threading.Lock() + +def increment(): + global counter + with lock: + counter += 1 +``` + +```go !! go +// Go - Atomic operations +package main + +import ( + "sync" + "sync/atomic" +) + +// Mutex approach +type MutexCounter struct { + mu sync.Mutex + value int64 +} + +func (c *MutexCounter) Increment() { + c.mu.Lock() + c.value++ + c.mu.Unlock() +} + +// Atomic approach (faster for simple operations) +type AtomicCounter struct { + value int64 +} + +func (c *AtomicCounter) Increment() { + atomic.AddInt64(&c.value, 1) +} + +func (c *AtomicCounter) Get() int64 { + return atomic.LoadInt64(&c.value) +} + +// Common atomic operations +func main() { + var counter int64 = 0 + + // Add + atomic.AddInt64(&counter, 1) + + // Load + val := atomic.LoadInt64(&counter) + + // Store + atomic.StoreInt64(&counter, 100) + + // Compare and Swap + atomic.CompareAndSwapInt64(&counter, 100, 200) + + // Swap + old := atomic.SwapInt64(&counter, 300) + + _, _ = val, old +} +``` + + +## Goroutine Leaks + +Goroutines are cheap, but not free. Leaking goroutines can cause memory issues. + + +```python !! py +# Python - Thread leak +import threading +import time + +def worker(): + while True: + time.sleep(1) + # Never exits! + +# Create thread that never exits +t = threading.Thread(target=worker) +t.start() + +# Thread keeps running, consuming resources +``` + +```go !! go +// Go - Goroutine leak +package main + +import ( + "fmt" + "runtime" + "time" +) + +// WRONG - Goroutine never exits +func leakWorker() { + for { + time.Sleep(time.Second) + // Never returns! + } +} + +// RIGHT - Always have exit condition +func goodWorker(stop <-chan struct{}) { + for { + select { + case <-stop: + return // Exit goroutine + default: + time.Sleep(time.Second) + // Do work... + } + } +} + +func main() { + fmt.Println("Goroutines:", runtime.NumGoroutine()) + + // Leak goroutine + go leakWorker() + time.Sleep(time.Millisecond) + fmt.Println("Goroutines (after leak):", runtime.NumGoroutine()) + + // Proper goroutine with stop channel + stop := make(chan struct{}) + go goodWorker(stop) + time.Sleep(time.Millisecond) + + close(stop) // Signal goroutine to stop + time.Sleep(time.Millisecond) + fmt.Println("Goroutines (after cleanup):", runtime.NumGoroutine()) +} +``` + + +## Worker Pools + +Limit the number of concurrent goroutines to avoid resource exhaustion. + + +```python !! py +# Python - Worker pool with ThreadPoolExecutor +from concurrent.futures import ThreadPoolExecutor + +def process_task(task_id): + print(f"Processing {task_id}") + return task_id * 2 + +# Limit to 10 workers +with ThreadPoolExecutor(max_workers=10) as executor: + futures = [executor.submit(process_task, i) for i in range(100)] + results = [f.result() for f in futures] +``` + +```go !! go +// Go - Worker pool +package main + +import ( + "fmt" + "sync" +) + +func worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) { + defer wg.Done() + for job := range jobs { + fmt.Printf("Worker %d processing job %d\n", id, job) + results <- job * 2 + } +} + +func main() { + numWorkers := 10 + numJobs := 100 + + jobs := make(chan int, numJobs) + results := make(chan int, numJobs) + + var wg sync.WaitGroup + + // Start workers + for i := 0; i < numWorkers; i++ { + wg.Add(1) + go worker(i, jobs, results, &wg) + } + + // Send jobs + for j := 0; j < numJobs; j++ { + jobs <- j + } + close(jobs) + + // Wait for workers to finish + go func() { + wg.Wait() + close(results) + }() + + // Collect results + for result := range results { + _ = result + } + + fmt.Println("All jobs processed") +} +``` + + +## Context with Goroutines + +Use context for cancellation and timeout handling. + + +```python !! py +# Python - Cancellation with Event +import threading +import time + +def worker(stop_event): + while not stop_event.is_set(): + time.sleep(0.1) + print("Working...") + print("Stopped") + +stop_event = threading.Event() +t = threading.Thread(target=worker, args=(stop_event,)) +t.start() + +time.sleep(1) +stop_event.set() # Signal stop +t.join() +``` + +```go !! go +// Go - Context for cancellation +package main + +import ( + "context" + "fmt" + "time" +) + +func worker(ctx context.Context) { + for { + select { + case <-ctx.Done(): + fmt.Println("Stopped") + return + default: + time.Sleep(100 * time.Millisecond) + fmt.Println("Working...") + } + } +} + +func main() { + // Create cancellable context + ctx, cancel := context.WithCancel(context.Background()) + + go worker(ctx) + + // Let it run for a second + time.Sleep(time.Second) + + // Cancel context + cancel() + + // Wait for cleanup + time.Sleep(100 * time.Millisecond) +} +``` + + +## Best Practices + +### 1. Know When to Use Goroutines + + +```go !! go +// GOOD - Use goroutines for: +// 1. I/O-bound operations (network, disk) +// 2. Independent tasks +// 3. Parallel processing + +func fetchUserData(userID int) { + // Network I/O - perfect for goroutines + resp := http.Get(fmt.Sprintf("http://api/user/%d", userID)) + // ... +} + +func processImages(images []Image) { + // CPU-bound parallel work + for _, img := range images { + go processImage(img) + } +} + +// BAD - Don't use goroutines for: +// 1. Simple operations (overhead) +// 2. Tight loops (use channels for coordination) +// 3. When order matters + +func add(a, b int) int { + // Don't do this: + go func() { + result = a + b // Race condition! + }() + + // Just do the work directly + return a + b +} +``` + + +### 2. Always Have Exit Conditions + + +```go !! go +// GOOD - Always exit +func worker(stop <-chan struct{}) { + ticker := time.NewTicker(time.Second) + defer ticker.Stop() + + for { + select { + case <-stop: + return // Clean exit + case <-ticker.C: + // Work... + } + } +} + +// BAD - Never exits +func worker() { + for { + time.Sleep(time.Second) + // No exit condition! + } +} +``` + + +### 3. Be Careful with Shared State + + +```go !! go +// GOOD - Use channels for communication +func producer(out chan<- int) { + for i := 0; i < 10; i++ { + out <- i + } + close(out) +} + +func consumer(in <-chan int) { + for val := range in { + fmt.Println(val) + } +} + +// BAD - Shared mutable state +var data []int + +func producer() { + for i := 0; i < 10; i++ { + data = append(data, i) // Race condition! + } +} + +func consumer() { + for _, val := range data { + fmt.Println(val) // Race condition! + } +} +``` + + +### 4. Use WaitGroup Properly + + +```go !! go +// GOOD - Add before starting goroutine +func processItems(items []Item) { + var wg sync.WaitGroup + + for _, item := range items { + wg.Add(1) // Add before goroutine + go func(i Item) { + defer wg.Done() + process(i) + }(item) + } + + wg.Wait() +} + +// BAD - Add inside goroutine +func processItemsBad(items []Item) { + var wg sync.WaitGroup + + for _, item := range items { + go func(i Item) { + wg.Add(1) // Race condition! + defer wg.Done() + process(i) + }(item) + } + + wg.Wait() // May return too early! +} +``` + + +### 5. Handle Panics in Goroutines + + +```go !! go +// GOOD - Recover from panics +func safeWorker() { + defer func() { + if r := recover(); r != nil { + fmt.Printf("Recovered: %v\n", r) + } + }() + + // Work that might panic + panic("oops") +} + +// BAD - Panic crashes goroutine +func unsafeWorker() { + panic("oops") // Goroutine dies, no recovery +} +``` + + +## Performance Considerations + +### Goroutine vs Thread Overhead + + +```go !! go +// Go - Memory and creation cost +package main + +import ( + "fmt" + "runtime" + "time" +) + +func main() { + // Goroutines are very lightweight + start := time.Now() + + var wg sync.WaitGroup + for i := 0; i < 100000; i++ { + wg.Add(1) + go func() { + defer wg.Done() + time.Sleep(time.Second) + }() + } + + fmt.Printf("Created 100k goroutines in %v\n", time.Since(start)) + fmt.Printf("Goroutines: %d\n", runtime.NumGoroutine()) + + wg.Wait() +} + +// Comparison: +// Python threads: ~8MB per thread, ~1ms creation time +// Go goroutines: ~2KB per goroutine (grows), ~0.001ms creation time +``` + + +## Summary + +### Key Concepts + +1. **No GIL**: Go has true parallelism, Python limited by GIL +2. **Lightweight**: Goroutines use ~2KB stack, threads use ~8MB +3. **M:N Scheduling**: Go runtime manages goroutine scheduling +4. **WaitGroup**: Proper synchronization without sleeping +5. **Mutex/RWMutex**: Protect shared state +6. **sync.Once**: Guaranteed single execution +7. **Atomic operations**: Lock-free synchronization for simple cases +8. **Worker pools**: Limit concurrent goroutines +9. **Context**: Cancellation and timeouts +10. **Avoid leaks**: Always provide exit conditions + +### Common Patterns + +- **WaitGroup**: Wait for multiple goroutines +- **Mutex**: Protect shared data +- **RWMutex**: Multiple readers, single writer +- **Channels**: Communication between goroutines (next module) +- **Context**: Cancellation propagation +- **Worker pool**: Bounded concurrency + +### Best Practices + +1. Always have exit conditions for goroutines +2. Use WaitGroup instead of time.Sleep +3. Be careful with shared state (prefer channels) +4. Handle panics in goroutines +5. Use worker pools for many tasks +6. Use RWMutex for read-heavy workloads +7. Use atomic operations for simple counters +8. Use context for cancellation + +### Comparison with Python + +| Python | Go | +|--------|-----| +| GIL limits parallelism | No GIL, true parallelism | +| Threading limited by GIL | Goroutines on all CPUs | +| Thread ~8MB memory | Goroutine ~2KB stack | +| OS manages scheduling | Go runtime schedules | +| `threading.Thread` | `go` keyword | +| `threading.Lock` | `sync.Mutex` | +| `threading.RLock` | `sync.RWMutex` | +| `threading.Event` | `context.Context` | + +## Exercises + +1. Create a worker pool that: + - Processes 1000 tasks + - Uses 20 workers + - Collects all results + - Measures execution time + +2. Implement a concurrent web scraper: + - Fetch multiple URLs concurrently + - Use WaitGroup for synchronization + - Handle panics gracefully + - Implement timeout with context + +3. Build a rate limiter: + - Limit to N requests per second + - Use goroutines and channels + - Drop or queue excess requests + +4. Create a concurrent cache: + - Use RWMutex for thread safety + - Implement Get/Set/Delete operations + - Add TTL (time-to-live) support + +5. Parallel data processing: + - Split large file into chunks + - Process chunks concurrently + - Aggregate results + - Handle errors properly + +## Next Steps + +Next module: **Channels and Communication** - Learn how goroutines communicate safely and effectively. diff --git a/content/docs/py2go/module-07-goroutines.zh-cn.mdx b/content/docs/py2go/module-07-goroutines.zh-cn.mdx new file mode 100644 index 0000000..4316cca --- /dev/null +++ b/content/docs/py2go/module-07-goroutines.zh-cn.mdx @@ -0,0 +1,1619 @@ +--- +title: "Module 7: Goroutine 与并发" +description: "使用 goroutine 实现轻量级并发" +--- + +## 简介 + +Python 的全局解释器锁(GIL)是 CPU 密集型并发任务的最大限制之一。Go 从设计之初就考虑了并发,使用 goroutine——由 Go 运行时管理的轻量级线程。 + +### 什么是 GIL? + +**全局解释器锁(GIL)**是一个互斥锁,用于保护对 Python 对象的访问,防止多个原生线程同时执行 Python 字节码。这意味着: + +**在 Python 中:** +- 同一时间只有一个线程执行 Python 字节码 +- CPU 密集型任务无法从线程中获得性能提升 +- I/O 密集型任务可以从线程中受益(因为 I/O 等待) +- 需要多进程才能实现真正的并行 + +**在 Go 中:** +- 无 GIL 限制 +- 多个 goroutine 可以真正并行执行 +- Goroutine 被多路复用到 OS 线程(M:N 调度) +- 完美适合 CPU 密集型和 I/O 密集型任务 + +## GIL vs Goroutine + + +```python !! py +# Python - CPU 密集型任务受 GIL 限制 +import threading +import time + +def cpu_bound_task(n): + """CPU 密集型计算""" + total = 0 + for i in range(n): + total += i * i + return total + +def run_parallel(): + threads = [] + start = time.time() + + for _ in range(4): + t = threading.Thread(target=cpu_bound_task, args=(10_000_000,)) + threads.append(t) + t.start() + + for t in threads: + t.join() + + elapsed = time.time() - start + print(f"Threading time: {elapsed:.2f}s") + +# 结果: ~4 秒(由于 GIL,线程顺序运行) +# 相同代码使用多进程: ~1 秒(真正并行) +``` + +```go !! go +// Go - 使用 goroutine 实现真正并行 +package main + +import ( + "fmt" + "sync" + "time" +) + +func cpuBoundTask(n int) int { + total := 0 + for i := 0; i < n; i++ { + total += i * i + } + return total +} + +func runParallel() { + var wg sync.WaitGroup + start := time.Now() + + for i := 0; i < 4; i++ { + wg.Add(1) + go func() { + defer wg.Done() + cpuBoundTask(10_000_000) + }() + } + + wg.Wait() + elapsed := time.Since(start) + fmt.Printf("Goroutines time: %v\n", elapsed) +} + +// 结果: ~1 秒(goroutine 在所有 CPU 上并行运行) +func main() { + runParallel() +} +``` + + +## Goroutine 内部机制 + +### 什么是 Goroutine? + +Goroutine 是由 Go 运行时管理的轻量级线程: + + +```python !! py +# Python - OS 线程 +import threading +import sys + +# 每个 Python 线程使用一个 OS 线程 +# 典型内存: 每线程 ~8 MB +# 创建成本: 高(OS 系统调用) + +def thread_info(): + thread = threading.current_thread() + print(f"Thread: {thread.name}") + +# 创建 10,000 个线程可能会使系统崩溃 +threads = [] +for i in range(100): # 即使 100 个也很重 + t = threading.Thread(target=thread_info) + threads.append(t) + t.start() + +for t in threads: + t.join() +``` + +```go !! go +// Go - Goroutines +package main + +import ( + "fmt" + "runtime" + "sync" + "time" +) + +// Goroutine 非常轻量: +// - 栈大小: 从 2KB 开始,按需增长 +// - 创建成本: 非常低 +// - 调度: Go 运行时(M:N 模型) + +func goroutineInfo(id int) { + fmt.Printf("Goroutine %d\n", id) +} + +func main() { + // 可以轻松创建 10,000+ goroutine + var wg sync.WaitGroup + + for i := 0; i < 10000; i++ { + wg.Add(1) + go func(id int) { + defer wg.Done() + goroutineInfo(id) + }(i) + } + + wg.Wait() + fmt.Printf("Goroutines: %d\n", runtime.NumGoroutine()) + fmt.Printf("CPUs: %d\n", runtime.NumCPU()) +} + +// 这完全正常!内存使用保持合理。 +``` + + +### Goroutine 调度 + +Go 使用 **M:N 调度器**: +- **M** 个 goroutine 多路复用到 **N** 个 OS 线程 +- Go 运行时处理调度,而非 OS +- Goroutine 是协作调度的(Go 1.14 后支持抢占式) + + +```python !! py +# Python - OS 调度(1:1) +# +# Thread 1 -> OS Thread 1 -> CPU Core +# Thread 2 -> OS Thread 2 -> CPU Core +# Thread 3 -> OS Thread 3 -> CPU Core +# +# 问题: +# - OS 管理调度(昂贵的上下文切换) +# - 受限于 OS 线程数量 +# - 每线程高内存(~8MB) + +import threading +import time + +def worker(): + time.sleep(0.001) # 上下文切换 + +# OS 决定何时切换线程 +for _ in range(100): + t = threading.Thread(target=worker) + t.start() + t.join() +``` + +```go !! go +// Go - Go 运行时调度(M:N) +package main + +import ( + "fmt" + "runtime" + "time" +) + +// Goroutine 1 --\ /--> CPU Core 1 +// Goroutine 2 ---> [Go Scheduler] --[N OS Threads]---> CPU Core 2 +// Goroutine 3 --/ \--> CPU Core 3 +// +// 优势: +// - Go 运行时管理调度(廉价的上下文切换) +// - 多个 goroutine per OS 线程 +// - 自动负载均衡 +// - 按需增减 OS 线程 + +func worker(id int) { + time.Sleep(time.Millisecond) + fmt.Printf("Worker %d\n", id) +} + +func main() { + // 设置最大 OS 线程数(可选) + runtime.GOMAXPROCS(runtime.NumCPU()) + + for i := 0; i < 100; i++ { + go worker(i) + } + + time.Sleep(100 * time.Millisecond) +} +``` + + +## 启动 Goroutine + +### 基本启动方式 + + +```python !! py +# Python - 线程 +import threading +import time + +def task(name, duration): + """运行任务""" + time.sleep(duration) + print(f"Task {name} completed") + +# 创建并启动线程 +thread = threading.Thread(target=task, args=("A", 1)) +thread.start() # 开始执行 + +# 等待完成 +thread.join() +print("Main continues") +``` + +```go !! go +// Go - Goroutines +package main + +import ( + "fmt" + "time" +) + +func task(name string, duration time.Duration) { + time.Sleep(duration) + fmt.Printf("Task %s completed\n", name) +} + +func main() { + // 启动 goroutine + go task("A", time.Second) + + // 等待 goroutine 完成 + // (实际代码中应使用 WaitGroup 或 channel) + time.Sleep(2 * time.Second) + fmt.Println("Main continues") +} +``` + + +### 多个 Goroutine + + +```python !! py +# Python - 多个线程 +import threading +import time + +def download(url, delay): + """模拟下载""" + time.sleep(delay) + print(f"Downloaded {url}") + +urls = [ + ("http://example.com/file1", 1), + ("http://example.com/file2", 2), + ("http://example.com/file3", 1), +] + +threads = [] +for url, delay in urls: + t = threading.Thread(target=download, args=(url, delay)) + threads.append(t) + t.start() + +for t in threads: + t.join() + +print("All downloads complete") +``` + +```go !! go +// Go - 多个 goroutine +package main + +import ( + "fmt" + "sync" + "time" +) + +type Download struct { + URL string + Delay time.Duration +} + +func download(d Download, wg *sync.WaitGroup) { + defer wg.Done() + time.Sleep(d.Delay) + fmt.Printf("Downloaded %s\n", d.URL) +} + +func main() { + downloads := []Download{ + {"http://example.com/file1", time.Second}, + {"http://example.com/file2", 2 * time.Second}, + {"http://example.com/file3", time.Second}, + } + + var wg sync.WaitGroup + + for _, d := range downloads { + wg.Add(1) + go download(d, &wg) + } + + wg.Wait() + fmt.Println("All downloads complete") +} +``` + + +## 匿名 Goroutine + +### 快速内联任务 + + +```python !! py +# Python - Lambda/thread +import threading +import time + +# 启动匿名任务 +thread = threading.Thread( + target=lambda: print("Anonymous task") +) +thread.start() +thread.join() + +# 带参数 +thread = threading.Thread( + target=lambda x, y: print(f"Sum: {x + y}"), + args=(5, 3) +) +thread.start() +thread.join() +``` + +```go !! go +// Go - 匿名 goroutine +package main + +import ( + "fmt" + "time" +) + +func main() { + // 简单匿名 goroutine + go func() { + fmt.Println("Anonymous task") + }() + + time.Sleep(time.Millisecond) + + // 带参数 + go func(x int, y int) { + fmt.Printf("Sum: %d\n", x+y) + }(5, 3) + + time.Sleep(time.Millisecond) +} +``` + + +## 带闭包的 Goroutine + +### 循环变量捕获陷阱 + +这是 Go 中最常见的错误之一! + + +```python !! py +# Python - 延迟绑定闭包问题 +import threading + +funcs = [] +for i in range(3): + funcs.append(lambda: print(i)) + +for f in funcs: + f() # 打印 2, 2, 2(都引用相同的 i) + +# 修复 - 使用默认参数捕获值 +funcs = [] +for i in range(3): + funcs.append(lambda i=i: print(i)) + +for f in funcs: + f() # 打印 0, 1, 2 +``` + +```go !! go +// Go - 循环变量捕获 +package main + +import ( + "fmt" + "sync" + "time" +) + +func main() { + // 错误 - 所有 goroutine 捕获相同的 i 变量 + var wg sync.WaitGroup + for i := 0; i < 3; i++ { + wg.Add(1) + go func() { + defer wg.Done() + fmt.Println(i) // 可能打印 3, 3, 3 或不可预测 + }() + } + wg.Wait() + time.Sleep(time.Millisecond) + + fmt.Println("---") + + // 正确 - 将 i 作为参数传递 + var wg2 sync.WaitGroup + for i := 0; i < 3; i++ { + wg2.Add(1) + go func(n int) { + defer wg2.Done() + fmt.Println(n) // 打印 0, 1, 2 + }(i) // 传递 i 的当前值 + } + wg2.Wait() +} +``` + + +### 为什么会发生这种情况? + +闭包捕获的是**变量**,而不是**值**。所有 goroutine 共享同一个循环变量,在 goroutine 运行前该变量可能已被修改。 + +## WaitGroup: 正确的同步方式 + +使用 `time.Sleep()` 等待 goroutine 是不良实践。应该使用 `sync.WaitGroup`。 + + +```python !! py +# Python - Join 线程 +import threading +import time + +def worker(id): + time.sleep(0.1) + print(f"Worker {id} done") + +threads = [] +for i in range(5): + t = threading.Thread(target=worker, args=(i,)) + threads.append(t) + t.start() + +# 等待所有线程 +for t in threads: + t.join() + +print("All workers done") +``` + +```go !! go +// Go - WaitGroup +package main + +import ( + "fmt" + "sync" + "time" +) + +func worker(id int, wg *sync.WaitGroup) { + defer wg.Done() // 完成时递减计数器 + time.Sleep(100 * time.Millisecond) + fmt.Printf("Worker %d done\n", id) +} + +func main() { + var wg sync.WaitGroup + + for i := 0; i < 5; i++ { + wg.Add(1) // 递增计数器 + go worker(i, &wg) + } + + wg.Wait() // 等待计数器归零 + fmt.Println("All workers done") +} +``` + + +### WaitGroup 最佳实践 + + +```go !! go +// Go - WaitGroup 模式 +package main + +import ( + "fmt" + "sync" +) + +// 模式 1: 在调用者 Add,在 goroutine 中 Done +func worker1(id int, wg *sync.WaitGroup) { + defer wg.Done() + fmt.Printf("Worker %d\n", id) +} + +func pattern1() { + var wg sync.WaitGroup + + for i := 0; i < 3; i++ { + wg.Add(1) // 在启动 goroutine 前添加 + go worker1(i, &wg) + } + + wg.Wait() +} + +// 模式 2: 在 goroutine 内部 Add +func worker2(id int) { + fmt.Printf("Worker %d\n", id) +} + +func pattern2() { + var wg sync.WaitGroup + + for i := 0; i < 3; i++ { + go func(id int) { + wg.Add(1) // 在 goroutine 开始时添加 + defer wg.Done() + worker2(id) + }(i) + } + + wg.Wait() +} + +// 模式 3: 带返回值的 WaitGroup +type Result struct { + ID int + Value int +} + +func workerWithResult(id int, results chan<- Result, wg *sync.WaitGroup) { + defer wg.Done() + results <- Result{ID: id, Value: id * 2} +} + +func pattern3() { + var wg sync.WaitGroup + results := make(chan Result, 3) + + for i := 0; i < 3; i++ { + wg.Add(1) + go workerWithResult(i, results, &wg) + } + + // 所有 worker 完成时关闭 channel + go func() { + wg.Wait() + close(results) + }() + + // 收集结果 + for r := range results { + fmt.Printf("Worker %d: %d\n", r.ID, r.Value) + } +} + +func main() { + fmt.Println("Pattern 1:") + pattern1() + + fmt.Println("\nPattern 2:") + pattern2() + + fmt.Println("\nPattern 3:") + pattern3() +} +``` + + +## 互斥锁(Mutex) + +当多个 goroutine 访问共享数据时,需要同步。 + + +```python !! py +# Python - Lock +import threading + +counter = 0 +lock = threading.Lock() + +def increment(): + global counter + with lock: # 获取锁 + counter += 1 # 临界区 + # 锁自动释放 + +# 启动 100 个线程 +threads = [] +for _ in range(100): + t = threading.Thread(target=increment) + threads.append(t) + t.start() + +for t in threads: + t.join() + +print(f"Counter: {counter}") # 100 +``` + +```go !! go +// Go - Mutex +package main + +import ( + "fmt" + "sync" +) + +var ( + counter int + mutex sync.Mutex +) + +func increment() { + mutex.Lock() // 获取锁 + counter++ // 临界区 + mutex.Unlock() // 释放锁 +} + +func main() { + var wg sync.WaitGroup + + for i := 0; i < 100; i++ { + wg.Add(1) + go func() { + defer wg.Done() + increment() + }() + } + + wg.Wait() + fmt.Printf("Counter: %d\n", counter) // 100 +} +``` + + +### 在 Mutex 中使用 defer + + +```go !! go +// Go - 使用 defer 进行 Unlock +package main + +import ( + "fmt" + "sync" +) + +var ( + counter int + mutex sync.Mutex +) + +func incrementGood() { + mutex.Lock() + defer mutex.Unlock() // 保证执行 + + counter++ + + // 即使发生 panic,Unlock 也会执行 + // if somethingBad() { panic("oops") } +} + +func incrementBad() { + mutex.Lock() + counter++ + mutex.Unlock() + + // 如果这里发生 panic,mutex 保持锁定! + // if somethingBad() { panic("oops") } +} + +func main() { + var wg sync.WaitGroup + + for i := 0; i < 100; i++ { + wg.Add(1) + go func() { + defer wg.Done() + incrementGood() + }() + } + + wg.Wait() + fmt.Printf("Counter: %d\n", counter) +} +``` + + +## RWMutex: 读写锁 + +对于读多写少的工作负载,`sync.RWMutex` 允许多个读者或一个写者。 + + +```python !! py +# Python - 读写锁 +import threading + +rwlock = threading.RLock() + +def read_data(): + with rwlock: + # 多个读者可以持有这个锁 + data = get_data() + return data + +def write_data(new_data): + with rwlock: + # 写者获得独占访问 + set_data(new_data) +``` + +```go !! go +// Go - RWMutex +package main + +import ( + "fmt" + "sync" + "time" +) + +type DataStore struct { + mu sync.RWMutex + data map[string]string +} + +func NewDataStore() *DataStore { + return &DataStore{ + data: make(map[string]string), + } +} + +// 读 - 多个 goroutine 可以同时读取 +func (ds *DataStore) Read(key string) (string, bool) { + ds.mu.RLock() // 读锁 + defer ds.mu.RUnlock() + time.Sleep(time.Millisecond) // 模拟工作 + val, ok := ds.data[key] + return val, ok +} + +// 写 - 独占访问 +func (ds *DataStore) Write(key, value string) { + ds.mu.Lock() // 写锁(独占) + defer ds.mu.Unlock() + time.Sleep(10 * time.Millisecond) // 模拟工作 + ds.data[key] = value +} + +func main() { + ds := NewDataStore() + var wg sync.WaitGroup + + // 启动 100 个读者 + for i := 0; i < 100; i++ { + wg.Add(1) + go func(id int) { + defer wg.Done() + ds.Read(fmt.Sprintf("key-%d", id)) + }(i) + } + + // 启动 10 个写者 + for i := 0; i < 10; i++ { + wg.Add(1) + go func(id int) { + defer wg.Done() + ds.Write(fmt.Sprintf("key-%d", id), fmt.Sprintf("value-%d", id)) + }(i) + } + + wg.Wait() + fmt.Println("All operations complete") +} +``` + + +### 何时使用 RWMutex vs Mutex + + +```go !! go +// Go - 何时使用哪个 +package main + +import "sync" + +// 使用常规 Mutex 当: +// - 写操作频繁 +// - 临界区短 +// - 不需要读优化 +type Counter struct { + mu sync.Mutex + value int +} + +// 使用 RWMutex 当: +// - 读操作远多于写操作 +// - 临界区较长 +// - 多个并发读者有益 +type Cache struct { + mu sync.RWMutex + data map[string]interface{} +} + +func (c *Cache) Get(key string) (interface{}, bool) { + c.mu.RLock() // 允许并发读 + defer c.mu.RUnlock() + val, ok := c.data[key] + return val, ok +} + +func (c *Cache) Set(key string, val interface{}) { + c.mu.Lock() // 独占写 + defer c.mu.Unlock() + c.data[key] = val +} +``` + + +## sync.Once: 单次初始化 + +确保函数只执行一次,即使从多个 goroutine 调用。 + + +```python !! py +# Python - 线程安全的单例 +import threading + +class Singleton: + _instance = None + _lock = threading.Lock() + + def __new__(cls): + if cls._instance is None: + with cls._lock: + # 双重检查 + if cls._instance is None: + cls._instance = super().__new__(cls) + return cls._instance + +# 使用 +s1 = Singleton() +s2 = Singleton() +assert s1 is s2 +``` + +```go !! go +// Go - sync.Once +package main + +import ( + "fmt" + "sync" +) + +type Singleton struct { + data string +} + +var ( + instance *Singleton + once sync.Once +) + +func getInstance() *Singleton { + once.Do(func() { + // 这只会执行一次 + instance = &Singleton{data: "initialized"} + fmt.Println("Singleton initialized") + }) + return instance +} + +func main() { + var wg sync.WaitGroup + + // 多个 goroutine 尝试初始化 + for i := 0; i < 10; i++ { + wg.Add(1) + go func(id int) { + defer wg.Done() + inst := getInstance() + fmt.Printf("Goroutine %d: %p\n", id, inst) + }(i) + } + + wg.Wait() + // "Singleton initialized" 只打印一次 +} +``` + + +### Once 模式 + + +```go !! go +// Go - 常见 sync.Once 模式 +package main + +import ( + "fmt" + "sync" +) + +// 模式 1: 延迟初始化 +var ( + config map[string]string + configOnce sync.Once +) + +func getConfig() map[string]string { + configOnce.Do(func() { + // 昂贵的初始化 + config = make(map[string]string) + config["host"] = "localhost" + config["port"] = "8080" + fmt.Println("Config initialized") + }) + return config +} + +// 模式 2: 多个 Once 实例 +type ConnectionPool struct { + once sync.Once + pool []*Connection +} + +func (cp *ConnectionPool) Init() { + cp.once.Do(func() { + // 初始化池 + cp.pool = make([]*Connection, 10) + fmt.Println("Connection pool initialized") + }) +} + +// 模式 3: 带错误处理的 Once +var ( + cache Cache + cacheOnce sync.Once + cacheErr error +) + +func getCache() (Cache, error) { + cacheOnce.Do(func() { + cache, cacheErr = initCache() + }) + return cache, cacheErr +} + +func initCache() (Cache, error) { + // 可能失败的初始化 + return Cache{}, nil +} + +type Cache struct{} + +func main() { + getConfig() + getConfig() + // "Config initialized" 只打印一次 + + pool := &ConnectionPool{} + pool.Init() + pool.Init() + // 连接池只初始化一次 +} +``` + + +## 原子操作 + +对于简单操作,使用 atomic 包代替 mutex。 + + +```python !! py +# Python - 原子操作(线程安全) +import threading + +counter = 0 + +# 由于 GIL,CPython 中递增是原子的 +# 但在所有实现中都不保证 + +# 对于保证原子性,使用锁 +lock = threading.Lock() + +def increment(): + global counter + with lock: + counter += 1 +``` + +```go !! go +// Go - 原子操作 +package main + +import ( + "sync" + "sync/atomic" +) + +// Mutex 方式 +type MutexCounter struct { + mu sync.Mutex + value int64 +} + +func (c *MutexCounter) Increment() { + c.mu.Lock() + c.value++ + c.mu.Unlock() +} + +// 原子方式(简单操作更快) +type AtomicCounter struct { + value int64 +} + +func (c *AtomicCounter) Increment() { + atomic.AddInt64(&c.value, 1) +} + +func (c *AtomicCounter) Get() int64 { + return atomic.LoadInt64(&c.value) +} + +// 常见原子操作 +func main() { + var counter int64 = 0 + + // 加 + atomic.AddInt64(&counter, 1) + + // 加载 + val := atomic.LoadInt64(&counter) + + // 存储 + atomic.StoreInt64(&counter, 100) + + // 比较并交换 + atomic.CompareAndSwapInt64(&counter, 100, 200) + + // 交换 + old := atomic.SwapInt64(&counter, 300) + + _, _ = val, old +} +``` + + +## Goroutine 泄漏 + +Goroutine 很便宜,但不是免费的。泄漏的 goroutine 可能导致内存问题。 + + +```python !! py +# Python - 线程泄漏 +import threading +import time + +def worker(): + while True: + time.sleep(1) + # 永不退出! + +# 创建永不退出的线程 +t = threading.Thread(target=worker) +t.start() + +# 线程持续运行,消耗资源 +``` + +```go !! go +// Go - Goroutine 泄漏 +package main + +import ( + "fmt" + "runtime" + "time" +) + +// 错误 - Goroutine 永不退出 +func leakWorker() { + for { + time.Sleep(time.Second) + // 永不返回! + } +} + +// 正确 - 始终有退出条件 +func goodWorker(stop <-chan struct{}) { + for { + select { + case <-stop: + return // 退出 goroutine + default: + time.Sleep(time.Second) + // 执行工作... + } + } +} + +func main() { + fmt.Println("Goroutines:", runtime.NumGoroutine()) + + // 泄漏 goroutine + go leakWorker() + time.Sleep(time.Millisecond) + fmt.Println("Goroutines (after leak):", runtime.NumGoroutine()) + + // 带停止 channel 的正确 goroutine + stop := make(chan struct{}) + go goodWorker(stop) + time.Sleep(time.Millisecond) + + close(stop) // 通知 goroutine 停止 + time.Sleep(time.Millisecond) + fmt.Println("Goroutines (after cleanup):", runtime.NumGoroutine()) +} +``` + + +## Worker Pool + +限制并发 goroutine 的数量以避免资源耗尽。 + + +```python !! py +# Python - 使用 ThreadPoolExecutor 的 worker pool +from concurrent.futures import ThreadPoolExecutor + +def process_task(task_id): + print(f"Processing {task_id}") + return task_id * 2 + +# 限制为 10 个 worker +with ThreadPoolExecutor(max_workers=10) as executor: + futures = [executor.submit(process_task, i) for i in range(100)] + results = [f.result() for f in futures] +``` + +```go !! go +// Go - Worker pool +package main + +import ( + "fmt" + "sync" +) + +func worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) { + defer wg.Done() + for job := range jobs { + fmt.Printf("Worker %d processing job %d\n", id, job) + results <- job * 2 + } +} + +func main() { + numWorkers := 10 + numJobs := 100 + + jobs := make(chan int, numJobs) + results := make(chan int, numJobs) + + var wg sync.WaitGroup + + // 启动 workers + for i := 0; i < numWorkers; i++ { + wg.Add(1) + go worker(i, jobs, results, &wg) + } + + // 发送任务 + for j := 0; j < numJobs; j++ { + jobs <- j + } + close(jobs) + + // 等待 workers 完成 + go func() { + wg.Wait() + close(results) + }() + + // 收集结果 + for result := range results { + _ = result + } + + fmt.Println("All jobs processed") +} +``` + + +## Goroutine 与 Context + +使用 context 进行取消和超时处理。 + + +```python !! py +# Python - 使用 Event 取消 +import threading +import time + +def worker(stop_event): + while not stop_event.is_set(): + time.sleep(0.1) + print("Working...") + print("Stopped") + +stop_event = threading.Event() +t = threading.Thread(target=worker, args=(stop_event,)) +t.start() + +time.sleep(1) +stop_event.set() # 发送停止信号 +t.join() +``` + +```go !! go +// Go - 使用 Context 取消 +package main + +import ( + "context" + "fmt" + "time" +) + +func worker(ctx context.Context) { + for { + select { + case <-ctx.Done(): + fmt.Println("Stopped") + return + default: + time.Sleep(100 * time.Millisecond) + fmt.Println("Working...") + } + } +} + +func main() { + // 创建可取消的 context + ctx, cancel := context.WithCancel(context.Background()) + + go worker(ctx) + + // 让它运行一秒 + time.Sleep(time.Second) + + // 取消 context + cancel() + + // 等待清理 + time.Sleep(100 * time.Millisecond) +} +``` + + +## 最佳实践 + +### 1. 知道何时使用 Goroutine + + +```go !! go +// 好的 - 使用 goroutine 用于: +// 1. I/O 密集型操作(网络、磁盘) +// 2. 独立任务 +// 3. 并行处理 + +func fetchUserData(userID int) { + // 网络 I/O - 完美适合 goroutine + resp := http.Get(fmt.Sprintf("http://api/user/%d", userID)) + // ... +} + +func processImages(images []Image) { + // CPU 密集型并行工作 + for _, img := range images { + go processImage(img) + } +} + +// 坏的 - 不要使用 goroutine 用于: +// 1. 简单操作(开销) +// 2. 紧循环(使用 channel 协调) +// 3. 当顺序很重要时 + +func add(a, b int) int { + // 不要这样做: + go func() { + result = a + b // 竞态条件! + }() + + // 直接执行工作 + return a + b +} +``` + + +### 2. 始终有退出条件 + + +```go !! go +// 好的 - 始终退出 +func worker(stop <-chan struct{}) { + ticker := time.NewTicker(time.Second) + defer ticker.Stop() + + for { + select { + case <-stop: + return // 清理退出 + case <-ticker.C: + // 工作... + } + } +} + +// 坏的 - 永不退出 +func worker() { + for { + time.Sleep(time.Second) + // 没有退出条件! + } +} +``` + + +### 3. 小心共享状态 + + +```go !! go +// 好的 - 使用 channel 通信 +func producer(out chan<- int) { + for i := 0; i < 10; i++ { + out <- i + } + close(out) +} + +func consumer(in <-chan int) { + for val := range in { + fmt.Println(val) + } +} + +// 坏的 - 共享可变状态 +var data []int + +func producer() { + for i := 0; i < 10; i++ { + data = append(data, i) // 竞态条件! + } +} + +func consumer() { + for _, val := range data { + fmt.Println(val) // 竞态条件! + } +} +``` + + +### 4. 正确使用 WaitGroup + + +```go !! go +// 好的 - 在启动 goroutine 前添加 +func processItems(items []Item) { + var wg sync.WaitGroup + + for _, item := range items { + wg.Add(1) // 在 goroutine 前添加 + go func(i Item) { + defer wg.Done() + process(i) + }(item) + } + + wg.Wait() +} + +// 坏的 - 在 goroutine 内部添加 +func processItemsBad(items []Item) { + var wg sync.WaitGroup + + for _, item := range items { + go func(i Item) { + wg.Add(1) // 竞态条件! + defer wg.Done() + process(i) + }(item) + } + + wg.Wait() // 可能过早返回! +} +``` + + +### 5. 处理 Goroutine 中的 Panic + + +```go !! go +// 好的 - 从 panic 中恢复 +func safeWorker() { + defer func() { + if r := recover(); r != nil { + fmt.Printf("Recovered: %v\n", r) + } + }() + + // 可能 panic 的工作 + panic("oops") +} + +// 坏的 - Panic 使 goroutine 崩溃 +func unsafeWorker() { + panic("oops") // Goroutine 死亡,无法恢复 +} +``` + + +## 性能考虑 + +### Goroutine vs Thread 开销 + + +```go !! go +// Go - 内存和创建成本 +package main + +import ( + "fmt" + "runtime" + "time" +) + +func main() { + // Goroutine 非常轻量 + start := time.Now() + + var wg sync.WaitGroup + for i := 0; i < 100000; i++ { + wg.Add(1) + go func() { + defer wg.Done() + time.Sleep(time.Second) + }() + } + + fmt.Printf("Created 100k goroutines in %v\n", time.Since(start)) + fmt.Printf("Goroutines: %d\n", runtime.NumGoroutine()) + + wg.Wait() +} + +// 比较: +// Python 线程: 每线程 ~8MB,~1ms 创建时间 +// Go goroutine: 每个 ~2KB(增长),~0.001ms 创建时间 +``` + + +## 总结 + +### 核心概念 + +1. **无 GIL**: Go 有真正并行,Python 受 GIL 限制 +2. **轻量级**: Goroutine 使用 ~2KB 栈,线程使用 ~8MB +3. **M:N 调度**: Go 运行时管理 goroutine 调度 +4. **WaitGroup**: 正确同步而不需要 sleep +5. **Mutex/RWMutex**: 保护共享状态 +6. **sync.Once**: 保证单次执行 +7. **原子操作**: 简单情况的无锁同步 +8. **Worker pool**: 限制并发 goroutine +9. **Context**: 取消和超时 +10. **避免泄漏**: 始终提供退出条件 + +### 常见模式 + +- **WaitGroup**: 等待多个 goroutine +- **Mutex**: 保护共享数据 +- **RWMutex**: 多读者单写者 +- **Channels**: Goroutine 间通信(下一个模块) +- **Context**: 取消传播 +- **Worker pool**: 有界并发 + +### 最佳实践 + +1. 始终为 goroutine 提供退出条件 +2. 使用 WaitGroup 代替 time.Sleep +3. 小心共享状态(优先使用 channel) +4. 处理 goroutine 中的 panic +5. 为多个任务使用 worker pool +6. 为读密集型工作负载使用 RWMutex +7. 为简单计数器使用原子操作 +8. 使用 context 进行取消 + +### 与 Python 的比较 + +| Python | Go | +|--------|-----| +| GIL 限制并行 | 无 GIL,真正并行 | +| 线程受 GIL 限制 | Goroutine 在所有 CPU 上 | +| 线程 ~8MB 内存 | Goroutine ~2KB 栈 | +| OS 管理调度 | Go 运行时调度 | +| `threading.Thread` | `go` 关键字 | +| `threading.Lock` | `sync.Mutex` | +| `threading.RLock` | `sync.RWMutex` | +| `threading.Event` | `context.Context` | + +## 练习 + +1. 创建一个 worker pool: + - 处理 1000 个任务 + - 使用 20 个 worker + - 收集所有结果 + - 测量执行时间 + +2. 实现并发网页爬虫: + - 并发获取多个 URL + - 使用 WaitGroup 同步 + - 优雅处理 panic + - 使用 context 实现超时 + +3. 构建限流器: + - 限制每秒 N 个请求 + - 使用 goroutine 和 channel + - 丢弃或排队多余请求 + +4. 创建并发缓存: + - 使用 RWMutex 保证线程安全 + - 实现 Get/Set/Delete 操作 + - 添加 TTL 支持 + +5. 并行数据处理: + - 将大文件分块 + - 并发处理块 + - 聚合结果 + - 正确处理错误 + +## 下一步 + +下一个模块: **Channel 与通信** - 学习 goroutine 如何安全有效地通信。 diff --git a/content/docs/py2go/module-07-goroutines.zh-tw.mdx b/content/docs/py2go/module-07-goroutines.zh-tw.mdx new file mode 100644 index 0000000..880e57a --- /dev/null +++ b/content/docs/py2go/module-07-goroutines.zh-tw.mdx @@ -0,0 +1,1619 @@ +--- +title: "Module 7: Goroutine 與並行" +description: "使用 goroutine 實現輕量級並行" +--- + +## 簡介 + +Python 的全域解釋器鎖(GIL)是 CPU 密集型並行任務的最大限制之一。Go 從設計之初就考慮了並行,使用 goroutine——由 Go 運行時管理的輕量級執行緒。 + +### 什麼是 GIL? + +**全域解釋器鎖(GIL)**是一個互斥鎖,用於保護對 Python 物件的存取,防止多個原生執行緒同時執行 Python 位元組碼。這意味著: + +**在 Python 中:** +- 同一時間只有一個執行緒執行 Python 位元組碼 +- CPU 密集型任務無法從執行緒中獲得效能提升 +- I/O 密集型任務可以從執行緒中受益(因為 I/O 等待) +- 需要多進程才能實現真正的並行 + +**在 Go 中:** +- 無 GIL 限制 +- 多個 goroutine 可以真正並行執行 +- Goroutine 被多路複用到 OS 執行緒(M:N 調度) +- 完美適合 CPU 密集型和 I/O 密集型任務 + +## GIL vs Goroutine + + +```python !! py +# Python - CPU 密集型任務受 GIL 限制 +import threading +import time + +def cpu_bound_task(n): + """CPU 密集型計算""" + total = 0 + for i in range(n): + total += i * i + return total + +def run_parallel(): + threads = [] + start = time.time() + + for _ in range(4): + t = threading.Thread(target=cpu_bound_task, args=(10_000_000,)) + threads.append(t) + t.start() + + for t in threads: + t.join() + + elapsed = time.time() - start + print(f"Threading time: {elapsed:.2f}s") + +# 結果: ~4 秒(由於 GIL,執行緒順序運行) +# 相同程式碼使用多進程: ~1 秒(真正並行) +``` + +```go !! go +// Go - 使用 goroutine 實現真正並行 +package main + +import ( + "fmt" + "sync" + "time" +) + +func cpuBoundTask(n int) int { + total := 0 + for i := 0; i < n; i++ { + total += i * i + } + return total +} + +func runParallel() { + var wg sync.WaitGroup + start := time.Now() + + for i := 0; i < 4; i++ { + wg.Add(1) + go func() { + defer wg.Done() + cpuBoundTask(10_000_000) + }() + } + + wg.Wait() + elapsed := time.Since(start) + fmt.Printf("Goroutines time: %v\n", elapsed) +} + +// 結果: ~1 秒(goroutine 在所有 CPU 上並行運行) +func main() { + runParallel() +} +``` + + +## Goroutine 內部機制 + +### 什麼是 Goroutine? + +Goroutine 是由 Go 運行時管理的輕量級執行緒: + + +```python !! py +# Python - OS 執行緒 +import threading +import sys + +# 每個 Python 執行緒使用一個 OS 執行緒 +# 典型記憶體: 每執行緒 ~8 MB +# 建立成本: 高(OS 系統呼叫) + +def thread_info(): + thread = threading.current_thread() + print(f"Thread: {thread.name}") + +# 建立 10,000 個執行緒可能會使系統崩潰 +threads = [] +for i in range(100): # 即使 100 個也很重 + t = threading.Thread(target=thread_info) + threads.append(t) + t.start() + +for t in threads: + t.join() +``` + +```go !! go +// Go - Goroutines +package main + +import ( + "fmt" + "runtime" + "sync" + "time" +) + +// Goroutine 非常輕量: +// - 堆疊大小: 從 2KB 開始,按需增長 +// - 建立成本: 非常低 +// - 調度: Go 運行時(M:N 模型) + +func goroutineInfo(id int) { + fmt.Printf("Goroutine %d\n", id) +} + +func main() { + // 可以輕鬆建立 10,000+ goroutine + var wg sync.WaitGroup + + for i := 0; i < 10000; i++ { + wg.Add(1) + go func(id int) { + defer wg.Done() + goroutineInfo(id) + }(i) + } + + wg.Wait() + fmt.Printf("Goroutines: %d\n", runtime.NumGoroutine()) + fmt.Printf("CPUs: %d\n", runtime.NumCPU()) +} + +// 這完全正常!記憶體使用保持合理。 +``` + + +### Goroutine 調度 + +Go 使用 **M:N 調度器**: +- **M** 個 goroutine 多路複用到 **N** 個 OS 執行緒 +- Go 運行時處理調度,而非 OS +- Goroutine 是協作調度的(Go 1.14 後支援搶占式) + + +```python !! py +# Python - OS 調度(1:1) +# +# Thread 1 -> OS Thread 1 -> CPU Core +# Thread 2 -> OS Thread 2 -> CPU Core +# Thread 3 -> OS Thread 3 -> CPU Core +# +# 問題: +# - OS 管理調度(昂貴的上下文切換) +# - 受限於 OS 執行緒數量 +# - 每執行緒高記憶體(~8MB) + +import threading +import time + +def worker(): + time.sleep(0.001) # 上下文切換 + +# OS 決定何時切換執行緒 +for _ in range(100): + t = threading.Thread(target=worker) + t.start() + t.join() +``` + +```go !! go +// Go - Go 運行時調度(M:N) +package main + +import ( + "fmt" + "runtime" + "time" +) + +// Goroutine 1 --\ /--> CPU Core 1 +// Goroutine 2 ---> [Go Scheduler] --[N OS Threads]---> CPU Core 2 +// Goroutine 3 --/ \--> CPU Core 3 +// +// 優勢: +// - Go 運行時管理調度(廉價的上下文切換) +// - 多個 goroutine per OS 執行緒 +// - 自動負載平衡 +// - 按需增減 OS 執行緒 + +func worker(id int) { + time.Sleep(time.Millisecond) + fmt.Printf("Worker %d\n", id) +} + +func main() { + // 設置最大 OS 執行緒數(可選) + runtime.GOMAXPROCS(runtime.NumCPU()) + + for i := 0; i < 100; i++ { + go worker(i) + } + + time.Sleep(100 * time.Millisecond) +} +``` + + +## 啟動 Goroutine + +### 基本啟動方式 + + +```python !! py +# Python - 執行緒 +import threading +import time + +def task(name, duration): + """運行任務""" + time.sleep(duration) + print(f"Task {name} completed") + +# 建立並啟動執行緒 +thread = threading.Thread(target=task, args=("A", 1)) +thread.start() # 開始執行 + +# 等待完成 +thread.join() +print("Main continues") +``` + +```go !! go +// Go - Goroutines +package main + +import ( + "fmt" + "time" +) + +func task(name string, duration time.Duration) { + time.Sleep(duration) + fmt.Printf("Task %s completed\n", name) +} + +func main() { + // 啟動 goroutine + go task("A", time.Second) + + // 等待 goroutine 完成 + // (實際程式碼中應使用 WaitGroup 或 channel) + time.Sleep(2 * time.Second) + fmt.Println("Main continues") +} +``` + + +### 多個 Goroutine + + +```python !! py +# Python - 多個執行緒 +import threading +import time + +def download(url, delay): + """模擬下載""" + time.sleep(delay) + print(f"Downloaded {url}") + +urls = [ + ("http://example.com/file1", 1), + ("http://example.com/file2", 2), + ("http://example.com/file3", 1), +] + +threads = [] +for url, delay in urls: + t = threading.Thread(target=download, args=(url, delay)) + threads.append(t) + t.start() + +for t in threads: + t.join() + +print("All downloads complete") +``` + +```go !! go +// Go - 多個 goroutine +package main + +import ( + "fmt" + "sync" + "time" +) + +type Download struct { + URL string + Delay time.Duration +} + +func download(d Download, wg *sync.WaitGroup) { + defer wg.Done() + time.Sleep(d.Delay) + fmt.Printf("Downloaded %s\n", d.URL) +} + +func main() { + downloads := []Download{ + {"http://example.com/file1", time.Second}, + {"http://example.com/file2", 2 * time.Second}, + {"http://example.com/file3", time.Second}, + } + + var wg sync.WaitGroup + + for _, d := range downloads { + wg.Add(1) + go download(d, &wg) + } + + wg.Wait() + fmt.Println("All downloads complete") +} +``` + + +## 匿名 Goroutine + +### 快速內聯任務 + + +```python !! py +# Python - Lambda/thread +import threading +import time + +# 啟動匿名任務 +thread = threading.Thread( + target=lambda: print("Anonymous task") +) +thread.start() +thread.join() + +# 帶參數 +thread = threading.Thread( + target=lambda x, y: print(f"Sum: {x + y}"), + args=(5, 3) +) +thread.start() +thread.join() +``` + +```go !! go +// Go - 匿名 goroutine +package main + +import ( + "fmt" + "time" +) + +func main() { + // 簡單匿名 goroutine + go func() { + fmt.Println("Anonymous task") + }() + + time.Sleep(time.Millisecond) + + // 帶參數 + go func(x int, y int) { + fmt.Printf("Sum: %d\n", x+y) + }(5, 3) + + time.Sleep(time.Millisecond) +} +``` + + +## 帶閉包的 Goroutine + +### 迴圈變數捕獲陷阱 + +這是 Go 中最常見的錯誤之一! + + +```python !! py +# Python - 延遲綁定閉包問題 +import threading + +funcs = [] +for i in range(3): + funcs.append(lambda: print(i)) + +for f in funcs: + f() # 打印 2, 2, 2(都引用相同的 i) + +# 修復 - 使用預設參數捕獲值 +funcs = [] +for i in range(3): + funcs.append(lambda i=i: print(i)) + +for f in funcs: + f() # 打印 0, 1, 2 +``` + +```go !! go +// Go - 迴圈變數捕獲 +package main + +import ( + "fmt" + "sync" + "time" +) + +func main() { + // 錯誤 - 所有 goroutine 捕獲相同的 i 變數 + var wg sync.WaitGroup + for i := 0; i < 3; i++ { + wg.Add(1) + go func() { + defer wg.Done() + fmt.Println(i) // 可能打印 3, 3, 3 或不可預測 + }() + } + wg.Wait() + time.Sleep(time.Millisecond) + + fmt.Println("---") + + // 正確 - 將 i 作為參數傳遞 + var wg2 sync.WaitGroup + for i := 0; i < 3; i++ { + wg2.Add(1) + go func(n int) { + defer wg2.Done() + fmt.Println(n) // 打印 0, 1, 2 + }(i) // 傳遞 i 的當前值 + } + wg2.Wait() +} +``` + + +### 為什麼會發生這種情況? + +閉包捕獲的是**變數**,而不是**值**。所有 goroutine 共用同一個迴圈變數,在 goroutine 運行前該變數可能已被修改。 + +## WaitGroup: 正確的同步方式 + +使用 `time.Sleep()` 等待 goroutine 是不良實踐。應該使用 `sync.WaitGroup`。 + + +```python !! py +# Python - Join 執行緒 +import threading +import time + +def worker(id): + time.sleep(0.1) + print(f"Worker {id} done") + +threads = [] +for i in range(5): + t = threading.Thread(target=worker, args=(i,)) + threads.append(t) + t.start() + +# 等待所有執行緒 +for t in threads: + t.join() + +print("All workers done") +``` + +```go !! go +// Go - WaitGroup +package main + +import ( + "fmt" + "sync" + "time" +) + +func worker(id int, wg *sync.WaitGroup) { + defer wg.Done() // 完成時遞減計數器 + time.Sleep(100 * time.Millisecond) + fmt.Printf("Worker %d done\n", id) +} + +func main() { + var wg sync.WaitGroup + + for i := 0; i < 5; i++ { + wg.Add(1) // 遞增計數器 + go worker(i, &wg) + } + + wg.Wait() // 等待計數器歸零 + fmt.Println("All workers done") +} +``` + + +### WaitGroup 最佳實踐 + + +```go !! go +// Go - WaitGroup 模式 +package main + +import ( + "fmt" + "sync" +) + +// 模式 1: 在呼叫者 Add,在 goroutine 中 Done +func worker1(id int, wg *sync.WaitGroup) { + defer wg.Done() + fmt.Printf("Worker %d\n", id) +} + +func pattern1() { + var wg sync.WaitGroup + + for i := 0; i < 3; i++ { + wg.Add(1) // 在啟動 goroutine 前添加 + go worker1(i, &wg) + } + + wg.Wait() +} + +// 模式 2: 在 goroutine 內部 Add +func worker2(id int) { + fmt.Printf("Worker %d\n", id) +} + +func pattern2() { + var wg sync.WaitGroup + + for i := 0; i < 3; i++ { + go func(id int) { + wg.Add(1) // 在 goroutine 開始時添加 + defer wg.Done() + worker2(id) + }(i) + } + + wg.Wait() +} + +// 模式 3: 帶返回值的 WaitGroup +type Result struct { + ID int + Value int +} + +func workerWithResult(id int, results chan<- Result, wg *sync.WaitGroup) { + defer wg.Done() + results <- Result{ID: id, Value: id * 2} +} + +func pattern3() { + var wg sync.WaitGroup + results := make(chan Result, 3) + + for i := 0; i < 3; i++ { + wg.Add(1) + go workerWithResult(i, results, &wg) + } + + // 所有 worker 完成時關閉 channel + go func() { + wg.Wait() + close(results) + }() + + // 收集結果 + for r := range results { + fmt.Printf("Worker %d: %d\n", r.ID, r.Value) + } +} + +func main() { + fmt.Println("Pattern 1:") + pattern1() + + fmt.Println("\nPattern 2:") + pattern2() + + fmt.Println("\nPattern 3:") + pattern3() +} +``` + + +## 互斥鎖(Mutex) + +當多個 goroutine 存取共享資料時,需要同步。 + + +```python !! py +# Python - Lock +import threading + +counter = 0 +lock = threading.Lock() + +def increment(): + global counter + with lock: # 獲取鎖 + counter += 1 # 臨界區 + # 鎖自動釋放 + +# 啟動 100 個執行緒 +threads = [] +for _ in range(100): + t = threading.Thread(target=increment) + threads.append(t) + t.start() + +for t in threads: + t.join() + +print(f"Counter: {counter}") # 100 +``` + +```go !! go +// Go - Mutex +package main + +import ( + "fmt" + "sync" +) + +var ( + counter int + mutex sync.Mutex +) + +func increment() { + mutex.Lock() // 獲取鎖 + counter++ // 臨界區 + mutex.Unlock() // 釋放鎖 +} + +func main() { + var wg sync.WaitGroup + + for i := 0; i < 100; i++ { + wg.Add(1) + go func() { + defer wg.Done() + increment() + }() + } + + wg.Wait() + fmt.Printf("Counter: %d\n", counter) // 100 +} +``` + + +### 在 Mutex 中使用 defer + + +```go !! go +// Go - 使用 defer 進行 Unlock +package main + +import ( + "fmt" + "sync" +) + +var ( + counter int + mutex sync.Mutex +) + +func incrementGood() { + mutex.Lock() + defer mutex.Unlock() // 保證執行 + + counter++ + + // 即使發生 panic,Unlock 也會執行 + // if somethingBad() { panic("oops") } +} + +func incrementBad() { + mutex.Lock() + counter++ + mutex.Unlock() + + // 如果這裡發生 panic,mutex 保持鎖定! + // if somethingBad() { panic("oops") } +} + +func main() { + var wg sync.WaitGroup + + for i := 0; i < 100; i++ { + wg.Add(1) + go func() { + defer wg.Done() + incrementGood() + }() + } + + wg.Wait() + fmt.Printf("Counter: %d\n", counter) +} +``` + + +## RWMutex: 讀寫鎖 + +對於讀多寫少的工作負載,`sync.RWMutex` 允許多個讀者或一個寫者。 + + +```python !! py +# Python - 讀寫鎖 +import threading + +rwlock = threading.RLock() + +def read_data(): + with rwlock: + # 多個讀者可以持有這個鎖 + data = get_data() + return data + +def write_data(new_data): + with rwlock: + # 寫者獲得獨占存取 + set_data(new_data) +``` + +```go !! go +// Go - RWMutex +package main + +import ( + "fmt" + "sync" + "time" +) + +type DataStore struct { + mu sync.RWMutex + data map[string]string +} + +func NewDataStore() *DataStore { + return &DataStore{ + data: make(map[string]string), + } +} + +// 讀 - 多個 goroutine 可以同時讀取 +func (ds *DataStore) Read(key string) (string, bool) { + ds.mu.RLock() // 讀鎖 + defer ds.mu.RUnlock() + time.Sleep(time.Millisecond) // 模擬工作 + val, ok := ds.data[key] + return val, ok +} + +// 寫 - 獨占存取 +func (ds *DataStore) Write(key, value string) { + ds.mu.Lock() // 寫鎖(獨占) + defer ds.mu.Unlock() + time.Sleep(10 * time.Millisecond) // 模擬工作 + ds.data[key] = value +} + +func main() { + ds := NewDataStore() + var wg sync.WaitGroup + + // 啟動 100 個讀者 + for i := 0; i < 100; i++ { + wg.Add(1) + go func(id int) { + defer wg.Done() + ds.Read(fmt.Sprintf("key-%d", id)) + }(i) + } + + // 啟動 10 個寫者 + for i := 0; i < 10; i++ { + wg.Add(1) + go func(id int) { + defer wg.Done() + ds.Write(fmt.Sprintf("key-%d", id), fmt.Sprintf("value-%d", id)) + }(i) + } + + wg.Wait() + fmt.Println("All operations complete") +} +``` + + +### 何時使用 RWMutex vs Mutex + + +```go !! go +// Go - 何時使用哪個 +package main + +import "sync" + +// 使用常規 Mutex 當: +// - 寫操作頻繁 +// - 臨界區短 +// - 不需要讀優化 +type Counter struct { + mu sync.Mutex + value int +} + +// 使用 RWMutex 當: +// - 讀操作遠多於寫操作 +// - 臨界區較長 +// - 多個並行讀者有益 +type Cache struct { + mu sync.RWMutex + data map[string]interface{} +} + +func (c *Cache) Get(key string) (interface{}, bool) { + c.mu.RLock() // 允許並行讀 + defer c.mu.RUnlock() + val, ok := c.data[key] + return val, ok +} + +func (c *Cache) Set(key string, val interface{}) { + c.mu.Lock() // 獨占寫 + defer c.mu.Unlock() + c.data[key] = val +} +``` + + +## sync.Once: 單次初始化 + +確保函數只執行一次,即使從多個 goroutine 呼叫。 + + +```python !! py +# Python - 執行緒安全的單例 +import threading + +class Singleton: + _instance = None + _lock = threading.Lock() + + def __new__(cls): + if cls._instance is None: + with cls._lock: + # 雙重檢查 + if cls._instance is None: + cls._instance = super().__new__(cls) + return cls._instance + +# 使用 +s1 = Singleton() +s2 = Singleton() +assert s1 is s2 +``` + +```go !! go +// Go - sync.Once +package main + +import ( + "fmt" + "sync" +) + +type Singleton struct { + data string +} + +var ( + instance *Singleton + once sync.Once +) + +func getInstance() *Singleton { + once.Do(func() { + // 這只會執行一次 + instance = &Singleton{data: "initialized"} + fmt.Println("Singleton initialized") + }) + return instance +} + +func main() { + var wg sync.WaitGroup + + // 多個 goroutine 嘗試初始化 + for i := 0; i < 10; i++ { + wg.Add(1) + go func(id int) { + defer wg.Done() + inst := getInstance() + fmt.Printf("Goroutine %d: %p\n", id, inst) + }(i) + } + + wg.Wait() + // "Singleton initialized" 只打印一次 +} +``` + + +### Once 模式 + + +```go !! go +// Go - 常見 sync.Once 模式 +package main + +import ( + "fmt" + "sync" +) + +// 模式 1: 延遲初始化 +var ( + config map[string]string + configOnce sync.Once +) + +func getConfig() map[string]string { + configOnce.Do(func() { + // 昂貴的初始化 + config = make(map[string]string) + config["host"] = "localhost" + config["port"] = "8080" + fmt.Println("Config initialized") + }) + return config +} + +// 模式 2: 多個 Once 實例 +type ConnectionPool struct { + once sync.Once + pool []*Connection +} + +func (cp *ConnectionPool) Init() { + cp.once.Do(func() { + // 初始化池 + cp.pool = make([]*Connection, 10) + fmt.Println("Connection pool initialized") + }) +} + +// 模式 3: 帶錯誤處理的 Once +var ( + cache Cache + cacheOnce sync.Once + cacheErr error +) + +func getCache() (Cache, error) { + cacheOnce.Do(func() { + cache, cacheErr = initCache() + }) + return cache, cacheErr +} + +func initCache() (Cache, error) { + // 可能失敗的初始化 + return Cache{}, nil +} + +type Cache struct{} + +func main() { + getConfig() + getConfig() + // "Config initialized" 只打印一次 + + pool := &ConnectionPool{} + pool.Init() + pool.Init() + // 連接池只初始化一次 +} +``` + + +## 原子操作 + +對於簡單操作,使用 atomic 包代替 mutex。 + + +```python !! py +# Python - 原子操作(執行緒安全) +import threading + +counter = 0 + +# 由於 GIL,CPython 中遞增是原子的 +# 但在所有實作中都不保證 + +# 對於保證原子性,使用鎖 +lock = threading.Lock() + +def increment(): + global counter + with lock: + counter += 1 +``` + +```go !! go +// Go - 原子操作 +package main + +import ( + "sync" + "sync/atomic" +) + +// Mutex 方式 +type MutexCounter struct { + mu sync.Mutex + value int64 +} + +func (c *MutexCounter) Increment() { + c.mu.Lock() + c.value++ + c.mu.Unlock() +} + +// 原子方式(簡單操作更快) +type AtomicCounter struct { + value int64 +} + +func (c *AtomicCounter) Increment() { + atomic.AddInt64(&c.value, 1) +} + +func (c *AtomicCounter) Get() int64 { + return atomic.LoadInt64(&c.value) +} + +// 常見原子操作 +func main() { + var counter int64 = 0 + + // 加 + atomic.AddInt64(&counter, 1) + + // 載入 + val := atomic.LoadInt64(&counter) + + // 存儲 + atomic.StoreInt64(&counter, 100) + + // 比較並交換 + atomic.CompareAndSwapInt64(&counter, 100, 200) + + // 交換 + old := atomic.SwapInt64(&counter, 300) + + _, _ = val, old +} +``` + + +## Goroutine 泄漏 + +Goroutine 很便宜,但不是免費的。泄漏的 goroutine 可能導致記憶體問題。 + + +```python !! py +# Python - 執行緒泄漏 +import threading +import time + +def worker(): + while True: + time.sleep(1) + # 永不退出! + +# 建立永不退出的執行緒 +t = threading.Thread(target=worker) +t.start() + +# 執行緒持續運行,消耗資源 +``` + +```go !! go +// Go - Goroutine 泄漏 +package main + +import ( + "fmt" + "runtime" + "time" +) + +// 錯誤 - Goroutine 永不退出 +func leakWorker() { + for { + time.Sleep(time.Second) + // 永不返回! + } +} + +// 正確 - 始終有退出條件 +func goodWorker(stop <-chan struct{}) { + for { + select { + case <-stop: + return // 退出 goroutine + default: + time.Sleep(time.Second) + // 執行工作... + } + } +} + +func main() { + fmt.Println("Goroutines:", runtime.NumGoroutine()) + + // 泄漏 goroutine + go leakWorker() + time.Sleep(time.Millisecond) + fmt.Println("Goroutines (after leak):", runtime.NumGoroutine()) + + // 帶停止 channel 的正確 goroutine + stop := make(chan struct{}) + go goodWorker(stop) + time.Sleep(time.Millisecond) + + close(stop) // 通知 goroutine 停止 + time.Sleep(time.Millisecond) + fmt.Println("Goroutines (after cleanup):", runtime.NumGoroutine()) +} +``` + + +## Worker Pool + +限制並行 goroutine 的數量以避免資源耗盡。 + + +```python !! py +# Python - 使用 ThreadPoolExecutor 的 worker pool +from concurrent.futures import ThreadPoolExecutor + +def process_task(task_id): + print(f"Processing {task_id}") + return task_id * 2 + +# 限制為 10 個 worker +with ThreadPoolExecutor(max_workers=10) as executor: + futures = [executor.submit(process_task, i) for i in range(100)] + results = [f.result() for f in futures] +``` + +```go !! go +// Go - Worker pool +package main + +import ( + "fmt" + "sync" +) + +func worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) { + defer wg.Done() + for job := range jobs { + fmt.Printf("Worker %d processing job %d\n", id, job) + results <- job * 2 + } +} + +func main() { + numWorkers := 10 + numJobs := 100 + + jobs := make(chan int, numJobs) + results := make(chan int, numJobs) + + var wg sync.WaitGroup + + // 啟動 workers + for i := 0; i < numWorkers; i++ { + wg.Add(1) + go worker(i, jobs, results, &wg) + } + + // 發送任務 + for j := 0; j < numJobs; j++ { + jobs <- j + } + close(jobs) + + // 等待 workers 完成 + go func() { + wg.Wait() + close(results) + }() + + // 收集結果 + for result := range results { + _ = result + } + + fmt.Println("All jobs processed") +} +``` + + +## Goroutine 與 Context + +使用 context 進行取消和超時處理。 + + +```python !! py +# Python - 使用 Event 取消 +import threading +import time + +def worker(stop_event): + while not stop_event.is_set(): + time.sleep(0.1) + print("Working...") + print("Stopped") + +stop_event = threading.Event() +t = threading.Thread(target=worker, args=(stop_event,)) +t.start() + +time.sleep(1) +stop_event.set() # 發送停止信號 +t.join() +``` + +```go !! go +// Go - 使用 Context 取消 +package main + +import ( + "context" + "fmt" + "time" +) + +func worker(ctx context.Context) { + for { + select { + case <-ctx.Done(): + fmt.Println("Stopped") + return + default: + time.Sleep(100 * time.Millisecond) + fmt.Println("Working...") + } + } +} + +func main() { + // 建立可取消的 context + ctx, cancel := context.WithCancel(context.Background()) + + go worker(ctx) + + // 讓它運行一秒 + time.Sleep(time.Second) + + // 取消 context + cancel() + + // 等待清理 + time.Sleep(100 * time.Millisecond) +} +``` + + +## 最佳實踐 + +### 1. 知道何時使用 Goroutine + + +```go !! go +// 好的 - 使用 goroutine 用於: +// 1. I/O 密集型操作(網路、磁碟) +// 2. 獨立任務 +// 3. 並行處理 + +func fetchUserData(userID int) { + // 網路 I/O - 完美適合 goroutine + resp := http.Get(fmt.Sprintf("http://api/user/%d", userID)) + // ... +} + +func processImages(images []Image) { + // CPU 密集型並行工作 + for _, img := range images { + go processImage(img) + } +} + +// 壞的 - 不要使用 goroutine 用於: +// 1. 簡單操作(開銷) +// 2. 緊迴圈(使用 channel 協調) +// 3. 當順序很重要時 + +func add(a, b int) int { + // 不要這樣做: + go func() { + result = a + b // 競態條件! + }() + + // 直接執行工作 + return a + b +} +``` + + +### 2. 始終有退出條件 + + +```go !! go +// 好的 - 始終退出 +func worker(stop <-chan struct{}) { + ticker := time.NewTicker(time.Second) + defer ticker.Stop() + + for { + select { + case <-stop: + return // 清理退出 + case <-ticker.C: + // 工作... + } + } +} + +// 壞的 - 永不退出 +func worker() { + for { + time.Sleep(time.Second) + // 沒有退出條件! + } +} +``` + + +### 3. 小心共享狀態 + + +```go !! go +// 好的 - 使用 channel 通訊 +func producer(out chan<- int) { + for i := 0; i < 10; i++ { + out <- i + } + close(out) +} + +func consumer(in <-chan int) { + for val := range in { + fmt.Println(val) + } +} + +// 壞的 - 共享可變狀態 +var data []int + +func producer() { + for i := 0; i < 10; i++ { + data = append(data, i) // 競態條件! + } +} + +func consumer() { + for _, val := range data { + fmt.Println(val) // 競態條件! + } +} +``` + + +### 4. 正確使用 WaitGroup + + +```go !! go +// 好的 - 在啟動 goroutine 前添加 +func processItems(items []Item) { + var wg sync.WaitGroup + + for _, item := range items { + wg.Add(1) // 在 goroutine 前添加 + go func(i Item) { + defer wg.Done() + process(i) + }(item) + } + + wg.Wait() +} + +// 壞的 - 在 goroutine 內部添加 +func processItemsBad(items []Item) { + var wg sync.WaitGroup + + for _, item := range items { + go func(i Item) { + wg.Add(1) // 競態條件! + defer wg.Done() + process(i) + }(item) + } + + wg.Wait() // 可能過早返回! +} +``` + + +### 5. 處理 Goroutine 中的 Panic + + +```go !! go +// 好的 - 從 panic 中恢復 +func safeWorker() { + defer func() { + if r := recover(); r != nil { + fmt.Printf("Recovered: %v\n", r) + } + }() + + // 可能 panic 的工作 + panic("oops") +} + +// 壞的 - Panic 使 goroutine 崩潰 +func unsafeWorker() { + panic("oops") // Goroutine 死亡,無法恢復 +} +``` + + +## 效能考慮 + +### Goroutine vs Thread 開銷 + + +```go !! go +// Go - 記憶體和建立成本 +package main + +import ( + "fmt" + "runtime" + "time" +) + +func main() { + // Goroutine 非常輕量 + start := time.Now() + + var wg sync.WaitGroup + for i := 0; i < 100000; i++ { + wg.Add(1) + go func() { + defer wg.Done() + time.Sleep(time.Second) + }() + } + + fmt.Printf("Created 100k goroutines in %v\n", time.Since(start)) + fmt.Printf("Goroutines: %d\n", runtime.NumGoroutine()) + + wg.Wait() +} + +// 比較: +// Python 執行緒: 每執行緒 ~8MB,~1ms 建立時間 +// Go goroutine: 每個 ~2KB(增長),~0.001ms 建立時間 +``` + + +## 總結 + +### 核心概念 + +1. **無 GIL**: Go 有真正並行,Python 受 GIL 限制 +2. **輕量級**: Goroutine 使用 ~2KB 堆疊,執行緒使用 ~8MB +3. **M:N 調度**: Go 運行時管理 goroutine 調度 +4. **WaitGroup**: 正確同步而不需要 sleep +5. **Mutex/RWMutex**: 保護共享狀態 +6. **sync.Once**: 保證單次執行 +7. **原子操作**: 簡單情況的無鎖同步 +8. **Worker pool**: 限制並行 goroutine +9. **Context**: 取消和超時 +10. **避免泄漏**: 始終提供退出條件 + +### 常見模式 + +- **WaitGroup**: 等待多個 goroutine +- **Mutex**: 保護共享資料 +- **RWMutex**: 多讀者單寫者 +- **Channels**: Goroutine 間通訊(下一個模組) +- **Context**: 取消傳播 +- **Worker pool**: 有界並行 + +### 最佳實踐 + +1. 始終為 goroutine 提供退出條件 +2. 使用 WaitGroup 代替 time.Sleep +3. 小心共享狀態(優先使用 channel) +4. 處理 goroutine 中的 panic +5. 為多個任務使用 worker pool +6. 為讀密集型工作負載使用 RWMutex +7. 為簡單計數器使用原子操作 +8. 使用 context 進行取消 + +### 與 Python 的比較 + +| Python | Go | +|--------|-----| +| GIL 限制並行 | 無 GIL,真正並行 | +| 執行緒受 GIL 限制 | Goroutine 在所有 CPU 上 | +| 執行緒 ~8MB 記憶體 | Goroutine ~2KB 堆疊 | +| OS 管理調度 | Go 運行時調度 | +| `threading.Thread` | `go` 關鍵字 | +| `threading.Lock` | `sync.Mutex` | +| `threading.RLock` | `sync.RWMutex` | +| `threading.Event` | `context.Context` | + +## 練習 + +1. 建立一個 worker pool: + - 處理 1000 個任務 + - 使用 20 個 worker + - 收集所有結果 + - 測量執行時間 + +2. 實作並行網頁爬蟲: + - 並行獲取多個 URL + - 使用 WaitGroup 同步 + - 優雅處理 panic + - 使用 context 實現超時 + +3. 構建限流器: + - 限制每秒 N 個請求 + - 使用 goroutine 和 channel + - 丟棄或排隊多餘請求 + +4. 建立並行快取: + - 使用 RWMutex 保證執行緒安全 + - 實作 Get/Set/Delete 操作 + - 新增 TTL 支援 + +5. 並行資料處理: + - 將大檔案分塊 + - 並行處理塊 + - 聚合結果 + - 正確處理錯誤 + +## 下一步 + +下一個模組: **Channel 與通訊** - 學習 goroutine 如何安全有效地通訊。 diff --git a/content/docs/py2go/module-08-channels.mdx b/content/docs/py2go/module-08-channels.mdx new file mode 100644 index 0000000..07e1add --- /dev/null +++ b/content/docs/py2go/module-08-channels.mdx @@ -0,0 +1,1523 @@ +--- +title: "Module 8: Channels and Communication" +description: "Safe communication between goroutines" +--- + +## Introduction + +Go's concurrency philosophy is captured in this famous quote: + +> **"Don't communicate by sharing memory; share memory by communicating."** +> — Go Proverb + +Instead of using locks and shared variables (like Python's threading), Go uses **channels** to pass data between goroutines. This approach: + +- **Prevents race conditions** by design +- **Makes data flow explicit** in the code +- **Encourages loose coupling** between goroutines +- **Follows CSP (Communicating Sequential Processes)** model + +## Why Channels Over Shared Memory? + + +```python !! py +# Python - Shared memory with locks +import threading + +counter = 0 +lock = threading.Lock() + +def increment(): + global counter + with lock: + counter += 1 + +# Problem: Easy to forget the lock +# Problem: Locks can cause deadlocks +# Problem: Hard to reason about state +``` + +```go !! go +// Go - Share memory by communicating +package main + +func increment(out chan<- int) { + out <- 1 +} + +func sum(values <-chan int, result chan<- int) { + total := 0 + for v := range values { + total += v + } + result <- total +} + +// Benefits: +// - No locks needed +// - Data flow is explicit +// - Each goroutine owns its data +// - No race conditions +``` + + +## Channel Basics + +### Creating Channels + + +```python !! py +# Python - Queue for thread communication +import queue +import threading + +# Create queue +q = queue.Queue() + +def producer(): + for i in range(5): + q.put(i) + print("Producer done") + +def consumer(): + for i in range(5): + value = q.get() + print(f"Got: {value}") + +t1 = threading.Thread(target=producer) +t2 = threading.Thread(target=consumer) +t1.start() +t2.start() +t1.join() +t2.join() +``` + +```go !! go +// Go - Channels +package main + +import ( + "fmt" + "sync" +) + +func main() { + // Create unbuffered channel + ch := make(chan int) + + var wg sync.WaitGroup + + // Producer + wg.Add(1) + go func() { + defer wg.Done() + for i := 0; i < 5; i++ { + ch <- i // Send operation + } + fmt.Println("Producer done") + }() + + // Consumer + wg.Add(1) + go func() { + defer wg.Done() + for i := 0; i < 5; i++ { + value := <-ch // Receive operation + fmt.Printf("Got: %d\n", value) + } + }() + + wg.Wait() +} +``` + + +### Unbuffered Channels + +Unbuffered channels provide **synchronous communication** - both sender and receiver must be ready. + + +```python !! py +# Python - Blocking queue (size 0 not supported) +# Python queues are always buffered +# Using threading.Event for synchronization + +import threading + +ready = threading.Event() +data = None + +def producer(): + global data + data = 42 + ready.set() # Signal data is ready + +def consumer(): + ready.wait() # Wait for signal + print(f"Got: {data}") + +t1 = threading.Thread(target=producer) +t2 = threading.Thread(target=consumer) +t2.start() +t1.start() +t1.join() +t2.join() +``` + +```go !! go +// Go - Unbuffered channel (synchronous) +package main + +import ( + "fmt" + "sync" +) + +func main() { + ch := make(chan int) // No buffer = synchronous + + var wg sync.WaitGroup + + // If we start producer first, it blocks until receiver is ready + wg.Add(1) + go func() { + defer wg.Done() + fmt.Println("Producer sending...") + ch <- 42 // Blocks until receiver is ready! + fmt.Println("Producer sent") + }() + + // Start receiver + wg.Add(1) + go func() { + defer wg.Done() + fmt.Println("Consumer waiting...") + value := <-ch // Blocks until sender is ready! + fmt.Printf("Consumer got: %d\n", value) + }() + + wg.Wait() + // Both goroutines must meet at the same time +} +``` + + +### Buffered Channels + +Buffered channels allow **asynchronous communication** - sender can send up to buffer size without receiver. + + +```python !! py +# Python - Bounded queue +import queue + +# Queue with max size +q = queue.Queue(maxsize=5) + +# Producer can add up to 5 items without consumer +for i in range(5): + q.put(i) # Doesn't block + print(f"Put: {i}") + +# q.put(5) # This would block (queue full) + +print("Queue full!") +``` + +```go !! go +// Go - Buffered channel +package main + +import "fmt" + +func main() { + // Channel with buffer of 5 + ch := make(chan int, 5) + + // Can send up to 5 values without receiver + for i := 0; i < 5; i++ { + ch <- i // Doesn't block + fmt.Printf("Put: %d\n", i) + } + + // ch <- 5 // This would block (buffer full) + + fmt.Println("Channel full!") + + // Consume values + for i := 0; i < 5; i++ { + value := <-ch + fmt.Printf("Got: %d\n", value) + } +} +``` + + +### Buffer Size Guidelines + + +```go !! go +// Go - Buffer size considerations +package main + +// Unbuffered (size 0) +// - Synchronous handshake +// - Guarantees receiver is ready +// - Use for coordination, synchronization +func unbufferedExample() { + ch := make(chan int) // Unbuffered + ch <- 1 // Blocks until receiver + value := <-ch // Blocks until sender + _ = value +} + +// Buffered (size 1) +// - Small buffer +// - Useful for semaphores, signals +// - Allows one item in flight +func bufferedOneExample() { + ch := make(chan int, 1) // Buffer of 1 + ch <- 1 // Doesn't block (room in buffer) + value := <-ch // Gets buffered value + _ = value +} + +// Buffered (size N) +// - Asynchronous processing +// - Decouples producer/consumer speeds +// - Choose based on expected load +func bufferedNExample() { + // Buffer of 100 - allows 100 items to queue + ch := make(chan int, 100) + + // Producer can send 100 items without blocking + for i := 0; i < 100; i++ { + ch <- i + } + close(ch) + + // Consumer processes at its own pace + for value := range ch { + _ = value + } +} + +// Rule of thumb: +// - Start with unbuffered (synchronous is safer) +// - Add buffer if you measure contention +// - Keep buffers small (10-100 typically) +// - Large buffers can hide problems +``` + + +## Channel Directions + +Go enforces channel directions at compile-time, making data flow explicit and preventing errors. + + +```python !! py +# Python - No compile-time checking +def process(queue): + # Can both read and write + item = queue.get() + result = process_item(item) + queue.put(result) + +# Easy to accidentally misuse +# No way to enforce read-only or write-only at compile time +``` + +```go !! go +// Go - Channel directions +package main + +import "fmt" + +// send-only channel (chan<-) +func producer(ch chan<- int) { + ch <- 42 + // value := <-ch // Compilation error! +} + +// receive-only channel (<-chan) +func consumer(ch <-chan int) { + value := <-ch + fmt.Println(value) + // ch <- 1 // Compilation error! +} + +// bidirectional channel (chan) +func inout(ch chan int) { + value := <-ch // OK + ch <- value // OK +} + +func main() { + ch := make(chan int) + + // Can pass bidirectional channel to send-only or receive-only + go producer(ch) + consumer(ch) +} +``` + + +### Direction Enforcement + + +```go !! go +// Go - Direction enforcement prevents bugs +package main + +import "fmt" + +// sendOnly - clearly indicates this function only sends +func sendOnly(ch chan<- int) { + ch <- 1 + // ch <- 2 + // close(ch) // Can only close send-only channels +} + +// receiveOnly - clearly indicates this function only receives +func receiveOnly(ch <-chan int) { + // Cannot close receive-only channel! + for value := range ch { + fmt.Println(value) + } +} + +// Benefits: +// 1. Documentation - Function signature shows intent +// 2. Safety - Compiler prevents accidental misuse +// 3. Clear ownership - Who sends, who receives + +func pipeline(numbers <-chan int, results chan<- int) { + // Process numbers, send to results + for n := range numbers { + results <- n * 2 + } + close(results) // We own the sending side +} + +func main() { + numbers := make(chan int) + results := make(chan int) + + go func() { + for i := 0; i < 5; i++ { + numbers <- i + } + close(numbers) + }() + + go pipeline(numbers, results) + + for result := range results { + fmt.Println(result) + } +} +``` + + +## Closing Channels + +Closing signals that no more values will be sent. This allows receivers to detect completion. + + +```python !! py +# Python - Sentinel values or None +import queue + +def producer(q): + for i in range(5): + q.put(i) + q.put(None) # Sentinel to signal done + +def consumer(q): + while True: + item = q.get() + if item is None: # Check for sentinel + break + print(f"Got: {item}") + +q = queue.Queue() +# ... start threads +``` + +```go !! go +// Go - Close channels +package main + +import "fmt" + +func producer(ch chan<- int) { + for i := 0; i < 5; i++ { + ch <- i + } + close(ch) // Signal no more values +} + +func consumer(ch <-chan int) { + // Range automatically detects close + for value := range ch { + fmt.Printf("Got: %d\n", value) + } + fmt.Println("Consumer done") +} + +func main() { + ch := make(chan int) + + go producer(ch) + consumer(ch) +} +``` + + +### Detecting Closed Channels + + +```go !! go +// Go - Detecting closed channels +package main + +import "fmt" + +func main() { + ch := make(chan int, 2) + ch <- 1 + ch <- 2 + close(ch) + + // Method 1: range (automatic close detection) + fmt.Println("Method 1: range") + for value := range ch { + fmt.Println(value) + } + + // Method 2: comma-ok idiom + ch2 := make(chan int, 2) + ch2 <- 1 + ch2 <- 2 + close(ch2) + + fmt.Println("\nMethod 2: comma-ok") + for { + value, ok := <-ch2 + if !ok { + fmt.Println("Channel closed") + break + } + fmt.Printf("Got: %d\n", value) + } + + // Method 3: select with comma-ok + ch3 := make(chan int, 2) + ch3 <- 1 + close(ch3) + + fmt.Println("\nMethod 3: select") + for { + select { + case value, ok := <-ch3: + if !ok { + fmt.Println("Channel closed in select") + return + } + fmt.Printf("Got: %d\n", value) + } + } +} +``` + + +### Closing Rules + + +```go !! go +// Go - Channel closing rules +package main + +import "fmt" + +// Rule 1: Only senders should close +func sender(ch chan<- int) { + for i := 0; i < 5; i++ { + ch <- i + } + close(ch) // OK - we're the sender +} + +// Rule 2: Don't close on receive side +func receiver(ch <-chan int) { + // close(ch) // Compilation error! Can't close receive-only + for value := range ch { + fmt.Println(value) + } +} + +// Rule 3: Close only once (panic otherwise) +func closeOnce(ch chan int) { + close(ch) + // close(ch) // Panic: close of closed channel +} + +// Rule 4: Sending to closed channel panics +func sendToClosed(ch chan int) { + close(ch) + // ch <- 1 // Panic: send on closed channel +} + +// Rule 5: Receiving from closed channel succeeds +func receiveFromClosed(ch chan int) { + ch <- 1 + close(ch) + + value, ok := <-ch // Gets the buffered value + fmt.Printf("Value: %d, OK: %v\n", value, ok) // OK = true + + value, ok = <-ch // Zero value, false + fmt.Printf("Value: %d, OK: %v\n", value, ok) // OK = false +} + +// Rule 6: Not necessary to close if not using range +func optionalClose(ch chan int) { + // If you know receiver will exit, close is optional + // But it's good practice for cleanup +} +``` + + +## Range Over Channels + +Use `range` to automatically receive until channel closes. + + +```python !! py +# Python - Iterate until sentinel +import queue + +def consumer(q): + while True: + item = q.get() + if item is None: # Manual check + break + process(item) +``` + +```go !! go +// Go - Range over channel +package main + +import "fmt" + +func producer(ch chan<- int) { + for i := 0; i < 5; i++ { + ch <- i + } + close(ch) // Range needs close to exit +} + +func consumer(ch <-chan int) { + // Range automatically: + // 1. Receives from channel + // 2. Exits when channel closes + for value := range ch { + fmt.Printf("Received: %d\n", value) + } + fmt.Println("Done") +} + +func main() { + ch := make(chan int) + go producer(ch) + consumer(ch) +} +``` + + +## Select Statement + +`select` waits on multiple channel operations. It's like a switch for channels. + + +```python !! py +# Python - No direct equivalent +# Would need complex polling or callbacks + +import queue +import select + +def wait_for_multiple(q1, q2): + while True: + # Poll queues (inefficient) + readable, _, _ = select.select([q1, q2], [], [], 0.1) + if readable: + if q1 in readable: + handle(q1.get()) + if q2 in readable: + handle(q2.get()) +``` + +```go !! go +// Go - Select statement +package main + +import ( + "fmt" + "time" +) + +func main() { + ch1 := make(chan string) + ch2 := make(chan string) + + go func() { + time.Sleep(100 * time.Millisecond) + ch1 <- "one" + }() + + go func() { + time.Sleep(200 * time.Millisecond) + ch2 <- "two" + }() + + // Select waits for ANY case to be ready + for i := 0; i < 2; i++ { + select { + case msg1 := <-ch1: + fmt.Println("Received from ch1:", msg1) + case msg2 := <-ch2: + fmt.Println("Received from ch2:", msg2) + } + } + + fmt.Println("Done") +} +``` + + +### Select Behavior + + +```go !! go +// Go - Select statement behavior +package main + +import ( + "fmt" + "time" +) + +func main() { + // Rule 1: Wait until one case is ready + ch1 := make(chan string) + ch2 := make(chan string) + + go func() { time.Sleep(100 * time.Millisecond); ch1 <- "first" }() + go func() { time.Sleep(200 * time.Millisecond); ch2 <- "second" }() + + select { + case msg1 := <-ch1: // This will win + fmt.Println(msg1) + case msg2 := <-ch2: + fmt.Println(msg2) + } + + // Rule 2: Random selection if multiple ready + ch3 := make(chan string, 1) + ch4 := make(chan string, 1) + + ch3 <- "ready" + ch4 <- "also ready" + + select { + case msg := <-ch3: + fmt.Println("ch3 won:", msg) // Random! + case msg := <-ch4: + fmt.Println("ch4 won:", msg) // Random! + } + + // Rule 3: Block if none ready + ch5 := make(chan string) + ch6 := make(chan string) + + // select { + // case msg := <-ch5: // Would block forever! + // fmt.Println(msg) + // case msg := <-ch6: + // fmt.Println(msg) + // } + + // Rule 4: Default case makes it non-blocking + select { + case msg := <-ch5: + fmt.Println(msg) + case msg := <-ch6: + fmt.Println(msg) + default: + fmt.Println("No channel ready") // This executes + } +} +``` + + +### Select with Timeout + + +```python !! py +# Python - Timeout with queue +import queue + +try: + item = q.get(timeout=1.0) + print(item) +except queue.Empty: + print("Timeout") +``` + +```go !! go +// Go - Timeout with select +package main + +import ( + "fmt" + "time" +) + +func main() { + ch := make(chan string) + + // Method 1: time.After + select { + case msg := <-ch: + fmt.Println("Received:", msg) + case <-time.After(time.Second): + fmt.Println("Timeout after 1 second") + } + + // Method 2: time.NewTicker (for repeated timeouts) + ticker := time.NewTicker(500 * time.Millisecond) + defer ticker.Stop() + + select { + case msg := <-ch: + fmt.Println("Received:", msg) + case <-ticker.C: + fmt.Println("Timeout after 500ms") + } + + // Method 3: context (preferred in real code) + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + + select { + case msg := <-ch: + fmt.Println("Received:", msg) + case <-ctx.Done(): + fmt.Println("Context timeout") + } +} +``` + + +### Non-Blocking Operations + + +```python !! py +# Python - Non-blocking queue operations +import queue + +q = queue.Queue() + +# Non-blocking get +try: + item = q.get(block=False) +except queue.Empty: + print("Empty") + +# Non-blocking put +try: + q.put(item, block=False) +except queue.Full: + print("Full") +``` + +```go !! go +// Go - Non-blocking with default case +package main + +import "fmt" + +func main() { + ch := make(chan int) + + // Non-blocking receive + select { + case value := <-ch: + fmt.Println("Received:", value) + default: + fmt.Println("No value available") + } + + // Non-blocking send + ch2 := make(chan int, 1) + ch2 <- 1 + ch2 <- 2 // Buffer full + + select { + case ch2 <- 3: + fmt.Println("Sent") + default: + fmt.Println("Channel full, can't send") + } + + // Check if channel has value + value := 0 + select { + case value = <-ch2: + fmt.Println("Got:", value) + default: + fmt.Println("No value") + } +} +``` + + +## Common Channel Patterns + +### Fan-Out: Distribute Work + + +```python !! py +# Python - Work distribution +import queue +import threading + +work_queue = queue.Queue() + +def worker(id): + while True: + item = work_queue.get() + if item is None: + break + print(f"Worker {id} processing {item}") + +# Fan-out to multiple workers +for i in range(5): + threading.Thread(target=worker, args=(i,)).start() + +for item in work_items: + work_queue.put(item) +``` + +```go !! go +// Go - Fan-out pattern +package main + +import ( + "fmt" + "sync" +) + +func worker(id int, jobs <-chan int, wg *sync.WaitGroup) { + defer wg.Done() + for job := range jobs { + fmt.Printf("Worker %d processing job %d\n", id, job) + } +} + +func main() { + jobs := make(chan int, 100) + var wg sync.WaitGroup + + // Fan-out: distribute work to 5 workers + for w := 1; w <= 5; w++ { + wg.Add(1) + go worker(w, jobs, &wg) + } + + // Send jobs + for j := 1; j <= 10; j++ { + jobs <- j + } + close(jobs) + + wg.Wait() + fmt.Println("All jobs processed") +} +``` + + +### Fan-In: Aggregate Results + + +```python !! py +# Python - Aggregate results +import queue +import threading + +results = queue.Queue() + +def worker(id, work_queue, results): + for item in iter(work_queue.get, None): + result = process(item) + results.put(result) + +# Fan-in: multiple workers to single results queue +workers = [] +for i in range(5): + t = threading.Thread(target=worker, args=(i, work, results)) + workers.append(t) + t.start() + +# Collect results +while any(t.is_alive() for t in workers) or not results.empty(): + result = results.get() + process_result(result) +``` + +```go !! go +// Go - Fan-in pattern +package main + +import ( + "fmt" + "sync" +) + +func worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) { + defer wg.Done() + for job := range jobs { + results <- job * 2 // Fan-in to single results channel + } +} + +func main() { + jobs := make(chan int, 100) + results := make(chan int, 100) + var wg sync.WaitGroup + + // Fan-out to workers + for w := 1; w <= 5; w++ { + wg.Add(1) + go worker(w, jobs, results, &wg) + } + + // Send jobs + for j := 1; j <= 10; j++ { + jobs <- j + } + close(jobs) + + // Close results when all workers done + go func() { + wg.Wait() + close(results) + }() + + // Fan-in: collect all results + for result := range results { + fmt.Printf("Result: %d\n", result) + } +} +``` + + +### Pipeline: Stages of Processing + + +```python !! py +# Python - Pipeline with queues +import queue + +def generator(out): + for i in range(10): + out.put(i) + out.put(None) // Sentinel + +def stage1(in_queue, out_queue): + while True: + item = in_queue.get() + if item is None: + out_queue.put(None) + break + out_queue.put(item * 2) + +def stage2(in_queue, out_queue): + while True: + item = in_queue.get() + if item is None: + break + out_queue.put(item + 10) + +# Connect pipeline +q1 = queue.Queue() +q2 = queue.Queue() +q3 = queue.Queue() + +threading.Thread(target=generator, args=(q1,)).start() +threading.Thread(target=stage1, args=(q1, q2)).start() +threading.Thread(target=stage2, args=(q2, q3)).start() +``` + +```go !! go +// Go - Pipeline pattern +package main + +import "fmt" + +// Stage 1: Generate numbers +func generator(out chan<- int) { + for i := 0; i < 10; i++ { + out <- i + } + close(out) +} + +// Stage 2: Multiply by 2 +func multiply(in <-chan int, out chan<- int) { + for n := range in { + out <- n * 2 + } + close(out) +} + +// Stage 3: Add 10 +func add(in <-chan int, out chan<- int) { + for n := range in { + out <- n + 10 + } + close(out) +} + +func main() { + // Create pipeline stages + ch1 := make(chan int) + ch2 := make(chan int) + ch3 := make(chan int) + + // Start pipeline + go generator(ch1) + go multiply(ch1, ch2) + go add(ch2, ch3) + + // Consume final results + for result := range ch3 { + fmt.Println(result) + } +} +``` + + +### Or-Channel: Multiple Cancellation Sources + + +```go !! go +// Go - Or-channel: wait on multiple channels +package main + +import ( + "fmt" + "time" +) + +// orChannel waits on multiple channels, returns when any closes +func orChannel(channels ...<-chan interface{}) <-chan interface{} { + switch len(channels) { + case 0: + return nil + case 1: + return channels[0] + default: + orDone := make(chan interface{}) + go func() { + defer close(orDone) + switch len(channels) { + case 2: + select { + case <-channels[0]: + case <-channels[1]: + } + default: + select { + case <-channels[0]: + case <-channels[1]: + case <-channels[2]: + case <-orChannel(channels[3:]...): + } + } + }() + return orDone + } +} + +func main() { + sig := func(after time.Duration) <-chan interface{} { + c := make(chan interface{}) + go func() { + defer close(c) + time.Sleep(after) + }() + return c + } + + start := time.Now() + <-orChannel( + sig(2*time.Hour), + sig(5*time.Minute), + sig(1*time.Second), + sig(1*time.Hour), + sig(2*time.Minute), + ) + + fmt.Printf("Done after %v\n", time.Since(start)) +} +``` + + +### Channel as Semaphore + + +```python !! py +# Python - Semaphore with threading +import threading + +semaphore = threading.Semaphore(5) # Max 5 concurrent + +def worker(): + with semaphore: + # Limited to 5 concurrent workers + do_work() +``` + +```go !! go +// Go - Channel as semaphore +package main + +import ( + "fmt" + "sync" + "time" +) + +func main() { + // Buffered channel as semaphore + // Buffer size = max concurrent operations + semaphore := make(chan struct{}, 5) + + var wg sync.WaitGroup + + for i := 0; i < 10; i++ { + wg.Add(1) + go func(id int) { + defer wg.Done() + + // Acquire + semaphore <- struct{}{} + defer func() { <-semaphore }() // Release + + // Only 5 goroutines here at once + fmt.Printf("Worker %d starting\n", id) + time.Sleep(time.Second) + fmt.Printf("Worker %d done\n", id) + }(i) + } + + wg.Wait() +} +``` + + +## Best Practices + +### 1. Channel Ownership + + +```go !! go +// Go - Clear channel ownership +package main + +// GOOD: Single owner +func producer() <-chan int { + ch := make(chan int) + go func() { + defer close(ch) // Owner closes + for i := 0; i < 5; i++ { + ch <- i + } + }() + return ch // Receive-only to caller +} + +func main() { + ch := producer() // We only receive + for value := range ch { + println(value) + } +} + +// BAD: Unclear ownership +func producer(ch chan int) { + // Who closes? Who sends? + ch <- 1 +} +``` + + +### 2. Avoid Goroutine Leaks + + +```go !! go +// Go - Prevent goroutine leaks +package main + +import "time" + +// BAD: Goroutine leak +func leak() { + ch := make(chan int) + go func() { + val := <-ch // Will wait forever! + println(val) + }() + // Function returns, goroutine leaks +} + +// GOOD: Always have exit condition +func noLeak() { + ch := make(chan int) + done := make(chan struct{}) + + go func() { + select { + case val := <-ch: + println(val) + case <-done: // Can exit + return + } + }() + + // Signal cleanup + close(done) +} + +// GOOD: Use context +func withContext() { + ctx, cancel := context.WithCancel(context.Background()) + ch := make(chan int) + + go func() { + select { + case val := <-ch: + println(val) + case <-ctx.Done(): // Exit on cancel + return + } + }() + + cancel() // Cleanup +} +``` + + +### 3. Know When to Use Buffered vs Unbuffered + + +```go !! go +// Go - When to use buffered vs unbuffered +package main + +// Use unbuffered when: +// - You need synchronization/handshake +// - Receiver must be ready before sender proceeds +// - You want to enforce strict ordering + +func syncHandshake() { + ch := make(chan int) // Unbuffered + + go func() { + ch <- 42 // Blocks until receiver ready + }() + + val := <-ch // Guarantees we're ready + println(val) +} + +// Use buffered when: +// - Producer and consumer run at different rates +// - You want to decouple the two +// - Small buffer improves throughput + +func asyncProcessing() { + ch := make(chan int, 100) // Buffered + + go func() { + for i := 0; i < 100; i++ { + ch <- i // Won't block (has buffer) + } + close(ch) + }() + + // Consumer can process at its own pace + for val := range ch { + process(val) + } +} + +func process(val int) {} +``` + + +### 4. Close Only from Sender Side + + +```go !! go +// Go - Closing channels correctly +package main + +// GOOD: Sender closes +func producer(ch chan<- int) { + for i := 0; i < 5; i++ { + ch <- i + } + close(ch) // Sender knows when done +} + +func consumer(ch <-chan int) { + for val := range ch { + println(val) + } + // Don't close - receiver only +} + +// GOOD: Document if you don't close +func stream(ch chan<- int) { + // Caller owns and must close + for i := 0; i < 5; i++ { + ch <- i + } + // No close - documented in function docs +} + +// BAD: Closing from receiver +func badConsumer(ch <-chan int) { + // close(ch) // Compilation error anyway +} +``` + + +### 5. Handle Zero Values on Closed Channels + + +```go !! go +// Go - Zero values on closed channels +package main + +import "fmt" + +func main() { + ch := make(chan int, 2) + ch <- 1 + ch <- 2 + close(ch) + + // Receiving gets buffered values first + val1, ok1 := <-ch + fmt.Printf("Value: %d, OK: %v\n", val1, ok1) // 1, true + + val2, ok2 := <-ch + fmt.Printf("Value: %d, OK: %v\n", val2, ok2) // 2, true + + // Subsequent receives get zero value + val3, ok3 := <-ch + fmt.Printf("Value: %d, OK: %v\n", val3, ok3) // 0, false + + // Be careful with zero values! + // If you need to distinguish "no value" from "zero value", + // use pointer channels or check the ok boolean +} +``` + + +## Summary + +### Key Concepts + +1. **Channels**: Typed conduits for data between goroutines +2. **Unbuffered**: Synchronous, blocking communication +3. **Buffered**: Asynchronous, capacity-based communication +4. **Directions**: Send-only, receive-only, bidirectional +5. **Closing**: Signal no more values, detect with range +6. **Select**: Wait on multiple channel operations +7. **Range**: Iterate until channel closes +8. **Fan-out/Fan-in**: Distribute and aggregate work +9. **Pipelines**: Chain of processing stages +10. **Ownership**: Clear who sends, receives, and closes + +### Common Patterns + +- **Unbuffered channel**: Synchronization, handshake +- **Buffered channel**: Asynchronous processing, semaphore +- **Fan-out**: Multiple workers from one source +- **Fan-in**: Aggregate to one sink +- **Pipeline**: Sequential processing stages +- **Worker pool**: Bounded concurrency +- **Or-channel**: Multiple cancellation sources + +### Best Practices + +1. Prefer channels over shared memory +2. Use unbuffered channels for synchronization +3. Make channel directions explicit +4. Close channels only from sender side +5. Use `range` for receiving until close +6. Use `select` for multiple operations +7. Always prevent goroutine leaks +8. Keep buffer sizes small + +### Comparison with Python + +| Python | Go | +|--------|-----| +| `queue.Queue()` | `make(chan Type)` | +| `queue.get()` | `<-ch` | +| `queue.put()` | `ch <-` | +| `queue.Empty` exception | Comma-ok idiom | +| Sentinel values (None) | `close(ch)` | +| `select` module | `select` statement | +| Polling | Blocking/select | +| No type safety | Typed channels | + +## Exercises + +1. Implement a pipeline with 3 stages: + - Stage 1: Generate numbers 1-100 + - Stage 2: Filter even numbers + - Stage 3: Square the numbers + - Collect final results + +2. Build a worker pool that: + - Has 10 workers + - Processes 1000 jobs + - Returns results through a channel + - Measures execution time + +3. Create a timeout pattern: + - Operation should complete in 2 seconds + - Return error if timeout + - Use context for cancellation + +4. Implement fan-in pattern: + - 5 producers generating data + - 1 consumer collecting all data + - Use channels for coordination + +5. Build a rate limiter: + - Allow N operations per second + - Block if rate exceeded + - Use ticker + channel + +## Next Steps + +Next module: **Select and Concurrency Patterns** - Advanced patterns and idioms with select statements and channels. diff --git a/content/docs/py2go/module-08-channels.zh-cn.mdx b/content/docs/py2go/module-08-channels.zh-cn.mdx new file mode 100644 index 0000000..a5cc4de --- /dev/null +++ b/content/docs/py2go/module-08-channels.zh-cn.mdx @@ -0,0 +1,1524 @@ +--- +title: "Module 8: Channel 与通信" +description: "Goroutine 之间的安全通信" +--- + +## 简介 + +Go 的并发哲学在这个名言中得到了完美体现: + +> **"不要通过共享内存来通信;通过通信来共享内存。"** +> — Go 谚语 + +Go 不使用锁和共享变量(像 Python 的线程那样),而是使用 **channel** 在 goroutine 之间传递数据。这种方法: + +- **从设计上防止竞态条件** +- **使数据流在代码中显式可见** +- **鼓励 goroutine 之间的松耦合** +- **遵循 CSP(通信顺序进程)模型** + +## 为什么使用 Channel 而非共享内存? + + +```python !! py +# Python - 使用锁的共享内存 +import threading + +counter = 0 +lock = threading.Lock() + +def increment(): + global counter + with lock: + counter += 1 + +# 问题: 容易忘记锁 +# 问题: 锁可能导致死锁 +# 问题: 难以推理状态 +``` + +```go !! go +// Go - 通过通信共享内存 +package main + +func increment(out chan<- int) { + out <- 1 +} + +func sum(values <-chan int, result chan<- int) { + total := 0 + for v := range values { + total += v + } + result <- total +} + +// 优势: +// - 不需要锁 +// - 数据流显式 +// - 每个 goroutine 拥有自己的数据 +// - 无竞态条件 +``` + + +## Channel 基础 + +### 创建 Channel + + +```python !! py +# Python - 用于线程通信的 Queue +import queue +import threading + +# 创建 queue +q = queue.Queue() + +def producer(): + for i in range(5): + q.put(i) + print("Producer done") + +def consumer(): + for i in range(5): + value = q.get() + print(f"Got: {value}") + +t1 = threading.Thread(target=producer) +t2 = threading.Thread(target=consumer) +t1.start() +t2.start() +t1.join() +t2.join() +``` + +```go !! go +// Go - Channels +package main + +import ( + "fmt" + "sync" +) + +func main() { + // 创建无缓冲 channel + ch := make(chan int) + + var wg sync.WaitGroup + + // 生产者 + wg.Add(1) + go func() { + defer wg.Done() + for i := 0; i < 5; i++ { + ch <- i // 发送操作 + } + fmt.Println("Producer done") + }() + + // 消费者 + wg.Add(1) + go func() { + defer wg.Done() + for i := 0; i < 5; i++ { + value := <-ch // 接收操作 + fmt.Printf("Got: %d\n", value) + } + }() + + wg.Wait() +} +``` + + +### 无缓冲 Channel + +无缓冲 channel 提供**同步通信**——发送者和接收者都必须准备好。 + + +```python !! py +# Python - 阻塞队列(不支持大小为 0) +# Python 队列总是有缓冲的 +# 使用 threading.Event 进行同步 + +import threading + +ready = threading.Event() +data = None + +def producer(): + global data + data = 42 + ready.set() # 信号数据准备就绪 + +def consumer(): + ready.wait() # 等待信号 + print(f"Got: {data}") + +t1 = threading.Thread(target=producer) +t2 = threading.Thread(target=consumer) +t2.start() +t1.start() +t1.join() +t2.join() +``` + +```go !! go +// Go - 无缓冲 channel(同步) +package main + +import ( + "fmt" + "sync" +) + +func main() { + ch := make(chan int) // 无缓冲 = 同步 + + var wg sync.WaitGroup + + // 如果先启动生产者,它会阻塞直到接收者准备好 + wg.Add(1) + go func() { + defer wg.Done() + fmt.Println("Producer sending...") + ch <- 42 // 阻塞直到接收者准备好! + fmt.Println("Producer sent") + }() + + // 启动接收者 + wg.Add(1) + go func() { + defer wg.Done() + fmt.Println("Consumer waiting...") + value := <-ch // 阻塞直到发送者准备好! + fmt.Printf("Consumer got: %d\n", value) + }() + + wg.Wait() + // 两个 goroutine 必须同时相遇 +} +``` + + +### 有缓冲 Channel + +有缓冲 channel 允许**异步通信**——发送者可以在没有接收者的情况下发送多达缓冲区大小的值。 + + +```python !! py +# Python - 有界队列 +import queue + +# 最大大小的队列 +q = queue.Queue(maxsize=5) + +# 生产者可以在没有消费者的情况下添加最多 5 个项 +for i in range(5): + q.put(i) # 不阻塞 + print(f"Put: {i}") + +# q.put(5) # 这会阻塞(队列满) + +print("Queue full!") +``` + +```go !! go +// Go - 有缓冲 channel +package main + +import "fmt" + +func main() { + // 缓冲大小为 5 的 channel + ch := make(chan int, 5) + + // 可以在没有接收者的情况下发送多达 5 个值 + for i := 0; i < 5; i++ { + ch <- i // 不阻塞 + fmt.Printf("Put: %d\n", i) + } + + // ch <- 5 // 这会阻塞(缓冲区满) + + fmt.Println("Channel full!") + + // 消费值 + for i := 0; i < 5; i++ { + value := <-ch + fmt.Printf("Got: %d\n", value) + } +} +``` + + +### 缓冲区大小指南 + + +```go !! go +// Go - 缓冲区大小考虑 +package main + +// 无缓冲(大小 0) +// - 同步握手 +// - 保证接收者准备好 +// - 用于协调、同步 +func unbufferedExample() { + ch := make(chan int) // 无缓冲 + ch <- 1 // 阻塞直到接收者 + value := <-ch // 阻塞直到发送者 + _ = value +} + +// 有缓冲(大小 1) +// - 小缓冲 +// - 用于信号量、信号 +// - 允许一个项在传输中 +func bufferedOneExample() { + ch := make(chan int, 1) // 缓冲为 1 + ch <- 1 // 不阻塞(缓冲区有空间) + value := <-ch // 获取缓冲值 + _ = value +} + +// 有缓冲(大小 N) +// - 异步处理 +// - 解耦生产者/消费者速度 +// - 根据预期负载选择 +func bufferedNExample() { + // 缓冲 100 - 允许 100 个项排队 + ch := make(chan int, 100) + + // 生产者可以发送 100 项而不阻塞 + for i := 0; i < 100; i++ { + ch <- i + } + close(ch) + + // 消费者以自己的节奏处理 + for value := range ch { + _ = value + } +} + +// 经验法则: +// - 从无缓冲开始(同步更安全) +// - 如果测量到竞争再添加缓冲 +// - 保持缓冲区小(通常 10-100) +// - 大缓冲区可能隐藏问题 +``` + + +## Channel 方向 + +Go 在编译时强制 channel 方向,使数据流显式并防止错误。 + + +```python !! py +# Python - 无编译时检查 +def process(queue): + # 可以读也可以写 + item = queue.get() + result = process_item(item) + queue.put(result) + +# 容易误用 +# 无法在编译时强制只读或只写 +``` + +```go !! go +// Go - Channel 方向 +package main + +import "fmt" + +// 只发送 channel (chan<-) +func producer(ch chan<- int) { + ch <- 42 + // value := <-ch // 编译错误! +} + +// 只接收 channel (<-chan) +func consumer(ch <-chan int) { + value := <-ch + fmt.Println(value) + // ch <- 1 // 编译错误! +} + +// 双向 channel (chan) +func inout(ch chan int) { + value := <-ch // OK + ch <- value // OK +} + +func main() { + ch := make(chan int) + + // 可以将双向 channel 传递给只发送或只接收 + go producer(ch) + consumer(ch) +} +``` + + +### 方向强制 + + +```go !! go +// Go - 方向强制防止 bug +package main + +import "fmt" + +// sendOnly - 明确指示此函数只发送 +func sendOnly(ch chan<- int) { + ch <- 1 + // ch <- 2 + // close(ch) // 只能关闭只发送 channel +} + +// receiveOnly - 明确指示此函数只接收 +func receiveOnly(ch <-chan int) { + // 不能关闭只接收 channel! + for value := range ch { + fmt.Println(value) + } +} + +// 优势: +// 1. 文档 - 函数签名显示意图 +// 2. 安全 - 编译器防止误用 +// 3. 清晰所有权 - 谁发送,谁接收 + +func pipeline(numbers <-chan int, results chan<- int) { + // 处理数字,发送到结果 + for n := range numbers { + results <- n * 2 + } + close(results) // 我们拥有发送方 +} + +func main() { + numbers := make(chan int) + results := make(chan int) + + go func() { + for i := 0; i < 5; i++ { + numbers <- i + } + close(numbers) + }() + + go pipeline(numbers, results) + + for result := range results { + fmt.Println(result) + } +} +``` + + +## 关闭 Channel + +关闭表示不再发送值。这允许接收者检测完成。 + + +```python !! py +# Python - 哨兵值或 None +import queue + +def producer(q): + for i in range(5): + q.put(i) + q.put(None) # 哨兵表示完成 + +def consumer(q): + while True: + item = q.get() + if item is None: # 检查哨兵 + break + print(f"Got: {item}") + +q = queue.Queue() +# ... 启动线程 +``` + +```go !! go +// Go - 关闭 channel +package main + +import "fmt" + +func producer(ch chan<- int) { + for i := 0; i < 5; i++ { + ch <- i + } + close(ch) // 信号不再有值 +} + +func consumer(ch <-chan int) { + // range 自动检测关闭 + for value := range ch { + fmt.Printf("Got: %d\n", value) + } + fmt.Println("Consumer done") +} + +func main() { + ch := make(chan int) + + go producer(ch) + consumer(ch) +} +``` + + +### 检测关闭的 Channel + + +```go !! go +// Go - 检测关闭的 channel +package main + +import "fmt" + +func main() { + ch := make(chan int, 2) + ch <- 1 + ch <- 2 + close(ch) + + // 方法 1: range(自动关闭检测) + fmt.Println("Method 1: range") + for value := range ch { + fmt.Println(value) + } + + // 方法 2: 逗号-ok 惯用语 + ch2 := make(chan int, 2) + ch2 <- 1 + ch2 <- 2 + close(ch2) + + fmt.Println("\nMethod 2: comma-ok") + for { + value, ok := <-ch2 + if !ok { + fmt.Println("Channel closed") + break + } + fmt.Printf("Got: %d\n", value) + } + + // 方法 3: select 使用逗号-ok + ch3 := make(chan int, 2) + ch3 <- 1 + close(ch3) + + fmt.Println("\nMethod 3: select") + for { + select { + case value, ok := <-ch3: + if !ok { + fmt.Println("Channel closed in select") + return + } + fmt.Printf("Got: %d\n", value) + } + } +} +``` + + +### 关闭规则 + + +```go !! go +// Go - Channel 关闭规则 +package main + +import "fmt" + +// 规则 1: 只有发送者应该关闭 +func sender(ch chan<- int) { + for i := 0; i < 5; i++ { + ch <- i + } + close(ch) // OK - 我们是发送者 +} + +// 规则 2: 不要在接收方关闭 +func receiver(ch <-chan int) { + // close(ch) // 编译错误! 不能关闭只接收 channel + for value := range ch { + fmt.Println(value) + } +} + +// 规则 3: 只关闭一次(否则 panic) +func closeOnce(ch chan int) { + close(ch) + // close(ch) // Panic: 关闭已关闭的 channel +} + +// 规则 4: 向已关闭 channel 发送会 panic +func sendToClosed(ch chan int) { + close(ch) + // ch <- 1 // Panic: 向已关闭 channel 发送 +} + +// 规则 5: 从已关闭 channel 接收成功 +func receiveFromClosed(ch chan int) { + ch <- 1 + close(ch) + + value, ok := <-ch // 获取缓冲值 + fmt.Printf("Value: %d, OK: %v\n", value, ok) // OK = true + + value, ok = <-ch // 零值,false + fmt.Printf("Value: %d, OK: %v\n", value, ok) // OK = false +} + +// 规则 6: 如果不使用 range 则不必关闭 +func optionalClose(ch chan int) { + // 如果知道接收者会退出,关闭是可选的 + // 但为了清理这是好习惯 +} +``` + + +## 遍历 Channel + +使用 `range` 自动接收直到 channel 关闭。 + + +```python !! py +# Python - 迭代直到哨兵 +import queue + +def consumer(q): + while True: + item = q.get() + if item is None: # 手动检查 + break + process(item) +``` + +```go !! go +// Go - 遍历 channel +package main + +import "fmt" + +func producer(ch chan<- int) { + for i := 0; i < 5; i++ { + ch <- i + } + close(ch) // range 需要关闭才能退出 +} + +func consumer(ch <-chan int) { + // range 自动: + // 1. 从 channel 接收 + // 2. 在 channel 关闭时退出 + for value := range ch { + fmt.Printf("Received: %d\n", value) + } + fmt.Println("Done") +} + +func main() { + ch := make(chan int) + go producer(ch) + consumer(ch) +} +``` + + +## Select 语句 + +`select` 等待多个 channel 操作。它就像 channel 的 switch。 + + +```python !! py +# Python - 无直接等价物 +# 需要复杂的轮询或回调 + +import queue +import select + +def wait_for_multiple(q1, q2): + while True: + # 轮询队列(低效) + readable, _, _ = select.select([q1, q2], [], [], 0.1) + if readable: + if q1 in readable: + handle(q1.get()) + if q2 in readable: + handle(q2.get()) +``` + +```go !! go +// Go - Select 语句 +package main + +import ( + "fmt" + "time" +) + +func main() { + ch1 := make(chan string) + ch2 := make(chan string) + + go func() { + time.Sleep(100 * time.Millisecond) + ch1 <- "one" + }() + + go func() { + time.Sleep(200 * time.Millisecond) + ch2 <- "two" + }() + + // Select 等待任何 case 准备好 + for i := 0; i < 2; i++ { + select { + case msg1 := <-ch1: + fmt.Println("Received from ch1:", msg1) + case msg2 := <-ch2: + fmt.Println("Received from ch2:", msg2) + } + } + + fmt.Println("Done") +} +``` + + +### Select 行为 + + +```go !! go +// Go - Select 语句行为 +package main + +import ( + "fmt" + "time" +) + +func main() { + // 规则 1: 等待直到一个 case 准备好 + ch1 := make(chan string) + ch2 := make(chan string) + + go func() { time.Sleep(100 * time.Millisecond); ch1 <- "first" }() + go func() { time.Sleep(200 * time.Millisecond); ch2 <- "second" }() + + select { + case msg1 := <-ch1: // 这会赢 + fmt.Println(msg1) + case msg2 := <-ch2: + fmt.Println(msg2) + } + + // 规则 2: 如果多个准备好随机选择 + ch3 := make(chan string, 1) + ch4 := make(chan string, 1) + + ch3 <- "ready" + ch4 <- "also ready" + + select { + case msg := <-ch3: + fmt.Println("ch3 won:", msg) // 随机! + case msg := <-ch4: + fmt.Println("ch4 won:", msg) // 随机! + } + + // 规则 3: 如果没有准备好则阻塞 + ch5 := make(chan string) + ch6 := make(chan string) + + // select { + // case msg := <-ch5: // 会永远阻塞! + // fmt.Println(msg) + // case msg := <-ch6: + // fmt.Println(msg) + // } + + // 规则 4: Default case 使其非阻塞 + select { + case msg := <-ch5: + fmt.Println(msg) + case msg := <-ch6: + fmt.Println(msg) + default: + fmt.Println("No channel ready") // 这会执行 + } +} +``` + + +### 带超时的 Select + + +```python !! py +# Python - 使用 queue 的超时 +import queue + +try: + item = q.get(timeout=1.0) + print(item) +except queue.Empty: + print("Timeout") +``` + +```go !! go +// Go - 使用 select 的超时 +package main + +import ( + "context" + "fmt" + "time" +) + +func main() { + ch := make(chan string) + + // 方法 1: time.After + select { + case msg := <-ch: + fmt.Println("Received:", msg) + case <-time.After(time.Second): + fmt.Println("Timeout after 1 second") + } + + // 方法 2: time.NewTicker(用于重复超时) + ticker := time.NewTicker(500 * time.Millisecond) + defer ticker.Stop() + + select { + case msg := <-ch: + fmt.Println("Received:", msg) + case <-ticker.C: + fmt.Println("Timeout after 500ms") + } + + // 方法 3: context(实际代码中首选) + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + + select { + case msg := <-ch: + fmt.Println("Received:", msg) + case <-ctx.Done(): + fmt.Println("Context timeout") + } +} +``` + + +### 非阻塞操作 + + +```python !! py +# Python - 非阻塞队列操作 +import queue + +q = queue.Queue() + +# 非阻塞获取 +try: + item = q.get(block=False) +except queue.Empty: + print("Empty") + +# 非阻塞放入 +try: + q.put(item, block=False) +except queue.Full: + print("Full") +``` + +```go !! go +// Go - 使用 default case 的非阻塞 +package main + +import "fmt" + +func main() { + ch := make(chan int) + + // 非阻塞接收 + select { + case value := <-ch: + fmt.Println("Received:", value) + default: + fmt.Println("No value available") + } + + // 非阻塞发送 + ch2 := make(chan int, 1) + ch2 <- 1 + ch2 <- 2 // 缓冲区满 + + select { + case ch2 <- 3: + fmt.Println("Sent") + default: + fmt.Println("Channel full, can't send") + } + + // 检查 channel 是否有值 + value := 0 + select { + case value = <-ch2: + fmt.Println("Got:", value) + default: + fmt.Println("No value") + } +} +``` + + +## 常见 Channel 模式 + +### Fan-Out: 分发工作 + + +```python !! py +# Python - 工作分发 +import queue +import threading + +work_queue = queue.Queue() + +def worker(id): + while True: + item = work_queue.get() + if item is None: + break + print(f"Worker {id} processing {item}") + +# Fan-out 到多个 worker +for i in range(5): + threading.Thread(target=worker, args=(i,)).start() + +for item in work_items: + work_queue.put(item) +``` + +```go !! go +// Go - Fan-out 模式 +package main + +import ( + "fmt" + "sync" +) + +func worker(id int, jobs <-chan int, wg *sync.WaitGroup) { + defer wg.Done() + for job := range jobs { + fmt.Printf("Worker %d processing job %d\n", id, job) + } +} + +func main() { + jobs := make(chan int, 100) + var wg sync.WaitGroup + + // Fan-out: 分发工作到 5 个 worker + for w := 1; w <= 5; w++ { + wg.Add(1) + go worker(w, jobs, &wg) + } + + // 发送任务 + for j := 1; j <= 10; j++ { + jobs <- j + } + close(jobs) + + wg.Wait() + fmt.Println("All jobs processed") +} +``` + + +### Fan-In: 聚合结果 + + +```python !! py +# Python - 聚合结果 +import queue +import threading + +results = queue.Queue() + +def worker(id, work_queue, results): + for item in iter(work_queue.get, None): + result = process(item) + results.put(result) + +# Fan-in: 多个 worker 到单个结果队列 +workers = [] +for i in range(5): + t = threading.Thread(target=worker, args=(i, work, results)) + workers.append(t) + t.start() + +# 收集结果 +while any(t.is_alive() for t in workers) or not results.empty(): + result = results.get() + process_result(result) +``` + +```go !! go +// Go - Fan-in 模式 +package main + +import ( + "fmt" + "sync" +) + +func worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) { + defer wg.Done() + for job := range jobs { + results <- job * 2 // Fan-in 到单个结果 channel + } +} + +func main() { + jobs := make(chan int, 100) + results := make(chan int, 100) + var wg sync.WaitGroup + + // Fan-out 到 workers + for w := 1; w <= 5; w++ { + wg.Add(1) + go worker(w, jobs, results, &wg) + } + + // 发送任务 + for j := 1; j <= 10; j++ { + jobs <- j + } + close(jobs) + + // 所有 worker 完成时关闭结果 + go func() { + wg.Wait() + close(results) + }() + + // Fan-in: 收集所有结果 + for result := range results { + fmt.Printf("Result: %d\n", result) + } +} +``` + + +### Pipeline: 处理阶段 + + +```python !! py +# Python - 使用队列的 pipeline +import queue + +def generator(out): + for i in range(10): + out.put(i) + out.put(None) // 哨兵 + +def stage1(in_queue, out_queue): + while True: + item = in_queue.get() + if item is None: + out_queue.put(None) + break + out_queue.put(item * 2) + +def stage2(in_queue, out_queue): + while True: + item = in_queue.get() + if item is None: + break + out_queue.put(item + 10) + +# 连接 pipeline +q1 = queue.Queue() +q2 = queue.Queue() +q3 = queue.Queue() + +threading.Thread(target=generator, args=(q1,)).start() +threading.Thread(target=stage1, args=(q1, q2)).start() +threading.Thread(target=stage2, args=(q2, q3)).start() +``` + +```go !! go +// Go - Pipeline 模式 +package main + +import "fmt" + +// 阶段 1: 生成数字 +func generator(out chan<- int) { + for i := 0; i < 10; i++ { + out <- i + } + close(out) +} + +// 阶段 2: 乘以 2 +func multiply(in <-chan int, out chan<- int) { + for n := range in { + out <- n * 2 + } + close(out) +} + +// 阶段 3: 加 10 +func add(in <-chan int, out chan<- int) { + for n := range in { + out <- n + 10 + } + close(out) +} + +func main() { + // 创建 pipeline 阶段 + ch1 := make(chan int) + ch2 := make(chan int) + ch3 := make(chan int) + + // 启动 pipeline + go generator(ch1) + go multiply(ch1, ch2) + go add(ch2, ch3) + + // 消费最终结果 + for result := range ch3 { + fmt.Println(result) + } +} +``` + + +### Or-Channel: 多取消源 + + +```go !! go +// Go - Or-channel: 等待多个 channel +package main + +import ( + "fmt" + "time" +) + +// orChannel 等待多个 channel,任意一个关闭时返回 +func orChannel(channels ...<-chan interface{}) <-chan interface{} { + switch len(channels) { + case 0: + return nil + case 1: + return channels[0] + default: + orDone := make(chan interface{}) + go func() { + defer close(orDone) + switch len(channels) { + case 2: + select { + case <-channels[0]: + case <-channels[1]: + } + default: + select { + case <-channels[0]: + case <-channels[1]: + case <-channels[2]: + case <-orChannel(channels[3:]...): + } + } + }() + return orDone + } +} + +func main() { + sig := func(after time.Duration) <-chan interface{} { + c := make(chan interface{}) + go func() { + defer close(c) + time.Sleep(after) + }() + return c + } + + start := time.Now() + <-orChannel( + sig(2*time.Hour), + sig(5*time.Minute), + sig(1*time.Second), + sig(1*time.Hour), + sig(2*time.Minute), + ) + + fmt.Printf("Done after %v\n", time.Since(start)) +} +``` + + +### Channel 作为信号量 + + +```python !! py +# Python - 使用 threading 的信号量 +import threading + +semaphore = threading.Semaphore(5) # 最多 5 个并发 + +def worker(): + with semaphore: + # 限制为 5 个并发 worker + do_work() +``` + +```go !! go +// Go - Channel 作为信号量 +package main + +import ( + "fmt" + "sync" + "time" +) + +func main() { + // 有缓冲 channel 作为信号量 + // 缓冲大小 = 最大并发操作数 + semaphore := make(chan struct{}, 5) + + var wg sync.WaitGroup + + for i := 0; i < 10; i++ { + wg.Add(1) + go func(id int) { + defer wg.Done() + + // 获取 + semaphore <- struct{}{} + defer func() { <-semaphore }() // 释放 + + // 这里一次只有 5 个 goroutine + fmt.Printf("Worker %d starting\n", id) + time.Sleep(time.Second) + fmt.Printf("Worker %d done\n", id) + }(i) + } + + wg.Wait() +} +``` + + +## 最佳实践 + +### 1. Channel 所有权 + + +```go !! go +// Go - 清晰的 channel 所有权 +package main + +// 好的: 单所有者 +func producer() <-chan int { + ch := make(chan int) + go func() { + defer close(ch) // 所有者关闭 + for i := 0; i < 5; i++ { + ch <- i + } + }() + return ch // 返回只接收给调用者 +} + +func main() { + ch := producer() // 我们只接收 + for value := range ch { + println(value) + } +} + +// 坏的: 所有权不明确 +func producer(ch chan int) { + // 谁关闭? 谁发送? + ch <- 1 +} +``` + + +### 2. 避免 Goroutine 泄漏 + + +```go !! go +// Go - 防止 goroutine 泄漏 +package main + +import "time" + +// 坏的: Goroutine 泄漏 +func leak() { + ch := make(chan int) + go func() { + val := <-ch // 会永远等待! + println(val) + }() + // 函数返回,goroutine 泄漏 +} + +// 好的: 始终有退出条件 +func noLeak() { + ch := make(chan int) + done := make(chan struct{}) + + go func() { + select { + case val := <-ch: + println(val) + case <-done: // 可以退出 + return + } + }() + + // 信号清理 + close(done) +} + +// 好的: 使用 context +func withContext() { + ctx, cancel := context.WithCancel(context.Background()) + ch := make(chan int) + + go func() { + select { + case val := <-ch: + println(val) + case <-ctx.Done(): // 取消时退出 + return + } + }() + + cancel() // 清理 +} +``` + + +### 3. 知道何时使用有缓冲 vs 无缓冲 + + +```go !! go +// Go - 何时使用有缓冲 vs 无缓冲 +package main + +// 使用无缓冲当: +// - 需要同步/握手 +// - 接收者必须在发送者继续前准备好 +// - 想要强制严格排序 + +func syncHandshake() { + ch := make(chan int) // 无缓冲 + + go func() { + ch <- 42 // 阻塞直到接收者准备好 + }() + + val := <-ch // 保证我们准备好 + println(val) +} + +// 使用有缓冲当: +// - 生产者和消费者以不同速率运行 +// - 想要解耦两者 +// - 小缓冲提高吞吐量 + +func asyncProcessing() { + ch := make(chan int, 100) // 有缓冲 + + go func() { + for i := 0; i < 100; i++ { + ch <- i // 不阻塞(有缓冲) + } + close(ch) + }() + + // 消费者以自己的节奏处理 + for val := range ch { + process(val) + } +} + +func process(val int) {} +``` + + +### 4. 只从发送方关闭 + + +```go !! go +// Go - 正确关闭 channel +package main + +// 好的: 发送者关闭 +func producer(ch chan<- int) { + for i := 0; i < 5; i++ { + ch <- i + } + close(ch) // 发送者知道何时完成 +} + +func consumer(ch <-chan int) { + for val := range ch { + println(val) + } + // 不要关闭 - 只是接收者 +} + +// 好的: 如果不关闭要文档化 +func stream(ch chan<- int) { + // 调用者拥有并必须关闭 + for i := 0; i < 5; i++ { + ch <- i + } + // 不关闭 - 在函数文档中说明 +} + +// 坏的: 从接收者关闭 +func badConsumer(ch <-chan int) { + // close(ch) // 反正编译错误 +} +``` + + +### 5. 处理关闭 Channel 的零值 + + +```go !! go +// Go - 关闭 channel 的零值 +package main + +import "fmt" + +func main() { + ch := make(chan int, 2) + ch <- 1 + ch <- 2 + close(ch) + + // 接收首先获取缓冲值 + val1, ok1 := <-ch + fmt.Printf("Value: %d, OK: %v\n", val1, ok1) // 1, true + + val2, ok2 := <-ch + fmt.Printf("Value: %d, OK: %v\n", val2, ok2) // 2, true + + // 后续接收获取零值 + val3, ok3 := <-ch + fmt.Printf("Value: %d, OK: %v\n", val3, ok3) // 0, false + + // 小心零值! + // 如果需要区分"无值"和"零值", + // 使用指针 channel 或检查 ok 布尔值 +} +``` + + +## 总结 + +### 核心概念 + +1. **Channel**: Goroutine 之间的类型化数据管道 +2. **无缓冲**: 同步、阻塞通信 +3. **有缓冲**: 异步、基于容量的通信 +4. **方向**: 只发送、只接收、双向 +5. **关闭**: 信号不再有值,用 range 检测 +6. **Select**: 等待多个 channel 操作 +7. **Range**: 迭代直到 channel 关闭 +8. **Fan-out/Fan-in**: 分发和聚合工作 +9. **Pipeline**: 链式处理阶段 +10. **所有权**: 明确谁发送、接收和关闭 + +### 常见模式 + +- **无缓冲 channel**: 同步、握手 +- **有缓冲 channel**: 异步处理、信号量 +- **Fan-out**: 一个源到多个 worker +- **Fan-in**: 聚合到一个接收者 +- **Pipeline**: 顺序处理阶段 +- **Worker pool**: 有界并发 +- **Or-channel**: 多取消源 + +### 最佳实践 + +1. 优先使用 channel 而非共享内存 +2. 使用无缓冲 channel 进行同步 +3. 使 channel 方向显式 +4. 只从发送方关闭 channel +5. 使用 `range` 接收直到关闭 +6. 使用 `select` 进行多个操作 +7. 始终防止 goroutine 泄漏 +8. 保持缓冲区小 + +### 与 Python 的比较 + +| Python | Go | +|--------|-----| +| `queue.Queue()` | `make(chan Type)` | +| `queue.get()` | `<-ch` | +| `queue.put()` | `ch <-` | +| `queue.Empty` 异常 | 逗号-ok 惯用语 | +| 哨兵值(None) | `close(ch)` | +| `select` 模块 | `select` 语句 | +| 轮询 | 阻塞/select | +| 无类型安全 | 类型化 channel | + +## 练习 + +1. 实现一个 3 阶段的 pipeline: + - 阶段 1: 生成数字 1-100 + - 阶段 2: 过滤偶数 + - 阶段 3: 对数字平方 + - 收集最终结果 + +2. 构建一个 worker pool: + - 有 10 个 worker + - 处理 1000 个任务 + - 通过 channel 返回结果 + - 测量执行时间 + +3. 创建超时模式: + - 操作应在 2 秒内完成 + - 超时返回错误 + - 使用 context 取消 + +4. 实现 fan-in 模式: + - 5 个生产者生成数据 + - 1 个消费者收集所有数据 + - 使用 channel 协调 + +5. 构建限流器: + - 允许每秒 N 次操作 + - 如果超过速率则阻塞 + - 使用 ticker + channel + +## 下一步 + +下一个模块: **Select 与并发模式** - 使用 select 语句和 channel 的高级模式和惯用语。 diff --git a/content/docs/py2go/module-08-channels.zh-tw.mdx b/content/docs/py2go/module-08-channels.zh-tw.mdx new file mode 100644 index 0000000..508947c --- /dev/null +++ b/content/docs/py2go/module-08-channels.zh-tw.mdx @@ -0,0 +1,1524 @@ +--- +title: "Module 8: Channel 與通訊" +description: "Goroutine 之間的安全通訊" +--- + +## 簡介 + +Go 的並行哲學在這個名言中得到了完美體現: + +> **"不要通過共享記憶體來通訊;通過通訊來共享記憶體。"** +> — Go 諺語 + +Go 不使用鎖和共享變數(像 Python 的執行緒那樣),而是使用 **channel** 在 goroutine 之間傳遞資料。這種方法: + +- **從設計上防止競態條件** +- **使資料流在程式碼中顯式可見** +- **鼓勵 goroutine 之間的鬆耦合** +- **遵循 CSP(通訊順序進程)模型** + +## 為什麼使用 Channel 而非共享記憶體? + + +```python !! py +# Python - 使用鎖的共享記憶體 +import threading + +counter = 0 +lock = threading.Lock() + +def increment(): + global counter + with lock: + counter += 1 + +# 問題: 容易忘記鎖 +# 問題: 鎖可能導致死鎖 +# 問題: 難以推理狀態 +``` + +```go !! go +// Go - 通過通訊共享記憶體 +package main + +func increment(out chan<- int) { + out <- 1 +} + +func sum(values <-chan int, result chan<- int) { + total := 0 + for v := range values { + total += v + } + result <- total +} + +// 優勢: +// - 不需要鎖 +// - 資料流顯式 +// - 每個 goroutine 擁有自己的資料 +// - 無競態條件 +``` + + +## Channel 基礎 + +### 建立 Channel + + +```python !! py +# Python - 用於執行緒通訊的 Queue +import queue +import threading + +# 建立 queue +q = queue.Queue() + +def producer(): + for i in range(5): + q.put(i) + print("Producer done") + +def consumer(): + for i in range(5): + value = q.get() + print(f"Got: {value}") + +t1 = threading.Thread(target=producer) +t2 = threading.Thread(target=consumer) +t1.start() +t2.start() +t1.join() +t2.join() +``` + +```go !! go +// Go - Channels +package main + +import ( + "fmt" + "sync" +) + +func main() { + // 建立無緩衝 channel + ch := make(chan int) + + var wg sync.WaitGroup + + // 生產者 + wg.Add(1) + go func() { + defer wg.Done() + for i := 0; i < 5; i++ { + ch <- i // 發送操作 + } + fmt.Println("Producer done") + }() + + // 消費者 + wg.Add(1) + go func() { + defer wg.Done() + for i := 0; i < 5; i++ { + value := <-ch // 接收操作 + fmt.Printf("Got: %d\n", value) + } + }() + + wg.Wait() +} +``` + + +### 無緩衝 Channel + +無緩衝 channel 提供**同步通訊**——發送者和接收者都必須準備好。 + + +```python !! py +# Python - 阻塞佇列(不支援大小為 0) +# Python 佇列總是有緩衝的 +# 使用 threading.Event 進行同步 + +import threading + +ready = threading.Event() +data = None + +def producer(): + global data + data = 42 + ready.set() # 信號資料準備就緒 + +def consumer(): + ready.wait() # 等待信號 + print(f"Got: {data}") + +t1 = threading.Thread(target=producer) +t2 = threading.Thread(target=consumer) +t2.start() +t1.start() +t1.join() +t2.join() +``` + +```go !! go +// Go - 無緩衝 channel(同步) +package main + +import ( + "fmt" + "sync" +) + +func main() { + ch := make(chan int) // 無緩衝 = 同步 + + var wg sync.WaitGroup + + // 如果先啟動生產者,它會阻塞直到接收者準備好 + wg.Add(1) + go func() { + defer wg.Done() + fmt.Println("Producer sending...") + ch <- 42 // 阻塞直到接收者準備好! + fmt.Println("Producer sent") + }() + + // 啟動接收者 + wg.Add(1) + go func() { + defer wg.Done() + fmt.Println("Consumer waiting...") + value := <-ch // 阻塞直到發送者準備好! + fmt.Printf("Consumer got: %d\n", value) + }() + + wg.Wait() + // 兩個 goroutine 必須同時相遇 +} +``` + + +### 有緩衝 Channel + +有緩衝 channel 允許**非同步通訊**——發送者可以在沒有接收者的情況下發送多達緩衝區大小的值。 + + +```python !! py +# Python - 有界佇列 +import queue + +# 最大大小的佇列 +q = queue.Queue(maxsize=5) + +# 生產者可以在沒有消費者的情況下新增最多 5 個項 +for i in range(5): + q.put(i) # 不阻塞 + print(f"Put: {i}") + +# q.put(5) # 這會阻塞(佇列滿) + +print("Queue full!") +``` + +```go !! go +// Go - 有緩衝 channel +package main + +import "fmt" + +func main() { + // 緩衝大小為 5 的 channel + ch := make(chan int, 5) + + // 可以在沒有接收者的情況下發送多達 5 個值 + for i := 0; i < 5; i++ { + ch <- i // 不阻塞 + fmt.Printf("Put: %d\n", i) + } + + // ch <- 5 // 這會阻塞(緩衝區滿) + + fmt.Println("Channel full!") + + // 消費值 + for i := 0; i < 5; i++ { + value := <-ch + fmt.Printf("Got: %d\n", value) + } +} +``` + + +### 緩衝區大小指南 + + +```go !! go +// Go - 緩衝區大小考慮 +package main + +// 無緩衝(大小 0) +// - 同步握手 +// - 保證接收者準備好 +// - 用於協調、同步 +func unbufferedExample() { + ch := make(chan int) // 無緩衝 + ch <- 1 // 阻塞直到接收者 + value := <-ch // 阻塞直到發送者 + _ = value +} + +// 有緩衝(大小 1) +// - 小緩衝 +// - 用於信號量、信號 +// - 允許一個項在傳輸中 +func bufferedOneExample() { + ch := make(chan int, 1) // 緩衝為 1 + ch <- 1 // 不阻塞(緩衝區有空間) + value := <-ch // 獲取緩衝值 + _ = value +} + +// 有緩衝(大小 N) +// - 非同步處理 +// - 解耦生產者/消費者速度 +// - 根據預期負載選擇 +func bufferedNExample() { + // 緩衝 100 - 允許 100 個項排隊 + ch := make(chan int, 100) + + // 生產者可以發送 100 項而不阻塞 + for i := 0; i < 100; i++ { + ch <- i + } + close(ch) + + // 消費者以自己的節奏處理 + for value := range ch { + _ = value + } +} + +// 經驗法則: +// - 從無緩衝開始(同步更安全) +// - 如果測量到競爭再新增緩衝 +// - 保持緩衝區小(通常 10-100) +// - 大緩衝區可能隱藏問題 +``` + + +## Channel 方向 + +Go 在編譯時強制 channel 方向,使資料流顯式並防止錯誤。 + + +```python !! py +# Python - 無編譯時檢查 +def process(queue): + # 可以讀也可以寫 + item = queue.get() + result = process_item(item) + queue.put(result) + +# 容易誤用 +# 無法在編譯時強制只讀或只寫 +``` + +```go !! go +// Go - Channel 方向 +package main + +import "fmt" + +// 只發送 channel (chan<-) +func producer(ch chan<- int) { + ch <- 42 + // value := <-ch // 編譯錯誤! +} + +// 只接收 channel (<-chan) +func consumer(ch <-chan int) { + value := <-ch + fmt.Println(value) + // ch <- 1 // 編譯錯誤! +} + +// 雙向 channel (chan) +func inout(ch chan int) { + value := <-ch // OK + ch <- value // OK +} + +func main() { + ch := make(chan int) + + // 可以將雙向 channel 傳遞給只發送或只接收 + go producer(ch) + consumer(ch) +} +``` + + +### 方向強制 + + +```go !! go +// Go - 方向強制防止 bug +package main + +import "fmt" + +// sendOnly - 明確指示此函數只發送 +func sendOnly(ch chan<- int) { + ch <- 1 + // ch <- 2 + // close(ch) // 只能關閉只發送 channel +} + +// receiveOnly - 明確指示此函數只接收 +func receiveOnly(ch <-chan int) { + // 不能關閉只接收 channel! + for value := range ch { + fmt.Println(value) + } +} + +// 優勢: +// 1. 文檔 - 函數簽章顯示意圖 +// 2. 安全 - 編譯器防止誤用 +// 3. 清晰所有權 - 誰發送,誰接收 + +func pipeline(numbers <-chan int, results chan<- int) { + // 處理數字,發送到結果 + for n := range numbers { + results <- n * 2 + } + close(results) // 我們擁有發送方 +} + +func main() { + numbers := make(chan int) + results := make(chan int) + + go func() { + for i := 0; i < 5; i++ { + numbers <- i + } + close(numbers) + }() + + go pipeline(numbers, results) + + for result := range results { + fmt.Println(result) + } +} +``` + + +## 關閉 Channel + +關閉表示不再發送值。這允許接收者檢測完成。 + + +```python !! py +# Python - 哨兵值或 None +import queue + +def producer(q): + for i in range(5): + q.put(i) + q.put(None) # 哨兵表示完成 + +def consumer(q): + while True: + item = q.get() + if item is None: # 檢查哨兵 + break + print(f"Got: {item}") + +q = queue.Queue() +# ... 啟動執行緒 +``` + +```go !! go +// Go - 關閉 channel +package main + +import "fmt" + +func producer(ch chan<- int) { + for i := 0; i < 5; i++ { + ch <- i + } + close(ch) // 信號不再有值 +} + +func consumer(ch <-chan int) { + // range 自動檢測關閉 + for value := range ch { + fmt.Printf("Got: %d\n", value) + } + fmt.Println("Consumer done") +} + +func main() { + ch := make(chan int) + + go producer(ch) + consumer(ch) +} +``` + + +### 檢測關閉的 Channel + + +```go !! go +// Go - 檢測關閉的 channel +package main + +import "fmt" + +func main() { + ch := make(chan int, 2) + ch <- 1 + ch <- 2 + close(ch) + + // 方法 1: range(自動關閉檢測) + fmt.Println("Method 1: range") + for value := range ch { + fmt.Println(value) + } + + // 方法 2: 逗號-ok 慣用語 + ch2 := make(chan int, 2) + ch2 <- 1 + ch2 <- 2 + close(ch2) + + fmt.Println("\nMethod 2: comma-ok") + for { + value, ok := <-ch2 + if !ok { + fmt.Println("Channel closed") + break + } + fmt.Printf("Got: %d\n", value) + } + + // 方法 3: select 使用逗號-ok + ch3 := make(chan int, 2) + ch3 <- 1 + close(ch3) + + fmt.Println("\nMethod 3: select") + for { + select { + case value, ok := <-ch3: + if !ok { + fmt.Println("Channel closed in select") + return + } + fmt.Printf("Got: %d\n", value) + } + } +} +``` + + +### 關閉規則 + + +```go !! go +// Go - Channel 關閉規則 +package main + +import "fmt" + +// 規則 1: 只有發送者應該關閉 +func sender(ch chan<- int) { + for i := 0; i < 5; i++ { + ch <- i + } + close(ch) // OK - 我們是發送者 +} + +// 規則 2: 不要在接收方關閉 +func receiver(ch <-chan int) { + // close(ch) // 編譯錯誤! 不能關閉只接收 channel + for value := range ch { + fmt.Println(value) + } +} + +// 規則 3: 只關閉一次(否則 panic) +func closeOnce(ch chan int) { + close(ch) + // close(ch) // Panic: 關閉已關閉的 channel +} + +// 規則 4: 向已關閉 channel 發送會 panic +func sendToClosed(ch chan int) { + close(ch) + // ch <- 1 // Panic: 向已關閉 channel 發送 +} + +// 規則 5: 從已關閉 channel 接收成功 +func receiveFromClosed(ch chan int) { + ch <- 1 + close(ch) + + value, ok := <-ch // 獲取緩衝值 + fmt.Printf("Value: %d, OK: %v\n", value, ok) // OK = true + + value, ok = <-ch // 零值,false + fmt.Printf("Value: %d, OK: %v\n", value, ok) // OK = false +} + +// 規則 6: 如果不使用 range 則不必關閉 +func optionalClose(ch chan int) { + // 如果知道接收者會退出,關閉是可選的 + // 但為了清理這是好習慣 +} +``` + + +## 遍歷 Channel + +使用 `range` 自動接收直到 channel 關閉。 + + +```python !! py +# Python - 迭代直到哨兵 +import queue + +def consumer(q): + while True: + item = q.get() + if item is None: # 手動檢查 + break + process(item) +``` + +```go !! go +// Go - 遍歷 channel +package main + +import "fmt" + +func producer(ch chan<- int) { + for i := 0; i < 5; i++ { + ch <- i + } + close(ch) // range 需要關閉才能退出 +} + +func consumer(ch <-chan int) { + // range 自動: + // 1. 從 channel 接收 + // 2. 在 channel 關閉時退出 + for value := range ch { + fmt.Printf("Received: %d\n", value) + } + fmt.Println("Done") +} + +func main() { + ch := make(chan int) + go producer(ch) + consumer(ch) +} +``` + + +## Select 語句 + +`select` 等待多個 channel 操作。它就像 channel 的 switch。 + + +```python !! py +# Python - 無直接等價物 +# 需要複雜的輪詢或回調 + +import queue +import select + +def wait_for_multiple(q1, q2): + while True: + # 輪詢佇列(低效) + readable, _, _ = select.select([q1, q2], [], [], 0.1) + if readable: + if q1 in readable: + handle(q1.get()) + if q2 in readable: + handle(q2.get()) +``` + +```go !! go +// Go - Select 語句 +package main + +import ( + "fmt" + "time" +) + +func main() { + ch1 := make(chan string) + ch2 := make(chan string) + + go func() { + time.Sleep(100 * time.Millisecond) + ch1 <- "one" + }() + + go func() { + time.Sleep(200 * time.Millisecond) + ch2 <- "two" + }() + + // Select 等待任何 case 準備好 + for i := 0; i < 2; i++ { + select { + case msg1 := <-ch1: + fmt.Println("Received from ch1:", msg1) + case msg2 := <-ch2: + fmt.Println("Received from ch2:", msg2) + } + } + + fmt.Println("Done") +} +``` + + +### Select 行為 + + +```go !! go +// Go - Select 語句行為 +package main + +import ( + "fmt" + "time" +) + +func main() { + // 規則 1: 等待直到一個 case 準備好 + ch1 := make(chan string) + ch2 := make(chan string) + + go func() { time.Sleep(100 * time.Millisecond); ch1 <- "first" }() + go func() { time.Sleep(200 * time.Millisecond); ch2 <- "second" }() + + select { + case msg1 := <-ch1: // 這會贏 + fmt.Println(msg1) + case msg2 := <-ch2: + fmt.Println(msg2) + } + + // 規則 2: 如果多個準備好隨機選擇 + ch3 := make(chan string, 1) + ch4 := make(chan string, 1) + + ch3 <- "ready" + ch4 <- "also ready" + + select { + case msg := <-ch3: + fmt.Println("ch3 won:", msg) // 隨機! + case msg := <-ch4: + fmt.Println("ch4 won:", msg) // 隨機! + } + + // 規則 3: 如果沒有準備好則阻塞 + ch5 := make(chan string) + ch6 := make(chan string) + + // select { + // case msg := <-ch5: // 會永遠阻塞! + // fmt.Println(msg) + // case msg := <-ch6: + // fmt.Println(msg) + // } + + // 規則 4: Default case 使其非阻塞 + select { + case msg := <-ch5: + fmt.Println(msg) + case msg := <-ch6: + fmt.Println(msg) + default: + fmt.Println("No channel ready") // 這會執行 + } +} +``` + + +### 帶超時的 Select + + +```python !! py +# Python - 使用 queue 的超時 +import queue + +try: + item = q.get(timeout=1.0) + print(item) +except queue.Empty: + print("Timeout") +``` + +```go !! go +// Go - 使用 select 的超時 +package main + +import ( + "context" + "fmt" + "time" +) + +func main() { + ch := make(chan string) + + // 方法 1: time.After + select { + case msg := <-ch: + fmt.Println("Received:", msg) + case <-time.After(time.Second): + fmt.Println("Timeout after 1 second") + } + + // 方法 2: time.NewTicker(用於重複超時) + ticker := time.NewTicker(500 * time.Millisecond) + defer ticker.Stop() + + select { + case msg := <-ch: + fmt.Println("Received:", msg) + case <-ticker.C: + fmt.Println("Timeout after 500ms") + } + + // 方法 3: context(實際程式碼中首選) + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + + select { + case msg := <-ch: + fmt.Println("Received:", msg) + case <-ctx.Done(): + fmt.Println("Context timeout") + } +} +``` + + +### 非阻塞操作 + + +```python !! py +# Python - 非阻塞佇列操作 +import queue + +q = queue.Queue() + +# 非阻塞獲取 +try: + item = q.get(block=False) +except queue.Empty: + print("Empty") + +# 非阻塞放入 +try: + q.put(item, block=False) +except queue.Full: + print("Full") +``` + +```go !! go +// Go - 使用 default case 的非阻塞 +package main + +import "fmt" + +func main() { + ch := make(chan int) + + // 非阻塞接收 + select { + case value := <-ch: + fmt.Println("Received:", value) + default: + fmt.Println("No value available") + } + + // 非阻塞發送 + ch2 := make(chan int, 1) + ch2 <- 1 + ch2 <- 2 // 緩衝區滿 + + select { + case ch2 <- 3: + fmt.Println("Sent") + default: + fmt.Println("Channel full, can't send") + } + + // 檢查 channel 是否有值 + value := 0 + select { + case value = <-ch2: + fmt.Println("Got:", value) + default: + fmt.Println("No value") + } +} +``` + + +## 常見 Channel 模式 + +### Fan-Out: 分發工作 + + +```python !! py +# Python - 工作分發 +import queue +import threading + +work_queue = queue.Queue() + +def worker(id): + while True: + item = work_queue.get() + if item is None: + break + print(f"Worker {id} processing {item}") + +# Fan-out 到多個 worker +for i in range(5): + threading.Thread(target=worker, args=(i,)).start() + +for item in work_items: + work_queue.put(item) +``` + +```go !! go +// Go - Fan-out 模式 +package main + +import ( + "fmt" + "sync" +) + +func worker(id int, jobs <-chan int, wg *sync.WaitGroup) { + defer wg.Done() + for job := range jobs { + fmt.Printf("Worker %d processing job %d\n", id, job) + } +} + +func main() { + jobs := make(chan int, 100) + var wg sync.WaitGroup + + // Fan-out: 分發工作到 5 個 worker + for w := 1; w <= 5; w++ { + wg.Add(1) + go worker(w, jobs, &wg) + } + + // 發送任務 + for j := 1; j <= 10; j++ { + jobs <- j + } + close(jobs) + + wg.Wait() + fmt.Println("All jobs processed") +} +``` + + +### Fan-In: 聚合結果 + + +```python !! py +# Python - 聚合結果 +import queue +import threading + +results = queue.Queue() + +def worker(id, work_queue, results): + for item in iter(work_queue.get, None): + result = process(item) + results.put(result) + +# Fan-in: 多個 worker 到單個結果佇列 +workers = [] +for i in range(5): + t = threading.Thread(target=worker, args=(i, work, results)) + workers.append(t) + t.start() + +# 收集結果 +while any(t.is_alive() for t in workers) or not results.empty(): + result = results.get() + process_result(result) +``` + +```go !! go +// Go - Fan-in 模式 +package main + +import ( + "fmt" + "sync" +) + +func worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) { + defer wg.Done() + for job := range jobs { + results <- job * 2 // Fan-in 到單個結果 channel + } +} + +func main() { + jobs := make(chan int, 100) + results := make(chan int, 100) + var wg sync.WaitGroup + + // Fan-out 到 workers + for w := 1; w <= 5; w++ { + wg.Add(1) + go worker(w, jobs, results, &wg) + } + + // 發送任務 + for j := 1; j <= 10; j++ { + jobs <- j + } + close(jobs) + + // 所有 worker 完成時關閉結果 + go func() { + wg.Wait() + close(results) + }() + + // Fan-in: 收集所有結果 + for result := range results { + fmt.Printf("Result: %d\n", result) + } +} +``` + + +### Pipeline: 處理階段 + + +```python !! py +# Python - 使用佇列的 pipeline +import queue + +def generator(out): + for i in range(10): + out.put(i) + out.put(None) // 哨兵 + +def stage1(in_queue, out_queue): + while True: + item = in_queue.get() + if item is None: + out_queue.put(None) + break + out_queue.put(item * 2) + +def stage2(in_queue, out_queue): + while True: + item = in_queue.get() + if item is None: + break + out_queue.put(item + 10) + +# 連接 pipeline +q1 = queue.Queue() +q2 = queue.Queue() +q3 = queue.Queue() + +threading.Thread(target=generator, args=(q1,)).start() +threading.Thread(target=stage1, args=(q1, q2)).start() +threading.Thread(target=stage2, args=(q2, q3)).start() +``` + +```go !! go +// Go - Pipeline 模式 +package main + +import "fmt" + +// 階段 1: 生成數字 +func generator(out chan<- int) { + for i := 0; i < 10; i++ { + out <- i + } + close(out) +} + +// 階段 2: 乘以 2 +func multiply(in <-chan int, out chan<- int) { + for n := range in { + out <- n * 2 + } + close(out) +} + +// 階段 3: 加 10 +func add(in <-chan int, out chan<- int) { + for n := range in { + out <- n + 10 + } + close(out) +} + +func main() { + // 建立 pipeline 階段 + ch1 := make(chan int) + ch2 := make(chan int) + ch3 := make(chan int) + + // 啟動 pipeline + go generator(ch1) + go multiply(ch1, ch2) + go add(ch2, ch3) + + // 消費最終結果 + for result := range ch3 { + fmt.Println(result) + } +} +``` + + +### Or-Channel: 多取消源 + + +```go !! go +// Go - Or-channel: 等待多個 channel +package main + +import ( + "fmt" + "time" +) + +// orChannel 等待多個 channel,任意一個關閉時返回 +func orChannel(channels ...<-chan interface{}) <-chan interface{} { + switch len(channels) { + case 0: + return nil + case 1: + return channels[0] + default: + orDone := make(chan interface{}) + go func() { + defer close(orDone) + switch len(channels) { + case 2: + select { + case <-channels[0]: + case <-channels[1]: + } + default: + select { + case <-channels[0]: + case <-channels[1]: + case <-channels[2]: + case <-orChannel(channels[3:]...): + } + } + }() + return orDone + } +} + +func main() { + sig := func(after time.Duration) <-chan interface{} { + c := make(chan interface{}) + go func() { + defer close(c) + time.Sleep(after) + }() + return c + } + + start := time.Now() + <-orChannel( + sig(2*time.Hour), + sig(5*time.Minute), + sig(1*time.Second), + sig(1*time.Hour), + sig(2*time.Minute), + ) + + fmt.Printf("Done after %v\n", time.Since(start)) +} +``` + + +### Channel 作為信號量 + + +```python !! py +# Python - 使用 threading 的信號量 +import threading + +semaphore = threading.Semaphore(5) # 最多 5 個並行 + +def worker(): + with semaphore: + # 限制為 5 個並行 worker + do_work() +``` + +```go !! go +// Go - Channel 作為信號量 +package main + +import ( + "fmt" + "sync" + "time" +) + +func main() { + // 有緩衝 channel 作為信號量 + // 緩衝大小 = 最大並行操作數 + semaphore := make(chan struct{}, 5) + + var wg sync.WaitGroup + + for i := 0; i < 10; i++ { + wg.Add(1) + go func(id int) { + defer wg.Done() + + // 獲取 + semaphore <- struct{}{} + defer func() { <-semaphore }() // 釋放 + + // 這裡一次只有 5 個 goroutine + fmt.Printf("Worker %d starting\n", id) + time.Sleep(time.Second) + fmt.Printf("Worker %d done\n", id) + }(i) + } + + wg.Wait() +} +``` + + +## 最佳實踐 + +### 1. Channel 所有權 + + +```go !! go +// Go - 清晰的 channel 所有權 +package main + +// 好的: 單所有者 +func producer() <-chan int { + ch := make(chan int) + go func() { + defer close(ch) // 所有者關閉 + for i := 0; i < 5; i++ { + ch <- i + } + }() + return ch // 返回只接收給呼叫者 +} + +func main() { + ch := producer() // 我們只接收 + for value := range ch { + println(value) + } +} + +// 壞的: 所有權不明確 +func producer(ch chan int) { + // 誰關閉? 誰發送? + ch <- 1 +} +``` + + +### 2. 避免 Goroutine 泄漏 + + +```go !! go +// Go - 防止 goroutine 泄漏 +package main + +import "time" + +// 壞的: Goroutine 泄漏 +func leak() { + ch := make(chan int) + go func() { + val := <-ch // 會永遠等待! + println(val) + }() + // 函數返回,goroutine 泄漏 +} + +// 好的: 始終有退出條件 +func noLeak() { + ch := make(chan int) + done := make(chan struct{}) + + go func() { + select { + case val := <-ch: + println(val) + case <-done: // 可以退出 + return + } + }() + + // 信號清理 + close(done) +} + +// 好的: 使用 context +func withContext() { + ctx, cancel := context.WithCancel(context.Background()) + ch := make(chan int) + + go func() { + select { + case val := <-ch: + println(val) + case <-ctx.Done(): // 取消時退出 + return + } + }() + + cancel() // 清理 +} +``` + + +### 3. 知道何時使用有緩衝 vs 無緩衝 + + +```go !! go +// Go - 何時使用有緩衝 vs 無緩衝 +package main + +// 使用無緩衝當: +// - 需要同步/握手 +// - 接收者必須在發送者繼續前準備好 +// - 想要強制嚴格排序 + +func syncHandshake() { + ch := make(chan int) // 無緩衝 + + go func() { + ch <- 42 // 阻塞直到接收者準備好 + }() + + val := <-ch // 保證我們準備好 + println(val) +} + +// 使用有緩衝當: +// - 生產者和消費者以不同速率運行 +// - 想要解耦兩者 +// - 小緩衝提高吞吐量 + +func asyncProcessing() { + ch := make(chan int, 100) // 有緩衝 + + go func() { + for i := 0; i < 100; i++ { + ch <- i // 不阻塞(有緩衝) + } + close(ch) + }() + + // 消費者以自己的節奏處理 + for val := range ch { + process(val) + } +} + +func process(val int) {} +``` + + +### 4. 只從發送方關閉 + + +```go !! go +// Go - 正確關閉 channel +package main + +// 好的: 發送者關閉 +func producer(ch chan<- int) { + for i := 0; i < 5; i++ { + ch <- i + } + close(ch) // 發送者知道何時完成 +} + +func consumer(ch <-chan int) { + for val := range ch { + println(val) + } + // 不要關閉 - 只是接收者 +} + +// 好的: 如果不關閉要文檔化 +func stream(ch chan<- int) { + // 呼叫者擁有並必須關閉 + for i := 0; i < 5; i++ { + ch <- i + } + // 不關閉 - 在函數文檔中說明 +} + +// 壞的: 從接收者關閉 +func badConsumer(ch <-chan int) { + // close(ch) // 反正編譯錯誤 +} +``` + + +### 5. 處理關閉 Channel 的零值 + + +```go !! go +// Go - 關閉 channel 的零值 +package main + +import "fmt" + +func main() { + ch := make(chan int, 2) + ch <- 1 + ch <- 2 + close(ch) + + // 接收首先獲取緩衝值 + val1, ok1 := <-ch + fmt.Printf("Value: %d, OK: %v\n", val1, ok1) // 1, true + + val2, ok2 := <-ch + fmt.Printf("Value: %d, OK: %v\n", val2, ok2) // 2, true + + // 後續接收獲取零值 + val3, ok3 := <-ch + fmt.Printf("Value: %d, OK: %v\n", val3, ok3) // 0, false + + // 小心零值! + // 如果需要區分"無值"和"零值", + // 使用指標 channel 或檢查 ok 布林值 +} +``` + + +## 總結 + +### 核心概念 + +1. **Channel**: Goroutine 之間的型別化資料管道 +2. **無緩衝**: 同步、阻塞通訊 +3. **有緩衝**: 非同步、基於容量的通訊 +4. **方向**: 只發送、只接收、雙向 +5. **關閉**: 信號不再有值,用 range 檢測 +6. **Select**: 等待多個 channel 操作 +7. **Range**: 迭代直到 channel 關閉 +8. **Fan-out/Fan-in**: 分發和聚合工作 +9. **Pipeline**: 鏈式處理階段 +10. **所有權**: 明確誰發送、接收和關閉 + +### 常見模式 + +- **無緩衝 channel**: 同步、握手 +- **有緩衝 channel**: 非同步處理、信號量 +- **Fan-out**: 一個源到多個 worker +- **Fan-in**: 聚合到一個接收者 +- **Pipeline**: 順序處理階段 +- **Worker pool**: 有界並行 +- **Or-channel**: 多取消源 + +### 最佳實踐 + +1. 優先使用 channel 而非共享記憶體 +2. 使用無緩衝 channel 進行同步 +3. 使 channel 方向顯式 +4. 只從發送方關閉 channel +5. 使用 `range` 接收直到關閉 +6. 使用 `select` 進行多個操作 +7. 始終防止 goroutine 泄漏 +8. 保持緩衝區小 + +### 與 Python 的比較 + +| Python | Go | +|--------|-----| +| `queue.Queue()` | `make(chan Type)` | +| `queue.get()` | `<-ch` | +| `queue.put()` | `ch <-` | +| `queue.Empty` 異常 | 逗號-ok 慣用語 | +| 哨兵值(None) | `close(ch)` | +| `select` 模組 | `select` 語句 | +| 輪詢 | 阻塞/select | +| 無型別安全 | 型別化 channel | + +## 練習 + +1. 實作一個 3 階段的 pipeline: + - 階段 1: 生成數字 1-100 + - 階段 2: 過濾偶數 + - 階段 3: 對數字平方 + - 收集最終結果 + +2. 構建一個 worker pool: + - 有 10 個 worker + - 處理 1000 個任務 + - 通過 channel 返回結果 + - 測量執行時間 + +3. 建立超時模式: + - 操作應在 2 秒內完成 + - 超時返回錯誤 + - 使用 context 取消 + +4. 實作 fan-in 模式: + - 5 個生產者生成資料 + - 1 個消費者收集所有資料 + - 使用 channel 協調 + +5. 構建限流器: + - 允許每秒 N 次操作 + - 如果超過速率則阻塞 + - 使用 ticker + channel + +## 下一步 + +下一個模組: **Select 與並行模式** - 使用 select 語句和 channel 的高級模式和慣用語。 diff --git a/content/docs/py2go/module-09-select-patterns.mdx b/content/docs/py2go/module-09-select-patterns.mdx new file mode 100644 index 0000000..2b0ea8d --- /dev/null +++ b/content/docs/py2go/module-09-select-patterns.mdx @@ -0,0 +1,1477 @@ +--- +title: "Module 9: Select and Concurrency Patterns" +description: "Advanced concurrency patterns with select" +--- + +## Introduction + +The `select` statement is one of Go's most powerful concurrency primitives. It enables goroutines to wait on multiple communication operations simultaneously, making it possible to implement complex coordination patterns safely and expressively. + +### Select Statement Features + +- **Wait on multiple channels**: Proceed when any channel is ready +- **Non-blocking operations**: Use `default` case +- **Timeout support**: With `time.After` or context +- **Random selection**: When multiple cases are ready +- **Send and receive**: Can include both operations + +## Non-Blocking Operations + +Using `select` with a `default` case enables non-blocking sends and receives. + + +```python !! py +# Python - Non-blocking queue operations +import queue + +q = queue.Queue() + +# Non-blocking get +try: + item = q.get(block=False) + print("Got:", item) +except queue.Empty: + print("No item available") + +# Non-blocking put +try: + q.put(item, block=False) + print("Put successful") +except queue.Full: + print("Queue full") +``` + +```go !! go +// Go - Non-blocking with default case +package main + +import "fmt" + +func main() { + ch := make(chan int) + + // Non-blocking receive + select { + case value := <-ch: + fmt.Println("Received:", value) + default: + fmt.Println("No value available") + } + + // Non-blocking send + ch2 := make(chan int, 1) + ch2 <- 1 + + select { + case ch2 <- 2: + fmt.Println("Sent successfully") + default: + fmt.Println("Channel full, can't send") + } +} +``` + + +### Polling Pattern + + +```python !! py +# Python - Polling multiple queues +import queue +import select + +def poll_multiple(*queues): + readable, _, _ = select.select(queues, [], [], 0) + results = [] + for q in readable: + try: + results.append(q.get(block=False)) + except queue.Empty: + pass + return results +``` + +```go !! go +// Go - Poll with select +package main + +import ( + "fmt" + "time" +) + +func poll(channels ...<-chan int) []int { + var results []int + + for _, ch := range channels { + select { + case val := <-ch: + results = append(results, val) + default: + // Channel not ready, skip + } + } + + return results +} + +func main() { + ch1 := make(chan int, 1) + ch2 := make(chan int, 1) + ch3 := make(chan int) // Unbuffered, no sender + + ch1 <- 1 + // ch2 has no value yet + + results := poll(ch1, ch2, ch3) + fmt.Println("Poll results:", results) // [1] +} +``` + + +## Timeout Patterns + +### Basic Timeout + + +```python !! py +# Python - Timeout with queue +import queue + +try: + item = queue.get(timeout=5.0) + print("Got:", item) +except queue.Empty: + print("Timeout after 5 seconds") +``` + +```go !! go +// Go - Timeout with time.After +package main + +import ( + "fmt" + "time" +) + +func operationWithTimeout(ch <-chan string) { + select { + case result := <-ch: + fmt.Println("Result:", result) + case <-time.After(3 * time.Second): + fmt.Println("Operation timed out") + } +} + +func main() { + ch := make(chan string) + go func() { + time.Sleep(5 * time.Second) + ch <- "done" + }() + + operationWithTimeout(ch) +} +``` + + +### Context Timeout (Preferred) + + +```python !! py +# Python - Timeout with concurrent.futures +from concurrent.futures import ThreadPoolExecutor, as_completed +import time + +def task(): + time.sleep(5) + return "done" + +with ThreadPoolExecutor() as executor: + future = executor.submit(task) + try: + result = future.result(timeout=3) + print(result) + except TimeoutError: + print("Task timed out") +``` + +```go !! go +// Go - Context timeout (preferred) +package main + +import ( + "context" + "fmt" + "time" +) + +func operationWithContext(ctx context.Context) error { + ch := make(chan string) + + go func() { + time.Sleep(5 * time.Second) + select { + case ch <- "done": + case <-ctx.Done(): + return + } + }() + + select { + case result := <-ch: + fmt.Println("Result:", result) + return nil + case <-ctx.Done(): + return ctx.Err() // context.DeadlineExceeded + } +} + +func main() { + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + defer cancel() + + err := operationWithContext(ctx) + if err != nil { + fmt.Println("Error:", err) + } +} +``` + + +### Cancel After First Response + + +```go !! go +// Go - Wait for first response, cancel others +package main + +import ( + "context" + "fmt" + "time" +) + +func query(ctx context.Context, server string, result chan<- string) { + start := time.Now() + defer func() { + fmt.Printf("%s took %v\n", server, time.Since(start)) + }() + + // Simulate variable response time + time.Sleep(time.Duration(server[1]) * 300 * time.Millisecond) + + select { + case result <- fmt.Sprintf("response from %s", server): + case <-ctx.Done(): + fmt.Printf("%s was cancelled\n", server) + } +} + +func main() { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + results := make(chan string) + servers := []string{"s1", "s2", "s3"} + + for _, server := range servers { + go query(ctx, server, results) + } + + // Wait for first result + result := <-results + fmt.Println("First result:", result) + + // Context is cancelled, other queries stop + time.Sleep(time.Second) // Let cancellations complete +} +``` + + +## Heartbeat Pattern + +Heartbeats signal that a long-running operation is still alive. + + +```python !! py +# Python - Heartbeat with threading +import threading +import time + +def worker(heartbeat): + while True: + # Do work + time.sleep(1) + heartbeat.set() # Signal alive + heartbeat.clear() + +heartbeat = threading.Event() +thread = threading.Thread(target=worker, args=(heartbeat,)) +thread.start() + +# Monitor heartbeat +while True: + heartbeat.wait(timeout=5) + if heartbeat.is_set(): + print("Worker is alive") + else: + print("Worker timeout!") + break +``` + +```go !! go +// Go - Heartbeat with ticker +package main + +import ( + "fmt" + "time" +) + +func worker(done <-chan struct{}, heartbeat <-chan time.Time) { + for { + select { + case <-done: + fmt.Println("Worker exiting") + return + case <-heartbeat: + fmt.Println("Worker heartbeat") + // Do some work... + } + } +} + +func monitor(workers int) { + done := make(chan struct{}) + heartbeat := time.NewTicker(500 * time.Millisecond) + defer heartbeat.Stop() + + for i := 0; i < workers; i++ { + go worker(done, heartbeat.C) + } + + // Monitor for 3 seconds + time.Sleep(3 * time.Second) + close(done) + time.Sleep(100 * time.Millisecond) + fmt.Println("Monitor done") +} + +func main() { + monitor(3) +} +``` + + +## Rate Limiting + +Control the rate of operations using ticker and select. + + +```python !! py +# Python - Rate limiting +import time +from collections import deque + +class RateLimiter: + def __init__(self, rate): + self.rate = rate # requests per second + self.tokens = deque() + + def acquire(self): + now = time.time() + # Remove old tokens + while self.tokens and now - self.tokens[0] > 1: + self.tokens.popleft() + + if len(self.tokens) < self.rate: + self.tokens.append(now) + return True + return False + +limiter = RateLimiter(10) # 10 requests per second + +for i in range(20): + if limiter.acquire(): + process_request(i) + else: + time.sleep(0.1) # Wait and retry +``` + +```go !! go +// Go - Rate limiting with ticker +package main + +import ( + "fmt" + "time" +) + +func processRequests(requests []int, rateLimit time.Duration) { + limiter := time.NewTicker(rateLimit) + defer limiter.Stop() + + for i, req := range requests { + <-limiter.C // Wait for rate limit token + fmt.Printf("Processing request %d at %v\n", i, time.Now()) + } +} + +func main() { + requests := make([]int, 10) + for i := range requests { + requests[i] = i + } + + fmt.Println("Processing at 3 requests per second") + processRequests(requests, 333*time.Millisecond) +} +``` + + +### Bursty Rate Limiter + + +```go !! go +// Go - Bursty rate limiter +package main + +import ( + "fmt" + "time" +) + +// Allows bursts but maintains average rate +func burstyLimiter(requests []int) { + // Fill bucket with 3 tokens + bucket := make(chan time.Time, 3) + + // Add tokens at 1 per second + go func() { + ticker := time.NewTicker(time.Second) + defer ticker.Stop() + for t := range ticker.C { + select { + case bucket <- t: + // Token added + default: + // Bucket full, token discarded + } + } + }() + + // Consume tokens + for i, req := range requests { + t := <-bucket // Wait for token + fmt.Printf("Request %d at %v\n", req, t.Format("15:04:05.000")) + } +} + +func main() { + requests := make([]int, 5) + for i := range requests { + requests[i] = i + } + + burstyLimiter(requests) +} +``` + + +## Worker Pool with Shutdown + +Graceful shutdown pattern for worker pools. + + +```python !! py +# Python - Worker pool with shutdown +import threading +import queue + +class WorkerPool: + def __init__(self, num_workers): + self.tasks = queue.Queue() + self.workers = [] + self.shutdown = threading.Event() + + for i in range(num_workers): + t = threading.Thread(target=self.worker, args=(i,)) + t.start() + self.workers.append(t) + + def worker(self, id): + while not self.shutdown.is_set(): + try: + task = self.tasks.get(timeout=0.1) + task() + except queue.Empty: + continue + + def add_task(self, task): + self.tasks.put(task) + + def stop(self): + self.shutdown.set() + for t in self.workers: + t.join() +``` + +```go !! go +// Go - Worker pool with graceful shutdown +package main + +import ( + "context" + "fmt" + "sync" + "time" +) + +func worker(id int, ctx context.Context, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) { + defer wg.Done() + for { + select { + case <-ctx.Done(): + fmt.Printf("Worker %d: shutting down\n", id) + return + case job, ok := <-jobs: + if !ok { + return + } + fmt.Printf("Worker %d: processing job %d\n", id, job) + results <- job * 2 + } + } +} + +func main() { + ctx, cancel := context.WithCancel(context.Background()) + jobs := make(chan int, 100) + results := make(chan int, 100) + + var wg sync.WaitGroup + + // Start workers + for w := 1; w <= 3; w++ { + wg.Add(1) + go worker(w, ctx, jobs, results, &wg) + } + + // Send some jobs + go func() { + for j := 1; j <= 5; j++ { + jobs <- j + } + }() + + // Wait a bit then cancel + time.Sleep(time.Millisecond) + cancel() + close(jobs) + + // Wait for workers to finish + go func() { + wg.Wait() + close(results) + }() + + // Collect results + for result := range results { + fmt.Println("Result:", result) + } + + fmt.Println("All workers shut down") +} +``` + + +## Context Patterns + +### Context Hierarchy + + +```python !! py +# Python - Context with threading.local +import threading + +class Context: + def __init__(self, parent=None): + self.parent = parent + self.data = threading.local() + self.cancelled = False + + def cancel(self): + self.cancelled = True + + def check_cancelled(self): + if self.cancelled: + raise CancelledError() + if self.parent: + self.parent.check_cancelled() + +# Usage +ctx = Context() +child_ctx = Context(parent=ctx) +ctx.cancel() # Cancels both ctx and child_ctx +``` + +```go !! go +// Go - Context hierarchy +package main + +import ( + "context" + "fmt" + "time" +) + +func operation(ctx context.Context, name string) { + for { + select { + case <-ctx.Done(): + fmt.Printf("%s: %v\n", name, ctx.Err()) + return + case <-time.After(500 * time.Millisecond): + fmt.Printf("%s: working...\n", name) + } + } +} + +func main() { + // Parent context with timeout + parent, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + + // Child context inherits parent's deadline + child, cancelChild := context.WithCancel(parent) + defer cancelChild() + + go operation(parent, "parent") + go operation(child, "child") + + time.Sleep(time.Second) + cancelChild() // Cancel child early + + time.Sleep(2 * time.Second) +} +``` + + +### Context With Values + + +```python !! py +# Python - Thread-local context values +import threading +from contextvars import ContextVar + +request_id = ContextVar('request_id', default=None) + +def handle_request(): + rid = request_id.get() + print(f"Handling request {rid}") + +def middleware(): + # Set context value + request_id.set('req-123') + handle_request() +``` + +```go !! go +// Go - Context values +package main + +import ( + "context" + "fmt" +) + +type contextKey string + +const ( + userIDKey contextKey = "userID" + requestIDKey contextKey = "requestID" +) + +func handleRequest(ctx context.Context) { + userID := ctx.Value(userIDKey).(string) + requestID := ctx.Value(requestIDKey).(string) + + fmt.Printf("User: %s, Request: %s\n", userID, requestID) +} + +func main() { + ctx := context.Background() + + // Add values to context + ctx = context.WithValue(ctx, userIDKey, "user-123") + ctx = context.WithValue(ctx, requestIDKey, "req-456") + + handleRequest(ctx) +} +``` + + +## Advanced Patterns + +### Generator Pattern + + +```python !! py +# Python - Generator with yield +def generate_numbers(n): + for i in range(n): + yield i + +def square(numbers): + for n in numbers: + yield n * n + +# Chain generators +result = list(square(generate_numbers(5))) +print(result) # [0, 1, 4, 9, 16] +``` + +```go !! go +// Go - Generator with channels +package main + +import "fmt" + +// Generator function that produces numbers +func generate(n int) <-chan int { + out := make(chan int) + go func() { + defer close(out) + for i := 0; i < n; i++ { + out <- i + } + }() + return out +} + +// Transformer function +func square(in <-chan int) <-chan int { + out := make(chan int) + go func() { + defer close(out) + for n := range in { + out <- n * n + } + }() + return out +} + +func main() { + // Chain generators + for n := range square(generate(5)) { + fmt.Println(n) + } +} +``` + + +### Tee Pattern: Split Channel + + +```python !! py +# Python - Split iterator +import itertools + +def tee(iterable, n=2): + it = iter(iterable) + deques = [collections.deque() for _ in range(n)] + + def gen(dq): + while True: + if not dq: + try: + val = next(it) + except StopIteration: + return + for d in deques: + d.append(val) + yield dq.popleft() + + return [gen(d) for d in deques] + +it1, it2, it3 = tee(range(5), 3) +``` + +```go !! go +// Go - Tee: split channel to multiple +package main + +import ( + "fmt" + "sync" +) + +func tee(in <-chan int, n int) []<-chan int { + outs := make([]chan int, n) + for i := 0; i < n; i++ { + outs[i] = make(chan int) + } + + go func() { + var wg sync.WaitGroup + for _, out := range outs { + wg.Add(1) + go func(ch chan<- int) { + defer wg.Done() + for val := range in { + ch <- val + } + close(ch) + }(out) + } + wg.Wait() + }() + + // Convert to read-only channels + result := make([]<-chan int, n) + for i, out := range outs { + result[i] = out + } + return result +} + +func main() { + in := make(chan int) + go func() { + defer close(in) + for i := 0; i < 5; i++ { + in <- i + } + }() + + // Split to 3 channels + channels := tee(in, 3) + + var wg sync.WaitGroup + for i, ch := range channels { + wg.Add(1) + go func(idx int, c <-chan int) { + defer wg.Done() + for val := range c { + fmt.Printf("Channel %d: %d\n", idx, val) + } + }(i, ch) + } + + wg.Wait() +} +``` + + +### Bridge Pattern: Channel Sequence + + +```go !! go +// Go - Bridge: consume channel of channels +package main + +import "fmt" + +// Bridge takes a channel of channels and returns a single channel +func bridge(done <-chan struct{}, chanStream <-chan <-chan int) <-chan int { + valStream := make(chan int) + go func() { + defer close(valStream) + for { + var stream <-chan int + select { + case maybeStream, ok := <-chanStream: + if !ok { + return + } + stream = maybeStream + case <-done: + return + } + + for val := range orDone(done, stream) { + select { + case valStream <- val: + case <-done: + } + } + } + }() + return valStream +} + +func orDone(done <-chan struct{}, c <-chan int) <-chan int { + valStream := make(chan int) + go func() { + defer close(valStream) + for { + select { + case <-done: + return + case v, ok := <-c: + if !ok { + return + } + select { + case valStream <- v: + case <-done: + } + } + } + }() + return valStream +} + +func main() { + done := make(chan struct{}) + defer close(done) + + chanStream := make(chan (<-chan int)) + go func() { + for i := 0; i < 5; i++ { + ch := make(chan int, 1) + ch <- i + close(ch) + chanStream <- ch + } + close(chanStream) + }() + + for val := range bridge(done, chanStream) { + fmt.Println(val) + } +} +``` + + +## Broadcast Pattern + +Send same message to multiple receivers. + + +```python !! py +# Python - Broadcast with threading +import threading + +def broadcaster(): + while True: + msg = queue.get() + for listener in listeners: + listener.put(msg) +``` + +```go !! go +// Go - Broadcast pattern +package main + +import ( + "fmt" + "sync" +) + +type Broadcaster struct { + mu sync.Mutex + listeners []chan<- string +} + +func (b *Broadcaster) AddListener(ch chan<- string) { + b.mu.Lock() + defer b.mu.Unlock() + b.listeners = append(b.listeners, ch) +} + +func (b *Broadcaster) Broadcast(msg string) { + b.mu.Lock() + defer b.mu.Unlock() + + for _, listener := range b.listeners { + select { + case listener <- msg: + default: + // Channel full, drop message + } + } +} + +func main() { + b := &Broadcaster{} + + // Add listeners + ch1 := make(chan string, 10) + ch2 := make(chan string, 10) + ch3 := make(chan string, 10) + + b.AddListener(ch1) + b.AddListener(ch2) + b.AddListener(ch3) + + // Broadcast messages + messages := []string{"hello", "world", "goodbye"} + for _, msg := range messages { + b.Broadcast(msg) + } + + // Collect messages + close(ch1) + close(ch2) + close(ch3) + + for msg := range ch1 { + fmt.Println("ch1:", msg) + } +} +``` + + +## Dining Philosophers + +Classic concurrency problem solved with select. + + +```python !! py +# Python - Dining philosophers with locks +import threading + +class Philosopher(threading.Thread): + def __init__(self, name, left_fork, right_fork): + super().__init__() + self.name = name + self.left_fork = left_fork + self.right_fork = right_fork + + def run(self): + while True: + with self.left_fork: + with self.right_fork: + self.eat() + self.think() +``` + +```go !! go +// Go - Dining philosophers with select +package main + +import ( + "fmt" + "sync" + "time" +) + +type Philosopher struct { + name string + leftFork *sync.Mutex + rightFork *sync.Mutex +} + +func (p *Philosopher) dine(wg *sync.WaitGroup, seat chan *Philosopher, quit chan struct{}) { + defer wg.Done() + + for { + select { + case seat <- p: // Acquire seat + p.leftFork.Lock() + p.rightFork.Lock() + + fmt.Printf("%s is eating\n", p.name) + time.Sleep(time.Millisecond) + + p.rightFork.Unlock() + p.leftFork.Unlock() + + <-seat // Release seat + fmt.Printf("%s is thinking\n", p.name) + + case <-quit: // Shutdown + return + } + } +} + +func main() { + philosophers := []*Philosopher{ + {"Plato", &sync.Mutex{}, &sync.Mutex{}}, + {"Socrates", &sync.Mutex{}, &sync.Mutex{}}, + {"Aristotle", &sync.Mutex{}, &sync.Mutex{}}, + } + + quit := make(chan struct{}) + seat := make(chan *Philosopher, len(philosophers)-1) + + var wg sync.WaitGroup + for _, p := range philosophers { + wg.Add(1) + go p.dine(&wg, seat, quit) + } + + time.Sleep(time.Second) + close(quit) + wg.Wait() +} +``` + + +## Best Practices + +### 1. Always Include Context in Select + + +```go !! go +// GOOD - Always check context +func worker(ctx context.Context, jobs <-chan int) { + for { + select { + case <-ctx.Done(): + return // Clean exit + case job, ok := <-jobs: + if !ok { + return + } + process(job) + } + } +} + +// BAD - No cancellation +func workerBad(jobs <-chan int) { + for { + select { + case job := <-jobs: + process(job) + // Can't exit cleanly! + } + } +} +``` + + +### 2. Handle Channel Close in Select + + +```go !! go +// GOOD - Check ok to detect close +func receiver(ch <-chan int) { + for { + select { + case val, ok := <-ch: + if !ok { + fmt.Println("Channel closed") + return + } + fmt.Println("Received:", val) + case <-time.After(time.Second): + fmt.Println("Timeout") + } + } +} + +// BAD - Doesn't detect close +func receiverBad(ch <-chan int) { + for { + select { + case val := <-ch: + fmt.Println(val) // Gets zero value on close! + } + } +} +``` + + +### 3. Avoid time.After in Loops + + +```go !! go +// BAD - time.After leaks in loops +func badTicker() { + for { + select { + case <-time.After(time.Second): + fmt.Println("tick") + // time.After creates new goroutine each loop! + } + } +} + +// GOOD - Reuse ticker +func goodTicker() { + ticker := time.NewTicker(time.Second) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + fmt.Println("tick") + } + } +} +``` + + +### 4. Use Default Sparingly + + +```go !! go +// GOOD - Default for non-blocking +func trySend(ch chan<- int, val int) bool { + select { + case ch <- val: + return true + default: + return false + } +} + +// BAD - Busy wait with default +func busyWait(ch <-chan int) int { + for { + select { + case val := <-ch: + return val + default: + // Spins CPU! Very bad! + } + } +} + +// GOOD - Sleep or use timeout +func goodWait(ch <-chan int) int { + for { + select { + case val := <-ch: + return val + case <-time.After(50 * time.Millisecond): + // Yield CPU + } + } +} +``` + + +## Real-World Examples + +### Pub-Sub System + + +```go !! go +// Go - Simple pub-sub +package main + +import ( + "fmt" + "sync" +) + +type Broker struct { + mu sync.RWMutex + topics map[string][]chan string +} + +func NewBroker() *Broker { + return &Broker{ + topics: make(map[string][]chan string), + } +} + +func (b *Broker) Subscribe(topic string) <-chan string { + b.mu.Lock() + defer b.mu.Unlock() + + ch := make(chan string, 10) + b.topics[topic] = append(b.topics[topic], ch) + return ch +} + +func (b *Broker) Publish(topic, msg string) { + b.mu.RLock() + defer b.mu.RUnlock() + + for _, sub := range b.topics[topic] { + go func(ch chan<- string) { + select { + case ch <- msg: + default: + // Subscriber slow, drop + } + }(sub) + } +} + +func main() { + broker := NewBroker() + + // Subscribe to topics + sub1 := broker.Subscribe("news") + sub2 := broker.Subscribe("news") + sub3 := broker.Subscribe("sports") + + // Publish messages + broker.Publish("news", "Breaking: Go is awesome!") + broker.Publish("sports", "Game update: 5-3") + + // Receive + fmt.Println("Sub1:", <-sub1) + fmt.Println("Sub2:", <-sub2) + fmt.Println("Sub3:", <-sub3) +} +``` + + +### Load Balancer + + +```go !! go +// Go - Simple load balancer +package main + +import ( + "fmt" + "sync" +) + +type Server struct { + id int +} + +type LoadBalancer struct { + servers []chan Request + current int + mu sync.Mutex +} + +type Request struct { + URL string + Result chan<- string +} + +func NewLoadBalancer(numServers int) *LoadBalancer { + lb := &LoadBalancer{ + servers: make([]chan Request, numServers), + } + + for i := 0; i < numServers; i++ { + lb.servers[i] = make(chan Request, 10) + s := &Server{id: i} + go s.handle(lb.servers[i]) + } + + return lb +} + +func (s *Server) handle(reqs <-chan Request) { + for req := range reqs { + result := fmt.Sprintf("Server %d handled %s", s.id, req.URL) + req.Result <- result + } +} + +func (lb *LoadBalancer) Balance(req Request) { + lb.mu.Lock() + lb.current = (lb.current + 1) % len(lb.servers) + server := lb.servers[lb.current] + lb.mu.Unlock() + + server <- req +} + +func main() { + lb := NewLoadBalancer(3) + + results := make(chan string, 10) + + for i := 0; i < 10; i++ { + go func(id int) { + req := Request{ + URL: fmt.Sprintf("/request-%d", id), + Result: results, + } + lb.Balance(req) + }(i) + } + + for i := 0; i < 10; i++ { + fmt.Println(<-results) + } +} +``` + + +## Summary + +### Key Concepts + +1. **Non-blocking operations**: Default case for non-blocking I/O +2. **Timeout patterns**: time.After vs context +3. **Heartbeat**: Keep-alive signals +4. **Rate limiting**: Control operation frequency +5. **Worker pools**: Graceful shutdown with context +6. **Context hierarchy**: Cancellation propagation +7. **Generators**: Channel-based sequences +8. **Fan-out/fan-in**: Distribute and aggregate +9. **Pipeline**: Chain processing stages +10. **Broadcast**: One-to-many communication + +### Common Patterns + +- **Or-done**: Multiple cancellation sources +- **Bridge**: Consume channel of channels +- **Tee**: Split channel to multiple +- **Heartbeat**: Liveness signals +- **Rate limiter**: Throttle operations +- **Pub-sub**: Topic-based messaging +- **Load balancer**: Distribute requests +- **Worker pool**: Bounded concurrency + +### Best Practices + +1. Include context in select for cancellation +2. Check `ok` to detect channel close +3. Avoid `time.After` in loops (leaks!) +4. Use default sparingly +5. Close channels only from sender +6. Handle panics in goroutines +7. Prevent goroutine leaks +8. Use defer for cleanup + +### Comparison with Python + +| Python | Go | +|--------|-----| +| `queue.get(block=False)` | `select` with `default` | +| `queue.get(timeout=5)` | `select` with `time.After` | +| `threading.Event` | `context.Context` | +| `select.select` | `select` statement | +| Generator functions | Channel generators | +| Threading primitives | Goroutine patterns | + +## Exercises + +1. Implement a pub-sub system: + - Topics: "news", "sports", "tech" + - Multiple subscribers per topic + - Blocking and non-blocking publish + +2. Build a rate limiter: + - 10 requests per second + - Allow burst of 5 + - Drop or queue excess requests + +3. Create a work queue: + - Multiple producers + - Multiple consumers + - Graceful shutdown + - Result collection + +4. Implement a pipeline: + - Generate → Filter → Transform → Aggregate + - Each stage in separate goroutine + - Context cancellation propagation + +5. Build a load balancer: + - Round-robin distribution + - Handle server failures + - Circuit breaker pattern + +## Next Steps + +Next module: **Testing in Go** - Writing tests, benchmarks, and table-driven tests. diff --git a/content/docs/py2go/module-09-select-patterns.zh-cn.mdx b/content/docs/py2go/module-09-select-patterns.zh-cn.mdx new file mode 100644 index 0000000..9267eca --- /dev/null +++ b/content/docs/py2go/module-09-select-patterns.zh-cn.mdx @@ -0,0 +1,1477 @@ +--- +title: "Module 9: Select 与并发模式" +description: "使用 Select 的高级并发模式" +--- + +## 简介 + +`select` 语句是 Go 最强大的并发原语之一。它使 goroutine 能够同时等待多个通信操作,从而能够安全且富有表现力地实现复杂的协调模式。 + +### Select 语句功能 + +- **等待多个 channel**: 任意 channel 准备好时继续 +- **非阻塞操作**: 使用 `default` case +- **超时支持**: 使用 `time.After` 或 context +- **随机选择**: 多个 case 准备好时 +- **发送和接收**: 可包含两种操作 + +## 非阻塞操作 + +使用带 `default` case 的 `select` 实现非阻塞发送和接收。 + + +```python !! py +# Python - 非阻塞队列操作 +import queue + +q = queue.Queue() + +# 非阻塞获取 +try: + item = q.get(block=False) + print("Got:", item) +except queue.Empty: + print("No item available") + +# 非阻塞放入 +try: + q.put(item, block=False) + print("Put successful") +except queue.Full: + print("Queue full") +``` + +```go !! go +// Go - 使用 default case 的非阻塞 +package main + +import "fmt" + +func main() { + ch := make(chan int) + + // 非阻塞接收 + select { + case value := <-ch: + fmt.Println("Received:", value) + default: + fmt.Println("No value available") + } + + // 非阻塞发送 + ch2 := make(chan int, 1) + ch2 <- 1 + + select { + case ch2 <- 2: + fmt.Println("Sent successfully") + default: + fmt.Println("Channel full, can't send") + } +} +``` + + +### 轮询模式 + + +```python !! py +# Python - 轮询多个队列 +import queue +import select + +def poll_multiple(*queues): + readable, _, _ = select.select(queues, [], [], 0) + results = [] + for q in readable: + try: + results.append(q.get(block=False)) + except queue.Empty: + pass + return results +``` + +```go !! go +// Go - 使用 select 轮询 +package main + +import ( + "fmt" + "time" +) + +func poll(channels ...<-chan int) []int { + var results []int + + for _, ch := range channels { + select { + case val := <-ch: + results = append(results, val) + default: + // Channel 未准备好,跳过 + } + } + + return results +} + +func main() { + ch1 := make(chan int, 1) + ch2 := make(chan int, 1) + ch3 := make(chan int) // 无缓冲,无发送者 + + ch1 <- 1 + // ch2 尚无值 + + results := poll(ch1, ch2, ch3) + fmt.Println("Poll results:", results) // [1] +} +``` + + +## 超时模式 + +### 基本超时 + + +```python !! py +# Python - 使用 queue 的超时 +import queue + +try: + item = queue.get(timeout=5.0) + print("Got:", item) +except queue.Empty: + print("Timeout after 5 seconds") +``` + +```go !! go +// Go - 使用 time.After 的超时 +package main + +import ( + "fmt" + "time" +) + +func operationWithTimeout(ch <-chan string) { + select { + case result := <-ch: + fmt.Println("Result:", result) + case <-time.After(3 * time.Second): + fmt.Println("Operation timed out") + } +} + +func main() { + ch := make(chan string) + go func() { + time.Sleep(5 * time.Second) + ch <- "done" + }() + + operationWithTimeout(ch) +} +``` + + +### Context 超时(首选) + + +```python !! py +# Python - 使用 concurrent.futures 的超时 +from concurrent.futures import ThreadPoolExecutor, as_completed +import time + +def task(): + time.sleep(5) + return "done" + +with ThreadPoolExecutor() as executor: + future = executor.submit(task) + try: + result = future.result(timeout=3) + print(result) + except TimeoutError: + print("Task timed out") +``` + +```go !! go +// Go - Context 超时(首选) +package main + +import ( + "context" + "fmt" + "time" +) + +func operationWithContext(ctx context.Context) error { + ch := make(chan string) + + go func() { + time.Sleep(5 * time.Second) + select { + case ch <- "done": + case <-ctx.Done(): + return + } + }() + + select { + case result := <-ch: + fmt.Println("Result:", result) + return nil + case <-ctx.Done(): + return ctx.Err() // context.DeadlineExceeded + } +} + +func main() { + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + defer cancel() + + err := operationWithContext(ctx) + if err != nil { + fmt.Println("Error:", err) + } +} +``` + + +### 首次响应后取消 + + +```go !! go +// Go - 等待首次响应,取消其他 +package main + +import ( + "context" + "fmt" + "time" +) + +func query(ctx context.Context, server string, result chan<- string) { + start := time.Now() + defer func() { + fmt.Printf("%s took %v\n", server, time.Since(start)) + }() + + // 模拟可变响应时间 + time.Sleep(time.Duration(server[1]) * 300 * time.Millisecond) + + select { + case result <- fmt.Sprintf("response from %s", server): + case <-ctx.Done(): + fmt.Printf("%s was cancelled\n", server) + } +} + +func main() { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + results := make(chan string) + servers := []string{"s1", "s2", "s3"} + + for _, server := range servers { + go query(ctx, server, results) + } + + // 等待首次结果 + result := <-results + fmt.Println("First result:", result) + + // Context 已取消,其他查询停止 + time.Sleep(time.Second) // 让取消完成 +} +``` + + +## 心跳模式 + +心跳信号表示长时间运行的操作仍然存活。 + + +```python !! py +# Python - 使用 threading 的心跳 +import threading +import time + +def worker(heartbeat): + while True: + # 执行工作 + time.sleep(1) + heartbeat.set() # 信号存活 + heartbeat.clear() + +heartbeat = threading.Event() +thread = threading.Thread(target=worker, args=(heartbeat,)) +thread.start() + +# 监控心跳 +while True: + heartbeat.wait(timeout=5) + if heartbeat.is_set(): + print("Worker is alive") + else: + print("Worker timeout!") + break +``` + +```go !! go +// Go - 使用 ticker 的心跳 +package main + +import ( + "fmt" + "time" +) + +func worker(done <-chan struct{}, heartbeat <-chan time.Time) { + for { + select { + case <-done: + fmt.Println("Worker exiting") + return + case <-heartbeat: + fmt.Println("Worker heartbeat") + // 执行一些工作... + } + } +} + +func monitor(workers int) { + done := make(chan struct{}) + heartbeat := time.NewTicker(500 * time.Millisecond) + defer heartbeat.Stop() + + for i := 0; i < workers; i++ { + go worker(done, heartbeat.C) + } + + // 监控 3 秒 + time.Sleep(3 * time.Second) + close(done) + time.Sleep(100 * time.Millisecond) + fmt.Println("Monitor done") +} + +func main() { + monitor(3) +} +``` + + +## 限流 + +使用 ticker 和 select 控制操作速率。 + + +```python !! py +# Python - 限流 +import time +from collections import deque + +class RateLimiter: + def __init__(self, rate): + self.rate = rate # 每秒请求数 + self.tokens = deque() + + def acquire(self): + now = time.time() + # 移除旧令牌 + while self.tokens and now - self.tokens[0] > 1: + self.tokens.popleft() + + if len(self.tokens) < self.rate: + self.tokens.append(now) + return True + return False + +limiter = RateLimiter(10) # 每秒 10 个请求 + +for i in range(20): + if limiter.acquire(): + process_request(i) + else: + time.sleep(0.1) # 等待并重试 +``` + +```go !! go +// Go - 使用 ticker 的限流 +package main + +import ( + "fmt" + "time" +) + +func processRequests(requests []int, rateLimit time.Duration) { + limiter := time.NewTicker(rateLimit) + defer limiter.Stop() + + for i, req := range requests { + <-limiter.C // 等待限流令牌 + fmt.Printf("Processing request %d at %v\n", i, time.Now()) + } +} + +func main() { + requests := make([]int, 10) + for i := range requests { + requests[i] = i + } + + fmt.Println("Processing at 3 requests per second") + processRequests(requests, 333*time.Millisecond) +} +``` + + +### 突发限流器 + + +```go !! go +// Go - 突发限流器 +package main + +import ( + "fmt" + "time" +) + +// 允许突发但维持平均速率 +func burstyLimiter(requests []int) { + // 用 3 个令牌填充桶 + bucket := make(chan time.Time, 3) + + // 每秒添加 1 个令牌 + go func() { + ticker := time.NewTicker(time.Second) + defer ticker.Stop() + for t := range ticker.C { + select { + case bucket <- t: + // 令牌已添加 + default: + // 桶满,丢弃令牌 + } + } + }() + + // 消费令牌 + for i, req := range requests { + t := <-bucket // 等待令牌 + fmt.Printf("Request %d at %v\n", req, t.Format("15:04:05.000")) + } +} + +func main() { + requests := make([]int, 5) + for i := range requests { + requests[i] = i + } + + burstyLimiter(requests) +} +``` + + +## 带关闭的 Worker Pool + +Worker pool 的优雅关闭模式。 + + +```python !! py +# Python - 带关闭的 worker pool +import threading +import queue + +class WorkerPool: + def __init__(self, num_workers): + self.tasks = queue.Queue() + self.workers = [] + self.shutdown = threading.Event() + + for i in range(num_workers): + t = threading.Thread(target=self.worker, args=(i,)) + t.start() + self.workers.append(t) + + def worker(self, id): + while not self.shutdown.is_set(): + try: + task = self.tasks.get(timeout=0.1) + task() + except queue.Empty: + continue + + def add_task(self, task): + self.tasks.put(task) + + def stop(self): + self.shutdown.set() + for t in self.workers: + t.join() +``` + +```go !! go +// Go - 带优雅关闭的 worker pool +package main + +import ( + "context" + "fmt" + "sync" + "time" +) + +func worker(id int, ctx context.Context, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) { + defer wg.Done() + for { + select { + case <-ctx.Done(): + fmt.Printf("Worker %d: shutting down\n", id) + return + case job, ok := <-jobs: + if !ok { + return + } + fmt.Printf("Worker %d: processing job %d\n", id, job) + results <- job * 2 + } + } +} + +func main() { + ctx, cancel := context.WithCancel(context.Background()) + jobs := make(chan int, 100) + results := make(chan int, 100) + + var wg sync.WaitGroup + + // 启动 workers + for w := 1; w <= 3; w++ { + wg.Add(1) + go worker(w, ctx, jobs, results, &wg) + } + + // 发送一些任务 + go func() { + for j := 1; j <= 5; j++ { + jobs <- j + } + }() + + // 等待一会然后取消 + time.Sleep(time.Millisecond) + cancel() + close(jobs) + + // 等待 workers 完成 + go func() { + wg.Wait() + close(results) + }() + + // 收集结果 + for result := range results { + fmt.Println("Result:", result) + } + + fmt.Println("All workers shut down") +} +``` + + +## Context 模式 + +### Context 层次结构 + + +```python !! py +# Python - 使用 threading.local 的 context +import threading + +class Context: + def __init__(self, parent=None): + self.parent = parent + self.data = threading.local() + self.cancelled = False + + def cancel(self): + self.cancelled = True + + def check_cancelled(self): + if self.cancelled: + raise CancelledError() + if self.parent: + self.parent.check_cancelled() + +# 使用 +ctx = Context() +child_ctx = Context(parent=ctx) +ctx.cancel() # 取消 ctx 和 child_ctx +``` + +```go !! go +// Go - Context 层次结构 +package main + +import ( + "context" + "fmt" + "time" +) + +func operation(ctx context.Context, name string) { + for { + select { + case <-ctx.Done(): + fmt.Printf("%s: %v\n", name, ctx.Err()) + return + case <-time.After(500 * time.Millisecond): + fmt.Printf("%s: working...\n", name) + } + } +} + +func main() { + // 带超时的父 context + parent, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + + // 子 context 继承父的截止时间 + child, cancelChild := context.WithCancel(parent) + defer cancelChild() + + go operation(parent, "parent") + go operation(child, "child") + + time.Sleep(time.Second) + cancelChild() // 提前取消子 + + time.Sleep(2 * time.Second) +} +``` + + +### 带值的 Context + + +```python !! py +# Python - 线程本地 context 值 +import threading +from contextvars import ContextVar + +request_id = ContextVar('request_id', default=None) + +def handle_request(): + rid = request_id.get() + print(f"Handling request {rid}") + +def middleware(): + # 设置 context 值 + request_id.set('req-123') + handle_request() +``` + +```go !! go +// Go - Context 值 +package main + +import ( + "context" + "fmt" +) + +type contextKey string + +const ( + userIDKey contextKey = "userID" + requestIDKey contextKey = "requestID" +) + +func handleRequest(ctx context.Context) { + userID := ctx.Value(userIDKey).(string) + requestID := ctx.Value(requestIDKey).(string) + + fmt.Printf("User: %s, Request: %s\n", userID, requestID) +} + +func main() { + ctx := context.Background() + + // 添加值到 context + ctx = context.WithValue(ctx, userIDKey, "user-123") + ctx = context.WithValue(ctx, requestIDKey, "req-456") + + handleRequest(ctx) +} +``` + + +## 高级模式 + +### 生成器模式 + + +```python !! py +# Python - 使用 yield 的生成器 +def generate_numbers(n): + for i in range(n): + yield i + +def square(numbers): + for n in numbers: + yield n * n + +# 链式生成器 +result = list(square(generate_numbers(5))) +print(result) # [0, 1, 4, 9, 16] +``` + +```go !! go +// Go - 使用 channel 的生成器 +package main + +import "fmt" + +// 生成数字的生成器函数 +func generate(n int) <-chan int { + out := make(chan int) + go func() { + defer close(out) + for i := 0; i < n; i++ { + out <- i + } + }() + return out +} + +// 转换器函数 +func square(in <-chan int) <-chan int { + out := make(chan int) + go func() { + defer close(out) + for n := range in { + out <- n * n + } + }() + return out +} + +func main() { + // 链式生成器 + for n := range square(generate(5)) { + fmt.Println(n) + } +} +``` + + +### Tee 模式: 分割 Channel + + +```python !! py +# Python - 分割迭代器 +import itertools + +def tee(iterable, n=2): + it = iter(iterable) + deques = [collections.deque() for _ in range(n)] + + def gen(dq): + while True: + if not dq: + try: + val = next(it) + except StopIteration: + return + for d in deques: + d.append(val) + yield dq.popleft() + + return [gen(d) for d in deques] + +it1, it2, it3 = tee(range(5), 3) +``` + +```go !! go +// Go - Tee: 分割 channel 到多个 +package main + +import ( + "fmt" + "sync" +) + +func tee(in <-chan int, n int) []<-chan int { + outs := make([]chan int, n) + for i := 0; i < n; i++ { + outs[i] = make(chan int) + } + + go func() { + var wg sync.WaitGroup + for _, out := range outs { + wg.Add(1) + go func(ch chan<- int) { + defer wg.Done() + for val := range in { + ch <- val + } + close(ch) + }(out) + } + wg.Wait() + }() + + // 转换为只读 channel + result := make([]<-chan int, n) + for i, out := range outs { + result[i] = out + } + return result +} + +func main() { + in := make(chan int) + go func() { + defer close(in) + for i := 0; i < 5; i++ { + in <- i + } + }() + + // 分割到 3 个 channel + channels := tee(in, 3) + + var wg sync.WaitGroup + for i, ch := range channels { + wg.Add(1) + go func(idx int, c <-chan int) { + defer wg.Done() + for val := range c { + fmt.Printf("Channel %d: %d\n", idx, val) + } + }(i, ch) + } + + wg.Wait() +} +``` + + +### Bridge 模式: Channel 序列 + + +```go !! go +// Go - Bridge: 消费 channel 的 channel +package main + +import "fmt" + +// Bridge 接收 channel 的 channel 并返回单个 channel +func bridge(done <-chan struct{}, chanStream <-chan <-chan int) <-chan int { + valStream := make(chan int) + go func() { + defer close(valStream) + for { + var stream <-chan int + select { + case maybeStream, ok := <-chanStream: + if !ok { + return + } + stream = maybeStream + case <-done: + return + } + + for val := range orDone(done, stream) { + select { + case valStream <- val: + case <-done: + } + } + } + }() + return valStream +} + +func orDone(done <-chan struct{}, c <-chan int) <-chan int { + valStream := make(chan int) + go func() { + defer close(valStream) + for { + select { + case <-done: + return + case v, ok := <-c: + if !ok { + return + } + select { + case valStream <- v: + case <-done: + } + } + } + }() + return valStream +} + +func main() { + done := make(chan struct{}) + defer close(done) + + chanStream := make(chan (<-chan int)) + go func() { + for i := 0; i < 5; i++ { + ch := make(chan int, 1) + ch <- i + close(ch) + chanStream <- ch + } + close(chanStream) + }() + + for val := range bridge(done, chanStream) { + fmt.Println(val) + } +} +``` + + +## 广播模式 + +向多个接收者发送相同消息。 + + +```python !! py +# Python - 使用 threading 的广播 +import threading + +def broadcaster(): + while True: + msg = queue.get() + for listener in listeners: + listener.put(msg) +``` + +```go !! go +// Go - 广播模式 +package main + +import ( + "fmt" + "sync" +) + +type Broadcaster struct { + mu sync.Mutex + listeners []chan<- string +} + +func (b *Broadcaster) AddListener(ch chan<- string) { + b.mu.Lock() + defer b.mu.Unlock() + b.listeners = append(b.listeners, ch) +} + +func (b *Broadcaster) Broadcast(msg string) { + b.mu.Lock() + defer b.mu.Unlock() + + for _, listener := range b.listeners { + select { + case listener <- msg: + default: + // Channel 满,丢弃消息 + } + } +} + +func main() { + b := &Broadcaster{} + + // 添加监听者 + ch1 := make(chan string, 10) + ch2 := make(chan string, 10) + ch3 := make(chan string, 10) + + b.AddListener(ch1) + b.AddListener(ch2) + b.AddListener(ch3) + + // 广播消息 + messages := []string{"hello", "world", "goodbye"} + for _, msg := range messages { + b.Broadcast(msg) + } + + // 收集消息 + close(ch1) + close(ch2) + close(ch3) + + for msg := range ch1 { + fmt.Println("ch1:", msg) + } +} +``` + + +## 哲学家就餐 + +使用 select 解决的经典并发问题。 + + +```python !! py +# Python - 使用锁的哲学家就餐 +import threading + +class Philosopher(threading.Thread): + def __init__(self, name, left_fork, right_fork): + super().__init__() + self.name = name + self.left_fork = left_fork + self.right_fork = right_fork + + def run(self): + while True: + with self.left_fork: + with self.right_fork: + self.eat() + self.think() +``` + +```go !! go +// Go - 使用 select 的哲学家就餐 +package main + +import ( + "fmt" + "sync" + "time" +) + +type Philosopher struct { + name string + leftFork *sync.Mutex + rightFork *sync.Mutex +} + +func (p *Philosopher) dine(wg *sync.WaitGroup, seat chan *Philosopher, quit chan struct{}) { + defer wg.Done() + + for { + select { + case seat <- p: // 获取座位 + p.leftFork.Lock() + p.rightFork.Lock() + + fmt.Printf("%s is eating\n", p.name) + time.Sleep(time.Millisecond) + + p.rightFork.Unlock() + p.leftFork.Unlock() + + <-seat // 释放座位 + fmt.Printf("%s is thinking\n", p.name) + + case <-quit: // 关闭 + return + } + } +} + +func main() { + philosophers := []*Philosopher{ + {"Plato", &sync.Mutex{}, &sync.Mutex{}}, + {"Socrates", &sync.Mutex{}, &sync.Mutex{}}, + {"Aristotle", &sync.Mutex{}, &sync.Mutex{}}, + } + + quit := make(chan struct{}) + seat := make(chan *Philosopher, len(philosophers)-1) + + var wg sync.WaitGroup + for _, p := range philosophers { + wg.Add(1) + go p.dine(&wg, seat, quit) + } + + time.Sleep(time.Second) + close(quit) + wg.Wait() +} +``` + + +## 最佳实践 + +### 1. 始终在 Select 中包含 Context + + +```go !! go +// 好的 - 始终检查 context +func worker(ctx context.Context, jobs <-chan int) { + for { + select { + case <-ctx.Done(): + return // 清理退出 + case job, ok := <-jobs: + if !ok { + return + } + process(job) + } + } +} + +// 坏的 - 无取消 +func workerBad(jobs <-chan int) { + for { + select { + case job := <-jobs: + process(job) + // 无法干净退出! + } + } +} +``` + + +### 2. 在 Select 中处理 Channel 关闭 + + +```go !! go +// 好的 - 检查 ok 以检测关闭 +func receiver(ch <-chan int) { + for { + select { + case val, ok := <-ch: + if !ok { + fmt.Println("Channel closed") + return + } + fmt.Println("Received:", val) + case <-time.After(time.Second): + fmt.Println("Timeout") + } + } +} + +// 坏的 - 不检测关闭 +func receiverBad(ch <-chan int) { + for { + select { + case val := <-ch: + fmt.Println(val) // 关闭时获取零值! + } + } +} +``` + + +### 3. 避免在循环中使用 time.After + + +```go !! go +// 坏的 - time.After 在循环中泄漏 +func badTicker() { + for { + select { + case <-time.After(time.Second): + fmt.Println("tick") + // time.After 每次循环创建新 goroutine! + } + } +} + +// 好的 - 重用 ticker +func goodTicker() { + ticker := time.NewTicker(time.Second) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + fmt.Println("tick") + } + } +} +``` + + +### 4. 谨慎使用 Default + + +```go !! go +// 好的 - Default 用于非阻塞 +func trySend(ch chan<- int, val int) bool { + select { + case ch <- val: + return true + default: + return false + } +} + +// 坏的 - 使用 default 忙等待 +func busyWait(ch <-chan int) int { + for { + select { + case val := <-ch: + return val + default: + // 占用 CPU! 很糟糕! + } + } +} + +// 好的 - Sleep 或使用超时 +func goodWait(ch <-chan int) int { + for { + select { + case val := <-ch: + return val + case <-time.After(50 * time.Millisecond): + // 让出 CPU + } + } +} +``` + + +## 实际示例 + +### 发布-订阅系统 + + +```go !! go +// Go - 简单的 pub-sub +package main + +import ( + "fmt" + "sync" +) + +type Broker struct { + mu sync.RWMutex + topics map[string][]chan string +} + +func NewBroker() *Broker { + return &Broker{ + topics: make(map[string][]chan string), + } +} + +func (b *Broker) Subscribe(topic string) <-chan string { + b.mu.Lock() + defer b.mu.Unlock() + + ch := make(chan string, 10) + b.topics[topic] = append(b.topics[topic], ch) + return ch +} + +func (b *Broker) Publish(topic, msg string) { + b.mu.RLock() + defer b.mu.RUnlock() + + for _, sub := range b.topics[topic] { + go func(ch chan<- string) { + select { + case ch <- msg: + default: + // 订阅者慢,丢弃 + } + }(sub) + } +} + +func main() { + broker := NewBroker() + + // 订阅主题 + sub1 := broker.Subscribe("news") + sub2 := broker.Subscribe("news") + sub3 := broker.Subscribe("sports") + + // 发布消息 + broker.Publish("news", "Breaking: Go is awesome!") + broker.Publish("sports", "Game update: 5-3") + + // 接收 + fmt.Println("Sub1:", <-sub1) + fmt.Println("Sub2:", <-sub2) + fmt.Println("Sub3:", <-sub3) +} +``` + + +### 负载均衡器 + + +```go !! go +// Go - 简单的负载均衡器 +package main + +import ( + "fmt" + "sync" +) + +type Server struct { + id int +} + +type LoadBalancer struct { + servers []chan Request + current int + mu sync.Mutex +} + +type Request struct { + URL string + Result chan<- string +} + +func NewLoadBalancer(numServers int) *LoadBalancer { + lb := &LoadBalancer{ + servers: make([]chan Request, numServers), + } + + for i := 0; i < numServers; i++ { + lb.servers[i] = make(chan Request, 10) + s := &Server{id: i} + go s.handle(lb.servers[i]) + } + + return lb +} + +func (s *Server) handle(reqs <-chan Request) { + for req := range reqs { + result := fmt.Sprintf("Server %d handled %s", s.id, req.URL) + req.Result <- result + } +} + +func (lb *LoadBalancer) Balance(req Request) { + lb.mu.Lock() + lb.current = (lb.current + 1) % len(lb.servers) + server := lb.servers[lb.current] + lb.mu.Unlock() + + server <- req +} + +func main() { + lb := NewLoadBalancer(3) + + results := make(chan string, 10) + + for i := 0; i < 10; i++ { + go func(id int) { + req := Request{ + URL: fmt.Sprintf("/request-%d", id), + Result: results, + } + lb.Balance(req) + }(i) + } + + for i := 0; i < 10; i++ { + fmt.Println(<-results) + } +} +``` + + +## 总结 + +### 核心概念 + +1. **非阻塞操作**: Default case 用于非阻塞 I/O +2. **超时模式**: time.After vs context +3. **心跳**: 存活信号 +4. **限流**: 控制操作频率 +5. **Worker pool**: 使用 context 优雅关闭 +6. **Context 层次结构**: 取消传播 +7. **生成器**: 基于 channel 的序列 +8. **Fan-out/fan-in**: 分发和聚合 +9. **Pipeline**: 链式处理阶段 +10. **广播**: 一对多通信 + +### 常见模式 + +- **Or-done**: 多取消源 +- **Bridge**: 消费 channel 的 channel +- **Tee**: 分割 channel 到多个 +- **心跳**: 存活信号 +- **限流器**: 节流操作 +- **发布-订阅**: 基于主题的消息传递 +- **负载均衡器**: 分发请求 +- **Worker pool**: 有界并发 + +### 最佳实践 + +1. 在 select 中包含 context 以便取消 +2. 检查 `ok` 以检测 channel 关闭 +3. 避免在循环中使用 `time.After`(会泄漏!) +4. 谨慎使用 default +5. 只从发送方关闭 channel +6. 处理 goroutine 中的 panic +7. 防止 goroutine 泄漏 +8. 使用 defer 进行清理 + +### 与 Python 的比较 + +| Python | Go | +|--------|-----| +| `queue.get(block=False)` | 带有 `default` 的 `select` | +| `queue.get(timeout=5)` | 带有 `time.After` 的 `select` | +| `threading.Event` | `context.Context` | +| `select.select` | `select` 语句 | +| 生成器函数 | Channel 生成器 | +| 线程原语 | Goroutine 模式 | + +## 练习 + +1. 实现发布-订阅系统: + - 主题: "news"、"sports"、"tech" + - 每个主题多个订阅者 + - 阻塞和非阻塞发布 + +2. 构建限流器: + - 每秒 10 个请求 + - 允许突发 5 个 + - 丢弃或排队多余请求 + +3. 创建工作队列: + - 多个生产者 + - 多个消费者 + - 优雅关闭 + - 结果收集 + +4. 实现 pipeline: + - 生成 → 过滤 → 转换 → 聚合 + - 每个阶段在单独 goroutine + - Context 取消传播 + +5. 构建负载均衡器: + - 循环分发 + - 处理服务器故障 + - 断路器模式 + +## 下一步 + +下一个模块: **Go 测试** - 编写测试、基准测试和表驱动测试。 diff --git a/content/docs/py2go/module-09-select-patterns.zh-tw.mdx b/content/docs/py2go/module-09-select-patterns.zh-tw.mdx new file mode 100644 index 0000000..ccbd9dd --- /dev/null +++ b/content/docs/py2go/module-09-select-patterns.zh-tw.mdx @@ -0,0 +1,1477 @@ +--- +title: "Module 9: Select 與並行模式" +description: "使用 Select 的高級並行模式" +--- + +## 簡介 + +`select` 語句是 Go 最強大的並行原語之一。它使 goroutine 能夠同時等待多個通訊操作,從而能夠安全且富有表現力地實現複雜的協調模式。 + +### Select 語句功能 + +- **等待多個 channel**: 任意 channel 準備好時繼續 +- **非阻塞操作**: 使用 `default` case +- **逾時支援**: 使用 `time.After` 或 context +- **隨機選擇**: 多個 case 準備好時 +- **發送和接收**: 可包含兩種操作 + +## 非阻塞操作 + +使用帶 `default` case 的 `select` 實現非阻塞發送和接收。 + + +```python !! py +# Python - 非阻塞佇列操作 +import queue + +q = queue.Queue() + +# 非阻塞獲取 +try: + item = q.get(block=False) + print("Got:", item) +except queue.Empty: + print("No item available") + +# 非阻塞放入 +try: + q.put(item, block=False) + print("Put successful") +except queue.Full: + print("Queue full") +``` + +```go !! go +// Go - 使用 default case 的非阻塞 +package main + +import "fmt" + +func main() { + ch := make(chan int) + + // 非阻塞接收 + select { + case value := <-ch: + fmt.Println("Received:", value) + default: + fmt.Println("No value available") + } + + // 非阻塞發送 + ch2 := make(chan int, 1) + ch2 <- 1 + + select { + case ch2 <- 2: + fmt.Println("Sent successfully") + default: + fmt.Println("Channel full, can't send") + } +} +``` + + +### 輪詢模式 + + +```python !! py +# Python - 輪詢多個佇列 +import queue +import select + +def poll_multiple(*queues): + readable, _, _ = select.select(queues, [], [], 0) + results = [] + for q in readable: + try: + results.append(q.get(block=False)) + except queue.Empty: + pass + return results +``` + +```go !! go +// Go - 使用 select 輪詢 +package main + +import ( + "fmt" + "time" +) + +func poll(channels ...<-chan int) []int { + var results []int + + for _, ch := range channels { + select { + case val := <-ch: + results = append(results, val) + default: + // Channel 未準備好,跳過 + } + } + + return results +} + +func main() { + ch1 := make(chan int, 1) + ch2 := make(chan int, 1) + ch3 := make(chan int) // 無緩衝,無發送者 + + ch1 <- 1 + // ch2 尚無值 + + results := poll(ch1, ch2, ch3) + fmt.Println("Poll results:", results) // [1] +} +``` + + +## 逾時模式 + +### 基本逾時 + + +```python !! py +# Python - 使用 queue 的逾時 +import queue + +try: + item = queue.get(timeout=5.0) + print("Got:", item) +except queue.Empty: + print("Timeout after 5 seconds") +``` + +```go !! go +// Go - 使用 time.After 的逾時 +package main + +import ( + "fmt" + "time" +) + +func operationWithTimeout(ch <-chan string) { + select { + case result := <-ch: + fmt.Println("Result:", result) + case <-time.After(3 * time.Second): + fmt.Println("Operation timed out") + } +} + +func main() { + ch := make(chan string) + go func() { + time.Sleep(5 * time.Second) + ch <- "done" + }() + + operationWithTimeout(ch) +} +``` + + +### Context 逾時(首選) + + +```python !! py +# Python - 使用 concurrent.futures 的逾時 +from concurrent.futures import ThreadPoolExecutor, as_completed +import time + +def task(): + time.sleep(5) + return "done" + +with ThreadPoolExecutor() as executor: + future = executor.submit(task) + try: + result = future.result(timeout=3) + print(result) + except TimeoutError: + print("Task timed out") +``` + +```go !! go +// Go - Context 逾時(首選) +package main + +import ( + "context" + "fmt" + "time" +) + +func operationWithContext(ctx context.Context) error { + ch := make(chan string) + + go func() { + time.Sleep(5 * time.Second) + select { + case ch <- "done": + case <-ctx.Done(): + return + } + }() + + select { + case result := <-ch: + fmt.Println("Result:", result) + return nil + case <-ctx.Done(): + return ctx.Err() // context.DeadlineExceeded + } +} + +func main() { + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + defer cancel() + + err := operationWithContext(ctx) + if err != nil { + fmt.Println("Error:", err) + } +} +``` + + +### 首次回應後取消 + + +```go !! go +// Go - 等待首次回應,取消其他 +package main + +import ( + "context" + "fmt" + "time" +) + +func query(ctx context.Context, server string, result chan<- string) { + start := time.Now() + defer func() { + fmt.Printf("%s took %v\n", server, time.Since(start)) + }() + + // 模擬可變回應時間 + time.Sleep(time.Duration(server[1]) * 300 * time.Millisecond) + + select { + case result <- fmt.Sprintf("response from %s", server): + case <-ctx.Done(): + fmt.Printf("%s was cancelled\n", server) + } +} + +func main() { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + results := make(chan string) + servers := []string{"s1", "s2", "s3"} + + for _, server := range servers { + go query(ctx, server, results) + } + + // 等待首次結果 + result := <-results + fmt.Println("First result:", result) + + // Context 已取消,其他查詢停止 + time.Sleep(time.Second) // 讓取消完成 +} +``` + + +## 心跳模式 + +心跳信號表示長時間執行的操作仍然存活。 + + +```python !! py +# Python - 使用 threading 的心跳 +import threading +import time + +def worker(heartbeat): + while True: + # 執行工作 + time.sleep(1) + heartbeat.set() # 信號存活 + heartbeat.clear() + +heartbeat = threading.Event() +thread = threading.Thread(target=worker, args=(heartbeat,)) +thread.start() + +# 監控心跳 +while True: + heartbeat.wait(timeout=5) + if heartbeat.is_set(): + print("Worker is alive") + else: + print("Worker timeout!") + break +``` + +```go !! go +// Go - 使用 ticker 的心跳 +package main + +import ( + "fmt" + "time" +) + +func worker(done <-chan struct{}, heartbeat <-chan time.Time) { + for { + select { + case <-done: + fmt.Println("Worker exiting") + return + case <-heartbeat: + fmt.Println("Worker heartbeat") + // 執行一些工作... + } + } +} + +func monitor(workers int) { + done := make(chan struct{}) + heartbeat := time.NewTicker(500 * time.Millisecond) + defer heartbeat.Stop() + + for i := 0; i < workers; i++ { + go worker(done, heartbeat.C) + } + + // 監控 3 秒 + time.Sleep(3 * time.Second) + close(done) + time.Sleep(100 * time.Millisecond) + fmt.Println("Monitor done") +} + +func main() { + monitor(3) +} +``` + + +## 限流 + +使用 ticker 和 select 控制作業速率。 + + +```python !! py +# Python - 限流 +import time +from collections import deque + +class RateLimiter: + def __init__(self, rate): + self.rate = rate # 每秒請求數 + self.tokens = deque() + + def acquire(self): + now = time.time() + # 移除舊令牌 + while self.tokens and now - self.tokens[0] > 1: + self.tokens.popleft() + + if len(self.tokens) < self.rate: + self.tokens.append(now) + return True + return False + +limiter = RateLimiter(10) # 每秒 10 個請求 + +for i in range(20): + if limiter.acquire(): + process_request(i) + else: + time.sleep(0.1) # 等待並重試 +``` + +```go !! go +// Go - 使用 ticker 的限流 +package main + +import ( + "fmt" + "time" +) + +func processRequests(requests []int, rateLimit time.Duration) { + limiter := time.NewTicker(rateLimit) + defer limiter.Stop() + + for i, req := range requests { + <-limiter.C // 等待限流令牌 + fmt.Printf("Processing request %d at %v\n", i, time.Now()) + } +} + +func main() { + requests := make([]int, 10) + for i := range requests { + requests[i] = i + } + + fmt.Println("Processing at 3 requests per second") + processRequests(requests, 333*time.Millisecond) +} +``` + + +### 突發限流器 + + +```go !! go +// Go - 突發限流器 +package main + +import ( + "fmt" + "time" +) + +// 允許突發但維持平均速率 +func burstyLimiter(requests []int) { + // 用 3 個令牌填充桶 + bucket := make(chan time.Time, 3) + + // 每秒新增 1 個令牌 + go func() { + ticker := time.NewTicker(time.Second) + defer ticker.Stop() + for t := range ticker.C { + select { + case bucket <- t: + // 令牌已新增 + default: + // 桶滿,丟棄令牌 + } + } + }() + + // 消費令牌 + for i, req := range requests { + t := <-bucket // 等待令牌 + fmt.Printf("Request %d at %v\n", req, t.Format("15:04:05.000")) + } +} + +func main() { + requests := make([]int, 5) + for i := range requests { + requests[i] = i + } + + burstyLimiter(requests) +} +``` + + +## 帶關閉的 Worker Pool + +Worker pool 的優雅關閉模式。 + + +```python !! py +# Python - 帶關閉的 worker pool +import threading +import queue + +class WorkerPool: + def __init__(self, num_workers): + self.tasks = queue.Queue() + self.workers = [] + self.shutdown = threading.Event() + + for i in range(num_workers): + t = threading.Thread(target=self.worker, args=(i,)) + t.start() + self.workers.append(t) + + def worker(self, id): + while not self.shutdown.is_set(): + try: + task = self.tasks.get(timeout=0.1) + task() + except queue.Empty: + continue + + def add_task(self, task): + self.tasks.put(task) + + def stop(self): + self.shutdown.set() + for t in self.workers: + t.join() +``` + +```go !! go +// Go - 帶優雅關閉的 worker pool +package main + +import ( + "context" + "fmt" + "sync" + "time" +) + +func worker(id int, ctx context.Context, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) { + defer wg.Done() + for { + select { + case <-ctx.Done(): + fmt.Printf("Worker %d: shutting down\n", id) + return + case job, ok := <-jobs: + if !ok { + return + } + fmt.Printf("Worker %d: processing job %d\n", id, job) + results <- job * 2 + } + } +} + +func main() { + ctx, cancel := context.WithCancel(context.Background()) + jobs := make(chan int, 100) + results := make(chan int, 100) + + var wg sync.WaitGroup + + // 啟動 workers + for w := 1; w <= 3; w++ { + wg.Add(1) + go worker(w, ctx, jobs, results, &wg) + } + + // 發送一些任務 + go func() { + for j := 1; j <= 5; j++ { + jobs <- j + } + }() + + // 等待一會然後取消 + time.Sleep(time.Millisecond) + cancel() + close(jobs) + + // 等待 workers 完成 + go func() { + wg.Wait() + close(results) + }() + + // 收集結果 + for result := range results { + fmt.Println("Result:", result) + } + + fmt.Println("All workers shut down") +} +``` + + +## Context 模式 + +### Context 層次結構 + + +```python !! py +# Python - 使用 threading.local 的 context +import threading + +class Context: + def __init__(self, parent=None): + self.parent = parent + self.data = threading.local() + self.cancelled = False + + def cancel(self): + self.cancelled = True + + def check_cancelled(self): + if self.cancelled: + raise CancelledError() + if self.parent: + self.parent.check_cancelled() + +# 使用 +ctx = Context() +child_ctx = Context(parent=ctx) +ctx.cancel() # 取消 ctx 和 child_ctx +``` + +```go !! go +// Go - Context 層次結構 +package main + +import ( + "context" + "fmt" + "time" +) + +func operation(ctx context.Context, name string) { + for { + select { + case <-ctx.Done(): + fmt.Printf("%s: %v\n", name, ctx.Err()) + return + case <-time.After(500 * time.Millisecond): + fmt.Printf("%s: working...\n", name) + } + } +} + +func main() { + // 帶逾時的父 context + parent, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + + // 子 context 繼承父的截止時間 + child, cancelChild := context.WithCancel(parent) + defer cancelChild() + + go operation(parent, "parent") + go operation(child, "child") + + time.Sleep(time.Second) + cancelChild() // 提前取消子 + + time.Sleep(2 * time.Second) +} +``` + + +### 帶值的 Context + + +```python !! py +# Python - 線程本地 context 值 +import threading +from contextvars import ContextVar + +request_id = ContextVar('request_id', default=None) + +def handle_request(): + rid = request_id.get() + print(f"Handling request {rid}") + +def middleware(): + # 設定 context 值 + request_id.set('req-123') + handle_request() +``` + +```go !! go +// Go - Context 值 +package main + +import ( + "context" + "fmt" +) + +type contextKey string + +const ( + userIDKey contextKey = "userID" + requestIDKey contextKey = "requestID" +) + +func handleRequest(ctx context.Context) { + userID := ctx.Value(userIDKey).(string) + requestID := ctx.Value(requestIDKey).(string) + + fmt.Printf("User: %s, Request: %s\n", userID, requestID) +} + +func main() { + ctx := context.Background() + + // 新增值到 context + ctx = context.WithValue(ctx, userIDKey, "user-123") + ctx = context.WithValue(ctx, requestIDKey, "req-456") + + handleRequest(ctx) +} +``` + + +## 高級模式 + +### 生成器模式 + + +```python !! py +# Python - 使用 yield 的生成器 +def generate_numbers(n): + for i in range(n): + yield i + +def square(numbers): + for n in numbers: + yield n * n + +# 鏈式生成器 +result = list(square(generate_numbers(5))) +print(result) # [0, 1, 4, 9, 16] +``` + +```go !! go +// Go - 使用 channel 的生成器 +package main + +import "fmt" + +// 生成數字的生成器函數 +func generate(n int) <-chan int { + out := make(chan int) + go func() { + defer close(out) + for i := 0; i < n; i++ { + out <- i + } + }() + return out +} + +// 轉換器函數 +func square(in <-chan int) <-chan int { + out := make(chan int) + go func() { + defer close(out) + for n := range in { + out <- n * n + } + }() + return out +} + +func main() { + // 鏈式生成器 + for n := range square(generate(5)) { + fmt.Println(n) + } +} +``` + + +### Tee 模式: 分割 Channel + + +```python !! py +# Python - 分割迭代器 +import itertools + +def tee(iterable, n=2): + it = iter(iterable) + deques = [collections.deque() for _ in range(n)] + + def gen(dq): + while True: + if not dq: + try: + val = next(it) + except StopIteration: + return + for d in deques: + d.append(val) + yield dq.popleft() + + return [gen(d) for d in deques] + +it1, it2, it3 = tee(range(5), 3) +``` + +```go !! go +// Go - Tee: 分割 channel 到多個 +package main + +import ( + "fmt" + "sync" +) + +func tee(in <-chan int, n int) []<-chan int { + outs := make([]chan int, n) + for i := 0; i < n; i++ { + outs[i] = make(chan int) + } + + go func() { + var wg sync.WaitGroup + for _, out := range outs { + wg.Add(1) + go func(ch chan<- int) { + defer wg.Done() + for val := range in { + ch <- val + } + close(ch) + }(out) + } + wg.Wait() + }() + + // 轉換為唯讀 channel + result := make([]<-chan int, n) + for i, out := range outs { + result[i] = out + } + return result +} + +func main() { + in := make(chan int) + go func() { + defer close(in) + for i := 0; i < 5; i++ { + in <- i + } + }() + + // 分割到 3 個 channel + channels := tee(in, 3) + + var wg sync.WaitGroup + for i, ch := range channels { + wg.Add(1) + go func(idx int, c <-chan int) { + defer wg.Done() + for val := range c { + fmt.Printf("Channel %d: %d\n", idx, val) + } + }(i, ch) + } + + wg.Wait() +} +``` + + +### Bridge 模式: Channel 序列 + + +```go !! go +// Go - Bridge: 消費 channel 的 channel +package main + +import "fmt" + +// Bridge 接收 channel 的 channel 並返回單個 channel +func bridge(done <-chan struct{}, chanStream <-chan <-chan int) <-chan int { + valStream := make(chan int) + go func() { + defer close(valStream) + for { + var stream <-chan int + select { + case maybeStream, ok := <-chanStream: + if !ok { + return + } + stream = maybeStream + case <-done: + return + } + + for val := range orDone(done, stream) { + select { + case valStream <- val: + case <-done: + } + } + } + }() + return valStream +} + +func orDone(done <-chan struct{}, c <-chan int) <-chan int { + valStream := make(chan int) + go func() { + defer close(valStream) + for { + select { + case <-done: + return + case v, ok := <-c: + if !ok { + return + } + select { + case valStream <- v: + case <-done: + } + } + } + }() + return valStream +} + +func main() { + done := make(chan struct{}) + defer close(done) + + chanStream := make(chan (<-chan int)) + go func() { + for i := 0; i < 5; i++ { + ch := make(chan int, 1) + ch <- i + close(ch) + chanStream <- ch + } + close(chanStream) + }() + + for val := range bridge(done, chanStream) { + fmt.Println(val) + } +} +``` + + +## 廣播模式 + +向多個接收者發送相同訊息。 + + +```python !! py +# Python - 使用 threading 的廣播 +import threading + +def broadcaster(): + while True: + msg = queue.get() + for listener in listeners: + listener.put(msg) +``` + +```go !! go +// Go - 廣播模式 +package main + +import ( + "fmt" + "sync" +) + +type Broadcaster struct { + mu sync.Mutex + listeners []chan<- string +} + +func (b *Broadcaster) AddListener(ch chan<- string) { + b.mu.Lock() + defer b.mu.Unlock() + b.listeners = append(b.listeners, ch) +} + +func (b *Broadcaster) Broadcast(msg string) { + b.mu.Lock() + defer b.mu.Unlock() + + for _, listener := range b.listeners { + select { + case listener <- msg: + default: + // Channel 滿,丟棄訊息 + } + } +} + +func main() { + b := &Broadcaster{} + + // 新增監聽者 + ch1 := make(chan string, 10) + ch2 := make(chan string, 10) + ch3 := make(chan string, 10) + + b.AddListener(ch1) + b.AddListener(ch2) + b.AddListener(ch3) + + // 廣播訊息 + messages := []string{"hello", "world", "goodbye"} + for _, msg := range messages { + b.Broadcast(msg) + } + + // 收集訊息 + close(ch1) + close(ch2) + close(ch3) + + for msg := range ch1 { + fmt.Println("ch1:", msg) + } +} +``` + + +## 哲學家就餐 + +使用 select 解決的經典並行問題。 + + +```python !! py +# Python - 使用鎖的哲學家就餐 +import threading + +class Philosopher(threading.Thread): + def __init__(self, name, left_fork, right_fork): + super().__init__() + self.name = name + self.left_fork = left_fork + self.right_fork = right_fork + + def run(self): + while True: + with self.left_fork: + with self.right_fork: + self.eat() + self.think() +``` + +```go !! go +// Go - 使用 select 的哲學家就餐 +package main + +import ( + "fmt" + "sync" + "time" +) + +type Philosopher struct { + name string + leftFork *sync.Mutex + rightFork *sync.Mutex +} + +func (p *Philosopher) dine(wg *sync.WaitGroup, seat chan *Philosopher, quit chan struct{}) { + defer wg.Done() + + for { + select { + case seat <- p: // 獲取座位 + p.leftFork.Lock() + p.rightFork.Lock() + + fmt.Printf("%s is eating\n", p.name) + time.Sleep(time.Millisecond) + + p.rightFork.Unlock() + p.leftFork.Unlock() + + <-seat // 釋放座位 + fmt.Printf("%s is thinking\n", p.name) + + case <-quit: // 關閉 + return + } + } +} + +func main() { + philosophers := []*Philosopher{ + {"Plato", &sync.Mutex{}, &sync.Mutex{}}, + {"Socrates", &sync.Mutex{}, &sync.Mutex{}}, + {"Aristotle", &sync.Mutex{}, &sync.Mutex{}}, + } + + quit := make(chan struct{}) + seat := make(chan *Philosopher, len(philosophers)-1) + + var wg sync.WaitGroup + for _, p := range philosophers { + wg.Add(1) + go p.dine(&wg, seat, quit) + } + + time.Sleep(time.Second) + close(quit) + wg.Wait() +} +``` + + +## 最佳實踐 + +### 1. 始終在 Select 中包含 Context + + +```go !! go +// 好的 - 始終檢查 context +func worker(ctx context.Context, jobs <-chan int) { + for { + select { + case <-ctx.Done(): + return // 清理退出 + case job, ok := <-jobs: + if !ok { + return + } + process(job) + } + } +} + +// 壞的 - 無取消 +func workerBad(jobs <-chan int) { + for { + select { + case job := <-jobs: + process(job) + // 無法乾淨退出! + } + } +} +``` + + +### 2. 在 Select 中處理 Channel 關閉 + + +```go !! go +// 好的 - 檢查 ok 以偵測關閉 +func receiver(ch <-chan int) { + for { + select { + case val, ok := <-ch: + if !ok { + fmt.Println("Channel closed") + return + } + fmt.Println("Received:", val) + case <-time.After(time.Second): + fmt.Println("Timeout") + } + } +} + +// 壞的 - 不偵測關閉 +func receiverBad(ch <-chan int) { + for { + select { + case val := <-ch: + fmt.Println(val) // 關閉時獲取零值! + } + } +} +``` + + +### 3. 避免在循環中使用 time.After + + +```go !! go +// 壞的 - time.After 在循環中泄漏 +func badTicker() { + for { + select { + case <-time.After(time.Second): + fmt.Println("tick") + // time.After 每次循環建立新 goroutine! + } + } +} + +// 好的 - 重用 ticker +func goodTicker() { + ticker := time.NewTicker(time.Second) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + fmt.Println("tick") + } + } +} +``` + + +### 4. 謹慎使用 Default + + +```go !! go +// 好的 - Default 用於非阻塞 +func trySend(ch chan<- int, val int) bool { + select { + case ch <- val: + return true + default: + return false + } +} + +// 壞的 - 使用 default 忙等待 +func busyWait(ch <-chan int) int { + for { + select { + case val := <-ch: + return val + default: + // 佔用 CPU! 很糟糕! + } + } +} + +// 好的 - Sleep 或使用逾時 +func goodWait(ch <-chan int) int { + for { + select { + case val := <-ch: + return val + case <-time.After(50 * time.Millisecond): + // 讓出 CPU + } + } +} +``` + + +## 實際示例 + +### 發布-訂閱系統 + + +```go !! go +// Go - 簡單的 pub-sub +package main + +import ( + "fmt" + "sync" +) + +type Broker struct { + mu sync.RWMutex + topics map[string][]chan string +} + +func NewBroker() *Broker { + return &Broker{ + topics: make(map[string][]chan string), + } +} + +func (b *Broker) Subscribe(topic string) <-chan string { + b.mu.Lock() + defer b.mu.Unlock() + + ch := make(chan string, 10) + b.topics[topic] = append(b.topics[topic], ch) + return ch +} + +func (b *Broker) Publish(topic, msg string) { + b.mu.RLock() + defer b.mu.RUnlock() + + for _, sub := range b.topics[topic] { + go func(ch chan<- string) { + select { + case ch <- msg: + default: + // 訂閱者慢,丟棄 + } + }(sub) + } +} + +func main() { + broker := NewBroker() + + // 訂閱主題 + sub1 := broker.Subscribe("news") + sub2 := broker.Subscribe("news") + sub3 := broker.Subscribe("sports") + + // 發布訊息 + broker.Publish("news", "Breaking: Go is awesome!") + broker.Publish("sports", "Game update: 5-3") + + // 接收 + fmt.Println("Sub1:", <-sub1) + fmt.Println("Sub2:", <-sub2) + fmt.Println("Sub3:", <-sub3) +} +``` + + +### 負載均衡器 + + +```go !! go +// Go - 簡單的負載均衡器 +package main + +import ( + "fmt" + "sync" +) + +type Server struct { + id int +} + +type LoadBalancer struct { + servers []chan Request + current int + mu sync.Mutex +} + +type Request struct { + URL string + Result chan<- string +} + +func NewLoadBalancer(numServers int) *LoadBalancer { + lb := &LoadBalancer{ + servers: make([]chan Request, numServers), + } + + for i := 0; i < numServers; i++ { + lb.servers[i] = make(chan Request, 10) + s := &Server{id: i} + go s.handle(lb.servers[i]) + } + + return lb +} + +func (s *Server) handle(reqs <-chan Request) { + for req := range reqs { + result := fmt.Sprintf("Server %d handled %s", s.id, req.URL) + req.Result <- result + } +} + +func (lb *LoadBalancer) Balance(req Request) { + lb.mu.Lock() + lb.current = (lb.current + 1) % len(lb.servers) + server := lb.servers[lb.current] + lb.mu.Unlock() + + server <- req +} + +func main() { + lb := NewLoadBalancer(3) + + results := make(chan string, 10) + + for i := 0; i < 10; i++ { + go func(id int) { + req := Request{ + URL: fmt.Sprintf("/request-%d", id), + Result: results, + } + lb.Balance(req) + }(i) + } + + for i := 0; i < 10; i++ { + fmt.Println(<-results) + } +} +``` + + +## 總結 + +### 核心概念 + +1. **非阻塞操作**: Default case 用於非阻塞 I/O +2. **逾時模式**: time.After vs context +3. **心跳**: 存活信號 +4. **限流**: 控制作業頻率 +5. **Worker pool**: 使用 context 優雅關閉 +6. **Context 層次結構**: 取消傳播 +7. **生成器**: 基於 channel 的序列 +8. **Fan-out/fan-in**: 分發和聚合 +9. **Pipeline**: 鏈式處理階段 +10. **廣播**: 一對多通訊 + +### 常見模式 + +- **Or-done**: 多取消源 +- **Bridge**: 消費 channel 的 channel +- **Tee**: 分割 channel 到多個 +- **心跳**: 存活信號 +- **限流器**: 節流操作 +- **發布-訂閱**: 基於主題的訊息傳遞 +- **負載均衡器**: 分發請求 +- **Worker pool**: 有界並行 + +### 最佳實踐 + +1. 在 select 中包含 context 以便取消 +2. 檢查 `ok` 以偵測 channel 關閉 +3. 避免在循環中使用 `time.After`(會泄漏!) +4. 謹慎使用 default +5. 只從發送方關閉 channel +6. 處理 goroutine 中的 panic +7. 防止 goroutine 泄漏 +8. 使用 defer 進行清理 + +### 與 Python 的比較 + +| Python | Go | +|--------|-----| +| `queue.get(block=False)` | 帶有 `default` 的 `select` | +| `queue.get(timeout=5)` | 帶有 `time.After` 的 `select` | +| `threading.Event` | `context.Context` | +| `select.select` | `select` 語句 | +| 生成器函數 | Channel 生成器 | +| 線程原語 | Goroutine 模式 | + +## 練習 + +1. 實現發布-訂閱系統: + - 主題: "news"、"sports"、"tech" + - 每個主題多個訂閱者 + - 阻塞和非阻塞發布 + +2. 建構限流器: + - 每秒 10 個請求 + - 允許突發 5 個 + - 丟棄或排隊多餘請求 + +3. 建立工作佇列: + - 多個生產者 + - 多個消費者 + - 優雅關閉 + - 結果收集 + +4. 實現 pipeline: + - 生成 → 過濾 → 轉換 → 聚合 + - 每個階段在單獨 goroutine + - Context 取消傳播 + +5. 建構負載均衡器: + - 循環分發 + - 處理伺服器故障 + - 斷路器模式 + +## 下一步 + +下一個模組: **Go 測試** - 撰寫測試、基準測試和表驅動測試。 diff --git a/content/docs/py2go/module-10-web-development.mdx b/content/docs/py2go/module-10-web-development.mdx new file mode 100644 index 0000000..6a98e48 --- /dev/null +++ b/content/docs/py2go/module-10-web-development.mdx @@ -0,0 +1,746 @@ +--- +title: "Module 10: Web Development" +description: "Building web servers and APIs in Go compared to Python frameworks" +--- + +## Introduction + +Go's standard library includes a powerful HTTP server. Unlike Python where you need frameworks like Flask or FastAPI, Go's `net/http` package provides everything needed for production-ready web services. + +## Basic HTTP Server + + +```python !! py +# Python - Flask +from flask import Flask + +app = Flask(__name__) + +@app.route('/') +def hello(): + return "Hello, World!" + +@app.route('/user/') +def user(name): + return f"Hello, {name}!" + +if __name__ == '__main__': + app.run(port=8080) +``` + +```go !! go +// Go - net/http (no framework needed!) +package main + +import ( + "fmt" + "net/http" +) + +func helloHandler(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, "Hello, World!") +} + +func userHandler(w http.ResponseWriter, r *http.Request) { + name := r.URL.Path[len("/user/"):] + fmt.Fprintf(w, "Hello, %s!", name) +} + +func main() { + http.HandleFunc("/", helloHandler) + http.HandleFunc("/user/", userHandler) + + fmt.Println("Server starting on :8080") + http.ListenAndServe(":8080", nil) +} +``` + + +## HTTP Methods and Routing + + +```python !! py +# Python - Flask +from flask import Flask, request, jsonify + +app = Flask(__name__) + +@app.route('/api/user', methods=['GET']) +def get_user(): + return jsonify({"name": "Alice"}) + +@app.route('/api/user', methods=['POST']) +def create_user(): + data = request.get_json() + return jsonify({"created": True}), 201 + +@app.route('/api/user', methods=['PUT', 'PATCH']) +def update_user(): + data = request.get_json() + return jsonify({"updated": True}) + +@app.route('/api/user', methods=['DELETE']) +def delete_user(): + return jsonify({"deleted": True}) +``` + +```go !! go +// Go - HTTP methods +package main + +import ( + "encoding/json" + "fmt" + "net/http" +) + +func userHandler(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case http.MethodGet: + json.NewEncoder(w).Encode(map[string]string{"name": "Alice"}) + + case http.MethodPost: + var data map[string]interface{} + json.NewDecoder(r.Body).Decode(&data) + w.WriteHeader(http.StatusCreated) + json.NewEncoder(w).Encode(map[string]bool{"created": true}) + + case http.MethodPut, http.MethodPatch: + var data map[string]interface{} + json.NewDecoder(r.Body).Decode(&data) + json.NewEncoder(w).Encode(map[string]bool{"updated": true}) + + case http.MethodDelete: + json.NewEncoder(w).Encode(map[string]bool{"deleted": true}) + + default: + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + } +} + +func main() { + http.HandleFunc("/api/user", userHandler) + http.ListenAndServe(":8080", nil) +} +``` + + +## Query Parameters and Forms + + +```python !! py +# Python - Flask +from flask import request + +@app.route('/search') +def search(): + query = request.args.get('q', '') + page = int(request.args.get('page', 1)) + return f"Search: {q}, Page: {page}" + +@app.route('/form', methods=['POST']) +def form(): + name = request.form.get('name') + email = request.form.get('email') + return f"Name: {name}, Email: {email}" +``` + +```go !! go +// Go - Query parameters and forms +package main + +import ( + "fmt" + "net/http" + "strconv" +) + +func searchHandler(w http.ResponseWriter, r *http.Request) { + query := r.URL.Query().Get("q") + pageStr := r.URL.Query().Get("page") + page, _ := strconv.Atoi(pageStr) + if page == 0 { + page = 1 + } + + fmt.Fprintf(w, "Search: %s, Page: %d", query, page) +} + +func formHandler(w http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodPost { + r.ParseForm() + name := r.FormValue("name") + email := r.FormValue("email") + fmt.Fprintf(w, "Name: %s, Email: %s", name, email) + } +} + +func main() { + http.HandleFunc("/search", searchHandler) + http.HandleFunc("/form", formHandler) + http.ListenAndServe(":8080", nil) +} +``` + + +## JSON Request/Response + + +```python !! py +# Python - Flask with JSON +from flask import Flask, request, jsonify + +app = Flask(__name__) + +class User: + def __init__(self, name, email): + self.name = name + self.email = email + +@app.route('/api/user', methods=['POST']) +def create_user(): + data = request.get_json() + user = User(data['name'], data['email']) + return jsonify({ + 'name': user.name, + 'email': user.email + }), 201 +``` + +```go !! go +// Go - JSON with struct tags +package main + +import ( + "encoding/json" + "net/http" +) + +type User struct { + Name string `json:"name"` + Email string `json:"email"` +} + +func createUserHandler(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + var user User + if err := json.NewDecoder(r.Body).Decode(&user); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + json.NewEncoder(w).Encode(user) +} + +func main() { + http.HandleFunc("/api/user", createUserHandler) + http.ListenAndServe(":8080", nil) +} +``` + + +## Path Variables and Routing + + +```python !! py +# Python - Flask path variables +@app.route('/user/') +def profile(username): + return f"Profile: {username}" + +@app.route('/post/') +def show_post(post_id): + return f"Post ID: {post_id}" +``` + +```go !! go +// Go - Manual path parsing (or use a router) +package main + +import ( + "fmt" + "net/http" + "strings" +) + +func profileHandler(w http.ResponseWriter, r *http.Request) { + // Extract username from path + parts := strings.Split(r.URL.Path, "/") + if len(parts) < 3 { + http.Error(w, "User not found", http.StatusNotFound) + return + } + username := parts[2] + fmt.Fprintf(w, "Profile: %s", username) +} + +func main() { + http.HandleFunc("/user/", profileHandler) + http.ListenAndServe(":8080", nil) +} +``` + + +## Middleware + + +```python !! py +# Python - Flask decorators +from functools import wraps +from flask import request, jsonify + +def require_auth(f): + @wraps(f) + def decorated(*args, **kwargs): + token = request.headers.get('Authorization') + if not token or not validate_token(token): + return jsonify({"error": "Unauthorized"}), 401 + return f(*args, **kwargs) + return decorated + +@app.route('/protected') +@require_auth +def protected(): + return jsonify({"message": "Access granted"}) +``` + +```go !! go +// Go - Middleware with closures +package main + +import ( + "fmt" + "net/http" +) + +func requireAuth(next http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + token := r.Header.Get("Authorization") + if token == "" || !validateToken(token) { + http.Error(w, "Unauthorized", http.StatusUnauthorized) + return + } + next(w, r) + } +} + +func validateToken(token string) bool { + return token == "valid-token" +} + +func protectedHandler(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, `{"message": "Access granted"}`) +} + +func loggingMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Printf("%s %s\n", r.Method, r.URL.Path) + next.ServeHTTP(w, r) + }) +} + +func main() { + http.Handle("/protected", requireAuth(protectedHandler)) + + // Chain middleware + handler := loggingMiddleware(http.DefaultServeMux) + http.ListenAndServe(":8080", handler) +} +``` + + +## File Upload + + +```python !! py +# Python - Flask file upload +from flask import Flask, request +from werkzeug.utils import secure_filename + +app = Flask(__name__) +app.config['UPLOAD_FOLDER'] = '/uploads' + +@app.route('/upload', methods=['POST']) +def upload_file(): + if 'file' not in request.files: + return "No file", 400 + + file = request.files['file'] + filename = secure_filename(file.filename) + file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename)) + return "File uploaded" +``` + +```go !! go +// Go - File upload +package main + +import ( + "fmt" + "io" + "net/http" + "os" +) + +func uploadHandler(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + // Parse multipart form (max 10MB) + err := r.ParseMultipartForm(10 << 20) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + file, handler, err := r.FormFile("file") + if err != nil { + http.Error(w, "Error retrieving file", http.StatusBadRequest) + return + } + defer file.Close() + + // Create uploaded file + dst, err := os.Create("uploads/" + handler.Filename) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + defer dst.Close() + + // Copy file + if _, err := io.Copy(dst, file); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + fmt.Fprintf(w, "File uploaded: %s", handler.Filename) +} + +func main() { + http.HandleFunc("/upload", uploadHandler) + http.ListenAndServe(":8080", nil) +} +``` + + +## Using Web Frameworks + +While Go's standard library is sufficient, frameworks add convenience: + +### Popular Go Web Frameworks + +1. **Gin** - Fast, feature-rich +2. **Echo** - Minimalist, high performance +3. **Fiber** - Express-like API +4. **Chi** - Lightweight, composable + + +```python !! py +# Python - Flask +from flask import Flask, jsonify + +app = Flask(__name__) + +@app.route('/api/users/') +def get_user(id): + return jsonify({"id": id, "name": "Alice"}) +``` + +```go !! go +// Go - Gin framework +package main + +import ( + "github.com/gin-gonic/gin" +) + +type User struct { + ID int `json:"id"` + Name string `json:"name"` +} + +func main() { + r := gin.Default() + + r.GET("/api/users/:id", func(c *gin.Context) { + id := c.Param("id") + c.JSON(200, User{ + ID: 1, + Name: "Alice", + }) + }) + + r.Run(":8080") +} +``` + + +## RESTful API Example + + +```python !! py +# Python - Flask RESTful API +from flask import Flask, request, jsonify +from typing import Dict + +app = Flask(__name__) +users: Dict[int, dict] = { + 1: {"id": 1, "name": "Alice", "email": "alice@example.com"}, +} + +@app.route('/api/users', methods=['GET']) +def get_users(): + return jsonify(list(users.values())) + +@app.route('/api/users/', methods=['GET']) +def get_user(user_id): + user = users.get(user_id) + if not user: + return jsonify({"error": "Not found"}), 404 + return jsonify(user) + +@app.route('/api/users', methods=['POST']) +def create_user(): + data = request.get_json() + user_id = max(users.keys()) + 1 + data['id'] = user_id + users[user_id] = data + return jsonify(data), 201 +``` + +```go !! go +// Go - RESTful API +package main + +import ( + "encoding/json" + "net/http" + "strconv" + "sync" +) + +type User struct { + ID int `json:"id"` + Name string `json:"name"` + Email string `json:"email"` +} + +var ( + users = map[int]User{ + 1: {ID: 1, Name: "Alice", Email: "alice@example.com"}, + } + mu sync.RWMutex + nextID = 2 +) + +func getUsersHandler(w http.ResponseWriter, r *http.Request) { + mu.RLock() + defer mu.RUnlock() + + userList := make([]User, 0, len(users)) + for _, user := range users { + userList = append(userList, user) + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(userList) +} + +func getUserHandler(w http.ResponseWriter, r *http.Request) { + idStr := r.URL.Path[len("/api/users/"):] + id, err := strconv.Atoi(idStr) + if err != nil { + http.Error(w, "Invalid user ID", http.StatusBadRequest) + return + } + + mu.RLock() + user, exists := users[id] + mu.RUnlock() + + if !exists { + http.Error(w, "User not found", http.StatusNotFound) + return + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(user) +} + +func createUserHandler(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + var user User + if err := json.NewDecoder(r.Body).Decode(&user); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + mu.Lock() + user.ID = nextID + nextID++ + users[user.ID] = user + mu.Unlock() + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + json.NewEncoder(w).Encode(user) +} + +func main() { + http.HandleFunc("/api/users", getUsersHandler) + http.HandleFunc("/api/users/", getUserHandler) + http.HandleFunc("/api/users", createUserHandler) + + fmt.Println("Server starting on :8080") + http.ListenAndServe(":8080", nil) +} +``` + + +## HTTP Client + + +```python !! py +# Python - requests library +import requests + +response = requests.get('https://api.example.com/data') +data = response.json() + +response = requests.post( + 'https://api.example.com/users', + json={'name': 'Alice'}, + headers={'Authorization': 'Bearer token'} +) +``` + +```go !! go +// Go - net/http client +package main + +import ( + "encoding/json" + "fmt" + "io" + "net/http" +) + +func main() { + // GET request + resp, err := http.Get("https://api.example.com/data") + if err != nil { + panic(err) + } + defer resp.Body.Close() + + body, _ := io.ReadAll(resp.Body) + fmt.Println(string(body)) + + // POST request + data := map[string]string{"name": "Alice"} + jsonData, _ := json.Marshal(data) + + req, _ := http.NewRequest( + "POST", + "https://api.example.com/users", + jsonData, + ) + req.Header.Set("Authorization", "Bearer token") + req.Header.Set("Content-Type", "application/json") + + client := &http.Client{} + resp2, err := client.Do(req) + if err != nil { + panic(err) + } + defer resp2.Body.Close() +} +``` + + +## Performance Comparison + + +```python !! py +# Python Flask +# Typical: 500-2,000 requests/second +# Memory: 50-100 MB + +from flask import Flask +app = Flask(__name__) + +@app.route('/') +def index(): + return "Hello" + +app.run(threaded=True) +``` + +```go !! go +// Go net/http +// Typical: 10,000-50,000+ requests/second +// Memory: 5-20 MB + +package main + +import "net/http" + +func handler(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("Hello")) +} + +func main() { + http.HandleFunc("/", handler) + http.ListenAndServe(":8080", nil) +} +``` + + +## Summary + +In this module, you learned: + +1. **net/http** - Powerful HTTP server in standard library +2. **Routing** - HandleFunc and manual routing +3. **JSON** - Encoding/decoding with struct tags +4. **Middleware** - Composable middleware chains +5. **File uploads** - Multipart form handling +6. **Web frameworks** - Gin, Echo, Chi for convenience +7. **REST APIs** - Complete CRUD operations +8. **HTTP client** - Making external requests +9. **Performance** - Go significantly faster than Python + +## Key Differences + +| Python | Go | +|--------|-----| +| Flask/Django needed | Standard library sufficient | +| `@app.route` decorator | `http.HandleFunc` | +| `request.get_json()` | `json.NewDecoder(r.Body)` | +| `@require_auth` | Middleware functions | +| 500-2,000 req/s | 10,000-50,000+ req/s | + +## Exercises + +1. Build a complete REST API with CRUD operations +2. Implement authentication middleware +3. Add file upload functionality +4. Create a WebSocket server +5. Compare performance with your Python applications + +## Next Steps + +Next module: **Testing and Debugging** - Writing tests and debugging Go code. diff --git a/content/docs/py2go/module-10-web-development.zh-cn.mdx b/content/docs/py2go/module-10-web-development.zh-cn.mdx new file mode 100644 index 0000000..a2784fa --- /dev/null +++ b/content/docs/py2go/module-10-web-development.zh-cn.mdx @@ -0,0 +1,746 @@ +--- +title: "模块 10: Web 开发" +description: "使用 Go 构建Web 服务器和 API,与 Python 框架对比" +--- + +## 简介 + +Go 的标准库包含强大的 HTTP 服务器。与 Python 需要使用 Flask 或 FastAPI 等框架不同,Go 的 `net/http` 包提供了生产级 Web 服务所需的一切。 + +## 基本 HTTP 服务器 + + +```python !! py +# Python - Flask +from flask import Flask + +app = Flask(__name__) + +@app.route('/') +def hello(): + return "Hello, World!" + +@app.route('/user/') +def user(name): + return f"Hello, {name}!" + +if __name__ == '__main__': + app.run(port=8080) +``` + +```go !! go +// Go - net/http (无需框架!) +package main + +import ( + "fmt" + "net/http" +) + +func helloHandler(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, "Hello, World!") +} + +func userHandler(w http.ResponseWriter, r *http.Request) { + name := r.URL.Path[len("/user/"):] + fmt.Fprintf(w, "Hello, %s!", name) +} + +func main() { + http.HandleFunc("/", helloHandler) + http.HandleFunc("/user/", userHandler) + + fmt.Println("Server starting on :8080") + http.ListenAndServe(":8080", nil) +} +``` + + +## HTTP 方法和路由 + + +```python !! py +# Python - Flask +from flask import Flask, request, jsonify + +app = Flask(__name__) + +@app.route('/api/user', methods=['GET']) +def get_user(): + return jsonify({"name": "Alice"}) + +@app.route('/api/user', methods=['POST']) +def create_user(): + data = request.get_json() + return jsonify({"created": True}), 201 + +@app.route('/api/user', methods=['PUT', 'PATCH']) +def update_user(): + data = request.get_json() + return jsonify({"updated": True}) + +@app.route('/api/user', methods=['DELETE']) +def delete_user(): + return jsonify({"deleted": True}) +``` + +```go !! go +// Go - HTTP 方法 +package main + +import ( + "encoding/json" + "fmt" + "net/http" +) + +func userHandler(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case http.MethodGet: + json.NewEncoder(w).Encode(map[string]string{"name": "Alice"}) + + case http.MethodPost: + var data map[string]interface{} + json.NewDecoder(r.Body).Decode(&data) + w.WriteHeader(http.StatusCreated) + json.NewEncoder(w).Encode(map[string]bool{"created": true}) + + case http.MethodPut, http.MethodPatch: + var data map[string]interface{} + json.NewDecoder(r.Body).Decode(&data) + json.NewEncoder(w).Encode(map[string]bool{"updated": true}) + + case http.MethodDelete: + json.NewEncoder(w).Encode(map[string]bool{"deleted": true}) + + default: + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + } +} + +func main() { + http.HandleFunc("/api/user", userHandler) + http.ListenAndServe(":8080", nil) +} +``` + + +## 查询参数和表单 + + +```python !! py +# Python - Flask +from flask import request + +@app.route('/search') +def search(): + query = request.args.get('q', '') + page = int(request.args.get('page', 1)) + return f"Search: {q}, Page: {page}" + +@app.route('/form', methods=['POST']) +def form(): + name = request.form.get('name') + email = request.form.get('email') + return f"Name: {name}, Email: {email}" +``` + +```go !! go +// Go - 查询参数和表单 +package main + +import ( + "fmt" + "net/http" + "strconv" +) + +func searchHandler(w http.ResponseWriter, r *http.Request) { + query := r.URL.Query().Get("q") + pageStr := r.URL.Query().Get("page") + page, _ := strconv.Atoi(pageStr) + if page == 0 { + page = 1 + } + + fmt.Fprintf(w, "Search: %s, Page: %d", query, page) +} + +func formHandler(w http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodPost { + r.ParseForm() + name := r.FormValue("name") + email := r.FormValue("email") + fmt.Fprintf(w, "Name: %s, Email: %s", name, email) + } +} + +func main() { + http.HandleFunc("/search", searchHandler) + http.HandleFunc("/form", formHandler) + http.ListenAndServe(":8080", nil) +} +``` + + +## JSON 请求/响应 + + +```python !! py +# Python - Flask with JSON +from flask import Flask, request, jsonify + +app = Flask(__name__) + +class User: + def __init__(self, name, email): + self.name = name + self.email = email + +@app.route('/api/user', methods=['POST']) +def create_user(): + data = request.get_json() + user = User(data['name'], data['email']) + return jsonify({ + 'name': user.name, + 'email': user.email + }), 201 +``` + +```go !! go +// Go - JSON with struct tags +package main + +import ( + "encoding/json" + "net/http" +) + +type User struct { + Name string `json:"name"` + Email string `json:"email"` +} + +func createUserHandler(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + var user User + if err := json.NewDecoder(r.Body).Decode(&user); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + json.NewEncoder(w).Encode(user) +} + +func main() { + http.HandleFunc("/api/user", createUserHandler) + http.ListenAndServe(":8080", nil) +} +``` + + +## 路径变量和路由 + + +```python !! py +# Python - Flask 路径变量 +@app.route('/user/') +def profile(username): + return f"Profile: {username}" + +@app.route('/post/') +def show_post(post_id): + return f"Post ID: {post_id}" +``` + +```go !! go +// Go - 手动路径解析(或使用路由器) +package main + +import ( + "fmt" + "net/http" + "strings" +) + +func profileHandler(w http.ResponseWriter, r *http.Request) { + // 从路径中提取用户名 + parts := strings.Split(r.URL.Path, "/") + if len(parts) < 3 { + http.Error(w, "User not found", http.StatusNotFound) + return + } + username := parts[2] + fmt.Fprintf(w, "Profile: %s", username) +} + +func main() { + http.HandleFunc("/user/", profileHandler) + http.ListenAndServe(":8080", nil) +} +``` + + +## 中间件 + + +```python !! py +# Python - Flask 装饰器 +from functools import wraps +from flask import request, jsonify + +def require_auth(f): + @wraps(f) + def decorated(*args, **kwargs): + token = request.headers.get('Authorization') + if not token or not validate_token(token): + return jsonify({"error": "Unauthorized"}), 401 + return f(*args, **kwargs) + return decorated + +@app.route('/protected') +@require_auth +def protected(): + return jsonify({"message": "Access granted"}) +``` + +```go !! go +// Go - 使用闭包的中间件 +package main + +import ( + "fmt" + "net/http" +) + +func requireAuth(next http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + token := r.Header.Get("Authorization") + if token == "" || !validateToken(token) { + http.Error(w, "Unauthorized", http.StatusUnauthorized) + return + } + next(w, r) + } +} + +func validateToken(token string) bool { + return token == "valid-token" +} + +func protectedHandler(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, `{"message": "Access granted"}`) +} + +func loggingMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Printf("%s %s\n", r.Method, r.URL.Path) + next.ServeHTTP(w, r) + }) +} + +func main() { + http.Handle("/protected", requireAuth(protectedHandler)) + + // 链式中间件 + handler := loggingMiddleware(http.DefaultServeMux) + http.ListenAndServe(":8080", handler) +} +``` + + +## 文件上传 + + +```python !! py +# Python - Flask 文件上传 +from flask import Flask, request +from werkzeug.utils import secure_filename + +app = Flask(__name__) +app.config['UPLOAD_FOLDER'] = '/uploads' + +@app.route('/upload', methods=['POST']) +def upload_file(): + if 'file' not in request.files: + return "No file", 400 + + file = request.files['file'] + filename = secure_filename(file.filename) + file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename)) + return "File uploaded" +``` + +```go !! go +// Go - 文件上传 +package main + +import ( + "fmt" + "io" + "net/http" + "os" +) + +func uploadHandler(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + // 解析多部分表单(最大 10MB) + err := r.ParseMultipartForm(10 << 20) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + file, handler, err := r.FormFile("file") + if err != nil { + http.Error(w, "Error retrieving file", http.StatusBadRequest) + return + } + defer file.Close() + + // 创建上传文件 + dst, err := os.Create("uploads/" + handler.Filename) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + defer dst.Close() + + // 复制文件 + if _, err := io.Copy(dst, file); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + fmt.Fprintf(w, "File uploaded: %s", handler.Filename) +} + +func main() { + http.HandleFunc("/upload", uploadHandler) + http.ListenAndServe(":8080", nil) +} +``` + + +## 使用 Web 框架 + +虽然 Go 的标准库已经足够,但框架可以提供更多便利: + +### 流行的 Go Web 框架 + +1. **Gin** - 快速、功能丰富 +2. **Echo** - 极简、高性能 +3. **Fiber** - Express-like API +4. **Chi** - 轻量级、可组合 + + +```python !! py +# Python - Flask +from flask import Flask, jsonify + +app = Flask(__name__) + +@app.route('/api/users/') +def get_user(id): + return jsonify({"id": id, "name": "Alice"}) +``` + +```go !! go +// Go - Gin 框架 +package main + +import ( + "github.com/gin-gonic/gin" +) + +type User struct { + ID int `json:"id"` + Name string `json:"name"` +} + +func main() { + r := gin.Default() + + r.GET("/api/users/:id", func(c *gin.Context) { + id := c.Param("id") + c.JSON(200, User{ + ID: 1, + Name: "Alice", + }) + }) + + r.Run(":8080") +} +``` + + +## RESTful API 示例 + + +```python !! py +# Python - Flask RESTful API +from flask import Flask, request, jsonify +from typing import Dict + +app = Flask(__name__) +users: Dict[int, dict] = { + 1: {"id": 1, "name": "Alice", "email": "alice@example.com"}, +} + +@app.route('/api/users', methods=['GET']) +def get_users(): + return jsonify(list(users.values())) + +@app.route('/api/users/', methods=['GET']) +def get_user(user_id): + user = users.get(user_id) + if not user: + return jsonify({"error": "Not found"}), 404 + return jsonify(user) + +@app.route('/api/users', methods=['POST']) +def create_user(): + data = request.get_json() + user_id = max(users.keys()) + 1 + data['id'] = user_id + users[user_id] = data + return jsonify(data), 201 +``` + +```go !! go +// Go - RESTful API +package main + +import ( + "encoding/json" + "net/http" + "strconv" + "sync" +) + +type User struct { + ID int `json:"id"` + Name string `json:"name"` + Email string `json:"email"` +} + +var ( + users = map[int]User{ + 1: {ID: 1, Name: "Alice", Email: "alice@example.com"}, + } + mu sync.RWMutex + nextID = 2 +) + +func getUsersHandler(w http.ResponseWriter, r *http.Request) { + mu.RLock() + defer mu.RUnlock() + + userList := make([]User, 0, len(users)) + for _, user := range users { + userList = append(userList, user) + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(userList) +} + +func getUserHandler(w http.ResponseWriter, r *http.Request) { + idStr := r.URL.Path[len("/api/users/"):] + id, err := strconv.Atoi(idStr) + if err != nil { + http.Error(w, "Invalid user ID", http.StatusBadRequest) + return + } + + mu.RLock() + user, exists := users[id] + mu.RUnlock() + + if !exists { + http.Error(w, "User not found", http.StatusNotFound) + return + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(user) +} + +func createUserHandler(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + var user User + if err := json.NewDecoder(r.Body).Decode(&user); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + mu.Lock() + user.ID = nextID + nextID++ + users[user.ID] = user + mu.Unlock() + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + json.NewEncoder(w).Encode(user) +} + +func main() { + http.HandleFunc("/api/users", getUsersHandler) + http.HandleFunc("/api/users/", getUserHandler) + http.HandleFunc("/api/users", createUserHandler) + + fmt.Println("Server starting on :8080") + http.ListenAndServe(":8080", nil) +} +``` + + +## HTTP 客户端 + + +```python !! py +# Python - requests 库 +import requests + +response = requests.get('https://api.example.com/data') +data = response.json() + +response = requests.post( + 'https://api.example.com/users', + json={'name': 'Alice'}, + headers={'Authorization': 'Bearer token'} +) +``` + +```go !! go +// Go - net/http 客户端 +package main + +import ( + "encoding/json" + "fmt" + "io" + "net/http" +) + +func main() { + // GET 请求 + resp, err := http.Get("https://api.example.com/data") + if err != nil { + panic(err) + } + defer resp.Body.Close() + + body, _ := io.ReadAll(resp.Body) + fmt.Println(string(body)) + + // POST 请求 + data := map[string]string{"name": "Alice"} + jsonData, _ := json.Marshal(data) + + req, _ := http.NewRequest( + "POST", + "https://api.example.com/users", + jsonData, + ) + req.Header.Set("Authorization", "Bearer token") + req.Header.Set("Content-Type", "application/json") + + client := &http.Client{} + resp2, err := client.Do(req) + if err != nil { + panic(err) + } + defer resp2.Body.Close() +} +``` + + +## 性能对比 + + +```python !! py +# Python Flask +# 典型性能: 500-2,000 请求/秒 +# 内存占用: 50-100 MB + +from flask import Flask +app = Flask(__name__) + +@app.route('/') +def index(): + return "Hello" + +app.run(threaded=True) +``` + +```go !! go +// Go net/http +// 典型性能: 10,000-50,000+ 请求/秒 +// 内存占用: 5-20 MB + +package main + +import "net/http" + +func handler(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("Hello")) +} + +func main() { + http.HandleFunc("/", handler) + http.ListenAndServe(":8080", nil) +} +``` + + +## 总结 + +在本模块中,你学习了: + +1. **net/http** - 标准库中的强大 HTTP 服务器 +2. **路由** - HandleFunc 和手动路由 +3. **JSON** - 使用 struct tag 进行编码/解码 +4. **中间件** - 可组合的中间件链 +5. **文件上传** - 多部分表单处理 +6. **Web 框架** - Gin、Echo、Chi 提供便利 +7. **REST API** - 完整的 CRUD 操作 +8. **HTTP 客户端** - 发起外部请求 +9. **性能** - Go 明显快于 Python + +## 主要区别 + +| Python | Go | +|--------|-----| +| 需要 Flask/Django | 标准库即可 | +| `@app.route` 装饰器 | `http.HandleFunc` | +| `request.get_json()` | `json.NewDecoder(r.Body)` | +| `@require_auth` | 中间件函数 | +| 500-2,000 请求/秒 | 10,000-50,000+ 请求/秒 | + +## 练习 + +1. 构建一个完整的 CRUD REST API +2. 实现认证中间件 +3. 添加文件上传功能 +4. 创建 WebSocket 服务器 +5. 与你的 Python 应用进行性能对比 + +## 下一步 + +下一模块:**测试和调试** - 编写测试和调试 Go 代码。 diff --git a/content/docs/py2go/module-10-web-development.zh-tw.mdx b/content/docs/py2go/module-10-web-development.zh-tw.mdx new file mode 100644 index 0000000..586382c --- /dev/null +++ b/content/docs/py2go/module-10-web-development.zh-tw.mdx @@ -0,0 +1,746 @@ +--- +title: "模組 10: Web 開發" +description: "使用 Go 建構Web 伺服器和 API,與 Python 框架對比" +--- + +## 簡介 + +Go 的標準庫包含強大的 HTTP 伺服器。與 Python 需要使用 Flask 或 FastAPI 等框架不同,Go 的 `net/http` 包提供了生產級 Web 服務所需的一切。 + +## 基本 HTTP 伺服器 + + +```python !! py +# Python - Flask +from flask import Flask + +app = Flask(__name__) + +@app.route('/') +def hello(): + return "Hello, World!" + +@app.route('/user/') +def user(name): + return f"Hello, {name}!" + +if __name__ == '__main__': + app.run(port=8080) +``` + +```go !! go +// Go - net/http (無需框架!) +package main + +import ( + "fmt" + "net/http" +) + +func helloHandler(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, "Hello, World!") +} + +func userHandler(w http.ResponseWriter, r *http.Request) { + name := r.URL.Path[len("/user/"):] + fmt.Fprintf(w, "Hello, %s!", name) +} + +func main() { + http.HandleFunc("/", helloHandler) + http.HandleFunc("/user/", userHandler) + + fmt.Println("Server starting on :8080") + http.ListenAndServe(":8080", nil) +} +``` + + +## HTTP 方法和路由 + + +```python !! py +# Python - Flask +from flask import Flask, request, jsonify + +app = Flask(__name__) + +@app.route('/api/user', methods=['GET']) +def get_user(): + return jsonify({"name": "Alice"}) + +@app.route('/api/user', methods=['POST']) +def create_user(): + data = request.get_json() + return jsonify({"created": True}), 201 + +@app.route('/api/user', methods=['PUT', 'PATCH']) +def update_user(): + data = request.get_json() + return jsonify({"updated": True}) + +@app.route('/api/user', methods=['DELETE']) +def delete_user(): + return jsonify({"deleted": True}) +``` + +```go !! go +// Go - HTTP 方法 +package main + +import ( + "encoding/json" + "fmt" + "net/http" +) + +func userHandler(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case http.MethodGet: + json.NewEncoder(w).Encode(map[string]string{"name": "Alice"}) + + case http.MethodPost: + var data map[string]interface{} + json.NewDecoder(r.Body).Decode(&data) + w.WriteHeader(http.StatusCreated) + json.NewEncoder(w).Encode(map[string]bool{"created": true}) + + case http.MethodPut, http.MethodPatch: + var data map[string]interface{} + json.NewDecoder(r.Body).Decode(&data) + json.NewEncoder(w).Encode(map[string]bool{"updated": true}) + + case http.MethodDelete: + json.NewEncoder(w).Encode(map[string]bool{"deleted": true}) + + default: + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + } +} + +func main() { + http.HandleFunc("/api/user", userHandler) + http.ListenAndServe(":8080", nil) +} +``` + + +## 查詢參數和表單 + + +```python !! py +# Python - Flask +from flask import request + +@app.route('/search') +def search(): + query = request.args.get('q', '') + page = int(request.args.get('page', 1)) + return f"Search: {q}, Page: {page}" + +@app.route('/form', methods=['POST']) +def form(): + name = request.form.get('name') + email = request.form.get('email') + return f"Name: {name}, Email: {email}" +``` + +```go !! go +// Go - 查詢參數和表單 +package main + +import ( + "fmt" + "net/http" + "strconv" +) + +func searchHandler(w http.ResponseWriter, r *http.Request) { + query := r.URL.Query().Get("q") + pageStr := r.URL.Query().Get("page") + page, _ := strconv.Atoi(pageStr) + if page == 0 { + page = 1 + } + + fmt.Fprintf(w, "Search: %s, Page: %d", query, page) +} + +func formHandler(w http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodPost { + r.ParseForm() + name := r.FormValue("name") + email := r.FormValue("email") + fmt.Fprintf(w, "Name: %s, Email: %s", name, email) + } +} + +func main() { + http.HandleFunc("/search", searchHandler) + http.HandleFunc("/form", formHandler) + http.ListenAndServe(":8080", nil) +} +``` + + +## JSON 請求/回應 + + +```python !! py +# Python - Flask with JSON +from flask import Flask, request, jsonify + +app = Flask(__name__) + +class User: + def __init__(self, name, email): + self.name = name + self.email = email + +@app.route('/api/user', methods=['POST']) +def create_user(): + data = request.get_json() + user = User(data['name'], data['email']) + return jsonify({ + 'name': user.name, + 'email': user.email + }), 201 +``` + +```go !! go +// Go - JSON with struct tags +package main + +import ( + "encoding/json" + "net/http" +) + +type User struct { + Name string `json:"name"` + Email string `json:"email"` +} + +func createUserHandler(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + var user User + if err := json.NewDecoder(r.Body).Decode(&user); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + json.NewEncoder(w).Encode(user) +} + +func main() { + http.HandleFunc("/api/user", createUserHandler) + http.ListenAndServe(":8080", nil) +} +``` + + +## 路徑變數和路由 + + +```python !! py +# Python - Flask 路徑變數 +@app.route('/user/') +def profile(username): + return f"Profile: {username}" + +@app.route('/post/') +def show_post(post_id): + return f"Post ID: {post_id}" +``` + +```go !! go +// Go - 手動路徑解析(或使用路由器) +package main + +import ( + "fmt" + "net/http" + "strings" +) + +func profileHandler(w http.ResponseWriter, r *http.Request) { + // 從路徑中提取使用者名稱 + parts := strings.Split(r.URL.Path, "/") + if len(parts) < 3 { + http.Error(w, "User not found", http.StatusNotFound) + return + } + username := parts[2] + fmt.Fprintf(w, "Profile: %s", username) +} + +func main() { + http.HandleFunc("/user/", profileHandler) + http.ListenAndServe(":8080", nil) +} +``` + + +## 中間件 + + +```python !! py +# Python - Flask 裝飾器 +from functools import wraps +from flask import request, jsonify + +def require_auth(f): + @wraps(f) + def decorated(*args, **kwargs): + token = request.headers.get('Authorization') + if not token or not validate_token(token): + return jsonify({"error": "Unauthorized"}), 401 + return f(*args, **kwargs) + return decorated + +@app.route('/protected') +@require_auth +def protected(): + return jsonify({"message": "Access granted"}) +``` + +```go !! go +// Go - 使用閉包的中間件 +package main + +import ( + "fmt" + "net/http" +) + +func requireAuth(next http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + token := r.Header.Get("Authorization") + if token == "" || !validateToken(token) { + http.Error(w, "Unauthorized", http.StatusUnauthorized) + return + } + next(w, r) + } +} + +func validateToken(token string) bool { + return token == "valid-token" +} + +func protectedHandler(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, `{"message": "Access granted"}`) +} + +func loggingMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Printf("%s %s\n", r.Method, r.URL.Path) + next.ServeHTTP(w, r) + }) +} + +func main() { + http.Handle("/protected", requireAuth(protectedHandler)) + + // 鏈式中間件 + handler := loggingMiddleware(http.DefaultServeMux) + http.ListenAndServe(":8080", handler) +} +``` + + +## 檔案上傳 + + +```python !! py +# Python - Flask 檔案上傳 +from flask import Flask, request +from werkzeug.utils import secure_filename + +app = Flask(__name__) +app.config['UPLOAD_FOLDER'] = '/uploads' + +@app.route('/upload', methods=['POST']) +def upload_file(): + if 'file' not in request.files: + return "No file", 400 + + file = request.files['file'] + filename = secure_filename(file.filename) + file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename)) + return "File uploaded" +``` + +```go !! go +// Go - 檔案上傳 +package main + +import ( + "fmt" + "io" + "net/http" + "os" +) + +func uploadHandler(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + // 解析多部分表單(最大 10MB) + err := r.ParseMultipartForm(10 << 20) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + file, handler, err := r.FormFile("file") + if err != nil { + http.Error(w, "Error retrieving file", http.StatusBadRequest) + return + } + defer file.Close() + + // 建立上傳檔案 + dst, err := os.Create("uploads/" + handler.Filename) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + defer dst.Close() + + // 複製檔案 + if _, err := io.Copy(dst, file); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + fmt.Fprintf(w, "File uploaded: %s", handler.Filename) +} + +func main() { + http.HandleFunc("/upload", uploadHandler) + http.ListenAndServe(":8080", nil) +} +``` + + +## 使用 Web 框架 + +雖然 Go 的標準庫已經足夠,但框架可以提供更多便利: + +### 流行的 Go Web 框架 + +1. **Gin** - 快速、功能豐富 +2. **Echo** - 極簡、高效能 +3. **Fiber** - Express-like API +4. **Chi** - 輕量級、可組合 + + +```python !! py +# Python - Flask +from flask import Flask, jsonify + +app = Flask(__name__) + +@app.route('/api/users/') +def get_user(id): + return jsonify({"id": id, "name": "Alice"}) +``` + +```go !! go +// Go - Gin 框架 +package main + +import ( + "github.com/gin-gonic/gin" +) + +type User struct { + ID int `json:"id"` + Name string `json:"name"` +} + +func main() { + r := gin.Default() + + r.GET("/api/users/:id", func(c *gin.Context) { + id := c.Param("id") + c.JSON(200, User{ + ID: 1, + Name: "Alice", + }) + }) + + r.Run(":8080") +} +``` + + +## RESTful API 示例 + + +```python !! py +# Python - Flask RESTful API +from flask import Flask, request, jsonify +from typing import Dict + +app = Flask(__name__) +users: Dict[int, dict] = { + 1: {"id": 1, "name": "Alice", "email": "alice@example.com"}, +} + +@app.route('/api/users', methods=['GET']) +def get_users(): + return jsonify(list(users.values())) + +@app.route('/api/users/', methods=['GET']) +def get_user(user_id): + user = users.get(user_id) + if not user: + return jsonify({"error": "Not found"}), 404 + return jsonify(user) + +@app.route('/api/users', methods=['POST']) +def create_user(): + data = request.get_json() + user_id = max(users.keys()) + 1 + data['id'] = user_id + users[user_id] = data + return jsonify(data), 201 +``` + +```go !! go +// Go - RESTful API +package main + +import ( + "encoding/json" + "net/http" + "strconv" + "sync" +) + +type User struct { + ID int `json:"id"` + Name string `json:"name"` + Email string `json:"email"` +} + +var ( + users = map[int]User{ + 1: {ID: 1, Name: "Alice", Email: "alice@example.com"}, + } + mu sync.RWMutex + nextID = 2 +) + +func getUsersHandler(w http.ResponseWriter, r *http.Request) { + mu.RLock() + defer mu.RUnlock() + + userList := make([]User, 0, len(users)) + for _, user := range users { + userList = append(userList, user) + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(userList) +} + +func getUserHandler(w http.ResponseWriter, r *http.Request) { + idStr := r.URL.Path[len("/api/users/"):] + id, err := strconv.Atoi(idStr) + if err != nil { + http.Error(w, "Invalid user ID", http.StatusBadRequest) + return + } + + mu.RLock() + user, exists := users[id] + mu.RUnlock() + + if !exists { + http.Error(w, "User not found", http.StatusNotFound) + return + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(user) +} + +func createUserHandler(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + var user User + if err := json.NewDecoder(r.Body).Decode(&user); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + mu.Lock() + user.ID = nextID + nextID++ + users[user.ID] = user + mu.Unlock() + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + json.NewEncoder(w).Encode(user) +} + +func main() { + http.HandleFunc("/api/users", getUsersHandler) + http.HandleFunc("/api/users/", getUserHandler) + http.HandleFunc("/api/users", createUserHandler) + + fmt.Println("Server starting on :8080") + http.ListenAndServe(":8080", nil) +} +``` + + +## HTTP 客戶端 + + +```python !! py +# Python - requests 函式庫 +import requests + +response = requests.get('https://api.example.com/data') +data = response.json() + +response = requests.post( + 'https://api.example.com/users', + json={'name': 'Alice'}, + headers={'Authorization': 'Bearer token'} +) +``` + +```go !! go +// Go - net/http 客戶端 +package main + +import ( + "encoding/json" + "fmt" + "io" + "net/http" +) + +func main() { + // GET 請求 + resp, err := http.Get("https://api.example.com/data") + if err != nil { + panic(err) + } + defer resp.Body.Close() + + body, _ := io.ReadAll(resp.Body) + fmt.Println(string(body)) + + // POST 請求 + data := map[string]string{"name": "Alice"} + jsonData, _ := json.Marshal(data) + + req, _ := http.NewRequest( + "POST", + "https://api.example.com/users", + jsonData, + ) + req.Header.Set("Authorization", "Bearer token") + req.Header.Set("Content-Type", "application/json") + + client := &http.Client{} + resp2, err := client.Do(req) + if err != nil { + panic(err) + } + defer resp2.Body.Close() +} +``` + + +## 效能對比 + + +```python !! py +# Python Flask +# 典型效能: 500-2,000 請求/秒 +// 記憶體佔用: 50-100 MB + +from flask import Flask +app = Flask(__name__) + +@app.route('/') +def index(): + return "Hello" + +app.run(threaded=True) +``` + +```go !! go +// Go net/http +// 典型效能: 10,000-50,000+ 請求/秒 +// 記憶體佔用: 5-20 MB + +package main + +import "net/http" + +func handler(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("Hello")) +} + +func main() { + http.HandleFunc("/", handler) + http.ListenAndServe(":8080", nil) +} +``` + + +## 總結 + +在本模組中,你學習了: + +1. **net/http** - 標準庫中的強大 HTTP 伺服器 +2. **路由** - HandleFunc 和手動路由 +3. **JSON** - 使用 struct tag 進行編碼/解碼 +4. **中間件** - 可組合的中間件鏈 +5. **檔案上傳** - 多部分表單處理 +6. **Web 框架** - Gin、Echo、Chi 提供便利 +7. **REST API** - 完整的 CRUD 操作 +8. **HTTP 客戶端** - 發起外部請求 +9. **效能** - Go 明顯快於 Python + +## 主要差別 + +| Python | Go | +|--------|-----| +| 需要 Flask/Django | 標準庫即可 | +| `@app.route` 裝飾器 | `http.HandleFunc` | +| `request.get_json()` | `json.NewDecoder(r.Body)` | +| `@require_auth` | 中間件函數 | +| 500-2,000 請求/秒 | 10,000-50,000+ 請求/秒 | + +## 練習 + +1. 建構一個完整的 CRUD REST API +2. 實現認證中間件 +3. 新增檔案上傳功能 +4. 建立 WebSocket 伺服器 +5. 與你的 Python 應用進行效能對比 + +## 下一步 + +下一模組:**測試和除錯** - 撰寫測試和除錯 Go 程式碼。 diff --git a/content/docs/py2go/module-11-testing-debugging.mdx b/content/docs/py2go/module-11-testing-debugging.mdx new file mode 100644 index 0000000..3561d55 --- /dev/null +++ b/content/docs/py2go/module-11-testing-debugging.mdx @@ -0,0 +1,664 @@ +--- +title: "Module 11: Testing and Debugging" +description: "Testing methodology and debugging techniques in Go" +--- + +## Introduction + +Go has built-in testing support with the `testing` package. Unlike Python where you need pytest or unittest, Go's testing is part of the standard library and follows simple conventions. + +## Basic Testing + + +```python !! py +# Python - pytest +def test_add(): + assert add(2, 3) == 5 + +def test_add_negative(): + assert add(-1, 1) == 0 + +# Run with: pytest test_file.py +``` + +```go !! go +// Go - testing package +package main + +import "testing" + +func TestAdd(t *testing.T) { + result := Add(2, 3) + if result != 5 { + t.Errorf("Add(2, 3) = %d; want 5", result) + } +} + +func TestAddNegative(t *testing.T) { + result := Add(-1, 1) + if result != 0 { + t.Errorf("Add(-1, 1) = %d; want 0", result) + } +} + +// Run with: go test +``` + + +## Test File Organization + + +```bash +# Python +project/ +├── src/ +│ ├── __init__.py +│ └── calculator.py +└── tests/ + ├── __init__.py + └── test_calculator.py +``` + +```bash +# Go +project/ +├── calculator.go +└── calculator_test.go // Same package, _test.go suffix +``` + + +## Table-Driven Tests + +Go's idiomatic way to test multiple cases: + + +```python !! py +# Python - parametrize +import pytest + +@pytest.mark.parametrize("a,b,expected", [ + (1, 2, 3), + (0, 0, 0), + (-1, 1, 0), + (100, 200, 300), +]) +def test_add(a, b, expected): + assert add(a, b) == expected +``` + +```go !! go +// Go - Table-driven tests +package main + +import "testing" + +func TestAdd(t *testing.T) { + tests := []struct { + name string + a, b int + expected int + }{ + {"basic", 1, 2, 3}, + {"zeros", 0, 0, 0}, + {"negative", -1, 1, 0}, + {"large", 100, 200, 300}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := Add(tt.a, tt.b) + if result != tt.expected { + t.Errorf("Add(%d, %d) = %d; want %d", + tt.a, tt.b, result, tt.expected) + } + }) + } +} +``` + + +## Test Helpers and Setup + + +```python !! py +# Python - pytest fixtures +import pytest + +@pytest.fixture +def database(): + db = Database(":memory:") + db.init() + yield db + db.cleanup() + +def test_query(database): + result = database.query("SELECT * FROM users") + assert len(result) > 0 +``` + +```go !! go +// Go - Setup and teardown +package main + +import ( + "testing" +) + +func setupTestDB(t *testing.T) *Database { + db := &Database{Path: ":memory:"} + if err := db.Init(); err != nil { + t.Fatalf("Failed to init DB: %v", err) + } + return db +} + +func TestQuery(t *testing.T) { + db := setupTestDB(t) + defer db.Cleanup() + + result := db.Query("SELECT * FROM users") + if len(result) == 0 { + t.Error("Expected results, got none") + } +} +``` + + +## Testing Errors + + +```python !! py +# Python - pytest +import pytest + +def test_divide_by_zero(): + with pytest.raises(ValueError): + divide(10, 0) + +def test_file_not_found(): + with pytest.raises(FileNotFoundError): + read_file("nonexistent.txt") +``` + +```go !! go +// Go - Testing errors +package main + +import "testing" + +func TestDivideByZero(t *testing.T) { + result, err := divide(10, 0) + if err == nil { + t.Error("Expected error for division by zero, got nil") + } + if result != 0 { + t.Errorf("Expected result 0, got %d", result) + } +} + +func TestFileNotFound(t *testing.T) { + _, err := ReadFile("nonexistent.txt") + if err == nil { + t.Error("Expected error for nonexistent file, got nil") + } +} +``` + + +## Mocking and Interfaces + + +```python !! py +# Python - mocking +from unittest.mock import Mock, patch + +def test_send_notification(): + mock_service = Mock() + mock_service.send.return_value = True + + with patch('app.notification_service', mock_service): + result = send_notification("Hello") + assert result == True + mock_service.send.assert_called_once_with("Hello") +``` + +```go !! go +// Go - Mock with interfaces +package main + +import "testing" + +// Interface +type NotificationService interface { + Send(message string) bool +} + +// Mock implementation +type MockNotificationService struct { + SendCalled bool + Message string +} + +func (m *MockNotificationService) Send(message string) bool { + m.SendCalled = true + m.Message = message + return true +} + +func TestSendNotification(t *testing.T) { + mock := &MockNotificationService{} + + result := SendNotification(mock, "Hello") + + if !result { + t.Error("Expected true, got false") + } + if !mock.SendCalled { + t.Error("Send was not called") + } + if mock.Message != "Hello" { + t.Errorf("Wrong message: %s", mock.Message) + } +} +``` + + +## Benchmarking + + +```python !! py +# Python - pytest-benchmark +import pytest + +def test_parse_json(benchmark): + data = '{"name": "Alice", "age": 30}' + result = benchmark(json.loads, data) + assert result['name'] == 'Alice' +``` + +```go !! go +// Go - Benchmarking +package main + +import ( + "encoding/json" + "testing" +) + +func BenchmarkParseJSON(b *testing.B) { + data := []byte(`{"name": "Alice", "age": 30}`) + + b.ResetTimer() // Reset timer before actual benchmark + for i := 0; i < b.N; i++ { + var result map[string]interface{} + json.Unmarshal(data, &result) + } +} + +// Run with: go test -bench=. +``` + + +## HTTP Testing + + +```python !! py +# Python - pytest with Flask test client +def test_home_page(client): + response = client.get('/') + assert response.status_code == 200 + assert b'Welcome' in response.data + +def test_api_endpoint(client): + response = client.post('/api/users', json={'name': 'Alice'}) + assert response.status_code == 201 +``` + +```go !! go +// Go - HTTP testing +package main + +import ( + "net/http" + "net/http/httptest" + "testing" +) + +func TestHomeHandler(t *testing.T) { + req := httptest.NewRequest("GET", "/", nil) + w := httptest.NewRecorder() + + homeHandler(w, req) + + if w.Code != http.StatusOK { + t.Errorf("Expected status 200, got %d", w.Code) + } + + if !contains(w.Body.String(), "Welcome") { + t.Error("Response should contain 'Welcome'") + } +} + +func TestCreateUserHandler(t *testing.T) { + jsonData := `{"name": "Alice"}` + req := httptest.NewRequest("POST", "/api/users", strings.NewReader(jsonData)) + req.Header.Set("Content-Type", "application/json") + w := httptest.NewRecorder() + + createUserHandler(w, req) + + if w.Code != http.StatusCreated { + t.Errorf("Expected status 201, got %d", w.Code) + } +} +``` + + +## Race Condition Testing + + +```python !! py +# Python - No built-in race detection +# Use tools like thread-safety analyzers +``` + +```go !! go +// Go - Race detector +package main + +import ( + "sync" + "testing" +) + +func TestConcurrentAccess(t *testing.T) { + var counter int + var wg sync.WaitGroup + + for i := 0; i < 100; i++ { + wg.Add(1) + go func() { + defer wg.Done() + counter++ // Data race! + }() + } + + wg.Wait() +} + +// Run with: go test -race +// Output will warn about data race +``` + + +## Coverage Reports + + +```bash +# Python - pytest with coverage +pytest --cov=src --cov-report=html + +# Output: Coverage report +# Name Stmts Miss Cover +# --------------------------------------- +# src/calculator.py 20 2 90% +``` + +```bash +# Go - built-in coverage +go test -coverprofile=coverage.out +go tool cover -html=coverage.out + +# Output: Coverage report in browser +# coverage: 85.7% of statements +``` + + +## Debugging + + +```python !! py +# Python - pdb +import pdb; pdb.set_trace() + +# Or use breakpoint() (Python 3.7+) +def my_function(): + breakpoint() # Execution stops here + x = 42 + return x +``` + +```go !! go +// Go - Delve debugger +package main + +func main() { + x := 42 + // Set breakpoint in Delve + println(x) +} + +// Run with: +// dlv debug main.go +// (dlv) break main.go:8 +// (dlv) continue +``` + + +## Logging + + +```python !! py +# Python - logging module +import logging + +logging.basicConfig(level=logging.DEBUG) +logger = logging.getLogger(__name__) + +def process(data): + logger.debug(f"Processing: {data}") + result = transform(data) + logger.info(f"Result: {result}") + return result +``` + +```go !! go +// Go - log package +package main + +import "log" + +func process(data string) string { + log.Printf("Processing: %s", data) + result := transform(data) + log.Printf("Result: %s", result) + return result +} + +// Or use structured logging +import "go.uber.org/zap" + +func processWithLogger(data string) string { + logger := zap.NewExample() + logger.Debug("Processing", zap.String("data", data)) + return transform(data) +} +``` + + +## Example-Based Tests (Examples) + + +```python !! py +# Python - doctests +def add(a, b): + """ + Add two numbers. + + >>> add(2, 3) + 5 + >>> add(-1, 1) + 0 + """ + return a + b + +# Run with: python -m doctest file.py +``` + +```go !! go +// Go - Example tests +package main + +import "fmt" + +func ExampleAdd() { + result := Add(2, 3) + fmt.Println(result) + // Output: 5 +} + +func ExampleAdd_multiple() { + fmt.Println(Add(1, 2)) + fmt.Println(Add(-1, 1)) + // Output: + // 3 + // 0 +} + +// Run with: go test +``` + + +## Subtests and Parallel Tests + + +```python !! py +# Python - pytest classes +class TestUserOperations: + def test_create(self): + pass + + def test_update(self): + pass + + def test_delete(self): + pass +``` + +```go !! go +// Go - Subtests and parallel tests +package main + +import "testing" + +func TestUserOperations(t *testing.T) { + tests := []struct { + name string + test func(*testing.T) + }{ + {"create", func(t *testing.T) { + // Test create + }}, + {"update", func(t *testing.T) { + // Test update + }}, + {"delete", func(t *testing.T) { + // Test delete + }}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() // Run in parallel + tt.test(t) + }) + } +} + +// Run with: go test -parallel 4 +``` + + +## Test Best Practices + + +```python !! py +# Python - Test structure +class TestCalculator: + def setup_method(self): + self.calc = Calculator() + + def teardown_method(self): + self.calc.cleanup() + + def test_add_positive_numbers(self): + assert self.calc.add(2, 3) == 5 +``` + +```go !! go +// Go - Test organization +package main + +import "testing" + +func TestCalculator(t *testing.T) { + // Setup + calc := NewCalculator() + defer calc.Cleanup() + + t.Run("AddPositiveNumbers", func(t *testing.T) { + result := calc.Add(2, 3) + if result != 5 { + t.Errorf("Expected 5, got %d", result) + } + }) + + t.Run("AddNegativeNumbers", func(t *testing.T) { + result := calc.Add(-2, -3) + if result != -5 { + t.Errorf("Expected -5, got %d", result) + } + }) +} +``` + + +## Summary + +In this module, you learned: + +1. **Testing package** - Built-in testing with `testing` +2. **Table-driven tests** - Idiomatic Go test pattern +3. **Test helpers** - Setup and teardown +4. **Error testing** - Checking error returns +5. **Mocking** - Using interfaces +6. **Benchmarking** - Performance tests +7. **HTTP testing** - httptest package +8. **Race detector** - `-race` flag +9. **Coverage** - Built-in coverage reports +10. **Debugging** - Delve debugger +11. **Logging** - log package for debugging + +## Key Differences + +| Python | Go | +|--------|-----| +| pytest/unittest | Built-in `testing` | +| `@pytest.mark.parametrize` | Table-driven tests | +| `pytest.raises()` | Check error return value | +| `unittest.mock` | Interfaces for mocking | +| `--cov` flag | `-coverprofile` | +| pdb debugger | Delve debugger | +| No race detection | `-race` flag | + +## Exercises + +1. Write comprehensive tests for a calculator package +2. Create benchmarks comparing string concatenation methods +3. Test an HTTP handler with various inputs +4. Use the race detector to find concurrency bugs +5. Generate a coverage report for your code + +## Next Steps + +Next module: **Performance Optimization** - Profiling and optimizing Go applications. diff --git a/content/docs/py2go/module-11-testing-debugging.zh-cn.mdx b/content/docs/py2go/module-11-testing-debugging.zh-cn.mdx new file mode 100644 index 0000000..b783b18 --- /dev/null +++ b/content/docs/py2go/module-11-testing-debugging.zh-cn.mdx @@ -0,0 +1,664 @@ +--- +title: "模块 11: 测试和调试" +description: "Go 中的测试方法和调试技巧" +--- + +## 简介 + +Go 内置了对测试的支持,使用 `testing` 包。与 Python 需要使用 pytest 或 unittest 不同,Go 的测试是标准库的一部分,遵循简单的约定。 + +## 基本测试 + + +```python !! py +# Python - pytest +def test_add(): + assert add(2, 3) == 5 + +def test_add_negative(): + assert add(-1, 1) == 0 + +# 运行: pytest test_file.py +``` + +```go !! go +// Go - testing 包 +package main + +import "testing" + +func TestAdd(t *testing.T) { + result := Add(2, 3) + if result != 5 { + t.Errorf("Add(2, 3) = %d; want 5", result) + } +} + +func TestAddNegative(t *testing.T) { + result := Add(-1, 1) + if result != 0 { + t.Errorf("Add(-1, 1) = %d; want 0", result) + } +} + +// 运行: go test +``` + + +## 测试文件组织 + + +```bash +# Python +project/ +├── src/ +│ ├── __init__.py +│ └── calculator.py +└── tests/ + ├── __init__.py + └── test_calculator.py +``` + +```bash +# Go +project/ +├── calculator.go +└── calculator_test.go // 同一个包,_test.go 后缀 +``` + + +## 表驱动测试 + +Go 测试多个用例的惯用方式: + + +```python !! py +# Python - parametrize +import pytest + +@pytest.mark.parametrize("a,b,expected", [ + (1, 2, 3), + (0, 0, 0), + (-1, 1, 0), + (100, 200, 300), +]) +def test_add(a, b, expected): + assert add(a, b) == expected +``` + +```go !! go +// Go - 表驱动测试 +package main + +import "testing" + +func TestAdd(t *testing.T) { + tests := []struct { + name string + a, b int + expected int + }{ + {"basic", 1, 2, 3}, + {"zeros", 0, 0, 0}, + {"negative", -1, 1, 0}, + {"large", 100, 200, 300}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := Add(tt.a, tt.b) + if result != tt.expected { + t.Errorf("Add(%d, %d) = %d; want %d", + tt.a, tt.b, result, tt.expected) + } + }) + } +} +``` + + +## 测试辅助和设置 + + +```python !! py +# Python - pytest fixtures +import pytest + +@pytest.fixture +def database(): + db = Database(":memory:") + db.init() + yield db + db.cleanup() + +def test_query(database): + result = database.query("SELECT * FROM users") + assert len(result) > 0 +``` + +```go !! go +// Go - 设置和清理 +package main + +import ( + "testing" +) + +func setupTestDB(t *testing.T) *Database { + db := &Database{Path: ":memory:"} + if err := db.Init(); err != nil { + t.Fatalf("Failed to init DB: %v", err) + } + return db +} + +func TestQuery(t *testing.T) { + db := setupTestDB(t) + defer db.Cleanup() + + result := db.Query("SELECT * FROM users") + if len(result) == 0 { + t.Error("Expected results, got none") + } +} +``` + + +## 错误测试 + + +```python !! py +# Python - pytest +import pytest + +def test_divide_by_zero(): + with pytest.raises(ValueError): + divide(10, 0) + +def test_file_not_found(): + with pytest.raises(FileNotFoundError): + read_file("nonexistent.txt") +``` + +```go !! go +// Go - 测试错误 +package main + +import "testing" + +func TestDivideByZero(t *testing.T) { + result, err := divide(10, 0) + if err == nil { + t.Error("Expected error for division by zero, got nil") + } + if result != 0 { + t.Errorf("Expected result 0, got %d", result) + } +} + +func TestFileNotFound(t *testing.T) { + _, err := ReadFile("nonexistent.txt") + if err == nil { + t.Error("Expected error for nonexistent file, got nil") + } +} +``` + + +## Mock 和接口 + + +```python !! py +# Python - mocking +from unittest.mock import Mock, patch + +def test_send_notification(): + mock_service = Mock() + mock_service.send.return_value = True + + with patch('app.notification_service', mock_service): + result = send_notification("Hello") + assert result == True + mock_service.send.assert_called_once_with("Hello") +``` + +```go !! go +// Go - 使用接口 Mock +package main + +import "testing" + +// 接口 +type NotificationService interface { + Send(message string) bool +} + +// Mock 实现 +type MockNotificationService struct { + SendCalled bool + Message string +} + +func (m *MockNotificationService) Send(message string) bool { + m.SendCalled = true + m.Message = message + return true +} + +func TestSendNotification(t *testing.T) { + mock := &MockNotificationService{} + + result := SendNotification(mock, "Hello") + + if !result { + t.Error("Expected true, got false") + } + if !mock.SendCalled { + t.Error("Send was not called") + } + if mock.Message != "Hello" { + t.Errorf("Wrong message: %s", mock.Message) + } +} +``` + + +## 基准测试 + + +```python !! py +# Python - pytest-benchmark +import pytest + +def test_parse_json(benchmark): + data = '{"name": "Alice", "age": 30}' + result = benchmark(json.loads, data) + assert result['name'] == 'Alice' +``` + +```go !! go +// Go - 基准测试 +package main + +import ( + "encoding/json" + "testing" +) + +func BenchmarkParseJSON(b *testing.B) { + data := []byte(`{"name": "Alice", "age": 30}`) + + b.ResetTimer() // 在实际基准测试前重置计时器 + for i := 0; i < b.N; i++ { + var result map[string]interface{} + json.Unmarshal(data, &result) + } +} + +// 运行: go test -bench=. +``` + + +## HTTP 测试 + + +```python !! py +# Python - pytest with Flask 测试客户端 +def test_home_page(client): + response = client.get('/') + assert response.status_code == 200 + assert b'Welcome' in response.data + +def test_api_endpoint(client): + response = client.post('/api/users', json={'name': 'Alice'}) + assert response.status_code == 201 +``` + +```go !! go +// Go - HTTP 测试 +package main + +import ( + "net/http" + "net/http/httptest" + "testing" +) + +func TestHomeHandler(t *testing.T) { + req := httptest.NewRequest("GET", "/", nil) + w := httptest.NewRecorder() + + homeHandler(w, req) + + if w.Code != http.StatusOK { + t.Errorf("Expected status 200, got %d", w.Code) + } + + if !contains(w.Body.String(), "Welcome") { + t.Error("Response should contain 'Welcome'") + } +} + +func TestCreateUserHandler(t *testing.T) { + jsonData := `{"name": "Alice"}` + req := httptest.NewRequest("POST", "/api/users", strings.NewReader(jsonData)) + req.Header.Set("Content-Type", "application/json") + w := httptest.NewRecorder() + + createUserHandler(w, req) + + if w.Code != http.StatusCreated { + t.Errorf("Expected status 201, got %d", w.Code) + } +} +``` + + +## 竞态条件测试 + + +```python !! py +# Python - 没有内置的竞态检测 +# 使用线程安全分析器等工具 +``` + +```go !! go +// Go - 竞态检测器 +package main + +import ( + "sync" + "testing" +) + +func TestConcurrentAccess(t *testing.T) { + var counter int + var wg sync.WaitGroup + + for i := 0; i < 100; i++ { + wg.Add(1) + go func() { + defer wg.Done() + counter++ // 数据竞争! + }() + } + + wg.Wait() +} + +// 运行: go test -race +// 输出将警告数据竞争 +``` + + +## 覆盖率报告 + + +```bash +# Python - pytest with coverage +pytest --cov=src --cov-report=html + +# 输出: 覆盖率报告 +# Name Stmts Miss Cover +# --------------------------------------- +# src/calculator.py 20 2 90% +``` + +```bash +# Go - 内置覆盖率 +go test -coverprofile=coverage.out +go tool cover -html=coverage.out + +# 输出: 浏览器中的覆盖率报告 +# coverage: 85.7% of statements +``` + + +## 调试 + + +```python !! py +# Python - pdb +import pdb; pdb.set_trace() + +# 或使用 breakpoint() (Python 3.7+) +def my_function(): + breakpoint() # 执行在此停止 + x = 42 + return x +``` + +```go !! go +// Go - Delve 调试器 +package main + +func main() { + x := 42 + // 在 Delve 中设置断点 + println(x) +} + +// 运行: +// dlv debug main.go +// (dlv) break main.go:8 +// (dlv) continue +``` + + +## 日志 + + +```python !! py +# Python - logging 模块 +import logging + +logging.basicConfig(level=logging.DEBUG) +logger = logging.getLogger(__name__) + +def process(data): + logger.debug(f"Processing: {data}") + result = transform(data) + logger.info(f"Result: {result}") + return result +``` + +```go !! go +// Go - log 包 +package main + +import "log" + +func process(data string) string { + log.Printf("Processing: %s", data) + result := transform(data) + log.Printf("Result: %s", result) + return result +} + +// 或使用结构化日志 +import "go.uber.org/zap" + +func processWithLogger(data string) string { + logger := zap.NewExample() + logger.Debug("Processing", zap.String("data", data)) + return transform(data) +} +``` + + +## 示例测试 + + +```python !! py +# Python - doctests +def add(a, b): + """ + 加两个数。 + + >>> add(2, 3) + 5 + >>> add(-1, 1) + 0 + """ + return a + b + +# 运行: python -m doctest file.py +``` + +```go !! go +// Go - 示例测试 +package main + +import "fmt" + +func ExampleAdd() { + result := Add(2, 3) + fmt.Println(result) + // Output: 5 +} + +func ExampleAdd_multiple() { + fmt.Println(Add(1, 2)) + fmt.Println(Add(-1, 1)) + // Output: + // 3 + // 0 +} + +// 运行: go test +``` + + +## 子测试和并行测试 + + +```python !! py +# Python - pytest 类 +class TestUserOperations: + def test_create(self): + pass + + def test_update(self): + pass + + def test_delete(self): + pass +``` + +```go !! go +// Go - 子测试和并行测试 +package main + +import "testing" + +func TestUserOperations(t *testing.T) { + tests := []struct { + name string + test func(*testing.T) + }{ + {"create", func(t *testing.T) { + // 测试创建 + }}, + {"update", func(t *testing.T) { + // 测试更新 + }}, + {"delete", func(t *testing.T) { + // 测试删除 + }}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() // 并行运行 + tt.test(t) + }) + } +} + +// 运行: go test -parallel 4 +``` + + +## 测试最佳实践 + + +```python !! py +# Python - 测试结构 +class TestCalculator: + def setup_method(self): + self.calc = Calculator() + + def teardown_method(self): + self.calc.cleanup() + + def test_add_positive_numbers(self): + assert self.calc.add(2, 3) == 5 +``` + +```go !! go +// Go - 测试组织 +package main + +import "testing" + +func TestCalculator(t *testing.T) { + // 设置 + calc := NewCalculator() + defer calc.Cleanup() + + t.Run("AddPositiveNumbers", func(t *testing.T) { + result := calc.Add(2, 3) + if result != 5 { + t.Errorf("Expected 5, got %d", result) + } + }) + + t.Run("AddNegativeNumbers", func(t *testing.T) { + result := calc.Add(-2, -3) + if result != -5 { + t.Errorf("Expected -5, got %d", result) + } + }) +} +``` + + +## 总结 + +在本模块中,你学习了: + +1. **testing 包** - 内置测试支持 +2. **表驱动测试** - Go 的惯用测试模式 +3. **测试辅助** - 设置和清理 +4. **错误测试** - 检查错误返回值 +5. **Mock** - 使用接口 +6. **基准测试** - 性能测试 +7. **HTTP 测试** - httptest 包 +8. **竞态检测** - `-race` 标志 +9. **覆盖率** - 内置覆盖率报告 +10. **调试** - Delve 调试器 +11. **日志** - log 包用于调试 + +## 主要区别 + +| Python | Go | +|--------|-----| +| pytest/unittest | 内置 `testing` | +| `@pytest.mark.parametrize` | 表驱动测试 | +| `pytest.raises()` | 检查错误返回值 | +| `unittest.mock` | 接口用于 mock | +| `--cov` 标志 | `-coverprofile` | +| pdb 调试器 | Delve 调试器 | +| 没有竞态检测 | `-race` 标志 | + +## 练习 + +1. 为计算器包编写全面的测试 +2. 创建基准测试比较字符串连接方法 +3. 测试使用各种输入的 HTTP 处理器 +4. 使用竞态检测器发现并发 bug +5. 为你的代码生成覆盖率报告 + +## 下一步 + +下一模块:**性能优化** - 分析和优化 Go 应用程序。 diff --git a/content/docs/py2go/module-11-testing-debugging.zh-tw.mdx b/content/docs/py2go/module-11-testing-debugging.zh-tw.mdx new file mode 100644 index 0000000..619cbc0 --- /dev/null +++ b/content/docs/py2go/module-11-testing-debugging.zh-tw.mdx @@ -0,0 +1,664 @@ +--- +title: "模組 11: 測試和除錯" +description: "Go 中的測試方法和除錯技巧" +--- + +## 簡介 + +Go 內建了對測試的支援,使用 `testing` 包。與 Python 需要使用 pytest 或 unittest 不同,Go 的測試是標準庫的一部分,遵循簡單的約定。 + +## 基本測試 + + +```python !! py +# Python - pytest +def test_add(): + assert add(2, 3) == 5 + +def test_add_negative(): + assert add(-1, 1) == 0 + +# 執行: pytest test_file.py +``` + +```go !! go +// Go - testing 包 +package main + +import "testing" + +func TestAdd(t *testing.T) { + result := Add(2, 3) + if result != 5 { + t.Errorf("Add(2, 3) = %d; want 5", result) + } +} + +func TestAddNegative(t *testing.T) { + result := Add(-1, 1) + if result != 0 { + t.Errorf("Add(-1, 1) = %d; want 0", result) + } +} + +// 執行: go test +``` + + +## 測試檔案組織 + + +```bash +# Python +project/ +├── src/ +│ ├── __init__.py +│ └── calculator.py +└── tests/ + ├── __init__.py + └── test_calculator.py +``` + +```bash +# Go +project/ +├── calculator.go +└── calculator_test.go // 同一個包,_test.go 後綴 +``` + + +## 表驅動測試 + +Go 測試多個用例的慣用方式: + + +```python !! py +# Python - parametrize +import pytest + +@pytest.mark.parametrize("a,b,expected", [ + (1, 2, 3), + (0, 0, 0), + (-1, 1, 0), + (100, 200, 300), +]) +def test_add(a, b, expected): + assert add(a, b) == expected +``` + +```go !! go +// Go - 表驅動測試 +package main + +import "testing" + +func TestAdd(t *testing.T) { + tests := []struct { + name string + a, b int + expected int + }{ + {"basic", 1, 2, 3}, + {"zeros", 0, 0, 0}, + {"negative", -1, 1, 0}, + {"large", 100, 200, 300}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := Add(tt.a, tt.b) + if result != tt.expected { + t.Errorf("Add(%d, %d) = %d; want %d", + tt.a, tt.b, result, tt.expected) + } + }) + } +} +``` + + +## 測試輔助和設置 + + +```python !! py +# Python - pytest fixtures +import pytest + +@pytest.fixture +def database(): + db = Database(":memory:") + db.init() + yield db + db.cleanup() + +def test_query(database): + result = database.query("SELECT * FROM users") + assert len(result) > 0 +``` + +```go !! go +// Go - 設置和清理 +package main + +import ( + "testing" +) + +func setupTestDB(t *testing.T) *Database { + db := &Database{Path: ":memory:"} + if err := db.Init(); err != nil { + t.Fatalf("Failed to init DB: %v", err) + } + return db +} + +func TestQuery(t *testing.T) { + db := setupTestDB(t) + defer db.Cleanup() + + result := db.Query("SELECT * FROM users") + if len(result) == 0 { + t.Error("Expected results, got none") + } +} +``` + + +## 錯誤測試 + + +```python !! py +# Python - pytest +import pytest + +def test_divide_by_zero(): + with pytest.raises(ValueError): + divide(10, 0) + +def test_file_not_found(): + with pytest.raises(FileNotFoundError): + read_file("nonexistent.txt") +``` + +```go !! go +// Go - 測試錯誤 +package main + +import "testing" + +func TestDivideByZero(t *testing.T) { + result, err := divide(10, 0) + if err == nil { + t.Error("Expected error for division by zero, got nil") + } + if result != 0 { + t.Errorf("Expected result 0, got %d", result) + } +} + +func TestFileNotFound(t *testing.T) { + _, err := ReadFile("nonexistent.txt") + if err == nil { + t.Error("Expected error for nonexistent file, got nil") + } +} +``` + + +## Mock 和介面 + + +```python !! py +# Python - mocking +from unittest.mock import Mock, patch + +def test_send_notification(): + mock_service = Mock() + mock_service.send.return_value = True + + with patch('app.notification_service', mock_service): + result = send_notification("Hello") + assert result == True + mock_service.send.assert_called_once_with("Hello") +``` + +```go !! go +// Go - 使用介面 Mock +package main + +import "testing" + +// 介面 +type NotificationService interface { + Send(message string) bool +} + +// Mock 實作 +type MockNotificationService struct { + SendCalled bool + Message string +} + +func (m *MockNotificationService) Send(message string) bool { + m.SendCalled = true + m.Message = message + return true +} + +func TestSendNotification(t *testing.T) { + mock := &MockNotificationService{} + + result := SendNotification(mock, "Hello") + + if !result { + t.Error("Expected true, got false") + } + if !mock.SendCalled { + t.Error("Send was not called") + } + if mock.Message != "Hello" { + t.Errorf("Wrong message: %s", mock.Message) + } +} +``` + + +## 基準測試 + + +```python !! py +# Python - pytest-benchmark +import pytest + +def test_parse_json(benchmark): + data = '{"name": "Alice", "age": 30}' + result = benchmark(json.loads, data) + assert result['name'] == 'Alice' +``` + +```go !! go +// Go - 基準測試 +package main + +import ( + "encoding/json" + "testing" +) + +func BenchmarkParseJSON(b *testing.B) { + data := []byte(`{"name": "Alice", "age": 30}`) + + b.ResetTimer() // 在實際基準測試前重置計時器 + for i := 0; i < b.N; i++ { + var result map[string]interface{} + json.Unmarshal(data, &result) + } +} + +// 執行: go test -bench=. +``` + + +## HTTP 測試 + + +```python !! py +# Python - pytest with Flask 測試客戶端 +def test_home_page(client): + response = client.get('/') + assert response.status_code == 200 + assert b'Welcome' in response.data + +def test_api_endpoint(client): + response = client.post('/api/users', json={'name': 'Alice'}) + assert response.status_code == 201 +``` + +```go !! go +// Go - HTTP 測試 +package main + +import ( + "net/http" + "net/http/httptest" + "testing" +) + +func TestHomeHandler(t *testing.T) { + req := httptest.NewRequest("GET", "/", nil) + w := httptest.NewRecorder() + + homeHandler(w, req) + + if w.Code != http.StatusOK { + t.Errorf("Expected status 200, got %d", w.Code) + } + + if !contains(w.Body.String(), "Welcome") { + t.Error("Response should contain 'Welcome'") + } +} + +func TestCreateUserHandler(t *testing.T) { + jsonData := `{"name": "Alice"}` + req := httptest.NewRequest("POST", "/api/users", strings.NewReader(jsonData)) + req.Header.Set("Content-Type", "application/json") + w := httptest.NewRecorder() + + createUserHandler(w, req) + + if w.Code != http.StatusCreated { + t.Errorf("Expected status 201, got %d", w.Code) + } +} +``` + + +## 競態條件測試 + + +```python !! py +# Python - 沒有內建的競態偵測 +# 使用線程安全分析器等工具 +``` + +```go !! go +// Go - 競態偵測器 +package main + +import ( + "sync" + "testing" +) + +func TestConcurrentAccess(t *testing.T) { + var counter int + var wg sync.WaitGroup + + for i := 0; i < 100; i++ { + wg.Add(1) + go func() { + defer wg.Done() + counter++ // 資料競爭! + }() + } + + wg.Wait() +} + +// 執行: go test -race +// 輸出將警告資料競爭 +``` + + +## 覆蓋率報告 + + +```bash +# Python - pytest with coverage +pytest --cov=src --cov-report=html + +# 輸出: 覆蓋率報告 +# Name Stmts Miss Cover +# --------------------------------------- +# src/calculator.py 20 2 90% +``` + +```bash +# Go - 內建覆蓋率 +go test -coverprofile=coverage.out +go tool cover -html=coverage.out + +# 輸出: 瀏覽器中的覆蓋率報告 +# coverage: 85.7% of statements +``` + + +## 除錯 + + +```python !! py +# Python - pdb +import pdb; pdb.set_trace() + +# 或使用 breakpoint() (Python 3.7+) +def my_function(): + breakpoint() # 執行在此停止 + x = 42 + return x +``` + +```go !! go +// Go - Delve 除錯器 +package main + +func main() { + x := 42 + // 在 Delve 中設定斷點 + println(x) +} + +// 執行: +// dlv debug main.go +// (dlv) break main.go:8 +// (dlv) continue +``` + + +## 日誌 + + +```python !! py +# Python - logging 模組 +import logging + +logging.basicConfig(level=logging.DEBUG) +logger = logging.getLogger(__name__) + +def process(data): + logger.debug(f"Processing: {data}") + result = transform(data) + logger.info(f"Result: {result}") + return result +``` + +```go !! go +// Go - log 包 +package main + +import "log" + +func process(data string) string { + log.Printf("Processing: %s", data) + result := transform(data) + log.Printf("Result: %s", result) + return result +} + +// 或使用結構化日誌 +import "go.uber.org/zap" + +func processWithLogger(data string) string { + logger := zap.NewExample() + logger.Debug("Processing", zap.String("data", data)) + return transform(data) +} +``` + + +## 示例測試 + + +```python !! py +# Python - doctests +def add(a, b): + """ + 加兩個數。 + + >>> add(2, 3) + 5 + >>> add(-1, 1) + 0 + """ + return a + b + +# 執行: python -m doctest file.py +``` + +```go !! go +// Go - 示例測試 +package main + +import "fmt" + +func ExampleAdd() { + result := Add(2, 3) + fmt.Println(result) + // Output: 5 +} + +func ExampleAdd_multiple() { + fmt.Println(Add(1, 2)) + fmt.Println(Add(-1, 1)) + // Output: + // 3 + // 0 +} + +// 執行: go test +``` + + +## 子測試和並行測試 + + +```python !! py +# Python - pytest 類別 +class TestUserOperations: + def test_create(self): + pass + + def test_update(self): + pass + + def test_delete(self): + pass +``` + +```go !! go +// Go - 子測試和並行測試 +package main + +import "testing" + +func TestUserOperations(t *testing.T) { + tests := []struct { + name string + test func(*testing.T) + }{ + {"create", func(t *testing.T) { + // 測試建立 + }}, + {"update", func(t *testing.T) { + // 測試更新 + }}, + {"delete", func(t *testing.T) { + // 測試刪除 + }}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() // 並行執行 + tt.test(t) + }) + } +} + +// 執行: go test -parallel 4 +``` + + +## 測試最佳實踐 + + +```python !! py +# Python - 測試結構 +class TestCalculator: + def setup_method(self): + self.calc = Calculator() + + def teardown_method(self): + self.calc.cleanup() + + def test_add_positive_numbers(self): + assert self.calc.add(2, 3) == 5 +``` + +```go !! go +// Go - 測試組織 +package main + +import "testing" + +func TestCalculator(t *testing.T) { + // 設置 + calc := NewCalculator() + defer calc.Cleanup() + + t.Run("AddPositiveNumbers", func(t *testing.T) { + result := calc.Add(2, 3) + if result != 5 { + t.Errorf("Expected 5, got %d", result) + } + }) + + t.Run("AddNegativeNumbers", func(t *testing.T) { + result := calc.Add(-2, -3) + if result != -5 { + t.Errorf("Expected -5, got %d", result) + } + }) +} +``` + + +## 總結 + +在本模組中,你學習了: + +1. **testing 包** - 內建測試支援 +2. **表驅動測試** - Go 的慣用測試模式 +3. **測試輔助** - 設置和清理 +4. **錯誤測試** - 檢查錯誤返回值 +5. **Mock** - 使用介面 +6. **基準測試** - 效能測試 +7. **HTTP 測試** - httptest 包 +8. **競態偵測** - `-race` 標誌 +9. **覆蓋率** - 內建覆蓋率報告 +10. **除錯** - Delve 除錯器 +11. **日誌** - log 包用於除錯 + +## 主要差別 + +| Python | Go | +|--------|-----| +| pytest/unittest | 內建 `testing` | +| `@pytest.mark.parametrize` | 表驅動測試 | +| `pytest.raises()` | 檢查錯誤返回值 | +| `unittest.mock` | 介面用於 mock | +| `--cov` 標誌 | `-coverprofile` | +| pdb 除錯器 | Delve 除錯器 | +| 沒有競態偵測 | `-race` 標誌 | + +## 練習 + +1. 為計算器包撰寫全面的測試 +2. 建立基準測試比較字串串連方法 +3. 測試使用各種輸入的 HTTP 處理器 +4. 使用競態偵測器發現並行 bug +5. 為你的程式碼產生覆蓋率報告 + +## 下一步 + +下一模組:**效能優化** - 分析和優化 Go 應用程式。 diff --git a/content/docs/py2go/module-12-performance-optimization.mdx b/content/docs/py2go/module-12-performance-optimization.mdx new file mode 100644 index 0000000..5a8030b --- /dev/null +++ b/content/docs/py2go/module-12-performance-optimization.mdx @@ -0,0 +1,715 @@ +--- +title: "Module 12: Performance Optimization" +description: "Profiling and optimizing Go applications" +--- + +## Introduction + +Go is designed for performance, but understanding how to profile and optimize code is crucial. This module covers profiling tools and optimization techniques. + +## Profiling with pprof + +Go includes powerful profiling tools in the standard library: + + +```python !! py +# Python - cProfile +import cProfile + +def my_function(): + # Code to profile + pass + +cProfile.run('my_function()', 'output.stats') + +# Or use line_profiler +# @profile +# def my_function(): +# pass +``` + +```go !! go +// Go - CPU profiling +package main + +import ( + "os" + "runtime/pprof" +) + +func main() { + // Start CPU profiling + f, _ := os.Create("cpu.prof") + pprof.StartCPUProfile(f) + defer pprof.StopCPUProfile() + + // Your code here + myFunction() +} + +// Run program, then: +// go tool pprof cpu.prof +``` + + +## Memory Profiling + + +```python !! py +# Python - memory_profiler +from memory_profiler import profile + +@profile +def my_function(): + data = [x for x in range(1000000)] + return data + +if __name__ == '__main__': + my_function() +``` + +```go !! go +// Go - Memory profiling +package main + +import ( + "os" + "runtime/pprof" +) + +func main() { + myFunction() + + // Write memory profile + f, _ := os.Create("mem.prof") + pprof.WriteHeapProfile(f) + f.Close() +} + +// Analyze with: +// go tool pprof mem.prof +``` + + +## Benchmarking and Comparison + + +```python !! py +# Python - String concatenation +import time + +def test_concatenate(n): + start = time.time() + result = "" + for i in range(n): + result += str(i) + return time.time() - start + +def test_join(n): + start = time.time() + parts = [str(i) for i in range(n)] + result = "".join(parts) + return time.time() - start + +n = 10000 +print(f"Concatenate: {test_concatenate(n):.4f}s") +print(f"Join: {test_join(n):.4f}s") + +# Concatenate is much slower! +``` + +```go !! go +// Go - String concatenation benchmark +package main + +import ( + "fmt" + "strconv" + "strings" + "testing" +) + +func BenchmarkConcatenate(b *testing.B) { + for i := 0; i < b.N; i++ { + result := "" + for j := 0; j < 100; j++ { + result += strconv.Itoa(j) + } + } +} + +func BenchmarkStringsBuilder(b *testing.B) { + for i := 0; i < b.N; i++ { + var builder strings.Builder + for j := 0; j < 100; j++ { + builder.WriteString(strconv.Itoa(j)) + } + _ = builder.String() + } +} + +// Run: go test -bench=. +// BenchmarkConcatenate-8 50000 35000 ns/op +// BenchmarkStringsBuilder-8 500000 3500 ns/op +// Builder is 10x faster! +``` + + +## Slice vs Map Performance + + +```python !! py +# Python - List vs Dict lookup +import time + +def test_list_lookup(n): + items = list(range(n)) + start = time.time() + for i in range(n): + _ = i in items # O(n) lookup + return time.time() - start + +def test_dict_lookup(n): + items = {i: i for i in range(n)} + start = time.time() + for i in range(n): + _ = i in items # O(1) lookup + return time.time() - start + +n = 10000 +print(f"List: {test_list_lookup(n):.4f}s") +print(f"Dict: {test_dict_lookup(n):.4f}s") +``` + +```go !! go +// Go - Slice vs Map performance +package main + +import "testing" + +func BenchmarkSliceLookup(b *testing.B) { + items := make([]int, 1000) + for i := range items { + items[i] = i + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + for j := 0; j < 1000; j++ { + _ = contains(items, j) // O(n) + } + } +} + +func BenchmarkMapLookup(b *testing.B) { + items := make(map[int]bool) + for i := 0; i < 1000; i++ { + items[i] = true + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + for j := 0; j < 1000; j++ { + _ = items[j] // O(1) + } + } +} + +func contains(slice []int, item int) bool { + for _, v := range slice { + if v == item { + return true + } + } + return false +} + +// Map is significantly faster for lookups +``` + + +## Goroutine Pool Pattern + + +```python !! py +# Python - ThreadPoolExecutor +from concurrent.futures import ThreadPoolExecutor + +def process_task(task_id): + return task_id * 2 + +with ThreadPoolExecutor(max_workers=10) as executor: + results = executor.map(process_task, range(1000)) +``` + +```go !! go +// Go - Worker pool (limit goroutines) +package main + +import ( + "fmt" + "sync" + "time" +) + +func worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) { + defer wg.Done() + for job := range jobs { + // Simulate work + time.Sleep(time.Millisecond) + results <- job * 2 + } +} + +func main() { + jobs := make(chan int, 100) + results := make(chan int, 100) + + var wg sync.WaitGroup + + // Start 10 workers (limited goroutines) + for w := 1; w <= 10; w++ { + wg.Add(1) + go worker(w, jobs, results, &wg) + } + + // Send jobs + for j := 0; j < 1000; j++ { + jobs <- j + } + close(jobs) + + // Wait for workers to finish + go func() { + wg.Wait() + close(results) + }() + + // Collect results + for result := range results { + fmt.Println(result) + } +} +``` + + +## Efficient JSON Processing + + +```python !! py +# Python - json module +import json +import time + +data = {"users": [{"id": i, "name": f"User{i}"} for i in range(1000)]} + +# Serialize +start = time.time() +json_str = json.dumps(data) +print(f"Serialize: {time.time() - start:.4f}s") + +# Deserialize +start = time.time() +parsed = json.loads(json_str) +print(f"Deserialize: {time.time() - start:.4f}s") +``` + +```go !! go +// Go - Efficient JSON with streaming +package main + +import ( + "encoding/json" + "os" + "testing" +) + +type User struct { + ID int `json:"id"` + Name string `json:"name"` +} + +type Data struct { + Users []User `json:"users"` +} + +func BenchmarkJSONMarshal(b *testing.B) { + users := make([]User, 1000) + for i := range users { + users[i] = User{ID: i, Name: fmt.Sprintf("User%d", i)} + } + data := Data{Users: users} + + b.ResetTimer() + for i := 0; i < b.N; i++ { + json.Marshal(data) + } +} + +func BenchmarkJSONStream(b *testing.B) { + users := make([]User, 1000) + for i := range users { + users[i] = User{ID: i, Name: fmt.Sprintf("User%d", i)} + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + encoder := json.NewEncoder(os.Stdout) + encoder.Encode(users) + } +} + +// Streaming is more memory-efficient +``` + + +## Caching with sync.Map + + +```python !! py +# Python - lru_cache with thread safety +from functools import lru_cache +import threading + +@lru_cache(maxsize=1000) +def expensive_computation(n): + return n * n + +# Or use custom cache +cache = {} +lock = threading.Lock() + +def get_value(key): + with lock: + if key not in cache: + cache[key] = expensive_computation(key) + return cache[key] +``` + +```go !! go +// Go - sync.Map for concurrent access +package main + +import ( + "sync" + "testing" +) + +// Regular map with mutex (better for most cases) +type Cache struct { + mu sync.RWMutex + data map[int]int +} + +func NewCache() *Cache { + return &Cache{ + data: make(map[int]int), + } +} + +func (c *Cache) Get(key int) (int, bool) { + c.mu.RLock() + defer c.mu.RUnlock() + val, ok := c.data[key] + return val, ok +} + +func (c *Cache) Set(key, value int) { + c.mu.Lock() + defer c.mu.Unlock() + c.data[key] = value +} + +// sync.Map (good for cache-like workloads) +func BenchmarkMapWithMutex(b *testing.B) { + cache := NewCache() + cache.Set(1, 100) + + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + cache.Get(1) + } + }) +} + +func BenchmarkSyncMap(b *testing.B) { + var m sync.Map + m.Store(1, 100) + + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + m.Load(1) + } + }) +} +``` + + +## Avoiding Memory Allocations + + +```python !! py +# Python - Reuse objects (limited control) +class Buffer: + def __init__(self): + self.data = [] + + def process(self, items): + # Reuse list instead of creating new + self.data.clear() + self.data.extend(items) + return self.data +``` + +```go !! go +// Go - Reuse buffers to reduce allocations +package main + +import "testing" + +func AllocateNew() []int { + // New allocation each time + return make([]int, 1000) +} + +var buffer = make([]int, 1000) + +func ReuseBuffer() []int { + // Reuse buffer (zero it first) + for i := range buffer { + buffer[i] = 0 + } + return buffer +} + +func BenchmarkAllocate(b *testing.B) { + for i := 0; i < b.N; i++ { + _ = AllocateNew() + } +} + +func BenchmarkReuse(b *testing.B) { + for i := 0; i < b.N; i++ { + _ = ReuseBuffer() + } +} + +// ReuseBuffer is much faster (no allocations) +``` + + +## Efficient String Operations + + +```python !! py +# Python - Strings are immutable +data = "hello world" +# data[0] = 'H' # TypeError! + +# Must create new string +data = "H" + data[1:] + +# For mutable data, use list +chars = list(data) +chars[0] = 'H' +data = ''.join(chars) +``` + +```go !! go +// Go - Use []byte for mutable data +package main + +import "testing" + +func processString(s string) string { + // Strings are immutable - creates new string + return "H" + s[1:] +} + +func processBytes(b []byte) []byte { + // Bytes are mutable - modifies in place + if len(b) > 0 { + b[0] = 'H' + } + return b +} + +func BenchmarkString(b *testing.B) { + for i := 0; i < b.N; i++ { + _ = processString("hello world") + } +} + +func BenchmarkBytes(b *testing.B) { + for i := 0; i < b.N; i++ { + data := []byte("hello world") + _ = processBytes(data) + } +} + +// Bytes version avoids allocations +``` + + +## Performance Monitoring + + +```python !! py +# Python - psutil for monitoring +import psutil +import time + +def monitor(): + process = psutil.Process() + print(f"CPU: {process.cpu_percent()}%") + print(f"Memory: {process.memory_info().rss / 1024 / 1024:.2f} MB") + print(f"Threads: {process.num_threads()}") +``` + +```go !! go +// Go - Runtime metrics +package main + +import ( + "fmt" + "runtime" + "time" +) + +func main() { + var m runtime.MemStats + + // Print stats every second + for i := 0; i < 5; i++ { + runtime.ReadMemStats(&m) + + fmt.Printf("Goroutines: %d\n", runtime.NumGoroutine()) + fmt.Printf("Memory: %.2f MB\n", float64(m.Alloc)/1024/1024) + fmt.Printf("GC cycles: %d\n", m.NumGC) + + time.Sleep(time.Second) + } +} +``` + + +## HTTP Performance Tips + + +```python !! py +# Python - Performance considerations +from flask import Flask + +app = Flask(__name__) + +# Use JSON encoder for performance +app.json_encoder = MyCustomEncoder + +# Enable gzip compression +from flask_compress import Compress +Compress(app) + +# Use connection pooling +import requests +session = requests.Session() +session.get('http://example.com') +``` + +```go !! go +// Go - HTTP performance tips +package main + +import ( + "compress/gzip" + "net/http" + "sync" +) + +// Connection pool +var transport = &http.Transport{ + MaxIdleConns: 100, + MaxIdleConnsPerHost: 100, + IdleConnTimeout: 90 * time.Second, +} + +var client = &http.Client{ + Transport: transport, + Timeout: 30 * time.Second, +} + +// Response compression +func gzipHandler(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if acceptsGzip(r) { + w = gzip.NewWriter(w) + defer w.(interface{ Flush() }).Flush() + } + next.ServeHTTP(w, r) + }) +} + +// Reuse buffers +var bufferPool = sync.Pool{ + New: func() interface{} { + return make([]byte, 1024) + }, +} + +func getBuffer() []byte { + return bufferPool.Get().([]byte) +} + +func putBuffer(b []byte) { + bufferPool.Put(b) +} +``` + + +## Summary + +In this module, you learned: + +1. **CPU profiling** with `pprof` +2. **Memory profiling** and heap analysis +3. **Benchmarking** with `testing` package +4. **String optimization** - use `strings.Builder` +5. **Data structure selection** - map vs slice +6. **Goroutine pools** - limit concurrency +7. **JSON streaming** for large data +8. **Concurrent caching** - `sync.Map` vs mutex+map +9. **Buffer reuse** - reduce allocations +10. **Byte vs string** - mutable byte arrays +11. **Runtime metrics** - monitor performance +12. **HTTP optimization** - pooling, compression, reuse + +## Performance Tips Summary + +| Area | Python | Go Optimization | +|------|--------|-----------------| +| String concatenation | Use `join()` | Use `strings.Builder` | +| Large datasets | Generators | Streaming, buffers | +| Concurrency | `ThreadPoolExecutor` | Worker pools | +| Caching | `lru_cache` | `sync.Map` or mutex+map | +| Memory | Limited control | Buffer pools, reuse | +| Profiling | `cProfile`, `memory_profiler` | Built-in `pprof` | + +## Common Performance Pitfalls + +1. **String concatenation in loops** - Use `strings.Builder` +2. **Excessive allocations** - Reuse buffers +3. **Unlimited goroutines** - Use worker pools +4. **Mutex contention** - Use RWMutex for read-heavy workloads +5. **Blocking in select** - Add default case for non-blocking +6. **Large struct copies** - Use pointers + +## Exercises + +1. Profile a Go application and find bottlenecks +2. Benchmark different string concatenation methods +3. Implement a worker pool for concurrent processing +4. Optimize JSON processing for large files +5. Compare sync.Map vs mutex-protected map performance + +## Next Steps + +Next module: **Microservices Architecture** - Building distributed systems with Go. diff --git a/content/docs/py2go/module-12-performance-optimization.zh-cn.mdx b/content/docs/py2go/module-12-performance-optimization.zh-cn.mdx new file mode 100644 index 0000000..2506e52 --- /dev/null +++ b/content/docs/py2go/module-12-performance-optimization.zh-cn.mdx @@ -0,0 +1,715 @@ +--- +title: "模块 12: 性能优化" +description: "分析和优化 Go 应用程序" +--- + +## 简介 + +Go 是为性能而设计的,但了解如何分析和优化代码至关重要。本模块介绍性能分析工具和优化技巧。 + +## 使用 pprof 进行性能分析 + +Go 在标准库中包含了强大的性能分析工具: + + +```python !! py +# Python - cProfile +import cProfile + +def my_function(): + # 要分析的代码 + pass + +cProfile.run('my_function()', 'output.stats') + +# 或使用 line_profiler +# @profile +# def my_function(): +# pass +``` + +```go !! go +// Go - CPU 性能分析 +package main + +import ( + "os" + "runtime/pprof" +) + +func main() { + // 开始 CPU 性能分析 + f, _ := os.Create("cpu.prof") + pprof.StartCPUProfile(f) + defer pprof.StopCPUProfile() + + // 你的代码 + myFunction() +} + +// 运行程序后: +// go tool pprof cpu.prof +``` + + +## 内存性能分析 + + +```python !! py +# Python - memory_profiler +from memory_profiler import profile + +@profile +def my_function(): + data = [x for x in range(1000000)] + return data + +if __name__ == '__main__': + my_function() +``` + +```go !! go +// Go - 内存性能分析 +package main + +import ( + "os" + "runtime/pprof" +) + +func main() { + myFunction() + + // 写入内存性能分析 + f, _ := os.Create("mem.prof") + pprof.WriteHeapProfile(f) + f.Close() +} + +// 使用以下命令分析: +// go tool pprof mem.prof +``` + + +## 基准测试和对比 + + +```python !! py +# Python - 字符串连接 +import time + +def test_concatenate(n): + start = time.time() + result = "" + for i in range(n): + result += str(i) + return time.time() - start + +def test_join(n): + start = time.time() + parts = [str(i) for i in range(n)] + result = "".join(parts) + return time.time() - start + +n = 10000 +print(f"Concatenate: {test_concatenate(n):.4f}s") +print(f"Join: {test_join(n):.4f}s") + +# Concatenate 慢得多! +``` + +```go !! go +// Go - 字符串连接基准测试 +package main + +import ( + "fmt" + "strconv" + "strings" + "testing" +) + +func BenchmarkConcatenate(b *testing.B) { + for i := 0; i < b.N; i++ { + result := "" + for j := 0; j < 100; j++ { + result += strconv.Itoa(j) + } + } +} + +func BenchmarkStringsBuilder(b *testing.B) { + for i := 0; i < b.N; i++ { + var builder strings.Builder + for j := 0; j < 100; j++ { + builder.WriteString(strconv.Itoa(j)) + } + _ = builder.String() + } +} + +// 运行: go test -bench=. +// BenchmarkConcatenate-8 50000 35000 ns/op +// BenchmarkStringsBuilder-8 500000 3500 ns/op +// Builder 快 10 倍! +``` + + +## 切片 vs Map 性能 + + +```python !! py +# Python - List vs Dict 查找 +import time + +def test_list_lookup(n): + items = list(range(n)) + start = time.time() + for i in range(n): + _ = i in items # O(n) 查找 + return time.time() - start + +def test_dict_lookup(n): + items = {i: i for i in range(n)} + start = time.time() + for i in range(n): + _ = i in items # O(1) 查找 + return time.time() - start + +n = 10000 +print(f"List: {test_list_lookup(n):.4f}s") +print(f"Dict: {test_dict_lookup(n):.4f}s") +``` + +```go !! go +// Go - 切片 vs Map 性能 +package main + +import "testing" + +func BenchmarkSliceLookup(b *testing.B) { + items := make([]int, 1000) + for i := range items { + items[i] = i + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + for j := 0; j < 1000; j++ { + _ = contains(items, j) // O(n) + } + } +} + +func BenchmarkMapLookup(b *testing.B) { + items := make(map[int]bool) + for i := 0; i < 1000; i++ { + items[i] = true + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + for j := 0; j < 1000; j++ { + _ = items[j] // O(1) + } + } +} + +func contains(slice []int, item int) bool { + for _, v := range slice { + if v == item { + return true + } + } + return false +} + +// Map 在查找方面明显更快 +``` + + +## Goroutine 池模式 + + +```python !! py +# Python - ThreadPoolExecutor +from concurrent.futures import ThreadPoolExecutor + +def process_task(task_id): + return task_id * 2 + +with ThreadPoolExecutor(max_workers=10) as executor: + results = executor.map(process_task, range(1000)) +``` + +```go !! go +// Go - Worker 池(限制 goroutines) +package main + +import ( + "fmt" + "sync" + "time" +) + +func worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) { + defer wg.Done() + for job := range jobs { + // 模拟工作 + time.Sleep(time.Millisecond) + results <- job * 2 + } +} + +func main() { + jobs := make(chan int, 100) + results := make(chan int, 100) + + var wg sync.WaitGroup + + // 启动 10 个 worker(限制 goroutines) + for w := 1; w <= 10; w++ { + wg.Add(1) + go worker(w, jobs, results, &wg) + } + + // 发送任务 + for j := 0; j < 1000; j++ { + jobs <- j + } + close(jobs) + + // 等待 worker 完成 + go func() { + wg.Wait() + close(results) + }() + + // 收集结果 + for result := range results { + fmt.Println(result) + } +} +``` + + +## 高效的 JSON 处理 + + +```python !! py +# Python - json 模块 +import json +import time + +data = {"users": [{"id": i, "name": f"User{i}"} for i in range(1000)]} + +# 序列化 +start = time.time() +json_str = json.dumps(data) +print(f"Serialize: {time.time() - start:.4f}s") + +# 反序列化 +start = time.time() +parsed = json.loads(json_str) +print(f"Deserialize: {time.time() - start:.4f}s") +``` + +```go !! go +// Go - 高效的 JSON 流式处理 +package main + +import ( + "encoding/json" + "os" + "testing" +) + +type User struct { + ID int `json:"id"` + Name string `json:"name"` +} + +type Data struct { + Users []User `json:"users"` +} + +func BenchmarkJSONMarshal(b *testing.B) { + users := make([]User, 1000) + for i := range users { + users[i] = User{ID: i, Name: fmt.Sprintf("User%d", i)} + } + data := Data{Users: users} + + b.ResetTimer() + for i := 0; i < b.N; i++ { + json.Marshal(data) + } +} + +func BenchmarkJSONStream(b *testing.B) { + users := make([]User, 1000) + for i := range users { + users[i] = User{ID: i, Name: fmt.Sprintf("User%d", i)} + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + encoder := json.NewEncoder(os.Stdout) + encoder.Encode(users) + } +} + +// 流式处理更节省内存 +``` + + +## 使用 sync.Map 缓存 + + +```python !! py +# Python - 带有线程安全的 lru_cache +from functools import lru_cache +import threading + +@lru_cache(maxsize=1000) +def expensive_computation(n): + return n * n + +# 或使用自定义缓存 +cache = {} +lock = threading.Lock() + +def get_value(key): + with lock: + if key not in cache: + cache[key] = expensive_computation(key) + return cache[key] +``` + +```go !! go +// Go - sync.Map 用于并发访问 +package main + +import ( + "sync" + "testing" +) + +// 带互斥锁的常规 map(大多数情况下更好) +type Cache struct { + mu sync.RWMutex + data map[int]int +} + +func NewCache() *Cache { + return &Cache{ + data: make(map[int]int), + } +} + +func (c *Cache) Get(key int) (int, bool) { + c.mu.RLock() + defer c.mu.RUnlock() + val, ok := c.data[key] + return val, ok +} + +func (c *Cache) Set(key, value int) { + c.mu.Lock() + defer c.mu.Unlock() + c.data[key] = value +} + +// sync.Map(适合缓存类工作负载) +func BenchmarkMapWithMutex(b *testing.B) { + cache := NewCache() + cache.Set(1, 100) + + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + cache.Get(1) + } + }) +} + +func BenchmarkSyncMap(b *testing.B) { + var m sync.Map + m.Store(1, 100) + + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + m.Load(1) + } + }) +} +``` + + +## 避免内存分配 + + +```python !! py +# Python - 重用对象(控制有限) +class Buffer: + def __init__(self): + self.data = [] + + def process(self, items): + # 重用列表而不是创建新的 + self.data.clear() + self.data.extend(items) + return self.data +``` + +```go !! go +// Go - 重用缓冲区以减少分配 +package main + +import "testing" + +func AllocateNew() []int { + // 每次都分配新的 + return make([]int, 1000) +} + +var buffer = make([]int, 1000) + +func ReuseBuffer() []int { + // 重用缓冲区(先清零) + for i := range buffer { + buffer[i] = 0 + } + return buffer +} + +func BenchmarkAllocate(b *testing.B) { + for i := 0; i < b.N; i++ { + _ = AllocateNew() + } +} + +func BenchmarkReuse(b *testing.B) { + for i := 0; i < b.N; i++ { + _ = ReuseBuffer() + } +} + +// ReuseBuffer 快得多(无分配) +``` + + +## 高效的字符串操作 + + +```python !! py +# Python - 字符串是不可变的 +data = "hello world" +# data[0] = 'H' # TypeError! + +# 必须创建新字符串 +data = "H" + data[1:] + +# 对于可变数据,使用 list +chars = list(data) +chars[0] = 'H' +data = ''.join(chars) +``` + +```go !! go +// Go - 使用 []byte 处理可变数据 +package main + +import "testing" + +func processString(s string) string { + // 字符串是不可变的 - 创建新字符串 + return "H" + s[1:] +} + +func processBytes(b []byte) []byte { + // 字节是可变的 - 原地修改 + if len(b) > 0 { + b[0] = 'H' + } + return b +} + +func BenchmarkString(b *testing.B) { + for i := 0; i < b.N; i++ { + _ = processString("hello world") + } +} + +func BenchmarkBytes(b *testing.B) { + for i := 0; i < b.N; i++ { + data := []byte("hello world") + _ = processBytes(data) + } +} + +// 字节版本避免分配 +``` + + +## 性能监控 + + +```python !! py +# Python - 使用 psutil 监控 +import psutil +import time + +def monitor(): + process = psutil.Process() + print(f"CPU: {process.cpu_percent()}%") + print(f"Memory: {process.memory_info().rss / 1024 / 1024:.2f} MB") + print(f"Threads: {process.num_threads()}") +``` + +```go !! go +// Go - 运行时指标 +package main + +import ( + "fmt" + "runtime" + "time" +) + +func main() { + var m runtime.MemStats + + // 每秒打印统计信息 + for i := 0; i < 5; i++ { + runtime.ReadMemStats(&m) + + fmt.Printf("Goroutines: %d\n", runtime.NumGoroutine()) + fmt.Printf("Memory: %.2f MB\n", float64(m.Alloc)/1024/1024) + fmt.Printf("GC cycles: %d\n", m.NumGC) + + time.Sleep(time.Second) + } +} +``` + + +## HTTP 性能技巧 + + +```python !! py +# Python - 性能考虑 +from flask import Flask + +app = Flask(__name__) + +# 使用 JSON 编码器以提高性能 +app.json_encoder = MyCustomEncoder + +# 启用 gzip 压缩 +from flask_compress import Compress +Compress(app) + +# 使用连接池 +import requests +session = requests.Session() +session.get('http://example.com') +``` + +```go !! go +// Go - HTTP 性能技巧 +package main + +import ( + "compress/gzip" + "net/http" + "sync" +) + +// 连接池 +var transport = &http.Transport{ + MaxIdleConns: 100, + MaxIdleConnsPerHost: 100, + IdleConnTimeout: 90 * time.Second, +} + +var client = &http.Client{ + Transport: transport, + Timeout: 30 * time.Second, +} + +// 响应压缩 +func gzipHandler(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if acceptsGzip(r) { + w = gzip.NewWriter(w) + defer w.(interface{ Flush() }).Flush() + } + next.ServeHTTP(w, r) + }) +} + +// 重用缓冲区 +var bufferPool = sync.Pool{ + New: func() interface{} { + return make([]byte, 1024) + }, +} + +func getBuffer() []byte { + return bufferPool.Get().([]byte) +} + +func putBuffer(b []byte) { + bufferPool.Put(b) +} +``` + + +## 总结 + +在本模块中,你学习了: + +1. **CPU 性能分析** 使用 `pprof` +2. **内存性能分析** 和堆分析 +3. **基准测试** 使用 `testing` 包 +4. **字符串优化** - 使用 `strings.Builder` +5. **数据结构选择** - map vs slice +6. **Goroutine 池** - 限制并发 +7. **JSON 流式处理** 处理大数据 +8. **并发缓存** - `sync.Map` vs mutex+map +9. **缓冲区重用** - 减少分配 +10. **Byte vs string** - 可变字节数组 +11. **运行时指标** - 监控性能 +12. **HTTP 优化** - 池化、压缩、重用 + +## 性能技巧总结 + +| 领域 | Python | Go 优化 | +|------|--------|---------| +| 字符串连接 | 使用 `join()` | 使用 `strings.Builder` | +| 大数据集 | 生成器 | 流式处理、缓冲区 | +| 并发 | `ThreadPoolExecutor` | Worker 池 | +| 缓存 | `lru_cache` | `sync.Map` 或 mutex+map | +| 内存 | 控制有限 | 缓冲区池、重用 | +| 性能分析 | `cProfile`、`memory_profiler` | 内置 `pprof` | + +## 常见性能陷阱 + +1. **循环中的字符串连接** - 使用 `strings.Builder` +2. **过度分配** - 重用缓冲区 +3. **无限制的 goroutines** - 使用 worker 池 +4. **互斥锁竞争** - 对读密集型工作负载使用 RWMutex +5. **select 中阻塞** - 添加 default case 实现非阻塞 +6. **大结构体复制** - 使用指针 + +## 练习 + +1. 分析 Go 应用程序并找到瓶颈 +2. 对不同的字符串连接方法进行基准测试 +3. 实现 worker 池进行并发处理 +4. 优化大文件的 JSON 处理 +5. 比较 sync.Map vs mutex-protected map 的性能 + +## 下一步 + +下一模块:**微服务架构** - 使用 Go 构建分布式系统。 diff --git a/content/docs/py2go/module-12-performance-optimization.zh-tw.mdx b/content/docs/py2go/module-12-performance-optimization.zh-tw.mdx new file mode 100644 index 0000000..debbd1e --- /dev/null +++ b/content/docs/py2go/module-12-performance-optimization.zh-tw.mdx @@ -0,0 +1,715 @@ +--- +title: "模組 12: 效能優化" +description: "分析和優化 Go 應用程式" +--- + +## 簡介 + +Go 是為效能而設計的,但了解如何分析和優化程式碼至關重要。本模組介紹效能分析工具和優化技巧。 + +## 使用 pprof 進行效能分析 + +Go 在標準庫中包含了強大的效能分析工具: + + +```python !! py +# Python - cProfile +import cProfile + +def my_function(): + # 要分析的程式碼 + pass + +cProfile.run('my_function()', 'output.stats') + +# 或使用 line_profiler +# @profile +# def my_function(): +# pass +``` + +```go !! go +// Go - CPU 效能分析 +package main + +import ( + "os" + "runtime/pprof" +) + +func main() { + // 開始 CPU 效能分析 + f, _ := os.Create("cpu.prof") + pprof.StartCPUProfile(f) + defer pprof.StopCPUProfile() + + // 你的程式碼 + myFunction() +} + +// 執行程式後: +// go tool pprof cpu.prof +``` + + +## 記憶體效能分析 + + +```python !! py +# Python - memory_profiler +from memory_profiler import profile + +@profile +def my_function(): + data = [x for x in range(1000000)] + return data + +if __name__ == '__main__': + my_function() +``` + +```go !! go +// Go - 記憶體效能分析 +package main + +import ( + "os" + "runtime/pprof" +) + +func main() { + myFunction() + + // 寫入記憶體效能分析 + f, _ := os.Create("mem.prof") + pprof.WriteHeapProfile(f) + f.Close() +} + +// 使用以下指令分析: +// go tool pprof mem.prof +``` + + +## 基準測試和對比 + + +```python !! py +# Python - 字串串連 +import time + +def test_concatenate(n): + start = time.time() + result = "" + for i in range(n): + result += str(i) + return time.time() - start + +def test_join(n): + start = time.time() + parts = [str(i) for i in range(n)] + result = "".join(parts) + return time.time() - start + +n = 10000 +print(f"Concatenate: {test_concatenate(n):.4f}s") +print(f"Join: {test_join(n):.4f}s") + +# Concatenate 慢得多! +``` + +```go !! go +// Go - 字串串連基準測試 +package main + +import ( + "fmt" + "strconv" + "strings" + "testing" +) + +func BenchmarkConcatenate(b *testing.B) { + for i := 0; i < b.N; i++ { + result := "" + for j := 0; j < 100; j++ { + result += strconv.Itoa(j) + } + } +} + +func BenchmarkStringsBuilder(b *testing.B) { + for i := 0; i < b.N; i++ { + var builder strings.Builder + for j := 0; j < 100; j++ { + builder.WriteString(strconv.Itoa(j)) + } + _ = builder.String() + } +} + +// 執行: go test -bench=. +// BenchmarkConcatenate-8 50000 35000 ns/op +// BenchmarkStringsBuilder-8 500000 3500 ns/op +// Builder 快 10 倍! +``` + + +## 切片 vs Map 效能 + + +```python !! py +# Python - List vs Dict 查找 +import time + +def test_list_lookup(n): + items = list(range(n)) + start = time.time() + for i in range(n): + _ = i in items # O(n) 查找 + return time.time() - start + +def test_dict_lookup(n): + items = {i: i for i in range(n)} + start = time.time() + for i in range(n): + _ = i in items # O(1) 查找 + return time.time() - start + +n = 10000 +print(f"List: {test_list_lookup(n):.4f}s") +print(f"Dict: {test_dict_lookup(n):.4f}s") +``` + +```go !! go +// Go - 切片 vs Map 效能 +package main + +import "testing" + +func BenchmarkSliceLookup(b *testing.B) { + items := make([]int, 1000) + for i := range items { + items[i] = i + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + for j := 0; j < 1000; j++ { + _ = contains(items, j) // O(n) + } + } +} + +func BenchmarkMapLookup(b *testing.B) { + items := make(map[int]bool) + for i := 0; i < 1000; i++ { + items[i] = true + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + for j := 0; j < 1000; j++ { + _ = items[j] // O(1) + } + } +} + +func contains(slice []int, item int) bool { + for _, v := range slice { + if v == item { + return true + } + } + return false +} + +// Map 在查找方面明顯更快 +``` + + +## Goroutine 池模式 + + +```python !! py +# Python - ThreadPoolExecutor +from concurrent.futures import ThreadPoolExecutor + +def process_task(task_id): + return task_id * 2 + +with ThreadPoolExecutor(max_workers=10) as executor: + results = executor.map(process_task, range(1000)) +``` + +```go !! go +// Go - Worker 池(限制 goroutines) +package main + +import ( + "fmt" + "sync" + "time" +) + +func worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) { + defer wg.Done() + for job := range jobs { + // 模擬工作 + time.Sleep(time.Millisecond) + results <- job * 2 + } +} + +func main() { + jobs := make(chan int, 100) + results := make(chan int, 100) + + var wg sync.WaitGroup + + // 啟動 10 個 worker(限制 goroutines) + for w := 1; w <= 10; w++ { + wg.Add(1) + go worker(w, jobs, results, &wg) + } + + // 發送任務 + for j := 0; j < 1000; j++ { + jobs <- j + } + close(jobs) + + // 等待 worker 完成 + go func() { + wg.Wait() + close(results) + }() + + // 收集結果 + for result := range results { + fmt.Println(result) + } +} +``` + + +## 高效的 JSON 處理 + + +```python !! py +# Python - json 模組 +import json +import time + +data = {"users": [{"id": i, "name": f"User{i}"} for i in range(1000)]} + +# 序列化 +start = time.time() +json_str = json.dumps(data) +print(f"Serialize: {time.time() - start:.4f}s") + +# 反序列化 +start = time.time() +parsed = json.loads(json_str) +print(f"Deserialize: {time.time() - start:.4f}s") +``` + +```go !! go +// Go - 高效的 JSON 串流處理 +package main + +import ( + "encoding/json" + "os" + "testing" +) + +type User struct { + ID int `json:"id"` + Name string `json:"name"` +} + +type Data struct { + Users []User `json:"users"` +} + +func BenchmarkJSONMarshal(b *testing.B) { + users := make([]User, 1000) + for i := range users { + users[i] = User{ID: i, Name: fmt.Sprintf("User%d", i)} + } + data := Data{Users: users} + + b.ResetTimer() + for i := 0; i < b.N; i++ { + json.Marshal(data) + } +} + +func BenchmarkJSONStream(b *testing.B) { + users := make([]User, 1000) + for i := range users { + users[i] = User{ID: i, Name: fmt.Sprintf("User%d", i)} + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + encoder := json.NewEncoder(os.Stdout) + encoder.Encode(users) + } +} + +// 串流處理更節省記憶體 +``` + + +## 使用 sync.Map 快取 + + +```python !! py +# Python - 帶有線程安全的 lru_cache +from functools import lru_cache +import threading + +@lru_cache(maxsize=1000) +def expensive_computation(n): + return n * n + +# 或使用自訂快取 +cache = {} +lock = threading.Lock() + +def get_value(key): + with lock: + if key not in cache: + cache[key] = expensive_computation(key) + return cache[key] +``` + +```go !! go +// Go - sync.Map 用於並行存取 +package main + +import ( + "sync" + "testing" +) + +// 帶互斥鎖的常規 map(大多數情況下更好) +type Cache struct { + mu sync.RWMutex + data map[int]int +} + +func NewCache() *Cache { + return &Cache{ + data: make(map[int]int), + } +} + +func (c *Cache) Get(key int) (int, bool) { + c.mu.RLock() + defer c.mu.RUnlock() + val, ok := c.data[key] + return val, ok +} + +func (c *Cache) Set(key, value int) { + c.mu.Lock() + defer c.mu.Unlock() + c.data[key] = value +} + +// sync.Map(適合快取類工作負載) +func BenchmarkMapWithMutex(b *testing.B) { + cache := NewCache() + cache.Set(1, 100) + + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + cache.Get(1) + } + }) +} + +func BenchmarkSyncMap(b *testing.B) { + var m sync.Map + m.Store(1, 100) + + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + m.Load(1) + } + }) +} +``` + + +## 避免記憶體分配 + + +```python !! py +# Python - 重用物件(控制有限) +class Buffer: + def __init__(self): + self.data = [] + + def process(self, items): + # 重用列表而不是建立新的 + self.data.clear() + self.data.extend(items) + return self.data +``` + +```go !! go +// Go - 重用緩衝區以減少分配 +package main + +import "testing" + +func AllocateNew() []int { + // 每次都分配新的 + return make([]int, 1000) +} + +var buffer = make([]int, 1000) + +func ReuseBuffer() []int { + // 重用緩衝區(先清零) + for i := range buffer { + buffer[i] = 0 + } + return buffer +} + +func BenchmarkAllocate(b *testing.B) { + for i := 0; i < b.N; i++ { + _ = AllocateNew() + } +} + +func BenchmarkReuse(b *testing.B) { + for i := 0; i < b.N; i++ { + _ = ReuseBuffer() + } +} + +// ReuseBuffer 快得多(無分配) +``` + + +## 高效的字串操作 + + +```python !! py +# Python - 字串是不可變的 +data = "hello world" +# data[0] = 'H' # TypeError! + +# 必須建立新字串 +data = "H" + data[1:] + +# 對於可變資料,使用 list +chars = list(data) +chars[0] = 'H' +data = ''.join(chars) +``` + +```go !! go +// Go - 使用 []byte 處理可變資料 +package main + +import "testing" + +func processString(s string) string { + // 字串是不可變的 - 建立新字串 + return "H" + s[1:] +} + +func processBytes(b []byte) []byte { + // 位元組是可變的 - 原地修改 + if len(b) > 0 { + b[0] = 'H' + } + return b +} + +func BenchmarkString(b *testing.B) { + for i := 0; i < b.N; i++ { + _ = processString("hello world") + } +} + +func BenchmarkBytes(b *testing.B) { + for i := 0; i < b.N; i++ { + data := []byte("hello world") + _ = processBytes(data) + } +} + +// 位元組版本避免分配 +``` + + +## 效能監控 + + +```python !! py +# Python - 使用 psutil 監控 +import psutil +import time + +def monitor(): + process = psutil.Process() + print(f"CPU: {process.cpu_percent()}%") + print(f"Memory: {process.memory_info().rss / 1024 / 1024:.2f} MB") + print(f"Threads: {process.num_threads()}") +``` + +```go !! go +// Go - 執行時指標 +package main + +import ( + "fmt" + "runtime" + "time" +) + +func main() { + var m runtime.MemStats + + // 每秒列印統計資訊 + for i := 0; i < 5; i++ { + runtime.ReadMemStats(&m) + + fmt.Printf("Goroutines: %d\n", runtime.NumGoroutine()) + fmt.Printf("Memory: %.2f MB\n", float64(m.Alloc)/1024/1024) + fmt.Printf("GC cycles: %d\n", m.NumGC) + + time.Sleep(time.Second) + } +} +``` + + +## HTTP 效能技巧 + + +```python !! py +# Python - 效能考慮 +from flask import Flask + +app = Flask(__name__) + +# 使用 JSON 編碼器以提高效能 +app.json_encoder = MyCustomEncoder + +# 啟用 gzip 壓縮 +from flask_compress import Compress +Compress(app) + +# 使用連線池 +import requests +session = requests.Session() +session.get('http://example.com') +``` + +```go !! go +// Go - HTTP 效能技巧 +package main + +import ( + "compress/gzip" + "net/http" + "sync" +) + +// 連線池 +var transport = &http.Transport{ + MaxIdleConns: 100, + MaxIdleConnsPerHost: 100, + IdleConnTimeout: 90 * time.Second, +} + +var client = &http.Client{ + Transport: transport, + Timeout: 30 * time.Second, +} + +// 回應壓縮 +func gzipHandler(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if acceptsGzip(r) { + w = gzip.NewWriter(w) + defer w.(interface{ Flush() }).Flush() + } + next.ServeHTTP(w, r) + }) +} + +// 重用緩衝區 +var bufferPool = sync.Pool{ + New: func() interface{} { + return make([]byte, 1024) + }, +} + +func getBuffer() []byte { + return bufferPool.Get().([]byte) +} + +func putBuffer(b []byte) { + bufferPool.Put(b) +} +``` + + +## 總結 + +在本模組中,你學習了: + +1. **CPU 效能分析** 使用 `pprof` +2. **記憶體效能分析** 和堆分析 +3. **基準測試** 使用 `testing` 包 +4. **字串優化** - 使用 `strings.Builder` +5. **資料結構選擇** - map vs slice +6. **Goroutine 池** - 限制並行 +7. **JSON 串流處理** 處理大資料 +8. **並行快取** - `sync.Map` vs mutex+map +9. **緩衝區重用** - 減少分配 +10. **Byte vs string** - 可變位元組陣列 +11. **執行時指標** - 監控效能 +12. **HTTP 優化** - 池化、壓縮、重用 + +## 效能技巧總結 + +| 領域 | Python | Go 優化 | +|------|--------|---------| +| 字串串連 | 使用 `join()` | 使用 `strings.Builder` | +| 大資料集 | 生成器 | 串流處理、緩衝區 | +| 並行 | `ThreadPoolExecutor` | Worker 池 | +| 快取 | `lru_cache` | `sync.Map` 或 mutex+map | +| 記憶體 | 控制有限 | 緩衝區池、重用 | +| 效能分析 | `cProfile`、`memory_profiler` | 內建 `pprof` | + +## 常見效能陷阱 + +1. **循環中的字串串連** - 使用 `strings.Builder` +2. **過度分配** - 重用緩衝區 +3. **無限制的 goroutines** - 使用 worker 池 +4. **互斥鎖競爭** - 對讀密集型工作負載使用 RWMutex +5. **select 中阻塞** - 新增 default case 實現非阻塞 +6. **大結構體複製** - 使用指標 + +## 練習 + +1. 分析 Go 應用程式並找到瓶頸 +2. 對不同的字串串連方法進行基準測試 +3. 實現 worker 池進行並行處理 +4. 優化大檔案的 JSON 處理 +5. 比較 sync.Map vs mutex-protected map 的效能 + +## 下一步 + +下一模組:**微服務架構** - 使用 Go 建構分散式系統。 diff --git a/content/docs/py2go/module-13-microservices.mdx b/content/docs/py2go/module-13-microservices.mdx new file mode 100644 index 0000000..cf2e5aa --- /dev/null +++ b/content/docs/py2go/module-13-microservices.mdx @@ -0,0 +1,775 @@ +--- +title: "Module 13: Microservices Architecture" +description: "Building distributed systems and microservices with Go" +--- + +## Introduction + +Go is ideal for microservices - fast compilation, small binaries, excellent concurrency, and low memory footprint make it perfect for distributed systems. + +## Microservice Basics + + +```python !! py +# Python - Flask microservice +from flask import Flask, jsonify +from typing import Dict + +app = Flask(__name__) + +users_db: Dict[int, dict] = { + 1: {"id": 1, "name": "Alice", "email": "alice@example.com"}, +} + +@app.route('/health') +def health(): + return jsonify({"status": "healthy"}) + +@app.route('/api/users/') +def get_user(user_id): + user = users_db.get(user_id) + if not user: + return jsonify({"error": "Not found"}), 404 + return jsonify(user) + +if __name__ == '__main__': + app.run(port=8080) +``` + +```go !! go +// Go - Microservice with net/http +package main + +import ( + "encoding/json" + "net/http" + "sync" +) + +type User struct { + ID int `json:"id"` + Name string `json:"name"` + Email string `json:"email"` +} + +var ( + users = map[int]User{ + 1: {ID: 1, Name: "Alice", Email: "alice@example.com"}, + } + mu sync.RWMutex +) + +func healthHandler(w http.ResponseWriter, r *http.Request) { + json.NewEncoder(w).Encode(map[string]string{"status": "healthy"}) +} + +func getUserHandler(w http.ResponseWriter, r *http.Request) { + userID := extractID(r.URL.Path) + + mu.RLock() + user, exists := users[userID] + mu.RUnlock() + + if !exists { + http.Error(w, "Not found", http.StatusNotFound) + return + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(user) +} + +func main() { + http.HandleFunc("/health", healthHandler) + http.HandleFunc("/api/users/", getUserHandler) + + http.ListenAndServe(":8080", nil) +} +``` + + +## Service Discovery + + +```python !! py +# Python - Consul service discovery +import consul + +consul_client = consul.Consul() + +# Register service +service_id = "user-service-1" +consul_client.agent.service.register( + name="user-service", + service_id=service_id, + port=8080, + tags=["api", "users"], + check=consul.Check.http( + url="http://localhost:8080/health", + interval="10s" + ) +) + +# Discover service +_, services = consul_client.health.service( + "user-service", + passing=True +) +``` + +```go !! go +// Go - Consul service discovery +package main + +import ( + "github.com/hashicorp/consul/api" +) + +func registerService() error { + config := api.DefaultConfig() + client, _ := api.NewClient(config) + + registration := &api.AgentServiceRegistration{ + ID: "user-service-1", + Name: "user-service", + Port: 8080, + Tags: []string{"api", "users"}, + Check: &api.AgentServiceCheck{ + HTTP: "http://localhost:8080/health", + Interval: "10s", + DeregisterCriticalServiceAfter: "30s", + }, + } + + return client.Agent().ServiceRegister(registration) +} + +func discoverService(serviceName string) ([]string, error) { + config := api.DefaultConfig() + client, _ := api.NewClient(config) + + services, _, err := client.Health().Service(serviceName, "", true, nil) + if err != nil { + return nil, err + } + + var addrs []string + for _, service := range services { + addr := fmt.Sprintf("%s:%d", + service.Service.Address, + service.Service.Port, + ) + addrs = append(addrs, addr) + } + + return addrs, nil +} +``` + + +## Inter-Service Communication + + +```python !! py +# Python - HTTP client with retry +import requests +from tenacity import retry, stop_after_attempt, wait_exponential + +class ServiceClient: + def __init__(self, base_url): + self.base_url = base_url + + @retry( + stop=stop_after_attempt(3), + wait=wait_exponential(multiplier=1, min=1, max=10) + ) + def get_user(self, user_id): + response = requests.get( + f"{self.base_url}/api/users/{user_id}", + timeout=5 + ) + response.raise_for_status() + return response.json() + +# Usage +client = ServiceClient("http://user-service:8080") +user = client.get_user(1) +``` + +```go !! go +// Go - HTTP client with retry and circuit breaker +package main + +import ( + "context" + "fmt" + "net/http" + "time" +) + +type ServiceClient struct { + baseURL string + httpClient *http.Client + retryCount int +} + +func NewServiceClient(baseURL string) *ServiceClient { + return &ServiceClient{ + baseURL: baseURL, + httpClient: &http.Client{ + Timeout: 5 * time.Second, + Transport: &http.Transport{ + MaxIdleConnsPerHost: 100, + }, + }, + retryCount: 3, + } +} + +func (c *ServiceClient) GetUser(ctx context.Context, userID int) (*User, error) { + var lastErr error + + for attempt := 0; attempt < c.retryCount; attempt++ { + url := fmt.Sprintf("%s/api/users/%d", c.baseURL, userID) + + req, err := http.NewRequestWithContext(ctx, "GET", url, nil) + if err != nil { + return nil, err + } + + resp, err := c.httpClient.Do(req) + if err == nil { + defer resp.Body.Close() + + if resp.StatusCode == http.StatusOK { + var user User + if err := json.NewDecoder(resp.Body).Decode(&user); err != nil { + return nil, err + } + return &user, nil + } + lastErr = fmt.Errorf("unexpected status: %d", resp.StatusCode) + } else { + lastErr = err + } + + // Exponential backoff + time.Sleep(time.Duration(1< + +## API Gateway Pattern + + +```python !! py +# Python - API Gateway with Flask +from flask import Flask, request, jsonify +import requests + +app = Flask(__name__) + +services = { + "users": "http://user-service:8080", + "orders": "http://order-service:8081", +} + +@app.route('/api/users/') +def proxy_users(path): + url = f"{services['users']}/{path}" + resp = requests.request( + method=request.method, + url=url, + headers=request.headers, + data=request.get_data(), + ) + return resp.content, resp.status_code + +@app.route('/api/orders/') +def proxy_orders(path): + url = f"{services['orders']}/{path}" + resp = requests.request( + method=request.method, + url=url, + headers=request.headers, + data=request.get_data(), + ) + return resp.content, resp.status_code +``` + +```go !! go +// Go - API Gateway +package main + +import ( + "fmt" + "net/http" + "net/http/httputil" + "net/url" + "strings" +) + +type APIGateway struct { + services map[string]string +} + +func NewAPIGateway() *APIGateway { + return &APIGateway{ + services: map[string]string{ + "users": "http://user-service:8080", + "orders": "http://order-service:8081", + }, + } +} + +func (g *APIGateway) ServeHTTP(w http.ResponseWriter, r *http.Request) { + // Extract service name from path + // /api/users/123 -> users + parts := strings.Split(r.URL.Path, "/") + if len(parts) < 3 { + http.Error(w, "Invalid path", http.StatusBadRequest) + return + } + + serviceName := parts[2] + serviceURL, exists := g.services[serviceName] + if !exists { + http.Error(w, "Service not found", http.StatusNotFound) + return + } + + // Create reverse proxy + target, _ := url.Parse(serviceURL) + proxy := httputil.NewSingleHostReverseProxy(target) + + // Update request path + r.URL.Path = "/" + strings.Join(parts[3:], "/") + + // Serve via proxy + proxy.ServeHTTP(w, r) +} + +func main() { + gateway := NewAPIGateway() + + http.ListenAndServe(":8080", gateway) +} +``` + + +## Distributed Tracing + + +```python !! py +# Python - OpenTelemetry +from opentelemetry import trace +from opentelemetry.sdk.trace import TracerProvider +from opentelemetry.sdk.trace.export import BatchSpanProcessor +from opentelemetry.exporter.jaeger import JaegerExporter + +# Setup +trace.set_tracer_provider(TracerProvider()) +tracer = trace.get_tracer(__name__) + +jaeger_exporter = JaegerExporter( + agent_host_name="jaeger", + agent_port=6831, +) + +span_processor = BatchSpanProcessor(jaeger_exporter) +trace.get_tracer_provider().add_span_processor(span_processor) + +# Use in code +@tracer.start_as_current_span("get_user") +def get_user(user_id): + with tracer.start_as_current_span("database_query"): + user = db.query(user_id) + return user +``` + +```go !! go +// Go - OpenTelemetry tracing +package main + +import ( + "context" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/exporters/jaeger" + "go.opentelemetry.io/otel/sdk/trace" +) + +func initTracer() error { + exporter, err := jaeger.New(jaeger.WithCollectorEndpoint( + jaeger.WithEndpoint("jaeger:14268/api/traces"), + )) + if err != nil { + return err + } + + tp := trace.NewTracerProvider( + trace.WithBatcher(exporter), + ) + otel.SetTracerProvider(tp) + + return nil +} + +func getUser(ctx context.Context, userID int) (*User, error) { + tracer := otel.Tracer("user-service") + + ctx, span := tracer.Start(ctx, "get_user") + defer span.End() + + // Database query span + _, dbSpan := tracer.Start(ctx, "database_query") + user := queryDatabase(ctx, userID) + dbSpan.End() + + return user, nil +} +``` + + +## Event-Driven Architecture + + +```python !! py +# Python - RabbitMQ publishing +import pika +import json + +connection = pika.BlockingConnection( + pika.ConnectionParameters('localhost') +) +channel = connection.channel() + +# Declare exchange +channel.exchange_declare( + exchange='user_events', + exchange_type='topic' +) + +# Publish event +event = { + 'type': 'user.created', + 'user_id': 123, + 'timestamp': datetime.now().isoformat() +} + +channel.basic_publish( + exchange='user_events', + routing_key='user.created', + body=json.dumps(event) +) +``` + +```go !! go +// Go - RabbitMQ publishing +package main + +import ( + "encoding/json" + "github.com/streadway/amqp" +) + +type Event struct { + Type string `json:"type"` + UserID int `json:"user_id"` + Timestamp string `json:"timestamp"` +} + +func publishEvent(event Event) error { + conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/") + if err != nil { + return err + } + defer conn.Close() + + ch, err := conn.Channel() + if err != nil { + return err + } + defer ch.Close() + + err = ch.ExchangeDeclare( + "user_events", + "topic", + true, + false, + false, + false, + nil, + ) + if err != nil { + return err + } + + body, _ := json.Marshal(event) + err = ch.Publish( + "user_events", + "user.created", + false, + false, + amqp.Publishing{ + ContentType: "application/json", + Body: body, + }, + ) + + return err +} +``` + + +## Configuration Management + + +```python !! py +# Python - Configuration with Pydantic +from pydantic import BaseSettings + +class Settings(BaseSettings): + database_url: str + redis_url: str + jwt_secret: str + debug: bool = False + + class Config: + env_file = ".env" + +settings = Settings() + +# Usage +db.connect(settings.database_url) +``` + +```go !! go +// Go - Configuration with Viper +package main + +import ( + "github.com/spf13/viper" +) + +type Config struct { + DatabaseURL string `mapstructure:"DATABASE_URL"` + RedisURL string `mapstructure:"REDIS_URL"` + JWTSecret string `mapstructure:"JWT_SECRET"` + Debug bool `mapstructure:"DEBUG"` +} + +func LoadConfig() (*Config, error) { + viper.SetConfigFile(".env") + viper.AutomaticEnv() + + if err := viper.ReadInConfig(); err != nil { + return nil, err + } + + var config Config + if err := viper.Unmarshal(&config); err != nil { + return nil, err + } + + return &config, nil +} + +func main() { + config, err := LoadConfig() + if err != nil { + panic(err) + } + + // Use config + connectDB(config.DatabaseURL) +} +``` + + +## Health Checks + + +```python !! py +# Python - Health check endpoints +from flask import jsonify +import psycopg2 +import redis + +@app.route('/health') +def health(): + status = { + "status": "healthy", + "checks": {} + } + + # Database check + try: + conn = psycopg2.connect(DB_URL) + status["checks"]["database"] = "ok" + conn.close() + except Exception as e: + status["checks"]["database"] = f"error: {str(e)}" + status["status"] = "unhealthy" + + # Redis check + try: + r = redis.from_url(REDIS_URL) + r.ping() + status["checks"]["redis"] = "ok" + except Exception as e: + status["checks"]["redis"] = f"error: {str(e)}" + status["status"] = "unhealthy" + + statusCode = 200 if status["status"] == "healthy" else 503 + return jsonify(status), statusCode +``` + +```go !! go +// Go - Health check endpoint +package main + +import ( + "database/sql" + "encoding/json" + "net/http" +) + +type HealthStatus struct { + Status string `json:"status"` + Checks map[string]string `json:"checks"` +} + +func healthHandler(db *sql.DB, redisClient *redis.Client) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + status := HealthStatus{ + Status: "healthy", + Checks: make(map[string]string), + } + + // Database check + if err := db.Ping(); err != nil { + status.Checks["database"] = err.Error() + status.Status = "unhealthy" + } else { + status.Checks["database"] = "ok" + } + + // Redis check + if err := redisClient.Ping().Err(); err != nil { + status.Checks["redis"] = err.Error() + status.Status = "unhealthy" + } else { + status.Checks["redis"] = "ok" + } + + statusCode := http.StatusOK + if status.Status != "healthy" { + statusCode = http.StatusServiceUnavailable + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(statusCode) + json.NewEncoder(w).Encode(status) + } +} +``` + + +## Containerization + + +```dockerfile +# Python - Dockerfile +FROM python:3.11-slim + +WORKDIR /app + +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +COPY . . + +EXPOSE 8080 + +CMD ["gunicorn", "--bind", "0.0.0.0:8080", "app:app"] +``` + +```dockerfile +# Go - Dockerfile (multi-stage) +FROM golang:1.21-alpine AS builder + +WORKDIR /app + +COPY go.mod go.sum ./ +RUN go mod download + +COPY . . +RUN CGO_ENABLED=0 go build -o user-service + +# Minimal final image +FROM alpine:latest + +RUN apk --no-cache add ca-certificates + +WORKDIR /root/ + +COPY --from=builder /app/user-service . + +EXPOSE 8080 + +CMD ["./user-service"] + +# Result: ~10MB image +``` + + +## Summary + +In this module, you learned: + +1. **Microservice basics** - Simple HTTP service +2. **Service discovery** - Consul integration +3. **Inter-service communication** - HTTP client with retry +4. **API Gateway** - Reverse proxy pattern +5. **Distributed tracing** - OpenTelemetry +6. **Event-driven architecture** - Message queues +7. **Configuration** - Viper for Go +8. **Health checks** - Comprehensive monitoring +9. **Containerization** - Small Docker images + +## Microservices Comparison + +| Aspect | Python | Go | +|--------|--------|-----| +| Service startup | ~2-5 seconds | ~0.1-0.5 seconds | +| Memory per service | 50-200 MB | 5-20 MB | +| Docker image | 200-500 MB | 10-20 MB | +| Request throughput | 500-2,000 req/s | 10,000-50,000 req/s | +| Cold start | Slower | Faster | +| Deployment complexity | Needs runtime | Single binary | + +## Best Practices + +1. **Keep services small** - Single responsibility +2. **Use service mesh** - Istio, Linkerd for complex systems +3. **Implement circuit breakers** - Hystrix, resilience4go +4. **Centralized logging** - ELK stack, Loki +5. **Metrics collection** - Prometheus, Grafana +6. **Graceful shutdown** - Handle SIGTERM +7. **Idempotent operations** - Safe retries +8. **API versioning** - Backward compatibility + +## Exercises + +1. Build a complete microservice with CRUD operations +2. Implement service discovery with Consul +3. Add distributed tracing with OpenTelemetry +4. Create an API Gateway for multiple services +5. Implement event-driven communication with RabbitMQ + +## Next Steps + +Next module: **Cloud Native Development** - Kubernetes and cloud-native patterns. diff --git a/content/docs/py2go/module-13-microservices.zh-cn.mdx b/content/docs/py2go/module-13-microservices.zh-cn.mdx new file mode 100644 index 0000000..374c9c8 --- /dev/null +++ b/content/docs/py2go/module-13-microservices.zh-cn.mdx @@ -0,0 +1,775 @@ +--- +title: "Module 13: 微服务架构" +description: "使用 Go 构建分布式系统和微服务" +--- + +## 简介 + +Go 非常适合构建微服务——快速编译、小体积二进制文件、出色的并发性能和低内存占用,使其成为构建分布式系统的理想选择。 + +## 微服务基础 + + +```python !! py +# Python - Flask 微服务 +from flask import Flask, jsonify +from typing import Dict + +app = Flask(__name__) + +users_db: Dict[int, dict] = { + 1: {"id": 1, "name": "Alice", "email": "alice@example.com"}, +} + +@app.route('/health') +def health(): + return jsonify({"status": "healthy"}) + +@app.route('/api/users/') +def get_user(user_id): + user = users_db.get(user_id) + if not user: + return jsonify({"error": "Not found"}), 404 + return jsonify(user) + +if __name__ == '__main__': + app.run(port=8080) +``` + +```go !! go +// Go - 使用 net/http 的微服务 +package main + +import ( + "encoding/json" + "net/http" + "sync" +) + +type User struct { + ID int `json:"id"` + Name string `json:"name"` + Email string `json:"email"` +} + +var ( + users = map[int]User{ + 1: {ID: 1, Name: "Alice", Email: "alice@example.com"}, + } + mu sync.RWMutex +) + +func healthHandler(w http.ResponseWriter, r *http.Request) { + json.NewEncoder(w).Encode(map[string]string{"status": "healthy"}) +} + +func getUserHandler(w http.ResponseWriter, r *http.Request) { + userID := extractID(r.URL.Path) + + mu.RLock() + user, exists := users[userID] + mu.RUnlock() + + if !exists { + http.Error(w, "Not found", http.StatusNotFound) + return + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(user) +} + +func main() { + http.HandleFunc("/health", healthHandler) + http.HandleFunc("/api/users/", getUserHandler) + + http.ListenAndServe(":8080", nil) +} +``` + + +## 服务发现 + + +```python !! py +# Python - Consul 服务发现 +import consul + +consul_client = consul.Consul() + +# 注册服务 +service_id = "user-service-1" +consul_client.agent.service.register( + name="user-service", + service_id=service_id, + port=8080, + tags=["api", "users"], + check=consul.Check.http( + url="http://localhost:8080/health", + interval="10s" + ) +) + +# 发现服务 +_, services = consul_client.health.service( + "user-service", + passing=True +) +``` + +```go !! go +// Go - Consul 服务发现 +package main + +import ( + "github.com/hashicorp/consul/api" +) + +func registerService() error { + config := api.DefaultConfig() + client, _ := api.NewClient(config) + + registration := &api.AgentServiceRegistration{ + ID: "user-service-1", + Name: "user-service", + Port: 8080, + Tags: []string{"api", "users"}, + Check: &api.AgentServiceCheck{ + HTTP: "http://localhost:8080/health", + Interval: "10s", + DeregisterCriticalServiceAfter: "30s", + }, + } + + return client.Agent().ServiceRegister(registration) +} + +func discoverService(serviceName string) ([]string, error) { + config := api.DefaultConfig() + client, _ := api.NewClient(config) + + services, _, err := client.Health().Service(serviceName, "", true, nil) + if err != nil { + return nil, err + } + + var addrs []string + for _, service := range services { + addr := fmt.Sprintf("%s:%d", + service.Service.Address, + service.Service.Port, + ) + addrs = append(addrs, addr) + } + + return addrs, nil +} +``` + + +## 服务间通信 + + +```python !! py +# Python - 带重试的 HTTP 客户端 +import requests +from tenacity import retry, stop_after_attempt, wait_exponential + +class ServiceClient: + def __init__(self, base_url): + self.base_url = base_url + + @retry( + stop=stop_after_attempt(3), + wait=wait_exponential(multiplier=1, min=1, max=10) + ) + def get_user(self, user_id): + response = requests.get( + f"{self.base_url}/api/users/{user_id}", + timeout=5 + ) + response.raise_for_status() + return response.json() + +# 使用 +client = ServiceClient("http://user-service:8080") +user = client.get_user(1) +``` + +```go !! go +// Go - 带重试和熔断器的 HTTP 客户端 +package main + +import ( + "context" + "fmt" + "net/http" + "time" +) + +type ServiceClient struct { + baseURL string + httpClient *http.Client + retryCount int +} + +func NewServiceClient(baseURL string) *ServiceClient { + return &ServiceClient{ + baseURL: baseURL, + httpClient: &http.Client{ + Timeout: 5 * time.Second, + Transport: &http.Transport{ + MaxIdleConnsPerHost: 100, + }, + }, + retryCount: 3, + } +} + +func (c *ServiceClient) GetUser(ctx context.Context, userID int) (*User, error) { + var lastErr error + + for attempt := 0; attempt < c.retryCount; attempt++ { + url := fmt.Sprintf("%s/api/users/%d", c.baseURL, userID) + + req, err := http.NewRequestWithContext(ctx, "GET", url, nil) + if err != nil { + return nil, err + } + + resp, err := c.httpClient.Do(req) + if err == nil { + defer resp.Body.Close() + + if resp.StatusCode == http.StatusOK { + var user User + if err := json.NewDecoder(resp.Body).Decode(&user); err != nil { + return nil, err + } + return &user, nil + } + lastErr = fmt.Errorf("unexpected status: %d", resp.StatusCode) + } else { + lastErr = err + } + + // 指数退避 + time.Sleep(time.Duration(1< + +## API 网关模式 + + +```python !! py +# Python - 使用 Flask 的 API 网关 +from flask import Flask, request, jsonify +import requests + +app = Flask(__name__) + +services = { + "users": "http://user-service:8080", + "orders": "http://order-service:8081", +} + +@app.route('/api/users/') +def proxy_users(path): + url = f"{services['users']}/{path}" + resp = requests.request( + method=request.method, + url=url, + headers=request.headers, + data=request.get_data(), + ) + return resp.content, resp.status_code + +@app.route('/api/orders/') +def proxy_orders(path): + url = f"{services['orders']}/{path}" + resp = requests.request( + method=request.method, + url=url, + headers=request.headers, + data=request.get_data(), + ) + return resp.content, resp.status_code +``` + +```go !! go +// Go - API 网关 +package main + +import ( + "fmt" + "net/http" + "net/http/httputil" + "net/url" + "strings" +) + +type APIGateway struct { + services map[string]string +} + +func NewAPIGateway() *APIGateway { + return &APIGateway{ + services: map[string]string{ + "users": "http://user-service:8080", + "orders": "http://order-service:8081", + }, + } +} + +func (g *APIGateway) ServeHTTP(w http.ResponseWriter, r *http.Request) { + // 从路径中提取服务名称 + // /api/users/123 -> users + parts := strings.Split(r.URL.Path, "/") + if len(parts) < 3 { + http.Error(w, "Invalid path", http.StatusBadRequest) + return + } + + serviceName := parts[2] + serviceURL, exists := g.services[serviceName] + if !exists { + http.Error(w, "Service not found", http.StatusNotFound) + return + } + + // 创建反向代理 + target, _ := url.Parse(serviceURL) + proxy := httputil.NewSingleHostReverseProxy(target) + + // 更新请求路径 + r.URL.Path = "/" + strings.Join(parts[3:], "/") + + // 通过代理服务 + proxy.ServeHTTP(w, r) +} + +func main() { + gateway := NewAPIGateway() + + http.ListenAndServe(":8080", gateway) +} +``` + + +## 分布式追踪 + + +```python !! py +# Python - OpenTelemetry +from opentelemetry import trace +from opentelemetry.sdk.trace import TracerProvider +from opentelemetry.sdk.trace.export import BatchSpanProcessor +from opentelemetry.exporter.jaeger import JaegerExporter + +# 设置 +trace.set_tracer_provider(TracerProvider()) +tracer = trace.get_tracer(__name__) + +jaeger_exporter = JaegerExporter( + agent_host_name="jaeger", + agent_port=6831, +) + +span_processor = BatchSpanProcessor(jaeger_exporter) +trace.get_tracer_provider().add_span_processor(span_processor) + +# 在代码中使用 +@tracer.start_as_current_span("get_user") +def get_user(user_id): + with tracer.start_as_current_span("database_query"): + user = db.query(user_id) + return user +``` + +```go !! go +// Go - OpenTelemetry 追踪 +package main + +import ( + "context" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/exporters/jaeger" + "go.opentelemetry.io/otel/sdk/trace" +) + +func initTracer() error { + exporter, err := jaeger.New(jaeger.WithCollectorEndpoint( + jaeger.WithEndpoint("jaeger:14268/api/traces"), + )) + if err != nil { + return err + } + + tp := trace.NewTracerProvider( + trace.WithBatcher(exporter), + ) + otel.SetTracerProvider(tp) + + return nil +} + +func getUser(ctx context.Context, userID int) (*User, error) { + tracer := otel.Tracer("user-service") + + ctx, span := tracer.Start(ctx, "get_user") + defer span.End() + + // 数据库查询 span + _, dbSpan := tracer.Start(ctx, "database_query") + user := queryDatabase(ctx, userID) + dbSpan.End() + + return user, nil +} +``` + + +## 事件驱动架构 + + +```python !! py +# Python - RabbitMQ 发布 +import pika +import json + +connection = pika.BlockingConnection( + pika.ConnectionParameters('localhost') +) +channel = connection.channel() + +# 声明交换机 +channel.exchange_declare( + exchange='user_events', + exchange_type='topic' +) + +# 发布事件 +event = { + 'type': 'user.created', + 'user_id': 123, + 'timestamp': datetime.now().isoformat() +} + +channel.basic_publish( + exchange='user_events', + routing_key='user.created', + body=json.dumps(event) +) +``` + +```go !! go +// Go - RabbitMQ 发布 +package main + +import ( + "encoding/json" + "github.com/streadway/amqp" +) + +type Event struct { + Type string `json:"type"` + UserID int `json:"user_id"` + Timestamp string `json:"timestamp"` +} + +func publishEvent(event Event) error { + conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/") + if err != nil { + return err + } + defer conn.Close() + + ch, err := conn.Channel() + if err != nil { + return err + } + defer ch.Close() + + err = ch.ExchangeDeclare( + "user_events", + "topic", + true, + false, + false, + false, + nil, + ) + if err != nil { + return err + } + + body, _ := json.Marshal(event) + err = ch.Publish( + "user_events", + "user.created", + false, + false, + amqp.Publishing{ + ContentType: "application/json", + Body: body, + }, + ) + + return err +} +``` + + +## 配置管理 + + +```python !! py +# Python - 使用 Pydantic 的配置 +from pydantic import BaseSettings + +class Settings(BaseSettings): + database_url: str + redis_url: str + jwt_secret: str + debug: bool = False + + class Config: + env_file = ".env" + +settings = Settings() + +# 使用 +db.connect(settings.database_url) +``` + +```go !! go +// Go - 使用 Viper 的配置 +package main + +import ( + "github.com/spf13/viper" +) + +type Config struct { + DatabaseURL string `mapstructure:"DATABASE_URL"` + RedisURL string `mapstructure:"REDIS_URL"` + JWTSecret string `mapstructure:"JWT_SECRET"` + Debug bool `mapstructure:"DEBUG"` +} + +func LoadConfig() (*Config, error) { + viper.SetConfigFile(".env") + viper.AutomaticEnv() + + if err := viper.ReadInConfig(); err != nil { + return nil, err + } + + var config Config + if err := viper.Unmarshal(&config); err != nil { + return nil, err + } + + return &config, nil +} + +func main() { + config, err := LoadConfig() + if err != nil { + panic(err) + } + + // 使用配置 + connectDB(config.DatabaseURL) +} +``` + + +## 健康检查 + + +```python !! py +# Python - 健康检查端点 +from flask import jsonify +import psycopg2 +import redis + +@app.route('/health') +def health(): + status = { + "status": "healthy", + "checks": {} + } + + # 数据库检查 + try: + conn = psycopg2.connect(DB_URL) + status["checks"]["database"] = "ok" + conn.close() + except Exception as e: + status["checks"]["database"] = f"error: {str(e)}" + status["status"] = "unhealthy" + + # Redis 检查 + try: + r = redis.from_url(REDIS_URL) + r.ping() + status["checks"]["redis"] = "ok" + except Exception as e: + status["checks"]["redis"] = f"error: {str(e)}" + status["status"] = "unhealthy" + + statusCode = 200 if status["status"] == "healthy" else 503 + return jsonify(status), statusCode +``` + +```go !! go +// Go - 健康检查端点 +package main + +import ( + "database/sql" + "encoding/json" + "net/http" +) + +type HealthStatus struct { + Status string `json:"status"` + Checks map[string]string `json:"checks"` +} + +func healthHandler(db *sql.DB, redisClient *redis.Client) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + status := HealthStatus{ + Status: "healthy", + Checks: make(map[string]string), + } + + // 数据库检查 + if err := db.Ping(); err != nil { + status.Checks["database"] = err.Error() + status.Status = "unhealthy" + } else { + status.Checks["database"] = "ok" + } + + // Redis 检查 + if err := redisClient.Ping().Err(); err != nil { + status.Checks["redis"] = err.Error() + status.Status = "unhealthy" + } else { + status.Checks["redis"] = "ok" + } + + statusCode := http.StatusOK + if status.Status != "healthy" { + statusCode = http.StatusServiceUnavailable + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(statusCode) + json.NewEncoder(w).Encode(status) + } +} +``` + + +## 容器化 + + +```dockerfile +# Python - Dockerfile +FROM python:3.11-slim + +WORKDIR /app + +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +COPY . . + +EXPOSE 8080 + +CMD ["gunicorn", "--bind", "0.0.0.0:8080", "app:app"] +``` + +```dockerfile +# Go - Dockerfile (多阶段构建) +FROM golang:1.21-alpine AS builder + +WORKDIR /app + +COPY go.mod go.sum ./ +RUN go mod download + +COPY . . +RUN CGO_ENABLED=0 go build -o user-service + +# 最小的最终镜像 +FROM alpine:latest + +RUN apk --no-cache add ca-certificates + +WORKDIR /root/ + +COPY --from=builder /app/user-service . + +EXPOSE 8080 + +CMD ["./user-service"] + +# 结果: ~10MB 镜像 +``` + + +## 总结 + +在本模块中,你学习了: + +1. **微服务基础** - 简单的 HTTP 服务 +2. **服务发现** - Consul 集成 +3. **服务间通信** - 带重试的 HTTP 客户端 +4. **API 网关** - 反向代理模式 +5. **分布式追踪** - OpenTelemetry +6. **事件驱动架构** - 消息队列 +7. **配置管理** - Go 的 Viper +8. **健康检查** - 综合监控 +9. **容器化** - 小型 Docker 镜像 + +## 微服务对比 + +| 方面 | Python | Go | +|--------|--------|-----| +| 服务启动时间 | ~2-5 秒 | ~0.1-0.5 秒 | +| 每个服务内存占用 | 50-200 MB | 5-20 MB | +| Docker 镜像大小 | 200-500 MB | 10-20 MB | +| 请求吞吐量 | 500-2,000 req/s | 10,000-50,000 req/s | +| 冷启动 | 较慢 | 更快 | +| 部署复杂度 | 需要运行时 | 单个二进制文件 | + +## 最佳实践 + +1. **保持服务小型** - 单一职责 +2. **使用服务网格** - 复杂系统使用 Istio、Linkerd +3. **实现熔断器** - Hystrix、resilience4go +4. **集中式日志** - ELK stack、Loki +5. **指标收集** - Prometheus、Grafana +6. **优雅关闭** - 处理 SIGTERM +7. **幂等操作** - 安全重试 +8. **API 版本管理** - 向后兼容 + +## 练习 + +1. 构建一个完整的 CRUD 操作微服务 +2. 使用 Consul 实现服务发现 +3. 使用 OpenTelemetry 添加分布式追踪 +4. 为多个服务创建 API 网关 +5. 使用 RabbitMQ 实现事件驱动通信 + +## 下一步 + +下一模块: **云原生开发** - Kubernetes 和云原生模式。 diff --git a/content/docs/py2go/module-13-microservices.zh-tw.mdx b/content/docs/py2go/module-13-microservices.zh-tw.mdx new file mode 100644 index 0000000..81b5307 --- /dev/null +++ b/content/docs/py2go/module-13-microservices.zh-tw.mdx @@ -0,0 +1,775 @@ +--- +title: "Module 13: 微服務架構" +description: "使用 Go 建構分散式系統和微服務" +--- + +## 簡介 + +Go 非常適合建構微服務——快速編譯、小體積二進位檔案、出色的並發效能和低記憶體佔用,使其成為建構分散式系統的理想選擇。 + +## 微服務基礎 + + +```python !! py +# Python - Flask 微服務 +from flask import Flask, jsonify +from typing import Dict + +app = Flask(__name__) + +users_db: Dict[int, dict] = { + 1: {"id": 1, "name": "Alice", "email": "alice@example.com"}, +} + +@app.route('/health') +def health(): + return jsonify({"status": "healthy"}) + +@app.route('/api/users/') +def get_user(user_id): + user = users_db.get(user_id) + if not user: + return jsonify({"error": "Not found"}), 404 + return jsonify(user) + +if __name__ == '__main__': + app.run(port=8080) +``` + +```go !! go +// Go - 使用 net/http 的微服務 +package main + +import ( + "encoding/json" + "net/http" + "sync" +) + +type User struct { + ID int `json:"id"` + Name string `json:"name"` + Email string `json:"email"` +} + +var ( + users = map[int]User{ + 1: {ID: 1, Name: "Alice", Email: "alice@example.com"}, + } + mu sync.RWMutex +) + +func healthHandler(w http.ResponseWriter, r *http.Request) { + json.NewEncoder(w).Encode(map[string]string{"status": "healthy"}) +} + +func getUserHandler(w http.ResponseWriter, r *http.Request) { + userID := extractID(r.URL.Path) + + mu.RLock() + user, exists := users[userID] + mu.RUnlock() + + if !exists { + http.Error(w, "Not found", http.StatusNotFound) + return + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(user) +} + +func main() { + http.HandleFunc("/health", healthHandler) + http.HandleFunc("/api/users/", getUserHandler) + + http.ListenAndServe(":8080", nil) +} +``` + + +## 服務發現 + + +```python !! py +# Python - Consul 服務發現 +import consul + +consul_client = consul.Consul() + +# 註冊服務 +service_id = "user-service-1" +consul_client.agent.service.register( + name="user-service", + service_id=service_id, + port=8080, + tags=["api", "users"], + check=consul.Check.http( + url="http://localhost:8080/health", + interval="10s" + ) +) + +# 發現服務 +_, services = consul_client.health.service( + "user-service", + passing=True +) +``` + +```go !! go +// Go - Consul 服務發現 +package main + +import ( + "github.com/hashicorp/consul/api" +) + +func registerService() error { + config := api.DefaultConfig() + client, _ := api.NewClient(config) + + registration := &api.AgentServiceRegistration{ + ID: "user-service-1", + Name: "user-service", + Port: 8080, + Tags: []string{"api", "users"}, + Check: &api.AgentServiceCheck{ + HTTP: "http://localhost:8080/health", + Interval: "10s", + DeregisterCriticalServiceAfter: "30s", + }, + } + + return client.Agent().ServiceRegister(registration) +} + +func discoverService(serviceName string) ([]string, error) { + config := api.DefaultConfig() + client, _ := api.NewClient(config) + + services, _, err := client.Health().Service(serviceName, "", true, nil) + if err != nil { + return nil, err + } + + var addrs []string + for _, service := range services { + addr := fmt.Sprintf("%s:%d", + service.Service.Address, + service.Service.Port, + ) + addrs = append(addrs, addr) + } + + return addrs, nil +} +``` + + +## 服務間通訊 + + +```python !! py +# Python - 帶重試的 HTTP 客戶端 +import requests +from tenacity import retry, stop_after_attempt, wait_exponential + +class ServiceClient: + def __init__(self, base_url): + self.base_url = base_url + + @retry( + stop=stop_after_attempt(3), + wait=wait_exponential(multiplier=1, min=1, max=10) + ) + def get_user(self, user_id): + response = requests.get( + f"{self.base_url}/api/users/{user_id}", + timeout=5 + ) + response.raise_for_status() + return response.json() + +# 使用 +client = ServiceClient("http://user-service:8080") +user = client.get_user(1) +``` + +```go !! go +// Go - 帶重試和熔斷器的 HTTP 客戶端 +package main + +import ( + "context" + "fmt" + "net/http" + "time" +) + +type ServiceClient struct { + baseURL string + httpClient *http.Client + retryCount int +} + +func NewServiceClient(baseURL string) *ServiceClient { + return &ServiceClient{ + baseURL: baseURL, + httpClient: &http.Client{ + Timeout: 5 * time.Second, + Transport: &http.Transport{ + MaxIdleConnsPerHost: 100, + }, + }, + retryCount: 3, + } +} + +func (c *ServiceClient) GetUser(ctx context.Context, userID int) (*User, error) { + var lastErr error + + for attempt := 0; attempt < c.retryCount; attempt++ { + url := fmt.Sprintf("%s/api/users/%d", c.baseURL, userID) + + req, err := http.NewRequestWithContext(ctx, "GET", url, nil) + if err != nil { + return nil, err + } + + resp, err := c.httpClient.Do(req) + if err == nil { + defer resp.Body.Close() + + if resp.StatusCode == http.StatusOK { + var user User + if err := json.NewDecoder(resp.Body).Decode(&user); err != nil { + return nil, err + } + return &user, nil + } + lastErr = fmt.Errorf("unexpected status: %d", resp.StatusCode) + } else { + lastErr = err + } + + // 指數退避 + time.Sleep(time.Duration(1< + +## API 閘道模式 + + +```python !! py +# Python - 使用 Flask 的 API 閘道 +from flask import Flask, request, jsonify +import requests + +app = Flask(__name__) + +services = { + "users": "http://user-service:8080", + "orders": "http://order-service:8081", +} + +@app.route('/api/users/') +def proxy_users(path): + url = f"{services['users']}/{path}" + resp = requests.request( + method=request.method, + url=url, + headers=request.headers, + data=request.get_data(), + ) + return resp.content, resp.status_code + +@app.route('/api/orders/') +def proxy_orders(path): + url = f"{services['orders']}/{path}" + resp = requests.request( + method=request.method, + url=url, + headers=request.headers, + data=request.get_data(), + ) + return resp.content, resp.status_code +``` + +```go !! go +// Go - API 閘道 +package main + +import ( + "fmt" + "net/http" + "net/http/httputil" + "net/url" + "strings" +) + +type APIGateway struct { + services map[string]string +} + +func NewAPIGateway() *APIGateway { + return &APIGateway{ + services: map[string]string{ + "users": "http://user-service:8080", + "orders": "http://order-service:8081", + }, + } +} + +func (g *APIGateway) ServeHTTP(w http.ResponseWriter, r *http.Request) { + // 從路徑中提取服務名稱 + // /api/users/123 -> users + parts := strings.Split(r.URL.Path, "/") + if len(parts) < 3 { + http.Error(w, "Invalid path", http.StatusBadRequest) + return + } + + serviceName := parts[2] + serviceURL, exists := g.services[serviceName] + if !exists { + http.Error(w, "Service not found", http.StatusNotFound) + return + } + + // 建立反向代理 + target, _ := url.Parse(serviceURL) + proxy := httputil.NewSingleHostReverseProxy(target) + + // 更新請求路徑 + r.URL.Path = "/" + strings.Join(parts[3:], "/") + + // 透過代理服務 + proxy.ServeHTTP(w, r) +} + +func main() { + gateway := NewAPIGateway() + + http.ListenAndServe(":8080", gateway) +} +``` + + +## 分散式追蹤 + + +```python !! py +# Python - OpenTelemetry +from opentelemetry import trace +from opentelemetry.sdk.trace import TracerProvider +from opentelemetry.sdk.trace.export import BatchSpanProcessor +from opentelemetry.exporter.jaeger import JaegerExporter + +# 設定 +trace.set_tracer_provider(TracerProvider()) +tracer = trace.get_tracer(__name__) + +jaeger_exporter = JaegerExporter( + agent_host_name="jaeger", + agent_port=6831, +) + +span_processor = BatchSpanProcessor(jaeger_exporter) +trace.get_tracer_provider().add_span_processor(span_processor) + +# 在程式碼中使用 +@tracer.start_as_current_span("get_user") +def get_user(user_id): + with tracer.start_as_current_span("database_query"): + user = db.query(user_id) + return user +``` + +```go !! go +// Go - OpenTelemetry 追蹤 +package main + +import ( + "context" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/exporters/jaeger" + "go.opentelemetry.io/otel/sdk/trace" +) + +func initTracer() error { + exporter, err := jaeger.New(jaeger.WithCollectorEndpoint( + jaeger.WithEndpoint("jaeger:14268/api/traces"), + )) + if err != nil { + return err + } + + tp := trace.NewTracerProvider( + trace.WithBatcher(exporter), + ) + otel.SetTracerProvider(tp) + + return nil +} + +func getUser(ctx context.Context, userID int) (*User, error) { + tracer := otel.Tracer("user-service") + + ctx, span := tracer.Start(ctx, "get_user") + defer span.End() + + // 資料庫查詢 span + _, dbSpan := tracer.Start(ctx, "database_query") + user := queryDatabase(ctx, userID) + dbSpan.End() + + return user, nil +} +``` + + +## 事件驅動架構 + + +```python !! py +# Python - RabbitMQ 發布 +import pika +import json + +connection = pika.BlockingConnection( + pika.ConnectionParameters('localhost') +) +channel = connection.channel() + +# 宣告交換機 +channel.exchange_declare( + exchange='user_events', + exchange_type='topic' +) + +# 發布事件 +event = { + 'type': 'user.created', + 'user_id': 123, + 'timestamp': datetime.now().isoformat() +} + +channel.basic_publish( + exchange='user_events', + routing_key='user.created', + body=json.dumps(event) +) +``` + +```go !! go +// Go - RabbitMQ 發布 +package main + +import ( + "encoding/json" + "github.com/streadway/amqp" +) + +type Event struct { + Type string `json:"type"` + UserID int `json:"user_id"` + Timestamp string `json:"timestamp"` +} + +func publishEvent(event Event) error { + conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/") + if err != nil { + return err + } + defer conn.Close() + + ch, err := conn.Channel() + if err != nil { + return err + } + defer ch.Close() + + err = ch.ExchangeDeclare( + "user_events", + "topic", + true, + false, + false, + false, + nil, + ) + if err != nil { + return err + } + + body, _ := json.Marshal(event) + err = ch.Publish( + "user_events", + "user.created", + false, + false, + amqp.Publishing{ + ContentType: "application/json", + Body: body, + }, + ) + + return err +} +``` + + +## 設定管理 + + +```python !! py +# Python - 使用 Pydantic 的設定 +from pydantic import BaseSettings + +class Settings(BaseSettings): + database_url: str + redis_url: str + jwt_secret: str + debug: bool = False + + class Config: + env_file = ".env" + +settings = Settings() + +# 使用 +db.connect(settings.database_url) +``` + +```go !! go +// Go - 使用 Viper 的設定 +package main + +import ( + "github.com/spf13/viper" +) + +type Config struct { + DatabaseURL string `mapstructure:"DATABASE_URL"` + RedisURL string `mapstructure:"REDIS_URL"` + JWTSecret string `mapstructure:"JWT_SECRET"` + Debug bool `mapstructure:"DEBUG"` +} + +func LoadConfig() (*Config, error) { + viper.SetConfigFile(".env") + viper.AutomaticEnv() + + if err := viper.ReadInConfig(); err != nil { + return nil, err + } + + var config Config + if err := viper.Unmarshal(&config); err != nil { + return nil, err + } + + return &config, nil +} + +func main() { + config, err := LoadConfig() + if err != nil { + panic(err) + } + + // 使用設定 + connectDB(config.DatabaseURL) +} +``` + + +## 健康檢查 + + +```python !! py +# Python - 健康檢查端點 +from flask import jsonify +import psycopg2 +import redis + +@app.route('/health') +def health(): + status = { + "status": "healthy", + "checks": {} + } + + # 資料庫檢查 + try: + conn = psycopg2.connect(DB_URL) + status["checks"]["database"] = "ok" + conn.close() + except Exception as e: + status["checks"]["database"] = f"error: {str(e)}" + status["status"] = "unhealthy" + + # Redis 檢查 + try: + r = redis.from_url(REDIS_URL) + r.ping() + status["checks"]["redis"] = "ok" + except Exception as e: + status["checks"]["redis"] = f"error: {str(e)}" + status["status"] = "unhealthy" + + statusCode = 200 if status["status"] == "healthy" else 503 + return jsonify(status), statusCode +``` + +```go !! go +// Go - 健康檢查端點 +package main + +import ( + "database/sql" + "encoding/json" + "net/http" +) + +type HealthStatus struct { + Status string `json:"status"` + Checks map[string]string `json:"checks"` +} + +func healthHandler(db *sql.DB, redisClient *redis.Client) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + status := HealthStatus{ + Status: "healthy", + Checks: make(map[string]string), + } + + // 資料庫檢查 + if err := db.Ping(); err != nil { + status.Checks["database"] = err.Error() + status.Status = "unhealthy" + } else { + status.Checks["database"] = "ok" + } + + // Redis 檢查 + if err := redisClient.Ping().Err(); err != nil { + status.Checks["redis"] = err.Error() + status.Status = "unhealthy" + } else { + status.Checks["redis"] = "ok" + } + + statusCode := http.StatusOK + if status.Status != "healthy" { + statusCode = http.StatusServiceUnavailable + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(statusCode) + json.NewEncoder(w).Encode(status) + } +} +``` + + +## 容器化 + + +```dockerfile +# Python - Dockerfile +FROM python:3.11-slim + +WORKDIR /app + +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +COPY . . + +EXPOSE 8080 + +CMD ["gunicorn", "--bind", "0.0.0.0:8080", "app:app"] +``` + +```dockerfile +# Go - Dockerfile (多階段建構) +FROM golang:1.21-alpine AS builder + +WORKDIR /app + +COPY go.mod go.sum ./ +RUN go mod download + +COPY . . +RUN CGO_ENABLED=0 go build -o user-service + +# 最小的最終映像檔 +FROM alpine:latest + +RUN apk --no-cache add ca-certificates + +WORKDIR /root/ + +COPY --from=builder /app/user-service . + +EXPOSE 8080 + +CMD ["./user-service"] + +# 結果: ~10MB 映像檔 +``` + + +## 總結 + +在本模組中,你學習了: + +1. **微服務基礎** - 簡單的 HTTP 服務 +2. **服務發現** - Consul 整合 +3. **服務間通訊** - 帶重試的 HTTP 客戶端 +4. **API 閘道** - 反向代理模式 +5. **分散式追蹤** - OpenTelemetry +6. **事件驅動架構** - 訊息佇列 +7. **設定管理** - Go 的 Viper +8. **健康檢查** - 綜合監控 +9. **容器化** - 小型 Docker 映像檔 + +## 微服務對比 + +| 方面 | Python | Go | +|--------|--------|-----| +| 服務啟動時間 | ~2-5 秒 | ~0.1-0.5 秒 | +| 每個服務記憶體佔用 | 50-200 MB | 5-20 MB | +| Docker 映像檔大小 | 200-500 MB | 10-20 MB | +| 請求吞吐量 | 500-2,000 req/s | 10,000-50,000 req/s | +| 冷啟動 | 較慢 | 更快 | +| 部署複雜度 | 需要執行時 | 單個二進位檔案 | + +## 最佳實踐 + +1. **保持服務小型** - 單一職責 +2. **使用服務網格** - 複雜系統使用 Istio、Linkerd +3. **實作熔斷器** - Hystrix、resilience4go +4. **集中式日誌** - ELK stack、Loki +5. **指標收集** - Prometheus、Grafana +6. **優雅關閉** - 處理 SIGTERM +7. **冪等操作** - 安全重試 +8. **API 版本管理** - 向後相容 + +## 練習 + +1. 建構一個完整的 CRUD 操作微服務 +2. 使用 Consul 實作服務發現 +3. 使用 OpenTelemetry 新增分散式追蹤 +4. 為多個服務建立 API 閘道 +5. 使用 RabbitMQ 實作事件驅動通訊 + +## 下一步 + +下一模組: **雲端原生開發** - Kubernetes 和雲端原生模式。 diff --git a/content/docs/py2go/module-14-cloud-native.mdx b/content/docs/py2go/module-14-cloud-native.mdx new file mode 100644 index 0000000..731e326 --- /dev/null +++ b/content/docs/py2go/module-14-cloud-native.mdx @@ -0,0 +1,682 @@ +--- +title: "Module 14: Cloud Native Development" +description: "Building cloud-native applications with Go and Kubernetes" +--- + +## Introduction + +Go is the language of the cloud-native ecosystem. Kubernetes, Docker, Terraform, and many other cloud tools are written in Go. This module covers cloud-native development patterns. + +## Why Go for Cloud Native? + +Go's advantages for cloud-native development: +- **Small binaries** - Easy containerization +- **Fast compilation** - Quick iteration +- **Low memory** - Efficient resource usage +- **Static linking** - No dependencies +- **Cross-compilation** - Build anywhere, deploy anywhere + +## Kubernetes Controllers + + +```python !! py +# Python - Kubernetes operator with kopf +import kopf + +@kopf.on.create('myapp.example.com', 'v1', 'myapp') +def create_fn(body, spec, **kwargs): + print(f"Creating MyApp: {body.metadata.name}") + + # Create resources + # Handle logic + return {'created': True} + +@kopf.on.update('myapp.example.com', 'v1', 'myapp') +def update_fn(body, spec, **kwargs): + print(f"Updating MyApp: {body.metadata.name}") + return {'updated': True} +``` + +```go !! go +// Go - Kubernetes controller with controller-runtime +package main + +import ( + "context" + "fmt" + + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// MyApp reconciler +type MyAppReconciler struct { + client.Client + Scheme *runtime.Scheme +} + +func (r *MyAppReconciler) Reconcile( + ctx context.Context, + req ctrl.Request, +) (ctrl.Result, error) { + fmt.Printf("Reconciling MyApp: %s\n", req.Name) + + // Fetch resource + // Check status + // Create/update resources + + return ctrl.Result{}, nil +} + +func (r *MyAppReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&myappv1.MyApp{}). + Complete(r) +} + +func main() { + mgr, err := ctrl.NewManager( + ctrl.GetConfigOrDie(), + ctrl.Options{ + Scheme: scheme, + }, + ) + if err != nil { + panic(err) + } + + reconciler := &MyAppReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + } + + if err := reconciler.SetupWithManager(mgr); err != nil { + panic(err) + } + + mgr.Start(ctrl.SetupSignalHandler()) +} +``` + + +## Custom Resource Definitions + + +```yaml +# CRD YAML (same for both) +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: myapps.myapp.example.com +spec: + group: myapp.example.com + versions: + - name: v1 + served: true + storage: true + schema: + openAPIV3Schema: + type: object + properties: + spec: + type: object + properties: + replicas: + type: integer + image: + type: string + scope: Namespaced + names: + plural: myapps + singular: myapp + kind: MyApp + shortNames: + - ma +``` + +```go +// Go - CRD Go types +package v1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// +genclient +// +kubebuilder:object:root=true + +type MyApp struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec MyAppSpec `json:"spec,omitempty"` + Status MyAppStatus `json:"status,omitempty"` +} + +type MyAppSpec struct { + Replicas int `json:"replicas"` + Image string `json:"image"` +} + +type MyAppStatus struct { + ReadyReplicas int `json:"readyReplicas"` +} + +// +kubebuilder:object:root=true + +type MyAppList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + + Items []MyApp `json:"items"` +} + +func init() { + SchemeBuilder.Register(&MyApp{}, &MyAppList{}) +} +``` + + +## Configuration Maps and Secrets + + +```python !! py +# Python - Read from environment or files +import os +from kubernetes import client, config + +config.load_kube_config() +v1 = client.CoreV1Api() + +# Get ConfigMap +cm = v1.read_namespaced_config_map( + 'app-config', + 'default' +) +print(cm.data) + +# Get Secret +secret = v1.read_namespaced_secret( + 'app-secret', + 'default' +) +print(secret.data) +``` + +```go !! go +// Go - Watch ConfigMaps and Secrets +package main + +import ( + "context" + "fmt" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/labels" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +func watchConfig(ctx context.Context, c client.Client) error { + configMap := &corev1.ConfigMap{} + err := c.Get( + ctx, + client.ObjectKey{ + Name: "app-config", + Namespace: "default", + }, + configMap, + ) + + if err != nil { + return err + } + + fmt.Println("Config data:", configMap.Data) + + // Watch for changes + watcher, err := c.Watch( + ctx, + &corev1.ConfigMap{}, + client.InNamespace("default"), + ) + if err != nil { + return err + } + + for event := range watcher.ResultChan() { + cm := event.Object.(*corev1.ConfigMap) + fmt.Printf("Config updated: %s\n", cm.Name) + } + + return nil +} +``` + + +## Probes (Liveness, Readiness, Startup) + + +```python !! py +# Python - Flask health endpoints +from flask import Flask + +app = Flask(__name__) + +@app.route('/healthz') +def liveness(): + # Liveness probe - is the app alive? + return {'status': 'alive'}, 200 + +@app.route('/readyz') +def readiness(): + # Readiness probe - can it serve traffic? + try: + db.ping() + return {'status': 'ready'}, 200 + except: + return {'status': 'not ready'}, 503 + +@app.route('/startupz') +def startup(): + # Startup probe - has the app started? + return {'status': 'started'}, 200 +``` + +```go !! go +// Go - Health probe handlers +package main + +import ( + "database/sql" + "net/http" +) + +func livenessHandler(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Write([]byte("OK")) +} + +func readinessHandler(db *sql.DB) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if err := db.Ping(); err != nil { + http.Error(w, "Database not ready", http.StatusServiceUnavailable) + return + } + + w.WriteHeader(http.StatusOK) + w.Write([]byte("OK")) + } +} + +func startupHandler(isReady *bool) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if !*isReady { + http.Error(w, "Starting up", http.StatusServiceUnavailable) + return + } + + w.WriteHeader(http.StatusOK) + w.Write([]byte("OK")) + } +} + +func main() { + http.HandleFunc("/healthz", livenessHandler) + http.HandleFunc("/readyz", readinessHandler(db)) + http.HandleFunc("/startupz", startupHandler(&ready)) + + http.ListenAndServe(":8080", nil) +} +``` + + +## Graceful Shutdown + + +```python !! py +# Python - Signal handling +import signal +import sys + +def shutdown_handler(signum, frame): + print("Shutting down gracefully...") + # Cleanup + # Close connections + # Finish requests + sys.exit(0) + +signal.signal(signal.SIGTERM, shutdown_handler) +signal.signal(signal.SIGINT, shutdown_handler) + +app.run() +``` + +```go !! go +// Go - Graceful shutdown +package main + +import ( + "context" + "log" + "net/http" + "os" + "os/signal" + "syscall" + "time" +) + +func main() { + server := &http.Server{ + Addr: ":8080", + Handler: newHandler(), + } + + // Start server in goroutine + go func() { + log.Println("Server starting") + if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { + log.Fatalf("Server error: %v", err) + } + }() + + // Wait for interrupt signal + quit := make(chan os.Signal, 1) + signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) + <-quit + + log.Println("Shutting down server...") + + // Graceful shutdown with timeout + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + if err := server.Shutdown(ctx); err != nil { + log.Fatalf("Server shutdown error: %v", err) + } + + log.Println("Server gracefully stopped") +} +``` + + +## Structured Logging + + +```python !! py +# Python - Structlog +import structlog + +log = structlog.get_logger() + +def process_order(order_id): + log.info( + "processing_order", + order_id=order_id, + user_id=123, + ) + + try: + # Process order + log.info( + "order_processed", + order_id=order_id, + amount=99.99, + ) + except Exception as e: + log.error( + "order_failed", + order_id=order_id, + error=str(e), + exc_info=e, + ) +``` + +```go !! go +// Go - Zap structured logging +package main + +import ( + "go.uber.org/zap" +) + +func main() { + logger, _ := zap.NewProduction() + defer logger.Sync() + + sugar := logger.Sugar() + + func processOrder(orderID string) { + sugar.Infow( + "processing_order", + "order_id", orderID, + "user_id", 123, + ) + + if err := process(orderID); err != nil { + sugar.Errorw( + "order_failed", + "order_id", orderID, + "error", err, + ) + return + } + + sugar.Infow( + "order_processed", + "order_id", orderID, + "amount", 99.99, + ) + } +} +``` + + +## Metrics with Prometheus + + +```python !! py +# Python - Prometheus client +from prometheus_client import Counter, Histogram, start_http_server + +request_count = Counter( + 'http_requests_total', + 'Total HTTP requests', + ['method', 'endpoint'] +) + +request_duration = Histogram( + 'http_request_duration_seconds', + 'HTTP request duration', + ['method', 'endpoint'] +) + +@app.route('/api/users') +def get_users(): + request_count.labels( + method='GET', + endpoint='/api/users' + ).inc() + + with request_duration.labels( + method='GET', + endpoint='/api/users' + ).time(): + # Handle request + return jsonify(users) + +start_http_server(9090) +``` + +```go !! go +// Go - Prometheus metrics +package main + +import ( + "net/http" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promhttp" +) + +var ( + requestCount = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Name: "http_requests_total", + Help: "Total HTTP requests", + }, + []string{"method", "endpoint"}, + ) + + requestDuration = prometheus.NewHistogramVec( + prometheus.HistogramOpts{ + Name: "http_request_duration_seconds", + Help: "HTTP request duration", + }, + []string{"method", "endpoint"}, + ) +) + +func init() { + prometheus.MustRegister(requestCount) + prometheus.MustRegister(requestDuration) +} + +func instrumentHandler(method, endpoint string, next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + requestCount.WithLabelValues(method, endpoint).Inc() + + timer := prometheus.NewTimer( + requestDuration.WithLabelValues(method, endpoint), + ) + + next.ServeHTTP(w, r) + + timer.ObserveDuration() + }) +} + +func main() { + // Metrics endpoint + http.Handle("/metrics", promhttp.Handler()) + + // Application endpoints + http.Handle("/api/users", + instrumentHandler("GET", "/api/users", + usersHandler(), + ), + ) + + http.ListenAndServe(":8080", nil) +} +``` + + +## Distributed Tracing Recap + + +```go +// Complete OpenTelemetry setup +package main + +import ( + "context" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/exporters/jaeger" + "go.opentelemetry.io/otel/sdk/resource" + tracesdk "go.opentelemetry.io/otel/sdk/trace" + semconv "go.opentelemetry.io/otel/semconv/v1.4.0" +) + +func initTracer(serviceName, jaegerEndpoint string) error { + exporter, err := jaeger.New(jaeger.WithCollectorEndpoint( + jaeger.WithEndpoint(jaegerEndpoint), + )) + if err != nil { + return err + } + + tp := tracesdk.NewTracerProvider( + tracesdk.WithBatcher(exporter), + tracesdk.WithResource(resource.NewWithAttributes( + semconv.SchemaURL, + semconv.ServiceNameKey.String(serviceName), + )), + ) + + otel.SetTracerProvider(tp) + return nil +} + +func handleRequest(ctx context.Context) { + tracer := otel.Tracer("my-service") + + ctx, span := tracer.Start(ctx, "handleRequest") + defer span.End() + + span.SetAttributes( + attribute.String("http.method", "GET"), + attribute.String("http.route", "/api/users"), + ) + + // Handle request + users := getUsers(ctx) + + span.SetAttributes( + attribute.Int("user.count", len(users)), + ) +} +``` + + +## Summary + +In this module, you learned: + +1. **Cloud-native advantages** of Go +2. **Kubernetes controllers** with controller-runtime +3. **Custom Resource Definitions** in Go +4. **ConfigMaps and Secrets** handling +5. **Health probes** (liveness, readiness, startup) +6. **Graceful shutdown** patterns +7. **Structured logging** with Zap +8. **Prometheus metrics** integration +9. **Distributed tracing** with OpenTelemetry + +## Cloud Native Best Practices + +1. **12-Factor App methodology** + - Config via environment variables + - Stateless processes + - Port binding + - Disposability + +2. **Resource management** + - Set resource limits + - Profile memory usage + - Handle OOM kills gracefully + +3. **Observability** + - Structured logging + - Metrics collection + - Distributed tracing + - Health checks + +4. **Security** + - Minimal containers + - Secrets management + - Network policies + - RBAC + +## Exercises + +1. Create a Kubernetes operator for a custom resource +2. Implement health probes for your service +3. Add Prometheus metrics to an application +4. Set up distributed tracing with Jaeger +5. Deploy a Go application to Kubernetes with Helm + +## Next Steps + +Next module: **Common Pitfalls and Best Practices** - Avoiding mistakes and writing idiomatic Go. diff --git a/content/docs/py2go/module-14-cloud-native.zh-cn.mdx b/content/docs/py2go/module-14-cloud-native.zh-cn.mdx new file mode 100644 index 0000000..242de0a --- /dev/null +++ b/content/docs/py2go/module-14-cloud-native.zh-cn.mdx @@ -0,0 +1,682 @@ +--- +title: "Module 14: 云原生开发" +description: "使用 Go 和 Kubernetes 构建云原生应用" +--- + +## 简介 + +Go 是云原生生态系统的语言。Kubernetes、Docker、Terraform 和许多其他云工具都是用 Go 编写的。本模块涵盖云原生开发模式。 + +## 为什么 Go 适合云原生? + +Go 在云原生开发中的优势: +- **小体积二进制** - 易于容器化 +- **快速编译** - 快速迭代 +- **低内存占用** - 高效的资源使用 +- **静态链接** - 无依赖 +- **交叉编译** - 在任何地方构建,部署到任何地方 + +## Kubernetes 控制器 + + +```python !! py +# Python - 使用 kopf 的 Kubernetes operator +import kopf + +@kopf.on.create('myapp.example.com', 'v1', 'myapp') +def create_fn(body, spec, **kwargs): + print(f"Creating MyApp: {body.metadata.name}") + + # 创建资源 + # 处理逻辑 + return {'created': True} + +@kopf.on.update('myapp.example.com', 'v1', 'myapp') +def update_fn(body, spec, **kwargs): + print(f"Updating MyApp: {body.metadata.name}") + return {'updated': True} +``` + +```go !! go +// Go - 使用 controller-runtime 的 Kubernetes controller +package main + +import ( + "context" + "fmt" + + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// MyApp reconciler +type MyAppReconciler struct { + client.Client + Scheme *runtime.Scheme +} + +func (r *MyAppReconciler) Reconcile( + ctx context.Context, + req ctrl.Request, +) (ctrl.Result, error) { + fmt.Printf("Reconciling MyApp: %s\n", req.Name) + + // 获取资源 + // 检查状态 + // 创建/更新资源 + + return ctrl.Result{}, nil +} + +func (r *MyAppReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&myappv1.MyApp{}). + Complete(r) +} + +func main() { + mgr, err := ctrl.NewManager( + ctrl.GetConfigOrDie(), + ctrl.Options{ + Scheme: scheme, + }, + ) + if err != nil { + panic(err) + } + + reconciler := &MyAppReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + } + + if err := reconciler.SetupWithManager(mgr); err != nil { + panic(err) + } + + mgr.Start(ctrl.SetupSignalHandler()) +} +``` + + +## 自定义资源定义 + + +```yaml +# CRD YAML (两者相同) +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: myapps.myapp.example.com +spec: + group: myapp.example.com + versions: + - name: v1 + served: true + storage: true + schema: + openAPIV3Schema: + type: object + properties: + spec: + type: object + properties: + replicas: + type: integer + image: + type: string + scope: Namespaced + names: + plural: myapps + singular: myapp + kind: MyApp + shortNames: + - ma +``` + +```go +// Go - CRD Go 类型 +package v1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// +genclient +// +kubebuilder:object:root=true + +type MyApp struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec MyAppSpec `json:"spec,omitempty"` + Status MyAppStatus `json:"status,omitempty"` +} + +type MyAppSpec struct { + Replicas int `json:"replicas"` + Image string `json:"image"` +} + +type MyAppStatus struct { + ReadyReplicas int `json:"readyReplicas"` +} + +// +kubebuilder:object:root=true + +type MyAppList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + + Items []MyApp `json:"items"` +} + +func init() { + SchemeBuilder.Register(&MyApp{}, &MyAppList{}) +} +``` + + +## ConfigMap 和 Secret + + +```python !! py +# Python - 从环境或文件读取 +import os +from kubernetes import client, config + +config.load_kube_config() +v1 = client.CoreV1Api() + +# 获取 ConfigMap +cm = v1.read_namespaced_config_map( + 'app-config', + 'default' +) +print(cm.data) + +# 获取 Secret +secret = v1.read_namespaced_secret( + 'app-secret', + 'default' +) +print(secret.data) +``` + +```go !! go +// Go - 监听 ConfigMaps 和 Secrets +package main + +import ( + "context" + "fmt" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/labels" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +func watchConfig(ctx context.Context, c client.Client) error { + configMap := &corev1.ConfigMap{} + err := c.Get( + ctx, + client.ObjectKey{ + Name: "app-config", + Namespace: "default", + }, + configMap, + ) + + if err != nil { + return err + } + + fmt.Println("Config data:", configMap.Data) + + // 监听变更 + watcher, err := c.Watch( + ctx, + &corev1.ConfigMap{}, + client.InNamespace("default"), + ) + if err != nil { + return err + } + + for event := range watcher.ResultChan() { + cm := event.Object.(*corev1.ConfigMap) + fmt.Printf("Config updated: %s\n", cm.Name) + } + + return nil +} +``` + + +## 探针(Liveness、Readiness、Startup) + + +```python !! py +# Python - Flask 健康端点 +from flask import Flask + +app = Flask(__name__) + +@app.route('/healthz') +def liveness(): + # 存活探针 - 应用是否存活? + return {'status': 'alive'}, 200 + +@app.route('/readyz') +def readiness(): + # 就绪探针 - 是否可以处理流量? + try: + db.ping() + return {'status': 'ready'}, 200 + except: + return {'status': 'not ready'}, 503 + +@app.route('/startupz') +def startup(): + # 启动探针 - 应用是否已启动? + return {'status': 'started'}, 200 +``` + +```go !! go +// Go - 健康探针处理器 +package main + +import ( + "database/sql" + "net/http" +) + +func livenessHandler(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Write([]byte("OK")) +} + +func readinessHandler(db *sql.DB) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if err := db.Ping(); err != nil { + http.Error(w, "Database not ready", http.StatusServiceUnavailable) + return + } + + w.WriteHeader(http.StatusOK) + w.Write([]byte("OK")) + } +} + +func startupHandler(isReady *bool) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if !*isReady { + http.Error(w, "Starting up", http.StatusServiceUnavailable) + return + } + + w.WriteHeader(http.StatusOK) + w.Write([]byte("OK")) + } +} + +func main() { + http.HandleFunc("/healthz", livenessHandler) + http.HandleFunc("/readyz", readinessHandler(db)) + http.HandleFunc("/startupz", startupHandler(&ready)) + + http.ListenAndServe(":8080", nil) +} +``` + + +## 优雅关闭 + + +```python !! py +# Python - 信号处理 +import signal +import sys + +def shutdown_handler(signum, frame): + print("Shutting down gracefully...") + # 清理 + # 关闭连接 + # 完成请求 + sys.exit(0) + +signal.signal(signal.SIGTERM, shutdown_handler) +signal.signal(signal.SIGINT, shutdown_handler) + +app.run() +``` + +```go !! go +// Go - 优雅关闭 +package main + +import ( + "context" + "log" + "net/http" + "os" + "os/signal" + "syscall" + "time" +) + +func main() { + server := &http.Server{ + Addr: ":8080", + Handler: newHandler(), + } + + // 在 goroutine 中启动服务器 + go func() { + log.Println("Server starting") + if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { + log.Fatalf("Server error: %v", err) + } + }() + + // 等待中断信号 + quit := make(chan os.Signal, 1) + signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) + <-quit + + log.Println("Shutting down server...") + + // 带超时的优雅关闭 + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + if err := server.Shutdown(ctx); err != nil { + log.Fatalf("Server shutdown error: %v", err) + } + + log.Println("Server gracefully stopped") +} +``` + + +## 结构化日志 + + +```python !! py +# Python - Structlog +import structlog + +log = structlog.get_logger() + +def process_order(order_id): + log.info( + "processing_order", + order_id=order_id, + user_id=123, + ) + + try: + # 处理订单 + log.info( + "order_processed", + order_id=order_id, + amount=99.99, + ) + except Exception as e: + log.error( + "order_failed", + order_id=order_id, + error=str(e), + exc_info=e, + ) +``` + +```go !! go +// Go - Zap 结构化日志 +package main + +import ( + "go.uber.org/zap" +) + +func main() { + logger, _ := zap.NewProduction() + defer logger.Sync() + + sugar := logger.Sugar() + + func processOrder(orderID string) { + sugar.Infow( + "processing_order", + "order_id", orderID, + "user_id", 123, + ) + + if err := process(orderID); err != nil { + sugar.Errorw( + "order_failed", + "order_id", orderID, + "error", err, + ) + return + } + + sugar.Infow( + "order_processed", + "order_id", orderID, + "amount", 99.99, + ) + } +} +``` + + +## Prometheus 指标 + + +```python !! py +# Python - Prometheus 客户端 +from prometheus_client import Counter, Histogram, start_http_server + +request_count = Counter( + 'http_requests_total', + 'Total HTTP requests', + ['method', 'endpoint'] +) + +request_duration = Histogram( + 'http_request_duration_seconds', + 'HTTP request duration', + ['method', 'endpoint'] +) + +@app.route('/api/users') +def get_users(): + request_count.labels( + method='GET', + endpoint='/api/users' + ).inc() + + with request_duration.labels( + method='GET', + endpoint='/api/users' + ).time(): + # 处理请求 + return jsonify(users) + +start_http_server(9090) +``` + +```go !! go +// Go - Prometheus 指标 +package main + +import ( + "net/http" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promhttp" +) + +var ( + requestCount = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Name: "http_requests_total", + Help: "Total HTTP requests", + }, + []string{"method", "endpoint"}, + ) + + requestDuration = prometheus.NewHistogramVec( + prometheus.HistogramOpts{ + Name: "http_request_duration_seconds", + Help: "HTTP request duration", + }, + []string{"method", "endpoint"}, + ) +) + +func init() { + prometheus.MustRegister(requestCount) + prometheus.MustRegister(requestDuration) +} + +func instrumentHandler(method, endpoint string, next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + requestCount.WithLabelValues(method, endpoint).Inc() + + timer := prometheus.NewTimer( + requestDuration.WithLabelValues(method, endpoint), + ) + + next.ServeHTTP(w, r) + + timer.ObserveDuration() + }) +} + +func main() { + // 指标端点 + http.Handle("/metrics", promhttp.Handler()) + + // 应用端点 + http.Handle("/api/users", + instrumentHandler("GET", "/api/users", + usersHandler(), + ), + ) + + http.ListenAndServe(":8080", nil) +} +``` + + +## 分布式追踪回顾 + + +```go +// 完整的 OpenTelemetry 设置 +package main + +import ( + "context" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/exporters/jaeger" + "go.opentelemetry.io/otel/sdk/resource" + tracesdk "go.opentelemetry.io/otel/sdk/trace" + semconv "go.opentelemetry.io/otel/semconv/v1.4.0" +) + +func initTracer(serviceName, jaegerEndpoint string) error { + exporter, err := jaeger.New(jaeger.WithCollectorEndpoint( + jaeger.WithEndpoint(jaegerEndpoint), + )) + if err != nil { + return err + } + + tp := tracesdk.NewTracerProvider( + tracesdk.WithBatcher(exporter), + tracesdk.WithResource(resource.NewWithAttributes( + semconv.SchemaURL, + semconv.ServiceNameKey.String(serviceName), + )), + ) + + otel.SetTracerProvider(tp) + return nil +} + +func handleRequest(ctx context.Context) { + tracer := otel.Tracer("my-service") + + ctx, span := tracer.Start(ctx, "handleRequest") + defer span.End() + + span.SetAttributes( + attribute.String("http.method", "GET"), + attribute.String("http.route", "/api/users"), + ) + + // 处理请求 + users := getUsers(ctx) + + span.SetAttributes( + attribute.Int("user.count", len(users)), + ) +} +``` + + +## 总结 + +在本模块中,你学习了: + +1. Go 的**云原生优势** +2. 使用 controller-runtime 的 **Kubernetes 控制器** +3. Go 中的**自定义资源定义** +4. **ConfigMaps 和 Secrets** 处理 +5. **健康探针**(liveness、readiness、startup) +6. **优雅关闭**模式 +7. 使用 Zap 的**结构化日志** +8. **Prometheus 指标**集成 +9. 使用 OpenTelemetry 的**分布式追踪** + +## 云原生最佳实践 + +1. **12-Factor 应用方法论** + - 通过环境变量配置 + - 无状态进程 + - 端口绑定 + - 可丢弃性 + +2. **资源管理** + - 设置资源限制 + - 分析内存使用 + - 优雅处理 OOM 杀死 + +3. **可观测性** + - 结构化日志 + - 指标收集 + - 分布式追踪 + - 健康检查 + +4. **安全** + - 最小化容器 + - Secret 管理 + - 网络策略 + - RBAC + +## 练习 + +1. 为自定义资源创建 Kubernetes operator +2. 为你的服务实现健康探针 +3. 为应用程序添加 Prometheus 指标 +4. 使用 Jaeger 设置分布式追踪 +5. 使用 Helm 将 Go 应用部署到 Kubernetes + +## 下一步 + +下一模块: **常见陷阱和最佳实践** - 避免错误并编写地道的 Go 代码。 diff --git a/content/docs/py2go/module-14-cloud-native.zh-tw.mdx b/content/docs/py2go/module-14-cloud-native.zh-tw.mdx new file mode 100644 index 0000000..43be311 --- /dev/null +++ b/content/docs/py2go/module-14-cloud-native.zh-tw.mdx @@ -0,0 +1,682 @@ +--- +title: "Module 14: 雲端原生開發" +description: "使用 Go 和 Kubernetes 建構雲端原生應用" +--- + +## 簡介 + +Go 是雲端原生生態系統的語言。Kubernetes、Docker、Terraform 和許多其他雲端工具都是用 Go 編寫的。本模組涵蓋雲端原生開發模式。 + +## 為什麼 Go 適合雲端原生? + +Go 在雲端原生開發中的優勢: +- **小體積二進位** - 易於容器化 +- **快速編譯** - 快速迭代 +- **低記憶體佔用** - 高效的資源使用 +- **靜態連結** - 無相依性 +- **交叉編譯** - 在任何地方建構,部署到任何地方 + +## Kubernetes 控制器 + + +```python !! py +# Python - 使用 kopf 的 Kubernetes operator +import kopf + +@kopf.on.create('myapp.example.com', 'v1', 'myapp') +def create_fn(body, spec, **kwargs): + print(f"Creating MyApp: {body.metadata.name}") + + # 建立資源 + # 處理邏輯 + return {'created': True} + +@kopf.on.update('myapp.example.com', 'v1', 'myapp') +def update_fn(body, spec, **kwargs): + print(f"Updating MyApp: {body.metadata.name}") + return {'updated': True} +``` + +```go !! go +// Go - 使用 controller-runtime 的 Kubernetes controller +package main + +import ( + "context" + "fmt" + + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// MyApp reconciler +type MyAppReconciler struct { + client.Client + Scheme *runtime.Scheme +} + +func (r *MyAppReconciler) Reconcile( + ctx context.Context, + req ctrl.Request, +) (ctrl.Result, error) { + fmt.Printf("Reconciling MyApp: %s\n", req.Name) + + // 取得資源 + // 檢查狀態 + // 建立/更新資源 + + return ctrl.Result{}, nil +} + +func (r *MyAppReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&myappv1.MyApp{}). + Complete(r) +} + +func main() { + mgr, err := ctrl.NewManager( + ctrl.GetConfigOrDie(), + ctrl.Options{ + Scheme: scheme, + }, + ) + if err != nil { + panic(err) + } + + reconciler := &MyAppReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + } + + if err := reconciler.SetupWithManager(mgr); err != nil { + panic(err) + } + + mgr.Start(ctrl.SetupSignalHandler()) +} +``` + + +## 自訂資源定義 + + +```yaml +# CRD YAML (兩者相同) +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: myapps.myapp.example.com +spec: + group: myapp.example.com + versions: + - name: v1 + served: true + storage: true + schema: + openAPIV3Schema: + type: object + properties: + spec: + type: object + properties: + replicas: + type: integer + image: + type: string + scope: Namespaced + names: + plural: myapps + singular: myapp + kind: MyApp + shortNames: + - ma +``` + +```go +// Go - CRD Go 類型 +package v1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// +genclient +// +kubebuilder:object:root=true + +type MyApp struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec MyAppSpec `json:"spec,omitempty"` + Status MyAppStatus `json:"status,omitempty"` +} + +type MyAppSpec struct { + Replicas int `json:"replicas"` + Image string `json:"image"` +} + +type MyAppStatus struct { + ReadyReplicas int `json:"readyReplicas"` +} + +// +kubebuilder:object:root=true + +type MyAppList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + + Items []MyApp `json:"items"` +} + +func init() { + SchemeBuilder.Register(&MyApp{}, &MyAppList{}) +} +``` + + +## ConfigMap 和 Secret + + +```python !! py +# Python - 從環境或檔案讀取 +import os +from kubernetes import client, config + +config.load_kube_config() +v1 = client.CoreV1Api() + +# 取得 ConfigMap +cm = v1.read_namespaced_config_map( + 'app-config', + 'default' +) +print(cm.data) + +# 取得 Secret +secret = v1.read_namespaced_secret( + 'app-secret', + 'default' +) +print(secret.data) +``` + +```go !! go +// Go - 監聽 ConfigMaps 和 Secrets +package main + +import ( + "context" + "fmt" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/labels" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +func watchConfig(ctx context.Context, c client.Client) error { + configMap := &corev1.ConfigMap{} + err := c.Get( + ctx, + client.ObjectKey{ + Name: "app-config", + Namespace: "default", + }, + configMap, + ) + + if err != nil { + return err + } + + fmt.Println("Config data:", configMap.Data) + + // 監聽變更 + watcher, err := c.Watch( + ctx, + &corev1.ConfigMap{}, + client.InNamespace("default"), + ) + if err != nil { + return err + } + + for event := range watcher.ResultChan() { + cm := event.Object.(*corev1.ConfigMap) + fmt.Printf("Config updated: %s\n", cm.Name) + } + + return nil +} +``` + + +## 探針(Liveness、Readiness、Startup) + + +```python !! py +# Python - Flask 健康端點 +from flask import Flask + +app = Flask(__name__) + +@app.route('/healthz') +def liveness(): + # 存活探針 - 應用是否存活? + return {'status': 'alive'}, 200 + +@app.route('/readyz') +def readiness(): + # 就緒探針 - 是否可以處理流量? + try: + db.ping() + return {'status': 'ready'}, 200 + except: + return {'status': 'not ready'}, 503 + +@app.route('/startupz') +def startup(): + # 啟動探針 - 應用是否已啟動? + return {'status': 'started'}, 200 +``` + +```go !! go +// Go - 健康探針處理器 +package main + +import ( + "database/sql" + "net/http" +) + +func livenessHandler(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Write([]byte("OK")) +} + +func readinessHandler(db *sql.DB) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if err := db.Ping(); err != nil { + http.Error(w, "Database not ready", http.StatusServiceUnavailable) + return + } + + w.WriteHeader(http.StatusOK) + w.Write([]byte("OK")) + } +} + +func startupHandler(isReady *bool) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if !*isReady { + http.Error(w, "Starting up", http.StatusServiceUnavailable) + return + } + + w.WriteHeader(http.StatusOK) + w.Write([]byte("OK")) + } +} + +func main() { + http.HandleFunc("/healthz", livenessHandler) + http.HandleFunc("/readyz", readinessHandler(db)) + http.HandleFunc("/startupz", startupHandler(&ready)) + + http.ListenAndServe(":8080", nil) +} +``` + + +## 優雅關閉 + + +```python !! py +# Python - 訊號處理 +import signal +import sys + +def shutdown_handler(signum, frame): + print("Shutting down gracefully...") + # 清理 + # 關閉連線 + # 完成請求 + sys.exit(0) + +signal.signal(signal.SIGTERM, shutdown_handler) +signal.signal(signal.SIGINT, shutdown_handler) + +app.run() +``` + +```go !! go +// Go - 優雅關閉 +package main + +import ( + "context" + "log" + "net/http" + "os" + "os/signal" + "syscall" + "time" +) + +func main() { + server := &http.Server{ + Addr: ":8080", + Handler: newHandler(), + } + + // 在 goroutine 中啟動伺服器 + go func() { + log.Println("Server starting") + if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { + log.Fatalf("Server error: %v", err) + } + }() + + // 等待中斷訊號 + quit := make(chan os.Signal, 1) + signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) + <-quit + + log.Println("Shutting down server...") + + // 帶逾時的優雅關閉 + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + if err := server.Shutdown(ctx); err != nil { + log.Fatalf("Server shutdown error: %v", err) + } + + log.Println("Server gracefully stopped") +} +``` + + +## 結構化日誌 + + +```python !! py +# Python - Structlog +import structlog + +log = structlog.get_logger() + +def process_order(order_id): + log.info( + "processing_order", + order_id=order_id, + user_id=123, + ) + + try: + # 處理訂單 + log.info( + "order_processed", + order_id=order_id, + amount=99.99, + ) + except Exception as e: + log.error( + "order_failed", + order_id=order_id, + error=str(e), + exc_info=e, + ) +``` + +```go !! go +// Go - Zap 結構化日誌 +package main + +import ( + "go.uber.org/zap" +) + +func main() { + logger, _ := zap.NewProduction() + defer logger.Sync() + + sugar := logger.Sugar() + + func processOrder(orderID string) { + sugar.Infow( + "processing_order", + "order_id", orderID, + "user_id", 123, + ) + + if err := process(orderID); err != nil { + sugar.Errorw( + "order_failed", + "order_id", orderID, + "error", err, + ) + return + } + + sugar.Infow( + "order_processed", + "order_id", orderID, + "amount", 99.99, + ) + } +} +``` + + +## Prometheus 指標 + + +```python !! py +# Python - Prometheus 客戶端 +from prometheus_client import Counter, Histogram, start_http_server + +request_count = Counter( + 'http_requests_total', + 'Total HTTP requests', + ['method', 'endpoint'] +) + +request_duration = Histogram( + 'http_request_duration_seconds', + 'HTTP request duration', + ['method', 'endpoint'] +) + +@app.route('/api/users') +def get_users(): + request_count.labels( + method='GET', + endpoint='/api/users' + ).inc() + + with request_duration.labels( + method='GET', + endpoint='/api/users' + ).time(): + # 處理請求 + return jsonify(users) + +start_http_server(9090) +``` + +```go !! go +// Go - Prometheus 指標 +package main + +import ( + "net/http" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promhttp" +) + +var ( + requestCount = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Name: "http_requests_total", + Help: "Total HTTP requests", + }, + []string{"method", "endpoint"}, + ) + + requestDuration = prometheus.NewHistogramVec( + prometheus.HistogramOpts{ + Name: "http_request_duration_seconds", + Help: "HTTP request duration", + }, + []string{"method", "endpoint"}, + ) +) + +func init() { + prometheus.MustRegister(requestCount) + prometheus.MustRegister(requestDuration) +} + +func instrumentHandler(method, endpoint string, next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + requestCount.WithLabelValues(method, endpoint).Inc() + + timer := prometheus.NewTimer( + requestDuration.WithLabelValues(method, endpoint), + ) + + next.ServeHTTP(w, r) + + timer.ObserveDuration() + }) +} + +func main() { + // 指標端點 + http.Handle("/metrics", promhttp.Handler()) + + // 應用端點 + http.Handle("/api/users", + instrumentHandler("GET", "/api/users", + usersHandler(), + ), + ) + + http.ListenAndServe(":8080", nil) +} +``` + + +## 分散式追蹤回顧 + + +```go +// 完整的 OpenTelemetry 設定 +package main + +import ( + "context" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/exporters/jaeger" + "go.opentelemetry.io/otel/sdk/resource" + tracesdk "go.opentelemetry.io/otel/sdk/trace" + semconv "go.opentelemetry.io/otel/semconv/v1.4.0" +) + +func initTracer(serviceName, jaegerEndpoint string) error { + exporter, err := jaeger.New(jaeger.WithCollectorEndpoint( + jaeger.WithEndpoint(jaegerEndpoint), + )) + if err != nil { + return err + } + + tp := tracesdk.NewTracerProvider( + tracesdk.WithBatcher(exporter), + tracesdk.WithResource(resource.NewWithAttributes( + semconv.SchemaURL, + semconv.ServiceNameKey.String(serviceName), + )), + ) + + otel.SetTracerProvider(tp) + return nil +} + +func handleRequest(ctx context.Context) { + tracer := otel.Tracer("my-service") + + ctx, span := tracer.Start(ctx, "handleRequest") + defer span.End() + + span.SetAttributes( + attribute.String("http.method", "GET"), + attribute.String("http.route", "/api/users"), + ) + + // 處理請求 + users := getUsers(ctx) + + span.SetAttributes( + attribute.Int("user.count", len(users)), + ) +} +``` + + +## 總結 + +在本模組中,你學習了: + +1. Go 的**雲端原生優勢** +2. 使用 controller-runtime 的 **Kubernetes 控制器** +3. Go 中的**自訂資源定義** +4. **ConfigMaps 和 Secrets** 處理 +5. **健康探針**(liveness、readiness、startup) +6. **優雅關閉**模式 +7. 使用 Zap 的**結構化日誌** +8. **Prometheus 指標**整合 +9. 使用 OpenTelemetry 的**分散式追蹤** + +## 雲端原生最佳實踐 + +1. **12-Factor 應用方法論** + - 透過環境變數設定 + - 無狀態程序 + - 連接埠綁定 + - 可拋棄性 + +2. **資源管理** + - 設定資源限制 + - 分析記憶體使用 + - 優雅處理 OOM 殺死 + +3. **可觀測性** + - 結構化日誌 + - 指標收集 + - 分散式追蹤 + - 健康檢查 + +4. **安全** + - 最小化容器 + - Secret 管理 + - 網路原則 + - RBAC + +## 練習 + +1. 為自訂資源建立 Kubernetes operator +2. 為你的服務實作健康探針 +3. 為應用程式新增 Prometheus 指標 +4. 使用 Jaeger 設定分散式追蹤 +5. 使用 Helm 將 Go 應用部署到 Kubernetes + +## 下一步 + +下一模組: **常見陷阱和最佳實踐** - 避免錯誤並編寫地道的 Go 程式碼。 diff --git a/content/docs/py2go/module-15-common-pitfalls.mdx b/content/docs/py2go/module-15-common-pitfalls.mdx new file mode 100644 index 0000000..8ba541d --- /dev/null +++ b/content/docs/py2go/module-15-common-pitfalls.mdx @@ -0,0 +1,533 @@ +--- +title: "Module 15: Common Pitfalls and Best Practices" +description: "Avoiding common mistakes when transitioning from Python to Go" +--- + +## Introduction + +This module covers common mistakes Python developers make when learning Go, and best practices for writing idiomatic Go code. + +## Pitfall 1: Using Pointers Incorrectly + + +```python !! py +# Python - Everything is a reference +def modify_item(item): + item['value'] = 42 # Modifies original + +data = {'value': 0} +modify_item(data) +print(data['value']) # 42 +``` + +```go !! go +// WRONG - Unnecessary pointer +func (p *MyStruct) GetValue() int { + return p.value // No need for pointer receiver +} + +// RIGHT - Pointer only when modifying +func (p *MyStruct) SetValue(v int) { + p.value = v // Pointer receiver needed +} + +// RIGHT - Value receiver for read-only +func (m MyStruct) GetValue() int { + return m.value // Value receiver is fine +} +``` + + +## Pitfall 2: Not Handling Errors + + +```python !! py +# Python - Exceptions are automatic +def divide(a, b): + return a / b # May raise ZeroDivisionError + +try: + result = divide(10, 0) +except ZeroDivisionError: + print("Cannot divide by zero") +``` + +```go !! go +// WRONG - Ignoring errors +func divide(a, b int) int { + return a / b // Panics on b=0 +} + +// RIGHT - Return errors +func divide(a, b int) (int, error) { + if b == 0 { + return 0, errors.New("division by zero") + } + return a / b, nil +} + +// RIGHT - Always check errors +result, err := divide(10, 0) +if err != nil { + log.Println("Error:", err) + return +} +fmt.Println(result) +``` + + +## Pitfall 3: Goroutine Leaks + + +```python !! py +# Python - Thread may not exit +import threading + +def worker(): + while True: + data = queue.get() + if data == "STOP": + break + process(data) + +t = threading.Thread(target=worker) +t.start() +# If we forget to send "STOP", thread never exits +``` + +```go !! go +// WRONG - Goroutine never exits +func worker() { + for { + data := <-ch + process(data) + } +} + +// RIGHT - Always have exit condition +func worker(stop <-chan struct{}) { + for { + select { + case data := <-ch: + process(data) + case <-stop: + return // Exit goroutine + } + } +} + +// Usage +stop := make(chan struct{}) +go worker(stop) + +// Signal stop +close(stop) +``` + + +## Pitfall 4: Loop Variable Capture + + +```python !! py +# Python - Late binding closure issue +funcs = [] +for i in range(3): + funcs.append(lambda: print(i)) + +for f in funcs: + f() # Prints 2, 2, 2 (all reference same i) + +# Fix - capture value +funcs = [] +for i in range(3): + funcs.append(lambda i=i: print(i)) +``` + +```go !! go +// WRONG - All goroutines capture same i +for i := 0; i < 3; i++ { + go func() { + fmt.Println(i) // May print 3, 3, 3 + }() +} + +// RIGHT - Pass as parameter +for i := 0; i < 3; i++ { + go func(n int) { + fmt.Println(n) // Prints 0, 1, 2 + }(i) +} +``` + + +## Pitfall 5: defer in Loops + + +```python !! py +# Python - Cleanup happens immediately or with context manager +for filename in filenames: + with open(filename) as f: + process(f) + # File closed here +``` + +```go !! go +// WRONG - defer runs at function exit, not loop iteration +func processFiles(filenames []string) { + for _, filename := range filenames { + file, err := os.Open(filename) + if err != nil { + return err + } + defer file.Close() // Wrong! Runs at function exit + + process(file) + } + // All files closed here (too late, may run out of file descriptors) +} + +// RIGHT - Create function or close immediately +func processFiles(filenames []string) error { + for _, filename := range filenames { + if err := processFile(filename); err != nil { + return err + } + } + return nil +} + +func processFile(filename string) error { + file, err := os.Open(filename) + if err != nil { + return err + } + defer file.Close() // OK - runs when processFile returns + + process(file) + return nil +} +``` + + +## Pitfall 6: String vs []byte + + +```python !! py +# Python - Strings and bytes +text = "hello" +data = b"hello" # bytes + +# Conversion +text = data.decode() +data = text.encode() +``` + +```go !! go +// WRONG - Unnecessary conversions +func processData(data string) []byte { + return []byte(data) // Creates copy +} + +// RIGHT - Use bytes directly when possible +func processData(data []byte) { + // Process bytes directly +} + +// RIGHT - Only convert when necessary +func readJSON(r io.Reader) (map[string]interface{}, error) { + var result map[string]interface{} + decoder := json.NewDecoder(r) + err := decoder.Decode(&result) + return result, err +} +``` + + +## Pitfall 7: Channel Blocking + + +```python !! py +# Python - Queue with timeout +try: + item = queue.get(timeout=1.0) +except queue.Empty: + item = None +``` + +```go !! go +// WRONG - May block forever +item := <-ch + +// RIGHT - Use select with timeout +select { +case item := <-ch: + // Process item +case <-time.After(time.Second): + // Handle timeout +} + +// RIGHT - Use default for non-blocking +select { +case item := <-ch: + // Process item +default: + // No data available +} +``` + + +## Pitfall 8: Nil Slices vs Empty Slices + + +```python !! py +# Python - None vs empty list +items1 = None +items2 = [] + +if items1: + print("Has items") # Doesn't print + +if items2: + print("Has items") # Doesn't print +``` + +```go !! go +// Go - Important distinction +var nilSlice []int // nil slice +emptySlice := []int{} // empty slice + +// Both have len() == 0 +fmt.Println(len(nilSlice)) // 0 +fmt.Println(len(emptySlice)) // 0 + +// But JSON encoding differs +json.Marshal(nilSlice) // null +json.Marshal(emptySlice) // [] + +// RIGHT - Use empty slice for JSON +func processItems() []int { + result := make([]int, 0) // Empty, not nil + + // Process... + return result // Always returns [], never null +} +``` + + +## Best Practices + +### 1. Error Messages + + +```python !! py +# Python +raise ValueError("Invalid user ID") +``` + +```go !! go +// WRONG - Generic error +return fmt.Errorf("error") + +// WRONG - Error without context +return errors.New("not found") + +// RIGHT - Descriptive error +return fmt.Errorf("user not found: ID %d", userID) + +// RIGHT - Wrap errors with context +return fmt.Errorf("failed to process user: %w", err) +``` + + +### 2. Variable Naming + + +```python !! py +# Python - snake_case +user_name = "Alice" +total_count = 42 +MAX_CONNECTIONS = 100 +``` + +```go !! go +// Go - camelCase (exported = PascalCase) +userName := "Alice" +totalCount := 42 +const MaxConnections = 100 // Exported +const maxRetries = 3 // Unexported +``` + + +### 3. Interface Design + + +```python !! py +# Python - Duck typing, no explicit interfaces +def process(obj): + if hasattr(obj, 'process'): + obj.process() +``` + +```go !! go +// WRONG - Giant interface +type Processor interface { + Process() + Validate() + Save() + Delete() + Update() +} + +// RIGHT - Small, focused interfaces +type Processor interface { + Process() error +} + +type Validator interface { + Validate() error +} + +// RIGHT - Accept interface, return struct +func ProcessData(p Processor) error { + return p.Process() +} +``` + + +### 4. Package Structure + + +```bash +# Python - Flat or nested +project/ +├── __init__.py +├── models.py +├── views.py +└── utils.py +``` + +```bash +# Go - Hierarchical +project/ +├── go.mod +├── main.go +├── internal/ +│ ├── app/ +│ ├── handler/ +│ └── service/ +├── pkg/ +│ └── util/ +└── api/ + └── v1/ +``` + + +### 5. Concurrency Patterns + + +```python !! py +# Python - Limited by GIL +import threading + +def worker(): + # Only one thread runs at a time + process() + +threads = [threading.Thread(target=worker) for _ in range(10)] +for t in threads: + t.start() +``` + +```go !! go +// RIGHT - Use worker pools +func workerPool(jobs <-chan Job, results chan<- Result) { + const numWorkers = 10 + + var wg sync.WaitGroup + for i := 0; i < numWorkers; i++ { + wg.Add(1) + go func() { + defer wg.Done() + for job := range jobs { + results <- process(job) + } + }() + } + + go func() { + wg.Wait() + close(results) + }() +} +``` + + +## Idiomatic Go Patterns + +### Context for Cancellation + + +```go +// RIGHT - Always accept context as first parameter +func GetUser(ctx context.Context, userID int) (*User, error) { + // Check for cancellation + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + // Query database + user, err := db.QueryUser(ctx, userID) + if err != nil { + return nil, err + } + + return user, nil +} + +// RIGHT - Pass context through call chain +func processRequest(ctx context.Context, req Request) error { + user, err := GetUser(ctx, req.UserID) + if err != nil { + return err + } + + return UpdateUser(ctx, user) +} +``` + + +## Summary + +Common pitfalls to avoid: +1. **Unnecessary pointers** - Use only when needed +2. **Ignoring errors** - Always check error returns +3. **Goroutine leaks** - Always provide exit condition +4. **Loop variable capture** - Pass as parameter +5. **defer in loops** - Use separate function +6. **String vs []byte** - Use appropriate type +7. **Channel blocking** - Use select with timeout/default +8. **Nil vs empty slices** - Understand the difference + +Best practices: +1. **Descriptive errors** - Include context +2. **Proper naming** - Follow Go conventions +3. **Small interfaces** - Focused, composable +4. **Package organization** - internal/, pkg/ +5. **Worker pools** - Limit goroutines +6. **Context propagation** - Pass through calls +7. **Early returns** - Check errors first +8. **Explicit is better** - Clear and readable + +## Exercises + +1. Find and fix goroutine leaks in sample code +2. Refactor to use proper error handling +3. Implement a worker pool pattern +4. Convert Python-style code to idiomatic Go +5. Review code for common pitfalls + +## Next Steps + +Next module: **Real-world Projects** - Complete projects to solidify your knowledge. diff --git a/content/docs/py2go/module-15-common-pitfalls.zh-cn.mdx b/content/docs/py2go/module-15-common-pitfalls.zh-cn.mdx new file mode 100644 index 0000000..107d612 --- /dev/null +++ b/content/docs/py2go/module-15-common-pitfalls.zh-cn.mdx @@ -0,0 +1,533 @@ +--- +title: "Module 15: 常见陷阱和最佳实践" +description: "避免从 Python 转到 Go 时的常见错误" +--- + +## 简介 + +本模块涵盖 Python 开发者在学习 Go 时常犯的错误,以及编写地道 Go 代码的最佳实践。 + +## 陷阱 1: 错误使用指针 + + +```python !! py +# Python - 一切都是引用 +def modify_item(item): + item['value'] = 42 # 修改原始对象 + +data = {'value': 0} +modify_item(data) +print(data['value']) # 42 +``` + +```go !! go +// 错误 - 不必要的指针 +func (p *MyStruct) GetValue() int { + return p.value // 不需要指针接收者 +} + +// 正确 - 只在修改时使用指针 +func (p *MyStruct) SetValue(v int) { + p.value = v // 需要指针接收者 +} + +// 正确 - 只读使用值接收者 +func (m MyStruct) GetValue() int { + return m.value // 值接收者就可以 +} +``` + + +## 陷阱 2: 不处理错误 + + +```python !! py +# Python - 异常是自动的 +def divide(a, b): + return a / b # 可能引发 ZeroDivisionError + +try: + result = divide(10, 0) +except ZeroDivisionError: + print("Cannot divide by zero") +``` + +```go !! go +// 错误 - 忽略错误 +func divide(a, b int) int { + return a / b // 当 b=0 时会 panic +} + +// 正确 - 返回错误 +func divide(a, b int) (int, error) { + if b == 0 { + return 0, errors.New("division by zero") + } + return a / b, nil +} + +// 正确 - 始终检查错误 +result, err := divide(10, 0) +if err != nil { + log.Println("Error:", err) + return +} +fmt.Println(result) +``` + + +## 陷阱 3: Goroutine 泄漏 + + +```python !! py +# Python - 线程可能不会退出 +import threading + +def worker(): + while True: + data = queue.get() + if data == "STOP": + break + process(data) + +t = threading.Thread(target=worker) +t.start() +# 如果忘记发送 "STOP",线程永远不会退出 +``` + +```go !! go +// 错误 - Goroutine 永不退出 +func worker() { + for { + data := <-ch + process(data) + } +} + +// 正确 - 始终有退出条件 +func worker(stop <-chan struct{}) { + for { + select { + case data := <-ch: + process(data) + case <-stop: + return // 退出 goroutine + } + } +} + +// 使用 +stop := make(chan struct{}) +go worker(stop) + +// 发送停止信号 +close(stop) +``` + + +## 陷阱 4: 循环变量捕获 + + +```python !! py +# Python - 延迟绑定闭包问题 +funcs = [] +for i in range(3): + funcs.append(lambda: print(i)) + +for f in funcs: + f() # 打印 2, 2, 2 (都引用同一个 i) + +# 修复 - 捕获值 +funcs = [] +for i in range(3): + funcs.append(lambda i=i: print(i)) +``` + +```go !! go +// 错误 - 所有 goroutine 捕获同一个 i +for i := 0; i < 3; i++ { + go func() { + fmt.Println(i) // 可能打印 3, 3, 3 + }() +} + +// 正确 - 作为参数传递 +for i := 0; i < 3; i++ { + go func(n int) { + fmt.Println(n) // 打印 0, 1, 2 + }(i) +} +``` + + +## 陷阱 5: 循环中的 defer + + +```python !! py +# Python - 清理立即发生或使用上下文管理器 +for filename in filenames: + with open(filename) as f: + process(f) + # 文件在此处关闭 +``` + +```go !! go +// 错误 - defer 在函数退出时运行,而非循环迭代时 +func processFiles(filenames []string) { + for _, filename := range filenames { + file, err := os.Open(filename) + if err != nil { + return err + } + defer file.Close() // 错误! 在函数退出时运行 + + process(file) + } + // 所有文件在此处关闭 (太晚了,可能会耗尽文件描述符) +} + +// 正确 - 创建函数或立即关闭 +func processFiles(filenames []string) error { + for _, filename := range filenames { + if err := processFile(filename); err != nil { + return err + } + } + return nil +} + +func processFile(filename string) error { + file, err := os.Open(filename) + if err != nil { + return err + } + defer file.Close() // 正确 - 在 processFile 返回时运行 + + process(file) + return nil +} +``` + + +## 陷阱 6: 字符串 vs []byte + + +```python !! py +# Python - 字符串和字节 +text = "hello" +data = b"hello" # 字节 + +# 转换 +text = data.decode() +data = text.encode() +``` + +```go !! go +// 错误 - 不必要的转换 +func processData(data string) []byte { + return []byte(data) // 创建副本 +} + +// 正确 - 尽可能直接使用字节 +func processData(data []byte) { + // 直接处理字节 +} + +// 正确 - 只在必要时转换 +func readJSON(r io.Reader) (map[string]interface{}, error) { + var result map[string]interface{} + decoder := json.NewDecoder(r) + err := decoder.Decode(&result) + return result, err +} +``` + + +## 陷阱 7: Channel 阻塞 + + +```python !! py +# Python - 带超时的队列 +try: + item = queue.get(timeout=1.0) +except queue.Empty: + item = None +``` + +```go !! go +// 错误 - 可能永久阻塞 +item := <-ch + +// 正确 - 使用 select 和超时 +select { +case item := <-ch: + // 处理 item +case <-time.After(time.Second): + // 处理超时 +} + +// 正确 - 使用 default 实现非阻塞 +select { +case item := <-ch: + // 处理 item +default: + // 没有可用数据 +} +``` + + +## 陷阱 8: Nil 切片 vs 空切片 + + +```python !! py +# Python - None vs 空列表 +items1 = None +items2 = [] + +if items1: + print("Has items") # 不打印 + +if items2: + print("Has items") # 不打印 +``` + +```go !! go +// Go - 重要区别 +var nilSlice []int // nil 切片 +emptySlice := []int{} // 空切片 + +// 两者长度都是 0 +fmt.Println(len(nilSlice)) // 0 +fmt.Println(len(emptySlice)) // 0 + +// 但 JSON 编码不同 +json.Marshal(nilSlice) // null +json.Marshal(emptySlice) // [] + +// 正确 - JSON 使用空切片 +func processItems() []int { + result := make([]int, 0) // 空的,不是 nil + + // 处理... + return result // 始终返回 [],从不返回 null +} +``` + + +## 最佳实践 + +### 1. 错误消息 + + +```python !! py +# Python +raise ValueError("Invalid user ID") +``` + +```go !! go +// 错误 - 通用错误 +return fmt.Errorf("error") + +// 错误 - 没有上下文的错误 +return errors.New("not found") + +// 正确 - 描述性错误 +return fmt.Errorf("user not found: ID %d", userID) + +// 正确 - 包装错误并添加上下文 +return fmt.Errorf("failed to process user: %w", err) +``` + + +### 2. 变量命名 + + +```python !! py +# Python - snake_case +user_name = "Alice" +total_count = 42 +MAX_CONNECTIONS = 100 +``` + +```go !! go +// Go - camelCase (导出 = PascalCase) +userName := "Alice" +totalCount := 42 +const MaxConnections = 100 // 导出的 +const maxRetries = 3 // 未导出的 +``` + + +### 3. 接口设计 + + +```python !! py +# Python - 鸭子类型,没有显式接口 +def process(obj): + if hasattr(obj, 'process'): + obj.process() +``` + +```go !! go +// 错误 - 巨大的接口 +type Processor interface { + Process() + Validate() + Save() + Delete() + Update() +} + +// 正确 - 小而专注的接口 +type Processor interface { + Process() error +} + +type Validator interface { + Validate() error +} + +// 正确 - 接受接口,返回结构体 +func ProcessData(p Processor) error { + return p.Process() +} +``` + + +### 4. 包结构 + + +```bash +# Python - 扁平或嵌套 +project/ +├── __init__.py +├── models.py +├── views.py +└── utils.py +``` + +```bash +# Go - 层次化 +project/ +├── go.mod +├── main.go +├── internal/ +│ ├── app/ +│ ├── handler/ +│ └── service/ +├── pkg/ +│ └── util/ +└── api/ + └── v1/ +``` + + +### 5. 并发模式 + + +```python !! py +# Python - 受 GIL 限制 +import threading + +def worker(): + # 一次只能运行一个线程 + process() + +threads = [threading.Thread(target=worker) for _ in range(10)] +for t in threads: + t.start() +``` + +```go !! go +// 正确 - 使用 worker pool +func workerPool(jobs <-chan Job, results chan<- Result) { + const numWorkers = 10 + + var wg sync.WaitGroup + for i := 0; i < numWorkers; i++ { + wg.Add(1) + go func() { + defer wg.Done() + for job := range jobs { + results <- process(job) + } + }() + } + + go func() { + wg.Wait() + close(results) + }() +} +``` + + +## 地道的 Go 模式 + +### Context 用于取消 + + +```go +// 正确 - 始终接受 context 作为第一个参数 +func GetUser(ctx context.Context, userID int) (*User, error) { + // 检查取消 + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + // 查询数据库 + user, err := db.QueryUser(ctx, userID) + if err != nil { + return nil, err + } + + return user, nil +} + +// 正确 - 通过调用链传递 context +func processRequest(ctx context.Context, req Request) error { + user, err := GetUser(ctx, req.UserID) + if err != nil { + return err + } + + return UpdateUser(ctx, user) +} +``` + + +## 总结 + +需要避免的常见陷阱: +1. **不必要的指针** - 只在需要时使用 +2. **忽略错误** - 始终检查错误返回 +3. **Goroutine 泄漏** - 始终提供退出条件 +4. **循环变量捕获** - 作为参数传递 +5. **循环中的 defer** - 使用单独的函数 +6. **字符串 vs []byte** - 使用适当的类型 +7. **Channel 阻塞** - 使用带超时/默认的 select +8. **Nil vs 空切片** - 理解区别 + +最佳实践: +1. **描述性错误** - 包含上下文 +2. **正确的命名** - 遵循 Go 约定 +3. **小接口** - 专注、可组合 +4. **包组织** - internal/、pkg/ +5. **Worker pool** - 限制 goroutine 数量 +6. **Context 传播** - 通过调用传递 +7. **提前返回** - 先检查错误 +8. **显式优于隐式** - 清晰易读 + +## 练习 + +1. 在示例代码中查找并修复 goroutine 泄漏 +2. 重构以使用正确的错误处理 +3. 实现 worker pool 模式 +4. 将 Python 风格的代码转换为地道的 Go +5. 审查代码中的常见陷阱 + +## 下一步 + +下一模块: **真实项目** - 完整的项目以巩固你的知识。 diff --git a/content/docs/py2go/module-15-common-pitfalls.zh-tw.mdx b/content/docs/py2go/module-15-common-pitfalls.zh-tw.mdx new file mode 100644 index 0000000..06cf331 --- /dev/null +++ b/content/docs/py2go/module-15-common-pitfalls.zh-tw.mdx @@ -0,0 +1,533 @@ +--- +title: "Module 15: 常見陷阱和最佳實踐" +description: "避免從 Python 轉到 Go 時的常見錯誤" +--- + +## 簡介 + +本模組涵蓋 Python 開發者在學習 Go 時常犯的錯誤,以及編寫地道 Go 程式碼的最佳實踐。 + +## 陷阱 1: 錯誤使用指標 + + +```python !! py +# Python - 一切都是參考 +def modify_item(item): + item['value'] = 42 # 修改原始物件 + +data = {'value': 0} +modify_item(data) +print(data['value']) # 42 +``` + +```go !! go +// 錯誤 - 不必要的指標 +func (p *MyStruct) GetValue() int { + return p.value // 不需要指標接收者 +} + +// 正確 - 只在修改時使用指標 +func (p *MyStruct) SetValue(v int) { + p.value = v // 需要指標接收者 +} + +// 正確 - 唯讀使用值接收者 +func (m MyStruct) GetValue() int { + return m.value // 值接收者就可以 +} +``` + + +## 陷阱 2: 不處理錯誤 + + +```python !! py +# Python - 異常是自動的 +def divide(a, b): + return a / b # 可能引發 ZeroDivisionError + +try: + result = divide(10, 0) +except ZeroDivisionError: + print("Cannot divide by zero") +``` + +```go !! go +// 錯誤 - 忽略錯誤 +func divide(a, b int) int { + return a / b // 當 b=0 時會 panic +} + +// 正確 - 返回錯誤 +func divide(a, b int) (int, error) { + if b == 0 { + return 0, errors.New("division by zero") + } + return a / b, nil +} + +// 正確 - 始終檢查錯誤 +result, err := divide(10, 0) +if err != nil { + log.Println("Error:", err) + return +} +fmt.Println(result) +``` + + +## 陷阱 3: Goroutine 泄漏 + + +```python !! py +# Python - 執行緒可能不會退出 +import threading + +def worker(): + while True: + data = queue.get() + if data == "STOP": + break + process(data) + +t = threading.Thread(target=worker) +t.start() +# 如果忘記發送 "STOP",執行緒永遠不會退出 +``` + +```go !! go +// 錯誤 - Goroutine 永不退出 +func worker() { + for { + data := <-ch + process(data) + } +} + +// 正確 - 始終有退出條件 +func worker(stop <-chan struct{}) { + for { + select { + case data := <-ch: + process(data) + case <-stop: + return // 退出 goroutine + } + } +} + +// 使用 +stop := make(chan struct{}) +go worker(stop) + +// 發送停止訊號 +close(stop) +``` + + +## 陷阱 4: 循環變數捕獲 + + +```python !! py +# Python - 延遲綁定閉包問題 +funcs = [] +for i in range(3): + funcs.append(lambda: print(i)) + +for f in funcs: + f() # 打印 2, 2, 2 (都引用同一個 i) + +# 修復 - 捕獲值 +funcs = [] +for i in range(3): + funcs.append(lambda i=i: print(i)) +``` + +```go !! go +// 錯誤 - 所有 goroutine 捕獲同一個 i +for i := 0; i < 3; i++ { + go func() { + fmt.Println(i) // 可能打印 3, 3, 3 + }() +} + +// 正確 - 作為參數傳遞 +for i := 0; i < 3; i++ { + go func(n int) { + fmt.Println(n) // 打印 0, 1, 2 + }(i) +} +``` + + +## 陷阱 5: 循環中的 defer + + +```python !! py +# Python - 清理立即發生或使用上下文管理器 +for filename in filenames: + with open(filename) as f: + process(f) + # 檔案在此處關閉 +``` + +```go !! go +// 錯誤 - defer 在函數退出時運行,而非循環迭代時 +func processFiles(filenames []string) { + for _, filename := range filenames { + file, err := os.Open(filename) + if err != nil { + return err + } + defer file.Close() // 錯誤! 在函數退出時運行 + + process(file) + } + // 所有檔案在此處關閉 (太晚了,可能會耗盡檔案描述符) +} + +// 正確 - 建立函數或立即關閉 +func processFiles(filenames []string) error { + for _, filename := range filenames { + if err := processFile(filename); err != nil { + return err + } + } + return nil +} + +func processFile(filename string) error { + file, err := os.Open(filename) + if err != nil { + return err + } + defer file.Close() // 正確 - 在 processFile 返回時運行 + + process(file) + return nil +} +``` + + +## 陷阱 6: 字串 vs []byte + + +```python !! py +# Python - 字串和位元組 +text = "hello" +data = b"hello" # 位元組 + +# 轉換 +text = data.decode() +data = text.encode() +``` + +```go !! go +// 錯誤 - 不必要的轉換 +func processData(data string) []byte { + return []byte(data) // 建立副本 +} + +// 正確 - 盡可能直接使用位元組 +func processData(data []byte) { + // 直接處理位元組 +} + +// 正確 - 只在必要時轉換 +func readJSON(r io.Reader) (map[string]interface{}, error) { + var result map[string]interface{} + decoder := json.NewDecoder(r) + err := decoder.Decode(&result) + return result, err +} +``` + + +## 陷阱 7: Channel 阻塞 + + +```python !! py +# Python - 帶逾時的佇列 +try: + item = queue.get(timeout=1.0) +except queue.Empty: + item = None +``` + +```go !! go +// 錯誤 - 可能永久阻塞 +item := <-ch + +// 正確 - 使用 select 和逾時 +select { +case item := <-ch: + // 處理 item +case <-time.After(time.Second): + // 處理逾時 +} + +// 正確 - 使用 default 實現非阻塞 +select { +case item := <-ch: + // 處理 item +default: + // 沒有可用資料 +} +``` + + +## 陷阱 8: Nil 切片 vs 空切片 + + +```python !! py +# Python - None vs 空列表 +items1 = None +items2 = [] + +if items1: + print("Has items") # 不打印 + +if items2: + print("Has items") # 不打印 +``` + +```go !! go +// Go - 重要區別 +var nilSlice []int // nil 切片 +emptySlice := []int{} // 空切片 + +// 兩者長度都是 0 +fmt.Println(len(nilSlice)) // 0 +fmt.Println(len(emptySlice)) // 0 + +// 但 JSON 編碼不同 +json.Marshal(nilSlice) // null +json.Marshal(emptySlice) // [] + +// 正確 - JSON 使用空切片 +func processItems() []int { + result := make([]int, 0) // 空的,不是 nil + + // 處理... + return result // 始終返回 [],從不返回 null +} +``` + + +## 最佳實踐 + +### 1. 錯誤訊息 + + +```python !! py +# Python +raise ValueError("Invalid user ID") +``` + +```go !! go +// 錯誤 - 通用錯誤 +return fmt.Errorf("error") + +// 錯誤 - 沒有上下文的錯誤 +return errors.New("not found") + +// 正確 - 描述性錯誤 +return fmt.Errorf("user not found: ID %d", userID) + +// 正確 - 包裝錯誤並新增上下文 +return fmt.Errorf("failed to process user: %w", err) +``` + + +### 2. 變數命名 + + +```python !! py +# Python - snake_case +user_name = "Alice" +total_count = 42 +MAX_CONNECTIONS = 100 +``` + +```go !! go +// Go - camelCase (導出 = PascalCase) +userName := "Alice" +totalCount := 42 +const MaxConnections = 100 // 導出的 +const maxRetries = 3 // 未導出的 +``` + + +### 3. 介面設計 + + +```python !! py +# Python - 鴨子類型,沒有顯式介面 +def process(obj): + if hasattr(obj, 'process'): + obj.process() +``` + +```go !! go +// 錯誤 - 巨大的介面 +type Processor interface { + Process() + Validate() + Save() + Delete() + Update() +} + +// 正確 - 小而專注的介面 +type Processor interface { + Process() error +} + +type Validator interface { + Validate() error +} + +// 正確 - 接受介面,返回結構體 +func ProcessData(p Processor) error { + return p.Process() +} +``` + + +### 4. 套件結構 + + +```bash +# Python - 扁平或巢狀 +project/ +├── __init__.py +├── models.py +├── views.py +└── utils.py +``` + +```bash +# Go - 層次化 +project/ +├── go.mod +├── main.go +├── internal/ +│ ├── app/ +│ ├── handler/ +│ └── service/ +├── pkg/ +│ └── util/ +└── api/ + └── v1/ +``` + + +### 5. 並發模式 + + +```python !! py +# Python - 受 GIL 限制 +import threading + +def worker(): + # 一次只能運行一個執行緒 + process() + +threads = [threading.Thread(target=worker) for _ in range(10)] +for t in threads: + t.start() +``` + +```go !! go +// 正確 - 使用 worker pool +func workerPool(jobs <-chan Job, results chan<- Result) { + const numWorkers = 10 + + var wg sync.WaitGroup + for i := 0; i < numWorkers; i++ { + wg.Add(1) + go func() { + defer wg.Done() + for job := range jobs { + results <- process(job) + } + }() + } + + go func() { + wg.Wait() + close(results) + }() +} +``` + + +## 地道的 Go 模式 + +### Context 用於取消 + + +```go +// 正確 - 始終接受 context 作為第一個參數 +func GetUser(ctx context.Context, userID int) (*User, error) { + // 檢查取消 + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + // 查詢資料庫 + user, err := db.QueryUser(ctx, userID) + if err != nil { + return nil, err + } + + return user, nil +} + +// 正確 - 透過調用鏈傳遞 context +func processRequest(ctx context.Context, req Request) error { + user, err := GetUser(ctx, req.UserID) + if err != nil { + return err + } + + return UpdateUser(ctx, user) +} +``` + + +## 總結 + +需要避免的常見陷阱: +1. **不必要的指標** - 只在需要時使用 +2. **忽略錯誤** - 始終檢查錯誤返回 +3. **Goroutine 泄漏** - 始終提供退出條件 +4. **循環變數捕獲** - 作為參數傳遞 +5. **循環中的 defer** - 使用單獨的函數 +6. **字串 vs []byte** - 使用適當的類型 +7. **Channel 阻塞** - 使用帶逾時/預設的 select +8. **Nil vs 空切片** - 理解區別 + +最佳實踐: +1. **描述性錯誤** - 包含上下文 +2. **正確的命名** - 遵循 Go 約定 +3. **小介面** - 專注、可組合 +4. **套件組織** - internal/、pkg/ +5. **Worker pool** - 限制 goroutine 數量 +6. **Context 傳播** - 透過調用傳遞 +7. **提前返回** - 先檢查錯誤 +8. **顯式優於隱式** - 清晰易讀 + +## 練習 + +1. 在示例程式碼中查找並修復 goroutine 泄漏 +2. 重構以使用正確的錯誤處理 +3. 實作 worker pool 模式 +4. 將 Python 風格的程式碼轉換為地道的 Go +5. 審查程式碼中的常見陷阱 + +## 下一步 + +下一模組: **真實專案** - 完整的專案以鞏固你的知識。 diff --git a/content/docs/py2go/module-16-real-projects.mdx b/content/docs/py2go/module-16-real-projects.mdx new file mode 100644 index 0000000..f0f3106 --- /dev/null +++ b/content/docs/py2go/module-16-real-projects.mdx @@ -0,0 +1,798 @@ +--- +title: "Module 16: Real-world Projects" +description: "Build complete projects to apply your Go knowledge" +--- + +## Introduction + +Time to put everything together! This module presents complete, practical projects that demonstrate real-world Go development. + +## Project 1: URL Shortener Service + +Build a production-ready URL shortener with: +- RESTful API +- Database persistence +- Caching layer +- Metrics collection +- Docker deployment + +### Architecture + +``` +┌─────────────┐ +│ Client │ +└──────┬──────┘ + │ + ▼ +┌─────────────────┐ +│ API Gateway │ +└────────┬────────┘ + │ + ┌────┴────┐ + ▼ ▼ +┌───────┐ ┌───────┐ +│ API │ │ Cache │ +└───┬───┘ └───────┘ + │ + ▼ +┌───────┐ +│ DB │ +└───────┘ +``` + +### Implementation + + +```python !! py +# Python - Models +from dataclasses import dataclass +from datetime import datetime +import hashlib +import random +import string + +@dataclass +class URL: + id: int + short_code: str + original_url: str + created_at: datetime + clicks: int + +def generate_short_code(url: str) -> str: + # Generate random 6-character code + chars = string.ascii_letters + string.digits + return ''.join(random.choices(chars, k=6)) +``` + +```go !! go +// Go - Models +package models + +import "time" + +type URL struct { + ID int `json:"id"` + ShortCode string `json:"short_code"` + OriginalURL string `json:"original_url"` + CreatedAt time.Time `json:"created_at"` + Clicks int `json:"clicks"` +} + +func GenerateShortCode() string { + // Generate random 6-character code + const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + b := make([]byte, 6) + for i := range b { + b[i] = chars[rand.Intn(len(chars))] + } + return string(b) +} +``` + + + +```python !! py +# Python - Flask API +from flask import Flask, request, jsonify, redirect +from datetime import datetime + +app = Flask(__name__) + +@app.route('/shorten', methods=['POST']) +def shorten(): + data = request.get_json() + url = data.get('url') + + if not url: + return jsonify({'error': 'URL required'}), 400 + + short_code = generate_short_code(url) + + # Save to database + url_obj = URL( + id=1, + short_code=short_code, + original_url=url, + created_at=datetime.now(), + clicks=0 + ) + + return jsonify({ + 'short_url': f'https://short.ly/{short_code}' + }), 201 + +@app.route('/') +def redirect_url(short_code): + # Lookup URL + url_obj = get_url(short_code) + + if not url_obj: + return jsonify({'error': 'Not found'}), 404 + + # Increment clicks + url_obj.clicks += 1 + + return redirect(url_obj.original_url) +``` + +```go !! go +// Go - HTTP API +package main + +import ( + "database/sql" + "encoding/json" + "fmt" + "net/http" + "time" + + _ "github.com/lib/pq" +) + +type Server struct { + db *sql.DB +} + +func (s *Server) shortenHandler(w http.ResponseWriter, r *http.Request) { + var req struct { + URL string `json:"url"` + } + + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + if req.URL == "" { + http.Error(w, "URL required", http.StatusBadRequest) + return + } + + shortCode := GenerateShortCode() + + // Save to database + var id int + err := s.db.QueryRow( + "INSERT INTO urls (short_code, original_url, created_at, clicks) VALUES ($1, $2, $3, $4) RETURNING id", + shortCode, req.URL, time.Now(), 0, + ).Scan(&id) + + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + json.NewEncoder(w).Encode(map[string]string{ + "short_url": fmt.Sprintf("https://short.ly/%s", shortCode), + }) +} + +func (s *Server) redirectHandler(w http.ResponseWriter, r *http.Request) { + shortCode := r.URL.Path[1:] // Remove leading '/' + + var originalURL string + err := s.db.QueryRow( + "SELECT original_url FROM urls WHERE short_code = $1", + shortCode, + ).Scan(&originalURL) + + if err == sql.ErrNoRows { + http.Error(w, "Not found", http.StatusNotFound) + return + } else if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + // Increment clicks + s.db.Exec("UPDATE urls SET clicks = clicks + 1 WHERE short_code = $1", shortCode) + + http.Redirect(w, r, originalURL, http.StatusMovedPermanently) +} +``` + + + +```sql +-- Schema (same for both) +CREATE TABLE urls ( + id SERIAL PRIMARY KEY, + short_code VARCHAR(10) UNIQUE NOT NULL, + original_url TEXT NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + clicks INTEGER DEFAULT 0 +); + +CREATE INDEX idx_short_code ON urls(short_code); +``` + + +## Project 2: WebSocket Chat Server + +Real-time chat with multiple rooms: + + +```python !! py +# Python - FastAPI WebSockets +from fastapi import FastAPI, WebSocket +from typing import Dict + +app = FastAPI() + +class ConnectionManager: + def __init__(self): + self.active_connections: Dict[str, list[WebSocket]] = {} + + async def connect(self, websocket: WebSocket, room: str): + await websocket.accept() + if room not in self.active_connections: + self.active_connections[room] = [] + self.active_connections[room].append(websocket) + + async def broadcast(self, message: str, room: str): + for connection in self.active_connections.get(room, []): + await connection.send_text(message) + +manager = ConnectionManager() + +@app.websocket("/ws/{room}") +async def websocket_endpoint(websocket: WebSocket, room: str): + await manager.connect(websocket, room) + try: + while True: + data = await websocket.receive_text() + await manager.broadcast(f"User: {data}", room) + except: + manager.disconnect(websocket, room) +``` + +```go !! go +// Go - Gorilla WebSocket +package main + +import ( + "log" + "net/http" + "sync" + + "github.com/gorilla/websocket" +) + +type Hub struct { + rooms map[string]*Room + mu sync.RWMutex +} + +type Room struct { + clients map[*Client]bool + broadcast chan []byte + register chan *Client + unregister chan *Client +} + +type Client struct { + hub *Hub + room *Room + conn *websocket.Conn + send chan []byte +} + +var upgrader = websocket.Upgrader{ + CheckOrigin: func(r *http.Request) bool { + return true + }, +} + +func NewHub() *Hub { + return &Hub{ + rooms: make(map[string]*Room), + } +} + +func (h *Hub) GetRoom(roomName string) *Room { + h.mu.Lock() + defer h.mu.Unlock() + + if room, exists := h.rooms[roomName]; exists { + return room + } + + room := &Room{ + clients: make(map[*Client]bool), + broadcast: make(chan []byte), + register: make(chan *Client), + unregister: make(chan *Client), + } + + h.rooms[roomName] = room + go room.run() + + return room +} + +func (r *Room) run() { + for { + select { + case client := <-r.register: + r.clients[client] = true + + case client := <-r.unregister: + if _, ok := r.clients[client]; ok { + delete(r.clients, client) + close(client.send) + } + + case message := <-r.broadcast: + for client := range r.clients { + select { + case client.send <- message: + default: + close(client.send) + delete(r.clients, client) + } + } + } + } +} + +func (h *Hub) HandleWebSocket(w http.ResponseWriter, r *http.Request) { + roomName := r.URL.Path[len("/ws/"):] + conn, err := upgrader.Upgrade(w, r, nil) + if err != nil { + log.Println(err) + return + } + + room := h.GetRoom(roomName) + + client := &Client{ + hub: h, + room: room, + conn: conn, + send: make(chan []byte, 256), + } + + room.register <- client + + go client.writePump() + go client.readPump() +} + +func (c *Client) readPump() { + defer func() { + c.room.unregister <- c + c.conn.Close() + }() + + for { + _, message, err := c.conn.ReadMessage() + if err != nil { + break + } + + c.room.broadcast <- message + } +} + +func (c *Client) writePump() { + defer c.conn.Close() + + for { + select { + case message, ok := <-c.send: + if !ok { + c.conn.WriteMessage(websocket.CloseMessage, []byte{}) + return + } + + c.conn.WriteMessage(websocket.TextMessage, message) + } + } +} + +func main() { + hub := NewHub() + + http.HandleFunc("/ws/", hub.HandleWebSocket) + + log.Println("Server starting on :8080") + log.Fatal(http.ListenAndServe(":8080", nil)) +} +``` + + +## Project 3: Task Queue System + +Background job processing with Redis: + + +```python !! py +# Python - Celery +from celery import Celery + +app = Celery('tasks', broker='redis://localhost:6379') + +@app.task +def process_image(image_path): + # Process image + result = resize_image(image_path) + return result + +# Usage +process_image.delay('/path/to/image.jpg') +``` + +```go !! go +// Go - Task queue with Redis +package main + +import ( + "encoding/json" + "fmt" + "log" + + "github.com/go-redis/redis/v8" + "golang.org/x/net/context" +) + +type Task struct { + ID string `json:"id"` + Type string `json:"type"` + Data map[string]interface{} `json:"data"` +} + +type Worker struct { + redis *redis.Client + queues []string + handler func(Task) error +} + +func NewWorker(redisAddr string, queues []string, handler func(Task) error) *Worker { + rdb := redis.NewClient(&redis.Options{ + Addr: redisAddr, + Password: "", + DB: 0, + }) + + return &Worker{ + redis: rdb, + queues: queues, + handler: handler, + } +} + +func (w *Worker) Start(ctx context.Context) error { + for _, queue := range w.queues { + go w.processQueue(ctx, queue) + } + + <-ctx.Done() + return nil +} + +func (w *Worker) processQueue(ctx context.Context, queue string) { + for { + select { + case <-ctx.Done(): + return + + default: + // BRPOP with timeout + result, err := w.redis.BRPop(ctx, queue, time.Second).Result() + if err != nil { + continue + } + + if len(result) < 2 { + continue + } + + var task Task + if err := json.Unmarshal([]byte(result[1]), &task); err != nil { + log.Printf("Error unmarshaling task: %v", err) + continue + } + + log.Printf("Processing task: %s", task.ID) + + if err := w.handler(task); err != nil { + log.Printf("Error processing task: %v", err) + } + } + } +} + +func enqueueTask(rdb *redis.Client, queue string, task Task) error { + data, err := json.Marshal(task) + if err != nil { + return err + } + + return rdb.LPush(context.Background(), queue, data).Err() +} + +func main() { + worker := NewWorker("localhost:6379", []string{"images", "emails"}, func(task Task) error { + switch task.Type { + case "process_image": + imagePath := task.Data["path"].(string) + return processImage(imagePath) + + case "send_email": + email := task.Data["email"].(string) + return sendEmail(email) + + default: + return fmt.Errorf("unknown task type: %s", task.Type) + } + }) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + if err := worker.Start(ctx); err != nil { + log.Fatal(err) + } +} +``` + + +## Project 4: CLI Tool + +Command-line tool for developers: + + +```python !! py +# Python - Click CLI +import click + +@click.group() +def cli(): + """Developer tools""" + pass + +@cli.command() +@click.argument('name') +def create(name): + """Create new project""" + click.echo(f"Creating project: {name}") + +@cli.command() +def status(): + """Show status""" + click.echo("Status: OK") + +if __name__ == '__main__': + cli() +``` + +```go !! go +// Go - Cobra CLI +package main + +import ( + "fmt" + "os" + + "github.com/spf13/cobra" +) + +var rootCmd = &cobra.Command{ + Use: "devtool", + Short: "Developer tools", + Long: "A collection of developer utilities", +} + +var createCmd = &cobra.Command{ + Use: "create [name]", + Short: "Create new project", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + name := args[0] + fmt.Printf("Creating project: %s\n", name) + // Create project... + }, +} + +var statusCmd = &cobra.Command{ + Use: "status", + Short: "Show status", + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("Status: OK") + }, +} + +func init() { + rootCmd.AddCommand(createCmd) + rootCmd.AddCommand(statusCmd) +} + +func main() { + if err := rootCmd.Execute(); err != nil { + fmt.Println(err) + os.Exit(1) + } +} +``` + + +## Project 5: Microservices Logger + +Centralized logging service: + + +```go +// Go - Logger service +package main + +import ( + "encoding/json" + "io" + "log" + "net/http" + "os" + "sync" + + "github.com/go-redis/redis/v8" +) + +type LogEntry struct { + Level string `json:"level"` + Message string `json:"message"` + Service string `json:"service"` + Timestamp string `json:"timestamp"` + Metadata map[string]interface{} `json:"metadata,omitempty"` +} + +type Logger struct { + mu sync.Mutex + file *os.File +} + +func NewLogger(filename string) (*Logger, error) { + file, err := os.OpenFile(filename, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) + if err != nil { + return nil, err + } + + return &Logger{file: file}, nil +} + +func (l *Logger) Log(entry LogEntry) error { + l.mu.Lock() + defer l.mu.Unlock() + + data, err := json.Marshal(entry) + if err != nil { + return err + } + + _, err = l.file.Write(append(data, '\n')) + return err +} + +func (l *Logger) logHandler(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + var entry LogEntry + if err := json.NewDecoder(r.Body).Decode(&entry); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + if err := l.Log(entry); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + // Also send to Redis for real-time viewing + // redis.Publish(ctx, "logs", data) + + w.WriteHeader(http.StatusCreated) +} + +func (l *Logger) streamHandler(w http.ResponseWriter, r *http.Request) { + flusher, ok := w.(http.Flusher) + if !ok { + http.Error(w, "Streaming not supported", http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "text/event-stream") + w.Header().Set("Cache-Control", "no-cache") + w.Header().Set("Connection", "keep-alive") + + // Subscribe to Redis + // pubsub := redis.Subscribe(ctx, "logs") + + for { + // msg, err := pubsub.ReceiveMessage(ctx) + // if err != nil { + // return + // } + + // fmt.Fprintf(w, "data: %s\n\n", msg.Payload) + fmt.Fprintf(w, "data: {\"level\": \"info\", \"message\": \"test\"}\n\n") + flusher.Flush() + } +} + +func main() { + logger, err := NewLogger("app.log") + if err != nil { + log.Fatal(err) + } + + http.HandleFunc("/log", logger.logHandler) + http.HandleFunc("/stream", logger.streamHandler) + + log.Println("Logger service starting on :8080") + log.Fatal(http.ListenAndServe(":8080", nil)) +} +``` + + +## Summary + +In this module, you learned to build: + +1. **URL Shortener** - REST API with database +2. **WebSocket Chat** - Real-time communication +3. **Task Queue** - Background job processing +4. **CLI Tool** - Command-line interface +5. **Logger Service** - Microservices logging + +## Project Best Practices + +1. **Structure** - Organize code logically +2. **Error handling** - Always check errors +3. **Testing** - Write tests for critical paths +4. **Documentation** - Document your code +5. **Docker** - Containerize applications +6. **Monitoring** - Add metrics and logging +7. **Security** - Validate inputs, use HTTPS + +## Final Exercises + +1. Build the URL shortener completely +2. Add authentication to the chat server +3. Create a dashboard for the task queue +4. Extend the CLI tool with more commands +5. Deploy a project to Kubernetes + +## Conclusion + +You've completed the Python → Go learning path! You now have: +- Solid understanding of Go fundamentals +- Experience with Go's concurrency model +- Knowledge of web development in Go +- Skills to build production-ready services +- Best practices for idiomatic Go + +Continue building projects and exploring Go's ecosystem. Happy coding! diff --git a/content/docs/py2go/module-16-real-projects.zh-cn.mdx b/content/docs/py2go/module-16-real-projects.zh-cn.mdx new file mode 100644 index 0000000..04bebf9 --- /dev/null +++ b/content/docs/py2go/module-16-real-projects.zh-cn.mdx @@ -0,0 +1,798 @@ +--- +title: "Module 16: 真实项目" +description: "构建完整的项目以应用你的 Go 知识" +--- + +## 简介 + +到了将所有内容整合在一起的时候了!本模块展示完整的实用项目,演示真实的 Go 开发。 + +## 项目 1: URL 短链服务 + +构建一个生产就绪的 URL 短链,包含: +- RESTful API +- 数据库持久化 +- 缓存层 +- 指标收集 +- Docker 部署 + +### 架构 + +``` +┌─────────────┐ +│ Client │ +└──────┬──────┘ + │ + ▼ +┌─────────────────┐ +│ API Gateway │ +└────────┬────────┘ + │ + ┌────┴────┐ + ▼ ▼ +┌───────┐ ┌───────┐ +│ API │ │ Cache │ +└───┬───┘ └───────┘ + │ + ▼ +┌───────┐ +│ DB │ +└───────┘ +``` + +### 实现 + + +```python !! py +# Python - 模型 +from dataclasses import dataclass +from datetime import datetime +import hashlib +import random +import string + +@dataclass +class URL: + id: int + short_code: str + original_url: str + created_at: datetime + clicks: int + +def generate_short_code(url: str) -> str: + # 生成随机 6 字符代码 + chars = string.ascii_letters + string.digits + return ''.join(random.choices(chars, k=6)) +``` + +```go !! go +// Go - 模型 +package models + +import "time" + +type URL struct { + ID int `json:"id"` + ShortCode string `json:"short_code"` + OriginalURL string `json:"original_url"` + CreatedAt time.Time `json:"created_at"` + Clicks int `json:"clicks"` +} + +func GenerateShortCode() string { + // 生成随机 6 字符代码 + const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + b := make([]byte, 6) + for i := range b { + b[i] = chars[rand.Intn(len(chars))] + } + return string(b) +} +``` + + + +```python !! py +# Python - Flask API +from flask import Flask, request, jsonify, redirect +from datetime import datetime + +app = Flask(__name__) + +@app.route('/shorten', methods=['POST']) +def shorten(): + data = request.get_json() + url = data.get('url') + + if not url: + return jsonify({'error': 'URL required'}), 400 + + short_code = generate_short_code(url) + + # 保存到数据库 + url_obj = URL( + id=1, + short_code=short_code, + original_url=url, + created_at=datetime.now(), + clicks=0 + ) + + return jsonify({ + 'short_url': f'https://short.ly/{short_code}' + }), 201 + +@app.route('/') +def redirect_url(short_code): + # 查找 URL + url_obj = get_url(short_code) + + if not url_obj: + return jsonify({'error': 'Not found'}), 404 + + # 增加点击数 + url_obj.clicks += 1 + + return redirect(url_obj.original_url) +``` + +```go !! go +// Go - HTTP API +package main + +import ( + "database/sql" + "encoding/json" + "fmt" + "net/http" + "time" + + _ "github.com/lib/pq" +) + +type Server struct { + db *sql.DB +} + +func (s *Server) shortenHandler(w http.ResponseWriter, r *http.Request) { + var req struct { + URL string `json:"url"` + } + + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + if req.URL == "" { + http.Error(w, "URL required", http.StatusBadRequest) + return + } + + shortCode := GenerateShortCode() + + // 保存到数据库 + var id int + err := s.db.QueryRow( + "INSERT INTO urls (short_code, original_url, created_at, clicks) VALUES ($1, $2, $3, $4) RETURNING id", + shortCode, req.URL, time.Now(), 0, + ).Scan(&id) + + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + json.NewEncoder(w).Encode(map[string]string{ + "short_url": fmt.Sprintf("https://short.ly/%s", shortCode), + }) +} + +func (s *Server) redirectHandler(w http.ResponseWriter, r *http.Request) { + shortCode := r.URL.Path[1:] // 移除前导 '/' + + var originalURL string + err := s.db.QueryRow( + "SELECT original_url FROM urls WHERE short_code = $1", + shortCode, + ).Scan(&originalURL) + + if err == sql.ErrNoRows { + http.Error(w, "Not found", http.StatusNotFound) + return + } else if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + // 增加点击数 + s.db.Exec("UPDATE urls SET clicks = clicks + 1 WHERE short_code = $1", shortCode) + + http.Redirect(w, r, originalURL, http.StatusMovedPermanently) +} +``` + + + +```sql +-- Schema (两者相同) +CREATE TABLE urls ( + id SERIAL PRIMARY KEY, + short_code VARCHAR(10) UNIQUE NOT NULL, + original_url TEXT NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + clicks INTEGER DEFAULT 0 +); + +CREATE INDEX idx_short_code ON urls(short_code); +``` + + +## 项目 2: WebSocket 聊天服务器 + +支持多房间的实时聊天: + + +```python !! py +# Python - FastAPI WebSockets +from fastapi import FastAPI, WebSocket +from typing import Dict + +app = FastAPI() + +class ConnectionManager: + def __init__(self): + self.active_connections: Dict[str, list[WebSocket]] = {} + + async def connect(self, websocket: WebSocket, room: str): + await websocket.accept() + if room not in self.active_connections: + self.active_connections[room] = [] + self.active_connections[room].append(websocket) + + async def broadcast(self, message: str, room: str): + for connection in self.active_connections.get(room, []): + await connection.send_text(message) + +manager = ConnectionManager() + +@app.websocket("/ws/{room}") +async def websocket_endpoint(websocket: WebSocket, room: str): + await manager.connect(websocket, room) + try: + while True: + data = await websocket.receive_text() + await manager.broadcast(f"User: {data}", room) + except: + manager.disconnect(websocket, room) +``` + +```go !! go +// Go - Gorilla WebSocket +package main + +import ( + "log" + "net/http" + "sync" + + "github.com/gorilla/websocket" +) + +type Hub struct { + rooms map[string]*Room + mu sync.RWMutex +} + +type Room struct { + clients map[*Client]bool + broadcast chan []byte + register chan *Client + unregister chan *Client +} + +type Client struct { + hub *Hub + room *Room + conn *websocket.Conn + send chan []byte +} + +var upgrader = websocket.Upgrader{ + CheckOrigin: func(r *http.Request) bool { + return true + }, +} + +func NewHub() *Hub { + return &Hub{ + rooms: make(map[string]*Room), + } +} + +func (h *Hub) GetRoom(roomName string) *Room { + h.mu.Lock() + defer h.mu.Unlock() + + if room, exists := h.rooms[roomName]; exists { + return room + } + + room := &Room{ + clients: make(map[*Client]bool), + broadcast: make(chan []byte), + register: make(chan *Client), + unregister: make(chan *Client), + } + + h.rooms[roomName] = room + go room.run() + + return room +} + +func (r *Room) run() { + for { + select { + case client := <-r.register: + r.clients[client] = true + + case client := <-r.unregister: + if _, ok := r.clients[client]; ok { + delete(r.clients, client) + close(client.send) + } + + case message := <-r.broadcast: + for client := range r.clients { + select { + case client.send <- message: + default: + close(client.send) + delete(r.clients, client) + } + } + } + } +} + +func (h *Hub) HandleWebSocket(w http.ResponseWriter, r *http.Request) { + roomName := r.URL.Path[len("/ws/"):] + conn, err := upgrader.Upgrade(w, r, nil) + if err != nil { + log.Println(err) + return + } + + room := h.GetRoom(roomName) + + client := &Client{ + hub: h, + room: room, + conn: conn, + send: make(chan []byte, 256), + } + + room.register <- client + + go client.writePump() + go client.readPump() +} + +func (c *Client) readPump() { + defer func() { + c.room.unregister <- c + c.conn.Close() + }() + + for { + _, message, err := c.conn.ReadMessage() + if err != nil { + break + } + + c.room.broadcast <- message + } +} + +func (c *Client) writePump() { + defer c.conn.Close() + + for { + select { + case message, ok := <-c.send: + if !ok { + c.conn.WriteMessage(websocket.CloseMessage, []byte{}) + return + } + + c.conn.WriteMessage(websocket.TextMessage, message) + } + } +} + +func main() { + hub := NewHub() + + http.HandleFunc("/ws/", hub.HandleWebSocket) + + log.Println("Server starting on :8080") + log.Fatal(http.ListenAndServe(":8080", nil)) +} +``` + + +## 项目 3: 任务队列系统 + +使用 Redis 的后台任务处理: + + +```python !! py +# Python - Celery +from celery import Celery + +app = Celery('tasks', broker='redis://localhost:6379') + +@app.task +def process_image(image_path): + # 处理图片 + result = resize_image(image_path) + return result + +# 使用 +process_image.delay('/path/to/image.jpg') +``` + +```go !! go +// Go - 使用 Redis 的任务队列 +package main + +import ( + "encoding/json" + "fmt" + "log" + + "github.com/go-redis/redis/v8" + "golang.org/x/net/context" +) + +type Task struct { + ID string `json:"id"` + Type string `json:"type"` + Data map[string]interface{} `json:"data"` +} + +type Worker struct { + redis *redis.Client + queues []string + handler func(Task) error +} + +func NewWorker(redisAddr string, queues []string, handler func(Task) error) *Worker { + rdb := redis.NewClient(&redis.Options{ + Addr: redisAddr, + Password: "", + DB: 0, + }) + + return &Worker{ + redis: rdb, + queues: queues, + handler: handler, + } +} + +func (w *Worker) Start(ctx context.Context) error { + for _, queue := range w.queues { + go w.processQueue(ctx, queue) + } + + <-ctx.Done() + return nil +} + +func (w *Worker) processQueue(ctx context.Context, queue string) { + for { + select { + case <-ctx.Done(): + return + + default: + // 带超时的 BRPOP + result, err := w.redis.BRPop(ctx, queue, time.Second).Result() + if err != nil { + continue + } + + if len(result) < 2 { + continue + } + + var task Task + if err := json.Unmarshal([]byte(result[1]), &task); err != nil { + log.Printf("Error unmarshaling task: %v", err) + continue + } + + log.Printf("Processing task: %s", task.ID) + + if err := w.handler(task); err != nil { + log.Printf("Error processing task: %v", err) + } + } + } +} + +func enqueueTask(rdb *redis.Client, queue string, task Task) error { + data, err := json.Marshal(task) + if err != nil { + return err + } + + return rdb.LPush(context.Background(), queue, data).Err() +} + +func main() { + worker := NewWorker("localhost:6379", []string{"images", "emails"}, func(task Task) error { + switch task.Type { + case "process_image": + imagePath := task.Data["path"].(string) + return processImage(imagePath) + + case "send_email": + email := task.Data["email"].(string) + return sendEmail(email) + + default: + return fmt.Errorf("unknown task type: %s", task.Type) + } + }) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + if err := worker.Start(ctx); err != nil { + log.Fatal(err) + } +} +``` + + +## 项目 4: CLI 工具 + +面向开发者的命令行工具: + + +```python !! py +# Python - Click CLI +import click + +@click.group() +def cli(): + """开发者工具""" + pass + +@cli.command() +@click.argument('name') +def create(name): + """创建新项目""" + click.echo(f"Creating project: {name}") + +@cli.command() +def status(): + """显示状态""" + click.echo("Status: OK") + +if __name__ == '__main__': + cli() +``` + +```go !! go +// Go - Cobra CLI +package main + +import ( + "fmt" + "os" + + "github.com/spf13/cobra" +) + +var rootCmd = &cobra.Command{ + Use: "devtool", + Short: "Developer tools", + Long: "A collection of developer utilities", +} + +var createCmd = &cobra.Command{ + Use: "create [name]", + Short: "Create new project", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + name := args[0] + fmt.Printf("Creating project: %s\n", name) + // 创建项目... + }, +} + +var statusCmd = &cobra.Command{ + Use: "status", + Short: "Show status", + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("Status: OK") + }, +} + +func init() { + rootCmd.AddCommand(createCmd) + rootCmd.AddCommand(statusCmd) +} + +func main() { + if err := rootCmd.Execute(); err != nil { + fmt.Println(err) + os.Exit(1) + } +} +``` + + +## 项目 5: 微服务日志记录器 + +集中式日志服务: + + +```go +// Go - 日志服务 +package main + +import ( + "encoding/json" + "io" + "log" + "net/http" + "os" + "sync" + + "github.com/go-redis/redis/v8" +) + +type LogEntry struct { + Level string `json:"level"` + Message string `json:"message"` + Service string `json:"service"` + Timestamp string `json:"timestamp"` + Metadata map[string]interface{} `json:"metadata,omitempty"` +} + +type Logger struct { + mu sync.Mutex + file *os.File +} + +func NewLogger(filename string) (*Logger, error) { + file, err := os.OpenFile(filename, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) + if err != nil { + return nil, err + } + + return &Logger{file: file}, nil +} + +func (l *Logger) Log(entry LogEntry) error { + l.mu.Lock() + defer l.mu.Unlock() + + data, err := json.Marshal(entry) + if err != nil { + return err + } + + _, err = l.file.Write(append(data, '\n')) + return err +} + +func (l *Logger) logHandler(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + var entry LogEntry + if err := json.NewDecoder(r.Body).Decode(&entry); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + if err := l.Log(entry); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + // 同时发送到 Redis 进行实时查看 + // redis.Publish(ctx, "logs", data) + + w.WriteHeader(http.StatusCreated) +} + +func (l *Logger) streamHandler(w http.ResponseWriter, r *http.Request) { + flusher, ok := w.(http.Flusher) + if !ok { + http.Error(w, "Streaming not supported", http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "text/event-stream") + w.Header().Set("Cache-Control", "no-cache") + w.Header().Set("Connection", "keep-alive") + + // 订阅 Redis + // pubsub := redis.Subscribe(ctx, "logs") + + for { + // msg, err := pubsub.ReceiveMessage(ctx) + // if err != nil { + // return + // } + + // fmt.Fprintf(w, "data: %s\n\n", msg.Payload) + fmt.Fprintf(w, "data: {\"level\": \"info\", \"message\": \"test\"}\n\n") + flusher.Flush() + } +} + +func main() { + logger, err := NewLogger("app.log") + if err != nil { + log.Fatal(err) + } + + http.HandleFunc("/log", logger.logHandler) + http.HandleFunc("/stream", logger.streamHandler) + + log.Println("Logger service starting on :8080") + log.Fatal(http.ListenAndServe(":8080", nil)) +} +``` + + +## 总结 + +在本模块中,你学习了构建: + +1. **URL 短链** - 带数据库的 REST API +2. **WebSocket 聊天** - 实时通信 +3. **任务队列** - 后台任务处理 +4. **CLI 工具** - 命令行界面 +5. **日志服务** - 微服务日志 + +## 项目最佳实践 + +1. **结构** - 逻辑组织代码 +2. **错误处理** - 始终检查错误 +3. **测试** - 为关键路径编写测试 +4. **文档** - 记录你的代码 +5. **Docker** - 容器化应用 +6. **监控** - 添加指标和日志 +7. **安全** - 验证输入,使用 HTTPS + +## 最终练习 + +1. 完整构建 URL 短链 +2. 为聊天服务器添加身份验证 +3. 创建任务队列的控制面板 +4. 使用更多命令扩展 CLI 工具 +5. 将项目部署到 Kubernetes + +## 结语 + +你已经完成了 Python → Go 学习路径!你现在拥有: +- 对 Go 基础知识的扎实理解 +- Go 并发模型的经验 +- Go Web 开发的知识 +- 构建生产就绪服务的技能 +- 地道 Go 的最佳实践 + +继续构建项目并探索 Go 生态系统。祝你编程愉快! diff --git a/content/docs/py2go/module-16-real-projects.zh-tw.mdx b/content/docs/py2go/module-16-real-projects.zh-tw.mdx new file mode 100644 index 0000000..f0795e0 --- /dev/null +++ b/content/docs/py2go/module-16-real-projects.zh-tw.mdx @@ -0,0 +1,798 @@ +--- +title: "Module 16: 真實專案" +description: "建構完整的專案以應用你的 Go 知識" +--- + +## 簡介 + +到了將所有內容整合在一起的時候了!本模組展示完整的實用專案,演示真實的 Go 開發。 + +## 專案 1: URL 短鏈服務 + +建構一個生產就緒的 URL 短鏈,包含: +- RESTful API +- 資料庫持久化 +- 快取層 +- 指標收集 +- Docker 部署 + +### 架構 + +``` +┌─────────────┐ +│ Client │ +└──────┬──────┘ + │ + ▼ +┌─────────────────┐ +│ API Gateway │ +└────────┬────────┘ + │ + ┌────┴────┐ + ▼ ▼ +┌───────┐ ┌───────┐ +│ API │ │ Cache │ +└───┬───┘ └───────┘ + │ + ▼ +┌───────┐ +│ DB │ +└───────┘ +``` + +### 實作 + + +```python !! py +# Python - 模型 +from dataclasses import dataclass +from datetime import datetime +import hashlib +import random +import string + +@dataclass +class URL: + id: int + short_code: str + original_url: str + created_at: datetime + clicks: int + +def generate_short_code(url: str) -> str: + # 生成隨機 6 字元代碼 + chars = string.ascii_letters + string.digits + return ''.join(random.choices(chars, k=6)) +``` + +```go !! go +// Go - 模型 +package models + +import "time" + +type URL struct { + ID int `json:"id"` + ShortCode string `json:"short_code"` + OriginalURL string `json:"original_url"` + CreatedAt time.Time `json:"created_at"` + Clicks int `json:"clicks"` +} + +func GenerateShortCode() string { + // 生成隨機 6 字元代碼 + const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + b := make([]byte, 6) + for i := range b { + b[i] = chars[rand.Intn(len(chars))] + } + return string(b) +} +``` + + + +```python !! py +# Python - Flask API +from flask import Flask, request, jsonify, redirect +from datetime import datetime + +app = Flask(__name__) + +@app.route('/shorten', methods=['POST']) +def shorten(): + data = request.get_json() + url = data.get('url') + + if not url: + return jsonify({'error': 'URL required'}), 400 + + short_code = generate_short_code(url) + + # 保存到資料庫 + url_obj = URL( + id=1, + short_code=short_code, + original_url=url, + created_at=datetime.now(), + clicks=0 + ) + + return jsonify({ + 'short_url': f'https://short.ly/{short_code}' + }), 201 + +@app.route('/') +def redirect_url(short_code): + # 查找 URL + url_obj = get_url(short_code) + + if not url_obj: + return jsonify({'error': 'Not found'}), 404 + + # 增加點擊數 + url_obj.clicks += 1 + + return redirect(url_obj.original_url) +``` + +```go !! go +// Go - HTTP API +package main + +import ( + "database/sql" + "encoding/json" + "fmt" + "net/http" + "time" + + _ "github.com/lib/pq" +) + +type Server struct { + db *sql.DB +} + +func (s *Server) shortenHandler(w http.ResponseWriter, r *http.Request) { + var req struct { + URL string `json:"url"` + } + + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + if req.URL == "" { + http.Error(w, "URL required", http.StatusBadRequest) + return + } + + shortCode := GenerateShortCode() + + // 保存到資料庫 + var id int + err := s.db.QueryRow( + "INSERT INTO urls (short_code, original_url, created_at, clicks) VALUES ($1, $2, $3, $4) RETURNING id", + shortCode, req.URL, time.Now(), 0, + ).Scan(&id) + + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + json.NewEncoder(w).Encode(map[string]string{ + "short_url": fmt.Sprintf("https://short.ly/%s", shortCode), + }) +} + +func (s *Server) redirectHandler(w http.ResponseWriter, r *http.Request) { + shortCode := r.URL.Path[1:] // 移除前導 '/' + + var originalURL string + err := s.db.QueryRow( + "SELECT original_url FROM urls WHERE short_code = $1", + shortCode, + ).Scan(&originalURL) + + if err == sql.ErrNoRows { + http.Error(w, "Not found", http.StatusNotFound) + return + } else if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + // 增加點擊數 + s.db.Exec("UPDATE urls SET clicks = clicks + 1 WHERE short_code = $1", shortCode) + + http.Redirect(w, r, originalURL, http.StatusMovedPermanently) +} +``` + + + +```sql +-- Schema (兩者相同) +CREATE TABLE urls ( + id SERIAL PRIMARY KEY, + short_code VARCHAR(10) UNIQUE NOT NULL, + original_url TEXT NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + clicks INTEGER DEFAULT 0 +); + +CREATE INDEX idx_short_code ON urls(short_code); +``` + + +## 專案 2: WebSocket 聊天伺服器 + +支援多房間的即時聊天: + + +```python !! py +# Python - FastAPI WebSockets +from fastapi import FastAPI, WebSocket +from typing import Dict + +app = FastAPI() + +class ConnectionManager: + def __init__(self): + self.active_connections: Dict[str, list[WebSocket]] = {} + + async def connect(self, websocket: WebSocket, room: str): + await websocket.accept() + if room not in self.active_connections: + self.active_connections[room] = [] + self.active_connections[room].append(websocket) + + async def broadcast(self, message: str, room: str): + for connection in self.active_connections.get(room, []): + await connection.send_text(message) + +manager = ConnectionManager() + +@app.websocket("/ws/{room}") +async def websocket_endpoint(websocket: WebSocket, room: str): + await manager.connect(websocket, room) + try: + while True: + data = await websocket.receive_text() + await manager.broadcast(f"User: {data}", room) + except: + manager.disconnect(websocket, room) +``` + +```go !! go +// Go - Gorilla WebSocket +package main + +import ( + "log" + "net/http" + "sync" + + "github.com/gorilla/websocket" +) + +type Hub struct { + rooms map[string]*Room + mu sync.RWMutex +} + +type Room struct { + clients map[*Client]bool + broadcast chan []byte + register chan *Client + unregister chan *Client +} + +type Client struct { + hub *Hub + room *Room + conn *websocket.Conn + send chan []byte +} + +var upgrader = websocket.Upgrader{ + CheckOrigin: func(r *http.Request) bool { + return true + }, +} + +func NewHub() *Hub { + return &Hub{ + rooms: make(map[string]*Room), + } +} + +func (h *Hub) GetRoom(roomName string) *Room { + h.mu.Lock() + defer h.mu.Unlock() + + if room, exists := h.rooms[roomName]; exists { + return room + } + + room := &Room{ + clients: make(map[*Client]bool), + broadcast: make(chan []byte), + register: make(chan *Client), + unregister: make(chan *Client), + } + + h.rooms[roomName] = room + go room.run() + + return room +} + +func (r *Room) run() { + for { + select { + case client := <-r.register: + r.clients[client] = true + + case client := <-r.unregister: + if _, ok := r.clients[client]; ok { + delete(r.clients, client) + close(client.send) + } + + case message := <-r.broadcast: + for client := range r.clients { + select { + case client.send <- message: + default: + close(client.send) + delete(r.clients, client) + } + } + } + } +} + +func (h *Hub) HandleWebSocket(w http.ResponseWriter, r *http.Request) { + roomName := r.URL.Path[len("/ws/"):] + conn, err := upgrader.Upgrade(w, r, nil) + if err != nil { + log.Println(err) + return + } + + room := h.GetRoom(roomName) + + client := &Client{ + hub: h, + room: room, + conn: conn, + send: make(chan []byte, 256), + } + + room.register <- client + + go client.writePump() + go client.readPump() +} + +func (c *Client) readPump() { + defer func() { + c.room.unregister <- c + c.conn.Close() + }() + + for { + _, message, err := c.conn.ReadMessage() + if err != nil { + break + } + + c.room.broadcast <- message + } +} + +func (c *Client) writePump() { + defer c.conn.Close() + + for { + select { + case message, ok := <-c.send: + if !ok { + c.conn.WriteMessage(websocket.CloseMessage, []byte{}) + return + } + + c.conn.WriteMessage(websocket.TextMessage, message) + } + } +} + +func main() { + hub := NewHub() + + http.HandleFunc("/ws/", hub.HandleWebSocket) + + log.Println("Server starting on :8080") + log.Fatal(http.ListenAndServe(":8080", nil)) +} +``` + + +## 專案 3: 任務佇列系統 + +使用 Redis 的背景任務處理: + + +```python !! py +# Python - Celery +from celery import Celery + +app = Celery('tasks', broker='redis://localhost:6379') + +@app.task +def process_image(image_path): + # 處理圖片 + result = resize_image(image_path) + return result + +# 使用 +process_image.delay('/path/to/image.jpg') +``` + +```go !! go +// Go - 使用 Redis 的任務佇列 +package main + +import ( + "encoding/json" + "fmt" + "log" + + "github.com/go-redis/redis/v8" + "golang.org/x/net/context" +) + +type Task struct { + ID string `json:"id"` + Type string `json:"type"` + Data map[string]interface{} `json:"data"` +} + +type Worker struct { + redis *redis.Client + queues []string + handler func(Task) error +} + +func NewWorker(redisAddr string, queues []string, handler func(Task) error) *Worker { + rdb := redis.NewClient(&redis.Options{ + Addr: redisAddr, + Password: "", + DB: 0, + }) + + return &Worker{ + redis: rdb, + queues: queues, + handler: handler, + } +} + +func (w *Worker) Start(ctx context.Context) error { + for _, queue := range w.queues { + go w.processQueue(ctx, queue) + } + + <-ctx.Done() + return nil +} + +func (w *Worker) processQueue(ctx context.Context, queue string) { + for { + select { + case <-ctx.Done(): + return + + default: + // 帶逾時的 BRPOP + result, err := w.redis.BRPop(ctx, queue, time.Second).Result() + if err != nil { + continue + } + + if len(result) < 2 { + continue + } + + var task Task + if err := json.Unmarshal([]byte(result[1]), &task); err != nil { + log.Printf("Error unmarshaling task: %v", err) + continue + } + + log.Printf("Processing task: %s", task.ID) + + if err := w.handler(task); err != nil { + log.Printf("Error processing task: %v", err) + } + } + } +} + +func enqueueTask(rdb *redis.Client, queue string, task Task) error { + data, err := json.Marshal(task) + if err != nil { + return err + } + + return rdb.LPush(context.Background(), queue, data).Err() +} + +func main() { + worker := NewWorker("localhost:6379", []string{"images", "emails"}, func(task Task) error { + switch task.Type { + case "process_image": + imagePath := task.Data["path"].(string) + return processImage(imagePath) + + case "send_email": + email := task.Data["email"].(string) + return sendEmail(email) + + default: + return fmt.Errorf("unknown task type: %s", task.Type) + } + }) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + if err := worker.Start(ctx); err != nil { + log.Fatal(err) + } +} +``` + + +## 專案 4: CLI 工具 + +面向開發者的命令列工具: + + +```python !! py +# Python - Click CLI +import click + +@click.group() +def cli(): + """開發者工具""" + pass + +@cli.command() +@click.argument('name') +def create(name): + """建立新專案""" + click.echo(f"Creating project: {name}") + +@cli.command() +def status(): + """顯示狀態""" + click.echo("Status: OK") + +if __name__ == '__main__': + cli() +``` + +```go !! go +// Go - Cobra CLI +package main + +import ( + "fmt" + "os" + + "github.com/spf13/cobra" +) + +var rootCmd = &cobra.Command{ + Use: "devtool", + Short: "Developer tools", + Long: "A collection of developer utilities", +} + +var createCmd = &cobra.Command{ + Use: "create [name]", + Short: "Create new project", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + name := args[0] + fmt.Printf("Creating project: %s\n", name) + // 建立專案... + }, +} + +var statusCmd = &cobra.Command{ + Use: "status", + Short: "Show status", + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("Status: OK") + }, +} + +func init() { + rootCmd.AddCommand(createCmd) + rootCmd.AddCommand(statusCmd) +} + +func main() { + if err := rootCmd.Execute(); err != nil { + fmt.Println(err) + os.Exit(1) + } +} +``` + + +## 專案 5: 微服務日誌記錄器 + +集中式日誌服務: + + +```go +// Go - 日誌服務 +package main + +import ( + "encoding/json" + "io" + "log" + "net/http" + "os" + "sync" + + "github.com/go-redis/redis/v8" +) + +type LogEntry struct { + Level string `json:"level"` + Message string `json:"message"` + Service string `json:"service"` + Timestamp string `json:"timestamp"` + Metadata map[string]interface{} `json:"metadata,omitempty"` +} + +type Logger struct { + mu sync.Mutex + file *os.File +} + +func NewLogger(filename string) (*Logger, error) { + file, err := os.OpenFile(filename, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) + if err != nil { + return nil, err + } + + return &Logger{file: file}, nil +} + +func (l *Logger) Log(entry LogEntry) error { + l.mu.Lock() + defer l.mu.Unlock() + + data, err := json.Marshal(entry) + if err != nil { + return err + } + + _, err = l.file.Write(append(data, '\n')) + return err +} + +func (l *Logger) logHandler(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + var entry LogEntry + if err := json.NewDecoder(r.Body).Decode(&entry); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + if err := l.Log(entry); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + // 同時發送到 Redis 進行即時查看 + // redis.Publish(ctx, "logs", data) + + w.WriteHeader(http.StatusCreated) +} + +func (l *Logger) streamHandler(w http.ResponseWriter, r *http.Request) { + flusher, ok := w.(http.Flusher) + if !ok { + http.Error(w, "Streaming not supported", http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "text/event-stream") + w.Header().Set("Cache-Control", "no-cache") + w.Header().Set("Connection", "keep-alive") + + // 訂閱 Redis + // pubsub := redis.Subscribe(ctx, "logs") + + for { + // msg, err := pubsub.ReceiveMessage(ctx) + // if err != nil { + // return + // } + + // fmt.Fprintf(w, "data: %s\n\n", msg.Payload) + fmt.Fprintf(w, "data: {\"level\": \"info\", \"message\": \"test\"}\n\n") + flusher.Flush() + } +} + +func main() { + logger, err := NewLogger("app.log") + if err != nil { + log.Fatal(err) + } + + http.HandleFunc("/log", logger.logHandler) + http.HandleFunc("/stream", logger.streamHandler) + + log.Println("Logger service starting on :8080") + log.Fatal(http.ListenAndServe(":8080", nil)) +} +``` + + +## 總結 + +在本模組中,你學習了建構: + +1. **URL 短鏈** - 帶資料庫的 REST API +2. **WebSocket 聊天** - 即時通訊 +3. **任務佇列** - 背景任務處理 +4. **CLI 工具** - 命令列介面 +5. **日誌服務** - 微服務日誌 + +## 專案最佳實踐 + +1. **結構** - 邏輯組織程式碼 +2. **錯誤處理** - 始終檢查錯誤 +3. **測試** - 為關鍵路徑編寫測試 +4. **文件** - 記錄你的程式碼 +5. **Docker** - 容器化應用 +6. **監控** - 新增指標和日誌 +7. **安全** - 驗證輸入,使用 HTTPS + +## 最終練習 + +1. 完整建構 URL 短鏈 +2. 為聊天伺服器新增身份驗證 +3. 建立任務佇列的控制面板 +4. 使用更多命令擴展 CLI 工具 +5. 將專案部署到 Kubernetes + +## 結語 + +你已經完成了 Python → Go 學習路徑!你現在擁有: +- 對 Go 基礎知識的紮實理解 +- Go 並發模型的經驗 +- Go Web 開發的知識 +- 建構生產就緒服務的技能 +- 地道 Go 的最佳實踐 + +繼續建構專案並探索 Go 生態系統。祝你程式愉快! From 4648c752bd4546b89923c6513b9f772bc63296b9 Mon Sep 17 00:00:00 2001 From: sunguangbiao <2991552132@qq.com> Date: Sat, 27 Dec 2025 16:23:56 +0800 Subject: [PATCH 5/7] feat: add Java to JavaScript learning path with 19 comprehensive modules covering syntax, control flow, functions, and more, including translations in Chinese and Taiwanese --- components/header.tsx | 18 + content/docs/java2js/index.mdx | 194 +++ content/docs/java2js/index.zh-cn.mdx | 194 +++ content/docs/java2js/index.zh-tw.mdx | 194 +++ content/docs/java2js/meta.json | 4 + .../java2js/module-00-js-introduction.mdx | 1034 +++++++++++++++ .../module-00-js-introduction.zh-cn.mdx | 980 ++++++++++++++ .../module-00-js-introduction.zh-tw.mdx | 1034 +++++++++++++++ .../docs/java2js/module-01-basic-syntax.mdx | 1031 +++++++++++++++ .../java2js/module-01-basic-syntax.zh-cn.mdx | 1011 ++++++++++++++ .../java2js/module-01-basic-syntax.zh-tw.mdx | 1031 +++++++++++++++ .../docs/java2js/module-02-control-flow.mdx | 1073 +++++++++++++++ .../java2js/module-02-control-flow.zh-cn.mdx | 1073 +++++++++++++++ .../java2js/module-02-control-flow.zh-tw.mdx | 1073 +++++++++++++++ .../java2js/module-03-functions-basics.mdx | 1122 ++++++++++++++++ .../module-03-functions-basics.zh-cn.mdx | 1122 ++++++++++++++++ .../module-03-functions-basics.zh-tw.mdx | 854 ++++++++++++ .../java2js/module-04-functions-advanced.mdx | 1157 +++++++++++++++++ .../module-04-functions-advanced.zh-cn.mdx | 1157 +++++++++++++++++ .../module-04-functions-advanced.zh-tw.mdx | 976 ++++++++++++++ .../java2js/module-05-arrays-collections.mdx | 1056 +++++++++++++++ .../module-05-arrays-collections.zh-cn.mdx | 1056 +++++++++++++++ .../module-05-arrays-collections.zh-tw.mdx | 351 +++++ content/docs/java2js/module-06-objects.mdx | 969 ++++++++++++++ .../docs/java2js/module-06-objects.zh-cn.mdx | 969 ++++++++++++++ .../docs/java2js/module-06-objects.zh-tw.mdx | 398 ++++++ content/docs/java2js/module-07-classes.mdx | 1089 ++++++++++++++++ .../docs/java2js/module-07-classes.zh-cn.mdx | 1089 ++++++++++++++++ .../docs/java2js/module-07-classes.zh-tw.mdx | 361 +++++ content/docs/java2js/module-08-prototypes.mdx | 971 ++++++++++++++ .../java2js/module-08-prototypes.zh-cn.mdx | 971 ++++++++++++++ .../java2js/module-08-prototypes.zh-tw.mdx | 333 +++++ .../docs/java2js/module-09-this-context.mdx | 936 +++++++++++++ .../java2js/module-09-this-context.zh-cn.mdx | 936 +++++++++++++ .../java2js/module-09-this-context.zh-tw.mdx | 260 ++++ .../docs/java2js/module-10-async-basics.mdx | 1057 +++++++++++++++ .../java2js/module-10-async-basics.zh-cn.mdx | 1057 +++++++++++++++ .../java2js/module-10-async-basics.zh-tw.mdx | 259 ++++ .../docs/java2js/module-11-async-advanced.mdx | 734 +++++++++++ .../module-11-async-advanced.zh-cn.mdx | 734 +++++++++++ .../module-11-async-advanced.zh-tw.mdx | 259 ++++ .../java2js/module-12-dom-manipulation.mdx | 303 +++++ .../module-12-dom-manipulation.zh-cn.mdx | 303 +++++ .../module-12-dom-manipulation.zh-tw.mdx | 201 +++ content/docs/java2js/module-13-events.mdx | 367 ++++++ .../docs/java2js/module-13-events.zh-cn.mdx | 367 ++++++ .../docs/java2js/module-13-events.zh-tw.mdx | 258 ++++ content/docs/java2js/module-14-modules.mdx | 325 +++++ .../docs/java2js/module-14-modules.zh-cn.mdx | 325 +++++ .../docs/java2js/module-14-modules.zh-tw.mdx | 246 ++++ content/docs/java2js/module-15-tooling.mdx | 221 ++++ .../docs/java2js/module-15-tooling.zh-cn.mdx | 221 ++++ .../docs/java2js/module-15-tooling.zh-tw.mdx | 221 ++++ .../java2js/module-16-testing-debugging.mdx | 244 ++++ .../module-16-testing-debugging.zh-cn.mdx | 244 ++++ .../module-16-testing-debugging.zh-tw.mdx | 244 ++++ content/docs/java2js/module-17-frameworks.mdx | 185 +++ .../java2js/module-17-frameworks.zh-cn.mdx | 185 +++ .../java2js/module-17-frameworks.zh-tw.mdx | 185 +++ content/docs/java2js/module-18-pitfalls.mdx | 182 +++ .../docs/java2js/module-18-pitfalls.zh-cn.mdx | 182 +++ .../docs/java2js/module-18-pitfalls.zh-tw.mdx | 182 +++ .../docs/java2js/module-19-real-projects.mdx | 277 ++++ .../java2js/module-19-real-projects.zh-cn.mdx | 277 ++++ .../java2js/module-19-real-projects.zh-tw.mdx | 277 ++++ 65 files changed, 38199 insertions(+) create mode 100644 content/docs/java2js/index.mdx create mode 100644 content/docs/java2js/index.zh-cn.mdx create mode 100644 content/docs/java2js/index.zh-tw.mdx create mode 100644 content/docs/java2js/meta.json create mode 100644 content/docs/java2js/module-00-js-introduction.mdx create mode 100644 content/docs/java2js/module-00-js-introduction.zh-cn.mdx create mode 100644 content/docs/java2js/module-00-js-introduction.zh-tw.mdx create mode 100644 content/docs/java2js/module-01-basic-syntax.mdx create mode 100644 content/docs/java2js/module-01-basic-syntax.zh-cn.mdx create mode 100644 content/docs/java2js/module-01-basic-syntax.zh-tw.mdx create mode 100644 content/docs/java2js/module-02-control-flow.mdx create mode 100644 content/docs/java2js/module-02-control-flow.zh-cn.mdx create mode 100644 content/docs/java2js/module-02-control-flow.zh-tw.mdx create mode 100644 content/docs/java2js/module-03-functions-basics.mdx create mode 100644 content/docs/java2js/module-03-functions-basics.zh-cn.mdx create mode 100644 content/docs/java2js/module-03-functions-basics.zh-tw.mdx create mode 100644 content/docs/java2js/module-04-functions-advanced.mdx create mode 100644 content/docs/java2js/module-04-functions-advanced.zh-cn.mdx create mode 100644 content/docs/java2js/module-04-functions-advanced.zh-tw.mdx create mode 100644 content/docs/java2js/module-05-arrays-collections.mdx create mode 100644 content/docs/java2js/module-05-arrays-collections.zh-cn.mdx create mode 100644 content/docs/java2js/module-05-arrays-collections.zh-tw.mdx create mode 100644 content/docs/java2js/module-06-objects.mdx create mode 100644 content/docs/java2js/module-06-objects.zh-cn.mdx create mode 100644 content/docs/java2js/module-06-objects.zh-tw.mdx create mode 100644 content/docs/java2js/module-07-classes.mdx create mode 100644 content/docs/java2js/module-07-classes.zh-cn.mdx create mode 100644 content/docs/java2js/module-07-classes.zh-tw.mdx create mode 100644 content/docs/java2js/module-08-prototypes.mdx create mode 100644 content/docs/java2js/module-08-prototypes.zh-cn.mdx create mode 100644 content/docs/java2js/module-08-prototypes.zh-tw.mdx create mode 100644 content/docs/java2js/module-09-this-context.mdx create mode 100644 content/docs/java2js/module-09-this-context.zh-cn.mdx create mode 100644 content/docs/java2js/module-09-this-context.zh-tw.mdx create mode 100644 content/docs/java2js/module-10-async-basics.mdx create mode 100644 content/docs/java2js/module-10-async-basics.zh-cn.mdx create mode 100644 content/docs/java2js/module-10-async-basics.zh-tw.mdx create mode 100644 content/docs/java2js/module-11-async-advanced.mdx create mode 100644 content/docs/java2js/module-11-async-advanced.zh-cn.mdx create mode 100644 content/docs/java2js/module-11-async-advanced.zh-tw.mdx create mode 100644 content/docs/java2js/module-12-dom-manipulation.mdx create mode 100644 content/docs/java2js/module-12-dom-manipulation.zh-cn.mdx create mode 100644 content/docs/java2js/module-12-dom-manipulation.zh-tw.mdx create mode 100644 content/docs/java2js/module-13-events.mdx create mode 100644 content/docs/java2js/module-13-events.zh-cn.mdx create mode 100644 content/docs/java2js/module-13-events.zh-tw.mdx create mode 100644 content/docs/java2js/module-14-modules.mdx create mode 100644 content/docs/java2js/module-14-modules.zh-cn.mdx create mode 100644 content/docs/java2js/module-14-modules.zh-tw.mdx create mode 100644 content/docs/java2js/module-15-tooling.mdx create mode 100644 content/docs/java2js/module-15-tooling.zh-cn.mdx create mode 100644 content/docs/java2js/module-15-tooling.zh-tw.mdx create mode 100644 content/docs/java2js/module-16-testing-debugging.mdx create mode 100644 content/docs/java2js/module-16-testing-debugging.zh-cn.mdx create mode 100644 content/docs/java2js/module-16-testing-debugging.zh-tw.mdx create mode 100644 content/docs/java2js/module-17-frameworks.mdx create mode 100644 content/docs/java2js/module-17-frameworks.zh-cn.mdx create mode 100644 content/docs/java2js/module-17-frameworks.zh-tw.mdx create mode 100644 content/docs/java2js/module-18-pitfalls.mdx create mode 100644 content/docs/java2js/module-18-pitfalls.zh-cn.mdx create mode 100644 content/docs/java2js/module-18-pitfalls.zh-tw.mdx create mode 100644 content/docs/java2js/module-19-real-projects.mdx create mode 100644 content/docs/java2js/module-19-real-projects.zh-cn.mdx create mode 100644 content/docs/java2js/module-19-real-projects.zh-tw.mdx diff --git a/components/header.tsx b/components/header.tsx index 6f544fc..839eea8 100644 --- a/components/header.tsx +++ b/components/header.tsx @@ -116,6 +116,24 @@ const SOURCE_LANGUAGES = [ status: 'completed' as const, }, ] + }, + { + id: 'java', + name: 'Java', + icon: '☕', + gradient: 'from-red-500 to-rose-500', + path: 'java2js', + status: 'completed' as const, + targets: [ + { + id: 'javascript', + name: 'JavaScript', + icon: '🚀', + gradient: 'from-yellow-500 to-orange-500', + path: 'java2js', + status: 'completed' as const, + }, + ] } // 未来可以添加其他源语言 ] as const; diff --git a/content/docs/java2js/index.mdx b/content/docs/java2js/index.mdx new file mode 100644 index 0000000..8f86e53 --- /dev/null +++ b/content/docs/java2js/index.mdx @@ -0,0 +1,194 @@ +--- +title: "Java → JavaScript" +description: "帮助 Java 开发者掌握现代 JavaScript 的完整学习路径" +--- + +## 欢迎使用 Java → JavaScript 学习路径 + +本课程专为有 Java 经验的开发者设计,帮助你系统地学习现代 JavaScript。 + +### 为什么学习 JavaScript? + +**从 Java 开发者的角度:** + +- **Web 开发必备**:前端开发的唯一语言 +- **全栈能力**:Node.js 让 JavaScript 可以运行在服务端 +- **丰富的生态系统**:npm 拥有超过 200 万个包 +- **灵活的语法**:从面向对象到函数式编程 +- **快速迭代**:无需编译,即时反馈 + +### Java vs JavaScript:关键差异 + +| 特性 | Java | JavaScript | +|------|------|------------| +| **类型系统** | 静态类型,强类型 | 动态类型,弱类型(TS 可选) | +| **运行环境** | JVM(编译后运行) | 浏览器/Node.js(解释/JIT) | +| **类加载** | 编译时加载类 | 运行时动态加载 | +| **多线程** | 原生线程支持 | 单线程 + 事件循环 | +| **内存管理** | GC(分代收集) | GC(标记清除) | +| **继承** | 类继承(extends) | 原型链继承 | +| **函数** | 方法(类成员) | 一等公民函数 | +| **异步** | Thread/Future | Promise/async/await | + +### 学习目标 + +完成本课程后,你将能够: + +✅ 理解 JavaScript 的核心概念和设计哲学 +✅ 掌握 ES6+ 现代语法 +✅ 编写清晰的异步代码(Promise、async/await) +✅ 使用原型和类进行面向对象编程 +✅ 操作 DOM 和处理事件 +✅ 使用现代工具链(npm、webpack) +✅ 编写测试和调试代码 +✅ 构建真实的前端应用 + +### 课程结构 + +#### 📖 第一部分:基础语法(模块 1-5) +- 变量声明和数据类型 +- 控制流和循环 +- 函数(箭头函数、闭包) +- 数组和集合操作 + +#### 📖 第二部分:对象和类(模块 6-9) +- 对象字面量和操作 +- ES6 类和继承 +- 原型和原型链 +- this 上下文和绑定 + +#### 📖 第三部分:异步编程(模块 10-11) +- 回调函数和事件循环 +- Promise 和 async/await +- 错误处理模式 + +#### 📖 第四部分:前端开发(模块 12-15) +- DOM 查询和操作 +- 事件处理和委托 +- ES6 模块系统 +- 构建工具链 + +#### 📖 第五部分:工具和实践(模块 16-20) +- 测试框架和调试 +- React/Vue 框架基础 +- 最佳实践和模式 +- 真实项目实战 + +### 前置知识 + +**开始之前,你应该熟悉:** +- Java 基础语法(类、对象、方法) +- 面向对象编程概念 +- 基本的编程概念(变量、函数、循环) +- 命令行基础操作 + +**不需要:** +- ❌ JavaScript 经验(完全零基础) +- ❌ 前端开发经验 +- ❌ 函数式编程知识 + +### 学习建议 + +**推荐学习路径:** + +1. **按顺序学习**:模块间有依赖关系 +2. **动手实践**:每个模块都有代码练习 +3. **对比理解**:使用你的 Java 知识来理解 JS +4. **实验探索**:尝试修改代码,观察效果 +5. **构建项目**:最后几个模块包含真实项目 + +**学习时间估计:** +- 每个模块:1-2 小时 +- 总计:20-40 小时 +- 建议:每天 1-2 个模块,10-20 天完成 + +### 代码示例约定 + +本书中所有代码示例都使用 **并列对比** 的方式: + +```java +// Java 代码 +public class Example { + private String name = "Java"; +} +``` + +```javascript +// JavaScript 代码 +class Example { + name = 'JavaScript'; // 字段声明 +} +``` + +### JavaScript 版本说明 + +本课程教授 **现代 JavaScript (ES6+)**: + +- ✅ ES2015 (ES6) - 类、箭头函数、Promise +- ✅ ES2016 - 幂运算符、Array.includes +- ✅ ES2017 - async/await、Object.entries +- ✅ ES2018+ - 对象展开、可选链、空值合并 + +**不涵盖:** +- ❌ ES5 及更早版本(过时语法) +- ❌ 浏览器兼容性处理(使用 Babel) + +### 开发环境设置 + +#### 推荐工具 + +**浏览器:** +- Chrome DevTools(调试) +- Firefox Developer Tools + +**IDE:** +- Visual Studio Code + ESLint + Prettier +- WebStorm(IntelliJ 系列) + +**运行环境:** +```bash +# 安装 Node.js(包含 npm) +node --version # 应该 >= 14.x +npm --version + +# 使用 REPL 交互式环境 +node +> console.log('Hello JavaScript') +``` + +### 开始学习 + +准备好了吗?让我们开始 **模块 0:JavaScript 语言介绍**,了解 JavaScript 的历史、设计哲学和运行环境! + +--- + +## 快速导航 + +### 基础模块 +- [模块 1: 基础语法对比](./module-01-basic-syntax) +- [模块 2: 控制流和循环](./module-02-control-flow) +- [模块 3: 函数基础](./module-03-functions-basics) +- [模块 4: 函数进阶](./module-04-functions-advanced) +- [模块 5: 数组和集合](./module-05-arrays-collections) + +### 面向对象 +- [模块 6: 对象基础](./module-06-objects) +- [模块 7: 类和继承](./module-07-classes) +- [模块 8: 原型和原型链](./module-08-prototypes) +- [模块 9: this 和上下文](./module-09-this-context) + +### 异步编程 +- [模块 10: 异步编程基础](./module-10-async-basics) +- [模块 11: 异步编程进阶](./module-11-async-advanced) + +### 前端开发 +- [模块 12: DOM 操作](./module-12-dom-manipulation) +- [模块 13: 事件处理](./module-13-events) +- [模块 14: ES6+ 模块系统](./module-14-modules) +- [模块 15: 工具链和构建](./module-15-tooling) + +### 实践 +- [模块 16: 测试和调试](./module-16-testing-debugging) +- [模块 17: 流行框架](./module-17-frameworks) +- [模块 18: 常见陷阱](./module-18-pitfalls) +- [模块 19: 真实项目](./module-19-real-projects) diff --git a/content/docs/java2js/index.zh-cn.mdx b/content/docs/java2js/index.zh-cn.mdx new file mode 100644 index 0000000..8f86e53 --- /dev/null +++ b/content/docs/java2js/index.zh-cn.mdx @@ -0,0 +1,194 @@ +--- +title: "Java → JavaScript" +description: "帮助 Java 开发者掌握现代 JavaScript 的完整学习路径" +--- + +## 欢迎使用 Java → JavaScript 学习路径 + +本课程专为有 Java 经验的开发者设计,帮助你系统地学习现代 JavaScript。 + +### 为什么学习 JavaScript? + +**从 Java 开发者的角度:** + +- **Web 开发必备**:前端开发的唯一语言 +- **全栈能力**:Node.js 让 JavaScript 可以运行在服务端 +- **丰富的生态系统**:npm 拥有超过 200 万个包 +- **灵活的语法**:从面向对象到函数式编程 +- **快速迭代**:无需编译,即时反馈 + +### Java vs JavaScript:关键差异 + +| 特性 | Java | JavaScript | +|------|------|------------| +| **类型系统** | 静态类型,强类型 | 动态类型,弱类型(TS 可选) | +| **运行环境** | JVM(编译后运行) | 浏览器/Node.js(解释/JIT) | +| **类加载** | 编译时加载类 | 运行时动态加载 | +| **多线程** | 原生线程支持 | 单线程 + 事件循环 | +| **内存管理** | GC(分代收集) | GC(标记清除) | +| **继承** | 类继承(extends) | 原型链继承 | +| **函数** | 方法(类成员) | 一等公民函数 | +| **异步** | Thread/Future | Promise/async/await | + +### 学习目标 + +完成本课程后,你将能够: + +✅ 理解 JavaScript 的核心概念和设计哲学 +✅ 掌握 ES6+ 现代语法 +✅ 编写清晰的异步代码(Promise、async/await) +✅ 使用原型和类进行面向对象编程 +✅ 操作 DOM 和处理事件 +✅ 使用现代工具链(npm、webpack) +✅ 编写测试和调试代码 +✅ 构建真实的前端应用 + +### 课程结构 + +#### 📖 第一部分:基础语法(模块 1-5) +- 变量声明和数据类型 +- 控制流和循环 +- 函数(箭头函数、闭包) +- 数组和集合操作 + +#### 📖 第二部分:对象和类(模块 6-9) +- 对象字面量和操作 +- ES6 类和继承 +- 原型和原型链 +- this 上下文和绑定 + +#### 📖 第三部分:异步编程(模块 10-11) +- 回调函数和事件循环 +- Promise 和 async/await +- 错误处理模式 + +#### 📖 第四部分:前端开发(模块 12-15) +- DOM 查询和操作 +- 事件处理和委托 +- ES6 模块系统 +- 构建工具链 + +#### 📖 第五部分:工具和实践(模块 16-20) +- 测试框架和调试 +- React/Vue 框架基础 +- 最佳实践和模式 +- 真实项目实战 + +### 前置知识 + +**开始之前,你应该熟悉:** +- Java 基础语法(类、对象、方法) +- 面向对象编程概念 +- 基本的编程概念(变量、函数、循环) +- 命令行基础操作 + +**不需要:** +- ❌ JavaScript 经验(完全零基础) +- ❌ 前端开发经验 +- ❌ 函数式编程知识 + +### 学习建议 + +**推荐学习路径:** + +1. **按顺序学习**:模块间有依赖关系 +2. **动手实践**:每个模块都有代码练习 +3. **对比理解**:使用你的 Java 知识来理解 JS +4. **实验探索**:尝试修改代码,观察效果 +5. **构建项目**:最后几个模块包含真实项目 + +**学习时间估计:** +- 每个模块:1-2 小时 +- 总计:20-40 小时 +- 建议:每天 1-2 个模块,10-20 天完成 + +### 代码示例约定 + +本书中所有代码示例都使用 **并列对比** 的方式: + +```java +// Java 代码 +public class Example { + private String name = "Java"; +} +``` + +```javascript +// JavaScript 代码 +class Example { + name = 'JavaScript'; // 字段声明 +} +``` + +### JavaScript 版本说明 + +本课程教授 **现代 JavaScript (ES6+)**: + +- ✅ ES2015 (ES6) - 类、箭头函数、Promise +- ✅ ES2016 - 幂运算符、Array.includes +- ✅ ES2017 - async/await、Object.entries +- ✅ ES2018+ - 对象展开、可选链、空值合并 + +**不涵盖:** +- ❌ ES5 及更早版本(过时语法) +- ❌ 浏览器兼容性处理(使用 Babel) + +### 开发环境设置 + +#### 推荐工具 + +**浏览器:** +- Chrome DevTools(调试) +- Firefox Developer Tools + +**IDE:** +- Visual Studio Code + ESLint + Prettier +- WebStorm(IntelliJ 系列) + +**运行环境:** +```bash +# 安装 Node.js(包含 npm) +node --version # 应该 >= 14.x +npm --version + +# 使用 REPL 交互式环境 +node +> console.log('Hello JavaScript') +``` + +### 开始学习 + +准备好了吗?让我们开始 **模块 0:JavaScript 语言介绍**,了解 JavaScript 的历史、设计哲学和运行环境! + +--- + +## 快速导航 + +### 基础模块 +- [模块 1: 基础语法对比](./module-01-basic-syntax) +- [模块 2: 控制流和循环](./module-02-control-flow) +- [模块 3: 函数基础](./module-03-functions-basics) +- [模块 4: 函数进阶](./module-04-functions-advanced) +- [模块 5: 数组和集合](./module-05-arrays-collections) + +### 面向对象 +- [模块 6: 对象基础](./module-06-objects) +- [模块 7: 类和继承](./module-07-classes) +- [模块 8: 原型和原型链](./module-08-prototypes) +- [模块 9: this 和上下文](./module-09-this-context) + +### 异步编程 +- [模块 10: 异步编程基础](./module-10-async-basics) +- [模块 11: 异步编程进阶](./module-11-async-advanced) + +### 前端开发 +- [模块 12: DOM 操作](./module-12-dom-manipulation) +- [模块 13: 事件处理](./module-13-events) +- [模块 14: ES6+ 模块系统](./module-14-modules) +- [模块 15: 工具链和构建](./module-15-tooling) + +### 实践 +- [模块 16: 测试和调试](./module-16-testing-debugging) +- [模块 17: 流行框架](./module-17-frameworks) +- [模块 18: 常见陷阱](./module-18-pitfalls) +- [模块 19: 真实项目](./module-19-real-projects) diff --git a/content/docs/java2js/index.zh-tw.mdx b/content/docs/java2js/index.zh-tw.mdx new file mode 100644 index 0000000..eb482b4 --- /dev/null +++ b/content/docs/java2js/index.zh-tw.mdx @@ -0,0 +1,194 @@ +--- +title: "Java → JavaScript" +description: "幫助 Java 開發者掌握現代 JavaScript 的完整學習路徑" +--- + +## 歡迎使用 Java → JavaScript 學習路徑 + +本課程專為有 Java 經驗的開發者設計,幫助你系統地學習現代 JavaScript。 + +### 為什麼學習 JavaScript? + +**從 Java 開發者的角度:** + +- **Web 開發必備**:前端開發的唯一語言 +- **全棧能力**:Node.js 讓 JavaScript 可以運行在服務端 +- **豐富的生態系統**:npm 擁有超過 200 萬個包 +- **靈活的語法**:從面向對象到函數式編程 +- **快速迭代**:無需編譯,即時反饋 + +### Java vs JavaScript:關鍵差異 + +| 特性 | Java | JavaScript | +|------|------|------------| +| **類型系統** | 靜態類型,強類型 | 動態類型,弱類型(TS 可選) | +| **運行環境** | JVM(編譯後運行) | 瀏覽器/Node.js(解釋/JIT) | +| **類加載** | 編譯時加載類 | 運行時動態加載 | +| **多線程** | 原生線程支持 | 單線程 + 事件循環 | +| **內存管理** | GC(分代收集) | GC(標記清除) | +| **繼承** | 類繼承(extends) | 原型鏈繼承 | +| **函數** | 方法(類成員) | 一等公民函數 | +| **異步** | Thread/Future | Promise/async/await | + +### 學習目標 + +完成本課程後,你將能夠: + +✅ 理解 JavaScript 的核心概念和設計哲學 +✅ 掌握 ES6+ 現代語法 +✅ 編寫清晰的異步代碼(Promise、async/await) +✅ 使用原型和類進行面向對象編程 +✅ 操作 DOM 和處理事件 +✅ 使用現代工具鏈(npm、webpack) +✅ 編寫測試和調試代碼 +✅ 構建真實的前端應用 + +### 課程結構 + +#### 📖 第一部分:基礎語法(模組 1-5) +- 變量聲明和數據類型 +- 控制流和循環 +- 函數(箭頭函數、閉包) +- 數組和集合操作 + +#### 📖 第二部分:對象和類(模組 6-9) +- 對象字面量和操作 +- ES6 類和繼承 +- 原型和原型鏈 +- this 上下文和綁定 + +#### 📖 第三部分:異步編程(模組 10-11) +- 回調函數和事件循環 +- Promise 和 async/await +- 錯誤處理模式 + +#### 📖 第四部分:前端開發(模組 12-15) +- DOM 查詢和操作 +- 事件處理和委託 +- ES6 模組系統 +- 構建工具鏈 + +#### 📖 第五部分:工具和實踐(模組 16-20) +- 測試框架和調試 +- React/Vue 框架基礎 +- 最佳實踐和模式 +- 真實項目實戰 + +### 前置知識 + +**開始之前,你應該熟悉:** +- Java 基礎語法(類、對象、方法) +- 面向對象編程概念 +- 基本的編程概念(變量、函數、循環) +- 命令行基礎操作 + +**不需要:** +- ❌ JavaScript 經驗(完全零基礎) +- ❌ 前端開發經驗 +- ❌ 函數式編程知識 + +### 學習建議 + +**推薦學習路徑:** + +1. **按順序學習**:模組間有依賴關係 +2. **動手實踐**:每個模組都有代碼練習 +3. **對比理解**:使用你的 Java 知識來理解 JS +4. **實驗探索**:嘗試修改代碼,觀察效果 +5. **構建項目**:最後幾個模組包含真實項目 + +**學習時間估計:** +- 每個模組:1-2 小時 +- 總計:20-40 小時 +- 建議:每天 1-2 個模組,10-20 天完成 + +### 代碼示例約定 + +本書中所有代碼示例都使用 **並列對比** 的方式: + +```java +// Java 代碼 +public class Example { + private String name = "Java"; +} +``` + +```javascript +// JavaScript 代碼 +class Example { + name = 'JavaScript'; // 字段聲明 +} +``` + +### JavaScript 版本說明 + +本課程教授 **現代 JavaScript (ES6+)**: + +- ✅ ES2015 (ES6) - 類、箭頭函數、Promise +- ✅ ES2016 - 冪運算符、Array.includes +- ✅ ES2017 - async/await、Object.entries +- ✅ ES2018+ - 對象展開、可選鏈、空值合併 + +**不涵蓋:** +- ❌ ES5 及更早版本(過時語法) +- ❌ 瀏覽器兼容性處理(使用 Babel) + +### 開發環境設置 + +#### 推薦工具 + +**瀏覽器:** +- Chrome DevTools(調試) +- Firefox Developer Tools + +**IDE:** +- Visual Studio Code + ESLint + Prettier +- WebStorm(IntelliJ 系列) + +**運行環境:** +```bash +# 安裝 Node.js(包含 npm) +node --version # 應該 >= 14.x +npm --version + +# 使用 REPL 交互式環境 +node +> console.log('Hello JavaScript') +``` + +### 開始學習 + +準備好了嗎?讓我們開始 **模組 0:JavaScript 語言介紹**,了解 JavaScript 的歷史、設計哲學和運行環境! + +--- + +## 快速導航 + +### 基礎模組 +- [模組 1: 基礎語法對比](./module-01-basic-syntax) +- [模組 2: 控制流和循環](./module-02-control-flow) +- [模組 3: 函數基礎](./module-03-functions-basics) +- [模組 4: 函數進階](./module-04-functions-advanced) +- [模組 5: 數組和集合](./module-05-arrays-collections) + +### 面向對象 +- [模組 6: 對象基礎](./module-06-objects) +- [模組 7: 類和繼承](./module-07-classes) +- [模組 8: 原型和原型鏈](./module-08-prototypes) +- [模組 9: this 和上下文](./module-09-this-context) + +### 異步編程 +- [模組 10: 異步編程基礎](./module-10-async-basics) +- [模組 11: 異步編程進階](./module-11-async-advanced) + +### 前端開發 +- [模組 12: DOM 操作](./module-12-dom-manipulation) +- [模組 13: 事件處理](./module-13-events) +- [模組 14: ES6+ 模組系統](./module-14-modules) +- [模組 15: 工具鏈和構建](./module-15-tooling) + +### 實踐 +- [模組 16: 測試和調試](./module-16-testing-debugging) +- [模組 17: 流行框架](./module-17-frameworks) +- [模組 18: 常見陷阱](./module-18-pitfalls) +- [模組 19: 真實項目](./module-19-real-projects) diff --git a/content/docs/java2js/meta.json b/content/docs/java2js/meta.json new file mode 100644 index 0000000..252ca94 --- /dev/null +++ b/content/docs/java2js/meta.json @@ -0,0 +1,4 @@ +{ + "title": "Java → JavaScript", + "root": true +} diff --git a/content/docs/java2js/module-00-js-introduction.mdx b/content/docs/java2js/module-00-js-introduction.mdx new file mode 100644 index 0000000..b7d4ccd --- /dev/null +++ b/content/docs/java2js/module-00-js-introduction.mdx @@ -0,0 +1,1034 @@ +--- +title: "Module 0: JavaScript Language Introduction" +description: "Understanding JavaScript's history, design philosophy, and setting up your development environment for Java developers" +--- + +## JavaScript Language History and Design Philosophy + +JavaScript was created by Brendan Eich at Netscape in 1995 (in just 10 days!). Originally named Mocha, then LiveScript, it was finally renamed JavaScript to capitalize on Java's popularity at the time. Despite the name, JavaScript and Java are fundamentally different languages with different design goals. + +### Why JavaScript Was Created + +JavaScript emerged from the need for a lightweight scripting language that could run in web browsers: + +- **Java was too heavy** for simple web interactions +- **HTML was static** and lacked interactivity +- **No client-side logic** existed in web browsers +- **Web pages needed** dynamic behavior without server round-trips + +JavaScript's creators wanted a language that: +- Could be embedded directly in HTML +- Was easy to learn for beginners +- Provided dynamic behavior to web pages +- Worked across different browsers + +### JavaScript's Design Philosophy + +JavaScript's design is guided by these core principles: + +#### 1. Dynamic Typing +- **No type declarations**: Variables can hold any type +- **Runtime type checking**: Types determined during execution +- **Flexible conversions**: Automatic type coercion +- **Rapid prototyping**: Less boilerplate code + + +```java !! java +// Java - Static typing +String name = "John"; +int age = 25; +boolean isActive = true; + +// age = "twenty five"; // Compilation error! +// Type must be declared explicitly +``` + +```javascript !! js +// JavaScript - Dynamic typing +let name = "John"; +let age = 25; +let isActive = true; + +age = "twenty five"; // Valid! Type can change +// No type declarations needed +``` + + +#### 2. First-Class Functions +- **Functions are values**: Can be assigned to variables +- **Higher-order functions**: Can accept and return functions +- **Closures**: Functions retain access to their scope +- **Functional programming**: Support for map, filter, reduce + + +```java !! java +// Java - Functions are class methods +public class Calculator { + public int add(int a, int b) { + return a + b; + } + + // Can't pass methods as arguments + // Need interfaces or lambda expressions (Java 8+) +} +``` + +```javascript !! js +// JavaScript - Functions are first-class citizens +const add = function(a, b) { + return a + b; +}; + +// Arrow function (ES6+) +const multiply = (a, b) => a * b; + +// Functions can be passed as arguments +const numbers = [1, 2, 3, 4, 5]; +const doubled = numbers.map(n => n * 2); + +// Higher-order function +function withLogging(fn) { + return function(...args) { + console.log('Calling', fn.name); + return fn(...args); + }; +} +``` + + +#### 3. Prototype-Based Inheritance +- **No classes (traditionally)**: Objects inherit from other objects +- **Prototypes**: Each object has a prototype chain +- **Dynamic behavior**: Can modify objects at runtime +- **ES6 classes**: Syntactic sugar over prototypes + + +```java !! java +// Java - Class-based inheritance +public class Animal { + public void speak() { + System.out.println("Some sound"); + } +} + +public class Dog extends Animal { + @Override + public void speak() { + System.out.println("Woof!"); + } +} + +// Usage +Animal dog = new Dog(); +dog.speak(); // "Woof!" +``` + +```javascript !! js +// JavaScript - Prototype-based inheritance +const animal = { + speak() { + console.log("Some sound"); + } +}; + +const dog = Object.create(animal); +dog.speak = function() { + console.log("Woof!"); +}; + +dog.speak(); // "Woof!" + +// ES6 class syntax (syntactic sugar) +class Animal { + speak() { + console.log("Some sound"); + } +} + +class Dog extends Animal { + speak() { + console.log("Woof!"); + } +} + +const dog2 = new Dog(); +dog2.speak(); // "Woof!" +``` + + +#### 4. Event-Driven Architecture +- **Event loop**: Single-threaded with event queue +- **Asynchronous**: Non-blocking I/O operations +- **Callbacks**: Functions called when events complete +- **Promises/async-await**: Modern async handling + + +```java !! java +// Java - Multi-threaded +public class DataLoader { + public String loadData() { + // Blocks thread until data loads + String data = fetchDataFromDatabase(); + return data; + } + + public static void main(String[] args) { + // Each thread runs independently + Thread thread1 = new Thread(() -> { + new DataLoader().loadData(); + }); + thread1.start(); + } +} +``` + +```javascript !! js +// JavaScript - Event-driven, single-threaded +function loadData() { + return new Promise((resolve) => { + // Non-blocking, continues execution + setTimeout(() => { + resolve("Data loaded!"); + }, 1000); + }); +} + +// Async/await (modern approach) +async function main() { + console.log("Loading..."); + const data = await loadData(); + console.log(data); + // All runs on single thread with event loop +} + +main(); +``` + + +## Comparison with Java + +| Feature | Java | JavaScript | +|---------|------|------------| +| **Paradigm** | Object-oriented (class-based) | Multi-paradigm (prototype-based, functional) | +| **Typing** | Static (must declare types) | Dynamic (types determined at runtime) | +| **Execution** | Compiled to bytecode, runs on JVM | Interpreted/JIT-compiled in browser or Node.js | +| **Environment** | JVM (server, desktop, Android) | Browser, Node.js (server), various embeddings | +| **Concurrency** | Multi-threaded with shared memory | Single-threaded with event loop | +| **Inheritance** | Class inheritance (extends, implements) | Prototype chain (class syntax is sugar) | +| **Functions** | Methods (attached to classes) | First-class citizens (can be passed around) | +| **Memory** | Manual object lifecycle, GC | GC, automatic memory management | +| **Deployment** | JAR/WAR files, requires JVM | Embedded in HTML, npm packages, standalone | +| **Package Management** | Maven, Gradle | npm, yarn, pnpm | +| **Standard Library** | Extensive, enterprise-focused | Smaller, web-focused | + +## Compiled vs Interpreted + +### Java (Compiled to Bytecode) + +Java code goes through a compilation process: + +1. **Source code** (`.java` files) is compiled to bytecode +2. **Bytecode** (`.class` files) runs on JVM +3. **JIT compiler** optimizes hot code paths +4. **Static typing** catches errors at compile time + +**Pros:** +- Fast execution after JIT compilation +- Compile-time error checking +- Strong type safety +- Optimized performance + +**Cons:** +- Compilation step required +- Verbose syntax +- Heavier memory footprint +- Slower startup time + +### JavaScript (Interpreted/JIT) + +JavaScript execution varies by environment: + +**In Browser:** +1. **Source code** parsed and executed immediately +2. **JIT compilation** optimizes frequently used code +3. **Dynamic typing** allows flexibility +4. **Event loop** handles asynchronous operations + +**In Node.js:** +1. **V8 engine** compiles to machine code +2. **Just-in-time** optimization +3. **No browser DOM** - server-side APIs + + +```java !! java +// Java - Compile then run +// File: HelloWorld.java +public class HelloWorld { + public static void main(String[] args) { + System.out.println("Hello, World!"); + } +} + +// Compilation +$ javac HelloWorld.java + +// Execution +$ java HelloWorld +Hello, World! +``` + +```javascript !! js +// JavaScript - Run directly (no compilation) +// File: hello.js +console.log("Hello, World!"); + +// In browser + + +// In Node.js +$ node hello.js +Hello, World! +``` + + +## JavaScript's Use Cases and Advantages + +### Primary Use Cases + +#### 1. Web Frontend Development +JavaScript is essential for web development: + +- **DOM Manipulation**: Interactive web pages +- **Event Handling**: User interactions +- **AJAX/Fetch**: Dynamic data loading +- **Single Page Apps**: React, Vue, Angular +- **Progressive Web Apps**: Offline capabilities + + +```java !! java +// Java - Requires full page reload for updates +@WebServlet("/users") +public class UserServlet extends HttpServlet { + protected void doGet(HttpServletRequest req, HttpServletResponse resp) { + // Server renders entire HTML page + req.setAttribute("users", getUsers()); + req.getRequestDispatcher("/users.jsp").forward(req, resp); + } +} +``` + +```javascript !! js +// JavaScript - Dynamic updates without reload +// Fetch user data asynchronously +async function loadUsers() { + const response = await fetch('/api/users'); + const users = await response.json(); + + // Update DOM dynamically + const userList = document.getElementById('user-list'); + userList.innerHTML = users.map(user => + `
  • ${user.name}
  • ` + ).join(''); +} + +// Call without page reload +loadUsers(); +``` +
    + +#### 2. Backend Development (Node.js) +- **REST APIs**: Express, Fastify, Koa +- **Real-time apps**: WebSockets, Socket.io +- **Microservices**: Lightweight services +- **Serverless**: AWS Lambda, Cloud Functions + + +```java !! java +// Java - Spring Boot REST API +@RestController +@RequestMapping("/api") +public class UserController { + + @GetMapping("/users/{id}") + public ResponseEntity getUser(@PathVariable Long id) { + User user = userService.findById(id); + return ResponseEntity.ok(user); + } +} +``` + +```javascript !! js +// JavaScript - Node.js REST API (Express) +const express = require('express'); +const app = express(); + +app.get('/api/users/:id', (req, res) => { + const user = userService.findById(req.params.id); + res.json(user); +}); + +app.listen(3000, () => { + console.log('Server running on port 3000'); +}); +``` + + +#### 3. Mobile Development +- **React Native**: iOS and Android apps +- **Ionic**: Hybrid mobile apps +- **Electron**: Desktop applications + +#### 4. Developer Tools +- **Build tools**: Webpack, Vite, esbuild +- **Testing**: Jest, Cypress, Playwright +- **Linting**: ESLint, Prettier +- **Package management**: npm, yarn + +### Key Advantages + +#### 1. Rapid Development +- **No compilation**: Immediate feedback +- **Dynamic typing**: Less boilerplate +- **Hot reload**: Instant updates +- **Huge ecosystem**: npm packages + + +```java !! java +// Java - More boilerplate +public class Person { + private String name; + private int age; + + public Person(String name, int age) { + this.name = name; + this.age = age; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + + @Override + public String toString() { + return "Person{name='" + name + "', age=" + age + "}"; + } +} +``` + +```javascript !! js +// JavaScript - Concise +class Person { + constructor(name, age) { + this.name = name; + this.age = age; + } + + toString() { + return `Person{name='${this.name}', age=${this.age}}`; + } +} + +// Or even simpler with object literals +const person = { name: "John", age: 30 }; +console.log(JSON.stringify(person)); +``` + + +#### 2. Universal Language +- **Frontend and backend**: Same language everywhere +- **Code sharing**: Share code between platforms +- **Full-stack development**: One developer, entire stack +- **Community**: Largest developer community + +#### 3. Flexibility +- **Multiple paradigms**: OOP, functional, procedural +- **Dynamic features**: Metaprogramming, reflection +- **Easy prototyping**: Quick experimentation +- **Adaptable**: Works everywhere + +#### 4. Modern Features +- **ES6+ syntax**: Classes, modules, arrow functions +- **Async/await**: Clean asynchronous code +- **Destructuring**: Convenient data extraction +- **Spread operator**: Easy object/array manipulation + + +```java !! java +// Java - Traditional approach +List names = Arrays.asList("Alice", "Bob", "Charlie"); +List filtered = new ArrayList<>(); + +for (String name : names) { + if (name.startsWith("A")) { + filtered.add(name); + } +} + +Map nameLengths = new HashMap<>(); +for (String name : filtered) { + nameLengths.put(name, name.length()); +} +``` + +```javascript !! js +// JavaScript - Modern functional approach +const names = ["Alice", "Bob", "Charlie"]; + +const result = names + .filter(name => name.startsWith("A")) + .reduce((acc, name) => ({ + ...acc, + [name]: name.length + }), {}); + +console.log(result); // { Alice: 5 } +``` + + +## Development Environment Setup + +### Prerequisites + +No special prerequisites - JavaScript works on: +- **Any modern browser**: Chrome, Firefox, Safari, Edge +- **Node.js**: For server-side development +- **Operating systems**: Windows, macOS, Linux + +### Installation Methods + +#### Method 1: Browser Console (Quickest Start) + +No installation needed - just open your browser: + +1. Open Chrome/Firefox/Safari +2. Press `F12` or `Cmd+Option+I` (Mac) / `Ctrl+Shift+I` (Windows) +3. Go to "Console" tab +4. Start coding! + +```javascript +// Try this in your browser console +console.log("Hello, JavaScript!"); +alert("Welcome to JS!"); +document.body.style.backgroundColor = "lightblue"; +``` + +#### Method 2: Node.js (For Development) + +**macOS/Linux:** +```bash +# Using Homebrew (macOS) +brew install node + +# Or using apt (Ubuntu/Debian) +sudo apt update +sudo apt install nodejs npm +``` + +**Windows:** +1. Download from https://nodejs.org/ +2. Run the installer +3. Restart your terminal + +**Verify Installation:** +```bash +node --version # Should be v18.x or higher +npm --version # Should be 9.x or higher +``` + +#### Method 3: Online Playgrounds (For Learning) + +- **CodePen**: https://codepen.io/pen/ +- **JSFiddle**: https://jsfiddle.net/ +- **Replit**: https://replit.com/ +- **StackBlitz**: https://stackblitz.com/ + +### Recommended Tools + +#### Code Editors + +**Visual Studio Code (Recommended):** +```bash +# Download from https://code.visualstudio.com/ +# Install these extensions: +- ESLint +- Prettier +- JavaScript (ES6) code snippets +- Node.js modules intellisense +``` + +**WebStorm (JetBrains):** +- Full-featured JavaScript IDE +- Built-in debugger +- Refactoring tools +- Intelligent code completion + +#### Browser DevTools + +**Chrome DevTools:** +- Elements: DOM inspection +- Console: JavaScript execution +- Sources: Debugging +- Network: HTTP monitoring +- Performance: Profiling + +#### Package Managers + +**npm (Node Package Manager):** +```bash +# Comes with Node.js +npm init -y # Initialize project +npm install lodash # Install package +npm install --save-dev jest # Dev dependency +``` + +**yarn (Alternative):** +```bash +npm install -g yarn # Install globally +yarn init # Initialize project +yarn add lodash # Install package +``` + +#### Build Tools + +**Vite (Modern, Fast):** +```bash +npm create vite@latest my-app +cd my-app +npm install +npm run dev +``` + +**Webpack (Flexible):** +```bash +npm init -y +npm install webpack webpack-cli --save-dev +``` + +### Your First JavaScript Program + + +```java !! java +// Java - Traditional approach +public class HelloWorld { + public static void main(String[] args) { + System.out.println("Hello, World!"); + + // Using variables + String name = "Java Developer"; + int years = 5; + + System.out.println("I'm a " + name + " with " + years + " years experience"); + } +} + +// Save as HelloWorld.java +// Compile: javac HelloWorld.java +// Run: java HelloWorld +``` + +```javascript !! js +// JavaScript - Simple and direct +console.log("Hello, World!"); + +// Using variables (no type declarations) +const name = "Java Developer"; +const years = 5; + +// Template literals +console.log(`I'm a ${name} with ${years} years experience`); + +// In browser, you can also: +// alert("Hello, World!"); +// document.write("Hello, World!"); +``` + + +### Setting Up a Project + +**Initialize a Node.js Project:** +```bash +# Create project directory +mkdir my-js-project +cd my-js-project + +# Initialize package.json +npm init -y + +# Create index.js +touch index.js + +# Add to index.js +console.log("Hello from Node.js!"); + +# Run +node index.js +``` + +**package.json:** +```json +{ + "name": "my-js-project", + "version": "1.0.0", + "description": "My JavaScript project", + "main": "index.js", + "scripts": { + "start": "node index.js", + "dev": "node --watch index.js", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC" +} +``` + +## JavaScript Versions (ES6+) + +This course focuses on **Modern JavaScript (ES6+)**: + +### ES2015 (ES6) - Major Release +- `let` and `const` block-scoped variables +- Arrow functions +- Classes +- Template literals +- Destructuring +- Spread operator +- Promises +- Modules + +### ES2016-ES2020 - Incremental Updates +- Array.includes (ES2016) +- Async/await (ES2017) +- Object rest/spread (ES2018) +- Optional chaining (ES2020) +- Nullish coalescing (ES2020) + +### ES2021+ - Latest Features +- Logical assignment operators +- Numeric separators +- String.replaceAll +- Promise.any +- Top-level await + + +```javascript !! js +// ES5 (Old JavaScript - We Won't Focus On This) +var name = "John"; +var age = 25; + +function greet(name) { + return "Hello, " + name; +} + +var numbers = [1, 2, 3]; +var doubled = numbers.map(function(n) { + return n * 2; +}); + +// Constructor function +function Person(name) { + this.name = name; +} + +Person.prototype.greet = function() { + console.log("Hello, " + this.name); +}; +``` + +```javascript !! js +// ES6+ (Modern JavaScript - What We'll Learn) +const name = "John"; +const age = 25; + +const greet = (name) => `Hello, ${name}`; + +const numbers = [1, 2, 3]; +const doubled = numbers.map(n => n * 2); + +// Class syntax +class Person { + constructor(name) { + this.name = name; + } + + greet() { + console.log(`Hello, ${this.name}`); + } +} +``` + + +## Browser Environment vs Node.js + +### Browser JavaScript +- **DOM API**: `document`, `window`, `querySelector` +- **Web APIs**: `fetch`, `localStorage`, `Canvas`, `WebGL` +- **No file system access** (security) +- **Single page context** + +### Node.js JavaScript +- **No DOM**: No `document` or `window` +- **File system access**: `fs` module +- **HTTP server**: `http`, `https` modules +- **CommonJS modules**: `require()` (ES modules also supported) + + +```javascript !! js +// Browser JavaScript +// Fetch data from API and update DOM +async function loadUser(userId) { + const response = await fetch(`/api/users/${userId}`); + const user = await response.json(); + + // Update DOM + document.getElementById('user-name').textContent = user.name; + document.getElementById('user-email').textContent = user.email; +} + +loadUser(123); +``` + +```javascript !! js +// Node.js (Server-side) +const fs = require('fs'); +const http = require('http'); + +// Create HTTP server +const server = http.createServer((req, res) => { + if (req.url === '/api/user') { + // Read from file system + fs.readFile('user.json', (err, data) => { + if (err) { + res.writeHead(500); + res.end('Error loading user'); + return; + } + + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(data); + }); + } +}); + +server.listen(3000); +``` + + +## Java Developer's Mental Model Adjustment + +### Key Mindset Shifts + +#### 1. From Compile-Time to Runtime Errors + +**Java:** +```java +String name = "John"; +// name = 42; // Compiler catches this immediately +``` + +**JavaScript:** +```javascript +let name = "John"; +name = 42; // Valid! But might cause issues later +console.log(name.toUpperCase()); // Runtime error: name.toUpperCase is not a function +``` + +#### 2. From Classes to Prototypes + +**Java:** +```java +// Everything must be in a class +public class Utils { + public static int add(int a, int b) { + return a + b; + } +} +``` + +**JavaScript:** +```javascript +// Functions can exist independently +function add(a, b) { + return a + b; +} + +// Or as arrow functions +const add = (a, b) => a + b; +``` + +#### 3. From Thread-Based to Event-Based Concurrency + +**Java:** +```java +// Create thread, run concurrently +new Thread(() -> { + doWork(); +}).start(); +``` + +**JavaScript:** +```javascript +// Use event loop, callbacks, promises +setTimeout(() => { + doWork(); +}, 1000); + +// Or better, with async/await +await new Promise(resolve => { + setTimeout(resolve, 1000); +}); +doWork(); +``` + +#### 4. From Explicit Types to Type Inference + +**Java:** +```java +Map> data = new HashMap<>(); +List names = data.get("key"); +``` + +**JavaScript:** +```javascript +const data = {}; // No type annotations +const names = data.key; // Access with dot notation +// Or: const names = data["key"]; +``` + +## Common JavaScript Terminology + +For Java developers, here are key JavaScript terms: + +| Term | Java Equivalent | Description | +|------|----------------|-------------| +| **Variable** | Variable | Storage for values (no type declaration) | +| **Function** | Method | Reusable code block (not tied to class) | +| **Object** | Instance/Map | Key-value pairs, dynamic properties | +| **Array** | List/ArrayList | Ordered list (can contain mixed types) | +| **Promise** | Future/CompletableFuture | Async operation result | +| **Callback** | Listener/Observer | Function passed as argument | +| **Closure** | Lambda with captured variables | Function retaining scope | +| **Module** | Package/Import | Code organization (ES6) | +| **npm** | Maven/Gradle | Package manager | +| **Event Loop** | Executor/Dispatcher | Async execution model | +| **DOM** | - | Document Object Model (browser only) | +| **JSON** | - | JavaScript Object Notation (data format) | + +## Learning Path Overview + +This course is structured to help Java developers master JavaScript: + +### Module Structure + +**Modules 1-5: Basic Syntax** +- Variable declarations (let, const, var) +- Data types and type coercion +- Control flow (if/else, switch, loops) +- Functions (declarations, expressions, arrows) +- Arrays and array methods + +**Modules 6-9: Objects and Classes** +- Object literals and properties +- ES6 classes and inheritance +- Prototypes and prototype chain +- `this` context and binding + +**Modules 10-11: Async Programming** +- Callbacks and event loop +- Promises and async/await +- Error handling patterns + +**Modules 12-15: Frontend Development** +- DOM manipulation and querySelector +- Event handling and propagation +- ES6 modules and imports +- Build tools (webpack, Vite) + +**Modules 16-19: Tools and Best Practices** +- Testing frameworks (Jest, Cypress) +- Framework basics (React, Vue) +- Common pitfalls and anti-patterns +- Real-world project examples + +## Exercises + +### Exercise 1: Browser Console +1. Open your browser's developer console (F12) +2. Try these commands: + ```javascript + console.log("Hello from console!"); + const name = "Java Developer"; + console.log(`Welcome, ${name}!`); + alert("You can do this!"); + ``` + +### Exercise 2: Node.js Setup +1. Install Node.js if you haven't +2. Create a file `hello.js`: + ```javascript + console.log("Hello from Node.js!"); + ``` +3. Run it: `node hello.js` + +### Exercise 3: Modern JavaScript +Compare ES5 vs ES6 syntax by running this: +```javascript +// ES5 +var add = function(a, b) { + return a + b; +}; + +// ES6 +const addES6 = (a, b) => a + b; + +console.log(add(1, 2)); +console.log(addES6(1, 2)); +``` + +### Exercise 4: Mental Model Shift +Reflect on these differences: +1. How does dynamic typing feel different from static typing? +2. What advantages do first-class functions provide? +3. How might the event loop model differ from Java threads? + +## Summary + +JavaScript offers Java developers a completely different programming paradigm: + +**Key Differences:** +- **Dynamic typing**: No type declarations, runtime flexibility +- **Prototype-based**: Objects inherit from objects, not classes +- **Event-driven**: Single-threaded with event loop, not multi-threaded +- **First-class functions**: Functions are values, can be passed around +- **Ubiquitous**: Runs everywhere - browser, server, mobile, desktop + +**What Makes JavaScript Powerful:** +- Rapid development with immediate feedback +- Universal language for full-stack development +- Massive ecosystem (npm packages) +- Modern syntax (ES6+) makes code clean and readable +- Asynchronous programming model for scalable apps + +**What You'll Learn:** +- Modern JavaScript (ES6+) syntax and features +- Functional and object-oriented programming patterns +- Asynchronous programming with Promises and async/await +- Frontend development with DOM manipulation +- Backend development with Node.js +- Best practices and common patterns + +In the next modules, we'll dive deep into JavaScript syntax, starting with basic comparisons to Java. Get ready to see how JavaScript's flexibility and simplicity can make you more productive! + +Ready to start learning JavaScript? Let's begin with **Module 1: Basic Syntax Comparison**! diff --git a/content/docs/java2js/module-00-js-introduction.zh-cn.mdx b/content/docs/java2js/module-00-js-introduction.zh-cn.mdx new file mode 100644 index 0000000..29fd837 --- /dev/null +++ b/content/docs/java2js/module-00-js-introduction.zh-cn.mdx @@ -0,0 +1,980 @@ +--- +title: "模块 0:JavaScript 语言简介" +description: "了解 JavaScript 的历史、设计理念,并为 Java 开发者设置开发环境" +--- + +## JavaScript 语言历史与设计理念 + +JavaScript 由 Brendan Eich 于 1995 年在 Netscape 公司创建(仅用了 10 天!)。最初命名为 Mocha,后改为 LiveScript,最后为了借助 Java 当时的人气而更名为 JavaScript。尽管名字相似,JavaScript 和 Java 是两种完全不同的语言,有着不同的设计目标。 + +### 为什么创建 JavaScript + +JavaScript 的出现源于对一种可以在 Web 浏览器中运行的轻量级脚本语言的需求: + +- **Java 太重了**,不适合简单的 Web 交互 +- **HTML 是静态的**,缺乏交互性 +- **浏览器中没有客户端逻辑** +- **Web 页面需要**动态行为,无需服务器往返 + +JavaScript 的创建者希望一种语言: +- 可以直接嵌入到 HTML 中 +- 对初学者来说简单易学 +- 为 Web 页面提供动态行为 +- 在不同浏览器中都能工作 + +### JavaScript 的设计理念 + +JavaScript 的设计遵循以下核心原则: + +#### 1. 动态类型 +- **无需类型声明**:变量可以保存任何类型 +- **运行时类型检查**:类型在执行期间确定 +- **灵活的类型转换**:自动类型强制转换 +- **快速原型开发**:减少样板代码 + + +```java !! java +// Java - 静态类型 +String name = "John"; +int age = 25; +boolean isActive = true; + +// age = "twenty five"; // 编译错误! +// 必须显式声明类型 +``` + +```javascript !! js +// JavaScript - 动态类型 +let name = "John"; +let age = 25; +let isActive = true; + +age = "twenty five"; // 有效!类型可以改变 +// 无需类型声明 +``` + + +#### 2. 一等函数 +- **函数是值**:可以赋值给变量 +- **高阶函数**:可以接受和返回函数 +- **闭包**:函数保留对其作用域的访问 +- **函数式编程**:支持 map、filter、reduce + + +```java !! java +// Java - 函数是类方法 +public class Calculator { + public int add(int a, int b) { + return a + b; + } + + // 不能直接将方法作为参数传递 + // 需要接口或 lambda 表达式(Java 8+) +} +``` + +```javascript !! js +// JavaScript - 函数是一等公民 +const add = function(a, b) { + return a + b; +}; + +// 箭头函数(ES6+) +const multiply = (a, b) => a * b; + +// 函数可以作为参数传递 +const numbers = [1, 2, 3, 4, 5]; +const doubled = numbers.map(n => n * 2); + +// 高阶函数 +function withLogging(fn) { + return function(...args) { + console.log('Calling', fn.name); + return fn(...args); + }; +} +``` + + +#### 3. 基于原型的继承 +- **传统上没有类**:对象从其他对象继承 +- **原型**:每个对象都有一个原型链 +- **动态行为**:可以在运行时修改对象 +- **ES6 类**:原型之上的语法糖 + + +```java !! java +// Java - 基于类的继承 +public class Animal { + public void speak() { + System.out.println("Some sound"); + } +} + +public class Dog extends Animal { + @Override + public void speak() { + System.out.println("Woof!"); + } +} + +// 使用 +Animal dog = new Dog(); +dog.speak(); // "Woof!" +``` + +```javascript !! js +// JavaScript - 基于原型的继承 +const animal = { + speak() { + console.log("Some sound"); + } +}; + +const dog = Object.create(animal); +dog.speak = function() { + console.log("Woof!"); +}; + +dog.speak(); // "Woof!" + +// ES6 类语法(语法糖) +class Animal { + speak() { + console.log("Some sound"); + } +} + +class Dog extends Animal { + speak() { + console.log("Woof!"); + } +} + +const dog2 = new Dog(); +dog2.speak(); // "Woof!" +``` + + +#### 4. 事件驱动架构 +- **事件循环**:单线程与事件队列 +- **异步**:非阻塞 I/O 操作 +- **回调**:事件完成时调用的函数 +- **Promise/async-await**:现代异步处理 + + +```java !! java +// Java - 多线程 +public class DataLoader { + public String loadData() { + // 阻塞线程直到数据加载 + String data = fetchDataFromDatabase(); + return data; + } + + public static void main(String[] args) { + // 每个线程独立运行 + Thread thread1 = new Thread(() -> { + new DataLoader().loadData(); + }); + thread1.start(); + } +} +``` + +```javascript !! js +// JavaScript - 事件驱动,单线程 +function loadData() { + return new Promise((resolve) => { + // 非阻塞,继续执行 + setTimeout(() => { + resolve("Data loaded!"); + }, 1000); + }); +} + +// Async/await(现代方法) +async function main() { + console.log("Loading..."); + const data = await loadData(); + console.log(data); + // 所有代码在单线程上通过事件循环运行 +} + +main(); +``` + + +## 与 Java 的比较 + +| 特性 | Java | JavaScript | +|---------|------|------------| +| **范式** | 面向对象(基于类) | 多范式(基于原型、函数式) | +| **类型** | 静态(必须声明类型) | 动态(运行时确定类型) | +| **执行** | 编译为字节码,在 JVM 上运行 | 在浏览器或 Node.js 中解释/JIT 编译 | +| **环境** | JVM(服务器、桌面、Android) | 浏览器、Node.js(服务器)、各种嵌入环境 | +| **并发** | 多线程,共享内存 | 单线程与事件循环 | +| **继承** | 类继承(extends、implements) | 原型链(类语法是语法糖) | +| **函数** | 方法(附加到类) | 一等公民(可以传递) | +| **内存** | 手动对象生命周期,GC | GC,自动内存管理 | +| **部署** | JAR/WAR 文件,需要 JVM | 嵌入 HTML、npm 包、独立运行 | +| **包管理** | Maven、Gradle | npm、yarn、pnpm | +| **标准库** | 广泛、面向企业 | 较小、面向 Web | + +## 编译 vs 解释 + +### Java(编译为字节码) + +Java 代码经历编译过程: + +1. **源代码**(`.java` 文件)被编译为字节码 +2. **字节码**(`.class` 文件)在 JVM 上运行 +3. **JIT 编译器**优化热点代码路径 +4. **静态类型**在编译时捕获错误 + +**优点:** +- JIT 编译后执行速度快 +- 编译时错误检查 +- 强类型安全 +- 优化的性能 + +**缺点:** +- 需要编译步骤 +- 冗长的语法 +- 更大的内存占用 +- 启动时间较慢 + +### JavaScript(解释/JIT) + +JavaScript 的执行因环境而异: + +**在浏览器中:** +1. **源代码**立即解析和执行 +2. **JIT 编译**优化常用代码 +3. **动态类型**提供灵活性 +4. **事件循环**处理异步操作 + +**在 Node.js 中:** +1. **V8 引擎**编译为机器码 +2. **即时**优化 +3. **无浏览器 DOM** - 服务器端 API + + +```java !! java +// Java - 先编译后运行 +// 文件:HelloWorld.java +public class HelloWorld { + public static void main(String[] args) { + System.out.println("Hello, World!"); + } +} + +// 编译 +$ javac HelloWorld.java + +// 运行 +$ java HelloWorld +Hello, World! +``` + +```javascript !! js +// JavaScript - 直接运行(无需编译) +// 文件:hello.js +console.log("Hello, World!"); + +// 在浏览器中 + + +// 在 Node.js 中 +$ node hello.js +Hello, World! +``` + + +## JavaScript 的用例和优势 + +### 主要用例 + +#### 1. Web 前端开发 +JavaScript 对 Web 开发至关重要: + +- **DOM 操作**:交互式 Web 页面 +- **事件处理**:用户交互 +- **AJAX/Fetch**:动态数据加载 +- **单页应用**:React、Vue、Angular +- **渐进式 Web 应用**:离线功能 + + +```java !! java +// Java - 更新需要完整页面刷新 +@WebServlet("/users") +public class UserServlet extends HttpServlet { + protected void doGet(HttpServletRequest req, HttpServletResponse resp) { + // 服务器渲染整个 HTML 页面 + req.setAttribute("users", getUsers()); + req.getRequestDispatcher("/users.jsp").forward(req, resp); + } +} +``` + +```javascript !! js +// JavaScript - 无需刷新的动态更新 +// 异步获取用户数据 +async function loadUsers() { + const response = await fetch('/api/users'); + const users = await response.json(); + + // 动态更新 DOM + const userList = document.getElementById('user-list'); + userList.innerHTML = users.map(user => + `
  • ${user.name}
  • ` + ).join(''); +} + +// 调用无需页面刷新 +loadUsers(); +``` +
    + +#### 2. 后端开发(Node.js) +- **REST API**:Express、Fastify、Koa +- **实时应用**:WebSockets、Socket.io +- **微服务**:轻量级服务 +- **无服务器**:AWS Lambda、Cloud Functions + + +```java !! java +// Java - Spring Boot REST API +@RestController +@RequestMapping("/api") +public class UserController { + + @GetMapping("/users/{id}") + public ResponseEntity getUser(@PathVariable Long id) { + User user = userService.findById(id); + return ResponseEntity.ok(user); + } +} +``` + +```javascript !! js +// JavaScript - Node.js REST API(Express) +const express = require('express'); +const app = express(); + +app.get('/api/users/:id', (req, res) => { + const user = userService.findById(req.params.id); + res.json(user); +}); + +app.listen(3000, () => { + console.log('Server running on port 3000'); +}); +``` + + +#### 3. 移动开发 +- **React Native**:iOS 和 Android 应用 +- **Ionic**:混合移动应用 +- **Electron**:桌面应用 + +#### 4. 开发工具 +- **构建工具**:Webpack、Vite、esbuild +- **测试**:Jest、Cypress、Playwright +- **代码检查**:ESLint、Prettier +- **包管理**:npm、yarn + +### 关键优势 + +#### 1. 快速开发 +- **无需编译**:立即反馈 +- **动态类型**:更少的样板代码 +- **热重载**:即时更新 +- **巨大的生态系统**:npm 包 + + +```java !! java +// Java - 更多样板代码 +public class Person { + private String name; + private int age; + + public Person(String name, int age) { + this.name = name; + this.age = age; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + + @Override + public String toString() { + return "Person{name='" + name + "', age=" + age + "}"; + } +} +``` + +```javascript !! js +// JavaScript - 简洁 +class Person { + constructor(name, age) { + this.name = name; + this.age = age; + } + + toString() { + return `Person{name='${this.name}', age=${this.age}}`; + } +} + +// 或者使用对象字面量更简单 +const person = { name: "John", age: 30 }; +console.log(JSON.stringify(person)); +``` + + +#### 2. 通用语言 +- **前端和后端**:到处使用相同的语言 +- **代码共享**:在平台间共享代码 +- **全栈开发**:一个开发者,整个技术栈 +- **社区**:最大的开发者社区 + +#### 3. 灵活性 +- **多种范式**:OOP、函数式、过程式 +- **动态特性**:元编程、反射 +- **快速原型**:快速实验 +- **适应性强**:在任何地方工作 + +#### 4. 现代特性 +- **ES6+ 语法**:类、模块、箭头函数 +- **Async/await**:清晰的异步代码 +- **解构**:方便的数据提取 +- **展开运算符**:简单的对象/数组操作 + + +```java !! java +// Java - 传统方法 +List names = Arrays.asList("Alice", "Bob", "Charlie"); +List filtered = new ArrayList<>(); + +for (String name : names) { + if (name.startsWith("A")) { + filtered.add(name); + } +} + +Map nameLengths = new HashMap<>(); +for (String name : filtered) { + nameLengths.put(name, name.length()); +} +``` + +```javascript !! js +// JavaScript - 现代函数式方法 +const names = ["Alice", "Bob", "Charlie"]; + +const result = names + .filter(name => name.startsWith("A")) + .reduce((acc, name) => ({ + ...acc, + [name]: name.length + }), {}); + +console.log(result); // { Alice: 5 } +``` + + +## 开发环境设置 + +### 前提条件 + +无需特殊前提条件 - JavaScript 可以运行在: +- **任何现代浏览器**:Chrome、Firefox、Safari、Edge +- **Node.js**:用于服务器端开发 +- **操作系统**:Windows、macOS、Linux + +### 安装方法 + +#### 方法 1:浏览器控制台(最快开始) + +无需安装 - 只需打开浏览器: + +1. 打开 Chrome/Firefox/Safari +2. 按 `F12` 或 `Cmd+Option+I`(Mac)/ `Ctrl+Shift+I`(Windows) +3. 转到"Console"标签 +4. 开始编码! + +```javascript +// 在浏览器控制台中尝试 +console.log("Hello, JavaScript!"); +alert("Welcome to JS!"); +document.body.style.backgroundColor = "lightblue"; +``` + +#### 方法 2:Node.js(用于开发) + +**macOS/Linux:** +```bash +# 使用 Homebrew(macOS) +brew install node + +# 或使用 apt(Ubuntu/Debian) +sudo apt update +sudo apt install nodejs npm +``` + +**Windows:** +1. 从 https://nodejs.org/ 下载 +2. 运行安装程序 +3. 重启终端 + +**验证安装:** +```bash +node --version # 应该是 v18.x 或更高 +npm --version # 应该是 9.x 或更高 +``` + +#### 方法 3:在线游乐场(用于学习) + +- **CodePen**:https://codepen.io/pen/ +- **JSFiddle**:https://jsfiddle.net/ +- **Replit**:https://replit.com/ +- **StackBlitz**:https://stackblitz.com/ + +### 推荐工具 + +#### 代码编辑器 + +**Visual Studio Code(推荐):** +```bash +# 从 https://code.visualstudio.com/ 下载 +# 安装这些扩展: +- ESLint +- Prettier +- JavaScript (ES6) code snippets +- Node.js modules intellisense +``` + +**WebStorm(JetBrains):** +- 全功能 JavaScript IDE +- 内置调试器 +- 重构工具 +- 智能代码补全 + +#### 浏览器开发工具 + +**Chrome DevTools:** +- Elements:DOM 检查 +- Console:JavaScript 执行 +- Sources:调试 +- Network:HTTP 监控 +- Performance:性能分析 + +#### 包管理器 + +**npm(Node Package Manager):** +```bash +# 随 Node.js 一起安装 +npm init -y # 初始化项目 +npm install lodash # 安装包 +npm install --save-dev jest # 开发依赖 +``` + +**yarn(替代方案):** +```bash +npm install -g yarn # 全局安装 +yarn init # 初始化项目 +yarn add lodash # 安装包 +``` + +#### 构建工具 + +**Vite(现代、快速):** +```bash +npm create vite@latest my-app +cd my-app +npm install +npm run dev +``` + +**Webpack(灵活):** +```bash +npm init -y +npm install webpack webpack-cli --save-dev +``` + +## 你的第一个 JavaScript 程序 + + +```java !! java +// Java - 传统方法 +public class HelloWorld { + public static void main(String[] args) { + System.out.println("Hello, World!"); + + // 使用变量 + String name = "Java Developer"; + int years = 5; + + System.out.println("I'm a " + name + " with " + years + " years experience"); + } +} + +// 保存为 HelloWorld.java +// 编译:javac HelloWorld.java +// 运行:java HelloWorld +``` + +```javascript !! js +// JavaScript - 简单直接 +console.log("Hello, World!"); + +// 使用变量(无需类型声明) +const name = "Java Developer"; +const years = 5; + +// 模板字面量 +console.log(`I'm a ${name} with ${years} years experience`); + +// 在浏览器中,你也可以: +// alert("Hello, World!"); +// document.write("Hello, World!"); +``` + + +## JavaScript 版本(ES6+) + +本课程专注于**现代 JavaScript(ES6+)**: + +### ES2015 (ES6) - 主要版本 +- `let` 和 `const` 块级变量 +- 箭头函数 +- 类 +- 模板字面量 +- 解构 +- 展开运算符 +- Promise +- 模块 + +### ES2016-ES2020 - 增量更新 +- Array.includes (ES2016) +- Async/await (ES2017) +- 对象 rest/spread (ES2018) +- 可选链 (ES2020) +- 空值合并 (ES2020) + +### ES2021+ - 最新特性 +- 逻辑赋值运算符 +- 数字分隔符 +- String.replaceAll +- Promise.any +- 顶层 await + + +```javascript !! js +// ES5(旧 JavaScript - 我们不会重点讲这个) +var name = "John"; +var age = 25; + +function greet(name) { + return "Hello, " + name; +} + +var numbers = [1, 2, 3]; +var doubled = numbers.map(function(n) { + return n * 2; +}); + +// 构造函数 +function Person(name) { + this.name = name; +} + +Person.prototype.greet = function() { + console.log("Hello, " + this.name); +}; +``` + +```javascript !! js +// ES6+(现代 JavaScript - 我们将学习的内容) +const name = "John"; +const age = 25; + +const greet = (name) => `Hello, ${name}`; + +const numbers = [1, 2, 3]; +const doubled = numbers.map(n => n * 2); + +// 类语法 +class Person { + constructor(name) { + this.name = name; + } + + greet() { + console.log(`Hello, ${this.name}`); + } +} +``` + + +## 浏览器环境 vs Node.js + +### 浏览器 JavaScript +- **DOM API**:`document`、`window`、`querySelector` +- **Web API**:`fetch`、`localStorage`、`Canvas`、`WebGL` +- **无文件系统访问**(安全) +- **单页面上下文** + +### Node.js JavaScript +- **无 DOM**:没有 `document` 或 `window` +- **文件系统访问**:`fs` 模块 +- **HTTP 服务器**:`http`、`https` 模块 +- **CommonJS 模块**:`require()`(也支持 ES 模块) + + +```javascript !! js +// 浏览器 JavaScript +// 从 API 获取数据并更新 DOM +async function loadUser(userId) { + const response = await fetch(`/api/users/${userId}`); + const user = await response.json(); + + // 更新 DOM + document.getElementById('user-name').textContent = user.name; + document.getElementById('user-email').textContent = user.email; +} + +loadUser(123); +``` + +```javascript !! js +// Node.js(服务器端) +const fs = require('fs'); +const http = require('http'); + +// 创建 HTTP 服务器 +const server = http.createServer((req, res) => { + if (req.url === '/api/user') { + // 从文件系统读取 + fs.readFile('user.json', (err, data) => { + if (err) { + res.writeHead(500); + res.end('Error loading user'); + return; + } + + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(data); + }); + } +}); + +server.listen(3000); +``` + + +## Java 开发者的思维模型调整 + +### 关键思维转变 + +#### 1. 从编译时错误到运行时错误 + +**Java:** +```java +String name = "John"; +// name = 42; // 编译器立即捕获此错误 +``` + +**JavaScript:** +```javascript +let name = "John"; +name = 42; // 有效!但稍后可能导致问题 +console.log(name.toUpperCase()); // 运行时错误:name.toUpperCase is not a function +``` + +#### 2. 从类到原型 + +**Java:** +```java +// 一切都必须在类中 +public class Utils { + public static int add(int a, int b) { + return a + b; + } +} +``` + +**JavaScript:** +```javascript +// 函数可以独立存在 +function add(a, b) { + return a + b; +} + +// 或作为箭头函数 +const add = (a, b) => a + b; +``` + +#### 3. 从基于线程到基于事件的并发 + +**Java:** +```java +// 创建线程,并发运行 +new Thread(() -> { + doWork(); +}).start(); +``` + +**JavaScript:** +```javascript +// 使用事件循环、回调、promise +setTimeout(() => { + doWork(); +}, 1000); + +// 或更好的方式,使用 async/await +await new Promise(resolve => { + setTimeout(resolve, 1000); +}); +doWork(); +``` + +## JavaScript 常用术语 + +对于 Java 开发者,以下是关键的 JavaScript 术语: + +| 术语 | Java 等价物 | 描述 | +|------|----------------|-------------| +| **变量** | Variable | 值的存储(无需类型声明) | +| **函数** | Method | 可重用的代码块(不绑定到类) | +| **对象** | Instance/Map | 键值对,动态属性 | +| **数组** | List/ArrayList | 有序列表(可包含混合类型) | +| **Promise** | Future/CompletableFuture | 异步操作结果 | +| **回调** | Listener/Observer | 作为参数传递的函数 | +| **闭包** | Lambda with captured variables | 保留作用域的函数 | +| **模块** | Package/Import | 代码组织(ES6) | +| **npm** | Maven/Gradle | 包管理器 | +| **事件循环** | Executor/Dispatcher | 异步执行模型 | +| **DOM** | - | 文档对象模型(仅浏览器) | +| **JSON** | - | JavaScript 对象表示法(数据格式) | + +## 学习路径概述 + +本课程结构化地帮助 Java 开发者掌握 JavaScript: + +### 模块结构 + +**模块 1-5:基本语法** +- 变量声明(let、const、var) +- 数据类型和类型强制转换 +- 控制流(if/else、switch、循环) +- 函数(声明、表达式、箭头函数) +- 数组和数组方法 + +**模块 6-9:对象和类** +- 对象字面量和属性 +- ES6 类和继承 +- 原型和原型链 +- `this` 上下文和绑定 + +**模块 10-11:异步编程** +- 回调和事件循环 +- Promise 和 async/await +- 错误处理模式 + +**模块 12-15:前端开发** +- DOM 操作和 querySelector +- 事件处理和传播 +- ES6 模块和导入 +- 构建工具(webpack、Vite) + +**模块 16-19:工具和最佳实践** +- 测试框架(Jest、Cypress) +- 框架基础(React、Vue) +- 常见陷阱和反模式 +- 真实项目示例 + +## 练习 + +### 练习 1:浏览器控制台 +1. 打开浏览器的开发者控制台(F12) +2. 尝试这些命令: + ```javascript + console.log("Hello from console!"); + const name = "Java Developer"; + console.log(`Welcome, ${name}!`); + alert("You can do this!"); + ``` + +### 练习 2:Node.js 设置 +1. 如果还没有,安装 Node.js +2. 创建文件 `hello.js`: + ```javascript + console.log("Hello from Node.js!"); + ``` +3. 运行:`node hello.js` + +### 练习 3:现代 JavaScript +通过运行此代码比较 ES5 vs ES6 语法: +```javascript +// ES5 +var add = function(a, b) { + return a + b; +}; + +// ES6 +const addES6 = (a, b) => a + b; + +console.log(add(1, 2)); +console.log(addES6(1, 2)); +``` + +### 练习 4:思维模型转变 +思考这些差异: +1. 动态类型与静态类型感觉有何不同? +2. 一等函数提供什么优势? +3. 事件循环模型与 Java 线程有何不同? + +## 总结 + +JavaScript 为 Java 开发者提供了一种完全不同的编程范式: + +**关键差异:** +- **动态类型**:无需类型声明,运行时灵活性 +- **基于原型**:对象从对象继承,而不是类 +- **事件驱动**:单线程与事件循环,而不是多线程 +- **一等函数**:函数是值,可以传递 +- **无处不在**:运行在任何地方 - 浏览器、服务器、移动、桌面 + +**使 JavaScript 强大的特性:** +- 快速开发和即时反馈 +- 全栈开发的通用语言 +- 庞大的生态系统(npm 包) +- 现代语法(ES6+)使代码清晰易读 +- 可扩展应用的异步编程模型 + +**你将学到:** +- 现代 JavaScript(ES6+)语法和特性 +- 函数式和面向对象编程模式 +- 使用 Promise 和 async/await 的异步编程 +- 使用 DOM 操作的前端开发 +- 使用 Node.js 的后端开发 +- 最佳实践和常见模式 + +在接下来的模块中,我们将深入探讨 JavaScript 语法,从与 Java 的基本比较开始。准备好看看 JavaScript 的灵活性和简单性如何提高你的生产力! + +准备好开始学习 JavaScript 了吗?让我们从**模块 1:基本语法比较**开始! diff --git a/content/docs/java2js/module-00-js-introduction.zh-tw.mdx b/content/docs/java2js/module-00-js-introduction.zh-tw.mdx new file mode 100644 index 0000000..c300689 --- /dev/null +++ b/content/docs/java2js/module-00-js-introduction.zh-tw.mdx @@ -0,0 +1,1034 @@ +--- +title: "Module 0: JavaScript 語言簡介" +description: "了解 JavaScript 的歷史、設計理念,以及為 Java 開發者設定開發環境" +--- + +## JavaScript 語言歷史與設計理念 + +JavaScript 由 Brendan Eich 於 1995 年在 Netscape 公司創造(僅用了 10 天!)。最初命名為 Mocha,後來改名為 LiveScript,最終改名為 JavaScript 是為了利用當時 Java 的知名度。儘管名稱相似,JavaScript 和 Java 是完全不同的語言,有著不同的設計目標。 + +### 為何創建 JavaScript + +JavaScript 的出現是因為需要一種可在瀏覽器中運行的輕量級腳本語言: + +- **Java 太重**,無法處理簡單的網頁互動 +- **HTML 是靜態的**,缺乏互動性 +- **沒有客戶端邏輯**存在於網頁瀏覽器中 +- **網頁需要**無需伺服器往返的動態行為 + +JavaScript 的創造者希望有一種語言: +- 可以直接嵌入 HTML +- 對初學者來說容易學習 +- 為網頁提供動態行為 +- 跨不同瀏覽器運作 + +### JavaScript 的設計理念 + +JavaScript 的設計由以下核心原則指導: + +#### 1. 動態型別 +- **無需型別宣告**:變數可以保存任何型別 +- **執行時期型別檢查**:型別在執行過程中確定 +- **靈活轉換**:自動型別強制轉換 +- **快速原型設計**:更少的樣板程式碼 + + +```java !! java +// Java - 靜態型別 +String name = "John"; +int age = 25; +boolean isActive = true; + +// age = "twenty five"; // 編譯錯誤! +// 必須明確宣告型別 +``` + +```javascript !! js +// JavaScript - 動態型別 +let name = "John"; +let age = 25; +let isActive = true; + +age = "twenty five"; // 有效!型別可以改變 +// 無需型別宣告 +``` + + +#### 2. 一等公民函數 +- **函數是值**:可賦值給變數 +- **高階函數**:可接受並返回函數 +- **閉包**:函數保留對其作用域的訪問 +- **函數式程設**:支援 map、filter、reduce + + +```java !! java +// Java - 函數是類別方法 +public class Calculator { + public int add(int a, int b) { + return a + b; + } + + // 無法將方法作為參數傳遞 + // 需要介面或 Lambda 運算式 (Java 8+) +} +``` + +```javascript !! js +// JavaScript - 函數是一等公民 +const add = function(a, b) { + return a + b; +}; + +// 箭頭函數 (ES6+) +const multiply = (a, b) => a * b; + +// 函數可作為參數傳遞 +const numbers = [1, 2, 3, 4, 5]; +const doubled = numbers.map(n => n * 2); + +// 高階函數 +function withLogging(fn) { + return function(...args) { + console.log('Calling', fn.name); + return fn(...args); + }; +} +``` + + +#### 3. 原型基礎繼承 +- **無類別(傳統上)**:物件從其他物件繼承 +- **原型**:每個物件都有一個原型鏈 +- **動態行為**:可在執行時修改物件 +- **ES6 類別**:原型的語法糖 + + +```java !! java +// Java - 基於類別的繼承 +public class Animal { + public void speak() { + System.out.println("Some sound"); + } +} + +public class Dog extends Animal { + @Override + public void speak() { + System.out.println("Woof!"); + } +} + +// 使用 +Animal dog = new Dog(); +dog.speak(); // "Woof!" +``` + +```javascript !! js +// JavaScript - 基於原型的繼承 +const animal = { + speak() { + console.log("Some sound"); + } +}; + +const dog = Object.create(animal); +dog.speak = function() { + console.log("Woof!"); +}; + +dog.speak(); // "Woof!" + +// ES6 類別語法(語法糖) +class Animal { + speak() { + console.log("Some sound"); + } +} + +class Dog extends Animal { + speak() { + console.log("Woof!"); + } +} + +const dog2 = new Dog(); +dog2.speak(); // "Woof!" +``` + + +#### 4. 事件驅動架構 +- **事件迴圈**:帶事件佇列的單執行緒 +- **非同步**:非阻塞 I/O 操作 +- **回呼函數**:事件完成時呼叫的函數 +- **Promise/async-await**:現代非同步處理 + + +```java !! java +// Java - 多執行緒 +public class DataLoader { + public String loadData() { + // 阻塞執行緒直到資料載入完成 + String data = fetchDataFromDatabase(); + return data; + } + + public static void main(String[] args) { + // 每個執行緒獨立運行 + Thread thread1 = new Thread(() -> { + new DataLoader().loadData(); + }); + thread1.start(); + } +} +``` + +```javascript !! js +// JavaScript - 事件驅動,單執行緒 +function loadData() { + return new Promise((resolve) => { + // 非阻塞,繼續執行 + setTimeout(() => { + resolve("Data loaded!"); + }, 1000); + }); +} + +// Async/await(現代方法) +async function main() { + console.log("Loading..."); + const data = await loadData(); + console.log(data); + // 全部在單執行緒上配合事件迴圈運行 +} + +main(); +``` + + +## 與 Java 的比較 + +| 特性 | Java | JavaScript | +|---------|------|------------| +| **範式** | 物件導向(基於類別) | 多範式(基於原型、函數式) | +| **型別** | 靜態(必須宣告型別) | 動態(執行時確定型別) | +| **執行** | 編譯為位元組碼,在 JVM 上運行 | 在瀏覽器或 Node.js 中解譯/JIT 編譯 | +| **環境** | JVM(伺服器、桌面、Android) | 瀏覽器、Node.js(伺服器)、各種嵌入 | +| **並發** | 多執行緒與共享記憶體 | 單執行緒與事件迴圈 | +| **繼承** | 類別繼承(extends、implements) | 原型鏈(類別語法是糖) | +| **函數** | 方法(附加到類別) | 一等公民(可隨意傳遞) | +| **記憶體** | 手動物件生命週期、GC | GC、自動記憶體管理 | +| **部署** | JAR/WAR 檔案,需要 JVM | 嵌入 HTML、npm 套件、獨立 | +| **套件管理** | Maven、Gradle | npm、yarn、pnpm | +| **標準函式庫** | 豐富、企業級 | 較小、網頁導向 | + +## 編譯 vs 解譯 + +### Java(編譯為位元組碼) + +Java 程式碼經歷編譯過程: + +1. **原始碼**(`.java` 檔案)被編譯為位元組碼 +2. **位元組碼**(`.class` 檔案)在 JVM 上運行 +3. **JIT 編譯器**優化熱程式碼路徑 +4. **靜態型別**在編譯時捕捉錯誤 + +**優點:** +- JIT 編譯後執行快速 +- 編譯時期錯誤檢查 +- 強型別安全 +- 優化的效能 + +**缺點:** +- 需要編譯步驟 +- 冗長的語法 +- 較重的記憶體佔用 +- 啟動時間較慢 + +### JavaScript(解譯/JIT) + +JavaScript 執行因環境而異: + +**在瀏覽器中:** +1. **原始碼**立即被解析和執行 +2. **JIT 編譯**優化經常使用的程式碼 +3. **動態型別**提供靈活性 +4. **事件迴圈**處理非同步操作 + +**在 Node.js 中:** +1. **V8 引擎**編譯為機器碼 +2. **即時**優化 +3. **無瀏覽器 DOM** - 伺服器端 API + + +```java !! java +// Java - 先編譯後執行 +// 檔案:HelloWorld.java +public class HelloWorld { + public static void main(String[] args) { + System.out.println("Hello, World!"); + } +} + +// 編譯 +$ javac HelloWorld.java + +// 執行 +$ java HelloWorld +Hello, World! +``` + +```javascript !! js +// JavaScript - 直接執行(無需編譯) +// 檔案:hello.js +console.log("Hello, World!"); + +// 在瀏覽器中 + + +// 在 Node.js 中 +$ node hello.js +Hello, World! +``` + + +## JavaScript 的使用案例與優勢 + +### 主要使用案例 + +#### 1. 網頁前端開發 +JavaScript 對於網頁開發至關重要: + +- **DOM 操作**:互動式網頁 +- **事件處理**:使用者互動 +- **AJAX/Fetch**:動態資料載入 +- **單頁應用**:React、Vue、Angular +- **漸進式網頁應用**:離線功能 + + +```java !! java +// Java - 需要完整頁面重新載入以更新 +@WebServlet("/users") +public class UserServlet extends HttpServlet { + protected void doGet(HttpServletRequest req, HttpServletResponse resp) { + // 伺服器渲染整個 HTML 頁面 + req.setAttribute("users", getUsers()); + req.getRequestDispatcher("/users.jsp").forward(req, resp); + } +} +``` + +```javascript !! js +// JavaScript - 無需重新載入的動態更新 +// 非同步獲取使用者資料 +async function loadUsers() { + const response = await fetch('/api/users'); + const users = await response.json(); + + // 動態更新 DOM + const userList = document.getElementById('user-list'); + userList.innerHTML = users.map(user => + `
  • ${user.name}
  • ` + ).join(''); +} + +// 呼叫無需頁面重新載入 +loadUsers(); +``` +
    + +#### 2. 後端開發(Node.js) +- **REST API**:Express、Fastify、Koa +- **即時應用**:WebSockets、Socket.io +- **微服務**:輕量級服務 +- **無伺服器**:AWS Lambda、Cloud Functions + + +```java !! java +// Java - Spring Boot REST API +@RestController +@RequestMapping("/api") +public class UserController { + + @GetMapping("/users/{id}") + public ResponseEntity getUser(@PathVariable Long id) { + User user = userService.findById(id); + return ResponseEntity.ok(user); + } +} +``` + +```javascript !! js +// JavaScript - Node.js REST API (Express) +const express = require('express'); +const app = express(); + +app.get('/api/users/:id', (req, res) => { + const user = userService.findById(req.params.id); + res.json(user); +}); + +app.listen(3000, () => { + console.log('Server running on port 3000'); +}); +``` + + +#### 3. 行動裝置開發 +- **React Native**:iOS 和 Android 應用 +- **Ionic**:混合行動應用 +- **Electron**:桌面應用程式 + +#### 4. 開發者工具 +- **建置工具**:Webpack、Vite、esbuild +- **測試**:Jest、Cypress、Playwright +- **Linting**:ESLint、Prettier +- **套件管理**:npm、yarn + +### 主要優勢 + +#### 1. 快速開發 +- **無需編譯**:立即回饋 +- **動態型別**:更少的樣板程式碼 +- **熱重載**:即時更新 +- **龐大的生態系**:npm 套件 + + +```java !! java +// Java - 更多樣板程式碼 +public class Person { + private String name; + private int age; + + public Person(String name, int age) { + this.name = name; + this.age = age; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + + @Override + public String toString() { + return "Person{name='" + name + "', age=" + age + "}"; + } +} +``` + +```javascript !! js +// JavaScript - 簡潔 +class Person { + constructor(name, age) { + this.name = name; + this.age = age; + } + + toString() { + return `Person{name='${this.name}', age=${this.age}}`; + } +} + +// 或使用物件字面量更簡單 +const person = { name: "John", age: 30 }; +console.log(JSON.stringify(person)); +``` + + +#### 2. 通用語言 +- **前端和後端**:到處使用相同的語言 +- **程式碼共享**:在平台間共享程式碼 +- **全端開發**:一個開發者,完整技術堆疊 +- **社群**:最大的開發者社群 + +#### 3. 靈活性 +- **多種範式**:OOP、函數式、程序式 +- **動態功能**:元程設計、反射 +- **快速原型設計**:快速實驗 +- **適應性強**:在任何地方運作 + +#### 4. 現代功能 +- **ES6+ 語法**:類別、模組、箭頭函數 +- **Async/await**:簡潔的非同步程式碼 +- **解構賦值**:方便的資料提取 +- **展開運算子**:簡單的物件/陣列操作 + + +```java !! java +// Java - 傳統方法 +List names = Arrays.asList("Alice", "Bob", "Charlie"); +List filtered = new ArrayList<>(); + +for (String name : names) { + if (name.startsWith("A")) { + filtered.add(name); + } +} + +Map nameLengths = new HashMap<>(); +for (String name : filtered) { + nameLengths.put(name, name.length()); +} +``` + +```javascript !! js +// JavaScript - 現代函數式方法 +const names = ["Alice", "Bob", "Charlie"]; + +const result = names + .filter(name => name.startsWith("A")) + .reduce((acc, name) => ({ + ...acc, + [name]: name.length + }), {}); + +console.log(result); // { Alice: 5 } +``` + + +## 開發環境設定 + +### 前置需求 + +無需特殊前置需求 - JavaScript 可運作於: +- **任何現代瀏覽器**:Chrome、Firefox、Safari、Edge +- **Node.js**:用於伺服器端開發 +- **作業系統**:Windows、macOS、Linux + +### 安裝方法 + +#### 方法 1:瀏覽器主控台(最快開始) + +無需安裝 - 只需打開瀏覽器: + +1. 開啟 Chrome/Firefox/Safari +2. 按 `F12` 或 `Cmd+Option+I`(Mac)/ `Ctrl+Shift+I`(Windows) +3. 前往「主控台」分頁 +4. 開始編寫程式碼! + +```javascript +// 在瀏覽器主控台中嘗試這些 +console.log("Hello, JavaScript!"); +alert("Welcome to JS!"); +document.body.style.backgroundColor = "lightblue"; +``` + +#### 方法 2:Node.js(用於開發) + +**macOS/Linux:** +```bash +# 使用 Homebrew (macOS) +brew install node + +# 或使用 apt (Ubuntu/Debian) +sudo apt update +sudo apt install nodejs npm +``` + +**Windows:** +1. 從 https://nodejs.org/ 下載 +2. 執行安裝程式 +3. 重新啟動終端機 + +**驗證安裝:** +```bash +node --version # 應該是 v18.x 或更高 +npm --version # 應該是 9.x 或更高 +``` + +#### 方法 3:線上遊樂場(用於學習) + +- **CodePen**:https://codepen.io/pen/ +- **JSFiddle**:https://jsfiddle.net/ +- **Replit**:https://replit.com/ +- **StackBlitz**:https://stackblitz.com/ + +### 推薦工具 + +#### 程式碼編輯器 + +**Visual Studio Code(推薦):** +```bash +# 從 https://code.visualstudio.com/ 下載 +# 安裝這些擴充功能: +- ESLint +- Prettier +- JavaScript (ES6) code snippets +- Node.js modules intellisense +``` + +**WebStorm(JetBrains):** +- 功能完整的 JavaScript IDE +- 內建除錯器 +- 重構工具 +- 智慧程式碼補全 + +#### 瀏覽器開發者工具 + +**Chrome DevTools:** +- Elements:DOM 檢查 +- Console:JavaScript 執行 +- Sources:除錯 +- Network:HTTP 監控 +- Performance:效能分析 + +#### 套件管理器 + +**npm(Node 套件管理器):** +```bash +# 隨 Node.js 一起提供 +npm init -y # 初始化專案 +npm install lodash # 安裝套件 +npm install --save-dev jest # 開發相依性 +``` + +**yarn(替代方案):** +```bash +npm install -g yarn # 全局安裝 +yarn init # 初始化專案 +yarn add lodash # 安裝套件 +``` + +#### 建置工具 + +**Vite(現代、快速):** +```bash +npm create vite@latest my-app +cd my-app +npm install +npm run dev +``` + +**Webpack(靈活):** +```bash +npm init -y +npm install webpack webpack-cli --save-dev +``` + +### 你的第一個 JavaScript 程式 + + +```java !! java +// Java - 傳統方法 +public class HelloWorld { + public static void main(String[] args) { + System.out.println("Hello, World!"); + + // 使用變數 + String name = "Java Developer"; + int years = 5; + + System.out.println("I'm a " + name + " with " + years + " years experience"); + } +} + +// 儲存為 HelloWorld.java +// 編譯:javac HelloWorld.java +// 執行:java HelloWorld +``` + +```javascript !! js +// JavaScript - 簡單直接 +console.log("Hello, World!"); + +// 使用變數(無需型別宣告) +const name = "Java Developer"; +const years = 5; + +// 模板字面量 +console.log(`I'm a ${name} with ${years} years experience`); + +// 在瀏覽器中,你也可以: +// alert("Hello, World!"); +// document.write("Hello, World!"); +``` + + +### 設定專案 + +**初始化 Node.js 專案:** +```bash +# 建立專案目錄 +mkdir my-js-project +cd my-js-project + +# 初始化 package.json +npm init -y + +# 建立 index.js +touch index.js + +# 新增至 index.js +console.log("Hello from Node.js!"); + +# 執行 +node index.js +``` + +**package.json:** +```json +{ + "name": "my-js-project", + "version": "1.0.0", + "description": "My JavaScript project", + "main": "index.js", + "scripts": { + "start": "node index.js", + "dev": "node --watch index.js", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC" +} +``` + +## JavaScript 版本(ES6+) + +本課程聚焦於**現代 JavaScript(ES6+)**: + +### ES2015 (ES6) - 重大版本 +- `let` 和 `const` 區塊作用域變數 +- 箭頭函數 +- 類別 +- 模板字面量 +- 解構賦值 +- 展開運算子 +- Promise +- 模組 + +### ES2016-ES2020 - 漸進更新 +- Array.includes (ES2016) +- Async/await (ES2017) +- 物件 rest/spread (ES2018) +- 選擇鏈 (ES2020) +- 空值合併 (ES2020) + +### ES2021+ - 最新功能 +- 邏輯指派運算子 +- 數字分隔符 +- String.replaceAll +- Promise.any +- 頂層 await + + +```javascript !! js +// ES5(舊 JavaScript - 我們不會重點講解) +var name = "John"; +var age = 25; + +function greet(name) { + return "Hello, " + name; +} + +var numbers = [1, 2, 3]; +var doubled = numbers.map(function(n) { + return n * 2; +}); + +// 建構函式 +function Person(name) { + this.name = name; +} + +Person.prototype.greet = function() { + console.log("Hello, " + this.name); +}; +``` + +```javascript !! js +// ES6+(現代 JavaScript - 我們將學習的) +const name = "John"; +const age = 25; + +const greet = (name) => `Hello, ${name}`; + +const numbers = [1, 2, 3]; +const doubled = numbers.map(n => n * 2); + +// 類別語法 +class Person { + constructor(name) { + this.name = name; + } + + greet() { + console.log(`Hello, ${this.name}`); + } +} +``` + + +## 瀏覽器環境 vs Node.js + +### 瀏覽器 JavaScript +- **DOM API**:`document`、`window`、`querySelector` +- **Web API**:`fetch`、`localStorage`、`Canvas`、`WebGL` +- **無檔案系統訪問**(安全性) +- **單頁面上下文** + +### Node.js JavaScript +- **無 DOM**:無 `document` 或 `window` +- **檔案系統訪問**:`fs` 模組 +- **HTTP 伺服器**:`http`、`https` 模組 +- **CommonJS 模組**:`require()`(也支援 ES 模組) + + +```javascript !! js +// 瀏覽器 JavaScript +// 從 API 獲取資料並更新 DOM +async function loadUser(userId) { + const response = await fetch(`/api/users/${userId}`); + const user = await response.json(); + + // 更新 DOM + document.getElementById('user-name').textContent = user.name; + document.getElementById('user-email').textContent = user.email; +} + +loadUser(123); +``` + +```javascript !! js +// Node.js(伺服器端) +const fs = require('fs'); +const http = require('http'); + +// 建立 HTTP 伺服器 +const server = http.createServer((req, res) => { + if (req.url === '/api/user') { + // 從檔案系統讀取 + fs.readFile('user.json', (err, data) => { + if (err) { + res.writeHead(500); + res.end('Error loading user'); + return; + } + + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(data); + }); + } +}); + +server.listen(3000); +``` + + +## Java 開發者的思維模型調整 + +### 關鍵思維轉變 + +#### 1. 從編譯時期錯誤到執行時期錯誤 + +**Java:** +```java +String name = "John"; +// name = 42; // 編譯器立即捕捉此錯誤 +``` + +**JavaScript:** +```javascript +let name = "John"; +name = 42; // 有效!但可能稍後導致問題 +console.log(name.toUpperCase()); // 執行時期錯誤:name.toUpperCase 不是一個函數 +``` + +#### 2. 從類別到原型 + +**Java:** +```java +// 一切必須在類別中 +public class Utils { + public static int add(int a, int b) { + return a + b; + } +} +``` + +**JavaScript:** +```javascript +// 函數可以獨立存在 +function add(a, b) { + return a + b; +} + +// 或作為箭頭函數 +const add = (a, b) => a + b; +``` + +#### 3. 從基於執行緒到基於事件的並發 + +**Java:** +```java +// 建立執行緒,並發執行 +new Thread(() -> { + doWork(); +}).start(); +``` + +**JavaScript:** +```javascript +// 使用事件迴圈、回呼、promise +setTimeout(() => { + doWork(); +}, 1000); + +// 或更好的方式,使用 async/await +await new Promise(resolve => { + setTimeout(resolve, 1000); +}); +doWork(); +``` + +#### 4. 從明確型別到型別推斷 + +**Java:** +```java +Map> data = new HashMap<>(); +List names = data.get("key"); +``` + +**JavaScript:** +```javascript +const data = {}; // 無型別註解 +const names = data.key; // 使用點號訪問 +// 或:const names = data["key"]; +``` + +## 常見 JavaScript 術語 + +對於 Java 開發者,以下是關鍵的 JavaScript 術語: + +| 術語 | Java 等效 | 說明 | +|------|----------------|-------------| +| **變數** | Variable | 值的儲存(無型別宣告) | +| **函數** | Method | 可重複使用的程式碼區塊(不綁定到類別) | +| **物件** | Instance/Map | 鍵值對,動態屬性 | +| **陣列** | List/ArrayList | 有序列表(可包含混合型別) | +| **Promise** | Future/CompletableFuture | 非同步操作結果 | +| **回呼** | Listener/Observer | 作為參數傳遞的函數 | +| **閉包** | Lambda 與捕獲變數 | 保留作用域的函數 | +| **模組** | Package/Import | 程式碼組織(ES6) | +| **npm** | Maven/Gradle | 套件管理器 | +| **事件迴圈** | Executor/Dispatcher | 非同步執行模型 | +| **DOM** | - | 文件物件模型(僅瀏覽器) | +| **JSON** | - | JavaScript 物件表示法(資料格式) | + +## 學習路徑概覽 + +本課程結構化以幫助 Java 開發者掌握 JavaScript: + +### 模組結構 + +**模組 1-5:基本語法** +- 變數宣告(let、const、var) +- 資料型別和型別強制轉換 +- 控制流程(if/else、switch、迴圈) +- 函數(宣告、運算式、箭頭) +- 陣列和陣列方法 + +**模組 6-9:物件與類別** +- 物件字面量和屬性 +- ES6 類別和繼承 +- 原型和原型鏈 +- `this` 上下文和綁定 + +**模組 10-11:非同步程設** +- 回呼和事件迴圈 +- Promise 和 async/await +- 錯誤處理模式 + +**模組 12-15:前端開發** +- DOM 操作和 querySelector +- 事件處理和傳播 +- ES6 模組和匯入 +- 建置工具(webpack、Vite) + +**模組 16-19:工具與最佳實踐** +- 測試框架(Jest、Cypress) +- 框架基礎(React、Vue) +- 常見陷阱和反模式 +- 真實專案範例 + +## 練習 + +### 練習 1:瀏覽器主控台 +1. 開啟瀏覽器的開發者主控台(F12) +2. 嘗試這些指令: + ```javascript + console.log("Hello from console!"); + const name = "Java Developer"; + console.log(`Welcome, ${name}!`); + alert("You can do this!"); + ``` + +### 練習 2:Node.js 設定 +1. 如果尚未安裝 Node.js,請先安裝 +2. 建立一個檔案 `hello.js`: + ```javascript + console.log("Hello from Node.js!"); + ``` +3. 執行它:`node hello.js` + +### 練習 3:現代 JavaScript +透過執行以下程式碼來比較 ES5 vs ES6 語法: +```javascript +// ES5 +var add = function(a, b) { + return a + b; +}; + +// ES6 +const addES6 = (a, b) => a + b; + +console.log(add(1, 2)); +console.log(addES6(1, 2)); +``` + +### 練習 4:思維模型轉變 +反思這些差異: +1. 動態型別與靜態型別的感覺有何不同? +2. 一等公民函數提供什麼優勢? +3. 事件迴圈模型與 Java 執行緒有何不同? + +## 總結 + +JavaScript 為 Java 開發者提供了一種完全不同的程設範式: + +**關鍵差異:** +- **動態型別**:無型別宣告,執行時期靈活性 +- **基於原型**:物件從物件繼承,而非類別 +- **事件驅動**:單執行緒與事件迴圈,而非多執行緒 +- **一等公民函數**:函數是值,可隨意傳遞 +- **無所不在**:在瀏覽器、伺服器、行動裝置、桌面任何地方運行 + +**JavaScript 的強大之處:** +- 快速開發並立即回饋 +- 全端開發的通用語言 +- 龐大的生態系(npm 套件) +- 現代語法(ES6+)使程式碼簡潔易讀 +- 可擴展應用的非同步程設模型 + +**你將學到:** +- 現代 JavaScript(ES6+)語法和功能 +- 函數式和物件導向程式設計模式 +- Promise 和 async/await 的非同步程設 +- DOM 操作的前端開發 +- Node.js 的後端開發 +- 最佳實踐和常見模式 + +在接下來的模組中,我們將深入探討 JavaScript 語法,從與 Java 的基本比較開始。準備好看 JavaScript 的靈活性和簡單性如何提高你的生產力! + +準備好開始學習 JavaScript 了嗎?讓我們從**模組 1:基本語法比較**開始吧! diff --git a/content/docs/java2js/module-01-basic-syntax.mdx b/content/docs/java2js/module-01-basic-syntax.mdx new file mode 100644 index 0000000..9540098 --- /dev/null +++ b/content/docs/java2js/module-01-basic-syntax.mdx @@ -0,0 +1,1031 @@ +--- +title: "Module 1: Basic Syntax Comparison" +description: "Learn JavaScript variables, data types, operators, and basic syntax compared to Java" +--- + +## Module 1: Basic Syntax Comparison + +Welcome to your first deep dive into JavaScript syntax! As a Java developer, you'll find many familiar concepts, but JavaScript has some important differences that make it unique. + +## Learning Objectives + +By the end of this module, you will: +✅ Understand JavaScript variable declarations (var, let, const) +✅ Know the differences between Java's static typing and JavaScript's dynamic typing +✅ Master JavaScript's primitive and reference types +✅ Understand type coercion and how to avoid common pitfalls +✅ Learn JavaScript operators and their quirks + +## Variable Declarations + +### Java Variables + +In Java, variables are explicitly typed: + + +```java !! java +// Java - Explicit type declarations +String name = "John"; +int age = 25; +double salary = 50000.50; +boolean isActive = true; +char grade = 'A'; + +// Final variable (cannot be reassigned) +final String CITY = "New York"; +// CITY = "Boston"; // Compilation error! + +// Null values +String middleName = null; + +// Type inference with var (Java 10+) +var username = "johndoe"; // Type is inferred as String +// username = 42; // Compilation error! +``` + +```javascript !! js +// JavaScript - No type declarations +let name = "John"; +let age = 25; +let salary = 50000.50; +let isActive = true; + +// Constants (cannot be reassigned) +const CITY = "New York"; +// CITY = "Boston"; // TypeError: Assignment to constant variable + +// No explicit null declaration needed +let middleName = null; +let middleName2 = undefined; + +// Type is dynamic +let username = "johndoe"; +username = 42; // Valid! Type changes to number +username = true; // Now it's a boolean +``` + + +### JavaScript Variable Keywords + +JavaScript has three ways to declare variables: + +#### 1. `var` (Old Way - Avoid in Modern Code) + + +```javascript !! js +// var - function-scoped (NOT block-scoped) +function example() { + if (true) { + var x = 10; + } + console.log(x); // 10 - accessible outside if block! +} + +// var can be redeclared +var name = "John"; +var name = "Jane"; // No error! + +// var hoisting +console.log(y); // undefined (not an error!) +var y = 5; + +// This is equivalent to: +var y; // Declaration hoisted to top +console.log(y); +y = 5; // Assignment stays here + +// DON'T USE var IN MODERN CODE! +// Use let and const instead +``` + + +#### 2. `let` (Modern Way for Variables That Change) + + +```javascript !! js +// let - block-scoped (like Java) +function example() { + if (true) { + let x = 10; + console.log(x); // 10 + } + console.log(x); // ReferenceError: x is not defined +} + +// let cannot be redeclared in same scope +let name = "John"; +// let name = "Jane"; // SyntaxError! + +// But can be reassigned +let age = 25; +age = 26; // Valid + +// let with for loops (block-scoped) +for (let i = 0; i < 3; i++) { + setTimeout(() => console.log(i), 100); +} +// Output: 0, 1, 2 (each iteration has its own i) + +// Compare with var (function-scoped) +for (var j = 0; j < 3; j++) { + setTimeout(() => console.log(j), 100); +} +// Output: 3, 3, 3 (shared j reference) +``` + + +#### 3. `const` (Modern Way for Constants) + + +```javascript !! js +// const - block-scoped, cannot be reassigned +const PI = 3.14159; +// PI = 3.14; // TypeError: Assignment to constant variable + +// Must be initialized +const name; // SyntaxError: Missing initializer + +// Objects and arrays can still be modified (not reassigned) +const person = { name: "John", age: 25 }; +person.name = "Jane"; // Valid! Object is modified +person.age = 26; // Valid! + +// person = {}; // TypeError: Cannot reassign const + +const numbers = [1, 2, 3]; +numbers.push(4); // Valid! Array is modified +// numbers = []; // TypeError: Cannot reassign const + +// To prevent modifications, use Object.freeze() +const frozenPerson = Object.freeze({ name: "John", age: 25 }); +frozenPerson.name = "Jane"; // Silently fails (strict mode: error) +console.log(frozenPerson.name); // "John" +``` + + +### Best Practice: When to Use What + + +```java !! java +// Java - Always use final when possible +public class User { + private final String name; // Use final for constants + private int age; // Use regular for mutable fields + + public User(String name) { + this.name = name; // Assign once + } + + public void setAge(int age) { + this.age = age; // Can change + } +} +``` + +```javascript !! js +// JavaScript - Prefer const, use let when needed +// 1. Use const by default (most variables don't need to change) +const API_URL = "https://api.example.com"; +const MAX_RETRIES = 3; + +// 2. Use let when you need to reassign +let counter = 0; +counter++; + +let userData = null; +userData = fetchUserData(); + +// 3. Avoid var entirely (it's function-scoped and confusing) +// ❌ Don't do this: +var i = 0; + +// ✅ Do this instead: +let i = 0; +``` + + +## Data Types + +### Primitive Types + +JavaScript has 7 primitive types (Java has 8): + + +```java !! java +// Java primitives +byte b = 127; // 8-bit integer +short s = 32767; // 16-bit integer +int i = 2147483647; // 32-bit integer +long l = 9223372036854775807L; // 64-bit integer +float f = 3.14f; // 32-bit floating point +double d = 3.14159265359; // 64-bit floating point +char c = 'A'; // 16-bit Unicode character +boolean bool = true; // true or false + +// Java also has void (no value) +``` + +```javascript !! js +// JavaScript primitives (simpler!) +let num = 42; // Number (all numbers are 64-bit floats) +let pi = 3.14159265359; // Number (no int/float distinction) +let bigInt = 9007199254740991n; // BigInt (for large integers) + +let str = "Hello"; // String (can use " or ' or `) +let char = 'A'; // Still a String (single chars are strings) + +let bool = true; // Boolean (true or false) + +let nothing = null; // Null (intentional absence) +let undefinedVar = undefined; // Undefined (unintentional absence) + +let symbol = Symbol('id'); // Symbol (unique identifier, ES6+) +``` + + +### Number Type + +JavaScript has only one numeric type (unlike Java): + + +```java !! java +// Java - Multiple numeric types +int integer = 42; +double decimal = 3.14; +long bigNumber = 9007199254740991L; + +// Type promotion +int a = 5; +int b = 2; +int result = a / b; // result = 2 (integer division) + +// Explicit casting +double x = 3.7; +int y = (int) x; // y = 3 (truncates) +``` + +```javascript !! js +// JavaScript - Single Number type (IEEE 754 64-bit float) +let integer = 42; +let decimal = 3.14; +let scientific = 1.5e-4; // 0.00015 +let hex = 0xFF; // 255 +let binary = 0b1010; // 10 +let octal = 0o755; // 493 + +// Division always produces float +let a = 5; +let b = 2; +let result = a / b; // result = 2.5 (not 2!) + +// Number methods +let num = 42.123; +console.log(num.toFixed(2)); // "42.12" +console.log(Math.round(num)); // 42 +console.log(Math.floor(num)); // 42 + +// Special numeric values +let infinity = Infinity; +let negInfinity = -Infinity; +let notANumber = NaN; // Not-a-Number + +console.log(0 / 0); // NaN +console.log(1 / 0); // Infinity +console.log(Math.sqrt(-1)); // NaN + +// Check for NaN (important!) +console.log(NaN === NaN); // false (NaN is not equal to itself!) +console.log(Number.isNaN(NaN)); // true +console.log(Number.isFinite(42)); // true +console.log(Number.isFinite(Infinity)); // false +``` + + +### String Type + +JavaScript strings are similar to Java strings but with more flexibility: + + +```java !! java +// Java strings are immutable +String name = "John"; +name.toUpperCase(); // Returns "JOHN" but name is still "John" +name = name.toUpperCase(); // Now name is "JOHN" + +// String concatenation +String greeting = "Hello, " + name; + +// Multiline strings (Java 15+) +String multiline = """ + This is a + multiline string + """; + +// String methods +String text = "JavaScript"; +System.out.println(text.length()); // 10 +System.out.println(text.substring(0, 4)); // "Java" +System.out.println(text.contains("Script")); // true +``` + +```javascript !! js +// JavaScript strings are immutable (same as Java) +let name = "John"; +name.toUpperCase(); // Returns "JOHN" but name is still "John" +name = name.toUpperCase(); // Now name is "JOHN" + +// String concatenation (multiple ways) +let greeting = "Hello, " + name; +let greeting2 = `Hello, ${name}`; // Template literal (preferred) + +// Multiline strings (template literals) +const multiline = `This is a +multiline string`; + +// String methods +const text = "JavaScript"; +console.log(text.length); // 10 +console.log(text.substring(0, 4)); // "Java" +console.log(text.includes("Script")); // true +console.log(text.startsWith("Java")); // true +console.log(text.endsWith("Script")); // true +console.log(text.split('a')); // ["J", "v", "Script"] + +// String methods that return modified strings +console.log(text.toUpperCase()); // "JAVASCRIPT" +console.log(text.toLowerCase()); // "javascript" +console.log(text.trim()); // Removes whitespace +console.log(text.replace("Java", "ECMA")); // "ECMAScript" + +// Template literals with expressions +const x = 10; +const y = 20; +console.log(`${x} + ${y} = ${x + y}`); // "10 + 20 = 30" + +// Tagged template literals (advanced) +function highlight(strings, ...values) { + return strings.reduce((result, str, i) => { + return result + str + (values[i] || ''); + }, ''); +} +const name2 = "John"; +const age = 25; +console.log(highlight`Name: ${name2}, Age: ${age}`); +``` + + +### Boolean Type and Truthy/Falsy Values + +This is a major difference from Java! + + +```java !! java +// Java - Strict boolean checking +boolean isActive = true; +boolean isInactive = false; + +// Only boolean expressions can be used in conditionals +String name = "John"; +if (name != null && !name.isEmpty()) { // Must check explicitly + System.out.println("Name exists"); +} + +// No truthy/falsy - must be explicit +int count = 0; +if (count > 0) { // Cannot just use "if (count)" + System.out.println("Count is positive"); +} +``` + +```javascript !! js +// JavaScript - Boolean plus truthy/falsy values +let isActive = true; +let isInactive = false; + +// Falsy values (evaluate to false in boolean context): +false, 0, -0, 0n, "", null, undefined, NaN + +// Truthy values (everything else): +true, 42, "hello", {}, [], [], function() {} + +// Short-circuit evaluation +const name = "John"; +if (name) { // name is truthy (non-empty string) + console.log("Name exists"); +} + +const count = 0; +if (count) { // count is falsy (zero) + console.log("Count is positive"); +} else { + console.log("Count is zero or negative"); +} + +// Common pattern: default values +function greet(name) { + // If name is falsy, use "Guest" + const displayName = name || "Guest"; + console.log(`Hello, ${displayName}`); +} + +greet("John"); // "Hello, John" +greet(""); // "Hello, Guest" +greet(null); // "Hello, Guest" + +// Nullish coalescing operator (ES2020) +// Only checks for null/undefined (not 0 or "") +function greet2(name) { + const displayName = name ?? "Guest"; + console.log(`Hello, ${displayName}`); +} + +greet2(""); // "Hello, " (empty string is not null/undefined) +greet2(null); // "Hello, Guest" + +// Boolean conversion +console.log(Boolean("hello")); // true +console.log(Boolean("")); // false +console.log(Boolean(42)); // true +console.log(Boolean(0)); // false +console.log.Boolean(Boolean({})); // true (empty object is truthy!) +console.log(Boolean([])); // true (empty array is truthy!) +``` + + +### Null vs Undefined + +Two ways to represent "no value": + + +```java !! java +// Java - Only null +String name = null; +int[] numbers = null; + +// Cannot have undefined - everything is initialized +// int x; // Must be initialized before use +int x = 0; // Default value for primitives + +// Optional type (Java 8+) +Optional optionalName = Optional.of("John"); +Optional emptyOptional = Optional.empty(); +``` + +```javascript !! js +// JavaScript - Both null and undefined + +// undefined: Variable declared but not assigned +let x; +console.log(x); // undefined + +// null: Intentionally set to no value +let y = null; +console.log(y); // null + +// Key differences: +console.log(typeof undefined); // "undefined" +console.log(typeof null); // "object" (historical bug!) + +// Converting to number +console.log(Number(undefined)); // NaN +console.log(Number(null)); // 0 + +// Loose equality (avoid this!) +console.log(null == undefined); // true +console.log(null === undefined); // false (use strict equality) + +// Best practice: +// Use null for "intentionally empty" +function getUser(id) { + const user = database.find(id); + return user !== undefined ? user : null; +} + +// Use undefined for "not set" +function process(options) { + // If options.timeout is not provided + if (options.timeout === undefined) { + options.timeout = 5000; // Use default + } +} + +// Checking for null/undefined +function safeGet(obj) { + // Old way + if (obj !== null && obj !== undefined) { + return obj.value; + } + + // Modern way (checks for both null and undefined) + if (obj != null) { // Loose equality works here + return obj.value; + } + + // Or using nullish coalescing + return obj?.value; +} +``` + + +## Type Coercion + +JavaScript automatically converts types in certain contexts - this is a major source of bugs! + + +```java !! java +// Java - No type coercion (mostly) +String result = "10" + 5; // "105" (String concatenation) +// int result = "10" + 5; // Compilation error! + +// Must explicitly convert +int num = Integer.parseInt("10"); +String str = String.valueOf(42); + +// Boolean is strict +boolean b = (1 == 2); // false +// boolean b2 = 1; // Compilation error! +``` + +```javascript !! js +// JavaScript - Automatic type coercion +console.log("10" + 5); // "105" (string concatenation) +console.log("10" - 5); // 5 (subtraction converts to number) +console.log("10" * 5); // 50 (multiplication converts to number) +console.log("10" / 5); // 2 (division converts to number) + +// String concatenation tricks +console.log(1 + "2"); // "12" +console.log(1 + 2 + "3"); // "33" (1+2=3, then "3"+"3"="33") +console.log("1" + 2 + 3); // "123" + +// Loose equality (==) with coercion +console.log(5 == "5"); // true (string converted to number) +console.log(0 == ""); // true (empty string converted to 0) +console.log(0 == "0"); // true +console.log(false == 0); // true +console.log(null == undefined); // true + +// Strict equality (===) - no coercion +console.log(5 === "5"); // false (different types) +console.log(0 === ""); // false +console.log(false === 0); // false +console.log(null === undefined); // false + +// ALWAYS use strict equality (=== and !==) +function compare(a, b) { + // ❌ Bad: loose equality + if (a == b) { // Might have unexpected behavior + // ... + } + + // ✅ Good: strict equality + if (a === b) { // Clear type checking + // ... + } +} + +// Explicit type conversion +let str = "42"; +let num = Number(str); // 42 (or NaN if invalid) +let num2 = parseInt(str); // 42 (integer only) +let num3 = parseFloat("3.14"); // 3.14 + +let numToStr = String(42); // "42" +let numToStr2 = 42.toString(); // "42" +let numToStr3 = (42).toString(); // "42" (need parentheses for literals) + +let toBool = Boolean("hello"); // true +let toBool2 = !!"hello"; // true (double negation trick) +``` + + +## Operators + +Most operators are similar to Java, but JavaScript has some unique ones: + + +```java !! java +// Java operators +int a = 10, b = 3; + +// Arithmetic +int sum = a + b; // 13 +int diff = a - b; // 7 +int prod = a * b; // 30 +int quot = a / b; // 3 (integer division) +int rem = a % b; // 1 + +// Comparison +boolean equal = (a == b); // false +boolean notEqual = (a != b); // true +boolean greater = (a > b); // true +boolean lessOrEqual = (a <= b); // false + +// Logical +boolean and = (true && false); // false +boolean or = (true || false); // true +boolean not = !true; // false + +// Bitwise +int bitwiseAnd = a & b; // 2 +int bitwiseOr = a | b; // 11 +int bitwiseXor = a ^ b; // 9 +int bitwiseNot = ~a; // -11 +int leftShift = a << 1; // 20 +int rightShift = a >> 1; // 5 + +// Ternary +int max = (a > b) ? a : b; // 10 + +// Increment/decrement +a++; // a = a + 1 +b--; // b = b - 1 +``` + +```javascript !! js +// JavaScript operators (similar but some differences) +let a = 10, b = 3; + +// Arithmetic (same as Java, but division produces float) +let sum = a + b; // 13 +let diff = a - b; // 7 +let prod = a * b; // 30 +let quot = a / b; // 3.333... (NOT integer division!) +let rem = a % b; // 1 + +// Integer division (ES2020) +let intQuot = Math.trunc(a / b); // 3 + +// Comparison (use strict versions!) +let equal = (a === b); // false (strict) +let notEqual = (a !== b); // true (strict) +let greater = (a > b); // true +let lessOrEqual = (a <= b); // false + +// Loose equality (avoid!) +console.log(10 == "10"); // true (coerced) +console.log(10 === "10"); // false (strict) + +// Logical operators (return the actual value, not just boolean) +let logicalAnd = true && false; // false +let logicalAnd2 = 10 && 20; // 20 (returns last truthy value) +let logicalOr = true || false; // true +let logicalOr2 = 0 || 42; // 42 (returns first truthy value) +let logicalNot = !true; // false + +// Nullish coalescing (ES2020) +let value = null ?? "default"; // "default" (only for null/undefined) +let value2 = 0 ?? "default"; // 0 (0 is not null/undefined) + +// Optional chaining (ES2020) +const user = { name: "John", address: { city: "NYC" } }; +console.log(user?.address?.city); // "NYC" +console.log(user?.phone?.number); // undefined (no error) + +// Bitwise (same as Java) +let bitwiseAnd = a & b; // 2 +let bitwiseOr = a | b; // 11 +let bitwiseXor = a ^ b; // 9 +let bitwiseNot = ~a; // -11 +let leftShift = a << 1; // 20 +let rightShift = a >> 1; // 5 +let unsignedRightShift = a >>> 1; // 5 (JavaScript only) + +// Exponentiation (ES2016) +let power = 2 ** 3; // 8 (2 to the power of 3) +// Equivalent to: Math.pow(2, 3) + +// Ternary (same as Java) +let max = (a > b) ? a : b; // 10 + +// Increment/decrement (same as Java) +a++; // a = a + 1 +b--; // b = b - 1 +++a; // Pre-increment +--b; // Pre-decrement + +// Unary operators +let positive = +10; // 10 +let negative = -10; // -10 +let toNumber = +"42"; // 42 (converts string to number) +``` + + +## typeof Operator + +JavaScript has a `typeof` operator to check types: + + +```java !! java +// Java - No typeof operator +// Use instanceof or getClass() +String str = "hello"; +if (str instanceof String) { + System.out.println("It's a String"); +} + +// Reflection +Class type = str.getClass(); +System.out.println(type.getName()); // "java.lang.String" +``` + +```javascript !! js +// JavaScript - typeof operator +let num = 42; +let str = "hello"; +let bool = true; +let obj = { name: "John" }; +let arr = [1, 2, 3]; +let func = function() {}; +let nothing = null; +let undef = undefined; + +console.log(typeof num); // "number" +console.log(typeof str); // "string" +console.log(typeof bool); // "boolean" +console.log(typeof obj); // "object" +console.log(typeof arr); // "object" (arrays are objects!) +console.log(typeof func); // "function" +console.log(typeof nothing); // "object" (historical bug!) +console.log(typeof undef); // "undefined" + +// Checking for null (need special handling) +function isNull(value) { + return value === null; +} + +// Checking for array +function isArray(value) { + return Array.isArray(value); +} + +// Checking for NaN +function isNaNCheck(value) { + return Number.isNaN(value); +} + +// Pattern: type checking +function process(value) { + if (typeof value === "string") { + console.log("Processing string:", value.toUpperCase()); + } else if (typeof value === "number") { + console.log("Processing number:", value * 2); + } else if (Array.isArray(value)) { + console.log("Processing array:", value.length); + } else if (value === null) { + console.log("Processing null value"); + } else { + console.log("Unknown type"); + } +} + +process("hello"); // "Processing string: HELLO" +process(42); // "Processing number: 84" +process([1, 2, 3]); // "Processing array: 3" +process(null); // "Processing null value" +``` + + +## Common Pitfalls + +### Pitfall 1: Using == Instead of === + + +```javascript !! js +// ❌ BAD: Loose equality +console.log(0 == false); // true +console.log("" == false); // true +console.log("0" == false); // true +console.log(null == undefined); // true +console.log([] == false); // true +console.log([1] == "1"); // true + +// ✅ GOOD: Strict equality +console.log(0 === false); // false +console.log("" === false); // false +console.log("0" === false); // false +console.log(null === undefined); // false +console.log([] === false); // false +console.log([1] === "1"); // false + +// ESLint rule to enforce this +/* eslint eqeqeq: ["error", "always"] */ +``` + + +### Pitfall 2: Forgetting const/let + + +```javascript !! js +// ❌ BAD: Using var +var name = "John"; +if (true) { + var name = "Jane"; // Redeclares in same scope! +} +console.log(name); // "Jane" (unexpected!) + +// ❌ BAD: Forgetting const/let +message = "Hello"; // Creates global variable (bad practice) + +// ✅ GOOD: Using let/const +let name = "John"; +if (true) { + let name = "Jane"; // Block-scoped +} +console.log(name); // "John" (as expected) + +// ✅ GOOD: Always declare variables +const message = "Hello"; // Properly declared +``` + + +### Pitfall 3: Type Coercion Surprises + + +```javascript !! js +// Pitfall: String + Number +console.log("10" + 20); // "1020" (string concatenation) +console.log(10 + "20"); // "1020" +console.log(10 + 20 + "30"); // "3030" (10+20=30, then "30"+"30") + +// Solution: Explicitly convert +console.log(Number("10") + 20); // 30 +console.log("10" + String(20)); // "1020" + +// Pitfall: Subtraction converts to number +console.log("10" - 5); // 5 +console.log("10px" - 5); // NaN (can't convert "10px") +console.log("10" - "5"); // 5 + +// Solution: Explicit parsing +console.log(parseInt("10px") - 5); // 5 + +// Pitfall: Object to primitive +console.log(1 + {}); // "1[object Object]" +console.log({} + 1); // "[object Object]1" +console.log([] + []); // "" (empty string) +console.log([] + {}); // "[object Object]" +console.log({} + []); // "[object Object]" +``` + + +## Best Practices Summary + + +```java !! java +// Java: Clear, explicit types +public class User { + private final String name; // Use final for constants + private int age; // Regular for mutable + + public User(String name, int age) { + this.name = name; + this.age = age; + } + + public boolean isAdult() { + return age >= 18; // Clear boolean check + } +} +``` + +```javascript !! js +// JavaScript: Modern, clean syntax +class User { + constructor(name, age) { + this.name = name; // Use _ for private convention (ES2022 has #) + this._age = age; + } + + isAdult() { + return this._age >= 18; // Same check + } +} + +// Variable declaration rules +const API_URL = "https://api.example.com"; // Use const for constants +let currentUser = null; // Use let for variables that change +let counter = 0; // Use let for counters + +// Always use strict equality +if (counter === 0) { // ✅ + // ... +} + +// Avoid type coercion - be explicit +const num = Number(str); // ✅ Explicit conversion +const sum = a + b; // ✅ Clear operation +const result = `${a}${b}`; // ✅ Explicit string concatenation + +// Use template literals +const greeting = `Hello, ${name}!`; // ✅ Modern syntax + +// Check for null/undefined properly +if (user != null) { // Checks both null and undefined (loose eq ok here) + // Safe to access user +} + +// Or use optional chaining +const city = user?.address?.city; // Safe property access +``` + + +## Exercises + +### Exercise 1: Variable Declarations +Convert this Java code to idiomatic JavaScript: +```java +String name = "John"; +int age = 25; +final String CITY = "New York"; +boolean isActive = true; +``` + +### Exercise 2: Type Conversion +Fix the type coercion issues: +```javascript +function add(a, b) { + return a + b; // Should add numbers, not concatenate strings +} + +console.log(add(5, "10")); // Should output 15, not "510" +``` + +### Exercise 3: Null/Undefined Handling +Write a function that handles null/undefined values: +```javascript +function getDisplayName(user) { + // Return user.name if user exists and has a name + // Otherwise return "Guest" +} +``` + +### Exercise 4: Type Checking +Write a function that processes different types: +```javascript +function processValue(value) { + // If string: return uppercase + // If number: return doubled + // If boolean: return negated + // If array: return length + // Otherwise: return "unknown" +} +``` + +## Summary + +### Key Takeaways + +1. **Variable Declarations:** + - Use `const` by default (cannot be reassigned) + - Use `let` when you need to reassign + - Avoid `var` (it's function-scoped and confusing) + +2. **Type System:** + - JavaScript is dynamically typed (no type declarations) + - Primitive types: number, string, boolean, null, undefined, symbol, bigint + - Everything else is an object (including arrays and functions) + +3. **Type Coercion:** + - JavaScript automatically converts types in many contexts + - Always use strict equality (`===` and `!==`) + - Be explicit about type conversion + +4. **Truthy/Falsy:** + - Falsy: `false`, `0`, `""`, `null`, `undefined`, `NaN` + - Truthy: Everything else (including `{}` and `[]`) + - Use `??` for nullish coalescing, `||` for logical OR + +5. **Best Practices:** + - Prefer `const` over `let` + - Always use strict equality + - Be explicit about type conversions + - Use template literals for string interpolation + - Check for null/undefined with optional chaining (`?.`) + +### Comparison Table: Java vs JavaScript + +| Aspect | Java | JavaScript | +|--------|------|------------| +| **Variable declaration** | `String name = "John";` | `let name = "John";` or `const NAME = "John";` | +| **Constants** | `final int MAX = 100;` | `const MAX = 100;` | +| **Primitive count** | 8 primitives | 7 primitives (number, string, boolean, null, undefined, symbol, bigint) | +| **Number types** | byte, short, int, long, float, double | Just `number` (64-bit float) + `bigint` | +| **Equality** | `==` (no loose equality) | `===` (strict) and `==` (loose - avoid) | +| **Type checking** | `instanceof`, `getClass()` | `typeof`, `Array.isArray()` | +| **Type conversion** | Explicit: `Integer.parseInt()` | Implicit (coercion) or explicit: `Number()`, `String()` | +| **String concatenation** | `"Hello " + name` | `` `Hello ${name}` `` (template literal preferred) | +| **Null handling** | Only `null` | Both `null` and `undefined` | + +## What's Next? + +Now that you understand JavaScript's basic syntax and type system, let's move on to **Module 2: Control Flow and Loops**, where we'll learn how JavaScript's control structures compare to Java's! + +You'll discover: +- How `if/else` differs (and doesn't!) +- The `switch` statement differences +- Loop variations (for, for...of, for...in) +- The power of array methods (map, filter, reduce) +- Break and continue behavior + +Let's continue your JavaScript journey! diff --git a/content/docs/java2js/module-01-basic-syntax.zh-cn.mdx b/content/docs/java2js/module-01-basic-syntax.zh-cn.mdx new file mode 100644 index 0000000..a60b813 --- /dev/null +++ b/content/docs/java2js/module-01-basic-syntax.zh-cn.mdx @@ -0,0 +1,1011 @@ +--- +title: "模块 1:基本语法比较" +description: "学习 JavaScript 变量、数据类型、运算符和与 Java 相比的基本语法" +--- + +## 模块 1:基本语法比较 + +欢迎首次深入了解 JavaScript 语法!作为 Java 开发者,你会发现许多熟悉的概念,但 JavaScript 有一些重要的差异使其独一无二。 + +## 学习目标 + +完成本模块后,你将: +✅ 理解 JavaScript 变量声明(var、let、const) +✅ 了解 Java 的静态类型和 JavaScript 的动态类型之间的差异 +✅ 掌握 JavaScript 的原始类型和引用类型 +✅ 理解类型强制转换以及如何避免常见陷阱 +✅ 学习 JavaScript 运算符及其特性 + +## 变量声明 + +### Java 变量 + +在 Java 中,变量是显式类型的: + + +```java !! java +// Java - 显式类型声明 +String name = "John"; +int age = 25; +double salary = 50000.50; +boolean isActive = true; +char grade = 'A'; + +// Final 变量(不能重新赋值) +final String CITY = "New York"; +// CITY = "Boston"; // 编译错误! + +// Null 值 +String middleName = null; + +// 使用 var 的类型推断(Java 10+) +var username = "johndoe"; // 类型推断为 String +// username = 42; // 编译错误! +``` + +```javascript !! js +// JavaScript - 无类型声明 +let name = "John"; +let age = 25; +let salary = 50000.50; +let isActive = true; + +// 常量(不能重新赋值) +const CITY = "New York"; +// CITY = "Boston"; // TypeError: Assignment to constant variable + +// 无需显式 null 声明 +let middleName = null; +let middleName2 = undefined; + +// 类型是动态的 +let username = "johndoe"; +username = 42; // 有效!类型变为 number +username = true; // 现在是 boolean +``` + + +### JavaScript 变量关键字 + +JavaScript 有三种声明变量的方式: + +#### 1. `var`(旧方式 - 在现代代码中避免使用) + + +```javascript !! js +// var - 函数作用域(非块作用域) +function example() { + if (true) { + var x = 10; + } + console.log(x); // 10 - 在 if 块外可访问! +} + +// var 可以重新声明 +var name = "John"; +var name = "Jane"; // 没有错误! + +// var 变量提升 +console.log(y); // undefined(不是错误!) +var y = 5; + +// 这等价于: +var y; // 声明提升到顶部 +console.log(y); +y = 5; // 赋值留在这里 + +// 不要在现代代码中使用 var! +// 改用 let 和 const +``` + + +#### 2. `let`(现代方式,用于可变变量) + + +```javascript !! js +// let - 块作用域(类似 Java) +function example() { + if (true) { + let x = 10; + console.log(x); // 10 + } + console.log(x); // ReferenceError: x is not defined +} + +// let 不能在同一作用域重新声明 +let name = "John"; +// let name = "Jane"; // SyntaxError! + +// 但可以重新赋值 +let age = 25; +age = 26; // 有效 + +// let 用于 for 循环(块作用域) +for (let i = 0; i < 3; i++) { + setTimeout(() => console.log(i), 100); +} +// 输出:0、1、2(每次迭代都有自己的 i) + +// 与 var 比较(函数作用域) +for (var j = 0; j < 3; j++) { + setTimeout(() => console.log(j), 100); +} +// 输出:3、3、3(共享 j 引用) +``` + + +#### 3. `const`(现代方式,用于常量) + + +```javascript !! js +// const - 块作用域,不能重新赋值 +const PI = 3.14159; +// PI = 3.14; // TypeError: Assignment to constant variable + +// 必须初始化 +const name; // SyntaxError: Missing initializer + +// 对象和数组仍然可以修改(不能重新赋值) +const person = { name: "John", age: 25 }; +person.name = "Jane"; // 有效!对象被修改 +person.age = 26; // 有效! + +// person = {}; // TypeError: Cannot reassign const + +const numbers = [1, 2, 3]; +numbers.push(4); // 有效!数组被修改 +// numbers = []; // TypeError: Cannot reassign const + +// 防止修改,使用 Object.freeze() +const frozenPerson = Object.freeze({ name: "John", age: 25 }); +frozenPerson.name = "Jane"; // 静默失败(严格模式:错误) +console.log(frozenPerson.name); // "John" +``` + + +### 最佳实践:何时使用什么 + + +```java !! java +// Java - 尽可能使用 final +public class User { + private final String name; // 对常量使用 final + private int age; // 可变字段使用常规方式 + + public User(String name) { + this.name = name; // 一次赋值 + } + + public void setAge(int age) { + this.age = age; // 可以改变 + } +} +``` + +```javascript !! js +// JavaScript - 优先使用 const,需要时使用 let +// 1. 默认使用 const(大多数变量不需要改变) +const API_URL = "https://api.example.com"; +const MAX_RETRIES = 3; + +// 2. 需要重新赋值时使用 let +let counter = 0; +counter++; + +let userData = null; +userData = fetchUserData(); + +// 3. 完全避免 var(它是函数作用域且令人困惑) +// ❌ 不要这样做: +var i = 0; + +// ✅ 改为这样做: +let i = 0; +``` + + +## 数据类型 + +### 原始类型 + +JavaScript 有 7 种原始类型(Java 有 8 种): + + +```java !! java +// Java 原始类型 +byte b = 127; // 8 位整数 +short s = 32767; // 16 位整数 +int i = 2147483647; // 32 位整数 +long l = 9223372036854775807L; // 64 位整数 +float f = 3.14f; // 32 位浮点数 +double d = 3.14159265359; // 64 位浮点数 +char c = 'A'; // 16 位 Unicode 字符 +boolean bool = true; // true 或 false + +// Java 也有 void(无值) +``` + +```javascript !! js +// JavaScript 原始类型(更简单!) +let num = 42; // Number(所有数字都是 64 位浮点数) +let pi = 3.14159265359; // Number(没有 int/float 区别) +let bigInt = 9007199254740991n; // BigInt(用于大整数) + +let str = "Hello"; // String(可以使用 " 或 ' 或 `) +let char = 'A'; // 仍然是 String(单个字符也是字符串) + +let bool = true; // Boolean(true 或 false) + +let nothing = null; // Null(有意为空) +let undefinedVar = undefined; // Undefined(无意为空) + +let symbol = Symbol('id'); // Symbol(唯一标识符,ES6+) +``` + + +### Number 类型 + +JavaScript 只有一种数字类型(与 Java 不同): + + +```java !! java +// Java - 多种数字类型 +int integer = 42; +double decimal = 3.14; +long bigNumber = 9007199254740991L; + +// 类型提升 +int a = 5; +int b = 2; +int result = a / b; // result = 2(整数除法) + +// 显式转换 +double x = 3.7; +int y = (int) x; // y = 3(截断) +``` + +```javascript !! js +// JavaScript - 单一 Number 类型(IEEE 754 64 位浮点数) +let integer = 42; +let decimal = 3.14; +let scientific = 1.5e-4; // 0.00015 +let hex = 0xFF; // 255 +let binary = 0b1010; // 10 +let octal = 0o755; // 493 + +// 除法总是产生浮点数 +let a = 5; +let b = 2; +let result = a / b; // result = 2.5(不是 2!) + +// Number 方法 +let num = 42.123; +console.log(num.toFixed(2)); // "42.12" +console.log(Math.round(num)); // 42 +console.log(Math.floor(num)); // 42 + +// 特殊数值 +let infinity = Infinity; +let negInfinity = -Infinity; +let notANumber = NaN; // Not-a-Number + +console.log(0 / 0); // NaN +console.log(1 / 0); // Infinity +console.log(Math.sqrt(-1)); // NaN + +// 检查 NaN(重要!) +console.log(NaN === NaN); // false(NaN 不等于自身!) +console.log(Number.isNaN(NaN)); // true +console.log(Number.isFinite(42)); // true +console.log(Number.isFinite(Infinity)); // false +``` + + +### String 类型 + +JavaScript 字符串与 Java 字符串类似,但更加灵活: + + +```java !! java +// Java 字符串是不可变的 +String name = "John"; +name.toUpperCase(); // 返回 "JOHN" 但 name 仍是 "John" +name = name.toUpperCase(); // 现在 name 是 "JOHN" + +// 字符串连接 +String greeting = "Hello, " + name; + +// 多行字符串(Java 15+) +String multiline = """ + This is a + multiline string + """; + +// 字符串方法 +String text = "JavaScript"; +System.out.println(text.length()); // 10 +System.out.println(text.substring(0, 4)); // "Java" +System.out.println(text.contains("Script")); // true +``` + +```javascript !! js +// JavaScript 字符串是不可变的(与 Java 相同) +let name = "John"; +name.toUpperCase(); // 返回 "JOHN" 但 name 仍是 "John" +name = name.toUpperCase(); // 现在 name 是 "JOHN" + +// 字符串连接(多种方式) +let greeting = "Hello, " + name; +let greeting2 = `Hello, ${name}`; // 模板字面量(推荐) + +// 多行字符串(模板字面量) +const multiline = `This is a +multiline string`; + +// 字符串方法 +const text = "JavaScript"; +console.log(text.length); // 10 +console.log(text.substring(0, 4)); // "Java" +console.log(text.includes("Script")); // true +console.log(text.startsWith("Java")); // true +console.log(text.endsWith("Script")); // true +console.log(text.split('a')); // ["J", "v", "Script"] + +// 返回修改后字符串的字符串方法 +console.log(text.toUpperCase()); // "JAVASCRIPT" +console.log(text.toLowerCase()); // "javascript" +console.log(text.trim()); // 删除空格 +console.log(text.replace("Java", "ECMA")); // "ECMAScript" + +// 带表达式的模板字面量 +const x = 10; +const y = 20; +console.log(`${x} + ${y} = ${x + y}`); // "10 + 20 = 30" +``` + + +### Boolean 类型和真值/假值 + +这是与 Java 的一个主要差异! + + +```java !! java +// Java - 严格的 boolean 检查 +boolean isActive = true; +boolean isInactive = false; + +// 只有 boolean 表达式可以在条件中使用 +String name = "John"; +if (name != null && !name.isEmpty()) { // 必须显式检查 + System.out.println("Name exists"); +} + +// 没有真值/假值 - 必须明确 +int count = 0; +if (count > 0) { // 不能仅使用 "if (count)" + System.out.println("Count is positive"); +} +``` + +```javascript !! js +// JavaScript - Boolean 加上真值/假值 +let isActive = true; +let isInactive = false; + +// 假值(在 boolean 上下文中评估为 false): +false, 0, -0, 0n, "", null, undefined, NaN + +// 真值(其他所有值): +true, 42, "hello", {}, [], function() {} + +// 短路求值 +const name = "John"; +if (name) { // name 是真值(非空字符串) + console.log("Name exists"); +} + +const count = 0; +if (count) { // count 是假值(零) + console.log("Count is positive"); +} else { + console.log("Count is zero or negative"); +} + +// 常见模式:默认值 +function greet(name) { + // 如果 name 是假值,使用 "Guest" + const displayName = name || "Guest"; + console.log(`Hello, ${displayName}`); +} + +greet("John"); // "Hello, John" +greet(""); // "Hello, Guest" +greet(null); // "Hello, Guest" + +// 空值合并运算符(ES2020) +// 仅检查 null/undefined(不包括 0 或 "") +function greet2(name) { + const displayName = name ?? "Guest"; + console.log(`Hello, ${displayName}`); +} + +greet2(""); // "Hello, "(空字符串不是 null/undefined) +greet2(null); // "Hello, Guest" + +// Boolean 转换 +console.log(Boolean("hello")); // true +console.log(Boolean("")); // false +console.log(Boolean(42)); // true +console.log(Boolean(0)); // false +console.log(Boolean({})); // true(空对象是真值!) +console.log(Boolean([])); // true(空数组是真值!) +``` + + +### Null vs Undefined + +两种表示"无值"的方式: + + +```java !! java +// Java - 只有 null +String name = null; +int[] numbers = null; + +// 不能有 undefined - 一切都被初始化 +// int x; // 使用前必须初始化 +int x = 0; // 原始类型的默认值 + +// Optional 类型(Java 8+) +Optional optionalName = Optional.of("John"); +Optional emptyOptional = Optional.empty(); +``` + +```javascript !! js +// JavaScript - 既有 null 又有 undefined + +// undefined:变量已声明但未赋值 +let x; +console.log(x); // undefined + +// null:有意设置为无值 +let y = null; +console.log(y); // null + +// 关键差异: +console.log(typeof undefined); // "undefined" +console.log(typeof null); // "object"(历史 bug!) + +// 转换为数字 +console.log(Number(undefined)); // NaN +console.log(Number(null)); // 0 + +// 宽松相等(避免使用!) +console.log(null == undefined); // true +console.log(null === undefined); // false(使用严格相等) + +// 最佳实践: +// 使用 null 表示"有意为空" +function getUser(id) { + const user = database.find(id); + return user !== undefined ? user : null; +} + +// 使用 undefined 表示"未设置" +function process(options) { + // 如果 options.timeout 未提供 + if (options.timeout === undefined) { + options.timeout = 5000; // 使用默认值 + } +} + +// 检查 null/undefined +function safeGet(obj) { + // 旧方法 + if (obj !== null && obj !== undefined) { + return obj.value; + } + + // 现代方法(检查 null 和 undefined) + if (obj != null) { // 宽松相等在这里可行 + return obj.value; + } + + // 或使用空值合并 + return obj?.value; +} +``` + + +## 类型强制转换 + +JavaScript 在某些上下文中自动转换类型 - 这是 bug 的主要来源! + + +```java !! java +// Java - 几乎没有类型强制转换 +String result = "10" + 5; // "105"(字符串连接) +// int result = "10" + 5; // 编译错误! + +// 必须显式转换 +int num = Integer.parseInt("10"); +String str = String.valueOf(42); + +// Boolean 是严格的 +boolean b = (1 == 2); // false +// boolean b2 = 1; // 编译错误! +``` + +```javascript !! js +// JavaScript - 自动类型强制转换 +console.log("10" + 5); // "105"(字符串连接) +console.log("10" - 5); // 5(减法转换为数字) +console.log("10" * 5); // 50(乘法转换为数字) +console.log("10" / 5); // 2(除法转换为数字) + +// 字符串连接技巧 +console.log(1 + "2"); // "12" +console.log(1 + 2 + "3"); // "33" (1+2=3, then "3"+"3"="33") +console.log("1" + 2 + 3); // "123" + +// 宽松相等(==)带有强制转换 +console.log(5 == "5"); // true(字符串转换为数字) +console.log(0 == ""); // true(空字符串转换为 0) +console.log(0 == "0"); // true +console.log(false == 0); // true +console.log(null == undefined); // true + +// 严格相等(===)- 无强制转换 +console.log(5 === "5"); // false(不同类型) +console.log(0 === ""); // false +console.log(false === 0); // false +console.log(null === undefined); // false + +// 始终使用严格相等(=== 和 !==) +function compare(a, b) { + // ❌ 不好:宽松相等 + if (a == b) { // 可能有意外行为 + // ... + } + + // ✅ 好:严格相等 + if (a === b) { // 清晰的类型检查 + // ... + } +} + +// 显式类型转换 +let str = "42"; +let num = Number(str); // 42(如果无效则 NaN) +let num2 = parseInt(str); // 42(仅整数) +let num3 = parseFloat("3.14"); // 3.14 + +let numToStr = String(42); // "42" +let numToStr2 = 42.toString(); // "42" +let numToStr3 = (42).toString(); // "42"(字面量需要括号) + +let toBool = Boolean("hello"); // true +let toBool2 = !!"hello"; // true(双重否定技巧) +``` + + +## 运算符 + +大多数运算符与 Java 类似,但 JavaScript 有一些独特的: + + +```java !! java +// Java 运算符 +int a = 10, b = 3; + +// 算术 +int sum = a + b; // 13 +int diff = a - b; // 7 +int prod = a * b; // 30 +int quot = a / b; // 3(整数除法) +int rem = a % b; // 1 + +// 比较 +boolean equal = (a == b); // false +boolean notEqual = (a != b); // true +boolean greater = (a > b); // true +boolean lessOrEqual = (a <= b); // false + +// 逻辑 +boolean and = (true && false); // false +boolean or = (true || false); // true +boolean not = !true; // false + +// 位运算 +int bitwiseAnd = a & b; // 2 +int bitwiseOr = a | b; // 11 +int bitwiseXor = a ^ b; // 9 +int bitwiseNot = ~a; // -11 +int leftShift = a << 1; // 20 +int rightShift = a >> 1; // 5 + +// 三元 +int max = (a > b) ? a : b; // 10 + +// 自增/自减 +a++; // a = a + 1 +b--; // b = b - 1 +``` + +```javascript !! js +// JavaScript 运算符(类似但有差异) +let a = 10, b = 3; + +// 算术(与 Java 相同,但除法产生浮点数) +let sum = a + b; // 13 +let diff = a - b; // 7 +let prod = a * b; // 30 +let quot = a / b; // 3.333...(非整数除法!) +let rem = a % b; // 1 + +// 整数除法(ES2020) +let intQuot = Math.trunc(a / b); // 3 + +// 比较(使用严格版本!) +let equal = (a === b); // false(严格) +let notEqual = (a !== b); // true(严格) +let greater = (a > b); // true +let lessOrEqual = (a <= b); // false + +// 宽松相等(避免!) +console.log(10 == "10"); // true(强制转换) +console.log(10 === "10"); // false(严格) + +// 逻辑运算符(返回实际值,不仅仅是 boolean) +let logicalAnd = true && false; // false +let logicalAnd2 = 10 && 20; // 20(返回最后的真值) +let logicalOr = true || false; // true +let logicalOr2 = 0 || 42; // 42(返回第一个真值) +let logicalNot = !true; // false + +// 空值合并(ES2020) +let value = null ?? "default"; // "default"(仅用于 null/undefined) +let value2 = 0 ?? "default"; // 0(0 不是 null/undefined) + +// 可选链(ES2020) +const user = { name: "John", address: { city: "NYC" } }; +console.log(user?.address?.city); // "NYC" +console.log(user?.phone?.number); // undefined(无错误) + +// 位运算(与 Java 相同) +let bitwiseAnd = a & b; // 2 +let bitwiseOr = a | b; // 11 +let bitwiseXor = a ^ b; // 9 +let bitwiseNot = ~a; // -11 +let leftShift = a << 1; // 20 +let rightShift = a >> 1; // 5 +let unsignedRightShift = a >>> 1; // 5(仅 JavaScript) + +// 幂运算(ES2016) +let power = 2 ** 3; // 8(2 的 3 次方) +// 等价于:Math.pow(2, 3) + +// 三元(与 Java 相同) +let max = (a > b) ? a : b; // 10 + +// 自增/自减(与 Java 相同) +a++; // a = a + 1 +b--; // b = b - 1 +++a; // 前置自增 +--b; // 前置自减 + +// 一元运算符 +let positive = +10; // 10 +let negative = -10; // -10 +let toNumber = +"42"; // 42(将字符串转换为数字) +``` + + +## typeof 运算符 + +JavaScript 有一个 `typeof` 运算符来检查类型: + + +```java !! java +// Java - 没有 typeof 运算符 +// 使用 instanceof 或 getClass() +String str = "hello"; +if (str instanceof String) { + System.out.println("It's a String"); +} + +// 反射 +Class type = str.getClass(); +System.out.println(type.getName()); // "java.lang.String" +``` + +```javascript !! js +// JavaScript - typeof 运算符 +let num = 42; +let str = "hello"; +let bool = true; +let obj = { name: "John" }; +let arr = [1, 2, 3]; +let func = function() {}; +let nothing = null; +let undef = undefined; + +console.log(typeof num); // "number" +console.log(typeof str); // "string" +console.log(typeof bool); // "boolean" +console.log(typeof obj); // "object" +console.log(typeof arr); // "object"(数组是对象!) +console.log(typeof func); // "function" +console.log(typeof nothing); // "object"(历史 bug!) +console.log(typeof undef); // "undefined" + +// 检查 null(需要特殊处理) +function isNull(value) { + return value === null; +} + +// 检查数组 +function isArray(value) { + return Array.isArray(value); +} + +// 检查 NaN +function isNaNCheck(value) { + return Number.isNaN(value); +} + +// 模式:类型检查 +function process(value) { + if (typeof value === "string") { + console.log("Processing string:", value.toUpperCase()); + } else if (typeof value === "number") { + console.log("Processing number:", value * 2); + } else if (Array.isArray(value)) { + console.log("Processing array:", value.length); + } else if (value === null) { + console.log("Processing null value"); + } else { + console.log("Unknown type"); + } +} + +process("hello"); // "Processing string: HELLO" +process(42); // "Processing number: 84" +process([1, 2, 3]); // "Processing array: 3" +process(null); // "Processing null value" +``` + + +## 常见陷阱 + +### 陷阱 1:使用 == 而不是 === + + +```javascript !! js +// ❌ 不好:宽松相等 +console.log(0 == false); // true +console.log("" == false); // true +console.log("0" == false); // true +console.log(null == undefined); // true +console.log([] == false); // true +console.log([1] == "1"); // true + +// ✅ 好:严格相等 +console.log(0 === false); // false +console.log("" === false); // false +console.log("0" === false); // false +console.log(null === undefined); // false +console.log([] === false); // false +console.log([1] === "1"); // false +``` + + +### 陷阱 2:忘记 const/let + + +```javascript !! js +// ❌ 不好:使用 var +var name = "John"; +if (true) { + var name = "Jane"; // 在同一作用域重新声明! +} +console.log(name); // "Jane"(意外!) + +// ❌ 不好:忘记 const/let +message = "Hello"; // 创建全局变量(不好) + +// ✅ 好:使用 let/const +let name = "John"; +if (true) { + let name = "Jane"; // 块作用域 +} +console.log(name); // "John"(符合预期) + +// ✅ 好:始终声明变量 +const message = "Hello"; // 正确声明 +``` + + +### 陷阱 3:类型强制转换惊喜 + + +```javascript !! js +// 陷阱:String + Number +console.log("10" + 20); // "1020"(字符串连接) +console.log(10 + "20"); // "1020" +console.log(10 + 20 + "30"); // "3030" (10+20=30, then "30"+"30") + +// 解决方案:显式转换 +console.log(Number("10") + 20); // 30 +console.log("10" + String(20)); // "1020" + +// 陷阱:减法转换为数字 +console.log("10" - 5); // 5 +console.log("10px" - 5); // NaN(无法转换 "10px") +console.log("10" - "5"); // 5 + +// 解决方案:显式解析 +console.log(parseInt("10px") - 5); // 5 +``` + + +## 最佳实践总结 + + +```java !! java +// Java:清晰、显式的类型 +public class User { + private final String name; // 对常量使用 final + private int age; // 可变使用常规方式 + + public User(String name, int age) { + this.name = name; + this.age = age; + } + + public boolean isAdult() { + return age >= 18; // 清晰的 boolean 检查 + } +} +``` + +```javascript !! js +// JavaScript:现代、简洁的语法 +class User { + constructor(name, age) { + this.name = name; // 使用 _ 表示私有约定(ES2022 有 #) + this._age = age; + } + + isAdult() { + return this._age >= 18; // 相同的检查 + } +} + +// 变量声明规则 +const API_URL = "https://api.example.com"; // 对常量使用 const +let currentUser = null; // 对可变变量使用 let +let counter = 0; // 对计数器使用 let + +// 始终使用严格相等 +if (counter === 0) { // ✅ + // ... +} + +// 避免类型强制转换 - 明确一点 +const num = Number(str); // ✅ 显式转换 +const sum = a + b; // ✅ 清晰的操作 +const result = `${a}${b}`; // ✅ 显式字符串连接 + +// 使用模板字面量 +const greeting = `Hello, ${name}!`; // ✅ 现代语法 + +// 正确检查 null/undefined +if (user != null) { // 检查 null 和 undefined(宽松相等可以) + // 安全访问 user +} + +// 或使用可选链 +const city = user?.address?.city; // 安全属性访问 +``` + + +## 练习 + +### 练习 1:变量声明 +将此 Java 代码转换为地道的 JavaScript: +```java +String name = "John"; +int age = 25; +final String CITY = "New York"; +boolean isActive = true; +``` + +### 练习 2:类型转换 +修复类型强制转换问题: +```javascript +function add(a, b) { + return a + b; // 应该相加数字,而不是连接字符串 +} + +console.log(add(5, "10")); // 应该输出 15,而不是 "510" +``` + +### 练习 3:Null/Undefined 处理 +编写一个处理 null/undefined 值的函数: +```javascript +function getDisplayName(user) { + // 如果 user 存在且有 name,返回 user.name + // 否则返回 "Guest" +} +``` + +### 练习 4:类型检查 +编写一个处理不同类型的函数: +```javascript +function processValue(value) { + // 如果是 string:返回大写 + // 如果是 number:返回双倍 + // 如果是 boolean:返回取反 + // 如果是 array:返回长度 + // 否则:返回 "unknown" +} +``` + +## 总结 + +### 关键要点 + +1. **变量声明:** + - 默认使用 `const`(不能重新赋值) + - 需要重新赋值时使用 `let` + - 避免 `var`(它是函数作用域且令人困惑) + +2. **类型系统:** + - JavaScript 是动态类型的(无需类型声明) + - 原始类型:number、string、boolean、null、undefined、symbol、bigint + - 其他一切都是对象(包括数组和函数) + +3. **类型强制转换:** + - JavaScript 在许多上下文中自动转换类型 + - 始终使用严格相等(`===` 和 `!==`) + - 明确类型转换 + +4. **真值/假值:** + - 假值:`false`、`0`、`""`、`null`、`undefined`、`NaN` + - 真值:其他所有值(包括 `{}` 和 `[]`) + - 使用 `??` 进行空值合并,`||` 用于逻辑或 + +5. **最佳实践:** + - 优先使用 `const` 而不是 `let` + - 始终使用严格相等 + - 明确类型转换 + - 使用模板字面量进行字符串插值 + - 使用可选链(`?.`)检查 null/undefined + +### 比较表:Java vs JavaScript + +| 方面 | Java | JavaScript | +|--------|------|------------| +| **变量声明** | `String name = "John";` | `let name = "John";` 或 `const NAME = "John";` | +| **常量** | `final int MAX = 100;` | `const MAX = 100;` | +| **原始类型数量** | 8 种原始类型 | 7 种原始类型(number、string、boolean、null、undefined、symbol、bigint) | +| **数字类型** | byte、short、int、long、float、double | 只有 `number`(64 位浮点数)+ `bigint` | +| **相等** | `==`(没有宽松相等) | `===`(严格)和 `==`(宽松 - 避免) | +| **类型检查** | `instanceof`、`getClass()` | `typeof`、`Array.isArray()` | +| **类型转换** | 显式:`Integer.parseInt()` | 隐式(强制转换)或显式:`Number()`、`String()` | +| **字符串连接** | `"Hello " + name` | `` `Hello ${name}` ``(模板字面量推荐) | +| **Null 处理** | 只有 `null` | 既有 `null` 又有 `undefined` | + +## 下一步? + +现在你已经了解了 JavaScript 的基本语法和类型系统,让我们继续学习**模块 2:控制流和循环**,我们将学习 JavaScript 的控制结构与 Java 的比较! + +你将发现: +- `if/else` 如何不同(以及如何相同!) +- `switch` 语句的差异 +- 循环变体(for、for...of、for...in) +- 数组方法的力量(map、filter、reduce) +- Break 和 continue 行为 + +让我们继续你的 JavaScript 之旅! diff --git a/content/docs/java2js/module-01-basic-syntax.zh-tw.mdx b/content/docs/java2js/module-01-basic-syntax.zh-tw.mdx new file mode 100644 index 0000000..25a7f5f --- /dev/null +++ b/content/docs/java2js/module-01-basic-syntax.zh-tw.mdx @@ -0,0 +1,1031 @@ +--- +title: "Module 1: Basic Syntax Comparison" +description: "Learn JavaScript variables, data types, operators, and basic syntax compared to Java" +--- + +## Module 1: Basic Syntax Comparison + +歡迎來到 JavaScript 語法的首次深入探討!作為 Java 開發者,你會發現許多熟悉的概念,但 JavaScript 有一些重要的差異使其獨特。 + +## Learning Objectives + +By the end of this module, you will: +✅ Understand JavaScript variable declarations (var, let, const) +✅ Know the differences between Java's static typing and JavaScript's dynamic typing +✅ Master JavaScript's primitive and reference types +✅ Understand type coercion and how to avoid common pitfalls +✅ Learn JavaScript operators and their quirks + +## Variable Declarations + +### Java Variables + +在 Java 中,變數是明確型別的: + + +```java !! java +// Java - Explicit type declarations +String name = "John"; +int age = 25; +double salary = 50000.50; +boolean isActive = true; +char grade = 'A'; + +// Final variable (cannot be reassigned) +final String CITY = "New York"; +// CITY = "Boston"; // Compilation error! + +// Null values +String middleName = null; + +// Type inference with var (Java 10+) +var username = "johndoe"; // Type is inferred as String +// username = 42; // Compilation error! +``` + +```javascript !! js +// JavaScript - No type declarations +let name = "John"; +let age = 25; +let salary = 50000.50; +let isActive = true; + +// Constants (cannot be reassigned) +const CITY = "New York"; +// CITY = "Boston"; // TypeError: Assignment to constant variable + +// No explicit null declaration needed +let middleName = null; +let middleName2 = undefined; + +// Type is dynamic +let username = "johndoe"; +username = 42; // Valid! Type changes to number +username = true; // Now it's a boolean +``` + + +### JavaScript Variable Keywords + +JavaScript 有三種宣告變數的方法: + +#### 1. `var`(舊方法 - 在現代程式碼中避免使用) + + +```javascript !! js +// var - function-scoped (NOT block-scoped) +function example() { + if (true) { + var x = 10; + } + console.log(x); // 10 - accessible outside if block! +} + +// var can be redeclared +var name = "John"; +var name = "Jane"; // No error! + +// var hoisting +console.log(y); // undefined (not an error!) +var y = 5; + +// This is equivalent to: +var y; // Declaration hoisted to top +console.log(y); +y = 5; // Assignment stays here + +// DON'T USE var IN MODERN CODE! +// Use let and const instead +``` + + +#### 2. `let`(現代方法,用於會改變的變數) + + +```javascript !! js +// let - block-scoped (like Java) +function example() { + if (true) { + let x = 10; + console.log(x); // 10 + } + console.log(x); // ReferenceError: x is not defined +} + +// let cannot be redeclared in same scope +let name = "John"; +// let name = "Jane"; // SyntaxError! + +// But can be reassigned +let age = 25; +age = 26; // Valid + +// let with for loops (block-scoped) +for (let i = 0; i < 3; i++) { + setTimeout(() => console.log(i), 100); +} +// Output: 0, 1, 2 (each iteration has its own i) + +// Compare with var (function-scoped) +for (var j = 0; j < 3; j++) { + setTimeout(() => console.log(j), 100); +} +// Output: 3, 3, 3 (shared j reference) +``` + + +#### 3. `const`(現代方法,用於常數) + + +```javascript !! js +// const - block-scoped, cannot be reassigned +const PI = 3.14159; +// PI = 3.14; // TypeError: Assignment to constant variable + +// Must be initialized +const name; // SyntaxError: Missing initializer + +// Objects and arrays can still be modified (not reassigned) +const person = { name: "John", age: 25 }; +person.name = "Jane"; // Valid! Object is modified +person.age = 26; // Valid! + +// person = {}; // TypeError: Cannot reassign const + +const numbers = [1, 2, 3]; +numbers.push(4); // Valid! Array is modified +// numbers = []; // TypeError: Cannot reassign const + +// To prevent modifications, use Object.freeze() +const frozenPerson = Object.freeze({ name: "John", age: 25 }); +frozenPerson.name = "Jane"; // Silently fails (strict mode: error) +console.log(frozenPerson.name); // "John" +``` + + +### Best Practice: When to Use What + + +```java !! java +// Java - Always use final when possible +public class User { + private final String name; // Use final for constants + private int age; // Use regular for mutable fields + + public User(String name) { + this.name = name; // Assign once + } + + public void setAge(int age) { + this.age = age; // Can change + } +} +``` + +```javascript !! js +// JavaScript - Prefer const, use let when needed +// 1. Use const by default (most variables don't need to change) +const API_URL = "https://api.example.com"; +const MAX_RETRIES = 3; + +// 2. Use let when you need to reassign +let counter = 0; +counter++; + +let userData = null; +userData = fetchUserData(); + +// 3. Avoid var entirely (it's function-scoped and confusing) +// ❌ Don't do this: +var i = 0; + +// ✅ Do this instead: +let i = 0; +``` + + +## Data Types + +### Primitive Types + +JavaScript 有 7 種基本型別(Java 有 8 種): + + +```java !! java +// Java primitives +byte b = 127; // 8-bit integer +short s = 32767; // 16-bit integer +int i = 2147483647; // 32-bit integer +long l = 9223372036854775807L; // 64-bit integer +float f = 3.14f; // 32-bit floating point +double d = 3.14159265359; // 64-bit floating point +char c = 'A'; // 16-bit Unicode character +boolean bool = true; // true or false + +// Java also has void (no value) +``` + +```javascript !! js +// JavaScript primitives (simpler!) +let num = 42; // Number (all numbers are 64-bit floats) +let pi = 3.14159265359; // Number (no int/float distinction) +let bigInt = 9007199254740991n; // BigInt (for large integers) + +let str = "Hello"; // String (can use " or ' or `) +let char = 'A'; // Still a String (single chars are strings) + +let bool = true; // Boolean (true or false) + +let nothing = null; // Null (intentional absence) +let undefinedVar = undefined; // Undefined (unintentional absence) + +let symbol = Symbol('id'); // Symbol (unique identifier, ES6+) +``` + + +### Number Type + +JavaScript 只有一種數字型別(不像 Java): + + +```java !! java +// Java - Multiple numeric types +int integer = 42; +double decimal = 3.14; +long bigNumber = 9007199254740991L; + +// Type promotion +int a = 5; +int b = 2; +int result = a / b; // result = 2 (integer division) + +// Explicit casting +double x = 3.7; +int y = (int) x; // y = 3 (truncates) +``` + +```javascript !! js +// JavaScript - Single Number type (IEEE 754 64-bit float) +let integer = 42; +let decimal = 3.14; +let scientific = 1.5e-4; // 0.00015 +let hex = 0xFF; // 255 +let binary = 0b1010; // 10 +let octal = 0o755; // 493 + +// Division always produces float +let a = 5; +let b = 2; +let result = a / b; // result = 2.5 (not 2!) + +// Number methods +let num = 42.123; +console.log(num.toFixed(2)); // "42.12" +console.log(Math.round(num)); // 42 +console.log(Math.floor(num)); // 42 + +// Special numeric values +let infinity = Infinity; +let negInfinity = -Infinity; +let notANumber = NaN; // Not-a-Number + +console.log(0 / 0); // NaN +console.log(1 / 0); // Infinity +console.log(Math.sqrt(-1)); // NaN + +// Check for NaN (important!) +console.log(NaN === NaN); // false (NaN is not equal to itself!) +console.log(Number.isNaN(NaN)); // true +console.log(Number.isFinite(42)); // true +console.log(Number.isFinite(Infinity)); // false +``` + + +### String Type + +JavaScript 字串類似於 Java 字串,但更具靈活性: + + +```java !! java +// Java strings are immutable +String name = "John"; +name.toUpperCase(); // Returns "JOHN" but name is still "John" +name = name.toUpperCase(); // Now name is "JOHN" + +// String concatenation +String greeting = "Hello, " + name; + +// Multiline strings (Java 15+) +String multiline = """ + This is a + multiline string + """; + +// String methods +String text = "JavaScript"; +System.out.println(text.length()); // 10 +System.out.println(text.substring(0, 4)); // "Java" +System.out.println(text.contains("Script")); // true +``` + +```javascript !! js +// JavaScript strings are immutable (same as Java) +let name = "John"; +name.toUpperCase(); // Returns "JOHN" but name is still "John" +name = name.toUpperCase(); // Now name is "JOHN" + +// String concatenation (multiple ways) +let greeting = "Hello, " + name; +let greeting2 = `Hello, ${name}`; // Template literal (preferred) + +// Multiline strings (template literals) +const multiline = `This is a +multiline string`; + +// String methods +const text = "JavaScript"; +console.log(text.length); // 10 +console.log(text.substring(0, 4)); // "Java" +console.log(text.includes("Script")); // true +console.log(text.startsWith("Java")); // true +console.log(text.endsWith("Script")); // true +console.log(text.split('a')); // ["J", "v", "Script"] + +// String methods that return modified strings +console.log(text.toUpperCase()); // "JAVASCRIPT" +console.log(text.toLowerCase()); // "javascript" +console.log(text.trim()); // Removes whitespace +console.log(text.replace("Java", "ECMA")); // "ECMAScript" + +// Template literals with expressions +const x = 10; +const y = 20; +console.log(`${x} + ${y} = ${x + y}`); // "10 + 20 = 30" + +// Tagged template literals (advanced) +function highlight(strings, ...values) { + return strings.reduce((result, str, i) => { + return result + str + (values[i] || ''); + }, ''); +} +const name2 = "John"; +const age = 25; +console.log(highlight`Name: ${name2}, Age: ${age}`); +``` + + +### Boolean Type and Truthy/Falsy Values + +這與 Java 的一個主要差異! + + +```java !! java +// Java - Strict boolean checking +boolean isActive = true; +boolean isInactive = false; + +// Only boolean expressions can be used in conditionals +String name = "John"; +if (name != null && !name.isEmpty()) { // Must check explicitly + System.out.println("Name exists"); +} + +// No truthy/falsy - must be explicit +int count = 0; +if (count > 0) { // Cannot just use "if (count)" + System.out.println("Count is positive"); +} +``` + +```javascript !! js +// JavaScript - Boolean plus truthy/falsy values +let isActive = true; +let isInactive = false; + +// Falsy values (evaluate to false in boolean context): +false, 0, -0, 0n, "", null, undefined, NaN + +// Truthy values (everything else): +true, 42, "hello", {}, [], [], function() {} + +// Short-circuit evaluation +const name = "John"; +if (name) { // name is truthy (non-empty string) + console.log("Name exists"); +} + +const count = 0; +if (count) { // count is falsy (zero) + console.log("Count is positive"); +} else { + console.log("Count is zero or negative"); +} + +// Common pattern: default values +function greet(name) { + // If name is falsy, use "Guest" + const displayName = name || "Guest"; + console.log(`Hello, ${displayName}`); +} + +greet("John"); // "Hello, John" +greet(""); // "Hello, Guest" +greet(null); // "Hello, Guest" + +// Nullish coalescing operator (ES2020) +// Only checks for null/undefined (not 0 or "") +function greet2(name) { + const displayName = name ?? "Guest"; + console.log(`Hello, ${displayName}`); +} + +greet2(""); // "Hello, " (empty string is not null/undefined) +greet2(null); // "Hello, Guest" + +// Boolean conversion +console.log(Boolean("hello")); // true +console.log(Boolean("")); // false +console.log(Boolean(42)); // true +console.log(Boolean(0)); // false +console.log(Boolean({})); // true (empty object is truthy!) +console.log(Boolean([])); // true (empty array is truthy!) +``` + + +### Null vs Undefined + +兩種表示「無值」的方式: + + +```java !! java +// Java - Only null +String name = null; +int[] numbers = null; + +// Cannot have undefined - everything is initialized +// int x; // Must be initialized before use +int x = 0; // Default value for primitives + +// Optional type (Java 8+) +Optional optionalName = Optional.of("John"); +Optional emptyOptional = Optional.empty(); +``` + +```javascript !! js +// JavaScript - Both null and undefined + +// undefined: Variable declared but not assigned +let x; +console.log(x); // undefined + +// null: Intentionally set to no value +let y = null; +console.log(y); // null + +// Key differences: +console.log(typeof undefined); // "undefined" +console.log(typeof null); // "object" (historical bug!) + +// Converting to number +console.log(Number(undefined)); // NaN +console.log(Number(null)); // 0 + +// Loose equality (avoid this!) +console.log(null == undefined); // true +console.log(null === undefined); // false (use strict equality) + +// Best practice: +// Use null for "intentionally empty" +function getUser(id) { + const user = database.find(id); + return user !== undefined ? user : null; +} + +// Use undefined for "not set" +function process(options) { + // If options.timeout is not provided + if (options.timeout === undefined) { + options.timeout = 5000; // Use default + } +} + +// Checking for null/undefined +function safeGet(obj) { + // Old way + if (obj !== null && obj !== undefined) { + return obj.value; + } + + // Modern way (checks for both null and undefined) + if (obj != null) { // Loose equality works here + return obj.value; + } + + // Or using nullish coalescing + return obj?.value; +} +``` + + +## Type Coercion + +JavaScript 在某些上下文中自動轉換型別 - 這是錯誤的主要來源! + + +```java !! java +// Java - No type coercion (mostly) +String result = "10" + 5; // "105" (String concatenation) +// int result = "10" + 5; // Compilation error! + +// Must explicitly convert +int num = Integer.parseInt("10"); +String str = String.valueOf(42); + +// Boolean is strict +boolean b = (1 == 2); // false +// boolean b2 = 1; // Compilation error! +``` + +```javascript !! js +// JavaScript - Automatic type coercion +console.log("10" + 5); // "105" (string concatenation) +console.log("10" - 5); // 5 (subtraction converts to number) +console.log("10" * 5); // 50 (multiplication converts to number) +console.log("10" / 5); // 2 (division converts to number) + +// String concatenation tricks +console.log(1 + "2"); // "12" +console.log(1 + 2 + "3"); // "33" (1+2=3, then "3"+"3"="33") +console.log("1" + 2 + 3); // "123" + +// Loose equality (==) with coercion +console.log(5 == "5"); // true (string converted to number) +console.log(0 == ""); // true (empty string converted to 0) +console.log(0 == "0"); // true +console.log(false == 0); // true +console.log(null == undefined); // true + +// Strict equality (===) - no coercion +console.log(5 === "5"); // false (different types) +console.log(0 === ""); // false +console.log(false === 0); // false +console.log(null === undefined); // false + +// ALWAYS use strict equality (=== and !==) +function compare(a, b) { + // ❌ Bad: loose equality + if (a == b) { // Might have unexpected behavior + // ... + } + + // ✅ Good: strict equality + if (a === b) { // Clear type checking + // ... + } +} + +// Explicit type conversion +let str = "42"; +let num = Number(str); // 42 (or NaN if invalid) +let num2 = parseInt(str); // 42 (integer only) +let num3 = parseFloat("3.14"); // 3.14 + +let numToStr = String(42); // "42" +let numToStr2 = 42.toString(); // "42" +let numToStr3 = (42).toString(); // "42" (need parentheses for literals) + +let toBool = Boolean("hello"); // true +let toBool2 = !!"hello"; // true (double negation trick) +``` + + +## Operators + +大多數運算子類似於 Java,但 JavaScript 有一些獨特的: + + +```java !! java +// Java operators +int a = 10, b = 3; + +// Arithmetic +int sum = a + b; // 13 +int diff = a - b; // 7 +int prod = a * b; // 30 +int quot = a / b; // 3 (integer division) +int rem = a % b; // 1 + +// Comparison +boolean equal = (a == b); // false +boolean notEqual = (a != b); // true +boolean greater = (a > b); // true +boolean lessOrEqual = (a <= b); // false + +// Logical +boolean and = (true && false); // false +boolean or = (true || false); // true +boolean not = !true; // false + +// Bitwise +int bitwiseAnd = a & b; // 2 +int bitwiseOr = a | b; // 11 +int bitwiseXor = a ^ b; // 9 +int bitwiseNot = ~a; // -11 +int leftShift = a << 1; // 20 +int rightShift = a >> 1; // 5 + +// Ternary +int max = (a > b) ? a : b; // 10 + +// Increment/decrement +a++; // a = a + 1 +b--; // b = b - 1 +``` + +```javascript !! js +// JavaScript operators (similar but some differences) +let a = 10, b = 3; + +// Arithmetic (same as Java, but division produces float) +let sum = a + b; // 13 +let diff = a - b; // 7 +let prod = a * b; // 30 +let quot = a / b; // 3.333... (NOT integer division!) +let rem = a % b; // 1 + +// Integer division (ES2020) +let intQuot = Math.trunc(a / b); // 3 + +// Comparison (use strict versions!) +let equal = (a === b); // false (strict) +let notEqual = (a !== b); // true (strict) +let greater = (a > b); // true +let lessOrEqual = (a <= b); // false + +// Loose equality (avoid!) +console.log(10 == "10"); // true (coerced) +console.log(10 === "10"); // false (strict) + +// Logical operators (return the actual value, not just boolean) +let logicalAnd = true && false; // false +let logicalAnd2 = 10 && 20; // 20 (returns last truthy value) +let logicalOr = true || false; // true +let logicalOr2 = 0 || 42; // 42 (returns first truthy value) +let logicalNot = !true; // false + +// Nullish coalescing (ES2020) +let value = null ?? "default"; // "default" (only for null/undefined) +let value2 = 0 ?? "default"; // 0 (0 is not null/undefined) + +// Optional chaining (ES2020) +const user = { name: "John", address: { city: "NYC" } }; +console.log(user?.address?.city); // "NYC" +console.log(user?.phone?.number); // undefined (no error) + +// Bitwise (same as Java) +let bitwiseAnd = a & b; // 2 +let bitwiseOr = a | b; // 11 +let bitwiseXor = a ^ b; // 9 +let bitwiseNot = ~a; // -11 +let leftShift = a << 1; // 20 +let rightShift = a >> 1; // 5 +let unsignedRightShift = a >>> 1; // 5 (JavaScript only) + +// Exponentiation (ES2016) +let power = 2 ** 3; // 8 (2 to the power of 3) +// Equivalent to: Math.pow(2, 3) + +// Ternary (same as Java) +let max = (a > b) ? a : b; // 10 + +// Increment/decrement (same as Java) +a++; // a = a + 1 +b--; // b = b - 1 +++a; // Pre-increment +--b; // Pre-decrement + +// Unary operators +let positive = +10; // 10 +let negative = -10; // -10 +let toNumber = +"42"; // 42 (converts string to number) +``` + + +## typeof Operator + +JavaScript 有一個 `typeof` 運算子來檢查型別: + + +```java !! java +// Java - No typeof operator +// Use instanceof or getClass() +String str = "hello"; +if (str instanceof String) { + System.out.println("It's a String"); +} + +// Reflection +Class type = str.getClass(); +System.out.println(type.getName()); // "java.lang.String" +``` + +```javascript !! js +// JavaScript - typeof operator +let num = 42; +let str = "hello"; +let bool = true; +let obj = { name: "John" }; +let arr = [1, 2, 3]; +let func = function() {}; +let nothing = null; +let undef = undefined; + +console.log(typeof num); // "number" +console.log(typeof str); // "string" +console.log(typeof bool); // "boolean" +console.log(typeof obj); // "object" +console.log(typeof arr); // "object" (arrays are objects!) +console.log(typeof func); // "function" +console.log(typeof nothing); // "object" (historical bug!) +console.log(typeof undef); // "undefined" + +// Checking for null (need special handling) +function isNull(value) { + return value === null; +} + +// Checking for array +function isArray(value) { + return Array.isArray(value); +} + +// Checking for NaN +function isNaNCheck(value) { + return Number.isNaN(value); +} + +// Pattern: type checking +function process(value) { + if (typeof value === "string") { + console.log("Processing string:", value.toUpperCase()); + } else if (typeof value === "number") { + console.log("Processing number:", value * 2); + } else if (Array.isArray(value)) { + console.log("Processing array:", value.length); + } else if (value === null) { + console.log("Processing null value"); + } else { + console.log("Unknown type"); + } +} + +process("hello"); // "Processing string: HELLO" +process(42); // "Processing number: 84" +process([1, 2, 3]); // "Processing array: 3" +process(null); // "Processing null value" +``` + + +## Common Pitfalls + +### Pitfall 1: Using == Instead of === + + +```javascript !! js +// ❌ BAD: Loose equality +console.log(0 == false); // true +console.log("" == false); // true +console.log("0" == false); // true +console.log(null == undefined); // true +console.log([] == false); // true +console.log([1] == "1"); // true + +// ✅ GOOD: Strict equality +console.log(0 === false); // false +console.log("" === false); // false +console.log("0" === false); // false +console.log(null === undefined); // false +console.log([] === false); // false +console.log([1] === "1"); // false + +// ESLint rule to enforce this +/* eslint eqeqeq: ["error", "always"] */ +``` + + +### Pitfall 2: Forgetting const/let + + +```javascript !! js +// ❌ BAD: Using var +var name = "John"; +if (true) { + var name = "Jane"; // Redeclares in same scope! +} +console.log(name); // "Jane" (unexpected!) + +// ❌ BAD: Forgetting const/let +message = "Hello"; // Creates global variable (bad practice) + +// ✅ GOOD: Using let/const +let name = "John"; +if (true) { + let name = "Jane"; // Block-scoped +} +console.log(name); // "John" (as expected) + +// ✅ GOOD: Always declare variables +const message = "Hello"; // Properly declared +``` + + +### Pitfall 3: Type Coercion Surprises + + +```javascript !! js +// Pitfall: String + Number +console.log("10" + 20); // "1020" (string concatenation) +console.log(10 + "20"); // "1020" +console.log(10 + 20 + "30"); // "3030" (10+20=30, then "30"+"30") + +// Solution: Explicitly convert +console.log(Number("10") + 20); // 30 +console.log("10" + String(20)); // "1020" + +// Pitfall: Subtraction converts to number +console.log("10" - 5); // 5 +console.log("10px" - 5); // NaN (can't convert "10px") +console.log("10" - "5"); // 5 + +// Solution: Explicit parsing +console.log(parseInt("10px") - 5); // 5 + +// Pitfall: Object to primitive +console.log(1 + {}); // "1[object Object]" +console.log({} + 1); // "[object Object]1" +console.log([] + []); // "" (empty string) +console.log([] + {}); // "[object Object]" +console.log({} + []); // "[object Object]" +``` + + +## Best Practices Summary + + +```java !! java +// Java: Clear, explicit types +public class User { + private final String name; // Use final for constants + private int age; // Regular for mutable + + public User(String name, int age) { + this.name = name; + this.age = age; + } + + public boolean isAdult() { + return age >= 18; // Clear boolean check + } +} +``` + +```javascript !! js +// JavaScript: Modern, clean syntax +class User { + constructor(name, age) { + this.name = name; // Use _ for private convention (ES2022 has #) + this._age = age; + } + + isAdult() { + return this._age >= 18; // Same check + } +} + +// Variable declaration rules +const API_URL = "https://api.example.com"; // Use const for constants +let currentUser = null; // Use let for variables that change +let counter = 0; // Use let for counters + +// Always use strict equality +if (counter === 0) { // ✅ + // ... +} + +// Avoid type coercion - be explicit +const num = Number(str); // ✅ Explicit conversion +const sum = a + b; // ✅ Clear operation +const result = `${a}${b}`; // ✅ Explicit string concatenation + +// Use template literals +const greeting = `Hello, ${name}!`; // ✅ Modern syntax + +// Check for null/undefined properly +if (user != null) { // Checks both null and undefined (loose eq ok here) + // Safe to access user +} + +// Or use optional chaining +const city = user?.address?.city; // Safe property access +``` + + +## Exercises + +### Exercise 1: Variable Declarations +Convert this Java code to idiomatic JavaScript: +```java +String name = "John"; +int age = 25; +final String CITY = "New York"; +boolean isActive = true; +``` + +### Exercise 2: Type Conversion +Fix the type coercion issues: +```javascript +function add(a, b) { + return a + b; // Should add numbers, not concatenate strings +} + +console.log(add(5, "10")); // Should output 15, not "510" +``` + +### Exercise 3: Null/Undefined Handling +Write a function that handles null/undefined values: +```javascript +function getDisplayName(user) { + // Return user.name if user exists and has a name + // Otherwise return "Guest" +} +``` + +### Exercise 4: Type Checking +Write a function that processes different types: +```javascript +function processValue(value) { + // If string: return uppercase + // If number: return doubled + // If boolean: return negated + // If array: return length + // Otherwise: return "unknown" +} +``` + +## Summary + +### Key Takeaways + +1. **Variable Declarations:** + - Use `const` by default (cannot be reassigned) + - Use `let` when you need to reassign + - Avoid `var` (it's function-scoped and confusing) + +2. **Type System:** + - JavaScript is dynamically typed (no type declarations) + - Primitive types: number, string, boolean, null, undefined, symbol, bigint + - Everything else is an object (including arrays and functions) + +3. **Type Coercion:** + - JavaScript automatically converts types in many contexts + - Always use strict equality (`===` and `!==`) + - Be explicit about type conversion + +4. **Truthy/Falsy:** + - Falsy: `false`, `0`, `""`, `null`, `undefined`, `NaN` + - Truthy: Everything else (including `{}` and `[]`) + - Use `??` for nullish coalescing, `||` for logical OR + +5. **Best Practices:** + - Prefer `const` over `let` + - Always use strict equality + - Be explicit about type conversions + - Use template literals for string interpolation + - Check for null/undefined with optional chaining (`?.`) + +### Comparison Table: Java vs JavaScript + +| Aspect | Java | JavaScript | +|--------|------|------------| +| **Variable declaration** | `String name = "John";` | `let name = "John";` or `const NAME = "John";` | +| **Constants** | `final int MAX = 100;` | `const MAX = 100;` | +| **Primitive count** | 8 primitives | 7 primitives (number, string, boolean, null, undefined, symbol, bigint) | +| **Number types** | byte, short, int, long, float, double | Just `number` (64-bit float) + `bigint` | +| **Equality** | `==` (no loose equality) | `===` (strict) and `==` (loose - avoid) | +| **Type checking** | `instanceof`, `getClass()` | `typeof`, `Array.isArray()` | +| **Type conversion** | Explicit: `Integer.parseInt()` | Implicit (coercion) or explicit: `Number()`, `String()` | +| **String concatenation** | `"Hello " + name` | `` `Hello ${name}` `` (template literal preferred) | +| **Null handling** | Only `null` | Both `null` and `undefined` | + +## What's Next? + +Now that you understand JavaScript's basic syntax and type system, let's move on to **Module 2: Control Flow and Loops**, where we'll learn how JavaScript's control structures compare to Java's! + +You'll discover: +- How `if/else` differs (and doesn't!) +- The `switch` statement differences +- Loop variations (for, for...of, for...in) +- The power of array methods (map, filter, reduce) +- Break and continue behavior + +Let's continue your JavaScript journey! diff --git a/content/docs/java2js/module-02-control-flow.mdx b/content/docs/java2js/module-02-control-flow.mdx new file mode 100644 index 0000000..8d81167 --- /dev/null +++ b/content/docs/java2js/module-02-control-flow.mdx @@ -0,0 +1,1073 @@ +--- +title: "Module 2: Control Flow and Loops" +description: "Master JavaScript's control flow statements, loops, and iteration patterns" +--- + +## Module 2: Control Flow and Loops + +In this module, we'll explore how JavaScript handles control flow and iteration. You'll find many similarities with Java, but JavaScript offers some powerful features that make iteration more expressive. + +## Learning Objectives + +By the end of this module, you will: +✅ Master conditional statements (if/else, switch, ternary) +✅ Understand all loop types (for, while, do-while, for...of, for...in) +✅ Learn functional iteration methods (map, filter, reduce) +✅ Discover短路求值 (short-circuit evaluation) +✅ Know when to use each iteration pattern + +## Conditional Statements + +### if/else Statements + +JavaScript's if/else is nearly identical to Java: + + +```java !! java +// Java - if/else +int age = 18; + +if (age >= 18) { + System.out.println("Adult"); +} else if (age >= 13) { + System.out.println("Teenager"); +} else { + System.out.println("Child"); +} + +// Ternary operator +String status = (age >= 18) ? "Adult" : "Minor"; + +// With null check +String name = null; +String displayName = (name != null) ? name : "Guest"; +``` + +```javascript !! js +// JavaScript - if/else (same syntax) +let age = 18; + +if (age >= 18) { + console.log("Adult"); +} else if (age >= 13) { + console.log("Teenager"); +} else { + console.log("Child"); +} + +// Ternary operator (same) +let status = (age >= 18) ? "Adult" : "Minor"; + +// With null/undefined check +let name = null; +let displayName = (name !== null && name !== undefined) ? name : "Guest"; + +// Or simpler with nullish coalescing +let displayName2 = name ?? "Guest"; +``` + + +### Short-Circuit Evaluation + +JavaScript uses short-circuit evaluation for logical operators: + + +```java !! java +// Java - Logical operators always return boolean +boolean isValid = true; +String name = "John"; + +// Both conditions are always evaluated to boolean +if (isValid && name != null && name.length() > 0) { + // ... +} + +// Cannot use logical operators for default values +// This doesn't work in Java: +// String displayName = name || "Guest"; // Compilation error! +``` + +```javascript !! js +// JavaScript - Short-circuit evaluation +let isValid = true; +let name = "John"; + +// && returns first falsy value or last truthy value +let result1 = isValid && name; // "John" (both truthy) +let result2 = isValid && null; // null (stops at null) +let result3 = false && name; // false (stops at false) + +// || returns first truthy value or last falsy value +let displayName = name || "Guest"; // "John" (name is truthy) +let displayName2 = null || "Guest"; // "Guest" (null is falsy) + +// Practical pattern: Default values +function greet(name) { + name = name || "Guest"; // Use "Guest" if name is falsy + console.log(`Hello, ${name}`); +} + +greet("John"); // "Hello, John" +greet(""); // "Hello, Guest" (empty string is falsy) +greet(null); // "Hello, Guest" + +// Nullish coalescing (ES2020) - only for null/undefined +function greet2(name) { + let displayName = name ?? "Guest"; + console.log(`Hello, ${displayName}`); +} + +greet2(""); // "Hello, " (empty string is NOT null/undefined) +greet2(null); // "Hello, Guest" + +// Optional chaining for safe property access +const user = { profile: { name: "John" } }; +const username = user?.profile?.name ?? "Guest"; // "John" +const username2 = user?.settings?.theme ?? "dark"; // "dark" (settings doesn't exist) +``` + + +### switch Statements + +JavaScript's switch has some important differences from Java: + + +```java !! java +// Java - switch with strict type matching +int dayOfWeek = 1; +String dayName; + +switch (dayOfWeek) { + case 1: + dayName = "Monday"; + break; + case 2: + dayName = "Tuesday"; + break; + case 3: + dayName = "Wednesday"; + break; + default: + dayName = "Unknown"; +} + +// Java - switch expressions (Java 14+) +String dayType = switch (dayOfWeek) { + case 1, 2, 3, 4, 5 -> "Weekday"; + case 6, 7 -> "Weekend"; + default -> "Invalid"; +}; +``` + +```javascript !! js +// JavaScript - switch with loose equality (be careful!) +let dayOfWeek = 1; +let dayName; + +switch (dayOfWeek) { + case 1: + dayName = "Monday"; + break; + case 2: + dayName = "Tuesday"; + break; + case "1": // ⚠️ This matches too! (loose equality) + dayName = "Monday (string)"; + break; + default: + dayName = "Unknown"; +} + +// Pitfall: Loose equality +let x = "1"; +switch (x) { + case 1: // This matches! ("1" == 1 is true) + console.log("Equal"); + break; +} + +// Solution: Use strict equality checks +function strictSwitch(value) { + if (value === 1) { + return "One"; + } else if (value === 2) { + return "Two"; + } else { + return "Unknown"; + } +} + +// Or use object lookup (better pattern) +const dayMap = { + 1: "Monday", + 2: "Tuesday", + 3: "Wednesday", + 4: "Thursday", + 5: "Friday", + 6: "Saturday", + 7: "Sunday" +}; + +const dayName2 = dayMap[dayOfWeek] ?? "Unknown"; +``` + + +## Traditional Loops + +### for Loop + +JavaScript's traditional for loop is similar to Java's: + + +```java !! java +// Java - for loop +for (int i = 0; i < 5; i++) { + System.out.println("Iteration " + i); +} + +// Enhanced for loop (for-each) +int[] numbers = {1, 2, 3, 4, 5}; +for (int num : numbers) { + System.out.println(num); +} + +// With index +for (int i = 0; i < numbers.length; i++) { + System.out.println("Index " + i + ": " + numbers[i]); +} +``` + +```javascript !! js +// JavaScript - for loop (same syntax) +for (let i = 0; i < 5; i++) { + console.log("Iteration " + i); +} + +// for...of loop (modern for-each) +const numbers = [1, 2, 3, 4, 5]; +for (const num of numbers) { + console.log(num); +} + +// With index using entries() +for (const [index, num] of numbers.entries()) { + console.log(`Index ${index}: ${num}`); +} + +// for...in loop (iterate over indices/keys) +for (const index in numbers) { + console.log(`Index ${index}: ${numbers[index]}`); +} + +// ⚠️ Be careful: for...in is for objects, not arrays +const user = { name: "John", age: 25 }; +for (const key in user) { + console.log(`${key}: ${user[key]}`); +} +``` + + +### while and do-while Loops + +These are identical to Java: + + +```java !! java +// Java - while loop +int count = 0; +while (count < 5) { + System.out.println("Count: " + count); + count++; +} + +// do-while loop +int i = 0; +do { + System.out.println("At least once: " + i); + i++; +} while (i < 5); +``` + +```javascript !! js +// JavaScript - while loop (same) +let count = 0; +while (count < 5) { + console.log("Count: " + count); + count++; +} + +// do-while loop (same) +let i = 0; +do { + console.log("At least once: " + i); + i++; +} while (i < 5); + +// Practical example: Input validation +function guessNumber() { + let guess; + let target = 42; + + do { + guess = parseInt(prompt("Guess a number:")); + if (guess < target) { + console.log("Too low!"); + } else if (guess > target) { + console.log("Too high!"); + } + } while (guess !== target); + + console.log("Correct!"); +} +``` + + +### break and continue + +JavaScript supports break and continue just like Java: + + +```java !! java +// Java - break and continue +for (int i = 0; i < 10; i++) { + if (i == 5) { + break; // Exit loop + } + if (i % 2 == 0) { + continue; // Skip to next iteration + } + System.out.println("Odd number: " + i); +} + +// Labeled breaks (for nested loops) +outer: +for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + if (i == 1 && j == 1) { + break outer; // Exit outer loop + } + System.out.println(i + ", " + j); + } +} +``` + +```javascript !! js +// JavaScript - break and continue (same) +for (let i = 0; i < 10; i++) { + if (i === 5) { + break; // Exit loop + } + if (i % 2 === 0) { + continue; // Skip to next iteration + } + console.log("Odd number: " + i); +} + +// Labeled breaks (same as Java) +outer: +for (let i = 0; i < 3; i++) { + for (let j = 0; j < 3; j++) { + if (i === 1 && j === 1) { + break outer; // Exit outer loop + } + console.log(`${i}, ${j}`); + } +} + +// Practical example: Search +function findUser(users, targetId) { + for (const user of users) { + if (user.id === targetId) { + return user; // Found it, exit early + } + } + return null; // Not found +} +``` + + +## Modern Iteration Methods + +JavaScript provides powerful array methods that Java developers will recognize from streams: + +### Array.prototype.forEach + + +```java !! java +// Java - for-each loop +List names = Arrays.asList("Alice", "Bob", "Charlie"); +for (String name : names) { + System.out.println(name); +} + +// Java 8+ forEach with lambda +names.forEach(name -> System.out.println(name)); + +// With index (need IntStream) +IntStream.range(0, names.size()) + .forEach(i -> System.out.println(i + ": " + names.get(i))); +``` + +```javascript !! js +// JavaScript - forEach method +const names = ["Alice", "Bob", "Charlie"]; +names.forEach(name => { + console.log(name); +}); + +// With index +names.forEach((name, index) => { + console.log(`${index}: ${name}`); +}); + +// Note: Cannot break out of forEach! +// Use a for...of loop if you need to break +for (const name of names) { + if (name === "Bob") break; // This works + console.log(name); +} + +// forEach doesn't return anything +const result = names.forEach(name => name.toUpperCase()); +console.log(result); // undefined +``` + + +### Array.prototype.map + +Creates a new array by transforming each element: + + +```java !! java +// Java - Stream.map() +List names = Arrays.asList("alice", "bob", "charlie"); +List upperNames = names.stream() + .map(String::toUpperCase) + .collect(Collectors.toList()); + +// Or with lambda +List lengths = names.stream() + .map(name -> name.length()) + .collect(Collectors.toList()); +``` + +```javascript !! js +// JavaScript - Array.map() +const names = ["alice", "bob", "charlie"]; +const upperNames = names.map(name => name.toUpperCase()); +console.log(upperNames); // ["ALICE", "BOB", "CHARLIE"] + +// Get lengths +const lengths = names.map(name => name.length()); +console.log(lengths); // [5, 3, 7] + +// Transform objects +const users = [ + { id: 1, name: "Alice" }, + { id: 2, name: "Bob" } +]; + +const userIds = users.map(user => user.id); +console.log(userIds); // [1, 2] + +// With index +const numbered = names.map((name, i) => `${i + 1}. ${name}`); +console.log(numbered); // ["1. Alice", "2. Bob", "3. Charlie"] +``` + + +### Array.prototype.filter + +Creates a new array with elements that pass a test: + + +```java !! java +// Java - Stream.filter() +List numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); +List evens = numbers.stream() + .filter(n -> n % 2 == 0) + .collect(Collectors.toList()); + +// Chain multiple operations +List result = numbers.stream() + .filter(n -> n % 2 == 0) + .filter(n -> n > 5) + .collect(Collectors.toList()); +``` + +```javascript !! js +// JavaScript - Array.filter() +const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; +const evens = numbers.filter(n => n % 2 === 0); +console.log(evens); // [2, 4, 6, 8, 10] + +// Chain methods +const result = numbers + .filter(n => n % 2 === 0) + .filter(n => n > 5); +console.log(result); // [6, 8, 10] + +// Filter objects +const users = [ + { id: 1, name: "Alice", age: 25 }, + { id: 2, name: "Bob", age: 17 }, + { id: 3, name: "Charlie", age: 30 } +]; + +const adults = users.filter(user => user.age >= 18); +console.log(adults); // [{ id: 1, ... }, { id: 3, ... }] + +// Remove null/undefined values +const values = [1, null, 2, undefined, 3, null, 4]; +const clean = values.filter(v => v != null); +console.log(clean); // [1, 2, 3, 4] +``` + + +### Array.prototype.reduce + +Reduces array to single value: + + +```java !! java +// Java - Stream.reduce() +List numbers = Arrays.asList(1, 2, 3, 4, 5); + +int sum = numbers.stream() + .reduce(0, (a, b) -> a + b); + +int product = numbers.stream() + .reduce(1, (a, b) -> a * b); + +// With Optional +Optional max = numbers.stream() + .reduce(Integer::max); +``` + +```javascript !! js +// JavaScript - Array.reduce() +const numbers = [1, 2, 3, 4, 5]; + +const sum = numbers.reduce((acc, n) => acc + n, 0); +console.log(sum); // 15 + +const product = numbers.reduce((acc, n) => acc * n, 1); +console.log(product); // 120 + +// Find max +const max = numbers.reduce((acc, n) => Math.max(acc, n), -Infinity); +console.log(max); // 5 + +// Group by +const users = [ + { name: "Alice", role: "admin" }, + { name: "Bob", role: "user" }, + { name: "Charlie", role: "admin" } +]; + +const byRole = users.reduce((acc, user) => { + if (!acc[user.role]) { + acc[user.role] = []; + } + acc[user.role].push(user); + return acc; +}, {}); + +console.log(byRole); +// { +// admin: [{ name: "Alice", ... }, { name: "Charlie", ... }], +// user: [{ name: "Bob", ... }] +// } +``` + + +### Other Array Methods + + +```java !! java +// Java - Various stream operations +List names = Arrays.asList("Alice", "Bob", "Charlie"); + +// Find first +Optional first = names.stream().findFirst(); + +// Any match +boolean hasAlice = names.stream().anyMatch(n -> n.equals("Alice")); + +// All match +boolean allLong = names.stream().allMatch(n -> n.length() > 3); + +// Count +long count = names.stream().count(); + +// Limit +List firstTwo = names.stream().limit(2).collect(Collectors.toList()); +``` + +```javascript !! js +// JavaScript - Array methods +const names = ["Alice", "Bob", "Charlie"]; + +// Find first (returns element, not Optional) +const first = names[0]; // "Alice" +const firstFind = names.find(name => name.startsWith("A")); // "Alice" + +// Find index +const indexOfBob = names.indexOf("Bob"); // 1 +const findIndex = names.findIndex(name => name.startsWith("B")); // 1 + +// Check if exists +const hasAlice = names.includes("Alice"); // true +const hasLongName = names.some(name => name.length > 3); // true +const allLong = names.every(name => name.length > 3); // false + +// Count +const count = names.length; // 3 + +// Slice (first two) +const firstTwo = names.slice(0, 2); // ["Alice", "Bob"] + +// Flat map (ES2019) +const matrix = [[1, 2], [3, 4], [5, 6]]; +const flattened = matrix.flat(); // [1, 2, 3, 4, 5, 6] + +// Sort (modifies in place!) +const nums = [3, 1, 4, 1, 5]; +nums.sort((a, b) => a - b); // [1, 1, 3, 4, 5] +``` + + +## Method Chaining + +JavaScript array methods can be chained just like Java streams: + + +```java !! java +// Java - Stream chaining +List result = numbers.stream() + .filter(n -> n % 2 == 0) + .map(n -> n * 2) + .limit(5) + .collect(Collectors.toList()); +``` + +```javascript !! js +// JavaScript - Array method chaining +const result = numbers + .filter(n => n % 2 === 0) + .map(n => n * 2) + .slice(0, 5); + +// Real-world example +const users = [ + { name: "Alice", age: 25, score: 85 }, + { name: "Bob", age: 17, score: 92 }, + { name: "Charlie", age: 30, score: 78 }, + { name: "David", age: 22, score: 88 } +]; + +// Get names of adult users with high scores, sorted +const topAdults = users + .filter(user => user.age >= 18) // Filter adults + .filter(user => user.score >= 80) // Filter high scores + .sort((a, b) => b.score - a.score) // Sort by score desc + .map(user => user.name); // Get names + +console.log(topAdults); // ["Bob", "David", "Alice"] + +// Calculate average score +const averageScore = users + .map(user => user.score) + .reduce((sum, score, _, arr) => sum + score / arr.length, 0); + +console.log(averageScore); // 85.75 +``` + + +## Iteration Patterns + +### Pattern 1: Transform and Filter + + +```java !! java +// Java +List words = Arrays.asList("hello", "world", "java", "stream"); + +List result = words.stream() + .filter(w -> w.length() > 4) + .map(String::toUpperCase) + .sorted() + .collect(Collectors.toList()); +``` + +```javascript !! js +// JavaScript +const words = ["hello", "world", "java", "javascript"]; + +const result = words + .filter(w => w.length > 4) + .map(w => w.toUpperCase()) + .sort(); + +console.log(result); // ["HELLO", "JAVASCRIPT", "WORLD"] +``` + + +### Pattern 2: Find and Update + + +```java !! java +// Java - Immutable approach +List users = getUsers(); +User updatedUser = users.stream() + .filter(u -> u.getId() == targetId) + .findFirst() + .map(u -> new User(u.getId(), u.getName(), true)) + .orElse(null); +``` + +```javascript !! js +// JavaScript - Find and update +const users = [ + { id: 1, name: "Alice", active: false }, + { id: 2, name: "Bob", active: false } +]; + +const targetId = 1; +const updatedUsers = users.map(user => { + if (user.id === targetId) { + return { ...user, active: true }; + } + return user; +}); + +console.log(updatedUsers); +// [ +// { id: 1, name: "Alice", active: true }, +// { id: 2, name: "Bob", active: false } +// ] +``` + + +### Pattern 3: Reduce to Object + + +```java !! java +// Java - Group by with Collectors.groupingBy +List users = getUsers(); +Map> byRole = users.stream() + .collect(Collectors.groupingBy(User::getRole)); +``` + +```javascript !! js +// JavaScript - Group by with reduce +const users = [ + { name: "Alice", role: "admin" }, + { name: "Bob", role: "user" }, + { name: "Charlie", role: "admin" } +]; + +const byRole = users.reduce((acc, user) => ({ + ...acc, + [user.role]: [...(acc[user.role] || []), user] +}), {}); + +console.log(byRole); +// { +// admin: [ +// { name: "Alice", role: "admin" }, +// { name: "Charlie", role: "admin" } +// ], +// user: [{ name: "Bob", role: "user" }] +// } +``` + + +## Performance Considerations + + +```java !! java +// Java - Streams have overhead +// For simple operations, traditional loops can be faster +List numbers = /* large list */; + +// Stream (cleaner but some overhead) +List result = numbers.stream() + .filter(n -> n > 0) + .collect(Collectors.toList()); + +// Traditional loop (faster for simple cases) +List result2 = new ArrayList<>(); +for (Integer n : numbers) { + if (n > 0) { + result2.add(n); + } +} +``` + +```javascript !! js +// JavaScript - Similar trade-offs +const numbers = /* large array */; + +// Array methods (cleaner, similar performance) +const result = numbers.filter(n => n > 0); + +// Traditional for loop (often faster) +const result2 = []; +for (let i = 0; i < numbers.length; i++) { + if (numbers[i] > 0) { + result2.push(numbers[i]); + } +} + +// for...of (clean, good middle ground) +const result3 = []; +for (const n of numbers) { + if (n > 0) { + result3.push(n); + } +} + +// General rule: +// - Use array methods for readability (usually fast enough) +// - Use traditional loops for performance-critical code +// - Avoid forEach when you might need to break (use for...of) +``` + + +## Common Pitfalls + +### Pitfall 1: Using for...in on Arrays + + +```javascript !! js +// ❌ BAD: for...in iterates over keys, not values +const arr = [10, 20, 30]; +for (const i in arr) { + console.log(i); // "0", "1", "2" (strings, not numbers!) + console.log(arr[i]); // 10, 20, 30 +} + +// Also iterates over inherited properties +Array.prototype.customProp = "inherited"; +for (const i in arr) { + console.log(i); // "0", "1", "2", "customProp" +} + +// ✅ GOOD: Use for...of for arrays +for (const value of arr) { + console.log(value); // 10, 20, 30 +} + +// ✅ GOOD: Use forEach for index and value +arr.forEach((value, index) => { + console.log(`${index}: ${value}`); +}); +``` + + +### Pitfall 2: Modifying Array While Iterating + + +```javascript !! js +// ❌ BAD: Removing elements during iteration +const numbers = [1, 2, 3, 4, 5]; +for (let i = 0; i < numbers.length; i++) { + if (numbers[i] % 2 === 0) { + numbers.splice(i, 1); // Removes element + } +} +// Result: [1, 3, 5] but skips some elements! + +// ✅ GOOD: Filter instead +const filtered = numbers.filter(n => n % 2 !== 0); +// Result: [1, 3, 5] + +// ✅ GOOD: Iterate backwards if you must modify +for (let i = numbers.length - 1; i >= 0; i--) { + if (numbers[i] % 2 === 0) { + numbers.splice(i, 1); + } +} +``` + + +### Pitfall 3: Forgetting Return in Array Methods + + +```javascript !! js +// ❌ BAD: Forgetting return +const numbers = [1, 2, 3, 4, 5]; +const doubled = numbers.map(n => { + n * 2; // No return! Results in [undefined, undefined, ...] +}); + +// ✅ GOOD: Explicit return +const doubled2 = numbers.map(n => { + return n * 2; +}); + +// ✅ GOOD: Implicit return (one-liner) +const doubled3 = numbers.map(n => n * 2); + +// ⚠️ Watch out with curly braces +const doubled4 = numbers.map(n => { n * 2 }); // Wrong! +const doubled5 = numbers.map(n => ({ value: n * 2 })); // Need parentheses for object +``` + + +## Best Practices + + +```java !! java +// Java: Use streams for readability +List names = Arrays.asList("alice", "bob", "charlie"); + +List result = names.stream() + .filter(n -> n.length() > 3) + .map(String::toUpperCase) + .sorted() + .collect(Collectors.toList()); + +// Use traditional loops for performance or when need to break +for (String name : names) { + if (name.equals("bob")) break; + System.out.println(name); +} +``` + +```javascript !! js +// JavaScript: Use array methods for readability +const names = ["alice", "bob", "charlie"]; + +const result = names + .filter(n => n.length > 3) + .map(n => n.toUpperCase()) + .sort(); + +// Use for...of when you need to break +for (const name of names) { + if (name === "bob") break; + console.log(name); +} + +// Use reduce for complex aggregations +const summary = names.reduce((acc, name) => ({ + count: acc.count + 1, + totalLength: acc.totalLength + name.length, + avgLength: (acc.totalLength + name.length) / (acc.count + 1) +}), { count: 0, totalLength: 0, avgLength: 0 }); + +// Avoid forEach when you could use map/filter +// ❌ BAD +const results = []; +items.forEach(item => { + if (item.isValid) { + results.push(transform(item)); + } +}); + +// ✅ GOOD +const results = items + .filter(item => item.isValid) + .map(transform); +``` + + +## Exercises + +### Exercise 1: Array Transformation +Convert this Java code to JavaScript: +```java +List numbers = Arrays.asList(1, 2, 3, 4, 5); +List squares = numbers.stream() + .filter(n -> n % 2 == 0) + .map(n -> n * n) + .collect(Collectors.toList()); +``` + +### Exercise 2: Find User +Write a function to find a user by ID: +```javascript +const users = [ + { id: 1, name: "Alice" }, + { id: 2, name: "Bob" }, + { id: 3, name: "Charlie" } +]; + +function findUser(users, targetId) { + // Return the user with matching ID, or null +} +``` + +### Exercise 3: Reduce to Object +Group users by role: +```javascript +const users = [ + { name: "Alice", role: "admin" }, + { name: "Bob", role: "user" }, + { name: "Charlie", role: "admin" } +]; + +// Create object: { admin: [...], user: [...] } +``` + +### Exercise 4: Conditional Logic +Fix the code to use strict equality: +```javascript +function processValue(value) { + if (value == "0") { // Bug: uses loose equality + return "zero"; + } + return value; +} +``` + +## Summary + +### Key Takeaways + +1. **Conditionals:** + - if/else works the same as Java + - Switch uses loose equality (be careful!) + - Ternary operator is identical + - Short-circuit evaluation provides default values + +2. **Loops:** + - Traditional for/while/do-while are identical + - for...of for array values (like Java's for-each) + - for...in for object keys (NOT arrays!) + - break and continue work the same + +3. **Array Methods:** + - forEach: Iterate (can't break) + - map: Transform to new array + - filter: Select elements + - reduce: Aggregate to single value + - find/findIndex: Search elements + - some/every: Test conditions + +4. **Method Chaining:** + - Array methods can be chained + - Similar to Java streams + - More readable than nested loops + +5. **Best Practices:** + - Use for...of for iteration (can break) + - Use array methods for transformations + - Avoid for...in on arrays + - Don't modify arrays while iterating + +## What's Next? + +You've mastered control flow and iteration! Next up is **Module 3: Functions**, where we'll explore JavaScript's powerful function features: + +- Function declarations vs expressions +- Arrow functions +- Closures +- Higher-order functions +- Default parameters +- Rest and spread parameters +- The `this` keyword in functions + +Ready to dive deeper into JavaScript functions? Let's go! diff --git a/content/docs/java2js/module-02-control-flow.zh-cn.mdx b/content/docs/java2js/module-02-control-flow.zh-cn.mdx new file mode 100644 index 0000000..f93ca70 --- /dev/null +++ b/content/docs/java2js/module-02-control-flow.zh-cn.mdx @@ -0,0 +1,1073 @@ +--- +title: "模块 2: 控制流和循环" +description: "掌握 JavaScript 的控制流语句、循环和迭代模式" +--- + +## 模块 2: 控制流和循环 + +在本模块中,我们将探索 JavaScript 如何处理控制流和迭代。你会发现它与 Java 有很多相似之处,但 JavaScript 提供了一些强大的功能,使迭代更加表达性强。 + +## 学习目标 + +完成本模块后,你将: +✅ 掌握条件语句(if/else、switch、三元运算符) +✅ 理解所有循环类型(for、while、do-while、for...of、for...in) +✅ 学习函数式迭代方法(map、filter、reduce) +✅ 了解短路求值(short-circuit evaluation) +✅ 知道何时使用每种迭代模式 + +## 条件语句 + +### if/else 语句 + +JavaScript 的 if/else 与 Java 几乎完全相同: + + +```java !! java +// Java - if/else +int age = 18; + +if (age >= 18) { + System.out.println("Adult"); +} else if (age >= 13) { + System.out.println("Teenager"); +} else { + System.out.println("Child"); +} + +// Ternary operator +String status = (age >= 18) ? "Adult" : "Minor"; + +// With null check +String name = null; +String displayName = (name != null) ? name : "Guest"; +``` + +```javascript !! js +// JavaScript - if/else (same syntax) +let age = 18; + +if (age >= 18) { + console.log("Adult"); +} else if (age >= 13) { + console.log("Teenager"); +} else { + console.log("Child"); +} + +// Ternary operator (same) +let status = (age >= 18) ? "Adult" : "Minor"; + +// With null/undefined check +let name = null; +let displayName = (name !== null && name !== undefined) ? name : "Guest"; + +// Or simpler with nullish coalescing +let displayName2 = name ?? "Guest"; +``` + + +### 短路求值 + +JavaScript 对逻辑运算符使用短路求值: + + +```java !! java +// Java - 逻辑运算符总是返回布尔值 +boolean isValid = true; +String name = "John"; + +// 两个条件总是被求值为布尔值 +if (isValid && name != null && name.length() > 0) { + // ... +} + +// 不能使用逻辑运算符来设置默认值 +// 这在 Java 中不起作用: +// String displayName = name || "Guest"; // Compilation error! +``` + +```javascript !! js +// JavaScript - 短路求值 +let isValid = true; +let name = "John"; + +// && 返回第一个假值或最后一个真值 +let result1 = isValid && name; // "John" (both truthy) +let result2 = isValid && null; // null (stops at null) +let result3 = false && name; // false (stops at false) + +// || 返回第一个真值或最后一个假值 +let displayName = name || "Guest"; // "John" (name is truthy) +let displayName2 = null || "Guest"; // "Guest" (null is falsy) + +// Practical pattern: Default values +function greet(name) { + name = name || "Guest"; // Use "Guest" if name is falsy + console.log(`Hello, ${name}`); +} + +greet("John"); // "Hello, John" +greet(""); // "Hello, Guest" (empty string is falsy) +greet(null); // "Hello, Guest" + +// Nullish coalescing (ES2020) - only for null/undefined +function greet2(name) { + let displayName = name ?? "Guest"; + console.log(`Hello, ${displayName}`); +} + +greet2(""); // "Hello, " (empty string is NOT null/undefined) +greet2(null); // "Hello, Guest" + +// Optional chaining for safe property access +const user = { profile: { name: "John" } }; +const username = user?.profile?.name ?? "Guest"; // "John" +const username2 = user?.settings?.theme ?? "dark"; // "dark" (settings doesn't exist) +``` + + +### switch 语句 + +JavaScript 的 switch 与 Java 有一些重要区别: + + +```java !! java +// Java - switch with strict type matching +int dayOfWeek = 1; +String dayName; + +switch (dayOfWeek) { + case 1: + dayName = "Monday"; + break; + case 2: + dayName = "Tuesday"; + break; + case 3: + dayName = "Wednesday"; + break; + default: + dayName = "Unknown"; +} + +// Java - switch expressions (Java 14+) +String dayType = switch (dayOfWeek) { + case 1, 2, 3, 4, 5 -> "Weekday"; + case 6, 7 -> "Weekend"; + default -> "Invalid"; +}; +``` + +```javascript !! js +// JavaScript - switch with loose equality (be careful!) +let dayOfWeek = 1; +let dayName; + +switch (dayOfWeek) { + case 1: + dayName = "Monday"; + break; + case 2: + dayName = "Tuesday"; + break; + case "1": // ⚠️ This matches too! (loose equality) + dayName = "Monday (string)"; + break; + default: + dayName = "Unknown"; +} + +// Pitfall: Loose equality +let x = "1"; +switch (x) { + case 1: // This matches! ("1" == 1 is true) + console.log("Equal"); + break; +} + +// Solution: Use strict equality checks +function strictSwitch(value) { + if (value === 1) { + return "One"; + } else if (value === 2) { + return "Two"; + } else { + return "Unknown"; + } +} + +// Or use object lookup (better pattern) +const dayMap = { + 1: "Monday", + 2: "Tuesday", + 3: "Wednesday", + 4: "Thursday", + 5: "Friday", + 6: "Saturday", + 7: "Sunday" +}; + +const dayName2 = dayMap[dayOfWeek] ?? "Unknown"; +``` + + +## 传统循环 + +### for 循环 + +JavaScript 的传统 for 循环与 Java 类似: + + +```java !! java +// Java - for loop +for (int i = 0; i < 5; i++) { + System.out.println("Iteration " + i); +} + +// Enhanced for loop (for-each) +int[] numbers = {1, 2, 3, 4, 5}; +for (int num : numbers) { + System.out.println(num); +} + +// With index +for (int i = 0; i < numbers.length; i++) { + System.out.println("Index " + i + ": " + numbers[i]); +} +``` + +```javascript !! js +// JavaScript - for loop (same syntax) +for (let i = 0; i < 5; i++) { + console.log("Iteration " + i); +} + +// for...of loop (modern for-each) +const numbers = [1, 2, 3, 4, 5]; +for (const num of numbers) { + console.log(num); +} + +// With index using entries() +for (const [index, num] of numbers.entries()) { + console.log(`Index ${index}: ${num}`); +} + +// for...in loop (iterate over indices/keys) +for (const index in numbers) { + console.log(`Index ${index}: ${numbers[index]}`); +} + +// ⚠️ Be careful: for...in is for objects, not arrays +const user = { name: "John", age: 25 }; +for (const key in user) { + console.log(`${key}: ${user[key]}`); +} +``` + + +### while 和 do-while 循环 + +这些与 Java 完全相同: + + +```java !! java +// Java - while loop +int count = 0; +while (count < 5) { + System.out.println("Count: " + count); + count++; +} + +// do-while loop +int i = 0; +do { + System.out.println("At least once: " + i); + i++; +} while (i < 5); +``` + +```javascript !! js +// JavaScript - while loop (same) +let count = 0; +while (count < 5) { + console.log("Count: " + count); + count++; +} + +// do-while loop (same) +let i = 0; +do { + console.log("At least once: " + i); + i++; +} while (i < 5); + +// Practical example: Input validation +function guessNumber() { + let guess; + let target = 42; + + do { + guess = parseInt(prompt("Guess a number:")); + if (guess < target) { + console.log("Too low!"); + } else if (guess > target) { + console.log("Too high!"); + } + } while (guess !== target); + + console.log("Correct!"); +} +``` + + +### break 和 continue + +JavaScript 像 Java 一样支持 break 和 continue: + + +```java !! java +// Java - break and continue +for (int i = 0; i < 10; i++) { + if (i == 5) { + break; // Exit loop + } + if (i % 2 == 0) { + continue; // Skip to next iteration + } + System.out.println("Odd number: " + i); +} + +// Labeled breaks (for nested loops) +outer: +for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + if (i == 1 && j == 1) { + break outer; // Exit outer loop + } + System.out.println(i + ", " + j); + } +} +``` + +```javascript !! js +// JavaScript - break and continue (same) +for (let i = 0; i < 10; i++) { + if (i === 5) { + break; // Exit loop + } + if (i % 2 === 0) { + continue; // Skip to next iteration + } + console.log("Odd number: " + i); +} + +// Labeled breaks (same as Java) +outer: +for (let i = 0; i < 3; i++) { + for (let j = 0; j < 3; j++) { + if (i === 1 && j === 1) { + break outer; // Exit outer loop + } + console.log(`${i}, ${j}`); + } +} + +// Practical example: Search +function findUser(users, targetId) { + for (const user of users) { + if (user.id === targetId) { + return user; // Found it, exit early + } + } + return null; // Not found +} +``` + + +## 现代迭代方法 + +JavaScript 提供了强大的数组方法,Java 开发者会从 streams 中认出它们: + +### Array.prototype.forEach + + +```java !! java +// Java - for-each loop +List names = Arrays.asList("Alice", "Bob", "Charlie"); +for (String name : names) { + System.out.println(name); +} + +// Java 8+ forEach with lambda +names.forEach(name -> System.out.println(name)); + +// With index (need IntStream) +IntStream.range(0, names.size()) + .forEach(i -> System.out.println(i + ": " + names.get(i))); +``` + +```javascript !! js +// JavaScript - forEach method +const names = ["Alice", "Bob", "Charlie"]; +names.forEach(name => { + console.log(name); +}); + +// With index +names.forEach((name, index) => { + console.log(`${index}: ${name}`); +}); + +// Note: Cannot break out of forEach! +// Use a for...of loop if you need to break +for (const name of names) { + if (name === "Bob") break; // This works + console.log(name); +} + +// forEach doesn't return anything +const result = names.forEach(name => name.toUpperCase()); +console.log(result); // undefined +``` + + +### Array.prototype.map + +通过转换每个元素来创建新数组: + + +```java !! java +// Java - Stream.map() +List names = Arrays.asList("alice", "bob", "charlie"); +List upperNames = names.stream() + .map(String::toUpperCase) + .collect(Collectors.toList()); + +// Or with lambda +List lengths = names.stream() + .map(name -> name.length()) + .collect(Collectors.toList()); +``` + +```javascript !! js +// JavaScript - Array.map() +const names = ["alice", "bob", "charlie"]; +const upperNames = names.map(name => name.toUpperCase()); +console.log(upperNames); // ["ALICE", "BOB", "CHARLIE"] + +// Get lengths +const lengths = names.map(name => name.length()); +console.log(lengths); // [5, 3, 7] + +// Transform objects +const users = [ + { id: 1, name: "Alice" }, + { id: 2, name: "Bob" } +]; + +const userIds = users.map(user => user.id); +console.log(userIds); // [1, 2] + +// With index +const numbered = names.map((name, i) => `${i + 1}. ${name}`); +console.log(numbered); // ["1. Alice", "2. Bob", "3. Charlie"] +``` + + +### Array.prototype.filter + +创建一个包含通过测试的元素的新数组: + + +```java !! java +// Java - Stream.filter() +List numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); +List evens = numbers.stream() + .filter(n -> n % 2 == 0) + .collect(Collectors.toList()); + +// Chain multiple operations +List result = numbers.stream() + .filter(n -> n % 2 == 0) + .filter(n -> n > 5) + .collect(Collectors.toList()); +``` + +```javascript !! js +// JavaScript - Array.filter() +const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; +const evens = numbers.filter(n => n % 2 === 0); +console.log(evens); // [2, 4, 6, 8, 10] + +// Chain methods +const result = numbers + .filter(n => n % 2 === 0) + .filter(n => n > 5); +console.log(result); // [6, 8, 10] + +// Filter objects +const users = [ + { id: 1, name: "Alice", age: 25 }, + { id: 2, name: "Bob", age: 17 }, + { id: 3, name: "Charlie", age: 30 } +]; + +const adults = users.filter(user => user.age >= 18); +console.log(adults); // [{ id: 1, ... }, { id: 3, ... }] + +// Remove null/undefined values +const values = [1, null, 2, undefined, 3, null, 4]; +const clean = values.filter(v => v != null); +console.log(clean); // [1, 2, 3, 4] +``` + + +### Array.prototype.reduce + +将数组归约为单个值: + + +```java !! java +// Java - Stream.reduce() +List numbers = Arrays.asList(1, 2, 3, 4, 5); + +int sum = numbers.stream() + .reduce(0, (a, b) -> a + b); + +int product = numbers.stream() + .reduce(1, (a, b) -> a * b); + +// With Optional +Optional max = numbers.stream() + .reduce(Integer::max); +``` + +```javascript !! js +// JavaScript - Array.reduce() +const numbers = [1, 2, 3, 4, 5]; + +const sum = numbers.reduce((acc, n) => acc + n, 0); +console.log(sum); // 15 + +const product = numbers.reduce((acc, n) => acc * n, 1); +console.log(product); // 120 + +// Find max +const max = numbers.reduce((acc, n) => Math.max(acc, n), -Infinity); +console.log(max); // 5 + +// Group by +const users = [ + { name: "Alice", role: "admin" }, + { name: "Bob", role: "user" }, + { name: "Charlie", role: "admin" } +]; + +const byRole = users.reduce((acc, user) => { + if (!acc[user.role]) { + acc[user.role] = []; + } + acc[user.role].push(user); + return acc; +}, {}); + +console.log(byRole); +// { +// admin: [{ name: "Alice", ... }, { name: "Charlie", ... }], +// user: [{ name: "Bob", ... }] +// } +``` + + +### 其他数组方法 + + +```java !! java +// Java - Various stream operations +List names = Arrays.asList("Alice", "Bob", "Charlie"); + +// Find first +Optional first = names.stream().findFirst(); + +// Any match +boolean hasAlice = names.stream().anyMatch(n -> n.equals("Alice")); + +// All match +boolean allLong = names.stream().allMatch(n -> n.length() > 3); + +// Count +long count = names.stream().count(); + +// Limit +List firstTwo = names.stream().limit(2).collect(Collectors.toList()); +``` + +```javascript !! js +// JavaScript - Array methods +const names = ["Alice", "Bob", "Charlie"]; + +// Find first (returns element, not Optional) +const first = names[0]; // "Alice" +const firstFind = names.find(name => name.startsWith("A")); // "Alice" + +// Find index +const indexOfBob = names.indexOf("Bob"); // 1 +const findIndex = names.findIndex(name => name.startsWith("B")); // 1 + +// Check if exists +const hasAlice = names.includes("Alice"); // true +const hasLongName = names.some(name => name.length > 3); // true +const allLong = names.every(name => name.length > 3); // false + +// Count +const count = names.length; // 3 + +// Slice (first two) +const firstTwo = names.slice(0, 2); // ["Alice", "Bob"] + +// Flat map (ES2019) +const matrix = [[1, 2], [3, 4], [5, 6]]; +const flattened = matrix.flat(); // [1, 2, 3, 4, 5, 6] + +// Sort (modifies in place!) +const nums = [3, 1, 4, 1, 5]; +nums.sort((a, b) => a - b); // [1, 1, 3, 4, 5] +``` + + +## 方法链 + +JavaScript 数组方法可以像 Java streams 一样链接: + + +```java !! java +// Java - Stream chaining +List result = numbers.stream() + .filter(n -> n % 2 == 0) + .map(n -> n * 2) + .limit(5) + .collect(Collectors.toList()); +``` + +```javascript !! js +// JavaScript - Array method chaining +const result = numbers + .filter(n => n % 2 === 0) + .map(n => n * 2) + .slice(0, 5); + +// Real-world example +const users = [ + { name: "Alice", age: 25, score: 85 }, + { name: "Bob", age: 17, score: 92 }, + { name: "Charlie", age: 30, score: 78 }, + { name: "David", age: 22, score: 88 } +]; + +// Get names of adult users with high scores, sorted +const topAdults = users + .filter(user => user.age >= 18) // Filter adults + .filter(user => user.score >= 80) // Filter high scores + .sort((a, b) => b.score - a.score) // Sort by score desc + .map(user => user.name); // Get names + +console.log(topAdults); // ["Bob", "David", "Alice"] + +// Calculate average score +const averageScore = users + .map(user => user.score) + .reduce((sum, score, _, arr) => sum + score / arr.length, 0); + +console.log(averageScore); // 85.75 +``` + + +## 迭代模式 + +### 模式 1: 转换和过滤 + + +```java !! java +// Java +List words = Arrays.asList("hello", "world", "java", "stream"); + +List result = words.stream() + .filter(w -> w.length() > 4) + .map(String::toUpperCase) + .sorted() + .collect(Collectors.toList()); +``` + +```javascript !! js +// JavaScript +const words = ["hello", "world", "java", "javascript"]; + +const result = words + .filter(w => w.length > 4) + .map(w => w.toUpperCase()) + .sort(); + +console.log(result); // ["HELLO", "JAVASCRIPT", "WORLD"] +``` + + +### 模式 2: 查找和更新 + + +```java !! java +// Java - Immutable approach +List users = getUsers(); +User updatedUser = users.stream() + .filter(u -> u.getId() == targetId) + .findFirst() + .map(u -> new User(u.getId(), u.getName(), true)) + .orElse(null); +``` + +```javascript !! js +// JavaScript - Find and update +const users = [ + { id: 1, name: "Alice", active: false }, + { id: 2, name: "Bob", active: false } +]; + +const targetId = 1; +const updatedUsers = users.map(user => { + if (user.id === targetId) { + return { ...user, active: true }; + } + return user; +}); + +console.log(updatedUsers); +// [ +// { id: 1, name: "Alice", active: true }, +// { id: 2, name: "Bob", active: false } +// ] +``` + + +### 模式 3: 归约为对象 + + +```java !! java +// Java - Group by with Collectors.groupingBy +List users = getUsers(); +Map> byRole = users.stream() + .collect(Collectors.groupingBy(User::getRole)); +``` + +```javascript !! js +// JavaScript - Group by with reduce +const users = [ + { name: "Alice", role: "admin" }, + { name: "Bob", role: "user" }, + { name: "Charlie", role: "admin" } +]; + +const byRole = users.reduce((acc, user) => ({ + ...acc, + [user.role]: [...(acc[user.role] || []), user] +}), {}); + +console.log(byRole); +// { +// admin: [ +// { name: "Alice", role: "admin" }, +// { name: "Charlie", role: "admin" } +// ], +// user: [{ name: "Bob", role: "user" }] +// } +``` + + +## 性能考虑 + + +```java !! java +// Java - Streams have overhead +// For simple operations, traditional loops can be faster +List numbers = /* large list */; + +// Stream (cleaner but some overhead) +List result = numbers.stream() + .filter(n -> n > 0) + .collect(Collectors.toList()); + +// Traditional loop (faster for simple cases) +List result2 = new ArrayList<>(); +for (Integer n : numbers) { + if (n > 0) { + result2.add(n); + } +} +``` + +```javascript !! js +// JavaScript - Similar trade-offs +const numbers = /* large array */; + +// Array methods (cleaner, similar performance) +const result = numbers.filter(n => n > 0); + +// Traditional for loop (often faster) +const result2 = []; +for (let i = 0; i < numbers.length; i++) { + if (numbers[i] > 0) { + result2.push(numbers[i]); + } +} + +// for...of (clean, good middle ground) +const result3 = []; +for (const n of numbers) { + if (n > 0) { + result3.push(n); + } +} + +// General rule: +// - Use array methods for readability (usually fast enough) +// - Use traditional loops for performance-critical code +// - Avoid forEach when you might need to break (use for...of) +``` + + +## 常见陷阱 + +### 陷阱 1: 在数组上使用 for...in + + +```javascript !! js +// ❌ BAD: for...in iterates over keys, not values +const arr = [10, 20, 30]; +for (const i in arr) { + console.log(i); // "0", "1", "2" (strings, not numbers!) + console.log(arr[i]); // 10, 20, 30 +} + +// Also iterates over inherited properties +Array.prototype.customProp = "inherited"; +for (const i in arr) { + console.log(i); // "0", "1", "2", "customProp" +} + +// ✅ GOOD: Use for...of for arrays +for (const value of arr) { + console.log(value); // 10, 20, 30 +} + +// ✅ GOOD: Use forEach for index and value +arr.forEach((value, index) => { + console.log(`${index}: ${value}`); +}); +``` + + +### 陷阱 2: 在迭代时修改数组 + + +```javascript !! js +// ❌ BAD: Removing elements during iteration +const numbers = [1, 2, 3, 4, 5]; +for (let i = 0; i < numbers.length; i++) { + if (numbers[i] % 2 === 0) { + numbers.splice(i, 1); // Removes element + } +} +// Result: [1, 3, 5] but skips some elements! + +// ✅ GOOD: Filter instead +const filtered = numbers.filter(n => n % 2 !== 0); +// Result: [1, 3, 5] + +// ✅ GOOD: Iterate backwards if you must modify +for (let i = numbers.length - 1; i >= 0; i--) { + if (numbers[i] % 2 === 0) { + numbers.splice(i, 1); + } +} +``` + + +### 陷阱 3: 在数组方法中忘记返回 + + +```javascript !! js +// ❌ BAD: Forgetting return +const numbers = [1, 2, 3, 4, 5]; +const doubled = numbers.map(n => { + n * 2; // No return! Results in [undefined, undefined, ...] +}); + +// ✅ GOOD: Explicit return +const doubled2 = numbers.map(n => { + return n * 2; +}); + +// ✅ GOOD: Implicit return (one-liner) +const doubled3 = numbers.map(n => n * 2); + +// ⚠️ Watch out with curly braces +const doubled4 = numbers.map(n => { n * 2 }); // Wrong! +const doubled5 = numbers.map(n => ({ value: n * 2 })); // Need parentheses for object +``` + + +## 最佳实践 + + +```java !! java +// Java: Use streams for readability +List names = Arrays.asList("alice", "bob", "charlie"); + +List result = names.stream() + .filter(n -> n.length() > 3) + .map(String::toUpperCase) + .sorted() + .collect(Collectors.toList()); + +// Use traditional loops for performance or when need to break +for (String name : names) { + if (name.equals("bob")) break; + System.out.println(name); +} +``` + +```javascript !! js +// JavaScript: Use array methods for readability +const names = ["alice", "bob", "charlie"]; + +const result = names + .filter(n => n.length > 3) + .map(n => n.toUpperCase()) + .sort(); + +// Use for...of when you need to break +for (const name of names) { + if (name === "bob") break; + console.log(name); +} + +// Use reduce for complex aggregations +const summary = names.reduce((acc, name) => ({ + count: acc.count + 1, + totalLength: acc.totalLength + name.length, + avgLength: (acc.totalLength + name.length) / (acc.count + 1) +}), { count: 0, totalLength: 0, avgLength: 0 }); + +// Avoid forEach when you could use map/filter +// ❌ BAD +const results = []; +items.forEach(item => { + if (item.isValid) { + results.push(transform(item)); + } +}); + +// ✅ GOOD +const results = items + .filter(item => item.isValid) + .map(transform); +``` + + +## 练习 + +### 练习 1: 数组转换 +将此 Java 代码转换为 JavaScript: +```java +List numbers = Arrays.asList(1, 2, 3, 4, 5); +List squares = numbers.stream() + .filter(n -> n % 2 == 0) + .map(n -> n * n) + .collect(Collectors.toList()); +``` + +### 练习 2: 查找用户 +编写一个按 ID 查找用户的函数: +```javascript +const users = [ + { id: 1, name: "Alice" }, + { id: 2, name: "Bob" }, + { id: 3, name: "Charlie" } +]; + +function findUser(users, targetId) { + // Return the user with matching ID, or null +} +``` + +### 练习 3: 归约为对象 +按角色分组用户: +```javascript +const users = [ + { name: "Alice", role: "admin" }, + { name: "Bob", role: "user" }, + { name: "Charlie", role: "admin" } +]; + +// Create object: { admin: [...], user: [...] } +``` + +### 练习 4: 条件逻辑 +修复代码以使用严格相等: +```javascript +function processValue(value) { + if (value == "0") { // Bug: uses loose equality + return "zero"; + } + return value; +} +``` + +## 总结 + +### 关键要点 + +1. **条件语句:** + - if/else 与 Java 相同 + - Switch 使用宽松相等(小心!) + - 三元运算符完全相同 + - 短路求值提供默认值 + +2. **循环:** + - 传统 for/while/do-while 完全相同 + - for...of 用于数组值(类似 Java 的 for-each) + - for...in 用于对象键(不是数组!) + - break 和 continue 工作方式相同 + +3. **数组方法:** + - forEach: 迭代(不能中断) + - map: 转换为新数组 + - filter: 选择元素 + - reduce: 归约为单个值 + - find/findIndex: 搜索元素 + - some/every: 测试条件 + +4. **方法链:** + - 数组方法可以链接 + - 类似 Java streams + - 比嵌套循环更易读 + +5. **最佳实践:** + - 使用 for...of 进行迭代(可以中断) + - 使用数组方法进行转换 + - 避免在数组上使用 for...in + - 不要在迭代时修改数组 + +## 下一步是什么? + +你已经掌握了控制流和迭代!接下来是**模块 3: 函数**,我们将探索 JavaScript 强大的函数功能: + +- 函数声明 vs 表达式 +- 箭头函数 +- 闭包 +- 高阶函数 +- 默认参数 +- Rest 和 spread 参数 +- 函数中的 `this` 关键字 + +准备深入了解 JavaScript 函数了吗?让我们继续! diff --git a/content/docs/java2js/module-02-control-flow.zh-tw.mdx b/content/docs/java2js/module-02-control-flow.zh-tw.mdx new file mode 100644 index 0000000..f49e959 --- /dev/null +++ b/content/docs/java2js/module-02-control-flow.zh-tw.mdx @@ -0,0 +1,1073 @@ +--- +title: "Module 2: Control Flow and Loops" +description: "Master JavaScript's control flow statements, loops, and iteration patterns" +--- + +## Module 2: Control Flow and Loops + +在這個模組中,我們將探討 JavaScript 如何處理控制流程和迭代。你會發現它與 Java 有許多相似之處,但 JavaScript 提供了一些強大的功能,使迭代更具表達力。 + +## Learning Objectives + +完成本模組後,你將: +✅ 掌握條件語句(if/else、switch、三元運算符) +✅ 理解所有迴圈類型(for、while、do-while、for...of、for...in) +✅ 學習函數式迭代方法(map、filter、reduce) +✅ 發現短路求值(short-circuit evaluation) +✅ 知道何時使用每種迭代模式 + +## Conditional Statements + +### if/else Statements + +JavaScript 的 if/else 與 Java 幾乎完全相同: + + +```java !! java +// Java - if/else +int age = 18; + +if (age >= 18) { + System.out.println("Adult"); +} else if (age >= 13) { + System.out.println("Teenager"); +} else { + System.out.println("Child"); +} + +// Ternary operator +String status = (age >= 18) ? "Adult" : "Minor"; + +// With null check +String name = null; +String displayName = (name != null) ? name : "Guest"; +``` + +```javascript !! js +// JavaScript - if/else(相同語法) +let age = 18; + +if (age >= 18) { + console.log("Adult"); +} else if (age >= 13) { + console.log("Teenager"); +} else { + console.log("Child"); +} + +// Ternary operator(相同) +let status = (age >= 18) ? "Adult" : "Minor"; + +// With null/undefined check +let name = null; +let displayName = (name !== null && name !== undefined) ? name : "Guest"; + +// Or simpler with nullish coalescing +let displayName2 = name ?? "Guest"; +``` + + +### Short-Circuit Evaluation + +JavaScript 對邏輯運算符使用短路求值: + + +```java !! java +// Java - 邏輯運算符總是返回布林值 +boolean isValid = true; +String name = "John"; + +// 兩個條件總是被求值為布林值 +if (isValid && name != null && name.length() > 0) { + // ... +} + +// Cannot use logical operators for default values +// This doesn't work in Java: +// String displayName = name || "Guest"; // Compilation error! +``` + +```javascript !! js +// JavaScript - 短路求值 +let isValid = true; +let name = "John"; + +// && 返回第一個假值或最後一個真值 +let result1 = isValid && name; // "John"(兩者都是真值) +let result2 = isValid && null; // null(在 null 處停止) +let result3 = false && name; // false(在 false 處停止) + +// || 返回第一個真值或最後一個假值 +let displayName = name || "Guest"; // "John"(name 是真值) +let displayName2 = null || "Guest"; // "Guest"(null 是假值) + +// 實用模式:預設值 +function greet(name) { + name = name || "Guest"; // 如果 name 是假值則使用 "Guest" + console.log(`Hello, ${name}`); +} + +greet("John"); // "Hello, John" +greet(""); // "Hello, Guest"(空字串是假值) +greet(null); // "Hello, Guest" + +// Nullish coalescing(ES2020)- 僅用於 null/undefined +function greet2(name) { + let displayName = name ?? "Guest"; + console.log(`Hello, ${displayName}`); +} + +greet2(""); // "Hello, "(空字串不是 null/undefined) +greet2(null); // "Hello, Guest" + +// Optional chaining for safe property access +const user = { profile: { name: "John" } }; +const username = user?.profile?.name ?? "Guest"; // "John" +const username2 = user?.settings?.theme ?? "dark"; // "dark"(settings 不存在) +``` + + +### switch Statements + +JavaScript 的 switch 與 Java 有一些重要差異: + + +```java !! java +// Java - switch with strict type matching +int dayOfWeek = 1; +String dayName; + +switch (dayOfWeek) { + case 1: + dayName = "Monday"; + break; + case 2: + dayName = "Tuesday"; + break; + case 3: + dayName = "Wednesday"; + break; + default: + dayName = "Unknown"; +} + +// Java - switch expressions(Java 14+) +String dayType = switch (dayOfWeek) { + case 1, 2, 3, 4, 5 -> "Weekday"; + case 6, 7 -> "Weekend"; + default -> "Invalid"; +}; +``` + +```javascript !! js +// JavaScript - switch with loose equality(小心!) +let dayOfWeek = 1; +let dayName; + +switch (dayOfWeek) { + case 1: + dayName = "Monday"; + break; + case 2: + dayName = "Tuesday"; + break; + case "1": // ⚠️ 這也匹配!(寬鬆相等) + dayName = "Monday (string)"; + break; + default: + dayName = "Unknown"; +} + +// Pitfall: Loose equality +let x = "1"; +switch (x) { + case 1: // 這匹配!("1" == 1 是 true) + console.log("Equal"); + break; +} + +// Solution: Use strict equality checks +function strictSwitch(value) { + if (value === 1) { + return "One"; + } else if (value === 2) { + return "Two"; + } else { + return "Unknown"; + } +} + +// Or use object lookup(更好的模式) +const dayMap = { + 1: "Monday", + 2: "Tuesday", + 3: "Wednesday", + 4: "Thursday", + 5: "Friday", + 6: "Saturday", + 7: "Sunday" +}; + +const dayName2 = dayMap[dayOfWeek] ?? "Unknown"; +``` + + +## Traditional Loops + +### for Loop + +JavaScript 的傳統 for loop 與 Java 類似: + + +```java !! java +// Java - for loop +for (int i = 0; i < 5; i++) { + System.out.println("Iteration " + i); +} + +// Enhanced for loop(for-each) +int[] numbers = {1, 2, 3, 4, 5}; +for (int num : numbers) { + System.out.println(num); +} + +// With index +for (int i = 0; i < numbers.length; i++) { + System.out.println("Index " + i + ": " + numbers[i]); +} +``` + +```javascript !! js +// JavaScript - for loop(相同語法) +for (let i = 0; i < 5; i++) { + console.log("Iteration " + i); +} + +// for...of loop(現代 for-each) +const numbers = [1, 2, 3, 4, 5]; +for (const num of numbers) { + console.log(num); +} + +// With index using entries() +for (const [index, num] of numbers.entries()) { + console.log(`Index ${index}: ${num}`); +} + +// for...in loop(iterate over indices/keys) +for (const index in numbers) { + console.log(`Index ${index}: ${numbers[index]}`); +} + +// ⚠️ 小心:for...in 是用於物件,不是陣列 +const user = { name: "John", age: 25 }; +for (const key in user) { + console.log(`${key}: ${user[key]}`); +} +``` + + +### while and do-while Loops + +這些與 Java 完全相同: + + +```java !! java +// Java - while loop +int count = 0; +while (count < 5) { + System.out.println("Count: " + count); + count++; +} + +// do-while loop +int i = 0; +do { + System.out.println("At least once: " + i); + i++; +} while (i < 5); +``` + +```javascript !! js +// JavaScript - while loop(相同) +let count = 0; +while (count < 5) { + console.log("Count: " + count); + count++; +} + +// do-while loop(相同) +let i = 0; +do { + console.log("At least once: " + i); + i++; +} while (i < 5); + +// Practical example: Input validation +function guessNumber() { + let guess; + let target = 42; + + do { + guess = parseInt(prompt("Guess a number:")); + if (guess < target) { + console.log("Too low!"); + } else if (guess > target) { + console.log("Too high!"); + } + } while (guess !== target); + + console.log("Correct!"); +} +``` + + +### break and continue + +JavaScript 支援 break 和 continue,就像 Java 一樣: + + +```java !! java +// Java - break and continue +for (int i = 0; i < 10; i++) { + if (i == 5) { + break; // Exit loop + } + if (i % 2 == 0) { + continue; // Skip to next iteration + } + System.out.println("Odd number: " + i); +} + +// Labeled breaks(for nested loops) +outer: +for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + if (i == 1 && j == 1) { + break outer; // Exit outer loop + } + System.out.println(i + ", " + j); + } +} +``` + +```javascript !! js +// JavaScript - break and continue(相同) +for (let i = 0; i < 10; i++) { + if (i === 5) { + break; // Exit loop + } + if (i % 2 === 0) { + continue; // Skip to next iteration + } + console.log("Odd number: " + i); +} + +// Labeled breaks(與 Java 相同) +outer: +for (let i = 0; i < 3; i++) { + for (let j = 0; j < 3; j++) { + if (i === 1 && j === 1) { + break outer; // Exit outer loop + } + console.log(`${i}, ${j}`); + } +} + +// Practical example: Search +function findUser(users, targetId) { + for (const user of users) { + if (user.id === targetId) { + return user; // Found it, exit early + } + } + return null; // Not found +} +``` + + +## Modern Iteration Methods + +JavaScript 提供了強大的陣列方法,Java 開發者會從 streams 中認識它們: + +### Array.prototype.forEach + + +```java !! java +// Java - for-each loop +List names = Arrays.asList("Alice", "Bob", "Charlie"); +for (String name : names) { + System.out.println(name); +} + +// Java 8+ forEach with lambda +names.forEach(name -> System.out.println(name)); + +// With index(need IntStream) +IntStream.range(0, names.size()) + .forEach(i -> System.out.println(i + ": " + names.get(i))); +``` + +```javascript !! js +// JavaScript - forEach method +const names = ["Alice", "Bob", "Charlie"]; +names.forEach(name => { + console.log(name); +}); + +// With index +names.forEach((name, index) => { + console.log(`${index}: ${name}`); +}); + +// Note: Cannot break out of forEach! +// Use a for...of loop if you need to break +for (const name of names) { + if (name === "Bob") break; // This works + console.log(name); +} + +// forEach doesn't return anything +const result = names.forEach(name => name.toUpperCase()); +console.log(result); // undefined +``` + + +### Array.prototype.map + +透過轉換每個元素來建立新陣列: + + +```java !! java +// Java - Stream.map() +List names = Arrays.asList("alice", "bob", "charlie"); +List upperNames = names.stream() + .map(String::toUpperCase) + .collect(Collectors.toList()); + +// Or with lambda +List lengths = names.stream() + .map(name -> name.length()) + .collect(Collectors.toList()); +``` + +```javascript !! js +// JavaScript - Array.map() +const names = ["alice", "bob", "charlie"]; +const upperNames = names.map(name => name.toUpperCase()); +console.log(upperNames); // ["ALICE", "BOB", "CHARLIE"] + +// Get lengths +const lengths = names.map(name => name.length()); +console.log(lengths); // [5, 3, 7] + +// Transform objects +const users = [ + { id: 1, name: "Alice" }, + { id: 2, name: "Bob" } +]; + +const userIds = users.map(user => user.id); +console.log(userIds); // [1, 2] + +// With index +const numbered = names.map((name, i) => `${i + 1}. ${name}`); +console.log(numbered); // ["1. Alice", "2. Bob", "3. Charlie"] +``` + + +### Array.prototype.filter + +建立包含通過測試的元素的新陣列: + + +```java !! java +// Java - Stream.filter() +List numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); +List evens = numbers.stream() + .filter(n -> n % 2 == 0) + .collect(Collectors.toList()); + +// Chain multiple operations +List result = numbers.stream() + .filter(n -> n % 2 == 0) + .filter(n -> n > 5) + .collect(Collectors.toList()); +``` + +```javascript !! js +// JavaScript - Array.filter() +const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; +const evens = numbers.filter(n => n % 2 === 0); +console.log(evens); // [2, 4, 6, 8, 10] + +// Chain methods +const result = numbers + .filter(n => n % 2 === 0) + .filter(n => n > 5); +console.log(result); // [6, 8, 10] + +// Filter objects +const users = [ + { id: 1, name: "Alice", age: 25 }, + { id: 2, name: "Bob", age: 17 }, + { id: 3, name: "Charlie", age: 30 } +]; + +const adults = users.filter(user => user.age >= 18); +console.log(adults); // [{ id: 1, ... }, { id: 3, ... }] + +// Remove null/undefined values +const values = [1, null, 2, undefined, 3, null, 4]; +const clean = values.filter(v => v != null); +console.log(clean); // [1, 2, 3, 4] +``` + + +### Array.prototype.reduce + +將陣列減少為單一值: + + +```java !! java +// Java - Stream.reduce() +List numbers = Arrays.asList(1, 2, 3, 4, 5); + +int sum = numbers.stream() + .reduce(0, (a, b) -> a + b); + +int product = numbers.stream() + .reduce(1, (a, b) -> a * b); + +// With Optional +Optional max = numbers.stream() + .reduce(Integer::max); +``` + +```javascript !! js +// JavaScript - Array.reduce() +const numbers = [1, 2, 3, 4, 5]; + +const sum = numbers.reduce((acc, n) => acc + n, 0); +console.log(sum); // 15 + +const product = numbers.reduce((acc, n) => acc * n, 1); +console.log(product); // 120 + +// Find max +const max = numbers.reduce((acc, n) => Math.max(acc, n), -Infinity); +console.log(max); // 5 + +// Group by +const users = [ + { name: "Alice", role: "admin" }, + { name: "Bob", role: "user" }, + { name: "Charlie", role: "admin" } +]; + +const byRole = users.reduce((acc, user) => { + if (!acc[user.role]) { + acc[user.role] = []; + } + acc[user.role].push(user); + return acc; +}, {}); + +console.log(byRole); +// { +// admin: [{ name: "Alice", ... }, { name: "Charlie", ... }], +// user: [{ name: "Bob", ... }] +// } +``` + + +### Other Array Methods + + +```java !! java +// Java - Various stream operations +List names = Arrays.asList("Alice", "Bob", "Charlie"); + +// Find first +Optional first = names.stream().findFirst(); + +// Any match +boolean hasAlice = names.stream().anyMatch(n -> n.equals("Alice")); + +// All match +boolean allLong = names.stream().allMatch(n -> n.length() > 3); + +// Count +long count = names.stream().count(); + +// Limit +List firstTwo = names.stream().limit(2).collect(Collectors.toList()); +``` + +```javascript !! js +// JavaScript - Array methods +const names = ["Alice", "Bob", "Charlie"]; + +// Find first(returns element, not Optional) +const first = names[0]; // "Alice" +const firstFind = names.find(name => name.startsWith("A")); // "Alice" + +// Find index +const indexOfBob = names.indexOf("Bob"); // 1 +const findIndex = names.findIndex(name => name.startsWith("B")); // 1 + +// Check if exists +const hasAlice = names.includes("Alice"); // true +const hasLongName = names.some(name => name.length > 3); // true +const allLong = names.every(name => name.length > 3); // false + +// Count +const count = names.length; // 3 + +// Slice(first two) +const firstTwo = names.slice(0, 2); // ["Alice", "Bob"] + +// Flat map(ES2019) +const matrix = [[1, 2], [3, 4], [5, 6]]; +const flattened = matrix.flat(); // [1, 2, 3, 4, 5, 6] + +// Sort(modifies in place!) +const nums = [3, 1, 4, 1, 5]; +nums.sort((a, b) => a - b); // [1, 1, 3, 4, 5] +``` + + +## Method Chaining + +JavaScript 陣列方法可以鏈結,就像 Java streams 一樣: + + +```java !! java +// Java - Stream chaining +List result = numbers.stream() + .filter(n -> n % 2 == 0) + .map(n -> n * 2) + .limit(5) + .collect(Collectors.toList()); +``` + +```javascript !! js +// JavaScript - Array method chaining +const result = numbers + .filter(n => n % 2 === 0) + .map(n => n * 2) + .slice(0, 5); + +// Real-world example +const users = [ + { name: "Alice", age: 25, score: 85 }, + { name: "Bob", age: 17, score: 92 }, + { name: "Charlie", age: 30, score: 78 }, + { name: "David", age: 22, score: 88 } +]; + +// Get names of adult users with high scores, sorted +const topAdults = users + .filter(user => user.age >= 18) // Filter adults + .filter(user => user.score >= 80) // Filter high scores + .sort((a, b) => b.score - a.score) // Sort by score desc + .map(user => user.name); // Get names + +console.log(topAdults); // ["Bob", "David", "Alice"] + +// Calculate average score +const averageScore = users + .map(user => user.score) + .reduce((sum, score, _, arr) => sum + score / arr.length, 0); + +console.log(averageScore); // 85.75 +``` + + +## Iteration Patterns + +### Pattern 1: Transform and Filter + + +```java !! java +// Java +List words = Arrays.asList("hello", "world", "java", "stream"); + +List result = words.stream() + .filter(w -> w.length() > 4) + .map(String::toUpperCase) + .sorted() + .collect(Collectors.toList()); +``` + +```javascript !! js +// JavaScript +const words = ["hello", "world", "java", "javascript"]; + +const result = words + .filter(w => w.length > 4) + .map(w => w.toUpperCase()) + .sort(); + +console.log(result); // ["HELLO", "JAVASCRIPT", "WORLD"] +``` + + +### Pattern 2: Find and Update + + +```java !! java +// Java - Immutable approach +List users = getUsers(); +User updatedUser = users.stream() + .filter(u -> u.getId() == targetId) + .findFirst() + .map(u -> new User(u.getId(), u.getName(), true)) + .orElse(null); +``` + +```javascript !! js +// JavaScript - Find and update +const users = [ + { id: 1, name: "Alice", active: false }, + { id: 2, name: "Bob", active: false } +]; + +const targetId = 1; +const updatedUsers = users.map(user => { + if (user.id === targetId) { + return { ...user, active: true }; + } + return user; +}); + +console.log(updatedUsers); +// [ +// { id: 1, name: "Alice", active: true }, +// { id: 2, name: "Bob", active: false } +// ] +``` + + +### Pattern 3: Reduce to Object + + +```java !! java +// Java - Group by with Collectors.groupingBy +List users = getUsers(); +Map> byRole = users.stream() + .collect(Collectors.groupingBy(User::getRole)); +``` + +```javascript !! js +// JavaScript - Group by with reduce +const users = [ + { name: "Alice", role: "admin" }, + { name: "Bob", role: "user" }, + { name: "Charlie", role: "admin" } +]; + +const byRole = users.reduce((acc, user) => ({ + ...acc, + [user.role]: [...(acc[user.role] || []), user] +}), {}); + +console.log(byRole); +// { +// admin: [ +// { name: "Alice", role: "admin" }, +// { name: "Charlie", role: "admin" } +// ], +// user: [{ name: "Bob", role: "user" }] +// } +``` + + +## Performance Considerations + + +```java !! java +// Java - Streams have overhead +// For simple operations, traditional loops can be faster +List numbers = /* large list */; + +// Stream(cleaner but some overhead) +List result = numbers.stream() + .filter(n -> n > 0) + .collect(Collectors.toList()); + +// Traditional loop(faster for simple cases) +List result2 = new ArrayList<>(); +for (Integer n : numbers) { + if (n > 0) { + result2.add(n); + } +} +``` + +```javascript !! js +// JavaScript - Similar trade-offs +const numbers = /* large array */; + +// Array methods(cleaner, similar performance) +const result = numbers.filter(n => n > 0); + +// Traditional for loop(often faster) +const result2 = []; +for (let i = 0; i < numbers.length; i++) { + if (numbers[i] > 0) { + result2.push(numbers[i]); + } +} + +// for...of(clean, good middle ground) +const result3 = []; +for (const n of numbers) { + if (n > 0) { + result3.push(n); + } +} + +// General rule: +// - Use array methods for readability(usually fast enough) +// - Use traditional loops for performance-critical code +// - Avoid forEach when you might need to break(use for...of) +``` + + +## Common Pitfalls + +### Pitfall 1: Using for...in on Arrays + + +```javascript !! js +// ❌ BAD: for...in iterates over keys, not values +const arr = [10, 20, 30]; +for (const i in arr) { + console.log(i); // "0", "1", "2"(字串,不是數字!) + console.log(arr[i]); // 10, 20, 30 +} + +// Also iterates over inherited properties +Array.prototype.customProp = "inherited"; +for (const i in arr) { + console.log(i); // "0", "1", "2", "customProp" +} + +// ✅ GOOD: Use for...of for arrays +for (const value of arr) { + console.log(value); // 10, 20, 30 +} + +// ✅ GOOD: Use forEach for index and value +arr.forEach((value, index) => { + console.log(`${index}: ${value}`); +}); +``` + + +### Pitfall 2: Modifying Array While Iterating + + +```javascript !! js +// ❌ BAD: Removing elements during iteration +const numbers = [1, 2, 3, 4, 5]; +for (let i = 0; i < numbers.length; i++) { + if (numbers[i] % 2 === 0) { + numbers.splice(i, 1); // Removes element + } +} +// Result: [1, 3, 5] but skips some elements! + +// ✅ GOOD: Filter instead +const filtered = numbers.filter(n => n % 2 !== 0); +// Result: [1, 3, 5] + +// ✅ GOOD: Iterate backwards if you must modify +for (let i = numbers.length - 1; i >= 0; i--) { + if (numbers[i] % 2 === 0) { + numbers.splice(i, 1); + } +} +``` + + +### Pitfall 3: Forgetting Return in Array Methods + + +```javascript !! js +// ❌ BAD: Forgetting return +const numbers = [1, 2, 3, 4, 5]; +const doubled = numbers.map(n => { + n * 2; // No return! Results in [undefined, undefined, ...] +}); + +// ✅ GOOD: Explicit return +const doubled2 = numbers.map(n => { + return n * 2; +}); + +// ✅ GOOD: Implicit return(one-liner) +const doubled3 = numbers.map(n => n * 2); + +// ⚠️ Watch out with curly braces +const doubled4 = numbers.map(n => { n * 2 }); // Wrong! +const doubled5 = numbers.map(n => ({ value: n * 2 })); // Need parentheses for object +``` + + +## Best Practices + + +```java !! java +// Java: Use streams for readability +List names = Arrays.asList("alice", "bob", "charlie"); + +List result = names.stream() + .filter(n -> n.length() > 3) + .map(String::toUpperCase) + .sorted() + .collect(Collectors.toList()); + +// Use traditional loops for performance or when need to break +for (String name : names) { + if (name.equals("bob")) break; + System.out.println(name); +} +``` + +```javascript !! js +// JavaScript: Use array methods for readability +const names = ["alice", "bob", "charlie"]; + +const result = names + .filter(n => n.length > 3) + .map(n => n.toUpperCase()) + .sort(); + +// Use for...of when you need to break +for (const name of names) { + if (name === "bob") break; + console.log(name); +} + +// Use reduce for complex aggregations +const summary = names.reduce((acc, name) => ({ + count: acc.count + 1, + totalLength: acc.totalLength + name.length, + avgLength: (acc.totalLength + name.length) / (acc.count + 1) +}), { count: 0, totalLength: 0, avgLength: 0 }); + +// Avoid forEach when you could use map/filter +// ❌ BAD +const results = []; +items.forEach(item => { + if (item.isValid) { + results.push(transform(item)); + } +}); + +// ✅ GOOD +const results = items + .filter(item => item.isValid) + .map(transform); +``` + + +## Exercises + +### Exercise 1: Array Transformation +將此 Java 程式碼轉換為 JavaScript: +```java +List numbers = Arrays.asList(1, 2, 3, 4, 5); +List squares = numbers.stream() + .filter(n -> n % 2 == 0) + .map(n -> n * n) + .collect(Collectors.toList()); +``` + +### Exercise 2: Find User +撰寫一個函數來透過 ID 查找使用者: +```javascript +const users = [ + { id: 1, name: "Alice" }, + { id: 2, name: "Bob" }, + { id: 3, name: "Charlie" } +]; + +function findUser(users, targetId) { + // Return the user with matching ID, or null +} +``` + +### Exercise 3: Reduce to Object +按角色群組使用者: +```javascript +const users = [ + { name: "Alice", role: "admin" }, + { name: "Bob", role: "user" }, + { name: "Charlie", role: "admin" } +]; + +// Create object: { admin: [...], user: [...] } +``` + +### Exercise 4: Conditional Logic +修正程式碼以使用嚴格相等: +```javascript +function processValue(value) { + if (value == "0") { // Bug: uses loose equality + return "zero"; + } + return value; +} +``` + +## Summary + +### Key Takeaways + +1. **Conditionals:** + - if/else 與 Java 運作方式相同 + - Switch 使用寬鬆相等(小心!) + - 三元運算符相同 + - 短路求值提供預設值 + +2. **Loops:** + - Traditional for/while/do-while 完全相同 + - for...of 用於陣列值(類似 Java 的 for-each) + - for...in 用於物件鍵(不是陣列!) + - break 和 continue 運作方式相同 + +3. **Array Methods:** + - forEach: 迭代(無法中斷) + - map: 轉換為新陣列 + - filter: 選擇元素 + - reduce: 聚合為單一值 + - find/findIndex: 搜尋元素 + - some/every: 測試條件 + +4. **Method Chaining:** + - 陣列方法可以鏈結 + - 類似 Java streams + - 比嵌套迴圈更易讀 + +5. **Best Practices:** + - 使用 for...of 進行迭代(可以中斷) + - 使用陣列方法進行轉換 + - 避免在陣列上使用 for...in + - 不要在迭代時修改陣列 + +## What's Next? + +你已經掌握了控制流程和迭代!接下來是 **Module 3: Functions**,我們將探討 JavaScript 強大的函數功能: + +- Function declarations vs expressions +- Arrow functions +- Closures +- Higher-order functions +- Default parameters +- Rest and spread parameters +- 函數中的 `this` 關鍵字 + +準備好深入學習 JavaScript 函數了嗎?讓我們繼續! diff --git a/content/docs/java2js/module-03-functions-basics.mdx b/content/docs/java2js/module-03-functions-basics.mdx new file mode 100644 index 0000000..d7e8ffc --- /dev/null +++ b/content/docs/java2js/module-03-functions-basics.mdx @@ -0,0 +1,1122 @@ +--- +title: "Module 3: Functions Basics" +description: "Learn JavaScript function declarations, expressions, arrow functions, and core concepts" +--- + +## Module 3: Functions Basics + +Welcome to one of the most important modules! Functions in JavaScript are far more powerful and flexible than methods in Java. Understanding functions deeply is crucial to becoming an effective JavaScript developer. + +## Learning Objectives + +By the end of this module, you will: +✅ Understand function declarations vs expressions +✅ Master arrow functions (ES6+) +✅ Learn about default parameters +✅ Understand rest and spread parameters +✅ Know the difference between parameters and arguments +✅ Learn about function hoisting + +## Function Basics: Java vs JavaScript + +In Java, methods are always attached to classes. In JavaScript, functions are first-class citizens - they can exist independently, be assigned to variables, passed as arguments, and returned from other functions. + + +```java !! java +// Java - Methods are class members +public class Calculator { + + // Method declaration + public int add(int a, int b) { + return a + b; + } + + // Method overloading + public int add(int a, int b, int c) { + return a + b + c; + } + + // Main method + public static void main(String[] args) { + Calculator calc = new Calculator(); + int result = calc.add(5, 3); + System.out.println(result); // 8 + } +} +``` + +```javascript !! js +// JavaScript - Functions can exist independently +// Function declaration +function add(a, b) { + return a + b; +} + +// Call the function +const result = add(5, 3); +console.log(result); // 8 + +// Can be assigned to variable +const multiply = function(a, b) { + return a * b; +}; + +// Arrow function (ES6+) +const subtract = (a, b) => a - b; + +// All of these are valid +console.log(add(2, 3)); // 5 +console.log(multiply(2, 3)); // 6 +console.log(subtract(5, 2)); // 3 +``` + + +## Function Declarations + +Function declarations are similar to Java methods but don't require a class: + + +```java !! java +// Java - Method inside class +public class Utils { + public static int calculate(int x, int y) { + return x + y; + } + + // Instance method + public String format(String text) { + return text.toUpperCase(); + } +} + +// Calling +Utils.calculate(5, 3); +Utils utils = new Utils(); +utils.format("hello"); +``` + +```javascript !! js +// JavaScript - Standalone functions +function calculate(x, y) { + return x + y; +} + +// Can be called before declaration (hoisting) +console.log(add(5, 3)); // 8 - Works! + +function add(a, b) { + return a + b; +} + +// No public/private modifiers (yet - ES2022 has #) +function privateFunction() { + return "private"; +} + +// Naming convention: camelCase +function getUserById(id) { + // ... implementation +} + +function calculateTotalPrice(items, taxRate) { + // ... implementation +} +``` + + +### Function Hoisting + +Function declarations are "hoisted" - they can be called before they're defined: + + +```java !! java +// Java - No hoisting +public class Example { + public static void main(String[] args) { + greet(); // Compilation error! Method not defined yet + } + + public static void greet() { + System.out.println("Hello!"); + } +} +``` + +```javascript !! js +// JavaScript - Hoisting works for declarations +greet(); // "Hello!" - Works because of hoisting + +function greet() { + console.log("Hello!"); +} + +// Hoisting doesn't work for expressions +greet2(); // TypeError: greet2 is not a function + +const greet2 = function() { + console.log("Hello 2!"); +}; + +// What actually happens: +// 1. Declaration is hoisted +function greet() { + console.log("Hello!"); +} +// 2. Assignment stays in place +// (greet2 is undefined until the assignment) +``` + + +## Function Expressions + +JavaScript allows functions to be assigned to variables: + + +```java !! java +// Java - Lambda expressions (Java 8+) +interface Operation { + int apply(int a, int b); +} + +public class Calculator { + public static void main(String[] args) { + Operation add = (a, b) -> a + b; + Operation multiply = (a, b) -> a * b; + + System.out.println(add.apply(5, 3)); // 8 + System.out.println(multiply.apply(5, 3)); // 15 + } +} +``` + +```javascript !! js +// JavaScript - Function expressions +const add = function(a, b) { + return a + b; +}; + +const multiply = function(a, b) { + return a * b; +}; + +console.log(add(5, 3)); // 8 +console.log(multiply(5, 3)); // 15 + +// Anonymous function (no name) +setTimeout(function() { + console.log("Delayed execution"); +}, 1000); + +// Named function expression (better for debugging) +const divide = function divideNumbers(a, b) { + if (b === 0) { + throw new Error("Cannot divide by zero"); + } + return a / b; +}; + +// The name is only visible inside the function +console.log(divide(10, 2)); // 5 +// console.log(divideNumbers(10, 2)); // ReferenceError +``` + + +## Arrow Functions + +Arrow functions (introduced in ES6) are a concise way to write functions: + + +```java !! java +// Java - Lambda expressions +List numbers = Arrays.asList(1, 2, 3, 4, 5); + +// Single expression +numbers.stream() + .map(n -> n * 2) + .forEach(System.out::println); + +// Multiple statements (need braces) +numbers.stream() + .map(n -> { + int doubled = n * 2; + return doubled + 1; + }) + .forEach(System.out::println); +``` + +```javascript !! js +// JavaScript - Arrow functions +const numbers = [1, 2, 3, 4, 5]; + +// Single expression (implicit return) +const doubled = numbers.map(n => n * 2); +console.log(doubled); // [2, 4, 6, 8, 10] + +// Multiple parameters (need parentheses) +const sum = numbers.reduce((a, b) => a + b, 0); + +// No parameters (need parentheses) +const getRandom = () => Math.random(); + +// Multiple statements (need braces and explicit return) +const processed = numbers.map(n => { + const doubled = n * 2; + return doubled + 1; +}); + +// Returning object literals (need parentheses) +const createUser = name => ({ name, id: Date.now() }); +const user = createUser("John"); +console.log(user); // { name: "John", id: 1234567890 } + +// Arrow functions are anonymous +// Use variable name for identification +const add = (a, b) => a + b; +``` + + +### Arrow Function Limitations + +Arrow functions are not always appropriate. They differ from regular functions in important ways: + + +```java !! java +// Java - 'this' refers to the object +public class Counter { + private int count = 0; + + public Runnable getIncrementer() { + return () -> { + this.count++; // 'this' refers to Counter instance + System.out.println(this.count); + }; + } +} +``` + +```javascript !! js +// JavaScript - Arrow functions lexically bind 'this' +const counter = { + count: 0, + increment: function() { + console.log(this.count); // 'this' refers to counter + }, + + // Arrow function as method (NOT recommended) + incrementBad: () => { + console.log(this.count); // 'this' is not counter! + // 'this' is lexically bound from surrounding scope + } +}; + +counter.increment(); // 0 (correct) +counter.incrementBad(); // undefined (wrong!) + +// Correct usage: callback functions +const timer = { + seconds: 0, + start: function() { + // Regular function would lose 'this' + // setInterval(function() { + // this.seconds++; // Error: 'this' is not timer + // }, 1000); + + // Arrow function preserves 'this' + setInterval(() => { + this.seconds++; + console.log(this.seconds); + }, 1000); + } +}; + +timer.start(); // Works correctly! +``` + + +## Default Parameters + +JavaScript supports default parameter values: + + +```java !! java +// Java - Method overloading +public class Greeter { + public void greet(String name) { + greet(name, "Hello"); + } + + public void greet(String name, String greeting) { + System.out.println(greeting + ", " + name); + } + + // Or use null checks + public void greet2(String name, String greeting) { + if (greeting == null) { + greeting = "Hello"; + } + System.out.println(greeting + ", " + name); + } +} +``` + +```javascript !! js +// JavaScript - Default parameters +function greet(name, greeting = "Hello") { + console.log(`${greeting}, ${name}`); +} + +greet("John"); // "Hello, John" +greet("Jane", "Hi"); // "Hi, Jane" + +// Default with undefined +greet("Bob", undefined); // "Hello, Bob" (uses default) +greet("Alice", null); // "null, Alice" (null is a value!) + +// Expressions as defaults +function createUser(name, role = "user", createdAt = Date.now()) { + return { name, role, createdAt }; +} + +const user1 = createUser("John"); +console.log(user1); // { name: "John", role: "user", createdAt: 1234567890 } + +const user2 = createUser("Jane", "admin"); +console.log(user2); // { name: "Jane", role: "admin", createdAt: 1234567890 } + +// Default can reference previous parameters +function createArray(size, defaultValue = 0) { + return Array(size).fill(defaultValue); +} + +console.log(createArray(5)); // [0, 0, 0, 0, 0] +console.log(createArray(3, "x")); // ["x", "x", "x"] +``` + + +## Rest Parameters + +Rest parameters allow functions to accept indefinite number of arguments: + + +```java !! java +// Java - Varargs +public class Sum { + public int sum(int... numbers) { + int total = 0; + for (int num : numbers) { + total += num; + } + return total; + } + + public static void main(String[] args) { + Sum s = new Sum(); + System.out.println(s.sum(1, 2, 3)); // 6 + System.out.println(s.sum(1, 2, 3, 4, 5)); // 15 + } +} +``` + +```javascript !! js +// JavaScript - Rest parameters +function sum(...numbers) { + return numbers.reduce((total, n) => total + n, 0); +} + +console.log(sum(1, 2, 3)); // 6 +console.log(sum(1, 2, 3, 4, 5)); // 15 +console.log(sum()); // 0 + +// Rest parameter must be last +function log(message, ...args) { + console.log(message, args); +} + +log("Values:", 1, 2, 3); // "Values:" [1, 2, 3] + +// Destructuring with rest +function getUserInfo(firstName, lastName, ...details) { + return { + name: `${firstName} ${lastName}`, + details + }; +} + +const info = getUserInfo("John", "Doe", "Engineer", "NYC", 30); +console.log(info); +// { +// name: "John Doe", +// details: ["Engineer", "NYC", 30] +// } + +// Arrow functions with rest +const multiply = (multiplier, ...numbers) => { + return numbers.map(n => n * multiplier); +}; + +console.log(multiply(2, 1, 2, 3)); // [2, 4, 6] +``` + + +## Spread Operator + +The spread operator expands arrays/objects into individual elements: + + +```java !! java +// Java - Arrays can't be easily spread +int[] arr1 = {1, 2, 3}; +int[] arr2 = {4, 5, 6}; +// Need manual copying or System.arraycopy + +// Java varargs for method calls +public void printAll(String... messages) { + for (String msg : messages) { + System.out.println(msg); + } +} + +printAll("Hello", "World"); // Works +// Can't easily spread an existing array +``` + +```javascript !! js +// JavaScript - Spread operator +const arr1 = [1, 2, 3]; +const arr2 = [4, 5, 6]; + +// Combine arrays +const combined = [...arr1, ...arr2]; +console.log(combined); // [1, 2, 3, 4, 5, 6] + +// Add elements +const withExtra = [0, ...arr1, 4, ...arr2, 7]; +console.log(withExtra); // [0, 1, 2, 3, 4, 4, 5, 6, 7] + +// Copy array +const copy = [...arr1]; + +// Spread in function calls +const numbers = [1, 2, 3, 4, 5]; +console.log(Math.max(...numbers)); // 5 +console.log(Math.min(...numbers)); // 1 + +// Spread with strings +const chars = [..."hello"]; +console.log(chars); // ["h", "e", "l", "l", "o"] + +// Spread objects +const user = { name: "John", age: 25 }; +const enhanced = { ...user, role: "admin" }; +console.log(enhanced); // { name: "John", age: 25, role: "admin" } + +// Override properties +const updated = { ...user, age: 26 }; +console.log(updated); // { name: "John", age: 26 } + +// Merge objects +const obj1 = { a: 1, b: 2 }; +const obj2 = { c: 3, d: 4 }; +const merged = { ...obj1, ...obj2 }; +console.log(merged); // { a: 1, b: 2, c: 3, d: 4 } +``` + + +## Parameters vs Arguments + + +```java !! java +// Java - Parameters (in declaration) +public int add(int a, int b) { // a and b are parameters + return a + b; +} + +// Arguments (in call) +int result = add(5, 3); // 5 and 3 are arguments +``` + +```javascript !! js +// JavaScript - Same concept, but more flexible +function add(a, b) { // a and b are parameters + console.log(a); // undefined if argument not provided + console.log(b); // undefined if argument not provided + return a + b; +} + +add(5, 3); // 5 and 3 are arguments +add(5); // Only 5 is provided, b is undefined +add(5, 3, 7); // 7 is ignored (no error!) + +// Access all arguments (old way) +function sumAll() { + // arguments is array-like object + let total = 0; + for (let i = 0; i < arguments.length; i++) { + total += arguments[i]; + } + return total; +} + +console.log(sumAll(1, 2, 3, 4, 5)); // 15 + +// Modern way: rest parameters +function sumAllModern(...numbers) { + return numbers.reduce((sum, n) => sum + n, 0); +} + +console.log(sumAllModern(1, 2, 3, 4, 5)); // 15 +``` + + +## Return Values + + +```java !! java +// Java - Must declare return type +public int add(int a, int b) { + return a + b; +} + +// Void return type +public void print(String message) { + System.out.println(message); + // No return needed +} + +// All paths must return value +public int max(int a, int b) { + if (a > b) { + return a; + } + // Error: Missing return statement +} +``` + +```javascript !! js +// JavaScript - No return type declaration +function add(a, b) { + return a + b; +} + +// No return = undefined +function print(message) { + console.log(message); + // Returns undefined implicitly +} + +const result = print("Hello"); +console.log(result); // undefined + +// Early returns +function max(a, b) { + if (a > b) { + return a; + } + return b; +} + +// Conditional return +function getDiscount(amount) { + if (amount > 1000) { + return 0.2; // 20% discount + } + if (amount > 500) { + return 0.1; // 10% discount + } + return 0; // No discount +} + +// Multiple return values (via array or object) +function getMinMax(numbers) { + return { + min: Math.min(...numbers), + max: Math.max(...numbers) + }; +} + +const { min, max } = getMinMax([1, 5, 3, 9, 2]); +console.log(min, max); // 1 9 +``` + + +## First-Class Functions + +Functions are first-class citizens - they can be: + +1. Assigned to variables +2. Passed as arguments +3. Returned from other functions +4. Stored in data structures + + +```java !! java +// Java - Limited support +interface Operation { + int apply(int a, int b); +} + +public class Calculator { + public int calculate(Operation op, int a, int b) { + return op.apply(a, b); + } + + public static void main(String[] args) { + Calculator calc = new Calculator(); + + // Pass lambda as argument + int result = calc.calculate((x, y) -> x + y, 5, 3); + System.out.println(result); // 8 + } +} +``` + +```javascript !! js +// JavaScript - Full first-class function support + +// 1. Assigned to variables +const greet = function(name) { + return `Hello, ${name}`; +}; + +// 2. Passed as arguments +function applyOperation(a, b, operation) { + return operation(a, b); +} + +const sum = (x, y) => x + y; +const difference = (x, y) => x - y; + +console.log(applyOperation(10, 5, sum)); // 15 +console.log(applyOperation(10, 5, difference)); // 5 + +// 3. Returned from functions +function createMultiplier(multiplier) { + return function(number) { + return number * multiplier; + }; +} + +const double = createMultiplier(2); +const triple = createMultiplier(3); + +console.log(double(5)); // 10 +console.log(triple(5)); // 15 + +// 4. Stored in data structures +const operations = { + add: (a, b) => a + b, + subtract: (a, b) => a - b, + multiply: (a, b) => a * b, + divide: (a, b) => a / b +}; + +console.log(operations.add(5, 3)); // 8 +console.log(operations.multiply(4, 3)); // 12 + +// Array of functions +const transformers = [ + x => x * 2, + x => x + 10, + x => x * x +]; + +const value = 5; +const results = transformers.map(fn => fn(value)); +console.log(results); // [10, 15, 25] +``` + + +## Common Patterns + +### Pattern 1: Function Factory + + +```java !! java +// Java - Not easily possible +// Would need classes and interfaces +``` + +```javascript !! js +// JavaScript - Function factory +function createValidator(regex) { + return function(value) { + return regex.test(value); + }; +} + +const isEmail = createValidator /^[^\s@]+@[^\s@]+\.[^\s@]+$/); +const isPhone = createValidator(/^\d{3}-\d{3}-\d{4}$/); + +console.log(isEmail("test@example.com")); // true +console.log(isPhone("123-456-7890")); // true + +// Practical example: API client creator +function createApiClient(baseUrl) { + return async function(endpoint, options = {}) { + const response = await fetch(`${baseUrl}${endpoint}`, options); + return response.json(); + }; +} + +const api = createApiClient("https://api.example.com"); +const users = await api("/users"); +const posts = await api("/posts"); +``` + + +### Pattern 2: Configuration Object + + +```java !! java +// Java - Builder pattern +public class Request { + private String url; + private String method = "GET"; + private Map headers = new HashMap<>(); + + public static class Builder { + // Builder implementation + } +} + +Request request = new Request.Builder() + .url("https://api.example.com") + .method("POST") + .build(); +``` + +```javascript !! js +// JavaScript - Configuration object with defaults +function makeRequest(url, options = {}) { + const defaults = { + method: "GET", + headers: { + "Content-Type": "application/json" + }, + body: null + }; + + const config = { ...defaults, ...options }; + + return fetch(url, config); +} + +// Usage +makeRequest("https://api.example.com/users", { + method: "POST", + body: JSON.stringify({ name: "John" }) +}); + +// Destructured parameters +function createUser({ name, email, role = "user", active = true }) { + return { name, email, role, active }; +} + +const user = createUser({ + name: "John", + email: "john@example.com" + // role and active use defaults +}); +``` + + +## Best Practices + + +```java !! java +// Java: Clear method signatures +public class UserService { + private UserRepository repository; + + public UserService(UserRepository repository) { + this.repository = repository; + } + + public Optional findById(Long id) { + return repository.findById(id); + } + + public List findActiveUsers() { + return repository.findAll() + .stream() + .filter(User::isActive) + .collect(Collectors.toList()); + } +} +``` + +```javascript !! js +// JavaScript: Similar principles + +// 1. Use descriptive names +function calculateMonthlyPayment(principal, rate, months) { + // Good +} + +function calc(p, r, m) { + // Bad - not descriptive +} + +// 2. Keep functions small and focused +function validateEmail(email) { + const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + return regex.test(email); +} + +function validatePassword(password) { + return password.length >= 8; +} + +// Instead of one big function +function validateCredentials(email, password) { + // Validates everything +} + +// 3. Use default parameters for optional arguments +function fetchUsers(options = {}) { + const { + limit = 10, + offset = 0, + active = true + } = options; + + // Implementation +} + +// 4. Prefer arrow functions for callbacks +const numbers = [1, 2, 3, 4, 5]; +const doubled = numbers.map(n => n * 2); + +// 5. Use regular functions for methods +const calculator = { + add: function(a, b) { + return a + b; + }, + + // Or use shorthand (still has correct 'this') + subtract(a, b) { + return a - b; + } +}; + +// 6. Return early to reduce nesting +function processUser(user) { + if (!user) { + return null; + } + + if (!user.isActive) { + return null; + } + + if (!user.email) { + return null; + } + + // Main logic + return transformUser(user); +} +``` + + +## Common Pitfalls + +### Pitfall 1: Forgetting Return in Arrow Functions + + +```javascript !! js +// ❌ BAD: Implicit return with curly braces +const doubled = [1, 2, 3].map(n => { + n * 2; +}); +// Result: [undefined, undefined, undefined] + +// ✅ GOOD: Either use explicit return +const doubled2 = [1, 2, 3].map(n => { + return n * 2; +}); + +// ✅ GOOD: Or remove curly braces for implicit return +const doubled3 = [1, 2, 3].map(n => n * 2); + +// ⚠️ Need parentheses for object literals +const users = ["alice", "bob"].map(name => ({ name })); +// Correct: ({ name }) creates object + +// Without parentheses: +const bad = ["alice"].map(name => { name }); +// Result: [undefined] - { name } is a code block, not object +``` + + +### Pitfall 2: Using Arrow Functions for Methods + + +```javascript !! js +// ❌ BAD: Arrow function doesn't have own 'this' +const counter = { + count: 0, + increment: () => { + this.count++; // 'this' is not counter! + } +}; + +// ✅ GOOD: Use regular function or shorthand +const counter2 = { + count: 0, + increment: function() { + this.count++; // 'this' is counter2 + }, + + // Or shorthand (same as function) + decrement() { + this.count--; // 'this' is counter2 + } +}; +``` + + +### Pitfall 3: Mutable Default Parameters + + +```javascript !! js +// ❌ BAD: Mutable default is shared across calls +function addItem(item, list = []) { + list.push(item); + return list; +} + +const list1 = addItem("a"); // ["a"] +const list2 = addItem("b"); // ["a", "b"] - Not a new array! + +// ✅ GOOD: Create new array in function body +function addItemSafe(item, list) { + const items = list ? [...list] : []; + items.push(item); + return items; +} + +// Or use null check +function addItemSafe2(item, list) { + if (!list) list = []; + return [...list, item]; +} +``` + + +## Exercises + +### Exercise 1: Convert to Arrow Functions +Convert these functions to arrow functions: +```javascript +function add(a, b) { + return a + b; +} + +function square(x) { + return x * x; +} + +function greet(name) { + return "Hello, " + name; +} +``` + +### Exercise 2: Default Parameters +Add default parameters: +```javascript +function createUser(name, email, role) { + return { name, email, role }; +} + +// role should default to "user" +// email should default to empty string +``` + +### Exercise 3: Rest Parameters +Create a function that calculates average: +```javascript +function average(/* numbers */) { + // Accept any number of arguments + // Return the average +} + +average(1, 2, 3, 4, 5); // 3 +average(10, 20); // 15 +``` + +### Exercise 4: Function Factory +Create a function that returns a function: +```javascript +function createGreeter(greeting) { + // Return a function that greets with the specified greeting +} + +const sayHello = createGreeter("Hello"); +const sayHi = createGreeter("Hi"); + +sayHello("John"); // "Hello, John" +sayHi("Jane"); // "Hi, Jane" +``` + +## Summary + +### Key Takeaways + +1. **Function Declarations:** + - Hoisted (can call before definition) + - Traditional function syntax + - Good for standalone functions + +2. **Function Expressions:** + - Not hoisted + - Assigned to variables + - Good for callbacks and closures + +3. **Arrow Functions:** + - Concise syntax + - Lexical `this` binding + - Perfect for callbacks + - Not suitable for methods + +4. **Default Parameters:** + - Provide default values + - Can be expressions + - Only triggered by `undefined`, not `null` + +5. **Rest/Spread:** + - Rest: Collect arguments into array + - Spread: Expand array/object into elements + - Powerful for manipulation + +6. **First-Class Functions:** + - Can be assigned, passed, returned + - Enable higher-order functions + - Support functional programming + +### Comparison Table: Java vs JavaScript + +| Feature | Java | JavaScript | +|---------|------|------------| +| **Declaration** | Inside classes only | Can be standalone | +| **Hoisting** | No | Yes (declarations only) | +| **Overloading** | Yes (multiple signatures) | No (use default params or rest) | +| **Return type** | Must declare | No declaration needed | +| **Default params** | Via overloading | Built-in support | +| **Varargs** | `Type...` | `...args` (rest) | +| **Lambda** | `() -> expression` | `() => expression` | +| **This binding** | Always instance | Depends on call site (arrow: lexical) | + +## What's Next? + +You've learned the basics of JavaScript functions! In **Module 4: Functions Advanced**, we'll explore: + +- Closures and lexical scope +- Higher-order functions +- Currying and partial application +- Memoization +- Recursion in JavaScript +- The `arguments` object +- Function properties and methods + +Ready to level up your function skills? Let's continue! diff --git a/content/docs/java2js/module-03-functions-basics.zh-cn.mdx b/content/docs/java2js/module-03-functions-basics.zh-cn.mdx new file mode 100644 index 0000000..62c3263 --- /dev/null +++ b/content/docs/java2js/module-03-functions-basics.zh-cn.mdx @@ -0,0 +1,1122 @@ +--- +title: "模块 3: 函数基础" +description: "学习 JavaScript 函数声明、表达式、箭头函数和核心概念" +--- + +## 模块 3: 函数基础 + +欢迎来到最重要的模块之一!JavaScript 中的函数比 Java 中的方法更强大、更灵活。深入理解函数对于成为高效的 JavaScript 开发者至关重要。 + +## 学习目标 + +完成本模块后,你将: +✅ 理解函数声明 vs 表达式 +✅ 掌握箭头函数(ES6+) +✅ 学习默认参数 +✅ 理解 rest 和 spread 参数 +✅ 了解参数和实参的区别 +✅ 学习函数提升 + +## 函数基础: Java vs JavaScript + +在 Java 中,方法总是附加到类。在 JavaScript 中,函数是一等公民 - 它们可以独立存在,被赋值给变量,作为参数传递,并从其他函数返回。 + + +```java !! java +// Java - Methods are class members +public class Calculator { + + // Method declaration + public int add(int a, int b) { + return a + b; + } + + // Method overloading + public int add(int a, int b, int c) { + return a + b + c; + } + + // Main method + public static void main(String[] args) { + Calculator calc = new Calculator(); + int result = calc.add(5, 3); + System.out.println(result); // 8 + } +} +``` + +```javascript !! js +// JavaScript - Functions can exist independently +// Function declaration +function add(a, b) { + return a + b; +} + +// Call the function +const result = add(5, 3); +console.log(result); // 8 + +// Can be assigned to variable +const multiply = function(a, b) { + return a * b; +}; + +// Arrow function (ES6+) +const subtract = (a, b) => a - b; + +// All of these are valid +console.log(add(2, 3)); // 5 +console.log(multiply(2, 3)); // 6 +console.log(subtract(5, 2)); // 3 +``` + + +## 函数声明 + +函数声明类似于 Java 方法,但不需要类: + + +```java !! java +// Java - Method inside class +public class Utils { + public static int calculate(int x, int y) { + return x + y; + } + + // Instance method + public String format(String text) { + return text.toUpperCase(); + } +} + +// Calling +Utils.calculate(5, 3); +Utils utils = new Utils(); +utils.format("hello"); +``` + +```javascript !! js +// JavaScript - Standalone functions +function calculate(x, y) { + return x + y; +} + +// Can be called before declaration (hoisting) +console.log(add(5, 3)); // 8 - Works! + +function add(a, b) { + return a + b; +} + +// No public/private modifiers (yet - ES2022 has #) +function privateFunction() { + return "private"; +} + +// Naming convention: camelCase +function getUserById(id) { + // ... implementation +} + +function calculateTotalPrice(items, taxRate) { + // ... implementation +} +``` + + +### 函数提升 + +函数声明被"提升" - 它们可以在定义之前被调用: + + +```java !! java +// Java - No hoisting +public class Example { + public static void main(String[] args) { + greet(); // Compilation error! Method not defined yet + } + + public static void greet() { + System.out.println("Hello!"); + } +} +``` + +```javascript !! js +// JavaScript - Hoisting works for declarations +greet(); // "Hello!" - Works because of hoisting + +function greet() { + console.log("Hello!"); +} + +// Hoisting doesn't work for expressions +greet2(); // TypeError: greet2 is not a function + +const greet2 = function() { + console.log("Hello 2!"); +}; + +// What actually happens: +// 1. Declaration is hoisted +function greet() { + console.log("Hello!"); +} +// 2. Assignment stays in place +// (greet2 is undefined until the assignment) +``` + + +## 函数表达式 + +JavaScript 允许将函数赋值给变量: + + +```java !! java +// Java - Lambda expressions (Java 8+) +interface Operation { + int apply(int a, int b); +} + +public class Calculator { + public static void main(String[] args) { + Operation add = (a, b) -> a + b; + Operation multiply = (a, b) -> a * b; + + System.out.println(add.apply(5, 3)); // 8 + System.out.println(multiply.apply(5, 3)); // 15 + } +} +``` + +```javascript !! js +// JavaScript - Function expressions +const add = function(a, b) { + return a + b; +}; + +const multiply = function(a, b) { + return a * b; +}; + +console.log(add(5, 3)); // 8 +console.log(multiply(5, 3)); // 15 + +// Anonymous function (no name) +setTimeout(function() { + console.log("Delayed execution"); +}, 1000); + +// Named function expression (better for debugging) +const divide = function divideNumbers(a, b) { + if (b === 0) { + throw new Error("Cannot divide by zero"); + } + return a / b; +}; + +// The name is only visible inside the function +console.log(divide(10, 2)); // 5 +// console.log(divideNumbers(10, 2)); // ReferenceError +``` + + +## 箭头函数 + +箭头函数(ES6 引入)是一种编写函数的简洁方式: + + +```java !! java +// Java - Lambda expressions +List numbers = Arrays.asList(1, 2, 3, 4, 5); + +// Single expression +numbers.stream() + .map(n -> n * 2) + .forEach(System.out::println); + +// Multiple statements (need braces) +numbers.stream() + .map(n -> { + int doubled = n * 2; + return doubled + 1; + }) + .forEach(System.out::println); +``` + +```javascript !! js +// JavaScript - Arrow functions +const numbers = [1, 2, 3, 4, 5]; + +// Single expression (implicit return) +const doubled = numbers.map(n => n * 2); +console.log(doubled); // [2, 4, 6, 8, 10] + +// Multiple parameters (need parentheses) +const sum = numbers.reduce((a, b) => a + b, 0); + +// No parameters (need parentheses) +const getRandom = () => Math.random(); + +// Multiple statements (need braces and explicit return) +const processed = numbers.map(n => { + const doubled = n * 2; + return doubled + 1; +}); + +// Returning object literals (need parentheses) +const createUser = name => ({ name, id: Date.now() }); +const user = createUser("John"); +console.log(user); // { name: "John", id: 1234567890 } + +// Arrow functions are anonymous +// Use variable name for identification +const add = (a, b) => a + b; +``` + + +### 箭头函数的局限性 + +箭头函数并不总是合适的。它们在重要方面与普通函数不同: + + +```java !! java +// Java - 'this' refers to the object +public class Counter { + private int count = 0; + + public Runnable getIncrementer() { + return () -> { + this.count++; // 'this' refers to Counter instance + System.out.println(this.count); + }; + } +} +``` + +```javascript !! js +// JavaScript - Arrow functions lexically bind 'this' +const counter = { + count: 0, + increment: function() { + console.log(this.count); // 'this' refers to counter + }, + + // Arrow function as method (NOT recommended) + incrementBad: () => { + console.log(this.count); // 'this' is not counter! + // 'this' is lexically bound from surrounding scope + } +}; + +counter.increment(); // 0 (correct) +counter.incrementBad(); // undefined (wrong!) + +// Correct usage: callback functions +const timer = { + seconds: 0, + start: function() { + // Regular function would lose 'this' + // setInterval(function() { + // this.seconds++; // Error: 'this' is not timer + // }, 1000); + + // Arrow function preserves 'this' + setInterval(() => { + this.seconds++; + console.log(this.seconds); + }, 1000); + } +}; + +timer.start(); // Works correctly! +``` + + +## 默认参数 + +JavaScript 支持默认参数值: + + +```java !! java +// Java - Method overloading +public class Greeter { + public void greet(String name) { + greet(name, "Hello"); + } + + public void greet(String name, String greeting) { + System.out.println(greeting + ", " + name); + } + + // Or use null checks + public void greet2(String name, String greeting) { + if (greeting == null) { + greeting = "Hello"; + } + System.out.println(greeting + ", " + name); + } +} +``` + +```javascript !! js +// JavaScript - Default parameters +function greet(name, greeting = "Hello") { + console.log(`${greeting}, ${name}`); +} + +greet("John"); // "Hello, John" +greet("Jane", "Hi"); // "Hi, Jane" + +// Default with undefined +greet("Bob", undefined); // "Hello, Bob" (uses default) +greet("Alice", null); // "null, Alice" (null is a value!) + +// Expressions as defaults +function createUser(name, role = "user", createdAt = Date.now()) { + return { name, role, createdAt }; +} + +const user1 = createUser("John"); +console.log(user1); // { name: "John", role: "user", createdAt: 1234567890 } + +const user2 = createUser("Jane", "admin"); +console.log(user2); // { name: "Jane", role: "admin", createdAt: 1234567890 } + +// Default can reference previous parameters +function createArray(size, defaultValue = 0) { + return Array(size).fill(defaultValue); +} + +console.log(createArray(5)); // [0, 0, 0, 0, 0] +console.log(createArray(3, "x")); // ["x", "x", "x"] +``` + + +## Rest 参数 + +Rest 参数允许函数接受无限数量的参数: + + +```java !! java +// Java - Varargs +public class Sum { + public int sum(int... numbers) { + int total = 0; + for (int num : numbers) { + total += num; + } + return total; + } + + public static void main(String[] args) { + Sum s = new Sum(); + System.out.println(s.sum(1, 2, 3)); // 6 + System.out.println(s.sum(1, 2, 3, 4, 5)); // 15 + } +} +``` + +```javascript !! js +// JavaScript - Rest parameters +function sum(...numbers) { + return numbers.reduce((total, n) => total + n, 0); +} + +console.log(sum(1, 2, 3)); // 6 +console.log(sum(1, 2, 3, 4, 5)); // 15 +console.log(sum()); // 0 + +// Rest parameter must be last +function log(message, ...args) { + console.log(message, args); +} + +log("Values:", 1, 2, 3); // "Values:" [1, 2, 3] + +// Destructuring with rest +function getUserInfo(firstName, lastName, ...details) { + return { + name: `${firstName} ${lastName}`, + details + }; +} + +const info = getUserInfo("John", "Doe", "Engineer", "NYC", 30); +console.log(info); +// { +// name: "John Doe", +// details: ["Engineer", "NYC", 30] +// } + +// Arrow functions with rest +const multiply = (multiplier, ...numbers) => { + return numbers.map(n => n * multiplier); +}; + +console.log(multiply(2, 1, 2, 3)); // [2, 4, 6] +``` + + +## Spread 运算符 + +spread 运算符将数组/对象展开为单个元素: + + +```java !! java +// Java - Arrays can't be easily spread +int[] arr1 = {1, 2, 3}; +int[] arr2 = {4, 5, 6}; +// Need manual copying or System.arraycopy + +// Java varargs for method calls +public void printAll(String... messages) { + for (String msg : messages) { + System.out.println(msg); + } +} + +printAll("Hello", "World"); // Works +// Can't easily spread an existing array +``` + +```javascript !! js +// JavaScript - Spread operator +const arr1 = [1, 2, 3]; +const arr2 = [4, 5, 6]; + +// Combine arrays +const combined = [...arr1, ...arr2]; +console.log(combined); // [1, 2, 3, 4, 5, 6] + +// Add elements +const withExtra = [0, ...arr1, 4, ...arr2, 7]; +console.log(withExtra); // [0, 1, 2, 3, 4, 4, 5, 6, 7] + +// Copy array +const copy = [...arr1]; + +// Spread in function calls +const numbers = [1, 2, 3, 4, 5]; +console.log(Math.max(...numbers)); // 5 +console.log(Math.min(...numbers)); // 1 + +// Spread with strings +const chars = [..."hello"]; +console.log(chars); // ["h", "e", "l", "l", "o"] + +// Spread objects +const user = { name: "John", age: 25 }; +const enhanced = { ...user, role: "admin" }; +console.log(enhanced); // { name: "John", age: 25, role: "admin" } + +// Override properties +const updated = { ...user, age: 26 }; +console.log(updated); // { name: "John", age: 26 } + +// Merge objects +const obj1 = { a: 1, b: 2 }; +const obj2 = { c: 3, d: 4 }; +const merged = { ...obj1, ...obj2 }; +console.log(merged); // { a: 1, b: 2, c: 3, d: 4 } +``` + + +## 参数 vs 实参 + + +```java !! java +// Java - Parameters (in declaration) +public int add(int a, int b) { // a and b are parameters + return a + b; +} + +// Arguments (in call) +int result = add(5, 3); // 5 and 3 are arguments +``` + +```javascript !! js +// JavaScript - Same concept, but more flexible +function add(a, b) { // a and b are parameters + console.log(a); // undefined if argument not provided + console.log(b); // undefined if argument not provided + return a + b; +} + +add(5, 3); // 5 and 3 are arguments +add(5); // Only 5 is provided, b is undefined +add(5, 3, 7); // 7 is ignored (no error!) + +// Access all arguments (old way) +function sumAll() { + // arguments is array-like object + let total = 0; + for (let i = 0; i < arguments.length; i++) { + total += arguments[i]; + } + return total; +} + +console.log(sumAll(1, 2, 3, 4, 5)); // 15 + +// Modern way: rest parameters +function sumAllModern(...numbers) { + return numbers.reduce((sum, n) => sum + n, 0); +} + +console.log(sumAllModern(1, 2, 3, 4, 5)); // 15 +``` + + +## 返回值 + + +```java !! java +// Java - Must declare return type +public int add(int a, int b) { + return a + b; +} + +// Void return type +public void print(String message) { + System.out.println(message); + // No return needed +} + +// All paths must return value +public int max(int a, int b) { + if (a > b) { + return a; + } + // Error: Missing return statement +} +``` + +```javascript !! js +// JavaScript - No return type declaration +function add(a, b) { + return a + b; +} + +// No return = undefined +function print(message) { + console.log(message); + // Returns undefined implicitly +} + +const result = print("Hello"); +console.log(result); // undefined + +// Early returns +function max(a, b) { + if (a > b) { + return a; + } + return b; +} + +// Conditional return +function getDiscount(amount) { + if (amount > 1000) { + return 0.2; // 20% discount + } + if (amount > 500) { + return 0.1; // 10% discount + } + return 0; // No discount +} + +// Multiple return values (via array or object) +function getMinMax(numbers) { + return { + min: Math.min(...numbers), + max: Math.max(...numbers) + }; +} + +const { min, max } = getMinMax([1, 5, 3, 9, 2]); +console.log(min, max); // 1 9 +``` + + +## 一等函数 + +函数是一等公民 - 它们可以: + +1. 赋值给变量 +2. 作为参数传递 +3. 从其他函数返回 +4. 存储在数据结构中 + + +```java !! java +// Java - Limited support +interface Operation { + int apply(int a, int b); +} + +public class Calculator { + public int calculate(Operation op, int a, int b) { + return op.apply(a, b); + } + + public static void main(String[] args) { + Calculator calc = new Calculator(); + + // Pass lambda as argument + int result = calc.calculate((x, y) -> x + y, 5, 3); + System.out.println(result); // 8 + } +} +``` + +```javascript !! js +// JavaScript - Full first-class function support + +// 1. Assigned to variables +const greet = function(name) { + return `Hello, ${name}`; +}; + +// 2. Passed as arguments +function applyOperation(a, b, operation) { + return operation(a, b); +} + +const sum = (x, y) => x + y; +const difference = (x, y) => x - y; + +console.log(applyOperation(10, 5, sum)); // 15 +console.log(applyOperation(10, 5, difference)); // 5 + +// 3. Returned from functions +function createMultiplier(multiplier) { + return function(number) { + return number * multiplier; + }; +} + +const double = createMultiplier(2); +const triple = createMultiplier(3); + +console.log(double(5)); // 10 +console.log(triple(5)); // 15 + +// 4. Stored in data structures +const operations = { + add: (a, b) => a + b, + subtract: (a, b) => a - b, + multiply: (a, b) => a * b, + divide: (a, b) => a / b +}; + +console.log(operations.add(5, 3)); // 8 +console.log(operations.multiply(4, 3)); // 12 + +// Array of functions +const transformers = [ + x => x * 2, + x => x + 10, + x => x * x +]; + +const value = 5; +const results = transformers.map(fn => fn(value)); +console.log(results); // [10, 15, 25] +``` + + +## 常见模式 + +### 模式 1: 函数工厂 + + +```java !! java +// Java - Not easily possible +// Would need classes and interfaces +``` + +```javascript !! js +// JavaScript - Function factory +function createValidator(regex) { + return function(value) { + return regex.test(value); + }; +} + +const isEmail = createValidator(/^[^\s@]+@[^\s@]+\.[^\s@]+$/); +const isPhone = createValidator(/^\d{3}-\d{3}-\d{4}$/); + +console.log(isEmail("test@example.com")); // true +console.log(isPhone("123-456-7890")); // true + +// Practical example: API client creator +function createApiClient(baseUrl) { + return async function(endpoint, options = {}) { + const response = await fetch(`${baseUrl}${endpoint}`, options); + return response.json(); + }; +} + +const api = createApiClient("https://api.example.com"); +const users = await api("/users"); +const posts = await api("/posts"); +``` + + +### 模式 2: 配置对象 + + +```java !! java +// Java - Builder pattern +public class Request { + private String url; + private String method = "GET"; + private Map headers = new HashMap<>(); + + public static class Builder { + // Builder implementation + } +} + +Request request = new Request.Builder() + .url("https://api.example.com") + .method("POST") + .build(); +``` + +```javascript !! js +// JavaScript - Configuration object with defaults +function makeRequest(url, options = {}) { + const defaults = { + method: "GET", + headers: { + "Content-Type": "application/json" + }, + body: null + }; + + const config = { ...defaults, ...options }; + + return fetch(url, config); +} + +// Usage +makeRequest("https://api.example.com/users", { + method: "POST", + body: JSON.stringify({ name: "John" }) +}); + +// Destructured parameters +function createUser({ name, email, role = "user", active = true }) { + return { name, email, role, active }; +} + +const user = createUser({ + name: "John", + email: "john@example.com" + // role and active use defaults +}); +``` + + +## 最佳实践 + + +```java !! java +// Java: Clear method signatures +public class UserService { + private UserRepository repository; + + public UserService(UserRepository repository) { + this.repository = repository; + } + + public Optional findById(Long id) { + return repository.findById(id); + } + + public List findActiveUsers() { + return repository.findAll() + .stream() + .filter(User::isActive) + .collect(Collectors.toList()); + } +} +``` + +```javascript !! js +// JavaScript: Similar principles + +// 1. Use descriptive names +function calculateMonthlyPayment(principal, rate, months) { + // Good +} + +function calc(p, r, m) { + // Bad - not descriptive +} + +// 2. Keep functions small and focused +function validateEmail(email) { + const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + return regex.test(email); +} + +function validatePassword(password) { + return password.length >= 8; +} + +// Instead of one big function +function validateCredentials(email, password) { + // Validates everything +} + +// 3. Use default parameters for optional arguments +function fetchUsers(options = {}) { + const { + limit = 10, + offset = 0, + active = true + } = options; + + // Implementation +} + +// 4. Prefer arrow functions for callbacks +const numbers = [1, 2, 3, 4, 5]; +const doubled = numbers.map(n => n * 2); + +// 5. Use regular functions for methods +const calculator = { + add: function(a, b) { + return a + b; + }, + + // Or use shorthand (still has correct 'this') + subtract(a, b) { + return a - b; + } +}; + +// 6. Return early to reduce nesting +function processUser(user) { + if (!user) { + return null; + } + + if (!user.isActive) { + return null; + } + + if (!user.email) { + return null; + } + + // Main logic + return transformUser(user); +} +``` + + +## 常见陷阱 + +### 陷阱 1: 在箭头函数中忘记返回 + + +```javascript !! js +// ❌ BAD: Implicit return with curly braces +const doubled = [1, 2, 3].map(n => { + n * 2; +}); +// Result: [undefined, undefined, undefined] + +// ✅ GOOD: Either use explicit return +const doubled2 = [1, 2, 3].map(n => { + return n * 2; +}); + +// ✅ GOOD: Or remove curly braces for implicit return +const doubled3 = [1, 2, 3].map(n => n * 2); + +// ⚠️ Need parentheses for object literals +const users = ["alice", "bob"].map(name => ({ name })); +// Correct: ({ name }) creates object + +// Without parentheses: +const bad = ["alice"].map(name => { name }); +// Result: [undefined] - { name } is a code block, not object +``` + + +### 陷阱 2: 将箭头函数用于方法 + + +```javascript !! js +// ❌ BAD: Arrow function doesn't have own 'this' +const counter = { + count: 0, + increment: () => { + this.count++; // 'this' is not counter! + } +}; + +// ✅ GOOD: Use regular function or shorthand +const counter2 = { + count: 0, + increment: function() { + this.count++; // 'this' is counter2 + }, + + // Or shorthand (same as function) + decrement() { + this.count--; // 'this' is counter2 + } +}; +``` + + +### 陷阱 3: 可变默认参数 + + +```javascript !! js +// ❌ BAD: Mutable default is shared across calls +function addItem(item, list = []) { + list.push(item); + return list; +} + +const list1 = addItem("a"); // ["a"] +const list2 = addItem("b"); // ["a", "b"] - Not a new array! + +// ✅ GOOD: Create new array in function body +function addItemSafe(item, list) { + const items = list ? [...list] : []; + items.push(item); + return items; +} + +// Or use null check +function addItemSafe2(item, list) { + if (!list) list = []; + return [...list, item]; +} +``` + + +## 练习 + +### 练习 1: 转换为箭头函数 +将这些函数转换为箭头函数: +```javascript +function add(a, b) { + return a + b; +} + +function square(x) { + return x * x; +} + +function greet(name) { + return "Hello, " + name; +} +``` + +### 练习 2: 默认参数 +添加默认参数: +```javascript +function createUser(name, email, role) { + return { name, email, role }; +} + +// role should default to "user" +// email should default to empty string +``` + +### 练习 3: Rest 参数 +创建一个计算平均值的函数: +```javascript +function average(/* numbers */) { + // Accept any number of arguments + // Return the average +} + +average(1, 2, 3, 4, 5); // 3 +average(10, 20); // 15 +``` + +### 练习 4: 函数工厂 +创建一个返回函数的函数: +```javascript +function createGreeter(greeting) { + // Return a function that greets with the specified greeting +} + +const sayHello = createGreeter("Hello"); +const sayHi = createGreeter("Hi"); + +sayHello("John"); // "Hello, John" +sayHi("Jane"); // "Hi, Jane" +``` + +## 总结 + +### 关键要点 + +1. **函数声明:** + - 被提升(可以在定义之前调用) + - 传统函数语法 + - 适合独立函数 + +2. **函数表达式:** + - 不被提升 + - 赋值给变量 + - 适合回调和闭包 + +3. **箭头函数:** + - 简洁的语法 + - 词法 `this` 绑定 + - 非常适合回调 + - 不适合方法 + +4. **默认参数:** + - 提供默认值 + - 可以是表达式 + - 仅由 `undefined` 触发,而不是 `null` + +5. **Rest/Spread:** + - Rest: 将参数收集到数组中 + - Spread: 将数组/对象展开为元素 + - 便于操作 + +6. **一等函数:** + - 可以被赋值、传递、返回 + - 支持高阶函数 + - 支持函数式编程 + +### 比较表: Java vs JavaScript + +| 特性 | Java | JavaScript | +|------|------|-----------| +| **声明** | 仅在类中 | 可以独立 | +| **提升** | 否 | 是(仅声明) | +| **重载** | 是(多个签名) | 否(使用默认参数或 rest) | +| **返回类型** | 必须声明 | 不需要声明 | +| **默认参数** | 通过重载 | 内置支持 | +| **可变参数** | `Type...` | `...args` (rest) | +| **Lambda** | `() -> expression` | `() => expression` | +| **This 绑定** | 始终是实例 | 取决于调用位置(箭头函数: 词法) | + +## 下一步是什么? + +你已经学习了 JavaScript 函数的基础!在**模块 4: 函数高级**中,我们将探索: + +- 闭包和词法作用域 +- 高阶函数 +- 柯里化和部分应用 +- 记忆化 +- JavaScript 中的递归 +- `arguments` 对象 +- 函数属性和方法 + +准备好提升你的函数技能了吗?让我们继续! diff --git a/content/docs/java2js/module-03-functions-basics.zh-tw.mdx b/content/docs/java2js/module-03-functions-basics.zh-tw.mdx new file mode 100644 index 0000000..2201c85 --- /dev/null +++ b/content/docs/java2js/module-03-functions-basics.zh-tw.mdx @@ -0,0 +1,854 @@ +--- +title: "Module 3: Functions Basics" +description: "Master JavaScript functions: declarations, expressions, arrows, and basic concepts" +--- + +## Module 3: Functions Basics + +Functions 是 JavaScript 的「一等公民」,比 Java 的方法更靈活且更強大。在本模組中,我們將探討函數聲明、表達式、箭頭函數,以及使 JavaScript 函數獨特的基本概念。 + +## Learning Objectives + +完成本模組後,你將: +✅ 理解函數聲明與表達式的區別 +✅ 掌握箭頭函數的語法與行為 +✅ 學習預設參數 +✅ 理解 rest 和 spread 參數 +✅ 掌握函數提升(hoisting) +✅ 知道一等函數的含義 + +## Functions vs Methods + +在 Java 中,函數存在於類別中並被稱為方法。在 JavaScript 中,函數是獨立的值: + + +```java !! java +// Java - Methods are part of classes +public class Calculator { + public int add(int a, int b) { + return a + b; + } + + public static int multiply(int a, int b) { + return a * b; + } + + public static void main(String[] args) { + Calculator calc = new Calculator(); + int sum = calc.add(5, 3); + + int product = Calculator.multiply(4, 3); + } +} + +// Methods must be in classes +// Cannot have standalone functions +``` + +```javascript !! js +// JavaScript - Functions are first-class values +function add(a, b) { + return a + b; +} + +const multiply = function(a, b) { + return a * b; +}; + +// Arrow function(ES6+) +const subtract = (a, b) => a - b; + +// Functions as values +const operation = add; +console.log(operation(5, 3)); // 8 + +// Functions in objects(methods) +const calculator = { + add: function(a, b) { + return a + b; + }, + + // Shorthand method syntax + subtract(a, b) { + return a - b; + } +}; + +console.log(calculator.add(5, 3)); // 8 +console.log(calculator.subtract(10, 4)); // 6 +``` + + +## Function Declarations vs Expressions + + +```java !! java +// Java - Only method declarations +public class Example { + // Method declaration + public void greet() { + System.out.println("Hello!"); + } + + // No equivalent to function expressions + // Methods are always bound to class +} +``` + +```javascript !! js +// JavaScript - Declarations +function greet1(name) { + console.log(`Hello, ${name}!`); +} + +greet1("Alice"); // "Hello, Alice!" + +// Function expression(anonymous function assigned to variable) +const greet2 = function(name) { + console.log(`Hi, ${name}!`); +}; + +greet2("Bob"); // "Hi, Bob!" + +// Named function expression(useful for debugging) +const greet3 = function greet(name) { + console.log(`Hey, ${name}!`); + // Can call greet recursively here +}; + +greet3("Charlie"); // "Hey, Charlie!" + +// Key differences: +// 1. Hoisting +console.log(declared(5)); // 10(works due to hoisting) +function declared(x) { + return x * 2; +} + +// console.log(expressed(5)); // Error: expressed is not a hoist +const expressed = function(x) { + return x * 2; +}; + +// 2. Scope +{ + functionscoped() { + console.log("Function scoped"); + } + varscoped = function() { + console.log("Var scoped"); + }; +} + +functionscoped(); // Works +varscoped(); // Error in strict mode +``` + + +## Arrow Functions + +箭頭函數提供了更簡潔的語法,但有一些重要的行為差異: + + +```java !! java +// Java - Lambda expressions +List numbers = Arrays.asList(1, 2, 3, 4, 5); + +numbers.stream() + .filter(n -> n % 2 == 0) + .forEach(n -> System.out.println(n)); + +// Multi-line lambda +numbers.stream() + .map(n -> { + int doubled = n * 2; + return doubled + 1; + }); +``` + +```javascript !! js +// JavaScript - Arrow functions +const numbers = [1, 2, 3, 4, 5]; + +// No parameters +const log = () => console.log("Logging"); +log(); + +// One parameter(parentheses optional) +const double = n => n * 2; +console.log(double(5)); // 10 + +// Multiple parameters(parentheses required) +const add = (a, b) => a + b; +console.log(add(3, 4)); // 7 + +// Function body(explicit return) +const calculate = (a, b) => { + const sum = a + b; + return sum * 2; +}; +console.log(calculate(3, 4)); // 14 + +// Returning object literal(parentheses needed) +const createUser = (name, age) => ({ + name: name, + age: age, + adult: age >= 18 +}); +console.log(createUser("Alice", 25)); // { name: "Alice", age: 25, adult: true } + +// Arrow functions as callbacks +numbers + .filter(n => n % 2 === 0) + .forEach(n => console.log(n)); + +// ⚠️ Key difference: No 'this' binding +const obj = { + value: 42, + // Regular function: 'this' is obj + regular: function() { + console.log(this.value); // 42 + }, + // Arrow function: 'this' is inherited from surrounding scope + arrow: () => { + console.log(this.value); // undefined(or window in browser) + } +}; + +obj.regular(); // 42 +obj.arrow(); // undefined + +// ⚠️ Arrow functions cannot be used as constructors +const User = (name) => { + this.name = name; +}; + +// const user = new User("Alice"); // TypeError: User is not a constructor +``` + + +## Default Parameters + +JavaScript 提供了比 Java 更靈活的預設參數: + + +```java !! java +// Java - Method overloading +public class Greeter { + public void greet() { + greet("World"); + } + + public void greet(String name) { + greet(name, "Hello"); + } + + public void greet(String name, String greeting) { + System.out.println(greeting + ", " + name + "!"); + } +} + +// Usage +greeter.greet(); // "Hello, World!" +greeter.greet("Alice"); // "Hello, Alice!" +greeter.greet("Bob", "Hi"); // "Hi, Bob!" +``` + +```javascript !! js +// JavaScript - Default parameters +function greet(name = "World", greeting = "Hello") { + console.log(`${greeting}, ${name}!`); +} + +greet(); // "Hello, World!" +greet("Alice"); // "Hello, Alice!" +greet("Bob", "Hi"); // "Hi, Bob!" + +// Expressions as defaults +function createUser(name, role = "user", createdAt = Date.now()) { + return { name, role, createdAt }; +} + +console.log(createUser("Alice")); +// { name: "Alice", role: "user", createdAt: 1234567890 } + +console.log(createUser("Bob", "admin")); +// { name: "Bob", role: "admin", createdAt: 1234567890 } + +// Default parameters can use previous parameters +function createArray(size, fillValue = 0, array = new Array(size).fill(fillValue)) { + return array; +} + +console.log(createArray(5)); // [0, 0, 0, 0, 0] +console.log(createArray(3, "x")); // ["x", "x", "x"] + +// ⚠️ Default parameters are not evaluated for null +function test(value = "default") { + console.log(value); +} + +test(undefined); // "default" +test(null); // null(not "default"!) +test(0); // 0 +test(""); // "" + +// Pattern: Provide defaults for objects +function config({ host = "localhost", port = 3000, ssl = false } = {}) { + return { host, port, ssl }; +} + +console.log(config()); +// { host: "localhost", port: 3000, ssl: false } + +console.log(config({ host: "example.com", port: 8080 })); +// { host: "example.com", port: 8080, ssl: false } +``` + + +## Rest Parameters + +Rest parameters 允許你將不定數量的參數表示為陣列: + + +```java !! java +// Java - Varargs +public class Calculator { + public int sum(int... numbers) { + int total = 0; + for (int num : numbers) { + total += num; + } + return total; + } + + // Must be last parameter + public void log(String message, Object... data) { + System.out.println(message + " " + Arrays.toString(data)); + } +} + +// Usage +calculator.sum(1, 2, 3, 4, 5); // 15 +calculator.log("Data:", 1, "test", true); +``` + +```javascript !! js +// JavaScript - Rest parameters +function sum(...numbers) { + return numbers.reduce((total, n) => total + n, 0); +} + +console.log(sum(1, 2, 3)); // 6 +console.log(sum(1, 2, 3, 4, 5)); // 15 + +// Mixed with regular parameters +function greet(greeting, ...names) { + names.forEach(name => { + console.log(`${greeting}, ${name}!`); + }); +} + +greet("Hello", "Alice", "Bob", "Charlie"); +// "Hello, Alice!" +// "Hello, Bob!" +// "Hello, Charlie!" + +// Rest parameter must be last +// function bad(...rest, last) {} // SyntaxError + +// Practical: Create flexible API +function middleware(...handlers) { + return function(req, res, next) { + handlers.forEach((handler, index) => { + if (index === handlers.length - 1) { + handler(req, res, next); + } else { + handler(req, res, () => {}); + } + }); + }; +} + +// Destructuring with rest +function processUser({ id, name, ...details }) { + console.log(`User ${id}: ${name}`); + console.log("Details:", details); +} + +processUser({ + id: 1, + name: "Alice", + age: 25, + email: "alice@example.com", + role: "admin" +}); +// User 1: Alice +// Details: { age: 25, email: "alice@example.com", role: "admin" } +``` + + +## Spread Operator + +Spread 運算符允許你展開可迭代物件: + + +```java !! java +// Java - Arrays.copyOf or manual copy +int[] original = {1, 2, 3}; +int[] copy = Arrays.copyOf(original, original.length); + +// Or manual +int[] copy2 = new int[original.length]; +for (int i = 0; i < original.length; i++) { + copy2[i] = original[i]; +} + +// Java doesn't have spread operator +``` + +```javascript !! js +// JavaScript - Spread operator +const arr1 = [1, 2, 3]; +const arr2 = [4, 5, 6]; + +// Spread into new array +const combined = [...arr1, ...arr2]; +console.log(combined); // [1, 2, 3, 4, 5, 6] + +// Spread with other values +const withExtras = [0, ...arr1, 3.5, ...arr2, 7]; +console.log(withExtras); // [0, 1, 2, 3, 3.5, 4, 5, 6, 7] + +// Copy array +const copy = [...arr1]; + +// Spread into function arguments +function add(a, b, c) { + return a + b + c; +} + +const numbers = [1, 2, 3]; +console.log(add(...numbers)); // 6 + +// Spread objects(ES2018+) +const user = { name: "Alice", age: 25 }; +const withRole = { ...user, role: "admin" }; +console.log(withRole); +// { name: "Alice", age: 25, role: "admin" } + +// Override properties +const updated = { ...user, age: 26 }; +console.log(updated); +// { name: "Alice", age: 26 } + +// Merge objects +const defaults = { theme: "dark", lang: "en" }; +const settings = { lang: "zh-tw" }; +const final = { ...defaults, ...settings }; +console.log(final); // { theme: "dark", lang: "zh-tw" } + +// Practical: Immutable updates +const state = { count: 0, name: "counter" }; +const newState = { ...state, count: state.count + 1 }; +console.log(state, newState); +// { count: 0, name: "counter" } { count: 1, name: "counter" } +``` + + +## Hoisting + +函數提升是 JavaScript 中的一個重要概念: + + +```java !! java +// Java - No hoisting +// Methods must be declared before use +public class Example { + public void method1() { + method2(); // Compilation error if method2 not declared + } +} +``` + +```javascript !! js +// JavaScript - Hoisting + +// Function declarations are hoisted +console.log(declared()); // "Declared!"(works) + +function declared() { + return "Declared!"; +} + +// Function expressions are NOT hoisted +// console.log(expressed()); // TypeError: expressed is not a function + +const expressed = function() { + return "Expressed!"; +}; + +// Var declarations are hoisted(but undefined) +console.log(varVar); // undefined(not ReferenceError) +var varVar = 5; + +// Let/const are NOT hoisted to the top +// console.log(letVar); // ReferenceError: Cannot access 'letVar' before initialization +let letVar = 10; + +// Practical: Safe ordering +// 1. Declare functions first +function processData(data) { + return data.map(transform); +} + +function transform(item) { + return item * 2; +} + +// 2. Use function expressions for mutual recursion +const isEven = (n) => { + if (n === 0) return true; + return isOdd(n - 1); +}; + +const isOdd = (n) => { + if (n === 0) return false; + return isEven(n - 1); +}; +``` + + +## First-Class Functions + +函數作為一等公民意味著它們可以: + + +```java !! java +// Java - Methods are not first-class +// Need interfaces/functional interfaces for function-like behavior + +@FunctionalInterface +interface Operation { + int apply(int a, int b); +} + +public class Calculator { + public int calculate(Operation op, int a, int b) { + return op.apply(a, b); + } + + public static void main(String[] args) { + Calculator calc = new Calculator(); + + // Lambda(close to first-class function) + int result = calc.calculate((x, y) -> x + y, 5, 3); + } +} +``` + +```javascript !! js +// JavaScript - Functions are first-class citizens + +// 1. Assign to variables +const greet = function(name) { + console.log(`Hello, ${name}!`); +}; + +// 2. Pass as arguments +function repeat(fn, times) { + for (let i = 0; i < times; i++) { + fn(); + } +} + +repeat(() => console.log("Hello!"), 3); +// "Hello!" +// "Hello!" +// "Hello!" + +// 3. Return from functions +function createGreeter(greeting) { + return function(name) { + console.log(`${greeting}, ${name}!`); + }; +} + +const sayHi = createGreeter("Hi"); +sayHi("Alice"); // "Hi, Alice!" + +const sayHello = createGreeter("Hello"); +sayHello("Bob"); // "Hello, Bob!" + +// 4. Store in data structures +const operations = { + add: (a, b) => a + b, + subtract: (a, b) => a - b, + multiply: (a, b) => a * b, + divide: (a, b) => a / b +}; + +console.log(operations.add(5, 3)); // 8 +console.log(operations.multiply(4, 3)); // 12 + +// 5. Have properties(functions are objects) +function greet(name) { + console.log(`Hello, ${name}!`); +} + +greet.version = "1.0"; +greet.author = "Alice"; +console.log(greet.version); // "1.0" +``` + + +## Practical Patterns + +### Pattern 1: Function Composition + + +```javascript !! js +// Compose multiple functions +function compose(...fns) { + return function(value) { + return fns.reduceRight((acc, fn) => fn(acc), value); + }; +} + +const toUpper = str => str.toUpperCase(); +const exclaim = str => str + "!"; +const reverse = str => str.split("").reverse().join(""); + +const transform = compose(reverse, exclaim, toUpper); +console.log(transform("hello")); // "!OLLEH" + +// Pipe(left to right) +function pipe(...fns) { + return function(value) { + return fns.reduce((acc, fn) => fn(acc), value); + }; +} + +const transform2 = pipe(toUpper, exclaim, reverse); +console.log(transform2("hello")); // "ELH!" +``` + + +### Pattern 2: Closures Introduction + + +```java !! java +// Java - Anonymous classes capture variables +public class Greeter { + public Function createGreeter(String greeting) { + return name -> greeting + ", " + name + "!"; + // greeting is "captured"(effectively final) + } +} +``` + +```javascript !! js +// JavaScript - Closures +function createGreeter(greeting) { + return function(name) { + console.log(`${greeting}, ${name}!`); + }; + // greeting is "closed over" and remembered +} + +const sayHi = createGreeter("Hi"); +const sayHello = createGreeter("Hello"); + +sayHi("Alice"); // "Hi, Alice!" +sayHello("Bob"); // "Hello, Bob!" + +// Practical: Function factories +function createMultiplier(factor) { + return function(number) { + return number * factor; + }; +} + +const double = createMultiplier(2); +const triple = createMultiplier(3); + +console.log(double(5)); // 10 +console.log(triple(5)); // 15 + +// Practical: Private state(covered more in Module 4) +function createCounter() { + let count = 0; + return { + increment: () => ++count, + decrement: () => --count, + getCount: () => count + }; +} + +const counter = createCounter(); +console.log(counter.increment()); // 1 +console.log(counter.increment()); // 2 +console.log(counter.getCount()); // 2 +``` + + +## Best Practices + + +```javascript !! js +// 1. Use arrow functions for short callbacks +const numbers = [1, 2, 3, 4, 5]; +const doubled = numbers.map(n => n * 2); + +// 2. Use regular functions for methods +const calculator = { + value: 0, + add: function(v) { + this.value += v; + }, + subtract(v) { + this.value -= v; + } +}; + +// 3. Use default parameters instead of checking for undefined +function greet(name = "World") { + console.log(`Hello, ${name}!`); +} + +// 4. Use rest parameters instead of arguments +function sum(...numbers) { + return numbers.reduce((a, b) => a + b, 0); +} + +// 5. Use descriptive names +// Bad +function fn(x) { + return x * 2; +} + +// Good +function double(value) { + return value * 2; +} + +// 6. Keep functions small and focused +// Bad +function process(data) { + // 50 lines of code doing multiple things +} + +// Good +function validate(data) { + // Validation logic +} + +function transform(data) { + // Transformation logic +} + +function save(data) { + // Save logic +} + +// 7. Return early(guard clauses) +function processUser(user) { + if (!user) { + return null; + } + + if (!user.isActive) { + return null; + } + + return transformUser(user); +} +``` + + +## Exercises + +### Exercise 1: Function Types +將此 Java 程式碼轉換為 JavaScript: +```java +public class Calculator { + public int add(int a, int b) { + return a + b; + } + + public int multiply(int a, int b) { + return a * b; + } +} +``` + +### Exercise 2: Arrow Functions +使用箭頭函數重寫: +```javascript +function add(a, b) { + return a + b; +} + +function greet(name) { + return "Hello, " + name; +} +``` + +### Exercise 3: Default Parameters +創建一個帶有預設參數的函數: +```javascript +function createRequest(url, method, body, headers) { + // Set defaults: method = "GET", body = null, headers = {} +} +``` + +### Exercise 4: Rest Parameters +創建一個函數來計算可變數量參數的平均值: +```javascript +function average(...numbers) { + // Return the average of all numbers +} +``` + +## Summary + +### Key Takeaways + +1. **Declarations vs Expressions:** + - Declarations 會被提升 + - Expressions 不會被提升 + - 選擇取決於風格和使用案例 + +2. **Arrow Functions:** + - 更簡潔的語法 + - 詞法 `this`(繼承自周圍作用域) + - 不能用作建構函數 + - 適合用於回調函數 + +3. **Default Parameters:** + - 比方法多載更靈活 + - 可以是運算式 + - 僅對 undefined 使用預設值 + +4. **Rest & Spread:** + - Rest: 收集參數為陣列 + - Spread: 展開陣列/物件 + - 對可變參數和不可變更新很有用 + +5. **First-Class Functions:** + - 可以被指派、傳遞、返回 + - 可以有屬性 + - 是 JavaScript 的核心概念 + +## What's Next? + +你已經掌握了函數基礎!接下來是 **Module 4: Functions Advanced**,我們將探討: + +- Closures in depth +- Higher-order functions +- Currying and partial application +- Memoization +- Recursion in JavaScript +- Generator functions + +準備好深入學習進階函數概念了嗎?讓我們繼續! diff --git a/content/docs/java2js/module-04-functions-advanced.mdx b/content/docs/java2js/module-04-functions-advanced.mdx new file mode 100644 index 0000000..5d41a41 --- /dev/null +++ b/content/docs/java2js/module-04-functions-advanced.mdx @@ -0,0 +1,1157 @@ +--- +title: "Module 4: Functions Advanced" +description: "Master closures, higher-order functions, currying, and advanced function patterns" +--- + +## Module 4: Functions Advanced + +Now that you understand the basics, let's explore JavaScript's advanced function capabilities. These concepts are powerful and enable elegant solutions to complex problems. + +## Learning Objectives + +By the end of this module, you will: +✅ Understand closures and lexical scope +✅ Master higher-order functions +✅ Learn currying and partial application +✅ Understand memoization for performance +✅ Know recursion patterns and tail call optimization +✅ Learn function composition and chaining + +## Closures + +Closures are one of JavaScript's most powerful features. A closure gives you access to an outer function's scope from an inner function. + + +```java !! java +// Java - Limited closure support (lambda with captured variables) +public class ClosureExample { + public static Supplier makeCounter() { + int[] count = {0}; // Must use array for mutability + return () -> { + count[0]++; + return count[0]; + }; + } + + public static void main(String[] args) { + Supplier counter1 = makeCounter(); + Supplier counter2 = makeCounter(); + + System.out.println(counter1.get()); // 1 + System.out.println(counter1.get()); // 2 + System.out.println(counter2.get()); // 1 (separate counter) + } +} +``` + +```javascript !! js +// JavaScript - True closures +function makeCounter() { + let count = 0; // Private variable + + // Inner function has access to outer scope + return function() { + count++; + return count; + }; +} + +const counter1 = makeCounter(); +const counter2 = makeCounter(); + +console.log(counter1()); // 1 +console.log(counter1()); // 2 +console.log(counter2()); // 1 (separate closure) + +// Practical example: Private data +function createUser(name) { + let age = 0; // Private + + return { + getName: () => name, + getAge: () => age, + setAge: (newAge) => { + if (newAge >= 0) { + age = newAge; + } + }, + incrementAge: () => { + age++; + } + }; +} + +const user = createUser("John"); +console.log(user.getName()); // "John" +console.log(user.getAge()); // 0 +user.setAge(25); +console.log(user.getAge()); // 25 +user.incrementAge(); +console.log(user.getAge()); // 26 +console.log(user.age); // undefined (private!) +``` + + +### Closure Use Cases + + +```java !! java +// Java - Need classes for state +public class Cache { + private Map storage = new HashMap<>(); + + public String get(String key) { + return storage.get(key); + } + + public void set(String key, value) { + storage.put(key, value); + } +} +``` + +```javascript !! js +// JavaScript - Closures for encapsulation + +// Pattern 1: Function factory +function createCache() { + const cache = new Map(); + + return { + get(key) { + return cache.get(key); + }, + set(key, value) { + cache.set(key, value); + }, + has(key) { + return cache.has(key); + }, + clear() { + cache.clear(); + } + }; +} + +const userCache = createCache(); +userCache.set("user1", { name: "John" }); +console.log(userCache.get("user1")); // { name: "John" } + +// Pattern 2: Configuration +function createLogger(prefix) { + return function(message) { + const timestamp = new Date().toISOString(); + console.log(`[${timestamp}] [${prefix}] ${message}`); + }; +} + +const errorLogger = createLogger("ERROR"); +const infoLogger = createLogger("INFO"); + +errorLogger("Database connection failed"); +infoLogger("User logged in"); + +// Pattern 3: Rate limiter +function rateLimit(func, delay) { + let lastCall = 0; + let timer = null; + + return function(...args) { + const now = Date.now(); + const timeSinceLastCall = now - lastCall; + + if (timeSinceLastCall >= delay) { + lastCall = now; + return func.apply(this, args); + } else { + // Clear pending call + if (timer) clearTimeout(timer); + + // Schedule new call + timer = setTimeout(() => { + lastCall = Date.now(); + func.apply(this, args); + }, delay - timeSinceLastCall); + } + }; +} + +const saveData = rateLimit((data) => { + console.log("Saving:", data); +}, 1000); + +saveData({ id: 1 }); // Executes immediately +saveData({ id: 2 }); // Debounced +``` + + +### Common Closure Pitfalls + + +```javascript !! js +// Pitfall 1: Loop closures +// ❌ BAD: All functions capture same variable +const funcs = []; +for (var i = 0; i < 3; i++) { + funcs.push(() => console.log(i)); +} +funcs[0](); // 3 (not 0!) +funcs[1](); // 3 (not 1!) +funcs[2](); // 3 (not 2!) + +// ✅ GOOD: Use let (block-scoped) +const funcs2 = []; +for (let i = 0; i < 3; i++) { + funcs2.push(() => console.log(i)); +} +funcs2[0](); // 0 +funcs2[1](); // 1 +funcs2[2](); // 2 + +// ✅ GOOD: Use IIFE (for var) +const funcs3 = []; +for (var i = 0; i < 3; i++) { + (function(j) { + funcs3.push(() => console.log(j)); + })(i); +} +funcs3[0](); // 0 + +// Pitfall 2: Memory leaks +function setupHandlers() { + const hugeData = new Array(1000000).fill("data"); + + document.getElementById("button").addEventListener("click", function() { + console.log("Clicked"); // Closes over hugeData! + }); +} + +// ✅ GOOD: Minimize closure scope +function setupHandlers() { + const button = document.getElementById("button"); + const handler = () => console.log("Clicked"); + + button.addEventListener("click", handler); + // hugeData not captured +} +``` + + +## Higher-Order Functions + +Functions that take or return other functions: + + +```java !! java +// Java - Functional interfaces +public interface Predicate { + boolean test(T t); +} + +public class Utils { + public static List filter(List list, Predicate predicate) { + List result = new ArrayList<>(); + for (T item : list) { + if (predicate.test(item)) { + result.add(item); + } + } + return result; + } + + // Higher-order function that returns a function + public static Predicate greaterThan(int n) { + return x -> x > n; + } +} +``` + +```javascript !! js +// JavaScript - Native higher-order functions + +// Function that takes a function +function filter(array, predicate) { + const result = []; + for (const item of array) { + if (predicate(item)) { + result.push(item); + } + } + return result; +} + +const numbers = [1, 2, 3, 4, 5]; +const evens = filter(numbers, n => n % 2 === 0); +console.log(evens); // [2, 4] + +// Function that returns a function +function greaterThan(n) { + return function(x) { + return x > n; + }; +} + +const greaterThan5 = greaterThan(5); +console.log(greaterThan5(3)); // false +console.log(greaterThan5(10)); // true + +// Practical example: Notifier +function createNotifier(sender) { + return function(recipient, message) { + console.log(`From: ${sender}`); + console.log(`To: ${recipient}`); + console.log(`Message: ${message}`); + }; +} + +const adminNotifier = createNotifier("admin@app.com"); +adminNotifier("user@app.com", "Welcome!"); + +// Composition +function compose(f, g) { + return function(x) { + return f(g(x)); + }; +} + +const toUpper = str => str.toUpperCase(); +const exclaim = str => str + "!"; + +const shout = compose(exclaim, toUpper); +console.log(shout("hello")); // "HELLO!" +``` + + +### Array Methods as Higher-Order Functions + + +```java !! java +// Java - Stream operations +List numbers = Arrays.asList(1, 2, 3, 4, 5); + +List result = numbers.stream() + .filter(n -> n % 2 == 0) + .map(n -> n * 2) + .collect(Collectors.toList()); +``` + +```javascript !! js +// JavaScript - Array methods (all are higher-order functions) +const numbers = [1, 2, 3, 4, 5]; + +// filter: Takes predicate function +const evens = numbers.filter(n => n % 2 === 0); + +// map: Takes transform function +const doubled = numbers.map(n => n * 2); + +// reduce: Takes reducer function +const sum = numbers.reduce((acc, n) => acc + n, 0); + +// find: Takes predicate function +const found = numbers.find(n => n > 3); + +// every: Takes predicate function +const allPositive = numbers.every(n => n > 0); + +// some: Takes predicate function +const hasEven = numbers.some(n => n % 2 === 0); + +// Chaining higher-order functions +const result = numbers + .filter(n => n % 2 === 0) // Keep evens + .map(n => n * 2) // Double them + .reduce((acc, n) => acc + n, 0); // Sum them + +console.log(result); // 12 (2*2 + 4*2 = 4 + 8) +``` + + +## Currying and Partial Application + +Currying transforms a function with multiple arguments into a sequence of functions taking a single argument: + + +```java !! java +// Java - No built-in currying +// Would need custom implementation +public interface TriFunction { + R apply(A a, B b, C c); +} + +public static Function>> curry( + TriFunction f +) { + return a -> b -> c -> f.apply(a, b, c); +} + +// Usage is verbose +``` + +```javascript !! js +// JavaScript - Currying + +// Non-curried function +function add(a, b, c) { + return a + b + c; +} + +console.log(add(1, 2, 3)); // 6 + +// Curried function +function curryAdd(a) { + return function(b) { + return function(c) { + return a + b + c; + }; + }; +} + +console.log(curryAdd(1)(2)(3)); // 6 + +// Arrow function curry (more concise) +const curryAdd2 = a => b => c => a + b + c; + +// Partial application +const add1 = curryAdd2(1); +const add1And2 = add1(2); + +console.log(add1And2(3)); // 6 + +// Practical example: API client +function createApiFetcher(baseUrl) { + return function(endpoint) { + return function(options) { + return fetch(`${baseUrl}${endpoint}`, options) + .then(res => res.json()); + }; + }; +} + +const jsonApi = createApiFetcher("https://api.example.com"); +const userApi = jsonApi("/users"); +const getAdmins = userApi({ ?role=admin }); + +// Currying utility +function curry(fn) { + return function curried(...args) { + if (args.length >= fn.length) { + return fn.apply(this, args); + } + return function(...more) { + return curried.apply(this, [...args, ...more]); + }; + }; +} + +const curriedMap = curry((fn, array) => array.map(fn)); +const doubleAll = curriedMap(x => x * 2); + +console.log(doubleAll([1, 2, 3])); // [2, 4, 6] +``` + + +### Partial Application + + +```java !! java +// Java - Method references (limited partial application) +public class Printer { + public void print(String prefix, String message) { + System.out.println(prefix + ": " + message); + } + + public void printInfo(String message) { + print("INFO", message); // Partial application + } +} +``` + +```javascript !! js +// JavaScript - Partial application + +// Using bind() +function greet(greeting, name) { + console.log(`${greeting}, ${name}`); +} + +const sayHello = greet.bind(null, "Hello"); +sayHello("John"); // "Hello, John" + +// Using closures (more flexible) +function partial(fn, ...presetArgs) { + return function(...laterArgs) { + return fn(...presetArgs, ...laterArgs); + }; +} + +const greetHello = partial(greet, "Hello"); +greetHello("Jane"); // "Hello, Jane" + +// Practical: Event handlers +function handleClick(element, callback) { + element.addEventListener("click", function(event) { + callback(element, event); + }); +} + +handleClick(button, (elem, e) => { + console.log("Clicked:", elem); + console.log("Event:", e); +}); + +// Partial application for configuration +function fetch(url, options, timeout) { + // Implementation +} + +const fetchJson = partial(fetch, null, { headers: { "Accept": "application/json" } }); +fetchJson("https://api.example.com", 5000); +``` + + +## Memoization + +Memoization caches function results to avoid redundant calculations: + + +```java !! java +// Java - Need custom caching +public class Memoizer { + private Map cache = new HashMap<>(); + + public int fibonacci(int n) { + if (n <= 1) return n; + + if (cache.containsKey(n)) { + return cache.get(n); + } + + int result = fibonacci(n - 1) + fibonacci(n - 2); + cache.put(n, result); + return result; + } +} +``` + +```javascript !! js +// JavaScript - Memoization utility + +function memoize(fn) { + const cache = new Map(); + + return function(...args) { + const key = JSON.stringify(args); + + if (cache.has(key)) { + console.log("From cache:", key); + return cache.get(key); + } + + const result = fn.apply(this, args); + cache.set(key, result); + return result; + }; +} + +// Expensive function +function fibonacci(n) { + if (n <= 1) return n; + return fibonacci(n - 1) + fibonacci(n - 2); +} + +const memoFib = memoize(fibonacci); + +console.log(memoFib(40)); // Slow (first time) +console.log(memoFib(40)); // Fast (from cache) + +// Practical example: API calls +function fetchUserData(userId) { + return fetch(`/api/users/${userId}`) + .then(res => res.json()); +} + +const memoFetch = memoize(fetchUserData); + +// Subsequent calls for same user are cached +memoFetch(1).then(console.log); +memoFetch(1).then(console.log); // From cache + +// Memoization with TTL +function memoizeTTL(fn, ttl = 1000) { + const cache = new Map(); + + return function(...args) { + const key = JSON.stringify(args); + const cached = cache.get(key); + + if (cached && Date.now() - cached.timestamp < ttl) { + return cached.value; + } + + const result = fn.apply(this, args); + cache.set(key, { + value: result, + timestamp: Date.now() + }); + + return result; + }; +} + +const memoFetchTTL = memoizeTTL(fetchUserData, 5000); // 5 second cache +``` + + +## Recursion + +JavaScript supports recursion, though it lacks tail call optimization in most implementations: + + +```java !! java +// Java - Recursion with tail call optimization +public class Recursion { + public int factorial(int n) { + if (n <= 1) return 1; + return n * factorial(n - 1); + } + + // Tail-recursive version + public int factorialTail(int n) { + return factorialTailHelper(n, 1); + } + + private int factorialTailHelper(int n, int accumulator) { + if (n <= 1) return accumulator; + return factorialTailHelper(n - 1, n * accumulator); + } +} +``` + +```javascript !! js +// JavaScript - Recursion + +// Factorial (not tail-optimized) +function factorial(n) { + if (n <= 1) return 1; + return n * factorial(n - 1); +} + +console.log(factorial(5)); // 120 + +// Tail-recursive version (not optimized in most JS engines) +function factorialTail(n, accumulator = 1) { + if (n <= 1) return accumulator; + return factorialTail(n - 1, n * accumulator); +} + +console.log(factorialTail(5)); // 120 + +// Practical: Tree traversal +const fileSystem = { + name: "root", + type: "folder", + children: [ + { + name: "docs", + type: "folder", + children: [ + { name: "report.txt", type: "file" }, + { name: "notes.txt", type: "file" } + ] + }, + { + name: "photos", + type: "folder", + children: [ + { name: "vacation.jpg", type: "file" } + ] + } + ] +}; + +function findFiles(node, type = "file") { + if (node.type === type) { + return [node.name]; + } + + if (!node.children) { + return []; + } + + return node.children.flatMap(child => findFiles(child, type)); +} + +console.log(findFiles(fileSystem)); +// ["report.txt", "notes.txt", "vacation.jpg"] + +// Recursion with trampoline (for deep recursion) +function trampoline(fn) { + return function(...args) { + let result = fn(...args); + + while (typeof result === "function") { + result = result(); + } + + return result; + }; +} + +function factorialTramp(n, acc = 1) { + if (n <= 1) return acc; + return () => factorialTramp(n - 1, n * acc); +} + +const safeFactorial = trampoline(factorialTramp); +``` + + +## Function Composition + +Combining functions to create new functions: + + +```java !! java +// Java - Composing functions manually +public class Composer { + public static Function compose( + Function f, + Function g + ) { + return x -> f.apply(g.apply(x)); + } + + // or using default method in Function interface + // f.compose(g) or f.andThen(g) +} +``` + +```javascript !! js +// JavaScript - Function composition + +// Basic composition +function compose(f, g) { + return function(x) { + return f(g(x)); + }; +} + +// Compose multiple functions +function composeMany(...functions) { + return function(x) { + return functions.reduceRight((acc, fn) => fn(acc), x); + }; +} + +const toUpper = str => str.toUpperCase(); +const trim = str => str.trim(); +const exclaim = str => `${str}!`; + +const process = composeMany(exclaim, toUpper, trim); +console.log(process(" hello ")); // "HELLO!" + +// Pipe (left-to-right composition) +function pipe(...functions) { + return function(x) { + return functions.reduce((acc, fn) => fn(acc), x); + }; +} + +const process2 = pipe(trim, toUpper, exclaim); +console.log(process2(" hello ")); // "HELLO!" + +// Practical: Data transformation pipeline +const users = [ + { name: "john doe", age: 25 }, + { name: "jane smith", age: 30 } +]; + +const pipeline = pipe( + arr => arr.map(u => u.name), + arr => arr.map(n => n.split(" ")), + arr => arr.flatMap(parts => parts), + arr => arr.map(n => n.charAt(0).toUpperCase() + n.slice(1)), + arr => arr.join(" ") +); + +console.log(pipeline(users)); // "John Doe Jane Smith" + +// Point-free style (partially applied) +const prop = key => obj => obj[key]; +const map = fn => arr => arr.map(fn); +const filter = predicate => arr => arr.filter(predicate); + +const getNames = map(prop("name")); +const getAdults = filter(user => user.age >= 18); + +const adultNames = pipe(getAdults, getNames); +console.log(adultNames(users)); // ["john doe", "jane smith"] +``` + + +## Lazy Evaluation + + +```java !! java +// Java - Streams are lazy +List numbers = Arrays.asList(1, 2, 3, 4, 5); + +int result = numbers.stream() + .filter(n -> { + System.out.println("Filtering: " + n); + return n % 2 == 0; + }) + .map(n -> { + System.out.println("Mapping: " + n); + return n * 2; + }) + .findFirst() + .orElse(0); +// Only processes until first match found +``` + +```javascript !! js +// JavaScript - Generators for lazy evaluation + +function* filter(iterator, predicate) { + for (const item of iterator) { + if (predicate(item)) { + yield item; + } + } +} + +function* map(iterator, fn) { + for (const item of iterator) { + yield fn(item); + } +} + +function* take(iterator, n) { + let count = 0; + for (const item of iterator) { + if (count >= n) break; + yield item; + count++; + } +} + +// Infinite sequence +function* naturals() { + let n = 1; + while (true) { + yield n++; + } +} + +// Lazy pipeline +const result = take( + map( + filter(naturals(), n => n % 2 === 0), + n => n * 2 + ), + 5 +); + +console.log([...result]); // [4, 8, 12, 16, 20] + +// Practical: Lazy file processing +function* processFile(lines) { + for (const line of lines) { + if (line.trim() === "") continue; // Skip empty + if (line.startsWith("#")) continue; // Skip comments + yield JSON.parse(line); // Parse JSON + } +} + +// Only processes what's consumed +const firstValid = processFile(fileLines).next().value; +``` + + +## Common Patterns + +### Pattern 1: Middleware + + +```javascript !! js +// Express-style middleware +function composeMiddleware(...middlewares) { + return function(context, next) { + let index = -1; + + function dispatch(i) { + if (i <= index) { + throw new Error("next() called multiple times"); + } + index = i; + + const fn = middlewares[i]; + + if (!fn) { + return next(); + } + + return fn(context, () => dispatch(i + 1)); + } + + return dispatch(0); + }; +} + +const logger = (ctx, next) => { + console.log("Request:", ctx.url); + return next().then(() => { + console.log("Response:", ctx.status); + }); +}; + +const auth = (ctx, next) => { + if (!ctx.headers.authorization) { + ctx.status = 401; + return; // Stop middleware chain + } + return next(); +}; + +const handler = (ctx, next) => { + ctx.status = 200; + ctx.body = "OK"; +}; + +const middleware = composeMiddleware(logger, auth, handler); +middleware({ url: "/api", headers: {} }, () => {}); +``` + + +### Pattern 2: Function Decorators + + +```javascript !! js +// Decorator: Add behavior to function +function withTiming(fn) { + return function(...args) { + const start = Date.now(); + const result = fn.apply(this, args); + const end = Date.now(); + console.log(`${fn.name} took ${end - start}ms`); + return result; + }; +} + +function withLogging(fn) { + return function(...args) { + console.log(`Calling ${fn.name} with:`, args); + const result = fn.apply(this, args); + console.log(`${fn.name} returned:`, result); + return result; + }; +} + +// Stack decorators +function fetchData(userId) { + // Expensive operation + return { id: userId, name: "John" }; +} + +const decoratedFetch = withTiming(withLogging(fetchData)); +decoratedFetch(123); + +// Method decorator (for classes) +function readonly(target, key, descriptor) { + descriptor.writable = false; + return descriptor; +} + +class User { + constructor(name) { + this.name = name; + } + + @readonly + getId() { + return 123; + } +} +``` + + +## Best Practices + + +```java !! java +// Java: Clear structure, minimal side effects +public class DataProcessor { + private final DataValidator validator; + private final DataTransformer transformer; + + public DataProcessor(DataValidator validator, DataTransformer transformer) { + this.validator = validator; + this.transformer = transformer; + } + + public Result processData(Input input) { + if (!validator.isValid(input)) { + return Result.error("Invalid input"); + } + + Data transformed = transformer.transform(input); + return Result.success(transformed); + } +} +``` + +```javascript !! js +// JavaScript: Same principles + +// 1. Prefer pure functions (no side effects) +function add(a, b) { + return a + b; // Pure +} + +function addToGlobal(x) { + globalResult += x; // Impure - side effect +} + +// 2. Keep closures minimal +function createHandler() { + const config = { timeout: 5000 }; + + return async function(request) { + // Only captures what's needed + return processRequest(request, config); + }; +} + +// 3. Memoize expensive pure functions +const expensiveOperation = memoize((input) => { + // Complex calculation +}); + +// 4. Use currying for specialization +const multiply = (a, b) => a * b; +const double = multiply.bind(null, 2); +// Or: const double = a => multiply(2, a); + +// 5. Compose small functions +const notEmpty = str => str.length > 0; +const notBlank = str => str.trim().length > 0; +const isValidString = str => notEmpty(str) && notBlank(str); + +// 6. Handle recursion depth +function recursiveSearch(data, depth = 0) { + if (depth > 1000) { + throw new Error("Maximum depth exceeded"); + } + + // Recursive logic +} +``` + + +## Exercises + +### Exercise 1: Create a Closure +Create a function that generates unique IDs: +```javascript +function createIdGenerator() { + // Return a function that returns incrementing IDs +} + +const genId = createIdGenerator(); +console.log(genId()); // 1 +console.log(genId()); // 2 +console.log(genId()); // 3 +``` + +### Exercise 2: Implement Memoization +Add memoization to this function: +```javascript +function slowFunction(x) { + console.log("Computing..."); + return x * x; +} + +// Add memoization +``` + +### Exercise 3: Curry a Function +Convert this to a curried function: +```javascript +function multiply(a, b, c) { + return a * b * c; +} + +// Create curried version +``` + +### Exercise 4: Compose Functions +Create a pipeline: +```javascript +const addOne = x => x + 1; +const double = x => x * 2; +const toString = x => x.toString(); + +// Create compose or pipe function +const pipeline = pipe(addOne, double, toString); +console.log(pipeline(5)); // "12" +``` + +## Summary + +### Key Takeaways + +1. **Closures:** + - Inner functions access outer scope + - Enable private data and encapsulation + - Can cause memory leaks if not careful + +2. **Higher-Order Functions:** + - Take functions as arguments + - Return functions + - Enable abstraction and composition + +3. **Currying:** + - Transform multi-arg functions to single-arg + - Enable partial application + - Improve reusability + +4. **Memoization:** + - Cache function results + - Improve performance + - Works best for pure functions + +5. **Recursion:** + - Functions call themselves + - Useful for tree/data structures + - Watch for stack overflow + +6. **Composition:** + - Combine simple functions + - Build complex behavior + - Enable declarative code + +### Comparison Table: Java vs JavaScript + +| Feature | Java | JavaScript | +|---------|------|------------| +| **Closures** | Limited (lambdas capture effectively final) | Full support | +| **Higher-order functions** | Yes (functional interfaces) | Yes (native) | +| **Currying** | Manual (verbose) | Native support | +| **Memoization** | Manual caching | Easy with closures | +| **Tail call optimization** | Yes | No (mostly) | +| **Function composition** | Yes (andThen/compose) | Yes (custom) | + +## What's Next? + +You've mastered advanced functions! Next up is **Module 5: Arrays and Collections**, where we'll explore: + +- Array creation and manipulation +- Array methods (map, filter, reduce, etc.) +- Set and Map data structures +- WeakSet and WeakMap +- Array destructuring +- Spread operator with arrays + +Ready to dive into JavaScript's collection types? Let's continue! diff --git a/content/docs/java2js/module-04-functions-advanced.zh-cn.mdx b/content/docs/java2js/module-04-functions-advanced.zh-cn.mdx new file mode 100644 index 0000000..32f9896 --- /dev/null +++ b/content/docs/java2js/module-04-functions-advanced.zh-cn.mdx @@ -0,0 +1,1157 @@ +--- +title: "模块 4: 函数高级" +description: "掌握闭包、高阶函数、柯里化和高级函数模式" +--- + +## 模块 4: 函数高级 + +既然你已经理解了基础知识,让我们探索 JavaScript 的高级函数功能。这些概念非常强大,能够为复杂问题提供优雅的解决方案。 + +## 学习目标 + +完成本模块后,你将: +✅ 理解闭包和词法作用域 +✅ 掌握高阶函数 +✅ 学习柯里化和部分应用 +✅ 理解记忆化以提高性能 +✅ 了解递归模式和尾调用优化 +✅ 学习函数组合和链式调用 + +## 闭包 + +闭包是 JavaScript 最强大的功能之一。闭包允许你从内部函数访问外部函数的作用域。 + + +```java !! java +// Java - Limited closure support (lambda with captured variables) +public class ClosureExample { + public static Supplier makeCounter() { + int[] count = {0}; // Must use array for mutability + return () -> { + count[0]++; + return count[0]; + }; + } + + public static void main(String[] args) { + Supplier counter1 = makeCounter(); + Supplier counter2 = makeCounter(); + + System.out.println(counter1.get()); // 1 + System.out.println(counter1.get()); // 2 + System.out.println(counter2.get()); // 1 (separate counter) + } +} +``` + +```javascript !! js +// JavaScript - True closures +function makeCounter() { + let count = 0; // Private variable + + // Inner function has access to outer scope + return function() { + count++; + return count; + }; +} + +const counter1 = makeCounter(); +const counter2 = makeCounter(); + +console.log(counter1()); // 1 +console.log(counter1()); // 2 +console.log(counter2()); // 1 (separate closure) + +// Practical example: Private data +function createUser(name) { + let age = 0; // Private + + return { + getName: () => name, + getAge: () => age, + setAge: (newAge) => { + if (newAge >= 0) { + age = newAge; + } + }, + incrementAge: () => { + age++; + } + }; +} + +const user = createUser("John"); +console.log(user.getName()); // "John" +console.log(user.getAge()); // 0 +user.setAge(25); +console.log(user.getAge()); // 25 +user.incrementAge(); +console.log(user.getAge()); // 26 +console.log(user.age); // undefined (private!) +``` + + +### 闭包用例 + + +```java !! java +// Java - Need classes for state +public class Cache { + private Map storage = new HashMap<>(); + + public String get(String key) { + return storage.get(key); + } + + public void set(String key, value) { + storage.put(key, value); + } +} +``` + +```javascript !! js +// JavaScript - Closures for encapsulation + +// Pattern 1: Function factory +function createCache() { + const cache = new Map(); + + return { + get(key) { + return cache.get(key); + }, + set(key, value) { + cache.set(key, value); + }, + has(key) { + return cache.has(key); + }, + clear() { + cache.clear(); + } + }; +} + +const userCache = createCache(); +userCache.set("user1", { name: "John" }); +console.log(userCache.get("user1")); // { name: "John" } + +// Pattern 2: Configuration +function createLogger(prefix) { + return function(message) { + const timestamp = new Date().toISOString(); + console.log(`[${timestamp}] [${prefix}] ${message}`); + }; +} + +const errorLogger = createLogger("ERROR"); +const infoLogger = createLogger("INFO"); + +errorLogger("Database connection failed"); +infoLogger("User logged in"); + +// Pattern 3: Rate limiter +function rateLimit(func, delay) { + let lastCall = 0; + let timer = null; + + return function(...args) { + const now = Date.now(); + const timeSinceLastCall = now - lastCall; + + if (timeSinceLastCall >= delay) { + lastCall = now; + return func.apply(this, args); + } else { + // Clear pending call + if (timer) clearTimeout(timer); + + // Schedule new call + timer = setTimeout(() => { + lastCall = Date.now(); + func.apply(this, args); + }, delay - timeSinceLastCall); + } + }; +} + +const saveData = rateLimit((data) => { + console.log("Saving:", data); +}, 1000); + +saveData({ id: 1 }); // Executes immediately +saveData({ id: 2 }); // Debounced +``` + + +### 常见闭包陷阱 + + +```javascript !! js +// Pitfall 1: Loop closures +// ❌ BAD: All functions capture same variable +const funcs = []; +for (var i = 0; i < 3; i++) { + funcs.push(() => console.log(i)); +} +funcs[0](); // 3 (not 0!) +funcs[1](); // 3 (not 1!) +funcs[2](); // 3 (not 2!) + +// ✅ GOOD: Use let (block-scoped) +const funcs2 = []; +for (let i = 0; i < 3; i++) { + funcs2.push(() => console.log(i)); +} +funcs2[0](); // 0 +funcs2[1](); // 1 +funcs2[2](); // 2 + +// ✅ GOOD: Use IIFE (for var) +const funcs3 = []; +for (var i = 0; i < 3; i++) { + (function(j) { + funcs3.push(() => console.log(j)); + })(i); +} +funcs3[0](); // 0 + +// Pitfall 2: Memory leaks +function setupHandlers() { + const hugeData = new Array(1000000).fill("data"); + + document.getElementById("button").addEventListener("click", function() { + console.log("Clicked"); // Closes over hugeData! + }); +} + +// ✅ GOOD: Minimize closure scope +function setupHandlers() { + const button = document.getElementById("button"); + const handler = () => console.log("Clicked"); + + button.addEventListener("click", handler); + // hugeData not captured +} +``` + + +## 高阶函数 + +接受或返回其他函数的函数: + + +```java !! java +// Java - Functional interfaces +public interface Predicate { + boolean test(T t); +} + +public class Utils { + public static List filter(List list, Predicate predicate) { + List result = new ArrayList<>(); + for (T item : list) { + if (predicate.test(item)) { + result.add(item); + } + } + return result; + } + + // Higher-order function that returns a function + public static Predicate greaterThan(int n) { + return x -> x > n; + } +} +``` + +```javascript !! js +// JavaScript - Native higher-order functions + +// Function that takes a function +function filter(array, predicate) { + const result = []; + for (const item of array) { + if (predicate(item)) { + result.push(item); + } + } + return result; +} + +const numbers = [1, 2, 3, 4, 5]; +const evens = filter(numbers, n => n % 2 === 0); +console.log(evens); // [2, 4] + +// Function that returns a function +function greaterThan(n) { + return function(x) { + return x > n; + }; +} + +const greaterThan5 = greaterThan(5); +console.log(greaterThan5(3)); // false +console.log(greaterThan5(10)); // true + +// Practical example: Notifier +function createNotifier(sender) { + return function(recipient, message) { + console.log(`From: ${sender}`); + console.log(`To: ${recipient}`); + console.log(`Message: ${message}`); + }; +} + +const adminNotifier = createNotifier("admin@app.com"); +adminNotifier("user@app.com", "Welcome!"); + +// Composition +function compose(f, g) { + return function(x) { + return f(g(x)); + }; +} + +const toUpper = str => str.toUpperCase(); +const exclaim = str => str + "!"; + +const shout = compose(exclaim, toUpper); +console.log(shout("hello")); // "HELLO!" +``` + + +### 数组方法作为高阶函数 + + +```java !! java +// Java - Stream operations +List numbers = Arrays.asList(1, 2, 3, 4, 5); + +List result = numbers.stream() + .filter(n -> n % 2 == 0) + .map(n -> n * 2) + .collect(Collectors.toList()); +``` + +```javascript !! js +// JavaScript - Array methods (all are higher-order functions) +const numbers = [1, 2, 3, 4, 5]; + +// filter: Takes predicate function +const evens = numbers.filter(n => n % 2 === 0); + +// map: Takes transform function +const doubled = numbers.map(n => n * 2); + +// reduce: Takes reducer function +const sum = numbers.reduce((acc, n) => acc + n, 0); + +// find: Takes predicate function +const found = numbers.find(n => n > 3); + +// every: Takes predicate function +const allPositive = numbers.every(n => n > 0); + +// some: Takes predicate function +const hasEven = numbers.some(n => n % 2 === 0); + +// Chaining higher-order functions +const result = numbers + .filter(n => n % 2 === 0) // Keep evens + .map(n => n * 2) // Double them + .reduce((acc, n) => acc + n, 0); // Sum them + +console.log(result); // 12 (2*2 + 4*2 = 4 + 8) +``` + + +## 柯里化和部分应用 + +柯里化将具有多个参数的函数转换为接受单个参数的函数序列: + + +```java !! java +// Java - No built-in currying +// Would need custom implementation +public interface TriFunction { + R apply(A a, B b, C c); +} + +public static Function>> curry( + TriFunction f +) { + return a -> b -> c -> f.apply(a, b, c); +} + +// Usage is verbose +``` + +```javascript !! js +// JavaScript - Currying + +// Non-curried function +function add(a, b, c) { + return a + b + c; +} + +console.log(add(1, 2, 3)); // 6 + +// Curried function +function curryAdd(a) { + return function(b) { + return function(c) { + return a + b + c; + }; + }; +} + +console.log(curryAdd(1)(2)(3)); // 6 + +// Arrow function curry (more concise) +const curryAdd2 = a => b => c => a + b + c; + +// Partial application +const add1 = curryAdd2(1); +const add1And2 = add1(2); + +console.log(add1And2(3)); // 6 + +// Practical example: API client +function createApiFetcher(baseUrl) { + return function(endpoint) { + return function(options) { + return fetch(`${baseUrl}${endpoint}`, options) + .then(res => res.json()); + }; + }; +} + +const jsonApi = createApiFetcher("https://api.example.com"); +const userApi = jsonApi("/users"); +const getAdmins = userApi({ ?role=admin }); + +// Currying utility +function curry(fn) { + return function curried(...args) { + if (args.length >= fn.length) { + return fn.apply(this, args); + } + return function(...more) { + return curried.apply(this, [...args, ...more]); + }; + }; +} + +const curriedMap = curry((fn, array) => array.map(fn)); +const doubleAll = curriedMap(x => x * 2); + +console.log(doubleAll([1, 2, 3])); // [2, 4, 6] +``` + + +### 部分应用 + + +```java !! java +// Java - Method references (limited partial application) +public class Printer { + public void print(String prefix, String message) { + System.out.println(prefix + ": " + message); + } + + public void printInfo(String message) { + print("INFO", message); // Partial application + } +} +``` + +```javascript !! js +// JavaScript - Partial application + +// Using bind() +function greet(greeting, name) { + console.log(`${greeting}, ${name}`); +} + +const sayHello = greet.bind(null, "Hello"); +sayHello("John"); // "Hello, John" + +// Using closures (more flexible) +function partial(fn, ...presetArgs) { + return function(...laterArgs) { + return fn(...presetArgs, ...laterArgs); + }; +} + +const greetHello = partial(greet, "Hello"); +greetHello("Jane"); // "Hello, Jane" + +// Practical: Event handlers +function handleClick(element, callback) { + element.addEventListener("click", function(event) { + callback(element, event); + }); +} + +handleClick(button, (elem, e) => { + console.log("Clicked:", elem); + console.log("Event:", e); +}); + +// Partial application for configuration +function fetch(url, options, timeout) { + // Implementation +} + +const fetchJson = partial(fetch, null, { headers: { "Accept": "application/json" } }); +fetchJson("https://api.example.com", 5000); +``` + + +## 记忆化 + +记忆化缓存函数结果以避免冗余计算: + + +```java !! java +// Java - Need custom caching +public class Memoizer { + private Map cache = new HashMap<>(); + + public int fibonacci(int n) { + if (n <= 1) return n; + + if (cache.containsKey(n)) { + return cache.get(n); + } + + int result = fibonacci(n - 1) + fibonacci(n - 2); + cache.put(n, result); + return result; + } +} +``` + +```javascript !! js +// JavaScript - Memoization utility + +function memoize(fn) { + const cache = new Map(); + + return function(...args) { + const key = JSON.stringify(args); + + if (cache.has(key)) { + console.log("From cache:", key); + return cache.get(key); + } + + const result = fn.apply(this, args); + cache.set(key, result); + return result; + }; +} + +// Expensive function +function fibonacci(n) { + if (n <= 1) return n; + return fibonacci(n - 1) + fibonacci(n - 2); +} + +const memoFib = memoize(fibonacci); + +console.log(memoFib(40)); // Slow (first time) +console.log(memoFib(40)); // Fast (from cache) + +// Practical example: API calls +function fetchUserData(userId) { + return fetch(`/api/users/${userId}`) + .then(res => res.json()); +} + +const memoFetch = memoize(fetchUserData); + +// Subsequent calls for same user are cached +memoFetch(1).then(console.log); +memoFetch(1).then(console.log); // From cache + +// Memoization with TTL +function memoizeTTL(fn, ttl = 1000) { + const cache = new Map(); + + return function(...args) { + const key = JSON.stringify(args); + const cached = cache.get(key); + + if (cached && Date.now() - cached.timestamp < ttl) { + return cached.value; + } + + const result = fn.apply(this, args); + cache.set(key, { + value: result, + timestamp: Date.now() + }); + + return result; + }; +} + +const memoFetchTTL = memoizeTTL(fetchUserData, 5000); // 5 second cache +``` + + +## 递归 + +JavaScript 支持递归,尽管在大多数实现中缺乏尾调用优化: + + +```java !! java +// Java - Recursion with tail call optimization +public class Recursion { + public int factorial(int n) { + if (n <= 1) return 1; + return n * factorial(n - 1); + } + + // Tail-recursive version + public int factorialTail(int n) { + return factorialTailHelper(n, 1); + } + + private int factorialTailHelper(int n, int accumulator) { + if (n <= 1) return accumulator; + return factorialTailHelper(n - 1, n * accumulator); + } +} +``` + +```javascript !! js +// JavaScript - Recursion + +// Factorial (not tail-optimized) +function factorial(n) { + if (n <= 1) return 1; + return n * factorial(n - 1); +} + +console.log(factorial(5)); // 120 + +// Tail-recursive version (not optimized in most JS engines) +function factorialTail(n, accumulator = 1) { + if (n <= 1) return accumulator; + return factorialTail(n - 1, n * accumulator); +} + +console.log(factorialTail(5)); // 120 + +// Practical: Tree traversal +const fileSystem = { + name: "root", + type: "folder", + children: [ + { + name: "docs", + type: "folder", + children: [ + { name: "report.txt", type: "file" }, + { name: "notes.txt", type: "file" } + ] + }, + { + name: "photos", + type: "folder", + children: [ + { name: "vacation.jpg", type: "file" } + ] + } + ] +}; + +function findFiles(node, type = "file") { + if (node.type === type) { + return [node.name]; + } + + if (!node.children) { + return []; + } + + return node.children.flatMap(child => findFiles(child, type)); +} + +console.log(findFiles(fileSystem)); +// ["report.txt", "notes.txt", "vacation.jpg"] + +// Recursion with trampoline (for deep recursion) +function trampoline(fn) { + return function(...args) { + let result = fn(...args); + + while (typeof result === "function") { + result = result(); + } + + return result; + }; +} + +function factorialTramp(n, acc = 1) { + if (n <= 1) return acc; + return () => factorialTramp(n - 1, n * acc); +} + +const safeFactorial = trampoline(factorialTramp); +``` + + +## 函数组合 + +组合函数以创建新函数: + + +```java !! java +// Java - Composing functions manually +public class Composer { + public static Function compose( + Function f, + Function g + ) { + return x -> f.apply(g.apply(x)); + } + + // or using default method in Function interface + // f.compose(g) or f.andThen(g) +} +``` + +```javascript !! js +// JavaScript - Function composition + +// Basic composition +function compose(f, g) { + return function(x) { + return f(g(x)); + }; +} + +// Compose multiple functions +function composeMany(...functions) { + return function(x) { + return functions.reduceRight((acc, fn) => fn(acc), x); + }; +} + +const toUpper = str => str.toUpperCase(); +const trim = str => str.trim(); +const exclaim = str => `${str}!`; + +const process = composeMany(exclaim, toUpper, trim); +console.log(process(" hello ")); // "HELLO!" + +// Pipe (left-to-right composition) +function pipe(...functions) { + return function(x) { + return functions.reduce((acc, fn) => fn(acc), x); + }; +} + +const process2 = pipe(trim, toUpper, exclaim); +console.log(process2(" hello ")); // "HELLO!" + +// Practical: Data transformation pipeline +const users = [ + { name: "john doe", age: 25 }, + { name: "jane smith", age: 30 } +]; + +const pipeline = pipe( + arr => arr.map(u => u.name), + arr => arr.map(n => n.split(" ")), + arr => arr.flatMap(parts => parts), + arr => arr.map(n => n.charAt(0).toUpperCase() + n.slice(1)), + arr => arr.join(" ") +); + +console.log(pipeline(users)); // "John Doe Jane Smith" + +// Point-free style (partially applied) +const prop = key => obj => obj[key]; +const map = fn => arr => arr.map(fn); +const filter = predicate => arr => arr.filter(predicate); + +const getNames = map(prop("name")); +const getAdults = filter(user => user.age >= 18); + +const adultNames = pipe(getAdults, getNames); +console.log(adultNames(users)); // ["john doe", "jane smith"] +``` + + +## 惰性求值 + + +```java !! java +// Java - Streams are lazy +List numbers = Arrays.asList(1, 2, 3, 4, 5); + +int result = numbers.stream() + .filter(n -> { + System.out.println("Filtering: " + n); + return n % 2 == 0; + }) + .map(n -> { + System.out.println("Mapping: " + n); + return n * 2; + }) + .findFirst() + .orElse(0); +// Only processes until first match found +``` + +```javascript !! js +// JavaScript - Generators for lazy evaluation + +function* filter(iterator, predicate) { + for (const item of iterator) { + if (predicate(item)) { + yield item; + } + } +} + +function* map(iterator, fn) { + for (const item of iterator) { + yield fn(item); + } +} + +function* take(iterator, n) { + let count = 0; + for (const item of iterator) { + if (count >= n) break; + yield item; + count++; + } +} + +// Infinite sequence +function* naturals() { + let n = 1; + while (true) { + yield n++; + } +} + +// Lazy pipeline +const result = take( + map( + filter(naturals(), n => n % 2 === 0), + n => n * 2 + ), + 5 +); + +console.log([...result]); // [4, 8, 12, 16, 20] + +// Practical: Lazy file processing +function* processFile(lines) { + for (const line of lines) { + if (line.trim() === "") continue; // Skip empty + if (line.startsWith("#")) continue; // Skip comments + yield JSON.parse(line); // Parse JSON + } +} + +// Only processes what's consumed +const firstValid = processFile(fileLines).next().value; +``` + + +## 常见模式 + +### 模式 1: 中间件 + + +```javascript !! js +// Express-style middleware +function composeMiddleware(...middlewares) { + return function(context, next) { + let index = -1; + + function dispatch(i) { + if (i <= index) { + throw new Error("next() called multiple times"); + } + index = i; + + const fn = middlewares[i]; + + if (!fn) { + return next(); + } + + return fn(context, () => dispatch(i + 1)); + } + + return dispatch(0); + }; +} + +const logger = (ctx, next) => { + console.log("Request:", ctx.url); + return next().then(() => { + console.log("Response:", ctx.status); + }); +}; + +const auth = (ctx, next) => { + if (!ctx.headers.authorization) { + ctx.status = 401; + return; // Stop middleware chain + } + return next(); +}; + +const handler = (ctx, next) => { + ctx.status = 200; + ctx.body = "OK"; +}; + +const middleware = composeMiddleware(logger, auth, handler); +middleware({ url: "/api", headers: {} }, () => {}); +``` + + +### 模式 2: 函数装饰器 + + +```javascript !! js +// Decorator: Add behavior to function +function withTiming(fn) { + return function(...args) { + const start = Date.now(); + const result = fn.apply(this, args); + const end = Date.now(); + console.log(`${fn.name} took ${end - start}ms`); + return result; + }; +} + +function withLogging(fn) { + return function(...args) { + console.log(`Calling ${fn.name} with:`, args); + const result = fn.apply(this, args); + console.log(`${fn.name} returned:`, result); + return result; + }; +} + +// Stack decorators +function fetchData(userId) { + // Expensive operation + return { id: userId, name: "John" }; +} + +const decoratedFetch = withTiming(withLogging(fetchData)); +decoratedFetch(123); + +// Method decorator (for classes) +function readonly(target, key, descriptor) { + descriptor.writable = false; + return descriptor; +} + +class User { + constructor(name) { + this.name = name; + } + + @readonly + getId() { + return 123; + } +} +``` + + +## 最佳实践 + + +```java !! java +// Java: Clear structure, minimal side effects +public class DataProcessor { + private final DataValidator validator; + private final DataTransformer transformer; + + public DataProcessor(DataValidator validator, DataTransformer transformer) { + this.validator = validator; + this.transformer = transformer; + } + + public Result processData(Input input) { + if (!validator.isValid(input)) { + return Result.error("Invalid input"); + } + + Data transformed = transformer.transform(input); + return Result.success(transformed); + } +} +``` + +```javascript !! js +// JavaScript: Same principles + +// 1. Prefer pure functions (no side effects) +function add(a, b) { + return a + b; // Pure +} + +function addToGlobal(x) { + globalResult += x; // Impure - side effect +} + +// 2. Keep closures minimal +function createHandler() { + const config = { timeout: 5000 }; + + return async function(request) { + // Only captures what's needed + return processRequest(request, config); + }; +} + +// 3. Memoize expensive pure functions +const expensiveOperation = memoize((input) => { + // Complex calculation +}); + +// 4. Use currying for specialization +const multiply = (a, b) => a * b; +const double = multiply.bind(null, 2); +// Or: const double = a => multiply(2, a); + +// 5. Compose small functions +const notEmpty = str => str.length > 0; +const notBlank = str => str.trim().length > 0; +const isValidString = str => notEmpty(str) && notBlank(str); + +// 6. Handle recursion depth +function recursiveSearch(data, depth = 0) { + if (depth > 1000) { + throw new Error("Maximum depth exceeded"); + } + + // Recursive logic +} +``` + + +## 练习 + +### 练习 1: 创建闭包 +创建一个生成唯一 ID 的函数: +```javascript +function createIdGenerator() { + // Return a function that returns incrementing IDs +} + +const genId = createIdGenerator(); +console.log(genId()); // 1 +console.log(genId()); // 2 +console.log(genId()); // 3 +``` + +### 练习 2: 实现记忆化 +为这个函数添加记忆化: +```javascript +function slowFunction(x) { + console.log("Computing..."); + return x * x; +} + +// Add memoization +``` + +### 练习 3: 柯里化函数 +将其转换为柯里化函数: +```javascript +function multiply(a, b, c) { + return a * b * c; +} + +// Create curried version +``` + +### 练习 4: 组合函数 +创建管道: +```javascript +const addOne = x => x + 1; +const double = x => x * 2; +const toString = x => x.toString(); + +// Create compose or pipe function +const pipeline = pipe(addOne, double, toString); +console.log(pipeline(5)); // "12" +``` + +## 总结 + +### 关键要点 + +1. **闭包:** + - 内部函数访问外部作用域 + - 支持私有数据和封装 + - 如果不小心可能导致内存泄漏 + +2. **高阶函数:** + - 接受函数作为参数 + - 返回函数 + - 支持抽象和组合 + +3. **柯里化:** + - 将多参数函数转换为单参数 + - 支持部分应用 + - 提高可重用性 + +4. **记忆化:** + - 缓存函数结果 + - 提高性能 + - 最适合纯函数 + +5. **递归:** + - 函数调用自身 + - 适用于树/数据结构 + - 注意堆栈溢出 + +6. **组合:** + - 组合简单函数 + - 构建复杂行为 + - 支持声明式代码 + +### 比较表: Java vs JavaScript + +| 特性 | Java | JavaScript | +|------|------|-----------| +| **闭包** | 有限(lambda 捕获实际上是 final) | 完全支持 | +| **高阶函数** | 是(函数式接口) | 是(原生) | +| **柯里化** | 手动(冗长) | 原生支持 | +| **记忆化** | 手动缓存 | 使用闭包很容易 | +| **尾调用优化** | 是 | 否(大多) | +| **函数组合** | 是(andThen/compose) | 是(自定义) | + +## 下一步是什么? + +你已经掌握了高级函数!接下来是**模块 5: 数组和集合**,我们将探索: + +- 数组创建和操作 +- 数组方法(map、filter、reduce 等) +- Set 和 Map 数据结构 +- WeakSet 和 WeakMap +- 数组解构 +- 数组的 spread 运算符 + +准备好深入了解 JavaScript 的集合类型了吗?让我们继续! diff --git a/content/docs/java2js/module-04-functions-advanced.zh-tw.mdx b/content/docs/java2js/module-04-functions-advanced.zh-tw.mdx new file mode 100644 index 0000000..d291ac9 --- /dev/null +++ b/content/docs/java2js/module-04-functions-advanced.zh-tw.mdx @@ -0,0 +1,976 @@ +--- +title: "Module 4: Functions Advanced" +description: "Master closures, higher-order functions, currying, and advanced function patterns" +--- + +## Module 4: Functions Advanced + +在掌握了函數基礎後,讓我們深入探討使 JavaScript 函數真正強大的進階概念。Closures(閉包)、higher-order functions(高階函數)、currying(柯里化)等模式將改變你思考程式設計的方式。 + +## Learning Objectives + +完成本模組後,你將: +✅ 深入理解 closures 及其用途 +✅ 掌握 higher-order functions +✅ 學習 currying 和 partial application +✅ 理解 memoization 技術 +✅ 掌握遞迴模式 +✅ 了解 generator functions + +## Closures + +Closures 是 JavaScript 中最重要且最強大的概念之一: + + +```java !! java +// Java - Anonymous classes/closures with "effectively final" +public class Greeter { + public Function createGreeter(String greeting) { + // greeting must be effectively final + return name -> greeting + ", " + name + "!"; + } + + public static void main(String[] args) { + Greeter greeter = new Greeter(); + Function sayHi = greeter.createGreeter("Hi"); + System.out.println(sayHi.apply("Alice")); // "Hi, Alice!" + } +} + +// Java's closure is limited compared to JavaScript +``` + +```javascript !! js +// JavaScript - True closures +function createGreeter(greeting) { + // greeting is "closed over" - accessible even after createGreeter returns + return function(name) { + console.log(`${greeting}, ${name}!`); + }; +} + +const sayHi = createGreeter("Hi"); +const sayHello = createGreeter("Hello"); + +sayHi("Alice"); // "Hi, Alice!" +sayHello("Bob"); // "Hello, Bob!" + +// Each closure has its own scope +console.log(sayHi.greeting); // undefined(private) + +// Closure with mutable state +function createCounter() { + let count = 0; // Private variable + + return { + increment() { + count++; + return count; + }, + decrement() { + count--; + return count; + }, + getCount() { + return count; + } + }; +} + +const counter1 = createCounter(); +const counter2 = createCounter(); + +console.log(counter1.increment()); // 1 +console.log(counter1.increment()); // 2 +console.log(counter2.increment()); // 1(separate closure) +console.log(counter1.getCount()); // 2 +console.log(counter2.getCount()); // 1 +``` + + +### Closure Patterns + + +```java !! java +// Java - Need classes for private state +public class User { + private String name; + private int age; + + public User(String name, int age) { + this.name = name; + this.age = age; + } + + public String getName() { return name; } + public void setName(String name) { this.name = name; } + public int getAge() { return age; } +} +``` + +```javascript !! js +// JavaScript - Private state with closures +function createUser(name, age) { + // Private variables + let _name = name; + let _age = age; + + return { + getName() { + return _name; + }, + setName(newName) { + _name = newName; + }, + getAge() { + return _age; + }, + setAge(newAge) { + if (newAge > 0) { + _age = newAge; + } + }, + // Computed property + getDetails() { + return `${_name} is ${_age} years old`; + } + }; +} + +const user = createUser("Alice", 25); +console.log(user.getName()); // "Alice" +console.log(user._name); // undefined(truly private) +user.setName("Bob"); +console.log(user.getName()); // "Bob" + +// Closure for configuration +function createMultiplier(multiplier) { + return function(number) { + return number * multiplier; + }; +} + +const double = createMultiplier(2); +const triple = createMultiplier(3); +const quadruple = createMultiplier(4); + +console.log(double(5)); // 10 +console.log(triple(5)); // 15 +console.log(quadruple(5)); // 20 +``` + + +### Common Closure Pitfalls + + +```javascript !! js +// Pitfall 1: Loop closures +// Problem +const handlers = []; +for (var i = 0; i < 3; i++) { + handlers.push(function() { + console.log(i); + }); +} + +handlers[0](); // 3(not 0!) +handlers[1](); // 3(not 1!) +handlers[2](); // 3(not 2!) + +// Solution 1: Use let(block scope) +const handlers2 = []; +for (let i = 0; i < 3; i++) { + handlers2.push(function() { + console.log(i); + }); +} +handlers2[0](); // 0 +handlers2[1](); // 1 +handlers2[2](); // 2 + +// Solution 2: IIFE(for var) +const handlers3 = []; +for (var i = 0; i < 3; i++) { + (function(j) { + handlers3.push(function() { + console.log(j); + }); + })(i); +} + +handlers3[0](); // 0 +handlers3[1](); // 1 +handlers3[2](); // 2 + +// Pitfall 2: Memory leaks +function createElement() { + const element = { data: "large data" }; + + return function() { + console.log("Element created"); + // If element is referenced here, it won't be garbage collected + console.log(element.data); + }; +} + +// Better: Don't capture unnecessary data +function createElementBetter() { + const element = { data: "large data" }; + + // Extract only what's needed + const data = element.data; + + return function() { + console.log("Element created"); + console.log(data); // Only capture the string, not whole object + }; +} +``` + + +## Higher-Order Functions + +接受或返回函數的函數: + + +```java !! java +// Java - Functional interfaces +@FunctionalInterface +interface Predicate { + boolean test(T t); +} + +public class Filter { + public static List filter(List list, Predicate predicate) { + List result = new ArrayList<>(); + for (Integer item : list) { + if (predicate.test(item)) { + result.add(item); + } + } + return result; + } + + public static void main(String[] args) { + List numbers = Arrays.asList(1, 2, 3, 4, 5, 6); + List evens = filter(numbers, n -> n % 2 == 0); + System.out.println(evens); // [2, 4, 6] + } +} +``` + +```javascript !! js +// JavaScript - Native higher-order functions +const numbers = [1, 2, 3, 4, 5, 6]; + +// Built-in HOFs +const evens = numbers.filter(n => n % 2 === 0); +console.log(evens); // [2, 4, 6] + +// Custom HOF +function filter(array, predicate) { + const result = []; + for (const item of array) { + if (predicate(item)) { + result.push(item); + } + } + return result; +} + +const odds = filter(numbers, n => n % 2 !== 0); +console.log(odds); // [1, 3, 5] + +// HOF that returns a function +function createComparator(property) { + return function(a, b) { + if (a[property] < b[property]) return -1; + if (a[property] > b[property]) return 1; + return 0; + }; +} + +const users = [ + { name: "Alice", age: 25 }, + { name: "Bob", age: 20 }, + { name: "Charlie", age: 30 } +]; + +users.sort(createComparator("age")); +console.log(users); +// [ +// { name: "Bob", age: 20 }, +// { name: "Alice", age: 25 }, +// { name: "Charlie", age: 30 } +// ] + +// Practical: Memoization HOF +function memoize(fn) { + const cache = new Map(); + + return function(...args) { + const key = JSON.stringify(args); + + if (cache.has(key)) { + console.log("From cache:", key); + return cache.get(key); + } + + const result = fn.apply(this, args); + cache.set(key, result); + return result; + }; +} + +function slowFibonacci(n) { + if (n <= 1) return n; + return slowFibonacci(n - 1) + slowFibonacci(n - 2); +} + +const fibonacci = memoize(slowFibonacci); +console.log(fibonacci(40)); // 102334155(fast with cache) +console.log(fibonacci(40)); // From cache, instant +``` + + +## Currying + +將接受多個參數的函數轉換為一系列接受單一參數的函數: + + +```java !! java +// Java - No built-in currying +// Need to manually create curried versions +public class Calculator { + public int add(int a, int b) { + return a + b; + } + + public Function addCurried(int a) { + return b -> a + b; + } + + public static void main(String[] args) { + Calculator calc = new Calculator(); + Function addFive = calc.addCurried(5); + System.out.println(addFive.apply(3)); // 8 + } +} +``` + +```javascript !! js +// JavaScript - Currying +function add(a, b, c) { + return a + b + c; +} + +// Manual currying +function curriedAdd(a) { + return function(b) { + return function(c) { + return a + b + c; + }; + }; +} + +console.log(curriedAdd(1)(2)(3)); // 6 + +// Arrow function currying(more concise) +const curriedAdd2 = a => b => c => a + b + c; +console.log(curriedAdd2(1)(2)(3)); // 6 + +// Partial application +const addFive = curriedAdd2(5); +const addFiveAndThree = addFive(3); +console.log(addFiveAndThree(2)); // 10 + +// Generic curry function +function curry(fn) { + return function curried(...args) { + if (args.length >= fn.length) { + return fn.apply(this, args); + } + return function(...more) { + return curried.apply(this, [...args, ...more]); + }; + }; +} + +const curriedSum = curry((a, b, c, d) => a + b + c + d); +console.log(curriedSum(1)(2)(3)(4)); // 10 +console.log(curriedSum(1, 2)(3, 4)); // 10 +console.log(curriedSum(1, 2, 3, 4)); // 10 + +// Practical: Curried API +function fetch(url) { + return function(method) { + return function(body) { + console.log(`Fetching ${url} with ${method}`, body); + // Would actually fetch here + }; + }; +} + +const getUser = fetch("/api/users")("GET")(null); +const createUser = fetch("/api/users")("POST")({ name: "Alice" }); + +getUser(); +// "Fetching /api/users with GET null" + +createUser(); +// "Fetching /api/users with POST { name: 'Alice' }" + +// Practical: Event handlers +function handleEvent(eventType) { + return function(selector) { + return function(callback) { + document.querySelectorAll(selector).forEach(el => { + el.addEventListener(eventType, callback); + }); + }; + }; +} + +const onClick = handleEvent("click"); +const onButtons = onClick("button"); +const logClick = onButtons(e => console.log("Button clicked:", e.target)); +``` + + +## Partial Application + +與 currying 類似,但更靈活: + + +```java !! java +// Java - Method references achieve partial application +public class Greeter { + public String greet(String greeting, String name) { + return greeting + ", " + name + "!"; + } + + // Cannot partially apply without creating new methods +} +``` + +```javascript !! js +// JavaScript - Partial application with bind +function greet(greeting, name, punctuation) { + return `${greeting}, ${name}${punctuation}`; +} + +const sayHello = greet.bind(null, "Hello"); +console.log(sayHello("Alice", "!")); // "Hello, Alice!" + +const sayHelloAlice = sayHello.bind(null, "Alice"); +console.log(sayHelloAlice("~")); // "Hello, Alice~" + +// Partial application function +function partial(fn, ...presetArgs) { + return function(...laterArgs) { + return fn.apply(this, [...presetArgs, ...laterArgs]); + }; +} + +const add = (a, b, c) => a + b + c; +const addFiveAndThree = partial(add, 5, 3); +console.log(addFiveAndThree(2)); // 10 + +// Practical: Configuration +function fetch(url, options, callback) { + console.log(`Fetching ${url}`, options); + callback(null, { data: "response" }); +} + +const apiFetch = partial(fetch, "https://api.example.com"); +const apiGet = partial(apiFetch, { method: "GET" }); + +apiGet((err, data) => { + console.log("Got:", data); +}); +// "Fetching https://api.example.com { method: 'GET' }" +// "Got: { data: 'response' }" +``` + + +## Memoization + +快取函數結果以提高效能: + + +```java !! java +// Java - Manual memoization +public class Fibonacci { + private Map cache = new HashMap<>(); + + public long fib(int n) { + if (n <= 1) return n; + + if (cache.containsKey(n)) { + return cache.get(n); + } + + long result = fib(n - 1) + fib(n - 2); + cache.put(n, result); + return result; + } +} +``` + +```javascript !! js +// JavaScript - Memoization closure +function memoize(fn) { + const cache = new Map(); + + return function(...args) { + const key = JSON.stringify(args); + + if (cache.has(key)) { + return cache.get(key); + } + + const result = fn.apply(this, args); + cache.set(key, result); + return result; + }; +} + +// Expensive function +function fibonacci(n) { + if (n <= 1) return n; + return fibonacci(n - 1) + fibonacci(n - 2); +} + +// Without memoization(very slow) +// console.log(fibonacci(40)); // Takes seconds + +// With memoization(instant) +const fastFibonacci = memoize(fibonacci); +console.log(fastFibonacci(40)); // 102334155(instant) +console.log(fastFibonacci(40)); // From cache + +// Memoization with multiple arguments +function add(a, b, c) { + console.log("Computing..."); + return a + b + c; +} + +const memoizedAdd = memoize(add); +console.log(memoizedAdd(1, 2, 3)); // "Computing..." then 6 +console.log(memoizedAdd(1, 2, 3)); // 6(no "Computing...") + +// Memoization with size limit +function memoizeLimited(fn, maxSize = 100) { + const cache = new Map(); + + return function(...args) { + const key = JSON.stringify(args); + + if (cache.has(key)) { + return cache.get(key); + } + + if (cache.size >= maxSize) { + const firstKey = cache.keys().next().value; + cache.delete(firstKey); + } + + const result = fn.apply(this, args); + cache.set(key, result); + return result; + }; +} + +// Practical: API memoization +function memoizedFetch(url) { + const cache = new Map(); + + return async function() { + if (cache.has(url)) { + console.log("From cache:", url); + return cache.get(url); + } + + console.log("Fetching:", url); + const response = await fetch(url); + const data = await response.json(); + cache.set(url, data); + return data; + }; +} + +const getUser = memoizedFetch("/api/user/1"); +// First call: "Fetching: /api/user/1" +// Second call: "From cache: /api/user/1" +``` + + +## Recursion + +JavaScript 中的遞迴與 Java 類似,但有一些注意事項: + + +```java !! java +// Java - Recursion +public class Recursion { + public int factorial(int n) { + if (n <= 1) return 1; + return n * factorial(n - 1); + } + + public int fibonacci(int n) { + if (n <= 1) return n; + return fibonacci(n - 1) + fibonacci(n - 2); + } + + // Tail recursion(Java doesn't optimize) + public int factorialTail(int n, int accumulator) { + if (n <= 1) return accumulator; + return factorialTail(n - 1, n * accumulator); + } +} +``` + +```javascript !! js +// JavaScript - Recursion +function factorial(n) { + if (n <= 1) return 1; + return n * factorial(n - 1); +} + +console.log(factorial(5)); // 120 + +// Tail recursion(not optimized in most JS engines) +function factorialTail(n, accumulator = 1) { + if (n <= 1) return accumulator; + return factorialTail(n - 1, n * accumulator); +} + +console.log(factorialTail(5)); // 120 + +// Practical: Deep clone +function deepClone(obj) { + if (obj === null || typeof obj !== "object") { + return obj; + } + + if (Array.isArray(obj)) { + return obj.map(deepClone); + } + + const cloned = {}; + for (const key in obj) { + if (obj.hasOwnProperty(key)) { + cloned[key] = deepClone(obj[key]); + } + } + return cloned; +} + +// Practical: Tree traversal +const tree = { + value: 1, + left: { + value: 2, + left: { value: 4 }, + right: { value: 5 } + }, + right: { + value: 3, + left: { value: 6 }, + right: { value: 7 } + } +}; + +function inOrderTraversal(node, result = []) { + if (!node) return result; + + inOrderTraversal(node.left, result); + result.push(node.value); + inOrderTraversal(node.right, result); + + return result; +} + +console.log(inOrderTraversal(tree)); // [4, 2, 5, 1, 6, 3, 7] + +// ⚠️ Stack overflow risk +function recurseDeeply(n) { + if (n <= 0) return "done"; + return recurseDeeply(n - 1); +} + +// recurseDeeply(100000); // Stack overflow! + +// Solution: Trampoline(covered in generators section) +``` + + +## Generator Functions + +Generators 提供了一種特殊的函數,可以暫停和恢復執行: + + +```java !! java +// Java - Iterators +public class Range implements Iterable { + private int start; + private int end; + + public Range(int start, int end) { + this.start = start; + this.end = end; + } + + @Override + public Iterator iterator() { + return new Iterator() { + private int current = start; + + @Override + public boolean hasNext() { + return current < end; + } + + @Override + public Integer next() { + return current++; + } + }; + } + + public static void main(String[] args) { + Range range = new Range(0, 5); + for (int i : range) { + System.out.println(i); + } + } +} +``` + +```javascript !! js +// JavaScript - Generators +function* range(start, end) { + for (let i = start; i < end; i++) { + yield i; + } +} + +for (const n of range(0, 5)) { + console.log(n); // 0, 1, 2, 3, 4 +} + +// Generator object +const gen = range(0, 5); +console.log(gen.next()); // { value: 0, done: false } +console.log(gen.next()); // { value: 1, done: false } +console.log(gen.next()); // { value: 2, done: false } +console.log(gen.next()); // { value: 3, done: false } +console.log(gen.next()); // { value: 4, done: false } +console.log(gen.next()); // { value: undefined, done: true } + +// Infinite sequence +function* fibonacci() { + let [prev, curr] = [0, 1]; + while (true) { + yield curr; + [prev, curr] = [curr, prev + curr]; + } +} + +const fib = fibonacci(); +console.log(fib.next().value); // 1 +console.log(fib.next().value); // 1 +console.log(fib.next().value); // 2 +console.log(fib.next().value); // 3 +console.log(fib.next().value); // 5 + +// Practical: State machines +function* trafficLight() { + while (true) { + yield "red"; + yield "yellow"; + yield "green"; + } +} + +const light = trafficLight(); +console.log(light.next().value); // "red" +console.log(light.next().value); // "yellow" +console.log(light.next().value); // "green" +console.log(light.next().value); // "red" + +// Practical: Async iteration(simplified) +function* fetchUsers(ids) { + for (const id of ids) { + // In real code, would yield fetch promise + yield { id, name: `User ${id}` }; + } +} + +const users = fetchUsers([1, 2, 3]); +for (const user of users) { + console.log(user); +} +// { id: 1, name: "User 1" } +// { id: 2, name: "User 2" } +// { id: 3, name: "User 3" } +``` + + +## Best Practices + + +```javascript !! js +// 1. Use closures for encapsulation +function createStack() { + const items = []; + + return { + push(item) { + items.push(item); + }, + pop() { + return items.pop(); + }, + isEmpty() { + return items.length === 0; + } + }; +} + +// 2. Use HOFs for abstraction +const withLogging = (fn) => (...args) => { + console.log("Calling:", fn.name, args); + const result = fn.apply(this, args); + console.log("Result:", result); + return result; +}; + +const add = (a, b) => a + b; +const loggedAdd = withLogging(add); +loggedAdd(5, 3); +// "Calling: add [5, 3]" +// "Result: 8" + +// 3. Memoize expensive operations +const memoizedExpensive = memoize(expensiveOperation); + +// 4. Use generators for sequences +function* naturalNumbers() { + let n = 1; + while (true) { + yield n++; + } +} + +// 5. Currying for configuration +const connect = curry((host, port, ssl) => ({ + host, + port, + ssl, + url: `${ssl ? "https" : "http"}://${host}:${port}` +})); + +const localhost = connect("localhost"); +const api = localhost(3000); +console.log(api(false)); // { host: "localhost", port: 3000, ssl: false, url: "..." } + +// 6. Avoid closures in hot paths(performance) +function createHandlerClosure() { + let count = 0; + return function() { + count++; + // Closure lookup has small overhead + }; +} + +// Better: Use object +function createHandlerObject() { + return { + count: 0, + handle() { + this.count++; + // Property access is faster + } + }; +} +``` + + +## Exercises + +### Exercise 1: Create Counter with Closure +```javascript +function createCounter(start) { + // Return object with increment, decrement, reset methods +} +``` + +### Exercise 2: Curry Function +```javascript +function multiply(a, b, c) { + return a * b * c; +} + +// Create curried version that can be called like: +// curriedMultiply(2)(3)(4) // 24 +// curriedMultiply(2, 3)(4) // 24 +``` + +### Exercise 3: Memoize +```javascript +// Implement memoize function that caches results +function memoize(fn) { + // Implementation +} +``` + +### Exercise 4: Generator Range +```javascript +// Create generator that yields range of numbers +function* range(start, end, step = 1) { + // Implementation +} +``` + +## Summary + +### Key Takeaways + +1. **Closures:** + - Functions remember their scope + - Enable private state + - Powerful but can cause memory leaks + +2. **Higher-Order Functions:** + - Accept/return functions + - Enable abstraction + - Core of functional programming + +3. **Currying & Partial Application:** + - Transform multi-arg functions + - Enable configuration + - Improve code reusability + +4. **Memoization:** + - Cache expensive operations + - Trade memory for speed + - Use with pure functions + +5. **Recursion & Generators:** + - Recursion for divide-and-conquer + - Generators for sequences + - Both enable elegant solutions + +## What's Next? + +你已經掌握了進階函數概念!接下來是 **Module 5: Arrays and Collections**,我們將探討: + +- Array methods in depth +- Set and Map data structures +- Array-like objects +- Typed arrays +- Collection performance + +準備好深入學習 JavaScript 集合了嗎?讓我們繼續! diff --git a/content/docs/java2js/module-05-arrays-collections.mdx b/content/docs/java2js/module-05-arrays-collections.mdx new file mode 100644 index 0000000..c1382dd --- /dev/null +++ b/content/docs/java2js/module-05-arrays-collections.mdx @@ -0,0 +1,1056 @@ +--- +title: "Module 5: Arrays and Collections" +description: "Master JavaScript arrays, Sets, Maps, and collection methods" +--- + +## Module 5: Arrays and Collections + +JavaScript's collection types are different from Java's. Arrays are more flexible, and JavaScript offers additional collection types like Set and Map. Let's explore these powerful data structures. + +## Learning Objectives + +By the end of this module, you will: +✅ Master JavaScript array creation and manipulation +✅ Understand array methods (mutating vs non-mutating) +✅ Learn about Set and Map data structures +✅ Know when to use WeakSet and WeakMap +✅ Understand array destructuring +✅ Learn performance considerations + +## Array Basics + +JavaScript arrays are more like dynamic lists than Java's arrays: + + +```java !! java +// Java - Fixed-size arrays +int[] numbers = new int[5]; +numbers[0] = 1; +numbers[1] = 2; +// numbers[5] = 3; // ArrayIndexOutOfBoundsException + +// Array initializer +int[] moreNumbers = {1, 2, 3, 4, 5}; + +// Size is fixed +System.out.println(numbers.length); // 5 + +// Cannot change size +// To add elements, need ArrayList +``` + +```javascript !! js +// JavaScript - Dynamic arrays +const numbers = [1, 2, 3, 4, 5]; + +// Can add elements +numbers.push(6); // Add to end +numbers.unshift(0); // Add to beginning + +// Can remove elements +numbers.pop(); // Remove from end +numbers.shift(); // Remove from beginning + +// Dynamic length +console.log(numbers.length); // 5 (after push/pop) + +// Mixed types (not recommended but possible) +const mixed = [1, "hello", true, { id: 1 }, [1, 2, 3]]; + +// Sparse arrays +const sparse = [1, , , 4]; // Holes in array +console.log(sparse.length); // 4 +console.log(sparse[1]); // undefined + +// Array constructor (avoid - use literal syntax) +const arr = new Array(5); // Creates array with 5 empty slots +const arr2 = new Array(1, 2, 3); // Creates [1, 2, 3] +// Better: const arr = [1, 2, 3]; +``` + + +### Array Length + + +```java !! java +// Java - Length is fixed +int[] arr = {1, 2, 3}; +System.out.println(arr.length); // 3 + +arr.length = 5; // Compilation error! Cannot change length +``` + +```javascript !! js +// JavaScript - Length is writable +let arr = [1, 2, 3]; +console.log(arr.length); // 3 + +// Setting length truncates or adds undefined +arr.length = 5; +console.log(arr); // [1, 2, 3, undefined × 2] + +arr.length = 2; +console.log(arr); // [1, 2] + +// Clear array +arr.length = 0; +console.log(arr); // [] + +// Practical: Fast truncate +const bigArray = [1, 2, 3, 4, 5]; +bigArray.length = 3; // Keep only first 3 elements + +// Check if empty +if (arr.length === 0) { + console.log("Array is empty"); +} + +// Truthy/falsy +if (arr.length) { // Works because 0 is falsy + console.log("Array has elements"); +} +``` + + +## Array Methods - Mutating vs Non-Mutating + +JavaScript distinguishes between methods that modify the array and those that create a new array: + +### Mutating Methods + + +```java !! java +// Java - Arrays don't have methods +// Use ArrayList or utility classes +List list = new ArrayList<>(Arrays.asList("a", "b", "c")); +list.add("d"); // Add to end +list.add(0, "start"); // Add at index +list.remove(0); // Remove at index +list.set(0, "x"); // Replace at index +``` + +```javascript !! js +// JavaScript - Mutating methods + +const arr = [1, 2, 3, 4, 5]; + +// push: Add to end +arr.push(6); +console.log(arr); // [1, 2, 3, 4, 5, 6] + +// pop: Remove from end +arr.pop(); +console.log(arr); // [1, 2, 3, 4, 5] + +// unshift: Add to beginning +arr.unshift(0); +console.log(arr); // [0, 1, 2, 3, 4, 5] + +// shift: Remove from beginning +arr.shift(); +console.log(arr); // [1, 2, 3, 4, 5] + +// splice: Remove/add at any position +arr.splice(2, 1); // Remove 1 element at index 2 +console.log(arr); // [1, 2, 4, 5] + +arr.splice(2, 0, 3); // Insert 3 at index 2 +console.log(arr); // [1, 2, 3, 4, 5] + +// sort: Sorts in place +arr.sort((a, b) => a - b); +console.log(arr); // [1, 2, 3, 4, 5] + +// reverse: Reverses in place +arr.reverse(); +console.log(arr); // [5, 4, 3, 2, 1] + +// fill: Fill with value +arr.fill(0); +console.log(arr); // [0, 0, 0, 0, 0] +``` + + +### Non-Mutating Methods + + +```java !! java +// Java - Streams create new collections +List numbers = Arrays.asList(1, 2, 3, 4, 5); + +List doubled = numbers.stream() + .map(n -> n * 2) + .collect(Collectors.toList()); + +List evens = numbers.stream() + .filter(n -> n % 2 == 0) + .collect(Collectors.toList()); + +// Original list unchanged +``` + +```javascript !! js +// JavaScript - Non-mutating methods +const numbers = [1, 2, 3, 4, 5]; + +// map: Transform to new array +const doubled = numbers.map(n => n * 2); +console.log(doubled); // [2, 4, 6, 8, 10] +console.log(numbers); // [1, 2, 3, 4, 5] (unchanged) + +// filter: Select elements +const evens = numbers.filter(n => n % 2 === 0); +console.log(evens); // [2, 4] +console.log(numbers); // [1, 2, 3, 4, 5] (unchanged) + +// slice: Extract portion +const first3 = numbers.slice(0, 3); +console.log(first3); // [1, 2, 3] + +// concat: Combine arrays +const more = [6, 7, 8]; +const combined = numbers.concat(more); +console.log(combined); // [1, 2, 3, 4, 5, 6, 7, 8] + +// join: Convert to string +const str = numbers.join("-"); +console.log(str); // "1-2-3-4-5" + +// flat: Flatten nested arrays +const nested = [1, [2, [3, [4]]]]; +const flat = nested.flat(2); +console.log(flat); // [1, 2, 3, [4]] + +// flatMap: Map and flatten in one step +const sentences = ["Hello world", "Goodbye earth"]; +const words = sentences.flatMap(s => s.split(" ")); +console.log(words); // ["Hello", "world", "Goodbye", "earth"] +``` + + +## Array Destructuring + +Destructuring allows unpacking values from arrays: + + +```java !! java +// Java - Manual unpacking +int[] coordinates = {10, 20, 30}; +int x = coordinates[0]; +int y = coordinates[1]; +int z = coordinates[2]; + +// Or use objects/classes +public class Point { + public int x, y, z; + // Constructor, getters, setters... +} +``` + +```javascript !! js +// JavaScript - Destructuring +const coordinates = [10, 20, 30]; + +// Basic destructuring +const [x, y, z] = coordinates; +console.log(x, y, z); // 10 20 30 + +// Skip elements +const [first, , third] = coordinates; +console.log(first, third); // 10 30 + +// Rest operator +const [head, ...tail] = coordinates; +console.log(head); // 10 +console.log(tail); // [20, 30] + +// Default values +const [a = 1, b = 2, c = 3] = [10]; +console.log(a, b, c); // 10 2 3 + +// Swap variables +let m = 1, n = 2; +[m, n] = [n, m]; +console.log(m, n); // 2 1 + +// Destructuring function returns +function getStats() { + return [100, 50, [25, 75]]; +} +const [total, min, [max1, max2]] = getStats(); +console.log(total, min, max1, max2); // 100 50 25 75 + +// Practical: Parse URL +const url = "https://example.com/users/123"; +const [, protocol, domain, path, id] = url.split(/\/+/); +console.log(protocol); // "https:" +console.log(domain); // "example.com" +console.log(path); // "users" +console.log(id); // "123" +``` + + +## Searching and Querying + + +```java !! java +// Java - Search methods +List names = Arrays.asList("Alice", "Bob", "Charlie"); + +boolean hasAlice = names.contains("Alice"); // true +int indexOfBob = names.indexOf("Bob"); // 1 + +// With streams +Optional found = names.stream() + .filter(n -> n.startsWith("B")) + .findFirst(); + +boolean allLong = names.stream() + .allMatch(n -> n.length() > 3); + +boolean anyLong = names.stream() + .anyMatch(n -> n.length() > 4); +``` + +```javascript !! js +// JavaScript - Search methods +const names = ["Alice", "Bob", "Charlie"]; + +// indexOf: Find index +const indexOfBob = names.indexOf("Bob"); +console.log(indexOfBob); // 1 + +const indexOfXYZ = names.indexOf("XYZ"); +console.log(indexOfXYZ); // -1 (not found) + +// includes: Check existence +const hasAlice = names.includes("Alice"); +console.log(hasAlice); // true + +// find: Find element matching predicate +const found = names.find(name => name.startsWith("B")); +console.log(found); // "Bob" + +// findIndex: Find index of matching element +const foundIndex = names.findIndex(name => name.startsWith("B")); +console.log(foundIndex); // 1 + +// some: Check if any element matches +const anyLong = names.some(name => name.length > 4); +console.log(anyLong); // true + +// every: Check if all elements match +const allLong = names.every(name => name.length > 3); +console.log(allLong); // true + +// lastIndexOf: Find from end +const dupes = [1, 2, 3, 2, 1]; +console.log(dupes.lastIndexOf(2)); // 3 + +// Practical: Find user by ID +const users = [ + { id: 1, name: "Alice" }, + { id: 2, name: "Bob" }, + { id: 3, name: "Charlie" } +]; + +const user = users.find(u => u.id === 2); +console.log(user); // { id: 2, name: "Bob" } + +const userIndex = users.findIndex(u => u.id === 2); +console.log(userIndex); // 1 +``` + + +## Array Reduction + + +```java !! java +// Java - Reduce with streams +List numbers = Arrays.asList(1, 2, 3, 4, 5); + +// Sum +int sum = numbers.stream().reduce(0, Integer::sum); + +// Product +int product = numbers.stream().reduce(1, (a, b) -> a * b); + +// Joining +String joined = names.stream().collect(Collectors.joining(", ")); + +// To map +Map map = names.stream() + .collect(Collectors.toMap( + String::length, + Function.identity(), + (a, b) -> a // Merge function + )); +``` + +```javascript !! js +// JavaScript - Reduce methods +const numbers = [1, 2, 3, 4, 5]; + +// reduce: Reduce to single value +const sum = numbers.reduce((acc, n) => acc + n, 0); +console.log(sum); // 15 + +const product = numbers.reduce((acc, n) => acc * n, 1); +console.log(product); // 120 + +const max = numbers.reduce((acc, n) => Math.max(acc, n), -Infinity); +console.log(max); // 5 + +const min = numbers.reduce((acc, n) => Math.min(acc, n), Infinity); +console.log(min); // 1 + +// reduceRight: Reduce from right to left +const flattened = [[1, 2], [3, 4], [5, 6]].reduce((acc, arr) => acc.concat(arr), []); +console.log(flattened); // [1, 2, 3, 4, 5, 6] + +// Practical: Group by +const words = ["apple", "banana", "avocado", "cherry", "blueberry"]; +const grouped = words.reduce((acc, word) => { + const first = word[0]; + if (!acc[first]) acc[first] = []; + acc[first].push(word); + return acc; +}, {}); + +console.log(grouped); +// { a: ["apple", "avocado"], b: ["banana", "blueberry"], c: ["cherry"] } + +// Practical: Build lookup map +const users = [ + { id: 1, name: "Alice" }, + { id: 2, name: "Bob" } +]; + +const userMap = users.reduce((acc, user) => { + acc[user.id] = user; + return acc; +}, {}); + +console.log(userMap[1]); // { id: 1, name: "Alice" } +``` + + +## Set and Map + +JavaScript offers Set and Map collections similar to Java's: + + +```java !! java +// Java - HashSet +Set names = new HashSet<>(); +names.add("Alice"); +names.add("Bob"); +names.add("Alice"); // Duplicate ignored + +System.out.println(names.contains("Alice")); // true +System.out.println(names.size()); // 2 + +names.remove("Alice"); + +for (String name : names) { + System.out.println(name); +} + +// LinkedHashSet (maintains insertion order) +Set ordered = new LinkedHashSet<>(); + +// TreeSet (sorted) +Set sorted = new TreeSet<>(); +``` + +```javascript !! js +// JavaScript - Set +const names = new Set(); +names.add("Alice"); +names.add("Bob"); +names.add("Alice"); // Duplicate ignored + +console.log(names.has("Alice")); // true +console.log(names.size); // 2 + +names.delete("Alice"); +console.log(names.has("Alice")); // false + +// Iterate +for (const name of names) { + console.log(name); +} + +// Convert to array +const nameArray = [...names]; +console.log(nameArray); // ["Bob"] + +// Initialize from array +const numbers = new Set([1, 2, 3, 2, 1]); +console.log(numbers); // Set {1, 2, 3} + +// Practical: Remove duplicates +const dupes = [1, 2, 2, 3, 3, 3, 4]; +const unique = [...new Set(dupes)]; +console.log(unique); // [1, 2, 3, 4] + +// Set methods +const set = new Set([1, 2, 3]); +set.clear(); // Remove all +console.log(set.size); // 0 + +// Set operations +const a = new Set([1, 2, 3]); +const b = new Set([3, 4, 5]); + +// Union +const union = new Set([...a, ...b]); +console.log(union); // Set {1, 2, 3, 4, 5} + +// Intersection +const intersection = new Set([...a].filter(x => b.has(x))); +console.log(intersection); // Set {3} + +// Difference +const difference = new Set([...a].filter(x => !b.has(x))); +console.log(difference); // Set {1, 2} +``` + + + +```java !! java +// Java - HashMap +Map scores = new HashMap<>(); +scores.put("Alice", 95); +scores.put("Bob", 87); + +System.out.println(scores.get("Alice")); // 95 +System.out.println(scores.containsKey("Bob")); // true + +scores.remove("Alice"); + +for (Map.Entry entry : scores.entrySet()) { + System.out.println(entry.getKey() + ": " + entry.getValue()); +} + +// LinkedHashMap (maintains insertion order) +// TreeMap (sorted by keys) +``` + +```javascript !! js +// JavaScript - Map +const scores = new Map(); +scores.set("Alice", 95); +scores.set("Bob", 87); + +console.log(scores.get("Alice")); // 95 +console.log(scores.has("Bob")); // true + +scores.delete("Alice"); +console.log(scores.has("Alice")); // false + +// Iterate +for (const [name, score] of scores) { + console.log(`${name}: ${score}`); +} + +// Initialize from array of pairs +const data = new Map([ + ["Alice", 95], + ["Bob", 87] +]); + +// Map vs Object: Map keys can be any type +const map = new Map(); +const key1 = { id: 1 }; +const key2 = { id: 2 }; + +map.set(key1, "Value 1"); +map.set(key2, "Value 2"); +map.set("string", "String key"); +map.set(123, "Number key"); + +console.log(map.get(key1)); // "Value 1" + +// Object keys are always strings/symbols +const obj = {}; +obj[key1] = "Value"; // key converted to "[object Object]" +obj[key2] = "Value"; // Overwrites previous! + +// Map iteration order is guaranteed +const orderedMap = new Map([ + ["z", 1], + ["a", 2], + ["m", 3] +]); + +for (const [key, value] of orderedMap) { + console.log(key, value); // Maintains insertion order +} + +// Map methods +const map2 = new Map([["a", 1], ["b", 2]]); +console.log(map2.keys()); // MapIterator {"a", "b"} +console.log(map2.values()); // MapIterator {1, 2} +console.log(map2.entries()); // MapIterator {"a" => 1, "b" => 2} + +// Convert Map to Object +const map3 = new Map([["a", 1], ["b", 2]]); +const obj2 = Object.fromEntries(map3); +console.log(obj2); // { a: 1, b: 2 } + +// Convert Object to Map +const obj3 = { a: 1, b: 2 }; +const map4 = new Map(Object.entries(obj3)); +``` + + +## WeakSet and WeakMap + +WeakSet and WeakMap hold object references weakly (allowing garbage collection): + + +```java !! java +// Java - WeakHashMap +Map weakMap = new WeakHashMap<>(); +// Keys are weakly referenced +// When key is no longer strongly reachable, entry is removed + +// No direct equivalent of WeakSet (until Java 9 with IdentityHashMap) +``` + +```javascript !! js +// JavaScript - WeakSet and WeakMap + +// WeakSet: Only holds objects, no iteration +const weakSet = new WeakSet(); +let obj1 = { id: 1 }; +let obj2 = { id: 2 }; + +weakSet.add(obj1); +weakSet.add(obj2); +// weakSet.add("string"); // TypeError! + +console.log(weakSet.has(obj1)); // true + +obj1 = null; // Remove strong reference +// obj1 will be garbage collected, WeakSet entry removed + +// Use case: Track objects without preventing GC +function processObjects(objects) { + const processed = new WeakSet(); + + return objects.filter(obj => { + if (processed.has(obj)) { + return false; // Already processed + } + processed.add(obj); + return true; + }); +} + +// WeakMap: Keys must be objects, values can be anything +const weakMap = new WeakMap(); +const key1 = { id: 1 }; +const key2 = { id: 2 }; + +weakMap.set(key1, "Metadata 1"); +weakMap.set(key2, "Metadata 2"); + +console.log(weakMap.get(key1)); // "Metadata 1" +console.log(weakMap.has(key1)); // true + +weakMap.delete(key1); + +// Use case: Private data +const privateData = new WeakMap(); + +class User { + constructor(name, age) { + privateData.set(this, { age, secret: "xyz" }); + this.name = name; + } + + getAge() { + return privateData.get(this).age; + } + + getSecret() { + return privateData.get(this).secret; + } +} + +const user = new User("Alice", 25); +console.log(user.name); // "Alice" (public) +console.log(user.getAge()); // 25 (accessed via WeakMap) +console.log(user.age); // undefined (truly private) + +// Use case: DOM node metadata +const nodeData = new WeakMap(); + +function setupNode(node) { + nodeData.set(node, { + clickCount: 0, + lastClick: null + }); + + node.addEventListener("click", () => { + const data = nodeData.get(node); + data.clickCount++; + data.lastClick = Date.now(); + console.log(`Clicked ${data.clickCount} times`); + }); +} + +// When node is removed from DOM, metadata is automatically GC'd +``` + + +## Performance Considerations + + +```java !! java +// Java - ArrayList vs LinkedList +// ArrayList: O(1) random access, O(n) insert/delete +// LinkedList: O(n) random access, O(1) insert/delete + +// Use ArrayList for most cases +List list = new ArrayList<>(); + +// Pre-size when possible +List sized = new ArrayList<>(1000); +``` + +```javascript !! js +// JavaScript - Array performance + +// 1. Pre-allocate when size known (helps V8 optimize) +const arr = new Array(1000); +// Or +const arr2 = []; +arr2.length = 1000; + +// 2. Use typed arrays for numeric data +const int8 = new Int8Array(1000); // 8-bit integers +const int16 = new Int16Array(1000); // 16-bit integers +const float64 = new Float64Array(1000); // 64-bit floats + +// Performance comparison +const regular = []; +for (let i = 0; i < 1000000; i++) { + regular.push(i); +} + +const typed = new Int32Array(1000000); +for (let i = 0; i < typed.length; i++) { + typed[i] = i; +} + +// 3. Avoid sparse arrays +const sparse = []; // Bad +sparse[1000] = 1; // Creates holes + +const dense = new Array(1001).fill(0); // Good +dense[1000] = 1; + +// 4. Use for loop for heavy operations (faster than forEach) +const numbers = [1, 2, 3, 4, 5]; + +// Slower (function call overhead) +numbers.forEach(n => { + // Process n +}); + +// Faster +for (let i = 0; i < numbers.length; i++) { + const n = numbers[i]; + // Process n +} + +// 5. Set/Map lookup vs Array includes +const arr = [1, 2, 3, 4, 5]; +const set = new Set([1, 2, 3, 4, 5]); + +// Array: O(n) +arr.includes(3); + +// Set: O(1) +set.has(3); + +// For large datasets, use Set/Map for lookups +const largeArray = Array.from({ length: 100000 }, (_, i) => i); +const largeSet = new Set(largeArray); + +// Fast lookup +console.time("array"); +largeArray.includes(99999); +console.timeEnd("array"); // ~5ms + +console.time("set"); +largeSet.has(99999); +console.timeEnd("set"); // ~0ms + +// 6. Array spread vs concat +const a = [1, 2, 3]; +const b = [4, 5, 6]; + +// Modern: spread (slower for very large arrays) +const combined = [...a, ...b]; + +// Traditional: concat (often faster) +const combined2 = a.concat(b); +``` + + +## Common Patterns + +### Pattern 1: Chunking + + +```java !! java +// Java - Chunk into sublists +public static List> chunk(List list, int size) { + List> chunks = new ArrayList<>(); + for (int i = 0; i < list.size(); i += size) { + chunks.add(list.subList(i, Math.min(i + size, list.size()))); + } + return chunks; +} +``` + +```javascript !! js +// JavaScript - Chunk array +function chunk(array, size) { + const chunks = []; + for (let i = 0; i < array.length; i += size) { + chunks.push(array.slice(i, i + size)); + } + return chunks; +} + +const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9]; +console.log(chunk(numbers, 3)); +// [[1, 2, 3], [4, 5, 6], [7, 8, 9]] + +// Functional approach +function chunkFunctional(array, size) { + return Array.from( + { length: Math.ceil(array.length / size) }, + (_, i) => array.slice(i * size, i * size + size) + ); +} +``` + + +### Pattern 2: Flatten + + +```javascript !! js +// Flatten nested arrays +const nested = [1, [2, [3, [4, 5]]]]; + +// Shallow flatten +const flat1 = nested.flat(); +console.log(flat1); // [1, 2, [3, [4, 5]]] + +// Deep flatten (specify depth) +const flat2 = nested.flat(2); +console.log(flat2); // [1, 2, 3, [4, 5]] + +// Infinite flatten +const flat3 = nested.flat(Infinity); +console.log(flat3); // [1, 2, 3, 4, 5] + +// Custom flatten function +function flatten(arr) { + const result = []; + + for (const item of arr) { + if (Array.isArray(item)) { + result.push(...flatten(item)); + } else { + result.push(item); + } + } + + return result; +} +``` + + +## Best Practices + + +```java !! java +// Java: Use appropriate collection type +List list = new ArrayList<>(); // Ordered, allows duplicates +Set set = new HashSet<>(); // Unordered, unique +Map map = new HashMap<>(); // Key-value pairs + +// Choose based on needs: +// - ArrayList: Fast random access +// - LinkedList: Frequent insertions/deletions +// - HashSet: Fast lookups, unique elements +// - TreeSet: Sorted elements +// - HashMap: Fast key lookups +// - TreeMap: Sorted keys +``` + +```javascript !! js +// JavaScript: Choose wisely + +// 1. Use arrays for ordered data +const items = ["apple", "banana", "cherry"]; + +// 2. Use Set for uniqueness +const uniqueItems = new Set(["apple", "banana", "apple"]); + +// 3. Use Map for key-value with non-string keys +const metadata = new Map(); +const element = document.getElementById("myDiv"); +metadata.set(element, { clicks: 0 }); + +// 4. Use object for simple string keys +const config = { + apiUrl: "https://api.example.com", + timeout: 5000, + retries: 3 +}; + +// 5. Prefer non-mutating methods +const numbers = [1, 2, 3, 4, 5]; + +// Good: Returns new array +const doubled = numbers.map(n => n * 2); + +// Be careful: Modifies in place +numbers.reverse(); // Modifies original! + +// To avoid mutation: Create copy first +const reversed = [...numbers].reverse(); + +// 6. Use destructuring for clarity +const [first, second, ...rest] = numbers; + +// 7. Use spread for immutable updates +const state = { count: 0, name: "app" }; +const newState = { ...state, count: 1 }; // Copy with update + +const list = [1, 2, 3]; +const newList = [...list, 4]; // Copy with addition + +// 8. Leverage Set for deduplication +const dupes = [1, 2, 2, 3, 3, 3]; +const unique = [...new Set(dupes)]; +``` + + +## Exercises + +### Exercise 1: Array Manipulation +Implement these operations: +```javascript +const arr = [1, 2, 3, 4, 5]; + +// 1. Add 0 to beginning +// 2. Add 6 to end +// 3. Remove first element +// 4. Remove last element +// 5. Double all elements (non-mutating) +// 6. Filter out even numbers +// 7. Sum all numbers +``` + +### Exercise 2: Set Operations +Create Set utility functions: +```javascript +// Union of two sets +function union(setA, setB) { } + +// Intersection of two sets +function intersection(setA, setB) { } + +// Difference of two sets +function difference(setA, setB) { } +``` + +### Exercise 3: Chunk Array +Split array into chunks of size n: +```javascript +function chunk(array, size) { + // Return array of arrays +} + +chunk([1, 2, 3, 4, 5, 6, 7], 3); +// [[1, 2, 3], [4, 5, 6], [7]] +``` + +### Exercise 4: Map Operations +Create frequency map: +```javascript +function frequencyMap(array) { + // Return Map with element counts +} + +frequencyMap(["apple", "banana", "apple", "cherry", "banana", "apple"]); +// Map {"apple" => 3, "banana" => 2, "cherry" => 1} +``` + +## Summary + +### Key Takeaways + +1. **Arrays:** + - Dynamic size (can grow/shrink) + - Mixed types possible + - Rich set of methods + - Distinguish mutating vs non-mutating + +2. **Destructuring:** + - Unpack values cleanly + - Skip with commas + - Rest operator for remaining + - Default values + +3. **Set:** + - Unique values only + - Fast lookups (O(1)) + - Good for deduplication + - WeakSet for memory management + +4. **Map:** + - Any key type + - Maintains insertion order + - Fast lookups (O(1)) + - WeakMap for metadata + +5. **Performance:** + - Pre-allocate when possible + - Use typed arrays for numbers + - Set/Map for large lookups + - for loops for heavy operations + +### Comparison Table: Java vs JavaScript + +| Feature | Java | JavaScript | +|---------|------|------------| +| **Array type** | Fixed size, single type | Dynamic, mixed types | +| **Growth** | Need ArrayList | Automatic | +| **Set** | HashSet, TreeSet, LinkedHashSet | Set (insertion ordered) | +| **Map** | HashMap, TreeMap, LinkedHashMap | Map (insertion ordered) | +| **Weak refs** | WeakHashMap | WeakSet, WeakMap | +| **Typed arrays** | No | Int8Array, Float64Array, etc. | +| **Destructuring** | No | Yes (ES6+) | + +## What's Next? + +You've mastered JavaScript collections! Next up is **Module 6: Objects**, where we'll explore: + +- Object literals and creation +- Property getters and setters +- Object methods (keys, values, entries) +- Object destructuring +- Spread operator with objects +- Object.freeze and Object.seal + +Ready to dive deeper into JavaScript objects? Let's continue! diff --git a/content/docs/java2js/module-05-arrays-collections.zh-cn.mdx b/content/docs/java2js/module-05-arrays-collections.zh-cn.mdx new file mode 100644 index 0000000..c1382dd --- /dev/null +++ b/content/docs/java2js/module-05-arrays-collections.zh-cn.mdx @@ -0,0 +1,1056 @@ +--- +title: "Module 5: Arrays and Collections" +description: "Master JavaScript arrays, Sets, Maps, and collection methods" +--- + +## Module 5: Arrays and Collections + +JavaScript's collection types are different from Java's. Arrays are more flexible, and JavaScript offers additional collection types like Set and Map. Let's explore these powerful data structures. + +## Learning Objectives + +By the end of this module, you will: +✅ Master JavaScript array creation and manipulation +✅ Understand array methods (mutating vs non-mutating) +✅ Learn about Set and Map data structures +✅ Know when to use WeakSet and WeakMap +✅ Understand array destructuring +✅ Learn performance considerations + +## Array Basics + +JavaScript arrays are more like dynamic lists than Java's arrays: + + +```java !! java +// Java - Fixed-size arrays +int[] numbers = new int[5]; +numbers[0] = 1; +numbers[1] = 2; +// numbers[5] = 3; // ArrayIndexOutOfBoundsException + +// Array initializer +int[] moreNumbers = {1, 2, 3, 4, 5}; + +// Size is fixed +System.out.println(numbers.length); // 5 + +// Cannot change size +// To add elements, need ArrayList +``` + +```javascript !! js +// JavaScript - Dynamic arrays +const numbers = [1, 2, 3, 4, 5]; + +// Can add elements +numbers.push(6); // Add to end +numbers.unshift(0); // Add to beginning + +// Can remove elements +numbers.pop(); // Remove from end +numbers.shift(); // Remove from beginning + +// Dynamic length +console.log(numbers.length); // 5 (after push/pop) + +// Mixed types (not recommended but possible) +const mixed = [1, "hello", true, { id: 1 }, [1, 2, 3]]; + +// Sparse arrays +const sparse = [1, , , 4]; // Holes in array +console.log(sparse.length); // 4 +console.log(sparse[1]); // undefined + +// Array constructor (avoid - use literal syntax) +const arr = new Array(5); // Creates array with 5 empty slots +const arr2 = new Array(1, 2, 3); // Creates [1, 2, 3] +// Better: const arr = [1, 2, 3]; +``` + + +### Array Length + + +```java !! java +// Java - Length is fixed +int[] arr = {1, 2, 3}; +System.out.println(arr.length); // 3 + +arr.length = 5; // Compilation error! Cannot change length +``` + +```javascript !! js +// JavaScript - Length is writable +let arr = [1, 2, 3]; +console.log(arr.length); // 3 + +// Setting length truncates or adds undefined +arr.length = 5; +console.log(arr); // [1, 2, 3, undefined × 2] + +arr.length = 2; +console.log(arr); // [1, 2] + +// Clear array +arr.length = 0; +console.log(arr); // [] + +// Practical: Fast truncate +const bigArray = [1, 2, 3, 4, 5]; +bigArray.length = 3; // Keep only first 3 elements + +// Check if empty +if (arr.length === 0) { + console.log("Array is empty"); +} + +// Truthy/falsy +if (arr.length) { // Works because 0 is falsy + console.log("Array has elements"); +} +``` + + +## Array Methods - Mutating vs Non-Mutating + +JavaScript distinguishes between methods that modify the array and those that create a new array: + +### Mutating Methods + + +```java !! java +// Java - Arrays don't have methods +// Use ArrayList or utility classes +List list = new ArrayList<>(Arrays.asList("a", "b", "c")); +list.add("d"); // Add to end +list.add(0, "start"); // Add at index +list.remove(0); // Remove at index +list.set(0, "x"); // Replace at index +``` + +```javascript !! js +// JavaScript - Mutating methods + +const arr = [1, 2, 3, 4, 5]; + +// push: Add to end +arr.push(6); +console.log(arr); // [1, 2, 3, 4, 5, 6] + +// pop: Remove from end +arr.pop(); +console.log(arr); // [1, 2, 3, 4, 5] + +// unshift: Add to beginning +arr.unshift(0); +console.log(arr); // [0, 1, 2, 3, 4, 5] + +// shift: Remove from beginning +arr.shift(); +console.log(arr); // [1, 2, 3, 4, 5] + +// splice: Remove/add at any position +arr.splice(2, 1); // Remove 1 element at index 2 +console.log(arr); // [1, 2, 4, 5] + +arr.splice(2, 0, 3); // Insert 3 at index 2 +console.log(arr); // [1, 2, 3, 4, 5] + +// sort: Sorts in place +arr.sort((a, b) => a - b); +console.log(arr); // [1, 2, 3, 4, 5] + +// reverse: Reverses in place +arr.reverse(); +console.log(arr); // [5, 4, 3, 2, 1] + +// fill: Fill with value +arr.fill(0); +console.log(arr); // [0, 0, 0, 0, 0] +``` + + +### Non-Mutating Methods + + +```java !! java +// Java - Streams create new collections +List numbers = Arrays.asList(1, 2, 3, 4, 5); + +List doubled = numbers.stream() + .map(n -> n * 2) + .collect(Collectors.toList()); + +List evens = numbers.stream() + .filter(n -> n % 2 == 0) + .collect(Collectors.toList()); + +// Original list unchanged +``` + +```javascript !! js +// JavaScript - Non-mutating methods +const numbers = [1, 2, 3, 4, 5]; + +// map: Transform to new array +const doubled = numbers.map(n => n * 2); +console.log(doubled); // [2, 4, 6, 8, 10] +console.log(numbers); // [1, 2, 3, 4, 5] (unchanged) + +// filter: Select elements +const evens = numbers.filter(n => n % 2 === 0); +console.log(evens); // [2, 4] +console.log(numbers); // [1, 2, 3, 4, 5] (unchanged) + +// slice: Extract portion +const first3 = numbers.slice(0, 3); +console.log(first3); // [1, 2, 3] + +// concat: Combine arrays +const more = [6, 7, 8]; +const combined = numbers.concat(more); +console.log(combined); // [1, 2, 3, 4, 5, 6, 7, 8] + +// join: Convert to string +const str = numbers.join("-"); +console.log(str); // "1-2-3-4-5" + +// flat: Flatten nested arrays +const nested = [1, [2, [3, [4]]]]; +const flat = nested.flat(2); +console.log(flat); // [1, 2, 3, [4]] + +// flatMap: Map and flatten in one step +const sentences = ["Hello world", "Goodbye earth"]; +const words = sentences.flatMap(s => s.split(" ")); +console.log(words); // ["Hello", "world", "Goodbye", "earth"] +``` + + +## Array Destructuring + +Destructuring allows unpacking values from arrays: + + +```java !! java +// Java - Manual unpacking +int[] coordinates = {10, 20, 30}; +int x = coordinates[0]; +int y = coordinates[1]; +int z = coordinates[2]; + +// Or use objects/classes +public class Point { + public int x, y, z; + // Constructor, getters, setters... +} +``` + +```javascript !! js +// JavaScript - Destructuring +const coordinates = [10, 20, 30]; + +// Basic destructuring +const [x, y, z] = coordinates; +console.log(x, y, z); // 10 20 30 + +// Skip elements +const [first, , third] = coordinates; +console.log(first, third); // 10 30 + +// Rest operator +const [head, ...tail] = coordinates; +console.log(head); // 10 +console.log(tail); // [20, 30] + +// Default values +const [a = 1, b = 2, c = 3] = [10]; +console.log(a, b, c); // 10 2 3 + +// Swap variables +let m = 1, n = 2; +[m, n] = [n, m]; +console.log(m, n); // 2 1 + +// Destructuring function returns +function getStats() { + return [100, 50, [25, 75]]; +} +const [total, min, [max1, max2]] = getStats(); +console.log(total, min, max1, max2); // 100 50 25 75 + +// Practical: Parse URL +const url = "https://example.com/users/123"; +const [, protocol, domain, path, id] = url.split(/\/+/); +console.log(protocol); // "https:" +console.log(domain); // "example.com" +console.log(path); // "users" +console.log(id); // "123" +``` + + +## Searching and Querying + + +```java !! java +// Java - Search methods +List names = Arrays.asList("Alice", "Bob", "Charlie"); + +boolean hasAlice = names.contains("Alice"); // true +int indexOfBob = names.indexOf("Bob"); // 1 + +// With streams +Optional found = names.stream() + .filter(n -> n.startsWith("B")) + .findFirst(); + +boolean allLong = names.stream() + .allMatch(n -> n.length() > 3); + +boolean anyLong = names.stream() + .anyMatch(n -> n.length() > 4); +``` + +```javascript !! js +// JavaScript - Search methods +const names = ["Alice", "Bob", "Charlie"]; + +// indexOf: Find index +const indexOfBob = names.indexOf("Bob"); +console.log(indexOfBob); // 1 + +const indexOfXYZ = names.indexOf("XYZ"); +console.log(indexOfXYZ); // -1 (not found) + +// includes: Check existence +const hasAlice = names.includes("Alice"); +console.log(hasAlice); // true + +// find: Find element matching predicate +const found = names.find(name => name.startsWith("B")); +console.log(found); // "Bob" + +// findIndex: Find index of matching element +const foundIndex = names.findIndex(name => name.startsWith("B")); +console.log(foundIndex); // 1 + +// some: Check if any element matches +const anyLong = names.some(name => name.length > 4); +console.log(anyLong); // true + +// every: Check if all elements match +const allLong = names.every(name => name.length > 3); +console.log(allLong); // true + +// lastIndexOf: Find from end +const dupes = [1, 2, 3, 2, 1]; +console.log(dupes.lastIndexOf(2)); // 3 + +// Practical: Find user by ID +const users = [ + { id: 1, name: "Alice" }, + { id: 2, name: "Bob" }, + { id: 3, name: "Charlie" } +]; + +const user = users.find(u => u.id === 2); +console.log(user); // { id: 2, name: "Bob" } + +const userIndex = users.findIndex(u => u.id === 2); +console.log(userIndex); // 1 +``` + + +## Array Reduction + + +```java !! java +// Java - Reduce with streams +List numbers = Arrays.asList(1, 2, 3, 4, 5); + +// Sum +int sum = numbers.stream().reduce(0, Integer::sum); + +// Product +int product = numbers.stream().reduce(1, (a, b) -> a * b); + +// Joining +String joined = names.stream().collect(Collectors.joining(", ")); + +// To map +Map map = names.stream() + .collect(Collectors.toMap( + String::length, + Function.identity(), + (a, b) -> a // Merge function + )); +``` + +```javascript !! js +// JavaScript - Reduce methods +const numbers = [1, 2, 3, 4, 5]; + +// reduce: Reduce to single value +const sum = numbers.reduce((acc, n) => acc + n, 0); +console.log(sum); // 15 + +const product = numbers.reduce((acc, n) => acc * n, 1); +console.log(product); // 120 + +const max = numbers.reduce((acc, n) => Math.max(acc, n), -Infinity); +console.log(max); // 5 + +const min = numbers.reduce((acc, n) => Math.min(acc, n), Infinity); +console.log(min); // 1 + +// reduceRight: Reduce from right to left +const flattened = [[1, 2], [3, 4], [5, 6]].reduce((acc, arr) => acc.concat(arr), []); +console.log(flattened); // [1, 2, 3, 4, 5, 6] + +// Practical: Group by +const words = ["apple", "banana", "avocado", "cherry", "blueberry"]; +const grouped = words.reduce((acc, word) => { + const first = word[0]; + if (!acc[first]) acc[first] = []; + acc[first].push(word); + return acc; +}, {}); + +console.log(grouped); +// { a: ["apple", "avocado"], b: ["banana", "blueberry"], c: ["cherry"] } + +// Practical: Build lookup map +const users = [ + { id: 1, name: "Alice" }, + { id: 2, name: "Bob" } +]; + +const userMap = users.reduce((acc, user) => { + acc[user.id] = user; + return acc; +}, {}); + +console.log(userMap[1]); // { id: 1, name: "Alice" } +``` + + +## Set and Map + +JavaScript offers Set and Map collections similar to Java's: + + +```java !! java +// Java - HashSet +Set names = new HashSet<>(); +names.add("Alice"); +names.add("Bob"); +names.add("Alice"); // Duplicate ignored + +System.out.println(names.contains("Alice")); // true +System.out.println(names.size()); // 2 + +names.remove("Alice"); + +for (String name : names) { + System.out.println(name); +} + +// LinkedHashSet (maintains insertion order) +Set ordered = new LinkedHashSet<>(); + +// TreeSet (sorted) +Set sorted = new TreeSet<>(); +``` + +```javascript !! js +// JavaScript - Set +const names = new Set(); +names.add("Alice"); +names.add("Bob"); +names.add("Alice"); // Duplicate ignored + +console.log(names.has("Alice")); // true +console.log(names.size); // 2 + +names.delete("Alice"); +console.log(names.has("Alice")); // false + +// Iterate +for (const name of names) { + console.log(name); +} + +// Convert to array +const nameArray = [...names]; +console.log(nameArray); // ["Bob"] + +// Initialize from array +const numbers = new Set([1, 2, 3, 2, 1]); +console.log(numbers); // Set {1, 2, 3} + +// Practical: Remove duplicates +const dupes = [1, 2, 2, 3, 3, 3, 4]; +const unique = [...new Set(dupes)]; +console.log(unique); // [1, 2, 3, 4] + +// Set methods +const set = new Set([1, 2, 3]); +set.clear(); // Remove all +console.log(set.size); // 0 + +// Set operations +const a = new Set([1, 2, 3]); +const b = new Set([3, 4, 5]); + +// Union +const union = new Set([...a, ...b]); +console.log(union); // Set {1, 2, 3, 4, 5} + +// Intersection +const intersection = new Set([...a].filter(x => b.has(x))); +console.log(intersection); // Set {3} + +// Difference +const difference = new Set([...a].filter(x => !b.has(x))); +console.log(difference); // Set {1, 2} +``` + + + +```java !! java +// Java - HashMap +Map scores = new HashMap<>(); +scores.put("Alice", 95); +scores.put("Bob", 87); + +System.out.println(scores.get("Alice")); // 95 +System.out.println(scores.containsKey("Bob")); // true + +scores.remove("Alice"); + +for (Map.Entry entry : scores.entrySet()) { + System.out.println(entry.getKey() + ": " + entry.getValue()); +} + +// LinkedHashMap (maintains insertion order) +// TreeMap (sorted by keys) +``` + +```javascript !! js +// JavaScript - Map +const scores = new Map(); +scores.set("Alice", 95); +scores.set("Bob", 87); + +console.log(scores.get("Alice")); // 95 +console.log(scores.has("Bob")); // true + +scores.delete("Alice"); +console.log(scores.has("Alice")); // false + +// Iterate +for (const [name, score] of scores) { + console.log(`${name}: ${score}`); +} + +// Initialize from array of pairs +const data = new Map([ + ["Alice", 95], + ["Bob", 87] +]); + +// Map vs Object: Map keys can be any type +const map = new Map(); +const key1 = { id: 1 }; +const key2 = { id: 2 }; + +map.set(key1, "Value 1"); +map.set(key2, "Value 2"); +map.set("string", "String key"); +map.set(123, "Number key"); + +console.log(map.get(key1)); // "Value 1" + +// Object keys are always strings/symbols +const obj = {}; +obj[key1] = "Value"; // key converted to "[object Object]" +obj[key2] = "Value"; // Overwrites previous! + +// Map iteration order is guaranteed +const orderedMap = new Map([ + ["z", 1], + ["a", 2], + ["m", 3] +]); + +for (const [key, value] of orderedMap) { + console.log(key, value); // Maintains insertion order +} + +// Map methods +const map2 = new Map([["a", 1], ["b", 2]]); +console.log(map2.keys()); // MapIterator {"a", "b"} +console.log(map2.values()); // MapIterator {1, 2} +console.log(map2.entries()); // MapIterator {"a" => 1, "b" => 2} + +// Convert Map to Object +const map3 = new Map([["a", 1], ["b", 2]]); +const obj2 = Object.fromEntries(map3); +console.log(obj2); // { a: 1, b: 2 } + +// Convert Object to Map +const obj3 = { a: 1, b: 2 }; +const map4 = new Map(Object.entries(obj3)); +``` + + +## WeakSet and WeakMap + +WeakSet and WeakMap hold object references weakly (allowing garbage collection): + + +```java !! java +// Java - WeakHashMap +Map weakMap = new WeakHashMap<>(); +// Keys are weakly referenced +// When key is no longer strongly reachable, entry is removed + +// No direct equivalent of WeakSet (until Java 9 with IdentityHashMap) +``` + +```javascript !! js +// JavaScript - WeakSet and WeakMap + +// WeakSet: Only holds objects, no iteration +const weakSet = new WeakSet(); +let obj1 = { id: 1 }; +let obj2 = { id: 2 }; + +weakSet.add(obj1); +weakSet.add(obj2); +// weakSet.add("string"); // TypeError! + +console.log(weakSet.has(obj1)); // true + +obj1 = null; // Remove strong reference +// obj1 will be garbage collected, WeakSet entry removed + +// Use case: Track objects without preventing GC +function processObjects(objects) { + const processed = new WeakSet(); + + return objects.filter(obj => { + if (processed.has(obj)) { + return false; // Already processed + } + processed.add(obj); + return true; + }); +} + +// WeakMap: Keys must be objects, values can be anything +const weakMap = new WeakMap(); +const key1 = { id: 1 }; +const key2 = { id: 2 }; + +weakMap.set(key1, "Metadata 1"); +weakMap.set(key2, "Metadata 2"); + +console.log(weakMap.get(key1)); // "Metadata 1" +console.log(weakMap.has(key1)); // true + +weakMap.delete(key1); + +// Use case: Private data +const privateData = new WeakMap(); + +class User { + constructor(name, age) { + privateData.set(this, { age, secret: "xyz" }); + this.name = name; + } + + getAge() { + return privateData.get(this).age; + } + + getSecret() { + return privateData.get(this).secret; + } +} + +const user = new User("Alice", 25); +console.log(user.name); // "Alice" (public) +console.log(user.getAge()); // 25 (accessed via WeakMap) +console.log(user.age); // undefined (truly private) + +// Use case: DOM node metadata +const nodeData = new WeakMap(); + +function setupNode(node) { + nodeData.set(node, { + clickCount: 0, + lastClick: null + }); + + node.addEventListener("click", () => { + const data = nodeData.get(node); + data.clickCount++; + data.lastClick = Date.now(); + console.log(`Clicked ${data.clickCount} times`); + }); +} + +// When node is removed from DOM, metadata is automatically GC'd +``` + + +## Performance Considerations + + +```java !! java +// Java - ArrayList vs LinkedList +// ArrayList: O(1) random access, O(n) insert/delete +// LinkedList: O(n) random access, O(1) insert/delete + +// Use ArrayList for most cases +List list = new ArrayList<>(); + +// Pre-size when possible +List sized = new ArrayList<>(1000); +``` + +```javascript !! js +// JavaScript - Array performance + +// 1. Pre-allocate when size known (helps V8 optimize) +const arr = new Array(1000); +// Or +const arr2 = []; +arr2.length = 1000; + +// 2. Use typed arrays for numeric data +const int8 = new Int8Array(1000); // 8-bit integers +const int16 = new Int16Array(1000); // 16-bit integers +const float64 = new Float64Array(1000); // 64-bit floats + +// Performance comparison +const regular = []; +for (let i = 0; i < 1000000; i++) { + regular.push(i); +} + +const typed = new Int32Array(1000000); +for (let i = 0; i < typed.length; i++) { + typed[i] = i; +} + +// 3. Avoid sparse arrays +const sparse = []; // Bad +sparse[1000] = 1; // Creates holes + +const dense = new Array(1001).fill(0); // Good +dense[1000] = 1; + +// 4. Use for loop for heavy operations (faster than forEach) +const numbers = [1, 2, 3, 4, 5]; + +// Slower (function call overhead) +numbers.forEach(n => { + // Process n +}); + +// Faster +for (let i = 0; i < numbers.length; i++) { + const n = numbers[i]; + // Process n +} + +// 5. Set/Map lookup vs Array includes +const arr = [1, 2, 3, 4, 5]; +const set = new Set([1, 2, 3, 4, 5]); + +// Array: O(n) +arr.includes(3); + +// Set: O(1) +set.has(3); + +// For large datasets, use Set/Map for lookups +const largeArray = Array.from({ length: 100000 }, (_, i) => i); +const largeSet = new Set(largeArray); + +// Fast lookup +console.time("array"); +largeArray.includes(99999); +console.timeEnd("array"); // ~5ms + +console.time("set"); +largeSet.has(99999); +console.timeEnd("set"); // ~0ms + +// 6. Array spread vs concat +const a = [1, 2, 3]; +const b = [4, 5, 6]; + +// Modern: spread (slower for very large arrays) +const combined = [...a, ...b]; + +// Traditional: concat (often faster) +const combined2 = a.concat(b); +``` + + +## Common Patterns + +### Pattern 1: Chunking + + +```java !! java +// Java - Chunk into sublists +public static List> chunk(List list, int size) { + List> chunks = new ArrayList<>(); + for (int i = 0; i < list.size(); i += size) { + chunks.add(list.subList(i, Math.min(i + size, list.size()))); + } + return chunks; +} +``` + +```javascript !! js +// JavaScript - Chunk array +function chunk(array, size) { + const chunks = []; + for (let i = 0; i < array.length; i += size) { + chunks.push(array.slice(i, i + size)); + } + return chunks; +} + +const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9]; +console.log(chunk(numbers, 3)); +// [[1, 2, 3], [4, 5, 6], [7, 8, 9]] + +// Functional approach +function chunkFunctional(array, size) { + return Array.from( + { length: Math.ceil(array.length / size) }, + (_, i) => array.slice(i * size, i * size + size) + ); +} +``` + + +### Pattern 2: Flatten + + +```javascript !! js +// Flatten nested arrays +const nested = [1, [2, [3, [4, 5]]]]; + +// Shallow flatten +const flat1 = nested.flat(); +console.log(flat1); // [1, 2, [3, [4, 5]]] + +// Deep flatten (specify depth) +const flat2 = nested.flat(2); +console.log(flat2); // [1, 2, 3, [4, 5]] + +// Infinite flatten +const flat3 = nested.flat(Infinity); +console.log(flat3); // [1, 2, 3, 4, 5] + +// Custom flatten function +function flatten(arr) { + const result = []; + + for (const item of arr) { + if (Array.isArray(item)) { + result.push(...flatten(item)); + } else { + result.push(item); + } + } + + return result; +} +``` + + +## Best Practices + + +```java !! java +// Java: Use appropriate collection type +List list = new ArrayList<>(); // Ordered, allows duplicates +Set set = new HashSet<>(); // Unordered, unique +Map map = new HashMap<>(); // Key-value pairs + +// Choose based on needs: +// - ArrayList: Fast random access +// - LinkedList: Frequent insertions/deletions +// - HashSet: Fast lookups, unique elements +// - TreeSet: Sorted elements +// - HashMap: Fast key lookups +// - TreeMap: Sorted keys +``` + +```javascript !! js +// JavaScript: Choose wisely + +// 1. Use arrays for ordered data +const items = ["apple", "banana", "cherry"]; + +// 2. Use Set for uniqueness +const uniqueItems = new Set(["apple", "banana", "apple"]); + +// 3. Use Map for key-value with non-string keys +const metadata = new Map(); +const element = document.getElementById("myDiv"); +metadata.set(element, { clicks: 0 }); + +// 4. Use object for simple string keys +const config = { + apiUrl: "https://api.example.com", + timeout: 5000, + retries: 3 +}; + +// 5. Prefer non-mutating methods +const numbers = [1, 2, 3, 4, 5]; + +// Good: Returns new array +const doubled = numbers.map(n => n * 2); + +// Be careful: Modifies in place +numbers.reverse(); // Modifies original! + +// To avoid mutation: Create copy first +const reversed = [...numbers].reverse(); + +// 6. Use destructuring for clarity +const [first, second, ...rest] = numbers; + +// 7. Use spread for immutable updates +const state = { count: 0, name: "app" }; +const newState = { ...state, count: 1 }; // Copy with update + +const list = [1, 2, 3]; +const newList = [...list, 4]; // Copy with addition + +// 8. Leverage Set for deduplication +const dupes = [1, 2, 2, 3, 3, 3]; +const unique = [...new Set(dupes)]; +``` + + +## Exercises + +### Exercise 1: Array Manipulation +Implement these operations: +```javascript +const arr = [1, 2, 3, 4, 5]; + +// 1. Add 0 to beginning +// 2. Add 6 to end +// 3. Remove first element +// 4. Remove last element +// 5. Double all elements (non-mutating) +// 6. Filter out even numbers +// 7. Sum all numbers +``` + +### Exercise 2: Set Operations +Create Set utility functions: +```javascript +// Union of two sets +function union(setA, setB) { } + +// Intersection of two sets +function intersection(setA, setB) { } + +// Difference of two sets +function difference(setA, setB) { } +``` + +### Exercise 3: Chunk Array +Split array into chunks of size n: +```javascript +function chunk(array, size) { + // Return array of arrays +} + +chunk([1, 2, 3, 4, 5, 6, 7], 3); +// [[1, 2, 3], [4, 5, 6], [7]] +``` + +### Exercise 4: Map Operations +Create frequency map: +```javascript +function frequencyMap(array) { + // Return Map with element counts +} + +frequencyMap(["apple", "banana", "apple", "cherry", "banana", "apple"]); +// Map {"apple" => 3, "banana" => 2, "cherry" => 1} +``` + +## Summary + +### Key Takeaways + +1. **Arrays:** + - Dynamic size (can grow/shrink) + - Mixed types possible + - Rich set of methods + - Distinguish mutating vs non-mutating + +2. **Destructuring:** + - Unpack values cleanly + - Skip with commas + - Rest operator for remaining + - Default values + +3. **Set:** + - Unique values only + - Fast lookups (O(1)) + - Good for deduplication + - WeakSet for memory management + +4. **Map:** + - Any key type + - Maintains insertion order + - Fast lookups (O(1)) + - WeakMap for metadata + +5. **Performance:** + - Pre-allocate when possible + - Use typed arrays for numbers + - Set/Map for large lookups + - for loops for heavy operations + +### Comparison Table: Java vs JavaScript + +| Feature | Java | JavaScript | +|---------|------|------------| +| **Array type** | Fixed size, single type | Dynamic, mixed types | +| **Growth** | Need ArrayList | Automatic | +| **Set** | HashSet, TreeSet, LinkedHashSet | Set (insertion ordered) | +| **Map** | HashMap, TreeMap, LinkedHashMap | Map (insertion ordered) | +| **Weak refs** | WeakHashMap | WeakSet, WeakMap | +| **Typed arrays** | No | Int8Array, Float64Array, etc. | +| **Destructuring** | No | Yes (ES6+) | + +## What's Next? + +You've mastered JavaScript collections! Next up is **Module 6: Objects**, where we'll explore: + +- Object literals and creation +- Property getters and setters +- Object methods (keys, values, entries) +- Object destructuring +- Spread operator with objects +- Object.freeze and Object.seal + +Ready to dive deeper into JavaScript objects? Let's continue! diff --git a/content/docs/java2js/module-05-arrays-collections.zh-tw.mdx b/content/docs/java2js/module-05-arrays-collections.zh-tw.mdx new file mode 100644 index 0000000..70eab73 --- /dev/null +++ b/content/docs/java2js/module-05-arrays-collections.zh-tw.mdx @@ -0,0 +1,351 @@ +--- +title: "Module 5: Arrays and Collections" +description: "掌握 JavaScript 陣列、Set、Map 和集合方法" +--- + +## Module 5: Arrays and Collections + +JavaScript 的集合類型與 Java 有很大不同。陣列更加靈活,且 JavaScript 提供了 Set 和 Map 等額外的集合類型。讓我們探索這些強大的資料結構。 + +## Learning Objectives + +完成本模組後,你將: +✅ 掌握 JavaScript 陣列的創建和操作 +✅ 理解陣列方法(變異 vs 非變異) +✅ 學習 Set 和 Map 資料結構 +✅ 知道何時使用 WeakSet 和 WeakMap +✅ 理解陣列解構 +✅ 學習效能考量 + +## Array Basics + +JavaScript 陣列更像動態列表,而不是 Java 的固定大小陣列: + + +```java !! java +// Java - 固定大小陣列 +int[] numbers = new int[5]; +numbers[0] = 1; +numbers[1] = 2; +// numbers[5] = 3; // ArrayIndexOutOfBoundsException + +// 陣列初始化器 +int[] moreNumbers = {1, 2, 3, 4, 5}; + +// 大小是固定的 +System.out.println(numbers.length); // 5 + +// 不能改變大小 +// 要添加元素需要使用 ArrayList +``` + +```javascript !! js +// JavaScript - 動態陣列 +const numbers = [1, 2, 3, 4, 5]; + +// 可以添加元素 +numbers.push(6); // 添加到末尾 +numbers.unshift(0); // 添加到開頭 + +// 可以移除元素 +numbers.pop(); // 從末尾移除 +numbers.shift(); // 從開頭移除 + +// 動態長度 +console.log(numbers.length); // 5 (push/pop 之後) + +// 混合類型(不推薦但可能) +const mixed = [1, "hello", true, { id: 1 }, [1, 2, 3]]; + +// 稀疏陣列 +const sparse = [1, , , 4]; // 陣列中有空隙 +console.log(sparse.length); // 4 +console.log(sparse[1]); // undefined + +// 陣列建構函數(避免 - 使用字面語法) +const arr = new Array(5); // 創建有 5 個空槽的陣列 +const arr2 = new Array(1, 2, 3); // 創建 [1, 2, 3] +// 更好:const arr = [1, 2, 3]; +``` + + +## Array Methods - Mutating vs Non-Mutating + +JavaScript 區分修改陣列的方法和創建新陣列的方法: + +### Mutating Methods + + +```java !! java +// Java - 陣列沒有方法 +// 使用 ArrayList 或工具類 +List list = new ArrayList<>(Arrays.asList("a", "b", "c")); +list.add("d"); // 添加到末尾 +list.add(0, "start"); // 在索引處添加 +list.remove(0); // 移除索引處的元素 +list.set(0, "x"); // 替換索引處的元素 +``` + +```javascript !! js +// JavaScript - 變異方法 + +const arr = [1, 2, 3, 4, 5]; + +// push: 添加到末尾 +arr.push(6); +console.log(arr); // [1, 2, 3, 4, 5, 6] + +// pop: 從末尾移除 +arr.pop(); +console.log(arr); // [1, 2, 3, 4, 5] + +// unshift: 添加到開頭 +arr.unshift(0); +console.log(arr); // [0, 1, 2, 3, 4, 5] + +// shift: 從開頭移除 +arr.shift(); +console.log(arr); // [1, 2, 3, 4, 5] + +// splice: 在任何位置移除/添加 +arr.splice(2, 1); // 在索引 2 處移除 1 個元素 +console.log(arr); // [1, 2, 4, 5] + +arr.splice(2, 0, 3); // 在索引 2 處插入 3 +console.log(arr); // [1, 2, 3, 4, 5] + +// sort: 原地排序 +arr.sort((a, b) => a - b); +console.log(arr); // [1, 2, 3, 4, 5] + +// reverse: 原地反轉 +arr.reverse(); +console.log(arr); // [5, 4, 3, 2, 1] + +// fill: 用值填充 +arr.fill(0); +console.log(arr); // [0, 0, 0, 0, 0] +``` + + +### Non-Mutating Methods + + +```java !! java +// Java - Streams 創建新集合 +List numbers = Arrays.asList(1, 2, 3, 4, 5); + +List doubled = numbers.stream() + .map(n -> n * 2) + .collect(Collectors.toList()); + +List evens = numbers.stream() + .filter(n -> n % 2 == 0) + .collect(Collectors.toList()); + +// 原始列表未改變 +``` + +```javascript !! js +// JavaScript - 非變異方法 +const numbers = [1, 2, 3, 4, 5]; + +// map: 轉換為新陣列 +const doubled = numbers.map(n => n * 2); +console.log(doubled); // [2, 4, 6, 8, 10] +console.log(numbers); // [1, 2, 3, 4, 5] (未改變) + +// filter: 選擇元素 +const evens = numbers.filter(n => n % 2 === 0); +console.log(evens); // [2, 4] +console.log(numbers); // [1, 2, 3, 4, 5] (未改變) + +// slice: 提取部分 +const slice = numbers.slice(1, 4); +console.log(slice); // [2, 3, 4] + +// concat: 合併陣列 +const more = [6, 7, 8]; +const combined = numbers.concat(more); +console.log(combined); // [1, 2, 3, 4, 5, 6, 7, 8] + +// join: 轉換為字串 +const str = numbers.join("-"); +console.log(str); // "1-2-3-4-5" + +// flat: 展平嵌套陣列 +const nested = [1, [2, [3, [4]]]]; +const flat = nested.flat(2); +console.log(flat); // [1, 2, 3, [4]] +``` + + +## Set and Map + +ES2015 引入了 Set 和 Map 集合類型: + + +```java !! java +// Java - HashSet 和 HashMap +Set set = new HashSet<>(); +set.add("apple"); +set.add("banana"); +set.add("apple"); // Duplicate ignored + +Map map = new HashMap<>(); +map.put("Alice", 25); +map.put("Bob", 30); +int age = map.get("Alice"); // 25 +``` + +```javascript !! js +// JavaScript - Set +const set = new Set(); +set.add("apple"); +set.add("banana"); +set.add("apple"); // 重複被忽略 + +console.log(set); // Set { "apple", "banana" } +console.log(set.has("apple")); // true +console.log(set.size); // 2 + +// 從陣列創建 Set +const numbers = [1, 2, 2, 3, 3, 3]; +const unique = new Set(numbers); +console.log(unique); // Set { 1, 2, 3 } + +// Map +const map = new Map(); +map.set("Alice", 25); +map.set("Bob", 30); + +console.log(map.get("Alice")); // 25 +console.log(map.has("Bob")); // true +console.log(map.size); // 2 + +// Map 可以使用任何類型的鍵 +const key1 = { id: 1 }; +const key2 = { id: 2 }; +map.set(key1, "Value 1"); +map.set(key2, "Value 2"); + +// 物件 vs Map +const obj = {}; +const keyObj = { id: 1 }; +obj[keyObj] = "value"; // 鍵變為 "[object Object]" +console.log(obj["[object Object]"]); // "value" + +// Map 保留鍵的類型 +const map2 = new Map(); +map2.set(keyObj, "value"); +console.log(map2.get(keyObj)); // "value" +``` + + +## Array Destructuring + + +```java !! java +// Java - Manual extraction +int[] arr = {1, 2, 3, 4, 5}; +int first = arr[0]; +int second = arr[1]; +int third = arr[2]; + +// Java 21+ has pattern matching(limited) +Object obj = arr; +if (obj instanceof int[] arr2) { + System.out.println(arr2[0]); +} +``` + +```javascript !! js +// JavaScript - Destructuring +const arr = [1, 2, 3, 4, 5]; + +// Basic destructuring +const [first, second, third] = arr; +console.log(first, second, third); // 1 2 3 + +// Skip elements +const [a, , b] = arr; // Skip second element +console.log(a, b); // 1 3 + +// Rest operator +const [head, ...tail] = arr; +console.log(head); // 1 +console.log(tail); // [2, 3, 4, 5] + +// Default values +const [x, y, z = 30] = [10, 20]; +console.log(x, y, z); // 10 20 30 + +// Swap variables(without temp) +let m = 1, n = 2; +[m, n] = [n, m]; +console.log(m, n); // 2 1 + +// Destructuring function return +function getStats() { + return [10, 20, 30]; +} + +const [min, avg, max] = getStats(); +console.log(min, avg, max); // 10 20 30 + +// Ignore some values +const [, , , fourth] = arr; +console.log(fourth); // 4 +``` + + +## Best Practices + + +```javascript !! js +// 1. Use non-mutating methods for immutability +const original = [1, 2, 3]; +const modified = [...original, 4]; // Add without mutation +const filtered = original.filter(n => n > 1); // Filter without mutation + +// 2. Use Set for unique values +function unique(array) { + return [...new Set(array)]; +} + +// 3. Use Map when keys are not strings +const cache = new Map(); +cache.set(objKey, value); // Works with object keys + +// 4. Use spread operator for copying +const copy = [...original]; + +// 5. Avoid mutating arrays in loops +// Bad +for (const item of arr) { + arr.push(transform(item)); // Infinite loop! +} + +// Good +const result = []; +for (const item of arr) { + result.push(transform(item)); +} + +// Or better +const result = arr.map(transform); +``` + + +## Summary + +### Key Takeaways + +1. **Arrays:** 動態大小,混合類型 +2. **Methods:** 區分變異和非變異 +3. **Set/Map:** 現代集合類型 +4. **Destructuring:** 簡潔的提取語法 + +## What's Next? + +接下來是 **Module 6: Objects** - 深入了解 JavaScript 物件! diff --git a/content/docs/java2js/module-06-objects.mdx b/content/docs/java2js/module-06-objects.mdx new file mode 100644 index 0000000..0325f03 --- /dev/null +++ b/content/docs/java2js/module-06-objects.mdx @@ -0,0 +1,969 @@ +--- +title: "Module 6: Objects" +description: "Master JavaScript object literals, methods, and manipulation techniques" +--- + +## Module 6: Objects + +JavaScript objects are quite different from Java objects. They're dynamic, mutable collections of properties. Let's explore how to work with objects effectively. + +## Learning Objectives + +By the end of this module, you will: +✅ Understand object literals and creation +✅ Master property access (dot vs bracket notation) +✅ Learn object methods and computed properties +✅ Understand property descriptors and attributes +✅ Know how to clone and merge objects +✅ Master object destructuring + +## Object Literals + +JavaScript objects are created using literal syntax: + + +```java !! java +// Java - Classes required +public class User { + private String name; + private int age; + + public User(String name, int age) { + this.name = name; + this.age = age; + } + + public String getName() { return name; } + public int getAge() { return age; } +} + +User user = new User("John", 25); +``` + +```javascript !! js +// JavaScript - Object literals (simple and direct) +const user = { + name: "John", + age: 25 +}; + +console.log(user.name); // "John" +console.log(user.age); // 25 + +// Can add properties anytime +user.email = "john@example.com"; +user.isActive = true; + +// Can delete properties +delete user.isActive; + +// Empty object +const empty = {}; + +// Nested objects +const person = { + name: "John", + address: { + street: "123 Main St", + city: "NYC", + country: "USA" + } +}; + +console.log(person.address.city); // "NYC" +``` + + +### Property Access + + +```java !! java +// Java - Direct field access or getters/setters +User user = new User("John", 25); +String name = user.getName(); // Getter +user.setAge(26); // Setter + +// Direct field (if public) +user.name = "Jane"; +``` + +```javascript !! js +// JavaScript - Dot notation vs bracket notation +const user = { + name: "John", + age: 25, + "first name": "John", // Property with space + "user-email": "john@example.com" // Invalid as identifier +}; + +// Dot notation (most common) +console.log(user.name); // "John" + +// Bracket notation (for dynamic keys or special characters) +console.log(user["first name"]); // "John" +console.log(user["user-email"]); // "john@example.com" + +// Bracket notation with variables +const key = "age"; +console.log(user[key]); // 25 + +// Dynamic property access +function getProperty(obj, prop) { + return obj[prop]; +} + +console.log(getProperty(user, "name")); // "John" + +// Nested access +const config = { + server: { + port: 3000, + host: "localhost" + } +}; + +const serverKey = "server"; +const portKey = "port"; +console.log(config[serverKey][portKey]); // 3000 + +// Optional chaining (ES2020) +const data = { + user: { + address: { + city: "NYC" + } + } +}; + +console.log(data.user?.address?.city); // "NYC" +console.log(data.user?.phone?.number); // undefined (no error) +console.log(data.nonExistent?.property); // undefined +``` + + +### Computed Properties + + +```java !! java +// Java - Dynamic properties not typical +// Would use Map +Map data = new HashMap<>(); +data.put("field_" + 1, "value1"); +data.put("field_" + 2, "value2"); +``` + +```javascript !! js +// JavaScript - Computed property names (ES6+) +const prefix = "user"; +const id = 1; + +const user = { + [`${prefix}_${id}`]: "John", + [`${prefix}_${id + 1}`]: "Jane" +}; + +console.log(user.user_1); // "John" +console.log(user.user_2); // "Jane" + +// Dynamic method names +const methodName = "greet"; +const calc = { + [methodName]() { + return "Hello!"; + }, + ["calc_" + "sum"](a, b) { + return a + b; + } +}; + +console.log(calc.greet()); // "Hello!" +console.log(calc.calc_sum(5, 3)); // 8 + +// Practical: Create getters/setters dynamically +function createAccessor(propertyName) { + return { + get [propertyName]() { + return this[`_${propertyName}`]; + }, + set [propertyName](value) { + this[`_${propertyName}`] = value; + } + }; +} + +const user = Object.assign( + { _name: "John" }, + createAccessor("name") +); + +console.log(user.name); // "John" +user.name = "Jane"; +console.log(user.name); // "Jane" +``` + + +## Object Methods + +JavaScript provides several methods for working with objects: + + +```java !! java +// Java - Reflection +import java.lang.reflect.Field; + +User user = new User("John", 25); +Class clazz = user.getClass(); +Field[] fields = clazz.getDeclaredFields(); + +for (Field field : fields) { + field.setAccessible(true); + System.out.println(field.getName() + ": " + field.get(user)); +} +``` + +```javascript !! js +// JavaScript - Object methods + +const user = { + name: "John", + age: 25, + email: "john@example.com" +}; + +// Object.keys(): Get property names +const keys = Object.keys(user); +console.log(keys); // ["name", "age", "email"] + +// Object.values(): Get property values +const values = Object.values(user); +console.log(values); // ["John", 25, "john@example.com"] + +// Object.entries(): Get [key, value] pairs +const entries = Object.entries(user); +console.log(entries); +// [["name", "John"], ["age", 25], ["email", "john@example.com"]] + +// Convert entries back to object +const fromEntries = Object.fromEntries(entries); +console.log(fromEntries); // { name: "John", age: 25, email: "john@example.com" } + +// Object.assign(): Copy properties +const target = { a: 1, b: 2 }; +const source = { b: 3, c: 4 }; +const merged = Object.assign(target, source); +console.log(merged); // { a: 1, b: 3, c: 4 } +// Note: target is modified! + +// Safer: Spread operator +const merged2 = { ...target, ...source }; +console.log(merged2); // { a: 1, b: 3, c: 4 } +// target is unchanged + +// Object.freeze(): Make immutable +const frozen = Object.freeze({ name: "John" }); +frozen.name = "Jane"; // Silently fails (strict mode: error) +console.log(frozen.name); // "John" + +// Object.seal(): Prevent adding/removing properties +const sealed = Object.seal({ name: "John" }); +sealed.name = "Jane"; // Allowed +sealed.age = 25; // Fails +console.log(sealed); // { name: "Jane" } + +// Check state +console.log(Object.isFrozen(frozen)); // true +console.log(Object.isSealed(sealed)); // true +``` + + +### Property Descriptors + + +```java !! java +// Java - No direct equivalent +// Properties are determined by fields and methods +``` + +```javascript !! js +// JavaScript - Fine-grained property control + +const user = { name: "John" }; + +// Get property descriptor +const descriptor = Object.getOwnPropertyDescriptor(user, "name"); +console.log(descriptor); +// { +// value: "John", +// writable: true, +// enumerable: true, +// configurable: true +// } + +// Define property with descriptor +const person = {}; +Object.defineProperty(person, "name", { + value: "John", + writable: false, // Cannot be changed + enumerable: true, // Shows up in loops + configurable: false // Cannot be deleted or reconfigured +}); + +person.name = "Jane"; // Silently fails +console.log(person.name); // "John" + +// Getters and setters +const user2 = { + _firstName: "John", + _lastName: "Doe", + + get firstName() { + return this._firstName; + }, + + set firstName(value) { + this._firstName = value; + }, + + get fullName() { + return `${this._firstName} ${this._lastName}`; + } +}; + +console.log(user2.firstName); // "John" +user2.firstName = "Jane"; +console.log(user2.fullName); // "Jane Doe" + +// Define properties with getters/setters +const account = { + _balance: 0, + + get balance() { + return this._balance; + }, + + set balance(value) { + if (value >= 0) { + this._balance = value; + } + } +}; + +account.balance = 100; +console.log(account.balance); // 100 + +account.balance = -50; // Validation fails +console.log(account.balance); // 100 (unchanged) + +// Multiple properties +Object.defineProperties(user, { + firstName: { + value: "John", + writable: true + }, + lastName: { + value: "Doe", + writable: true + }, + fullName: { + get() { + return `${this.firstName} ${this.lastName}`; + } + } +}); +``` + + +## Object Destructuring + +Destructuring unpacks properties into variables: + + +```java !! java +// Java - Manual extraction +User user = new User("John", 25); +String name = user.getName(); +int age = user.getAge(); + +// Or use objects/tuples (Java 16+) +record User(String name, int age) {} +User user = new User("John", 25); +String name = user.name(); +int age = user.age(); +``` + +```javascript !! js +// JavaScript - Object destructuring + +const user = { + name: "John", + age: 25, + email: "john@example.com", + city: "NYC" +}; + +// Basic destructuring +const { name, age } = user; +console.log(name); // "John" +console.log(age); // 25 + +// Different variable names +const { name: userName, age: userAge } = user; +console.log(userName); // "John" + +// Default values +const { name: n, country = "USA" } = user; +console.log(country); // "USA" (user didn't have country) + +// Nested destructuring +const data = { + user: { + name: "John", + address: { + city: "NYC", + country: "USA" + } + } +}; + +const { user: { name, address: { city } } } = data; +console.log(name); // "John" +console.log(city); // "NYC" + +// Rest operator +const { name, ...rest } = user; +console.log(name); // "John" +console.log(rest); // { age: 25, email: "john@example.com", city: "NYC" } + +// Destructuring in function parameters +function greet({ name, age = 30 }) { + console.log(`Hello ${name}, you are ${age}`); +} + +greet(user); // "Hello John, you are 25" + +// Destructuring with arrays +const users = [ + { id: 1, name: "Alice" }, + { id: 2, name: "Bob" } +]; + +const [{ id: firstId }, { name: secondName }] = users; +console.log(firstId); // 1 +console.log(secondName); // "Bob" + +// Practical: API response +function processUser({ data: { user: { name, email }, meta } }) { + console.log(name, email, meta); +} + +processUser({ + data: { + user: { name: "John", email: "john@example.com" }, + meta: { timestamp: 1234567890 } + } +}); +``` + + +## Object Cloning and Merging + + +```java !! java +// Java - Clone not straightforward +// Need to implement Cloneable or use copy constructors + +public class User implements Cloneable { + private String name; + private int age; + + public User(User other) { + this.name = other.name; + this.age = other.age; + } + + @Override + protected Object clone() throws CloneNotSupportedException { + return super.clone(); + } +} + +User original = new User("John", 25); +User copy = new User(original); +``` + +```javascript !! js +// JavaScript - Multiple ways to clone + +// Shallow clone with spread +const original = { name: "John", age: 25 }; +const clone = { ...original }; + +clone.name = "Jane"; +console.log(original.name); // "John" (unchanged) + +// Object.assign() for shallow clone +const clone2 = Object.assign({}, original); + +// ⚠️ Shallow clone issue with nested objects +const nested = { + user: { + name: "John", + address: { + city: "NYC" + } + } +}; + +const shallow = { ...nested }; +shallow.user.name = "Jane"; +shallow.user.address.city = "LA"; + +console.log(nested.user.name); // "Jane" (changed!) +console.log(nested.user.address.city); // "LA" (changed!) + +// Deep clone with JSON +const deep1 = JSON.parse(JSON.stringify(nested)); +deep1.user.name = "Alice"; +console.log(nested.user.name); // "Jane" (unchanged) + +// Deep clone with structuredClone (modern) +const deep2 = structuredClone(nested); + +// Deep clone utility +function deepClone(obj) { + if (obj === null || typeof obj !== "object") { + return obj; + } + + if (obj instanceof Date) { + return new Date(obj); + } + + if (obj instanceof Array) { + return obj.map(item => deepClone(item)); + } + + const cloned = {}; + for (const key in obj) { + if (obj.hasOwnProperty(key)) { + cloned[key] = deepClone(obj[key]); + } + } + return cloned; +} + +// Merging objects +const defaults = { + theme: "light", + language: "en", + timeout: 5000 +}; + +const userConfig = { + language: "fr", + timeout: 10000 +}; + +// Merge (userConfig overrides defaults) +const config = { ...defaults, ...userConfig }; +console.log(config); +// { theme: "light", language: "fr", timeout: 10000 } + +// Deep merge utility +function deepMerge(target, ...sources) { + if (!sources.length) return target; + const source = sources.shift(); + + if (isObject(target) && isObject(source)) { + for (const key in source) { + if (isObject(source[key])) { + if (!target[key]) Object.assign(target, { [key]: {} }); + deepMerge(target[key], source[key]); + } else { + Object.assign(target, { [key]: source[key] }); + } + } + } + return deepMerge(target, ...sources); +} + +function isObject(item) { + return item && typeof item === "object" && !Array.isArray(item); +} + +const merged = deepMerge( + { user: { name: "John", settings: { theme: "light" } } }, + { user: { settings: { language: "en" } } } +); +console.log(merged); +// { user: { name: "John", settings: { theme: "light", language: "en" } } } +``` + + +## This Context in Objects + + +```java !! java +// Java - 'this' always refers to current instance +public class Counter { + private int count = 0; + + public void increment() { + this.count++; // 'this' is the Counter instance + } +} +``` + +```javascript !! js +// JavaScript - 'this' depends on call site + +const user = { + name: "John", + age: 25, + + greet() { + console.log(`Hello, I'm ${this.name}`); + }, + + // Arrow function: 'this' from surrounding scope + greetArrow: () => { + console.log(`Hello, I'm ${this.name}`); // undefined! + }, + + // Nested function issue + nestedGreet() { + // 'this' works here + console.log(this.name); + + setTimeout(function() { + // 'this' is lost here! + console.log(this.name); // undefined + }, 1000); + + // Solution 1: Arrow function + setTimeout(() => { + console.log(this.name); // "John" - works! + }, 1000); + + // Solution 2: Bind + setTimeout(function() { + console.log(this.name); // "John" + }.bind(this), 1000); + + // Solution 3: Capture this + const self = this; + setTimeout(function() { + console.log(self.name); // "John" + }, 1000); + } +}; + +user.greet(); // "Hello, I'm John" +user.greetArrow(); // "Hello, I'm undefined" + +// 'this' depends on call site +const greet = user.greet; +greet(); // "Hello, I'm undefined" (not called as method) + +// Explicit this binding +const greet2 = user.greet.bind(user); +greet2(); // "Hello, I'm John" + +// Call and apply +user.greet.call({ name: "Jane" }); // "Hello, I'm Jane" +user.greet.apply({ name: "Bob" }); // "Hello, I'm Bob" +``` + + +## Common Patterns + +### Pattern 1: Factory Functions + + +```java !! java +// Java - Static factory methods +public class User { + private String name; + private int age; + + public static User create(String name, int age) { + return new User(name, age); + } + + public static User fromJson(String json) { + // Parse and create + } +} +``` + +```javascript !! js +// JavaScript - Factory functions (simple, no classes needed) + +function createUser(name, age) { + return { + name, + age, + greet() { + console.log(`Hi, I'm ${this.name}`); + }, + incrementAge() { + this.age++; + return this; // Chaining + } + }; +} + +const user = createUser("John", 25); +user.greet(); // "Hi, I'm John" +user.incrementAge().greet(); // Chaining + +// Factory with closures (private data) +function createCounter() { + let count = 0; // Private + + return { + increment() { + count++; + return this; + }, + getCount() { + return count; + }, + reset() { + count = 0; + return this; + } + }; +} + +const counter = createCounter(); +console.log(counter.increment().increment().getCount()); // 2 +console.log(counter.count); // undefined (truly private) +``` + + +### Pattern 2: Options Object Pattern + + +```javascript !! js +// Function with many parameters (bad) +function createUser(name, age, email, active, role, permissions) { + // Hard to remember parameter order + // Can't skip optional parameters +} + +// Options object pattern (good) +function createUser(options) { + const defaults = { + active: true, + role: "user", + permissions: [] + }; + + const config = { ...defaults, ...options }; + + return { + ...config, + createdAt: new Date() + }; +} + +const user = createUser({ + name: "John", + age: 25, + role: "admin" +}); + +console.log(user); +// { +// name: "John", +// age: 25, +// active: true, // from defaults +// role: "admin", +// permissions: [], // from defaults +// createdAt: Date +// } + +// With destructuring +function createUser2({ name, age, active = true, role = "user" } = {}) { + return { name, age, active, role, createdAt: new Date() }; +} + +const user2 = createUser2({ name: "Jane", age: 30 }); +console.log(user2); +``` + + +## Best Practices + + +```java !! java +// Java: Use classes, encapsulation +public class User { + private String name; + private int age; + + public User(String name, int age) { + this.name = name; + this.age = age; + } + + public String getName() { return name; } + public void setName(String name) { this.name = name; } + + // Getters/setters for all fields +} +``` + +```javascript !! js +// JavaScript: Embrace object literals + +// 1. Use object literals for simple data +const user = { + name: "John", + age: 25 +}; + +// 2. Use computed properties for dynamic keys +const key = "user_" + Date.now(); +const users = { + [key]: { name: "John" } +}; + +// 3. Use method shorthand +const calculator = { + add(a, b) { // Not: add: function(a, b) + return a + b; + }, + + subtract(a, b) { + return a - b; + } +}; + +// 4. Use destructuring for clean property access +const { name, age } = getUser(); + +// 5. Use spread for immutable updates +const updatedUser = { ...user, age: 26 }; + +// 6. Use Object.freeze for constants +const CONFIG = Object.freeze({ + apiUrl: "https://api.example.com", + timeout: 5000 +}); + +// 7. Use optional chaining for safe access +const city = user?.address?.city ?? "Unknown"; + +// 8. Avoid prototypes with object literals +// (we'll cover classes in module 7) +``` + + +## Exercises + +### Exercise 1: Object Manipulation +```javascript +const user = { name: "John", age: 25, email: "john@example.com" }; + +// 1. Add phone property +// 2. Remove email property +// 3. Update age to 26 +// 4. Clone the object +// 5. Get all property names +// 6. Get all property values +``` + +### Exercise 2: Destructuring +```javascript +const config = { + server: { + host: "localhost", + port: 3000 + }, + database: { + name: "mydb", + port: 5432 + } +}; + +// Extract: serverHost, serverPort, dbName +``` + +### Exercise 3: Merge Objects +```javascript +const defaults = { theme: "light", lang: "en", timeout: 5000 }; +const userSettings = { lang: "fr", notifications: true }; + +// Merge with userSettings overriding defaults +``` + +### Exercise 4: Factory Function +```javascript +// Create a factory function that makes rectangle objects +// with: width, height, area() method, perimeter() method +function createRectangle(width, height) { + // Return object with methods +} +``` + +## Summary + +### Key Takeaways + +1. **Object Literals:** + - Simple, direct syntax + - Can add/remove properties anytime + - Supports nested structures + +2. **Property Access:** + - Dot notation for known keys + - Bracket notation for dynamic/special keys + - Optional chaining for safe access + +3. **Object Methods:** + - `Object.keys()`: Get property names + - `Object.values()`: Get property values + - `Object.entries()`: Get key-value pairs + - `Object.assign()`: Merge objects + +4. **Destructuring:** + - Extract properties cleanly + - Support for default values + - Renaming and nested destructuring + +5. **Cloning:** + - Spread for shallow clone + - `structuredClone()` for deep clone + - Watch out for nested objects + +6. **This Context:** + - Depends on call site + - Arrow functions capture surrounding this + - Use `bind()` for explicit binding + +### Comparison Table: Java vs JavaScript + +| Feature | Java | JavaScript | +|---------|------|------------| +| **Creation** | `new Class()` | Literal `{}` | +| **Properties** | Fixed (fields) | Dynamic | +| **Methods** | Class methods | Object functions | +| **Access** | Direct or getters/setters | Dot or bracket notation | +| **This** | Always current instance | Depends on call site | +| **Cloning** | Manual or Cloneable | Spread or structuredClone | +| **Immutable** | `final` fields | `Object.freeze()` | + +## What's Next? + +You've mastered JavaScript objects! Next up is **Module 7: Classes and Inheritance**, where we'll explore: + +- ES6 class syntax +- Constructors and methods +- Inheritance with `extends` +- Super keyword +- Static methods and properties +- Private fields (ES2022) + +Ready to explore JavaScript's class system? Let's continue! diff --git a/content/docs/java2js/module-06-objects.zh-cn.mdx b/content/docs/java2js/module-06-objects.zh-cn.mdx new file mode 100644 index 0000000..0325f03 --- /dev/null +++ b/content/docs/java2js/module-06-objects.zh-cn.mdx @@ -0,0 +1,969 @@ +--- +title: "Module 6: Objects" +description: "Master JavaScript object literals, methods, and manipulation techniques" +--- + +## Module 6: Objects + +JavaScript objects are quite different from Java objects. They're dynamic, mutable collections of properties. Let's explore how to work with objects effectively. + +## Learning Objectives + +By the end of this module, you will: +✅ Understand object literals and creation +✅ Master property access (dot vs bracket notation) +✅ Learn object methods and computed properties +✅ Understand property descriptors and attributes +✅ Know how to clone and merge objects +✅ Master object destructuring + +## Object Literals + +JavaScript objects are created using literal syntax: + + +```java !! java +// Java - Classes required +public class User { + private String name; + private int age; + + public User(String name, int age) { + this.name = name; + this.age = age; + } + + public String getName() { return name; } + public int getAge() { return age; } +} + +User user = new User("John", 25); +``` + +```javascript !! js +// JavaScript - Object literals (simple and direct) +const user = { + name: "John", + age: 25 +}; + +console.log(user.name); // "John" +console.log(user.age); // 25 + +// Can add properties anytime +user.email = "john@example.com"; +user.isActive = true; + +// Can delete properties +delete user.isActive; + +// Empty object +const empty = {}; + +// Nested objects +const person = { + name: "John", + address: { + street: "123 Main St", + city: "NYC", + country: "USA" + } +}; + +console.log(person.address.city); // "NYC" +``` + + +### Property Access + + +```java !! java +// Java - Direct field access or getters/setters +User user = new User("John", 25); +String name = user.getName(); // Getter +user.setAge(26); // Setter + +// Direct field (if public) +user.name = "Jane"; +``` + +```javascript !! js +// JavaScript - Dot notation vs bracket notation +const user = { + name: "John", + age: 25, + "first name": "John", // Property with space + "user-email": "john@example.com" // Invalid as identifier +}; + +// Dot notation (most common) +console.log(user.name); // "John" + +// Bracket notation (for dynamic keys or special characters) +console.log(user["first name"]); // "John" +console.log(user["user-email"]); // "john@example.com" + +// Bracket notation with variables +const key = "age"; +console.log(user[key]); // 25 + +// Dynamic property access +function getProperty(obj, prop) { + return obj[prop]; +} + +console.log(getProperty(user, "name")); // "John" + +// Nested access +const config = { + server: { + port: 3000, + host: "localhost" + } +}; + +const serverKey = "server"; +const portKey = "port"; +console.log(config[serverKey][portKey]); // 3000 + +// Optional chaining (ES2020) +const data = { + user: { + address: { + city: "NYC" + } + } +}; + +console.log(data.user?.address?.city); // "NYC" +console.log(data.user?.phone?.number); // undefined (no error) +console.log(data.nonExistent?.property); // undefined +``` + + +### Computed Properties + + +```java !! java +// Java - Dynamic properties not typical +// Would use Map +Map data = new HashMap<>(); +data.put("field_" + 1, "value1"); +data.put("field_" + 2, "value2"); +``` + +```javascript !! js +// JavaScript - Computed property names (ES6+) +const prefix = "user"; +const id = 1; + +const user = { + [`${prefix}_${id}`]: "John", + [`${prefix}_${id + 1}`]: "Jane" +}; + +console.log(user.user_1); // "John" +console.log(user.user_2); // "Jane" + +// Dynamic method names +const methodName = "greet"; +const calc = { + [methodName]() { + return "Hello!"; + }, + ["calc_" + "sum"](a, b) { + return a + b; + } +}; + +console.log(calc.greet()); // "Hello!" +console.log(calc.calc_sum(5, 3)); // 8 + +// Practical: Create getters/setters dynamically +function createAccessor(propertyName) { + return { + get [propertyName]() { + return this[`_${propertyName}`]; + }, + set [propertyName](value) { + this[`_${propertyName}`] = value; + } + }; +} + +const user = Object.assign( + { _name: "John" }, + createAccessor("name") +); + +console.log(user.name); // "John" +user.name = "Jane"; +console.log(user.name); // "Jane" +``` + + +## Object Methods + +JavaScript provides several methods for working with objects: + + +```java !! java +// Java - Reflection +import java.lang.reflect.Field; + +User user = new User("John", 25); +Class clazz = user.getClass(); +Field[] fields = clazz.getDeclaredFields(); + +for (Field field : fields) { + field.setAccessible(true); + System.out.println(field.getName() + ": " + field.get(user)); +} +``` + +```javascript !! js +// JavaScript - Object methods + +const user = { + name: "John", + age: 25, + email: "john@example.com" +}; + +// Object.keys(): Get property names +const keys = Object.keys(user); +console.log(keys); // ["name", "age", "email"] + +// Object.values(): Get property values +const values = Object.values(user); +console.log(values); // ["John", 25, "john@example.com"] + +// Object.entries(): Get [key, value] pairs +const entries = Object.entries(user); +console.log(entries); +// [["name", "John"], ["age", 25], ["email", "john@example.com"]] + +// Convert entries back to object +const fromEntries = Object.fromEntries(entries); +console.log(fromEntries); // { name: "John", age: 25, email: "john@example.com" } + +// Object.assign(): Copy properties +const target = { a: 1, b: 2 }; +const source = { b: 3, c: 4 }; +const merged = Object.assign(target, source); +console.log(merged); // { a: 1, b: 3, c: 4 } +// Note: target is modified! + +// Safer: Spread operator +const merged2 = { ...target, ...source }; +console.log(merged2); // { a: 1, b: 3, c: 4 } +// target is unchanged + +// Object.freeze(): Make immutable +const frozen = Object.freeze({ name: "John" }); +frozen.name = "Jane"; // Silently fails (strict mode: error) +console.log(frozen.name); // "John" + +// Object.seal(): Prevent adding/removing properties +const sealed = Object.seal({ name: "John" }); +sealed.name = "Jane"; // Allowed +sealed.age = 25; // Fails +console.log(sealed); // { name: "Jane" } + +// Check state +console.log(Object.isFrozen(frozen)); // true +console.log(Object.isSealed(sealed)); // true +``` + + +### Property Descriptors + + +```java !! java +// Java - No direct equivalent +// Properties are determined by fields and methods +``` + +```javascript !! js +// JavaScript - Fine-grained property control + +const user = { name: "John" }; + +// Get property descriptor +const descriptor = Object.getOwnPropertyDescriptor(user, "name"); +console.log(descriptor); +// { +// value: "John", +// writable: true, +// enumerable: true, +// configurable: true +// } + +// Define property with descriptor +const person = {}; +Object.defineProperty(person, "name", { + value: "John", + writable: false, // Cannot be changed + enumerable: true, // Shows up in loops + configurable: false // Cannot be deleted or reconfigured +}); + +person.name = "Jane"; // Silently fails +console.log(person.name); // "John" + +// Getters and setters +const user2 = { + _firstName: "John", + _lastName: "Doe", + + get firstName() { + return this._firstName; + }, + + set firstName(value) { + this._firstName = value; + }, + + get fullName() { + return `${this._firstName} ${this._lastName}`; + } +}; + +console.log(user2.firstName); // "John" +user2.firstName = "Jane"; +console.log(user2.fullName); // "Jane Doe" + +// Define properties with getters/setters +const account = { + _balance: 0, + + get balance() { + return this._balance; + }, + + set balance(value) { + if (value >= 0) { + this._balance = value; + } + } +}; + +account.balance = 100; +console.log(account.balance); // 100 + +account.balance = -50; // Validation fails +console.log(account.balance); // 100 (unchanged) + +// Multiple properties +Object.defineProperties(user, { + firstName: { + value: "John", + writable: true + }, + lastName: { + value: "Doe", + writable: true + }, + fullName: { + get() { + return `${this.firstName} ${this.lastName}`; + } + } +}); +``` + + +## Object Destructuring + +Destructuring unpacks properties into variables: + + +```java !! java +// Java - Manual extraction +User user = new User("John", 25); +String name = user.getName(); +int age = user.getAge(); + +// Or use objects/tuples (Java 16+) +record User(String name, int age) {} +User user = new User("John", 25); +String name = user.name(); +int age = user.age(); +``` + +```javascript !! js +// JavaScript - Object destructuring + +const user = { + name: "John", + age: 25, + email: "john@example.com", + city: "NYC" +}; + +// Basic destructuring +const { name, age } = user; +console.log(name); // "John" +console.log(age); // 25 + +// Different variable names +const { name: userName, age: userAge } = user; +console.log(userName); // "John" + +// Default values +const { name: n, country = "USA" } = user; +console.log(country); // "USA" (user didn't have country) + +// Nested destructuring +const data = { + user: { + name: "John", + address: { + city: "NYC", + country: "USA" + } + } +}; + +const { user: { name, address: { city } } } = data; +console.log(name); // "John" +console.log(city); // "NYC" + +// Rest operator +const { name, ...rest } = user; +console.log(name); // "John" +console.log(rest); // { age: 25, email: "john@example.com", city: "NYC" } + +// Destructuring in function parameters +function greet({ name, age = 30 }) { + console.log(`Hello ${name}, you are ${age}`); +} + +greet(user); // "Hello John, you are 25" + +// Destructuring with arrays +const users = [ + { id: 1, name: "Alice" }, + { id: 2, name: "Bob" } +]; + +const [{ id: firstId }, { name: secondName }] = users; +console.log(firstId); // 1 +console.log(secondName); // "Bob" + +// Practical: API response +function processUser({ data: { user: { name, email }, meta } }) { + console.log(name, email, meta); +} + +processUser({ + data: { + user: { name: "John", email: "john@example.com" }, + meta: { timestamp: 1234567890 } + } +}); +``` + + +## Object Cloning and Merging + + +```java !! java +// Java - Clone not straightforward +// Need to implement Cloneable or use copy constructors + +public class User implements Cloneable { + private String name; + private int age; + + public User(User other) { + this.name = other.name; + this.age = other.age; + } + + @Override + protected Object clone() throws CloneNotSupportedException { + return super.clone(); + } +} + +User original = new User("John", 25); +User copy = new User(original); +``` + +```javascript !! js +// JavaScript - Multiple ways to clone + +// Shallow clone with spread +const original = { name: "John", age: 25 }; +const clone = { ...original }; + +clone.name = "Jane"; +console.log(original.name); // "John" (unchanged) + +// Object.assign() for shallow clone +const clone2 = Object.assign({}, original); + +// ⚠️ Shallow clone issue with nested objects +const nested = { + user: { + name: "John", + address: { + city: "NYC" + } + } +}; + +const shallow = { ...nested }; +shallow.user.name = "Jane"; +shallow.user.address.city = "LA"; + +console.log(nested.user.name); // "Jane" (changed!) +console.log(nested.user.address.city); // "LA" (changed!) + +// Deep clone with JSON +const deep1 = JSON.parse(JSON.stringify(nested)); +deep1.user.name = "Alice"; +console.log(nested.user.name); // "Jane" (unchanged) + +// Deep clone with structuredClone (modern) +const deep2 = structuredClone(nested); + +// Deep clone utility +function deepClone(obj) { + if (obj === null || typeof obj !== "object") { + return obj; + } + + if (obj instanceof Date) { + return new Date(obj); + } + + if (obj instanceof Array) { + return obj.map(item => deepClone(item)); + } + + const cloned = {}; + for (const key in obj) { + if (obj.hasOwnProperty(key)) { + cloned[key] = deepClone(obj[key]); + } + } + return cloned; +} + +// Merging objects +const defaults = { + theme: "light", + language: "en", + timeout: 5000 +}; + +const userConfig = { + language: "fr", + timeout: 10000 +}; + +// Merge (userConfig overrides defaults) +const config = { ...defaults, ...userConfig }; +console.log(config); +// { theme: "light", language: "fr", timeout: 10000 } + +// Deep merge utility +function deepMerge(target, ...sources) { + if (!sources.length) return target; + const source = sources.shift(); + + if (isObject(target) && isObject(source)) { + for (const key in source) { + if (isObject(source[key])) { + if (!target[key]) Object.assign(target, { [key]: {} }); + deepMerge(target[key], source[key]); + } else { + Object.assign(target, { [key]: source[key] }); + } + } + } + return deepMerge(target, ...sources); +} + +function isObject(item) { + return item && typeof item === "object" && !Array.isArray(item); +} + +const merged = deepMerge( + { user: { name: "John", settings: { theme: "light" } } }, + { user: { settings: { language: "en" } } } +); +console.log(merged); +// { user: { name: "John", settings: { theme: "light", language: "en" } } } +``` + + +## This Context in Objects + + +```java !! java +// Java - 'this' always refers to current instance +public class Counter { + private int count = 0; + + public void increment() { + this.count++; // 'this' is the Counter instance + } +} +``` + +```javascript !! js +// JavaScript - 'this' depends on call site + +const user = { + name: "John", + age: 25, + + greet() { + console.log(`Hello, I'm ${this.name}`); + }, + + // Arrow function: 'this' from surrounding scope + greetArrow: () => { + console.log(`Hello, I'm ${this.name}`); // undefined! + }, + + // Nested function issue + nestedGreet() { + // 'this' works here + console.log(this.name); + + setTimeout(function() { + // 'this' is lost here! + console.log(this.name); // undefined + }, 1000); + + // Solution 1: Arrow function + setTimeout(() => { + console.log(this.name); // "John" - works! + }, 1000); + + // Solution 2: Bind + setTimeout(function() { + console.log(this.name); // "John" + }.bind(this), 1000); + + // Solution 3: Capture this + const self = this; + setTimeout(function() { + console.log(self.name); // "John" + }, 1000); + } +}; + +user.greet(); // "Hello, I'm John" +user.greetArrow(); // "Hello, I'm undefined" + +// 'this' depends on call site +const greet = user.greet; +greet(); // "Hello, I'm undefined" (not called as method) + +// Explicit this binding +const greet2 = user.greet.bind(user); +greet2(); // "Hello, I'm John" + +// Call and apply +user.greet.call({ name: "Jane" }); // "Hello, I'm Jane" +user.greet.apply({ name: "Bob" }); // "Hello, I'm Bob" +``` + + +## Common Patterns + +### Pattern 1: Factory Functions + + +```java !! java +// Java - Static factory methods +public class User { + private String name; + private int age; + + public static User create(String name, int age) { + return new User(name, age); + } + + public static User fromJson(String json) { + // Parse and create + } +} +``` + +```javascript !! js +// JavaScript - Factory functions (simple, no classes needed) + +function createUser(name, age) { + return { + name, + age, + greet() { + console.log(`Hi, I'm ${this.name}`); + }, + incrementAge() { + this.age++; + return this; // Chaining + } + }; +} + +const user = createUser("John", 25); +user.greet(); // "Hi, I'm John" +user.incrementAge().greet(); // Chaining + +// Factory with closures (private data) +function createCounter() { + let count = 0; // Private + + return { + increment() { + count++; + return this; + }, + getCount() { + return count; + }, + reset() { + count = 0; + return this; + } + }; +} + +const counter = createCounter(); +console.log(counter.increment().increment().getCount()); // 2 +console.log(counter.count); // undefined (truly private) +``` + + +### Pattern 2: Options Object Pattern + + +```javascript !! js +// Function with many parameters (bad) +function createUser(name, age, email, active, role, permissions) { + // Hard to remember parameter order + // Can't skip optional parameters +} + +// Options object pattern (good) +function createUser(options) { + const defaults = { + active: true, + role: "user", + permissions: [] + }; + + const config = { ...defaults, ...options }; + + return { + ...config, + createdAt: new Date() + }; +} + +const user = createUser({ + name: "John", + age: 25, + role: "admin" +}); + +console.log(user); +// { +// name: "John", +// age: 25, +// active: true, // from defaults +// role: "admin", +// permissions: [], // from defaults +// createdAt: Date +// } + +// With destructuring +function createUser2({ name, age, active = true, role = "user" } = {}) { + return { name, age, active, role, createdAt: new Date() }; +} + +const user2 = createUser2({ name: "Jane", age: 30 }); +console.log(user2); +``` + + +## Best Practices + + +```java !! java +// Java: Use classes, encapsulation +public class User { + private String name; + private int age; + + public User(String name, int age) { + this.name = name; + this.age = age; + } + + public String getName() { return name; } + public void setName(String name) { this.name = name; } + + // Getters/setters for all fields +} +``` + +```javascript !! js +// JavaScript: Embrace object literals + +// 1. Use object literals for simple data +const user = { + name: "John", + age: 25 +}; + +// 2. Use computed properties for dynamic keys +const key = "user_" + Date.now(); +const users = { + [key]: { name: "John" } +}; + +// 3. Use method shorthand +const calculator = { + add(a, b) { // Not: add: function(a, b) + return a + b; + }, + + subtract(a, b) { + return a - b; + } +}; + +// 4. Use destructuring for clean property access +const { name, age } = getUser(); + +// 5. Use spread for immutable updates +const updatedUser = { ...user, age: 26 }; + +// 6. Use Object.freeze for constants +const CONFIG = Object.freeze({ + apiUrl: "https://api.example.com", + timeout: 5000 +}); + +// 7. Use optional chaining for safe access +const city = user?.address?.city ?? "Unknown"; + +// 8. Avoid prototypes with object literals +// (we'll cover classes in module 7) +``` + + +## Exercises + +### Exercise 1: Object Manipulation +```javascript +const user = { name: "John", age: 25, email: "john@example.com" }; + +// 1. Add phone property +// 2. Remove email property +// 3. Update age to 26 +// 4. Clone the object +// 5. Get all property names +// 6. Get all property values +``` + +### Exercise 2: Destructuring +```javascript +const config = { + server: { + host: "localhost", + port: 3000 + }, + database: { + name: "mydb", + port: 5432 + } +}; + +// Extract: serverHost, serverPort, dbName +``` + +### Exercise 3: Merge Objects +```javascript +const defaults = { theme: "light", lang: "en", timeout: 5000 }; +const userSettings = { lang: "fr", notifications: true }; + +// Merge with userSettings overriding defaults +``` + +### Exercise 4: Factory Function +```javascript +// Create a factory function that makes rectangle objects +// with: width, height, area() method, perimeter() method +function createRectangle(width, height) { + // Return object with methods +} +``` + +## Summary + +### Key Takeaways + +1. **Object Literals:** + - Simple, direct syntax + - Can add/remove properties anytime + - Supports nested structures + +2. **Property Access:** + - Dot notation for known keys + - Bracket notation for dynamic/special keys + - Optional chaining for safe access + +3. **Object Methods:** + - `Object.keys()`: Get property names + - `Object.values()`: Get property values + - `Object.entries()`: Get key-value pairs + - `Object.assign()`: Merge objects + +4. **Destructuring:** + - Extract properties cleanly + - Support for default values + - Renaming and nested destructuring + +5. **Cloning:** + - Spread for shallow clone + - `structuredClone()` for deep clone + - Watch out for nested objects + +6. **This Context:** + - Depends on call site + - Arrow functions capture surrounding this + - Use `bind()` for explicit binding + +### Comparison Table: Java vs JavaScript + +| Feature | Java | JavaScript | +|---------|------|------------| +| **Creation** | `new Class()` | Literal `{}` | +| **Properties** | Fixed (fields) | Dynamic | +| **Methods** | Class methods | Object functions | +| **Access** | Direct or getters/setters | Dot or bracket notation | +| **This** | Always current instance | Depends on call site | +| **Cloning** | Manual or Cloneable | Spread or structuredClone | +| **Immutable** | `final` fields | `Object.freeze()` | + +## What's Next? + +You've mastered JavaScript objects! Next up is **Module 7: Classes and Inheritance**, where we'll explore: + +- ES6 class syntax +- Constructors and methods +- Inheritance with `extends` +- Super keyword +- Static methods and properties +- Private fields (ES2022) + +Ready to explore JavaScript's class system? Let's continue! diff --git a/content/docs/java2js/module-06-objects.zh-tw.mdx b/content/docs/java2js/module-06-objects.zh-tw.mdx new file mode 100644 index 0000000..8eed44e --- /dev/null +++ b/content/docs/java2js/module-06-objects.zh-tw.mdx @@ -0,0 +1,398 @@ +--- +title: "Module 6: Objects" +description: "掌握 JavaScript 物件、屬性、方法和物件操作" +--- + +## Module 6: Objects + +物件是 JavaScript 的核心。與 Java 不同,JavaScript 的物件是動態的鍵值對集合,不基於類別。讓我們掌握 JavaScript 物件。 + +## Learning Objectives + +完成本模組後,你將: +✅ 理解 JavaScript 物件本質 +✅ 掌握物件創建語法 +✅ 學習屬性訪問和操作 +✅ 理解物件方法 +✅ 掌握物件解構 +✅ 學習物件比較和複製 + +## Objects Comparison + + +```java !! java +// Java - 類別基礎物件 +public class User { + private String name; + private int age; + + public User(String name, int age) { + this.name = name; + this.age = age; + } + + public String getName() { return name; } + public int getAge() { return age; } +} + +User user = new User("Alice", 25); +System.out.println(user.getName()); +``` + +```javascript !! js +// JavaScript - 動態物件 +const user = { + name: "Alice", + age: 25, + greet: function() { + console.log(`Hello, I'm ${this.name}`); + } +}; + +// Property access +console.log(user.name); // "Alice" +console.log(user["name"]); // "Alice" + +// Dynamic access +const key = "age"; +console.log(user[key]); // 25 + +// Add properties +user.email = "alice@example.com"; +user["isAdmin"] = false; + +// Remove properties +delete user.email; + +// Call method +user.greet(); // "Hello, I'm Alice" +``` + + +## Object Creation + + +```java !! java +// Java - 類別實例化 +public class Point { + public int x; + public int y; + + public Point(int x, int y) { + this.x = x; + this.y = y; + } +} + +Point p = new Point(10, 20); +``` + +```javascript !! js +// JavaScript - 多種創建方式 + +// 1. Object literal(最常用) +const obj1 = { + name: "Alice", + age: 25 +}; + +// 2. Object.create() +const proto = { greet: function() { console.log("Hi!"); } }; +const obj2 = Object.create(proto); + +// 3. new Object(避免使用) +const obj3 = new Object(); +obj3.name = "Bob"; + +// 4. Constructor function +function User(name) { + this.name = name; +} +const user = new User("Charlie"); + +// 5. Class(ES6+) +class Person { + constructor(name) { + this.name = name; + } +} +const person = new Person("David"); + +// Computed property names +const key = "dynamic"; +const obj4 = { + [key]: "value", + [`prop_${Date.now()}`]: "timestamp" +}; + +// Shorthand +const name = "Alice"; +const age = 25; +const obj5 = { name, age }; // Same as { name: name, age: age } + +// Method shorthand +const obj6 = { + greet() { + console.log("Hello!"); + } +}; +``` + + +## Property Descriptors + + +```java !! java +// Java - Private fields with getters/setters +public class User { + private String name; + + public String getName() { return name; } + public void setName(String name) { this.name = name; } +} +``` + +```javascript !! js +// JavaScript - Property descriptors +const obj = {}; + +// Data property +Object.defineProperty(obj, "name", { + value: "Alice", + writable: true, // Can be changed + enumerable: true, // Shows in for...in + configurable: true // Can be deleted or modified +}); + +// Read-only property +Object.defineProperty(obj, "ID", { + value: 12345, + writable: false +}); + +obj.ID = 99999; // Silently fails in non-strict mode + +// Getter/setter +const user = { + _name: "Alice", + + get name() { + return this._name.toUpperCase(); + }, + + set name(value) { + if (value.length > 0) { + this._name = value; + } + } +}; + +console.log(user.name); // "ALICE" +user.name = "Bob"; +console.log(user.name); // "BOB" + +// Check property +console.log(obj.hasOwnProperty("name")); // true +console.log("name" in obj); // true + +// Get all keys +console.log(Object.keys(obj)); // Own enumerable keys +console.log(Object.getOwnPropertyNames(obj)); // All own keys +``` + + +## Object Destructuring + + +```java !! java +// Java - Manual field access +User user = new User("Alice", 25); +String name = user.getName(); +int age = user.getAge(); +``` + +```javascript !! js +// JavaScript - Destructuring +const user = { + name: "Alice", + age: 25, + email: "alice@example.com" +}; + +// Basic destructuring +const { name, age } = user; +console.log(name, age); // "Alice" 25 + +// Rename +const { name: userName, age: userAge } = user; + +// Default values +const { name: n = "Anonymous", country = "USA" } = user; + +// Nested destructuring +const data = { + user: { + name: "Alice", + address: { + city: "Taipei" + } + } +}; + +const { user: { name, address: { city } } } = data; +console.log(name, city); // "Alice" "Taipei" + +// Function parameter destructuring +function greet({ name, age = 18 }) { + console.log(`${name} is ${age} years old`); +} + +greet({ name: "Bob" }); // "Bob is 18 years old" + +// Destructuring with rest +const { name, ...rest } = user; +console.log(rest); // { age: 25, email: "alice@example.com" } +``` + + +## Object Methods + + +```javascript !! js +const obj = { + name: "Alice", + + // Method shorthand + greet() { + return `Hello, I'm ${this.name}`; + }, + + // Arrow function(no this binding) + greetArrow: () => { + return `Hello`; // this.name is undefined + } +}; + +console.log(obj.greet()); // "Hello, I'm Alice" +console.log(obj.greetArrow()); // "Hello" + +// Dynamic methods +const methodName = "sayHello"; +const obj2 = { + [methodName]() { + console.log("Hello!"); + } +}; + +obj2.sayHello(); // "Hello!" + +// Computed method names +const prefix = "get"; +const obj3 = { + [`${prefix}Name`() { + return "Alice"; + } +}; + +console.log(obj3.getName()); // "Alice" +``` + + +## Object Comparison + + +```java !! java +// Java - equals() for content comparison +String s1 = new String("hello"); +String s2 = new String("hello"); + +System.out.println(s1 == s2); // false(different references) +System.out.println(s1.equals(s2)); // true(same content) +``` + +```javascript !! js +// JavaScript - Reference comparison +const obj1 = { name: "Alice" }; +const obj2 = { name: "Alice" }; +const obj3 = obj1; + +console.log(obj1 === obj2); // false(different references) +console.log(obj1 === obj3); // true(same reference) + +// Shallow equality check +function shallowEqual(obj1, obj2) { + const keys1 = Object.keys(obj1); + const keys2 = Object.keys(obj2); + + if (keys1.length !== keys2.length) return false; + + for (const key of keys1) { + if (obj1[key] !== obj2[key]) return false; + } + + return true; +} + +console.log(shallowEqual(obj1, obj2)); // true + +// Deep equality(simplified) +function deepEqual(obj1, obj2) { + if (obj1 === obj2) return true; + + if (typeof obj1 !== "object" || obj1 === null || + typeof obj2 !== "object" || obj2 === null) { + return false; + } + + const keys1 = Object.keys(obj1); + const keys2 = Object.keys(obj2); + + if (keys1.length !== keys2.length) return false; + + for (const key of keys1) { + if (!keys2.includes(key) || !deepEqual(obj1[key], obj2[key])) { + return false; + } + } + + return true; +} +``` + + +## Best Practices + + +```javascript !! js +// 1. Use object literals for simple objects +const config = { apiUrl: "https://api.example.com", timeout: 5000 }; + +// 2. Use destructuring for cleaner code +const { name, age } = user; + +// 3. Use spread for shallow copy +const copy = { ...original }; + +// 4. Use Object.freeze for immutability +const constants = Object.freeze({ API_KEY: "xxx", VERSION: 1 }); + +// 5. Use optional chaining +const city = user?.address?.city; + +// 6. Use nullish coalescing +const timeout = config.timeout ?? 3000; +``` + + +## Summary + +### Key Takeaways + +1. **Objects:** 動態鍵值對 +2. **Creation:** 字面語法最常用 +3. **Destructuring:** 簡潔的屬性提取 +4. **Comparison:** 參考比較 + +## What's Next? + +接下來是 **Module 7: Classes** - 了解 ES6 類別! diff --git a/content/docs/java2js/module-07-classes.mdx b/content/docs/java2js/module-07-classes.mdx new file mode 100644 index 0000000..352d39b --- /dev/null +++ b/content/docs/java2js/module-07-classes.mdx @@ -0,0 +1,1089 @@ +--- +title: "Module 7: Classes and Inheritance" +description: "Learn ES6 classes, inheritance, and object-oriented patterns in JavaScript" +--- + +## Module 7: Classes and Inheritance + +JavaScript introduced class syntax in ES6 (2015), making object-oriented programming more familiar to Java developers. However, JavaScript classes are syntactic sugar over prototypes and work differently from Java classes. + +## Learning Objectives + +By the end of this module, you will: +✅ Understand ES6 class syntax +✅ Master constructors and methods +✅ Learn inheritance with `extends` +✅ Understand the `super` keyword +✅ Know static methods and properties +✅ Learn about private fields (ES2022) + +## Class Syntax + + +```java !! java +// Java - Traditional class +public class User { + private String name; + private int age; + + public User(String name, int age) { + this.name = name; + this.age = age; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getAge() { + return age; + } + + public void greet() { + System.out.println("Hello, I'm " + name); + } +} + +// Usage +User user = new User("John", 25); +user.greet(); +``` + +```javascript !! js +// JavaScript - ES6 class syntax +class User { + // Field declarations (ES2022+) + name; + age; + + // Constructor + constructor(name, age) { + this.name = name; + this.age = age; + } + + // Instance method + greet() { + console.log(`Hello, I'm ${this.name}`); + } + + // Getter + get info() { + return `${this.name} (${this.age})`; + } + + // Setter + set setName(name) { + this.name = name; + } +} + +// Usage +const user = new User("John", 25); +user.greet(); // "Hello, I'm John" +console.log(user.info); // "John (25)" + +// Classes are first-class citizens +const UserClass = User; +const user2 = new UserClass("Jane", 30); +``` + + +### Hoisting + + +```java !! java +// Java - No hoisting +User user = new User(); // Compilation error if User not defined + +public class User { + // ... +} +``` + +```javascript !! js +// JavaScript - Classes are NOT hoisted +// ❌ BAD: Using class before declaration +const user = new User(); // ReferenceError + +class User { + constructor(name) { + this.name = name; + } +} + +// ✅ GOOD: Declare class first +class User2 { + constructor(name) { + this.name = name; + } +} + +const user2 = new User2("John"); // Works + +// Function declarations ARE hoisted +const user3 = createUser("Bob"); // Works! + +function createUser(name) { + return { name }; +} +``` + + +### Class Expressions + + +```java !! java +// Java - Anonymous classes +Runnable runnable = new Runnable() { + @Override + public void run() { + System.out.println("Running"); + } +}; + +// Lambda (Java 8+) +Runnable r2 = () -> System.out.println("Running"); +``` + +```javascript !! js +// JavaScript - Class expressions +const User = class { + constructor(name) { + this.name = name; + } + + greet() { + console.log(`Hi, ${this.name}`); + } +}; + +const user = new User("John"); +user.greet(); + +// Named class expression (better for debugging) +const User2 = class UserClass { + constructor(name) { + this.name = name; + } +}; + +// Can be exported immediately +export default class { + constructor() { + // ... + } +} +``` + + +## Inheritance + + +```java !! java +// Java - Extends keyword +public class Animal { + protected String name; + + public Animal(String name) { + this.name = name; + } + + public void speak() { + System.out.println("Some sound"); + } +} + +public class Dog extends Animal { + private String breed; + + public Dog(String name, String breed) { + super(name); // Call parent constructor + this.breed = breed; + } + + @Override + public void speak() { + System.out.println("Woof!"); + } + + public void fetch() { + System.out.println("Fetching!"); + } +} +``` + +```javascript !! js +// JavaScript - Extends keyword (similar syntax) +class Animal { + constructor(name) { + this.name = name; + } + + speak() { + console.log("Some sound"); + } +} + +class Dog extends Animal { + constructor(name, breed) { + super(name); // Must call super before using 'this' + this.breed = breed; + } + + speak() { + console.log("Woof!"); + } + + fetch() { + console.log("Fetching!"); + } +} + +const dog = new Dog("Buddy", "Labrador"); +dog.speak(); // "Woof!" +dog.fetch(); // "Fetching!" +console.log(dog instanceof Dog); // true +console.log(dog instanceof Animal); // true +``` + + +### Super Keyword + + +```java !! java +// Java - Super for parent methods +public class Dog extends Animal { + @Override + public void speak() { + super.speak(); // Call parent method + System.out.println("Woof!"); + } + + public void displayInfo() { + System.out.println(super.name); // Access parent field + } +} +``` + +```javascript !! js +// JavaScript - Super keyword + +class Parent { + constructor(name) { + this.name = name; + } + + greet() { + return `Hello, I'm ${this.name}`; + } + + static getType() { + return "Parent"; + } +} + +class Child extends Parent { + constructor(name, age) { + super(name); // Call parent constructor (required!) + this.age = age; + } + + greet() { + // Call parent method + const parentGreeting = super.greet(); + return `${parentGreeting} and I'm ${this.age}`; + } + + // Override static method + static getType() { + return `${super.getType()} -> Child`; + } +} + +const child = new Child("John", 25); +console.log(child.greet()); // "Hello, I'm John and I'm 25" +console.log(Child.getType()); // "Parent -> Child" + +// Super in object literals +const parent = { + greet() { + return "Hello from parent"; + } +}; + +const child2 = { + __proto__: parent, + greet() { + return super.greet() + " and child"; + } +}; + +console.log(child2.greet()); // "Hello from parent and child" +``` + + +## Static Members + + +```java !! java +// Java - Static members +public class MathUtil { + private static final double PI = 3.14159; + + public static double circleArea(double radius) { + return PI * radius * radius; + } + + public static class Constants { + public static final String APP_NAME = "MyApp"; + } +} + +// Usage +double area = MathUtil.circleArea(5.0); +String name = MathUtil.Constants.APP_NAME; +``` + +```javascript !! js +// JavaScript - Static methods and properties + +class MathUtil { + // Static property (ES2022+) + static PI = 3.14159; + + // Static method + static circleArea(radius) { + return this.PI * radius * radius; + } + + // Static methods can't access instance properties + static notValid() { + // console.log(this.name); // Error: undefined + } +} + +// Usage (no instantiation needed) +console.log(MathUtil.PI); // 3.14159 +console.log(MathUtil.circleArea(5)); // 78.53975 + +// Static code block (ES2022+) +class AppConfig { + static config = {}; + + static { + // Runs once when class is defined + this.config = { + apiUrl: "https://api.example.com", + timeout: 5000 + }; + console.log("Config initialized"); + } + + static get(key) { + return this.config[key]; + } +} + +console.log(AppConfig.get("apiUrl")); // "https://api.example.com" + +// Static methods with inheritance +class Parent { + static staticMethod() { + return "Parent"; + } +} + +class Child extends Parent { + static staticMethod() { + // Call parent static method + return `${super.staticMethod()} -> Child`; + } +} + +console.log(Child.staticMethod()); // "Parent -> Child" +``` + + +## Private Fields + + +```java !! java +// Java - Private fields +public class BankAccount { + private double balance; + + public BankAccount(double initialBalance) { + this.balance = initialBalance; + } + + public void deposit(double amount) { + if (amount > 0) { + this.balance += amount; + } + } + + public double getBalance() { + return this.balance; + } +} +``` + +```javascript !! js +// JavaScript - Private fields (ES2022+) +class BankAccount { + // Private fields (declared with #) + #balance; + + constructor(initialBalance) { + this.#balance = initialBalance; + } + + deposit(amount) { + if (amount > 0) { + this.#balance += amount; + } + } + + getBalance() { + return this.#balance; + } + + // Private methods (ES2022+) + #validateAmount(amount) { + return amount > 0 && !isNaN(amount); + } + + withdraw(amount) { + if (this.#validateAmount(amount)) { + this.#balance -= amount; + } + } +} + +const account = new BankAccount(100); +account.deposit(50); +console.log(account.getBalance()); // 150 +console.log(account.#balance); // SyntaxError! (truly private) + +// Private fields are not inherited +class SavingsAccount extends BankAccount { + #interestRate; + + constructor(initialBalance, interestRate) { + super(initialBalance); + this.#interestRate = interestRate; + } + + addInterest() { + const balance = this.getBalance(); + const interest = balance * this.#interestRate; + this.deposit(interest); + } +} + +// Before private fields: Convention with _ +class User { + constructor(name) { + this._name = name; // Privacy by convention only + } + + getName() { + return this._name; + } +} +``` + + +## Getters and Setters + + +```java !! java +// Java - Getters and setters +public class User { + private String name; + private int age; + + public String getName() { + return name; + } + + public void setName(String name) { + if (name != null && !name.isEmpty()) { + this.name = name; + } + } + + public int getAge() { + return age; + } + + public void setAge(int age) { + if (age >= 0) { + this.age = age; + } + } +} +``` + +```javascript !! js +// JavaScript - Getters and setters +class User { + constructor(name, age) { + this._name = name; // Convention for backing property + this._age = age; + } + + // Getter + get name() { + return this._name; + } + + // Setter + set name(value) { + if (value && value.trim().length > 0) { + this._name = value; + } + } + + get age() { + return this._age; + } + + set age(value) { + if (value >= 0) { + this._age = value; + } + } + + // Computed property + get info() { + return `${this._name} is ${this._age} years old`; + } +} + +const user = new User("John", 25); + +// Access like properties (not methods!) +console.log(user.name); // "John" (calls getter) +user.name = "Jane"; // Calls setter +console.log(user.name); // "Jane" +user.name = ""; // Fails validation +console.log(user.name); // "Jane" (unchanged) + +console.log(user.info); // "Jane is 25 years old" + +// Object literals also support getters/setters +const person = { + _firstName: "John", + _lastName: "Doe", + + get fullName() { + return `${this._firstName} ${this._lastName}`; + }, + + set fullName(value) { + [this._firstName, this._lastName] = value.split(" "); + } +}; + +console.log(person.fullName); // "John Doe" +person.fullName = "Jane Smith"; +console.log(person.fullName); // "Jane Smith" +``` + + +## Instance vs Static vs Private + + +```java !! java +// Java - Member types +public class Example { + private int instanceField; // Instance + private static int staticField; // Static + + public void instanceMethod() { } + + public static void staticMethod() { } +} +``` + +```javascript !! js +// JavaScript - All member types + +class Example { + // Public instance field + instanceField = 1; + + // Static field + static staticField = 2; + + // Private instance field + #privateField = 3; + + // Static private field + static #staticPrivate = 4; + + // Instance method + instanceMethod() { + console.log(this.instanceField); // ✓ Access + console.log(this.#privateField); // ✓ Access + console.log(Example.staticField); // ✓ Access + // console.log(Example.#staticPrivate); // ✗ Can't access private static + } + + // Static method + static staticMethod() { + console.log(this.staticField); // ✓ Access + console.log(this.#staticPrivate); // ✓ Access + // console.log(this.instanceField); // ✗ Can't access instance + } +} + +const ex = new Example(); +ex.instanceMethod(); +Example.staticMethod(); + +// Accessing members +console.log(ex.instanceField); // 1 +console.log(Example.staticField); // 2 +// console.log(ex.#privateField); // SyntaxError! +``` + + +## Method Overriding and Polymorphism + + +```java !! java +// Java - Method overriding +public class Animal { + public void speak() { + System.out.println("Animal sound"); + } + + public void perform() { + speak(); // Calls overridden version in subclass + } +} + +public class Dog extends Animal { + @Override + public void speak() { + System.out.println("Woof!"); + } +} + +Dog dog = new Dog(); +dog.perform(); // "Woof!" (polymorphic call) +``` + +```javascript !! js +// JavaScript - Method overriding (works similarly) + +class Animal { + speak() { + console.log("Animal sound"); + } + + perform() { + this.speak(); // Polymorphic - calls subclass version + } +} + +class Dog extends Animal { + speak() { + console.log("Woof!"); + } +} + +const dog = new Dog(); +dog.perform(); // "Woof!" + +// Explicit parent method call +class Dog2 extends Animal { + speak() { + super.speak(); // "Animal sound" + console.log("Woof!"); + } +} + +const dog2 = new Dog2(); +dog2.speak(); // "Animal sound" then "Woof!" +``` + + +## Common Patterns + +### Pattern 1: Singleton + + +```java !! java +// Java - Singleton +public class Database { + private static Database instance; + + private Database() { + // Private constructor + } + + public static synchronized Database getInstance() { + if (instance == null) { + instance = new Database(); + } + return instance; + } +} +``` + +```javascript !! js +// JavaScript - Singleton with class + +class Database { + constructor() { + if (Database.instance) { + return Database.instance; + } + this.connection = "connected"; + Database.instance = this; + } + + static getInstance() { + if (!Database.instance) { + Database.instance = new Database(); + } + return Database.instance; + } +} + +const db1 = Database.getInstance(); +const db2 = Database.getInstance(); +console.log(db1 === db2); // true + +// Simpler: Use object literal +const config = Object.freeze({ + apiUrl: "https://api.example.com", + timeout: 5000 +}); +``` + + +### Pattern 2: Mixin Pattern + + +```java !! java +// Java - Multiple inheritance not supported +// Use interfaces for similar behavior +interface Flyable { + void fly(); +} + +interface Swimmable { + void swim(); +} + +class Duck implements Flyable, Swimmable { + public void fly() { /* ... */ } + public void swim() { /* ... */ } +} +``` + +```javascript !! js +// JavaScript - Mixin pattern + +const Flyable = { + fly() { + console.log("Flying!"); + } +}; + +const Swimmable = { + swim() { + console.log("Swimming!"); + } +}; + +class Duck {} +Object.assign(Duck.prototype, Flyable, Swimmable); + +const duck = new Duck(); +duck.fly(); // "Flying!" +duck.swim(); // "Swimming!" + +// Mixin factory function +function mix(...mixins) { + return function(targetClass) { + Object.assign(targetClass.prototype, ...mixins); + return targetClass; + }; +} + +class Bird {} +const MixedBird = mix(Flyable)(Bird); + +const bird = new MixedBird(); +bird.fly(); // "Flying!" +``` + + +### Pattern 3: Factory with Classes + + +```javascript !! js +// Abstract base class +class Vehicle { + constructor(make, model) { + if (this.constructor === Vehicle) { + throw new Error("Abstract class cannot be instantiated"); + } + this.make = make; + this.model = model; + } + + // Abstract method + start() { + throw new Error("Must implement start()"); + } +} + +class Car extends Vehicle { + start() { + console.log(`${this.make} ${this.model} is starting`); + } +} + +class Motorcycle extends Vehicle { + start() { + console.log(`${this.make} ${this.model} vroom!`); + } +} + +// Factory function +function createVehicle(type, make, model) { + switch (type) { + case "car": + return new Car(make, model); + case "motorcycle": + return new Motorcycle(make, model); + default: + throw new Error("Unknown vehicle type"); + } +} + +const car = createVehicle("car", "Toyota", "Camry"); +car.start(); // "Toyota Camry is starting" +``` + + +## Best Practices + + +```java !! java +// Java: Clear encapsulation +public class User { + private final String name; // Immutable + private int age; + + public User(String name, int age) { + this.name = Objects.requireNonNull(name); + this.age = age; + } + + public String getName() { + return name; + } + + // Validation in setters + public void setAge(int age) { + if (age < 0) { + throw new IllegalArgumentException("Age cannot be negative"); + } + this.age = age; + } +} +``` + +```javascript !! js +// JavaScript: Similar principles + +// 1. Use classes for objects with behavior +class User { + // Use private fields (ES2022+) + #name; + #age; + + constructor(name, age) { + this.#name = name; + this.#age = age; + } + + get name() { + return this.#name; + } + + get age() { + return this.#age; + } + + set age(value) { + if (value >= 0) { + this.#age = value; + } + } +} + +// 2. Use object literals for simple data +const config = { apiUrl: "https://api.example.com" }; + +// 3. Prefer composition over inheritance +// Good +class TodoApp { + constructor(storage, logger) { + this.storage = storage; + this.logger = logger; + } +} + +// Avoid: Deep inheritance chains +// Bad +class Animal {} +class Mammal extends Animal {} +class Dog extends Mammal {} +class Labrador extends Dog {} + +// 4. Use static methods for utility functions +class MathUtils { + static clamp(value, min, max) { + return Math.min(Math.max(value, min), max); + } +} + +// 5. Use getters/setters for computed or validated properties +class Temperature { + #celsius; + + get celsius() { + return this.#celsius; + } + + set celsius(value) { + this.#celsius = value; + } + + get fahrenheit() { + return (this.#celsius * 9/5) + 32; + } + + set fahrenheit(value) { + this.#celsius = (value - 32) * 5/9; + } +} + +// 6. Always call super() in subclass constructors +class Child extends Parent { + constructor(name) { + super(name); // Must be first line + // ... + } +} +``` + + +## Exercises + +### Exercise 1: Create a Class +```javascript +// Create a Rectangle class with: +// - width, height properties +// - area() method +// - perimeter() method +// - get isSquare() getter +``` + +### Exercise 2: Inheritance +```javascript +// Create Shape base class +// Create Circle and Rectangle subclasses +// Each should have area() method +``` + +### Exercise 3: Private Fields +```javascript +// Create BankAccount class with: +// - Private #balance field +// - deposit() and withdraw() methods +// - Validation for negative amounts +``` + +### Exercise 4: Static Methods +```javascript +// Create MathUtility class with: +// - Static methods: clamp(), lerp(), randomInRange() +// - Use them without instantiation +``` + +## Summary + +### Key Takeaways + +1. **Class Syntax:** + - Syntactic sugar over prototypes + - Not hoisted (unlike functions) + - Can be named or anonymous + +2. **Inheritance:** + - `extends` for inheritance + - `super` to call parent + - Must call `super()` before `this` + +3. **Static Members:** + - `static` keyword + - Accessed via class name + - Don't require instantiation + +4. **Private Fields:** + - `#` prefix (ES2022+) + - Truly encapsulated + - Cannot be accessed outside class + +5. **Getters/Setters:** + - Computed properties + - Validation + - Encapsulation + +6. **Best Practices:** + - Classes for behavior, objects for data + - Prefer composition over deep inheritance + - Use private fields for true encapsulation + - Static methods for utilities + +### Comparison Table: Java vs JavaScript + +| Feature | Java | JavaScript | +|---------|------|------------| +| **Syntax** | `class Name { }` | Same (ES6+) | +| **Inheritance** | `extends` (single) | `extends` (single) | +| **Interfaces** | Yes | No (use protocols) | +| **Abstract** | `abstract` keyword | Manual enforcement | +| **Private** | `private` keyword | `#` prefix (ES2022+) | +| **Protected** | `protected` keyword | Convention (`_name`) | +| **Static** | `static` keyword | Same | +| **Final** | `final` keyword | No (use Object.freeze) | +| **Super** | `super.method()` | Same | +| **Constructor** | Same name as class | `constructor()` | + +## What's Next? + +You've mastered JavaScript classes! Next up is **Module 8: Prototypes and Prototype Chain**, where we'll explore: + +- How prototypes work under the hood +- Prototype chain lookup +- Object.create() and prototype inheritance +- Constructor functions +- The relationship between classes and prototypes +- When to use prototypes vs classes + +Ready to understand JavaScript's prototype system? Let's continue! diff --git a/content/docs/java2js/module-07-classes.zh-cn.mdx b/content/docs/java2js/module-07-classes.zh-cn.mdx new file mode 100644 index 0000000..352d39b --- /dev/null +++ b/content/docs/java2js/module-07-classes.zh-cn.mdx @@ -0,0 +1,1089 @@ +--- +title: "Module 7: Classes and Inheritance" +description: "Learn ES6 classes, inheritance, and object-oriented patterns in JavaScript" +--- + +## Module 7: Classes and Inheritance + +JavaScript introduced class syntax in ES6 (2015), making object-oriented programming more familiar to Java developers. However, JavaScript classes are syntactic sugar over prototypes and work differently from Java classes. + +## Learning Objectives + +By the end of this module, you will: +✅ Understand ES6 class syntax +✅ Master constructors and methods +✅ Learn inheritance with `extends` +✅ Understand the `super` keyword +✅ Know static methods and properties +✅ Learn about private fields (ES2022) + +## Class Syntax + + +```java !! java +// Java - Traditional class +public class User { + private String name; + private int age; + + public User(String name, int age) { + this.name = name; + this.age = age; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getAge() { + return age; + } + + public void greet() { + System.out.println("Hello, I'm " + name); + } +} + +// Usage +User user = new User("John", 25); +user.greet(); +``` + +```javascript !! js +// JavaScript - ES6 class syntax +class User { + // Field declarations (ES2022+) + name; + age; + + // Constructor + constructor(name, age) { + this.name = name; + this.age = age; + } + + // Instance method + greet() { + console.log(`Hello, I'm ${this.name}`); + } + + // Getter + get info() { + return `${this.name} (${this.age})`; + } + + // Setter + set setName(name) { + this.name = name; + } +} + +// Usage +const user = new User("John", 25); +user.greet(); // "Hello, I'm John" +console.log(user.info); // "John (25)" + +// Classes are first-class citizens +const UserClass = User; +const user2 = new UserClass("Jane", 30); +``` + + +### Hoisting + + +```java !! java +// Java - No hoisting +User user = new User(); // Compilation error if User not defined + +public class User { + // ... +} +``` + +```javascript !! js +// JavaScript - Classes are NOT hoisted +// ❌ BAD: Using class before declaration +const user = new User(); // ReferenceError + +class User { + constructor(name) { + this.name = name; + } +} + +// ✅ GOOD: Declare class first +class User2 { + constructor(name) { + this.name = name; + } +} + +const user2 = new User2("John"); // Works + +// Function declarations ARE hoisted +const user3 = createUser("Bob"); // Works! + +function createUser(name) { + return { name }; +} +``` + + +### Class Expressions + + +```java !! java +// Java - Anonymous classes +Runnable runnable = new Runnable() { + @Override + public void run() { + System.out.println("Running"); + } +}; + +// Lambda (Java 8+) +Runnable r2 = () -> System.out.println("Running"); +``` + +```javascript !! js +// JavaScript - Class expressions +const User = class { + constructor(name) { + this.name = name; + } + + greet() { + console.log(`Hi, ${this.name}`); + } +}; + +const user = new User("John"); +user.greet(); + +// Named class expression (better for debugging) +const User2 = class UserClass { + constructor(name) { + this.name = name; + } +}; + +// Can be exported immediately +export default class { + constructor() { + // ... + } +} +``` + + +## Inheritance + + +```java !! java +// Java - Extends keyword +public class Animal { + protected String name; + + public Animal(String name) { + this.name = name; + } + + public void speak() { + System.out.println("Some sound"); + } +} + +public class Dog extends Animal { + private String breed; + + public Dog(String name, String breed) { + super(name); // Call parent constructor + this.breed = breed; + } + + @Override + public void speak() { + System.out.println("Woof!"); + } + + public void fetch() { + System.out.println("Fetching!"); + } +} +``` + +```javascript !! js +// JavaScript - Extends keyword (similar syntax) +class Animal { + constructor(name) { + this.name = name; + } + + speak() { + console.log("Some sound"); + } +} + +class Dog extends Animal { + constructor(name, breed) { + super(name); // Must call super before using 'this' + this.breed = breed; + } + + speak() { + console.log("Woof!"); + } + + fetch() { + console.log("Fetching!"); + } +} + +const dog = new Dog("Buddy", "Labrador"); +dog.speak(); // "Woof!" +dog.fetch(); // "Fetching!" +console.log(dog instanceof Dog); // true +console.log(dog instanceof Animal); // true +``` + + +### Super Keyword + + +```java !! java +// Java - Super for parent methods +public class Dog extends Animal { + @Override + public void speak() { + super.speak(); // Call parent method + System.out.println("Woof!"); + } + + public void displayInfo() { + System.out.println(super.name); // Access parent field + } +} +``` + +```javascript !! js +// JavaScript - Super keyword + +class Parent { + constructor(name) { + this.name = name; + } + + greet() { + return `Hello, I'm ${this.name}`; + } + + static getType() { + return "Parent"; + } +} + +class Child extends Parent { + constructor(name, age) { + super(name); // Call parent constructor (required!) + this.age = age; + } + + greet() { + // Call parent method + const parentGreeting = super.greet(); + return `${parentGreeting} and I'm ${this.age}`; + } + + // Override static method + static getType() { + return `${super.getType()} -> Child`; + } +} + +const child = new Child("John", 25); +console.log(child.greet()); // "Hello, I'm John and I'm 25" +console.log(Child.getType()); // "Parent -> Child" + +// Super in object literals +const parent = { + greet() { + return "Hello from parent"; + } +}; + +const child2 = { + __proto__: parent, + greet() { + return super.greet() + " and child"; + } +}; + +console.log(child2.greet()); // "Hello from parent and child" +``` + + +## Static Members + + +```java !! java +// Java - Static members +public class MathUtil { + private static final double PI = 3.14159; + + public static double circleArea(double radius) { + return PI * radius * radius; + } + + public static class Constants { + public static final String APP_NAME = "MyApp"; + } +} + +// Usage +double area = MathUtil.circleArea(5.0); +String name = MathUtil.Constants.APP_NAME; +``` + +```javascript !! js +// JavaScript - Static methods and properties + +class MathUtil { + // Static property (ES2022+) + static PI = 3.14159; + + // Static method + static circleArea(radius) { + return this.PI * radius * radius; + } + + // Static methods can't access instance properties + static notValid() { + // console.log(this.name); // Error: undefined + } +} + +// Usage (no instantiation needed) +console.log(MathUtil.PI); // 3.14159 +console.log(MathUtil.circleArea(5)); // 78.53975 + +// Static code block (ES2022+) +class AppConfig { + static config = {}; + + static { + // Runs once when class is defined + this.config = { + apiUrl: "https://api.example.com", + timeout: 5000 + }; + console.log("Config initialized"); + } + + static get(key) { + return this.config[key]; + } +} + +console.log(AppConfig.get("apiUrl")); // "https://api.example.com" + +// Static methods with inheritance +class Parent { + static staticMethod() { + return "Parent"; + } +} + +class Child extends Parent { + static staticMethod() { + // Call parent static method + return `${super.staticMethod()} -> Child`; + } +} + +console.log(Child.staticMethod()); // "Parent -> Child" +``` + + +## Private Fields + + +```java !! java +// Java - Private fields +public class BankAccount { + private double balance; + + public BankAccount(double initialBalance) { + this.balance = initialBalance; + } + + public void deposit(double amount) { + if (amount > 0) { + this.balance += amount; + } + } + + public double getBalance() { + return this.balance; + } +} +``` + +```javascript !! js +// JavaScript - Private fields (ES2022+) +class BankAccount { + // Private fields (declared with #) + #balance; + + constructor(initialBalance) { + this.#balance = initialBalance; + } + + deposit(amount) { + if (amount > 0) { + this.#balance += amount; + } + } + + getBalance() { + return this.#balance; + } + + // Private methods (ES2022+) + #validateAmount(amount) { + return amount > 0 && !isNaN(amount); + } + + withdraw(amount) { + if (this.#validateAmount(amount)) { + this.#balance -= amount; + } + } +} + +const account = new BankAccount(100); +account.deposit(50); +console.log(account.getBalance()); // 150 +console.log(account.#balance); // SyntaxError! (truly private) + +// Private fields are not inherited +class SavingsAccount extends BankAccount { + #interestRate; + + constructor(initialBalance, interestRate) { + super(initialBalance); + this.#interestRate = interestRate; + } + + addInterest() { + const balance = this.getBalance(); + const interest = balance * this.#interestRate; + this.deposit(interest); + } +} + +// Before private fields: Convention with _ +class User { + constructor(name) { + this._name = name; // Privacy by convention only + } + + getName() { + return this._name; + } +} +``` + + +## Getters and Setters + + +```java !! java +// Java - Getters and setters +public class User { + private String name; + private int age; + + public String getName() { + return name; + } + + public void setName(String name) { + if (name != null && !name.isEmpty()) { + this.name = name; + } + } + + public int getAge() { + return age; + } + + public void setAge(int age) { + if (age >= 0) { + this.age = age; + } + } +} +``` + +```javascript !! js +// JavaScript - Getters and setters +class User { + constructor(name, age) { + this._name = name; // Convention for backing property + this._age = age; + } + + // Getter + get name() { + return this._name; + } + + // Setter + set name(value) { + if (value && value.trim().length > 0) { + this._name = value; + } + } + + get age() { + return this._age; + } + + set age(value) { + if (value >= 0) { + this._age = value; + } + } + + // Computed property + get info() { + return `${this._name} is ${this._age} years old`; + } +} + +const user = new User("John", 25); + +// Access like properties (not methods!) +console.log(user.name); // "John" (calls getter) +user.name = "Jane"; // Calls setter +console.log(user.name); // "Jane" +user.name = ""; // Fails validation +console.log(user.name); // "Jane" (unchanged) + +console.log(user.info); // "Jane is 25 years old" + +// Object literals also support getters/setters +const person = { + _firstName: "John", + _lastName: "Doe", + + get fullName() { + return `${this._firstName} ${this._lastName}`; + }, + + set fullName(value) { + [this._firstName, this._lastName] = value.split(" "); + } +}; + +console.log(person.fullName); // "John Doe" +person.fullName = "Jane Smith"; +console.log(person.fullName); // "Jane Smith" +``` + + +## Instance vs Static vs Private + + +```java !! java +// Java - Member types +public class Example { + private int instanceField; // Instance + private static int staticField; // Static + + public void instanceMethod() { } + + public static void staticMethod() { } +} +``` + +```javascript !! js +// JavaScript - All member types + +class Example { + // Public instance field + instanceField = 1; + + // Static field + static staticField = 2; + + // Private instance field + #privateField = 3; + + // Static private field + static #staticPrivate = 4; + + // Instance method + instanceMethod() { + console.log(this.instanceField); // ✓ Access + console.log(this.#privateField); // ✓ Access + console.log(Example.staticField); // ✓ Access + // console.log(Example.#staticPrivate); // ✗ Can't access private static + } + + // Static method + static staticMethod() { + console.log(this.staticField); // ✓ Access + console.log(this.#staticPrivate); // ✓ Access + // console.log(this.instanceField); // ✗ Can't access instance + } +} + +const ex = new Example(); +ex.instanceMethod(); +Example.staticMethod(); + +// Accessing members +console.log(ex.instanceField); // 1 +console.log(Example.staticField); // 2 +// console.log(ex.#privateField); // SyntaxError! +``` + + +## Method Overriding and Polymorphism + + +```java !! java +// Java - Method overriding +public class Animal { + public void speak() { + System.out.println("Animal sound"); + } + + public void perform() { + speak(); // Calls overridden version in subclass + } +} + +public class Dog extends Animal { + @Override + public void speak() { + System.out.println("Woof!"); + } +} + +Dog dog = new Dog(); +dog.perform(); // "Woof!" (polymorphic call) +``` + +```javascript !! js +// JavaScript - Method overriding (works similarly) + +class Animal { + speak() { + console.log("Animal sound"); + } + + perform() { + this.speak(); // Polymorphic - calls subclass version + } +} + +class Dog extends Animal { + speak() { + console.log("Woof!"); + } +} + +const dog = new Dog(); +dog.perform(); // "Woof!" + +// Explicit parent method call +class Dog2 extends Animal { + speak() { + super.speak(); // "Animal sound" + console.log("Woof!"); + } +} + +const dog2 = new Dog2(); +dog2.speak(); // "Animal sound" then "Woof!" +``` + + +## Common Patterns + +### Pattern 1: Singleton + + +```java !! java +// Java - Singleton +public class Database { + private static Database instance; + + private Database() { + // Private constructor + } + + public static synchronized Database getInstance() { + if (instance == null) { + instance = new Database(); + } + return instance; + } +} +``` + +```javascript !! js +// JavaScript - Singleton with class + +class Database { + constructor() { + if (Database.instance) { + return Database.instance; + } + this.connection = "connected"; + Database.instance = this; + } + + static getInstance() { + if (!Database.instance) { + Database.instance = new Database(); + } + return Database.instance; + } +} + +const db1 = Database.getInstance(); +const db2 = Database.getInstance(); +console.log(db1 === db2); // true + +// Simpler: Use object literal +const config = Object.freeze({ + apiUrl: "https://api.example.com", + timeout: 5000 +}); +``` + + +### Pattern 2: Mixin Pattern + + +```java !! java +// Java - Multiple inheritance not supported +// Use interfaces for similar behavior +interface Flyable { + void fly(); +} + +interface Swimmable { + void swim(); +} + +class Duck implements Flyable, Swimmable { + public void fly() { /* ... */ } + public void swim() { /* ... */ } +} +``` + +```javascript !! js +// JavaScript - Mixin pattern + +const Flyable = { + fly() { + console.log("Flying!"); + } +}; + +const Swimmable = { + swim() { + console.log("Swimming!"); + } +}; + +class Duck {} +Object.assign(Duck.prototype, Flyable, Swimmable); + +const duck = new Duck(); +duck.fly(); // "Flying!" +duck.swim(); // "Swimming!" + +// Mixin factory function +function mix(...mixins) { + return function(targetClass) { + Object.assign(targetClass.prototype, ...mixins); + return targetClass; + }; +} + +class Bird {} +const MixedBird = mix(Flyable)(Bird); + +const bird = new MixedBird(); +bird.fly(); // "Flying!" +``` + + +### Pattern 3: Factory with Classes + + +```javascript !! js +// Abstract base class +class Vehicle { + constructor(make, model) { + if (this.constructor === Vehicle) { + throw new Error("Abstract class cannot be instantiated"); + } + this.make = make; + this.model = model; + } + + // Abstract method + start() { + throw new Error("Must implement start()"); + } +} + +class Car extends Vehicle { + start() { + console.log(`${this.make} ${this.model} is starting`); + } +} + +class Motorcycle extends Vehicle { + start() { + console.log(`${this.make} ${this.model} vroom!`); + } +} + +// Factory function +function createVehicle(type, make, model) { + switch (type) { + case "car": + return new Car(make, model); + case "motorcycle": + return new Motorcycle(make, model); + default: + throw new Error("Unknown vehicle type"); + } +} + +const car = createVehicle("car", "Toyota", "Camry"); +car.start(); // "Toyota Camry is starting" +``` + + +## Best Practices + + +```java !! java +// Java: Clear encapsulation +public class User { + private final String name; // Immutable + private int age; + + public User(String name, int age) { + this.name = Objects.requireNonNull(name); + this.age = age; + } + + public String getName() { + return name; + } + + // Validation in setters + public void setAge(int age) { + if (age < 0) { + throw new IllegalArgumentException("Age cannot be negative"); + } + this.age = age; + } +} +``` + +```javascript !! js +// JavaScript: Similar principles + +// 1. Use classes for objects with behavior +class User { + // Use private fields (ES2022+) + #name; + #age; + + constructor(name, age) { + this.#name = name; + this.#age = age; + } + + get name() { + return this.#name; + } + + get age() { + return this.#age; + } + + set age(value) { + if (value >= 0) { + this.#age = value; + } + } +} + +// 2. Use object literals for simple data +const config = { apiUrl: "https://api.example.com" }; + +// 3. Prefer composition over inheritance +// Good +class TodoApp { + constructor(storage, logger) { + this.storage = storage; + this.logger = logger; + } +} + +// Avoid: Deep inheritance chains +// Bad +class Animal {} +class Mammal extends Animal {} +class Dog extends Mammal {} +class Labrador extends Dog {} + +// 4. Use static methods for utility functions +class MathUtils { + static clamp(value, min, max) { + return Math.min(Math.max(value, min), max); + } +} + +// 5. Use getters/setters for computed or validated properties +class Temperature { + #celsius; + + get celsius() { + return this.#celsius; + } + + set celsius(value) { + this.#celsius = value; + } + + get fahrenheit() { + return (this.#celsius * 9/5) + 32; + } + + set fahrenheit(value) { + this.#celsius = (value - 32) * 5/9; + } +} + +// 6. Always call super() in subclass constructors +class Child extends Parent { + constructor(name) { + super(name); // Must be first line + // ... + } +} +``` + + +## Exercises + +### Exercise 1: Create a Class +```javascript +// Create a Rectangle class with: +// - width, height properties +// - area() method +// - perimeter() method +// - get isSquare() getter +``` + +### Exercise 2: Inheritance +```javascript +// Create Shape base class +// Create Circle and Rectangle subclasses +// Each should have area() method +``` + +### Exercise 3: Private Fields +```javascript +// Create BankAccount class with: +// - Private #balance field +// - deposit() and withdraw() methods +// - Validation for negative amounts +``` + +### Exercise 4: Static Methods +```javascript +// Create MathUtility class with: +// - Static methods: clamp(), lerp(), randomInRange() +// - Use them without instantiation +``` + +## Summary + +### Key Takeaways + +1. **Class Syntax:** + - Syntactic sugar over prototypes + - Not hoisted (unlike functions) + - Can be named or anonymous + +2. **Inheritance:** + - `extends` for inheritance + - `super` to call parent + - Must call `super()` before `this` + +3. **Static Members:** + - `static` keyword + - Accessed via class name + - Don't require instantiation + +4. **Private Fields:** + - `#` prefix (ES2022+) + - Truly encapsulated + - Cannot be accessed outside class + +5. **Getters/Setters:** + - Computed properties + - Validation + - Encapsulation + +6. **Best Practices:** + - Classes for behavior, objects for data + - Prefer composition over deep inheritance + - Use private fields for true encapsulation + - Static methods for utilities + +### Comparison Table: Java vs JavaScript + +| Feature | Java | JavaScript | +|---------|------|------------| +| **Syntax** | `class Name { }` | Same (ES6+) | +| **Inheritance** | `extends` (single) | `extends` (single) | +| **Interfaces** | Yes | No (use protocols) | +| **Abstract** | `abstract` keyword | Manual enforcement | +| **Private** | `private` keyword | `#` prefix (ES2022+) | +| **Protected** | `protected` keyword | Convention (`_name`) | +| **Static** | `static` keyword | Same | +| **Final** | `final` keyword | No (use Object.freeze) | +| **Super** | `super.method()` | Same | +| **Constructor** | Same name as class | `constructor()` | + +## What's Next? + +You've mastered JavaScript classes! Next up is **Module 8: Prototypes and Prototype Chain**, where we'll explore: + +- How prototypes work under the hood +- Prototype chain lookup +- Object.create() and prototype inheritance +- Constructor functions +- The relationship between classes and prototypes +- When to use prototypes vs classes + +Ready to understand JavaScript's prototype system? Let's continue! diff --git a/content/docs/java2js/module-07-classes.zh-tw.mdx b/content/docs/java2js/module-07-classes.zh-tw.mdx new file mode 100644 index 0000000..ae6309a --- /dev/null +++ b/content/docs/java2js/module-07-classes.zh-tw.mdx @@ -0,0 +1,361 @@ +--- +title: "Module 7: Classes" +description: "掌握 JavaScript ES6 類別、繼承和物件導向程式設計" +--- + +## Module 7: Classes + +雖然 JavaScript 基於原型,但 ES6 引入了類別語法,為 Java 開發者提供熟悉的物件導向程式設計體驗。讓我們學習 JavaScript 類別。 + +## Learning Objectives + +完成本模組後,你將: +✅ 理解 ES6 類別語法 +✅ 掌握建構函數和方法 +✅ 學習繼承和 extends +✅ 理解 static 方法 +✅ 掌握 getters 和 setters +✅ 了解類別與原型的關係 + +## Classes Comparison + + +```java !! java +// Java - Traditional class +public class User { + private String name; + private int age; + + public User(String name, int age) { + this.name = name; + this.age = age; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public void greet() { + System.out.println("Hello, I'm " + name); + } +} + +User user = new User("Alice", 25); +user.greet(); +``` + +```javascript !! js +// JavaScript - ES6 class +class User { + constructor(name, age) { + this.name = name; + this.age = age; + } + + getName() { + return this.name; + } + + setName(name) { + this.name = name; + } + + greet() { + console.log(`Hello, I'm ${this.name}`); + } +} + +const user = new User("Alice", 25); +user.greet(); // "Hello, I'm Alice" + +// Classes are syntactic sugar over prototypes +// Under the hood, they still use prototype-based inheritance +``` + + +## Class Declarations vs Expressions + + +```java !! java +// Java - Only declarations +public class MyClass { + // ... +} + +// Anonymous classes +Runnable r = new Runnable() { + public void run() { + System.out.println("Running"); + } +}; +``` + +```javascript !! js +// JavaScript - Declarations +class User { + constructor(name) { + this.name = name; + } +} + +// Hoisted? No(unlike function declarations) +// const user = new User(); // ReferenceError + +class User2 { + // ... +} + +// Expressions +const UserClass = class { + constructor(name) { + this.name = name; + } +}; + +const user = new UserClass("Alice"); + +// Named class expression(useful for recursion) +const Factory = class User { + constructor(name) { + this.name = name; + } + + static create(name) { + return new User(name); + } +}; +``` + + +## Inheritance + + +```java !! java +// Java - extends +public class Animal { + public void speak() { + System.out.println("Some sound"); + } +} + +public class Dog extends Animal { + @Override + public void speak() { + System.out.println("Woof!"); + } +} + +Dog dog = new Dog(); +dog.speak(); // "Woof!" +``` + +```javascript !! js +// JavaScript - extends +class Animal { + speak() { + console.log("Some sound"); + } +} + +class Dog extends Animal { + speak() { + console.log("Woof!"); + } +} + +const dog = new Dog(); +dog.speak(); // "Woof!" + +// super keyword +class Dog2 extends Animal { + speak() { + super.speak(); // Call parent method + console.log("Woof!"); + } +} + +// Calling super constructor +class Animal2 { + constructor(name) { + this.name = name; + } +} + +class Dog3 extends Animal2 { + constructor(name, breed) { + super(name); // Must call super before using this + this.breed = breed; + } +} + +// instanceof check +console.log(dog instanceof Dog); // true +console.log(dog instanceof Animal); // true +``` + + +## Static Members + + +```java !! java +// Java - static members +public class MathUtil { + public static final double PI = 3.14159; + + public static int add(int a, int b) { + return a + b; + } +} + +int sum = MathUtil.add(5, 3); +double pi = MathUtil.PI; +``` + +```javascript !! js +// JavaScript - static members +class MathUtil { + static PI = 3.14159; + + static add(a, b) { + return a + b; + } +} + +const sum = MathUtil.add(5, 3); +const pi = MathUtil.PI; + +// Static methods cannot access instance members +class Example { + static count = 0; + + constructor() { + Example.count++; // Access static via class name + } + + static getCount() { + return Example.count; // Cannot use this.count + } +} + +const e1 = new Example(); +const e2 = new Example(); +console.log(Example.getCount()); // 2 +``` + + +## Getters and Setters + + +```java !! java +// Java - Getters and setters +public class User { + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} +``` + +```javascript !! js +// JavaScript - Getters and setters +class User { + constructor() { + this._name = ""; + } + + get name() { + return this._name.toUpperCase(); + } + + set name(value) { + if (value.length > 0) { + this._name = value; + } + } +} + +const user = new User(); +user.name = "alice"; +console.log(user.name); // "ALICE" + +// Computed properties +class Rectangle { + constructor(width, height) { + this.width = width; + this.height = height; + } + + get area() { + return this.width * this.height; + } +} + +const rect = new Rectangle(10, 20); +console.log(rect.area); // 200 +``` + + +## Best Practices + + +```javascript !! js +// 1. Use classes for objects with behavior +class UserService { + constructor(apiUrl) { + this.apiUrl = apiUrl; + } + + async getUser(id) { + const response = await fetch(`${this.apiUrl}/users/${id}`); + return response.json(); + } +} + +// 2. Use factory methods for complex construction +class User { + constructor(name, age) { + this.name = name; + this.age = age; + } + + static fromJSON(json) { + return new User(json.name, json.age); + } +} + +// 3. Prefer composition over deep inheritance +class Logger { + log(message) { + console.log(message); + } +} + +class UserService { + constructor(logger) { + this.logger = logger; + } +} +``` + + +## Summary + +### Key Takeaways + +1. **Classes:** 原型的語法糖 +2. **Inheritance:** extends 和 super +3. **Static:** 類別層級成員 +4. **Getters/Setters:** 計算屬性 + +## What's Next? + +接下來是 **Module 8: Prototypes** - 深入了解原型系統! diff --git a/content/docs/java2js/module-08-prototypes.mdx b/content/docs/java2js/module-08-prototypes.mdx new file mode 100644 index 0000000..15030fa --- /dev/null +++ b/content/docs/java2js/module-08-prototypes.mdx @@ -0,0 +1,971 @@ +--- +title: "Module 8: Prototypes and Prototype Chain" +description: "Understand JavaScript's prototype system and how it enables inheritance" +--- + +## Module 8: Prototypes and Prototype Chain + +Now that you understand ES6 classes, let's look under the hood at how JavaScript actually works. Classes are syntactic sugar over JavaScript's prototype-based inheritance system. Understanding prototypes is crucial for advanced JavaScript development. + +## Learning Objectives + +By the end of this module, you will: +✅ Understand what prototypes are +✅ Master the prototype chain lookup +✅ Learn Object.create() and prototype inheritance +✅ Understand constructor functions +✅ Know the relationship between classes and prototypes +✅ Learn when to use prototypes vs classes + +## Prototypes vs Classes + +Java uses class-based inheritance, while JavaScript uses prototype-based inheritance: + + +```java !! java +// Java - Class-based inheritance +public class Animal { + public void speak() { + System.out.println("Some sound"); + } +} + +public class Dog extends Animal { + @Override + public void speak() { + System.out.println("Woof!"); + } +} + +// Instance methods are defined in class +// Each instance has same methods +Animal animal = new Dog(); +animal.speak(); // "Woof!" +``` + +```javascript !! js +// JavaScript - Prototype-based inheritance +// Objects inherit directly from other objects + +const animal = { + speak() { + console.log("Some sound"); + } +}; + +const dog = Object.create(animal); +dog.speak = function() { + console.log("Woof!"); +}; + +dog.speak(); // "Woof!" +// But can still access animal's methods via prototype +delete dog.speak; +dog.speak(); // "Some sound" (from prototype!) + +// Classes are just sugar over prototypes +class Animal2 { + speak() { + console.log("Some sound"); + } +} + +class Dog2 extends Animal2 { + speak() { + console.log("Woof!"); + } +} + +const dog2 = new Dog2(); +console.log(Object.getPrototypeOf(dog2) === Dog2.prototype); // true +console.log(Object.getPrototypeOf(Dog2.prototype) === Animal2.prototype); // true +``` + + +## The Prototype Chain + +Every object has an internal link to another object called its prototype. This forms a chain: + + +```java !! java +// Java - Class hierarchy +Object -> Animal -> Dog + +// Method lookup: Check class, then parent class +Dog dog = new Dog(); +// dog.toString() looks in Dog, then Animal, then Object +``` + +```javascript !! js +// JavaScript - Prototype chain +const grandParent = { + name: "GrandParent", + greet() { + console.log(`Hello from ${this.name}`); + } +}; + +const parent = Object.create(grandParent); +parent.name = "Parent"; + +const child = Object.create(parent); +child.name = "Child"; + +// Lookup walks up the chain +child.greet(); // "Hello from Child" +// 1. Check child - has name property +// 2. Check child for greet() - not found +// 3. Check parent - no greet() +// 4. Check grandParent - found greet()! + +console.log(child.hasOwnProperty("name")); // true +console.log(child.hasOwnProperty("greet")); // false (inherited) + +// Full prototype chain +console.log(Object.getPrototypeOf(child) === parent); // true +console.log(Object.getPrototypeOf(parent) === grandParent); // true +console.log(Object.getPrototypeOf(grandParent) === Object.prototype); // true +console.log(Object.getPrototypeOf(Object.prototype) === null); // true (end of chain) + +// Property lookup with hasOwnProperty +for (let key in child) { + console.log(key, child.hasOwnProperty(key)); + // name true (own property) + // greet false (inherited) +} +``` + + +## Object.create() + +Object.create() creates a new object with a specified prototype: + + +```java !! java +// Java - Inheritance requires classes +public class Animal { + public void speak() { + System.out.println("Sound"); + } +} + +public class Dog extends Animal { + // Dog inherits from Animal +} +``` + +```javascript !! js +// JavaScript - Direct object inheritance + +// Create object with specific prototype +const animal = { + speak() { + console.log("Some sound"); + } +}; + +const dog = Object.create(animal); +dog.bark = function() { + console.log("Woof!"); +}; + +dog.speak(); // "Some sound" (from prototype) +dog.bark(); // "Woof!" (own property) + +console.log(Object.getPrototypeOf(dog) === animal); // true + +// Create null prototype (no inherited methods) +const empty = Object.create(null); +empty.toString(); // TypeError! (no Object.prototype methods) + +// Object.create with property descriptors +const person = Object.create(Object.prototype, { + name: { + value: "John", + writable: true, + enumerable: true, + configurable: true + }, + age: { + value: 25, + writable: true + } +}); + +console.log(person.name); // "John" + +// Practical: Create object with default methods +const withLogging = { + log(method) { + return function(...args) { + console.log(`Calling ${method} with:`, args); + const result = this[method](...args); + console.log(`Result:`, result); + return result; + }; + } +}; + +const calculator = Object.create(withLogging); +calculator.add = function(a, b) { + return a + b; +}; + +calculator.multiply = function(a, b) { + return a * b; +}; + +calculator.log("add")(5, 3); // Logs then returns 8 +``` + + +## Constructor Functions + +Before ES6 classes, JavaScript used constructor functions: + + +```java !! java +// Java - Class constructors +public class User { + private String name; + + public User(String name) { + this.name = name; + } +} + +User user = new User("John"); +``` + +```javascript !! js +// JavaScript - Constructor functions (pre-ES6) + +function User(name) { + this.name = name; +} + +// Methods added to prototype +User.prototype.greet = function() { + console.log(`Hello, I'm ${this.name}`); +}; + +User.prototype.getAge = function() { + return this.age; +}; + +// Create instance with 'new' +const user = new User("John"); +user.greet(); // "Hello, I'm John" + +// What 'new' does: +// 1. Creates new object +// 2. Sets object's __proto__ to constructor's prototype +// 3. Executes constructor with 'this' = new object +// 4. Returns new object (or explicit return) + +// Manual equivalent of 'new' +function createFromConstructor(Constructor, ...args) { + const obj = Object.create(Constructor.prototype); + const result = Constructor.apply(obj, args); + return result instanceof Object ? result : obj; +} + +const user2 = createFromConstructor(User, "Jane"); +user2.greet(); // "Hello, I'm Jane" + +// Check instance +console.log(user instanceof User); // true +console.log(user instanceof Object); // true + +// Constructor property +console.log(User.prototype.constructor === User); // true +console.log(user.constructor === User); // true + +// Arrow functions cannot be constructors +const BadUser = (name) => { + this.name = name; +}; + +// const bad = new BadUser("John"); // TypeError! +``` + + +## Prototype Properties vs Instance Properties + + +```java !! java +// Java - Instance methods (same for all instances) +public class User { + public void greet() { + // Same method for all User instances + } +} + +// Static methods (class-level) +public static User create(String name) { + return new User(name); +} +``` + +```javascript ^^ js +// JavaScript - Prototype vs instance + +// Methods on prototype (shared) +function User(name) { + this.name = name; // Instance property (unique per instance) +} + +// Prototype property (shared) +User.prototype.species = "Homo sapiens"; + +// Prototype method (shared) +User.prototype.greet = function() { + console.log(`Hi, I'm ${this.name}`); +}; + +const user1 = new User("John"); +const user2 = new User("Jane"); + +console.log(user1.greet === user2.greet); // true (same function) +console.log(user1.species === user2.species); // true + +// Override prototype property +user1.species = "Alien"; +console.log(user1.species); // "Alien" (instance property) +console.log(user2.species); // "Homo sapiens" (prototype) + +// Delete to reveal prototype again +delete user1.species; +console.log(user1.species); // "Homo sapiens" (from prototype) + +// When to use prototype vs instance: +// - Methods: prototype (shared) +// - Instance data: constructor (unique) +// - Class constants: prototype or static + +// Modern equivalent with classes +class User2 { + static species = "Homo sapiens"; // Static (class-level) + + constructor(name) { + this.name = name; // Instance + } + + greet() { // Prototype (shared) + console.log(`Hi, I'm ${this.name}`); + } +} +``` + + +## Modifying Prototypes + +You can modify prototypes even after objects are created: + + +```java !! java +// Java - Cannot modify classes at runtime +// (except with bytecode manipulation) +``` + +```javascript !! js +// JavaScript - Prototypes are modifiable + +const arr = [1, 2, 3]; +// arr.sum() doesn't exist + +// Add method to Array prototype +Array.prototype.sum = function() { + return this.reduce((a, b) => a + b, 0); +}; + +console.log(arr.sum()); // 6 + +// All arrays now have sum() +const arr2 = [4, 5, 6]; +console.log(arr2.sum()); // 15 + +// ⚠️ Be careful modifying built-in prototypes! +// Can break code or cause conflicts + +// Better: Create your own class +class MyArray extends Array { + sum() { + return this.reduce((a, b) => a + b, 0); + } +} + +const myArr = new MyArray(1, 2, 3); +console.log(myArr.sum()); // 6 + +// Polyfill example (adding missing methods) +if (!Array.prototype.first) { + Array.prototype.first = function() { + return this[0]; + }; +} + +if (!String.prototype.capitalize) { + String.prototype.capitalize = function() { + return this.charAt(0).toUpperCase() + this.slice(1); + }; +} + +console.log("hello".capitalize()); // "Hello" +``` + + +## Prototype Inheritance Patterns + +### Pattern 1: Prototype Delegation + + +```javascript !! js +// Simple prototype chain +const animal = { + init(name) { + this.name = name; + }, + speak() { + console.log(`${this.name} makes a sound`); + } +}; + +const dog = Object.create(animal); +dog.bark = function() { + console.log(`${this.name} barks`); +}; + +const myDog = Object.create(dog); +myDog.init("Buddy"); + +myDog.speak(); // "Buddy makes a sound" (from animal) +myDog.bark(); // "Buddy barks" (from dog) + +console.log(myDog instanceof Object); // true +// No way to check instanceof for prototype chains without constructors +``` + + +### Pattern 2: Constructor Inheritance + + +```javascript !! js +// Parent constructor +function Animal(name) { + this.name = name; +} + +Animal.prototype.speak = function() { + console.log(`${this.name} makes a sound`); +}; + +// Child constructor +function Dog(name, breed) { + Animal.call(this, name); // Call parent constructor + this.breed = breed; +} + +// Inherit prototype +Dog.prototype = Object.create(Animal.prototype); +Dog.prototype.constructor = Dog; // Fix constructor + +// Add child methods +Dog.prototype.bark = function() { + console.log(`${this.name} barks`); +}; + +// Override parent method +Dog.prototype.speak = function() { + Animal.prototype.speak.call(this); // Call parent + this.bark(); +}; + +const dog = new Dog("Buddy", "Labrador"); +dog.speak(); // "Buddy makes a sound" then "Buddy barks" + +console.log(dog instanceof Dog); // true +console.log(dog instanceof Animal); // true + +// Modern equivalent (classes are easier!) +class Animal2 { + constructor(name) { + this.name = name; + } + speak() { + console.log(`${this.name} makes a sound`); + } +} + +class Dog2 extends Animal2 { + constructor(name, breed) { + super(name); + this.breed = breed; + } + bark() { + console.log(`${this.name} barks`); + } + speak() { + super.speak(); + this.bark(); + } +} +``` + + +### Pattern 3: Functional Inheritance + + +```java !! java +// Java - Composition over inheritance +interface Flyable { + void fly(); +} + +class Bird implements Flyable { + public void fly() { + System.out.println("Flying"); + } +} + +class Bat implements Flyable { + public void fly() { + System.out.println("Flying"); + } +} +``` + +```javascript ^^ js +// JavaScript - Mixins with prototypes + +const Flyable = { + fly() { + console.log("Flying!"); + } +}; + +const Swimmable = { + swim() { + console.log("Swimming!"); + } +}; + +function Duck(name) { + this.name = name; +} + +// Mix in capabilities +Object.assign(Duck.prototype, Flyable, Swimmable); + +const duck = new Duck("Donald"); +duck.fly(); // "Flying!" +duck.swim(); // "Swimming!" + +// Or use Object.create with multiple prototypes +const flyingSwimmer = Object.assign( + Object.create(Object.prototype), + Flyable, + Swimmable +); + +const duck2 = Object.create(flyingSwimmer); +duck2.name = "Daisy"; +duck2.fly(); // "Flying!" +``` + + +## Prototypes vs Classes + + +```javascript !! js +// Prototypes (flexible, dynamic) +const animal = { + speak() { + console.log("Sound"); + } +}; + +const dog = Object.create(animal); + +// Can modify prototype anytime +animal.speak = function() { + console.log("Loud sound"); +}; + +dog.speak(); // "Loud sound" (reflects prototype change) + +// Classes (structured, familiar) +class Animal2 { + speak() { + console.log("Sound"); + } +} + +class Dog2 extends Animal2 { + // Can't easily modify Animal2.prototype after creation + // Better to use inheritance or override methods +} + +// When to use each: + +// Use prototypes when: +// - Need dynamic modification +// - Creating simple object hierarchies +// - Want memory efficiency (shared methods) +// - Don't need 'new' operator + +const utils = { + log(msg) { + console.log(msg); + }, + warn(msg) { + console.warn(msg); + } +}; + +// Use classes when: +// - Need constructor logic +// - Want familiar OOP structure +// - Need instanceof checks +// - Building complex applications + +class UserService { + constructor(apiUrl) { + this.apiUrl = apiUrl; + } + + async getUser(id) { + const response = await fetch(`${this.apiUrl}/users/${id}`); + return response.json(); + } +} +``` + + +## Performance Considerations + + +```javascript !! js +// Prototype lookup has a cost +const obj = { + a: 1, + b: 2, + c: 3 +}; + +// Direct property access (fast) +console.log(obj.a); + +// Deep prototype chain (slower) +const proto1 = { x: 1 }; +const proto2 = Object.create(proto1); +const proto3 = Object.create(proto2); +const obj2 = Object.create(proto3); + +// Many lookups needed to find x +console.log(obj2.x); // Checks obj2, proto3, proto2, proto1 + +// Shallow prototype chain (faster) +const obj3 = Object.create({ x: 1 }); +console.log(obj3.x); // Only one lookup + +// Caching frequently accessed prototype properties +function fastAccess(obj) { + const proto = Object.getPrototypeOf(obj); + const method = proto.method; + + // Use cached reference + return function() { + return method.call(obj); + }; +} + +// Own properties are faster than prototype lookups +const fast = { name: "Fast" }; +const slow = Object.create({ name: "Slow" }); + +// Direct access vs prototype lookup +console.time("direct"); +for (let i = 0; i < 1000000; i++) { + fast.name; +} +console.timeEnd("direct"); // ~2ms + +console.time("prototype"); +for (let i = 0; i < 1000000; i++) { + slow.name; +} +console.timeEnd("prototype"); // ~3ms (slightly slower) +``` + + +## Common Pitfalls + +### Pitfall 1: Modifying Object.prototype + + +```javascript !! js +// ❌ BAD: Modifying Object.prototype affects everything +Object.prototype.each = function(fn) { + for (let key in this) { + fn(this[key], key); + } +}; + +// Now even empty objects have 'each' +const empty = {}; +empty.each(x => console.log(x)); // Unexpected! + +// Also breaks for...in loops +for (let key in empty) { + console.log(key); // "each" (unwanted!) +} + +// ✅ GOOD: Use your own base object +const MyBaseObject = { + each(fn) { + for (let key in this) { + if (this.hasOwnProperty(key)) { + fn(this[key], key); + } + } + } +}; + +const myObj = Object.create(MyyBaseObject); +myObj.each(x => console.log(x)); // Works as expected +``` + + +### Pitfall 2: Prototype Pollution + + +```javascript !! js +// Security vulnerability: Prototype pollution + +function merge(target, source) { + for (let key in source) { + if (typeof source[key] === "object" && typeof target[key] === "object") { + merge(target[key], source[key]); + } else { + target[key] = source[key]; + } + } + return target; +} + +// Malicious input +const malicious = { + __proto__: { + isAdmin: true + } +}; + +const empty = {}; +merge(empty, malicious); + +// Now all objects have isAdmin = true! +console.log(({}).isAdmin); // true (security issue!) + +// ✅ GOOD: Validate keys +function safeMerge(target, source) { + for (let key in source) { + // Skip dangerous keys + if (key === "__proto__" || key === "constructor" || key === "prototype") { + continue; + } + // ... rest of merge logic + } + return target; +} + +// Or use Object.create(null) for pure data objects +const pure = Object.create(null); +// pure has no prototype, so no prototype pollution possible +``` + + +## Best Practices + + +```java !! java +// Java: Clear class hierarchy +public abstract class Animal { + public abstract void speak(); +} + +public class Dog extends Animal { + public void speak() { + System.out.println("Woof!"); + } +} +``` + +```javascript !! js +// JavaScript: Choose the right approach + +// 1. Use classes for most cases (modern, familiar) +class Animal { + speak() { + console.log("Sound"); + } +} + +class Dog extends Animal { + speak() { + console.log("Woof!"); + } +} + +// 2. Use Object.create for simple prototypes +const animal = { + speak() { + console.log("Sound"); + } +}; + +const dog = Object.create(animal); + +// 3. Don't modify built-in prototypes +// ❌ Array.prototype.first = function() { return this[0]; }; + +// ✅ Create subclass or utility function +class MyArray extends Array { + first() { + return this[0]; + } +} + +// 4. Use hasOwnProperty in for...in +for (let key in obj) { + if (obj.hasOwnProperty(key)) { + // Only own properties + } +} + +// Or use Object.keys() +Object.keys(obj).forEach(key => { + // Only own enumerable properties +}); + +// 5. Understand instanceof limitations +// Only works with constructors +const obj = Object.create(null); +obj instanceof Object; // false (no prototype chain) + +// Use Object.getPrototypeOf() instead +console.log(Object.getPrototypeOf(obj) === null); // true + +// 6. Prefer composition over deep prototype chains +// Bad: Deep inheritance +class A {} +class B extends A {} +class C extends B {} +class D extends C {} + +// Good: Composition +class D { + constructor(a, b, c) { + this.a = a; + this.b = b; + this.c = c; + } +} +``` + + +## Exercises + +### Exercise 1: Prototype Chain +```javascript +const grandParent = { a: 1 }; +const parent = Object.create(grandParent); +parent.b = 2; +const child = Object.create(parent); +child.c = 3; + +// What will these output? +console.log(child.a); +console.log(child.b); +console.log(child.hasOwnProperty("a")); +console.log(child.hasOwnProperty("b")); +``` + +### Exercise 2: Create with Object.create +```javascript +// Create a vehicle prototype with start() and stop() methods +// Create car and motorcycle objects that inherit from vehicle +``` + +### Exercise 3: Constructor Function +```javascript +// Create a Book constructor function +// Add methods to prototype: getTitle(), getAuthor() +// Create instances and test +``` + +### Exercise 4: Prototype Chain Lookup +```javascript +// Create prototype chain of 3 levels +// Add a method that only exists on level 3 +// Test that level 1 can access it +``` + +## Summary + +### Key Takeaways + +1. **Prototypes:** + - Every object has a prototype + - Prototype chain for property lookup + - Enables inheritance without classes + +2. **Object.create():** + - Creates object with specific prototype + - Supports property descriptors + - Can create null-prototype objects + +3. **Constructor Functions:** + - Pre-ES6 pattern for classes + - Methods added to prototype + - 'new' keyword creates instances + +4. **Prototype Chain:** + - Lookups walk up the chain + - Own properties found first + - Ends at Object.prototype then null + +5. **Classes vs Prototypes:** + - Classes are sugar over prototypes + - Classes are clearer for most cases + - Prototypes offer more flexibility + +6. **Best Practices:** + - Don't modify built-in prototypes + - Use classes for structure + - Use Object.create for simple inheritance + - Understand prototype performance + +### Comparison Table: Java vs JavaScript + +| Feature | Java | JavaScript | +|---------|------|------------| +| **Inheritance** | Class-based | Prototype-based | +| **Chain** | Class hierarchy | Prototype chain | +| **Methods** | In class definition | On prototype object | +| **Lookup** | Compile-time | Runtime (chain walk) | +| **Modification** | Cannot modify at runtime | Can modify anytime | +| **Checking** | `instanceof` | `instanceof` or `Object.getPrototypeOf()` | + +## What's Next? + +You've mastered JavaScript's prototype system! Next up is **Module 9: This and Context**, where we'll explore: + +- How `this` binding works +- Call, apply, and bind methods +- Arrow functions and lexical this +- This in different contexts +- Common this-related pitfalls +- Best practices for managing context + +Ready to master the `this` keyword? Let's continue! diff --git a/content/docs/java2js/module-08-prototypes.zh-cn.mdx b/content/docs/java2js/module-08-prototypes.zh-cn.mdx new file mode 100644 index 0000000..15030fa --- /dev/null +++ b/content/docs/java2js/module-08-prototypes.zh-cn.mdx @@ -0,0 +1,971 @@ +--- +title: "Module 8: Prototypes and Prototype Chain" +description: "Understand JavaScript's prototype system and how it enables inheritance" +--- + +## Module 8: Prototypes and Prototype Chain + +Now that you understand ES6 classes, let's look under the hood at how JavaScript actually works. Classes are syntactic sugar over JavaScript's prototype-based inheritance system. Understanding prototypes is crucial for advanced JavaScript development. + +## Learning Objectives + +By the end of this module, you will: +✅ Understand what prototypes are +✅ Master the prototype chain lookup +✅ Learn Object.create() and prototype inheritance +✅ Understand constructor functions +✅ Know the relationship between classes and prototypes +✅ Learn when to use prototypes vs classes + +## Prototypes vs Classes + +Java uses class-based inheritance, while JavaScript uses prototype-based inheritance: + + +```java !! java +// Java - Class-based inheritance +public class Animal { + public void speak() { + System.out.println("Some sound"); + } +} + +public class Dog extends Animal { + @Override + public void speak() { + System.out.println("Woof!"); + } +} + +// Instance methods are defined in class +// Each instance has same methods +Animal animal = new Dog(); +animal.speak(); // "Woof!" +``` + +```javascript !! js +// JavaScript - Prototype-based inheritance +// Objects inherit directly from other objects + +const animal = { + speak() { + console.log("Some sound"); + } +}; + +const dog = Object.create(animal); +dog.speak = function() { + console.log("Woof!"); +}; + +dog.speak(); // "Woof!" +// But can still access animal's methods via prototype +delete dog.speak; +dog.speak(); // "Some sound" (from prototype!) + +// Classes are just sugar over prototypes +class Animal2 { + speak() { + console.log("Some sound"); + } +} + +class Dog2 extends Animal2 { + speak() { + console.log("Woof!"); + } +} + +const dog2 = new Dog2(); +console.log(Object.getPrototypeOf(dog2) === Dog2.prototype); // true +console.log(Object.getPrototypeOf(Dog2.prototype) === Animal2.prototype); // true +``` + + +## The Prototype Chain + +Every object has an internal link to another object called its prototype. This forms a chain: + + +```java !! java +// Java - Class hierarchy +Object -> Animal -> Dog + +// Method lookup: Check class, then parent class +Dog dog = new Dog(); +// dog.toString() looks in Dog, then Animal, then Object +``` + +```javascript !! js +// JavaScript - Prototype chain +const grandParent = { + name: "GrandParent", + greet() { + console.log(`Hello from ${this.name}`); + } +}; + +const parent = Object.create(grandParent); +parent.name = "Parent"; + +const child = Object.create(parent); +child.name = "Child"; + +// Lookup walks up the chain +child.greet(); // "Hello from Child" +// 1. Check child - has name property +// 2. Check child for greet() - not found +// 3. Check parent - no greet() +// 4. Check grandParent - found greet()! + +console.log(child.hasOwnProperty("name")); // true +console.log(child.hasOwnProperty("greet")); // false (inherited) + +// Full prototype chain +console.log(Object.getPrototypeOf(child) === parent); // true +console.log(Object.getPrototypeOf(parent) === grandParent); // true +console.log(Object.getPrototypeOf(grandParent) === Object.prototype); // true +console.log(Object.getPrototypeOf(Object.prototype) === null); // true (end of chain) + +// Property lookup with hasOwnProperty +for (let key in child) { + console.log(key, child.hasOwnProperty(key)); + // name true (own property) + // greet false (inherited) +} +``` + + +## Object.create() + +Object.create() creates a new object with a specified prototype: + + +```java !! java +// Java - Inheritance requires classes +public class Animal { + public void speak() { + System.out.println("Sound"); + } +} + +public class Dog extends Animal { + // Dog inherits from Animal +} +``` + +```javascript !! js +// JavaScript - Direct object inheritance + +// Create object with specific prototype +const animal = { + speak() { + console.log("Some sound"); + } +}; + +const dog = Object.create(animal); +dog.bark = function() { + console.log("Woof!"); +}; + +dog.speak(); // "Some sound" (from prototype) +dog.bark(); // "Woof!" (own property) + +console.log(Object.getPrototypeOf(dog) === animal); // true + +// Create null prototype (no inherited methods) +const empty = Object.create(null); +empty.toString(); // TypeError! (no Object.prototype methods) + +// Object.create with property descriptors +const person = Object.create(Object.prototype, { + name: { + value: "John", + writable: true, + enumerable: true, + configurable: true + }, + age: { + value: 25, + writable: true + } +}); + +console.log(person.name); // "John" + +// Practical: Create object with default methods +const withLogging = { + log(method) { + return function(...args) { + console.log(`Calling ${method} with:`, args); + const result = this[method](...args); + console.log(`Result:`, result); + return result; + }; + } +}; + +const calculator = Object.create(withLogging); +calculator.add = function(a, b) { + return a + b; +}; + +calculator.multiply = function(a, b) { + return a * b; +}; + +calculator.log("add")(5, 3); // Logs then returns 8 +``` + + +## Constructor Functions + +Before ES6 classes, JavaScript used constructor functions: + + +```java !! java +// Java - Class constructors +public class User { + private String name; + + public User(String name) { + this.name = name; + } +} + +User user = new User("John"); +``` + +```javascript !! js +// JavaScript - Constructor functions (pre-ES6) + +function User(name) { + this.name = name; +} + +// Methods added to prototype +User.prototype.greet = function() { + console.log(`Hello, I'm ${this.name}`); +}; + +User.prototype.getAge = function() { + return this.age; +}; + +// Create instance with 'new' +const user = new User("John"); +user.greet(); // "Hello, I'm John" + +// What 'new' does: +// 1. Creates new object +// 2. Sets object's __proto__ to constructor's prototype +// 3. Executes constructor with 'this' = new object +// 4. Returns new object (or explicit return) + +// Manual equivalent of 'new' +function createFromConstructor(Constructor, ...args) { + const obj = Object.create(Constructor.prototype); + const result = Constructor.apply(obj, args); + return result instanceof Object ? result : obj; +} + +const user2 = createFromConstructor(User, "Jane"); +user2.greet(); // "Hello, I'm Jane" + +// Check instance +console.log(user instanceof User); // true +console.log(user instanceof Object); // true + +// Constructor property +console.log(User.prototype.constructor === User); // true +console.log(user.constructor === User); // true + +// Arrow functions cannot be constructors +const BadUser = (name) => { + this.name = name; +}; + +// const bad = new BadUser("John"); // TypeError! +``` + + +## Prototype Properties vs Instance Properties + + +```java !! java +// Java - Instance methods (same for all instances) +public class User { + public void greet() { + // Same method for all User instances + } +} + +// Static methods (class-level) +public static User create(String name) { + return new User(name); +} +``` + +```javascript ^^ js +// JavaScript - Prototype vs instance + +// Methods on prototype (shared) +function User(name) { + this.name = name; // Instance property (unique per instance) +} + +// Prototype property (shared) +User.prototype.species = "Homo sapiens"; + +// Prototype method (shared) +User.prototype.greet = function() { + console.log(`Hi, I'm ${this.name}`); +}; + +const user1 = new User("John"); +const user2 = new User("Jane"); + +console.log(user1.greet === user2.greet); // true (same function) +console.log(user1.species === user2.species); // true + +// Override prototype property +user1.species = "Alien"; +console.log(user1.species); // "Alien" (instance property) +console.log(user2.species); // "Homo sapiens" (prototype) + +// Delete to reveal prototype again +delete user1.species; +console.log(user1.species); // "Homo sapiens" (from prototype) + +// When to use prototype vs instance: +// - Methods: prototype (shared) +// - Instance data: constructor (unique) +// - Class constants: prototype or static + +// Modern equivalent with classes +class User2 { + static species = "Homo sapiens"; // Static (class-level) + + constructor(name) { + this.name = name; // Instance + } + + greet() { // Prototype (shared) + console.log(`Hi, I'm ${this.name}`); + } +} +``` + + +## Modifying Prototypes + +You can modify prototypes even after objects are created: + + +```java !! java +// Java - Cannot modify classes at runtime +// (except with bytecode manipulation) +``` + +```javascript !! js +// JavaScript - Prototypes are modifiable + +const arr = [1, 2, 3]; +// arr.sum() doesn't exist + +// Add method to Array prototype +Array.prototype.sum = function() { + return this.reduce((a, b) => a + b, 0); +}; + +console.log(arr.sum()); // 6 + +// All arrays now have sum() +const arr2 = [4, 5, 6]; +console.log(arr2.sum()); // 15 + +// ⚠️ Be careful modifying built-in prototypes! +// Can break code or cause conflicts + +// Better: Create your own class +class MyArray extends Array { + sum() { + return this.reduce((a, b) => a + b, 0); + } +} + +const myArr = new MyArray(1, 2, 3); +console.log(myArr.sum()); // 6 + +// Polyfill example (adding missing methods) +if (!Array.prototype.first) { + Array.prototype.first = function() { + return this[0]; + }; +} + +if (!String.prototype.capitalize) { + String.prototype.capitalize = function() { + return this.charAt(0).toUpperCase() + this.slice(1); + }; +} + +console.log("hello".capitalize()); // "Hello" +``` + + +## Prototype Inheritance Patterns + +### Pattern 1: Prototype Delegation + + +```javascript !! js +// Simple prototype chain +const animal = { + init(name) { + this.name = name; + }, + speak() { + console.log(`${this.name} makes a sound`); + } +}; + +const dog = Object.create(animal); +dog.bark = function() { + console.log(`${this.name} barks`); +}; + +const myDog = Object.create(dog); +myDog.init("Buddy"); + +myDog.speak(); // "Buddy makes a sound" (from animal) +myDog.bark(); // "Buddy barks" (from dog) + +console.log(myDog instanceof Object); // true +// No way to check instanceof for prototype chains without constructors +``` + + +### Pattern 2: Constructor Inheritance + + +```javascript !! js +// Parent constructor +function Animal(name) { + this.name = name; +} + +Animal.prototype.speak = function() { + console.log(`${this.name} makes a sound`); +}; + +// Child constructor +function Dog(name, breed) { + Animal.call(this, name); // Call parent constructor + this.breed = breed; +} + +// Inherit prototype +Dog.prototype = Object.create(Animal.prototype); +Dog.prototype.constructor = Dog; // Fix constructor + +// Add child methods +Dog.prototype.bark = function() { + console.log(`${this.name} barks`); +}; + +// Override parent method +Dog.prototype.speak = function() { + Animal.prototype.speak.call(this); // Call parent + this.bark(); +}; + +const dog = new Dog("Buddy", "Labrador"); +dog.speak(); // "Buddy makes a sound" then "Buddy barks" + +console.log(dog instanceof Dog); // true +console.log(dog instanceof Animal); // true + +// Modern equivalent (classes are easier!) +class Animal2 { + constructor(name) { + this.name = name; + } + speak() { + console.log(`${this.name} makes a sound`); + } +} + +class Dog2 extends Animal2 { + constructor(name, breed) { + super(name); + this.breed = breed; + } + bark() { + console.log(`${this.name} barks`); + } + speak() { + super.speak(); + this.bark(); + } +} +``` + + +### Pattern 3: Functional Inheritance + + +```java !! java +// Java - Composition over inheritance +interface Flyable { + void fly(); +} + +class Bird implements Flyable { + public void fly() { + System.out.println("Flying"); + } +} + +class Bat implements Flyable { + public void fly() { + System.out.println("Flying"); + } +} +``` + +```javascript ^^ js +// JavaScript - Mixins with prototypes + +const Flyable = { + fly() { + console.log("Flying!"); + } +}; + +const Swimmable = { + swim() { + console.log("Swimming!"); + } +}; + +function Duck(name) { + this.name = name; +} + +// Mix in capabilities +Object.assign(Duck.prototype, Flyable, Swimmable); + +const duck = new Duck("Donald"); +duck.fly(); // "Flying!" +duck.swim(); // "Swimming!" + +// Or use Object.create with multiple prototypes +const flyingSwimmer = Object.assign( + Object.create(Object.prototype), + Flyable, + Swimmable +); + +const duck2 = Object.create(flyingSwimmer); +duck2.name = "Daisy"; +duck2.fly(); // "Flying!" +``` + + +## Prototypes vs Classes + + +```javascript !! js +// Prototypes (flexible, dynamic) +const animal = { + speak() { + console.log("Sound"); + } +}; + +const dog = Object.create(animal); + +// Can modify prototype anytime +animal.speak = function() { + console.log("Loud sound"); +}; + +dog.speak(); // "Loud sound" (reflects prototype change) + +// Classes (structured, familiar) +class Animal2 { + speak() { + console.log("Sound"); + } +} + +class Dog2 extends Animal2 { + // Can't easily modify Animal2.prototype after creation + // Better to use inheritance or override methods +} + +// When to use each: + +// Use prototypes when: +// - Need dynamic modification +// - Creating simple object hierarchies +// - Want memory efficiency (shared methods) +// - Don't need 'new' operator + +const utils = { + log(msg) { + console.log(msg); + }, + warn(msg) { + console.warn(msg); + } +}; + +// Use classes when: +// - Need constructor logic +// - Want familiar OOP structure +// - Need instanceof checks +// - Building complex applications + +class UserService { + constructor(apiUrl) { + this.apiUrl = apiUrl; + } + + async getUser(id) { + const response = await fetch(`${this.apiUrl}/users/${id}`); + return response.json(); + } +} +``` + + +## Performance Considerations + + +```javascript !! js +// Prototype lookup has a cost +const obj = { + a: 1, + b: 2, + c: 3 +}; + +// Direct property access (fast) +console.log(obj.a); + +// Deep prototype chain (slower) +const proto1 = { x: 1 }; +const proto2 = Object.create(proto1); +const proto3 = Object.create(proto2); +const obj2 = Object.create(proto3); + +// Many lookups needed to find x +console.log(obj2.x); // Checks obj2, proto3, proto2, proto1 + +// Shallow prototype chain (faster) +const obj3 = Object.create({ x: 1 }); +console.log(obj3.x); // Only one lookup + +// Caching frequently accessed prototype properties +function fastAccess(obj) { + const proto = Object.getPrototypeOf(obj); + const method = proto.method; + + // Use cached reference + return function() { + return method.call(obj); + }; +} + +// Own properties are faster than prototype lookups +const fast = { name: "Fast" }; +const slow = Object.create({ name: "Slow" }); + +// Direct access vs prototype lookup +console.time("direct"); +for (let i = 0; i < 1000000; i++) { + fast.name; +} +console.timeEnd("direct"); // ~2ms + +console.time("prototype"); +for (let i = 0; i < 1000000; i++) { + slow.name; +} +console.timeEnd("prototype"); // ~3ms (slightly slower) +``` + + +## Common Pitfalls + +### Pitfall 1: Modifying Object.prototype + + +```javascript !! js +// ❌ BAD: Modifying Object.prototype affects everything +Object.prototype.each = function(fn) { + for (let key in this) { + fn(this[key], key); + } +}; + +// Now even empty objects have 'each' +const empty = {}; +empty.each(x => console.log(x)); // Unexpected! + +// Also breaks for...in loops +for (let key in empty) { + console.log(key); // "each" (unwanted!) +} + +// ✅ GOOD: Use your own base object +const MyBaseObject = { + each(fn) { + for (let key in this) { + if (this.hasOwnProperty(key)) { + fn(this[key], key); + } + } + } +}; + +const myObj = Object.create(MyyBaseObject); +myObj.each(x => console.log(x)); // Works as expected +``` + + +### Pitfall 2: Prototype Pollution + + +```javascript !! js +// Security vulnerability: Prototype pollution + +function merge(target, source) { + for (let key in source) { + if (typeof source[key] === "object" && typeof target[key] === "object") { + merge(target[key], source[key]); + } else { + target[key] = source[key]; + } + } + return target; +} + +// Malicious input +const malicious = { + __proto__: { + isAdmin: true + } +}; + +const empty = {}; +merge(empty, malicious); + +// Now all objects have isAdmin = true! +console.log(({}).isAdmin); // true (security issue!) + +// ✅ GOOD: Validate keys +function safeMerge(target, source) { + for (let key in source) { + // Skip dangerous keys + if (key === "__proto__" || key === "constructor" || key === "prototype") { + continue; + } + // ... rest of merge logic + } + return target; +} + +// Or use Object.create(null) for pure data objects +const pure = Object.create(null); +// pure has no prototype, so no prototype pollution possible +``` + + +## Best Practices + + +```java !! java +// Java: Clear class hierarchy +public abstract class Animal { + public abstract void speak(); +} + +public class Dog extends Animal { + public void speak() { + System.out.println("Woof!"); + } +} +``` + +```javascript !! js +// JavaScript: Choose the right approach + +// 1. Use classes for most cases (modern, familiar) +class Animal { + speak() { + console.log("Sound"); + } +} + +class Dog extends Animal { + speak() { + console.log("Woof!"); + } +} + +// 2. Use Object.create for simple prototypes +const animal = { + speak() { + console.log("Sound"); + } +}; + +const dog = Object.create(animal); + +// 3. Don't modify built-in prototypes +// ❌ Array.prototype.first = function() { return this[0]; }; + +// ✅ Create subclass or utility function +class MyArray extends Array { + first() { + return this[0]; + } +} + +// 4. Use hasOwnProperty in for...in +for (let key in obj) { + if (obj.hasOwnProperty(key)) { + // Only own properties + } +} + +// Or use Object.keys() +Object.keys(obj).forEach(key => { + // Only own enumerable properties +}); + +// 5. Understand instanceof limitations +// Only works with constructors +const obj = Object.create(null); +obj instanceof Object; // false (no prototype chain) + +// Use Object.getPrototypeOf() instead +console.log(Object.getPrototypeOf(obj) === null); // true + +// 6. Prefer composition over deep prototype chains +// Bad: Deep inheritance +class A {} +class B extends A {} +class C extends B {} +class D extends C {} + +// Good: Composition +class D { + constructor(a, b, c) { + this.a = a; + this.b = b; + this.c = c; + } +} +``` + + +## Exercises + +### Exercise 1: Prototype Chain +```javascript +const grandParent = { a: 1 }; +const parent = Object.create(grandParent); +parent.b = 2; +const child = Object.create(parent); +child.c = 3; + +// What will these output? +console.log(child.a); +console.log(child.b); +console.log(child.hasOwnProperty("a")); +console.log(child.hasOwnProperty("b")); +``` + +### Exercise 2: Create with Object.create +```javascript +// Create a vehicle prototype with start() and stop() methods +// Create car and motorcycle objects that inherit from vehicle +``` + +### Exercise 3: Constructor Function +```javascript +// Create a Book constructor function +// Add methods to prototype: getTitle(), getAuthor() +// Create instances and test +``` + +### Exercise 4: Prototype Chain Lookup +```javascript +// Create prototype chain of 3 levels +// Add a method that only exists on level 3 +// Test that level 1 can access it +``` + +## Summary + +### Key Takeaways + +1. **Prototypes:** + - Every object has a prototype + - Prototype chain for property lookup + - Enables inheritance without classes + +2. **Object.create():** + - Creates object with specific prototype + - Supports property descriptors + - Can create null-prototype objects + +3. **Constructor Functions:** + - Pre-ES6 pattern for classes + - Methods added to prototype + - 'new' keyword creates instances + +4. **Prototype Chain:** + - Lookups walk up the chain + - Own properties found first + - Ends at Object.prototype then null + +5. **Classes vs Prototypes:** + - Classes are sugar over prototypes + - Classes are clearer for most cases + - Prototypes offer more flexibility + +6. **Best Practices:** + - Don't modify built-in prototypes + - Use classes for structure + - Use Object.create for simple inheritance + - Understand prototype performance + +### Comparison Table: Java vs JavaScript + +| Feature | Java | JavaScript | +|---------|------|------------| +| **Inheritance** | Class-based | Prototype-based | +| **Chain** | Class hierarchy | Prototype chain | +| **Methods** | In class definition | On prototype object | +| **Lookup** | Compile-time | Runtime (chain walk) | +| **Modification** | Cannot modify at runtime | Can modify anytime | +| **Checking** | `instanceof` | `instanceof` or `Object.getPrototypeOf()` | + +## What's Next? + +You've mastered JavaScript's prototype system! Next up is **Module 9: This and Context**, where we'll explore: + +- How `this` binding works +- Call, apply, and bind methods +- Arrow functions and lexical this +- This in different contexts +- Common this-related pitfalls +- Best practices for managing context + +Ready to master the `this` keyword? Let's continue! diff --git a/content/docs/java2js/module-08-prototypes.zh-tw.mdx b/content/docs/java2js/module-08-prototypes.zh-tw.mdx new file mode 100644 index 0000000..9dc6fb6 --- /dev/null +++ b/content/docs/java2js/module-08-prototypes.zh-tw.mdx @@ -0,0 +1,333 @@ +--- +title: "Module 8: Prototypes and Prototype Chain" +description: "理解 JavaScript 原型系統和繼承機制" +--- + +## Module 8: Prototypes and Prototype Chain + +現在您了解了 ES6 類別,讓我們看看 JavaScript 底層的運作方式。類別是 JavaScript 原型繼承系統的語法糖。理解原型對於進階 JavaScript 開發至關重要。 + +## Learning Objectives + +完成本模組後,你將: +✅ 理解什麼是原型 +✅ 掌握原型鏈查找 +✅ 學習 Object.create() 和原型繼承 +✅ 理解建構函數 +✅ 知道類別與原型的關係 +✅ 學習何時使用原型 vs 類別 + +## Prototypes vs Classes + +Java 使用基於類別的繼承,而 JavaScript 使用基於原型的繼承: + + +```java !! java +// Java - 基於類別的繼承 +public class Animal { + public void speak() { + System.out.println("Some sound"); + } +} + +public class Dog extends Animal { + @Override + public void speak() { + System.out.println("Woof!"); + } +} + +// 實例方法在類別中定義 +// 每個實例有相同的方法 +Animal animal = new Dog(); +animal.speak(); // "Woof!" +``` + +```javascript !! js +// JavaScript - 基於原型的繼承 +// 物件直接從其他物件繼承 + +const animal = { + speak() { + console.log("Some sound"); + } +}; + +const dog = Object.create(animal); +dog.speak = function() { + console.log("Woof!"); +}; + +dog.speak(); // "Woof!" +// 但仍可透過原型訪問 animal 的方法 +delete dog.speak; +dog.speak(); // "Some sound"(來自原型!) + +// 類別只是原型的語法糖 +class Animal2 { + speak() { + console.log("Some sound"); + } +} + +class Dog2 extends Animal2 { + speak() { + console.log("Woof!"); + } +} + +const dog2 = new Dog2(); +console.log(Object.getPrototypeOf(dog2) === Dog2.prototype); // true +console.log(Object.getPrototypeOf(Dog2.prototype) === Animal2.prototype); // true +``` + + +## The Prototype Chain + +每個物件都有一個指向另一個物件的內部鏈結,稱為其原型。這形成了一個鏈: + + +```java !! java +// Java - 類別層次結構 +Object -> Animal -> Dog + +// 方法查找:檢查類別,然後父類別 +Dog dog = new Dog(); +// dog.toString() 查找 Dog,然後 Animal,然後 Object +``` + +```javascript !! js +// JavaScript - 原型鏈 +const grandParent = { + name: "GrandParent", + greet() { + console.log(`Hello from ${this.name}`); + } +}; + +const parent = Object.create(grandParent); +parent.name = "Parent"; + +const child = Object.create(parent); +child.name = "Child"; + +// 查找沿著鏈向上 +child.greet(); // "Hello from Child" +// 1. 檢查 child - 有 name 屬性 +// 2. 檢查 child 是否有 greet() - 未找到 +// 3. 檢查 parent - 沒有 greet() +// 4. 檢查 grandParent - 找到 greet()! + +console.log(child.hasOwnProperty("name")); // true +console.log(child.hasOwnProperty("greet")); // false(繼承) + +// 完整原型鏈 +console.log(Object.getPrototypeOf(child) === parent); // true +console.log(Object.getPrototypeOf(parent) === grandParent); // true +console.log(Object.getPrototypeOf(grandParent) === Object.prototype); // true +console.log(Object.getPrototypeOf(Object.prototype) === null); // true(鏈的末端) +``` + + +## Object.create() + +Object.create() 創建具有指定原型的新物件: + + +```java !! java +// Java - 繼承需要類別 +public class Animal { + public void speak() { + System.out.println("Sound"); + } +} + +public class Dog extends Animal { + // Dog 從 Animal 繼承 +} +``` + +```javascript !! js +// JavaScript - 直接物件繼承 + +// 創建具有特定原型的物件 +const animal = { + speak() { + console.log("Some sound"); + } +}; + +const dog = Object.create(animal); +dog.bark = function() { + console.log("Woof!"); +}; + +dog.speak(); // "Some sound"(來自原型) +dog.bark(); // "Woof!"(自有屬性) + +// 創建 null 原型(無繼承方法) +const empty = Object.create(null); +empty.toString(); // TypeError!(無 Object.prototype 方法) +``` + + +## Constructor Functions + +在 ES6 類別之前,JavaScript 使用建構函數: + + +```java !! java +// Java - 類別建構函數 +public class User { + private String name; + + public User(String name) { + this.name = name; + } +} + +User user = new User("John"); +``` + +```javascript !! js +// JavaScript - 建構函數(ES6 之前) + +function User(name) { + this.name = name; +} + +// 方法添加到原型 +User.prototype.greet = function() { + console.log(`Hello, I'm ${this.name}`); +}; + +// 使用 'new' 創建實例 +const user = new User("John"); +user.greet(); // "Hello, I'm John" + +// 檢查實例 +console.log(user instanceof User); // true +console.log(user instanceof Object); // true + +// 箭頭函數不能是建構函數 +const BadUser = (name) => { + this.name = name; +}; + +// const bad = new BadUser("John"); // TypeError! +``` + + +## Prototype vs Classes + + +```javascript !! js +// 原型(靈活、動態) +const animal = { + speak() { + console.log("Sound"); + } +}; + +const dog = Object.create(animal); + +// 可隨時修改原型 +animal.speak = function() { + console.log("Loud sound"); +}; + +dog.speak(); // "Loud sound"(反映原型變更) + +// 類別(結構化、熟悉) +class Animal2 { + speak() { + console.log("Sound"); + } +} + +class Dog2 extends Animal2 { + // 創建後不易修改 Animal2.prototype + // 最好使用繼承或覆寫方法 +} + +// 何時使用每種: + +// 使用原型當: +// - 需要動態修改 +// - 創建簡單物件層次結構 +// - 想要記憶效率(共享方法) +// - 不需要 'new' 運算符 + +// 使用類別當: +// - 需要建構邏輯 +// - 想要熟悉的 OOP 結構 +// - 需要 instanceof 檢查 +// - 構建複雜應用程式 +``` + + +## Best Practices + + +```java !! java +// Java: 清晰的類別層次結構 +public abstract class Animal { + public abstract void speak(); +} + +public class Dog extends Animal { + public void speak() { + System.out.println("Woof!"); + } +} +``` + +```javascript !! js +// JavaScript: 選擇正確的方法 + +// 1. 大多數情況使用類別(現代、熟悉) +class Animal { + speak() { + console.log("Sound"); + } +} + +class Dog extends Animal { + speak() { + console.log("Woof!"); + } +} + +// 2. 簡單原型使用 Object.create +const animal = { + speak() { + console.log("Sound"); + } +}; + +const dog = Object.create(animal); + +// 3. 不要修改內置原型 +// ❌ Array.prototype.first = function() { return this[0]; }; + +// ✅ 創建子類別或工具函數 +class MyArray extends Array { + first() { + return this[0]; + } +} +``` + + +## Summary + +### Key Takeaways + +1. **Prototypes:** 每個物件都有原型 +2. **Object.create():** 創建具有特定原型的物件 +3. **Constructor Functions:** ES6 之前的類別模式 +4. **Classes vs Prototypes:** 類別是原型的語法糖 + +## What's Next? + +接下來是 **Module 9: This and Context** - 探索 this 關鍵字! diff --git a/content/docs/java2js/module-09-this-context.mdx b/content/docs/java2js/module-09-this-context.mdx new file mode 100644 index 0000000..0aaf136 --- /dev/null +++ b/content/docs/java2js/module-09-this-context.mdx @@ -0,0 +1,936 @@ +--- +title: "Module 9: This and Context" +description: "Master JavaScript's 'this' keyword and context binding" +--- + +## Module 9: This and Context + +The `this` keyword in JavaScript behaves very differently from Java. Understanding how `this` works is crucial for writing bug-free JavaScript code. This module will demystify `this` and teach you how to control context. + +## Learning Objectives + +By the end of this module, you will: +✅ Understand how `this` binding works +✅ Master call(), apply(), and bind() methods +✅ Learn arrow functions and lexical this +✅ Know this in different contexts (global, function, class, etc.) +✅ Understand common this-related pitfalls +✅ Learn best practices for managing context + +## This Comparison: Java vs JavaScript + + +```java !! java +// Java - 'this' always refers to current instance +public class Counter { + private int count = 0; + + public Counter() { + this.count = 0; // 'this' is the Counter instance + } + + public void increment() { + this.count++; // Always refers to Counter instance + } + + public void multiThread() { + // Lambda captures 'this' + Runnable r = () -> { + this.count++; // Still refers to Counter instance + }; + } +} + +// 'this' is predictable and always refers to the instance +``` + +```javascript !! js +// JavaScript - 'this' depends on call site +const counter = { + count: 0, + + increment() { + this.count++; // 'this' depends on how function is called + }, + + // Arrow function: 'this' from surrounding scope + incrementArrow: () => { + this.count++; // 'this' is NOT counter! + } +}; + +counter.increment(); // 'this' = counter ✓ +counter.incrementArrow(); // 'this' = undefined (or window in browser) ✗ + +// 'this' changes based on call site +const fn = counter.increment; +fn(); // 'this' = undefined (strict mode) or window + +// Can explicitly bind 'this' +fn.call(counter); // 'this' = counter ✓ +``` + + +## This Rules + +JavaScript determines `this` based on how a function is called: + + +```javascript !! js +// Rule 1: Default binding +// 'this' is global object (non-strict) or undefined (strict) +function sayName() { + console.log(this.name); +} + +const name = "Global"; + +sayName(); // "Global" (non-strict) or undefined (strict) + +// Rule 2: Implicit binding +// 'this' is object to left of dot +const person = { + name: "John", + greet() { + console.log(this.name); + } +}; + +person.greet(); // "John" - 'this' = person + +// Rule 3: Explicit binding +// 'this' is specified with call/apply/bind +function greet() { + console.log(this.name); +} + +const user = { name: "Jane" }; +greet.call(user); // "Jane" - 'this' = user +greet.apply(user); // "Jane" - 'this' = user +const bound = greet.bind(user); +bound(); // "Jane" - 'this' = user + +// Rule 4: New binding +// 'this' is newly created object +function User(name) { + this.name = name; +} + +const user2 = new User("Bob"); +console.log(user2.name); // "Bob" - 'this' = user2 +``` + + +## Call, Apply, and Bind + +JavaScript provides methods to explicitly control `this`: + + +```java !! java +// Java - No direct equivalent +// Methods are bound to instances +user.method(); // 'this' is always user + +// Would need reflection or wrapper classes +``` + +```javascript !! js +// JavaScript - call(), apply(), bind() + +// call(): Invoke with specific 'this' +function introduce(greeting, punctuation) { + console.log(`${greeting}, I'm ${this.name}${punctuation}`); +} + +const person = { name: "John" }; + +introduce.call(person, "Hello", "!"); // "Hello, I'm John!" +// Arguments passed individually + +// apply(): Same as call but arguments as array +introduce.apply(person, ["Hi", "?"]); // "Hi, I'm John?" +// Arguments passed as array + +// Practical: Borrow methods +const numbers = [1, 2, 3]; +const max = Math.max.apply(null, numbers); // 3 +const min = Math.min.apply(null, numbers); // 1 + +// Modern: Spread operator (preferred) +const max2 = Math.max(...numbers); // 3 + +// bind(): Create function with permanent 'this' +const person2 = { name: "Jane" }; +const introduceJane = introduce.bind(person2, "Hey"); +introduceJane("!"); // "Hey, I'm Jane!" +// Can add more arguments, but 'this' and first arg are fixed + +// Practical: Event handlers +class Button { + constructor(label) { + this.label = label; + this.button = document.createElement("button"); + this.button.textContent = label; + + // ❌ BAD: 'this' is lost + // this.button.addEventListener("click", this.handleClick); + + // ✅ GOOD: Bind 'this' + this.handleClick = this.handleClick.bind(this); + this.button.addEventListener("click", this.handleClick); + + // ✅ ALSO GOOD: Arrow function (see below) + // this.button.addEventListener("click", () => this.handleClick()); + } + + handleClick() { + console.log(`Button "${this.label}" clicked`); + } +} +``` + + +## Arrow Functions and Lexical This + +Arrow functions don't have their own `this` - they inherit it from surrounding scope: + + +```java !! java +// Java - Lambdas capture 'this' from enclosing class +public class Timer { + private int count = 0; + + public void start() { + // Lambda can access 'this' + Runnable r = () -> { + this.count++; // 'this' is Timer instance + }; + } +} +``` + +```javascript !! js +// JavaScript - Arrow functions lexically bind 'this' + +// Regular function: 'this' based on call site +const counter = { + count: 0, + increment: function() { + setTimeout(function() { + this.count++; // ❌ 'this' is not counter! + }, 1000); + } +}; + +// Arrow function: 'this' from surrounding scope +const counter2 = { + count: 0, + increment: function() { + setTimeout(() => { + this.count++; // ✅ 'this' is counter2 + }, 1000); + } +}; + +// Practical: Event handlers +class UI { + constructor() { + this.handleClick = this.handleClick.bind(this); + // Or use arrow function: + // this.handleClick = () => { ... }; + } + + handleClick() { + console.log(this); // UI instance + } + + setup() { + // ❌ BAD: 'this' is button element + // button.addEventListener("click", this.handleClick); + + // ✅ GOOD: Arrow function + button.addEventListener("click", () => this.handleClick()); + + // ✅ ALSO GOOD: Bind + button.addEventListener("click", this.handleClick.bind(this)); + } +} + +// Arrow functions cannot be bound +const obj = { + name: "John", + greet: () => { + console.log(this.name); // 'this' is not obj! + } +}; + +obj.greet(); // undefined + +// You cannot change arrow function's 'this' +const arrow = () => console.log(this); +arrow.call({ name: "John" }); // Still global/undefined (not { name: "John" }) +``` + + +## This in Different Contexts + + +```java !! java +// Java - 'this' is always instance +public class Example { + private String name = "Instance"; + + public void method() { + System.out.println(this.name); // Always instance + } + + public void inner() { + // Even in inner methods + class Inner { + public void go() { + System.out.println(Example.this.name); // Still instance + } + } + } +} +``` + +```javascript !! js +// JavaScript - 'this' varies by context + +// 1. Global scope +console.log(this); // global object (window in browser, global in Node) + +// 2. Function scope (strict mode) +"use strict"; +function test() { + console.log(this); // undefined (not global!) +} + +// 3. Function scope (non-strict) +function test2() { + console.log(this); // global object +} + +// 4. Object method +const obj = { + name: "John", + greet() { + console.log(this.name); // 'this' = obj + }, + inner: { + greet() { + console.log(this.name); // 'this' = obj.inner (not obj!) + } + } +}; + +// 5. Constructor function +function User(name) { + this.name = name; // 'this' = new object +} +const user = new User("John"); + +// 6. DOM event handlers +button.addEventListener("click", function() { + console.log(this); // 'this' = button element +}); + +button.addEventListener("click", () => { + console.log(this); // 'this' = surrounding context (not button!) +}); + +// 7. Classes +class MyClass { + constructor() { + this.name = "John"; + } + + method() { + console.log(this); // 'this' = instance + } + + static staticMethod() { + console.log(this); // 'this' = MyClass (class itself) + } +} + +// 8. getters/setters +const obj2 = { + _name: "John", + get name() { + console.log(this); // 'this' = obj2 + return this._name; + } +}; +``` + + +## Common Pitfalls + +### Pitfall 1: Losing This + + +```javascript !! js +// Problem: 'this' is lost when passing methods + +const controller = { + data: [1, 2, 3], + process() { + return this.data.map(x => x * 2); + } +}; + +// ❌ BAD: Extracting method loses 'this' +const process = controller.process; +process(); // Error: Cannot read 'data' of undefined + +// ✅ Solution 1: Bind +const process2 = controller.process.bind(controller); +process2(); // Works! + +// ✅ Solution 2: Arrow function +const process3 = () => controller.process(); +process3(); // Works! + +// ✅ Solution 3: Keep method attached +controller.process(); // Works! +``` + + +### Pitfall 2: Callback This + + +```javascript !! js +// Problem: Callbacks have different 'this' + +class Timer { + constructor(seconds) { + this.seconds = seconds; + } + + start() { + // ❌ BAD: Callback has different 'this' + setTimeout(function() { + this.seconds--; // Error: 'this' is not Timer + }, 1000); + } +} + +// ✅ Solution 1: Arrow function +class Timer2 { + constructor(seconds) { + this.seconds = seconds; + } + + start() { + setTimeout(() => { + this.seconds--; // 'this' is Timer2 ✓ + }, 1000); + } +} + +// ✅ Solution 2: Bind +class Timer3 { + constructor(seconds) { + this.seconds = seconds; + } + + start() { + setTimeout(function() { + this.seconds--; + }.bind(this), 1000); // Bind 'this' + } +} + +// ✅ Solution 3: Capture 'this' +class Timer4 { + constructor(seconds) { + this.seconds = seconds; + } + + start() { + const self = this; // Capture 'this' + setTimeout(function() { + self.seconds--; + }, 1000); + } +} +``` + + +### Pitfall 3: Array Methods + + +```javascript !! js +// Problem: Array methods change 'this' + +const processor = { + multiplier: 2, + process(numbers) { + return numbers.map(function(x) { + return x * this.multiplier; // Error: 'this' is not processor + }); + } +}; + +// ✅ Solution 1: Arrow function +const processor2 = { + multiplier: 2, + process(numbers) { + return numbers.map(x => x * this.multiplier); + } +}; + +// ✅ Solution 2: Second argument to map/filter/etc +const processor3 = { + multiplier: 2, + process(numbers) { + return numbers.map(function(x) { + return x * this.multiplier; + }, this); // Set 'this' for callback + } +}; + +// ✅ Solution 3: Bind +const processor4 = { + multiplier: 2, + process(numbers) { + return numbers.map(function(x) { + return x * this.multiplier; + }.bind(this)); + } +}; +``` + + +## Practical Patterns + +### Pattern 1: Class Method Binding + + +```javascript !! js +// Auto-bind all methods in constructor +class UserService { + constructor(apiUrl) { + this.apiUrl = apiUrl; + this.users = []; + + // Bind all methods + this.fetchUsers = this.fetchUsers.bind(this); + this.getUser = this.getUser.bind(this); + } + + async fetchUsers() { + const response = await fetch(`${this.apiUrl}/users`); + this.users = await response.json(); + } + + getUser(id) { + return this.users.find(u => u.id === id); + } +} + +// Or use arrow functions for methods (auto-bound) +class UserService2 { + constructor(apiUrl) { + this.apiUrl = apiUrl; + this.users = []; + } + + fetchUsers = async () => { + const response = await fetch(`${this.apiUrl}/users`); + this.users = await response.json(); + } + + getUser = (id) => { + return this.users.find(u => u.id === id); + } +} + +// Usage +const service = new UserService2("https://api.example.com"); +const fetchFn = service.fetchUsers; // 'this' is bound! +fetchFn(); // Works! +``` + + +### Pattern 2: Event Delegation + + +```javascript !! js +// Problem: Event handlers receive element as 'this' + +class Menu { + constructor(items) { + this.items = items; + this.activeItem = null; + } + + render() { + const list = document.createElement("ul"); + + this.items.forEach(item => { + const li = document.createElement("li"); + li.textContent = item; + li.addEventListener("click", this.handleClick.bind(this)); + list.appendChild(li); + }); + + return list; + } + + handleClick(event) { + this.activeItem = event.target.textContent; + console.log(`Selected: ${this.activeItem}`); + } +} + +// Or use event delegation +class Menu2 { + constructor(items) { + this.items = items; + this.activeItem = null; + } + + render() { + const list = document.createElement("ul"); + + this.items.forEach(item => { + const li = document.createElement("li"); + li.textContent = item; + li.dataset.item = item; + list.appendChild(li); + }); + + // Single listener on container + list.addEventListener("click", this); + return list; + } + + handleEvent(event) { + if (event.target.tagName === "LI") { + this.activeItem = event.target.dataset.item; + console.log(`Selected: ${this.activeItem}`); + } + } +} +``` + + +### Pattern 3: Partial Application + + +```javascript !! js +// Bind both 'this' and arguments +function greet(greeting, punctuation) { + console.log(`${greeting}, I'm ${this.name}${punctuation}`); +} + +const person = { name: "John" }; + +// Partial application with 'this' +const sayHello = greet.bind(person, "Hello"); +sayHello("!"); // "Hello, I'm John!" + +const sayHi = greet.bind(person, "Hi"); +sayHi("?"); // "Hi, I'm John?" + +// Practical: Reusable functions +function fetch(endpoint, options) { + console.log(`Fetching ${this.baseUrl}${endpoint}`, options); +} + +const api = { + baseUrl: "https://api.example.com", + get: fetch.bind(null, "/users"), // 'this' will be api + post: fetch.bind(null, "/users", { method: "POST" }) +}; + +api.get.call(api, { method: "GET" }); // Explicit 'this' + +// Better: Use arrow functions +const api2 = { + baseUrl: "https://api.example.com", + get: function(endpoint, options) { + return fetch(`${this.baseUrl}${endpoint}`, options); + } +}; + +const getUsers = (endpoint, options) => api2.get.call(api2, endpoint, options); +``` + + +## Performance Considerations + + +```javascript !! js +// Binding creates new function (overhead) + +class Button { + constructor() { + this.text = "Click me"; + + // ❌ BAD: Creates new function on each render + this.handleClick = function() { + console.log(this.text); + }.bind(this); + } +} + +// ✅ GOOD: Use arrow function (still creates per instance) +class Button2 { + constructor() { + this.text = "Click me"; + this.handleClick = () => { + console.log(this.text); + }; + } +} + +// ✅ BETTER: Define method in prototype +class Button3 { + constructor() { + this.text = "Click me"; + } + + handleClick() { + console.log(this.text); + } + + setup() { + // Bind once + this.handleClick = this.handleClick.bind(this); + } +} + +// Best: Bind at call site (if possible) +const button = new Button3(); +button.element.addEventListener("click", () => button.handleClick()); + +// Performance comparison +const obj = { value: 42 }; + +// Bound function (slight overhead) +const bound = function() { + return this.value; +}.bind(obj); + +// Direct call (faster) +function getValue() { + return this.value; +} +getValue.call(obj); +``` + + +## Best Practices + + +```java !! java +// Java: Clear 'this' usage +public class Example { + private String name; + + public void method() { + this.name = "John"; // Clear 'this' + } +} +``` + +```javascript !! js +// JavaScript: Be intentional with 'this' + +// 1. Use arrow functions for callbacks +class Component { + constructor() { + this.state = { count: 0 }; + } + + increment() { + this.state.count++; + } + + setup() { + // ✅ Arrow function preserves 'this' + button.addEventListener("click", () => this.increment()); + } +} + +// 2. Bind when extracting methods +class Service { + async fetchData() { + // ... + } + + getFetcher() { + // ✅ Bind when returning method + return this.fetchData.bind(this); + } +} + +// 3. Use classes for predictable 'this' +class Counter { + constructor() { + this.count = 0; + } + + increment() { + this.count++; + } +} + +// Avoid constructor functions (use classes) +function Counter() { + this.count = 0; +} + +// 4. Understand arrow function limitations +const obj = { + // ❌ Don't use arrow for methods + // greet: () => console.log(this.name), + + // ✅ Use regular methods + greet() { + console.log(this.name); + } +}; + +// 5. Be careful with array methods +const processor = { + multiplier: 2, + + process(numbers) { + // ✅ Use arrow functions + return numbers.map(x => x * this.multiplier); + } +}; + +// 6. Don't use 'this' in static methods +class Utils { + static helper() { + // ❌ Don't use 'this' (it's the class, not instance) + // this.method(); + + // ✅ Use static calls + Utils.staticHelper(); + } + + static staticHelper() { + // ... + } +} + +// 7. Explicit 'this' binding when needed +function logThis() { + console.log(this); +} + +const context = { name: "Context" }; +setTimeout(logThis.bind(context), 1000); // Explicit 'this' +``` + + +## Exercises + +### Exercise 1: Fix This Context +```javascript +const counter = { + count: 0, + increment: function() { + setTimeout(function() { + this.count++; // Fix this + }, 1000); + } +}; +``` + +### Exercise 2: Array Method This +```javascript +const calculator = { + base: 10, + add(numbers) { + // Fix: return numbers with base added to each + return numbers.map(function(x) { + return x + this.base; + }); + } +}; +``` + +### Exercise 3: Event Handler +```javascript +class Button { + constructor(text) { + this.text = text; + this.element = document.createElement("button"); + this.element.textContent = text; + // Add click handler that logs this.text + } +} +``` + +### Exercise 4: Bind Arguments +```javascript +function multiply(a, b, c) { + return a * b * c; +} + +// Create a function that multiplies by 2 and 3 +// using bind (just needs one argument) +const multiplyBy6 = multiply.bind(null, ?, ?); +``` + +## Summary + +### Key Takeaways + +1. **This Rules:** + - Default: Global/undefined + - Implicit: Object left of dot + - Explicit: call/apply/bind + - New: Newly created object + +2. **Arrow Functions:** + - Lexical this (from surrounding scope) + - Cannot be bound with call/apply/bind + - Perfect for callbacks + +3. **Call/Apply/Bind:** + - call(): Invoke with specific this + - apply(): Same but array of args + - bind(): Create permanent this + +4. **Context Loss:** + - Common when passing methods + - Solve with bind or arrow functions + - Be careful with callbacks + +5. **Best Practices:** + - Use arrow functions for callbacks + - Use classes for structure + - Bind when extracting methods + - Understand arrow limitations + +### Comparison Table: Java vs JavaScript + +| Feature | Java | JavaScript | +|---------|------|------------| +| **This** | Always current instance | Depends on call site | +| **Binding** | Always bound | Can be changed | +| **Lambdas/Arrows** | Capture this | Lexical this | +| **Explicit binding** | No equivalent | call/apply/bind | +| **Loss of this** | Never happens | Common issue | +| **Solution** | Not needed | bind or arrow function | + +## What's Next? + +You've mastered JavaScript's `this` keyword! Next up is **Module 10: Asynchronous Programming Basics**, where we'll explore: + +- The JavaScript event loop +- Callback functions +- Understanding synchronous vs asynchronous +- Error handling in async code +- Common async patterns + +Ready to dive into asynchronous programming? Let's continue! diff --git a/content/docs/java2js/module-09-this-context.zh-cn.mdx b/content/docs/java2js/module-09-this-context.zh-cn.mdx new file mode 100644 index 0000000..0aaf136 --- /dev/null +++ b/content/docs/java2js/module-09-this-context.zh-cn.mdx @@ -0,0 +1,936 @@ +--- +title: "Module 9: This and Context" +description: "Master JavaScript's 'this' keyword and context binding" +--- + +## Module 9: This and Context + +The `this` keyword in JavaScript behaves very differently from Java. Understanding how `this` works is crucial for writing bug-free JavaScript code. This module will demystify `this` and teach you how to control context. + +## Learning Objectives + +By the end of this module, you will: +✅ Understand how `this` binding works +✅ Master call(), apply(), and bind() methods +✅ Learn arrow functions and lexical this +✅ Know this in different contexts (global, function, class, etc.) +✅ Understand common this-related pitfalls +✅ Learn best practices for managing context + +## This Comparison: Java vs JavaScript + + +```java !! java +// Java - 'this' always refers to current instance +public class Counter { + private int count = 0; + + public Counter() { + this.count = 0; // 'this' is the Counter instance + } + + public void increment() { + this.count++; // Always refers to Counter instance + } + + public void multiThread() { + // Lambda captures 'this' + Runnable r = () -> { + this.count++; // Still refers to Counter instance + }; + } +} + +// 'this' is predictable and always refers to the instance +``` + +```javascript !! js +// JavaScript - 'this' depends on call site +const counter = { + count: 0, + + increment() { + this.count++; // 'this' depends on how function is called + }, + + // Arrow function: 'this' from surrounding scope + incrementArrow: () => { + this.count++; // 'this' is NOT counter! + } +}; + +counter.increment(); // 'this' = counter ✓ +counter.incrementArrow(); // 'this' = undefined (or window in browser) ✗ + +// 'this' changes based on call site +const fn = counter.increment; +fn(); // 'this' = undefined (strict mode) or window + +// Can explicitly bind 'this' +fn.call(counter); // 'this' = counter ✓ +``` + + +## This Rules + +JavaScript determines `this` based on how a function is called: + + +```javascript !! js +// Rule 1: Default binding +// 'this' is global object (non-strict) or undefined (strict) +function sayName() { + console.log(this.name); +} + +const name = "Global"; + +sayName(); // "Global" (non-strict) or undefined (strict) + +// Rule 2: Implicit binding +// 'this' is object to left of dot +const person = { + name: "John", + greet() { + console.log(this.name); + } +}; + +person.greet(); // "John" - 'this' = person + +// Rule 3: Explicit binding +// 'this' is specified with call/apply/bind +function greet() { + console.log(this.name); +} + +const user = { name: "Jane" }; +greet.call(user); // "Jane" - 'this' = user +greet.apply(user); // "Jane" - 'this' = user +const bound = greet.bind(user); +bound(); // "Jane" - 'this' = user + +// Rule 4: New binding +// 'this' is newly created object +function User(name) { + this.name = name; +} + +const user2 = new User("Bob"); +console.log(user2.name); // "Bob" - 'this' = user2 +``` + + +## Call, Apply, and Bind + +JavaScript provides methods to explicitly control `this`: + + +```java !! java +// Java - No direct equivalent +// Methods are bound to instances +user.method(); // 'this' is always user + +// Would need reflection or wrapper classes +``` + +```javascript !! js +// JavaScript - call(), apply(), bind() + +// call(): Invoke with specific 'this' +function introduce(greeting, punctuation) { + console.log(`${greeting}, I'm ${this.name}${punctuation}`); +} + +const person = { name: "John" }; + +introduce.call(person, "Hello", "!"); // "Hello, I'm John!" +// Arguments passed individually + +// apply(): Same as call but arguments as array +introduce.apply(person, ["Hi", "?"]); // "Hi, I'm John?" +// Arguments passed as array + +// Practical: Borrow methods +const numbers = [1, 2, 3]; +const max = Math.max.apply(null, numbers); // 3 +const min = Math.min.apply(null, numbers); // 1 + +// Modern: Spread operator (preferred) +const max2 = Math.max(...numbers); // 3 + +// bind(): Create function with permanent 'this' +const person2 = { name: "Jane" }; +const introduceJane = introduce.bind(person2, "Hey"); +introduceJane("!"); // "Hey, I'm Jane!" +// Can add more arguments, but 'this' and first arg are fixed + +// Practical: Event handlers +class Button { + constructor(label) { + this.label = label; + this.button = document.createElement("button"); + this.button.textContent = label; + + // ❌ BAD: 'this' is lost + // this.button.addEventListener("click", this.handleClick); + + // ✅ GOOD: Bind 'this' + this.handleClick = this.handleClick.bind(this); + this.button.addEventListener("click", this.handleClick); + + // ✅ ALSO GOOD: Arrow function (see below) + // this.button.addEventListener("click", () => this.handleClick()); + } + + handleClick() { + console.log(`Button "${this.label}" clicked`); + } +} +``` + + +## Arrow Functions and Lexical This + +Arrow functions don't have their own `this` - they inherit it from surrounding scope: + + +```java !! java +// Java - Lambdas capture 'this' from enclosing class +public class Timer { + private int count = 0; + + public void start() { + // Lambda can access 'this' + Runnable r = () -> { + this.count++; // 'this' is Timer instance + }; + } +} +``` + +```javascript !! js +// JavaScript - Arrow functions lexically bind 'this' + +// Regular function: 'this' based on call site +const counter = { + count: 0, + increment: function() { + setTimeout(function() { + this.count++; // ❌ 'this' is not counter! + }, 1000); + } +}; + +// Arrow function: 'this' from surrounding scope +const counter2 = { + count: 0, + increment: function() { + setTimeout(() => { + this.count++; // ✅ 'this' is counter2 + }, 1000); + } +}; + +// Practical: Event handlers +class UI { + constructor() { + this.handleClick = this.handleClick.bind(this); + // Or use arrow function: + // this.handleClick = () => { ... }; + } + + handleClick() { + console.log(this); // UI instance + } + + setup() { + // ❌ BAD: 'this' is button element + // button.addEventListener("click", this.handleClick); + + // ✅ GOOD: Arrow function + button.addEventListener("click", () => this.handleClick()); + + // ✅ ALSO GOOD: Bind + button.addEventListener("click", this.handleClick.bind(this)); + } +} + +// Arrow functions cannot be bound +const obj = { + name: "John", + greet: () => { + console.log(this.name); // 'this' is not obj! + } +}; + +obj.greet(); // undefined + +// You cannot change arrow function's 'this' +const arrow = () => console.log(this); +arrow.call({ name: "John" }); // Still global/undefined (not { name: "John" }) +``` + + +## This in Different Contexts + + +```java !! java +// Java - 'this' is always instance +public class Example { + private String name = "Instance"; + + public void method() { + System.out.println(this.name); // Always instance + } + + public void inner() { + // Even in inner methods + class Inner { + public void go() { + System.out.println(Example.this.name); // Still instance + } + } + } +} +``` + +```javascript !! js +// JavaScript - 'this' varies by context + +// 1. Global scope +console.log(this); // global object (window in browser, global in Node) + +// 2. Function scope (strict mode) +"use strict"; +function test() { + console.log(this); // undefined (not global!) +} + +// 3. Function scope (non-strict) +function test2() { + console.log(this); // global object +} + +// 4. Object method +const obj = { + name: "John", + greet() { + console.log(this.name); // 'this' = obj + }, + inner: { + greet() { + console.log(this.name); // 'this' = obj.inner (not obj!) + } + } +}; + +// 5. Constructor function +function User(name) { + this.name = name; // 'this' = new object +} +const user = new User("John"); + +// 6. DOM event handlers +button.addEventListener("click", function() { + console.log(this); // 'this' = button element +}); + +button.addEventListener("click", () => { + console.log(this); // 'this' = surrounding context (not button!) +}); + +// 7. Classes +class MyClass { + constructor() { + this.name = "John"; + } + + method() { + console.log(this); // 'this' = instance + } + + static staticMethod() { + console.log(this); // 'this' = MyClass (class itself) + } +} + +// 8. getters/setters +const obj2 = { + _name: "John", + get name() { + console.log(this); // 'this' = obj2 + return this._name; + } +}; +``` + + +## Common Pitfalls + +### Pitfall 1: Losing This + + +```javascript !! js +// Problem: 'this' is lost when passing methods + +const controller = { + data: [1, 2, 3], + process() { + return this.data.map(x => x * 2); + } +}; + +// ❌ BAD: Extracting method loses 'this' +const process = controller.process; +process(); // Error: Cannot read 'data' of undefined + +// ✅ Solution 1: Bind +const process2 = controller.process.bind(controller); +process2(); // Works! + +// ✅ Solution 2: Arrow function +const process3 = () => controller.process(); +process3(); // Works! + +// ✅ Solution 3: Keep method attached +controller.process(); // Works! +``` + + +### Pitfall 2: Callback This + + +```javascript !! js +// Problem: Callbacks have different 'this' + +class Timer { + constructor(seconds) { + this.seconds = seconds; + } + + start() { + // ❌ BAD: Callback has different 'this' + setTimeout(function() { + this.seconds--; // Error: 'this' is not Timer + }, 1000); + } +} + +// ✅ Solution 1: Arrow function +class Timer2 { + constructor(seconds) { + this.seconds = seconds; + } + + start() { + setTimeout(() => { + this.seconds--; // 'this' is Timer2 ✓ + }, 1000); + } +} + +// ✅ Solution 2: Bind +class Timer3 { + constructor(seconds) { + this.seconds = seconds; + } + + start() { + setTimeout(function() { + this.seconds--; + }.bind(this), 1000); // Bind 'this' + } +} + +// ✅ Solution 3: Capture 'this' +class Timer4 { + constructor(seconds) { + this.seconds = seconds; + } + + start() { + const self = this; // Capture 'this' + setTimeout(function() { + self.seconds--; + }, 1000); + } +} +``` + + +### Pitfall 3: Array Methods + + +```javascript !! js +// Problem: Array methods change 'this' + +const processor = { + multiplier: 2, + process(numbers) { + return numbers.map(function(x) { + return x * this.multiplier; // Error: 'this' is not processor + }); + } +}; + +// ✅ Solution 1: Arrow function +const processor2 = { + multiplier: 2, + process(numbers) { + return numbers.map(x => x * this.multiplier); + } +}; + +// ✅ Solution 2: Second argument to map/filter/etc +const processor3 = { + multiplier: 2, + process(numbers) { + return numbers.map(function(x) { + return x * this.multiplier; + }, this); // Set 'this' for callback + } +}; + +// ✅ Solution 3: Bind +const processor4 = { + multiplier: 2, + process(numbers) { + return numbers.map(function(x) { + return x * this.multiplier; + }.bind(this)); + } +}; +``` + + +## Practical Patterns + +### Pattern 1: Class Method Binding + + +```javascript !! js +// Auto-bind all methods in constructor +class UserService { + constructor(apiUrl) { + this.apiUrl = apiUrl; + this.users = []; + + // Bind all methods + this.fetchUsers = this.fetchUsers.bind(this); + this.getUser = this.getUser.bind(this); + } + + async fetchUsers() { + const response = await fetch(`${this.apiUrl}/users`); + this.users = await response.json(); + } + + getUser(id) { + return this.users.find(u => u.id === id); + } +} + +// Or use arrow functions for methods (auto-bound) +class UserService2 { + constructor(apiUrl) { + this.apiUrl = apiUrl; + this.users = []; + } + + fetchUsers = async () => { + const response = await fetch(`${this.apiUrl}/users`); + this.users = await response.json(); + } + + getUser = (id) => { + return this.users.find(u => u.id === id); + } +} + +// Usage +const service = new UserService2("https://api.example.com"); +const fetchFn = service.fetchUsers; // 'this' is bound! +fetchFn(); // Works! +``` + + +### Pattern 2: Event Delegation + + +```javascript !! js +// Problem: Event handlers receive element as 'this' + +class Menu { + constructor(items) { + this.items = items; + this.activeItem = null; + } + + render() { + const list = document.createElement("ul"); + + this.items.forEach(item => { + const li = document.createElement("li"); + li.textContent = item; + li.addEventListener("click", this.handleClick.bind(this)); + list.appendChild(li); + }); + + return list; + } + + handleClick(event) { + this.activeItem = event.target.textContent; + console.log(`Selected: ${this.activeItem}`); + } +} + +// Or use event delegation +class Menu2 { + constructor(items) { + this.items = items; + this.activeItem = null; + } + + render() { + const list = document.createElement("ul"); + + this.items.forEach(item => { + const li = document.createElement("li"); + li.textContent = item; + li.dataset.item = item; + list.appendChild(li); + }); + + // Single listener on container + list.addEventListener("click", this); + return list; + } + + handleEvent(event) { + if (event.target.tagName === "LI") { + this.activeItem = event.target.dataset.item; + console.log(`Selected: ${this.activeItem}`); + } + } +} +``` + + +### Pattern 3: Partial Application + + +```javascript !! js +// Bind both 'this' and arguments +function greet(greeting, punctuation) { + console.log(`${greeting}, I'm ${this.name}${punctuation}`); +} + +const person = { name: "John" }; + +// Partial application with 'this' +const sayHello = greet.bind(person, "Hello"); +sayHello("!"); // "Hello, I'm John!" + +const sayHi = greet.bind(person, "Hi"); +sayHi("?"); // "Hi, I'm John?" + +// Practical: Reusable functions +function fetch(endpoint, options) { + console.log(`Fetching ${this.baseUrl}${endpoint}`, options); +} + +const api = { + baseUrl: "https://api.example.com", + get: fetch.bind(null, "/users"), // 'this' will be api + post: fetch.bind(null, "/users", { method: "POST" }) +}; + +api.get.call(api, { method: "GET" }); // Explicit 'this' + +// Better: Use arrow functions +const api2 = { + baseUrl: "https://api.example.com", + get: function(endpoint, options) { + return fetch(`${this.baseUrl}${endpoint}`, options); + } +}; + +const getUsers = (endpoint, options) => api2.get.call(api2, endpoint, options); +``` + + +## Performance Considerations + + +```javascript !! js +// Binding creates new function (overhead) + +class Button { + constructor() { + this.text = "Click me"; + + // ❌ BAD: Creates new function on each render + this.handleClick = function() { + console.log(this.text); + }.bind(this); + } +} + +// ✅ GOOD: Use arrow function (still creates per instance) +class Button2 { + constructor() { + this.text = "Click me"; + this.handleClick = () => { + console.log(this.text); + }; + } +} + +// ✅ BETTER: Define method in prototype +class Button3 { + constructor() { + this.text = "Click me"; + } + + handleClick() { + console.log(this.text); + } + + setup() { + // Bind once + this.handleClick = this.handleClick.bind(this); + } +} + +// Best: Bind at call site (if possible) +const button = new Button3(); +button.element.addEventListener("click", () => button.handleClick()); + +// Performance comparison +const obj = { value: 42 }; + +// Bound function (slight overhead) +const bound = function() { + return this.value; +}.bind(obj); + +// Direct call (faster) +function getValue() { + return this.value; +} +getValue.call(obj); +``` + + +## Best Practices + + +```java !! java +// Java: Clear 'this' usage +public class Example { + private String name; + + public void method() { + this.name = "John"; // Clear 'this' + } +} +``` + +```javascript !! js +// JavaScript: Be intentional with 'this' + +// 1. Use arrow functions for callbacks +class Component { + constructor() { + this.state = { count: 0 }; + } + + increment() { + this.state.count++; + } + + setup() { + // ✅ Arrow function preserves 'this' + button.addEventListener("click", () => this.increment()); + } +} + +// 2. Bind when extracting methods +class Service { + async fetchData() { + // ... + } + + getFetcher() { + // ✅ Bind when returning method + return this.fetchData.bind(this); + } +} + +// 3. Use classes for predictable 'this' +class Counter { + constructor() { + this.count = 0; + } + + increment() { + this.count++; + } +} + +// Avoid constructor functions (use classes) +function Counter() { + this.count = 0; +} + +// 4. Understand arrow function limitations +const obj = { + // ❌ Don't use arrow for methods + // greet: () => console.log(this.name), + + // ✅ Use regular methods + greet() { + console.log(this.name); + } +}; + +// 5. Be careful with array methods +const processor = { + multiplier: 2, + + process(numbers) { + // ✅ Use arrow functions + return numbers.map(x => x * this.multiplier); + } +}; + +// 6. Don't use 'this' in static methods +class Utils { + static helper() { + // ❌ Don't use 'this' (it's the class, not instance) + // this.method(); + + // ✅ Use static calls + Utils.staticHelper(); + } + + static staticHelper() { + // ... + } +} + +// 7. Explicit 'this' binding when needed +function logThis() { + console.log(this); +} + +const context = { name: "Context" }; +setTimeout(logThis.bind(context), 1000); // Explicit 'this' +``` + + +## Exercises + +### Exercise 1: Fix This Context +```javascript +const counter = { + count: 0, + increment: function() { + setTimeout(function() { + this.count++; // Fix this + }, 1000); + } +}; +``` + +### Exercise 2: Array Method This +```javascript +const calculator = { + base: 10, + add(numbers) { + // Fix: return numbers with base added to each + return numbers.map(function(x) { + return x + this.base; + }); + } +}; +``` + +### Exercise 3: Event Handler +```javascript +class Button { + constructor(text) { + this.text = text; + this.element = document.createElement("button"); + this.element.textContent = text; + // Add click handler that logs this.text + } +} +``` + +### Exercise 4: Bind Arguments +```javascript +function multiply(a, b, c) { + return a * b * c; +} + +// Create a function that multiplies by 2 and 3 +// using bind (just needs one argument) +const multiplyBy6 = multiply.bind(null, ?, ?); +``` + +## Summary + +### Key Takeaways + +1. **This Rules:** + - Default: Global/undefined + - Implicit: Object left of dot + - Explicit: call/apply/bind + - New: Newly created object + +2. **Arrow Functions:** + - Lexical this (from surrounding scope) + - Cannot be bound with call/apply/bind + - Perfect for callbacks + +3. **Call/Apply/Bind:** + - call(): Invoke with specific this + - apply(): Same but array of args + - bind(): Create permanent this + +4. **Context Loss:** + - Common when passing methods + - Solve with bind or arrow functions + - Be careful with callbacks + +5. **Best Practices:** + - Use arrow functions for callbacks + - Use classes for structure + - Bind when extracting methods + - Understand arrow limitations + +### Comparison Table: Java vs JavaScript + +| Feature | Java | JavaScript | +|---------|------|------------| +| **This** | Always current instance | Depends on call site | +| **Binding** | Always bound | Can be changed | +| **Lambdas/Arrows** | Capture this | Lexical this | +| **Explicit binding** | No equivalent | call/apply/bind | +| **Loss of this** | Never happens | Common issue | +| **Solution** | Not needed | bind or arrow function | + +## What's Next? + +You've mastered JavaScript's `this` keyword! Next up is **Module 10: Asynchronous Programming Basics**, where we'll explore: + +- The JavaScript event loop +- Callback functions +- Understanding synchronous vs asynchronous +- Error handling in async code +- Common async patterns + +Ready to dive into asynchronous programming? Let's continue! diff --git a/content/docs/java2js/module-09-this-context.zh-tw.mdx b/content/docs/java2js/module-09-this-context.zh-tw.mdx new file mode 100644 index 0000000..14fea93 --- /dev/null +++ b/content/docs/java2js/module-09-this-context.zh-tw.mdx @@ -0,0 +1,260 @@ +--- +title: "Module 9: This and Context" +description: "掌握 JavaScript 'this' 關鍵字和 context 綁定" +--- + +## Module 9: This and Context + +JavaScript 中的 `this` 關鍵字行為與 Java 非常不同。理解 `this` 如何運作對於編寫無錯誤的 JavaScript 程式碼至關重要。本模組將揭開 `this` 的神秘面紗。 + +## Learning Objectives + +完成本模組後,你將: +✅ 理解 `this` 綁定如何運作 +✅ 掌握 call()、apply() 和 bind() 方法 +✅ 學習箭頭函數和詞法 this +✅ 知道不同 context 中的 this +✅ 理解常見的 this 相關陷阱 +✅ 學習管理 context 的最佳實踐 + +## This Comparison: Java vs JavaScript + + +```java !! java +// Java - 'this' 總是指向當前實例 +public class Counter { + private int count = 0; + + public Counter() { + this.count = 0; // 'this' 是 Counter 實例 + } + + public void increment() { + this.count++; // 總是指向 Counter 實例 + } +} + +// 'this' 是可預測的,總是指向實例 +``` + +```javascript !! js +// JavaScript - 'this' 取決於調用點 +const counter = { + count: 0, + + increment() { + this.count++; // 'this' 取決於函數如何被調用 + }, + + // 箭頭函數:'this' 來自周圍作用域 + incrementArrow: () => { + this.count++; // 'this' 不是 counter! + } +}; + +counter.increment(); // 'this' = counter ✓ +counter.incrementArrow(); // 'this' = undefined(或瀏覽器中的 window)✗ + +// 'this' 根據調用點變化 +const fn = counter.increment; +fn(); // 'this' = undefined(嚴格模式)或 window + +// 可顯式綁定 'this' +fn.call(counter); // 'this' = counter ✓ +``` + + +## This Rules + +JavaScript 根據函數如何被調用來決定 `this`: + + +```javascript !! js +// 規則 1: 默認綁定 +// 'this' 是全局物件(非嚴格)或 undefined(嚴格) +function sayName() { + console.log(this.name); +} + +const name = "Global"; +sayName(); // "Global"(非嚴格)或 undefined(嚴格) + +// 規則 2: 隱式綁定 +// 'this' 是點左邊的物件 +const person = { + name: "John", + greet() { + console.log(this.name); + } +}; + +person.greet(); // "John" - 'this' = person + +// 規則 3: 顯式綁定 +// 'this' 由 call/apply/bind 指定 +function greet() { + console.log(this.name); +} + +const user = { name: "Jane" }; +greet.call(user); // "Jane" - 'this' = user +greet.apply(user); // "Jane" - 'this' = user +const bound = greet.bind(user); +bound(); // "Jane" - 'this' = user + +// 規則 4: New 綁定 +// 'this' 是新創建的物件 +function User(name) { + this.name = name; +} + +const user2 = new User("Bob"); +console.log(user2.name); // "Bob" - 'this' = user2 +``` + + +## Call, Apply, and Bind + +JavaScript 提供了明確控制 `this` 的方法: + + +```javascript !! js +// call(): 使用特定 'this' 調用 +function introduce(greeting, punctuation) { + console.log(`${greeting}, I'm ${this.name}${punctuation}`); +} + +const person = { name: "John" }; +introduce.call(person, "Hello", "!"); // "Hello, I'm John!" + +// apply(): 與 call 相同但參數為陣列 +introduce.apply(person, ["Hi", "?"]); // "Hi, I'm John?" + +// bind(): 創建永久 'this' 的函數 +const person2 = { name: "Jane" }; +const introduceJane = introduce.bind(person2, "Hey"); +introduceJane("!"); // "Hey, I'm Jane!" + +// 實用:事件處理程序 +class Button { + constructor(label) { + this.label = label; + this.handleClick = this.handleClick.bind(this); + } + + handleClick() { + console.log(`Button "${this.label}" clicked`); + } +} +``` + + +## Arrow Functions and Lexical This + +箭頭函數沒有自己的 `this` - 它們從周圍作用域繼承: + + +```javascript !! js +// 正則函數:'this' 基於調用點 +const counter = { + count: 0, + increment: function() { + setTimeout(function() { + this.count++; // ❌ 'this' 不是 counter! + }, 1000); + } +}; + +// 箭頭函數:'this' 來自周圍作用域 +const counter2 = { + count: 0, + increment: function() { + setTimeout(() => { + this.count++; // ✅ 'this' 是 counter2 + }, 1000); + } +}; + +// 箭頭函數不能被綁定 +const arrow = () => console.log(this); +arrow.call({ name: "John" }); // 仍然是全局/undefined +``` + + +## Common Pitfalls + +### Pitfall 1: Losing This + + +```javascript !! js +// 問題:傳遞方法時丟失 'this' +const controller = { + data: [1, 2, 3], + process() { + return this.data.map(x => x * 2); + } +}; + +// ❌ 錯誤:提取方法丟失 'this' +const process = controller.process; +process(); // Error: Cannot read 'data' of undefined + +// ✅ 解決方案 1: Bind +const process2 = controller.process.bind(controller); +process2(); // Works! + +// ✅ 解決方案 2: 箭頭函數 +const process3 = () => controller.process(); +process3(); // Works! +``` + + +## Best Practices + + +```javascript !! js +// 1. 對回調使用箭頭函數 +class Component { + constructor() { + this.state = { count: 0 }; + } + + setup() { + // ✅ 箭頭函數保留 'this' + button.addEventListener("click", () => this.increment()); + } +} + +// 2. 提取方法時綁定 +class Service { + getFetcher() { + // ✅ 返回時綁定 + return this.fetchData.bind(this); + } +} + +// 3. 使用類別以獲得可預測的 'this' +class Counter { + constructor() { + this.count = 0; + } + + increment() { + this.count++; + } +} +``` + + +## Summary + +### Key Takeaways + +1. **This Rules:** 默認、隱式、顯式、New +2. **Arrow Functions:** 詞法 this +3. **Call/Apply/Bind:** 顯式控制 this +4. **Context Loss:** 常見問題,使用箭頭函數或 bind 解決 + +## What's Next? + +接下來是 **Module 10: Asynchronous Programming Basics** - 學習事件循環和回調! diff --git a/content/docs/java2js/module-10-async-basics.mdx b/content/docs/java2js/module-10-async-basics.mdx new file mode 100644 index 0000000..125b655 --- /dev/null +++ b/content/docs/java2js/module-10-async-basics.mdx @@ -0,0 +1,1057 @@ +--- +title: "Module 10: Asynchronous Programming Basics" +description: "Understand JavaScript's asynchronous programming model, callbacks, and the event loop" +--- + +## Module 10: Asynchronous Programming Basics + +Asynchronous programming is fundamental to JavaScript. Unlike Java's multi-threaded model, JavaScript uses a single-threaded event loop. Understanding this model is crucial for writing effective JavaScript code. + +## Learning Objectives + +By the end of this module, you will: +✅ Understand the JavaScript event loop +✅ Learn synchronous vs asynchronous execution +✅ Master callback functions +✅ Understand error handling in callbacks +✅ Learn about timing and scheduling +✅ Know common async patterns and pitfalls + +## Concurrency Models: Java vs JavaScript + + +```java !! java +// Java - Multi-threaded +public class Example { + public static void main(String[] args) { + // Each thread runs independently + Thread thread1 = new Thread(() -> { + System.out.println("Thread 1"); + try { + Thread.sleep(1000); + System.out.println("Thread 1 done"); + } catch (InterruptedException e) { + e.printStackTrace(); + } + }); + + Thread thread2 = new Thread(() -> { + System.out.println("Thread 2"); + }); + + thread1.start(); + thread2.start(); + + System.out.println("Main thread"); + } +} +// Output: Main thread, Thread 1, Thread 2 (parallel execution) +``` + +```javascript !! js +// JavaScript - Single-threaded with event loop +console.log("Start"); + +setTimeout(() => { + console.log("Timeout 1"); +}, 0); + +console.log("Middle"); + +setTimeout(() => { + console.log("Timeout 2"); +}, 0); + +console.log("End"); + +// Output: +// Start +// Middle +// End +// Timeout 1 +// Timeout 2 + +// Even with 0ms delay, callbacks run after synchronous code +``` + + +## The Event Loop + +JavaScript has a single call stack and an event loop that manages execution: + + +```java !! java +// Java - Multiple call stacks (threads) +public class MultiThread { + public static void main(String[] args) { + // Main thread + new Thread(() -> method1()).start(); + new Thread(() -> method2()).start(); + // Both run concurrently on different stacks + } + + static void method1() { + // Own stack + } + + static void method2() { + // Own stack + } +} +``` + +```javascript !! js +// JavaScript - Single call stack, event loop + +console.log("1. Synchronous"); + +// Callback goes to task queue (microtask for promise, macrotask for setTimeout) +setTimeout(() => { + console.log("2. Async (macrotask)"); +}, 0); + +Promise.resolve().then(() => { + console.log("3. Async (microtask)"); +}); + +console.log("4. Synchronous"); + +// Output: +// 1. Synchronous +// 4. Synchronous +// 3. Async (microtask) - runs before macrotasks +// 2. Async (macrotask) + +// Event loop process: +// 1. Execute synchronous code (call stack) +// 2. Process all microtasks (Promise callbacks) +// 3. Render UI (browser) +// 4. Process one macrotask (setTimeout, setInterval, I/O) +// 5. Repeat + +// Microtasks (higher priority): +// - Promise.then/catch/finally +// - queueMicrotask() +// - MutationObserver + +// Macrotasks (lower priority): +// - setTimeout +// - setInterval +// - setImmediate (Node.js) +// - I/O operations +// - UI rendering +``` + + +### Visualizing the Event Loop + + +```javascript !! js +console.log("Start"); + +// Macrotask 1 +setTimeout(() => { + console.log("Timeout 1"); +}, 0); + +// Microtask 1 +Promise.resolve().then(() => { + console.log("Promise 1"); +}); + +// Macrotask 2 +setTimeout(() => { + console.log("Timeout 2"); + // Microtask inside macrotask + Promise.resolve().then(() => { + console.log("Promise 2"); + }); +}, 0); + +// Synchronous +console.log("End"); + +// Execution order: +// 1. "Start" (synchronous) +// 2. "End" (synchronous) +// 3. Clear call stack +// 4. Process microtasks -> "Promise 1" +// 5. Process one macrotask -> "Timeout 1" +// 6. Check for new microtasks -> None +// 7. Process next macrotask -> "Timeout 2" +// 8. Check for new microtasks -> "Promise 2" +// 9. No more tasks, wait for new events + +// Final output: +// Start +// End +// Promise 1 +// Timeout 1 +// Timeout 2 +// Promise 2 +``` + + +## Callback Functions + +Callbacks are functions passed as arguments to be executed later: + + +```java !! java +// Java - Callbacks with interfaces +public interface Callback { + void onComplete(String result); +} + +public class DataFetcher { + public void fetchData(Callback callback) { + // Simulate async operation + new Thread(() -> { + String result = "Data"; + callback.onComplete(result); + }).start(); + } +} + +// Usage +fetcher.fetchData(result -> { + System.out.println("Received: " + result); +}); +``` + +```javascript !! js +// JavaScript - First-class callback functions + +function fetchData(callback) { + // Simulate async operation + setTimeout(() => { + const data = { id: 1, name: "John" }; + callback(data); + }, 1000); +} + +// Callback function +fetchData(function(data) { + console.log("Received:", data); +}); + +// Arrow function callback +fetchData(data => { + console.log("Received:", data); +}); + +// Synchronous callback (executes immediately) +function map(array, callback) { + const result = []; + for (const item of array) { + result.push(callback(item)); + } + return result; +} + +const doubled = map([1, 2, 3], x => x * 2); +console.log(doubled); // [2, 4, 6] + +// Asynchronous callback +function fetchUser(id, callback) { + setTimeout(() => { + const user = { id, name: "User " + id }; + callback(null, user); + }, 1000); +} + +fetchUser(1, (error, user) => { + if (error) { + console.error("Error:", error); + return; + } + console.log("User:", user); +}); +``` + + +### Callback Patterns + + +```java !! java +// Java - Synchronous vs asynchronous +public class FileProcessor { + // Synchronous (blocks) + public String readFile(String path) { + return java.nio.file.Files.readString(path); + } + + // Asynchronous (uses CompletableFuture) + public CompletableFuture readFileAsync(String path) { + return CompletableFuture.supplyAsync(() -> { + return readFile(path); + }); + } +} +``` + +```javascript !! fs +// JavaScript - Callback patterns + +// Pattern 1: Error-first callback (Node.js convention) +function readFile(path, callback) { + setTimeout(() => { + if (path.endsWith(".txt")) { + callback(null, "File content"); + } else { + callback(new Error("Invalid file type")); + } + }, 1000); +} + +readFile("data.txt", (error, content) => { + if (error) { + console.error("Error:", error.message); + return; + } + console.log("Content:", content); +}); + +// Pattern 2: Success/error callbacks +function fetchUserData(userId, onSuccess, onError) { + setTimeout(() => { + if (userId > 0) { + onSuccess({ id: userId, name: "John" }); + } else { + onError(new Error("Invalid user ID")); + } + }, 1000); +} + +fetchUserData( + 1, + user => console.log("User:", user), + error => console.error("Error:", error.message) +); + +// Pattern 3: Event emitter pattern +class EventEmitter { + constructor() { + this.events = {}; + } + + on(event, callback) { + if (!this.events[event]) { + this.events[event] = []; + } + this.events[event].push(callback); + } + + emit(event, data) { + if (this.events[event]) { + this.events[event].forEach(callback => callback(data)); + } + } +} + +const emitter = new EventEmitter(); + +emitter.on("data", data => { + console.log("Received data:", data); +}); + +emitter.emit("data", { message: "Hello" }); +``` + + +## Callback Hell and Solutions + + +```javascript !! js +// ❌ BAD: Callback hell (pyramid of doom) +getUser(userId, (error, user) => { + if (error) { + console.error(error); + return; + } + + getOrders(user.id, (error, orders) => { + if (error) { + console.error(error); + return; + } + + getOrderItems(orders[0].id, (error, items) => { + if (error) { + console.error(error); + return; + } + + console.log("Items:", items); + }); + }); +}); + +// ✅ Solution 1: Named functions +function handleUser(error, user) { + if (error) return console.error(error); + getOrders(user.id, handleOrders); +} + +function handleOrders(error, orders) { + if (error) return console.error(error); + getOrderItems(orders[0].id, handleItems); +} + +function handleItems(error, items) { + if (error) return console.error(error); + console.log("Items:", items); +} + +getUser(userId, handleUser); + +// ✅ Solution 2: Promise chaining (preferred) +getUser(userId) + .then(user => getOrders(user.id)) + .then(orders => getOrderItems(orders[0].id)) + .then(items => console.log("Items:", items)) + .catch(error => console.error(error)); + +// ✅ Solution 3: Async/await (modern) +async function fetchItems() { + try { + const user = await getUser(userId); + const orders = await getOrders(user.id); + const items = await getOrderItems(orders[0].id); + console.log("Items:", items); + } catch (error) { + console.error(error); + } +} +``` + + +## Timing Functions + +JavaScript provides several timing functions: + + +```java !! java +// Java - ScheduledExecutorService +ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); + +// Delay execution +scheduler.schedule(() -> { + System.out.println("Delayed"); +}, 1, TimeUnit.SECONDS); + +// Periodic execution +scheduler.scheduleAtFixedRate(() -> { + System.out.println("Repeated"); +}, 0, 1, TimeUnit.SECONDS); +``` + +```javascript !! js +// JavaScript - Timing functions + +// setTimeout: Execute once after delay +const timeoutId = setTimeout(() => { + console.log("Delayed execution"); +}, 1000); + +// Cancel timeout +clearTimeout(timeoutId); + +// setInterval: Execute repeatedly +let count = 0; +const intervalId = setInterval(() => { + count++; + console.log("Repeated:", count); + + if (count >= 5) { + clearInterval(intervalId); // Stop after 5 times + } +}, 1000); + +// setImmediate: Execute after current call stack (Node.js) +setImmediate(() => { + console.log("Immediate"); +}); + +// Practical: Debounce +function debounce(fn, delay) { + let timeoutId; + return function(...args) { + clearTimeout(timeoutId); + timeoutId = setTimeout(() => fn.apply(this, args), delay); + }; +} + +const searchInput = debounce((value) => { + console.log("Searching for:", value); +}, 300); + +searchInput("a"); +searchInput("ab"); +searchInput("abc"); +// Only searches once, 300ms after last input + +// Practical: Throttle +function throttle(fn, delay) { + let lastCall = 0; + return function(...args) { + const now = Date.now(); + if (now - lastCall >= delay) { + lastCall = now; + fn.apply(this, args); + } + }; +} + +const handleScroll = throttle(() => { + console.log("Scroll handler"); +}, 100); + +window.addEventListener("scroll", handleScroll); +``` + + +## Error Handling in Callbacks + + +```java !! java +// Java - Try-catch works with threads +try { + future.get(); // Wait for result +} catch (InterruptedException | ExecutionException e) { + e.printStackTrace(); +} +``` + +```javascript !! js +// JavaScript - Try-catch doesn't catch async errors + +try { + setTimeout(() => { + throw new Error("Async error"); // Uncaught! + }, 1000); +} catch (e) { + console.error("Caught:", e); // Never runs +} + +// ❌ BAD: Callback errors need explicit handling +function fetchData(callback) { + setTimeout(() => { + const data = JSON.parse(invalidJson); // Throws! + callback(null, data); + }, 1000); +} + +// ✅ GOOD: Try-catch inside callback +function fetchData(callback) { + setTimeout(() => { + try { + const data = JSON.parse(jsonString); + callback(null, data); + } catch (error) { + callback(error); + } + }, 1000); +} + +// ✅ GOOD: Error-first callback convention +fs.readFile("data.txt", (error, data) => { + if (error) { + console.error("Read error:", error); + return; + } + + try { + const parsed = JSON.parse(data); + console.log("Parsed:", parsed); + } catch (parseError) { + console.error("Parse error:", parseError); + } +}); + +// Error propagation in nested callbacks +function processUser(userId, callback) { + getUser(userId, (error, user) => { + if (error) { + return callback(error); // Propagate error + } + + getOrders(user.id, (error, orders) => { + if (error) { + return callback(error); // Propagate error + } + + callback(null, orders); + }); + }); +} + +processUser(1, (error, orders) => { + if (error) { + console.error("Failed:", error); + return; + } + + console.log("Orders:", orders); +}); +``` + + +## Common Patterns + +### Pattern 1: Parallel Async Operations + + +```java !! java +// Java - CompletableFuture for parallel operations +CompletableFuture future1 = CompletableFuture.supplyAsync(() -> fetchData1()); +CompletableFuture future2 = CompletableFuture.supplyAsync(() -> fetchData2()); + +CompletableFuture.allOf(future1, future2) + .thenRun(() -> { + String result1 = future1.join(); + String result2 = future2.join(); + System.out.println(result1 + result2); + }); +``` + +```javascript !! js +// JavaScript - Parallel callbacks + +function parallel(tasks, callback) { + let completed = 0; + const results = []; + let hasError = false; + + tasks.forEach((task, index) => { + task((error, result) => { + if (hasError) return; // Already failed + + if (error) { + hasError = true; + callback(error); + return; + } + + results[index] = result; + completed++; + + if (completed === tasks.length) { + callback(null, results); + } + }); + }); +} + +// Usage +const tasks = [ + cb => setTimeout(() => cb(null, "Result 1"), 1000), + cb => setTimeout(() => cb(null, "Result 2"), 500), + cb => setTimeout(() => cb(null, "Result 3"), 1500) +]; + +parallel(tasks, (error, results) => { + if (error) { + console.error(error); + return; + } + console.log("All results:", results); + // All results: ["Result 1", "Result 2", "Result 3"] + // Total time: ~1500ms (longest task), not 3000ms +}); +``` + + +### Pattern 2: Sequential Async Operations + + +```javascript !! js +// JavaScript - Sequential execution with callbacks + +function series(tasks, callback) { + let index = 0; + + function runNext() { + if (index >= tasks.length) { + return callback(null); + } + + tasks[index]((error, result) => { + if (error) { + return callback(error); + } + + index++; + runNext(); // Next task + }); + } + + runNext(); +} + +// Usage +const tasks = [ + cb => setTimeout(() => { + console.log("Task 1"); + cb(null); + }, 1000), + cb => setTimeout(() => { + console.log("Task 2"); + cb(null); + }, 500), + cb => setTimeout(() => { + console.log("Task 3"); + cb(null); + }, 100) +]; + +series(tasks, (error) => { + if (error) { + console.error(error); + return; + } + console.log("All tasks done"); + // Total time: 1600ms (sum of all tasks) +}); +``` + + +### Pattern 3: Waterfall (Pass Results) + + +```javascript !! js +// JavaScript - Waterfall (each task gets previous results) + +function waterfall(tasks, callback) { + let index = 0; + const results = []; + + function runNext(...args) { + if (index >= tasks.length) { + return callback(null, ...results); + } + + const task = tasks[index]; + + // First task gets no arguments + const taskArgs = index === 0 ? [] : [args]; + + task(...taskArgs, (error, ...result) => { + if (error) { + return callback(error); + } + + results.push(...result); + index++; + runNext(...result); + }); + } + + runNext(); +} + +// Usage +const tasks = [ + (cb) => { + setTimeout(() => cb(null, "User 1"), 100); + }, + (user, cb) => { + setTimeout(() => cb(null, user, "Orders"), 100); + }, + (user, orders, cb) => { + setTimeout(() => cb(null, user, orders, "Items"), 100); + } +]; + +waterfall(tasks, (error, user, orders, items) => { + if (error) { + console.error(error); + return; + } + console.log("Final:", { user, orders, items }); + // Final: { user: "User 1", orders: "Orders", items: "Items" } +}); +``` + + +## Common Pitfalls + +### Pitfall 1: Forgetting Return + + +```javascript !! js +// ❌ BAD: Forgetting return leads to continued execution +function processData(data, callback) { + if (!data) { + callback(new Error("No data")); + // Missing return - execution continues! + } + + const result = transform(data); + callback(null, result); +} + +// ✅ GOOD: Always return after callback +function processData(data, callback) { + if (!data) { + return callback(new Error("No data")); + } + + const result = transform(data); + callback(null, result); +} +``` + + +### Pitfall 2: Loop Issues + + +```javascript !! js +// ❌ BAD: Closure issue in loop +for (var i = 0; i < 3; i++) { + setTimeout(() => { + console.log(i); // 3, 3, 3 (not 0, 1, 2!) + }, 100); +} + +// ✅ Solution 1: Use let +for (let i = 0; i < 3; i++) { + setTimeout(() => { + console.log(i); // 0, 1, 2 + }, 100); +} + +// ✅ Solution 2: IIFE (for var) +for (var i = 0; i < 3; i++) { + (function(j) { + setTimeout(() => { + console.log(j); // 0, 1, 2 + }, 100); + })(i); +} + +// ✅ Solution 3: forEach (with callbacks) +[0, 1, 2].forEach(i => { + setTimeout(() => { + console.log(i); // 0, 1, 2 + }, 100); +}); +``` + + +### Pitfall 3: This Context + + +```javascript !! js +// ❌ BAD: 'this' lost in callback +class Timer { + constructor() { + this.seconds = 0; + } + + start() { + setTimeout(function() { + this.seconds++; // Error: 'this' is not Timer + }, 1000); + } +} + +// ✅ GOOD: Use arrow function +class Timer { + constructor() { + this.seconds = 0; + } + + start() { + setTimeout(() => { + this.seconds++; // 'this' is Timer + }, 1000); + } +} + +// ✅ ALSO GOOD: Bind +class Timer { + constructor() { + this.seconds = 0; + } + + start() { + setTimeout(function() { + this.seconds++; + }.bind(this), 1000); + } +} +``` + + +## Best Practices + + +```java !! java +// Java: Clear exception handling +try { + future.get(); +} catch (ExecutionException e) { + logger.error("Failed", e); +} +``` + +```javascript !! js +// JavaScript: Callback best practices + +// 1. Error-first callback convention (Node.js style) +function operation(callback) { + // callback(error, result) + callback(null, "success"); +} + +operation((error, result) => { + if (error) { + // Handle error + return; + } + // Use result +}); + +// 2. Always check for errors +fs.readFile("data.txt", (error, data) => { + if (error) { + console.error("Read failed:", error); + return; + } + console.log("Data:", data); +}); + +// 3. Return after callback +function process(data, callback) { + if (!data) { + return callback(new Error("No data")); + } + callback(null, processData(data)); +} + +// 4. Prefer named functions for callbacks +// Good: Debuggable, reusable +function handleData(error, data) { + if (error) return console.error(error); + console.log(data); +} + +fetchData(handleData); + +// 5. Handle errors in async operations +function safeOperation(callback) { + setTimeout(() => { + try { + const result = riskyOperation(); + callback(null, result); + } catch (error) { + callback(error); + } + }, 1000); +} + +// 6. Avoid callback hell - use promises/async-await +// We'll cover this in Module 11! +``` + + +## Exercises + +### Exercise 1: Fix Callback Order +```javascript +console.log("Start"); +setTimeout(() => console.log("Timeout"), 0); +console.log("End"); +// What's the output and why? +``` + +### Exercise 2: Implement Debounce +```javascript +function debounce(fn, delay) { + // Implement debounce function +} + +const search = debounce((query) => { + console.log("Searching:", query); +}, 300); +``` + +### Exercise 3: Sequential Execution +```javascript +// Execute these tasks sequentially: +// Task 1: Wait 1s, log "Task 1" +// Task 2: Wait 2s, log "Task 2" +// Task 3: Wait 1s, log "Task 3" +``` + +### Exercise 4: Parallel Execution +```javascript +// Execute tasks in parallel and get all results +const tasks = [ + (cb) => setTimeout(() => cb(null, "A"), 1000), + (cb) => setTimeout(() => cb(null, "B"), 500), + (cb) => setTimeout(() => cb(null, "C"), 1500) +]; +// Implement parallel() function +``` + +## Summary + +### Key Takeaways + +1. **Event Loop:** + - Single-threaded execution + - Microtasks before macrotasks + - Non-blocking I/O + +2. **Callbacks:** + - Functions passed as arguments + - Execute after async operation + - Error-first convention + +3. **Timing:** + - setTimeout: Delayed execution + - setInterval: Repeated execution + - Debounce/throttle for optimization + +4. **Patterns:** + - Parallel: Multiple operations + - Series: Sequential execution + - Waterfall: Pass results + +5. **Pitfalls:** + - Callback hell + - This context loss + - Loop closure issues + - Uncaught async errors + +6. **Best Practices:** + - Error-first callbacks + - Always check for errors + - Use named functions + - Prefer promises/async-await + +### Comparison Table: Java vs JavaScript + +| Feature | Java | JavaScript | +|---------|------|------------| +| **Model** | Multi-threaded | Single-threaded + event loop | +| **Blocking** | Common | Avoided | +| **Async** | CompletableFuture/Threads | Callbacks/Promises | +| **Parallelism** | True parallel | Event loop (single thread) | +| **Error Handling** | Try-catch works | Need error callbacks | +| **Timing** | ScheduledExecutorService | setTimeout/setInterval | + +## What's Next? + +You've learned JavaScript's asynchronous basics! Next up is **Module 11: Asynchronous Programming Advanced**, where we'll explore: + +- Promises and promise chaining +- async/await syntax +- Error handling with promises +- Promise.all, Promise.race, etc. +- Converting callbacks to promises +- Modern async patterns + +Ready to master modern async JavaScript? Let's continue! diff --git a/content/docs/java2js/module-10-async-basics.zh-cn.mdx b/content/docs/java2js/module-10-async-basics.zh-cn.mdx new file mode 100644 index 0000000..c2c69b6 --- /dev/null +++ b/content/docs/java2js/module-10-async-basics.zh-cn.mdx @@ -0,0 +1,1057 @@ +--- +title: "模块 10:异步编程基础" +description: "理解 JavaScript 的异步编程模型、回调和事件循环" +--- + +## 模块 10:异步编程基础 + +异步编程是 JavaScript 的基础。与 Java 的多线程模型不同,JavaScript 使用单线程事件循环。理解这个模型对于编写有效的 JavaScript 代码至关重要。 + +## 学习目标 + +完成本模块后,你将: +✅ 理解 JavaScript 事件循环 +✅ 学习同步与异步执行 +✅ 掌握回调函数 +✅ 理解回调中的错误处理 +✅ 了解定时和调度 +✅ 掌握常见的异步模式和陷阱 + +## 并发模型:Java vs JavaScript + + +```java !! java +// Java - 多线程 +public class Example { + public static void main(String[] args) { + // 每个线程独立运行 + Thread thread1 = new Thread(() -> { + System.out.println("Thread 1"); + try { + Thread.sleep(1000); + System.out.println("Thread 1 done"); + } catch (InterruptedException e) { + e.printStackTrace(); + } + }); + + Thread thread2 = new Thread(() -> { + System.out.println("Thread 2"); + }); + + thread1.start(); + thread2.start(); + + System.out.println("Main thread"); + } +} +// 输出: Main thread, Thread 1, Thread 2 (并行执行) +``` + +```javascript !! js +// JavaScript - 单线程与事件循环 +console.log("Start"); + +setTimeout(() => { + console.log("Timeout 1"); +}, 0); + +console.log("Middle"); + +setTimeout(() => { + console.log("Timeout 2"); +}, 0); + +console.log("End"); + +// 输出: +// Start +// Middle +// End +// Timeout 1 +// Timeout 2 + +// 即使延迟为 0ms,回调也在同步代码之后执行 +``` + + +## 事件循环 + +JavaScript 有一个单一的调用栈和一个管理执行的事件循环: + + +```java !! java +// Java - 多个调用栈(线程) +public class MultiThread { + public static void main(String[] args) { + // 主线程 + new Thread(() -> method1()).start(); + new Thread(() -> method2()).start(); + // 两者在不同的栈上并发运行 + } + + static void method1() { + // 自己的栈 + } + + static void method2() { + // 自己的栈 + } +} +``` + +```javascript !! js +// JavaScript - 单调用栈,事件循环 + +console.log("1. Synchronous"); + +// 回调进入任务队列(Promise 为微任务,setTimeout 为宏任务) +setTimeout(() => { + console.log("2. Async (macrotask)"); +}, 0); + +Promise.resolve().then(() => { + console.log("3. Async (microtask)"); +}); + +console.log("4. Synchronous"); + +// 输出: +// 1. Synchronous +// 4. Synchronous +// 3. Async (microtask) - 在宏任务之前运行 +// 2. Async (macrotask) + +// 事件循环过程: +// 1. 执行同步代码(调用栈) +// 2. 处理所有微任务(Promise 回调) +// 3. 渲染 UI (浏览器) +// 4. 处理一个宏任务(setTimeout, setInterval, I/O) +// 5. 重复 + +// 微任务(高优先级): +// - Promise.then/catch/finally +// - queueMicrotask() +// - MutationObserver + +// 宏任务(低优先级): +// - setTimeout +// - setInterval +// - setImmediate (Node.js) +// - I/O 操作 +// - UI 渲染 +``` + + +### 可视化事件循环 + + +```javascript !! js +console.log("Start"); + +// 宏任务 1 +setTimeout(() => { + console.log("Timeout 1"); +}, 0); + +// 微任务 1 +Promise.resolve().then(() => { + console.log("Promise 1"); +}); + +// 宏任务 2 +setTimeout(() => { + console.log("Timeout 2"); + // 宏任务内的微任务 + Promise.resolve().then(() => { + console.log("Promise 2"); + }); +}, 0); + +// 同步 +console.log("End"); + +// 执行顺序: +// 1. "Start" (同步) +// 2. "End" (同步) +// 3. 清空调用栈 +// 4. 处理微任务 -> "Promise 1" +// 5. 处理一个宏任务 -> "Timeout 1" +// 6. 检查新微任务 -> 无 +// 7. 处理下一个宏任务 -> "Timeout 2" +// 8. 检查新微任务 -> "Promise 2" +// 9. 没有更多任务,等待新事件 + +// 最终输出: +// Start +// End +// Promise 1 +// Timeout 1 +// Timeout 2 +// Promise 2 +``` + + +## 回调函数 + +回调是作为参数传递的函数,稍后执行: + + +```java !! java +// Java - 使用接口的回调 +public interface Callback { + void onComplete(String result); +} + +public class DataFetcher { + public void fetchData(Callback callback) { + // 模拟异步操作 + new Thread(() -> { + String result = "Data"; + callback.onComplete(result); + }).start(); + } +} + +// 使用 +fetcher.fetchData(result -> { + System.out.println("Received: " + result); +}); +``` + +```javascript !! js +// JavaScript - 一等公民回调函数 + +function fetchData(callback) { + // 模拟异步操作 + setTimeout(() => { + const data = { id: 1, name: "John" }; + callback(data); + }, 1000); +} + +// 回调函数 +fetchData(function(data) { + console.log("Received:", data); +}); + +// 箭头函数回调 +fetchData(data => { + console.log("Received:", data); +}); + +// 同步回调(立即执行) +function map(array, callback) { + const result = []; + for (const item of array) { + result.push(callback(item)); + } + return result; +} + +const doubled = map([1, 2, 3], x => x * 2); +console.log(doubled); // [2, 4, 6] + +// 异步回调 +function fetchUser(id, callback) { + setTimeout(() => { + const user = { id, name: "User " + id }; + callback(null, user); + }, 1000); +} + +fetchUser(1, (error, user) => { + if (error) { + console.error("Error:", error); + return; + } + console.log("User:", user); +}); +``` + + +### 回调模式 + + +```java !! java +// Java - 同步与异步 +public class FileProcessor { + // 同步(阻塞) + public String readFile(String path) { + return java.nio.file.Files.readString(path); + } + + // 异步(使用 CompletableFuture) + public CompletableFuture readFileAsync(String path) { + return CompletableFuture.supplyAsync(() -> { + return readFile(path); + }); + } +} +``` + +```javascript !! fs +// JavaScript - 回调模式 + +// 模式 1: 错误优先回调(Node.js 约定) +function readFile(path, callback) { + setTimeout(() => { + if (path.endsWith(".txt")) { + callback(null, "File content"); + } else { + callback(new Error("Invalid file type")); + } + }, 1000); +} + +readFile("data.txt", (error, content) => { + if (error) { + console.error("Error:", error.message); + return; + } + console.log("Content:", content); +}); + +// 模式 2: 成功/错误回调 +function fetchUserData(userId, onSuccess, onError) { + setTimeout(() => { + if (userId > 0) { + onSuccess({ id: userId, name: "John" }); + } else { + onError(new Error("Invalid user ID")); + } + }, 1000); +} + +fetchUserData( + 1, + user => console.log("User:", user), + error => console.error("Error:", error.message) +); + +// 模式 3: 事件发射器模式 +class EventEmitter { + constructor() { + this.events = {}; + } + + on(event, callback) { + if (!this.events[event]) { + this.events[event] = []; + } + this.events[event].push(callback); + } + + emit(event, data) { + if (this.events[event]) { + this.events[event].forEach(callback => callback(data)); + } + } +} + +const emitter = new EventEmitter(); + +emitter.on("data", data => { + console.log("Received data:", data); +}); + +emitter.emit("data", { message: "Hello" }); +``` + + +## 回调地狱和解决方案 + + +```javascript !! js +// ❌ 不好:回调地狱(厄运金字塔) +getUser(userId, (error, user) => { + if (error) { + console.error(error); + return; + } + + getOrders(user.id, (error, orders) => { + if (error) { + console.error(error); + return; + } + + getOrderItems(orders[0].id, (error, items) => { + if (error) { + console.error(error); + return; + } + + console.log("Items:", items); + }); + }); +}); + +// ✅ 解决方案 1:命名函数 +function handleUser(error, user) { + if (error) return console.error(error); + getOrders(user.id, handleOrders); +} + +function handleOrders(error, orders) { + if (error) return console.error(error); + getOrderItems(orders[0].id, handleItems); +} + +function handleItems(error, items) { + if (error) return console.error(error); + console.log("Items:", items); +} + +getUser(userId, handleUser); + +// ✅ 解决方案 2:Promise 链(推荐) +getUser(userId) + .then(user => getOrders(user.id)) + .then(orders => getOrderItems(orders[0].id)) + .then(items => console.log("Items:", items)) + .catch(error => console.error(error)); + +// ✅ 解决方案 3:Async/await(现代) +async function fetchItems() { + try { + const user = await getUser(userId); + const orders = await getOrders(user.id); + const items = await getOrderItems(orders[0].id); + console.log("Items:", items); + } catch (error) { + console.error(error); + } +} +``` + + +## 定时函数 + +JavaScript 提供了几个定时函数: + + +```java !! java +// Java - ScheduledExecutorService +ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); + +// 延迟执行 +scheduler.schedule(() -> { + System.out.println("Delayed"); +}, 1, TimeUnit.SECONDS); + +// 周期执行 +scheduler.scheduleAtFixedRate(() -> { + System.out.println("Repeated"); +}, 0, 1, TimeUnit.SECONDS); +``` + +```javascript !! js +// JavaScript - 定时函数 + +// setTimeout:延迟后执行一次 +const timeoutId = setTimeout(() => { + console.log("Delayed execution"); +}, 1000); + +// 取消超时 +clearTimeout(timeoutId); + +// setInterval:重复执行 +let count = 0; +const intervalId = setInterval(() => { + count++; + console.log("Repeated:", count); + + if (count >= 5) { + clearInterval(intervalId); // 5 次后停止 + } +}, 1000); + +// setImmediate:当前调用栈后执行(Node.js) +setImmediate(() => { + console.log("Immediate"); +}); + +// 实用:防抖 +function debounce(fn, delay) { + let timeoutId; + return function(...args) { + clearTimeout(timeoutId); + timeoutId = setTimeout(() => fn.apply(this, args), delay); + }; +} + +const searchInput = debounce((value) => { + console.log("Searching for:", value); +}, 300); + +searchInput("a"); +searchInput("ab"); +searchInput("abc"); +// 只在最后一次输入 300ms 后搜索一次 + +// 实用:节流 +function throttle(fn, delay) { + let lastCall = 0; + return function(...args) { + const now = Date.now(); + if (now - lastCall >= delay) { + lastCall = now; + fn.apply(this, args); + } + }; +} + +const handleScroll = throttle(() => { + console.log("Scroll handler"); +}, 100); + +window.addEventListener("scroll", handleScroll); +``` + + +## 回调中的错误处理 + + +```java !! java +// Java - Try-catch 适用于线程 +try { + future.get(); // 等待结果 +} catch (InterruptedException | ExecutionException e) { + e.printStackTrace(); +} +``` + +```javascript !! js +// JavaScript - Try-catch 无法捕获异步错误 + +try { + setTimeout(() => { + throw new Error("Async error"); // 未捕获! + }, 1000); +} catch (e) { + console.error("Caught:", e); // 永不运行 +} + +// ❌ 不好:回调错误需要显式处理 +function fetchData(callback) { + setTimeout(() => { + const data = JSON.parse(invalidJson); // 抛出! + callback(null, data); + }, 1000); +} + +// ✅ 好:回调内使用 try-catch +function fetchData(callback) { + setTimeout(() => { + try { + const data = JSON.parse(jsonString); + callback(null, data); + } catch (error) { + callback(error); + } + }, 1000); +} + +// ✅ 好:错误优先回调约定 +fs.readFile("data.txt", (error, data) => { + if (error) { + console.error("Read error:", error); + return; + } + + try { + const parsed = JSON.parse(data); + console.log("Parsed:", parsed); + } catch (parseError) { + console.error("Parse error:", parseError); + } +}); + +// 嵌套回调中的错误传播 +function processUser(userId, callback) { + getUser(userId, (error, user) => { + if (error) { + return callback(error); // 传播错误 + } + + getOrders(user.id, (error, orders) => { + if (error) { + return callback(error); // 传播错误 + } + + callback(null, orders); + }); + }); +} + +processUser(1, (error, orders) => { + if (error) { + console.error("Failed:", error); + return; + } + + console.log("Orders:", orders); +}); +``` + + +## 常见模式 + +### 模式 1:并行异步操作 + + +```java !! java +// Java - CompletableFuture 用于并行操作 +CompletableFuture future1 = CompletableFuture.supplyAsync(() -> fetchData1()); +CompletableFuture future2 = CompletableFuture.supplyAsync(() -> fetchData2()); + +CompletableFuture.allOf(future1, future2) + .thenRun(() -> { + String result1 = future1.join(); + String result2 = future2.join(); + System.out.println(result1 + result2); + }); +``` + +```javascript !! js +// JavaScript - 并行回调 + +function parallel(tasks, callback) { + let completed = 0; + const results = []; + let hasError = false; + + tasks.forEach((task, index) => { + task((error, result) => { + if (hasError) return; // 已经失败 + + if (error) { + hasError = true; + callback(error); + return; + } + + results[index] = result; + completed++; + + if (completed === tasks.length) { + callback(null, results); + } + }); + }); +} + +// 使用 +const tasks = [ + cb => setTimeout(() => cb(null, "Result 1"), 1000), + cb => setTimeout(() => cb(null, "Result 2"), 500), + cb => setTimeout(() => cb(null, "Result 3"), 1500) +]; + +parallel(tasks, (error, results) => { + if (error) { + console.error(error); + return; + } + console.log("All results:", results); + // All results: ["Result 1", "Result 2", "Result 3"] + // 总时间: ~1500ms(最长任务),而不是 3000ms +}); +``` + + +### 模式 2:顺序异步操作 + + +```javascript !! js +// JavaScript - 使用回调的顺序执行 + +function series(tasks, callback) { + let index = 0; + + function runNext() { + if (index >= tasks.length) { + return callback(null); + } + + tasks[index]((error, result) => { + if (error) { + return callback(error); + } + + index++; + runNext(); // 下一个任务 + }); + } + + runNext(); +} + +// 使用 +const tasks = [ + cb => setTimeout(() => { + console.log("Task 1"); + cb(null); + }, 1000), + cb => setTimeout(() => { + console.log("Task 2"); + cb(null); + }, 500), + cb => setTimeout(() => { + console.log("Task 3"); + cb(null); + }, 100) +]; + +series(tasks, (error) => { + if (error) { + console.error(error); + return; + } + console.log("All tasks done"); + // 总时间: 1600ms(所有任务之和) +}); +``` + + +### 模式 3:瀑布流(传递结果) + + +```javascript !! js +// JavaScript - 瀑布流(每个任务获取之前的结果) + +function waterfall(tasks, callback) { + let index = 0; + const results = []; + + function runNext(...args) { + if (index >= tasks.length) { + return callback(null, ...results); + } + + const task = tasks[index]; + + // 第一个任务不接收参数 + const taskArgs = index === 0 ? [] : [args]; + + task(...taskArgs, (error, ...result) => { + if (error) { + return callback(error); + } + + results.push(...result); + index++; + runNext(...result); + }); + } + + runNext(); +} + +// 使用 +const tasks = [ + (cb) => { + setTimeout(() => cb(null, "User 1"), 100); + }, + (user, cb) => { + setTimeout(() => cb(null, user, "Orders"), 100); + }, + (user, orders, cb) => { + setTimeout(() => cb(null, user, orders, "Items"), 100); + } +]; + +waterfall(tasks, (error, user, orders, items) => { + if (error) { + console.error(error); + return; + } + console.log("Final:", { user, orders, items }); + // Final: { user: "User 1", orders: "Orders", items: "Items" } +}); +``` + + +## 常见陷阱 + +### 陷阱 1:忘记返回 + + +```javascript !! js +// ❌ 不好:忘记返回导致继续执行 +function processData(data, callback) { + if (!data) { + callback(new Error("No data")); + // 缺少返回 - 继续执行! + } + + const result = transform(data); + callback(null, result); +} + +// ✅ 好:回调后总是返回 +function processData(data, callback) { + if (!data) { + return callback(new Error("No data")); + } + + const result = transform(data); + callback(null, result); +} +``` + + +### 陷阱 2:循环问题 + + +```javascript !! js +// ❌ 不好:循环中的闭包问题 +for (var i = 0; i < 3; i++) { + setTimeout(() => { + console.log(i); // 3, 3, 3 (不是 0, 1, 2!) + }, 100); +} + +// ✅ 解决方案 1:使用 let +for (let i = 0; i < 3; i++) { + setTimeout(() => { + console.log(i); // 0, 1, 2 + }, 100); +} + +// ✅ 解决方案 2:IIFE(用于 var) +for (var i = 0; i < 3; i++) { + (function(j) { + setTimeout(() => { + console.log(j); // 0, 1, 2 + }, 100); + })(i); +} + +// ✅ 解决方案 3:forEach(带回调) +[0, 1, 2].forEach(i => { + setTimeout(() => { + console.log(i); // 0, 1, 2 + }, 100); +}); +``` + + +### 陷阱 3:this 上下文 + + +```javascript !! js +// ❌ 不好:回调中丢失 'this' +class Timer { + constructor() { + this.seconds = 0; + } + + start() { + setTimeout(function() { + this.seconds++; // 错误: 'this' 不是 Timer + }, 1000); + } +} + +// ✅ 好:使用箭头函数 +class Timer { + constructor() { + this.seconds = 0; + } + + start() { + setTimeout(() => { + this.seconds++; // 'this' 是 Timer + }, 1000); + } +} + +// ✅ 也可以:绑定 +class Timer { + constructor() { + this.seconds = 0; + } + + start() { + setTimeout(function() { + this.seconds++; + }.bind(this), 1000); + } +} +``` + + +## 最佳实践 + + +```java !! java +// Java:清晰的异常处理 +try { + future.get(); +} catch (ExecutionException e) { + logger.error("Failed", e); +} +``` + +```javascript !! js +// JavaScript:回调最佳实践 + +// 1. 错误优先回调约定(Node.js 风格) +function operation(callback) { + // callback(error, result) + callback(null, "success"); +} + +operation((error, result) => { + if (error) { + // 处理错误 + return; + } + // 使用结果 +}); + +// 2. 总是检查错误 +fs.readFile("data.txt", (error, data) => { + if (error) { + console.error("Read failed:", error); + return; + } + console.log("Data:", data); +}); + +// 3. 回调后返回 +function process(data, callback) { + if (!data) { + return callback(new Error("No data")); + } + callback(null, processData(data)); +} + +// 4. 回调优先使用命名函数 +// 好:可调试,可重用 +function handleData(error, data) { + if (error) return console.error(error); + console.log(data); +} + +fetchData(handleData); + +// 5. 在异步操作中处理错误 +function safeOperation(callback) { + setTimeout(() => { + try { + const result = riskyOperation(); + callback(null, result); + } catch (error) { + callback(error); + } + }, 1000); +} + +// 6. 避免回调地狱 - 使用 promises/async-await +// 我们将在模块 11 中介绍! +``` + + +## 练习 + +### 练习 1:修复回调顺序 +```javascript +console.log("Start"); +setTimeout(() => console.log("Timeout"), 0); +console.log("End"); +// 输出是什么?为什么? +``` + +### 练习 2:实现防抖 +```javascript +function debounce(fn, delay) { + // 实现防抖函数 +} + +const search = debounce((query) => { + console.log("Searching:", query); +}, 300); +``` + +### 练习 3:顺序执行 +```javascript +// 按顺序执行这些任务: +// 任务 1:等待 1s,输出 "Task 1" +// 任务 2:等待 2s,输出 "Task 2" +// 任务 3:等待 1s,输出 "Task 3" +``` + +### 练习 4:并行执行 +```javascript +// 并行执行任务并获取所有结果 +const tasks = [ + (cb) => setTimeout(() => cb(null, "A"), 1000), + (cb) => setTimeout(() => cb(null, "B"), 500), + (cb) => setTimeout(() => cb(null, "C"), 1500) +]; +// 实现 parallel() 函数 +``` + +## 总结 + +### 关键要点 + +1. **事件循环:** + - 单线程执行 + - 微任务优先于宏任务 + - 非阻塞 I/O + +2. **回调:** + - 作为参数传递的函数 + - 异步操作后执行 + - 错误优先约定 + +3. **定时:** + - setTimeout:延迟执行 + - setInterval:重复执行 + - 防抖/节流用于优化 + +4. **模式:** + - 并行:多个操作 + - 串行:顺序执行 + - 瀑布流:传递结果 + +5. **陷阱:** + - 回调地狱 + - this 上下文丢失 + - 循环闭包问题 + - 未捕获的异步错误 + +6. **最佳实践:** + - 错误优先回调 + - 总是检查错误 + - 使用命名函数 + - 优先使用 promises/async-await + +### 对比表:Java vs JavaScript + +| 特性 | Java | JavaScript | +|------|------|-----------| +| **模型** | 多线程 | 单线程 + 事件循环 | +| **阻塞** | 常见 | 避免 | +| **异步** | CompletableFuture/线程 | 回调/Promises | +| **并行** | 真正并行 | 事件循环(单线程) | +| **错误处理** | Try-catch 有效 | 需要错误回调 | +| **定时** | ScheduledExecutorService | setTimeout/setInterval | + +## 接下来是什么? + +你已经学习了 JavaScript 的异步基础!接下来是**模块 11:异步编程进阶**,我们将探索: + +- Promises 和 promise 链 +- async/await 语法 +- 使用 promises 处理错误 +- Promise.all、Promise.race 等 +- 将回调转换为 promises +- 现代异步模式 + +准备掌握现代异步 JavaScript 了吗?让我们继续! diff --git a/content/docs/java2js/module-10-async-basics.zh-tw.mdx b/content/docs/java2js/module-10-async-basics.zh-tw.mdx new file mode 100644 index 0000000..0fdcb3e --- /dev/null +++ b/content/docs/java2js/module-10-async-basics.zh-tw.mdx @@ -0,0 +1,259 @@ +--- +title: "Module 10: Asynchronous Programming Basics" +description: "理解 JavaScript 非同步程式設計模型、回調和事件循環" +--- + +## Module 10: Asynchronous Programming Basics + +非同步程式設計是 JavaScript 的基礎。與 Java 的多線程模型不同,JavaScript 使用單線程事件循環。理解這個模型對於編寫有效的 JavaScript 程式碼至關重要。 + +## Learning Objectives + +完成本模組後,你將: +✅ 理解 JavaScript 事件循環 +✅ 學習同步 vs 非同步執行 +✅ 掌握回調函數 +✅ 理解回調中的錯誤處理 +✅ 學習計時和調度 +✅ 知道常見的非同步模式和陷阱 + +## Concurrency Models: Java vs JavaScript + + +```java !! java +// Java - 多線程 +public class Example { + public static void main(String[] args) { + Thread thread1 = new Thread(() -> { + System.out.println("Thread 1"); + }); + + Thread thread2 = new Thread(() -> { + System.out.println("Thread 2"); + }); + + thread1.start(); + thread2.start(); + System.out.println("Main thread"); + } +} +// 輸出:並行執行 +``` + +```javascript !! js +// JavaScript - 單線程與事件循環 +console.log("Start"); + +setTimeout(() => { + console.log("Timeout"); +}, 0); + +console.log("End"); + +// 輸出: +// Start +// End +// Timeout + +// 即使 0ms 延遲,回調也在同步代碼之後運行 +``` + + +## The Event Loop + +JavaScript 有一個單一調用棧和一個管理執行的事件循環: + + +```javascript !! js +console.log("1. Synchronous"); + +setTimeout(() => { + console.log("2. Async (macrotask)"); +}, 0); + +Promise.resolve().then(() => { + console.log("3. Async (microtask)"); +}); + +console.log("4. Synchronous"); + +// 輸出: +// 1. Synchronous +// 4. Synchronous +// 3. Async (microtask) - 在 macrotasks 之前運行 +// 2. Async (macrotask) + +// 事件循環過程: +// 1. 執行同步代碼(調用棧) +// 2. 處理所有 microtasks(Promise 回調) +// 3. 渲染 UI(瀏覽器) +// 4. 處理一個 macrotask(setTimeout、I/O) +// 5. 重複 +``` + + +## Callback Functions + +回調是作為參數傳遞以供稍後執行的函數: + + +```java !! java +// Java - 介面回調 +public interface Callback { + void onComplete(String result); +} + +public class DataFetcher { + public void fetchData(Callback callback) { + new Thread(() -> { + String result = "Data"; + callback.onComplete(result); + }).start(); + } +} +``` + +```javascript !! js +// JavaScript - 一等回調函數 + +function fetchData(callback) { + setTimeout(() => { + const data = { id: 1, name: "John" }; + callback(data); + }, 1000); +} + +// 回調函數 +fetchData(function(data) { + console.log("Received:", data); +}); + +// Error-first callback(Node.js 約定) +fetchUser(1, (error, user) => { + if (error) { + console.error("Error:", error); + return; + } + console.log("User:", user); +}); +``` + + +## Timing Functions + +JavaScript 提供了幾個計時函數: + + +```java !! java +// Java - ScheduledExecutorService +ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); + +scheduler.schedule(() -> { + System.out.println("Delayed"); +}, 1, TimeUnit.SECONDS); +``` + +```javascript !! js +// JavaScript - 計時函數 + +// setTimeout: 延遲後執行一次 +const timeoutId = setTimeout(() => { + console.log("Delayed execution"); +}, 1000); + +// 取消超時 +clearTimeout(timeoutId); + +// setInterval: 重複執行 +let count = 0; +const intervalId = setInterval(() => { + count++; + if (count >= 5) { + clearInterval(intervalId); // 5 次後停止 + } +}, 1000); + +// Debounce(實用模式) +function debounce(fn, delay) { + let timeoutId; + return function(...args) { + clearTimeout(timeoutId); + timeoutId = setTimeout(() => fn.apply(this, args), delay); + }; +} +``` + + +## Error Handling in Callbacks + + +```javascript !! js +// Try-catch 不捕獲非同步錯誤 + +try { + setTimeout(() => { + throw new Error("Async error"); // Uncaught! + }, 1000); +} catch (e) { + console.error("Caught:", e); // 永遠不運行 +} + +// ✅ GOOD: 回調內部 try-catch +function fetchData(callback) { + setTimeout(() => { + try { + const data = JSON.parse(jsonString); + callback(null, data); + } catch (error) { + callback(error); + } + }, 1000); +} +``` + + +## Best Practices + + +```javascript !! js +// 1. Error-first callback 約定 +function operation(callback) { + callback(null, "success"); +} + +operation((error, result) => { + if (error) return; + console.log(result); +}); + +// 2. 總是檢查錯誤 +fs.readFile("data.txt", (error, data) => { + if (error) { + console.error("Read failed:", error); + return; + } + console.log("Data:", data); +}); + +// 3. 回調後返回 +function process(data, callback) { + if (!data) { + return callback(new Error("No data")); + } + callback(null, processData(data)); +} +``` + + +## Summary + +### Key Takeaways + +1. **Event Loop:** 單線程執行、microtasks 優先 +2. **Callbacks:** 函數作為參數傳遞 +3. **Timing:** setTimeout、setInterval、debounce/throttle +4. **Pitfalls:** Callback hell、this 丟失、循環閉包 + +## What's Next? + +接下來是 **Module 11: Asynchronous Programming Advanced** - 學習 Promise 和 async/await! diff --git a/content/docs/java2js/module-11-async-advanced.mdx b/content/docs/java2js/module-11-async-advanced.mdx new file mode 100644 index 0000000..075a27b --- /dev/null +++ b/content/docs/java2js/module-11-async-advanced.mdx @@ -0,0 +1,734 @@ +--- +title: "Module 11: Asynchronous Programming Advanced" +description: "Master Promises, async/await, and modern async patterns" +--- + +## Module 11: Asynchronous Programming Advanced + +Now that you understand callbacks, let's explore Promises and async/await - modern JavaScript features that make asynchronous code much more manageable and readable. + +## Learning Objectives + +By the end of this module, you will: +✅ Master Promises and promise chaining +✅ Learn async/await syntax +✅ Understand error handling with promises +✅ Know Promise.all, Promise.race, Promise.allSettled +✅ Learn to convert callbacks to promises +✅ Master modern async patterns and best practices + +## Promises: Java vs JavaScript + + +```java !! java +// Java - CompletableFuture +CompletableFuture future = CompletableFuture.supplyAsync(() -> { + try { + Thread.sleep(1000); + return "Data"; + } catch (InterruptedException e) { + throw new RuntimeException(e); + } +}); + +future.thenAccept(data -> System.out.println("Received: " + data)) + .exceptionally(error -> { + System.err.println("Error: " + error); + return null; + }); +``` + +```javascript !! js +// JavaScript - Promises +const promise = new Promise((resolve, reject) => { + setTimeout(() => { + resolve("Data"); + // Or: reject(new Error("Failed")); + }, 1000); +}); + +promise + .then(data => console.log("Received:", data)) + .catch(error => console.error("Error:", error)); + +// Modern: async/await +async function fetchData() { + try { + const data = await promise; + console.log("Received:", data); + } catch (error) { + console.error("Error:", error); + } +} +``` + + +## Creating Promises + + +```javascript !! js +// Basic promise constructor +function fetchUser(id) { + return new Promise((resolve, reject) => { + setTimeout(() => { + if (id > 0) { + resolve({ id, name: "User " + id }); + } else { + reject(new Error("Invalid user ID")); + } + }, 1000); + }); +} + +// Using the promise +fetchUser(1) + .then(user => console.log("User:", user)) + .catch(error => console.error("Error:", error.message)); + +// Promise states: +// - Pending: Initial state +// - Fulfilled: Operation completed successfully +// - Rejected: Operation failed +// - Settled: Either fulfilled or rejected (final) + +// Once settled, cannot change state +const promise = new Promise((resolve, reject) => { + resolve("Success"); + reject("Fail"); // Ignored - already settled +}); + +// Promises are eager (execute immediately) +const eager = new Promise(resolve => { + console.log("Executing now!"); // Runs immediately + resolve("Done"); +}); + +console.log("After creation"); +// Output: +// Executing now! +// After creation + +// To defer execution, wrap in function +function createDeferredPromise() { + return new Promise(resolve => { + console.log("Executing when called"); + resolve("Done"); + }); +} + +// Lazy execution +const lazy = createDeferredPromise(); // Only executes when called +``` + + +## Promise Chaining + + +```java !! java +// Java - CompletableFuture chaining +CompletableFuture.supplyAsync(() -> getUser(id)) + .thenCompose(user -> getOrders(user.id)) + .thenApply(orders -> processOrders(orders)) + .thenAccept(processed -> System.out.println(processed)); +``` + +```javascript !! js +// JavaScript - Promise chaining +fetchUser(1) + .then(user => { + console.log("User:", user); + return getOrders(user.id); // Return promise + }) + .then(orders => { + console.log("Orders:", orders); + return getOrderItems(orders[0].id); + }) + .then(items => { + console.log("Items:", items); + }) + .catch(error => { + console.error("Error:", error); + }); + +// Each then() returns new promise +Promise.resolve(1) + .then(value => { + console.log(value); // 1 + return value + 1; + }) + .then(value => { + console.log(value); // 2 + return value + 1; + }) + .then(value => { + console.log(value); // 3 + }); + +// Returning non-promise values +Promise.resolve(10) + .then(value => { + return value * 2; // Auto-wrapped in promise + }) + .then(value => { + console.log(value); // 20 + }); + +// Throwing in then() triggers catch +Promise.resolve() + .then(() => { + throw new Error("Failed"); + }) + .catch(error => { + console.error(error.message); // "Failed" + }); +``` + + +## Async/Await + +Async/await is syntactic sugar over promises: + + +```java !! java +// Java - Synchronous-looking async code +public String fetchData() { + try { + // Looks synchronous but blocks thread + String result = future.get(); + return result; + } catch (Exception e) { + throw new RuntimeException(e); + } +} +``` + +```javascript !! js +// JavaScript - Async/await (non-blocking) +async function fetchData() { + try { + const response = await fetch("/api/data"); + const data = await response.json(); + return data; + } catch (error) { + console.error("Error:", error); + throw error; + } +} + +// Usage +fetchData().then(data => console.log(data)); + +// async always returns Promise +async function fn() { + return "Value"; // Automatically wrapped in Promise +} + +fn().then(value => console.log(value)); // "Value" + +// await pauses function execution (non-blocking) +async function example() { + console.log("Start"); + + const result1 = await Promise.resolve(1); + console.log("After first await:", result1); + + const result2 = await Promise.resolve(2); + console.log("After second await:", result2); + + return "Done"; +} + +example().then(console.log); +// Output: +// Start +// After first await: 1 +// After second await: 2 +// Done + +// Parallel execution with Promise.all +async function fetchMultiple() { + const [user, posts, comments] = await Promise.all([ + fetchUser(1), + fetchPosts(1), + fetchComments(1) + ]); + + return { user, posts, comments }; +} + +// Sequential vs parallel +// Sequential (slow - ~3 seconds) +async function sequential() { + const a = await delay(1000, "A"); + const b = await delay(1000, "B"); + const c = await delay(1000, "C"); + return [a, b, c]; +} + +// Parallel (fast - ~1 second) +async function parallel() { + const [a, b, c] = await Promise.all([ + delay(1000, "A"), + delay(1000, "B"), + delay(1000, "C") + ]); + return [a, b, c]; +} + +function delay(ms, value) { + return new Promise(resolve => setTimeout(() => resolve(value), ms)); +} +``` + + +## Error Handling + + +```java !! java +// Java - Try-catch with CompletableFuture +CompletableFuture.supplyAsync(() -> { + if (error) { + throw new RuntimeException("Failed"); + } + return "Success"; +}) +.exceptionally(error -> { + System.err.println("Error: " + error.getMessage()); + return null; // Fallback value +}); +``` + +```javascript !! js +// JavaScript - Promise error handling + +// .catch() handles any rejection +Promise.reject(new Error("Failed")) + .catch(error => { + console.error("Caught:", error.message); + }); + +// Try-catch in async functions +async function handleError() { + try { + const data = await riskyOperation(); + return data; + } catch (error) { + console.error("Error:", error); + return null; // Fallback + } +} + +// .then(null, error) is same as .catch(error) +Promise.reject("error") + .then(null, error => { + console.error("Caught:", error); + }); + +// .finally() always runs +Promise.resolve("value") + .then(value => { + console.log("Value:", value); + throw new Error("Failed"); + }) + .catch(error => { + console.error("Error:", error.message); + }) + .finally(() => { + console.log("Cleanup"); // Always runs + }); + +// Practical: Resource cleanup +async function withResource(callback) { + const resource = acquireResource(); + try { + return await callback(resource); + } finally { + resource.release(); // Always cleanup + } +} + +// Multiple error handlers +Promise.resolve() + .then(() => { + throw new Error("Error 1"); + }) + .catch(error => { + console.error("First catch:", error.message); + return "Recovered"; // Recover from error + }) + .then(value => { + console.log("Continue:", value); // Runs because error was handled + }); +``` + + +## Promise Combinators + + +```java !! java +// Java - CompletableFuture combinators +CompletableFuture f1 = CompletableFuture.supplyAsync(() -> "A"); +CompletableFuture f2 = CompletableFuture.supplyAsync(() -> "B"); + +// All must complete +CompletableFuture all = CompletableFuture.allOf(f1, f2); + +// Any completes +CompletableFuture any = CompletableFuture.anyOf(f1, f2); +``` + +```javascript !! js +// JavaScript - Promise combinators + +// Promise.all: All must fulfill (or any rejects) +const p1 = Promise.resolve(1); +const p2 = Promise.resolve(2); +const p3 = Promise.resolve(3); + +Promise.all([p1, p2, p3]) + .then(values => { + console.log(values); // [1, 2, 3] + }) + .catch(error => { + console.error("One failed:", error); + }); + +// Practical: Fetch multiple resources +async function fetchDashboard() { + const [user, posts, stats] = await Promise.all([ + fetchUser(1), + fetchPosts(1), + fetchStats() + ]); + + return { user, posts, stats }; +} + +// Promise.race: First to settle wins +const slow = new Promise(resolve => setTimeout(() => resolve("Slow"), 1000)); +const fast = new Promise(resolve => setTimeout(() => resolve("Fast"), 100)); + +Promise.race([slow, fast]) + .then(value => { + console.log(value); // "Fast" + }); + +// Practical: Timeout with race +function fetchWithTimeout(url, timeout = 5000) { + return Promise.race([ + fetch(url), + new Promise((_, reject) => + setTimeout(() => reject(new Error("Timeout")), timeout) + ) + ]); +} + +// Promise.allSettled: All settle, don't fail on rejection +const promise1 = Promise.resolve(1); +const promise2 = Promise.reject("Error"); +const promise3 = Promise.resolve(3); + +Promise.allSettled([promise1, promise2, promise3]) + .then(results => { + results.forEach(result => { + if (result.status === "fulfilled") { + console.log("Value:", result.value); + } else { + console.error("Reason:", result.reason); + } + }); + }); + +// Promise.any: First fulfillment wins (all rejections lose) +try { + const first = await Promise.any([ + Promise.reject("Error 1"), + Promise.reject("Error 2"), + Promise.resolve("Success") + ]); + console.log(first); // "Success" +} catch (error) { + console.error("All rejected:", error); +} +``` + + +## Converting Callbacks to Promises + + +```javascript !! js +// Wrap callback-based function in Promise +function promisify(fn) { + return function(...args) { + return new Promise((resolve, reject) => { + fn(...args, (error, result) => { + if (error) { + reject(error); + } else { + resolve(result); + } + }); + }); + }; +} + +// Node.js util.promisify (built-in) +const fs = require("fs").promises; + +// Before (callback) +fs.readFile("data.txt", (error, data) => { + if (error) throw error; + console.log(data); +}); + +// After (promise) +fs.readFile("data.txt") + .then(data => console.log(data)) + .catch(error => console.error(error)); + +// Or async/await +async function readFile() { + try { + const data = await fs.readFile("data.txt"); + console.log(data); + } catch (error) { + console.error(error); + } +} + +// Manual promise wrapper +function readFile(path) { + return new Promise((resolve, reject) => { + fs.readFile(path, (error, data) => { + if (error) { + reject(error); + } else { + resolve(data); + } + }); + }); +} + +// Custom promisification +function fetchUserCallback(id, callback) { + setTimeout(() => { + if (id > 0) { + callback(null, { id, name: "User " + id }); + } else { + callback(new Error("Invalid ID")); + } + }, 1000); +} + +// Wrap it +const fetchUser = promisify(fetchUserCallback); + +// Now use as promise +fetchUser(1) + .then(user => console.log("User:", user)) + .catch(error => console.error("Error:", error)); +``` + + +## Best Practices + + +```java !! java +// Java: Proper exception handling +public Result process() { + try { + Data data = fetchData(); + return transform(data); + } catch (Exception e) { + logger.error("Failed", e); + return Result.failure(e); + } +} +``` + +```javascript !! js +// JavaScript: Async best practices + +// 1. Always handle promise rejections +Promise.resolve() + .then(() => { + throw new Error("Oops"); + }) + .catch(error => { + console.error(error); // Always catch + }); + +// 2. Use async/await for clarity +// Good +async function process() { + const user = await getUser(id); + const orders = await getOrders(user.id); + return orders; +} + +// Avoid excessive .then() chaining +// Bad +Promise.resolve() + .then(() => Promise.resolve()) + .then(() => Promise.resolve()) + .then(() => Promise.resolve()); + +// 3. Parallelize independent operations +// Bad (sequential) +async function fetchAll() { + const user = await fetchUser(); + const posts = await fetchPosts(); + const comments = await fetchComments(); + return { user, posts, comments }; +} + +// Good (parallel) +async function fetchAll() { + const [user, posts, comments] = await Promise.all([ + fetchUser(), + fetchPosts(), + fetchComments() + ]); + return { user, posts, comments }; +} + +// 4. Handle errors at appropriate level +async function fetchUser(id) { + const response = await fetch(`/api/users/${id}`); + if (!response.ok) { + throw new Error(`HTTP ${response.status}`); + } + return response.json(); +} + +async function init() { + try { + const user = await fetchUser(1); + console.log(user); + } catch (error) { + console.error("Failed to initialize:", error); + } +} + +// 5. Clean up with finally +async function withLock(fn) { + const lock = await acquireLock(); + try { + return await fn(lock); + } finally { + await lock.release(); // Always runs + } +} + +// 6. Don't mix callbacks and promises +// Bad +function bad() { + fetch("/api/data") + .then(response => { + response.json(data => { + console.log(data); + }); + }); +} + +// Good +function good() { + return fetch("/api/data") + .then(response => response.json()) + .then(data => console.log(data)); +} + +// Or async/await +async function good2() { + const response = await fetch("/api/data"); + const data = await response.json(); + console.log(data); +} +``` + + +## Exercises + +### Exercise 1: Promise Chaining +```javascript +// Chain these operations: +// 1. Fetch user by ID +// 2. Fetch user's orders +// 3. Fetch first order's items +// Use .then() chaining +``` + +### Exercise 2: Async/Await +```javascript +// Rewrite Exercise 1 using async/await +async function getOrderItems(userId) { + // Implementation +} +``` + +### Exercise 3: Parallel Execution +```javascript +// Fetch user, posts, and comments in parallel +// Use Promise.all +``` + +### Exercise 4: Error Handling +```javascript +// Add proper error handling to async function +async function process(id) { + const user = await fetchUser(id); + const orders = await getOrders(user.id); + return orders; +} +``` + +## Summary + +### Key Takeaways + +1. **Promises:** + - Represent future values + - Three states: pending, fulfilled, rejected + - Chainable with .then() + - Error handling with .catch() + +2. **Async/Await:** + - Syntactic sugar over promises + - Makes async code look synchronous + - Non-blocking + - Use try/catch for errors + +3. **Combinators:** + - Promise.all: All must succeed + - Promise.race: First to settle + - Promise.allSettled: Wait for all + - Promise.any: First success + +4. **Best Practices:** + - Always handle errors + - Parallelize when possible + - Prefer async/await + - Don't mix callbacks and promises + +### Comparison Table: Java vs JavaScript + +| Feature | Java | JavaScript | +|---------|------|------------| +| **Async** | CompletableFuture, Future | Promise, async/await | +| **Chaining** | .thenCompose(), .thenApply() | .then(), await | +| **Error** | .exceptionally() | .catch(), try/catch | +| **Parallel** | CompletableFuture.allOf() | Promise.all() | +| **Race** | CompletableFuture.anyOf() | Promise.race() | + +## What's Next? + +You've mastered modern async JavaScript! Next up is **Module 12: DOM Manipulation**, where we'll explore: + +- DOM structure and traversal +- Querying elements +- Modifying the DOM +- Creating and removing elements +- Event handling basics +- DOM performance + +Ready to learn how to manipulate web pages? Let's continue! diff --git a/content/docs/java2js/module-11-async-advanced.zh-cn.mdx b/content/docs/java2js/module-11-async-advanced.zh-cn.mdx new file mode 100644 index 0000000..c63fa1c --- /dev/null +++ b/content/docs/java2js/module-11-async-advanced.zh-cn.mdx @@ -0,0 +1,734 @@ +--- +title: "模块 11:异步编程进阶" +description: "掌握 Promises、async/await 和现代异步模式" +--- + +## 模块 11:异步编程进阶 + +既然你已经理解了回调,让我们探索 Promises 和 async/await - 让异步代码更易管理和可读的现代 JavaScript 特性。 + +## 学习目标 + +完成本模块后,你将: +✅ 掌握 Promises 和 promise 链 +✅ 学习 async/await 语法 +✅ 理解使用 promises 处理错误 +✅ 了解 Promise.all、Promise.race、Promise.allSettled +✅ 学习将回调转换为 promises +✅ 掌握现代异步模式和最佳实践 + +## Promises: Java vs JavaScript + + +```java !! java +// Java - CompletableFuture +CompletableFuture future = CompletableFuture.supplyAsync(() -> { + try { + Thread.sleep(1000); + return "Data"; + } catch (InterruptedException e) { + throw new RuntimeException(e); + } +}); + +future.thenAccept(data -> System.out.println("Received: " + data)) + .exceptionally(error -> { + System.err.println("Error: " + error); + return null; + }); +``` + +```javascript !! js +// JavaScript - Promises +const promise = new Promise((resolve, reject) => { + setTimeout(() => { + resolve("Data"); + // 或: reject(new Error("Failed")); + }, 1000); +}); + +promise + .then(data => console.log("Received:", data)) + .catch(error => console.error("Error:", error)); + +// 现代:async/await +async function fetchData() { + try { + const data = await promise; + console.log("Received:", data); + } catch (error) { + console.error("Error:", error); + } +} +``` + + +## 创建 Promises + + +```javascript !! js +// 基本 promise 构造函数 +function fetchUser(id) { + return new Promise((resolve, reject) => { + setTimeout(() => { + if (id > 0) { + resolve({ id, name: "User " + id }); + } else { + reject(new Error("Invalid user ID")); + } + }, 1000); + }); +} + +// 使用 promise +fetchUser(1) + .then(user => console.log("User:", user)) + .catch(error => console.error("Error:", error.message)); + +// Promise 状态: +// - Pending:初始状态 +// - Fulfilled:操作成功完成 +// - Rejected:操作失败 +// - Settled:已兑现或已拒绝(最终) + +// 一旦 settled,无法更改状态 +const promise = new Promise((resolve, reject) => { + resolve("Success"); + reject("Fail"); // 被忽略 - 已 settled +}); + +// Promises 是急切的(立即执行) +const eager = new Promise(resolve => { + console.log("Executing now!"); // 立即运行 + resolve("Done"); +}); + +console.log("After creation"); +// 输出: +// Executing now! +// After creation + +// 延迟执行,包装在函数中 +function createDeferredPromise() { + return new Promise(resolve => { + console.log("Executing when called"); + resolve("Done"); + }); +} + +// 延迟执行 +const lazy = createDeferredPromise(); // 只在调用时执行 +``` + + +## Promise 链 + + +```java !! java +// Java - CompletableFuture 链 +CompletableFuture.supplyAsync(() -> getUser(id)) + .thenCompose(user -> getOrders(user.id)) + .thenApply(orders -> processOrders(orders)) + .thenAccept(processed -> System.out.println(processed)); +``` + +```javascript !! js +// JavaScript - Promise 链 +fetchUser(1) + .then(user => { + console.log("User:", user); + return getOrders(user.id); // 返回 promise + }) + .then(orders => { + console.log("Orders:", orders); + return getOrderItems(orders[0].id); + }) + .then(items => { + console.log("Items:", items); + }) + .catch(error => { + console.error("Error:", error); + }); + +// 每个 then() 返回新 promise +Promise.resolve(1) + .then(value => { + console.log(value); // 1 + return value + 1; + }) + .then(value => { + console.log(value); // 2 + return value + 1; + }) + .then(value => { + console.log(value); // 3 + }); + +// 返回非 promise 值 +Promise.resolve(10) + .then(value => { + return value * 2; // 自动包装在 promise 中 + }) + .then(value => { + console.log(value); // 20 + }); + +// 在 then() 中抛出触发 catch +Promise.resolve() + .then(() => { + throw new Error("Failed"); + }) + .catch(error => { + console.error(error.message); // "Failed" + }); +``` + + +## Async/Await + +Async/await 是 promises 的语法糖: + + +```java !! java +// Java - 看起来同步的异步代码 +public String fetchData() { + try { + // 看起来同步但阻塞线程 + String result = future.get(); + return result; + } catch (Exception e) { + throw new RuntimeException(e); + } +} +``` + +```javascript !! js +// JavaScript - Async/await(非阻塞) +async function fetchData() { + try { + const response = await fetch("/api/data"); + const data = await response.json(); + return data; + } catch (error) { + console.error("Error:", error); + throw error; + } +} + +// 使用 +fetchData().then(data => console.log(data)); + +// async 总是返回 Promise +async function fn() { + return "Value"; // 自动包装在 Promise 中 +} + +fn().then(value => console.log(value)); // "Value" + +// await 暂停函数执行(非阻塞) +async function example() { + console.log("Start"); + + const result1 = await Promise.resolve(1); + console.log("After first await:", result1); + + const result2 = await Promise.resolve(2); + console.log("After second await:", result2); + + return "Done"; +} + +example().then(console.log); +// 输出: +// Start +// After first await: 1 +// After second await: 2 +// Done + +// 使用 Promise.all 并行执行 +async function fetchMultiple() { + const [user, posts, comments] = await Promise.all([ + fetchUser(1), + fetchPosts(1), + fetchComments(1) + ]); + + return { user, posts, comments }; +} + +// 顺序 vs 并行 +// 顺序(慢 - ~3 秒) +async function sequential() { + const a = await delay(1000, "A"); + const b = await delay(1000, "B"); + const c = await delay(1000, "C"); + return [a, b, c]; +} + +// 并行(快 - ~1 秒) +async function parallel() { + const [a, b, c] = await Promise.all([ + delay(1000, "A"), + delay(1000, "B"), + delay(1000, "C") + ]); + return [a, b, c]; +} + +function delay(ms, value) { + return new Promise(resolve => setTimeout(() => resolve(value), ms)); +} +``` + + +## 错误处理 + + +```java !! java +// Java - Try-catch 与 CompletableFuture +CompletableFuture.supplyAsync(() -> { + if (error) { + throw new RuntimeException("Failed"); + } + return "Success"; +}) +.exceptionally(error -> { + System.err.println("Error: " + error.getMessage()); + return null; // 回退值 +}); +``` + +```javascript !! js +// JavaScript - Promise 错误处理 + +// .catch() 处理任何拒绝 +Promise.reject(new Error("Failed")) + .catch(error => { + console.error("Caught:", error.message); + }); + +// 异步函数中的 try-catch +async function handleError() { + try { + const data = await riskyOperation(); + return data; + } catch (error) { + console.error("Error:", error); + return null; // 回退 + } +} + +// .then(null, error) 与 .catch(error) 相同 +Promise.reject("error") + .then(null, error => { + console.error("Caught:", error); + }); + +// .finally() 总是运行 +Promise.resolve("value") + .then(value => { + console.log("Value:", value); + throw new Error("Failed"); + }) + .catch(error => { + console.error("Error:", error.message); + }) + .finally(() => { + console.log("Cleanup"); // 总是运行 + }); + +// 实用:资源清理 +async function withResource(callback) { + const resource = acquireResource(); + try { + return await callback(resource); + } finally { + resource.release(); // 总是清理 + } +} + +// 多个错误处理器 +Promise.resolve() + .then(() => { + throw new Error("Error 1"); + }) + .catch(error => { + console.error("First catch:", error.message); + return "Recovered"; // 从错误恢复 + }) + .then(value => { + console.log("Continue:", value); // 运行因为错误已处理 + }); +``` + + +## Promise 组合器 + + +```java !! java +// Java - CompletableFuture 组合器 +CompletableFuture f1 = CompletableFuture.supplyAsync(() -> "A"); +CompletableFuture f2 = CompletableFuture.supplyAsync(() -> "B"); + +// 所有必须完成 +CompletableFuture all = CompletableFuture.allOf(f1, f2); + +// 任何一个完成 +CompletableFuture any = CompletableFuture.anyOf(f1, f2); +``` + +```javascript !! js +// JavaScript - Promise 组合器 + +// Promise.all:所有必须兑现(或任何一个拒绝) +const p1 = Promise.resolve(1); +const p2 = Promise.resolve(2); +const p3 = Promise.resolve(3); + +Promise.all([p1, p2, p3]) + .then(values => { + console.log(values); // [1, 2, 3] + }) + .catch(error => { + console.error("One failed:", error); + }); + +// 实用:获取多个资源 +async function fetchDashboard() { + const [user, posts, stats] = await Promise.all([ + fetchUser(1), + fetchPosts(1), + fetchStats() + ]); + + return { user, posts, stats }; +} + +// Promise.race:第一个 settled 的获胜 +const slow = new Promise(resolve => setTimeout(() => resolve("Slow"), 1000)); +const fast = new Promise(resolve => setTimeout(() => resolve("Fast"), 100)); + +Promise.race([slow, fast]) + .then(value => { + console.log(value); // "Fast" + }); + +// 实用:使用 race 的超时 +function fetchWithTimeout(url, timeout = 5000) { + return Promise.race([ + fetch(url), + new Promise((_, reject) => + setTimeout(() => reject(new Error("Timeout")), timeout) + ) + ]); +} + +// Promise.allSettled:所有 settle,拒绝不会失败 +const promise1 = Promise.resolve(1); +const promise2 = Promise.reject("Error"); +const promise3 = Promise.resolve(3); + +Promise.allSettled([promise1, promise2, promise3]) + .then(results => { + results.forEach(result => { + if (result.status === "fulfilled") { + console.log("Value:", result.value); + } else { + console.error("Reason:", result.reason); + } + }); + }); + +// Promise.any:第一个兑现获胜(所有拒绝都失败) +try { + const first = await Promise.any([ + Promise.reject("Error 1"), + Promise.reject("Error 2"), + Promise.resolve("Success") + ]); + console.log(first); // "Success" +} catch (error) { + console.error("All rejected:", error); +} +``` + + +## 将回调转换为 Promises + + +```javascript !! js +// 在 Promise 中包装基于回调的函数 +function promisify(fn) { + return function(...args) { + return new Promise((resolve, reject) => { + fn(...args, (error, result) => { + if (error) { + reject(error); + } else { + resolve(result); + } + }); + }); + }; +} + +// Node.js util.promisify(内置) +const fs = require("fs").promises; + +// 之前(回调) +fs.readFile("data.txt", (error, data) => { + if (error) throw error; + console.log(data); +}); + +// 之后(promise) +fs.readFile("data.txt") + .then(data => console.log(data)) + .catch(error => console.error(error)); + +// 或 async/await +async function readFile() { + try { + const data = await fs.readFile("data.txt"); + console.log(data); + } catch (error) { + console.error(error); + } +} + +// 手动 promise 包装器 +function readFile(path) { + return new Promise((resolve, reject) => { + fs.readFile(path, (error, data) => { + if (error) { + reject(error); + } else { + resolve(data); + } + }); + }); +} + +// 自定义 promisification +function fetchUserCallback(id, callback) { + setTimeout(() => { + if (id > 0) { + callback(null, { id, name: "User " + id }); + } else { + callback(new Error("Invalid ID")); + } + }, 1000); +} + +// 包装它 +const fetchUser = promisify(fetchUserCallback); + +// 现在作为 promise 使用 +fetchUser(1) + .then(user => console.log("User:", user)) + .catch(error => console.error("Error:", error)); +``` + + +## 最佳实践 + + +```java !! java +// Java:适当的异常处理 +public Result process() { + try { + Data data = fetchData(); + return transform(data); + } catch (Exception e) { + logger.error("Failed", e); + return Result.failure(e); + } +} +``` + +```javascript !! js +// JavaScript:异步最佳实践 + +// 1. 总是处理 promise 拒绝 +Promise.resolve() + .then(() => { + throw new Error("Oops"); + }) + .catch(error => { + console.error(error); // 总是 catch + }); + +// 2. 使用 async/await 提高清晰度 +// 好 +async function process() { + const user = await getUser(id); + const orders = await getOrders(user.id); + return orders; +} + +// 避免过度的 .then() 链 +// 不好 +Promise.resolve() + .then(() => Promise.resolve()) + .then(() => Promise.resolve()) + .then(() => Promise.resolve()); + +// 3. 并行化独立操作 +// 不好(顺序) +async function fetchAll() { + const user = await fetchUser(); + const posts = await fetchPosts(); + const comments = await fetchComments(); + return { user, posts, comments }; +} + +// 好(并行) +async function fetchAll() { + const [user, posts, comments] = await Promise.all([ + fetchUser(), + fetchPosts(), + fetchComments() + ]); + return { user, posts, comments }; +} + +// 4. 在适当级别处理错误 +async function fetchUser(id) { + const response = await fetch(`/api/users/${id}`); + if (!response.ok) { + throw new Error(`HTTP ${response.status}`); + } + return response.json(); +} + +async function init() { + try { + const user = await fetchUser(1); + console.log(user); + } catch (error) { + console.error("Failed to initialize:", error); + } +} + +// 5. 使用 finally 清理 +async function withLock(fn) { + const lock = await acquireLock(); + try { + return await fn(lock); + } finally { + await lock.release(); // 总是运行 + } +} + +// 6. 不要混用回调和 promises +// 不好 +function bad() { + fetch("/api/data") + .then(response => { + response.json(data => { + console.log(data); + }); + }); +} + +// 好 +function good() { + return fetch("/api/data") + .then(response => response.json()) + .then(data => console.log(data)); +} + +// 或 async/await +async function good2() { + const response = await fetch("/api/data"); + const data = await response.json(); + console.log(data); +} +``` + + +## 练习 + +### 练习 1:Promise 链 +```javascript +// 链接这些操作: +// 1. 通过 ID 获取用户 +// 2. 获取用户的订单 +// 3. 获取第一个订单的项目 +// 使用 .then() 链 +``` + +### 练习 2:Async/Await +```javascript +// 使用 async/await 重写练习 1 +async function getOrderItems(userId) { + // 实现 +} +``` + +### 练习 3:并行执行 +```javascript +// 并行获取用户、帖子和评论 +// 使用 Promise.all +``` + +### 练习 4:错误处理 +```javascript +// 为异步函数添加适当的错误处理 +async function process(id) { + const user = await fetchUser(id); + const orders = await getOrders(user.id); + return orders; +} +``` + +## 总结 + +### 关键要点 + +1. **Promises:** + - 代表未来值 + - 三种状态:pending、fulfilled、rejected + - 可用 .then() 链接 + - 使用 .catch() 处理错误 + +2. **Async/Await:** + - Promises 的语法糖 + - 使异步代码看起来同步 + - 非阻塞 + - 使用 try/catch 处理错误 + +3. **组合器:** + - Promise.all:所有必须成功 + - Promise.race:第一个 settle + - Promise.allSettled:等待所有 + - Promise.any:第一个成功 + +4. **最佳实践:** + - 总是处理错误 + - 尽可能并行化 + - 优先使用 async/await + - 不要混用回调和 promises + +### 对比表:Java vs JavaScript + +| 特性 | Java | JavaScript | +|------|------|-----------| +| **异步** | CompletableFuture, Future | Promise, async/await | +| **链式** | .thenCompose(), .thenApply() | .then(), await | +| **错误** | .exceptionally() | .catch(), try/catch | +| **并行** | CompletableFuture.allOf() | Promise.all() | +| **竞态** | CompletableFuture.anyOf() | Promise.race() | + +## 接下来是什么? + +你已经掌握了现代异步 JavaScript!接下来是**模块 12:DOM 操作**,我们将探索: + +- DOM 结构和遍历 +- 查询元素 +- 修改 DOM +- 创建和删除元素 +- 事件处理基础 +- DOM 性能 + +准备学习如何操作网页了吗?让我们继续! diff --git a/content/docs/java2js/module-11-async-advanced.zh-tw.mdx b/content/docs/java2js/module-11-async-advanced.zh-tw.mdx new file mode 100644 index 0000000..a0a93f4 --- /dev/null +++ b/content/docs/java2js/module-11-async-advanced.zh-tw.mdx @@ -0,0 +1,259 @@ +--- +title: "Module 11: Asynchronous Programming Advanced" +description: "掌握 Promise、async/await 和現代非同步模式" +--- + +## Module 11: Asynchronous Programming Advanced + +現在您了解了回調,讓我們探索 Promise 和 async/await - 使非同步程式碼更易管理和閱讀的現代 JavaScript 功能。 + +## Learning Objectives + +完成本模組後,你將: +✅ 掌握 Promise 和 promise chaining +✅ 學習 async/await 語法 +✅ 理解 Promise 的錯誤處理 +✅ 知道 Promise.all、Promise.race、Promise.allSettled +✅ 學習將回調轉換為 Promise +✅ 掌握現代非同步模式和最佳實踐 + +## Promises: Java vs JavaScript + + +```java !! java +// Java - CompletableFuture +CompletableFuture future = CompletableFuture.supplyAsync(() -> { + try { + Thread.sleep(1000); + return "Data"; + } catch (InterruptedException e) { + throw new RuntimeException(e); + } +}); + +future.thenAccept(data -> System.out.println("Received: " + data)) + .exceptionally(error -> { + System.err.println("Error: " + error); + return null; + }); +``` + +```javascript !! js +// JavaScript - Promises +const promise = new Promise((resolve, reject) => { + setTimeout(() => { + resolve("Data"); + // 或:reject(new Error("Failed")); + }, 1000); +}); + +promise + .then(data => console.log("Received:", data)) + .catch(error => console.error("Error:", error)); + +// 現代:async/await +async function fetchData() { + try { + const data = await promise; + console.log("Received:", data); + } catch (error) { + console.error("Error:", error); + } +} +``` + + +## Creating Promises + + +```javascript !! js +function fetchUser(id) { + return new Promise((resolve, reject) => { + setTimeout(() => { + if (id > 0) { + resolve({ id, name: "User " + id }); + } else { + reject(new Error("Invalid user ID")); + } + }, 1000); + }); +} + +// Promise 狀態: +// - Pending: 初始狀態 +// - Fulfilled: 操作成功完成 +// - Rejected: 操作失敗 +// - Settled: 已完成或已拒絕(最終) + +// 一旦 settled,不能改變狀態 +``` + + +## Promise Chaining + + +```java !! java +// Java - CompletableFuture chaining +CompletableFuture.supplyAsync(() -> getUser(id)) + .thenCompose(user -> getOrders(user.id)) + .thenApply(orders -> processOrders(orders)) + .thenAccept(processed -> System.out.println(processed)); +``` + +```javascript !! js +// JavaScript - Promise chaining +fetchUser(1) + .then(user => { + console.log("User:", user); + return getOrders(user.id); // 返回 promise + }) + .then(orders => { + console.log("Orders:", orders); + return getOrderItems(orders[0].id); + }) + .then(items => { + console.log("Items:", items); + }) + .catch(error => { + console.error("Error:", error); + }); +``` + + +## Async/Await + +Async/await 是 Promise 的語法糖: + + +```javascript !! js +async function fetchData() { + try { + const response = await fetch("/api/data"); + const data = await response.json(); + return data; + } catch (error) { + console.error("Error:", error); + throw error; + } +} + +// async 總是返回 Promise +async function fn() { + return "Value"; // 自動包裝在 Promise 中 +} + +// 並行執行 +async function fetchMultiple() { + const [user, posts, comments] = await Promise.all([ + fetchUser(1), + fetchPosts(1), + fetchComments(1) + ]); + + return { user, posts, comments }; +} +``` + + +## Promise Combinators + + +```java !! java +// Java - CompletableFuture 組合器 +CompletableFuture f1 = CompletableFuture.supplyAsync(() -> "A"); +CompletableFuture f2 = CompletableFuture.supplyAsync(() -> "B"); + +CompletableFuture.allOf(f1, f2); +CompletableFuture.anyOf(f1, f2); +``` + +```javascript !! js +// JavaScript - Promise 組合器 + +// Promise.all: 全部必須成功 +Promise.all([p1, p2, p3]) + .then(values => console.log(values)); + +// Promise.race: 第一個完成的獲勝 +Promise.race([slow, fast]) + .then(value => console.log(value)); + +// Promise.allSettled: 等待全部,不會因拒絕而失敗 +Promise.allSettled([promise1, promise2, promise3]) + .then(results => { + results.forEach(result => { + if (result.status === "fulfilled") { + console.log("Value:", result.value); + } else { + console.error("Reason:", result.reason); + } + }); + }); + +// Promise.any: 第一個成功獲勝 +try { + const first = await Promise.any([ + Promise.reject("Error 1"), + Promise.resolve("Success") + ]); + console.log(first); // "Success" +} catch (error) { + console.error("All rejected:", error); +} +``` + + +## Best Practices + + +```javascript !! js +// 1. 總是處理 promise 拒絕 +Promise.resolve() + .then(() => { + throw new Error("Oops"); + }) + .catch(error => { + console.error(error); // 總是捕獲 + }); + +// 2. 使用 async/await 提高清晰度 +async function process() { + const user = await getUser(id); + const orders = await getOrders(user.id); + return orders; +} + +// 3. 並行化獨立操作 +async function fetchAll() { + const [user, posts, comments] = await Promise.all([ + fetchUser(), + fetchPosts(), + fetchComments() + ]); + return { user, posts, comments }; +} + +// 4. 使用 finally 進行清理 +async function withResource(callback) { + const resource = acquireResource(); + try { + return await callback(resource); + } finally { + resource.release(); // 總是運行 + } +} +``` + + +## Summary + +### Key Takeaways + +1. **Promises:** 代表未來值 +2. **Async/Await:** 使非同步代碼看起來同步 +3. **Combinators:** Promise.all、Promise.race、Promise.allSettled、Promise.any +4. **Best Practices:** 總是處理錯誤、並行化、使用 async/await + +## What's Next? + +接下來是 **Module 12: DOM Manipulation** - 學習如何操作網頁! diff --git a/content/docs/java2js/module-12-dom-manipulation.mdx b/content/docs/java2js/module-12-dom-manipulation.mdx new file mode 100644 index 0000000..4c8b9c2 --- /dev/null +++ b/content/docs/java2js/module-12-dom-manipulation.mdx @@ -0,0 +1,303 @@ +--- +title: "Module 12: DOM Manipulation" +description: "Master DOM traversal, manipulation, and best practices" +--- + +## Module 12: DOM Manipulation + +The Document Object Model (DOM) is JavaScript's interface to HTML documents. This module teaches you how to dynamically interact with web pages. + +## Learning Objectives + +By the end of this module, you will: +✅ Understand DOM structure and traversal +✅ Master element selection methods +✅ Learn to modify DOM elements +✅ Understand creating and removing elements +✅ Know performance best practices +✅ Master DOM manipulation patterns + +## DOM vs Java GUI + + +```java !! java +// Java - Swing +JFrame frame = new JFrame("My App"); +JButton button = new JButton("Click me"); +frame.add(button); +frame.setVisible(true); + +// JavaFX +Button btn = new Button("Click me"); +Scene scene = new Scene(new StackPane(btn), 300, 250); +stage.setScene(scene); +stage.show(); +``` + +```javascript !! js +// JavaScript - DOM +const button = document.createElement("button"); +button.textContent = "Click me"; +document.body.appendChild(button); +``` + + +## Selecting Elements + + +```javascript !! js +// Modern methods (preferred) +const byId = document.getElementById("myId"); +const byClass = document.getElementsByClassName("myClass"); +const byTag = document.getElementsByTagName("div"); + +// Query selectors (most flexible) +const element = document.querySelector("#myId"); +const allElements = document.querySelectorAll(".myClass"); + +// CSS selectors +const element1 = document.querySelector("#container .item"); +const element2 = document.querySelector("button[data-action='submit']"); +const element3 = document.querySelector("ul > li:first-child"); + +// Context selection +const container = document.querySelector("#container"); +const items = container.querySelectorAll(".item"); // Only within container + +// Performance considerations +// getElementById is fastest for ID lookup +// querySelector is slower but more flexible + +// Best practice: Cache selectors +const button = document.getElementById("submit"); +button.addEventListener("click", handleClick); +``` + + +## Traversing the DOM + + +```javascript !! js +const parent = document.getElementById("parent"); + +// Children +const children = parent.children; // HTMLCollection (live) +const childNodes = parent.childNodes; // NodeList (live) +const firstChild = parent.firstElementChild; +const lastChild = parent.lastElementChild; + +// Siblings +const nextSibling = element.nextElementSibling; +const prevSibling = element.previousElementSibling; + +// Parent +const parent = element.parentElement; + +// Walking the DOM tree +function walkDOM(node, callback) { + callback(node); + node = node.firstChild; + while (node) { + walkDOM(node, callback); + node = node.nextSibling; + } +} + +// Finding elements +const closestParent = element.closest(".container"); +const matches = element.matches(".active"); +``` + + +## Modifying Elements + + +```javascript !! js +const element = document.querySelector("#myElement"); + +// Content +element.textContent = "New text"; // Plain text +element.innerHTML = "New HTML"; // HTML (security risk!) + +// Attributes +element.setAttribute("class", "active"); +element.getAttribute("class"); +element.hasAttribute("class"); +element.removeAttribute("class"); + +// Properties (preferred) +element.id = "myId"; +element.className = "my-class"; +element.href = "https://example.com"; +element.disabled = true; + +// Classes +element.classList.add("active"); +element.classList.remove("inactive"); +element.classList.toggle("active"); +element.classList.contains("active"); + +// Multiple classes +element.classList.add("class1", "class2"); +element.classList.remove("class1", "class2"); + +// Replace class +element.classList.replace("old", "new"); + +// Styles +element.style.color = "red"; +element.style.backgroundColor = "blue"; + +// CSS custom properties +element.style.setProperty("--main-color", "#00ff00"); +const color = element.style.getPropertyValue("--main-color"); + +// Dataset (data-* attributes) +element.dataset.userId = "123"; +console.log(element.dataset.userId); // "123" +``` + + +## Creating and Removing Elements + + +```javascript !! js +// Create element +const div = document.createElement("div"); +div.textContent = "Hello"; +div.className = "greeting"; + +// Add to DOM +document.body.appendChild(div); +container.insertBefore(div, container.firstChild); + +// Replace element +parent.replaceChild(newElement, oldElement); + +// Clone element +const clone = element.cloneNode(true); // Deep clone +const shallow = element.cloneNode(false); // Shallow clone + +// Remove element +parent.removeChild(child); +child.remove(); // Modern + +// Insert HTML (security risk!) +element.insertAdjacentHTML("beforeend", "New"); + +// Insert positions: +// "beforebegin" - Before element +// "afterbegin" - Inside, at start +// "beforeend" - Inside, at end +// "afterend" - After element + +// Insert element +const span = document.createElement("span"); +element.insertAdjacentElement("beforeend", span); +``` + + +## Best Practices + + +```javascript !! js +// 1. Minimize reflows and repaints +// Bad: Multiple reflows +element.style.width = "100px"; +element.style.height = "100px"; +element.style.margin = "10px"; + +// Good: Single reflow +element.style.cssText = "width: 100px; height: 100px; margin: 10px;"; + +// Better: Use classes +element.className = "optimized-style"; + +// 2. Batch DOM updates +// Bad: Multiple DOM insertions +items.forEach(item => { + document.body.appendChild(createElement(item)); +}); + +// Good: Document fragment +const fragment = document.createDocumentFragment(); +items.forEach(item => { + fragment.appendChild(createElement(item)); +}); +document.body.appendChild(fragment); // Single reflow + +// 3. Cache DOM queries +// Bad +function update() { + document.getElementById("result").textContent = "Done"; +} + +// Good +const result = document.getElementById("result"); +function update() { + result.textContent = "Done"; +} + +// 4. Use event delegation +// Bad (many listeners) +items.forEach(item => { + item.addEventListener("click", handleClick); +}); + +// Good (single listener) +container.addEventListener("click", (e) => { + if (e.target.matches(".item")) { + handleClick(e); + } +}); +``` + + +## Exercises + +### Exercise 1: Dynamic List +Create a function that builds a list from an array: +```javascript +function buildList(items) { + // Create
      with
    • for each item +} +``` + +### Exercise 2: Toggle Classes +```javascript +// Add "active" class on click, remove from others +function setupTabs(tabs) { + // Implementation +} +``` + +### Exercise 3: Form Validation +```javascript +// Validate form on submit +function validateForm(form) { + // Check required fields + // Show errors +} +``` + +## Summary + +### Key Takeaways + +1. **Selection:** Use querySelector/querySelectorAll +2. **Traversal:** parentElement, children, nextElementSibling +3. **Modification:** textContent, classList, style +4. **Performance:** Minimize reflows, batch updates, cache queries + +### Comparison: Java vs JavaScript + +| Feature | Java | JavaScript | +|---------|------|------------| +| **DOM Access** | No DOM | document methods | +| **Event Handling** | Listeners | addEventListener | +| **Styling** | CSS files | Inline + classes | +| **Updates** | Repaint | Reflow/repaint | + +## What's Next? + +Next: **Module 13: Event Handling** - Master events, propagation, and delegation! diff --git a/content/docs/java2js/module-12-dom-manipulation.zh-cn.mdx b/content/docs/java2js/module-12-dom-manipulation.zh-cn.mdx new file mode 100644 index 0000000..c4a0f26 --- /dev/null +++ b/content/docs/java2js/module-12-dom-manipulation.zh-cn.mdx @@ -0,0 +1,303 @@ +--- +title: "模块 12:DOM 操作" +description: "掌握 DOM 遍历、操作和最佳实践" +--- + +## 模块 12:DOM 操作 + +文档对象模型(DOM)是 JavaScript 与 HTML 文档的接口。本模块教你如何动态地与网页交互。 + +## 学习目标 + +完成本模块后,你将: +✅ 理解 DOM 结构和遍历 +✅ 掌握元素选择方法 +✅ 学习修改 DOM 元素 +✅ 理解创建和删除元素 +✅ 了解性能最佳实践 +✅ 掌握 DOM 操作模式 + +## DOM vs Java GUI + + +```java !! java +// Java - Swing +JFrame frame = new JFrame("My App"); +JButton button = new JButton("Click me"); +frame.add(button); +frame.setVisible(true); + +// JavaFX +Button btn = new Button("Click me"); +Scene scene = new Scene(new StackPane(btn), 300, 250); +stage.setScene(scene); +stage.show(); +``` + +```javascript !! js +// JavaScript - DOM +const button = document.createElement("button"); +button.textContent = "Click me"; +document.body.appendChild(button); +``` + + +## 选择元素 + + +```javascript !! js +// 现代方法(推荐) +const byId = document.getElementById("myId"); +const byClass = document.getElementsByClassName("myClass"); +const byTag = document.getElementsByTagName("div"); + +// 查询选择器(最灵活) +const element = document.querySelector("#myId"); +const allElements = document.querySelectorAll(".myClass"); + +// CSS 选择器 +const element1 = document.querySelector("#container .item"); +const element2 = document.querySelector("button[data-action='submit']"); +const element3 = document.querySelector("ul > li:first-child"); + +// 上下文选择 +const container = document.querySelector("#container"); +const items = container.querySelectorAll(".item"); // 仅在容器内 + +// 性能考虑 +// getElementById 对于 ID 查找最快 +// querySelector 较慢但更灵活 + +// 最佳实践:缓存选择器 +const button = document.getElementById("submit"); +button.addEventListener("click", handleClick); +``` + + +## 遍历 DOM + + +```javascript !! js +const parent = document.getElementById("parent"); + +// 子元素 +const children = parent.children; // HTMLCollection (实时) +const childNodes = parent.childNodes; // NodeList (实时) +const firstChild = parent.firstElementChild; +const lastChild = parent.lastElementChild; + +// 兄弟元素 +const nextSibling = element.nextElementSibling; +const prevSibling = element.previousElementSibling; + +// 父元素 +const parent = element.parentElement; + +// 遍历 DOM 树 +function walkDOM(node, callback) { + callback(node); + node = node.firstChild; + while (node) { + walkDOM(node, callback); + node = node.nextSibling; + } +} + +// 查找元素 +const closestParent = element.closest(".container"); +const matches = element.matches(".active"); +``` + + +## 修改元素 + + +```javascript !! js +const element = document.querySelector("#myElement"); + +// 内容 +element.textContent = "New text"; // 纯文本 +element.innerHTML = "New HTML"; // HTML (安全风险!) + +// 属性 +element.setAttribute("class", "active"); +element.getAttribute("class"); +element.hasAttribute("class"); +element.removeAttribute("class"); + +// 属性(推荐) +element.id = "myId"; +element.className = "my-class"; +element.href = "https://example.com"; +element.disabled = true; + +// 类 +element.classList.add("active"); +element.classList.remove("inactive"); +element.classList.toggle("active"); +element.classList.contains("active"); + +// 多个类 +element.classList.add("class1", "class2"); +element.classList.remove("class1", "class2"); + +// 替换类 +element.classList.replace("old", "new"); + +// 样式 +element.style.color = "red"; +element.style.backgroundColor = "blue"; + +// CSS 自定义属性 +element.style.setProperty("--main-color", "#00ff00"); +const color = element.style.getPropertyValue("--main-color"); + +// 数据集(data-* 属性) +element.dataset.userId = "123"; +console.log(element.dataset.userId); // "123" +``` + + +## 创建和删除元素 + + +```javascript !! js +// 创建元素 +const div = document.createElement("div"); +div.textContent = "Hello"; +div.className = "greeting"; + +// 添加到 DOM +document.body.appendChild(div); +container.insertBefore(div, container.firstChild); + +// 替换元素 +parent.replaceChild(newElement, oldElement); + +// 克隆元素 +const clone = element.cloneNode(true); // 深克隆 +const shallow = element.cloneNode(false); // 浅克隆 + +// 删除元素 +parent.removeChild(child); +child.remove(); // 现代 + +// 插入 HTML(安全风险!) +element.insertAdjacentHTML("beforeend", "New"); + +// 插入位置: +// "beforebegin" - 元素之前 +// "afterbegin" - 内部,开始处 +// "beforeend" - 内部,结束处 +// "afterend" - 元素之后 + +// 插入元素 +const span = document.createElement("span"); +element.insertAdjacentElement("beforeend", span); +``` + + +## 最佳实践 + + +```javascript !! js +// 1. 最小化回流和重绘 +// 不好:多次回流 +element.style.width = "100px"; +element.style.height = "100px"; +element.style.margin = "10px"; + +// 好:单次回流 +element.style.cssText = "width: 100px; height: 100px; margin: 10px;"; + +// 更好:使用类 +element.className = "optimized-style"; + +// 2. 批处理 DOM 更新 +// 不好:多次 DOM 插入 +items.forEach(item => { + document.body.appendChild(createElement(item)); +}); + +// 好:文档片段 +const fragment = document.createDocumentFragment(); +items.forEach(item => { + fragment.appendChild(createElement(item)); +}); +document.body.appendChild(fragment); // 单次回流 + +// 3. 缓存 DOM 查询 +// 不好 +function update() { + document.getElementById("result").textContent = "Done"; +} + +// 好 +const result = document.getElementById("result"); +function update() { + result.textContent = "Done"; +} + +// 4. 使用事件委托 +// 不好(许多监听器) +items.forEach(item => { + item.addEventListener("click", handleClick); +}); + +// 好(单个监听器) +container.addEventListener("click", (e) => { + if (e.target.matches(".item")) { + handleClick(e); + } +}); +``` + + +## 练习 + +### 练习 1:动态列表 +创建一个从数组构建列表的函数: +```javascript +function buildList(items) { + // 创建
        ,每个项目一个
      • +} +``` + +### 练习 2:切换类 +```javascript +// 点击时添加 "active" 类,从其他类中删除 +function setupTabs(tabs) { + // 实现 +} +``` + +### 练习 3:表单验证 +```javascript +// 提交时验证表单 +function validateForm(form) { + // 检查必填字段 + // 显示错误 +} +``` + +## 总结 + +### 关键要点 + +1. **选择:**使用 querySelector/querySelectorAll +2. **遍历:**parentElement、children、nextElementSibling +3. **修改:**textContent、classList、style +4. **性能:**最小化回流、批处理更新、缓存查询 + +### 对比:Java vs JavaScript + +| 特性 | Java | JavaScript | +|------|------|-----------| +| **DOM 访问** | 无 DOM | document 方法 | +| **事件处理** | 监听器 | addEventListener | +| **样式** | CSS 文件 | 内联 + 类 | +| **更新** | 重绘 | 回流/重绘 | + +## 接下来是什么? + +接下来:**模块 13:事件处理** - 掌握事件、传播和委托! diff --git a/content/docs/java2js/module-12-dom-manipulation.zh-tw.mdx b/content/docs/java2js/module-12-dom-manipulation.zh-tw.mdx new file mode 100644 index 0000000..a419635 --- /dev/null +++ b/content/docs/java2js/module-12-dom-manipulation.zh-tw.mdx @@ -0,0 +1,201 @@ +--- +title: "Module 12: DOM Manipulation" +description: "掌握 DOM 遍歷、操作和最佳實踐" +--- + +## Module 12: DOM Manipulation + +文件物件模型(DOM)是 JavaScript 與 HTML 文件的介面。本模組將教你如何動態地與網頁互動。 + +## Learning Objectives + +完成本模組後,你將: +✅ 理解 DOM 結構和遍歷 +✅ 掌握元素選擇方法 +✅ 學習修改 DOM 元素 +✅ 理解創建和移除元素 +✅ 知道效能最佳實踐 +✅ 掌握 DOM 操作模式 + +## DOM vs Java GUI + + +```java !! java +// Java - Swing +JFrame frame = new JFrame("My App"); +JButton button = new JButton("Click me"); +frame.add(button); +frame.setVisible(true); + +// JavaFX +Button btn = new Button("Click me"); +Scene scene = new Scene(new StackPane(btn), 300, 250); +stage.setScene(scene); +stage.show(); +``` + +```javascript !! js +// JavaScript - DOM +const button = document.createElement("button"); +button.textContent = "Click me"; +document.body.appendChild(button); +``` + + +## Selecting Elements + + +```javascript !! js +// 現代方法(首選) +const byId = document.getElementById("myId"); +const byClass = document.getElementsByClassName("myClass"); +const byTag = document.getElementsByTagName("div"); + +// Query selectors(最靈活) +const element = document.querySelector("#myId"); +const allElements = document.querySelectorAll(".myClass"); + +// CSS selectors +const element1 = document.querySelector("#container .item"); +const element2 = document.querySelector("button[data-action='submit']"); + +// Context selection +const container = document.querySelector("#container"); +const items = container.querySelectorAll(".item"); // 僅在 container 內 + +// 最佳實踐:緩存選擇器 +const button = document.getElementById("submit"); +button.addEventListener("click", handleClick); +``` + + +## Traversing the DOM + + +```javascript !! js +const parent = document.getElementById("parent"); + +// Children +const children = parent.children; // HTMLCollection (live) +const childNodes = parent.childNodes; // NodeList (live) +const firstChild = parent.firstElementChild; +const lastChild = parent.lastElementChild; + +// Siblings +const nextSibling = element.nextElementSibling; +const prevSibling = element.previousElementSibling; + +// Parent +const parent = element.parentElement; + +// Finding elements +const closestParent = element.closest(".container"); +const matches = element.matches(".active"); +``` + + +## Modifying Elements + + +```javascript !! js +const element = document.querySelector("#myElement"); + +// Content +element.textContent = "New text"; // 純文本 +element.innerHTML = "New HTML"; // HTML(安全風險!) + +// Attributes +element.setAttribute("class", "active"); +element.getAttribute("class"); +element.hasAttribute("class"); +element.removeAttribute("class"); + +// Properties(首選) +element.id = "myId"; +element.className = "my-class"; +element.href = "https://example.com"; +element.disabled = true; + +// Classes +element.classList.add("active"); +element.classList.remove("inactive"); +element.classList.toggle("active"); +element.classList.contains("active"); + +// Styles +element.style.color = "red"; +element.style.backgroundColor = "blue"; +``` + + +## Creating and Removing Elements + + +```javascript !! js +// Create element +const div = document.createElement("div"); +div.textContent = "Hello"; +div.className = "greeting"; + +// Add to DOM +document.body.appendChild(div); +container.insertBefore(div, container.firstChild); + +// Remove element +child.remove(); // Modern + +// Clone element +const clone = element.cloneNode(true); // Deep clone +``` + + +## Best Practices + + +```javascript !! js +// 1. 最小化 reflows 和 repaints +// Bad: 多次 reflow +element.style.width = "100px"; +element.style.height = "100px"; + +// Good: 單次 reflow +element.style.cssText = "width: 100px; height: 100px;"; + +// Better: 使用類別 +element.className = "optimized-style"; + +// 2. 批量 DOM 更新 +// Good: Document fragment +const fragment = document.createDocumentFragment(); +items.forEach(item => { + fragment.appendChild(createElement(item)); +}); +document.body.appendChild(fragment); // 單次 reflow + +// 3. 緩存 DOM 查詢 +const result = document.getElementById("result"); +function update() { + result.textContent = "Done"; +} + +// 4. 使用事件委派 +container.addEventListener("click", (e) => { + if (e.target.matches(".item")) { + handleClick(e); + } +}); +``` + + +## Summary + +### Key Takeaways + +1. **Selection:** 使用 querySelector/querySelectorAll +2. **Traversal:** parentElement、children、nextElementSibling +3. **Modification:** textContent、classList、style +4. **Performance:** 最小化 reflows、批量更新、緩存查詢 + +## What's Next? + +接下來是 **Module 13: Event Handling** - 掌握事件、傳播和委派! diff --git a/content/docs/java2js/module-13-events.mdx b/content/docs/java2js/module-13-events.mdx new file mode 100644 index 0000000..505c6a3 --- /dev/null +++ b/content/docs/java2js/module-13-events.mdx @@ -0,0 +1,367 @@ +--- +title: "Module 13: Event Handling" +description: "Master JavaScript events, propagation, and event delegation" +--- + +## Module 13: Event Handling + +Events are actions that happen in the browser. Understanding events is crucial for creating interactive web applications. + +## Learning Objectives + +By the end of this module, you will: +✅ Understand event handlers and listeners +✅ Master event propagation (bubbling and capturing) +✅ Learn event delegation +✅ Know common event types +✅ Understand event objects +✅ Master event best practices + +## Event Handling: Java vs JavaScript + + +```java !! java +// Java - ActionListener +button.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + System.out.println("Clicked!"); + } +}); + +// Java 8+ Lambda +button.addActionListener(e -> System.out.println("Clicked!")); +``` + +```javascript !! js +// JavaScript - Event listeners +button.addEventListener("click", function(e) { + console.log("Clicked!"); +}); + +// Arrow function +button.addEventListener("click", (e) => { + console.log("Clicked!"); +}); +``` + + +## Adding Event Listeners + + +```javascript !! js +const button = document.querySelector("#myButton"); + +// Add listener +button.addEventListener("click", function(event) { + console.log("Button clicked!"); +}); + +// Arrow function (no own 'this') +button.addEventListener("click", (event) => { + console.log(this); // Window (not button!) +}); + +// Named function (better for removal) +function handleClick(event) { + console.log("Handled!"); +} + +button.addEventListener("click", handleClick); + +// Remove listener +button.removeEventListener("click", handleClick); + +// Options parameter +button.addEventListener("click", handleClick, { + capture: false, // Use capturing phase + once: true, // Remove after first call + passive: true // Don't prevent default +}); + +// Event attributes (avoid) + +``` + + +## Event Propagation + + +```html +
        +
        +
        Click me
        +
        +
        + + +``` +
        + +## Event Delegation + + +```javascript !! js +// Bad: Individual listeners (memory intensive) +const buttons = document.querySelectorAll("button"); +buttons.forEach(button => { + button.addEventListener("click", handleClick); +}); + +// Good: Event delegation (single listener) +document.addEventListener("click", (e) => { + if (e.target.matches("button")) { + handleClick(e); + } +}); + +// Practical: Dynamic content +const list = document.getElementById("list"); + +// Add listener to parent +list.addEventListener("click", (e) => { + const item = e.target.closest("li"); + if (!item) return; + + console.log("Clicked:", item.textContent); +}); + +// Add items dynamically (no need to add listeners) +function addItem(text) { + const li = document.createElement("li"); + li.textContent = text; + list.appendChild(li); +} + +// Delegation with data attributes +document.addEventListener("click", (e) => { + const button = e.target.closest("[data-action]"); + if (!button) return; + + const action = button.dataset.action; + switch (action) { + case "save": + handleSave(button); + break; + case "delete": + handleDelete(button); + break; + } +}); +``` + + +## Common Events + + +```javascript !! js +// Mouse events +element.addEventListener("click", handler); +element.addEventListener("dblclick", handler); +element.addEventListener("mousedown", handler); +element.addEventListener("mouseup", handler); +element.addEventListener("mouseover", handler); +element.addEventListener("mouseout", handler); +element.addEventListener("mouseenter", handler); // Doesn't bubble +element.addEventListener("mouseleave", handler); // Doesn't bubble +element.addEventListener("mousemove", handler); + +// Keyboard events +element.addEventListener("keydown", handler); +element.addEventListener("keyup", handler); +element.addEventListener("keypress", handler); + +// Form events +form.addEventListener("submit", handler); +input.addEventListener("input", handler); // On any change +input.addEventListener("change", handler); // On blur +input.addEventListener("focus", handler); +input.addEventListener("blur", handler); + +// Document events +document.addEventListener("DOMContentLoaded", handler); +window.addEventListener("load", handler); +window.addEventListener("resize", handler); +window.addEventListener("scroll", handler); + +// Practical: Keyboard shortcuts +document.addEventListener("keydown", (e) => { + // Ctrl+S + if (e.ctrlKey && e.key === "s") { + e.preventDefault(); + save(); + } + + // Escape + if (e.key === "Escape") { + closeModal(); + } +}); +``` + + +## Event Object + + +```javascript !! js +element.addEventListener("click", (e) => { + // Target vs currentTarget + console.log(e.target); // Element clicked + console.log(e.currentTarget); // Element with listener + + // Coordinates + console.log(e.clientX, e.clientY); // Relative to viewport + console.log(e.pageX, e.pageY); // Relative to document + console.log(e.offsetX, e.offsetY); // Relative to target + + // Modifiers + console.log(e.ctrlKey); + console.log(e.shiftKey); + console.log(e.altKey); + console.log(e.metaKey); + + // Button (mouse) + console.log(e.button); // 0: left, 1: middle, 2: right + + // Key + console.log(e.key); // Character + console.log(e.code); // Physical key + console.log(e.keyCode); // Deprecated + + // Prevent default + e.preventDefault(); // Stop default behavior + + // Form validation + form.addEventListener("submit", (e) => { + if (!isValid()) { + e.preventDefault(); // Stop form submission + } + }); + + // Stop propagation + e.stopPropagation(); // Stop bubbling +}); +``` + + +## Best Practices + + +```javascript !! js +// 1. Use event delegation for dynamic content +container.addEventListener("click", (e) => { + if (e.target.matches(".item")) { + handleItemClick(e); + } +}); + +// 2. Remove listeners when done +function setup() { + const handler = (e) => console.log(e); + button.addEventListener("click", handler); + + // Later + button.removeEventListener("click", handler); +} + +// 3. Use passive listeners for scroll/touch +window.addEventListener("scroll", handler, { passive: true }); + +// 4. Debounce expensive handlers +const handleResize = debounce(() => { + // Expensive operation +}, 100); + +window.addEventListener("resize", handleResize); + +// 5. Don't inline event handlers +// Bad + + +// Good + +document.getElementById("btn").addEventListener("click", doSomething); +``` + + +## Exercises + +### Exercise 1: Event Delegation +```javascript +// Add click handling to dynamically created list items +function setupList(items) { + // Create list and add delegation +} +``` + +### Exercise 2: Keyboard Navigation +```javascript +// Add arrow key navigation to list +function setupNavigation(items) { + // Implementation +} +``` + +### Exercise 3: Form Validation +```javascript +// Validate on blur and submit +function setupFormValidation(form) { + // Implementation +} +``` + +## Summary + +### Key Takeaways + +1. **Listeners:** addEventListener is preferred +2. **Propagation:** Capturing → Target → Bubbling +3. **Delegation:** Single listener for multiple elements +4. **Best:** Use delegation, remove unused listeners + +### Comparison: Java vs JavaScript + +| Feature | Java | JavaScript | +|---------|------|------------| +| **Listeners** | Interfaces | addEventListener | +| **Propagation** | None | Capturing/Bubbling | +| **Delegation** | Event sources | DOM hierarchy | +| **Removal** | removeListener | removeEventListener | + +## What's Next? + +Next: **Module 14: ES6+ Modules** - Learn modern JavaScript module system! diff --git a/content/docs/java2js/module-13-events.zh-cn.mdx b/content/docs/java2js/module-13-events.zh-cn.mdx new file mode 100644 index 0000000..c4ede04 --- /dev/null +++ b/content/docs/java2js/module-13-events.zh-cn.mdx @@ -0,0 +1,367 @@ +--- +title: "模块 13:事件处理" +description: "掌握 JavaScript 事件、传播和事件委托" +--- + +## 模块 13:事件处理 + +事件是在浏览器中发生的动作。理解事件对于创建交互式 Web 应用程序至关重要。 + +## 学习目标 + +完成本模块后,你将: +✅ 理解事件处理器和监听器 +✅ 掌握事件传播(冒泡和捕获) +✅ 学习事件委托 +✅ 了解常见事件类型 +✅ 理解事件对象 +✅ 掌握事件最佳实践 + +## 事件处理: Java vs JavaScript + + +```java !! java +// Java - ActionListener +button.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + System.out.println("Clicked!"); + } +}); + +// Java 8+ Lambda +button.addActionListener(e -> System.out.println("Clicked!")); +``` + +```javascript !! js +// JavaScript - 事件监听器 +button.addEventListener("click", function(e) { + console.log("Clicked!"); +}); + +// 箭头函数 +button.addEventListener("click", (e) => { + console.log("Clicked!"); +}); +``` + + +## 添加事件监听器 + + +```javascript !! js +const button = document.querySelector("#myButton"); + +// 添加监听器 +button.addEventListener("click", function(event) { + console.log("Button clicked!"); +}); + +// 箭头函数(没有自己的 'this') +button.addEventListener("click", (event) => { + console.log(this); // Window (不是 button!) +}); + +// 命名函数(更适合删除) +function handleClick(event) { + console.log("Handled!"); +} + +button.addEventListener("click", handleClick); + +// 删除监听器 +button.removeEventListener("click", handleClick); + +// 选项参数 +button.addEventListener("click", handleClick, { + capture: false, // 使用捕获阶段 + once: true, // 第一次调用后删除 + passive: true // 不阻止默认行为 +}); + +// 事件属性(避免) + +``` + + +## 事件传播 + + +```html +
        +
        +
        Click me
        +
        +
        + + +``` +
        + +## 事件委托 + + +```javascript !! js +// 不好:单个监听器(内存密集) +const buttons = document.querySelectorAll("button"); +buttons.forEach(button => { + button.addEventListener("click", handleClick); +}); + +// 好:事件委托(单个监听器) +document.addEventListener("click", (e) => { + if (e.target.matches("button")) { + handleClick(e); + } +}); + +// 实用:动态内容 +const list = document.getElementById("list"); + +// 为父元素添加监听器 +list.addEventListener("click", (e) => { + const item = e.target.closest("li"); + if (!item) return; + + console.log("Clicked:", item.textContent); +}); + +// 动态添加项目(无需添加监听器) +function addItem(text) { + const li = document.createElement("li"); + li.textContent = text; + list.appendChild(li); +} + +// 使用数据属性委托 +document.addEventListener("click", (e) => { + const button = e.target.closest("[data-action]"); + if (!button) return; + + const action = button.dataset.action; + switch (action) { + case "save": + handleSave(button); + break; + case "delete": + handleDelete(button); + break; + } +}); +``` + + +## 常见事件 + + +```javascript !! js +// 鼠标事件 +element.addEventListener("click", handler); +element.addEventListener("dblclick", handler); +element.addEventListener("mousedown", handler); +element.addEventListener("mouseup", handler); +element.addEventListener("mouseover", handler); +element.addEventListener("mouseout", handler); +element.addEventListener("mouseenter", handler); // 不冒泡 +element.addEventListener("mouseleave", handler); // 不冒泡 +element.addEventListener("mousemove", handler); + +// 键盘事件 +element.addEventListener("keydown", handler); +element.addEventListener("keyup", handler); +element.addEventListener("keypress", handler); + +// 表单事件 +form.addEventListener("submit", handler); +input.addEventListener("input", handler); // 任何更改时 +input.addEventListener("change", handler); // 失焦时 +input.addEventListener("focus", handler); +input.addEventListener("blur", handler); + +// 文档事件 +document.addEventListener("DOMContentLoaded", handler); +window.addEventListener("load", handler); +window.addEventListener("resize", handler); +window.addEventListener("scroll", handler); + +// 实用:键盘快捷键 +document.addEventListener("keydown", (e) => { + // Ctrl+S + if (e.ctrlKey && e.key === "s") { + e.preventDefault(); + save(); + } + + // Escape + if (e.key === "Escape") { + closeModal(); + } +}); +``` + + +## 事件对象 + + +```javascript !! js +element.addEventListener("click", (e) => { + // target vs currentTarget + console.log(e.target); // 被点击的元素 + console.log(e.currentTarget); // 带有监听器的元素 + + // 坐标 + console.log(e.clientX, e.clientY); // 相对于视口 + console.log(e.pageX, e.pageY); // 相对于文档 + console.log(e.offsetX, e.offsetY); // 相对于目标 + + // 修饰键 + console.log(e.ctrlKey); + console.log(e.shiftKey); + console.log(e.altKey); + console.log(e.metaKey); + + // 按钮(鼠标) + console.log(e.button); // 0:左键, 1:中键, 2:右键 + + // 键 + console.log(e.key); // 字符 + console.log(e.code); // 物理键 + console.log(e.keyCode); // 已弃用 + + // 阻止默认 + e.preventDefault(); // 停止默认行为 + + // 表单验证 + form.addEventListener("submit", (e) => { + if (!isValid()) { + e.preventDefault(); // 停止表单提交 + } + }); + + // 停止传播 + e.stopPropagation(); // 停止冒泡 +}); +``` + + +## 最佳实践 + + +```javascript !! js +// 1. 对动态内容使用事件委托 +container.addEventListener("click", (e) => { + if (e.target.matches(".item")) { + handleItemClick(e); + } +}); + +// 2. 完成后删除监听器 +function setup() { + const handler = (e) => console.log(e); + button.addEventListener("click", handler); + + // 稍后 + button.removeEventListener("click", handler); +} + +// 3. 对滚动/触摸使用被动监听器 +window.addEventListener("scroll", handler, { passive: true }); + +// 4. 对昂贵的处理器进行防抖 +const handleResize = debounce(() => { + // 昂贵的操作 +}, 100); + +window.addEventListener("resize", handleResize); + +// 5. 不要内联事件处理器 +// 不好 + + +// 好 + +document.getElementById("btn").addEventListener("click", doSomething); +``` + + +## 练习 + +### 练习 1:事件委托 +```javascript +// 为动态创建的列表项添加点击处理 +function setupList(items) { + // 创建列表并添加委托 +} +``` + +### 练习 2:键盘导航 +```javascript +// 为列表添加箭头键导航 +function setupNavigation(items) { + // 实现 +} +``` + +### 练习 3:表单验证 +```javascript +// 在失焦和提交时验证 +function setupFormValidation(form) { + // 实现 +} +``` + +## 总结 + +### 关键要点 + +1. **监听器:**addEventListener 是首选 +2. **传播:**捕获 → 目标 → 冒泡 +3. **委托:**多个元素使用单个监听器 +4. **最佳:**使用委托,删除未使用的监听器 + +### 对比:Java vs JavaScript + +| 特性 | Java | JavaScript | +|------|------|-----------| +| **监听器** | 接口 | addEventListener | +| **传播** | 无 | 捕获/冒泡 | +| **委托** | 事件源 | DOM 层级 | +| **删除** | removeListener | removeEventListener | + +## 接下来是什么? + +接下来:**模块 14:ES6+ 模块** - 学习现代 JavaScript 模块系统! diff --git a/content/docs/java2js/module-13-events.zh-tw.mdx b/content/docs/java2js/module-13-events.zh-tw.mdx new file mode 100644 index 0000000..fc5839f --- /dev/null +++ b/content/docs/java2js/module-13-events.zh-tw.mdx @@ -0,0 +1,258 @@ +--- +title: "Module 13: Event Handling" +description: "掌握 JavaScript 事件、傳播和事件委派" +--- + +## Module 13: Event Handling + +事件是在瀏覽器中發生的動作。理解事件對於創建互動式網頁應用程式至關重要。 + +## Learning Objectives + +完成本模組後,你將: +✅ 理解事件處理程序和監聽器 +✅ 掌握事件傳播(冒泡和捕獲) +✅ 學習事件委派 +✅ 知道常見事件類型 +✅ 理解事件物件 +✅ 掌握事件最佳實踐 + +## Event Handling: Java vs JavaScript + + +```java !! java +// Java - ActionListener +button.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + System.out.println("Clicked!"); + } +}); + +// Java 8+ Lambda +button.addActionListener(e -> System.out.println("Clicked!")); +``` + +```javascript !! js +// JavaScript - Event listeners +button.addEventListener("click", function(e) { + console.log("Clicked!"); +}); + +// Arrow function +button.addEventListener("click", (e) => { + console.log("Clicked!"); +}); +``` + + +## Adding Event Listeners + + +```javascript !! js +const button = document.querySelector("#myButton"); + +// Add listener +button.addEventListener("click", function(event) { + console.log("Button clicked!"); +}); + +// Named function(便於移除) +function handleClick(event) { + console.log("Handled!"); +} + +button.addEventListener("click", handleClick); + +// Remove listener +button.removeEventListener("click", handleClick); + +// Options parameter +button.addEventListener("click", handleClick, { + capture: false, // 使用捕獲階段 + once: true, // 第一次調用後移除 + passive: true // 不阻止默認行為 +}); +``` + + +## Event Propagation + + +```html +
        +
        +
        Click me
        +
        +
        + + +``` +
        + +## Event Delegation + + +```javascript !! js +// Bad: 個別監聽器(記憶體密集) +const buttons = document.querySelectorAll("button"); +buttons.forEach(button => { + button.addEventListener("click", handleClick); +}); + +// Good: 事件委派(單一監聽器) +document.addEventListener("click", (e) => { + if (e.target.matches("button")) { + handleClick(e); + } +}); + +// 實用:動態內容 +const list = document.getElementById("list"); + +// 在父級添加監聽器 +list.addEventListener("click", (e) => { + const item = e.target.closest("li"); + if (!item) return; + + console.log("Clicked:", item.textContent); +}); + +// 動態添加項目(無需添加監聽器) +function addItem(text) { + const li = document.createElement("li"); + li.textContent = text; + list.appendChild(li); +} +``` + + +## Common Events + + +```javascript !! js +// Mouse events +element.addEventListener("click", handler); +element.addEventListener("dblclick", handler); +element.addEventListener("mousedown", handler); +element.addEventListener("mouseup", handler); +element.addEventListener("mouseover", handler); +element.addEventListener("mouseout", handler); + +// Keyboard events +element.addEventListener("keydown", handler); +element.addEventListener("keyup", handler); + +// Form events +form.addEventListener("submit", handler); +input.addEventListener("input", handler); // 任何更改時 +input.addEventListener("change", handler); // on blur +input.addEventListener("focus", handler); +input.addEventListener("blur", handler); + +// Document events +document.addEventListener("DOMContentLoaded", handler); +window.addEventListener("load", handler); +window.addEventListener("resize", handler); +window.addEventListener("scroll", handler); + +// 實用:鍵盤快捷鍵 +document.addEventListener("keydown", (e) => { + // Ctrl+S + if (e.ctrlKey && e.key === "s") { + e.preventDefault(); + save(); + } + + // Escape + if (e.key === "Escape") { + closeModal(); + } +}); +``` + + +## Event Object + + +```javascript !! js +element.addEventListener("click", (e) => { + // Target vs currentTarget + console.log(e.target); // 被點擊的元素 + console.log(e.currentTarget); // 有監聽器的元素 + + // Coordinates + console.log(e.clientX, e.clientY); // 相對於視口 + console.log(e.pageX, e.pageY); // 相對於文件 + + // Modifiers + console.log(e.ctrlKey); + console.log(e.shiftKey); + console.log(e.altKey); + + // Prevent default + e.preventDefault(); // 阻止默認行為 + + // Stop propagation + e.stopPropagation(); // 阻止冒泡 +}); +``` + + +## Best Practices + + +```javascript !! js +// 1. 對動態內容使用事件委派 +container.addEventListener("click", (e) => { + if (e.target.matches(".item")) { + handleItemClick(e); + } +}); + +// 2. 完成後移除監聽器 +function setup() { + const handler = (e) => console.log(e); + button.addEventListener("click", handler); + + // 之後 + button.removeEventListener("click", handler); +} + +// 3. 對 scroll/touch 使用被動監聽器 +window.addEventListener("scroll", handler, { passive: true }); + +// 4. 對昂貴的處理程序使用 debounce +const handleResize = debounce(() => { + // 昂貴的操作 +}, 100); + +window.addEventListener("resize", handleResize); +``` + + +## Summary + +### Key Takeaways + +1. **Listeners:** addEventListener 是首選 +2. **Propagation:** 捕獲 → 目標 → 冒泡 +3. **Delegation:** 單一監聽器處理多個元素 +4. **Best:** 使用委派、移除未使用的監聽器 + +## What's Next? + +接下來是 **Module 14: ES6+ Modules** - 學習現代 JavaScript 模組系統! diff --git a/content/docs/java2js/module-14-modules.mdx b/content/docs/java2js/module-14-modules.mdx new file mode 100644 index 0000000..5b53cdb --- /dev/null +++ b/content/docs/java2js/module-14-modules.mdx @@ -0,0 +1,325 @@ +--- +title: "Module 14: ES6+ Modules" +description: "Master JavaScript ES6 modules, imports, and exports" +--- + +## Module 14: ES6+ Modules + +JavaScript modules allow you to split code into separate files, making code more maintainable and reusable. + +## Learning Objectives + +By the end of this module, you will: +✅ Understand ES6 module syntax +✅ Master import and export statements +✅ Learn default vs named exports +✅ Understand module loading +✅ Know common module patterns +✅ Master best practices for code organization + +## Modules: Java vs JavaScript + + +```java !! java +// Java - Packages and imports +package com.example.app; + +import java.util.List; +import java.util.ArrayList; + +public class MyClass { + public static void main(String[] args) { + List list = new ArrayList<>(); + } +} +``` + +```javascript !! js +// JavaScript - ES6 modules +// utils.js +export function add(a, b) { + return a + b; +} + +export const PI = 3.14159; + +// main.js +import { add, PI } from "./utils.js"; +import MyClass from "./MyClass.js"; +``` + + +## Exporting + + +```javascript !! js +// Named exports +export const PI = 3.14159; +export function add(a, b) { + return a + b; +} +export class Calculator { + // ... +} + +// Export list +const name = "John"; +const age = 25; +function greet() { + console.log("Hello!"); +} +export { name, age, greet }; + +// Export with rename +export { greet as sayHello }; + +// Default export +export default class UserService { + // ... +} + +// Or +class UserService { } +export { UserService as default }; + +// Mixed exports (one default + named) +export default class API { + // ... +} +export const BASE_URL = "https://api.example.com"; +export function get(endpoint) { + // ... +} + +// Re-exporting +export { add, subtract } from "./math.js"; +export * from "./utils.js"; +export { default as Calculator } from "./calc.js"; +``` + + +## Importing + + +```javascript !! js +// Named imports +import { add, subtract } from "./math.js"; +import { add as sum } from "./math.js"; + +// Default import +import Calculator from "./calc.js"; + +// Mixed imports +import UserService, { BASE_URL, get } from "./api.js"; + +// Import all (namespace import) +import * as math from "./math.js"; +math.add(1, 2); + +// Import side effects (no imports) +import "./polyfills.js"; + +// Dynamic import (async) +const module = await import("./math.js"); +const { add } = module; + +// Dynamic import with error handling +async function loadModule() { + try { + const { default: Component } = await import("./Component.js"); + return Component; + } catch (error) { + console.error("Failed to load module:", error); + } +} + +// Lazy loading +button.addEventListener("click", async () => { + const { handleAuth } = await import("./auth.js"); + handleAuth(); +}); +``` + + +## Module Structure + + +```javascript !! js +// file structure: +// src/ +// utils/ +// math.js +// string.js +// services/ +// api.js +// auth.js +// components/ +// Button.js +// Modal.js +// main.js + +// utils/math.js +export function add(a, b) { return a + b; } +export function subtract(a, b) { return a - b; } + +// utils/string.js +export function capitalize(str) { + return str.charAt(0).toUpperCase() + str.slice(1); +} + +// utils/index.js (barrel file) +export * from "./math.js"; +export * from "./string.js"; + +// services/api.js +const BASE_URL = "https://api.example.com"; +export async function get(url) { + const response = await fetch(BASE_URL + url); + return response.json(); +} + +// main.js +import { add, capitalize } from "./utils/index.js"; +import { get } from "./services/api.js"; + +// Usage +add(1, 2); +capitalize("hello"); +get("/users"); +``` + + +## Best Practices + + +```javascript !! js +// 1. Use named exports for utilities +// utils.js +export function add(a, b) { return a + b; } +export function subtract(a, b) { return a - b; } + +// 2. Use default export for main class/component +// UserService.js +export default class UserService { + // ... +} + +// 3. Organize by feature +// features/auth/ +// auth.js +// login.js +// logout.js + +// 4. Use index.js for clean imports +// components/ +// Button.js +// Modal.js +// index.js + +export { default as Button } from "./Button.js"; +export { default as Modal } from "./Modal.js"; + +// Now import from components +import { Button, Modal } from "./components/index.js"; + +// 5. Avoid circular dependencies +// Bad: a.js imports b.js, b.js imports a.js +// Good: Create shared module c.js + +// 6. Use dynamic imports for code splitting +const router = { + home: () => import("./pages/Home.js"), + about: () => import("./pages/About.js") +}; +``` + + +## Common Patterns + + +```javascript !! js +// Pattern 1: Singleton +let instance; + +export default class Database { + constructor() { + if (instance) { + return instance; + } + instance = this; + } +} + +// Pattern 2: Factory +export function createClient(config) { + return new APIClient(config); +} + +// Pattern 3: Module with private state +const privateData = new WeakMap(); + +export class User { + constructor(name) { + privateData.set(this, { secret: "xyz" }); + this.name = name; + } + + getSecret() { + return privateData.get(this).secret; + } +} + +// Pattern 4: Testing export +export function internalFunction() { + // For testing only +} + +export function publicFunction() { + return internalFunction(); +} +``` + + +## Exercises + +### Exercise 1: Create Modules +```javascript +// Create math.js with add, subtract, multiply, divide +// Export them as named exports +// Create main.js that imports and uses them +``` + +### Exercise 2: Default Export +```javascript +// Create APIClass.js with default export +// Import and instantiate it +``` + +### Exercise 3: Barrel File +```javascript +// Create utils/index.js that re-exports from other files +``` + +## Summary + +### Key Takeaways + +1. **Named Exports:** Multiple per module +2. **Default Export:** One per module +3. **Imports:** Named, default, namespace +4. **Dynamic:** import() for code splitting +5. **Organization:** Feature-based folders + +### Comparison: Java vs JavaScript + +| Feature | Java | JavaScript | +|---------|------|------------| +| **Modules** | Packages | ES6 modules | +| **Import** | import keyword | import statement | +| **Export** | public keyword | export keyword | +| **Loading** | Compile-time | Runtime (async) | +| **Cycles** | Allowed | Avoid | + +## What's Next? + +Next: **Module 15: Tooling and Build** - Learn webpack, Vite, and modern build tools! diff --git a/content/docs/java2js/module-14-modules.zh-cn.mdx b/content/docs/java2js/module-14-modules.zh-cn.mdx new file mode 100644 index 0000000..1c8e469 --- /dev/null +++ b/content/docs/java2js/module-14-modules.zh-cn.mdx @@ -0,0 +1,325 @@ +--- +title: "模块 14:ES6+ 模块" +description: "掌握 JavaScript ES6 模块、导入和导出" +--- + +## 模块 14:ES6+ 模块 + +JavaScript 模块允许你将代码拆分为单独的文件,使代码更易维护和可重用。 + +## 学习目标 + +完成本模块后,你将: +✅ 理解 ES6 模块语法 +✅ 掌握导入和导出语句 +✅ 学习默认导出 vs 命名导出 +✅ 理解模块加载 +✅ 了解常见模块模式 +✅ 掌握代码组织的最佳实践 + +## 模块: Java vs JavaScript + + +```java !! java +// Java - 包和导入 +package com.example.app; + +import java.util.List; +import java.util.ArrayList; + +public class MyClass { + public static void main(String[] args) { + List list = new ArrayList<>(); + } +} +``` + +```javascript !! js +// JavaScript - ES6 模块 +// utils.js +export function add(a, b) { + return a + b; +} + +export const PI = 3.14159; + +// main.js +import { add, PI } from "./utils.js"; +import MyClass from "./MyClass.js"; +``` + + +## 导出 + + +```javascript !! js +// 命名导出 +export const PI = 3.14159; +export function add(a, b) { + return a + b; +} +export class Calculator { + // ... +} + +// 导出列表 +const name = "John"; +const age = 25; +function greet() { + console.log("Hello!"); +} +export { name, age, greet }; + +// 导出时重命名 +export { greet as sayHello }; + +// 默认导出 +export default class UserService { + // ... +} + +// 或 +class UserService { } +export { UserService as default }; + +// 混合导出(一个默认 + 命名) +export default class API { + // ... +} +export const BASE_URL = "https://api.example.com"; +export function get(endpoint) { + // ... +} + +// 重新导出 +export { add, subtract } from "./math.js"; +export * from "./utils.js"; +export { default as Calculator } from "./calc.js"; +``` + + +## 导入 + + +```javascript !! js +// 命名导入 +import { add, subtract } from "./math.js"; +import { add as sum } from "./math.js"; + +// 默认导入 +import Calculator from "./calc.js"; + +// 混合导入 +import UserService, { BASE_URL, get } from "./api.js"; + +// 导入所有(命名空间导入) +import * as math from "./math.js"; +math.add(1, 2); + +// 导入副作用(无导入) +import "./polyfills.js"; + +// 动态导入(异步) +const module = await import("./math.js"); +const { add } = module; + +// 带错误处理的动态导入 +async function loadModule() { + try { + const { default: Component } = await import("./Component.js"); + return Component; + } catch (error) { + console.error("Failed to load module:", error); + } +} + +// 延迟加载 +button.addEventListener("click", async () => { + const { handleAuth } = await import("./auth.js"); + handleAuth(); +}); +``` + + +## 模块结构 + + +```javascript !! js +// 文件结构: +// src/ +// utils/ +// math.js +// string.js +// services/ +// api.js +// auth.js +// components/ +// Button.js +// Modal.js +// main.js + +// utils/math.js +export function add(a, b) { return a + b; } +export function subtract(a, b) { return a - b; } + +// utils/string.js +export function capitalize(str) { + return str.charAt(0).toUpperCase() + str.slice(1); +} + +// utils/index.js (桶文件) +export * from "./math.js"; +export * from "./string.js"; + +// services/api.js +const BASE_URL = "https://api.example.com"; +export async function get(url) { + const response = await fetch(BASE_URL + url); + return response.json(); +} + +// main.js +import { add, capitalize } from "./utils/index.js"; +import { get } from "./services/api.js"; + +// 使用 +add(1, 2); +capitalize("hello"); +get("/users"); +``` + + +## 最佳实践 + + +```javascript !! js +// 1. 对工具函数使用命名导出 +// utils.js +export function add(a, b) { return a + b; } +export function subtract(a, b) { return a - b; } + +// 2. 对主类/组件使用默认导出 +// UserService.js +export default class UserService { + // ... +} + +// 3. 按功能组织 +// features/auth/ +// auth.js +// login.js +// logout.js + +// 4. 使用 index.js 进行清晰导入 +// components/ +// Button.js +// Modal.js +// index.js + +export { default as Button } from "./Button.js"; +export { default as Modal } from "./Modal.js"; + +// 现在从 components 导入 +import { Button, Modal } from "./components/index.js"; + +// 5. 避免循环依赖 +// 不好:a.js 导入 b.js, b.js 导入 a.js +// 好:创建共享模块 c.js + +// 6. 使用动态导入进行代码分割 +const router = { + home: () => import("./pages/Home.js"), + about: () => import("./pages/About.js") +}; +``` + + +## 常见模式 + + +```javascript !! js +// 模式 1:单例 +let instance; + +export default class Database { + constructor() { + if (instance) { + return instance; + } + instance = this; + } +} + +// 模式 2:工厂 +export function createClient(config) { + return new APIClient(config); +} + +// 模式 3:带私有状态的模块 +const privateData = new WeakMap(); + +export class User { + constructor(name) { + privateData.set(this, { secret: "xyz" }); + this.name = name; + } + + getSecret() { + return privateData.get(this).secret; + } +} + +// 模式 4:测试导出 +export function internalFunction() { + // 仅用于测试 +} + +export function publicFunction() { + return internalFunction(); +} +``` + + +## 练习 + +### 练习 1:创建模块 +```javascript +// 创建带有 add、subtract、multiply、divide 的 math.js +// 将它们作为命名导出导出 +// 创建导入并使用它们的 main.js +``` + +### 练习 2:默认导出 +```javascript +// 创建带有默认导出的 APIClass.js +// 导入并实例化它 +``` + +### 练习 3:桶文件 +```javascript +// 创建从其他文件重新导出的 utils/index.js +``` + +## 总结 + +### 关键要点 + +1. **命名导出:**每个模块多个 +2. **默认导出:**每个模块一个 +3. **导入:**命名、默认、命名空间 +4. **动态:**import() 用于代码分割 +5. **组织:**基于功能的文件夹 + +### 对比:Java vs JavaScript + +| 特性 | Java | JavaScript | +|------|------|-----------| +| **模块** | 包 | ES6 模块 | +| **导入** | import 关键字 | import 语句 | +| **导出** | public 关键字 | export 关键字 | +| **加载** | 编译时 | 运行时(异步) | +| **循环** | 允许 | 避免 | + +## 接下来是什么? + +接下来:**模块 15:工具和构建** - 学习 webpack、Vite 和现代构建工具! diff --git a/content/docs/java2js/module-14-modules.zh-tw.mdx b/content/docs/java2js/module-14-modules.zh-tw.mdx new file mode 100644 index 0000000..4451552 --- /dev/null +++ b/content/docs/java2js/module-14-modules.zh-tw.mdx @@ -0,0 +1,246 @@ +--- +title: "Module 14: ES6+ Modules" +description: "掌握 JavaScript ES6 模組、imports 和 exports" +--- + +## Module 14: ES6+ Modules + +JavaScript 模組允許你將程式碼分割成單獨的文件,使程式碼更易維護和重用。 + +## Learning Objectives + +完成本模組後,你將: +✅ 理解 ES6 模組語法 +✅ 掌握 import 和 export 語句 +✅ 學習 default vs named exports +✅ 理解模組加載 +✅ 知道常見模組模式 +✅ 掌握程式碼組織的最佳實踐 + +## Modules: Java vs JavaScript + + +```java !! java +// Java - Packages 和 imports +package com.example.app; + +import java.util.List; +import java.util.ArrayList; + +public class MyClass { + public static void main(String[] args) { + List list = new ArrayList<>(); + } +} +``` + +```javascript !! js +// JavaScript - ES6 modules +// utils.js +export function add(a, b) { + return a + b; +} + +export const PI = 3.14159; + +// main.js +import { add, PI } from "./utils.js"; +import MyClass from "./MyClass.js"; +``` + + +## Exporting + + +```javascript !! js +// Named exports +export const PI = 3.14159; +export function add(a, b) { + return a + b; +} +export class Calculator { + // ... +} + +// Export list +const name = "John"; +const age = 25; +function greet() { + console.log("Hello!"); +} +export { name, age, greet }; + +// Export with rename +export { greet as sayHello }; + +// Default export +export default class UserService { + // ... +} + +// Mixed exports(一個 default + named) +export default class API { + // ... +} +export const BASE_URL = "https://api.example.com"; +export function get(endpoint) { + // ... +} + +// Re-exporting +export { add, subtract } from "./math.js"; +export * from "./utils.js"; +export { default as Calculator } from "./calc.js"; +``` + + +## Importing + + +```javascript !! js +// Named imports +import { add, subtract } from "./math.js"; +import { add as sum } from "./math.js"; + +// Default import +import Calculator from "./calc.js"; + +// Mixed imports +import UserService, { BASE_URL, get } from "./api.js"; + +// Import all(namespace import) +import * as math from "./math.js"; +math.add(1, 2); + +// Import side effects(無導入) +import "./polyfills.js"; + +// Dynamic import(async) +const module = await import("./math.js"); +const { add } = module; + +// Lazy loading +button.addEventListener("click", async () => { + const { handleAuth } = await import("./auth.js"); + handleAuth(); +}); +``` + + +## Module Structure + + +```javascript !! js +// file structure: +// src/ +// utils/ +// math.js +// string.js +// services/ +// api.js +// auth.js +// components/ +// Button.js +// Modal.js +// main.js + +// utils/math.js +export function add(a, b) { return a + b; } +export function subtract(a, b) { return a - b; } + +// utils/index.js (barrel file) +export * from "./math.js"; +export * from "./string.js"; + +// main.js +import { add, capitalize } from "./utils/index.js"; +import { get } from "./services/api.js"; +``` + + +## Best Practices + + +```javascript !! js +// 1. 對工具使用 named exports +// utils.js +export function add(a, b) { return a + b; } + +// 2. 對主類別/組件使用 default export +// UserService.js +export default class UserService { + // ... +} + +// 3. 按功能組織 +// features/auth/ +// auth.js +// login.js +// logout.js + +// 4. 使用 index.js 進行乾淨導入 +// components/index.js +export { default as Button } from "./Button.js"; +export { default as Modal } from "./Modal.js"; + +// 5. 避免循環依賴 +// Bad: a.js imports b.js, b.js imports a.js +// Good: 創建共享模組 c.js + +// 6. 對程式碼分割使用動態導入 +const router = { + home: () => import("./pages/Home.js"), + about: () => import("./pages/About.js") +}; +``` + + +## Common Patterns + + +```javascript !! js +// Pattern 1: Singleton +let instance; +export default class Database { + constructor() { + if (instance) { + return instance; + } + instance = this; + } +} + +// Pattern 2: Factory +export function createClient(config) { + return new APIClient(config); +} + +// Pattern 3: 模組與私有狀態 +const privateData = new WeakMap(); + +export class User { + constructor(name) { + privateData.set(this, { secret: "xyz" }); + this.name = name; + } + + getSecret() { + return privateData.get(this).secret; + } +} +``` + + +## Summary + +### Key Takeaways + +1. **Named Exports:** 每個模組多個 +2. **Default Export:** 每個模組一個 +3. **Imports:** Named、default、namespace +4. **Dynamic:** import() 進行程式碼分割 +5. **Organization:** 基於功能的文件夾 + +## What's Next? + +恭喜!你已經完成了 JavaScript 基礎到進階的學習之旅。繼續練習和構建專案來巩固你的知識! diff --git a/content/docs/java2js/module-15-tooling.mdx b/content/docs/java2js/module-15-tooling.mdx new file mode 100644 index 0000000..af24a9c --- /dev/null +++ b/content/docs/java2js/module-15-tooling.mdx @@ -0,0 +1,221 @@ +--- +title: "Module 15: Tooling and Build" +description: "Learn JavaScript build tools, bundlers, and development workflow" +--- + +## Module 15: Tooling and Build + +Modern JavaScript development requires build tools for bundling, transpiling, and optimizing code. + +## Learning Objectives + +By the end of this module, you will: +✅ Understand npm and package management +✅ Learn about bundlers (webpack, Vite) +✅ Understand transpilation (Babel) +✅ Know development vs production builds +✅ Learn about code splitting +✅ Master modern development workflow + +## Package Management: Java vs JavaScript + + +```java !! java +// Java - Maven/Gradle +// pom.xml (Maven) + + + org.springframework + spring-core + 5.3.8 + + + +// build.gradle +dependencies { + implementation 'org.springframework:spring-core:5.3.8' +} + +// Install dependencies +mvn install // or: gradle build +``` + +```javascript !! js +// JavaScript - npm/yarn +// package.json +{ + "dependencies": { + "lodash": "^4.17.21", + "axios": "^0.27.2" + }, + "devDependencies": { + "webpack": "^5.72.0", + "jest": "^28.1.0" + } +} + +// Install dependencies +npm install +npm install lodash // Add package +npm install --save-dev webpack // Add dev dependency +``` + + +## Project Structure + + +```bash +my-project/ +├── src/ +│ ├── index.js # Entry point +│ ├── utils/ +│ │ └── helpers.js +│ └── styles/ +│ └── main.css +├── public/ +│ └── index.html # HTML template +├── dist/ # Build output (generated) +├── package.json # Dependencies +├── webpack.config.js # Build config +└── babel.config.js # Transpiler config +``` + + +## Build Tools + + +```javascript !! js +// Webpack configuration +// webpack.config.js +module.exports = { + entry: './src/index.js', + output: { + filename: 'bundle.js', + path: path.resolve(__dirname, 'dist') + }, + module: { + rules: [ + { + test: /\.js$/, + exclude: /node_modules/, + use: 'babel-loader' + }, + { + test: /\.css$/, + use: ['style-loader', 'css-loader'] + } + ] + }, + plugins: [ + new HtmlWebpackPlugin({ + template: './public/index.html' + }) + ], + mode: 'development' // or 'production' +}; + +// Vite (modern, faster) +// vite.config.js +export default { + plugins: [react()], + build: { + outDir: 'dist', + rollupOptions: { + output: { + manualChunks: { + vendor: ['react', 'react-dom'] + } + } + } + } +}; +``` + + +## Transpilation + + +```javascript !! js +// .babelrc or babel.config.js +module.exports = { + presets: [ + ['@babel/preset-env', { + targets: { + browsers: ['> 1%', 'last 2 versions'] + } + }], + '@babel/preset-react' + ], + plugins: [ + '@babel/plugin-proposal-class-properties', + '@babel/plugin-syntax-dynamic-import' + ] +}; + +// Polyfills +import 'core-js/stable'; +import 'regenerator-runtime/runtime'; +``` + + +## Scripts + + +```json +{ + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "test": "jest", + "lint": "eslint src/", + "format": "prettier --write src/" + } +} +``` + + +## Best Practices + + +```javascript !! js +// 1. Use modern build tools +// Vite (faster than webpack) +npm create vite@latest my-app + +// 2. Optimize production builds +// webpack +mode: 'production', +optimization: { + minimize: true, + splitChunks: { + chunks: 'all' + } +} + +// 3. Code splitting +// Dynamic import +const module = await import('./module.js'); + +// 4. Source maps for debugging +devtool: 'source-map' // webpack + +// 5. Hot module replacement +// Vite has this built-in +npm run dev +``` + + +## Summary + +### Key Takeaways + +1. **npm**: Package manager for JavaScript +2. **Bundlers**: webpack, Vite (combine files) +3. **Babel**: Transpile modern JS +4. **Dev tools**: HMR, source maps +5. **Production**: Minification, splitting + +## What's Next? + +Next: **Module 16: Testing and Debugging** - Learn Jest, debugging tools, and testing patterns! diff --git a/content/docs/java2js/module-15-tooling.zh-cn.mdx b/content/docs/java2js/module-15-tooling.zh-cn.mdx new file mode 100644 index 0000000..af24a9c --- /dev/null +++ b/content/docs/java2js/module-15-tooling.zh-cn.mdx @@ -0,0 +1,221 @@ +--- +title: "Module 15: Tooling and Build" +description: "Learn JavaScript build tools, bundlers, and development workflow" +--- + +## Module 15: Tooling and Build + +Modern JavaScript development requires build tools for bundling, transpiling, and optimizing code. + +## Learning Objectives + +By the end of this module, you will: +✅ Understand npm and package management +✅ Learn about bundlers (webpack, Vite) +✅ Understand transpilation (Babel) +✅ Know development vs production builds +✅ Learn about code splitting +✅ Master modern development workflow + +## Package Management: Java vs JavaScript + + +```java !! java +// Java - Maven/Gradle +// pom.xml (Maven) + + + org.springframework + spring-core + 5.3.8 + + + +// build.gradle +dependencies { + implementation 'org.springframework:spring-core:5.3.8' +} + +// Install dependencies +mvn install // or: gradle build +``` + +```javascript !! js +// JavaScript - npm/yarn +// package.json +{ + "dependencies": { + "lodash": "^4.17.21", + "axios": "^0.27.2" + }, + "devDependencies": { + "webpack": "^5.72.0", + "jest": "^28.1.0" + } +} + +// Install dependencies +npm install +npm install lodash // Add package +npm install --save-dev webpack // Add dev dependency +``` + + +## Project Structure + + +```bash +my-project/ +├── src/ +│ ├── index.js # Entry point +│ ├── utils/ +│ │ └── helpers.js +│ └── styles/ +│ └── main.css +├── public/ +│ └── index.html # HTML template +├── dist/ # Build output (generated) +├── package.json # Dependencies +├── webpack.config.js # Build config +└── babel.config.js # Transpiler config +``` + + +## Build Tools + + +```javascript !! js +// Webpack configuration +// webpack.config.js +module.exports = { + entry: './src/index.js', + output: { + filename: 'bundle.js', + path: path.resolve(__dirname, 'dist') + }, + module: { + rules: [ + { + test: /\.js$/, + exclude: /node_modules/, + use: 'babel-loader' + }, + { + test: /\.css$/, + use: ['style-loader', 'css-loader'] + } + ] + }, + plugins: [ + new HtmlWebpackPlugin({ + template: './public/index.html' + }) + ], + mode: 'development' // or 'production' +}; + +// Vite (modern, faster) +// vite.config.js +export default { + plugins: [react()], + build: { + outDir: 'dist', + rollupOptions: { + output: { + manualChunks: { + vendor: ['react', 'react-dom'] + } + } + } + } +}; +``` + + +## Transpilation + + +```javascript !! js +// .babelrc or babel.config.js +module.exports = { + presets: [ + ['@babel/preset-env', { + targets: { + browsers: ['> 1%', 'last 2 versions'] + } + }], + '@babel/preset-react' + ], + plugins: [ + '@babel/plugin-proposal-class-properties', + '@babel/plugin-syntax-dynamic-import' + ] +}; + +// Polyfills +import 'core-js/stable'; +import 'regenerator-runtime/runtime'; +``` + + +## Scripts + + +```json +{ + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "test": "jest", + "lint": "eslint src/", + "format": "prettier --write src/" + } +} +``` + + +## Best Practices + + +```javascript !! js +// 1. Use modern build tools +// Vite (faster than webpack) +npm create vite@latest my-app + +// 2. Optimize production builds +// webpack +mode: 'production', +optimization: { + minimize: true, + splitChunks: { + chunks: 'all' + } +} + +// 3. Code splitting +// Dynamic import +const module = await import('./module.js'); + +// 4. Source maps for debugging +devtool: 'source-map' // webpack + +// 5. Hot module replacement +// Vite has this built-in +npm run dev +``` + + +## Summary + +### Key Takeaways + +1. **npm**: Package manager for JavaScript +2. **Bundlers**: webpack, Vite (combine files) +3. **Babel**: Transpile modern JS +4. **Dev tools**: HMR, source maps +5. **Production**: Minification, splitting + +## What's Next? + +Next: **Module 16: Testing and Debugging** - Learn Jest, debugging tools, and testing patterns! diff --git a/content/docs/java2js/module-15-tooling.zh-tw.mdx b/content/docs/java2js/module-15-tooling.zh-tw.mdx new file mode 100644 index 0000000..276e062 --- /dev/null +++ b/content/docs/java2js/module-15-tooling.zh-tw.mdx @@ -0,0 +1,221 @@ +--- +title: "Module 15: Tooling and Build" +description: "Learn JavaScript build tools, bundlers, and development workflow" +--- + +## Module 15: Tooling and Build + +現代 JavaScript 開發需要建置工具來打包、轉譯和優化程式碼。 + +## Learning Objectives + +完成本模組後,您將: +✅ 了解 npm 和套件管理 +✅ 學習打包工具(webpack、Vite) +✅ 了解轉譯(Babel) +✅ 知道開發與生產建置的差異 +✅ 學習程式碼分割 +✅ 掌握現代開發工作流程 + +## Package Management: Java vs JavaScript + + +```java !! java +// Java - Maven/Gradle +// pom.xml (Maven) + + + org.springframework + spring-core + 5.3.8 + + + +// build.gradle +dependencies { + implementation 'org.springframework:spring-core:5.3.8' +} + +// Install dependencies +mvn install // or: gradle build +``` + +```javascript !! js +// JavaScript - npm/yarn +// package.json +{ + "dependencies": { + "lodash": "^4.17.21", + "axios": "^0.27.2" + }, + "devDependencies": { + "webpack": "^5.72.0", + "jest": "^28.1.0" + } +} + +// Install dependencies +npm install +npm install lodash // Add package +npm install --save-dev webpack // Add dev dependency +``` + + +## Project Structure + + +```bash +my-project/ +├── src/ +│ ├── index.js # Entry point +│ ├── utils/ +│ │ └── helpers.js +│ └── styles/ +│ └── main.css +├── public/ +│ └── index.html # HTML template +├── dist/ # Build output (generated) +├── package.json # Dependencies +├── webpack.config.js # Build config +└── babel.config.js # Transpiler config +``` + + +## Build Tools + + +```javascript !! js +// Webpack configuration +// webpack.config.js +module.exports = { + entry: './src/index.js', + output: { + filename: 'bundle.js', + path: path.resolve(__dirname, 'dist') + }, + module: { + rules: [ + { + test: /\.js$/, + exclude: /node_modules/, + use: 'babel-loader' + }, + { + test: /\.css$/, + use: ['style-loader', 'css-loader'] + } + ] + }, + plugins: [ + new HtmlWebpackPlugin({ + template: './public/index.html' + }) + ], + mode: 'development' // or 'production' +}; + +// Vite (modern, faster) +// vite.config.js +export default { + plugins: [react()], + build: { + outDir: 'dist', + rollupOptions: { + output: { + manualChunks: { + vendor: ['react', 'react-dom'] + } + } + } + } +}; +``` + + +## Transpilation + + +```javascript !! js +// .babelrc or babel.config.js +module.exports = { + presets: [ + ['@babel/preset-env', { + targets: { + browsers: ['> 1%', 'last 2 versions'] + } + }], + '@babel/preset-react' + ], + plugins: [ + '@babel/plugin-proposal-class-properties', + '@babel/plugin-syntax-dynamic-import' + ] +}; + +// Polyfills +import 'core-js/stable'; +import 'regenerator-runtime/runtime'; +``` + + +## Scripts + + +```json +{ + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "test": "jest", + "lint": "eslint src/", + "format": "prettier --write src/" + } +} +``` + + +## Best Practices + + +```javascript !! js +// 1. Use modern build tools +// Vite (faster than webpack) +npm create vite@latest my-app + +// 2. Optimize production builds +// webpack +mode: 'production', +optimization: { + minimize: true, + splitChunks: { + chunks: 'all' + } +} + +// 3. Code splitting +// Dynamic import +const module = await import('./module.js'); + +// 4. Source maps for debugging +devtool: 'source-map' // webpack + +// 5. Hot module replacement +// Vite has this built-in +npm run dev +``` + + +## Summary + +### Key Takeaways + +1. **npm**: JavaScript 的套件管理器 +2. **Bundlers**: webpack、Vite(組合檔案) +3. **Babel**: 轉譯現代 JS +4. **Dev tools**: HMR、source maps +5. **Production**: 壓縮、分割 + +## What's Next? + +下一個:**Module 16: Testing and Debugging** - 學習 Jest、除錯工具和測試模式! diff --git a/content/docs/java2js/module-16-testing-debugging.mdx b/content/docs/java2js/module-16-testing-debugging.mdx new file mode 100644 index 0000000..882603c --- /dev/null +++ b/content/docs/java2js/module-16-testing-debugging.mdx @@ -0,0 +1,244 @@ +--- +title: "Module 16: Testing and Debugging" +description: "Master JavaScript testing frameworks and debugging techniques" +--- + +## Module 16: Testing and Debugging + +Testing and debugging are essential skills for writing reliable JavaScript code. + +## Learning Objectives + +By the end of this module, you will: +✅ Master Jest testing framework +✅ Learn testing patterns (unit, integration, E2E) +✅ Understand debugging techniques +✅ Know browser DevTools +✅ Learn error handling strategies +✅ Master testing best practices + +## Testing: Java vs JavaScript + + +```java !! java +// Java - JUnit +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +public class CalculatorTest { + @Test + public void testAdd() { + Calculator calc = new Calculator(); + assertEquals(5, calc.add(2, 3)); + } +} +``` + +```javascript !! js +// JavaScript - Jest +test("add returns sum", () => { + const calc = new Calculator(); + expect(calc.add(2, 3)).toBe(5); +}); +``` + + +## Jest Testing + + +```javascript !! js +// math.test.js +const { add, subtract } = require("./math"); + +describe("Math operations", () => { + test("add adds numbers", () => { + expect(add(2, 3)).toBe(5); + }); + + test("subtract subtracts numbers", () => { + expect(subtract(5, 3)).toBe(2); + }); + + // Matchers + test("matchers", () => { + expect(42).toBe(42); + expect({ name: "John" }).toEqual({ name: "John" }); + expect("hello").toContain("ell"); + expect([1, 2, 3]).toHaveLength(3); + expect(null).toBeNull(); + expect(undefined).toBeUndefined(); + expect(true).toBeTruthy(); + expect(false).toBeFalsy(); + }); +}); + +// Async testing +test("async fetch", async () => { + const data = await fetchData(); + expect(data).toBeDefined(); +}); + +// Mocking +test("mock function", () => { + const mock = jest.fn(); + mock("arg1", "arg2"); + + expect(mock).toHaveBeenCalledWith("arg1", "arg2"); + expect(mock).toHaveBeenCalledTimes(1); +}); +``` + + +## Debugging + + +```javascript !! js +// 1. console.log (simplest) +console.log("Value:", value); +console.table(data); // Tabular data +console.group("Group"); +console.log("Inside group"); +console.groupEnd(); + +// 2. debugger statement +function buggyFunction() { + const x = 1; + debugger; // Pauses execution + const y = 2; + return x + y; +} + +// 3. Chrome DevTools +// - Sources panel +// - Set breakpoints +// - Step through code +// - Inspect variables + +// 4. Error handling +try { + riskyOperation(); +} catch (error) { + console.error("Caught:", error); + console.trace(); // Stack trace +} + +// 5. Performance profiling +console.time("operation"); +// ... code ... +console.timeEnd("operation"); +``` + + +## Browser DevTools + + +```html + + + + + + + +``` + + +## Testing Patterns + + +```javascript !! js +// Unit test +describe("UserService", () => { + test("creates user", () => { + const user = new User("John"); + expect(user.name).toBe("John"); + }); +}); + +// Integration test +describe("API", () => { + test("fetches user", async () => { + const response = await fetch("/api/users/1"); + const user = await response.json(); + expect(user.id).toBe(1); + }); +}); + +// Mock external dependencies +jest.mock("./api"); +import { fetchUser } from "./api"; + +test("displays user", async () => { + fetchUser.mockResolvedValue({ id: 1, name: "John" }); + + const user = await getUser(1); + expect(user.name).toBe("John"); +}); +``` + + +## Best Practices + + +```javascript !! js +// 1. Test behavior, not implementation +// Bad +test("uses array.push", () => { + arr.push(1); + expect(arr.push).toHaveBeenCalled(); +}); + +// Good +test("adds item to array", () => { + addItem(arr, 1); + expect(arr).toContain(1); +}); + +// 2. Arrange-Act-Assert +test("user can login", () => { + // Arrange + const user = { email: "test@test.com", password: "pass" }; + + // Act + const result = authService.login(user); + + // Assert + expect(result.success).toBe(true); +}); + +// 3. One assertion per test (mostly) +test("returns user data", () => { + const user = getUser(1); + expect(user.id).toBe(1); + // Don't test everything in one test +}); + +// 4. Use descriptive test names +test("returns 404 when user not found", () => { + // Clear what it tests +}); +``` + + +## Summary + +### Key Takeaways + +1. **Jest**: Testing framework +2. **Matchers**: toBe, toEqual, toContain +3. **Async**: await async tests +4. **Mocking**: jest.fn(), jest.mock() +5. **Debug**: console.log, debugger, DevTools + +### Comparison: Java vs JavaScript + +| Feature | Java | JavaScript | +|---------|------|------------| +| **Testing** | JUnit | Jest | +| **Mocking** | Mockito | jest.fn() | +| **Debugging** | IDE debugger | Browser DevTools | +| **Profiling** | JProfiler | Chrome DevTools | + +## What's Next? + +Next: **Module 17: Popular Frameworks** - Learn React and Vue basics! diff --git a/content/docs/java2js/module-16-testing-debugging.zh-cn.mdx b/content/docs/java2js/module-16-testing-debugging.zh-cn.mdx new file mode 100644 index 0000000..211930f --- /dev/null +++ b/content/docs/java2js/module-16-testing-debugging.zh-cn.mdx @@ -0,0 +1,244 @@ +--- +title: "Module 16: Testing and Debugging" +description: "Master JavaScript testing frameworks and debugging techniques" +--- + +## Module 16: Testing and Debugging + +Testing and debugging are essential skills for writing reliable JavaScript code. + +## Learning Objectives + +By the end of this module, you will: +✅ Master Jest testing framework +✅ Learn testing patterns (unit, integration, E2E) +✅ Understand debugging techniques +✅ Know browser DevTools +✅ Learn error handling strategies +✅ Master testing best practices + +## Testing: Java vs JavaScript + + +```java !! java +// Java - JUnit +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +public class CalculatorTest { + @Test + public void testAdd() { + Calculator calc = new Calculator(); + assertEquals(5, calc.add(2, 3)); + } +} +``` + +```javascript !! js +// JavaScript - Jest +test("add returns sum", () => { + const calc = new Calculator(); + expect(calc.add(2, 3)).toBe(5); +}); +``` + + +## Jest Testing + + +```javascript !! js +// math.test.js +const { add, subtract } = require("./math"); + +describe("Math operations", () => { + test("add adds numbers", () => { + expect(add(2, 3)).toBe(5); + }); + + test("subtract subtracts numbers", () => { + expect(subtract(5, 3)).toBe(2); + }); + + // Matchers + test("matchers", () => { + expect(42).toBe(42); + expect({ name: "John" }).toEqual({ name: "John" }); + expect("hello").toContain("ell"); + expect([1, 2, 3]).toHaveLength(3); + expect(null).toBeNull(); + expect(undefined).toBeUndefined(); + expect(true).toBeTruthy(); + expect(false).toBeFalsy(); + }); +}); + +// Async testing +test("async fetch", async () => { + const data = await fetchData(); + expect(data).toBeDefined(); +}); + +// Mocking +test("mock function", () => { + const mock = jest.fn(); + mock("arg1", "arg2"); + + expect(mock).toHaveBeenCalledWith("arg1", "arg2"); + expect(mock).toHaveBeenCalledTimes(1); +}); +``` + + +## Debugging + + +```javascript !! js +// 1. console.log (simplest) +console.log("Value:", value); +console.table(data); // Tabular data +console.group("Group"); +console.log("Inside group"); +console.groupEnd(); + +// 2. debugger statement +function buggyFunction() { + const x = 1; + debugger; // Pauses execution + const y = 2; + return x + y; +} + +// 3. Chrome DevTools +// - Sources panel +// - Set breakpoints +// - Step through code +// - Inspect variables + +// 4. Error handling +try { + riskyOperation(); +} catch (error) { + console.error("Caught:", error); + console.trace(); // Stack trace +} + +// 5. Performance profiling +console.time("operation"); +// ... code ... +console.timeEnd("operation"); +``` + + +## Browser DevTools + + +```html + + + + + + + +``` + + +## Testing Patterns + + +```javascript !! js +// Unit test +describe("UserService", () => { + test("creates user", () => { + const user = new User("John"); + expect(user.name).toBe("John"); + }); +}); + +// Integration test +describe("API", () => { + test("fetches user", async () => { + const response = await fetch("/api/users/1"); + const user = await response.json(); + expect(user.id).toBe(1); + }); +}); + +// Mock external dependencies +jest.mock("./api"); +import { fetchUser } from "./api"; + +test("displays user", async () => { + fetchUser.mockResolvedValue({ id: 1, name: "John" }); + + const user = await getUser(1); + expect(user.name).toBe("John"); +}); +``` + + +## Best Practices + + +```javascript !! js +// 1. Test behavior, not implementation +// Bad +test("uses array.push", () => { + arr.push(1); + expect(arr.push).toHaveBeenCalled(); +}); + +// Good +test("adds item to array", () => { + addItem(arr, 1); + expect(arr).toContain(1); +}); + +// 2. Arrange-Act-Assert +test("user can login", () => { + // Arrange + const user = { email: "test@test.com", password: "pass" }; + + // Act + const result = authService.login(user); + + // Assert + expect(result.success).toBe(true); +}); + +// 3. One assertion per test (mostly) +test("returns user data", () => { + const user = getUser(1); + expect(user.id).toBe(1); + // Don't test everything in one test +}); + +// 4. Use descriptive test names +test("returns 404 when user not found", () => { + // Clear what it tests +}); +``` + + +## Summary + +### Key Takeaways + +1. **Jest**: Testing framework +2. **Matchers**: toBe, toEqual, toContain +3. **Async**: await async tests +4. **Mocking**: jest.fn(), jest.mock() +5. **Debug**: console.log, debugger, DevTools + +### Comparison: Java vs JavaScript + +| Feature | Java | JavaScript | +|---------|------|------------| +| **Testing** | JUnit | Jest | +| **Mocking** | Mockito | jest.fn() | +| **Debugging** | IDE debugger | Browser DevTools | +| **Profiling** | JProfiler | Chrome DevTools | + +## What's Next? + +Next: **Module 17: Popular Frameworks** - Learn React and Vue basics! diff --git a/content/docs/java2js/module-16-testing-debugging.zh-tw.mdx b/content/docs/java2js/module-16-testing-debugging.zh-tw.mdx new file mode 100644 index 0000000..f437750 --- /dev/null +++ b/content/docs/java2js/module-16-testing-debugging.zh-tw.mdx @@ -0,0 +1,244 @@ +--- +title: "Module 16: Testing and Debugging" +description: "Master JavaScript testing frameworks and debugging techniques" +--- + +## Module 16: Testing and Debugging + +測試和除錯是編寫可靠 JavaScript 程式碼的必備技能。 + +## Learning Objectives + +完成本模組後,您將: +✅ 掌握 Jest 測試框架 +✅ 學習測試模式(單元測試、整合測試、端對端測試) +✅ 了解除錯技巧 +✅ 知道瀏覽器開發者工具 +✅ 學習錯誤處理策略 +✅ 掌握測試最佳實踐 + +## Testing: Java vs JavaScript + + +```java !! java +// Java - JUnit +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +public class CalculatorTest { + @Test + public void testAdd() { + Calculator calc = new Calculator(); + assertEquals(5, calc.add(2, 3)); + } +} +``` + +```javascript !! js +// JavaScript - Jest +test("add returns sum", () => { + const calc = new Calculator(); + expect(calc.add(2, 3)).toBe(5); +}); +``` + + +## Jest Testing + + +```javascript !! js +// math.test.js +const { add, subtract } = require("./math"); + +describe("Math operations", () => { + test("add adds numbers", () => { + expect(add(2, 3)).toBe(5); + }); + + test("subtract subtracts numbers", () => { + expect(subtract(5, 3)).toBe(2); + }); + + // Matchers + test("matchers", () => { + expect(42).toBe(42); + expect({ name: "John" }).toEqual({ name: "John" }); + expect("hello").toContain("ell"); + expect([1, 2, 3]).toHaveLength(3); + expect(null).toBeNull(); + expect(undefined).toBeUndefined(); + expect(true).toBeTruthy(); + expect(false).toBeFalsy(); + }); +}); + +// Async testing +test("async fetch", async () => { + const data = await fetchData(); + expect(data).toBeDefined(); +}); + +// Mocking +test("mock function", () => { + const mock = jest.fn(); + mock("arg1", "arg2"); + + expect(mock).toHaveBeenCalledWith("arg1", "arg2"); + expect(mock).toHaveBeenCalledTimes(1); +}); +``` + + +## Debugging + + +```javascript !! js +// 1. console.log (simplest) +console.log("Value:", value); +console.table(data); // Tabular data +console.group("Group"); +console.log("Inside group"); +console.groupEnd(); + +// 2. debugger statement +function buggyFunction() { + const x = 1; + debugger; // Pauses execution + const y = 2; + return x + y; +} + +// 3. Chrome DevTools +// - Sources panel +// - Set breakpoints +// - Step through code +// - Inspect variables + +// 4. Error handling +try { + riskyOperation(); +} catch (error) { + console.error("Caught:", error); + console.trace(); // Stack trace +} + +// 5. Performance profiling +console.time("operation"); +// ... code ... +console.timeEnd("operation"); +``` + + +## Browser DevTools + + +```html + + + + + + + +``` + + +## Testing Patterns + + +```javascript !! js +// Unit test +describe("UserService", () => { + test("creates user", () => { + const user = new User("John"); + expect(user.name).toBe("John"); + }); +}); + +// Integration test +describe("API", () => { + test("fetches user", async () => { + const response = await fetch("/api/users/1"); + const user = await response.json(); + expect(user.id).toBe(1); + }); +}); + +// Mock external dependencies +jest.mock("./api"); +import { fetchUser } from "./api"; + +test("displays user", async () => { + fetchUser.mockResolvedValue({ id: 1, name: "John" }); + + const user = await getUser(1); + expect(user.name).toBe("John"); +}); +``` + + +## Best Practices + + +```javascript !! js +// 1. Test behavior, not implementation +// Bad +test("uses array.push", () => { + arr.push(1); + expect(arr.push).toHaveBeenCalled(); +}); + +// Good +test("adds item to array", () => { + addItem(arr, 1); + expect(arr).toContain(1); +}); + +// 2. Arrange-Act-Assert +test("user can login", () => { + // Arrange + const user = { email: "test@test.com", password: "pass" }; + + // Act + const result = authService.login(user); + + // Assert + expect(result.success).toBe(true); +}); + +// 3. One assertion per test (mostly) +test("returns user data", () => { + const user = getUser(1); + expect(user.id).toBe(1); + // Don't test everything in one test +}); + +// 4. Use descriptive test names +test("returns 404 when user not found", () => { + // Clear what it tests +}); +``` + + +## Summary + +### Key Takeaways + +1. **Jest**: 測試框架 +2. **Matchers**: toBe、toEqual、toContain +3. **Async**: await 非同步測試 +4. **Mocking**: jest.fn()、jest.mock() +5. **Debug**: console.log、debugger、DevTools + +### Comparison: Java vs JavaScript + +| Feature | Java | JavaScript | +|---------|------|------------| +| **Testing** | JUnit | Jest | +| **Mocking** | Mockito | jest.fn() | +| **Debugging** | IDE debugger | Browser DevTools | +| **Profiling** | JProfiler | Chrome DevTools | + +## What's Next? + +下一個:**Module 17: Popular Frameworks** - 學習 React 和 Vue 基礎! diff --git a/content/docs/java2js/module-17-frameworks.mdx b/content/docs/java2js/module-17-frameworks.mdx new file mode 100644 index 0000000..fdbbb8b --- /dev/null +++ b/content/docs/java2js/module-17-frameworks.mdx @@ -0,0 +1,185 @@ +--- +title: "Module 17: Popular Frameworks" +description: "Introduction to React and modern JavaScript frameworks" +--- + +## Module 17: Popular Frameworks + +Modern JavaScript development uses frameworks to build complex applications. This module introduces React and Vue. + +## Learning Objectives + +By the end of this module, you will: +✅ Understand component-based architecture +✅ Learn React basics (components, props, state) +✅ Know Vue basics +✅ Understand framework differences +✅ Learn when to use frameworks +✅ Know framework ecosystem + +## Framework Philosophy + + +```javascript !! js +// Vanilla JS - Imperative +const button = document.createElement("button"); +button.textContent = "Click me"; +button.addEventListener("click", () => { + console.log("Clicked!"); +}); +document.body.appendChild(button); + +// React - Declarative +function Button() { + return ; +} +``` + + +## React Basics + + +```javascript !! js +// Function component +function Welcome(props) { + return

        Hello, {props.name}

        ; +} + +// With JSX +function App() { + const [count, setCount] = useState(0); + + return ( +
        +

        Count: {count}

        + +
        + ); +} + +// Hooks +import { useState, useEffect } from "react"; + +function UserProfile({ userId }) { + const [user, setUser] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + fetchUser(userId).then(data => { + setUser(data); + setLoading(false); + }); + }, [userId]); + + if (loading) return
        Loading...
        ; + return
        {user.name}
        ; +} +``` +
        + +## Props and State + + +```javascript !! js +// Props (read-only) +function Greeting({ name, age }) { + return ( +
        +

        Hello, {name}

        +

        Age: {age}

        +
        + ); +} + +// Usage + + +// State (mutable) +function Counter() { + const [count, setCount] = useState(0); + + return ( +
        +

        Count: {count}

        + +
        + ); +} +``` +
        + +## Vue Basics + + +```javascript !! js +// Vue component + + + +``` + + +## When to Use Frameworks + + +```javascript !! js +// Use vanilla JS when: +// - Simple sites (mostly static) +// - Performance critical (no overhead) +// - Learning JavaScript basics + +// Use frameworks when: +// - Complex SPAs +// - Team development +// - Need state management +// - Rich UI interactions +``` + + +## Summary + +### Key Takeaways + +1. **React**: Component-based, hooks, JSX +2. **Vue**: Template-based, reactive +3. **State Management**: useState, Vuex +4. **Components**: Reusable UI pieces +5. **Ecosystem**: Rich libraries + +### Comparison: Java vs JavaScript + +| Feature | Java | JavaScript | +|---------|------|------------| +| **UI** | Swing/JavaFX | React/Vue | +| **State** | Fields | useState | +| **Events** | Listeners | onClick/@click | +| **Components** | Classes | Functions | + +## What's Next? + +Next: **Module 18: Common Pitfalls** - Learn mistakes to avoid in JavaScript! diff --git a/content/docs/java2js/module-17-frameworks.zh-cn.mdx b/content/docs/java2js/module-17-frameworks.zh-cn.mdx new file mode 100644 index 0000000..c385389 --- /dev/null +++ b/content/docs/java2js/module-17-frameworks.zh-cn.mdx @@ -0,0 +1,185 @@ +--- +title: "Module 17: Popular Frameworks" +description: "Introduction to React and modern JavaScript frameworks" +--- + +## Module 17: Popular Frameworks + +Modern JavaScript development uses frameworks to build complex applications. This module introduces React and Vue. + +## Learning Objectives + +By the end of this module, you will: +✅ Understand component-based architecture +✅ Learn React basics (components, props, state) +✅ Know Vue basics +✅ Understand framework differences +✅ Learn when to use frameworks +✅ Know framework ecosystem + +## Framework Philosophy + + +```javascript !! js +// Vanilla JS - Imperative +const button = document.createElement("button"); +button.textContent = "Click me"; +button.addEventListener("click", () => { + console.log("Clicked!"); +}); +document.body.appendChild(button); + +// React - Declarative +function Button() { + return ; +} +``` + + +## React Basics + + +```javascript !! js +// Function component +function Welcome(props) { + return

        Hello, {props.name}

        ; +} + +// With JSX +function App() { + const [count, setCount] = useState(0); + + return ( +
        +

        Count: {count}

        + +
        + ); +} + +// Hooks +import { useState, useEffect } from "react"; + +function UserProfile({ userId }) { + const [user, setUser] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + fetchUser(userId).then(data => { + setUser(data); + setLoading(false); + }); + }, [userId]); + + if (loading) return
        Loading...
        ; + return
        {user.name}
        ; +} +``` +
        + +## Props and State + + +```javascript !! js +// Props (read-only) +function Greeting({ name, age }) { + return ( +
        +

        Hello, {name}

        +

        Age: {age}

        +
        + ); +} + +// Usage + + +// State (mutable) +function Counter() { + const [count, setCount] = useState(0); + + return ( +
        +

        Count: {count}

        + +
        + ); +} +``` +
        + +## Vue Basics + + +```javascript !! js +// Vue component + + + +``` + + +## When to Use Frameworks + + +```javascript !! js +// Use vanilla JS when: +// - Simple sites (mostly static) +// - Performance critical (no overhead) +// - Learning JavaScript basics + +// Use frameworks when: +// - Complex SPAs +// - Team development +// - Need state management +// - Rich UI interactions +``` + + +## Summary + +### Key Takeaways + +1. **React**: Component-based, hooks, JSX +2. **Vue**: Template-based, reactive +3. **State Management**: useState, Vuex +4. **Components**: Reusable UI pieces +5. **Ecosystem**: Rich libraries + +### Comparison: Java vs JavaScript + +| Feature | Java | JavaScript | +|---------|------|------------| +| **UI** | Swing/JavaFX | React/Vue | +| **State** | Fields | useState | +| **Events** | Listeners | onClick/@click | +| **Components** | Classes | Functions | + +## What's Next? + +Next: **Module 18: Common Pitfalls** - Learn mistakes to avoid in JavaScript! diff --git a/content/docs/java2js/module-17-frameworks.zh-tw.mdx b/content/docs/java2js/module-17-frameworks.zh-tw.mdx new file mode 100644 index 0000000..8231f19 --- /dev/null +++ b/content/docs/java2js/module-17-frameworks.zh-tw.mdx @@ -0,0 +1,185 @@ +--- +title: "Module 17: Popular Frameworks" +description: "Introduction to React and modern JavaScript frameworks" +--- + +## Module 17: Popular Frameworks + +現代 JavaScript 開發使用框架來建構複雜的應用程式。本模組介紹 React 和 Vue。 + +## Learning Objectives + +完成本模組後,您將: +✅ 了解元件式架構 +✅ 學習 React 基礎(元件、props、state) +✅ 知道 Vue 基礎 +✅ 了解框架差異 +✅ 學習何時使用框架 +✅ 知道框架生態系統 + +## Framework Philosophy + + +```javascript !! js +// Vanilla JS - Imperative +const button = document.createElement("button"); +button.textContent = "Click me"; +button.addEventListener("click", () => { + console.log("Clicked!"); +}); +document.body.appendChild(button); + +// React - Declarative +function Button() { + return ; +} +``` + + +## React Basics + + +```javascript !! js +// Function component +function Welcome(props) { + return

        Hello, {props.name}

        ; +} + +// With JSX +function App() { + const [count, setCount] = useState(0); + + return ( +
        +

        Count: {count}

        + +
        + ); +} + +// Hooks +import { useState, useEffect } from "react"; + +function UserProfile({ userId }) { + const [user, setUser] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + fetchUser(userId).then(data => { + setUser(data); + setLoading(false); + }); + }, [userId]); + + if (loading) return
        Loading...
        ; + return
        {user.name}
        ; +} +``` +
        + +## Props and State + + +```javascript !! js +// Props (read-only) +function Greeting({ name, age }) { + return ( +
        +

        Hello, {name}

        +

        Age: {age}

        +
        + ); +} + +// Usage + + +// State (mutable) +function Counter() { + const [count, setCount] = useState(0); + + return ( +
        +

        Count: {count}

        + +
        + ); +} +``` +
        + +## Vue Basics + + +```javascript !! js +// Vue component + + + +``` + + +## When to Use Frameworks + + +```javascript !! js +// Use vanilla JS when: +// - Simple sites (mostly static) +// - Performance critical (no overhead) +// - Learning JavaScript basics + +// Use frameworks when: +// - Complex SPAs +// - Team development +// - Need state management +// - Rich UI interactions +``` + + +## Summary + +### Key Takeaways + +1. **React**: 元件式、hooks、JSX +2. **Vue**: 樣板式、響應式 +3. **State Management**: useState、Vuex +4. **Components**: 可重用的 UI 元件 +5. **Ecosystem**: 豐富的函式庫 + +### Comparison: Java vs JavaScript + +| Feature | Java | JavaScript | +|---------|------|------------| +| **UI** | Swing/JavaFX | React/Vue | +| **State** | Fields | useState | +| **Events** | Listeners | onClick/@click | +| **Components** | Classes | Functions | + +## What's Next? + +下一個:**Module 18: Common Pitfalls** - 學習 JavaScript 中要避免的錯誤! diff --git a/content/docs/java2js/module-18-pitfalls.mdx b/content/docs/java2js/module-18-pitfalls.mdx new file mode 100644 index 0000000..0e3d54c --- /dev/null +++ b/content/docs/java2js/module-18-pitfalls.mdx @@ -0,0 +1,182 @@ +--- +title: "Module 18: Common Pitfalls" +description: "Avoid common JavaScript mistakes and anti-patterns" +--- + +## Module 18: Common Pitfalls + +This module covers common mistakes Java developers make when transitioning to JavaScript. + +## Learning Objectives + +By the end of this module, you will: +✅ Know common JavaScript pitfalls +✅ Understand type coercion issues +✅ Learn async/await mistakes +✅ Know DOM manipulation issues +✅ Understand this-related problems +✅ Master debugging strategies + +## Type Coercion + + +```javascript !! js +// ❌ BAD: Using == instead of === +if (value == "0") { } // Matches 0, "0", false + +// ✅ GOOD: Use === +if (value === "0") { } + +// ❌ BAD: Forgetting typeof null +typeof null // "object" (historical bug) + +// ✅ GOOD: Check for null properly +if (value === null) { } + +// ❌ BAD: NaN comparison +if (NaN === NaN) { } // Never true! + +// ✅ GOOD: Use isNaN +if (Number.isNaN(value)) { } +``` + + +## Async Pitfalls + + +```javascript !! js +// ❌ BAD: Not awaiting promises +async function getData() { + const result = fetch("/api/data"); // Returns promise, not data! + console.log(result); // Promise {} +} + +// ✅ GOOD: Await the promise +async function getData() { + const result = await fetch("/api/data"); + const data = await result.json(); + console.log(data); +} + +// ❌ BAD: Forgetting try-catch +async function risky() { + const data = await fetchData(); // Unhandled rejection! +} + +// ✅ GOOD: Always handle errors +async function risky() { + try { + const data = await fetchData(); + return data; + } catch (error) { + console.error(error); + throw error; + } +} +``` + + +## This Context + + +```javascript !! js +// ❌ BAD: Losing this in callbacks +class Component { + constructor() { + this.data = null; + + button.addEventListener("click", this.handleClick); + } + + handleClick() { + console.log(this.data); // undefined! + } +} + +// ✅ GOOD: Bind or use arrow +class Component { + constructor() { + this.data = null; + + // Bind + this.handleClick = this.handleClick.bind(this); + button.addEventListener("click", this.handleClick); + + // Or arrow + button.addEventListener("click", () => this.handleClick()); + } +} +``` + + +## Loop Closures + + +```javascript !! js +// ❌ BAD: Closure in loop with var +for (var i = 0; i < 3; i++) { + setTimeout(() => console.log(i), 100); +} +// Output: 3, 3, 3 + +// ✅ GOOD: Use let +for (let i = 0; i < 3; i++) { + setTimeout(() => console.log(i), 100); +} +// Output: 0, 1, 2 +``` + + +## Array Methods + + +```javascript !! js +// ❌ BAD: forEach when need to break +numbers.forEach(num => { + if (num === 5) break; // Error! +}); + +// ✅ GOOD: Use for...of +for (const num of numbers) { + if (num === 5) break; +} + +// ❌ BAD: Forgetting return in map +const doubled = numbers.map(n => { + n * 2; // No return! +}); + +// ✅ GOOD: Always return +const doubled = numbers.map(n => n * 2); +``` + + +## Best Practices Summary + + +```javascript !! js +// 1. Always use === and !== +// 2. Use let/const, never var +// 3. Handle async errors +// 4. Understand this binding +// 5. Use for...of to break loops +// 6. Return in map/filter/reduce +// 7. Check for null/undefined +// 8. Don't modify while iterating +``` + + +## Summary + +### Key Takeaways + +1. **Equality**: Use ===, not == +2. **Async**: Always await and catch +3. **This**: Bind or use arrows +4. **Loops**: Use let, not var +5. **Arrays**: Return in callbacks +6. **Null**: Check explicitly + +## What's Next? + +Next: **Module 19: Real Projects** - Build practical applications! diff --git a/content/docs/java2js/module-18-pitfalls.zh-cn.mdx b/content/docs/java2js/module-18-pitfalls.zh-cn.mdx new file mode 100644 index 0000000..23e4d2d --- /dev/null +++ b/content/docs/java2js/module-18-pitfalls.zh-cn.mdx @@ -0,0 +1,182 @@ +--- +title: "Module 18: Common Pitfalls" +description: "Avoid common JavaScript mistakes and anti-patterns" +--- + +## Module 18: Common Pitfalls + +This module covers common mistakes Java developers make when transitioning to JavaScript. + +## Learning Objectives + +By the end of this module, you will: +✅ Know common JavaScript pitfalls +✅ Understand type coercion issues +✅ Learn async/await mistakes +✅ Know DOM manipulation issues +✅ Understand this-related problems +✅ Master debugging strategies + +## Type Coercion + + +```javascript !! js +// ❌ BAD: Using == instead of === +if (value == "0") { } // Matches 0, "0", false + +// ✅ GOOD: Use === +if (value === "0") { } + +// ❌ BAD: Forgetting typeof null +typeof null // "object" (historical bug) + +// ✅ GOOD: Check for null properly +if (value === null) { } + +// ❌ BAD: NaN comparison +if (NaN === NaN) { } // Never true! + +// ✅ GOOD: Use isNaN +if (Number.isNaN(value)) { } +``` + + +## Async Pitfalls + + +```javascript !! js +// ❌ BAD: Not awaiting promises +async function getData() { + const result = fetch("/api/data"); // Returns promise, not data! + console.log(result); // Promise {} +} + +// ✅ GOOD: Await the promise +async function getData() { + const result = await fetch("/api/data"); + const data = await result.json(); + console.log(data); +} + +// ❌ BAD: Forgetting try-catch +async function risky() { + const data = await fetchData(); // Unhandled rejection! +} + +// ✅ GOOD: Always handle errors +async function risky() { + try { + const data = await fetchData(); + return data; + } catch (error) { + console.error(error); + throw error; + } +} +``` + + +## This Context + + +```javascript !! js +// ❌ BAD: Losing this in callbacks +class Component { + constructor() { + this.data = null; + + button.addEventListener("click", this.handleClick); + } + + handleClick() { + console.log(this.data); // undefined! + } +} + +// ✅ GOOD: Bind or use arrow +class Component { + constructor() { + this.data = null; + + // Bind + this.handleClick = this.handleClick.bind(this); + button.addEventListener("click", this.handleClick); + + // Or arrow + button.addEventListener("click", () => this.handleClick()); + } +} +``` + + +## Loop Closures + + +```javascript !! js +// ❌ BAD: Closure in loop with var +for (var i = 0; i < 3; i++) { + setTimeout(() => console.log(i), 100); +} +// Output: 3, 3, 3 + +// ✅ GOOD: Use let +for (let i = 0; i < 3; i++) { + setTimeout(() => console.log(i), 100); +} +// Output: 0, 1, 2 +``` + + +## Array Methods + + +```javascript !! js +// ❌ BAD: forEach when need to break +numbers.forEach(num => { + if (num === 5) break; // Error! +}); + +// ✅ GOOD: Use for...of +for (const num of numbers) { + if (num === 5) break; +} + +// ❌ BAD: Forgetting return in map +const doubled = numbers.map(n => { + n * 2; // No return! +}); + +// ✅ GOOD: Always return +const doubled = numbers.map(n => n * 2); +``` + + +## Best Practices Summary + + +```javascript !! js +// 1. Always use === and !== +// 2. Use let/const, never var +// 3. Handle async errors +// 4. Understand this binding +// 5. Use for...of to break loops +// 6. Return in map/filter/reduce +// 7. Check for null/undefined +// 8. Don't modify while iterating +``` + + +## Summary + +### Key Takeaways + +1. **Equality**: Use ===, not == +2. **Async**: Always await and catch +3. **This**: Bind or use arrows +4. **Loops**: Use let, not var +5. **Arrays**: Return in callbacks +6. **Null**: Check explicitly + +## What's Next? + +Next: **Module 19: Real Projects** - Build practical applications! diff --git a/content/docs/java2js/module-18-pitfalls.zh-tw.mdx b/content/docs/java2js/module-18-pitfalls.zh-tw.mdx new file mode 100644 index 0000000..e68e959 --- /dev/null +++ b/content/docs/java2js/module-18-pitfalls.zh-tw.mdx @@ -0,0 +1,182 @@ +--- +title: "Module 18: Common Pitfalls" +description: "Avoid common JavaScript mistakes and anti-patterns" +--- + +## Module 18: Common Pitfalls + +本模組涵蓋 Java 開發者在轉向 JavaScript 時常犯的錯誤。 + +## Learning Objectives + +完成本模組後,您將: +✅ 知道常見的 JavaScript 陷阱 +✅ 了解型別轉換問題 +✅ 學習 async/await 錯誤 +✅ 知道 DOM 操作問題 +✅ 了解 this 相關問題 +✅ 掌握除錯策略 + +## Type Coercion + + +```javascript !! js +// ❌ BAD: Using == instead of === +if (value == "0") { } // Matches 0, "0", false + +// ✅ GOOD: Use === +if (value === "0") { } + +// ❌ BAD: Forgetting typeof null +typeof null // "object" (historical bug) + +// ✅ GOOD: Check for null properly +if (value === null) { } + +// ❌ BAD: NaN comparison +if (NaN === NaN) { } // Never true! + +// ✅ GOOD: Use isNaN +if (Number.isNaN(value)) { } +``` + + +## Async Pitfalls + + +```javascript !! js +// ❌ BAD: Not awaiting promises +async function getData() { + const result = fetch("/api/data"); // Returns promise, not data! + console.log(result); // Promise {} +} + +// ✅ GOOD: Await the promise +async function getData() { + const result = await fetch("/api/data"); + const data = await result.json(); + console.log(data); +} + +// ❌ BAD: Forgetting try-catch +async function risky() { + const data = await fetchData(); // Unhandled rejection! +} + +// ✅ GOOD: Always handle errors +async function risky() { + try { + const data = await fetchData(); + return data; + } catch (error) { + console.error(error); + throw error; + } +} +``` + + +## This Context + + +```javascript !! js +// ❌ BAD: Losing this in callbacks +class Component { + constructor() { + this.data = null; + + button.addEventListener("click", this.handleClick); + } + + handleClick() { + console.log(this.data); // undefined! + } +} + +// ✅ GOOD: Bind or use arrow +class Component { + constructor() { + this.data = null; + + // Bind + this.handleClick = this.handleClick.bind(this); + button.addEventListener("click", this.handleClick); + + // Or arrow + button.addEventListener("click", () => this.handleClick()); + } +} +``` + + +## Loop Closures + + +```javascript !! js +// ❌ BAD: Closure in loop with var +for (var i = 0; i < 3; i++) { + setTimeout(() => console.log(i), 100); +} +// Output: 3, 3, 3 + +// ✅ GOOD: Use let +for (let i = 0; i < 3; i++) { + setTimeout(() => console.log(i), 100); +} +// Output: 0, 1, 2 +``` + + +## Array Methods + + +```javascript !! js +// ❌ BAD: forEach when need to break +numbers.forEach(num => { + if (num === 5) break; // Error! +}); + +// ✅ GOOD: Use for...of +for (const num of numbers) { + if (num === 5) break; +} + +// ❌ BAD: Forgetting return in map +const doubled = numbers.map(n => { + n * 2; // No return! +}); + +// ✅ GOOD: Always return +const doubled = numbers.map(n => n * 2); +``` + + +## Best Practices Summary + + +```javascript !! js +// 1. Always use === and !== +// 2. Use let/const, never var +// 3. Handle async errors +// 4. Understand this binding +// 5. Use for...of to break loops +// 6. Return in map/filter/reduce +// 7. Check for null/undefined +// 8. Don't modify while iterating +``` + + +## Summary + +### Key Takeaways + +1. **Equality**: 使用 ===,而非 == +2. **Async**: 始終 await 和 catch +3. **This**: 綁定或使用箭頭函式 +4. **Loops**: 使用 let,而非 var +5. **Arrays**: 在回呼函式中回傳 +6. **Null**: 明確檢查 + +## What's Next? + +下一個:**Module 19: Real Projects** - 建構實用的應用程式! diff --git a/content/docs/java2js/module-19-real-projects.mdx b/content/docs/java2js/module-19-real-projects.mdx new file mode 100644 index 0000000..9e4a20f --- /dev/null +++ b/content/docs/java2js/module-19-real-projects.mdx @@ -0,0 +1,277 @@ +--- +title: "Module 19: Real Projects" +description: "Build practical JavaScript projects applying all learned concepts" +--- + +## Module 19: Real Projects + +Apply your JavaScript knowledge by building real-world projects. + +## Project Ideas + +### Project 1: Todo App + + +```javascript !! js +// Features: +// - Add, edit, delete todos +// - Mark as complete +// - Filter by status +// - Persist to localStorage + +class TodoApp { + constructor() { + this.todos = this.loadTodos(); + this.filter = "all"; + this.render(); + } + + addTodo(text) { + this.todos.push({ + id: Date.now(), + text, + completed: false + }); + this.saveTodos(); + this.render(); + } + + toggleTodo(id) { + const todo = this.todos.find(t => t.id === id); + if (todo) { + todo.completed = !todo.completed; + this.saveTodos(); + this.render(); + } + } + + deleteTodo(id) { + this.todos = this.todos.filter(t => t.id !== id); + this.saveTodos(); + this.render(); + } + + saveTodos() { + localStorage.setItem("todos", JSON.stringify(this.todos)); + } + + loadTodos() { + const stored = localStorage.getItem("todos"); + return stored ? JSON.parse(stored) : []; + } + + render() { + const filtered = this.todos.filter(todo => { + if (this.filter === "active") return !todo.completed; + if (this.filter === "completed") return todo.completed; + return true; + }); + + // Render filtered todos + } +} +``` + + +### Project 2: Weather App + + +```javascript !! js +class WeatherApp { + constructor(apiKey) { + this.apiKey = apiKey; + this.baseUrl = "https://api.openweathermap.org/data/2.5"; + } + + async getWeather(city) { + try { + const response = await fetch( + `${this.baseUrl}/weather?q=${city}&appid=${this.apiKey}&units=metric` + ); + + if (!response.ok) { + throw new Error(`HTTP ${response.status}`); + } + + return await response.json(); + } catch (error) { + console.error("Failed to fetch weather:", error); + throw error; + } + } + + async getForecast(city) { + const response = await fetch( + `${this.baseUrl}/forecast?q=${city}&appid=${this.apiKey}&units=metric` + ); + return await response.json(); + } + + displayWeather(data) { + return { + city: data.name, + temp: data.main.temp, + description: data.weather[0].description, + icon: data.weather[0].icon + }; + } +} +``` + + +### Project 3: Chat Application + + +```javascript !! js +class ChatApp { + constructor(wsUrl) { + this.ws = new WebSocket(wsUrl); + this.messages = []; + this.setupWebSocket(); + } + + setupWebSocket() { + this.ws.onopen = () => { + console.log("Connected to chat"); + }; + + this.ws.onmessage = (event) => { + const message = JSON.parse(event.data); + this.messages.push(message); + this.renderMessages(); + }; + + this.ws.onerror = (error) => { + console.error("WebSocket error:", error); + }; + + this.ws.onclose = () => { + console.log("Disconnected from chat"); + }; + } + + sendMessage(text) { + const message = { + type: "message", + text, + timestamp: Date.now(), + user: this.currentUser + }; + + this.ws.send(JSON.stringify(message)); + } + + renderMessages() { + // Render messages array + } +} +``` + + +## Project Checklist + + +```markdown +For each project, ensure: +- [ ] Clean code structure +- [ ] Error handling +- [ ] Loading states +- [ ] Input validation +- [ ] Responsive design +- [ ] Accessibility +- [ ] Performance optimization +- [ ] Testing +- [ ] Documentation +``` + + +## Best Practices + + +```javascript !! js +// 1. Organize code by modules +// services/api.js +export class APIClient { + async get(endpoint) { + // ... + } +} + +// 2. Use constants +const CONFIG = { + API_URL: "https://api.example.com", + MAX_RETRIES: 3 +}; + +// 3. Error boundaries (React) +class ErrorBoundary extends React.Component { + state = { hasError: false }; + + static getDerivedStateFromError(error) { + return { hasError: true }; + } + + render() { + if (this.state.hasError) { + return

        Something went wrong.

        ; + } + return this.props.children; + } +} + +// 4. Validation +function validateEmail(email) { + const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + return regex.test(email); +} +``` +
        + +## Summary + +### Key Takeaways + +1. **Build Real Projects**: Apply knowledge +2. **Clean Code**: Organize well +3. **Error Handling**: Always handle errors +4. **Testing**: Write tests +5. **Documentation**: Comment code +6. **Iterate**: Start simple, improve + +### Congratulations! + +You've completed the Java → JavaScript learning path! You now have: +- ✅ Solid JavaScript fundamentals +- ✅ Modern ES6+ syntax knowledge +- ✅ Async/await mastery +- ✅ DOM manipulation skills +- ✅ Understanding of frameworks +- ✅ Best practices knowledge + +Keep practicing and building projects to become a proficient JavaScript developer! + +## Learning Path Complete + +**Modules Completed:** +- Module 0: JavaScript Introduction +- Module 1: Basic Syntax +- Module 2: Control Flow +- Module 3-4: Functions +- Module 5: Arrays and Collections +- Module 6-7: Objects and Classes +- Module 8: Prototypes +- Module 9: This Context +- Module 10-11: Async Programming +- Module 12-13: DOM and Events +- Module 14: Modules +- Module 15: Tooling +- Module 16: Testing +- Module 17: Frameworks +- Module 18: Pitfalls +- Module 19: Real Projects + +**Next Steps:** +- Build more projects +- Learn advanced topics +- Explore specialized libraries +- Join the JavaScript community diff --git a/content/docs/java2js/module-19-real-projects.zh-cn.mdx b/content/docs/java2js/module-19-real-projects.zh-cn.mdx new file mode 100644 index 0000000..66dd7b0 --- /dev/null +++ b/content/docs/java2js/module-19-real-projects.zh-cn.mdx @@ -0,0 +1,277 @@ +--- +title: "Module 19: Real Projects" +description: "Build practical JavaScript projects applying all learned concepts" +--- + +## Module 19: Real Projects + +Apply your JavaScript knowledge by building real-world projects. + +## Project Ideas + +### Project 1: Todo App + + +```javascript !! js +// Features: +// - Add, edit, delete todos +// - Mark as complete +// - Filter by status +// - Persist to localStorage + +class TodoApp { + constructor() { + this.todos = this.loadTodos(); + this.filter = "all"; + this.render(); + } + + addTodo(text) { + this.todos.push({ + id: Date.now(), + text, + completed: false + }); + this.saveTodos(); + this.render(); + } + + toggleTodo(id) { + const todo = this.todos.find(t => t.id === id); + if (todo) { + todo.completed = !todo.completed; + this.saveTodos(); + this.render(); + } + } + + deleteTodo(id) { + this.todos = this.todos.filter(t => t.id !== id); + this.saveTodos(); + this.render(); + } + + saveTodos() { + localStorage.setItem("todos", JSON.stringify(this.todos)); + } + + loadTodos() { + const stored = localStorage.getItem("todos"); + return stored ? JSON.parse(stored) : []; + } + + render() { + const filtered = this.todos.filter(todo => { + if (this.filter === "active") return !todo.completed; + if (this.filter === "completed") return todo.completed; + return true; + }); + + // Render filtered todos + } +} +``` + + +### Project 2: Weather App + + +```javascript !! js +class WeatherApp { + constructor(apiKey) { + this.apiKey = apiKey; + this.baseUrl = "https://api.openweathermap.org/data/2.5"; + } + + async getWeather(city) { + try { + const response = await fetch( + `${this.baseUrl}/weather?q=${city}&appid=${this.apiKey}&units=metric` + ); + + if (!response.ok) { + throw new Error(`HTTP ${response.status}`); + } + + return await response.json(); + } catch (error) { + console.error("Failed to fetch weather:", error); + throw error; + } + } + + async getForecast(city) { + const response = await fetch( + `${this.baseUrl}/forecast?q=${city}&appid=${this.apiKey}&units=metric` + ); + return await response.json(); + } + + displayWeather(data) { + return { + city: data.name, + temp: data.main.temp, + description: data.weather[0].description, + icon: data.weather[0].icon + }; + } +} +``` + + +### Project 3: Chat Application + + +```javascript !! js +class ChatApp { + constructor(wsUrl) { + this.ws = new WebSocket(wsUrl); + this.messages = []; + this.setupWebSocket(); + } + + setupWebSocket() { + this.ws.onopen = () => { + console.log("Connected to chat"); + }; + + this.ws.onmessage = (event) => { + const message = JSON.parse(event.data); + this.messages.push(message); + this.renderMessages(); + }; + + this.ws.onerror = (error) => { + console.error("WebSocket error:", error); + }; + + this.ws.onclose = () => { + console.log("Disconnected from chat"); + }; + } + + sendMessage(text) { + const message = { + type: "message", + text, + timestamp: Date.now(), + user: this.currentUser + }; + + this.ws.send(JSON.stringify(message)); + } + + renderMessages() { + // Render messages array + } +} +``` + + +## Project Checklist + + +```markdown +For each project, ensure: +- [ ] Clean code structure +- [ ] Error handling +- [ ] Loading states +- [ ] Input validation +- [ ] Responsive design +- [ ] Accessibility +- [ ] Performance optimization +- [ ] Testing +- [ ] Documentation +``` + + +## Best Practices + + +```javascript !! js +// 1. Organize code by modules +// services/api.js +export class APIClient { + async get(endpoint) { + // ... + } +} + +// 2. Use constants +const CONFIG = { + API_URL: "https://api.example.com", + MAX_RETRIES: 3 +}; + +// 3. Error boundaries (React) +class ErrorBoundary extends React.Component { + state = { hasError: false }; + + static getDerivedStateFromError(error) { + return { hasError: true }; + } + + render() { + if (this.state.hasError) { + return

        Something went wrong.

        ; + } + return this.props.children; + } +} + +// 4. Validation +function validateEmail(email) { + const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + return regex.test(email); +} +``` +
        + +## Summary + +### Key Takeaways + +1. **Build Real Projects**: Apply knowledge +2. **Clean Code**: Organize well +3. **Error Handling**: Always handle errors +4. **Testing**: Write tests +5. **Documentation**: Comment code +6. **Iterate**: Start simple, improve + +### Congratulations! + +You've completed the Java → JavaScript learning path! You now have: +- ✅ Solid JavaScript fundamentals +- ✅ Modern ES6+ syntax knowledge +- ✅ Async/await mastery +- ✅ DOM manipulation skills +- ✅ Understanding of frameworks +- ✅ Best practices knowledge + +Keep practicing and building projects to become a proficient JavaScript developer! + +## Learning Path Complete + +**Modules Completed:** +- Module 0: JavaScript Introduction +- Module 1: Basic Syntax +- Module 2: Control Flow +- Module 3-4: Functions +- Module 5: Arrays and Collections +- Module 6-7: Objects and Classes +- Module 8: Prototypes +- Module 9: This Context +- Module 10-11: Async Programming +- Module 12-13: DOM and Events +- Module 14: Modules +- Module 15: Tooling +- Module 16: Testing +- Module 17: Frameworks +- Module 18: Pitfalls +- Module 19: Real Projects + +**Next Steps:** +- Build more projects +- Learn advanced topics +- Explore specialized libraries +- Join the JavaScript community diff --git a/content/docs/java2js/module-19-real-projects.zh-tw.mdx b/content/docs/java2js/module-19-real-projects.zh-tw.mdx new file mode 100644 index 0000000..f472233 --- /dev/null +++ b/content/docs/java2js/module-19-real-projects.zh-tw.mdx @@ -0,0 +1,277 @@ +--- +title: "Module 19: Real Projects" +description: "Build practical JavaScript projects applying all learned concepts" +--- + +## Module 19: Real Projects + +透過建構真實世界的專案來應用您的 JavaScript 知識。 + +## Project Ideas + +### Project 1: Todo App + + +```javascript !! js +// Features: +// - Add, edit, delete todos +// - Mark as complete +// - Filter by status +// - Persist to localStorage + +class TodoApp { + constructor() { + this.todos = this.loadTodos(); + this.filter = "all"; + this.render(); + } + + addTodo(text) { + this.todos.push({ + id: Date.now(), + text, + completed: false + }); + this.saveTodos(); + this.render(); + } + + toggleTodo(id) { + const todo = this.todos.find(t => t.id === id); + if (todo) { + todo.completed = !todo.completed; + this.saveTodos(); + this.render(); + } + } + + deleteTodo(id) { + this.todos = this.todos.filter(t => t.id !== id); + this.saveTodos(); + this.render(); + } + + saveTodos() { + localStorage.setItem("todos", JSON.stringify(this.todos)); + } + + loadTodos() { + const stored = localStorage.getItem("todos"); + return stored ? JSON.parse(stored) : []; + } + + render() { + const filtered = this.todos.filter(todo => { + if (this.filter === "active") return !todo.completed; + if (this.filter === "completed") return todo.completed; + return true; + }); + + // Render filtered todos + } +} +``` + + +### Project 2: Weather App + + +```javascript !! js +class WeatherApp { + constructor(apiKey) { + this.apiKey = apiKey; + this.baseUrl = "https://api.openweathermap.org/data/2.5"; + } + + async getWeather(city) { + try { + const response = await fetch( + `${this.baseUrl}/weather?q=${city}&appid=${this.apiKey}&units=metric` + ); + + if (!response.ok) { + throw new Error(`HTTP ${response.status}`); + } + + return await response.json(); + } catch (error) { + console.error("Failed to fetch weather:", error); + throw error; + } + } + + async getForecast(city) { + const response = await fetch( + `${this.baseUrl}/forecast?q=${city}&appid=${this.apiKey}&units=metric` + ); + return await response.json(); + } + + displayWeather(data) { + return { + city: data.name, + temp: data.main.temp, + description: data.weather[0].description, + icon: data.weather[0].icon + }; + } +} +``` + + +### Project 3: Chat Application + + +```javascript !! js +class ChatApp { + constructor(wsUrl) { + this.ws = new WebSocket(wsUrl); + this.messages = []; + this.setupWebSocket(); + } + + setupWebSocket() { + this.ws.onopen = () => { + console.log("Connected to chat"); + }; + + this.ws.onmessage = (event) => { + const message = JSON.parse(event.data); + this.messages.push(message); + this.renderMessages(); + }; + + this.ws.onerror = (error) => { + console.error("WebSocket error:", error); + }; + + this.ws.onclose = () => { + console.log("Disconnected from chat"); + }; + } + + sendMessage(text) { + const message = { + type: "message", + text, + timestamp: Date.now(), + user: this.currentUser + }; + + this.ws.send(JSON.stringify(message)); + } + + renderMessages() { + // Render messages array + } +} +``` + + +## Project Checklist + + +```markdown +For each project, ensure: +- [ ] Clean code structure +- [ ] Error handling +- [ ] Loading states +- [ ] Input validation +- [ ] Responsive design +- [ ] Accessibility +- [ ] Performance optimization +- [ ] Testing +- [ ] Documentation +``` + + +## Best Practices + + +```javascript !! js +// 1. Organize code by modules +// services/api.js +export class APIClient { + async get(endpoint) { + // ... + } +} + +// 2. Use constants +const CONFIG = { + API_URL: "https://api.example.com", + MAX_RETRIES: 3 +}; + +// 3. Error boundaries (React) +class ErrorBoundary extends React.Component { + state = { hasError: false }; + + static getDerivedStateFromError(error) { + return { hasError: true }; + } + + render() { + if (this.state.hasError) { + return

        Something went wrong.

        ; + } + return this.props.children; + } +} + +// 4. Validation +function validateEmail(email) { + const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + return regex.test(email); +} +``` +
        + +## Summary + +### Key Takeaways + +1. **Build Real Projects**: 應用知識 +2. **Clean Code**: 良好組織 +3. **Error Handling**: 始終處理錯誤 +4. **Testing**: 撰寫測試 +5. **Documentation**: 為程式碼加註解 +6. **Iterate**: 從簡單開始,逐步改進 + +### Congratulations! + +您已完成 Java → JavaScript 學習路徑!您現在擁有: +- ✅ 紮實的 JavaScript 基礎 +- ✅ 現代 ES6+ 語法知識 +- ✅ Async/await 掌握能力 +- ✅ DOM 操作技能 +- ✅ 框架理解 +- ✅ 最佳實踐知識 + +繼續練習和建構專案,成為熟練的 JavaScript 開發者! + +## Learning Path Complete + +**Modules Completed:** +- Module 0: JavaScript Introduction +- Module 1: Basic Syntax +- Module 2: Control Flow +- Module 3-4: Functions +- Module 5: Arrays and Collections +- Module 6-7: Objects and Classes +- Module 8: Prototypes +- Module 9: This Context +- Module 10-11: Async Programming +- Module 12-13: DOM and Events +- Module 14: Modules +- Module 15: Tooling +- Module 16: Testing +- Module 17: Frameworks +- Module 18: Pitfalls +- Module 19: Real Projects + +**Next Steps:** +- 建構更多專案 +- 學習進階主題 +- 探索專門的函式庫 +- 加入 JavaScript 社群 From b47c9ca8f3697ceea8bdf9fa369f6e6a9af7e62b Mon Sep 17 00:00:00 2001 From: sunguangbiao <2991552132@qq.com> Date: Sat, 27 Dec 2025 20:44:53 +0800 Subject: [PATCH 6/7] feat: add Java to Go learning path with 19 comprehensive modules covering syntax, object-oriented programming, concurrency, web development, and best practices, including translations in Chinese and Taiwanese --- components/header.tsx | 9 +- content/docs/java2go/meta.json | 4 + .../java2go/module-00-go-introduction.mdx | 417 +++++ .../module-00-go-introduction.zh-cn.mdx | 417 +++++ .../module-00-go-introduction.zh-tw.mdx | 417 +++++ .../java2go/module-01-syntax-comparison.mdx | 897 ++++++++++ .../module-01-syntax-comparison.zh-cn.mdx | 899 ++++++++++ .../module-01-syntax-comparison.zh-tw.mdx | 888 ++++++++++ content/docs/java2go/module-02-oop-to-go.mdx | 1288 ++++++++++++++ .../java2go/module-02-oop-to-go.zh-cn.mdx | 1288 ++++++++++++++ .../java2go/module-02-oop-to-go.zh-tw.mdx | 1288 ++++++++++++++ .../docs/java2go/module-03-package-system.mdx | 994 +++++++++++ .../module-03-package-system.zh-cn.mdx | 993 +++++++++++ .../module-03-package-system.zh-tw.mdx | 993 +++++++++++ .../module-04-interfaces-composition.mdx | 1124 +++++++++++++ ...module-04-interfaces-composition.zh-cn.mdx | 845 ++++++++++ ...module-04-interfaces-composition.zh-tw.mdx | 500 ++++++ .../docs/java2go/module-05-error-handling.mdx | 798 +++++++++ .../module-05-error-handling.zh-cn.mdx | 448 +++++ .../module-05-error-handling.zh-tw.mdx | 448 +++++ .../java2go/module-06-goroutines-threads.mdx | 1216 ++++++++++++++ .../module-06-goroutines-threads.zh-cn.mdx | 1216 ++++++++++++++ .../module-06-goroutines-threads.zh-tw.mdx | 1216 ++++++++++++++ .../java2go/module-07-channels-select.mdx | 1494 +++++++++++++++++ .../module-07-channels-select.zh-cn.mdx | 727 ++++++++ .../module-07-channels-select.zh-tw.mdx | 625 +++++++ .../java2go/module-08-web-development.mdx | 883 ++++++++++ .../module-08-web-development.zh-cn.mdx | 355 ++++ .../module-08-web-development.zh-tw.mdx | 278 +++ content/docs/java2go/module-09-testing.mdx | 965 +++++++++++ .../docs/java2go/module-09-testing.zh-cn.mdx | 540 ++++++ .../docs/java2go/module-09-testing.zh-tw.mdx | 346 ++++ .../java2go/module-10-common-pitfalls.mdx | 1013 +++++++++++ .../module-10-common-pitfalls.zh-cn.mdx | 1053 ++++++++++++ .../module-10-common-pitfalls.zh-tw.mdx | 1053 ++++++++++++ .../docs/java2go/module-11-idiomatic-go.mdx | 1287 ++++++++++++++ .../java2go/module-11-idiomatic-go.zh-cn.mdx | 1287 ++++++++++++++ .../java2go/module-11-idiomatic-go.zh-tw.mdx | 1287 ++++++++++++++ .../docs/java2go/module-12-performance.mdx | 917 ++++++++++ .../java2go/module-12-performance.zh-cn.mdx | 917 ++++++++++ .../java2go/module-12-performance.zh-tw.mdx | 917 ++++++++++ content/docs/java2go/module-13-deployment.mdx | 808 +++++++++ .../java2go/module-13-deployment.zh-cn.mdx | 808 +++++++++ .../java2go/module-13-deployment.zh-tw.mdx | 808 +++++++++ .../docs/java2go/module-14-build-tools.mdx | 814 +++++++++ .../java2go/module-14-build-tools.zh-cn.mdx | 518 ++++++ .../java2go/module-14-build-tools.zh-tw.mdx | 176 ++ content/docs/java2go/module-15-ecosystem.mdx | 863 ++++++++++ .../java2go/module-15-ecosystem.zh-cn.mdx | 238 +++ .../java2go/module-15-ecosystem.zh-tw.mdx | 238 +++ content/docs/java2go/module-16-database.mdx | 452 +++++ .../docs/java2go/module-16-database.zh-cn.mdx | 244 +++ .../docs/java2go/module-16-database.zh-tw.mdx | 244 +++ .../docs/java2go/module-17-microservices.mdx | 550 ++++++ .../java2go/module-17-microservices.zh-cn.mdx | 230 +++ .../java2go/module-17-microservices.zh-tw.mdx | 112 ++ content/docs/java2go/module-18-real-world.mdx | 515 ++++++ .../java2go/module-18-real-world.zh-cn.mdx | 199 +++ .../java2go/module-18-real-world.zh-tw.mdx | 73 + .../docs/java2go/module-19-best-practices.mdx | 615 +++++++ .../module-19-best-practices.zh-cn.mdx | 461 +++++ .../module-19-best-practices.zh-tw.mdx | 146 ++ 62 files changed, 43658 insertions(+), 1 deletion(-) create mode 100644 content/docs/java2go/meta.json create mode 100644 content/docs/java2go/module-00-go-introduction.mdx create mode 100644 content/docs/java2go/module-00-go-introduction.zh-cn.mdx create mode 100644 content/docs/java2go/module-00-go-introduction.zh-tw.mdx create mode 100644 content/docs/java2go/module-01-syntax-comparison.mdx create mode 100644 content/docs/java2go/module-01-syntax-comparison.zh-cn.mdx create mode 100644 content/docs/java2go/module-01-syntax-comparison.zh-tw.mdx create mode 100644 content/docs/java2go/module-02-oop-to-go.mdx create mode 100644 content/docs/java2go/module-02-oop-to-go.zh-cn.mdx create mode 100644 content/docs/java2go/module-02-oop-to-go.zh-tw.mdx create mode 100644 content/docs/java2go/module-03-package-system.mdx create mode 100644 content/docs/java2go/module-03-package-system.zh-cn.mdx create mode 100644 content/docs/java2go/module-03-package-system.zh-tw.mdx create mode 100644 content/docs/java2go/module-04-interfaces-composition.mdx create mode 100644 content/docs/java2go/module-04-interfaces-composition.zh-cn.mdx create mode 100644 content/docs/java2go/module-04-interfaces-composition.zh-tw.mdx create mode 100644 content/docs/java2go/module-05-error-handling.mdx create mode 100644 content/docs/java2go/module-05-error-handling.zh-cn.mdx create mode 100644 content/docs/java2go/module-05-error-handling.zh-tw.mdx create mode 100644 content/docs/java2go/module-06-goroutines-threads.mdx create mode 100644 content/docs/java2go/module-06-goroutines-threads.zh-cn.mdx create mode 100644 content/docs/java2go/module-06-goroutines-threads.zh-tw.mdx create mode 100644 content/docs/java2go/module-07-channels-select.mdx create mode 100644 content/docs/java2go/module-07-channels-select.zh-cn.mdx create mode 100644 content/docs/java2go/module-07-channels-select.zh-tw.mdx create mode 100644 content/docs/java2go/module-08-web-development.mdx create mode 100644 content/docs/java2go/module-08-web-development.zh-cn.mdx create mode 100644 content/docs/java2go/module-08-web-development.zh-tw.mdx create mode 100644 content/docs/java2go/module-09-testing.mdx create mode 100644 content/docs/java2go/module-09-testing.zh-cn.mdx create mode 100644 content/docs/java2go/module-09-testing.zh-tw.mdx create mode 100644 content/docs/java2go/module-10-common-pitfalls.mdx create mode 100644 content/docs/java2go/module-10-common-pitfalls.zh-cn.mdx create mode 100644 content/docs/java2go/module-10-common-pitfalls.zh-tw.mdx create mode 100644 content/docs/java2go/module-11-idiomatic-go.mdx create mode 100644 content/docs/java2go/module-11-idiomatic-go.zh-cn.mdx create mode 100644 content/docs/java2go/module-11-idiomatic-go.zh-tw.mdx create mode 100644 content/docs/java2go/module-12-performance.mdx create mode 100644 content/docs/java2go/module-12-performance.zh-cn.mdx create mode 100644 content/docs/java2go/module-12-performance.zh-tw.mdx create mode 100644 content/docs/java2go/module-13-deployment.mdx create mode 100644 content/docs/java2go/module-13-deployment.zh-cn.mdx create mode 100644 content/docs/java2go/module-13-deployment.zh-tw.mdx create mode 100644 content/docs/java2go/module-14-build-tools.mdx create mode 100644 content/docs/java2go/module-14-build-tools.zh-cn.mdx create mode 100644 content/docs/java2go/module-14-build-tools.zh-tw.mdx create mode 100644 content/docs/java2go/module-15-ecosystem.mdx create mode 100644 content/docs/java2go/module-15-ecosystem.zh-cn.mdx create mode 100644 content/docs/java2go/module-15-ecosystem.zh-tw.mdx create mode 100644 content/docs/java2go/module-16-database.mdx create mode 100644 content/docs/java2go/module-16-database.zh-cn.mdx create mode 100644 content/docs/java2go/module-16-database.zh-tw.mdx create mode 100644 content/docs/java2go/module-17-microservices.mdx create mode 100644 content/docs/java2go/module-17-microservices.zh-cn.mdx create mode 100644 content/docs/java2go/module-17-microservices.zh-tw.mdx create mode 100644 content/docs/java2go/module-18-real-world.mdx create mode 100644 content/docs/java2go/module-18-real-world.zh-cn.mdx create mode 100644 content/docs/java2go/module-18-real-world.zh-tw.mdx create mode 100644 content/docs/java2go/module-19-best-practices.mdx create mode 100644 content/docs/java2go/module-19-best-practices.zh-cn.mdx create mode 100644 content/docs/java2go/module-19-best-practices.zh-tw.mdx diff --git a/components/header.tsx b/components/header.tsx index 839eea8..4f07934 100644 --- a/components/header.tsx +++ b/components/header.tsx @@ -122,7 +122,6 @@ const SOURCE_LANGUAGES = [ name: 'Java', icon: '☕', gradient: 'from-red-500 to-rose-500', - path: 'java2js', status: 'completed' as const, targets: [ { @@ -133,6 +132,14 @@ const SOURCE_LANGUAGES = [ path: 'java2js', status: 'completed' as const, }, + { + id: 'go', + name: 'Go', + icon: '🐹', + gradient: 'from-cyan-500 to-blue-500', + path: 'java2go', + status: 'completed' as const, + }, ] } // 未来可以添加其他源语言 diff --git a/content/docs/java2go/meta.json b/content/docs/java2go/meta.json new file mode 100644 index 0000000..58a35cc --- /dev/null +++ b/content/docs/java2go/meta.json @@ -0,0 +1,4 @@ +{ + "title": "Java → Go", + "root": true +} diff --git a/content/docs/java2go/module-00-go-introduction.mdx b/content/docs/java2go/module-00-go-introduction.mdx new file mode 100644 index 0000000..c245649 --- /dev/null +++ b/content/docs/java2go/module-00-go-introduction.mdx @@ -0,0 +1,417 @@ +--- +title: "Module 00: Go Language Introduction for Java Developers" +--- + +## Why Go? The Java Developer's Perspective + +As a Java developer, you're likely familiar with the JVM ecosystem, Spring framework, object-oriented programming, and managing complex class hierarchies. Go (Golang) offers a refreshing alternative that prioritizes simplicity, performance, and modern development practices. + +### Go vs Java: A High-Level Comparison + +| Aspect | Java | Go | +|--------|------|-----| +| **Paradigm** | Object-Oriented (classes, inheritance) | Multi-paradigm (no classes, uses structs and interfaces) | +| **Execution** | JVM (compiled to bytecode) | Native binary compilation | +| **Startup Time** | Slow (JVM warm-up) | Instant (native binary) | +| **Memory Footprint** | High (JVM overhead) | Low (single binary) | +| **Concurrency** | Threads (heavyweight) | Goroutines (lightweight) | +| **Type System** | Generic types (since Java 5) | Type inference, no generics until recently | +| **Error Handling** | Exceptions (try-catch) | Explicit error returns | +| **Dependency Management** | Maven, Gradle | Go Modules | +| **Learning Curve** | Steep (complex ecosystem) | Gentle (minimal language) | +| **Compilation Speed** | Slow | Extremely fast | +| **Typical Use Cases** | Enterprise apps, Android, Big Data | Microservices, Cloud-native, CLI tools | + +## Go Language History and Design Philosophy + +Go was created at Google in 2007 by Robert Griesemer, Rob Pike, and Ken Thompson (who also created Unix). It was designed to address the frustrations with existing languages like C++, Java, and Python in large-scale software development. + +### Core Design Principles + +1. **Simplicity Over Complexity** + - Only 25 keywords (vs Java's 50+) + - No classes, no inheritance, no constructors + - Minimal syntax that can be learned in days + +2. **Concurrency as a First-Class Citizen** + - Built-in goroutines (lightweight threads) + - Channels for communication + - No callback hell or complex async patterns + +3. **Fast Compilation** + - Compiles in seconds, not minutes + - Enables rapid development cycles + - No separate build step needed during development + +4. **Performance with Safety** + - Statically typed with type inference + - Memory safe (garbage collected) + - Near-C performance + +5. **Pragmatic over Dogmatic** + - "Don't communicate by sharing memory; share memory by communicating" + - Explicit is better than implicit + - Less is exponentially more + +## When to Choose Go Over Java + +### Perfect Use Cases for Go: + +✅ **Microservices & APIs** +- Lightweight services +- Fast startup and scaling +- Simple deployment (single binary) + +✅ **Cloud-Native Applications** +- Kubernetes (written in Go) +- Docker (written in Go) +- Infrastructure tools + +✅ **High-Performance Network Services** +- Proxy servers +- Load balancers +- Real-time data processing + +✅ **DevOps & Tooling** +- CLI applications +- Automation scripts +- Developer tools + +✅ **Systems Programming** +- File systems +- Database engines +- Network protocols + +### When Java Might Be Better: + +✅ **Large Enterprise Applications** +- Complex business logic +- Mature ecosystem (Spring, Hibernate) +- Enterprise support and tooling + +✅ **Android Development** +- Native Android apps +- Kotlin interoperability + +✅ **Big Data Processing** +- Hadoop, Spark ecosystem +- Mature data processing frameworks + +## Real-World Success Stories + +Many companies have successfully migrated from Java to Go for specific use cases: + +**Google** +- Developed Go for internal use +- Migrated many services from Java/C++ to Go +- Better performance and simpler code + +**Uber** +- Migrated many microservices from Java to Go +- 10x reduction in memory footprint +- Improved startup time from seconds to milliseconds + +**Twitch** +- Replaced Java services with Go +- Handle millions of concurrent connections +- Simpler deployment and management + +**Dropbox** +- Migrated from Python/Java to Go +- Improved performance significantly +- Reduced infrastructure costs + +**ByteDance (TikTok)** +- Heavy Go adoption +- Built their own Go framework (CloudWeGo) +- Handle billions of users + +## The Java to Go Learning Journey + +### What Will Feel Familiar: +✅ Statically typed +✅ Garbage collected +✅ Similar control flow structures (if, for, switch) +✅ Package imports +✅ Interface-based polymorphism +✅ Compiled language + +### What Will Be Different: +❌ No classes (use structs) +❌ No inheritance (use composition) +❌ No constructors (use factory functions) +❌ No exceptions (use error returns) +❌ No generics in old Go code (Go 1.18+ has generics) +❌ No while loop (only for) +❌ No implicit type conversions + +### What You'll Gain: +✅ Simpler syntax (write less code) +✅ Faster compilation +✅ Better concurrency model +✅ Easier deployment (single binary) +✅ Smaller memory footprint +✅ Faster startup time + +## Development Environment Setup + +### Installing Go + +**macOS (Homebrew):** +```bash +brew install go +``` + +**Ubuntu/Debian:** +```bash +sudo apt update +sudo apt install golang-go +``` + +**Windows:** +Download from [golang.org/dl/](https://golang.org/dl/) + +### Verify Installation + +```bash +go version +# Output: go version go1.21.x darwin/amd64 (or similar) +``` + +### Setting Up Your Workspace + +```bash +# Create a project directory +mkdir ~/go-projects +cd ~/go-projects + +# Initialize a new module +go mod init github.com/yourusername/yourproject + +# This creates a go.mod file +``` + +### Recommended Tools + +**VS Code Extensions:** +- Go (Google's official extension) +- Code runner +- Error lens + +**Alternative IDEs:** +- GoLand (JetBrains, paid) +- Vim/Neovim with vim-go +- Go plugin for IntelliJ IDEA + +## Your First Go Program + +Let's compare a simple "Hello, World!" program in Java vs Go: + + +```java !! java +// Java: Hello World +public class HelloWorld { + public static void main(String[] args) { + System.out.println("Hello, World from Java!"); + } +} + +// Compile: javac HelloWorld.java +// Run: java HelloWorld +``` + +```go !! go +// Go: Hello World +package main + +import "fmt" + +func main() { + fmt.Println("Hello, World from Go!") +} + +// Run: go run main.go +// Or build: go build main.go && ./main +``` + + +### Key Observations: + +1. **No Class Required**: Go doesn't require a class wrapper +2. **Simpler Entry Point**: Just `func main()`, not `public static void main` +3. **Semicolons Optional**: Go infers them (but you can use them) +4. **Package Declaration**: Every file starts with `package` declaration +5. **Single File**: No need to match filename to class name + +## Understanding Go's Build Process + +### Java Build Process: +``` +Source (.java) → Compiler → Bytecode (.class) → JVM → Machine Code + ↓ + Slow compilation, but portable bytecode +``` + +### Go Build Process: +``` +Source (.go) → Compiler → Machine Code (Binary) + ↓ + Fast compilation, platform-specific binary +``` + +### Running Go Code + +**Development (fast iteration):** +```bash +go run main.go +``` + +**Production (create executable):** +```bash +go build -o myapp main.go +./myapp # or myapp.exe on Windows +``` + +**Build for different platforms:** +```bash +# Build for Linux +GOOS=linux go build -o myapp-linux main.go + +# Build for Windows +GOOS=windows go build -o myapp.exe main.go + +# Build for macOS ARM (Apple Silicon) +GOOS=darwin GOARCH=arm64 go build -o myapp-mac-arm main.go +``` + +## Go Module System + +Go 1.11+ introduced Go Modules for dependency management: + +```bash +# Initialize a new module +go mod init github.com/user/project + +# Add dependencies (automatic when you go build/go test) +go get github.com/gin-gonic/gin + +# Tidy up dependencies +go mod tidy + +# Download dependencies +go mod download + +# Verify dependencies +go mod verify +``` + +**Example go.mod file:** +```go +module github.com/user/project + +go 1.21 + +require ( + github.com/gin-gonic/gin v1.9.1 + github.com/go-redis/redis/v8 v8.11.5 +) +``` + +## Common Go Commands + +```bash +# Run code +go run main.go + +# Build executable +go build + +# Run tests +go test ./... + +# Format code +go fmt ./... + +# Lint code +go vet ./... + +# Download dependencies +go mod download + +# Update dependencies +go get -u ./... + +# See package documentation +go doc fmt.Println + +# Install a tool +go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest +``` + +## Next Steps: What You'll Learn + +In this course, you'll learn: + +**Modules 1-2: Language Basics** +- Go syntax from a Java perspective +- Variables, types, and control flow +- Functions and error handling + +**Modules 3-4: Object-Oriented Go** +- How Go does OOP without classes +- Interfaces vs abstract classes +- Composition over inheritance + +**Modules 5-6: Concurrency** +- Goroutines vs Java threads +- Channels vs shared memory +- Select statements and timeouts + +**Modules 7-9: Practical Development** +- Web development without Spring +- Microservices architecture +- Testing and best practices + +**Modules 10-14: Advanced Topics** +- Performance optimization +- Production deployment +- Common pitfalls to avoid +- Idiomatic Go patterns + +## Transition Strategy for Java Developers + +### Phase 1: Unlearn Java Patterns (Weeks 1-2) +- Forget about classes and inheritance +- Embrace composition +- Get comfortable with explicit error handling + +### Phase 2: Learn Go Fundamentals (Weeks 3-4) +- Master basic syntax +- Understand Go's type system +- Learn package structure + +### Phase 3: Embrace Go Concurrency (Weeks 5-6) +- Goroutines and channels +- Concurrent patterns +- Performance considerations + +### Phase 4: Production-Ready Go (Weeks 7-8) +- Testing frameworks +- Error handling patterns +- Deployment strategies + +--- + +### Practice Questions: +1. What are the main differences between Java's JVM execution and Go's native compilation? +2. Why might a company choose Go over Java for microservices? Provide three reasons. +3. What Java concepts do you need to "unlearn" when switching to Go? +4. Describe the process of setting up a Go development environment from scratch. + +### Project Idea: +- Create a simple HTTP server in Go that responds with "Hello, World!" and compare it with a Java Spring Boot equivalent. Measure startup time, memory usage, and binary size. + +### Next Steps: +- Dive into Go's syntax and see how it compares to Java +- Learn about Go's approach to object-oriented programming +- Explore Go's powerful concurrency model diff --git a/content/docs/java2go/module-00-go-introduction.zh-cn.mdx b/content/docs/java2go/module-00-go-introduction.zh-cn.mdx new file mode 100644 index 0000000..7180fb8 --- /dev/null +++ b/content/docs/java2go/module-00-go-introduction.zh-cn.mdx @@ -0,0 +1,417 @@ +--- +title: "模块 00:面向 Java 开发者的 Go 语言介绍" +--- + +## 为什么选择 Go?Java 开发者的视角 + +作为一名 Java 开发者,你可能熟悉 JVM 生态系统、Spring 框架、面向对象编程以及管理复杂的类层次结构。Go(Golang)提供了一个令人耳目一新的替代方案,它强调简洁性、性能和现代开发实践。 + +### Go 与 Java 的高层对比 + +| 方面 | Java | Go | +|------|------|-----| +| **编程范式** | 面向对象(类、继承) | 多范式(无类,使用结构和接口) | +| **执行方式** | JVM(编译为字节码) | 原生二进制编译 | +| **启动时间** | 慢(JVM 预热) | 即时(原生二进制) | +| **内存占用** | 高(JVM 开销) | 低(单一二进制) | +| **并发模型** | 线程(重量级) | Goroutines(轻量级) | +| **类型系统** | 泛型(Java 5 起) | 类型推断,早期无泛型 | +| **错误处理** | 异常(try-catch) | 显式错误返回 | +| **依赖管理** | Maven, Gradle | Go Modules | +| **学习曲线** | 陡峭(复杂生态系统) | 平缓(极简语言) | +| **编译速度** | 慢 | 极快 | +| **典型应用** | 企业应用、Android、大数据 | 微服务、云原生、CLI 工具 | + +## Go 语言历史和设计哲学 + +Go 于 2007 年在 Google 由 Robert Griesemer、Rob Pike 和 Ken Thompson(Unix 之父)创建。它的设计旨在解决 C++、Java 和 Python 在大规模软件开发中遇到的挫折。 + +### 核心设计原则 + +1. **简洁优于复杂** + - 仅 25 个关键字(Java 有 50+ 个) + - 无类、无继承、无构造函数 + - 最小化语法,几天即可学会 + +2. **并发是一等公民** + - 内置 goroutines(轻量级线程) + - 通道通信 + - 无回调地狱或复杂的异步模式 + +3. **快速编译** + - 秒级编译,而非分钟级 + - 支持快速开发迭代 + - 开发期间无需单独构建步骤 + +4. **性能与安全兼顾** + - 静态类型配合类型推断 + - 内存安全(垃圾回收) + - 接近 C 的性能 + +5. **实用优于教条** + - "不要通过共享内存来通信;通过通信来共享内存" + - 显式优于隐式 + - 少即是多(指数级) + +## 何时选择 Go 而非 Java + +### Go 的完美使用场景: + +✅ **微服务和 API** +- 轻量级服务 +- 快速启动和扩展 +- 简单部署(单一二进制) + +✅ **云原生应用** +- Kubernetes(Go 编写) +- Docker(Go 编写) +- 基础设施工具 + +✅ **高性能网络服务** +- 代理服务器 +- 负载均衡器 +- 实时数据处理 + +✅ **DevOps 和工具** +- CLI 应用 +- 自动化脚本 +- 开发工具 + +✅ **系统编程** +- 文件系统 +- 数据库引擎 +- 网络协议 + +### Java 可能更合适的场景: + +✅ **大型企业应用** +- 复杂业务逻辑 +- 成熟生态系统(Spring、Hibernate) +- 企业级支持和工具 + +✅ **Android 开发** +- 原生 Android 应用 +- Kotlin 互操作性 + +✅ **大数据处理** +- Hadoop、Spark 生态系统 +- 成熟的数据处理框架 + +## 真实成功案例 + +许多公司已成功从 Java 迁移到 Go: + +**Google** +- 为内部使用开发了 Go +- 将许多服务从 Java/C++ 迁移到 Go +- 更好的性能和更简单的代码 + +**Uber** +- 将许多微服务从 Java 迁移到 Go +- 内存占用减少 10 倍 +- 启动时间从秒级改善到毫秒级 + +**Twitch** +- 用 Go 替换 Java 服务 +- 处理数百万并发连接 +- 更简单的部署和管理 + +**Dropbox** +- 从 Python/Java 迁移到 Go +- 性能显著提升 +- 降低基础设施成本 + +**字节跳动(TikTok)** +- 大量采用 Go +- 构建了自己的 Go 框架(CloudWeGo) +- 处理数十亿用户 + +## Java 到 Go 的学习之旅 + +### 你会感到熟悉的部分: +✅ 静态类型 +✅ 垃圾回收 +✅ 类似的控制流结构(if、for、switch) +✅ 包导入 +✅ 基于接口的多态 +✅ 编译型语言 + +### 不同的部分: +❌ 无类(使用结构体) +❌ 无继承(使用组合) +❌ 无构造函数(使用工厂函数) +❌ 无异常(使用错误返回) +❌ 旧版本无泛型(Go 1.18+ 支持泛型) +❌ 无 while 循环(只有 for) +❌ 无隐式类型转换 + +### 你将获得: +✅ 更简单的语法(写更少的代码) +✅ 更快的编译 +✅ 更好的并发模型 +✅ 更简单的部署(单一二进制) +✅ 更小的内存占用 +✅ 更快的启动时间 + +## 开发环境搭建 + +### 安装 Go + +**macOS(Homebrew):** +```bash +brew install go +``` + +**Ubuntu/Debian:** +```bash +sudo apt update +sudo apt install golang-go +``` + +**Windows:** +从 [golang.org/dl/](https://golang.org/dl/) 下载 + +### 验证安装 + +```bash +go version +# 输出:go version go1.21.x darwin/amd64(或类似) +``` + +### 设置工作空间 + +```bash +# 创建项目目录 +mkdir ~/go-projects +cd ~/go-projects + +# 初始化新模块 +go mod init github.com/yourusername/yourproject + +# 这会创建一个 go.mod 文件 +``` + +### 推荐工具 + +**VS Code 扩展:** +- Go(Google 官方扩展) +- Code runner +- Error lens + +**替代 IDE:** +- GoLand(JetBrains,付费) +- Vim/Neovim 配合 vim-go +- IntelliJ IDEA 的 Go 插件 + +## 你的第一个 Go 程序 + +让我们对比 Java 和 Go 中的简单 "Hello, World!" 程序: + + +```java !! java +// Java: Hello World +public class HelloWorld { + public static void main(String[] args) { + System.out.println("Hello, World from Java!"); + } +} + +// 编译:javac HelloWorld.java +// 运行:java HelloWorld +``` + +```go !! go +// Go: Hello World +package main + +import "fmt" + +func main() { + fmt.Println("Hello, World from Go!") +} + +// 运行:go run main.go +// 或构建:go build main.go && ./main +``` + + +### 关键观察: + +1. **无需类**:Go 不需要类包装器 +2. **更简单的入口**:只需 `func main()`,而不是 `public static void main` +3. **分号可选**:Go 会推断(但你可以使用) +4. **包声明**:每个文件以 `package` 声明开始 +5. **单文件**:无需将文件名与类名匹配 + +## 理解 Go 的构建过程 + +### Java 构建过程: +``` +源码 (.java) → 编译器 → 字节码 (.class) → JVM → 机器码 + ↓ + 编译慢,但字节码可移植 +``` + +### Go 构建过程: +``` +源码 (.go) → 编译器 → 机器码(二进制) + ↓ + 编译快,平台特定二进制 +``` + +### 运行 Go 代码 + +**开发(快速迭代):** +```bash +go run main.go +``` + +**生产(创建可执行文件):** +```bash +go build -o myapp main.go +./myapp # Windows 上是 myapp.exe +``` + +**为不同平台构建:** +```bash +# 为 Linux 构建 +GOOS=linux go build -o myapp-linux main.go + +# 为 Windows 构建 +GOOS=windows go build -o myapp.exe main.go + +# 为 macOS ARM(Apple Silicon)构建 +GOOS=darwin GOARCH=arm64 go build -o myapp-mac-arm main.go +``` + +## Go 模块系统 + +Go 1.11+ 引入了 Go Modules 进行依赖管理: + +```bash +# 初始化新模块 +go mod init github.com/user/project + +# 添加依赖(go build/go test 时自动) +go get github.com/gin-gonic/gin + +# 整理依赖 +go mod tidy + +# 下载依赖 +go mod download + +# 验证依赖 +go mod verify +``` + +**go.mod 文件示例:** +```go +module github.com/user/project + +go 1.21 + +require ( + github.com/gin-gonic/gin v1.9.1 + github.com/go-redis/redis/v8 v8.11.5 +) +``` + +## 常用 Go 命令 + +```bash +# 运行代码 +go run main.go + +# 构建可执行文件 +go build + +# 运行测试 +go test ./... + +# 格式化代码 +go fmt ./... + +# 代码检查 +go vet ./... + +# 下载依赖 +go mod download + +# 更新依赖 +go get -u ./... + +# 查看包文档 +go doc fmt.Println + +# 安装工具 +go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest +``` + +## 下一步:你将学到什么 + +在本课程中,你将学习: + +**模块 1-2:语言基础** +- 从 Java 角角看 Go 语法 +- 变量、类型和控制流 +- 函数和错误处理 + +**模块 3-4:面向对象的 Go** +- Go 如何在没有类的情况下做 OOP +- 接口 vs 抽象类 +- 组合优于继承 + +**模块 5-6:并发** +- Goroutines vs Java 线程 +- 通道 vs 共享内存 +- Select 语句和超时 + +**模块 7-9:实战开发** +- 没有 Spring 的 Web 开发 +- 微服务架构 +- 测试和最佳实践 + +**模块 10-14:高级主题** +- 性能优化 +- 生产部署 +- 常见陷阱 +- 地道的 Go 模式 + +## Java 开发者的转型策略 + +### 第一阶段:忘掉 Java 模式(第 1-2 周) +- 忘掉类和继承 +- 拥抱组合 +- 习惯显式错误处理 + +### 第二阶段:学习 Go 基础(第 3-4 周) +- 掌握基本语法 +- 理解 Go 的类型系统 +- 学习包结构 + +### 第三阶段:掌握 Go 并发(第 5-6 周) +- Goroutines 和通道 +- 并发模式 +- 性能考虑 + +### 第四阶段:生产级 Go(第 7-8 周) +- 测试框架 +- 错误处理模式 +- 部署策略 + +--- + +### 练习题: +1. Java 的 JVM 执行和 Go 的原生编译之间有哪些主要区别? +2. 为什么公司可能在微服务中选择 Go 而非 Java?给出三个理由。 +3. 切换到 Go 时,你需要"忘掉"哪些 Java 概念? +4. 描述从零开始设置 Go 开发环境的过程。 + +### 项目想法: +- 在 Go 中创建一个简单的 HTTP 服务器响应 "Hello, World!",并与 Java Spring Boot 版本比较。测量启动时间、内存使用和二进制大小。 + +### 下一步: +- 深入了解 Go 语法,看看它与 Java 有何不同 +- 学习 Go 的面向对象编程方法 +- 探索 Go 强大的并发模型 diff --git a/content/docs/java2go/module-00-go-introduction.zh-tw.mdx b/content/docs/java2go/module-00-go-introduction.zh-tw.mdx new file mode 100644 index 0000000..1a3a7fe --- /dev/null +++ b/content/docs/java2go/module-00-go-introduction.zh-tw.mdx @@ -0,0 +1,417 @@ +--- +title: "模組 00:面向 Java 開發者的 Go 語言介紹" +--- + +## 為什麼選擇 Go?Java 開發者的視角 + +作為一名 Java 開發者,你可能熟悉 JVM 生態系統、Spring 框架、物件導向程式設計以及管理複雜的類別層次結構。Go(Golang)提供了一個令人耳目一新的替代方案,它強調簡潔性、效能和現代開發實踐。 + +### Go 與 Java 的高層對比 + +| 方面 | Java | Go | +|------|------|-----| +| **程式設計範式** | 物件導向(類別、繼承) | 多範式(無類別,使用結構和介面) | +| **執行方式** | JVM(編譯為位元組碼) | 原生二進位編譯 | +| **啟動時間** | 慢(JVM 預熱) | 即時(原生二進位) | +| **記憶體佔用** | 高(JVM 開銷) | 低(單一二進位) | +| **並發模型** | 執行緒(重量級) | Goroutines(輕量級) | +| **類型系統** | 泛型(Java 5 起) | 類型推斷,早期無泛型 | +| **錯誤處理** | 例外(try-catch) | 顯式錯誤返回 | +| **依賴管理** | Maven, Gradle | Go Modules | +| **學習曲線** | 陡峭(複雜生態系統) | 平緩(極簡語言) | +| **編譯速度** | 慢 | 極快 | +| **典型應用** | 企業應用、Android、大數據 | 微服務、雲原生、CLI 工具 | + +## Go 語言歷史和設計哲學 + +Go 於 2007 年在 Google 由 Robert Griesemer、Rob Pike 和 Ken Thompson(Unix 之父)創建。它的設計旨在解決 C++、Java 和 Python 在大規模軟體開發中遇到的挫折。 + +### 核心設計原則 + +1. **簡潔優於複雜** + - 僅 25 個關鍵字(Java 有 50+ 個) + - 無類別、無繼承、無建構函式 + - 最小化語法,幾天即可學會 + +2. **並發是一等公民** + - 內建 goroutines(輕量級執行緒) + - 通道通訊 + - 無回調地獄或複雜的異步模式 + +3. **快速編譯** + - 秒級編譯,而非分鐘級 + - 支援快速開發迭代 + - 開發期間無需單獨建構步驟 + +4. **效能與安全兼顧** + - 靜態類型配合類型推斷 + - 記憶體安全(垃圾回收) + - 接近 C 的效能 + +5. **實用優於教條** + - "不要透過共享記憶體來通訊;透過通訊來共享記憶體" + - 顯式優於隱式 + - 少即是多(指數級) + +## 何時選擇 Go 而非 Java + +### Go 的完美使用場景: + +✅ **微服務和 API** +- 輕量級服務 +- 快速啟動和擴展 +- 簡單部署(單一二進位) + +✅ **雲原生應用** +- Kubernetes(Go 編寫) +- Docker(Go 編寫) +- 基礎設施工具 + +✅ **高效能網路服務** +- 代理伺服器 +- 負載均衡器 +- 即時資料處理 + +✅ **DevOps 和工具** +- CLI 應用 +- 自動化腳本 +- 開發工具 + +✅ **系統程式設計** +- 檔案系統 +- 資料庫引擎 +- 網路協定 + +### Java 可能更合適的場景: + +✅ **大型企業應用** +- 複雜業務邏輯 +- 成熟生態系統(Spring、Hibernate) +- 企業級支援和工具 + +✅ **Android 開發** +- 原生 Android 應用 +- Kotlin 互操作性 + +✅ **大數據處理** +- Hadoop、Spark 生態系統 +- 成熟的資料處理框架 + +## 真實成功案例 + +許多公司已成功從 Java 遷移到 Go: + +**Google** +- 為內部使用開發了 Go +- 將許多服務從 Java/C++ 遷移到 Go +- 更好的效能和更簡單的程式碼 + +**Uber** +- 將許多微服務從 Java 遷移到 Go +- 記憶體佔用減少 10 倍 +- 啟動時間從秒級改善到毫秒級 + +**Twitch** +- 用 Go 替換 Java 服務 +- 處理數百萬並發連線 +- 更簡單的部署和管理 + +**Dropbox** +- 從 Python/Java 遷移到 Go +- 效能顯著提升 +- 降低基礎設施成本 + +**字節跳動(TikTok)** +- 大量採用 Go +- 構建了自己的 Go 框架(CloudWeGo) +- 處理數十億用戶 + +## Java 到 Go 的學習之旅 + +### 你會感到熟悉的部分: +✅ 靜態類型 +✅ 垃圾回收 +✅ 類似的控制流結構(if、for、switch) +✅ 套件匯入 +✅ 基於介面的多型 +✅ 編譯型語言 + +### 不同的部分: +❌ 無類別(使用結構體) +❌ 無繼承(使用組合) +❌ 無建構函式(使用工廠函式) +❌ 無例外(使用錯誤返回) +❌ 舊版本無泛型(Go 1.18+ 支援泛型) +❌ 無 while 迴圈(只有 for) +❌ 無隱式類型轉換 + +### 你將獲得: +✅ 更簡單的語法(寫更少的程式碼) +✅ 更快的編譯 +✅ 更好的並發模型 +✅ 更簡單的部署(單一二進位) +✅ 更小的記憶體佔用 +✅ 更快的啟動時間 + +## 開發環境搭建 + +### 安裝 Go + +**macOS(Homebrew):** +```bash +brew install go +``` + +**Ubuntu/Debian:** +```bash +sudo apt update +sudo apt install golang-go +``` + +**Windows:** +從 [golang.org/dl/](https://golang.org/dl/) 下載 + +### 驗證安裝 + +```bash +go version +# 輸出:go version go1.21.x darwin/amd64(或類似) +``` + +### 設置工作空間 + +```bash +# 建立專案目錄 +mkdir ~/go-projects +cd ~/go-projects + +# 初始化新模組 +go mod init github.com/yourusername/yourproject + +# 這會建立一個 go.mod 檔案 +``` + +### 推薦工具 + +**VS Code 擴充功能:** +- Go(Google 官方擴充功能) +- Code runner +- Error lens + +**替代 IDE:** +- GoLand(JetBrains,付費) +- Vim/Neovim 配合 vim-go +- IntelliJ IDEA 的 Go 外掛程式 + +## 你的第一個 Go 程式 + +讓我們對比 Java 和 Go 中的簡單 "Hello, World!" 程式: + + +```java !! java +// Java: Hello World +public class HelloWorld { + public static void main(String[] args) { + System.out.println("Hello, World from Java!"); + } +} + +// 編譯:javac HelloWorld.java +// 執行:java HelloWorld +``` + +```go !! go +// Go: Hello World +package main + +import "fmt" + +func main() { + fmt.Println("Hello, World from Go!") +} + +// 執行:go run main.go +// 或建構:go build main.go && ./main +``` + + +### 關鍵觀察: + +1. **無需類別**:Go 不需要類別包裝器 +2. **更簡單的入口**:只需 `func main()`,而不是 `public static void main` +3. **分號可選**:Go 會推斷(但你可以使用) +4. **套件宣告**:每個檔案以 `package` 宣告開始 +5. **單檔案**:無需將檔案名與類別名匹配 + +## 理解 Go 的建構過程 + +### Java 建構過程: +``` +原始碼 (.java) → 編譯器 → 位元組碼 (.class) → JVM → 機器碼 + ↓ + 編譯慢,但位元組碼可移植 +``` + +### Go 建構過程: +``` +原始碼 (.go) → 編譯器 → 機器碼(二進位) + ↓ + 編譯快,平台特定二進位 +``` + +### 執行 Go 程式碼 + +**開發(快速迭代):** +```bash +go run main.go +``` + +**生產(建立可執行檔):** +```bash +go build -o myapp main.go +./myapp # Windows 上是 myapp.exe +``` + +**為不同平台建構:** +```bash +# 為 Linux 建構 +GOOS=linux go build -o myapp-linux main.go + +# 為 Windows 建構 +GOOS=windows go build -o myapp.exe main.go + +# 為 macOS ARM(Apple Silicon)建構 +GOOS=darwin GOARCH=arm64 go build -o myapp-mac-arm main.go +``` + +## Go 模組系統 + +Go 1.11+ 引入了 Go Modules 進行依賴管理: + +```bash +# 初始化新模組 +go mod init github.com/user/project + +# 新增依賴(go build/go test 時自動) +go get github.com/gin-gonic/gin + +# 整理依賴 +go mod tidy + +# 下載依賴 +go mod download + +# 驗證依賴 +go mod verify +``` + +**go.mod 檔案範例:** +```go +module github.com/user/project + +go 1.21 + +require ( + github.com/gin-gonic/gin v1.9.1 + github.com/go-redis/redis/v8 v8.11.5 +) +``` + +## 常用 Go 指令 + +```bash +# 執行程式碼 +go run main.go + +# 建構可執行檔 +go build + +# 執行測試 +go test ./... + +# 格式化程式碼 +go fmt ./... + +# 程式碼檢查 +go vet ./... + +# 下載依賴 +go mod download + +# 更新依賴 +go get -u ./... + +# 查看套件文件 +go doc fmt.Println + +# 安裝工具 +go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest +``` + +## 下一步:你將學到什麼 + +在本課程中,你將學習: + +**模組 1-2:語言基礎** +- 從 Java 角角看 Go 語法 +- 變數、類型和控制流 +- 函式和錯誤處理 + +**模組 3-4:物件導向的 Go** +- Go 如何在沒有類別的情況下做 OOP +- 介面 vs 抽象類別 +- 組合優於繼承 + +**模組 5-6:並發** +- Goroutines vs Java 執行緒 +- 通道 vs 共享記憶體 +- Select 陳述式和逾時 + +**模組 7-9:實戰開發** +- 沒有 Spring 的 Web 開發 +- 微服務架構 +- 測試和最佳實踐 + +**模組 10-14:進階主題** +- 效能優化 +- 生產部署 +- 常見陷阱 +- 地道的 Go 模式 + +## Java 開發者的轉型策略 + +### 第一階段:忘掉 Java 模式(第 1-2 週) +- 忘掉類別和繼承 +- 擁抱組合 +- 習慣顯式錯誤處理 + +### 第二階段:學習 Go 基礎(第 3-4 週) +- 掌握基本語法 +- 理解 Go 的類型系統 +- 學習套件結構 + +### 第三階段:掌握 Go 並發(第 5-6 週) +- Goroutines 和通道 +- 並發模式 +- 效能考慮 + +### 第四階段:生產級 Go(第 7-8 週) +- 測試框架 +- 錯誤處理模式 +- 部署策略 + +--- + +### 練習題: +1. Java 的 JVM 執行和 Go 的原生編譯之間有哪些主要區別? +2. 為什麼公司可能在微服務中選擇 Go 而非 Java?給出三個理由。 +3. 切換到 Go 時,你需要"忘掉"哪些 Java 概念? +4. 描述從零開始設置 Go 開發環境的過程。 + +### 專案想法: +- 在 Go 中建立一個簡單的 HTTP 伺服器回應 "Hello, World!",並與 Java Spring Boot 版本比較。測量啟動時間、記憶體使用和二進位大小。 + +### 下一步: +- 深入了解 Go 語法,看看它與 Java 有何不同 +- 學習 Go 的物件導向程式設計方法 +- 探索 Go 強大的並發模型 diff --git a/content/docs/java2go/module-01-syntax-comparison.mdx b/content/docs/java2go/module-01-syntax-comparison.mdx new file mode 100644 index 0000000..9530626 --- /dev/null +++ b/content/docs/java2go/module-01-syntax-comparison.mdx @@ -0,0 +1,897 @@ +--- +title: "Module 01: Syntax Comparison - Java to Go" +--- + +This module explores the fundamental syntax differences between Java and Go, helping you transition your Java knowledge to Go effectively. + +## Variable Declarations + +### The Philosophy Difference + +**Java:** Explicit types, verbose declaration +```java +String name = "Alice"; +int age = 25; +double salary = 50000.50; +boolean isActive = true; +``` + +**Go:** Type inference, concise syntax +```go +name := "Alice" // string inferred +age := 25 // int inferred +salary := 50000.50 // float64 inferred +isActive := true // bool inferred +``` + +### Declaration Methods Comparison + +| Java | Go | Description | +|------|-----|-------------| +| `String name = "Alice";` | `name := "Alice"` | Short declaration with type inference | +| `final String NAME = "Bob";` | `const NAME = "Bob"` | Constant declaration | +| `String name;` | `var name string` | Variable with zero value | +| `int x = 10;` | `x := 10` or `var x int = 10` | Explicit vs inferred type | + + +```java !! java +// Java: Variable Declarations +public class Variables { + public static void main(String[] args) { + // Explicit type declaration + String name = "Alice"; + int age = 30; + double salary = 75000.50; + boolean isActive = true; + + // Final constant + final int MAX_COUNT = 100; + + // Declaration without initialization (null for objects) + String uninitialized; + uninitialized = "Now initialized"; + + // Multiple declarations + int x = 10, y = 20, z = 30; + + System.out.println("Name: " + name); + System.out.println("Age: " + age); + System.out.println("Salary: " + salary); + System.out.println("Active: " + isActive); + } +} +``` + +```go !! go +// Go: Variable Declarations +package main + +import "fmt" + +func main() { + // Short declaration (type inferred) - MOST COMMON + name := "Alice" + age := 30 + salary := 75000.50 + isActive := true + + // Constant declaration + const MAX_COUNT = 100 + + // Variable with zero value (no uninitialized variables in Go) + var uninitialized string + uninitialized = "Now initialized" + + // Multiple declarations + x, y, z := 10, 20, 30 + + // Explicit type declaration + var employeeId int = 12345 + + fmt.Printf("Name: %s\n", name) + fmt.Printf("Age: %d\n", age) + fmt.Printf("Salary: %.2f\n", salary) + fmt.Printf("Active: %t\n", isActive) + fmt.Printf("Employee ID: %d\n", employeeId) +} +``` + + +### Zero Values in Go + +Unlike Java where uninitialized objects are `null`, Go always initializes variables to their zero value: + +| Go Type | Zero Value | Java Equivalent | +|---------|------------|-----------------| +| `int`, `int64`, etc. | `0` | `0` | +| `float64`, `float32` | `0.0` | `0.0` | +| `bool` | `false` | `false` | +| `string` | `""` (empty string) | `null` | +| pointer types | `nil` | `null` | +| `slice` | `nil` | `null` | + + +```java !! java +// Java: Null vs Default Values +public class ZeroValues { + static int primitiveInt; // 0 + static double primitiveDouble; // 0.0 + static boolean primitiveBool; // false + static String objectString; // null + + public static void main(String[] args) { + System.out.println(primitiveInt); // 0 + System.out.println(primitiveDouble); // 0.0 + System.out.println(primitiveBool); // false + System.out.println(objectString); // null + + // Null check required + if (objectString != null) { + System.out.println(objectString.length()); + } + } +} +``` + +```go !! go +// Go: Zero Values (No Null for Primitives) +package main + +import "fmt" + +var primitiveInt int +var primitiveDouble float64 +var primitiveBool bool +var objectString string + +func main() { + fmt.Println(primitiveInt) // 0 + fmt.Println(primitiveDouble) // 0 + fmt.Println(primitiveBool) // false + fmt.Println(objectString) // "" (empty string, not nil!) + + // No null check needed for string + fmt.Println(len(objectString)) // 0 + + // Pointer example (can be nil) + var pointer *int + fmt.Println(pointer) // + + if pointer != nil { + fmt.Println(*pointer) + } +} +``` + + +## Data Types Comparison + +### Primitive Types + +| Java | Go | Notes | +|------|-----|-------| +| `byte` | `byte` | Same (8-bit) | +| `short` | `int16` | 16-bit integer | +| `int` | `int` | Platform-dependent (32 or 64-bit) | +| `long` | `int64` | 64-bit integer | +| `float` | `float32` | 32-bit float | +| `double` | `float64` | 64-bit float | +| `char` | `rune` | Go's rune is Unicode code point | +| `boolean` | `bool` | Same concept | +| `String` | `string` | Both immutable, UTF-8 | + +### Go-Specific Types + +Go provides more precise integer types: + +```go +// Signed integers +int8 // 8-bit (-128 to 127) +int16 // 16-bit +int32 // 32-bit +int64 // 64-bit +int // Platform-dependent (32 or 64-bit) + +// Unsigned integers +uint8 // 8-bit (0 to 255) - aka byte +uint16 // 16-bit +uint32 // 32-bit +uint64 // 64-bit +uint // Platform-dependent +uintptr // Unsigned integer for pointer + +// Floating-point +float32 // 32-bit IEEE 754 +float64 // 64-bit IEEE 754 + +// Complex numbers (Java doesn't have these!) +complex64 // Complex with float32 real and imaginary +complex128 // Complex with float64 real and imaginary +``` + + +```java !! java +// Java: Type System +public class Types { + public static void main(String[] args) { + // Primitive types + byte b = 100; + short s = 1000; + int i = 100000; + long l = 1000000L; + float f = 3.14f; + double d = 3.14159; + char c = 'A'; + boolean bool = true; + + // String (reference type) + String name = "Alice"; + + // Arrays (fixed size) + int[] numbers = new int[5]; + numbers[0] = 10; + + // ArrayList (dynamic) + java.util.List list = new java.util.ArrayList<>(); + list.add("Hello"); + + System.out.println("Int: " + i); + System.out.println("Double: " + d); + System.out.println("String: " + name); + } +} +``` + +```go !! go +// Go: Type System +package main + +import "fmt" + +func main() { + // Basic types + var b byte = 100 // uint8 + var s int16 = 1000 + var i int = 100000 + var l int64 = 1000000 + var f float32 = 3.14 + var d float64 = 3.14159 + var c rune = 'A' // rune is alias for int32 + var boolVal bool = true + + // String (built-in type, not a class) + var name string = "Alice" + + // Arrays (fixed size) + var numbers [5]int + numbers[0] = 10 + + // Slices (dynamic arrays) - BUILT-IN, no import needed + var slice []int = []int{1, 2, 3} + slice = append(slice, 4) // Add element + + // Maps (built-in) + var person map[string]int = map[string]int{ + "age": 30, + } + + fmt.Printf("Int: %d\n", i) + fmt.Printf("Double: %f\n", d) + fmt.Printf("String: %s\n", name) + fmt.Printf("Slice: %v\n", slice) + fmt.Printf("Map: %v\n", person) +} +``` + + +## Control Flow + +### If/Else Statements + +Go's if/else is similar to Java but with important differences: +- No parentheses required around condition +- Opening brace must be on same line as if +- No ternary operator in Go + + +```java !! java +// Java: If/Else +public class Conditionals { + public static void main(String[] args) { + int score = 85; + + // With parentheses + if (score >= 90) { + System.out.println("A"); + } else if (score >= 80) { + System.out.println("B"); + } else { + System.out.println("C"); + } + + // Ternary operator + String result = (score >= 60) ? "Pass" : "Fail"; + System.out.println(result); + + // Null check + String name = null; + if (name != null && name.length() > 0) { + System.out.println(name); + } + } +} +``` + +```go !! go +// Go: If/Else +package main + +import "fmt" + +func main() { + score := 85 + + // No parentheses required + if score >= 90 { + fmt.Println("A") + } else if score >= 80 { + fmt.Println("B") + } else { + fmt.Println("C") + } + + // No ternary operator in Go! + // Use regular if/else instead + var result string + if score >= 60 { + result = "Pass" + } else { + result = "Fail" + } + fmt.Println(result) + + // If with initialization statement + if name := getName(); name != "" { + fmt.Printf("Name: %s\n", name) + } + + // Multiple conditions in one if + if x := 10; x < 0 { + fmt.Println("Negative") + } else if x < 10 { + fmt.Println("Single digit") + } else { + fmt.Println("Double digit") + } +} + +func getName() string { + return "Alice" +} +``` + + +### Switch Statements + +Go's switch is more powerful than Java's: +- No break needed (no fall-through by default) +- Can switch on any type, not just integers +- Multiple values in one case +- No default required + + +```java !! java +// Java: Switch Statement +public class SwitchCase { + public static void main(String[] args) { + int dayOfWeek = 3; + + // Traditional switch + switch (dayOfWeek) { + case 1: + System.out.println("Monday"); + break; // Required to prevent fall-through + case 2: + System.out.println("Tuesday"); + break; + case 3: + System.out.println("Wednesday"); + break; + default: + System.out.println("Other day"); + } + + // Java 14+ enhanced switch + String dayType = switch (dayOfWeek) { + case 1, 2, 3, 4, 5 -> "Weekday"; + case 6, 7 -> "Weekend"; + default -> "Invalid"; + }; + System.out.println(dayType); + } +} +``` + +```go !! go +// Go: Switch Statement +package main + +import "fmt" + +func main() { + dayOfWeek := 3 + + // No break needed! No fall-through by default + switch dayOfWeek { + case 1: + fmt.Println("Monday") + case 2: + fmt.Println("Tuesday") + case 3: + fmt.Println("Wednesday") + default: + fmt.Println("Other day") + } + + // Multiple values in one case + switch dayOfWeek { + case 1, 2, 3, 4, 5: + fmt.Println("Weekday") + case 6, 7: + fmt.Println("Weekend") + default: + fmt.Println("Invalid") + } + + // Switch with condition (no expression needed) + score := 85 + switch { + case score >= 90: + fmt.Println("A") + case score >= 80: + fmt.Println("B") + case score >= 70: + fmt.Println("C") + default: + fmt.Println("D") + } + + // Switch on strings (and any type!) + OS := "linux" + switch OS { + case "darwin": + fmt.Println("macOS") + case "linux": + fmt.Println("Linux") + case "windows": + fmt.Println("Windows") + } +} +``` + + +## Loops + +### For Loops - The Only Loop in Go + +Go only has `for` loops (no `while`, no `do-while`), but it's very flexible: + + +```java !! java +// Java: Multiple Loop Types +public class Loops { + public static void main(String[] args) { + // Traditional for loop + for (int i = 0; i < 5; i++) { + System.out.println("Count: " + i); + } + + // Enhanced for loop (for-each) + int[] numbers = {1, 2, 3, 4, 5}; + for (int num : numbers) { + System.out.println(num); + } + + // While loop + int count = 0; + while (count < 5) { + System.out.println("While: " + count); + count++; + } + + // Do-while loop + do { + System.out.println("Do-while: " + count); + count--; + } while (count > 0); + } +} +``` + +```go !! go +// Go: Only for Loop, But Very Flexible +package main + +import "fmt" + +func main() { + // Traditional for loop + for i := 0; i < 5; i++ { + fmt.Printf("Count: %d\n", i) + } + + // Range-based for (like enhanced for) + numbers := []int{1, 2, 3, 4, 5} + for i, num := range numbers { + fmt.Printf("Index %d: %d\n", i, num) + } + + // Range with only value + for _, num := range numbers { + fmt.Printf("Value: %d\n", num) + } + + // Range with only index + for i := range numbers { + fmt.Printf("Index: %d\n", i) + } + + // While-style loop (just omit all three components) + count := 0 + for count < 5 { + fmt.Printf("While: %d\n", count) + count++ + } + + // Infinite loop + sum := 0 + for { + sum++ + if sum > 10 { + break + } + } + fmt.Printf("Sum: %d\n", sum) + + // Range over map + person := map[string]int{ + "age": 30, + "score": 85, + } + for key, value := range person { + fmt.Printf("%s: %d\n", key, value) + } + + // Range over string (by rune) + for i, char := range "Hello" { + fmt.Printf("Index %d: %c\n", i, char) + } +} +``` + + +## Functions + +### Function Declaration + + +```java !! java +// Java: Function Methods +public class Functions { + // Static method (like Go function) + public static int add(int a, int b) { + return a + b; + } + + // Method with return type + public String greet(String name) { + return "Hello, " + name; + } + + // Void method + public void printMessage(String message) { + System.out.println(message); + } + + // Method with multiple return values? Not possible directly! + // Need to use a class or array + + // Varargs + public int sum(int... numbers) { + int total = 0; + for (int num : numbers) { + total += num; + } + return total; + } + + public static void main(String[] args) { + System.out.println(add(5, 3)); + + Functions f = new Functions(); + System.out.println(f.greet("Alice")); + f.printMessage("Hello"); + + System.out.println(sum(1, 2, 3, 4, 5)); + } +} +``` + +```go !! go +// Go: Functions with Multiple Return Values! +package main + +import "fmt" + +// Simple function +func add(a int, b int) int { + return a + b +} + +// Shorthand parameter types +func subtract(a, b int) int { + return a - b +} + +// Multiple return values (VERY USEFUL!) +func divide(a, b float64) (float64, error) { + if b == 0 { + return 0, fmt.Errorf("division by zero") + } + return a / b, nil +} + +// Named return values +func getDimensions() (width, height int) { + width = 100 + height = 200 + return // naked return (returns width, height) +} + +// Variadic function (varargs) +func sum(numbers ...int) int { + total := 0 + for _, num := range numbers { + total += num + } + return total +} + +// Function as a type (first-class citizen) +var operation func(int, int) int = func(a, b int) int { + return a * b +} + +func main() { + fmt.Println(add(5, 3)) + fmt.Println(subtract(10, 3)) + + // Multiple return values + result, err := divide(10, 2) + if err != nil { + fmt.Println("Error:", err) + } else { + fmt.Println("Result:", result) + } + + // Named return values + w, h := getDimensions() + fmt.Printf("Dimensions: %d x %d\n", w, h) + + // Variadic function + fmt.Println(sum(1, 2, 3, 4, 5)) + + // Function as variable + fmt.Println(operation(5, 6)) +} +``` + + +### Methods in Go + +Unlike Java, methods are defined outside structs: + + +```java !! java +// Java: Methods inside Class +public class Person { + private String name; + private int age; + + public Person(String name, int age) { + this.name = name; + this.age = age; + } + + // Instance method + public String getName() { + return name; + } + + public void setAge(int age) { + this.age = age; + } + + public String introduce() { + return "Hi, I'm " + name; + } + + public static void main(String[] args) { + Person p = new Person("Alice", 30); + System.out.println(p.introduce()); + } +} +``` + +```go !! go +// Go: Methods defined outside Struct +package main + +import "fmt" + +// Struct definition +type Person struct { + Name string + Age int +} + +// Constructor function (idiomatic Go) +func NewPerson(name string, age int) *Person { + return &Person{ + Name: name, + Age: age, + } +} + +// Method with value receiver +func (p Person) GetName() string { + return p.Name +} + +// Method with pointer receiver (can modify) +func (p *Person) SetAge(age int) { + p.Age = age +} + +// Method +func (p Person) Introduce() string { + return fmt.Sprintf("Hi, I'm %s", p.Name) +} + +func main() { + // Constructor + p := NewPerson("Alice", 30) + fmt.Println(p.Introduce()) + + p.SetAge(31) + fmt.Printf("Age: %d\n", p.Age) +} +``` + + +## Arrays and Slices + + +```java !! java +// Java: Arrays and ArrayList +import java.util.ArrayList; +import java.util.List; + +public class Arrays { + public static void main(String[] args) { + // Array (fixed size) + int[] fixedArray = new int[5]; + fixedArray[0] = 10; + + // Array literal + int[] numbers = {1, 2, 3, 4, 5}; + System.out.println("Length: " + numbers.length); + + // ArrayList (dynamic) + List list = new ArrayList<>(); + list.add("Hello"); + list.add("World"); + list.add("Java"); + + System.out.println("List size: " + list.size()); + System.out.println("Element: " + list.get(0)); + + // Iterate + for (String item : list) { + System.out.println(item); + } + + // Sublist + List subList = list.subList(0, 2); + System.out.println("Sublist: " + subList); + } +} +``` + +```go !! go +// Go: Arrays and Slices +package main + +import "fmt" + +func main() { + // Array (fixed size) - rarely used directly + var fixedArray [5]int + fixedArray[0] = 10 + + // Array literal + numbers := [5]int{1, 2, 3, 4, 5} + fmt.Printf("Array length: %d\n", len(numbers)) + + // Slice (dynamic view of array) - MOST COMMON + var slice []int + slice = append(slice, 1) + slice = append(slice, 2) + slice = append(slice, 3) + + // Slice literal + fruits := []string{"Apple", "Banana", "Cherry"} + fmt.Printf("Slice length: %d\n", len(fruits)) + fmt.Printf("Element: %s\n", fruits[0]) + + // Iterate with range + for i, fruit := range fruits { + fmt.Printf("Index %d: %s\n", i, fruit) + } + + // Slicing (sub-slice) + subSlice := fruits[0:2] // elements 0 and 1 + fmt.Printf("Sub-slice: %v\n", subSlice) + + // Append to slice + fruits = append(fruits, "Date") + fmt.Printf("After append: %v\n", fruits) + + // Copy slice + copySlice := make([]string, len(fruits)) + copy(copySlice, fruits) + fmt.Printf("Copy: %v\n", copySlice) + + // Pre-allocate slice with capacity + bigSlice := make([]int, 0, 100) // length 0, capacity 100 + fmt.Printf("Length: %d, Capacity: %d\n", len(bigSlice), cap(bigSlice)) +} +``` + + +## Key Syntax Differences Summary + +| Feature | Java | Go | +|---------|------|-----| +| **Semicolons** | Required | Optional (inferred) | +| **Parentheses in if/for** | Required | Optional | +| **Brace position** | Flexible | Must be on same line | +| **Ternary operator** | `condition ? true : false` | No ternary, use if/else | +| **While loop** | `while (condition)` | `for condition` | +| **Do-while** | `do {} while (condition)` | No direct equivalent | +| **For-each** | `for (item : collection)` | `for item := range collection` | +| **Break/Continue** | `break label` | `break label` (similar) | +| **Switch fall-through** | Default (need break) | Opt-in with `fallthrough` keyword | +| **Multiple returns** | Need class/object | Built-in `(type1, type2)` | +| **Null check** | `if (obj != null)` | `if obj != nil` | +| **String concatenation** | `+` operator | `+` or `fmt.Sprintf` | + +--- + +### Practice Questions: +1. How does Go's type inference differ from Java's type declarations? +2. Why doesn't Go have a ternary operator? How would you achieve the same effect? +3. Explain the difference between Go's arrays and slices, and compare them to Java's arrays and ArrayList. +4. Write a Go function that returns multiple values, and show how to handle each returned value. + +### Project Idea: +- Create a simple grade calculator program that: + - Takes student scores as input + - Calculates average and letter grade + - Uses multiple return values for the result + - Demonstrates if/else and switch statements + +### Next Steps: +- Learn about Go's approach to object-oriented programming +- Understand Go's package system +- Explore Go's powerful concurrency model diff --git a/content/docs/java2go/module-01-syntax-comparison.zh-cn.mdx b/content/docs/java2go/module-01-syntax-comparison.zh-cn.mdx new file mode 100644 index 0000000..cb5e780 --- /dev/null +++ b/content/docs/java2go/module-01-syntax-comparison.zh-cn.mdx @@ -0,0 +1,899 @@ +--- +title: "模块 01:语法对比 - Java 到 Go" +--- + +本模块探讨 Java 和 Go 之间的基本语法差异,帮助你有效地将 Java 知识迁移到 Go。 + +## 变量声明 + +### 哲学差异 + +**Java:** 显式类型,冗长的声明 +```java +String name = "Alice"; +int age = 25; +double salary = 50000.50; +boolean isActive = true; +``` + +**Go:** 类型推断,简洁语法 +```go +name := "Alice" // 推断为 string +age := 25 // 推断为 int +salary := 50000.50 // 推断为 float64 +isActive := true // 推断为 bool +``` + +### 声明方法对比 + +| Java | Go | 描述 | +|------|-----|-------------| +| `String name = "Alice";` | `name := "Alice"` | 类型推断的简短声明 | +| `final String NAME = "Bob";` | `const NAME = "Bob"` | 常量声明 | +| `String name;` | `var name string` | 零值变量 | +| `int x = 10;` | `x := 10` 或 `var x int = 10` | 显式 vs 推断类型 | + + +```java !! java +// Java: 变量声明 +public class Variables { + public static void main(String[] args) { + // 显式类型声明 + String name = "Alice"; + int age = 30; + double salary = 75000.50; + boolean isActive = true; + + // Final 常量 + final int MAX_COUNT = 100; + + // 声明但不初始化(对象为 null) + String uninitialized; + uninitialized = "现在已初始化"; + + // 多个声明 + int x = 10, y = 20, z = 30; + + System.out.println("Name: " + name); + System.out.println("Age: " + age); + System.out.println("Salary: " + salary); + System.out.println("Active: " + isActive); + } +} +``` + +```go !! go +// Go: 变量声明 +package main + +import "fmt" + +func main() { + // 简短声明(类型推断)- 最常用 + name := "Alice" + age := 30 + salary := 75000.50 + isActive := true + + // 常量声明 + const MAX_COUNT = 100 + + // 零值变量(Go 中没有未初始化的变量) + var uninitialized string + uninitialized = "现在已初始化" + + // 多个声明 + x, y, z := 10, 20, 30 + + // 显式类型声明 + var employeeId int = 12345 + + fmt.Printf("Name: %s\n", name) + fmt.Printf("Age: %d\n", age) + fmt.Printf("Salary: %.2f\n", salary) + fmt.Printf("Active: %t\n", isActive) + fmt.Printf("Employee ID: %d\n", employeeId) +} +``` + + +### Go 中的零值 + +与 Java 中未初始化的对象为 `null` 不同,Go 总是将变量初始化为零值: + +| Go 类型 | 零值 | Java 等价物 | +|---------|------------|-----------------| +| `int`, `int64` 等 | `0` | `0` | +| `float64`, `float32` | `0.0` | `0.0` | +| `bool` | `false` | `false` | +| `string` | `""`(空字符串)| `null` | +| 指针类型 | `nil` | `null` | +| `slice` | `nil` | `null` | + + +```java !! java +// Java: Null vs 默认值 +public class ZeroValues { + static int primitiveInt; // 0 + static double primitiveDouble; // 0.0 + static boolean primitiveBool; // false + static String objectString; // null + + public static void main(String[] args) { + System.out.println(primitiveInt); // 0 + System.out.println(primitiveDouble); // 0.0 + System.out.println(primitiveBool); // false + System.out.println(objectString); // null + + // 需要 null 检查 + if (objectString != null) { + System.out.println(objectString.length()); + } + } +} +``` + +```go !! go +// Go: 零值(基本类型没有 Null) +package main + +import "fmt" + +var primitiveInt int +var primitiveDouble float64 +var primitiveBool bool +var objectString string + +func main() { + fmt.Println(primitiveInt) // 0 + fmt.Println(primitiveDouble) // 0 + fmt.Println(primitiveBool) // false + fmt.Println(objectString) // ""(空字符串,不是 nil!) + + // 不需要 null 检查 + fmt.Println(len(objectString)) // 0 + + // 指针示例(可以为 nil) + var pointer *int + fmt.Println(pointer) // + + if pointer != nil { + fmt.Println(*pointer) + } +} +``` + + +## 数据类型对比 + +### 基本类型 + +| Java | Go | 说明 | +|------|-----|-------| +| `byte` | `byte` | 相同(8位)| +| `short` | `int16` | 16位整数 | +| `int` | `int` | 平台相关(32或64位)| +| `long` | `int64` | 64位整数 | +| `float` | `float32` | 32位浮点 | +| `double` | `float64` | 64位浮点 | +| `char` | `rune` | Go 的 rune 是 Unicode 码点 | +| `boolean` | `bool` | 相同概念 | +| `String` | `string` | 都不可变,UTF-8 | + +### Go 特有类型 + +Go 提供了更精确的整数类型: + +```go +// 有符号整数 +int8 // 8位 (-128 到 127) +int16 // 16位 +int32 // 32位 +int64 // 64位 +int // 平台相关(32或64位) + +// 无符号整数 +uint8 // 8位 (0 到 255) - 也叫 byte +uint16 // 16位 +uint32 // 32位 +uint64 // 64位 +uint // 平台相关 +uintptr // 用于指针的无符号整数 + +// 浮点数 +float32 // 32位 IEEE 754 +float64 // 64位 IEEE 754 + +// 复数(Java 没有!) +complex64 // complex64 带 float32 实部和虚部 +complex128 // complex128 带 float64 实部和虚部 +``` + + +```java !! java +// Java: 类型系统 +public class Types { + public static void main(String[] args) { + // 基本类型 + byte b = 100; + short s = 1000; + int i = 100000; + long l = 1000000L; + float f = 3.14f; + double d = 3.14159; + char c = 'A'; + boolean bool = true; + + // String(引用类型) + String name = "Alice"; + + // 数组(固定大小) + int[] numbers = new int[5]; + numbers[0] = 10; + + // ArrayList(动态) + java.util.List list = new java.util.ArrayList<>(); + list.add("Hello"); + + System.out.println("Int: " + i); + System.out.println("Double: " + d); + System.out.println("String: " + name); + } +} +``` + +```go !! go +// Go: 类型系统 +package main + +import "fmt" + +func main() { + // 基本类型 + var b byte = 100 // uint8 + var s int16 = 1000 + var i int = 100000 + var l int64 = 1000000 + var f float32 = 3.14 + var d float64 = 3.14159 + var c rune = 'A' // rune 是 int32 的别名 + var boolVal bool = true + + // String(内置类型,不是类) + var name string = "Alice" + + // 数组(固定大小) + var numbers [5]int + numbers[0] = 10 + + // 切片(动态数组)- 内置,无需导入 + var slice []int = []int{1, 2, 3} + slice = append(slice, 4) // 添加元素 + + // Map(内置) + var person map[string]int = map[string]int{ + "age": 30, + } + + fmt.Printf("Int: %d\n", i) + fmt.Printf("Double: %f\n", d) + fmt.Printf("String: %s\n", name) + fmt.Printf("Slice: %v\n", slice) + fmt.Printf("Map: %v\n", person) +} +``` + + +## 控制流 + +### If/Else 语句 + +Go 的 if/else 与 Java 类似,但有重要区别: +- 条件周围不需要括号 +- 左大括号必须在 if 同一行 +- Go 中没有三元运算符 + + +```java !! java +// Java: If/Else +public class Conditionals { + public static void main(String[] args) { + int score = 85; + + // 带括号 + if (score >= 90) { + System.out.println("A"); + } else if (score >= 80) { + System.out.println("B"); + } else { + System.out.println("C"); + } + + // 三元运算符 + String result = (score >= 60) ? "Pass" : "Fail"; + System.out.println(result); + + // Null 检查 + String name = null; + if (name != null && name.length() > 0) { + System.out.println(name); + } + } +} +``` + +```go !! go +// Go: If/Else +package main + +import "fmt" + +func main() { + score := 85 + + // 不需要括号 + if score >= 90 { + fmt.Println("A") + } else if score >= 80 { + fmt.Println("B") + } else { + fmt.Println("C") + } + + // Go 中没有三元运算符! + // 使用常规 if/else 代替 + var result string + if score >= 60 { + result = "Pass" + } else { + result = "Fail" + } + fmt.Println(result) + + // 带初始化语句的 if + if name := getName(); name != "" { + fmt.Printf("Name: %s\n", name) + } + + // 一个 if 中的多个条件 + x := 10 + switch { + case x < 0: + fmt.Println("Negative") + case x < 10: + fmt.Println("Single digit") + default: + fmt.Println("Double digit") + } +} + +func getName() string { + return "Alice" +} +``` + + +### Switch 语句 + +Go 的 switch 比 Java 更强大: +- 不需要 break(默认不穿透) +- 可以对任何类型切换,不只是整数 +- 一个 case 中可以有多个值 +- 不需要 default + + +```java !! java +// Java: Switch 语句 +public class SwitchCase { + public static void main(String[] args) { + int dayOfWeek = 3; + + // 传统 switch + switch (dayOfWeek) { + case 1: + System.out.println("Monday"); + break; // 需要防止穿透 + case 2: + System.out.println("Tuesday"); + break; + case 3: + System.out.println("Wednesday"); + break; + default: + System.out.println("Other day"); + } + + // Java 14+ 增强型 switch + String dayType = switch (dayOfWeek) { + case 1, 2, 3, 4, 5 -> "Weekday"; + case 6, 7 -> "Weekend"; + default -> "Invalid"; + }; + System.out.println(dayType); + } +} +``` + +```go !! go +// Go: Switch 语句 +package main + +import "fmt" + +func main() { + dayOfWeek := 3 + + // 不需要 break!默认不穿透 + switch dayOfWeek { + case 1: + fmt.Println("Monday") + case 2: + fmt.Println("Tuesday") + case 3: + fmt.Println("Wednesday") + default: + fmt.Println("Other day") + } + + // 一个 case 中的多个值 + switch dayOfWeek { + case 1, 2, 3, 4, 5: + fmt.Println("Weekday") + case 6, 7: + fmt.Println("Weekend") + default: + fmt.Println("Invalid") + } + + // 带条件的 switch(不需要表达式) + score := 85 + switch { + case score >= 90: + fmt.Println("A") + case score >= 80: + fmt.Println("B") + case score >= 70: + fmt.Println("C") + default: + fmt.Println("D") + } + + // 字符串 switch(以及任何类型!) + OS := "linux" + switch OS { + case "darwin": + fmt.Println("macOS") + case "linux": + fmt.Println("Linux") + case "windows": + fmt.Println("Windows") + } +} +``` + + +## 循环 + +### For 循环 - Go 中唯一的循环 + +Go 只有 `for` 循环(没有 `while`,没有 `do-while`),但非常灵活: + + +```java !! java +// Java: 多种循环类型 +public class Loops { + public static void main(String[] args) { + // 传统 for 循环 + for (int i = 0; i < 5; i++) { + System.out.println("Count: " + i); + } + + // 增强型 for 循环(for-each) + int[] numbers = {1, 2, 3, 4, 5}; + for (int num : numbers) { + System.out.println(num); + } + + // While 循环 + int count = 0; + while (count < 5) { + System.out.println("While: " + count); + count++; + } + + // Do-while 循环 + do { + System.out.println("Do-while: " + count); + count--; + } while (count > 0); + } +} +``` + +```go !! go +// Go: 只有 for 循环,但非常灵活 +package main + +import "fmt" + +func main() { + // 传统 for 循环 + for i := 0; i < 5; i++ { + fmt.Printf("Count: %d\n", i) + } + + // 基于 range 的 for(类似增强型 for) + numbers := []int{1, 2, 3, 4, 5} + for i, num := range numbers { + fmt.Printf("Index %d: %d\n", i, num) + } + + // 只有值的 range + for _, num := range numbers { + fmt.Printf("Value: %d\n", num) + } + + // 只有索引的 range + for i := range numbers { + fmt.Printf("Index: %d\n", i) + } + + // While 风格循环(省略所有三个组件) + count := 0 + for count < 5 { + fmt.Printf("While: %d\n", count) + count++ + } + + // 无限循环 + sum := 0 + for { + sum++ + if sum > 10 { + break + } + } + fmt.Printf("Sum: %d\n", sum) + + // Range 遍历 map + person := map[string]int{ + "age": 30, + "score": 85, + } + for key, value := range person { + fmt.Printf("%s: %d\n", key, value) + } + + // Range 遍历字符串(按 rune) + for i, char := range "Hello" { + fmt.Printf("Index %d: %c\n", i, char) + } +} +``` + + +## 函数 + +### 函数声明 + + +```java !! java +// Java: 函数方法 +public class Functions { + // 静态方法(类似 Go 函数) + public static int add(int a, int b) { + return a + b; + } + + // 带返回类型的方法 + public String greet(String name) { + return "Hello, " + name; + } + + // Void 方法 + public void printMessage(String message) { + System.out.println(message); + } + + // 多返回值?无法直接实现! + // 需要使用类或数组 + + // 可变参数 + public int sum(int... numbers) { + int total = 0; + for (int num : numbers) { + total += num; + } + return total; + } + + public static void main(String[] args) { + System.out.println(add(5, 3)); + + Functions f = new Functions(); + System.out.println(f.greet("Alice")); + f.printMessage("Hello"); + + System.out.println(sum(1, 2, 3, 4, 5)); + } +} +``` + +```go !! go +// Go: 多返回值函数! +package main + +import "fmt" + +// 简单函数 +func add(a int, b int) int { + return a + b +} + +// 简写参数类型 +func subtract(a, b int) int { + return a - b +} + +// 多返回值(非常有用!) +func divide(a, b float64) (float64, error) { + if b == 0 { + return 0, fmt.Errorf("除以零") + } + return a / b, nil +} + +// 命名返回值 +func getDimensions() (width, height int) { + width = 100 + height = 200 + return // 裸返回(返回 width, height) +} + +// 可变函数(可变参数) +func sum(numbers ...int) int { + total := 0 + for _, num := range numbers { + total += num + } + return total +} + +// 函数作为类型(一等公民) +var operation func(int, int) int = func(a, b int) int { + return a * b +} + +func main() { + fmt.Println(add(5, 3)) + fmt.Println(subtract(10, 3)) + + // 多返回值 + result, err := divide(10, 2) + if err != nil { + fmt.Println("Error:", err) + } else { + fmt.Println("Result:", result) + } + + // 命名返回值 + w, h := getDimensions() + fmt.Printf("Dimensions: %d x %d\n", w, h) + + // 可变函数 + fmt.Println(sum(1, 2, 3, 4, 5)) + + // 函数作为变量 + fmt.Println(operation(5, 6)) +} +``` + + +### Go 中的方法 + +与 Java 不同,方法在结构体外部定义: + + +```java !! java +// Java: 类内部的方法 +public class Person { + private String name; + private int age; + + public Person(String name, int age) { + this.name = name; + this.age = age; + } + + // 实例方法 + public String getName() { + return name; + } + + public void setAge(int age) { + this.age = age; + } + + public String introduce() { + return "Hi, I'm " + name; + } + + public static void main(String[] args) { + Person p = new Person("Alice", 30); + System.out.println(p.introduce()); + } +} +``` + +```go !! go +// Go: 结构体外部定义的方法 +package main + +import "fmt" + +// 结构体定义 +type Person struct { + Name string + Age int +} + +// 构造函数(地道的 Go) +func NewPerson(name string, age int) *Person { + return &Person{ + Name: name, + Age: age, + } +} + +// 值接收者方法 +func (p Person) GetName() string { + return p.Name +} + +// 指针接收者方法(可修改) +func (p *Person) SetAge(age int) { + p.Age = age +} + +// 方法 +func (p Person) Introduce() string { + return fmt.Sprintf("Hi, I'm %s", p.Name) +} + +func main() { + // 构造函数 + p := NewPerson("Alice", 30) + fmt.Println(p.Introduce()) + + p.SetAge(31) + fmt.Printf("Age: %d\n", p.Age) +} +``` + + +## 数组和切片 + + +```java !! java +// Java: 数组和 ArrayList +import java.util.ArrayList; +import java.util.List; + +public class Arrays { + public static void main(String[] args) { + // 数组(固定大小) + int[] fixedArray = new int[5]; + fixedArray[0] = 10; + + // 数组字面量 + int[] numbers = {1, 2, 3, 4, 5}; + System.out.println("Length: " + numbers.length); + + // ArrayList(动态) + List list = new ArrayList<>(); + list.add("Hello"); + list.add("World"); + list.add("Java"); + + System.out.println("List size: " + list.size()); + System.out.println("Element: " + list.get(0)); + + // 迭代 + for (String item : list) { + System.out.println(item); + } + + // 子列表 + List subList = list.subList(0, 2); + System.out.println("Sublist: " + subList); + } +} +``` + +```go !! go +// Go: 数组和切片 +package main + +import "fmt" + +func main() { + // 数组(固定大小)- 很少直接使用 + var fixedArray [5]int + fixedArray[0] = 10 + + // 数组字面量 + numbers := [5]int{1, 2, 3, 4, 5} + fmt.Printf("Array length: %d\n", len(numbers)) + + // 切片(数组的动态视图)- 最常用 + var slice []int + slice = append(slice, 1) + slice = append(slice, 2) + slice = append(slice, 3) + + // 切片字面量 + fruits := []string{"Apple", "Banana", "Cherry"} + fmt.Printf("Slice length: %d\n", len(fruits)) + fmt.Printf("Element: %s\n", fruits[0]) + + // 使用 range 迭代 + for i, fruit := range fruits { + fmt.Printf("Index %d: %s\n", i, fruit) + } + + // 切片切片(子切片) + subSlice := fruits[0:2] // 元素 0 和 1 + fmt.Printf("Sub-slice: %v\n", subSlice) + + // 追加到切片 + fruits = append(fruits, "Date") + fmt.Printf("After append: %v\n", fruits) + + // 复制切片 + copySlice := make([]string, len(fruits)) + copy(copySlice, fruits) + fmt.Printf("Copy: %v\n", copySlice) + + // 预分配带容量的切片 + bigSlice := make([]int, 0, 100) // 长度 0,容量 100 + fmt.Printf("Length: %d, Capacity: %d\n", len(bigSlice), cap(bigSlice)) +} +``` + + +## 关键语法差异总结 + +| 特性 | Java | Go | +|---------|------|-----| +| **分号** | 必需 | 可选(推断)| +| **if/for 中的括号** | 必需 | 可选 | +| **大括号位置** | 灵活 | 必须在同一行 | +| **三元运算符** | `condition ? true : false` | 无三元,使用 if/else | +| **While 循环** | `while (condition)` | `for condition` | +| **Do-while** | `do {} while (condition)` | 无直接等价物 | +| **For-each** | `for (item : collection)` | `for item := range collection` | +| **Break/Continue** | `break label` | `break label`(类似)| +| **Switch 穿透** | 默认(需要 break)| 使用 `fallthrough` 关键字选择加入 | +| **多返回值** | 需要类/对象 | 内置 `(type1, type2)` | +| **Null 检查** | `if (obj != null)` | `if obj != nil` | +| **字符串连接** | `+` 运算符 | `+` 或 `fmt.Sprintf` | + +--- + +### 练习题: +1. Go 的类型推断与 Java 的类型声明有何不同? +2. 为什么 Go 没有三元运算符?你如何实现相同的效果? +3. 解释 Go 的数组和切片的区别,并将它们与 Java 的数组和 ArrayList 进行比较。 +4. 编写一个返回多个值的 Go 函数,并展示如何处理每个返回值。 + +### 项目想法: +- 创建一个简单的成绩计算器程序,它: + - 接受学生成绩作为输入 + - 计算平均分和字母等级 + - 使用多返回值作为结果 + - 演示 if/else 和 switch 语句 + +### 下一步: +- 学习 Go 的面向对象编程方法 +- 理解 Go 的包系统 +- 探索 Go 强大的并发模型 diff --git a/content/docs/java2go/module-01-syntax-comparison.zh-tw.mdx b/content/docs/java2go/module-01-syntax-comparison.zh-tw.mdx new file mode 100644 index 0000000..d6e73c4 --- /dev/null +++ b/content/docs/java2go/module-01-syntax-comparison.zh-tw.mdx @@ -0,0 +1,888 @@ +--- +title: "模組 01:語法對比 - Java 到 Go" +--- + +本模組探討 Java 和 Go 之間的基本語法差異,幫助你有效地將 Java 知識遷移到 Go。 + +## 變數聲明 + +### 哲學差異 + +**Java:** 顯式類型,冗長的聲明 +```java +String name = "Alice"; +int age = 25; +double salary = 50000.50; +boolean isActive = true; +``` + +**Go:** 類型推斷,簡潔語法 +```go +name := "Alice" // 推斷為 string +age := 25 // 推斷為 int +salary := 50000.50 // 推斷為 float64 +isActive := true // 推斷為 bool +``` + +### 聲明方法對比 + +| Java | Go | 描述 | +|------|-----|-------------| +| `String name = "Alice";` | `name := "Alice"` | 類型推斷的簡短聲明 | +| `final String NAME = "Bob";` | `const NAME = "Bob"` | 常量聲明 | +| `String name;` | `var name string` | 零值變數 | +| `int x = 10;` | `x := 10` 或 `var x int = 10` | 顯式 vs 推斷類型 | + + +```java !! java +// Java: 變數聲明 +public class Variables { + public static void main(String[] args) { + // 顯式類型聲明 + String name = "Alice"; + int age = 30; + double salary = 75000.50; + boolean isActive = true; + + // Final 常量 + final int MAX_COUNT = 100; + + // 聲明但不初始化(物件為 null) + String uninitialized; + uninitialized = "現在已初始化"; + + // 多個聲明 + int x = 10, y = 20, z = 30; + + System.out.println("Name: " + name); + System.out.println("Age: " + age); + System.out.println("Salary: " + salary); + System.out.println("Active: " + isActive); + } +} +``` + +```go !! go +// Go: 變數聲明 +package main + +import "fmt" + +func main() { + // 簡短聲明(類型推斷)- 最常用 + name := "Alice" + age := 30 + salary := 75000.50 + isActive := true + + // 常量聲明 + const MAX_COUNT = 100 + + // 零值變數(Go 中沒有未初始化的變數) + var uninitialized string + uninitialized = "現在已初始化" + + // 多個聲明 + x, y, z := 10, 20, 30 + + // 顯式類型聲明 + var employeeId int = 12345 + + fmt.Printf("Name: %s\n", name) + fmt.Printf("Age: %d\n", age) + fmt.Printf("Salary: %.2f\n", salary) + fmt.Printf("Active: %t\n", isActive) + fmt.Printf("Employee ID: %d\n", employeeId) +} +``` + + +### Go 中的零值 + +與 Java 中未初始化的物件為 `null` 不同,Go 總是將變數初始化為零值: + +| Go 類型 | 零值 | Java 等價物 | +|---------|------------|-----------------| +| `int`, `int64` 等 | `0` | `0` | +| `float64`, `float32` | `0.0` | `0.0` | +| `bool` | `false` | `false` | +| `string` | `""`(空字串)| `null` | +| 指標類型 | `nil` | `null` | +| `slice` | `nil` | `null` | + + +```java !! java +// Java: Null vs 預設值 +public class ZeroValues { + static int primitiveInt; // 0 + static double primitiveDouble; // 0.0 + static boolean primitiveBool; // false + static String objectString; // null + + public static void main(String[] args) { + System.out.println(primitiveInt); // 0 + System.out.println(primitiveDouble); // 0.0 + System.out.println(primitiveBool); // false + System.out.println(objectString); // null + + // 需要 null 檢查 + if (objectString != null) { + System.out.println(objectString.length()); + } + } +} +``` + +```go !! go +// Go: 零值(基本類型沒有 Null) +package main + +import "fmt" + +var primitiveInt int +var primitiveDouble float64 +var primitiveBool bool +var objectString string + +func main() { + fmt.Println(primitiveInt) // 0 + fmt.Println(primitiveDouble) // 0 + fmt.Println(primitiveBool) // false + fmt.Println(objectString) // ""(空字串,不是 nil!) + + // 不需要 null 檢查 + fmt.Println(len(objectString)) // 0 + + // 指標示例(可以為 nil) + var pointer *int + fmt.Println(pointer) // + + if pointer != nil { + fmt.Println(*pointer) + } +} +``` + + +## 資料類型對比 + +### 基本類型 + +| Java | Go | 說明 | +|------|-----|-------| +| `byte` | `byte` | 相同(8位)| +| `short` | `int16` | 16位整數 | +| `int` | `int` | 平台相關(32或64位)| +| `long` | `int64` | 64位整數 | +| `float` | `float32` | 32位浮點 | +| `double` | `float64` | 64位浮點 | +| `char` | `rune` | Go 的 rune 是 Unicode 碼點 | +| `boolean` | `bool` | 相同概念 | +| `String` | `string` | 都不可變,UTF-8 | + +### Go 特有類型 + +Go 提供了更精確的整數類型: + +```go +// 有符號整數 +int8 // 8位 (-128 到 127) +int16 // 16位 +int32 // 32位 +int64 // 64位 +int // 平台相關(32或64位) + +// 無符號整數 +uint8 // 8位 (0 到 255) - 也叫 byte +uint16 // 16位 +uint32 // 32位 +uint64 // 64位 +uint // 平台相關 +uintptr // 用於指標的無符號整數 + +// 浮點數 +float32 // 32位 IEEE 754 +float64 // 64位 IEEE 754 + +// 複數(Java 沒有!) +complex64 // complex64 带 float32 實部和虛部 +complex128 // complex128 带 float64 實部和虛部 +``` + + +```java !! java +// Java: 類型系統 +public class Types { + public static void main(String[] args) { + // 基本類型 + byte b = 100; + short s = 1000; + int i = 100000; + long l = 1000000L; + float f = 3.14f; + double d = 3.14159; + char c = 'A'; + boolean bool = true; + + // String(引用類型) + String name = "Alice"; + + // 陣列(固定大小) + int[] numbers = new int[5]; + numbers[0] = 10; + + // ArrayList(動態) + java.util.List list = new java.util.ArrayList<>(); + list.add("Hello"); + + System.out.println("Int: " + i); + System.out.println("Double: " + d); + System.out.println("String: " + name); + } +} +``` + +```go !! go +// Go: 類型系統 +package main + +import "fmt" + +func main() { + // 基本類型 + var b byte = 100 // uint8 + var s int16 = 1000 + var i int = 100000 + var l int64 = 1000000 + var f float32 = 3.14 + var d float64 = 3.14159 + var c rune = 'A' // rune 是 int32 的別名 + var boolVal bool = true + + // String(內建類型,不是類別) + var name string = "Alice" + + // 陣列(固定大小) + var numbers [5]int + numbers[0] = 10 + + // 切片(動態陣列)- 內建,無需匯入 + var slice []int = []int{1, 2, 3} + slice = append(slice, 4) // 新增元素 + + // Map(內建) + var person map[string]int = map[string]int{ + "age": 30, + } + + fmt.Printf("Int: %d\n", i) + fmt.Printf("Double: %f\n", d) + fmt.Printf("String: %s\n", name) + fmt.Printf("Slice: %v\n", slice) + fmt.Printf("Map: %v\n", person) +} +``` + + +## 控制流 + +### If/Else 陳述式 + +Go 的 if/else 與 Java 類似,但有重要區別: +- 條件周圍不需要括號 +- 左大括號必須在 if 同一行 +- Go 中沒有三元運算子 + + +```java !! java +// Java: If/Else +public class Conditionals { + public static void main(String[] args) { + int score = 85; + + // 帶括號 + if (score >= 90) { + System.out.println("A"); + } else if (score >= 80) { + System.out.println("B"); + } else { + System.out.println("C"); + } + + // 三元運算子 + String result = (score >= 60) ? "Pass" : "Fail"; + System.out.println(result); + + // Null 檢查 + String name = null; + if (name != null && name.length() > 0) { + System.out.println(name); + } + } +} +``` + +```go !! go +// Go: If/Else +package main + +import "fmt" + +func main() { + score := 85 + + // 不需要括號 + if score >= 90 { + fmt.Println("A") + } else if score >= 80 { + fmt.Println("B") + } else { + fmt.Println("C") + } + + // Go 中沒有三元運算子! + // 使用常規 if/else 代替 + var result string + if score >= 60 { + result = "Pass" + } else { + result = "Fail" + } + fmt.Println(result) + + // 帶初始化陳述式的 if + if name := getName(); name != "" { + fmt.Printf("Name: %s\n", name) + } +} + +func getName() string { + return "Alice" +} +``` + + +### Switch 陳述式 + +Go 的 switch 比 Java 更強大: +- 不需要 break(預設不穿透) +- 可以對任何類型切換,不只是整數 +- 一個 case 中可以有多個值 +- 不需要 default + + +```java !! java +// Java: Switch 陳述式 +public class SwitchCase { + public static void main(String[] args) { + int dayOfWeek = 3; + + // 傳統 switch + switch (dayOfWeek) { + case 1: + System.out.println("Monday"); + break; // 需要防止穿透 + case 2: + System.out.println("Tuesday"); + break; + case 3: + System.out.println("Wednesday"); + break; + default: + System.out.println("Other day"); + } + + // Java 14+ 增強型 switch + String dayType = switch (dayOfWeek) { + case 1, 2, 3, 4, 5 -> "Weekday"; + case 6, 7 -> "Weekend"; + default -> "Invalid"; + }; + System.out.println(dayType); + } +} +``` + +```go !! go +// Go: Switch 陳述式 +package main + +import "fmt" + +func main() { + dayOfWeek := 3 + + // 不需要 break!預設不穿透 + switch dayOfWeek { + case 1: + fmt.Println("Monday") + case 2: + fmt.Println("Tuesday") + case 3: + fmt.Println("Wednesday") + default: + fmt.Println("Other day") + } + + // 一個 case 中的多個值 + switch dayOfWeek { + case 1, 2, 3, 4, 5: + fmt.Println("Weekday") + case 6, 7: + fmt.Println("Weekend") + default: + fmt.Println("Invalid") + } + + // 帶條件的 switch(不需要表達式) + score := 85 + switch { + case score >= 90: + fmt.Println("A") + case score >= 80: + fmt.Println("B") + case score >= 70: + fmt.Println("C") + default: + fmt.Println("D") + } + + // 字串 switch(以及任何類型!) + OS := "linux" + switch OS { + case "darwin": + fmt.Println("macOS") + case "linux": + fmt.Println("Linux") + case "windows": + fmt.Println("Windows") + } +} +``` + + +## 迴圈 + +### For 迴圈 - Go 中唯一的迴圈 + +Go 只有 `for` 迴圈(沒有 `while`,沒有 `do-while`),但非常靈活: + + +```java !! java +// Java: 多種迴圈類型 +public class Loops { + public static void main(String[] args) { + // 傳統 for 迴圈 + for (int i = 0; i < 5; i++) { + System.out.println("Count: " + i); + } + + // 增強型 for 迴圈(for-each) + int[] numbers = {1, 2, 3, 4, 5}; + for (int num : numbers) { + System.out.println(num); + } + + // While 迴圈 + int count = 0; + while (count < 5) { + System.out.println("While: " + count); + count++; + } + + // Do-while 迴圈 + do { + System.out.println("Do-while: " + count); + count--; + } while (count > 0); + } +} +``` + +```go !! go +// Go: 只有 for 迴圈,但非常靈活 +package main + +import "fmt" + +func main() { + // 傳統 for 迴圈 + for i := 0; i < 5; i++ { + fmt.Printf("Count: %d\n", i) + } + + // 基於 range 的 for(類似增強型 for) + numbers := []int{1, 2, 3, 4, 5} + for i, num := range numbers { + fmt.Printf("Index %d: %d\n", i, num) + } + + // 只有值的 range + for _, num := range numbers { + fmt.Printf("Value: %d\n", num) + } + + // 只有索引的 range + for i := range numbers { + fmt.Printf("Index: %d\n", i) + } + + // While 風格迴圈(省略所有三個元件) + count := 0 + for count < 5 { + fmt.Printf("While: %d\n", count) + count++ + } + + // 無限迴圈 + sum := 0 + for { + sum++ + if sum > 10 { + break + } + } + fmt.Printf("Sum: %d\n", sum) + + // Range 遍歷 map + person := map[string]int{ + "age": 30, + "score": 85, + } + for key, value := range person { + fmt.Printf("%s: %d\n", key, value) + } + + // Range 遍歷字串(按 rune) + for i, char := range "Hello" { + fmt.Printf("Index %d: %c\n", i, char) + } +} +``` + + +## 函式 + +### 函式聲明 + + +```java !! java +// Java: 函式方法 +public class Functions { + // 靜態方法(類似 Go 函式) + public static int add(int a, int b) { + return a + b; + } + + // 帶返回類型的方法 + public String greet(String name) { + return "Hello, " + name; + } + + // Void 方法 + public void printMessage(String message) { + System.out.println(message); + } + + // 多返回值?無法直接實現! + // 需要使用類別或陣列 + + // 可變參數 + public int sum(int... numbers) { + int total = 0; + for (int num : numbers) { + total += num; + } + return total; + } + + public static void main(String[] args) { + System.out.println(add(5, 3)); + + Functions f = new Functions(); + System.out.println(f.greet("Alice")); + f.printMessage("Hello"); + + System.out.println(sum(1, 2, 3, 4, 5)); + } +} +``` + +```go !! go +// Go: 多返回值函式! +package main + +import "fmt" + +// 簡單函式 +func add(a int, b int) int { + return a + b +} + +// 簡寫參數類型 +func subtract(a, b int) int { + return a - b +} + +// 多返回值(非常有用!) +func divide(a, b float64) (float64, error) { + if b == 0 { + return 0, fmt.Errorf("除以零") + } + return a / b, nil +} + +// 命名返回值 +func getDimensions() (width, height int) { + width = 100 + height = 200 + return // 裸返回(返回 width, height) +} + +// 可變函式(可變參數) +func sum(numbers ...int) int { + total := 0 + for _, num := range numbers { + total += num + } + return total +} + +// 函式作為類型(一等公民) +var operation func(int, int) int = func(a, b int) int { + return a * b +} + +func main() { + fmt.Println(add(5, 3)) + fmt.Println(subtract(10, 3)) + + // 多返回值 + result, err := divide(10, 2) + if err != nil { + fmt.Println("Error:", err) + } else { + fmt.Println("Result:", result) + } + + // 命名返回值 + w, h := getDimensions() + fmt.Printf("Dimensions: %d x %d\n", w, h) + + // 可變函式 + fmt.Println(sum(1, 2, 3, 4, 5)) + + // 函式作為變數 + fmt.Println(operation(5, 6)) +} +``` + + +### Go 中的方法 + +與 Java 不同,方法在結構體外部定義: + + +```java !! java +// Java: 類別內部的方法 +public class Person { + private String name; + private int age; + + public Person(String name, int age) { + this.name = name; + this.age = age; + } + + // 實例方法 + public String getName() { + return name; + } + + public void setAge(int age) { + this.age = age; + } + + public String introduce() { + return "Hi, I'm " + name; + } + + public static void main(String[] args) { + Person p = new Person("Alice", 30); + System.out.println(p.introduce()); + } +} +``` + +```go !! go +// Go: 結構體外部定義的方法 +package main + +import "fmt" + +// 結構體定義 +type Person struct { + Name string + Age int +} + +// 建構函式(地道的 Go) +func NewPerson(name string, age int) *Person { + return &Person{ + Name: name, + Age: age, + } +} + +// 值接收者方法 +func (p Person) GetName() string { + return p.Name +} + +// 指標接收者方法(可修改) +func (p *Person) SetAge(age int) { + p.Age = age +} + +// 方法 +func (p Person) Introduce() string { + return fmt.Sprintf("Hi, I'm %s", p.Name) +} + +func main() { + // 建構函式 + p := NewPerson("Alice", 30) + fmt.Println(p.Introduce()) + + p.SetAge(31) + fmt.Printf("Age: %d\n", p.Age) +} +``` + + +## 陣列和切片 + + +```java !! java +// Java: 陣列和 ArrayList +import java.util.ArrayList; +import java.util.List; + +public class Arrays { + public static void main(String[] args) { + // 陣列(固定大小) + int[] fixedArray = new int[5]; + fixedArray[0] = 10; + + // 陣列字面量 + int[] numbers = {1, 2, 3, 4, 5}; + System.out.println("Length: " + numbers.length); + + // ArrayList(動態) + List list = new ArrayList<>(); + list.add("Hello"); + list.add("World"); + list.add("Java"); + + System.out.println("List size: " + list.size()); + System.out.println("Element: " + list.get(0)); + + // 迭代 + for (String item : list) { + System.out.println(item); + } + + // 子列表 + List subList = list.subList(0, 2); + System.out.println("Sublist: " + subList); + } +} +``` + +```go !! go +// Go: 陣列和切片 +package main + +import "fmt" + +func main() { + // 陣列(固定大小)- 很少直接使用 + var fixedArray [5]int + fixedArray[0] = 10 + + // 陣列字面量 + numbers := [5]int{1, 2, 3, 4, 5} + fmt.Printf("Array length: %d\n", len(numbers)) + + // 切片(陣列的動態視圖)- 最常用 + var slice []int + slice = append(slice, 1) + slice = append(slice, 2) + slice = append(slice, 3) + + // 切片字面量 + fruits := []string{"Apple", "Banana", "Cherry"} + fmt.Printf("Slice length: %d\n", len(fruits)) + fmt.Printf("Element: %s\n", fruits[0]) + + // 使用 range 迭代 + for i, fruit := range fruits { + fmt.Printf("Index %d: %s\n", i, fruit) + } + + // 切片切片(子切片) + subSlice := fruits[0:2] // 元素 0 和 1 + fmt.Printf("Sub-slice: %v\n", subSlice) + + // 追加到切片 + fruits = append(fruits, "Date") + fmt.Printf("After append: %v\n", fruits) + + // 複製切片 + copySlice := make([]string, len(fruits)) + copy(copySlice, fruits) + fmt.Printf("Copy: %v\n", copySlice) + + // 預分配帶容量的切片 + bigSlice := make([]int, 0, 100) // 長度 0,容量 100 + fmt.Printf("Length: %d, Capacity: %d\n", len(bigSlice), cap(bigSlice)) +} +``` + + +## 關鍵語法差異總結 + +| 特性 | Java | Go | +|---------|------|-----| +| **分號** | 必需 | 可選(推斷)| +| **if/for 中的括號** | 必需 | 可選 | +| **大括號位置** | 靈活 | 必須在同一行 | +| **三元運算子** | `condition ? true : false` | 無三元,使用 if/else | +| **While 迴圈** | `while (condition)` | `for condition` | +| **Do-while** | `do {} while (condition)` | 無直接等價物 | +| **For-each** | `for (item : collection)` | `for item := range collection` | +| **Break/Continue** | `break label` | `break label`(類似)| +| **Switch 穿透** | 預設(需要 break)| 使用 `fallthrough` 關鍵字選擇加入 | +| **多返回值** | 需要類別/物件 | 內建 `(type1, type2)` | +| **Null 檢查** | `if (obj != null)` | `if obj != nil` | +| **字串串接** | `+` 運算子 | `+` 或 `fmt.Sprintf` | + +--- + +### 練習題: +1. Go 的類型推斷與 Java 的類型聲明有何不同? +2. 為什麼 Go 沒有三元運算子?你如何實現相同的效果? +3. 解釋 Go 的陣列和切片的區別,並將它們與 Java 的陣列和 ArrayList 進行比較。 +4. 編寫一個返回多個值的 Go 函式,並展示如何處理每個返回值。 + +### 專案想法: +- 建立一個簡單的成績計算器程式,它: + - 接受學生成績作為輸入 + - 計算平均分和字母等級 + - 使用多返回值作為結果 + - 演示 if/else 和 switch 陳述式 + +### 下一步: +- 學習 Go 的物件導向程式設計方法 +- 理解 Go 的套件系統 +- 探索 Go 強大的並發模型 diff --git a/content/docs/java2go/module-02-oop-to-go.mdx b/content/docs/java2go/module-02-oop-to-go.mdx new file mode 100644 index 0000000..c7ae9e2 --- /dev/null +++ b/content/docs/java2go/module-02-oop-to-go.mdx @@ -0,0 +1,1288 @@ +--- +title: "Module 02: OOP to Go" +description: "Learn how Go handles object-oriented programming without classes - using structs, methods, and composition instead of traditional inheritance" +--- + +# Module 02: OOP to Go + +Welcome to Module 02! In this module, you'll learn how Go approaches object-oriented programming differently from Java. While Java relies heavily on classes and inheritance, Go uses structs, methods, and composition to achieve similar goals with simpler, more flexible code. + +## Learning Objectives + +By the end of this module, you will: +- Understand how Go implements OOP concepts without classes +- Learn how to use structs instead of classes +- Master value receivers vs pointer receivers +- Understand composition over inheritance +- Learn factory functions instead of constructors +- Compare Java OOP patterns with Go equivalents + +## 1. The Big Picture: Classes vs Structs + +### Java: Classes and Objects + +In Java, everything is built around classes. A class defines both data (fields) and behavior (methods): + +```java +public class Person { + // Private fields + private String name; + private int age; + + // Constructor + public Person(String name, int age) { + this.name = name; + this.age = age; + } + + // Methods + public String getName() { + return name; + } + + public void setAge(int age) { + this.age = age; + } + + public void introduce() { + System.out.println("Hi, I'm " + name + ", " + age + " years old"); + } +} +``` + +### Go: Structs and Methods + +Go separates data definition (structs) from behavior (methods): + + +```java !! java +public class Person { + private String name; + private int age; + + public Person(String name, int age) { + this.name = name; + this.age = age; + } + + public String getName() { + return name; + } + + public void introduce() { + System.out.println("Hi, I'm " + name); + } +} +``` + +```go !! go +// Person struct - data only +type Person struct { + Name string + Age int +} + +// Method defined on Person +func (p Person) Introduce() { + fmt.Printf("Hi, I'm %s\n", p.Name) +} + +// Note: Go doesn't have getters/setters +// Direct access is idiomatic for simple fields +``` + + +## 2. Defining Structs + +### Basic Struct Declaration + + +```java !! java +public class Rectangle { + private double width; + private double height; + + public Rectangle(double width, double height) { + this.width = width; + this.height = height; + } + + public double getWidth() { return width; } + public double getHeight() { return height; } + public void setWidth(double width) { this.width = width; } + public void setHeight(double height) { this.height = height; } +} +``` + +```go !! go +// Rectangle struct +type Rectangle struct { + Width float64 + Height float64 +} + +// No need for getters/setters in Go +// Direct field access is idiomatic +func main() { + r := Rectangle{Width: 10.0, Height: 5.0} + fmt.Println(r.Width) // Direct access + r.Width = 15.0 // Direct modification +} +``` + + +### Struct with Different Field Types + + +```java !! java +public class Employee { + private int id; + private String name; + private double salary; + private String[] skills; + private Address address; + + public Employee(int id, String name, double salary, String[] skills, Address address) { + this.id = id; + this.name = name; + this.salary = salary; + this.skills = skills; + this.address = address; + } +} + +class Address { + private String street; + private String city; + // constructor, getters, etc. +} +``` + +```go !! go +// Employee struct with multiple field types +type Employee struct { + ID int + Name string + Salary float64 + Skills []string + Address Address // Embedded struct +} + +// Address struct +type Address struct { + Street string + City string +} + +func main() { + emp := Employee{ + ID: 1, + Name: "Alice", + Salary: 75000.0, + Skills: []string{"Go", "Java", "Python"}, + Address: Address{ + Street: "123 Main St", + City: "San Francisco", + }, + } +} +``` + + +## 3. Methods: Value vs Pointer Receivers + +This is one of the most important concepts in Go! Methods can be defined on either values or pointers. + +### Value Receivers + + +```java !! java +public class Counter { + private int count; + + public Counter(int count) { + this.count = count; + } + + // This method doesn't modify state + public int getCount() { + return count; + } + + // This method DOES modify state + public void increment() { + this.count++; + } +} +``` + +```go !! go +type Counter struct { + Count int +} + +// Value receiver - doesn't modify original +func (c Counter) GetCount() int { + return c.Count +} + +// Pointer receiver - CAN modify original +func (c *Counter) Increment() { + c.Count++ // Modifies the original Counter +} + +func main() { + counter := Counter{Count: 0} + + // Value receiver creates a copy + fmt.Println(counter.GetCount()) // 0 + + // Pointer receiver modifies original + counter.Increment() + fmt.Println(counter.Count) // 1 +} +``` + + +### When to Use Value vs Pointer Receivers + + +```java !! java +// In Java, methods on objects can always modify state +// You don't have to think about it + +public class Circle { + private double radius; + + public Circle(double radius) { + this.radius = radius; + } + + // Method that calculates (no state change) + public double getArea() { + return Math.PI * radius * radius; + } + + // Method that modifies state + public void setRadius(double radius) { + this.radius = radius; + } +} +``` + +```go !! go +type Circle struct { + Radius float64 +} + +// Use VALUE receiver when: +// - Method doesn't need to modify struct +// - Method is more efficient with small structs +// - You want immutability +func (c Circle) Area() float64 { + return math.Pi * c.Radius * c.Radius +} + +// Use POINTER receiver when: +// - Method needs to modify struct +// - Struct is large (avoid copying) +// - Consistency: if one method uses pointer, all should +func (c *Circle) SetRadius(r float64) { + c.Radius = r +} + +func (c *Circle) Grow(factor float64) { + c.Radius *= factor +} +``` + + +### Practical Example: Bank Account + + +```java !! java +public class BankAccount { + private String owner; + private double balance; + + public BankAccount(String owner, double initialBalance) { + this.owner = owner; + this.balance = initialBalance; + } + + // Query method - no state change + public double getBalance() { + return balance; + } + + // Modify state + public void deposit(double amount) { + if (amount > 0) { + balance += amount; + } + } + + public boolean withdraw(double amount) { + if (amount > 0 && balance >= amount) { + balance -= amount; + return true; + } + return false; + } +} +``` + +```go !! go +type BankAccount struct { + Owner string + Balance float64 +} + +// Value receiver for queries +func (a BankAccount) GetBalance() float64 { + return a.Balance +} + +// Pointer receiver for modifications +func (a *BankAccount) Deposit(amount float64) { + if amount > 0 { + a.Balance += amount + } +} + +func (a *BankAccount) Withdraw(amount float64) bool { + if amount > 0 && a.Balance >= amount { + a.Balance -= amount + return true + } + return false +} + +func main() { + account := BankAccount{ + Owner: "Alice", + Balance: 1000.0, + } + + account.Deposit(500.0) + account.Withdraw(200.0) + fmt.Println(account.GetBalance()) // 1300.0 +} +``` + + +## 4. No Constructors: Factory Functions + +Go doesn't have constructors. Instead, use factory functions. + +### Basic Factory Function + + +```java !! java +public class User { + private String username; + private String email; + private int age; + + // Constructor + public User(String username, String email, int age) { + this.username = username; + this.email = email; + this.age = age; + } + + // Static factory method + public static User createAdult(String username, String email) { + return new User(username, email, 18); + } +} +``` + +```go !! go +type User struct { + Username string + Email string + Age int +} + +// Factory function (convention: NewTypeName) +func NewUser(username, email string, age int) *User { + return &User{ + Username: username, + Email: email, + Age: age, + } +} + +// Factory function with default values +func NewAdultUser(username, email string) *User { + return &User{ + Username: username, + Email: email, + Age: 18, + } +} + +func main() { + // Using factory function + user := NewUser("alice", "alice@example.com", 25) + adult := NewAdultUser("bob", "bob@example.com") +} +``` + + +### Factory Function with Validation + + +```java !! java +public class Product { + private String name; + private double price; + + private Product(String name, double price) { + this.name = name; + this.price = price; + } + + public static Product create(String name, double price) { + if (name == null || name.isEmpty()) { + throw new IllegalArgumentException("Name cannot be empty"); + } + if (price < 0) { + throw new IllegalArgumentException("Price cannot be negative"); + } + return new Product(name, price); + } +} +``` + +```go !! go +type Product struct { + Name string + Price float64 +} + +// Factory function returning error for validation +func NewProduct(name string, price float64) (*Product, error) { + if name == "" { + return nil, errors.New("name cannot be empty") + } + if price < 0 { + return nil, errors.New("price cannot be negative") + } + + return &Product{ + Name: name, + Price: price, + }, nil +} + +func main() { + product, err := NewProduct("Laptop", 999.99) + if err != nil { + fmt.Println("Error creating product:", err) + return + } + fmt.Println("Created:", product.Name) +} +``` + + +### Multiple Constructor Variants + + +```java !! java +public class Configuration { + private String host; + private int port; + private boolean useSSL; + private int timeout; + + public Configuration(String host, int port) { + this(host, port, true, 30); + } + + public Configuration(String host, int port, boolean useSSL) { + this(host, port, useSSL, 30); + } + + public Configuration(String host, int port, boolean useSSL, int timeout) { + this.host = host; + this.port = port; + this.useSSL = useSSL; + this.timeout = timeout; + } +} +``` + +```go !! go +type Configuration struct { + Host string + Port int + UseSSL bool + Timeout int +} + +// Default configuration +func NewDefaultConfig() *Configuration { + return &Configuration{ + Host: "localhost", + Port: 8080, + UseSSL: true, + Timeout: 30, + } +} + +// Custom configuration +func NewConfig(host string, port int) *Configuration { + return &Configuration{ + Host: host, + Port: port, + UseSSL: true, + Timeout: 30, + } +} + +// Full custom configuration +func NewCustomConfig(host string, port int, useSSL bool, timeout int) *Configuration { + return &Configuration{ + Host: host, + Port: port, + UseSSL: useSSL, + Timeout: timeout, + } +} +``` + + +## 5. Composition Over Inheritance + +Go doesn't have inheritance. Instead, it uses composition to build complex types from simpler ones. + +### Java: Inheritance + +```java +public class Animal { + protected String name; + + public Animal(String name) { + this.name = name; + } + + public void eat() { + System.out.println(name + " is eating"); + } + + public void sleep() { + System.out.println(name + " is sleeping"); + } +} + +public class Dog extends Animal { + private String breed; + + public Dog(String name, String breed) { + super(name); + this.breed = breed; + } + + public void bark() { + System.out.println(name + " is barking"); + } +} +``` + +### Go: Composition + + +```java !! java +// Java: Using inheritance +public class Vehicle { + protected String make; + protected String model; + + public Vehicle(String make, String model) { + this.make = make; + this.model = model; + } + + public void start() { + System.out.println("Vehicle starting"); + } +} + +public class Car extends Vehicle { + private int numDoors; + + public Car(String make, String model, int numDoors) { + super(make, model); + this.numDoors = numDoors; + } + + public void honk() { + System.out.println("Beep beep!"); + } +} +``` + +```go !! go +// Go: Using composition +type Vehicle struct { + Make string + Model string +} + +func (v Vehicle) Start() { + fmt.Println("Vehicle starting") +} + +// Car embeds Vehicle (composition) +type Car struct { + Vehicle // Embedded struct (anonymous field) + NumDoors int +} + +func (c Car) Honk() { + fmt.Println("Beep beep!") +} + +func main() { + car := Car{ + Vehicle: Vehicle{ + Make: "Toyota", + Model: "Camry", + }, + NumDoors: 4, + } + + // Car has access to Vehicle's fields and methods + car.Start() // Called through embedding + fmt.Println(car.Make) // Direct access to embedded field + car.Honk() +} +``` + + +### Embedding for Behavior Sharing + + +```java !! java +// Java: Multiple inheritance is not allowed (except interfaces) +public class Engine { + public void start() { + System.out.println("Engine starting"); + } +} + +public class AudioSystem { + public void playMusic() { + System.out.println("Playing music"); + } +} + +public class Car { + private Engine engine; + private AudioSystem audio; + + public Car() { + this.engine = new Engine(); + this.audio = new AudioSystem(); + } + + public void startEngine() { + engine.start(); + } + + public void playMusic() { + audio.playMusic(); + } +} +``` + +```go !! go +// Go: Embed multiple types +type Engine struct { + Horsepower int +} + +func (e Engine) Start() { + fmt.Println("Engine starting") +} + +type AudioSystem struct { + Brand string +} + +func (a AudioSystem) PlayMusic() { + fmt.Println("Playing music") +} + +// Car embeds both Engine and AudioSystem +type Car struct { + Engine + AudioSystem + Make string +} + +func main() { + car := Car{ + Engine: Engine{Horsepower: 200}, + AudioSystem: AudioSystem{Brand: "Bose"}, + Make: "Tesla", + } + + // Direct access to embedded methods + car.Start() // From Engine + car.PlayMusic() // From AudioSystem + fmt.Println(car.Make) +} +``` + + +### Overriding Methods + + +```java !! java +public class BaseClass { + public void greet() { + System.out.println("Hello from BaseClass"); + } +} + +public class DerivedClass extends BaseClass { + @Override + public void greet() { + System.out.println("Hello from DerivedClass"); + super.greet(); // Call parent method + } +} +``` + +```go !! go +type Base struct { + Name string +} + +func (b Base) Greet() { + fmt.Println("Hello from Base") +} + +type Derived struct { + Base // Embedded + ExtraField string +} + +// Override Greet method +func (d Derived) Greet() { + fmt.Println("Hello from Derived") + // Can call Base method via field name + d.Base.Greet() +} + +func main() { + base := Base{Name: "Base"} + derived := Derived{ + Base: Base{Name: "Base"}, + ExtraField: "Extra", + } + + base.Greet() // "Hello from Base" + derived.Greet() // "Hello from Derived" then "Hello from Base" +} +``` + + +## 6. Real-World Example: File System + +Let's build a more complex example using composition. + + +```java !! java +// Java: File system with inheritance +public abstract class FileSystemNode { + protected String name; + + public FileSystemNode(String name) { + this.name = name; + } + + public abstract int getSize(); +} + +public class File extends FileSystemNode { + private int size; + + public File(String name, int size) { + super(name); + this.size = size; + } + + @Override + public int getSize() { + return size; + } +} + +public class Directory extends FileSystemNode { + private List children; + + public Directory(String name) { + super(name); + this.children = new ArrayList<>(); + } + + public void addChild(FileSystemNode child) { + children.add(child); + } + + @Override + public int getSize() { + int total = 0; + for (FileSystemNode child : children) { + total += child.getSize(); + } + return total; + } +} +``` + +```go !! go +// Go: File system with composition +type FileSystemNode interface { + GetName() string + GetSize() int +} + +type File struct { + Name string + Size int +} + +func (f File) GetName() string { + return f.Name +} + +func (f File) GetSize() int { + return f.Size +} + +type Directory struct { + Name string + Children []FileSystemNode +} + +func NewDirectory(name string) *Directory { + return &Directory{ + Name: name, + Children: make([]FileSystemNode, 0), + } +} + +func (d *Directory) AddChild(child FileSystemNode) { + d.Children = append(d.Children, child) +} + +func (d Directory) GetName() string { + return d.Name +} + +func (d Directory) GetSize() int { + total := 0 + for _, child := range d.Children { + total += child.GetSize() + } + return total +} + +func main() { + file1 := File{Name: "file1.txt", Size: 100} + file2 := File{Name: "file2.txt", Size: 200} + + dir := NewDirectory("documents") + dir.AddChild(file1) + dir.AddChild(file2) + + fmt.Printf("Directory %s size: %d bytes\n", dir.GetName(), dir.GetSize()) +} +``` + + +## 7. Best Practices + +### When to Use Value vs Pointer Receivers + + +```go !! go +// Rule 1: Use pointer receivers for mutations +type Account struct { + balance float64 +} + +func (a *Account) Deposit(amount float64) { + a.balance += amount // ✓ Correct: pointer receiver +} + +// Rule 2: Use value receivers for immutable operations +func (a Account) CanWithdraw(amount float64) bool { + return a.balance >= amount // ✓ Correct: no mutation +} + +// Rule 3: Be consistent - if one method uses pointer, all should +type Circle struct { + radius float64 +} + +func (c *Circle) Area() float64 { // All pointer receivers + return math.Pi * c.radius * c.radius +} + +func (c *Circle) SetRadius(r float64) { + c.radius = r +} + +// Rule 4: Use value receivers for small structs +type Point struct { + X, Y int +} + +func (p Point) Distance() float64 { + return math.Sqrt(float64(p.X*p.X + p.Y*p.Y)) +} +``` + + +### Factory Function Conventions + + +```go !! go +// Pattern 1: Simple factory - returns pointer +func NewUser(name string) *User { + return &User{Name: name} +} + +// Pattern 2: Factory with validation - returns error +func NewValidUser(name string, age int) (*User, error) { + if name == "" { + return nil, errors.New("name required") + } + if age < 0 || age > 150 { + return nil, errors.New("invalid age") + } + return &User{Name: name, Age: age}, nil +} + +// Pattern 3: Factory with options (functional options) +type ServerOption func(*Server) + +func WithPort(port int) ServerOption { + return func(s *Server) { + s.Port = port + } +} + +func WithTLS(tls bool) ServerOption { + return func(s *Server) { + s.UseTLS = tls + } +} + +func NewServer(opts ...ServerOption) *Server { + server := &Server{ + Port: 8080, + UseTLS: false, + } + for _, opt := range opts { + opt(server) + } + return server +} + +// Usage: +// server := NewServer(WithPort(9000), WithTLS(true)) +``` + + +### Composition Guidelines + + +```go !! go +// DO: Embed types that truly represent "is-a" relationship +type Animal struct { + Name string +} + +func (a Animal) Eat() { + fmt.Println("Eating") +} + +type Dog struct { + Animal // Dog IS an Animal + Breed string +} + +// DON'T: Embed just for convenience +type Logger struct{} + +func (l Logger) Log(msg string) { + fmt.Println(msg) +} + +type Service struct { + *Logger // ✗ Avoid: Service is not really a Logger + Name string +} + +// DO: Use regular fields for "has-a" relationship +type Service struct { + logger *Logger // ✓ Better: Service HAS a logger + Name string +} + +func (s Service) DoWork() { + s.logger.Log("Working") // Clear intent +} +``` + + +## 8. Common Patterns + +### Builder Pattern + + +```java !! java +// Java: Builder pattern +public class StringBuilder { + private String data; + + public StringBuilder() { + this.data = ""; + } + + public StringBuilder append(String str) { + this.data += str; + return this; + } + + public StringBuilder appendLine(String str) { + this.data += str + "\n"; + return this; + } + + public String build() { + return data; + } +} + +// Usage: +// String result = new StringBuilder() +// .append("Hello") +// .appendLine("World") +// .build(); +``` + +```go !! go +// Go: Builder pattern using functional options +type QueryBuilder struct { + selectFields []string + fromTable string + whereClause string + orderBy string +} + +type QueryOption func(*QueryBuilder) + +func Select(fields ...string) QueryOption { + return func(qb *QueryBuilder) { + qb.selectFields = fields + } +} + +func From(table string) QueryOption { + return func(qb *QueryBuilder) { + qb.fromTable = table + } +} + +func Where(where string) QueryOption { + return func(qb *QueryBuilder) { + qb.whereClause = where + } +} + +func OrderBy(order string) QueryOption { + return func(qb *QueryBuilder) { + qb.orderBy = order + } +} + +func NewQuery(opts ...QueryOption) *QueryBuilder { + qb := &QueryBuilder{} + for _, opt := range opts { + opt(qb) + } + return qb +} + +func (qb *QueryBuilder) Build() string { + query := "SELECT " + strings.Join(qb.selectFields, ", ") + query += " FROM " + qb.fromTable + if qb.whereClause != "" { + query += " WHERE " + qb.whereClause + } + if qb.orderBy != "" { + query += " ORDER BY " + qb.orderBy + } + return query +} + +// Usage: +// query := NewQuery( +// Select("name", "age", "email"), +// From("users"), +// Where("age > 18"), +// OrderBy("name"), +// ) +// fmt.Println(query.Build()) +``` + + +### Singleton Pattern + + +```java !! java +// Java: Singleton pattern +public class Database { + private static Database instance; + private String connection; + + private Database() { + this.connection = "connected"; + } + + public static Database getInstance() { + if (instance == null) { + instance = new Database(); + } + return instance; + } +} +``` + +```go !! go +// Go: Singleton using sync.Once +type Database struct { + connection string +} + +var ( + instance *Database + once sync.Once +) + +func GetDatabase() *Database { + once.Do(func() { + instance = &Database{ + connection: "connected", + } + }) + return instance +} + +// Or even simpler: use package init +var db *Database + +func init() { + db = &Database{connection: "connected"} +} + +func GetDB() *Database { + return db +} +``` + + +## 9. Practice Questions + +### Beginner + +1. Create a `Book` struct with fields: Title, Author, ISBN, Price + - Add a factory function `NewBook` + - Add methods: `GetDiscountedPrice(discount float64)` + - Use appropriate receivers + +2. Create a `Rectangle` struct with Width and Height + - Add methods: `Area()`, `Perimeter()`, `Scale(factor float64)` + - Decide which methods should use value vs pointer receivers + +### Intermediate + +3. Create a `BankAccount` struct with: + - Fields: AccountNumber, Owner, Balance + - Methods: `Deposit`, `Withdraw`, `TransferTo`, `GetStatement` + - Include validation (no negative amounts, sufficient balance) + +4. Create a file system using composition: + - `File` struct with Name, Size, Content + - `Directory` struct that can contain Files and Directories + - Methods to add items and calculate total size + +### Advanced + +5. Implement a simplified e-commerce system: + - `Product`, `Customer`, `Order` structs + - Order should contain multiple Products + - Methods for adding items, calculating totals, applying discounts + - Use composition and embedding appropriately + +6. Create a game character system: + - `Character` struct with basic stats + - `Warrior`, `Mage`, `Archer` using composition (not inheritance!) + - Each type has unique abilities + - Demonstrate method overriding + +## 10. Project Ideas + +### Project 1: Library Management System + +Create a library system with these components: +- `Book`, `Member`, `Loan` structs +- Methods for borrowing, returning, searching books +- Track due dates and calculate fines +- Use composition for different member types (Student, Faculty) + +### Project 2: Task Management System + +Build a task manager with: +- `Task`, `Project`, `User` structs +- Tasks can be assigned to users +- Projects contain multiple tasks +- Implement priority, status tracking +- Use factory functions for different task types + +### Project 3: Simple Database + +Create an in-memory database: +- `Table`, `Row`, `Column` structs +- Methods for CRUD operations +- Query building using functional options +- Demonstrate composition for complex queries + +## 11. Key Takeaways + +- **No Classes**: Go uses structs instead of classes +- **Separation**: Data (structs) is separate from behavior (methods) +- **Value vs Pointer**: Use value receivers for immutable operations, pointer receivers for mutations +- **No Constructors**: Use factory functions instead +- **Composition over Inheritance**: Go favors composition over class inheritance +- **Embedding**: Struct embedding provides behavior reuse without traditional inheritance +- **Simplicity**: Go's approach leads to simpler, more flexible code + +## 12. Next Steps + +In the next module, we'll explore: +- Go's package system compared to Java packages +- Import conventions and visibility +- Dependency management with Go modules +- Package design best practices + +Continue to [Module 03: Package System](/docs/java2go/module-03-package-system) to learn how Go organizes code! diff --git a/content/docs/java2go/module-02-oop-to-go.zh-cn.mdx b/content/docs/java2go/module-02-oop-to-go.zh-cn.mdx new file mode 100644 index 0000000..f5a6989 --- /dev/null +++ b/content/docs/java2go/module-02-oop-to-go.zh-cn.mdx @@ -0,0 +1,1288 @@ +--- +title: "模块 02:面向对象编程到 Go" +description: "学习 Go 如何在没有类的情况下实现面向对象编程 - 使用结构体、方法和组合来替代传统的继承" +--- + +# 模块 02:面向对象编程到 Go + +欢迎来到模块 02!在本模块中,你将学习 Go 如何以不同于 Java 的方式处理面向对象编程。虽然 Java 严重依赖类和继承,但 Go 使用结构体、方法和组合来实现类似的目标,代码更简单、更灵活。 + +## 学习目标 + +完成本模块后,你将: +- 理解 Go 如何在没有类的情况下实现 OOP 概念 +- 学习如何使用结构体替代类 +- 掌握值接收者与指针接收者 +- 理解组合优于继承 +- 学习工厂函数替代构造函数 +- 比较 Java OOP 模式与 Go 等价实现 + +## 1. 大局观:类与结构体 + +### Java: 类和对象 + +在 Java 中,所有内容都围绕类构建。类定义数据(字段)和行为(方法): + +```java +public class Person { + // 私有字段 + private String name; + private int age; + + // 构造函数 + public Person(String name, int age) { + this.name = name; + this.age = age; + } + + // 方法 + public String getName() { + return name; + } + + public void setAge(int age) { + this.age = age; + } + + public void introduce() { + System.out.println("Hi, I'm " + name + ", " + age + " years old"); + } +} +``` + +### Go: 结构体和方法 + +Go 将数据定义(结构体)与行为(方法)分离: + + +```java !! java +public class Person { + private String name; + private int age; + + public Person(String name, int age) { + this.name = name; + this.age = age; + } + + public String getName() { + return name; + } + + public void introduce() { + System.out.println("Hi, I'm " + name); + } +} +``` + +```go !! go +// Person 结构体 - 仅数据 +type Person struct { + Name string + Age int +} + +// 在 Person 上定义的方法 +func (p Person) Introduce() { + fmt.Printf("Hi, I'm %s\n", p.Name) +} + +// 注意:Go 不需要 getter/setter +// 对于简单字段,直接访问是惯用的 +``` + + +## 2. 定义结构体 + +### 基本结构体声明 + + +```java !! java +public class Rectangle { + private double width; + private double height; + + public Rectangle(double width, double height) { + this.width = width; + this.height = height; + } + + public double getWidth() { return width; } + public double getHeight() { return height; } + public void setWidth(double width) { this.width = width; } + public void setHeight(double height) { this.height = height; } +} +``` + +```go !! go +// Rectangle 结构体 +type Rectangle struct { + Width float64 + Height float64 +} + +// Go 中不需要 getter/setter +// 直接字段访问是惯用的 +func main() { + r := Rectangle{Width: 10.0, Height: 5.0} + fmt.Println(r.Width) // 直接访问 + r.Width = 15.0 // 直接修改 +} +``` + + +### 包含不同字段类型的结构体 + + +```java !! java +public class Employee { + private int id; + private String name; + private double salary; + private String[] skills; + private Address address; + + public Employee(int id, String name, double salary, String[] skills, Address address) { + this.id = id; + this.name = name; + this.salary = salary; + this.skills = skills; + this.address = address; + } +} + +class Address { + private String street; + private String city; + // 构造函数、getter 等 +} +``` + +```go !! go +// Employee 结构体,包含多种字段类型 +type Employee struct { + ID int + Name string + Salary float64 + Skills []string + Address Address // 嵌入结构体 +} + +// Address 结构体 +type Address struct { + Street string + City string +} + +func main() { + emp := Employee{ + ID: 1, + Name: "Alice", + Salary: 75000.0, + Skills: []string{"Go", "Java", "Python"}, + Address: Address{ + Street: "123 Main St", + City: "San Francisco", + }, + } +} +``` + + +## 3. 方法:值接收者与指针接收者 + +这是 Go 中最重要的概念之一!方法可以在值或指针上定义。 + +### 值接收者 + + +```java !! java +public class Counter { + private int count; + + public Counter(int count) { + this.count = count; + } + + // 此方法不修改状态 + public int getCount() { + return count; + } + + // 此方法确实修改状态 + public void increment() { + this.count++; + } +} +``` + +```go !! go +type Counter struct { + Count int +} + +// 值接收者 - 不修改原始值 +func (c Counter) GetCount() int { + return c.Count +} + +// 指针接收者 - 可以修改原始值 +func (c *Counter) Increment() { + c.Count++ // 修改原始 Counter +} + +func main() { + counter := Counter{Count: 0} + + // 值接收者创建副本 + fmt.Println(counter.GetCount()) // 0 + + // 指针接收者修改原始值 + counter.Increment() + fmt.Println(counter.Count) // 1 +} +``` + + +### 何时使用值与指针接收者 + + +```java !! java +// 在 Java 中,对象方法始终可以修改状态 +// 你不需要考虑这个问题 + +public class Circle { + private double radius; + + public Circle(double radius) { + this.radius = radius; + } + + // 计算方法(无状态变化) + public double getArea() { + return Math.PI * radius * radius; + } + + // 修改状态的方法 + public void setRadius(double radius) { + this.radius = radius; + } +} +``` + +```go !! go +type Circle struct { + Radius float64 +} + +// 使用值接收者当: +// - 方法不需要修改结构体 +// - 方法使用小结构体更高效 +// - 你希望不可变性 +func (c Circle) Area() float64 { + return math.Pi * c.Radius * c.Radius +} + +// 使用指针接收者当: +// - 方法需要修改结构体 +// - 结构体很大(避免复制) +// - 一致性:如果一个方法使用指针,所有都应该使用 +func (c *Circle) SetRadius(r float64) { + c.Radius = r +} + +func (c *Circle) Grow(factor float64) { + c.Radius *= factor +} +``` + + +### 实际示例:银行账户 + + +```java !! java +public class BankAccount { + private String owner; + private double balance; + + public BankAccount(String owner, double initialBalance) { + this.owner = owner; + this.balance = initialBalance; + } + + // 查询方法 - 无状态变化 + public double getBalance() { + return balance; + } + + // 修改状态 + public void deposit(double amount) { + if (amount > 0) { + balance += amount; + } + } + + public boolean withdraw(double amount) { + if (amount > 0 && balance >= amount) { + balance -= amount; + return true; + } + return false; + } +} +``` + +```go !! go +type BankAccount struct { + Owner string + Balance float64 +} + +// 查询使用值接收者 +func (a BankAccount) GetBalance() float64 { + return a.Balance +} + +// 修改操作使用指针接收者 +func (a *BankAccount) Deposit(amount float64) { + if amount > 0 { + a.Balance += amount + } +} + +func (a *BankAccount) Withdraw(amount float64) bool { + if amount > 0 && a.Balance >= amount { + a.Balance -= amount + return true + } + return false +} + +func main() { + account := BankAccount{ + Owner: "Alice", + Balance: 1000.0, + } + + account.Deposit(500.0) + account.Withdraw(200.0) + fmt.Println(account.GetBalance()) // 1300.0 +} +``` + + +## 4. 无构造函数:工厂函数 + +Go 没有构造函数。相反,使用工厂函数。 + +### 基本工厂函数 + + +```java !! java +public class User { + private String username; + private String email; + private int age; + + // 构造函数 + public User(String username, String email, int age) { + this.username = username; + this.email = email; + this.age = age; + } + + // 静态工厂方法 + public static User createAdult(String username, String email) { + return new User(username, email, 18); + } +} +``` + +```go !! go +type User struct { + Username string + Email string + Age int +} + +// 工厂函数(约定: NewTypeName) +func NewUser(username, email string, age int) *User { + return &User{ + Username: username, + Email: email, + Age: age, + } +} + +// 带默认值的工厂函数 +func NewAdultUser(username, email string) *User { + return &User{ + Username: username, + Email: email, + Age: 18, + } +} + +func main() { + // 使用工厂函数 + user := NewUser("alice", "alice@example.com", 25) + adult := NewAdultUser("bob", "bob@example.com") +} +``` + + +### 带验证的工厂函数 + + +```java !! java +public class Product { + private String name; + private double price; + + private Product(String name, double price) { + this.name = name; + this.price = price; + } + + public static Product create(String name, double price) { + if (name == null || name.isEmpty()) { + throw new IllegalArgumentException("Name cannot be empty"); + } + if (price < 0) { + throw new IllegalArgumentException("Price cannot be negative"); + } + return new Product(name, price); + } +} +``` + +```go !! go +type Product struct { + Name string + Price float64 +} + +// 返回错误的工厂函数用于验证 +func NewProduct(name string, price float64) (*Product, error) { + if name == "" { + return nil, errors.New("name cannot be empty") + } + if price < 0 { + return nil, errors.New("price cannot be negative") + } + + return &Product{ + Name: name, + Price: price, + }, nil +} + +func main() { + product, err := NewProduct("Laptop", 999.99) + if err != nil { + fmt.Println("Error creating product:", err) + return + } + fmt.Println("Created:", product.Name) +} +``` + + +### 多个构造函数变体 + + +```java !! java +public class Configuration { + private String host; + private int port; + private boolean useSSL; + private int timeout; + + public Configuration(String host, int port) { + this(host, port, true, 30); + } + + public Configuration(String host, int port, boolean useSSL) { + this(host, port, useSSL, 30); + } + + public Configuration(String host, int port, boolean useSSL, int timeout) { + this.host = host; + this.port = port; + this.useSSL = useSSL; + this.timeout = timeout; + } +} +``` + +```go !! go +type Configuration struct { + Host string + Port int + UseSSL bool + Timeout int +} + +// 默认配置 +func NewDefaultConfig() *Configuration { + return &Configuration{ + Host: "localhost", + Port: 8080, + UseSSL: true, + Timeout: 30, + } +} + +// 自定义配置 +func NewConfig(host string, port int) *Configuration { + return &Configuration{ + Host: host, + Port: port, + UseSSL: true, + Timeout: 30, + } +} + +// 完全自定义配置 +func NewCustomConfig(host string, port int, useSSL bool, timeout int) *Configuration { + return &Configuration{ + Host: host, + Port: port, + UseSSL: useSSL, + Timeout: timeout, + } +} +``` + + +## 5. 组合优于继承 + +Go 没有继承。相反,它使用组合从更简单的类型构建复杂类型。 + +### Java: 继承 + +```java +public class Animal { + protected String name; + + public Animal(String name) { + this.name = name; + } + + public void eat() { + System.out.println(name + " is eating"); + } + + public void sleep() { + System.out.println(name + " is sleeping"); + } +} + +public class Dog extends Animal { + private String breed; + + public Dog(String name, String breed) { + super(name); + this.breed = breed; + } + + public void bark() { + System.out.println(name + " is barking"); + } +} +``` + +### Go: 组合 + + +```java !! java +// Java: 使用继承 +public class Vehicle { + protected String make; + protected String model; + + public Vehicle(String make, String model) { + this.make = make; + this.model = model; + } + + public void start() { + System.out.println("Vehicle starting"); + } +} + +public class Car extends Vehicle { + private int numDoors; + + public Car(String make, String model, int numDoors) { + super(make, model); + this.numDoors = numDoors; + } + + public void honk() { + System.out.println("Beep beep!"); + } +} +``` + +```go !! go +// Go: 使用组合 +type Vehicle struct { + Make string + Model string +} + +func (v Vehicle) Start() { + fmt.Println("Vehicle starting") +} + +// Car 嵌入 Vehicle(组合) +type Car struct { + Vehicle // 嵌入结构体(匿名字段) + NumDoors int +} + +func (c Car) Honk() { + fmt.Println("Beep beep!") +} + +func main() { + car := Car{ + Vehicle: Vehicle{ + Make: "Toyota", + Model: "Camry", + }, + NumDoors: 4, + } + + // Car 可以访问 Vehicle 的字段和方法 + car.Start() // 通过嵌入调用 + fmt.Println(car.Make) // 直接访问嵌入字段 + car.Honk() +} +``` + + +### 嵌入以共享行为 + + +```java !! java +// Java: 不允许多重继承(除了接口) +public class Engine { + public void start() { + System.out.println("Engine starting"); + } +} + +public class AudioSystem { + public void playMusic() { + System.out.println("Playing music"); + } +} + +public class Car { + private Engine engine; + private AudioSystem audio; + + public Car() { + this.engine = new Engine(); + this.audio = new AudioSystem(); + } + + public void startEngine() { + engine.start(); + } + + public void playMusic() { + audio.playMusic(); + } +} +``` + +```go !! go +// Go: 嵌入多个类型 +type Engine struct { + Horsepower int +} + +func (e Engine) Start() { + fmt.Println("Engine starting") +} + +type AudioSystem struct { + Brand string +} + +func (a AudioSystem) PlayMusic() { + fmt.Println("Playing music") +} + +// Car 嵌入 Engine 和 AudioSystem +type Car struct { + Engine + AudioSystem + Make string +} + +func main() { + car := Car{ + Engine: Engine{Horsepower: 200}, + AudioSystem: AudioSystem{Brand: "Bose"}, + Make: "Tesla", + } + + // 直接访问嵌入方法 + car.Start() // 来自 Engine + car.PlayMusic() // 来自 AudioSystem + fmt.Println(car.Make) +} +``` + + +### 覆盖方法 + + +```java !! java +public class BaseClass { + public void greet() { + System.out.println("Hello from BaseClass"); + } +} + +public class DerivedClass extends BaseClass { + @Override + public void greet() { + System.out.println("Hello from DerivedClass"); + super.greet(); // 调用父方法 + } +} +``` + +```go !! go +type Base struct { + Name string +} + +func (b Base) Greet() { + fmt.Println("Hello from Base") +} + +type Derived struct { + Base // 嵌入 + ExtraField string +} + +// 覆盖 Greet 方法 +func (d Derived) Greet() { + fmt.Println("Hello from Derived") + // 可以通过字段名调用 Base 方法 + d.Base.Greet() +} + +func main() { + base := Base{Name: "Base"} + derived := Derived{ + Base: Base{Name: "Base"}, + ExtraField: "Extra", + } + + base.Greet() // "Hello from Base" + derived.Greet() // "Hello from Derived" 然后 "Hello from Base" +} +``` + + +## 6. 实际示例:文件系统 + +让我们使用组合构建一个更复杂的示例。 + + +```java !! java +// Java: 使用继承的文件系统 +public abstract class FileSystemNode { + protected String name; + + public FileSystemNode(String name) { + this.name = name; + } + + public abstract int getSize(); +} + +public class File extends FileSystemNode { + private int size; + + public File(String name, int size) { + super(name); + this.size = size; + } + + @Override + public int getSize() { + return size; + } +} + +public class Directory extends FileSystemNode { + private List children; + + public Directory(String name) { + super(name); + this.children = new ArrayList<>(); + } + + public void addChild(FileSystemNode child) { + children.add(child); + } + + @Override + public int getSize() { + int total = 0; + for (FileSystemNode child : children) { + total += child.getSize(); + } + return total; + } +} +``` + +```go !! go +// Go: 使用组合的文件系统 +type FileSystemNode interface { + GetName() string + GetSize() int +} + +type File struct { + Name string + Size int +} + +func (f File) GetName() string { + return f.Name +} + +func (f File) GetSize() int { + return f.Size +} + +type Directory struct { + Name string + Children []FileSystemNode +} + +func NewDirectory(name string) *Directory { + return &Directory{ + Name: name, + Children: make([]FileSystemNode, 0), + } +} + +func (d *Directory) AddChild(child FileSystemNode) { + d.Children = append(d.Children, child) +} + +func (d Directory) GetName() string { + return d.Name +} + +func (d Directory) GetSize() int { + total := 0 + for _, child := range d.Children { + total += child.GetSize() + } + return total +} + +func main() { + file1 := File{Name: "file1.txt", Size: 100} + file2 := File{Name: "file2.txt", Size: 200} + + dir := NewDirectory("documents") + dir.AddChild(file1) + dir.AddChild(file2) + + fmt.Printf("Directory %s size: %d bytes\n", dir.GetName(), dir.GetSize()) +} +``` + + +## 7. 最佳实践 + +### 何时使用值与指针接收者 + + +```go !! go +// 规则 1: 使用指针接收者进行修改 +type Account struct { + balance float64 +} + +func (a *Account) Deposit(amount float64) { + a.balance += amount // ✓ 正确:指针接收者 +} + +// 规则 2: 使用值接收者进行不可变操作 +func (a Account) CanWithdraw(amount float64) bool { + return a.balance >= amount // ✓ 正确:无修改 +} + +// 规则 3: 保持一致性 - 如果一个方法使用指针,所有都应该使用 +type Circle struct { + radius float64 +} + +func (c *Circle) Area() float64 { // 所有方法都用指针接收者 + return math.Pi * c.radius * c.radius +} + +func (c *Circle) SetRadius(r float64) { + c.radius = r +} + +// 规则 4: 小结构体使用值接收者 +type Point struct { + X, Y int +} + +func (p Point) Distance() float64 { + return math.Sqrt(float64(p.X*p.X + p.Y*p.Y)) +} +``` + + +### 工厂函数约定 + + +```go !! go +// 模式 1: 简单工厂 - 返回指针 +func NewUser(name string) *User { + return &User{Name: name} +} + +// 模式 2: 带验证的工厂 - 返回错误 +func NewValidUser(name string, age int) (*User, error) { + if name == "" { + return nil, errors.New("name required") + } + if age < 0 || age > 150 { + return nil, errors.New("invalid age") + } + return &User{Name: name, Age: age}, nil +} + +// 模式 3: 带选项的工厂(函数式选项) +type ServerOption func(*Server) + +func WithPort(port int) ServerOption { + return func(s *Server) { + s.Port = port + } +} + +func WithTLS(tls bool) ServerOption { + return func(s *Server) { + s.UseTLS = tls + } +} + +func NewServer(opts ...ServerOption) *Server { + server := &Server{ + Port: 8080, + UseTLS: false, + } + for _, opt := range opts { + opt(server) + } + return server +} + +// 使用: +// server := NewServer(WithPort(9000), WithTLS(true)) +``` + + +### 组合指南 + + +```go !! go +// 应该: 嵌入真正代表"是"关系的类型 +type Animal struct { + Name string +} + +func (a Animal) Eat() { + fmt.Println("Eating") +} + +type Dog struct { + Animal // Dog 是动物 + Breed string +} + +// 不应该: 仅为了方便而嵌入 +type Logger struct{} + +func (l Logger) Log(msg string) { + fmt.Println(msg) +} + +type Service struct { + *Logger // ✗ 避免: Service 并不是真正的 Logger + Name string +} + +// 应该: 对"有"关系使用常规字段 +type Service struct { + logger *Logger // ✓ 更好: Service 有一个 logger + Name string +} + +func (s Service) DoWork() { + s.logger.Log("Working") // 意图清晰 +} +``` + + +## 8. 常见模式 + +### 构建器模式 + + +```java !! java +// Java: 构建器模式 +public class StringBuilder { + private String data; + + public StringBuilder() { + this.data = ""; + } + + public StringBuilder append(String str) { + this.data += str; + return this; + } + + public StringBuilder appendLine(String str) { + this.data += str + "\n"; + return this; + } + + public String build() { + return data; + } +} + +// 使用: +// String result = new StringBuilder() +// .append("Hello") +// .appendLine("World") +// .build(); +``` + +```go !! go +// Go: 使用函数式选项的构建器模式 +type QueryBuilder struct { + selectFields []string + fromTable string + whereClause string + orderBy string +} + +type QueryOption func(*QueryBuilder) + +func Select(fields ...string) QueryOption { + return func(qb *QueryBuilder) { + qb.selectFields = fields + } +} + +func From(table string) QueryOption { + return func(qb *QueryBuilder) { + qb.fromTable = table + } +} + +func Where(where string) QueryOption { + return func(qb *QueryBuilder) { + qb.whereClause = where + } +} + +func OrderBy(order string) QueryOption { + return func(qb *QueryBuilder) { + qb.orderBy = order + } +} + +func NewQuery(opts ...QueryOption) *QueryBuilder { + qb := &QueryBuilder{} + for _, opt := range opts { + opt(qb) + } + return qb +} + +func (qb *QueryBuilder) Build() string { + query := "SELECT " + strings.Join(qb.selectFields, ", ") + query += " FROM " + qb.fromTable + if qb.whereClause != "" { + query += " WHERE " + qb.whereClause + } + if qb.orderBy != "" { + query += " ORDER BY " + qb.orderBy + } + return query +} + +// 使用: +// query := NewQuery( +// Select("name", "age", "email"), +// From("users"), +// Where("age > 18"), +// OrderBy("name"), +// ) +// fmt.Println(query.Build()) +``` + + +### 单例模式 + + +```java !! java +// Java: 单例模式 +public class Database { + private static Database instance; + private String connection; + + private Database() { + this.connection = "connected"; + } + + public static Database getInstance() { + if (instance == null) { + instance = new Database(); + } + return instance; + } +} +``` + +```go !! go +// Go: 使用 sync.Once 的单例 +type Database struct { + connection string +} + +var ( + instance *Database + once sync.Once +) + +func GetDatabase() *Database { + once.Do(func() { + instance = &Database{ + connection: "connected", + } + }) + return instance +} + +// 或者更简单: 使用包 init +var db *Database + +func init() { + db = &Database{connection: "connected"} +} + +func GetDB() *Database { + return db +} +``` + + +## 9. 练习题 + +### 初级 + +1. 创建一个 `Book` 结构体,包含字段: Title、Author、ISBN、Price + - 添加工厂函数 `NewBook` + - 添加方法: `GetDiscountedPrice(discount float64)` + - 使用适当的接收者 + +2. 创建一个 `Rectangle` 结构体,包含 Width 和 Height + - 添加方法: `Area()`、`Perimeter()`、`Scale(factor float64)` + - 决定哪些方法应该使用值或指针接收者 + +### 中级 + +3. 创建一个 `BankAccount` 结构体,包含: + - 字段: AccountNumber、Owner、Balance + - 方法: `Deposit`、`Withdraw`、`TransferTo`、`GetStatement` + - 包含验证(无负金额,余额充足) + +4. 使用组合创建文件系统: + - `File` 结构体,包含 Name、Size、Content + - `Directory` 结构体,可以包含 Files 和 Directories + - 添加项目和计算总大小的方法 + +### 高级 + +5. 实现一个简化的电商系统: + - `Product`、`Customer`、`Order` 结构体 + - Order 应该包含多个 Products + - 添加项目、计算总计、应用折扣的方法 + - 适当使用组合和嵌入 + +6. 创建游戏角色系统: + - `Character` 结构体,包含基本属性 + - 使用组合(而非继承!)创建 `Warrior`、`Mage`、`Archer` + - 每种类型都有独特的能力 + - 演示方法覆盖 + +## 10. 项目想法 + +### 项目 1: 图书馆管理系统 + +创建一个图书馆系统,包含以下组件: +- `Book`、`Member`、`Loan` 结构体 +- 借书、还书、搜索图书的方法 +- 跟踪到期日期并计算罚款 +- 使用组合表示不同成员类型(Student、Faculty) + +### 项目 2: 任务管理系统 + +构建任务管理器,包含: +- `Task`、`Project`、`User` 结构体 +- 任务可以分配给用户 +- 项目包含多个任务 +- 实现优先级、状态跟踪 +- 使用工厂函数创建不同类型的任务 + +### 项目 3: 简单数据库 + +创建内存数据库: +- `Table`、`Row`、`Column` 结构体 +- CRUD 操作方法 +- 使用函数式选项构建查询 +- 演示复杂查询的组合 + +## 11. 关键要点 + +- **无类**: Go 使用结构体替代类 +- **分离**: 数据(结构体)与行为(方法)分离 +- **值与指针**: 对不可变操作使用值接收者,对修改操作使用指针接收者 +- **无构造函数**: 使用工厂函数替代 +- **组合优于继承**: Go 更倾向于组合而非类继承 +- **嵌入**: 结构体嵌入提供行为复用,无需传统继承 +- **简单性**: Go 的方法导致更简单、更灵活的代码 + +## 12. 下一步 + +在下一个模块中,我们将探索: +- Go 的包系统与 Java 包的比较 +- 导入约定和可见性 +- 使用 Go 模块进行依赖管理 +- 包设计最佳实践 + +继续学习 [模块 03: 包系统](/docs/java2go/module-03-package-system),了解 Go 如何组织代码! diff --git a/content/docs/java2go/module-02-oop-to-go.zh-tw.mdx b/content/docs/java2go/module-02-oop-to-go.zh-tw.mdx new file mode 100644 index 0000000..243805e --- /dev/null +++ b/content/docs/java2go/module-02-oop-to-go.zh-tw.mdx @@ -0,0 +1,1288 @@ +--- +title: "模組 02:物件導向程式設計到 Go" +description: "學習 Go 如何在沒有類別的情況下實作物件導向程式設計 - 使用結構體、方法和組合來替代傳統的繼承" +--- + +# 模組 02:物件導向程式設計到 Go + +歡迎來到模組 02!在本模組中,你將學習 Go 如何以不同於 Java 的方式處理物件導向程式設計。雖然 Java 嚴重依賴類別和繼承,但 Go 使用結構體、方法和組合來實現類似的目標,程式碼更簡單、更靈活。 + +## 學習目標 + +完成本模組後,你將: +- 理解 Go 如何在沒有類別的情況下實作 OOP 概念 +- 學習如何使用結構體替代類別 +- 掌握值接收者與指標接收者 +- 理解組合優於繼承 +- 學習工廠函式替代建構函式 +- 比較 Java OOP 模式與 Go 等價實作 + +## 1. 大局觀:類別與結構體 + +### Java: 類別和物件 + +在 Java 中,所有內容都圍繞類別建構。類別定義資料(欄位)和行為(方法): + +```java +public class Person { + // 私有欄位 + private String name; + private int age; + + // 建構函式 + public Person(String name, int age) { + this.name = name; + this.age = age; + } + + // 方法 + public String getName() { + return name; + } + + public void setAge(int age) { + this.age = age; + } + + public void introduce() { + System.out.println("Hi, I'm " + name + ", " + age + " years old"); + } +} +``` + +### Go: 結構體和方法 + +Go 將資料定義(結構體)與行為(方法)分離: + + +```java !! java +public class Person { + private String name; + private int age; + + public Person(String name, int age) { + this.name = name; + this.age = age; + } + + public String getName() { + return name; + } + + public void introduce() { + System.out.println("Hi, I'm " + name); + } +} +``` + +```go !! go +// Person 結構體 - 僅資料 +type Person struct { + Name string + Age int +} + +// 在 Person 上定義的方法 +func (p Person) Introduce() { + fmt.Printf("Hi, I'm %s\n", p.Name) +} + +// 注意:Go 不需要 getter/setter +// 對於簡單欄位,直接存取是慣用的 +``` + + +## 2. 定義結構體 + +### 基本結構體宣告 + + +```java !! java +public class Rectangle { + private double width; + private double height; + + public Rectangle(double width, double height) { + this.width = width; + this.height = height; + } + + public double getWidth() { return width; } + public double getHeight() { return height; } + public void setWidth(double width) { this.width = width; } + public void setHeight(double height) { this.height = height; } +} +``` + +```go !! go +// Rectangle 結構體 +type Rectangle struct { + Width float64 + Height float64 +} + +// Go 中不需要 getter/setter +// 直接欄位存取是慣用的 +func main() { + r := Rectangle{Width: 10.0, Height: 5.0} + fmt.Println(r.Width) // 直接存取 + r.Width = 15.0 // 直接修改 +} +``` + + +### 包含不同欄位類型的結構體 + + +```java !! java +public class Employee { + private int id; + private String name; + private double salary; + private String[] skills; + private Address address; + + public Employee(int id, String name, double salary, String[] skills, Address address) { + this.id = id; + this.name = name; + this.salary = salary; + this.skills = skills; + this.address = address; + } +} + +class Address { + private String street; + private String city; + // 建構函式、getter 等 +} +``` + +```go !! go +// Employee 結構體,包含多種欄位類型 +type Employee struct { + ID int + Name string + Salary float64 + Skills []string + Address Address // 嵌入結構體 +} + +// Address 結構體 +type Address struct { + Street string + City string +} + +func main() { + emp := Employee{ + ID: 1, + Name: "Alice", + Salary: 75000.0, + Skills: []string{"Go", "Java", "Python"}, + Address: Address{ + Street: "123 Main St", + City: "San Francisco", + }, + } +} +``` + + +## 3. 方法:值接收者與指標接收者 + +這是 Go 中最重要的概念之一!方法可以在值或指標上定義。 + +### 值接收者 + + +```java !! java +public class Counter { + private int count; + + public Counter(int count) { + this.count = count; + } + + // 此方法不修改狀態 + public int getCount() { + return count; + } + + // 此方法確實修改狀態 + public void increment() { + this.count++; + } +} +``` + +```go !! go +type Counter struct { + Count int +} + +// 值接收者 - 不修改原始值 +func (c Counter) GetCount() int { + return c.Count +} + +// 指標接收者 - 可以修改原始值 +func (c *Counter) Increment() { + c.Count++ // 修改原始 Counter +} + +func main() { + counter := Counter{Count: 0} + + // 值接收者建立副本 + fmt.Println(counter.GetCount()) // 0 + + // 指標接收者修改原始值 + counter.Increment() + fmt.Println(counter.Count) // 1 +} +``` + + +### 何時使用值與指標接收者 + + +```java !! java +// 在 Java 中,物件方法始終可以修改狀態 +// 你不需要考慮這個問題 + +public class Circle { + private double radius; + + public Circle(double radius) { + this.radius = radius; + } + + // 計算方法(無狀態變化) + public double getArea() { + return Math.PI * radius * radius; + } + + // 修改狀態的方法 + public void setRadius(double radius) { + this.radius = radius; + } +} +``` + +```go !! go +type Circle struct { + Radius float64 +} + +// 使用值接收者當: +// - 方法不需要修改結構體 +// - 方法使用小結構體更高效 +// - 你希望不可變性 +func (c Circle) Area() float64 { + return math.Pi * c.Radius * c.Radius +} + +// 使用指標接收者當: +// - 方法需要修改結構體 +// - 結構體很大(避免複製) +// - 一致性:如果一個方法使用指標,所有都應該使用 +func (c *Circle) SetRadius(r float64) { + c.Radius = r +} + +func (c *Circle) Grow(factor float64) { + c.Radius *= factor +} +``` + + +### 實際範例:銀行帳戶 + + +```java !! java +public class BankAccount { + private String owner; + private double balance; + + public BankAccount(String owner, double initialBalance) { + this.owner = owner; + this.balance = initialBalance; + } + + // 查詢方法 - 無狀態變化 + public double getBalance() { + return balance; + } + + // 修改狀態 + public void deposit(double amount) { + if (amount > 0) { + balance += amount; + } + } + + public boolean withdraw(double amount) { + if (amount > 0 && balance >= amount) { + balance -= amount; + return true; + } + return false; + } +} +``` + +```go !! go +type BankAccount struct { + Owner string + Balance float64 +} + +// 查詢使用值接收者 +func (a BankAccount) GetBalance() float64 { + return a.Balance +} + +// 修改操作使用指標接收者 +func (a *BankAccount) Deposit(amount float64) { + if amount > 0 { + a.Balance += amount + } +} + +func (a *BankAccount) Withdraw(amount float64) bool { + if amount > 0 && a.Balance >= amount { + a.Balance -= amount + return true + } + return false +} + +func main() { + account := BankAccount{ + Owner: "Alice", + Balance: 1000.0, + } + + account.Deposit(500.0) + account.Withdraw(200.0) + fmt.Println(account.GetBalance()) // 1300.0 +} +``` + + +## 4. 無建構函式:工廠函式 + +Go 沒有建構函式。相反,使用工廠函式。 + +### 基本工廠函式 + + +```java !! java +public class User { + private String username; + private String email; + private int age; + + // 建構函式 + public User(String username, String email, int age) { + this.username = username; + this.email = email; + this.age = age; + } + + // 靜態工廠方法 + public static User createAdult(String username, String email) { + return new User(username, email, 18); + } +} +``` + +```go !! go +type User struct { + Username string + Email string + Age int +} + +// 工廠函式(約定: NewTypeName) +func NewUser(username, email string, age int) *User { + return &User{ + Username: username, + Email: email, + Age: age, + } +} + +// 帶預設值的工廠函式 +func NewAdultUser(username, email string) *User { + return &User{ + Username: username, + Email: email, + Age: 18, + } +} + +func main() { + // 使用工廠函式 + user := NewUser("alice", "alice@example.com", 25) + adult := NewAdultUser("bob", "bob@example.com") +} +``` + + +### 帶驗證的工廠函式 + + +```java !! java +public class Product { + private String name; + private double price; + + private Product(String name, double price) { + this.name = name; + this.price = price; + } + + public static Product create(String name, double price) { + if (name == null || name.isEmpty()) { + throw new IllegalArgumentException("Name cannot be empty"); + } + if (price < 0) { + throw new IllegalArgumentException("Price cannot be negative"); + } + return new Product(name, price); + } +} +``` + +```go !! go +type Product struct { + Name string + Price float64 +} + +// 返回錯誤的工廠函式用於驗證 +func NewProduct(name string, price float64) (*Product, error) { + if name == "" { + return nil, errors.New("name cannot be empty") + } + if price < 0 { + return nil, errors.New("price cannot be negative") + } + + return &Product{ + Name: name, + Price: price, + }, nil +} + +func main() { + product, err := NewProduct("Laptop", 999.99) + if err != nil { + fmt.Println("Error creating product:", err) + return + } + fmt.Println("Created:", product.Name) +} +``` + + +### 多個建構函式變體 + + +```java !! java +public class Configuration { + private String host; + private int port; + private boolean useSSL; + private int timeout; + + public Configuration(String host, int port) { + this(host, port, true, 30); + } + + public Configuration(String host, int port, boolean useSSL) { + this(host, port, useSSL, 30); + } + + public Configuration(String host, int port, boolean useSSL, int timeout) { + this.host = host; + this.port = port; + this.useSSL = useSSL; + this.timeout = timeout; + } +} +``` + +```go !! go +type Configuration struct { + Host string + Port int + UseSSL bool + Timeout int +} + +// 預設配置 +func NewDefaultConfig() *Configuration { + return &Configuration{ + Host: "localhost", + Port: 8080, + UseSSL: true, + Timeout: 30, + } +} + +// 自訂配置 +func NewConfig(host string, port int) *Configuration { + return &Configuration{ + Host: host, + Port: port, + UseSSL: true, + Timeout: 30, + } +} + +// 完全自訂配置 +func NewCustomConfig(host string, port int, useSSL bool, timeout int) *Configuration { + return &Configuration{ + Host: host, + Port: port, + UseSSL: useSSL, + Timeout: timeout, + } +} +``` + + +## 5. 組合優於繼承 + +Go 沒有繼承。相反,它使用組合從更簡單的類型建構複雜類型。 + +### Java: 繼承 + +```java +public class Animal { + protected String name; + + public Animal(String name) { + this.name = name; + } + + public void eat() { + System.out.println(name + " is eating"); + } + + public void sleep() { + System.out.println(name + " is sleeping"); + } +} + +public class Dog extends Animal { + private String breed; + + public Dog(String name, String breed) { + super(name); + this.breed = breed; + } + + public void bark() { + System.out.println(name + " is barking"); + } +} +``` + +### Go: 組合 + + +```java !! java +// Java: 使用繼承 +public class Vehicle { + protected String make; + protected String model; + + public Vehicle(String make, String model) { + this.make = make; + this.model = model; + } + + public void start() { + System.out.println("Vehicle starting"); + } +} + +public class Car extends Vehicle { + private int numDoors; + + public Car(String make, String model, int numDoors) { + super(make, model); + this.numDoors = numDoors; + } + + public void honk() { + System.out.println("Beep beep!"); + } +} +``` + +```go !! go +// Go: 使用組合 +type Vehicle struct { + Make string + Model string +} + +func (v Vehicle) Start() { + fmt.Println("Vehicle starting") +} + +// Car 嵌入 Vehicle(組合) +type Car struct { + Vehicle // 嵌入結構體(匿名欄位) + NumDoors int +} + +func (c Car) Honk() { + fmt.Println("Beep beep!") +} + +func main() { + car := Car{ + Vehicle: Vehicle{ + Make: "Toyota", + Model: "Camry", + }, + NumDoors: 4, + } + + // Car 可以存取 Vehicle 的欄位和方法 + car.Start() // 透過嵌入呼叫 + fmt.Println(car.Make) // 直接存取嵌入欄位 + car.Honk() +} +``` + + +### 嵌入以共享行為 + + +```java !! java +// Java: 不允許多重繼承(除了介面) +public class Engine { + public void start() { + System.out.println("Engine starting"); + } +} + +public class AudioSystem { + public void playMusic() { + System.out.println("Playing music"); + } +} + +public class Car { + private Engine engine; + private AudioSystem audio; + + public Car() { + this.engine = new Engine(); + this.audio = new AudioSystem(); + } + + public void startEngine() { + engine.start(); + } + + public void playMusic() { + audio.playMusic(); + } +} +``` + +```go !! go +// Go: 嵌入多個類型 +type Engine struct { + Horsepower int +} + +func (e Engine) Start() { + fmt.Println("Engine starting") +} + +type AudioSystem struct { + Brand string +} + +func (a AudioSystem) PlayMusic() { + fmt.Println("Playing music") +} + +// Car 嵌入 Engine 和 AudioSystem +type Car struct { + Engine + AudioSystem + Make string +} + +func main() { + car := Car{ + Engine: Engine{Horsepower: 200}, + AudioSystem: AudioSystem{Brand: "Bose"}, + Make: "Tesla", + } + + // 直接存取嵌入方法 + car.Start() // 來自 Engine + car.PlayMusic() // 來自 AudioSystem + fmt.Println(car.Make) +} +``` + + +### 覆蓋方法 + + +```java !! java +public class BaseClass { + public void greet() { + System.out.println("Hello from BaseClass"); + } +} + +public class DerivedClass extends BaseClass { + @Override + public void greet() { + System.out.println("Hello from DerivedClass"); + super.greet(); // 呼叫父方法 + } +} +``` + +```go !! go +type Base struct { + Name string +} + +func (b Base) Greet() { + fmt.Println("Hello from Base") +} + +type Derived struct { + Base // 嵌入 + ExtraField string +} + +// 覆蓋 Greet 方法 +func (d Derived) Greet() { + fmt.Println("Hello from Derived") + // 可以透過欄位名稱呼叫 Base 方法 + d.Base.Greet() +} + +func main() { + base := Base{Name: "Base"} + derived := Derived{ + Base: Base{Name: "Base"}, + ExtraField: "Extra", + } + + base.Greet() // "Hello from Base" + derived.Greet() // "Hello from Derived" 然後 "Hello from Base" +} +``` + + +## 6. 實際範例:檔案系統 + +讓我們使用組合建構一個更複雜的範例。 + + +```java !! java +// Java: 使用繼承的檔案系統 +public abstract class FileSystemNode { + protected String name; + + public FileSystemNode(String name) { + this.name = name; + } + + public abstract int getSize(); +} + +public class File extends FileSystemNode { + private int size; + + public File(String name, int size) { + super(name); + this.size = size; + } + + @Override + public int getSize() { + return size; + } +} + +public class Directory extends FileSystemNode { + private List children; + + public Directory(String name) { + super(name); + this.children = new ArrayList<>(); + } + + public void addChild(FileSystemNode child) { + children.add(child); + } + + @Override + public int getSize() { + int total = 0; + for (FileSystemNode child : children) { + total += child.getSize(); + } + return total; + } +} +``` + +```go !! go +// Go: 使用組合的檔案系統 +type FileSystemNode interface { + GetName() string + GetSize() int +} + +type File struct { + Name string + Size int +} + +func (f File) GetName() string { + return f.Name +} + +func (f File) GetSize() int { + return f.Size +} + +type Directory struct { + Name string + Children []FileSystemNode +} + +func NewDirectory(name string) *Directory { + return &Directory{ + Name: name, + Children: make([]FileSystemNode, 0), + } +} + +func (d *Directory) AddChild(child FileSystemNode) { + d.Children = append(d.Children, child) +} + +func (d Directory) GetName() string { + return d.Name +} + +func (d Directory) GetSize() int { + total := 0 + for _, child := range d.Children { + total += child.GetSize() + } + return total +} + +func main() { + file1 := File{Name: "file1.txt", Size: 100} + file2 := File{Name: "file2.txt", Size: 200} + + dir := NewDirectory("documents") + dir.AddChild(file1) + dir.AddChild(file2) + + fmt.Printf("Directory %s size: %d bytes\n", dir.GetName(), dir.GetSize()) +} +``` + + +## 7. 最佳實踐 + +### 何時使用值與指標接收者 + + +```go !! go +// 規則 1: 使用指標接收者進行修改 +type Account struct { + balance float64 +} + +func (a *Account) Deposit(amount float64) { + a.balance += amount // ✓ 正確:指標接收者 +} + +// 規則 2: 使用值接收者進行不可變操作 +func (a Account) CanWithdraw(amount float64) bool { + return a.balance >= amount // ✓ 正確:無修改 +} + +// 規則 3: 保持一致性 - 如果一個方法使用指標,所有都應該使用 +type Circle struct { + radius float64 +} + +func (c *Circle) Area() float64 { // 所有方法都用指標接收者 + return math.Pi * c.radius * c.radius +} + +func (c *Circle) SetRadius(r float64) { + c.radius = r +} + +// 規則 4: 小結構體使用值接收者 +type Point struct { + X, Y int +} + +func (p Point) Distance() float64 { + return math.Sqrt(float64(p.X*p.X + p.Y*p.Y)) +} +``` + + +### 工廠函式約定 + + +```go !! go +// 模式 1: 簡單工廠 - 返回指標 +func NewUser(name string) *User { + return &User{Name: name} +} + +// 模式 2: 帶驗證的工廠 - 返回錯誤 +func NewValidUser(name string, age int) (*User, error) { + if name == "" { + return nil, errors.New("name required") + } + if age < 0 || age > 150 { + return nil, errors.New("invalid age") + } + return &User{Name: name, Age: age}, nil +} + +// 模式 3: 帶選項的工廠(函式式選項) +type ServerOption func(*Server) + +func WithPort(port int) ServerOption { + return func(s *Server) { + s.Port = port + } +} + +func WithTLS(tls bool) ServerOption { + return func(s *Server) { + s.UseTLS = tls + } +} + +func NewServer(opts ...ServerOption) *Server { + server := &Server{ + Port: 8080, + UseTLS: false, + } + for _, opt := range opts { + opt(server) + } + return server +} + +// 使用: +// server := NewServer(WithPort(9000), WithTLS(true)) +``` + + +### 組合指南 + + +```go !! go +// 應該: 嵌入真正代表"是"關係的類型 +type Animal struct { + Name string +} + +func (a Animal) Eat() { + fmt.Println("Eating") +} + +type Dog struct { + Animal // Dog 是動物 + Breed string +} + +// 不應該: 僅為方便而嵌入 +type Logger struct{} + +func (l Logger) Log(msg string) { + fmt.Println(msg) +} + +type Service struct { + *Logger // ✗ 避免: Service 並不是真正的 Logger + Name string +} + +// 應該: 對"有"關係使用常規欄位 +type Service struct { + logger *Logger // ✓ 更好: Service 有一個 logger + Name string +} + +func (s Service) DoWork() { + s.logger.Log("Working") // 意圖清晰 +} +``` + + +## 8. 常見模式 + +### 建構器模式 + + +```java !! java +// Java: 建構器模式 +public class StringBuilder { + private String data; + + public StringBuilder() { + this.data = ""; + } + + public StringBuilder append(String str) { + this.data += str; + return this; + } + + public StringBuilder appendLine(String str) { + this.data += str + "\n"; + return this; + } + + public String build() { + return data; + } +} + +// 使用: +// String result = new StringBuilder() +// .append("Hello") +// .appendLine("World") +// .build(); +``` + +```go !! go +// Go: 使用函式式選項的建構器模式 +type QueryBuilder struct { + selectFields []string + fromTable string + whereClause string + orderBy string +} + +type QueryOption func(*QueryBuilder) + +func Select(fields ...string) QueryOption { + return func(qb *QueryBuilder) { + qb.selectFields = fields + } +} + +func From(table string) QueryOption { + return func(qb *QueryBuilder) { + qb.fromTable = table + } +} + +func Where(where string) QueryOption { + return func(qb *QueryBuilder) { + qb.whereClause = where + } +} + +func OrderBy(order string) QueryOption { + return func(qb *QueryBuilder) { + qb.orderBy = order + } +} + +func NewQuery(opts ...QueryOption) *QueryBuilder { + qb := &QueryBuilder{} + for _, opt := range opts { + opt(qb) + } + return qb +} + +func (qb *QueryBuilder) Build() string { + query := "SELECT " + strings.Join(qb.selectFields, ", ") + query += " FROM " + qb.fromTable + if qb.whereClause != "" { + query += " WHERE " + qb.whereClause + } + if qb.orderBy != "" { + query += " ORDER BY " + qb.orderBy + } + return query +} + +// 使用: +// query := NewQuery( +// Select("name", "age", "email"), +// From("users"), +// Where("age > 18"), +// OrderBy("name"), +// ) +// fmt.Println(query.Build()) +``` + + +### 單例模式 + + +```java !! java +// Java: 單例模式 +public class Database { + private static Database instance; + private String connection; + + private Database() { + this.connection = "connected"; + } + + public static Database getInstance() { + if (instance == null) { + instance = new Database(); + } + return instance; + } +} +``` + +```go !! go +// Go: 使用 sync.Once 的單例 +type Database struct { + connection string +} + +var ( + instance *Database + once sync.Once +) + +func GetDatabase() *Database { + once.Do(func() { + instance = &Database{ + connection: "connected", + } + }) + return instance +} + +// 或者更簡單: 使用包 init +var db *Database + +func init() { + db = &Database{connection: "connected"} +} + +func GetDB() *Database { + return db +} +``` + + +## 9. 練習題 + +### 初級 + +1. 建立一個 `Book` 結構體,包含欄位: Title、Author、ISBN、Price + - 新加工廠函式 `NewBook` + - 新增方法: `GetDiscountedPrice(discount float64)` + - 使用適當的接收者 + +2. 建立一個 `Rectangle` 結構體,包含 Width 和 Height + - 新增方法: `Area()`、`Perimeter()`、`Scale(factor float64)` + - 決定哪些方法應該使用值或指標接收者 + +### 中級 + +3. 建立一個 `BankAccount` 結構體,包含: + - 欄位: AccountNumber、Owner、Balance + - 方法: `Deposit`、`Withdraw`、`TransferTo`、`GetStatement` + - 包含驗證(無負金額,餘額充足) + +4. 使用組合建立檔案系統: + - `File` 結構體,包含 Name、Size、Content + - `Directory` 結構體,可以包含 Files 和 Directories + - 新增項目和計算總大小的方法 + +### 高級 + +5. 實作一個簡化的電商系統: + - `Product`、`Customer`、`Order` 結構體 + - Order 應該包含多個 Products + - 新增項目、計算總計、套用折扣的方法 + - 適當使用組合和嵌入 + +6. 建立遊戲角色系統: + - `Character` 結構體,包含基本屬性 + - 使用組合(而非繼承!)建立 `Warrior`、`Mage`、`Archer` + - 每種類型都有獨特的能力 + - 演示方法覆蓋 + +## 10. 專案想法 + +### 專案 1: 圖書館管理系統 + +建立圖書館系統,包含以下元件: +- `Book`、`Member`、`Loan` 結構體 +- 借書、還書、搜尋圖書的方法 +- 追蹤到期日期並計算罰款 +- 使用組合表示不同成員類型(Student、Faculty) + +### 專案 2: 任務管理系統 + +建構任務管理器,包含: +- `Task`、`Project`、`User` 結構體 +- 任務可以分配給使用者 +- 專案包含多個任務 +- 實作優先級、狀態追蹤 +- 使用工廠函式建立不同類型的任務 + +### 專案 3: 簡單資料庫 + +建立記憶體資料庫: +- `Table`、`Row`、`Column` 結構體 +- CRUD 操作方法 +- 使用函式式選項建構查詢 +- 演示複雜查詢的組合 + +## 11. 關鍵要點 + +- **無類別**: Go 使用結構體替代類別 +- **分離**: 資料(結構體)與行為(方法)分離 +- **值與指標**: 對不可變操作使用值接收者,對修改操作使用指標接收者 +- **無建構函式**: 使用工廠函式替代 +- **組合優於繼承**: Go 更傾向於組合而非類別繼承 +- **嵌入**: 結構體嵌入提供行為複用,無需傳統繼承 +- **簡單性**: Go 的方法導致更簡單、更靈活的程式碼 + +## 12. 下一步 + +在下一個模組中,我們將探索: +- Go 的套件系統與 Java 套件的比較 +- 匯入約定和可見性 +- 使用 Go 模組進行依賴管理 +- 套件設計最佳實踐 + +繼續學習 [模組 03: 套件系統](/docs/java2go/module-03-package-system),了解 Go 如何組織程式碼! diff --git a/content/docs/java2go/module-03-package-system.mdx b/content/docs/java2go/module-03-package-system.mdx new file mode 100644 index 0000000..e860464 --- /dev/null +++ b/content/docs/java2go/module-03-package-system.mdx @@ -0,0 +1,994 @@ +--- +title: "Module 03: Package System" +description: "Understand Go's package system compared to Java packages, including imports, visibility, dependency management with Go modules, and package design best practices" +--- + +# Module 03: Package System + +Welcome to Module 03! In this module, you'll learn how Go organizes code through packages, how it differs from Java's package system, and how to manage dependencies using Go modules. + +## Learning Objectives + +By the end of this module, you will: +- Understand Go packages vs Java packages +- Learn Go's import conventions +- Master exported/unexported identifiers +- Understand Go modules (go.mod) vs Maven/Gradle +- Learn package naming best practices +- Understand the vendor directory +- Master dependency management in Go + +## 1. Packages: Go vs Java + +### Java Packages + +In Java, packages organize classes and provide namespace isolation: + +```java +// File: com/example/myapp/utils/StringUtil.java +package com.example.myapp.utils; + +public class StringUtil { + public static boolean isEmpty(String str) { + return str == null || str.isEmpty(); + } +} +``` + +### Go Packages + +Go packages are simpler - the package name is determined by the `package` declaration, not the directory structure: + + +```java !! java +// Java: Package must match directory structure +// File: com/example/myapp/utils/StringUtils.java +package com.example.myapp.utils; + +public class StringUtils { + public static boolean isEmpty(String str) { + return str == null || str.length() == 0; + } +} + +// Usage +import com.example.myapp.utils.StringUtils; +``` + +```go !! go +// Go: Package name is declared in the file +// File: myapp/utils/stringutil.go (or any .go file) +package utils // Package name, not directory path + +func IsEmpty(str string) bool { + return str == "" +} + +// The directory path is used for importing, but the package +// name is what you use in code +``` + + +## 2. Import Conventions + +### Java Imports + + +```java !! java +// Java: Explicit imports +import com.example.utils.StringUtil; +import com.example.models.User; +import java.util.List; +import java.util.ArrayList; + +// Or wildcard import (discouraged) +import com.example.utils.*; + +public class Example { + public void process() { + String name = StringUtil.trim(" hello "); + List items = new ArrayList<>(); + } +} +``` + +```go !! go +// Go: Imports are grouped +package main + +import ( + "fmt" // Standard library + "strings" // Standard library + "myapp/utils" // Local package - uses directory path + "myapp/models" // Local package +) + +func main() { + name := strings.TrimSpace(" hello ") + fmt.Println(name) + + // Use the package name (utils), not directory path + user := models.NewUser("Alice") +} +``` + + +### Import Aliases + + +```java !! java +// Java doesn't have import aliases +// You must use fully qualified names if there are conflicts +import com.example.json.JSON; +import org.json.JSONObject; + +public class Example { + public void process() { + // Must use fully qualified name + JSONObject obj = new JSONObject(); + } +} +``` + +```go !! go +// Go: Use import aliases to resolve conflicts +package main + +import ( + myjson "myapp/json" // Alias for my json package + "encoding/json" // Standard library json +) + +func main() { + // Use alias to differentiate + obj1 := json.NewEncoder(nil) // Standard library + obj2 := myjson.NewEncoder() // My package +} +``` + + +### Blank Imports + + +```java !! java +// Java: No equivalent to blank imports +// You must explicitly use everything you import +``` + +```go !! go +// Go: Blank imports import for side effects only +package main + +import ( + _ "fmt" // ✗ Useless - no side effects + _ "image/jpeg" // ✓ Good - registers JPEG decoder + "database/sql" + _ "github.com/lib/pq" // ✓ Good - registers PostgreSQL driver +) + +func main() { + // Can't use fmt directly (it's blank imported) + // But JPEG format is now available for image decoding + // And PostgreSQL driver is registered with database/sql +} +``` + + +## 3. Exported vs Unexported Identifiers + +Go uses capitalization to determine visibility, unlike Java's `public`/`private` keywords. + +### Java Visibility Modifiers + + +```java !! java +package com.example.myapp; + +public class UserService { + private String apiKey; // Private to this class + protected String dbUrl; // Private + subclasses + String defaultConfig; // Package-private (default) + public String version; // Public everywhere + + private void validate() { } // Private method + public void process() { } // Public method + + // Private inner class + private class Config { } + + // Public inner class + public class Result { } +} +``` + +```go !! go +package users + +import "fmt" + +// Exported (public) - starts with capital letter +type UserService struct { + APIKey string // Exported field + dbUrl string // Unexported field (private) + Version string // Exported field +} + +// Exported constructor +func NewUserService(key string) *UserService { + return &UserService{ + APIKey: key, + dbUrl: "localhost:5432", // Private field + } +} + +// Unexported method (private) +func (s *UserService) validate() { + fmt.Println("validating...") +} + +// Exported method (public) +func (s *UserService) Process() { + s.validate() // Can call private methods +} + +// Unexported type (private) +type config struct { + debug bool +} + +// Exported type (public) +type Result struct { + Success bool + Message string +} +``` + + +### Practical Examples + + +```java !! java +// Java: Explicit visibility keywords +package com.example.models; + +public class User { + private String id; + private String email; + public String name; + + public User(String id, String email, String name) { + this.id = id; + this.email = email; + this.name = name; + } + + public String getId() { return id; } + public String getEmail() { return email; } + private boolean isValid() { + return email != null && email.contains("@"); + } +} +``` + +```go !! go +package models + +// User is exported (starts with capital) +type User struct { + ID string // Exported field + email string // Unexported field (private) + Name string // Exported field +} + +// Exported constructor +func NewUser(id, email, name string) *User { + return &User{ + ID: id, + email: email, + Name: name, + } +} + +// Exported getter +func (u *User) GetEmail() string { + return u.email +} + +// Unexported method (private) +func (u *User) isValid() bool { + return strings.Contains(u.email, "@") +} +``` + + +## 4. Package Naming Best Practices + +### Package Name Guidelines + + +```java !! java +// Java: Package names are lowercase, domain-reversed +package com.example.companyname.projectname.module; +package org.apache.commons.lang3; +package com.google.gson; + +// File structure must match package structure +// com/example/companyname/projectname/module/Class.java +``` + +```go !! go +// Go: Package names should be short, lowercase, single words +package user // ✓ Good +package users // ✓ Good +package http // ✓ Good +package json // ✓ Good + +// Avoid: +package userData // ✗ Too verbose +package myUser // ✗ Includes "my" +package httpServer // ✗ Two words, use "http" or "server" + +// Directory: go/src/github.com/user/project/ +// File: go/src/github.com/user/project/storage.go +package storage // Package name, not full path + +// Package names don't need to be unique across projects +// Import paths (directory paths) provide uniqueness +``` + + +### Package Organization + + +```java !! java +// Java: Hierarchical package structure +com/ +└── example/ + └── myapp/ + ├── model/ + │ ├── User.java + │ └── Product.java + ├── service/ + │ ├── UserService.java + │ └── ProductService.java + ├── util/ + │ └── StringUtil.java + └── Main.java +``` + +```go !! go +// Go: Flat package structure (usually) +myapp/ +├── model/ +│ ├── user.go // package model +│ └── product.go // package model +├── service/ +│ ├── user.go // package service +│ └── product.go // package service +├── util/ +│ └── string.go // package util +└── main.go // package main + +// Don't create deeply nested packages like: +// myapp/model/user/profile/ +// Keep it flat: myapp/model/ +``` + + +## 5. Go Modules (go.mod) + +Go modules manage dependencies and versioning, similar to Maven/Gradle in Java. + +### Creating a Go Module + + +```xml + + + 4.0.0 + com.example + myapp + 1.0.0 + + + + com.google.code.gson + gson + 2.10.1 + + + +``` + +```go +// Go: go.mod +module github.com/user/myapp + +go 1.21 + +require ( + github.com/gin-gonic/gin v1.9.1 + github.com/google/uuid v1.3.1 +) +``` + + +### Module Commands + + +```bash +# Java: Maven commands +mvn dependency:tree # Show dependency tree +mvn dependency:resolve # Resolve dependencies +mvn clean install # Build and install +mvn package # Create JAR +``` + +```bash +# Go: Module commands +go mod init github.com/user/myapp # Initialize module +go mod tidy # Add missing/remove unused deps +go mod vendor # Copy dependencies to vendor/ +go get github.com/pkg/errors@latest # Add/update dependency +go get github.com/pkg/errors@v1.9.0 # Use specific version +go build # Build the module +go test # Run tests +``` + + +### Semantic Versioning + + +```xml + + + com.fasterxml.jackson.core + jackson-databind + 2.15.2 + + + +[2.15,3.0) +``` + +```go +// go.mod: Semantic import versioning +module github.com/user/myapp + +go 1.21 + +require ( + // Specific version + github.com/gin-gonic/gin v1.9.1 + + // Pre-release version + github.com/example/pkg v1.3.0-beta + + // Pseudo-versions for commits without tags + github.com/example/other v0.0.0-20231201123456-abc123 +) + +// go.mod automatically updates with: +// - Major version (v1, v2, etc.) in import path +// - Minor and patch versions tracked in go.mod +``` + + +## 6. The Vendor Directory + +The vendor directory stores dependencies locally in your project. + + +```bash +# Java: Maven/Gradle local repository +# Dependencies stored in ~/.m2/ or ~/.gradle/ + +# Project-level dependency: +# Maven: mvn dependency:copy-dependencies +# Creates: target/dependency/ +``` + +```bash +# Go: Vendor directory +# After: go mod vendor +myapp/ +├── go.mod +├── go.sum +├── main.go +└── vendor/ + ├── github.com/ + │ ├── gin-gonic/ + │ │ └── gin/... + │ └── google/ + │ └── uuid/... + └── golang.org/ + └── x/... + +# Build with vendor: +# go build -mod=vendor + +# Benefits: +# - Reproducible builds +# - Offline builds +# - Version control of dependencies +``` + + +## 7. Package Visibility Examples + +### Real-World Example: HTTP Server + + +```java !! java +// Java: Multiple files, visibility keywords +package com.example.server; + +import java.io.*; +import java.net.*; + +public class HTTPServer { + private int port; + private ServerSocket serverSocket; + + public HTTPServer(int port) { + this.port = port; + } + + public void start() throws IOException { + serverSocket = new ServerSocket(port); + } + + private void handleConnection(Socket socket) { + // Private implementation + } +} + +class RequestHandler { + // Package-private class + void handle(Socket socket) { } +} +``` + +```go !! go +// Go: Exported/unexported via capitalization +package server + +import "net" + +// Server is exported +type Server struct { + port int // Exported field + listener net.Listener // Unexported field +} + +// NewServer is exported (constructor) +func NewServer(port int) *Server { + return &Server{port: port} +} + +// Start is exported +func (s *Server) Start() error { + var err error + s.listener, err = net.Listen("tcp", ":"+strconv.Itoa(s.port)) + return err +} + +// handleConnection is unexported (private) +func (s *Server) handleConnection(conn net.Conn) { + // Private implementation +} + +// RequestHandler is exported +type RequestHandler struct { + // Unexported fields +} + +// Handle is exported +func (h *RequestHandler) Handle(conn net.Conn) { + // Implementation +} +``` + + +## 8. Import Management + +### Import Grouping + + +```java !! java +// Java: Imports are typically organized by IDE +package com.example; + +// Standard library first +import java.util.List; +import java.util.ArrayList; + +// Third-party libraries +import com.google.gson.Gson; +import org.apache.commons.lang3.StringUtils; + +// Project imports +import com.example.models.User; +import com.example.services.UserService; +``` + +```go !! go +// Go: Imports are grouped and sorted by go fmt +package main + +import ( + // Standard library + "fmt" + "net/http" + "strings" + + // Local/Third-party packages + "github.com/gin-gonic/gin" + "myapp/models" + "myapp/services" +) + +// go fmt will automatically group and sort imports +``` + + +### Dot Imports (Use with Caution) + + +```java !! java +// Java: Static imports +import static java.lang.Math.*; +import static java.util.Collections.*; + +public class Example { + public void calculate() { + double result = sqrt(16.0); // No Math. prefix + List list = emptyList(); // No Collections. prefix + } +} +``` + +```go !! go +// Go: Dot imports (use sparingly!) +package main + +import ( + . "fmt" // ✗ Avoid: Can cause confusion + "math" +) + +func main() { + Println("Hello") // No fmt. prefix (confusing!) + println("Bye") // Is this fmt or another package? +} + +// Better approach: Use proper package names +package main + +import ( + "fmt" + "math" +) + +func main() { + fmt.Println("Hello") // Clear which package + math.Sqrt(16.0) +} +``` + + +## 9. Best Practices + +### Package Design + + +```java !! java +// Java: Separate packages for different concerns +package com.example.repository; +public interface UserRepository { } + +package com.example.service; +public class UserService { } + +package com.example.model; +public class User { } +``` + +```go !! go +// Go: Flat package structure, clear names +// repository/user.go +package repository + +type UserRepository interface { + Find(id string) (*User, error) +} + +// service/user.go +package service + +type UserService struct { + repo repository.UserRepository +} + +// model/user.go +package model + +type User struct { + ID string + Name string + Email string +} + +// Don't: Don't create: +// repository/user/user.go (redundant) +// service/user/service.go (redundant) +``` + + +### Avoid Cycles + + +```java !! java +// Java: Package cycle +// com.example.a depends on com.example.b +// com.example.b depends on com.example.a +// Compiler error! (usually) +``` + +```go !! go +// Go: Import cycle detected +// Package a imports package b +// Package b imports package a + +// a/a.go +package a + +import "myapp/b" + +func Process() { + b.DoSomething() +} + +// b/b.go +package b + +import "myapp/a" // ✗ Import cycle! + +func DoSomething() { + a.OtherProcess() +} + +// Solution: Refactor to create package c +// Both a and b can import c +``` + + +## 10. Package Examples + +### Database Package + + +```java !! java +// Java: Database package with interfaces +package com.example.database; + +public interface Database { + Connection getConnection() throws SQLException; + void close() throws SQLException; +} + +public class MySQLDatabase implements Database { + private String url; + private Connection connection; + + public MySQLDatabase(String url) { + this.url = url; + } + + @Override + public Connection getConnection() { + if (connection == null) { + connection = DriverManager.getConnection(url); + } + return connection; + } + + @Override + public void close() { + if (connection != null) { + connection.close(); + } + } +} +``` + +```go !! go +// database/database.go +package database + +import "database/sql" + +// Database is exported +type Database struct { + db *sql.DB // Unexported +} + +// NewDatabase is exported +func NewDatabase(driver, dsn string) (*Database, error) { + db, err := sql.Open(driver, dsn) + if err != nil { + return nil, err + } + return &Database{db: db}, nil +} + +// Conn is exported (gets connection) +func (d *Database) Conn() *sql.DB { + return d.db +} + +// Close is exported +func (d *Database) Close() error { + return d.db.Close() +} + +// Ping is exported (health check) +func (d *Database) Ping() error { + return d.db.Ping() +} +``` + + +### Utility Package + + +```java !! java +// Java: Utility class with static methods +package com.example.utils; + +public class StringUtil { + // Private constructor - utility class + private StringUtil() { } + + public static boolean isEmpty(String str) { + return str == null || str.isEmpty(); + } + + public static String truncate(String str, int maxLen) { + if (str == null) return null; + if (str.length() <= maxLen) return str; + return str.substring(0, maxLen) + "..."; + } + + public static String reverse(String str) { + return new StringBuilder(str).reverse().toString(); + } +} +``` + +```go !! go +// utils/string.go +package utils + +// IsEmpty is exported +func IsEmpty(str string) bool { + return str == "" +} + +// Truncate is exported +func Truncate(str string, maxLen int) string { + if len(str) <= maxLen { + return str + } + return str[:maxLen] + "..." +} + +// Reverse is exported +func Reverse(str string) string { + runes := []rune(str) + for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 { + runes[i], runes[j] = runes[j], runes[i] + } + return string(runes) +} + +// No need for constructors or "static" keywords +// All functions are package-level +``` + + +## 11. Practice Questions + +### Beginner + +1. Create a package `mathops` with functions: + - `Add(a, b int) int` + - `Multiply(a, b int) int` + - Write a main package that imports and uses `mathops` + +2. Create a `shapes` package: + - Exported type: `Rectangle` + - Unexported fields: `width`, `height` + - Exported method: `Area()` + - Write tests using the package + +### Intermediate + +3. Create a multi-package project: + - `models` package: `User` struct + - `repository` package: `UserRepository` interface + - `service` package: `UserService` struct + - Demonstrate proper import cycles (avoid them!) + +4. Create a Go module: + - Initialize with `go mod init` + - Add a dependency (e.g., `github.com/google/uuid`) + - Use the dependency in your code + - Run `go mod tidy` and examine go.sum + +### Advanced + +5. Design a configuration package: + - Exported interface: `Config` + - Unexported implementation: `fileConfig` + - Factory function: `LoadConfig(path string)` + - Proper error handling + +6. Create a package with subdirectory: + - `storage/storage.go` (package storage) + - `storage/memory/memory.go` (package memory) + - `storage/disk/disk.go` (package disk) + - Implement a common interface + +## 12. Project Ideas + +### Project 1: Package Structure for Web App + +Create a web application with proper package structure: +``` +myapp/ +├── go.mod +├── main.go +├── handlers/ # HTTP handlers +├── services/ # Business logic +├── models/ # Data models +├── repository/ # Data access +└── config/ # Configuration +``` + +### Project 2: Utility Library + +Create a reusable utility library: +- `strings` package: string manipulation functions +- `timeutils` package: time/date helpers +- `mathutils` package: mathematical operations +- Publish as a Go module + +### Project 3: SDK Package + +Create an SDK for a hypothetical API: +- `client` package: API client +- `resources` package: API resource types +- `errors` package: custom error types +- Demonstrate exported/unexported design + +## 13. Key Takeaways + +- **Simple Declaration**: Package names are declared in files, not derived from directories +- **Capitalization = Exported**: Capital letters = public, lowercase = private +- **Import Path != Package Name**: Import path is directory, package name is in file +- **Go Modules**: Use `go.mod` for dependency management (like Maven/Gradle) +- **Flat Structure**: Prefer flat package structures over deep hierarchies +- **No Cycles**: Go forbids import cycles +- **Semantic Versioning**: Major versions in import paths (v2, v3, etc.) + +## 14. Next Steps + +In the next module, we'll explore: +- Go interfaces vs Java interfaces +- Implicit implementation (no "implements" keyword) +- Interface composition +- Type assertions and type switches +- Polymorphism in Go vs Java + +Continue to [Module 04: Interfaces and Composition](/docs/java2go/module-04-interfaces-composition) to learn about Go's powerful interface system! diff --git a/content/docs/java2go/module-03-package-system.zh-cn.mdx b/content/docs/java2go/module-03-package-system.zh-cn.mdx new file mode 100644 index 0000000..8c477fd --- /dev/null +++ b/content/docs/java2go/module-03-package-system.zh-cn.mdx @@ -0,0 +1,993 @@ +--- +title: "模块 03: 包系统" +description: "理解 Go 的包系统与 Java 包的比较,包括导入、可见性、Go 模块依赖管理和包设计最佳实践" +--- + +# 模块 03: 包系统 + +欢迎来到模块 03!在本模块中,你将学习 Go 如何通过包组织代码,它与 Java 的包系统有何不同,以及如何使用 Go 模块管理依赖。 + +## 学习目标 + +完成本模块后,你将: +- 理解 Go 包与 Java 包的区别 +- 学习 Go 的导入约定 +- 掌握导出/未导出标识符 +- 理解 Go 模块(go.mod)与 Maven/Gradle +- 学习包命名最佳实践 +- 理解 vendor 目录 +- 掌握 Go 中的依赖管理 + +## 1. 包:Go vs Java + +### Java 包 + +在 Java 中,包组织类并提供命名空间隔离: + +```java +// 文件: com/example/myapp/utils/StringUtil.java +package com.example.myapp.utils; + +public class StringUtil { + public static boolean isEmpty(String str) { + return str == null || str.isEmpty(); + } +} +``` + +### Go 包 + +Go 包更简单 - 包名由 `package` 声明决定,而不是目录结构: + + +```java !! java +// Java: 包必须匹配目录结构 +// 文件: com/example/myapp/utils/StringUtils.java +package com.example.myapp.utils; + +public class StringUtils { + public static boolean isEmpty(String str) { + return str == null || str.length() == 0; + } +} + +// 使用 +import com.example.myapp.utils.StringUtils; +``` + +```go !! go +// Go: 包名在文件中声明 +// 文件: myapp/utils/stringutil.go (或任何 .go 文件) +package utils // 包名,不是目录路径 + +func IsEmpty(str string) bool { + return str == "" +} + +// 目录路径用于导入,但包名是你在代码中使用的 +``` + + +## 2. 导入约定 + +### Java 导入 + + +```java !! java +// Java: 显式导入 +import com.example.utils.StringUtil; +import com.example.models.User; +import java.util.List; +import java.util.ArrayList; + +// 或通配符导入(不推荐) +import com.example.utils.*; + +public class Example { + public void process() { + String name = StringUtil.trim(" hello "); + List items = new ArrayList<>(); + } +} +``` + +```go !! go +// Go: 导入是分组的 +package main + +import ( + "fmt" // 标准库 + "strings" // 标准库 + "myapp/utils" // 本地包 - 使用目录路径 + "myapp/models" // 本地包 +) + +func main() { + name := strings.TrimSpace(" hello ") + fmt.Println(name) + + // 使用包名(utils),不是目录路径 + user := models.NewUser("Alice") +} +``` + + +### 导入别名 + + +```java !! java +// Java 没有导入别名 +// 如果有冲突,必须使用完全限定名 +import com.example.json.JSON; +import org.json.JSONObject; + +public class Example { + public void process() { + // 必须使用完全限定名 + JSONObject obj = new JSONObject(); + } +} +``` + +```go !! go +// Go: 使用导入别名解决冲突 +package main + +import ( + myjson "myapp/json" // 我的 json 包的别名 + "encoding/json" // 标准库 json +) + +func main() { + // 使用别名区分 + obj1 := json.NewEncoder(nil) // 标准库 + obj2 := myjson.NewEncoder() // 我的包 +} +``` + + +### 空白导入 + + +```java !! java +// Java: 没有与空白导入等价的东西 +// 你必须显式使用所有导入的内容 +``` + +```go !! go +// Go: 空白导入仅为了副作用导入 +package main + +import ( + _ "fmt" // ✗ 无用 - 没有副作用 + _ "image/jpeg" // ✓ 好 - 注册 JPEG 解码器 + "database/sql" + _ "github.com/lib/pq" // ✓ 好 - 注册 PostgreSQL 驱动 +) + +func main() { + // 不能直接使用 fmt(它是空白导入) + // 但 JPEG 格式现在可用于图像解码 + // 并且 PostgreSQL 驱动已在 database/sql 中注册 +} +``` + + +## 3. 导出 vs 未导出标识符 + +Go 使用首字母大写确定可见性,不像 Java 的 `public`/`private` 关键字。 + +### Java 可见性修饰符 + + +```java !! java +package com.example.myapp; + +public class UserService { + private String apiKey; // 此类私有 + protected String dbUrl; // 私有 + 子类 + String defaultConfig; // 包私有(默认) + public String version; // 到处公开 + + private void validate() { } // 私有方法 + public void process() { } // 公开方法 + + // 私有内部类 + private class Config { } + + // 公开内部类 + public class Result { } +} +``` + +```go !! go +package users + +import "fmt" + +// 导出(公开) - 以大写字母开头 +type UserService struct { + APIKey string // 导出字段 + dbUrl string // 未导出字段(私有) + Version string // 导出字段 +} + +// 导出构造函数 +func NewUserService(key string) *UserService { + return &UserService{ + APIKey: key, + dbUrl: "localhost:5432", // 私有字段 + } +} + +// 未导出方法(私有) +func (s *UserService) validate() { + fmt.Println("validating...") +} + +// 导出方法(公开) +func (s *UserService) Process() { + s.validate() // 可以调用私有方法 +} + +// 未导出类型(私有) +type config struct { + debug bool +} + +// 导出类型(公开) +type Result struct { + Success bool + Message string +} +``` + + +### 实际示例 + + +```java !! java +// Java: 显式可见性关键字 +package com.example.models; + +public class User { + private String id; + private String email; + public String name; + + public User(String id, String email, String name) { + this.id = id; + this.email = email; + this.name = name; + } + + public String getId() { return id; } + public String getEmail() { return email; } + private boolean isValid() { + return email != null && email.contains("@"); + } +} +``` + +```go !! go +package models + +// User 是导出的(首字母大写) +type User struct { + ID string // 导出字段 + email string // 未导出字段(私有) + Name string // 导出字段 +} + +// 导出构造函数 +func NewUser(id, email, name string) *User { + return &User{ + ID: id, + email: email, + Name: name, + } +} + +// 导出 getter +func (u *User) GetEmail() string { + return u.email +} + +// 未导出方法(私有) +func (u *User) isValid() bool { + return strings.Contains(u.email, "@") +} +``` + + +## 4. 包命名最佳实践 + +### 包名指南 + + +```java !! java +// Java: 包名小写,域名反转 +package com.example.companyname.projectname.module; +package org.apache.commons.lang3; +package com.google.gson; + +// 文件结构必须匹配包结构 +// com/example/companyname/projectname/module/Class.java +``` + +```go !! go +// Go: 包名应该简短、小写、单词 +package user // ✓ 好 +package users // ✓ 好 +package http // ✓ 好 +package json // ✓ 好 + +// 避免: +package userData // ✗ 太冗长 +package myUser // ✗ 包含 "my" +package httpServer // ✗ 两个词,使用 "http" 或 "server" + +// 目录: go/src/github.com/user/project/ +// 文件: go/src/github.com/user/project/storage.go +package storage // 包名,不是完整路径 + +// 包名不需要跨项目唯一 +// 导入路径(目录路径)提供唯一性 +``` + + +### 包组织 + + +```java !! java +// Java: 分层包结构 +com/ +└── example/ + └── myapp/ + ├── model/ + │ ├── User.java + │ └── Product.java + ├── service/ + │ ├── UserService.java + │ └── ProductService.java + ├── util/ + │ └── StringUtil.java + └── Main.java +``` + +```go !! go +// Go: 扁平包结构(通常) +myapp/ +├── model/ +│ ├── user.go // package model +│ └── product.go // package model +├── service/ +│ ├── user.go // package service +│ └── product.go // package service +├── util/ +│ └── string.go // package util +└── main.go // package main + +// 不要创建深层嵌套包,如: +// myapp/model/user/profile/ +// 保持扁平: myapp/model/ +``` + + +## 5. Go 模块(go.mod) + +Go 模块管理依赖和版本控制,类似于 Java 中的 Maven/Gradle。 + +### 创建 Go 模块 + + +```xml + + + 4.0.0 + com.example + myapp + 1.0.0 + + + + com.google.code.gson + gson + 2.10.1 + + + +``` + +```go +// Go: go.mod +module github.com/user/myapp + +go 1.21 + +require ( + github.com/gin-gonic/gin v1.9.1 + github.com/google/uuid v1.3.1 +) +``` + + +### 模块命令 + + +```bash +# Java: Maven 命令 +mvn dependency:tree # 显示依赖树 +mvn dependency:resolve # 解析依赖 +mvn clean install # 构建和安装 +mvn package # 创建 JAR +``` + +```bash +# Go: 模块命令 +go mod init github.com/user/myapp # 初始化模块 +go mod tidy # 添加缺失/删除未使用的依赖 +go mod vendor # 复制依赖到 vendor/ +go get github.com/pkg/errors@latest # 添加/更新依赖 +go get github.com/pkg/errors@v1.9.0 # 使用特定版本 +go build # 构建模块 +go test # 运行测试 +``` + + +### 语义化版本控制 + + +```xml + + + com.fasterxml.jackson.core + jackson-databind + 2.15.2 + + + +[2.15,3.0) +``` + +```go +// go.mod: 语义化导入版本 +module github.com/user/myapp + +go 1.21 + +require ( + // 特定版本 + github.com/gin-gonic/gin v1.9.1 + + // 预发布版本 + github.com/example/pkg v1.3.0-beta + + // 无标签提交的伪版本 + github.com/example/other v0.0.0-20231201123456-abc123 +) + +// go.mod 自动更新: +// - 导入路径中的主版本(v1, v2 等) +// - go.mod 中跟踪的次版本和补丁版本 +``` + + +## 6. Vendor 目录 + +vendor 目录在项目中本地存储依赖。 + + +```bash +# Java: Maven/Gradle 本地仓库 +# 依赖存储在 ~/.m2/ 或 ~/.gradle/ + +# 项目级依赖: +# Maven: mvn dependency:copy-dependencies +# 创建: target/dependency/ +``` + +```bash +# Go: Vendor 目录 +# 执行: go mod vendor +myapp/ +├── go.mod +├── go.sum +├── main.go +└── vendor/ + ├── github.com/ + │ ├── gin-gonic/ + │ │ └── gin/... + │ └── google/ + │ └── uuid/... + └── golang.org/ + └── x/... + +# 使用 vendor 构建: +# go build -mod=vendor + +# 好处: +# - 可重现构建 +# - 离线构建 +# - 依赖的版本控制 +``` + + +## 7. 包可见性示例 + +### 实际示例: HTTP 服务器 + + +```java !! java +// Java: 多个文件,可见性关键字 +package com.example.server; + +import java.io.*; +import java.net.*; + +public class HTTPServer { + private int port; + private ServerSocket serverSocket; + + public HTTPServer(int port) { + this.port = port; + } + + public void start() throws IOException { + serverSocket = new ServerSocket(port); + } + + private void handleConnection(Socket socket) { + // 私有实现 + } +} + +class RequestHandler { + // 包私有类 + void handle(Socket socket) { } +} +``` + +```go !! go +// Go: 通过首字母大写导出/未导出 +package server + +import "net" + +// Server 是导出的 +type Server struct { + port int // 导出字段 + listener net.Listener // 未导出字段 +} + +// NewServer 是导出的(构造函数) +func NewServer(port int) *Server { + return &Server{port: port} +} + +// Start 是导出的 +func (s *Server) Start() error { + var err error + s.listener, err = net.Listen("tcp", ":"+strconv.Itoa(s.port)) + return err +} + +// handleConnection 是未导出的(私有) +func (s *Server) handleConnection(conn net.Conn) { + // 私有实现 +} + +// RequestHandler 是导出的 +type RequestHandler struct { + // 未导出字段 +} + +// Handle 是导出的 +func (h *RequestHandler) Handle(conn net.Conn) { + // 实现 +} +``` + + +## 8. 导入管理 + +### 导入分组 + + +```java !! java +// Java: 导入通常由 IDE 组织 +package com.example; + +// 标准库优先 +import java.util.List; +import java.util.ArrayList; + +// 第三方库 +import com.google.gson.Gson; +import org.apache.commons.lang3.StringUtils; + +// 项目导入 +import com.example.models.User; +import com.example.services.UserService; +``` + +```go !! go +// Go: 导入被分组和排序,由 go fmt 完成 +package main + +import ( + // 标准库 + "fmt" + "net/http" + "strings" + + // 本地/第三方包 + "github.com/gin-gonic/gin" + "myapp/models" + "myapp/services" +) + +// go fmt 会自动分组和排序导入 +``` + + +### 点导入(谨慎使用) + + +```java !! java +// Java: 静态导入 +import static java.lang.Math.*; +import static java.util.Collections.*; + +public class Example { + public void calculate() { + double result = sqrt(16.0); // 无 Math. 前缀 + List list = emptyList(); // 无 Collections. 前缀 + } +} +``` + +```go !! go +// Go: 点导入(谨慎使用!) +package main + +import ( + . "fmt" // ✗ 避免: 可能导致混淆 + "math" +) + +func main() { + Println("Hello") // 无 fmt. 前缀(混淆!) + println("Bye") // 这是 fmt 还是另一个包? +} + +// 更好的方法: 使用适当的包名 +package main + +import ( + "fmt" + "math" +) + +func main() { + fmt.Println("Hello") // 清楚哪个包 + math.Sqrt(16.0) +} +``` + + +## 9. 最佳实践 + +### 包设计 + + +```java !! java +// Java: 为不同关注点分离包 +package com.example.repository; +public interface UserRepository { } + +package com.example.service; +public class UserService { } + +package com.example.model; +public class User { } +``` + +```go !! go +// Go: 扁平包结构,清晰的名称 +// repository/user.go +package repository + +type UserRepository interface { + Find(id string) (*User, error) +} + +// service/user.go +package service + +type UserService struct { + repo repository.UserRepository +} + +// model/user.go +package model + +type User struct { + ID string + Name string + Email string +} + +// 不要: 不要创建: +// repository/user/user.go (冗余) +// service/user/service.go (冗余) +``` + + +### 避免循环 + + +```java !! java +// Java: 包循环 +// com.example.a 依赖 com.example.b +// com.example.b 依赖 com.example.a +// 编译错误!(通常) +``` + +```go !! go +// Go: 检测到导入循环 +// 包 a 导入包 b +// 包 b 导入包 a + +// a/a.go +package a + +import "myapp/b" + +func Process() { + b.DoSomething() +} + +// b/b.go +package b + +import "myapp/a" // ✗ 导入循环! + +func DoSomething() { + a.OtherProcess() +} + +// 解决方案: 重构创建包 c +// a 和 b 都可以导入 c +``` + + +## 10. 包示例 + +### 数据库包 + + +```java !! java +// Java: 带接口的数据库包 +package com.example.database; + +public interface Database { + Connection getConnection() throws SQLException; + void close() throws SQLException; +} + +public class MySQLDatabase implements Database { + private String url; + private Connection connection; + + public MySQLDatabase(String url) { + this.url = url; + } + + @Override + public Connection getConnection() { + if (connection == null) { + connection = DriverManager.getConnection(url); + } + return connection; + } + + @Override + public void close() { + if (connection != null) { + connection.close(); + } + } +} +``` + +```go !! go +// database/database.go +package database + +import "database/sql" + +// Database 是导出的 +type Database struct { + db *sql.DB // 未导出 +} + +// NewDatabase 是导出的 +func NewDatabase(driver, dsn string) (*Database, error) { + db, err := sql.Open(driver, dsn) + if err != nil { + return nil, err + } + return &Database{db: db}, nil +} + +// Conn 是导出的(获取连接) +func (d *Database) Conn() *sql.DB { + return d.db +} + +// Close 是导出的 +func (d *Database) Close() error { + return d.db.Close() +} + +// Ping 是导出的(健康检查) +func (d *Database) Ping() error { + return d.db.Ping() +} +``` + + +### 工具包 + + +```java !! java +// Java: 带静态方法的工具类 +package com.example.utils; + +public class StringUtil { + // 私有构造函数 - 工具类 + private StringUtil() { } + + public static boolean isEmpty(String str) { + return str == null || str.isEmpty(); + } + + public static String truncate(String str, int maxLen) { + if (str == null) return null; + if (str.length() <= maxLen) return str; + return str.substring(0, maxLen) + "..."; + } + + public static String reverse(String str) { + return new StringBuilder(str).reverse().toString(); + } +} +``` + +```go !! go +// utils/string.go +package utils + +// IsEmpty 是导出的 +func IsEmpty(str string) bool { + return str == "" +} + +// Truncate 是导出的 +func Truncate(str string, maxLen int) string { + if len(str) <= maxLen { + return str + } + return str[:maxLen] + "..." +} + +// Reverse 是导出的 +func Reverse(str string) string { + runes := []rune(str) + for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 { + runes[i], runes[j] = runes[j], runes[i] + } + return string(runes) +} + +// 不需要构造函数或 "static" 关键字 +// 所有函数都是包级别的 +``` + + +## 11. 练习题 + +### 初级 + +1. 创建包 `mathops`,包含函数: + - `Add(a, b int) int` + - `Multiply(a, b int) int` + - 编写使用 `mathops` 的 main 包 + +2. 创建 `shapes` 包: + - 导出类型: `Rectangle` + - 未导出字段: `width`, `height` + - 导出方法: `Area()` + - 使用该包编写测试 + +### 中级 + +3. 创建多包项目: + - `models` 包: `User` 结构体 + - `repository` 包: `UserRepository` 接口 + - `service` 包: `UserService` 结构体 + - 演示适当的导入循环(避免它们!) + +4. 创建 Go 模块: + - 用 `go mod init` 初始化 + - 添加依赖(如 `github.com/google/uuid`) + - 在代码中使用依赖 + - 运行 `go mod tidy` 并检查 go.sum + +### 高级 + +5. 设计配置包: + - 导出接口: `Config` + - 未导出实现: `fileConfig` + - 工厂函数: `LoadConfig(path string)` + - 适当的错误处理 + +6. 创建带子目录的包: + - `storage/storage.go` (package storage) + - `storage/memory/memory.go` (package memory) + - `storage/disk/disk.go` (package disk) + - 实现公共接口 + +## 12. 项目想法 + +### 项目 1: Web 应用的包结构 + +创建具有适当包结构的 Web 应用: +``` +myapp/ +├── go.mod +├── main.go +├── handlers/ # HTTP 处理器 +├── services/ # 业务逻辑 +├── models/ # 数据模型 +├── repository/ # 数据访问 +└── config/ # 配置 +``` + +### 项目 2: 工具库 + +创建可重用的工具库: +- `strings` 包: 字符串操作函数 +- `timeutils` 包: 时间/日期助手 +- `mathutils` 包: 数学运算 +- 发布为 Go 模块 + +### 项目 3: SDK 包 + +为假设的 API 创建 SDK: +- `client` 包: API 客户端 +- `resources` 包: API 资源类型 +- `errors` 包: 自定义错误类型 +- 演示导出/未导出设计 + +## 13. 关键要点 + +- **简单声明**: 包名在文件中声明,不从目录派生 +- **首字母大写 = 导出**: 大写字母 = 公开,小写 = 私有 +- **导入路径 != 包名**: 导入路径是目录,包名在文件中 +- **Go 模块**: 使用 `go.mod` 管理依赖(像 Maven/Gradle) +- **扁平结构**: 优先使用扁平包结构而非深层层次 +- **无循环**: Go 禁止导入循环 +- **语义化版本**: 导入路径中的主版本(v2, v3 等) + +## 14. 下一步 + +在下一个模块中,我们将探索: +- Go 接口 vs Java 接口 +- 隐式实现(无 "implements" 关键字) +- 接口组合 +- 类型断言和类型开关 +- Go vs Java 中的多态 + +继续学习 [模块 04: 接口和组合](/docs/java2go/module-04-interfaces-composition),了解 Go 强大的接口系统! diff --git a/content/docs/java2go/module-03-package-system.zh-tw.mdx b/content/docs/java2go/module-03-package-system.zh-tw.mdx new file mode 100644 index 0000000..42113d8 --- /dev/null +++ b/content/docs/java2go/module-03-package-system.zh-tw.mdx @@ -0,0 +1,993 @@ +--- +title: "模組 03: 套件系統" +description: "理解 Go 的套件系統與 Java 套件的比較,包括匯入、可見性、Go 模組依賴管理和套件設計最佳實踐" +--- + +# 模組 03: 套件系統 + +歡迎來到模組 03!在本模組中,你將學習 Go 如何透過套件組織程式碼,它與 Java 的套件系統有何不同,以及如何使用 Go 模組管理相依性。 + +## 學習目標 + +完成本模組後,你將: +- 理解 Go 套件與 Java 套件的區別 +- 學習 Go 的匯入約定 +- 掌握匯出/未匯出識別符 +- 理解 Go 模組(go.mod)與 Maven/Gradle +- 學習套件命名最佳實踐 +- 理解 vendor 目錄 +- 掌握 Go 中的相依性管理 + +## 1. 套件:Go vs Java + +### Java 套件 + +在 Java 中,套件組織類別並提供命名空間隔離: + +```java +// 檔案: com/example/myapp/utils/StringUtil.java +package com.example.myapp.utils; + +public class StringUtil { + public static boolean isEmpty(String str) { + return str == null || str.isEmpty(); + } +} +``` + +### Go 套件 + +Go 套件更簡單 - 套件名由 `package` 宣告決定,而不是目錄結構: + + +```java !! java +// Java: 套件必須符合目錄結構 +// 檔案: com/example/myapp/utils/StringUtils.java +package com.example.myapp.utils; + +public class StringUtils { + public static boolean isEmpty(String str) { + return str == null || str.length() == 0; + } +} + +// 使用 +import com.example.myapp.utils.StringUtils; +``` + +```go !! go +// Go: 套件名在檔案中宣告 +// 檔案: myapp/utils/stringutil.go (或任何 .go 檔案) +package utils // 套件名,不是目錄路徑 + +func IsEmpty(str string) bool { + return str == "" +} + +// 目錄路徑用於匯入,但套件名是你在程式碼中使用的 +``` + + +## 2. 匯入約定 + +### Java 匯入 + + +```java !! java +// Java: 顯式匯入 +import com.example.utils.StringUtil; +import com.example.models.User; +import java.util.List; +import java.util.ArrayList; + +// 或萬用字元匯入(不推薦) +import com.example.utils.*; + +public class Example { + public void process() { + String name = StringUtil.trim(" hello "); + List items = new ArrayList<>(); + } +} +``` + +```go !! go +// Go: 匯入是分組的 +package main + +import ( + "fmt" // 標準庫 + "strings" // 標準庫 + "myapp/utils" // 本地套件 - 使用目錄路徑 + "myapp/models" // 本地套件 +) + +func main() { + name := strings.TrimSpace(" hello ") + fmt.Println(name) + + // 使用套件名(utils),不是目錄路徑 + user := models.NewUser("Alice") +} +``` + + +### 匯入別名 + + +```java !! java +// Java 沒有匯入別名 +// 如果有衝突,必須使用完全限定名 +import com.example.json.JSON; +import org.json.JSONObject; + +public class Example { + public void process() { + // 必須使用完全限定名 + JSONObject obj = new JSONObject(); + } +} +``` + +```go !! go +// Go: 使用匯入別名解決衝突 +package main + +import ( + myjson "myapp/json" // 我的 json 套件的別名 + "encoding/json" // 標準庫 json +) + +func main() { + // 使用別名區分 + obj1 := json.NewEncoder(nil) // 標準庫 + obj2 := myjson.NewEncoder() // 我的套件 +} +``` + + +### 空白匯入 + + +```java !! java +// Java: 沒有與空白匯入等價的東西 +// 你必須顯式使用所有匯入的內容 +``` + +```go !! go +// Go: 空白匯入僅為了副作用匯入 +package main + +import ( + _ "fmt" // ✗ 無用 - 沒有副作用 + _ "image/jpeg" // ✓ 好 - 註冊 JPEG 解碼器 + "database/sql" + _ "github.com/lib/pq" // ✓ 好 - 註冊 PostgreSQL 驅動程式 +) + +func main() { + // 不能直接使用 fmt(它是空白匯入) + // 但 JPEG 格式現在可用於圖像解碼 + // 並且 PostgreSQL 驅動程式已在 database/sql 中註冊 +} +``` + + +## 3. 匯出 vs 未匯出識別符 + +Go 使用首字母大寫確定可見性,不像 Java 的 `public`/`private` 關鍵字。 + +### Java 可見性修飾符 + + +```java !! java +package com.example.myapp; + +public class UserService { + private String apiKey; // 此類別私有 + protected String dbUrl; // 私有 + 子類別 + String defaultConfig; // 套件私有(預設) + public String version; // 到處公開 + + private void validate() { } // 私有方法 + public void process() { } // 公開方法 + + // 私有內部類別 + private class Config { } + + // 公開內部類別 + public class Result { } +} +``` + +```go !! go +package users + +import "fmt" + +// 匯出(公開) - 以大寫字母開頭 +type UserService struct { + APIKey string // 匯出欄位 + dbUrl string // 未匯出欄位(私有) + Version string // 匯出欄位 +} + +// 匯出建構函式 +func NewUserService(key string) *UserService { + return &UserService{ + APIKey: key, + dbUrl: "localhost:5432", // 私有欄位 + } +} + +// 未匯出方法(私有) +func (s *UserService) validate() { + fmt.Println("validating...") +} + +// 匯出方法(公開) +func (s *UserService) Process() { + s.validate() // 可以呼叫私有方法 +} + +// 未匯出類型(私有) +type config struct { + debug bool +} + +// 匯出類型(公開) +type Result struct { + Success bool + Message string +} +``` + + +### 實際範例 + + +```java !! java +// Java: 顯式可見性關鍵字 +package com.example.models; + +public class User { + private String id; + private String email; + public String name; + + public User(String id, String email, String name) { + this.id = id; + this.email = email; + this.name = name; + } + + public String getId() { return id; } + public String getEmail() { return email; } + private boolean isValid() { + return email != null && email.contains("@"); + } +} +``` + +```go !! go +package models + +// User 是匯出的(首字母大寫) +type User struct { + ID string // 匯出欄位 + email string // 未匯出欄位(私有) + Name string // 匯出欄位 +} + +// 匯出建構函式 +func NewUser(id, email, name string) *User { + return &User{ + ID: id, + email: email, + Name: name, + } +} + +// 匯出 getter +func (u *User) GetEmail() string { + return u.email +} + +// 未匯出方法(私有) +func (u *User) isValid() bool { + return strings.Contains(u.email, "@") +} +``` + + +## 4. 套件命名最佳實踐 + +### 套件名指南 + + +```java !! java +// Java: 套件名小寫,網域反轉 +package com.example.companyname.projectname.module; +package org.apache.commons.lang3; +package com.google.gson; + +// 檔案結構必須符合套件結構 +// com/example/companyname/projectname/module/Class.java +``` + +```go !! go +// Go: 套件名應該簡短、小寫、單詞 +package user // ✓ 好 +package users // ✓ 好 +package http // ✓ 好 +package json // ✓ 好 + +// 避免: +package userData // ✗ 太冗長 +package myUser // ✗ 包含 "my" +package httpServer // ✗ 兩個詞,使用 "http" 或 "server" + +// 目錄: go/src/github.com/user/project/ +// 檔案: go/src/github.com/user/project/storage.go +package storage // 套件名,不是完整路徑 + +// 套件名不需要跨專案唯一 +// 匯入路徑(目錄路徑)提供唯一性 +``` + + +### 套件組織 + + +```java !! java +// Java: 分層套件結構 +com/ +└── example/ + └── myapp/ + ├── model/ + │ ├── User.java + │ └── Product.java + ├── service/ + │ ├── UserService.java + │ └── ProductService.java + ├── util/ + │ └── StringUtil.java + └── Main.java +``` + +```go !! go +// Go: 扁平套件結構(通常) +myapp/ +├── model/ +│ ├── user.go // package model +│ └── product.go // package model +├── service/ +│ ├── user.go // package service +│ └── product.go // package service +├── util/ +│ └── string.go // package util +└── main.go // package main + +// 不要建立深層嵌套套件,如: +// myapp/model/user/profile/ +// 保持扁平: myapp/model/ +``` + + +## 5. Go 模組(go.mod) + +Go 模組管理相依性和版本控制,類似於 Java 中的 Maven/Gradle。 + +### 建立 Go 模組 + + +```xml + + + 4.0.0 + com.example + myapp + 1.0.0 + + + + com.google.code.gson + gson + 2.10.1 + + + +``` + +```go +// Go: go.mod +module github.com/user/myapp + +go 1.21 + +require ( + github.com/gin-gonic/gin v1.9.1 + github.com/google/uuid v1.3.1 +) +``` + + +### 模組指令 + + +```bash +# Java: Maven 指令 +mvn dependency:tree # 顯示相依性樹 +mvn dependency:resolve # 解析相依性 +mvn clean install # 建構和安裝 +mvn package # 建立 JAR +``` + +```bash +# Go: 模組指令 +go mod init github.com/user/myapp # 初始化模組 +go mod tidy # 新增缺失/刪除未使用的相依性 +go mod vendor # 複製相依性到 vendor/ +go get github.com/pkg/errors@latest # 新增/更新相依性 +go get github.com/pkg/errors@v1.9.0 # 使用特定版本 +go build # 建構模組 +go test # 執行測試 +``` + + +### 語義化版本控制 + + +```xml + + + com.fasterxml.jackson.core + jackson-databind + 2.15.2 + + + +[2.15,3.0) +``` + +```go +// go.mod: 語義化匯入版本 +module github.com/user/myapp + +go 1.21 + +require ( + // 特定版本 + github.com/gin-gonic/gin v1.9.1 + + // 預發布版本 + github.com/example/pkg v1.3.0-beta + + // 無標籤提交的偽版本 + github.com/example/other v0.0.0-20231201123456-abc123 +) + +// go.mod 自動更新: +// - 匯入路徑中的主版本(v1, v2 等) +// - go.mod 中追蹤的次版本和補丁版本 +``` + + +## 6. Vendor 目錄 + +vendor 目錄在專案中本地儲存相依性。 + + +```bash +# Java: Maven/Gradle 本地儲存庫 +# 相依性儲存在 ~/.m2/ 或 ~/.gradle/ + +# 專案級相依性: +# Maven: mvn dependency:copy-dependencies +# 建立: target/dependency/ +``` + +```bash +# Go: Vendor 目錄 +# 執行: go mod vendor +myapp/ +├── go.mod +├── go.sum +├── main.go +└── vendor/ + ├── github.com/ + │ ├── gin-gonic/ + │ │ └── gin/... + │ └── google/ + │ └── uuid/... + └── golang.org/ + └── x/... + +# 使用 vendor 建構: +# go build -mod=vendor + +# 好處: +# - 可重現建構 +# - 離線建構 +# - 相依性的版本控制 +``` + + +## 7. 套件可見性範例 + +### 實際範例: HTTP 伺服器 + + +```java !! java +// Java: 多個檔案,可見性關鍵字 +package com.example.server; + +import java.io.*; +import java.net.*; + +public class HTTPServer { + private int port; + private ServerSocket serverSocket; + + public HTTPServer(int port) { + this.port = port; + } + + public void start() throws IOException { + serverSocket = new ServerSocket(port); + } + + private void handleConnection(Socket socket) { + // 私有實作 + } +} + +class RequestHandler { + // 套件私有類別 + void handle(Socket socket) { } +} +``` + +```go !! go +// Go: 透過首字母大寫匯出/未匯出 +package server + +import "net" + +// Server 是匯出的 +type Server struct { + port int // 匯出欄位 + listener net.Listener // 未匯出欄位 +} + +// NewServer 是匯出的(建構函式) +func NewServer(port int) *Server { + return &Server{port: port} +} + +// Start 是匯出的 +func (s *Server) Start() error { + var err error + s.listener, err = net.Listen("tcp", ":"+strconv.Itoa(s.port)) + return err +} + +// handleConnection 是未匯出的(私有) +func (s *Server) handleConnection(conn net.Conn) { + // 私有實作 +} + +// RequestHandler 是匯出的 +type RequestHandler struct { + // 未匯出欄位 +} + +// Handle 是匯出的 +func (h *RequestHandler) Handle(conn net.Conn) { + // 實作 +} +``` + + +## 8. 匯入管理 + +### 匯入分組 + + +```java !! java +// Java: 匯入通常由 IDE 組織 +package com.example; + +// 標準庫優先 +import java.util.List; +import java.util.ArrayList; + +// 第三方函式庫 +import com.google.gson.Gson; +import org.apache.commons.lang3.StringUtils; + +// 專案匯入 +import com.example.models.User; +import com.example.services.UserService; +``` + +```go !! go +// Go: 匯入被分組和排序,由 go fmt 完成 +package main + +import ( + // 標準庫 + "fmt" + "net/http" + "strings" + + // 本地/第三方套件 + "github.com/gin-gonic/gin" + "myapp/models" + "myapp/services" +) + +// go fmt 會自動分組和排序匯入 +``` + + +### 點匯入(謹慎使用) + + +```java !! java +// Java: 靜態匯入 +import static java.lang.Math.*; +import static java.util.Collections.*; + +public class Example { + public void calculate() { + double result = sqrt(16.0); // 無 Math. 前綴 + List list = emptyList(); // 無 Collections. 前綴 + } +} +``` + +```go !! go +// Go: 點匯入(謹慎使用!) +package main + +import ( + . "fmt" // ✗ 避免: 可能導致混淆 + "math" +) + +func main() { + Println("Hello") // 無 fmt. 前綴(混淆!) + println("Bye") // 這是 fmt 還是另一個套件? +} + +// 更好的方法: 使用適當的套件名 +package main + +import ( + "fmt" + "math" +) + +func main() { + fmt.Println("Hello") // 清楚哪個套件 + math.Sqrt(16.0) +} +``` + + +## 9. 最佳實踐 + +### 套件設計 + + +```java !! java +// Java: 為不同關注點分離套件 +package com.example.repository; +public interface UserRepository { } + +package com.example.service; +public class UserService { } + +package com.example.model; +public class User { } +``` + +```go !! go +// Go: 扁平套件結構,清晰的名稱 +// repository/user.go +package repository + +type UserRepository interface { + Find(id string) (*User, error) +} + +// service/user.go +package service + +type UserService struct { + repo repository.UserRepository +} + +// model/user.go +package model + +type User struct { + ID string + Name string + Email string +} + +// 不要: 不要建立: +// repository/user/user.go (冗餘) +// service/user/service.go (冗餘) +``` + + +### 避免循環 + + +```java !! java +// Java: 套件循環 +// com.example.a 相依於 com.example.b +// com.example.b 相依於 com.example.a +// 編譯錯誤!(通常) +``` + +```go !! go +// Go: 檢測到匯入循環 +// 套件 a 匯入套件 b +// 套件 b 匯入套件 a + +// a/a.go +package a + +import "myapp/b" + +func Process() { + b.DoSomething() +} + +// b/b.go +package b + +import "myapp/a" // ✗ 匯入循環! + +func DoSomething() { + a.OtherProcess() +} + +// 解決方案: 重構建立套件 c +// a 和 b 都可以匯入 c +``` + + +## 10. 套件範例 + +### 資料庫套件 + + +```java !! java +// Java: 帶介面的資料庫套件 +package com.example.database; + +public interface Database { + Connection getConnection() throws SQLException; + void close() throws SQLException; +} + +public class MySQLDatabase implements Database { + private String url; + private Connection connection; + + public MySQLDatabase(String url) { + this.url = url; + } + + @Override + public Connection getConnection() { + if (connection == null) { + connection = DriverManager.getConnection(url); + } + return connection; + } + + @Override + public void close() { + if (connection != null) { + connection.close(); + } + } +} +``` + +```go !! go +// database/database.go +package database + +import "database/sql" + +// Database 是匯出的 +type Database struct { + db *sql.DB // 未匯出 +} + +// NewDatabase 是匯出的 +func NewDatabase(driver, dsn string) (*Database, error) { + db, err := sql.Open(driver, dsn) + if err != nil { + return nil, err + } + return &Database{db: db}, nil +} + +// Conn 是匯出的(取得連線) +func (d *Database) Conn() *sql.DB { + return d.db +} + +// Close 是匯出的 +func (d *Database) Close() error { + return d.db.Close() +} + +// Ping 是匯出的(健康檢查) +func (d *Database) Ping() error { + return d.db.Ping() +} +``` + + +### 工具套件 + + +```java !! java +// Java: 帶靜態方法的工具類別 +package com.example.utils; + +public class StringUtil { + // 私有建構函式 - 工具類別 + private StringUtil() { } + + public static boolean isEmpty(String str) { + return str == null || str.isEmpty(); + } + + public static String truncate(String str, int maxLen) { + if (str == null) return null; + if (str.length() <= maxLen) return str; + return str.substring(0, maxLen) + "..."; + } + + public static String reverse(String str) { + return new StringBuilder(str).reverse().toString(); + } +} +``` + +```go !! go +// utils/string.go +package utils + +// IsEmpty 是匯出的 +func IsEmpty(str string) bool { + return str == "" +} + +// Truncate 是匯出的 +func Truncate(str string, maxLen int) string { + if len(str) <= maxLen { + return str + } + return str[:maxLen] + "..." +} + +// Reverse 是匯出的 +func Reverse(str string) string { + runes := []rune(str) + for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 { + runes[i], runes[j] = runes[j], runes[i] + } + return string(runes) +} + +// 不需要建構函式或 "static" 關鍵字 +// 所有函式都是套件層級的 +``` + + +## 11. 練習題 + +### 初級 + +1. 建立套件 `mathops`,包含函式: + - `Add(a, b int) int` + - `Multiply(a, b int) int` + - 編寫使用 `mathops` 的 main 套件 + +2. 建立 `shapes` 套件: + - 匯出類型: `Rectangle` + - 未匯出欄位: `width`, `height` + - 匯出方法: `Area()` + - 使用該套件編寫測試 + +### 中級 + +3. 建立多套件專案: + - `models` 套件: `User` 結構體 + - `repository` 套件: `UserRepository` 介面 + - `service` 套件: `UserService` 結構體 + - 演示適當的匯入循環(避免它們!) + +4. 建立 Go 模組: + - 用 `go mod init` 初始化 + - 新增相依性(如 `github.com/google/uuid`) + - 在程式碼中使用相依性 + - 執行 `go mod tidy` 並檢查 go.sum + +### 高級 + +5. 設計配置套件: + - 匯出介面: `Config` + - 未匯出實作: `fileConfig` + - 工廠函式: `LoadConfig(path string)` + - 適當的錯誤處理 + +6. 建立帶子目錄的套件: + - `storage/storage.go` (package storage) + - `storage/memory/memory.go` (package memory) + - `storage/disk/disk.go` (package disk) + - 實作公共介面 + +## 12. 專案想法 + +### 專案 1: Web 應用的套件結構 + +建立具有適當套件結構的 Web 應用: +``` +myapp/ +├── go.mod +├── main.go +├── handlers/ # HTTP 處理器 +├── services/ # 業務邏輯 +├── models/ # 資料模型 +├── repository/ # 資料存取 +└── config/ # 配置 +``` + +### 專案 2: 工具庫 + +建立可重用的工具庫: +- `strings` 套件: 字串操作函式 +- `timeutils` 套件: 時間/日期助手 +- `mathutils` 套件: 數學運算 +- 發布為 Go 模組 + +### 專案 3: SDK 套件 + +為假設的 API 建立 SDK: +- `client` 套件: API 用戶端 +- `resources` 套件: API 資源類型 +- `errors` 套件: 自訂錯誤類型 +- 演示匯出/未匯出設計 + +## 13. 關鍵要點 + +- **簡單宣告**: 套件名在檔案中宣告,不從目錄派生 +- **首字母大寫 = 匯出**: 大寫字母 = 公開,小寫 = 私有 +- **匯入路徑 != 套件名**: 匯入路徑是目錄,套件名在檔案中 +- **Go 模組**: 使用 `go.mod` 管理相依性(像 Maven/Gradle) +- **扁平結構**: 優先使用扁平套件結構而非深層次 +- **無循環**: Go 禁止匯入循環 +- **語義化版本**: 匯入路徑中的主版本(v2, v3 等) + +## 14. 下一步 + +在下一個模組中,我們將探索: +- Go 介面 vs Java 介面 +- 隱式實作(無 "implements" 關鍵字) +- 介面組合 +- 型別斷言和型別開關 +- Go vs Java 中的多型 + +繼續學習 [模組 04: 介面和組合](/docs/java2go/module-04-interfaces-composition),了解 Go 強大的介面系統! diff --git a/content/docs/java2go/module-04-interfaces-composition.mdx b/content/docs/java2go/module-04-interfaces-composition.mdx new file mode 100644 index 0000000..d0f81ba --- /dev/null +++ b/content/docs/java2go/module-04-interfaces-composition.mdx @@ -0,0 +1,1124 @@ +--- +title: "Module 04: Interfaces and Composition" +description: "Master Go's interface system compared to Java interfaces - implicit implementation, interface composition, type assertions, and polymorphism patterns" +--- + +# Module 04: Interfaces and Composition + +Welcome to Module 04! In this module, you'll learn about Go's powerful and flexible interface system, which differs significantly from Java's explicit interface implementation. + +## Learning Objectives + +By the end of this module, you will: +- Understand Go interfaces vs Java interfaces +- Master implicit implementation (no "implements" keyword) +- Learn interface composition +- Understand the empty interface (interface{}) +- Master type assertions and type switches +- Learn composition vs inheritance patterns +- Understand polymorphism in Go vs Java + +## 1. Interfaces: Go vs Java + +### Java: Explicit Interface Implementation + +In Java, you must explicitly declare which interfaces a class implements: + +```java +public interface Drawable { + void draw(); + void resize(int width, int height); +} + +public class Circle implements Drawable { + private int radius; + + public Circle(int radius) { + this.radius = radius; + } + + @Override + public void draw() { + System.out.println("Drawing circle with radius " + radius); + } + + @Override + public void resize(int width, int height) { + this.radius = Math.min(width, height) / 2; + } +} +``` + +### Go: Implicit Interface Implementation + +Go uses implicit implementation - if a type has all the methods required by an interface, it automatically implements the interface: + + +```java !! java +// Java: Explicit "implements" keyword +public interface Speaker { + void speak(); +} + +public class Dog implements Speaker { + @Override + public void speak() { + System.out.println("Woof!"); + } +} + +// Must declare implements Speaker +``` + +```go !! go +// Go: Implicit implementation +type Speaker interface { + Speak() +} + +type Dog struct{} + +func (d Dog) Speak() { + fmt.Println("Woof!") +} + +// Dog automatically implements Speaker +// No "implements" keyword needed! +// Any type with Speak() method is a Speaker +``` + + +## 2. Defining Interfaces + +### Basic Interface Definition + + +```java !! java +// Java: Interface with method signatures +public interface Calculator { + int add(int a, int b); + int subtract(int a, int b); + int multiply(int a, int b); +} + +public class BasicCalculator implements Calculator { + @Override + public int add(int a, int b) { + return a + b; + } + + @Override + public int subtract(int a, int b) { + return a - b; + } + + @Override + public int multiply(int a, int b) { + return a * b; + } +} +``` + +```go !! go +// Go: Interface with method signatures +type Calculator interface { + Add(a, b int) int + Subtract(a, b int) int + Multiply(a, b int) int +} + +type BasicCalculator struct{} + +func (c BasicCalculator) Add(a, b int) int { + return a + b +} + +func (c BasicCalculator) Subtract(a, b int) int { + return a - b +} + +func (c BasicCalculator) Multiply(a, b int) int { + return a * b +} + +// BasicCalculator automatically implements Calculator +``` + + +### Interface with Multiple Method Signatures + + +```java !! java +// Java: Interface with various method types +public interface DataStore { + void save(String key, String value); + String load(String key); + boolean exists(String key); + void delete(String key); + List getAllKeys(); +} +``` + +```go !! go +// Go: Interface with multiple methods +type DataStore interface { + Save(key, value string) + Load(key string) string + Exists(key string) bool + Delete(key string) + GetAllKeys() []string +} + +// Multiple types can implement this independently +type MemoryStore struct { + data map[string]string +} + +func (m *MemoryStore) Save(key, value string) { + m.data[key] = value +} + +func (m *MemoryStore) Load(key string) string { + return m.data[key] +} + +func (m *MemoryStore) Exists(key string) bool { + _, exists := m.data[key] + return exists +} + +func (m *MemoryStore) Delete(key string) { + delete(m.data, key) +} + +func (m *MemoryStore) GetAllKeys() []string { + keys := make([]string, 0, len(m.data)) + for k := range m.data { + keys = append(keys, k) + } + return keys +} +``` + + +## 3. Implicit Implementation + +### Multiple Interface Implementation + + +```java !! java +// Java: Explicitly implement multiple interfaces +public interface Reader { + String read(); +} + +public interface Writer { + void write(String data); +} + +public class FileHandler implements Reader, Writer { + @Override + public String read() { + return "file contents"; + } + + @Override + public void write(String data) { + System.out.println("Writing: " + data); + } +} +``` + +```go !! go +// Go: Automatically implements both interfaces +type Reader interface { + Read() string +} + +type Writer interface { + Write(data string) +} + +type FileHandler struct{} + +func (f FileHandler) Read() string { + return "file contents" +} + +func (f FileHandler) Write(data string) { + fmt.Println("Writing:", data) +} + +// FileHandler implements both Reader and Writer +// No need to declare it! + +func process(r Reader) { + content := r.Read() + fmt.Println("Read:", content) +} + +func save(w Writer, data string) { + w.Write(data) +} +``` + + +### Interface Satisfaction at Compile Time + + +```java !! java +// Java: Compile-time checking +public interface Flyable { + void fly(); +} + +// This won't compile - missing fly() method +public class Airplane implements Flyable { + public void takeOff() { + System.out.println("Taking off"); + } + // Compiler error: Airplane is not abstract and does not override abstract method fly() +} +``` + +```go !! go +// Go: Compile-time checking +type Flyable interface { + Fly() +} + +// This won't compile - missing Fly() method +type Airplane struct{} + +func (a Airplane) TakeOff() { + fmt.Println("Taking off") +} + +// Uncommenting this causes compile error: +// var _ Flyable = Airplane{} + +// Correct implementation: +func (a Airplane) Fly() { + fmt.Println("Flying") +} + +// Compile-time assertion (optional but useful): +var _ Flyable = Airplane{} // Verifies Airplane implements Flyable +``` + + +## 4. Interface Composition + +Go allows you to compose interfaces from other interfaces. + + +```java !! java +// Java: Interface inheritance +public interface Readable { + String read(); +} + +public interface Writable { + void write(String data); +} + +public interface ReadWrite extends Readable, Writable { + void flush(); +} + +public class File implements ReadWrite { + @Override + public String read() { return "data"; } + + @Override + public void write(String data) { } + + @Override + public void flush() { } +} +``` + +```go !! go +// Go: Interface composition +type Readable interface { + Read() string +} + +type Writable interface { + Write(data string) +} + +type ReadWrite interface { + Readable // Compose Readable interface + Writable // Compose Writable interface + Flush() // Add new method +} + +type File struct{} + +func (f File) Read() string { + return "data" +} + +func (f File) Write(data string) { + // Write implementation +} + +func (f File) Flush() { + // Flush implementation +} + +// File implements ReadWrite (implements Readable, Writable, and Flush) +``` + + +### Standard Library Interface Composition + + +```java !! java +// Java: Multiple interfaces +public interface Reader { + int read(byte[] buffer); +} + +public interface Writer { + void write(byte[] buffer); +} + +public interface ReadWriter extends Reader, Writer { + // Inherits both read() and write() +} +``` + +```go !! go +// Go: Standard library uses interface composition +package io + +type Reader interface { + Read(p []byte) (n int, err error) +} + +type Writer interface { + Write(p []byte) (n int, err error) +} + +type ReadWriter interface { + Reader + Writer +} + +// ReadWriter combines Reader and Writer +// Any type that has both Read() and Write() methods +// automatically implements ReadWriter +``` + + +## 5. The Empty Interface + +The empty interface `interface{}` (or `any` in Go 1.18+) can hold values of any type. + + +```java !! java +// Java: Object is the root of all classes +public void process(Object obj) { + if (obj instanceof String) { + String str = (String) obj; + System.out.println("String: " + str); + } else if (obj instanceof Integer) { + Integer num = (Integer) obj; + System.out.println("Integer: " + num); + } +} +``` + +```go !! go +// Go: interface{} can hold any type +func process(obj interface{}) { + switch v := obj.(type) { + case string: + fmt.Println("String:", v) + case int: + fmt.Println("Integer:", v) + default: + fmt.Println("Unknown type") + } +} + +func main() { + process("hello") + process(42) + process(3.14) +} +``` + + +### Using interface{} for Flexibility + + +```java !! java +// Java: Using Object for generic storage +public class Container { + private Object value; + + public Container(Object value) { + this.value = value; + } + + public Object getValue() { + return value; + } + + @SuppressWarnings("unchecked") + public T getValue(Class type) { + return type.cast(value); + } +} +``` + +```go !! go +// Go: Using interface{} for flexible storage +type Container struct { + value interface{} +} + +func NewContainer(value interface{}) *Container { + return &Container{value: value} +} + +func (c *Container) GetValue() interface{} { + return c.value +} + +func main() { + strContainer := NewContainer("hello") + numContainer := NewContainer(42) + + fmt.Println(strContainer.GetValue()) // "hello" + fmt.Println(numContainer.GetValue()) // 42 +} +``` + + +## 6. Type Assertions + +Type assertions allow you to extract the concrete value from an interface. + + +```java !! java +// Java: Type casting with instanceof +public void process(Object obj) { + if (obj instanceof String) { + String str = (String) obj; // Explicit cast + System.out.println(str.toUpperCase()); + } +} +``` + +```go !! go +// Go: Type assertions +func process(obj interface{}) { + // Type assertion (will panic if wrong type) + str := obj.(string) + fmt.Println(strings.ToUpper(str)) +} + +// Safe type assertion with comma-ok pattern +func processSafe(obj interface{}) { + str, ok := obj.(string) + if !ok { + fmt.Println("Not a string!") + return + } + fmt.Println(strings.ToUpper(str)) +} +``` + + +### Type Assertion Examples + + +```java !! java +// Java: Multiple type checks +public void handle(Object obj) { + if (obj instanceof String) { + String s = (String) obj; + // Handle string + } else if (obj instanceof Integer) { + Integer i = (Integer) obj; + // Handle integer + } else if (obj instanceof List) { + List list = (List) obj; + // Handle list + } +} +``` + +```go !! go +// Go: Type assertions +func handle(obj interface{}) { + // Safe type assertion + if str, ok := obj.(string); ok { + fmt.Println("String:", str) + return + } + + if num, ok := obj.(int); ok { + fmt.Println("Integer:", num) + return + } + + if list, ok := obj.([]string); ok { + fmt.Println("String slice:", list) + return + } + + fmt.Println("Unknown type") +} +``` + + +## 7. Type Switches + +Type switches are a cleaner way to handle multiple type assertions. + + +```java !! java +// Java: Chain of instanceof checks +public void describe(Object obj) { + if (obj instanceof String) { + String s = (String) obj; + System.out.println("String: " + s); + } else if (obj instanceof Integer) { + Integer i = (Integer) obj; + System.out.println("Integer: " + i); + } else if (obj instanceof Boolean) { + Boolean b = (Boolean) obj; + System.out.println("Boolean: " + b); + } else { + System.out.println("Unknown: " + obj.getClass().getName()); + } +} +``` + +```go !! go +// Go: Type switch +func describe(obj interface{}) { + switch v := obj.(type) { + case string: + fmt.Println("String:", v) + case int: + fmt.Println("Integer:", v) + case bool: + fmt.Println("Boolean:", v) + default: + fmt.Printf("Unknown: %T\n", v) // %T prints type + } +} + +func main() { + describe("hello") + describe(42) + describe(true) + describe(3.14) +} +``` + + +### Type Switch with Multiple Cases + + +```java !! java +// Java: Pattern matching (Java 16+) +public void process(Object obj) { + if (obj instanceof Integer i) { + System.out.println("Integer: " + i); + } else if (obj instanceof Long l) { + System.out.println("Long: " + l); + } else if (obj instanceof String s) { + System.out.println("String: " + s); + } +} +``` + +```go !! go +// Go: Type switch with multiple cases +func process(obj interface{}) { + switch v := obj.(type) { + case int, int32, int64: + fmt.Println("Integer type:", v) + case uint, uint32, uint64: + fmt.Println("Unsigned integer:", v) + case string: + fmt.Println("String:", v) + case nil: + fmt.Println("Nil value") + default: + fmt.Printf("Other type %T: %v\n", v, v) + } +} +``` + + +## 8. Polymorphism in Go + +Go achieves polymorphism through interfaces, not inheritance. + + +```java !! java +// Java: Polymorphism through inheritance +public abstract class Animal { + public abstract void makeSound(); +} + +public class Dog extends Animal { + @Override + public void makeSound() { + System.out.println("Woof!"); + } +} + +public class Cat extends Animal { + @Override + public void makeSound() { + System.out.println("Meow!"); + } +} + +public class Main { + public static void main(String[] args) { + List animals = Arrays.asList( + new Dog(), + new Cat() + ); + + for (Animal animal : animals) { + animal.makeSound(); // Polymorphic call + } + } +} +``` + +```go !! go +// Go: Polymorphism through interfaces +type Animal interface { + MakeSound() +} + +type Dog struct{} + +func (d Dog) MakeSound() { + fmt.Println("Woof!") +} + +type Cat struct{} + +func (c Cat) MakeSound() { + fmt.Println("Meow!") +} + +func main() { + animals := []Animal{ + Dog{}, + Cat{}, + } + + for _, animal := range animals { + animal.MakeSound() // Polymorphic call + } +} +``` + + +### Real-World Example: Payment System + + +```java !! java +// Java: Payment processing with interfaces +public interface PaymentMethod { + boolean process(double amount); + void refund(String transactionId); +} + +public class CreditCard implements PaymentMethod { + private String cardNumber; + + public CreditCard(String cardNumber) { + this.cardNumber = cardNumber; + } + + @Override + public boolean process(double amount) { + System.out.println("Processing credit card payment: $" + amount); + return true; + } + + @Override + public void refund(String transactionId) { + System.out.println("Refunding credit card: " + transactionId); + } +} + +public class PayPal implements PaymentMethod { + private String email; + + public PayPal(String email) { + this.email = email; + } + + @Override + public boolean process(double amount) { + System.out.println("Processing PayPal payment: $" + amount); + return true; + } + + @Override + public void refund(String transactionId) { + System.out.println("Refunding PayPal: " + transactionId); + } +} +``` + +```go !! go +// Go: Payment processing with interfaces +type PaymentMethod interface { + Process(amount float64) bool + Refund(transactionID string) +} + +type CreditCard struct { + CardNumber string +} + +func (c CreditCard) Process(amount float64) bool { + fmt.Printf("Processing credit card payment: $%.2f\n", amount) + return true +} + +func (c CreditCard) Refund(transactionID string) { + fmt.Println("Refunding credit card:", transactionID) +} + +type PayPal struct { + Email string +} + +func (p PayPal) Process(amount float64) bool { + fmt.Printf("Processing PayPal payment: $%.2f\n", amount) + return true +} + +func (p PayPal) Refund(transactionID string) { + fmt.Println("Refunding PayPal:", transactionID) +} + +func processPayment(method PaymentMethod, amount float64) { + if method.Process(amount) { + fmt.Println("Payment successful") + } +} + +func main() { + creditCard := CreditCard{CardNumber: "4111-1111-1111-1111"} + paypal := PayPal{Email: "user@example.com"} + + processPayment(creditCard, 100.0) + processPayment(paypal, 50.0) +} +``` + + +## 9. Best Practices + +### Interface Design Guidelines + + +```java !! java +// Java: Small, focused interfaces +public interface Closeable { + void close() throws IOException; +} + +public interface Readable { + int read(byte[] buffer) throws IOException; +} + +public interface Writable { + void write(byte[] buffer) throws IOException; +} +``` + +```go !! go +// Go: Accept interfaces, return structs +// Define small interfaces +type Reader interface { + Read(p []byte) (n int, err error) +} + +type Writer interface { + Write(p []byte) (n int, err error) +} + +// Function accepts interface (flexible) +func Copy(dst Writer, src Reader) (written int64, err error) { + // Implementation + return 0, nil +} + +// Function returns concrete type (stable) +func NewFileReader(path string) *FileReader { + return &FileReader{path: path} +} + +// Don't: Don't return interfaces unless necessary +// This is usually wrong: +// func NewReader() Reader { ... } +// Unless you're returning different implementations +``` + + +### Interface Naming Conventions + + +```java !! java +// Java: Interface names are often adjectives or nouns +public interface Runnable { void run(); } +public interface Serializable { } +public interface List { } +public interface Comparator { } +``` + +```go !! go +// Go: Single-method interfaces are often verb+er +type Reader interface { Read(...) } +type Writer interface { Write(...) } +type Stringer interface { String() string } +type Runner interface { Run() } + +// Multi-method interfaces are nouns +type Shape interface { + Area() float64 + Perimeter() float64 +} + +type Database interface { + Query(query string) Result + Execute(query string) error + Close() error +} +``` + + +## 10. Common Patterns + +### Strategy Pattern + + +```java !! java +// Java: Strategy pattern +public interface SortStrategy { + void sort(int[] array); +} + +public class BubbleSort implements SortStrategy { + @Override + public void sort(int[] array) { + // Bubble sort implementation + } +} + +public class QuickSort implements SortStrategy { + @Override + public void sort(int[] array) { + // Quick sort implementation + } +} + +public class Sorter { + private SortStrategy strategy; + + public Sorter(SortStrategy strategy) { + this.strategy = strategy; + } + + public void sortArray(int[] array) { + strategy.sort(array); + } +} +``` + +```go !! go +// Go: Strategy pattern +type SortStrategy interface { + Sort(array []int) +} + +type BubbleSort struct{} + +func (b BubbleSort) Sort(array []int) { + // Bubble sort implementation +} + +type QuickSort struct{} + +func (q QuickSort) Sort(array []int) { + // Quick sort implementation +} + +type Sorter struct { + strategy SortStrategy +} + +func NewSorter(strategy SortStrategy) *Sorter { + return &Sorter{strategy: strategy} +} + +func (s *Sorter) SortArray(array []int) { + s.strategy.Sort(array) +} +``` + + +### Middleware Pattern + + +```java !! java +// Java: Middleware with interfaces +public interface Middleware { + void handle(Request request, Response response); +} + +public class LoggingMiddleware implements Middleware { + @Override + public void handle(Request request, Response response) { + System.out.println("Request: " + request.getPath()); + // Pass to next middleware + } +} + +public class AuthMiddleware implements Middleware { + @Override + public void handle(Request request, Response response) { + if (!isAuthenticated(request)) { + response.setStatus(401); + return; + } + // Pass to next middleware + } +} +``` + +```go !! go +// Go: Middleware pattern +type Handler interface { + ServeHTTP(w http.ResponseWriter, r *http.Request) +} + +type Middleware func(Handler) Handler + +func LoggingMiddleware(next Handler) Handler { + return HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Println("Request:", r.URL.Path) + next.ServeHTTP(w, r) + }) +} + +func AuthMiddleware(next Handler) Handler { + return HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if !isAuthenticated(r) { + w.WriteHeader(http.StatusUnauthorized) + return + } + next.ServeHTTP(w, r) + }) +} +``` + + +## 11. Practice Questions + +### Beginner + +1. Create a `Shape` interface with `Area()` and `Perimeter()` methods + - Implement `Rectangle`, `Circle`, and `Triangle` types + - Create a slice of `Shape` and calculate total area + +2. Create a `Notifier` interface with `Notify(message string)` method + - Implement `EmailNotifier`, `SMSNotifier`, and `PushNotifier` + - Write a function that accepts a `Notifier` slice + +### Intermediate + +3. Create a `Database` interface with CRUD methods + - Implement `MemoryDatabase` and `FileDatabase` + - Use type assertions to handle specific implementations + - Demonstrate interface composition + +4. Build a plugin system: + - Define `Plugin` interface + - Create multiple plugin implementations + - Load plugins dynamically (using type assertions) + - Execute plugins through the interface + +### Advanced + +5. Create a middleware chain: + - Define `Middleware` interface + - Implement logging, authentication, and rate limiting + - Compose middlewares in a chain + - Use interface composition for flexibility + +6. Build a type-safe container: + - Use `interface{}` for storage + - Implement type assertions safely + - Use type switches for different types + - Demonstrate proper error handling + +## 12. Project Ideas + +### Project 1: Plugin Architecture + +Create a plugin system for a text editor: +- `Plugin` interface with `Execute()` and `GetName()` methods +- Multiple plugins: SpellCheck, WordCount, Format +- Plugin manager that loads and executes plugins +- Type-safe plugin registration + +### Project 2: Data Processing Pipeline + +Build a flexible data processing system: +- `Processor` interface with `Process(input interface{}) interface{}` +- Multiple processors: Filter, Transform, Validate +- Compose processors into pipelines +- Type assertions for type-specific processing + +### Project 3: Multi-Database Support + +Create an application supporting multiple databases: +- `Database` interface with CRUD operations +- Implementations for MySQL, PostgreSQL, MongoDB +- Database factory returning appropriate implementation +- Connection pooling and transaction support + +## 13. Key Takeaways + +- **Implicit Implementation**: No "implements" keyword - if it quacks like a duck, it's a duck +- **Small Interfaces**: Prefer small, focused interfaces (1-3 methods) +- **Interface Composition**: Build complex interfaces from simple ones +- **Accept Interfaces, Return Structs**: Functions should accept interfaces, return concrete types +- **Type Assertions**: Use comma-ok pattern for safe assertions +- **Type Switches**: Clean alternative to multiple type assertions +- **Empty Interface**: `interface{}` holds any type, but use sparingly +- **Polymorphism**: Achieved through interfaces, not inheritance + +## 14. Next Steps + +In the next module, we'll explore: +- Go error handling vs Java exceptions +- Errors as return values +- Custom error types +- Error wrapping (Go 1.13+) +- Panic and recover vs try-catch-finally +- When to use panic vs errors +- Best practices for error handling + +Continue to [Module 05: Error Handling](/docs/java2go/module-05-error-handling) to learn how Go handles errors differently from Java! diff --git a/content/docs/java2go/module-04-interfaces-composition.zh-cn.mdx b/content/docs/java2go/module-04-interfaces-composition.zh-cn.mdx new file mode 100644 index 0000000..2041ec2 --- /dev/null +++ b/content/docs/java2go/module-04-interfaces-composition.zh-cn.mdx @@ -0,0 +1,845 @@ +--- +title: "模块 04: 接口和组合" +description: "掌握 Go 的接口系统与 Java 接口的比较 - 隐式实现、接口组合、类型断言和多态模式" +--- + +# 模块 04: 接口和组合 + +欢迎来到模块 04!在本模块中,你将学习 Go 强大而灵活的接口系统,它与 Java 的显式接口实现有显著不同。 + +## 学习目标 + +完成本模块后,你将: +- 理解 Go 接口与 Java 接口的区别 +- 掌握隐式实现(无 "implements" 关键字) +- 学习接口组合 +- 理解空接口(interface{}) +- 掌握类型断言和类型开关 +- 学习组合与继承模式 +- 理解 Go 与 Java 中的多态 + +## 1. 接口:Go vs Java + +### Java: 显式接口实现 + +在 Java 中,必须显式声明类实现的接口: + +```java +public interface Drawable { + void draw(); + void resize(int width, int height); +} + +public class Circle implements Drawable { + private int radius; + + public Circle(int radius) { + this.radius = radius; + } + + @Override + public void draw() { + System.out.println("Drawing circle with radius " + radius); + } + + @Override + public void resize(int width, int height) { + this.radius = Math.min(width, height) / 2; + } +} +``` + +### Go: 隐式接口实现 + +Go 使用隐式实现 - 如果类型拥有接口所需的所有方法,它就自动实现该接口: + + +```java !! java +// Java: 显式 "implements" 关键字 +public interface Speaker { + void speak(); +} + +public class Dog implements Speaker { + @Override + public void speak() { + System.out.println("Woof!"); + } +} + +// 必须声明 implements Speaker +``` + +```go !! go +// Go: 隐式实现 +type Speaker interface { + Speak() +} + +type Dog struct{} + +func (d Dog) Speak() { + fmt.Println("Woof!") +} + +// Dog 自动实现 Speaker +// 不需要 "implements" 关键字! +// 任何有 Speak() 方法的类型都是 Speaker +``` + + +## 2. 定义接口 + +### 基本接口定义 + + +```java !! java +// Java: 带方法签名的接口 +public interface Calculator { + int add(int a, int b); + int subtract(int a, int b); + int multiply(int a, int b); +} + +public class BasicCalculator implements Calculator { + @Override + public int add(int a, int b) { + return a + b; + } + + @Override + public int subtract(int a, int b) { + return a - b; + } + + @Override + public int multiply(int a, int b) { + return a * b; + } +} +``` + +```go !! go +// Go: 带方法签名的接口 +type Calculator interface { + Add(a, b int) int + Subtract(a, b int) int + Multiply(a, b int) int +} + +type BasicCalculator struct{} + +func (c BasicCalculator) Add(a, b int) int { + return a + b +} + +func (c BasicCalculator) Subtract(a, b int) int { + return a - b +} + +func (c BasicCalculator) Multiply(a, b int) int { + return a * b +} + +// BasicCalculator 自动实现 Calculator +``` + + +### Interface with Multiple Method Signatures + + +```java !! java +// Java: Interface with various method types +public interface DataStore { + void save(String key, String value); + String load(String key); + boolean exists(String key); + void delete(String key); + List getAllKeys(); +} +``` + +```go !! go +// Go: Interface with multiple methods +type DataStore interface { + Save(key, value string) + Load(key string) string + Exists(key string) bool + Delete(key string) + GetAllKeys() []string +} + +// Multiple types can implement this independently +type MemoryStore struct { + data map[string]string +} + +func (m *MemoryStore) Save(key, value string) { + m.data[key] = value +} + +func (m *MemoryStore) Load(key string) string { + return m.data[key] +} + +func (m *MemoryStore) Exists(key string) bool { + _, exists := m.data[key] + return exists +} + +func (m *MemoryStore) Delete(key string) { + delete(m.data, key) +} + +func (m *MemoryStore) GetAllKeys() []string { + keys := make([]string, 0, len(m.data)) + for k := range m.data { + keys = append(keys, k) + } + return keys +} +``` + + +## 3. 隐式实现 + +### Multiple Interface Implementation + + +```java !! java +// Java: Explicitly implement multiple interfaces +public interface Reader { + String read(); +} + +public interface Writer { + void write(String data); +} + +public class FileHandler implements Reader, Writer { + @Override + public String read() { + return "file contents"; + } + + @Override + public void write(String data) { + System.out.println("Writing: " + data); + } +} +``` + +```go !! go +// Go: Automatically implements both interfaces +type Reader interface { + Read() string +} + +type Writer interface { + Write(data string) +} + +type FileHandler struct{} + +func (f FileHandler) Read() string { + return "file contents" +} + +func (f FileHandler) Write(data string) { + fmt.Println("Writing:", data) +} + +// FileHandler implements both Reader and Writer +// No need to declare it! + +func process(r Reader) { + content := r.Read() + fmt.Println("Read:", content) +} + +func save(w Writer, data string) { + w.Write(data) +} +``` + + +### Interface Satisfaction at Compile Time + + +```java !! java +// Java: Compile-time checking +public interface Flyable { + void fly(); +} + +// This won't compile - missing fly() method +public class Airplane implements Flyable { + public void takeOff() { + System.out.println("Taking off"); + } + // Compiler error: Airplane is not abstract and does not override abstract method fly() +} +``` + +```go !! go +// Go: Compile-time checking +type Flyable interface { + Fly() +} + +// This won't compile - missing Fly() method +type Airplane struct{} + +func (a Airplane) TakeOff() { + fmt.Println("Taking off") +} + +// Uncommenting this causes compile error: +// var _ Flyable = Airplane{} + +// Correct implementation: +func (a Airplane) Fly() { + fmt.Println("Flying") +} + +// Compile-time assertion (optional but useful): +var _ Flyable = Airplane{} // Verifies Airplane implements Flyable +``` + + +## 4. 接口组合 + +Go allows you to compose interfaces from other interfaces. + + +```java !! java +// Java: Interface inheritance +public interface Readable { + String read(); +} + +public interface Writable { + void write(String data); +} + +public interface ReadWrite extends Readable, Writable { + void flush(); +} + +public class File implements ReadWrite { + @Override + public String read() { return "data"; } + + @Override + public void write(String data) { } + + @Override + public void flush() { } +} +``` + +```go !! go +// Go: Interface composition +type Readable interface { + Read() string +} + +type Writable interface { + Write(data string) +} + +type ReadWrite interface { + Readable // Compose Readable interface + Writable // Compose Writable interface + Flush() // Add new method +} + +type File struct{} + +func (f File) Read() string { + return "data" +} + +func (f File) Write(data string) { + // Write implementation +} + +func (f File) Flush() { + // Flush implementation +} + +// File implements ReadWrite (implements Readable, Writable, and Flush) +``` + + +### Standard Library Interface Composition + + +```java !! java +// Java: Multiple interfaces +public interface Reader { + int read(byte[] buffer); +} + +public interface Writer { + void write(byte[] buffer); +} + +public interface ReadWriter extends Reader, Writer { + // Inherits both read() and write() +} +``` + +```go !! go +// Go: Standard library uses interface composition +package io + +type Reader interface { + Read(p []byte) (n int, err error) +} + +type Writer interface { + Write(p []byte) (n int, err error) +} + +type ReadWriter interface { + Reader + Writer +} + +// ReadWriter combines Reader and Writer +// Any type that has both Read() and Write() methods +// automatically implements ReadWriter +``` + + +## 5. 空接口 + +The empty interface `interface{}` (or `any` in Go 1.18+) can hold values of any type. + + +```java !! java +// Java: Object is the root of all classes +public void process(Object obj) { + if (obj instanceof String) { + String str = (String) obj; + System.out.println("String: " + str); + } else if (obj instanceof Integer) { + Integer num = (Integer) obj; + System.out.println("Integer: " + num); + } +} +``` + +```go !! go +// Go: interface{} can hold any type +func process(obj interface{}) { + switch v := obj.(type) { + case string: + fmt.Println("String:", v) + case int: + fmt.Println("Integer:", v) + default: + fmt.Println("Unknown type") + } +} + +func main() { + process("hello") + process(42) + process(3.14) +} +``` + + +### Using interface{} for Flexibility + + +```java !! java +// Java: Using Object for generic storage +public class Container { + private Object value; + + public Container(Object value) { + this.value = value; + } + + public Object getValue() { + return value; + } + + @SuppressWarnings("unchecked") + public T getValue(Class type) { + return type.cast(value); + } +} +``` + +```go !! go +// Go: Using interface{} for flexible storage +type Container struct { + value interface{} +} + +func NewContainer(value interface{}) *Container { + return &Container{value: value} +} + +func (c *Container) GetValue() interface{} { + return c.value +} + +func main() { + strContainer := NewContainer("hello") + numContainer := NewContainer(42) + + fmt.Println(strContainer.GetValue()) // "hello" + fmt.Println(numContainer.GetValue()) // 42 +} +``` + + +## 6. 类型断言 + +Type assertions allow you to extract the concrete value from an interface. + + +```java !! java +// Java: Type casting with instanceof +public void process(Object obj) { + if (obj instanceof String) { + String str = (String) obj; // Explicit cast + System.out.println(str.toUpperCase()); + } +} +``` + +```go !! go +// Go: Type assertions +func process(obj interface{}) { + // Type assertion (will panic if wrong type) + str := obj.(string) + fmt.Println(strings.ToUpper(str)) +} + +// Safe type assertion with comma-ok pattern +func processSafe(obj interface{}) { + str, ok := obj.(string) + if !ok { + fmt.Println("Not a string!") + return + } + fmt.Println(strings.ToUpper(str)) +} +``` + + +### Type Assertion Examples + + +```java !! java +// Java: Multiple type checks +public void handle(Object obj) { + if (obj instanceof String) { + String s = (String) obj; + // Handle string + } else if (obj instanceof Integer) { + Integer i = (Integer) obj; + // Handle integer + } else if (obj instanceof List) { + List list = (List) obj; + // Handle list + } +} +``` + +```go !! go +// Go: Type assertions +func handle(obj interface{}) { + // Safe type assertion + if str, ok := obj.(string); ok { + fmt.Println("String:", str) + return + } + + if num, ok := obj.(int); ok { + fmt.Println("Integer:", num) + return + } + + if list, ok := obj.([]string); ok { + fmt.Println("String slice:", list) + return + } + + fmt.Println("Unknown type") +} +``` + + +## 7. 类型开关 + +Type switches are a cleaner way to handle multiple type assertions. + + +```java !! java +// Java: Chain of instanceof checks +public void describe(Object obj) { + if (obj instanceof String) { + String s = (String) obj; + System.out.println("String: " + s); + } else if (obj instanceof Integer) { + Integer i = (Integer) obj; + System.out.println("Integer: " + i); + } else if (obj instanceof Boolean) { + Boolean b = (Boolean) obj; + System.out.println("Boolean: " + b); + } else { + System.out.println("Unknown: " + obj.getClass().getName()); + } +} +``` + +```go !! go +// Go: Type switch +func describe(obj interface{}) { + switch v := obj.(type) { + case string: + fmt.Println("String:", v) + case int: + fmt.Println("Integer:", v) + case bool: + fmt.Println("Boolean:", v) + default: + fmt.Printf("Unknown: %T\n", v) // %T prints type + } +} + +func main() { + describe("hello") + describe(42) + describe(true) + describe(3.14) +} +``` + + +### Type Switch with Multiple Cases + + +```java !! java +// Java: Pattern matching (Java 16+) +public void process(Object obj) { + if (obj instanceof Integer i) { + System.out.println("Integer: " + i); + } else if (obj instanceof Long l) { + System.out.println("Long: " + l); + } else if (obj instanceof String s) { + System.out.println("String: " + s); + } +} +``` + +```go !! go +// Go: Type switch with multiple cases +func process(obj interface{}) { + switch v := obj.(type) { + case int, int32, int64: + fmt.Println("Integer type:", v) + case uint, uint32, uint64: + fmt.Println("Unsigned integer:", v) + case string: + fmt.Println("String:", v) + case nil: + fmt.Println("Nil value") + default: + fmt.Printf("Other type %T: %v\n", v, v) + } +} +``` + + +## 8. Go 中的多态 + +Go achieves polymorphism through interfaces, not inheritance. + + +```java !! java +// Java: Polymorphism through inheritance +public abstract class Animal { + public abstract void makeSound(); +} + +public class Dog extends Animal { + @Override + public void makeSound() { + System.out.println("Woof!"); + } +} + +public class Cat extends Animal { + @Override + public void makeSound() { + System.out.println("Meow!"); + } +} + +public class Main { + public static void main(String[] args) { + List animals = Arrays.asList( + new Dog(), + new Cat() + ); + + for (Animal animal : animals) { + animal.makeSound(); // Polymorphic call + } + } +} +``` + +```go !! go +// Go: Polymorphism through interfaces +type Animal interface { + MakeSound() +} + +type Dog struct{} + +func (d Dog) MakeSound() { + fmt.Println("Woof!") +} + +type Cat struct{} + +func (c Cat) MakeSound() { + fmt.Println("Meow!") +} + +func main() { + animals := []Animal{ + Dog{}, + Cat{}, + } + + for _, animal := range animals { + animal.MakeSound() // Polymorphic call + } +} +``` + + +## 9. 最佳实践 + +### Interface Design Guidelines + + +```go !! go +// Go: Accept interfaces, return structs +// Define small interfaces +type Reader interface { + Read(p []byte) (n int, err error) +} + +type Writer interface { + Write(p []byte) (n int, err error) +} + +// Function accepts interface (flexible) +func Copy(dst Writer, src Reader) (written int64, err error) { + // Implementation + return 0, nil +} + +// Function returns concrete type (stable) +func NewFileReader(path string) *FileReader { + return &FileReader{path: path} +} + +// Don't: Don't return interfaces unless necessary +// This is usually wrong: +// func NewReader() Reader { ... } +// Unless you're returning different implementations +``` + + +## 10. 关键要点 + +- **隐式实现**: 无需 "implements" 关键字 - 如果它走起路来像鸭子,它就是鸭子 +- **小接口**: 优先使用小而专注的接口(1-3 个方法) +- **接口组合**: 从简单接口构建复杂接口 +- **接受接口,返回结构体**: 函数应该接受接口,返回具体类型 +- **类型断言**: 使用 comma-ok 模式进行安全断言 +- **类型开关**: 多个类型断言的清晰替代方案 +- **空接口**: `interface{}` 保存任何类型,但要谨慎使用 +- **多态**: 通过接口实现,而非继承 + +## 11. 练习题 + +### Beginner + +1. Create a `Shape` interface with `Area()` and `Perimeter()` methods + - Implement `Rectangle`, `Circle`, and `Triangle` types + - Create a slice of `Shape` and calculate total area + +2. Create a `Notifier` interface with `Notify(message string)` method + - Implement `EmailNotifier`, `SMSNotifier`, and `PushNotifier` + - Write a function that accepts a `Notifier` slice + +### Intermediate + +3. Create a `Database` interface with CRUD methods + - Implement `MemoryDatabase` and `FileDatabase` + - Use type assertions to handle specific implementations + - Demonstrate interface composition + +4. Build a plugin system: + - Define `Plugin` interface + - Create multiple plugin implementations + - Load plugins dynamically (using type assertions) + - Execute plugins through the interface + +### Advanced + +5. Create a middleware chain: + - Define `Middleware` interface + - Implement logging, authentication, and rate limiting + - Compose middlewares in a chain + - Use interface composition for flexibility + +6. Build a type-safe container: + - Use `interface{}` for storage + - Implement type assertions safely + - Use type switches for different types + - Demonstrate proper error handling + +## 12. 项目想法 + +### Project 1: Plugin Architecture + +Create a plugin system for a text editor: +- `Plugin` interface with `Execute()` and `GetName()` methods +- Multiple plugins: SpellCheck, WordCount, Format +- Plugin manager that loads and executes plugins +- Type-safe plugin registration + +### Project 2: Data Processing Pipeline + +Build a flexible data processing system: +- `Processor` interface with `Process(input interface{}) interface{}` +- Multiple processors: Filter, Transform, Validate +- Compose processors into pipelines +- Type assertions for type-specific processing + +## 13. 下一步 + +在下一个模块中,我们将探索: +- Go 错误处理 vs Java 异常 +- 错误作为返回值 +- 自定义错误类型 +- 错误包装(Go 1.13+) +- Panic 和 recover vs try-catch-finally +- 何时使用 panic vs errors +- 错误处理最佳实践 + +继续学习 [模块 05: 错误处理](/docs/java2go/module-05-error-handling),了解 Go 如何以不同于 Java 的方式处理错误! diff --git a/content/docs/java2go/module-04-interfaces-composition.zh-tw.mdx b/content/docs/java2go/module-04-interfaces-composition.zh-tw.mdx new file mode 100644 index 0000000..bc16a61 --- /dev/null +++ b/content/docs/java2go/module-04-interfaces-composition.zh-tw.mdx @@ -0,0 +1,500 @@ +--- +title: "模組 04: 介面和組合" +description: "掌握 Go 的介面系統與 Java 介面的比較 - 隱式實作、介面組合、型別斷言和多型模式" +--- + +# 模組 04: 介面和組合 + +歡迎來到模組 04!在本模組中,你將學習 Go 強大而靈活的介面系統,它與 Java 的顯式介面實作有顯著不同。 + +## 學習目標 + +完成本模組後,你將: +- 理解 Go 介面與 Java 介面的區別 +- 掌握隱式實作(無 "implements" 關鍵字) +- 學習介面組合 +- 理解空介面(interface{}) +- 掌握型別斷言和型別開關 +- 學習組合與繼承模式 +- 理解 Go 與 Java 中的多型 + +## 1. 介面:Go vs Java + +### Java: 顯式介面實作 + +在 Java 中,必須顯式宣告類別實作的介面: + +```java +public interface Drawable { + void draw(); + void resize(int width, int height); +} + +public class Circle implements Drawable { + private int radius; + + public Circle(int radius) { + this.radius = radius; + } + + @Override + public void draw() { + System.out.println("Drawing circle with radius " + radius); + } + + @Override + public void resize(int width, int height) { + this.radius = Math.min(width, height) / 2; + } +} +``` + +### Go: 隱式介面實作 + +Go 使用隱式實作 - 如果型別擁有介面所需的所有方法,它就自動實作該介面: + + +```java !! java +// Java: 顯式 "implements" 關鍵字 +public interface Speaker { + void speak(); +} + +public class Dog implements Speaker { + @Override + public void speak() { + System.out.println("Woof!"); + } +} + +// 必須宣告 implements Speaker +``` + +```go !! go +// Go: 隱式實作 +type Speaker interface { + Speak() +} + +type Dog struct{} + +func (d Dog) Speak() { + fmt.Println("Woof!") +} + +// Dog 自動實作 Speaker +// 不需要 "implements" 關鍵字! +// 任何有 Speak() 方法的型別都是 Speaker +``` + + +## 2. 定義介面 + +### 基本介面定義 + + +```java !! java +// Java: 帶方法簽名的介面 +public interface Calculator { + int add(int a, int b); + int subtract(int a, int b); + int multiply(int a, int b); +} + +public class BasicCalculator implements Calculator { + @Override + public int add(int a, int b) { + return a + b; + } + + @Override + public int subtract(int a, int b) { + return a - b; + } + + @Override + public int multiply(int a, int b) { + return a * b; + } +} +``` + +```go !! go +// Go: 帶方法簽名的介面 +type Calculator interface { + Add(a, b int) int + Subtract(a, b int) int + Multiply(a, b int) int +} + +type BasicCalculator struct{} + +func (c BasicCalculator) Add(a, b int) int { + return a + b +} + +func (c BasicCalculator) Subtract(a, b int) int { + return a - b +} + +func (c BasicCalculator) Multiply(a, b int) int { + return a * b +} + +// BasicCalculator 自動實作 Calculator +``` + + +## 3. 隱式實作 + +### 多介面實作 + + +```java !! java +// Java: 顯式實作多個介面 +public interface Reader { + String read(); +} + +public interface Writer { + void write(String data); +} + +public class FileHandler implements Reader, Writer { + @Override + public String read() { + return "file contents"; + } + + @Override + public void write(String data) { + System.out.println("Writing: " + data); + } +} +``` + +```go !! go +// Go: 自動實作兩個介面 +type Reader interface { + Read() string +} + +type Writer interface { + Write(data string) +} + +type FileHandler struct{} + +func (f FileHandler) Read() string { + return "file contents" +} + +func (f FileHandler) Write(data string) { + fmt.Println("Writing:", data) +} + +// FileHandler 實作 Reader 和 Writer +// 不需要宣告! +``` + + +## 4. 介面組合 + +Go 允許你從其他介面組合介面。 + + +```java !! java +// Java: 介面繼承 +public interface Readable { + String read(); +} + +public interface Writable { + void write(String data); +} + +public interface ReadWrite extends Readable, Writable { + void flush(); +} + +public class File implements ReadWrite { + @Override + public String read() { return "data"; } + + @Override + public void write(String data) { } + + @Override + public void flush() { } +} +``` + +```go !! go +// Go: 介面組合 +type Readable interface { + Read() string +} + +type Writable interface { + Write(data string) +} + +type ReadWrite interface { + Readable // 組合 Readable 介面 + Writable // 組合 Writable 介面 + Flush() // 新增新方法 +} + +type File struct{} + +func (f File) Read() string { + return "data" +} + +func (f File) Write(data string) { + // Write 實作 +} + +func (f File) Flush() { + // Flush 實作 +} + +// File 實作 ReadWrite (實作 Readable、Writable 和 Flush) +``` + + +## 5. 空介面 + +空介面 `interface{}`(或在 Go 1.18+ 中的 `any`)可以儲存任何型別的值。 + + +```java !! java +// Java: Object 是所有類別的根 +public void process(Object obj) { + if (obj instanceof String) { + String str = (String) obj; + System.out.println("String: " + str); + } else if (obj instanceof Integer) { + Integer num = (Integer) obj; + System.out.println("Integer: " + num); + } +} +``` + +```go !! go +// Go: interface{} 可以儲存任何型別 +func process(obj interface{}) { + switch v := obj.(type) { + case string: + fmt.Println("String:", v) + case int: + fmt.Println("Integer:", v) + default: + fmt.Println("Unknown type") + } +} + +func main() { + process("hello") + process(42) + process(3.14) +} +``` + + +## 6. 型別斷言 + +型別斷言允許你從介面中提取具體值。 + + +```java !! java +// Java: 使用 instanceof 進行型別轉換 +public void process(Object obj) { + if (obj instanceof String) { + String str = (String) obj; // 顯式轉換 + System.out.println(str.toUpperCase()); + } +} +``` + +```go !! go +// Go: 型別斷言 +func process(obj interface{}) { + // 型別斷言(如果型別錯誤會 panic) + str := obj.(string) + fmt.Println(strings.ToUpper(str)) +} + +// 使用 comma-ok 模式的安全型別斷言 +func processSafe(obj interface{}) { + str, ok := obj.(string) + if !ok { + fmt.Println("Not a string!") + return + } + fmt.Println(strings.ToUpper(str)) +} +``` + + +## 7. 型別開關 + +型別開關是處理多個型別斷言的更清晰方式。 + + +```java !! java +// Java: instanceof 鏈 +public void describe(Object obj) { + if (obj instanceof String) { + String s = (String) obj; + System.out.println("String: " + s); + } else if (obj instanceof Integer) { + Integer i = (Integer) obj; + System.out.println("Integer: " + i); + } else if (obj instanceof Boolean) { + Boolean b = (Boolean) obj; + System.out.println("Boolean: " + b); + } else { + System.out.println("Unknown: " + obj.getClass().getName()); + } +} +``` + +```go !! go +// Go: 型別開關 +func describe(obj interface{}) { + switch v := obj.(type) { + case string: + fmt.Println("String:", v) + case int: + fmt.Println("Integer:", v) + case bool: + fmt.Println("Boolean:", v) + default: + fmt.Printf("Unknown: %T\n", v) // %T 列印型別 + } +} + +func main() { + describe("hello") + describe(42) + describe(true) + describe(3.14) +} +``` + + +## 8. Go 中的多型 + +Go 透過介面而非繼承實作多型。 + + +```java !! java +// Java: 透過繼承的多型 +public abstract class Animal { + public abstract void makeSound(); +} + +public class Dog extends Animal { + @Override + public void makeSound() { + System.out.println("Woof!"); + } +} + +public class Cat extends Animal { + @Override + public void makeSound() { + System.out.println("Meow!"); + } +} +``` + +```go !! go +// Go: 透過介面的多型 +type Animal interface { + MakeSound() +} + +type Dog struct{} + +func (d Dog) MakeSound() { + fmt.Println("Woof!") +} + +type Cat struct{} + +func (c Cat) MakeSound() { + fmt.Println("Meow!") +} + +func main() { + animals := []Animal{ + Dog{}, + Cat{}, + } + + for _, animal := range animals { + animal.MakeSound() // 多型呼叫 + } +} +``` + + +## 9. 最佳實踐 + +### 介面設計指南 + + +```go !! go +// Go: 接受介面,返回結構體 +// 定義小介面 +type Reader interface { + Read(p []byte) (n int, err error) +} + +type Writer interface { + Write(p []byte) (n int, err error) +} + +// 函式接受介面(靈活) +func Copy(dst Writer, src Reader) (written int64, err error) { + // 實作 + return 0, nil +} + +// 函式返回具體型別(穩定) +func NewFileReader(path string) *FileReader { + return &FileReader{path: path} +} + +// 不要: 除非必要,否則不要返回介面 +// 這通常是錯誤的: +// func NewReader() Reader { ... } +// 除非你返回不同的實作 +``` + + +## 10. 關鍵要點 + +- **隱式實作**: 無需 "implements" 關鍵字 - 如果它走起路來像鴨子,它就是鴨子 +- **小介面**: 優先使用小而專注的介面(1-3 個方法) +- **介面組合**: 從簡單介面建構複雜介面 +- **接受介面,返回結構體**: 函式應該接受介面,返回具體型別 +- **型別斷言**: 使用 comma-ok 模式進行安全斷言 +- **型別開關**: 多個型別斷言的清晰替代方案 +- **空介面**: `interface{}` 儲存任何型別,但要謹慎使用 +- **多型**: 透過介面實作,而非繼承 + +## 11. 下一步 + +在下一個模組中,我們將探索: +- Go 錯誤處理 vs Java 例外 +- 錯誤作為返回值 +- 自訂錯誤型別 +- 錯誤包裝(Go 1.13+) +- Panic 和 recover vs try-catch-finally +- 何時使用 panic vs errors +- 錯誤處理最佳實踐 + +繼續學習 [模組 05: 錯誤處理](/docs/java2go/module-05-error-handling),了解 Go 如何以不同於 Java 的方式處理錯誤! diff --git a/content/docs/java2go/module-05-error-handling.mdx b/content/docs/java2go/module-05-error-handling.mdx new file mode 100644 index 0000000..653e3d1 --- /dev/null +++ b/content/docs/java2go/module-05-error-handling.mdx @@ -0,0 +1,798 @@ +--- +title: "Module 05: Error Handling" +description: "Master Go's error handling approach compared to Java exceptions - errors as values, custom error types, error wrapping, and panic/recover patterns" +--- + +# Module 05: Error Handling + +Welcome to Module 05! In this module, you'll learn Go's fundamentally different approach to error handling compared to Java's exception system. + +## Learning Objectives + +By the end of this module, you will: +- Understand Go error handling vs Java exceptions +- Master errors as return values +- Learn custom error types +- Understand error wrapping (Go 1.13+) +- Master panic and recover vs try-catch-finally +- Learn when to use panic vs errors +- Understand error handling best practices +- Compare try-catch patterns with Go idioms + +## 1. The Big Picture: Exceptions vs Error Values + +### Java: Exception-Based Error Handling + +Java uses exceptions for error handling: + +```java +public class FileProcessor { + public String readFile(String path) throws IOException { + File file = new File(path); + if (!file.exists()) { + throw new FileNotFoundException("File not found: " + path); + } + // Read and return content + return "file content"; + } + + public void processFile(String path) { + try { + String content = readFile(path); + System.out.println("Processing: " + content); + } catch (FileNotFoundException e) { + System.err.println("File not found: " + e.getMessage()); + } catch (IOException e) { + System.err.println("IO error: " + e.getMessage()); + } finally { + System.out.println("Cleanup code"); + } + } +} +``` + +### Go: Error Values + +Go treats errors as values: + + +```java !! java +// Java: Exception-based +public String readFile(String path) throws IOException { + File file = new File(path); + if (!file.exists()) { + throw new FileNotFoundException("File not found"); + } + return Files.readString(file.toPath()); +} + +// Usage +try { + String content = readFile(path); + System.out.println(content); +} catch (IOException e) { + System.err.println("Error: " + e); +} +``` + +```go !! go +// Go: Error as value +func readFile(path string) (string, error) { + file, err := os.Open(path) + if err != nil { + return "", err + } + defer file.Close() + + content, err := io.ReadAll(file) + if err != nil { + return "", err + } + return string(content), nil +} + +// Usage +content, err := readFile(path) +if err != nil { + fmt.Println("Error:", err) + return +} +fmt.Println(content) +``` + + +## 2. Errors as Return Values + +### Basic Error Handling + + +```java !! java +// Java: Throwing exceptions +public class Calculator { + public int divide(int a, int b) throws ArithmeticException { + if (b == 0) { + throw new ArithmeticException("Division by zero"); + } + return a / b; + } + + public void calculate() { + try { + int result = divide(10, 0); + System.out.println("Result: " + result); + } catch (ArithmeticException e) { + System.err.println("Error: " + e.getMessage()); + } + } +} +``` + +```go !! go +// Go: Returning errors +func divide(a, b int) (int, error) { + if b == 0 { + return 0, errors.New("division by zero") + } + return a / b, nil +} + +func calculate() { + result, err := divide(10, 0) + if err != nil { + fmt.Println("Error:", err) + return + } + fmt.Println("Result:", result) +} +``` + + +### Multiple Return Values with Errors + + +```java !! java +// Java: Throwing exceptions or using wrapper objects +public class Result { + private final T value; + private final String error; + + public Result(T value, String error) { + this.value = value; + this.error = error; + } + + public boolean isSuccess() { return error == null; } + public T getValue() { return value; } + public String getError() { return error; } +} + +public Result divide(int a, int b) { + if (b == 0) { + return new Result<>(null, "Division by zero"); + } + return new Result<>(a / b, null); +} +``` + +```go !! go +// Go: Multiple return values are idiomatic +func divide(a, b int) (int, error) { + if b == 0 { + return 0, errors.New("division by zero") + } + return a / b, nil +} + +func main() { + result, err := divide(10, 2) + if err != nil { + fmt.Println("Error:", err) + return + } + fmt.Println("Result:", result) +} +``` + + +## 3. Custom Error Types + +### Java: Custom Exceptions + + +```java !! java +// Java: Custom exception class +public class ValidationError extends Exception { + private final String field; + private final String value; + + public ValidationError(String field, String value, String message) { + super(message); + this.field = field; + this.value = value; + } + + public String getField() { return field; } + public String getValue() { return value; } +} + +// Usage +public void validate(String email) throws ValidationError { + if (email == null || email.isEmpty()) { + throw new ValidationError("email", email, "Email is required"); + } + if (!email.contains("@")) { + throw new ValidationError("email", email, "Invalid email format"); + } +} +``` + +```go !! go +// Go: Custom error type +type ValidationError struct { + Field string + Value string + Message string +} + +func (e ValidationError) Error() string { + return fmt.Sprintf("%s: %s (value: %s)", e.Field, e.Message, e.Value) +} + +// Usage +func validate(email string) error { + if email == "" { + return ValidationError{ + Field: "email", + Value: email, + Message: "Email is required", + } + } + if !strings.Contains(email, "@") { + return ValidationError{ + Field: "email", + Value: email, + Message: "Invalid email format", + } + } + return nil +} +``` + + +### Error Type Assertions + + +```java !! java +// Java: Catching specific exceptions +public void process(String email) { + try { + validate(email); + } catch (ValidationError e) { + System.err.println("Field: " + e.getField()); + System.err.println("Value: " + e.getValue()); + System.err.println("Error: " + e.getMessage()); + } catch (Exception e) { + System.err.println("Unknown error: " + e.getMessage()); + } +} +``` + +```go !! go +// Go: Type assertions on errors +func process(email string) { + err := validate(email) + if err != nil { + // Type assertion to access custom error + if validationErr, ok := err.(ValidationError); ok { + fmt.Println("Field:", validationErr.Field) + fmt.Println("Value:", validationErr.Value) + fmt.Println("Error:", validationErr.Message) + } else { + fmt.Println("Unknown error:", err) + } + return + } + fmt.Println("Valid!") +} +``` + + +## 4. Error Wrapping (Go 1.13+) + +Go 1.13 introduced error wrapping with `%w` verb in `fmt.Errorf`. + + +```java !! java +// Java: Exception chaining +public void processFile(String path) throws IOException { + try { + readFile(path); + } catch (IOException e) { + throw new IOException("Failed to process file: " + path, e); + } +} + +// Stack trace shows both exceptions +``` + +```go !! go +// Go: Error wrapping with %w +func processFile(path string) error { + content, err := readFile(path) + if err != nil { + return fmt.Errorf("failed to process file %s: %w", path, err) + } + fmt.Println(content) + return nil +} + +// Check if a specific error is wrapped +func main() { + err := processFile("test.txt") + if err != nil { + // Check if error wraps a specific error + if errors.Is(err, os.ErrNotExist) { + fmt.Println("File does not exist") + } else { + fmt.Println("Error:", err) + } + } +} +``` + + +### Errors.Is and Errors.As + + +```java !! java +// Java: getCause() for exception chaining +try { + processFile(path); +} catch (IOException e) { + Throwable cause = e.getCause(); + if (cause instanceof FileNotFoundException) { + System.out.println("File not found"); + } +} +``` + +```go !! go +// Go: errors.Is and errors.As +func main() { + err := processFile("test.txt") + + // errors.Is: Check if error wraps a specific error + if errors.Is(err, os.ErrNotExist) { + fmt.Println("File does not exist") + } + + // errors.As: Check if error wraps a specific type + var pathErr *os.PathError + if errors.As(err, &pathErr) { + fmt.Println("Path error:", pathErr.Path) + fmt.Println("Operation:", pathErr.Op) + } +} +``` + + +## 5. Panic and Recover vs Try-Catch-Finally + +Go's `panic` and `recover` are similar to Java's exceptions but should be used sparingly. + + +```java !! java +// Java: try-catch-finally +public void processFile(String path) { + File file = null; + try { + file = new File(path); + // Process file + if (file.length() > 1000000) { + throw new RuntimeException("File too large"); + } + } catch (RuntimeException e) { + System.err.println("Error: " + e.getMessage()); + } finally { + if (file != null) { + // Cleanup + System.out.println("Cleanup"); + } + } +} +``` + +```go !! go +// Go: panic and recover +func processFile(path string) (err error) { + // defer with recover (similar to finally) + defer func() { + if r := recover(); r != nil { + fmt.Println("Recovered from panic:", r) + err = fmt.Errorf("panic: %v", r) + } + }() + + file, err := os.Open(path) + if err != nil { + return err + } + defer file.Close() + + info, err := file.Stat() + if err != nil { + return err + } + + if info.Size() > 1000000 { + panic("file too large") // Panic (like throw) + } + + return nil +} +``` + + +### When to Use Panic + + +```java !! java +// Java: Use exceptions for exceptional conditions +public void process(int value) { + if (value < 0) { + throw new IllegalArgumentException("Value must be positive"); + } + // Process value +} +``` + +```go !! go +// Go: Use errors for expected problems, panic for truly exceptional ones +func process(value int) error { + if value < 0 { + return errors.New("value must be positive") // Normal error + } + return nil +} + +// Panic is appropriate for: +// 1. Unrecoverable conditions +func init() { + requiredEnv := os.Getenv("REQUIRED_ENV") + if requiredEnv == "" { + panic("REQUIRED_ENV not set") // Cannot run without this + } +} + +// 2. Programmer errors +func processArray(arr []int, index int) int { + if index < 0 || index >= len(arr) { + panic("index out of range") // Programmer error, should not happen + } + return arr[index] +} +``` + + +## 6. Common Error Patterns + +### Sentinel Errors + + +```java !! java +// Java: Constant exceptions +public class Errors { + public static final EOFException EOF = new EOFException("End of file"); + public static final IOException CONNECTION_CLOSED = + new IOException("Connection closed"); +} + +public String read() throws IOException { + if (atEnd) { + throw Errors.EOF; + } + return readNext(); +} +``` + +```go !! go +// Go: Sentinel errors +var ( + ErrEOF = errors.New("end of file") + ErrClosed = errors.New("connection closed") +) + +func read() (string, error) { + if atEnd { + return "", ErrEOF + } + return readNext(), nil +} + +// Usage +data, err := read() +if errors.Is(err, ErrEOF) { + fmt.Println("Reached end of file") +} +``` + + +### Error Context + + +```java !! java +// Java: Message chaining +public void processUser(String userID) throws Exception { + try { + User user = findUser(userID); + } catch (SQLException e) { + throw new Exception("Failed to find user: " + userID, e); + } +} +``` + +```go !! go +// Go: Error wrapping for context +func processUser(userID string) error { + user, err := findUser(userID) + if err != nil { + return fmt.Errorf("failed to find user %s: %w", userID, err) + } + // Process user + return nil +} + +// The error chain shows: +// "failed to find user 123: database connection failed" +``` + + +## 7. Deferring Cleanup + +Go's `defer` statement is often better than Java's `finally`. + + +```java !! java +// Java: finally for cleanup +public void processFile(String path) { + FileInputStream fis = null; + try { + fis = new FileInputStream(path); + // Process file + } catch (IOException e) { + System.err.println("Error: " + e); + } finally { + if (fis != null) { + try { + fis.close(); + } catch (IOException e) { + System.err.println("Error closing: " + e); + } + } + } +} +``` + +```go !! go +// Go: defer for cleanup +func processFile(path string) error { + file, err := os.Open(path) + if err != nil { + return err + } + defer file.Close() // Automatically called when function returns + + // Process file + return nil +} +``` + + +### Multiple Defers + + +```java !! java +// Java: Multiple resources (try-with-resources) +public void processFiles() throws IOException { + try (FileInputStream f1 = new FileInputStream("file1.txt"); + FileInputStream f2 = new FileInputStream("file2.txt")) { + // Process both files + } // Both closed automatically +} +``` + +```go !! go +// Go: Multiple defers (LIFO order) +func processFiles() error { + f1, err := os.Open("file1.txt") + if err != nil { + return err + } + defer f1.Close() + + f2, err := os.Open("file2.txt") + if err != nil { + return err + } + defer f2.Close() // Closed first (LIFO) + + // Process both files + // f2.Close() called, then f1.Close() + return nil +} +``` + + +## 8. Best Practices + +### Error Handling Guidelines + + +```java !! java +// Java: Don't ignore exceptions +public void process() { + try { + riskyOperation(); + } catch (Exception e) { + // ✗ Don't do this - ignoring exceptions + } +} + +// ✓ Do handle exceptions properly +public void process() { + try { + riskyOperation(); + } catch (Exception e) { + logger.error("Operation failed", e); + throw e; // Or handle appropriately + } +} +``` + +```go !! go +// Go: Don't ignore errors +func process() { + data := riskyOperation() // ✗ Don't ignore error + fmt.Println(data) +} + +// ✓ Do check errors +func process() error { + data, err := riskyOperation() + if err != nil { + return fmt.Errorf("risky operation failed: %w", err) + } + fmt.Println(data) + return nil +} + +// Only ignore when you have a good reason +data, _ := riskyOperation() // Sometimes OK for tested conditions +``` + + +### Error Messages + + +```java !! java +// Java: Error messages +public User findUser(String id) throws UserNotFoundException { + User user = database.query(id); + if (user == null) { + throw new UserNotFoundException("User not found: " + id); + } + return user; +} +``` + +```go !! go +// Go: Error messages should not be capitalized +// and should not end with punctuation +func findUser(id string) (*User, error) { + user := database.Query(id) + if user == nil { + return nil, fmt.Errorf("user not found: %s", id) + } + return user, nil +} + +// Prefix context, don't suffix +func saveUser(u User) error { + if err := validate(u); err != nil { + return fmt.Errorf("validation failed: %w", err) // ✓ Good + // return fmt.Errorf("%w: validation failed", err) // ✗ Bad + } + return nil +} +``` + + +## 9. Practice Questions + +### Beginner + +1. Create functions that return errors: + - `divide(a, b int) (int, error)` - check for division by zero + - `sqrt(x float64) (float64, error)` - check for negative numbers + - Handle errors appropriately + +2. Create a custom error type: + - `InvalidInputError` with field and message + - Use it in validation functions + - Demonstrate error type assertions + +### Intermediate + +3. Build error wrapping: + - Create functions that call other functions + - Wrap errors with context + - Use `errors.Is` and `errors.As` to inspect errors + +4. Create a file processor: + - Read files with proper error handling + - Use `defer` for cleanup + - Handle multiple error types + - Wrap errors with context + +### Advanced + +5. Implement retry logic: + - Retry operations on specific errors + - Use `errors.Is` to check for retryable errors + - Implement exponential backoff + - Handle context cancellation + +6. Build an error handler middleware: + - Catch panics in HTTP handlers + - Log errors with context + - Return appropriate error responses + - Demonstrate recovery patterns + +## 10. Project Ideas + +### Project 1: File Processing Utility + +Create a robust file processing tool: +- Read files with comprehensive error handling +- Handle file not found, permission errors, etc. +- Use error wrapping to add context +- Implement retry logic for transient failures +- Log all errors appropriately + +### Project 2: Database Wrapper + +Create a database access layer: +- Custom error types for different DB errors +- Wrap database driver errors +- Handle connection failures gracefully +- Implement connection pooling with error handling +- Provide transaction support with proper rollback + +### Project 3: API Client + +Build an HTTP API client: +- Handle network errors, timeouts +- Custom error types for different HTTP status codes +- Retry logic with exponential backoff +- Request/response logging +- Error context preservation + +## 11. Key Takeaways + +- **Errors as Values**: Go returns errors as values, doesn't throw exceptions +- **Always Check Errors**: Never ignore returned errors +- **Error Wrapping**: Use `%w` to wrap errors with context +- **Custom Errors**: Implement `Error()` method for custom error types +- **errors.Is**: Check if error wraps a specific error +- **errors.As**: Check if error matches a specific type +- **Panic Sparingly**: Use panic only for truly exceptional conditions +- **Defer Cleanup**: Use `defer` for cleanup, better than `finally` + +## 12. Next Steps + +In the next modules, we'll explore: +- **Module 06**: Goroutines vs Java Threads +- **Module 07**: Channels for communication +- **Module 08**: Concurrency patterns and best practices + +Continue to [Module 06: Goroutines and Threads](/docs/java2go/module-06-goroutines-threads) to learn about Go's lightweight threads! diff --git a/content/docs/java2go/module-05-error-handling.zh-cn.mdx b/content/docs/java2go/module-05-error-handling.zh-cn.mdx new file mode 100644 index 0000000..822334a --- /dev/null +++ b/content/docs/java2go/module-05-error-handling.zh-cn.mdx @@ -0,0 +1,448 @@ +--- +title: "模块 05: 错误处理" +description: "掌握 Go 的错误处理方法与 Java 异常的比较 - 错误作为值、自定义错误类型、错误包装和 panic/recover 模式" +--- + +# 模块 05: 错误处理 + +欢迎来到模块 05!在本模块中,你将学习 Go 与 Java 异常系统根本不同的错误处理方法。 + +## 学习目标 + +完成本模块后,你将: +- 理解 Go 错误处理 vs Java 异常 +- 掌握错误作为返回值 +- 学习自定义错误类型 +- 理解错误包装(Go 1.13+) +- 掌握 panic 和 recover vs try-catch-finally +- 学习何时使用 panic vs errors +- 理解错误处理最佳实践 +- 比较 try-catch 模式与 Go 惯用语 + +## 1. 大局观:异常 vs 错误值 + +### Java: 基于异常的错误处理 + +Java 使用异常进行错误处理: + +```java +public class FileProcessor { + public String readFile(String path) throws IOException { + File file = new File(path); + if (!file.exists()) { + throw new FileNotFoundException("File not found: " + path); + } + return "file content"; + } + + public void processFile(String path) { + try { + String content = readFile(path); + System.out.println("Processing: " + content); + } catch (FileNotFoundException e) { + System.err.println("File not found: " + e.getMessage()); + } catch (IOException e) { + System.err.println("IO error: " + e.getMessage()); + } finally { + System.out.println("Cleanup code"); + } + } +} +``` + +### Go: 错误值 + +Go 将错误视为值: + + +```java !! java +// Java: 基于异常 +public String readFile(String path) throws IOException { + File file = new File(path); + if (!file.exists()) { + throw new FileNotFoundException("File not found"); + } + return Files.readString(file.toPath()); +} + +// 使用 +try { + String content = readFile(path); + System.out.println(content); +} catch (IOException e) { + System.err.println("Error: " + e); +} +``` + +```go !! go +// Go: 错误作为值 +func readFile(path string) (string, error) { + file, err := os.Open(path) + if err != nil { + return "", err + } + defer file.Close() + + content, err := io.ReadAll(file) + if err != nil { + return "", err + } + return string(content), nil +} + +// 使用 +content, err := readFile(path) +if err != nil { + fmt.Println("Error:", err) + return +} +fmt.Println(content) +``` + + +## 2. 错误作为返回值 + +### 基本错误处理 + + +```java !! java +// Java: 抛出异常 +public class Calculator { + public int divide(int a, int b) throws ArithmeticException { + if (b == 0) { + throw new ArithmeticException("Division by zero"); + } + return a / b; + } + + public void calculate() { + try { + int result = divide(10, 0); + System.out.println("Result: " + result); + } catch (ArithmeticException e) { + System.err.println("Error: " + e.getMessage()); + } + } +} +``` + +```go !! go +// Go: 返回错误 +func divide(a, b int) (int, error) { + if b == 0 { + return 0, errors.New("division by zero") + } + return a / b, nil +} + +func calculate() { + result, err := divide(10, 0) + if err != nil { + fmt.Println("Error:", err) + return + } + fmt.Println("Result:", result) +} +``` + + +## 3. 自定义错误类型 + +### Java: 自定义异常 + + +```java !! java +// Java: 自定义异常类 +public class ValidationError extends Exception { + private final String field; + private final String value; + + public ValidationError(String field, String value, String message) { + super(message); + this.field = field; + this.value = value; + } + + public String getField() { return field; } + public String getValue() { return value; } +} + +// 使用 +public void validate(String email) throws ValidationError { + if (email == null || email.isEmpty()) { + throw new ValidationError("email", email, "Email is required"); + } +} +``` + +```go !! go +// Go: 自定义错误类型 +type ValidationError struct { + Field string + Value string + Message string +} + +func (e ValidationError) Error() string { + return fmt.Sprintf("%s: %s (value: %s)", e.Field, e.Message, e.Value) +} + +// 使用 +func validate(email string) error { + if email == "" { + return ValidationError{ + Field: "email", + Value: email, + Message: "Email is required", + } + } + return nil +} +``` + + +## 4. 错误包装(Go 1.13+) + +Go 1.13 引入了使用 `fmt.Errorf` 中的 `%w` 动词进行错误包装。 + + +```java !! java +// Java: 异常链 +public void processFile(String path) throws IOException { + try { + readFile(path); + } catch (IOException e) { + throw new IOException("Failed to process file: " + path, e); + } +} + +// 堆栈跟踪显示两个异常 +``` + +```go !! go +// Go: 使用 %w 进行错误包装 +func processFile(path string) error { + content, err := readFile(path) + if err != nil { + return fmt.Errorf("failed to process file %s: %w", path, err) + } + fmt.Println(content) + return nil +} + +// 检查是否包装了特定错误 +func main() { + err := processFile("test.txt") + if err != nil { + // 检查错误是否包装了特定错误 + if errors.Is(err, os.ErrNotExist) { + fmt.Println("File does not exist") + } else { + fmt.Println("Error:", err) + } + } +} +``` + + +## 5. Panic 和 Recover vs Try-Catch-Finally + +Go 的 `panic` 和 `recover` 类似于 Java 的异常,但应该谨慎使用。 + + +```java !! java +// Java: try-catch-finally +public void processFile(String path) { + File file = null; + try { + file = new File(path); + if (file.length() > 1000000) { + throw new RuntimeException("File too large"); + } + } catch (RuntimeException e) { + System.err.println("Error: " + e.getMessage()); + } finally { + if (file != null) { + System.out.println("Cleanup"); + } + } +} +``` + +```go !! go +// Go: panic 和 recover +func processFile(path string) (err error) { + // 带有 recover 的 defer(类似于 finally) + defer func() { + if r := recover(); r != nil { + fmt.Println("Recovered from panic:", r) + err = fmt.Errorf("panic: %v", r) + } + }() + + file, err := os.Open(path) + if err != nil { + return err + } + defer file.Close() + + info, err := file.Stat() + if err != nil { + return err + } + + if info.Size() > 1000000 { + panic("file too large") // Panic(类似 throw) + } + + return nil +} +``` + + +## 6. 延迟清理 + +Go 的 `defer` 语句通常比 Java 的 `finally` 更好。 + + +```java !! java +// Java: finally 用于清理 +public void processFile(String path) { + FileInputStream fis = null; + try { + fis = new FileInputStream(path); + } catch (IOException e) { + System.err.println("Error: " + e); + } finally { + if (fis != null) { + try { + fis.close(); + } catch (IOException e) { + System.err.println("Error closing: " + e); + } + } + } +} +``` + +```go !! go +// Go: defer 用于清理 +func processFile(path string) error { + file, err := os.Open(path) + if err != nil { + return err + } + defer file.Close() // 函数返回时自动调用 + + // 处理文件 + return nil +} +``` + + +## 7. 最佳实践 + +### 错误处理指南 + + +```go !! go +// Go: 不要忽略错误 +func process() { + data := riskyOperation() // ✗ 不要忽略错误 + fmt.Println(data) +} + +// ✓ 要检查错误 +func process() error { + data, err := riskyOperation() + if err != nil { + return fmt.Errorf("risky operation failed: %w", err) + } + fmt.Println(data) + return nil +} + +// 只有在有充分理由时才忽略 +data, _ := riskyOperation() // 有时可以,对于已测试的条件 +``` + + +### 错误消息 + + +```go !! go +// Go: 错误消息不应大写 +// 也不应以标点符号结尾 +func findUser(id string) (*User, error) { + user := database.Query(id) + if user == nil { + return nil, fmt.Errorf("user not found: %s", id) + } + return user, nil +} + +// 前缀上下文,不要后缀 +func saveUser(u User) error { + if err := validate(u); err != nil { + return fmt.Errorf("validation failed: %w", err) // ✓ 好 + // return fmt.Errorf("%w: validation failed", err) // ✗ 不好 + } + return nil +} +``` + + +## 8. 练习题 + +### 初级 + +1. 创建返回错误的函数: + - `divide(a, b int) (int, error)` - 检查除零 + - `sqrt(x float64) (float64, error)` - 检查负数 + - 适当处理错误 + +2. 创建自定义错误类型: + - `InvalidInputError` 带有字段和消息 + - 在验证函数中使用它 + - 演示错误类型断言 + +### 中级 + +3. 构建错误包装: + - 创建调用其他函数的函数 + - 使用上下文包装错误 + - 使用 `errors.Is` 和 `errors.As` 检查错误 + +4. 创建文件处理器: + - 使用适当的错误处理读取文件 + - 使用 `defer` 进行清理 + - 处理多种错误类型 + - 使用上下文包装错误 + +### 高级 + +5. 实现重试逻辑: + - 在特定错误时重试操作 + - 使用 `errors.Is` 检查可重试错误 + - 实现指数退避 + - 处理上下文取消 + +## 9. 关键要点 + +- **错误作为值**: Go 返回错误作为值,不抛出异常 +- **始终检查错误**: 永远不要忽略返回的错误 +- **错误包装**: 使用 `%w` 用上下文包装错误 +- **自定义错误**: 为自定义错误类型实现 `Error()` 方法 +- **errors.Is**: 检查错误是否包装了特定错误 +- **errors.As**: 检查错误是否匹配特定类型 +- **谨慎使用 Panic**: 仅对真正的异常情况使用 panic +- **Defer 清理**: 使用 `defer` 进行清理,比 `finally` 更好 + +## 10. 下一步 + +在接下来的模块中,我们将探索: +- **模块 06**: Goroutines vs Java 线程 +- **模块 07**: 用于通信的通道 +- **模块 08**: 并发模式和最佳实践 + +继续学习 [模块 06: Goroutines 和线程](/docs/java2go/module-06-goroutines-threads),了解 Go 的轻量级线程! diff --git a/content/docs/java2go/module-05-error-handling.zh-tw.mdx b/content/docs/java2go/module-05-error-handling.zh-tw.mdx new file mode 100644 index 0000000..a3e15da --- /dev/null +++ b/content/docs/java2go/module-05-error-handling.zh-tw.mdx @@ -0,0 +1,448 @@ +--- +title: "模組 05: 錯誤處理" +description: "掌握 Go 的錯誤處理方法與 Java 例外狀況的比較 - 錯誤作為值、自訂錯誤型別、錯誤包裝和 panic/recover 模式" +--- + +# 模組 05: 錯誤處理 + +歡迎來到模組 05!在本模組中,你將學習 Go 與 Java 例外狀況系統根本不同的錯誤處理方法。 + +## 學習目標 + +完成本模組後,你將: +- 理解 Go 錯誤處理 vs Java 例外 +- 掌握錯誤作為返回值 +- 學習自訂錯誤型別 +- 理解錯誤包裝(Go 1.13+) +- 掌握 panic 和 recover vs try-catch-finally +- 學習何時使用 panic vs errors +- 理解錯誤處理最佳實踐 +- 比較 try-catch 模式與 Go 慣用語 + +## 1. 大局觀:例外 vs 錯誤值 + +### Java: 基於例外的錯誤處理 + +Java 使用例外進行錯誤處理: + +```java +public class FileProcessor { + public String readFile(String path) throws IOException { + File file = new File(path); + if (!file.exists()) { + throw new FileNotFoundException("File not found: " + path); + } + return "file content"; + } + + public void processFile(String path) { + try { + String content = readFile(path); + System.out.println("Processing: " + content); + } catch (FileNotFoundException e) { + System.err.println("File not found: " + e.getMessage()); + } catch (IOException e) { + System.err.println("IO error: " + e.getMessage()); + } finally { + System.out.println("Cleanup code"); + } + } +} +``` + +### Go: 錯誤值 + +Go 將錯誤視為值: + + +```java !! java +// Java: 基於例外 +public String readFile(String path) throws IOException { + File file = new File(path); + if (!file.exists()) { + throw new FileNotFoundException("File not found"); + } + return Files.readString(file.toPath()); +} + +// 使用 +try { + String content = readFile(path); + System.out.println(content); +} catch (IOException e) { + System.err.println("Error: " + e); +} +``` + +```go !! go +// Go: 錯誤作為值 +func readFile(path string) (string, error) { + file, err := os.Open(path) + if err != nil { + return "", err + } + defer file.Close() + + content, err := io.ReadAll(file) + if err != nil { + return "", err + } + return string(content), nil +} + +// 使用 +content, err := readFile(path) +if err != nil { + fmt.Println("Error:", err) + return +} +fmt.Println(content) +``` + + +## 2. 錯誤作為返回值 + +### 基本錯誤處理 + + +```java !! java +// Java: 拋出例外 +public class Calculator { + public int divide(int a, int b) throws ArithmeticException { + if (b == 0) { + throw new ArithmeticException("Division by zero"); + } + return a / b; + } + + public void calculate() { + try { + int result = divide(10, 0); + System.out.println("Result: " + result); + } catch (ArithmeticException e) { + System.err.println("Error: " + e.getMessage()); + } + } +} +``` + +```go !! go +// Go: 返回錯誤 +func divide(a, b int) (int, error) { + if b == 0 { + return 0, errors.New("division by zero") + } + return a / b, nil +} + +func calculate() { + result, err := divide(10, 0) + if err != nil { + fmt.Println("Error:", err) + return + } + fmt.Println("Result:", result) +} +``` + + +## 3. 自訂錯誤型別 + +### Java: 自訂例外 + + +```java !! java +// Java: 自訂例外類別 +public class ValidationError extends Exception { + private final String field; + private final String value; + + public ValidationError(String field, String value, String message) { + super(message); + this.field = field; + this.value = value; + } + + public String getField() { return field; } + public String getValue() { return value; } +} + +// 使用 +public void validate(String email) throws ValidationError { + if (email == null || email.isEmpty()) { + throw new ValidationError("email", email, "Email is required"); + } +} +``` + +```go !! go +// Go: 自訂錯誤型別 +type ValidationError struct { + Field string + Value string + Message string +} + +func (e ValidationError) Error() string { + return fmt.Sprintf("%s: %s (value: %s)", e.Field, e.Message, e.Value) +} + +// 使用 +func validate(email string) error { + if email == "" { + return ValidationError{ + Field: "email", + Value: email, + Message: "Email is required", + } + } + return nil +} +``` + + +## 4. 錯誤包裝(Go 1.13+) + +Go 1.13 引入了使用 `fmt.Errorf` 中的 `%w` 動詞進行錯誤包裝。 + + +```java !! java +// Java: 例外鏈 +public void processFile(String path) throws IOException { + try { + readFile(path); + } catch (IOException e) { + throw new IOException("Failed to process file: " + path, e); + } +} + +// 堆疊追蹤顯示兩個例外 +``` + +```go !! go +// Go: 使用 %w 進行錯誤包裝 +func processFile(path string) error { + content, err := readFile(path) + if err != nil { + return fmt.Errorf("failed to process file %s: %w", path, err) + } + fmt.Println(content) + return nil +} + +// 檢查是否包裝了特定錯誤 +func main() { + err := processFile("test.txt") + if err != nil { + // 檢查錯誤是否包裝了特定錯誤 + if errors.Is(err, os.ErrNotExist) { + fmt.Println("File does not exist") + } else { + fmt.Println("Error:", err) + } + } +} +``` + + +## 5. Panic 和 Recover vs Try-Catch-Finally + +Go 的 `panic` 和 `recover` 類似於 Java 的例外,但應該謹慎使用。 + + +```java !! java +// Java: try-catch-finally +public void processFile(String path) { + File file = null; + try { + file = new File(path); + if (file.length() > 1000000) { + throw new RuntimeException("File too large"); + } + } catch (RuntimeException e) { + System.err.println("Error: " + e.getMessage()); + } finally { + if (file != null) { + System.out.println("Cleanup"); + } + } +} +``` + +```go !! go +// Go: panic 和 recover +func processFile(path string) (err error) { + // 帶有 recover 的 defer(類似於 finally) + defer func() { + if r := recover(); r != nil { + fmt.Println("Recovered from panic:", r) + err = fmt.Errorf("panic: %v", r) + } + }() + + file, err := os.Open(path) + if err != nil { + return err + } + defer file.Close() + + info, err := file.Stat() + if err != nil { + return err + } + + if info.Size() > 1000000 { + panic("file too large") // Panic(類似 throw) + } + + return nil +} +``` + + +## 6. 延遲清理 + +Go 的 `defer` 陳述式通常比 Java 的 `finally` 更好。 + + +```java !! java +// Java: finally 用於清理 +public void processFile(String path) { + FileInputStream fis = null; + try { + fis = new FileInputStream(path); + } catch (IOException e) { + System.err.println("Error: " + e); + } finally { + if (fis != null) { + try { + fis.close(); + } catch (IOException e) { + System.err.println("Error closing: " + e); + } + } + } +} +``` + +```go !! go +// Go: defer 用於清理 +func processFile(path string) error { + file, err := os.Open(path) + if err != nil { + return err + } + defer file.Close() // 函式返回時自動呼叫 + + // 處理檔案 + return nil +} +``` + + +## 7. 最佳實踐 + +### 錯誤處理指南 + + +```go !! go +// Go: 不要忽略錯誤 +func process() { + data := riskyOperation() // ✗ 不要忽略錯誤 + fmt.Println(data) +} + +// ✓ 要檢查錯誤 +func process() error { + data, err := riskyOperation() + if err != nil { + return fmt.Errorf("risky operation failed: %w", err) + } + fmt.Println(data) + return nil +} + +// 只有在有充分理由時才忽略 +data, _ := riskyOperation() // 有時可以,對於已測試的條件 +``` + + +### 錯誤訊息 + + +```go !! go +// Go: 錯誤訊息不應大寫 +// 也不應以標點符號結尾 +func findUser(id string) (*User, error) { + user := database.Query(id) + if user == nil { + return nil, fmt.Errorf("user not found: %s", id) + } + return user, nil +} + +// 前綴上下文,不要後綴 +func saveUser(u User) error { + if err := validate(u); err != nil { + return fmt.Errorf("validation failed: %w", err) // ✓ 好 + // return fmt.Errorf("%w: validation failed", err) // ✗ 不好 + } + return nil +} +``` + + +## 8. 練習題 + +### 初級 + +1. 建立返回錯誤的函式: + - `divide(a, b int) (int, error)` - 檢查除零 + - `sqrt(x float64) (float64, error)` - 檢查負數 + - 適當處理錯誤 + +2. 建立自訂錯誤型別: + - `InvalidInputError` 帶有欄位和訊息 + - 在驗證函式中使用它 + - 演示錯誤型別斷言 + +### 中級 + +3. 建構錯誤包裝: + - 建立呼叫其他函式的函式 + - 使用上下文包裝錯誤 + - 使用 `errors.Is` 和 `errors.As` 檢查錯誤 + +4. 建立檔案處理器: + - 使用適當的錯誤處理讀取檔案 + - 使用 `defer` 進行清理 + - 處理多種錯誤型別 + - 使用上下文包裝錯誤 + +### 高級 + +5. 實作重試邏輯: + - 在特定錯誤時重試操作 + - 使用 `errors.Is` 檢查可重試錯誤 + - 實作指數退避 + - 處理上下文取消 + +## 9. 關鍵要點 + +- **錯誤作為值**: Go 返回錯誤作為值,不拋出例外 +- **始終檢查錯誤**: 永遠不要忽略返回的錯誤 +- **錯誤包裝**: 使用 `%w` 用上下文包裝錯誤 +- **自訂錯誤**: 為自訂錯誤型別實作 `Error()` 方法 +- **errors.Is**: 檢查錯誤是否包裝了特定錯誤 +- **errors.As**: 檢查錯誤是否匹配特定型別 +- **謹慎使用 Panic**: 僅對真正的異常情況使用 panic +- **Defer 清理**: 使用 `defer` 進行清理,比 `finally` 更好 + +## 10. 下一步 + +在接下來的模組中,我們將探索: +- **模組 06**: Goroutines vs Java 執行緒 +- **模組 07**: 用於通訊的通道 +- **模組 08**: 並行模式和最佳實踐 + +繼續學習 [模組 06: Goroutines 和執行緒](/docs/java2go/module-06-goroutines-threads),了解 Go 的輕量級執行緒! diff --git a/content/docs/java2go/module-06-goroutines-threads.mdx b/content/docs/java2go/module-06-goroutines-threads.mdx new file mode 100644 index 0000000..c662bdf --- /dev/null +++ b/content/docs/java2go/module-06-goroutines-threads.mdx @@ -0,0 +1,1216 @@ +--- +title: "Module 06: Goroutines vs Threads - Lightweight Concurrency" +description: "Master Go's goroutines and compare them with Java threads. Learn about lightweight concurrency, goroutine lifecycle, WaitGroups, and performance characteristics." +--- + +# Module 06: Goroutines vs Threads - Lightweight Concurrency + +In this module, you'll learn how Go handles **concurrency through goroutines**, comparing them with Java's threading model. Go's goroutines are lightweight, cheap to create, and make concurrent programming significantly easier and more efficient. + +## Learning Objectives + +By the end of this module, you'll be able to: +- Create and manage goroutines +- Compare goroutines with Java threads +- Use WaitGroups for synchronization +- Understand goroutine scheduling and lifecycle +- Analyze memory footprint differences +- Implement common goroutine patterns +- Build concurrent programs efficiently + +## Background: Java Threads vs Go Goroutines + +### The Fundamental Difference + +**Java Threads:** +- Heavyweight - each thread maps to an OS thread +- Expensive to create (~1MB stack per thread) +- Limited by OS resources (thousands at most) +- Context switching is expensive (kernel-level) +- Complex synchronization (synchronized, Lock, Condition) + +**Go Goroutines:** +- Lightweight - managed by Go runtime +- Extremely cheap to create (~2KB stack initially) +- Can spawn millions of goroutines +- Context switching is cheap (user-level) +- Simple synchronization through channels + + +```java !! java +// Java: Creating threads is heavy +public class ThreadExample { + public static void main(String[] args) { + // Create and start a thread + Thread thread = new Thread(() -> { + System.out.println("Hello from thread!"); + }); + thread.start(); + + // Wait for thread to finish + try { + thread.join(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + System.out.println("Main thread done"); + } +} +``` + +```go !! go +// Go: Goroutines are lightweight +package main + +import ( + "fmt" +) + +func main() { + // Spawn a goroutine + go func() { + fmt.Println("Hello from goroutine!") + }() + + // Wait a bit (not ideal - we'll learn better ways) + fmt.Println("Main goroutine done") +} +``` + + +## Creating Goroutines + +### Basic Goroutine Creation + + +```java !! java +// Java: Multiple threads for concurrent tasks +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class MultiThread { + public static void main(String[] args) { + // Using thread pool (better than creating threads directly) + ExecutorService executor = Executors.newFixedThreadPool(5); + + for (int i = 0; i < 5; i++) { + final int taskId = i; + executor.submit(() -> { + System.out.println("Task " + taskId + + " running on " + Thread.currentThread().getName()); + }); + } + + executor.shutdown(); + } +} +``` + +```go !! go +// Go: Spawning goroutines +package main + +import ( + "fmt" + "time" +) + +func task(id int) { + fmt.Printf("Task %d running\n", id) +} + +func main() { + // Spawn 5 goroutines + for i := 0; i < 5; i++ { + go task(i) + } + + // Wait for goroutines to finish + time.Sleep(time.Millisecond) +} +``` + + +### Anonymous Goroutines + + +```java !! java +// Java: Anonymous Runnable and lambda +public class AnonymousThread { + public static void main(String[] args) { + // Anonymous class + Thread t1 = new Thread(new Runnable() { + @Override + public void run() { + System.out.println("Anonymous class"); + } + }); + + // Lambda (preferred) + Thread t2 = new Thread(() -> { + System.out.println("Lambda expression"); + }); + + t1.start(); + t2.start(); + } +} +``` + +```go !! go +// Go: Anonymous goroutines with closures +package main + +import ( + "fmt" +) + +func main() { + // Anonymous goroutine + go func() { + fmt.Println("Anonymous goroutine") + }() + + // Goroutine with parameters + go func(msg string) { + fmt.Printf("Message: %s\n", msg) + }("Hello from closure") + + // Wait for goroutines + time.Sleep(100 * time.Millisecond) +} +``` + + +## WaitGroups vs CountDownLatch + +### Synchronization Primitives + + +```java !! java +// Java: CountDownLatch to wait for multiple threads +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class LatchExample { + public static void main(String[] args) throws InterruptedException { + final int TASK_COUNT = 3; + CountDownLatch latch = new CountDownLatch(TASK_COUNT); + ExecutorService executor = Executors.newFixedThreadPool(TASK_COUNT); + + for (int i = 0; i < TASK_COUNT; i++) { + final int taskId = i; + executor.submit(() -> { + try { + Thread.sleep(1000); + System.out.println("Task " + taskId + " completed"); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } finally { + latch.countDown(); // Signal completion + } + }); + } + + latch.await(); // Wait for all tasks + System.out.println("All tasks completed"); + executor.shutdown(); + } +} +``` + +```go !! go +// Go: WaitGroup for synchronization +package main + +import ( + "fmt" + "sync" + "time" +) + +func worker(id int, wg *sync.WaitGroup) { + defer wg.Done() // Signal completion when done + + fmt.Printf("Worker %d starting\n", id) + time.Sleep(time.Second) + fmt.Printf("Worker %d done\n", id) +} + +func main() { + var wg sync.WaitGroup + + // Spawn 3 workers + for i := 1; i <= 3; i++ { + wg.Add(1) // Increment counter + go worker(i, &wg) + } + + wg.Wait() // Wait for all workers + fmt.Println("All workers completed") +} +``` + + +### WaitGroup Best Practices + + +```java !! java +// Java: More complex synchronization scenarios +import java.util.concurrent.*; +import java.util.*; + +public class ComplexSync { + static class Result { + List results = new ArrayList<>(); + } + + public static void main(String[] args) throws Exception { + int taskCount = 5; + CountDownLatch latch = new CountDownLatch(taskCount); + Result result = new Result(); + + ExecutorService executor = Executors.newFixedThreadPool(10); + + for (int i = 0; i < taskCount; i++) { + final int taskId = i; + executor.submit(() -> { + try { + String data = "Result " + taskId; + synchronized (result.results) { + result.results.add(data); + } + } finally { + latch.countDown(); + } + }); + } + + latch.await(); + System.out.println("Collected: " + result.results); + executor.shutdown(); + } +} +``` + +```go !! go +// Go: Cleaner WaitGroup patterns +package main + +import ( + "fmt" + "sync" +) + +func collectResults(id int, wg *sync.WaitGroup, results *[]string) { + defer wg.Done() + + data := fmt.Sprintf("Result %d", id) + *results = append(*results, data) +} + +func main() { + var wg sync.WaitGroup + var results []string + var mu sync.Mutex // Protect shared slice + + for i := 0; i < 5; i++ { + wg.Add(1) + go func(id int) { + defer wg.Done() + + data := fmt.Sprintf("Result %d", id) + + mu.Lock() + results = append(results, data) + mu.Unlock() + }(i) + } + + wg.Wait() + fmt.Println("Collected:", results) +} +``` + + +## Goroutine Lifecycle and Scheduling + +### Understanding Goroutine States + + +```java !! java +// Java: Thread states (NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED) +public class ThreadStates { + public static void main(String[] args) throws Exception { + Thread thread = new Thread(() -> { + try { + // RUNNABLE + System.out.println("Thread running"); + + // TIMED_WAITING + Thread.sleep(1000); + + // WAITING + synchronized (ThreadStates.class) { + ThreadStates.class.wait(); + } + } catch (InterruptedException e) { + // TERMINATED + } + }); + + // NEW state + System.out.println("State: " + thread.getState()); + + thread.start(); // RUNNABLE + + Thread.sleep(100); + System.out.println("State: " + thread.getState()); + } +} +``` + +```go !! go +// Go: Goroutine states (simpler model) +package main + +import ( + "fmt" + "runtime" + "time" +) + +func goroutineStates() { + // Goroutine is executing + fmt.Println("Goroutine running") + + // Goroutine is blocked (waiting) + time.Sleep(time.Second) + + // Goroutine completes and is garbage collected + fmt.Println("Goroutine done") +} + +func main() { + // Before goroutine: no goroutine yet + fmt.Printf("Goroutines: %d\n", runtime.NumGoroutine()) + + go goroutineStates() + + time.Sleep(100 * time.Millisecond) + fmt.Printf("Goroutines: %d\n", runtime.NumGoroutine()) + + time.Sleep(2 * time.Second) + fmt.Printf("Goroutines: %d\n", runtime.NumGoroutine()) +} +``` + + +### Goroutine Scheduler (M:N Scheduler) + + +```java !! java +// Java: 1:1 threading model (one Java thread = one OS thread) +// Managed by OS scheduler +public class JavaScheduler { + public static void main(String[] args) { + // Each thread creates an OS thread + for (int i = 0; i < 10; i++) { + new Thread(() -> { + // This maps 1:1 to an OS thread + Thread.currentThread().setName("Worker"); + System.out.println(Thread.currentThread().getName()); + }).start(); + } + } +} + +/* OS manages scheduling - expensive context switches */ +``` + +```go !! go +// Go: M:N scheduler (M goroutines : N OS threads) +// Go runtime manages scheduling +package main + +import ( + "fmt" + "runtime" + "time" +) + +func main() { + // Set number of OS threads (GOMAXPROCS) + fmt.Printf("GOMAXPROCS: %d\n", runtime.GOMAXPROCS(0)) + + // Can spawn thousands of goroutines + for i := 0; i < 1000; i++ { + go func(id int) { + time.Sleep(100 * time.Millisecond) + fmt.Printf("Goroutine %d\n", id) + }(i) + } + + // Go runtime multiplexes goroutines onto fewer OS threads + time.Sleep(time.Second) + fmt.Printf("Total goroutines created: 1000\n") +} +``` + + +## Memory Footprint Comparison + +### Stack Size and Memory Usage + + +```java !! java +// Java: Threads have large stack sizes +public class ThreadMemory { + public static void main(String[] args) { + // Default stack size: ~1MB per thread + // 1000 threads = ~1GB memory! + + Runtime runtime = Runtime.getRuntime(); + long before = runtime.totalMemory() - runtime.freeMemory(); + + for (int i = 0; i < 100; i++) { + new Thread(() -> { + try { + Thread.sleep(60000); // Keep alive + } catch (InterruptedException e) {} + }).start(); + } + + long after = runtime.totalMemory() - runtime.freeMemory(); + System.out.println("Memory used: " + (after - before) / 1024 / 1024 + " MB"); + System.out.println("100 threads created"); + } +} +``` + +```go !! go +// Go: Goroutines start with tiny stacks (2KB) +package main + +import ( + "fmt" + "runtime" + "time" +) + +func main() { + var m runtime.MemStats + runtime.ReadMemStats(&m) + before := m.Alloc + + // Create 100,000 goroutines! + for i := 0; i < 100000; i++ { + go func() { + time.Sleep(time.Minute) + }() + } + + runtime.ReadMemStats(&m) + after := m.Alloc + + fmt.Printf("Memory used: %d MB\n", (after-before)/1024/1024) + fmt.Printf("100,000 goroutines created\n") +} +``` + + +### Practical Performance Comparison + + +```java !! java +// Java: Performance with thread pool +import java.util.concurrent.*; +import java.util.*; + +public class ThreadPerformance { + public static void main(String[] args) throws Exception { + int tasks = 10000; + + ExecutorService executor = Executors.newFixedThreadPool(100); + long start = System.currentTimeMillis(); + + List> futures = new ArrayList<>(); + + for (int i = 0; i < tasks; i++) { + final int taskId = i; + futures.add(executor.submit(() -> { + int sum = 0; + for (int j = 0; j < 100; j++) { + sum += j; + } + return sum; + })); + } + + int total = 0; + for (Future future : futures) { + total += future.get(); + } + + long end = System.currentTimeMillis(); + System.out.println("Time: " + (end - start) + "ms"); + System.out.println("Total: " + total); + + executor.shutdown(); + } +} +``` + +```go !! go +// Go: Performance with goroutines +package main + +import ( + "fmt" + "sync" + "time" +) + +func task() int { + sum := 0 + for i := 0; i < 100; i++ { + sum += i + } + return sum +} + +func main() { + tasks := 10000 + + start := time.Now() + var wg sync.WaitGroup + results := make(chan int, tasks) + + for i := 0; i < tasks; i++ { + wg.Add(1) + go func() { + defer wg.Done() + results <- task() + }() + } + + go func() { + wg.Wait() + close(results) + }() + + total := 0 + for result := range results { + total += result + } + + elapsed := time.Since(start) + fmt.Printf("Time: %dms\n", elapsed.Milliseconds()) + fmt.Printf("Total: %d\n", total) +} +``` + + +## Common Goroutine Patterns + +### Worker Pool Pattern + + +```java !! java +// Java: Worker pool with ExecutorService +import java.util.concurrent.*; +import java.util.*; + +public class WorkerPool { + static class Job { + int id; + String data; + + Job(int id, String data) { + this.id = id; + this.data = data; + } + } + + public static void main(String[] args) throws Exception { + int numWorkers = 5; + ExecutorService executor = Executors.newFixedThreadPool(numWorkers); + + List jobs = new ArrayList<>(); + for (int i = 0; i < 20; i++) { + jobs.add(new Job(i, "Task " + i)); + } + + for (Job job : jobs) { + executor.submit(() -> { + System.out.println("Processing " + job.id); + try { + Thread.sleep(100); + } catch (InterruptedException e) {} + System.out.println("Completed " + job.id); + }); + } + + executor.shutdown(); + executor.awaitTermination(1, TimeUnit.MINUTES); + } +} +``` + +```go !! go +// Go: Worker pool with buffered channel +package main + +import ( + "fmt" + "sync" + "time" +) + +type Job struct { + ID int + Data string +} + +func worker(id int, jobs <-chan Job, wg *sync.WaitGroup) { + defer wg.Done() + + for job := range jobs { + fmt.Printf("Worker %d processing job %d\n", id, job.ID) + time.Sleep(100 * time.Millisecond) + fmt.Printf("Worker %d completed job %d\n", id, job.ID) + } +} + +func main() { + numWorkers := 5 + numJobs := 20 + + jobs := make(chan Job, numJobs) + var wg sync.WaitGroup + + // Start workers + for i := 1; i <= numWorkers; i++ { + wg.Add(1) + go worker(i, jobs, &wg) + } + + // Send jobs + for i := 0; i < numJobs; i++ { + jobs <- Job{ID: i, Data: fmt.Sprintf("Task %d", i)} + } + close(jobs) + + // Wait for all workers + wg.Wait() + fmt.Println("All jobs completed") +} +``` + + +### Fan-Out / Fan-In Pattern + + +```java !! java +// Java: Fan-out with multiple consumers +import java.util.concurrent.*; +import java.util.*; + +public class FanOutFanIn { + public static void main(String[] args) throws Exception { + ExecutorService executor = Executors.newFixedThreadPool(10); + + List> futures = new ArrayList<>(); + + // Fan-out: distribute work + for (int i = 0; i < 10; i++) { + final int input = i; + futures.add(executor.submit(() -> { + return process(input); + })); + } + + // Fan-in: collect results + int sum = 0; + for (Future future : futures) { + sum += future.get(); + } + + System.out.println("Sum: " + sum); + executor.shutdown(); + } + + private static int process(int n) { + return n * n; + } +} +``` + +```go !! go +// Go: Elegant fan-out/fan-in with channels +package main + +import ( + "fmt" + "sync" +) + +func process(n int) int { + return n * n +} + +func fanOut(inputs <-chan int) <-chan int { + results := make(chan int) + + var wg sync.WaitGroup + for i := 0; i < 3; i++ { // 3 workers + wg.Add(1) + go func() { + defer wg.Done() + for n := range inputs { + results <- process(n) + } + }() + } + + go func() { + wg.Wait() + close(results) + }() + + return results +} + +func main() { + inputs := make(chan int) + + // Fan-out + results := fanOut(inputs) + + // Send inputs + go func() { + for i := 0; i < 10; i++ { + inputs <- i + } + close(inputs) + }() + + // Fan-in: collect results + sum := 0 + for result := range results { + sum += result + } + + fmt.Println("Sum:", sum) +} +``` + + +### Pipeline Pattern + + +```java !! java +// Java: Pipeline with CompletableFuture +import java.util.concurrent.*; +import java.util.stream.*; + +public class Pipeline { + public static void main(String[] args) throws Exception { + CompletableFuture.supplyAsync(() -> { + // Stage 1: Generate data + return IntStream.range(0, 10).boxed().collect(Collectors.toList()); + }).thenComposeAsync(data -> { + // Stage 2: Transform data + List> futures = data.stream() + .map(n -> CompletableFuture.supplyAsync(() -> n * 2)) + .collect(Collectors.toList()); + + return CompletableFuture.allOf( + futures.toArray(new CompletableFuture[0]) + ).thenApply(v -> futures.stream() + .map(CompletableFuture::join) + .collect(Collectors.toList()) + ); + }).thenAcceptAsync(results -> { + // Stage 3: Consume results + results.forEach(System.out::println); + }).get(); + } +} +``` + +```go !! go +// Go: Pipeline with channels +package main + +import ( + "fmt" +) + +func generate(nums ...int) <-chan int { + out := make(chan int) + go func() { + for _, n := range nums { + out <- n + } + close(out) + }() + return out +} + +func square(in <-chan int) <-chan int { + out := make(chan int) + go func() { + for n := range in { + out <- n * n + } + close(out) + }() + return out +} + +func main() { + // Set up pipeline + numbers := generate(0, 1, 2, 3, 4, 5, 6, 7, 8, 9) + squares := square(numbers) + + // Consume results + for result := range squares { + fmt.Println(result) + } +} +``` + + +## Goroutine Gotchas and Best Practices + +### Common Pitfalls + + +```java !! java +// Java: Common thread pitfalls +public class Pitfalls { + // Pitfall 1: Not handling exceptions in threads + public static void pitfall1() { + new Thread(() -> { + throw new RuntimeException("Unhandled!"); // Thread dies silently + }).start(); + } + + // Pitfall 2: Shared mutable state without synchronization + static int counter = 0; + public static void pitfall2() throws Exception { + for (int i = 0; i < 1000; i++) { + new Thread(() -> counter++).start(); // Race condition! + } + Thread.sleep(1000); + System.out.println("Counter: " + counter); // Unpredictable! + } + + // Pitfall 3: Thread leakage + public static void pitfall3() { + ExecutorService executor = Executors.newCachedThreadPool(); + for (int i = 0; i < 100000; i++) { + executor.submit(() -> { + try { + Thread.sleep(1000); + } catch (InterruptedException e) {} + }); + } + // Forgot to shutdown - threads never terminate! + } +} +``` + +```go !! go +// Go: Common goroutine pitfalls +package main + +import ( + "fmt" + "sync" + "time" +) + +// Pitfall 1: Not waiting for goroutines +func pitfall1() { + go func() { + fmt.Println("Goroutine running") + }() + // Main exits immediately, goroutine never completes +} + +// Pitfall 2: Shared mutable state without synchronization +func pitfall2() { + var counter int + var wg sync.WaitGroup + + for i := 0; i < 1000; i++ { + wg.Add(1) + go func() { + defer wg.Done() + counter++ // Race condition! + }() + } + + wg.Wait() + fmt.Println("Counter:", counter) // Unpredictable! +} + +// Pitfall 3: Goroutine leakage +func pitfall3() { + for i := 0; i < 100000; i++ { + go func() { + time.Sleep(time.Hour) // Goroutine never exits + }() + } + // Goroutines pile up, memory leak! +} + +func main() { + fmt.Println("Demonstrating pitfall 2:") + pitfall2() +} +``` + + +### Best Practices + + +```java !! java +// Java: Thread best practices +import java.util.concurrent.*; + +public class BestPractices { + + // Good: Use thread pools + public static void good1() { + ExecutorService executor = Executors.newFixedThreadPool(10); + + for (int i = 0; i < 100; i++) { + final int taskId = i; + executor.submit(() -> { + System.out.println("Task " + taskId); + }); + } + + executor.shutdown(); + try { + executor.awaitTermination(1, TimeUnit.MINUTES); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + + // Good: Proper synchronization + static class SafeCounter { + private int count = 0; + + public synchronized void increment() { + count++; + } + + public synchronized int get() { + return count; + } + } + + // Good: Handle exceptions + public static void good3() { + Thread thread = new Thread(() -> { + try { + // Work that might throw + } catch (Exception e) { + e.printStackTrace(); // Handle exception + } + }); + thread.setUncaughtExceptionHandler((t, e) -> { + System.err.println("Exception in thread " + t.getName()); + }); + thread.start(); + } +} +``` + +```go !! go +// Go: Goroutine best practices +package main + +import ( + "fmt" + "sync" + "time" +) + +// Good: Always manage goroutine lifecycle +func good1() { + var wg sync.WaitGroup + + for i := 0; i < 5; i++ { + wg.Add(1) + go func(id int) { + defer wg.Done() + fmt.Printf("Worker %d\n", id) + }(i) + } + + wg.Wait() +} + +// Good: Proper synchronization +type SafeCounter struct { + mu sync.Mutex + count int +} + +func (c *SafeCounter) Increment() { + c.mu.Lock() + defer c.mu.Unlock() + c.count++ +} + +func (c *SafeCounter) Get() int { + c.mu.Lock() + defer c.mu.Unlock() + return c.count +} + +// Good: Handle panics in goroutines +func good3() { + go func() { + defer func() { + if r := recover(); r != nil { + fmt.Printf("Recovered: %v\n", r) + } + }() + + // Work that might panic + panic("Oops!") + }() + + time.Sleep(time.Second) +} + +func main() { + fmt.Println("Example 1: Proper lifecycle management") + good1() + + fmt.Println("\nExample 2: Safe counter") + counter := &SafeCounter{} + var wg sync.WaitGroup + + for i := 0; i < 100; i++ { + wg.Add(1) + go func() { + defer wg.Done() + counter.Increment() + }() + } + + wg.Wait() + fmt.Printf("Counter: %d\n", counter.Get()) +} +``` + + +## Performance Benchmarks + + +```java !! java +// Java: Benchmark thread creation +public class ThreadBenchmark { + public static void main(String[] args) throws Exception { + int iterations = 10000; + + long start = System.currentTimeMillis(); + + for (int i = 0; i < iterations; i++) { + Thread t = new Thread(() -> {}); + t.start(); + t.join(); + } + + long end = System.currentTimeMillis(); + System.out.println("Created and joined " + iterations + + " threads in " + (end - start) + "ms"); + } +} +``` + +```go !! go +// Go: Benchmark goroutine creation +package main + +import ( + "fmt" + "sync" + "time" +) + +func main() { + iterations := 10000 + + start := time.Now() + + var wg sync.WaitGroup + for i := 0; i < iterations; i++ { + wg.Add(1) + go func() { + wg.Done() + }() + } + + wg.Wait() + + elapsed := time.Since(start) + fmt.Printf("Created and waited for %d goroutines in %dms\n", + iterations, elapsed.Milliseconds()) +} +``` + + +## Practice Questions + +1. **Memory Efficiency**: Why can Go spawn millions of goroutines while Java can only spawn thousands of threads? + +2. **Synchronization**: How does `sync.WaitGroup` compare to `CountDownLatch`? When would you use each? + +3. **Scheduling**: Explain the M:N scheduler in Go and how it differs from Java's 1:1 thread model. + +4. **Best Practices**: What are the common pitfalls when working with goroutines, and how can you avoid them? + +5. **Performance**: In what scenarios would goroutines significantly outperform Java threads? + +## Project Ideas + +1. **Concurrent Web Scraper**: Build a web scraper that fetches multiple URLs concurrently using goroutines + +2. **Parallel Data Processing**: Create a program that processes large CSV files in parallel chunks + +3. **Worker Pool System**: Implement a robust worker pool with job queuing, result collection, and error handling + +4. **Real-time Data Pipeline**: Build a pipeline that ingests, transforms, and outputs data using the pipeline pattern + +5. **Concurrent Cache**: Implement a thread-safe in-memory cache with expiration and concurrent access + +## Next Steps + +Now that you understand goroutines and concurrency: + +- **Next Module**: Learn about **Channels and Select** for communication between goroutines +- **Go Deeper**: Study Go's memory model and synchronization patterns +- **Practice**: Build concurrent applications to get comfortable with goroutines +- **Compare**: Analyze how goroutines compare to other concurrency models (async/await, actors) + +## Summary + +**Goroutines vs Java Threads:** +- Goroutines are lightweight (~2KB) vs Threads (~1MB) +- Go uses M:N scheduling for efficiency +- `sync.WaitGroup` simplifies synchronization +- Goroutines enable massive concurrency +- Channels (next module) provide safe communication + +**Key Takeaway**: Go's goroutines make concurrent programming significantly more efficient and approachable compared to Java's threading model. You can spawn millions of goroutines with minimal overhead, enabling new patterns of concurrent programming that would be impractical with Java threads. diff --git a/content/docs/java2go/module-06-goroutines-threads.zh-cn.mdx b/content/docs/java2go/module-06-goroutines-threads.zh-cn.mdx new file mode 100644 index 0000000..ff57f47 --- /dev/null +++ b/content/docs/java2go/module-06-goroutines-threads.zh-cn.mdx @@ -0,0 +1,1216 @@ +--- +title: "模块 06:Goroutine vs 线程 - 轻量级并发" +description: "掌握 Go 的 goroutine 并与 Java 线程进行比较。了解轻量级并发、goroutine 生命周期、WaitGroup 和性能特性。" +--- + +# 模块 06:Goroutine vs 线程 - 轻量级并发 + +在本模块中,你将学习 Go 如何通过 **goroutine 处理并发**,并将其与 Java 的线程模型进行比较。Go 的 goroutine 是轻量级的,创建成本极低,使并发编程更加简单和高效。 + +## 学习目标 + +完成本模块后,你将能够: +- 创建和管理 goroutine +- 比较 goroutine 和 Java 线程 +- 使用 WaitGroup 进行同步 +- 理解 goroutine 调度和生命周期 +- 分析内存占用的差异 +- 实现常见的 goroutine 模式 +- 高效构建并发程序 + +## 背景:Java 线程 vs Go Goroutine + +### 根本区别 + +**Java 线程:** +- 重量级 - 每个线程映射到一个操作系统线程 +- 创建成本高(每个线程约 1MB 栈空间) +- 受操作系统资源限制(最多数千个) +- 上下文切换成本高(内核级) +- 复杂的同步机制(synchronized、Lock、Condition) + +**Go Goroutine:** +- 轻量级 - 由 Go 运行时管理 +- 创建极其廉价(初始栈约 2KB) +- 可以创建数百万个 goroutine +- 上下文切换成本低(用户级) +- 通过 channel 实现简单的同步 + + +```java !! java +// Java:创建线程是重量级的 +public class ThreadExample { + public static void main(String[] args) { + // 创建并启动线程 + Thread thread = new Thread(() -> { + System.out.println("Hello from thread!"); + }); + thread.start(); + + // 等待线程完成 + try { + thread.join(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + System.out.println("Main thread done"); + } +} +``` + +```go !! go +// Go:Goroutine 是轻量级的 +package main + +import ( + "fmt" +) + +func main() { + // 启动 goroutine + go func() { + fmt.Println("Hello from goroutine!") + }() + + // 等待一下(不是理想方式 - 我们会学习更好的方法) + fmt.Println("Main goroutine done") +} +``` + + +## 创建 Goroutine + +### 基本 Goroutine 创建 + + +```java !! java +// Java:使用线程池进行并发任务 +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class MultiThread { + public static void main(String[] args) { + // 使用线程池(比直接创建线程更好) + ExecutorService executor = Executors.newFixedThreadPool(5); + + for (int i = 0; i < 5; i++) { + final int taskId = i; + executor.submit(() -> { + System.out.println("Task " + taskId + + " running on " + Thread.currentThread().getName()); + }); + } + + executor.shutdown(); + } +} +``` + +```go !! go +// Go:启动 goroutine +package main + +import ( + "fmt" + "time" +) + +func task(id int) { + fmt.Printf("Task %d running\n", id) +} + +func main() { + // 启动 5 个 goroutine + for i := 0; i < 5; i++ { + go task(i) + } + + // 等待 goroutine 完成 + time.Sleep(time.Millisecond) +} +``` + + +### 匿名 Goroutine + + +```java !! java +// Java:匿名 Runnable 和 lambda +public class AnonymousThread { + public static void main(String[] args) { + // 匿名类 + Thread t1 = new Thread(new Runnable() { + @Override + public void run() { + System.out.println("Anonymous class"); + } + }); + + // Lambda(首选) + Thread t2 = new Thread(() -> { + System.out.println("Lambda expression"); + }); + + t1.start(); + t2.start(); + } +} +``` + +```go !! go +// Go:带闭包的匿名 goroutine +package main + +import ( + "fmt" +) + +func main() { + // 匿名 goroutine + go func() { + fmt.Println("Anonymous goroutine") + }() + + // 带参数的 goroutine + go func(msg string) { + fmt.Printf("Message: %s\n", msg) + }("Hello from closure") + + // 等待 goroutine + time.Sleep(100 * time.Millisecond) +} +``` + + +## WaitGroup vs CountDownLatch + +### 同步原语 + + +```java !! java +// Java:使用 CountDownLatch 等待多个线程 +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class LatchExample { + public static void main(String[] args) throws InterruptedException { + final int TASK_COUNT = 3; + CountDownLatch latch = new CountDownLatch(TASK_COUNT); + ExecutorService executor = Executors.newFixedThreadPool(TASK_COUNT); + + for (int i = 0; i < TASK_COUNT; i++) { + final int taskId = i; + executor.submit(() -> { + try { + Thread.sleep(1000); + System.out.println("Task " + taskId + " completed"); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } finally { + latch.countDown(); // 发出完成信号 + } + }); + } + + latch.await(); // 等待所有任务 + System.out.println("All tasks completed"); + executor.shutdown(); + } +} +``` + +```go !! go +// Go:使用 WaitGroup 同步 +package main + +import ( + "fmt" + "sync" + "time" +) + +func worker(id int, wg *sync.WaitGroup) { + defer wg.Done() // 完成时发出信号 + + fmt.Printf("Worker %d starting\n", id) + time.Sleep(time.Second) + fmt.Printf("Worker %d done\n", id) +} + +func main() { + var wg sync.WaitGroup + + // 启动 3 个 worker + for i := 1; i <= 3; i++ { + wg.Add(1) // 增加计数器 + go worker(i, &wg) + } + + wg.Wait() // 等待所有 worker + fmt.Println("All workers completed") +} +``` + + +### WaitGroup 最佳实践 + + +```java !! java +// Java:更复杂的同步场景 +import java.util.concurrent.*; +import java.util.*; + +public class ComplexSync { + static class Result { + List results = new ArrayList<>(); + } + + public static void main(String[] args) throws Exception { + int taskCount = 5; + CountDownLatch latch = new CountDownLatch(taskCount); + Result result = new Result(); + + ExecutorService executor = Executors.newFixedThreadPool(10); + + for (int i = 0; i < taskCount; i++) { + final int taskId = i; + executor.submit(() -> { + try { + String data = "Result " + taskId; + synchronized (result.results) { + result.results.add(data); + } + } finally { + latch.countDown(); + } + }); + } + + latch.await(); + System.out.println("Collected: " + result.results); + executor.shutdown(); + } +} +``` + +```go !! go +// Go:更简洁的 WaitGroup 模式 +package main + +import ( + "fmt" + "sync" +) + +func collectResults(id int, wg *sync.WaitGroup, results *[]string) { + defer wg.Done() + + data := fmt.Sprintf("Result %d", id) + *results = append(*results, data) +} + +func main() { + var wg sync.WaitGroup + var results []string + var mu sync.Mutex // 保护共享的 slice + + for i := 0; i < 5; i++ { + wg.Add(1) + go func(id int) { + defer wg.Done() + + data := fmt.Sprintf("Result %d", id) + + mu.Lock() + results = append(results, data) + mu.Unlock() + }(i) + } + + wg.Wait() + fmt.Println("Collected:", results) +} +``` + + +## Goroutine 生命周期和调度 + +### 理解 Goroutine 状态 + + +```java !! java +// Java:线程状态(NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED) +public class ThreadStates { + public static void main(String[] args) throws Exception { + Thread thread = new Thread(() -> { + try { + // RUNNABLE + System.out.println("Thread running"); + + // TIMED_WAITING + Thread.sleep(1000); + + // WAITING + synchronized (ThreadStates.class) { + ThreadStates.class.wait(); + } + } catch (InterruptedException e) { + // TERMINATED + } + }); + + // NEW 状态 + System.out.println("State: " + thread.getState()); + + thread.start(); // RUNNABLE + + Thread.sleep(100); + System.out.println("State: " + thread.getState()); + } +} +``` + +```go !! go +// Go:Goroutine 状态(更简单的模型) +package main + +import ( + "fmt" + "runtime" + "time" +) + +func goroutineStates() { + // Goroutine 正在执行 + fmt.Println("Goroutine running") + + // Goroutine 被阻塞(等待) + time.Sleep(time.Second) + + // Goroutine 完成并被垃圾回收 + fmt.Println("Goroutine done") +} + +func main() { + // goroutine 创建前:还没有 goroutine + fmt.Printf("Goroutines: %d\n", runtime.NumGoroutine()) + + go goroutineStates() + + time.Sleep(100 * time.Millisecond) + fmt.Printf("Goroutines: %d\n", runtime.NumGoroutine()) + + time.Sleep(2 * time.Second) + fmt.Printf("Goroutines: %d\n", runtime.NumGoroutine()) +} +``` + + +### Goroutine 调度器(M:N 调度器) + + +```java !! java +// Java:1:1 线程模型(一个 Java 线程 = 一个 OS 线程) +// 由 OS 调度器管理 +public class JavaScheduler { + public static void main(String[] args) { + // 每个线程创建一个 OS 线程 + for (int i = 0; i < 10; i++) { + new Thread(() -> { + // 这是 1:1 映射到 OS 线程 + Thread.currentThread().setName("Worker"); + System.out.println(Thread.currentThread().getName()); + }).start(); + } + } +} + +/* OS 管理调度 - 昂贵的上下文切换 */ +``` + +```go !! go +// Go:M:N 调度器(M 个 goroutine : N 个 OS 线程) +// Go 运行时管理调度 +package main + +import ( + "fmt" + "runtime" + "time" +) + +func main() { + // 设置 OS 线程数(GOMAXPROCS) + fmt.Printf("GOMAXPROCS: %d\n", runtime.GOMAXPROCS(0)) + + // 可以启动数千个 goroutine + for i := 0; i < 1000; i++ { + go func(id int) { + time.Sleep(100 * time.Millisecond) + fmt.Printf("Goroutine %d\n", id) + }(i) + } + + // Go 运行时将 goroutine 多路复用到更少的 OS 线程上 + time.Sleep(time.Second) + fmt.Printf("Total goroutines created: 1000\n") +} +``` + + +## 内存占用比较 + +### 栈大小和内存使用 + + +```java !! java +// Java:线程有很大的栈空间 +public class ThreadMemory { + public static void main(String[] args) { + // 默认栈大小:每个线程约 1MB + // 1000 个线程 = 约 1GB 内存! + + Runtime runtime = Runtime.getRuntime(); + long before = runtime.totalMemory() - runtime.freeMemory(); + + for (int i = 0; i < 100; i++) { + new Thread(() -> { + try { + Thread.sleep(60000); // 保持活动 + } catch (InterruptedException e) {} + }).start(); + } + + long after = runtime.totalMemory() - runtime.freeMemory(); + System.out.println("Memory used: " + (after - before) / 1024 / 1024 + " MB"); + System.out.println("100 threads created"); + } +} +``` + +```go !! go +// Go:Goroutine 开始时栈很小(2KB) +package main + +import ( + "fmt" + "runtime" + "time" +) + +func main() { + var m runtime.MemStats + runtime.ReadMemStats(&m) + before := m.Alloc + + // 创建 100,000 个 goroutine! + for i := 0; i < 100000; i++ { + go func() { + time.Sleep(time.Minute) + }() + } + + runtime.ReadMemStats(&m) + after := m.Alloc + + fmt.Printf("Memory used: %d MB\n", (after-before)/1024/1024) + fmt.Printf("100,000 goroutines created\n") +} +``` + + +### 实际性能比较 + + +```java !! java +// Java:线程池性能 +import java.util.concurrent.*; +import java.util.*; + +public class ThreadPerformance { + public static void main(String[] args) throws Exception { + int tasks = 10000; + + ExecutorService executor = Executors.newFixedThreadPool(100); + long start = System.currentTimeMillis(); + + List> futures = new ArrayList<>(); + + for (int i = 0; i < tasks; i++) { + final int taskId = i; + futures.add(executor.submit(() -> { + int sum = 0; + for (int j = 0; j < 100; j++) { + sum += j; + } + return sum; + })); + } + + int total = 0; + for (Future future : futures) { + total += future.get(); + } + + long end = System.currentTimeMillis(); + System.out.println("Time: " + (end - start) + "ms"); + System.out.println("Total: " + total); + + executor.shutdown(); + } +} +``` + +```go !! go +// Go:Goroutine 性能 +package main + +import ( + "fmt" + "sync" + "time" +) + +func task() int { + sum := 0 + for i := 0; i < 100; i++ { + sum += i + } + return sum +} + +func main() { + tasks := 10000 + + start := time.Now() + var wg sync.WaitGroup + results := make(chan int, tasks) + + for i := 0; i < tasks; i++ { + wg.Add(1) + go func() { + defer wg.Done() + results <- task() + }() + } + + go func() { + wg.Wait() + close(results) + }() + + total := 0 + for result := range results { + total += result + } + + elapsed := time.Since(start) + fmt.Printf("Time: %dms\n", elapsed.Milliseconds()) + fmt.Printf("Total: %d\n", total) +} +``` + + +## 常见 Goroutine 模式 + +### Worker Pool 模式 + + +```java !! java +// Java:使用 ExecutorService 的 worker pool +import java.util.concurrent.*; +import java.util.*; + +public class WorkerPool { + static class Job { + int id; + String data; + + Job(int id, String data) { + this.id = id; + this.data = data; + } + } + + public static void main(String[] args) throws Exception { + int numWorkers = 5; + ExecutorService executor = Executors.newFixedThreadPool(numWorkers); + + List jobs = new ArrayList<>(); + for (int i = 0; i < 20; i++) { + jobs.add(new Job(i, "Task " + i)); + } + + for (Job job : jobs) { + executor.submit(() -> { + System.out.println("Processing " + job.id); + try { + Thread.sleep(100); + } catch (InterruptedException e) {} + System.out.println("Completed " + job.id); + }); + } + + executor.shutdown(); + executor.awaitTermination(1, TimeUnit.MINUTES); + } +} +``` + +```go !! go +// Go:使用缓冲 channel 的 worker pool +package main + +import ( + "fmt" + "sync" + "time" +) + +type Job struct { + ID int + Data string +} + +func worker(id int, jobs <-chan Job, wg *sync.WaitGroup) { + defer wg.Done() + + for job := range jobs { + fmt.Printf("Worker %d processing job %d\n", id, job.ID) + time.Sleep(100 * time.Millisecond) + fmt.Printf("Worker %d completed job %d\n", id, job.ID) + } +} + +func main() { + numWorkers := 5 + numJobs := 20 + + jobs := make(chan Job, numJobs) + var wg sync.WaitGroup + + // 启动 workers + for i := 1; i <= numWorkers; i++ { + wg.Add(1) + go worker(i, jobs, &wg) + } + + // 发送任务 + for i := 0; i < numJobs; i++ { + jobs <- Job{ID: i, Data: fmt.Sprintf("Task %d", i)} + } + close(jobs) + + // 等待所有 worker + wg.Wait() + fmt.Println("All jobs completed") +} +``` + + +### Fan-Out / Fan-In 模式 + + +```java !! java +// Java:使用多个消费者的 fan-out +import java.util.concurrent.*; +import java.util.*; + +public class FanOutFanIn { + public static void main(String[] args) throws Exception { + ExecutorService executor = Executors.newFixedThreadPool(10); + + List> futures = new ArrayList<>(); + + // Fan-out:分发工作 + for (int i = 0; i < 10; i++) { + final int input = i; + futures.add(executor.submit(() -> { + return process(input); + })); + } + + // Fan-in:收集结果 + int sum = 0; + for (Future future : futures) { + sum += future.get(); + } + + System.out.println("Sum: " + sum); + executor.shutdown(); + } + + private static int process(int n) { + return n * n; + } +} +``` + +```go !! go +// Go:使用 channel 的优雅 fan-out/fan-in +package main + +import ( + "fmt" + "sync" +) + +func process(n int) int { + return n * n +} + +func fanOut(inputs <-chan int) <-chan int { + results := make(chan int) + + var wg sync.WaitGroup + for i := 0; i < 3; i++ { // 3 个 worker + wg.Add(1) + go func() { + defer wg.Done() + for n := range inputs { + results <- process(n) + } + }() + } + + go func() { + wg.Wait() + close(results) + }() + + return results +} + +func main() { + inputs := make(chan int) + + // Fan-out + results := fanOut(inputs) + + // 发送输入 + go func() { + for i := 0; i < 10; i++ { + inputs <- i + } + close(inputs) + }() + + // Fan-in:收集结果 + sum := 0 + for result := range results { + sum += result + } + + fmt.Println("Sum:", sum) +} +``` + + +### Pipeline 模式 + + +```java !! java +// Java:使用 CompletableFuture 的 pipeline +import java.util.concurrent.*; +import java.util.stream.*; + +public class Pipeline { + public static void main(String[] args) throws Exception { + CompletableFuture.supplyAsync(() -> { + // 阶段 1:生成数据 + return IntStream.range(0, 10).boxed().collect(Collectors.toList()); + }).thenComposeAsync(data -> { + // 阶段 2:转换数据 + List> futures = data.stream() + .map(n -> CompletableFuture.supplyAsync(() -> n * 2)) + .collect(Collectors.toList()); + + return CompletableFuture.allOf( + futures.toArray(new CompletableFuture[0]) + ).thenApply(v -> futures.stream() + .map(CompletableFuture::join) + .collect(Collectors.toList()) + ); + }).thenAcceptAsync(results -> { + // 阶段 3:消费结果 + results.forEach(System.out::println); + }).get(); + } +} +``` + +```go !! go +// Go:使用 channel 的 pipeline +package main + +import ( + "fmt" +) + +func generate(nums ...int) <-chan int { + out := make(chan int) + go func() { + for _, n := range nums { + out <- n + } + close(out) + }() + return out +} + +func square(in <-chan int) <-chan int { + out := make(chan int) + go func() { + for n := range in { + out <- n * n + } + close(out) + }() + return out +} + +func main() { + // 设置 pipeline + numbers := generate(0, 1, 2, 3, 4, 5, 6, 7, 8, 9) + squares := square(numbers) + + // 消费结果 + for result := range squares { + fmt.Println(result) + } +} +``` + + +## Goroutine 常见陷阱和最佳实践 + +### 常见陷阱 + + +```java !! java +// Java:常见线程陷阱 +public class Pitfalls { + // 陷阱 1:不在线程中处理异常 + public static void pitfall1() { + new Thread(() -> { + throw new RuntimeException("Unhandled!"); // 线程静默死亡 + }).start(); + } + + // 陷阱 2:没有同步的共享可变状态 + static int counter = 0; + public static void pitfall2() throws Exception { + for (int i = 0; i < 1000; i++) { + new Thread(() -> counter++).start(); // 竞态条件! + } + Thread.sleep(1000); + System.out.println("Counter: " + counter); // 不可预测! + } + + // 陷阱 3:线程泄漏 + public static void pitfall3() { + ExecutorService executor = Executors.newCachedThreadPool(); + for (int i = 0; i < 100000; i++) { + executor.submit(() -> { + try { + Thread.sleep(1000); + } catch (InterruptedException e) {} + }); + } + // 忘记 shutdown - 线程永不终止! + } +} +``` + +```go !! go +// Go:常见 goroutine 陷阱 +package main + +import ( + "fmt" + "sync" + "time" +) + +// 陷阱 1:不等待 goroutine +func pitfall1() { + go func() { + fmt.Println("Goroutine running") + }() + // 主程序立即退出,goroutine 永不完成 +} + +// 陷阱 2:没有同步的共享可变状态 +func pitfall2() { + var counter int + var wg sync.WaitGroup + + for i := 0; i < 1000; i++ { + wg.Add(1) + go func() { + defer wg.Done() + counter++ // 竞态条件! + }() + } + + wg.Wait() + fmt.Println("Counter:", counter) // 不可预测! +} + +// 陷阱 3:Goroutine 泄漏 +func pitfall3() { + for i := 0; i < 100000; i++ { + go func() { + time.Sleep(time.Hour) // Goroutine 永不退出 + }() + } + // Goroutine 堆积,内存泄漏! +} + +func main() { + fmt.Println("Demonstrating pitfall 2:") + pitfall2() +} +``` + + +### 最佳实践 + + +```java !! java +// Java:线程最佳实践 +import java.util.concurrent.*; + +public class BestPractices { + + // 好:使用线程池 + public static void good1() { + ExecutorService executor = Executors.newFixedThreadPool(10); + + for (int i = 0; i < 100; i++) { + final int taskId = i; + executor.submit(() -> { + System.out.println("Task " + taskId); + }); + } + + executor.shutdown(); + try { + executor.awaitTermination(1, TimeUnit.MINUTES); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + + // 好:正确的同步 + static class SafeCounter { + private int count = 0; + + public synchronized void increment() { + count++; + } + + public synchronized int get() { + return count; + } + } + + // 好:处理异常 + public static void good3() { + Thread thread = new Thread(() -> { + try { + // 可能抛出的工作 + } catch (Exception e) { + e.printStackTrace(); // 处理异常 + } + }); + thread.setUncaughtExceptionHandler((t, e) -> { + System.err.println("Exception in thread " + t.getName()); + }); + thread.start(); + } +} +``` + +```go !! go +// Go:Goroutine 最佳实践 +package main + +import ( + "fmt" + "sync" + "time" +) + +// 好:总是管理 goroutine 生命周期 +func good1() { + var wg sync.WaitGroup + + for i := 0; i < 5; i++ { + wg.Add(1) + go func(id int) { + defer wg.Done() + fmt.Printf("Worker %d\n", id) + }(i) + } + + wg.Wait() +} + +// 好:正确的同步 +type SafeCounter struct { + mu sync.Mutex + count int +} + +func (c *SafeCounter) Increment() { + c.mu.Lock() + defer c.mu.Unlock() + c.count++ +} + +func (c *SafeCounter) Get() int { + c.mu.Lock() + defer c.mu.Unlock() + return c.count +} + +// 好:在 goroutine 中处理 panic +func good3() { + go func() { + defer func() { + if r := recover(); r != nil { + fmt.Printf("Recovered: %v\n", r) + } + }() + + // 可能 panic 的工作 + panic("Oops!") + }() + + time.Sleep(time.Second) +} + +func main() { + fmt.Println("Example 1: Proper lifecycle management") + good1() + + fmt.Println("\nExample 2: Safe counter") + counter := &SafeCounter{} + var wg sync.WaitGroup + + for i := 0; i < 100; i++ { + wg.Add(1) + go func() { + defer wg.Done() + counter.Increment() + }() + } + + wg.Wait() + fmt.Printf("Counter: %d\n", counter.Get()) +} +``` + + +## 性能基准测试 + + +```java !! java +// Java:基准测试线程创建 +public class ThreadBenchmark { + public static void main(String[] args) throws Exception { + int iterations = 10000; + + long start = System.currentTimeMillis(); + + for (int i = 0; i < iterations; i++) { + Thread t = new Thread(() -> {}); + t.start(); + t.join(); + } + + long end = System.currentTimeMillis(); + System.out.println("Created and joined " + iterations + + " threads in " + (end - start) + "ms"); + } +} +``` + +```go !! go +// Go:基准测试 goroutine 创建 +package main + +import ( + "fmt" + "sync" + "time" +) + +func main() { + iterations := 10000 + + start := time.Now() + + var wg sync.WaitGroup + for i := 0; i < iterations; i++ { + wg.Add(1) + go func() { + wg.Done() + }() + } + + wg.Wait() + + elapsed := time.Since(start) + fmt.Printf("Created and waited for %d goroutines in %dms\n", + iterations, elapsed.Milliseconds()) +} +``` + + +## 练习题 + +1. **内存效率**:为什么 Go 可以创建数百万个 goroutine,而 Java 只能创建数千个线程? + +2. **同步**:`sync.WaitGroup` 与 `CountDownLatch` 相比如何?你会何时使用它们? + +3. **调度**:解释 Go 中的 M:N 调度器以及它与 Java 的 1:1 线程模型的区别。 + +4. **最佳实践**:使用 goroutine 时的常见陷阱是什么,如何避免它们? + +5. **性能**:在什么场景下 goroutine 会显著优于 Java 线程? + +## 项目想法 + +1. **并发网页爬虫**:构建一个使用 goroutine 并发获取多个 URL 的网页爬虫 + +2. **并行数据处理**:创建一个程序,并行分块处理大型 CSV 文件 + +3. **Worker Pool 系统**:实现一个健壮的 worker pool,包含作业队列、结果收集和错误处理 + +4. **实时数据 Pipeline**:构建一个使用 pipeline 模式摄取、转换和输出数据的 pipeline + +5. **并发缓存**:实现一个带有过期和并发访问的线程安全内存缓存 + +## 下一步 + +现在你已经理解了 goroutine 和并发: + +- **下一模块**:学习 **Channel 和 Select** 用于 goroutine 之间的通信 +- **深入**:研究 Go 的内存模型和同步模式 +- **练习**:构建并发应用以熟悉 goroutine +- **比较**:分析 goroutine 如何与其他并发模型(async/await、actors)比较 + +## 总结 + +**Goroutine vs Java 线程:** +- Goroutine 是轻量级的(~2KB)vs 线程(~1MB) +- Go 使用 M:N 调度以提高效率 +- `sync.WaitGroup` 简化同步 +- Goroutine 支持大规模并发 +- Channel(下一模块)提供安全通信 + +**关键要点**:Go 的 goroutine 使并发编程比 Java 的线程模型更加高效和易于理解。你可以用最小的开销创建数百万个 goroutine,实现使用 Java 线程无法实现的并发编程新模式。 diff --git a/content/docs/java2go/module-06-goroutines-threads.zh-tw.mdx b/content/docs/java2go/module-06-goroutines-threads.zh-tw.mdx new file mode 100644 index 0000000..e5cf8d4 --- /dev/null +++ b/content/docs/java2go/module-06-goroutines-threads.zh-tw.mdx @@ -0,0 +1,1216 @@ +--- +title: "模組 06:Goroutine vs 執行緒 - 輕量級並行" +description: "掌握 Go 的 goroutine 並與 Java 執行緒進行比較。了解輕量級並行、goroutine 生命週期、WaitGroup 和效能特性。" +--- + +# 模組 06:Goroutine vs 執行緒 - 輕量級並行 + +在本模組中,你將學習 Go 如何透過 **goroutine 處理並行**,並將其與 Java 的執行緒模型進行比較。Go 的 goroutine 是輕量級的,建立成本極低,使並行程式設計更加簡單和高效。 + +## 學習目標 + +完成本模組後,你將能夠: +- 建立和管理 goroutine +- 比較 goroutine 和 Java 執行緒 +- 使用 WaitGroup 進行同步 +- 理解 goroutine 調度和生命週期 +- 分析記憶體佔用的差異 +- 實作常見的 goroutine 模式 +- 高效建構並行程式 + +## 背景:Java 執行緒 vs Go Goroutine + +### 根本差異 + +**Java 執行緒:** +- 重量級 - 每個執行緒映射到一個作業系統執行緒 +- 建立成本高(每個執行緒約 1MB 堆疊空間) +- 受作業系統資源限制(最多數千個) +- 上下文切換成本高(核心層級) +- 複雜的同步機制(synchronized、Lock、Condition) + +**Go Goroutine:** +- 輕量級 - 由 Go 執行時管理 +- 建立極其廉價(初始堆疊約 2KB) +- 可以建立數百萬個 goroutine +- 上下文切換成本低(使用者層級) +- 透過 channel 實作簡單的同步 + + +```java !! java +// Java:建立執行緒是重量級的 +public class ThreadExample { + public static void main(String[] args) { + // 建立並啟動執行緒 + Thread thread = new Thread(() -> { + System.out.println("Hello from thread!"); + }); + thread.start(); + + // 等待執行緒完成 + try { + thread.join(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + System.out.println("Main thread done"); + } +} +``` + +```go !! go +// Go:Goroutine 是輕量級的 +package main + +import ( + "fmt" +) + +func main() { + // 啟動 goroutine + go func() { + fmt.Println("Hello from goroutine!") + }() + + // 等待一下(不是理想方式 - 我們會學習更好的方法) + fmt.Println("Main goroutine done") +} +``` + + +## 建立 Goroutine + +### 基本 Goroutine 建立 + + +```java !! java +// Java:使用執行緒池進行並行任務 +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class MultiThread { + public static void main(String[] args) { + // 使用執行緒池(比直接建立執行緒更好) + ExecutorService executor = Executors.newFixedThreadPool(5); + + for (int i = 0; i < 5; i++) { + final int taskId = i; + executor.submit(() -> { + System.out.println("Task " + taskId + + " running on " + Thread.currentThread().getName()); + }); + } + + executor.shutdown(); + } +} +``` + +```go !! go +// Go:啟動 goroutine +package main + +import ( + "fmt" + "time" +) + +func task(id int) { + fmt.Printf("Task %d running\n", id) +} + +func main() { + // 啟動 5 個 goroutine + for i := 0; i < 5; i++ { + go task(i) + } + + // 等待 goroutine 完成 + time.Sleep(time.Millisecond) +} +``` + + +### 匿名 Goroutine + + +```java !! java +// Java:匿名 Runnable 和 lambda +public class AnonymousThread { + public static void main(String[] args) { + // 匿名類別 + Thread t1 = new Thread(new Runnable() { + @Override + public void run() { + System.out.println("Anonymous class"); + } + }); + + // Lambda(首選) + Thread t2 = new Thread(() -> { + System.out.println("Lambda expression"); + }); + + t1.start(); + t2.start(); + } +} +``` + +```go !! go +// Go:帶閉包的匿名 goroutine +package main + +import ( + "fmt" +) + +func main() { + // 匿名 goroutine + go func() { + fmt.Println("Anonymous goroutine") + }() + + // 帶參數的 goroutine + go func(msg string) { + fmt.Printf("Message: %s\n", msg) + }("Hello from closure") + + // 等待 goroutine + time.Sleep(100 * time.Millisecond) +} +``` + + +## WaitGroup vs CountDownLatch + +### 同步原語 + + +```java !! java +// Java:使用 CountDownLatch 等待多個執行緒 +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class LatchExample { + public static void main(String[] args) throws InterruptedException { + final int TASK_COUNT = 3; + CountDownLatch latch = new CountDownLatch(TASK_COUNT); + ExecutorService executor = Executors.newFixedThreadPool(TASK_COUNT); + + for (int i = 0; i < TASK_COUNT; i++) { + final int taskId = i; + executor.submit(() -> { + try { + Thread.sleep(1000); + System.out.println("Task " + taskId + " completed"); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } finally { + latch.countDown(); // 發出完成信號 + } + }); + } + + latch.await(); // 等待所有任務 + System.out.println("All tasks completed"); + executor.shutdown(); + } +} +``` + +```go !! go +// Go:使用 WaitGroup 同步 +package main + +import ( + "fmt" + "sync" + "time" +) + +func worker(id int, wg *sync.WaitGroup) { + defer wg.Done() // 完成時發出信號 + + fmt.Printf("Worker %d starting\n", id) + time.Sleep(time.Second) + fmt.Printf("Worker %d done\n", id) +} + +func main() { + var wg sync.WaitGroup + + // 啟動 3 個 worker + for i := 1; i <= 3; i++ { + wg.Add(1) // 增加計數器 + go worker(i, &wg) + } + + wg.Wait() // 等待所有 worker + fmt.Println("All workers completed") +} +``` + + +### WaitGroup 最佳實踐 + + +```java !! java +// Java:更複雜的同步場景 +import java.util.concurrent.*; +import java.util.*; + +public class ComplexSync { + static class Result { + List results = new ArrayList<>(); + } + + public static void main(String[] args) throws Exception { + int taskCount = 5; + CountDownLatch latch = new CountDownLatch(taskCount); + Result result = new Result(); + + ExecutorService executor = Executors.newFixedThreadPool(10); + + for (int i = 0; i < taskCount; i++) { + final int taskId = i; + executor.submit(() -> { + try { + String data = "Result " + taskId; + synchronized (result.results) { + result.results.add(data); + } + } finally { + latch.countDown(); + } + }); + } + + latch.await(); + System.out.println("Collected: " + result.results); + executor.shutdown(); + } +} +``` + +```go !! go +// Go:更簡潔的 WaitGroup 模式 +package main + +import ( + "fmt" + "sync" +) + +func collectResults(id int, wg *sync.WaitGroup, results *[]string) { + defer wg.Done() + + data := fmt.Sprintf("Result %d", id) + *results = append(*results, data) +} + +func main() { + var wg sync.WaitGroup + var results []string + var mu sync.Mutex // 保護共享的 slice + + for i := 0; i < 5; i++ { + wg.Add(1) + go func(id int) { + defer wg.Done() + + data := fmt.Sprintf("Result %d", id) + + mu.Lock() + results = append(results, data) + mu.Unlock() + }(i) + } + + wg.Wait() + fmt.Println("Collected:", results) +} +``` + + +## Goroutine 生命週期和調度 + +### 理解 Goroutine 狀態 + + +```java !! java +// Java:執行緒狀態(NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED) +public class ThreadStates { + public static void main(String[] args) throws Exception { + Thread thread = new Thread(() -> { + try { + // RUNNABLE + System.out.println("Thread running"); + + // TIMED_WAITING + Thread.sleep(1000); + + // WAITING + synchronized (ThreadStates.class) { + ThreadStates.class.wait(); + } + } catch (InterruptedException e) { + // TERMINATED + } + }); + + // NEW 狀態 + System.out.println("State: " + thread.getState()); + + thread.start(); // RUNNABLE + + Thread.sleep(100); + System.out.println("State: " + thread.getState()); + } +} +``` + +```go !! go +// Go:Goroutine 狀態(更簡單的模型) +package main + +import ( + "fmt" + "runtime" + "time" +) + +func goroutineStates() { + // Goroutine 正在執行 + fmt.Println("Goroutine running") + + // Goroutine 被阻塞(等待) + time.Sleep(time.Second) + + // Goroutine 完成並被垃圾回收 + fmt.Println("Goroutine done") +} + +func main() { + // goroutine 建立前:還沒有 goroutine + fmt.Printf("Goroutines: %d\n", runtime.NumGoroutine()) + + go goroutineStates() + + time.Sleep(100 * time.Millisecond) + fmt.Printf("Goroutines: %d\n", runtime.NumGoroutine()) + + time.Sleep(2 * time.Second) + fmt.Printf("Goroutines: %d\n", runtime.NumGoroutine()) +} +``` + + +### Goroutine 調度器(M:N 調度器) + + +```java !! java +// Java:1:1 執行緒模型(一個 Java 執行緒 = 一個 OS 執行緒) +// 由 OS 調度器管理 +public class JavaScheduler { + public static void main(String[] args) { + // 每個執行緒建立一個 OS 執行緒 + for (int i = 0; i < 10; i++) { + new Thread(() -> { + // 這是 1:1 映射到 OS 執行緒 + Thread.currentThread().setName("Worker"); + System.out.println(Thread.currentThread().getName()); + }).start(); + } + } +} + +/* OS 管理調度 - 昂貴的上下文切換 */ +``` + +```go !! go +// Go:M:N 調度器(M 個 goroutine : N 個 OS 執行緒) +// Go 執行時管理調度 +package main + +import ( + "fmt" + "runtime" + "time" +) + +func main() { + // 設定 OS 執行緒數(GOMAXPROCS) + fmt.Printf("GOMAXPROCS: %d\n", runtime.GOMAXPROCS(0)) + + // 可以啟動數千個 goroutine + for i := 0; i < 1000; i++ { + go func(id int) { + time.Sleep(100 * time.Millisecond) + fmt.Printf("Goroutine %d\n", id) + }(i) + } + + // Go 執行時將 goroutine 多路復用到更少的 OS 執行緒上 + time.Sleep(time.Second) + fmt.Printf("Total goroutines created: 1000\n") +} +``` + + +## 記憶體佔用比較 + +### 堆疊大小和記憶體使用 + + +```java !! java +// Java:執行緒有很大的堆疊空間 +public class ThreadMemory { + public static void main(String[] args) { + // 預設堆疊大小:每個執行緒約 1MB + // 1000 個執行緒 = 約 1GB 記憶體! + + Runtime runtime = Runtime.getRuntime(); + long before = runtime.totalMemory() - runtime.freeMemory(); + + for (int i = 0; i < 100; i++) { + new Thread(() -> { + try { + Thread.sleep(60000); // 保持活動 + } catch (InterruptedException e) {} + }).start(); + } + + long after = runtime.totalMemory() - runtime.freeMemory(); + System.out.println("Memory used: " + (after - before) / 1024 / 1024 + " MB"); + System.out.println("100 threads created"); + } +} +``` + +```go !! go +// Go:Goroutine 開始時堆疊很小(2KB) +package main + +import ( + "fmt" + "runtime" + "time" +) + +func main() { + var m runtime.MemStats + runtime.ReadMemStats(&m) + before := m.Alloc + + // 建立 100,000 個 goroutine! + for i := 0; i < 100000; i++ { + go func() { + time.Sleep(time.Minute) + }() + } + + runtime.ReadMemStats(&m) + after := m.Alloc + + fmt.Printf("Memory used: %d MB\n", (after-before)/1024/1024) + fmt.Printf("100,000 goroutines created\n") +} +``` + + +### 實際效能比較 + + +```java !! java +// Java:執行緒池效能 +import java.util.concurrent.*; +import java.util.*; + +public class ThreadPerformance { + public static void main(String[] args) throws Exception { + int tasks = 10000; + + ExecutorService executor = Executors.newFixedThreadPool(100); + long start = System.currentTimeMillis(); + + List> futures = new ArrayList<>(); + + for (int i = 0; i < tasks; i++) { + final int taskId = i; + futures.add(executor.submit(() -> { + int sum = 0; + for (int j = 0; j < 100; j++) { + sum += j; + } + return sum; + })); + } + + int total = 0; + for (Future future : futures) { + total += future.get(); + } + + long end = System.currentTimeMillis(); + System.out.println("Time: " + (end - start) + "ms"); + System.out.println("Total: " + total); + + executor.shutdown(); + } +} +``` + +```go !! go +// Go:Goroutine 效能 +package main + +import ( + "fmt" + "sync" + "time" +) + +func task() int { + sum := 0 + for i := 0; i < 100; i++ { + sum += i + } + return sum +} + +func main() { + tasks := 10000 + + start := time.Now() + var wg sync.WaitGroup + results := make(chan int, tasks) + + for i := 0; i < tasks; i++ { + wg.Add(1) + go func() { + defer wg.Done() + results <- task() + }() + } + + go func() { + wg.Wait() + close(results) + }() + + total := 0 + for result := range results { + total += result + } + + elapsed := time.Since(start) + fmt.Printf("Time: %dms\n", elapsed.Milliseconds()) + fmt.Printf("Total: %d\n", total) +} +``` + + +## 常見 Goroutine 模式 + +### Worker Pool 模式 + + +```java !! java +// Java:使用 ExecutorService 的 worker pool +import java.util.concurrent.*; +import java.util.*; + +public class WorkerPool { + static class Job { + int id; + String data; + + Job(int id, String data) { + this.id = id; + this.data = data; + } + } + + public static void main(String[] args) throws Exception { + int numWorkers = 5; + ExecutorService executor = Executors.newFixedThreadPool(numWorkers); + + List jobs = new ArrayList<>(); + for (int i = 0; i < 20; i++) { + jobs.add(new Job(i, "Task " + i)); + } + + for (Job job : jobs) { + executor.submit(() -> { + System.out.println("Processing " + job.id); + try { + Thread.sleep(100); + } catch (InterruptedException e) {} + System.out.println("Completed " + job.id); + }); + } + + executor.shutdown(); + executor.awaitTermination(1, TimeUnit.MINUTES); + } +} +``` + +```go !! go +// Go:使用緩衝 channel 的 worker pool +package main + +import ( + "fmt" + "sync" + "time" +) + +type Job struct { + ID int + Data string +} + +func worker(id int, jobs <-chan Job, wg *sync.WaitGroup) { + defer wg.Done() + + for job := range jobs { + fmt.Printf("Worker %d processing job %d\n", id, job.ID) + time.Sleep(100 * time.Millisecond) + fmt.Printf("Worker %d completed job %d\n", id, job.ID) + } +} + +func main() { + numWorkers := 5 + numJobs := 20 + + jobs := make(chan Job, numJobs) + var wg sync.WaitGroup + + // 啟動 workers + for i := 1; i <= numWorkers; i++ { + wg.Add(1) + go worker(i, jobs, &wg) + } + + // 發送任務 + for i := 0; i < numJobs; i++ { + jobs <- Job{ID: i, Data: fmt.Sprintf("Task %d", i)} + } + close(jobs) + + // 等待所有 worker + wg.Wait() + fmt.Println("All jobs completed") +} +``` + + +### Fan-Out / Fan-In 模式 + + +```java !! java +// Java:使用多個消費者的 fan-out +import java.util.concurrent.*; +import java.util.*; + +public class FanOutFanIn { + public static void main(String[] args) throws Exception { + ExecutorService executor = Executors.newFixedThreadPool(10); + + List> futures = new ArrayList<>(); + + // Fan-out:分發工作 + for (int i = 0; i < 10; i++) { + final int input = i; + futures.add(executor.submit(() -> { + return process(input); + })); + } + + // Fan-in:收集結果 + int sum = 0; + for (Future future : futures) { + sum += future.get(); + } + + System.out.println("Sum: " + sum); + executor.shutdown(); + } + + private static int process(int n) { + return n * n; + } +} +``` + +```go !! go +// Go:使用 channel 的優雅 fan-out/fan-in +package main + +import ( + "fmt" + "sync" +) + +func process(n int) int { + return n * n +} + +func fanOut(inputs <-chan int) <-chan int { + results := make(chan int) + + var wg sync.WaitGroup + for i := 0; i < 3; i++ { // 3 個 worker + wg.Add(1) + go func() { + defer wg.Done() + for n := range inputs { + results <- process(n) + } + }() + } + + go func() { + wg.Wait() + close(results) + }() + + return results +} + +func main() { + inputs := make(chan int) + + // Fan-out + results := fanOut(inputs) + + // 發送輸入 + go func() { + for i := 0; i < 10; i++ { + inputs <- i + } + close(inputs) + }() + + // Fan-in:收集結果 + sum := 0 + for result := range results { + sum += result + } + + fmt.Println("Sum:", sum) +} +``` + + +### Pipeline 模式 + + +```java !! java +// Java:使用 CompletableFuture 的 pipeline +import java.util.concurrent.*; +import java.util.stream.*; + +public class Pipeline { + public static void main(String[] args) throws Exception { + CompletableFuture.supplyAsync(() -> { + // 階段 1:生成資料 + return IntStream.range(0, 10).boxed().collect(Collectors.toList()); + }).thenComposeAsync(data -> { + // 階段 2:轉換資料 + List> futures = data.stream() + .map(n -> CompletableFuture.supplyAsync(() -> n * 2)) + .collect(Collectors.toList()); + + return CompletableFuture.allOf( + futures.toArray(new CompletableFuture[0]) + ).thenApply(v -> futures.stream() + .map(CompletableFuture::join) + .collect(Collectors.toList()) + ); + }).thenAcceptAsync(results -> { + // 階段 3:消費結果 + results.forEach(System.out::println); + }).get(); + } +} +``` + +```go !! go +// Go:使用 channel 的 pipeline +package main + +import ( + "fmt" +) + +func generate(nums ...int) <-chan int { + out := make(chan int) + go func() { + for _, n := range nums { + out <- n + } + close(out) + }() + return out +} + +func square(in <-chan int) <-chan int { + out := make(chan int) + go func() { + for n := range in { + out <- n * n + } + close(out) + }() + return out +} + +func main() { + // 設定 pipeline + numbers := generate(0, 1, 2, 3, 4, 5, 6, 7, 8, 9) + squares := square(numbers) + + // 消費結果 + for result := range squares { + fmt.Println(result) + } +} +``` + + +## Goroutine 常見陷阱和最佳實踐 + +### 常見陷阱 + + +```java !! java +// Java:常見執行緒陷阱 +public class Pitfalls { + // 陷阱 1:不在執行緒中處理異常 + public static void pitfall1() { + new Thread(() -> { + throw new RuntimeException("Unhandled!"); // 執行緒靜默死亡 + }).start(); + } + + // 陷阱 2:沒有同步的共享可變狀態 + static int counter = 0; + public static void pitfall2() throws Exception { + for (int i = 0; i < 1000; i++) { + new Thread(() -> counter++).start(); // 競態條件! + } + Thread.sleep(1000); + System.out.println("Counter: " + counter); // 不可預測! + } + + // 陷阱 3:執行緒泄漏 + public static void pitfall3() { + ExecutorService executor = Executors.newCachedThreadPool(); + for (int i = 0; i < 100000; i++) { + executor.submit(() -> { + try { + Thread.sleep(1000); + } catch (InterruptedException e) {} + }); + } + // 忘記 shutdown - 執行緒永不終止! + } +} +``` + +```go !! go +// Go:常見 goroutine 陷阱 +package main + +import ( + "fmt" + "sync" + "time" +) + +// 陷阱 1:不等待 goroutine +func pitfall1() { + go func() { + fmt.Println("Goroutine running") + }() + // 主程式立即退出,goroutine 永不完成 +} + +// 陷阱 2:沒有同步的共享可變狀態 +func pitfall2() { + var counter int + var wg sync.WaitGroup + + for i := 0; i < 1000; i++ { + wg.Add(1) + go func() { + defer wg.Done() + counter++ // 競態條件! + }() + } + + wg.Wait() + fmt.Println("Counter:", counter) // 不可預測! +} + +// 陷阱 3:Goroutine 泄漏 +func pitfall3() { + for i := 0; i < 100000; i++ { + go func() { + time.Sleep(time.Hour) // Goroutine 永不退出 + }() + } + // Goroutine 堆積,記憶體泄漏! +} + +func main() { + fmt.Println("Demonstrating pitfall 2:") + pitfall2() +} +``` + + +### 最佳實踐 + + +```java !! java +// Java:執行緒最佳實踐 +import java.util.concurrent.*; + +public class BestPractices { + + // 好:使用執行緒池 + public static void good1() { + ExecutorService executor = Executors.newFixedThreadPool(10); + + for (int i = 0; i < 100; i++) { + final int taskId = i; + executor.submit(() -> { + System.out.println("Task " + taskId); + }); + } + + executor.shutdown(); + try { + executor.awaitTermination(1, TimeUnit.MINUTES); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + + // 好:正確的同步 + static class SafeCounter { + private int count = 0; + + public synchronized void increment() { + count++; + } + + public synchronized int get() { + return count; + } + } + + // 好:處理異常 + public static void good3() { + Thread thread = new Thread(() -> { + try { + // 可能拋出的工作 + } catch (Exception e) { + e.printStackTrace(); // 處理異常 + } + }); + thread.setUncaughtExceptionHandler((t, e) -> { + System.err.println("Exception in thread " + t.getName()); + }); + thread.start(); + } +} +``` + +```go !! go +// Go:Goroutine 最佳實踐 +package main + +import ( + "fmt" + "sync" + "time" +) + +// 好:總是管理 goroutine 生命週期 +func good1() { + var wg sync.WaitGroup + + for i := 0; i < 5; i++ { + wg.Add(1) + go func(id int) { + defer wg.Done() + fmt.Printf("Worker %d\n", id) + }(i) + } + + wg.Wait() +} + +// 好:正確的同步 +type SafeCounter struct { + mu sync.Mutex + count int +} + +func (c *SafeCounter) Increment() { + c.mu.Lock() + defer c.mu.Unlock() + c.count++ +} + +func (c *SafeCounter) Get() int { + c.mu.Lock() + defer c.mu.Unlock() + return c.count +} + +// 好:在 goroutine 中處理 panic +func good3() { + go func() { + defer func() { + if r := recover(); r != nil { + fmt.Printf("Recovered: %v\n", r) + } + }() + + // 可能 panic 的工作 + panic("Oops!") + }() + + time.Sleep(time.Second) +} + +func main() { + fmt.Println("Example 1: Proper lifecycle management") + good1() + + fmt.Println("\nExample 2: Safe counter") + counter := &SafeCounter{} + var wg sync.WaitGroup + + for i := 0; i < 100; i++ { + wg.Add(1) + go func() { + defer wg.Done() + counter.Increment() + }() + } + + wg.Wait() + fmt.Printf("Counter: %d\n", counter.Get()) +} +``` + + +## 效能基準測試 + + +```java !! java +// Java:基準測試執行緒建立 +public class ThreadBenchmark { + public static void main(String[] args) throws Exception { + int iterations = 10000; + + long start = System.currentTimeMillis(); + + for (int i = 0; i < iterations; i++) { + Thread t = new Thread(() -> {}); + t.start(); + t.join(); + } + + long end = System.currentTimeMillis(); + System.out.println("Created and joined " + iterations + + " threads in " + (end - start) + "ms"); + } +} +``` + +```go !! go +// Go:基準測試 goroutine 建立 +package main + +import ( + "fmt" + "sync" + "time" +) + +func main() { + iterations := 10000 + + start := time.Now() + + var wg sync.WaitGroup + for i := 0; i < iterations; i++ { + wg.Add(1) + go func() { + wg.Done() + }() + } + + wg.Wait() + + elapsed := time.Since(start) + fmt.Printf("Created and waited for %d goroutines in %dms\n", + iterations, elapsed.Milliseconds()) +} +``` + + +## 練習題 + +1. **記憶體效率**:為什麼 Go 可以建立數百萬個 goroutine,而 Java 只能建立數千個執行緒? + +2. **同步**:`sync.WaitGroup` 與 `CountDownLatch` 相比如何?你會何時使用它們? + +3. **調度**:解釋 Go 中的 M:N 調度器以及它與 Java 的 1:1 執行緒模型的區別。 + +4. **最佳實踐**:使用 goroutine 時的常見陷阱是什麼,如何避免它們? + +5. **效能**:在什麼場景下 goroutine 會顯著優於 Java 執行緒? + +## 專案想法 + +1. **並行網頁爬蟲**:建構一個使用 goroutine 並行獲取多個 URL 的網頁爬蟲 + +2. **平行資料處理**:建立一個程式,平行分塊處理大型 CSV 檔案 + +3. **Worker Pool 系統**:實作一個健壯的 worker pool,包含作業佇列、結果收集和錯誤處理 + +4. **即時資料 Pipeline**:建構一個使用 pipeline 模式攝取、轉換和輸出資料的 pipeline + +5. **並行快取**:實作一個帶有過期和並行存取的執行緒安全記憶體快取 + +## 下一步 + +現在你已經理解了 goroutine 和並行: + +- **下一模組**:學習 **Channel 和 Select** 用於 goroutine 之間的通訊 +- **深入**:研究 Go 的記憶體模型和同步模式 +- **練習**:建構並行應用以熟悉 goroutine +- **比較**:分析 goroutine 如何與其他並行模型(async/await、actors)比較 + +## 總結 + +**Goroutine vs Java 執行緒:** +- Goroutine 是輕量級的(~2KB)vs 執行緒(~1MB) +- Go 使用 M:N 調度以提高效率 +- `sync.WaitGroup` 簡化同步 +- Goroutine 支援大規模並行 +- Channel(下一模組)提供安全通訊 + +**關鍵要點**:Go 的 goroutine 使並行程式設計比 Java 的執行緒模型更加高效和易於理解。你可以用最小的開銷建立數百萬個 goroutine,實作使用 Java 執行緒無法實現的並行程式設計新模式。 diff --git a/content/docs/java2go/module-07-channels-select.mdx b/content/docs/java2go/module-07-channels-select.mdx new file mode 100644 index 0000000..f808210 --- /dev/null +++ b/content/docs/java2go/module-07-channels-select.mdx @@ -0,0 +1,1494 @@ +--- +title: "Module 07: Channels and Select - Safe Communication" +description: "Master Go's channels for safe goroutine communication. Learn about buffered/unbuffered channels, select statements, channel directions, and concurrency patterns." +--- + +# Module 07: Channels and Select - Safe Communication + +In this module, you'll learn how Go uses **channels** to communicate safely between goroutines. Go's philosophy is: **"Don't communicate by sharing memory; share memory by communicating."** This is a fundamental shift from Java's shared-state concurrency model. + +## Learning Objectives + +By the end of this module, you'll be able to: +- Create and use buffered and unbuffered channels +- Understand channel directions (send, receive, bidirectional) +- Use select statements for multiple channel operations +- Implement timeout patterns with select +- Close channels and range over them +- Build fan-in and fan-out patterns +- Create producer-consumer systems +- Compare channels with Java's BlockingQueue and CompletableFuture + +## Background: Shared Memory vs Channel Communication + +### The Philosophy Difference + +**Java:** Share memory by communicating (with synchronization) +- Threads access shared data structures +- Must use locks, synchronized blocks, atomic variables +- Prone to deadlocks, race conditions +- Complex coordination logic + +**Go:** Communicate to share memory +- Goroutines send data through channels +- No explicit locking needed for channel operations +- Safe by design (ownership transfer) +- Simpler coordination through message passing + + +```java !! java +// Java: Shared memory with synchronization +import java.util.concurrent.*; + +public class SharedMemory { + private static int counter = 0; + private static final Object lock = new Object(); + + public static void main(String[] args) throws Exception { + ExecutorService executor = Executors.newFixedThreadPool(2); + + // Multiple threads access shared counter + executor.submit(() -> { + synchronized (lock) { + counter++; + System.out.println("Incremented: " + counter); + } + }); + + executor.submit(() -> { + synchronized (lock) { + counter++; + System.out.println("Incremented: " + counter); + } + }); + + executor.shutdown(); + executor.awaitTermination(1, TimeUnit.SECONDS); + } +} +``` + +```go !! go +// Go: Share memory by communicating +package main + +import ( + "fmt" + "sync" +) + +func main() { + // Channel for communication + counter := make(chan int) + + var wg sync.WaitGroup + + // Goroutine 1: Send to channel + wg.Add(1) + go func() { + defer wg.Done() + counter <- 1 + fmt.Println("Sent: 1") + }() + + // Goroutine 2: Send to channel + wg.Add(1) + go func() { + defer wg.Done() + counter <- 2 + fmt.Println("Sent: 2") + }() + + // Main goroutine: Receive from channel + wg.Add(1) + go func() { + defer wg.Done() + value := <-counter + fmt.Printf("Received: %d\n", value) + }() + + wg.Wait() +} +``` + + +## Creating Channels + +### Unbuffered vs Buffered Channels + + +```java !! java +// Java: BlockingQueue (similar to buffered channels) +import java.util.concurrent.*; + +public class QueueExample { + public static void main(String[] args) throws Exception { + // SynchronousQueue (like unbuffered channel) + BlockingQueue synchronousQueue = new SynchronousQueue<>(); + + // LinkedBlockingQueue with capacity (like buffered channel) + BlockingQueue bufferedQueue = new LinkedBlockingQueue<>(10); + + // Producer + new Thread(() -> { + try { + synchronousQueue.put("Message"); // Blocks until consumer takes + System.out.println("Sent to synchronous queue"); + } catch (InterruptedException e) {} + }).start(); + + // Consumer + new Thread(() -> { + try { + String msg = synchronousQueue.take(); // Blocks until available + System.out.println("Received: " + msg); + } catch (InterruptedException e) {} + }).start(); + } +} +``` + +```go !! go +// Go: Unbuffered and buffered channels +package main + +import ( + "fmt" + "time" +) + +func main() { + // Unbuffered channel (synchronous) + unbuffered := make(chan int) + + // Buffered channel (asynchronous) + buffered := make(chan string, 10) + + // Unbuffered: sender blocks until receiver ready + go func() { + fmt.Println("Sending to unbuffered...") + unbuffered <- 42 + fmt.Println("Sent to unbuffered!") + }() + + time.Sleep(time.Second) // Ensure receiver is ready + value := <-unbuffered + fmt.Printf("Received from unbuffered: %d\n", value) + + // Buffered: sender doesn't block if buffer has space + buffered <- "Hello" + buffered <- "World" + fmt.Printf("Buffered length: %d\n", len(buffered)) + + msg := <-buffered + fmt.Printf("Received from buffered: %s\n", msg) +} +``` + + +### Channel Capacity and Operations + + +```java !! java +// Java: BlockingQueue operations +import java.util.concurrent.*; + +public class QueueOperations { + public static void main(String[] args) throws Exception { + BlockingQueue queue = new ArrayBlockingQueue<>(5); + + // add() - Throws exception if full + queue.add(1); + + // offer() - Returns false if full + boolean success = queue.offer(2); + System.out.println("Offer success: " + success); + + // put() - Blocks if full + queue.put(3); + + // poll() - Returns null if empty + Integer value = queue.poll(); + System.out.println("Polled: " + value); + + // take() - Blocks if empty + Integer taken = queue.take(); + System.out.println("Taken: " + taken); + + // peek() - Looks at head without removing + Integer head = queue.peek(); + System.out.println("Peeked: " + head); + + System.out.println("Remaining capacity: " + queue.remainingCapacity()); + } +} +``` + +```go !! go +// Go: Channel operations +package main + +import ( + "fmt" +) + +func main() { + // Buffered channel + ch := make(chan int, 5) + + // Send (blocks if channel is full) + ch <- 1 + ch <- 2 + ch <- 3 + + // Receive (blocks if channel is empty) + value := <-ch + fmt.Printf("Received: %d\n", value) + + // Check length and capacity + fmt.Printf("Length: %d, Capacity: %d\n", len(ch), cap(ch)) + + // Receive without blocking (comma-ok idiom) + v, ok := <-ch + fmt.Printf("Value: %d, Ok: %t\n", v, ok) + + // Send to full channel (would block, so we don't do it) + // Use select with default for non-blocking operations +} +``` + + +## Channel Directions + +### Restricting Channel Usage + + +```java !! java +// Java: No built-in channel direction restriction +// You must document producer/consumer roles +import java.util.concurrent.*; + +public class ChannelDirections { + static class Producer { + private final BlockingQueue queue; + + Producer(BlockingQueue queue) { + this.queue = queue; + } + + void produce(String message) throws InterruptedException { + queue.put(message); // Can only send + } + } + + static class Consumer { + private final BlockingQueue queue; + + Consumer(BlockingQueue queue) { + this.queue = queue; + } + + String consume() throws InterruptedException { + return queue.take(); // Can only receive + } + } + + public static void main(String[] args) throws Exception { + BlockingQueue queue = new LinkedBlockingQueue<>(); + + Producer producer = new Producer(queue); + Consumer consumer = new Consumer(queue); + + producer.produce("Hello"); + String msg = consumer.consume(); + System.out.println("Consumed: " + msg); + } +} +``` + +```go !! go +// Go: Bidirectional, send-only, and receive-only channels +package main + +import ( + "fmt" +) + +// Send-only channel parameter +func producer(ch chan<- int) { + ch <- 42 + // <-ch // Compile error: cannot receive from send-only channel + close(ch) +} + +// Receive-only channel parameter +func consumer(ch <-chan int) { + value := <-ch + fmt.Printf("Received: %d\n", value) + // ch <- 1 // Compile error: cannot send to receive-only channel +} + +// Bidirectional channel parameter +func relay(in <-chan int, out chan<- int) { + value := <-in + out <- value +} + +func main() { + // Bidirectional channel + ch := make(chan int) + + // Convert to send-only or receive-only when passing + go producer(ch) + consumer(ch) + + // Relay example + in := make(chan int) + out := make(chan int) + + go func() { + in <- 100 + close(in) + }() + + relay(in, out) + fmt.Printf("Relayed: %d\n", <-out) +} +``` + + +## Closing Channels + +### Graceful Shutdown + + +```java !! java +// Java: Poison pill or special value to signal completion +import java.util.concurrent.*; +import java.util.*; + +public class ClosingChannels { + static final String POISON_PILL = "POISON"; + + public static void main(String[] args) throws Exception { + BlockingQueue queue = new LinkedBlockingQueue<>(); + + // Producer + new Thread(() -> { + try { + for (int i = 0; i < 5; i++) { + queue.put("Message " + i); + Thread.sleep(100); + } + // Send poison pill to signal completion + queue.put(POISON_PILL); + } catch (InterruptedException e) {} + }).start(); + + // Consumer + new Thread(() -> { + try { + while (true) { + String msg = queue.take(); + if (msg.equals(POISON_PILL)) { + System.out.println("Received poison pill, stopping"); + break; + } + System.out.println("Processed: " + msg); + } + } catch (InterruptedException e) {} + }).start(); + } +} +``` + +```go !! go +// Go: Close channels to signal no more values +package main + +import ( + "fmt" + "time" +) + +func producer(ch chan<- int) { + for i := 0; i < 5; i++ { + ch <- i + time.Sleep(100 * time.Millisecond) + } + close(ch) // Signal no more values +} + +func consumer(ch <-chan int) { + for value := range ch { // Automatically stops when channel closed + fmt.Printf("Received: %d\n", value) + } + fmt.Println("Channel closed, consumer done") +} + +func main() { + ch := make(chan int) + + go producer(ch) + consumer(ch) +} +``` + + +### Detecting Closed Channels + + +```java !! java +// Java: Handle poison pill or check for null +import java.util.concurrent.*; + +public class ClosedDetection { + public static void main(String[] args) throws Exception { + BlockingQueue queue = new LinkedBlockingQueue<>(); + String doneSignal = "DONE"; + + // Producer + new Thread(() -> { + try { + for (int i = 0; i < 3; i++) { + queue.put("Item " + i); + } + queue.put(doneSignal); + } catch (InterruptedException e) {} + }).start(); + + // Consumer + new Thread(() -> { + try { + while (true) { + String item = queue.take(); + if (item.equals(doneSignal)) { + break; + } + System.out.println("Processed: " + item); + } + } catch (InterruptedException e) {} + }).start(); + } +} +``` + +```go !! go +// Go: Comma-ok idiom to detect closed channel +package main + +import ( + "fmt" + "time" +) + +func main() { + ch := make(chan int, 3) + + // Producer + go func() { + for i := 1; i <= 3; i++ { + ch <- i + } + close(ch) + }() + + // Consumer with comma-ok idiom + for { + value, ok := <-ch + if !ok { + fmt.Println("Channel closed!") + break + } + fmt.Printf("Received: %d\n", value) + } + + // Alternative: range automatically detects close + ch2 := make(chan string, 2) + go func() { + ch2 <- "Hello" + ch2 <- "World" + close(ch2) + }() + + time.Sleep(100 * time.Millisecond) + for msg := range ch2 { + fmt.Println("Range:", msg) + } + fmt.Println("Done ranging") +} +``` + + +## Select Statements + +### Waiting on Multiple Channels + + +```java !! java +// Java: No direct equivalent to select +// You must use complex polling or CompletableFuture +import java.util.concurrent.*; +import java.util.*; + +public class SelectEquivalent { + public static void main(String[] args) throws Exception { + BlockingQueue queue1 = new LinkedBlockingQueue<>(); + BlockingQueue queue2 = new LinkedBlockingQueue<>(); + + // Simulate select with polling (not efficient) + new Thread(() -> { + try { + while (true) { + String msg; + + // Poll with timeout (inefficient) + if ((msg = queue1.poll(100, TimeUnit.MILLISECONDS)) != null) { + System.out.println("From queue1: " + msg); + break; + } + + if ((msg = queue2.poll(100, TimeUnit.MILLISECONDS)) != null) { + System.out.println("From queue2: " + msg); + break; + } + } + } catch (InterruptedException e) {} + }).start(); + + Thread.sleep(50); + queue1.put("Hello"); + } +} +``` + +```go !! go +// Go: Select statement waits on multiple channels +package main + +import ( + "fmt" + "time" +) + +func main() { + ch1 := make(chan string) + ch2 := make(chan string) + + go func() { + time.Sleep(100 * time.Millisecond) + ch1 <- "one" + }() + + go func() { + time.Sleep(200 * time.Millisecond) + ch2 <- "two" + }() + + for i := 0; i < 2; i++ { + select { + case msg1 := <-ch1: + fmt.Println("Received from ch1:", msg1) + case msg2 := <-ch2: + fmt.Println("Received from ch2:", msg2) + } + } + + fmt.Println("Done") +} +``` + + +### Non-Blocking Operations with Default + + +```java !! java +// Java: poll() and offer() for non-blocking operations +import java.util.concurrent.*; + +public class NonBlockingOps { + public static void main(String[] args) { + BlockingQueue queue = new LinkedBlockingQueue<>(1); + + // Non-blocking offer + boolean offered = queue.offer("Message"); + System.out.println("Offered: " + offered); + + // Non-blocking poll + String msg = queue.poll(); + System.out.println("Polled: " + msg); + + // Non-blocking poll when empty + String empty = queue.poll(); + System.out.println("Poll empty: " + empty); // Returns null + } +} +``` + +```go !! go +// Go: Select with default for non-blocking operations +package main + +import ( + "fmt" +) + +func main() { + ch := make(chan int, 1) + + // Non-blocking send + select { + case ch <- 42: + fmt.Println("Sent 42") + default: + fmt.Println("Could not send (channel full or no receiver)") + } + + // Non-blocking receive + select { + case value := <-ch: + fmt.Printf("Received: %d\n", value) + default: + fmt.Println("Could not receive (channel empty)") + } + + // Try receive again + select { + case value := <-ch: + fmt.Printf("Received: %d\n", value) + default: + fmt.Println("No value available") + } +} +``` + + +### Timeout Patterns + + +```java !! java +// Java: Timeout with poll(timeout) +import java.util.concurrent.*; +import java.util.*; + +public class TimeoutPattern { + public static void main(String[] args) throws Exception { + BlockingQueue queue = new LinkedBlockingQueue<>(); + + // Producer with delay + new Thread(() -> { + try { + Thread.sleep(2000); + queue.put("Delayed message"); + } catch (InterruptedException e) {} + }).start(); + + // Consumer with timeout + String msg = queue.poll(1, TimeUnit.SECONDS); + if (msg == null) { + System.out.println("Timeout - no message received"); + } else { + System.out.println("Received: " + msg); + } + + // Using CompletableFuture with timeout + CompletableFuture future = CompletableFuture.supplyAsync(() -> { + try { + Thread.sleep(2000); + return "Result"; + } catch (InterruptedException e) { + return "Error"; + } + }); + + try { + String result = future.get(1, TimeUnit.SECONDS); + System.out.println("Got: " + result); + } catch (TimeoutException e) { + System.out.println("Future timeout"); + } + } +} +``` + +```go !! go +// Go: Select with time.After for timeout +package main + +import ( + "fmt" + "time" +) + +func main() { + ch := make(chan string) + + go func() { + time.Sleep(2 * time.Second) + ch <- "Result" + }() + + select { + case result := <-ch: + fmt.Println("Received:", result) + case <-time.After(1 * time.Second): + fmt.Println("Timeout - no message received") + } + + // Using context with timeout (more idiomatic) + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + + select { + case <-ctx.Done(): + fmt.Println("Context timeout") + case result := <-ch: + fmt.Println("Received:", result) + } +} +``` + + +## Fan-Out and Fan-In Patterns + +### Fan-Out: Distribute Work + + +```java !! java +// Java: Fan-out with multiple consumers +import java.util.concurrent.*; +import java.util.*; + +public class FanOut { + static BlockingQueue queue = new LinkedBlockingQueue<>(100); + + static class Worker implements Runnable { + private final String name; + + Worker(String name) { + this.name = name; + } + + @Override + public void run() { + try { + while (true) { + Integer task = queue.take(); + if (task == null) break; // Poison pill + + System.out.println(name + " processing " + task); + Thread.sleep(100); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + } + + public static void main(String[] args) throws Exception { + int numWorkers = 3; + + // Start workers + ExecutorService executor = Executors.newFixedThreadPool(numWorkers); + for (int i = 0; i < numWorkers; i++) { + executor.submit(new Worker("Worker-" + i)); + } + + // Distribute work + for (int i = 0; i < 10; i++) { + queue.put(i); + } + + Thread.sleep(2000); + executor.shutdownNow(); + } +} +``` + +```go !! go +// Go: Fan-out with multiple goroutines +package main + +import ( + "fmt" + "sync" + "time" +) + +func worker(id int, jobs <-chan int, wg *sync.WaitGroup) { + defer wg.Done() + + for job := range jobs { + fmt.Printf("Worker %d processing job %d\n", id, job) + time.Sleep(100 * time.Millisecond) + } +} + +func main() { + numWorkers := 3 + numJobs := 10 + + jobs := make(chan int, numJobs) + var wg sync.WaitGroup + + // Start workers + for i := 1; i <= numWorkers; i++ { + wg.Add(1) + go worker(i, jobs, &wg) + } + + // Distribute jobs + for j := 0; j < numJobs; j++ { + jobs <- j + } + close(jobs) + + wg.Wait() + fmt.Println("All jobs completed") +} +``` + + +### Fan-In: Collect Results + + +```java !! java +// Java: Fan-in collecting results from multiple sources +import java.util.concurrent.*; +import java.util.*; + +public class FanIn { + public static void main(String[] args) throws Exception { + ExecutorService executor = Executors.newFixedThreadPool(3); + + List> futures = new ArrayList<>(); + + // Submit tasks + for (int i = 0; i < 5; i++) { + final int taskId = i; + futures.add(executor.submit(() -> { + Thread.sleep(100); + return taskId * taskId; + })); + } + + // Collect results (fan-in) + int sum = 0; + for (Future future : futures) { + sum += future.get(); + } + + System.out.println("Total sum: " + sum); + executor.shutdown(); + } +} +``` + +```go !! go +// Go: Fan-in with channels +package main + +import ( + "fmt" + "sync" +) + +func worker(id int, results chan<- int, wg *sync.WaitGroup) { + defer wg.Done() + + result := id * id + results <- result +} + +func main() { + numWorkers := 5 + + results := make(chan int, numWorkers) + var wg sync.WaitGroup + + // Fan-out: start workers + for i := 0; i < numWorkers; i++ { + wg.Add(1) + go worker(i, results, &wg) + } + + // Wait for all workers in background + go func() { + wg.Wait() + close(results) + }() + + // Fan-in: collect results + sum := 0 + for result := range results { + sum += result + } + + fmt.Printf("Total sum: %d\n", sum) +} +``` + + +## Producer-Consumer Pattern + +### Complete Producer-Consumer System + + +```java !! java +// Java: Classic producer-consumer with BlockingQueue +import java.util.concurrent.*; + +public class ProducerConsumer { + private static final BlockingQueue queue = new ArrayBlockingQueue<>(10); + + static class Producer implements Runnable { + public void run() { + try { + for (int i = 0; i < 20; i++) { + queue.put(i); + System.out.println("Produced: " + i); + Thread.sleep(50); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + } + + static class Consumer implements Runnable { + public void run() { + try { + while (true) { + Integer value = queue.poll(1, TimeUnit.SECONDS); + if (value == null) break; + + System.out.println("Consumed: " + value); + Thread.sleep(100); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + } + + public static void main(String[] args) throws Exception { + ExecutorService executor = Executors.newFixedThreadPool(3); + + executor.submit(new Producer()); + executor.submit(new Producer()); + executor.submit(new Consumer()); + + executor.shutdown(); + executor.awaitTermination(10, TimeUnit.SECONDS); + } +} +``` + +```go !! go +// Go: Producer-consumer with channels +package main + +import ( + "fmt" + "sync" + "time" +) + +func producer(id int, items chan<- int, wg *sync.WaitGroup) { + defer wg.Done() + + for i := 0; i < 10; i++ { + item := id*10 + i + items <- item + fmt.Printf("Producer %d produced: %d\n", id, item) + time.Sleep(50 * time.Millisecond) + } +} + +func consumer(items <-chan int, done chan<- bool) { + for item := range items { + fmt.Printf("Consumed: %d\n", item) + time.Sleep(100 * time.Millisecond) + } + done <- true +} + +func main() { + items := make(chan int, 10) + done := make(chan bool) + var wg sync.WaitGroup + + // Start producers + wg.Add(2) + go producer(1, items, &wg) + go producer(2, items, &wg) + + // Wait for producers then close channel + go func() { + wg.Wait() + close(items) + }() + + // Start consumer + go consumer(items, done) + + <-done + fmt.Println("All done") +} +``` + + +## Advanced Patterns + +### Pipeline of Stages + + +```java !! java +// Java: Pipeline with CompletionStage +import java.util.concurrent.*; +import java.util.stream.*; + +public class PipelinePattern { + public static void main(String[] args) throws Exception { + CompletableFuture.supplyAsync(() -> { + // Stage 1: Generate + return IntStream.range(0, 10).boxed().collect(Collectors.toList()); + }).thenApplyAsync(numbers -> { + // Stage 2: Transform + return numbers.stream() + .map(n -> n * 2) + .collect(Collectors.toList()); + }).thenAcceptAsync(results -> { + // Stage 3: Consume + results.forEach(System.out::println); + }).get(); + } +} +``` + +```go !! go +// Go: Pipeline with channels +package main + +import ( + "fmt" +) + +func generate(numbers ...int) <-chan int { + out := make(chan int) + go func() { + defer close(out) + for _, n := range numbers { + out <- n + } + }() + return out +} + +func double(in <-chan int) <-chan int { + out := make(chan int) + go func() { + defer close(out) + for n := range in { + out <- n * 2 + } + }() + return out +} + +func main() { + // Create pipeline: generate -> double + numbers := generate(0, 1, 2, 3, 4, 5, 6, 7, 8, 9) + doubled := double(numbers) + + // Consume results + for result := range doubled { + fmt.Println(result) + } +} +``` + + +### Bounded Parallelism + + +```java !! java +// Java: Semaphores for bounded parallelism +import java.util.concurrent.*; + +public class BoundedParallelism { + public static void main(String[] args) throws Exception { + int maxConcurrent = 3; + Semaphore semaphore = new Semaphore(maxConcurrent); + ExecutorService executor = Executors.newCachedThreadPool(); + + for (int i = 0; i < 10; i++) { + final int taskId = i; + executor.submit(() -> { + try { + semaphore.acquire(); + System.out.println("Task " + taskId + " started"); + Thread.sleep(1000); + System.out.println("Task " + taskId + " completed"); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } finally { + semaphore.release(); + } + }); + } + + executor.shutdown(); + executor.awaitTermination(1, TimeUnit.MINUTES); + } +} +``` + +```go !! go +// Go: Bounded parallelism with buffered channel +package main + +import ( + "fmt" + "time" +) + +func main() { + maxConcurrent := 3 + sem := make(chan struct{}, maxConcurrent) + + tasks := make(chan int, 10) + + // Generate tasks + go func() { + for i := 0; i < 10; i++ { + tasks <- i + } + close(tasks) + }() + + // Process with bounded parallelism + for task := range tasks { + sem <- struct{}{} // Acquire + + go func(id int) { + defer func() { <-sem }() // Release + + fmt.Printf("Task %d started\n", id) + time.Sleep(time.Second) + fmt.Printf("Task %d completed\n", id) + }(task) + } + + // Wait for all goroutines + for i := 0; i < maxConcurrent; i++ { + sem <- struct{}{} + } + + fmt.Println("All tasks completed") +} +``` + + +## Channel Gotchas and Best Practices + +### Common Pitfalls + + +```java !! java +// Java: Common concurrency pitfalls +import java.util.concurrent.*; + +public class Pitfalls { + // Pitfall 1: Deadlock + public static void deadlock() { + final Object lock1 = new Object(); + final Object lock2 = new Object(); + + new Thread(() -> { + synchronized (lock1) { + try { Thread.sleep(100); } catch (Exception e) {} + synchronized (lock2) { } // Deadlock if other thread holds lock2 + } + }).start(); + + new Thread(() -> { + synchronized (lock2) { + try { Thread.sleep(100); } catch (Exception e) {} + synchronized (lock1) { } // Deadlock! + } + }).start(); + } + + // Pitfall 2: Forgotten signal + public static void forgottenSignal() { + final boolean[] ready = {false}; + + new Thread(() -> { + // Waiting for ready flag + while (!ready[0]) { + // Busy waiting - inefficient! + } + System.out.println("Ready!"); + }).start(); + + // Forgot to set ready[0] = true + } + + // Pitfall 3: Sending to closed channel (no Java equivalent for channels) +} +``` + +```go !! go +// Go: Common channel pitfalls +package main + +import ( + "fmt" + "sync" + "time" +) + +// Pitfall 1: Sending to closed channel +func closedChannelSend() { + ch := make(chan int, 1) + close(ch) + + // ch <- 42 // Panic: send on closed channel + fmt.Println("Cannot send to closed channel") +} + +// Pitfall 2: Closing channel from receiver +func closeFromReceiver() { + ch := make(chan int) + + go func() { + // close(ch) // Wrong: only sender should close + value := <-ch + fmt.Printf("Received: %d\n", value) + }() + + ch <- 42 + time.Sleep(time.Second) +} + +// Pitfall 3: Forgetting to close channel +func forgetToClose() { + ch := make(chan int) + + go func() { + for i := 0; i < 5; i++ { + ch <- i + } + // Forgot: close(ch) + }() + + for value := range ch { // Deadlock! Channel never closed + fmt.Println(value) + if value == 4 { + break // Must break manually + } + } +} + +// Pitfall 4: Goroutine leak with channel +func goroutineLeak() { + ch := make(chan int) + + go func() { + value := <-ch // Goroutine blocks forever + fmt.Printf("Received: %d\n", value) + }() + + // Never send to ch - goroutine leaks! + time.Sleep(time.Second) +} + +func main() { + fmt.Println("Demonstrating pitfalls...") + + closedChannelSend() + + fmt.Println("\nDemonstrating forget to close:") + forgetToClose() +} +``` + + +### Best Practices + + +```java !! java +// Java: Best practices for concurrent collections +import java.util.concurrent.*; + +public class BestPractices { + + // Good 1: Use appropriate queue type + public static void good1() throws Exception { + // SynchronousQueue for handoff + BlockingQueue handoff = new SynchronousQueue<>(); + + // LinkedBlockingQueue for unbounded + BlockingQueue unbounded = new LinkedBlockingQueue<>(); + + // ArrayBlockingQueue for bounded + BlockingQueue bounded = new ArrayBlockingQueue<>(100); + } + + // Good 2: Always handle InterruptedException + public static void good2() { + BlockingQueue queue = new LinkedBlockingQueue<>(); + + new Thread(() -> { + try { + String msg = queue.take(); + System.out.println("Received: " + msg); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + System.out.println("Interrupted"); + } + }).start(); + } + + // Good 3: Use try-finally for cleanup + public static void good3() { + BlockingQueue queue = new LinkedBlockingQueue<>(); + ExecutorService executor = Executors.newFixedThreadPool(10); + + try { + // Do work + } finally { + executor.shutdown(); + } + } +} +``` + +```go !! go +// Go: Channel best practices +package main + +import ( + "context" + "fmt" + "sync" + "time" +) + +// Good 1: Own the channel you close +func producer(ctx context.Context, ch chan<- int) <-chan struct{} { + done := make(chan struct{}) + + go func() { + defer close(ch) // Only sender closes + defer close(done) + + for i := 0; i < 5; i++ { + select { + case ch <- i: + case <-ctx.Done(): + return + } + time.Sleep(100 * time.Millisecond) + } + }() + + return done +} + +// Good 2: Use range for receiving +func consumer(ch <-chan int) { + for value := range ch { // Automatically handles close + fmt.Printf("Received: %d\n", value) + } + fmt.Println("Consumer done") +} + +// Good 3: Use select for timeout/cancellation +func withTimeout(ch chan int, timeout time.Duration) bool { + select { + case val := <-ch: + fmt.Printf("Got: %d\n", val) + return true + case <-time.After(timeout): + fmt.Println("Timeout") + return false + } +} + +// Good 4: Buffer only when needed +func bufferedVsUnbuffered() { + // Unbuffered: synchronous, guarantees delivery + unbuffered := make(chan int) + go func() { + unbuffered <- 42 // Will wait for receiver + }() + fmt.Println(<-unbuffered) + + // Buffered: asynchronous for throughput + buffered := make(chan int, 10) + buffered <- 1 // Won't block + buffered <- 2 + fmt.Printf("Buffered length: %d\n", len(buffered)) +} + +func main() { + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + + ch := make(chan int) + done := producer(ctx, ch) + + go consumer(ch) + <-done + + fmt.Println("\nBuffered vs unbuffered:") + bufferedVsUnbuffered() +} +``` + + +## Practice Questions + +1. **Philosophy**: Why does Go advocate "sharing memory by communicating" instead of "communicating by sharing memory"? + +2. **Channel Types**: When would you use an unbuffered channel vs a buffered channel? + +3. **Select**: What makes Go's select statement powerful compared to Java's blocking queue operations? + +4. **Closing**: Why is it important to close channels, and who should be responsible for closing them? + +5. **Patterns**: How do fan-out and fan-in patterns improve throughput in concurrent programs? + +## Project Ideas + +1. **Concurrent Data Pipeline**: Build a multi-stage pipeline that processes data through multiple transformation steps + +2. **Job Queue System**: Implement a distributed job queue with multiple workers and priority channels + +3. **Rate Limiter**: Create a token bucket rate limiter using channels and tickers + +4. **Pub/Sub System**: Build a publish-subscribe system with multiple topics and subscribers + +5. **Work Stealing Pool**: Implement a work-stealing thread pool using channels for load balancing + +## Next Steps + +Now that you understand channels and select: + +- **Next Module**: Learn about **Web Development** with Go's net/http package +- **Practice**: Build concurrent applications using channels extensively +- **Explore**: Study Go's context package for cancellation and timeouts +- **Compare**: Analyze how Go's CSP model compares to actor models and other concurrency paradigms + +## Summary + +**Channels vs Java's BlockingQueue:** +- Channels are language primitives, not just library classes +- Directional types provide compile-time safety +- Select statement enables powerful multi-channel operations +- Closing channels provides clean shutdown semantics +- Go's CSP model promotes safer concurrency + +**Key Takeaway**: Go's channels and select statements provide a powerful and safe way to coordinate goroutines. By sharing memory through communication rather than communicating through shared memory, you can avoid entire classes of concurrency bugs like deadlocks and race conditions. diff --git a/content/docs/java2go/module-07-channels-select.zh-cn.mdx b/content/docs/java2go/module-07-channels-select.zh-cn.mdx new file mode 100644 index 0000000..c8a0ca9 --- /dev/null +++ b/content/docs/java2go/module-07-channels-select.zh-cn.mdx @@ -0,0 +1,727 @@ +--- +title: "模块 07:Channel 和 Select - 安全通信" +description: "掌握 Go 的 channel 用于安全的 goroutine 通信。了解缓冲/无缓冲 channel、select 语句、channel 方向和并发模式。" +--- + +# 模块 07:Channel 和 Select - 安全通信 + +在本模块中,你将学习 Go 如何使用 **channel** 在 goroutine 之间安全通信。Go 的哲学是:**"不要通过共享内存来通信;通过通信来共享内存。"** 这与 Java 的共享状态并发模型有着根本的不同。 + +## 学习目标 + +完成本模块后,你将能够: +- 创建和使用缓冲及无缓冲 channel +- 理解 channel 方向(发送、接收、双向) +- 使用 select 语句进行多 channel 操作 +- 使用 select 实现超时模式 +- 关闭 channel 并遍历 channel +- 构建 fan-in 和 fan-out 模式 +- 创建生产者-消费者系统 +- 将 channel 与 Java 的 BlockingQueue 和 CompletableFuture 进行比较 + +## 背景:共享内存 vs Channel 通信 + +### 哲学差异 + +**Java**:通过通信共享内存(需要同步) +- 线程访问共享数据结构 +- 必须使用锁、synchronized 块、原子变量 +- 容易出现死锁和竞态条件 +- 复杂的协调逻辑 + +**Go**:通过通信共享内存 +- Goroutine 通过 channel 发送数据 +- Channel 操作不需要显式加锁 +- 设计安全(所有权转移) +- 通过消息传递简化协调 + + +```java !! java +// Java:使用同步的共享内存 +import java.util.concurrent.*; + +public class SharedMemory { + private static int counter = 0; + private static final Object lock = new Object(); + + public static void main(String[] args) throws Exception { + ExecutorService executor = Executors.newFixedThreadPool(2); + + // 多个线程访问共享计数器 + executor.submit(() -> { + synchronized (lock) { + counter++; + System.out.println("Incremented: " + counter); + } + }); + + executor.submit(() -> { + synchronized (lock) { + counter++; + System.out.println("Incremented: " + counter); + } + }); + + executor.shutdown(); + executor.awaitTermination(1, TimeUnit.SECONDS); + } +} +``` + +```go !! go +// Go:通过通信共享内存 +package main + +import ( + "fmt" + "sync" +) + +func main() { + // 用于通信的 channel + counter := make(chan int) + + var wg sync.WaitGroup + + // Goroutine 1:发送到 channel + wg.Add(1) + go func() { + defer wg.Done() + counter <- 1 + fmt.Println("Sent: 1") + }() + + // Goroutine 2:发送到 channel + wg.Add(1) + go func() { + defer wg.Done() + counter <- 2 + fmt.Println("Sent: 2") + }() + + // 主 goroutine:从 channel 接收 + wg.Add(1) + go func() { + defer wg.Done() + value := <-counter + fmt.Printf("Received: %d\n", value) + }() + + wg.Wait() +} +``` + + +## 创建 Channel + +### 无缓冲 vs 缓冲 Channel + + +```java !! java +// Java:BlockingQueue(类似缓冲 channel) +import java.util.concurrent.*; + +public class QueueExample { + public static void main(String[] args) throws Exception { + // SynchronousQueue(类似无缓冲 channel) + BlockingQueue synchronousQueue = new SynchronousQueue<>(); + + // 带容量的 LinkedBlockingQueue(类似缓冲 channel) + BlockingQueue bufferedQueue = new LinkedBlockingQueue<>(10); + + // 生产者 + new Thread(() -> { + try { + synchronousQueue.put("Message"); // 阻塞直到消费者取走 + System.out.println("Sent to synchronous queue"); + } catch (InterruptedException e) {} + }).start(); + + // 消费者 + new Thread(() -> { + try { + String msg = synchronousQueue.take(); // 阻塞直到可用 + System.out.println("Received: " + msg); + } catch (InterruptedException e) {} + }).start(); + } +} +``` + +```go !! go +// Go:无缓冲和缓冲 channel +package main + +import ( + "fmt" + "time" +) + +func main() { + // 无缓冲 channel(同步) + unbuffered := make(chan int) + + // 缓冲 channel(异步) + buffered := make(chan string, 10) + + // 无缓冲:发送方阻塞直到接收方准备好 + go func() { + fmt.Println("Sending to unbuffered...") + unbuffered <- 42 + fmt.Println("Sent to unbuffered!") + }() + + time.Sleep(time.Second) // 确保接收方准备好 + value := <-unbuffered + fmt.Printf("Received from unbuffered: %d\n", value) + + // 缓冲:如果缓冲区有空间,发送方不会阻塞 + buffered <- "Hello" + buffered <- "World" + fmt.Printf("Buffered length: %d\n", len(buffered)) + + msg := <-buffered + fmt.Printf("Received from buffered: %s\n", msg) +} +``` + + +## Channel 方向 + +### 限制 Channel 使用 + + +```java !! java +// Java:没有内置的 channel 方向限制 +// 你必须文档化生产者/消费者角色 +import java.util.concurrent.*; + +public class ChannelDirections { + static class Producer { + private final BlockingQueue queue; + + Producer(BlockingQueue queue) { + this.queue = queue; + } + + void produce(String message) throws InterruptedException { + queue.put(message); // 只能发送 + } + } + + static class Consumer { + private final BlockingQueue queue; + + Consumer(BlockingQueue queue) { + this.queue = queue; + } + + String consume() throws InterruptedException { + return queue.take(); // 只能接收 + } + } +} +``` + +```go !! go +// Go:双向、只发送、只接收 channel +package main + +import ( + "fmt" +) + +// 只发送 channel 参数 +func producer(ch chan<- int) { + ch <- 42 + // <-ch // 编译错误:不能从只发送 channel 接收 + close(ch) +} + +// 只接收 channel 参数 +func consumer(ch <-chan int) { + value := <-ch + fmt.Printf("Received: %d\n", value) + // ch <- 1 // 编译错误:不能向只接收 channel 发送 +} + +// 双向 channel 参数 +func relay(in <-chan int, out chan<- int) { + value := <-in + out <- value +} + +func main() { + ch := make(chan int) + go producer(ch) + consumer(ch) +} +``` + + +## 关闭 Channel + +### 优雅关闭 + + +```java !! java +// Java:使用毒丸或特殊值表示完成 +import java.util.concurrent.*; + +public class ClosingChannels { + static final String POISON_PILL = "POISON"; + + public static void main(String[] args) throws Exception { + BlockingQueue queue = new LinkedBlockingQueue<>(); + + // 生产者 + new Thread(() -> { + try { + for (int i = 0; i < 5; i++) { + queue.put("Message " + i); + } + queue.put(POISON_PILL); // 发送毒丸信号 + } catch (InterruptedException e) {} + }).start(); + + // 消费者 + new Thread(() -> { + try { + while (true) { + String msg = queue.take(); + if (msg.equals(POISON_PILL)) break; + System.out.println("Processed: " + msg); + } + } catch (InterruptedException e) {} + }).start(); + } +} +``` + +```go !! go +// Go:关闭 channel 表示没有更多值 +package main + +import ( + "fmt" + "time" +) + +func producer(ch chan<- int) { + for i := 0; i < 5; i++ { + ch <- i + time.Sleep(100 * time.Millisecond) + } + close(ch) // 信号没有更多值 +} + +func consumer(ch <-chan int) { + for value := range ch { // channel 关闭时自动停止 + fmt.Printf("Received: %d\n", value) + } + fmt.Println("Channel closed, consumer done") +} + +func main() { + ch := make(chan int) + + go producer(ch) + consumer(ch) +} +``` + + +## Select 语句 + +### 等待多个 Channel + + +```java !! java +// Java:没有 select 的直接等价物 +// 必须使用复杂的轮询或 CompletableFuture +import java.util.concurrent.*; + +public class SelectEquivalent { + public static void main(String[] args) throws Exception { + BlockingQueue queue1 = new LinkedBlockingQueue<>(); + BlockingQueue queue2 = new LinkedBlockingQueue<>(); + + // 用轮询模拟 select(效率低) + new Thread(() -> { + try { + while (true) { + String msg; + + if ((msg = queue1.poll(100, TimeUnit.MILLISECONDS)) != null) { + System.out.println("From queue1: " + msg); + break; + } + + if ((msg = queue2.poll(100, TimeUnit.MILLISECONDS)) != null) { + System.out.println("From queue2: " + msg); + break; + } + } + } catch (InterruptedException e) {} + }).start(); + + Thread.sleep(50); + queue1.put("Hello"); + } +} +``` + +```go !! go +// Go:Select 语句等待多个 channel +package main + +import ( + "fmt" + "time" +) + +func main() { + ch1 := make(chan string) + ch2 := make(chan string) + + go func() { + time.Sleep(100 * time.Millisecond) + ch1 <- "one" + }() + + go func() { + time.Sleep(200 * time.Millisecond) + ch2 <- "two" + }() + + for i := 0; i < 2; i++ { + select { + case msg1 := <-ch1: + fmt.Println("Received from ch1:", msg1) + case msg2 := <-ch2: + fmt.Println("Received from ch2:", msg2) + } + } +} +``` + + +### 超时模式 + + +```java !! java +// Java:使用 poll(timeout) 实现超时 +import java.util.concurrent.*; + +public class TimeoutPattern { + public static void main(String[] args) throws Exception { + BlockingQueue queue = new LinkedBlockingQueue<>(); + + new Thread(() -> { + try { + Thread.sleep(2000); + queue.put("Delayed message"); + } catch (InterruptedException e) {} + }).start(); + + String msg = queue.poll(1, TimeUnit.SECONDS); + if (msg == null) { + System.out.println("Timeout - no message received"); + } + } +} +``` + +```go !! go +// Go:Select with time.After 实现超时 +package main + +import ( + "fmt" + "time" +) + +func main() { + ch := make(chan string) + + go func() { + time.Sleep(2 * time.Second) + ch <- "Result" + }() + + select { + case result := <-ch: + fmt.Println("Received:", result) + case <-time.After(1 * time.Second): + fmt.Println("Timeout - no message received") + } +} +``` + + +## Fan-Out 和 Fan-In 模式 + +### Fan-Out:分发工作 + + +```java !! java +// Java:使用多个消费者的 fan-out +import java.util.concurrent.*; + +public class FanOut { + static BlockingQueue queue = new LinkedBlockingQueue<>(100); + + static class Worker implements Runnable { + public void run() { + try { + while (true) { + Integer task = queue.take(); + System.out.println("Processing " + task); + Thread.sleep(100); + } + } catch (InterruptedException e) {} + } + } + + public static void main(String[] args) throws Exception { + ExecutorService executor = Executors.newFixedThreadPool(3); + + for (int i = 0; i < 3; i++) { + executor.submit(new Worker()); + } + + for (int i = 0; i < 10; i++) { + queue.put(i); + } + } +} +``` + +```go !! go +// Go:使用多个 goroutine 的 fan-out +package main + +import ( + "fmt" + "sync" + "time" +) + +func worker(id int, jobs <-chan int, wg *sync.WaitGroup) { + defer wg.Done() + + for job := range jobs { + fmt.Printf("Worker %d processing job %d\n", id, job) + time.Sleep(100 * time.Millisecond) + } +} + +func main() { + jobs := make(chan int, 10) + var wg sync.WaitGroup + + for i := 1; i <= 3; i++ { + wg.Add(1) + go worker(i, jobs, &wg) + } + + for j := 0; j < 10; j++ { + jobs <- j + } + close(jobs) + + wg.Wait() +} +``` + + +## 生产者-消费者模式 + + +```java !! java +// Java:使用 BlockingQueue 的经典生产者-消费者 +import java.util.concurrent.*; + +public class ProducerConsumer { + private static final BlockingQueue queue = new ArrayBlockingQueue<>(10); + + static class Producer implements Runnable { + public void run() { + try { + for (int i = 0; i < 20; i++) { + queue.put(i); + System.out.println("Produced: " + i); + Thread.sleep(50); + } + } catch (InterruptedException e) {} + } + } + + static class Consumer implements Runnable { + public void run() { + try { + while (true) { + Integer value = queue.poll(1, TimeUnit.SECONDS); + if (value == null) break; + System.out.println("Consumed: " + value); + Thread.sleep(100); + } + } catch (InterruptedException e) {} + } + } +} +``` + +```go !! go +// Go:使用 channel 的生产者-消费者 +package main + +import ( + "fmt" + "sync" + "time" +) + +func producer(id int, items chan<- int, wg *sync.WaitGroup) { + defer wg.Done() + + for i := 0; i < 10; i++ { + item := id*10 + i + items <- item + fmt.Printf("Producer %d produced: %d\n", id, item) + time.Sleep(50 * time.Millisecond) + } +} + +func consumer(items <-chan int, done chan<- bool) { + for item := range items { + fmt.Printf("Consumed: %d\n", item) + time.Sleep(100 * time.Millisecond) + } + done <- true +} + +func main() { + items := make(chan int, 10) + done := make(chan bool) + var wg sync.WaitGroup + + wg.Add(2) + go producer(1, items, &wg) + go producer(2, items, &wg) + + go func() { + wg.Wait() + close(items) + }() + + go consumer(items, done) + <-done +} +``` + + +## 最佳实践 + + +```go !! go +// Go:Channel 最佳实践 +package main + +import ( + "context" + "fmt" + "time" +) + +// 好 1:拥有你关闭的 channel +func producer(ctx context.Context, ch chan<- int) <-chan struct{} { + done := make(chan struct{}) + + go func() { + defer close(ch) // 只有发送方关闭 + defer close(done) + + for i := 0; i < 5; i++ { + select { + case ch <- i: + case <-ctx.Done(): + return + } + time.Sleep(100 * time.Millisecond) + } + }() + + return done +} + +// 好 2:使用 range 接收 +func consumer(ch <-chan int) { + for value := range ch { // 自动处理关闭 + fmt.Printf("Received: %d\n", value) + } +} + +// 好 3:使用 select 处理超时/取消 +func withTimeout(ch chan int, timeout time.Duration) bool { + select { + case val := <-ch: + fmt.Printf("Got: %d\n", val) + return true + case <-time.After(timeout): + fmt.Println("Timeout") + return false + } +} + +func main() { + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + + ch := make(chan int) + done := producer(ctx, ch) + + go consumer(ch) + <-done +} +``` + + +## 练习题 + +1. **哲学**:为什么 Go 倡导"通过通信共享内存"而不是"通过共享内存通信"? + +2. **Channel 类型**:何时使用无缓冲 channel vs 缓冲 channel? + +3. **Select**:Go 的 select 语句与 Java 的阻塞队列操作相比有什么强大之处? + +4. **关闭**:为什么关闭 channel 很重要,谁应该负责关闭它们? + +## 项目想法 + +1. **并发数据 Pipeline**:构建多阶段 pipeline,通过多个转换步骤处理数据 + +2. **作业队列系统**:实现具有多个 worker 和优先级 channel 的分布式作业队列 + +3. **限流器**:使用 channel 和 ticker 创建令牌桶限流器 + +4. **Pub/Sub 系统**:构建具有多个主题和订阅者的发布-订阅系统 + +## 下一步 + +现在你已经理解了 channel 和 select: + +- **下一模块**:学习使用 Go 的 net/http 包进行 **Web 开发** +- **练习**:广泛使用 channel 构建并发应用 +- **探索**:研究 Go 的 context 包用于取消和超时 + +## 总结 + +**Channel vs Java 的 BlockingQueue:** +- Channel 是语言原语,不仅仅是库类 +- 方向类型提供编译时安全 +- Select 语句支持强大的多 channel 操作 +- 关闭 channel 提供清晰的关闭语义 +- Go 的 CSP 模型促进更安全的并发 + +**关键要点**:Go 的 channel 和 select 语句为协调 goroutine 提供了强大且安全的方式。通过通信共享内存而不是通过共享内存通信,你可以避免整类并发 bug,如死锁和竞态条件。 diff --git a/content/docs/java2go/module-07-channels-select.zh-tw.mdx b/content/docs/java2go/module-07-channels-select.zh-tw.mdx new file mode 100644 index 0000000..33aa5bd --- /dev/null +++ b/content/docs/java2go/module-07-channels-select.zh-tw.mdx @@ -0,0 +1,625 @@ +--- +title: "模組 07:Channel 和 Select - 安全通訊" +description: "掌握 Go 的 channel 用於安全的 goroutine 通訊。了解緩衝/無緩衝 channel、select 陳述式、channel 方向和並行模式。" +--- + +# 模組 07:Channel 和 Select - 安全通訊 + +在本模組中,你將學習 Go 如何使用 **channel** 在 goroutine 之間安全通訊。Go 的哲學是:**"不要透過共享記憶體來通訊;透過通訊來共享記憶體。"** 這與 Java 的共享狀態並行模型有著根本的不同。 + +## 學習目標 + +完成本模組後,你將能夠: +- 建立和使用緩衝及無緩衝 channel +- 理解 channel 方向(發送、接收、雙向) +- 使用 select 陳述式進行多 channel 操作 +- 使用 select 實作超時模式 +- 關閉 channel 並遍歷 channel +- 構建 fan-in 和 fan-out 模式 +- 建立生產者-消費者系統 +- 將 channel 與 Java 的 BlockingQueue 和 CompletableFuture 進行比較 + +## 背景:共享記憶體 vs Channel 通訊 + +### 哲學差異 + +**Java**:透過通訊共享記憶體(需要同步) +- 執行緒存取共享資料結構 +- 必須使用鎖、synchronized 區塊、原子變數 +- 容易出現死鎖和競態條件 +- 複雜的協調邏輯 + +**Go**:透過通訊共享記憶體 +- Goroutine 透過 channel 發送資料 +- Channel 操作不需要顯式加鎖 +- 設計安全(所有權轉移) +- 透過訊息傳遞簡化協調 + + +```java !! java +// Java:使用同步的共享記憶體 +import java.util.concurrent.*; + +public class SharedMemory { + private static int counter = 0; + private static final Object lock = new Object(); + + public static void main(String[] args) throws Exception { + ExecutorService executor = Executors.newFixedThreadPool(2); + + executor.submit(() -> { + synchronized (lock) { + counter++; + System.out.println("Incremented: " + counter); + } + }); + + executor.shutdown(); + executor.awaitTermination(1, TimeUnit.SECONDS); + } +} +``` + +```go !! go +// Go:透過通訊共享記憶體 +package main + +import ( + "fmt" + "sync" +) + +func main() { + counter := make(chan int) + var wg sync.WaitGroup + + wg.Add(1) + go func() { + defer wg.Done() + counter <- 1 + fmt.Println("Sent: 1") + }() + + wg.Add(1) + go func() { + defer wg.Done() + value := <-counter + fmt.Printf("Received: %d\n", value) + }() + + wg.Wait() +} +``` + + +## 建立 Channel + +### 無緩衝 vs 緩衝 Channel + + +```java !! java +// Java:BlockingQueue(類似緩衝 channel) +import java.util.concurrent.*; + +public class QueueExample { + public static void main(String[] args) throws Exception { + BlockingQueue synchronousQueue = new SynchronousQueue<>(); + BlockingQueue bufferedQueue = new LinkedBlockingQueue<>(10); + + new Thread(() -> { + try { + synchronousQueue.put("Message"); + System.out.println("Sent to synchronous queue"); + } catch (InterruptedException e) {} + }).start(); + + new Thread(() -> { + try { + String msg = synchronousQueue.take(); + System.out.println("Received: " + msg); + } catch (InterruptedException e) {} + }).start(); + } +} +``` + +```go !! go +// Go:無緩衝和緩衝 channel +package main + +import ( + "fmt" + "time" +) + +func main() { + unbuffered := make(chan int) + buffered := make(chan string, 10) + + go func() { + fmt.Println("Sending to unbuffered...") + unbuffered <- 42 + fmt.Println("Sent to unbuffered!") + }() + + time.Sleep(time.Second) + value := <-unbuffered + fmt.Printf("Received from unbuffered: %d\n", value) + + buffered <- "Hello" + buffered <- "World" + fmt.Printf("Buffered length: %d\n", len(buffered)) +} +``` + + +## Channel 方向 + +### 限制 Channel 使用 + + +```java !! java +// Java:沒有內建的 channel 方向限制 +import java.util.concurrent.*; + +public class ChannelDirections { + static class Producer { + private final BlockingQueue queue; + + Producer(BlockingQueue queue) { + this.queue = queue; + } + + void produce(String message) throws InterruptedException { + queue.put(message); + } + } +} +``` + +```go !! go +// Go:雙向、只發送、只接收 channel +package main + +import ( + "fmt" +) + +func producer(ch chan<- int) { + ch <- 42 + close(ch) +} + +func consumer(ch <-chan int) { + value := <-ch + fmt.Printf("Received: %d\n", value) +} + +func main() { + ch := make(chan int) + go producer(ch) + consumer(ch) +} +``` + + +## 關閉 Channel + +### 優雅關閉 + + +```java !! java +// Java:使用毒丸或特殊值表示完成 +import java.util.concurrent.*; + +public class ClosingChannels { + static final String POISON_PILL = "POISON"; + + public static void main(String[] args) throws Exception { + BlockingQueue queue = new LinkedBlockingQueue<>(); + + new Thread(() -> { + try { + for (int i = 0; i < 5; i++) { + queue.put("Message " + i); + } + queue.put(POISON_PILL); + } catch (InterruptedException e) {} + }).start(); + + new Thread(() -> { + try { + while (true) { + String msg = queue.take(); + if (msg.equals(POISON_PILL)) break; + System.out.println("Processed: " + msg); + } + } catch (InterruptedException e) {} + }).start(); + } +} +``` + +```go !! go +// Go:關閉 channel 表示沒有更多值 +package main + +import ( + "fmt" +) + +func producer(ch chan<- int) { + for i := 0; i < 5; i++ { + ch <- i + } + close(ch) +} + +func consumer(ch <-chan int) { + for value := range ch { + fmt.Printf("Received: %d\n", value) + } +} + +func main() { + ch := make(chan int) + go producer(ch) + consumer(ch) +} +``` + + +## Select 陳述式 + +### 等待多個 Channel + + +```java !! java +// Java:沒有 select 的直接等價物 +import java.util.concurrent.*; + +public class SelectEquivalent { + public static void main(String[] args) throws Exception { + BlockingQueue queue1 = new LinkedBlockingQueue<>(); + BlockingQueue queue2 = new LinkedBlockingQueue<>(); + + new Thread(() -> { + try { + while (true) { + String msg; + if ((msg = queue1.poll(100, TimeUnit.MILLISECONDS)) != null) { + System.out.println("From queue1: " + msg); + break; + } + if ((msg = queue2.poll(100, TimeUnit.MILLISECONDS)) != null) { + System.out.println("From queue2: " + msg); + break; + } + } + } catch (InterruptedException e) {} + }).start(); + + Thread.sleep(50); + queue1.put("Hello"); + } +} +``` + +```go !! go +// Go:Select 陳述式等待多個 channel +package main + +import ( + "fmt" + "time" +) + +func main() { + ch1 := make(chan string) + ch2 := make(chan string) + + go func() { + time.Sleep(100 * time.Millisecond) + ch1 <- "one" + }() + + go func() { + time.Sleep(200 * time.Millisecond) + ch2 <- "two" + }() + + for i := 0; i < 2; i++ { + select { + case msg1 := <-ch1: + fmt.Println("Received from ch1:", msg1) + case msg2 := <-ch2: + fmt.Println("Received from ch2:", msg2) + } + } +} +``` + + +### 超時模式 + + +```java !! java +// Java:使用 poll(timeout) 實作超時 +import java.util.concurrent.*; + +public class TimeoutPattern { + public static void main(String[] args) throws Exception { + BlockingQueue queue = new LinkedBlockingQueue<>(); + + new Thread(() -> { + try { + Thread.sleep(2000); + queue.put("Delayed message"); + } catch (InterruptedException e) {} + }).start(); + + String msg = queue.poll(1, TimeUnit.SECONDS); + if (msg == null) { + System.out.println("Timeout"); + } + } +} +``` + +```go !! go +// Go:Select with time.After 實作超時 +package main + +import ( + "fmt" + "time" +) + +func main() { + ch := make(chan string) + + go func() { + time.Sleep(2 * time.Second) + ch <- "Result" + }() + + select { + case result := <-ch: + fmt.Println("Received:", result) + case <-time.After(1 * time.Second): + fmt.Println("Timeout") + } +} +``` + + +## Fan-Out 和 Fan-In 模式 + +### Fan-Out:分發工作 + + +```java !! java +// Java:使用多個消費者的 fan-out +import java.util.concurrent.*; + +public class FanOut { + static BlockingQueue queue = new LinkedBlockingQueue<>(100); + + static class Worker implements Runnable { + public void run() { + try { + while (true) { + Integer task = queue.take(); + System.out.println("Processing " + task); + Thread.sleep(100); + } + } catch (InterruptedException e) {} + } + } +} +``` + +```go !! go +// Go:使用多個 goroutine 的 fan-out +package main + +import ( + "fmt" + "sync" + "time" +) + +func worker(id int, jobs <-chan int, wg *sync.WaitGroup) { + defer wg.Done() + + for job := range jobs { + fmt.Printf("Worker %d processing job %d\n", id, job) + time.Sleep(100 * time.Millisecond) + } +} + +func main() { + jobs := make(chan int, 10) + var wg sync.WaitGroup + + for i := 1; i <= 3; i++ { + wg.Add(1) + go worker(i, jobs, &wg) + } + + for j := 0; j < 10; j++ { + jobs <- j + } + close(jobs) + + wg.Wait() +} +``` + + +## 生產者-消費者模式 + + +```java !! java +// Java:使用 BlockingQueue 的經典生產者-消費者 +import java.util.concurrent.*; + +public class ProducerConsumer { + private static final BlockingQueue queue = new ArrayBlockingQueue<>(10); + + static class Producer implements Runnable { + public void run() { + try { + for (int i = 0; i < 20; i++) { + queue.put(i); + System.out.println("Produced: " + i); + Thread.sleep(50); + } + } catch (InterruptedException e) {} + } + } + + static class Consumer implements Runnable { + public void run() { + try { + while (true) { + Integer value = queue.poll(1, TimeUnit.SECONDS); + if (value == null) break; + System.out.println("Consumed: " + value); + Thread.sleep(100); + } + } catch (InterruptedException e) {} + } + } +} +``` + +```go !! go +// Go:使用 channel 的生產者-消費者 +package main + +import ( + "fmt" + "sync" + "time" +) + +func producer(id int, items chan<- int, wg *sync.WaitGroup) { + defer wg.Done() + + for i := 0; i < 10; i++ { + item := id*10 + i + items <- item + time.Sleep(50 * time.Millisecond) + } +} + +func consumer(items <-chan int, done chan<- bool) { + for item := range items { + fmt.Printf("Consumed: %d\n", item) + time.Sleep(100 * time.Millisecond) + } + done <- true +} + +func main() { + items := make(chan int, 10) + done := make(chan bool) + var wg sync.WaitGroup + + wg.Add(2) + go producer(1, items, &wg) + go producer(2, items, &wg) + + go func() { + wg.Wait() + close(items) + }() + + go consumer(items, done) + <-done +} +``` + + +## 最佳實踐 + + +```go !! go +// Go:Channel 最佳實踐 +package main + +import ( + "context" + "fmt" + "time" +) + +func producer(ctx context.Context, ch chan<- int) <-chan struct{} { + done := make(chan struct{}) + + go func() { + defer close(ch) + defer close(done) + + for i := 0; i < 5; i++ { + select { + case ch <- i: + case <-ctx.Done(): + return + } + } + }() + + return done +} + +func consumer(ch <-chan int) { + for value := range ch { + fmt.Printf("Received: %d\n", value) + } +} + +func main() { + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + + ch := make(chan int) + done := producer(ctx, ch) + + go consumer(ch) + <-done +} +``` + + +## 練習題 + +1. **哲學**:為什麼 Go 倡導"透過通訊共享記憶體"而不是"透過共享記憶體通訊"? + +2. **Channel 類型**:何時使用無緩衝 channel vs 緩衝 channel? + +3. **Select**:Go 的 select 陳述式與 Java 的阻塞佇列操作相比有什麼強大之處? + +## 專案想法 + +1. **並行資料 Pipeline**:構建多階段 pipeline +2. **作業佇列系統**:實作分布式作業佇列 +3. **限流器**:使用 channel 和 ticker 建立令牌桶限流器 + +## 下一步 + +現在你已經理解了 channel 和 select: + +- **下一模組**:學習使用 Go 的 net/http 套件進行 **Web 開發** +- **練習**:廣泛使用 channel 建構並行應用 + +## 總結 + +**Channel vs Java 的 BlockingQueue:** +- Channel 是語言原語,不僅僅是庫類別 +- 方向類型提供編譯時安全 +- Select 陳述式支援強大的多 channel 操作 +- Go 的 CSP 模型促進更安全的並行 + +**關鍵要點**:Go 的 channel 和 select 陳述式為協調 goroutine 提供了強大且安全的方式。透過通訊共享記憶體,你可以避免整類並行 bug。 diff --git a/content/docs/java2go/module-08-web-development.mdx b/content/docs/java2go/module-08-web-development.mdx new file mode 100644 index 0000000..0ecfc6a --- /dev/null +++ b/content/docs/java2go/module-08-web-development.mdx @@ -0,0 +1,883 @@ +--- +title: "Module 08: Web Development - Building HTTP Servers" +description: "Master web development in Go with net/http. Compare with Java/Spring Boot, learn routing, JSON handling, middleware, and popular Go frameworks." +--- + +# Module 08: Web Development - Building HTTP Servers + +In this module, you'll learn how to build web applications in Go using the **net/http** package. You'll see how Go's approach to web development differs from Java's Spring Boot, often requiring less boilerplate and fewer dependencies. + +## Learning Objectives + +By the end of this module, you'll be able to: +- Create HTTP servers with net/http +- Implement routing and handlers +- Handle JSON requests and responses +- Create middleware +- Compare Go's approach with Spring Boot +- Use popular Go web frameworks (Gin, Echo) +- Build RESTful APIs +- Implement WebSocket support +- Use template rendering + +## Background: Go vs Spring Boot + +### The Philosophy Difference + +**Java/Spring Boot:** +- Heavy framework with many abstractions +- Annotation-driven configuration +- Dependency injection container +- Auto-configuration and magic +- Large memory footprint +- Longer startup times + +**Go/net/http:** +- Minimal standard library approach +- Explicit code over configuration +- No built-in dependency injection +- Simple and predictable +- Small memory footprint +- Fast startup and execution + + +```java !! java +// Java: Spring Boot REST controller +import org.springframework.boot.*; +import org.springframework.web.bind.annotation.*; + +@RestController +@SpringBootApplication +public class HelloApplication { + @GetMapping("/hello") + public String hello() { + return "Hello, World!"; + } + + @GetMapping("/hello/{name}") + public String helloName(@PathVariable String name) { + return "Hello, " + name + "!"; + } + + public static void main(String[] args) { + SpringApplication.run(HelloApplication.class, args); + } +} +``` + +```go !! go +// Go: Standard library HTTP server +package main + +import ( + "fmt" + "net/http" +) + +func helloHandler(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, "Hello, World!") +} + +func helloNameHandler(w http.ResponseWriter, r *http.Request) { + name := r.PathValue("name") + fmt.Fprintf(w, "Hello, %s!", name) +} + +func main() { + mux := http.NewServeMux() + + mux.HandleFunc("GET /hello", helloHandler) + mux.HandleFunc("GET /hello/{name}", helloNameHandler) + + fmt.Println("Server starting on :8080") + http.ListenAndServe(":8080", mux) +} +``` + + +## Basic HTTP Server + +### Creating a Simple Server + + +```java !! java +// Java: Basic Spring Boot application +import org.springframework.boot.*; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api") +public class BasicController { + + @GetMapping("/ping") + public String ping() { + return "pong"; + } + + @GetMapping("/health") + public Map health() { + Map status = new HashMap<>(); + status.put("status", "UP"); + return status; + } + + public static void main(String[] args) { + SpringApplication.run(BasicController.class, args); + } +} +``` + +```go !! go +// Go: Basic HTTP server +package main + +import ( + "encoding/json" + "net/http" +) + +func pingHandler(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, "pong") +} + +func healthHandler(w http.ResponseWriter, r *http.Request) { + response := map[string]string{ + "status": "UP", + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(response) +} + +func main() { + mux := http.NewServeMux() + + mux.HandleFunc("GET /api/ping", pingHandler) + mux.HandleFunc("GET /api/health", healthHandler) + + fmt.Println("Server starting on :8080") + http.ListenAndServe(":8080", mux) +} +``` + + +## Routing and Handlers + +### Path Variables and Query Parameters + + +```java !! java +// Java: Spring Boot routing +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/users") +public class UserController { + + @GetMapping + public List getUsers( + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "10") int size + ) { + return userService.getUsers(page, size); + } + + @GetMapping("/{id}") + public User getUser(@PathVariable Long id) { + return userService.getById(id); + } + + @PostMapping + public User createUser(@RequestBody User user) { + return userService.save(user); + } + + @PutMapping("/{id}") + public User updateUser( + @PathVariable Long id, + @RequestBody User user + ) { + return userService.update(id, user); + } + + @DeleteMapping("/{id}") + public void deleteUser(@PathVariable Long id) { + userService.delete(id); + } +} +``` + +```go !! go +// Go: HTTP routing +package main + +import ( + "encoding/json" + "fmt" + "net/http" + "strconv" +) + +type User struct { + ID int `json:"id"` + Name string `json:"name"` + Email string `json:"email"` +} + +func getUsersHandler(w http.ResponseWriter, r *http.Request) { + page, _ := strconv.Atoi(r.URL.Query().Get("page")) + size, _ := strconv.Atoi(r.URL.Query().Get("size")) + + if page == 0 { + page = 1 + } + if size == 0 { + size = 10 + } + + users := []User{ + {ID: 1, Name: "Alice", Email: "alice@example.com"}, + {ID: 2, Name: "Bob", Email: "bob@example.com"}, + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(users) +} + +func getUserHandler(w http.ResponseWriter, r *http.Request) { + idStr := r.PathValue("id") + id, _ := strconv.Atoi(idStr) + + user := User{ID: id, Name: "Alice", Email: "alice@example.com"} + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(user) +} + +func main() { + mux := http.NewServeMux() + + mux.HandleFunc("GET /users", getUsersHandler) + mux.HandleFunc("GET /users/{id}", getUserHandler) + + fmt.Println("Server starting on :8080") + http.ListenAndServe(":8080", mux) +} +``` + + +## JSON Handling + +### Request and Response with JSON + + +```java !! java +// Java: JSON with Spring Boot +import org.springframework.web.bind.annotation.*; +import com.fasterxml.jackson.annotation.*; + +@RestController +@RequestMapping("/api") +public class JsonController { + + static class CreateUserRequest { + private String name; + private String email; + + // Getters and setters + public String getName() { return name; } + public void setName(String name) { this.name = name; } + public String getEmail() { return email; } + public void setEmail(String email) { this.email = email; } + } + + @PostMapping("/users") + public ResponseEntity createUser(@RequestBody CreateUserRequest request) { + User user = new User(); + user.setName(request.getName()); + user.setEmail(request.getEmail()); + user.setId(1L); + + return ResponseEntity.status(HttpStatus.CREATED).body(user); + } +} +``` + +```go !! go +// Go: JSON handling +package main + +import ( + "encoding/json" + "net/http" +) + +type CreateUserRequest struct { + Name string `json:"name"` + Email string `json:"email"` +} + +type User struct { + ID int `json:"id"` + Name string `json:"name"` + Email string `json:"email"` +} + +func createUserHandler(w http.ResponseWriter, r *http.Request) { + var req CreateUserRequest + + // Decode JSON request + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + // Create user + user := User{ + ID: 1, + Name: req.Name, + Email: req.Email, + } + + // Encode JSON response + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + json.NewEncoder(w).Encode(user) +} + +func main() { + mux := http.NewServeMux() + mux.HandleFunc("POST /api/users", createUserHandler) + + http.ListenAndServe(":8080", mux) +} +``` + + +## Middleware + +### Logging, Authentication, CORS + + +```java !! java +// Java: Spring Boot filters/interceptors +import org.springframework.web.filter.*; +import javax.servlet.*; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +@Component +public class LoggingFilter extends OncePerRequestFilter { + @Override + protected void doFilterInternal( + HttpServletRequest request, + HttpServletResponse response, + FilterChain filterChain + ) throws ServletException, IOException { + + System.out.println(request.getMethod() + " " + request.getRequestURI()); + + long start = System.currentTimeMillis(); + + filterChain.doFilter(request, response); + + long duration = System.currentTimeMillis() - start; + response.setHeader("X-Response-Time", String.valueOf(duration)); + } +} + +@Component +public class CorsFilter implements Filter { + @Override + public void doFilter( + ServletRequest request, + ServletResponse response, + FilterChain chain + ) throws IOException, ServletException { + + HttpServletResponse httpResponse = (HttpServletResponse) response; + httpResponse.setHeader("Access-Control-Allow-Origin", "*"); + httpResponse.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE"); + chain.doFilter(request, response); + } +} +``` + +```go !! go +// Go: Middleware functions +package main + +import ( + "log" + "net/http" + "time" +) + +// Logging middleware +func loggingMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + start := time.Now() + + log.Printf("%s %s", r.Method, r.URL.Path) + + // Call next handler + next.ServeHTTP(w, r) + + duration := time.Since(start) + w.Header().Set("X-Response-Time", duration.String()) + }) +} + +// CORS middleware +func corsMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Access-Control-Allow-Origin", "*") + w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") + w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization") + + if r.Method == "OPTIONS" { + w.WriteHeader(http.StatusOK) + return + } + + next.ServeHTTP(w, r) + }) +} + +// Authentication middleware +func authMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + token := r.Header.Get("Authorization") + + if token == "" { + http.Error(w, "Unauthorized", http.StatusUnauthorized) + return + } + + // Validate token (simplified) + if token != "Bearer valid-token" { + http.Error(w, "Invalid token", http.StatusUnauthorized) + return + } + + next.ServeHTTP(w, r) + }) +} + +func main() { + mux := http.NewServeMux() + + mux.HandleFunc("GET /public", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, "Public endpoint") + }) + + mux.HandleFunc("GET /protected", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, "Protected endpoint") + }) + + // Apply middleware + handler := loggingMiddleware(corsMiddleware(mux)) + + fmt.Println("Server starting on :8080") + http.ListenAndServe(":8080", handler) +} +``` + + +## Go Web Frameworks + +### Using Gin Framework + + +```java !! java +// Java: Spring Boot REST controller +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/products") +public class ProductController { + + @Autowired + private ProductService productService; + + @GetMapping + public List getAllProducts() { + return productService.findAll(); + } + + @GetMapping("/{id}") + public Product getProduct(@PathVariable Long id) { + return productService.findById(id); + } + + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + public Product createProduct(@RequestBody Product product) { + return productService.save(product); + } + + @PutMapping("/{id}") + public Product updateProduct( + @PathVariable Long id, + @RequestBody Product product + ) { + return productService.update(id, product); + } + + @DeleteMapping("/{id}") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void deleteProduct(@PathVariable Long id) { + productService.delete(id); + } +} +``` + +```go !! go +// Go: Gin framework +package main + +import ( + "net/http" + + "github.com/gin-gonic/gin" +) + +type Product struct { + ID int `json:"id"` + Name string `json:"name"` + Price float64 `json:"price"` +} + +var products = []Product{ + {ID: 1, Name: "Laptop", Price: 999.99}, + {ID: 2, Name: "Mouse", Price: 29.99}, +} + +func getAllProducts(c *gin.Context) { + c.JSON(http.StatusOK, products) +} + +func getProduct(c *gin.Context) { + id := c.Param("id") + + for _, product := range products { + if product.ID == atoi(id) { + c.JSON(http.StatusOK, product) + return + } + } + + c.JSON(http.StatusNotFound, gin.H{"error": "Product not found"}) +} + +func createProduct(c *gin.Context) { + var newProduct Product + + if err := c.ShouldBindJSON(&newProduct); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + newProduct.ID = len(products) + 1 + products = append(products, newProduct) + + c.JSON(http.StatusCreated, newProduct) +} + +func main() { + r := gin.Default() + + // Middleware + r.Use(corsMiddleware()) + + // Routes + api := r.Group("/api/products") + { + api.GET("", getAllProducts) + api.GET("/:id", getProduct) + api.POST("", createProduct) + api.PUT("/:id", updateProduct) + api.DELETE("/:id", deleteProduct) + } + + r.Run(":8080") +} +``` + + +### Using Echo Framework + + +```go !! go +// Go: Echo framework +package main + +import ( + "net/http" + + "github.com/labstack/echo/v4" + "github.com/labstack/echo/v4/middleware" +) + +type User struct { + ID int `json:"id"` + Name string `json:"name"` + Email string `json:"email"` +} + +func main() { + e := echo.New() + + // Middleware + e.Use(middleware.Logger()) + e.Use(middleware.Recover()) + e.Use(middleware.CORS()) + + // Routes + e.GET("/users", getUsers) + e.GET("/users/:id", getUser) + e.POST("/users", createUser) + e.PUT("/users/:id", updateUser) + e.DELETE("/users/:id", deleteUser) + + e.Logger.Fatal(e.Start(":8080")) +} + +func getUsers(c echo.Context) error { + users := []User{ + {ID: 1, Name: "Alice", Email: "alice@example.com"}, + } + return c.JSON(http.StatusOK, users) +} + +func getUser(c echo.Context) error { + id := c.Param("id") + user := User{ID: atoi(id), Name: "Alice"} + return c.JSON(http.StatusOK, user) +} + +func createUser(c echo.Context) error { + u := new(User) + if err := c.Bind(u); err != nil { + return err + } + + u.ID = 1 + return c.JSON(http.StatusCreated, u) +} +``` + + +## Complete REST API Example + +### Building a Complete API + + +```java !! java +// Java: Complete Spring Boot REST API +import org.springframework.boot.*; +import org.springframework.web.bind.annotation.*; +import javax.persistence.*; +import java.util.*; + +@Entity +class Todo { + @Id + @GeneratedValue + private Long id; + private String title; + private boolean completed; + + // Getters and setters +} + +@RestController +@RequestMapping("/api/todos") +public class TodoController { + + @Autowired + private TodoRepository todoRepository; + + @GetMapping + public List getAllTodos() { + return todoRepository.findAll(); + } + + @PostMapping + public Todo createTodo(@RequestBody Todo todo) { + return todoRepository.save(todo); + } + + @PutMapping("/{id}") + public Todo updateTodo(@PathVariable Long id, @RequestBody Todo todo) { + todo.setId(id); + return todoRepository.save(todo); + } + + @DeleteMapping("/{id}") + public void deleteTodo(@PathVariable Long id) { + todoRepository.deleteById(id); + } +} +``` + +```go !! go +// Go: Complete REST API with net/http +package main + +import ( + "encoding/json" + "net/http" + "sync" +) + +type Todo struct { + ID int `json:"id"` + Title string `json:"title"` + Completed bool `json:"completed"` +} + +type TodoStore struct { + mu sync.RWMutex + todos map[int]*Todo + nextID int +} + +func NewTodoStore() *TodoStore { + return &TodoStore{ + todos: make(map[int]*Todo), + nextID: 1, + } +} + +func (s *TodoStore) GetAll() []*Todo { + s.mu.RLock() + defer s.mu.RUnlock() + + todos := make([]*Todo, 0, len(s.todos)) + for _, todo := range s.todos { + todos = append(todos, todo) + } + return todos +} + +func (s *TodoStore) Create(title string) *Todo { + s.mu.Lock() + defer s.mu.Unlock() + + todo := &Todo{ + ID: s.nextID, + Title: title, + Completed: false, + } + s.todos[todo.ID] = todo + s.nextID++ + + return todo +} + +func (s *TodoStore) Update(id int, title string, completed bool) *Todo { + s.mu.Lock() + defer s.mu.Unlock() + + todo := s.todos[id] + if todo == nil { + return nil + } + + todo.Title = title + todo.Completed = completed + return todo +} + +func (s *TodoStore) Delete(id int) bool { + s.mu.Lock() + defer s.mu.Unlock() + + if _, exists := s.todos[id]; !exists { + return false + } + + delete(s.todos, id) + return true +} + +var store = NewTodoStore() + +func getAllTodosHandler(w http.ResponseWriter, r *http.Request) { + todos := store.GetAll() + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(todos) +} + +func createTodoHandler(w http.ResponseWriter, r *http.Request) { + var req struct { + Title string `json:"title"` + } + + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + todo := store.Create(req.Title) + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + json.NewEncoder(w).Encode(todo) +} + +func main() { + mux := http.NewServeMux() + + mux.HandleFunc("GET /api/todos", getAllTodosHandler) + mux.HandleFunc("POST /api/todos", createTodoHandler) + + fmt.Println("Server starting on :8080") + http.ListenAndServe(":8080", mux) +} +``` + + +## Practice Questions + +1. **Simplicity**: What makes Go's web development approach simpler than Spring Boot? + +2. **Dependencies**: Why might you choose Go's standard library over a framework like Gin? + +3. **Performance**: How does Go's concurrency model benefit web applications? + +4. **Middleware**: How does Go's middleware pattern compare to Spring's filters and interceptors? + +## Project Ideas + +1. **RESTful Blog API**: Build a complete blog API with posts, comments, and tags + +2. **Task Management System**: Create a task manager with CRUD operations and authentication + +3. **Real-time Chat**: Implement a WebSocket chat server + +4. **File Upload Service**: Build a file upload and download service + +5. **URL Shortener**: Create a URL shortening service with analytics + +## Next Steps + +Now that you understand web development in Go: + +- **Next Module**: Learn about **Testing** in Go +- **Practice**: Build RESTful APIs using net/http and frameworks +- **Explore**: Study Go's template system for HTML rendering +- **Deploy**: Learn to deploy Go web applications + +## Summary + +**Go vs Spring Boot for Web Development:** +- Go requires less boilerplate and configuration +- Standard library (net/http) is production-ready +- Simpler deployment (single binary) +- Better performance and lower memory usage +- Concurrency built into the language + +**Key Takeaway**: Go's approach to web development prioritizes simplicity and performance over framework magic. The standard library's net/http package provides everything you need to build production web services, while frameworks like Gin and Echo offer additional features when needed. diff --git a/content/docs/java2go/module-08-web-development.zh-cn.mdx b/content/docs/java2go/module-08-web-development.zh-cn.mdx new file mode 100644 index 0000000..afcca62 --- /dev/null +++ b/content/docs/java2go/module-08-web-development.zh-cn.mdx @@ -0,0 +1,355 @@ +--- +title: "模块 08:Web 开发 - 构建 HTTP 服务器" +description: "使用 net/http 掌握 Go Web 开发。与 Java/Spring Boot 比较,学习路由、JSON 处理、中间件和流行的 Go 框架。" +--- + +# 模块 08:Web 开发 - 构建 HTTP 服务器 + +在本模块中,你将学习如何使用 **net/http** 包构建 Go Web 应用程序。你将看到 Go 的 Web 开发方法与 Java 的 Spring Boot 有何不同,通常需要更少的样板代码和依赖。 + +## 学习目标 + +完成本模块后,你将能够: +- 使用 net/http 创建 HTTP 服务器 +- 实现路由和处理程序 +- 处理 JSON 请求和响应 +- 创建中间件 +- 将 Go 的方法与 Spring Boot 进行比较 +- 使用流行的 Go Web 框架(Gin、Echo) +- 构建 RESTful API +- 实现 WebSocket 支持 +- 使用模板渲染 + +## 背景:Go vs Spring Boot + +### 哲学差异 + +**Java/Spring Boot:** +- 具有许多抽象的重量级框架 +- 注解驱动的配置 +- 依赖注入容器 +- 自动配置和魔法 +- 大内存占用 +- 较长的启动时间 + +**Go/net/http:** +- 最小标准库方法 +- 显式代码优于配置 +- 没有内置依赖注入 +- 简单且可预测 +- 小内存占用 +- 快速启动和执行 + + +```java !! java +// Java: Spring Boot REST 控制器 +import org.springframework.boot.*; +import org.springframework.web.bind.annotation.*; + +@RestController +@SpringBootApplication +public class HelloApplication { + @GetMapping("/hello") + public String hello() { + return "Hello, World!"; + } + + public static void main(String[] args) { + SpringApplication.run(HelloApplication.class, args); + } +} +``` + +```go !! go +// Go: 标准库 HTTP 服务器 +package main + +import ( + "fmt" + "net/http" +) + +func helloHandler(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, "Hello, World!") +} + +func main() { + mux := http.NewServeMux() + mux.HandleFunc("GET /hello", helloHandler) + + fmt.Println("Server starting on :8080") + http.ListenAndServe(":8080", mux) +} +``` + + +## 基本 HTTP 服务器 + + +```java !! java +// Java: 基本 Spring Boot 应用 +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api") +public class BasicController { + @GetMapping("/ping") + public String ping() { + return "pong"; + } + + @GetMapping("/health") + public Map health() { + Map status = new HashMap<>(); + status.put("status", "UP"); + return status; + } +} +``` + +```go !! go +// Go: 基本 HTTP 服务器 +package main + +import ( + "encoding/json" + "net/http" +) + +func pingHandler(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, "pong") +} + +func healthHandler(w http.ResponseWriter, r *http.Request) { + response := map[string]string{"status": "UP"} + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(response) +} + +func main() { + mux := http.NewServeMux() + mux.HandleFunc("GET /api/ping", pingHandler) + mux.HandleFunc("GET /api/health", healthHandler) + + http.ListenAndServe(":8080", mux) +} +``` + + +## 路由和处理程序 + + +```java !! java +// Java: Spring Boot 路由 +@RestController +@RequestMapping("/users") +public class UserController { + @GetMapping + public List getUsers() { + return userService.getUsers(); + } + + @GetMapping("/{id}") + public User getUser(@PathVariable Long id) { + return userService.getById(id); + } + + @PostMapping + public User createUser(@RequestBody User user) { + return userService.save(user); + } +} +``` + +```go !! go +// Go: HTTP 路由 +package main + +import ( + "encoding/json" + "net/http" +) + +type User struct { + ID int `json:"id"` + Name string `json:"name"` + Email string `json:"email"` +} + +func getUsersHandler(w http.ResponseWriter, r *http.Request) { + users := []User{ + {ID: 1, Name: "Alice", Email: "alice@example.com"}, + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(users) +} + +func main() { + mux := http.NewServeMux() + mux.HandleFunc("GET /users", getUsersHandler) + http.ListenAndServe(":8080", mux) +} +``` + + +## JSON 处理 + + +```java !! java +// Java: JSON with Spring Boot +@PostMapping("/users") +public ResponseEntity createUser(@RequestBody CreateUserRequest request) { + User user = new User(); + user.setName(request.getName()); + user.setEmail(request.getEmail()); + + return ResponseEntity.status(HttpStatus.CREATED).body(user); +} +``` + +```go !! go +// Go: JSON 处理 +func createUserHandler(w http.ResponseWriter, r *http.Request) { + var req CreateUserRequest + + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + user := User{ID: 1, Name: req.Name, Email: req.Email} + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + json.NewEncoder(w).Encode(user) +} +``` + + +## 中间件 + + +```go !! go +// Go: 中间件函数 +package main + +import ( + "log" + "net/http" + "time" +) + +// 日志中间件 +func loggingMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + start := time.Now() + log.Printf("%s %s", r.Method, r.URL.Path) + + next.ServeHTTP(w, r) + + duration := time.Since(start) + w.Header().Set("X-Response-Time", duration.String()) + }) +} + +// CORS 中间件 +func corsMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Access-Control-Allow-Origin", "*") + w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE") + + if r.Method == "OPTIONS" { + w.WriteHeader(http.StatusOK) + return + } + + next.ServeHTTP(w, r) + }) +} + +func main() { + mux := http.NewServeMux() + mux.HandleFunc("GET /public", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, "Public endpoint") + }) + + handler := loggingMiddleware(corsMiddleware(mux)) + http.ListenAndServe(":8080", handler) +} +``` + + +## Go Web 框架 + +### 使用 Gin 框架 + + +```go !! go +// Go: Gin 框架 +package main + +import ( + "net/http" + + "github.com/gin-gonic/gin" +) + +type Product struct { + ID int `json:"id"` + Name string `json:"name"` + Price float64 `json:"price"` +} + +func getAllProducts(c *gin.Context) { + products := []Product{ + {ID: 1, Name: "Laptop", Price: 999.99}, + } + c.JSON(http.StatusOK, products) +} + +func main() { + r := gin.Default() + + api := r.Group("/api/products") + { + api.GET("", getAllProducts) + api.POST "", createProduct) + } + + r.Run(":8080") +} +``` + + +## 练习题 + +1. **简洁性**:是什么让 Go 的 Web 开发方法比 Spring Boot 更简单? + +2. **依赖**:为什么你可能选择 Go 的标准库而不是像 Gin 这样的框架? + +3. **性能**:Go 的并发模型如何使 Web 应用受益? + +## 项目想法 + +1. **RESTful Blog API**:构建完整的博客 API +2. **任务管理系统**:创建任务管理器 +3. **实时聊天**:实现 WebSocket 聊天服务器 + +## 下一步 + +现在你已经理解了 Go 的 Web 开发: + +- **下一模块**:学习 Go 中的 **测试** +- **练习**:使用 net/http 和框架构建 RESTful API + +## 总结 + +**Go vs Spring Boot 用于 Web 开发:** +- Go 需要更少的样板代码和配置 +- 标准库 (net/http) 是生产就绪的 +- 更简单的部署(单个二进制文件) +- 更好的性能和更低的内存使用 +- 并发内置于语言中 + +**关键要点**:Go 的 Web 开发方法优先考虑简单性和性能,而不是框架魔法。 diff --git a/content/docs/java2go/module-08-web-development.zh-tw.mdx b/content/docs/java2go/module-08-web-development.zh-tw.mdx new file mode 100644 index 0000000..d79ff64 --- /dev/null +++ b/content/docs/java2go/module-08-web-development.zh-tw.mdx @@ -0,0 +1,278 @@ +--- +title: "模組 08:Web 開發 - 建構 HTTP 伺服器" +description: "使用 net/http 掌握 Go Web 開發。與 Java/Spring Boot 比較,學習路由、JSON 處理、中介軟體和流行的 Go 框架。" +--- + +# 模組 08:Web 開發 - 建構 HTTP 伺服器 + +在本模組中,你將學習如何使用 **net/http** 套件建構 Go Web 應用程式。你將看到 Go 的 Web 開發方法與 Java 的 Spring Boot 有何不同,通常需要更少的樣板程式碼和相依性。 + +## 學習目標 + +完成本模組後,你將能夠: +- 使用 net/http 建立 HTTP 伺服器 +- 實作路由和處理程式 +- 處理 JSON 請求和回應 +- 建立中介軟體 +- 將 Go 的方法與 Spring Boot 進行比較 +- 使用流行的 Go Web 框架(Gin、Echo) +- 建構 RESTful API + +## 背景:Go vs Spring Boot + +### 哲學差異 + +**Java/Spring Boot:** +- 具有許多抽象的重量級框架 +- 註解驅動的設定 +- 相依性注入容器 +- 自動設定和魔法 +- 大記憶體佔用 + +**Go/net/http:** +- 最小標準庫方法 +- 顯式程式碼優於設定 +- 沒有內建相依性注入 +- 簡單且可預測 +- 小記憶體佔用 + + +```java !! java +// Java: Spring Boot REST 控制器 +import org.springframework.web.bind.annotation.*; + +@RestController +public class HelloApplication { + @GetMapping("/hello") + public String hello() { + return "Hello, World!"; + } +} +``` + +```go !! go +// Go: 標準庫 HTTP 伺服器 +package main + +import ( + "fmt" + "net/http" +) + +func helloHandler(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, "Hello, World!") +} + +func main() { + mux := http.NewServeMux() + mux.HandleFunc("GET /hello", helloHandler) + http.ListenAndServe(":8080", mux) +} +``` + + +## 基本 HTTP 伺服器 + + +```java !! java +// Java: 基本 Spring Boot 應用 +@RestController +@RequestMapping("/api") +public class BasicController { + @GetMapping("/ping") + public String ping() { + return "pong"; + } +} +``` + +```go !! go +// Go: 基本 HTTP 伺服器 +package main + +import ( + "encoding/json" + "net/http" +) + +func pingHandler(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, "pong") +} + +func healthHandler(w http.ResponseWriter, r *http.Request) { + response := map[string]string{"status": "UP"} + json.NewEncoder(w).Encode(response) +} + +func main() { + mux := http.NewServeMux() + mux.HandleFunc("GET /api/ping", pingHandler) + http.ListenAndServe(":8080", mux) +} +``` + + +## 路由和處理程式 + + +```java !! java +// Java: Spring Boot 路由 +@RestController +@RequestMapping("/users") +public class UserController { + @GetMapping + public List getUsers() { + return userService.getUsers(); + } +} +``` + +```go !! go +// Go: HTTP 路由 +package main + +import ( + "encoding/json" + "net/http" +) + +type User struct { + ID int `json:"id"` + Name string `json:"name"` + Email string `json:"email"` +} + +func getUsersHandler(w http.ResponseWriter, r *http.Request) { + users := []User{ + {ID: 1, Name: "Alice", Email: "alice@example.com"}, + } + json.NewEncoder(w).Encode(users) +} + +func main() { + mux := http.NewServeMux() + mux.HandleFunc("GET /users", getUsersHandler) + http.ListenAndServe(":8080", mux) +} +``` + + +## JSON 處理 + + +```java !! java +// Java: JSON with Spring Boot +@PostMapping("/users") +public ResponseEntity createUser(@RequestBody CreateUserRequest request) { + User user = new User(); + user.setName(request.getName()); + return ResponseEntity.status(HttpStatus.CREATED).body(user); +} +``` + +```go !! go +// Go: JSON 處理 +func createUserHandler(w http.ResponseWriter, r *http.Request) { + var req CreateUserRequest + json.NewDecoder(r.Body).Decode(&req) + + user := User{ID: 1, Name: req.Name} + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + json.NewEncoder(w).Encode(user) +} +``` + + +## 中介軟體 + + +```go !! go +// Go: 中介軟體函式 +package main + +import ( + "log" + "net/http" +) + +// 日誌中介軟體 +func loggingMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + log.Printf("%s %s", r.Method, r.URL.Path) + next.ServeHTTP(w, r) + }) +} + +func main() { + mux := http.NewServeMux() + handler := loggingMiddleware(mux) + http.ListenAndServe(":8080", handler) +} +``` + + +## Go Web 框架 + +### 使用 Gin 框架 + + +```go !! go +// Go: Gin 框架 +package main + +import ( + "github.com/gin-gonic/gin" +) + +type Product struct { + ID int `json:"id"` + Name string `json:"name"` + Price float64 `json:"price"` +} + +func getAllProducts(c *gin.Context) { + products := []Product{{ID: 1, Name: "Laptop", Price: 999.99}} + c.JSON(http.StatusOK, products) +} + +func main() { + r := gin.Default() + api := r.Group("/api/products") + { + api.GET("", getAllProducts) + } + r.Run(":8080") +} +``` + + +## 練習題 + +1. **簡潔性**:是什麼讓 Go 的 Web 開發方法比 Spring Boot 更簡單? + +2. **相依性**:為什麼你可能選擇 Go 的標準庫而不是像 Gin 這樣的框架? + +## 專案想法 + +1. **RESTful Blog API**:建構完整的部落格 API +2. **任務管理系統**:建立任務管理器 +3. **即時聊天**:實作 WebSocket 聊天伺服器 + +## 下一步 + +現在你已經理解了 Go 的 Web 開發: + +- **下一模組**:學習 Go 中的 **測試** +- **練習**:使用 net/http 和框架建構 RESTful API + +## 總結 + +**Go vs Spring Boot 用於 Web 開發:** +- Go 需要更少的樣板程式碼和設定 +- 標準庫 (net/http) 是生產就緒的 +- 更簡單的部署(單個二進位檔案) +- 更好的效能和更低的記憶體使用 + +**關鍵要點**:Go 的 Web 開發方法優先考慮簡單性和效能,而不是框架魔法。 diff --git a/content/docs/java2go/module-09-testing.mdx b/content/docs/java2go/module-09-testing.mdx new file mode 100644 index 0000000..b2d3b89 --- /dev/null +++ b/content/docs/java2go/module-09-testing.mdx @@ -0,0 +1,965 @@ +--- +title: "Module 09: Testing - Table-Driven Tests and Benchmarking" +description: "Master Go's testing framework. Learn table-driven tests, benchmarking, test coverage, and compare with JUnit and Mockito." +--- + +# Module 09: Testing - Table-Driven Tests and Benchmarking + +In this module, you'll learn how Go approaches **testing** differently from Java. Go's testing philosophy emphasizes simplicity, table-driven tests, and built-in benchmarking - all without requiring external frameworks. + +## Learning Objectives + +By the end of this module, you'll be able to: +- Write tests using Go's testing package +- Create table-driven tests +- Measure test coverage +- Write benchmarks +- Use test assertions +- Mock dependencies with interfaces +- Organize tests with _test.go files +- Run tests with `go test` +- Compare Go's approach with JUnit and Mockito + +## Background: JUnit vs Go Testing + +### The Philosophy Difference + +**Java (JUnit + Mockito):** +- Separate testing framework +- Annotation-based (@Test, @Before, @Mock) +- External mocking framework needed +- Complex setup with @RunWith +- Heavy use of reflection +- Verbose assertion syntax + +**Go (testing package):** +- Testing built into standard library +- Convention-based (_test.go files) +- Mocking with interfaces (no framework needed) +- Simple function-based tests +- No reflection required +- Concise assertions + + +```java !! java +// Java: JUnit test +import org.junit.jupiter.api.*; +import static org.junit.jupiter.api.Assertions.*; + +class CalculatorTest { + private Calculator calculator; + + @BeforeEach + void setUp() { + calculator = new Calculator(); + } + + @Test + void testAdd() { + int result = calculator.add(2, 3); + assertEquals(5, result); + } + + @Test + void testSubtract() { + int result = calculator.subtract(5, 3); + assertEquals(2, result); + } + + @Test + void testDivideByZero() { + assertThrows(ArithmeticException.class, () -> { + calculator.divide(10, 0); + }); + } +} +``` + +```go !! go +// Go: Test with testing package +package main + +import ( + "testing" +) + +func TestAdd(t *testing.T) { + result := Add(2, 3) + if result != 5 { + t.Errorf("Add(2, 3) = %d; want 5", result) + } +} + +func TestSubtract(t *testing.T) { + result := Subtract(5, 3) + if result != 2 { + t.Errorf("Subtract(5, 3) = %d; want 2", result) + } +} + +func TestDivideByZero(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Errorf("Divide did not panic") + } + }() + + Divide(10, 0) +} +``` + + +## Table-Driven Tests + +### Testing Multiple Scenarios + + +```java !! java +// Java: Parameterized tests +import org.junit.jupiter.params.*; +import org.junit.jupiter.params.provider.*; + +@ParameterizedTest +@CsvSource({ + "2, 3, 5", + "0, 0, 0", + "-1, 1, 0", + "100, 200, 300" +}) +void testAdd(int a, int b, int expected) { + Calculator calc = new Calculator(); + assertEquals(expected, calc.add(a, b)); +} + +// Or with @MethodSource +@ParameterizedTest +@MethodSource("provideTestCases") +void testAdd(TestCase testCase) { + assertEquals(testCase.expected, + calculator.add(testCase.a, testCase.b)); +} + +private static Stream provideTestCases() { + return Stream.of( + new TestCase(2, 3, 5), + new TestCase(0, 0, 0), + new TestCase(-1, 1, 0) + ); +} +``` + +```go !! go +// Go: Table-driven tests (very idiomatic) +package main + +import ( + "testing" +) + +func TestAdd(t *testing.T) { + tests := []struct { + name string + a, b int + expected int + }{ + {"positive numbers", 2, 3, 5}, + {"zeros", 0, 0, 0}, + {"negative and positive", -1, 1, 0}, + {"large numbers", 100, 200, 300}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := Add(tt.a, tt.b) + if result != tt.expected { + t.Errorf("Add(%d, %d) = %d; want %d", + tt.a, tt.b, result, tt.expected) + } + }) + } +} + +// Even simpler for basic cases +func TestMultiply(t *testing.T) { + tests := []struct { + a, b int + expected int + }{ + {2, 3, 6}, + {0, 5, 0}, + {-2, 3, -6}, + } + + for _, tt := range tests { + got := Multiply(tt.a, tt.b) + if got != tt.expected { + t.Errorf("Multiply(%d, %d) = %d; want %d", + tt.a, tt.b, got, tt.expected) + } + } +} +``` + + +## Test Assertions + +### Comparing Assertion Styles + + +```java !! java +// Java: JUnit assertions +import static org.junit.jupiter.api.Assertions.*; + +@Test +void testAssertions() { + // Equality + assertEquals(4, calculator.add(2, 2)); + + // Not equals + assertNotEquals(5, calculator.add(2, 2)); + + // True/False + assertTrue(isValid("input")); + assertFalse(isValid("")); + + // Null checks + assertNull(getNullValue()); + assertNotNull(getValue()); + + // Same instance + Object obj = new Object(); + assertSame(obj, obj); + + // Arrays + assertArrayEquals(new int[]{1, 2}, new int[]{1, 2}); + + // Throws + assertThrows(IllegalArgumentException.class, + () -> validator.validate(null)); + + // Timeout + assertTimeout(Duration.ofSeconds(2), + () -> longRunningTask()); +} +``` + +```go !! go +// Go: Simple and direct assertions +package main + +import ( + "reflect" + "testing" + "time" +) + +func TestAssertions(t *testing.T) { + // Equality + result := Add(2, 2) + if result != 4 { + t.Errorf("expected 4, got %d", result) + } + + // True/False + if !isValid("input") { + t.Error("expected true") + } + + if isValid("") { + t.Error("expected false") + } + + // Nil checks + if getValue() == nil { + t.Error("expected non-nil") + } + + // Deep equality for slices/maps + expected := []int{1, 2, 3} + got := []int{1, 2, 3} + if !reflect.DeepEqual(expected, got) { + t.Errorf("expected %v, got %v", expected, got) + } + + // Testing for panic + t.Run("panic test", func(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Error("expected panic") + } + }() + panicFunc() + }) + + // Timeout + done := make(chan bool) + go func() { + longRunningTask() + done <- true + }() + + select { + case <-done: + // Success + case <-time.After(2 * time.Second): + t.Error("timeout") + } +} +``` + + +## Test Organization + +### Test File Structure + + +```java !! java +// Java: Test class structure +// File: src/test/java/com/example/CalculatorTest.java + +package com.example; + +import org.junit.jupiter.api.*; +import static org.junit.jupiter.api.Assertions.*; + +class CalculatorTest { + private Calculator calculator; + + @BeforeAll + static void setUpClass() { + // Runs once before all tests + } + + @BeforeEach + void setUp() { + // Runs before each test + calculator = new Calculator(); + } + + @Test + void testAdd() { + } + + @AfterEach + void tearDown() { + // Runs after each test + } + + @AfterAll + static void tearDownClass() { + // Runs once after all tests + } +} + +// Nested tests +class CalculatorTest { + @Nested + @DisplayName("Addition Tests") + class AdditionTests { + @Test + void testPositiveNumbers() { + } + + @Test + void testNegativeNumbers() { + } + } +} +``` + +```go !! go +// Go: Test file organization +// File: calculator_test.go (same package as calculator.go) + +package main + +import ( + "testing" +) + +// Setup and teardown using helper functions +func setup(t *testing.T) *Calculator { + t.Helper() + return &Calculator{} +} + +func TestMain(m *testing.M) { + // Setup before all tests + println("Setting up tests") + + // Run tests + code := m.Run() + + // Teardown after all tests + println("Tearing down tests") + + // Exit with test result code + os.Exit(code) +} + +// Test with setup +func TestWithSetup(t *testing.T) { + calc := setup(t) + + result := calc.Add(2, 3) + if result != 5 { + t.Errorf("expected 5, got %d", result) + } +} + +// Subtests for grouping +func TestCalculator(t *testing.T) { + calc := &Calculator{} + + t.Run("Addition", func(t *testing.T) { + t.Run("Positive numbers", func(t *testing.T) { + if calc.Add(2, 3) != 5 { + t.Error("failed") + } + }) + + t.Run("Negative numbers", func(t *testing.T) { + if calc.Add(-2, -3) != -5 { + t.Error("failed") + } + }) + }) + + t.Run("Subtraction", func(t *testing.T) { + if calc.Subtract(5, 3) != 2 { + t.Error("failed") + } + }) +} +``` + + +## Mocking with Interfaces + +### Dependency Injection for Testing + + +```java !! java +// Java: Mockito for mocking +import org.mockito.*; +import static org.mockito.Mockito.*; + +interface Repository { + User findById(Long id); + void save(User user); +} + +class UserService { + private final Repository repository; + + @Autowired + UserService(Repository repository) { + this.repository = repository; + } + + public User getUser(Long id) { + return repository.findById(id); + } +} + +class UserServiceTest { + @Mock + private Repository repository; + + @InjectMocks + private UserService userService; + + @Test + void testGetUser() { + // Arrange + User expectedUser = new User(1L, "Alice"); + when(repository.findById(1L)).thenReturn(expectedUser); + + // Act + User user = userService.getUser(1L); + + // Assert + assertNotNull(user); + assertEquals("Alice", user.getName()); + + // Verify interaction + verify(repository).findById(1L); + verify(repository, times(1)).findById(1L); + verifyNoMoreInteractions(repository); + } + + @Test + void testNotFound() { + when(repository.findById(999L)).thenReturn(null); + + User user = userService.getUser(999L); + + assertNull(user); + verify(repository).findById(999L); + } +} +``` + +```go !! go +// Go: Mocking with interfaces (no framework needed!) +package main + +import ( + "testing" +) + +// Repository interface +type Repository interface { + FindById(id int) (*User, error) + Save(user *User) error +} + +// UserService +type UserService struct { + repo Repository +} + +func NewUserService(repo Repository) *UserService { + return &UserService{repo: repo} +} + +func (s *UserService) GetUser(id int) (*User, error) { + return s.repo.FindById(id) +} + +// Mock implementation for testing +type MockRepository struct { + users map[int]*User +} + +func NewMockRepository() *MockRepository { + return &MockRepository{ + users: make(map[int]*User), + } +} + +func (m *MockRepository) FindById(id int) (*User, error) { + return m.users[id], nil +} + +func (m *MockRepository) Save(user *User) error { + m.users[user.ID] = user + return nil +} + +// Test with mock +func TestUserService_GetUser(t *testing.T) { + // Setup mock + mockRepo := NewMockRepository() + mockRepo.users[1] = &User{ID: 1, Name: "Alice"} + + // Create service with mock + userService := NewUserService(mockRepo) + + // Test + user, err := userService.GetUser(1) + + // Assert + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if user == nil { + t.Fatal("expected user, got nil") + } + if user.Name != "Alice" { + t.Errorf("expected name Alice, got %s", user.Name) + } +} + +// Test not found +func TestUserService_GetUserNotFound(t *testing.T) { + mockRepo := NewMockRepository() + userService := NewUserService(mockRepo) + + user, err := userService.GetUser(999) + + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if user != nil { + t.Errorf("expected nil, got user: %v", user) + } +} +``` + + +## Benchmarking + +### Performance Testing + + +```java !! java +// Java: JMH (Java Microbenchmark Harness) +import org.openjdk.jmh.annotations.*; +import java.util.concurrent.TimeUnit; + +@BenchmarkMode(Mode.AverageTime) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@Warmup(iterations = 3, time = 1, timeUnit = TimeUnit.SECONDS) +@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) +@Fork(1) +public class CalculatorBenchmark { + + private Calculator calculator = new Calculator(); + + @Benchmark + public int benchmarkAdd() { + return calculator.add(10, 20); + } + + @Benchmark + public int benchmarkMultiply() { + return calculator.multiply(10, 20); + } + + @Benchmark + public void benchmarkStringConcat() { + String result = "Hello" + " " + "World"; + } + + @Benchmark + public void benchmarkStringBuilder() { + StringBuilder sb = new StringBuilder(); + sb.append("Hello"); + sb.append(" "); + sb.append("World"); + String result = sb.toString(); + } +} + +// Run with: java -jar target/benchmarks.jar +``` + +```go !! go +// Go: Built-in benchmarking +package main + +import ( + "fmt" + "strings" + "testing" +) + +func BenchmarkAdd(b *testing.B) { + calculator := &Calculator{} + + for i := 0; i < b.N; i++ { + calculator.Add(10, 20) + } +} + +func BenchmarkMultiply(b *testing.B) { + calculator := &Calculator{} + + for i := 0; i < b.N; i++ { + calculator.Multiply(10, 20) + } +} + +func BenchmarkStringConcat(b *testing.B) { + for i := 0; i < b.N; i++ { + result := "Hello" + " " + "World" + _ = result + } +} + +func BenchmarkStringBuilder(b *testing.B) { + for i := 0; i < b.N; i++ { + var sb strings.Builder + sb.WriteString("Hello") + sb.WriteString(" ") + sb.WriteString("World") + _ = sb.String() + } +} + +// Benchmark with different input sizes +func BenchmarkFibonacci10(b *testing.B) { + for i := 0; i < b.N; i++ { + Fibonacci(10) + } +} + +func BenchmarkFibonacci20(b *testing.B) { + for i := 0; i < b.N; i++ { + Fibonacci(20) + } +} + +// Parallel benchmark +func BenchmarkParallelAdd(b *testing.B) { + calculator := &Calculator{} + + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + calculator.Add(10, 20) + } + }) +} + +// Run with: go test -bench=. -benchmem +``` + + +## Test Coverage + +### Measuring Code Coverage + + +```java !! java +// Java: JaCoCo for code coverage +// Run with: mvn test jacoco:report + +// Or with Gradle: +// test { +// useJUnitPlatform() +// finalizedBy jacocoTestReport +// } + +// Generates HTML report in target/site/jacoco/index.html +``` + +```go !! go +// Go: Built-in coverage tool + +// Run tests with coverage: +// go test -cover + +// Coverage by function: +// go test -coverprofile=coverage.out + +// View coverage in browser: +// go tool cover -html=coverage.out + +// Show only functions below threshold: +// go test -coverprofile=coverage.out -covermode=atomic +// go tool cover -func=coverage.out + +// Example output: +// github.com/user/calculator/calculator.go:10: Add 100.0% +// github.com/user/calculator/calculator.go:15: Subtract 100.0% +// github.com/user/calculator/calculator.go:20: Multiply 66.7% + +// Package level coverage: +// go test -cover ./... + +// Set coverage threshold (fail if below): +// go test -coverprofile=coverage.out -covermode=atomic +``` + + +## Running Tests + +### Test Execution + + +```bash +# Java: Run tests with Maven/Gradle + +# Maven +mvn test +mvn test -Dtest=CalculatorTest +mvn test -Dtest=*Test + +# Gradle +./gradlew test +./gradlew test --tests CalculatorTest + +# Run specific test method +mvn test -Dtest=CalculatorTest#testAdd + +# Run with coverage +mvn test jacoco:report +``` + +```bash +# Go: Run tests with go test + +# Run all tests in current directory +go test + +# Run tests with verbose output +go test -v + +# Run specific test +go test -run TestAdd +go test -run TestAdd/positive + +# Run tests in all subdirectories +go test ./... + +# Run tests with coverage +go test -cover + +# Run benchmarks +go test -bench=. +go test -bench=BenchmarkAdd + +# Run benchmarks with memory stats +go test -bench=. -benchmem + +# Run tests and race detector +go test -race + +# Run tests with coverage profile +go test -coverprofile=coverage.out +go tool cover -html=coverage.out + +# Run specific package +go test github.com/user/project/package + +# Run tests with timeout +go test -timeout 30s +``` + + +## Best Practices + +### Testing Guidelines + + +```java !! java +// Java: Testing best practices + +// 1. Use descriptive test names +@Test +void shouldReturnTrueWhenUserExists() { +} + +// 2. Arrange-Act-Assert pattern +@Test +void testAdd() { + // Arrange + Calculator calc = new Calculator(); + + // Act + int result = calc.add(2, 3); + + // Assert + assertEquals(5, result); +} + +// 3. Test one thing per test +@Test +void testAddReturnsCorrectSum() { + // Only tests addition +} + +// 4. Use meaningful assertions +assertEquals(5, result); // Good +assertTrue(result == 5); // Less clear + +// 5. Mock dependencies appropriately +@Mock +private Repository repository; +``` + +```go !! go +// Go: Testing best practices + +// 1. Use descriptive test names (table-driven helps) +tests := []struct { + name string + input int + expected int +}{ + {"should return 5 for 2+3", 2, 3, 5}, + {"should handle zero", 0, 0, 0}, +} + +// 2. Keep tests simple and readable +func TestAdd(t *testing.T) { + result := Add(2, 3) + if result != 5 { + t.Errorf("got %d, want 5", result) + } +} + +// 3. Use table-driven tests for multiple scenarios +func TestMultiply(t *testing.T) { + tests := []struct { + a, b, expected int + }{ + {2, 3, 6}, + {0, 5, 0}, + } + for _, tt := range tests { + t.Run("", func(t *testing.T) { + if got := Multiply(tt.a, tt.b); got != tt.expected { + t.Errorf("got %d, want %d", got, tt.expected) + } + }) + } +} + +// 4. Use interfaces for mocking +type MockRepository struct { + users map[int]*User +} + +// 5. Test both success and error cases +func TestDivide(t *testing.T) { + t.Run("success", func(t *testing.T) { + result, err := Divide(10, 2) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if result != 5 { + t.Errorf("got %d, want 5", result) + } + }) + + t.Run("divide by zero", func(t *testing.T) { + _, err := Divide(10, 0) + if err == nil { + t.Error("expected error") + } + }) +} +``` + + +## Practice Questions + +1. **Table-Driven Tests**: Why are table-driven tests considered idiomatic in Go? + +2. **Mocking**: How does Go's approach to mocking with interfaces compare to Mockito? + +3. **Benchmarking**: What advantages does Go's built-in benchmarking have over JMH? + +4. **Test Organization**: How does Go's file-based test organization compare to Java's class-based approach? + +## Project Ideas + +1. **Test Coverage**: Achieve 80%+ test coverage for a Go project + +2. **Benchmark Suite**: Create comprehensive benchmarks for data structures + +3. **Mock Implementation**: Build a mock HTTP client for testing web services + +4. **Property Testing**: Implement property-based tests using Go's testing approach + +## Next Steps + +Now that you understand testing in Go: + +- **Review**: Review all previous modules +- **Practice**: Write comprehensive tests for your Go projects +- **Explore**: Study property-based testing libraries +- **Build**: Create a complete Go application with full test coverage + +## Summary + +**Go Testing vs JUnit/Mockito:** +- Built-in testing package (no external dependencies) +- Table-driven tests are idiomatic and powerful +- Interface-based mocking (no mocking framework needed) +- Simple and fast test execution +- Built-in benchmarking and coverage tools + +**Key Takeaway**: Go's testing philosophy emphasizes simplicity and convention over configuration. The testing package provides everything you need for unit testing, benchmarking, and code coverage without requiring external frameworks or complex setup. diff --git a/content/docs/java2go/module-09-testing.zh-cn.mdx b/content/docs/java2go/module-09-testing.zh-cn.mdx new file mode 100644 index 0000000..a5f7990 --- /dev/null +++ b/content/docs/java2go/module-09-testing.zh-cn.mdx @@ -0,0 +1,540 @@ +--- +title: "模块 09:测试 - 表驱动测试和基准测试" +description: "掌握 Go 的测试框架。学习表驱动测试、基准测试、测试覆盖率,并与 JUnit 和 Mockito 进行比较。" +--- + +# 模块 09:测试 - 表驱动测试和基准测试 + +在本模块中,你将学习 Go 如何以与 Java 不同的方式处理**测试**。Go 的测试哲学强调简洁性、表驱动测试和内置基准测试 - 所有这些都不需要外部框架。 + +## 学习目标 + +完成本模块后,你将能够: +- 使用 Go 的 testing 包编写测试 +- 创建表驱动测试 +- 测量测试覆盖率 +- 编写基准测试 +- 使用测试断言 +- 使用接口模拟依赖 +- 使用 _test.go 文件组织测试 +- 使用 `go test` 运行测试 +- 将 Go 的方法与 JUnit 和 Mockito 进行比较 + +## 背景:JUnit vs Go Testing + +### 哲学差异 + +**Java (JUnit + Mockito):** +- 单独的测试框架 +- 基于注解(@Test、@Before、@Mock) +- 需要外部模拟框架 +- 使用 @RunWith 进行复杂设置 +- 大量使用反射 +- 冗长的断言语法 + +**Go (testing package):** +- 测试内置于标准库中 +- 基于约定(_test.go 文件) +- 使用接口进行模拟(无需框架) +- 简单的基于函数的测试 +- 不需要反射 +- 简洁的断言 + + +```java !! java +// Java: JUnit 测试 +import org.junit.jupiter.api.*; +import static org.junit.jupiter.api.Assertions.*; + +class CalculatorTest { + private Calculator calculator; + + @BeforeEach + void setUp() { + calculator = new Calculator(); + } + + @Test + void testAdd() { + int result = calculator.add(2, 3); + assertEquals(5, result); + } +} +``` + +```go !! go +// Go: 使用 testing 包测试 +package main + +import ( + "testing" +) + +func TestAdd(t *testing.T) { + result := Add(2, 3) + if result != 5 { + t.Errorf("Add(2, 3) = %d; want 5", result) + } +} + +func TestSubtract(t *testing.T) { + result := Subtract(5, 3) + if result != 2 { + t.Errorf("Subtract(5, 3) = %d; want 2", result) + } +} +``` + + +## 表驱动测试 + +### 测试多个场景 + + +```java !! java +// Java: 参数化测试 +@ParameterizedTest +@CsvSource({ + "2, 3, 5", + "0, 0, 0", + "-1, 1, 0" +}) +void testAdd(int a, int b, int expected) { + Calculator calc = new Calculator(); + assertEquals(expected, calc.add(a, b)); +} +``` + +```go !! go +// Go: 表驱动测试(非常符合 Go 惯例) +package main + +import ( + "testing" +) + +func TestAdd(t *testing.T) { + tests := []struct { + name string + a, b int + expected int + }{ + {"positive numbers", 2, 3, 5}, + {"zeros", 0, 0, 0}, + {"negative and positive", -1, 1, 0}, + {"large numbers", 100, 200, 300}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := Add(tt.a, tt.b) + if result != tt.expected { + t.Errorf("Add(%d, %d) = %d; want %d", + tt.a, tt.b, result, tt.expected) + } + }) + } +} +``` + + +## 测试断言 + +### 比较断言风格 + + +```java !! java +// Java: JUnit 断言 +@Test +void testAssertions() { + // 相等性 + assertEquals(4, calculator.add(2, 2)); + + // True/False + assertTrue(isValid("input")); + assertFalse(isValid("")); + + // Null 检查 + assertNull(getNullValue()); + assertNotNull(getValue()); + + // 抛出异常 + assertThrows(IllegalArgumentException.class, + () -> validator.validate(null)); +} +``` + +```go !! go +// Go: 简单直接的断言 +package main + +import ( + "reflect" + "testing" +) + +func TestAssertions(t *testing.T) { + // 相等性 + result := Add(2, 2) + if result != 4 { + t.Errorf("expected 4, got %d", result) + } + + // True/False + if !isValid("input") { + t.Error("expected true") + } + + // 深度相等(用于 slices/maps) + expected := []int{1, 2, 3} + got := []int{1, 2, 3} + if !reflect.DeepEqual(expected, got) { + t.Errorf("expected %v, got %v", expected, got) + } + + // 测试 panic + t.Run("panic test", func(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Error("expected panic") + } + }() + panicFunc() + }) +} +``` + + +## 使用接口进行模拟 + +### 依赖注入用于测试 + + +```java !! java +// Java: Mockito 模拟 +import org.mockito.*; +import static org.mockito.Mockito.*; + +interface Repository { + User findById(Long id); + void save(User user); +} + +class UserServiceTest { + @Mock + private Repository repository; + + @InjectMocks + private UserService userService; + + @Test + void testGetUser() { + User expectedUser = new User(1L, "Alice"); + when(repository.findById(1L)).thenReturn(expectedUser); + + User user = userService.getUser(1L); + + assertEquals("Alice", user.getName()); + verify(repository).findById(1L); + } +} +``` + +```go !! go +// Go: 使用接口模拟(无需框架!) +package main + +import ( + "testing" +) + +// Repository 接口 +type Repository interface { + FindById(id int) (*User, error) + Save(user *User) error +} + +// UserService +type UserService struct { + repo Repository +} + +func NewUserService(repo Repository) *UserService { + return &UserService{repo: repo} +} + +// Mock 实现 +type MockRepository struct { + users map[int]*User +} + +func NewMockRepository() *MockRepository { + return &MockRepository{ + users: make(map[int]*User), + } +} + +func (m *MockRepository) FindById(id int) (*User, error) { + return m.users[id], nil +} + +// 测试 +func TestUserService_GetUser(t *testing.T) { + // 设置 mock + mockRepo := NewMockRepository() + mockRepo.users[1] = &User{ID: 1, Name: "Alice"} + + // 创建服务 + userService := NewUserService(mockRepo) + + // 测试 + user, err := userService.GetUser(1) + + // 断言 + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if user.Name != "Alice" { + t.Errorf("expected name Alice, got %s", user.Name) + } +} +``` + + +## 基准测试 + +### 性能测试 + + +```java !! java +// Java: JMH +@BenchmarkMode(Mode.AverageTime) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +public class CalculatorBenchmark { + + @Benchmark + public int benchmarkAdd() { + return calculator.add(10, 20); + } +} +``` + +```go !! go +// Go: 内置基准测试 +package main + +import ( + "testing" +) + +func BenchmarkAdd(b *testing.B) { + calculator := &Calculator{} + + for i := 0; i < b.N; i++ { + calculator.Add(10, 20) + } +} + +func BenchmarkMultiply(b *testing.B) { + calculator := &Calculator{} + + for i := 0; i < b.N; i++ { + calculator.Multiply(10, 20) + } +} + +// 并行基准测试 +func BenchmarkParallelAdd(b *testing.B) { + calculator := &Calculator{} + + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + calculator.Add(10, 20) + } + }) +} + +// 运行:go test -bench=. -benchmem +``` + + +## 测试覆盖率 + +### 测量代码覆盖率 + + +```bash +# Java: JaCoCo +mvn test jacoco:report + +# 生成 HTML 报告 +``` + +```bash +# Go: 内置覆盖率工具 + +# 运行测试并显示覆盖率 +go test -cover + +# 生成覆盖率报告 +go test -coverprofile=coverage.out + +# 在浏览器中查看 +go tool cover -html=coverage.out + +# 按函数显示覆盖率 +go test -coverprofile=coverage.out +go tool cover -func=coverage.out + +# 所有子目录的覆盖率 +go test -cover ./... +``` + + +## 运行测试 + +### 测试执行 + + +```bash +# Java: Maven/Gradle + +# Maven +mvn test +mvn test -Dtest=CalculatorTest + +# Gradle +./gradlew test +./gradlew test --tests CalculatorTest +``` + +```bash +# Go: go test + +# 当前目录的所有测试 +go test + +# 详细输出 +go test -v + +# 运行特定测试 +go test -run TestAdd + +# 所有子目录 +go test ./... + +# 带覆盖率 +go test -cover + +# 基准测试 +go test -bench=. + +# 竞态检测器 +go test -race + +# 超时 +go test -timeout 30s +``` + + +## 最佳实践 + + +```go !! go +// Go: 测试最佳实践 + +// 1. 使用描述性测试名称(表驱动有帮助) +tests := []struct { + name string + input int + expected int +}{ + {"should return 5 for 2+3", 2, 3, 5}, +} + +// 2. 保持测试简单可读 +func TestAdd(t *testing.T) { + result := Add(2, 3) + if result != 5 { + t.Errorf("got %d, want 5", result) + } +} + +// 3. 对多个场景使用表驱动测试 +func TestMultiply(t *testing.T) { + tests := []struct { + a, b, expected int + }{ + {2, 3, 6}, + {0, 5, 0}, + } + for _, tt := range tests { + t.Run("", func(t *testing.T) { + if got := Multiply(tt.a, tt.b); got != tt.expected { + t.Errorf("got %d, want %d", got, tt.expected) + } + }) + } +} + +// 4. 使用接口进行模拟 +type MockRepository struct { + users map[int]*User +} + +// 5. 测试成功和错误情况 +func TestDivide(t *testing.T) { + t.Run("success", func(t *testing.T) { + result, err := Divide(10, 2) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if result != 5 { + t.Errorf("got %d, want 5", result) + } + }) + + t.Run("divide by zero", func(t *testing.T) { + _, err := Divide(10, 0) + if err == nil { + t.Error("expected error") + } + }) +} +``` + + +## 练习题 + +1. **表驱动测试**:为什么表驱动测试被认为是 Go 的惯用方法? + +2. **模拟**:Go 的基于接口的模拟方法与 Mockito 相比如何? + +3. **基准测试**:Go 的内置基准测试与 JMH 相比有什么优势? + +## 项目想法 + +1. **测试覆盖率**:为 Go 项目实现 80%+ 的测试覆盖率 + +2. **基准测试套件**:为数据结构创建全面的基准测试 + +3. **Mock 实现**:构建模拟 HTTP 客户端用于测试 Web 服务 + +## 下一步 + +现在你已经理解了 Go 的测试: + +- **回顾**:回顾所有之前的模块 +- **练习**:为你的 Go 项目编写全面的测试 +- **探索**:研究基于属性的测试库 + +## 总结 + +**Go Testing vs JUnit/Mockito:** +- 内置测试包(无外部依赖) +- 表驱动测试是惯用且强大的 +- 基于接口的模拟(无需模拟框架) +- 简单快速的测试执行 +- 内置基准测试和覆盖率工具 + +**关键要点**:Go 的测试哲学强调简洁性和约定而非配置。testing 包为单元测试、基准测试和代码覆盖率提供了所需的一切,无需外部框架或复杂设置。 diff --git a/content/docs/java2go/module-09-testing.zh-tw.mdx b/content/docs/java2go/module-09-testing.zh-tw.mdx new file mode 100644 index 0000000..e3e4a51 --- /dev/null +++ b/content/docs/java2go/module-09-testing.zh-tw.mdx @@ -0,0 +1,346 @@ +--- +title: "模組 09:測試 - 表驅動測試和基準測試" +description: "掌握 Go 的測試框架。學習表驅動測試、基準測試、測試覆蓋率,並與 JUnit 和 Mockito 進行比較。" +--- + +# 模組 09:測試 - 表驅動測試和基準測試 + +在本模組中,你將學習 Go 如何以與 Java 不同的方式處理**測試**。Go 的測試哲學強調簡潔性、表驅動測試和內建基準測試 - 所有這些都不需要外部框架。 + +## 學習目標 + +完成本模組後,你將能夠: +- 使用 Go 的 testing 套件編寫測試 +- 建立表驅動測試 +- 測量測試覆蓋率 +- 編寫基準測試 +- 使用測試斷言 +- 使用介面模擬相依性 +- 使用 _test.go 檔案組織測試 +- 使用 `go test` 執行測試 + +## 背景:JUnit vs Go Testing + +### 哲學差異 + +**Java (JUnit + Mockito):** +- 單獨的測試框架 +- 基於註解(@Test、@Before、@Mock) +- 需要外部模擬框架 +- 使用 @RunWith 進行複雜設定 +- 大量使用反射 + +**Go (testing package):** +- 測試內建於標準庫中 +- 基於約定(_test.go 檔案) +- 使用介面進行模擬(無需框架) +- 簡單的基於函式的測試 + + +```java !! java +// Java: JUnit 測試 +import org.junit.jupiter.api.*; + +class CalculatorTest { + private Calculator calculator; + + @BeforeEach + void setUp() { + calculator = new Calculator(); + } + + @Test + void testAdd() { + int result = calculator.add(2, 3); + assertEquals(5, result); + } +} +``` + +```go !! go +// Go: 使用 testing 套件測試 +package main + +import ( + "testing" +) + +func TestAdd(t *testing.T) { + result := Add(2, 3) + if result != 5 { + t.Errorf("Add(2, 3) = %d; want 5", result) + } +} +``` + + +## 表驅動測試 + +### 測試多個場景 + + +```java !! java +// Java: 參數化測試 +@ParameterizedTest +@CsvSource({ + "2, 3, 5", + "0, 0, 0" +}) +void testAdd(int a, int b, int expected) { + assertEquals(expected, calculator.add(a, b)); +} +``` + +```go !! go +// Go: 表驅動測試(非常符合 Go 慣例) +package main + +import ( + "testing" +) + +func TestAdd(t *testing.T) { + tests := []struct { + name string + a, b int + expected int + }{ + {"positive numbers", 2, 3, 5}, + {"zeros", 0, 0, 0}, + {"large numbers", 100, 200, 300}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := Add(tt.a, tt.b) + if result != tt.expected { + t.Errorf("Add(%d, %d) = %d; want %d", + tt.a, tt.b, result, tt.expected) + } + }) + } +} +``` + + +## 使用介面進行模擬 + +### 相依性注入用於測試 + + +```java !! java +// Java: Mockito 模擬 +class UserServiceTest { + @Mock + private Repository repository; + + @InjectMocks + private UserService userService; + + @Test + void testGetUser() { + when(repository.findById(1L)).thenReturn(expectedUser); + User user = userService.getUser(1L); + assertEquals("Alice", user.getName()); + } +} +``` + +```go !! go +// Go: 使用介面模擬(無需框架!) +package main + +import ( + "testing" +) + +// Repository 介面 +type Repository interface { + FindById(id int) (*User, error) +} + +// Mock 實作 +type MockRepository struct { + users map[int]*User +} + +func NewMockRepository() *MockRepository { + return &MockRepository{ + users: make(map[int]*User), + } +} + +func (m *MockRepository) FindById(id int) (*User, error) { + return m.users[id], nil +} + +// 測試 +func TestUserService_GetUser(t *testing.T) { + mockRepo := NewMockRepository() + mockRepo.users[1] = &User{ID: 1, Name: "Alice"} + + userService := NewUserService(mockRepo) + user, err := userService.GetUser(1) + + if user.Name != "Alice" { + t.Errorf("expected name Alice, got %s", user.Name) + } +} +``` + + +## 基準測試 + +### 效能測試 + + +```go !! go +// Go: 內建基準測試 +package main + +import ( + "testing" +) + +func BenchmarkAdd(b *testing.B) { + calculator := &Calculator{} + + for i := 0; i < b.N; i++ { + calculator.Add(10, 20) + } +} + +// 並行基準測試 +func BenchmarkParallelAdd(b *testing.B) { + calculator := &Calculator{} + + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + calculator.Add(10, 20) + } + }) +} + +// 執行:go test -bench=. -benchmem +``` + + +## 測試覆蓋率 + + +```bash +# Go: 內建覆蓋率工具 + +# 執行測試並顯示覆蓋率 +go test -cover + +# 產生覆蓋率報告 +go test -coverprofile=coverage.out + +# 在瀏覽器中查看 +go tool cover -html=coverage.out + +# 所有子目錄的覆蓋率 +go test -cover ./... +``` + + +## 執行測試 + + +```bash +# Go: go test + +# 當前目錄的所有測試 +go test + +# 詳細輸出 +go test -v + +# 執行特定測試 +go test -run TestAdd + +# 帶覆蓋率 +go test -cover + +# 基準測試 +go test -bench=. + +# 競態檢測器 +go test -race +``` + + +## 最佳實踐 + + +```go !! go +// Go: 測試最佳實踐 + +// 1. 使用描述性測試名稱 +tests := []struct { + name string + input int + expected int +}{ + {"should return 5 for 2+3", 2, 3, 5}, +} + +// 2. 保持測試簡單可讀 +func TestAdd(t *testing.T) { + result := Add(2, 3) + if result != 5 { + t.Errorf("got %d, want 5", result) + } +} + +// 3. 對多個場景使用表驅動測試 +func TestMultiply(t *testing.T) { + tests := []struct { + a, b, expected int + }{ + {2, 3, 6}, + {0, 5, 0}, + } + for _, tt := range tests { + if got := Multiply(tt.a, tt.b); got != tt.expected { + t.Errorf("got %d, want %d", got, tt.expected) + } + } +} + +// 4. 使用介面進行模擬 +type MockRepository struct { + users map[int]*User +} +``` + + +## 練習題 + +1. **表驅動測試**:為什麼表驅動測試被認為是 Go 的慣用方法? + +2. **模擬**:Go 的基於介面的模擬方法與 Mockito 相比如何? + +## 專案想法 + +1. **測試覆蓋率**:為 Go 專案實作 80%+ 的測試覆蓋率 +2. **基準測試套件**:為資料結構建立全面的基準測試 + +## 下一步 + +現在你已經理解了 Go 的測試: + +- **回顧**:回顧所有之前的模組 +- **練習**:為你的 Go 專案編寫全面的測試 + +## 總結 + +**Go Testing vs JUnit/Mockito:** +- 內建測試套件(無外部相依性) +- 表驅動測試是慣用且強大的 +- 基於介面的模擬(無需模擬框架) +- 簡單快速的測試執行 +- 內建基準測試和覆蓋率工具 + +**關鍵要點**:Go 的測試哲學強調簡潔性和約定而非設定。testing 套件為單元測試、基準測試和程式碼覆蓋率提供了所需的一切,無需外部框架。 diff --git a/content/docs/java2go/module-10-common-pitfalls.mdx b/content/docs/java2go/module-10-common-pitfalls.mdx new file mode 100644 index 0000000..89f5400 --- /dev/null +++ b/content/docs/java2go/module-10-common-pitfalls.mdx @@ -0,0 +1,1013 @@ +--- +title: "Module 10: Common Pitfalls" +description: "Common mistakes Java developers make when transitioning to Go and how to avoid them" +--- + +# Module 10: Common Pitfalls + +## Learning Objectives + +By the end of this module, you will: +- Identify common mistakes Java developers make in Go +- Understand the differences between nil and null +- Avoid slice and array gotchas +- Prevent goroutine and closure pitfalls +- Handle errors properly +- Use channels correctly +- Understand interface nil confusion + +## Introduction + +Transitioning from Java to Go involves more than just learning new syntax. It requires understanding fundamental differences in how the languages handle memory, concurrency, and error handling. In this module, we'll explore the most common pitfalls Java developers encounter when writing Go code. + +## 1. Nil vs Null Confusion + +### The Problem + +In Java, `null` is a universal value that can be assigned to any non-primitive variable. Go has `nil`, but it behaves differently across types. + +### Pitfall: Not Checking Nil Pointers + + +```java !! java +// Java - NPE is a common runtime error +public class User { + private String name; + + public String getName() { + return name; // Might return null + } + + public void printNameLength() { + // NullPointerException if getName() returns null + System.out.println(getName().length()); + } +} +``` + +```go !! go +// Go - Must explicitly handle nil +type User struct { + name *string // Pointer can be nil +} + +func (u *User) Name() *string { + return u.name // Returns nil if not set +} + +func (u *User) PrintNameLength() { + if u.name == nil { + fmt.Println("Name not set") + return + } + fmt.Println(len(*u.name)) +} +``` + + +### Pitfall: Nil Interface Values + + +```java !! java +// Java - Reference comparison is straightforward +String s = null; +if (s == null) { + System.out.println("s is null"); +} + +List list = null; +if (list == null) { + System.out.println("list is null"); +} +``` + +```go !! go +// Go - Interface nil is tricky +type Printer interface { + Print() +} + +type MyPrinter struct{} + +func (p *MyPrinter) Print() { + fmt.Println("Printing") +} + +func main() { + var p Printer // p is nil + fmt.Println(p == nil) // true + + var mp *MyPrinter // mp is nil + p = mp // p contains a nil *MyPrinter + fmt.Println(p == nil) // FALSE! This is a common gotcha + + // The interface value itself is not nil, + // it holds a nil concrete value +} +``` + + +**Best Practice:** Always check both the interface and the concrete type: + +```go +if p == nil || p.(*MyPrinter) == nil { + fmt.Println("Printer is not properly initialized") +} +``` + +## 2. Slice Gotchas + +### Pitfall: Slice Reference Semantics + + +```java !! java +// Java - Arrays are passed by reference +public class SliceExample { + public static void modifyArray(int[] arr) { + arr[0] = 999; + } + + public static void main(String[] args) { + int[] arr = {1, 2, 3}; + modifyArray(arr); + System.out.println(Arrays.toString(arr)); // [999, 2, 3] + } +} +``` + +```go !! go +// Go - Slices are references to underlying arrays +func modifySlice(s []int) { + s[0] = 999 + // This modifies the original array +} + +func main() { + s := []int{1, 2, 3} + modifySlice(s) + fmt.Println(s) // [999 2 3] - modified! + + // But appending doesn't always work as expected + appendToSlice(s) + fmt.Println(s) // Still [999 2 3] - append didn't affect original +} + +func appendToSlice(s []int) { + s = append(s, 4) // This creates a new slice! +} +``` + + +### Pitfall: Slice Capacity and Reallocation + + +```java !! java +// Java - ArrayList handles capacity automatically +ArrayList list = new ArrayList<>(); +for (int i = 0; i < 1000; i++) { + list.add(i); // Capacity grows automatically +} +``` + +```go !! go +// Go - Understanding slice capacity is crucial +func main() { + s := make([]int, 3, 5) // length=3, capacity=5 + fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s) + + s = append(s, 4, 5) + fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s) + + // This append will cause reallocation + s = append(s, 6) + fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s) + // Capacity likely doubled + + // Common mistake: appending in loop without preallocation + var result []int + for i := 0; i < 100000; i++ { + result = append(result, i) // Multiple reallocations! + } + + // Better: Preallocate + result = make([]int, 0, 100000) + for i := 0; i < 100000; i++ { + result = append(result, i) // Single allocation + } +} +``` + + +### Pitfall: Slice Subtlety with Sub-slicing + + +```java !! java +// Java - SubList keeps reference to original list +List bigList = new ArrayList<>(); +for (int i = 0; i < 1000000; i++) { + bigList.add(i); +} +List small = bigList.subList(0, 10); +// bigList cannot be GC'd while small exists +``` + +```go !! go +// Go - Sub-slices hold reference to entire backing array +func main() { + bigSlice := make([]byte, 1024*1024) // 1 MB + for i := range bigSlice { + bigSlice[i] = 'x' + } + + // Only need first 10 bytes + smallSlice := bigSlice[:10] + + // bigSlice is still in memory because smallSlice + // references the same backing array! + + // Solution: copy to new slice + tinySlice := make([]byte, 10) + copy(tinySlice, bigSlice[:10]) + // Now bigSlice can be garbage collected +} +``` + + +## 3. Range Loop Variable Capture + +### Pitfall: Goroutine Closure Capture + + +```java !! java +// Java - Each iteration gets its own variable +List numbers = Arrays.asList(1, 2, 3, 4, 5); +List> futures = new ArrayList<>(); + +for (Integer number : numbers) { + Future future = executor.submit(() -> { + return number * 2; // Each closure captures its own number + }); + futures.add(future); +} + +// Results: 2, 4, 6, 8, 10 +``` + +```go !! go +// Go - WRONG! All goroutines capture the same variable +func main() { + numbers := []int{1, 2, 3, 4, 5} + + for _, number := range numbers { + go func() { + fmt.Println(number * 2) // All print 10! + }() + } + + time.Sleep(time.Second) +} + +// Correct: Pass variable as parameter +func main() { + numbers := []int{1, 2, 3, 4, 5} + + for _, number := range numbers { + go func(n int) { + fmt.Println(n * 2) // Prints: 2, 4, 6, 8, 10 + }(number) // Pass as argument + } + + time.Sleep(time.Second) +} + +// Also correct: Create new variable in loop scope +func main() { + numbers := []int{1, 2, 3, 4, 5} + + for _, number := range numbers { + number := number // Create new variable + go func() { + fmt.Println(number * 2) // Correct! + }() + } + + time.Sleep(time.Second) +} +``` + + +## 4. Error Handling Mistakes + +### Pitfall: Ignoring Errors + + +```java !! java +// Java - Exceptions can be ignored (bad practice) +public void readFile() { + try { + Files.readAllLines(Paths.get("file.txt")); + } catch (IOException e) { + // Empty catch block - silent failure + } +} +``` + +```go !! go +// Go - Errors must be explicitly handled +func readFile() error { + data, err := os.ReadFile("file.txt") + if err != nil { + return err // Must handle error + } + fmt.Println(string(data)) + return nil +} + +// WRONG: Ignoring error +func readFileBad() { + data, _ := os.ReadFile("file.txt") // Never ignore errors! + fmt.Println(string(data)) +} + +// Common pitfall: defer with error +func processFile(filename string) error { + f, err := os.Open(filename) + if err != nil { + return err + } + defer f.Close() // Can't check this error! + + // Better: Use wrapper function + defer func() { + if err := f.Close(); err != nil { + log.Printf("Error closing file: %v", err) + } + }() + + return nil +} +``` + + +### Pitfall: Wrapping Errors Incorrectly + + +```java !! java +// Java - Exception chaining +try { + // ... code that throws +} catch (IOException e) { + throw new RuntimeException("Failed to process", e); +} +``` + +```go !! go +// Go - Proper error wrapping +func processFile(path string) error { + f, err := os.Open(path) + if err != nil { + return fmt.Errorf("failed to open %s: %w", path, err) + } + defer f.Close() + + // ... processing ... + + return nil +} + +// Common mistake: Not wrapping context +func processFileBad(path string) error { + f, err := os.Open(path) + if err != nil { + return err // Lost context about which file! + } + defer f.Close() + + return nil +} +``` + + +## 5. Channel Misuse + +### Pitfall: Forgetting to Close Channels + + +```java !! java +// Java - BlockingQueue doesn't need explicit closing +BlockingQueue queue = new LinkedBlockingQueue<>(); +``` + +```go !! go +// Go - Channels must be closed by sender +func producer(ch chan<- int) { + for i := 0; i < 10; i++ { + ch <- i + } + close(ch) // CRITICAL: Must close to signal completion +} + +func consumer(ch <-chan int) { + for value := range ch { // Loop terminates when channel closed + fmt.Println(value) + } +} + +// WRONG: Consumer closing channel +func consumerBad(ch <-chan int) { + for value := range ch { + fmt.Println(value) + } + close(ch) // PANIC! Cannot close receive-only channel +} +``` + + +### Pitfall: Deadlock with Unbuffered Channels + + +```java !! java +// Java - Synchronized blocks can deadlock +public synchronized void method1() { + // Holding lock + method2(); // Needs same lock - works in Java +} + +public synchronized void method2() { + // Reentrant lock - same thread can acquire +} +``` + +```go !! go +// Go - Deadlock with goroutine communication +func main() { + ch := make(chan int) // Unbuffered + + // WRONG: Deadlock! + ch <- 42 // Blocking - no receiver + // Fatal error: all goroutines are asleep - deadlock! +} + +// Correct: Have receiver ready +func main() { + ch := make(chan int) + + go func() { + ch <- 42 + }() + + value := <-ch + fmt.Println(value) +} + +// Or use buffered channel +func main() { + ch := make(chan int, 1) // Buffered + ch <- 42 // Doesn't block + value := <-ch + fmt.Println(value) +} +``` + + +### Pitfall: Sending Nil Values + + +```go !! go +// Go - Nil channels can be useful but are tricky +func main() { + var ch chan int // nil channel + + // These operations block forever! + // ch <- 42 // Would block + // value := <-ch // Would block + + // Use case: select with nil channels + var send, receive chan int + receive = make(chan int) + + go func() { + receive <- 42 + }() + + select { + case v := <-receive: + fmt.Println("Received:", v) + case send <- 42: // send is nil, so this case is skipped + fmt.Println("Sent") // Never executes + } +} +``` + + +## 6. Variable Shadowing + +### Pitfall: Unintended Shadowing + + +```java !! java +// Java - Compiler warns about shadowing +public class Shadow { + private int count = 0; + + public void increment() { + int count = 5; // Warning: shadows field + count++; + } +} +``` + +```go !! go +// Go - Shadowing can cause subtle bugs +func process() error { + err := fmt.Errorf("initial error") + + if true { + err := fmt.Errorf("new error") // Shadows outer err! + // This err is different from outer err + } + + return err // Returns "initial error", not "new error"! +} + +// Correct: Don't use := +func processCorrect() error { + err := fmt.Errorf("initial error") + + if true { + err = fmt.Errorf("new error") // Reassigns outer err + } + + return err // Returns "new error" +} + +// Common pitfall in for loops +func processItems(items []string) error { + var err error + + for _, item := range items { + err := processItem(item) // Shadows outer err! + if err != nil { + // Error handling here doesn't affect outer err + } + } + + return err // Always nil! +} +``` + + +## 7. Map Safety + +### Pitfall: Concurrent Map Access + + +```java !! java +// Java - ConcurrentHashMap is thread-safe +Map map = new ConcurrentHashMap<>(); + +// Multiple threads can safely access +map.put("key", 1); +Integer value = map.get("key"); +``` + +```go !! go +// Go - Regular maps are NOT thread-safe +var m = make(map[string]int) + +// WRONG: Concurrent access causes panic +go func() { + for i := 0; i < 1000; i++ { + m["key"] = i // PANIC: concurrent map writes + } +}() + +go func() { + for i := 0; i < 1000; i++ { + m["key"] = i // PANIC: concurrent map writes + } +}() + +// Solution 1: Use mutex +var ( + mu sync.Mutex + m = make(map[string]int) +) + +func safeWrite(key string, value int) { + mu.Lock() + m[key] = value + mu.Unlock() +} + +func safeRead(key string) int { + mu.Lock() + defer mu.Unlock() + return m[key] +} + +// Solution 2: Use sync.Map (for specific use cases) +var m sync.Map + +m.Store("key", 42) +if value, ok := m.Load("key"); ok { + fmt.Println(value.(int)) +} +``` + + +## 8. Defer Pitfalls + +### Pitfall: Defer Execution Order + + +```java !! java +// Java - try-finally executes in order +public void process() { + try { + System.out.println("Processing"); + } finally { + System.out.println("Cleanup"); + } +} +``` + +```go !! go +// Go - Defer executes LIFO (Last In, First Out) +func main() { + defer fmt.Println("First defer") + defer fmt.Println("Second defer") + defer fmt.Println("Third defer") + + fmt.Println("Function body") + + // Output: + // Function body + // Third defer + // Second defer + // First defer +} + +// Pitfall: Defer with loop variable +func processFiles(files []string) error { + for _, file := range files { + f, err := os.Open(file) + if err != nil { + return err + } + defer f.Close() // WRONG: All closes happen at function exit! + + // Process file... + } + // All files stay open until function returns! + return nil +} + +// Correct: Use anonymous function +func processFilesCorrect(files []string) error { + for _, file := range files { + if err := func() error { + f, err := os.Open(file) + if err != nil { + return err + } + defer f.Close() // Close after each iteration + + // Process file... + return nil + }(); err != nil { + return err + } + } + return nil +} +``` + + +### Pitfall: Defer with Method Values + + +```go !! go +// Go - Parameters in defer are evaluated immediately +type Counter struct { + count int +} + +func (c *Counter) Increment() { + c.count++ +} + +func (c *Counter) Value() int { + return c.count +} + +func main() { + c := &Counter{count: 0} + + defer fmt.Println(c.Value()) // Evaluated now: prints 0 + + c.Increment() + c.Increment() + + // Output: 0, not 2! +} + +// To defer method call correctly +func main() { + c := &Counter{count: 0} + + defer func() { + fmt.Println(c.Value()) // Evaluated later: prints 2 + }() + + c.Increment() + c.Increment() +} +``` + + +## 9. Goroutine Leaks + +### Pitfall: Goroutine Never Exiting + + +```java !! java +// Java - Threads can be daemon or have timeout +ExecutorService executor = Executors.newCachedThreadPool(); +Future future = executor.submit(() -> { + // Long-running task + return "result"; +}); + +try { + String result = future.get(1, TimeUnit.SECONDS); +} catch (TimeoutException e) { + future.cancel(true); // Interrupt the thread +} +``` + +```go !! go +// Go - Goroutine leaks are easy to create +func leakyGoroutine() { + ch := make(chan int) + + go func() { + value := <-ch // Waits forever + fmt.Println(value) + }() + + // Function returns, but goroutine is still waiting! + // Goroutine leak! +} + +// Correct: Use context for cancellation +func correctGoroutine(ctx context.Context) error { + ch := make(chan int) + + done := make(chan struct{}) + go func() { + select { + case value := <-ch: + fmt.Println(value) + case <-ctx.Done(): + return // Exit when context cancelled + } + close(done) + }() + + select { + case <-done: + return nil + case <-ctx.Done(): + return ctx.Err() + } +} + +// Or use timeout +func withTimeout() error { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + return correctGoroutine(ctx) +} +``` + + +## 10. String Encoding Pitfalls + +### Pitfall: String vs []byte + + +```java !! java +// Java - Strings are UTF-16, bytes are platform-specific +String str = "Hello"; +byte[] bytes = str.getBytes(StandardCharsets.UTF_8); +String decoded = new String(bytes, StandardCharsets.UTF_8); +``` + +```go !! go +// Go - Strings are read-only, bytes are mutable +func processString() { + s := "hello" + + // WRONG: Cannot index string bytes for Unicode + fmt.Println(s[0]) // Prints 104 (ASCII 'h'), works for ASCII + fmt.Println(s[0:1]) // "h" + + // For Unicode: Use range + for i, r := range s { + fmt.Printf("%d: %c\n", i, r) + } + + // Converting between string and []byte creates copies + b := []byte(s) // Copy: heap allocation + s2 := string(b) // Another copy + + // For []rune (Unicode code points) + runes := []rune(s) // Copy with rune conversion +} + +// Common mistake: Modifying "string" via []byte +func modifyStringBad(s string) string { + b := []byte(s) + b[0] = 'H' // Modifies the copy, not original + return string(b) // Creates new string +} + +// For efficient string building +func buildString() string { + var b strings.Builder + for i := 0; i < 1000; i++ { + b.WriteString("text") + } + return b.String() // Efficient: single allocation +} +``` + + +## 11. Method Receiver Pitfalls + +### Pitfall: Value vs Pointer Receivers + + +```java !! java +// Java - Methods always on references +public class Counter { + private int count; + + public void increment() { + this.count++; + } + + public int getValue() { + return this.count; + } +} + +Counter c = new Counter(); +c.increment(); // Modifies the object +``` + +```go !! go +// Go - Value vs pointer receivers matter +type Counter struct { + count int +} + +// Value receiver - gets copy +func (c Counter) Increment() { + c.count++ // Modifies copy, not original! +} + +// Pointer receiver - modifies original +func (c *Counter) IncrementCorrect() { + c.count++ // Modifies original +} + +func main() { + c := Counter{count: 0} + c.Increment() // No effect! + fmt.Println(c.count) // Still 0 + + c.IncrementCorrect() // Works + fmt.Println(c.count) // Now 1 +} + +// Pitfall: Inconsistency +type BadCounter struct { + count int +} + +func (c BadCounter) Increment() { + c.count++ +} + +func (c *BadCounter) GetValue() int { + return c.count +} + +// Confusing: Some methods use pointer, some use value +// Best practice: Be consistent - all pointer or all value +``` + + +## 12. Interface Satisfaction + +### Pitfall: Implicit Interface Satisfaction + + +```java !! java +// Java - Explicit implements keyword +public class MyReader implements Reader { + @Override + public int read(char[] cbuf) throws IOException { + return 0; + } + + @Override + public void close() throws IOException { + } +} +``` + +```go !! go +// Go - Implicit satisfaction can lead to errors +type Reader interface { + Read([]byte) (int, error) +} + +type MyReader struct{} + +// WRONG: Method signature doesn't match +func (r MyReader) Read(p []byte) (int, error) { + return 0, nil +} + +// Correct: Pointer receiver to match interface +func (r *MyReader) Read(p []byte) (int, error) { + return 0, nil +} + +// Pitfall: Method value vs method expression +func process() { + var r Reader = &MyReader{} + + // Method value - bound to instance + fn1 := r.Read + fn1([]byte{}) // Works + + // Method expression - needs explicit receiver + fn2 := (*MyReader).Read + fn2(&MyReader{}, []byte{}) // Also works + + // Easy to mix up! +} +``` + + +## Summary + +Common pitfalls when transitioning from Java to Go: + +1. **Nil vs null**: Go's nil is more type-specific +2. **Slice gotchas**: Understand backing arrays and capacity +3. **Range loops**: Watch for variable capture in goroutines +4. **Error handling**: Never ignore errors, always handle them +5. **Channels**: Remember to close, avoid deadlocks +6. **Shadowing**: Be careful with `:=` in new scopes +7. **Maps**: Use mutex or sync.Map for concurrent access +8. **Defer**: Executes LIFO, parameters evaluated immediately +9. **Goroutine leaks**: Always provide cancellation mechanism +10. **String encoding**: Use range for Unicode iteration +11. **Method receivers**: Understand value vs pointer +12. **Interfaces**: Check method signatures match exactly + +## Practice Questions + +1. Why does `fmt.Println(interface{}(nil) == nil)` return `false` in some cases? +2. How can you prevent goroutine leaks when using channels? +3. What's the difference between `close(ch)` and leaving a channel open? +4. Why might preallocating slice capacity improve performance? +5. How does Go's error handling differ from Java's exceptions? + +## Project Idea + +Create a "Go Pitfall Detector" tool that: +- Scans Go code for common pitfalls +- Suggests fixes for each detected issue +- Includes examples of wrong vs right code +- Provides explanations for each pitfall + +## Next Steps + +- **Module 11**: Learn idiomatic Go patterns and philosophy +- **Module 12**: Performance optimization techniques +- **Module 13**: Deployment and production considerations +- **Module 14**: Build a complete real-world project + +## Further Reading + +- [Go Common Mistakes](https://github.com/golang/go/wiki/CommonMistakes) +- [Effective Go](https://go.dev/doc/effective_go) +- [Go Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments) +- [The Go Blog: Go Concurrency Patterns](https://go.dev/blog/concurrency-patterns) diff --git a/content/docs/java2go/module-10-common-pitfalls.zh-cn.mdx b/content/docs/java2go/module-10-common-pitfalls.zh-cn.mdx new file mode 100644 index 0000000..d960c6d --- /dev/null +++ b/content/docs/java2go/module-10-common-pitfalls.zh-cn.mdx @@ -0,0 +1,1053 @@ +--- +title: "Module 10: Common Pitfalls" +description: "Java 开发者过渡到 Go 时的常见错误以及如何避免它们" +--- + +# Module 10: Common Pitfalls +# 模块 10:常见陷阱 + +## Learning Objectives +## 学习目标 + +完成本模块后,您将: +- 识别 Java 开发者在 Go 中常犯的错误 +- 理解 nil 和 null 之间的区别 +- 避免切片和数组的陷阱 +- 防止 goroutine 和闭包的陷阱 +- 正确处理错误 +- 正确使用通道 +- 理解接口的 nil 混淆 + +## Introduction +## 简介 + +从 Java 过渡到 Go 不仅仅是学习新语法。它需要理解两种语言在内存管理、并发和错误处理方面的根本差异。在本模块中,我们将探讨 Java 开发者在编写 Go 代码时遇到的最常见陷阱。 + +## 1. Nil vs Null Confusion +## 1. Nil 与 Null 的混淆 + +### The Problem +### 问题 + +在 Java 中,`null` 是一个可以赋值给任何非原始类型的通用值。Go 有 `nil`,但它在不同类型上的行为不同。 + +### Pitfall: Not Checking Nil Pointers +### 陷阱:未检查 Nil 指针 + + +```java !! java +// Java - NPE 是常见的运行时错误 +public class User { + private String name; + + public String getName() { + return name; // 可能返回 null + } + + public void printNameLength() { + // 如果 getName() 返回 null 则抛出 NullPointerException + System.out.println(getName().length()); + } +} +``` + +```go !! go +// Go - 必须显式处理 nil +type User struct { + name *string // 指针可以为 nil +} + +func (u *User) Name() *string { + return u.name // 如果未设置则返回 nil +} + +func (u *User) PrintNameLength() { + if u.name == nil { + fmt.Println("Name not set") + return + } + fmt.Println(len(*u.name)) +} +``` + + +### Pitfall: Nil Interface Values +### 陷阱:Nil 接口值 + + +```java !! java +// Java - 引用比较很直接 +String s = null; +if (s == null) { + System.out.println("s is null"); +} + +List list = null; +if (list == null) { + System.out.println("list is null"); +} +``` + +```go !! go +// Go - 接口 nil 很棘手 +type Printer interface { + Print() +} + +type MyPrinter struct{} + +func (p *MyPrinter) Print() { + fmt.Println("Printing") +} + +func main() { + var p Printer // p 是 nil + fmt.Println(p == nil) // true + + var mp *MyPrinter // mp 是 nil + p = mp // p 包含一个 nil *MyPrinter + fmt.Println(p == nil) // FALSE! 这是一个常见的陷阱 + + // 接口值本身不是 nil, + // 它持有一个 nil 的具体值 +} +``` + + +**Best Practice:** **最佳实践:** 始终检查接口和具体类型: + +```go +if p == nil || p.(*MyPrinter) == nil { + fmt.Println("Printer is not properly initialized") +} +``` + +## 2. Slice Gotchas +## 2. 切片陷阱 + +### Pitfall: Slice Reference Semantics +### 陷阱:切片引用语义 + + +```java !! java +// Java - 数组通过引用传递 +public class SliceExample { + public static void modifyArray(int[] arr) { + arr[0] = 999; + } + + public static void main(String[] args) { + int[] arr = {1, 2, 3}; + modifyArray(arr); + System.out.println(Arrays.toString(arr)); // [999, 2, 3] + } +} +``` + +```go !! go +// Go - 切片是对底层数组的引用 +func modifySlice(s []int) { + s[0] = 999 + // 这会修改原始数组 +} + +func main() { + s := []int{1, 2, 3} + modifySlice(s) + fmt.Println(s) // [999 2 3] - 被修改了! + + // 但追加并不总是按预期工作 + appendToSlice(s) + fmt.Println(s) // 仍然是 [999 2 3] - append 没有影响原始值 +} + +func appendToSlice(s []int) { + s = append(s, 4) // 这会创建一个新的切片! +} +``` + + +### Pitfall: Slice Capacity and Reallocation +### 陷阱:切片容量和重新分配 + + +```java !! java +// Java - ArrayList 自动处理容量 +ArrayList list = new ArrayList<>(); +for (int i = 0; i < 1000; i++) { + list.add(i); // 容量自动增长 +} +``` + +```go !! go +// Go - 理解切片容量至关重要 +func main() { + s := make([]int, 3, 5) // length=3, capacity=5 + fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s) + + s = append(s, 4, 5) + fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s) + + // 这个追加会导致重新分配 + s = append(s, 6) + fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s) + // 容量可能翻倍 + + // 常见错误:循环中追加而不预分配 + var result []int + for i := 0; i < 100000; i++ { + result = append(result, i) // 多次重新分配! + } + + // 更好:预分配 + result = make([]int, 0, 100000) + for i := 0; i < 100000; i++ { + result = append(result, i) // 单次分配 + } +} +``` + + +### Pitfall: Slice Subtlety with Sub-slicing +### 陷阱:子切片的微妙之处 + + +```java !! java +// Java - SubList 保持对原始列表的引用 +List bigList = new ArrayList<>(); +for (int i = 0; i < 1000000; i++) { + bigList.add(i); +} +List small = bigList.subList(0, 10); +// bigList 在 small 存在期间无法被 GC +``` + +```go !! go +// Go - 子切片保持对整个后备数组的引用 +func main() { + bigSlice := make([]byte, 1024*1024) // 1 MB + for i := range bigSlice { + bigSlice[i] = 'x' + } + + // 只需要前 10 个字节 + smallSlice := bigSlice[:10] + + // bigSlice 仍在内存中,因为 smallSlice + // 引用同一个后备数组! + + // 解决方案:复制到新切片 + tinySlice := make([]byte, 10) + copy(tinySlice, bigSlice[:10]) + // 现在可以垃圾回收 bigSlice +} +``` + + +## 3. Range Loop Variable Capture +## 3. Range 循环变量捕获 + +### Pitfall: Goroutine Closure Capture +### 陷阱:Goroutine 闭包捕获 + + +```java !! java +// Java - 每次迭代都有自己的变量 +List numbers = Arrays.asList(1, 2, 3, 4, 5); +List> futures = new ArrayList<>(); + +for (Integer number : numbers) { + Future future = executor.submit(() -> { + return number * 2; // 每个闭包捕获自己的 number + }); + futures.add(future); +} + +// 结果:2, 4, 6, 8, 10 +``` + +```go !! go +// Go - 错误!所有 goroutine 捕获同一个变量 +func main() { + numbers := []int{1, 2, 3, 4, 5} + + for _, number := range numbers { + go func() { + fmt.Println(number * 2) // 都打印 10! + }() + } + + time.Sleep(time.Second) +} + +// 正确:将变量作为参数传递 +func main() { + numbers := []int{1, 2, 3, 4, 5} + + for _, number := range numbers { + go func(n int) { + fmt.Println(n * 2) // 打印:2, 4, 6, 8, 10 + }(number) // 作为参数传递 + } + + time.Sleep(time.Second) +} + +// 也正确:在循环作用域中创建新变量 +func main() { + numbers := []int{1, 2, 3, 4, 5} + + for _, number := range numbers { + number := number // 创建新变量 + go func() { + fmt.Println(number * 2) // 正确! + }() + } + + time.Sleep(time.Second) +} +``` + + +## 4. Error Handling Mistakes +## 4. 错误处理错误 + +### Pitfall: Ignoring Errors +### 陷阱:忽略错误 + + +```java !! java +// Java - 异常可以被忽略(坏实践) +public void readFile() { + try { + Files.readAllLines(Paths.get("file.txt")); + } catch (IOException e) { + // 空的 catch 块 - 静默失败 + } +} +``` + +```go !! go +// Go - 必须显式处理错误 +func readFile() error { + data, err := os.ReadFile("file.txt") + if err != nil { + return err // 必须处理错误 + } + fmt.Println(string(data)) + return nil +} + +// 错误:忽略错误 +func readFileBad() { + data, _ := os.ReadFile("file.txt") // 永远不要忽略错误! + fmt.Println(string(data)) +} + +// 常见陷阱:defer 中的错误 +func processFile(filename string) error { + f, err := os.Open(filename) + if err != nil { + return err + } + defer f.Close() // 无法检查这个错误! + + // 更好:使用包装函数 + defer func() { + if err := f.Close(); err != nil { + log.Printf("Error closing file: %v", err) + } + }() + + return nil +} +``` + + +### Pitfall: Wrapping Errors Incorrectly +### 陷阱:错误包装不正确 + + +```java !! java +// Java - 异常链 +try { + // ... 抛出异常的代码 +} catch (IOException e) { + throw new RuntimeException("Failed to process", e); +} +``` + +```go !! go +// Go - 正确的错误包装 +func processFile(path string) error { + f, err := os.Open(path) + if err != nil { + return fmt.Errorf("failed to open %s: %w", path, err) + } + defer f.Close() + + // ... 处理 ... + + return nil +} + +// 常见错误:未包装上下文 +func processFileBad(path string) error { + f, err := os.Open(path) + if err != nil { + return err // 丢失了哪个文件的上下文! + } + defer f.Close() + + return nil +} +``` + + +## 5. Channel Misuse +## 5. 通道误用 + +### Pitfall: Forgetting to Close Channels +### 陷阱:忘记关闭通道 + + +```java !! java +// Java - BlockingQueue 不需要显式关闭 +BlockingQueue queue = new LinkedBlockingQueue<>(); +``` + +```go !! go +// Go - 通道必须由发送者关闭 +func producer(ch chan<- int) { + for i := 0; i < 10; i++ { + ch <- i + } + close(ch) // 关键:必须关闭以信号完成 +} + +func consumer(ch <-chan int) { + for value := range ch { // 通道关闭时循环终止 + fmt.Println(value) + } +} + +// 错误:消费者关闭通道 +func consumerBad(ch <-chan int) { + for value := range ch { + fmt.Println(value) + } + close(ch) // PANIC!不能关闭只接收通道 +} +``` + + +### Pitfall: Deadlock with Unbuffered Channels +### 陷阱:无缓冲通道的死锁 + + +```java !! java +// Java - 同步块可能会死锁 +public synchronized void method1() { + // 持有锁 + method2(); // 需要相同的锁 - 在 Java 中可行 +} + +public synchronized void method2() { + // 可重入锁 - 同一线程可以获取 +} +``` + +```go !! go +// Go - goroutine 通信的死锁 +func main() { + ch := make(chan int) // 无缓冲 + + // 错误:死锁! + ch <- 42 // 阻塞 - 没有接收者 + // 致命错误:所有 goroutine 都在休眠 - 死锁! +} + +// 正确:准备好接收者 +func main() { + ch := make(chan int) + + go func() { + ch <- 42 + }() + + value := <-ch + fmt.Println(value) +} + +// 或使用缓冲通道 +func main() { + ch := make(chan int, 1) // 缓冲 + ch <- 42 // 不阻塞 + value := <-ch + fmt.Println(value) +} +``` + + +### Pitfall: Sending Nil Values +### 陷阱:发送 Nil 值 + + +```go !! go +// Go - nil 通道可能有用但很棘手 +func main() { + var ch chan int // nil 通道 + + // 这些操作永远阻塞! + // ch <- 42 // 会阻塞 + // value := <-ch // 会阻塞 + + // 用例:select 与 nil 通道 + var send, receive chan int + receive = make(chan int) + + go func() { + receive <- 42 + }() + + select { + case v := <-receive: + fmt.Println("Received:", v) + case send <- 42: // send 是 nil,所以这个 case 被跳过 + fmt.Println("Sent") // 永远不执行 + } +} +``` + + +## 6. Variable Shadowing +## 6. 变量遮蔽 + +### Pitfall: Unintended Shadowing +### 陷阱:意外的遮蔽 + + +```java !! java +// Java - 编译器警告遮蔽 +public class Shadow { + private int count = 0; + + public void increment() { + int count = 5; // 警告:遮蔽字段 + count++; + } +} +``` + +```go !! go +// Go - 遮蔽可能导致微妙的错误 +func process() error { + err := fmt.Errorf("initial error") + + if true { + err := fmt.Errorf("new error") // 遮蔽外部 err! + // 这个 err 与外部 err 不同 + } + + return err // 返回 "initial error",而不是 "new error"! +} + +// 正确:不要使用 := +func processCorrect() error { + err := fmt.Errorf("initial error") + + if true { + err = fmt.Errorf("new error") // 重新赋值外部 err + } + + return err // 返回 "new error" +} + +// for 循环中的常见陷阱 +func processItems(items []string) error { + var err error + + for _, item := range items { + err := processItem(item) // 遮蔽外部 err! + if err != nil { + // 这里的错误处理不影响外部 err + } + } + + return err // 总是 nil! +} +``` + + +## 7. Map Safety +## 7. Map 安全性 + +### Pitfall: Concurrent Map Access +### 陷阱:并发 Map 访问 + + +```java !! java +// Java - ConcurrentHashMap 是线程安全的 +Map map = new ConcurrentHashMap<>(); + +// 多个线程可以安全访问 +map.put("key", 1); +Integer value = map.get("key"); +``` + +```go !! go +// Go - 普通 map 不是线程安全的 +var m = make(map[string]int) + +// 错误:并发访问导致 panic +go func() { + for i := 0; i < 1000; i++ { + m["key"] = i // PANIC: 并发 map 写入 + } +}() + +go func() { + for i := 0; i < 1000; i++ { + m["key"] = i // PANIC: 并发 map 写入 + } +}() + +// 解决方案 1:使用 mutex +var ( + mu sync.Mutex + m = make(map[string]int) +) + +func safeWrite(key string, value int) { + mu.Lock() + m[key] = value + mu.Unlock() +} + +func safeRead(key string) int { + mu.Lock() + defer mu.Unlock() + return m[key] +} + +// 解决方案 2:使用 sync.Map(用于特定用例) +var m sync.Map + +m.Store("key", 42) +if value, ok := m.Load("key"); ok { + fmt.Println(value.(int)) +} +``` + + +## 8. Defer Pitfalls +## 8. Defer 陷阱 + +### Pitfall: Defer Execution Order +### 陷阱:Defer 执行顺序 + + +```java !! java +// Java - try-finally 按顺序执行 +public void process() { + try { + System.out.println("Processing"); + } finally { + System.out.println("Cleanup"); + } +} +``` + +```go !! go +// Go - defer 执行 LIFO(后进先出) +func main() { + defer fmt.Println("First defer") + defer fmt.Println("Second defer") + defer fmt.Println("Third defer") + + fmt.Println("Function body") + + // 输出: + // Function body + // Third defer + // Second defer + // First defer +} + +// 陷阱:defer 与循环变量 +func processFiles(files []string) error { + for _, file := range files { + f, err := os.Open(file) + if err != nil { + return err + } + defer f.Close() // 错误:所有关闭都在函数退出时发生! + + // 处理文件... + } + // 所有文件保持打开状态直到函数返回! + return nil +} + +// 正确:使用匿名函数 +func processFilesCorrect(files []string) error { + for _, file := range files { + if err := func() error { + f, err := os.Open(file) + if err != nil { + return err + } + defer f.Close() // 每次迭代后关闭 + + // 处理文件... + return nil + }(); err != nil { + return err + } + } + return nil +} +``` + + +### Pitfall: Defer with Method Values +### 陷阱:Defer 与方法值 + + +```go !! go +// Go - defer 中的参数立即被求值 +type Counter struct { + count int +} + +func (c *Counter) Increment() { + c.count++ +} + +func (c *Counter) Value() int { + return c.count +} + +func main() { + c := &Counter{count: 0} + + defer fmt.Println(c.Value()) // 现在求值:打印 0 + + c.Increment() + c.Increment() + + // 输出:0,而不是 2! +} + +// 要正确地延迟方法调用 +func main() { + c := &Counter{count: 0} + + defer func() { + fmt.Println(c.Value()) // 稍后求值:打印 2 + }() + + c.Increment() + c.Increment() +} +``` + + +## 9. Goroutine Leaks +## 9. Goroutine 泄漏 + +### Pitfall: Goroutine Never Exiting +### 陷阱:Goroutine 永不退出 + + +```java !! java +// Java - 线程可以是守护线程或具有超时 +ExecutorService executor = Executors.newCachedThreadPool(); +Future future = executor.submit(() -> { + // 长时间运行的任务 + return "result"; +}); + +try { + String result = future.get(1, TimeUnit.SECONDS); +} catch (TimeoutException e) { + future.cancel(true); // 中断线程 +} +``` + +```go !! go +// Go - Goroutine 泄漏很容易创建 +func leakyGoroutine() { + ch := make(chan int) + + go func() { + value := <-ch // 永远等待 + fmt.Println(value) + }() + + // 函数返回,但 goroutine 仍在等待! + // Goroutine 泄漏! +} + +// 正确:使用 context 进行取消 +func correctGoroutine(ctx context.Context) error { + ch := make(chan int) + + done := make(chan struct{}) + go func() { + select { + case value := <-ch: + fmt.Println(value) + case <-ctx.Done(): + return // context 取消时退出 + } + close(done) + }() + + select { + case <-done: + return nil + case <-ctx.Done(): + return ctx.Err() + } +} + +// 或使用超时 +func withTimeout() error { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + return correctGoroutine(ctx) +} +``` + + +## 10. String Encoding Pitfalls +## 10. 字符串编码陷阱 + +### Pitfall: String vs []byte +### 陷阱:String vs []byte + + +```java !! java +// Java - Strings 是 UTF-16,bytes 是平台特定的 +String str = "Hello"; +byte[] bytes = str.getBytes(StandardCharsets.UTF_8); +String decoded = new String(bytes, StandardCharsets.UTF_8); +``` + +```go !! go +// Go - Strings 是只读的,bytes 是可变的 +func processString() { + s := "hello" + + // 错误:不能对 Unicode 使用字符串字节索引 + fmt.Println(s[0]) // 打印 104(ASCII 'h'),对 ASCII 有效 + fmt.Println(s[0:1]) // "h" + + // 对于 Unicode:使用 range + for i, r := range s { + fmt.Printf("%d: %c\n", i, r) + } + + // 在 string 和 []byte 之间转换会创建副本 + b := []byte(s) // 复制:堆分配 + s2 := string(b) // 另一个复制 + + // 对于 []rune(Unicode 码点) + runes := []rune(s) // 使用 rune 转换复制 +} + +// 常见错误:通过 []byte "修改"字符串 +func modifyStringBad(s string) string { + b := []byte(s) + b[0] = 'H' // 修改副本,不是原始值 + return string(b) // 创建新字符串 +} + +// 对于高效的字符串构建 +func buildString() string { + var b strings.Builder + for i := 0; i < 1000; i++ { + b.WriteString("text") + } + return b.String() // 高效:单次分配 +} +``` + + +## 11. Method Receiver Pitfalls +## 11. 方法接收者陷阱 + +### Pitfall: Value vs Pointer Receivers +### 陷阱:值与指针接收者 + + +```java !! java +// Java - 方法总是在引用上 +public class Counter { + private int count; + + public void increment() { + this.count++; + } + + public int getValue() { + return this.count; + } +} + +Counter c = new Counter(); +c.increment(); // 修改对象 +``` + +```go !! go +// Go - 值与指针接收者很重要 +type Counter struct { + count int +} + +// 值接收者 - 获取副本 +func (c Counter) Increment() { + c.count++ // 修改副本,不是原始值! +} + +// 指针接收者 - 修改原始值 +func (c *Counter) IncrementCorrect() { + c.count++ // 修改原始值 +} + +func main() { + c := Counter{count: 0} + c.Increment() // 没有作用! + fmt.Println(c.count) // 仍然是 0 + + c.IncrementCorrect() // 有效 + fmt.Println(c.count) // 现在是 1 +} + +// 陷阱:不一致 +type BadCounter struct { + count int +} + +func (c BadCounter) Increment() { + c.count++ +} + +func (c *BadCounter) GetValue() int { + return c.count +} + +// 令人困惑:有些方法使用指针,有些使用值 +// 最佳实践:保持一致 - 全部指针或全部值 +``` + + +## 12. Interface Satisfaction +## 12. 接口满足 + +### Pitfall: Implicit Interface Satisfaction +### 陷阱:隐式接口满足 + + +```java !! java +// Java - 显式 implements 关键字 +public class MyReader implements Reader { + @Override + public int read(char[] cbuf) throws IOException { + return 0; + } + + @Override + public void close() throws IOException { + } +} +``` + +```go !! go +// Go - 隐式满足可能导致错误 +type Reader interface { + Read([]byte) (int, error) +} + +type MyReader struct{} + +// 错误:方法签名不匹配 +func (r MyReader) Read(p []byte) (int, error) { + return 0, nil +} + +// 正确:指针接收者以匹配接口 +func (r *MyReader) Read(p []byte) (int, error) { + return 0, nil +} + +// 陷阱:方法值与方法表达式 +func process() { + var r Reader = &MyReader{} + + // 方法值 - 绑定到实例 + fn1 := r.Read + fn1([]byte{}) // 有效 + + // 方法表达式 - 需要显式接收者 + fn2 := (*MyReader).Read + fn2(&MyReader{}, []byte{}) // 也有效 + + // 容易混淆! +} +``` + + +## Summary +## 总结 + +从 Java 过渡到 Go 时的常见陷阱: + +1. **Nil vs null**:Go 的 nil 更类型特定 +2. **Slice 陷阱**:理解后备数组和容量 +3. **Range 循环**:注意 goroutine 中的变量捕获 +4. **错误处理**:永远不要忽略错误,始终处理它们 +5. **通道**:记得关闭,避免死锁 +6. **遮蔽**:小心新作用域中的 `:=` +7. **Maps**:使用 mutex 或 sync.Map 进行并发访问 +8. **Defer**:执行 LIFO,参数立即求值 +9. **Goroutine 泄漏**:始终提供取消机制 +10. **字符串编码**:使用 range 进行 Unicode 迭代 +11. **方法接收者**:理解值与指针 +12. **接口**:检查方法签名是否完全匹配 + +## Practice Questions +## 练习问题 + +1. 为什么在某些情况下 `fmt.Println(interface{}(nil) == nil)` 返回 `false`? +2. 使用通道时如何防止 goroutine 泄漏? +3. `close(ch)` 和保持通道打开有什么区别? +4. 为什么预分配切片容量可能会提高性能? +5. Go 的错误处理与 Java 的异常有何不同? + +## Project Idea +## 项目想法 + +创建一个"Go 陷阱检测器"工具: +- 扫描 Go 代码以查找常见陷阱 +- 为每个检测到的问题建议修复 +- 包括错误与正确代码的示例 +- 为每个陷阱提供解释 + +## Next Steps +## 下一步 + +- **Module 11**:学习地道的 Go 模式和哲学 +- **Module 12**:性能优化技术 +- **Module 13**:部署和生产考虑 +- **Module 14**:构建一个完整的真实世界项目 + +## Further Reading +## 延伸阅读 + +- [Go Common Mistakes](https://github.com/golang/go/wiki/CommonMistakes) +- [Effective Go](https://go.dev/doc/effective_go) +- [Go Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments) +- [The Go Blog: Go Concurrency Patterns](https://go.dev/blog/concurrency-patterns) diff --git a/content/docs/java2go/module-10-common-pitfalls.zh-tw.mdx b/content/docs/java2go/module-10-common-pitfalls.zh-tw.mdx new file mode 100644 index 0000000..37cecc7 --- /dev/null +++ b/content/docs/java2go/module-10-common-pitfalls.zh-tw.mdx @@ -0,0 +1,1053 @@ +--- +title: "Module 10: Common Pitfalls" +description: "Java 開發者過渡到 Go 時的常見錯誤以及如何避免它們" +--- + +# Module 10: Common Pitfalls +# 模組 10:常見陷阱 + +## Learning Objectives +## 學習目標 + +完成本模組後,您將: +- 識別 Java 開發者在 Go 中常犯的錯誤 +- 理解 nil 和 null 之間的差別 +- 避免切片和陣列的陷阱 +- 防止 goroutine 和閉包的陷阱 +- 正確處理錯誤 +- 正確使用通道 +- 理解介面的 nil 混淆 + +## Introduction +## 簡介 + +從 Java 過渡到 Go 不僅僅是學習新語法。它需要理解兩種語言在記憶體管理、並發和錯誤處理方面的根本差異。在本模組中,我們將探討 Java 開發者在編寫 Go 程式碼時遇到的最常見陷阱。 + +## 1. Nil vs Null Confusion +## 1. Nil 與 Null 的混淆 + +### The Problem +### 問題 + +在 Java 中,`null` 是一個可以賦值給任何非原始型別的通用值。Go 有 `nil`,但它在不同型別上的行為不同。 + +### Pitfall: Not Checking Nil Pointers +### 陷阱:未檢查 Nil 指標 + + +```java !! java +// Java - NPE 是常見的執行時錯誤 +public class User { + private String name; + + public String getName() { + return name; // 可能返回 null + } + + public void printNameLength() { + // 如果 getName() 返回 null 則拋出 NullPointerException + System.out.println(getName().length()); + } +} +``` + +```go !! go +// Go - 必須顯式處理 nil +type User struct { + name *string // 指標可以為 nil +} + +func (u *User) Name() *string { + return u.name // 如果未設定則返回 nil +} + +func (u *User) PrintNameLength() { + if u.name == nil { + fmt.Println("Name not set") + return + } + fmt.Println(len(*u.name)) +} +``` + + +### Pitfall: Nil Interface Values +### 陷阱:Nil 介面值 + + +```java !! java +// Java - 引用比較很直接 +String s = null; +if (s == null) { + System.out.println("s is null"); +} + +List list = null; +if (list == null) { + System.out.println("list is null"); +} +``` + +```go !! go +// Go - 介面 nil 很棘手 +type Printer interface { + Print() +} + +type MyPrinter struct{} + +func (p *MyPrinter) Print() { + fmt.Println("Printing") +} + +func main() { + var p Printer // p 是 nil + fmt.Println(p == nil) // true + + var mp *MyPrinter // mp 是 nil + p = mp // p 包含一個 nil *MyPrinter + fmt.Println(p == nil) // FALSE! 這是一個常見的陷阱 + + // 介面值本身不是 nil, + // 它持有一個 nil 的具體值 +} +``` + + +**Best Practice:** **最佳實踐:** 始終檢查介面和具體型別: + +```go +if p == nil || p.(*MyPrinter) == nil { + fmt.Println("Printer is not properly initialized") +} +``` + +## 2. Slice Gotchas +## 2. 切片陷阱 + +### Pitfall: Slice Reference Semantics +### 陷阱:切片引用語義 + + +```java !! java +// Java - 陣列通過引用傳遞 +public class SliceExample { + public static void modifyArray(int[] arr) { + arr[0] = 999; + } + + public static void main(String[] args) { + int[] arr = {1, 2, 3}; + modifyArray(arr); + System.out.println(Arrays.toString(arr)); // [999, 2, 3] + } +} +``` + +```go !! go +// Go - 切片是對底層陣列的引用 +func modifySlice(s []int) { + s[0] = 999 + // 這會修改原始陣列 +} + +func main() { + s := []int{1, 2, 3} + modifySlice(s) + fmt.Println(s) // [999 2 3] - 被修改了! + + // 但追加並不總是按預期工作 + appendToSlice(s) + fmt.Println(s) // 仍然是 [999 2 3] - append 沒有影響原始值 +} + +func appendToSlice(s []int) { + s = append(s, 4) // 這會建立一個新的切片! +} +``` + + +### Pitfall: Slice Capacity and Reallocation +### 陷阱:切片容量和重新分配 + + +```java !! java +// Java - ArrayList 自動處理容量 +ArrayList list = new ArrayList<>(); +for (int i = 0; i < 1000; i++) { + list.add(i); // 容量自動增長 +} +``` + +```go !! go +// Go - 理解切片容量至關重要 +func main() { + s := make([]int, 3, 5) // length=3, capacity=5 + fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s) + + s = append(s, 4, 5) + fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s) + + // 這個追加會導致重新分配 + s = append(s, 6) + fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s) + // 容量可能翻倍 + + // 常見錯誤:循環中追加而不預分配 + var result []int + for i := 0; i < 100000; i++ { + result = append(result, i) // 多次重新分配! + } + + // 更好:預分配 + result = make([]int, 0, 100000) + for i := 0; i < 100000; i++ { + result = append(result, i) // 單次分配 + } +} +``` + + +### Pitfall: Slice Subtlety with Sub-slicing +### 陷阱:子切片的微妙之處 + + +```java !! java +// Java - SubList 保持對原始列表的引用 +List bigList = new ArrayList<>(); +for (int i = 0; i < 1000000; i++) { + bigList.add(i); +} +List small = bigList.subList(0, 10); +// bigList 在 small 存在期間無法被 GC +``` + +```go !! go +// Go - 子切片保持對整個備份陣列的引用 +func main() { + bigSlice := make([]byte, 1024*1024) // 1 MB + for i := range bigSlice { + bigSlice[i] = 'x' + } + + // 只需要前 10 個位元組 + smallSlice := bigSlice[:10] + + // bigSlice 仍在記憶體中,因為 smallSlice + // 引用同一個備份陣列! + + // 解決方案:複製到新切片 + tinySlice := make([]byte, 10) + copy(tinySlice, bigSlice[:10]) + // 現在可以垃圾回收 bigSlice +} +``` + + +## 3. Range Loop Variable Capture +## 3. Range 迴圈變數捕獲 + +### Pitfall: Goroutine Closure Capture +### 陷阱:Goroutine 閉包捕獲 + + +```java !! java +// Java - 每次迭代都有自己的變數 +List numbers = Arrays.asList(1, 2, 3, 4, 5); +List> futures = new ArrayList<>(); + +for (Integer number : numbers) { + Future future = executor.submit(() -> { + return number * 2; // 每個閉包捕獲自己的 number + }); + futures.add(future); +} + +// 結果:2, 4, 6, 8, 10 +``` + +```go !! go +// Go - 錯誤!所有 goroutine 捕獲同一個變數 +func main() { + numbers := []int{1, 2, 3, 4, 5} + + for _, number := range numbers { + go func() { + fmt.Println(number * 2) // 都列印 10! + }() + } + + time.Sleep(time.Second) +} + +// 正確:將變數作為參數傳遞 +func main() { + numbers := []int{1, 2, 3, 4, 5} + + for _, number := range numbers { + go func(n int) { + fmt.Println(n * 2) // 列印:2, 4, 6, 8, 10 + }(number) // 作為參數傳遞 + } + + time.Sleep(time.Second) +} + +// 也正確:在迴圈作用域中建立新變數 +func main() { + numbers := []int{1, 2, 3, 4, 5} + + for _, number := range numbers { + number := number // 建立新變數 + go func() { + fmt.Println(number * 2) // 正確! + }() + } + + time.Sleep(time.Second) +} +``` + + +## 4. Error Handling Mistakes +## 4. 錯誤處理錯誤 + +### Pitfall: Ignoring Errors +### 陷阱:忽略錯誤 + + +```java !! java +// Java - 異常可以被忽略(壞實踐) +public void readFile() { + try { + Files.readAllLines(Paths.get("file.txt")); + } catch (IOException e) { + // 空的 catch 區塊 - 靜默失敗 + } +} +``` + +```go !! go +// Go - 必須顯式處理錯誤 +func readFile() error { + data, err := os.ReadFile("file.txt") + if err != nil { + return err // 必須處理錯誤 + } + fmt.Println(string(data)) + return nil +} + +// 錯誤:忽略錯誤 +func readFileBad() { + data, _ := os.ReadFile("file.txt") // 永遠不要忽略錯誤! + fmt.Println(string(data)) +} + +// 常見陷阱:defer 中的錯誤 +func processFile(filename string) error { + f, err := os.Open(filename) + if err != nil { + return err + } + defer f.Close() // 無法檢查這個錯誤! + + // 更好:使用包裝函式 + defer func() { + if err := f.Close(); err != nil { + log.Printf("Error closing file: %v", err) + } + }() + + return nil +} +``` + + +### Pitfall: Wrapping Errors Incorrectly +### 陷阱:錯誤包裝不正確 + + +```java !! java +// Java - 異常鏈 +try { + // ... 拋出異常的程式碼 +} catch (IOException e) { + throw new RuntimeException("Failed to process", e); +} +``` + +```go !! go +// Go - 正確的錯誤包裝 +func processFile(path string) error { + f, err := os.Open(path) + if err != nil { + return fmt.Errorf("failed to open %s: %w", path, err) + } + defer f.Close() + + // ... 處理 ... + + return nil +} + +// 常見錯誤:未包裝上下文 +func processFileBad(path string) error { + f, err := os.Open(path) + if err != nil { + return err // 丟失了哪個檔案的上下文! + } + defer f.Close() + + return nil +} +``` + + +## 5. Channel Misuse +## 5. 通道誤用 + +### Pitfall: Forgetting to Close Channels +### 陷阱:忘記關閉通道 + + +```java !! java +// Java - BlockingQueue 不需要顯式關閉 +BlockingQueue queue = new LinkedBlockingQueue<>(); +``` + +```go !! go +// Go - 通道必須由發送者關閉 +func producer(ch chan<- int) { + for i := 0; i < 10; i++ { + ch <- i + } + close(ch) // 關鍵:必須關閉以信號完成 +} + +func consumer(ch <-chan int) { + for value := range ch { // 通道關閉時迴圈終止 + fmt.Println(value) + } +} + +// 錯誤:消費者關閉通道 +func consumerBad(ch <-chan int) { + for value := range ch { + fmt.Println(value) + } + close(ch) // PANIC!不能關閉只接收通道 +} +``` + + +### Pitfall: Deadlock with Unbuffered Channels +### 陷阱:無緩衝通道的死鎖 + + +```java !! java +// Java - 同步區塊可能會死鎖 +public synchronized void method1() { + // 持有鎖 + method2(); // 需要相同的鎖 - 在 Java 中可行 +} + +public synchronized void method2() { + // 可重入鎖 - 同一執行緒可以獲取 +} +``` + +```go !! go +// Go - goroutine 通訊的死鎖 +func main() { + ch := make(chan int) // 無緩衝 + + // 錯誤:死鎖! + ch <- 42 // 阻塞 - 沒有接收者 + // 致命錯誤:所有 goroutine 都在休眠 - 死鎖! +} + +// 正確:準備好接收者 +func main() { + ch := make(chan int) + + go func() { + ch <- 42 + }() + + value := <-ch + fmt.Println(value) +} + +// 或使用緩衝通道 +func main() { + ch := make(chan int, 1) // 緩衝 + ch <- 42 // 不阻塞 + value := <-ch + fmt.Println(value) +} +``` + + +### Pitfall: Sending Nil Values +### 陷阱:發送 Nil 值 + + +```go !! go +// Go - nil 通道可能有用但很棘手 +func main() { + var ch chan int // nil 通道 + + // 這些操作永遠阻塞! + // ch <- 42 // 會阻塞 + // value := <-ch // 會阻塞 + + // 用例:select 與 nil 通道 + var send, receive chan int + receive = make(chan int) + + go func() { + receive <- 42 + }() + + select { + case v := <-receive: + fmt.Println("Received:", v) + case send <- 42: // send 是 nil,所以這個 case 被跳過 + fmt.Println("Sent") // 永遠不執行 + } +} +``` + + +## 6. Variable Shadowing +## 6. 變數遮蔽 + +### Pitfall: Unintended Shadowing +### 陷阱:意外的遮蔽 + + +```java !! java +// Java - 編譯器警告遮蔽 +public class Shadow { + private int count = 0; + + public void increment() { + int count = 5; // 警告:遮蔽欄位 + count++; + } +} +``` + +```go !! go +// Go - 遮蔽可能導致微妙的錯誤 +func process() error { + err := fmt.Errorf("initial error") + + if true { + err := fmt.Errorf("new error") // 遮蔽外部 err! + // 這個 err 與外部 err 不同 + } + + return err // 返回 "initial error",而不是 "new error"! +} + +// 正確:不要使用 := +func processCorrect() error { + err := fmt.Errorf("initial error") + + if true { + err = fmt.Errorf("new error") // 重新賦值外部 err + } + + return err // 返回 "new error" +} + +// for 迴圈中的常見陷阱 +func processItems(items []string) error { + var err error + + for _, item := range items { + err := processItem(item) // 遮蔽外部 err! + if err != nil { + // 這裡的錯誤處理不影響外部 err + } + } + + return err // 總是 nil! +} +``` + + +## 7. Map Safety +## 7. Map 安全性 + +### Pitfall: Concurrent Map Access +### 陷阱:並發 Map 存取 + + +```java !! java +// Java - ConcurrentHashMap 是執行緒安全的 +Map map = new ConcurrentHashMap<>(); + +// 多個執行緒可以安全存取 +map.put("key", 1); +Integer value = map.get("key"); +``` + +```go !! go +// Go - 普通 map 不是執行緒安全的 +var m = make(map[string]int) + +// 錯誤:並發存取導致 panic +go func() { + for i := 0; i < 1000; i++ { + m["key"] = i // PANIC: 並發 map 寫入 + } +}() + +go func() { + for i := 0; i < 1000; i++ { + m["key"] = i // PANIC: 並發 map 寫入 + } +}() + +// 解決方案 1:使用 mutex +var ( + mu sync.Mutex + m = make(map[string]int) +) + +func safeWrite(key string, value int) { + mu.Lock() + m[key] = value + mu.Unlock() +} + +func safeRead(key string) int { + mu.Lock() + defer mu.Unlock() + return m[key] +} + +// 解決方案 2:使用 sync.Map(用於特定用例) +var m sync.Map + +m.Store("key", 42) +if value, ok := m.Load("key"); ok { + fmt.Println(value.(int)) +} +``` + + +## 8. Defer Pitfalls +## 8. Defer 陷阱 + +### Pitfall: Defer Execution Order +### 陷阱:Defer 執行順序 + + +```java !! java +// Java - try-finally 按順序執行 +public void process() { + try { + System.out.println("Processing"); + } finally { + System.out.println("Cleanup"); + } +} +``` + +```go !! go +// Go - defer 執行 LIFO(後進先出) +func main() { + defer fmt.Println("First defer") + defer fmt.Println("Second defer") + defer fmt.Println("Third defer") + + fmt.Println("Function body") + + // 輸出: + // Function body + // Third defer + // Second defer + // First defer +} + +// 陷阱:defer 與迴圈變數 +func processFiles(files []string) error { + for _, file := range files { + f, err := os.Open(file) + if err != nil { + return err + } + defer f.Close() // 錯誤:所有關閉都在函式退出時發生! + + // 處理檔案... + } + // 所有檔案保持開啟狀態直到函式返回! + return nil +} + +// 正確:使用匿名函式 +func processFilesCorrect(files []string) error { + for _, file := range files { + if err := func() error { + f, err := os.Open(file) + if err != nil { + return err + } + defer f.Close() // 每次迭代後關閉 + + // 處理檔案... + return nil + }(); err != nil { + return err + } + } + return nil +} +``` + + +### Pitfall: Defer with Method Values +### 陷阱:Defer 與方法值 + + +```go !! go +// Go - defer 中的參數立即被求值 +type Counter struct { + count int +} + +func (c *Counter) Increment() { + c.count++ +} + +func (c *Counter) Value() int { + return c.count +} + +func main() { + c := &Counter{count: 0} + + defer fmt.Println(c.Value()) // 現在求值:列印 0 + + c.Increment() + c.Increment() + + // 輸出:0,而不是 2! +} + +// 要正確地延遲方法呼叫 +func main() { + c := &Counter{count: 0} + + defer func() { + fmt.Println(c.Value()) // 稍後求值:列印 2 + }() + + c.Increment() + c.Increment() +} +``` + + +## 9. Goroutine Leaks +## 9. Goroutine 泄漏 + +### Pitfall: Goroutine Never Exiting +### 陷阱:Goroutine 永不退出 + + +```java !! java +// Java - 執行緒可以是守護執行緒或具有逾時 +ExecutorService executor = Executors.newCachedThreadPool(); +Future future = executor.submit(() -> { + // 長時間執行的任務 + return "result"; +}); + +try { + String result = future.get(1, TimeUnit.SECONDS); +} catch (TimeoutException e) { + future.cancel(true); // 中斷執行緒 +} +``` + +```go !! go +// Go - Goroutine 泄漏很容易建立 +func leakyGoroutine() { + ch := make(chan int) + + go func() { + value := <-ch // 永遠等待 + fmt.Println(value) + }() + + // 函式返回,但 goroutine 仍在等待! + // Goroutine 泄漏! +} + +// 正確:使用 context 進行取消 +func correctGoroutine(ctx context.Context) error { + ch := make(chan int) + + done := make(chan struct{}) + go func() { + select { + case value := <-ch: + fmt.Println(value) + case <-ctx.Done(): + return // context 取消時退出 + } + close(done) + }() + + select { + case <-done: + return nil + case <-ctx.Done(): + return ctx.Err() + } +} + +// 或使用逾時 +func withTimeout() error { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + return correctGoroutine(ctx) +} +``` + + +## 10. String Encoding Pitfalls +## 10. 字串編碼陷阱 + +### Pitfall: String vs []byte +### 陷阱:String vs []byte + + +```java !! java +// Java - Strings 是 UTF-16,bytes 是平台特定的 +String str = "Hello"; +byte[] bytes = str.getBytes(StandardCharsets.UTF_8); +String decoded = new String(bytes, StandardCharsets.UTF_8); +``` + +```go !! go +// Go - Strings 是唯讀的,bytes 是可變的 +func processString() { + s := "hello" + + // 錯誤:不能對 Unicode 使用字串位元組索引 + fmt.Println(s[0]) // 列印 104(ASCII 'h'),對 ASCII 有效 + fmt.Println(s[0:1]) // "h" + + // 對於 Unicode:使用 range + for i, r := range s { + fmt.Printf("%d: %c\n", i, r) + } + + // 在 string 和 []byte 之間轉換會建立副本 + b := []byte(s) // 複製:堆分配 + s2 := string(b) // 另一個複製 + + // 對於 []rune(Unicode 碼點) + runes := []rune(s) // 使用 rune 轉換複製 +} + +// 常見錯誤:透過 []byte "修改"字串 +func modifyStringBad(s string) string { + b := []byte(s) + b[0] = 'H' // 修改副本,不是原始值 + return string(b) // 建立新字串 +} + +// 對於高效能的字串建構 +func buildString() string { + var b strings.Builder + for i := 0; i < 1000; i++ { + b.WriteString("text") + } + return b.String() // 高效:單次分配 +} +``` + + +## 11. Method Receiver Pitfalls +## 11. 方法接收者陷阱 + +### Pitfall: Value vs Pointer Receivers +### 陷阱:值與指標接收者 + + +```java !! java +// Java - 方法總是在引用上 +public class Counter { + private int count; + + public void increment() { + this.count++; + } + + public int getValue() { + return this.count; + } +} + +Counter c = new Counter(); +c.increment(); // 修改物件 +``` + +```go !! go +// Go - 值與指標接收者很重要 +type Counter struct { + count int +} + +// 值接收者 - 獲取副本 +func (c Counter) Increment() { + c.count++ // 修改副本,不是原始值! +} + +// 指標接收者 - 修改原始值 +func (c *Counter) IncrementCorrect() { + c.count++ // 修改原始值 +} + +func main() { + c := Counter{count: 0} + c.Increment() // 沒有作用! + fmt.Println(c.count) // 仍然是 0 + + c.IncrementCorrect() // 有效 + fmt.Println(c.count) // 現在是 1 +} + +// 陷阱:不一致 +type BadCounter struct { + count int +} + +func (c BadCounter) Increment() { + c.count++ +} + +func (c *BadCounter) GetValue() int { + return c.count +} + +// 令人困惑:有些方法使用指標,有些使用值 +// 最佳實踐:保持一致 - 全部指標或全部值 +``` + + +## 12. Interface Satisfaction +## 12. 介面滿足 + +### Pitfall: Implicit Interface Satisfaction +### 陷阱:隱式介面滿足 + + +```java !! java +// Java - 顯式 implements 關鍵字 +public class MyReader implements Reader { + @Override + public int read(char[] cbuf) throws IOException { + return 0; + } + + @Override + public void close() throws IOException { + } +} +``` + +```go !! go +// Go - 隱式滿足可能導致錯誤 +type Reader interface { + Read([]byte) (int, error) +} + +type MyReader struct{} + +// 錯誤:方法簽名不匹配 +func (r MyReader) Read(p []byte) (int, error) { + return 0, nil +} + +// 正確:指標接收者以匹配介面 +func (r *MyReader) Read(p []byte) (int, error) { + return 0, nil +} + +// 陷阱:方法值與方法表達式 +func process() { + var r Reader = &MyReader{} + + // 方法值 - 綁定到實例 + fn1 := r.Read + fn1([]byte{}) // 有效 + + // 方法表達式 - 需要顯式接收者 + fn2 := (*MyReader).Read + fn2(&MyReader{}, []byte{}) // 也有效 + + // 容易混淆! +} +``` + + +## Summary +## 總結 + +從 Java 過渡到 Go 時的常見陷阱: + +1. **Nil vs null**:Go 的 nil 更型別特定 +2. **Slice 陷阱**:理解備份陣列和容量 +3. **Range 迴圈**:注意 goroutine 中的變數捕獲 +4. **錯誤處理**:永遠不要忽略錯誤,始終處理它們 +5. **通道**:記得關閉,避免死鎖 +6. **遮蔽**:小心新作用域中的 `:=` +7. **Maps**:使用 mutex 或 sync.Map 進行並發存取 +8. **Defer**:執行 LIFO,參數立即求值 +9. **Goroutine 泄漏**:始終提供取消機制 +10. **字串編碼**:使用 range 進行 Unicode 迭代 +11. **方法接收者**:理解值與指標 +12. **介面**:檢查方法簽名是否完全匹配 + +## Practice Questions +## 練習問題 + +1. 為什麼在某些情況下 `fmt.Println(interface{}(nil) == nil)` 返回 `false`? +2. 使用通道時如何防止 goroutine 泄漏? +3. `close(ch)` 和保持通道開啟有什麼區別? +4. 為什麼預分配切片容量可能會提高效能? +5. Go 的錯誤處理與 Java 的異常有何不同? + +## Project Idea +## 專案想法 + +建立一個"Go 陷阱偵測器"工具: +- 掃描 Go 程式碼以查找常見陷阱 +- 為每個偵測到的問題建議修復 +- 包括錯誤與正確程式碼的範例 +- 為每個陷阱提供解釋 + +## Next Steps +## 下一步 + +- **Module 11**:學習地道的 Go 模式和哲學 +- **Module 12**:效能最佳化技術 +- **Module 13**:部署和生產考慮 +- **Module 14**:建立一個完整的真實世界專案 + +## Further Reading +## 延伸閱讀 + +- [Go Common Mistakes](https://github.com/golang/go/wiki/CommonMistakes) +- [Effective Go](https://go.dev/doc/effective_go) +- [Go Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments) +- [The Go Blog: Go Concurrency Patterns](https://go.dev/blog/concurrency-patterns) diff --git a/content/docs/java2go/module-11-idiomatic-go.mdx b/content/docs/java2go/module-11-idiomatic-go.mdx new file mode 100644 index 0000000..64000cd --- /dev/null +++ b/content/docs/java2go/module-11-idiomatic-go.mdx @@ -0,0 +1,1287 @@ +--- +title: "Module 11: Idiomatic Go" +description: "Learn the Go programming philosophy and idiomatic patterns that distinguish elegant Go code from Java-style Go code" +--- + +# Module 11: Idiomatic Go + +## Learning Objectives + +By the end of this module, you will: +- Understand Go's programming philosophy +- Write idiomatic Go code vs Java-style Go +- Apply "Errors are values" pattern +- Use interfaces effectively (accept interfaces, return structs) +- Leverage defer for resource cleanup +- Understand goroutine ownership patterns +- Use contexts effectively +- Follow Go naming conventions +- Structure packages properly +- Apply common Go patterns and idioms + +## Introduction + +Writing Go code that "feels like Go" rather than "Java translated to Go" is key to becoming an effective Go developer. This module covers the philosophy, patterns, and idioms that make Go code elegant and maintainable. + +## 1. Go Programming Philosophy + +### Less is Exponentially More + + +```java !! java +// Java - Often verbose with multiple layers +public interface UserService { + User getUserById(Long id) throws UserNotFoundException; +} + +public class UserServiceImpl implements UserService { + private final UserRepository userRepository; + private final UserCache userCache; + + @Autowired + public UserServiceImpl(UserRepository userRepository, UserCache userCache) { + this.userRepository = userRepository; + this.userCache = userCache; + } + + @Override + public User getUserById(Long id) throws UserNotFoundException { + User user = userCache.get(id); + if (user == null) { + user = userRepository.findById(id) + .orElseThrow(() -> new UserNotFoundException("User not found: " + id)); + userCache.put(id, user); + } + return user; + } +} +``` + +```go !! go +// Go - Simple and direct +type UserService struct { + repo UserRepository + cache UserCache +} + +func (s *UserService) GetUser(id int64) (*User, error) { + // Try cache first + if user, ok := s.cache.Get(id); ok { + return user, nil + } + + // Fallback to repository + user, err := s.repo.FindByID(id) + if err != nil { + return nil, fmt.Errorf("user not found: %d", id) + } + + s.cache.Put(id, user) + return user, nil +} +``` + + +### Key Principles + +1. **Simplicity**: Avoid unnecessary abstraction +2. **Readability**: Code is read more than written +3. **Orthogonality**: Features work well together +4. **Safety**: Type safety and memory safety +5. **Performance**: Efficient compilation and execution + +## 2. "Errors are Values" Pattern + +### The Philosophy + +In Java, errors are often treated as exceptional control flow. In Go, errors are values that are handled like any other data. + + +```java !! java +// Java - Exceptions for control flow +public void processUser(Long userId) { + try { + User user = userRepository.findById(userId).orElseThrow(); + validateUser(user); + sendEmail(user); + } catch (UserNotFoundException e) { + logger.error("User not found", e); + throw new BusinessException("Invalid user"); + } catch (ValidationException e) { + logger.error("Validation failed", e); + throw new BusinessException("Invalid data"); + } catch (EmailException e) { + logger.error("Email failed", e); + throw new BusinessException("Communication error"); + } +} +``` + +```go !! go +// Go - Errors are values +func (s *Service) ProcessUser(userID int64) error { + user, err := s.repo.FindByID(userID) + if err != nil { + return fmt.Errorf("find user: %w", err) + } + + if err := s.validateUser(user); err != nil { + return fmt.Errorf("validate: %w", err) + } + + if err := s.sendEmail(user); err != nil { + return fmt.Errorf("send email: %w", err) + } + + return nil +} + +// Idiomatic: Sentinel errors +var ( + ErrUserNotFound = errors.New("user not found") + ErrInvalidInput = errors.New("invalid input") + ErrInternalError = errors.New("internal error") +) + +func (s *Service) ProcessUser(userID int64) error { + user, err := s.repo.FindByID(userID) + if errors.Is(err, ErrUserNotFound) { + return ErrUserNotFound + } + if err != nil { + return fmt.Errorf("find user: %w", err) + } + // ... rest of logic + return nil +} +``` + + +### Custom Error Types + + +```java !! java +// Java - Custom exception with error code +public class ApiException extends RuntimeException { + private final ErrorCode code; + private final int statusCode; + + public ApiException(ErrorCode code, String message) { + super(message); + this.code = code; + this.statusCode = code.getHttpCode(); + } +} +``` + +```go !! go +// Go - Custom error type +type APIError struct { + Code ErrorCode + Message string + StatusCode int + Err error +} + +func (e *APIError) Error() string { + if e.Err != nil { + return fmt.Sprintf("%s: %v", e.Message, e.Err) + } + return e.Message +} + +func (e *APIError) Unwrap() error { + return e.Err +} + +// Usage +func (s *Service) ProcessUser(id int64) error { + user, err := s.repo.FindByID(id) + if err != nil { + return &APIError{ + Code: ErrNotFound, + Message: "User not found", + StatusCode: 404, + Err: err, + } + } + return nil +} +``` + + +## 3. Interfaces: Accept Interfaces, Return Structs + +### The Golden Rule + + +```java !! java +// Java - Interfaces everywhere (often overused) +public interface UserRepository extends Repository { +} + +public interface UserService { + User getUserById(Long id); +} + +@Service +public class UserServiceImpl implements UserService { + private final UserRepository userRepository; + + public UserServiceImpl(UserRepository userRepository) { + this.userRepository = userRepository; + } +} +``` + +```go !! go +// Go - Define interfaces at the consumer side +// WRONG: Defining interface alongside implementation +type UserRepository interface { + FindByID(id int64) (*User, error) +} + +type MySQLUserRepository struct { + db *sql.DB +} + +func (r *MySQLUserRepository) FindByID(id int64) (*User, error) { + // ... +} + +// CORRECT: Define interface where it's used +type UserFinder interface { + FindByID(id int64) (*User, error) +} + +type UserService struct { + repo UserFinder // Accept interface +} + +func NewUserService(repo UserFinder) *UserService { + return &UserService{repo: repo} +} + +// Return concrete struct +func NewUserRepository(db *sql.DB) *MySQLUserRepository { + return &MySQLUserRepository{db: db} +} +``` + + +### Small Interfaces + + +```java !! java +// Java - Large interfaces are common +public interface Repository { + T save(T entity); + Optional findById(ID id); + List findAll(); + List findAllById(Iterable ids); + long count(); + void deleteById(ID id); + void delete(T entity); + boolean existsById(ID id); +} +``` + +```go !! go +// Go - Small, focused interfaces +// io package example +type Reader interface { + Read(p []byte) (n int, err error) +} + +type Writer interface { + Write(p []byte) (n int, err error) +} + +type ReadWriter interface { + Reader + Writer +} + +// Compose interfaces as needed +type UserReader interface { + FindByID(id int64) (*User, error) + FindByEmail(email string) (*User, error) +} + +type UserWriter interface { + Create(user *User) error + Update(user *User) error + Delete(id int64) error +} + +type UserReaderWriter interface { + UserReader + UserWriter +} +``` + + +### Interface Satisfaction is Implicit + + +```java !! java +// Java - Explicit implementation +public class FileLogger implements Logger { + @Override + public void log(String message) { + System.out.println(message); + } +} + +// Must declare "implements Logger" +``` + +```go !! go +// Go - Implicit satisfaction +type Logger interface { + Log(message string) +} + +// FileLogger automatically satisfies Logger +type FileLogger struct { + file *os.File +} + +func (l *FileLogger) Log(message string) { + fmt.Fprintln(l.file, message) +} + +// No "implements" declaration needed! + +// This enables adding interface satisfaction externally +type Console struct{} + +func (c *Console) Log(message string) { + fmt.Println(message) +} + +// Console now satisfies Logger interface +``` + + +## 4. Defer for Cleanup + +### Replacing try-finally + + +```java !! java +// Java - try-finally or try-with-resources +public void processFile(String path) throws IOException { + FileInputStream fis = null; + try { + fis = new FileInputStream(path); + // Process file + } finally { + if (fis != null) { + fis.close(); + } + } +} + +// Better: try-with-resources +public void processFile(String path) throws IOException { + try (FileInputStream fis = new FileInputStream(path)) { + // Process file + } +} +``` + +```go !! go +// Go - defer for cleanup +func processFile(path string) error { + f, err := os.Open(path) + if err != nil { + return err + } + defer f.Close() // Always executed + + // Process file + return nil +} + +// Multiple defers execute LIFO +func processMultiple() { + defer fmt.Println("First") + defer fmt.Println("Second") + defer fmt.Println("Third") + + // Output: Third, Second, First +} + +// Defer with error handling +func processFileWithError(path string) error { + f, err := os.Open(path) + if err != nil { + return err + } + defer func() { + if err := f.Close(); err != nil { + log.Printf("Warning: failed to close file: %v", err) + } + }() + + // Process file + return nil +} +``` + + +### Defer Gotchas and Best Practices + + +```go !! go +// GOTCHA: Loop variable capture +func processFiles(files []string) error { + for _, file := range files { + f, err := os.Open(file) + if err != nil { + return err + } + defer f.Close() // WRONG: All closes happen at function exit! + } + return nil +} + +// CORRECT: Use closure in loop +func processFilesCorrect(files []string) error { + for _, file := range files { + if err := func() error { + f, err := os.Open(file) + if err != nil { + return err + } + defer f.Close() // Closed after each iteration + // Process file + return nil + }(); err != nil { + return err + } + } + return nil +} + +// GOTCHA: Parameters evaluated immediately +func process() { + start := time.Now() + defer fmt.Println("Duration:", time.Since(start)) // Evaluated at defer! + + time.Sleep(1 * time.Second) + // Prints Duration: 0s (wrong!) +} + +// CORRECT: Use function +func process() { + start := time.Now() + defer func() { + fmt.Println("Duration:", time.Since(start)) // Evaluated at execution + }() + + time.Sleep(1 * time.Second) + // Prints Duration: 1s (correct!) +} +``` + + +## 5. Goroutine Ownership + +### Who Creates, Who Cleans Up + + +```java !! java +// Java - ExecutorService manages thread lifecycle +ExecutorService executor = Executors.newFixedThreadPool(10); + +public void processTasks(List tasks) { + List> futures = new ArrayList<>(); + for (Task task : tasks) { + Future future = executor.submit(() -> { + return task.execute(); + }); + futures.add(future); + } + + // Wait for all to complete + for (Future future : futures) { + future.get(); // blocks + } +} +``` + +```go !! go +// Go - Parent goroutine manages children +func ProcessTasks(ctx context.Context, tasks []Task) ([]Result, error) { + ctx, cancel := context.WithCancel(ctx) + defer cancel() // Ensure children are cancelled + + results := make([]Result, len(tasks)) + errChan := make(chan error, len(tasks)) + + for i, task := range tasks { + go func(idx int, t Task) { + result, err := t.Execute(ctx) + if err != nil { + errChan <- err + cancel() // Cancel other goroutines + return + } + results[idx] = result + }(i, task) + } + + // Wait for all or first error + for range tasks { + select { + case <-ctx.Done(): + return nil, ctx.Err() + case err := <-errChan: + return nil, err + } + } + + return results, nil +} +``` + + +### WaitGroups for Coordination + + +```java !! java +// Java - CountDownLatch +CountDownLatch latch = new CountDownLatch(taskCount); +for (Task task : tasks) { + executor.submit(() -> { + try { + task.execute(); + } finally { + latch.countDown(); + } + }); +} +latch.await(); // Wait for all +``` + +```go !! go +// Go - WaitGroup +func processTasks(tasks []Task) { + var wg sync.WaitGroup + + for _, task := range tasks { + wg.Add(1) // Increment counter + go func(t Task) { + defer wg.Done() // Decrement when done + t.Execute() + }(task) + } + + wg.Wait() // Wait for all goroutines +} + +// With error handling +func processTasks(tasks []Task) error { + var wg sync.WaitGroup + errChan := make(chan error, len(tasks)) + + for _, task := range tasks { + wg.Add(1) + go func(t Task) { + defer wg.Done() + if err := t.Execute(); err != nil { + errChan <- err + } + }(task) + } + + wg.Wait() + close(errChan) + + // Collect errors + var errors []error + for err := range errChan { + errors = append(errors, err) + } + + if len(errors) > 0 { + return fmt.Errorf("tasks failed: %v", errors) + } + return nil +} +``` + + +## 6. Effective Use of Contexts + +### Context for Cancellation + + +```java !! java +// Java - Future cancellation +Future future = executor.submit(() -> { + return longRunningTask(); +}); + +try { + String result = future.get(5, TimeUnit.SECONDS); +} catch (TimeoutException e) { + future.cancel(true); // May interrupt +} +``` + +```go !! go +// Go - Context for cancellation +func longRunningTask(ctx context.Context) error { + for { + select { + case <-ctx.Done(): + return ctx.Err() // Context cancelled + default: + // Do work + if err := processChunk(); err != nil { + return err + } + } + } +} + +// Usage +func processWithTimeout() error { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + return longRunningTask(ctx) +} +``` + + +### Context Values (Use Sparingly) + + +```java !! java +// Java - ThreadLocal +public class RequestContext { + private static final ThreadLocal userId = new ThreadLocal<>(); + + public static void setUserId(String id) { + userId.set(id); + } + + public static String getUserId() { + return userId.get(); + } +} +``` + +```go !! go +// Go - Context values (use sparingly!) +type contextKey string + +const ( + userIDKey contextKey = "userID" + traceIDKey contextKey = "traceID" +) + +func withUserID(ctx context.Context, userID string) context.Context { + return context.WithValue(ctx, userIDKey, userID) +} + +func getUserID(ctx context.Context) (string, bool) { + userID, ok := ctx.Value(userIDKey).(string) + return userID, ok +} + +// Usage +func handleRequest(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + userID, ok := getUserID(ctx) + if !ok { + http.Error(w, "Unauthorized", http.StatusUnauthorized) + return + } + + // Process request +} +``` + + +## 7. Naming Conventions + +### Package Names + + +```java !! java +// Java - Reverse domain notation +package com.example.myapp.service; + +public class UserService { + // ... +} +``` + +```go !! go +// Go - Short, lowercase, single word +// In file: user/service.go +package service + +type Service struct { + // Since package is 'service', type is just 'User', not 'UserService' +} + +func NewService() *Service { + return &Service{} +} +``` + + +### Interface Names + + +```java !! java +// Java - Often ends with "Impl" for implementations +public interface UserRepository { +} + +public class JdbcUserRepository implements UserRepository { +} + +public class JpaUserRepository implements UserRepository { +} +``` + +```go !! go +// Go - Interface names often end with -er +type Reader interface { + Read(p []byte) (n int, err error) +} + +type Writer interface { + Write(p []byte) (n int, err error) +} + +// For more complex interfaces, use descriptive names +type UserRepository interface { + FindByID(id int64) (*User, error) +} + +type MySQLUserRepository struct { + db *sql.DB +} + +type PostgresUserRepository struct { + db *sql.DB +} +``` + + +### Acronyms + + +```java !! java +// Java - Acronyms are uppercase +URL url = new URL("https://example.com"); +HTTPServer server = new HTTPServer(); +``` + +```go !! go +// Go - Acronyms are treated as words +// Correct +type URL struct { + Scheme string + Host string +} + +func getURL() *URL { + return &URL{} +} + +type HTTPServer struct { + addr string +} + +// Incorrect (don't do this) +type Url struct {} +type HttpServer struct {} +``` + + +## 8. Package Structure + +### Standard Layout + + +``` +// Java - Maven/Gradle structure +src/ +├── main/ +│ ├── java/ +│ │ └── com/ +│ │ └── example/ +│ │ └── app/ +│ │ ├── controller/ +│ │ ├── service/ +│ │ ├── repository/ +│ │ └── model/ +│ └── resources/ +└── test/ + └── java/ +``` + +``` +# Go - Standard project layout +myapp/ +├── cmd/ +│ ├── myapp/ +│ │ └── main.go # Application entry point +│ └── myappctl/ +│ └── main.go # CLI tool +├── internal/ +│ ├── auth/ # Private auth code +│ ├── database/ # Private database code +│ └── user/ # Private user logic +├── pkg/ +│ ├── api/ # Public API library +│ └── util/ # Public utilities +├── api/ +│ ├── openapi/ # OpenAPI specs +│ └── proto/ # Protocol buffers +├── web/ +│ ├── static/ # Static assets +│ └── templates/ # HTML templates +├── configs/ # Configuration files +├── scripts/ # Build and deployment scripts +├── test/ # Additional test data +├── docs/ # Documentation +├── go.mod +├── go.sum +├── Makefile +└── README.md +``` + + +### The internal Package + + +```go !! go +// internal/auth/auth.go +package auth + +// This code cannot be imported by packages outside myapp +type Authenticator struct { + // ... +} + +func New() *Authenticator { + return &Authenticator{} +} + +// cmd/myapp/main.go can import this +package main + +import "myapp/internal/auth" + +func main() { + auth := auth.New() + // ... +} + +// But external packages cannot: +// This would fail to compile: +// package external +// import "myapp/internal/auth" // ERROR! +``` + + +## 9. Code Organization + +### File Organization + + +```java !! java +// Java - One public class per file +// UserService.java +package com.example.service; + +public class UserService { + // All user service logic here +} + +// UserRepository.java +package com.example.repository; + +public interface UserRepository { + // All repository methods here +} +``` + +```go !! go +// Go - Organize by functionality, not just types +// service.go +package user + +type Service struct { + repo Repository + cache Cache +} + +func NewService(repo Repository, cache Cache) *Service { + return &Service{ + repo: repo, + cache: cache, + } +} + +func (s *Service) GetUser(id int64) (*User, error) { + // ... +} + +// repository.go +package user + +type Repository interface { + FindByID(id int64) (*User, error) +} + +type mysqlRepository struct { + db *sql.DB +} + +func NewMySQLRepository(db *sql.DB) Repository { + return &mysqlRepository{db: db} +} + +// models.go +package user + +type User struct { + ID int64 + Email string + Name string + CreatedAt time.Time +} +``` + + +### Exporting Rules + + +```java !! java +// Java - public/private keywords +public class UserService { + private UserRepository repository; + + public UserService(UserRepository repository) { + this.repository = repository; + } + + public User getUser(Long id) { + return repository.findById(id); + } + + private void validate(User user) { + // Internal validation + } +} +``` + +```go !! go +// Go - Capitalization determines export +package service + +type Service struct { // Exported + repo repository // Unexported + cache cache // Unexported +} + +func NewService(repo repository) *Service { + return &Service{repo: repo} +} + +func (s *Service) GetUser(id int64) (*User, error) { // Exported + return s.repo.FindByID(id) +} + +func (s *Service) validate(user *User) error { // Unexported + // Internal validation + return nil +} + +// Interface fields must be exported in embedding +type Server struct { + *http.Server // Embedded, exported + logger *log.Logger // Unexported field +} +``` + + +## 10. Common Go Patterns + +### The Option Pattern + + +```java !! java +// Java - Builder pattern +public class Server { + private int port = 8080; + private String host = "localhost"; + private int timeout = 30; + + private Server(Builder builder) { + this.port = builder.port; + this.host = builder.host; + this.timeout = builder.timeout; + } + + public static class Builder { + private int port = 8080; + private String host = "localhost"; + private int timeout = 30; + + public Builder port(int port) { + this.port = port; + return this; + } + + public Builder host(String host) { + this.host = host; + return this; + } + + public Builder timeout(int timeout) { + this.timeout = timeout; + return this; + } + + public Server build() { + return new Server(this); + } + } +} + +// Usage +Server server = new Server.Builder() + .port(9090) + .host("0.0.0.0") + .timeout(60) + .build(); +``` + +```go !! go +// Go - Functional options pattern +type Server struct { + port int + host string + timeout time.Duration +} + +type Option func(*Server) + +func WithPort(port int) Option { + return func(s *Server) { + s.port = port + } +} + +func WithHost(host string) Option { + return func(s *Server) { + s.host = host + } +} + +func WithTimeout(timeout time.Duration) Option { + return func(s *Server) { + s.timeout = timeout + } +} + +func NewServer(opts ...Option) *Server { + server := &Server{ + port: 8080, + host: "localhost", + timeout: 30 * time.Second, + } + + for _, opt := range opts { + opt(server) + } + + return server +} + +// Usage +server := NewServer( + WithPort(9090), + WithHost("0.0.0.0"), + WithTimeout(60*time.Second), +) +``` + + +### The Table-Driven Test + + +```java !! java +// Java - Separate test methods +@Test +public void testAddition() { + assertEquals(4, Calculator.add(2, 2)); + assertEquals(0, Calculator.add(0, 0)); + assertEquals(-2, Calculator.add(2, -4)); +} + +@Test +public void testSubtraction() { + assertEquals(0, Calculator.subtract(2, 2)); + assertEquals(2, Calculator.subtract(0, -2)); +} +``` + +```go !! go +// Go - Table-driven tests +func TestAdd(t *testing.T) { + tests := []struct { + name string + a, b int + expected int + }{ + {"positive numbers", 2, 2, 4}, + {"zeros", 0, 0, 0}, + {"negative result", 2, -4, -2}, + {"large numbers", 1000000, 2000000, 3000000}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := Add(tt.a, tt.b) + if result != tt.expected { + t.Errorf("Add(%d, %d) = %d; want %d", + tt.a, tt.b, result, tt.expected) + } + }) + } +} + +// Test helpers +func assertEqual[T comparable](t *testing.T, got, want T) { + t.Helper() + if got != want { + t.Errorf("got %v, want %v", got, want) + } +} +``` + + +### Channel Patterns + + +```java !! java +// Java - Stream API +List result = numbers.stream() + .filter(n -> n % 2 == 0) + .map(n -> n * 2) + .collect(Collectors.toList()); +``` + +```go !! go +// Go - Pipeline with goroutines and channels +func generator(nums ...int) <-chan int { + out := make(chan int) + go func() { + for _, n := range nums { + out <- n + } + close(out) + }() + return out +} + +func filter(in <-chan int, predicate func(int) bool) <-chan int { + out := make(chan int) + go func() { + for n := range in { + if predicate(n) { + out <- n + } + } + close(out) + }() + return out +} + +func mapChan(in <-chan int, transform func(int) int) <-chan int { + out := make(chan int) + go func() { + for n := range in { + out <- transform(n) + } + close(out) + }() + return out +} + +// Usage +func main() { + numbers := generator(1, 2, 3, 4, 5, 6) + + evens := filter(numbers, func(n int) bool { + return n%2 == 0 + }) + + doubled := mapChan(evens, func(n int) int { + return n * 2 + }) + + for result := range doubled { + fmt.Println(result) + } +} +``` + + +## Summary + +Key principles for writing idiomatic Go: + +1. **Simplicity**: Avoid over-engineering +2. **Errors are values**: Handle errors explicitly, don't use exceptions +3. **Accept interfaces, return structs**: Define interfaces at consumption point +4. **Use defer**: For cleanup and resource management +5. **Own your goroutines**: Manage their lifecycle +6. **Context for cancellation**: Use contexts for deadlines and cancellation +7. **Follow naming conventions**: Package names, interface names, acronyms +8. **Organize by functionality**: Not by type like Java +9. **Use functional options**: For configurable objects +10. **Table-driven tests**: For comprehensive testing + +## Practice Questions + +1. Why should interfaces be defined at the consumer side rather than the producer side? +2. What's wrong with using context values for request-scoped data? +3. When should you use `defer` versus explicit cleanup? +4. How does Go's implicit interface satisfaction affect package design? +5. Why are small interfaces preferred over large ones? + +## Project Idea + +Create a "Go Idioms Linter" that: +- Detects Java-style patterns in Go code +- Suggests more idiomatic alternatives +- Provides before/after examples +- Covers common patterns from this module + +## Next Steps + +- **Module 12**: Performance optimization techniques +- **Module 13**: Deployment and production considerations +- **Module 14**: Build a complete real-world project + +## Further Reading + +- [Effective Go](https://go.dev/doc/effective_go) +- [Go Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments) +- [The Go Blog: Go Proverbs](https://go-proverbs.github.io/) +- [Standard Library Package Names](https://go.dev/pkg/) diff --git a/content/docs/java2go/module-11-idiomatic-go.zh-cn.mdx b/content/docs/java2go/module-11-idiomatic-go.zh-cn.mdx new file mode 100644 index 0000000..b7b746c --- /dev/null +++ b/content/docs/java2go/module-11-idiomatic-go.zh-cn.mdx @@ -0,0 +1,1287 @@ +--- +title: "模块 11: 地道的 Go 语言" +description: "学习 Go 编程哲学和惯用模式,这些模式能让优雅的 Go 代码区别于 Java 风格的 Go 代码" +--- + +# 模块 11: 地道的 Go 语言 + +## 学习目标 + +学完本模块后,你将: +- 理解 Go 的编程哲学 +- 编写地道的 Go 代码而非 Java 风格的 Go 代码 +- 应用"错误是值"模式 +- 有效使用接口(接受接口,返回结构体) +- 利用 defer 进行资源清理 +- 理解 goroutine 所有权模式 +- 有效使用 context +- 遵循 Go 命名约定 +- 合理组织包结构 +- 应用常见的 Go 模式和惯用法 + +## 简介 + +编写"感觉像 Go"而非"Java 翻译成 Go"的代码是成为高效 Go 开发者的关键。本模块涵盖使 Go 代码优雅和可维护的哲学、模式和惯用法。 + +## 1. Go 编程哲学 + +### 少即是指数级的更多 + + +```java !! java +// Java - 通常冗长且有多层抽象 +public interface UserService { + User getUserById(Long id) throws UserNotFoundException; +} + +public class UserServiceImpl implements UserService { + private final UserRepository userRepository; + private final UserCache userCache; + + @Autowired + public UserServiceImpl(UserRepository userRepository, UserCache userCache) { + this.userRepository = userRepository; + this.userCache = userCache; + } + + @Override + public User getUserById(Long id) throws UserNotFoundException { + User user = userCache.get(id); + if (user == null) { + user = userRepository.findById(id) + .orElseThrow(() -> new UserNotFoundException("User not found: " + id)); + userCache.put(id, user); + } + return user; + } +} +``` + +```go !! go +// Go - 简单直接 +type UserService struct { + repo UserRepository + cache UserCache +} + +func (s *UserService) GetUser(id int64) (*User, error) { + // 先尝试缓存 + if user, ok := s.cache.Get(id); ok { + return user, nil + } + + // 回退到仓库 + user, err := s.repo.FindByID(id) + if err != nil { + return nil, fmt.Errorf("user not found: %d", id) + } + + s.cache.Put(id, user) + return user, nil +} +``` + + +### 核心原则 + +1. **简单性**: 避免不必要的抽象 +2. **可读性**: 代码被阅读的次数多于编写的次数 +3. **正交性**: 功能特性协同工作 +4. **安全性**: 类型安全和内存安全 +5. **性能**: 高效的编译和执行 + +## 2. "错误是值"模式 + +### 哲学 + +在 Java 中,错误通常被视为异常控制流。在 Go 中,错误是像任何其他数据一样处理的值。 + + +```java !! java +// Java - 使用异常进行控制流 +public void processUser(Long userId) { + try { + User user = userRepository.findById(userId).orElseThrow(); + validateUser(user); + sendEmail(user); + } catch (UserNotFoundException e) { + logger.error("User not found", e); + throw new BusinessException("Invalid user"); + } catch (ValidationException e) { + logger.error("Validation failed", e); + throw new BusinessException("Invalid data"); + } catch (EmailException e) { + logger.error("Email failed", e); + throw new BusinessException("Communication error"); + } +} +``` + +```go !! go +// Go - 错误是值 +func (s *Service) ProcessUser(userID int64) error { + user, err := s.repo.FindByID(userID) + if err != nil { + return fmt.Errorf("find user: %w", err) + } + + if err := s.validateUser(user); err != nil { + return fmt.Errorf("validate: %w", err) + } + + if err := s.sendEmail(user); err != nil { + return fmt.Errorf("send email: %w", err) + } + + return nil +} + +// 惯用法: 哨兵错误 +var ( + ErrUserNotFound = errors.New("user not found") + ErrInvalidInput = errors.New("invalid input") + ErrInternalError = errors.New("internal error") +) + +func (s *Service) ProcessUser(userID int64) error { + user, err := s.repo.FindByID(userID) + if errors.Is(err, ErrUserNotFound) { + return ErrUserNotFound + } + if err != nil { + return fmt.Errorf("find user: %w", err) + } + // ... 其余逻辑 + return nil +} +``` + + +### 自定义错误类型 + + +```java !! java +// Java - 带错误码的自定义异常 +public class ApiException extends RuntimeException { + private final ErrorCode code; + private final int statusCode; + + public ApiException(ErrorCode code, String message) { + super(message); + this.code = code; + this.statusCode = code.getHttpCode(); + } +} +``` + +```go !! go +// Go - 自定义错误类型 +type APIError struct { + Code ErrorCode + Message string + StatusCode int + Err error +} + +func (e *APIError) Error() string { + if e.Err != nil { + return fmt.Sprintf("%s: %v", e.Message, e.Err) + } + return e.Message +} + +func (e *APIError) Unwrap() error { + return e.Err +} + +// 使用 +func (s *Service) ProcessUser(id int64) error { + user, err := s.repo.FindByID(id) + if err != nil { + return &APIError{ + Code: ErrNotFound, + Message: "User not found", + StatusCode: 404, + Err: err, + } + } + return nil +} +``` + + +## 3. 接口: 接受接口,返回结构体 + +### 黄金法则 + + +```java !! java +// Java - 到处都是接口(经常过度使用) +public interface UserRepository extends Repository { +} + +public interface UserService { + User getUserById(Long id); +} + +@Service +public class UserServiceImpl implements UserService { + private final UserRepository userRepository; + + public UserServiceImpl(UserRepository userRepository) { + this.userRepository = userRepository; + } +} +``` + +```go !! go +// Go - 在消费者端定义接口 +// 错误: 在实现旁边定义接口 +type UserRepository interface { + FindByID(id int64) (*User, error) +} + +type MySQLUserRepository struct { + db *sql.DB +} + +func (r *MySQLUserRepository) FindByID(id int64) (*User, error) { + // ... +} + +// 正确: 在使用的地方定义接口 +type UserFinder interface { + FindByID(id int64) (*User, error) +} + +type UserService struct { + repo UserFinder // 接受接口 +} + +func NewUserService(repo UserFinder) *UserService { + return &UserService{repo: repo} +} + +// 返回具体结构体 +func NewUserRepository(db *sql.DB) *MySQLUserRepository { + return &MySQLUserRepository{db: db} +} +``` + + +### 小接口 + + +```java !! java +// Java - 大接口很常见 +public interface Repository { + T save(T entity); + Optional findById(ID id); + List findAll(); + List findAllById(Iterable ids); + long count(); + void deleteById(ID id); + void delete(T entity); + boolean existsById(ID id); +} +``` + +```go !! go +// Go - 小而专注的接口 +// io 包示例 +type Reader interface { + Read(p []byte) (n int, err error) +} + +type Writer interface { + Write(p []byte) (n int, err error) +} + +type ReadWriter interface { + Reader + Writer +} + +// 按需组合接口 +type UserReader interface { + FindByID(id int64) (*User, error) + FindByEmail(email string) (*User, error) +} + +type UserWriter interface { + Create(user *User) error + Update(user *User) error + Delete(id int64) error +} + +type UserReaderWriter interface { + UserReader + UserWriter +} +``` + + +### 接口满足是隐式的 + + +```java !! java +// Java - 显式实现 +public class FileLogger implements Logger { + @Override + public void log(String message) { + System.out.println(message); + } +} + +// 必须声明 "implements Logger" +``` + +```go !! go +// Go - 隐式满足 +type Logger interface { + Log(message string) +} + +// FileLogger 自动满足 Logger +type FileLogger struct { + file *os.File +} + +func (l *FileLogger) Log(message string) { + fmt.Fprintln(l.file, message) +} + +// 不需要 "implements" 声明! + +// 这使得可以在外部添加接口满足 +type Console struct{} + +func (c *Console) Log(message string) { + fmt.Println(message) +} + +// Console 现在满足 Logger 接口 +``` + + +## 4. 使用 Defer 进行清理 + +### 替换 try-finally + + +```java !! java +// Java - try-finally 或 try-with-resources +public void processFile(String path) throws IOException { + FileInputStream fis = null; + try { + fis = new FileInputStream(path); + // 处理文件 + } finally { + if (fis != null) { + fis.close(); + } + } +} + +// 更好: try-with-resources +public void processFile(String path) throws IOException { + try (FileInputStream fis = new FileInputStream(path)) { + // 处理文件 + } +} +``` + +```go !! go +// Go - defer 用于清理 +func processFile(path string) error { + f, err := os.Open(path) + if err != nil { + return err + } + defer f.Close() // 总是执行 + + // 处理文件 + return nil +} + +// 多个 defer 按 LIFO 顺序执行 +func processMultiple() { + defer fmt.Println("First") + defer fmt.Println("Second") + defer fmt.Println("Third") + + // 输出: Third, Second, First +} + +// 带错误处理的 defer +func processFileWithError(path string) error { + f, err := os.Open(path) + if err != nil { + return err + } + defer func() { + if err := f.Close(); err != nil { + log.Printf("Warning: failed to close file: %v", err) + } + }() + + // 处理文件 + return nil +} +``` + + +### Defer 陷阱和最佳实践 + + +```go !! go +// 陷阱: 循环变量捕获 +func processFiles(files []string) error { + for _, file := range files { + f, err := os.Open(file) + if err != nil { + return err + } + defer f.Close() // 错误: 所有 close 在函数退出时发生! + } + return nil +} + +// 正确: 在循环中使用闭包 +func processFilesCorrect(files []string) error { + for _, file := range files { + if err := func() error { + f, err := os.Open(file) + if err != nil { + return err + } + defer f.Close() // 每次迭代后关闭 + // 处理文件 + return nil + }(); err != nil { + return err + } + } + return nil +} + +// 陷阱: 参数立即求值 +func process() { + start := time.Now() + defer fmt.Println("Duration:", time.Since(start)) // 在 defer 时求值! + + time.Sleep(1 * time.Second) + // 输出 Duration: 0s (错误!) +} + +// 正确: 使用函数 +func process() { + start := time.Now() + defer func() { + fmt.Println("Duration:", time.Since(start)) // 执行时求值 + }() + + time.Sleep(1 * time.Second) + // 输出 Duration: 1s (正确!) +} +``` + + +## 5. Goroutine 所有权 + +### 谁创建,谁清理 + + +```java !! java +// Java - ExecutorService 管理线程生命周期 +ExecutorService executor = Executors.newFixedThreadPool(10); + +public void processTasks(List tasks) { + List> futures = new ArrayList<>(); + for (Task task : tasks) { + Future future = executor.submit(() -> { + return task.execute(); + }); + futures.add(future); + } + + // 等待所有完成 + for (Future future : futures) { + future.get(); // 阻塞 + } +} +``` + +```go !! go +// Go - 父 goroutine 管理子 goroutine +func ProcessTasks(ctx context.Context, tasks []Task) ([]Result, error) { + ctx, cancel := context.WithCancel(ctx) + defer cancel() // 确保子 goroutine 被取消 + + results := make([]Result, len(tasks)) + errChan := make(chan error, len(tasks)) + + for i, task := range tasks { + go func(idx int, t Task) { + result, err := t.Execute(ctx) + if err != nil { + errChan <- err + cancel() // 取消其他 goroutine + return + } + results[idx] = result + }(i, task) + } + + // 等待所有或第一个错误 + for range tasks { + select { + case <-ctx.Done(): + return nil, ctx.Err() + case err := <-errChan: + return nil, err + } + } + + return results, nil +} +``` + + +### 使用 WaitGroup 进行协调 + + +```java !! java +// Java - CountDownLatch +CountDownLatch latch = new CountDownLatch(taskCount); +for (Task task : tasks) { + executor.submit(() -> { + try { + task.execute(); + } finally { + latch.countDown(); + } + }); +} +latch.await(); // 等待所有 +``` + +```go !! go +// Go - WaitGroup +func processTasks(tasks []Task) { + var wg sync.WaitGroup + + for _, task := range tasks { + wg.Add(1) // 增加计数器 + go func(t Task) { + defer wg.Done() // 完成时减少计数器 + t.Execute() + }(task) + } + + wg.Wait() // 等待所有 goroutine +} + +// 带错误处理 +func processTasks(tasks []Task) error { + var wg sync.WaitGroup + errChan := make(chan error, len(tasks)) + + for _, task := range tasks { + wg.Add(1) + go func(t Task) { + defer wg.Done() + if err := t.Execute(); err != nil { + errChan <- err + } + }(task) + } + + wg.Wait() + close(errChan) + + // 收集错误 + var errors []error + for err := range errChan { + errors = append(errors, err) + } + + if len(errors) > 0 { + return fmt.Errorf("tasks failed: %v", errors) + } + return nil +} +``` + + +## 6. 有效使用 Context + +### Context 用于取消 + + +```java !! java +// Java - Future 取消 +Future future = executor.submit(() -> { + return longRunningTask(); +}); + +try { + String result = future.get(5, TimeUnit.SECONDS); +} catch (TimeoutException e) { + future.cancel(true); // 可能中断 +} +``` + +```go !! go +// Go - Context 用于取消 +func longRunningTask(ctx context.Context) error { + for { + select { + case <-ctx.Done(): + return ctx.Err() // Context 被取消 + default: + // 执行工作 + if err := processChunk(); err != nil { + return err + } + } + } +} + +// 使用 +func processWithTimeout() error { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + return longRunningTask(ctx) +} +``` + + +### Context 值(谨慎使用) + + +```java !! java +// Java - ThreadLocal +public class RequestContext { + private static final ThreadLocal userId = new ThreadLocal<>(); + + public static void setUserId(String id) { + userId.set(id); + } + + public static String getUserId() { + return userId.get(); + } +} +``` + +```go !! go +// Go - Context 值(谨慎使用!) +type contextKey string + +const ( + userIDKey contextKey = "userID" + traceIDKey contextKey = "traceID" +) + +func withUserID(ctx context.Context, userID string) context.Context { + return context.WithValue(ctx, userIDKey, userID) +} + +func getUserID(ctx context.Context) (string, bool) { + userID, ok := ctx.Value(userIDKey).(string) + return userID, ok +} + +// 使用 +func handleRequest(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + userID, ok := getUserID(ctx) + if !ok { + http.Error(w, "Unauthorized", http.StatusUnauthorized) + return + } + + // 处理请求 +} +``` + + +## 7. 命名约定 + +### 包名 + + +```java !! java +// Java - 反向域名符号 +package com.example.myapp.service; + +public class UserService { + // ... +} +``` + +```go !! go +// Go - 短小、小写、单词 +// 在文件: user/service.go 中 +package service + +type Service struct { + // 因为包名是 'service',类型是 'User',不是 'UserService' +} + +func NewService() *Service { + return &Service{} +} +``` + + +### 接口名 + + +```java !! java +// Java - 实现类通常以 "Impl" 结尾 +public interface UserRepository { +} + +public class JdbcUserRepository implements UserRepository { +} + +public class JpaUserRepository implements UserRepository { +} +``` + +```go !! go +// Go - 接口名通常以 -er 结尾 +type Reader interface { + Read(p []byte) (n int, err error) +} + +type Writer interface { + Write(p []byte) (n int, err error) +} + +// 对于更复杂的接口,使用描述性名称 +type UserRepository interface { + FindByID(id int64) (*User, error) +} + +type MySQLUserRepository struct { + db *sql.DB +} + +type PostgresUserRepository struct { + db *sql.DB +} +``` + + +### 缩写词 + + +```java !! java +// Java - 缩写词大写 +URL url = new URL("https://example.com"); +HTTPServer server = new HTTPServer(); +``` + +```go !! go +// Go - 缩写词被视为单词 +// 正确 +type URL struct { + Scheme string + Host string +} + +func getURL() *URL { + return &URL{} +} + +type HTTPServer struct { + addr string +} + +// 错误(不要这样做) +type Url struct {} +type HttpServer struct {} +``` + + +## 8. 包结构 + +### 标准布局 + + +``` +// Java - Maven/Gradle 结构 +src/ +├── main/ +│ ├── java/ +│ │ └── com/ +│ │ └── example/ +│ │ └── app/ +│ │ ├── controller/ +│ │ ├── service/ +│ │ ├── repository/ +│ │ └── model/ +│ └── resources/ +└── test/ + └── java/ +``` + +``` +# Go - 标准项目布局 +myapp/ +├── cmd/ +│ ├── myapp/ +│ │ └── main.go # 应用程序入口 +│ └── myappctl/ +│ └── main.go # CLI 工具 +├── internal/ +│ ├── auth/ # 私有 auth 代码 +│ ├── database/ # 私有 database 代码 +│ └── user/ # 私有 user 逻辑 +├── pkg/ +│ ├── api/ # 公共 API 库 +│ └── util/ # 公共工具 +├── api/ +│ ├── openapi/ # OpenAPI 规范 +│ └── proto/ # Protocol buffers +├── web/ +│ ├── static/ # 静态资源 +│ └── templates/ # HTML 模板 +├── configs/ # 配置文件 +├── scripts/ # 构建和部署脚本 +├── test/ # 额外的测试数据 +├── docs/ # 文档 +├── go.mod +├── go.sum +├── Makefile +└── README.md +``` + + +### internal 包 + + +```go !! go +// internal/auth/auth.go +package auth + +// 此代码不能被 myapp 外部的包导入 +type Authenticator struct { + // ... +} + +func New() *Authenticator { + return &Authenticator{} +} + +// cmd/myapp/main.go 可以导入这个 +package main + +import "myapp/internal/auth" + +func main() { + auth := auth.New() + // ... +} + +// 但外部包不能: +// 这将无法编译: +// package external +// import "myapp/internal/auth" // 错误! +``` + + +## 9. 代码组织 + +### 文件组织 + + +```java !! java +// Java - 每个文件一个公共类 +// UserService.java +package com.example.service; + +public class UserService { + // 所有用户服务逻辑在这里 +} + +// UserRepository.java +package com.example.repository; + +public interface UserRepository { + // 所有仓库方法在这里 +} +``` + +```go !! go +// Go - 按功能组织,而不仅仅是类型 +// service.go +package user + +type Service struct { + repo Repository + cache Cache +} + +func NewService(repo Repository, cache Cache) *Service { + return &Service{ + repo: repo, + cache: cache, + } +} + +func (s *Service) GetUser(id int64) (*User, error) { + // ... +} + +// repository.go +package user + +type Repository interface { + FindByID(id int64) (*User, error) +} + +type mysqlRepository struct { + db *sql.DB +} + +func NewMySQLRepository(db *sql.DB) Repository { + return &mysqlRepository{db: db} +} + +// models.go +package user + +type User struct { + ID int64 + Email string + Name string + CreatedAt time.Time +} +``` + + +### 导出规则 + + +```java !! java +// Java - public/private 关键字 +public class UserService { + private UserRepository repository; + + public UserService(UserRepository repository) { + this.repository = repository; + } + + public User getUser(Long id) { + return repository.findById(id); + } + + private void validate(User user) { + // 内部验证 + } +} +``` + +```go !! go +// Go - 大小写决定导出 +package service + +type Service struct { // 导出 + repo repository // 未导出 + cache cache // 未导出 +} + +func NewService(repo repository) *Service { + return &Service{repo: repo} +} + +func (s *Service) GetUser(id int64) (*User, error) { // 导出 + return s.repo.FindByID(id) +} + +func (s *Service) validate(user *User) error { // 未导出 + // 内部验证 + return nil +} + +// 嵌入中的接口字段必须导出 +type Server struct { + *http.Server // 嵌入,导出 + logger *log.Logger // 未导出字段 +} +``` + + +## 10. 常见 Go 模式 + +### 选项模式 + + +```java !! java +// Java - Builder 模式 +public class Server { + private int port = 8080; + private String host = "localhost"; + private int timeout = 30; + + private Server(Builder builder) { + this.port = builder.port; + this.host = builder.host; + this.timeout = builder.timeout; + } + + public static class Builder { + private int port = 8080; + private String host = "localhost"; + private int timeout = 30; + + public Builder port(int port) { + this.port = port; + return this; + } + + public Builder host(String host) { + this.host = host; + return this; + } + + public Builder timeout(int timeout) { + this.timeout = timeout; + return this; + } + + public Server build() { + return new Server(this); + } + } +} + +// 使用 +Server server = new Server.Builder() + .port(9090) + .host("0.0.0.0") + .timeout(60) + .build(); +``` + +```go !! go +// Go - 函数式选项模式 +type Server struct { + port int + host string + timeout time.Duration +} + +type Option func(*Server) + +func WithPort(port int) Option { + return func(s *Server) { + s.port = port + } +} + +func WithHost(host string) Option { + return func(s *Server) { + s.host = host + } +} + +func WithTimeout(timeout time.Duration) Option { + return func(s *Server) { + s.timeout = timeout + } +} + +func NewServer(opts ...Option) *Server { + server := &Server{ + port: 8080, + host: "localhost", + timeout: 30 * time.Second, + } + + for _, opt := range opts { + opt(server) + } + + return server +} + +// 使用 +server := NewServer( + WithPort(9090), + WithHost("0.0.0.0"), + WithTimeout(60*time.Second), +) +``` + + +### 表驱动测试 + + +```java !! java +// Java - 独立的测试方法 +@Test +public void testAddition() { + assertEquals(4, Calculator.add(2, 2)); + assertEquals(0, Calculator.add(0, 0)); + assertEquals(-2, Calculator.add(2, -4)); +} + +@Test +public void testSubtraction() { + assertEquals(0, Calculator.subtract(2, 2)); + assertEquals(2, Calculator.subtract(0, -2)); +} +``` + +```go !! go +// Go - 表驱动测试 +func TestAdd(t *testing.T) { + tests := []struct { + name string + a, b int + expected int + }{ + {"positive numbers", 2, 2, 4}, + {"zeros", 0, 0, 0}, + {"negative result", 2, -4, -2}, + {"large numbers", 1000000, 2000000, 3000000}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := Add(tt.a, tt.b) + if result != tt.expected { + t.Errorf("Add(%d, %d) = %d; want %d", + tt.a, tt.b, result, tt.expected) + } + }) + } +} + +// 测试辅助函数 +func assertEqual[T comparable](t *testing.T, got, want T) { + t.Helper() + if got != want { + t.Errorf("got %v, want %v", got, want) + } +} +``` + + +### 通道模式 + + +```java !! java +// Java - Stream API +List result = numbers.stream() + .filter(n -> n % 2 == 0) + .map(n -> n * 2) + .collect(Collectors.toList()); +``` + +```go !! go +// Go - 使用 goroutine 和通道的管道 +func generator(nums ...int) <-chan int { + out := make(chan int) + go func() { + for _, n := range nums { + out <- n + } + close(out) + }() + return out +} + +func filter(in <-chan int, predicate func(int) bool) <-chan int { + out := make(chan int) + go func() { + for n := range in { + if predicate(n) { + out <- n + } + } + close(out) + }() + return out +} + +func mapChan(in <-chan int, transform func(int) int) <-chan int { + out := make(chan int) + go func() { + for n := range in { + out <- transform(n) + } + close(out) + }() + return out +} + +// 使用 +func main() { + numbers := generator(1, 2, 3, 4, 5, 6) + + evens := filter(numbers, func(n int) bool { + return n%2 == 0 + }) + + doubled := mapChan(evens, func(n int) int { + return n * 2 + }) + + for result := range doubled { + fmt.Println(result) + } +} +``` + + +## 总结 + +编写地道 Go 代码的关键原则: + +1. **简单性**: 避免过度工程化 +2. **错误是值**: 显式处理错误,不要使用异常 +3. **接受接口,返回结构体**: 在消费点定义接口 +4. **使用 defer**: 用于清理和资源管理 +5. **拥有你的 goroutine**: 管理它们的生命周期 +6. **Context 用于取消**: 使用 context 进行超时和取消 +7. **遵循命名约定**: 包名、接口名、缩写词 +8. **按功能组织**: 而不是像 Java 那样按类型组织 +9. **使用函数式选项**: 用于可配置对象 +10. **表驱动测试**: 用于全面测试 + +## 练习题 + +1. 为什么接口应该在消费端而不是生产端定义? +2. 使用 context 值传递请求范围的数据有什么问题? +3. 什么时候应该使用 `defer` 而不是显式清理? +4. Go 的隐式接口满足如何影响包设计? +5. 为什么小接口优于大接口? + +## 项目想法 + +创建一个"Go 惯用法检查器"工具: +- 检测 Go 代码中的 Java 风格模式 +- 建议更地道的替代方案 +- 提供前后对比示例 +- 涵盖本模块的常见模式 + +## 下一步 + +- **模块 12**: 性能优化技术 +- **模块 13**: 部署和生产环境考虑 +- **模块 14**: 构建完整的真实项目 + +## 延伸阅读 + +- [Effective Go](https://go.dev/doc/effective_go) +- [Go Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments) +- [The Go Blog: Go Proverbs](https://go-proverbs.github.io/) +- [Standard Library Package Names](https://go.dev/pkg/) diff --git a/content/docs/java2go/module-11-idiomatic-go.zh-tw.mdx b/content/docs/java2go/module-11-idiomatic-go.zh-tw.mdx new file mode 100644 index 0000000..3648e8c --- /dev/null +++ b/content/docs/java2go/module-11-idiomatic-go.zh-tw.mdx @@ -0,0 +1,1287 @@ +--- +title: "模組 11: 地道的 Go 語言" +description: "學習 Go 程式設計哲學和慣用模式,這些模式能讓優雅的 Go 程式碼區別於 Java 風格的 Go 程式碼" +--- + +# 模組 11: 地道的 Go 語言 + +## 學習目標 + +學完本模組後,你將: +- 理解 Go 的程式設計哲學 +- 撰寫地道的 Go 程式碼而非 Java 風格的 Go 程式碼 +- 應用「錯誤是值」模式 +- 有效使用介面(接受介面,返回結構體) +- 利用 defer 進行資源清理 +- 理解 goroutine 所有权模式 +- 有效使用 context +- 遵循 Go 命名約定 +- 合理組織包結構 +- 應用常見的 Go 模式和慣用法 + +## 簡介 + +撰寫「感覺像 Go」而非「Java 翻譯成 Go」的程式碼是成為高效 Go 開發者的關鍵。本模組涵蓋使 Go 程式碼優雅和可維護的哲學、模式和慣用法。 + +## 1. Go 程式設計哲學 + +### 少即是指數級的更多 + + +```java !! java +// Java - 通常冗長且有多層抽象 +public interface UserService { + User getUserById(Long id) throws UserNotFoundException; +} + +public class UserServiceImpl implements UserService { + private final UserRepository userRepository; + private final UserCache userCache; + + @Autowired + public UserServiceImpl(UserRepository userRepository, UserCache userCache) { + this.userRepository = userRepository; + this.userCache = userCache; + } + + @Override + public User getUserById(Long id) throws UserNotFoundException { + User user = userCache.get(id); + if (user == null) { + user = userRepository.findById(id) + .orElseThrow(() -> new UserNotFoundException("User not found: " + id)); + userCache.put(id, user); + } + return user; + } +} +``` + +```go !! go +// Go - 簡單直接 +type UserService struct { + repo UserRepository + cache UserCache +} + +func (s *UserService) GetUser(id int64) (*User, error) { + // 先嘗試快取 + if user, ok := s.cache.Get(id); ok { + return user, nil + } + + // 回退到倉庫 + user, err := s.repo.FindByID(id) + if err != nil { + return nil, fmt.Errorf("user not found: %d", id) + } + + s.cache.Put(id, user) + return user, nil +} +``` + + +### 核心原則 + +1. **簡單性**: 避免不必要的抽象 +2. **可讀性**: 程式碼被閱讀的次數多於撰寫的次數 +3. **正交性**: 功能特性協同工作 +4. **安全性**: 型別安全和記憶體安全 +5. **效能**: 高效的編譯和執行 + +## 2. 「錯誤是值」模式 + +### 哲學 + +在 Java 中,錯誤通常被視為例外控制流。在 Go 中,錯誤是像任何其他資料一樣處理的值。 + + +```java !! java +// Java - 使用例外進行控制流 +public void processUser(Long userId) { + try { + User user = userRepository.findById(userId).orElseThrow(); + validateUser(user); + sendEmail(user); + } catch (UserNotFoundException e) { + logger.error("User not found", e); + throw new BusinessException("Invalid user"); + } catch (ValidationException e) { + logger.error("Validation failed", e); + throw new BusinessException("Invalid data"); + } catch (EmailException e) { + logger.error("Email failed", e); + throw new BusinessException("Communication error"); + } +} +``` + +```go !! go +// Go - 錯誤是值 +func (s *Service) ProcessUser(userID int64) error { + user, err := s.repo.FindByID(userID) + if err != nil { + return fmt.Errorf("find user: %w", err) + } + + if err := s.validateUser(user); err != nil { + return fmt.Errorf("validate: %w", err) + } + + if err := s.sendEmail(user); err != nil { + return fmt.Errorf("send email: %w", err) + } + + return nil +} + +// 慣用法: 哨兵錯誤 +var ( + ErrUserNotFound = errors.New("user not found") + ErrInvalidInput = errors.New("invalid input") + ErrInternalError = errors.New("internal error") +) + +func (s *Service) ProcessUser(userID int64) error { + user, err := s.repo.FindByID(userID) + if errors.Is(err, ErrUserNotFound) { + return ErrUserNotFound + } + if err != nil { + return fmt.Errorf("find user: %w", err) + } + // ... 其餘邏輯 + return nil +} +``` + + +### 自訂錯誤型別 + + +```java !! java +// Java - 帶錯誤碼的自訂例外 +public class ApiException extends RuntimeException { + private final ErrorCode code; + private final int statusCode; + + public ApiException(ErrorCode code, String message) { + super(message); + this.code = code; + this.statusCode = code.getHttpCode(); + } +} +``` + +```go !! go +// Go - 自訂錯誤型別 +type APIError struct { + Code ErrorCode + Message string + StatusCode int + Err error +} + +func (e *APIError) Error() string { + if e.Err != nil { + return fmt.Sprintf("%s: %v", e.Message, e.Err) + } + return e.Message +} + +func (e *APIError) Unwrap() error { + return e.Err +} + +// 使用 +func (s *Service) ProcessUser(id int64) error { + user, err := s.repo.FindByID(id) + if err != nil { + return &APIError{ + Code: ErrNotFound, + Message: "User not found", + StatusCode: 404, + Err: err, + } + } + return nil +} +``` + + +## 3. 介面: 接受介面,返回結構體 + +### 黃金法則 + + +```java !! java +// Java - 到處都是介面(經常過度使用) +public interface UserRepository extends Repository { +} + +public interface UserService { + User getUserById(Long id); +} + +@Service +public class UserServiceImpl implements UserService { + private final UserRepository userRepository; + + public UserServiceImpl(UserRepository userRepository) { + this.userRepository = userRepository; + } +} +``` + +```go !! go +// Go - 在消費者端定義介面 +// 錯誤: 在實作旁邊定義介面 +type UserRepository interface { + FindByID(id int64) (*User, error) +} + +type MySQLUserRepository struct { + db *sql.DB +} + +func (r *MySQLUserRepository) FindByID(id int64) (*User, error) { + // ... +} + +// 正確: 在使用的地方定義介面 +type UserFinder interface { + FindByID(id int64) (*User, error) +} + +type UserService struct { + repo UserFinder // 接受介面 +} + +func NewUserService(repo UserFinder) *UserService { + return &UserService{repo: repo} +} + +// 返回具體結構體 +func NewUserRepository(db *sql.DB) *MySQLUserRepository { + return &MySQLUserRepository{db: db} +} +``` + + +### 小介面 + + +```java !! java +// Java - 大介面很常見 +public interface Repository { + T save(T entity); + Optional findById(ID id); + List findAll(); + List findAllById(Iterable ids); + long count(); + void deleteById(ID id); + void delete(T entity); + boolean existsById(ID id); +} +``` + +```go !! go +// Go - 小而專注的介面 +// io 包範例 +type Reader interface { + Read(p []byte) (n int, err error) +} + +type Writer interface { + Write(p []byte) (n int, err error) +} + +type ReadWriter interface { + Reader + Writer +} + +// 按需組合介面 +type UserReader interface { + FindByID(id int64) (*User, error) + FindByEmail(email string) (*User, error) +} + +type UserWriter interface { + Create(user *User) error + Update(user *User) error + Delete(id int64) error +} + +type UserReaderWriter interface { + UserReader + UserWriter +} +``` + + +### 介面滿足是隱式的 + + +```java !! java +// Java - 顯式實作 +public class FileLogger implements Logger { + @Override + public void log(String message) { + System.out.println(message); + } +} + +// 必須宣告 "implements Logger" +``` + +```go !! go +// Go - 隱式滿足 +type Logger interface { + Log(message string) +} + +// FileLogger 自動滿足 Logger +type FileLogger struct { + file *os.File +} + +func (l *FileLogger) Log(message string) { + fmt.Fprintln(l.file, message) +} + +// 不需要 "implements" 宣告! + +// 這使得可以在外部新增介面滿足 +type Console struct{} + +func (c *Console) Log(message string) { + fmt.Println(message) +} + +// Console 現在滿足 Logger 介面 +``` + + +## 4. 使用 Defer 進行清理 + +### 替換 try-finally + + +```java !! java +// Java - try-finally 或 try-with-resources +public void processFile(String path) throws IOException { + FileInputStream fis = null; + try { + fis = new FileInputStream(path); + // 處理檔案 + } finally { + if (fis != null) { + fis.close(); + } + } +} + +// 更好: try-with-resources +public void processFile(String path) throws IOException { + try (FileInputStream fis = new FileInputStream(path)) { + // 處理檔案 + } +} +``` + +```go !! go +// Go - defer 用於清理 +func processFile(path string) error { + f, err := os.Open(path) + if err != nil { + return err + } + defer f.Close() // 總是執行 + + // 處理檔案 + return nil +} + +// 多個 defer 按 LIFO 順序執行 +func processMultiple() { + defer fmt.Println("First") + defer fmt.Println("Second") + defer fmt.Println("Third") + + // 輸出: Third, Second, First +} + +// 帶錯誤處理的 defer +func processFileWithError(path string) error { + f, err := os.Open(path) + if err != nil { + return err + } + defer func() { + if err := f.Close(); err != nil { + log.Printf("Warning: failed to close file: %v", err) + } + }() + + // 處理檔案 + return nil +} +``` + + +### Defer 陷阱和最佳實踐 + + +```go !! go +// 陷阱: 迴圈變數捕獲 +func processFiles(files []string) error { + for _, file := range files { + f, err := os.Open(file) + if err != nil { + return err + } + defer f.Close() // 錯誤: 所有 close 在函式退出時發生! + } + return nil +} + +// 正確: 在迴圈中使用閉包 +func processFilesCorrect(files []string) error { + for _, file := range files { + if err := func() error { + f, err := os.Open(file) + if err != nil { + return err + } + defer f.Close() // 每次迭代後關閉 + // 處理檔案 + return nil + }(); err != nil { + return err + } + } + return nil +} + +// 陷阱: 參數立即求值 +func process() { + start := time.Now() + defer fmt.Println("Duration:", time.Since(start)) // 在 defer 時求值! + + time.Sleep(1 * time.Second) + // 輸出 Duration: 0s (錯誤!) +} + +// 正確: 使用函式 +func process() { + start := time.Now() + defer func() { + fmt.Println("Duration:", time.Since(start)) // 執行時求值 + }() + + time.Sleep(1 * time.Second) + // 輸出 Duration: 1s (正確!) +} +``` + + +## 5. Goroutine 所有权 + +### 誰建立,誰清理 + + +```java !! java +// Java - ExecutorService 管理執行緒生命週期 +ExecutorService executor = Executors.newFixedThreadPool(10); + +public void processTasks(List tasks) { + List> futures = new ArrayList<>(); + for (Task task : tasks) { + Future future = executor.submit(() -> { + return task.execute(); + }); + futures.add(future); + } + + // 等待所有完成 + for (Future future : futures) { + future.get(); // 阻塞 + } +} +``` + +```go !! go +// Go - 父 goroutine 管理子 goroutine +func ProcessTasks(ctx context.Context, tasks []Task) ([]Result, error) { + ctx, cancel := context.WithCancel(ctx) + defer cancel() // 確保子 goroutine 被取消 + + results := make([]Result, len(tasks)) + errChan := make(chan error, len(tasks)) + + for i, task := range tasks { + go func(idx int, t Task) { + result, err := t.Execute(ctx) + if err != nil { + errChan <- err + cancel() // 取消其他 goroutine + return + } + results[idx] = result + }(i, task) + } + + // 等待所有或第一個錯誤 + for range tasks { + select { + case <-ctx.Done(): + return nil, ctx.Err() + case err := <-errChan: + return nil, err + } + } + + return results, nil +} +``` + + +### 使用 WaitGroup 進行協調 + + +```java !! java +// Java - CountDownLatch +CountDownLatch latch = new CountDownLatch(taskCount); +for (Task task : tasks) { + executor.submit(() -> { + try { + task.execute(); + } finally { + latch.countDown(); + } + }); +} +latch.await(); // 等待所有 +``` + +```go !! go +// Go - WaitGroup +func processTasks(tasks []Task) { + var wg sync.WaitGroup + + for _, task := range tasks { + wg.Add(1) // 增加計數器 + go func(t Task) { + defer wg.Done() // 完成時減少計數器 + t.Execute() + }(task) + } + + wg.Wait() // 等待所有 goroutine +} + +// 帶錯誤處理 +func processTasks(tasks []Task) error { + var wg sync.WaitGroup + errChan := make(chan error, len(tasks)) + + for _, task := range tasks { + wg.Add(1) + go func(t Task) { + defer wg.Done() + if err := t.Execute(); err != nil { + errChan <- err + } + }(task) + } + + wg.Wait() + close(errChan) + + // 收集錯誤 + var errors []error + for err := range errChan { + errors = append(errors, err) + } + + if len(errors) > 0 { + return fmt.Errorf("tasks failed: %v", errors) + } + return nil +} +``` + + +## 6. 有效使用 Context + +### Context 用於取消 + + +```java !! java +// Java - Future 取消 +Future future = executor.submit(() -> { + return longRunningTask(); +}); + +try { + String result = future.get(5, TimeUnit.SECONDS); +} catch (TimeoutException e) { + future.cancel(true); // 可能中斷 +} +``` + +```go !! go +// Go - Context 用於取消 +func longRunningTask(ctx context.Context) error { + for { + select { + case <-ctx.Done(): + return ctx.Err() // Context 被取消 + default: + // 執行工作 + if err := processChunk(); err != nil { + return err + } + } + } +} + +// 使用 +func processWithTimeout() error { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + return longRunningTask(ctx) +} +``` + + +### Context 值(謹慎使用) + + +```java !! java +// Java - ThreadLocal +public class RequestContext { + private static final ThreadLocal userId = new ThreadLocal<>(); + + public static void setUserId(String id) { + userId.set(id); + } + + public static String getUserId() { + return userId.get(); + } +} +``` + +```go !! go +// Go - Context 值(謹慎使用!) +type contextKey string + +const ( + userIDKey contextKey = "userID" + traceIDKey contextKey = "traceID" +) + +func withUserID(ctx context.Context, userID string) context.Context { + return context.WithValue(ctx, userIDKey, userID) +} + +func getUserID(ctx context.Context) (string, bool) { + userID, ok := ctx.Value(userIDKey).(string) + return userID, ok +} + +// 使用 +func handleRequest(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + userID, ok := getUserID(ctx) + if !ok { + http.Error(w, "Unauthorized", http.StatusUnauthorized) + return + } + + // 處理請求 +} +``` + + +## 7. 命名約定 + +### 套件名稱 + + +```java !! java +// Java - 反向域名符號 +package com.example.myapp.service; + +public class UserService { + // ... +} +``` + +```go !! go +// Go - 短小、小寫、單詞 +// 在檔案: user/service.go 中 +package service + +type Service struct { + // 因為套件名是 'service',型別是 'User',不是 'UserService' +} + +func NewService() *Service { + return &Service{} +} +``` + + +### 介面名稱 + + +```java !! java +// Java - 實作類別通常以 "Impl" 結尾 +public interface UserRepository { +} + +public class JdbcUserRepository implements UserRepository { +} + +public class JpaUserRepository implements UserRepository { +} +``` + +```go !! go +// Go - 介面名稱通常以 -er 結尾 +type Reader interface { + Read(p []byte) (n int, err error) +} + +type Writer interface { + Write(p []byte) (n int, err error) +} + +// 對於更複雜的介面,使用描述性名稱 +type UserRepository interface { + FindByID(id int64) (*User, error) +} + +type MySQLUserRepository struct { + db *sql.DB +} + +type PostgresUserRepository struct { + db *sql.DB +} +``` + + +### 縮寫詞 + + +```java !! java +// Java - 縮寫詞大寫 +URL url = new URL("https://example.com"); +HTTPServer server = new HTTPServer(); +``` + +```go !! go +// Go - 縮寫詞被視為單詞 +// 正確 +type URL struct { + Scheme string + Host string +} + +func getURL() *URL { + return &URL{} +} + +type HTTPServer struct { + addr string +} + +// 錯誤(不要這樣做) +type Url struct {} +type HttpServer struct {} +``` + + +## 8. 套件結構 + +### 標準佈局 + + +``` +// Java - Maven/Gradle 結構 +src/ +├── main/ +│ ├── java/ +│ │ └── com/ +│ │ └── example/ +│ │ └── app/ +│ │ ├── controller/ +│ │ ├── service/ +│ │ ├── repository/ +│ │ └── model/ +│ └── resources/ +└── test/ + └── java/ +``` + +``` +# Go - 標準專案佈局 +myapp/ +├── cmd/ +│ ├── myapp/ +│ │ └── main.go # 應用程式入口 +│ └── myappctl/ +│ └── main.go # CLI 工具 +├── internal/ +│ ├── auth/ # 私有 auth 程式碼 +│ ├── database/ # 私有 database 程式碼 +│ └── user/ # 私有 user 邏輯 +├── pkg/ +│ ├── api/ # 公共 API 函式庫 +│ └── util/ # 公共工具 +├── api/ +│ ├── openapi/ # OpenAPI 規格 +│ └── proto/ # Protocol buffers +├── web/ +│ ├── static/ # 靜態資源 +│ └── templates/ # HTML 樣板 +├── configs/ # 設定檔 +├── scripts/ # 建置和部署腳本 +├── test/ # 額外的測試資料 +├── docs/ # 文件 +├── go.mod +├── go.sum +├── Makefile +└── README.md +``` + + +### internal 套件 + + +```go !! go +// internal/auth/auth.go +package auth + +// 此程式碼不能被 myapp 外部的套件匯入 +type Authenticator struct { + // ... +} + +func New() *Authenticator { + return &Authenticator{} +} + +// cmd/myapp/main.go 可以匯入這個 +package main + +import "myapp/internal/auth" + +func main() { + auth := auth.New() + // ... +} + +// 但外部套件不能: +// 這將無法編譯: +// package external +// import "myapp/internal/auth" // 錯誤! +``` + + +## 9. 程式碼組織 + +### 檔案組織 + + +```java !! java +// Java - 每個檔案一個公共類別 +// UserService.java +package com.example.service; + +public class UserService { + // 所有使用者服務邏輯在這裡 +} + +// UserRepository.java +package com.example.repository; + +public interface UserRepository { + // 所有倉庫方法在這裡 +} +``` + +```go !! go +// Go - 按功能組織,而不僅僅是型別 +// service.go +package user + +type Service struct { + repo Repository + cache Cache +} + +func NewService(repo Repository, cache Cache) *Service { + return &Service{ + repo: repo, + cache: cache, + } +} + +func (s *Service) GetUser(id int64) (*User, error) { + // ... +} + +// repository.go +package user + +type Repository interface { + FindByID(id int64) (*User, error) +} + +type mysqlRepository struct { + db *sql.DB +} + +func NewMySQLRepository(db *sql.DB) Repository { + return &mysqlRepository{db: db} +} + +// models.go +package user + +type User struct { + ID int64 + Email string + Name string + CreatedAt time.Time +} +``` + + +### 匯出規則 + + +```java !! java +// Java - public/private 關鍵字 +public class UserService { + private UserRepository repository; + + public UserService(UserRepository repository) { + this.repository = repository; + } + + public User getUser(Long id) { + return repository.findById(id); + } + + private void validate(User user) { + // 內部驗證 + } +} +``` + +```go !! go +// Go - 大小寫決定匯出 +package service + +type Service struct { // 匯出 + repo repository // 未匯出 + cache cache // 未匯出 +} + +func NewService(repo repository) *Service { + return &Service{repo: repo} +} + +func (s *Service) GetUser(id int64) (*User, error) { // 匯出 + return s.repo.FindByID(id) +} + +func (s *Service) validate(user *User) error { // 未匯出 + // 內部驗證 + return nil +} + +// 嵌入中的介面欄位必須匯出 +type Server struct { + *http.Server // 嵌入,匯出 + logger *log.Logger // 未匯出欄位 +} +``` + + +## 10. 常見 Go 模式 + +### 選項模式 + + +```java !! java +// Java - Builder 模式 +public class Server { + private int port = 8080; + private String host = "localhost"; + private int timeout = 30; + + private Server(Builder builder) { + this.port = builder.port; + this.host = builder.host; + this.timeout = builder.timeout; + } + + public static class Builder { + private int port = 8080; + private String host = "localhost"; + private int timeout = 30; + + public Builder port(int port) { + this.port = port; + return this; + } + + public Builder host(String host) { + this.host = host; + return this; + } + + public Builder timeout(int timeout) { + this.timeout = timeout; + return this; + } + + public Server build() { + return new Server(this); + } + } +} + +// 使用 +Server server = new Server.Builder() + .port(9090) + .host("0.0.0.0") + .timeout(60) + .build(); +``` + +```go !! go +// Go - 函數式選項模式 +type Server struct { + port int + host string + timeout time.Duration +} + +type Option func(*Server) + +func WithPort(port int) Option { + return func(s *Server) { + s.port = port + } +} + +func WithHost(host string) Option { + return func(s *Server) { + s.host = host + } +} + +func WithTimeout(timeout time.Duration) Option { + return func(s *Server) { + s.timeout = timeout + } +} + +func NewServer(opts ...Option) *Server { + server := &Server{ + port: 8080, + host: "localhost", + timeout: 30 * time.Second, + } + + for _, opt := range opts { + opt(server) + } + + return server +} + +// 使用 +server := NewServer( + WithPort(9090), + WithHost("0.0.0.0"), + WithTimeout(60*time.Second), +) +``` + + +### 表驅動測試 + + +```java !! java +// Java - 獨立的測試方法 +@Test +public void testAddition() { + assertEquals(4, Calculator.add(2, 2)); + assertEquals(0, Calculator.add(0, 0)); + assertEquals(-2, Calculator.add(2, -4)); +} + +@Test +public void testSubtraction() { + assertEquals(0, Calculator.subtract(2, 2)); + assertEquals(2, Calculator.subtract(0, -2)); +} +``` + +```go !! go +// Go - 表驅動測試 +func TestAdd(t *testing.T) { + tests := []struct { + name string + a, b int + expected int + }{ + {"positive numbers", 2, 2, 4}, + {"zeros", 0, 0, 0}, + {"negative result", 2, -4, -2}, + {"large numbers", 1000000, 2000000, 3000000}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := Add(tt.a, tt.b) + if result != tt.expected { + t.Errorf("Add(%d, %d) = %d; want %d", + tt.a, tt.b, result, tt.expected) + } + }) + } +} + +// 測試輔助函式 +func assertEqual[T comparable](t *testing.T, got, want T) { + t.Helper() + if got != want { + t.Errorf("got %v, want %v", got, want) + } +} +``` + + +### 通道模式 + + +```java !! java +// Java - Stream API +List result = numbers.stream() + .filter(n -> n % 2 == 0) + .map(n -> n * 2) + .collect(Collectors.toList()); +``` + +```go !! go +// Go - 使用 goroutine 和通道的管道 +func generator(nums ...int) <-chan int { + out := make(chan int) + go func() { + for _, n := range nums { + out <- n + } + close(out) + }() + return out +} + +func filter(in <-chan int, predicate func(int) bool) <-chan int { + out := make(chan int) + go func() { + for n := range in { + if predicate(n) { + out <- n + } + } + close(out) + }() + return out +} + +func mapChan(in <-chan int, transform func(int) int) <-chan int { + out := make(chan int) + go func() { + for n := range in { + out <- transform(n) + } + close(out) + }() + return out +} + +// 使用 +func main() { + numbers := generator(1, 2, 3, 4, 5, 6) + + evens := filter(numbers, func(n int) bool { + return n%2 == 0 + }) + + doubled := mapChan(evens, func(n int) int { + return n * 2 + }) + + for result := range doubled { + fmt.Println(result) + } +} +``` + + +## 總結 + +撰寫地道 Go 程式碼的關鍵原則: + +1. **簡單性**: 避免過度工程化 +2. **錯誤是值**: 顯式處理錯誤,不要使用例外 +3. **接受介面,返回結構體**: 在消費點定義介面 +4. **使用 defer**: 用於清理和資源管理 +5. **擁有你的 goroutine**: 管理它們的生命週期 +6. **Context 用於取消**: 使用 context 進行逾時和取消 +7. **遵循命名約定**: 套件名、介面名、縮寫詞 +8. **按功能組織**: 而不是像 Java 那樣按型別組織 +9. **使用函數式選項**: 用於可配置物件 +10. **表驅動測試**: 用於全面測試 + +## 練習題 + +1. 為什麼介面應該在消費端而不是生產端定義? +2. 使用 context 值傳遞請求範圍的資料有什麼問題? +3. 什麼時候應該使用 `defer` 而不是顯式清理? +4. Go 的隱式介面滿足如何影響套件設計? +5. 為什麼小介面優於大介面? + +## 專案想法 + +建立一個「Go 慣用法檢查器」工具: +- 檢測 Go 程式碼中的 Java 風格模式 +- 建議更地道的替代方案 +- 提供前後對比範例 +- 涵蓋本模組的常見模式 + +## 下一步 + +- **模組 12**: 效能優化技術 +- **模組 13**: 部署和生產環境考慮 +- **模組 14**: 建立完整的真實專案 + +## 延伸閱讀 + +- [Effective Go](https://go.dev/doc/effective_go) +- [Go Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments) +- [The Go Blog: Go Proverbs](https://go-proverbs.github.io/) +- [Standard Library Package Names](https://go.dev/pkg/) diff --git a/content/docs/java2go/module-12-performance.mdx b/content/docs/java2go/module-12-performance.mdx new file mode 100644 index 0000000..10180f0 --- /dev/null +++ b/content/docs/java2go/module-12-performance.mdx @@ -0,0 +1,917 @@ +--- +title: "Module 12: Performance Optimization in Go" +--- + +This module covers performance optimization techniques in Go, comparing them with Java's performance tuning approaches. + +## Performance Philosophy: Go vs Java + +**Java Performance Philosophy:** +- JVM optimizations (JIT compilation, GC tuning) +- Profiling with VisualVM, JProfiler +- Garbage collection tuning +- Memory pool management +- Thread pool configuration + +**Go Performance Philosophy:** +- Simple, predictable performance +- Built-in profiling tools (pprof) +- Efficient memory allocation patterns +- Minimal runtime overhead +- Direct compilation to machine code + + +```java !! java +// Java: Performance considerations +public class Performance { + // JVM warm-up required for optimal performance + // JIT compilation happens at runtime + // Garbage collection pauses possible + + private List numbers = new ArrayList<>(); + + public void addNumber(int num) { + numbers.add(num); // May trigger GC and array resize + } + + // Object allocation overhead + public String processData(String input) { + StringBuilder sb = new StringBuilder(); + // String operations create intermediate objects + return sb.toString(); + } +} +``` + +```go !! go +// Go: Predictable performance +package main + +// No warm-up needed +// Direct compilation +// Simple GC with minimal pauses + +type Performance struct { + numbers []int +} + +func (p *Performance) AddNumber(num int) { + p.numbers = append(p.numbers, num) // Efficient growth +} + +// Minimal allocation overhead +func (p *Performance) ProcessData(input string) string { + var builder strings.Builder + // Efficient string building + return builder.String() +} +``` + + +## Built-in Profiling Tools + +Go provides excellent built-in profiling tools that are easier to use than Java's external profilers. + +### CPU Profiling + + +```java !! java +// Java: CPU Profiling with VisualVM/JProfiler +/* +1. Start application with profiling agent: + java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 MyApp + +2. Connect with VisualVM or JProfiler + +3. Take CPU snapshot + +4. Analyze hotspots + +Example: Using Java Flight Recorder +*/ +import jdk.jfr.Category; +import jdk.jfr.Event; +import jdk.jfr.Label; + +@Label("Processing Event") +@Category("Processing") +public class ProcessingEvent extends Event { + @Label "Items Processed" + public int items; +} + +public class ProfilerDemo { + public void processItems(int count) { + ProcessingEvent event = new ProcessingEvent(); + event.begin(); + event.items = count; + + // Processing logic + for (int i = 0; i < count; i++) { + // Do work + } + + event.end(); + event.commit(); + } +} +``` + +```go !! go +// Go: Built-in CPU Profiling +package main + +import ( + "fmt" + "os" + "runtime/pprof" + "time" +) + +func main() { + // Start CPU profiling + f, _ := os.Create("cpu.prof") + pprof.StartCPUProfile(f) + defer pprof.StopCPUProfile() + + // Run code to profile + processItems(1000000) + + f.Close() + fmt.Println("Profile saved to cpu.prof") + // Analyze with: go tool pprof cpu.prof +} + +func processItems(count int) { + for i := 0; i < count; i++ { + // Do work + calculate(i) + } +} + +func calculate(n int) int { + result := 0 + for i := 0; i < n; i++ { + result += i + } + return result +} +``` + + +### Memory Profiling + + +```java !! java +// Java: Heap Analysis +import java.util.ArrayList; +import java.util.List; + +public class MemoryProfiler { + private List allocations = new ArrayList<>(); + + public void allocateMemory() { + // Runtime.getRuntime().gc() to force GC + // MemoryMXBean for heap usage + + for (int i = 0; i < 1000; i++) { + allocations.add(new byte[1024]); // 1KB each + } + + // Analyze heap dump + Runtime runtime = Runtime.getRuntime(); + long usedMemory = runtime.totalMemory() - runtime.freeMemory(); + System.out.println("Used memory: " + usedMemory + " bytes"); + } + + // Use VisualVM to analyze heap dumps + // jmap -dump:format=b,file=heap.hprof +} +``` + +```go !! go +// Go: Memory Profiling +package main + +import ( + "fmt" + "os" + "runtime/pprof" +) + +func main() { + // Memory profiling + f, _ := os.Create("mem.prof") + defer f.Close() + + // Take memory snapshot + pprof.WriteHeapProfile(f) + fmt.Println("Memory profile saved to mem.prof") + // Analyze with: go tool pprof mem.prof +} + +func allocateMemory() { + allocations := make([][]byte, 0) + + for i := 0; i < 1000; i++ { + allocations = append(allocations, make([]byte, 1024)) + } + + // Get memory stats + var m runtime.MemStats + runtime.ReadMemStats(&m) + fmt.Printf("Allocated memory: %d bytes\n", m.Alloc) + fmt.Printf("Total allocations: %d\n", m.TotalAlloc) + fmt.Printf("Heap objects: %d\n", m.HeapObjects) +} +``` + + +## Memory Allocation Patterns + +### Avoiding Unnecessary Allocations + + +```java !! java +// Java: Common allocation patterns +public class Allocations { + // String concatenation in loops - BAD + public String buildStringBad(String[] items) { + String result = ""; + for (String item : items) { + result += item; // Creates new String each iteration + } + return result; + } + + // Using StringBuilder - GOOD + public String buildStringGood(String[] items) { + StringBuilder sb = new StringBuilder(); + for (String item : items) { + sb.append(item); // Efficient + } + return sb.toString(); + } + + // Object pooling example + private static final ObjectPool bufferPool = + new ObjectPool<>(() -> new Buffer()); + + public Buffer getBuffer() { + return bufferPool.borrow(); + } + + public void returnBuffer(Buffer buffer) { + bufferPool.release(buffer); + } +} +``` + +```go !! go +// Go: Efficient allocation patterns +package main + +import ( + "strings" +) + +// Bad: String concatenation in loop +func buildStringBad(items []string) string { + result := "" + for _, item := range items { + result += item // Creates new string each iteration + } + return result +} + +// Good: Use strings.Builder +func buildStringGood(items []string) string { + var builder strings.Builder + builder.Grow(len(items) * 10) // Pre-allocate capacity + + for _, item := range items { + builder.WriteString(item) // Efficient + } + return builder.String() +} + +// Good: Use buffer pool +var bufferPool = sync.Pool{ + New: func() interface{} { + return new(bytes.Buffer) + }, +} + +func getBuffer() *bytes.Buffer { + return bufferPool.Get().(*bytes.Buffer) +} + +func returnBuffer(buf *bytes.Buffer) { + buf.Reset() + bufferPool.Put(buf) +} + +// Good: Pre-allocate slices +func preAllocateSlice(count int) []int { + // Make with capacity avoids reallocation + slice := make([]int, 0, count) + for i := 0; i < count; i++ { + slice = append(slice, i) // No reallocation needed + } + return slice +} +``` + + +## Benchmarking + + +```java !! java +// Java: JMH Benchmarking +import org.openjdk.jmh.annotations.*; +import java.util.concurrent.TimeUnit; + +@BenchmarkMode(Mode.AverageTime) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +@State(Scope.Benchmark) +public class Benchmarks { + private int[] data; + + @Setup + public void setup() { + data = new int[1000]; + for (int i = 0; i < data.length; i++) { + data[i] = i; + } + } + + @Benchmark + public int sumWithLoop() { + int sum = 0; + for (int num : data) { + sum += num; + } + return sum; + } + + @Benchmark + public int sumWithStream() { + return java.util.Arrays.stream(data).sum(); + } +} + +// Run with: java -jar benchmarks.jar +``` + +```go !! go +// Go: Built-in Benchmarking +package main + +import ( + "testing" +) + +var data []int + +func setup() { + data = make([]int, 1000) + for i := 0; i < len(data); i++ { + data[i] = i + } +} + +func BenchmarkSumWithLoop(b *testing.B) { + setup() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + sum := 0 + for _, num := range data { + sum += num + } + } +} + +func BenchmarkSumWithRange(b *testing.B) { + setup() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + sum := 0 + for i := range data { + sum += data[i] + } + } +} + +// Run with: go test -bench=. -benchmem +``` + + +## Goroutine Performance + + +```java !! java +// Java: Thread overhead +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +public class ThreadPerformance { + private static final int TASK_COUNT = 100000; + + public void runWithThreads() throws InterruptedException { + long start = System.currentTimeMillis(); + + ExecutorService executor = Executors.newFixedThreadPool(100); + + for (int i = 0; i < TASK_COUNT; i++) { + final int taskNum = i; + executor.submit(() -> { + // Simulate work + int result = taskNum * 2; + }); + } + + executor.shutdown(); + executor.awaitTermination(1, TimeUnit.MINUTES); + + long duration = System.currentTimeMillis() - start; + System.out.println("Threads: " + duration + "ms"); + // Output: Threads: ~2000-5000ms + // Memory: ~500MB-1GB for 100K threads + } +} +``` + +```go !! go +// Go: Goroutine efficiency +package main + +import ( + "fmt" + "sync" + "time" +) + +const taskCount = 100000 + +func runWithGoroutines() { + start := time.Now() + + var wg sync.WaitGroup + + for i := 0; i < taskCount; i++ { + wg.Add(1) + go func(taskNum int) { + defer wg.Done() + // Simulate work + result := taskNum * 2 + _ = result + }(i) + } + + wg.Wait() + + duration := time.Since(start) + fmt.Printf("Goroutines: %v\n", duration) + // Output: Goroutines: ~200-500ms + // Memory: ~50-100MB for 100K goroutines +} + +func main() { + runWithGoroutines() +} +``` + + +## Channel Performance + + +```java !! java +// Java: BlockingQueue +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; + +public class QueuePerformance { + private BlockingQueue queue = new ArrayBlockingQueue<>(100); + + public void producer() throws InterruptedException { + for (int i = 0; i < 1000000; i++) { + queue.put(i); // Blocking if full + } + } + + public void consumer() throws InterruptedException { + while (true) { + Integer value = queue.take(); // Blocking if empty + // Process value + } + } +} +``` + +```go !! go +// Go: Buffered channels +package main + +import "sync" + +// Unbuffered channel (synchronous) +func unbufferedChannel() { + ch := make(chan int) + go func() { + for i := 0; i < 1000000; i++ { + ch <- i // Blocks until receiver ready + } + close(ch) + }() + + for value := range ch { + _ = value + } +} + +// Buffered channel (asynchronous) +func bufferedChannel() { + ch := make(chan int, 100) // Buffer size 100 + go func() { + for i := 0; i < 1000000; i++ { + ch <- i // Doesn't block until buffer is full + } + close(ch) + }() + + for value := range ch { + _ = value + } +} + +// Best practice: Choose buffer size based on workload +func optimizedChannel() { + // For producer-consumer with different rates + ch := make(chan int, 1000) // Larger buffer for faster producer + + var wg sync.WaitGroup + + // Multiple producers + for i := 0; i < 10; i++ { + wg.Add(1) + go func(id int) { + defer wg.Done() + for j := 0; j < 10000; j++ { + ch <- id*10000 + j + } + }(i) + } + + // Single consumer + go func() { + for value := range ch { + _ = value + } + }() + + wg.Wait() + close(ch) +} +``` + + +## String Optimization + + +```java !! java +// Java: String optimization +public class StringOptimization { + // Bad: String concatenation in loop + public String concatenateBad(List items) { + String result = ""; + for (String item : items) { + result += item; // Creates new objects + } + return result; + } + + // Good: StringBuilder + public String concatenateGood(List items) { + StringBuilder sb = new StringBuilder(items.size() * 16); + for (String item : items) { + sb.append(item); + } + return sb.toString(); + } + + // String comparison + public boolean compareStrings(String a, String b) { + return a.equals(b); // Correct way + // return a == b; // WRONG! Compares references + } + + // String interning for memory efficiency + public void stringInterning() { + String s1 = "hello".intern(); // Interned + String s2 = "hello".intern(); // Same reference + boolean same = (s1 == s2); // true + } +} +``` + +```go !! go +// Go: String optimization +package main + +import ( + "strings" +) + +// Bad: String concatenation in loop +func concatenateBad(items []string) string { + result := "" + for _, item := range items { + result += item // Inefficient + } + return result +} + +// Good: strings.Builder +func concatenateGood(items []string) string { + var builder strings.Builder + // Pre-allocate capacity + builder.Grow(len(items) * 16) + + for _, item := range items { + builder.WriteString(item) + } + return builder.String() +} + +// String comparison (direct == works in Go!) +func compareStrings(a, b string) bool { + return a == b // Efficient and correct +} + +// String interning (manual) +var internPool = sync.Pool{ + New: func() interface{} { + return make(map[string]string) + }, +} + +// String reuse pattern +func stringReuse() { + // Common pattern: use byte slices for building + // Convert to string only at the end + data := []byte("hello") + str := string(data) // One allocation + _ = str +} + +// Efficient string joining +func joinStrings(items []string) string { + return strings.Join(items, ",") // Efficient +} +``` + + +## Slice Performance + + +```java !! java +// Java: ArrayList vs Array +public class CollectionPerformance { + // ArrayList (dynamic) + public void arrayListTest() { + List list = new ArrayList<>(); + for (int i = 0; i < 100000; i++) { + list.add(i); // May need resize + } + } + + // Array (fixed size, faster) + public void arrayTest() { + int[] array = new int[100000]; + for (int i = 0; i < 100000; i++) { + array[i] = i; // Direct access + } + } + + // Pre-sized ArrayList + public void preSizedList() { + List list = new ArrayList<>(100000); + for (int i = 0; i < 100000; i++) { + list.add(i); // No resize needed + } + } +} +``` + +```go !! go +// Go: Slice vs Array +package main + +// Slice (dynamic) +func sliceTest() { + slice := make([]int, 0) + for i := 0; i < 100000; i++ { + slice = append(slice, i) // May grow and copy + } +} + +// Pre-allocated slice (faster) +func preAllocatedSlice() { + slice := make([]int, 0, 100000) // Capacity pre-allocated + for i := 0; i < 100000; i++ { + slice = append(slice, i) // No copying needed + } +} + +// Array (fixed size, fastest) +func arrayTest() { + var array [100000]int + for i := 0; i < 100000; i++ { + array[i] = i // Direct access, no bounds checking overhead + } +} + +// Slice tricks for performance +func sliceTricks() { + // Reuse slice backing array + buffer := make([]int, 100) + + // Use same buffer multiple times + work1 := buffer[:50] // First 50 elements + work2 := buffer[50:] // Remaining 50 elements + + _ = work1 + _ = work2 +} + +// Clear slice efficiently +func clearSlice(slice []int) { + // Good: Reuse backing array + for i := range slice { + slice[i] = 0 + } + + // Alternative: Create new slice (may allocate) + // slice = make([]int, len(slice)) +} +``` + + +## Performance Best Practices + +### 1. Pre-allocate When Possible + +```go +// Bad: Repeated allocations +func badGrowth() { + data := []int{} + for i := 0; i < 1000000; i++ { + data = append(data, i) // Multiple reallocations + } +} + +// Good: Pre-allocate capacity +func goodGrowth() { + data := make([]int, 0, 1000000) + for i := 0; i < 1000000; i++ { + data = append(data, i) // No reallocation + } +} +``` + +### 2. Use Value Types Where Appropriate + +```go +// Prefer value types for small structs +type Point struct { + X, Y int +} + +// Pass by value (efficient for small structs) +func distance(p1, p2 Point) int { + dx := p1.X - p2.X + dy := p1.Y - p2.Y + return dx*dx + dy*dy +} + +// Use pointer only for large structs +type LargeStruct struct { + data [1024]int +} + +func process(l *LargeStruct) { + // Use pointer to avoid copying +} +``` + +### 3. Avoid Premature Optimization + +```go +// Write clear code first +func processData(items []string) string { + var result strings.Builder + for _, item := range items { + result.WriteString(item) + result.WriteString(",") + } + return result.String() +} + +// Profile first, optimize hotspots only +// Use: go test -bench=. -cpuprofile=cpu.prof +``` + +## Common Performance Pitfalls + +### 1. Substring Allocations + + +```java !! java +// Java: substring() copies data (before Java 7u6) +public class Substrings { + public void processString(String text) { + // Creates new String object + String sub = text.substring(0, 10); + System.out.println(sub); + } +} +``` + +```go !! go +// Go: String slicing doesn't copy +package main + +func processString(text string) { + // No allocation! Just slices the string + sub := text[:10] + println(sub) +} + +// But beware: keeps original string in memory +func memoryLeakExample() { + // Read 1GB file + data := readFile("largefile.txt") // 1GB + + // Extract small portion + firstLine := getFirstLine(data) // First 100 chars + + // firstLine still references entire 1GB data! + useString(firstLine) + + // Fix: convert to []byte and back +} + +func getFirstLine(data string) string { + // This keeps entire data in memory + return data[:100] + + // Better: copy to new string + // return strings.Clone(data[:100]) +} +``` + + +### 2. Range Loop Variable Capture + +```go +// Wrong: All goroutines capture same variable +func wrongRange() { + items := []int{1, 2, 3, 4, 5} + for _, item := range items { + go func() { + println(item) // Always prints 5 + }() + } +} + +// Correct: Pass variable as parameter +func correctRange() { + items := []int{1, 2, 3, 4, 5} + for _, item := range items { + go func(val int) { + println(val) // Prints correct values + }(item) + } +} +``` + +--- + +### Practice Questions: +1. When should you pre-allocate slice capacity vs using append? +2. How does Go's garbage collection differ from Java's? +3. What are the trade-offs between buffered and unbuffered channels? +4. Why is string slicing in Go more efficient than Java's substring()? + +### Project Ideas: +- Create a performance comparison tool that benchmarks equivalent Java and Go code +- Build a web service that demonstrates goroutine efficiency vs Java threads +- Implement a memory profiling dashboard for Go applications + +### Next Steps: +- Learn about deployment strategies for Go applications +- Understand production monitoring and observability +- Explore real-world project architecture diff --git a/content/docs/java2go/module-12-performance.zh-cn.mdx b/content/docs/java2go/module-12-performance.zh-cn.mdx new file mode 100644 index 0000000..3d762de --- /dev/null +++ b/content/docs/java2go/module-12-performance.zh-cn.mdx @@ -0,0 +1,917 @@ +--- +title: "模块 12:Go 性能优化" +--- + +本模块介绍 Go 中的性能优化技术,并与 Java 的性能调优方法进行比较。 + +## 性能哲学:Go vs Java + +**Java 性能哲学:** +- JVM 优化(JIT 编译、GC 调优) +- 使用 VisualVM、JProfiler 进行性能分析 +- 垃圾回收调优 +- 内存池管理 +- 线程池配置 + +**Go 性能哲学:** +- 简单、可预测的性能 +- 内置性能分析工具(pprof) +- 高效的内存分配模式 +- 最小的运行时开销 +- 直接编译为机器码 + + +```java !! java +// Java: 性能考虑 +public class Performance { + // 需要预热才能达到最佳性能 + // JIT 编译在运行时进行 + // 可能发生垃圾回收暂停 + + private List numbers = new ArrayList<>(); + + public void addNumber(int num) { + numbers.add(num); // 可能触发 GC 和数组扩容 + } + + // 对象分配开销 + public String processData(String input) { + StringBuilder sb = new StringBuilder(); + // 字符串操作创建中间对象 + return sb.toString(); + } +} +``` + +```go !! go +// Go: 可预测的性能 +package main + +// 无需预热 +// 直接编译 +// 简单的 GC,暂停时间短 + +type Performance struct { + numbers []int +} + +func (p *Performance) AddNumber(num int) { + p.numbers = append(p.numbers, num) // 高效增长 +} + +// 最小分配开销 +func (p *Performance) ProcessData(input string) string { + var builder strings.Builder + // 高效的字符串构建 + return builder.String() +} +``` + + +## 内置性能分析工具 + +Go 提供了优秀的内置性能分析工具,比 Java 的外部分析器更容易使用。 + +### CPU 性能分析 + + +```java !! java +// Java: 使用 VisualVM/JProfiler 进行 CPU 性能分析 +/* +1. 使用性能分析代理启动应用程序: + java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 MyApp + +2. 使用 VisualVM 或 JProfiler 连接 + +3. 获取 CPU 快照 + +4. 分析热点 + +示例:使用 Java Flight Recorder +*/ +import jdk.jfr.Category; +import jdk.jfr.Event; +import jdk.jfr.Label; + +@Label("Processing Event") +@Category("Processing") +public class ProcessingEvent extends Event { + @Label "Items Processed" + public int items; +} + +public class ProfilerDemo { + public void processItems(int count) { + ProcessingEvent event = new ProcessingEvent(); + event.begin(); + event.items = count; + + // 处理逻辑 + for (int i = 0; i < count; i++) { + // 执行工作 + } + + event.end(); + event.commit(); + } +} +``` + +```go !! go +// Go: 内置 CPU 性能分析 +package main + +import ( + "fmt" + "os" + "runtime/pprof" + "time" +) + +func main() { + // 开始 CPU 性能分析 + f, _ := os.Create("cpu.prof") + pprof.StartCPUProfile(f) + defer pprof.StopCPUProfile() + + // 运行要分析的代码 + processItems(1000000) + + f.Close() + fmt.Println("性能分析已保存到 cpu.prof") + // 使用以下命令分析: go tool pprof cpu.prof +} + +func processItems(count int) { + for i := 0; i < count; i++ { + // 执行工作 + calculate(i) + } +} + +func calculate(n int) int { + result := 0 + for i := 0; i < n; i++ { + result += i + } + return result +} +``` + + +### 内存性能分析 + + +```java !! java +// Java: 堆分析 +import java.util.ArrayList; +import java.util.List; + +public class MemoryProfiler { + private List allocations = new ArrayList<>(); + + public void allocateMemory() { + // Runtime.getRuntime().gc() 强制 GC + // MemoryMXBean 获取堆使用情况 + + for (int i = 0; i < 1000; i++) { + allocations.add(new byte[1024]); // 每个 1KB + } + + // 分析堆转储 + Runtime runtime = Runtime.getRuntime(); + long usedMemory = runtime.totalMemory() - runtime.freeMemory(); + System.out.println("已用内存: " + usedMemory + " 字节"); + } + + // 使用 VisualVM 分析堆转储 + // jmap -dump:format=b,file=heap.hprof +} +``` + +```go !! go +// Go: 内存性能分析 +package main + +import ( + "fmt" + "os" + "runtime/pprof" +) + +func main() { + // 内存性能分析 + f, _ := os.Create("mem.prof") + defer f.Close() + + // 获取内存快照 + pprof.WriteHeapProfile(f) + fmt.Println("内存性能分析已保存到 mem.prof") + // 使用以下命令分析: go tool pprof mem.prof +} + +func allocateMemory() { + allocations := make([][]byte, 0) + + for i := 0; i < 1000; i++ { + allocations = append(allocations, make([]byte, 1024)) + } + + // 获取内存统计 + var m runtime.MemStats + runtime.ReadMemStats(&m) + fmt.Printf("已分配内存: %d 字节\n", m.Alloc) + fmt.Printf("总分配次数: %d\n", m.TotalAlloc) + fmt.Printf("堆对象数: %d\n", m.HeapObjects) +} +``` + + +## 内存分配模式 + +### 避免不必要的分配 + + +```java !! java +// Java: 常见分配模式 +public class Allocations { + // 循环中的字符串拼接 - 不好 + public String buildStringBad(String[] items) { + String result = ""; + for (String item : items) { + result += item; // 每次迭代创建新字符串 + } + return result; + } + + // 使用 StringBuilder - 好 + public String buildStringGood(String[] items) { + StringBuilder sb = new StringBuilder(); + for (String item : items) { + sb.append(item); // 高效 + } + return sb.toString(); + } + + // 对象池示例 + private static final ObjectPool bufferPool = + new ObjectPool<>(() -> new Buffer()); + + public Buffer getBuffer() { + return bufferPool.borrow(); + } + + public void returnBuffer(Buffer buffer) { + bufferPool.release(buffer); + } +} +``` + +```go !! go +// Go: 高效分配模式 +package main + +import ( + "strings" +) + +// 不好:循环中的字符串拼接 +func buildStringBad(items []string) string { + result := "" + for _, item := range items { + result += item // 每次迭代创建新字符串 + } + return result +} + +// 好:使用 strings.Builder +func buildStringGood(items []string) string { + var builder strings.Builder + builder.Grow(len(items) * 10) // 预分配容量 + + for _, item := range items { + builder.WriteString(item) // 高效 + } + return builder.String() +} + +// 好:使用 buffer pool +var bufferPool = sync.Pool{ + New: func() interface{} { + return new(bytes.Buffer) + }, +} + +func getBuffer() *bytes.Buffer { + return bufferPool.Get().(*bytes.Buffer) +} + +func returnBuffer(buf *bytes.Buffer) { + buf.Reset() + bufferPool.Put(buf) +} + +// 好:预分配切片 +func preAllocateSlice(count int) []int { + // 使用容量避免重新分配 + slice := make([]int, 0, count) + for i := 0; i < count; i++ { + slice = append(slice, i) // 无需重新分配 + } + return slice +} +``` + + +## 基准测试 + + +```java !! java +// Java: JMH 基准测试 +import org.openjdk.jmh.annotations.*; +import java.util.concurrent.TimeUnit; + +@BenchmarkMode(Mode.AverageTime) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +@State(Scope.Benchmark) +public class Benchmarks { + private int[] data; + + @Setup + public void setup() { + data = new int[1000]; + for (int i = 0; i < data.length; i++) { + data[i] = i; + } + } + + @Benchmark + public int sumWithLoop() { + int sum = 0; + for (int num : data) { + sum += num; + } + return sum; + } + + @Benchmark + public int sumWithStream() { + return java.util.Arrays.stream(data).sum(); + } +} + +// 运行命令: java -jar benchmarks.jar +``` + +```go !! go +// Go: 内置基准测试 +package main + +import ( + "testing" +) + +var data []int + +func setup() { + data = make([]int, 1000) + for i := 0; i < len(data); i++ { + data[i] = i + } +} + +func BenchmarkSumWithLoop(b *testing.B) { + setup() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + sum := 0 + for _, num := range data { + sum += num + } + } +} + +func BenchmarkSumWithRange(b *testing.B) { + setup() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + sum := 0 + for i := range data { + sum += data[i] + } + } +} + +// 运行命令: go test -bench=. -benchmem +``` + + +## Goroutine 性能 + + +```java !! java +// Java: 线程开销 +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +public class ThreadPerformance { + private static final int TASK_COUNT = 100000; + + public void runWithThreads() throws InterruptedException { + long start = System.currentTimeMillis(); + + ExecutorService executor = Executors.newFixedThreadPool(100); + + for (int i = 0; i < TASK_COUNT; i++) { + final int taskNum = i; + executor.submit(() -> { + // 模拟工作 + int result = taskNum * 2; + }); + } + + executor.shutdown(); + executor.awaitTermination(1, TimeUnit.MINUTES); + + long duration = System.currentTimeMillis() - start; + System.out.println("线程: " + duration + "ms"); + // 输出: 线程: ~2000-5000ms + // 内存: 10 万线程约 ~500MB-1GB + } +} +``` + +```go !! go +// Go: Goroutine 效率 +package main + +import ( + "fmt" + "sync" + "time" +) + +const taskCount = 100000 + +func runWithGoroutines() { + start := time.Now() + + var wg sync.WaitGroup + + for i := 0; i < taskCount; i++ { + wg.Add(1) + go func(taskNum int) { + defer wg.Done() + // 模拟工作 + result := taskNum * 2 + _ = result + }(i) + } + + wg.Wait() + + duration := time.Since(start) + fmt.Printf("Goroutine: %v\n", duration) + // 输出: Goroutine: ~200-500ms + // 内存: 10 万 goroutine 约 ~50-100MB +} + +func main() { + runWithGoroutines() +} +``` + + +## Channel 性能 + + +```java !! java +// Java: BlockingQueue +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; + +public class QueuePerformance { + private BlockingQueue queue = new ArrayBlockingQueue<>(100); + + public void producer() throws InterruptedException { + for (int i = 0; i < 1000000; i++) { + queue.put(i); // 队列满时阻塞 + } + } + + public void consumer() throws InterruptedException { + while (true) { + Integer value = queue.take(); // 队列空时阻塞 + // 处理值 + } + } +} +``` + +```go !! go +// Go: 缓冲 channel +package main + +import "sync" + +// 无缓冲 channel(同步) +func unbufferedChannel() { + ch := make(chan int) + go func() { + for i := 0; i < 1000000; i++ { + ch <- i // 阻塞直到接收者准备就绪 + } + close(ch) + }() + + for value := range ch { + _ = value + } +} + +// 缓冲 channel(异步) +func bufferedChannel() { + ch := make(chan int, 100) // 缓冲大小 100 + go func() { + for i := 0; i < 1000000; i++ { + ch <- i // 缓冲满时才阻塞 + } + close(ch) + }() + + for value := range ch { + _ = value + } +} + +// 最佳实践:根据工作负载选择缓冲大小 +func optimizedChannel() { + // 对于生产者-消费者不同速率的场景 + ch := make(chan int, 1000) // 较大缓冲给更快生产者 + + var wg sync.WaitGroup + + // 多个生产者 + for i := 0; i < 10; i++ { + wg.Add(1) + go func(id int) { + defer wg.Done() + for j := 0; j < 10000; j++ { + ch <- id*10000 + j + } + }(i) + } + + // 单个消费者 + go func() { + for value := range ch { + _ = value + } + }() + + wg.Wait() + close(ch) +} +``` + + +## 字符串优化 + + +```java !! java +// Java: 字符串优化 +public class StringOptimization { + // 不好:循环中字符串拼接 + public String concatenateBad(List items) { + String result = ""; + for (String item : items) { + result += item; // 创建新对象 + } + return result; + } + + // 好:使用 StringBuilder + public String concatenateGood(List items) { + StringBuilder sb = new StringBuilder(items.size() * 16); + for (String item : items) { + sb.append(item); + } + return sb.toString(); + } + + // 字符串比较 + public boolean compareStrings(String a, String b) { + return a.equals(b); // 正确方式 + // return a == b; // 错误!比较引用 + } + + // 字符串驻留提高内存效率 + public void stringInterning() { + String s1 = "hello".intern(); // 驻留 + String s2 = "hello".intern(); // 相同引用 + boolean same = (s1 == s2); // true + } +} +``` + +```go !! go +// Go: 字符串优化 +package main + +import ( + "strings" +) + +// 不好:循环中字符串拼接 +func concatenateBad(items []string) string { + result := "" + for _, item := range items { + result += item // 低效 + } + return result +} + +// 好:使用 strings.Builder +func concatenateGood(items []string) string { + var builder strings.Builder + // 预分配容量 + builder.Grow(len(items) * 16) + + for _, item := range items { + builder.WriteString(item) + } + return builder.String() +} + +// 字符串比较(Go 中直接 == 即可!) +func compareStrings(a, b string) bool { + return a == b // 高效且正确 +} + +// 字符串驻留(手动) +var internPool = sync.Pool{ + New: func() interface{} { + return make(map[string]string) + }, +} + +// 字符串重用模式 +func stringReuse() { + // 常见模式:使用字节切片构建 + // 只在最后转换为字符串 + data := []byte("hello") + str := string(data) // 一次分配 + _ = str +} + +// 高效的字符串连接 +func joinStrings(items []string) string { + return strings.Join(items, ",") // 高效 +} +``` + + +## 切片性能 + + +```java !! java +// Java: ArrayList vs Array +public class CollectionPerformance { + // ArrayList(动态) + public void arrayListTest() { + List list = new ArrayList<>(); + for (int i = 0; i < 100000; i++) { + list.add(i); // 可能需要扩容 + } + } + + // Array(固定大小,更快) + public void arrayTest() { + int[] array = new int[100000]; + for (int i = 0; i < 100000; i++) { + array[i] = i; // 直接访问 + } + } + + // 预设大小的 ArrayList + public void preSizedList() { + List list = new ArrayList<>(100000); + for (int i = 0; i < 100000; i++) { + list.add(i); // 无需扩容 + } + } +} +``` + +```go !! go +// Go: 切片 vs 数组 +package main + +// 切片(动态) +func sliceTest() { + slice := make([]int, 0) + for i := 0; i < 100000; i++ { + slice = append(slice, i) // 可能增长并复制 + } +} + +// 预分配切片(更快) +func preAllocatedSlice() { + slice := make([]int, 0, 100000) // 容量预分配 + for i := 0; i < 100000; i++ { + slice = append(slice, i) // 无需复制 + } +} + +// 数组(固定大小,最快) +func arrayTest() { + var array [100000]int + for i := 0; i < 100000; i++ { + array[i] = i // 直接访问,无边界检查开销 + } +} + +// 切片性能技巧 +func sliceTricks() { + // 重用切片底层数组 + buffer := make([]int, 100) + + // 多次使用相同缓冲区 + work1 := buffer[:50] // 前 50 个元素 + work2 := buffer[50:] // 剩余 50 个元素 + + _ = work1 + _ = work2 +} + +// 高效清空切片 +func clearSlice(slice []int) { + // 好:重用底层数组 + for i := range slice { + slice[i] = 0 + } + + // 替代方案:创建新切片(可能分配) + // slice = make([]int, len(slice)) +} +``` + + +## 性能最佳实践 + +### 1. 尽可能预分配 + +```go +// 不好:重复分配 +func badGrowth() { + data := []int{} + for i := 0; i < 1000000; i++ { + data = append(data, i) // 多次重新分配 + } +} + +// 好:预分配容量 +func goodGrowth() { + data := make([]int, 0, 1000000) + for i := 0; i < 1000000; i++ { + data = append(data, i) // 无重新分配 + } +} +``` + +### 2. 适当使用值类型 + +```go +// 小结构体优先使用值类型 +type Point struct { + X, Y int +} + +// 按值传递(小结构体高效) +func distance(p1, p2 Point) int { + dx := p1.X - p2.X + dy := p1.Y - p2.Y + return dx*dx + dy*dy +} + +// 大结构体使用指针 +type LargeStruct struct { + data [1024]int +} + +func process(l *LargeStruct) { + // 使用指针避免复制 +} +``` + +### 3. 避免过早优化 + +```go +// 先编写清晰的代码 +func processData(items []string) string { + var result strings.Builder + for _, item := range items { + result.WriteString(item) + result.WriteString(",") + } + return result.String() +} + +// 先进行性能分析,只优化热点 +// 使用: go test -bench=. -cpuprofile=cpu.prof +``` + +## 常见性能陷阱 + +### 1. 子字符串分配 + + +```java !! java +// Java: substring() 复制数据(Java 7u6 之前) +public class Substrings { + public void processString(String text) { + // 创建新字符串对象 + String sub = text.substring(0, 10); + System.out.println(sub); + } +} +``` + +```go !! go +// Go: 字符串切片不复制 +package main + +func processString(text string) { + // 无分配!只是切片字符串 + sub := text[:10] + println(sub) +} + +// 但要注意:保持原字符串在内存中 +func memoryLeakExample() { + // 读取 1GB 文件 + data := readFile("largefile.txt") // 1GB + + // 提取小部分 + firstLine := getFirstLine(data) // 前 100 个字符 + + // firstLine 仍然引用整个 1GB 数据! + useString(firstLine) + + // 修复:转换为 []byte 再转回 +} + +func getFirstLine(data string) string { + // 这会保持整个 data 在内存中 + return data[:100] + + // 更好:复制到新字符串 + // return strings.Clone(data[:100]) +} +``` + + +### 2. Range 循环变量捕获 + +```go +// 错误:所有 goroutine 捕获同一变量 +func wrongRange() { + items := []int{1, 2, 3, 4, 5} + for _, item := range items { + go func() { + println(item) // 总是打印 5 + }() + } +} + +// 正确:将变量作为参数传递 +func correctRange() { + items := []int{1, 2, 3, 4, 5} + for _, item := range items { + go func(val int) { + println(val) // 打印正确的值 + }(item) + } +} +``` + +--- + +### 练习问题: +1. 什么时候应该预分配切片容量而不是使用 append? +2. Go 的垃圾回收与 Java 有什么不同? +3. 缓冲 channel 和无缓冲 channel 有什么权衡? +4. 为什么 Go 中的字符串切片比 Java 的 substring() 更高效? + +### 项目想法: +- 创建一个性能比较工具,对等价的 Java 和 Go 代码进行基准测试 +- 构建一个 Web 服务,展示 goroutine 相比 Java 线程的效率 +- 实现一个 Go 应用程序的内存性能分析仪表板 + +### 下一步: +- 学习 Go 应用程序的部署策略 +- 了解生产环境监控和可观察性 +- 探索真实项目架构 diff --git a/content/docs/java2go/module-12-performance.zh-tw.mdx b/content/docs/java2go/module-12-performance.zh-tw.mdx new file mode 100644 index 0000000..9e89b0c --- /dev/null +++ b/content/docs/java2go/module-12-performance.zh-tw.mdx @@ -0,0 +1,917 @@ +--- +title: "模組 12:Go 效能優化" +--- + +本模組介紹 Go 中的效能優化技術,並與 Java 的效能調優方法進行比較。 + +## 效能哲學:Go vs Java + +**Java 效能哲學:** +- JVM 優化(JIT 編譯、GC 調優) +- 使用 VisualVM、JProfiler 進行效能分析 +- 垃圾回收調優 +- 記憶體池管理 +- 執行緒池配置 + +**Go 效能哲學:** +- 簡單、可預測的效能 +- 內建效能分析工具(pprof) +- 高效的記憶體分配模式 +- 最小的執行時開銷 +- 直接編譯為機器碼 + + +```java !! java +// Java: 效能考慮 +public class Performance { + // 需要預熱才能達到最佳效能 + // JIT 編譯在執行時進行 + // 可能發生垃圾回收暫停 + + private List numbers = new ArrayList<>(); + + public void addNumber(int num) { + numbers.add(num); // 可能觸發 GC 和陣列擴容 + } + + // 物件分配開銷 + public String processData(String input) { + StringBuilder sb = new StringBuilder(); + // 字串操作建立中間物件 + return sb.toString(); + } +} +``` + +```go !! go +// Go: 可預測的效能 +package main + +// 無需預熱 +// 直接編譯 +// 簡單的 GC,暫停時間短 + +type Performance struct { + numbers []int +} + +func (p *Performance) AddNumber(num int) { + p.numbers = append(p.numbers, num) // 高效增長 +} + +// 最小分配開銷 +func (p *Performance) ProcessData(input string) string { + var builder strings.Builder + // 高效的字串構建 + return builder.String() +} +``` + + +## 內建效能分析工具 + +Go 提供了優秀的內建效能分析工具,比 Java 的部分析器更容易使用。 + +### CPU 效能分析 + + +```java !! java +// Java: 使用 VisualVM/JProfiler 進行 CPU 效能分析 +/* +1. 使用效能分析代理啟動應用程式: + java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 MyApp + +2. 使用 VisualVM 或 JProfiler 連線 + +3. 獲取 CPU 快照 + +4. 分析熱點 + +範例:使用 Java Flight Recorder +*/ +import jdk.jfr.Category; +import jdk.jfr.Event; +import jdk.jfr.Label; + +@Label("Processing Event") +@Category("Processing") +public class ProcessingEvent extends Event { + @Label "Items Processed" + public int items; +} + +public class ProfilerDemo { + public void processItems(int count) { + ProcessingEvent event = new ProcessingEvent(); + event.begin(); + event.items = count; + + // 處理邏輯 + for (int i = 0; i < count; i++) { + // 執行工作 + } + + event.end(); + event.commit(); + } +} +``` + +```go !! go +// Go: 內建 CPU 效能分析 +package main + +import ( + "fmt" + "os" + "runtime/pprof" + "time" +) + +func main() { + // 開始 CPU 效能分析 + f, _ := os.Create("cpu.prof") + pprof.StartCPUProfile(f) + defer pprof.StopCPUProfile() + + // 執行要分析的程式碼 + processItems(1000000) + + f.Close() + fmt.Println("效能分析已儲存到 cpu.prof") + // 使用以下指令分析: go tool pprof cpu.prof +} + +func processItems(count int) { + for i := 0; i < count; i++ { + // 執行工作 + calculate(i) + } +} + +func calculate(n int) int { + result := 0 + for i := 0; i < n; i++ { + result += i + } + return result +} +``` + + +### 記憶體效能分析 + + +```java !! java +// Java: 堆分析 +import java.util.ArrayList; +import java.util.List; + +public class MemoryProfiler { + private List allocations = new ArrayList<>(); + + public void allocateMemory() { + // Runtime.getRuntime().gc() 強制 GC + // MemoryMXBean 取得堆使用情況 + + for (int i = 0; i < 1000; i++) { + allocations.add(new byte[1024]); // 每 1KB + } + + // 分析堆轉儲 + Runtime runtime = Runtime.getRuntime(); + long usedMemory = runtime.totalMemory() - runtime.freeMemory(); + System.out.println("已用記憶體: " + usedMemory + " 位元組"); + } + + // 使用 VisualVM 分析堆轉儲 + // jmap -dump:format=b,file=heap.hprof +} +``` + +```go !! go +// Go: 記憶體效能分析 +package main + +import ( + "fmt" + "os" + "runtime/pprof" +) + +func main() { + // 記憶體效能分析 + f, _ := os.Create("mem.prof") + defer f.Close() + + // 取得記憶體快照 + pprof.WriteHeapProfile(f) + fmt.Println("記憶體效能分析已儲存到 mem.prof") + // 使用以下指令分析: go tool pprof mem.prof +} + +func allocateMemory() { + allocations := make([][]byte, 0) + + for i := 0; i < 1000; i++ { + allocations = append(allocations, make([]byte, 1024)) + } + + // 取得記憶體統計 + var m runtime.MemStats + runtime.ReadMemStats(&m) + fmt.Printf("已分配記憶體: %d 位元組\n", m.Alloc) + fmt.Printf("總分配次數: %d\n", m.TotalAlloc) + fmt.Printf("堆物件數: %d\n", m.HeapObjects) +} +``` + + +## 記憶體分配模式 + +### 避免不必要的分配 + + +```java !! java +// Java: 常見分配模式 +public class Allocations { + // 迴圈中的字串拼接 - 不好 + public String buildStringBad(String[] items) { + String result = ""; + for (String item : items) { + result += item; // 每次迭代建立新字串 + } + return result; + } + + // 使用 StringBuilder - 好 + public String buildStringGood(String[] items) { + StringBuilder sb = new StringBuilder(); + for (String item : items) { + sb.append(item); // 高效 + } + return sb.toString(); + } + + // 物件池範例 + private static final ObjectPool bufferPool = + new ObjectPool<>(() -> new Buffer()); + + public Buffer getBuffer() { + return bufferPool.borrow(); + } + + public void returnBuffer(Buffer buffer) { + bufferPool.release(buffer); + } +} +``` + +```go !! go +// Go: 高效分配模式 +package main + +import ( + "strings" +) + +// 不好:迴圈中的字串拼接 +func buildStringBad(items []string) string { + result := "" + for _, item := range items { + result += item // 每次迭代建立新字串 + } + return result +} + +// 好:使用 strings.Builder +func buildStringGood(items []string) string { + var builder strings.Builder + builder.Grow(len(items) * 10) // 預分配容量 + + for _, item := range items { + builder.WriteString(item) // 高效 + } + return builder.String() +} + +// 好:使用 buffer pool +var bufferPool = sync.Pool{ + New: func() interface{} { + return new(bytes.Buffer) + }, +} + +func getBuffer() *bytes.Buffer { + return bufferPool.Get().(*bytes.Buffer) +} + +func returnBuffer(buf *bytes.Buffer) { + buf.Reset() + bufferPool.Put(buf) +} + +// 好:預分配切片 +func preAllocateSlice(count int) []int { + // 使用容量避免重新分配 + slice := make([]int, 0, count) + for i := 0; i < count; i++ { + slice = append(slice, i) // 無需重新分配 + } + return slice +} +``` + + +## 基準測試 + + +```java !! java +// Java: JMH 基準測試 +import org.openjdk.jmh.annotations.*; +import java.util.concurrent.TimeUnit; + +@BenchmarkMode(Mode.AverageTime) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +@State(Scope.Benchmark) +public class Benchmarks { + private int[] data; + + @Setup + public void setup() { + data = new int[1000]; + for (int i = 0; i < data.length; i++) { + data[i] = i; + } + } + + @Benchmark + public int sumWithLoop() { + int sum = 0; + for (int num : data) { + sum += num; + } + return sum; + } + + @Benchmark + public int sumWithStream() { + return java.util.Arrays.stream(data).sum(); + } +} + +// 執行指令: java -jar benchmarks.jar +``` + +```go !! go +// Go: 內建基準測試 +package main + +import ( + "testing" +) + +var data []int + +func setup() { + data = make([]int, 1000) + for i := 0; i < len(data); i++ { + data[i] = i + } +} + +func BenchmarkSumWithLoop(b *testing.B) { + setup() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + sum := 0 + for _, num := range data { + sum += num + } + } +} + +func BenchmarkSumWithRange(b *testing.B) { + setup() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + sum := 0 + for i := range data { + sum += data[i] + } + } +} + +// 執行指令: go test -bench=. -benchmem +``` + + +## Goroutine 效能 + + +```java !! java +// Java: 執行緒開銷 +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +public class ThreadPerformance { + private static final int TASK_COUNT = 100000; + + public void runWithThreads() throws InterruptedException { + long start = System.currentTimeMillis(); + + ExecutorService executor = Executors.newFixedThreadPool(100); + + for (int i = 0; i < TASK_COUNT; i++) { + final int taskNum = i; + executor.submit(() -> { + // 模擬工作 + int result = taskNum * 2; + }); + } + + executor.shutdown(); + executor.awaitTermination(1, TimeUnit.MINUTES); + + long duration = System.currentTimeMillis() - start; + System.out.println("執行緒: " + duration + "ms"); + // 輸出: 執行緒: ~2000-5000ms + // 記憶體: 10 萬執行緒約 ~500MB-1GB + } +} +``` + +```go !! go +// Go: Goroutine 效率 +package main + +import ( + "fmt" + "sync" + "time" +) + +const taskCount = 100000 + +func runWithGoroutines() { + start := time.Now() + + var wg sync.WaitGroup + + for i := 0; i < taskCount; i++ { + wg.Add(1) + go func(taskNum int) { + defer wg.Done() + // 模擬工作 + result := taskNum * 2 + _ = result + }(i) + } + + wg.Wait() + + duration := time.Since(start) + fmt.Printf("Goroutine: %v\n", duration) + // 輸出: Goroutine: ~200-500ms + // 記憶體: 10 萬 goroutine 約 ~50-100MB +} + +func main() { + runWithGoroutines() +} +``` + + +## Channel 效能 + + +```java !! java +// Java: BlockingQueue +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; + +public class QueuePerformance { + private BlockingQueue queue = new ArrayBlockingQueue<>(100); + + public void producer() throws InterruptedException { + for (int i = 0; i < 1000000; i++) { + queue.put(i); // 佇列滿時阻塞 + } + } + + public void consumer() throws InterruptedException { + while (true) { + Integer value = queue.take(); // 佇列空時阻塞 + // 處理值 + } + } +} +``` + +```go !! go +// Go: 緩衝 channel +package main + +import "sync" + +// 無緩衝 channel(同步) +func unbufferedChannel() { + ch := make(chan int) + go func() { + for i := 0; i < 1000000; i++ { + ch <- i // 阻塞直到接收者準備就緒 + } + close(ch) + }() + + for value := range ch { + _ = value + } +} + +// 緩衝 channel(非同步) +func bufferedChannel() { + ch := make(chan int, 100) // 緩衝大小 100 + go func() { + for i := 0; i < 1000000; i++ { + ch <- i // 緩衝滿時才阻塞 + } + close(ch) + }() + + for value := range ch { + _ = value + } +} + +// 最佳實踐:根據工作負載選擇緩衝大小 +func optimizedChannel() { + // 對於生產者-消費者不同速率的場景 + ch := make(chan int, 1000) // 較大緩衝給更快生產者 + + var wg sync.WaitGroup + + // 多個生產者 + for i := 0; i < 10; i++ { + wg.Add(1) + go func(id int) { + defer wg.Done() + for j := 0; j < 10000; j++ { + ch <- id*10000 + j + } + }(i) + } + + // 單一消費者 + go func() { + for value := range ch { + _ = value + } + }() + + wg.Wait() + close(ch) +} +``` + + +## 字串優化 + + +```java !! java +// Java: 字串優化 +public class StringOptimization { + // 不好:迴圈中字串拼接 + public String concatenateBad(List items) { + String result = ""; + for (String item : items) { + result += item; // 建立新物件 + } + return result; + } + + // 好:使用 StringBuilder + public String concatenateGood(List items) { + StringBuilder sb = new StringBuilder(items.size() * 16); + for (String item : items) { + sb.append(item); + } + return sb.toString(); + } + + // 字串比較 + public boolean compareStrings(String a, String b) { + return a.equals(b); // 正確方式 + // return a == b; // 錯誤!比較引用 + } + + // 字串駐留提高記憶體效率 + public void stringInterning() { + String s1 = "hello".intern(); // 駐留 + String s2 = "hello".intern(); // 相同引用 + boolean same = (s1 == s2); // true + } +} +``` + +```go !! go +// Go: 字串優化 +package main + +import ( + "strings" +) + +// 不好:迴圈中字串拼接 +func concatenateBad(items []string) string { + result := "" + for _, item := range items { + result += item // 低效 + } + return result +} + +// 好:使用 strings.Builder +func concatenateGood(items []string) string { + var builder strings.Builder + // 預分配容量 + builder.Grow(len(items) * 16) + + for _, item := range items { + builder.WriteString(item) + } + return builder.String() +} + +// 字串比較(Go 中直接 == 即可!) +func compareStrings(a, b string) bool { + return a == b // 高效且正確 +} + +// 字串駐留(手動) +var internPool = sync.Pool{ + New: func() interface{} { + return make(map[string]string) + }, +} + +// 字串重用模式 +func stringReuse() { + // 常見模式:使用位元組切片構建 + // 只在最後轉換為字串 + data := []byte("hello") + str := string(data) // 一次分配 + _ = str +} + +// 高效的字串連接 +func joinStrings(items []string) string { + return strings.Join(items, ",") // 高效 +} +``` + + +## 切片效能 + + +```java !! java +// Java: ArrayList vs Array +public class CollectionPerformance { + // ArrayList(動態) + public void arrayListTest() { + List list = new ArrayList<>(); + for (int i = 0; i < 100000; i++) { + list.add(i); // 可能需要擴容 + } + } + + // Array(固定大小,更快) + public void arrayTest() { + int[] array = new int[100000]; + for (int i = 0; i < 100000; i++) { + array[i] = i; // 直接存取 + } + } + + // 預設大小的 ArrayList + public void preSizedList() { + List list = new ArrayList<>(100000); + for (int i = 0; i < 100000; i++) { + list.add(i); // 無需擴容 + } + } +} +``` + +```go !! go +// Go: 切片 vs 陣列 +package main + +// 切片(動態) +func sliceTest() { + slice := make([]int, 0) + for i := 0; i < 100000; i++ { + slice = append(slice, i) // 可能增長並複製 + } +} + +// 預分配切片(更快) +func preAllocatedSlice() { + slice := make([]int, 0, 100000) // 容量預分配 + for i := 0; i < 100000; i++ { + slice = append(slice, i) // 無需複製 + } +} + +// 陣列(固定大小,最快) +func arrayTest() { + var array [100000]int + for i := 0; i < 100000; i++ { + array[i] = i // 直接存取,無邊界檢查開銷 + } +} + +// 切片效能技巧 +func sliceTricks() { + // 重用切片底層陣列 + buffer := make([]int, 100) + + // 多次使用相同緩衝區 + work1 := buffer[:50] // 前 50 個元素 + work2 := buffer[50:] // 剩餘 50 個元素 + + _ = work1 + _ = work2 +} + +// 高效清空切片 +func clearSlice(slice []int) { + // 好:重用底層陣列 + for i := range slice { + slice[i] = 0 + } + + // 替代方案:建立新切片(可能分配) + // slice = make([]int, len(slice)) +} +``` + + +## 效能最佳實踐 + +### 1. 盡可能預分配 + +```go +// 不好:重複分配 +func badGrowth() { + data := []int{} + for i := 0; i < 1000000; i++ { + data = append(data, i) // 多次重新分配 + } +} + +// 好:預分配容量 +func goodGrowth() { + data := make([]int, 0, 1000000) + for i := 0; i < 1000000; i++ { + data = append(data, i) // 無重新分配 + } +} +``` + +### 2. 適當使用值型別 + +```go +// 小結構體優先使用值型別 +type Point struct { + X, Y int +} + +// 按值傳遞(小結構體高效) +func distance(p1, p2 Point) int { + dx := p1.X - p2.X + dy := p1.Y - p2.Y + return dx*dx + dy*dy +} + +// 大結構體使用指標 +type LargeStruct struct { + data [1024]int +} + +func process(l *LargeStruct) { + // 使用指標避免複製 +} +``` + +### 3. 避免過早優化 + +```go +// 先編寫清晰的程式碼 +func processData(items []string) string { + var result strings.Builder + for _, item := range items { + result.WriteString(item) + result.WriteString(",") + } + return result.String() +} + +// 先進行效能分析,只優化熱點 +// 使用: go test -bench=. -cpuprofile=cpu.prof +``` + +## 常見效能陷阱 + +### 1. 子字串分配 + + +```java !! java +// Java: substring() 複製資料(Java 7u6 之前) +public class Substrings { + public void processString(String text) { + // 建立新字串物件 + String sub = text.substring(0, 10); + System.out.println(sub); + } +} +``` + +```go !! go +// Go: 字串切片不複製 +package main + +func processString(text string) { + // 無分配!只是切片字串 + sub := text[:10] + println(sub) +} + +// 但要注意:保持原字串在記憶體中 +func memoryLeakExample() { + // 讀取 1GB 檔案 + data := readFile("largefile.txt") // 1GB + + // 提取小部分 + firstLine := getFirstLine(data) // 前 100 個字元 + + // firstLine 仍然引用整個 1GB 資料! + useString(firstLine) + + // 修復:轉換為 []byte 再轉回 +} + +func getFirstLine(data string) string { + // 這會保持整個 data 在記憶體中 + return data[:100] + + // 更好:複製到新字串 + // return strings.Clone(data[:100]) +} +``` + + +### 2. Range 迴圈變數捕獲 + +```go +// 錯誤:所有 goroutine 捕獲同一變數 +func wrongRange() { + items := []int{1, 2, 3, 4, 5} + for _, item := range items { + go func() { + println(item) // 總是打印 5 + }() + } +} + +// 正確:將變數作為參數傳遞 +func correctRange() { + items := []int{1, 2, 3, 4, 5} + for _, item := range items { + go func(val int) { + println(val) // 打印正確的值 + }(item) + } +} +``` + +--- + +### 練習問題: +1. 什麼時候應該預分配切片容量而不是使用 append? +2. Go 的垃圾回收與 Java 有什麼不同? +3. 緩衝 channel 和無緩衝 channel 有什麼權衡? +4. 為什麼 Go 中的字串切片比 Java 的 substring() 更高效? + +### 專案想法: +- 建立一個效能比較工具,對等價的 Java 和 Go 程式碼進行基準測試 +- 建構一個 Web 服務,展示 goroutine 相比 Java 執行緒的效率 +- 實作一個 Go 應用程式的記憶體效能分析儀表板 + +### 下一步: +- 學習 Go 應用程式的部署策略 +- 了解生產環境監控和可觀察性 +- 探索真實專案架構 diff --git a/content/docs/java2go/module-13-deployment.mdx b/content/docs/java2go/module-13-deployment.mdx new file mode 100644 index 0000000..5ddc41e --- /dev/null +++ b/content/docs/java2go/module-13-deployment.mdx @@ -0,0 +1,808 @@ +--- +title: "Module 13: Building and Deployment" +--- + +This module covers building and deploying Go applications, comparing Go's simple deployment model with Java's more complex deployment strategies. + +## Deployment Philosophy: Go vs Java + +**Java Deployment:** +- Requires JVM installed on target machine +- JAR/WAR files containing bytecode +- Application servers (Tomcat, Jetty, WebSphere) +- Heavy startup time and memory footprint +- Complex dependency management (Maven/Gradle) + +**Go Deployment:** +- Single static binary executable +- No runtime dependencies required +- Cross-compilation support +- Fast startup and minimal memory footprint +- Simple dependency management (go.mod) + + +```java !! java +// Java: Build with Maven/Gradle +// pom.xml +/* + + 4.0.0 + com.example + myapp + 1.0.0 + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + + org.apache.maven.plugins + maven-shade-plugin + 3.2.4 + + + package + shade + + + + + + +*/ + +// Build commands: +// mvn clean package +// java -jar target/myapp-1.0.0.jar + +public class Main { + public static void main(String[] args) { + System.out.println("Hello, World!"); + } +} +``` + +```go !! go +// Go: Build with go build +// go.mod +/* +module myapp + +go 1.21 + +require github.com/gin-gonic/gin v1.9.1 +*/ + +package main + +import "fmt" + +func main() { + fmt.Println("Hello, World!") +} + +// Build commands: +// go build -o myapp +// ./myapp + +// Cross-compilation: +// GOOS=linux GOARCH=amd64 go build -o myapp-linux +// GOOS=windows GOARCH=amd64 go build -o myapp.exe +// GOOS=darwin GOARCH=arm64 go build -o myapp-mac-arm +``` + + +## Cross-Compilation + +Go makes cross-compilation trivial compared to Java's platform-specific builds. + + +```java !! java +// Java: Platform-specific considerations +/* +Java is "write once, run anywhere" but requires: +1. JVM for each target platform +2. Platform-specific native libraries (JNI) +3. Different application servers + +Building native images with GraalVM: +*/ +public class NativeBuild { + // Requires GraalVM installed + // native-image:native-image -jar myapp.jar + + public static void main(String[] args) { + System.out.println("Native image!"); + } +} + +/* +Build process: +1. Install GraalVM +2. Build native image (takes minutes) +3. Platform-specific binary produced +4. Limited support for some Java features +*/ +``` + +```go !! go +// Go: Easy cross-compilation +package main + +import ( + "fmt" + "runtime" +) + +func main() { + fmt.Printf("OS: %s, Arch: %s\n", runtime.GOOS, runtime.GOARCH) + fmt.Println("Cross-compilation is easy!") +} + +/* +Build for different platforms: +# Linux (amd64) +GOOS=linux GOARCH=amd64 go build -o myapp-linux + +# Windows (amd64) +GOOS=windows GOARCH=amd64 go build -o myapp.exe + +# macOS (Apple Silicon) +GOOS=darwin GOARCH=arm64 go build -o myapp-mac-arm + +# Linux ARM (Raspberry Pi) +GOOS=linux GOARCH=arm64 go build -o myapp-pi + +# Build for all platforms at once +GOOS=linux GOARCH=amd64 go build -o dist/linux-amd64/myapp +GOOS=windows GOARCH=amd64 go build -o dist/windows-amd64/myapp.exe +GOOS=darwin GOARCH=amd64 go build -o dist/darwin-amd64/myapp +GOOS=darwin GOARCH=arm64 go build -o dist/darwin-arm64/myapp +*/ +``` + + +## Docker Deployment + + +```dockerfile !! dockerfile +# Java: Multi-stage Dockerfile +FROM maven:3.8-openjdk-17 AS builder + +WORKDIR /app +COPY pom.xml . +COPY src ./src + +RUN mvn clean package -DskipTests + +# Runtime stage +FROM openjdk:17-slim + +WORKDIR /app +COPY --from=builder /app/target/myapp.jar app.jar + +# Expose port +EXPOSE 8080 + +# Set JVM options +ENV JAVA_OPTS="-Xmx512m -Xms256m" + +ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"] + +# Result: ~400-500MB image +# Startup time: 2-5 seconds +``` + +```dockerfile !! dockerfile +# Go: Multi-stage Dockerfile +FROM golang:1.21-alpine AS builder + +WORKDIR /app + +# Download dependencies first (better layer caching) +COPY go.mod go.sum ./ +RUN go mod download + +COPY . . + +# Build the application +RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main . + +# Runtime stage - minimal base image +FROM alpine:latest + +WORKDIR /app + +# Copy the binary +COPY --from=builder /app/main . + +# Expose port +EXPOSE 8080 + +# Run the binary +ENTRYPOINT ["./main"] + +# Result: ~10-20MB image +# Startup time: <100ms +``` + + +## Build Optimization + + +```bash !! bash +# Java: Build optimization + +## Maven +# Skip tests for faster builds +mvn package -DskipTests + +# Parallel builds +mvn -T 4 package + +# Offline mode (uses cached dependencies) +mvn -o package + +# Profile-specific builds +mvn package -P production + +## Gradle +# Configure build cache +org.gradle.caching=true + +# Parallel execution +org.gradle.parallel=true + +# Configuration cache +org.gradle.configuration-cache=true +``` + +```go !! go +// Go: Build optimization techniques + +// 1. Build tags and constraints +//go:build !windows + +package main + +import "fmt" + +func main() { + fmt.Println("Not built for Windows") +} + +// 2. Link-time optimization +// go build -ldflags="-s -w" +// -s: Remove symbol table +// -w: Remove DWARF debug info +// Result: 20-30% smaller binary + +// 3. Build tags for platform-specific code +//go:build linux && amd64 + +package main + +func optimizeForLinuxAMD64() { + // Linux AMD64-specific optimizations +} + +// 4. Trim paths for smaller binaries +// go build -trimpath + +// 5. Upx compression (after build) +// upx --best --lzma myapp +// Can reduce size by 50-70% +``` + + +## Dependency Management + + +```xml !! xml + + + + + + org.springframework.boot + spring-boot-starter-web + 3.1.0 + + + + + + com.example + some-library + 2.0.0 + + + conflicting.lib + bad-version + + + + + + + + + org.springframework.boot + spring-boot-dependencies + 3.1.0 + pom + import + + + + + +``` + +```go !! go +// Go: go.mod for dependency management +module myapp + +go 1.21 + +require ( + github.com/gin-gonic/gin v1.9.1 + github.com/go-redis/redis/v8 v8.11.5 + gorm.io/gorm v1.25.4 +) + +// Go solves diamond dependency problem differently: +// - Minimal version selection (MVS) +// - No need for conflict resolution +// - All versions must be compatible + +/* +Commands: +go mod init myapp # Initialize module +go mod tidy # Clean up dependencies +go get github.com/gin-gonic/gin@latest # Add dependency +go get -u ./... # Update all dependencies +go mod verify # Verify dependencies +go mod vendor # Vendor dependencies +*/ +``` + + +## Environment Configuration + + +```java !! java +// Java: Configuration approaches +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.PropertySource; + +@Configuration +@PropertySource(value = "classpath:application-${environment}.properties") +public class AppConfig { + + @Value("${app.port}") + private int port; + + @Value("${app.database.url}") + private String databaseUrl; + + @Value("${app.api.key}") + private String apiKey; + + // application.properties: + // app.port=8080 + // app.database.url=jdbc:postgresql://localhost:5432/mydb + // app.api.key=${API_KEY} + + // Environment variables: + // export API_KEY="secret-key" + // java -jar app.jar --spring.profiles.active=production +} + +// Or use external config files: +// java -jar app.jar --spring.config.location=file:/path/to/config/ +``` + +```go !! go +// Go: Configuration approaches +package main + +import ( + "fmt" + "os" + "strconv" +) + +// Simple struct-based configuration +type Config struct { + Port int + DatabaseURL string + APIKey string +} + +func LoadConfig() *Config { + config := &Config{ + Port: getEnvInt("PORT", 8080), + DatabaseURL: getEnv("DATABASE_URL", "postgres://localhost:5432/mydb"), + APIKey: getEnv("API_KEY", ""), + } + return config +} + +func getEnv(key, defaultValue string) string { + if value := os.Getenv(key); value != "" { + return value + } + return defaultValue +} + +func getEnvInt(key string, defaultValue int) int { + if value := os.Getenv(key); value != "" { + if intVal, err := strconv.Atoi(value); err == nil { + return intVal + } + } + return defaultValue +} + +// Using viper for advanced configuration: +/* +import github.com/spf13/viper + +viper.SetConfigName("config") +viper.SetConfigType("yaml") +viper.AddConfigPath(".") +viper.AddConfigPath("/etc/myapp/") +viper.AutomaticEnv() + +viper.SetDefault("port", 8080) +viper.BindEnv("api_key", "API_KEY") + +if err := viper.ReadInConfig(); err != nil { + log.Fatalf("Error reading config: %v", err) +} + +port := viper.GetInt("port") +apiKey := viper.GetString("api_key") +*/ +``` + + +## Cloud Deployment + + +```yaml !! yaml +# Java: Kubernetes Deployment +apiVersion: apps/v1 +kind: Deployment +metadata: + name: java-app +spec: + replicas: 3 + selector: + matchLabels: + app: java-app + template: + metadata: + labels: + app: java-app + spec: + containers: + - name: app + image: myregistry/java-app:latest + ports: + - containerPort: 8080 + env: + - name: JAVA_OPTS + value: "-Xmx512m -Xms256m" + - name: SPRING_PROFILES_ACTIVE + value: "production" + resources: + requests: + memory: "512Mi" + cpu: "250m" + limits: + memory: "1Gi" + cpu: "500m" + livenessProbe: + httpGet: + path: /actuator/health + port: 8080 + initialDelaySeconds: 60 + periodSeconds: 10 + readinessProbe: + httpGet: + path: /actuator/health + port: 8080 + initialDelaySeconds: 30 + periodSeconds: 5 +``` + +```yaml !! yaml +# Go: Kubernetes Deployment +apiVersion: apps/v1 +kind: Deployment +metadata: + name: go-app +spec: + replicas: 3 + selector: + matchLabels: + app: go-app + template: + metadata: + labels: + app: go-app + spec: + containers: + - name: app + image: myregistry/go-app:latest + ports: + - containerPort: 8080 + env: + - name: PORT + value: "8080" + - name: DATABASE_URL + valueFrom: + secretKeyRef: + name: db-secret + key: url + resources: + requests: + memory: "64Mi" + cpu: "50m" + limits: + memory: "128Mi" + cpu: "100m" + livenessProbe: + httpGet: + path: /health + port: 8080 + initialDelaySeconds: 5 + periodSeconds: 10 + readinessProbe: + httpGet: + path: /health + port: 8080 + initialDelaySeconds: 2 + periodSeconds: 5 +``` + + +## CI/CD Pipeline + + +```yaml !! yaml +# Java: GitHub Actions for Maven +name: Java CI + +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + + - name: Cache Maven packages + uses: actions/cache@v3 + with: + path: ~/.m2 + key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} + + - name: Build with Maven + run: mvn -B package --file pom.xml + + - name: Run tests + run: mvn test + + - name: Build Docker image + run: docker build -t myapp:${{ github.sha }} . +``` + +```yaml !! yaml +# Go: GitHub Actions +name: Go CI + +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: '1.21' + + - name: Cache Go modules + uses: actions/cache@v3 + with: + path: | + ~/.cache/go-build + ~/go/pkg/mod + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + + - name: Download dependencies + run: go mod download + + - name: Run go vet + run: go vet ./... + + - name: Run tests + run: go test -v -race -coverprofile=coverage.txt ./... + + - name: Build + run: go build -v ./... + + - name: Build Docker image + run: docker build -t myapp:${{ github.sha }} . +``` + + +## Zero-Downtime Deployment + + +```java !! java +// Java: Zero-downtime deployment considerations +/* +1. Rolling updates with Kubernetes +2. Blue-green deployment +3. Canary deployments +4. Load balancer configuration + +Example: Spring Boot with graceful shutdown +*/ +@Component +public class ShutdownConfig { + + @PreDestroy + public void onShutdown() { + // Close connections gracefully + // Finish in-flight requests + // Release resources + } +} + +// application.properties: +server.shutdown=graceful +spring.lifecycle.timeout-per-shutdown-phase=30s + +/* +Challenges: +- Long JVM warmup time +- Memory-intensive +- Complex class loading +- Session state management +*/ +``` + +```go !! go +// Go: Zero-downtime deployment +package main + +import ( + "context" + "fmt" + "net/http" + "os" + "os/signal" + "syscall" + "time" +) + +func main() { + server := &http.Server{ + Addr: ":8080", + Handler: newHandler(), + } + + // Start server in goroutine + go func() { + fmt.Println("Server starting on :8080") + if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { + fmt.Printf("Server error: %v\n", err) + } + }() + + // Wait for interrupt signal + quit := make(chan os.Signal, 1) + signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) + <-quit + + fmt.Println("Shutting down server...") + + // Graceful shutdown with timeout + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + if err := server.Shutdown(ctx); err != nil { + fmt.Printf("Server shutdown error: %v\n", err) + } + + fmt.Println("Server stopped") +} + +func newHandler() http.Handler { + mux := http.NewServeMux() + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintln(w, "Hello, World!") + }) + mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintln(w, "OK") + }) + return mux +} + +/* +Advantages: +- Instant startup (<100ms) +- Low memory footprint +- Easy graceful shutdown +- No session state to manage +- Simple process management +*/ +``` + + +## Production Checklist + +### Java Production Checklist: +- [ ] JVM heap size configured +- [ ] GC strategy selected +- [ ] Application server tuned +- [ ] Connection pools sized +- [ ] Monitoring enabled (JMX, Micrometer) +- [ ] Logging configured +- [ ] Security hardening +- [ ] Database connection testing +- [ ] Load testing performed +- [ ] Backup and recovery plan + +### Go Production Checklist: +- [ ] Binary stripped and optimized +- [ ] Error handling and logging +- [ ] Health check endpoints +- [ ] Graceful shutdown implemented +- [ ] Memory profiling tested +- [ ] CPU profiling tested +- [ ] Rate limiting configured +- [ ] Security headers set +- [ ] Database connection pooling +- [ ] Metrics and tracing enabled + +--- + +### Practice Questions: +1. Why does Go produce smaller Docker images than Java? +2. What are the advantages of Go's cross-compilation over Java's "write once, run anywhere"? +3. How does Go's minimal version selection differ from Maven's dependency resolution? +4. Why is zero-downtime deployment easier with Go? + +### Project Ideas: +- Create a multi-stage Dockerfile for a Go microservice +- Set up a CI/CD pipeline for a Go application +- Implement blue-green deployment for a Go service +- Build a deployment automation tool + +### Next Steps: +- Learn about Go's build tools and ecosystem +- Explore Go's third-party libraries and frameworks +- Understand database integration in Go +- Build production-ready applications diff --git a/content/docs/java2go/module-13-deployment.zh-cn.mdx b/content/docs/java2go/module-13-deployment.zh-cn.mdx new file mode 100644 index 0000000..29c30b2 --- /dev/null +++ b/content/docs/java2go/module-13-deployment.zh-cn.mdx @@ -0,0 +1,808 @@ +--- +title: "模块 13:构建和部署" +--- + +本模块介绍 Go 应用程序的构建和部署,比较 Go 的简单部署模型与 Java 更复杂的部署策略。 + +## 部署哲学:Go vs Java + +**Java 部署:** +- 需要在目标机器上安装 JVM +- 包含字节码的 JAR/WAR 文件 +- 应用服务器(Tomcat、Jetty、WebSphere) +- 重的启动时间和内存占用 +- 复杂的依赖管理(Maven/Gradle) + +**Go 部署:** +- 单个静态二进制可执行文件 +- 无需运行时依赖 +- 支持交叉编译 +- 快速启动和最小内存占用 +- 简单的依赖管理(go.mod) + + +```java !! java +// Java: 使用 Maven/Gradle 构建 +// pom.xml +/* + + 4.0.0 + com.example + myapp + 1.0.0 + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + + org.apache.maven.plugins + maven-shade-plugin + 3.2.4 + + + package + shade + + + + + + +*/ + +// 构建命令: +// mvn clean package +// java -jar target/myapp-1.0.0.jar + +public class Main { + public static void main(String[] args) { + System.out.println("Hello, World!"); + } +} +``` + +```go !! go +// Go: 使用 go build 构建 +// go.mod +/* +module myapp + +go 1.21 + +require github.com/gin-gonic/gin v1.9.1 +*/ + +package main + +import "fmt" + +func main() { + fmt.Println("Hello, World!") +} + +// 构建命令: +// go build -o myapp +// ./myapp + +// 交叉编译: +// GOOS=linux GOARCH=amd64 go build -o myapp-linux +// GOOS=windows GOARCH=amd64 go build -o myapp.exe +// GOOS=darwin GOARCH=arm64 go build -o myapp-mac-arm +``` + + +## 交叉编译 + +与 Java 特定平台的构建相比,Go 使交叉编译变得轻而易举。 + + +```java !! java +// Java: 特定平台的考虑 +/* +Java 是"一次编写,到处运行"但需要: +1. 每个目标平台的 JVM +2. 特定于平台的本机库(JNI) +3. 不同的应用服务器 + +使用 GraalVM 构建本机镜像: +*/ +public class NativeBuild { + // 需要安装 GraalVM + // native-image:native-image -jar myapp.jar + + public static void main(String[] args) { + System.out.println("Native image!"); + } +} + +/* +构建过程: +1. 安装 GraalVM +2. 构建本机镜像(需要几分钟) +3. 生成特定于平台的二进制文件 +4. 对某些 Java 功能的支持有限 +*/ +``` + +```go !! go +// Go: 简单的交叉编译 +package main + +import ( + "fmt" + "runtime" +) + +func main() { + fmt.Printf("OS: %s, Arch: %s\n", runtime.GOOS, runtime.GOARCH) + fmt.Println("交叉编译很简单!") +} + +/* +为不同平台构建: +# Linux (amd64) +GOOS=linux GOARCH=amd64 go build -o myapp-linux + +# Windows (amd64) +GOOS=windows GOARCH=amd64 go build -o myapp.exe + +# macOS (Apple Silicon) +GOOS=darwin GOARCH=arm64 go build -o myapp-mac-arm + +# Linux ARM (Raspberry Pi) +GOOS=linux GOARCH=arm64 go build -o myapp-pi + +# 一次为所有平台构建 +GOOS=linux GOARCH=amd64 go build -o dist/linux-amd64/myapp +GOOS=windows GOARCH=amd64 go build -o dist/windows-amd64/myapp.exe +GOOS=darwin GOARCH=amd64 go build -o dist/darwin-amd64/myapp +GOOS=darwin GOARCH=arm64 go build -o dist/darwin-arm64/myapp +*/ +``` + + +## Docker 部署 + + +```dockerfile !! dockerfile +# Java: 多阶段 Dockerfile +FROM maven:3.8-openjdk-17 AS builder + +WORKDIR /app +COPY pom.xml . +COPY src ./src + +RUN mvn clean package -DskipTests + +# 运行时阶段 +FROM openjdk:17-slim + +WORKDIR /app +COPY --from=builder /app/target/myapp.jar app.jar + +# 暴露端口 +EXPOSE 8080 + +# 设置 JVM 选项 +ENV JAVA_OPTS="-Xmx512m -Xms256m" + +ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"] + +# 结果: ~400-500MB 镜像 +# 启动时间: 2-5 秒 +``` + +```dockerfile !! dockerfile +# Go: 多阶段 Dockerfile +FROM golang:1.21-alpine AS builder + +WORKDIR /app + +# 首先下载依赖(更好的层缓存) +COPY go.mod go.sum ./ +RUN go mod download + +COPY . . + +# 构建应用程序 +RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main . + +# 运行时阶段 - 最小基础镜像 +FROM alpine:latest + +WORKDIR /app + +# 复制二进制文件 +COPY --from=builder /app/main . + +# 暴露端口 +EXPOSE 8080 + +# 运行二进制文件 +ENTRYPOINT ["./main"] + +# 结果: ~10-20MB 镜像 +# 启动时间: <100ms +``` + + +## 构建优化 + + +```bash !! bash +# Java: 构建优化 + +## Maven +# 跳过测试以加快构建 +mvn package -DskipTests + +# 并行构建 +mvn -T 4 package + +# 离线模式(使用缓存的依赖) +mvn -o package + +# 特定配置文件的构建 +mvn package -P production + +## Gradle +# 配置构建缓存 +org.gradle.caching=true + +# 并行执行 +org.gradle.parallel=true + +# 配置缓存 +org.gradle.configuration-cache=true +``` + +```go !! go +// Go: 构建优化技术 + +// 1. 构建标签和约束 +//go:build !windows + +package main + +import "fmt" + +func main() { + fmt.Println("未为 Windows 构建") +} + +// 2. 链接时优化 +// go build -ldflags="-s -w" +// -s: 删除符号表 +// -w: 删除 DWARF 调试信息 +// 结果: 二进制文件小 20-30% + +// 3. 特定于平台代码的构建标签 +//go:build linux && amd64 + +package main + +func optimizeForLinuxAMD64() { + // Linux AMD64 特定的优化 +} + +// 4. 修剪路径以获得更小的二进制文件 +// go build -trimpath + +// 5. Upx 压缩(构建后) +// upx --best --lzma myapp +// 可以减小 50-70% 的大小 +``` + + +## 依赖管理 + + +```xml !! xml + + + + + + org.springframework.boot + spring-boot-starter-web + 3.1.0 + + + + + + com.example + some-library + 2.0.0 + + + conflicting.lib + bad-version + + + + + + + + + org.springframework.boot + spring-boot-dependencies + 3.1.0 + pom + import + + + + + +``` + +```go !! go +// Go: go.mod 用于依赖管理 +module myapp + +go 1.21 + +require ( + github.com/gin-gonic/gin v1.9.1 + github.com/go-redis/redis/v8 v8.11.5 + gorm.io/gorm v1.25.4 +) + +// Go 以不同方式解决菱形依赖问题: +// - 最小版本选择(MVS) +// - 无需冲突解决 +// - 所有版本必须兼容 + +/* +命令: +go mod init myapp # 初始化模块 +go mod tidy # 清理依赖 +go get github.com/gin-gonic/gin@latest # 添加依赖 +go get -u ./... # 更新所有依赖 +go mod verify # 验证依赖 +go mod vendor # 将依赖 vendor 化 +*/ +``` + + +## 环境配置 + + +```java !! java +// Java: 配置方法 +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.PropertySource; + +@Configuration +@PropertySource(value = "classpath:application-${environment}.properties") +public class AppConfig { + + @Value("${app.port}") + private int port; + + @Value("${app.database.url}") + private String databaseUrl; + + @Value("${app.api.key}") + private String apiKey; + + // application.properties: + // app.port=8080 + // app.database.url=jdbc:postgresql://localhost:5432/mydb + // app.api.key=${API_KEY} + + // 环境变量: + // export API_KEY="secret-key" + // java -jar app.jar --spring.profiles.active=production +} + +// 或使用外部配置文件: +// java -jar app.jar --spring.config.location=file:/path/to/config/ +``` + +```go !! go +// Go: 配置方法 +package main + +import ( + "fmt" + "os" + "strconv" +) + +// 基于结构体的简单配置 +type Config struct { + Port int + DatabaseURL string + APIKey string +} + +func LoadConfig() *Config { + config := &Config{ + Port: getEnvInt("PORT", 8080), + DatabaseURL: getEnv("DATABASE_URL", "postgres://localhost:5432/mydb"), + APIKey: getEnv("API_KEY", ""), + } + return config +} + +func getEnv(key, defaultValue string) string { + if value := os.Getenv(key); value != "" { + return value + } + return defaultValue +} + +func getEnvInt(key string, defaultValue int) int { + if value := os.Getenv(key); value != "" { + if intVal, err := strconv.Atoi(value); err == nil { + return intVal + } + } + return defaultValue +} + +// 使用 viper 进行高级配置: +/* +import github.com/spf13/viper + +viper.SetConfigName("config") +viper.SetConfigType("yaml") +viper.AddConfigPath(".") +viper.AddConfigPath("/etc/myapp/") +viper.AutomaticEnv() + +viper.SetDefault("port", 8080) +viper.BindEnv("api_key", "API_KEY") + +if err := viper.ReadInConfig(); err != nil { + log.Fatalf("Error reading config: %v", err) +} + +port := viper.GetInt("port") +apiKey := viper.GetString("api_key") +*/ +``` + + +## 云平台部署 + + +```yaml !! yaml +# Java: Kubernetes 部署 +apiVersion: apps/v1 +kind: Deployment +metadata: + name: java-app +spec: + replicas: 3 + selector: + matchLabels: + app: java-app + template: + metadata: + labels: + app: java-app + spec: + containers: + - name: app + image: myregistry/java-app:latest + ports: + - containerPort: 8080 + env: + - name: JAVA_OPTS + value: "-Xmx512m -Xms256m" + - name: SPRING_PROFILES_ACTIVE + value: "production" + resources: + requests: + memory: "512Mi" + cpu: "250m" + limits: + memory: "1Gi" + cpu: "500m" + livenessProbe: + httpGet: + path: /actuator/health + port: 8080 + initialDelaySeconds: 60 + periodSeconds: 10 + readinessProbe: + httpGet: + path: /actuator/health + port: 8080 + initialDelaySeconds: 30 + periodSeconds: 5 +``` + +```yaml !! yaml +# Go: Kubernetes 部署 +apiVersion: apps/v1 +kind: Deployment +metadata: + name: go-app +spec: + replicas: 3 + selector: + matchLabels: + app: go-app + template: + metadata: + labels: + app: go-app + spec: + containers: + - name: app + image: myregistry/go-app:latest + ports: + - containerPort: 8080 + env: + - name: PORT + value: "8080" + - name: DATABASE_URL + valueFrom: + secretKeyRef: + name: db-secret + key: url + resources: + requests: + memory: "64Mi" + cpu: "50m" + limits: + memory: "128Mi" + cpu: "100m" + livenessProbe: + httpGet: + path: /health + port: 8080 + initialDelaySeconds: 5 + periodSeconds: 10 + readinessProbe: + httpGet: + path: /health + port: 8080 + initialDelaySeconds: 2 + periodSeconds: 5 +``` + + +## CI/CD 管道 + + +```yaml !! yaml +# Java: Maven 的 GitHub Actions +name: Java CI + +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + + - name: Cache Maven packages + uses: actions/cache@v3 + with: + path: ~/.m2 + key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} + + - name: Build with Maven + run: mvn -B package --file pom.xml + + - name: Run tests + run: mvn test + + - name: Build Docker image + run: docker build -t myapp:${{ github.sha }} . +``` + +```yaml !! yaml +# Go: GitHub Actions +name: Go CI + +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: '1.21' + + - name: Cache Go modules + uses: actions/cache@v3 + with: + path: | + ~/.cache/go-build + ~/go/pkg/mod + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + + - name: Download dependencies + run: go mod download + + - name: Run go vet + run: go vet ./... + + - name: Run tests + run: go test -v -race -coverprofile=coverage.txt ./... + + - name: Build + run: go build -v ./... + + - name: Build Docker image + run: docker build -t myapp:${{ github.sha }} . +``` + + +## 零停机部署 + + +```java !! java +// Java: 零停机部署考虑 +/* +1. 使用 Kubernetes 进行滚动更新 +2. 蓝绿部署 +3. 金丝雀部署 +4. 负载均衡器配置 + +示例:Spring Boot 优雅关闭 +*/ +@Component +public class ShutdownConfig { + + @PreDestroy + public void onShutdown() { + // 优雅地关闭连接 + // 完成进行中的请求 + // 释放资源 + } +} + +// application.properties: +server.shutdown=graceful +spring.lifecycle.timeout-per-shutdown-phase=30s + +/* +挑战: +- JVM 预热时间长 +- 内存密集 +- 复杂的类加载 +- 会话状态管理 +*/ +``` + +```go !! go +// Go: 零停机部署 +package main + +import ( + "context" + "fmt" + "net/http" + "os" + "os/signal" + "syscall" + "time" +) + +func main() { + server := &http.Server{ + Addr: ":8080", + Handler: newHandler(), + } + + // 在 goroutine 中启动服务器 + go func() { + fmt.Println("Server starting on :8080") + if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { + fmt.Printf("Server error: %v\n", err) + } + }() + + // 等待中断信号 + quit := make(chan os.Signal, 1) + signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) + <-quit + + fmt.Println("Shutting down server...") + + // 带超时的优雅关闭 + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + if err := server.Shutdown(ctx); err != nil { + fmt.Printf("Server shutdown error: %v\n", err) + } + + fmt.Println("Server stopped") +} + +func newHandler() http.Handler { + mux := http.NewServeMux() + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintln(w, "Hello, World!") + }) + mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintln(w, "OK") + }) + return mux +} + +/* +优势: +- 即时启动(<100ms) +- 低内存占用 +- 易于优雅关闭 +- 无需管理会话状态 +- 简单的进程管理 +*/ +``` + + +## 生产检查清单 + +### Java 生产检查清单: +- [ ] JVM 堆大小已配置 +- [ ] GC 策略已选择 +- [ ] 应用服务器已调优 +- [ ] 连接池大小已设置 +- [ ] 监控已启用(JMX、Micrometer) +- [ ] 日志已配置 +- [ ] 安全加固 +- [ ] 数据库连接测试 +- [ ] 已执行负载测试 +- [ ] 备份和恢复计划 + +### Go 生产检查清单: +- [ ] 二进制文件已剥离和优化 +- [ ] 错误处理和日志 +- [ ] 健康检查端点 +- [ ] 优雅关闭已实现 +- [ ] 内存性能测试 +- [ ] CPU 性能测试 +- [ ] 速率限制已配置 +- [ ] 安全头已设置 +- [ ] 数据库连接池 +- [ ] 指标和跟踪已启用 + +--- + +### 练习问题: +1. 为什么 Go 产生的 Docker 镜像比 Java 小? +2. Go 的交叉编译比 Java 的"一次编写,到处运行"有什么优势? +3. Go 的最小版本选择与 Maven 的依赖解决有什么不同? +4. 为什么零停机部署在 Go 中更容易? + +### 项目想法: +- 为 Go 微服务创建多阶段 Dockerfile +- 为 Go 应用程序设置 CI/CD 管道 +- 为 Go 服务实现蓝绿部署 +- 构建部署自动化工具 + +### 下一步: +- 学习 Go 的构建工具和生态系统 +- 探索 Go 的第三方库和框架 +- 了解 Go 中的数据库集成 +- 构建生产就绪的应用程序 diff --git a/content/docs/java2go/module-13-deployment.zh-tw.mdx b/content/docs/java2go/module-13-deployment.zh-tw.mdx new file mode 100644 index 0000000..2b02448 --- /dev/null +++ b/content/docs/java2go/module-13-deployment.zh-tw.mdx @@ -0,0 +1,808 @@ +--- +title: "模組 13:建構和部署" +--- + +本模組介紹 Go 應用程式的建構和部署,比較 Go 的簡單部署模型與 Java 更複雜的部署策略。 + +## 部署哲學:Go vs Java + +**Java 部署:** +- 需要在目標機器上安裝 JVM +- 包含位元組碼的 JAR/WAR 檔案 +- 應用程式伺服器(Tomcat、Jetty、WebSphere) +- 重的啟動時間和記憶體佔用 +- 複雜的依賴管理(Maven/Gradle) + +**Go 部署:** +- 單個靜態二進制可執行檔 +- 無需執行時依賴 +- 支援交叉編譯 +- 快速啟動和最小記憶體佔用 +- 簡單的依賴管理(go.mod) + + +```java !! java +// Java: 使用 Maven/Gradle 建構 +// pom.xml +/* + + 4.0.0 + com.example + myapp + 1.0.0 + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + + org.apache.maven.plugins + maven-shade-plugin + 3.2.4 + + + package + shade + + + + + + +*/ + +// 建構指令: +// mvn clean package +// java -jar target/myapp-1.0.0.jar + +public class Main { + public static void main(String[] args) { + System.out.println("Hello, World!"); + } +} +``` + +```go !! go +// Go: 使用 go build 建構 +// go.mod +/* +module myapp + +go 1.21 + +require github.com/gin-gonic/gin v1.9.1 +*/ + +package main + +import "fmt" + +func main() { + fmt.Println("Hello, World!") +} + +// 建構指令: +// go build -o myapp +// ./myapp + +// 交叉編譯: +// GOOS=linux GOARCH=amd64 go build -o myapp-linux +// GOOS=windows GOARCH=amd64 go build -o myapp.exe +// GOOS=darwin GOARCH=arm64 go build -o myapp-mac-arm +``` + + +## 交叉編譯 + +與 Java 特定平台的建構相比,Go 使交叉編譯變得輕而易舉。 + + +```java !! java +// Java: 特定平台的考慮 +/* +Java 是「一次編寫,到處執行」但需要: +1. 每個目標平台的 JVM +2. 特定於平台的原生庫(JNI) +3. 不同的應用程式伺服器 + +使用 GraalVM 建構原生映像: +*/ +public class NativeBuild { + // 需要安裝 GraalVM + // native-image:native-image -jar myapp.jar + + public static void main(String[] args) { + System.out.println("Native image!"); + } +} + +/* +建構過程: +1. 安裝 GraalVM +2. 建構原生映像(需要幾分鐘) +3. 產生特定於平台的二進制檔 +4. 對某些 Java 功能的支援有限 +*/ +``` + +```go !! go +// Go: 簡單的交叉編譯 +package main + +import ( + "fmt" + "runtime" +) + +func main() { + fmt.Printf("OS: %s, Arch: %s\n", runtime.GOOS, runtime.GOARCH) + fmt.Println("交叉編譯很簡單!") +} + +/* +為不同平台建構: +# Linux (amd64) +GOOS=linux GOARCH=amd64 go build -o myapp-linux + +# Windows (amd64) +GOOS=windows GOARCH=amd64 go build -o myapp.exe + +# macOS (Apple Silicon) +GOOS=darwin GOARCH=arm64 go build -o myapp-mac-arm + +# Linux ARM (Raspberry Pi) +GOOS=linux GOARCH=arm64 go build -o myapp-pi + +# 一次為所有平台建構 +GOOS=linux GOARCH=amd64 go build -o dist/linux-amd64/myapp +GOOS=windows GOARCH=amd64 go build -o dist/windows-amd64/myapp.exe +GOOS=darwin GOARCH=amd64 go build -o dist/darwin-amd64/myapp +GOOS=darwin GOARCH=arm64 go build -o dist/darwin-arm64/myapp +*/ +``` + + +## Docker 部署 + + +```dockerfile !! dockerfile +# Java: 多階段 Dockerfile +FROM maven:3.8-openjdk-17 AS builder + +WORKDIR /app +COPY pom.xml . +COPY src ./src + +RUN mvn clean package -DskipTests + +# 執行時階段 +FROM openjdk:17-slim + +WORKDIR /app +COPY --from=builder /app/target/myapp.jar app.jar + +# 暴露連接埠 +EXPOSE 8080 + +# 設定 JVM 選項 +ENV JAVA_OPTS="-Xmx512m -Xms256m" + +ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"] + +# 結果: ~400-500MB 映像 +# 啟動時間: 2-5 秒 +``` + +```dockerfile !! dockerfile +# Go: 多階段 Dockerfile +FROM golang:1.21-alpine AS builder + +WORKDIR /app + +# 首先下載依賴(更好的層快取) +COPY go.mod go.sum ./ +RUN go mod download + +COPY . . + +# 建構應用程式 +RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main . + +# 執行時階段 - 最小基礎映像 +FROM alpine:latest + +WORKDIR /app + +# 複製二進制檔 +COPY --from=builder /app/main . + +# 暴露連接埠 +EXPOSE 8080 + +# 執行二進制檔 +ENTRYPOINT ["./main"] + +# 結果: ~10-20MB 映像 +# 啟動時間: <100ms +``` + + +## 建構最佳化 + + +```bash !! bash +# Java: 建構最佳化 + +## Maven +# 跳過測試以加快建構 +mvn package -DskipTests + +# 平行建構 +mvn -T 4 package + +# 離線模式(使用快取的依賴) +mvn -o package + +# 特定設定檔的建構 +mvn package -P production + +## Gradle +# 設定建構快取 +org.gradle.caching=true + +# 平行執行 +org.gradle.parallel=true + +# 設定快取 +org.gradle.configuration-cache=true +``` + +```go !! go +// Go: 建構最佳化技術 + +// 1. 建構標籤和約束 +//go:build !windows + +package main + +import "fmt" + +func main() { + fmt.Println("未為 Windows 建構") +} + +// 2. 鏈結時最佳化 +// go build -ldflags="-s -w" +// -s: 刪除符號表 +// -w: 刪除 DWARF 除錯資訊 +// 結果: 二進制檔小 20-30% + +// 3. 特定於平台程式碼的建構標籤 +//go:build linux && amd64 + +package main + +func optimizeForLinuxAMD64() { + // Linux AMD64 特定的最佳化 +} + +// 4. 修剪路徑以獲得更小的二進制檔 +// go build -trimpath + +// 5. Upx 壓縮(建構後) +// upx --best --lzma myapp +// 可以減小 50-70% 的大小 +``` + + +## 依賴管理 + + +```xml !! xml + + + + + + org.springframework.boot + spring-boot-starter-web + 3.1.0 + + + + + + com.example + some-library + 2.0.0 + + + conflicting.lib + bad-version + + + + + + + + + org.springframework.boot + spring-boot-dependencies + 3.1.0 + pom + import + + + + + +``` + +```go !! go +// Go: go.mod 用於依賴管理 +module myapp + +go 1.21 + +require ( + github.com/gin-gonic/gin v1.9.1 + github.com/go-redis/redis/v8 v8.11.5 + gorm.io/gorm v1.25.4 +) + +// Go 以不同方式解決菱形依賴問題: +// - 最小版本選擇(MVS) +// - 無需衝突解決 +// - 所有版本必須相容 + +/* +指令: +go mod init myapp # 初始化模組 +go mod tidy # 清理依賴 +go get github.com/gin-gonic/gin@latest # 新增依賴 +go get -u ./... # 更新所有依賴 +go mod verify # 驗證依賴 +go mod vendor # 將依賴 vendor 化 +*/ +``` + + +## 環境配置 + + +```java !! java +// Java: 設定方法 +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.PropertySource; + +@Configuration +@PropertySource(value = "classpath:application-${environment}.properties") +public class AppConfig { + + @Value("${app.port}") + private int port; + + @Value("${app.database.url}") + private String databaseUrl; + + @Value("${app.api.key}") + private String apiKey; + + // application.properties: + // app.port=8080 + // app.database.url=jdbc:postgresql://localhost:5432/mydb + // app.api.key=${API_KEY} + + // 環境變數: + // export API_KEY="secret-key" + // java -jar app.jar --spring.profiles.active=production +} + +// 或使用外部設定檔: +// java -jar app.jar --spring.config.location=file:/path/to/config/ +``` + +```go !! go +// Go: 設定方法 +package main + +import ( + "fmt" + "os" + "strconv" +) + +// 基於結構體的簡單設定 +type Config struct { + Port int + DatabaseURL string + APIKey string +} + +func LoadConfig() *Config { + config := &Config{ + Port: getEnvInt("PORT", 8080), + DatabaseURL: getEnv("DATABASE_URL", "postgres://localhost:5432/mydb"), + APIKey: getEnv("API_KEY", ""), + } + return config +} + +func getEnv(key, defaultValue string) string { + if value := os.Getenv(key); value != "" { + return value + } + return defaultValue +} + +func getEnvInt(key string, defaultValue int) int { + if value := os.Getenv(key); value != "" { + if intVal, err := strconv.Atoi(value); err == nil { + return intVal + } + } + return defaultValue +} + +// 使用 viper 進行進階設定: +/* +import github.com/spf13/viper + +viper.SetConfigName("config") +viper.SetConfigType("yaml") +viper.AddConfigPath(".") +viper.AddConfigPath("/etc/myapp/") +viper.AutomaticEnv() + +viper.SetDefault("port", 8080) +viper.BindEnv("api_key", "API_KEY") + +if err := viper.ReadInConfig(); err != nil { + log.Fatalf("Error reading config: %v", err) +} + +port := viper.GetInt("port") +apiKey := viper.GetString("api_key") +*/ +``` + + +## 雲端平台部署 + + +```yaml !! yaml +# Java: Kubernetes 部署 +apiVersion: apps/v1 +kind: Deployment +metadata: + name: java-app +spec: + replicas: 3 + selector: + matchLabels: + app: java-app + template: + metadata: + labels: + app: java-app + spec: + containers: + - name: app + image: myregistry/java-app:latest + ports: + - containerPort: 8080 + env: + - name: JAVA_OPTS + value: "-Xmx512m -Xms256m" + - name: SPRING_PROFILES_ACTIVE + value: "production" + resources: + requests: + memory: "512Mi" + cpu: "250m" + limits: + memory: "1Gi" + cpu: "500m" + livenessProbe: + httpGet: + path: /actuator/health + port: 8080 + initialDelaySeconds: 60 + periodSeconds: 10 + readinessProbe: + httpGet: + path: /actuator/health + port: 8080 + initialDelaySeconds: 30 + periodSeconds: 5 +``` + +```yaml !! yaml +# Go: Kubernetes 部署 +apiVersion: apps/v1 +kind: Deployment +metadata: + name: go-app +spec: + replicas: 3 + selector: + matchLabels: + app: go-app + template: + metadata: + labels: + app: go-app + spec: + containers: + - name: app + image: myregistry/go-app:latest + ports: + - containerPort: 8080 + env: + - name: PORT + value: "8080" + - name: DATABASE_URL + valueFrom: + secretKeyRef: + name: db-secret + key: url + resources: + requests: + memory: "64Mi" + cpu: "50m" + limits: + memory: "128Mi" + cpu: "100m" + livenessProbe: + httpGet: + path: /health + port: 8080 + initialDelaySeconds: 5 + periodSeconds: 10 + readinessProbe: + httpGet: + path: /health + port: 8080 + initialDelaySeconds: 2 + periodSeconds: 5 +``` + + +## CI/CD 管線 + + +```yaml !! yaml +# Java: Maven 的 GitHub Actions +name: Java CI + +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + + - name: Cache Maven packages + uses: actions/cache@v3 + with: + path: ~/.m2 + key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} + + - name: Build with Maven + run: mvn -B package --file pom.xml + + - name: Run tests + run: mvn test + + - name: Build Docker image + run: docker build -t myapp:${{ github.sha }} . +``` + +```yaml !! yaml +# Go: GitHub Actions +name: Go CI + +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: '1.21' + + - name: Cache Go modules + uses: actions/cache@v3 + with: + path: | + ~/.cache/go-build + ~/go/pkg/mod + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + + - name: Download dependencies + run: go mod download + + - name: Run go vet + run: go vet ./... + + - name: Run tests + run: go test -v -race -coverprofile=coverage.txt ./... + + - name: Build + run: go build -v ./... + + - name: Build Docker image + run: docker build -t myapp:${{ github.sha }} . +``` + + +## 零停機部署 + + +```java !! java +// Java: 零停機部署考慮 +/* +1. 使用 Kubernetes 進行滾動更新 +2. 藍綠部署 +3. 金絲雀部署 +4. 負載均衡器設定 + +範例:Spring Boot 優雅關閉 +*/ +@Component +public class ShutdownConfig { + + @PreDestroy + public void onShutdown() { + // 優雅地關閉連線 + // 完成進行中的請求 + // 釋放資源 + } +} + +// application.properties: +server.shutdown=graceful +spring.lifecycle.timeout-per-shutdown-phase=30s + +/* +挑戰: +- JVM 預熱時間長 +- 記憶體密集 +- 複雜的類加載 +- 會話狀態管理 +*/ +``` + +```go !! go +// Go: 零停機部署 +package main + +import ( + "context" + "fmt" + "net/http" + "os" + "os/signal" + "syscall" + "time" +) + +func main() { + server := &http.Server{ + Addr: ":8080", + Handler: newHandler(), + } + + // 在 goroutine 中啟動伺服器 + go func() { + fmt.Println("Server starting on :8080") + if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { + fmt.Printf("Server error: %v\n", err) + } + }() + + // 等待中斷訊號 + quit := make(chan os.Signal, 1) + signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) + <-quit + + fmt.Println("Shutting down server...") + + // 帶超時的優雅關閉 + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + if err := server.Shutdown(ctx); err != nil { + fmt.Printf("Server shutdown error: %v\n", err) + } + + fmt.Println("Server stopped") +} + +func newHandler() http.Handler { + mux := http.NewServeMux() + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintln(w, "Hello, World!") + }) + mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintln(w, "OK") + }) + return mux +} + +/* +優勢: +- 即時啟動(<100ms) +- 低記憶體佔用 +- 易於優雅關閉 +- 無需管理會話狀態 +- 簡單的行程管理 +*/ +``` + + +## 生產檢查清單 + +### Java 生產檢查清單: +- [ ] JVM 堆大小已配置 +- [ ] GC 策略已選擇 +- [ ] 應用程式伺服器已調優 +- [ ] 連線池大小已設定 +- [ ] 監控已啟用(JMX、Micrometer) +- [ ] 日誌已配置 +- [ ] 安全加固 +- [ ] 資料庫連線測試 +- [ ] 已執行負載測試 +- [ ] 備份和恢復計劃 + +### Go 生產檢查清單: +- [ ] 二進制檔已剝離和最佳化 +- [ ] 錯誤處理和日誌 +- [ ] 健康檢查端點 +- [ ] 優雅關閉已實作 +- [ ] 記憶體效能測試 +- [ ] CPU 效能測試 +- [ ] 速率限制已配置 +- [ ] 安全頭已設定 +- [ ] 資料庫連線池 +- [ ] 指標和追蹤已啟用 + +--- + +### 練習問題: +1. 為什麼 Go 產生的 Docker 映像比 Java 小? +2. Go 的交叉編譯比 Java 的「一次編寫,到處執行」有什麼優勢? +3. Go 的最小版本選擇與 Maven 的依賴解決有什麼不同? +4. 為什麼零停機部署在 Go 中更容易? + +### 專案想法: +- 為 Go 微服務建立多階段 Dockerfile +- 為 Go 應用程式設定 CI/CD 管線 +- 為 Go 服務實作藍綠部署 +- 建構部署自動化工具 + +### 下一步: +- 學習 Go 的建構工具和生態系統 +- 探索 Go 的第三方庫和框架 +- 了解 Go 中的資料庫整合 +- 建構生產就緒的應用程式 diff --git a/content/docs/java2go/module-14-build-tools.mdx b/content/docs/java2go/module-14-build-tools.mdx new file mode 100644 index 0000000..a8fd688 --- /dev/null +++ b/content/docs/java2go/module-14-build-tools.mdx @@ -0,0 +1,814 @@ +--- +title: "Module 14: Build Tools and Dependency Management" +--- + +This module covers Go's build tools and dependency management, comparing them with Java's Maven and Gradle ecosystems. + +## Go Tools vs Java Build Tools + +**Java Build Tools:** +- Maven: XML-based, convention over configuration +- Gradle: Groovy/Kotlin DSL, flexible and powerful +- Complex dependency resolution with transitive conflicts +- Plugin ecosystem for various tasks +- Build lifecycle management + +**Go Build Tools:** +- `go`: Official command-line toolchain +- Simple, fast, and opinionated +- Minimal version selection (MVS) for dependencies +- Standardized project structure +- Built-in testing, benchmarking, and profiling + + +```bash !! bash +# Java: Maven commands +mvn clean # Clean build artifacts +mvn compile # Compile source code +mvn test # Run tests +mvn package # Create JAR/WAR +mvn install # Install to local repo +mvn deploy # Deploy to remote repo +mvn dependency:tree # Show dependency tree +mvn versions:display-dependency-updates # Check updates + +# Java: Gradle commands +gradle clean # Clean build artifacts +gradle build # Build project +gradle test # Run tests +gradle bootRun # Run Spring Boot app +gradle dependencies # Show dependencies +gradle dependencyUpdates # Check for updates + +# Both support: +# - Multi-module projects +# - Custom plugins +# - Build profiles +# - Continuous integration +``` + +```bash !! bash +# Go: go command +go build # Build executable +go test # Run tests +go run main.go # Build and run +go fmt ./... # Format code +go vet ./... # Run static analysis +go mod init # Initialize module +go mod tidy # Clean dependencies +go get github.com/pkg/pkg # Add dependency +go install github.com/pkg/cmd@latest # Install tool + +# Go tools: +go list -m all # List all dependencies +go mod graph # Show dependency graph +go mod verify # Verify dependencies +go mod why github.com/pkg # Explain why dependency needed +go generate # Generate code +go tool cover # Coverage analysis + +# Advantages: +# - Single toolchain +# - Faster builds +# - Simpler dependency model +# - Built-in to language +``` + + +## Dependency Management + + +```xml !! xml + + + 4.0.0 + com.example + myapp + 1.0.0 + jar + + + 17 + 3.1.0 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.projectlombok + lombok + 1.18.28 + provided + + + + org.junit.jupiter + junit-jupiter + test + + + + + + + org.springframework.boot + spring-boot-dependencies + ${spring.boot.version} + pom + import + + + + +``` + +```go !! go +// Go: go.mod +module github.com/example/myapp + +go 1.21 + +require ( + github.com/gin-gonic/gin v1.9.1 + github.com/go-redis/redis/v8 v8.11.5 + gorm.io/gorm v1.25.4 +) + +require ( + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/jinzhu/inflection v1.0.0 // indirect +) + +// go.sum contains checksums for all dependencies +// Automatically managed by Go tools + +// No version conflicts in Go! +// Minimal Version Selection (MVS) algorithm: +// - Uses the minimum version that satisfies all requirements +// - Predictable and reproducible builds +// - No need for conflict resolution +``` + + +## Project Structure + + +``` +# Java: Maven Standard Layout +myapp/ +├── pom.xml +├── src/ +│ ├── main/ +│ │ ├── java/ +│ │ │ └── com/ +│ │ │ └── example/ +│ │ │ └── myapp/ +│ │ │ ├── Main.java +│ │ │ ├── service/ +│ │ │ └── model/ +│ │ └── resources/ +│ │ ├── application.properties +│ │ └── static/ +│ └── test/ +│ ├── java/ +│ └── resources/ +└── target/ # Build output + +# Features: +# - Strict convention over configuration +# - Separation of concerns +# - Resource management +# - Multiple output types (jar, war) +``` + +``` +# Go: Standard Layout +myapp/ +├── go.mod +├── go.sum +├── main.go +├── api/ +│ └── handler/ +├── service/ +├── model/ +├── repository/ +├── config/ +│ └── config.go +├── go build # Build output (if needed) +├── myapp # Binary output +├── main_test.go # Test files next to source +└── README.md + +# Features: +# - Flexible structure +# - Tests next to source code +# or in separate files with _test.go suffix +# - Simple and straightforward +# - No enforced conventions +``` + + +## Testing + + +```java !! java +// Java: JUnit 5 Test +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +@DisplayName("User Service Tests") +class UserServiceTest { + + private UserRepository userRepository; + private UserService userService; + + @BeforeEach + void setUp() { + userRepository = mock(UserRepository.class); + userService = new UserService(userRepository); + } + + @Test + @DisplayName("Should create user successfully") + void shouldCreateUser() { + // Given + User user = new User("john@example.com"); + when(userRepository.save(any(User.class))) + .thenReturn(user); + + // When + User result = userService.create(user); + + // Then + assertNotNull(result); + assertEquals("john@example.com", result.getEmail()); + verify(userRepository).save(user); + } + + @Test + @DisplayName("Should throw exception for duplicate email") + void shouldThrowForDuplicate() { + // Given + User user = new User("john@example.com"); + when(userRepository.existsByEmail("john@example.com")) + .thenReturn(true); + + // When & Then + assertThrows(DuplicateEmailException.class, + () -> userService.create(user)); + } +} +``` + +```go !! go +// Go: Testing +package main + +import ( + "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +// Mock repository +type MockUserRepository struct { + mock.Mock +} + +func (m *MockUserRepository) Save(user *User) error { + args := m.Called(user) + return args.Error(0) +} + +func (m *MockUserRepository) ExistsByEmail(email string) bool { + args := m.Called(email) + return args.Bool(0) +} + +func TestUserService_Create(t *testing.T) { + tests := []struct { + name string + email string + mockSetup func(*MockUserRepository) + wantErr bool + errType error + }{ + { + name: "successful creation", + email: "john@example.com", + mockSetup: func(m *MockUserRepository) { + m.On("ExistsByEmail", "john@example.com").Return(false) + m.On("Save", mock.Anything).Return(nil) + }, + wantErr: false, + }, + { + name: "duplicate email", + email: "john@example.com", + mockSetup: func(m *MockUserRepository) { + m.On("ExistsByEmail", "john@example.com").Return(true) + }, + wantErr: true, + errType: ErrDuplicateEmail, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockRepo := new(MockUserRepository) + tt.mockSetup(mockRepo) + + service := NewUserService(mockRepo) + user := &User{Email: tt.email} + + err := service.Create(user) + + if tt.wantErr { + assert.Error(t, err) + if tt.errType != nil { + assert.ErrorIs(t, err, tt.errType) + } + } else { + assert.NoError(t, err) + } + + mockRepo.AssertExpectations(t) + }) + } +} +``` + + +## Code Generation + + +```java !! java +// Java: Annotation Processing +import javax.annotation.processing.*; +import javax.lang.model.element.*; +import javax.tools.*; +import lombok.Data; +import lombok.Builder; + +// Using Lombok for code generation +@Data // Generate getters/setters +@Builder // Generate builder +@AllArgsConstructor // Generate constructor +@NoArgsConstructor // Generate no-args constructor +public class User { + private Long id; + private String email; + private String name; + + // Lombok generates: + // - Getters and setters + // - equals() and hashCode() + // - toString() + // - Builder pattern +} + +// Or use annotation processors for custom generation: +@Generated("MyProcessor") +public class GeneratedClass { + // Custom generated code +} +``` + +```go !! go +// Go: go generate +//go:generate go run github.com/google/golang-api/cmd/goapi + +package main + +// Generate string methods +//go:generate stringer -type=Role + +type Role int + +const ( + RoleGuest Role = iota + RoleUser + RoleAdmin +) + +//go:generate mockgen -destination=mocks/mock_repository.go -package=mocks github.com/example/myapp Repository + +type Repository interface { + GetUser(id int) (*User, error) +} + +// Usage: +// 1. Add //go:generate comments +// 2. Run: go generate ./... +// 3. Generated files appear in source tree + +// Common generators: +// - stringer: Generate String() methods +// - mockgen: Generate mocks (from gomock) +// - go run: Custom generators +// - protoc: Protocol buffer code +``` + + +## Build Tags and Constraints + + +```java !! java +// Java: Platform-specific code +public class PlatformUtils { + public static String getPlatformInfo() { + String os = System.getProperty("os.name").toLowerCase(); + String arch = System.getProperty("os.arch").toLowerCase(); + + if (os.contains("win")) { + return "Windows"; + } else if (os.contains("mac")) { + return "macOS"; + } else if (os.contains("nix") || os.contains("nux")) { + return "Linux"; + } + + return "Unknown"; + } + + // Or use conditional compilation + // with different source sets +} + +// Or use native libraries with JNI: +// - Different .so/.dll files per platform +// - Complex setup +// - Runtime loading issues +``` + +```go !! go +// Go: Build tags for conditional compilation + +//go:build !windows + +package main + +import "fmt" + +func getPlatformFeatures() []string { + return []string{"Symlinks", "Unix sockets", "Signals"} +} + +// File: platform_linux.go +//go:build linux + +package main + +func getOSSpecificInfo() string { + return "Linux-specific features" +} + +// File: platform_windows.go +//go:build windows + +package main + +func getOSSpecificInfo() string { + return "Windows-specific features" +} + +// Build constraints support: +// - OS: linux, darwin, windows +// - Architecture: amd64, arm64 +// - Compiler: gc, gccgo +// - Custom tags: !cgo, integration + +// Example: ignore integration tests in normal build +//go:build integration + +package main_test + +func TestIntegration(t *testing.T) { + // Integration tests only +} +``` + + +## Continuous Integration Configuration + + +```yaml !! yaml +# Java: .github/workflows/ci.yml +name: Java CI + +on: + push: + branches: [ main, develop ] + pull_request: + branches: [ main ] + +jobs: + build: + runs-on: ubuntu-latest + + strategy: + matrix: + java: ['17', '21'] + + steps: + - uses: actions/checkout@v3 + + - name: Set up JDK ${{ matrix.java }} + uses: actions/setup-java@v3 + with: + java-version: ${{ matrix.java }} + cache: 'maven' + + - name: Build with Maven + run: mvn -B verify + + - name: Upload coverage + uses: codecov/codecov-action@v3 + with: + file: target/site/jacoco/jacoco.xml +``` + +```yaml !! yaml +# Go: .github/workflows/ci.yml +name: Go CI + +on: + push: + branches: [ main, develop ] + pull_request: + branches: [ main ] + +jobs: + test: + runs-on: ${{ matrix.os }} + + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + go: ['1.20', '1.21'] + + steps: + - uses: actions/checkout@v3 + + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: ${{ matrix.go }} + cache: true + + - name: Download dependencies + run: go mod download + + - name: Verify dependencies + run: go mod verify + + - name: Run go vet + run: go vet ./... + + - name: Run tests + run: go test -v -race -coverprofile=coverage.txt -covermode=atomic ./... + + - name: Upload coverage + uses: codecov/codecov-action@v3 + with: + file: ./coverage.txt + + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: golangci-lint + uses: golangci/golangci-lint-action@v3 + with: + version: latest +``` + + +## Popular Go Tools + + +```bash !! bash +# Java: Common tools + +# Code quality +checkstyle # Code style checking +PMD # Code analysis +SpotBugs # Bug detection +SonarQube # Code quality platform + +# Build tools +Maven # Build automation +Gradle # Flexible build tool +JBang # Run Java code directly + +# Testing +JUnit # Unit testing +TestNG # Testing framework +Mockito # Mocking +RestAssured # REST testing + +# Profiling and monitoring +JProfiler # Java profiler +VisualVM # Monitoring and profiling +JConsole # JVM monitoring +``` + +```bash !! bash +# Go: Essential tools + +# Install tools +go install github.com/...@latest + +# Linting and formatting +golangci-lint # Comprehensive linter (supersedes many others) +go fmt # Built-in formatter +go vet # Static analysis +staticcheck # Advanced static analysis + +# Testing +go test # Built-in testing +gotestsum # Test runner +gomock # Mock generation +testify # Assertion library + +# Development tools +go generate # Code generation +impl # Interface implementation +gofumpt # Stricter formatter +errcheck # Check error handling + +# Code quality +revive # Linter +ineffassign # Detect ineffective assignments +unconvert # Detect unnecessary conversions +goconst # Find repeated strings + +# Documentation +godoc # Documentation +pkgsite # Package documentation +``` + + +## Makefiles for Go Projects + + +```makefile !! makefile +# Java: Makefile (less common) +.PHONY: clean build test run + +clean: + mvn clean + +build: + mvn package + +test: + mvn test + +run: + mvn spring-boot:run + +# Most use Maven/Gradle directly +# or IDE integration +``` + +```makefile !! makefile +# Go: Makefile (very common) +.PHONY: build test clean run lint fmt + +# Variables +APP_NAME=myapp +GO=go +GOFLAGS=-v + +build: + $(GO) build $(GOFLAGS) -o $(APP_NAME) + +test: + $(GO) test -v -race -cover ./... + +test-coverage: + $(GO) test -coverprofile=coverage.out ./... + $(GO) tool cover -html=coverage.out -o coverage.html + +lint: + golangci-lint run + +fmt: + $(GO) fmt ./... + gofumpt -l -w . + +run: + $(GO) run main.go + +clean: + $(GO) clean + rm -f $(APP_NAME) + rm -f coverage.out coverage.html + +deps: + $(GO) mod download + $(GO) mod tidy + +generate: + $(GO) generate ./... + +# Multi-platform build +build-all: + GOOS=linux GOARCH=amd64 $(GO) build -o dist/$(APP_NAME)-linux-amd64 + GOOS=darwin GOARCH=amd64 $(GO) build -o dist/$(APP_NAME)-darwin-amd64 + GOOS=darwin GOARCH=arm64 $(GO) build -o dist/$(APP_NAME)-darwin-arm64 + GOOS=windows GOARCH=amd64 $(GO) build -o dist/$(APP_NAME)-windows-amd64.exe + +# Development +dev: + air # Live reload tool + +# Docker +docker-build: + docker build -t $(APP_NAME) . + +docker-run: + docker run -p 8080:8080 $(APP_NAME) +``` + + +## Workspace Management (Go 1.18+) + + +```bash !! bash +# Java: Multi-module projects (Maven) +# pom.xml (parent) + + + common + service-a + service-b + + + +# Each module has its own pom.xml +# Shared dependencies managed in parent +``` + +```bash !! bash +# Go: Workspaces (Go 1.18+) + +# Create workspace +go work init + +# Add modules to workspace +go work use ./common +go work use ./service-a +go work use ./service-b + +# go.work file: +go 1.21 + +use ( + ./common + ./service-a + ./service-b +) + +# Benefits: +# - Work on multiple modules simultaneously +# - Replace directives for local development +# - No need to publish modules to test changes +# - All modules share same dependency cache + +# Commands work across workspace: +go build ./... +go test ./... +go mod tidy +``` + + +--- + +### Practice Questions: +1. Why is Go's dependency management simpler than Java's? +2. What are the advantages of built-in testing in Go? +3. How do build tags enable platform-specific code in Go? +4. Why does Go typically not require Makefiles while Java projects often do? + +### Project Ideas: +- Create a custom Go generator for boilerplate code +- Set up a comprehensive CI/CD pipeline for a Go project +- Build a tool that analyzes Go dependency graphs +- Implement a workspace-based multi-module Go project + +### Next Steps: +- Explore Go's ecosystem and popular libraries +- Learn database integration in Go +- Understand microservices architecture +- Build production-ready applications diff --git a/content/docs/java2go/module-14-build-tools.zh-cn.mdx b/content/docs/java2go/module-14-build-tools.zh-cn.mdx new file mode 100644 index 0000000..03b504d --- /dev/null +++ b/content/docs/java2go/module-14-build-tools.zh-cn.mdx @@ -0,0 +1,518 @@ +--- +title: "模块 14:构建工具和依赖管理" +--- + +本模块介绍 Go 的构建工具和依赖管理,与 Java 的 Maven 和 Gradle 生态系统进行比较。 + +## Go 工具 vs Java 构建工具 + +**Java 构建工具:** +- Maven:基于 XML,约定优于配置 +- Gradle:Groovy/Kotlin DSL,灵活强大 +- 复杂的依赖解析和传递冲突 +- 各种任务的插件生态系统 +- 构建生命周期管理 + +**Go 构建工具:** +- `go`:官方命令行工具链 +- 简单、快速、有主见 +- 最小版本选择(MVS)用于依赖 +- 标准化的项目结构 +- 内置测试、基准测试和性能分析 + + +```bash !! bash +# Java: Maven 命令 +mvn clean # 清理构建产物 +mvn compile # 编译源代码 +mvn test # 运行测试 +mvn package # 创建 JAR/WAR +mvn install # 安装到本地仓库 +mvn deploy # 部署到远程仓库 +mvn dependency:tree # 显示依赖树 +mvn versions:display-dependency-updates # 检查更新 + +# Java: Gradle 命令 +gradle clean # 清理构建产物 +gradle build # 构建项目 +gradle test # 运行测试 +gradle bootRun # 运行 Spring Boot 应用 +gradle dependencies # 显示依赖 +gradle dependencyUpdates # 检查更新 + +# 两者都支持: +# - 多模块项目 +# - 自定义插件 +# - 构建配置文件 +# - 持续集成 +``` + +```bash !! bash +# Go: go 命令 +go build # 构建可执行文件 +go test # 运行测试 +go run main.go # 构建并运行 +go fmt ./... # 格式化代码 +go vet ./... # 运行静态分析 +go mod init # 初始化模块 +go mod tidy # 清理依赖 +go get github.com/pkg/pkg # 添加依赖 +go install github.com/pkg/cmd@latest # 安装工具 + +# Go 工具: +go list -m all # 列出所有依赖 +go mod graph # 显示依赖图 +go mod verify # 验证依赖 +go mod why github.com/pkg # 解释为什么需要依赖 +go generate # 生成代码 +go tool cover # 覆盖率分析 + +# 优势: +# - 单一工具链 +# - 更快的构建 +# - 更简单的依赖模型 +# - 内置于语言 +``` + + +## 依赖管理 + + +```xml !! xml + + + 4.0.0 + com.example + myapp + 1.0.0 + jar + + + 17 + 3.1.0 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.projectlombok + lombok + 1.18.28 + provided + + + + org.junit.jupiter + junit-jupiter + test + + + + + + + org.springframework.boot + spring-boot-dependencies + ${spring.boot.version} + pom + import + + + + +``` + +```go !! go +// Go: go.mod +module github.com/example/myapp + +go 1.21 + +require ( + github.com/gin-gonic/gin v1.9.1 + github.com/go-redis/redis/v8 v8.11.5 + gorm.io/gorm v1.25.4 +) + +require ( + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/jinzhu/inflection v1.0.0 // indirect +) + +// go.sum 包含所有依赖的校验和 +// 由 Go 工具自动管理 + +// Go 中没有版本冲突! +// 最小版本选择(MVS)算法: +// - 使用满足所有要求的最小版本 +// - 可预测和可复现的构建 +// - 无需冲突解决 +``` + + +## 项目结构 + + +``` +# Java: Maven 标准布局 +myapp/ +├── pom.xml +├── src/ +│ ├── main/ +│ │ ├── java/ +│ │ │ └── com/ +│ │ │ └── example/ +│ │ │ └── myapp/ +│ │ │ ├── Main.java +│ │ │ ├── service/ +│ │ │ └── model/ +│ │ └── resources/ +│ │ ├── application.properties +│ │ └── static/ +│ └── test/ +│ ├── java/ +│ └── resources/ +└── target/ # 构建输出 + +# 特性: +# - 严格的约定优于配置 +# - 关注点分离 +# - 资源管理 +# - 多种输出类型(jar、war) +``` + +``` +# Go: 标准布局 +myapp/ +├── go.mod +├── go.sum +├── main.go +├── api/ +│ └── handler/ +├── service/ +├── model/ +├── repository/ +├── config/ +│ └── config.go +├── go build # 构建输出(如果需要) +├── myapp # 二进制输出 +├── main_test.go # 测试文件在源码旁边 +└── README.md + +# 特性: +# - 灵活的结构 +# - 测试文件在源码旁边 +# 或在单独的 _test.go 文件中 +# - 简单直接 +# - 无强制约定 +``` + + +## 测试 + + +```java !! java +// Java: JUnit 5 测试 +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +@DisplayName("User Service Tests") +class UserServiceTest { + + private UserRepository userRepository; + private UserService userService; + + @BeforeEach + void setUp() { + userRepository = mock(UserRepository.class); + userService = new UserService(userRepository); + } + + @Test + @DisplayName("Should create user successfully") + void shouldCreateUser() { + // Given + User user = new User("john@example.com"); + when(userRepository.save(any(User.class))) + .thenReturn(user); + + // When + User result = userService.create(user); + + // Then + assertNotNull(result); + assertEquals("john@example.com", result.getEmail()); + verify(userRepository).save(user); + } + + @Test + @DisplayName("Should throw exception for duplicate email") + void shouldThrowForDuplicate() { + // Given + User user = new User("john@example.com"); + when(userRepository.existsByEmail("john@example.com")) + .thenReturn(true); + + // When & Then + assertThrows(DuplicateEmailException.class, + () -> userService.create(user)); + } +} +``` + +```go !! go +// Go: 测试 +package main + +import ( + "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +// Mock 仓储 +type MockUserRepository struct { + mock.Mock +} + +func (m *MockUserRepository) Save(user *User) error { + args := m.Called(user) + return args.Error(0) +} + +func (m *MockUserRepository) ExistsByEmail(email string) bool { + args := m.Called(email) + return args.Bool(0) +} + +func TestUserService_Create(t *testing.T) { + tests := []struct { + name string + email string + mockSetup func(*MockUserRepository) + wantErr bool + errType error + }{ + { + name: "successful creation", + email: "john@example.com", + mockSetup: func(m *MockUserRepository) { + m.On("ExistsByEmail", "john@example.com").Return(false) + m.On("Save", mock.Anything).Return(nil) + }, + wantErr: false, + }, + { + name: "duplicate email", + email: "john@example.com", + mockSetup: func(m *MockUserRepository) { + m.On("ExistsByEmail", "john@example.com").Return(true) + }, + wantErr: true, + errType: ErrDuplicateEmail, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockRepo := new(MockUserRepository) + tt.mockSetup(mockRepo) + + service := NewUserService(mockRepo) + user := &User{Email: tt.email} + + err := service.Create(user) + + if tt.wantErr { + assert.Error(t, err) + if tt.errType != nil { + assert.ErrorIs(t, err, tt.errType) + } + } else { + assert.NoError(t, err) + } + + mockRepo.AssertExpectations(t) + }) + } +} +``` + + +## 代码生成 + + +```java !! java +// Java: 注解处理 +import javax.annotation.processing.*; +import javax.lang.model.element.*; +import javax.tools.*; +import lombok.Data; +import lombok.Builder; + +// 使用 Lombok 进行代码生成 +@Data // 生成 getter/setter +@Builder // 生成构建器 +@AllArgsConstructor // 生成构造函数 +@NoArgsConstructor // 生成无参构造函数 +public class User { + private Long id; + private String email; + private String name; + + // Lombok 生成: + // - Getter 和 setter + // - equals() 和 hashCode() + // - toString() + // - 构建器模式 +} + +// 或使用注解处理器进行自定义生成: +@Generated("MyProcessor") +public class GeneratedClass { + // 自定义生成的代码 +} +``` + +```go !! go +// Go: go generate +//go:generate go run github.com/google/golang-api/cmd/goapi + +package main + +// 生成字符串方法 +//go:generate stringer -type=Role + +type Role int + +const ( + RoleGuest Role = iota + RoleUser + RoleAdmin +) + +//go:generate mockgen -destination=mocks/mock_repository.go -package=mocks github.com/example/myapp Repository + +type Repository interface { + GetUser(id int) (*User, error) +} + +// 用法: +// 1. 添加 //go:generate 注释 +// 2. 运行:go generate ./... +// 3. 生成的文件出现在源码树中 + +// 常用生成器: +// - stringer:生成 String() 方法 +// - mockgen:生成 mock(来自 gomock) +// - go run:自定义生成器 +// - protoc:Protocol buffer 代码 +``` + + +## 构建标签和约束 + + +```java !! java +// Java: 特定于平台的代码 +public class PlatformUtils { + public static String getPlatformInfo() { + String os = System.getProperty("os.name").toLowerCase(); + String arch = System.getProperty("os.arch").toLowerCase(); + + if (os.contains("win")) { + return "Windows"; + } else if (os.contains("mac")) { + return "macOS"; + } else if (os.contains("nix") || os.contains("nux")) { + return "Linux"; + } + + return "Unknown"; + } + + // 或使用条件编译 + // 配合不同的源码集 +} + +// 或使用 JNI 的原生库: +// - 每个平台不同的 .so/.dll 文件 +// - 复杂的设置 +// - 运行时加载问题 +``` + +```go !! go +// Go: 用于条件编译的构建标签 + +//go:build !windows + +package main + +import "fmt" + +func getPlatformFeatures() []string { + return []string{"符号链接", "Unix 套接字", "信号"} +} + +// 文件:platform_linux.go +//go:build linux + +package main + +func getOSSpecificInfo() string { + return "Linux 特定功能" +} + +// 文件:platform_windows.go +//go:build windows + +package main + +func getOSSpecificInfo() string { + return "Windows 特定功能" +} + +// 构建约束支持: +// - 操作系统:linux、darwin、windows +// - 架构:amd64、arm64 +// - 编译器:gc、gccgo +// - 自定义标签:!cgo、integration + +// 示例:在正常构建中忽略集成测试 +//go:build integration + +package main_test + +func TestIntegration(t *testing.T) { + // 仅集成测试 +} +``` + + +--- + +### 练习问题: +1. 为什么 Go 的依赖管理比 Java 更简单? +2. Go 中内置测试的优势是什么? +3. 构建标签如何在 Go 中启用特定于平台的代码? +4. 为什么 Go 通常不需要 Makefile 而 Java 项目经常需要? + +### 项目想法: +- 为样板代码创建自定义 Go 生成器 +- 为 Go 项目设置全面的 CI/CD 管道 +- 构建分析 Go 依赖图的工具 +- 实现基于工作区的多模块 Go 项目 + +### 下一步: +- 探索 Go 的生态系统和流行库 +- 学习 Go 中的数据库集成 +- 了解微服务架构 +- 构建生产就绪的应用程序 diff --git a/content/docs/java2go/module-14-build-tools.zh-tw.mdx b/content/docs/java2go/module-14-build-tools.zh-tw.mdx new file mode 100644 index 0000000..7d39c2d --- /dev/null +++ b/content/docs/java2go/module-14-build-tools.zh-tw.mdx @@ -0,0 +1,176 @@ +--- +title: "模組 14:建構工具和依賴管理" +--- + +本模組介紹 Go 的建構工具和依賴管理,與 Java 的 Maven 和 Gradle 生態系統進行比較。 + +## Go 工具 vs Java 建構工具 + +**Java 建構工具:** +- Maven:基於 XML,約定優於配置 +- Gradle:Groovy/Kotlin DSL,靈活強大 +- 複雜的依賴解析和傳遞衝突 +- 各種任務的插件生態系統 +- 建構生命週期管理 + +**Go 建構工具:** +- `go`:官方命令列工具鏈 +- 簡單、快速、有主見 +- 最小版本選擇(MVS)用於依賴 +- 標準化的專案結構 +- 內建測試、基準測試和效能分析 + + +```bash !! bash +# Java: Maven 指令 +mvn clean # 清理建構產物 +mvn compile # 編譯原始碼 +mvn test # 執行測試 +mvn package # 建立 JAR/WAR +mvn install # 安裝到本地儲存庫 +mvn deploy # 部署到遠端儲存庫 +mvn dependency:tree # 顯示依賴樹 +mvn versions:display-dependency-updates # 檢查更新 + +# Java: Gradle 指令 +gradle clean # 清理建構產物 +gradle build # 建構專案 +gradle test # 執行測試 +gradle bootRun # 執行 Spring Boot 應用 +gradle dependencies # 顯示依賴 +gradle dependencyUpdates # 檢查更新 + +# 兩者都支援: +# - 多模組專案 +# - 自訂外掛 +# - 建構設定檔 +# - 持續整合 +``` + +```bash !! bash +# Go: go 指令 +go build # 建構可執行檔 +go test # 執行測試 +go run main.go # 建構並執行 +go fmt ./... # 格式化程式碼 +go vet ./... # 執行靜態分析 +go mod init # 初始化模組 +go mod tidy # 清理依賴 +go get github.com/pkg/pkg # 新增依賴 +go install github.com/pkg/cmd@latest # 安裝工具 + +# Go 工具: +go list -m all # 列出所有依賴 +go mod graph # 顯示依賴圖 +go mod verify # 驗證依賴 +go mod why github.com/pkg # 解釋為什麼需要依賴 +go generate # 產生程式碼 +go tool cover # 覆蓋率分析 + +# 優勢: +# - 單一工具鏈 +# - 更快的建構 +# - 更簡單的依賴模型 +# - 內建於語言 +``` + + +## 依賴管理 + + +```xml !! xml + + + 4.0.0 + com.example + myapp + 1.0.0 + jar + + + 17 + 3.1.0 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.projectlombok + lombok + 1.18.28 + provided + + + + org.junit.jupiter + junit-jupiter + test + + + + + + + org.springframework.boot + spring-boot-dependencies + ${spring.boot.version} + pom + import + + + + +``` + +```go !! go +// Go: go.mod +module github.com/example/myapp + +go 1.21 + +require ( + github.com/gin-gonic/gin v1.9.1 + github.com/go-redis/redis/v8 v8.11.5 + gorm.io/gorm v1.25.4 +) + +require ( + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/jinzhu/inflection v1.0.0 // indirect +) + +// go.sum 包含所有依賴的校驗和 +// 由 Go 工具自動管理 + +// Go 中沒有版本衝突! +// 最小版本選擇(MVS)演算法: +// - 使用滿足所有要求的最小版本 +// - 可預測和可重現的建構 +// - 無需衝突解決 +``` + + +--- + +### 練習問題: +1. 為什麼 Go 的依賴管理比 Java 更簡單? +2. Go 中內建測試的優勢是什麼? +3. 建構標籤如何在 Go 中啟用特定於平台的程式碼? +4. 為什麼 Go 通常不需要 Makefile 而 Java 專案經常需要? + +### 專案想法: +- 為樣板程式碼建立自訂 Go 產生器 +- 為 Go 專案設置全面的 CI/CD 管線 +- 建構分析 Go 依賴圖的工具 +- 實作基於工作區的多模組 Go 專案 + +### 下一步: +- 探索 Go 的生態系統和熱門函式庫 +- 學習 Go 中的資料庫整合 +- 了解微服務架構 +- 建構生產就緒的應用程式 diff --git a/content/docs/java2go/module-15-ecosystem.mdx b/content/docs/java2go/module-15-ecosystem.mdx new file mode 100644 index 0000000..51da480 --- /dev/null +++ b/content/docs/java2go/module-15-ecosystem.mdx @@ -0,0 +1,863 @@ +--- +title: "Module 15: Go Ecosystem and Libraries" +--- + +This module explores the Go ecosystem, comparing popular Go libraries with their Java counterparts. + +## Web Frameworks + +**Java Web Frameworks:** +- Spring Boot: Comprehensive, feature-rich +- Jakarta EE: Enterprise-focused +- Micronaut: Modern, compile-time focused +- Quarkus: Cloud-native, fast startup + +**Go Web Frameworks:** +- Gin: Fast, minimalist +- Echo: High performance, minimalist +- Fiber: Express-like, fast +- Chi: Lightweight, composable + + +```java !! java +// Java: Spring Boot REST Controller +import org.springframework.web.bind.annotation.*; +import org.springframework.stereotype.Service; +import org.springframework.beans.factory.annotation.Autowired; + +@RestController +@RequestMapping("/api/users") +public class UserController { + + @Autowired + private UserService userService; + + @GetMapping + public List getAllUsers() { + return userService.findAll(); + } + + @GetMapping("/{id}") + public ResponseEntity getUser(@PathVariable Long id) { + return userService.findById(id) + .map(user -> ResponseEntity.ok().body(user)) + .orElse(ResponseEntity.notFound().build()); + } + + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + public User createUser(@Valid @RequestBody User user) { + return userService.save(user); + } + + @PutMapping("/{id}") + public User updateUser(@PathVariable Long id, @RequestBody User user) { + user.setId(id); + return userService.save(user); + } + + @DeleteMapping("/{id}") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void deleteUser(@PathVariable Long id) { + userService.deleteById(id); + } +} +``` + +```go !! go +// Go: Gin REST Handler +package main + +import ( + "net/http" + "github.com/gin-gonic/gin" +) + +type User struct { + ID uint `json:"id"` + Name string `json:"name" binding:"required"` + Email string `json:"email" binding:"required,email"` +} + +func SetupRoutes(r *gin.Engine) { + users := r.Group("/api/users") + { + users.GET("", getAllUsers) + users.GET("/:id", getUser) + users.POST("", createUser) + users.PUT("/:id", updateUser) + users.DELETE("/:id", deleteUser) + } +} + +func getAllUsers(c *gin.Context) { + users, err := userService.FindAll() + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, users) +} + +func getUser(c *gin.Context) { + id := c.Param("id") + user, err := userService.FindByID(id) + if err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "User not found"}) + return + } + c.JSON(http.StatusOK, user) +} + +func createUser(c *gin.Context) { + var user User + if err := c.ShouldBindJSON(&user); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + if err := userService.Save(&user); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusCreated, user) +} + +func updateUser(c *gin.Context) { + id := c.Param("id") + var user User + if err := c.ShouldBindJSON(&user); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + if err := userService.Update(id, &user); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, user) +} + +func deleteUser(c *gin.Context) { + id := c.Param("id") + if err := userService.Delete(id); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.Status(http.StatusNoContent) +} +``` + + +## ORM and Database Libraries + + +```java !! java +// Java: Spring Data JPA +import org.springframework.data.jpa.repository.*; +import org.springframework.data.domain.*; +import javax.persistence.*; + +@Entity +@Table(name = "users") +public class User { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, unique = true) + private String email; + + private String name; + + @CreationTimestamp + private LocalDateTime createdAt; + + // Getters and setters +} + +interface UserRepository extends JpaRepository { + Optional findByEmail(String email); + + List findByNameContainingIgnoreCase(String name); + + @Query("SELECT u FROM User u WHERE u.email LIKE %:email%") + List searchByEmail(@Param("email") String email); +} + +@Service +public class UserService { + @Autowired + private UserRepository userRepository; + + public List searchUsers(String keyword) { + return userRepository.findByNameContainingIgnoreCase(keyword); + } + + @Transactional + public User createUser(User user) { + return userRepository.save(user); + } +} +``` + +```go !! go +// Go: GORM +package main + +import ( + "time" + "gorm.io/gorm" +) + +type User struct { + ID uint `gorm:"primarykey" json:"id"` + Email string `gorm:"uniqueIndex;not null" json:"email"` + Name string `json:"name"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` +} + +// Repository pattern +type UserRepository struct { + db *gorm.DB +} + +func NewUserRepository(db *gorm.DB) *UserRepository { + return &UserRepository{db: db} +} + +func (r *UserRepository) FindByID(id uint) (*User, error) { + var user User + err := r.db.First(&user, id).Error + if err != nil { + return nil, err + } + return &user, nil +} + +func (r *UserRepository) FindByEmail(email string) (*User, error) { + var user User + err := r.db.Where("email = ?", email).First(&user).Error + if err != nil { + return nil, err + } + return &user, nil +} + +func (r *UserRepository) SearchByName(keyword string) ([]User, error) { + var users []User + err := r.db.Where("name ILIKE ?", "%"+keyword+"%").Find(&users).Error + return users, err +} + +func (r *UserRepository) Create(user *User) error { + return r.db.Create(user).Error +} + +func (r *UserRepository) Update(user *User) error { + return r.db.Save(user).Error +} + +func (r *UserRepository) Delete(id uint) error { + return r.db.Delete(&User{}, id).Error +} + +// Pagination +func (r *UserRepository) List(page, pageSize int) ([]User, int64, error) { + var users []User + var total int64 + + err := r.db.Model(&User{}).Count(&total).Error + if err != nil { + return nil, 0, err + } + + err = r.db.Offset((page - 1) * pageSize). + Limit(pageSize). + Find(&users).Error + + return users, total, err +} +``` + + +## Configuration Management + + +```java !! java +// Java: Spring Boot Configuration +// application.yml +spring: + application: + name: myapp + datasource: + url: jdbc:postgresql://localhost:5432/mydb + username: user + password: ${DB_PASSWORD} + driver-class-name: org.postgresql.Driver + jpa: + hibernate: + ddl-auto: update + show-sql: true + +server: + port: ${PORT:8080} + +app: + name: MyApp + api-key: ${API_KEY} + features: + enabled: true + +@Configuration +@ConfigurationProperties(prefix = "app") +public class AppConfig { + private String name; + private String apiKey; + private Features features; + + // Getters and setters + + public static class Features { + private boolean enabled; + // Getters and setters + } +} +``` + +```go !! go +// Go: Viper configuration +package main + +import ( + "github.com/spf13/viper" +) + +type Config struct { + Name string `mapstructure:"name"` + APIKey string `mapstructure:"api_key"` + Server ServerConfig + Database DatabaseConfig +} + +type ServerConfig struct { + Port int `mapstructure:"port"` +} + +type DatabaseConfig struct { + Host string `mapstructure:"host"` + Port int `mapstructure:"port"` + User string `mapstructure:"user"` + Password string `mapstructure:"password"` + DBName string `mapstructure:"dbname"` +} + +func LoadConfig(path string) (*Config, error) { + viper.SetConfigName("config") + viper.SetConfigType("yaml") + viper.AddConfigPath(path) + viper.AddConfigPath(".") + viper.AddConfigPath("/etc/myapp/") + + // Set defaults + viper.SetDefault("server.port", 8080) + viper.SetDefault("database.port", 5432) + + // Allow environment variables + viper.AutomaticEnv() + viper.SetEnvPrefix("APP") + viper.BindEnv("database.password", "DB_PASSWORD") + + // Read config file + if err := viper.ReadInConfig(); err != nil { + return nil, err + } + + var config Config + if err := viper.Unmarshal(&config); err != nil { + return nil, err + } + + return &config, nil +} +``` + + +## Logging + + +```java !! java +// Java: SLF4J with Logback +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +@Service +public class UserService { + private static final Logger logger = LoggerFactory.getLogger(UserService.class); + + public User createUser(User user) { + logger.debug("Creating user: {}", user.getEmail()); + + try { + User result = userRepository.save(user); + logger.info("User created successfully with ID: {}", result.getId()); + return result; + + } catch (DataIntegrityViolationException e) { + logger.error("Failed to create user. Email already exists: {}", user.getEmail(), e); + throw new DuplicateEmailException(user.getEmail()); + } + } + + public User getUser(Long id) { + logger.trace("Fetching user with ID: {}", id); + + return userRepository.findById(id) + .orElseThrow(() -> { + logger.warn("User not found with ID: {}", id); + return new UserNotFoundException(id); + }); + } +} + +// logback-spring.xml + + + + %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n + + + + + logs/application.log + + logs/application.%d{yyyy-MM-dd}.log + 30 + + + %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + +``` + +```go !! go +// Go: Logrus / Zap / Zerolog +package main + +import ( + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +var logger *zap.Logger + +func InitLogger(env string) error { + var config zap.Config + + if env == "production" { + config = zap.NewProductionConfig() + } else { + config = zap.NewDevelopmentConfig() + } + + config.EncoderConfig.TimeKey = "timestamp" + config.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder + + log, err := config.Build() + if err != nil { + return err + } + + logger = log + return nil +} + +type UserService struct { + repo *UserRepository +} + +func (s *UserService) CreateUser(user *User) error { + logger.Debug("Creating user", + zap.String("email", user.Email)) + + if err := s.repo.Create(user); err != nil { + logger.Error("Failed to create user", + zap.String("email", user.Email), + zap.Error(err)) + + return err + } + + logger.Info("User created successfully", + zap.Uint("id", user.ID), + zap.String("email", user.Email)) + + return nil +} + +func (s *UserService) GetUser(id uint) (*User, error) { + logger.Debug("Fetching user", + zap.Uint("id", id)) + + user, err := s.repo.FindByID(id) + if err != nil { + logger.Warn("User not found", + zap.Uint("id", id)) + return nil, err + } + + return user, nil +} + +// Structured logging with context +func (s *UserService) UpdateUser(id uint, updates map[string]interface{}) error { + logger.Info("Updating user", + zap.Uint("id", id), + zap.Any("updates", updates)) + + // Update logic here + return nil +} +``` + + +## HTTP Client Libraries + + +```java !! java +// Java: RestTemplate / WebClient +import org.springframework.web.client.*; +import org.springframework.http.*; +import reactor.core.publisher.Mono; + +@Service +public class ApiClient { + + // RestTemplate (synchronous, being phased out) + private final RestTemplate restTemplate; + + public ApiClient() { + this.restTemplate = new RestTemplate(); + } + + public User getUserById(Long id) { + String url = "https://api.example.com/users/" + id; + + ResponseEntity response = restTemplate.getForEntity( + url, + User.class + ); + + return response.getBody(); + } + + public User createUser(User user) { + String url = "https://api.example.com/users"; + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + + HttpEntity request = new HttpEntity<>(user, headers); + + ResponseEntity response = restTemplate.postForEntity( + url, + request, + User.class + ); + + return response.getBody(); + } + + // WebClient (asynchronous, modern) + private final WebClient webClient; + + public ApiClient(WebClient.Builder webClientBuilder) { + this.webClient = webClientBuilder + .baseUrl("https://api.example.com") + .build(); + } + + public Mono getUserAsync(Long id) { + return webClient.get() + .uri("/users/{id}", id) + .retrieve() + .bodyToMono(User.class); + } + + public Mono createUserAsync(User user) { + return webClient.post() + .uri("/users") + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(user) + .retrieve() + .bodyToMono(User.class); + } +} +``` + +```go !! go +// Go: resty +package main + +import ( + "github.com/go-resty/resty/v2" +) + +type APIClient struct { + client *resty.Client + baseURL string +} + +func NewAPIClient(baseURL string) *APIClient { + client := resty.New(). + SetHeader("Content-Type", "application/json"). + SetHeader("Accept", "application/json") + + return &APIClient{ + client: client, + baseURL: baseURL, + } +} + +func (c *APIClient) GetUser(id uint) (*User, error) { + var user User + + resp, err := c.client.R(). + SetPathParams(map[string]string{ + "id": string(rune(id)), + }). + SetResult(&user). + Get(c.baseURL + "/users/{id}") + + if err != nil { + return nil, err + } + + if resp.IsError() { + return nil, fmt.Errorf("API error: %s", resp.Status()) + } + + return &user, nil +} + +func (c *APIClient) CreateUser(user *User) (*User, error) { + var result User + + resp, err := c.client.R(). + SetBody(user). + SetResult(&result). + Post(c.baseURL + "/users") + + if err != nil { + return nil, err + } + + if resp.IsError() { + return nil, fmt.Errorf("API error: %s", resp.Status()) + } + + return &result, nil +} + +// With retry logic +func NewAPIClientWithRetry(baseURL string) *APIClient { + client := resty.New(). + SetRetryCount(3). + SetRetryWaitTime(1 * time.Second). + SetRetryMaxWaitTime(10 * time.Second). + AddRetryCondition( + func(r *resty.Response, err error) bool { + return r.StatusCode() >= 500 || err != nil + }, + ) + + return &APIClient{ + client: client, + baseURL: baseURL, + } +} +``` + + +## Testing Libraries + + +```java !! java +// Java: JUnit 5 + Mockito + AssertJ +import org.junit.jupiter.api.*; +import org.mockito.Mockito; +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; + +@DisplayName("UserService Tests") +class UserServiceTest { + + private UserRepository userRepository; + private UserService userService; + + @BeforeEach + void setUp() { + userRepository = Mockito.mock(UserRepository.class); + userService = new UserService(userRepository); + } + + @Test + @DisplayName("Should create user successfully") + void shouldCreateUser() { + // Given + User user = new User("john@example.com"); + when(userRepository.save(any(User.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); + + // When + User result = userService.create(user); + + // Then + assertThat(result).isNotNull(); + assertThat(result.getEmail()).isEqualTo("john@example.com"); + verify(userRepository).save(user); + } + + @Test + @DisplayName("Should throw exception for duplicate email") + void shouldThrowForDuplicateEmail() { + // Given + User user = new User("john@example.com"); + when(userRepository.existsByEmail("john@example.com")) + .thenReturn(true); + + // When & Then + assertThatThrownBy(() -> userService.create(user)) + .isInstanceOf(DuplicateEmailException.class) + .hasMessageContaining("john@example.com"); + } +} +``` + +```go !! go +// Go: testing + testify +package main + +import ( + "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +type MockUserRepository struct { + mock.Mock +} + +func (m *MockUserRepository) Save(user *User) error { + args := m.Called(user) + return args.Error(0) +} + +func (m *MockUserRepository) ExistsByEmail(email string) bool { + args := m.Called(email) + return args.Bool(0) +} + +func TestUserService_CreateUser(t *testing.T) { + tests := []struct { + name string + user *User + setup func(*MockUserRepository) + wantErr bool + errType error + }{ + { + name: "successful creation", + user: &User{Email: "john@example.com"}, + setup: func(m *MockUserRepository) { + m.On("ExistsByEmail", "john@example.com").Return(false) + m.On("Save", mock.AnythingOfType("*User")).Return(nil) + }, + wantErr: false, + }, + { + name: "duplicate email", + user: &User{Email: "john@example.com"}, + setup: func(m *MockUserRepository) { + m.On("ExistsByEmail", "john@example.com").Return(true) + }, + wantErr: true, + errType: ErrDuplicateEmail, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockRepo := new(MockUserRepository) + tt.setup(mockRepo) + + service := NewUserService(mockRepo) + err := service.Create(tt.user) + + if tt.wantErr { + assert.Error(t, err) + if tt.errType != nil { + assert.ErrorIs(t, err, tt.errType) + } + } else { + assert.NoError(t, err) + assert.NotNil(t, tt.user.ID) + } + + mockRepo.AssertExpectations(t) + }) + } +} +``` + + +## Popular Go Libraries by Category + +| Category | Java Library | Go Library | +|----------|--------------|------------| +| Web Framework | Spring Boot | Gin, Echo, Fiber | +| ORM | Hibernate/JPA | GORM, sqlx | +| Validation | Hibernate Validator | go-playground/validator | +| Configuration | Spring Cloud Config | Viper | +| Logging | SLF4J + Logback | Zap, Zerolog | +| Testing | JUnit + Mockito | testing + testify | +| HTTP Client | RestTemplate/WebClient | resty, fasthttp | +| Dependency Injection | Spring DI / Google Guice | Wire, Fx | +| Rate Limiting | Resilience4j | uber-go/ratelimit | +| Caching | Caffeine / Redis | go-redis, bigcache | +| Authentication | Spring Security | casbin, jwt-go | +| CLI | Picocli | cobra, viper | +| Database Migrations | Flyway / Liquibase | golang-migrate, goose | +| Message Queue | Kafka/RabbitMQ | Kafka, amqp091-go | +| Tracing | OpenTelemetry | OpenTelemetry Go | + +--- + +### Practice Questions: +1. Why does Go favor smaller, focused libraries over comprehensive frameworks? +2. How does Go's approach to dependency injection differ from Spring's? +3. What are the advantages of Go's standard library over Java's? +4. When would you choose GORM over sqlx? + +### Project Ideas: +- Build a REST API using Gin and GORM +- Create a CLI tool with Cobra +- Implement a microservice with proper logging and monitoring +- Set up a project using Wire for dependency injection + +### Next Steps: +- Learn database integration in Go +- Understand microservices architecture +- Explore real-world project patterns +- Build production-ready applications diff --git a/content/docs/java2go/module-15-ecosystem.zh-cn.mdx b/content/docs/java2go/module-15-ecosystem.zh-cn.mdx new file mode 100644 index 0000000..e59306c --- /dev/null +++ b/content/docs/java2go/module-15-ecosystem.zh-cn.mdx @@ -0,0 +1,238 @@ +--- +title: "模块 15:Go 生态系统和库" +--- + +本模块探索 Go 生态系统,比较流行的 Go 库与 Java 的对应库。 + +## Web 框架 + +**Java Web 框架:** +- Spring Boot:全面、功能丰富 +- Jakarta EE:面向企业 +- Micronaut:现代、编译时聚焦 +- Quarkus:云原生、快速启动 + +**Go Web 框架:** +- Gin:快速、极简主义 +- Echo:高性能、极简主义 +- Fiber:Express 风格、快速 +- Chi:轻量级、可组合 + + +```java !! java +// Java: Spring Boot REST 控制器 +import org.springframework.web.bind.annotation.*; +import org.springframework.stereotype.Service; +import org.springframework.beans.factory.annotation.Autowired; + +@RestController +@RequestMapping("/api/users") +public class UserController { + + @Autowired + private UserService userService; + + @GetMapping + public List getAllUsers() { + return userService.findAll(); + } + + @GetMapping("/{id}") + public ResponseEntity getUser(@PathVariable Long id) { + return userService.findById(id) + .map(user -> ResponseEntity.ok().body(user)) + .orElse(ResponseEntity.notFound().build()); + } + + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + public User createUser(@Valid @RequestBody User user) { + return userService.save(user); + } + + @PutMapping("/{id}") + public User updateUser(@PathVariable Long id, @RequestBody User user) { + user.setId(id); + return userService.save(user); + } + + @DeleteMapping("/{id}") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void deleteUser(@PathVariable Long id) { + userService.deleteById(id); + } +} +``` + +```go !! go +// Go: Gin REST 处理器 +package main + +import ( + "net/http" + "github.com/gin-gonic/gin" +) + +type User struct { + ID uint `json:"id"` + Name string `json:"name" binding:"required"` + Email string `json:"email" binding:"required,email"` +} + +func SetupRoutes(r *gin.Engine) { + users := r.Group("/api/users") + { + users.GET("", getAllUsers) + users.GET("/:id", getUser) + users.POST("", createUser) + users.PUT("/:id", updateUser) + users.DELETE("/:id", deleteUser) + } +} + +func getAllUsers(c *gin.Context) { + users, err := userService.FindAll() + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, users) +} + +func createUser(c *gin.Context) { + var user User + if err := c.ShouldBindJSON(&user); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + if err := userService.Save(&user); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusCreated, user) +} +``` + + +## ORM 和数据库库 + + +```java !! java +// Java: Spring Data JPA +import org.springframework.data.jpa.repository.*; +import org.springframework.data.domain.*; +import javax.persistence.*; + +@Entity +@Table(name = "users") +public class User { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, unique = true) + private String email; + + private String name; + + @CreationTimestamp + private LocalDateTime createdAt; + + // Getters and setters +} + +interface UserRepository extends JpaRepository { + Optional findByEmail(String email); + + List findByNameContainingIgnoreCase(String name); +} +``` + +```go !! go +// Go: GORM +package main + +import ( + "time" + "gorm.io/gorm" +) + +type User struct { + ID uint `gorm:"primarykey" json:"id"` + Email string `gorm:"uniqueIndex;not null" json:"email"` + Name string `json:"name"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` +} + +type UserRepository struct { + db *gorm.DB +} + +func NewUserRepository(db *gorm.DB) *UserRepository { + return &UserRepository{db: db} +} + +func (r *UserRepository) FindByEmail(email string) (*User, error) { + var user User + err := r.db.Where("email = ?", email).First(&user).Error + if err != nil { + return nil, err + } + return &user, nil +} + +func (r *UserRepository) Create(user *User) error { + return r.db.Create(user).Error +} + +func (r *UserRepository) Update(user *User) error { + return r.db.Save(user).Error +} + +func (r *UserRepository) Delete(id uint) error { + return r.db.Delete(&User{}, id).Error +} +``` + + +## 常用 Go 库分类 + +| 类别 | Java 库 | Go 库 | +|------|---------|-------| +| Web 框架 | Spring Boot | Gin, Echo, Fiber | +| ORM | Hibernate/JPA | GORM, sqlx | +| 验证 | Hibernate Validator | go-playground/validator | +| 配置 | Spring Cloud Config | Viper | +| 日志 | SLF4J + Logback | Zap, Zerolog | +| 测试 | JUnit + Mockito | testing + testify | +| HTTP 客户端 | RestTemplate/WebClient | resty, fasthttp | +| 依赖注入 | Spring DI / Google Guice | Wire, Fx | +| 速率限制 | Resilience4j | uber-go/ratelimit | +| 缓存 | Caffeine / Redis | go-redis, bigcache | +| 认证 | Spring Security | casbin, jwt-go | +| CLI | Picocli | cobra, viper | +| 数据库迁移 | Flyway / Liquibase | golang-migrate, goose | + +--- + +### 练习问题: +1. 为什么 Go 偏好小型、专注的库而不是综合框架? +2. Go 的依赖注入方法与 Spring 有什么不同? +3. Go 标准库相比 Java 有什么优势? +4. 什么时候你会选择 GORM 而不是 sqlx? + +### 项目想法: +- 使用 Gin 和 GORM 构建 REST API +- 使用 Cobra 创建 CLI 工具 +- 实现具有适当日志和监控的微服务 +- 使用 Wire 设置依赖注入项目 + +### 下一步: +- 学习 Go 中的数据库集成 +- 了解微服务架构 +- 探索真实世界项目模式 +- 构建生产就绪的应用程序 diff --git a/content/docs/java2go/module-15-ecosystem.zh-tw.mdx b/content/docs/java2go/module-15-ecosystem.zh-tw.mdx new file mode 100644 index 0000000..d169233 --- /dev/null +++ b/content/docs/java2go/module-15-ecosystem.zh-tw.mdx @@ -0,0 +1,238 @@ +--- +title: "模組 15:Go 生態系統和函式庫" +--- + +本模組探索 Go 生態系統,比較熱門的 Go 函式庫與 Java 的對應函式庫。 + +## Web 框架 + +**Java Web 框架:** +- Spring Boot:全面、功能豐富 +- Jakarta EE:面向企業 +- Micronaut:現代、編譯時聚焦 +- Quarkus:雲端原生、快速啟動 + +**Go Web 框架:** +- Gin:快速、極簡主義 +- Echo:高效能、極簡主義 +- Fiber:Express 風格、快速 +- Chi:輕量級、可組合 + + +```java !! java +// Java: Spring Boot REST 控制器 +import org.springframework.web.bind.annotation.*; +import org.springframework.stereotype.Service; +import org.springframework.beans.factory.annotation.Autowired; + +@RestController +@RequestMapping("/api/users") +public class UserController { + + @Autowired + private UserService userService; + + @GetMapping + public List getAllUsers() { + return userService.findAll(); + } + + @GetMapping("/{id}") + public ResponseEntity getUser(@PathVariable Long id) { + return userService.findById(id) + .map(user -> ResponseEntity.ok().body(user)) + .orElse(ResponseEntity.notFound().build()); + } + + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + public User createUser(@Valid @RequestBody User user) { + return userService.save(user); + } + + @PutMapping("/{id}") + public User updateUser(@PathVariable Long id, @RequestBody User user) { + user.setId(id); + return userService.save(user); + } + + @DeleteMapping("/{id}") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void deleteUser(@PathVariable Long id) { + userService.deleteById(id); + } +} +``` + +```go !! go +// Go: Gin REST 處理器 +package main + +import ( + "net/http" + "github.com/gin-gonic/gin" +) + +type User struct { + ID uint `json:"id"` + Name string `json:"name" binding:"required"` + Email string `json:"email" binding:"required,email"` +} + +func SetupRoutes(r *gin.Engine) { + users := r.Group("/api/users") + { + users.GET("", getAllUsers) + users.GET("/:id", getUser) + users.POST("", createUser) + users.PUT("/:id", updateUser) + users.DELETE("/:id", deleteUser) + } +} + +func getAllUsers(c *gin.Context) { + users, err := userService.FindAll() + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, users) +} + +func createUser(c *gin.Context) { + var user User + if err := c.ShouldBindJSON(&user); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + if err := userService.Save(&user); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusCreated, user) +} +``` + + +## ORM 和資料庫函式庫 + + +```java !! java +// Java: Spring Data JPA +import org.springframework.data.jpa.repository.*; +import org.springframework.data.domain.*; +import javax.persistence.*; + +@Entity +@Table(name = "users") +public class User { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, unique = true) + private String email; + + private String name; + + @CreationTimestamp + private LocalDateTime createdAt; + + // Getters and setters +} + +interface UserRepository extends JpaRepository { + Optional findByEmail(String email); + + List findByNameContainingIgnoreCase(String name); +} +``` + +```go !! go +// Go: GORM +package main + +import ( + "time" + "gorm.io/gorm" +) + +type User struct { + ID uint `gorm:"primarykey" json:"id"` + Email string `gorm:"uniqueIndex;not null" json:"email"` + Name string `json:"name"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` +} + +type UserRepository struct { + db *gorm.DB +} + +func NewUserRepository(db *gorm.DB) *UserRepository { + return &UserRepository{db: db} +} + +func (r *UserRepository) FindByEmail(email string) (*User, error) { + var user User + err := r.db.Where("email = ?", email).First(&user).Error + if err != nil { + return nil, err + } + return &user, nil +} + +func (r *UserRepository) Create(user *User) error { + return r.db.Create(user).Error +} + +func (r *UserRepository) Update(user *User) error { + return r.db.Save(user).Error +} + +func (r *UserRepository) Delete(id uint) error { + return r.db.Delete(&User{}, id).Error +} +``` + + +## 常用 Go 函式庫分類 + +| 類別 | Java 函式庫 | Go 函式庫 | +|------|-------------|-----------| +| Web 框架 | Spring Boot | Gin, Echo, Fiber | +| ORM | Hibernate/JPA | GORM, sqlx | +| 驗證 | Hibernate Validator | go-playground/validator | +| 設定 | Spring Cloud Config | Viper | +| 日誌 | SLF4J + Logback | Zap, Zerolog | +| 測試 | JUnit + Mockito | testing + testify | +| HTTP 客戶端 | RestTemplate/WebClient | resty, fasthttp | +| 依賴注入 | Spring DI / Google Guice | Wire, Fx | +| 速率限制 | Resilience4j | uber-go/ratelimit | +| 快取 | Caffeine / Redis | go-redis, bigcache | +| 認證 | Spring Security | casbin, jwt-go | +| CLI | Picocli | cobra, viper | +| 資料庫遷移 | Flyway / Liquibase | golang-migrate, goose | + +--- + +### 練習問題: +1. 為什麼 Go 偏好小型、專注的函式庫而不是綜合框架? +2. Go 的依賴注入方法與 Spring 有什麼不同? +3. Go 標準函式庫相比 Java 有什麼優勢? +4. 什麼時候你會選擇 GORM 而不是 sqlx? + +### 專案想法: +- 使用 Gin 和 GORM 建構 REST API +- 使用 Cobra 建立 CLI 工具 +- 實作具有適當日誌和監控的微服務 +- 使用 Wire 設定依賴注入專案 + +### 下一步: +- 學習 Go 中的資料庫整合 +- 了解微服務架構 +- 探索真實世界專案模式 +- 建構生產就緒的應用程式 diff --git a/content/docs/java2go/module-16-database.mdx b/content/docs/java2go/module-16-database.mdx new file mode 100644 index 0000000..649e2dc --- /dev/null +++ b/content/docs/java2go/module-16-database.mdx @@ -0,0 +1,452 @@ +--- +title: "Module 16: Database Integration" +--- + +This module covers database integration in Go, comparing it with Java's database approaches. + +## Database Access Patterns + +**Java Database Approaches:** +- JDBC: Low-level database access +- Spring Data JPA: ORM with repositories +- Hibernate: Feature-rich ORM +- MyBatis: SQL mapping framework +- jOOQ: Type-safe SQL + +**Go Database Approaches:** +- database/sql: Standard library interface +- GORM: Full-featured ORM +- sqlx: Extensions to database/sql +- sqlc: Type-safe SQL code generation + + +```java !! java +// Java: Spring Data JPA +import org.springframework.data.jpa.repository.*; +import org.springframework.stereotype.*; +import javax.transaction.Transactional; +import java.util.Optional; + +@Service +@Transactional +public class UserService { + + @Autowired + private UserRepository userRepository; + + public User createUser(String email, String name) { + User user = new User(); + user.setEmail(email); + user.setName(name); + return userRepository.save(user); + } + + public Optional getUserById(Long id) { + return userRepository.findById(id); + } + + public Optional getUserByEmail(String email) { + return userRepository.findByEmail(email); + } + + public List searchUsers(String keyword) { + return userRepository.findByNameContainingIgnoreCase(keyword); + } + + public User updateUser(Long id, String name) { + User user = userRepository.findById(id) + .orElseThrow(() -> new UserNotFoundException(id)); + user.setName(name); + return userRepository.save(user); + } + + public void deleteUser(Long id) { + if (!userRepository.existsById(id)) { + throw new UserNotFoundException(id); + } + userRepository.deleteById(id); + } + + // Custom query example + public List findActiveUsers() { + return userRepository.findByActiveTrue(); + } +} +``` + +```go !! go +// Go: GORM +package main + +import ( + "gorm.io/gorm" + "gorm.io/gorm/clause" +) + +type UserService struct { + db *gorm.DB +} + +func NewUserService(db *gorm.DB) *UserService { + return &UserService{db: db} +} + +func (s *UserService) CreateUser(email, name string) (*User, error) { + user := &User{ + Email: email, + Name: name, + } + + if err := s.db.Create(user).Error; err != nil { + return nil, err + } + + return user, nil +} + +func (s *UserService) GetUserByID(id uint) (*User, error) { + var user User + err := s.db.First(&user, id).Error + if err != nil { + return nil, err + } + return &user, nil +} + +func (s *UserService) GetUserByEmail(email string) (*User, error) { + var user User + err := s.db.Where("email = ?", email).First(&user).Error + if err != nil { + return nil, err + } + return &user, nil +} + +func (s *UserService) SearchUsers(keyword string) ([]User, error) { + var users []User + err := s.db.Where("name ILIKE ?", "%"+keyword+"%").Find(&users).Error + return users, err +} + +func (s *UserService) UpdateUser(id uint, name string) (*User, error) { + var user User + if err := s.db.First(&user, id).Error; err != nil { + return nil, err + } + + user.Name = name + if err := s.db.Save(&user).Error; err != nil { + return nil, err + } + + return &user, nil +} + +func (s *UserService) DeleteUser(id uint) error { + result := s.db.Delete(&User{}, id) + if result.Error != nil { + return result.Error + } + if result.RowsAffected == 0 { + return gorm.ErrRecordNotFound + } + return nil +} + +func (s *UserService) FindActiveUsers() ([]User, error) { + var users []User + err := s.db.Where("active = ?", true).Find(&users).Error + return users, err +} + +// Transaction example +func (s *UserService) TransferFunds(fromID, toID uint, amount float64) error { + return s.db.Transaction(func(tx *gorm.DB) error { + // Lock rows for update + var fromUser User + if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}). + First(&fromUser, fromID).Error; err != nil { + return err + } + + var toUser User + if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}). + First(&toUser, toID).Error; err != nil { + return err + } + + fromUser.Balance -= amount + toUser.Balance += amount + + if err := tx.Save(&fromUser).Error; err != nil { + return err + } + + if err := tx.Save(&toUser).Error; err != nil { + return err + } + + return nil + }) +} +``` + + +## Connection Pooling + + +```java !! java +// Java: HikariCP (Spring Boot) +// application.properties +spring.datasource.url=jdbc:postgresql://localhost:5432/mydb +spring.datasource.username=myuser +spring.datasource.password=mypassword +spring.datasource.hikari.maximum-pool-size=10 +spring.datasource.hikari.minimum-idle=5 +spring.datasource.hikari.connection-timeout=30000 +spring.datasource.hikari.idle-timeout=600000 +spring.datasource.hikari.max-lifetime=1800000 + +// Programmatic configuration +@Bean +public DataSource dataSource() { + HikariConfig config = new HikariConfig(); + config.setJdbcUrl("jdbc:postgresql://localhost:5432/mydb"); + config.setUsername("myuser"); + config.setPassword("mypassword"); + config.setMaximumPoolSize(10); + config.setMinimumIdle(5); + config.setConnectionTimeout(30000); + config.setIdleTimeout(600000); + config.setMaxLifetime(1800000); + + return new HikariDataSource(config); +} +``` + +```go !! go +// Go: database/sql connection pool +package main + +import ( + "database/sql" + "time" + _ "github.com/lib/pq" +) + +func NewDB(dataSourceName string) (*sql.DB, error) { + db, err := sql.Open("postgres", dataSourceName) + if err != nil { + return nil, err + } + + // Connection pool settings + db.SetMaxOpenConns(25) // Maximum open connections + db.SetMaxIdleConns(5) // Maximum idle connections + db.SetConnMaxLifetime(5 * time.Minute) // Maximum connection lifetime + db.SetConnMaxIdleTime(1 * time.Minute) // Maximum idle time for a connection + + // Verify connection + if err := db.Ping(); err != nil { + return nil, err + } + + return db, nil +} + +// GORM connection pool +func NewGormDB(dataSourceName string) (*gorm.DB, error) { + db, err := sql.Open("postgres", dataSourceName) + if err != nil { + return nil, err + } + + // Configure pool + db.SetMaxOpenConns(25) + db.SetMaxIdleConns(5) + db.SetConnMaxLifetime(5 * time.Minute) + + gormDB, err := gorm.Open(postgres.New(postgres.Config{ + Conn: db, + }), &gorm.Config{}) + + return gormDB, err +} +``` + + +## Migrations + + +```sql !! sql +-- Java: Flyway migration (V1__Create_users_table.sql) +CREATE TABLE users ( + id BIGSERIAL PRIMARY KEY, + email VARCHAR(255) NOT NULL UNIQUE, + name VARCHAR(255) NOT NULL, + password_hash VARCHAR(255) NOT NULL, + active BOOLEAN DEFAULT true, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX idx_users_email ON users(email); +CREATE INDEX idx_users_active ON users(active); + +-- V2__Add_last_login_column.sql +ALTER TABLE users ADD COLUMN last_login TIMESTAMP; +CREATE INDEX idx_users_last_login ON users(last_login); +``` + +```sql !! sql +-- Go: golang-migrate (000001_create_users_table.up.sql) +CREATE TABLE users ( + id BIGSERIAL PRIMARY KEY, + email VARCHAR(255) NOT NULL UNIQUE, + name VARCHAR(255) NOT NULL, + password_hash VARCHAR(255) NOT NULL, + active BOOLEAN DEFAULT true, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX idx_users_email ON users(email); +CREATE INDEX idx_users_active ON users(active); + +-- 000001_create_users_table.down.sql +DROP INDEX IF EXISTS idx_users_active; +DROP INDEX IF EXISTS idx_users_email; +DROP TABLE IF EXISTS users; + +-- Run migrations: +-- migrate -path migrations -database "postgres://user:pass@localhost:5432/dbname?sslmode=disable" up +-- migrate -path migrations -database "postgres://user:pass@localhost:5432/dbname?sslmode=disable" down 1 +``` + + +## Complex Queries + + +```java !! java +// Java: JPA Criteria API +public List findUsersWithCriteria(String email, Boolean active, Integer minAge) { + CriteriaBuilder cb = entityManager.getCriteriaBuilder(); + CriteriaQuery query = cb.createQuery(User.class); + Root user = query.from(User.class); + + List predicates = new ArrayList<>(); + + if (email != null) { + predicates.add(cb.equal(user.get("email"), email)); + } + + if (active != null) { + predicates.add(cb.equal(user.get("active"), active)); + } + + if (minAge != null) { + predicates.add(cb.ge(user.get("age"), minAge)); + } + + query.where(predicates.toArray(new Predicate[0])); + + return entityManager.createQuery(query).getResultList(); +} + +// JOIN example +public interface UserRepository extends JpaRepository { + + @Query("SELECT u FROM User u JOIN FETCH u.roles r WHERE r.name = :roleName") + List findByRoleName(@Param("roleName") String roleName); + + @Query(""" + SELECT new com.example.dto.UserStats(u.id, u.name, COUNT(o.id)) + FROM User u + LEFT JOIN u.orders o + GROUP BY u.id, u.name + """) + List findUserStatistics(); +} +``` + +```go !! go +// Go: GORM complex queries +func (s *UserService) FindUsersWithCriteria(email string, active *bool, minAge int) ([]User, error) { + var users []User + + query := s.db.Model(&User{}) + + if email != "" { + query = query.Where("email = ?", email) + } + + if active != nil { + query = query.Where("active = ?", *active) + } + + if minAge > 0 { + query = query.Where("age >= ?", minAge) + } + + err := query.Find(&users).Error + return users, err +} + +func (s *UserService) FindByRoleName(roleName string) ([]User, error) { + var users []User + err := s.db. + Joins("JOIN user_roles ON user_roles.user_id = users.id"). + Joins("JOIN roles ON roles.id = user_roles.role_id"). + Where("roles.name = ?", roleName). + Find(&users).Error + + return users, err +} + +func (s *UserService) FindUserStatistics() ([]UserStats, error) { + var results []UserStats + + err := s.db. + Model(&User{}). + Select("users.id, users.name, COUNT(orders.id) as order_count"). + Joins("LEFT JOIN orders ON orders.user_id = users.id"). + Group("users.id, users.name"). + Scan(&results).Error + + return results, err +} + +// Raw SQL when needed +func (s *UserService) ExecuteCustomQuery() ([]User, error) { + var users []User + err := s.db.Raw(` + SELECT u.* FROM users u + WHERE u.created_at > NOW() - INTERVAL '30 days' + AND u.active = true + ORDER BY u.created_at DESC + LIMIT 100 + `).Scan(&users).Error + + return users, err +} +``` + + +--- + +### Practice Questions: +1. When should you use GORM vs sqlx vs raw SQL? +2. How does Go's approach to transactions differ from Java's? +3. What are the advantages of Go's database/sql interface? +4. How do connection pools work in Go compared to Java? + +### Project Ideas: +- Build a REST API with full CRUD operations +- Implement database migrations for a multi-service application +- Create a reporting service with complex queries +- Set up read replicas and write splitting + +### Next Steps: +- Learn about microservices architecture +- Explore real-world project patterns +- Build production-ready applications diff --git a/content/docs/java2go/module-16-database.zh-cn.mdx b/content/docs/java2go/module-16-database.zh-cn.mdx new file mode 100644 index 0000000..69d8e33 --- /dev/null +++ b/content/docs/java2go/module-16-database.zh-cn.mdx @@ -0,0 +1,244 @@ +--- +title: "模块 16:数据库集成" +--- + +本模块介绍 Go 中的数据库集成,与 Java 的数据库方法进行比较。 + +## 数据库访问模式 + +**Java 数据库方法:** +- JDBC:低级数据库访问 +- Spring Data JPA:带仓储的 ORM +- Hibernate:功能丰富的 ORM +- MyBatis:SQL 映射框架 +- jOOQ:类型安全的 SQL + +**Go 数据库方法:** +- database/sql:标准库接口 +- GORM:全功能 ORM +- sqlx:database/sql 的扩展 +- sqlc:类型安全的 SQL 代码生成 + + +```java !! java +// Java: Spring Data JPA +@Service +@Transactional +public class UserService { + + @Autowired + private UserRepository userRepository; + + public User createUser(String email, String name) { + User user = new User(); + user.setEmail(email); + user.setName(name); + return userRepository.save(user); + } + + public Optional getUserById(Long id) { + return userRepository.findById(id); + } + + public Optional getUserByEmail(String email) { + return userRepository.findByEmail(email); + } + + public List searchUsers(String keyword) { + return userRepository.findByNameContainingIgnoreCase(keyword); + } + + public User updateUser(Long id, String name) { + User user = userRepository.findById(id) + .orElseThrow(() -> new UserNotFoundException(id)); + user.setName(name); + return userRepository.save(user); + } + + public void deleteUser(Long id) { + if (!userRepository.existsById(id)) { + throw new UserNotFoundException(id); + } + userRepository.deleteById(id); + } +} +``` + +```go !! go +// Go: GORM +package main + +import ( + "gorm.io/gorm" + "gorm.io/gorm/clause" +) + +type UserService struct { + db *gorm.DB +} + +func NewUserService(db *gorm.DB) *UserService { + return &UserService{db: db} +} + +func (s *UserService) CreateUser(email, name string) (*User, error) { + user := &User{ + Email: email, + Name: name, + } + + if err := s.db.Create(user).Error; err != nil { + return nil, err + } + + return user, nil +} + +func (s *UserService) GetUserByID(id uint) (*User, error) { + var user User + err := s.db.First(&user, id).Error + if err != nil { + return nil, err + } + return &user, nil +} + +func (s *UserService) GetUserByEmail(email string) (*User, error) { + var user User + err := s.db.Where("email = ?", email).First(&user).Error + if err != nil { + return nil, err + } + return &user, nil +} + +func (s *UserService) SearchUsers(keyword string) ([]User, error) { + var users []User + err := s.db.Where("name ILIKE ?", "%"+keyword+"%").Find(&users).Error + return users, err +} + +func (s *UserService) UpdateUser(id uint, name string) (*User, error) { + var user User + if err := s.db.First(&user, id).Error; err != nil { + return nil, err + } + + user.Name = name + if err := s.db.Save(&user).Error; err != nil { + return nil, err + } + + return &user, nil +} + +func (s *UserService) DeleteUser(id uint) error { + result := s.db.Delete(&User{}, id) + if result.Error != nil { + return result.Error + } + if result.RowsAffected == 0 { + return gorm.ErrRecordNotFound + } + return nil +} + +// 事务示例 +func (s *UserService) TransferFunds(fromID, toID uint, amount float64) error { + return s.db.Transaction(func(tx *gorm.DB) error { + // 锁定行以进行更新 + var fromUser User + if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}). + First(&fromUser, fromID).Error; err != nil { + return err + } + + var toUser User + if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}). + First(&toUser, toID).Error; err != nil { + return err + } + + fromUser.Balance -= amount + toUser.Balance += amount + + if err := tx.Save(&fromUser).Error; err != nil { + return err + } + + if err := tx.Save(&toUser).Error; err != nil { + return err + } + + return nil + }) +} +``` + + +## 连接池 + + +```java !! java +// Java: HikariCP (Spring Boot) +// application.properties +spring.datasource.url=jdbc:postgresql://localhost:5432/mydb +spring.datasource.username=myuser +spring.datasource.password=mypassword +spring.datasource.hikari.maximum-pool-size=10 +spring.datasource.hikari.minimum-idle=5 +spring.datasource.hikari.connection-timeout=30000 +spring.datasource.hikari.idle-timeout=600000 +spring.datasource.hikari.max-lifetime=1800000 +``` + +```go !! go +// Go: database/sql 连接池 +package main + +import ( + "database/sql" + "time" + _ "github.com/lib/pq" +) + +func NewDB(dataSourceName string) (*sql.DB, error) { + db, err := sql.Open("postgres", dataSourceName) + if err != nil { + return nil, err + } + + // 连接池设置 + db.SetMaxOpenConns(25) // 最大打开连接数 + db.SetMaxIdleConns(5) // 最大空闲连接数 + db.SetConnMaxLifetime(5 * time.Minute) // 最大连接生命周期 + db.SetConnMaxIdleTime(1 * time.Minute) // 连接的最大空闲时间 + + // 验证连接 + if err := db.Ping(); err != nil { + return nil, err + } + + return db, nil +} +``` + + +--- + +### 练习问题: +1. 什么时候应该使用 GORM vs sqlx vs 原始 SQL? +2. Go 的事务方法与 Java 有什么不同? +3. Go 的 database/sql 接口有什么优势? +4. Go 中的连接池与 Java 相比如何工作? + +### 项目想法: +- 构建具有完整 CRUD 操作的 REST API +- 为多服务应用实现数据库迁移 +- 创建具有复杂查询的报告服务 +- 设置读副本和写分离 + +### 下一步: +- 学习微服务架构 +- 探索真实世界项目模式 +- 构建生产就绪的应用程序 diff --git a/content/docs/java2go/module-16-database.zh-tw.mdx b/content/docs/java2go/module-16-database.zh-tw.mdx new file mode 100644 index 0000000..d7522be --- /dev/null +++ b/content/docs/java2go/module-16-database.zh-tw.mdx @@ -0,0 +1,244 @@ +--- +title: "模組 16:資料庫整合" +--- + +本模組介紹 Go 中的資料庫整合,與 Java 的資料庫方法進行比較。 + +## 資料庫存取模式 + +**Java 資料庫方法:** +- JDBC:低階資料庫存取 +- Spring Data JPA:帶儲存庫的 ORM +- Hibernate:功能豐富的 ORM +- MyBatis:SQL 對應框架 +- jOOQ:型別安全的 SQL + +**Go 資料庫方法:** +- database/sql:標準函式庫介面 +- GORM:全功能 ORM +- sqlx:database/sql 的擴充 +- sqlc:型別安全的 SQL 程式碼產生 + + +```java !! java +// Java: Spring Data JPA +@Service +@Transactional +public class UserService { + + @Autowired + private UserRepository userRepository; + + public User createUser(String email, String name) { + User user = new User(); + user.setEmail(email); + user.setName(name); + return userRepository.save(user); + } + + public Optional getUserById(Long id) { + return userRepository.findById(id); + } + + public Optional getUserByEmail(String email) { + return userRepository.findByEmail(email); + } + + public List searchUsers(String keyword) { + return userRepository.findByNameContainingIgnoreCase(keyword); + } + + public User updateUser(Long id, String name) { + User user = userRepository.findById(id) + .orElseThrow(() -> new UserNotFoundException(id)); + user.setName(name); + return userRepository.save(user); + } + + public void deleteUser(Long id) { + if (!userRepository.existsById(id)) { + throw new UserNotFoundException(id); + } + userRepository.deleteById(id); + } +} +``` + +```go !! go +// Go: GORM +package main + +import ( + "gorm.io/gorm" + "gorm.io/gorm/clause" +) + +type UserService struct { + db *gorm.DB +} + +func NewUserService(db *gorm.DB) *UserService { + return &UserService{db: db} +} + +func (s *UserService) CreateUser(email, name string) (*User, error) { + user := &User{ + Email: email, + Name: name, + } + + if err := s.db.Create(user).Error; err != nil { + return nil, err + } + + return user, nil +} + +func (s *UserService) GetUserByID(id uint) (*User, error) { + var user User + err := s.db.First(&user, id).Error + if err != nil { + return nil, err + } + return &user, nil +} + +func (s *UserService) GetUserByEmail(email string) (*User, error) { + var user User + err := s.db.Where("email = ?", email).First(&user).Error + if err != nil { + return nil, err + } + return &user, nil +} + +func (s *UserService) SearchUsers(keyword string) ([]User, error) { + var users []User + err := s.db.Where("name ILIKE ?", "%"+keyword+"%").Find(&users).Error + return users, err +} + +func (s *UserService) UpdateUser(id uint, name string) (*User, error) { + var user User + if err := s.db.First(&user, id).Error; err != nil { + return nil, err + } + + user.Name = name + if err := s.db.Save(&user).Error; err != nil { + return nil, err + } + + return &user, nil +} + +func (s *UserService) DeleteUser(id uint) error { + result := s.db.Delete(&User{}, id) + if result.Error != nil { + return result.Error + } + if result.RowsAffected == 0 { + return gorm.ErrRecordNotFound + } + return nil +} + +// 交易範例 +func (s *UserService) TransferFunds(fromID, toID uint, amount float64) error { + return s.db.Transaction(func(tx *gorm.DB) error { + // 鎖定列以進行更新 + var fromUser User + if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}). + First(&fromUser, fromID).Error; err != nil { + return err + } + + var toUser User + if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}). + First(&toUser, toID).Error; err != nil { + return err + } + + fromUser.Balance -= amount + toUser.Balance += amount + + if err := tx.Save(&fromUser).Error; err != nil { + return err + } + + if err := tx.Save(&toUser).Error; err != nil { + return err + } + + return nil + }) +} +``` + + +## 連線池 + + +```java !! java +// Java: HikariCP (Spring Boot) +// application.properties +spring.datasource.url=jdbc:postgresql://localhost:5432/mydb +spring.datasource.username=myuser +spring.datasource.password=mypassword +spring.datasource.hikari.maximum-pool-size=10 +spring.datasource.hikari.minimum-idle=5 +spring.datasource.hikari.connection-timeout=30000 +spring.datasource.hikari.idle-timeout=600000 +spring.datasource.hikari.max-lifetime=1800000 +``` + +```go !! go +// Go: database/sql 連線池 +package main + +import ( + "database/sql" + "time" + _ "github.com/lib/pq" +) + +func NewDB(dataSourceName string) (*sql.DB, error) { + db, err := sql.Open("postgres", dataSourceName) + if err != nil { + return nil, err + } + + // 連線池設定 + db.SetMaxOpenConns(25) // 最大打開連線數 + db.SetMaxIdleConns(5) // 最大閒置連線數 + db.SetConnMaxLifetime(5 * time.Minute) // 最大連線生命週期 + db.SetConnMaxIdleTime(1 * time.Minute) // 連線的最大閒置時間 + + // 驗證連線 + if err := db.Ping(); err != nil { + return nil, err + } + + return db, nil +} +``` + + +--- + +### 練習問題: +1. 什麼時候應該使用 GORM vs sqlx vs 原始 SQL? +2. Go 的交易方法與 Java 有什麼不同? +3. Go 的 database/sql 介面有什麼優勢? +4. Go 中的連線池與 Java 相比如何工作? + +### 專案想法: +- 建構具有完整 CRUD 操作的 REST API +- 為多服務應用實作資料庫遷移 +- 建立具有複雜查詢的報告服務 +- 設定讀副本和寫分離 + +### 下一步: +- 學習微服務架構 +- 探索真實世界專案模式 +- 建構生產就緒的應用程式 diff --git a/content/docs/java2go/module-17-microservices.mdx b/content/docs/java2go/module-17-microservices.mdx new file mode 100644 index 0000000..46ee277 --- /dev/null +++ b/content/docs/java2go/module-17-microservices.mdx @@ -0,0 +1,550 @@ +--- +title: "Module 17: Microservices Architecture" +--- + +This module covers building microservices in Go compared to Java's Spring Boot/Cloud approach. + +## Microservices Philosophy + +**Java Microservices (Spring Cloud):** +- Spring Boot: Quick service creation +- Spring Cloud: Distributed system patterns +- Service discovery with Eureka/Consul +- API Gateway with Spring Cloud Gateway +- Configuration with Spring Cloud Config +- Circuit breakers with Resilience4j +- Distributed tracing with Sleuth/Zipkin + +**Go Microservices:** +- Lightweight, fast services +- Minimal resource usage +- Quick startup times +- Simple deployment +- Built-in concurrency with goroutines +- Community libraries for distributed patterns + + +```java !! java +// Java: Spring Boot Microservice +@SpringBootApplication +@EnableDiscoveryClient +@EnableCircuitBreaker +public class UserServiceApplication { + + public static void main(String[] args) { + SpringApplication.run(UserServiceApplication.class, args); + } + + @Bean + public RestTemplate restTemplate() { + return new RestTemplate(); + } +} + +@RestController +@RequestMapping("/api/users") +public class UserController { + + @Autowired + private UserService userService; + + @Autowired + private RestTemplate restTemplate; + + @GetMapping("/{id}") + public User getUser(@PathVariable Long id) { + return userService.findById(id); + } + + @CircuitBreaker(name = "orderService", fallbackMethod = "getOrdersFallback") + @GetMapping("/{id}/orders") + public List getUserOrders(@PathVariable Long id) { + return restTemplate.getForObject( + "http://order-service/api/orders?userId=" + id, + List.class + ); + } + + public List getOrdersFallback(Long id, Exception e) { + return Collections.emptyList(); + } +} + +// application.yml +spring: + application: + name: user-service + cloud: + discovery: + enabled: true +server: + port: 8081 + +eureka: + client: + service-url: + defaultZone: http://eureka:8761/eureka/ +``` + +```go !! go +// Go: Microservice with Gin +package main + +import ( + "context" + "net/http" + "time" + "github.com/gin-gonic/gin" + "github.com/hashicorp/consul/api" +) + +type UserService struct { + orderServiceClient *OrderServiceClient + userRepo *UserRepository +} + +func NewUserService(orderClient *OrderServiceClient, userRepo *UserRepository) *UserService { + return &UserService{ + orderServiceClient: orderClient, + userRepo: userRepo, + } +} + +func (s *UserService) RegisterRoutes(r *gin.Engine) { + users := r.Group("/api/users") + { + users.GET("/:id", s.GetUser) + users.GET("/:id/orders", s.GetUserOrders) + } +} + +func (s *UserService) GetUser(c *gin.Context) { + id := c.Param("id") + + user, err := s.userRepo.FindByID(id) + if err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "User not found"}) + return + } + + c.JSON(http.StatusOK, user) +} + +func (s *UserService) GetUserOrders(c *gin.Context) { + id := c.Param("id") + + // Check if user exists + if _, err := s.userRepo.FindByID(id); err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "User not found"}) + return + } + + // Call order service with timeout + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + orders, err := s.orderServiceClient.GetOrdersByUserID(ctx, id) + if err != nil { + c.JSON(http.StatusServiceUnavailable, gin.H{ + "error": "Order service unavailable", + "orders": []Order{}, // Fallback + }) + return + } + + c.JSON(http.StatusOK, orders) +} + +// Service registration with Consul +func RegisterWithConsul(serviceID, serviceName, address string, port int) error { + config := api.DefaultConfig() + config.Address = "consul:8500" + + client, err := api.NewClient(config) + if err != nil { + return err + } + + registration := &api.AgentServiceRegistration{ + ID: serviceID, + Name: serviceName, + Address: address, + Port: port, + Check: &api.AgentServiceCheck{ + HTTP: fmt.Sprintf("http://%s:%d/health", address, port), + Interval: "10s", + Timeout: "3s", + DeregisterCriticalServiceAfter: "30s", + }, + } + + return client.Agent().ServiceRegister(registration) +} + +func main() { + // Register with Consul + if err := RegisterWithConsul("user-service-1", "user-service", "localhost", 8081); err != nil { + log.Fatalf("Failed to register service: %v", err) + } + + r := gin.Default() + + userService := NewUserService(NewOrderServiceClient(), NewUserRepository()) + userService.RegisterRoutes(r) + + r.GET("/health", func(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{"status": "healthy"}) + }) + + r.Run(":8081") +} +``` + + +## Service Discovery + + +```java !! java +// Java: Eureka Client +@Configuration +public class EurekaConfig { + + @Bean + @LoadBalanced + public RestTemplate restTemplate() { + return new RestTemplate(); + } +} + +@Service +public class OrderService { + + @Autowired + private RestTemplate restTemplate; + + public User getUser(Long userId) { + // Service discovery with load balancing + return restTemplate.getForObject( + "http://user-service/api/users/" + userId, + User.class + ); + } +} + +// application.yml +eureka: + client: + service-url: + defaultZone: http://eureka:8761/eureka/ + instance: + prefer-ip-address: true + lease-renewal-interval-in-seconds: 10 + lease-expiration-duration-in-seconds: 30 +``` + +```go !! go +// Go: Consul service discovery +package main + +import ( + "github.com/hashicorp/consul/api" +) + +type ServiceDiscovery struct { + client *api.Client +} + +func NewServiceDiscovery(consulAddr string) (*ServiceDiscovery, error) { + config := api.DefaultConfig() + config.Address = consulAddr + + client, err := api.NewClient(config) + if err != nil { + return nil, err + } + + return &ServiceDiscovery{client: client}, nil +} + +func (sd *ServiceDiscovery) DiscoverService(serviceName string) (string, int, error) { + services, _, err := sd.client.Health().Service(serviceName, "", true, nil) + if err != nil { + return "", 0, err + } + + if len(services) == 0 { + return "", 0, fmt.Errorf("no instances found for service: %s", serviceName) + } + + // Simple load balancing (round-robin) + service := services[0] + return service.Service.Address, service.Service.Port, nil +} + +func (sd *ServiceDiscovery) WatchService(serviceName string) <-chan []*api.ServiceEntry { + ch := make(chan []*api.ServiceEntry) + + go func() { + lastIndex := uint64(0) + for { + services, meta, err := sd.client.Health().Service( + serviceName, + "", + true, + &api.QueryOptions{ + WaitIndex: lastIndex, + }, + ) + + if err != nil { + log.Printf("Error watching service: %v", err) + time.Sleep(5 * time.Second) + continue + } + + if lastIndex != meta.LastIndex { + lastIndex = meta.LastIndex + ch <- services + } + } + }() + + return ch +} + +type OrderServiceClient struct { + discovery *ServiceDiscovery + httpClient *http.Client + currentIndex int + services []*api.ServiceEntry +} + +func NewOrderServiceClient(discovery *ServiceDiscovery) *OrderServiceClient { + client := &OrderServiceClient{ + discovery: discovery, + httpClient: &http.Client{Timeout: 5 * time.Second}, + } + + // Watch for service changes + go client.watchServices() + + return client +} + +func (c *OrderServiceClient) watchServices() { + ch := c.discovery.WatchService("order-service") + for services := range ch { + c.services = services + } +} + +func (c *OrderServiceClient) GetOrdersByUserID(ctx context.Context, userID string) ([]Order, error) { + if len(c.services) == 0 { + return nil, fmt.Errorf("no order service instances available") + } + + // Round-robin load balancing + service := c.services[c.currentIndex%len(c.services)] + c.currentIndex++ + + url := fmt.Sprintf( + "http://%s:%d/api/orders?userId=%s", + service.Service.Address, + service.Service.Port, + userID, + ) + + req, err := http.NewRequestWithContext(ctx, "GET", url, nil) + if err != nil { + return nil, err + } + + resp, err := c.httpClient.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + var orders []Order + if err := json.NewDecoder(resp.Body).Decode(&orders); err != nil { + return nil, err + } + + return orders, nil +} +``` + + +## API Gateway + + +```yaml !! yaml +# Java: Spring Cloud Gateway +spring: + cloud: + gateway: + routes: + - id: user-service + uri: lb://user-service + predicates: + - Path=/api/users/** + filters: + - StripPrefix=0 + + - id: order-service + uri: lb://order-service + predicates: + - Path=/api/orders/** + filters: + - StripPrefix=0 + - CircuitBreaker=orderServiceCB + + # Global CORS configuration + globalcors: + cors-configurations: + '[/**]': + allowed-origins: "*" + allowed-methods: + - GET + - POST + - PUT + - DELETE +``` + +```go !! go +// Go: API Gateway with Gin +package main + +import ( + "net/http/httputil" + "net/url" + "github.com/gin-gonic/gin" +) + +type APIGateway struct { + services map[string]string + proxies map[string]*httputil.ReverseProxy +} + +func NewAPIGateway() *APIGateway { + return &APIGateway{ + services: map[string]string{ + "user-service": "http://user-service:8081", + "order-service": "http://order-service:8082", + "product-service": "http://product-service:8083", + }, + proxies: make(map[string]*httputil.ReverseProxy), + } +} + +func (g *APIGateway) SetupProxies() error { + for name, addr := range g.services { + url, err := url.Parse(addr) + if err != nil { + return err + } + + proxy := httputil.NewSingleHostReverseProxy(url) + g.proxies[name] = proxy + } + + return nil +} + +func (g *APIGateway) RegisterRoutes(r *gin.Engine) { + // User service routes + r.Any("/api/users/*path", gin.WrapH(g.proxies["user-service"])) + + // Order service routes + r.Any("/api/orders/*path", gin.WrapH(g.proxies["order-service"])) + + // Product service routes + r.Any("/api/products/*path", gin.WrapH(g.proxies["product-service"])) + + // Health check + r.GET("/health", func(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{ + "status": "healthy", + "services": g.services, + }) + }) +} + +// Middleware for authentication +func AuthMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + token := c.GetHeader("Authorization") + + if token == "" { + c.JSON(http.StatusUnauthorized, gin.H{"error": "Missing token"}) + c.Abort() + return + } + + // Validate token + // ... + + c.Next() + } +} + +// Middleware for rate limiting +func RateLimitMiddleware() gin.HandlerFunc { + // Implement rate limiting + return func(c *gin.Context) { + // Check rate limit + c.Next() + } +} + +// Middleware for CORS +func CORSMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + c.Writer.Header().Set("Access-Control-Allow-Origin", "*") + c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") + c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization") + + if c.Request.Method == "OPTIONS" { + c.AbortWithStatus(http.StatusNoContent) + return + } + + c.Next() + } +} + +func main() { + r := gin.Default() + + // Global middleware + r.Use(CORSMiddleware()) + r.Use(AuthMiddleware()) + r.Use(RateLimitMiddleware()) + + gateway := NewAPIGateway() + gateway.SetupProxies() + gateway.RegisterRoutes(r) + + r.Run(":8080") +} +``` + + +--- + +### Practice Questions: +1. Why are Go microservices more lightweight than Java Spring Boot microservices? +2. How does service discovery work in Go compared to Spring Cloud? +3. What are the advantages of using Go for microservices? +4. When would you choose Go over Java for microservices? + +### Project Ideas: +- Build a complete microservices architecture with Go +- Implement service discovery with Consul +- Create an API gateway with rate limiting +- Set up distributed tracing + +### Next Steps: +- Learn about real-world project patterns +- Explore production-ready application architecture +- Build complete systems diff --git a/content/docs/java2go/module-17-microservices.zh-cn.mdx b/content/docs/java2go/module-17-microservices.zh-cn.mdx new file mode 100644 index 0000000..6bac1b5 --- /dev/null +++ b/content/docs/java2go/module-17-microservices.zh-cn.mdx @@ -0,0 +1,230 @@ +--- +title: "模块 17:微服务架构" +--- + +本模块介绍使用 Go 构建微服务,与 Java 的 Spring Boot/Cloud 方法进行比较。 + +## 微服务哲学 + +**Java 微服务(Spring Cloud):** +- Spring Boot:快速创建服务 +- Spring Cloud:分布式系统模式 +- 使用 Eureka/Consul 进行服务发现 +- 使用 Spring Cloud Gateway 的 API 网关 +- 使用 Spring Cloud Config 的配置 +- 使用 Resilience4j 的断路器 +- 使用 Sleuth/Zipkin 的分布式追踪 + +**Go 微服务:** +- 轻量级、快速的服务 +- 最小的资源使用 +- 快速启动时间 +- 简单的部署 +- 使用 goroutine 内置并发 +- 分布式模式的社区库 + + +```java !! java +// Java: Spring Boot 微服务 +@SpringBootApplication +@EnableDiscoveryClient +@EnableCircuitBreaker +public class UserServiceApplication { + + public static void main(String[] args) { + SpringApplication.run(UserServiceApplication.class, args); + } +} + +@RestController +@RequestMapping("/api/users") +public class UserController { + + @GetMapping("/{id}/orders") + @CircuitBreaker(name = "orderService", fallbackMethod = "getOrdersFallback") + public List getUserOrders(@PathVariable Long id) { + return restTemplate.getForObject( + "http://order-service/api/orders?userId=" + id, + List.class + ); + } + + public List getOrdersFallback(Long id, Exception e) { + return Collections.emptyList(); + } +} +``` + +```go !! go +// Go: 使用 Gin 的微服务 +package main + +import ( + "context" + "net/http" + "time" + "github.com/gin-gonic/gin" +) + +type UserService struct { + orderServiceClient *OrderServiceClient + userRepo *UserRepository +} + +func (s *UserService) GetUserOrders(c *gin.Context) { + id := c.Param("id") + + // 调用订单服务,带超时 + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + orders, err := s.orderServiceClient.GetOrdersByUserID(ctx, id) + if err != nil { + c.JSON(http.StatusServiceUnavailable, gin.H{ + "error": "Order service unavailable", + "orders": []Order{}, // Fallback + }) + return + } + + c.JSON(http.StatusOK, orders) +} + +// 使用 Consul 注册服务 +func RegisterWithConsul(serviceID, serviceName, address string, port int) error { + config := api.DefaultConfig() + config.Address = "consul:8500" + + client, err := api.NewClient(config) + if err != nil { + return err + } + + registration := &api.AgentServiceRegistration{ + ID: serviceID, + Name: serviceName, + Address: address, + Port: port, + Check: &api.AgentServiceCheck{ + HTTP: fmt.Sprintf("http://%s:%d/health", address, port), + Interval: "10s", + Timeout: "3s", + DeregisterCriticalServiceAfter: "30s", + }, + } + + return client.Agent().ServiceRegister(registration) +} +``` + + +## 服务发现 + + +```java !! java +// Java: Eureka 客户端 +@Service +public class OrderService { + + @Autowired + private RestTemplate restTemplate; + + public User getUser(Long userId) { + // 服务发现与负载均衡 + return restTemplate.getForObject( + "http://user-service/api/users/" + userId, + User.class + ); + } +} +``` + +```go !! go +// Go: Consul 服务发现 +package main + +import ( + "github.com/hashicorp/consul/api" +) + +type ServiceDiscovery struct { + client *api.Client +} + +func (sd *ServiceDiscovery) DiscoverService(serviceName string) (string, int, error) { + services, _, err := sd.client.Health().Service(serviceName, "", true, nil) + if err != nil { + return "", 0, err + } + + if len(services) == 0 { + return "", 0, fmt.Errorf("no instances found for service: %s", serviceName) + } + + // 简单负载均衡(轮询) + service := services[0] + return service.Service.Address, service.Service.Port, nil +} + +type OrderServiceClient struct { + discovery *ServiceDiscovery + httpClient *http.Client + currentIndex int + services []*api.ServiceEntry +} + +func (c *OrderServiceClient) GetOrdersByUserID(ctx context.Context, userID string) ([]Order, error) { + if len(c.services) == 0 { + return nil, fmt.Errorf("no order service instances available") + } + + // 轮询负载均衡 + service := c.services[c.currentIndex%len(c.services)] + c.currentIndex++ + + url := fmt.Sprintf( + "http://%s:%d/api/orders?userId=%s", + service.Service.Address, + service.Service.Port, + userID, + ) + + req, err := http.NewRequestWithContext(ctx, "GET", url, nil) + if err != nil { + return nil, err + } + + resp, err := c.httpClient.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + var orders []Order + if err := json.NewDecoder(resp.Body).Decode(&orders); err != nil { + return nil, err + } + + return orders, nil +} +``` + + +--- + +### 练习问题: +1. 为什么 Go 微服务比 Java Spring Boot 微服务更轻量? +2. Go 中的服务发现与 Spring Cloud 相比如何工作? +3. 使用 Go 构建微服务有什么优势? +4. 什么时候你会选择 Go 而不是 Java 来构建微服务? + +### 项目想法: +- 使用 Go 构建完整的微服务架构 +- 使用 Consul 实现服务发现 +- 创建具有速率限制的 API 网关 +- 设置分布式追踪 + +### 下一步: +- 学习真实世界项目模式 +- 探索生产就绪的应用程序架构 +- 构建完整的系统 diff --git a/content/docs/java2go/module-17-microservices.zh-tw.mdx b/content/docs/java2go/module-17-microservices.zh-tw.mdx new file mode 100644 index 0000000..c0b3cd8 --- /dev/null +++ b/content/docs/java2go/module-17-microservices.zh-tw.mdx @@ -0,0 +1,112 @@ +--- +title: "模組 17:微服務架構" +--- + +本模組介紹使用 Go 建構微服務,與 Java 的 Spring Boot/Cloud 方法進行比較。 + +## 微服務哲學 + +**Java 微服務(Spring Cloud):** +- Spring Boot:快速建立服務 +- Spring Cloud:分散式系統模式 +- 使用 Eureka/Consul 進行服務發現 +- 使用 Spring Cloud Gateway 的 API 閘道 +- 使用 Spring Cloud Config 的設定 +- 使用 Resilience4j 的斷路器 +- 使用 Sleuth/Zipkin 的分散式追蹤 + +**Go 微服務:** +- 輕量級、快速的服務 +- 最小的資源使用 +- 快速啟動時間 +- 簡單的部署 +- 使用 goroutine 內建並行 +- 分散式模式的社群函式庫 + + +```java !! java +// Java: Spring Boot 微服務 +@SpringBootApplication +@EnableDiscoveryClient +@EnableCircuitBreaker +public class UserServiceApplication { + + public static void main(String[] args) { + SpringApplication.run(UserServiceApplication.class, args); + } +} + +@RestController +@RequestMapping("/api/users") +public class UserController { + + @GetMapping("/{id}/orders") + @CircuitBreaker(name = "orderService", fallbackMethod = "getOrdersFallback") + public List getUserOrders(@PathVariable Long id) { + return restTemplate.getForObject( + "http://order-service/api/orders?userId=" + id, + List.class + ); + } + + public List getOrdersFallback(Long id, Exception e) { + return Collections.emptyList(); + } +} +``` + +```go !! go +// Go: 使用 Gin 的微服務 +package main + +import ( + "context" + "net/http" + "time" + "github.com/gin-gonic/gin" +) + +type UserService struct { + orderServiceClient *OrderServiceClient + userRepo *UserRepository +} + +func (s *UserService) GetUserOrders(c *gin.Context) { + id := c.Param("id") + + // 呼叫訂單服務,帶逾時 + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + orders, err := s.orderServiceClient.GetOrdersByUserID(ctx, id) + if err != nil { + c.JSON(http.StatusServiceUnavailable, gin.H{ + "error": "Order service unavailable", + "orders": []Order{}, // Fallback + }) + return + } + + c.JSON(http.StatusOK, orders) +} +``` + + +--- + +### 練習問題: +1. 為什麼 Go 微服務比 Java Spring Boot 微服務更輕量? +2. Go 中的服務發現與 Spring Cloud 相比如何工作? +3. 使用 Go 建構微服務有什麼優勢? +4. 什麼時候你會選擇 Go 而不是 Java 來建構微服務? + +### 專案想法: +- 使用 Go 建構完整的微服務架構 +- 使用 Consul 實作服務發現 +- 建立具有速率限制的 API 閘道 +- 設定分散式追蹤 + +### 下一步: +- 學習真實世界專案模式 +- 探索生產就緒的應用程式架構 +- 建構完整的系統 diff --git a/content/docs/java2go/module-18-real-world.mdx b/content/docs/java2go/module-18-real-world.mdx new file mode 100644 index 0000000..372943c --- /dev/null +++ b/content/docs/java2go/module-18-real-world.mdx @@ -0,0 +1,515 @@ +--- +title: "Module 18: Real-World Project Architecture" +--- + +This module explores real-world project architecture patterns in Go compared to Java enterprise applications. + +## Project Structure + +**Java Enterprise Structure:** +- Layered architecture (controller, service, repository) +- Separate modules for different concerns +- Heavy use of dependency injection +- Configuration-driven + +**Go Project Structure:** +- Practical, domain-driven organization +- Interface-based design +- Minimal magic, explicit dependencies +- Code organization over framework conventions + + +``` +# Java: Spring Boot Multi-Module Project +myapp/ +├── pom.xml +├── myapp-api/ +│ └── src/main/java/ +│ └── com/example/myapp/api/ +│ ├── UserController.java +│ └── dto/ +├── myapp-service/ +│ └── src/main/java/ +│ └── com/example/myapp/service/ +│ ├── UserService.java +│ └── impl/ +├── myapp-repository/ +│ └── src/main/java/ +│ └── com/example/myapp/repository/ +│ ├── UserRepository.java +│ └── entities/ +├── myapp-common/ +│ └── src/main/java/ +│ └── com/example/myapp/common/ +│ ├── exceptions/ +│ ├── utils/ +│ └── config/ +└── myapp-integration-tests/ +``` + +``` +# Go: Standard Project Layout +myapp/ +├── cmd/ +│ └── myapp/ +│ └── main.go # Application entry point +├── internal/ +│ ├── api/ +│ │ ├── handler/ +│ │ │ └── user_handler.go +│ │ ├── middleware/ +│ │ └── router.go +│ ├── service/ +│ │ ├── user_service.go +│ │ └── auth_service.go +│ ├── repository/ +│ │ ├── user_repository.go +│ │ └── postgres.go +│ ├── domain/ +│ │ ├── user.go +│ │ └── errors.go +│ └── config/ +│ └── config.go +├── pkg/ # Public libraries +│ └── logger/ +│ └── logger.go +├── api/ +│ └── openapi/ # API definitions +├── migrations/ # Database migrations +├── scripts/ # Build and deploy scripts +├── configs/ # Configuration files +├── go.mod +├── go.sum +├── Dockerfile +├── Makefile +└── README.md +``` + + +## Clean Architecture in Go + + +```go !! go +// internal/domain/user.go +package domain + +import ( + "errors" + "time" +) + +var ( + ErrUserNotFound = errors.New("user not found") + ErrInvalidEmail = errors.New("invalid email") + ErrEmailAlreadyTaken = errors.New("email already taken") +) + +type User struct { + ID uint + Email string + Name string + Password string + Active bool + CreatedAt time.Time + UpdatedAt time.Time +} + +type UserRepository interface { + FindByID(id uint) (*User, error) + FindByEmail(email string) (*User, error) + Create(user *User) error + Update(user *User) error + Delete(id uint) error + ExistsByEmail(email string) (bool, error) +} + +type UserService interface { + Register(email, password, name string) (*User, error) + Login(email, password string) (*User, error) + GetProfile(id uint) (*User, error) + UpdateProfile(id uint, name string) (*User, error) +} + +// internal/service/user_service.go +package service + +import ( + "crypto/rand" + "encoding/base64" + "fmt" + + "myapp/internal/domain" + "myapp/internal/repository" + "myapp/pkg/logger" +) + +type userServiceImpl struct { + userRepo domain.UserRepository + logger logger.Logger +} + +func NewUserService(userRepo domain.UserRepository, log logger.Logger) domain.UserService { + return &userServiceImpl{ + userRepo: userRepo, + logger: log, + } +} + +func (s *userServiceImpl) Register(email, password, name string) (*domain.User, error) { + // Validate email + if email == "" { + return nil, domain.ErrInvalidEmail + } + + // Check if email exists + exists, err := s.userRepo.ExistsByEmail(email) + if err != nil { + s.logger.Error("Failed to check email existence", "error", err) + return nil, err + } + + if exists { + return nil, domain.ErrEmailAlreadyTaken + } + + // Hash password + hashedPassword, err := s.hashPassword(password) + if err != nil { + return nil, err + } + + // Create user + user := &domain.User{ + Email: email, + Name: name, + Password: hashedPassword, + Active: true, + } + + if err := s.userRepo.Create(user); err != nil { + s.logger.Error("Failed to create user", "error", err) + return nil, err + } + + s.logger.Info("User created", "user_id", user.ID) + return user, nil +} + +func (s *userServiceImpl) Login(email, password string) (*domain.User, error) { + user, err := s.userRepo.FindByEmail(email) + if err != nil { + return nil, domain.ErrUserNotFound + } + + if !s.verifyPassword(user.Password, password) { + return nil, domain.ErrInvalidCredentials + } + + return user, nil +} + +// internal/api/handler/user_handler.go +package handler + +import ( + "net/http" + + "myapp/internal/domain" + "myapp/internal/service" + + "github.com/gin-gonic/gin" +) + +type UserHandler struct { + userService domain.UserService +} + +func NewUserHandler(userService domain.UserService) *UserHandler { + return &UserHandler{ + userService: userService, + } +} + +func (h *UserHandler) Register(c *gin.Context) { + var req struct { + Email string `json:"email" binding:"required,email"` + Password string `json:"password" binding:"required,min=8"` + Name string `json:"name" binding:"required"` + } + + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + user, err := h.userService.Register(req.Email, req.Password, req.Name) + if err != nil { + if err == domain.ErrEmailAlreadyTaken { + c.JSON(http.StatusConflict, gin.H{"error": "Email already taken"}) + return + } + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create user"}) + return + } + + c.JSON(http.StatusCreated, gin.H{ + "id": user.ID, + "email": user.Email, + "name": user.Name, + }) +} + +func (h *UserHandler) Login(c *gin.Context) { + var req struct { + Email string `json:"email" binding:"required"` + Password string `json:"password" binding:"required"` + } + + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + user, err := h.userService.Login(req.Email, req.Password) + if err != nil { + c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid credentials"}) + return + } + + token, err := generateToken(user) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to generate token"}) + return + } + + c.JSON(http.StatusOK, gin.H{ + "token": token, + "user": gin.H{ + "id": user.ID, + "email": user.Email, + "name": user.Name, + }, + }) +} +``` + + +## Configuration Management + + +```go !! go +// internal/config/config.go +package config + +import ( + "time" + + "github.com/spf13/viper" +) + +type Config struct { + Server ServerConfig + Database DatabaseConfig + Redis RedisConfig + Auth AuthConfig + Log LogConfig +} + +type ServerConfig struct { + Port int `mapstructure:"port"` + ReadTimeout time.Duration `mapstructure:"read_timeout"` + WriteTimeout time.Duration `mapstructure:"write_timeout"` + ShutdownTimeout time.Duration `mapstructure:"shutdown_timeout"` +} + +type DatabaseConfig struct { + Host string `mapstructure:"host"` + Port int `mapstructure:"port"` + User string `mapstructure:"user"` + Password string `mapstructure:"password"` + DBName string `mapstructure:"dbname"` + SSLMode string `mapstructure:"sslmode"` + MaxOpenConns int `mapstructure:"max_open_conns"` + MaxIdleConns int `mapstructure:"max_idle_conns"` + ConnMaxLifetime time.Duration `mapstructure:"conn_max_lifetime"` +} + +type AuthConfig struct { + JWTSecret string `mapstructure:"jwt_secret"` + JWTExpiration time.Duration `mapstructure:"jwt_expiration"` +} + +func Load(path string) (*Config, error) { + viper.SetConfigName("config") + viper.SetConfigType("yaml") + viper.AddConfigPath(path) + viper.AddConfigPath(".") + viper.AddConfigPath("/etc/myapp/") + + // Set defaults + setDefaults() + + // Read environment variables + viper.AutomaticEnv() + viper.SetEnvPrefix("APP") + + // Read config file + if err := viper.ReadInConfig(); err != nil { + return nil, err + } + + var config Config + if err := viper.Unmarshal(&config); err != nil { + return nil, err + } + + return &config, nil +} + +func setDefaults() { + viper.SetDefault("server.port", 8080) + viper.SetDefault("server.read_timeout", 10*time.Second) + viper.SetDefault("server.write_timeout", 10*time.Second) + viper.SetDefault("server.shutdown_timeout", 30*time.Second) + + viper.SetDefault("database.max_open_conns", 25) + viper.SetDefault("database.max_idle_conns", 5) + viper.SetDefault("database.conn_max_lifetime", 5*time.Minute) + + viper.SetDefault("auth.jwt_expiration", 24*time.Hour) +} +``` + + +## Dependency Injection + + +```java !! java +// Java: Spring DI +@Configuration +public class AppConfig { + + @Bean + public UserRepository userRepository(DataSource dataSource) { + return new JdbcUserRepository(dataSource); + } + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Bean + public UserService userService( + UserRepository userRepository, + PasswordEncoder passwordEncoder) { + return new UserServiceImpl(userRepository, passwordEncoder); + } + + @Bean + public UserRestController userController(UserService userService) { + return new UserRestController(userService); + } +} +``` + +```go !! go +// Go: Wire (compile-time dependency injection) +// cmd/myapp/wire.go +//go:build wireinject +// +build wireinject + +package main + +import ( + "github.com/google/wire" + "myapp/internal/api/handler" + "myapp/internal/repository" + "myapp/internal/service" + "myapp/pkg/logger" +) + +func InitializeApplication(cfg *config.Config) (*Application, error) { + wire.Build( + // Providers + logger.NewLogger, + repository.NewUserRepository, + service.NewUserService, + handler.NewUserHandler, + + // Application + NewApplication, + ) + return &Application{}, nil +} + +// cmd/myapp/wire_gen.go (generated) +// +build !wireinject + +func InitializeApplication(cfg *config.Config) (*Application, error) { + log := logger.NewLogger(cfg.Log) + userRepo := repository.NewUserRepository(cfg.Database, log) + userService := service.NewUserService(userRepo, log) + userHandler := handler.NewUserHandler(userService) + application := NewApplication(cfg, userHandler, log) + return application, nil +} + +// cmd/myapp/main.go +package main + +func main() { + cfg, err := config.Load("./configs") + if err != nil { + log.Fatal("Failed to load config", err) + } + + app, err := InitializeApplication(cfg) + if err != nil { + log.Fatal("Failed to initialize application", err) + } + + if err := app.Run(); err != nil { + log.Fatal("Application error", err) + } +} + +// Or use Fx (runtime dependency injection) +import "go.uber.org/fx" + +func main() { + fx.New( + fx.Provide( + config.Load, + logger.NewLogger, + repository.NewUserRepository, + service.NewUserService, + handler.NewUserHandler, + ), + fx.Invoke(RegisterHandlers), + fx.Invoke(func(app *Application) { + app.Run() + }), + ).Run() +} +``` + + +--- + +### Practice Questions: +1. Why does Go favor explicit dependency injection over frameworks? +2. How does Go's clean architecture differ from Java's? +3. What are the advantages of the standard Go project layout? +4. When should you use Wire vs Fx for dependency injection? + +### Project Ideas: +- Build a complete REST API with clean architecture +- Implement authentication and authorization +- Set up logging and monitoring +- Create a reusable project template + +### Next Steps: +- Learn best practices for Go applications +- Build production-ready systems +- Explore advanced topics diff --git a/content/docs/java2go/module-18-real-world.zh-cn.mdx b/content/docs/java2go/module-18-real-world.zh-cn.mdx new file mode 100644 index 0000000..e61593d --- /dev/null +++ b/content/docs/java2go/module-18-real-world.zh-cn.mdx @@ -0,0 +1,199 @@ +--- +title: "模块 18:真实世界项目架构" +--- + +本模块探索 Go 中的真实世界项目架构模式,与 Java 企业应用程序进行比较。 + +## 项目结构 + +**Java 企业结构:** +- 分层架构(controller、service、repository) +- 针对不同关注点的单独模块 +- 大量使用依赖注入 +- 配置驱动 + +**Go 项目结构:** +- 实用的、领域驱动的组织 +- 基于接口的设计 +- 最少的魔法,显式的依赖 +- 代码组织优于框架约定 + + +``` +# Java: Spring Boot 多模块项目 +myapp/ +├── pom.xml +├── myapp-api/ +├── myapp-service/ +├── myapp-repository/ +├── myapp-common/ +└── myapp-integration-tests/ +``` + +``` +# Go: 标准项目布局 +myapp/ +├── cmd/ +│ └── myapp/ +│ └── main.go # 应用程序入口点 +├── internal/ +│ ├── api/ +│ │ ├── handler/ +│ │ ├── middleware/ +│ │ └── router.go +│ ├── service/ +│ ├── repository/ +│ ├── domain/ +│ └── config/ +├── pkg/ # 公共库 +├── api/ +│ └── openapi/ # API 定义 +├── migrations/ # 数据库迁移 +├── scripts/ # 构建和部署脚本 +├── configs/ # 配置文件 +├── go.mod +├── Dockerfile +└── Makefile +``` + + +## Go 中的清洁架构 + + +```go !! go +// internal/domain/user.go +package domain + +import ( + "errors" + "time" +) + +var ( + ErrUserNotFound = errors.New("user not found") + ErrInvalidEmail = errors.New("invalid email") + ErrEmailAlreadyTaken = errors.New("email already taken") +) + +type User struct { + ID uint + Email string + Name string + Password string + Active bool + CreatedAt time.Time + UpdatedAt time.Time +} + +type UserRepository interface { + FindByID(id uint) (*User, error) + FindByEmail(email string) (*User, error) + Create(user *User) error + Update(user *User) error + Delete(id uint) error +} + +type UserService interface { + Register(email, password, name string) (*User, error) + Login(email, password string) (*User, error) +} + +// internal/service/user_service.go +package service + +type userServiceImpl struct { + userRepo domain.UserRepository + logger logger.Logger +} + +func NewUserService(userRepo domain.UserRepository, log logger.Logger) domain.UserService { + return &userServiceImpl{ + userRepo: userRepo, + logger: log, + } +} + +func (s *userServiceImpl) Register(email, password, name string) (*domain.User, error) { + // 验证邮箱 + if email == "" { + return nil, domain.ErrInvalidEmail + } + + // 检查邮箱是否存在 + exists, err := s.userRepo.ExistsByEmail(email) + if err != nil { + return nil, err + } + + if exists { + return nil, domain.ErrEmailAlreadyTaken + } + + // 创建用户 + user := &domain.User{ + Email: email, + Name: name, + Password: hashedPassword, + Active: true, + } + + if err := s.userRepo.Create(user); err != nil { + return nil, err + } + + return user, nil +} + +// internal/api/handler/user_handler.go +package handler + +type UserHandler struct { + userService domain.UserService +} + +func NewUserHandler(userService domain.UserService) *UserHandler { + return &UserHandler{ + userService: userService, + } +} + +func (h *UserHandler) Register(c *gin.Context) { + var req struct { + Email string `json:"email" binding:"required,email"` + Password string `json:"password" binding:"required,min=8"` + Name string `json:"name" binding:"required"` + } + + user, err := h.userService.Register(req.Email, req.Password, req.Name) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusCreated, gin.H{ + "id": user.ID, + "email": user.Email, + "name": user.Name, + }) +} +``` + + +--- + +### 练习问题: +1. 为什么 Go 偏好显式依赖注入而不是框架? +2. Go 的清洁架构与 Java 的有什么不同? +3. 标准 Go 项目布局有什么优势? +4. 什么时候应该使用 Wire vs Fx 进行依赖注入? + +### 项目想法: +- 使用清洁架构构建完整的 REST API +- 实现身份验证和授权 +- 设置日志和监控 +- 创建可重用的项目模板 + +### 下一步: +- 学习 Go 应用程序的最佳实践 +- 构建生产就绪的系统 +- 探索高级主题 diff --git a/content/docs/java2go/module-18-real-world.zh-tw.mdx b/content/docs/java2go/module-18-real-world.zh-tw.mdx new file mode 100644 index 0000000..0f37f50 --- /dev/null +++ b/content/docs/java2go/module-18-real-world.zh-tw.mdx @@ -0,0 +1,73 @@ +--- +title: "模組 18:真實世界專案架構" +--- + +本模組探索 Go 中的真實世界專案架構模式,與 Java 企業應用程式進行比較。 + +## 專案結構 + +**Java 企業結構:** +- 分層架構(controller、service、repository) +- 針對不同關注點的單獨模組 +- 大量使用依賴注入 +- 配置驅動 + +**Go 專案結構:** +- 實用的、領域驅動的組織 +- 基於介面的設計 +- 最少的魔法,顯式的依賴 +- 程式碼組織優於框架慣例 + + +``` +# Java: Spring Boot 多模組專案 +myapp/ +├── pom.xml +├── myapp-api/ +├── myapp-service/ +├── myapp-repository/ +├── myapp-common/ +└── myapp-integration-tests/ +``` + +``` +# Go: 標準專案佈局 +myapp/ +├── cmd/ +│ └── myapp/ +│ └── main.go # 應用程式進入點 +├── internal/ +│ ├── api/ +│ ├── service/ +│ ├── repository/ +│ ├── domain/ +│ └── config/ +├── pkg/ # 公用函式庫 +├── api/ +│ └── openapi/ # API 定義 +├── migrations/ # 資料庫遷移 +├── scripts/ # 建構和部署腳本 +├── configs/ # 設定檔 +├── go.mod +├── Dockerfile +└── Makefile +``` + + +--- + +### 練習問題: +1. 為什麼 Go 偏好顯式依賴注入而不是框架? +2. Go 的乾淨架構與 Java 的有什麼不同? +3. 標準 Go 專案佈局有什麼優勢? + +### 專案想法: +- 使用乾淨架構建構完整的 REST API +- 實作身分驗證和授權 +- 設定日誌和監控 +- 建立可重用的專案範本 + +### 下一步: +- 學習 Go 應用程式的最佳實踐 +- 建構生產就緒的系統 +- 探索進階主題 diff --git a/content/docs/java2go/module-19-best-practices.mdx b/content/docs/java2go/module-19-best-practices.mdx new file mode 100644 index 0000000..ae363e2 --- /dev/null +++ b/content/docs/java2go/module-19-best-practices.mdx @@ -0,0 +1,615 @@ +--- +title: "Module 19: Best Practices and Idiomatic Go" +--- + +This final module covers best practices and idiomatic Go patterns, comparing them with Java conventions. + +## Code Style and Conventions + +**Java Conventions:** +- CamelCase for classes and methods +- PascalCase for classes, camelCase for methods +- Extensive use of getters/setters +- Annotation-driven configuration +- Heavy use of inheritance + +**Go Conventions:** +- PascalCase for exported, camelCase for unexported +- No getters/setters pattern +- Interface-based design +- Composition over inheritance +- Explicit is better than implicit + + +```java !! java +// Java: Naming conventions +public class UserService { + + private UserRepository userRepository; // camelCase + private static final int MAX_RETRIES = 3; // UPPER_CASE + + @Autowired + public UserService(UserRepository userRepository) { + this.userRepository = userRepository; + } + + public User getUserById(Long userId) { // camelCase + return userRepository.findById(userId) + .orElseThrow(() -> new UserNotFoundException(userId)); + } + + public void createUser(CreateUserRequest request) { // camelCase + User user = new User(); + user.setEmail(request.getEmail()); + user.setName(request.getName()); + userRepository.save(user); + } + + // Getters and setters + public UserRepository getUserRepository() { + return userRepository; + } + + public void setUserRepository(UserRepository userRepository) { + this.userRepository = userRepository; + } +} +``` + +```go !! go +// Go: Naming conventions +package user + +type Service struct { // PascalCase for exported + repo Repository // No "this", clear naming + logger Logger +} + +// PascalCase for exported +func NewService(repo Repository, logger Logger) *Service { + return &Service{ + repo: repo, + logger: logger, + } +} + +// PascalCase for exported +func (s *Service) GetUserByID(id uint) (*User, error) { + return s.repo.FindByID(id) +} + +// No request objects needed, use parameters directly +func (s *Service) CreateUser(email, name string) (*User, error) { + user := &User{ + Email: email, + Name: name, + } + + if err := s.repo.Create(user); err != nil { + return nil, err + } + + return user, nil +} + +// No getters/setters needed +// Exported fields are accessed directly +``` + + +## Error Handling + + +```java !! java +// Java: Exception-based error handling +public class UserService { + + public User getUser(Long id) { + try { + return userRepository.findById(id) + .orElseThrow(() -> new UserNotFoundException(id)); + } catch (DataAccessException e) { + throw new ServiceException("Failed to access database", e); + } + } + + public void createUser(CreateUserRequest request) { + try { + if (userRepository.existsByEmail(request.getEmail())) { + throw new DuplicateEmailException(request.getEmail()); + } + + User user = new User(); + user.setEmail(request.getEmail()); + userRepository.save(user); + + } catch (DataIntegrityViolationException e) { + throw new ServiceException("Data integrity violation", e); + } + } + + // Checked exceptions + public void exportUsers(OutputStream out) throws IOException { + // Write to output stream + } +} +``` + +```go !! go +// Go: Error return values +package user + +import ( + "errors" + "fmt" +) + +var ( + ErrUserNotFound = errors.New("user not found") + ErrDuplicateEmail = errors.New("email already exists") + ErrInvalidInput = errors.New("invalid input") +) + +func (s *Service) GetUser(id uint) (*User, error) { + user, err := s.repo.FindByID(id) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, fmt.Errorf("%w: %d", ErrUserNotFound, id) + } + return nil, fmt.Errorf("failed to find user: %w", err) + } + + return user, nil +} + +func (s *Service) CreateUser(email, name string) (*User, error) { + // Validate input + if email == "" || name == "" { + return nil, ErrInvalidInput + } + + // Check for duplicate + exists, err := s.repo.ExistsByEmail(email) + if err != nil { + return nil, fmt.Errorf("failed to check email: %w", err) + } + + if exists { + return nil, fmt.Errorf("%w: %s", ErrDuplicateEmail, email) + } + + // Create user + user := &User{ + Email: email, + Name: name, + } + + if err := s.repo.Create(user); err != nil { + return nil, fmt.Errorf("failed to create user: %w", err) + } + + return user, nil +} + +// Wrap errors with context +func (s *Service) ProcessUser(id uint) error { + user, err := s.GetUser(id) + if err != nil { + return fmt.Errorf("process user: %w", err) + } + + // Process user + return nil +} + +// Custom error types +type ValidationError struct { + Field string + Message string +} + +func (e *ValidationError) Error() string { + return fmt.Sprintf("validation failed for field '%s': %s", e.Field, e.Message) +} + +// Usage +if email == "" { + return &ValidationError{Field: "email", Message: "required"} +} +``` + + +## Concurrency Patterns + + +```java !! java +// Java: Thread-based concurrency +@Service +public class UserService { + + @Async + public CompletableFuture getUserAsync(Long id) { + return CompletableFuture.supplyAsync(() -> { + return userRepository.findById(id) + .orElse(null); + }); + } + + public List getUsersBatch(List ids) { + // Parallel stream + return ids.parallelStream() + .map(userRepository::findById) + .filter(Optional::isPresent) + .map(Optional::get) + .collect(Collectors.toList()); + } + + // Synchronized method + public synchronized void updateUser(User user) { + userRepository.save(user); + } + + // Thread pool + private final ExecutorService executor = Executors.newFixedThreadPool(10); + + public void processUsers(List users) { + users.forEach(user -> { + executor.submit(() -> processUser(user)); + }); + } +} +``` + +```go !! go +// Go: Goroutine-based concurrency +package user + +import ( + "sync" + "context" +) + +func (s *Service) GetUserAsync(ctx context.Context, id uint) (<-chan *User, <-chan error) { + userCh := make(chan *User, 1) + errCh := make(chan error, 1) + + go func() { + defer close(userCh) + defer close(errCh) + + user, err := s.repo.FindByID(id) + if err != nil { + errCh <- err + return + } + + select { + case userCh <- user: + case <-ctx.Done(): + errCh <- ctx.Err() + } + }() + + return userCh, errCh +} + +func (s *Service) GetUsersBatch(ctx context.Context, ids []uint) ([]*User, error) { + var ( + wg sync.WaitGroup + mu sync.Mutex + users = make([]*User, 0, len(ids)) + errors = make([]error, 0) + ) + + for _, id := range ids { + wg.Add(1) + go func(id uint) { + defer wg.Done() + + user, err := s.repo.FindByID(id) + if err != nil { + mu.Lock() + errors = append(errors, err) + mu.Unlock() + return + } + + mu.Lock() + users = append(users, user) + mu.Unlock() + }(id) + } + + wg.Wait() + + if len(errors) > 0 { + return nil, fmt.Errorf("failed to get some users: %v", errors) + } + + return users, nil +} + +// Worker pool pattern +func (s *Service) ProcessUsers(users []*User) error { + const numWorkers = 10 + + jobs := make(chan *User, len(users)) + results := make(chan error, len(users)) + + // Start workers + var wg sync.WaitGroup + for i := 0; i < numWorkers; i++ { + wg.Add(1) + go func() { + defer wg.Done() + for user := range jobs { + results <- s.processUser(user) + } + }() + } + + // Send jobs + for _, user := range users { + jobs <- user + } + close(jobs) + + // Wait for completion + go func() { + wg.Wait() + close(results) + }() + + // Collect results + var errors []error + for err := range results { + if err != nil { + errors = append(errors, err) + } + } + + if len(errors) > 0 { + return fmt.Errorf("failed to process some users: %v", errors) + } + + return nil +} +``` + + +## Interface Design + + +```java !! java +// Java: Interface-heavy design +public interface UserRepository extends JpaRepository { + Optional findByEmail(String email); + List findByNameContainingIgnoreCase(String name); +} + +public interface UserService { + User createUser(CreateUserRequest request); + Optional getUserById(Long id); + List searchUsers(String keyword); + void deleteUser(Long id); +} + +@Service +public class UserServiceImpl implements UserService { + + @Autowired + private UserRepository userRepository; + + @Override + public User createUser(CreateUserRequest request) { + // Implementation + } +} +``` + +```go !! go +// Go: Accept interfaces, return structs +package user + +// Small, focused interfaces +type Repository interface { + FindByID(id uint) (*User, error) + FindByEmail(email string) (*User, error) + Create(user *User) error + Update(user *User) error + Delete(id uint) error +} + +// Service is a concrete struct +type Service struct { + repo Repository + cache Cache + logger Logger +} + +// Accept interfaces +func NewService(repo Repository, cache Cache, logger Logger) *Service { + return &Service{ + repo: repo, + cache: cache, + logger: logger, + } +} + +// No need for separate interface unless multiple implementations +func (s *Service) CreateUser(email, name string) (*User, error) { + // Implementation +} + +// Define interface where it's used (consumer side) +type UserFinder interface { + FindByID(id uint) (*User, error) +} + +func ProcessUser(id uint, finder UserFinder) error { + user, err := finder.FindByID(id) + if err != nil { + return err + } + + // Process user + return nil +} +``` + + +## Testing Best Practices + + +```go !! go +// Table-driven tests +func TestService_CreateUser(t *testing.T) { + tests := []struct { + name string + email string + nameStr string + setup func(*MockRepository) + wantErr error + }{ + { + name: "successful creation", + email: "test@example.com", + nameStr: "Test User", + setup: func(m *MockRepository) { + m.On("ExistsByEmail", "test@example.com").Return(false) + m.On("Create", mock.Anything).Return(nil) + }, + wantErr: nil, + }, + { + name: "duplicate email", + email: "test@example.com", + nameStr: "Test User", + setup: func(m *MockRepository) { + m.On("ExistsByEmail", "test@example.com").Return(true) + }, + wantErr: ErrDuplicateEmail, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mock := new(MockRepository) + tt.setup(mock) + + svc := NewService(mock, &mockLogger{}) + + user, err := svc.CreateUser(tt.email, tt.nameStr) + + if tt.wantErr != nil { + assert.Error(t, err) + assert.ErrorIs(t, err, tt.wantErr) + } else { + assert.NoError(t, err) + assert.NotNil(t, user) + } + + mock.AssertExpectations(t) + }) + } +} + +// Test helpers +func setupTestDB(t *testing.T) *gorm.DB { + db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) + require.NoError(t, err) + + err = db.AutoMigrate(&User{}) + require.NoError(t, err) + + return db +} + +func cleanupTestDB(t *testing.T, db *gorm.DB) { + sqlDB, _ := db.DB() + sqlDB.Close() +} +``` + + +## Performance Tips + + +```go !! go +// Pre-allocate slices +func BuildUserSlice(count int) []*User { + users := make([]*User, 0, count) // Pre-allocate capacity + for i := 0; i < count; i++ { + users = append(users, &User{ID: uint(i)}) + } + return users +} + +// Reuse buffers with sync.Pool +var bufferPool = sync.Pool{ + New: func() interface{} { + return new(bytes.Buffer) + }, +} + +func ProcessData(data []byte) ([]byte, error) { + buf := bufferPool.Get().(*bytes.Buffer) + defer func() { + buf.Reset() + bufferPool.Put(buf) + }() + + // Use buffer + buf.Write(data) + // ... process + + return buf.Bytes(), nil +} + +// Avoid string allocations in loops +// Bad: +result := "" +for _, s := range items { + result += s // New allocation each iteration +} + +// Good: +var builder strings.Builder +builder.Grow(len(items) * 10) +for _, s := range items { + builder.WriteString(s) +} +result := builder.String() +``` + + +--- + +### Summary of Go Best Practices: + +1. **Keep it simple** - Avoid over-engineering +2. **Errors are values** - Handle them explicitly +3. **Use composition** - Prefer over inheritance +4. **Accept interfaces, return structs** - Define interfaces where used +5. **Less is more** - Minimal code that works is better +6. **Test thoroughly** - Table-driven tests are idiomatic +7. **Profile before optimizing** - Measure first +8. **Use goroutines wisely** - Don't create too many +9. **Handle resources properly** - defer cleanup +10. **Follow standard conventions** - Use go fmt and standard layouts + +### Congratulations! + +You've completed the Java to Go learning path! You now have: +- Understanding of Go's syntax and features +- Knowledge of Go's ecosystem and tools +- Experience with Go's patterns and best practices +- Ability to build production-ready Go applications + +Continue practicing and building real projects to master Go! + +### Next Steps: +- Build real projects in Go +- Contribute to open-source Go projects +- Explore advanced Go topics +- Join the Go community diff --git a/content/docs/java2go/module-19-best-practices.zh-cn.mdx b/content/docs/java2go/module-19-best-practices.zh-cn.mdx new file mode 100644 index 0000000..b0b297a --- /dev/null +++ b/content/docs/java2go/module-19-best-practices.zh-cn.mdx @@ -0,0 +1,461 @@ +--- +title: "模块 19:最佳实践和惯用 Go" +--- + +最后一个模块涵盖 Go 的最佳实践和惯用模式,与 Java 约定进行比较。 + +## 代码风格和约定 + +**Java 约定:** +- 类和方法使用驼峰命名 +- 类使用 PascalCase,方法使用 camelCase +- 大量使用 getter/setter +- 注解驱动的配置 +- 大量使用继承 + +**Go 约定:** +- 导出使用 PascalCase,未导出使用 camelCase +- 无 getter/setter 模式 +- 基于接口的设计 +- 组合优于继承 +- 显式优于隐式 + + +```java !! java +// Java: 命名约定 +public class UserService { + + private UserRepository userRepository; + + @Autowired + public UserService(UserRepository userRepository) { + this.userRepository = userRepository; + } + + public User getUserById(Long userId) { + return userRepository.findById(userId) + .orElseThrow(() -> new UserNotFoundException(userId)); + } + + // Getters and setters + public UserRepository getUserRepository() { + return userRepository; + } +} +``` + +```go !! go +// Go: 命名约定 +package user + +type Service struct { // 导出使用 PascalCase + repo Repository // 无 "this",清晰的命名 + logger Logger +} + +// 导出使用 PascalCase +func NewService(repo Repository, logger Logger) *Service { + return &Service{ + repo: repo, + logger: logger, + } +} + +func (s *Service) GetUserByID(id uint) (*User, error) { + return s.repo.FindByID(id) +} + +// 不需要 getter/setter +// 导出的字段可以直接访问 +``` + + +## 错误处理 + + +```java !! java +// Java: 基于异常的错误处理 +public class UserService { + + public User getUser(Long id) { + try { + return userRepository.findById(id) + .orElseThrow(() -> new UserNotFoundException(id)); + } catch (DataAccessException e) { + throw new ServiceException("Failed to access database", e); + } + } +} +``` + +```go !! go +// Go: 错误返回值 +package user + +import ( + "errors" + "fmt" +) + +var ( + ErrUserNotFound = errors.New("user not found") + ErrDuplicateEmail = errors.New("email already exists") + ErrInvalidInput = errors.New("invalid input") +) + +func (s *Service) GetUser(id uint) (*User, error) { + user, err := s.repo.FindByID(id) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, fmt.Errorf("%w: %d", ErrUserNotFound, id) + } + return nil, fmt.Errorf("failed to find user: %w", err) + } + + return user, nil +} + +// 使用上下文包装错误 +func (s *Service) ProcessUser(id uint) error { + user, err := s.GetUser(id) + if err != nil { + return fmt.Errorf("process user: %w", err) + } + + // 处理用户 + return nil +} + +// 自定义错误类型 +type ValidationError struct { + Field string + Message string +} + +func (e *ValidationError) Error() string { + return fmt.Sprintf("validation failed for field '%s': %s", e.Field, e.Message) +} +``` + + +## 并发模式 + + +```java !! java +// Java: 基于线程的并发 +@Service +public class UserService { + + @Async + public CompletableFuture getUserAsync(Long id) { + return CompletableFuture.supplyAsync(() -> { + return userRepository.findById(id).orElse(null); + }); + } + + // 线程池 + private final ExecutorService executor = Executors.newFixedThreadPool(10); + + public void processUsers(List users) { + users.forEach(user -> { + executor.submit(() -> processUser(user)); + }); + } +} +``` + +```go !! go +// Go: 基于 goroutine 的并发 +package user + +import ( + "sync" + "context" +) + +func (s *Service) GetUsersBatch(ctx context.Context, ids []uint) ([]*User, error) { + var ( + wg sync.WaitGroup + mu sync.Mutex + users = make([]*User, 0, len(ids)) + errors = make([]error, 0) + ) + + for _, id := range ids { + wg.Add(1) + go func(id uint) { + defer wg.Done() + + user, err := s.repo.FindByID(id) + if err != nil { + mu.Lock() + errors = append(errors, err) + mu.Unlock() + return + } + + mu.Lock() + users = append(users, user) + mu.Unlock() + }(id) + } + + wg.Wait() + + if len(errors) > 0 { + return nil, fmt.Errorf("failed to get some users: %v", errors) + } + + return users, nil +} + +// Worker pool 模式 +func (s *Service) ProcessUsers(users []*User) error { + const numWorkers = 10 + + jobs := make(chan *User, len(users)) + results := make(chan error, len(users)) + + // 启动 workers + var wg sync.WaitGroup + for i := 0; i < numWorkers; i++ { + wg.Add(1) + go func() { + defer wg.Done() + for user := range jobs { + results <- s.processUser(user) + } + }() + } + + // 发送任务 + for _, user := range users { + jobs <- user + } + close(jobs) + + // 等待完成 + go func() { + wg.Wait() + close(results) + }() + + // 收集结果 + var errors []error + for err := range results { + if err != nil { + errors = append(errors, err) + } + } + + if len(errors) > 0 { + return fmt.Errorf("failed to process some users: %v", errors) + } + + return nil +} +``` + + +## 接口设计 + + +```java !! java +// Java: 重接口的设计 +public interface UserRepository extends JpaRepository { + Optional findByEmail(String email); +} + +public interface UserService { + User createUser(CreateUserRequest request); +} + +@Service +public class UserServiceImpl implements UserService { + // 实现 +} +``` + +```go !! go +// Go: 接受接口,返回结构体 +package user + +// 小而专注的接口 +type Repository interface { + FindByID(id uint) (*User, error) + FindByEmail(email string) (*User, error) + Create(user *User) error +} + +// Service 是具体结构体 +type Service struct { + repo Repository + cache Cache + logger Logger +} + +// 接受接口 +func NewService(repo Repository, cache Cache, logger Logger) *Service { + return &Service{ + repo: repo, + cache: cache, + logger: logger, + } +} + +// 在使用的地方定义接口(消费者侧) +type UserFinder interface { + FindByID(id uint) (*User, error) +} + +func ProcessUser(id uint, finder UserFinder) error { + user, err := finder.FindByID(id) + if err != nil { + return err + } + + // 处理用户 + return nil +} +``` + + +## 测试最佳实践 + + +```go !! go +// 表驱动测试 +func TestService_CreateUser(t *testing.T) { + tests := []struct { + name string + email string + nameStr string + setup func(*MockRepository) + wantErr error + }{ + { + name: "successful creation", + email: "test@example.com", + nameStr: "Test User", + setup: func(m *MockRepository) { + m.On("ExistsByEmail", "test@example.com").Return(false) + m.On("Create", mock.Anything).Return(nil) + }, + wantErr: nil, + }, + { + name: "duplicate email", + email: "test@example.com", + nameStr: "Test User", + setup: func(m *MockRepository) { + m.On("ExistsByEmail", "test@example.com").Return(true) + }, + wantErr: ErrDuplicateEmail, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mock := new(MockRepository) + tt.setup(mock) + + svc := NewService(mock, &mockLogger{}) + + user, err := svc.CreateUser(tt.email, tt.nameStr) + + if tt.wantErr != nil { + assert.Error(t, err) + assert.ErrorIs(t, err, tt.wantErr) + } else { + assert.NoError(t, err) + assert.NotNil(t, user) + } + + mock.AssertExpectations(t) + }) + } +} +``` + + +## 性能技巧 + + +```go !! go +// 预分配切片 +func BuildUserSlice(count int) []*User { + users := make([]*User, 0, count) // 预分配容量 + for i := 0; i < count; i++ { + users = append(users, &User{ID: uint(i)}) + } + return users +} + +// 使用 sync.Pool 重用缓冲区 +var bufferPool = sync.Pool{ + New: func() interface{} { + return new(bytes.Buffer) + }, +} + +func ProcessData(data []byte) ([]byte, error) { + buf := bufferPool.Get().(*bytes.Buffer) + defer func() { + buf.Reset() + bufferPool.Put(buf) + }() + + // 使用缓冲区 + buf.Write(data) + // ... 处理 + + return buf.Bytes(), nil +} + +// 避免循环中的字符串分配 +// 不好: +result := "" +for _, s := range items { + result += s // 每次迭代新分配 +} + +// 好: +var builder strings.Builder +builder.Grow(len(items) * 10) +for _, s := range items { + builder.WriteString(s) +} +result := builder.String() +``` + + +--- + +### Go 最佳实践总结: + +1. **保持简单** - 避免过度工程 +2. **错误是值** - 显式处理它们 +3. **使用组合** - 优于继承 +4. **接受接口,返回结构体** - 在使用处定义接口 +5. **少即是多** - 有效的极简代码更好 +6. **彻底测试** - 表驱动测试是惯用的 +7. **优化前先分析** - 先测量 +8. **明智使用 goroutine** - 不要创建太多 +9. **正确处理资源** - defer 清理 +10. **遵循标准约定** - 使用 go fmt 和标准布局 + +### 恭喜! + +您已完成 Java 到 Go 的学习路径!您现在拥有: +- 对 Go 的语法和功能的理解 +- Go 生态系统和工具的知识 +- Go 的模式和最佳实践的经验 +- 构建生产就绪的 Go 应用程序的能力 + +继续练习和构建真实项目来掌握 Go! + +### 下一步: +- 用 Go 构建真实项目 +- 为开源 Go 项目做贡献 +- 探索高级 Go 主题 +- 加入 Go 社区 diff --git a/content/docs/java2go/module-19-best-practices.zh-tw.mdx b/content/docs/java2go/module-19-best-practices.zh-tw.mdx new file mode 100644 index 0000000..fc229a9 --- /dev/null +++ b/content/docs/java2go/module-19-best-practices.zh-tw.mdx @@ -0,0 +1,146 @@ +--- +title: "模組 19:最佳實踐和慣用 Go" +--- + +最後一個模組涵蓋 Go 的最佳實踐和慣用模式,與 Java 約定進行比較。 + +## 程式碼風格和約定 + +**Java 約定:** +- 類別和方法使用駝峰命名 +- 類別使用 PascalCase,方法使用 camelCase +- 大量使用 getter/setter +- 註解驅動的設定 +- 大量使用繼承 + +**Go 約定:** +- 匯出使用 PascalCase,未匯出使用 camelCase +- 無 getter/setter 模式 +- 基於介面的設計 +- 組合優於繼承 +- 顯式優於隱式 + + +```java !! java +// Java: 命名約定 +public class UserService { + + private UserRepository userRepository; + + @Autowired + public UserService(UserRepository userRepository) { + this.userRepository = userRepository; + } + + public User getUserById(Long userId) { + return userRepository.findById(userId) + .orElseThrow(() -> new UserNotFoundException(userId)); + } +} +``` + +```go !! go +// Go: 命名約定 +package user + +type Service struct { // 匯出使用 PascalCase + repo Repository + logger Logger +} + +func NewService(repo Repository, logger Logger) *Service { + return &Service{ + repo: repo, + logger: logger, + } +} + +func (s *Service) GetUserByID(id uint) (*User, error) { + return s.repo.FindByID(id) +} + +// 不需要 getter/setter +// 匯出的欄位可以直接存取 +``` + + +## 錯誤處理 + + +```java !! java +// Java: 基於例外的錯誤處理 +public class UserService { + + public User getUser(Long id) { + try { + return userRepository.findById(id) + .orElseThrow(() -> new UserNotFoundException(id)); + } catch (DataAccessException e) { + throw new ServiceException("Failed to access database", e); + } + } +} +``` + +```go !! go +// Go: 錯誤傳回值 +package user + +import ( + "errors" + "fmt" +) + +var ( + ErrUserNotFound = errors.New("user not found") + ErrDuplicateEmail = errors.New("email already exists") +) + +func (s *Service) GetUser(id uint) (*User, error) { + user, err := s.repo.FindByID(id) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, fmt.Errorf("%w: %d", ErrUserNotFound, id) + } + return nil, fmt.Errorf("failed to find user: %w", err) + } + + return user, nil +} + +// 使用上下文包裝錯誤 +func (s *Service) ProcessUser(id uint) error { + user, err := s.GetUser(id) + if err != nil { + return fmt.Errorf("process user: %w", err) + } + + return nil +} +``` + + +--- + +### Go 最佳實踐總結: + +1. **保持簡單** - 避免過度工程 +2. **錯誤是值** - 顯式處理它們 +3. **使用組合** - 優於繼承 +4. **接受介面,傳回結構體** - 在使用處定義介面 +5. **少即是多** - 有效的極簡程式碼更好 +6. **徹底測試** - 表驅動測試是慣用的 +7. **優化前先分析** - 先測量 +8. **明智使用 goroutine** - 不要建立太多 +9. **正確處理資源** - defer 清理 +10. **遵循標準約定** - 使用 go fmt 和標準佈局 + +### 恭喜! + +您已完成 Java 到 Go 的學習路徑! + +### 下一步: +- 用 Go 建構真實專案 +- 為開源 Go 專案貢獻 +- 探索進階 Go 主題 +- 加入 Go 社群 From 611b20be4b15386eb4fb26a21c875e64b6d5980b Mon Sep 17 00:00:00 2001 From: sunguangbiao <2991552132@qq.com> Date: Sun, 28 Dec 2025 12:04:56 +0800 Subject: [PATCH 7/7] feat: enable comparison mode in UniversalEditor and add Java to Go learning path with comprehensive modules and translations in Chinese and Taiwanese --- components/universal-editor.tsx | 2 +- content/docs/java2go/index.mdx | 316 +++++++++ content/docs/java2go/index.zh-cn.mdx | 316 +++++++++ content/docs/java2go/index.zh-tw.mdx | 316 +++++++++ .../module-05-arrays-collections.zh-cn.mdx | 632 +++++++++--------- .../docs/java2js/module-06-objects.zh-cn.mdx | 454 ++++++------- .../docs/java2js/module-07-classes.zh-cn.mdx | 452 ++++++------- .../java2js/module-08-prototypes.zh-cn.mdx | 534 +++++++-------- .../java2js/module-09-this-context.zh-cn.mdx | 474 ++++++------- .../docs/java2js/module-15-tooling.zh-cn.mdx | 112 ++-- .../module-16-testing-debugging.zh-cn.mdx | 192 +++--- .../java2js/module-17-frameworks.zh-cn.mdx | 132 ++-- .../docs/java2js/module-18-pitfalls.zh-cn.mdx | 144 ++-- .../java2js/module-19-real-projects.zh-cn.mdx | 178 ++--- 14 files changed, 2601 insertions(+), 1653 deletions(-) create mode 100644 content/docs/java2go/index.mdx create mode 100644 content/docs/java2go/index.zh-cn.mdx create mode 100644 content/docs/java2go/index.zh-tw.mdx diff --git a/components/universal-editor.tsx b/components/universal-editor.tsx index 5855de6..fa7813c 100644 --- a/components/universal-editor.tsx +++ b/components/universal-editor.tsx @@ -711,7 +711,7 @@ export default function UniversalEditor(params: UniversalEditorProps) { theme = 'auto', readOnly = false, showOutput = true, - compare = false, + compare = true, code = [], height = 300, preloadLibraries = [], diff --git a/content/docs/java2go/index.mdx b/content/docs/java2go/index.mdx new file mode 100644 index 0000000..d8ce5ee --- /dev/null +++ b/content/docs/java2go/index.mdx @@ -0,0 +1,316 @@ +--- +title: "Java → Go: Complete Learning Path" +description: "Master Go programming starting from your Java knowledge. Learn simplicity, concurrency, and performance through comparative examples." +--- + +# Java → Go: Complete Learning Path + +Welcome to the **Java → Go** learning path! This comprehensive curriculum is designed specifically for Java developers who want to master Go programming language. + +## Why Learn Go as a Java Developer? + +As a Java developer, you possess strong programming fundamentals, object-oriented design skills, and enterprise development experience. Go builds on this foundation while introducing a refreshingly different approach to software development that will make you a more versatile programmer. + +### Key Advantages of Go + + +```java !! java +// Java: JVM startup overhead +public class Application { + public static void main(String[] args) { + System.out.println("Starting application..."); + // JVM warm-up time: 1-3 seconds + // JIT compilation overhead + } +} +``` + +```go !! go +// Go: Instant startup +package main + +import "fmt" + +func main() { + fmt.Println("Starting application...") + // No warm-up needed + // Runs instantly +} +``` + + +### What Makes Go Different? + +1. **Simplicity Over Complexity**: Go has only 25 keywords (vs Java's 50+), no classes, no inheritance, and a minimal syntax that can be learned in days rather than months. + +2. **Lightweight Concurrency**: Go's goroutines are lightweight threads managed by the Go runtime, allowing you to run millions of concurrent operations efficiently. + +3. **Fast Compilation**: Go compiles in seconds, enabling rapid development cycles and immediate feedback. + +4. **Single Binary Deployment**: Deploy a single static binary without needing JVM installation or complex dependency management. + +5. **Modern Tooling**: Built-in formatting, testing, profiling, and benchmarking tools come with the standard installation. + +## What You'll Learn + +This learning path consists of **20 comprehensive modules** that take you from Go beginner to confident practitioner. + +### Module Structure + +1. **Modules 0-1**: Getting Started + - Go language introduction and philosophy + - Syntax comparison and key differences from Java + - Environment setup and basic tooling + +2. **Modules 2-4**: Object-Oriented Go + - Understanding Go's approach to OOP without classes + - Structs and composition vs inheritance + - Interfaces and type system + - Package organization and visibility + +3. **Modules 5-7**: Concurrency & Parallelism + - Goroutines vs Java threads + - Channels for communication + - Select statements and patterns + - Concurrent design patterns + +4. **Modules 8-9**: Web Development + - Building web services without Spring + - HTTP servers and routing + - JSON handling and REST APIs + - Middleware and request handling + +5. **Modules 10-12**: Testing & Quality + - Go's testing framework + - Table-driven tests + - Benchmarking and profiling + - Common pitfalls and how to avoid them + - Idiomatic Go patterns + +6. **Modules 13-15**: Production Readiness + - Performance optimization + - Build tools and deployment + - Error handling best practices + - Go ecosystem and libraries + +7. **Modules 16-17**: Advanced Topics + - Database integration + - Microservices architecture + - Real-world patterns + +8. **Modules 18-19**: Mastery + - Complete real-world project + - Best practices and design patterns + - Production deployment strategies + +## Learning Approach + +### Java-First Teaching + +Every concept in this curriculum is introduced by: +1. **Starting with familiar Java code** - See how you'd solve it in Java +2. **Introducing the Go equivalent** - Understand the mapping between languages +3. **Explaining key differences** - Learn why Go approaches things differently +4. **Practical examples** - Work through real-world scenarios + + +```java !! java +// Java: Exception-based error handling +public class UserService { + public User getUser(String id) throws UserNotFoundException { + User user = database.findUser(id); + if (user == null) { + throw new UserNotFoundException("User not found: " + id); + } + return user; + } +} + +// Callers must remember to catch exceptions +try { + User user = userService.getUser("123"); +} catch (UserNotFoundException e) { + // Handle error +} +``` + +```go !! go +// Go: Explicit error returns +func (s *UserService) GetUser(id string) (User, error) { + user, err := s.database.FindUser(id) + if err != nil { + return User{}, fmt.Errorf("user not found: %s", id) + } + return user, nil +} + +// Compiler forces you to handle errors +user, err := userService.GetUser("123") +if err != nil { + // Handle error +} +``` + + +### Hands-On Practice + +Each module includes: +- **Multiple code comparisons** using our interactive UniversalEditor +- **Practical exercises** to reinforce learning +- **Common pitfalls** and how to avoid them +- **Real-world examples** from production Go code +- **Performance considerations** and optimization tips + +## Prerequisites + +Before starting this learning path, you should have: + +- ✅ **Intermediate Java knowledge**: Comfortable with classes, interfaces, and OOP principles +- ✅ **Understanding of Java ecosystem**: Familiar with Spring, Maven/Gradle, and JVM concepts +- ✅ **Basic programming concepts**: Understanding of variables, loops, and control flow +- ✅ **Terminal familiarity**: Comfortable running commands in a shell +- ✅ **Open mindset**: Willingness to learn a different approach to software development + +No prior systems programming experience required - we'll cover everything you need to know. + +## What Makes This Different from Other Go Tutorials? + +### 1. Comparative Learning Approach +Most Go tutorials assume you're coming from dynamic languages like Python or JavaScript. This path is specifically designed for **Java developers**, addressing the specific challenges and mindset shifts needed when transitioning from a heavy OOP background. + +### 2. Emphasis on Concurrency +Special focus on **goroutines, channels, and concurrent patterns**, areas where Go truly shines compared to Java's threading model. + +### 3. Production-Ready Patterns +Learn not just syntax, but **real-world patterns** and best practices used in production Go applications at companies like Google, Uber, and Dropbox. + +### 4. Enterprise Context +Understand when to use Go vs Java, and how Go fits into enterprise architecture alongside existing Java systems. + +## Time Commitment + +Each module is designed to take approximately **2-4 hours** to complete thoroughly, including: +- Reading and understanding concepts +- Working through code examples +- Completing exercises +- Experimenting with the code + +Total time: **40-80 hours** for the complete learning path. + +## Getting the Most Out of This Course + +### Active Learning Strategy + +1. **Don't just read - code along**: Type out the examples yourself +2. **Experiment**: Modify the code, see what breaks, understand why +3. **Compare implementations**: Think about how you'd solve the same problem in Java +4. **Build things**: Apply concepts to small projects as you learn +5. **Embrace simplicity**: Resist the urge to over-engineer - Go values simplicity + +### Recommended Tools + +- **Go Installation**: We'll cover this in Module 0 +- **IDE**: VS Code with Go extension (recommended) or GoLand +- **Playground**: [Go Playground](https://go.dev/play/) for quick experiments + +## Community & Resources + +Go has a growing and welcoming community. Don't hesitate to ask questions! + +- **Official Go Website**: [go.dev](https://go.dev/) +- **Effective Go**: [go.dev/doc/effective_go](https://go.dev/doc/effective_go) +- **Go by Example**: [gobyexample.com](https://gobyexample.com/) +- **Go Forum**: [forum.golangbridge.org](https://forum.golangbridge.org/) + +## What You'll Be Able to Build + +By the end of this learning path, you'll be able to: + +- ✅ Write **idiomatic Go code** that follows community best practices +- ✅ Build **high-performance concurrent services** with goroutines and channels +- ✅ Create **microservices** that start instantly and deploy as single binaries +- ✅ Develop **CLI tools** with great user experiences +- ✅ Integrate Go services **with existing Java systems** +- ✅ Contribute to **Go open-source projects** +- ✅ Make informed decisions **about when to use Go vs Java** + +## Common Concerns + +### "Isn't Go too simple compared to Java?" + +Go's simplicity is intentional and powerful: +- **Less is more**: Fewer features mean less cognitive overhead +- **Fast onboarding**: New team members can be productive in days +- **Easy maintenance**: Simple code is easier to understand and maintain +- **Focus on business logic**: Spend time solving problems, not fighting the language + +### "Will I still use Java after learning Go?" + +Absolutely! Go and Java serve different purposes: +- **Java**: Large enterprise applications, complex business logic, Android, Big Data +- **Go**: Microservices, cloud-native apps, CLI tools, high-performance network services + +Many companies use **both** - Java for complex business logic, Go for lightweight, high-performance services. + +### "Do I need to unlearn OOP?" + +Not unlearn, but adapt: +- **Composition over inheritance**: Use structs and composition instead of class hierarchies +- **Interfaces are implicit**: No need to explicitly implement interfaces +- **Behavior-focused**: Think about what things do, not what they are + +## Java to Go: Key Mindset Shifts + +### From Frameworks to Standard Library + +**Java**: Spring ecosystem, Hibernate, external libraries +**Go**: Rich standard library, minimal dependencies + +### From Complex to Simple + +**Java**: Abstract factories, builder patterns, heavy DI +**Go**: Direct functions, simple constructors, explicit wiring + +### From Implicit to Explicit + +**Java**: Exceptions, magic frameworks, hidden behavior +**Go**: Error returns, clear code flow, obvious behavior + +### From Heavyweight to Lightweight + +**Java**: JVM startup, memory overhead, slow scaling +**Go**: Instant startup, minimal memory, fast scaling + +## Real-World Applications + +By the end of this course, you'll be ready to build: + +**Microservices** +- Lightweight, fast services +- Easy deployment and scaling +- Single binary deployment + +**API Gateways & Proxies** +- High-performance routing +- Concurrent request handling +- Low memory footprint + +**CLI Tools** +- Developer productivity tools +- Automation scripts +- System utilities + +**Cloud-Native Applications** +- Kubernetes operators +- Docker tooling +- Infrastructure management + +## Let's Get Started! + +Ready to begin your Go journey? Head over to **[Module 0: Go Language Introduction](./module-00-go-introduction)** to understand Go's philosophy and why it's becoming the language of choice for cloud-native development. + +Remember: **The best Go developers are often those who learned Java first and then discovered the power of simplicity.** + +--- + +**Next: [Module 0 - Go Language Introduction](./module-00-go-introduction)** → diff --git a/content/docs/java2go/index.zh-cn.mdx b/content/docs/java2go/index.zh-cn.mdx new file mode 100644 index 0000000..18fe1de --- /dev/null +++ b/content/docs/java2go/index.zh-cn.mdx @@ -0,0 +1,316 @@ +--- +title: "Java → Go:完整学习路径" +description: "从您的 Java 知识出发掌握 Go 编程。通过对比学习理解简洁性、并发性和高性能。" +--- + +# Java → Go:完整学习路径 + +欢迎来到 **Java → Go** 学习路径!这是一个专为 Java 开发者设计的综合性课程,帮助您掌握 Go 编程语言。 + +## 为什么 Java 开发者应该学习 Go? + +作为一名 Java 开发者,您已经具备了扎实的编程基础、面向对象设计技能和企业级开发经验。Go 在这些基础之上,引入了一种截然不同的软件开发方法,将使您成为一名更加全面的多语言程序员。 + +### Go 的核心优势 + + +```java !! java +// Java:JVM 启动开销 +public class Application { + public static void main(String[] args) { + System.out.println("启动应用程序..."); + // JVM 预热时间:1-3 秒 + // JIT 编译开销 + } +} +``` + +```go !! go +// Go:即时启动 +package main + +import "fmt" + +func main() { + fmt.Println("启动应用程序...") + // 无需预热 + // 立即运行 +} +``` + + +### Go 的独特之处 + +1. **简洁胜于复杂**:Go 只有 25 个关键字(Java 有 50+ 个),没有类,没有继承,语法精简,几天即可掌握而非几个月。 + +2. **轻量级并发**:Go 的 goroutine 是由 Go 运行时管理的轻量级线程,让您能够高效地运行数百万个并发操作。 + +3. **快速编译**:Go 几秒钟就能完成编译,实现快速开发周期和即时反馈。 + +4. **单一二进制部署**:无需安装 JVM 或复杂的依赖管理,只需部署一个静态二进制文件。 + +5. **现代化工具**:内置代码格式化、测试、性能分析和基准测试工具。 + +## 您将学到什么 + +本学习路径包含 **20 个综合模块**,将您从 Go 初学者培养为自信的实践者。 + +### 模块结构 + +1. **模块 0-1**:入门开始 + - Go 语言介绍和设计哲学 + - 语法对比和与 Java 的主要差异 + - 环境设置和基础工具 + +2. **模块 2-4**:Go 的面向对象 + - 理解 Go 无类的 OOP 方法 + - 结构体和组合 vs 继承 + - 接口和类型系统 + - 包组织和可见性 + +3. **模块 5-7**:并发与并行 + - Goroutine vs Java 线程 + - 通信通道 + - Select 语句和模式 + - 并发设计模式 + +4. **模块 8-9**:Web 开发 + - 不使用 Spring 构建 Web 服务 + - HTTP 服务器和路由 + - JSON 处理和 REST API + - 中间件和请求处理 + +5. **模块 10-12**:测试与质量 + - Go 测试框架 + - 表格驱动测试 + - 基准测试和性能分析 + - 常见陷阱及避免方法 + - 地道的 Go 模式 + +6. **模块 13-15**:生产就绪 + - 性能优化 + - 构建工具和部署 + - 错误处理最佳实践 + - Go 生态系统和库 + +7. **模块 16-17**:高级主题 + - 数据库集成 + - 微服务架构 + - 真实世界模式 + +8. **模块 18-19**:精通 + - 完整的真实项目 + - 最佳实践和设计模式 + - 生产部署策略 + +## 学习方法 + +### 以 Java 为起点的教学 + +课程中的每个概念都通过以下方式引入: +1. **从熟悉的 Java 代码开始** - 看看您如何在 Java 中解决问题 +2. **介绍 Go 的等价实现** - 理解语言之间的映射关系 +3. **解释关键差异** - 学习 Go 为什么采用不同的方法 +4. **实践示例** - 通过真实场景学习 + + +```java !! java +// Java:基于异常的错误处理 +public class UserService { + public User getUser(String id) throws UserNotFoundException { + User user = database.findUser(id); + if (user == null) { + throw new UserNotFoundException("用户未找到:" + id); + } + return user; + } +} + +// 调用者必须记得捕获异常 +try { + User user = userService.getUser("123"); +} catch (UserNotFoundException e) { + // 处理错误 +} +``` + +```go !! go +// Go:显式错误返回 +func (s *UserService) GetUser(id string) (User, error) { + user, err := s.database.FindUser(id) + if err != nil { + return User{}, fmt.Errorf("用户未找到:%s", id) + } + return user, nil +} + +// 编译器强制您处理错误 +user, err := userService.GetUser("123") +if err != nil { + // 处理错误 +} +``` + + +### 动手实践 + +每个模块包含: +- 使用交互式 UniversalEditor 的**多个代码对比** +- **实践练习**以加强学习 +- **常见陷阱**及如何避免 +- 生产 Go 代码中的**真实示例** +- **性能考虑**和优化技巧 + +## 前置要求 + +在开始此学习路径之前,您应该具备: + +- ✅ **中级 Java 知识**:熟悉类、接口和 OOP 原则 +- ✅ **理解 Java 生态系统**:熟悉 Spring、Maven/Gradle 和 JVM 概念 +- ✅ **基础编程概念**:理解变量、循环和控制流 +- ✅ **终端使用经验**:能够在 shell 中运行命令 +- ✅ **开放的心态**:愿意学习不同的软件开发方法 + +无需先前的系统编程经验 - 我们将涵盖您需要知道的所有内容。 + +## 与其他 Go 教程的区别 + +### 1. 对比学习方法 +大多数 Go 教程假设您来自 Python 或 JavaScript 等动态语言。本路径专门为 **Java 开发者**设计,解决了从重型 OOP 背景过渡时需要面临的特定挑战和思维转变。 + +### 2. 强调并发 +特别关注 **goroutine、通道和并发模式**,这是 Go 相比 Java 线程模型真正闪耀的领域。 + +### 3. 生产就绪模式 +不仅学习语法,还学习 Google、Uber 和 Dropbox 等公司生产 Go 应用程序中使用的**真实世界模式**和最佳实践。 + +### 4. 企业环境背景 +理解何时使用 Go vs Java,以及 Go 如何与现有 Java 系统一起融入企业架构。 + +## 时间投入 + +每个模块设计为大约 **2-4 小时**完成,包括: +- 阅读和理解概念 +- 学习代码示例 +- 完成练习 +- 实验代码 + +完整学习路径总时间:**40-80 小时**。 + +## 充分利用本课程 + +### 主动学习策略 + +1. **不要只读 - 一起编码**:自己输入示例代码 +2. **实验**:修改代码,看看什么会破坏,理解为什么 +3. **比较实现**:思考您将如何在 Java 中解决相同问题 +4. **构建项目**:边学边应用概念到小项目 +5. **拥抱简洁**:避免过度工程的冲动 - Go 重视简洁 + +### 推荐工具 + +- **Go 安装**:我们将在模块 0 中介绍 +- **IDE**:VS Code 配合 Go 扩展(推荐)或 GoLand +- **Playground**:[Go Playground](https://go.dev/play/) 用于快速实验 + +## 社区与资源 + +Go 拥有一个不断发展的友好社区。不要犹豫,随时提问! + +- **Go 官方网站**:[go.dev](https://go.dev/) +- **Effective Go**:[go.dev/doc/effective_go](https://go.dev/doc/effective_go) +- **Go by Example**:[gobyexample.com](https://gobyexample.com/) +- **Go 论坛**:[forum.golangbridge.org](https://forum.golangbridge.org/) + +## 完成后您的能力 + +学完本学习路径后,您将能够: + +- ✅ 编写**符合社区最佳实践的地道 Go 代码** +- ✅ 使用 goroutine 和通道构建**高性能并发服务** +- ✅ 创建**微服务**,即时启动并作为单一二进制文件部署 +- ✅ 开发具有出色用户体验的 **CLI 工具** +- ✅ 将 Go 服务**与现有 Java 系统集成** +- ✅ 为**Go 开源项目做贡献** +- ✅ 就**何时使用 Go vs Java 做出明智决策** + +## 常见疑虑 + +### "Go 相比 Java 是否太简单?" + +Go 的简洁性是有意为之且强大的: +- **少即是多**:更少的特性意味着更少的认知负担 +- **快速上手**:新团队成员几天内就能高效工作 +- **易于维护**:简单的代码更易于理解和维护 +- **专注于业务逻辑**:花时间解决问题,而不是与语言抗争 + +### "学习 Go 后还会用 Java 吗?" + +当然!Go 和 Java 服务于不同目的: +- **Java**:大型企业应用、复杂业务逻辑、Android、大数据 +- **Go**:微服务、云原生应用、CLI 工具、高性能网络服务 + +许多公司**同时使用**两者 - Java 用于复杂业务逻辑,Go 用于轻量级、高性能服务。 + +### "需要忘掉 OOP 吗?" + +不是忘掉,而是适应: +- **组合优于继承**:使用结构体和组合而不是类层次结构 +- **接口是隐式的**:无需显式实现接口 +- **以行为为中心**:思考事物做什么,而不是它们是什么 + +## Java 到 Go:关键思维转变 + +### 从框架到标准库 + +**Java**:Spring 生态系统、Hibernate、外部库 +**Go**:丰富的标准库、最小依赖 + +### 从复杂到简单 + +**Java**:抽象工厂、构建器模式、重型 DI +**Go**:直接函数、简单构造函数、显式装配 + +### 从隐式到显式 + +**Java**:异常、魔法框架、隐藏行为 +**Go**:错误返回、清晰代码流、明显行为 + +### 从重量级到轻量级 + +**Java**:JVM 启动、内存开销、缓慢扩展 +**Go**:即时启动、最小内存、快速扩展 + +## 真实世界应用 + +本课程结束时,您将准备好构建: + +**微服务** +- 轻量级、快速的服务 +- 易于部署和扩展 +- 单一二进制部署 + +**API 网关和代理** +- 高性能路由 +- 并发请求处理 +- 低内存占用 + +**CLI 工具** +- 开发者生产力工具 +- 自动化脚本 +- 系统实用程序 + +**云原生应用** +- Kubernetes 操作器 +- Docker 工具 +- 基础设施管理 + +## 让我们开始吧! + +准备开始您的 Go 之旅了吗?前往 **[模块 0:Go 语言介绍](./module-00-go-introduction)** 了解 Go 的哲学以及为什么它成为云原生开发的首选语言。 + +请记住:**最优秀的 Go 开发者往往是那些先学习 Java 然后发现简洁性力量的人。** + +--- + +**下一步:[模块 0 - Go 语言介绍](./module-00-go-introduction)** → diff --git a/content/docs/java2go/index.zh-tw.mdx b/content/docs/java2go/index.zh-tw.mdx new file mode 100644 index 0000000..f202c24 --- /dev/null +++ b/content/docs/java2go/index.zh-tw.mdx @@ -0,0 +1,316 @@ +--- +title: "Java → Go:完整學習路徑" +description: "從您的 Java 知識出發掌握 Go 程式設計。透過對比學習理解簡潔性、並發性和高效能。" +--- + +# Java → Go:完整學習路徑 + +歡迎來到 **Java → Go** 學習路徑!這是一個專為 Java 開發者設計的綜合性課程,協助您掌握 Go 程式設計語言。 + +## 為什麼 Java 開發者應該學習 Go? + +身為一名 Java 開發者,您已經具備了紮實的程式設計基礎、物件導向設計技能和企業級開發經驗。Go 在這些基礎之上,引進了一種截然不同的軟體開發方法,將使您成为一名更加全面的多語言程式設計師。 + +### Go 的核心優勢 + + +```java !! java +// Java:JVM 啟動開銷 +public class Application { + public static void main(String[] args) { + System.out.println("啟動應用程式..."); + // JVM 預熱時間:1-3 秒 + // JIT 編譯開銷 + } +} +``` + +```go !! go +// Go:即時啟動 +package main + +import "fmt" + +func main() { + fmt.Println("啟動應用程式...") + // 無需預熱 + // 立即執行 +} +``` + + +### Go 的獨特之處 + +1. **簡潔勝於複雜**:Go 只有 25 個關鍵字(Java 有 50+ 個),沒有類別,沒有繼承,語法精簡,幾天即可掌握而非幾個月。 + +2. **輕量級並發**:Go 的 goroutine 是由 Go 執行時管理的輕量級執行緒,讓您能夠高效地執行數百萬個並發操作。 + +3. **快速編譯**:Go 幾秒鐘就能完成編譯,實現快速開發週期和即時回饋。 + +4. **單一二進位部署**:無需安裝 JVM 或複雜的相依性管理,只需部署一個靜態二進位檔案。 + +5. **現代化工具**:內建程式碼格式化、測試、效能分析和基準測試工具。 + +## 您將學到什麼 + +本學習路徑包含 **20 個綜合模組**,將您從 Go 初學者培養為自信的實踐者。 + +### 模組結構 + +1. **模組 0-1**:入門開始 + - Go 語言介紹和設計哲學 + - 語法對比和與 Java 的主要差異 + - 環境設定和基礎工具 + +2. **模組 2-4**:Go 的物件導向 + - 理解 Go 無類別的 OOP 方法 + - 結構體和組合 vs 繼承 + - 介面和型別系統 + - 套件組織和可見性 + +3. **模組 5-7**:並發與平行 + - Goroutine vs Java 執行緒 + - 通訊通道 + - Select 陳述式和模式 + - 並發設計模式 + +4. **模組 8-9**:Web 開發 + - 不使用 Spring 建構 Web 服務 + - HTTP 伺服器和路由 + - JSON 處理和 REST API + - 中介軟體和請求處理 + +5. **模組 10-12**:測試與品質 + - Go 測試框架 + - 表格驅動測試 + - 基準測試和效能分析 + - 常見陷阱及避免方法 + - 地道的 Go 模式 + +6. **模組 13-15**:生產就緒 + - 效能最佳化 + - 建構工具和部署 + - 錯誤處理最佳實踐 + - Go 生態系統和函式庫 + +7. **模組 16-17**:進階主題 + - 資料庫整合 + - 微服務架構 + - 真實世界模式 + +8. **模組 18-19**:精通 + - 完整的真實專案 + - 最佳實踐和設計模式 + - 生產部署策略 + +## 學習方法 + +### 以 Java 為起點的教學 + +課程中的每個概念都透過以下方式引入: +1. **從熟悉的 Java 程式碼開始** - 看看您如何在 Java 中解決問題 +2. **介紹 Go 的等價實作** - 理解語言之間的對應關係 +3. **解釋關鍵差異** - 學習 Go 為什麼採用不同的方法 +4. **實務範例** - 透過真實場景學習 + + +```java !! java +// Java:基於例外的錯誤處理 +public class UserService { + public User getUser(String id) throws UserNotFoundException { + User user = database.findUser(id); + if (user == null) { + throw new UserNotFoundException("使用者未找到:" + id); + } + return user; + } +} + +// 呼叫者必須記得捕獲例外 +try { + User user = userService.getUser("123"); +} catch (UserNotFoundException e) { + // 處理錯誤 +} +``` + +```go !! go +// Go:顯式錯誤返回 +func (s *UserService) GetUser(id string) (User, error) { + user, err := s.database.FindUser(id) + if err != nil { + return User{}, fmt.Errorf("使用者未找到:%s", id) + } + return user, nil +} + +// 編譯器強制您處理錯誤 +user, err := userService.GetUser("123") +if err != nil { + // 處理錯誤 +} +``` + + +### 動手實作 + +每個模組包含: +- 使用互動式 UniversalEditor 的**多個程式碼對比** +- **實務練習**以加強學習 +- **常見陷阱**及如何避免 +- 生產 Go 程式碼中的**真實範例** +- **效能考量**和最佳化技巧 + +## 前置要求 + +在開始此學習路徑之前,您應該具備: + +- ✅ **中級 Java 知識**:熟悉類別、介面和 OOP 原則 +- ✅ **理解 Java 生態系統**:熟悉 Spring、Maven/Gradle 和 JVM 概念 +- ✅ **基礎程式設計概念**:理解變數、迴圈和控制流 +- ✅ **終端機使用經驗**:能夠在 shell 中執行指令 +- ✅ **開放的心態**:願意學習不同的軟體開發方法 + +無需先前的系統程式設計經驗 - 我們將涵蓋您需要知道的所有內容。 + +## 與其他 Go 教程的區別 + +### 1. 對比學習方法 +大多數 Go 教程假設您來自 Python 或 JavaScript 等動態語言。本路徑專門為 **Java 開發者**設計,解決了從重型 OOP 背景過渡時需要面臨的特定挑戰和思維轉變。 + +### 2. 強調並發 +特別關注 **goroutine、通道和並發模式**,這是 Go 相比 Java 執行緒模型真正閃耀的領域。 + +### 3. 生產就緒模式 +不僅學習語法,還學習 Google、Uber 和 Dropbox 等公司生產 Go 應用程式中使用的**真實世界模式**和最佳實踐。 + +### 4. 企業環境背景 +理解何時使用 Go vs Java,以及 Go 如何與現有 Java 系統一起融入企業架構。 + +## 時間投入 + +每個模組設計為大約 **2-4 小時**完成,包括: +- 閱讀和理解概念 +- 學習程式碼範例 +- 完成練習 +- 實驗程式碼 + +完整學習路徑總時間:**40-80 小時**。 + +## 充分利用本課程 + +### 主動學習策略 + +1. **不要只讀 - 一起編碼**:自己輸入範例程式碼 +2. **實驗**:修改程式碼,看看什麼會破壞,理解為什麼 +3. **比較實作**:思考您將如何在 Java 中解決相同問題 +4. **建構專案**:邊學邊應用概念到小專案 +5. **擁抱簡潔**:避免過度工程的衝動 - Go 重視簡潔 + +### 推薦工具 + +- **Go 安裝**:我們將在模組 0 中介紹 +- **IDE**:VS Code 配合 Go 擴充功能(推薦)或 GoLand +- **Playground**:[Go Playground](https://go.dev/play/) 用於快速實驗 + +## 社群與資源 + +Go 擁有一個不斷發展的友善社群。不要猶豫,隨時提問! + +- **Go 官方網站**:[go.dev](https://go.dev/) +- **Effective Go**:[go.dev/doc/effective_go](https://go.dev/doc/effective_go) +- **Go by Example**:[gobyexample.com](https://gobyexample.com/) +- **Go 論壇**:[forum.golangbridge.org](https://forum.golangbridge.org/) + +## 完成後您的能力 + +學完本學習路徑後,您將能夠: + +- ✅ 撰寫**符合社群最佳實踐的地道 Go 程式碼** +- ✅ 使用 goroutine 和通道建構**高效能並發服務** +- ✅ 建立**微服務**,即時啟動並作為單一二進位檔案部署 +- ✅ 開發具有出色使用者體驗的 **CLI 工具** +- ✅ 將 Go 服務**與現有 Java 系統整合** +- ✅ 為**Go 開源專案做出貢獻** +- ✅ 就**何時使用 Go vs Java 做出明智決策** + +## 常見疑慮 + +### "Go 相比 Java 是否太簡單?" + +Go 的簡潔性是有意為之且強大的: +- **少即是多**:更少的特性意味著更少的認知負擔 +- **快速上手**:新團隊成員幾天內就能高效工作 +- **易於維護**:簡單的程式碼更易於理解和維護 +- **專注於業務邏輯**:花時間解決問題,而不是與語言抗爭 + +### "學習 Go 後還會用 Java 嗎?" + +當然!Go 和 Java 服務於不同目的: +- **Java**:大型企業應用、複雜業務邏輯、Android、大數據 +- **Go**:微服務、雲端原生應用、CLI 工具、高效能網路服務 + +許多公司**同時使用**兩者 - Java 用於複雜業務邏輯,Go 用於輕量級、高效能服務。 + +### "需要忘掉 OOP 嗎?" + +不是忘掉,而是適應: +- **組合優於繼承**:使用結構體和組合而不是類別階層結構 +- **介面是隱式的**:無需顯式實作介面 +- **以行為為中心**:思考事物做什麼,而不是它們是什麼 + +## Java 到 Go:關鍵思維轉變 + +### 從框架到標準函式庫 + +**Java**:Spring 生態系統、Hibernate、外部函式庫 +**Go**:豐富的標準函式庫、最小相依性 + +### 從複雜到簡單 + +**Java**:抽象工廠、建構器模式、重型 DI +**Go**:直接函式、簡單建構函式、顯式裝配 + +### 從隱式到顯式 + +**Java**:例外、魔法框架、隱藏行為 +**Go**:錯誤返回、清晰程式碼流、明顯行為 + +### 從重量級到輕量級 + +**Java**:JVM 啟動、記憶體開銷、緩慢擴展 +**Go**:即時啟動、最小記憶體、快速擴展 + +## 真實世界應用 + +本課程結束時,您將準備好建構: + +**微服務** +- 輕量級、快速的服務 +- 易於部署和擴展 +- 單一二進位部署 + +**API 閘道和代理** +- 高效能路由 +- 並發請求處理 +- 低記憶體佔用 + +**CLI 工具** +- 開發者生產力工具 +- 自動化腳本 +- 系統實用程式 + +**雲端原生應用** +- Kubernetes 操作器 +- Docker 工具 +- 基礎設施管理 + +## 讓我們開始吧! + +準備開始您的 Go 之旅了嗎?前往 **[模組 0:Go 語言介紹](./module-00-go-introduction)** 了解 Go 的哲學以及為什麼它成為雲端原生開發的首選語言。 + +請記住:**最優秀的 Go 開發者往往是那些先學習 Java 然後發現簡潔性力量的人。** + +--- + +**下一步:[模組 0 - Go 語言介紹](./module-00-go-introduction)** → diff --git a/content/docs/java2js/module-05-arrays-collections.zh-cn.mdx b/content/docs/java2js/module-05-arrays-collections.zh-cn.mdx index c1382dd..7aa5545 100644 --- a/content/docs/java2js/module-05-arrays-collections.zh-cn.mdx +++ b/content/docs/java2js/module-05-arrays-collections.zh-cn.mdx @@ -1,181 +1,181 @@ --- -title: "Module 5: Arrays and Collections" -description: "Master JavaScript arrays, Sets, Maps, and collection methods" +title: "模块 5:数组和集合" +description: "掌握 JavaScript 数组、Set、Map 和集合方法" --- -## Module 5: Arrays and Collections +## 模块 5:数组和集合 -JavaScript's collection types are different from Java's. Arrays are more flexible, and JavaScript offers additional collection types like Set and Map. Let's explore these powerful data structures. +JavaScript 的集合类型与 Java 有很大不同。数组更加灵活,而且 JavaScript 提供了额外的集合类型如 Set 和 Map。让我们探索这些强大的数据结构。 -## Learning Objectives +## 学习目标 -By the end of this module, you will: -✅ Master JavaScript array creation and manipulation -✅ Understand array methods (mutating vs non-mutating) -✅ Learn about Set and Map data structures -✅ Know when to use WeakSet and WeakMap -✅ Understand array destructuring -✅ Learn performance considerations +完成本模块后,你将: +✅ 掌握 JavaScript 数组的创建和操作 +✅ 理解数组方法(变异方法与非变异方法) +✅ 学习 Set 和 Map 数据结构 +✅ 了解何时使用 WeakSet 和 WeakMap +✅ 理解数组解构 +✅ 学习性能考虑 -## Array Basics +## 数组基础 -JavaScript arrays are more like dynamic lists than Java's arrays: +JavaScript 数组更像动态列表,而不是 Java 的固定大小数组: - + ```java !! java -// Java - Fixed-size arrays +// Java - 固定大小数组 int[] numbers = new int[5]; numbers[0] = 1; numbers[1] = 2; // numbers[5] = 3; // ArrayIndexOutOfBoundsException -// Array initializer +// 数组初始化器 int[] moreNumbers = {1, 2, 3, 4, 5}; -// Size is fixed +// 大小是固定的 System.out.println(numbers.length); // 5 -// Cannot change size -// To add elements, need ArrayList +// 不能改变大小 +// 要添加元素,需要 ArrayList ``` ```javascript !! js -// JavaScript - Dynamic arrays +// JavaScript - 动态数组 const numbers = [1, 2, 3, 4, 5]; -// Can add elements -numbers.push(6); // Add to end -numbers.unshift(0); // Add to beginning +// 可以添加元素 +numbers.push(6); // 添加到末尾 +numbers.unshift(0); // 添加到开头 -// Can remove elements -numbers.pop(); // Remove from end -numbers.shift(); // Remove from beginning +// 可以删除元素 +numbers.pop(); // 从末尾删除 +numbers.shift(); // 从开头删除 -// Dynamic length -console.log(numbers.length); // 5 (after push/pop) +// 动态长度 +console.log(numbers.length); // 5 (在 push/pop 之后) -// Mixed types (not recommended but possible) +// 混合类型(不推荐但可能) const mixed = [1, "hello", true, { id: 1 }, [1, 2, 3]]; -// Sparse arrays -const sparse = [1, , , 4]; // Holes in array +// 稀疏数组 +const sparse = [1, , , 4]; // 数组中有空缺 console.log(sparse.length); // 4 console.log(sparse[1]); // undefined -// Array constructor (avoid - use literal syntax) -const arr = new Array(5); // Creates array with 5 empty slots -const arr2 = new Array(1, 2, 3); // Creates [1, 2, 3] -// Better: const arr = [1, 2, 3]; +// 数组构造函数(避免 - 使用字面量语法) +const arr = new Array(5); // 创建有 5 个空槽的数组 +const arr2 = new Array(1, 2, 3); // 创建 [1, 2, 3] +// 更好: const arr = [1, 2, 3]; ``` -### Array Length +### 数组长度 - + ```java !! java -// Java - Length is fixed +// Java - 长度是固定的 int[] arr = {1, 2, 3}; System.out.println(arr.length); // 3 -arr.length = 5; // Compilation error! Cannot change length +arr.length = 5; // 编译错误!不能改变长度 ``` ```javascript !! js -// JavaScript - Length is writable +// JavaScript - 长度是可写的 let arr = [1, 2, 3]; console.log(arr.length); // 3 -// Setting length truncates or adds undefined +// 设置长度会截断或添加 undefined arr.length = 5; console.log(arr); // [1, 2, 3, undefined × 2] arr.length = 2; console.log(arr); // [1, 2] -// Clear array +// 清空数组 arr.length = 0; console.log(arr); // [] -// Practical: Fast truncate +// 实用:快速截断 const bigArray = [1, 2, 3, 4, 5]; -bigArray.length = 3; // Keep only first 3 elements +bigArray.length = 3; // 只保留前 3 个元素 -// Check if empty +// 检查是否为空 if (arr.length === 0) { - console.log("Array is empty"); + console.log("数组为空"); } -// Truthy/falsy -if (arr.length) { // Works because 0 is falsy - console.log("Array has elements"); +// 真值/假值 +if (arr.length) { // 有效,因为 0 是假值 + console.log("数组有元素"); } ``` -## Array Methods - Mutating vs Non-Mutating +## 数组方法 - 变异方法与非变异方法 -JavaScript distinguishes between methods that modify the array and those that create a new array: +JavaScript 区分修改数组的方法和创建新数组的方法: -### Mutating Methods +### 变异方法 - + ```java !! java -// Java - Arrays don't have methods -// Use ArrayList or utility classes +// Java - 数组没有方法 +// 使用 ArrayList 或工具类 List list = new ArrayList<>(Arrays.asList("a", "b", "c")); -list.add("d"); // Add to end -list.add(0, "start"); // Add at index -list.remove(0); // Remove at index -list.set(0, "x"); // Replace at index +list.add("d"); // 添加到末尾 +list.add(0, "start"); // 在索引处添加 +list.remove(0); // 删除索引处 +list.set(0, "x"); // 替换索引处 ``` ```javascript !! js -// JavaScript - Mutating methods +// JavaScript - 变异方法 const arr = [1, 2, 3, 4, 5]; -// push: Add to end +// push: 添加到末尾 arr.push(6); console.log(arr); // [1, 2, 3, 4, 5, 6] -// pop: Remove from end +// pop: 从末尾删除 arr.pop(); console.log(arr); // [1, 2, 3, 4, 5] -// unshift: Add to beginning +// unshift: 添加到开头 arr.unshift(0); console.log(arr); // [0, 1, 2, 3, 4, 5] -// shift: Remove from beginning +// shift: 从开头删除 arr.shift(); console.log(arr); // [1, 2, 3, 4, 5] -// splice: Remove/add at any position -arr.splice(2, 1); // Remove 1 element at index 2 +// splice: 在任何位置删除/添加 +arr.splice(2, 1); // 删除索引 2 的 1 个元素 console.log(arr); // [1, 2, 4, 5] -arr.splice(2, 0, 3); // Insert 3 at index 2 +arr.splice(2, 0, 3); // 在索引 2 插入 3 console.log(arr); // [1, 2, 3, 4, 5] -// sort: Sorts in place +// sort: 原地排序 arr.sort((a, b) => a - b); console.log(arr); // [1, 2, 3, 4, 5] -// reverse: Reverses in place +// reverse: 原地反转 arr.reverse(); console.log(arr); // [5, 4, 3, 2, 1] -// fill: Fill with value +// fill: 用值填充 arr.fill(0); console.log(arr); // [0, 0, 0, 0, 0] ``` -### Non-Mutating Methods +### 非变异方法 - + ```java !! java -// Java - Streams create new collections +// Java - Streams 创建新集合 List numbers = Arrays.asList(1, 2, 3, 4, 5); List doubled = numbers.stream() @@ -186,101 +186,101 @@ List evens = numbers.stream() .filter(n -> n % 2 == 0) .collect(Collectors.toList()); -// Original list unchanged +// 原列表不变 ``` ```javascript !! js -// JavaScript - Non-mutating methods +// JavaScript - 非变异方法 const numbers = [1, 2, 3, 4, 5]; -// map: Transform to new array +// map: 转换为新数组 const doubled = numbers.map(n => n * 2); console.log(doubled); // [2, 4, 6, 8, 10] -console.log(numbers); // [1, 2, 3, 4, 5] (unchanged) +console.log(numbers); // [1, 2, 3, 4, 5] (未改变) -// filter: Select elements +// filter: 选择元素 const evens = numbers.filter(n => n % 2 === 0); console.log(evens); // [2, 4] -console.log(numbers); // [1, 2, 3, 4, 5] (unchanged) +console.log(numbers); // [1, 2, 3, 4, 5] (未改变) -// slice: Extract portion +// slice: 提取部分 const first3 = numbers.slice(0, 3); console.log(first3); // [1, 2, 3] -// concat: Combine arrays +// concat: 合并数组 const more = [6, 7, 8]; const combined = numbers.concat(more); console.log(combined); // [1, 2, 3, 4, 5, 6, 7, 8] -// join: Convert to string +// join: 转换为字符串 const str = numbers.join("-"); console.log(str); // "1-2-3-4-5" -// flat: Flatten nested arrays +// flat: 展平嵌套数组 const nested = [1, [2, [3, [4]]]]; const flat = nested.flat(2); console.log(flat); // [1, 2, 3, [4]] -// flatMap: Map and flatten in one step +// flatMap: 一步完成映射和展平 const sentences = ["Hello world", "Goodbye earth"]; const words = sentences.flatMap(s => s.split(" ")); console.log(words); // ["Hello", "world", "Goodbye", "earth"] ``` -## Array Destructuring +## 数组解构 -Destructuring allows unpacking values from arrays: +解构允许从数组中解包值: - + ```java !! java -// Java - Manual unpacking +// Java - 手动解包 int[] coordinates = {10, 20, 30}; int x = coordinates[0]; int y = coordinates[1]; int z = coordinates[2]; -// Or use objects/classes +// 或使用对象/类 public class Point { public int x, y, z; - // Constructor, getters, setters... + // 构造函数、getter、setter... } ``` ```javascript !! js -// JavaScript - Destructuring +// JavaScript - 解构 const coordinates = [10, 20, 30]; -// Basic destructuring +// 基础解构 const [x, y, z] = coordinates; console.log(x, y, z); // 10 20 30 -// Skip elements +// 跳过元素 const [first, , third] = coordinates; console.log(first, third); // 10 30 -// Rest operator +// 剩余运算符 const [head, ...tail] = coordinates; console.log(head); // 10 console.log(tail); // [20, 30] -// Default values +// 默认值 const [a = 1, b = 2, c = 3] = [10]; console.log(a, b, c); // 10 2 3 -// Swap variables +// 交换变量 let m = 1, n = 2; [m, n] = [n, m]; console.log(m, n); // 2 1 -// Destructuring function returns +// 解构函数返回 function getStats() { return [100, 50, [25, 75]]; } const [total, min, [max1, max2]] = getStats(); console.log(total, min, max1, max2); // 100 50 25 75 -// Practical: Parse URL +// 实用: 解析 URL const url = "https://example.com/users/123"; const [, protocol, domain, path, id] = url.split(/\/+/); console.log(protocol); // "https:" @@ -290,17 +290,17 @@ console.log(id); // "123" ``` -## Searching and Querying +## 搜索和查询 - + ```java !! java -// Java - Search methods +// Java - 搜索方法 List names = Arrays.asList("Alice", "Bob", "Charlie"); boolean hasAlice = names.contains("Alice"); // true int indexOfBob = names.indexOf("Bob"); // 1 -// With streams +// 使用 streams Optional found = names.stream() .filter(n -> n.startsWith("B")) .findFirst(); @@ -313,41 +313,41 @@ boolean anyLong = names.stream() ``` ```javascript !! js -// JavaScript - Search methods +// JavaScript - 搜索方法 const names = ["Alice", "Bob", "Charlie"]; -// indexOf: Find index +// indexOf: 查找索引 const indexOfBob = names.indexOf("Bob"); console.log(indexOfBob); // 1 const indexOfXYZ = names.indexOf("XYZ"); -console.log(indexOfXYZ); // -1 (not found) +console.log(indexOfXYZ); // -1 (未找到) -// includes: Check existence +// includes: 检查存在 const hasAlice = names.includes("Alice"); console.log(hasAlice); // true -// find: Find element matching predicate +// find: 查找匹配谓词的元素 const found = names.find(name => name.startsWith("B")); console.log(found); // "Bob" -// findIndex: Find index of matching element +// findIndex: 查找匹配元素的索引 const foundIndex = names.findIndex(name => name.startsWith("B")); console.log(foundIndex); // 1 -// some: Check if any element matches +// some: 检查是否有任何元素匹配 const anyLong = names.some(name => name.length > 4); console.log(anyLong); // true -// every: Check if all elements match +// every: 检查是否所有元素匹配 const allLong = names.every(name => name.length > 3); console.log(allLong); // true -// lastIndexOf: Find from end +// lastIndexOf: 从末尾查找 const dupes = [1, 2, 3, 2, 1]; console.log(dupes.lastIndexOf(2)); // 3 -// Practical: Find user by ID +// 实用: 按 ID 查找用户 const users = [ { id: 1, name: "Alice" }, { id: 2, name: "Bob" }, @@ -362,36 +362,36 @@ console.log(userIndex); // 1 ``` -## Array Reduction +## 数组归约 - + ```java !! java -// Java - Reduce with streams +// Java - 使用 streams 归约 List numbers = Arrays.asList(1, 2, 3, 4, 5); -// Sum +// 求和 int sum = numbers.stream().reduce(0, Integer::sum); -// Product +// 求积 int product = numbers.stream().reduce(1, (a, b) -> a * b); -// Joining +// 连接 String joined = names.stream().collect(Collectors.joining(", ")); -// To map +// 转为 map Map map = names.stream() .collect(Collectors.toMap( String::length, Function.identity(), - (a, b) -> a // Merge function + (a, b) -> a // 合并函数 )); ``` ```javascript !! js -// JavaScript - Reduce methods +// JavaScript - 归约方法 const numbers = [1, 2, 3, 4, 5]; -// reduce: Reduce to single value +// reduce: 归约为单个值 const sum = numbers.reduce((acc, n) => acc + n, 0); console.log(sum); // 15 @@ -404,11 +404,11 @@ console.log(max); // 5 const min = numbers.reduce((acc, n) => Math.min(acc, n), Infinity); console.log(min); // 1 -// reduceRight: Reduce from right to left +// reduceRight: 从右到左归约 const flattened = [[1, 2], [3, 4], [5, 6]].reduce((acc, arr) => acc.concat(arr), []); console.log(flattened); // [1, 2, 3, 4, 5, 6] -// Practical: Group by +// 实用: 分组 const words = ["apple", "banana", "avocado", "cherry", "blueberry"]; const grouped = words.reduce((acc, word) => { const first = word[0]; @@ -420,7 +420,7 @@ const grouped = words.reduce((acc, word) => { console.log(grouped); // { a: ["apple", "avocado"], b: ["banana", "blueberry"], c: ["cherry"] } -// Practical: Build lookup map +// 实用: 构建查找 map const users = [ { id: 1, name: "Alice" }, { id: 2, name: "Bob" } @@ -435,17 +435,17 @@ console.log(userMap[1]); // { id: 1, name: "Alice" } ``` -## Set and Map +## Set 和 Map -JavaScript offers Set and Map collections similar to Java's: +JavaScript 提供类似 Java 的 Set 和 Map 集合: - + ```java !! java // Java - HashSet Set names = new HashSet<>(); names.add("Alice"); names.add("Bob"); -names.add("Alice"); // Duplicate ignored +names.add("Alice"); // 重复被忽略 System.out.println(names.contains("Alice")); // true System.out.println(names.size()); // 2 @@ -456,10 +456,10 @@ for (String name : names) { System.out.println(name); } -// LinkedHashSet (maintains insertion order) +// LinkedHashSet (保持插入顺序) Set ordered = new LinkedHashSet<>(); -// TreeSet (sorted) +// TreeSet (排序) Set sorted = new TreeSet<>(); ``` @@ -468,7 +468,7 @@ Set sorted = new TreeSet<>(); const names = new Set(); names.add("Alice"); names.add("Bob"); -names.add("Alice"); // Duplicate ignored +names.add("Alice"); // 重复被忽略 console.log(names.has("Alice")); // true console.log(names.size); // 2 @@ -476,48 +476,48 @@ console.log(names.size); // 2 names.delete("Alice"); console.log(names.has("Alice")); // false -// Iterate +// 迭代 for (const name of names) { console.log(name); } -// Convert to array +// 转为数组 const nameArray = [...names]; console.log(nameArray); // ["Bob"] -// Initialize from array +// 从数组初始化 const numbers = new Set([1, 2, 3, 2, 1]); console.log(numbers); // Set {1, 2, 3} -// Practical: Remove duplicates +// 实用: 去重 const dupes = [1, 2, 2, 3, 3, 3, 4]; const unique = [...new Set(dupes)]; console.log(unique); // [1, 2, 3, 4] -// Set methods +// Set 方法 const set = new Set([1, 2, 3]); -set.clear(); // Remove all +set.clear(); // 删除所有 console.log(set.size); // 0 -// Set operations +// Set 操作 const a = new Set([1, 2, 3]); const b = new Set([3, 4, 5]); -// Union +// 并集 const union = new Set([...a, ...b]); console.log(union); // Set {1, 2, 3, 4, 5} -// Intersection +// 交集 const intersection = new Set([...a].filter(x => b.has(x))); console.log(intersection); // Set {3} -// Difference +// 差集 const difference = new Set([...a].filter(x => !b.has(x))); console.log(difference); // Set {1, 2} ``` - + ```java !! java // Java - HashMap Map scores = new HashMap<>(); @@ -533,8 +533,8 @@ for (Map.Entry entry : scores.entrySet()) { System.out.println(entry.getKey() + ": " + entry.getValue()); } -// LinkedHashMap (maintains insertion order) -// TreeMap (sorted by keys) +// LinkedHashMap (保持插入顺序) +// TreeMap (按键排序) ``` ```javascript !! js @@ -549,18 +549,18 @@ console.log(scores.has("Bob")); // true scores.delete("Alice"); console.log(scores.has("Alice")); // false -// Iterate +// 迭代 for (const [name, score] of scores) { console.log(`${name}: ${score}`); } -// Initialize from array of pairs +// 从对数组初始化 const data = new Map([ ["Alice", 95], ["Bob", 87] ]); -// Map vs Object: Map keys can be any type +// Map vs Object: Map 键可以是任何类型 const map = new Map(); const key1 = { id: 1 }; const key2 = { id: 2 }; @@ -572,12 +572,12 @@ map.set(123, "Number key"); console.log(map.get(key1)); // "Value 1" -// Object keys are always strings/symbols +// Object 键总是字符串/符号 const obj = {}; -obj[key1] = "Value"; // key converted to "[object Object]" -obj[key2] = "Value"; // Overwrites previous! +obj[key1] = "Value"; // 键被转换为 "[object Object]" +obj[key2] = "Value"; // 覆盖前一个! -// Map iteration order is guaranteed +// Map 迭代顺序有保证 const orderedMap = new Map([ ["z", 1], ["a", 2], @@ -585,44 +585,44 @@ const orderedMap = new Map([ ]); for (const [key, value] of orderedMap) { - console.log(key, value); // Maintains insertion order + console.log(key, value); // 保持插入顺序 } -// Map methods +// Map 方法 const map2 = new Map([["a", 1], ["b", 2]]); console.log(map2.keys()); // MapIterator {"a", "b"} console.log(map2.values()); // MapIterator {1, 2} console.log(map2.entries()); // MapIterator {"a" => 1, "b" => 2} -// Convert Map to Object +// 将 Map 转为 Object const map3 = new Map([["a", 1], ["b", 2]]); const obj2 = Object.fromEntries(map3); console.log(obj2); // { a: 1, b: 2 } -// Convert Object to Map +// 将 Object 转为 Map const obj3 = { a: 1, b: 2 }; const map4 = new Map(Object.entries(obj3)); ``` -## WeakSet and WeakMap +## WeakSet 和 WeakMap -WeakSet and WeakMap hold object references weakly (allowing garbage collection): +WeakSet 和 WeakMap 弱持有对象引用(允许垃圾回收): - + ```java !! java // Java - WeakHashMap Map weakMap = new WeakHashMap<>(); -// Keys are weakly referenced -// When key is no longer strongly reachable, entry is removed +// 键被弱引用 +// 当键不再强可达时,条目被删除 -// No direct equivalent of WeakSet (until Java 9 with IdentityHashMap) +// 没有 WeakSet 的直接等价物 (直到 Java 9 使用 IdentityHashMap) ``` ```javascript !! js -// JavaScript - WeakSet and WeakMap +// JavaScript - WeakSet 和 WeakMap -// WeakSet: Only holds objects, no iteration +// WeakSet: 只持有对象,不能迭代 const weakSet = new WeakSet(); let obj1 = { id: 1 }; let obj2 = { id: 2 }; @@ -633,23 +633,23 @@ weakSet.add(obj2); console.log(weakSet.has(obj1)); // true -obj1 = null; // Remove strong reference -// obj1 will be garbage collected, WeakSet entry removed +obj1 = null; // 删除强引用 +// obj1 将被垃圾回收,WeakSet 条目被删除 -// Use case: Track objects without preventing GC +// 用例: 跟踪对象而不阻止 GC function processObjects(objects) { const processed = new WeakSet(); return objects.filter(obj => { if (processed.has(obj)) { - return false; // Already processed + return false; // 已处理 } processed.add(obj); return true; }); } -// WeakMap: Keys must be objects, values can be anything +// WeakMap: 键必须是对象,值可以是任何东西 const weakMap = new WeakMap(); const key1 = { id: 1 }; const key2 = { id: 2 }; @@ -662,7 +662,7 @@ console.log(weakMap.has(key1)); // true weakMap.delete(key1); -// Use case: Private data +// 用例: 私有数据 const privateData = new WeakMap(); class User { @@ -681,11 +681,11 @@ class User { } const user = new User("Alice", 25); -console.log(user.name); // "Alice" (public) -console.log(user.getAge()); // 25 (accessed via WeakMap) -console.log(user.age); // undefined (truly private) +console.log(user.name); // "Alice" (公开) +console.log(user.getAge()); // 25 (通过 WeakMap 访问) +console.log(user.age); // undefined (真正私有) -// Use case: DOM node metadata +// 用例: DOM 节点元数据 const nodeData = new WeakMap(); function setupNode(node) { @@ -698,44 +698,44 @@ function setupNode(node) { const data = nodeData.get(node); data.clickCount++; data.lastClick = Date.now(); - console.log(`Clicked ${data.clickCount} times`); + console.log(`点击了 ${data.clickCount} 次`); }); } -// When node is removed from DOM, metadata is automatically GC'd +// 当节点从 DOM 移除时,元数据自动被 GC 回收 ``` -## Performance Considerations +## 性能考虑 - + ```java !! java // Java - ArrayList vs LinkedList -// ArrayList: O(1) random access, O(n) insert/delete -// LinkedList: O(n) random access, O(1) insert/delete +// ArrayList: O(1) 随机访问, O(n) 插入/删除 +// LinkedList: O(n) 随机访问, O(1) 插入/删除 -// Use ArrayList for most cases +// 大多数情况使用 ArrayList List list = new ArrayList<>(); -// Pre-size when possible +// 可能时预分配大小 List sized = new ArrayList<>(1000); ``` ```javascript !! js -// JavaScript - Array performance +// JavaScript - 数组性能 -// 1. Pre-allocate when size known (helps V8 optimize) +// 1. 知道大小时预分配(帮助 V8 优化) const arr = new Array(1000); -// Or +// 或 const arr2 = []; arr2.length = 1000; -// 2. Use typed arrays for numeric data -const int8 = new Int8Array(1000); // 8-bit integers -const int16 = new Int16Array(1000); // 16-bit integers -const float64 = new Float64Array(1000); // 64-bit floats +// 2. 对数值使用类型化数组 +const int8 = new Int8Array(1000); // 8 位整数 +const int16 = new Int16Array(1000); // 16 位整数 +const float64 = new Float64Array(1000); // 64 位浮点数 -// Performance comparison +// 性能比较 const regular = []; for (let i = 0; i < 1000000; i++) { regular.push(i); @@ -746,28 +746,28 @@ for (let i = 0; i < typed.length; i++) { typed[i] = i; } -// 3. Avoid sparse arrays -const sparse = []; // Bad -sparse[1000] = 1; // Creates holes +// 3. 避免稀疏数组 +const sparse = []; // 坏 +sparse[1000] = 1; // 创建空缺 -const dense = new Array(1001).fill(0); // Good +const dense = new Array(1001).fill(0); // 好 dense[1000] = 1; -// 4. Use for loop for heavy operations (faster than forEach) +// 4. 对重操作使用 for 循环比 forEach 快 const numbers = [1, 2, 3, 4, 5]; -// Slower (function call overhead) +// 较慢(函数调用开销) numbers.forEach(n => { - // Process n + // 处理 n }); -// Faster +// 更快 for (let i = 0; i < numbers.length; i++) { const n = numbers[i]; - // Process n + // 处理 n } -// 5. Set/Map lookup vs Array includes +// 5. Set/Map 查找 vs Array includes const arr = [1, 2, 3, 4, 5]; const set = new Set([1, 2, 3, 4, 5]); @@ -777,11 +777,11 @@ arr.includes(3); // Set: O(1) set.has(3); -// For large datasets, use Set/Map for lookups +// 对大数据集,使用 Set/Map 查找 const largeArray = Array.from({ length: 100000 }, (_, i) => i); const largeSet = new Set(largeArray); -// Fast lookup +// 快速查找 console.time("array"); largeArray.includes(99999); console.timeEnd("array"); // ~5ms @@ -790,25 +790,25 @@ console.time("set"); largeSet.has(99999); console.timeEnd("set"); // ~0ms -// 6. Array spread vs concat +// 6. 数组展开 vs concat const a = [1, 2, 3]; const b = [4, 5, 6]; -// Modern: spread (slower for very large arrays) +// 现代: 展开(对非常大的数组较慢) const combined = [...a, ...b]; -// Traditional: concat (often faster) +// 传统: concat(通常更快) const combined2 = a.concat(b); ``` -## Common Patterns +## 常见模式 -### Pattern 1: Chunking +### 模式 1: 分块 - + ```java !! java -// Java - Chunk into sublists +// Java - 分块为子列表 public static List> chunk(List list, int size) { List> chunks = new ArrayList<>(); for (int i = 0; i < list.size(); i += size) { @@ -819,7 +819,7 @@ public static List> chunk(List list, int size) { ``` ```javascript !! js -// JavaScript - Chunk array +// JavaScript - 分块数组 function chunk(array, size) { const chunks = []; for (let i = 0; i < array.length; i += size) { @@ -832,7 +832,7 @@ const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9]; console.log(chunk(numbers, 3)); // [[1, 2, 3], [4, 5, 6], [7, 8, 9]] -// Functional approach +// 函数式方法 function chunkFunctional(array, size) { return Array.from( { length: Math.ceil(array.length / size) }, @@ -842,26 +842,26 @@ function chunkFunctional(array, size) { ``` -### Pattern 2: Flatten +### 模式 2: 展平 - + ```javascript !! js -// Flatten nested arrays +// 展平嵌套数组 const nested = [1, [2, [3, [4, 5]]]]; -// Shallow flatten +// 浅展平 const flat1 = nested.flat(); console.log(flat1); // [1, 2, [3, [4, 5]]] -// Deep flatten (specify depth) +// 深展平(指定深度) const flat2 = nested.flat(2); console.log(flat2); // [1, 2, 3, [4, 5]] -// Infinite flatten +// 无限展平 const flat3 = nested.flat(Infinity); console.log(flat3); // [1, 2, 3, 4, 5] -// Custom flatten function +// 自定义展平函数 function flatten(arr) { const result = []; @@ -878,179 +878,179 @@ function flatten(arr) { ``` -## Best Practices +## 最佳实践 - + ```java !! java -// Java: Use appropriate collection type -List list = new ArrayList<>(); // Ordered, allows duplicates -Set set = new HashSet<>(); // Unordered, unique -Map map = new HashMap<>(); // Key-value pairs - -// Choose based on needs: -// - ArrayList: Fast random access -// - LinkedList: Frequent insertions/deletions -// - HashSet: Fast lookups, unique elements -// - TreeSet: Sorted elements -// - HashMap: Fast key lookups -// - TreeMap: Sorted keys +// Java: 使用适当的集合类型 +List list = new ArrayList<>(); // 有序,允许重复 +Set set = new HashSet<>(); // 无序,唯一 +Map map = new HashMap<>(); // 键值对 + +// 根据需求选择: +// - ArrayList: 快速随机访问 +// - LinkedList: 频繁插入/删除 +// - HashSet: 快速查找,唯一元素 +// - TreeSet: 排序元素 +// - HashMap: 快速键查找 +// - TreeMap: 排序键 ``` ```javascript !! js -// JavaScript: Choose wisely +// JavaScript: 明智选择 -// 1. Use arrays for ordered data +// 1. 对有序数据使用数组 const items = ["apple", "banana", "cherry"]; -// 2. Use Set for uniqueness +// 2. 对唯一性使用 Set const uniqueItems = new Set(["apple", "banana", "apple"]); -// 3. Use Map for key-value with non-string keys +// 3. 对非字符串键的键值使用 Map const metadata = new Map(); const element = document.getElementById("myDiv"); metadata.set(element, { clicks: 0 }); -// 4. Use object for simple string keys +// 4. 对简单字符串键使用对象 const config = { apiUrl: "https://api.example.com", timeout: 5000, retries: 3 }; -// 5. Prefer non-mutating methods +// 5. 优先使用非变异方法 const numbers = [1, 2, 3, 4, 5]; -// Good: Returns new array +// 好: 返回新数组 const doubled = numbers.map(n => n * 2); -// Be careful: Modifies in place -numbers.reverse(); // Modifies original! +// 小心: 原地修改 +numbers.reverse(); // 修改原数组! -// To avoid mutation: Create copy first +// 避免变异: 先创建副本 const reversed = [...numbers].reverse(); -// 6. Use destructuring for clarity +// 6. 使用解构提高清晰度 const [first, second, ...rest] = numbers; -// 7. Use spread for immutable updates +// 7. 使用展开进行不可变更新 const state = { count: 0, name: "app" }; -const newState = { ...state, count: 1 }; // Copy with update +const newState = { ...state, count: 1 }; // 复制并更新 const list = [1, 2, 3]; -const newList = [...list, 4]; // Copy with addition +const newList = [...list, 4]; // 复制并添加 -// 8. Leverage Set for deduplication +// 8. 利用 Set 去重 const dupes = [1, 2, 2, 3, 3, 3]; const unique = [...new Set(dupes)]; ``` -## Exercises +## 练习 -### Exercise 1: Array Manipulation -Implement these operations: +### 练习 1: 数组操作 +实现这些操作: ```javascript const arr = [1, 2, 3, 4, 5]; -// 1. Add 0 to beginning -// 2. Add 6 to end -// 3. Remove first element -// 4. Remove last element -// 5. Double all elements (non-mutating) -// 6. Filter out even numbers -// 7. Sum all numbers +// 1. 在开头添加 0 +// 2. 在末尾添加 6 +// 3. 删除第一个元素 +// 4. 删除最后一个元素 +// 5. 将所有元素翻倍(非变异) +// 6. 过滤掉偶数 +// 7. 求所有数字的和 ``` -### Exercise 2: Set Operations -Create Set utility functions: +### 练习 2: Set 操作 +创建 Set 工具函数: ```javascript -// Union of two sets +// 两个集合的并集 function union(setA, setB) { } -// Intersection of two sets +// 两个集合的交集 function intersection(setA, setB) { } -// Difference of two sets +// 两个集合的差集 function difference(setA, setB) { } ``` -### Exercise 3: Chunk Array -Split array into chunks of size n: +### 练习 3: 分块数组 +将数组分成大小为 n 的块: ```javascript function chunk(array, size) { - // Return array of arrays + // 返回数组的数组 } chunk([1, 2, 3, 4, 5, 6, 7], 3); // [[1, 2, 3], [4, 5, 6], [7]] ``` -### Exercise 4: Map Operations -Create frequency map: +### 练习 4: Map 操作 +创建频率 map: ```javascript function frequencyMap(array) { - // Return Map with element counts + // 返回包含元素计数的 Map } frequencyMap(["apple", "banana", "apple", "cherry", "banana", "apple"]); // Map {"apple" => 3, "banana" => 2, "cherry" => 1} ``` -## Summary +## 总结 -### Key Takeaways +### 关键要点 -1. **Arrays:** - - Dynamic size (can grow/shrink) - - Mixed types possible - - Rich set of methods - - Distinguish mutating vs non-mutating +1. **数组:** + - 动态大小(可以增长/缩小) + - 可能混合类型 + - 丰富的方法集 + - 区分变异方法和非变异方法 -2. **Destructuring:** - - Unpack values cleanly - - Skip with commas - - Rest operator for remaining - - Default values +2. **解构:** + - 清晰地解包值 + - 用逗号跳过 + - 剩余运算符用于其余部分 + - 默认值 3. **Set:** - - Unique values only - - Fast lookups (O(1)) - - Good for deduplication - - WeakSet for memory management + - 只有唯一值 + - 快速查找(O(1)) + - 适合去重 + - WeakSet 用于内存管理 4. **Map:** - - Any key type - - Maintains insertion order - - Fast lookups (O(1)) - - WeakMap for metadata + - 任何键类型 + - 保持插入顺序 + - 快速查找(O(1)) + - WeakMap 用于元数据 -5. **Performance:** - - Pre-allocate when possible - - Use typed arrays for numbers - - Set/Map for large lookups - - for loops for heavy operations +5. **性能:** + - 可能时预分配 + - 对数字使用类型化数组 + - 大量查找使用 Set/Map + - 重操作使用 for 循环 -### Comparison Table: Java vs JavaScript +### 对比表: Java vs JavaScript -| Feature | Java | JavaScript | +| 特性 | Java | JavaScript | |---------|------|------------| -| **Array type** | Fixed size, single type | Dynamic, mixed types | -| **Growth** | Need ArrayList | Automatic | -| **Set** | HashSet, TreeSet, LinkedHashSet | Set (insertion ordered) | -| **Map** | HashMap, TreeMap, LinkedHashMap | Map (insertion ordered) | -| **Weak refs** | WeakHashMap | WeakSet, WeakMap | -| **Typed arrays** | No | Int8Array, Float64Array, etc. | -| **Destructuring** | No | Yes (ES6+) | - -## What's Next? - -You've mastered JavaScript collections! Next up is **Module 6: Objects**, where we'll explore: - -- Object literals and creation -- Property getters and setters -- Object methods (keys, values, entries) -- Object destructuring -- Spread operator with objects -- Object.freeze and Object.seal - -Ready to dive deeper into JavaScript objects? Let's continue! +| **数组类型** | 固定大小,单一类型 | 动态,混合类型 | +| **增长** | 需要 ArrayList | 自动 | +| **Set** | HashSet, TreeSet, LinkedHashSet | Set (插入顺序) | +| **Map** | HashMap, TreeMap, LinkedHashMap | Map (插入顺序) | +| **弱引用** | WeakHashMap | WeakSet, WeakMap | +| **类型化数组** | 无 | Int8Array, Float64Array 等 | +| **解构** | 无 | 有 (ES6+) | + +## 接下来是什么? + +你已经掌握了 JavaScript 集合!接下来是**模块 6: 对象**,我们将探索: + +- 对象字面量和创建 +- 属性 getter 和 setter +- 对象方法(keys, values, entries) +- 对象解构 +- 对象的展开运算符 +- Object.freeze 和 Object.seal + +准备好深入探索 JavaScript 对象了吗?让我们继续! diff --git a/content/docs/java2js/module-06-objects.zh-cn.mdx b/content/docs/java2js/module-06-objects.zh-cn.mdx index 0325f03..53a89a5 100644 --- a/content/docs/java2js/module-06-objects.zh-cn.mdx +++ b/content/docs/java2js/module-06-objects.zh-cn.mdx @@ -1,29 +1,29 @@ --- -title: "Module 6: Objects" -description: "Master JavaScript object literals, methods, and manipulation techniques" +title: "模块 6:对象" +description: "掌握 JavaScript 对象字面量、方法和操作技巧" --- -## Module 6: Objects +## 模块 6:对象 -JavaScript objects are quite different from Java objects. They're dynamic, mutable collections of properties. Let's explore how to work with objects effectively. +JavaScript 对象与 Java 对象有很大不同。它们是动态的、可变的属性集合。让我们探索如何有效地使用对象。 -## Learning Objectives +## 学习目标 -By the end of this module, you will: -✅ Understand object literals and creation -✅ Master property access (dot vs bracket notation) -✅ Learn object methods and computed properties -✅ Understand property descriptors and attributes -✅ Know how to clone and merge objects -✅ Master object destructuring +完成本模块后,您将: +✅ 理解对象字面量和创建 +✅ 掌握属性访问(点号 vs 括号表示法) +✅ 学习对象方法和计算属性 +✅ 理解属性描述符和特性 +✅ 知道如何克隆和合并对象 +✅ 掌握对象解构 -## Object Literals +## 对象字面量 -JavaScript objects are created using literal syntax: +JavaScript 对象使用字面量语法创建: - + ```java !! java -// Java - Classes required +// Java - 需要类 public class User { private String name; private int age; @@ -41,7 +41,7 @@ User user = new User("John", 25); ``` ```javascript !! js -// JavaScript - Object literals (simple and direct) +// JavaScript - 对象字面量(简单直接) const user = { name: "John", age: 25 @@ -50,17 +50,17 @@ const user = { console.log(user.name); // "John" console.log(user.age); // 25 -// Can add properties anytime +// 可以随时添加属性 user.email = "john@example.com"; user.isActive = true; -// Can delete properties +// 可以删除属性 delete user.isActive; -// Empty object +// 空对象 const empty = {}; -// Nested objects +// 嵌套对象 const person = { name: "John", address: { @@ -74,47 +74,47 @@ console.log(person.address.city); // "NYC" ``` -### Property Access +### 属性访问 - + ```java !! java -// Java - Direct field access or getters/setters +// Java - 直接字段访问或 getter/setter User user = new User("John", 25); String name = user.getName(); // Getter user.setAge(26); // Setter -// Direct field (if public) +// 直接访问字段(如果是 public) user.name = "Jane"; ``` ```javascript !! js -// JavaScript - Dot notation vs bracket notation +// JavaScript - 点号表示法 vs 括号表示法 const user = { name: "John", age: 25, - "first name": "John", // Property with space - "user-email": "john@example.com" // Invalid as identifier + "first name": "John", // 带空格的属性 + "user-email": "john@example.com" // 作为标识符无效 }; -// Dot notation (most common) +// 点号表示法(最常见) console.log(user.name); // "John" -// Bracket notation (for dynamic keys or special characters) +// 括号表示法(用于动态键或特殊字符) console.log(user["first name"]); // "John" console.log(user["user-email"]); // "john@example.com" -// Bracket notation with variables +// 括号表示法与变量 const key = "age"; console.log(user[key]); // 25 -// Dynamic property access +// 动态属性访问 function getProperty(obj, prop) { return obj[prop]; } console.log(getProperty(user, "name")); // "John" -// Nested access +// 嵌套访问 const config = { server: { port: 3000, @@ -126,7 +126,7 @@ const serverKey = "server"; const portKey = "port"; console.log(config[serverKey][portKey]); // 3000 -// Optional chaining (ES2020) +// 可选链(ES2020) const data = { user: { address: { @@ -136,24 +136,24 @@ const data = { }; console.log(data.user?.address?.city); // "NYC" -console.log(data.user?.phone?.number); // undefined (no error) +console.log(data.user?.phone?.number); // undefined(不会报错) console.log(data.nonExistent?.property); // undefined ``` -### Computed Properties +### 计算属性 - + ```java !! java -// Java - Dynamic properties not typical -// Would use Map +// Java - 动态属性不常见 +// 会使用 Map Map data = new HashMap<>(); data.put("field_" + 1, "value1"); data.put("field_" + 2, "value2"); ``` ```javascript !! js -// JavaScript - Computed property names (ES6+) +// JavaScript - 计算属性名(ES6+) const prefix = "user"; const id = 1; @@ -165,7 +165,7 @@ const user = { console.log(user.user_1); // "John" console.log(user.user_2); // "Jane" -// Dynamic method names +// 动态方法名 const methodName = "greet"; const calc = { [methodName]() { @@ -179,7 +179,7 @@ const calc = { console.log(calc.greet()); // "Hello!" console.log(calc.calc_sum(5, 3)); // 8 -// Practical: Create getters/setters dynamically +// 实用:动态创建 getter/setter function createAccessor(propertyName) { return { get [propertyName]() { @@ -202,13 +202,13 @@ console.log(user.name); // "Jane" ``` -## Object Methods +## 对象方法 -JavaScript provides several methods for working with objects: +JavaScript 提供了多种处理对象的方法: - + ```java !! java -// Java - Reflection +// Java - 反射 import java.lang.reflect.Field; User user = new User("John", 25); @@ -222,7 +222,7 @@ for (Field field : fields) { ``` ```javascript !! js -// JavaScript - Object methods +// JavaScript - Object 方法 const user = { name: "John", @@ -230,66 +230,66 @@ const user = { email: "john@example.com" }; -// Object.keys(): Get property names +// Object.keys(): 获取属性名 const keys = Object.keys(user); console.log(keys); // ["name", "age", "email"] -// Object.values(): Get property values +// Object.values(): 获取属性值 const values = Object.values(user); console.log(values); // ["John", 25, "john@example.com"] -// Object.entries(): Get [key, value] pairs +// Object.entries(): 获取 [键, 值] 对 const entries = Object.entries(user); console.log(entries); // [["name", "John"], ["age", 25], ["email", "john@example.com"]] -// Convert entries back to object +// 将 entries 转换回对象 const fromEntries = Object.fromEntries(entries); console.log(fromEntries); // { name: "John", age: 25, email: "john@example.com" } -// Object.assign(): Copy properties +// Object.assign(): 复制属性 const target = { a: 1, b: 2 }; const source = { b: 3, c: 4 }; const merged = Object.assign(target, source); console.log(merged); // { a: 1, b: 3, c: 4 } -// Note: target is modified! +// 注意:target 被修改了! -// Safer: Spread operator +// 更安全:展开运算符 const merged2 = { ...target, ...source }; console.log(merged2); // { a: 1, b: 3, c: 4 } -// target is unchanged +// target 未改变 -// Object.freeze(): Make immutable +// Object.freeze(): 使其不可变 const frozen = Object.freeze({ name: "John" }); -frozen.name = "Jane"; // Silently fails (strict mode: error) +frozen.name = "Jane"; // 静默失败(严格模式下报错) console.log(frozen.name); // "John" -// Object.seal(): Prevent adding/removing properties +// Object.seal(): 防止添加/删除属性 const sealed = Object.seal({ name: "John" }); -sealed.name = "Jane"; // Allowed -sealed.age = 25; // Fails +sealed.name = "Jane"; // 允许 +sealed.age = 25; // 失败 console.log(sealed); // { name: "Jane" } -// Check state +// 检查状态 console.log(Object.isFrozen(frozen)); // true console.log(Object.isSealed(sealed)); // true ``` -### Property Descriptors +### 属性描述符 - + ```java !! java -// Java - No direct equivalent -// Properties are determined by fields and methods +// Java - 没有直接等价物 +// 属性由字段和方法决定 ``` ```javascript !! js -// JavaScript - Fine-grained property control +// JavaScript - 细粒度属性控制 const user = { name: "John" }; -// Get property descriptor +// 获取属性描述符 const descriptor = Object.getOwnPropertyDescriptor(user, "name"); console.log(descriptor); // { @@ -299,19 +299,19 @@ console.log(descriptor); // configurable: true // } -// Define property with descriptor +// 使用描述符定义属性 const person = {}; Object.defineProperty(person, "name", { value: "John", - writable: false, // Cannot be changed - enumerable: true, // Shows up in loops - configurable: false // Cannot be deleted or reconfigured + writable: false, // 不能被修改 + enumerable: true, // 在循环中显示 + configurable: false // 不能被删除或重新配置 }); -person.name = "Jane"; // Silently fails +person.name = "Jane"; // 静默失败 console.log(person.name); // "John" -// Getters and setters +// Getter 和 setter const user2 = { _firstName: "John", _lastName: "Doe", @@ -333,7 +333,7 @@ console.log(user2.firstName); // "John" user2.firstName = "Jane"; console.log(user2.fullName); // "Jane Doe" -// Define properties with getters/setters +// 使用 getter/setter 定义属性 const account = { _balance: 0, @@ -351,10 +351,10 @@ const account = { account.balance = 100; console.log(account.balance); // 100 -account.balance = -50; // Validation fails -console.log(account.balance); // 100 (unchanged) +account.balance = -50; // 验证失败 +console.log(account.balance); // 100(未改变) -// Multiple properties +// 多个属性 Object.defineProperties(user, { firstName: { value: "John", @@ -373,18 +373,18 @@ Object.defineProperties(user, { ``` -## Object Destructuring +## 对象解构 -Destructuring unpacks properties into variables: +解构将属性解包到变量中: - + ```java !! java -// Java - Manual extraction +// Java - 手动提取 User user = new User("John", 25); String name = user.getName(); int age = user.getAge(); -// Or use objects/tuples (Java 16+) +// 或使用对象/元组(Java 16+) record User(String name, int age) {} User user = new User("John", 25); String name = user.name(); @@ -392,7 +392,7 @@ int age = user.age(); ``` ```javascript !! js -// JavaScript - Object destructuring +// JavaScript - 对象解构 const user = { name: "John", @@ -401,20 +401,20 @@ const user = { city: "NYC" }; -// Basic destructuring +// 基本解构 const { name, age } = user; console.log(name); // "John" console.log(age); // 25 -// Different variable names +// 不同的变量名 const { name: userName, age: userAge } = user; console.log(userName); // "John" -// Default values +// 默认值 const { name: n, country = "USA" } = user; -console.log(country); // "USA" (user didn't have country) +console.log(country); // "USA"(user 没有 country 属性) -// Nested destructuring +// 嵌套解构 const data = { user: { name: "John", @@ -429,19 +429,19 @@ const { user: { name, address: { city } } } = data; console.log(name); // "John" console.log(city); // "NYC" -// Rest operator +// 剩余运算符 const { name, ...rest } = user; console.log(name); // "John" console.log(rest); // { age: 25, email: "john@example.com", city: "NYC" } -// Destructuring in function parameters +// 函数参数中的解构 function greet({ name, age = 30 }) { console.log(`Hello ${name}, you are ${age}`); } greet(user); // "Hello John, you are 25" -// Destructuring with arrays +// 数组解构 const users = [ { id: 1, name: "Alice" }, { id: 2, name: "Bob" } @@ -451,7 +451,7 @@ const [{ id: firstId }, { name: secondName }] = users; console.log(firstId); // 1 console.log(secondName); // "Bob" -// Practical: API response +// 实用:API 响应 function processUser({ data: { user: { name, email }, meta } }) { console.log(name, email, meta); } @@ -465,12 +465,12 @@ processUser({ ``` -## Object Cloning and Merging +## 对象克隆和合并 - + ```java !! java -// Java - Clone not straightforward -// Need to implement Cloneable or use copy constructors +// Java - 克隆不简单 +// 需要实现 Cloneable 或使用拷贝构造函数 public class User implements Cloneable { private String name; @@ -492,19 +492,19 @@ User copy = new User(original); ``` ```javascript !! js -// JavaScript - Multiple ways to clone +// JavaScript - 多种克隆方式 -// Shallow clone with spread +// 使用展开运算符进行浅克隆 const original = { name: "John", age: 25 }; const clone = { ...original }; clone.name = "Jane"; -console.log(original.name); // "John" (unchanged) +console.log(original.name); // "John"(未改变) -// Object.assign() for shallow clone +// 使用 Object.assign() 进行浅克隆 const clone2 = Object.assign({}, original); -// ⚠️ Shallow clone issue with nested objects +// ⚠️ 浅克隆问题:嵌套对象 const nested = { user: { name: "John", @@ -518,18 +518,18 @@ const shallow = { ...nested }; shallow.user.name = "Jane"; shallow.user.address.city = "LA"; -console.log(nested.user.name); // "Jane" (changed!) -console.log(nested.user.address.city); // "LA" (changed!) +console.log(nested.user.name); // "Jane"(改变了!) +console.log(nested.user.address.city); // "LA"(改变了!) -// Deep clone with JSON +// 使用 JSON 进行深克隆 const deep1 = JSON.parse(JSON.stringify(nested)); deep1.user.name = "Alice"; -console.log(nested.user.name); // "Jane" (unchanged) +console.log(nested.user.name); // "Jane"(未改变) -// Deep clone with structuredClone (modern) +// 使用 structuredClone 进行深克隆(现代) const deep2 = structuredClone(nested); -// Deep clone utility +// 深克隆工具函数 function deepClone(obj) { if (obj === null || typeof obj !== "object") { return obj; @@ -552,7 +552,7 @@ function deepClone(obj) { return cloned; } -// Merging objects +// 合并对象 const defaults = { theme: "light", language: "en", @@ -564,12 +564,12 @@ const userConfig = { timeout: 10000 }; -// Merge (userConfig overrides defaults) +// 合并(userConfig 覆盖 defaults) const config = { ...defaults, ...userConfig }; console.log(config); // { theme: "light", language: "fr", timeout: 10000 } -// Deep merge utility +// 深度合并工具函数 function deepMerge(target, ...sources) { if (!sources.length) return target; const source = sources.shift(); @@ -600,22 +600,22 @@ console.log(merged); ``` -## This Context in Objects +## 对象中的 This 上下文 - + ```java !! java -// Java - 'this' always refers to current instance +// Java - 'this' 始终引用当前实例 public class Counter { private int count = 0; public void increment() { - this.count++; // 'this' is the Counter instance + this.count++; // 'this' 是 Counter 实例 } } ``` ```javascript !! js -// JavaScript - 'this' depends on call site +// JavaScript - 'this' 取决于调用位置 const user = { name: "John", @@ -625,32 +625,32 @@ const user = { console.log(`Hello, I'm ${this.name}`); }, - // Arrow function: 'this' from surrounding scope + // 箭头函数:'this' 来自外部作用域 greetArrow: () => { console.log(`Hello, I'm ${this.name}`); // undefined! }, - // Nested function issue + // 嵌套函数问题 nestedGreet() { - // 'this' works here + // 'this' 在这里有效 console.log(this.name); setTimeout(function() { - // 'this' is lost here! + // 'this' 在这里丢失了! console.log(this.name); // undefined }, 1000); - // Solution 1: Arrow function + // 解决方案 1:箭头函数 setTimeout(() => { - console.log(this.name); // "John" - works! + console.log(this.name); // "John" - 有效! }, 1000); - // Solution 2: Bind + // 解决方案 2:Bind setTimeout(function() { console.log(this.name); // "John" }.bind(this), 1000); - // Solution 3: Capture this + // 解决方案 3:捕获 this const self = this; setTimeout(function() { console.log(self.name); // "John" @@ -661,27 +661,27 @@ const user = { user.greet(); // "Hello, I'm John" user.greetArrow(); // "Hello, I'm undefined" -// 'this' depends on call site +// 'this' 取决于调用位置 const greet = user.greet; -greet(); // "Hello, I'm undefined" (not called as method) +greet(); // "Hello, I'm undefined"(不是作为方法调用) -// Explicit this binding +// 显式 this 绑定 const greet2 = user.greet.bind(user); greet2(); // "Hello, I'm John" -// Call and apply +// Call 和 apply user.greet.call({ name: "Jane" }); // "Hello, I'm Jane" user.greet.apply({ name: "Bob" }); // "Hello, I'm Bob" ``` -## Common Patterns +## 常见模式 -### Pattern 1: Factory Functions +### 模式 1:工厂函数 - + ```java !! java -// Java - Static factory methods +// Java - 静态工厂方法 public class User { private String name; private int age; @@ -691,13 +691,13 @@ public class User { } public static User fromJson(String json) { - // Parse and create + // 解析并创建 } } ``` ```javascript !! js -// JavaScript - Factory functions (simple, no classes needed) +// JavaScript - 工厂函数(简单,不需要类) function createUser(name, age) { return { @@ -708,18 +708,18 @@ function createUser(name, age) { }, incrementAge() { this.age++; - return this; // Chaining + return this; // 链式调用 } }; } const user = createUser("John", 25); user.greet(); // "Hi, I'm John" -user.incrementAge().greet(); // Chaining +user.incrementAge().greet(); // 链式调用 -// Factory with closures (private data) +// 使用闭包的工厂(私有数据) function createCounter() { - let count = 0; // Private + let count = 0; // 私有 return { increment() { @@ -738,21 +738,21 @@ function createCounter() { const counter = createCounter(); console.log(counter.increment().increment().getCount()); // 2 -console.log(counter.count); // undefined (truly private) +console.log(counter.count); // undefined(真正私有) ``` -### Pattern 2: Options Object Pattern +### 模式 2:选项对象模式 - + ```javascript !! js -// Function with many parameters (bad) +// 带多个参数的函数(不好) function createUser(name, age, email, active, role, permissions) { - // Hard to remember parameter order - // Can't skip optional parameters + // 难以记住参数顺序 + // 不能跳过可选参数 } -// Options object pattern (good) +// 选项对象模式(好) function createUser(options) { const defaults = { active: true, @@ -778,13 +778,13 @@ console.log(user); // { // name: "John", // age: 25, -// active: true, // from defaults +// active: true, // 来自 defaults // role: "admin", -// permissions: [], // from defaults +// permissions: [], // 来自 defaults // createdAt: Date // } -// With destructuring +// 使用解构 function createUser2({ name, age, active = true, role = "user" } = {}) { return { name, age, active, role, createdAt: new Date() }; } @@ -794,11 +794,11 @@ console.log(user2); ``` -## Best Practices +## 最佳实践 - + ```java !! java -// Java: Use classes, encapsulation +// Java:使用类、封装 public class User { private String name; private int age; @@ -811,28 +811,28 @@ public class User { public String getName() { return name; } public void setName(String name) { this.name = name; } - // Getters/setters for all fields + // 所有字段的 getter/setter } ``` ```javascript !! js -// JavaScript: Embrace object literals +// JavaScript:拥抱对象字面量 -// 1. Use object literals for simple data +// 1. 对简单数据使用对象字面量 const user = { name: "John", age: 25 }; -// 2. Use computed properties for dynamic keys +// 2. 对动态键使用计算属性 const key = "user_" + Date.now(); const users = { [key]: { name: "John" } }; -// 3. Use method shorthand +// 3. 使用方法简写 const calculator = { - add(a, b) { // Not: add: function(a, b) + add(a, b) { // 不是:add: function(a, b) return a + b; }, @@ -841,41 +841,41 @@ const calculator = { } }; -// 4. Use destructuring for clean property access +// 4. 使用解构进行清晰的属性访问 const { name, age } = getUser(); -// 5. Use spread for immutable updates +// 5. 使用展开进行不可变更新 const updatedUser = { ...user, age: 26 }; -// 6. Use Object.freeze for constants +// 6. 使用 Object.freeze 定义常量 const CONFIG = Object.freeze({ apiUrl: "https://api.example.com", timeout: 5000 }); -// 7. Use optional chaining for safe access +// 7. 使用可选链进行安全访问 const city = user?.address?.city ?? "Unknown"; -// 8. Avoid prototypes with object literals -// (we'll cover classes in module 7) +// 8. 对象字面量避免使用原型 +//(我们将在模块 7 中讨论类) ``` -## Exercises +## 练习 -### Exercise 1: Object Manipulation +### 练习 1:对象操作 ```javascript const user = { name: "John", age: 25, email: "john@example.com" }; -// 1. Add phone property -// 2. Remove email property -// 3. Update age to 26 -// 4. Clone the object -// 5. Get all property names -// 6. Get all property values +// 1. 添加 phone 属性 +// 2. 删除 email 属性 +// 3. 将 age 更新为 26 +// 4. 克隆对象 +// 5. 获取所有属性名 +// 6. 获取所有属性值 ``` -### Exercise 2: Destructuring +### 练习 2:解构 ```javascript const config = { server: { @@ -888,82 +888,82 @@ const config = { } }; -// Extract: serverHost, serverPort, dbName +// 提取:serverHost, serverPort, dbName ``` -### Exercise 3: Merge Objects +### 练习 3:合并对象 ```javascript const defaults = { theme: "light", lang: "en", timeout: 5000 }; const userSettings = { lang: "fr", notifications: true }; -// Merge with userSettings overriding defaults +// 合并,userSettings 覆盖 defaults ``` -### Exercise 4: Factory Function +### 练习 4:工厂函数 ```javascript -// Create a factory function that makes rectangle objects -// with: width, height, area() method, perimeter() method +// 创建一个工厂函数,制作矩形对象 +// 包含:width、height、area() 方法、perimeter() 方法 function createRectangle(width, height) { - // Return object with methods + // 返回带有方法的对象 } ``` -## Summary +## 总结 -### Key Takeaways +### 关键要点 -1. **Object Literals:** - - Simple, direct syntax - - Can add/remove properties anytime - - Supports nested structures +1. **对象字面量:** + - 简单直接的语法 + - 可以随时添加/删除属性 + - 支持嵌套结构 -2. **Property Access:** - - Dot notation for known keys - - Bracket notation for dynamic/special keys - - Optional chaining for safe access +2. **属性访问:** + - 点号表示法用于已知键 + - 括号表示法用于动态/特殊键 + - 可选链用于安全访问 -3. **Object Methods:** - - `Object.keys()`: Get property names - - `Object.values()`: Get property values - - `Object.entries()`: Get key-value pairs - - `Object.assign()`: Merge objects +3. **对象方法:** + - `Object.keys()`: 获取属性名 + - `Object.values()`: 获取属性值 + - `Object.entries()`: 获取键值对 + - `Object.assign()`: 合并对象 -4. **Destructuring:** - - Extract properties cleanly - - Support for default values - - Renaming and nested destructuring +4. **解构:** + - 清晰地提取属性 + - 支持默认值 + - 重命名和嵌套解构 -5. **Cloning:** - - Spread for shallow clone - - `structuredClone()` for deep clone - - Watch out for nested objects +5. **克隆:** + - 展开运算符用于浅克隆 + - `structuredClone()` 用于深克隆 + - 注意嵌套对象 -6. **This Context:** - - Depends on call site - - Arrow functions capture surrounding this - - Use `bind()` for explicit binding +6. **This 上下文:** + - 取决于调用位置 + - 箭头函数捕获外部 this + - 使用 `bind()` 进行显式绑定 -### Comparison Table: Java vs JavaScript +### 对比表:Java vs JavaScript -| Feature | Java | JavaScript | +| 特性 | Java | JavaScript | |---------|------|------------| -| **Creation** | `new Class()` | Literal `{}` | -| **Properties** | Fixed (fields) | Dynamic | -| **Methods** | Class methods | Object functions | -| **Access** | Direct or getters/setters | Dot or bracket notation | -| **This** | Always current instance | Depends on call site | -| **Cloning** | Manual or Cloneable | Spread or structuredClone | -| **Immutable** | `final` fields | `Object.freeze()` | - -## What's Next? - -You've mastered JavaScript objects! Next up is **Module 7: Classes and Inheritance**, where we'll explore: - -- ES6 class syntax -- Constructors and methods -- Inheritance with `extends` -- Super keyword -- Static methods and properties -- Private fields (ES2022) - -Ready to explore JavaScript's class system? Let's continue! +| **创建** | `new Class()` | 字面量 `{}` | +| **属性** | 固定(字段) | 动态 | +| **方法** | 类方法 | 对象函数 | +| **访问** | 直接或 getter/setter | 点号或括号表示法 | +| **This** | 始终是当前实例 | 取决于调用位置 | +| **克隆** | 手动或 Cloneable | 展开或 structuredClone | +| **不可变** | `final` 字段 | `Object.freeze()` | + +## 下一步? + +您已掌握 JavaScript 对象!接下来是 **模块 7:类和继承**,我们将探索: + +- ES6 类语法 +- 构造函数和方法 +- 使用 `extends` 的继承 +- Super 关键字 +- 静态方法和属性 +- 私有字段(ES2022) + +准备好探索 JavaScript 的类系统了吗?让我们继续! diff --git a/content/docs/java2js/module-07-classes.zh-cn.mdx b/content/docs/java2js/module-07-classes.zh-cn.mdx index 352d39b..308893a 100644 --- a/content/docs/java2js/module-07-classes.zh-cn.mdx +++ b/content/docs/java2js/module-07-classes.zh-cn.mdx @@ -1,27 +1,27 @@ --- -title: "Module 7: Classes and Inheritance" -description: "Learn ES6 classes, inheritance, and object-oriented patterns in JavaScript" +title: "模块 7:类与继承" +description: "学习 ES6 类、继承和 JavaScript 中的面向对象模式" --- -## Module 7: Classes and Inheritance +## 模块 7:类与继承 -JavaScript introduced class syntax in ES6 (2015), making object-oriented programming more familiar to Java developers. However, JavaScript classes are syntactic sugar over prototypes and work differently from Java classes. +JavaScript 在 ES6 (2015) 中引入了类语法,使面向对象编程对 Java 开发者来说更加熟悉。然而,JavaScript 类是原型之上的语法糖,与 Java 类的工作方式不同。 -## Learning Objectives +## 学习目标 -By the end of this module, you will: -✅ Understand ES6 class syntax -✅ Master constructors and methods -✅ Learn inheritance with `extends` -✅ Understand the `super` keyword -✅ Know static methods and properties -✅ Learn about private fields (ES2022) +完成本模块后,你将: +✅ 理解 ES6 类语法 +✅ 掌握构造函数和方法 +✅ 学习使用 `extends` 进行继承 +✅ 理解 `super` 关键字 +✅ 了解静态方法和属性 +✅ 学习私有字段 (ES2022) -## Class Syntax +## 类语法 - + ```java !! java -// Java - Traditional class +// Java - 传统类 public class User { private String name; private int age; @@ -48,25 +48,25 @@ public class User { } } -// Usage +// 使用 User user = new User("John", 25); user.greet(); ``` ```javascript !! js -// JavaScript - ES6 class syntax +// JavaScript - ES6 类语法 class User { - // Field declarations (ES2022+) + // 字段声明 (ES2022+) name; age; - // Constructor + // 构造函数 constructor(name, age) { this.name = name; this.age = age; } - // Instance method + // 实例方法 greet() { console.log(`Hello, I'm ${this.name}`); } @@ -82,23 +82,23 @@ class User { } } -// Usage +// 使用 const user = new User("John", 25); user.greet(); // "Hello, I'm John" console.log(user.info); // "John (25)" -// Classes are first-class citizens +// 类是一等公民 const UserClass = User; const user2 = new UserClass("Jane", 30); ``` -### Hoisting +### 提升 - + ```java !! java -// Java - No hoisting -User user = new User(); // Compilation error if User not defined +// Java - 没有提升 +User user = new User(); // 如果 User 未定义则编译错误 public class User { // ... @@ -106,8 +106,8 @@ public class User { ``` ```javascript !! js -// JavaScript - Classes are NOT hoisted -// ❌ BAD: Using class before declaration +// JavaScript - 类不会被提升 +// ❌ 错误:在声明前使用类 const user = new User(); // ReferenceError class User { @@ -116,17 +116,17 @@ class User { } } -// ✅ GOOD: Declare class first +// ✅ 正确:先声明类 class User2 { constructor(name) { this.name = name; } } -const user2 = new User2("John"); // Works +const user2 = new User2("John"); // 可以工作 -// Function declarations ARE hoisted -const user3 = createUser("Bob"); // Works! +// 函数声明会被提升 +const user3 = createUser("Bob"); // 可以工作! function createUser(name) { return { name }; @@ -134,11 +134,11 @@ function createUser(name) { ``` -### Class Expressions +### 类表达式 - + ```java !! java -// Java - Anonymous classes +// Java - 匿名类 Runnable runnable = new Runnable() { @Override public void run() { @@ -151,7 +151,7 @@ Runnable r2 = () -> System.out.println("Running"); ``` ```javascript !! js -// JavaScript - Class expressions +// JavaScript - 类表达式 const User = class { constructor(name) { this.name = name; @@ -165,14 +165,14 @@ const User = class { const user = new User("John"); user.greet(); -// Named class expression (better for debugging) +// 命名类表达式(更适合调试) const User2 = class UserClass { constructor(name) { this.name = name; } }; -// Can be exported immediately +// 可以立即导出 export default class { constructor() { // ... @@ -181,11 +181,11 @@ export default class { ``` -## Inheritance +## 继承 - + ```java !! java -// Java - Extends keyword +// Java - Extends 关键字 public class Animal { protected String name; @@ -202,7 +202,7 @@ public class Dog extends Animal { private String breed; public Dog(String name, String breed) { - super(name); // Call parent constructor + super(name); // 调用父类构造函数 this.breed = breed; } @@ -218,7 +218,7 @@ public class Dog extends Animal { ``` ```javascript !! js -// JavaScript - Extends keyword (similar syntax) +// JavaScript - Extends 关键字(类似语法) class Animal { constructor(name) { this.name = name; @@ -231,7 +231,7 @@ class Animal { class Dog extends Animal { constructor(name, breed) { - super(name); // Must call super before using 'this' + super(name); // 必须在使用 'this' 之前调用 super this.breed = breed; } @@ -252,26 +252,26 @@ console.log(dog instanceof Animal); // true ``` -### Super Keyword +### Super 关键字 - + ```java !! java -// Java - Super for parent methods +// Java - Super 用于调用父类方法 public class Dog extends Animal { @Override public void speak() { - super.speak(); // Call parent method + super.speak(); // 调用父类方法 System.out.println("Woof!"); } public void displayInfo() { - System.out.println(super.name); // Access parent field + System.out.println(super.name); // 访问父类字段 } } ``` ```javascript !! js -// JavaScript - Super keyword +// JavaScript - Super 关键字 class Parent { constructor(name) { @@ -289,17 +289,17 @@ class Parent { class Child extends Parent { constructor(name, age) { - super(name); // Call parent constructor (required!) + super(name); // 调用父类构造函数(必需!) this.age = age; } greet() { - // Call parent method + // 调用父类方法 const parentGreeting = super.greet(); return `${parentGreeting} and I'm ${this.age}`; } - // Override static method + // 重写静态方法 static getType() { return `${super.getType()} -> Child`; } @@ -309,7 +309,7 @@ const child = new Child("John", 25); console.log(child.greet()); // "Hello, I'm John and I'm 25" console.log(Child.getType()); // "Parent -> Child" -// Super in object literals +// 对象字面量中的 super const parent = { greet() { return "Hello from parent"; @@ -327,11 +327,11 @@ console.log(child2.greet()); // "Hello from parent and child" ``` -## Static Members +## 静态成员 - + ```java !! java -// Java - Static members +// Java - 静态成员 public class MathUtil { private static final double PI = 3.14159; @@ -344,39 +344,39 @@ public class MathUtil { } } -// Usage +// 使用 double area = MathUtil.circleArea(5.0); String name = MathUtil.Constants.APP_NAME; ``` ```javascript !! js -// JavaScript - Static methods and properties +// JavaScript - 静态方法和属性 class MathUtil { - // Static property (ES2022+) + // 静态属性 (ES2022+) static PI = 3.14159; - // Static method + // 静态方法 static circleArea(radius) { return this.PI * radius * radius; } - // Static methods can't access instance properties + // 静态方法不能访问实例属性 static notValid() { - // console.log(this.name); // Error: undefined + // console.log(this.name); // 错误:undefined } } -// Usage (no instantiation needed) +// 使用(无需实例化) console.log(MathUtil.PI); // 3.14159 console.log(MathUtil.circleArea(5)); // 78.53975 -// Static code block (ES2022+) +// 静态代码块 (ES2022+) class AppConfig { static config = {}; static { - // Runs once when class is defined + // 在类定义时运行一次 this.config = { apiUrl: "https://api.example.com", timeout: 5000 @@ -391,7 +391,7 @@ class AppConfig { console.log(AppConfig.get("apiUrl")); // "https://api.example.com" -// Static methods with inheritance +// 静态方法与继承 class Parent { static staticMethod() { return "Parent"; @@ -400,7 +400,7 @@ class Parent { class Child extends Parent { static staticMethod() { - // Call parent static method + // 调用父类静态方法 return `${super.staticMethod()} -> Child`; } } @@ -409,11 +409,11 @@ console.log(Child.staticMethod()); // "Parent -> Child" ``` -## Private Fields +## 私有字段 - + ```java !! java -// Java - Private fields +// Java - 私有字段 public class BankAccount { private double balance; @@ -434,9 +434,9 @@ public class BankAccount { ``` ```javascript !! js -// JavaScript - Private fields (ES2022+) +// JavaScript - 私有字段 (ES2022+) class BankAccount { - // Private fields (declared with #) + // 私有字段(用 # 声明) #balance; constructor(initialBalance) { @@ -453,7 +453,7 @@ class BankAccount { return this.#balance; } - // Private methods (ES2022+) + // 私有方法 (ES2022+) #validateAmount(amount) { return amount > 0 && !isNaN(amount); } @@ -468,9 +468,9 @@ class BankAccount { const account = new BankAccount(100); account.deposit(50); console.log(account.getBalance()); // 150 -console.log(account.#balance); // SyntaxError! (truly private) +console.log(account.#balance); // 语法错误!(真正私有) -// Private fields are not inherited +// 私有字段不会被继承 class SavingsAccount extends BankAccount { #interestRate; @@ -486,10 +486,10 @@ class SavingsAccount extends BankAccount { } } -// Before private fields: Convention with _ +// 在私有字段之前:使用 _ 约定 class User { constructor(name) { - this._name = name; // Privacy by convention only + this._name = name; // 仅通过约定实现私有 } getName() { @@ -499,11 +499,11 @@ class User { ``` -## Getters and Setters +## Getter 和 Setter - + ```java !! java -// Java - Getters and setters +// Java - Getter 和 setter public class User { private String name; private int age; @@ -531,10 +531,10 @@ public class User { ``` ```javascript !! js -// JavaScript - Getters and setters +// JavaScript - Getter 和 setter class User { constructor(name, age) { - this._name = name; // Convention for backing property + this._name = name; // 支持属性的约定 this._age = age; } @@ -560,7 +560,7 @@ class User { } } - // Computed property + // 计算属性 get info() { return `${this._name} is ${this._age} years old`; } @@ -568,16 +568,16 @@ class User { const user = new User("John", 25); -// Access like properties (not methods!) -console.log(user.name); // "John" (calls getter) -user.name = "Jane"; // Calls setter +// 像属性一样访问(不是方法!) +console.log(user.name); // "John" (调用 getter) +user.name = "Jane"; // 调用 setter console.log(user.name); // "Jane" -user.name = ""; // Fails validation -console.log(user.name); // "Jane" (unchanged) +user.name = ""; // 验证失败 +console.log(user.name); // "Jane" (未改变) console.log(user.info); // "Jane is 25 years old" -// Object literals also support getters/setters +// 对象字面量也支持 getter/setter const person = { _firstName: "John", _lastName: "Doe", @@ -597,14 +597,14 @@ console.log(person.fullName); // "Jane Smith" ``` -## Instance vs Static vs Private +## 实例 vs 静态 vs 私有 - + ```java !! java -// Java - Member types +// Java - 成员类型 public class Example { - private int instanceField; // Instance - private static int staticField; // Static + private int instanceField; // 实例 + private static int staticField; // 静态 public void instanceMethod() { } @@ -613,34 +613,34 @@ public class Example { ``` ```javascript !! js -// JavaScript - All member types +// JavaScript - 所有成员类型 class Example { - // Public instance field + // 公共实例字段 instanceField = 1; - // Static field + // 静态字段 static staticField = 2; - // Private instance field + // 私有实例字段 #privateField = 3; - // Static private field + // 静态私有字段 static #staticPrivate = 4; - // Instance method + // 实例方法 instanceMethod() { - console.log(this.instanceField); // ✓ Access - console.log(this.#privateField); // ✓ Access - console.log(Example.staticField); // ✓ Access - // console.log(Example.#staticPrivate); // ✗ Can't access private static + console.log(this.instanceField); // ✓ 可访问 + console.log(this.#privateField); // ✓ 可访问 + console.log(Example.staticField); // ✓ 可访问 + // console.log(Example.#staticPrivate); // ✗ 不能访问私有静态 } - // Static method + // 静态方法 static staticMethod() { - console.log(this.staticField); // ✓ Access - console.log(this.#staticPrivate); // ✓ Access - // console.log(this.instanceField); // ✗ Can't access instance + console.log(this.staticField); // ✓ 可访问 + console.log(this.#staticPrivate); // ✓ 可访问 + // console.log(this.instanceField); // ✗ 不能访问实例 } } @@ -648,25 +648,25 @@ const ex = new Example(); ex.instanceMethod(); Example.staticMethod(); -// Accessing members +// 访问成员 console.log(ex.instanceField); // 1 console.log(Example.staticField); // 2 -// console.log(ex.#privateField); // SyntaxError! +// console.log(ex.#privateField); // 语法错误! ``` -## Method Overriding and Polymorphism +## 方法重写和多态 - + ```java !! java -// Java - Method overriding +// Java - 方法重写 public class Animal { public void speak() { System.out.println("Animal sound"); } public void perform() { - speak(); // Calls overridden version in subclass + speak(); // 调用子类中的重写版本 } } @@ -678,11 +678,11 @@ public class Dog extends Animal { } Dog dog = new Dog(); -dog.perform(); // "Woof!" (polymorphic call) +dog.perform(); // "Woof!" (多态调用) ``` ```javascript !! js -// JavaScript - Method overriding (works similarly) +// JavaScript - 方法重写(工作方式类似) class Animal { speak() { @@ -690,7 +690,7 @@ class Animal { } perform() { - this.speak(); // Polymorphic - calls subclass version + this.speak(); // 多态 - 调用子类版本 } } @@ -703,7 +703,7 @@ class Dog extends Animal { const dog = new Dog(); dog.perform(); // "Woof!" -// Explicit parent method call +// 显式调用父类方法 class Dog2 extends Animal { speak() { super.speak(); // "Animal sound" @@ -712,22 +712,22 @@ class Dog2 extends Animal { } const dog2 = new Dog2(); -dog2.speak(); // "Animal sound" then "Woof!" +dog2.speak(); // "Animal sound" 然后 "Woof!" ``` -## Common Patterns +## 常见模式 -### Pattern 1: Singleton +### 模式 1: 单例 - + ```java !! java -// Java - Singleton +// Java - 单例 public class Database { private static Database instance; private Database() { - // Private constructor + // 私有构造函数 } public static synchronized Database getInstance() { @@ -740,7 +740,7 @@ public class Database { ``` ```javascript !! js -// JavaScript - Singleton with class +// JavaScript - 使用类的单例 class Database { constructor() { @@ -763,7 +763,7 @@ const db1 = Database.getInstance(); const db2 = Database.getInstance(); console.log(db1 === db2); // true -// Simpler: Use object literal +// 更简单:使用对象字面量 const config = Object.freeze({ apiUrl: "https://api.example.com", timeout: 5000 @@ -771,12 +771,12 @@ const config = Object.freeze({ ``` -### Pattern 2: Mixin Pattern +### 模式 2: Mixin 模式 - + ```java !! java -// Java - Multiple inheritance not supported -// Use interfaces for similar behavior +// Java - 不支持多重继承 +// 使用接口实现类似行为 interface Flyable { void fly(); } @@ -792,7 +792,7 @@ class Duck implements Flyable, Swimmable { ``` ```javascript !! js -// JavaScript - Mixin pattern +// JavaScript - Mixin 模式 const Flyable = { fly() { @@ -813,7 +813,7 @@ const duck = new Duck(); duck.fly(); // "Flying!" duck.swim(); // "Swimming!" -// Mixin factory function +// Mixin 工厂函数 function mix(...mixins) { return function(targetClass) { Object.assign(targetClass.prototype, ...mixins); @@ -829,11 +829,11 @@ bird.fly(); // "Flying!" ``` -### Pattern 3: Factory with Classes +### 模式 3: 类的工厂模式 - + ```javascript !! js -// Abstract base class +// 抽象基类 class Vehicle { constructor(make, model) { if (this.constructor === Vehicle) { @@ -843,7 +843,7 @@ class Vehicle { this.model = model; } - // Abstract method + // 抽象方法 start() { throw new Error("Must implement start()"); } @@ -861,7 +861,7 @@ class Motorcycle extends Vehicle { } } -// Factory function +// 工厂函数 function createVehicle(type, make, model) { switch (type) { case "car": @@ -878,13 +878,13 @@ car.start(); // "Toyota Camry is starting" ``` -## Best Practices +## 最佳实践 - + ```java !! java -// Java: Clear encapsulation +// Java: 清晰的封装 public class User { - private final String name; // Immutable + private final String name; // 不可变 private int age; public User(String name, int age) { @@ -896,7 +896,7 @@ public class User { return name; } - // Validation in setters + // setter 中的验证 public void setAge(int age) { if (age < 0) { throw new IllegalArgumentException("Age cannot be negative"); @@ -907,11 +907,11 @@ public class User { ``` ```javascript !! js -// JavaScript: Similar principles +// JavaScript: 类似原则 -// 1. Use classes for objects with behavior +// 1. 为具有行为的对象使用类 class User { - // Use private fields (ES2022+) + // 使用私有字段 (ES2022+) #name; #age; @@ -935,11 +935,11 @@ class User { } } -// 2. Use object literals for simple data +// 2. 简单数据使用对象字面量 const config = { apiUrl: "https://api.example.com" }; -// 3. Prefer composition over inheritance -// Good +// 3. 优先使用组合而非继承 +// 好 class TodoApp { constructor(storage, logger) { this.storage = storage; @@ -947,21 +947,21 @@ class TodoApp { } } -// Avoid: Deep inheritance chains -// Bad +// 避免:深层继承链 +// 不好 class Animal {} class Mammal extends Animal {} class Dog extends Mammal {} class Labrador extends Dog {} -// 4. Use static methods for utility functions +// 4. 静态方法用于工具函数 class MathUtils { static clamp(value, min, max) { return Math.min(Math.max(value, min), max); } } -// 5. Use getters/setters for computed or validated properties +// 5. getter/setter 用于计算或验证属性 class Temperature { #celsius; @@ -982,108 +982,108 @@ class Temperature { } } -// 6. Always call super() in subclass constructors +// 6. 总是在子类构造函数中调用 super() class Child extends Parent { constructor(name) { - super(name); // Must be first line + super(name); // 必须是第一行 // ... } } ``` -## Exercises +## 练习 -### Exercise 1: Create a Class +### 练习 1: 创建类 ```javascript -// Create a Rectangle class with: -// - width, height properties -// - area() method -// - perimeter() method +// 创建一个 Rectangle 类,包含: +// - width, height 属性 +// - area() 方法 +// - perimeter() 方法 // - get isSquare() getter ``` -### Exercise 2: Inheritance +### 练习 2: 继承 ```javascript -// Create Shape base class -// Create Circle and Rectangle subclasses -// Each should have area() method +// 创建 Shape 基类 +// 创建 Circle 和 Rectangle 子类 +// 每个类都应该有 area() 方法 ``` -### Exercise 3: Private Fields +### 练习 3: 私有字段 ```javascript -// Create BankAccount class with: -// - Private #balance field -// - deposit() and withdraw() methods -// - Validation for negative amounts +// 创建 BankAccount 类,包含: +// - 私有 #balance 字段 +// - deposit() 和 withdraw() 方法 +// - 对负数的验证 ``` -### Exercise 4: Static Methods +### 练习 4: 静态方法 ```javascript -// Create MathUtility class with: -// - Static methods: clamp(), lerp(), randomInRange() -// - Use them without instantiation +// 创建 MathUtility 类,包含: +// - 静态方法: clamp(), lerp(), randomInRange() +// - 无需实例化即可使用 ``` -## Summary +## 总结 -### Key Takeaways +### 关键要点 -1. **Class Syntax:** - - Syntactic sugar over prototypes - - Not hoisted (unlike functions) - - Can be named or anonymous +1. **类语法:** + - 原型之上的语法糖 + - 不会被提升(不像函数) + - 可以命名或匿名 -2. **Inheritance:** - - `extends` for inheritance - - `super` to call parent - - Must call `super()` before `this` +2. **继承:** + - `extends` 用于继承 + - `super` 调用父类 + - 必须在 `this` 之前调用 `super()` -3. **Static Members:** - - `static` keyword - - Accessed via class name - - Don't require instantiation +3. **静态成员:** + - `static` 关键字 + - 通过类名访问 + - 不需要实例化 -4. **Private Fields:** - - `#` prefix (ES2022+) - - Truly encapsulated - - Cannot be accessed outside class +4. **私有字段:** + - `#` 前缀 (ES2022+) + - 真正封装 + - 不能在类外访问 -5. **Getters/Setters:** - - Computed properties - - Validation - - Encapsulation +5. **Getter/Setter:** + - 计算属性 + - 验证 + - 封装 -6. **Best Practices:** - - Classes for behavior, objects for data - - Prefer composition over deep inheritance - - Use private fields for true encapsulation - - Static methods for utilities +6. **最佳实践:** + - 类用于行为,对象用于数据 + - 优先使用组合而非深层继承 + - 使用私有字段实现真正封装 + - 静态方法用于工具函数 -### Comparison Table: Java vs JavaScript +### 比较表: Java vs JavaScript -| Feature | Java | JavaScript | +| 特性 | Java | JavaScript | |---------|------|------------| -| **Syntax** | `class Name { }` | Same (ES6+) | -| **Inheritance** | `extends` (single) | `extends` (single) | -| **Interfaces** | Yes | No (use protocols) | -| **Abstract** | `abstract` keyword | Manual enforcement | -| **Private** | `private` keyword | `#` prefix (ES2022+) | -| **Protected** | `protected` keyword | Convention (`_name`) | -| **Static** | `static` keyword | Same | -| **Final** | `final` keyword | No (use Object.freeze) | -| **Super** | `super.method()` | Same | -| **Constructor** | Same name as class | `constructor()` | - -## What's Next? - -You've mastered JavaScript classes! Next up is **Module 8: Prototypes and Prototype Chain**, where we'll explore: - -- How prototypes work under the hood -- Prototype chain lookup -- Object.create() and prototype inheritance -- Constructor functions -- The relationship between classes and prototypes -- When to use prototypes vs classes - -Ready to understand JavaScript's prototype system? Let's continue! +| **语法** | `class Name { }` | 相同 (ES6+) | +| **继承** | `extends` (单一) | `extends` (单一) | +| **接口** | 支持 | 不支持 (使用协议) | +| **抽象** | `abstract` 关键字 | 手动强制 | +| **私有** | `private` 关键字 | `#` 前缀 (ES2022+) | +| **受保护** | `protected` 关键字 | 约定 (`_name`) | +| **静态** | `static` 关键字 | 相同 | +| **最终** | `final` 关键字 | 不支持 (使用 Object.freeze) | +| **Super** | `super.method()` | 相同 | +| **构造函数** | 与类同名 | `constructor()` | + +## 下一步? + +你已经掌握了 JavaScript 类!接下来是 **模块 8: 原型和原型链**,我们将探索: + +- 原型如何在底层工作 +- 原型链查找 +- Object.create() 和原型继承 +- 构造函数 +- 类和原型之间的关系 +- 何时使用原型 vs 类 + +准备理解 JavaScript 的原型系统了吗?让我们继续! diff --git a/content/docs/java2js/module-08-prototypes.zh-cn.mdx b/content/docs/java2js/module-08-prototypes.zh-cn.mdx index 15030fa..421aad5 100644 --- a/content/docs/java2js/module-08-prototypes.zh-cn.mdx +++ b/content/docs/java2js/module-08-prototypes.zh-cn.mdx @@ -1,29 +1,29 @@ --- -title: "Module 8: Prototypes and Prototype Chain" -description: "Understand JavaScript's prototype system and how it enables inheritance" +title: "模块 8: 原型和原型链" +description: "理解 JavaScript 的原型系统及其如何实现继承" --- -## Module 8: Prototypes and Prototype Chain +## 模块 8: 原型和原型链 -Now that you understand ES6 classes, let's look under the hood at how JavaScript actually works. Classes are syntactic sugar over JavaScript's prototype-based inheritance system. Understanding prototypes is crucial for advanced JavaScript development. +现在你已经了解了 ES6 类,让我们深入了解 JavaScript 的实际工作原理。类是 JavaScript 基于原型的继承系统之上的语法糖。理解原型对于高级 JavaScript 开发至关重要。 -## Learning Objectives +## 学习目标 -By the end of this module, you will: -✅ Understand what prototypes are -✅ Master the prototype chain lookup -✅ Learn Object.create() and prototype inheritance -✅ Understand constructor functions -✅ Know the relationship between classes and prototypes -✅ Learn when to use prototypes vs classes +完成本模块后,你将: +✅ 理解什么是原型 +✅ 掌握原型链查找 +✅ 学习 Object.create() 和原型继承 +✅ 理解构造函数 +✅ 了解类和原型之间的关系 +✅ 学习何时使用原型 vs 类 -## Prototypes vs Classes +## 原型 vs 类 -Java uses class-based inheritance, while JavaScript uses prototype-based inheritance: +Java 使用基于类的继承,而 JavaScript 使用基于原型的继承: - + ```java !! java -// Java - Class-based inheritance +// Java - 基于类的继承 public class Animal { public void speak() { System.out.println("Some sound"); @@ -37,15 +37,15 @@ public class Dog extends Animal { } } -// Instance methods are defined in class -// Each instance has same methods +// 实例方法在类中定义 +// 每个实例有相同的方法 Animal animal = new Dog(); animal.speak(); // "Woof!" ``` ```javascript !! js -// JavaScript - Prototype-based inheritance -// Objects inherit directly from other objects +// JavaScript - 基于原型的继承 +// 对象直接继承自其他对象 const animal = { speak() { @@ -59,11 +59,11 @@ dog.speak = function() { }; dog.speak(); // "Woof!" -// But can still access animal's methods via prototype +// 但仍可以通过原型访问 animal 的方法 delete dog.speak; -dog.speak(); // "Some sound" (from prototype!) +dog.speak(); // "Some sound" (来自原型!) -// Classes are just sugar over prototypes +// 类只是原型的语法糖 class Animal2 { speak() { console.log("Some sound"); @@ -82,22 +82,22 @@ console.log(Object.getPrototypeOf(Dog2.prototype) === Animal2.prototype); // tr ``` -## The Prototype Chain +## 原型链 -Every object has an internal link to another object called its prototype. This forms a chain: +每个对象都有一个指向另一个对象的内部链接,称为其原型。这形成了一个链: - + ```java !! java -// Java - Class hierarchy +// Java - 类层次结构 Object -> Animal -> Dog -// Method lookup: Check class, then parent class +// 方法查找: 检查类,然后父类 Dog dog = new Dog(); -// dog.toString() looks in Dog, then Animal, then Object +// dog.toString() 查找 Dog,然后 Animal,然后 Object ``` ```javascript !! js -// JavaScript - Prototype chain +// JavaScript - 原型链 const grandParent = { name: "GrandParent", greet() { @@ -111,38 +111,38 @@ parent.name = "Parent"; const child = Object.create(parent); child.name = "Child"; -// Lookup walks up the chain +// 查找沿链向上 child.greet(); // "Hello from Child" -// 1. Check child - has name property -// 2. Check child for greet() - not found -// 3. Check parent - no greet() -// 4. Check grandParent - found greet()! +// 1. 检查 child - 有 name 属性 +// 2. 在 child 中查找 greet() - 未找到 +// 3. 检查 parent - 没有 greet() +// 4. 检查 grandParent - 找到 greet()! console.log(child.hasOwnProperty("name")); // true -console.log(child.hasOwnProperty("greet")); // false (inherited) +console.log(child.hasOwnProperty("greet")); // false (继承) -// Full prototype chain +// 完整原型链 console.log(Object.getPrototypeOf(child) === parent); // true console.log(Object.getPrototypeOf(parent) === grandParent); // true console.log(Object.getPrototypeOf(grandParent) === Object.prototype); // true -console.log(Object.getPrototypeOf(Object.prototype) === null); // true (end of chain) +console.log(Object.getPrototypeOf(Object.prototype) === null); // true (链的末尾) -// Property lookup with hasOwnProperty +// 使用 hasOwnProperty 进行属性查找 for (let key in child) { console.log(key, child.hasOwnProperty(key)); - // name true (own property) - // greet false (inherited) + // name true (自有属性) + // greet false (继承) } ``` ## Object.create() -Object.create() creates a new object with a specified prototype: +Object.create() 创建一个具有指定原型的新对象: ```java !! java -// Java - Inheritance requires classes +// Java - 继承需要类 public class Animal { public void speak() { System.out.println("Sound"); @@ -150,14 +150,14 @@ public class Animal { } public class Dog extends Animal { - // Dog inherits from Animal + // Dog 继承自 Animal } ``` ```javascript !! js -// JavaScript - Direct object inheritance +// JavaScript - 直接对象继承 -// Create object with specific prototype +// 创建具有特定原型的对象 const animal = { speak() { console.log("Some sound"); @@ -169,16 +169,16 @@ dog.bark = function() { console.log("Woof!"); }; -dog.speak(); // "Some sound" (from prototype) -dog.bark(); // "Woof!" (own property) +dog.speak(); // "Some sound" (来自原型) +dog.bark(); // "Woof!" (自有属性) console.log(Object.getPrototypeOf(dog) === animal); // true -// Create null prototype (no inherited methods) +// 创建 null 原型(无继承方法) const empty = Object.create(null); -empty.toString(); // TypeError! (no Object.prototype methods) +empty.toString(); // TypeError! (无 Object.prototype 方法) -// Object.create with property descriptors +// Object.create 与属性描述符 const person = Object.create(Object.prototype, { name: { value: "John", @@ -194,7 +194,7 @@ const person = Object.create(Object.prototype, { console.log(person.name); // "John" -// Practical: Create object with default methods +// 实际: 创建具有默认方法的对象 const withLogging = { log(method) { return function(...args) { @@ -215,17 +215,17 @@ calculator.multiply = function(a, b) { return a * b; }; -calculator.log("add")(5, 3); // Logs then returns 8 +calculator.log("add")(5, 3); // 记录日志然后返回 8 ``` -## Constructor Functions +## 构造函数 -Before ES6 classes, JavaScript used constructor functions: +在 ES6 类之前,JavaScript 使用构造函数: - + ```java !! java -// Java - Class constructors +// Java - 类构造函数 public class User { private String name; @@ -238,13 +238,13 @@ User user = new User("John"); ``` ```javascript !! js -// JavaScript - Constructor functions (pre-ES6) +// JavaScript - 构造函数 (ES6 之前) function User(name) { this.name = name; } -// Methods added to prototype +// 方法添加到原型 User.prototype.greet = function() { console.log(`Hello, I'm ${this.name}`); }; @@ -253,17 +253,17 @@ User.prototype.getAge = function() { return this.age; }; -// Create instance with 'new' +// 使用 'new' 创建实例 const user = new User("John"); user.greet(); // "Hello, I'm John" -// What 'new' does: -// 1. Creates new object -// 2. Sets object's __proto__ to constructor's prototype -// 3. Executes constructor with 'this' = new object -// 4. Returns new object (or explicit return) +// 'new' 做什么: +// 1. 创建新对象 +// 2. 设置对象的 __proto__ 为构造函数的 prototype +// 3. 用 'this' = 新对象执行构造函数 +// 4. 返回新对象(或显式返回) -// Manual equivalent of 'new' +// 手动等效的 'new' function createFromConstructor(Constructor, ...args) { const obj = Object.create(Constructor.prototype); const result = Constructor.apply(obj, args); @@ -273,15 +273,15 @@ function createFromConstructor(Constructor, ...args) { const user2 = createFromConstructor(User, "Jane"); user2.greet(); // "Hello, I'm Jane" -// Check instance +// 检查实例 console.log(user instanceof User); // true console.log(user instanceof Object); // true -// Constructor property +// 构造函数属性 console.log(User.prototype.constructor === User); // true console.log(user.constructor === User); // true -// Arrow functions cannot be constructors +// 箭头函数不能是构造函数 const BadUser = (name) => { this.name = name; }; @@ -290,35 +290,35 @@ const BadUser = (name) => { ``` -## Prototype Properties vs Instance Properties +## 原型属性 vs 实例属性 - + ```java !! java -// Java - Instance methods (same for all instances) +// Java - 实例方法(所有实例相同) public class User { public void greet() { - // Same method for all User instances + // 所有 User 实例的相同方法 } } -// Static methods (class-level) +// 静态方法(类级别) public static User create(String name) { return new User(name); } ``` ```javascript ^^ js -// JavaScript - Prototype vs instance +// JavaScript - 原型 vs 实例 -// Methods on prototype (shared) +// 原型上的方法(共享) function User(name) { - this.name = name; // Instance property (unique per instance) + this.name = name; // 实例属性(每个实例唯一) } -// Prototype property (shared) +// 原型属性(共享) User.prototype.species = "Homo sapiens"; -// Prototype method (shared) +// 原型方法(共享) User.prototype.greet = function() { console.log(`Hi, I'm ${this.name}`); }; @@ -326,69 +326,69 @@ User.prototype.greet = function() { const user1 = new User("John"); const user2 = new User("Jane"); -console.log(user1.greet === user2.greet); // true (same function) +console.log(user1.greet === user2.greet); // true (相同函数) console.log(user1.species === user2.species); // true -// Override prototype property +// 覆盖原型属性 user1.species = "Alien"; -console.log(user1.species); // "Alien" (instance property) -console.log(user2.species); // "Homo sapiens" (prototype) +console.log(user1.species); // "Alien" (实例属性) +console.log(user2.species); // "Homo sapiens" (原型) -// Delete to reveal prototype again +// 删除以再次显示原型 delete user1.species; -console.log(user1.species); // "Homo sapiens" (from prototype) +console.log(user1.species); // "Homo sapiens" (来自原型) -// When to use prototype vs instance: -// - Methods: prototype (shared) -// - Instance data: constructor (unique) -// - Class constants: prototype or static +// 何时使用原型 vs 实例: +// - 方法: 原型(共享) +// - 实例数据: 构造函数(唯一) +// - 类常量: 原型或静态 -// Modern equivalent with classes +// 现代等效的类 class User2 { - static species = "Homo sapiens"; // Static (class-level) + static species = "Homo sapiens"; // 静态(类级别) constructor(name) { - this.name = name; // Instance + this.name = name; // 实例 } - greet() { // Prototype (shared) + greet() { // 原型(共享) console.log(`Hi, I'm ${this.name}`); } } ``` -## Modifying Prototypes +## 修改原型 -You can modify prototypes even after objects are created: +即使在创建对象之后,你也可以修改原型: - + ```java !! java -// Java - Cannot modify classes at runtime -// (except with bytecode manipulation) +// Java - 不能在运行时修改类 +// (除非使用字节码操作) ``` ```javascript !! js -// JavaScript - Prototypes are modifiable +// JavaScript - 原型是可修改的 const arr = [1, 2, 3]; -// arr.sum() doesn't exist +// arr.sum() 不存在 -// Add method to Array prototype +// 添加方法到 Array 原型 Array.prototype.sum = function() { return this.reduce((a, b) => a + b, 0); }; console.log(arr.sum()); // 6 -// All arrays now have sum() +// 现在所有数组都有 sum() const arr2 = [4, 5, 6]; console.log(arr2.sum()); // 15 -// ⚠️ Be careful modifying built-in prototypes! -// Can break code or cause conflicts +// ⚠️ 修改内置原型要小心! +// 可能破坏代码或导致冲突 -// Better: Create your own class +// 更好: 创建自己的类 class MyArray extends Array { sum() { return this.reduce((a, b) => a + b, 0); @@ -398,7 +398,7 @@ class MyArray extends Array { const myArr = new MyArray(1, 2, 3); console.log(myArr.sum()); // 6 -// Polyfill example (adding missing methods) +// Polyfill 示例(添加缺失的方法) if (!Array.prototype.first) { Array.prototype.first = function() { return this[0]; @@ -415,13 +415,13 @@ console.log("hello".capitalize()); // "Hello" ``` -## Prototype Inheritance Patterns +## 原型继承模式 -### Pattern 1: Prototype Delegation +### 模式 1: 原型委托 - + ```javascript !! js -// Simple prototype chain +// 简单原型链 const animal = { init(name) { this.name = name; @@ -439,19 +439,19 @@ dog.bark = function() { const myDog = Object.create(dog); myDog.init("Buddy"); -myDog.speak(); // "Buddy makes a sound" (from animal) -myDog.bark(); // "Buddy barks" (from dog) +myDog.speak(); // "Buddy makes a sound" (来自 animal) +myDog.bark(); // "Buddy barks" (来自 dog) console.log(myDog instanceof Object); // true -// No way to check instanceof for prototype chains without constructors +// 无法在没有构造函数的原型链中检查 instanceof ``` -### Pattern 2: Constructor Inheritance +### 模式 2: 构造函数继承 - + ```javascript !! js -// Parent constructor +// 父构造函数 function Animal(name) { this.name = name; } @@ -460,34 +460,34 @@ Animal.prototype.speak = function() { console.log(`${this.name} makes a sound`); }; -// Child constructor +// 子构造函数 function Dog(name, breed) { - Animal.call(this, name); // Call parent constructor + Animal.call(this, name); // 调用父构造函数 this.breed = breed; } -// Inherit prototype +// 继承原型 Dog.prototype = Object.create(Animal.prototype); -Dog.prototype.constructor = Dog; // Fix constructor +Dog.prototype.constructor = Dog; // 修复构造函数 -// Add child methods +// 添加子方法 Dog.prototype.bark = function() { console.log(`${this.name} barks`); }; -// Override parent method +// 重写父方法 Dog.prototype.speak = function() { - Animal.prototype.speak.call(this); // Call parent + Animal.prototype.speak.call(this); // 调用父类 this.bark(); }; const dog = new Dog("Buddy", "Labrador"); -dog.speak(); // "Buddy makes a sound" then "Buddy barks" +dog.speak(); // "Buddy makes a sound" 然后 "Buddy barks" console.log(dog instanceof Dog); // true console.log(dog instanceof Animal); // true -// Modern equivalent (classes are easier!) +// 现代等效(类更简单!) class Animal2 { constructor(name) { this.name = name; @@ -513,11 +513,11 @@ class Dog2 extends Animal2 { ``` -### Pattern 3: Functional Inheritance +### 模式 3: 函数式继承 - + ```java !! java -// Java - Composition over inheritance +// Java - 组合优于继承 interface Flyable { void fly(); } @@ -536,7 +536,7 @@ class Bat implements Flyable { ``` ```javascript ^^ js -// JavaScript - Mixins with prototypes +// JavaScript - 使用原型的 Mixin const Flyable = { fly() { @@ -554,14 +554,14 @@ function Duck(name) { this.name = name; } -// Mix in capabilities +// 混合功能 Object.assign(Duck.prototype, Flyable, Swimmable); const duck = new Duck("Donald"); duck.fly(); // "Flying!" duck.swim(); // "Swimming!" -// Or use Object.create with multiple prototypes +// 或使用 Object.create 与多个原型 const flyingSwimmer = Object.assign( Object.create(Object.prototype), Flyable, @@ -574,11 +574,11 @@ duck2.fly(); // "Flying!" ``` -## Prototypes vs Classes +## 原型 vs 类 - + ```javascript !! js -// Prototypes (flexible, dynamic) +// 原型(灵活,动态) const animal = { speak() { console.log("Sound"); @@ -587,14 +587,14 @@ const animal = { const dog = Object.create(animal); -// Can modify prototype anytime +// 可以随时修改原型 animal.speak = function() { console.log("Loud sound"); }; -dog.speak(); // "Loud sound" (reflects prototype change) +dog.speak(); // "Loud sound" (反映原型变化) -// Classes (structured, familiar) +// 类(结构化,熟悉) class Animal2 { speak() { console.log("Sound"); @@ -602,17 +602,17 @@ class Animal2 { } class Dog2 extends Animal2 { - // Can't easily modify Animal2.prototype after creation - // Better to use inheritance or override methods + // 创建后不能轻易修改 Animal2.prototype + // 最好使用继承或重写方法 } -// When to use each: +// 何时使用: -// Use prototypes when: -// - Need dynamic modification -// - Creating simple object hierarchies -// - Want memory efficiency (shared methods) -// - Don't need 'new' operator +// 原型用于: +// - 需要动态修改 +// - 创建简单对象层次结构 +// - 想要内存效率(共享方法) +// - 不需要 'new' 操作符 const utils = { log(msg) { @@ -623,11 +623,11 @@ const utils = { } }; -// Use classes when: -// - Need constructor logic -// - Want familiar OOP structure -// - Need instanceof checks -// - Building complex applications +// 类用于: +// - 需要构造函数逻辑 +// - 想要熟悉的 OOP 结构 +// - 需要 instanceof 检查 +// - 构建复杂应用 class UserService { constructor(apiUrl) { @@ -642,49 +642,49 @@ class UserService { ``` -## Performance Considerations +## 性能考虑 - + ```javascript !! js -// Prototype lookup has a cost +// 原型查找有成本 const obj = { a: 1, b: 2, c: 3 }; -// Direct property access (fast) +// 直接属性访问(快) console.log(obj.a); -// Deep prototype chain (slower) +// 深原型链(慢) const proto1 = { x: 1 }; const proto2 = Object.create(proto1); const proto3 = Object.create(proto2); const obj2 = Object.create(proto3); -// Many lookups needed to find x -console.log(obj2.x); // Checks obj2, proto3, proto2, proto1 +// 需要多次查找来找到 x +console.log(obj2.x); // 检查 obj2, proto3, proto2, proto1 -// Shallow prototype chain (faster) +// 浅原型链(快) const obj3 = Object.create({ x: 1 }); -console.log(obj3.x); // Only one lookup +console.log(obj3.x); // 只有一次查找 -// Caching frequently accessed prototype properties +// 缓存频繁访问的原型属性 function fastAccess(obj) { const proto = Object.getPrototypeOf(obj); const method = proto.method; - // Use cached reference + // 使用缓存的引用 return function() { return method.call(obj); }; } -// Own properties are faster than prototype lookups +// 自有属性比原型查找快 const fast = { name: "Fast" }; const slow = Object.create({ name: "Slow" }); -// Direct access vs prototype lookup +// 直接访问 vs 原型查找 console.time("direct"); for (let i = 0; i < 1000000; i++) { fast.name; @@ -695,33 +695,33 @@ console.time("prototype"); for (let i = 0; i < 1000000; i++) { slow.name; } -console.timeEnd("prototype"); // ~3ms (slightly slower) +console.timeEnd("prototype"); // ~3ms (稍慢) ``` -## Common Pitfalls +## 常见陷阱 -### Pitfall 1: Modifying Object.prototype +### 陷阱 1: 修改 Object.prototype - + ```javascript !! js -// ❌ BAD: Modifying Object.prototype affects everything +// ❌ 错误: 修改 Object.prototype 影响一切 Object.prototype.each = function(fn) { for (let key in this) { fn(this[key], key); } }; -// Now even empty objects have 'each' +// 现在即使是空对象也有 'each' const empty = {}; -empty.each(x => console.log(x)); // Unexpected! +empty.each(x => console.log(x)); // 意外! -// Also breaks for...in loops +// 也破坏 for...in 循环 for (let key in empty) { - console.log(key); // "each" (unwanted!) + console.log(key); // "each" (不想要的!) } -// ✅ GOOD: Use your own base object +// ✅ 正确: 使用自己的基础对象 const MyBaseObject = { each(fn) { for (let key in this) { @@ -733,15 +733,15 @@ const MyBaseObject = { }; const myObj = Object.create(MyyBaseObject); -myObj.each(x => console.log(x)); // Works as expected +myObj.each(x => console.log(x)); // 按预期工作 ``` -### Pitfall 2: Prototype Pollution +### 陷阱 2: 原型污染 - + ```javascript !! js -// Security vulnerability: Prototype pollution +// 安全漏洞: 原型污染 function merge(target, source) { for (let key in source) { @@ -754,7 +754,7 @@ function merge(target, source) { return target; } -// Malicious input +// 恶意输入 const malicious = { __proto__: { isAdmin: true @@ -764,32 +764,32 @@ const malicious = { const empty = {}; merge(empty, malicious); -// Now all objects have isAdmin = true! -console.log(({}).isAdmin); // true (security issue!) +// 现在所有对象都有 isAdmin = true! +console.log(({}).isAdmin); // true (安全问题!) -// ✅ GOOD: Validate keys +// ✅ 正确: 验证键 function safeMerge(target, source) { for (let key in source) { - // Skip dangerous keys + // 跳过危险键 if (key === "__proto__" || key === "constructor" || key === "prototype") { continue; } - // ... rest of merge logic + // ... 其余合并逻辑 } return target; } -// Or use Object.create(null) for pure data objects +// 或使用 Object.create(null) 用于纯数据对象 const pure = Object.create(null); -// pure has no prototype, so no prototype pollution possible +// pure 没有原型,所以不可能原型污染 ``` -## Best Practices +## 最佳实践 - + ```java !! java -// Java: Clear class hierarchy +// Java: 清晰的类层次结构 public abstract class Animal { public abstract void speak(); } @@ -802,9 +802,9 @@ public class Dog extends Animal { ``` ```javascript !! js -// JavaScript: Choose the right approach +// JavaScript: 选择正确的方法 -// 1. Use classes for most cases (modern, familiar) +// 1. 大多数情况下使用类(现代,熟悉) class Animal { speak() { console.log("Sound"); @@ -817,7 +817,7 @@ class Dog extends Animal { } } -// 2. Use Object.create for simple prototypes +// 2. 使用 Object.create 用于简单原型 const animal = { speak() { console.log("Sound"); @@ -826,44 +826,44 @@ const animal = { const dog = Object.create(animal); -// 3. Don't modify built-in prototypes +// 3. 不要修改内置原型 // ❌ Array.prototype.first = function() { return this[0]; }; -// ✅ Create subclass or utility function +// ✅ 创建子类或工具函数 class MyArray extends Array { first() { return this[0]; } } -// 4. Use hasOwnProperty in for...in +// 4. 在 for...in 中使用 hasOwnProperty for (let key in obj) { if (obj.hasOwnProperty(key)) { - // Only own properties + // 只有自有属性 } } -// Or use Object.keys() +// 或使用 Object.keys() Object.keys(obj).forEach(key => { - // Only own enumerable properties + // 只有自有可枚举属性 }); -// 5. Understand instanceof limitations -// Only works with constructors +// 5. 理解 instanceof 限制 +// 只适用于构造函数 const obj = Object.create(null); -obj instanceof Object; // false (no prototype chain) +obj instanceof Object; // false (无原型链) -// Use Object.getPrototypeOf() instead +// 使用 Object.getPrototypeOf() 代替 console.log(Object.getPrototypeOf(obj) === null); // true -// 6. Prefer composition over deep prototype chains -// Bad: Deep inheritance +// 6. 优先使用组合而非深层原型链 +// 不好: 深层继承 class A {} class B extends A {} class C extends B {} class D extends C {} -// Good: Composition +// 好: 组合 class D { constructor(a, b, c) { this.a = a; @@ -874,9 +874,9 @@ class D { ``` -## Exercises +## 练习 -### Exercise 1: Prototype Chain +### 练习 1: 原型链 ```javascript const grandParent = { a: 1 }; const parent = Object.create(grandParent); @@ -884,88 +884,88 @@ parent.b = 2; const child = Object.create(parent); child.c = 3; -// What will these output? +// 这些会输出什么? console.log(child.a); console.log(child.b); console.log(child.hasOwnProperty("a")); console.log(child.hasOwnProperty("b")); ``` -### Exercise 2: Create with Object.create +### 练习 2: 使用 Object.create 创建 ```javascript -// Create a vehicle prototype with start() and stop() methods -// Create car and motorcycle objects that inherit from vehicle +// 创建一个具有 start() 和 stop() 方法的 vehicle 原型 +// 创建继承自 vehicle 的 car 和 motorcycle 对象 ``` -### Exercise 3: Constructor Function +### 练习 3: 构造函数 ```javascript -// Create a Book constructor function -// Add methods to prototype: getTitle(), getAuthor() -// Create instances and test +// 创建一个 Book 构造函数 +// 添加方法到原型: getTitle(), getAuthor() +// 创建实例并测试 ``` -### Exercise 4: Prototype Chain Lookup +### 练习 4: 原型链查找 ```javascript -// Create prototype chain of 3 levels -// Add a method that only exists on level 3 -// Test that level 1 can access it +// 创建 3 层原型链 +// 添加一个只存在于第 3 层的方法 +// 测试第 1 层可以访问它 ``` -## Summary +## 总结 -### Key Takeaways +### 关键要点 -1. **Prototypes:** - - Every object has a prototype - - Prototype chain for property lookup - - Enables inheritance without classes +1. **原型:** + - 每个对象都有原型 + - 原型链用于属性查找 + - 无需类即可实现继承 2. **Object.create():** - - Creates object with specific prototype - - Supports property descriptors - - Can create null-prototype objects - -3. **Constructor Functions:** - - Pre-ES6 pattern for classes - - Methods added to prototype - - 'new' keyword creates instances - -4. **Prototype Chain:** - - Lookups walk up the chain - - Own properties found first - - Ends at Object.prototype then null - -5. **Classes vs Prototypes:** - - Classes are sugar over prototypes - - Classes are clearer for most cases - - Prototypes offer more flexibility - -6. **Best Practices:** - - Don't modify built-in prototypes - - Use classes for structure - - Use Object.create for simple inheritance - - Understand prototype performance - -### Comparison Table: Java vs JavaScript - -| Feature | Java | JavaScript | + - 创建具有特定原型的对象 + - 支持属性描述符 + - 可以创建 null 原型对象 + +3. **构造函数:** + - ES6 之前的类模式 + - 方法添加到原型 + - 'new' 关键字创建实例 + +4. **原型链:** + - 查找沿链向上 + - 首先找到自有属性 + - 在 Object.prototype 然后 null 结束 + +5. **类 vs 原型:** + - 类是原型的语法糖 + - 类在大多数情况下更清晰 + - 原型提供更多灵活性 + +6. **最佳实践:** + - 不要修改内置原型 + - 使用类获得结构 + - 使用 Object.create 进行简单继承 + - 理解原型性能 + +### 比较表: Java vs JavaScript + +| 特性 | Java | JavaScript | |---------|------|------------| -| **Inheritance** | Class-based | Prototype-based | -| **Chain** | Class hierarchy | Prototype chain | -| **Methods** | In class definition | On prototype object | -| **Lookup** | Compile-time | Runtime (chain walk) | -| **Modification** | Cannot modify at runtime | Can modify anytime | -| **Checking** | `instanceof` | `instanceof` or `Object.getPrototypeOf()` | +| **继承** | 基于类 | 基于原型 | +| **链** | 类层次结构 | 原型链 | +| **方法** | 在类定义中 | 在原型对象上 | +| **查找** | 编译时 | 运行时(链遍历) | +| **修改** | 运行时不能修改 | 可以随时修改 | +| **检查** | `instanceof` | `instanceof` 或 `Object.getPrototypeOf()` | -## What's Next? +## 下一步? -You've mastered JavaScript's prototype system! Next up is **Module 9: This and Context**, where we'll explore: +你已经掌握了 JavaScript 的原型系统!接下来是 **模块 9: This 和上下文**,我们将探索: -- How `this` binding works -- Call, apply, and bind methods -- Arrow functions and lexical this -- This in different contexts -- Common this-related pitfalls -- Best practices for managing context +- `this` 绑定如何工作 +- Call、apply 和 bind 方法 +- 箭头函数和词法 this +- 不同上下文中的 this +- 常见的 this 相关陷阱 +- 管理上下文的最佳实践 -Ready to master the `this` keyword? Let's continue! +准备掌握 `this` 关键字了吗?让我们继续! diff --git a/content/docs/java2js/module-09-this-context.zh-cn.mdx b/content/docs/java2js/module-09-this-context.zh-cn.mdx index 0aaf136..37ed369 100644 --- a/content/docs/java2js/module-09-this-context.zh-cn.mdx +++ b/content/docs/java2js/module-09-this-context.zh-cn.mdx @@ -1,94 +1,94 @@ --- -title: "Module 9: This and Context" -description: "Master JavaScript's 'this' keyword and context binding" +title: "模块 9: This 和上下文" +description: "掌握 JavaScript 的 'this' 关键字和上下文绑定" --- -## Module 9: This and Context +## 模块 9: This 和上下文 -The `this` keyword in JavaScript behaves very differently from Java. Understanding how `this` works is crucial for writing bug-free JavaScript code. This module will demystify `this` and teach you how to control context. +JavaScript 中的 `this` 关键字与 Java 的行为非常不同。理解 `this` 如何工作对于编写无 bug 的 JavaScript 代码至关重要。本模块将揭开 `this` 的神秘面纱,教你如何控制上下文。 -## Learning Objectives +## 学习目标 -By the end of this module, you will: -✅ Understand how `this` binding works -✅ Master call(), apply(), and bind() methods -✅ Learn arrow functions and lexical this -✅ Know this in different contexts (global, function, class, etc.) -✅ Understand common this-related pitfalls -✅ Learn best practices for managing context +完成本模块后,你将: +✅ 理解 `this` 绑定如何工作 +✅ 掌握 call()、apply() 和 bind() 方法 +✅ 学习箭头函数和词法 this +✅ 了解不同上下文中的 this(全局、函数、类等) +✅ 理解常见的 this 相关陷阱 +✅ 学习管理上下文的最佳实践 -## This Comparison: Java vs JavaScript +## This 比较: Java vs JavaScript - + ```java !! java -// Java - 'this' always refers to current instance +// Java - 'this' 始终引用当前实例 public class Counter { private int count = 0; public Counter() { - this.count = 0; // 'this' is the Counter instance + this.count = 0; // 'this' 是 Counter 实例 } public void increment() { - this.count++; // Always refers to Counter instance + this.count++; // 始终引用 Counter 实例 } public void multiThread() { - // Lambda captures 'this' + // Lambda 捕获 'this' Runnable r = () -> { - this.count++; // Still refers to Counter instance + this.count++; // 仍然引用 Counter 实例 }; } } -// 'this' is predictable and always refers to the instance +// 'this' 是可预测的,始终引用实例 ``` ```javascript !! js -// JavaScript - 'this' depends on call site +// JavaScript - 'this' 取决于调用位置 const counter = { count: 0, increment() { - this.count++; // 'this' depends on how function is called + this.count++; // 'this' 取决于函数如何调用 }, - // Arrow function: 'this' from surrounding scope + // 箭头函数: 'this' 来自周围作用域 incrementArrow: () => { - this.count++; // 'this' is NOT counter! + this.count++; // 'this' 不是 counter! } }; counter.increment(); // 'this' = counter ✓ -counter.incrementArrow(); // 'this' = undefined (or window in browser) ✗ +counter.incrementArrow(); // 'this' = undefined (或浏览器中的 window) ✗ -// 'this' changes based on call site +// 'this' 基于调用位置改变 const fn = counter.increment; -fn(); // 'this' = undefined (strict mode) or window +fn(); // 'this' = undefined (严格模式) 或 window -// Can explicitly bind 'this' +// 可以显式绑定 'this' fn.call(counter); // 'this' = counter ✓ ``` -## This Rules +## This 规则 -JavaScript determines `this` based on how a function is called: +JavaScript 根据函数的调用方式确定 `this`: - + ```javascript !! js -// Rule 1: Default binding -// 'this' is global object (non-strict) or undefined (strict) +// 规则 1: 默认绑定 +// 'this' 是全局对象(非严格模式)或 undefined(严格模式) function sayName() { console.log(this.name); } const name = "Global"; -sayName(); // "Global" (non-strict) or undefined (strict) +sayName(); // "Global" (非严格模式) 或 undefined (严格模式) -// Rule 2: Implicit binding -// 'this' is object to left of dot +// 规则 2: 隐式绑定 +// 'this' 是点左边的对象 const person = { name: "John", greet() { @@ -98,8 +98,8 @@ const person = { person.greet(); // "John" - 'this' = person -// Rule 3: Explicit binding -// 'this' is specified with call/apply/bind +// 规则 3: 显式绑定 +// 'this' 用 call/apply/bind 指定 function greet() { console.log(this.name); } @@ -110,8 +110,8 @@ greet.apply(user); // "Jane" - 'this' = user const bound = greet.bind(user); bound(); // "Jane" - 'this' = user -// Rule 4: New binding -// 'this' is newly created object +// 规则 4: New 绑定 +// 'this' 是新创建的对象 function User(name) { this.name = name; } @@ -121,23 +121,23 @@ console.log(user2.name); // "Bob" - 'this' = user2 ``` -## Call, Apply, and Bind +## Call、Apply 和 Bind -JavaScript provides methods to explicitly control `this`: +JavaScript 提供了显式控制 `this` 的方法: - + ```java !! java -// Java - No direct equivalent -// Methods are bound to instances -user.method(); // 'this' is always user +// Java - 没有直接等效 +// 方法绑定到实例 +user.method(); // 'this' 始终是 user -// Would need reflection or wrapper classes +// 需要反射或包装类 ``` ```javascript !! js // JavaScript - call(), apply(), bind() -// call(): Invoke with specific 'this' +// call(): 用特定的 'this' 调用 function introduce(greeting, punctuation) { console.log(`${greeting}, I'm ${this.name}${punctuation}`); } @@ -145,41 +145,41 @@ function introduce(greeting, punctuation) { const person = { name: "John" }; introduce.call(person, "Hello", "!"); // "Hello, I'm John!" -// Arguments passed individually +// 参数单独传递 -// apply(): Same as call but arguments as array +// apply(): 与 call 相同但参数作为数组 introduce.apply(person, ["Hi", "?"]); // "Hi, I'm John?" -// Arguments passed as array +// 参数作为数组传递 -// Practical: Borrow methods +// 实际: 借用方法 const numbers = [1, 2, 3]; const max = Math.max.apply(null, numbers); // 3 const min = Math.min.apply(null, numbers); // 1 -// Modern: Spread operator (preferred) +// 现代: 展开运算符(首选) const max2 = Math.max(...numbers); // 3 -// bind(): Create function with permanent 'this' +// bind(): 创建永久 'this' 的函数 const person2 = { name: "Jane" }; const introduceJane = introduce.bind(person2, "Hey"); introduceJane("!"); // "Hey, I'm Jane!" -// Can add more arguments, but 'this' and first arg are fixed +// 可以添加更多参数,但 'this' 和第一个参数固定 -// Practical: Event handlers +// 实际: 事件处理器 class Button { constructor(label) { this.label = label; this.button = document.createElement("button"); this.button.textContent = label; - // ❌ BAD: 'this' is lost + // ❌ 错误: 'this' 丢失 // this.button.addEventListener("click", this.handleClick); - // ✅ GOOD: Bind 'this' + // ✅ 正确: 绑定 'this' this.handleClick = this.handleClick.bind(this); this.button.addEventListener("click", this.handleClick); - // ✅ ALSO GOOD: Arrow function (see below) + // ✅ 也正确: 箭头函数(见下文) // this.button.addEventListener("click", () => this.handleClick()); } @@ -190,105 +190,105 @@ class Button { ``` -## Arrow Functions and Lexical This +## 箭头函数和词法 This -Arrow functions don't have their own `this` - they inherit it from surrounding scope: +箭头函数没有自己的 `this` - 它们从周围作用域继承: - + ```java !! java -// Java - Lambdas capture 'this' from enclosing class +// Java - Lambda 从封闭类捕获 'this' public class Timer { private int count = 0; public void start() { - // Lambda can access 'this' + // Lambda 可以访问 'this' Runnable r = () -> { - this.count++; // 'this' is Timer instance + this.count++; // 'this' 是 Timer 实例 }; } } ``` ```javascript !! js -// JavaScript - Arrow functions lexically bind 'this' +// JavaScript - 箭头函数词法绑定 'this' -// Regular function: 'this' based on call site +// 常规函数: 'this' 基于调用位置 const counter = { count: 0, increment: function() { setTimeout(function() { - this.count++; // ❌ 'this' is not counter! + this.count++; // ❌ 'this' 不是 counter! }, 1000); } }; -// Arrow function: 'this' from surrounding scope +// 箭头函数: 'this' 来自周围作用域 const counter2 = { count: 0, increment: function() { setTimeout(() => { - this.count++; // ✅ 'this' is counter2 + this.count++; // ✅ 'this' 是 counter2 }, 1000); } }; -// Practical: Event handlers +// 实际: 事件处理器 class UI { constructor() { this.handleClick = this.handleClick.bind(this); - // Or use arrow function: + // 或使用箭头函数: // this.handleClick = () => { ... }; } handleClick() { - console.log(this); // UI instance + console.log(this); // UI 实例 } setup() { - // ❌ BAD: 'this' is button element + // ❌ 错误: 'this' 是 button 元素 // button.addEventListener("click", this.handleClick); - // ✅ GOOD: Arrow function + // ✅ 正确: 箭头函数 button.addEventListener("click", () => this.handleClick()); - // ✅ ALSO GOOD: Bind + // ✅ 也正确: 绑定 button.addEventListener("click", this.handleClick.bind(this)); } } -// Arrow functions cannot be bound +// 箭头函数不能被绑定 const obj = { name: "John", greet: () => { - console.log(this.name); // 'this' is not obj! + console.log(this.name); // 'this' 不是 obj! } }; obj.greet(); // undefined -// You cannot change arrow function's 'this' +// 不能改变箭头函数的 'this' const arrow = () => console.log(this); -arrow.call({ name: "John" }); // Still global/undefined (not { name: "John" }) +arrow.call({ name: "John" }); // 仍然是全局/undefined (不是 { name: "John" }) ``` -## This in Different Contexts +## 不同上下文中的 This - + ```java !! java -// Java - 'this' is always instance +// Java - 'this' 始终是实例 public class Example { private String name = "Instance"; public void method() { - System.out.println(this.name); // Always instance + System.out.println(this.name); // 始终是实例 } public void inner() { - // Even in inner methods + // 即使在内部方法中 class Inner { public void go() { - System.out.println(Example.this.name); // Still instance + System.out.println(Example.this.name); // 仍然是实例 } } } @@ -296,23 +296,23 @@ public class Example { ``` ```javascript !! js -// JavaScript - 'this' varies by context +// JavaScript - 'this' 根据上下文变化 -// 1. Global scope -console.log(this); // global object (window in browser, global in Node) +// 1. 全局作用域 +console.log(this); // 全局对象(浏览器中是 window,Node 中是 global) -// 2. Function scope (strict mode) +// 2. 函数作用域(严格模式) "use strict"; function test() { - console.log(this); // undefined (not global!) + console.log(this); // undefined (不是全局!) } -// 3. Function scope (non-strict) +// 3. 函数作用域(非严格) function test2() { - console.log(this); // global object + console.log(this); // 全局对象 } -// 4. Object method +// 4. 对象方法 const obj = { name: "John", greet() { @@ -320,38 +320,38 @@ const obj = { }, inner: { greet() { - console.log(this.name); // 'this' = obj.inner (not obj!) + console.log(this.name); // 'this' = obj.inner (不是 obj!) } } }; -// 5. Constructor function +// 5. 构造函数 function User(name) { - this.name = name; // 'this' = new object + this.name = name; // 'this' = 新对象 } const user = new User("John"); -// 6. DOM event handlers +// 6. DOM 事件处理器 button.addEventListener("click", function() { - console.log(this); // 'this' = button element + console.log(this); // 'this' = button 元素 }); button.addEventListener("click", () => { - console.log(this); // 'this' = surrounding context (not button!) + console.log(this); // 'this' = 周围上下文(不是 button!) }); -// 7. Classes +// 7. 类 class MyClass { constructor() { this.name = "John"; } method() { - console.log(this); // 'this' = instance + console.log(this); // 'this' = 实例 } static staticMethod() { - console.log(this); // 'this' = MyClass (class itself) + console.log(this); // 'this' = MyClass (类本身) } } @@ -366,13 +366,13 @@ const obj2 = { ``` -## Common Pitfalls +## 常见陷阱 -### Pitfall 1: Losing This +### 陷阱 1: 丢失 This - + ```javascript !! js -// Problem: 'this' is lost when passing methods +// 问题: 传递方法时 'this' 丢失 const controller = { data: [1, 2, 3], @@ -381,28 +381,28 @@ const controller = { } }; -// ❌ BAD: Extracting method loses 'this' +// ❌ 错误: 提取方法丢失 'this' const process = controller.process; -process(); // Error: Cannot read 'data' of undefined +process(); // 错误: Cannot read 'data' of undefined -// ✅ Solution 1: Bind +// ✅ 解决方案 1: 绑定 const process2 = controller.process.bind(controller); -process2(); // Works! +process2(); // 可以工作! -// ✅ Solution 2: Arrow function +// ✅ 解决方案 2: 箭头函数 const process3 = () => controller.process(); -process3(); // Works! +process3(); // 可以工作! -// ✅ Solution 3: Keep method attached -controller.process(); // Works! +// ✅ 解决方案 3: 保持方法附加 +controller.process(); // 可以工作! ``` -### Pitfall 2: Callback This +### 陷阱 2: 回调 This - + ```javascript !! js -// Problem: Callbacks have different 'this' +// 问题: 回调有不同的 'this' class Timer { constructor(seconds) { @@ -410,14 +410,14 @@ class Timer { } start() { - // ❌ BAD: Callback has different 'this' + // ❌ 错误: 回调有不同的 'this' setTimeout(function() { - this.seconds--; // Error: 'this' is not Timer + this.seconds--; // 错误: 'this' 不是 Timer }, 1000); } } -// ✅ Solution 1: Arrow function +// ✅ 解决方案 1: 箭头函数 class Timer2 { constructor(seconds) { this.seconds = seconds; @@ -425,12 +425,12 @@ class Timer2 { start() { setTimeout(() => { - this.seconds--; // 'this' is Timer2 ✓ + this.seconds--; // 'this' 是 Timer2 ✓ }, 1000); } } -// ✅ Solution 2: Bind +// ✅ 解决方案 2: 绑定 class Timer3 { constructor(seconds) { this.seconds = seconds; @@ -439,18 +439,18 @@ class Timer3 { start() { setTimeout(function() { this.seconds--; - }.bind(this), 1000); // Bind 'this' + }.bind(this), 1000); // 绑定 'this' } } -// ✅ Solution 3: Capture 'this' +// ✅ 解决方案 3: 捕获 'this' class Timer4 { constructor(seconds) { this.seconds = seconds; } start() { - const self = this; // Capture 'this' + const self = this; // 捕获 'this' setTimeout(function() { self.seconds--; }, 1000); @@ -459,22 +459,22 @@ class Timer4 { ``` -### Pitfall 3: Array Methods +### 陷阱 3: 数组方法 - + ```javascript !! js -// Problem: Array methods change 'this' +// 问题: 数组方法改变 'this' const processor = { multiplier: 2, process(numbers) { return numbers.map(function(x) { - return x * this.multiplier; // Error: 'this' is not processor + return x * this.multiplier; // 错误: 'this' 不是 processor }); } }; -// ✅ Solution 1: Arrow function +// ✅ 解决方案 1: 箭头函数 const processor2 = { multiplier: 2, process(numbers) { @@ -482,17 +482,17 @@ const processor2 = { } }; -// ✅ Solution 2: Second argument to map/filter/etc +// ✅ 解决方案 2: map/filter/etc 的第二个参数 const processor3 = { multiplier: 2, process(numbers) { return numbers.map(function(x) { return x * this.multiplier; - }, this); // Set 'this' for callback + }, this); // 为回调设置 'this' } }; -// ✅ Solution 3: Bind +// ✅ 解决方案 3: 绑定 const processor4 = { multiplier: 2, process(numbers) { @@ -504,19 +504,19 @@ const processor4 = { ``` -## Practical Patterns +## 实用模式 -### Pattern 1: Class Method Binding +### 模式 1: 类方法绑定 - + ```javascript !! js -// Auto-bind all methods in constructor +// 在构造函数中自动绑定所有方法 class UserService { constructor(apiUrl) { this.apiUrl = apiUrl; this.users = []; - // Bind all methods + // 绑定所有方法 this.fetchUsers = this.fetchUsers.bind(this); this.getUser = this.getUser.bind(this); } @@ -531,7 +531,7 @@ class UserService { } } -// Or use arrow functions for methods (auto-bound) +// 或使用箭头函数作为方法(自动绑定) class UserService2 { constructor(apiUrl) { this.apiUrl = apiUrl; @@ -548,18 +548,18 @@ class UserService2 { } } -// Usage +// 使用 const service = new UserService2("https://api.example.com"); -const fetchFn = service.fetchUsers; // 'this' is bound! -fetchFn(); // Works! +const fetchFn = service.fetchUsers; // 'this' 已绑定! +fetchFn(); // 可以工作! ``` -### Pattern 2: Event Delegation +### 模式 2: 事件委托 - + ```javascript !! js -// Problem: Event handlers receive element as 'this' +// 问题: 事件处理器接收元素作为 'this' class Menu { constructor(items) { @@ -586,7 +586,7 @@ class Menu { } } -// Or use event delegation +// 或使用事件委托 class Menu2 { constructor(items) { this.items = items; @@ -603,7 +603,7 @@ class Menu2 { list.appendChild(li); }); - // Single listener on container + // 容器上的单个监听器 list.addEventListener("click", this); return list; } @@ -618,38 +618,38 @@ class Menu2 { ``` -### Pattern 3: Partial Application +### 模式 3: 部分应用 - + ```javascript !! js -// Bind both 'this' and arguments +// 同时绑定 'this' 和参数 function greet(greeting, punctuation) { console.log(`${greeting}, I'm ${this.name}${punctuation}`); } const person = { name: "John" }; -// Partial application with 'this' +// 使用 'this' 的部分应用 const sayHello = greet.bind(person, "Hello"); sayHello("!"); // "Hello, I'm John!" const sayHi = greet.bind(person, "Hi"); sayHi("?"); // "Hi, I'm John?" -// Practical: Reusable functions +// 实际: 可重用函数 function fetch(endpoint, options) { console.log(`Fetching ${this.baseUrl}${endpoint}`, options); } const api = { baseUrl: "https://api.example.com", - get: fetch.bind(null, "/users"), // 'this' will be api + get: fetch.bind(null, "/users"), // 'this' 将是 api post: fetch.bind(null, "/users", { method: "POST" }) }; -api.get.call(api, { method: "GET" }); // Explicit 'this' +api.get.call(api, { method: "GET" }); // 显式 'this' -// Better: Use arrow functions +// 更好: 使用箭头函数 const api2 = { baseUrl: "https://api.example.com", get: function(endpoint, options) { @@ -661,24 +661,24 @@ const getUsers = (endpoint, options) => api2.get.call(api2, endpoint, options); ``` -## Performance Considerations +## 性能考虑 - + ```javascript !! js -// Binding creates new function (overhead) +// 绑定创建新函数(开销) class Button { constructor() { this.text = "Click me"; - // ❌ BAD: Creates new function on each render + // ❌ 错误: 每次渲染创建新函数 this.handleClick = function() { console.log(this.text); }.bind(this); } } -// ✅ GOOD: Use arrow function (still creates per instance) +// ✅ 正确: 使用箭头函数(仍然每个实例创建) class Button2 { constructor() { this.text = "Click me"; @@ -688,7 +688,7 @@ class Button2 { } } -// ✅ BETTER: Define method in prototype +// ✅ 更好: 在原型中定义方法 class Button3 { constructor() { this.text = "Click me"; @@ -699,24 +699,24 @@ class Button3 { } setup() { - // Bind once + // 绑定一次 this.handleClick = this.handleClick.bind(this); } } -// Best: Bind at call site (if possible) +// 最佳: 在调用点绑定(如果可能) const button = new Button3(); button.element.addEventListener("click", () => button.handleClick()); -// Performance comparison +// 性能比较 const obj = { value: 42 }; -// Bound function (slight overhead) +// 绑定函数(轻微开销) const bound = function() { return this.value; }.bind(obj); -// Direct call (faster) +// 直接调用(更快) function getValue() { return this.value; } @@ -724,24 +724,24 @@ getValue.call(obj); ``` -## Best Practices +## 最佳实践 - + ```java !! java -// Java: Clear 'this' usage +// Java: 清晰的 'this' 使用 public class Example { private String name; public void method() { - this.name = "John"; // Clear 'this' + this.name = "John"; // 清晰的 'this' } } ``` ```javascript !! js -// JavaScript: Be intentional with 'this' +// JavaScript: 有意地使用 'this' -// 1. Use arrow functions for callbacks +// 1. 回调使用箭头函数 class Component { constructor() { this.state = { count: 0 }; @@ -752,24 +752,24 @@ class Component { } setup() { - // ✅ Arrow function preserves 'this' + // ✅ 箭头函数保留 'this' button.addEventListener("click", () => this.increment()); } } -// 2. Bind when extracting methods +// 2. 提取方法时绑定 class Service { async fetchData() { // ... } getFetcher() { - // ✅ Bind when returning method + // ✅ 返回方法时绑定 return this.fetchData.bind(this); } } -// 3. Use classes for predictable 'this' +// 3. 使用类获得可预测的 'this' class Counter { constructor() { this.count = 0; @@ -780,39 +780,39 @@ class Counter { } } -// Avoid constructor functions (use classes) +// 避免构造函数(使用类) function Counter() { this.count = 0; } -// 4. Understand arrow function limitations +// 4. 理解箭头函数限制 const obj = { - // ❌ Don't use arrow for methods + // ❌ 不要使用箭头函数作为方法 // greet: () => console.log(this.name), - // ✅ Use regular methods + // ✅ 使用常规方法 greet() { console.log(this.name); } }; -// 5. Be careful with array methods +// 5. 小心使用数组方法 const processor = { multiplier: 2, process(numbers) { - // ✅ Use arrow functions + // ✅ 使用箭头函数 return numbers.map(x => x * this.multiplier); } }; -// 6. Don't use 'this' in static methods +// 6. 不要在静态方法中使用 'this' class Utils { static helper() { - // ❌ Don't use 'this' (it's the class, not instance) + // ❌ 不要使用 'this'(它是类,不是实例) // this.method(); - // ✅ Use static calls + // ✅ 使用静态调用 Utils.staticHelper(); } @@ -821,36 +821,36 @@ class Utils { } } -// 7. Explicit 'this' binding when needed +// 7. 需要时显式 'this' 绑定 function logThis() { console.log(this); } const context = { name: "Context" }; -setTimeout(logThis.bind(context), 1000); // Explicit 'this' +setTimeout(logThis.bind(context), 1000); // 显式 'this' ``` -## Exercises +## 练习 -### Exercise 1: Fix This Context +### 练习 1: 修复 This 上下文 ```javascript const counter = { count: 0, increment: function() { setTimeout(function() { - this.count++; // Fix this + this.count++; // 修复这个 }, 1000); } }; ``` -### Exercise 2: Array Method This +### 练习 2: 数组方法 This ```javascript const calculator = { base: 10, add(numbers) { - // Fix: return numbers with base added to each + // 修复: 返回每个数都加上 base 的数组 return numbers.map(function(x) { return x + this.base; }); @@ -858,79 +858,79 @@ const calculator = { }; ``` -### Exercise 3: Event Handler +### 练习 3: 事件处理器 ```javascript class Button { constructor(text) { this.text = text; this.element = document.createElement("button"); this.element.textContent = text; - // Add click handler that logs this.text + // 添加记录 this.text 的点击处理器 } } ``` -### Exercise 4: Bind Arguments +### 练习 4: 绑定参数 ```javascript function multiply(a, b, c) { return a * b * c; } -// Create a function that multiplies by 2 and 3 -// using bind (just needs one argument) +// 使用 bind 创建一个乘以 2 和 3 的函数 +// (只需要一个参数) const multiplyBy6 = multiply.bind(null, ?, ?); ``` -## Summary +## 总结 -### Key Takeaways +### 关键要点 -1. **This Rules:** - - Default: Global/undefined - - Implicit: Object left of dot - - Explicit: call/apply/bind - - New: Newly created object +1. **This 规则:** + - 默认: 全局/undefined + - 隐式: 点左边的对象 + - 显式: call/apply/bind + - New: 新创建的对象 -2. **Arrow Functions:** - - Lexical this (from surrounding scope) - - Cannot be bound with call/apply/bind - - Perfect for callbacks +2. **箭头函数:** + - 词法 this(来自周围作用域) + - 不能用 call/apply/bind 绑定 + - 完美用于回调 3. **Call/Apply/Bind:** - - call(): Invoke with specific this - - apply(): Same but array of args - - bind(): Create permanent this + - call(): 用特定 this 调用 + - apply(): 相同但参数数组 + - bind(): 创建永久 this -4. **Context Loss:** - - Common when passing methods - - Solve with bind or arrow functions - - Be careful with callbacks +4. **上下文丢失:** + - 传递方法时常见 + - 使用 bind 或箭头函数解决 + - 小心回调 -5. **Best Practices:** - - Use arrow functions for callbacks - - Use classes for structure - - Bind when extracting methods - - Understand arrow limitations +5. **最佳实践:** + - 回调使用箭头函数 + - 使用类获得结构 + - 提取方法时绑定 + - 理解箭头函数限制 -### Comparison Table: Java vs JavaScript +### 比较表: Java vs JavaScript -| Feature | Java | JavaScript | +| 特性 | Java | JavaScript | |---------|------|------------| -| **This** | Always current instance | Depends on call site | -| **Binding** | Always bound | Can be changed | -| **Lambdas/Arrows** | Capture this | Lexical this | -| **Explicit binding** | No equivalent | call/apply/bind | -| **Loss of this** | Never happens | Common issue | -| **Solution** | Not needed | bind or arrow function | +| **This** | 始终当前实例 | 取决于调用位置 | +| **绑定** | 始终绑定 | 可以改变 | +| **Lambda/箭头** | 捕获 this | 词法 this | +| **显式绑定** | 无等效 | call/apply/bind | +| **this 丢失** | 从不发生 | 常见问题 | +| **解决方案** | 不需要 | bind 或箭头函数 | -## What's Next? +## 下一步? -You've mastered JavaScript's `this` keyword! Next up is **Module 10: Asynchronous Programming Basics**, where we'll explore: +你已经掌握了 JavaScript 的 `this` 关键字!接下来是 **模块 10: 异步编程基础**,我们将探索: -- The JavaScript event loop -- Callback functions -- Understanding synchronous vs asynchronous -- Error handling in async code -- Common async patterns +- JavaScript 事件循环 +- 回调函数 +- 理解同步 vs 异步 +- 异步代码中的错误处理 +- 常见异步模式 -Ready to dive into asynchronous programming? Let's continue! +准备深入异步编程了吗?让我们继续! diff --git a/content/docs/java2js/module-15-tooling.zh-cn.mdx b/content/docs/java2js/module-15-tooling.zh-cn.mdx index af24a9c..6ba8ae3 100644 --- a/content/docs/java2js/module-15-tooling.zh-cn.mdx +++ b/content/docs/java2js/module-15-tooling.zh-cn.mdx @@ -1,25 +1,25 @@ --- -title: "Module 15: Tooling and Build" -description: "Learn JavaScript build tools, bundlers, and development workflow" +title: "模块 15:工具与构建" +description: "学习 JavaScript 构建工具、打包工具和开发工作流" --- -## Module 15: Tooling and Build +## 模块 15:工具与构建 -Modern JavaScript development requires build tools for bundling, transpiling, and optimizing code. +现代 JavaScript 开发需要构建工具来进行代码打包、转译和优化。 -## Learning Objectives +## 学习目标 -By the end of this module, you will: -✅ Understand npm and package management -✅ Learn about bundlers (webpack, Vite) -✅ Understand transpilation (Babel) -✅ Know development vs production builds -✅ Learn about code splitting -✅ Master modern development workflow +完成本模块后,你将: +✅ 理解 npm 和包管理 +✅ 了解打包工具(webpack、Vite) +✅ 理解转译(Babel) +✅ 了解开发环境与生产环境构建 +✅ 学习代码分割 +✅ 掌握现代开发工作流 -## Package Management: Java vs JavaScript +## 包管理:Java vs JavaScript - + ```java !! java // Java - Maven/Gradle // pom.xml (Maven) @@ -36,8 +36,8 @@ dependencies { implementation 'org.springframework:spring-core:5.3.8' } -// Install dependencies -mvn install // or: gradle build +// 安装依赖 +mvn install // 或: gradle build ``` ```javascript !! js @@ -54,38 +54,38 @@ mvn install // or: gradle build } } -// Install dependencies +// 安装依赖 npm install -npm install lodash // Add package -npm install --save-dev webpack // Add dev dependency +npm install lodash // 添加包 +npm install --save-dev webpack // 添加开发依赖 ``` -## Project Structure +## 项目结构 - + ```bash my-project/ ├── src/ -│ ├── index.js # Entry point +│ ├── index.js # 入口文件 │ ├── utils/ │ │ └── helpers.js │ └── styles/ │ └── main.css ├── public/ -│ └── index.html # HTML template -├── dist/ # Build output (generated) -├── package.json # Dependencies -├── webpack.config.js # Build config -└── babel.config.js # Transpiler config +│ └── index.html # HTML 模板 +├── dist/ # 构建输出(自动生成) +├── package.json # 依赖配置 +├── webpack.config.js # 构建配置 +└── babel.config.js # 转译器配置 ``` -## Build Tools +## 构建工具 - + ```javascript !! js -// Webpack configuration +// Webpack 配置 // webpack.config.js module.exports = { entry: './src/index.js', @@ -111,10 +111,10 @@ module.exports = { template: './public/index.html' }) ], - mode: 'development' // or 'production' + mode: 'development' // 或 'production' }; -// Vite (modern, faster) +// Vite(现代、更快) // vite.config.js export default { plugins: [react()], @@ -132,11 +132,11 @@ export default { ``` -## Transpilation +## 代码转译 - + ```javascript !! js -// .babelrc or babel.config.js +// .babelrc 或 babel.config.js module.exports = { presets: [ ['@babel/preset-env', { @@ -158,9 +158,9 @@ import 'regenerator-runtime/runtime'; ``` -## Scripts +## 脚本命令 - + ```json { "scripts": { @@ -175,15 +175,15 @@ import 'regenerator-runtime/runtime'; ``` -## Best Practices +## 最佳实践 - + ```javascript !! js -// 1. Use modern build tools -// Vite (faster than webpack) +// 1. 使用现代构建工具 +// Vite(比 webpack 更快) npm create vite@latest my-app -// 2. Optimize production builds +// 2. 优化生产环境构建 // webpack mode: 'production', optimization: { @@ -193,29 +193,29 @@ optimization: { } } -// 3. Code splitting -// Dynamic import +// 3. 代码分割 +// 动态导入 const module = await import('./module.js'); -// 4. Source maps for debugging +// 4. 源映射用于调试 devtool: 'source-map' // webpack -// 5. Hot module replacement -// Vite has this built-in +// 5. 热模块替换 +// Vite 内置此功能 npm run dev ``` -## Summary +## 总结 -### Key Takeaways +### 关键要点 -1. **npm**: Package manager for JavaScript -2. **Bundlers**: webpack, Vite (combine files) -3. **Babel**: Transpile modern JS -4. **Dev tools**: HMR, source maps -5. **Production**: Minification, splitting +1. **npm**:JavaScript 的包管理器 +2. **打包工具**:webpack、Vite(合并文件) +3. **Babel**:转译现代 JavaScript +4. **开发工具**:HMR、源映射 +5. **生产环境**:压缩、分割 -## What's Next? +## 接下来是什么? -Next: **Module 16: Testing and Debugging** - Learn Jest, debugging tools, and testing patterns! +下一节:**模块 16:测试与调试** - 学习 Jest、调试工具和测试模式! diff --git a/content/docs/java2js/module-16-testing-debugging.zh-cn.mdx b/content/docs/java2js/module-16-testing-debugging.zh-cn.mdx index 211930f..09c6ac1 100644 --- a/content/docs/java2js/module-16-testing-debugging.zh-cn.mdx +++ b/content/docs/java2js/module-16-testing-debugging.zh-cn.mdx @@ -1,25 +1,25 @@ --- -title: "Module 16: Testing and Debugging" -description: "Master JavaScript testing frameworks and debugging techniques" +title: "模块 16:测试与调试" +description: "掌握 JavaScript 测试框架和调试技巧" --- -## Module 16: Testing and Debugging +## 模块 16:测试与调试 -Testing and debugging are essential skills for writing reliable JavaScript code. +测试和调试是编写可靠 JavaScript 代码的必备技能。 -## Learning Objectives +## 学习目标 -By the end of this module, you will: -✅ Master Jest testing framework -✅ Learn testing patterns (unit, integration, E2E) -✅ Understand debugging techniques -✅ Know browser DevTools -✅ Learn error handling strategies -✅ Master testing best practices +完成本模块后,你将: +✅ 掌握 Jest 测试框架 +✅ 学习测试模式(单元测试、集成测试、端到端测试) +✅ 理解调试技巧 +✅ 了解浏览器开发者工具 +✅ 学习错误处理策略 +✅ 掌握测试最佳实践 -## Testing: Java vs JavaScript +## 测试:Java vs JavaScript - + ```java !! java // Java - JUnit import org.junit.jupiter.api.Test; @@ -36,31 +36,31 @@ public class CalculatorTest { ```javascript !! js // JavaScript - Jest -test("add returns sum", () => { +test("add 返回和", () => { const calc = new Calculator(); expect(calc.add(2, 3)).toBe(5); }); ``` -## Jest Testing +## Jest 测试 - + ```javascript !! js // math.test.js const { add, subtract } = require("./math"); -describe("Math operations", () => { - test("add adds numbers", () => { +describe("数学运算", () => { + test("add 加法运算", () => { expect(add(2, 3)).toBe(5); }); - test("subtract subtracts numbers", () => { + test("subtract 减法运算", () => { expect(subtract(5, 3)).toBe(2); }); - // Matchers - test("matchers", () => { + // 匹配器 + test("匹配器", () => { expect(42).toBe(42); expect({ name: "John" }).toEqual({ name: "John" }); expect("hello").toContain("ell"); @@ -72,14 +72,14 @@ describe("Math operations", () => { }); }); -// Async testing -test("async fetch", async () => { +// 异步测试 +test("异步获取数据", async () => { const data = await fetchData(); expect(data).toBeDefined(); }); -// Mocking -test("mock function", () => { +// 模拟 +test("模拟函数", () => { const mock = jest.fn(); mock("arg1", "arg2"); @@ -89,86 +89,86 @@ test("mock function", () => { ``` -## Debugging +## 调试 - + ```javascript !! js -// 1. console.log (simplest) -console.log("Value:", value); -console.table(data); // Tabular data -console.group("Group"); -console.log("Inside group"); +// 1. console.log(最简单) +console.log("值:", value); +console.table(data); // 表格数据 +console.group("分组"); +console.log("组内"); console.groupEnd(); -// 2. debugger statement +// 2. debugger 语句 function buggyFunction() { const x = 1; - debugger; // Pauses execution + debugger; // 暂停执行 const y = 2; return x + y; } -// 3. Chrome DevTools -// - Sources panel -// - Set breakpoints -// - Step through code -// - Inspect variables +// 3. Chrome 开发者工具 +// - Sources 面板 +// - 设置断点 +// - 单步执行代码 +// - 检查变量 -// 4. Error handling +// 4. 错误处理 try { riskyOperation(); } catch (error) { - console.error("Caught:", error); - console.trace(); // Stack trace + console.error("捕获错误:", error); + console.trace(); // 堆栈跟踪 } -// 5. Performance profiling -console.time("operation"); -// ... code ... -console.timeEnd("operation"); +// 5. 性能分析 +console.time("操作"); +// ... 代码 ... +console.timeEnd("操作"); ``` -## Browser DevTools +## 浏览器开发者工具 - + ```html - + - - - - - + + + + + ``` -## Testing Patterns +## 测试模式 - + ```javascript !! js -// Unit test +// 单元测试 describe("UserService", () => { - test("creates user", () => { + test("创建用户", () => { const user = new User("John"); expect(user.name).toBe("John"); }); }); -// Integration test +// 集成测试 describe("API", () => { - test("fetches user", async () => { + test("获取用户", async () => { const response = await fetch("/api/users/1"); const user = await response.json(); expect(user.id).toBe(1); }); }); -// Mock external dependencies +// 模拟外部依赖 jest.mock("./api"); import { fetchUser } from "./api"; -test("displays user", async () => { +test("显示用户", async () => { fetchUser.mockResolvedValue({ id: 1, name: "John" }); const user = await getUser(1); @@ -177,68 +177,68 @@ test("displays user", async () => { ``` -## Best Practices +## 最佳实践 - + ```javascript !! js -// 1. Test behavior, not implementation -// Bad -test("uses array.push", () => { +// 1. 测试行为,而非实现 +// 坏的做法 +test("使用 array.push", () => { arr.push(1); expect(arr.push).toHaveBeenCalled(); }); -// Good -test("adds item to array", () => { +// 好的做法 +test("添加项到数组", () => { addItem(arr, 1); expect(arr).toContain(1); }); -// 2. Arrange-Act-Assert -test("user can login", () => { - // Arrange +// 2. 安排-执行-断言 +test("用户可以登录", () => { + // 安排 const user = { email: "test@test.com", password: "pass" }; - // Act + // 执行 const result = authService.login(user); - // Assert + // 断言 expect(result.success).toBe(true); }); -// 3. One assertion per test (mostly) -test("returns user data", () => { +// 3. 每个测试一个断言(主要) +test("返回用户数据", () => { const user = getUser(1); expect(user.id).toBe(1); - // Don't test everything in one test + // 不要在一个测试中测试所有内容 }); -// 4. Use descriptive test names -test("returns 404 when user not found", () => { - // Clear what it tests +// 4. 使用描述性测试名称 +test("用户未找到时返回 404", () => { + // 清楚地测试了什么 }); ``` -## Summary +## 总结 -### Key Takeaways +### 关键要点 -1. **Jest**: Testing framework -2. **Matchers**: toBe, toEqual, toContain -3. **Async**: await async tests -4. **Mocking**: jest.fn(), jest.mock() -5. **Debug**: console.log, debugger, DevTools +1. **Jest**:测试框架 +2. **匹配器**:toBe、toEqual、toContain +3. **异步**:await 异步测试 +4. **模拟**:jest.fn()、jest.mock() +5. **调试**:console.log、debugger、开发者工具 -### Comparison: Java vs JavaScript +### 对比:Java vs JavaScript -| Feature | Java | JavaScript | +| 特性 | Java | JavaScript | |---------|------|------------| -| **Testing** | JUnit | Jest | -| **Mocking** | Mockito | jest.fn() | -| **Debugging** | IDE debugger | Browser DevTools | -| **Profiling** | JProfiler | Chrome DevTools | +| **测试** | JUnit | Jest | +| **模拟** | Mockito | jest.fn() | +| **调试** | IDE 调试器 | 浏览器开发者工具 | +| **性能分析** | JProfiler | Chrome 开发者工具 | -## What's Next? +## 接下来是什么? -Next: **Module 17: Popular Frameworks** - Learn React and Vue basics! +下一节:**模块 17:流行框架** - 学习 React 和 Vue 基础! diff --git a/content/docs/java2js/module-17-frameworks.zh-cn.mdx b/content/docs/java2js/module-17-frameworks.zh-cn.mdx index c385389..2dbb881 100644 --- a/content/docs/java2js/module-17-frameworks.zh-cn.mdx +++ b/content/docs/java2js/module-17-frameworks.zh-cn.mdx @@ -1,61 +1,61 @@ --- -title: "Module 17: Popular Frameworks" -description: "Introduction to React and modern JavaScript frameworks" +title: "模块 17:流行框架" +description: "React 和现代 JavaScript 框架介绍" --- -## Module 17: Popular Frameworks +## 模块 17:流行框架 -Modern JavaScript development uses frameworks to build complex applications. This module introduces React and Vue. +现代 JavaScript 开发使用框架来构建复杂应用程序。本模块介绍 React 和 Vue。 -## Learning Objectives +## 学习目标 -By the end of this module, you will: -✅ Understand component-based architecture -✅ Learn React basics (components, props, state) -✅ Know Vue basics -✅ Understand framework differences -✅ Learn when to use frameworks -✅ Know framework ecosystem +完成本模块后,你将: +✅ 理解基于组件的架构 +✅ 学习 React 基础(组件、props、state) +✅ 了解 Vue 基础 +✅ 理解框架差异 +✅ 学习何时使用框架 +✅ 了解框架生态系统 -## Framework Philosophy +## 框架理念 - + ```javascript !! js -// Vanilla JS - Imperative +// 原生 JS - 命令式 const button = document.createElement("button"); -button.textContent = "Click me"; +button.textContent = "点击我"; button.addEventListener("click", () => { - console.log("Clicked!"); + console.log("已点击!"); }); document.body.appendChild(button); -// React - Declarative +// React - 声明式 function Button() { - return ; } ``` -## React Basics +## React 基础 - + ```javascript !! js -// Function component +// 函数组件 function Welcome(props) { - return

        Hello, {props.name}

        ; + return

        你好, {props.name}

        ; } -// With JSX +// 使用 JSX function App() { const [count, setCount] = useState(0); return (
        -

        Count: {count}

        +

        计数: {count}

        ); @@ -75,36 +75,36 @@ function UserProfile({ userId }) { }); }, [userId]); - if (loading) return
        Loading...
        ; + if (loading) return
        加载中...
        ; return
        {user.name}
        ; } ```
        -## Props and State +## Props 和 State - + ```javascript !! js -// Props (read-only) +// Props(只读) function Greeting({ name, age }) { return (
        -

        Hello, {name}

        -

        Age: {age}

        +

        你好, {name}

        +

        年龄: {age}

        ); } -// Usage +// 使用 -// State (mutable) +// State(可变) function Counter() { const [count, setCount] = useState(0); return (
        -

        Count: {count}

        +

        计数: {count}

        @@ -114,11 +114,11 @@ function Counter() { ``` -## Vue Basics +## Vue 基础 - + ```javascript !! js -// Vue component +// Vue 组件