在多种语言之间跳来跳去可以帮助您注意到不同语言的习惯用法和最佳做法之间的某些差异。 比较有趣的差异之一与一个函数执行多项操作有关。
Python
我们先来看一下Python。 Python实际上无法重载,因为用相同的名称定义一个新的函数/方法只会覆盖前一个函数/方法。 因此,在Python中使用标志参数(布尔,枚举或“无”或“某物”参数)来表示稍微不同的行为是自然而惯用的,尤其是使用默认参数时。 默认参数使它特别有用,因为标志参数通常具有比其他参数频繁使用的值。
只有当有人调用该函数并仅传递硬编码的值时,这才真正成为问题。 这主要仅适用于布尔标志参数,但是当其名称/值不能自动显示上下文时,也可以将其应用于其他类型的标志参数。 例如,如下函数:
def lookUpPerson(id, cache_result):# looks up the person, caching the result if cache_result is Truereturn person
然后调用该函数,如下所示:
person = lookUpPerson(id, True)
当有人经过并阅读此行时,他们可能不知道或忘记了第二个参数是什么。 令人困惑。 幸运的是,在Python中,您可以使用命名参数并像下面这样调用函数:
person = lookUpPerson(id, cache_result=True)
这使得参数的目的容易得多。 他们在Python 3中做到了这一点,因此您可以制作仅称为关键字参数的参数。 这是一个好主意,因为通常应以这种方式一致地调用这些标志参数。 因此,应将函数更改为如下所示:
def lookUpPerson(id, *, cache_result):# looks up the person, caching the result if cache_result is Truereturn person
好的,现在它真的很不错,并且使用它始终会很清晰。
Java
现在,进入Java。 在Java中,使用标志参数被认为是非常糟糕的形式。 这样做有两个很好的理由:Java允许重载,但不允许命名参数。 如果没有命名参数,则先前的函数调用(现已转换为Java)将始终如下所示:
Person person = repo.lookUpPerson(id, false);
需要做一些实际工作才能使第二个参数完全清楚。 您可以在其中添加注释,也可以在名称定义名称的地方创建一个等于false
的变量。 这两种方法都可以工作,但是用Java处理这种想法的标准,最佳实践方法是制作两种不同的方法:
public Person lookUpPerson(int id) {// looks up the personreturn person;
}public Person lookUpAndCachePerson(int id){// looks up and caches the personreturn person
}
这可以在Python中完成,但通常不是惯用的方式。 这样做的好处是,它更清楚地说明了它的工作方式和工作方式。 不好的是,它通常会有点麻烦,尤其是当您通过添加更多标志来升级问题时。
混合起来
就我个人而言,我同意这两种观点,因为它们都是使用其自己的语言提供的绝佳解决方案。 有充分的理由让他们习惯自己所在的位置。 但我想稍微扩展一下Python的习惯用法。
Python的执行方式存在的问题是,根据定义,该函数执行的功能不止一件事,因为它有时做一件事,而在其他时候则做另一件事。 我想稍微改变一下习惯用法,以更好地遵循SRP(单一责任原则)。
您可以按原样保留当前的函数签名,但是实现被更改,并且弹出了另外两个函数。
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 lookupreturn persondef simpleLookUpPerson(id):# looks up the personreturn person
这给了我们什么? 如前所述,它使代码更好地遵循SRP。 lookUpPerson()
仅负责选择要调用的哪个更精细的函数。 尽管lookUpAndCachePerson()
显然具有两个职责,但您可以通过阅读其名称来了解其他两个功能的职责。 但是缓存实际上是一个潜在的副作用,并且总体而言,这可能不足以代表我的观点,因此,我太忙了,无法尝试思考不同的东西:)
这不仅为我们提供了更好的代码段,而且还为用户提供了一些在某些情况下更清晰的代码选项。 用户可以调用原始函数,甚至可以动态地提供关键字参数,或者可以通过调用分支函数之一来明确是否使用缓存。
那Kotlin呢?
最后,我们到达Kotlin。 Kotlin是一种有趣的野兽,并且是一门新语言(甚至在版本1上甚至还没有),它的某些部分还没有惯用的用法,这是到目前为止尚未定义的惯用法之一。 Kotlin可以提供关键字参数,并具有默认参数,但不能强制参数成为关键字参数。 此外,它确实支持重载。 但是,所有这一切的最大因素是Kotlin与Java完全可互操作,而Java无法使用关键字参数。
我提出对Python稍有改动的习惯用法的最大原因并不在于提出我们应该对Python进行的更改,不如我希望的那样,但更多的是介绍我的想法应该是Kotlin的成语。 每当有人在Kotlin中创建具有默认参数的函数或方法时,都应创建其他方法,最好是公共方法。
为什么,除了给出为什么要使用Python的原因之外? 由于Kotlin代码本来也可以从Java代码中调用,并且具有默认参数的Kotlin函数只是具有Java完整参数列表的函数,因此我们应该以不会让用户失望的方式编写Kotlin代码。 Java。 尽管,如果您确定自己的代码只能由Kotlin代码使用,那么我对遵循这套建议会宽容得多。
但是,在Kotlin中要记住一些事情:您应该避免使用重载来提供其他功能。 为了理解原因,让我给您看一个例子:
fun aFunction(x: Int, y: Int = 5): Int = x + y
fun aFunction(x: Int): Int = aFunction(x, 5)
定义了这两个函数后,提供了第二个函数,以便Java用户可以使用具有“默认”值的版本,当您执行此调用时会发生什么:
z = aFunction(2)
您会收到一个编译器错误。 关于调用哪个函数是模棱两可的。 当您尝试传递一个(Int)-> Int`参数的函数时,也会发生同样的事情。 因此,请避免此问题,并使您的辅助方法的名称与默认方法的名称不同。
奥托罗
这就是我本周的全部。 我真的很想听听对此的一些看法,尤其是有很好的例子来支持批评。 这只是关于统一的一些观察和一些想法。 现在该回到写我的书了。 谢谢阅读!
翻译自: https://www.javacodegeeks.com/2015/08/flag-parameters-and-overloading-in-python-java-and-kotlin.html