Miért rossz a Go az oktalan programozóknak?

A cikk válaszként íródott egy korábban megjelent cikkre antipodean cikk.

Miért rossz a Go az oktalan programozóknak?

Az elmúlt több mint két évben a Go-t használtam egy speciális RADIUS szerver megvalósítására, fejlett számlázási rendszerrel. Útközben magának a nyelvnek a fortélyait tanulom. Maguk a programok nagyon egyszerűek, és nem ez a cikk célja, de maga a Go használatának tapasztalata megérdemel néhány szót védekezésképpen. A Go a komoly, méretezhető kódok egyre inkább mainstream nyelvévé válik. A nyelvet a Google hozta létre, ahol aktívan használják. A lényeg, őszintén úgy gondolom, hogy a Go nyelv kialakítása rossz az UNintelligent programozóknak.

Gyenge programozóknak tervezték?

A gyengék a problémákról beszélnek. Az erős beszéd az ötletekről és álmokról...

A Go nagyon könnyen megtanulható, olyan egyszerű, hogy gyakorlatilag edzés nélkül is elolvashatja a kódot. A nyelvnek ezt a funkcióját sok globális vállalat használja, amikor a kódot nem alapvető szakemberekkel (menedzserekkel, ügyfelekkel stb.) együtt olvassák be. Ez nagyon kényelmes az olyan módszereknél, mint a tervezésvezérelt fejlesztés.
Még a kezdő programozók is egészen tisztességes kódokat kezdenek előállítani egy-két hét után. A könyv, amiből tanultam, a „Go Programming” (Mark Summerfieldtől). A könyv nagyon jó, a nyelv sok árnyalatát érinti. Az olyan feleslegesen bonyolult nyelvek után, mint a Java, PHP, a varázslat hiánya üdítő. De előbb-utóbb sok korlátozott programozónak az az ötlete támad, hogy régi módszereket használjon egy új területen. Ez tényleg szükséges?

Rob Pike (a nyelv fő ideológusa) a Go nyelvet ipari nyelvként hozta létre, amely könnyen érthető és hatékonyan használható. A nyelvet a nagy csapatok maximális termelékenységére tervezték, és ehhez nincs kétség. Sok kezdő programozó panaszkodik, hogy számos funkció hiányzik. Ez az egyszerűség iránti vágy tudatos döntés volt a nyelv tervezőitől, és ahhoz, hogy teljes mértékben megértsük, miért volt erre szükség, meg kell értenünk a fejlesztők motivációját és azt, hogy mit próbáltak elérni a Go-val.

Akkor miért lett ilyen egyszerű? Íme néhány idézet Rob Pike-tól:

A lényeg itt az, hogy a programozóink nem kutatók. Ők általában elég fiatalok, tanulás után jönnek hozzánk, esetleg Java-t, C/C++-t vagy Python-t tanultak. Nem értenek egy nagyszerű nyelvet, de ugyanakkor azt akarjuk, hogy jó szoftvereket készítsenek. Éppen ezért a nyelvnek könnyen érthetőnek és tanulhatónak kell lennie.

Ismerősnek kell lennie, nagyjából C-hez hasonlóan. A Google-nál dolgozó programozók korán kezdik pályafutásukat, és többnyire ismerik az eljárási nyelveket, különösen a C családot. Az új programozási nyelv gyors termelékenységének követelménye azt jelenti, hogy a nyelv nem lehet túl radikális.

Bölcs szavak, nem?

Az egyszerűség műtermékei

Az egyszerűség a szépség elengedhetetlen feltétele. Lev Tolsztoj.

Az egyszerűség megőrzése az egyik legfontosabb cél minden tervezésnél. Mint tudják, a tökéletes projekt nem az a projekt, amelyhez nincs mit hozzátenni, hanem olyan, amelyből nincs mit eltávolítani. Sokan úgy gondolják, hogy az összetett problémák megoldásához (vagy akár kifejezéséhez) komplex eszközre van szükség. Azonban nem. Vegyük például a PERL nyelvet. A nyelvideológusok úgy vélték, hogy egy programozónak legalább három különböző módszerrel kell rendelkeznie egy probléma megoldására. A Go nyelv ideológusai más utat választottak, úgy döntöttek, hogy egy, de igazán jó út elég a cél eléréséhez. Ennek a megközelítésnek komoly alapja van: csak így könnyebb tanulni, és nehezebb elfelejteni.

