Pull to refresh

Comments 99

Изящное решение со сбором пользовательского опыта! Все же мне глубоко симпотично стремление подумать хорошо и понять, какую задачу решаем, для чего и только после вносить изменения в код!
Ну и всех поздравляю с довольно формальным событием, но все же важным: golang в top 10 tiobe!

Так и вижу уже чью-нибудь статью под названием "Go 2 Considered Harmful" и прочий сарказм о созвучии "Go 2" и "goto".
Не совсем удачное название, ИМХО.

С другой стороны, можно сыграть на созвучии Go2 и «Go too!».
Есть решение изящнее: сломать совместимость к версии 1.30 и сразу выпустить Go 3.)

Здравый подход, особенно если вспомнить что было после появления 3его питона ...

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

Т.е. что бы идентификатор _<...> — был приватным, а любой другой — публичным. Ну и тогда название функций можно было бы писать со строчной буквы.

Иными словами, вместо

type mytype struct {
    size string
    hash uint32
    Data []byte
}


Хотелось бы видеть:

type _mytype struct {
    _size string
    _hash uint32
    data  []byte
}


Кто-то есть с таким же мнением?
это мне кажется просто классическим примером пожелания, нарушающего все идеи и все методы/подходы принятия решений об изменениях в этом языке. То, что такое предложения вообще возникло, скорее всего означает нарушение первого пункта («используй язык, набирай опыт»). Я не могу себе представить как кто-то, кто разрабатывает на Go предложит настолько радикально и калечаще поменять нечто, что совершенно не важно для тех, кто этот код на самом деле пишет. И тут подходим к нарушению второго пункта — а какую проблему это должно решить? Проблемы тут нет никакой, но есть «мне так приятней/привычней, и вообще — хочу как в питоне».
Да за австорством Russ Cox кажется(могу шибаться) есть в сети документ — будем CamelCase а не underscore одобренный сообществом.
Я не могу себе представить как кто-то, кто разрабатывает на Go предложит настолько радикально и калечаще поменять нечто, что совершенно не важно для тех, кто этот код на самом деле пишет.

Хочу заметить, что переход стиля от одного к другому может быть полностью автоматизирован (руками ничего переименовывать не надо). Можно даже сделать форк на эту тему.

хочу как в питоне

Про питон особо не знаю, скорее как в JavaScript.

Естественно, вероятность принятия такого решения стремится к нулю. Я скорее хотел узнать, тяготит ли такое соглашение еще кого-то.
Прекрасно сказано!!! Говорю как человек, вынужденный жить с интерфейсами, которые обязательно начинаются с 'I', ресиверами только this и прочим, что нам в рабочем code style напридумывали люди с полугодом опыта в golang, но зато с десятком лет в java, c или perl.
Складывается впечатление что языку некуда расти. Вроде достигнут предел эффективности runtime и компилятора. Хиндли-Милнер не реализуем, гарантии shared memory access не рассматриваются, о distributed goroutines забыли в существующем дизайне. Но вот же прорыв — мы получим type alias. Бледно как то. При этом язык называется «Пошел», а инженер называется «суслик» — это было забавно лет пять назад.
Сразу минусовать. У меня есть production и opensource Go code base. Все же я считаю поскольку компилятор 6c/8с не догонит gcc по эффективности кода на существующем железе и системных вызовах by design, постольку пусть даст легкость/скорость разработки.
А может не нужны «прорывы»? Радикальные изменения в каждой версии?
Язык развивается не только изменениями самого языка, но и новыми пакетами и инструментами. Вернее, в первую очередь надо развивать язык именно созданием и доработкой сторонних решений и уже потом изменениями в языке.
Пусть лучше будет продуманные нововведения через 2-3 года, нежели постоянно растущий набор новых фич, которые убивают фичи легкого изучения языка и чтания кода.
  1. Язык и не планировался "расти".
  2. Не думаю, что предел достигнут — скорее наоборот, изменения вроде SSA-бекенда направлены на то, чтобы эти пределы расширять.
  3. Ни разу не слышал, чтобы про алиасы говорилось как о "прорыве".
  4. Gopher это не суслик, это другое животное, не сильно известное за пределами Северной Америки. Оба из отряда грызунов, но суслик это семейство беличьих, а гоферы — семейство гоферовых. :)
Из опыта/пожеланий могу сказать еще несколько пунктов.

