آشنایی با پکیج‌ها و ماژول‌ها در Go

فهرست مطالب

مقدمه: چرا پکیج‌ها و ماژول‌ها حیاتی هستند؟

در دنیای پیچیده توسعه نرم‌افزار، سازماندهی و مدیریت کد برای حفظ خوانایی، قابلیت نگهداری، و قابلیت استفاده مجدد از اهمیت بالایی برخوردار است. زبان برنامه‌نویسی Go، با فلسفه سادگی و کارایی خود، راهکارهای قدرتمندی را برای این منظور در قالب «پکیج‌ها» و «ماژول‌ها» ارائه می‌دهد. این دو مفهوم، اگرچه مرتبط با یکدیگرند، اما اهداف متمایزی را دنبال می‌کنند و در کنار هم یک سیستم مدیریت کد و وابستگی جامع را تشکیل می‌دهند که ستون فقرات هر پروژه Go را تشکیل می‌دهد.

پکیج‌ها، به عنوان واحدهای بنیادی سازماندهی کد در Go، به ما امکان می‌دهند تا کدهای مرتبط را در گروه‌های منطقی دسته‌بندی کنیم. این دسته‌بندی نه تنها به بهبود خوانایی کد کمک می‌کند، بلکه با تعریف مرزهای مشخص برای قابلیت دید (visibility) شناسه‌ها، اصل کپسوله‌سازی را تقویت کرده و از تداخل نام‌ها جلوگیری می‌نماید. در واقع، هر فایل Go بخشی از یک پکیج است و هر برنامه Go از یک یا چند پکیج تشکیل می‌شود.

از سوی دیگر، ماژول‌ها یک گام فراتر رفته و به مشکل پیچیده‌تر مدیریت وابستگی‌ها در پروژه‌ها می‌پردازند. پیش از معرفی ماژول‌ها، Go از رویکرد `GOPATH` برای مدیریت کد استفاده می‌کرد که محدودیت‌هایی برای نسخه‌بندی و جداسازی وابستگی‌های پروژه‌ها داشت. Go Modules، که از نسخه 1.11 به صورت آزمایشی و از 1.16 به صورت کامل به زبان اضافه شد، انقلابی در نحوه مدیریت وابستگی‌ها ایجاد کرد. آن‌ها یک سیستم یکپارچه برای تعریف، دانلود، و نسخه‌بندی وابستگی‌های خارجی را فراهم می‌آورند که پایداری و تکرارپذیری ساخت پروژه‌ها را تضمین می‌کند. این سیستم به توسعه‌دهندگان اجازه می‌دهد تا با اطمینان خاطر از کتابخانه‌های شخص ثالث استفاده کرده و از سازگاری نسخه‌ها در محیط‌های مختلف توسعه و استقرار اطمینان حاصل کنند.

درک عمیق نحوه عملکرد پکیج‌ها و ماژول‌ها، کلید نوشتن کدهای Go کارآمد، قابل نگهداری، و مقیاس‌پذیر است. این مقاله به بررسی جامع و تخصصی این دو مفهوم حیاتی می‌پردازد، از ساختار و اصول طراحی پکیج‌ها گرفته تا نحوه مدیریت پیچیده وابستگی‌ها با استفاده از ماژول‌ها. هدف ما توانمندسازی شما برای ساخت پروژه‌های Go بهینه و قابل اطمینان است.

پکیج‌ها در Go: سنگ بنای سازماندهی کد

پکیج‌ها در Go، کوچکترین واحدهای قابل توزیع و استفاده مجدد از کد هستند. آن‌ها به توسعه‌دهندگان اجازه می‌دهند تا کدهای مرتبط را در یک فضای نام (namespace) مشترک گروه‌بندی کنند. این رویکرد نه تنها به سازماندهی منطقی پروژه کمک می‌کند، بلکه از تداخل نام‌ها جلوگیری کرده و قابلیت کپسوله‌سازی را فراهم می‌آورد.

تعریف پکیج و هدف آن

هر فایل سورس کد Go باید با یک اعلان `package` در بالای فایل آغاز شود، که نام پکیجی را که آن فایل به آن تعلق دارد، مشخص می‌کند. به عنوان مثال، `package main` یا `package utils`. هدف اصلی پکیج‌ها عبارت است از:

  • سازماندهی منطقی کد: گروه‌بندی توابع، ساختارها، و متغیرهای مرتبط با یکدیگر.
  • قابلیت استفاده مجدد: امکان استفاده از کد نوشته شده در یک پکیج در سایر پکیج‌ها و پروژه‌ها.
  • کپسوله‌سازی: کنترل قابلیت دسترسی به شناسه‌ها (متغیرها، توابع، ساختارها، اینترفیس‌ها) در خارج از پکیج.
  • جلوگیری از تداخل نام‌ها: هر پکیج فضای نام مخصوص به خود را دارد، بنابراین می‌توان از نام‌های مشابه در پکیج‌های مختلف استفاده کرد بدون اینکه تداخلی ایجاد شود.

ساختار دایرکتوری پکیج‌ها

در Go، رابطه مستقیمی بین نام پکیج و ساختار دایرکتوری وجود دارد. به طور کلی، تمام فایل‌های Go در یک دایرکتوری خاص، باید به یک پکیج واحد تعلق داشته باشند. نام پکیج معمولاً با نام دایرکتوری مطابقت دارد، اگرچه این یک الزام نیست (به جز در مورد پکیج main). به عنوان مثال، اگر شما یک دایرکتوری به نام utils دارید، فایل‌های .go درون آن معمولاً با package utils آغاز می‌شوند.


myproject/
├── main.go            // package main
└── internal/
    └── handlers/
        └── user.go    // package handlers
    └── models/
        └── user.go    // package models
└── pkg/
    └── database/
        └── db.go      // package database

پکیج main و تابع main

پکیج main یک پکیج ویژه در Go است. هر برنامه اجرایی Go باید دقیقاً یک پکیج به نام main داشته باشد. درون این پکیج، باید یک تابع main() نیز وجود داشته باشد که نقطه ورودی اجرای برنامه است. زمانی که شما دستور go run یا go build را برای یک برنامه اجرایی Go اجرا می‌کنید، کامپایلر Go به دنبال پکیج main و تابع main() می‌گردد تا اجرای برنامه را از آنجا آغاز کند.


// main.go
package main

import "fmt"

func main() {
    fmt.Println("Hello from the main package!")
}

وارد کردن (Import) پکیج‌ها

