Зашто је Го дизајн лош за паметне програмере

Последњих месеци користио сам Го за имплементације. Доказ концепта (прибл.: код за тестирање функционалности идеје) у слободно време, делом и за проучавање самог програмског језика. Сами програми су веома једноставни и нису сврха овог чланка, али само искуство коришћења Го-а заслужује неколико речи о томе. Го обећава да ће бити (прибл.: чланак написан 2015.) популаран језик за озбиљан скалабилан код. Језик је креирао Гугл, где се активно користи. У крајњој линији, искрено мислим да је дизајн Го језика лош за паметне програмере.

Дизајниран за слабе програмере?

Го је веома лак за учење, толико лак да ми је увод требало једно вече, након чега сам већ могао продуктивно да кодирам. Књига коју сам научио Го зове се Увод у програмирање у Го (превод), доступан је на мрежи. Књига је, као и сам изворни код Го, лака за читање, има добре примере кода и садржи око 150 страница које се могу прочитати у једном даху. Ова једноставност је на почетку освежавајућа, посебно у свету програмирања препуном компликоване технологије. Али на крају, пре или касније се јавља мисао: „Да ли је то заиста тако?“

Гоогле тврди да је Го-ова једноставност његова продајна тачка и да је језик дизајниран за максималну продуктивност у великим тимовима, али сумњам у то. Постоје карактеристике које недостају или су превише детаљне. А све због недостатка поверења у програмере, уз претпоставку да они ништа не могу да ураде како треба. Ова жеља за једноставношћу била је свесна одлука дизајнера језика, а да бисмо у потпуности разумели зашто је то било потребно, морамо разумети мотивацију програмера и шта су они покушавали да постигну у Го-у.

Па зашто је то тако једноставно? Ево пар цитата Роб Пике (прибл.: један од ко-креатора Го језика):

Кључна ствар је да наши програмери (прибл.: Гоогле радници) нису истраживачи. Они су, по правилу, прилично млади, долазе код нас након студија, можда су учили Јаву, или Ц/Ц++, или Питхон. Они не могу да разумеју одличан језик, али у исто време желимо да креирају добар софтвер. Зато њихов језик треба да им буде лак за разумевање и учење.
 
Он би требао бити познат, отприлике слично као Ц. Програмери који раде у Гоогле-у рано почињу своју каријеру и углавном су упознати са процедуралним језицима, посебно са породицом Ц. Захтев за брзом продуктивношћу у новом програмском језику значи да језик не би требало да буде превише радикалан.

Шта? Дакле, Роб Пике у суштини каже да програмери у Гуглу нису толико добри, зато су направили језик за идиоте (прибл.: заглупљени) да би могли нешто да ураде. Какав арогантан поглед на сопствене колеге? Увек сам веровао да су Гоогле-ови програмери изабрани од најсјајнијих и најбољих на Земљи. Сигурно могу да поднесу нешто теже?

Артефакти претеране једноставности

Бити једноставан је достојан циљ у сваком дизајну, а тешко је покушати да направите нешто једноставно. Међутим, када покушавате да решите (или чак изразите) сложене проблеме, понекад је потребан комплексан алат. Сложеност и замршеност нису најбоље карактеристике програмског језика, али постоји средина у којој језик може да креира елегантне апстракције које је лако разумети и користити.

Није баш изражајно

Због своје посвећености једноставности, Гоу недостају конструкције које се у другим језицима доживљавају као природне. Ово у почетку може изгледати као добра идеја, али у пракси резултира опширним кодом. Разлог за то би требао бити очигледан – програмерима мора бити лако да читају туђи код, али у ствари ова поједностављења само штете читљивости. У Го нема скраћеница: или много или ништа.

На пример, услужни програм за конзолу који чита стдин или датотеку из аргумената командне линије би изгледао овако:

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

Иако овај код такође покушава да буде што општији, Го-ова принудна опширност стоји на путу, и као резултат, решавање једноставног проблема резултира великом количином кода.

Ево, на пример, решења за исти проблем у 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);
    }
}

А ко је сада читљивији? Даћу свој глас Д. Његов код је много читљивији јер јасније описује радње. Д користи много сложеније концепте (прибл.: алтернативни позив функције и темплатес) него у Го примеру, али заправо нема ништа компликовано у њиховом разумевању.

