Чаму мова Go дрэнны для шалёных праграмістаў

Артыкул напісаны, як адказ на апублікаваную раней артыкул-антыпод.

Чаму мова Go дрэнны для шалёных праграмістаў

На працягу апошніх двух з лішнім гадоў выкарыстоўваю Go для рэалізацыі спецыялізаванага RADIUS сервера з развітой білінгавай сістэмай. Па ходзе вывучаю тонкасці самой мовы. Праграмы па сабе вельмі простыя і не з'яўляюцца мэтай напісання артыкула, але сам досвед выкарыстання Go заслугоўвае таго, каб сказаць пару слоў у яго абарону. Go становіцца ўсё больш масавай мовай для сур'ёзнага які маштабуецца кода. Мова створаны ў Google, у якім ім актыўна карыстаюцца. Падвядучы рысу, я шчыра лічу, што дызайн мовы Go дрэнны для НЕразумных праграмістаў.

Створаны для слабых праграмістаў?

Слабыя гавораць аб праблемах. Моцныя гавораць пра ідэі і мары…

Go вельмі проста навучыцца, настолькі проста, што чытаць код можна практычна без падрыхтоўкі ўвогуле. Гэтую асаблівасць мовы выкарыстоўваюць у многіх сусветных кампаніях, калі код чытаюць разам з няпрофільнымі спецыялістамі (мэнэджарамі, заказчыкамі і т. д.). Гэта вельмі зручна для метадалогій тыпу Design Driven Development.
Нават пачаткоўцы праграмісты пачынаюць выдаваць суцэль прыстойны код праз тыдзень-іншую. Кніга, па якой я вывучаў “Go называецца Праграмаванне на мове Go” (аўтар Марк Самэрфілд). Кніга вельмі добрая, у ёй закранаюцца многія нюансы мовы. Пасля неапраўдана ўскладненых моў такіх, як Java, PHP, адсутнасць магіі дзейнічае асвяжальна. Але рана ці позна ў многіх абмежаваных праграмістаў узнікае выкарыстоўваць старыя метады на новай ніве. Ці сапраўды гэта так неабходна?

Роб Пайк (галоўны ідэолаг мовы) ствараў мову Go, як індустрыяльную мову, якая лёгкая ва ўспрыманні, эфектыўная ў выкарыстанні. Мова прызначаны для максімальнай прадуктыўнасці ў вялікіх камандах і сумнявацца ў гэтым не даводзіцца. Многія пачаткоўцы праграмісты скардзяцца, што ёсць многія фічы, якіх ім не хапае. Гэтае імкненне да прастаты было свядомым рашэннем распрацоўнікаў мовы і, для таго, каб цалкам зразумець для чаго гэта было трэба, мы павінны зразумець матывацыю распрацоўнікаў і чаго яны дамагаліся ў Go.

Дык для чаго ж ён быў створаны такім простым? Вось пара цытат Роба Пайка:

Ключавы момант тут, што нашы праграмісты не даследнікі. Яны, як правіла, вельмі маладыя, ідуць да нас пасля вучобы, магчыма вывучалі Java, ці C/C++, ці Python. Яны не ў стане зразумець выбітную мову, але ў той жа час мы жадаем, каб яны стваралі добрае ПЗ. Менавіта таму мова павінна простая для разумення і вывучэння.

Ён павінен быць знаёмым, груба кажучы падобным да Сі. Праграмісты якія працуюць у Google рана пачынаюць сваю кар'еру і ў большасці сваёй знаёмыя з працэдурнымі мовамі, у прыватнасці сямейства Сі. Патрабаванне ў хуткай прадуктыўнасці на новай мове праграмавання азначае, што мова не павінна быць занадта радыкальнай.

Мудрыя словы, ці не праўда?

Артэфакты прастаты

Прастата - неабходная ўмова выдатнага. Леў Талсты.

Быць простым - гэта адно з найважнейшых імкненняў у любым дызайне. Як вядома, дасканалы праект гэта не той праект, куды няма чаго дадаць, а той - у з якога няма чаго выдаліць. Многія лічаць, што для таго, каб вырашыць (ці нават выказаць) складаныя задачы, неабходна складаная прылада. Аднак гэта не так. Возьмем да прыкладу мову PERL. Ідэолагі мовы лічылі, што праграміст павінен мець як мінімум тры розныя шляхі для вырашэння адной задачы. Ідэолагі мовы Go пайшлі іншым шляхам, яны вырашылі, што для дасягнення мэты дастаткова аднаго шляху, але сапраўды добрага. Такі падыход мае пад сабой сур'ёзны падмурак: адзіны шлях лягчэй вучыцца і цяжэй забываецца.

