Статьи

Параметры флагов и перегрузка в Python, Java и Kotlin

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

питон

Сначала мы рассмотрим Python. Python фактически не способен к перегрузке, поскольку определение новой функции / метода с тем же именем просто перезапишет предыдущую. Из-за этого наличие флаговых параметров (булевых, перечислимых или параметров «Ничего или что-то») для сигнализации о немного различном поведении является естественным и идиоматичным в Python, особенно с аргументами по умолчанию. Аргументы по умолчанию делают его особенно полезным, поскольку параметры флага обычно имеют значение, которое используется значительно чаще, чем другие.

Единственный раз, когда это действительно становится проблемой, это когда кто-то вызывает функцию и передает только жестко закодированное значение. Это в основном относится только к параметрам булевых флагов, но также может применяться к другим типам параметров флагов, когда их имя / значение автоматически не раскрывает контекст. Например, такая функция:

1
2
3
def lookUpPerson(id, cache_result):
    # looks up the person, caching the result if cache_result is True
    return person

И тогда эта функция вызывается так:

1
person = lookUpPerson(id, True)

Когда кто-то приходит и читает эту строку, он может не знать или может забыть, что является вторым параметром. Это сбивает с толку. К счастью, в Python вы можете использовать именованные аргументы и вызывать функцию следующим образом:

1
person = lookUpPerson(id, cache_result=True)

Это значительно упрощает назначение параметра. В Python 3 они сделали это так, что вы можете создавать параметры, которые называются только ключевыми словами. Это хорошая идея, так как эти параметры флага обычно должны называться таким образом последовательно. Так что функция должна быть изменена, чтобы она выглядела так:

1
2
3
def lookUpPerson(id, *, cache_result):
    # looks up the person, caching the result if cache_result is True
    return person

Хорошо, теперь это действительно хорошо, и использовать его всегда будет хорошо и разборчиво.

Ява

На Java, сейчас. В Java считается ужасно плохой формой использовать параметры флага. Для этого есть две веские причины: Java допускает перегрузку, но не допускает именованных аргументов. Без именованных аргументов предыдущий вызов функции (теперь переведенный на Java) всегда будет выглядеть так:

1
Person person = repo.lookUpPerson(id, false);

Требуется некоторая реальная работа, чтобы полностью понять, для чего предназначен второй параметр. Вы можете разместить там комментарий или создать переменную, равную false где имя определяет, что это такое. Оба они работают, но стандартный, наилучший способ справиться с этой идеей в Java — создать два разных метода:

1
2
3
4
5
6
7
8
9
public Person lookUpPerson(int id) {
    // looks up the person
    return person;
}
 
public Person lookUpAndCachePerson(int id){
    // looks up and caches the person
    return person
}

Это может быть сделано в Python, но обычно это не идиоматический способ. Хорошая вещь об этом — то, что это более ясно о том, что это делает и как это работает. Плохо то, что это часто становится немного затянуто, особенно когда вы обостряете проблему, добавляя больше флагов.

Смешивая это

Лично я согласен с обоими мнениями, поскольку они оба являются отличными решениями на своих языках. У них есть веская причина быть идиоматичными там, где они есть. Но я бы хотел немного расширить идиому Python.

Проблема с тем, как Python делает это, заключается в том, что функция, по определению, делает больше, чем одну вещь, поскольку иногда она выполняет одну вещь, а другую — другую. Я хотел бы немного изменить идиому, чтобы немного лучше следовать SRP (принципу единой ответственности).

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
def lookUpPerson(id, cache_result):
    if cache_result:
        return lookUpAndCachePerson(id)
    else:
        return simplePersonLookup(id)
 
def lookUpAndCachePerson(id):
    # looks up and caches person
    # probably uses the next function for doing the lookup
    return person
 
def simpleLookUpPerson(id):
    # looks up the person
    return person

Что это нам дает? Как указывалось ранее, это заставляет код лучше следовать SRP; lookUpPerson() отвечает только за выбор того, какую из более совершенных функций вызывать. Две другие функции отточены в ответственности, хотя у lookUpAndCachePerson() явно есть две обязанности, которые вы можете увидеть, прочитав его название. Но кеширование на самом деле является побочным эффектом, и в целом это, вероятно, не лучший пример для моей точки зрения, и я слишком занят, чтобы попытаться придумать что-то другое 🙂

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

Что насчет Котлина?

Наконец-то мы добрались до Котлина. Kotlin — интересный зверь, и, будучи новым языком (до сих пор даже не в версии 1), некоторые его части еще не имеют идиоматического использования, и это пока что один из тех неопределенных идиом. Kotlin имеет возможность задавать аргументы ключевого слова и имеет параметры по умолчанию, но не может заставить параметр быть аргументом ключевого слова. Кроме того, он поддерживает перегрузку. Но самым важным фактором для всего этого является тот факт, что Kotlin полностью совместим с Java, который не сможет использовать ключевые аргументы.

Основная причина, по которой я привел слегка измененную идиому для Python, была не столько для представления изменений, которые мы должны вносить в Python, сколько для того, чтобы я хотел, чтобы это было так, но это было больше для представления того, что я думаю должна быть идиома в Kotlin. Каждый раз, когда кто-то создает функцию или метод в Kotlin, который имеет параметры по умолчанию, следует создавать дополнительные методы, предпочтительно общедоступные.

Почему это, помимо причин, по которым Python должен? В связи с тем, что код Kotlin должен также вызываться из кода Java, а функции Kotlin с параметрами по умолчанию — это просто функции с полным списком параметров для Java, мы должны писать код Kotlin так, чтобы пользователи не отбрасывались. Ява. Хотя, если бы вы были уверены, что ваш код будет использоваться только кодом Kotlin, я бы гораздо более снисходительно следовал этому набору советов.

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

1
2
fun aFunction(x: Int, y: Int = 5): Int = x + y
fun aFunction(x: Int): Int = aFunction(x, 5)

После определения этих двух функций, вторая предоставляется для того, чтобы пользователи Java могли иметь версию со значениями «по умолчанию», что происходит при выполнении этого вызова:

1
z = aFunction(2)

Вы получаете ошибку компилятора. Не ясно, какая функция вызывается. То же самое происходит, когда вы пытаетесь передать функцию для параметра `(Int) -> Int`. Поэтому избегайте этой проблемы и сделайте так, чтобы у ваших вторичных методов были имена, отличающиеся от имен по умолчанию.

Outro

Это действительно все, что у меня есть на этой неделе. Я действительно хотел бы услышать некоторые мнения об этом, особенно с хорошими примерами, чтобы поддержать критику. Это были только некоторые наблюдения и некоторые мысли об объединении. Теперь пришло время вернуться к написанию моей книги. Спасибо за чтение!