1. Все же отсутствие возможности (необходимости) задекларировать реализуемый интерфейс ухудшает естественную документируемость кода. Это важно при чтении кода.

2. Возможность произвольно назвать receiver в методе (это, например, «v» в func (v Vertex) Abs() float64 ..) по факту приводит к разнобою в именовании этих receiver-переменных в разных местах. По-моему, в этом месте дана ненужная свобода. Лучше бы чего-то зарезервировали или советовали бы использовать одинаковое название. Сейчас имеется разнобой и необходимость задумываться, а как назвать эту переменную — лишняя трата времени.

3. Думаю, было бы здорово иметь возможность писать комментарии в Markdown (как в Rust, например).
1. Oracle, https://github.com/fzipp/pythia, например
2. Не наблюдал с этим сложностей. Можете подробнее рассказать, когда именно возникают эти трудности? Большие ли файлы у вас? Много ли типов в рамках одного файла?
3. Думаю, это не запрещено. Можно писать в markdown и найти или сделать пакет для работы с такими комментариями.
По п. 2.

Вы выше написали: «что нам в рабочем code style напридумывали люди с полугодом опыта в golang, но зато с десятком лет в java, c или perl».

В моем понимании, это следствие того, что в java, с++, js этой свободы нет (и не надо) и программисту не нужно задумываться, как назвать receiver. Обычно при написании кода есть, над чем подумать более полезном. А задумываться, как называть receiver — это трата времени и снижение эффективности.

В Go ведь сделали штатный форматер кода? Да, это отлично, вероятность различия формата минимизирована.

Так же есть рекомендации по названию интерфейсов, например. Тоже хорошо.

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

Насчет вопроса наименования. Какая в обзем-то разница — надо дать имена n переменным или n+1 при реализации методов типа? Мне лично имена ресиверов помогают — они сохраняют контекст внутри кода. То есть я могу открыть любой файл в любом месте и читать код, без необходимости отматывать код к тому месту, где есть сигнатура метода и названия типа, реализующего логику. This, self и прочее этого не дают.
То есть я могу открыть любой файл в любом месте и читать код, без необходимости отматывать код к тому месту, где есть сигнатура метода и названия типа

А не отмотав к сигнатуре как вы узнаете название ресивера?
А зачем мне знать, что именно вот эта переменная в коде ресивер? Ее поведение ничем не отличается от поведения любых других объектов
У этого объекта, в отличие от других доступы приватные поля, например. По отношению к этому объекту нарушается (может не использоваться или не подходит) инкапсуляция.
Странно… И то и то решается тем, что начинаешь читать код, а после вносить правки, на этом этапе получаешь список доступных полей и методов.
Мне лично имена ресиверов помогают — они сохраняют контекст внутри кода. То есть я могу открыть любой файл в любом месте и читать код

Вот тут вы что имеете в виду?

И это касается только вашего кода или вообще любого кода на Go?
Кода на golang, в нем есть возможность давать имена ресиверам.
На мой взгляд
func Sum(accumulator *Summer, value int)

Более читаемо внутри тела метода, чем
func Sum(this *Summer, value int)


По п.1.

Ну есть разные варианты, в т.ч. явно в комментариях каким-либо образом описать свои намерения. Но в том же Java, например эта проблема решена на уровне языка.

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

Может быть частично проблему решает и упомянутый Oracle.

Понятно, что авторы Go хотели «как лучше», но это «как лучше» тоже без последствий не обошлось.
Не очень понятно, зачем такая проверка нужна.
Я лично пробовал того же oracle, но очень скоро удалил за ненадобностью.
Вы говорите о «последствиях», можете уточнить, о чем именно речь?
Типичный случай: есть тип и нужно выяснить, реализует от какой-то интерфейс или нет.

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

Проблемы возникают когда тип реализует-таки интерфейс «случайно» и… неправильно. И вот тут-таки интересно узнать статистику: как часто это происходит? Как сложно это заметить?
Если класс реализует нужный вам интерфейс правильно — то неважно: хотели ли его реализовать или «так случайно получилось».

На самом деле это важно. Реализация интерфейса предполагает выполнение заявленного контракта по этому интерфейсу.

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

Причем здесь Duck typing?

Речь идет о Structural type system против Nominal type system.

См., например, Nominative And Structural Typing.

