Niyə Go Dizayn Ağıllı Proqramçılar üçün Pisdir

Keçən aylarda tətbiqetmələr üçün Go-dan istifadə edirəm. Konsepsiyasının Proof (təqribən.: ideyanın funksionallığını yoxlamaq üçün kod) boş vaxtlarında, qismən proqramlaşdırma dilinin özünü öyrənmək üçün. Proqramların özləri çox sadədir və bu məqalənin məqsədi deyil, lakin Go-dan istifadə təcrübəsi bu barədə bir neçə sözə layiqdir. Get olacağına söz verir (təqribən.: 2015-ci ildə yazılmış məqalə) ciddi ölçülə bilən kod üçün məşhur dildir. Dil Google tərəfindən yaradılıb və burada fəal şəkildə istifadə olunur. Nəticə etibarı ilə mən Go dilinin dizaynının ağıllı proqramçılar üçün pis olduğunu düşünürəm.

Zəif proqramçılar üçün nəzərdə tutulub?

Go öyrənmək çox asandır, o qədər asandır ki, giriş məni bir axşam çəkdi, ondan sonra mən artıq məhsuldar şəkildə kodlaşdıra bildim. Go öyrəndiyim kitab adlanır Go-da Proqramlaşdırmaya Giriş (tərcümə), onlayn olaraq mövcuddur. Kitab, Go mənbə kodunun özü kimi, asan oxunur, yaxşı kod nümunələri var və bir anda oxuna bilən təxminən 150 səhifədən ibarətdir. Bu sadəlik, xüsusən də həddən artıq mürəkkəb texnologiya ilə dolu proqramlaşdırma dünyasında ilk baxışda təravətləndiricidir. Ancaq sonda gec-tez belə bir fikir yaranır: “Doğrudanmı belədir?”

Google iddia edir ki, Go-nun sadəliyi onun satış nöqtəsidir və dil böyük komandalarda maksimum məhsuldarlıq üçün nəzərdə tutulub, lakin mən buna şübhə edirəm. Ya çatışmayan, ya da həddindən artıq təfərrüatlı xüsusiyyətlər var. Və hamısı tərtibatçılara inamın olmaması səbəbindən, onların heç bir şeyi düzgün edə bilməyəcəyi ehtimalı ilə. Bu sadəlik istəyi dil dizaynerlərinin şüurlu qərarı idi və bunun nə üçün lazım olduğunu tam başa düşmək üçün biz tərtibatçıların motivasiyasını və onların Go-da nəyə nail olmağa çalışdıqlarını başa düşməliyik.

Bəs niyə bu qədər sadələşdirildi? Burada bir neçə sitat var Rob Pike (təqribən.: Go dilinin yaradıcılarından biri):

Burada əsas məqam ondan ibarətdir ki, bizim proqramçılar (təqribən.: Google işçiləri) tədqiqatçı deyillər. Onlar, bir qayda olaraq, kifayət qədər gəncdirlər, oxuyandan sonra bizə gəlirlər, bəlkə də Java, ya C/C++, ya da Python dilini öyrəniblər. Onlar gözəl bir dili başa düşə bilmirlər, lakin eyni zamanda biz onlardan yaxşı proqram təminatı yaratmalarını istəyirik. Buna görə də onların dili başa düşülməsi və öyrənilməsi üçün asan olmalıdır.
 
O, tanış olmalıdır, təxminən C-yə bənzəyir. Google-da işləyən proqramçılar karyeralarına erkən başlayırlar və əsasən prosedur dilləri, xüsusən də C ailəsi ilə tanış olurlar. Yeni proqramlaşdırma dilində sürətli məhsuldarlıq tələbi o deməkdir ki, bu dil çox radikal olmamalıdır.

Nə? Beləliklə, Rob Pike əsasən deyir ki, Google-da tərtibatçılar o qədər də yaxşı deyillər, buna görə də axmaqlar üçün bir dil yaratdılar (təqribən.: dilsiz) ki, bir şey edə bilsinlər. Öz həmkarlarınıza necə təkəbbürlü baxırsınız? Mən həmişə Google-un tərtibatçılarının yer üzündəki ən parlaq və ən yaxşılarından seçildiyinə inanmışam. Şübhəsiz ki, daha çətin bir şeyin öhdəsindən gələ bilərlər?

