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

مقاله به عنوان پاسخی به مطلبی که قبلا منتشر شده بود نوشته شده است مقاله ضد پودی.

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

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

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

ضعیف ها از مشکلات صحبت می کنند. صحبت های قوی در مورد ایده ها و رویاها ...

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

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

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

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

او باید آشنا باشد، تقریباً شبیه به سی. برنامه نویسانی که در گوگل کار می کنند کار خود را زود شروع می کنند و بیشتر با زبان های رویه ای، به ویژه خانواده C آشنا هستند. لازمه بهره وری سریع در یک زبان برنامه نویسی جدید به این معنی است که زبان نباید خیلی رادیکال باشد.

سخنان حکیمانه، اینطور نیست؟

مصنوعات سادگی

سادگی شرط لازم برای زیبایی است. لو تولستوی.

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

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

خیلی رسا نیست

هنر تحمل نمی کند که آزادی اش محدود شود. دقت مسئولیت او نیست.

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

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

package main

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

func main() {

    flag.Parse()

    scanner := newScanner(flag.Args())

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

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

    fmt.Println(text)
}

func newScanner(flags []string) *bufio.Scanner {
    if len(flags) == 0 {
        return bufio.NewScanner(os.Stdin)
    }

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

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

    return bufio.NewScanner(file)
}

راه‌حل همان مشکل در 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);
    }
}

جهنم کپی کردن

انسان جهنم را در درون خود حمل می کند. مارتین لوتر.

مبتدی ها دائماً از 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 main() {

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

    fmt.Println(int32Sum(list32))
    fmt.Println(int64Sum(list64))
}

زبان ابزار کافی برای اجرای چنین ساختارهایی دارد. به عنوان مثال، برنامه نویسی عمومی خوب است.

package main

import "fmt"

func Eval32(list []int32, fn func(a, b int32)int32) int32 {
    var res int32
    for _, val := range list {
        res = fn(res, val)
    }
    return res
}

func int32Add(a, b int32) int32 {
    return a + b
}

func int32Sub(a, b int32) int32 {
    return a + b
}

func Eval64(list []int64, fn func(a, b int64)int64) int64 {
    var res int64
    for _, val := range list {
        res = fn(res, val)
    }
    return res
}

func int64Add(a, b int64) int64 {
    return a + b
}

func int64Sub(a, b int64) int64 {
    return a - b
}

func main() {

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

    fmt.Println(Eval32(list32, int32Add))
    fmt.Println(Eval64(list64, int64Add))
    fmt.Println(Eval64(list64, int64Sub))
}

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

بسیاری خواهند گفت که یک برنامه در D به طور قابل توجهی کوتاه تر به نظر می رسد و آنها درست خواهند گفت.

import std.stdio;
import std.algorithm;

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

با این حال، فقط کوتاه‌تر است، اما درست‌تر نیست، زیرا اجرای D به طور کامل مشکل رسیدگی به خطا را نادیده می‌گیرد.

در زندگی واقعی، با افزایش پیچیدگی منطق، شکاف به سرعت کاهش می یابد. هنگامی که شما نیاز به انجام عملی دارید که با استفاده از عملگرهای زبان استاندارد قابل انجام نیست، شکاف با سرعت بیشتری بسته می شود.

از نظر قابلیت نگهداری، گسترش پذیری و خوانایی، به نظر من، زبان Go برنده است، اگرچه در پرحرفی بازنده است.

برنامه نویسی تعمیم یافته در برخی موارد مزایای غیر قابل انکاری به ما می دهد. این به وضوح توسط بسته مرتب سازی نشان داده شده است. بنابراین، برای مرتب کردن هر لیست، فقط باید رابط sort.Interface را پیاده سازی کنیم.

import "sort"

type Names []string

func (ns Names) Len() int {
    return len(ns)
}

func (ns Names) Less(i, j int) bool {
    return ns[i] < ns[j]
}

func (ns Names) Swap(i, j int) {
    ns[i], ns[j] = ns[j], ns[i]
}

func main() {
    names := Names{"London", "Berlin", "Rim"}
    sort.Sort(names)
}