برای استفاده از شناسه‌هایی (توابع، متغیرها، ساختارها و غیره) که در یک پکیج دیگر تعریف شده‌اند، باید آن پکیج را به فایل خود import کنید. دستور import مسیر پکیج را مشخص می‌کند. این مسیر می‌تواند یک پکیج استاندارد Go باشد (مانند "fmt" برای قالب‌بندی ورودی/خروجی)، یا یک پکیج خارجی که با ماژول‌ها مدیریت می‌شود، یا یک پکیج محلی در پروژه شما.


package main

import (
    "fmt"        // پکیج استاندارد Go
    "log"        // پکیج استاندارد Go
    "myproject/pkg/utils" // پکیج محلی/داخلی در پروژه
)

func main() {
    fmt.Println("Message from main.")
    log.Println("Logging from main.")
    result := utils.Add(5, 3)
    fmt.Printf("Result of addition: %d\n", result)
}

// myproject/pkg/utils/math.go
package utils

// Add adds two integers and returns their sum.
func Add(a, b int) int {
    return a + b
}

توجه داشته باشید که در مثال بالا، مسیر "myproject/pkg/utils" یک مسیر ماژول است که در ادامه توضیح داده خواهد شد. به طور پیش‌فرض، Go از نام آخرین جزء مسیر وارداتی به عنوان نام پکیج برای دسترسی به شناسه‌های آن استفاده می‌کند (مثلاً utils.Add). می‌توان از نام‌های مستعار (aliases) برای پکیج‌های وارد شده نیز استفاده کرد، به خصوص زمانی که دو پکیج وارد شده نام مشابهی دارند:


import (
    f "fmt"
    myutils "myproject/pkg/utils"
)

func main() {
    f.Println("Using alias for fmt.")
    sum := myutils.Add(10, 20)
    f.Printf("Sum using alias for myutils: %d\n", sum)
}

همچنین می‌توان پکیج‌ها را بدون نامگذاری (blank import) وارد کرد که عمدتاً برای فعال کردن اثرات جانبی پکیج (مانند ثبت درایور دیتابیس) استفاده می‌شود:


import (
    _ "github.com/go-sql-driver/mysql" // فقط برای اثرات جانبی
)

Visibility (قابلیت دید) شناسه‌ها: Exported vs. Unexported

یکی از مهمترین ویژگی‌های پکیج‌ها در Go، مفهوم قابلیت دید (Visibility) است که تعیین می‌کند آیا یک شناسه (مانند متغیر، تابع، ساختار، اینترفیس یا ثابت) از خارج از پکیج قابل دسترسی است یا خیر. این مفهوم با استفاده از حروف اول بزرگ و کوچک مشخص می‌شود:

  • شناسه‌های Exported (صادراتی): اگر نام یک شناسه با یک حرف بزرگ (ASCII) آغاز شود، آن شناسه Exported است و از هر پکیج دیگری که آن پکیج را Import کرده باشد، قابل دسترسی است.
  • شناسه‌های Unexported (غیر صادراتی/داخلی): اگر نام یک شناسه با یک حرف کوچک (ASCII) آغاز شود، آن شناسه Unexported است و فقط از داخل همان پکیجی که در آن تعریف شده، قابل دسترسی است. این شناسه‌ها به نوعی پیاده‌سازی داخلی پکیج محسوب می‌شوند.

این مکانیزم ساده و قدرتمند، اصل کپسوله‌سازی را در Go پیاده‌سازی می‌کند و به توسعه‌دهندگان اجازه می‌دهد تا جزئیات پیاده‌سازی داخلی را از کاربران پکیج پنهان کنند و فقط API عمومی (شناسه‌های Exported) را در معرض دید قرار دهند.


// mypackage/some_code.go
package mypackage

import "fmt"

// ExportedFunction is an exported function. It can be called from other packages.
func ExportedFunction() {
    fmt.Println("This is an exported function.")
    unexportedFunction() // Can call unexported functions within the same package.
    fmt.Printf("Accessing exported variable: %s\n", ExportedVariable)
}

// unexportedFunction is an unexported function. It can only be called from within mypackage.
func unexportedFunction() {
    fmt.Println("This is an unexported function.")
}

// ExportedVariable is an exported variable.
var ExportedVariable = "I am visible outside."

// unexportedVariable is an unexported variable.
var unexportedVariable = "I am visible only inside."

// ExportedStruct is an exported struct.
type ExportedStruct struct {
    ExportedField   string // Exported field
    unexportedField string // Unexported field
}

// NewExportedStruct is an exported constructor-like function.
func NewExportedStruct(ef, uf string) *ExportedStruct {
    return &ExportedStruct{
        ExportedField:   ef,
        unexportedField: uf,
    }
}

و در پکیج main:


// main.go
package main

import (
    "fmt"
    "myproject/mypackage" // Assuming mypackage is within your module path
)

func main() {
    mypackage.ExportedFunction() // OK
    // mypackage.unexportedFunction() // ERROR: unexportedFunction not exported
    fmt.Println(mypackage.ExportedVariable) // OK
    // fmt.Println(mypackage.unexportedVariable) // ERROR: unexportedVariable not exported

    myStruct := mypackage.NewExportedStruct("Hello", "World")
    fmt.Println(myStruct.ExportedField) // OK
    // fmt.Println(myStruct.unexportedField) // ERROR: unexportedField not exported
}

پکیج‌های استاندارد Go

Go با یک کتابخانه استاندارد غنی و قدرتمند همراه است که طیف وسیعی از قابلیت‌ها را ارائه می‌دهد، از عملیات ورودی/خروجی و شبکه گرفته تا رمزنگاری و کار با داده‌ها. این پکیج‌ها بخش جدایی‌ناپذیری از اکوسیستم Go هستند و به دلیل پایداری و عملکرد بالا، به شدت توصیه می‌شوند.

برخی از پکیج‌های استاندارد پرکاربرد عبارتند از:

  • fmt: برای قالب‌بندی و چاپ ورودی/خروجی.
  • io: برای عملیات ورودی/خروجی پایه.
  • net/http: برای ساخت سرورهای وب و کلاینت‌های HTTP.
  • os: برای تعامل با سیستم عامل (فایل‌ها، دایرکتوری‌ها، متغیرهای محیطی).
  • strconv: برای تبدیل بین رشته‌ها و انواع عددی.
  • encoding/json: برای کدگذاری و کدگشایی داده‌های JSON.
  • time: برای کار با زمان و تاریخ.
  • sync: برای همگام‌سازی در برنامه‌های همزمان (concurrency).

