Mengapa Reka Bentuk Go Tidak Baik untuk Pengaturcara Pintar

Sejak beberapa bulan lalu saya telah menggunakan Go untuk pelaksanaan. Bukti konsep (lebih kurang: kod untuk menguji kefungsian idea) pada masa lapangnya, sebahagiannya untuk mengkaji bahasa pengaturcaraan itu sendiri. Program itu sendiri adalah sangat mudah dan bukan tujuan artikel ini, tetapi pengalaman menggunakan Go itu sendiri memerlukan beberapa perkataan mengenainya. Pergi berjanji untuk menjadi (lebih kurang: artikel yang ditulis pada tahun 2015) bahasa popular untuk kod boleh skala yang serius. Bahasa ini dicipta oleh Google, di mana ia digunakan secara aktif. Intinya, saya secara jujur ​​berpendapat bahawa reka bentuk bahasa Go adalah buruk untuk pengaturcara pintar.

Direka untuk pengaturcara yang lemah?

Go sangat mudah dipelajari, begitu mudah sehingga pengenalan membawa saya pada suatu petang, selepas itu saya sudah boleh membuat kod secara produktif. Buku yang pernah saya pelajari Go dipanggil Pengenalan kepada Pengaturcaraan dalam Go (terjemahan), ia boleh didapati dalam talian. Buku itu, seperti kod sumber Go itu sendiri, mudah dibaca, mempunyai contoh kod yang baik dan mengandungi kira-kira 150 halaman yang boleh dibaca sekali gus. Kesederhanaan ini menyegarkan pada mulanya, terutamanya dalam dunia pengaturcaraan yang dipenuhi dengan teknologi yang terlalu rumit. Tetapi pada akhirnya, lambat laun timbul pemikiran: "Adakah ini benar-benar begitu?"

Google mendakwa kesederhanaan Go adalah titik jualannya dan bahasa itu direka untuk produktiviti maksimum dalam pasukan besar, tetapi saya meraguinya. Terdapat ciri yang sama ada hilang atau terlalu terperinci. Dan semua kerana kekurangan kepercayaan kepada pemaju, dengan andaian bahawa mereka tidak dapat melakukan apa-apa dengan betul. Keinginan untuk kesederhanaan ini adalah keputusan sedar oleh pereka bahasa, dan untuk memahami sepenuhnya mengapa ia diperlukan, kita mesti memahami motivasi pembangun dan apa yang mereka cuba capai dalam Go.

Jadi mengapa ia dibuat begitu mudah? Berikut adalah beberapa petikan Rob Pike (lebih kurang: salah seorang pencipta bersama bahasa Go):

Perkara utama di sini ialah pengaturcara kami (lebih kurang: Googler) bukan penyelidik. Mereka, sebagai peraturan, agak muda, datang kepada kami selepas belajar, mungkin mereka belajar Java, atau C/C++, atau Python. Mereka tidak dapat memahami bahasa yang hebat, tetapi pada masa yang sama kami mahu mereka mencipta perisian yang baik. Sebab itu bahasa mereka harus mudah difahami dan dipelajari.
 
Dia sepatutnya biasa, secara kasarnya sama dengan C. Pengaturcara yang bekerja di Google memulakan kerjaya mereka lebih awal dan kebanyakannya biasa dengan bahasa prosedur, khususnya keluarga C. Keperluan untuk produktiviti cepat dalam bahasa pengaturcaraan baharu bermakna bahasa itu tidak boleh terlalu radikal.

Apa? Jadi Rob Pike pada dasarnya mengatakan bahawa pembangun di Google tidak begitu bagus, itulah sebabnya mereka mencipta bahasa untuk orang bodoh (lebih kurang: terkedu) supaya mereka mampu melakukan sesuatu. Apa jenis sombong melihat rakan sekerja anda sendiri? Saya sentiasa percaya bahawa pembangun Google dipilih sendiri daripada yang paling terang dan terbaik di Bumi. Pasti mereka boleh mengendalikan sesuatu yang lebih sukar?