اگر هر پروژه منبع باز را انتخاب کنید و دستور grep "interface{}" -R را اجرا کنید، خواهید دید که چقدر از رابط های گیج کننده استفاده می شود. رفقای نزدیک فوراً خواهند گفت که همه اینها به دلیل کمبود ژنریک است. اما همیشه هم به این صورت نیست. بیایید دلفی را به عنوان مثال در نظر بگیریم. علیرغم وجود همین ژنریک ها، دارای یک نوع VARIANT ویژه برای عملیات با انواع داده دلخواه است. زبان Go نیز همین کار را می کند.

از توپ گرفته تا گنجشک

و جلیقه باید به اندازه جنون باشد. استانیسلاو لک.

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

راب پایک به ما هشدار می دهد:

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

ویکی پدیا موارد زیر را به ما می گوید:

انعکاس به فرآیندی اشاره دارد که طی آن یک برنامه می تواند ساختار و رفتار خود را در حین اجرا نظارت و اصلاح کند. پارادایم برنامه نویسی زیربنایی بازتاب برنامه ریزی بازتابی نامیده می شود. این یک نوع فرابرنامه نویسی است.

با این حال، همانطور که می دانید، شما باید برای همه چیز هزینه کنید. در این مورد این است:

  • مشکل در نوشتن برنامه ها
  • سرعت اجرای برنامه

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

توشه فرهنگی از شی؟ نه، از تعدادی زبان!

همراه با ثروت، بدهی نیز به ورثه سپرده می شود.

علیرغم این واقعیت که بسیاری معتقدند این زبان کاملاً بر اساس میراث C است، اینطور نیست. این زبان جنبه های بسیاری از بهترین زبان های برنامه نویسی را در خود جای داده است.

نحو

اول از همه، نحو ساختارهای دستوری بر اساس نحو زبان C است. با این حال، زبان دلفی نیز تأثیر قابل توجهی داشت. بدین ترتیب می بینیم که پرانتزهای اضافی که خوانایی برنامه را بسیار کاهش می دهد، به طور کامل حذف شده اند. این زبان همچنین شامل عملگر “:=” ذاتی زبان دلفی است. مفهوم بسته ها از زبان هایی مانند ADA به عاریت گرفته شده است. اعلان موجودیت های استفاده نشده از زبان PROLOG به عاریت گرفته شده است.

مفاهیم

بسته ها بر اساس معناشناسی زبان دلفی بودند. هر بسته داده ها و کدها را محصور می کند و شامل نهادهای خصوصی و عمومی است. این به شما امکان می دهد تا رابط بسته را به حداقل برسانید.

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

تلفیقی

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

سازه های سطح بالا

این زبان شامل بسیاری از ساختارهای سطح بالا مختلف است که به هیچ وجه به زبان های سطح پایین مانند C مرتبط نیستند.

  • رشته های
  • جداول هش
  • برش ها
  • تایپ اردک از زبان هایی مانند RUBY وام گرفته شده است (که متأسفانه بسیاری آن را نمی فهمند یا از پتانسیل کامل آن استفاده نمی کنند).

مدیریت حافظه

