Приветствую вас, хабровчане, решил поделиться с сообществом переводом довольно часто (по личным наблюдениям) упоминаемого поста SOLID Go Design из блога Dave Cheney, который выполнял для собственных нужд, но кто-то говорил, что нужно делиться. Возможно для кого-то это окажется полезным.


SOLID дизайн Go


Этот пост на основе текста из основного доклада GolangUK прошедшего 18-ого Августа 2016.
Запись выступления доступна в YouTube.


Как много программистов на Go в мире?


Как много программистов на Go в мире? Подумайте о числе и держите его в своей голове,
мы вернемся к этому вопросу в конце разговора.


Рецензирование кода


Кто здесь проводит рецензирование кода, как часть своей работы? (большая часть аудитории поднимает свои руки, что обнадеживает). Хорошо, почему вы делаете рецензирование кода? (кто то выкрикивает "чтобы сделать код лучше")


Если рецензирование кода нужно для того, чтобы отловить плохой код, тогда как вы узнаете, что код, который вы рецензируете, хороший или плохой?


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


Плохой код


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


  • Негибкий. Является ли код негибким? Содержит ли он жесткий набор типов и параметров, что затрудняет модификацию.
  • Хрупкий. Является ли код хрупким? Вызывает ли хаос малейшее изменение кодовой базы?
  • Неподвижный. Сложно ли код поддается рефакторингу? Находится ли он в одном нажатии клавиши от цикличного импорта?
  • Cложный. Существует ли этот код только ради кода и не является ли он переусложненным?
  • Многословный. Использование кода изнурительно? Можете ли вы глядя на код сказать, что он пытается сделать?

Относятся ли эти слова к позитивным? Доставило бы вам удовольствие слышать эти слова при рецензировании вашего кода?


Возможно, что нет.


Хороший дизайн


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


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


SOLID


В 2002 году Роберт Мартин опубликовал свою книгу Agile Software Development, Principles, Patterns, and Practices. В ней он описал пять принципов переиспользуемого дизайна програмного обеспечения, которые он назвал SOLID принципами, аббревиатурой их названий.


  • Принцип единственной ответственности
  • Принцип открытости/закрытости
  • Принцип подстановки Барбары Лисков
  • Принцип разделения интерфейса
  • Принцип инверсии зависимостей

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


Это именно то, что я хотел бы обсудить с вами этим утром.


Принцип единственной ответственности


Первый принцип SOLID, это S — принцип единой ответственности.


Класс должен иметь одну и только одну причину для изменений.
-Роберт С. Мартин

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


Почему это так важно, чтобы один участок кода имел только одну причину для изменений? Чтож, идея того, что ваш код может изменяться мучительна, но она гораздо менее тягостная, чем то, что код от которого зависит ваш код, тоже может изменяться. И когда ваш код должен изменяться, он должен делать это в соответствии с конкретным требованием, а не быть жертвой сопутствуюещго ущерба.


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


Связанность & Единство


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


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


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


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


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


Имена пакетов


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


  • net/http, который предоставляет http клиент и сервер.
  • os/exec, который запускает внешние команды.
  • encoding/json, который реализует кодирование и декодирование документов JSON.

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


Плохие имена пакетов


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


Какую возможность предоставляет package server?.. возможно это сервер, но с какой протокол он реализует?


Какую возможность предоставляет package private? Штуки, которые я не должен увидеть? Должен ли он вообще иметь какие -то публичные символы?


И package common, ровно как и его соучастник package utils часто находятся рядом с другими злостными нарушителями.


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


Филисофия UNIX в Go


В моем представлении, никакое обсуждение раздельного дизайна не будет полным без упоминания труда Дугласа Маклроя "Филисофия UNIX"; мелкие, острые инструменты, которые сочетаются для решения более крупных задач, часто таких, которые не были предусмотренны оригинальными авторами. Я думаю что пакеты Go воплощают дух философии UNIX. В действительности каждый пакет Go сам по себе -это маленькая Go программа, единственная точка изменений с единственной ответственностью.


Принцип открытости/закрытости


Второй принцип O — принцип открытости / закрытости Бертрана Мейера, который в 1988 году писал:


Програмные объекты должны быть открыты для расширения и закрыты для модификации.
-Бертран Мейер, Построение Объектно-Ориентированного Программного Обеспечения

Как этот совет применялся для языков созданных 21 год назад?


