۶.۶.۱ استفاده از 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) |
کد تکراری | زیاد (اگر برای هر نوع دستی بنویسید) | حداقل (یک بار برای همه انواع) |