Sok migráns panaszkodik, hogy a nyelv nem tartalmaz elegáns absztrakciókat. Igen, ez igaz, de ez a nyelv egyik fő előnye. A nyelv minimális mágiát tartalmaz – így nem szükséges mély tudás a program olvasásához. Ami a kód bőbeszédűségét illeti, ez egyáltalán nem probléma. Egy jól megírt Golang program függőlegesen olvas, kevés vagy semmilyen szerkezettel. Ráadásul egy program olvasási sebessége legalább egy nagyságrenddel nagyobb, mint az írási sebesség. Ha úgy gondolja, hogy az összes kód egységes formázású (a beépített gofmt paranccsal történik), akkor néhány extra sor elolvasása egyáltalán nem jelent problémát.

Nem túl kifejező

A művészet nem tűri, ha szabadságát korlátozzák. A pontosság nem az ő felelőssége.

Az egyszerűség iránti vágy miatt a Go-ból hiányoznak azok a konstrukciók, amelyeket más nyelveken a hozzászokott emberek természetesnek tekintenek. Először kissé kényelmetlen lehet, de aztán észreveszi, hogy a program sokkal könnyebben és egyértelműbben olvasható.

Például egy konzol-segédprogram, amely beolvassa az stdin-t vagy egy fájlt a parancssori argumentumokból, így néz ki:

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

Ugyanennek a D-beli problémának a megoldása, bár valamivel rövidebbnek tűnik, nem könnyebben olvasható

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

Pokoli másolás

Az ember magában hordozza a poklot. Luther Márton.

A kezdők folyamatosan panaszkodnak a Go-ra a generikumok hiánya miatt. A probléma megoldására legtöbbjük közvetlen kódmásolást használ. Például egy egész számok listájának összegzésére szolgáló függvény, az ilyen leendő szakemberek úgy vélik, hogy a funkcionalitás nem valósítható meg más módon, mint egyszerű másolás-beillesztéssel minden adattípushoz.

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

A nyelvnek elegendő eszköze van az ilyen konstrukciók megvalósításához. Például az általános programozás jó lenne.

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

És bár a kódunk valamivel hosszabbnak bizonyult, mint az előző esetben, általánossá vált. Ezért nem lesz nehéz számunkra minden aritmetikai műveletet végrehajtani.

Sokan azt mondják, hogy egy D nyelvű program lényegesen rövidebbnek tűnik, és igazuk lesz.

import std.stdio;
import std.algorithm;

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

Ez azonban csak rövidebb, de nem korrektebb, mivel a D implementáció teljesen figyelmen kívül hagyja a hibakezelés problémáját.

A való életben a logika összetettségének növekedésével a szakadék gyorsan szűkül. A rés még gyorsabban bezárul, ha olyan műveletet kell végrehajtania, amelyet nem lehet szabványos nyelvi operátorokkal végrehajtani.

Karbantarthatóság, bővíthetőség és olvashatóság tekintetében véleményem szerint a Go nyelv nyer, bár bőbeszédűségben veszít.

Az általános programozás bizonyos esetekben tagadhatatlan előnyökkel jár. Ezt jól szemlélteti a rendezési csomag. Tehát bármely lista rendezéséhez csak a sort.Interface felületet kell megvalósítanunk.

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

Ha bármilyen nyílt forráskódú projektet használ, és lefuttatja a grep „interface{}” -R parancsot, látni fogja, milyen gyakran használnak zavaros felületeket. A közeli elvtársak azonnal azt mondják, hogy mindez a generikumok hiányának köszönhető. Ez azonban nem mindig van így. Vegyük például a DELPHI-t. Ugyanennek az általánosnak a jelenléte ellenére tartalmaz egy speciális VARIANT típust a tetszőleges adattípusokkal végzett műveletekhez. A Go nyelv ugyanezt teszi.

Az ágyútól a verebekig

A kényszerzubbonynak pedig az őrület méretéhez kell illeszkednie. Stanislav Lec.

Sok szélsőséges rajongó állíthatja, hogy a Go-nak van egy másik mechanizmusa a generikumok létrehozására - a reflexió. És igazuk lesz... de csak ritka esetekben.

Rob Pike figyelmeztet minket:

Ez egy hatékony eszköz, amelyet óvatosan kell használni. El kell kerülni, hacsak nem feltétlenül szükséges.

A Wikipédia a következőket mondja:

A reflexió azt a folyamatot jelenti, amelynek során a program figyelemmel kísérheti és módosíthatja saját struktúráját és viselkedését a végrehajtás során. A reflexió mögött meghúzódó programozási paradigmát reflektív programozásnak nevezik. Ez a metaprogramozás egy fajtája.