package main

type A struct {
        year int
}

func (a A) Greet() { fmt.Println("Hello GolangUK", a.year) }

type B struct {
        A
}

func (b B) Greet() { fmt.Println("Welcome to GolangUK", b.year) }

func main() {
        var a A
        a.year = 2016
        var b B
        b.year = 2016
        a.Greet() // Hello GolangUK 2016
        b.Greet() // Welcome to GolangUK 2016
}

У нас есть тип A, с полем year и методом Greet. Мы имеем второй тип B в который встроен A, вызовы методов B перекрывают вызовы методов A поскольку A встроен, как поле в B и B предлогает свой собственный метод Greet скрывая аналогичный в A.


Но встраивание существует не только для методов, оно также предоставляет доступ к встроенным полям типа. Как вы можете увидеть, поскольку оба A и B определены в одном пакете, B может получить доступ к приватному полю year в A, как будто оно было определенно внутри B.


Итак встраивание-это мощный инструмент, который позволяет типам в Go быть открытыми для расширения.


package main

type Cat struct {
        Name string
}

func (c Cat) Legs() int { return 4 }

func (c Cat) PrintLegs() {
        fmt.Printf("I have %d legs\n", c.Legs())
}

type OctoCat struct {
        Cat
}

func (o OctoCat) Legs() int { return 5 }

func main() {
        var octo OctoCat
        fmt.Println(octo.Legs()) // 5
        octo.PrintLegs()         // I have 4 legs
}

В этом примере у нас есть тип Cat, который может посчитать колличество ног с помощью своего метода Legs. Мы встраиваем этот тип Cat в новый тип OctoCat и декларируем то, что OctocatS имеет пять ног. При этом OctoCat определяет свой собственный метод Legs, который возвращается 5, когда вызывается метод PrintLegs, он возвращает 4.


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


Отсюда мы можем сказать, что типы в Go открыты для расширения и закрыты для модификации.


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


func (c Cat) PrintLegs() {
        fmt.Printf("I have %d legs\n", c.Legs())
}

func PrintLegs(c Cat) {
        fmt.Printf("I have %d legs\n", c.Legs())
}

Ресивер-это в точности то, что вы передаете в него, первый параметр функции и посколько Go не поддерживает перегрузку функций, OctoCat не является взаимозаменяемым с обычным типом Cats. Что подводит меня к следующему принципу.


Принцип подстановки Барбары Лисков


Придуманный Барбарой Лисков, принцип подстановки Лисков утверждает, что два типа являются взаимозаменяемыми, если они проявляют такое поведение, при котором вызывающий не может определить разницу.


В языке основанном на классах, принцип подстановки Лисков часто интерпретируется, как спецификация для абстрактного класса с различными конкретными подтипами. Но в Go нет классов или наследования, потому подстановка не может быть реализованна в терминах иерархии абстратного класса.


Интерфейсы


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


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


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


io.Reader


type Reader interface {
        // Read reads up to len(buf) bytes into buf.
        Read(buf []byte) (n int, err error)
}

Что приводит меня к io.Reader моему любимому интерфейсу в Go.


Интерфейс io.Reader очень прост; Read читает данные в указанный буфер и возвращает вызывающему коду число байт, которые были прочитаны, и любую ошибку, которая может возникнуть в процесе чтения. Это выглядит просто, но это очень мощно.


Поскольку io.Reader имеет дело с чем угодно, что может быть выраженно как поток байт, мы может конструировать объекты читатели буквально из чего угодно; константной строки, массива байт, стандартного потока входа, сетевого потока, архива gzip tar, стандартного выходного потока или команды выполненной удаленно через ssh.


И все эти реализации взаимозаменяемы, поскольку они удовлетворяют одному простому контракту.


Итак, принцип подстановки Лисков применим в Go и сказанное можно суммировать прекрасным афоризмом покойного Джима Вейриха:


Требуй не больше, обещай не меньше.
-Джим Вейрих

И это отличный переход к четвертому принципу SOLID.


Принцип разделения интерфейса


Четвертый принцип, это принцип разделения интерфейса, который читается, как:


Клиенты не должны быть вынужденны зависеть от методов, которые они не используют.
-Роберт С. Мартин

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


// Save writes the contents of doc to the file f.
func Save(f *os.File, doc *Document) error

