📜 ⬆️ ⬇️

Kotlin: combat use experience

Alexander Karyagin, a member of the ISDEF Association, leads marketing at Devexperts. We gave him and his colleagues a platform for a story about a fairly fresh development experience. We are Independent Developers :)

Recently, the growth of interest in the Kotlin programming language is about the same as the growth rate of Bitcoin. Increased attention is also due to the fact that in May 2017, Kotlin was declared the official development language for Android. Of course, we could not but join the study of this topic, and decided to experiment with Kotlin, applying it in one of the new projects for Android.

Kotlin (Kotlin) is a statically typed programming language that runs on top of the JVM and is developed by JetBrains. Kotlin combines the principles of an object-oriented and functional programming language. According to the developers, has such qualities as pragmatic, conciseness and interoperability (pragmatic, concise, interoperable). Programs written on it can be executed on a JVM or compiled into JavaScript, support for native compilation is not far off. It is important to note that the language was created simultaneously with the development tools and was originally sharpened for them.
')
To date, Kotlin devoted a lot of articles and made a lot of reports. We will try to focus not so much on the distinctive features of the language, to praise it or scold it, but rather on our practical experience of using the stated advantages.

So, first things first ...

Best tooling


The developer of the Kotlin language is JetBrains, which developed perhaps the best IDE for Java and many other programming languages. Despite all the verbosity of the Java language, the speed of writing remains very high: the environment “writes code” for you.

With Kotlin, it seems that you bought a new keyboard and still can’t get used to it and cannot type blindly. IntelliSense often just doesn’t keep up with typing speed, where for the Java IDE it will generate a whole class, for Kotlin you will look at the progress bar. And the problem is not only for new files: with active navigation through the IDE project, it just starts to hang and only its restart saves.

It upsets that many of the tricks you are used to simply stop working. For example, Live Templates. Android Studio - (IntelliJ IDEA version sharpened for Android development) comes with a set of convenient templates for frequently used operations, such as logging. The combination logm + Tab will insert a code for you that will write to the log a message about which method and with what parameters was called:

Log.d(TAG, "method() called with: param = [" + param + "]"); 

At the same time, this template “can” correctly determine the method and parameters, depending on where you applied it.

However, this does not work for Kotlin, moreover, you will have to create a separate template (for example, klogd + Tab) for Kotlin and use it depending on the programming language. The reason why for languages ​​that are 100% IDE compatible, you have to set the settings twice, remains a mystery to us.

Easy to learn