Azonban, mint tudod, mindenért fizetni kell. Ebben az esetben ez:

  • nehézségek a programok írásában
  • program végrehajtási sebessége

Ezért a reflexiót óvatosan kell használni, mint egy nagy kaliberű fegyvert. A tükrözés meggondolatlan használata olvashatatlan programokhoz, állandó hibákhoz és alacsony sebességhez vezet. Pont az a baj, hogy egy sznob programozó más, pragmatikusabb és szerényebb kollégák előtt megmutassa a kódját.

Kulturális poggyász Xi-től? Nem, több nyelvről!

A vagyonnal együtt adósságok is az örökösökre maradnak.

Annak ellenére, hogy sokan úgy vélik, hogy a nyelv teljes egészében a C örökségen alapul, ez nem így van. A nyelv a legjobb programozási nyelvek számos aspektusát tartalmazza.

szintaxis

Mindenekelőtt a nyelvtani szerkezetek szintaxisa a C nyelv szintaxisán alapul. A DELPHI nyelvnek azonban jelentős hatása volt. Így azt látjuk, hogy a redundáns zárójelek, amelyek nagymértékben csökkentik a program olvashatóságát, teljesen eltűntek. A nyelv tartalmazza a DELPHI nyelvben rejlő „:=” operátort is. A csomagok fogalma olyan nyelvekből származik, mint az ADA. A nem használt entitások deklarációja a PROLOG nyelvből származik.

Szemantika

A csomagok a DELPHI nyelv szemantikáján alapultak. Minden csomag adatokat és kódot tartalmaz, valamint magán- és nyilvános entitásokat tartalmaz. Ez lehetővé teszi a csomag felületének minimálisra csökkentését.

A delegálási módszerrel történő megvalósítás a DELPHI nyelvből lett kölcsönözve.

Összeállítás

Nem ok nélkül van egy vicc: a Go-t egy C program fordítása közben fejlesztették ki. A nyelv egyik erőssége az ultragyors összeállítása. Az ötletet a DELPHI nyelvből kölcsönözték. Minden Go csomag egy DELPHI modulnak felel meg. Ezeket a csomagokat csak akkor fordítják újra, ha valóban szükséges. Ezért a következő szerkesztés után nem kell a teljes programot lefordítani, hanem csak a megváltozott csomagokat és az ezektől a megváltozott csomagoktól függő csomagokat kell újrafordítani (és akkor is csak akkor, ha a csomagfelületek megváltoztak).

Magas szintű konstrukciók

A nyelv sok különböző magas szintű konstrukciót tartalmaz, amelyek semmilyen módon nem kapcsolódnak olyan alacsony szintű nyelvekhez, mint a C.

  • Húrok
  • Hash táblázatok
  • Szeletek
  • A kacsa gépelést olyan nyelvekből kölcsönözték, mint a RUBY (melyet sajnos sokan nem értenek meg, vagy nem használják ki a benne rejlő lehetőségeket).

Memóriakezelés