توابع init: آماده‌سازی پکیج

علاوه بر تابع main، Go مفهوم توابع init() را نیز دارد. هر پکیج می‌تواند یک یا چند تابع init() داشته باشد. این توابع به طور خودکار قبل از اجرای تابع main() و قبل از فراخوانی هر تابع دیگری در پکیج، فراخوانی می‌شوند.

توابع init() برای اهداف زیر استفاده می‌شوند:

  • مقداردهی اولیه پیچیده برای متغیرهای پکیج.
  • بررسی اعتبار یا تنظیمات.
  • ثبت درایورها یا پلاگین‌ها (مانند درایورهای دیتابیس).

ترتیب اجرای توابع init() به این صورت است: ابتدا تمام توابع init() در پکیج‌های وارد شده (Imported) به ترتیب سلسله مراتبی (از عمیق‌ترین وابستگی‌ها به سمت بالا) اجرا می‌شوند، و سپس توابع init() پکیج جاری اجرا می‌شوند. اگر یک پکیج چندین تابع init() داشته باشد، آن‌ها به ترتیب تعریف شده در فایل‌ها (بر اساس نام فایل و سپس ترتیب تعریف درون فایل) اجرا می‌شوند.


// mypackage/config.go
package mypackage

import "fmt"

var ConfigValue string

func init() {
    fmt.Println("mypackage: init function 1 called")
    ConfigValue = "Initial Configuration"
}

func init() {
    fmt.Println("mypackage: init function 2 called")
    // ConfigValue += " - Updated" // Can modify ConfigValue again
}

// main.go
package main

import (
    "fmt"
    "myproject/mypackage"
)

func init() {
    fmt.Println("main: init function called")
}

func main() {
    fmt.Println("main: main function called")
    fmt.Printf("ConfigValue from mypackage: %s\n", mypackage.ConfigValue)
}

خروجی این کد نشان می‌دهد که توابع init قبل از main اجرا می‌شوند:


mypackage: init function 1 called
mypackage: init function 2 called
main: init function called
main: main function called
ConfigValue from mypackage: Initial Configuration

ماژول‌ها در Go: مدیریت وابستگی‌ها و نسخه‌بندی

اگرچه پکیج‌ها روشی عالی برای سازماندهی کد در یک پروژه واحد هستند، اما در مدیریت وابستگی‌های خارجی و نسخه‌بندی پروژه‌ها در مقیاس بزرگ، محدودیت‌هایی دارند. اینجاست که ماژول‌ها وارد عمل می‌شوند. Go Modules، که از Go 1.11 به بعد به طور فزاینده‌ای پذیرفته شدند، راه حل رسمی و توصیه‌شده Go برای مدیریت وابستگی‌ها هستند.

نیاز به ماژول‌ها: چرا و چگونه ما به Go Modules رسیدیم؟

قبل از Go Modules، سیستم مدیریت وابستگی Go بر پایه `GOPATH` بود. `GOPATH` یک متغیر محیطی بود که به Go می‌گفت کجا کدهای سورس، کتابخانه‌های کامپایل شده، و فایل‌های اجرایی را پیدا کند. تمام پروژه‌ها و وابستگی‌های آن‌ها در یک ساختار دایرکتوری مشترک زیر `GOPATH` قرار می‌گرفتند. این رویکرد دارای معایب قابل توجهی بود:

  • عدم پشتیبانی از نسخه‌بندی: `GOPATH` هیچ مکانیزمی برای مدیریت نسخه‌های مختلف یک کتابخانه ارائه نمی‌داد. اگر دو پروژه به نسخه‌های متفاوتی از یک کتابخانه نیاز داشتند، مشکلاتی پیش می‌آمد.
  • وابستگی به یک دایرکتوری مرکزی: تمام پروژه‌ها و وابستگی‌هایشان در `GOPATH` زندگی می‌کردند، که مدیریت پروژه‌ها را در خارج از آن ساختار دشوار می‌کرد.
  • عدم تکرارپذیری ساخت: بدون نسخه‌بندی مشخص، تضمین اینکه ساخت یک پروژه در زمان‌های مختلف یا در محیط‌های مختلف نتیجه یکسانی داشته باشد، دشوار بود.

Go Modules این مشکلات را با معرفی یک سیستم ماژولار و نسخه‌بندی‌شده حل کردند. یک ماژول، مجموعه‌ای از پکیج‌های مرتبط است که به عنوان یک واحد منتشر و نسخه‌بندی می‌شوند. هر ماژول مسیر ریشه خاص خود را دارد و وابستگی‌های خود را به صورت صریح در یک فایل go.mod تعریف می‌کند.

مفهوم ماژول و فایل go.mod

یک ماژول در Go، یک مجموعه از پکیج‌های Go است که به عنوان یک واحد با یک نام ماژول خاص و یک نسخه مشخص، منتشر و مدیریت می‌شوند. ریشه یک ماژول توسط فایل go.mod مشخص می‌شود. فایل go.mod قلب سیستم Go Modules است و اطلاعات حیاتی زیر را در خود نگه می‌دارد:

  • module <module_path>: مسیر ماژول که یک شناسه منحصر به فرد برای ماژول است و معمولاً شامل مسیر مخزن Git (مثلاً github.com/your_username/your_repo) است. این نام، همان پیشوندی است که برای وارد کردن پکیج‌های داخل این ماژول استفاده می‌شود.
  • go <go_version>: نسخه Go مورد نیاز برای کامپایل این ماژول.
  • require <dependency> <version>: لیست وابستگی‌های خارجی که ماژول به آن‌ها نیاز دارد، به همراه نسخه‌های مورد نظر.
  • exclude <dependency> <version>: برای جلوگیری از استفاده از نسخه‌های خاصی از یک ماژول.
  • replace <old_path> => <new_path>: برای جایگزینی یک وابستگی با یک نسخه محلی یا یک مسیر دیگر (معمولاً برای توسعه محلی یا فورک‌ها).

یک فایل go.mod ساده می‌تواند به شکل زیر باشد:


module example.com/myproject

go 1.20

require (
    github.com/spf13/cobra v1.7.0
    gopkg.in/yaml.v2 v2.4.0
)

شروع به کار با Go Modules: go mod init

برای شروع استفاده از Go Modules در یک پروژه جدید یا موجود، به سادگی به دایرکتوری ریشه پروژه خود رفته و دستور زیر را اجرا کنید:


go mod init <module_path>

<module_path> نام ماژول شما خواهد بود که به عنوان پیشوند برای وارد کردن پکیج‌های داخلی پروژه شما استفاده می‌شود. به عنوان مثال، اگر پروژه شما در github.com/myuser/myapp قرار دارد، شما معمولاً از go mod init github.com/myuser/myapp استفاده می‌کنید.

این دستور فایل go.mod را در دایرکتوری جاری ایجاد می‌کند. اکنون پروژه شما یک ماژول است و Go شروع به استفاده از مکانیزم ماژول برای مدیریت وابستگی‌ها خواهد کرد.

اضافه کردن، به‌روزرسانی و حذف وابستگی‌ها: go get

دستور go get ابزار اصلی برای مدیریت وابستگی‌ها در یک ماژول است:

  • اضافه کردن یک وابستگی جدید:
    go get github.com/some/package

    این دستور جدیدترین نسخه پایدار (latest stable release) پکیج را دانلود کرده و به فایل go.mod اضافه می‌کند.

    برای اضافه کردن یک نسخه خاص (مثلاً یک تگ Git یا یک کامیت هش):

    go get github.com/some/package@v1.2.3
    go get github.com/some/package@master
  • به‌روزرسانی یک وابستگی:
    go get -u github.com/some/package

    این دستور وابستگی را به جدیدترین نسخه Minor یا Patch به‌روزرسانی می‌کند.

    go get -u=patch github.com/some/package

    برای به‌روزرسانی به جدیدترین نسخه Patch فقط.

    go get github.com/some/package@latest

    برای به‌روزرسانی به جدیدترین نسخه موجود (شامل نسخه‌های Major جدید).

  • حذف یک وابستگی:

    برای حذف یک وابستگی، ابتدا تمام importهای مربوط به آن را از کد خود پاک کنید. سپس دستور go mod tidy را اجرا کنید. این دستور به طور خودکار وابستگی‌های استفاده نشده را از go.mod حذف می‌کند.

دستورات کلیدی مدیریت ماژول: go mod tidy، go mod download، go mod verify، go mod graph

علاوه بر go get، چندین دستور go mod دیگر برای مدیریت ماژول‌ها ضروری هستند:

  • go mod tidy: این دستور فایل‌های go.mod و go.sum را مرتب و به‌روزرسانی می‌کند. وابستگی‌هایی که در کد شما استفاده می‌شوند اما در go.mod نیستند را اضافه می‌کند و وابستگی‌هایی که در go.mod هستند اما در کد استفاده نمی‌شوند را حذف می‌کند. این دستور را همیشه پس از تغییر وابستگی‌ها یا کد خود اجرا کنید تا اطمینان حاصل شود که فایل‌های ماژول شما در وضعیت صحیحی قرار دارند.
  • go mod download: این دستور تمام وابستگی‌های ذکر شده در go.mod را دانلود می‌کند و آن‌ها را در کش ماژول Go (معمولاً در $GOPATH/pkg/mod) ذخیره می‌کند. این دستور برای تضمین اینکه تمام وابستگی‌ها به صورت محلی در دسترس هستند، مفید است، به خصوص در محیط‌های CI/CD یا قبل از کامپایل آفلاین.
  • go mod verify: این دستور بررسی می‌کند که ماژول‌های کش شده در go.sum با هش‌های ذخیره شده مطابقت دارند تا از دستکاری یا خرابی داده‌ها اطمینان حاصل شود.
  • go mod graph: این دستور یک نمایش متنی از گراف وابستگی ماژول فعلی را چاپ می‌کند. برای درک وابستگی‌های پیچیده و شناسایی مشکلات مفید است.
  • go mod vendor: (اختیاری) این دستور یک دایرکتوری vendor ایجاد می‌کند و تمام وابستگی‌های ماژول را در آن کپی می‌کند. این کار به شما امکان می‌دهد تا پروژه خود را بدون نیاز به دسترسی به اینترنت یا Go module proxy کامپایل کنید. در محیط‌های حساس امنیتی یا زمانی که وابستگی‌ها باید به طور کامل در کنترل پروژه باشند، مفید است. برای استفاده از آن در حین ساخت، باید از فلگ -mod=vendor استفاده کنید: go build -mod=vendor.

فایل go.sum: بررسی‌های امنیتی

فایل go.sum مکمل فایل go.mod است و شامل هش‌های رمزنگاری شده برای تمام ماژول‌های مستقیم و غیرمستقیم (انتقالی) مورد نیاز پروژه شماست. هر خط در go.sum شامل نام ماژول، نسخه آن، و دو نوع هش (معمولاً یک هش SHA256 برای فایل zip و یک هش SHA256 برای فایل go.mod آن ماژول) است. هدف اصلی go.sum تضمین یکپارچگی و امنیت وابستگی‌هاست. زمانی که Go یک ماژول را دانلود می‌کند، هش آن را با آنچه در go.sum ذکر شده مقایسه می‌کند. اگر هش‌ها مطابقت نداشته باشند، Go یک خطا را برمی‌گرداند که نشان‌دهنده دستکاری احتمالی یا دانلود اشتباه ماژول است.

شما هرگز نباید go.sum را به صورت دستی ویرایش کنید. این فایل به طور خودکار توسط دستوراتی مانند go mod tidy و go get مدیریت می‌شود. حتماً go.mod و go.sum را به کنترل نسخه (مانند Git) اضافه کنید.

نسخه‌بندی معنایی (Semantic Versioning – SemVer) در Go

Go Modules از Semantic Versioning 2.0.0 (SemVer) برای مدیریت نسخه‌ها پیروی می‌کنند. SemVer یک قرارداد نسخه‌بندی سه بخشی (MAJOR.MINOR.PATCH) است که تغییرات در API یک کتابخانه را با معنای خاصی همراه می‌کند:

  • MAJOR (مثلاً v1, v2): نشان‌دهنده تغییرات ناسازگار با عقب (backward incompatible changes) در API عمومی است.
  • MINOR (مثلاً .1, .2): نشان‌دهنده اضافه شدن قابلیت‌های جدید است که سازگار با عقب هستند.
  • PATCH (مثلاً .0.1, .0.2): نشان‌دهنده رفع اشکالات و بهبودهای جزئی است که سازگار با عقب هستند.

