6.6 مقایسه ژنریک‌ها با راهکارهای قبل از Go 1.18

6.6 مقایسه ژنریک‌ها با راهکارهای قبل از Go 1.18

۶.۶.۱ استفاده از interface{} و reflect قبل از ژنریک‌ها #

پیش از Go 1.18، برای پیاده‌سازی توابع یا ساختارهای داده عمومی، معمولاً از نوع interface{} (نوع همه‌کاره) استفاده می‌شد.
در موارد نیاز به عملیات خاص یا تبدیل نوع، ناچار به استفاده از reflect یا type assertion بودیم. این روش‌ها معایب و ریسک‌های خاص خود را داشتند.

مثال: تابع Max با interface{} و reflect #

 1import (
 2    "fmt"
 3    "reflect"
 4)
 5
 6func Max(a, b interface{}) interface{} {
 7    av := reflect.ValueOf(a)
 8    bv := reflect.ValueOf(b)
 9
10    if av.Kind() == reflect.Int && bv.Kind() == reflect.Int {
11        if av.Int() > bv.Int() {
12            return a
13        }
14        return b
15    }
16    // می‌توانید برای انواع دیگر هم کد بنویسید
17    return nil
18}
19
20func main() {
21    fmt.Println(Max(3, 7)) // خروجی: 7
22}

توضیح:
در این مثال تابع Max با هر نوعی که به آن بدهید کار می‌کند، اما باید به کمک reflect نوع مقدار را بررسی و مقایسه کنید. این کار هم کند است و هم ایمنی نوعی ندارد و در زمان اجرا ممکن است باعث panic یا رفتار ناخواسته شود.

۶.۶.۲ مزایا و معایب هر روش #

interface{} و reflect (روش قدیمی) #

مزایا:

  • قابلیت انعطاف برای پذیرش هر نوع داده (generic ظاهر)
  • قابل استفاده در زبان‌های قبل از Go 1.18

معایب:

  • کاهش ایمنی نوعی (Type Safety): خطاهای نوع فقط در زمان اجرا کشف می‌شوند.
  • پیچیدگی و خوانایی پایین: بررسی نوع با reflect یا type assertion باعث طولانی و پیچیده شدن کد می‌شود.
  • افت کارایی: بازتاب (reflect) کند است و فراخوانی‌های زیاد باعث overhead می‌شود.
  • خطر panic: اگر نوع داده اشتباه ارسال شود، احتمال panic بالا می‌رود.
  • عدم هشدار کامپایلری: هیچ هشدار یا خطایی از سمت کامپایلر دریافت نمی‌کنید.
  • تست و نگهداری دشوار: تست و اشکال‌زدایی کدهایی که مبتنی بر interface{} و reflect هستند به مراتب سخت‌تر است.

ژِنریک‌های Go (از 1.18 به بعد) #

مزایا:

  • ایمنی نوعی بالا: همه خطاهای نوع در زمان کامپایل مشخص می‌شوند.
  • کد کوتاه‌تر و خواناتر: نیاز به تکرار تابع برای هر نوع داده نیست و بازتاب حذف می‌شود.
  • کارایی بهتر: هیچ overhead ناشی از reflect یا type assertion وجود ندارد و کد تولیدشده شبیه کد دستی است.
  • نگهداری آسان‌تر: refactoring راحت‌تر و تست‌پذیری بالاتر
  • مستندسازی خودکار و بهتر: امضاهای توابع و structها واضح و قابل فهم برای توسعه‌دهندگان و ابزارهاست.

معایب:

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

۶.۶.۳ مقایسه کارایی، خوانایی و نگهداشت #

ویژگیinterface{} و reflect (قدیمی)ژنریک‌های Go (جدید)
ایمنی نوعبسیار پایین (خطر panic بالا)بسیار بالا (compile-time checked)
خواناییپایین و پیچیده (reflect و assert)بالا و شفاف (type-safe)
کاراییکند (overhead بازتاب)سریع (مانند کد معمولی)
نگهداریدشوار و پرخطاآسان و قابل refactor
تست‌پذیریسخت (خطاهای run-time)بسیار آسان (خطاهای compile-time)
کد تکراریزیاد (اگر برای هر نوع دستی بنویسید)حداقل (یک بار برای همه انواع)