Which is better? There are advantages to both approaches. Structural typing is arguably more flexible — one common complaint in JavaLanguage… On the other hand, it is very common that two objects that are structurally equivalent are semantically different...

Вот о чем я говорю.
Эээ… Duck typing собственно и может относиться к языкам с structural typing, а может и нет.
В go два типа не равны друг другу даже если полностью совпадают всем кроме имени. Но в runtime работает ducktyping, про который собственно и речь. Не будь его, было бы необходимо явно указывать список реализованных интерфейсов. Правда, как здоровый минус, мы бы попутно убили гошную возможность отделения интерфейса от реализации, больше никаких интерфейсов не стороне пользователя.
Go использует структурную типизацию по методам для определения совместимости типа и интерфейса.
Но в runtime работает ducktyping

Я, кстати, про runtime изначально ничего не говорил.

В Go главным образом используются проверки на этапе компиляции. Посмотрел, в Wikipedia даже есть пояснение на этот счет:

Duck typing is similar to, but distinct from structural typing. Structural typing is a static typing system that determines type compatibility and equivalence by a type's structure, whereas duck typing is dynamic and determines type compatibility by only that part of a type's structure that is accessed during run time.

The OCaml, Scala, Go, Elm, and Gosu languages support structural typing to varying degrees.
Правда, как здоровый минус, мы бы попутно убили гошную возможность отделения интерфейса от реализации, больше никаких интерфейсов не стороне пользователя.

Кстати, а здесь вы что имеете ввиду?
То и имею. На уровне реализиции работать с конкретной структурой, а пользователю предоставлять возможность определять нужные интерфейсы у себя.
Если у типа надо явно указывать реализуемые интерфейсы, то это становится невозможным.
На уровне реализиции работать с конкретной структурой, а пользователю предоставлять возможность определять нужные интерфейсы у себя.

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

Но размещение интерфейса у пользователя (потребителя) — это какое-то извращение, когда реализация не в курсе (явно или неявно, не важно) спецификации, которую она реализует.

В этом случае смысл интерфейса стремится к нулю.

Но размещение интерфейса у пользователя позволяет уменьшить зависимости пакета, ничего не нужно импортить. Тоже самое про другую сторону — когда реализация не знает про интерфейс.


Смысл интерфейса стремится к нулю, когда у вас каждый про каждого знает.

Но размещение интерфейса у пользователя позволяет уменьшить зависимости пакета, ничего не нужно импортить.

Размещение интерфейса у пользователя лишено смысла.

Во первых, потому что никто, кроме пользователя, им воспользоваться не может.

Во вторых, потому что нарушается отношение общее-частное и общее (интерфейс) начинает зависеть от частного (реализация), что полная ерунда.

Интерфейс это не общее, интерфейс описывает реализацию.
Каким образом интерфейс будет зависеть от реализации в случае когда интерфейс объявлен в месте, где он используется?

Интерфейс это не общее, интерфейс описывает реализацию.

Интерфейс — это спецификация, контракт. Он определяет требования в максимально приемлемом общем виде.

Реализация интерфейса — это написание кода, который удовлетворяет его спецификации.

Каким образом интерфейс будет зависеть от реализации в случае когда интерфейс объявлен в месте, где он используется?

Хотя бы просто потому, что он у вас будет написан после реализации.

С чего это он будет написан после реализации?


package service

import "encoding/json"

type Publisher interface {
    Publish([]byte) error
}

type Service struct {
   pub Publisher
}

func NewService(p Publisher) *Service {
    return &Service{
        pub: p,
    }
}

func (s *Service) Send(msg Message) error {
    data, err := json.Marshal(msg)
    if err != nil [
        return err
    }
    return s.pub.Publish(data)
}

Где тут зависимость от реализации Publisher?

Где тут зависимость от реализации Publisher

Если здесь не описана спецификация Publish(), значит вы надеетесь на какую-то реализацию.

Отсутствие «технической» зависимости не означает, что логическая связь отсутствует. Как узнать, подходит какая-то структура семантически для использования в Send() или нет?
Если здесь не описана спецификация Publish(), значит вы надеетесь на какую-то реализацию.

Сразу напомню, как выглядит спецификация. Посмотрите, какой объем описания дан всего для одного метода.

