Sujets

2d 3d 3d-scanning 3d-sensor 420 420-6gw-hy 420-cae-hy 420-g 420-gef-hy 420-gel-hy 420-gen-hy 420-gep-hy 420-gep-hy-obligatoire 420-ges-hy 5rj 5rj-android 5rj-javase acceptance-testing aecgis agile ai airplay ajax alpine-linux analytics andengine android android-5-0 android-studio android-update-architecture angular angularjs api app-v ar arcade architecture arduino asp-net asynchronous-programming audio audio-analysis augmented-reality baas backbone-js banana-pi banana-pro banq bash battery bayes bb-8 bcjr bdd beast best-practices bi big-data bintray bluetooth boost-asio build-tool bytebuffer c camera cegep cg2 chess circuit citrix clojure clojurescript cloud cms cntk code-review code-structure collision-detection command-query-separation common-lisp completablefuture completionstage continuous-integration convolutional-coding couchbase cqrs cqs css css3 custom-language data-analysis data-center data-reporting data-storage data-story data-visualization database date-time dbms ddos deep-learning deep-search dependency-injection design-patterns devops dimensionality-reduction django docker dom drivers drone drum dsl e-commerce e-learning ebook ecmascript ecmascript-6 edgehtml efficacite-organisationnelle elasticsearch elearning elixir elk-stack embeded-systems encryption enterprise-search entity-framework erlang es2015 escher esp8266 event-driven examen excel exercices-java exfat express f facebook fat32 filechannel flask fonts for-dummies fpga functionnal-programming game-dev garbage-collector genetic-algorithm geospatial-analysis gimp git github go gof google google-analytics google-apps google-cloud googlecalendar gpio gps gpu gradle graph-database gui gvoice-texts hadoop haskell hci heroku hibernate high-availability hotspot-vm html html5 http-2 ide ifttt immutable-os intellij-idea internet-security ionic ios itil java java-9 java-ide java-module javascript javase jaxb jdbc jdk8 jeu jinja jit jmeter jms jpa jquery jsf json jta junit jvm kali-linux kibana kids kinect kotlin kubernetes laptop latex law-of-demeter ldpc learning legal libgdx linq linux load-balancing load-testing logic-programming logstash machine-learning magento mahout mathematiques maven mean meteo meteor-framework micro-framework microservices microsoft-azure midi mit-scratch mobile-app mongodb monitoring moodle ms-access ms-excel multithread music-instrument music-production musique mvc mvvm mxnet mysql neo4j netty network-as-a-service network-routing neural-networks neuro newsql nfc nixie-tube node-js nosql ntfs oauth open-source opencv opengl opengl-es openstack optimisation ospf otka outdoor-robot ov2460 pares-com pattern-matching pcie pdf pedagogie pentaho performance persistance php physique physique-quantique picat polarized-lenses powershell predictive-analytics privacy prolog pupillometry puppet puredata python qa quantum-computing quantum-gravity quantum-time r-language rails raspberry-pi react reactive-programming real-time refactoring regression-tree repl rest robot ros rpg rsa ruby rust salesforce san scala science scratch-jr scribus scrum search-engine security selenium selenuim-testing-tool semanticweb sensor seo serial-port serrurier serverless service service-manual servlet sitecore soap solar-system-simulator solaris solid solr solus spa spark spark-ml spdy specification sphero splunk spring spring-boot sql sql-server sqlite sre srp statistics statistiques stephanedenis-s-blurblog storm swift-2 tableau-publiic tdd telephonyapi tensorflow test test-driven-development thread threat-analysis time-banking travis-ci typography ubuntu uml unit-tests unity-3d unreal-game-engine usb user-story uwp virtualization-platform visual-studio visualstudio viterbi vmware vr vrealize vsphere wcf wcms wearable web web-design web-framework web-scraping webdriver webview windows windows-10 windows-server wine wireless wsdl wxpython xamarin xen xenapp xml zurb

How Kotlin’s “@Deprecated” Relieves Pain of Colossal Refactoring?


How Kotlin’s “@Deprecated" Relieves Pain of Colossal Refactoring?

I’m going to tell you a real story how we saved ourselves tons of time. The power of Kotlin’s @Deprecated refactoring is astounding.

Don’t think it is the same as Java’s @Deprecated. It is much more powerful. It allows for multi-step automated replacement.

So, let me tell you about it.

We were migrating our code base from Java to Kotlin. When somebody needed to touch an existing Java file, we would auto-convert it to Kotlin. And only then introduce change. Otherwise, all new code was written in Kotlin.

We were using mockito library in our unit tests. And as you might know, mockito heavily relies on Mockito.when method.

when is a reserved keyword in Kotlin. So we had to fence it with backticks.

To be honest, it looked quite ugly, and was awkward to type:

class AccountTest {
    private val statementReporter =
mock(AccountStatementReporter::class.java)

private val account = Account(statementReporter)
    @Test
fun `testing with Mockito when`() {
        `when`(statementReporter.canPrintItem())
.thenReturn(true)
        account.generateStatement()
        verify(statementReporter).printItem("-5,00\t73,91")
    }
}

At the time, we weren’t aware of the mockito-kotlin, so we have built our own whenever function to mitigate the keyword problem:

fun <T> whenever(x: T): OngoingStub<T> {
return `when`(x)
}

It is an alias for Mockito.when.

And it can be typed quickly, and IntelliJ helps with autocomplete. Which is nice:

    @Test
