این جزوه به بررسی جامع بهترین شیوهها و تکنیکهای پیشرفته در Go میپردازد. Go به دلیل سادگی و کارایی، برای ساخت سیستمهای مقیاسپذیر و قابل نگهداری بسیار مناسب است. تمام موضوعات درخواستی با جزئیات کامل، مثالهای عملی، و نکات پیشرفته شرح داده شدهاند.
طراحی کد با کیفیت بالا شامل استفاده از اصول طراحی (مثل SOLID) و معماری تمیز (Clean Architecture) است که کد را قابل نگهداری، تستپذیر، و مقیاسپذیر میکند.
SOLID شامل پنج اصل طراحی شیءگرا است که در Go نیز قابل اعمال هستند، اگرچه Go به طور کامل شیءگرا نیست.
هر نوع یا تابع باید تنها یک دلیل برای تغییر داشته باشد.
// بد: یک struct با چندین مسئولیت
type UserManager struct {
// مدیریت کاربر
// ارسال ایمیل
// لاگگیری
}
// خوب: جداسازی مسئولیتها
type UserService struct {
db *sql.DB
}
func (s *UserService) CreateUser(name string) error {
// فقط مدیریت کاربر
_, err := s.db.Exec("INSERT INTO users (name) VALUES ($1)", name)
return err
}
type EmailService struct{}
func (e *EmailService) SendEmail(to, msg string) error {
// فقط ارسال ایمیل
return nil
}
کدها باید برای گسترش باز و برای تغییر بسته باشند.
type Logger interface {
Log(message string)
}
type ConsoleLogger struct{}
func (c ConsoleLogger) Log(message string) {
fmt.Println("Console:", message)
}
type FileLogger struct{}
func (f FileLogger) Log(message string) {
// نوشتن در فایل
}
func ProcessData(data string, logger Logger) {
logger.Log(data) // قابل گسترش بدون تغییر ProcessData
}
انواع مشتقشده باید بدون تغییر رفتار قابل جایگزینی با نوع پایه باشند.
type Writer interface {
Write(data string) error
}
type FileWriter struct{}
func (f FileWriter) Write(data string) error {
// نوشتن در فایل
return nil
}
type BufferWriter struct{}
func (b BufferWriter) Write(data string) error {
// نوشتن در بافر
return nil
}
func SaveData(w Writer, data string) error {
return w.Write(data) // هر Writer بدون مشکل کار میکند
}
کلاینتها نباید مجبور به پیادهسازی رابطهای غیرضروری شوند.
// بد: رابط بزرگ
type BigInterface interface {
Read()
Write()
Close()
}
// خوب: رابطهای کوچک
type Reader interface {
Read()
}
type Writer interface {
Write()
}
type FileReader struct{}
func (f FileReader) Read() {
// فقط خواندن
}
ماژولهای سطح بالا نباید به ماژولهای سطح پایین وابسته باشند؛ هر دو باید به abstractions وابسته باشند.
type Repository interface {
Save(data string) error
}
type SQLRepository struct{}
func (s SQLRepository) Save(data string) error {
// ذخیره در دیتابیس
return nil
}
type Service struct {
repo Repository
}
func NewService(repo Repository) *Service {
return &Service{repo: repo}
}
func (s *Service) Process(data string) error {
return s.repo.Save(data)
}
معماری تمیز کد را به لایههای مستقل تقسیم میکند:
ساختار پروژه:
/user-service
├── /entities
│ └── user.go
├── /usecases
│ └── user.go
├── /adapters
│ └── http.go
├── /drivers
│ └── postgres.go
├── main.go
package entities
type User struct {
ID int
Name string
}
package usecases
type UserRepository interface {
Save(user entities.User) error
Find(id int) (entities.User, error)
}
type UserService struct {
repo UserRepository
}
func NewUserService(repo UserRepository) *UserService {
return &UserService{repo: repo}
}
func (s *UserService) CreateUser(name string) (entities.User, error) {
user := entities.User{ID: 1, Name: name}
return user, s.repo.Save(user)
}
package drivers
import (
"database/sql"
"github.com/username/user-service/entities"
)
type PostgresRepo struct {
db *sql.DB
}
func (r PostgresRepo) Save(user entities.User) error {
_, err := r.db.Exec("INSERT INTO users (id, name) VALUES ($1, $2)", user.ID, user.Name)
return err
}
func (r PostgresRepo) Find(id int) (entities.User, error) {
var user entities.User
err := r.db.QueryRow("SELECT id, name FROM users WHERE id = $1", id).Scan(&user.ID, &user.Name)
return user, err
}
package adapters
import (
"encoding/json"
"net/http"
"github.com/username/user-service/usecases"
)
type HTTPHandler struct {
service *usecases.UserService
}
func NewHTTPHandler(service *usecases.UserService) *HTTPHandler {
return &HTTPHandler{service: service}
}
func (h *HTTPHandler) CreateUser(w http.ResponseWriter, r *http.Request) {
var input struct {
Name string `json:"name"`
}
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
http.Error(w, "Invalid input", http.StatusBadRequest)
return
}
user, err := h.service.CreateUser(input.Name)
if err != nil {
http.Error(w, "Failed to create user", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(user)
}
package main
import (
"database/sql"
"log"
"net/http"
"github.com/username/user-service/adapters"
"github.com/username/user-service/drivers"
"github.com/username/user-service/usecases"
_ "github.com/lib/pq"
)
func main() {
db, err := sql.Open("postgres", "user=postgres password=secret dbname=mydb sslmode=disable")
if err != nil {
log.Fatal(err)
}
repo := drivers.PostgresRepo{db: db}
service := usecases.NewUserService(&repo)
handler := adapters.NewHTTPHandler(service)
http.HandleFunc("/users", handler.CreateUser)
log.Fatal(http.ListenAndServe(":8080", nil))
}
func TestUserService(t *testing.T) {
repo := &MockRepo{}
service := usecases.NewUserService(repo)
user, err := service.CreateUser("Ali")
if err != nil || user.Name != "Ali" {
t.Errorf("Expected user Ali, got %v", user)
}
}
مدیریت خطا در Go با استفاده از نوع error
انجام میشود. پکیج pkg/errors
ابزارهای پیشرفتهای برای wrapping و تحلیل خطاها ارائه میدهد.
go get github.com/pkg/errors
Wrapping خطاها اطلاعات زمینهای (context) اضافه میکند:
package main
import (
"database/sql"
"fmt"
"github.com/pkg/errors"
)
func queryUser(db *sql.DB, id int) error {
var name string
err := db.QueryRow("SELECT name FROM users WHERE id = $1", id).Scan(&name)
if err == sql.ErrNoRows {
return errors.Wrapf(err, "user with id %d not found", id)
}
if err != nil {
return errors Wrap(err, "failed to query user")
}
return nil
}
func main() {
db, _ := sql.Open("postgres", "user=postgres password=secret dbname=mydb sslmode=disable")
err := queryUser(db, 999)
if err != nil {
fmt.Printf("Error: %+v\n", err) // چاپ stack trace
}
}
if errors.Is(err, sql.ErrNoRows) {
fmt.Println("No rows found")
}
var dbErr *sql.DBError
if errors.As(err, &dbErr) {
fmt.Println("Database error:", dbErr)
}
type NotFoundError struct {
Resource string
ID int
}
func (e *NotFoundError) Error() string {
return fmt.Sprintf("%s with ID %d not found", e.Resource, e.ID)
}
func findUser(id int) error {
return &NotFoundError{Resource: "user", ID: id}
}
func main() {
err := findUser(999)
if err != nil {
if e, ok := err.(*NotFoundError); ok {
fmt.Printf("Not found: %s, ID: %d\n", e.Resource, e.ID)
}
}
}
errors.WithStack
برای افزودن stack trace استفاده کنید:
return errors.WithStack(fmt.Errorf("something went wrong"))
var ErrNotFound = errors.New("resource not found")
defer func() {
if r := recover(); r != nil {
log.Printf("Recovered: %v", r)
}
}()
الگوهای طراحی راهحلهای استاندارد برای مسائل رایج هستند. در Go، به دلیل سادگی زبان، برخی الگوها سادهتر پیادهسازی میشوند.
Factory برای ایجاد اشیاء بدون مشخص کردن نوع دقیق استفاده میشود.
type Database interface {
Query() string
}
type PostgresDB struct{}
func (p PostgresDB) Query() string {
return "Postgres query"
}
type MongoDB struct{}
func (m MongoDB) Query() string {
return "MongoDB query"
}
func NewDatabase(dbType string) (Database, error) {
switch dbType {
case "postgres":
return PostgresDB{}, nil
case "mongo":
return MongoDB{}, nil
default:
return nil, fmt.Errorf("unsupported db: %s", dbType)
}
}
func main() {
db, err := NewDatabase("postgres")
if err != nil {
log.Fatal(err)
}
fmt.Println(db.Query()) // Postgres query
}
Singleton تضمین میکند که تنها یک نمونه از یک نوع وجود داشته باشد.
package main
import (
"fmt"
"sync"
)
type Config struct {
setting string
}
var (
instance *Config
once sync.Once
)
func GetConfig() *Config {
once.Do(func() {
instance = &Config{setting: "default"}
})
return instance
}
func main() {
cfg1 := GetConfig()
cfg2 := GetConfig()
fmt.Println(cfg1 == cfg2) // true
}
Observer به اشیاء اجازه میدهد تا از تغییرات یکدیگر مطلع شوند.
package main
import "fmt"
type Subscriber interface {
Update(message string)
}
type Publisher struct {
subscribers []Subscriber
}
func (p *Publisher) Subscribe(s Subscriber) {
p.subscribers = append(p.subscribers, s)
}
func (p *Publisher) Notify(message string) {
for _, s := range p.subscribers {
s.Update(message)
}
}
type EmailSubscriber struct {
email string
}
func (e EmailSubscriber) Update(message string) {
fmt.Printf("Email to %s: %s\n", e.email, message)
}
func main() {
pub := Publisher{}
sub1 := EmailSubscriber{email: "user1@example.com"}
sub2 := EmailSubscriber{email: "user2@example.com"}
pub.Subscribe(sub1)
pub.Subscribe(sub2)
pub.Notify("New update!") // Email to user1@example.com: New update!
}
messages := make(chan string)
go func() {
for msg := range messages {
fmt.Println("Received:", msg)
}
}()
کارایی (Performance) به سرعت اجرای برنامه و مقیاسپذیری (Scalability) به توانایی مدیریت بار زیاد اشاره دارد.
s := make([]int, 0, 1000)
sync.Pool
برای اشیاء موقت استفاده کنید:
var pool = sync.Pool{New: func() interface{} { return &bytes.Buffer{} }}
jobs := make(chan int, 100)
for w := 0; w < 4; w++ {
go worker(jobs)
}
go tool pprof http://localhost:6060/debug/pprof/profile
import "github.com/go-redis/redis/v8"
client := redis.NewClient(&redis.Options{Addr: "localhost:6379"})
sql.DB
:
db, _ := sql.Open("postgres", connStr)
db.SetMaxOpenConns(100)
groupcache
یا Redis برای کش کردن دادهها:
import "github.com/golang/groupcache"
cache := groupcache.NewGroup("users", 64<<20, groupcache.GetterFunc(
func(ctx context.Context, key string, dest groupcache.Sink) error {
// دریافت داده
return nil
},
))
import "golang.org/x/time/rate"
limiter := rate.NewLimiter(10, 1) // 10 درخواست در ثانیه
server := &http.Server{Addr: ":8080"}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
server.Shutdown(ctx)
import "github.com/prometheus/client_golang/prometheus/promhttp"
http.Handle("/metrics", promhttp.Handler())
مستندسازی و نگهداری کد برای درک، استفاده، و توسعه آینده ضروری است. Go با ابزارهایی مثل godoc
مستندسازی را ساده میکند.
// Package user manages user-related operations.
package user
// UserService provides methods to manage users.
type UserService struct{}
// CreateUser creates a new user with the given name.
// Returns an error if the user already exists.
func (s *UserService) CreateUser(name string) error {
return nil
}
go doc github.com/username/user
func ExampleUserService_CreateUser() {
s := &UserService{}
err := s.CreateUser("Ali")
fmt.Println(err)
// Output: <nil>
}
# User Service
A Go service for managing users.
## Installation
```bash
go get github.com/username/user
s := user.NewUserService()
s.CreateUser("Ali")
```
go get github.com/swaggo/swag/cmd/swag
swag init
pkg.go.dev
برای انتشار مستندات استفاده کنید.git tag v1.0.0
git push origin v1.0.0
func TestCreateUser(t *testing.T) {
// این تست به عنوان مستندات نیز عمل میکند
}
مشارکت در انجمن Go و استفاده از منابع یادگیری برای بهبود مهارتها ضروری است.
good first issue
.// اضافه کردن پرچم جدید
cmd.Flags().StringVar(&output, "output", "", "Output file")
https://golang.org/doc/
https://go.dev/tour
https://gobyexample.com
r/golang
https://gophers.slack.com
)go
go test -cover
pprof
برای شناسایی گلوگاهها استفاده کنید.این جزوه تمام جنبههای بهترین شیوهها و نکات پیشرفته در Go را با جزئیات کامل پوشش داد. از طراحی کد با SOLID و معماری تمیز تا مدیریت خطاها، الگوهای طراحی، بهینهسازی، مستندسازی، و مشارکت در انجمن، هر بخش با مثالهای عملی و نکات پیشرفته ارائه شد. برای یادگیری عمیقتر:
https://golang.org/doc/