// Reader is the interface that wraps the basic Read method.
//
// Read reads up to len(p) bytes into p. It returns the number of bytes
// read (0 <= n <= len(p)) and any error encountered. Even if Read
// returns n < len(p), it may use all of p as scratch space during the call.
// If some data is available but not len(p) bytes, Read conventionally
// returns what is available instead of waiting for more.
//
// When Read encounters an error or end-of-file condition after
// successfully reading n > 0 bytes, it returns the number of
// bytes read. It may return the (non-nil) error from the same call
// or return the error (and n == 0) from a subsequent call.
// An instance of this general case is that a Reader returning
// a non-zero number of bytes at the end of the input stream may
// return either err == EOF or err == nil. The next Read should
// return 0, EOF.
//
// Callers should always process the n > 0 bytes returned before
// considering the error err. Doing so correctly handles I/O errors
// that happen after reading some bytes and also both of the
// allowed EOF behaviors.
//
// Implementations of Read are discouraged from returning a
// zero byte count with a nil error, except when len(p) == 0.
// Callers should treat a return of 0 and nil as indicating that
// nothing happened; in particular it does not indicate EOF.
//
// Implementations must not retain p.
type Reader interface {
	Read(p []byte) (n int, err error)
}

вы так и не ответили на вопрос.
С чего это интерфейс будет написан после реализации?


Как узнать, подходит какая-то структура семантически для использования в Send() или нет?

Где тут зависимость от реализации Publisher?

Где тут зависимость от реализации Publisher?

Повторяю, если у вас здесь не описан контракт, то он описан где-то в другом месте. А это означает, что интерфейс зависит от этого места.

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

То есть весь упрек в том, что контракт (документация?). Так когда реальный код я буду писать, я напишу доку и интерфейсу, и структуре Сервис и методу Сенд.


Я очень часто писал либы, сервисы, где у меня был интерфейс Logger. И никакой зависимости от реализации не было.
Логическая зависимость — это демагогия. Я создаю интерфейс, я выдвигаю требования, тот кто использует мою библиотеку должен выполнить мое требование.


Так вот, с чего это интерфейс будет написан после реализации? Вы так и не ответили на вопрос, с чего этот спор начался. Конечно он может быть написан после, это совершенно не обязательно. Что я вам и показал выше на примере Логгера.

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

Вот, отлично. Если реализация должна учитывать спецификации, то она зависит от интерфейса (явно или неявно).

Теперь возвращаемся к началу и видим:

… Правда, как здоровый минус, мы бы попутно убили гошную возможность отделения интерфейса от реализации, больше никаких интерфейсов не стороне пользователя.

Ну и каким образом явная зависимость убивает отделение интерфейса от реализации, если «кто использует мою библиотеку должен выполнить мое требование»?

Чем мешает явная зависимость размещению интерфейса у пользователя?

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


Так вот я имел ввиду, что пакет, который нуждается в каком-то функционале (хочет использовать сторонний сервис, как тот же логгер), что бы не вызывать прямой зависимости от других пакетов, объявляет у себя интерфейс, убирая прямую зависимость от других пакетов.
Если вы скажете про логическую зависимость, покажите пример в джаве (или другом языке с implements интерфейсов), где такой зависимости не будет.


Чем мешает явная зависимость размещению интерфейса у пользователя?

У какого пользователя? Приведите пример кода, а то уж слишком много путаницы.

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

По «классике», зависимости уменьшаются путем выделения общих сущностей (например, интерфейсов) в отдельную библиотеку. Тогда реализация зависит только от этой «библиотеки спецификаций», а не напрямую от потребителя.

То, что вам понравилось в Go, полагаю, является возможностью использования тривиального адаптера, см. Design Patterns in Golang: Adapter в конце:

If the Target and Adaptee has similarities then the adapter has just to delegate the requests from the Target to the Adaptee.
По «классике», зависимости уменьшаются путем выделения общих сущностей (например, интерфейсов) в отдельную библиотеку.

Никто не мешает вам так сделать, если вы так привыкли. Пример с io.Reader сами привели. Иногда так даже лучше делать. Я стараюсь такого избегать.

Никто не мешает вам так сделать, если вы так привыкли. Пример с io.Reader сами привели. Иногда так даже лучше делать. Я стараюсь такого избегать.

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

Более того, если у вас в интерфейсе присутствует хотя бы с один метод с параметром или возвратом проблемно-ориентированного интерфейса/структуры, то иного пути достичь желаемого (уменьшения зависимости) нет — нужно выделять общие сущности отдельно.

