Hi everyone, I am DHL. ByteCode, focus on the latest technology to share original articles, involving Kotlin, Jetpack, algorithm animation, data structure, system source code, LeetCode/point Offer/multithreading/domestic and foreign large factory algorithm problems and so on.

  • Effective Kotlin Item 46: Avoid member Extensions
  • Kt. academy/article/ek-…
  • Original author: Marcin Moskała
  • Translator: DHL
  • This paper has been included in the warehouse Technical-Article-Translation

When we define an extension function for a class, it is not added to the class as a member. An extension function is a special function whose default first argument is the acceptor of the function. As shown in the following example, an extension function is compiled as a normal function.

fun String.isPhoneNumber(): Boolean =
    length == 7 && all { it.isDigit() }
Copy the code

Compile into a function similar to an extension function

fun isPhoneNumber(`$this$: String): Boolean =
    $this$.length == 7 && $this`.all { it.isDigit() }
Copy the code

In addition to defining extension functions for classes, you can also define member extensions, and you can even define extensions in interfaces.

interface PhoneBook {
    fun String.isPhoneNumber(): Boolean
}

class Fizz : PhoneBook {
    override fun String.isPhoneNumber(): Boolean =
        this.length == 7 && this.all { it.isDigit() }
}
Copy the code

Do not define functions as member extension functions just to limit visibility, as shown below.

// Bad practice, do not do this
class PhoneBookIncorrect {

    fun verify(number: String): Boolean {
        require(number.isPhoneNumber())
        // ...
    }

    // ...

    fun String.isPhoneNumber(): Boolean =
        this.length == 7 && this.all { it.isDigit() }
}
Copy the code

This doesn’t really limit visibility; it just makes the extension function more complex, requiring both the extension receiver and the dispatch receiver to be called.

PhoneBookIncorrect().apply {
    "1234567890".isPhoneNumber()
}
Copy the code

You should limit the visibility of extension functions by using visible modifiers, rather than setting them to member extension functions.

class PhoneBook {

    fun verify(number: String): Boolean {
        require(number.isPhoneNumber())
        // ...
    }

    // ...
}

// This is how we limit extension functions visibility
private fun String.isPhoneNumber(): Boolean =
    this.length == 7 && this.all { it.isDigit() }
Copy the code

If you need a function as a member and want to use it as an extension function, consider using let.

class PhoneBook(
    private val phoneNumberVerifier: PhoneNumberVerifier
) {

    fun verify(number: String): Boolean {
        require(number.let(::isPhoneNumber))
    }

    private fun isPhoneNumber(number: String): Boolean =
        phoneNumberVerifier.verify(number)
}
Copy the code

Why should member extension functions be avoided

It is recommended to avoid using member extension functions for several reasons:

  • References not supported
val ref = String::isPhoneNumber
val str = "1234567890"
val boundedRef = str::isPhoneNumber

val refX = PhoneBookIncorrect::isPhoneNumber // ERROR
val book = PhoneBookIncorrect()
val boundedRefX = book::isPhoneNumber // ERROR
Copy the code
  • Implicit access between two recipients can be confusing
class A {
    val a = 10
}
class B {
    val a = 20
    val b = 30

    fun A.test() = a + b // Is it 40 or 50?
}
Copy the code
  • When we expect to modify the reference receiver, it is not clear whether we are modifying the extension receiver or the dispatch receiver
class A {
    //...
}
class B {
    //...

    fun A.update() ... // Does it update A or B?
}
Copy the code
  • For less experienced developers, seeing member extensions can be counterintuitive and unreadable.

Avoid, not ban

This rule does not apply everywhere, most obviously when we define a DSL, we need to use member extensions.

The full name of DSL is Domain Specific Language (DSL). The main implementation in Kotlin is higher-order functions

Member extensions are also useful when you need to call functions defined on a scope. Two examples might be a member function that uses Produce to generate a Channel, and an integration test function defined on an interface.

class OrderUseCase( // ... ) {/ /... private fun CoroutineScope.produceOrders() = produce<Order> { var page = 0 do { val orders = api .requestOrders(page = page++) .orEmpty() for (order in orders) send(order) } while (orders.isNotEmpty()) } } interface UserApiTrait { fun TestApplicationEngine.requestRegisterUser( token: String, request: RegisterUserRequest ): UserJson? =... fun TestApplicationEngine.requestGetUserSelf( token: String ): UserJson? =... / /... }Copy the code

We recommend avoiding defining member extension functions whenever possible, but we will use them if they are the best option.

conclusion

This article mainly introduced the, members should try to avoid using extension functions, such as DSL in addition to the special scene, because members extension function exists many shortcomings, we should try to avoid, this is just a suggestion, not compulsory, more members should not use extension function to restrict the visibility, you should use visible modifier, limiting visibility extension function.


A “like” would be the biggest encouragement if it helps

More code, more articles

Welcome to the public account: ByteCode, continue to share the latest technology



Finally, recommend long-term update and maintenance projects:

  • Personal blog, will all articles classification, welcome to check hi-dhl.com

  • KtKit compact and practical, written in Kotlin language tool library, welcome to check KtKit

  • Androidx-jetpack-practice androidX-Jetpack-practice androidX-Jetpack-practice androidX-Jetpack-Practice androidX-Jetpack-Practice

  • LeetCode/multiple thread solution, language Java and Kotlin, including a variety of solutions, problem solving ideas, time complexity, spatial complexity analysis

    • Job interview with major companies at home and abroad
    • LeetCode: Read online

Must-read articles these days

  • Android 12 is here, did your App crash?
  • Android has evolved from 1.0 to 12. Remember when you first used it?
  • Is the LinkedList down?
  • Oracle recommends the details of using ReentrantLock
  • Kotlin announced a blockbuster feature
  • Google has announced the abandonment of the LiveData.observe method
  • One detail to note when using Kotlin
  • Kotlin code affecting Performance (1)
  • Jetpack Splashscreen parsing | power generation IT migrant workers get twice the result with half the effort
  • Kotlin’s Technique and Analysis (3)
  • Kotlin’s Technique and Principle Analysis that few people know (II)
  • Kotlin’s Technique and Principle Analysis that few people know (1)
  • Uncover the == and === in Kotlin
  • The Kotlin hermetic class evolved
  • The sealed class in Kotlin is superior to the tagged class
  • What is Kotlin Sealed? Why does Google use it all