Həddindən artıq sadəlik artefaktları

Sadə olmaq istənilən dizaynda layiqli məqsəddir və sadə bir şey yaratmağa çalışmaq çətindir. Bununla belə, mürəkkəb problemləri həll etməyə (və ya hətta ifadə etməyə) çalışarkən, bəzən mürəkkəb bir vasitə tələb olunur. Mürəkkəblik və mürəkkəblik proqramlaşdırma dilinin ən yaxşı xüsusiyyətləri deyil, lakin dilin başa düşülməsi və istifadəsi asan olan zərif abstraksiyalar yarada biləcəyi orta bir zəmin var.

Çox ifadəli deyil

Sadəliyə sadiqliyinə görə Go digər dillərdə təbii kimi qəbul edilən konstruksiyalardan məhrumdur. Bu, ilk baxışda yaxşı fikir kimi görünə bilər, amma praktikada ətraflı kodla nəticələnir. Bunun səbəbi aydın olmalıdır - tərtibatçılar üçün digər insanların kodunu oxumaq asan olmalıdır, lakin əslində bu sadələşdirmələr yalnız oxunaqlılığa zərər verir. Go-da heç bir abbreviatura yoxdur: ya çox, ya da heç nə.

Məsələn, stdin və ya komanda xətti arqumentlərindən faylı oxuyan konsol yardım proqramı belə görünür:

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)
}

Bu kod da mümkün qədər ümumi olmağa çalışsa da, Go-nun məcburi təfərrüatlılığı buna mane olur və nəticədə sadə bir problemin həlli böyük miqdarda kodla nəticələnir.

Burada, məsələn, eyni problemin həlli var 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);
    }
}

İndi kim daha oxunaqlıdır? Mən səsimi D-yə verəcəm. Onun kodu daha oxunaqlıdır, çünki o, hərəkətləri daha aydın təsvir edir. D daha mürəkkəb anlayışlardan istifadə edir (təqribən.: alternativ funksiya çağırışı и şablonları) Go nümunəsindən fərqli olaraq, lakin onları başa düşmək üçün həqiqətən mürəkkəb bir şey yoxdur.

Kopyalama cəhənnəmi

Go-nu təkmilləşdirmək üçün məşhur təklif ümumilikdir. Bu, ən azı bütün məlumat növlərini dəstəkləmək üçün kodun lazımsız surətinin çıxarılmasının qarşısını almağa kömək edəcək. Məsələn, tam ədədlərin siyahısını toplamaq funksiyası hər bir tam ədəd növü üçün onun əsas funksiyasını kopyalayıb yapışdırmaqdan başqa heç bir şəkildə həyata keçirilə bilməz; başqa yol yoxdur:

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))
}

Və bu nümunə hətta imzalanmış növlər üçün işləmir. Bu yanaşma özünü təkrarlamamaq prinsipini tamamilə pozur (DRY), ən məşhur və aşkar prinsiplərdən biri olan məhəl qoymamaq bir çox səhvlərin mənbəyidir. Go bunu niyə edir? Bu, dilin dəhşətli tərəfidir.

D-də eyni nümunə:

import std.stdio;
import std.algorithm;

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

Sadə, zərif və düz nöqtəyə. Burada istifadə olunan funksiyadır reduce şablon növü və predikat üçün. Bəli, bu Go versiyasından daha mürəkkəbdir, lakin ağıllı proqramçılar üçün başa düşmək o qədər də çətin deyil. Hansı nümunəni saxlamaq və oxumaq daha asandır?

Sadə tipli sistem bypass

Təsəvvür edirəm ki, bunu oxuyan Go proqramçıları ağızlarından köpüklənəcək və “Sən bunu səhv edirsən!” deyə qışqıracaqlar. Yaxşı, ümumi funksiya və növlər yaratmağın başqa bir yolu var, lakin o, tip sistemini tamamilə pozur!

Problemi həll etmək üçün bu axmaq dil düzəliş nümunəsinə nəzər salın:

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)
}

