В этой статье хотелось бы поделиться одним из способов простого и удобного интеграционного тестирования http-сервиса, написанного на Go. Интеграционные тесты бывает непросто создавать так, чтобы обходиться без сложных скриптов, но на помощь нам придет Docker, пакет из стандартной библиотеки httptest и билд-теги. Для примера мы будем использовать MySQL базу данных с миграциями, управляемыми пакетом goose. Финальной целью является получить простое и удобное кроссплатформенное интеграционное тестирование простым запуском команды go test, будь это рабочий ноутбук или Continuous Integration сервер.

image

Основы


Итак, для начала, вспомним, как Go относится к тестированию. Поскольку авторы Go делают акцент на том, что Go стимулирует хорошие практики — а тестирование — это одна, из пожалуй, самых хороших практик для программистов вообще, то тестирование в Go — неотъемлемая часть инструментария и идёт из коробки. Кроме того, чтобы им воспользоваться, нужно запомнить самый минимум.

Вот этот минимум:
  • команда для запуска тестов — go test
  • тесты находятся в файлах *_test.go. Код в этих файлах не используется при сборке, только при тестах.
  • функция, которая называется TestXxx(t *testing.T) будет запускаться во время тестирования
  • в пакете стандартной библиотеки testing есть все необходимое для тестов


Скажем, если вы написали функцию для подсчета площади круга, тест на Go будет выглядеть вот так:
package main

import (
	"math"
	"testing"
)

func TestCircle(t *testing.T) {
	area := CircleArea(10)
	want := 100 * math.Pi
	if area != want {
		t.Fatalf("Want %v, but got %v", want, area)
	}
}

Конечно, тем, кто привык к умным assert-ам и оберткам для тестирования, упрощающим проверки, такой код может показаться слишком многословным. Но «умные сравнение» разных типов это не такая уж тривиальная задача, и отдана полностью на откуп сторонним пакетам, о которых я расскажу чуть ниже. Такой же минималистичный подход позволяет с минимальным порогом входа начать писать тесты по поводу и без повода. Многие даже утверждают, что полюбили TDD (Testing Driven Development) именно в Go. Как бы, уже нет оправданий, чтобы не писать тесты — это стало слишком просто.

При этом, чтобы вы понимали, под капотом там происходят достаточно сложные вещи. go test берет ваш код, помещает его во временную директорию, модифицирует так, чтобы получилась самодостаточная программа, запускающая тесты, компилирует, запускает и выводит результаты с подсчетом времени. Всё это происходит за доли секунды, и такой подход стал возможным и удобным только благодаря простой грамматике языка и сверхбыстрой компиляции.

go test умеет из коробки еще много чего, включая запись покрытия (coverage), профайлинга памяти и процессора, такие же простые бенчмарки (func BenchmarkXxx), параллельное исполнение, рейс-детектор и много чего другого. Обо всем можно узнать выполнив команды go help test и go help testflag.

Тестинг-фреймворки


Разумеется для больших программ, есть смысл использовать более мощные способы тестирования. Для Go существует множество фреймворков, которые легко и просто подключаются к вашим тестам, и совместимы с командой go test. Мне больше всего нравится GoConvey, добавляющий DSL-подобный синтаксис для BDD-тестов. Вышеприведенный пример будет выглядеть с GoConvey вот так:
package main

import (
	"math"
	"testing"
        . "github.com/smartystreets/goconvey/convey"
)

func TestCircle(t *testing.T) {
        Convey("Circle should work correctly", t, func() {
		Convey("Area should be calculated correctly", func() {
			area := CircleArea(10)
			So(area, ShouldEqual, 100 * math.Pi)
		})
	})
}



GoConvey умеет делать массу assertions, в том числе для глубокого сравнения структур и сложных типов, умеет работать со временем и так далее. Если начнёте его использовать, убедитесь, что прочитали про порядок выполнения вложенных Convey-функций — это важная фишка.

Как бонус, у Goconvey есть навороченный веб UI, который умеет мониторить изменения в коде и перезапускать тесты, присылать Desktop-уведомления и, вообще, выглядит как панель управления запуском шаттла. Очень круто на самом деле, удобно вынести на второй монитор. Как многие отзываются, GoConvey заставит вас полюбить тестирование еще больше :)


Есть еще популярные фреймворки вроде Ginkgo, testify, gocheck, Agouti, GoMega и другие. Вот тут есть неплохое сравнение.

Как видите, подход Go по принципу «самое необходимое — из коробки, все остальное — на откуп коммьюнити» как нельзя лучше себя оправдывает в тестировании.

Интеграционные тесты


Как уже отмечалось выше, интеграционные тесты, которые подразумевают тестирование всей системы в целом, а не отдельных частей кода, могут быть непростой задачей. Зачастую они требуют сложных скриптов, которые далеко не всегда кроссплатформенны, занимают много времени и так далее. Но и тут с Go подобные задачи становятся гораздо проще.

Я рассмотрю следующий пример:
  • веб-бекенд, с использованием фреймворка gin
  • данные для сервиса — в MySQL базе данных
  • схема базы строится с использованием утилиты для миграций — goose

Это может быть как типичный REST-бекенд, так и какой-нибудь сервис, следующий принципам 12-factor app, зависимостей и сервисов может быть намного больше. Сейчас цель — показать подход.