در Go، یک قانون مهم وجود دارد: اگر ماژول شما به نسخه Major 2 یا بالاتر (v2, v3 و غیره) ارتقا پیدا می‌کند، مسیر ماژول باید با پسوند /vN خاتمه یابد (مثلاً github.com/some/package/v2). این کار به Go اجازه می‌دهد تا نسخه‌های Major ناسازگار از یک ماژول را در یک پروژه به طور همزمان پشتیبانی کند.

ماژول‌های خصوصی و پراکسی‌ها

در محیط‌های سازمانی، اغلب نیاز است که از ماژول‌های خصوصی (که در مخازن خصوصی مانند GitLab یا GitHub Private Repositories نگهداری می‌شوند) استفاده شود. برای اینکه Go بتواند این ماژول‌ها را پیدا و دانلود کند، باید متغیرهای محیطی خاصی را تنظیم کنید:

  • GOPRIVATE: یک لیست از الگوهای مسیر ماژول است که به Go می‌گوید این ماژول‌ها خصوصی هستند و نباید از پروکسی‌های ماژول عمومی برای دانلود آن‌ها استفاده شود. Go برای این ماژول‌ها مستقیماً به مخزن اصلی مراجعه می‌کند.
    export GOPRIVATE="*.corp.example.com,github.com/myorg/*"
  • GONOPROXY: مشابه GOPRIVATE است، اما به Go می‌گوید که برای این ماژول‌ها از پروکسی استفاده نکند و مستقیماً به مخزن اصلی مراجعه کند. GOPRIVATE به طور ضمنی GONOPROXY را نیز تنظیم می‌کند، بنابراین معمولاً GOPRIVATE کافی است.
  • GONOSUMDB: مشابه GOPRIVATE است، اما به Go می‌گوید که برای این ماژول‌ها از پایگاه داده هش‌های عمومی (SumDB) استفاده نکند و اطلاعات هش را از خود مخزن دریافت کند. GOPRIVATE به طور ضمنی GONOSUMDB را نیز تنظیم می‌کند.

Go Module Proxy: Go از پروکسی‌های ماژول برای ارائه سریع و قابل اطمینان ماژول‌ها استفاده می‌کند. این پروکسی‌ها به عنوان یک کش عمل کرده و ماژول‌ها را از مخازن اصلی دانلود کرده و سپس به توسعه‌دهندگان ارائه می‌دهند. پروکسی عمومی Go (proxy.golang.org) به طور پیش‌فرض استفاده می‌شود. می‌توانید با تنظیم متغیر محیطی GOPROXY از پروکسی‌های دیگر یا زنجیره‌ای از آن‌ها استفاده کنید:

export GOPROXY="https://proxy.example.com,direct"

این تنظیم به Go می‌گوید که ابتدا سعی کند از https://proxy.example.com ماژول‌ها را دریافت کند. اگر پیدا نشد، به طور مستقیم (direct) به مخزن اصلی مراجعه کند.

همزیستی پکیج‌ها و ماژول‌ها: بهترین شیوه‌ها

درک نحوه کار پکیج‌ها و ماژول‌ها به صورت جداگانه یک چیز است، اما مهم‌تر این است که بدانیم چگونه آن‌ها را با هم به کار گیریم تا پروژه‌های Go قوی و قابل نگهداری ایجاد کنیم.

طراحی پکیج‌های موثر

  • تک مسئولیتی (Single Responsibility Principle): هر پکیج باید یک مسئولیت واحد و مشخص داشته باشد. به عنوان مثال، یک پکیج database مسئولیت تعامل با پایگاه داده را بر عهده دارد، نه پردازش منطق کسب‌وکار یا هندل کردن درخواست‌های HTTP.
  • نام‌گذاری واضح: نام پکیج‌ها باید مختصر، دقیق و توصیفی باشند. معمولاً از نام‌های تک کلمه‌ای با حروف کوچک استفاده می‌شود (مانند utils, models, handlers). از نام‌های عمومی و مبهم مانند common یا helper خودداری کنید.
  • کپسوله‌سازی صحیح: از قابلیت دید (Exported vs. Unexported) برای کنترل دسترسی به شناسه‌ها استفاده کنید. تنها توابع و ساختارهایی را Export کنید که بخشی از API عمومی پکیج شما هستند. جزئیات پیاده‌سازی داخلی را با Unexported کردن پنهان کنید.
  • پرهیز از وابستگی‌های چرخشی: پکیج‌ها نباید به صورت چرخشی به یکدیگر وابسته باشند (یعنی P1 به P2 و P2 به P1 وابسته باشد). این وضعیت منجر به مشکلات کامپایل و طراحی ضعیف می‌شود. از ابزارهایی مانند go vet یا تحلیل‌گرهای کد برای شناسایی این وابستگی‌ها استفاده کنید.
  • اندازه مناسب: پکیج‌ها نباید خیلی بزرگ یا خیلی کوچک باشند. پکیج‌های خیلی بزرگ ممکن است وظایف متعددی را انجام دهند و پکیج‌های خیلی کوچک ممکن است به افزایش پیچیدگی غیرضروری منجر شوند. یک تعادل مناسب پیدا کنید.

مدیریت وابستگی‌های ماژول‌ها

  • تعریف دقیق وابستگی‌ها: همیشه نسخه‌های دقیق و ثابت را برای وابستگی‌های خود در go.mod مشخص کنید. این کار تکرارپذیری ساخت را تضمین می‌کند. استفاده از @latest در محیط تولید توصیه نمی‌شود، زیرا ممکن است منجر به دریافت نسخه‌های ناسازگار شود.
  • استفاده منظم از go mod tidy: پس از هر تغییر در Importها یا افزودن/حذف وابستگی‌ها، go mod tidy را اجرا کنید تا go.mod و go.sum در وضعیت صحیحی باشند.
  • کنترل نسخه go.mod و go.sum: این دو فایل را همیشه به سیستم کنترل نسخه خود (مانند Git) اضافه کنید. این کار تضمین می‌کند که هر کسی که پروژه شما را کلون می‌کند، دقیقاً با همان وابستگی‌ها و نسخه‌هایی کار می‌کند که شما کار می‌کنید.
  • مدیریت ماژول‌های خصوصی: اگر در سازمان خود از ماژول‌های خصوصی استفاده می‌کنید، متغیرهای محیطی GOPRIVATE را به درستی تنظیم کنید.
  • پرهیز از وابستگی‌های غیرضروری: فقط کتابخانه‌هایی را Import کنید که واقعاً به آن‌ها نیاز دارید. وابستگی‌های کمتر به معنای زمان کامپایل سریع‌تر، اندازه باینری کوچکتر و سطح حمله امنیتی کمتر است.