Многія мігранты скардзяцца, што мова не змяшчае элегантных абстракцый. Так, гэта так, аднак гэта і ёсць адна з галоўных добрых якасцяў мовы. Мова змяшчае ў сваім складзе мінімум магіі - таму не патрабуецца глыбокіх ведаў для чытання праграмы. Што ж тычыцца шматслоўнасці кода, то гэта і зусім не праблема. Добра напісаная праграма на мове Golang чытаецца па вертыкалі, практычна без структуравання. Акрамя таго, хуткасць чытання праграмы як мінімум на парадак пераўзыходзіць хуткасць яе напісання. Калі ўлічыць, што ўвесь код мае аднастайнае фарматаванне (выкананае пры дапамозе ўбудаванай каманды gofmt), тое прачытаць некалькі лішніх радкоў наогул не з'яўляецца праблемай.

Не вельмі выразны

Мастацтва не церпіць, калі абмяжоўваюць яго свабоду. Дакладнасць не ўваходзіць у яго абавязкі.

З-за імкнення да прастаты ў Go адсутнічаюць канструкцыі, якія ў астатніх мовах успрымаюцца як нешта натуральнае, якія звыкнуліся да іх людзям. Спачатку гэта можа некалькі няёмкім, але затым заўважаеш, што праграма чытаецца ў разы прасцей і адназначней.

Напрыклад, кансольная ўтыліта, якая чытае stdin або файл з аргументаў каманднага радка, будзе выглядаць наступным чынам:

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

Рашэнне гэтай жа задачы на ​​мове 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);
    }
}

Пекла капіявання

Чалавек носіць пекла ў самім сабе. Марцін Лютар.

Навічкі пастаянна скардзяцца на 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 main() {

    list32 := []int32{1, 2, 3, 4, 5}
    list64 := []int64{1, 2, 3, 4, 5}

    fmt.Println(int32Sum(list32))
    fmt.Println(int64Sum(list64))
}

У мове ёсць дастатковыя сродкі для рэалізацыі падобных канструкцый. Напрыклад, суцэль падыдзе абагульненае праграмаванне.

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

І, хоць у нас код атрымаўся некалькі даўжэйшы за папярэдні выпадак, затое ён стаў абагульненым. Таму нам не складзе працы рэалізаваць усе арыфметычныя дзеянні.

Многія скажуць, што праграма на мове D выглядае істотна карацей і маюць рацыю.

import std.stdio;
import std.algorithm;

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

Аднак толькі карацей, але не правільней, паколькі ў рэалізацыі на D цалкам ігнаруецца праблема апрацоўкі памылак.

У рэальным жыцці, калі складанасць логікі ўзрастае, разрыў імкліва скарачаецца. Яшчэ хутчэй разрыў скарачаецца, калі патрабуецца выканаць дзеянне, якое не можа быць выканана пры дапамозе стандартных аператараў мовы.

З пункту гледжання падтрымлівасці, пашыральнасці, чытальнасці, па-мойму выйграе мова Go, хоць і прайграе па шматслоўнасці.

Абагульненае праграмаванне ў шэрагу выпадкаў дае нам бясспрэчную выгаду. Гэта наглядна ілюструе нам пакет sort. Так, для сартавання любога спісу нам дастаткова рэалізаваць інтэрфейс sort.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)
}

Калі Вы возьмеце любы open source праект і выканаеце каманду grep "interface{}" -R, то ўбачыце, як часта выкарыстоўваюцца шляхі інтэрфейсы. Недалёкія таварышы адразу ж скажуць, што ўсё гэта з-за адсутнасці джэнерыкаў. Аднак гэта далёка не заўсёды так. Возьмем да прыкладу мову DELPHI. Нягледзячы на ​​наяўнасць у яго гэтых самых джэнерыкаў, ён утрымоўвае адмысловы тып VARIANT для аперацый з адвольнымі тыпамі дадзеных. Аналагічна паступае і мова Go.

З гарматы па вераб'ях

І ўціхамірвальная кашуля павінна адпавядаць памеру вар'яцтва. Станіслаў Лец.

Многія аматары экстрыму могуць заявіць, што ў Go ёсць яшчэ адзін механізм для стварэння джэнерыкаў – рэфлексія. І яны маюць рацыю,… але толькі ў рэдкіх выпадках.

Роб Пайк папярэджвае нас:

Гэта магутная прылада, які павінен быць скарыстаны з асцярожнасцю. Яго варта пазбягаць пакуль у ім няма строгай неабходнасці.

Вікіпедыя кажа нам наступнае:

Рэфлексія азначае працэс, падчас якога праграма можа адсочваць і мадыфікаваць уласную структуру і паводзіны падчас выканання. Парадыгма праграмавання, пакладзеная ў аснову адлюстравання, называецца рэфлексіўным праграмаваннем. Гэта адзін з відаў метапраграмавання.