Для внешних сервисов (в данном случае MySQL-базы) я буду использовать, как это ни банально, Docker. Пока весь мир рассказывает друг-другу, что Docker — это не панацея и не нужно его использовать там где не нужно (и истину говорит же), применение контейнеров для быстрого поднятия зависимостей в интеграционных тестах — это самое оно.

Миграции и Dockerfile


Сначала займемся не-Go частью, а именно написанием Dockerfile и разберемся, как работать с миграциями.

goose — это бинарный файл, который при запуске ищет директорию db/, а в ней:
  • файл dbconf.yml
  • папку migrations/

В yaml-файле описаны различные конфигурации баз данных, с которыми goose сможет работать, а в папке migrations/ — SQL-код, созданный с помощью goose create. На страничке проекта это расписано более детально, я не буду подробно останавливаться.

Наша задача — создать контейнер с MySQL, при билде контейнера стартануть его, поднять миграции до последней версии с помощью команды goose up, и подготовить контейнер для дальнейшей работы.
Dockerfile может выглядеть вот так:
Dockerfile
FROM debian

ENV DEBIAN_FRONTEND noninteractive
RUN apt-get update
RUN apt-get install -y mysql-server

RUN sed -i -e«s/^bind-address\s*=\s*127.0.0.1/bind-address = 0.0.0.0/» /etc/mysql/my.cnf

RUN apt-get install -y golang git ca-certificates gcc
ENV GOPATH /root
RUN go get bitbucket.org/liamstask/goose/cmd/goose

ADD. /db
RUN \
service mysql start && \
sleep 10 && \
while true; do mysql -e «SELECT 1» &> /dev/null; [ $? -eq 0 ] && break; echo -n "."; sleep 1; done && \
mysql -e «GRANT ALL ON *.* to 'root'@'%'; FLUSH PRIVILEGES;» && \
mysql -e «CREATE DATABASE mydb DEFAULT COLLATE utf8_general_ci;» && \
/root/bin/goose -env=default up && \
service mysql stop

EXPOSE 3306
CMD [«mysqld_safe»]

Собираем контейнер командой «docker build -t mydb_test .» Теперь, при запуске docker run -p 3306:3306 mydb_test — мы получим свежезапущенную базу, с последними миграциями и в свежем состоянии.

Пишем Go тесты


Для начала, поставим билд-тег, чтобы этот тест не запускался каждый раз, а только когда мы принудительно попросим запустить «интеграционные тесты».
Начнем наш service_test.go:
// +build integration

package main

import (
    "testing"
)

Теперь, обычный вызов go test не будет трогать именно этот тест, а go test -tag=integration — будет. К слову, в go test есть режим -short — можно использовать его, только он, наоборот, по умолчанию выключен:
if testing.Short() {
        t.Skip("skipping test in short mode.")
}


Поднимаем Docker-контейнер из тестов


Первым делом, мы захотим поднимать наш Docker-контейнер при старте теста. Для Docker есть удобные Go-библиотеки, я воспользуюсь клиентом от fsouza, который пользую уже больше 1.5 лет. Чтобы запустить контейнер, нужно выполнить три шага:
    client, err := docker.NewClientFromEnv()
    if err != nil {
        t.Fatalf("Cannot connect to Docker daemon: %s", err)
    }
    c, err := client.CreateContainer(createOptions())
    if err != nil {
        t.Fatalf("Cannot create Docker container: %s", err)
    }
    defer func() {
        if err := client.RemoveContainer(docker.RemoveContainerOptions{
            ID:    c.ID,
            Force: true,
        }); err != nil {
            t.Fatalf("cannot remove container: %s", err)
        }
    }()

    err = client.StartContainer(c.ID, &docker.HostConfig{})
    if err != nil {
        t.Fatalf("Cannot start Docker container: %s", err)
    }

createOptions() возвращает структуру с параметрами для операции создания контейнера. Именно там мы и указываем имя нашего контейнера, который будет использоваться для тестирования — mydb_test.
код этих функций
func сreateOptions() docker.CreateContainerOptions {
ports := make(map[docker.Port]struct{})
ports[«3306»] = struct{}{}
opts := docker.CreateContainerOptions{
Config: &docker.Config{
Image: «mydb_test»,
ExposedPorts: ports,
},
}

return opts
}


Всё что нам остается, это написать код, который будет ждать поднятия базы, и возвращать IP адрес или сразу отформатированный DSN для использования с mysql-драйвером Go.
    // wait for container to wake up
    if err := waitStarted(client, c.ID, 5*time.Second); err != nil {
        t.Fatalf("Couldn't reach MySQL server for testing, aborting.")
    }
    c, err = client.InspectContainer(c.ID)
    if err != nil {
        t.Fatalf("Couldn't inspect container: %s", err)
    }

    // determine IP address for MySQL
    ip = strings.TrimSpace(c.NetworkSettings.IPAddress)

    // wait MySQL to wake up
    if err := waitReachable(ip+":3306", 5*time.Second); err != nil {
        t.Fatalf("Couldn't reach MySQL server for testing, aborting.")
    }

    // pass IP to DB connect code