fun `testing with handcrafted whenever`() {
        whenever(statementReporter.canPrintItem())
.thenReturn(true)
        account.generateStatement()
        verify(statementReporter).printItem("-5,00\t73,91")
    }

Over the course of half a year, we had this whenever-based code everywhere.

Thousands of occurrences.

    @Test
fun `testing with handcrafted whenever – false case`() {
        whenever(statementReporter.canPrintItem())
.thenReturn(false)
        account.generateStatement()
        verify(statementReporter).canPrintItem()
verifyNoMoreInteractions(statementReporter)
    }

At that point, somebody has discovered BDDMockito API. It has allowed us to use slightly different words for when, thenReturn, thenThrow, and others.

Most importantly, it was part of the official mockito library!

By the way, mockito is usually used when testing in Java and Kotlin.

Curious about testing in Kotlin? Or Kotlin in general?

I suggest you download my free Ultimate Tutorial: Getting Started With Kotlin. You will be minutes away from your first full-fledged command-line application in Kotlin.

The tutorial is an 80-page book and is entirely hands-on. You can follow along with it while learning about various Kotlin and IntelliJ features. And lots of other things.

Anyways.

Here is an example of BDDMockito syntax for you:

    @Test
fun `testing with handcrafted whenever`() {
        given(statementReporter.canPrintItem())
.willReturn(true)
        account.generateStatement()
        verify(statementReporter).printItem("-5,00\t73,91")
    }

Of course, we wanted to switch from hand-crafted function to the official API!

By the way, BDDMockito is using slightly different naming. But the mocking structure is same:

// given vs when
given(statementReporter.canPrintItem())
// willReturn vs thenReturn
.willReturn(true)

As you can see, instead of when it allows us to use given, and all the then*methods become will*.

At that point, we decided to switch from whenever to given. We didn’t want to change thousands of occurrences in one go, as it will take a lot of time.

And we just might have changed some things that shouldn’t have been changed if we were to do the search & replace.

So, we decided to put a simple @Deprecated annotation on the wheneverfunction. That would make all the usages appear as deprecated (crossed out) in IntelliJ. New usages are unlikely to be introduced, as well.

As my pair was typing it out, I’ve noticed that IntelliJ was showing something interesting in the parameter list tooltip:

It got me curious about this replaceWith parameter. If I understood it correctly, this would allow some automated replacement.

We fiddled a bit with it. And it seemed like you can do such replacement.

Here is the code:

@Deprecated(
"use BDDMockito.given",
replaceWith = ReplaceWith(
"given(x)",
"org.mockito.BDDMockito.Companion.given"))
fun <T> whenever(x: T): OngoingStub<T> {
return `when`(x)
}

Now we tried to go to the usage of whenever, place our cursor on the deprecated whenever call, and press ALT + ENTER (the magic hotkey of IntelliJ).

What happened is amazing:

You can indeed do the automated replacement. And there is even an option to replace it in the WHOLE PROJECT!

Astounding!

But first, we decided to double-check how it will work just for that one occurrence.

That was the right decision:

It resulted in a compile error as BDDOngoingStub<T> doesn’t have thenReturnmethod defined on it. It should be willReturn.

And now it got us thinking.

What if we could define that method using “extension methods" feature of Kotlin? That would make things compile. Sure.

But then we still didn’t get rid of the custom code yet…

“So what if we deprecate that extension method too?!" — I asked.

“And then we can add replaceWith!" – my pair replied.

In rejoice, we started doing it:

fun <T> BDDOngoingStub<T>.thenReturn(x: T) {
willReturn(x)
}

That allowed us to fix the problem by auto-importing thenReturn extension:

And now, we were about to deprecate it with the same kind of annotation:

@Deprecated(
"use willReturn",
replaceWith = ReplaceWith(
"willReturn(x)"))
fun <T> BDDOngoingStub<T>.thenReturn(x: T) {
willReturn(x)
}

Now we were able to replace these thenReturn method calls with willReturn:

We could have run that replacement for the whole project, as well.

Neat.

We thought we were ready to do the “WHOLE PROJECT" refactoring:

That still forced us to do “ALT+ENTER" auto-importing in about 40 files.

But that, to be honest, was a quick one.

There is a shortcut to jump to the next compile error: CMD+ALT+DOWN. So the fix for that little problem was to press one shortcut and another right after.

Over and over and over again.

It took us a minute to do that.

We did the same “WHOLE PROJECT" replacement for thenReturn too:

That worked without any problems:

    @Test
fun `testing with handcrafted whenever`() {
        given(statementReporter.canPrintItem())
.willReturn(true)
        account.generateStatement()
        verify(statementReporter).printItem("-5,00\t73,91")
    }
    @Test
fun `testing with handcrafted whenever – false case`() {
        given(statementReporter.canPrintItem())
.willReturn(false)
        account.generateStatement()
        verify(statementReporter).canPrintItem()
verifyNoMoreInteractions(statementReporter)
    }

Finally, we double-checked that there are no occurrences of whenever, thenReturn, and thenThrow.

There wasn’t.

So we safely removed our testutils/whenever.kt file.

As you can guess, some wit goes into these @Deprecated definitions. But once in place – saves you a lot of time and relieves you of a lot of pain.

I genuinely thank you for reading this piece. It was quite a ride for me.

If you liked what you just read, I would appreciate some Medium claps! Social media shares are warmly welcome, as well. Same goes for the feedback!

If you are just starting in Kotlin, you will make me happy by downloading this free ultimate tutorial about getting started with Kotlin.

Thank you!



comments powered by Disqus