Bakit Masama ang Go para sa Mga Hindi Matalinong Programmer

Ang artikulo ay isinulat bilang tugon sa isang naunang nai-publish antipodean na artikulo.

Bakit Masama ang Go para sa Mga Hindi Matalinong Programmer

Sa nakalipas na dalawang dagdag na taon, ginagamit ko ang Go upang ipatupad ang isang espesyal na server ng RADIUS na may binuong sistema ng pagsingil. Sa daan, natututo ako sa mga intricacies ng wika mismo. Ang mga programa mismo ay napaka-simple at hindi ang layunin ng artikulong ito, ngunit ang karanasan sa paggamit ng Go mismo ay nararapat ng ilang mga salita sa pagtatanggol nito. Ang Go ay nagiging pangunahing wika para sa seryoso at nasusukat na code. Ang wika ay nilikha ng Google, kung saan ito ay aktibong ginagamit. Bottom line, sa totoo lang iniisip ko na ang disenyo ng Go language ay masama para sa UNintelligent programmer.

Idinisenyo para sa mahinang programmer?

Ang mahina ay nagsasalita ng mga problema. Ang malakas na usapan tungkol sa mga ideya at pangarap...

Napakadaling matutunan ng Go, napakadali na maaari mong basahin ang code nang halos walang pagsasanay. Ang tampok na ito ng wika ay ginagamit sa maraming pandaigdigang kumpanya, kapag binasa ang code kasama ng mga hindi pangunahing espesyalista (mga tagapamahala, customer, atbp.). Ito ay napaka-maginhawa para sa mga pamamaraan tulad ng Design Driven Development.
Kahit na ang mga baguhang programmer ay nagsisimulang gumawa ng medyo disenteng code pagkatapos ng isang linggo o dalawa. Ang aklat na pinag-aralan ko ay "Go Programming" (ni Mark Summerfield). Ang libro ay napakahusay, nakakaantig ito sa maraming mga nuances ng wika. Pagkatapos ng hindi kinakailangang kumplikadong mga wika tulad ng Java, PHP, ang kakulangan ng magic ay nagre-refresh. Ngunit maaga o huli, maraming limitadong programmer ang may ideya ng paggamit ng mga lumang pamamaraan sa isang bagong larangan. Kailangan ba talaga ito?

Nilikha ni Rob Pike (ang pangunahing ideologist ng wika) ang Go language bilang isang pang-industriyang wika na madaling maunawaan at mabisang gamitin. Ang wika ay idinisenyo para sa maximum na produktibo sa malalaking koponan at walang duda tungkol dito. Maraming mga baguhang programmer ang nagreklamo na maraming mga tampok na nawawala sa kanila. Ang pagnanais na ito para sa pagiging simple ay isang mulat na desisyon ng mga taga-disenyo ng wika, at upang lubos na maunawaan kung bakit ito kinakailangan, dapat nating maunawaan ang motibasyon ng mga developer at kung ano ang sinusubukan nilang makamit sa Go.

Kaya bakit ito ginawang napakasimple? Narito ang ilang mga panipi mula kay Rob Pike:

Ang pangunahing punto dito ay ang aming mga programmer ay hindi mga mananaliksik. Sila ay, bilang isang panuntunan, medyo bata, pumunta sa amin pagkatapos ng pag-aaral, marahil sila ay nag-aral ng Java, o C/C++, o Python. Hindi nila maintindihan ang isang mahusay na wika, ngunit sa parehong oras gusto naming lumikha sila ng mahusay na software. Kaya naman ang wika ay dapat na madaling maunawaan at matutunan.

Dapat ay pamilyar siya, halos katulad sa C. Ang mga programmer na nagtatrabaho sa Google ay nagsisimula ng kanilang mga karera nang maaga at kadalasan ay pamilyar sa mga procedural na wika, partikular sa pamilyang C. Ang pangangailangan para sa mabilis na produktibidad sa isang bagong programming language ay nangangahulugan na ang wika ay hindi dapat masyadong radikal.

Mga matatalinong salita, hindi ba?

Mga Artifact ng Simplicity

Ang pagiging simple ay isang kinakailangang kondisyon para sa kagandahan. Lev Tolstoy.

