چرا به Go2 نیازی نداریم ؟

این چند ماه اخیر بحث های خیلی زیادی هم در توییتر هم در ردیت درباره Go2 داره میشه و تا الان 3 پروپوزال برای 3 تا فیچر جدید داده شده ( البته از طرف کامیونیتی ) خب ببینیم این 3 پروپوزال چی هستن ؟

اررور هندلینگ

اررور ها در گو بر خلاف اکثر زبون های امروزی Exception نیستن و همیشه جزیی از خروجی فانکشن هستن که عموما به این شکل هندل میشن

func someFunc(path string) (string, error){
    file1, err := func1(path)
    if err != nil {
            return "", fmt.Errorf("Some error while func1 : %v", err)
    }
     file2, err := func2(path)    
      if err != nil { 
             return "", fmt.Errorf("Some error while func2 : %v", err)   
       } 
    file3, err := func3(path)           
    if err != nil {               
             return "", fmt.Errorf("Some error while func3 : %v", err)
     }  
       file4, err := func4(path)           
       if err != nil {               
           return "", fmt.Errorf("Some error while func4 : %v", err)
     }  
       file5, err := func5(path)          
        if err != nil {              
         return "", fmt.Errorf("Some error while func5 : %v", err) 
      }  
    return file, nil

نقطه ضعف این روش اینه که اگر فرض کنیم در یک فانکشن شما 5 فانکشن کال داشته باشید و برای همه بخواید خروجی رو چک کنید باید نزدیک 20 خط کد بنویسید که یه مقدار نسبت به کاری که میکنید زیاده حالا ببینیم پروپوزال اول چی ارایه میکنه

func someFunc(path string) (string, error) {
    handle err {
        return fmt.Errorf("Some error while reading file : %v", err)
    }
    file1 := check func1(path)
    file2 = check func2(path)
    file3 := check func3(path)
    file4 := check func4(path)
    file5 := chech func5(path)
    return file5
  }

خب نسبتا خلاصه شد نه ؟؟

توی این سناریو کد ما خیلی خلاصه تر شد و البته به قیمت اضافه شدن دو کیوورد به زبون و یک لایه پیچیدگی.

حالا این سناریو رو در نظر بگیرید

func someFunc(path string) (string, error) {
    file1,err := func1(path)
    if err != nil {
        if err == errType1 {
            return "", fmt.Errorf("some error :%v", err)
        }
        if err == errType2 {
            //some kind of retry mechanism
            return someFunc(path)
        }
        if err == errType3 {
            panic(err)
        }
    }
}

خب توی این سناریو دیزاین ارایه شده نه تنها کمکی نمیکنه بلکه عملا غیر قابل استفاده است ... پس چرا فیچری به زبون اضافه شه که در خیلی موارد عملا استفاده نمیشه ؟

بهبود اررور ها ( ساختار درختی, فرمت بهتر موقع نمایش )

این پروپوزال به طور کلی در مورد wrap کردن اررور ها توی تایپ های مخصوص به خودشون صحبت میکنه مثلا

type DatabaseError struct {
    err error
    DatabaseName string
}
func (e *DatabaseError) Unwrap() error {
    return err
}
func (e *DatabaseError) Formatter () string{
    return someFormatter(e.err)
}

در ابتدا پیشنهاد میده که دو اینترفیس به استاندارد لایبرری اضافه شه که تغییر خیلی مهمی نیست اما اضافه است چون دقیقا کاری که این پروپوزال پیشنهاد میده رو pkg/errors انجام میده و عملا نیازی به تغییر نیست بخش بعدی هم در واقع کاملا وابسته به جنریک هاست که تو بخش بعدی بررسی میکنیم .

جنریک ها

جنریک ها شاید پر بحث ترین و مهمترین بخش این 3 پروپوزال باشن . فیچری که از همون روزهای اول گو سرش بحث بود و در خوب بودنشون شکی نیست اما دو تا بحث مهم در مورد اونها وجود داره اولی آسیب زدن به سادگی کد و دومی پیاده سازی .

بخش دوم توی پروپوزال تا حدی بحث شده و البته من دانش نظر دادن در مورد خوب یا بد بودن این پیاده سازی رو ندارم بهتره خود گو تیم نظر بدن اما در مورد آسیب زدن به سادگی به نظرم نکته مهمیه .

عموما وقتی بخوایم به این مورد فکر کنیم بهتره یه همچین چیزی رو اول ببینیم

public class SomeTypeOfCat<? extends Cat<? extends Mamal<? extends Animal<? extends SomeThingAlive<? extends SomeThing>>>>>


ممکنه بگید که این کد یه کد بده یا اینکه اصلا در گو ارث بری وجود نداره اما این یک مثاله از اینکه جنریک ها به خاطر ماهیتشون چطور میتونن باعث پیچیده شدن کد بشن تازه از سربار موقع کامپایل همچین چیزی بگذریم . جنریک ها در بین این 3 پروپوزال ویژگی نسبتا معقولی هستن ولی هنوز همون دو سوال اول به درستی پاسخ داده نشدن بحث سادگی و پیاده سازی . جالبه که بدونید گو در استاندارد لایبرری خودش شکلی از جنریک ها رو داره برای تایپ هایی مثل اسلایس ها یا مپ ها ولی چون تمام این تایپ ها مربوط به استاندارد لایبرری هستن و به طور محدود پیاده سازی شدن مشکلی پیش نمیارن در این مورد این مقاله توضیح خیلی خوبی از نحوه پیاده شدن این تایپ ها در استاندارد لایبرری ارایه میده .

خب اما این پروپوزال چه چیزی ارایه میده

func echo(type T) (arg T) T {
    return arg
}
type LinkedList(type T) []T

خب بازم به نظر میاد خیلی خوب باشه نه ؟ میشه فانکشنی نوشت یا تایپی تعریف کرد که به شکل تایپ سیف رو هر تایپی کار کنه ولی خب طبق معمول این بخش شیرین ماجراست

وقتی جنریک ها بحثشون پیش میاد بحث بعدی اینه که به چه شکل میشه یه گروه خاص از تایپ ها رو مجاز دونست که تو مثال جاوایی بالا تایپ هایی که از یک کلاس خاص ارث بری داشتن مجاز بودن اما این موضوع توی این پروپوزال چطور حل شده ؟

contract Equal (t) {
    t == t
}
contract Graph(n Node, e Edge) {
    var edges []Edge = n.Edges()
    var nodes []Node = n.Nodes() 
}

توی این پروپوزال برای محدود کردن تایپ های مورد پذیرش مفهوم جدیدی به اسم contract تعریف شده ( که من نفهمیدم چرا از اینترفیس ها استفاده نشده البته اینترفیس ها باید تغییراتی بکنن تا بتونن همه کارای contract ها رو انجام بدن ولی خب باز بهتر از یه چیز جدیده ) و این contract ها یک یا چند رفتار رو برای تایپ تعریف می کنن که اگر اون تایپ بتونه این کار ها رو انجام بده پذیرفته میشه مثلا در contract اول اگر اپراتور == بتونه رو این تایپ کار کنه پس این تایپ پذیرفته میشه یا توی contract دوم اگر متدی به اسم Edges باشه روی تایپ دوم و یک آرایه از Edge ها برگردونه تایپ پذیرفته میشه . contract دوم دقیقا همون کاری رو انجام میده که اینترفیس ها انجام میدن و عملا پیچیدگی هست که اضافه است اما contract اول کاری رو انجام میده که اینترفیس ها قادر به انجامش نبودن و البته پیشنهاد من اینه که اگر قراره این رفتار اضافه بشه به شکلی به اینترفیس ها اضافه بشه .

در کل پروپوزال 3 از بقیه قوی تره دلیلش هم اینه که زمان طولانی تری روش بحث شده اما بازم با فرهنگ زبون گو به نظر من سازگار نیست .

خب امیدوارم مفید بوده باشه نظراتتون چه اینجا چه توییتر خوشحالم میکنه .