1 điểm bởi GN⁺ 3 giờ trước | 1 bình luận | Chia sẻ qua WhatsApp
  • Go là một lựa chọn giúp giảm bớt sự phức tạp quá mức trong phát triển backend, với các ưu điểm cốt lõi là biên dịch nhanh, triển khai bằng một binary duy nhất và quản lý dependency ổn định
  • Thay vì những tầng trừu tượng phức tạp như decorator, metaclass, macro, trait hay monad, Go chọn thiết kế ngôn ngữ đơn giản xoay quanh struct, hàm, interface, goroutine và channel
  • Chỉ với standard library và công cụ mặc định như embed, html/template, net/http, database/sql, encoding/json, go test, pprof, có thể xử lý từ web app, cơ sở dữ liệu, kiểm thử, benchmark đến profiling
  • Goroutine là đơn vị thực thi stackful có chi phí khoảng 2KB, và có thể xử lý concurrency cùng việc lan truyền hủy tác vụ một cách đơn giản thông qua channel, sync.Mutex, race detector và context.Context
  • Luồng go mod init, go build, scp, systemctl restart khuyến nghị triển khai đơn giản với một binary Go và Postgres, thay vì node_modules, cấu hình Docker·Kubernetes phức tạp hay microservice quá mức

Vì sao nên chọn Go

  • Go là một lựa chọn giúp giảm bớt sự phức tạp quá mức trong phát triển backend, với các ưu điểm cốt lõi là biên dịch nhanh, triển khai bằng một binary duy nhất và quản lý dependency ổn định
  • Cũng như HTML vẫn là phương án thay thế cho sự phức tạp hóa quá mức ở frontend, Go đã tồn tại hơn 10 năm như một lựa chọn để đơn giản hóa backend
  • Với một ứng dụng CRUD chỉ cung cấp form đơn giản hoặc xử lý khoảng 40 request mỗi giây, việc huy động hàng loạt package Node, công cụ build TypeScript, Kubernetes, đội platform cho Rails, hay thậm chí viết lại bằng Rust là quá tay
  • Mục tiêu của Go là mã dễ đọc, kết quả có thể triển khai, và gánh nặng vận hành nhỏ, thay vì “những tầng trừu tượng thông minh”

Thiết kế ngôn ngữ cố tình nhàm chán

  • Lý do Go có vẻ nhàm chán nằm ở thiết kế có chủ đích: nó không cung cấp những tầng trừu tượng phức tạp như decorator, metaclass, macro, trait hay monad
  • Các thành phần cốt lõi chỉ giới hạn ở struct, hàm, interface, goroutine và channel
  • Mục tiêu là đủ đơn giản để có thể đọc xong đặc tả trong thời gian ngắn và viết mã hiệu quả ngay trong ngày hôm đó
  • Sự nhàm chán lại là lợi thế trong codebase của cả nhóm
    • Một junior mới vào tháng trước vẫn có thể đọc được đoạn mã mà principal viết từ 2 năm trước
    • gofmt ép mọi người theo một định dạng duy nhất nên giảm tranh cãi về style code
    • Bản thân ngôn ngữ khiến việc nhét các tầng trừu tượng quá phức tạp vào codebase trở nên khó hơn

Standard library đóng vai trò như framework

  • Go có thể tạo web app chỉ bằng standard library mà không cần framework web riêng
  • Dùng embed, html/template, net/http có thể tạo ứng dụng nhúng template HTML vào binary và render qua HTTP handler
package main

import (
    "embed"
    "html/template"
    "net/http"
)

//go:embed templates/*.html
var files embed.FS

var tmpl = template.Must(template.ParseFS(files, "templates/*.html"))

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        tmpl.ExecuteTemplate(w, "index.html", map[string]string{
            "Name": "asshole",
        })
    })

    http.ListenAndServe(":8080", nil)
}
  • Ví dụ này là một web app chạy được, trong đó template HTML được biên dịch và nhúng vào binary
  • Không cần webpack, Vite, dev server hay node_modules khổng lồ; chỉ cần go build rồi triển khai một file duy nhất
  • Chỉ với standard library và công cụ mặc định là có thể xử lý hầu hết tác vụ backend chính
    • Cơ sở dữ liệu: database/sql
    • JSON: encoding/json
    • Gọi dịch vụ khác: client net/http
    • Chạy đồng thời: từ khóa go
    • Kiểm thử: go test
    • Benchmark: go test -bench
    • Profiling: pprof