Я могу определить такую функцию. давайте назовем ее Save, она принимает *os.File как источник для записи предоставленного Document. Но здесь возникает несколько проблем.


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


Поскольку Save оперирует непосредственно файлами на диске, тестировать ее довольно неприятно. Чтобы верифицировать операции тесты должны считать содержимое файла после записи. Кроме того тесты должны убедиться, что f был записан во временное хранилище и всегда удаляется впоследствии.


*os.File также определяет много методов, которые не релевантны Save, как чтение дирректорий и проверка того, является ли путь символической ссылкой. Было бы полезно, если бы сигнатура нашей функции Save была описанна только теми частями *os.File, которые релевантны ее задаче.


Что мы можем сделать с этими проблемами?


// Save writes the contents of doc to the supplied ReadWriterCloser.
func Save(rwc io.ReadWriteCloser, doc *Document) error

Используя io.ReadWriteCloser мы можем применить принцип разделения интерфейса для переопределения Save таким образом, чтобы она принимала интерфейс, который описывает более общие задачи операций с файлами.


С этими изменениями любой тип, который реализует интерфейс io.ReadWriteCloser может быть замещен предыдущим *os.File. Это делает применение Save более широким и поясняет стороне вызывающей Save, какой метод из типа *os.File релевантный требуемой операции.


Как автор Save я более не должен иметь возможность вызова всех нерелевантных методов из *os.File, поскольку они спрятанны за io.ReadWriteCloser интерфейсом. Но мы можем пойти немного дальше с методом разделения интерфейса.


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


// Save writes the contents of doc to the supplied WriteCloser.
func Save(wc io.WriteCloser, doc *Document) error

Во-вторых, предоставляя Save с механизмом закрытия потока, который мы унаследовали с желанием сделать это похожим на обычный механизм работы с файлом, возникает вопрос, при каких обстоятельствах wc будет закрыт. Возможно Save будет вызывать Close без каки либо условий, или Close будет вызван в случае успеха.


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


type NopCloser struct {
        io.Writer
}

// Close has no effect on the underlying writer.
func (c *NopCloser) Close() error { return nil }

Грубым решением будет определение нового типа, который встраивает io.Writer и переопределяет метод Close, предотвращая вызов Save из закрытого основного потока.


Но это будет скорее всего нарушением принципа подстановки Барбары Лисков, поскольку NopCloser на самом деле ничего не закрывает.


// Save writes the contents of doc to the supplied Writer.
func Save(w io.Writer, doc *Document) error

Куда лучше будет решение переопределить Save, принимая только io.Writer, полностью лишив его возможности делать что -то помимо записи данных в поток.


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


Важное эмпирическое правило для Go, принимать интерфейсы, а возвращать структуры.
-Джек Линдамуд

Приведенная выше цитата интересный мем, который просочился в дух Go в течении нескольких последних лет.


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


Принцип инверсии зависимостей


Последний принцип SOLID, это принцип инверсии зависимостей, который утверждает:


Модули верхнего уровня не должны зависеть от модулей нижнего уровня. Оба уровня должны зависеть от абстракций.
Абстракции не должны зависеть от их деталей. Детали должны зависеть от абстракций.
-Роберт С. Мартин

Но что означает инверсия зависмостей на практике для программиста на Go?


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


Итак, как я представляю себе то, о чем Мартин тут говорит, в основном в контексте Go-это структура вашего графа импортов.


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


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


SOLID дизайн Go


В качестве резюме, когда каждый из принципов SOLID применяется в отношении Go они являются мощным инструментом дизайна, но используемые вместе, они являются главной темой.


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


Принцип открытости / закрытости поощряет вас к компромису простых типов и более сложных путем использования встривания.


Принцип подстановки Барбары Лисков поощряет вас выражать зависимости между вашими пакетами в терминах интерфейсов, а не конкретных типов. Определяя небольшые интерфейсы мы можем быть более уверенны, что реализации будут удовлетворять своим контрактам.


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


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


Если вы хотите подвести итог этого разговора, то скорее всего это будет: интерфейсы позволяют вам применять принципы SOLID в Go программах.


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


Как заметил Санди Метз:


Дизайн- это искусство организации кода, который должен работать сегодня и легко поддаваться изменениям всегда
-Санди Метз

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


Заключение


В завершении давайте вернемся к вопросу, которым я открыл этот разговор. Как много программистов на Go во всем мире? Вот мое предположение:


В 2020 году будет 500,000 разработчиков на Go.
-Дейв Чейни

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


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


В С++ есть гораздо более чистый и эволюционный язык, который пытается выйти.
-Бьёрн Страуструп, Дизайн и Эволюция С++

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


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


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


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


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


… и еще одна деталь


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


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


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


-Написали пост в блоге об этом.
-Расскажите на семинаре о том, что вы сделали.
-Напишите книгу о том, чему вы научились.
И приходите снова на эту конференцию в следующем году и расскажите о том, чего вы добились.


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


Спасибо.




Original post by Dave Cheney

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


  1. TheShock
    12.02.2018 01:39
    +1

    Go совершенно не содержит классов, вместо них мы имеем гораздо более мощную концепцию композиции

    Религиозный бред с первого же принципа. Что значит «более мощная, чем классы концепция композиции»? Это ведь что-то абсурдное вроде «кирпич более мощная концепция, чем трехэтажка». Почему так сказано, словно концепция концепция композиции не реализуется на классах? И чем она более мощная в Гоу, чем, скажем, в СиШарп? Ведь в Шарпах можно сделать все то же, что и в Го и даже больше. Я бы еще понял, если бы вы сказали, что в Гоу она более поддерживаемая (хотя это тоже крайне спорно), но уж более мощная — точно нет.

    А знаете, почему автор так сказал? Потому что он — обычный слепой евангелист со стокгольмским синдромом. Берете недостатки и без всяких аргументов выдаете за достоинства, потому что хотите видеть именно так.


    1. dmbreaker
      12.02.2018 08:04

      Вот такие комментарии пишут те, кто не знают Го. Сначала попробовали бы разобраться.
      "Всегда предпочитайте композицию наследованию" — об этом говорит автор. В Го у вас просто нет такого выбора, в смысле нет наследования, и это хорошо.
      На счёт того, что шарп может все, что и Го. Напомните, с какой версии C# поддерживает утиную типизацию интерфейсов?


      1. x2bool
        12.02.2018 08:23

        Почему вы утиную типизацию интерфейсов записали в плюсы? Спорный ведь вопрос.


        1. JekaMas
          12.02.2018 11:48

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


          1. TheShock
            12.02.2018 15:31

            Вот по-этому я и сказал: «Я бы еще понял, если бы вы сказали, что в Гоу она более поддерживаемая (хотя это тоже крайне спорно), но уж более мощная — точно нет».

            В целом, это положительно сказывается на проектах и их масштабировании.

            Только во влажных снах. На практике — превращает код больших проектов в ужасную лапшу, в которой черт ногу сломит. Ну то есть маленькие проекты — неплохо, можно получить выиграш, но большие — опс, лучше бы мы писали на ДжавоШарпах


            1. JekaMas
              12.02.2018 15:48

              Можете примеры таких больших проектов привести?
              Я работаю в основном в больших проектах и пока описанного вами явления не увидел.


              1. TheShock
                12.02.2018 15:59

                Могу задать аналогичный вопрос. Вы сказали: «это положительно сказывается на проектах и их масштабировании». Положительно в сравнении с какими проектами на C#? Покажите мне код таких проектов, потому что я знаю огромные и разнообразные проекты на C#. Это вам и сервера, и игры, и сервисы, и библиотеки. Все, что я знаю на Гоу — значительно меньше и слабее и мой опыт работы на Гоу подтверждает, что причина этому — неподходящий дизайн языка.


                1. JekaMas
                  12.02.2018 17:05
                  -1

                  Давайте по порядку:
                  1. я сказал «это положительно сказывается на проектах и их масштабировании». почему вопрос перевелся в контекст C#? Я могу ответить про то, что знаю я, а это PHP, python, C# в старые времена (по 1.5). Вероятно, это странно требовать от незнакомого человека, чтобы он вас уважил, а не просто поделился опытом? Давайте переведем разговор в несколько более дружелюбное русло общения двух инженеров, делящихся опытом?
                  2. Вы изучали вопрос о том, какие проекты на go есть?
                  3. С чем я сталкивался: Lazada — очень много гошного кода (мир праху его), «микросервисы» по сотне тысяч строк; проекты coreOs, всеми любимые docker, kubernates, prometheus; сейчас работаю в/с Ethereum (сейчас посмотрел 139488 строк гошного кода).


                  1. TheShock
                    12.02.2018 17:47

                    я сказал «это положительно сказывается на проектах и их масштабировании». почему вопрос перевелся в контекст C#

                    Если вы говорите, что что-то сказывается положительно, то будьте любезны — скажите в сравнении с чем. По контексту я понял, что в сравнении с классическим подходом к ООП, например C#. Может, вы имели ввиду в сравнении с ассемблером? Тогда я соглашусь, пожалуй большой проект на Гоу будет поддержить полегче, чем проект на Ассемблере (во всяком случае мне). Тут дизайн языка Гоу сказывается положительно.

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

                    Lazada — очень много гошного кода

                    Это оно? Как-то не очень впечатляет

                    «микросервисы» по сотне тысяч строк

                    Ну с копипаст-стайлом Гоу и отсутствием Дженериком оно и не удивительно. Разве в адекватных языках придется писать такой код для каждого вида массива?
                    func find(slice []*TypeMuxSubscription, item *TypeMuxSubscription) int {
                    	for i, v := range slice {
                    		if v == item {
                    			return i
                    		}
                    	}
                    	return -1
                    }


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

                    Видите в чем проблема? Для того, чтобы показать преимущество Гоу перед другими языками вы показываете лучшие проекты на Гоу, которые обычные для других языков. Так в чем положительное влияние, если такие же проекты пишутся и прекрасно поддерживаются и на других языках?

                    С другой стороны я смутно представляю, как можно написать и поддерживать на Гоу, скажем такую игру как Cities: Skylines. Особо учитывая, что моддеры могут поменять любой ее кусок. Или такое ПО как Blender.


                    1. JekaMas
                      12.02.2018 17:56

                      Я не сказал, что написать такое — это подвиг.
                      Я сказал, то в го есть такие же большие проекты, как и в других языках, но вход в них, как и масштабирование, легче. Что собственно вы и подтвердили только что.
                      Я не называл лучшие примеры — я где-то говорил про лучшие? Я назвал те, в которых непосредственно работал.
                      А Lazada — вот тут, самый большой e-commerce в SEA.

                      Написать можно что угодно на чем угодно Тьюринг полном. Вопрос в скорости входа, поддержки и соответствия инструмента.
                      Так я никогда не возьму Go, если надо считать цифры. Статистика, ML, DL, моделирование — боже упаси брать go.

                      Я ответил на ваши вопросы. Ваша очередь.


                      1. TheShock
                        12.02.2018 18:05
                        +1

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

                        Простите, что в Гоу есть несколько больших проектов — я знаю. Но вы сказали:

                        В целом, это положительно сказывается на проектах и их масштабировании.

                        Я не вижу положительной корреляции на примере этих проектов. Вы заявили о положительной корреляции — я попросил пример положительной корреляции.

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

                        Я вам не привел в пример цифры. Я вам привел в пример ГУИ+3д-софт и игрушку.


                        1. JekaMas
                          12.02.2018 18:23

                          Как из

                          Есть более сложные проекты на других языках
                          может следовать
                          уровень которых на Гоу не писался и это пример отрицательной корреляции.
                          ?
                          Давайте уже перейдем на серьезный разговор? Начиная с утверждения статистики о
                          correlation does not imply causation
                          .
                          Я привел несколько больших проектов. Вы согласны, что они большие? Если нет, то собственно вот и поговорили.


                          1. TheShock
                            12.02.2018 18:46

                            correlation does not imply causation

                            С этим я согласен. Моя «отрицательная корреляция» — не больше, чем предположение о существовании отрицательной корреляции.

                            Вы согласны, что они большие?

                            Я не буду поддаваться на ваши манипуляции) Это не важно, т.к. см. ниже

                            Самое важное вы почему-то упорно игнорируете, видимо потому что не хотите признавать свою неправоту. Вы утверждали о том, что дизайн Гоу положительно сказывается на поддерживаемости (не «тоже можно поддерживать», а именно «положительно»), но никаких доказательств этому не привели и съехали в итоге «ну ведь на гоу тоже есть довольно большие проекты».

                            Если бы вы с самого начала вместо

                            В целом, это положительно сказывается на проектах и их масштабировании.

                            написали то, что стараетесь утверждать теперь:

                            В целом, на Гоу тоже пишут и поддерживают большие проекты.

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


              1. TheKnight
                12.02.2018 16:08

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


                1. JekaMas
                  12.02.2018 17:06

                  LoC и число людей.


                  1. TheKnight
                    12.02.2018 17:12

                    Чуть подробней о критериях, пожалуйста: сколько LoC и сколько людей?
                    30 человек на проекте — это большой проект? А 10? А 100?
                    Миллион строчек кода? 10 миллионов? 5 тысяч?


                    1. JekaMas
                      12.02.2018 17:18

                      Большой проект — это более сотни разработчиков, лучше сотни.
                      Число строк кода — десятки тысяч на отдельный репозиторий. Или в сумме речь идет о более чем миллионе.


                      1. TheKnight
                        12.02.2018 17:22

                        Ок, спасибо.
                        Значит я всегда работал над маленькими проектами, если судить по числу разработчиков.


        1. dmbreaker
          12.02.2018 12:43

          Потому что этот подход удобнее.
          Представьте, что класс какой-то внешней библиотеки подходит по методам, но однако не поддерживает нужный вам интерфейс. И вот вы идете и пишете обертку над этим классом, хорошо, если он не sealed. В Go вы просто его используете, потому что он уже подходит, зачем предпринимать лишние шаги? Здорово же, никакой спорности тут нет.

          Отсутствие выбора хорошо по той причине, что мы не живем в мире розовых пони и единорогов — большинство разработчиков мыслят только в парадигме наследования. Это порождает монстров. Разработчики Go пошли на серьезный шаг — построили язык, который доказывает, что концепция наследования оказалась ошибочной и без нее можно обойтись (кол-во успешных проектов на Go подтверждают это).
          Можно не значит, что нужно.


          1. anjensan
            12.02.2018 13:45

            Здорово же, никакой спорности тут нет.
            Зато как иногда бывает прятно разбираться в чужом коде, в попытках понять «а кто же этот интерфейс реализует» или «хм, это имя тут просто так, или это мы реализуем некий интерфейс». Особенно если код идеоматичный, и методы называются Get(), New() и т.п.

            большинство разработчиков мыслят только в парадигме наследования
            У вас есть исследования или статистика на этот счет?

            что концепция наследования оказалась ошибочной и без нее можно обойтись
            Я наверное не буду оригинальным… Но посчитайте количество успешных проектов на C. А потом найдите там наследование. А еще найдите там сборку мусора. И рефлексию. И зеленые потоки… Ох, какие же это все ошибочные концепции, язык C доказывает это!


            1. dmbreaker
              12.02.2018 16:29

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

              Язык СИ появился куда раньше, приводить его в пример — это передергивание. Ну и у него есть своя ниша высокопроизводительных приложений.


          1. x2bool
            12.02.2018 14:16
            +1

            image


          1. rraderio
            12.02.2018 15:19
            +1

            Представьте, что класс какой-то внешней библиотеки подходит по методам, но однако не поддерживает нужный вам интерфейс
            А что делать если подходит по методам, но не подходит по смыслу, не делает того чего нужно?


            1. dmbreaker
              12.02.2018 16:29

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


              1. rraderio
                12.02.2018 17:06

                Но он нужен для другого интерфейса в этом же файле


          1. acmnu
            12.02.2018 15:37

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

            Не подтверждает. Успех говорит, что количество достоинств опережает количество недостатков. А вот что считать достоинством, а что недостатком — вопрос статистики.


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


          1. TheShock
            12.02.2018 15:38

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

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


            1. dmbreaker
              12.02.2018 16:31

              Не показывает. Джава гораздо раньше появилась и имела свои преимущества перед другими языками/платформами на ТОТ момент, что и вызвало взрывной рост числа приложений на этом языке.


              1. TheShock
                12.02.2018 17:18

                Ну ок, по вашей логике, популярность PHP, который вышел в 1995 и имел взрывной рост числа приложений показывает ошибочность подходов Haskell (1990), Lisp (1958), Erlang (1987)?


      1. TheShock
        12.02.2018 15:35

        Вот такие комментарии пишут те, кто не знают Го. Сначала попробовали бы разобраться.

        И пробовал, и разобрался.

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

        Так более мощная или нет выбора? И почему противоставляется, если композиция реализуется и на классах. Так и пишите: «на Гоу нету выбора, потому он лучше».

        А то пишут: «вот в классах наследование, а не композиция, как в Гоу — потому они хуже». Ну ложь ведь.

        поддерживает утиную типизацию интерфейсов?

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

        Go совершенно не содержит классов, вместо них мы имеем гораздо более мощную концепцию утиной типизации


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


        1. dmbreaker
          12.02.2018 16:35

          Более мощная и нет выбора. Да


    1. nahmnenik Автор
      12.02.2018 08:18

      кирпич более мощная концепция, чем трехэтажка

      В ваших словах есть доля правды, но это зависит от точки зрения, а по поводу цитаты- я бы сказал иначе. «Здание более мощная концепция, чем трехэтажка.» Да и нет тут ничего нового, это не автор поста придумал en.wikipedia.org/wiki/Composition_over_inheritance


      1. TheShock
        12.02.2018 15:44

        я бы сказал иначе. «Здание более мощная концепция, чем трехэтажка.»

        Тогда уж наоборот: «Трехэтажка более мощная концепция, чем здание». Потому что композиция — только один из методов ООП, как трехэтажка — только один из видов зданий.

        Да и нет тут ничего нового, это не автор поста придумал

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


        1. nahmnenik Автор
          12.02.2018 16:56
          -1

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

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

          Думаю вам стоит относится к миру менее агрессивно и для начала попытаться понять о чем идет речь. Никто не говорил, что наследование или ООП в целом несет в мир что -то плохое, речь о том (и опять таки в статье это проговаривается), что минимализм -это меньше ошибок и более явная реализация принципов SOLID и о том, что в контексте других ЯП очень часто говорят вспоминая о том, что они «раздуты, многословны и перегруженны» и в Go пытаются этого избежать. Очень много чего в языке не появляется именно с целью сохранения концепции минимализма.


          1. TheShock
            12.02.2018 17:15
            +1

            В данном случае думаю подразумевалось то, что при выборе между композицией и наследованием -первая концепция показалась авторам языка и автору поста более «мощной»

            Но ведь в статье написано совершенно иначе. Вы толкуете статью, как какой-то религиозный талмуд, который можно толковать как то выгоднее фанатикам. Я вам второй раз процитирую:

            Go совершенно не содержит классов, вместо них мы имеем гораздо более мощную концепцию композиции


            Я вам еще раз повторюсь:
            1. Вместо классов
            2. Более мощная
            3. Концепция композиции

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

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


            1. nahmnenik Автор
              12.02.2018 17:28

              Вы занимаетесь словоблудием и придираетесь к фразе вырванной из контекста, какое отношение ваш последний комментарий имеет к «противопоставлению композиции и ООП», о котром вы говорите? В каком месте было сказанно, что композиция не может быть реализованна с помощью классов? Можно переформулировать иначе:

              Вместо классов, которые существуют в ЯП исключительно для обеспечения реализации механизма наследования у нас есть гораздо более мощный инструмент — композиция.


              Вы толкуете статью, как какой-то религиозный талмуд, который можно толковать как то выгоднее фанатикам

              Эта статья является расшифровкой выступления и ее толкование -это пояснение того самого контекста.


              1. rraderio
                12.02.2018 17:36

                В каком месте было сказанно, что композиция не может быть реализованна с помощью классов?
                Ну если композиция может быть реализована с классами, то почему Go мощнее?


                1. nahmnenik Автор
                  12.02.2018 17:48

                  почему Go мощнее?

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

                  Никто такого не говорил, мощнее не Go, в данном случае (на сколько я могу понять) речь шла именно о наследовании, говорилось о классах, по скольку обеспечение реализации механизма наследования их изначальная задача. И никто не говорил, что Go мощнее, говорили о том, что композиция более мощный инструмент, чем наследование. Более того, мощный -не означает, что он «лучше», просто возможности предоставляемые им шире.

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


                  1. rraderio
                    12.02.2018 18:07
                    +1

                    пора привыкнуть к тому, что серебрянной пули нет и это все понимают

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

                    композиция это серебрянная пуля?


                    1. nahmnenik Автор
                      12.02.2018 18:10

                      нет просто ее мощь в сравнении с наследованием проявляется в большей гибкости. Это не взаимоисключающие понятия.


                      1. rraderio
                        12.02.2018 18:14

                        Авторы Go видимо думают иначе


              1. TheShock
                12.02.2018 17:59

                В каком месте было сказанно, что композиция не может быть реализованна с помощью классов?

                Черт! Я ведь вам уже трижды процитировал!

                Go совершенно не содержит классов, вместо них мы имеем гораздо более мощную концепцию композиции

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

                У нас на конференции совершенно не было фруктов, вместо них мы положили в тарелки значительно более вкусные яблоки

                Я понимаю, что когда что-то связано с Гоу, то фанатам все выглядит корректно, но теперь, когда я перевел на пример с фруктами — вы понимаете абсурдность фразы?
                — Как яблоки могут быть вкуснее фруктов, если яблоки — тоже фрукты?
                — Как композиция может быть мощнее классов, если через классы реализуется и композиция?

                к фразе вырванной из контекста

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

                Как у вас написано:
                Класс должен иметь одну и только одну причину для изменений.
                -Роберт С. Мартин

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


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

                Класс должен иметь одну и только одну причину для изменений.
                -Роберт С. Мартин

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

                Видите? Никакого «более мощная, чем классы». И «менее мощная, чем классы». Просто другая. И никакого бреда из разряда «яблоки вместо фруктов».


                1. nahmnenik Автор
                  12.02.2018 18:08

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

                  По сути вы правы.

                  Я не заинтересован в том, чтобы быть чьим -то адвокатом и не смотря на то, что мне нравится Go, я просто понимаю свои задачи и не собираюсь писать на нем интернет магазины избегая наследования.


  1. rraderio
    12.02.2018 18:01

    del


  1. r3code
    13.02.2018 13:40

    А если отбросить евангелистическую подачу материала, то о solid можно и не говорить вовсе. Лучше бы на примере показали как этому следовать в go. Я вот форк https://github.com/r3code/clean-go сделал от clean-go чтобы посмотреть инверсию зависимости, тут как раз есть части solid.


  1. anjensan
    13.02.2018 14:48

    Почитал. Имеет место быть пытытка привязать принципы для построения ООП программ к не-ООП языку. Ведь по сути эти принципы это что? Это свод высокоуровневых рекомендаций и практик того, как следует писать ООП-программы, чтобы они были кошерными…

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

    По пунктам

    Принцип единственной ответственности
    Универсальный принцип, применимый не только к ООП — каждый объект/функция/процедура/модуль/класс/актор/сервис/компонент/пакет/допишитесамичтохотитетут должен выполнять/нести отвественность за одну функциональность. Ну собственно да, тут сложно поспорить.
    Но блин, это ведь НЕ сводится к «давайте своим пакетам хорошие имена».

    Даже пример приводится кривой — 'net/http', который выполняет роль и криента, и сервера, нарушая наш принцип. Работу с http-прокси тоже туда? А куда spdy или HTTP2? Потом добавим работу с REST, санитайзеры http… Незачет.

    Принцип открытости/закрытости
    Встраивание действительно делает «дописывание» кода проще и легче.
    Но давайте глянем, скажем, сюда Robert C. Martin «The Open-Closed Principle», C++ Report, January 1996

    выдержка
    Modules that conform to the open-closed principle have two primary attributes.
    1. They are “Open For Extension”. This means that the behavior of the module can be extended. That we can make the module behave in new and different ways as the requirements of the application change, or to meet the needs of new applicationАгаs.
    2. They are “Closed for Modification”. The source code of such a module is inviolate. No one is allowed to make source code changes to it.
    It would seem that these two attributes are at odds with each other. The normal way to extend the behavior of a module is to make changes to that module. A module that cannot be changed is normally thought to have a fixed behavior. How can these two opposing attributes be resolved?


  1. KirEv
    13.02.2018 23:53

    Мне не понятно, зачем описывать SOLID в Gо, «приклеивать» ООП и всякое то, что от чего разработчики Go, по большому счету, отказались…

    какая-то речь про классы и преимущества мифической композиции перед ними.

    можно, простите, на Pascal налепить подобие ООП, как-то придерживаться SOLID и т.п…

    смешивание все во едино — добром не кончится…

    хоть бы задачи какие приводили…

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

    Простите, идиотизм какой-то…

    так и бывает… смотришь решение бизнес-задачи в виде одной программки, логики чуть-чуть, но б*ять применяемые паттерны, абстракции и куча всего, что показывает крутость программиста, а не логику работы программы… мозг можно сломать…

    люди иногда извращаются над кодом, предполагая, если логику менять потребуется — то это будет кто-то другой… а вот шишь )

    простите за простыню…