Это презентация, которой поделился Sameer Ajmani, технический руководитель команды Go в Google, подготовленная для программистов Java, чтобы они могли быстро начать работу с Go.

为 Java 程序员准备的 Go 语言入门 PPT

Видео

Эта презентация была использована 23 апреля 2015 года наNYJavaSIGиспользовалась в.

Перейдите на YouTube, чтобы посмотреть видео

Основное содержание

1. Что такое Go и кто использует Go?
2. Сравнение Go и Java
3. Примеры кода
4. Конкурентность
5. Инструменты

Что такое Go?

"Go — это язык программирования с открытым исходным кодом, который позволяет легко создавать простое, надёжное и эффективное программное обеспечение."

golang.org

История Go

Разработка началась во второй половине 2007 года

  • Роберт Гриземер, Роб Пайк и Кен Томпсон.
  • Иэн Лэнс Тейлор и Расс Кокс

Открытый исходный код с 2009 года, очень активное сообщество.

Стабильная версия Go 1 была выпущена в начале 2012 года.

Почему Go?

Go — это решение для масштаба Google.

Масштаб системы

  • Масштаб, рассчитанный на 10⁶⁺ машин
  • Задачи выполняются на тысячах машин каждый день
  • Сотрудничает и взаимодействует с другими задачами в системе
  • Одновременно происходит много работы

Решение: очень сильная поддержка конкурентности

Вторая проблема: масштаб разработки

В 2011 году

  • 5000+ разработчиков в 40+ офисах
  • 20+ изменений в минуту
  • 50% кодовой базы изменяется каждый месяц
  • 50 миллионов тестовых случаев выполняется в день
  • Единое дерево кода

Решение: язык, спроектированный для больших кодовых баз

Кто использует Go в Google?

Множество проектов, тысячи программистов на Go и миллионы строк кода на Go.

Публичные примеры:

  • SPDY-прокси Chrome для мобильных устройств
  • Серверы загрузок для Chrome, ChromeOS, Android SDK, Earth и т. д.
  • Балансировщик MySQL Vitess для YouTube

Его основные задачи — веб-серверы, но это язык общего назначения.

Кто ещё использует Go, кроме Google?

golang.org/wiki/GoUsers

Apcera, Bitbucket, bitly, Canonical, CloudFlare, Core OS, Digital Ocean, Docker, Dropbox, Facebook, Getty Images, GitHub, Heroku, Iron.io, Kubernetes, Medium, сервисы MongoDB, сервисы Mozilla, New York Times, pool.ntp.org, Secret, SmugMug, SoundCloud, Stripe, Square, Thomson Reuters, Tumblr, ...

Сравнение Go и Java

У Go и Java много общего

  • Семейство C (строгая типизация, фигурные скобки)
  • Статическая типизация
  • Сборка мусора
  • Безопасность памяти (nil-ссылки, проверка границ во время выполнения)
  • Переменные всегда инициализируются (нулевым значением/nil/false)
  • Методы
  • Интерфейсы
  • Утверждения типа (экземпляры)
  • Рефлексия

Различия между Go и Java

  • Программы компилируются напрямую в машинный код, без виртуальной машины
  • Статически связанные бинарные файлы
  • Управление размещением в памяти
  • Функциональные значения и лексические замыкания
  • Встроенные строки (UTF-8)
  • Встроенные обобщённые map и массивы/срезы
  • Встроенная конкурентность

Go намеренно исключает ряд возможностей

  • Нет классов
  • Нет конструкторов
  • Нет наследования
  • Нет final
  • Нет исключений
  • Нет аннотаций
  • Нет пользовательских дженериков

Почему в Go отсутствуют эти возможности?

Ясность кода превыше всего

Читая код, вы можете чётко понять, что будет делать программа

При написании кода вы также можете ясно добиться того, чтобы программа делала то, что вы хотите

Иногда это означает написать цикл вместо вызова непонятной функции.

(Не будьте слишком скучными)

Для подробной информации о дизайне см.:

Примеры

Программистам на Java Go должен показаться знакомым

Main.java

public class Main {
    public static void main(String[] args) {
        System.out.println("Hello, world!");
    }
}

hello.go

package main
import "fmt"
func main() {
    fmt.Println("Hello, 世界!")
}

Привет, веб-сервер

package main

import (
    "fmt"
    "log"
    "net/http"
)
func main() {
    http.HandleFunc("/hello", handleHello)
    fmt.Println("serving on http://localhost:7777/hello")
    log.Fatal(http.ListenAndServe("localhost:7777", nil))
}
func handleHello(w http.ResponseWriter, req *http.Request) {
    log.Println("serving", req.URL)
    fmt.Fprintln(w, "Hello, 世界!")
}

Тип (доступа) объявляется на основе имени переменной.
Имена публичных переменных начинаются с заглавной буквы, имена приватных переменных — со строчной.

Пример: GoogleFrontend