Cấu trúc standard library có chiều sâu

  • io.Readerio.Writer

    • io.Readerio.Writer là các interface chỉ có một method, nhưng lại là nền tảng quan trọng xuyên suốt hệ sinh thái Go
    • Có thể ghép response body HTTP với gzip writer rồi nối tiếp sang file trên đĩa bằng rất ít code
    • Vì các package chính đều dùng chung hai interface này, cùng một pattern có thể tái sử dụng lặp đi lặp lại ở nhiều nơi
  • context.Context

    • context.Context là cách tiêu chuẩn để lan truyền hủy tác vụ
    • Nếu người dùng đóng tab trình duyệt, request context sẽ bị hủy, từ đó có thể hủy luôn cả query database và các lời gọi HTTP xuống các dịch vụ bên dưới
    • Muốn tránh rò rỉ goroutine hay các query “xác sống” làm cạn pool kết nối, cần truyền context làm tham số đầu tiên và tôn trọng nó
  • Các package encoding

    • encoding/json, encoding/xml, encoding/csv, encoding/binary đều có sẵn trong standard library
    • Vì cách dùng với struct tag và decode bằng con trỏ khá giống nhau, học một package thì cũng dễ dùng các package còn lại

Mô hình concurrency giúp bớt đau khổ

  • Goroutine không phải là OS thread, mà là đơn vị thực thi stackful được runtime multiplex lên trên các OS thread
  • Chi phí khởi tạo goroutine chỉ khoảng 2KB, nên ngay cả laptop cũng có thể tạo ra 100.000 goroutine
  • Channel hoạt động như một đường ống có kiểu giữa các goroutine; một bên gửi và bên kia nhận, còn runtime lo phần đồng bộ hóa
  • Khi cần trạng thái dùng chung có thể dùng sync.Mutex, và race detector sẽ giúp tìm ra data race
  • Ngay cả HTTP fetcher chạy song song cũng có thể viết mà không cần thư viện riêng, framework hay nghi thức async/await
results := make(chan string, len(urls))
for _, url := range urls {
    go func(u string) {
        resp, _ := http.Get(u)
        results <- resp.Status
    }(url)
}
for range urls {
    fmt.Println(<-results)
}

Ví dụ route CRUD thực tế

  • Một route kiểu CRUD đọc bài viết từ Postgres và render HTML cũng có thể được tổ chức đơn giản đến mức nằm gọn trong một màn hình
//go:embed templates/*.html
var tmplFS embed.FS

var tmpl = template.Must(template.ParseFS(tmplFS, "templates/*.html"))

type Post struct {
    ID    int
    Title string
    Body  string
}

func postsHandler(db *sql.DB) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        rows, err := db.QueryContext(r.Context(),
            "SELECT id, title, body FROM posts ORDER BY id DESC LIMIT 50")
        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }
        defer rows.Close()

        var posts []Post
        for rows.Next() {
            var p Post
            if err := rows.Scan(&p.ID, &p.Title, &p.Body); err != nil {
                http.Error(w, err.Error(), http.StatusInternalServerError)
                return
            }
            posts = append(posts, p)
        }

        tmpl.ExecuteTemplate(w, "posts.html", posts)
    }
}
  • Ví dụ này cho thấy database, template và HTTP handler trong cùng một chỗ
  • r.Context() được truyền vào câu lệnh SQL, nếu kết nối bị đóng thì query cũng có thể bị hủy
  • Không cần ORM, DI container, tầng service hay thư mục controllers/ đầy các abstract base class; chỉ cần đọc từ trên xuống dưới là hiểu được luồng hoạt động

