Что означает идиома «Принимай интерфейсы, возвращай структуры» в Go?

Ответ

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

1. Принимай интерфейсы (Accept Interfaces)

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

  • Почему? Это делает функцию более универсальной и тестируемой. Она зависит от поведения (методов интерфейса), а не от конкретной реализации. Вы можете передать в неё любой тип, который удовлетворяет этому интерфейсу, включая моки для тестов.
// Функция принимает любой тип, реализующий io.Reader
func countLines(r io.Reader) (int, error) {
    scanner := bufio.NewScanner(r)
    count := 0
    for scanner.Scan() {
        count++
    }
    return count, scanner.Err()
}

// Мы можем передать как файл, так и строку:
file, _ := os.Open("text.txt")
countLines(file)

strReader := strings.NewReader("line1nline2")
countLines(strReader)

2. Возвращай структуры (Return Structs)

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

  • Почему? Это даёт потребителю (вызывающему коду) максимальную гибкость. Возвращая конкретный тип, вы предоставляете всю его функциональность (все поля и методы). Потребитель сам решит, нужно ли ему использовать это значение через какой-либо интерфейс или работать с ним напрямую. Если бы вы вернули интерфейс, потребитель был бы ограничен только методами этого интерфейса.
// Фабричная функция возвращает конкретный тип *User
type User struct {
    ID   int
    Name string
}

func NewUser(name string) *User {
    return &User{Name: name}
}

// Потребитель получает *User и может делать с ним что угодно
user := NewUser("Alice")
fmt.Println(user.ID, user.Name)

Итог: Этот принцип приводит к созданию API, которые легко использовать и расширять, при этом сохраняя низкую связанность между компонентами системы.

Ответ 18+ 🔞

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

1. Принимай интерфейсы, блядь (Accept Interfaces)

Вот смотри, когда пишешь функцию — не затачивай её под какую-то конкретную хрень. Пусть она ждёт не *os.File или *bytes.Buffer, а интерфейс. То есть, она говорит: «Да мне похуй, что ты мне передашь, лишь бы у тебя был метод Read()!».

  • Зачем это, ёбта? Да чтобы не привязываться намертво! Функция становится универсальной, как швейцарский нож, и её проще тестировать. Подсунул ей в тестах какую-нибудь заглушку (mock), и всё, пиздец, работает.
// Эта функция схавает ВСЁ, что умеет читать (io.Reader)
func countLines(r io.Reader) (int, error) {
    scanner := bufio.NewScanner(r)
    count := 0
    for scanner.Scan() {
        count++
    }
    return count, scanner.Err()
}

// И похуй, откуда данные — из файла или из строки:
file, _ := os.Open("text.txt")
countLines(file) // Работает!

strReader := strings.NewReader("line1nline2")
countLines(strReader) // И это работает! Удивление пиздец!

2. Возвращай структуры, ёпта (Return Structs)

А вот когда функция что-то создаёт и отдаёт — тут уже не надо выёбываться. Отдавай конкретную структуру, а не интерфейс.

  • А это нахуя? Да чтобы не ограничивать того, кто эту хрень использует! Вернул структуру — и у пользователя полный доступ ко всем её полям и методам. Он сам решит, работать с ней напрямую или через интерфейс. А если вернёшь интерфейс — то ты, сука, сам же и загонишь его в рамки, оставив только методы из интерфейса. Зачем так делать-то, а?
// Фабрика должна отдавать конкретную структуру *User, а не какую-то абстракцию
type User struct {
    ID   int
    Name string
}

func NewUser(name string) *User {
    return &User{Name: name}
}

// Получатель получает ВСЮ власть над объектом
user := NewUser("Alice")
fmt.Println(user.ID, user.Name) // Может всё, что душе угодно

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