func main() {
    http.HandleFunc("/search", handleSearch)
    fmt.Println("serving on http://localhost:8080/search")
    log.Fatal(http.ListenAndServe("localhost:8080", nil))
}
// handleSearch handles URLs like "/search?q=golang" by running a
// Google search for "golang" and writing the results as HTML to w.
func handleSearch(w http.ResponseWriter, req *http.Request) {

Валидация запроса

func handleSearch(w http.ResponseWriter, req *http.Request) {
    log.Println("serving", req.URL)
    // Check the search query.
    query := req.FormValue("q")
    if query == "" {
        http.Error(w, `missing "q" URL parameter`, http.StatusBadRequest)
        return
    }

FormValue — метод *http.Request:

package http
type Request struct {...}
func (r *Request) FormValue(key string) string {...}

`query := req.FormValue("q")` инициализирует переменную query, тип которой — результат выражения справа, здесь тип string.

Получение результатов поиска

// Run the Google search.
    start := time.Now()
    results, err := Search(query)
    elapsed := time.Since(start)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

Метод Search возвращает два значения: results и error.

func Search(query string) ([]Result, error) {...}

Когда значение error равно nil, results корректны.

type error interface {
    Error() string // a useful human-readable error message
}

Тип Error может содержать дополнительную информацию, доступ к которой можно получить через утверждение типа.

Отображение результатов поиска

// Render the results.
    type templateData struct {
        Results []Result
        Elapsed time.Duration
    }
    if err := resultsTemplate.Execute(w, templateData{
        Results: results,
        Elapsed: elapsed,
    }); err != nil {
        log.Print(err)
        return
    }

results используют Template.Execute для генерации HTML и записи в io.Writer:

type Writer interface {
        Write(p []byte) (n int, err error)
}

http.ResponseWriter реализует интерфейс io.Writer.

Переменные Go управляют HTML-шаблонами

// A Result contains the title and URL of a search result.
type Result struct {
    Title, URL string
}
var resultsTemplate = template.Must(template.New("results").Parse(`
<html>
<head/>
<body>
  <ol>
  {{range .Results}}
    <li>{{.Title}} - <a href="{{.URL}}.html">{{.URL}}</a></li>
  {{end}}
  </ol>
  <p>{{len .Results}} results in {{.Elapsed}}</p>
</body>
</html>
`))

Запрос к Google Search API

func Search(query string) ([]Result, error) {
    // Prepare the Google Search API request.
    u, err := url.Parse("https://ajax.googleapis.com/ajax/services/search/web?v=1.0")
    if err != nil {
        return nil, err
    }
    q := u.Query()
    q.Set("q", query)
    u.RawQuery = q.Encode()
    // Issue the HTTP request and handle the response.
    resp, err := http.Get(u.String())
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()

Оператор defer вызывает выполнение resp.Body.Close при возврате метода Search.

Разбор возвращённых данных JSON в типы структур Go

developers.google.com/web-search/docs/#fonje

var jsonResponse struct {
        ResponseData struct {
            Results []struct {
                TitleNoFormatting, URL string
            }
        }
    }
    if err := json.NewDecoder(resp.Body).Decode(&jsonResponse); err != nil {
        return nil, err
    }
    // Extract the Results from jsonResponse and return them.
    var results []Result
    for _, r := range jsonResponse.ResponseData.Results {
        results = append(results, Result{Title: r.TitleNoFormatting, URL: r.URL})
    }
    return results, nil
}

Это фронтенд для него

Все используемые пакеты берутся из стандартной библиотеки:

import (
    "encoding/json"
    "fmt"
    "html/template"
    "log"
    "net/http"
    "net/url"
    "time"
)

Масштабируемость сервера Go: каждый запрос выполняется в собственной горутине.

Давайте поговорим о конкурентности.

Взаимодействующие последовательные процессы (Хоар, 1978)

Конкурентные программы как независимые процессы, выполняющиеся последовательно посредством обмена сообщениями.

Последовательное выполнение легко понять; асинхронное — нет.

"Не общайтесь, разделяя память; разделяйте память, общаясь."

Принципы Go:горутины, каналы и операторы select.

Горутины

Горутины похожи на легковесные потоки.

Они используют небольшиестеки (крошечные стеки) и работают с настройкой по требованию.

Программы на Go могут иметь тысячи и тысячи(горутин)-экземпляров.

Используйте оператор go для запуска горутины:

go f(args)

Среда выполнения Go помещает горутины в потоки ОС.

Не используйте потоки для блокировки горутин.

Каналы

Каналы предназначены для взаимодействия между горутинами.

c := make(chan string)
 
// goroutine 1
c <- "hello!"
 
// goroutine 2
s := <-c
fmt.Println(s) // "hello!"

Выбор

Оператор select объявляет блок для определения выполнения.

select {
case n := <-in:
  fmt.Println("received", n)
case out <- v:
  fmt.Println("sent", v)
}

Выполнится только блок case, условие которого истинно.

Пример: Google Search (бэкенд)

Вопрос: Что может делать Google Search?

Ответ: Задайте вопрос, и он возвращает страницу результатов поиска (и немного рекламы).

Вопрос: Как мы получаем эти результаты поиска?

Ответ: Отправить запрос в веб-поиск, поиск изображений, YouTube (видео), карты, новости, подождать, а затем получить результаты.

Как нам это реализовать?

Поиск Google: фейковый фреймворк

Мы смоделируем функцию поиска, которая случайным образом завершается по тайм-ауту в пределах от 0 до 100 миллисекунд.

var (
    Web   = fakeSearch("web")
    Image = fakeSearch("image")
    Video = fakeSearch("video")
)
type Search func(query string) Result
func fakeSearch(kind string) Search {
    return func(query string) Result {
        time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)
        return Result(fmt.Sprintf("%s result for %q\n", kind, query))
    }
}

Поиск Google: тестовый фреймворк

func main() {
    start := time.Now()
    results := Google("golang")
    elapsed := time.Since(start)
    fmt.Println(results)
    fmt.Println(elapsed)
}

Поиск Google (последовательный)

Функция Google принимает запрос и возвращает набор результатов (не обязательно строк).

Google последовательно вызывает Web, Image и Video и добавляет результаты в набор результатов.

func Google(query string) (results []Result) {
    results = append(results, Web(query))
    results = append(results, Image(query))
    results = append(results, Video(query))
    return
}

Поиск Google (параллельный)

Запустить поиск Web, Image и Video одновременно и дождаться всех результатов.

Метод func замкнут над query и c.

func Google(query string) (results []Result) {
    c := make(chan Result)
    go func() { c <- Web(query) }()
    go func() { c <- Image(query) }()
    go func() { c <- Video(query) }()
    for i := 0; i < 3; i++ {
        result := <-c
        results = append(results, result)
    }
    return
}

Google Search (тайм-аут)

Ожидание медленных серверов.

Никаких блокировок, никаких условных переменных, никаких возвращаемых значений.

    c := make(chan Result, 3)
    go func() { c <- Web(query) }()
    go func() { c <- Image(query) }()
    go func() { c <- Video(query) }()
    timeout := time.After(80 * time.Millisecond)
    for i := 0; i < 3; i++ {
        select {
        case result := <-c:
            results = append(results, result)
        case <-timeout:
            fmt.Println("timed out")
            return
        }
    }
    return

Избегание тайм-аута

Вопрос: Как не потерять результаты от медленных сервисов?

Ответ: Реплицируйте сервис, отправляйте запросы нескольким реплицированным сервисам и используйте результат первого ответа.

func First(query string, replicas ...Search) Result {
    c := make(chan Result, len(replicas))
    searchReplica := func(i int) { c <- replicas[i](query) }
    for i := range replicas {
        go searchReplica(i)
    }
    return <-c
}

Использование функции First

func main() {
    start := time.Now()
    result := First("golang",
        fakeSearch("replica 1"),
        fakeSearch("replica 2"))
    elapsed := time.Since(start)
    fmt.Println(result)
    fmt.Println(elapsed)
}

Google Search (репликация)

Используйте реплицированные сервисы, чтобы уменьшить избыточную задержку.

    c := make(chan Result, 3)
    go func() { c <- First(query, Web1, Web2) }()
    go func() { c <- First(query, Image1, Image2) }()
    go func() { c <- First(query, Video1, Video2) }()
    timeout := time.After(80 * time.Millisecond)
    for i := 0; i < 3; i++ {
        select {
        case result := <-c:
            results = append(results, result)
        case <-timeout:
            fmt.Println("timed out")
            return
        }
    }
    return

Другое

Никаких блокировок, никаких условных переменных, никаких вызовов.

Итог

С помощью нескольких простых преобразований мы используем примитивы конкурентности Go, чтобы превратить

  • Медленная
  • последовательный
  • чувствительный к сбоям

программу в

  • Высокая
  • конкурентный
  • переиспользуемый
  • устойчивый

Инструменты

В Go есть много мощных инструментов

  • gofmt и goimports
  • Инструмент go
  • godoc
  • Поддержка IDE и редакторов

Язык разработан для инструментальной цепочки.

gofmt и goimports

Gofmt может автоматически форматировать код без опций.

Goimports обновляет объявления импорта на основе вашего рабочего пространства.

Большинство людей могут безопасно использовать эти инструменты.

play.golang.org/p/GPqra77cBK

Инструмент go

Инструмент go tool может собирать программы Go из исходного кода в стандартной структуре каталогов. Makefile или другая конфигурация не требуются.

Загрузите эти инструменты и их зависимости, затем соберите и установите:

% go get golang.org/x/tools/cmd/present

Запустите:

% present

godoc

Создавайте документацию для всего открытого кода Go в мире:

godoc.org

Поддержка IDE и редакторов

Eclipse, IntelliJ, emacs, vim и т. д.:

  • gofmt
  • goimports
  • godoclookups
  • автодополнение кода
  • навигация по коду

Но нет "Go IDE".

Инструменты Go повсюду.

Дальнейшие шаги Go

Просмотрите дорожную карту Go онлайн.

tour.golang.org

Множество учебных ресурсов.

golang.org/wiki/Learn

Отличное сообщество.

golang.org/project

Спасибо

Sameer Ajmani

Технический лид-менеджер, команда Go

Google

@Sajma

sameer@golang.org