Kotlin, despite the possibility of compilation in JavaScript and potentially into native code (using Kotlin.Native), is primarily a language for the JVM and aims to save Java developers from unnecessary, potentially dangerous (in the sense of introducing bugs) boilerplate. However, it is a mistake to assume that you will write to Kotlin from the first lines on Kotlin. If you draw an analogy with languages, then at first you will write in “ruglish” with a strong Java accent. This effect is confirmed by reviewing your code, after some time, as well as observing the code of colleagues just starting to master the language. This is most noticeable in working with null and nonNull types, as well as excessive “verbosity” of expressions - habits that are hardest to fight. In addition, the presence of just a huge number of new features like extension-methods opens the “Pandora's Box” for writing black magic, adding unnecessary complexity where it is not needed, and also making the code more confusing, i.e. less adapted for review. What is it worth overloading the invoke () method [ more ], which allows you to disguise its call as a constructor call, so that visually creating an object of type Dog you get anything you want:

 class Dog private constructor() { companion object { operator fun invoke(): String = "MAGIC" } } object DogPrinter { @JvmStatic fun main(args: Array<String>) { println(Dog()) // MAGIC } 

Thus, despite the fact that it will take no more than a week to master the syntax, it may take more than a month to learn how to correctly use the features of the language. Some places will require a more detailed study of the principles of operation of one or another syntactic sugar, including the study of the obtained byte-code. When using Java, you can always refer to sources like Effective Java in order to avoid many troubles. Despite the fact that Kotlin was designed taking into account the “troubles” introduced by Java, the “troubles” introduced by Kotlin still have to be learned.

Null safety


Kotlin language has an elegant type system. It allows in most cases to avoid the most popular problem in Java - NullPointerException. Each type has two options depending on whether a variable of this type can be null. If a variable can be assigned a null, a question symbol is added to the type. Example:

 val nullable: String? = null val notNull: String = "" 

Variable nullable methods are invoked using the operator.? If such a method is called on a variable that has a value of null, the result of the entire expression also becomes null, the method will not be called, and a NullPointerException will not happen. Of course, the developers of the language left the way to call the method on a nullable variable, no matter what, and get a NullPointerException. For this instead? have to write !!:

 nullable!!.subSequence(start, end) 

This line immediately cuts the eye and makes the code less comfortable. Two consecutive exclamation marks increase the likelihood that such code will be written exclusively consciously. However, it is difficult to think of a situation in which it would be necessary to use the operator !! ..

Everything looks good until all the code is written in Kotlin. If Kotlin is used in an existing Java project, everything becomes much more complicated. The compiler cannot track in which variables null comes to us, and, accordingly, correctly determine the type. For variables that come from Java, null checks are missing at the time of compilation. Responsibility for choosing the right type falls on the developer. In this case, in order for automatic conversion from Java to Kotlin to work correctly, the @ Nullable / @ Nonnull annotations must be included in the Java code. A full list of supported annotations can be found at the link .

If null from Java code snuck into Kotlin, a crash will occur with the following exception:

FATAL EXCEPTION: main
Process: com.devexperts.dxmobile.global, PID: 16773
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.devexperts.dxmobile.global/com.devexperts.dxmarket.client.ui.generic.activity.GlbSideNavigationActivity}: java.lang.IllegalArgumentException: Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull, parameter savedInstanceState


By disassembling the bytecode, we find the place from which the exception was thrown:

ALOAD 1
LDC "param"
INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull(Ljava/lang/Object;Ljava/lang/String;)V


The fact is that for all parameters of non-private methods the compiler adds a special check: the standard library method is called
 kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull(param, "param") 

If desired, it can be disabled using the compiler directive
-Xno-param-assertions

Use this directive is only as a last resort, because it gives only a slight increase in performance to the detriment of the likely loss of reliability.

For all classes that have a get () method in Kotlin, you can use the operator []. It is very convenient. For example:

 val str = “my string” val ch = str[2] 

However, the access operator by index can only be used for non-null types. The nullable version does not exist, and in this case you have to explicitly call the get () method:

 var str: String? = null val ch = str?.get(2) 

Properties


Kotlin simplifies working with class fields. You can refer to the fields as you would to ordinary variables, and a getter or setter of the desired field will be called.

 // Java code public class IndicationViewController extends ViewController { private IndicationHelper indicationHelper; protected IndicationHelper getIndicationHelper() { return indicationHelper; } } 

 // Kotlin code val indicationViewController = IndicationViewController() val indicationHelper = indicationViewController.indicationHelper 

Everything is complicated if you want to override the Java getter class in the class on Kotlin. At first glance, it seems that indicationHelper is a full-fledged property compatible with Kotlin. In fact, it is not. If we try to override it head-on, we get a compilation error:

 class GlobalAccountInfoViewController(context: Context) : IndicationViewController(context) { protected open val indicationHelper = IndicationHelper(context, this) } 



Everything is done correctly: in the class the heir is declared a property whose getter has an absolutely identical signature to the getter of the superclass. What is wrong? The compiler takes care of us and believes that the override occurred by mistake. On this topic there is even a discussion on the Kotlin forum . From here we learn two important things:
  1. “Java getters are not seen as property accessors from Kotlin” - Getters in Java code are not visible from Cotlin as property.
  2. “This may be enhanced in the future, though” - there is hope that this will change for the better in the future.

There is also the only correct way to achieve our goal: to create a private variable and at the same time redefine the getter.

 class GlobalAccountInfoViewController(context: Context) : IndicationViewController(context) { private val indicationHelper = IndicationHelper(context, this) override fun getIndicationHelper() = indicationHelper } 

100% java interop


Perhaps it was worth putting this item in the first place, because it was Java-interop that allowed the new language to gain such popularity so quickly that even Google announced the official language support for Android development. Unfortunately, there were no surprises here either.

Consider something as simple and familiar to all Java developers as access modifiers or visibility modifiers. There are four things in Java: public, private, protected and package-private. Package-private is used by default, unless you specify otherwise. In Kotlin, the public modifier is used by default, and it, like protected and private, is called and works in exactly the same way as in Java. But the modifier package-private in Kotlin is called internal and it works a little differently.

Language designers wanted to solve a problem with the potential possibility to break encapsulation when using package-private modifier by creating a package with the same name in client code as in the library code and predetermining the desired method. Such a trick is often used when writing unit tests in order not to open the “out” method for testing purposes only. This is how the internal modifier appeared, which makes the object visible inside the module.

The module is called:

The problem is that actually internal is public final. Thus, when compiling at the level of bytes of code, it may happen that you accidentally override the method that you did not want to override. Because of this, the compiler renames your method so that it does not happen, which in turn will make it impossible to call this method from Java code. Even if the file with this code will be in the same module, in the same package.

 class SomeClass { internal fun someMethod() { println("") } } public final someMethod$production_sources_for_module_test()V 

You can compile your Kotlin code with an internal modifier and add it as a dependency to your Java project, in which case you can call this method where you wouldn’t give a protected modifier, that is, get access to the private API outside the package ( because the de facto method is public), although you cannot override it. It seems that the internal modifier was not designed as part of the “Pragmatic language”, but rather as an IDE feature. Given that this behavior could be done, for example, using annotations. Amid claims that Kotlin has very few keywords reserved, for example, for Corutin, internal actually nails your project on Kotlin to the IDE from JetBrains. If you are developing a complex project consisting of a large number of modules, some of which can be used as a dependency by colleagues, in a project in pure Java, think carefully about whether or not to write common parts on Kotlin.

Data classes


Next, perhaps one of the most famous features of the language is the data classes. Data classes allow you to quickly and easily write POJO objects, equals, hashCode, toString, and other methods for which the compiler will write for you.

This is a really handy thing, however, traps can wait for you in compatibility with the libraries used in the project. In one of our projects, we used Jackson to serialize / deserialize JSON. At that moment, when we decided to rewrite some POJOs on Kotlin, it turned out that Jackson annotations do not work correctly with Kotlin and you need to additionally connect a separate jackson-module-kotlin module for compatibility.

In conclusion


Summing up, I would like to say that despite the fact that the article may seem criticizing Kotlin, we like it! Especially on Android, where Java got stuck on version 1.6 - this was a real salvation. We understand that Kotlin.Native, Korutin and other new features of the language are very important and correct things, however, they are not for everyone. While IDE support is something that every developer uses, and IDE slow work eliminates all the speed benefits of switching from verbose Java to Kotlin. Whether switching to a new Kotlin or staying on Java for now is the choice of each individual team, we just wanted to share the problems we had to face in the hope that it might save some time.

The authors:
Timur Valeev, Software Engineer Devexperts
Alexander Vereshchagin, Software Engineer Devexperts

Source: https://habr.com/ru/post/336798/


All Articles