Quản lý dependency không phá hỏng cuối tuần

  • Khởi tạo module bằng go mod init thì dependency sẽ được ghi trong go.modgo.sum
  • go.sum thực chất là một bản ghi mật mã học của những gì đã tải về, giúp kiểm tra khi có dependency khác với thứ mong đợi xuất hiện
  • Không có sự phức tạp kiểu thư mục node_modules, lockfile bị lệch giữa môi trường dev và CI, peer dependencies, optional dependencies, devDependencies hay peerDependenciesMeta
  • Nếu cần build offline, go mod vendor sẽ tải dependency về thư mục vendor/, và toolchain sẽ tự động dùng chúng
  • Có thể gói toàn bộ project cùng dependency vào một tarball duy nhất, thuận lợi cho vận hành và rà soát bảo mật

Công cụ đi kèm cùng compiler

  • Các công cụ mặc định của Go được cung cấp sẵn mà không cần plugin bên thứ ba hay file cấu hình riêng
  • gofmt chuẩn hóa format code, giảm tranh cãi về format và giảm diff phình ra chỉ vì khoảng trắng
  • go vet dùng để bắt các lỗi rõ ràng
  • go test chạy test
  • go test -race chạy test cùng race detector để tìm data race
  • go test -bench chạy benchmark
  • go test -cover kiểm tra test coverage
  • go tool pprof cho phép lấy flame graph CPU và bộ nhớ thông qua HTTP endpoint của service production đang chạy

Triển khai chỉ cần lệnh copy là xong

  • Luồng cốt lõi của việc triển khai Go là build binary, copy lên server rồi chạy
GOOS=linux GOARCH=amd64 go build -o myapp ./cmd/myapp
scp myapp user@server:/usr/local/bin/
ssh user@server 'systemctl restart myapp'
  • Luồng này cho phép triển khai mà không cần Dockerfile, multi-stage build, cảnh báo CVE của base image, Kubernetes manifest, Helm chart, ArgoCD, service mesh hay sidecar
  • Chỉ với một binary static link khoảng 12MB và một file unit systemd khoảng 20 dòng là có thể triển khai production
  • Nếu thật sự cần Docker, chỉ cần đặt binary Go vào image FROM scratch là đủ

So với framework

  • Các framework như Rails, Django, Express hay Next.js đều đi kèm gánh nặng riêng như quy trình deploy, ORM, admin, middleware, cảnh báo npm hay thay đổi trong quy ước routing
  • Binary Go được biên dịch rồi chạy, và có lợi thế về tính ổn định đủ để 5 năm sau vẫn có thể chạy
  • Trong bối cảnh framework có thể bị bỏ đi nhanh hơn hoặc maintainer có thể kiệt sức, mô hình thực thi đơn giản của Go càng nổi bật

Một binary Go duy nhất thay vì microservice

  • Microservice không nên là lựa chọn mặc định; tốt hơn là viết monolith trước
  • Cấu hình được khuyến nghị là một binary Go, một Postgres và chỉ thêm một Redis khi thật sự cần
  • Có thể phục vụ cả HTML lẫn JSON API trên cùng một cổng và chạy trên một VPS duy nhất
  • Vì Go có chi phí goroutine thấp và xử lý concurrency mạnh, nó có thể scale lên đến 10.000 request mỗi giây mà không gặp khó khăn lớn
  • Khi thực sự cần tách riêng, có thể chia từ monolith Go bằng cách chuyển package sang repository khác
  • Vì interface đã có sẵn, bản thân ngôn ngữ tự nhiên dẫn đến cấu trúc có tính đến khả năng tách ra sau này

Generics và xử lý lỗi

  • if err != nil không phải bug mà là một tính năng
  • Nó buộc lập trình viên tự quyết định phải làm gì ở từng điểm thất bại thay vì che giấu lỗi
  • Việc lồng try/catch không làm lỗi biến mất, mà chỉ có thể che nó đi cho đến lúc production gặp sự cố
  • Generics được đưa vào từ Go 1.18, và chỉ cần dùng khi thật sự cần