Или вы и свои типы тоже стараетесь не использовать?
Более того, если у вас в интерфейсе присутствует хотя бы с один метод с параметром или возвратом проблемно-ориентированного интерфейса/структуры, то иного пути достичь желаемого (уменьшения зависимости) нет — нужно выделять общие сущности отдельно.

Или вы и свои типы тоже стараетесь не использовать?

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

Опять же, в данном случае реализация интерфейса (интерфейс в пакете foo) будет зависеть от пакета foo (от интерфейса)

Ну так как вы от этого избавитесь, кроме как выносом общих типов в отдельный пакет?

но не наоборот

Вот если вы сделаете реализацию, которая не в курсе о потребителях (как в примере https://github.com/golang/go/wiki/CodeReviewComments#interfaces), и у которого Thing() будет чуть сложнее (с использованием типа в producer), то при попытке вынести интерфейс в consumer у вас будет прямая явная зависимость consumer от producer.
Ну так как вы от этого избавитесь, кроме как выносом общих типов в отдельный пакет?

Определением интерфейса в пакете, в котором он используется.


с использованием типа в producer

Вы только что это сами придумали и сами придумали эту зависимость.
Я бы определил этот сложный тип в консюмере и как уже сказал — при этом пакет с реализацией зависел бы от пакета с интерфейсом.


Еще раз, вы мне пытаетесь доказать про логическую зависимость при определении интерфейса в пакете, где он используется — можете привести код с интерфейсами где есть логическая зависимость и где ее нету?

Определением интерфейса в пакете, в котором он используется.

Ну разместили, получили зависимость consumer от producer.



Я бы определил этот сложный тип в консюмере и как уже сказал — при этом пакет с реализацией зависел бы от пакета с интерфейсом.

Ну перенесли туда Data. И что видим? Зачем-то у нас реализация зависит от Consumer. А если у нас есть Consumer2, то он зачем-то зависит от Consumer.



А теперь, как правильно.

Вы берет частный случай, добавляете туда кучу всего и теперь говорите, что было неправильно. Но и как я уже писал, иногда лучше выносить в отдельный пакет.


Что если еще одного консюмера не будет, а будут только продюсеры? Что если не будет этой сложной структуры данных? Что если определить queue пакет и там иметь интерфейсы консюмеру и продюсеру и общую структуру данных?

Вы берет частный случай

Блин, да это не частный, это как раз общий случай!

Это у вас был простой частный случай.

Что если еще одного консюмера не будет, а будут только продюсеры?

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

Что если не будет этой сложной структуры данных?

Это общий случай, с данными.

Что если определить queue пакет и там иметь интерфейсы консюмеру и продюсеру и общую структуру данных?

Да пожалуйста.

частный, потому что вы вцепились в название consumer, переименуйте его в queue, определите там интерфейс и используйте точно так же.

Смысл интерфейса в отделении данных и поведения. И вполне есть смысл в том, чтобы пользователь определял нужный ему интерфейс для работы(он в общем-то, можем быть сильно уже, чем набор методов, предоставляемый в стороннем пакете).
Если поставлять код в виде структур, а не интерфейсов, то пользоваться ими может любой пользователь, вне зависимости от того, какой набор поставляемых методов нужен именно ему.
Поищите, тут на хабре был перевод хорошей статьи именно про этот ключевой момент.
Еще лучше после попробовать на практике, попробовать все же код, идеоматичный для go. вполне может оказаться, что до этого была какая-нибудь java-go.
Смысл интерфейса в отделении данных и поведения.

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

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

Узким относительно чего? Релизации? Ну так это переворот с ног на голову отношение интерфейс-реализация (общее-частное).

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

Интерфейс не конечное и фиксированное количество сценариев задает, а стандартизирует поведение согласно разработанной спецификации.

Узким относительно чего?
Что значит «относительно чего»? Узкий — это интерфейс, в котором мало методов, широкий — в котором много…

Интерфейс не конечное и фиксированное количество сценариев задает, а стандартизирует поведение согласно разработанной спецификации.
Совершенно верно. Вот только кто у нас задаёт спецификацию? В Go и в современном C++, внезапно — потребитель.

Тот факт, что в язык где классические интерфейсы реализуются «как нефиг делать» люди упорно, годами, добавляют интерфейсы «в стиле Go» должен бы заставить вас остановиться и подумать… но, похоже, вы этому просто в принципе не обучены.
Совершенно верно. Вот только кто у нас задаёт спецификацию? В Go и в современном C++, внезапно — потребитель.

Это невозможно, т.к. потребитель не может изменить реализацию. Поэтому, он может потребить только то, что есть.

Как реализация может узнать, что нужно потребителю? Реализация ни про потребителей, ни про их количество абсолютно не в курсе.
Это невозможно, т.к. потребитель не может изменить реализацию.
Ещё как может! Вы никогда с заказчиками не общались, которые просили «вот тут кнопочку добавить»? Вот это — типиченый вариант, когда потребитель меняет реализацию.

Реализация ни про потребителей, ни про их количество абсолютно не в курсе.
Реализация — может быть и нет, а программист, пишущий реализацию — таки да. И в его интересах расширить класс так, чтобы им смогли пользоваться как можно большее число потребителей, так что если кому-то какого-то метода не хватить — он отреагирует на запись в багтрекере и его добавит. А то что не все потребители будут все возможности реализации использовать — так это нормально. В конце-концов когда транзисторы используются как резистры или конденсаоторы в микросхемах — тоже не все их возможности используются…
Ещё как может! Вы никогда с заказчиками не общались,

Это здесь причем? Нужна доработка — сделаем, согласно букве O в принципе SOLID.

Реализация — может быть и нет, а программист, пишущий реализацию — таки да.

Ну так отсутствие «технической» связи (например, явного упоминания интерфейса в реализации) не означает, что нет логической связи.
Ну так отсутствие «технической» связи (например, явного упоминания интерфейса в реализации) не означает, что нет логической связи.
А это уже другая история. Речь идёт про технические аспекты.

В Go (и, как я уже говорил выше, в современном C++) интерфейс описывает потребитель — и при наличии 100 потребителей реализация может одновременно и 100 интерфейсов реализовывать.

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

Вы вспомните теорию, для чего вообще была введена система типов.

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

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

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

flatscode непонятно, с чем вы спорите. В Go интерфейсы используются несколько иначе, чем в Джаве например. В Go интерфейс как-правило создается только при надобности и после того, как как написаны "реализации". Тоесть, сначала был *os.File и *http.Request, а потом был io.Reader. И да, именно поэтому интерфейс часто создается пользователем. Пример: вы пишете код для работы с git — создаете конкретный тип с методами Git, потом добавляете Hg, потом, по мере усложнения проекта, выноса кода в библиотеку, например, абстрагируете их поведение в интерфейс VCS, и это все делано вами, пользователем. Я как-то пытался свести эти мысли воедино вот в этой статье: https://habrahabr.ru/post/276981/ Не уверен, что получилось, но вдруг.

flatscode непонятно, с чем вы спорите. В Go интерфейсы используются несколько иначе, чем в Джаве например. В Go интерфейс как-правило создается только при надобности и после того, как как написаны «реализации». Тоесть, сначала был *os.File и *http.Request, а потом был io.Reader

Я не спорю, а как раз описывают эту ситуацию: интерфейс после реализации. Из этого следует, что интерфейс зависит от реализации, что является переворотом с ног на голову отношение общее-частное.

Выше я привел пример io.Reader — это классический интерфейс со спецификацией и благодаря ей есть возможность:

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

Реализация может или явно (как в Java, например с помощью implements), или неявно (в комментариях упомянуты совместимые интерфейсы или просто описание) ссылаться на спецификацию.

Неявная ссылка на интерфейс (спецификацию) — это не отсутствие логической связи между реализацией и интерфейсом (спецификации). Логические/семантические ограничения никуда не деваются!

Если в JavaScript в сигнатуре функции не указаны типы по технической причине (язык этого не предусматривает), это ведь не означает, что в качестве параметров туда можно передавать абсолютно что угодно!

Разнобой в именовании ресивера ловится go lint-ом.


Думаю, эти пункты не проходят ни шаг 1 (использовать Go, накопить опыт), ни шаг 2 (описать и объяснить проблему)..

Это здорово, но выглядит, как «мы создадим проблему, а потом будем с ней героически бороться».

Из моего опыта, именование ресивера никогда не вызывает конфликтов или споров, а разнобой обычно появляется только в результате рефакторинга (переименовали тип, к примеру, и в методе, над которым работали, изменили ресивер). Но многие ставят даже go lint и go vet на сохранение файла, у других это git post hooks, ну или при код-ревью, кому-то в глаза бросится — исправят. Лично я не видел ещё споров или конфликтов на эту тему.


Если это и можно классифицировать как проблему, то решается она слишком легко и просто.

Из моего опыта, именование ресивера никогда не вызывает конфликтов или споров

Ну выше JekaMas упомянул конфликт/спор.

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

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

Сделали, ведь, gofmt — решили проблему на канонический формат кода. Могли бы и не делать gofmt, тоже решали бы эту проблему (правда, каждый по-своему).
  1. Тут идея интерфейсов другая, не "я хочу быть вот таким то", а "мне нужно вот такое то"
  2. Есть принятые стандарты — ресивер как первая буква типа. Например (p *Producer), (c *Client), etc.
  3. https://github.com/golang/go/issues/2016
По п. 2.

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

Где-то пишут так:

func (s SpReg) String() string {
	return fmt.Sprintf("SpReg(%d)", int(s))
}


а где-то так:

func (r PCRel) String() string {
	return fmt.Sprintf("PC%+#x", int32(r))
}


Но очевидно, что такое правило приводит еще к паре неприятных случаев.

Первый — это использование «i» в качестве ресивера, переменной, которая обычно применяется в циклах.

func (i Imm) String() string {
	return fmt.Sprintf("%d", int32(i))
}


Второе — это использование «l» в качестве идентификатора. За такое нужно бить сильно и больно по пальцам (за сходство с цифрой 1):

func (l Label) String() string {
	return fmt.Sprintf("%#x", uint32(l))
}


Кстати, в Питоне похожая ситуация с возможностью именования. Однако, так на этот счет есть рекомендация:

...Often, the first argument of a method is called self. This is nothing more than a convention: the name self has absolutely no special meaning to Python. Note, however, that by not following the convention your code may be less readable to other Python programmers, and it is also conceivable that a class browser program might be written that relies upon such a convention...

См. The Python Tutorial: 9.4. Random Remarks
Стандартная библиотека — это плохой пример. Ее писали тогда, когда гошного опыта ни у кого не было.
Код там, очень часто, не идеоматичный для golang.

Я же сказал принято, не обязательно. Если ресиверы будут именоваться по-разному — то golint ворнинг выдаст.
Иногда и я пишу не первую букву, например вот в таких случая (p AsyncProducer), хотя опять же — первая важной части названия.

А если вы делаете рефакторинг, например, «AsyncProducer» переименовываете в «AsyncFactory», имена ресиверов тоже автоматически изменяются или это надо делать вручную?

вручную надо это делать, gorename'ом переименовывать каждый ресивер.

gorename'ом переименовывать каждый ресивер

А если новое имя конфликтует с переменными в функции, то еще и их нужно переименовать.

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

На фоне этих проблем на ровном месте случай с «Говорю как человек, вынужденный жить с… ресиверами только this ...» выглядит не так уж и плохо.

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

только вот IDEA это не тулза из коробки которая идет с Java. Это IDE которая разрабатывается отдельной компанией, а не комьюнити.


В Go же gorename, guru, govet, golint, gofmt, etc и все это разрабатывается Go сообществом.

Ну да, потому что для Go нет такой мощной IDE как для Java. Осталось goide
написать.

во-первых есть gogland (даже ссылку вам уже дали). Во вторых, я говорю про сам язык и комьюнити.
В стандартной библиотеке есть go/ast, go/parser, go/printer, etc которые позволяют очень просто создавать свои тулзы для работы с Go кодом (то ли генерация, то ли рефакторинг), которые потом очень просто интегрировать с vim/emacs/sublime/atom.
Потому опять же скажу — не могу вспомнить я других языков с таким большим набором тулзов для работы с кодом которые поставляются с языком, как есть у Go.

Плохо, поверьте.
Очень чувствуется контраст между чтением корпоративного кода и хороших open source проектов. Вторые значительно легче для восприятия, если есть внятные имена ресиверов, отсутствуют интерфейсы ради интерфейсов, код соответствует golang review guide.
Да нет проблемы, похоже. Стоит попробовать какое-то время покодить в соответствии с рекомендациями от golang.

*извиняюсь, похоже веткой промазал. комментарий предназначался flatscode
Sign up to leave a comment.

Articles