Код не сильно интересный, поэтому также спрячу под спойлер:
wait code
// waitReachable waits for hostport to became reachable for the maxWait time.
func waitReachable(hostport string, maxWait time.Duration) error {
    done := time.Now().Add(maxWait)
    for time.Now().Before(done) {
        c, err := net.Dial("tcp", hostport)
        if err == nil {
            c.Close()
            return nil
        }
        time.Sleep(100 * time.Millisecond)
    }
    return fmt.Errorf("cannot connect %v for %v", hostport, maxWait)
}

// waitStarted waits for container to start for the maxWait time.
func waitStarted(client *docker.Client, id string, maxWait time.Duration) error {
    done := time.Now().Add(maxWait)
    for time.Now().Before(done) {
        c, err := client.InspectContainer(id)
        if err != nil {
           break
        }
        if c.State.Running {
            return nil
        }
        time.Sleep(100 * time.Millisecond)
    }
    return fmt.Errorf("cannot start container %s for %v", id, maxWait)
}


Всего этого достаточно, чтобы двигаться дальше, но есть один момент — я хочу, чтобы этот код работал и на MacOS X, и на Windows, а это значит, что нужно уметь отличать Linux и не-Linux среду, и уметь поддерживать docker-machine или boot2docker (если кто ещё не переехал на docker-machine).

К счастью, это тоже тривиальная задача — необходимо всего лишь несколько функций. Для того, чтобы узнать IP адрес виртуальной машины, в которой запущен Docker. можно использовать вот такой код:
// DockerMachineIP returns IP of docker-machine or boot2docker VM instance.
//
// If docker-machine or boot2socker is running and has IP, it will be used to
// connect to dockerized services (MySQL, etc).
//
// Basically, it adds support for MacOS X and Windows.
func DockerMachineIP() string {
	// Docker-machine is a modern solution for docker in MacOS X.
	// Try to detect it, with fallback to boot2docker
	var dockerMachine bool
	machine := os.Getenv("DOCKER_MACHINE_NAME")
	if machine != "" {
		dockerMachine = true
	}

	var buf bytes.Buffer

	var cmd *exec.Cmd
	if dockerMachine {
		cmd = exec.Command("docker-machine", "ip", machine)
	} else {
		cmd = exec.Command("boot2docker", "ip")
	}
	cmd.Stdout = &buf

	if err := cmd.Run(); err != nil {
		// ignore error, as it's perfectly OK on Linux
		return ""
	}

	return buf.String()
}

Также придется передавать параметры проброса портов в CreateContainerOptions.

В итоге, будет удобней вынести весь этот код в отдельный пакет, в отдельную поддиректорию. Чтобы не делать этот пакет доступным наружу, я положу его в подкаталог internal — это гарантирует, что только мой пакет сможет его (пере)использовать.

Код этого пакета целиком: pastebin.com/faUUN0M1

Теперь его можно смело импортировать в наш проект, в код для тестирования и одной функцией получать готовый DSN для подключения.
    // start db in docker container
    dsn, deferFn, err := dockertest.StartMysql()
    if err != nil {
        t.Fatalf("cannot start mysql in container for testing: %s", err)
    }
    defer deferFn()

    db, err := sql.Open("mysql", dsn)
    if err != nil {
        t.Fatalf("Couldn't connect to test database: %s", err)
    }
    defer db.Close()

И передавать db в дальнейший код, который будет работать с базой данных. Обратите внимание, что функцию deferFn() мы вызываем, как положено, но даже понятия не имеем, что она делает — это на совести пакета dockertest, который знает, как почистить после себя и удалить контейнер.

Тестируем http-запросы


Следующим шагом у нас стоит проверка http-запросов — возвращают ли они нужные коды ошибок, возвращают ли ожидаемые данные, передают ли необходимые заголовки и тому подобное. Конечно, можно запустить реальный сервис, и запускать «снаружи» curl-запросы, но это неуклюже, неудобно и некрасиво. В Go есть великолепный способ тестировать http-хендлеры — это пакет net/http/httptest

httptest был, пожалуй, одним из первых моментов, которые произвели на меня вау-эффект в Go. Сама архитектура построения http-приложений в Go и без того может вызвать подобный эффект, но тут было совсем удачно. Как устроен пакет net/http я в этой статье не буду рассказывать, это материал для отдельной статьи, но вкратце — есть стандартный интерфейс http.Handler, которому удовлетворяет любой тип, у которого есть метод ServeHttp(http.ResponseWriter, *http.Request):
type Handler interface {
        ServeHTTP(ResponseWriter, *Request)
}

Веб-фреймворк gin, как и подобает всем цивилизованным http-фреймворкам в Go, реализует эти интерфейсы, поэтому для его тестирования мы можем легко сконструировать произвольные объекты, удовлетворяющие http.ResponseWriter (это тоже интерфейс), передать нужный Request и смотреть на ответ! При этом не понадобится открывать никаких внешних портов, всё будет происходить в адресном пространстве программы-теста. И это очень круто.

Вот как это выглядит (я сразу буду использовать вышеописанный GoConvey):
func NewServer(db *sql.DB) *gin.Engine {
    r := gin.Default()
    r.Use(cors.Middleware(cors.Options{}))
    // more middlewares ...

    // Health check
    r.GET("/ping", ping)

    // CRUD resources
    usersRes := &UsersResource{db: db}

    // Define routes
    api := r.Group("/api")
    {
        v1 := api.Group("/v1")
        {
            rest.CRUD(v1, "/users", usersRes)
        }
    }

    return r
}
...
   r := NewServer(db)
   Convey("Users endpoints should respond correctly", t, func() {
        Convey("User should return empty list", func() {
            // it's safe to ignore error here, because we're manually entering URL
            req, _ := http.NewRequest("GET", "http://localhost/api/v1/users", nil)
            w := httptest.NewRecorder()
            r.ServeHTTP(w, req)

            So(w.Code, ShouldEqual, http.StatusOK)
            body := strings.TrimSpace(w.Body.String())
            So(body, ShouldEqual, "[]")
        })
   })