Kết luận

  • Không phải lúc nào framework, microservice, việc viết lại bằng Rust hay meta-framework JavaScript mới cũng là thứ cần thiết
  • Khuyến nghị là chạy go mod init, viết main.go, embed template rồi biên dịch và triển khai theo một luồng đơn giản
  • Lựa chọn nhàm chán mới là lựa chọn đúng đắn, và Go chính là lựa chọn đó

1 bình luận

 
Ý kiến trên Lobste.rs
  • Không phải muốn trách người truyền đạt, nhưng kiểu văn phong blog như thế này thật mệt và trẻ con. Có thể lúc đầu thấy buồn cười, nhưng càng lặp lại thì mức độ khó chịu càng tăng theo cấp số nhân
    Dù vậy thì Go vẫn tốt. Gần đây tôi chuyển từ một dự án TypeScript sang dự án Go, và sức khỏe tinh thần lẫn tinh thần làm việc đang cải thiện rất nhanh
    Tôi chấp nhận ý rằng if err != nil không phải lỗi mà là tính năng, nhưng vẫn cho rằng đó là khuyết điểm lớn nhất của Go. Nếu có sum types thì có lẽ mọi thứ đã tiện dụng hơn nhiều mà không phải dựa vào ép kiểu lúc chạy

    • Tôi thấy kiểu bài này vẫn còn hơn mấy bài AI rác kiểu đứng núi này trông núi nọ mà thực ra chẳng có lập trường gì
    • Đọc cũng vui, nhưng tôi không thấy quá nhiều bài kiểu này. Dù sao thì gọi ai đó là “walnut” vẫn buồn cười hơn “dipshit”
      Nếu đã viết theo kiểu này thì ít nhất cũng nên xúc phạm cho có tí sáng tạo
    • Đồng ý. Có cách nào để báo cáo không? Nó chẳng khớp với hạng mục báo cáo nào cả
  • Nhìn các bình luận khác thì có vẻ đây là ý kiến không được ưa chuộng, và tôi không muốn nghe quá gay gắt, nhưng tôi thật sự ghét Go
    Go là một ngôn ngữ có cú pháp tạm ổn đặt lên trên một runtime hiệu quả cho đồng thời, rồi được đẩy mạnh hệ sinh thái nhờ sức mạnh của Google. Ngoài ra thì tôi thấy nó kinh khủng
    Vấn đề lớn nhất là nó trông như được thiết kế để cố tình phớt lờ hàng chục năm nghiên cứu thiết kế ngôn ngữ lập trình, thậm chí cả thực hành thực tế. Mãi hàng chục năm sau mới có generics
    Không phải lúc nào cũng cần dùng dependent types, nhưng dù sao cũng phải có mức độ. Go gần như không có các tính năng mô hình hóa dữ liệu, mô hình hóa bất biến và cấu trúc mã mà một ngôn ngữ hiện đại nên có. Rust có đường cong học tập dốc hơn, nhưng ở những mặt này tốt hơn hẳn, mà thật ra cũng không cần hệ thống kiểu tinh vi tới mức Rust mới ổn. Nếu lo thời gian biên dịch, vẫn có thể làm một hệ thống kiểu lành mạnh, nhanh và giàu biểu đạt chỉ với các tính năng đơn giản nhưng hữu ích
    Và tôi thấy if err != nil là cách tệ nhất để rải đầy mã bằng tiếng ồn xử lý lỗi. Tôi không hiểu vì sao phía Go lại dị ứng với sum types đến vậy. Ở điểm này ngay cả ngoại lệ của Java còn tốt hơn. Thực tế là vì ngôn ngữ không có tính năng tốt hơn để xử lý lỗi, nên người ta nhầm một miếng vá tệ hại thành tính năng
    Nếu ngay từ đầu bài gốc không viết kiểu trịch thượng thì tôi cũng đã không viết bình luận này. “Cứ dùng X đi” là câu ngu ngốc. Hãy dùng công cụ phù hợp với trường hợp sử dụng, thoải mái và hiệu quả. Nếu đó là Go thì dùng Go, nếu không thì chọn thứ khác

    • Tôi nghĩ vị trí của Go trong không gian thiết kế là lựa chọn ưu tiên sự đơn giản cho lập trình viên mới vào nghề trong các codebase lớn và tổ chức lớn hơn gần như mọi thứ khác. Vì thế ngay cả lập trình viên ít kinh nghiệm cũng dễ đọc mã và sửa cục bộ mà không cần tích lũy nhiều bối cảnh
      Điều này đặc biệt hữu ích ở các tổ chức như Google, nơi có hàng nghìn lập trình viên và thời gian họ ở lại một nhóm hay công ty cụ thể có thể khá ngắn
      Trong bối cảnh đó, việc thiếu một hệ thống kiểu nâng cao ở mức nào đó lại thành ưu điểm, nhất là với lập trình viên non tay. Họ gần như không phải nghĩ về kiểu ngoài các khái niệm rất cơ bản như kiểu nguyên thủy hay struct. Nó gần như không cho bạn công cụ để mô hình hóa dữ liệu, nhưng bù lại bạn có thể viết rất nhiều mã mà không cần nghĩ quá nhiều
      Tôi không cho đó là điều tốt cho tính đúng đắn ở cấp độ ngôn ngữ. Nhưng trong tổ chức lớn, người ta dựa nhiều hơn vào hạ tầng xung quanh như phân tích monorepo, CI/CD, canary test, công cụ quan sát. Những thứ đó gánh tải nhiều hơn hẳn so với ở tổ chức nhỏ
      Tôi cũng phần nào thích Go vì gánh nặng nhận thức thấp tương tự như vậy, vì tôi chỉ thỉnh thoảng viết mã cho một số dự án và hiện không tham gia sâu hằng ngày vào một dự án dài hạn nào. Có thể quay lại một codebase không đụng tới cả tháng và làm việc dưới một giờ là lợi thế lớn. Nhưng nếu là lập trình viên toàn thời gian cho một dự án phức tạp, có lẽ tôi sẽ bớt thích hơn
    • Dart cũng là một ngôn ngữ của Google mà không có vẻ như đang phớt lờ hàng chục năm nghiên cứu, nhưng ngoài Flutter thì chẳng ai dùng. Go thì vẫn ổn
    • Bài này bắt chước một khuôn meme hung hăng và trịch thượng. Vì vậy rõ ràng nó sẽ chọc tức người khác, và tôi không thích điều đó vì tôi nghĩ ý chính của bài đáng để có một cuộc trao đổi tử tế chứ không phải một cuộc khẩu chiến
      Tôi nghĩ các nhà phát triển Go tập trung vào việc làm tốt các thứ nền tảng, vì những ngôn ngữ trước đó và cả cộng đồng nghiên cứu lý thuyết ngôn ngữ lập trình đã bỏ bê các thứ nền tảng ấy. Mọi người cứ ám ảnh với hệ thống kiểu toàn diện nhất, nhưng hệ thống kiểu càng phức tạp và giàu biểu đạt thì lợi ích tăng thêm càng ít, và dù có dồn bao nhiêu công sức vào hệ thống kiểu cũng không thể bù cho quản lý gói tệ hại, công cụ build buộc cả đội phải học một DSL mới, hệ thống tài liệu không tự tạo thông tin kiểu hay liên kết tới tài liệu gói bên thứ ba, thư viện chuẩn nghèo nàn, vấn đề hiệu năng nghiêm trọng, thiếu chiến lược biên dịch tĩnh, thời gian build đau khổ, đường cong học tập dốc, hệ thống kiểu mang tính trừng phạt, cú pháp khó đọc, hay tích hợp trình soạn thảo tệ
      Nói Go hoàn toàn không có tính năng mô hình hóa dữ liệu là sai rõ ràng. Trong bất kỳ ngôn ngữ nào cũng có thể mô hình hóa dữ liệu và bất biến, và Go cũng cung cấp đủ hệ thống kiểu để cưỡng chế mô hình đó ở mức khá
      Rust rất tuyệt, và là lựa chọn tốt khi tốc độ lặp không quan trọng, hoặc khi triển khai trên bare metal, hoặc khi yêu cầu về tính đúng đắn hay hiệu năng cực cao. Nhưng nó không phải mặc định tốt cho phát triển ứng dụng phổ thông, nhất là theo nhóm. Đúng là phải gõ if err != nil nhiều, nhưng tôi không nghĩ có ai bị nghẽn cổ chai ở số phím gõ mỗi giây
    • Ngoài Rust ra thì gần như không có ngôn ngữ hiện đại nào có các tính năng như vậy. Trừ khi bạn muốn lập trình bằng Gleam hay Swift, mà nếu đã ngách đến mức đó thì thà dùng Haskell còn hơn
  • Ý rằng if err != nil không phải lỗi mà là tính năng, và nó khiến bạn nhìn thấy mọi điểm có thể xảy ra vấn đề, là sai
    Thực tế là nó không hề bắt buộc. Nếu không tự kiểm tra thì bỏ qua lỗi còn dễ hơn
    Xét về cách xử lý hoặc lan truyền lỗi, Rust vẫn là ví dụ sáng giá

    • Đúng vậy. Nếu không có thứ như errcheck thì lỗi bị bỏ qua quá dễ, và điều đó đơn giản là ngớ ngẩn. Ít nhất phải bắt buộc người ta vứt bỏ lỗi một cách tường minh
      May là mọi dự án Go tôi làm trong vài năm gần đây đều dùng golangci-lint bên trên lớp kiểm tra tĩnh tích hợp nghèo nàn của Go. Thành thật mà nói, đó nên là thứ bắt buộc cho mọi dự án Go
    • Ở điểm này Swift làm tốt hơn. Về mặt chức năng thì vẫn là cùng một mô hình, nhưng phía Swift giúp việc lan truyền lỗi giữa các thư viện khác nhau dễ hơn. Tuy nhiên đó chỉ là khác biệt về ưu nhược điểm và lựa chọn thiết kế, chứ không hẳn là tốt hơn hay tệ hơn
  • Tôi thật sự ghét trào lưu viết lách kiểu này, nhưng lại đồng ý với ý chính mà bài muốn nói
    Câu “không có node_modules to bằng chiếc Volkswagen” là đúng, nhưng thay vì node_modules cục bộ của dự án thì chỉ là bộ nhớ đệm gói toàn cục trong ~/go

    • Hơn nữa nó còn làm bẩn thư mục home. Thậm chí còn không có dấu chấm ở đầu. Tôi không hiểu sao chuyện này lại được chấp nhận
    • Trước khi chê kích thước cây phụ thuộc của hệ sinh thái ngôn ngữ khác, tôi luôn muốn bảo họ chạy wc -l go.sum trước đã
  • Vừa mở trang ra thấy ngay “Hey, dipshit.” nên tôi đóng luôn

  • Nó mắc cùng vấn đề với hầu hết các bài ca ngợi ngôn ngữ lập trình khác. Tập trung vào việc ngôn ngữ trước đây tệ ra sao nhiều hơn là ngôn ngữ hiện tại tuyệt thế nào
    Có vẻ tác giả đã khổ sở nặng với Ruby và TypeScript, có thể cả Python nữa, và Go đã giải quyết chuyện đó cho họ. Nhưng tôi không dùng Ruby hay TypeScript nên bài này không tạo được nhiều liên hệ với tôi
    Cảm giác như tôi đã đọc hàng chục biến thể của kiểu này suốt nhiều năm rồi. Haskell vì có kiểu tĩnh còn Python và JavaScript thì không. Rust vì có thể triển khai thành một binary đơn còn Perl và Erlang thì không. Elixir vì có đồng thời đúng nghĩa và channels còn Ruby và Tcl thì không
    Tôi mừng vì tác giả đã tìm được ngôn ngữ phù hợp với mình, nhưng tôi sẽ không làm theo lời khuyên đó

    • Có vẻ ở đây có kha khá người nghĩ rằng cần phải bán Go cho độc giả Lobsters. Với một số người, điều đó thậm chí có thể phản tác dụng
  • Giá trị zero của Go lúc nào cũng khiến tôi thấy là khuyết điểm. Tôi nghĩ tốt hơn là bắt người dùng phải chỉ rõ giá trị mặc định. Ngoài chuyện nó không phải OCaml ra thì đây vẫn là một ngôn ngữ khá tốt

    • Tôi thích giá trị zero và thấy nó khá thông minh. Nhưng tính năng đặt giá trị mặc định thì thật sự đáng tiếc. Ví dụ, rất khó marshal một đối tượng JSON mà trong đó một bool bị thiếu lại phải mang giá trị true
  • Trải nghiệm triển khai và biên dịch thì tuyệt vời, nhưng tôi thật sự ghét viết chính ngôn ngữ này. Mỗi lần dùng đều là trải nghiệm tệ. Có ngôn ngữ nào khác cũng có trải nghiệm triển khai tốt mà không bị gò bó như Go không?
    Có phải tôi đang bỏ lỡ điều gì ở Go không?
    Gần đây tôi triển khai một ứng dụng Rails nhỏ và phải cấu hình quá nhiều, nên đúng là càng thấy trân trọng ưu điểm của Go

    • Gần đây tôi bắt đầu biên dịch các dự án Rust sang x86_64-unknown-linux-musl. Làm vậy sẽ ra một binary tĩnh chạy thẳng trên mọi máy Linux 64-bit
      Sau đó tôi chuyển nó bằng scp rồi chạy
      Vẫn còn vấn đề phải cấp cổng và khởi động thủ công, nhưng tôi định giải quyết bằng một chút ma thuật systemd
    • Về trải nghiệm triển khai, công ty tôi gần đây thành công bất ngờ với nix bundler. Để có bối cảnh thì chúng tôi đang làm một ứng dụng GUI Qt6
      Dùng bundler có thể tạo ra một tệp thực thi đơn, rồi thả nó lên một máy Linux chạy distro khác, thậm chí không cài Qt, mà người dùng chỉ cần chạy tệp đó là toàn bộ GUI hoạt động
      Tuy vậy có một lưu ý là driver OpenGL vẫn có vấn đề. Vẫn làm được, nhưng sẽ phức tạp hơn “copy rồi chạy”
  • Vấn đề lớn nhất là trong khi Go tự nhận được thiết kế cho đồng thời, nó lại tích hợp sẵn con trỏ nguyên thủy có thể rất dễ bị chia sẻ nhầm

  • Bản thân sự nhàm chán thì không sao, nhưng tôi thấy Go lại thất bại một cách khác thường trong việc thực sự trở thành một ngôn ngữ nhàm chán
    Người ta nói “không có decorator”, nhưng lại có struct tags và reflection. Rất khó biết chúng tương tác với nhau thế nào nếu chưa chạy thử
    Interface theo kiểu cấu trúc và reflection là nguồn gốc đáng sợ của việc hành vi thay đổi từ những nơi rất xa. Chỉ cần thêm nhầm một method vào struct là hành vi thư viện có thể đổi hoàn toàn
    Xét từ góc độ tài liệu, chuyện này cũng lạ. Có lý do gì để không muốn thể hiện rõ rằng một kiểu được thiết kế để thỏa mãn interface nào không?
    Tôi không hiểu vì sao goroutine không được gọi đơn giản là thread
    Vì sao channel lại phải là tính năng ngôn ngữ? Tôi đoán vì phải chậm mất 10 năm mới thừa nhận generics hữu ích cho nhiều thứ hơn chỉ khoảng ba loại kiểu

    • Goroutine không phải thread, mà là một lớp trừu tượng nhẹ hơn chạy trên một pool thread. Vì vậy có thể dễ dàng tạo ra hàng nghìn goroutine
      Tôi nghĩ channel là một phần của runtime vì scheduler của goroutine cần biết về channel để đánh thức goroutine bên nhận dễ hơn khi channel không còn rỗng. Có lẽ làm vậy sẽ dễ hơn
    • Vì goroutine là green thread có kèm thêm nhiều công cụ hỗ trợ