ساختار پروژه نمونه با پکیج‌ها و ماژول‌ها

یک ساختار پروژه Go خوب سازماندهی شده می‌تواند به این صورت باشد:


my-go-app/
├── go.mod
├── go.sum
├── main.go                       // پکیج main، نقطه ورودی برنامه
├── internal/                     // کد داخلی پروژه که نباید توسط پروژه‌های خارجی Import شود
│   ├── handlers/                 // منطق هندل کردن درخواست‌های HTTP (پکیج handlers)
│   │   └── user.go
│   ├── services/                 // منطق کسب‌وکار (پکیج services)
│   │   └── auth.go
│   └── database/                 // تعامل با دیتابیس (پکیج database)
│       └── client.go
├── pkg/                          // کد قابل استفاده مجدد که ممکن است توسط پروژه‌های خارجی Import شود
│   ├── utils/                    // توابع عمومی (پکیج utils)
│   │   └── math.go
│   └── errors/                   // تعریف خطاهای سفارشی (پکیج errors)
│       └── errors.go
└── cmd/                          // دایرکتوری برای برنامه‌های اجرایی (اگر پروژه شامل چندین باینری باشد)
    └── myapp/                    // یک باینری (پکیج main)
        └── main.go
    └── cli/                      // یک باینری دیگر (مثلاً ابزار CLI) (پکیج main)
        └── main.go

در این ساختار:

  • main.go: معمولاً برای برنامه‌های ساده و تک باینری استفاده می‌شود.
  • internal/: یک دایرکتوری ویژه در Go است. پکیج‌های داخل internal/ فقط می‌توانند توسط کدهای موجود در همان ماژول Import شوند و برای ماژول‌های خارجی قابل Import نیستند. این برای کپسوله‌سازی منطق داخلی پروژه بسیار مفید است.
  • pkg/: برای کتابخانه‌هایی که قصد دارید به عنوان یک پکیج عمومی از آن‌ها استفاده شود یا توسط پروژه‌های Go دیگر Import شوند، مناسب است.
  • cmd/: برای برنامه‌های اجرایی که ممکن است پروژه شما تولید کند. هر زیردایرکتوری در cmd/ یک نقطه ورودی جداگانه (با پکیج main و تابع main() خودش) است.

پرهیز از وابستگی‌های چرخشی

وابستگی‌های چرخشی (Cyclic Dependencies) بین پکیج‌ها یک مشکل رایج در طراحی نرم‌افزار است که به ویژه در Go، به دلیل نحوه کامپایل کد، می‌تواند منجر به خطاهای کامپایل شود. اگر پکیج A به پکیج B و پکیج B به پکیج A وابسته باشد، Go نمی‌تواند آن‌ها را به درستی کامپایل کند.

برای پرهیز از وابستگی‌های چرخشی:

  • استفاده از اینترفیس‌ها: به جای اینکه پکیج‌ها مستقیماً به پیاده‌سازی‌های یکدیگر وابسته باشند، می‌توانند به اینترفیس‌هایی که در یک پکیج سطح بالاتر یا پکیج مجزا تعریف شده‌اند، وابسته باشند. این کار به جداسازی و کاهش وابستگی‌های مستقیم کمک می‌کند.
  • معکوس کردن وابستگی‌ها (Dependency Inversion): یک اصل از SOLID که به معنای این است که ماژول‌های سطح بالا نباید به ماژول‌های سطح پایین وابسته باشند، بلکه هر دو باید به انتزاعات وابسته باشند.
  • بازآرایی (Refactoring) پکیج‌ها: ممکن است لازم باشد منطق را به پکیج‌های جدیدی منتقل کنید یا پکیج‌های موجود را بازآرایی کنید تا وابستگی‌های چرخشی از بین بروند.

نام‌گذاری پکیج‌ها و ماژول‌ها

نام‌گذاری صحیح در Go اهمیت زیادی دارد. برای پکیج‌ها:

  • مختصر و توصیفی: نام پکیج باید مختصر و دقیقاً بیانگر کاری باشد که پکیج انجام می‌دهد.
  • تک کلمه‌ای با حروف کوچک: به طور کلی، نام پکیج‌ها تک کلمه‌ای و با حروف کوچک هستند (مانند http, json, errors).
  • پرهیز از نام‌های تکراری: از نام‌هایی که ممکن است با پکیج‌های استاندارد یا پکیج‌های خارجی پرکاربرد تداخل داشته باشند، خودداری کنید.

برای ماژول‌ها:

  • مسیر معتبر مخزن: نام ماژول باید یک مسیر URL معتبر باشد که به مخزن کد شما اشاره دارد (مانند github.com/yourorg/yourrepo).
  • پایان با /vN برای نسخه‌های Major بالا: اگر ماژول شما به نسخه Major 2 یا بالاتر می‌رود، مسیر ماژول در go.mod و Import Path باید با /vN خاتمه یابد (مثلاً github.com/yourorg/yourrepo/v2).

Advanced Topics در مدیریت پکیج و ماژول

همانطور که پروژه‌های Go بزرگتر و پیچیده‌تر می‌شوند، ممکن است با نیازهایی مواجه شوید که نیازمند درک عمیق‌تری از قابلیت‌های پیشرفته Go Modules است.

Workspaces در Go 1.18+ (go.work)

با Go 1.18، مفهوم Workspaces معرفی شد تا توسعه‌دهندگان بتوانند با چندین ماژول به صورت همزمان، بدون نیاز به استفاده از دستورات replace در go.mod برای هر ماژول، کار کنند. این ویژگی به ویژه برای پروژه‌هایی که به چندین ماژول داخلی تقسیم شده‌اند (monorepo) یا زمانی که روی یک ماژول و وابستگی‌های محلی آن به صورت همزمان کار می‌کنید، مفید است.

یک Workspace توسط فایل go.work در دایرکتوری ریشه Workspace تعریف می‌شود. این فایل به Go می‌گوید که کدام ماژول‌ها بخشی از Workspace هستند و چگونه باید آن‌ها را حل کند.

ایجاد یک Workspace:


mkdir my_workspace
cd my_workspace
go work init

این دستور یک فایل go.work خالی ایجاد می‌کند.

اضافه کردن ماژول‌ها به Workspace:


go work use ../path/to/module1
go work use ../path/to/module2