مدیریت حافظه به طور کلی مستحق یک مقاله جداگانه است. اگر در زبان هایی مانند C++ کنترل به طور کامل به توسعه دهنده واگذار می شود، در زبان های بعدی مانند DELPHI از مدل شمارش مرجع استفاده می شود. با این رویکرد، ارجاعات چرخه‌ای مجاز نبودند، زیرا خوشه‌های یتیم تشکیل شدند، پس Go دارای تشخیص داخلی چنین خوشه‌هایی (مانند C#) است. علاوه بر این، جمع‌آوری زباله کارآمدتر از بسیاری از پیاده‌سازی‌های شناخته‌شده در حال حاضر است و می‌توان از آن برای بسیاری از کارهای بلادرنگ استفاده کرد. خود زبان موقعیت هایی را تشخیص می دهد که می توان مقداری برای ذخیره یک متغیر در پشته تخصیص داد. این کار باعث کاهش بار مدیریت حافظه و افزایش سرعت برنامه می شود.

همزمانی و همزمانی

موازی بودن و رقابت پذیری زبان فراتر از ستایش است. هیچ زبان سطح پایینی نمی تواند حتی از راه دور با Go رقابت کند. اگر منصف باشیم، شایان ذکر است که این مدل توسط نویسندگان این زبان اختراع نشده است، بلکه به سادگی از زبان خوب قدیمی ADA وام گرفته شده است. این زبان می‌تواند میلیون‌ها اتصال موازی را با استفاده از تمام CPUها پردازش کند، در حالی که دارای یک مرتبه مشکلات پیچیده‌تر با بن‌بست‌ها و شرایط مسابقه است که برای کدهای چند رشته‌ای معمول است.

مزایای بیشتری

اگر سودآور باشد همه از خود بی خود می شوند.

زبان همچنین تعدادی از مزایای بدون شک را برای ما فراهم می کند:

  • یک فایل اجرایی پس از ساخت پروژه، استقرار برنامه ها را بسیار ساده می کند.
  • تایپ استاتیک و استنتاج نوع می تواند به میزان قابل توجهی تعداد خطاهای کد شما را حتی بدون نوشتن تست کاهش دهد. من برخی از برنامه نویسان را می شناسم که اصلاً تست رایتینگ انجام نمی دهند و کیفیت کد آنها به شدت آسیب نمی بیند.
  • کامپایل متقابل بسیار ساده و قابلیت حمل عالی کتابخانه استاندارد که توسعه برنامه های کاربردی چند پلتفرمی را بسیار ساده می کند.
  • عبارات منظم RE2 از نظر رشته ای ایمن هستند و زمان اجرای قابل پیش بینی دارند.
  • یک کتابخانه استاندارد قدرتمند که به اکثر پروژه ها اجازه می دهد بدون چارچوب های شخص ثالث انجام دهند.
  • زبان به اندازه کافی قدرتمند است که به جای اینکه چگونه آن را حل کند، روی مشکل تمرکز کند، اما به اندازه کافی سطح پایینی دارد که بتوان مشکل را به طور موثر حل کرد.
  • سیستم Go eco در حال حاضر شامل ابزارهای توسعه‌یافته برای همه موارد است: آزمایش‌ها، مستندات، مدیریت بسته‌ها، لینترهای قدرتمند، تولید کد، آشکارساز شرایط مسابقه و غیره.
  • نسخه 1.11 Go مدیریت وابستگی معنایی داخلی را معرفی کرد که بر روی هاست محبوب VCS ساخته شده است. تمام ابزارهایی که اکوسیستم Go را تشکیل می دهند از این خدمات برای دانلود، ساخت و نصب کد از آنها به صورت یکباره استفاده می کنند. و این عالی است. با ورود نسخه 1.11 مشکل نسخه پکیج نیز به طور کامل برطرف شد.
  • از آنجایی که ایده اصلی زبان کاهش جادو است، این زبان توسعه دهندگان را تشویق می کند تا به طور صریح رسیدگی به خطا را انجام دهند. و این درست است، زیرا در غیر این صورت، به سادگی مدیریت خطا را به طور کلی فراموش می کند. نکته دیگر این است که بیشتر توسعه دهندگان عمداً رسیدگی به خطا را نادیده می گیرند و ترجیح می دهند به جای پردازش آنها، خطا را به سمت بالا ارسال کنند.
  • این زبان روش کلاسیک OOP را پیاده سازی نمی کند، زیرا در شکل خالص آن هیچ مجازی در Go وجود ندارد. با این حال، این مشکل در هنگام استفاده از رابط ها نیست. عدم وجود OOP به طور قابل توجهی مانع ورود مبتدیان را کاهش می دهد.

سادگی برای منافع جامعه

پیچیده کردن آن آسان است، ساده کردن آن دشوار است.

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

شرکت ها همچنین تعدادی مزیت را دریافت می کنند: یک مانع ورودی کم به آنها امکان می دهد به سرعت متخصص را پیدا کنند و تغییرناپذیری زبان به آنها امکان می دهد حتی پس از 10 سال از همان کد استفاده کنند.

نتیجه

اندازه مغز بزرگ هرگز هیچ فیلی را برنده جایزه نوبل نکرده است.

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

منبع: www.habr.com

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