Ang pagpapanatiling simple ay isa sa pinakamahalagang layunin sa anumang disenyo. Tulad ng alam mo, ang isang perpektong proyekto ay hindi isang proyekto kung saan walang maidaragdag, ngunit isa kung saan walang dapat alisin. Maraming tao ang naniniwala na upang malutas (o kahit na maipahayag) ang mga kumplikadong problema, kailangan ang isang kumplikadong tool. Gayunpaman, hindi ito. Kunin natin ang wikang PERL bilang halimbawa. Naniniwala ang mga ideologist sa wika na ang isang programmer ay dapat magkaroon ng hindi bababa sa tatlong magkakaibang paraan upang malutas ang isang problema. Ang mga ideologist ng wikang Go ay tumahak sa ibang landas; nagpasya sila na ang isang paraan, ngunit ang isang talagang mahusay, ay sapat na upang makamit ang layunin. Ang diskarte na ito ay may seryosong pundasyon: ang tanging paraan ay mas madaling matutunan at mas mahirap kalimutan.

Maraming migrante ang nagrereklamo na ang wika ay hindi naglalaman ng mga eleganteng abstraction. Oo, ito ay totoo, ngunit ito ay isa sa mga pangunahing bentahe ng wika. Ang wika ay naglalaman ng isang minimum na magic - kaya walang malalim na kaalaman ang kinakailangan upang basahin ang programa. Tulad ng para sa verbosity ng code, ito ay hindi isang problema sa lahat. Ang isang mahusay na pagkakasulat na programa ng Golang ay nagbabasa nang patayo, na may kaunti o walang istraktura. Bilang karagdagan, ang bilis ng pagbabasa ng isang programa ay hindi bababa sa isang order ng magnitude na mas malaki kaysa sa bilis ng pagsulat nito. Kung isasaalang-alang mo na ang lahat ng code ay may pare-parehong pag-format (ginawa gamit ang built-in na gofmt command), kung gayon ang pagbabasa ng ilang karagdagang linya ay hindi isang problema.

Hindi masyadong expressive

Hindi kinukunsinti ng sining kapag napipigilan ang kalayaan nito. Ang katumpakan ay hindi kanyang responsibilidad.

Dahil sa pagnanais para sa pagiging simple, ang Go ay kulang sa mga konstruksyon na sa ibang mga wika ay itinuturing na isang bagay na natural ng mga taong nakasanayan na sa kanila. Sa una ay maaaring medyo hindi maginhawa, ngunit pagkatapos ay napansin mo na ang programa ay mas madali at mas malinaw na basahin.

Halimbawa, ang isang console utility na nagbabasa ng stdin o isang file mula sa mga argumento ng command line ay magiging ganito:

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

Ang solusyon sa parehong problema sa D, kahit na mukhang mas maikli, ay hindi madaling basahin

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

Impiyerno ng pagkopya

Ang tao ay nagdadala ng impiyerno sa kanyang sarili. Martin Luther.

Ang mga nagsisimula ay patuloy na nagrereklamo tungkol sa Go sa mga tuntunin ng kakulangan ng mga generic. Upang malutas ang isyung ito, karamihan sa kanila ay gumagamit ng direktang pagkopya ng code. Halimbawa, ang isang function para sa pagbubuod ng isang listahan ng mga integer, ang mga magiging propesyonal ay naniniwala na ang functionality ay hindi maaaring ipatupad sa anumang iba pang paraan kaysa sa pamamagitan ng simpleng pagkopya-paste para sa bawat uri ng data.

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

Ang wika ay may sapat na paraan upang ipatupad ang mga naturang konstruksiyon. Halimbawa, ang generic na programming ay magiging maayos.

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

At, kahit na ang aming code ay naging medyo mas mahaba kaysa sa nakaraang kaso, ito ay naging pangkalahatan. Samakatuwid, hindi magiging mahirap para sa amin na ipatupad ang lahat ng mga operasyon sa aritmetika.

Marami ang magsasabi na ang isang programa sa D ay mukhang mas maikli, at sila ay magiging tama.

import std.stdio;
import std.algorithm;

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

Gayunpaman, ito ay mas maikli lamang, ngunit hindi mas tama, dahil ang pagpapatupad ng D ay ganap na binabalewala ang problema sa paghawak ng error.

Sa totoong buhay, habang lumalaki ang pagiging kumplikado ng lohika, mabilis na lumiliit ang agwat. Mas mabilis na nagsasara ang gap kapag kailangan mong magsagawa ng pagkilos na hindi maaaring gawin gamit ang mga karaniwang operator ng wika.

