Menu
CoddyTech

Tổng quan về tạo code

Thuộc phần Lập trình hướng đối tượng trong hành trình GO của Coddy. Bài học 84 trên 107.

Tạo code là một giải pháp thay thế cho reflection, tạo mã nguồn Go trong quá trình biên dịch thay vì kiểm tra kiểu dữ liệu khi chạy. Cách tiếp cận này giúp bạn đảm bảo an toàn kiểu dữ liệu và cải thiện hiệu suất, đồng thời giảm bớt code mẫu lặp lại.

Go cung cấp lệnh go generate để tự động hóa việc tạo code. Bạn đặt một chỉ thị chú thích đặc biệt trong tệp nguồn, rồi chạy go generate để thực thi công cụ được chỉ định:

//go:generate stringer -type=Status

type Status int

const (
    Pending Status = iota
    Active
    Completed
)

Chạy go generate ./... sẽ gọi công cụ stringer, công cụ này tạo một tệp mới chứa phương thức String() cho kiểu Status. Code được tạo là code Go thông thường và sẽ được biên dịch cùng với chương trình của bạn.

Các công cụ tạo code phổ biến trong hệ sinh thái Go gồm stringer để tạo phương thức chuỗi cho enum, mockgen để tạo mock kiểm thử từ interface và protoc để tạo code từ định nghĩa Protocol Buffer. Nhiều ORM và framework API cũng dùng cách tạo code để tạo truy vấn cơ sở dữ liệu có kiểm tra kiểu hoặc code phía client.

Ưu điểm chính so với reflection là trình biên dịch sẽ kiểm tra code được tạo. Lỗi được phát hiện khi build, không phải lúc chạy. Đổi lại, bạn cần thêm một bước build và quản lý các tệp được tạo trong repository. Với các mẫu lập trình hướng đối tượng cần triển khai lặp đi lặp lại cho nhiều kiểu dữ liệu, tạo code thường mang lại sự cân bằng tốt nhất giữa độ an toàn và tiện lợi.

challenge icon

Thử thách

Dễ

Cùng xây dựng một trình tạo code để tạo các phương thức String() cho các kiểu giống enum nhé! Dù không thể chạy go generate thật trong môi trường này, bạn sẽ mô phỏng cách các công cụ tạo code hoạt động: lập trình để tạo mã nguồn Go dựa trên định nghĩa kiểu.

Bạn sẽ sắp xếp code trong hai tệp:

  • generator.go: Tạo logic sinh code.

    Xây dựng một struct EnumType đại diện cho định nghĩa kiểu giống enum, với Name (chuỗi) và Values (slice chứa các chuỗi đại diện cho tên hằng số).

    Tạo hàm GenerateStringMethod(e EnumType) string để tạo mã nguồn Go cho phương thức String(). Code được tạo cần theo mẫu sau:

    func (e TypeName) String() string {
        switch e {
        case Value1:
            return "Value1"
        case Value2:
            return "Value2"
        default:
            return "Unknown"
        }
    }

    Đồng thời tạo hàm GenerateConstants(e EnumType) string để tạo các khai báo hằng số bằng iota:

    const (
        Value1 TypeName = iota
        Value2
        Value3
    )

    Cuối cùng, tạo GenerateFullCode(e EnumType) string để kết hợp cả hai đầu ra cùng một chú thích đầu tệp cho biết đây là code được tạo:

    // Code generated by enum generator. DO NOT EDIT.
    
    [constants]
    
    [string method]
  • main.go: Dùng trình tạo để tạo code cho các kiểu enum khác nhau.

    Đọc tên kiểu enum, sau đó đọc số lượng giá trị và lần lượt đọc tên của từng giá trị. Truyền thông tin này cho trình tạo rồi in toàn bộ code đã tạo.

Chương trình sẽ cung cấp đầu vào như sau:

  • Dòng 1: Tên kiểu enum
  • Dòng 2: Số lượng giá trị
  • Các dòng tiếp theo: Tên của từng giá trị

Ví dụ, với đầu vào:

Status
3
Pending
Active
Completed

Đầu ra của bạn sẽ là:

// Code generated by enum generator. DO NOT EDIT.

const (
    Pending Status = iota
    Active
    Completed
)

func (e Status) String() string {
    switch e {
    case Pending:
        return "Pending"
    case Active:
        return "Active"
    case Completed:
        return "Completed"
    default:
        return "Unknown"
    }
}

Và với đầu vào:

Priority
2
Low
High

Đầu ra của bạn sẽ là:

// Code generated by enum generator. DO NOT EDIT.

const (
    Low Priority = iota
    High
)

func (e Priority) String() string {
    switch e {
    case Low:
        return "Low"
    case High:
        return "High"
    default:
        return "Unknown"
    }
}

Bài tập này minh họa khái niệm cốt lõi đằng sau các công cụ như stringer: tạo code lặp lại nhưng an toàn về kiểu dữ liệu trong lúc biên dịch, thay vì dùng reflection khi chạy. Trình tạo của bạn tạo ra code Go hợp lệ, có thể lưu vào tệp rồi biên dịch cùng phần còn lại của dự án.

Tự làm thử

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

func main() {
	reader := bufio.NewReader(os.Stdin)

	// Đọc tên kiểu enum
	typeName, _ := reader.ReadString('\n')
	typeName = strings.TrimSpace(typeName)

	// Đọc số lượng giá trị
	countStr, _ := reader.ReadString('\n')
	count, _ := strconv.Atoi(strings.TrimSpace(countStr))

	// Đọc tên từng giá trị
	values := make([]string, count)
	for i := 0; i < count; i++ {
		value, _ := reader.ReadString('\n')
		values[i] = strings.TrimSpace(value)
	}

	// TODO: Tạo EnumType từ dữ liệu đã đọc

	// TODO: Tạo và in ra toàn bộ code bằng GenerateFullCode
}
quiz iconTự kiểm tra

Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.

Tất cả bài học trong Lập trình hướng đối tượng

Tự luyện tập: Trình biên dịch Go online