Зошто Go Design е лош за паметните програмери

Во текот на изминатите месеци го користев Go за имплементации. Доказ за концептот (прибл.: код за тестирање на функционалноста на идејата) во слободно време, делумно за да го проучува самиот програмски јазик. Самите програми се многу едноставни и не се целта на овој напис, но самото искуство на користење на Go заслужува неколку зборови за тоа. Оди ветува дека ќе биде (прибл.: статија напишана во 2015 година) популарен јазик за сериозен скалабилен код. Јазикот е создаден од Google, каде што активно се користи. Во крајна линија, искрено мислам дека дизајнот на јазикот Go е лош за паметните програмери.

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

Go е многу лесно за учење, толку лесно што воведот ме однесе една вечер, по што веќе можев продуктивно да кодирам. Книгата што ја научив Go се вика Вовед во програмирање во Go (превод), таа е достапна на интернет. Книгата, како и самиот изворен код на Go, е лесна за читање, има добри примери за кодови и содржи околу 150 страници кои можат да се прочитаат со едно движење. Оваа едноставност е освежувачка на почетокот, особено во програмскиот свет исполнет со премногу комплицирана технологија. Но, на крајот, порано или подоцна се јавува мислата: „Дали е навистина така?

Google тврди дека едноставноста на Go е неговата продажна точка и јазикот е дизајниран за максимална продуктивност во големите тимови, но јас се сомневам во тоа. Постојат карактеристики кои или недостасуваат или се премногу детални. И сето тоа поради недостаток на доверба во програмерите, со претпоставка дека тие не се способни да направат ништо како што треба. Оваа желба за едноставност беше свесна одлука на дизајнерите на јазикот, а за целосно да разбереме зошто е потребна, мора да ја разбереме мотивацијата на програмерите и што се обидуваа да постигнат во Go.

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

Клучната точка овде е дека нашите програмери (прибл.: Работници на Google) не се истражувачи. Тие се, по правило, доста млади, доаѓаат кај нас по студирањето, можеби студирале Java, или C/C++ или Python. Тие не можат да разберат одличен јазик, но во исто време сакаме да создадат добар софтвер. Затоа нивниот јазик треба да биде лесен за разбирање и учење.
 
Тој треба да биде запознаен, грубо кажано слично на Ц. Програмерите кои работат во Google ја започнуваат својата кариера рано и главно се запознаени со процедуралните јазици, особено со семејството C. Барањето за брза продуктивност во новиот програмски јазик значи дека јазикот не треба да биде премногу радикален.

Што? Значи, Роб Пајк во основа вели дека програмерите во Google не се толку добри, затоа создадоа јазик за идиоти (прибл.: занемени) за да можат да направат нешто. Каков арогантен поглед кон сопствените колеги? Отсекогаш верував дека програмерите на 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);
    }
}

А кој е сега почитлив? Мојот глас ќе му го дадам на Д. Неговиот код е многу почитлив бидејќи појасно ги опишува дејствата. Д користи многу посложени концепти (прибл.: алтернативен повик на функција и шаблони) отколку во примерот 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))
}

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

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

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

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