Теперь можно добавить еще вызовы, и проверять состояние — скажем, добавить пользователя, и проверить снова список:
    Convey("Create should return ID of newly created user", func() {
            user := &User{Name: "Test user"}
            data, err := json.Marshal(user)
            So(err, ShouldBeNil)
            buf := bytes.NewBuffer(data)
            req, err := http.NewRequest("POST", "http://localhost/api/v1/users", buf)
            So(err, ShouldBeNil)
            w := httptest.NewRecorder()
            r.ServeHTTP(w, req)

            So(w.Code, ShouldEqual, http.StatusOK)
            body := strings.TrimSpace(w.Body.String())
            So(body, ShouldEqual, "1")
    })
    Convey("List should return one user with name 'Test user'", func() {
            req, _ := http.NewRequest("GET", "http://localhost/api/v1/users", nil)
            w := httptest.NewRecorder()
            r.ServeHTTP(w, req)

            So(w.Code, ShouldEqual, http.StatusOK)
            body := w.Body.Bytes()
            var users []*User
            err := json.Unmarshal(body, &users)
            So(err, ShouldBeNil)
            user := &User{
                  ID: 1,
                  Name: "Test user",
             }
            So(len(users), ShouldEqual, 1)
            So(users[0], ShouldResemble, user)
    })

И так далее, для любых других stateful- или не очень запросов.

Выводы


Как видите, Go не только упрощает написание unit-тестов, создавая стимул их писать на каждом шагу, и превращая Go программистов в приверженцев BDD и TDD методологий, но и открывает новые возможности для более сложных integration- и acceptance-тестов.

Пример, приведенный в статье, служит лишь демонстрацией, хоть и основан на реальном коде, который таким образом тестируется уже более 1.5 года в продакшене. На моём Macbook, в котором docker бежит внутри виртуальной машины (через docker-machine), весь тест (скомпилировать код для теста, поднять контейнер, прогнать ~35 http-запросов) — занимает три секунды. Как по мне, вполне неплохо для такого уровня теста, учитывая практически полную изоляцию от системы и кроссплатформенность. На Linux это, понятное дело, будет ещё быстрее.

Безусловно, разные сервисы требуют разных сценариев тестирования, но данный пример и не пытается ответить на все случаи (ремарка специально для главного хабратролля lair), а является демонстрацией того, как можно использовать потенциал Go для ускорения цикла интеграционного тестирования.

Так что, пишите тесты! С Go это так просто, что нет больше оправданий не писать.