Sa mga tuntunin ng maintainability, extensibility, at readability, sa palagay ko, ang wika ng Go ay nanalo, bagama't natalo ito sa verbosity.

Ang pangkalahatang programming sa ilang mga kaso ay nagbibigay sa amin ng hindi maikakaila na mga benepisyo. Ito ay malinaw na inilalarawan ng sort package. Kaya, upang ayusin ang anumang listahan, kailangan lang nating ipatupad ang sort.Interface na 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)
}

Kung kukuha ka ng anumang open source na proyekto at patakbuhin ang grep na β€œinterface{}” -R command, makikita mo kung gaano kadalas ginagamit ang nakakalito na mga interface. Sasabihin kaagad ng mga malapit na kasama na ang lahat ng ito ay dahil sa kakulangan ng generics. Gayunpaman, hindi ito palaging nangyayari. Kunin natin ang DELPHI bilang isang halimbawa. Sa kabila ng pagkakaroon ng mga parehong generic na ito, naglalaman ito ng isang espesyal na uri ng VARIANT para sa mga operasyon na may mga arbitrary na uri ng data. Ganoon din ang ginagawa ng wikang Go.

Mula sa isang kanyon hanggang sa mga maya

At dapat magkasya ang straitjacket sa laki ng kabaliwan. Stanislav Lec.

Maaaring i-claim ng maraming extreme fans na may isa pang mekanismo si Go para sa paggawa ng generics - reflection. At magiging tama sila... ngunit sa mga bihirang pagkakataon lamang.

Binabalaan tayo ni Rob Pike:

Ito ay isang makapangyarihang tool na dapat gamitin nang may pag-iingat. Dapat itong iwasan maliban kung mahigpit na kinakailangan.

Sinasabi sa atin ng Wikipedia ang sumusunod:

Ang pagninilay ay tumutukoy sa proseso kung saan maaaring subaybayan at baguhin ng isang programa ang sarili nitong istraktura at pag-uugali sa panahon ng pagpapatupad. Ang programming paradigm na pinagbabatayan ng reflection ay tinatawag na reflective programming. Ito ay isang uri ng metaprogramming.

Gayunpaman, tulad ng alam mo, kailangan mong bayaran ang lahat. Sa kasong ito, ito ay:

  • kahirapan sa pagsulat ng mga programa
  • bilis ng pagpapatupad ng programa

Samakatuwid, ang pagmuni-muni ay dapat gamitin nang may pag-iingat, tulad ng isang malaking kalibre na armas. Ang walang pag-iisip na paggamit ng pagmuni-muni ay humahantong sa hindi nababasa na mga programa, patuloy na mga error at mababang bilis. Bagay lang para sa isang snob programmer na maipakita ang kanyang code sa harap ng iba, mas pragmatic at mahinhin na mga kasamahan.

Mga bagahe ng kultura mula kay Xi? Hindi, mula sa maraming wika!

Kasabay ng kayamanan, ang mga utang ay naiwan din sa mga tagapagmana.

Sa kabila ng katotohanang marami ang naniniwala na ang wika ay ganap na nakabatay sa C pamana, hindi ito ang kaso. Ang wika ay nagsasama ng maraming aspeto ng pinakamahusay na mga programming language.

palaugnayan

Una sa lahat, ang syntax ng mga istrukturang panggramatika ay batay sa syntax ng wikang C. Gayunpaman, ang wikang DELPHI ay nagkaroon din ng malaking impluwensya. Kaya, nakikita namin na ang mga kalabisan na panaklong, na lubos na nagpapababa sa pagiging madaling mabasa ng programa, ay ganap na tinanggal. Ang wika ay naglalaman din ng ":=" operator na likas sa wikang DELPHI. Ang konsepto ng mga pakete ay hiniram mula sa mga wika tulad ng ADA. Ang deklarasyon ng mga hindi nagamit na entity ay hiniram mula sa PROLOG na wika.

Semantiko

Ang mga pakete ay batay sa mga semantika ng wikang DELPHI. Ang bawat pakete ay naglalaman ng data at code at naglalaman ng mga pribado at pampublikong entity. Pinapayagan ka nitong bawasan ang interface ng package sa pinakamababa.

