از زمان انتشار Go 1.18، زبان برنامهنویسی Go با معرفی پارامترهای نوع، که به طور رایج «جنریک» نامیده میشوند، تحول چشمگیری را تجربه کرده است. برای سالها، توسعهدهندگان Go برای دستیابی به انعطافپذیری نوع به interface{} (اینترفیسهای خالی) و ریفکشن تکیه میکردند که اغلب به قیمت ایمنی در زمان کامپایل و عملکرد تمام میشد. جنریکها بالاخره راهی را فراهم کردند تا کد قابل استفاده مجدد و ایمن از نظر نوع نوشته شود، بدون آنکه از سادگی مشخصه Go کاسته شود.
در این پست، ما از نحو پایه فراتر رفته و به دو سناریوی واقعی با تأثیر بالا میپردازیم که جنریکها در آنها درخشش دارند: مدیریت مارشالینگ JSON برای ساختارهای داده متنوع و ایجاد توابع کمکی برای عملیات اسلایس سفارشی. این مثالها نشان میدهند که چگونه جنریکها میتوانند کد تکراری را کاهش دهند و در عین حال بررسی نوع قوی که Go به آن معروف است را حفظ کنند.
سادهسازی مارشالینگ JSON با توابع کمکی جنریک
یکی از رایجترین وظایف در توسعه وب Go، تجزیه بدنههای درخواست HTTP به ساختارها (structs) است. به طور سنتی، شما باید یک تابع تجزیه معکوس (unmarshaling) خاص برای هر نوع ساختار مینوشتید، یا به یک interface{} عمومی تکیه میکردید که بعداً نیاز به ارجاع نوع (type assertions) دارد؛ فرآیندی که هم طولانی و هم مستعد خطا است.
با استفاده از جنریکها، میتوانیم یک تابع کمکی ایجاد کنیم که هر نوع T را که محدودیتهای مورد نیاز برای تجزیه معکوس JSON را برآورده کند، بپذیرد. این به ما امکان میدهد ساختارهای داده مختلف را به طور یکنواخت مدیریت کنیم، در حالی که اطلاعات نوع را در طول خط لوله حفظ میکنیم.
مثال زیر را در نظر بگیرید. ما میخواهیم یک رشته JSON را به یک نوع جنریک T تجزیه معکوس کنیم. محدودیت در اینجا این است که T باید یک اشارهگر به نوعی باشد که رابط json.Unmarshaler را پیادهسازی کرده است، یا به سادگی میتوانیم فقط به توانایی کتابخانه استاندارد برای تجزیه معکوس به هر نوع قابل مقایسه تکیه کنیم.
package utils
import (
"encoding/json"
"fmt"
)
// UnmarshalJSON یک تابع کمکی جنریک است که یک اسلایس بایت JSON را به یک اشارهگر ارائه شده تجزیه معکوس میکند.
// در صورت شکست عملیات، خطایی را برمیگرداند و مدیریت ایمن از نظر نوع دادههای پاسخ را تضمین میکند.
func UnmarshalJSON[T any](data []byte, target *T) error {
err := json.Unmarshal(data, target)
if err != nil {
return fmt.Errorf("failed to unmarshal JSON into %T: %w", *target, err)
}
return nil
}
// مثال استفاده:
// var user User
// err := UnmarshalJSON(responseBody, &user)
این رویکرد نیاز به چندین تابع با بارگذاری بیش از حد (overloaded) را از بین میبرد. به جای نوشتن UnmarshalUser، UnmarshalProduct و غیره، شما از همان امضای تابع برای تمام ساختارهای داده خود استفاده میکنید. کامپیوتر اطمینان حاصل میکند که target واقعاً یک اشارهگر به یک نوع معتبر است و از پانیکهای زمان اجرا ناشی از ارجاعات نوع نادرست جلوگیری میکند.
ساخت ابزارهای سفارشی اسلایس با محدودیتها
حوزه دیگری که جنریکها ارزش بسیار زیادی ارائه میدهند، دستکاری دادهها است. کتابخانه استاندارد Go فاقد یک تابع داخلی Filter یا Map برای اسلایسها است که اغلب برای توسعهدهندگانی که از زبانهایی مانند پایتون یا جاوااسکریپت میآیند، تعجببرانگیز است. اگرچه میتوانید اینها را به صورت دستی بنویسید، اما انجام این کار برای هر نوع اسلایس منجر به تکرار کد میشود.
با استفاده از محدودیتهای نوع، میتوانیم یک تابع Filter جنریک ایجاد کنیم که روی اسلایسهای هر نوعی کار کند. این برای خدمات بکاند که در آنها اغلب نیاز به فیلتر کردن لیستهای رکوردهای پایگاه داده یا پاسخهای API دارید، به ویژه مفید است.
package utils
// Filter یک اسلایس جدید را برمیگرداند که فقط شامل عناصری است که تابع شرطی (predicate) را برآورده میکنند.
// تابع شرطی یک عنصر از نوع T را دریافت کرده و یک مقدار بولین برمیگرداند.
func Filter[T any](slice []T, predicate func(T) bool) []T {
var result []T
for _, v := range slice {
if predicate(v) {
result = append(result, v)
}
}
return result
}
// مثال استفاده:
// numbers := []int{1, 2, 3, 4, 5}
// evenNumbers := Filter(numbers, func(n int) bool {
// return n%2 == 0
// })
این تابع ایمن از نظر نوع و کارآمد است. برخلاف استفاده از interface{}، کامپایلر نوع دقیق عناصری که روی آنها تکرار میشود را میداند، که اجازه تخصیص و دسترسی بهینه حافظه را میدهد. علاوه بر این، از آنجا که T یک پارامتر نوع است، این تابع را میتوان برای اسلایسهای اعداد صحیح، رشتهها یا حتی ساختارهای سفارشی پیچیده بدون هیچ تغییری در منطق تابع استفاده مجدد کرد.
نتیجهگیری
جنریکهای Go فقط یک شکر مصنوعی نحوی نیستند؛ آنها ابزاری قدرتمند برای بهبود کیفیت کد، کاهش کد تکراری و افزایش ایمنی نوع هستند. با اعمال جنریکها به الگوهای رایج مانند مارشالینگ JSON و دستکاری اسلایس، توسعهدهندگان میتوانند کدی تمیزتر، قابل نگهداریتر و کارآمدتر بنویسند.
همانطور که اکوسیستم Go به تکامل خود ادامه میدهد، پذیرش جنریکها برای توسعه مدرن Go ضروری خواهد شد. از امروز شروع به ادغام این الگوها در پروژههای خود کنید تا از مزایای استفاده مجدد ایمن از نظر نوع، مستقیماً بهرهمند شوید. چه در حال ساخت میکروسرویسها باشید و چه در حال ساخت خط لولههای پردازش داده پیچیده، جنریکها انعطافپذیری و ایمنی مورد نیاز شما را ارائه میدهند.