پس از افزودن، فایل go.work شما ممکن است به این شکل باشد:


go 1.20

use (
    ./module1
    ./module2
)

حالا، هر زمان که دستورات Go را (مانند go build, go run, go test) در داخل هر یک از دایرکتوری‌های ماژول‌های موجود در Workspace اجرا کنید، Go به جای دانلود وابستگی‌های آن ماژول از اینترنت، به دنبال نسخه‌های محلی آن در ماژول‌های دیگر موجود در Workspace می‌گردد. این کار توسعه و آزمایش را در محیط‌های چندماژولی بسیار ساده می‌کند.

replace و exclude در go.mod

همانطور که قبلاً اشاره شد، replace و exclude دستورالعمل‌های قدرتمندی در go.mod هستند که به شما امکان می‌دهند رفتار Go Modules را در مورد وابستگی‌های خاص سفارشی‌سازی کنید.

  • replace <old_path> => <new_path>:

    این دستورالعمل به Go می‌گوید که به جای یک ماژول خاص (<old_path>) در یک نسخه خاص، از یک مسیر جایگزین (<new_path>) استفاده کند. موارد استفاده رایج عبارتند از:

    • توسعه محلی: زمانی که روی یک فورک از یک کتابخانه کار می‌کنید یا تغییراتی را در یک وابستگی محلی آزمایش می‌کنید.
    • replace github.com/some/package v1.2.3 => ../path/to/local/package
    • انتقال از یک مخزن به مخزن دیگر: زمانی که یک پروژه به یک URL مخزن جدید منتقل شده است.
    • replace example.com/old/module => example.com/new/module v1.0.0

    نکته مهم: replace معمولاً فقط در محیط توسعه شما استفاده می‌شود و نباید به طور دائمی در go.mod کامیت شود، مگر اینکه برای یک فورک دائمی یا انتقال مخزن باشد. Workspaces (go.work) جایگزین مدرن و ترجیحی برای replace برای توسعه محلی چندماژولی است.

  • exclude <module_path> <version>:

    این دستورالعمل به Go می‌گوید که یک نسخه خاص از یک ماژول را هرگز استفاده نکند، حتی اگر یک وابستگی دیگر به آن نسخه نیاز داشته باشد. این می‌تواند در مواردی مفید باشد که یک نسخه خاص دارای یک باگ شناخته شده یا یک آسیب‌پذیری امنیتی است و می‌خواهید از آن اجتناب کنید. Go سعی می‌کند از نسخه دیگری از همان ماژول استفاده کند که الزامات وابستگی‌ها را برآورده می‌کند.

    exclude github.com/bad/module v1.2.3

تجربه توسعه محلی با ماژول‌ها

توسعه و آزمایش تغییرات در یک ماژول که وابستگی یک ماژول دیگر در همان سیستم شماست، می‌تواند چالش‌برانگیز باشد. قبل از Workspaces، توسعه‌دهندگان اغلب مجبور بودند از replace استفاده کنند تا ماژول بالا دستی به جای نسخه منتشر شده، از کدهای محلی ماژول پایین دستی استفاده کند. با Workspaces، این فرایند بسیار ساده‌تر شده است:

  1. ماژول‌های مورد نظر را در یک Workspace قرار دهید.
  2. go.work را در ریشه Workspace ایجاد کنید و ماژول‌ها را با go work use اضافه کنید.
  3. اکنون، وقتی در یکی از ماژول‌ها کار می‌کنید، Go به طور خودکار به جای نسخه راه دور، از کدهای محلی ماژول‌های دیگر در همان Workspace استفاده می‌کند. این کار به شما امکان می‌دهد تا تغییرات را در چندین ماژول به صورت همزمان ایجاد و آزمایش کنید بدون نیاز به کامیت و انتشار مداوم.

عیب‌یابی رایج در Go Modules

با وجود اینکه Go Modules ابزاری قدرتمند است، اما گاهی اوقات ممکن است با مشکلاتی در مدیریت وابستگی‌ها مواجه شوید. درک نحوه عیب‌یابی این مشکلات می‌تواند به شما در رفع سریع آن‌ها کمک کند.

مشکلات وابستگی

  • وابستگی‌های گمشده یا دانلود نشده:

    علائم: خطاهای کامپایل مانند package <path> is not in GOROOT (<path> or module is not in module cache) یا cannot find package "<path>" in any of: (...).

    راه حل:

    1. اطمینان حاصل کنید که در دایرکتوری ریشه ماژول خود هستید (جایی که go.mod قرار دارد).
    2. اجرای go mod tidy: این دستور وابستگی‌های گمشده را اضافه و وابستگی‌های اضافی را حذف می‌کند.
    3. اجرای go mod download: این دستور اطمینان حاصل می‌کند که تمام وابستگی‌ها به صورت محلی دانلود شده‌اند.
    4. بررسی اتصال اینترنت و تنظیمات پروکسی Go (GOPROXY).
  • ناسازگاری نسخه‌ها (Version Mismatch):

    علائم: خطاها در زمان کامپایل یا اجرا که نشان می‌دهد تابع/متد خاصی وجود ندارد یا امضای آن متفاوت است.

    راه حل:

    1. اجرای go mod graph برای مشاهده نمودار وابستگی‌ها و شناسایی ماژول‌هایی که نسخه‌های متفاوتی را می‌خواهند.
    2. استفاده از go get -u <module_path> برای به‌روزرسانی ماژول خاص به آخرین نسخه سازگار.
    3. در صورت نیاز به حل دستی تداخل‌ها، می‌توانید نسخه‌های خاصی را در go.mod با استفاده از require با یک نسخه بالاتر (یا پایین‌تر در صورت نیاز) به صورت صریح تعریف کنید. Go همیشه سعی می‌کند بالاترین نسخه مورد نیاز را انتخاب کند.
    4. برای ماژول‌هایی با نسخه‌های Major مختلف (/v2, /v3)، اطمینان حاصل کنید که مسیر Import و نسخه در go.mod صحیح است.
  • مشکلات با ماژول‌های خصوصی:

    علائم: خطاهای احراز هویت یا “module not found” برای ماژول‌هایی که در مخازن خصوصی شما هستند.

    راه حل:

    1. بررسی تنظیمات GOPRIVATE. اطمینان حاصل کنید که مسیر ماژول خصوصی شما به درستی در GOPRIVATE تعریف شده است.
    2. بررسی تنظیمات ابزارهای Git/SSH برای احراز هویت با مخزن خصوصی.
    3. در برخی موارد، ممکن است نیاز باشد GONOSUMDB را نیز برای مسیر ماژول خصوصی تنظیم کنید.