Комментарии (33)


  1. poxu
    19.11.2015 13:55

    Вот уж не думал в статье про Go встретить клон Mocha. :)
    Кстати о джаваскрипте. Можно в Go запускать тесты автоматически при каждом изменении в файлах проекта? Скорость компиляции позволяет сделать это частью цикла разработки?


    1. ramzes_yan
      19.11.2015 14:06

      GoConvey как раз этим и занимается


      1. poxu
        19.11.2015 14:15

        Я имею в виду без веб-интерфейса. В консоли, как mocha -w


        1. ramzes_yan
          19.11.2015 14:33

          запустить в консоли goconvey. закрыть вкладку в браузере
          и смотреть вывод в консоли

          но имхо в браузере симпатичнее


          1. poxu
            20.11.2015 12:24

            Почему-то тут написано, что надо использовать отдельный скрипт на питоне.


            1. ramzes_yan
              20.11.2015 12:30

              прикольный вариант не использовал такой
              как я писал выше мне нравится веб-интерфейс


  1. psylosss
    19.11.2015 16:03

    (ремарка специально для главного хабратролля lair)

    Мне доводы lair часто кажутся гораздо более убедительными, чем ваши. Не можешь атаковать мысль — атакуй мыслителя. Грустно всё это.

    Так что, пишите тесты! С Go это так просто, что нет больше оправданий не писать.

    image


    1. divan0
      19.11.2015 16:17

      Мне доводы lair часто кажутся гораздо более убедительными, чем ваши. Не можешь атаковать мысль — атакуй мыслителя.

      Знаете, чем «мыслители» отличаются от троллей? Мыслитель стремиться докопаться до сути, тролль — доказать свою изначальную точку зрения.

      Вот, серьезно, очень здорово дискутировать о языке, в том числе о минусах, с человеком, который имеет практический опыт, и может делать взвешенные выводы, а не придумывать убедительные конструкции в голове в поддержку своего изначального неверного и предвзятого мнения. А подобные тролли — вот это грустно.


      1. psylosss
        19.11.2015 16:26

        Знаете, чем «мыслители» отличаются от троллей? Мыслитель стремиться докопаться до сути, тролль — доказать свою изначальную точку зрения.

        Знаю, поэтому так и написал. Но вы привели очень опасное определение тролля. Оно в большей степени вам подходит, чем вашим оппонентам.

        За статью — спасибо. Если когда-нибудь вернусь к Go, будет полезна. Но зачем нужна была эта эмоциональная хрень в конце? Если не нравится вам, что есть мнения, отличные от ваших, это не повод обзывать людей троллями. Но как бы кощунственно это ни звучало, мне нравится наблюдать как два профессионала мочат друг друга :) в качестве побочного эффекта наружу выходит огромное количество знаний и опыта (с обеих сторон), про которые не пишут статьи


        1. divan0
          19.11.2015 17:24

          Но вы привели очень опасное определение тролля.

          Давайте проще — есть нечто А и Б. Один человек попробовал А и Б и делится с другими своим мнением. Другой, попробовал только А, и тратит массу сил и времени, чтобы очернить Б. Точка зрения первого и точка зрения второго — это опыт против предвзятости.

          Если не нравится вам, что есть мнения, отличные от ваших, это не повод обзывать людей троллями.

          Ну я повторюсь, я обожаю общаться предметно с людьми с другими мнениями, когда эти мнения подкреплены практикой и опытом. 8К комментариев вышеупомянутого юзера, как-бы намекают.


          1. psylosss
            19.11.2015 17:33

            Не обязательно пробовать Б, чтобы делать о нём какие-то выводы. Кроме того, насколько я понял, ситуация несколько иная. Ваши оппоненты (не все, но некоторые точно) имели дело с Go.

            Разговор в духе:
            — Вы: в Go правильно так.
            — Оппоненты: пробовал, не понравилось, так не удобно. Криво сделано. Вон там лучше.
            — Вы: у тебя нет опыта. У гугла есть. А Пайк написал линукс. Поэтому ты лузер и ничего не понимаешь.
            — Оппоненты: попробовал ещё раз. Все равно не понравилось.
            — Вы: ты просто ничего не понял. Ты делаешь это неправильно.
            — Оппоненты: ну напиши чего я не понимаю и почему это должно быть удобно.
            — Вы: ты тролль, у тебя 8к комментов, которые как бы намекают. Go прекрасен! Кто не с Go, тот лох.


            1. divan0
              19.11.2015 17:51

              Не обязательно пробовать Б, чтобы делать о нём какие-то выводы.

              Делать выводы, и навязывать свои выводы тем, кто пробовал — это не одно и тоже, согласитесь.

              Разговор в духе:
              — Я: в Go принято так.
              — Оппоненты: Go писали идиоты, так плохо.
              — Я: нет, Go писали не идиоты, а одни из опытнейших программистов планеты.
              — Оппоненты: сектанты, выбрали язык из-за авторитета! Ведь всем известно, что язык без исключений могли придумать только идиоты.
              — Я: Нет, у исключений есть масса своих проблем, неочевидных поначалу, но видимых с опытом.
              — Оппоненты: докажи! вот я придумал код, в котором исключения объективно лучше.
              — Я: Речь о компромиссах, ваш код — частный пример, который не имеет ничего общего с проблемой исключений.
              — Оппоненты: (яростно минусуют) Вот в C#/Java/Rust все классно, а Go — говно.


              1. psylosss
                19.11.2015 18:15

                Честно, стараюсь разобраться в чём дело, но не могу (не способен). В диалоге с вами возникают ровно те же ощущения, что при использовании Go. Вроде бы всё хорошо и гладко, вроде бы даже аргументированно, вроде и опыт есть и человек хороший. А всё равно фигня какая-то несъедобная на выходе.


                1. Aleksi
                  19.11.2015 22:21

                  Не надо есть Ваню :)


                1. divan0
                  20.11.2015 11:00

                  Ну, я про свои ощущения не буду писать, пожалуй, от общения с некоторыми комментаторами.

                  Смотрите, ведь почему глупо спорить, не имея опыта, а только «выводы» и «мнения», особенно, когда речь идёт о чём-то новом. Опыт всегда обновляет систему ценностей, позволяет оценить плюсы и минусы как они есть, а не какими они кажутся. Вы, наверняка, такое видели, когда человек, никогда не бывавший в Европе, вам рассказывает, как там плохо жить. Тут тоже самое. То, какими вещи кажутся из-за границы или через призму телевизора, на практике окажутся совсем иными, но для этого нужно туда поехать и увидеть своими глазами.

                  Как вы понимаете, человек, который никогда не был в США, но тратящий тонну времени и сил, чтобы доказать мне, что там плохо, и строящий для этого убедительные аргументы — это не адекватный собеседник. Ему не интересно узнать, как там (а ведь есть такая хорошая возможность), ему интересно только навязать свое мнение, сформированное его изоляцией и телевизором.

                  Вот такой уровень вышеобозначенного собеседника тут.


                  1. psylosss
                    20.11.2015 11:40

                    Поймите же, что не в Европу и не в США вы зовёте и не их расхваливаете. А Зимбабве. И нет желания ехать туда чтобы проверять ваши доводы, которые не убедительны вобще ни разу. Ну какое мне дело, что там живёт лучший программист планеты, и что Гугл качает оттуда нефть? Мне туда всё равно не хочется. Да, я там ни разу не был, но мне как-то достаточно рейтинга википедии об индексе уровня жизни, чтобы понять, что ноги моей там не будет. И я буду странным белым взглядом смотреть на человека, который не только публикует посты о Зимбабве (за что ему огромное спасибо), но и всеми мыслимыми способами агитирует за переезд туда и кроет всех чуть не матом, кто возражает. А вы всё витаете в заблуждениях, что Зимбабве — это центр мира, что это лучшая страна в мире и т.п. И обижаетесь, когда вам возражают, даже те, кто там не был.


                    1. divan0
                      20.11.2015 15:19

                      Вот вы прямо демонстрируете мой поинт. Вы не были в стране А, про которую пишет человек, там находящийся, но вы «объективно уверены», что это Зимбабве, а не США. Человек вам пишет — это США, а вы ему — нет, это Зимбабве, мне по телевизору сказали.

                      Уж наезд про то, что я считаю Go лучшим языком в мире — простите, не по адресу. У меня крайне взвешенный взгляд на применимость языков, просто та сфера, в которой я больше всего работаю — серверный софт, как раз наиболее удачная ниша для Go. Поэтому, ваши надежды о том, что я «витаю в заблуждениях» также несостоятельны, как и попытка неизвестную вам страну обозвать Зимбабве.


                      1. psylosss
                        20.11.2015 15:59

                        Вы не были в стране А, про которую пишет человек, там находящийся, но вы «объективно уверены», что это Зимбабве, а не США. Человек вам пишет — это США, а вы ему — нет, это Зимбабве, мне по телевизору сказали.

                        Ещё раз, на пальцах. Go — это Зимбабве. Ваши статьи — про жизнь в Зимбабве. В Зимбабве нет правил дорожного движения (ПДД), вы про это написали статью. Вам сказали — что за хрень? С ПДД намного удобнее, весь мир пользуется ПДД в том или ином виде. Вы же доказываете, что ПДД не нужно, потому что в Зимбабве один простой закон — «Смотри-В-Оба». Он настолько прост, что ПДД не требуется. Вам говорят — не, нафиг-нафиг такую страну где нет ПДД. А вы что? Вы обвиняете всех (меня уже тоже) в недостатке опыта, и что вам обязательно надо съездить в Зимбабве, чтобы понять, что без ПДД можно прожить. Поймите: я не хочу без ПДД. В частности, поэтому я не хочу в Зимбабве. Окей, по-вашему я теперь тролль, который не был в Зимбабве, но которому по телевизору сказали, что без ПДД — это плохо. Ещё раз: спор не о том, в какой стране вы находитесь. Вы находитесь в стране Go, с этим никто не спорит. Спорят с тем, что страна без ПДД — не так уж хороша, по крайней мере для тех, кто привык соблюдать ПДД.

                        И еще немного по поводу опыта и навязывания.

                        Воображение на то и дано человеку, чтобы не испытывать всё на себе и уметь представить результат согласно своему опыту. Вы же навязываете свою точку зрения, что при обсуждении языков программирования любой опыт, кроме непосредственно использования Go должен быть исключен, а также запрещаете использовать воображение. И удивляетесь откуда столько минусов. У этой статьи 9 минусов на настоящий момент, все девять — лично ваши, потому что статья объективно хорошая. Может быть, это повод задуматься? Что там Пайк говорит про самокритичность? Извините за резкое сравнение, но именно такую линию держат одержимые сектанты. Мой вам дружеский совет: меняйте подход к популяризации.


                        1. divan0
                          20.11.2015 16:10

                          Ещё раз, на пальцах. Go — это Зимбабве

                          Вот это я называю — неумение выйти из рамок предвзятости. Вы вбили в голову себе, что незнакомая страна А — это Зимбабве, и сколько вам люди из той страны не пишут, что это не Зимбабве, вы будете непоколебимы в своем «мнении» — вам же виднее :) Это то, что в статье «For better of for worse» автор называл «пытаясь объяснить популярность Go… — они считают всех дураками».

                          Воображение на то и дано человеку, чтобы не испытывать всё на себе и уметь представить результат согласно своему опыту.

                          Продолжайте воображать, что США — это Зимбабве. Мне только не нужно это навязывать, у меня есть практический опыт, а воображение я предпочитаю использовать для чего-то более креативного, чем хейтерские комментарии :) Договорились?

                          Извините за резкое сравнение, но именно такую линию держат одержимые сектанты.

                          Не извиняю :) Вы чуть выше так возвышенно писали о необходимости уметь принимать чужую точку зрения, а теперь обзываете сектантами тех, кто не согласен с вашим «мнением со стороны» о том, что США это Зимбабве.

                          И удивляетесь откуда столько минусов.

                          Вот опять — с чего вы взяли, что я этому удивляюсь? Откуда эта страсть придумывать мои мысли и приписывать их мне?
                          Глядя на все эти «мороженным лечат горло» и «да поймите вы, что вы находитесь в Зимбабве, мне по телевизору сказали», я давно не удивляюсь.


                          1. psylosss
                            20.11.2015 16:14

                            Читать будем?

                            Ещё раз: спор не о том, в какой стране вы находитесь. Вы находитесь в стране Go, с этим никто не спорит. Спорят с тем, что страна без ПДД — не так уж хороша, по крайней мере для тех, кто привык соблюдать ПДД.


                            Все остальные выводы опять из-за неумения слышать то что вам говорят, даже комментировать не буду. Да хоть как вы назовите свою страну, ПДД от этого в ней не появится.


                            1. divan0
                              20.11.2015 16:25

                              То что там нет ПДД — это вы сами себе придумали, потому что в вашей реальности есть правило «должно быть ПДД». Если бы вы приехали в эту страну и обнаружили, что там давно уже все пересели self-driving автомобили, вы бы могли пересмотреть свои убеждения. Но не раньше.

                              Нет, серьезно, есть вещи, которые нельзя понять просто прочитав спецификацию или википедию. Так или иначе, ваше представление о мире ограничено вашим практическим опытом — будь это языки или политический устрой страны. Вот lair уже потратил целые часы и килобайты текста, чтобы мне доказать, что Go не стимулирует правильно обрабатывать ошибки. Он находит и придумывает «убедительнейшие» доказательства, оперируя спецификацией и частными примерами, но если бы он сам попробовал писать на Go — не предвзято, разумеется — он бы обнаружил, что сам язык, тулинг и экосистема вокруг него, приводят к прямо противположному его мнению эффекту — язык воспитывает внимательное отношение к ошибкам. Эти вещи можно обнаружить только на практике, и либо принять их. либо погрузиться в когнитивный диссоннанс, как автор одной из предыдущих статей, который имел «объективные доказательства» того, как плох дизайн Go, но разглядевший, что на практике эффект совершенно иной.

                              А вообще из-за вот таких комментариев — «поверь, что наше мнение более правильное, чем твое», которые мусолятся раз за разом очень маленькой группкой людей тут — Хабр приобретает своеобразную репутацию. Мой вам дружеский совет: меняйте подход к отношению к новым и незнакомым вам вещам. Прислушивайтесь больше к тем, кто имеет практический опыт, а не к диванным теоретикам.


                  1. poxu
                    20.11.2015 12:29

                    Аналогия неверна. Верная аналогия — в США, когда болит горло — советуют поесть мороженного. А у нас категорических не рекомендуют. Не надо ездить в США, чтобы узнать зачем есть мороженное, когда болит горло. И не надо ездить в США, чтобы усомниться в эффективности такого лечения.


                    1. divan0
                      20.11.2015 15:21

                      в США, когда болит горло — советуют поесть мороженного

                      Как вы удачно. Я жил в США, и могу вас заверить, что не советуют там такого. Но вы же не будете меня слушать, вы найдете убедительные доводы, что вы правы, а я нет :)


                      1. poxu
                        20.11.2015 15:52

                        Как вы удачно.

                        Удачно, точно. Вам привели аналогию, которая верно отражает положение вещей, но вы взялись обсуждать не суть аналогии, а её форму. Как вы достаточно часто делаете и в дискуссиях об исключениях и дженериках.
                        Я жил в США, и могу вас заверить, что не советуют там такого.

                        Если с вашей точки зрения тот факт, что я не жил в США, а вы жили, делает ваше утверждение более заслуживающим доверия, чем моё, то несомненно утверждение человека, который родился в США, вырос в США и живёт в США до сих пор, будет более достоверным, чем ваше.
                        Но вы же не будете меня слушать, вы найдете убедительные доводы, что вы правы, а я нет :)
                        Как скажете :). Есть гугл, который сразу находит такое. Или такое.

                        Но я, повторю ещё раз, призываю вас обсуждать не форму аналогии, а её суть.


                        1. divan0
                          20.11.2015 15:59

                          Удачно, точно. Вам привели аналогию, которая верно отражает положение вещей, но вы взялись обсуждать не суть аналогии, а её форму. Как вы достаточно часто делаете и в дискуссиях об исключениях и дженериках.

                          Абсолютно не верно. Не хочу ввязываться в дискуссию на десятки комментариев о том, почему эта аналогия верна или не верная, это уход от темы. Что мы и наблюдаем тут во многих обсуждениях.

                          Ну и есть гугл, который сразу находит такое. Или такое.

                          Вот об этом я и говорю. Вы найдете нужные вам «доказательства». Вам любой ценой нужно доказать, что мнение верно и вы будете искать доводы и частные примеры, чтобы подать их как общее. Если бы вы хотели докопаться до истины, вы — в вашем же примере с «человеком, который вырос в США» — побежали бы писать ему, а почему вот другой человек говорит иначе. Но нет — вы насаживаете это мнение мне.

                          Я, правда, не понимаю, зачем тратить время на подобные дискуссии. Хотите думать, что в США все лечат горло мороженным — верьте. Но мне не нужно это рассказывать, еще и доказывая, что я не прав :) Я такого не понимаю, правда.


                          1. poxu
                            20.11.2015 16:23

                            Не хочу ввязываться в дискуссию на десятки комментариев о том, почему эта аналогия верна или не верная, это уход от темы.

                            От какой темы? Тема ветки — какая аналогия лучше подходит, чтобы отразить суть вашей полемики с lair.
                            Вы найдете нужные вам «доказательства». Вам любой ценой нужно доказать, что мнение верно и вы будете искать доводы и частные примеры, чтобы подать их как общее.

                            У меня сложилось впечатление, что вы считаете свои примеры обработки исключений общими. Это так?
                            Я, правда, не понимаю, зачем тратить время на подобные дискуссии.

                            Я тоже не понимаю, именно поэтому я предложил вам оппонировать сути аналогии, а не её форме. Но вы почему-то этого не сделали.

                            lair приводит вам примеры, где обработка ошибок в стиле go неудобна. Так приводите примеры, где она удобна.


                            1. divan0
                              20.11.2015 16:32

                              Тема ветки — какая аналогия лучше подходит, чтобы отразить суть вашей полемики с lair.

                              Нет, тема ветки — почему я считаю его яростным троллем.

                              lair приводит вам примеры, где обработка ошибок в стиле go неудобна. Так приводите примеры, где она удобна.

                              Я начинаю уставать от этого. Человек говорит «мне неудобно». Я говорю «у этого 'неудобства' есть важное преимущество». А человеку кроме своего «неудобно» больше ничего не интересует. Поверьте, за те сотни комментариев, я уже хорошо увидел, пытается ли человек услышать аргументы или тупо бомбит своим мнением, пока от него не устанут.


                              1. poxu
                                20.11.2015 16:47

                                Человек говорит «мне неудобно». Я говорю «у этого 'неудобства' есть важное преимущество».

                                Если я правильно понял, то важным преимуществом является тот факт, что обработка ошибок является такой же частью кода, как и бизнес логика. Пожалуйста, сделайте ещё шаг — напишите, чем это хорошо для программиста. В каких случаях это очень удобно. Дайте ссылок на код с хорошо сделанной обработкой ошибок. Это окажет неоценимую помощь всем, кто пытается понять, как программировать на Go, имея за плечами многолетний опыт программирования на Джаве или С# или С++.


                                1. divan0
                                  20.11.2015 20:58

                                  Пожалуйста, сделайте ещё шаг — напишите, чем это хорошо для программиста.

                                  Если человек не понимает, почему хорошо, когда на ошибки не забивают, то это, согласитесь, совсем другая проблема. Тут вообще не о чем дискутировать.


                                  1. poxu
                                    21.11.2015 12:54

                                    Все понимают, почему хорошо, когда на ошибки не забивают.
                                    Только вот как подход, рекомендуемый в Go помогает не забивать на ошибки — непонятно. Более того, вам привели пару примеров, как легко проигнорировать ошибку. Так приведите примеры, в которых это сделать сложно.


                                    1. divan0
                                      21.11.2015 12:59

                                      Так приведите примеры, в которых это сделать сложно.

                                      Вы серьезно? Я статью про это писал, и несколько статей на тему переводил.

                                      Только вот как подход, рекомендуемый в Go помогает не забивать на ошибки — непонятно.

                                      Нет ничего ужасного в том, что непонятно. Обычно, в таких случаях, люди которые хотят понять, пробуют инструмент на практике.


                                      1. poxu
                                        21.11.2015 23:32

                                        Вы серьезно? Я статью про это писал, и несколько статей на тему переводил.

                                        В переведённых статьях чётко показано, что когда функция возвращает 2 значения и одно из них ошибка, то для того, чтобы не обрабатывать ошибку нужно сознательно игнорировать второе значение. И поэтому, если нас интересует результат, возвращаемый функцией, то ошибку проигнорировать сложно.

                                        Если результат работы функции в бизнес логике не используется, то ошибку проигнорировать легко. В случае с ошибками при обработке цикла — её тоже легко проигнорировать. Для того, чтобы не игнорировать эти ошибки предлагается тщательно проверять что возвращает функция и тщательно изучать документацию в каждом случае.

                                        Вот, что я понял из опубликованного вами на хабре. Я правильно понял?
                                        Нет ничего ужасного в том, что непонятно. Обычно, в таких случаях, люди которые хотят понять, пробуют инструмент на практике.

                                        Это только в тех случаях, когда понятно объяснить словами невозможно.


                                        1. divan0
                                          21.11.2015 23:47

                                          Вот, что я понял из опубликованного вами на хабре. Я правильно понял?

                                          Нет. Язык — это не только спецификация, это ещё и способ мышления и выражения, это сопутствующий инструментарий, это код других, на котором вы учитесь, это культура, в конце концов. Если пытаться найти объяснение исходя только из спецификации — можно доказать абсолютно любую сторону спектра мнений.

                                          Давайте попробую объяснить на аналогии со странами, которую я очень люблю относительно языков программирования (о том, что язык — это как гражданство, вы перенимаете не только язык, но и законы, правила, менталитет и налоги). Если человеку, не выезжавшему за пределы пост-советских стран рассказать, что, скажем, в Праге нет турникетов в метро, или что цветочная лавка продает цветы без продавца, и люди сами оставляют деньги — он не поверит. Да, он сможет убедительно построить «доказательство» того, что в Праге все люди ездят зайцами, и эту систему придумали идиоты. Но это будет далеко от реальности. Чтобы действительно понять, почему такая система работает и работает эффективно, человеку нужно расширить свой кругозор, поверить людям, живущим в Праге, что это действительно так и это работает, а не пытаться любой ценой им доказать, что они не правы. Иначе, сколько не рассказывай про ментальность, про теорию игр, которая стоит за системой выборочных проверок, и экономической составляющей — человек, который не готов отказаться от своих предубеждений, будет всегда стоять на своем — это не работает, придумали идиоты, плохой дизайн. Не говоря уже о том эффекте, который будет, если человек сам съездит, поживет в Праге и увидит, как это работает на практике.

                                          Это только в тех случаях, когда понятно объяснить словами невозможно.

                                          По-моему, это именно этот случай.