Пользователь
0,0
рейтинг
15 апреля 2014 в 18:30

Разработка → Язык Go для начинающих из песочницы

Gopher

Цель этой статьи — рассказать о языке программирования Go (Golang) тем разработчикам, которые смотрят в сторону этого языка, но еще не решились взяться за его изучение. Рассказ будет вестись на примере реального приложения, которое представляет из себя RESTful API веб-сервис.

Передо мной стояла задача разработать бэкэнд к мобильному сервису. Суть сервиса довольно проста. Мобильное приложение, которое показывает посты пользователей, находящихся рядом с текущим местоположением. На посты пользователи могут оставлять свои комментарии, которые тоже, в свою очередь, можно комментировать. Получается своеобразный гео-форум.

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

Основные преимущества языка Go:

  • Простой и понятный синтаксис. Это делает написание кода приятным занятием.
  • Статическая типизация. Позволяет избежать ошибок, допущенных по невнимательности, упрощает чтение и понимание кода, делает код однозначным.
  • Скорость и компиляция. Скорость у Go в десятки раз быстрее, чем у скриптовых языков, при меньшем потреблении памяти. При этом, компиляция практически мгновенна. Весь проект компилируется в один бинарный файл, без зависимостей. Как говорится, «просто добавь воды». И вам не надо заботиться о памяти, есть сборщик мусора.
  • Отход от ООП. В языке нет классов, но есть структуры данных с методами. Наследование заменяется механизмом встраивания. Существуют интерфейсы, которые не нужно явно имплементировать, а лишь достаточно реализовать методы интерфейса.
  • Параллелизм. Параллельные вычисления в языке делаются просто, изящно и без головной боли. Горутины (что-то типа потоков) легковесны, потребляют мало памяти.
  • Богатая стандартная библиотека. В языке есть все необходимое для веб-разработки и не только. Количество сторонних библиотек постоянно растет. Кроме того, есть возможность использовать библиотеки C и C++.
  • Возможность писать в функциональном стиле. В языке есть замыкания (closures) и анонимные функции. Функции являются объектами первого порядка, их можно передавать в качестве аргументов и использовать в качестве типов данных.
  • Авторитетные отцы-основатели и сильное комьюнити. Роб Пайк, Кен Томпсон, Роберт Гризмер стояли у истоков. Сейчас у языка более 300 контрибьюторов. Язык имеет сильное сообщество и постоянно развивается.
  • Open Source
  • Обаятельный талисман


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

Итак, вернемся к нашей задаче. Хоть язык и не накладывает ограничений на структуру проекта, данное приложение я решил организовать по модели MVC. Правда View реализовывается на стороне клиента. В моем случае это был AngularJS, в перспективе — нативное мобильное приложение. Здесь я расскажу лишь об API на стороне сервиса.

Структура проекта получилась следующая:

/project/
	/conf/
		errors.go
		settings.go
	/controllers/
		posts.go
		users.go
	/models/
		posts.go
		users.go
	/utils/
		helpers.go
	loctalk.go


Программа в Go разделяется на пакеты (package), что указывается в начале каждого файла. Имя пакета должно соответствовать директории в которой находятся файлы, входящие в пакет. Так же, должен быть главный пакет main с функцией main(). Он у меня находится в корневом файле приложения loctalk.go. Таким образом, у меня получилось 5 пакетов: conf, controllers, models, utils, main.
Буду приводить неполное содержание файлов, а только минимально необходимое для понимания.

Пакет conf содержит константы и настройки сайта.

package conf

import (
	"os"
)

const (
	SITE_NAME string = "LocTalk"
	DEFAULT_LIMIT  int = 10
	MAX_LIMIT      int = 1000
	MAX_POST_CHARS int = 1000
)
func init() {
	mode := os.Getenv("MARTINI_ENV")

	switch mode {
	case "production":
		SiteUrl = "http://loctalk.net"
		AbsolutePath = "/path/to/project/"
	default:
		SiteUrl = "http://127.0.0.1"
		AbsolutePath = "/path/to/project/"
	}
}


Думаю, комментировать тут нечего. Функция init() вызывается в каждом пакете до вызова main(). Их может быть несколько в разных файлах.

Пакет main.

package main

import (
	"github.com/go-martini/martini"
	"net/http"
	"loctalk/conf"
	"loctalk/controllers"
	"loctalk/models"
	"loctalk/utils"
)

