Go Tasarımı Akıllı Programcılar İçin Neden Kötü?

Geçtiğimiz aylarda uygulamalar için Go'yu kullanıyorum. Kavramın ispatı (yaklaşık.: bir fikrin işlevselliğini test etmek için kod) boş zamanlarında, kısmen programlama dilinin kendisini incelemek için. Programların kendisi çok basittir ve bu makalenin amacı değildir, ancak Go'yu kullanma deneyimi bu konuda birkaç kelimeyi hak ediyor. Git olacağına söz veriyor (yaklaşık.: 2015'te yazılmış makale) ciddi ölçeklenebilir kodlar için popüler bir dil. Dil, aktif olarak kullanıldığı Google tarafından oluşturuldu. Sonuç olarak, açıkçası Go dilinin tasarımının akıllı programcılar için kötü olduğunu düşünüyorum.

Zayıf programcılar için mi tasarlandınız?

Go'yu öğrenmesi çok kolay, o kadar kolay ki giriş bir akşamımı aldı, sonrasında zaten verimli bir şekilde kod yazabildim. Go'yu öğrendiğim kitabın adı Go'da Programlamaya Giriş (çeviri), çevrimiçi olarak mevcuttur. Kitabın kendisi de Go kaynak kodunun kendisi gibi okunması kolaydır, iyi kod örnekleri içerir ve tek seferde okunabilecek yaklaşık 150 sayfa içerir. Bu basitlik, özellikle aşırı karmaşık teknolojiyle dolu bir programlama dünyasında, ilk başta canlandırıcıdır. Ama sonunda er ya da geç şu düşünce ortaya çıkıyor: "Bu gerçekten böyle mi?"

Google, Go'nun sadeliğinin onun satış noktası olduğunu ve dilin büyük ekiplerde maksimum üretkenlik için tasarlandığını iddia ediyor, ancak bundan şüpheliyim. Eksik veya aşırı ayrıntılı özellikler var. Ve bunların hepsi, hiçbir şeyi doğru yapamayacakları varsayımıyla geliştiricilere olan güven eksikliğinden kaynaklanıyor. Bu basitlik arzusu, dil tasarımcılarının bilinçli bir kararıydı ve buna neden ihtiyaç duyulduğunu tam olarak anlamak için geliştiricilerin motivasyonunu ve Go'da neyi başarmaya çalıştıklarını anlamamız gerekiyor.

Peki neden bu kadar basit hale getirildi? İşte birkaç alıntı Rob Pike (yaklaşık.: Go dilinin ortak yaratıcılarından biri):

Buradaki kilit nokta, programcılarımızın (yaklaşık.: Google çalışanları) araştırmacı değildir. Kural olarak oldukça gençler, okuduktan sonra bize geliyorlar, belki de Java, C/C++ veya Python öğrenmişlerdir. Harika bir dili anlayamazlar ama aynı zamanda onlardan iyi bir yazılım yaratmalarını istiyoruz. Bu nedenle dilin onlar için anlaşılması ve öğrenilmesi kolay olması gerekir.
 
Kabaca C'ye benzer şekilde konuşması tanıdık olmalıdır. Google'da çalışan programcılar kariyerlerine erken başlarlar ve çoğunlukla prosedürel dillere, özellikle de C ailesine aşinadırlar. Yeni bir programlama dilinde hızlı üretkenlik gereksinimi, dilin çok radikal olmaması gerektiği anlamına gelir.

Ne? Yani Rob Pike temelde Google'daki geliştiricilerin o kadar iyi olmadığını, bu yüzden aptallar için bir dil yarattıklarını söylüyor (yaklaşık.: basitleştirilmiş) böylece bir şeyler yapabilsinler. Kendi meslektaşlarınıza ne kadar kibirli bir bakış atıyorsunuz? Her zaman Google'ın geliştiricilerinin dünyanın en parlak ve en iyileri arasından özenle seçildiğine inandım. Elbette daha zor bir şeyin üstesinden gelebilirler mi?

Aşırı basitlik eserleri

Basit olmak her tasarımda önemli bir hedeftir ve basit bir şey yapmaya çalışmak zordur. Ancak karmaşık sorunları çözmeye (hatta ifade etmeye) çalışırken bazen karmaşık bir araca ihtiyaç duyulur. Karmaşıklık ve karmaşıklık bir programlama dilinin en iyi özellikleri değildir ancak dilin anlaşılması ve kullanılması kolay zarif soyutlamalar oluşturabileceği bir orta yol vardır.

Çok etkileyici değil

Sadelik arzusu nedeniyle Go, diğer dillerde doğal bir şey olarak algılanan yapılardan yoksundur. Bu ilk başta iyi bir fikir gibi görünebilir, ancak pratikte ayrıntılı kodlarla sonuçlanır. Bunun nedeni açık olmalıdır; geliştiricilerin diğer kişilerin kodlarını okumasının kolay olması gerekir, ancak aslında bu basitleştirmeler yalnızca okunabilirliğe zarar verir. Go'da kısaltma yoktur: ya çok ya da hiç.

Örneğin, stdin'i veya komut satırı argümanlarından bir dosyayı okuyan bir konsol yardımcı programı şöyle görünecektir:

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

Her ne kadar bu kod mümkün olduğu kadar genel olmaya çalışsa da Go'nun zorunlu ayrıntı düzeyi buna engel oluyor ve sonuç olarak basit bir problemin çözülmesi büyük miktarda kodla sonuçlanıyor.

Örneğin burada aynı problemin çözümü var. 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);
    }
}

Peki şimdi kim daha okunaklı? Oyumu D'ye vereceğim. Eylemleri daha net tanımladığı için kodu çok daha okunaklı. D çok daha karmaşık kavramları kullanır (yaklaşık.: alternatif işlev çağrısı и Desenler) Go örneğindekinden daha fazladır, ancak aslında bunları anlamanın karmaşık bir yanı yoktur.

