چرا طراحی Go برای برنامه نویسان هوشمند بد است؟

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

برای برنامه نویسان ضعیف طراحی شده است؟

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

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

پس چرا اینقدر ساده ساخته شد؟ در اینجا چند نقل قول وجود دارد راب پایک (تقریبا: یکی از همسازان زبان Go):

نکته کلیدی در اینجا این است که برنامه نویسان ما (تقریبا: کارمندان گوگل) محقق نیستند. آنها معمولاً کاملاً جوان هستند، پس از مطالعه به ما مراجعه کنند، شاید جاوا یا C/C++ یا Python را مطالعه کرده باشند. آنها نمی توانند یک زبان عالی را بفهمند، اما در عین حال ما از آنها می خواهیم که نرم افزار خوبی ایجاد کنند. به همین دلیل است که زبان آنها باید برای آنها قابل درک و یادگیری باشد.
 
او باید آشنا باشد، تقریباً شبیه به سی. برنامه نویسانی که در گوگل کار می کنند کار خود را زود شروع می کنند و بیشتر با زبان های رویه ای، به ویژه خانواده C آشنا هستند. لازمه بهره وری سریع در یک زبان برنامه نویسی جدید به این معنی است که زبان نباید خیلی رادیکال باشد.

چی؟ بنابراین راب پایک اساساً می‌گوید که توسعه‌دهندگان در گوگل آنقدرها خوب نیستند، به همین دلیل آنها زبانی برای احمق‌ها ایجاد کردند (تقریبا: خنگ) تا بتوانند کاری کنند. چه نوع نگاه متکبرانه ای به همکاران خودت؟ من همیشه بر این باور بوده‌ام که توسعه‌دهندگان Google از باهوش‌ترین و بهترین‌های روی زمین انتخاب شده‌اند. مطمئناً آنها می توانند چیز دشوارتری را تحمل کنند؟

مصنوعات از سادگی بیش از حد

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

خیلی رسا نیست

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

به عنوان مثال، یک ابزار کنسول که stdin یا یک فایل را از آرگومان های خط فرمان می خواند، به شکل زیر است:

package main

import (
    "bufio"
    "flag"
    "fmt"
    "log"
    "os"
)

func main() {

    flag.Parse()
    flags := flag.Args()

    var text string
    var scanner *bufio.Scanner
    var err error

    if len(flags) > 0 {

        file, err := os.Open(flags[0])

        if err != nil {
            log.Fatal(err)
        }

        scanner = bufio.NewScanner(file)

    } else {
        scanner = bufio.NewScanner(os.Stdin)
    }

    for scanner.Scan() {
        text += scanner.Text()
    }

    err = scanner.Err()
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println(text)
}

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

در اینجا، برای مثال، راه حلی برای همان مشکل در D:

import std.stdio, std.array, std.conv;

void main(string[] args)
{
    try
    {
        auto source = args.length > 1 ? File(args[1], "r") : stdin;
        auto text   = source.byLine.join.to!(string);

        writeln(text);
    }
    catch (Exception ex)
    {
        writeln(ex.msg);
    }
}

و حالا چه کسی خواناتر است؟ من رای خود را به D می دهم. کد او بسیار خواناتر است زیرا او اقدامات را واضح تر توصیف می کند. D از مفاهیم بسیار پیچیده تری استفاده می کند (تقریبا: فراخوانی تابع جایگزین и قالب ها) نسبت به مثال Go، اما در واقع هیچ چیز پیچیده ای در درک آنها وجود ندارد.

جهنم کپی کردن

یک پیشنهاد محبوب برای بهبود Go عمومیت است. این حداقل به جلوگیری از کپی غیر ضروری کد برای پشتیبانی از انواع داده ها کمک می کند. به عنوان مثال، یک تابع برای جمع کردن لیستی از اعداد صحیح را نمی توان به هیچ وجه به جز کپی پیست کردن تابع اصلی آن برای هر نوع عدد صحیح پیاده سازی کرد؛ هیچ راه دیگری وجود ندارد:

package main

import "fmt"

func int64Sum(list []int64) (uint64) {
    var result int64 = 0
    for x := 0; x < len(list); x++ {
        result += list[x]
    }
    return uint64(result)
}

func int32Sum(list []int32) (uint64) {
    var result int32 = 0
    for x := 0; x < len(list); x++ {
        result += list[x]
    }
    return uint64(result)
}

func int16Sum(list []int16) (uint64) {
    var result int16 = 0
    for x := 0; x < len(list); x++ {
        result += list[x]
    }
    return uint64(result)
}

func int8Sum(list []int8) (uint64) {
    var result int8 = 0
    for x := 0; x < len(list); x++ {
        result += list[x]
    }
    return uint64(result)
}