func main() {
	m := martini.Classic()

	m.Use(func(w http.ResponseWriter) {
		w.Header().Set("Content-Type", "application/json; charset=utf-8")
	})

	m.Map(new(utils.MarshUnmarsh))

	Auth := func(mu *utils.MarshUnmarsh, req *http.Request, rw http.ResponseWriter) {
		reqUserId := req.Header.Get("X-Auth-User")
		reqToken := req.Header.Get("X-Auth-Token")
		if !models.CheckToken(reqUserId, reqToken) {
			rw.WriteHeader(http.StatusUnauthorized)
			rw.Write(mu.Marshal(conf.ErrUserAccessDenied))
		}
	}

	// ROUTES
	m.Get("/", controllers.Home)

	// users
	m.Get("/api/v1/users", controllers.GetUsers)
	m.Get("/api/v1/users/:id", controllers.GetUserById)
	m.Post("/api/v1/users", controllers.CreateUser)
	// …

	// posts
	m.Get("/api/v1/posts", controllers.GetRootPosts)
	m.Get("/api/v1/posts/:id", controllers.GetPostById)
	m.Post("/api/v1/posts", Auth, controllers.CreatePost)
	// ...

	m.Run()
}


В самом верху определяется имя пакета. Далее идет список импортируемых пакетов. Мы будем использовать пакет Martini. Он добавляет легкую прослойку для быстрого и удобного создания веб-приложений. Обратите внимание как импортируется этот пакет. Нужно указать путь к репозиторию откуда он был взят. А чтобы его получить, достаточно в консоли набрать команду go get github.com/go-martini/martini

Далее мы создаем экземпляр Martini, настраиваем и запускаем его. Обратите внимание на знак « := ». Это сокращенный синтаксис, он означает: создать переменную соответствующего типа и инициализировать ее. Например, написав a := «hello», мы создадим переменную a типа string и присвоим ей строку «hello».

Переменная m в нашем случае имеет тип *ClassicMartini, именно это возвращает martini.Classic(). * означает указатель, т. е. передается не само значение, а лишь указатель на него. В метод m.Use() мы передаем функцию-обработчик. Этот Middleware позволяет Martini делать определенные действия над каждым запросом. В данном случае, мы определяем Content-Type для каждого запроса. Метод m.Map() же позволяет привязать нашу структуру и использовать ее затем в контроллерах при необходимости (механизм dependency injection). В данном случае, я создал обертку для кодирования структуры данных в формат json.

Тут же мы создаем внутреннюю функцию Auth, которая проверяет авторизацию пользователя. Ее можно вставить в наши роуты и она будет вызываться до вызова контроллера. Эти вещи возможны благодаря Martini. С использованием стандартной библиотеки код получился бы немного другой.

Взглянем на файл errors.go пакета conf.

package conf

import (
	"fmt"
	"net/http"
)

type ApiError struct {
	Code        int    `json:"errorCode"`
	HttpCode    int    `json:"-"`
	Message     string `json:"errorMsg"`
	Info        string `json:"errorInfo"`
}

func (e *ApiError) Error() string {
	return e.Message
}

func NewApiError(err error) *ApiError {
	return &ApiError{0, http.StatusInternalServerError, err.Error(), ""}
}

var ErrUserPassEmpty = &ApiError{110, http.StatusBadRequest, "Password is empty", ""}
var ErrUserNotFound = &ApiError{123, http.StatusNotFound, "User not found", ""}
var ErrUserIdEmpty = &ApiError{130, http.StatusBadRequest, "Empty User Id", ""}
var ErrUserIdWrong = &ApiError{131, http.StatusBadRequest, "Wrong User Id", ""}
// … и т. д. 


Язык поддерживает возврат нескольких значений. Вместо механизма try-catch, очень часто используется прием, когда вторым аргументом возвращается ошибка. И при ее наличии, она обрабатывается. Есть встроенный тип error, который представляет из себя интерфейс:

type error interface {
	Error() string
}


Таким образом, чтобы реализовать этот интерфейс, достаточно иметь метод Error() string. Я создал свой тип для ошибок ApiError, который более специфичен для моих задач, однако совместим со встроенным типом error.

Обратите внимание на — type ApiError struct. Это определение структуры, модели данных, которую вы будете использовать постоянно в своей работе. Она состоит из полей определенных типов (надеюсь, вы успели заметить, что тип данных пишется после имени переменной). Кстати, полями могут быть другие структуры, наследуя все методы и поля. В одинарных кавычках `` указаны теги. Их указывать не обязательно. В данном случае они используются пакетом encoding/json для указания имени в выводе json (знак минус «-» вообще исключает поле из вывода).

Обратите внимание, что поля структуры написаны с заглавной буквы. Это означает, что они имеют область видимости за пределами пакета. Если написать их с прописной буквы, они экспортироваться не будут, а будут доступны только в пределах пакета. Это же относится и к функциям и методам. Вот такой простой механизм инкапсуляции.

Двигаемся дальше. Определение func (e *ApiError) Error() string означает ни что иное, как метод данной структуры. Переменная e — это указатель на структуру, своего рода self/this. Соответственно вызвав метод .Error() на структуре, мы получим ее поле Message.