Bu icra Reduce məqalədən götürülmüşdür Go-da idiomatik generiklər (təqribən.: Tərcüməni tapa bilmədim, bu işdə kömək etsəniz şad olaram). Yaxşı, əgər bu idiomatikdirsə, qeyri-idiomatik bir nümunə görməkdən nifrət edərdim. İstifadəsi interface{} - bir fars və dildə yalnız yazmağı keçmək lazımdır. Bu boş interfeysdir və bütün növlər onu həyata keçirir, hər kəsə tam azadlıq verir. Bu proqramlaşdırma tərzi olduqca çirkindir və bu, hamısı deyil. Bu kimi akrobatik cəsarətlər iş vaxtı əksinin istifadəsini tələb edir. Hətta Rob Pike hesabatlarından birində qeyd etdiyi kimi bundan sui-istifadə edən şəxsləri sevmir.

Bu ehtiyatla istifadə edilməli olan güclü bir vasitədir. Çox zərurət olmadıqca bundan çəkinmək lazımdır.

Bu cəfəngiyyatın yerinə D şablonlarını götürərdim. Kim bunu necə deyə bilər interface{} daha oxunaqlı və ya hətta təhlükəsiz yazın?

Asılılığın İdarə Edilməsinin Bəlaları

Go, məşhur hosting provayderləri üzərində qurulmuş daxili asılılıq sisteminə malikdir VCS. Go ilə gələn alətlər bu xidmətlər haqqında bilir və onlardan kodu bir anda endirə, yarada və quraşdıra bilər. Bu əla olsa da, versiya ilə bağlı böyük bir qüsur var! Bəli, mənbə kodunu Go alətlərindən istifadə edərək github və ya bitbucket kimi xidmətlərdən əldə edə biləcəyiniz doğrudur, lakin versiyanı təyin edə bilməzsiniz. Və yenə də faydalılıq hesabına sadəlik. Mən belə bir qərarın məntiqini başa düşə bilmirəm.

Bu problemin həlli ilə bağlı suallar verdikdən sonra Go inkişaf komandası yaradıldı forum mövzusu, bu məsələnin ətrafından necə çıxacaqlarını təsvir etdi. Onların tövsiyəsi sadəcə olaraq bütün deponu bir gün layihənizə köçürmək və onu “olduğu kimi” tərk etmək idi. Onlar nə düşünürlər? Go yaradıcılarının məhəl qoymadığı və sadəcə mənbə kodunu kopyaladığı əla etiketləmə və versiya dəstəyi ilə heyrətamiz versiya idarəetmə sistemlərimiz var.

Xi-dən mədəni baqaj

Məncə, Go bütün həyatı boyu C-dən istifadə etmiş insanlar və yeni bir şey sınamaq istəməyənlər tərəfindən hazırlanmışdır. Dil əlavə təkərlərlə C kimi təsvir edilə bilər(orij.: təlim çarxları). Paralelizmə dəstəkdən başqa (yeri gəlmişkən, gözəldir) yeni ideyalar yoxdur və bu, ayıbdır. Çətinliklə istifadə edilə bilən, axsaq bir dildə əla paralelliyə sahibsiniz.

Başqa bir xırıltılı problem Go-nun prosedur dili olmasıdır (C-nin səssiz dəhşəti kimi). Siz arxaik və köhnəlmiş hiss olunan prosedur üslubunda kod yazırsınız. Mən obyekt yönümlü proqramlaşdırmanın gümüş güllə olmadığını bilirəm, lakin təfərrüatları növlərə ayırmaq və inkapsulyasiyanı təmin etmək əla olardı.

Öz mənfəətiniz üçün sadəlik

Go sadə olmaq üçün hazırlanmışdır və bu məqsədə nail olur. Şablon kimi köhnə bir dildən istifadə edərək zəif proqramçılar üçün yazılmışdır. Sadə işləri görmək üçün sadə alətlərlə tamamlanır. Oxumaq asandır və istifadəsi asandır.

Bu, son dərəcə ətraflı, təsirsiz və ağıllı proqramçılar üçün pisdir.

Təşəkkür mersinvald redaktələr üçün

Mənbə: www.habr.com

Добавить комментарий