Ang pagpapatakbo ng pagpapatupad sa pamamagitan ng paraan ng delegasyon ay hiniram mula sa wikang DELPHI.

Kompilasyon

Ito ay hindi walang dahilan na mayroong isang biro: Go ay binuo habang ang isang C program ay pinagsama-sama. Isa sa mga kalakasan ng wika ay ang napakabilis nitong compilation. Ang ideya ay hiniram mula sa wikang DELPHI. Ang bawat Go package ay tumutugma sa isang DELPHI module. Ang mga paketeng ito ay muling pinagsama-sama lamang kapag talagang kinakailangan. Samakatuwid, pagkatapos ng susunod na pag-edit, hindi mo na kailangang i-compile ang buong programa, bagkus ay muling i-compile lamang ang mga binagong pakete at mga pakete na nakadepende sa mga binagong paketeng ito (at kahit na, kung nagbago lamang ang mga interface ng package).

Mataas na antas ng mga konstruksyon

Ang wika ay naglalaman ng maraming iba't ibang mataas na antas ng mga konstruksyon na sa anumang paraan ay hindi nauugnay sa mababang antas ng mga wika tulad ng C.

  • Mga string
  • Mga hash table
  • Mga hiwa
  • Ang pag-type ng pato ay hiniram mula sa mga wika tulad ng RUBY (na, sa kasamaang-palad, marami ang hindi naiintindihan o ginagamit sa buong potensyal nito).

Pamamahala ng kaisipan