Artifak kesederhanaan yang berlebihan

Menjadi mudah adalah matlamat yang layak dalam mana-mana reka bentuk, dan cuba membuat sesuatu yang mudah adalah sukar. Walau bagaimanapun, apabila cuba menyelesaikan (atau menyatakan) masalah yang rumit, kadangkala alat yang kompleks diperlukan. Kerumitan dan kerumitan bukanlah ciri terbaik bahasa pengaturcaraan, tetapi terdapat jalan tengah di mana bahasa itu boleh mencipta abstraksi elegan yang mudah difahami dan digunakan.

Tidak terlalu ekspresif

Oleh kerana komitmennya terhadap kesederhanaan, Go tidak mempunyai binaan yang dianggap semula jadi dalam bahasa lain. Ini mungkin kelihatan seperti idea yang baik pada mulanya, tetapi dalam praktiknya ia menghasilkan kod verbose. Sebab untuk ini harus jelas - ia perlu mudah untuk pembangun membaca kod orang lain, tetapi sebenarnya pemudahan ini hanya membahayakan kebolehbacaan. Tiada singkatan dalam Go: sama ada banyak atau tiada.

Sebagai contoh, utiliti konsol yang membaca stdin atau fail daripada argumen baris arahan akan kelihatan seperti ini:

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

Walaupun kod ini juga cuba menjadi seumum mungkin, kata kerja paksa Go menghalang, dan akibatnya, menyelesaikan masalah mudah menghasilkan sejumlah besar kod.

Di sini, sebagai contoh, adalah penyelesaian kepada masalah yang sama dalam 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);
    }
}

Dan siapa yang lebih mudah dibaca sekarang? Saya akan memberikan undi saya kepada D. Kodnya lebih mudah dibaca kerana dia menerangkan tindakan dengan lebih jelas. D menggunakan konsep yang lebih kompleks (lebih kurang: panggilan fungsi alternatif ΠΈ Template) daripada dalam contoh Go, tetapi tidak ada yang rumit untuk memahaminya.

Jahanam meniru

Cadangan popular untuk menambah baik Go ialah keumuman. Ini sekurang-kurangnya akan membantu mengelakkan penyalinan kod yang tidak perlu untuk menyokong semua jenis data. Sebagai contoh, fungsi untuk menjumlahkan senarai integer tidak boleh dilaksanakan dengan cara lain melainkan dengan menyalin-menampal fungsi asasnya untuk setiap jenis integer; tidak ada cara lain:

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

Dan contoh ini bahkan tidak berfungsi untuk jenis yang ditandatangani. Pendekatan ini benar-benar melanggar prinsip tidak mengulangi diri sendiri (DRY), salah satu prinsip yang paling terkenal dan jelas, mengabaikan yang merupakan punca banyak kesilapan. Mengapa Go melakukan ini? Ini adalah aspek bahasa yang dahsyat.

Contoh yang sama pada D:

import std.stdio;
import std.algorithm;

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

Ringkas, elegan dan straight to the point. Fungsi yang digunakan di sini ialah reduce untuk jenis templat dan predikat. Ya, ini sekali lagi lebih rumit daripada versi Go, tetapi tidak begitu sukar untuk pengaturcara pintar untuk memahami. Contoh yang manakah lebih mudah diselenggara dan lebih mudah dibaca?

Pintasan sistem jenis ringkas

Saya bayangkan pengaturcara Go yang membaca ini akan berbuih mulut dan menjerit, "Anda salah melakukannya!" Nah, terdapat satu lagi cara untuk membuat fungsi dan jenis generik, tetapi ia benar-benar memecahkan sistem jenis!

Lihat contoh pembetulan bahasa bodoh ini untuk menyelesaikan masalah:

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

