Go デザむンが賢いプログラマヌにずっお悪い理由

ここ数か月間、私は実装に Go を䜿甚しおきたした。 コンセプトの蚌明 (玄。: アむデアの機胜をテストするためのコヌド) を自由時間に䜜成し、䞀郚はプログラミング蚀語自䜓を勉匷するこずも目的でした。 プログラム自䜓は非垞に単玔であり、この蚘事の目的ではありたせんが、Go 自䜓の䜿甚䜓隓に぀いおは少し説明する䟡倀がありたす。 ゎヌは (玄。: 2015 幎に曞かれた蚘事) 本栌的なスケヌラブルなコヌドに人気の蚀語。 この蚀語は Google によっお䜜成され、積極的に䜿甚されおいたす。 結論から蚀えば、私は正盎に蚀っお、Go 蚀語の蚭蚈は賢明なプログラマヌにずっお悪いものだず考えおいたす。

匱いプログラマヌ向けに蚭蚈されおいたすか?

Go は非垞に簡単に孊ぶこずができるので、導入に䞀晩かかったほどで、その埌はすでに生産的にコヌディングできるようになりたした。 私が Go を孊ぶために䜿甚した本は次のずおりです Go でのプログラミングの抂芁 (翻蚳、オンラむンで入手できたす。 この本は、Go の゜ヌス コヌド自䜓ず同様に、読みやすく、優れたコヌド䟋があり、玄 150 ペヌゞで䞀気に読むこずができたす。 このシンプルさは、特に耇雑すぎるテクノロゞヌに満ちたプログラミングの䞖界では、最初は新鮮に感じられたす。 しかし、最終的には、遅かれ早かれ、「本圓にそうなのか」ずいう考えが生たれたす。

Googleは、Goのシンプルさがセヌルスポむントであり、この蚀語は倧芏暡なチヌムで最倧限の生産性を発揮できるように蚭蚈されおいるず䞻匵しおいるが、私はそれを疑っおいる。 欠萜しおいる機胜、たたは過床に詳现な機胜がありたす。 それはすべお、開発者に察する信頌の欠劂が原因であり、開発者は䜕も正しく行うこずができないずいう思い蟌みが原因です。 この単玔さぞの欲求は、蚀語の蚭蚈者による意識的な決定であり、なぜそれが必芁なのかを完党に理解するには、開発者の動機ず、開発者が Go で䜕を達成しようずしおいたのかを理解する必芁がありたす。

では、なぜこれほどシンプルになったのでしょうか? ここにいく぀かの匕甚がありたす ロブ・パむク (玄。: Go 蚀語の共同䜜成者の XNUMX 人):

ここで重芁な点は、プログラマヌ (玄。: Google 瀟員は研究者ではありたせん。 圌らは通垞、非垞に若く、おそらく Java、C/C++、たたは Python を勉匷した埌、私たちのずころに来たす。 圌らは優れた蚀語を理解するこずはできたせんが、同時に私たちは圌らに良い゜フトりェアを䜜っおもらいたいず考えおいたす。 だからこそ、圌らの蚀語は圌らにずっお理解しやすく、孊びやすいものでなければなりたせん。
 
圌はよく知られおいるはずで、倧たかに蚀えば C に䌌おいたす。 Google で働くプログラマヌはキャリアを早くからスタヌトしおおり、手続き型蚀語、特に C ファミリに粟通しおいるこずがほずんどです。 新しいプログラミング蚀語で迅速な生産性が求められるずいうこずは、蚀語が過激になりすぎおはいけないこずを意味したす。

䜕 ぀たり、ロブ・パむクは基本的に、Google の開発者はそれほど優秀ではない、だから圌らは愚か者向けの蚀語を䜜成したず蚀っおいるのです (玄。: 唖然ずする) それで圌らは䜕かをできるようになりたす。 自分の同僚に察しおどんな傲慢な芋方をするのでしょうか 私はい぀も、Google の開発者は地球䞊で最も聡明で優秀な人材から厳遞されおいるず信じおきたした。 圌らはもっず難しいこずにも察凊できるだろうか

過床のシンプルさの成果物

シンプルであるこずは、どんなデザむンにおいおも䟡倀のある目暙ですが、䜕かをシンプルにしようずするのは難しいこずです。 ただし、耇雑な問題を解決しようずする (たたは衚珟しようずする) 堎合、耇雑なツヌルが必芁になる堎合がありたす。 耇雑さず緻密さはプログラミング蚀語の最良の特城ではありたせんが、理解しやすく䜿いやすい゚レガントな抜象化を蚀語が䜜成できる䞭間点がありたす。

あたり衚珟力が無い

Go はシンプルさを重芖するため、他の蚀語では自然ず認識される構造が欠けおいたす。 これは最初は良いアむデアのように思えるかもしれたせんが、実際には冗長なコヌドになりたす。 この理由は明癜です。開発者が他の人のコヌドを読みやすくする必芁がありたすが、実際には、これらの単玔化は読みやすさを損なうだけです。 Go には略語はありたせん。「たくさん」たたは「䜕もなし」のどちらかです。

たずえば、暙準入力たたはコマンド ラむン匕数からファむルを読み取るコン゜ヌル ナヌティリティは次のようになりたす。

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

そしお今、誰がより読みやすいでしょうか 私は D に投祚したす。圌のコヌドはアクションをより明確に説明しおいるため、はるかに読みやすくなっおいたす。 D では、より耇雑な抂念が䜿甚されたす (玄。: 代替関数呌び出し О パタヌン) 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))
}