Ang pamamahala ng memorya sa pangkalahatan ay nararapat sa isang hiwalay na artikulo. Kung sa mga wika tulad ng C++, ang kontrol ay ganap na natitira sa developer, pagkatapos ay sa mga susunod na wika tulad ng DELPHI, ginamit ang isang modelo ng pagbibilang ng sanggunian. Sa diskarteng ito, hindi pinahintulutan ang mga cyclic na sanggunian, dahil nabuo ang mga orphan cluster, pagkatapos ay may built-in na detection si Go ng mga naturang cluster (tulad ng C#). Bilang karagdagan, ang tagakolekta ng basura ay mas mahusay kaysa sa karamihan sa mga kasalukuyang kilalang pagpapatupad at maaari nang magamit para sa maraming mga real-time na gawain. Ang wika mismo ay kinikilala ang mga sitwasyon kung kailan ang isang halaga upang mag-imbak ng isang variable ay maaaring ilaan sa stack. Binabawasan nito ang pagkarga sa memory manager at pinatataas ang bilis ng programa.

Concurrency at Concurrency

Ang paralelismo at pagiging mapagkumpitensya ng wika ay higit sa papuri. Walang mababang antas na wika ang maaaring makipagkumpitensya kahit malayo sa Go. Upang maging patas, nararapat na tandaan na ang modelo ay hindi naimbento ng mga may-akda ng wika, ngunit hiniram lamang mula sa magandang lumang wika ng ADA. Ang wika ay may kakayahang magproseso ng milyun-milyong parallel na koneksyon gamit ang lahat ng mga CPU, habang mayroong isang order ng magnitude na hindi gaanong kumplikadong mga problema sa mga deadlock at kundisyon ng lahi na tipikal para sa multi-threaded code.

Karagdagang benepisyo

Kung ito ay kumikita, lahat ay magiging walang pag-iimbot.

Ang wika ay nagbibigay din sa atin ng ilang hindi mapag-aalinlanganang benepisyo:

  • Ang isang solong maipapatupad na file pagkatapos buuin ang proyekto ay lubos na nagpapasimple sa pag-deploy ng mga application.
  • Ang static na pag-type at inference ng uri ay maaaring makabuluhang bawasan ang bilang ng mga error sa iyong code, kahit na walang mga pagsubok sa pagsulat. Alam ko ang ilang programmer na gumagawa nang walang pagsusulat ng mga pagsusulit at ang kalidad ng kanilang code ay hindi gaanong nagdurusa.
  • Napakasimpleng cross-compilation at mahusay na portability ng karaniwang library, na lubos na nagpapadali sa pagbuo ng mga cross-platform na application.
  • Ang mga regular na expression ng RE2 ay ligtas sa thread at may mga predictable na oras ng pagpapatupad.
  • Isang malakas na karaniwang library na nagbibigay-daan sa karamihan ng mga proyekto na magawa nang walang mga third-party na frameworks.
  • Ang wika ay sapat na makapangyarihan upang tumuon sa problema sa halip na kung paano ito lutasin, ngunit sapat na mababa ang antas upang ang problema ay malulutas nang mahusay.
  • Naglalaman na ang Go eco system ng mga binuong tool out of the box para sa lahat ng okasyon: mga pagsubok, dokumentasyon, pamamahala ng package, malakas na linter, pagbuo ng code, detector ng kundisyon ng lahi, atbp.
  • Ipinakilala ng Go version 1.11 ang built-in na semantic dependency management, na binuo sa ibabaw ng sikat na VCS hosting. Ang lahat ng tool na bumubuo sa Go ecosystem ay gumagamit ng mga serbisyong ito upang mag-download, bumuo, at mag-install ng code mula sa mga ito nang isang beses. At ang galing. Sa pagdating ng bersyon 1.11, ang problema sa package versioning ay ganap ding nalutas.
  • Dahil ang pangunahing ideya ng wika ay upang bawasan ang mahika, ang wika ay nag-uudyok sa mga developer na tahasan ang paghawak ng error. At ito ay tama, dahil kung hindi, ito ay makakalimutan lamang ang tungkol sa paghawak ng error sa kabuuan. Ang isa pang bagay ay ang karamihan sa mga developer ay sadyang binabalewala ang paghawak ng error, mas pinipili sa halip na iproseso ang mga ito na ipasa lamang ang error pataas.
  • Ang wika ay hindi nagpapatupad ng klasikal na pamamaraan ng OOP, dahil sa dalisay nitong anyo ay walang virtuality sa Go. Gayunpaman, hindi ito problema kapag gumagamit ng mga interface. Ang kawalan ng OOP ay makabuluhang binabawasan ang hadlang sa pagpasok para sa mga nagsisimula.

Ang pagiging simple para sa kapakanan ng komunidad

Madaling pasimplehin, mahirap pasimplehin.

Ang Go ay idinisenyo upang maging simple at nagtagumpay ito sa layuning iyon. Isinulat ito para sa matatalinong programmer na nauunawaan ang mga benepisyo ng pagtutulungan ng magkakasama at pagod na sa walang katapusang pagkakaiba-iba ng mga wika sa antas ng Enterprise. Ang pagkakaroon ng isang medyo maliit na hanay ng mga syntactic na istruktura sa arsenal nito, halos hindi ito napapailalim sa mga pagbabago sa paglipas ng panahon, kaya ang mga developer ay may maraming oras na nabakante para sa pag-unlad, at hindi para sa walang katapusang pag-aaral ng mga pagbabago sa wika.

Ang mga kumpanya ay nakakatanggap din ng ilang mga pakinabang: ang isang mababang hadlang sa pagpasok ay nagbibigay-daan sa kanila na mabilis na makahanap ng isang espesyalista, at ang immutability ng wika ay nagpapahintulot sa kanila na gamitin ang parehong code kahit na pagkatapos ng 10 taon.

Konklusyon

Ang malaking sukat ng utak ay hindi kailanman nagawang magwagi ng Nobel Prize ang sinumang elepante.

Para sa mga programmer na ang personal na kaakuhan ay nangunguna kaysa sa espiritu ng pangkat, pati na rin ang mga teorista na mahilig sa mga hamon sa akademiko at walang katapusang "pagpapabuti sa sarili", ang wika ay talagang masama, dahil ito ay isang pangkalahatang layunin na artisanal na wika na hindi nagpapahintulot sa iyo na makakuha ng aesthetic na kasiyahan mula sa resulta ng iyong trabaho at ipakita ang iyong sarili na propesyonal sa harap ng mga kasamahan (sa kondisyon na sinusukat namin ang katalinuhan sa pamamagitan ng mga pamantayang ito, at hindi sa pamamagitan ng IQ). Tulad ng lahat ng bagay sa buhay, ito ay isang bagay ng mga personal na priyoridad. Tulad ng lahat ng kapaki-pakinabang na inobasyon, malayo na ang narating ng wika mula sa unibersal na pagtanggi hanggang sa malawakang pagtanggap. Ang wika ay mapanlikha sa pagiging simple nito, at, tulad ng alam mo, lahat ng mapanlikha ay simple!

Pinagmulan: www.habr.com

Magdagdag ng komento