Далее мы определяем предустановленные ошибки и заполняем их поля. Поля вида http.StatusBadRequest — это значения типа int в пакете http для стандартных кодов ответа, своего рода алиасы. Мы используем сокращенный синтаксис объявления структуры &ApiError{} с инициализацией. По другому можно было бы написать так:

MyError := new(ApiError)
MyError.Code = 110
// …


Символ & означает получить указатель на данную структуру. Оператор new() так же возвращает указатель, а не значение. По-началу возникает небольшая путаница с указателями, но, со временем, вы привыкните.

Перейдем к нашим моделям. Приведу урезанную версию модели постов:

package models

import (
	"labix.org/v2/mgo/bson"
	"loctalk/conf"
	"loctalk/utils"
	"time"
	"unicode/utf8"
	"log"
)

// GeoJSON format
type Geo struct {
	Type        string     `json:"-"`          
	Coordinates [2]float64 `json:"coordinates"`
}

type Post struct {
	Id         bson.ObjectId `json:"id" bson:"_id,omitempty"`
	UserId     bson.ObjectId `json:"userId"`
	UserName   string		 `json:"userName"`
	ThumbUrl   string		 `json:"thumbUrl"`
	ParentId   bson.ObjectId `json:"parentId,omitempty" bson:",omitempty"`
	Enabled    bool          `json:"-"`
	Body       string        `json:"body"`
	Geo        Geo           `json:"geo"`
	Date       time.Time     `json:"date" bson:",omitempty"`
}

func NewPost() *Post {
	return new(Post)
}

func (p *Post) LoadById(id string) *conf.ApiError {
	if !bson.IsObjectIdHex(id) {
		return conf.ErrPostIdWrong
	}

	session := utils.NewDbSession()
	defer session.Close()
	c := session.Col("posts")
	err := c.Find(bson.M{"_id": bson.ObjectIdHex(id), "enabled": true}).One(p)
	if p.Id == "" {
		return conf.ErrPostNotFound
	}
	if err != nil {
		return conf.NewApiError(err)
	}
	return nil
}

func (p *Post) Create() (id string, err *conf.ApiError) {
    // validation
    switch {
    case p.UserId == "":
        err = conf.ErrUserIdEmpty
    case p.Body == "":
        err = conf.ErrPostBodyEmpty
    case utf8.RuneCountInString(p.Body) > conf.MAX_POST_CHARS:
        err = conf.ErrPostMaxSize
    case p.Geo.Coordinates[0] == 0.0 || p.Geo.Coordinates[1] == 0.0:
        err = conf.ErrPostLocationEmpty
    }
    if err != nil {
        return
    }

    p.Id = bson.NewObjectId()
    p.Geo.Type = "Point"
    p.Enabled = true
    p.Date = time.Now()

    session := utils.NewDbSession()
    defer session.Close()

    c := session.Col("posts")
    errDb := c.Insert(p)

    if errDb != nil {
        return "", conf.NewApiError(errDb)
    }

    return p.Id.Hex(), nil
}

func (p *Post) Update() *conf.ApiError {
	session := utils.NewDbSession()
	defer session.Close()
	c := session.Col("posts")
	err := c.UpdateId(p.Id, p)
	if err != nil {
		return conf.NewApiError(err)
	}
	return nil
}

func (p *Post) Disable() *conf.ApiError {
	session := utils.NewDbSession()
	defer session.Close()
	p.Enabled = false
	c := session.Col("posts")
	err := c.UpdateId(p.Id, p)
	if err != nil {
		return conf.NewApiError(err)
	}
	return nil
}

// … 


Здесь мы используем замечательный драйвер для MongoDb — mgo, чтобы сохранять данные. Для удобства, я создал небольшую обертку над api mgo — utils.NewDbSession. Логика работы с данными: сначала мы создаем объект во внутренней структуре языка, а затем, с помощью метода этой структуры, сохраняем его в базу данных.

Обратите внимание, что в этих методах мы везде используем наш тип ошибки conf.ApiError. Стандартные ошибки мы конвертируем в наши с помощью conf.NewApiError(err). Так же, важен оператор defer. Он исполняется в самом конце выполнения метода. В данном случае, закрывает соединение с БД.

Что ж, осталось взглянуть на контроллер, который обрабатывает запросы и выводит json в ответ.

package controllers

import (
	"encoding/json"
	"fmt"
	"github.com/go-martini/martini"
	"labix.org/v2/mgo/bson"
	"loctalk/conf"
	"loctalk/models"
	"loctalk/utils"
	"net/http"
)
func GetPostById(mu *utils.MarshUnmarsh, params martini.Params) (int, []byte) {
	id := params["id"]
	post := models.NewPost()
	err := post.LoadById(id)
	if err != nil {
		return err.HttpCode, mu.Marshal(err)
	}
	return http.StatusOK, mu.Marshal(post)
}