A memóriakezelés általában külön cikket érdemel. Ha az olyan nyelvekben, mint a C++, az irányítást teljes mértékben a fejlesztőre bízzák, akkor a későbbi nyelvekben, például a DELPHI-ben referenciaszámláló modellt alkalmaztak. Ezzel a megközelítéssel a ciklikus hivatkozások nem voltak megengedettek, mivel árva klaszterek jöttek létre, így a Go beépített észlelést tartalmaz az ilyen klaszterekre (például C#). Ráadásul a szemétgyűjtő hatékonyabb, mint a legtöbb jelenleg ismert megvalósítás, és már számos valós idejű feladatra használható. Maga a nyelv felismeri azokat a helyzeteket, amikor egy változó tárolására szolgáló érték hozzárendelhető a veremben. Ez csökkenti a memóriakezelő terhelését és növeli a program sebességét.

Egyidejűség és egyidejűség

A nyelv párhuzamossága és versenyképessége dicséretre méltó. Egyetlen alacsony szintű nyelv sem tud még távolról sem versenyezni a Go-val. Az igazság kedvéért érdemes megjegyezni, hogy a modellt nem a nyelv szerzői találták ki, hanem egyszerűen a jó öreg ADA nyelvből kölcsönözték. A nyelv több millió párhuzamos kapcsolat feldolgozására képes az összes CPU-t használva, miközben nagyságrenddel kevésbé bonyolult problémákat okoz a többszálú kódra jellemző holtpontokkal és versenyfeltételekkel.

További előnyök

Ha nyereséges, mindenki önzetlen lesz.

A nyelv számos kétségtelen előnnyel is jár számunkra:

  • Egyetlen végrehajtható fájl a projekt felépítése után nagyban leegyszerűsíti az alkalmazások telepítését.
  • A statikus gépelés és a típuskövetkeztetés jelentősen csökkentheti a kódban előforduló hibák számát, még tesztek írása nélkül is. Ismerek olyan programozókat, akik egyáltalán nem írnak teszteket, és a kódjuk minősége nem romlik jelentősen.
  • Nagyon egyszerű keresztfordítás és a szabványos könyvtár kiváló hordozhatósága, ami nagyban leegyszerűsíti a platformok közötti alkalmazások fejlesztését.
  • Az RE2 reguláris kifejezések szálbiztosak, és előre látható végrehajtási időkkel rendelkeznek.
  • Hatékony szabványos könyvtár, amely lehetővé teszi a legtöbb projekt számára, hogy harmadik féltől származó keretrendszerek nélkül működjön.
  • A nyelv elég erős ahhoz, hogy a problémára összpontosítson, nem pedig annak megoldására, ugyanakkor elég alacsony szintű ahhoz, hogy a problémát hatékonyan meg lehessen oldani.
  • A Go eco rendszer már a dobozból kifejlesztett eszközöket tartalmaz minden alkalomra: tesztek, dokumentáció, csomagkezelés, nagy teljesítményű linterek, kódgenerálás, versenyfeltételek érzékelő stb.
  • A Go 1.11-es verziója beépített szemantikus függőségkezelést vezetett be, amely a népszerű VCS-tárhelyre épül. A Go ökoszisztémát alkotó összes eszköz ezeket a szolgáltatásokat használja arra, hogy egy csapásra letöltsön, építsen és telepítsen belőlük kódot. És ez nagyszerű. Az 1.11-es verzió érkezésével a csomagverzióval kapcsolatos probléma is teljesen megoldódott.
  • Mivel a nyelv alapötlete a varázslat csökkentése, a nyelv kifejezetten a hibakezelésre ösztönzi a fejlesztőket. És ez helyes, mert különben egyszerűen elfelejti a hibakezelést. Egy másik dolog az, hogy a legtöbb fejlesztő szándékosan figyelmen kívül hagyja a hibakezelést, és a feldolgozás helyett inkább egyszerűen továbbítja a hibát felfelé.
  • A nyelv nem valósítja meg a klasszikus OOP módszertant, mivel tiszta formájában a Go-ban nincs virtualitás. Ez azonban nem jelent problémát interfészek használatakor. Az OOP hiánya jelentősen csökkenti a kezdők belépési akadályát.

Egyszerűség a közösség érdekében

Könnyű bonyolítani, nehéz leegyszerűsíteni.

A Go-t egyszerűnek tervezték, és ezt a célt eléri. Okos programozóknak íródott, akik értik a csapatmunka előnyeit, és belefáradtak a vállalati szintű nyelvek végtelen változatosságába. Mivel a szintaktikai struktúrák viszonylag kis készlete van az arzenáljában, gyakorlatilag nincs kitéve az idő múlásával történő változásoknak, így a fejlesztőknek sok ideje marad a fejlesztésre, és nem a nyelvi újítások végtelen tanulmányozására.

A cégek számos előnnyel is járnak: az alacsony belépési korlát lehetővé teszi, hogy gyorsan szakembert találjanak, a nyelv megváltoztathatatlansága pedig 10 év után is ugyanazt a kódot használja.

Következtetés

A nagy agyméret miatt soha egyetlen elefánt sem lett Nobel-díjas.

Azoknak a programozóknak, akiknek személyes egója elsőbbséget élvez a csapatszellemnél, valamint azoknak a teoretikusoknak, akik szeretik az akadémiai kihívásokat és a végtelen "önfejlesztést", a nyelvezet nagyon rossz, mivel ez egy általános célú kézműves nyelv, amely nem teszi lehetővé esztétikai örömet szerez a munkája eredményéből, és mutassa meg magát professzionálisan kollégái előtt (feltéve, hogy az intelligenciát ezekkel a kritériumokkal mérjük, és nem az IQ-val). Mint minden az életben, ez is személyes prioritások kérdése. Mint minden érdemes újítás, a nyelv már hosszú utat tett meg az egyetemes tagadástól a tömeges elfogadásig. A nyelv a maga egyszerűségében zseniális, és mint tudod, minden zseniális egyszerű!

Forrás: will.com

Hozzászólás