func main() {

    list8  := []int8 {1, 2, 3, 4, 5}
    list16 := []int16{1, 2, 3, 4, 5}
    list32 := []int32{1, 2, 3, 4, 5}
    list64 := []int64{1, 2, 3, 4, 5}

    fmt.Println(int8Sum(list8))
    fmt.Println(int16Sum(list16))
    fmt.Println(int32Sum(list32))
    fmt.Println(int64Sum(list64))
}

و این مثال حتی برای انواع علامت دار هم کار نمی کند. این رویکرد کاملاً اصل عدم تکرار خود را نقض می کند (خشک) یکی از معروف ترین و بدیهی ترین اصولی است که نادیده گرفتن آن منشأ بسیاری از اشتباهات است. چرا Go این کار را می کند؟ این یک جنبه وحشتناک زبان است.

همین مثال در D:

import std.stdio;
import std.algorithm;

void main(string[] args)
{
    [1, 2, 3, 4, 5].reduce!((a, b) => a + b).writeln;
}

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

بای پس سیستم نوع ساده

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

به این مثال از یک راه حل احمقانه زبان برای حل مشکل نگاه کنید:

package main

import "fmt"
import "reflect"

func Reduce(in interface{}, memo interface{}, fn func(interface{}, interface{}) interface{}) interface{} {
    val := reflect.ValueOf(in)

    for i := 0; i < val.Len(); i++ {
        memo = fn(val.Index(i).Interface(), memo)
    }

    return memo
}

func main() {

    list := []int{1, 2, 3, 4, 5}

    result := Reduce(list, 0, func(val interface{}, memo interface{}) interface{} {
        return memo.(int) + val.(int)
    })

    fmt.Println(result)
}

این پیاده سازی Reduce از مقاله وام گرفته شده است ژنریک های اصطلاحی در Go (تقریبا: من نتوانستم ترجمه را پیدا کنم، خوشحال می شوم اگر در این مورد کمک کنید). خوب، اگر اصطلاحی است، من از دیدن مثال غیر اصطلاحی متنفرم. استفاده interface{} - یک مسخره، و در زبان فقط برای دور زدن تایپ لازم است. این یک رابط خالی است و همه انواع آن را پیاده سازی می کنند و آزادی کامل را برای همه فراهم می کند. این سبک برنامه نویسی به طرز وحشتناکی زشت است و این همه ماجرا نیست. شاهکارهای آکروباتیک مانند این نیاز به استفاده از بازتاب زمان اجرا دارند. همانطور که او در یکی از گزارش های خود اشاره کرد، حتی راب پایک نیز از افرادی که از این موضوع سوء استفاده می کنند، خوشش نمی آید.

این یک ابزار قدرتمند است که باید با احتیاط استفاده شود. باید از آن اجتناب کرد مگر اینکه به شدت ضروری باشد.

من به جای این مزخرفات از قالب های D استفاده می کنم. چگونه کسی می تواند این را بگوید interface{} قابل خواندن تر است یا حتی امن تایپ کنید؟

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

Go دارای یک سیستم وابستگی داخلی است که بر روی ارائه دهندگان هاست محبوب ساخته شده است VCS. ابزارهایی که با Go ارائه می‌شوند در مورد این خدمات می‌دانند و می‌توانند به صورت یک‌باره کد را از آن‌ها دانلود، بسازند و نصب کنند. در حالی که این عالی است، یک نقص بزرگ در نسخه سازی وجود دارد! بله، در واقع، شما می توانید کد منبع را از سرویس هایی مانند github یا bitbucket با استفاده از ابزار Go دریافت کنید، اما نمی توانید نسخه را مشخص کنید. و باز هم سادگی به قیمت سودمندی. من قادر به درک منطق چنین تصمیمی نیستم.

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

توشه فرهنگی از شی

به نظر من، Go توسط افرادی ساخته شد که تمام عمر خود از C استفاده کرده بودند و توسط کسانی که نمی خواستند چیز جدیدی را امتحان کنند. زبان را می توان به صورت C با چرخ های اضافی توصیف کرد(منشا.: چرخ های آموزشی). هیچ ایده جدیدی در آن وجود ندارد، به جز حمایت از موازی گرایی (که اتفاقاً فوق العاده است) و این مایه شرمساری است. توازی بسیار خوبی در زبانی به سختی قابل استفاده و لنگ دارید.

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

سادگی به نفع خود شماست

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

برای برنامه نویسان هوشمند بسیار پرمخاطب، غیر قابل توجه و بد است.

سپاس ها مرسینوالد برای ویرایش

منبع: www.habr.com

اضافه کردن نظر