// ...


Здесь мы получаем из URL id запрашиваемого поста, создаем новый экземпляр нашей структуры и вызываем на ней метод LoadById(id) для загрузки данных из БД и заполнения данной структуры. Которую мы и выводим в HTTP ответ, предварительно преобразовав в json нашим методом mu.Marshal(post).

Обратите внимание на сигнатуру функции:
func GetPostById(mu *utils.MarshUnmarsh, params martini.Params) (int, []byte)

Входные параметры нам предоставляет Martini с помощью механизма внедрения зависимостей (dependency injection). И мы возвращаем два параметра (int, []byte) — число (статус ответа) и массив байт.

Итак, мы разобрали основные компоненты и подходы, используя которые, вы сможете сделать эффективный RESTful API интерфейс в короткие сроки. Надеюсь, статья была полезна и вдохновит некоторых из вас заняться изучением замечательного языка Go. Уверен, за ним будущее.

Для изучения могу порекомендовать хорошую книгу на русском «Программирование на языке Go» Марка Саммерфильда. И, конечно, больше практиковаться.

UPD: Tour Go на русском.
Алексей @alehano
карма
29,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

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

  • 0
    Жаль, что нельзя использовать для написания мобильных приложений.
    • +3
      Смотря для каких! Под ARM кросс-компилируется.
      • +2
        Всё очень сложно, над проблемой работает 1 человек из команды Ubuntu Touch, так что пока только для Ubuntu Touch. С Android дела обстоят хорошо, уже можно создавать приложения на базе нативной активити и рисовать через EGL, но поддержка QML пока отсутствует.
    • 0
      Только что на конференции привели пример: софт для Beacon (PayPal) написан на GO.
  • +4
    А есть что-нибудь а-ля go on rails? :) а то ваш пример ни для чего, что больше хелло ворлд, не подойдет, уж слишком low-level.
    • 0
    • +1
      Есть Revel, оно или нет не скажу, т.к. Ruby не использовал, а Revel так и не осилил, ибо для моих целей проще, удобнее и быстрее было писать на pure Go. А то что получалось с Revel, выглядело как то, что написано на pure Go, только запутаннее.
    • 0
    • +1
      Есть Revel или Gobee. Но использование высокоуровневых фреймворков это не go-way. Да и не за чем, почти все есть в комплекте. Если чего не хватает, можно добавить из таких тулкитов как Gorilla. Я предпочитаю использовать Martini. Это не полноценный фреймворк, но несколько упрощает жизнь.
      • 0
        Прошу прощения, второй фреймворк называется beego.
      • +3
        Может быть большие проекты это тоже не go-way тогда? :) Глядя на ваш код,

        1. Нет стандартов
          Хоть язык и не накладывает ограничений на структуру проекта, данное приложение я решил организовать по модели MVC

          Значит каждый будет организовывать код как хочет.
        2. Пакет main это какой-то god-object. Он и сторонние пакеты подключает и route, и headers меняет, и auth делает.
        3. Все ошибки в одном файле, нет translations.
        4. С моделями совсем все плохо.
          а) создаются соединения с базой прямо из методов,
          session := utils.NewDbSession()
          defer session.Close()
          c := session.Col(«posts»)

          б) не знаю есть ли статические методы в Go, но делать так:
          post := models.NewPost()
          err := post.LoadById(id)

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


        Так что даже не знаю как можно написать что-то большое таким образом. Все-таки full-stack фрэймворки существуют не просто так :)
        • 0
          Да, многие решения были приняты как раз исходя из малого размера проекта. Возможно, для более крупных проектов и есть смысл использовать фреймворк. Либо какое-либо внутреннее соглашение о структуре кода и т. п.
          По поводу замечаний.
          1. Да, язык системный и служит для разных целей. Можно придерживаться best practice или соглашений.
          2. Пакеты подключаются в любом файле. Решил использовать роуты и все что к ним относится в main из-за небольшого размера проекта.
          3. То же самое, проект небольшой.
          4. а) Да, тоже думал над этим. Можно было для контроллеров сдлеать middleware который бы автоматически подключался и отключался от бызы, но хотелось бы чтобы модели были независимы от контроллеров.
          По поводу пункта б. Не могли бы пояснить что вы имели ввиду? Создание нового поста отдельной функцией models.NewPost()? Если да, то это принятый в языке способ.
          в) Опять же, в силу малого размера проекта, валидация простая. Часть валидации берет на себя структура данных, т.е. вы не сможете сохранить стоку в поле с типом int. Часть критичной валидации (без которой пост не сохранится, например не передали id) проверяется в модели, возвращая ошибку. Остальное, если нужно проверяется в контролере. Но этих проверок мало. С одной стороны кажется что все разбросано по разным местам, но определенная логика в этом есть. Методы модели, например, можно использовать не только из контроллеров, а отдельно внутри (для тех же тестов, например). В более серьезных проектах правила валидации задаются тегами в структуре данных. Несомненно, это более правильный подход.
          • 0
            Основная валидация все-таки сделана у меня в модели. Она возвращает ошибку, которую потом обрабатывает контроллер. Как-то так:

            func (p *Post) Create() (id string, err *conf.ApiError) {
            	// validation
            	switch {
            	case p.UserId == "":
            		err = conf.ErrUserIdEmpty
            	case p.Body == "":
            		err = conf.ErrPostBodyEmpty
            	case utf8.RuneCountInString(p.Body) > conf.MAX_POST_CHARS:
            		err = conf.ErrPostMaxSize
            	case p.Geo.Coordinates[0] == 0.0 || p.Geo.Coordinates[1] == 0.0:
            		err = conf.ErrPostLocationEmpty
            	}
            	if err != nil {
            		return
            	}
            
            	p.Id = bson.NewObjectId()
            	p.Geo.Type = "Point"
            	p.Enabled = true
            	p.Date = time.Now()
            
            	session := utils.NewDbSession()
            	defer session.Close()
            
            	c := session.Col("posts")
            	errDb := c.Insert(p)
            
            	if errDb != nil {
            		return "", conf.NewApiError(errDb)
            	}
            
            	p.UpdParentComments()
            
            	return p.Id.Hex(), nil
            }
            
            • +1
              Я бы не сказал, что этот метод хорош, и, думаю, многие согласятся.
              • Обычно валидацию отделяют от модели
              • Метод не должен быть таким большим


              По поводу пункта б) — я имею в виду, что вы вызываете post.LoadById(id) у инстанса, обычно это делают у класса, статически, например, Post.find(id). (если это AR)
              в) — Все зависит от паттерна для реализации моделей, если это AR, то она должна сама иметь инстанс какого-то адаптера базы или где вы там храните данные. В вашем случае каждая модель имеет свое соединение к базе. А это дублирование. А дублирование — это плохо. Напрашивается наследование или что там есть в Go.

              Получается, все ваши аргументы сводятся к тому, что проект небольшой, а зачем тогда эта производительность и прочие плюшки Go, если небольшой проект можно накидать за пару команд на чем угодно и я уверен, что это будет быстрее и надежнее (ввиду того что по стандартам сделано или даже кодогенерацией).
          • +1
            По поводу год-объекта: пакет скорее аналог нэймпэйса, чем класса.
            • 0
              в namespace вы задаете routes, делате Auth и подменяете заголовки?
    • +1
      В Go чаще всего используется не один большой фреймворк, а комбинация нескольких пакетов.

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

      Например, Martini очень простой для понимания пакет, особенно удобно его Dependency Injection (правда с ним нужно быть аккуратнее, потому что при компилировании не будут выводится никакие ошибки из-за того, что зависимости передаются динамически — например передан пустой указатель).

      В Go шаблоны (templates) по-моему отличные из коробки. Очень быстрые и легкие в использовании.

      В Martini очень просто создавать middleware: мы используем их, например, для подготовки структуры логов и для переменных уровня запросов вот так:

      func midRequestValues(c martini.Context, req *http.Request) {
      	values := &ReqValues{}
      	c.Map(values)
      } 
      

      Где ReqValues — это struct со значениями глобальных оповещений (alert'ов), пользователя (берется из Redis в отдельном middleware) и т.д.

      Так как map передается всегда как указатель, но можно создать несколько простых функций, которые будут заполнять template.FuncMap какими-нибудь функциями-помощниками, как в Rails. Например:

      func (s *Sprockets) SetFuncMap(funcMap template.FuncMap) {
      	funcMap["asset_path"] = s.AssetPath
      }
      


      Тестирование Martini хитрее. Можно сделать простую функцию App(), которая инициализирует все middleware, создает пути и возвращает *martini.Martini.

      func App() *martini.Martini {
      
      	m := martini.New()
      
      	// Setup default middleware
      	m.Use(martini.Recovery())
      
      	r := martini.NewRouter()
      
      	r.NotFound(func() (int, string) {
      		return 404, wutapi.Error404()
      	})
      
      	// Add the router action
      	m.Action(r.Handle)
      
      	return m
      }
      


      После чего в тестах можно вызывать ServeHTTP и смотреть результат. Например:

      func Test_Applications_GetAll(t *testing.T) {
      
      	res := httptest.NewRecorder()
      	req := makeReq("GET", "/v1/oauth2/applications")
      	App().ServeHTTP(res, req)
      
      	assert.Equal(t, res.Code, 200, "they should be equal")
      }
      


      Этот подход удобен тем, что почти все пакеты используют стандартную библиотеку (к слову, очень хорошо и грамотно сделаную, можно даже сказать потрясающе, но у меня мало опыта сравнения например с Java или C++ — мне лично стандартная библиотека Go очень понравилась), поэтому можно заменять и дополнять пакеты как душе угодно и как лучше для проекта.

      Проблемы взаимосвязи между пакетами решатся на уровне компиляции.

      Первый раз только сложновато было придумать структуру проекта и разобратся что и где использовать, а потом это кажется уже натуральным.
    • +1
      Странное заявление. Как раз хелло ворды пишут на модных фреймворках. А когда речь заходит о серьезных крупных проектах, там все фреймворки пишутся под себя, потому что стандартные перестают подходить в тех или иных местах.
      • 0
        Вы серьезно? В мире есть только Twitter, Facebook и ВКонтакте? Вы говорите о super high load проектах, даже не так — SUPER HIGH LOAD. Весь остальной интернет живет на готовых фреймворках (я про web приложения, а не про сайты визитки). Что-то на php(zend-symfony-yii), ruby on rails, django, всякая джавятина для enterprise. И это немаленькие проекты (как вам github на rails?).
        Дело вовсе не в моде (может на Go как раз и мода), а в том, что эти инструменты работают и дают большие преимущества в скорости разработки и в наличии сотен маленьких решений для ежедневных проблем (речь как минимум про rails и ruby gems).
  • +1
    Нынче модно форумы на вебсокетах делать. code.google.com/p/go/source/browse/?repo=net#hg%2Fwebsocket — достойная библиотека. Martini и MVC для такой простой задачки на мой взгляд избыточны, net/http из стандартной библиотеки хватило бы. Пост понравился.
    • 0
      Вот поскорее бы прикрутили биндинги к Qt, да так, чтоб кроссплатформенно и качественно — тогда будет общего.
  • +2
    Язык создавался с прицелом на веб-разработку
    Отчего же? Насколько мне известно, он задуман как язык общего назначения.
    • 0
      Да, совершенно верно. Я просто хотел подчеркнуть что все для веба там есть.
  • +2
    В Go лично мне еще нравится автоформатирование исходного кода с помощью утилиты Gofmt (особенно когда редактор запускает форматирование кода после каждого сохранения файла, например SublimeText с расширением GoSublime).

    Так удобно, когда все в одном стиле.
    • 0
      Это не фишка языка, а, скорее, редактора кода. Сейчас любой нормальный редактор умеет форматировать код по нужному стандарту. Причем некоторые умеют все файлы в папке обработать… :)
      • +3
        Эта утилита идёт в составе языка. Даже если вы пишите код в любом блокноте, есть возможность его правильно отформатировать.
      • 0
        Это не фишка языка, а, скорее, редактора кода.

        Не совсем. Например, если фигурные скобочки ставить не в египетском стиле, а не следующей строке, то компилятор откажется такой код компилировать :).
  • 0
    Добавлю пару хвалебных песен про Go :).

    Из того, что мне понравилось.

    1. Можно парсить подмножество xml-данных в статически типизированные определенные пользователем данные! Очень круто. Ну, то есть, вы объявляете иерархический набор структур, с только теми полями, которые вам нужны, и Go в рантайме создаст вам из xml набор этих структур.

    2. Создание форматтеров для дат. Вам нужно передать строку в которой будет записана дата «Mon Jan 2 15:04:05 MST 2006» в формате, который вам нужен (например «02.01.06»). По-моему гениально :)
    • 0
      2. Создание форматтеров для дат. Вам нужно передать строку в которой будет записана дата «Mon Jan 2 15:04:05 MST 2006» в формате, который вам нужен (например «02.01.06»). По-моему гениально :)

      >>strftime?
  • 0
    Полиморфизм реализуется интерфейсами, которые не нужно явно имплементировать, а лишь достаточно реализовать методы интерфейса.

    Заставила призадуматься эта фраза. Каким образом это выглядит?
    • 0
      Посмотрите мой пример с ошибкой ApiError. Я реализую метод .Error(), что автоматически означает, что ApiError соответствует стандартному интерфейсу error. И там, где ожидается error, я могу передать свою ApiError.
      • +1
        Утиная типизация, то бишь.
        • 0
          Да, я это имел ввиду.
  • –3
    Скорость и компиляция. Скорость у Go в десятки раз быстрее, чем у скриптовых языков, при меньшем потреблении памяти. При этом, компиляция практически мгновенна. Весь проект компилируется в один бинарный файл, без зависимостей. Как говорится, «просто добавь воды». И вам не надо заботиться о памяти, есть сборщик мусора.


    Ты смотри, прям золотая пуля какая-то.
    Скриптовые языки при правильном использовании медленнее нативного кода в 2-5 раз. Выходит, Go в десяток раз быстрее C?
    А уж сказки про то, что в языках со сборщиком мусора можно добиться приемлемой скорости, в 2014 году выглядят даже не смешно.
    sealedabstract.com/rants/why-mobile-web-apps-are-slow/ (Хабраперевод: habrahabr.ru/post/188580/)
    • +1
      Сборщик мусора накладывает некоторые ограничения на сферы использование языка. В частности, в приложениях реального времени его использовать не стоит.
      Go, конечно, не быстрее C (может только чуть-чуть и только в особых случаях). Но в реальных веб-приложениях порядки такие. Скорость генерации веб-страницы будет в разы/десятки быстрее. Но, опять же, смотря с чем сравнивать.
      Вот человек переписал с Ruby на Go свой API и получил 50-кратный прирост — My simple web API is 50X faster....
      • 0
        > Вот человек переписал с Ruby на Go свой API и получил 50-кратный прирост

        Это говорит исключительно о том, что он очень фигово пишет на Руби.

        • +1
          В жизни, обычно, узким местом становится БД.
        • 0
          Сомневаюсь, что человек в должности Sr Software Architect, который написал книгу о Ruby (изд. O’Reilly), активный участник Ruby-сообщества, контрибьютор, и спикер на различных конференциях, «фигово» пишет на Ruby.
          • –2
            Ну значит, он очень фигово написал сам Руби.
            • 0
              Сам создатель Руби ставил своей целью не скорость работы приложений написаных на нем, а скорость и удобство разработки на нём.
              • 0
                Но не в 50 же раз.
  • –1
    Вы вот говорите про преимущества, но преимущества перед чем? Среди каких «всех остальных языков» его можно выделить?

    И про преимущества:
    Талисман у Go — дурацкий какой-то, само имя — тоже (приходится искать не "{langName}", а "{langName} programming language").

    Open source — очень спорное преимущество: да, в любой момент ты можешь форкнуть проект и пойти своим путём, но по факту много ли таких людей будет? А если не форкать, то шанс того, что в проект будут принимать твои изменения багов, какие-то новые фичи и прочее — достаточно маленький.

    Про простой синтаксис — это, тоже неоднозначно. Меня, например, убивает первый же пример, предоставленный на сайте:
    package main
    
    import "fmt"
    
    func main() {
    	fmt.Println("Hello, 世界")
    }
    

    Вопрос: почему модуль fmt занимается выводом?
    Или вот ещё: почему функция main написана с маленькой буквы, а Println — с большой?
    А зачем при импорте имя модуля в кавычках? Там что, можно использовать переменные?

    следующий пример новичку будет сложно распарсить:
    func NewField(w, h int) *Field {
    	s := make([][]bool, h)
    	for i := range s {
    		s[i] = make([]bool, w)
    	}
    	return &Field{s: s, w: w, h: h}
    }
    

    А эти * и & перед именами переменных напоминают сишечку (так не лучше ли на си и писать?)

    • +3
      Хоть язык и задумывался как замена C/C++, но на него стали переходить люди с Python, Ruby. Прежде всего, с ними и сравниваю. По удобству разработки и порогу входжения они сравнимы, но Go дает дополнительные преимущества.

      По поводу поиска языка, советую искать «Golang».

      Действительно C многое напоминает. Вот, например, описание пакета fmt:
      Package fmt implements formatted I/O with functions analogous
      to C's printf and scanf.


      По поводу маленькой и большой буквы (main / Println), тут есть объяснение. То, что с большой буквы — экспортируется. См. объяснение в тексте статьи.

      По поводу кавычек в импортируемом пакете. Точно не уверен, но возможно это связано с тем, что можно делать алиасы к пакетам. Написать, например, так import myfmt "fmt" и использовать в коде вместо fmt — myfmt.

      Приведенный пример с NewField очень простой. Если немного набить руку, все читается просто, там применяются стандартные вещи и сокращения.
      • 0
        Ну в том же Python'е алиас для импортируемого модуля можно сделать через «as» безо всяких кавычек:
        import foo as bar
        


        Если бы он просто Си напоминал — ещё пол-беды, но зачем делать присваивания через ":="?
        • 0
          чтобы напоминал еще и паскаль :) язык для всех
        • 0
          := — это не просто присваивание, это еще и наследование типа
        • 0
          := — это вывод типа, создание переменной в lvalue и само присваивание.
          Обычно присваивание через = конечно тоже есть.
      • +1
        Скрытый текст
        image
        • 0
          Вы прям как правительство РФ: любое высказанное мнение, противоречащее политике партии — так разжигатель, провокатор и просто нехороший человек :-)
          • +2
            Одно дело — высказывать мнение, другое — выдавать за мнимые недостатки то, чего вы не понимаете.
            • 0
              Я высказываю личное мнение, вызванное своими ощущениями, возникшими на стадии ознакомления с данным продуктом. И для меня эти недостатки не мнимые, а вполне себе реальные.

              А про «то, чего я не понимаю» — это вообще странный выпад, опять же, в стиле правительства.
              "- они воруют деньги!"
              "- да вы ничего понимаете!"
              • 0
                При должном упорстве до абсурда довести можно все, что угодно — и тут уже вы как правительство РФ:
                — В вашем Go воруют деньги!

                Если честно, на вопросы типа такого даже отвечать не хочется:
                > почему функция main написана с маленькой буквы, а Println — с большой?

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

                А про opensource я даже рассуждать не хочу. Какие из современных яп вы используете? Какие из интерпретаторов для них не opensource?
        • 0
          Не вижу особой проблемы. Если троллям отвечать неэмоционально и по делу, то для читающих будет польза.
    • +1
      Талисман у Go — дурацкий какой-то, само имя — тоже (приходится искать не "{langName}", а "{langName} programming language").

      Нормальный талисман :). Ищется всё хорошо по «golang».

      А зачем при импорте имя модуля в кавычках? Там что, можно использовать переменные?

      Там можно писать, грубо говоря, урл. Например «github.com/go-martini/martini»

      А эти * и & перед именами переменных напоминают сишечку (так не лучше ли на си и писать?)

      Не лучше, т.к. <см. преимущества шапке статьи>.
  • 0
    С языком близко не знаком, но вот это порадовало:
    import "github.com/go-martini/martini"
    • +1
      и то что оно само вытягивает зависимости пакета при установке
      + аналогичная структура папок в src
    • 0
      Лично мне не очень. Если либа переедет с гитхаба на битбакет, то что, исходники что ли править?
      Было бы интересно узнать у реально практикующих на Go — зависимости локально держите, или таки используете go get?
      • 0
        Если не во всех, то в почти во всех пакетных менеджерах других языков программирования репозитории с пакетами поддерживаются силами сообщества. Чем вас не устраивает, например, gopkg.in?
        • 0
          Мне не очень нравится, что мне нужно указывать непосредственно в коде, откуда брать библиотеку.
          Ну и повторюсь:
          Если либа переедет с гитхаба на битбакет, то что, исходники что ли править?

          Или я не так понял про go get и можно как-то сделать так, чтобы например при импорте «github.com/go-martini/martini» не писать «github.com/go-martini», а куда-то этот путь отдельно вынести?

          За gopkg.in спасибо.
          • 0
            В коде вы указываете, где у вас локально лежит пакет. go get github.com/go-martini/martini скачивает библиотеку в $GOPATH/src/github.com/go-martini/martini.
            • 0
              Но если библиотека меняет хост (или юзернейм на гитхабе например — что кстати уже было с Martini), то мне нужно править все исходники, где сделан импорт «github.com/go-martini/martini» (чтобы go get продолжал работать), так?
              • 0
                Ага.
                Но, тут же только import поправить, довольно тривиальная задача. Весь код лопатить не придется.
      • 0
        Локально.
        go get делает локальную копию. Создается инфраструктура. И, если качество библиотек устраивает их больше не дергают. Пока поставщик не выкатит что-то в самом деле важное. По крайней мере, все кого я знаю, придерживаются подобной политики.

        Если источник мигрирует — просто переписывается путь. Ну а если библиотека исчезает из сети совсем — остается локальная копия.
        • 0
          Есть тонкость, вендоринг. Если вы свой проект опенсорсите для широкой публики, то у импортера вашего пакета зависимости будут собираться с последними коммитами из репозитариев, а не с вашими локальными копиями. Поэтому надо или время от времени пересобирать с go get -u (-u флаг обновляет зависимости) дабы убедиться что ваша поделка работает с последними коммитами зависимостей, или форкать и вендорить зависимости.
          • 0
            Или брать с gopkg.in — там и версии можно указывать, как раз
  • +1
    Недавно написал для себя приложение на Go (минималистичный веб-аналог Dash с fuzzy-поиском на биграммах, всего 1000 LOC), ощутил на себе и прелести и недостатки языка.

    Из удобств — прозрачный механизм FFI с сишными библиотеками, весьма удобная и функциональная стандартная библиотека, приемлимая скорость работы, неплохие инструменты для профилирования (pprof tool очень помог), удобная модель асинхронной обработки данных.

    Недостатков тоже хватает: много дублирования кода при реализации простых структур данных (скучные монотонные Len Swap Less Pop Push), серьёзные потери производительности на вызове через интерфейсы (ручной инлайнинг кода из container/heap увеличил производительность поиска в 5-6 раз), сложности с пакетированием (не так просто собрать deb пакет для Go-приложения, хорошо хоть у Ubuntu есть примеры).

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.