Go Phase 2: Core Concepts - Kiểu Dữ Liệu, Interface & Error Handling

Chào mừng bạn đến với Phase 2 của Go Learning Roadmap! Ở Phase 1, bạn đã học cú pháp Go — biến, hàm, luồng điều khiển, slices, maps và structs. Giờ là lúc học những concept giúp chương trình Go có cấu trúc tốt: cách tổ chức behavior với methods và interfaces, xử lý lỗi đúng cách, và chia code thành packages.
Đây là nền tảng của mọi dự án Go thực tế. Nắm vững chúng ở đây, Phase 3 (concurrency) sẽ dễ hiểu hơn rất nhiều.
Bạn Sẽ Học Được Gì
✅ Định nghĩa methods trên structs — value receiver vs pointer receiver
✅ Dùng interfaces cho polymorphism — mô hình implicit implementation của Go
✅ Xử lý lỗi đúng cách — error interface, custom errors, wrapping
✅ Tổ chức code thành packages — exported vs unexported, quy ước đặt tên
✅ Quản lý dependencies với Go modules — go.mod, go get, go mod tidy
✅ Làm việc với standard library — fmt, strings, strconv, os, io
Phần 1: Structs và Methods
Bạn đã gặp structs ở Phase 1. Giờ đi sâu hơn — structs là cách chính trong Go để định nghĩa custom types và gắn behavior.
Định Nghĩa Structs
type User struct {
ID int
FirstName string
LastName string
Email string
Age int
Active bool
}
// Tạo instance
u1 := User{ID: 1, FirstName: "Alice", LastName: "Smith", Email: "[email protected]", Age: 30, Active: true}
u2 := User{FirstName: "Bob"} // Zero values cho field chưa set: ID=0, Age=0, Active=falseMethods
Method là function có receiver — gắn liền với một type:
// Method trên User — value receiver
func (u User) FullName() string {
return u.FirstName + " " + u.LastName
}
// Method trên User — pointer receiver (có thể sửa struct)
func (u *User) Deactivate() {
u.Active = false
}
func main() {
user := User{FirstName: "Alice", LastName: "Smith", Active: true}
fmt.Println(user.FullName()) // "Alice Smith"
user.Deactivate()
fmt.Println(user.Active) // false
}Value Receiver vs Pointer Receiver
Đây là một trong những quyết định quan trọng nhất khi viết Go:
| Khía Cạnh | Value Receiver (u User) | Pointer Receiver (u *User) |
|---|---|---|
| Sửa bản gốc? | Không — làm việc trên bản sao | Có — sửa trực tiếp bản gốc |
| Dùng khi | Method chỉ đọc, struct nhỏ | Method thay đổi state, struct lớn |
| Quy ước | Getters, string formatting | Setters, thay đổi trạng thái |
type Counter struct {
count int
}
// Value receiver — trả giá trị, không sửa bản gốc
func (c Counter) Value() int {
return c.count
}
// Pointer receiver — sửa struct gốc
func (c *Counter) Increment() {
c.count++
}
func main() {
c := Counter{}
c.Increment()
c.Increment()
fmt.Println(c.Value()) // 2
}Quy tắc: Nếu bất kỳ method nào dùng pointer receiver, tất cả method trên type đó nên dùng pointer receiver. Để tìm hiểu sâu hơn, xem Pointer Receiver vs Value Receiver.
Struct Embedding (Composition)
Go không có inheritance. Thay vào đó, bạn embed struct vào struct khác để tái sử dụng fields và methods:
type Address struct {
Street string
City string
State string
Zip string
}
func (a Address) Format() string {
return fmt.Sprintf("%s, %s, %s %s", a.Street, a.City, a.State, a.Zip)
}
type Employee struct {
Name string
Role string
Address // Embedded — Employee "kế thừa" fields và methods của Address
}
func main() {
e := Employee{
Name: "Alice",
Role: "Engineer",
Address: Address{
Street: "123 Main St",
City: "Portland",
State: "OR",
Zip: "97201",
},
}
// Truy cập trực tiếp embedded fields
fmt.Println(e.City) // "Portland" — promoted từ Address
fmt.Println(e.Format()) // "123 Main St, Portland, OR 97201" — promoted method
}Điểm quan trọng: Embedding là composition, không phải inheritance.
EmployeecóAddress, không làAddress. Xem thêm Favor Composition Over Inheritance.
Phần 2: Interfaces
Interface là công cụ trừu tượng mạnh nhất của Go — và hoạt động khác hoàn toàn so với Java hay TypeScript.
Implicit Implementation
Trong Go, một type implement interface tự động nếu nó có tất cả methods cần thiết. Không cần keyword implements:
// Định nghĩa interface
type Writer interface {
Write(data []byte) (int, error)
}
// FileWriter implement Writer — không cần khai báo gì
type FileWriter struct {
path string
}
func (fw FileWriter) Write(data []byte) (int, error) {
fmt.Printf("Writing %d bytes to %s\n", len(data), fw.path)
return len(data), nil
}
// ConsoleWriter cũng implement Writer
type ConsoleWriter struct{}
func (cw ConsoleWriter) Write(data []byte) (int, error) {
fmt.Print(string(data))
return len(data), nil
}
// Function nhận bất kỳ thứ gì implement Writer
func SaveReport(w Writer, report string) error {
_, err := w.Write([]byte(report))
return err
}
func main() {
// Cả hai đều hoạt động — Go kiểm tra lúc compile time
SaveReport(FileWriter{path: "/tmp/report.txt"}, "Q4 Results")
SaveReport(ConsoleWriter{}, "Q4 Results")
}Không có keyword
implements. Nếu methods khớp, type tự động satisfy interface. Đây gọi là structural typing — chính là thứ làm Go interfaces cực kỳ linh hoạt.
Các Pattern Interface Phổ Biến
Interface Nhỏ Là Tốt Nhất
Standard library của Go ưu tiên interface nhỏ gọn:
// Một method — cực kỳ hữu dụng
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
}
// Compose interface nhỏ thành lớn hơn
type ReadWriter interface {
Reader
Writer
}
type ReadWriteCloser interface {
Reader
Writer
Closer
}"Interface càng lớn, abstraction càng yếu." — Rob Pike. Giữ interface nhỏ (1-3 methods). Compose khi cần.
Interface Làm Parameter
Nhận interface, trả concrete type:
type Storage interface {
Save(key string, data []byte) error
Load(key string) ([]byte, error)
}
// MemoryStorage implement Storage
type MemoryStorage struct {
data map[string][]byte
}
func NewMemoryStorage() *MemoryStorage {
return &MemoryStorage{data: make(map[string][]byte)}
}
func (m *MemoryStorage) Save(key string, data []byte) error {
m.data[key] = data
return nil
}
func (m *MemoryStorage) Load(key string) ([]byte, error) {
data, ok := m.data[key]
if !ok {
return nil, fmt.Errorf("key not found: %s", key)
}
return data, nil
}
// Nhận interface — dễ test và linh hoạt
func ProcessData(store Storage, key string) error {
data := []byte("processed data")
return store.Save(key, data)
}Empty Interface và any
Empty interface interface{} (hoặc alias any từ Go 1.18) nhận bất kỳ type nào:
func PrintAnything(v any) {
fmt.Printf("Type: %T, Value: %v\n", v, v)
}
PrintAnything(42) // Type: int, Value: 42
PrintAnything("hello") // Type: string, Value: hello
PrintAnything(true) // Type: bool, Value: trueDùng
anyhạn chế. Nó bỏ qua type safety. Ưu tiên interface cụ thể hoặc generics (Go 1.18+) khi có thể.
Type Assertions và Type Switches
Khi nhận interface value, bạn có thể kiểm tra underlying type:
func Describe(i any) string {
// Type switch — check nhiều types
switch v := i.(type) {
case string:
return fmt.Sprintf("string dài %d", len(v))
case int:
return fmt.Sprintf("integer: %d", v)
case bool:
return fmt.Sprintf("boolean: %t", v)
default:
return fmt.Sprintf("type không xác định: %T", v)
}
}
// Type assertion — lấy type cụ thể
func MustBeString(i any) string {
s, ok := i.(string) // safe assertion — không panic
if !ok {
return "not a string"
}
return s
}Gotcha: Interface Nil
Một trong những hành vi khó hiểu nhất của Go — interface value chỉ nil khi cả type và value đều nil:
type MyError struct {
msg string
}
func (e *MyError) Error() string { return e.msg }
func DoSomething() error {
var err *MyError = nil // typed nil pointer
return err // ⚠️ KHÔNG nil! Interface có type (*MyError) với value nil
}
func main() {
err := DoSomething()
if err != nil {
// Code này chạy! Dù underlying pointer là nil
fmt.Println("Error:", err) // panic: nil pointer dereference
}
}
// ✅ Sửa: return nil trực tiếp
func DoSomethingFixed() error {
var err *MyError = nil
if err != nil {
return err
}
return nil // Untyped nil — interface thực sự nil
}Phần 3: Error Handling
Go không có exceptions. Thay vào đó, errors là values — functions trả về chúng, callers kiểm tra chúng. Đây là thiết kế có chủ đích: buộc bạn phải nghĩ về mọi điểm có thể thất bại.
error Interface
error interface tích hợp chỉ là:
type error interface {
Error() string
}Bất kỳ type nào có method Error() string đều là error.
Error Handling Cơ Bản
import (
"errors"
"fmt"
"os"
)
// Functions trả error là giá trị cuối cùng
func ReadConfig(path string) (string, error) {
data, err := os.ReadFile(path)
if err != nil {
return "", err // truyền error lên
}
return string(data), nil
}
func main() {
config, err := ReadConfig("/etc/app.conf")
if err != nil {
fmt.Println("Không đọc được config:", err)
return
}
fmt.Println("Config:", config)
}Pattern
if err != nillà đặc trưng của Go. Bạn sẽ viết nó hàng trăm lần. Dài dòng nhưng rõ ràng — luôn biết chính xác error được xử lý ở đâu.
Tạo Errors
import (
"errors"
"fmt"
)
// Error đơn giản với errors.New
func Divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
// Error có format với fmt.Errorf
func ValidateAge(age int) error {
if age < 0 {
return fmt.Errorf("tuổi không hợp lệ: %d (phải >= 0)", age)
}
if age > 150 {
return fmt.Errorf("tuổi không hợp lệ: %d (phải <= 150)", age)
}
return nil
}Custom Error Types
Khi error cần mang dữ liệu có cấu trúc:
// 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)
}
type NotFoundError struct {
Resource string
ID string
}
func (e *NotFoundError) Error() string {
return fmt.Sprintf("%s with ID %s not found", e.Resource, e.ID)
}
// Trả custom errors
func FindUser(id string) (*User, error) {
// ... tra database
return nil, &NotFoundError{Resource: "User", ID: id}
}
// Kiểm tra error type cụ thể
func HandleRequest(id string) {
user, err := FindUser(id)
if err != nil {
var notFound *NotFoundError
if errors.As(err, ¬Found) {
fmt.Printf("404: %s %s not found\n", notFound.Resource, notFound.ID)
return
}
fmt.Println("500: Internal error:", err)
return
}
fmt.Println("Found user:", user)
}Error Wrapping (Go 1.13+)
Wrap error kèm context bằng fmt.Errorf và %w:
func ReadUserConfig(userID string) (Config, error) {
path := fmt.Sprintf("/home/%s/.config", userID)
data, err := os.ReadFile(path)
if err != nil {
// Wrap kèm context — error gốc được giữ nguyên
return Config{}, fmt.Errorf("reading config for user %s: %w", userID, err)
}
config, err := parseConfig(data)
if err != nil {
return Config{}, fmt.Errorf("parsing config for user %s: %w", userID, err)
}
return config, nil
}
// Unwrap để kiểm tra error gốc
func main() {
config, err := ReadUserConfig("alice")
if err != nil {
// errors.Is kiểm tra toàn bộ error chain
if errors.Is(err, os.ErrNotExist) {
fmt.Println("File config không tồn tại — dùng giá trị mặc định")
return
}
// errors.As lấy type cụ thể từ chain
var pathErr *os.PathError
if errors.As(err, &pathErr) {
fmt.Printf("Path error on %s: %v\n", pathErr.Path, pathErr.Err)
return
}
fmt.Println("Unexpected error:", err)
}
}Sentinel Errors
Errors định nghĩa sẵn cho các điều kiện phổ biến:
import "errors"
// Định nghĩa sentinel errors ở package level
var (
ErrNotFound = errors.New("not found")
ErrUnauthorized = errors.New("unauthorized")
ErrConflict = errors.New("conflict")
)
func GetUser(id string) (*User, error) {
// ... lookup
return nil, fmt.Errorf("user %s: %w", id, ErrNotFound)
}
func main() {
_, err := GetUser("123")
if errors.Is(err, ErrNotFound) {
fmt.Println("User not found") // hoạt động kể cả khi đã wrapped
}
}Error Handling Tóm Tắt
| Function | Mục Đích | Ví Dụ |
|---|---|---|
errors.New(msg) | Tạo error đơn giản | errors.New("something failed") |
fmt.Errorf("...: %w", err) | Wrap error kèm context | fmt.Errorf("reading file: %w", err) |
errors.Is(err, target) | Kiểm tra error có match không (kiểm tra cả chain) | errors.Is(err, os.ErrNotExist) |
errors.As(err, &target) | Lấy typed error (kiểm tra cả chain) | errors.As(err, &pathErr) |
Phần 4: Packages và Visibility
Packages là cách Go tổ chức code. Mỗi file Go thuộc về một package, và hệ thống package kiểm soát thứ gì được nhìn thấy từ bên ngoài.
Package Cơ Bản
myproject/
├── go.mod
├── main.go // package main
├── user/
│ ├── user.go // package user
│ └── validation.go // package user
└── store/
└── memory.go // package store// user/user.go
package user
// Exported (chữ hoa) — nhìn thấy từ ngoài package
type User struct {
ID int
Name string
email string // unexported (chữ thường) — chỉ thấy trong package user
}
// Exported function
func New(id int, name, email string) User {
return User{ID: id, Name: name, email: email}
}
// Exported method
func (u User) DisplayName() string {
return u.Name
}
// unexported method — chỉ gọi được trong package user
func (u User) emailDomain() string {
// ...
return ""
}// main.go
package main
import (
"fmt"
"myproject/user"
)
func main() {
u := user.New(1, "Alice", "[email protected]")
fmt.Println(u.Name) // ✅ exported field
fmt.Println(u.DisplayName()) // ✅ exported method
// fmt.Println(u.email) // ❌ compile error — unexported field
// fmt.Println(u.emailDomain()) // ❌ compile error — unexported method
}Quy Tắc Visibility
| Tên bắt đầu bằng | Visibility | Ví Dụ |
|---|---|---|
| Chữ hoa | Exported (public) | User, New(), ID |
| Chữ thường | Unexported (package-private) | email, emailDomain() |
Không có keyword
public/private. Chỉ viết hoa hay viết thường. Đơn giản và compiler enforce.
Quy Ước Đặt Tên Package
// ✅ Tên package tốt — ngắn, chữ thường, một từ
package user
package store
package http
package json
// ❌ Tên package tệ
package userManager // đừng dùng camelCase
package user_service // đừng dùng underscores
package util // quá chung chung — nó làm gì?
package helpers // quá chung chung
package common // quá chung chungTránh package
utils,helpers,common. Chúng sẽ thành nơi đổ rác. Đặt tên theo thứ package cung cấp, không phải cách nó được dùng.
Hàm init()
Mỗi package có thể có hàm init() chạy tự động khi khởi động:
package database
import "fmt"
var connectionPool *Pool
func init() {
// Chạy tự động khi package được import
connectionPool = NewPool(10)
fmt.Println("Database pool initialized")
}Dùng init() hạn chế — nó chạy trước main() và khiến startup khó đoán. Ưu tiên khởi tạo tường minh.
Phần 5: Go Modules
Go modules là cách chuẩn để quản lý dependencies và versioning.
Tạo Module
# Tạo module mới
mkdir myproject && cd myproject
go mod init github.com/username/myproject
# Tạo ra file go.mod:
# module github.com/username/myproject
# go 1.22File go.mod
module github.com/username/myproject
go 1.22
require (
github.com/gin-gonic/gin v1.9.1
github.com/lib/pq v1.10.9
)
require (
// indirect dependencies (tự quản lý)
golang.org/x/net v0.17.0 // indirect
)Các Lệnh Module Thiết Yếu
# Thêm dependency
go get github.com/gin-gonic/gin
# Thêm version cụ thể
go get github.com/lib/[email protected]
# Dọn dẹp — xóa dependencies không dùng, thêm thiếu
go mod tidy
# Tải tất cả dependencies về cache local
go mod download
# Liệt kê tất cả dependencies
go list -m all
# Kiểm tra bản cập nhật
go list -m -u all
# Cập nhật dependency
go get -u github.com/gin-gonic/ginFile go.sum
go.sum chứa hash mã hóa của dependencies. Luôn commit nó — đảm bảo build nhất quán:
github.com/gin-gonic/gin v1.9.1 h1:4+fr/el88...
github.com/gin-gonic/gin v1.9.1/go.mod h1:2fq3...Best Practices Module
| Thực Hành | Lý Do |
|---|---|
Chạy go mod tidy trước khi commit | Xóa deps thừa, thêm deps thiếu |
Commit cả go.mod lẫn go.sum | Đảm bảo build nhất quán |
| Dùng version cụ thể | Tránh breaking changes từ @latest |
| Cập nhật dependencies định kỳ | Security patches và bug fixes |
Phần 6: Standard Library Thiết Yếu
Go có standard library phong phú. Đây là những package bạn dùng nhiều nhất:
fmt — Formatted I/O
name := "Alice"
age := 30
// Print
fmt.Println("Hello, World!") // có newline
fmt.Printf("Name: %s, Age: %d\n", name, age) // formatted
fmt.Print("No newline") // không newline
// Sprint — format thành string
msg := fmt.Sprintf("Hello, %s! You are %d.", name, age)
// Format verbs phổ biến
fmt.Printf("%v\n", user) // format mặc định
fmt.Printf("%+v\n", user) // kèm tên field: {ID:1 Name:Alice}
fmt.Printf("%#v\n", user) // Go-syntax: main.User{ID:1, Name:"Alice"}
fmt.Printf("%T\n", user) // type: main.User
fmt.Printf("%d\n", 42) // integer
fmt.Printf("%f\n", 3.14) // float
fmt.Printf("%s\n", "hello") // string
fmt.Printf("%q\n", "hello") // quoted string: "hello"
fmt.Printf("%x\n", 255) // hex: ff
fmt.Printf("%p\n", &user) // địa chỉ pointerstrings — Xử Lý Chuỗi
import "strings"
s := "Hello, World!"
strings.Contains(s, "World") // true
strings.HasPrefix(s, "Hello") // true
strings.HasSuffix(s, "!") // true
strings.ToUpper(s) // "HELLO, WORLD!"
strings.ToLower(s) // "hello, world!"
strings.TrimSpace(" hello ") // "hello"
strings.Split("a,b,c", ",") // ["a", "b", "c"]
strings.Join([]string{"a", "b"}, "-") // "a-b"
strings.Replace(s, "World", "Go", 1) // "Hello, Go!"
strings.Count(s, "l") // 3
strings.Index(s, "World") // 7strconv — Chuyển Đổi Chuỗi
import "strconv"
// String sang number
i, err := strconv.Atoi("42") // int: 42
f, err := strconv.ParseFloat("3.14", 64) // float64: 3.14
b, err := strconv.ParseBool("true") // bool: true
// Number sang string
s := strconv.Itoa(42) // "42"
s = strconv.FormatFloat(3.14, 'f', 2, 64) // "3.14"
s = strconv.FormatBool(true) // "true"os — Tương Tác Hệ Điều Hành
import "os"
// Biến môi trường
home := os.Getenv("HOME")
port := os.Getenv("PORT")
// Thao tác file
data, err := os.ReadFile("config.json") // đọc toàn bộ file
err = os.WriteFile("output.txt", []byte("hello"), 0644) // ghi file
// Mở file cho thao tác chi tiết hơn
file, err := os.Open("data.txt") // chỉ đọc
file, err = os.Create("new.txt") // tạo/truncate
defer file.Close()
// Command-line arguments
args := os.Args // os.Args[0] là tên chương trình
if len(args) > 1 {
fmt.Println("First arg:", args[1])
}
// Thoát
os.Exit(1) // thoát với status codeio — I/O Primitives
import (
"io"
"os"
"strings"
)
// io.Reader và io.Writer là các abstraction cốt lõi
// Nhiều types implement chúng: files, network connections, buffers...
// Copy từ reader sang writer
src := strings.NewReader("Hello, World!")
dst := os.Stdout
io.Copy(dst, src) // prints "Hello, World!"
// Đọc tất cả bytes từ reader
data, err := io.ReadAll(strings.NewReader("data"))Best Practices Tóm Tắt
Structs & Methods
| Thực Hành | Ví Dụ |
|---|---|
| Dùng pointer receiver nếu bất kỳ method nào mutate | Tất cả method trên type nên dùng *T |
| Cung cấp constructor function | func NewUser(name string) *User |
| Giữ struct tập trung | Mỗi struct một trách nhiệm |
| Embed cho composition, không phải inheritance | type Admin struct { User } |
Interfaces
| Thực Hành | Ví Dụ |
|---|---|
| Giữ interface nhỏ (1-3 methods) | type Reader interface { Read(p []byte) (int, error) } |
| Định nghĩa interface ở nơi dùng, không phải nơi implement | Package consumer định nghĩa interface |
| Nhận interface, trả concrete type | func NewServer(store Storage) *Server |
| Đừng tạo interface quá sớm | Đợi khi có 2+ implementations |
Error Handling
| Thực Hành | Ví Dụ |
|---|---|
| Luôn kiểm tra error trả về | if err != nil { return err } |
| Thêm context khi wrapping | fmt.Errorf("reading user %s: %w", id, err) |
| Dùng sentinel errors cho điều kiện expected | var ErrNotFound = errors.New("not found") |
| Dùng custom types cho structured errors | type ValidationError struct { ... } |
Packages
| Thực Hành | Ví Dụ |
|---|---|
| Tên ngắn, chữ thường, một từ | user, store, auth |
Tránh package utils/helpers | Đặt tên theo thứ nó cung cấp |
Giữ init() đơn giản hoặc tránh dùng | Ưu tiên khởi tạo tường minh |
Chạy go mod tidy trước khi commit | Dọn dẹp dependencies thừa |
Sai Lầm Thường Gặp
1. Trả Typed Nil (Interface Gotcha)
// SAI: Trả interface non-nil với value nil
func GetError() error {
var err *MyError = nil
return err // interface{type: *MyError, value: nil} — KHÔNG nil!
}
// ĐÚNG: Trả untyped nil
func GetError() error {
var err *MyError = nil
if err != nil {
return err
}
return nil // interface thực sự nil
}2. Bỏ Qua Errors
// SAI: Im lặng bỏ qua error
data, _ := os.ReadFile("config.json") // Nếu file không tồn tại thì sao?
// ĐÚNG: Xử lý hoặc ít nhất là log
data, err := os.ReadFile("config.json")
if err != nil {
log.Fatal("failed to read config:", err)
}3. Interface Quá Lớn
// SAI: Interface "bếp chén" — khó implement, khó test
type UserService interface {
Create(u User) error
Update(u User) error
Delete(id string) error
Find(id string) (*User, error)
List() ([]User, error)
SendEmail(id, subject, body string) error
ResetPassword(id string) error
GenerateReport() ([]byte, error)
}
// ĐÚNG: Interface nhỏ, tập trung
type UserReader interface {
Find(id string) (*User, error)
List() ([]User, error)
}
type UserWriter interface {
Create(u User) error
Update(u User) error
Delete(id string) error
}4. Không Dùng errors.Is / errors.As
// SAI: So sánh trực tiếp — hỏng khi error bị wrapped
if err == os.ErrNotExist {
// Không match nếu err đã bị wrap bằng fmt.Errorf("%w", ...)
}
// ĐÚNG: errors.Is kiểm tra toàn bộ error chain
if errors.Is(err, os.ErrNotExist) {
// Hoạt động kể cả với wrapped errors
}Bài Tập Thực Hành
Bài 1: Shape Calculator
Xây shape calculator dùng interfaces:
type Shape interface {
Area() float64
Perimeter() float64
String() string
}Implement Circle, Rectangle và Triangle. Viết hàm PrintShapeInfo(s Shape) hoạt động với mọi shape.
Lời giải:
package main
import (
"fmt"
"math"
)
type Shape interface {
Area() float64
Perimeter() float64
String() string
}
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 }
func (c Circle) String() string { return fmt.Sprintf("Circle(r=%.1f)", c.Radius) }
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) }
func (r Rectangle) String() string {
return fmt.Sprintf("Rectangle(%.1f x %.1f)", r.Width, r.Height)
}
type Triangle struct {
A, B, C float64 // các cạnh
}
func (t Triangle) Area() float64 {
s := t.Perimeter() / 2 // nửa chu vi (công thức Heron)
return math.Sqrt(s * (s - t.A) * (s - t.B) * (s - t.C))
}
func (t Triangle) Perimeter() float64 { return t.A + t.B + t.C }
func (t Triangle) String() string {
return fmt.Sprintf("Triangle(%.1f, %.1f, %.1f)", t.A, t.B, t.C)
}
func PrintShapeInfo(s Shape) {
fmt.Printf("%s → Area: %.2f, Perimeter: %.2f\n", s, s.Area(), s.Perimeter())
}
func main() {
shapes := []Shape{
Circle{Radius: 5},
Rectangle{Width: 4, Height: 6},
Triangle{A: 3, B: 4, C: 5},
}
for _, s := range shapes {
PrintShapeInfo(s)
}
}
// Circle(r=5.0) → Area: 78.54, Perimeter: 31.42
// Rectangle(4.0 x 6.0) → Area: 24.00, Perimeter: 20.00
// Triangle(3.0, 4.0, 5.0) → Area: 6.00, Perimeter: 12.00Bài 2: Error Handling Chain
Xây hàm đăng ký user với validation và error handling đúng cách:
// Implement:
// - ValidateEmail(email string) error
// - ValidatePassword(password string) error
// - RegisterUser(name, email, password string) (*User, error)
//
// Dùng custom error types, error wrapping và errors.As để xử lý.Lời giải:
package main
import (
"errors"
"fmt"
"strings"
"unicode"
)
type ValidationError struct {
Field string
Message string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("%s: %s", e.Field, e.Message)
}
var ErrUserExists = errors.New("user already exists")
func ValidateEmail(email string) error {
if !strings.Contains(email, "@") {
return &ValidationError{Field: "email", Message: "phải chứa @"}
}
if !strings.Contains(email, ".") {
return &ValidationError{Field: "email", Message: "phải chứa domain"}
}
return nil
}
func ValidatePassword(password string) error {
if len(password) < 8 {
return &ValidationError{Field: "password", Message: "phải ít nhất 8 ký tự"}
}
hasUpper := false
hasDigit := false
for _, r := range password {
if unicode.IsUpper(r) { hasUpper = true }
if unicode.IsDigit(r) { hasDigit = true }
}
if !hasUpper {
return &ValidationError{Field: "password", Message: "phải chứa chữ hoa"}
}
if !hasDigit {
return &ValidationError{Field: "password", Message: "phải chứa số"}
}
return nil
}
type User struct {
Name string
Email string
}
var existingUsers = map[string]bool{"[email protected]": true}
func RegisterUser(name, email, password string) (*User, error) {
if err := ValidateEmail(email); err != nil {
return nil, fmt.Errorf("đăng ký thất bại: %w", err)
}
if err := ValidatePassword(password); err != nil {
return nil, fmt.Errorf("đăng ký thất bại: %w", err)
}
if existingUsers[email] {
return nil, fmt.Errorf("đăng ký cho %s: %w", email, ErrUserExists)
}
user := &User{Name: name, Email: email}
existingUsers[email] = true
return user, nil
}
func main() {
testCases := []struct{ name, email, password string }{
{"Alice", "alice", "Pass1234"}, // email sai
{"Bob", "[email protected]", "short"}, // password yếu
{"Carol", "[email protected]", "Pass1234"}, // trùng user
{"Dave", "[email protected]", "Secure1234"}, // thành công
}
for _, tc := range testCases {
user, err := RegisterUser(tc.name, tc.email, tc.password)
if err != nil {
var valErr *ValidationError
if errors.As(err, &valErr) {
fmt.Printf("Validation error on %s: %s\n", valErr.Field, valErr.Message)
} else if errors.Is(err, ErrUserExists) {
fmt.Printf("User %s đã tồn tại\n", tc.email)
} else {
fmt.Println("Unexpected error:", err)
}
continue
}
fmt.Printf("Đăng ký thành công: %s (%s)\n", user.Name, user.Email)
}
}Bài 3: Simple Key-Value Store
Xây key-value store với Storage interface, MemoryStore implementation, và LoggingStore decorator log mọi thao tác:
type Storage interface {
Get(key string) (string, error)
Set(key string, value string) error
Delete(key string) error
}Implement MemoryStore và LoggingStore (bọc bất kỳ Storage nào). Viết main() demo cả hai.
Lời giải:
package main
import (
"errors"
"fmt"
"time"
)
var ErrKeyNotFound = errors.New("key not found")
type Storage interface {
Get(key string) (string, error)
Set(key, value string) error
Delete(key string) error
}
// MemoryStore — implementation in-memory
type MemoryStore struct {
data map[string]string
}
func NewMemoryStore() *MemoryStore {
return &MemoryStore{data: make(map[string]string)}
}
func (m *MemoryStore) Get(key string) (string, error) {
val, ok := m.data[key]
if !ok {
return "", fmt.Errorf("get %q: %w", key, ErrKeyNotFound)
}
return val, nil
}
func (m *MemoryStore) Set(key, value string) error {
m.data[key] = value
return nil
}
func (m *MemoryStore) Delete(key string) error {
if _, ok := m.data[key]; !ok {
return fmt.Errorf("delete %q: %w", key, ErrKeyNotFound)
}
delete(m.data, key)
return nil
}
// LoggingStore — decorator log mọi thao tác
type LoggingStore struct {
inner Storage
}
func NewLoggingStore(inner Storage) *LoggingStore {
return &LoggingStore{inner: inner}
}
func (l *LoggingStore) Get(key string) (string, error) {
start := time.Now()
val, err := l.inner.Get(key)
fmt.Printf("[GET] key=%q duration=%v err=%v\n", key, time.Since(start), err)
return val, err
}
func (l *LoggingStore) Set(key, value string) error {
start := time.Now()
err := l.inner.Set(key, value)
fmt.Printf("[SET] key=%q value=%q duration=%v err=%v\n", key, value, time.Since(start), err)
return err
}
func (l *LoggingStore) Delete(key string) error {
start := time.Now()
err := l.inner.Delete(key)
fmt.Printf("[DEL] key=%q duration=%v err=%v\n", key, time.Since(start), err)
return err
}
func main() {
var store Storage = NewLoggingStore(NewMemoryStore())
store.Set("name", "Alice")
store.Set("role", "admin")
val, _ := store.Get("name")
fmt.Println("Got:", val)
store.Delete("role")
_, err := store.Get("role")
if errors.Is(err, ErrKeyNotFound) {
fmt.Println("Key đã xóa thành công")
}
}Tiếp Theo
Bạn đã hiểu hệ thống type, interfaces, error handling và package organization — nền tảng cốt lõi của mọi chương trình Go.
Trong Phase 3: Concurrency & Advanced Patterns, bạn sẽ học:
- Goroutines và Go scheduler
- Channels cho giao tiếp giữa các goroutines
- Select statements và patterns
syncpackage (Mutex, WaitGroup, Once)- Context cho cancellation và timeouts
Bài viết liên quan:
- Go Learning Roadmap — Tổng quan series
- Pointer Receiver vs Value Receiver — Deep dive vào method receivers
- Favor Composition Over Inheritance — Cách Go tái sử dụng code
- Goroutines & Concurrency Fundamentals — Preview Phase 3
- Go Channels Communication — Channel deep dive
Tóm Tắt
Trong bài này, bạn đã học:
✅ Structs & Methods: Định nghĩa custom types, gắn behavior với value và pointer receivers
✅ Embedding: Compose types bằng struct embedding thay vì inheritance
✅ Interfaces: Implicit implementation, interface nhỏ, type assertions và switches
✅ Error Handling: error interface, custom errors, wrapping với %w, errors.Is/errors.As
✅ Packages: Exported vs unexported, quy ước đặt tên, hàm init()
✅ Go Modules: go.mod, go.sum, các lệnh thiết yếu (go get, go mod tidy)
✅ Standard Library: fmt, strings, strconv, os, io cơ bản
Bạn đã sẵn sàng chinh phục tính năng đặc trưng nhất của Go — concurrency với goroutines và channels ở Phase 3!
Series: Go Learning Roadmap
Previous: Phase 1: Go Fundamentals
Next: Phase 3: Concurrency & Advanced Patterns (coming soon)
📬 Đăng ký nhận bản tin
Nhận những bài viết mới nhất qua email mỗi tuần. Không spam, hủy đăng ký bất cứ lúc nào.
Chúng tôi tôn trọng quyền riêng tư của bạn. Hủy đăng ký bất cứ lúc nào.
💬 Comments
Đăng nhập để bình luận
Chúng tôi sẽ không đăng bất cứ điều gì mà không có sự cho phép của bạn.