Не лишенный нюансов
Я упоминал общие правила принятия интерфейсов, возвращаемых структур в предыдущем посте и несколько раз при проверке кода коллегам, но часто получаю вопрос Почему. Тем более, что это несложное правило. Суть идеи и понимание того, когда ее изменить, заключается в том, чтобы избежать упреждающих абстракций при сохранении гибкости.
Упреждающие абстракции делают системы сложными
Все проблемы в информатике можно решить с помощью другого уровня косвенного обращения, за исключением, конечно, проблемы слишком большого количества косвенных указаний
- Дэвид Дж. Уиллер
Разработчики программного обеспечения любят абстракции. Лично я никогда не встречал коллег, более вовлеченных в написание кода, чем когда они создают абстракцию для чего-то. Интерфейсы абстрагируются от структур в Go, и это косвенное обращение имеет ненулевой уровень встроенной сложности. Следуя философии проектирования программного обеспечения Вам это не понадобится, нет смысла создавать такую сложность, пока она не понадобится. Распространенной причиной возврата интерфейсов из вызовов функций является предоставление пользователям возможности сосредоточиться на API, создаваемом функцией. В Go это не требуется из-за неявных интерфейсов. Этим API становятся публичные функции возвращаемой структуры.
Всегда [абстрагируйте] вещи, когда они действительно вам нужны, и никогда, когда вы просто предвидите, что они вам нужны.
Некоторые языки требуют, чтобы вы предвидели каждый интерфейс, который вам когда-либо понадобится. Большим преимуществом неявных интерфейсов является то, что они позволяют изящную абстракцию постфактум, не требуя абстрагирования заранее.
Потребность в глазах смотрящего
когда они вам действительно нужны
Как вы определяете, когда нужна абстракция? Для возвращаемого типа это просто. Вы пишете функцию, поэтому вы точно знаете, когда вам нужно абстрагироваться от возвращаемого значения.
Что касается вводов функций, то вам не нужно их контролировать. Вы можете подумать, что вашей структуры базы данных достаточно, но пользователю может потребоваться обернуть ее чем-то еще. Трудно, если не невозможно, предугадать состояние всех, кто использует вашу функцию. Этот дисбаланс между возможностью точно контролировать вывод, но неспособностью предвидеть ввод пользователя, создает более сильную тенденцию к абстракции на вводе, чем на выводе.
Удалить детали мертвого кода
Еще один аспект упрощения - удаление ненужных деталей. Функции похожи на рецепты приготовления: введя этот ввод, вы получите торт! В рецепте нет списка ингредиентов, в которых он не нуждается. Точно так же функции не должны перечислять вводимые данные, в которых они не нуждаются. Что вы думаете о следующей функции?
func addNumbers(a int, b int, s string) int { return a + b }
Для большинства программистов очевидно, что параметр s не принадлежит. Менее очевидно, когда параметры являются структурами.
type Database struct{ } func (d *Database) AddUser(s string) {...} func (d *Database) RemoveUser(s string) {...} func NewUser(d *Database, firstName string, lastName string) { d.AddUser(firstName + lastName) }
Как и рецепт со слишком большим количеством ингредиентов, NewUser принимает объект Database, который может делать слишком много вещей. Ему нужен только AddUser, но он требует того, что также имеет RemoveUser. Интерфейсы позволяют нам создавать функцию, которая зависит только от того, что нам нужно.
type DatabaseWriter interface { AddUser(string) } func NewUser(d DatabaseWriter, firstName string, lastName string) { d.AddUser(firstName + lastName) }
Дэйв Чейни писал об этом, когда описывал Принцип разделения интерфейсов. Он также описывает другие преимущества ограничения ввода, которые стоит прочитать. Общая цель, которая воплощает идею в жизнь, такова:
результаты одновременно были функцией, которая является наиболее конкретной с точки зрения требований - ей нужна только вещь, доступная для записи - и самой общей по своей функции
Я бы просто добавил, что точно так же, как функция addNumbers, указанная выше, очевидно, не должна иметь строку параметра s, функция NewUser в идеале не требует базы данных, которая также может удалять пользователей.
Обобщите причины и изучите исключения
Перечислены основные причины:
- Удалите ненужные абстракции
- Неопределенность потребности пользователя во вводе функции
- Упростите ввод функций
Эти причины также позволяют нам определять исключения из правил. Например, если ваша функция действительно может возвращать несколько типов, очевидно, что она вернет интерфейс. Точно так же, если функция является частной, то при вводе функции нет двусмысленности, поскольку это вы контролируете, поэтому предпочтение отдается не вытесняющей абстракции. Для третьего правила go не имеет возможности абстрагироваться от значений элементов структуры. Итак, если вашей функции требуется доступ к членам структуры (а не только к функциям в структуре), вы вынуждены принимать структуру напрямую.