Аднак, як вядома, за ўсё трэба плаціць. У дадзеным выпадку гэта:

  • складанасць напісання праграм
  • хуткасць выканання праграм

Таму выкарыстоўваць рэфлексію трэба з асцярожнасцю, як прылады вялікага калібра. Бяздумнае ж выкарыстанне рэфлексіі прыводзіць да нечытальнасці праграм, пастаянным памылкам і нізкай хуткасці працы. Якраз самае тое, каб праграміст-сноб змог пафранціць сваім кодам перад іншымі, больш прагматычнымі і сціплымі калегамі.

Культурны багаж з Сі? Не, з шэрагу моў!

Разам са станам спадчыннікам пакідаюць і даўгі.

Нягледзячы на ​​тое, што шмат хто лічыць, што мова цалкам грунтуецца на спадчыне Сі — гэта не так. Мова ўвабрала ў сябе шматлікія аспекты лепшых моў праграмавання.

сінтаксіс

Першым чынам, сінтаксіс граматычных канструкцый засноўваецца на сінтаксісе мовы Сі. Аднак істотны ўплыў аказала і мова DELPHI. Так, мы бачым, што цалкам прыбраныя залішнія дужкі, якія так моцна зніжаюць чытальнасць праграмы. Таксама мова змяшчае аператар ":=", уласцівы мове DELPHI. Паняцце пакетаў запазычана з моў, падобных ADA. Дэкларацыя сутнасцяў, якія не выкарыстоўваюцца, запазычаная з мовы PROLOG.

семантыка

За аснову пакетаў была ўзятая семантыка мовы DELPHI. Кожны пакет інкапсулюе дадзеныя і код і ўтрымоўвае прыватныя і публічныя сутнасці. Гэта дазваляе скарачаць інтэрфейс пакета да мінімуму.

Аперацыя рэалізацыі метадам дэлегавання была запазычаная з мовы DELPHI.

Кампіляцыя

Нездарма ходзіць жарт: Go быў распрацаваны, пакуль кампілявалася праграма на Сі. Адной з моцных бакоў мовы з'яўляецца звышхуткая кампіляцыя. Ідэя была запазычаная з мовы DELPHI. Пры гэтым кожны пакет Go адпавядае модулю DELPHI. Гэтыя пакет перакампілююцца толькі пры рэальнай неабходнасці. Таму пасля чарговай праўкі не патрабуецца кампіляваць усю праграму, а дастаткова перакампіляваць толькі змененыя пакеты і пакеты, якія залежаць ад гэтых змененых пакетаў (ды і тое, толькі ў выпадку, калі змяніліся інтэрфейсы пакетаў).

Высокаўзроўневыя канструкцыі

Мова змяшчае мноства розных высокаўзроўневых канструкцый, ніяк не звязаных з нізкаўзроўневымі мовы тыпу Сі.

  • радкі
  • Хэш табліцы
  • Слайсы
  • Качыная тыпізацыя запазычаная з моў, падобных RUBY (якую, нажаль, шматлікія не разумеюць і не выкарыстоўваюць на поўную моц).

Упраўленне памяццю

