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

Недавно я реализовал элементарный код для выполнения некоторых видов музыкальных треков и прошел пару итераций дизайна 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.