Slide 1

Slide 1 text

BIBLIOTEK Construisez votre bibliothèque Java/Kotlin 06.06.2024 DEVFEST Design and Quality of France

Slide 2

Slide 2 text

Quelques chiffres •> 500k bibliothèques dans Maven Central (44TB)¹ •> 1 000 000 000 000 requêtes/an •190 bibliothèques dans nowinandroid² •39 dans le spring graphql initializr³ 1.https://mvnrepository.com/repos/central 2.https://github.com/android/nowinandroid 3.https://start.spring.io/

Slide 3

Slide 3 text

Libs Apps

Slide 4

Slide 4 text

xkcd: dependency

Slide 5

Slide 5 text

ÅGENDA 1 2 3 DEVFEST Design and Quality of France

Slide 6

Slide 6 text

ÅGENDA 1 2 3 !! DEVFEST Design and Quality of France Billy

Slide 7

Slide 7 text

ÅGENDA 1 2 3 DEVFEST Design and Quality of France

Slide 8

Slide 8 text

query GetUser { user { name address } } Apollo Kotlin* * https://github.com/apollographql/apollo-kotlin class User( val name: String, val address: String )

Slide 9

Slide 9 text

LA SURFACE D’API MINIMISER •apollo-compiler •apollo-runtime •apollo-normalized-cache •apollo-mockserver •apollo-tooling •apollo-cli •apollo-testing-support •apollo-mpp-utils •😬 •apollo-compiler •apollo-runtime •apollo-normalized-cache •apollo-mockserver •apollo-cli

Slide 10

Slide 10 text

LES DEPENDANCES MINIMISER •Minimiser le risque •Minimiser la complexité •Maximiser la compatibilité

Slide 11

Slide 11 text

LA CHARGE MENTALE MINIMISER

Slide 12

Slide 12 text

LA CHARGE MENTALE MINIMISER “There should be one-- and preferably only one --obvious way to do it.” — Zen of Pyhton

Slide 13

Slide 13 text

