Книга Философия дизайна программного обеспечения Джона Остерхаута действительно нашла отклик у меня, когда я размышлял о хорошем дизайне интерфейса.
Недавно я реализовал элементарный код для выполнения некоторых видов музыкальных треков и прошел пару итераций дизайна API для своего программного обеспечения, поэтому подумал, что было бы интересно написать этот краткий процесс.
В спецификации должен был быть список треков, например:
Клиентский код должен иметь возможность запрашивать эти треки в различном и комбинированном порядке, например, сначала по названию, а затем по году в обратном порядке.
Первоначальный дизайн API был прост:
pl := new(Playlist) pl.Tracks = someTracks pl.OrderBy(“title”, “-year”)
Подход с использованием строк с префиксом дефисов был заимствован из параметров SQL / url, с использованием title для сортировки по заголовку и -year для обратной сортировки по годам. Однако очевидно, что title - это просто произвольная волшебная строка, и реализация должна каким-то образом понять это… с помощью оператора switch может быть.
При рассмотрении этих типов магических строк лучше использовать код для их документирования, поэтому на ум приходит константный стиль Java:
public class Attributes { public static final String TITLE = "title"; }
Есть разумный аргумент в пользу того, что это также утечка информации, поскольку позже трудно изменить тип с String, и некоторые реализации, использующие эту константу, могут обрабатывать их как типы String, несмотря на наше намерение сделать их более абстрактными. Поэтому я провел быстрое исследование, используя интерфейсы, чтобы скрыть реальную реализацию. Это привело к немного странной реализации с использованием интерфейса Attribute, который имеет метод Key (), который возвращает строковое значение, чтобы мой код мог интерпретировать тип атрибута:
type Attribute interface { Key() string }
Код для реализации и возврата этого интерфейса был:
// some attribute type title string // implement the `Attribute` interface: func (t title) Key() string { return “title” } // return the actual attribute, hidden as an `Attribute` func ByTitle() Attribute { return new(title) }
Это дает API:
pl := new(music.Playlist) pl.Tracks = tracks order := []music.Attribute{music.ByTitle(), music.ByYear()} pl.OrderBy(order)
Это нормально ... на самом деле строки не отображаются (хотя результат действительно оценивается как строка), но выглядит странно и многословно. Обратите внимание: здесь мы не учитываем перевернутые годы.
Вместо этого нумерованный тип казался более подходящим. Атрибут, определенный как:
type Attribute int
… И перечисление, реализованное как константа Go с использованием iota ( механизма Go для упрощения определений увеличивающихся чисел):
const ( Title Attribute = iota Artist Album Year Length )
Это означает, что мой API:
pl := new(music.Playlist) pl.Tracks = tracks order := []music.Attribute{music.Title, music.Year} pl.OrderBy(order)
… Который становится намного чище. Кроме того, вместо того, чтобы искать в документации или угадывать, что это за магические строки, увеличивая когнитивную нагрузку на кого-то, использующего этот интерфейс, они относятся к типу Attribute.
Хотя я прошел всего несколько итераций - и это ни в коем случае не рабочий код, - надеюсь, он показывает мою работу. Создание чистых и понятных интерфейсов - действительно важная часть разработки программного обеспечения, и сокращение утечки информации (в первом примере строки, связывающие API с реализацией) - один из способов достижения этой цели.
Джон Остерхаут также выступил с докладом о философии дизайна программного обеспечения в Google.