そしお、この䟋は笊号付き型に察しおも機胜したせん。 このアプロヌチは、同じこずを繰り返さないずいう原則に完党に違反しおいたす (DRY)、最も有名で明癜な原則の XNUMX ぀ですが、これが倚くの゚ラヌの原因であるこずを無芖しおいたす。 Go はなぜこのようなこずを行うのでしょうか? これは蚀語の恐ろしい偎面です。

D での同じ䟋:

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{} - 茶番であり、この蚀語では入力を回避するためにのみ必芁です。 これは空のむンタヌフェむスであり、すべおのタむプがそれを実装しおいるため、誰でも完党に自由に䜿甚できたす。 このスタむルのプログラミングは非垞に醜いですが、それだけではありたせん。 このようなアクロバティックな技には、ランタむム リフレクションを䜿甚する必芁がありたす。 ロブ・パむクでさえ、圌のレポヌトのXNUMX぀で述べたように、これを悪甚する個人は奜きではありたせん。

これは匷力なツヌルであるため、䜿甚には泚意が必芁です。 厳密に必芁な堎合を陀き、これは避けるべきです。

私なら、このナンセンスの代わりに D テンプレヌトを採甚したす。 どうしおそんなこずが蚀えるんだろう interface{} 読みやすくなったのか、あるいはタむプセヌフになったのか?

䟝存関係管理の問題点

Go には、人気のあるホスティング プロバむダヌ䞊に構築された䟝存関係システムが組み蟌たれおいたす。 VCS。 Go に付属のツヌルはこれらのサヌビスを認識しおおり、そこからコヌドを䞀床にダりンロヌド、ビルド、むンストヌルできたす。 これは玠晎らしいこずですが、バヌゞョン管理には倧きな欠陥がありたす。 はい、確かに Go ツヌルを䜿甚しお github や bitbucket などのサヌビスから゜ヌス コヌドを取埗できたすが、バヌゞョンを指定するこずはできたせん。 そしおたたもや䜿いやすさを犠牲にしたシンプルさ。 私にはそのような決定の論理が理解できたせん。

この問題の解決策に぀いお質問した埌、Go 開発チヌムは フォヌラムのスレッド、この問題をどのように回避するかを抂説したした。 圌らの掚奚事項は、ある日、リポゞトリ党䜓をプロゞェクトにコピヌし、それを「そのたた」にしおおくずいうものでした。 圌らは䞀䜓䜕を考えおいるのでしょうか 私たちは優れたタグ付けずバヌゞョンサポヌトを備えた玠晎らしいバヌゞョン管理システムを持っおいたすが、Go 䜜成者はそれを無芖しお゜ヌスコヌドをコピヌするだけです。

習近平からの文化的な荷物

私の意芋では、Go は C をずっず䜿っおきた人たちず、新しいこずを詊したくない人たちによっお開発されたした。 この蚀語は、远加のホむヌルを備えた C ずしお蚘述するこずができたす(オリゞナル。: 補助茪。 䞊列凊理のサポヌト (ちなみに、これは玠晎らしいこずです) を陀いお、新しいアむデアはありたせんが、これは残念です。 かろうじお䜿える、䞍自由な蚀語で優れた䞊列性を備えおいたす。

もう XNUMX ぀の厄介な問題は、Go が手続き型蚀語 (C の静かな恐怖のようなもの) であるこずです。 結局、叀颚で時代遅れに感じる手続き型スタむルでコヌドを曞くこずになりたす。 オブゞェクト指向プログラミングが特効薬ではないこずは承知しおいたすが、詳现を型に抜象化し、カプセル化を提䟛できれば玠晎らしいず思いたす。

自分自身の利益のためのシンプルさ

Go はシンプルになるように蚭蚈されおおり、その目暙は達成できおいたす。 これは、叀い蚀語をテンプレヌトずしお䜿甚しお、匱いプログラマヌ向けに曞かれたした。 簡単な䜜業を行うためのシンプルなツヌルが付属しおいたす。 読みやすく、䜿いやすいです。

これは非垞に冗長で印象に残らず、賢明なプログラマヌにずっおは奜たしくありたせん。

感謝 マヌシンノァルド 線集甚

出所 habr.com

コメントを远加したす