خطاهای go.mod

  • تغییرات دستی و نامعتبر در go.mod:

    علائم: خطا در هنگام اجرای دستورات Go Mod یا خطاهای کامپایل عجیب.

    راه حل: فایل go.mod را به صورت دستی ویرایش نکنید مگر اینکه دقیقاً بدانید چه کاری انجام می‌دهید. همیشه از دستورات go get، go mod tidy و go work برای مدیریت آن استفاده کنید. در صورت بروز مشکل، می‌توانید تغییرات را برگردانید و go mod tidy را دوباره اجرا کنید.

  • همگام نبودن go.sum با go.mod:

    علائم: خطاهای هش مربوط به go.sum یا پیام‌هایی مانند “checksum mismatch”.

    راه حل: این مشکل معمولاً زمانی رخ می‌دهد که go.sum به درستی به‌روز نشده باشد یا یک وابستگی از راه دور تغییر کرده باشد. همیشه go mod tidy را اجرا کنید تا go.sum با go.mod و وضعیت واقعی وابستگی‌ها همگام شود. اگر مشکل ادامه یافت، کش ماژول Go را پاک کنید (go clean -modcache) و سپس go mod tidy را دوباره اجرا کنید.

برخورد نسخه‌ها

زمانی که چندین وابستگی به نسخه‌های متفاوتی از یک ماژول مشترک نیاز دارند، Go از اصل انتخاب حداقل نسخه (Minimal Version Selection) استفاده می‌کند. این بدان معناست که Go به جای انتخاب آخرین نسخه موجود، حداقل نسخه‌ای را انتخاب می‌کند که تمامی الزامات وابستگی‌های شما را برآورده کند.

اگر با مشکلات ناشی از این انتخاب مواجه شدید (مثلاً یک باگ در نسخه انتخابی):

  1. بالا بردن صریح نسخه: می‌توانید یک require به go.mod اضافه کنید که نسخه بالاتری از ماژول مورد نظر را مشخص کند.
  2. require github.com/problem/module v1.5.0 // Force a higher version
  3. استفاده از replace: در موارد شدیدتر که نیاز به استفاده از یک فورک یا نسخه خاص غیررسمی دارید، replace می‌تواند کمک کند.

نتیجه‌گیری و چشم‌انداز آینده

پکیج‌ها و ماژول‌ها دو ستون اصلی معماری و مدیریت کد در Go هستند. پکیج‌ها، سازماندهی منطقی و کپسوله‌سازی را در سطح کد فراهم می‌کنند، در حالی که ماژول‌ها، یک راه حل قدرتمند و استاندارد برای مدیریت وابستگی‌ها و نسخه‌بندی در مقیاس پروژه ارائه می‌دهند. درک و استفاده صحیح از این دو مفهوم، نه تنها به شما کمک می‌کند تا کدهای خواناتر و قابل نگهداری بنویسید، بلکه پایداری و تکرارپذیری ساخت پروژه‌های شما را در محیط‌های مختلف تضمین می‌کند.

با معرفی Go Modules، اکوسیستم Go به بلوغ بیشتری رسیده و مشکلات رایج در مدیریت وابستگی‌ها را که پیشتر در سیستم `GOPATH` وجود داشت، به طور موثری حل کرده است. ویژگی‌هایی مانند Semantic Versioning، فایل go.sum برای تضمین یکپارچگی، و قابلیت‌هایی برای مدیریت ماژول‌های خصوصی، Go را به یکی از بهترین گزینه‌ها برای ساخت برنامه‌های مقیاس‌پذیر و قابل اطمینان تبدیل کرده است.

قابلیت Workspaces (go.work) که از Go 1.18 به بعد معرفی شد، گام بعدی در بهبود تجربه توسعه‌دهندگان است، به ویژه برای کسانی که در محیط‌های چندماژولی یا Monorepo کار می‌کنند. این ویژگی به ساده‌سازی فرایندهای توسعه محلی کمک کرده و نیاز به جایگزینی‌های موقت در go.mod را کاهش داده است.

جامعه Go به طور مداوم در حال تکامل و بهبود ابزارهای خود است. انتظار می‌رود در نسخه‌های آینده Go، شاهد بهبودهای بیشتری در مدیریت ماژول‌ها، عملکرد، و ابزارهای توسعه باشیم. با تسلط بر پکیج‌ها و ماژول‌ها، شما مجهز به دانش و ابزارهایی خواهید بود که می‌توانید با اطمینان خاطر در پروژه‌های Go مشارکت کرده و راه‌حل‌های نرم‌افزاری کارآمد و پایدار بسازید.

به یاد داشته باشید که بهترین شیوه‌ها در طراحی پکیج‌ها و مدیریت ماژول‌ها، مانند تک مسئولیتی بودن پکیج‌ها، استفاده صحیح از قابلیت دید، پرهیز از وابستگی‌های چرخشی، و استفاده منظم از go mod tidy، کلید موفقیت در پروژه‌های Go هستند. با رعایت این اصول، مسیر توسعه شما هموارتر و نتایج نهایی قابل اطمینان‌تر خواهد بود.

“تسلط به برنامه‌نویسی پایتون با هوش مصنوعی: آموزش کدنویسی هوشمند با ChatGPT”

قیمت اصلی 2.290.000 ریال بود.قیمت فعلی 1.590.000 ریال است.

"تسلط به برنامه‌نویسی پایتون با هوش مصنوعی: آموزش کدنویسی هوشمند با ChatGPT"

"با شرکت در این دوره جامع و کاربردی، به راحتی مهارت‌های برنامه‌نویسی پایتون را از سطح مبتدی تا پیشرفته با کمک هوش مصنوعی ChatGPT بیاموزید. این دوره، با بیش از 6 ساعت محتوای آموزشی، شما را قادر می‌سازد تا به سرعت الگوریتم‌های پیچیده را درک کرده و اپلیکیشن‌های هوشمند ایجاد کنید. مناسب برای تمامی سطوح با زیرنویس فارسی حرفه‌ای و امکان دانلود و تماشای آنلاین."

ویژگی‌های کلیدی:

بدون نیاز به تجربه قبلی برنامه‌نویسی

زیرنویس فارسی با ترجمه حرفه‌ای

۳۰ ٪ تخفیف ویژه برای دانشجویان و دانش آموزان