Pelaksanaan ini Reduce telah dipinjam daripada artikel tersebut Generik idiomatik dalam Go (lebih kurang: Saya tidak dapat mencari terjemahan, saya akan gembira jika anda membantu dengan ini). Nah, jika ia adalah idiomatik, saya tidak suka melihat contoh bukan idiomatik. Penggunaan interface{} - sandiwara, dan dalam bahasa itu hanya diperlukan untuk memintas menaip. Ini adalah antara muka kosong dan semua jenis melaksanakannya, membenarkan kebebasan sepenuhnya untuk semua orang. Gaya pengaturcaraan ini sangat hodoh, dan bukan itu sahaja. Prestasi akrobatik seperti ini memerlukan penggunaan refleksi masa jalan. Malah Rob Pike tidak menyukai individu yang menyalahgunakan ini, seperti yang disebutnya dalam salah satu laporannya.

Ini adalah alat yang berkuasa yang harus digunakan dengan berhati-hati. Ia harus dielakkan melainkan sangat diperlukan.

Saya akan mengambil templat D dan bukannya karut ini. Macam mana orang boleh cakap macam tu interface{} lebih mudah dibaca atau menaip selamat?

Kesengsaraan Pengurusan Kebergantungan

Go mempunyai sistem pergantungan terbina dalam yang dibina di atas penyedia pengehosan yang popular VCS. Alat yang disertakan bersama Go mengetahui tentang perkhidmatan ini dan boleh memuat turun, membina dan memasang kod daripadanya dalam satu masa. Walaupun ini bagus, terdapat kelemahan besar dengan versi! Ya, memang benar anda boleh mendapatkan kod sumber daripada perkhidmatan seperti github atau bitbucket menggunakan alat Go, tetapi anda tidak boleh menentukan versinya. Dan sekali lagi kesederhanaan dengan mengorbankan kegunaan. Saya tidak dapat memahami logik keputusan sedemikian.

Selepas bertanya soalan tentang penyelesaian kepada masalah ini, pasukan pembangunan Go membuat benang forum, yang menggariskan cara mereka akan mengatasi isu ini. Cadangan mereka adalah untuk menyalin keseluruhan repositori ke dalam projek anda satu hari dan biarkan ia "seadanya." Apa yang mereka fikirkan? Kami mempunyai sistem kawalan versi yang menakjubkan dengan penandaan dan sokongan versi yang hebat yang pencipta Go diabaikan dan hanya menyalin kod sumber.

Bagasi budaya dari Xi

Pada pendapat saya, Go dibangunkan oleh orang yang telah menggunakan C sepanjang hidup mereka dan oleh mereka yang tidak mahu mencuba sesuatu yang baru. Bahasa ini boleh digambarkan sebagai C dengan roda tambahan(asal.: roda latihan). Tidak ada idea baru di dalamnya, kecuali untuk sokongan untuk paralelisme (yang, dengan cara itu, indah) dan ini memalukan. Anda mempunyai keselarian yang sangat baik dalam bahasa lumpuh yang hampir tidak boleh digunakan.

Satu lagi masalah berderit ialah Go ialah bahasa prosedur (seperti seram senyap C). Anda akhirnya menulis kod dalam gaya prosedur yang terasa kuno dan ketinggalan zaman. Saya tahu pengaturcaraan berorientasikan objek bukan peluru perak, tetapi ia akan menjadi bagus untuk dapat mengabstrakkan butiran ke dalam jenis dan menyediakan enkapsulasi.

Kesederhanaan untuk kepentingan anda sendiri

Go direka untuk menjadi mudah dan ia berjaya mencapai matlamat itu. Ia ditulis untuk pengaturcara yang lemah, menggunakan bahasa lama sebagai templat. Ia datang lengkap dengan alat mudah untuk melakukan perkara mudah. Ia mudah dibaca dan mudah digunakan.

Ia sangat bertele-tele, tidak mengagumkan dan tidak baik untuk pengaturcara pintar.

Terima kasih mersinvald untuk suntingan

Sumber: www.habr.com

Tambah komen