Пакао од копирања

Популаран предлог за побољшање Го је општост. Ово ће барем помоћи да се избегне непотребно копирање кода за подршку свих типова података. На пример, функција за сумирање листе целих бројева не може се имплементирати ни на који други начин осим копирањем и лепљењем њене основне функције за сваки целобројни тип; не постоји други начин:

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

А овај пример чак и не ради за потписане типове. Овај приступ у потпуности крши принцип да се не понављате (СУВ), један од најпознатијих и очигледних принципа чије игнорисање је извор многих грешака. Зашто Го то ради? Ово је ужасан аспект језика.

Исти пример на Д:

import std.stdio;
import std.algorithm;

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

Једноставно, елегантно и директно у ствар. Функција која се овде користи је reduce за тип шаблона и предикат. Да, ово је опет компликованије од Го верзије, али паметним програмерима није толико тешко за разумевање. Који пример је лакши за одржавање и лакши за читање?

Једноставан тип заобилазнице система

Замишљам да ће Го програмери који ово читају имати пену на устима и вриштати: „Радиш то погрешно!“ Па, постоји још један начин да се направи генеричка функција и типови, али то потпуно разбија систем типова!

Погледајте овај пример глупог језичког поправка да бисте заобишли проблем:

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 је позајмљено из чланка Идиоматска генерика у Го (прибл.: Нисам успео да пронађем превод, биће ми драго ако ми помогнете са овим). Па, ако је идиоматски, не бих волео да видим неидиоматски пример. Употреба interface{} - фарса, а у језику је потребно само да се заобиђе куцање. Ово је празан интерфејс и сви типови га примењују, омогућавајући потпуну слободу за све. Овај стил програмирања је страшно ружан, и то није све. Акробатски подвизи попут ових захтевају употребу рефлексије у току извођења. Чак и Роб Пике не воли појединце који ово злоупотребљавају, као што је поменуо у једном од својих извештаја.

Ово је моћан алат који треба користити са опрезом. Треба га избегавати осим ако није неопходно.

Узео бих Д шаблоне уместо ових глупости. Како то неко може да каже interface{} читљивији или чак безбеднији за куцање?

Јади управљања зависношћу

Го има уграђени систем зависности изграђен на врху популарних хостинг провајдера ВЦС. Алати који се испоручују са Го знају о овим услугама и могу једним потезом да преузму, направе и инсталирају код са њих. Иако је ово одлично, постоји велика мана у верзији! Да, тачно је да можете добити изворни код са сервиса као што су гитхуб или битбуцкет користећи Го алате, али не можете да наведете верзију. И опет једноставност на рачун корисности. Нисам у стању да разумем логику такве одлуке.

Након постављања питања о решењу овог проблема, креирао је Го развојни тим нит форума, који је оцртао како ће заобићи ово питање. Њихова препорука је била да једноставно копирате цело спремиште у ваш пројекат једног дана и оставите га „како јесте“. Шта дођавола они мисле? Имамо невероватне системе контроле верзија са одличним означавањем и подршком за верзије које креатори Го игноришу и само копирају изворни код.

Културни пртљаг из Си

По мом мишљењу, Го су развили људи који су користили Ц цео живот и они који нису желели да пробају нешто ново. Језик се може описати као Ц са додатним точковима (ориг.: тренажни точкови). У њему нема нових идеја, осим подршке паралелизму (што је, иначе, дивно) и ово је штета. Имате одличан паралелизам на једва употребљивом, јадном језику.

Још један проблем који шкрипи је то што је Го процедурални језик (попут тихог ужаса Ц). На крају пишете код у процедуралном стилу који делује архаично и застарело. Знам да објектно оријентисано програмирање није сребрни метак, али би било сјајно да можете да апстрахујете детаље у типове и обезбедите инкапсулацију.

Једноставност за своју корист

Го је дизајниран да буде једноставан и успева у том циљу. Написан је за слабе програмере, користећи стари језик као шаблон. Долази у комплету са једноставним алатима за обављање једноставних ствари. Лако се чита и лако се користи.

Изузетно је опсежан, неимпресиван и лош за паметне програмере.

захвалити мерсинвалд за измене

Извор: ввв.хабр.цом

Додај коментар