Kopyalamanın cehennemi

Go'yu geliştirmek için popüler bir öneri genelliktir. Bu, en azından tüm veri türlerini desteklemek için kodun gereksiz kopyalanmasını önlemeye yardımcı olacaktır. Örneğin, bir tamsayı listesinin toplanmasına yönelik bir işlev, her tamsayı türü için temel işlevinin kopyalanıp yapıştırılmasından başka bir şekilde uygulanamaz; başka yolu yoktur:

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

Ve bu örnek imzalı türler için bile işe yaramıyor. Bu yaklaşım kendini tekrarlamama ilkesini tamamen ihlal etmektedir (KURU), en ünlü ve bariz ilkelerden biri olan göz ardı edilmesi birçok hatanın kaynağıdır. Go bunu neden yapıyor? Bu dilin korkunç bir yönüdür.

Aynı örnek D'de:

import std.stdio;
import std.algorithm;

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

Basit, zarif ve doğrudan konuya. Burada kullanılan fonksiyon reduce şablon türü ve yüklem için. Evet, bu yine Go sürümünden daha karmaşıktır ancak akıllı programcıların anlaması o kadar da zor değildir. Hangi örneğin bakımı daha kolay ve okunması daha kolaydır?

Basit tip sistem bypass'ı

Bunu okuyan Go programcılarının ağızlarından köpükler çıkaracaklarını ve "Yanlış yapıyorsun!" diye bağıracaklarını hayal ediyorum. Genel bir işlev ve türler oluşturmanın başka bir yolu daha var, ancak bu, tür sistemini tamamen bozuyor!

Soruna geçici bir çözüm bulmak için şu aptal dil düzeltme örneğine bir göz atın:

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

Bu uygulama Reduce makaleden ödünç alınmıştır Go'da deyimsel jenerikler (yaklaşık.: Çevirisini bulamadım, yardımcı olursanız sevinirim). Eğer deyimselse, deyimsel olmayan bir örnek görmekten nefret ederim. Kullanım interface{} - bir saçmalık ve dilde yalnızca yazmayı atlamak için gereklidir. Bu boş bir arayüzdür ve tüm türler bunu uygulayarak herkese tam bir özgürlük sağlar. Bu programlama tarzı son derece çirkin ve hepsi bu değil. Bunun gibi akrobatik beceriler, çalışma zamanı yansımasının kullanılmasını gerektirir. Rob Pike bile raporlarından birinde belirttiği gibi bunu kötüye kullanan kişilerden hoşlanmıyor.

Bu, dikkatli kullanılması gereken güçlü bir araçtır. Kesinlikle gerekli olmadıkça kaçınılmalıdır.

Bu saçmalık yerine D şablonlarını alırdım. Birisi bunu nasıl söyleyebilir? interface{} daha okunaklı mı, hatta güvenli mi?

Bağımlılık Yönetiminin Sorunları

Go, popüler barındırma sağlayıcılarının üzerine kurulmuş yerleşik bir bağımlılık sistemine sahiptir VCS. Go ile birlikte gelen araçlar bu hizmetleri bilir ve tek bir hamlede onlardan kod indirebilir, oluşturabilir ve yükleyebilir. Bu harika olsa da sürüm oluşturmada büyük bir kusur var! Evet, Go araçlarını kullanarak github veya bitbucket gibi servislerden kaynak kodunu alabileceğiniz doğrudur ancak sürümü belirtemezsiniz. Ve yine kullanışlılık pahasına basitlik. Böyle bir kararın mantığını anlayamıyorum.

Go geliştirme ekibi, bu sorunun çözümüne ilişkin sorular sorduktan sonra şunu oluşturdu: forum başlığıBu sorunu nasıl aşacaklarını özetledi. Onların tavsiyesi, bir gün havuzun tamamını projenize kopyalamanız ve onu "olduğu gibi" bırakmanızdı. Ne düşünüyorlar? Go yaratıcılarının görmezden gelip sadece kaynak kodunu kopyaladığı harika etiketleme ve sürüm desteğine sahip harika sürüm kontrol sistemlerimiz var.

Xi'den kültürel bagaj

Bana göre Go, hayatları boyunca C kullanmış ve yeni bir şey denemek istemeyen kişiler tarafından geliştirildi. Dil, ekstra tekerleklere sahip C olarak tanımlanabilir (orijinal: eğitim tekerlekleri). Paralelliği desteklemek dışında (ki bu arada harika) yeni bir fikir yok ve bu utanç verici. Neredeyse hiç kullanılmayan, topal bir dilde mükemmel bir paralelliğe sahipsiniz.

Gıcırdayan bir başka sorun da Go'nun prosedürel bir dil olmasıdır (C'nin sessiz dehşeti gibi). Sonunda, eski ve modası geçmiş hissettiren prosedürel bir tarzda kod yazmaya başlarsınız. Nesne yönelimli programlamanın sihirli bir değnek olmadığını biliyorum, ancak ayrıntıları türlere ayırıp kapsülleme sağlayabilmek harika olurdu.

Kendi yararınız için basitlik

Go basit olacak şekilde tasarlandı ve bu hedefte başarılı oldu. Zayıf programcılar için eski bir dili şablon olarak kullanarak yazılmıştır. Basit şeyler yapmak için basit araçlarla birlikte gelir. Okuması kolay ve kullanımı kolaydır.

Son derece ayrıntılı, etkileyici olmayan ve akıllı programcılar için kötü.

Teşekkürler Mersinvald düzenlemeler için

Kaynak: habr.com

Yorum ekle