Chuyển đến nội dung chính

レッスン 3: 関数、インターフェイス、エラー処理

関数、複数の戻り値、可変長関数。インターフェース、埋め込み、継承よりも合成。エラー処理パターン、カスタム エラー、errors.Is/As、パニック/リカバリ。

💻 プログラミング — レッスン 3 レッスン 3: 関数、インターフェイス、エラー 取り扱い

Golang: 基本から高度まで

パート 1: Go の基礎

xdev.asia

1. Goの関数

関数は Go の基本的な構成要素です。 Go 関数には他の言語と比べて多くのユニークな機能があります。

1.1.基本機能

package main

import "fmt"

// Function cơ bản
func greet(name string) string {
    return "Hello, " + name + "!"
}

// Multiple parameters cùng type
func add(a, b int) int {
    return a + b
}

// Multiple return values - đặc trưng Go
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 swap(a, b string) (first, second string) {
    first = b
    second = a
    return // naked return - trả named values
}

func main() {
    fmt.Println(greet("Go"))       // "Hello, Go!"
    fmt.Println(add(3, 5))         // 8
    
    result, err := divide(10, 3)
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
    fmt.Println(result)            // 3.333...
    
    a, b := swap("hello", "world")
    fmt.Println(a, b)              // "world" "hello"
}

1.2.可変個引数関数

// Variadic function - nhận số lượng tham số bất kỳ
func sum(nums ...int) int {
    total := 0
    for _, n := range nums {
        total += n
    }
    return total
}

// Mix fixed và variadic params
func printf(format string, args ...interface{}) {
    fmt.Printf(format, args...)
}

func main() {
    fmt.Println(sum(1, 2, 3))         // 6
    fmt.Println(sum(1, 2, 3, 4, 5))   // 15
    
    // Spread slice
    nums := []int{10, 20, 30}
    fmt.Println(sum(nums...))          // 60
}

1.3.最高級の関数

// Function as variable
var multiply func(int, int) int = func(a, b int) int {
    return a * b
}

// Function as parameter (callback)
func apply(a, b int, op func(int, int) int) int {
    return op(a, b)
}

// Function as return value (closure)
func makeCounter() func() int {
    count := 0
    return func() int {
        count++
        return count
    }
}

// Function type alias
type MathFunc func(int, int) int

func calculate(a, b int, fn MathFunc) int {
    return fn(a, b)
}

func main() {
    // Anonymous function (lambda)
    double := func(x int) int {
        return x * 2
    }
    fmt.Println(double(5)) // 10
    
    // Callback
    fmt.Println(apply(3, 4, multiply))  // 12
    fmt.Println(apply(10, 3, func(a, b int) int {
        return a - b
    }))                                  // 7
    
    // Closure
    counter := makeCounter()
    fmt.Println(counter()) // 1
    fmt.Println(counter()) // 2
    fmt.Println(counter()) // 3
    
    // IIFE (Immediately Invoked Function Expression)
    result := func(x int) int {
        return x * x
    }(5)
    fmt.Println(result) // 25
}

1.4.延期

import "os"

// defer: thực thi khi function return (LIFO order)
func readFile(path string) error {
    f, err := os.Open(path)
    if err != nil {
        return err
    }
    defer f.Close() // Đảm bảo file luôn được close
    
    // ... đọc file
    return nil
}

func main() {
    // Defer chạy theo LIFO (Last In, First Out)
    defer fmt.Println("1")
    defer fmt.Println("2")
    defer fmt.Println("3")
    // Output: 3, 2, 1
    
    // Use case phổ biến: cleanup resources
    // defer mutex.Unlock()
    // defer rows.Close()
    // defer tx.Rollback()
    // defer span.End()
}

2. インターフェース

Go のインターフェイスは次のとおりです。 暗黙的.暗黙的 — type はメソッドを実装するだけでよく、「implements」を宣言する必要はありません。これは Java/C# と比較すると大きな違いです。

2.1.インターフェースの定義

// Interface definition
type Shape interface {
    Area() float64
    Perimeter() float64
}

// Circle implements Shape implicitly
type Circle struct {
    Radius float64
}

func (c Circle) Area() float64 {
    return math.Pi * c.Radius * c.Radius
}

func (c Circle) Perimeter() float64 {
    return 2 * math.Pi * c.Radius
}

// Rectangle implements Shape implicitly
type Rectangle struct {
    Width, Height float64
}

func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}

func (r Rectangle) Perimeter() float64 {
    return 2 * (r.Width + r.Height)
}

