Может ли добавление переменных параметров к функции нарушить существующий код?

Является ли добавление вариационного параметра к существующей функции Go критическим изменением?

Например:

// Old function
func Foo(a int)

// Updated to:
func Foo(a int, params ...string)

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

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


person Duncan Jones    schedule 14.03.2019    source источник
comment
Учитывая, что foo не экспортируется: никого это не волнует. Было бы экспортировано: Это тормозное изменение. Что это значит для semver, могут судить по semverionistas.   -  person Volker    schedule 14.03.2019
comment
Я удалил ссылки на semver, так как эта часть субъективна. Теперь вопрос сосредоточен на том, может ли это что-то сломать для кого-то. Я также преобразовал foo в Foo, так как имеет смысл обсуждать только экспортированные функции.   -  person Duncan Jones    schedule 14.03.2019


Ответы (1)


I. Изменение функций

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

Например (попробуйте на Go Playground):

func Foo(a int)                    {}
func Foo2(a int, params ...string) {}

func main() {
    var f func(int)

    f = Foo
    f = Foo2 // Compile-time error!

    _ = f
}

Строка f = Foo2 выдает ошибку времени компиляции:

нельзя использовать Foo2 (тип func(int, ...string)) как тип func(int) в назначении

Так что это обратно несовместимое изменение, не делайте этого.

Приведенный выше пример дал ошибку времени компиляции, что является удачным/лучшим случаем, но также может быть код, который завершится ошибкой только во время выполнения (недетерминированный, если/когда это произойдет), как в этом примере:

func Foo(a int)                    {}
func Foo2(a int, params ...string) {}

func main() {
    process(Foo)
    process(Foo2) // This will panic at runtime (type assertion will not hold)!
}

func process(f interface{}) {
    f.(func(int))(1)
}

Вызов process(foo) выполнен успешно, вызов process(foo2) вызовет панику во время выполнения. Попробуйте на Go Playground.

II. Изменение методов

Ваш вопрос был направлен на функции, но та же «проблема» существует и с методами (при использовании в качестве выражений метода или значения метода, например см. golang — метод передачи для работы).

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

type Fooer interface {
    Foo(int)
}

type fooImpl int

func (fooImpl) Foo(a int) {}

type fooImpl2 int

func (fooImpl2) Foo(a int, params ...string) {}

func main() {
    var f Fooer

    f = fooImpl(0)
    f = fooImpl2(0) // Compile time error!

    _ = f
}

Поскольку подписи не совпадают, fooImpl2 не реализует Fooer, хотя fooImpl реализует:

cannot use fooImpl2(0) (type fooImpl2) as type Fooer in assignment:
  fooImpl2 does not implement Fooer (wrong type for Foo method)
      have Foo(int, ...string)
      want Foo(int)
person icza    schedule 14.03.2019
comment
поскольку он говорит о вызывающих API, я бы сказал, что это не обратное несовместимое изменение для пользователей API, поскольку старый способ вызова API все еще будет работать. - person Pizza lord; 14.03.2019
comment
@Pizzalord В некоторых случаях он может продолжать работать, но в других случаях он сломается. Примеры в ответах тому подтверждение. Это зависит от того, как он используется, но все варианты использования в моем ответе действительны. - person icza; 14.03.2019
comment
Большинство изменений (даже те, которые приняты Go в соответствии с их обещанием обратной совместимости) могут сломать что-то. Добавление поля в структуру может нарушить возможность копирования между структурами разных типов, но, например, с одними и теми же полями, но Go обычно делает это. Так что, имеет ли значение эта поломка для semver, ИМО, в основном вопрос мнения. - person Flimzy; 14.03.2019
comment
@Flimzy Я согласен, отчасти правда. Но дизайн интерфейса Go особенный и очень мощный, и если функция, изменяемая таким образом, является методом, это нарушило бы неявные реализации интерфейса, что, на мой взгляд, является серьезным изменением. Расширение структуры новым полем не влечет за собой изменения подписи. - person icza; 14.03.2019
comment
Я согласен. Изменение сигнатуры функции/метода гораздо важнее, чем добавление поля структуры. - person Flimzy; 14.03.2019
comment
Спасибо за ответ. Я немного упростил свой вопрос, так как он немного беспокоил людей из-за некоторых двусмысленностей. Тем не менее, вы все же ответили на новый вопрос - кто-то может использовать мой старый API и быть сбитым с толку изменением. Так что это перемена в перерыве, и я так и сделаю. Спасибо. - person Duncan Jones; 14.03.2019