plugins { id("org.jetbrains.kotlin.jvm") } buildscript { dependencies { classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.0") } } dependencies { implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.0") } Ajouter un plugin Gradle? plugins { id("org.jetbrains.kotlin.jvm").version("2.0.0").apply(false) } plugins { alias(“libs.plugins.kotlin") }

Slide 14

Slide 14 text

LA PRÉCÉDENCE ÉVITER •Gradle properties •Themes Android •Apollo headers •… val apolloClient = ApolloClient.Builder() .addHttpHeader("conference", “Android Makers”) .build() apolloClient.query(myQuery) .addHttpHeader("conference", "Devfest Lille") .httpHeaders(emptyList()) .execute() fun ApolloClient.myQuery(query: Query): ApolloCall { return query(query).addHttpHeader("conference", "Devfest Lille") }

Slide 15

Slide 15 text

FAVORISER LA COMPOSITION fun MockServer.enqueueString(response: String) {…} fun MockServer.enqueueResponse(response: ApolloResponse) {…} fun MockServer.enqueueString(response: String) {…} fun ApolloResponse.serialize(): String {}

Slide 16

Slide 16 text

ÉCRIRE UNE API CHAINABLE val request = ApolloRequest.Builder(query) .httpHeader( "conference", "Devfest Lille” ) .fetchPolicy(CacheOnly) .build() apolloClient.execute(query) apolloClient.query(query) .httpHeader( "conference", "Devfest Lille” ) .fetchPolicy(CacheOnly) .execute()

Slide 17

Slide 17 text

DOCUMENTATION •Paramètres •Valeurs autorisées •Cas limites •Erreurs •@since, @see, @link etc… •Sample code

Slide 18

Slide 18 text

LES GUIDELINES UTILISER •Java •https://jlbp.dev/ •https://google.github.io/styleguide/javaguide.html •Kotlin •https://kotlinlang.org/docs/api-guidelines-introduction.html •https://github.com/androidx/androidx/tree/androidx-main/docs/ api_guidelines •Votre bibliothèque préférée

Slide 19

Slide 19 text

GESTION D’ERREUR

Slide 20

Slide 20 text

Google SDK index

Slide 21

Slide 21 text

GÉRER LES ERREURS •Checked exceptions? •Unchecked exceptions? •Sealed classes? •Result? •Untagged unions (KT-68296) •… https://elizarov.medium.com/kotlin-and-exceptions-8062f589d07

Slide 22

Slide 22 text

CHOISIR SON PUBLIC Java friendly Large Audience 30 ans d ’ experience @JvmName @JvmStatic @JvmOverloads Kotlin friendly Builder DSL Extensions Coroutines Compose Union types?

Slide 23

Slide 23 text

DOGFOODING

Slide 24

Slide 24 text

https://confetti-app.dev/

Slide 25

Slide 25 text

1x 1x 1x 1x 1x 1x 1x 1x MINIMALE TESTABLE PRÉVISIBLE TYPÉE DOCUMENTÉE COMPOSABLE COHÉRENTE CHAINABLE

Slide 26

Slide 26 text

ÅGENDA 1 2 3 DEVFEST Design and Quality of France

Slide 27

Slide 27 text

No content

Slide 28

Slide 28 text

2021: Jcenter sunset https://jfrog.com/blog/into-the-sunset-bintray-jcenter-gocenter-and-chartcenter/

Slide 29

Slide 29 text

Maven Central . └── com └── example └── coffee ├── 0.0.1 │ ├── coffee-0.0.1.jar │ ├── coffee-0.0.1.module │ └── coffee-0.0.1.pom └── maven-metadata-local.xml https://repo1.maven.org/maven2/ com.example:coffee:0.0.1 artifact group version

Slide 30

Slide 30 text

Maven != Maven Central •Maven = build tool •Maven Repository Layout = format¹ •Maven Central = repository •Sonatype 1.https://maven.apache.org/repository/layout.html

Slide 31

Slide 31 text

Maven Central in 2011* •4GB RAM •2x Xeon 1.6 GHz •286GB repo size •12B requêtes * https://www.sonatype.com/blog/2011/07/central-grows-up-see-the-history

Slide 32

Slide 32 text

Maven Central moderne* •S3 •Fastly •44TB repo size •>1Trillion requêtes S3 * circa 2022-2024

Slide 33

Slide 33 text

Maven Central •Gratuit •Immuable •Véri fi cations •Domaine •Licence, description •Signatures & checksums •Sources et javadoc •Potentiellement vides

Slide 34

Slide 34 text

Maven Central •S3 •Fastly •44TB repo size •>1Trillion requêtes S3

Slide 35

Slide 35 text

Maven Central •S3 •Fastly •44TB repo size •>1Trillion requêtes S3

Slide 36

Slide 36 text

No content

Slide 37

Slide 37 text

S01 Février 2021

Slide 38

Slide 38 text

No content

Slide 39

Slide 39 text

No content

Slide 40

Slide 40 text

Février 2024 Nouvelle interface Nouvelle API Pas de stats (pour l ’ instant) Pas de SNAPSHOTs

Slide 41

Slide 41 text

Publication •Maven Central •Top pour les utilisateurs •Moins top pour les contributeurs •Maven •sonatype/central-publishing-maven-plugin¹ •Gradle •vanniktech/gradle-maven-publish-plugin² •or just “vanniktech ’ s” 1.https://central.sonatype.org/publish/publish-maven/ 2.https://github.com/vanniktech/gradle-maven-publish-plugin

Slide 42

Slide 42 text

ÅGENDA 1 2 3 DEVFEST Design and Quality of France

Slide 43

Slide 43 text

COMPATIBILITÉ

Slide 44

Slide 44 text

INCOMPATIBILITÉS MAINTAINERS

Slide 45

Slide 45 text

SOURCE INCOMPATIBILITÉ 1/3 public class CoffeeMachine { public Drink brew(){ return new Coffee(); } }

Slide 46

Slide 46 text

SOURCE INCOMPATIBILITÉ 1/3 public class CoffeeMachine { public Drink brew() throws OutOfCoffeeException { return new Coffee(); } }

Slide 47

Slide 47 text

BINAIRE INCOMPATIBILITÉ 2/3 public class CoffeeMachine { public Drink brew(){ return new Coffee(); } }

Slide 48

Slide 48 text

BINAIRE INCOMPATIBILITÉ 2/3 public class CoffeeMachine { public Coffee brew(){ return new Coffee(); } }

Slide 49

Slide 49 text

APP LibA LibB LibCoffee brew() a.0.0 b.0.0 1.0.0 1.0.0 1.0.0

Slide 50

Slide 50 text

APP LibA LibB LibCoffee brew() a.0.0 b.1.0 1.0.0 1.0.1 1.0.1 LibCoffee brew() grind()

Slide 51

Slide 51 text

1.0.2 APP LibA LibB LibCoffee brew() 1.0.3 1.0.1 ?? 1.0.3 1.0.1

Slide 52

Slide 52 text

TERMINOLOGIE Compatibilité source •à la compilation • Ne pas casser Compatibilité binaire •à l ’ execution • Ne VRAIMENT pas casser

Slide 53

Slide 53 text

SOURCE & BINAIRE WHY NOT BOTH? public class CoffeeMachine { public Drink brew(){ return new Coffee(); } }

Slide 54

Slide 54 text

SOURCE & BINAIRE WHY NOT BOTH? public class CoffeeMachine { public Drink brew(){ return new Coffee(); } }

Slide 55

Slide 55 text

L’illusion de la compatibilité source 100% import coffee.*; import tea.*; public class Main { public static void main(String[] args) { Machine teaMachine = new Machine(); //... } }

Slide 56

Slide 56 text

COMPORTEMENTALE INCOMPATIBILITÉ 3/3 xkcd: work fl ow public class CoffeeMachine { public Coffee brew() { return new Coffee(“Peru"); } }

Slide 57

Slide 57 text

COMPORTEMENTALE INCOMPATIBILITÉ 3/3 xkcd: work fl ow public class CoffeeMachine { public Coffee brew() { return new Coffee("Colombia"); } }

Slide 58

Slide 58 text

OUTILS DEVFEST Design and Quality of France

Slide 59

Slide 59 text

COMPATIBILITY FLAGS java { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } tasks.withType().configureEach { options.release = 17 }

Slide 60

Slide 60 text

COMPATIBILITY FLAGS kotlin { compilerOptions { languageVersion.set(KotlinVersion.KOTLIN_2_0) apiVersion.set(KotlinVersion.KOTLIN_2_0) } coreLibrariesVersion = "2.0.0" }

Slide 61

Slide 61 text

https://semver.org/ MAJOR.MINOR.PATCH-prerelease •PATCH => bug fi x •MINOR => fonctionnalité •MAJOR => incompatibilité SEMVER SEMANTIC VERSIONING

Slide 62

Slide 62 text

0.x.y 1.0.0 1.x.y 2.0.0-alpha.x 2.0.0-beta.x 2.0.0-rc.x 2.0.0 2.0.1 • • • • • • • • •“J ’ ai tout cassé hier soir” •Bugs •Tests •Feature complete •Documentation •API stable •Migration guide •Battle tested •Long term support Documentez la stabilité de vos alpha Utilisez les alphas en production

Slide 63

Slide 63 text

“Breaking changes are broken” — Rich Hickey¹ 1.https://www.youtube.com/watch?v=oyLBGkS5ICk •Enlever du code produit des incompatibilités •Il suf fi t de ne jamais enlever (!) •A la place, changer le namespace •Renommer le groupId en com.example.lib2 •Renommer le package en lib2

Slide 64

Slide 64 text

private static final String[] KNOWN_UNSTABLE_API_ANNOTATIONS = { "org.jetbrains.annotations.ApiStatus.ScheduledForRemoval", "org.jetbrains.annotations.ApiStatus.Experimental", "org.jetbrains.annotations.ApiStatus.Internal", "com.google.common.annotations.Beta", "io.reactivex.annotations.Beta", "io.reactivex.annotations.Experimental", "rx.annotations.Experimental", "rx.annotations.Beta", "org.apache.http.annotation.Beta", "org.gradle.api.Incubating" }; @Incubating ANNOTATIONS @Incubating interface GradleLifecycle { @Incubating open fun beforeProject(action: IsolatedAction?) } StaticAnalysisAnnotationManager.java

Slide 65

Slide 65 text

@RequiresOptIn ANNOTATIONS @RequiresOptIn(level = RequiresOptIn.Level.WARNING) annotation class ExperimentalApolloApi @ExperimentalApolloApi fun downloadSchema(url: String): String { TODO() }

Slide 66

Slide 66 text

@RequiresOptIn ANNOTATIONS @RequiresOptIn(level = RequiresOptIn.Level.WARNING) annotation class ExperimentalApolloApi @ExperimentalApolloApi fun downloadSchema(url: String): String { TODO() }

Slide 67

Slide 67 text

@RequiresOptIn ANNOTATIONS @RequiresOptIn(level = RequiresOptIn.Level.WARNING) annotation class ExperimentalApolloApi @ExperimentalApolloApi fun downloadSchema(url: String): String { TODO() }

Slide 68

Slide 68 text

@Deprecated ANNOTATIONS public class ApolloClient implements Closeable { @Deprecated(since = "4.0") void stop() { close(); } @Override public void close() {} }

Slide 69

Slide 69 text

@Deprecated ANNOTATIONS class ApolloClient : Closeable { @Deprecated("Use close() instead", ReplaceWith("close()")) fun stop() {close()} override fun close() {} }

Slide 70

Slide 70 text

@Deprecated ANNOTATIONS class ApolloClient : Closeable { @Deprecated("Use close() instead", level = DeprecationLevel.ERROR) fun stop() {close()} override fun close() {} }

Slide 71

Slide 71 text

@Deprecated ANNOTATIONS class ApolloClient : Closeable { @Deprecated("Use close() instead", level = DeprecationLevel.HIDDEN) fun stop() {close()} override fun close() {} }

Slide 72

Slide 72 text

Deprecated Warning Deprecated Hidden Deprecated Error Stable CYCLE DE VIE Opt-In Removed

Slide 73

Slide 73 text

S ’ assurer qu ’ on ne casse pas l ’ API de manière accidentelle: •japicmp (jar)¹ •binary-compatibility-validator (kotlin)² •metalava (java + kotlin)³ API/ABI validation 1. https://github.com/siom79/japicmp 2. https://github.com/Kotlin/binary-compatibility-validator 3. https://android.googlesource.com/platform/tools/metalava/

Slide 74

Slide 74 text

METALAVA plugins { id("java") id("me.tylerbwong.gradle.metalava") version "0.3.5" } ./gradlew metalavaGenerateSignature ./gradlew metalavaCheckCompatibility

Slide 75

Slide 75 text

METALAVA ./gradlew metalavaGenerateSignature // Signature format: 4.0 package coffee { public class Coffee implements coffee.Drink {} public class CoffeeMachine { ctor public CoffeeMachine(); method public coffee.Drink! brew(); } public interface Drink {} }

Slide 76

Slide 76 text

public class CoffeeMachine { public Drink brew(){ return new Coffee(); } }

Slide 77

Slide 77 text

public class CoffeeMachine { public Drink brew() throws OutOfCoffeeException { return new Coffee(); } } lib/build/metalava/current.txt:9: error: Method coffee.CoffeeMachine.brew added thrown exception coffee.OutOfCoffeeException [ChangedThrows] Aborting: Found compatibility problems checking the public API (lib/build/metalava/current.txt) against the API in lib/api.txt ./gradlew metalavaCheckCompatibility

Slide 78

Slide 78 text

public class CoffeeMachine { public Drink brew(){ return new Coffee(); } }

Slide 79

Slide 79 text

public class CoffeeMachine { public Coffee brew(){ return new Coffee(); } } lib/build/metalava/current.txt:9: error: Method coffee.CoffeeMachine.brew has changed return type from coffee.Drink to coffee.Coffee [ChangedType] Aborting: Found compatibility problems checking the public API (lib/build/metalava/current.txt) against the API in lib/api.txt ./gradlew metalavaCheckCompatibility

Slide 80

Slide 80 text

No content

Slide 81

Slide 81 text

ÅGENDA 1 2 3 DEVFEST Design and Quality of France

Slide 82

Slide 82 text

•Minimisez! •Utilisez les guidelines •Dogfood! •Remerciez MavenCentral (meme si des fois…) •Gérez votre compatibilité binaire: •semver •@OptIn/@Deprecated •metalava & friends CONCLUSION

Slide 83

Slide 83 text

EFFET IKEA* *https://www.hbs.edu/ris/Publication%20Files/11-091.pdf

Slide 84

Slide 84 text

DEVFEST Design and Quality of France MERCÏ @martinbonnin