// Hàm nhận interface
func printShapeInfo(s Shape) {
    fmt.Printf("Area: %.2f, Perimeter: %.2f\n", s.Area(), s.Perimeter())
}

func main() {
    c := Circle{Radius: 5}
    r := Rectangle{Width: 10, Height: 3}
    
    printShapeInfo(c)  // Area: 78.54, Perimeter: 31.42
    printShapeInfo(r)  // Area: 30.00, Perimeter: 26.00
    
    // Slice of interfaces
    shapes := []Shape{c, r}
    for _, s := range shapes {
        printShapeInfo(s)
    }
}

2.2. Go の共通インターフェース

// io.Reader - interface quan trọng nhất
type Reader interface {
    Read(p []byte) (n int, err error)
}

// io.Writer
type Writer interface {
    Write(p []byte) (n int, err error)
}

// fmt.Stringer (tương đương toString() trong Java)
type Stringer interface {
    String() string
}

// error interface
type error interface {
    Error() string
}

// sort.Interface
type Interface interface {
    Len() int
    Less(i, j int) bool
    Swap(i, j int)
}

// Implement Stringer
type User struct {
    Name string
    Age  int
}

func (u User) String() string {
    return fmt.Sprintf("%s (age %d)", u.Name, u.Age)
}

func main() {
    user := User{Name: "Alice", Age: 30}
    fmt.Println(user) // "Alice (age 30)" - tự gọi String()
}

2.3.インターフェース構成(エンベディング)

// Compose interfaces từ các interfaces nhỏ
type Reader interface {
    Read(p []byte) (n int, err error)
}

type Writer interface {
    Write(p []byte) (n int, err error)
}

type Closer interface {
    Close() error
}

// Composed interface
type ReadWriter interface {
    Reader
    Writer
}

type ReadWriteCloser interface {
    Reader
    Writer
    Closer
}

// Best practice: Keep interfaces SMALL
// Go proverb: "The bigger the interface, the weaker the abstraction"
// Thường chỉ 1-3 methods

2.4.空のインターフェイスと任意の

// interface{} (Go < 1.18) hoặc any (Go 1.18+) chấp nhận mọi type
func printAnything(v any) {
    fmt.Printf("Type: %T, Value: %v\n", v, v)
}

func main() {
    printAnything(42)           // Type: int, Value: 42
    printAnything("hello")      // Type: string, Value: hello
    printAnything(true)         // Type: bool, Value: true
    printAnything([]int{1, 2})  // Type: []int, Value: [1 2]
    
    // map[string]any - thường dùng cho JSON
    data := map[string]any{
        "name":  "Alice",
        "age":   30,
        "roles": []string{"admin", "user"},
    }
    fmt.Println(data)
}

3. エラー処理

Go には例外 (トライキャッチ) はありません。使用されるエラー処理 明示的な戻り値 — これは意図的な設計上の選択です。

3.1.基本的なエラー処理

import (
    "errors"
    "fmt"
    "os"
    "strconv"
)

// Hàm trả về (result, error)
func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("division by zero")
    }
    return a / b, nil
}

// fmt.Errorf với format string
func findUser(id int) (*User, error) {
    if id <= 0 {
        return nil, fmt.Errorf("invalid user ID: %d", id)
    }
    // ... lookup user
    return &User{Name: "Alice"}, nil
}

func main() {
    // Pattern: check error immediately
    result, err := divide(10, 0)
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
    fmt.Println(result)
    
    // Chaining error checks
    f, err := os.Open("config.json")
    if err != nil {
        fmt.Println("Cannot open file:", err)
        return
    }
    defer f.Close()
    
    // strconv error example
    num, err := strconv.Atoi("abc")
    if err != nil {
        fmt.Println("Parse error:", err)
    }
    fmt.Println(num)
}

3.2.カスタムエラー

// Custom error type
type ValidationError struct {
    Field   string
    Message string
}

func (e *ValidationError) Error() string {
    return fmt.Sprintf("validation failed on %s: %s", e.Field, e.Message)
}

// Sentinel errors (pre-defined errors)
var (
    ErrNotFound     = errors.New("not found")
    ErrUnauthorized = errors.New("unauthorized")
    ErrForbidden    = errors.New("forbidden")
)

func getUser(id int) (*User, error) {
    if id <= 0 {
        return nil, &ValidationError{
            Field:   "id",
            Message: "must be positive",
        }
    }
    // ... user not in DB
    return nil, fmt.Errorf("user %d: %w", id, ErrNotFound)
}

3.3.エラー ラッピング (Go 1.13 以降)

import "errors"

