Slide 1

Slide 1 text

From Java 8 to Kotlin A series of small bang conversions Nat Pryce info@natpryce.com | @natpryce | github.com/npryce | speakerdeck.com/npryce

Slide 2

Slide 2 text

@

Slide 3

Slide 3 text

Coming up... What we did Overenthusiasm Culture Shock You can gradually convert Java to Kotlin without slowing delivery. We went too far so that you don't have to. Kotlin is a foreign country; they do things differently there.

Slide 4

Slide 4 text

Conversion

Slide 5

Slide 5 text

Culture Shock

Slide 6

Slide 6 text

Code layout and organisation The Kotlin style guide is not very extensive It seems trivial, but inconsistent conventions became annoying ● Code layout ● Code organisation ● Visibility modifiers Our project developed its own conventions … eventually.

Slide 7

Slide 7 text

It took us time to accept using null data class SubmissionDraft( override val id: DraftId, override val submissionStatus: SubmissionStatus, override val createdTimestamp: Instant, override val submittedTimestamp: Instant? = null, override val submissionId: SubmissionId?, override val decisions: UserDecisions )

Slide 8

Slide 8 text

Extension functions on nullable types "Elvis has left the building!" fun String?.orEmpty() = this ?: "" fun List?.orEmpty() = this ?: emptyList() fun T?.discardIf(p: (T)->Boolean) = this?.let { if (p(it)) null else this } But beware… val maybeCount: Int? = … maybeCount?.toString() // returns null or the count maybeCount.toString() // returns "null" or the count The type checker won't help: String is a subtype of String? Impossible to spot the difference in test diagnostics!

Slide 9

Slide 9 text

Exceptions are not type checked! sealed class Result data class Failure(val value: ErrorCode) : Result() data class Success(val value: T) : Result() inline fun Result.map(f: (T) -> U): Result = ... inline fun Result.flatMap(f: ( T) -> Result): Result = ... inline fun Result.forEach(f: (T) -> Unit): Unit = ... inline fun Result.orElse(f: (ErrorCode) -> T) = ... inline fun T?.asResultOr(errorCodeFn: () -> ErrorCode): Result< T> = ... ... etc.

Slide 10

Slide 10 text

Exceptions are not type checked! Technica l error? Further action? Catch as close as possible to failing call. Return error code in Result Translate to HTTP response in the web layer Let the exception propagate – the web layer will send a 500 response Yes No Yes No

Slide 11

Slide 11 text

Result instead of exceptions fun sendToEjp(draft: SubmissionDraft): Result = journals.getByPCode(draft. pCode) .flatMap { journal -> journal.ejpJournalId .asResultOr { PeerReviewSystemNotSupported() } .flatMap { ejpJournalId -> payloadConverter.convertForCreate(draft) .flatMap { requestJson -> val uri = EjpUrlRoutes. create.path(ejpJournalId) sendCreate(uri, payload) .flatMap { responseJson -> responseJson.toSubmissionId() } } } } Type safe, but...

Slide 12

Slide 12 text

Accept early returns in functional code inline fun Result.onFailure(handler: (Failure) -> Nothing): T = when (this) { is Failure -> handler( this) is Success -> value } fun create(draft: SubmissionDraft): Result { val journal = journals.getByPCode(draft. pCode) ?: return Failure(JournalNotFound(draft. pCode)) val journalId = journal. ejpJournalId ?: return Failure(PeerReviewSystemMisconfiguration()) val ejpPayload = ejpPayloadConverter.convertForCreate(draft) .onFailure { return it } val uri = EjpUrlRoutes. createPath.path(journalId) val responseJson = sendCreate(uri, payload) .onFailure { return it } return responseJson.toSubmissionId() }

Slide 13

Slide 13 text

Overenthusiasm

Slide 14

Slide 14 text

Infixation val config = EnvironmentVariables overriding ConfigurationProperties.fromResource("default.conf") infix fun Configuration.overriding(defaults: Configuration) = Override(this, defaults) (n) An unhealthy obsession with infix functions. fun Author.toJson(): JsonNode = obj( "name" of name, "affiliation" of affiliation.toJson(), "email" of emailAddress ) Unnecessary (but published, so can't be changed without breaking code) Useful

Slide 15

Slide 15 text

Operator punning Prefer conventional operators or clearly named functions val submissionId = PathElement(::SubmissionId) val fileId = PathElement(::FileId) val submissionPath = "submission"/submissionId val submissionFilePath = submissionPath/"file"/fileId WTF?!?

Slide 16

Slide 16 text

Spaces in Identifiers private fun String.`fromCamelCaseTo space separated`() = this.replace(wordBoundaryRegex, "$1 $2") private val wordBoundaryRegex = Regex("(\\p{Ll})(\\p{Lu})") @Test fun `an author cannot edit another author's submission`() … Too jokey & makes client code hard to read Ok – test names act as documentation in IDE views & no client code

Slide 17

Slide 17 text

Nesting apply, with, extension functions, extension function literals, extension functions as instance methods, ...

Slide 18

Slide 18 text

The beginning... info@natpryce.com | @natpryce | github.com/npryce | speakerdeck.com/npryce (who are hiring in London & Berlin) https://www.springernature.com/gp/group/careers Thanks to