Упраўленне памяццю наогул заслугоўвае асобнага артыкула. Калі ў мовах тыпу C++, кіраванне цалкам аддадзена на водкуп распрацоўніка, то ў пазнейшых мовах тыпу DELPHI, была скарыстана мадэль падліку спасылак. Пры такім падыходзе не дапушчалася цыклічных спасылак, паколькі ўтвараліся страчаныя кластара, то ў Go убудавана дэтэктаванне такіх кластараў (як у C#). Акрамя таго, па эфектыўнасці garbage collector пераўзыходзіць большасць вядомых на бягучы момант рэалізацый і ўжо можа быць скарыстаны для шматлікіх real time задач. Мова сама распазнае сітуацыі, калі значэнне для захоўвання зменнай можа быць выдзелена ў стэку. Гэта памяншае нагрузку на мэнэджар памяці і павялічвае хуткасць працы праграмы.

Паралельнасць і канкурэнтнасць

Паралельнасць і канкурэнтнасць мовы вышэй за ўсялякія пахвалы. Ніводная нізкаўзроўневая мова не можа нават падалена канкураваць з мовай Go. Дзеля справядлівасці, варта адзначыць, што мадэль не была вынайдзена аўтарамі мовы, а проста запазычаная са старой добрай мовы ADA. Мова здольны апрацоўваць мільёны паралельных злучэнняў задзейнічаючы ўсе CPU, маючы пры гэтым на парадак радзей тыповыя для шматструменнага кода складаныя праблемы з дзядлакамі і race conditions.

Дадатковыя выгады

Калі гэта будзе выгадна - бескарыслівымі стануць усё.

Мова таксама дае нам таксама шэраг несумненных выгод:

  • Адзіны выканальны файл пасля зборкі праекта істотна спрашчае deploy прыкладанні.
  • Статычная тыпізацыя і вывад тыпаў дазваляюць істотна скараціць колькасць памылак у кодзе нават без напісання тэстаў. Я ведаю некаторых праграмістаў, якія ўвогуле абыходзяцца без напісання тэстаў і пры гэтым якасць іх кода істотна не пакутуе.
  • Вельмі простая крос-кампіляцыя і выдатная партабельнасць стандартнай бібліятэкі, што моцна спрашчае распрацоўку крос-платформавых прыкладанняў.
  • Рэгулярныя выразы RE2 струменебяспечныя і з прадказальным часам выканання.
  • Магутная стандартная бібліятэка, што дазваляе ў большасці праектаў абыходзіцца без іншых фрэймворкаў.
  • Мова дастаткова магутная, каб канцэнтравацца на задачы, а не на метадах яе вырашэння і ў той жа час дастаткова нізкаўзроўневая, каб задачу можна было вырашыць эфектыўна.
  • Эка сістэма Go змяшчае ўжо са скрынкі развіты інструментарый на ўсе выпадкі жыцця: тэсты, дакументацыя, кіраванне пакетамі, магутныя лінтэры, кодагенерацыя, дэтэктар race conditions і г. д.
  • У Go версіі 1.11 з'явілася ўбудаванае семантычнае кіраванне залежнасцямі, пабудаванае па-над папулярнымі хостынгамі VCS. Усе прылады, якія ўваходзяць у склад экасістэмы Go выкарыстоўваюць гэтыя сэрвісы, каб спампоўваць, збіраць і ўсталёўваць з іх код адным махам. І гэта выдатна. З прыходам версіі 1.11 таксама цалкам вырашылася праблема з версаваннем пакетаў.
  • Паколькі асноўнай ідэяй мовы з'яўляецца памяншэнне магіі, мова стымулюе распрацоўшчыкаў выконваць апрацоўку памылак відавочна. І гэта правільна, паколькі ў адваротным выпадку, ён проста будзе забывацца ўвогуле пра апрацоўку памылак. Іншая справа, што большасць распрацоўнікаў свядома ігнарую апрацоўку памылак, аддаючы перавагу замест іх апрацоўкі, проста прабіваць памылку ўверх.
  • Мова не рэалізуе класічнай метадалогіі ААП, паколькі ў чыстым выглядзе ў Go няма віртуальнасці. Аднак, гэта не праблема пры выкарыстанні інтэрфейсаў. Адсутнасць ААП істотна зніжае ўваходны бар'ер для пачаткоўцаў.

Прастата для выгады супольнасці

Ускладняць проста, спрашчаць складана.

Go быў распрацаваны, каб быць простым і ён атрымаў поспех у гэтай мэты. Ён быў напісаны для разумных праграмістаў, якія разумеюць усе добрыя якасці каманднай працы і стаміліся ад бясконцай варыябельнасці моў Enterprise ўзроўню. Маючы ў сваім арсенале адносна невялікі набор сінтаксічных канструкцый, ён практычна не схільны зменам з цягам часу, таму ў распрацоўнікаў вызваляецца маса часу менавіта для распрацоўкі, а не для бясконцага вывучэння новаўвядзенняў мовы.

Кампаніі ж атрымліваюць таксама шэраг пераваг: нізкі парог уваходжання дазваляе хутчэй знайсці спецыяліста, а нязменнасць мовы дазваляе выкарыстоўваць той жа код і праз 10 гадоў.

Заключэнне

Вялікі памер мозгу яшчэ не зрабіў ніводнага слана лаўрэатам Нобелеўскай прэміі.

Для тых праграмістаў, у якіх асабістае эга пераважае над камандным духам, а таксама тэарэтыкаў, якія любяць акадэмічныя задачы і бясконцае «самаўдасканаленне», мова сапраўды дрэнная, паколькі гэта рамесніцкая мова агульнага прызначэння, якая не дазваляе атрымаць эстэтычнага задавальнення ад выніку сваёй працы і паказаць сябе прафесіяналам перад калегамі (пры ўмове, што мы вымяраем розум менавіта гэтымі крытэрамі, а не каэфіцыентам IQ). Як і ўсё ў жыцці - гэта пытанне асабістых прыярытэтаў. Як і ўсе вартыя навінкі, мова ўжо прарабіла дастатковы шлях ад усеагульнага адмаўлення да масавага прызнання. Мова геніяльны па сваёй прастаце, а, як вядома, усё геніяльнае - проста!

Крыніца: habr.com

Дадаць каментар