func readConfig() error {
    f, err := os.Open("config.yaml")
    if err != nil {
        // %w wraps the original error
        return fmt.Errorf("readConfig: %w", err)
    }
    defer f.Close()
    
    // ... parse
    return nil
}

func initApp() error {
    if err := readConfig(); err != nil {
        return fmt.Errorf("initApp: %w", err)
    }
    return nil
}

func main() {
    err := initApp()
    if err != nil {
        fmt.Println(err)
        // "initApp: readConfig: open config.yaml: no such file or directory"
        
        // errors.Is - check nếu error chain chứa target error
        if errors.Is(err, os.ErrNotExist) {
            fmt.Println("File does not exist")
        }
        
        // errors.As - extract specific error type từ chain
        var pathErr *os.PathError
        if errors.As(err, &pathErr) {
            fmt.Println("Path:", pathErr.Path)
            fmt.Println("Op:", pathErr.Op)
        }
    }
}

3.4.パニック&リカバリー

// panic: dùng cho unrecoverable errors (thay vì throw)
// recover: bắt panic (thay vì catch)

func riskyOperation() {
    // panic dừng execution, unwind stack
    panic("something went terribly wrong")
}

// recover chỉ hoạt động trong defer
func safeOperation() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recovered from panic:", r)
        }
    }()
    
    riskyOperation()
    fmt.Println("This won't print")
}

func main() {
    safeOperation()
    fmt.Println("Program continues normally")
}

// ⚠️ Quy tắc:
// - KHÔNG dùng panic cho business logic errors
// - Dùng panic khi: programmer error, unrecoverable state
// - recover thường dùng trong middleware (HTTP handler recovery)

3.5.エラー処理パターンは一般的に使用されます

// Pattern 1: Early return
func processOrder(order *Order) error {
    if order == nil {
        return errors.New("order is nil")
    }
    if order.Total <= 0 {
        return fmt.Errorf("invalid total: %.2f", order.Total)
    }
    if err := validateItems(order.Items); err != nil {
        return fmt.Errorf("validate items: %w", err)
    }
    if err := chargePayment(order); err != nil {
        return fmt.Errorf("charge payment: %w", err)
    }
    return nil
}

// Pattern 2: Error type assertion for flow control
func handleError(err error) {
    var validErr *ValidationError
    if errors.As(err, &validErr) {
        // 400 Bad Request
        fmt.Printf("Validation: %s - %s\n", validErr.Field, validErr.Message)
        return
    }
    if errors.Is(err, ErrNotFound) {
        // 404 Not Found
        fmt.Println("Resource not found")
        return
    }
    if errors.Is(err, ErrUnauthorized) {
        // 401 Unauthorized
        fmt.Println("Please login")
        return
    }
    // 500 Internal Server Error
    fmt.Println("Internal error:", err)
}

// Pattern 3: errors.Join (Go 1.20+) - multiple errors
func validateUser(u *User) error {
    var errs []error
    if u.Name == "" {
        errs = append(errs, errors.New("name is required"))
    }
    if u.Email == "" {
        errs = append(errs, errors.New("email is required"))
    }
    if u.Age < 0 {
        errs = append(errs, errors.New("age must be non-negative"))
    }
    return errors.Join(errs...)
}

4. 構造の埋め込み(合成)

// Go dùng composition thay vì inheritance
type BaseModel struct {
    ID        int
    CreatedAt time.Time
    UpdatedAt time.Time
}

type User struct {
    BaseModel          // Embedding - "kế thừa" tất cả fields và methods
    Name      string
    Email     string
}

type Post struct {
    BaseModel
    Title   string
    Content string
    Author  User
}

func main() {
    user := User{
        BaseModel: BaseModel{
            ID:        1,
            CreatedAt: time.Now(),
        },
        Name:  "Alice",
        Email: "[email protected]",
    }
    
    // Access embedded fields trực tiếp
    fmt.Println(user.ID)        // 1 (từ BaseModel)
    fmt.Println(user.Name)      // "Alice"
    fmt.Println(user.CreatedAt) // (từ BaseModel)
}

5. まとめ

  • 機能: 複数の戻り値、ファーストクラス、クロージャ、遅延
  • インターフェース: 暗黙的な実装、小さなインターフェイス、合成
  • エラー処理: 明示的なエラー、ラッピング (%w)、errors.Is/As、カスタム エラー
  • 構成: 継承の代わりに構造体を埋め込む
  • パニック/回復: 回復不可能なエラーのみが対象であり、ビジネス ロジックは対象外です

次の記事: ジェネリック、パッケージ、モジュール — Go での型パラメーター システムとコード管理。