Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Lessons From Librarians Makers

jbarr21
April 20, 2020
160

Lessons From Librarians Makers

jbarr21

April 20, 2020
Tweet

Transcript

  1. Photo by Gabriel Sollmann on Unsplash
    Lessons From a Librarian
    James Barr - @JBarr21
    Zac Sweers - @ZacSweers

    View full-size slide

  2. cutt.ly/lessons-from-libs

    View full-size slide

  3. cutt.ly/lessons-from-libs

    View full-size slide

  4. subscribe(new Consumer() {
    @Override
    public void accept(String value) {
    // Do stuff
    }
    })

    View full-size slide

  5. subscribe(new CrashOnErrorConsumer() {
    @Override
    public void accept(String value) {
    // Do stuff
    }
    })

    View full-size slide

  6. subscribe(new CrashOnErrorConsumer() {
    @Override
    public void accept(String value) {
    // Do stuff
    }
    })

    View full-size slide

  7. subscribe(new CrashOnErrorConsumer() {
    @Override
    public void accept(String value) {
    // Do stuff
    }
    })
    subscribe(new CrashOnErrorConsumer() {
    @Override
    public void accept(String value) {
    // Do stuff
    }
    })
    subscribe(new CrashOnErrorConsumer() {
    @Override
    public void accept(String value) {
    // Do stuff
    }
    })
    subscribe(new CrashOnErrorConsumer() {
    @Override
    public void accept(String value) {
    // Do stuff
    }
    })
    subscribe(new CrashOnErrorConsumer() {
    @Override
    public void accept(String value) {
    // Do stuff
    }
    })
    subscribe(new CrashOnErrorConsumer() {
    @Override
    public void accept(String value) {
    // Do stuff
    }
    })
    subscribe(new CrashOnErrorConsumer() {
    @Override
    public void accept(String value) {
    // Do stuff
    }
    })
    subscribe(new CrashOnErrorConsumer()
    @Override
    public void accept(String value) {
    // Do stuff
    }
    })
    subscribe(new CrashOnErrorConsumer() {
    @Override
    public void accept(String value) {
    // Do stuff
    }
    })
    subscribe(new CrashOnErrorConsumer() {
    @Override
    public void accept(String value) {
    // Do stuff
    }
    })
    subscribe(new CrashOnErrorConsumer() {
    @Override
    public void accept(String value) {
    // Do stuff
    }
    })
    subscribe(new CrashOnErrorConsumer() {
    @Override
    public void accept(String value) {
    // Do stuff
    }
    })
    subscribe(new CrashOnErrorConsumer() {
    @Override
    public void accept(String value) {
    // Do stuff
    }
    })

    View full-size slide

  8. subscribe(new CrashOnErrorConsumer() {
    @Override
    public void accept(String value) {
    // Do stuff
    }
    })

    View full-size slide

  9. subscribe(value -> {
    // Do stuff
    })

    View full-size slide

  10. subscribe(value -> {
    // Do stuff
    })
    github.com/uber/rxdogtag

    View full-size slide

  11. subscribe(new CrashOnErrorConsumer() {
    @Override
    public void accept(String value) {
    // Do stuff
    }
    })
    subscribe(new CrashOnErrorConsumer() {
    @Override
    public void accept(String value) {
    // Do stuff
    }
    })
    subscribe(new CrashOnErrorConsumer() {
    @Override
    public void accept(String value) {
    // Do stuff
    }
    })
    subscribe(new CrashOnErrorConsumer() {
    @Override
    public void accept(String value) {
    // Do stuff
    }
    })
    subscribe(new CrashOnErrorConsumer() {
    @Override
    public void accept(String value) {
    // Do stuff
    }
    })
    subscribe(new CrashOnErrorConsumer() {
    @Override
    public void accept(String value) {
    // Do stuff
    }
    })
    subscribe(new CrashOnErrorConsumer()
    @Override
    public void accept(String value) {
    // Do stuff
    }
    })
    subscribe(new CrashOnErrorConsumer() {
    @Override
    public void accept(String value) {
    // Do stuff
    }
    })
    subscribe(new CrashOnErrorConsumer() {
    @Override
    public void accept(String value) {
    // Do stuff
    }
    })
    subscribe(new CrashOnErrorConsumer() {
    @Override
    public void accept(String value) {
    // Do stuff
    }
    })
    subscribe(new CrashOnErrorConsumer() {
    @Override
    public void accept(String value) {
    // Do stuff
    }
    })
    subscribe(new CrashOnErrorConsumer() {
    @Override
    public void accept(String value) {
    // Do stuff
    }
    })
    subscribe(value -> {
    // Do stuff
    })

    View full-size slide

  12. subscribe(new CrashOnErrorConsumer() {
    @Override
    public void accept(String value) {
    // Do stuff
    }
    })
    subscribe(new CrashOnErrorConsumer() {
    @Override
    public void accept(String value) {
    // Do stuff
    }
    })
    subscribe(new CrashOnErrorConsumer() {
    @Override
    public void accept(String value) {
    // Do stuff
    }
    })
    subscribe(new CrashOnErrorConsumer() {
    @Override
    public void accept(String value) {
    // Do stuff
    }
    })
    subscribe(new CrashOnErrorConsumer() {
    @Override
    public void accept(String value) {
    // Do stuff
    }
    })
    subscribe(new CrashOnErrorConsumer() {
    @Override
    public void accept(String value) {
    // Do stuff
    }
    })
    subscribe(new CrashOnErrorConsumer()
    @Override
    public void accept(String value) {
    // Do stuff
    }
    })
    subscribe(new CrashOnErrorConsumer() {
    @Override
    public void accept(String value) {
    // Do stuff
    }
    })
    subscribe(new CrashOnErrorConsumer() {
    @Override
    public void accept(String value) {
    // Do stuff
    }
    })
    subscribe(new CrashOnErrorConsumer() {
    @Override
    public void accept(String value) {
    // Do stuff
    }
    })
    subscribe(new CrashOnErrorConsumer() {
    @Override
    public void accept(String value) {
    // Do stuff
    }
    })
    subscribe(new CrashOnErrorConsumer() {
    @Override
    public void accept(String value) {
    // Do stuff
    }
    })
    subscribe(value -> {
    // Do stuff
    })

    View full-size slide

  13. subscribe(new CrashOnErrorConsumer() {
    @Override
    public void accept(String value) {
    // Do stuff
    }
    })
    subscribe(new CrashOnErrorConsumer() {
    @Override
    public void accept(String value) {
    // Do stuff
    }
    })
    subscribe(new CrashOnErrorConsumer() {
    @Override
    public void accept(String value) {
    // Do stuff
    }
    })
    subscribe(new CrashOnErrorConsumer() {
    @Override
    public void accept(String value) {
    // Do stuff
    }
    })
    subscribe(new CrashOnErrorConsumer() {
    @Override
    public void accept(String value) {
    // Do stuff
    }
    })
    subscribe(new CrashOnErrorConsumer() {
    @Override
    public void accept(String value) {
    // Do stuff
    }
    })
    subscribe(new CrashOnErrorConsumer()
    @Override
    public void accept(String value) {
    // Do stuff
    }
    })
    subscribe(new CrashOnErrorConsumer() {
    @Override
    public void accept(String value) {
    // Do stuff
    }
    })
    subscribe(new CrashOnErrorConsumer() {
    @Override
    public void accept(String value) {
    // Do stuff
    }
    })
    subscribe(new CrashOnErrorConsumer() {
    @Override
    public void accept(String value) {
    // Do stuff
    }
    })
    subscribe(new CrashOnErrorConsumer() {
    @Override
    public void accept(String value) {
    // Do stuff
    }
    })
    subscribe(new CrashOnErrorConsumer() {
    @Override
    public void accept(String value) {
    // Do stuff
    }
    })
    subscribe(value -> {
    // Do stuff
    })
    And thousands more

    View full-size slide

  14. subscribe(value -> {
    // Do stuff
    })
    subscribe(value -> {
    // Do stuff
    })
    subscribe(value -> {
    // Do stuff
    })
    subscribe(value -> {
    // Do stuff
    })
    subscribe(value -> {
    // Do stuff
    })
    subscribe(value -> {
    // Do stuff
    })
    subscribe(value -> {
    // Do stuff
    })
    subscribe(value -> {
    // Do stuff
    })
    subscribe(value -> {
    // Do stuff
    })
    subscribe(value -> {
    // Do stuff
    })
    subscribe(value -> {
    // Do stuff
    })
    subscribe(value -> {
    // Do stuff
    })
    subscribe(value -> {
    // Do stuff
    })

    View full-size slide

  15. - Anonymous audience member
    "But how?!"

    View full-size slide

  16. Lessons from a Librarian

    View full-size slide

  17. "Writing Code That Lasts Forever" - Jesse Wilson - Droidcon NYC 2018

    View full-size slide

  18. Writing Code That Lasts
    Forever

    View full-size slide

  19. Helping Everyone Write
    Code That Lasts Forever

    View full-size slide

  20. Codebase Health
    ✅ Builds quickly
    ✅ Test coverage
    ✅ Well documented
    ❌ Duplicate code
    ❌ Deprecated APIs
    ❌ Large modules/classes

    View full-size slide

  21. Name Build Time
    Undocumented
    Public Methods
    Module Size
    Deprecated
    Method Calls
    Quality Score
    (Overall: 75%)
    app 5m25s 0 10,000 LOC 0 75%
    feature1 4m20s 0 120,000 LOC 0 50%
    feature2 20s 0 4,000 LOC 0 100%
    library1 7s 90 1,000 LOC 0 75%
    library2 5s 0 2,000 LOC 88 75%
    Codebase Health

    View full-size slide

  22. Name Build Time
    Undocumented
    Public Methods
    Module Size
    Deprecated
    Method Calls
    Quality Score
    (Overall: 75%)
    app 5m25s 0 10,000 LOC 0 75%
    feature1 4m20s 0 120,000 LOC 0 50%
    feature2 20s 0 4,000 LOC 0 100%
    library1 7s 90 1,000 LOC 0 75%
    library2 5s 0 2,000 LOC 88 75%
    Codebase Health

    View full-size slide

  23. How does tech debt
    happen?

    View full-size slide

  24. Limit Surface Area

    View full-size slide

  25. Limit Surface Area
    class Hammer {
    public void hammer(Nail nail) {
    // ow
    }
    }

    View full-size slide

  26. Limit Surface Area
    • Final by default
    final class Hammer {
    public void hammer(Nail nail) {
    // ow
    }
    }

    View full-size slide

  27. Limit Surface Area
    • Final by default
    • Visibility final class Hammer {
    public void hammer(Nail nail) {
    // ow
    }
    }

    View full-size slide

  28. Limit Surface Area
    • Final by default
    • Visibility
    • Factories
    final class Hammer {
    public static Hammer create() {
    // Stuff
    }
    private Hammer() { ... }
    }

    View full-size slide

  29. Limit Surface Area
    final class Hammer {
    public void hammer(Nail nail) {
    // ow
    }
    }
    • Final by default
    • Visibility
    • Factories

    View full-size slide

  30. API Design
    • Limit surface area
    • Final by default
    • Visibility
    • Factories
    • Consume interfaces
    interface Nail {
    void onHammer();
    int length();
    }

    View full-size slide

  31. API Design
    • Limit surface area
    • Final by default
    • Visibility
    • Factories
    • Consume interfaces
    interface Nail {
    void onHammer();
    int length();
    }

    View full-size slide

  32. - you, writing libraries
    "How easy is it to do the wrong
    thing?"

    View full-size slide

  33. - you, writing libraries
    "How easy is it to do the wrong
    thing?"
    "Is this going to limit me
    later?"

    View full-size slide

  34. API vs Implementation

    View full-size slide

  35. API vs Implementation
    class Hammer {
    public void hammer(Nail nail) {
    // ow
    }
    }

    View full-size slide

  36. API vs Implementation
    class Hammer {
    public void hammer(Nail nail) {
    // ow
    }
    }
    :hammer

    View full-size slide

  37. API vs Implementation
    class Hammer {
    public void hammer(Nail nail) {
    // ow
    }
    }
    :hammer
    libA
    libA
    App

    View full-size slide

  38. API vs Implementation
    class Hammer {
    public void hammer(Nail nail) {
    // ow
    }
    }
    :hammer
    libA
    libA
    App

    View full-size slide

  39. API vs Implementation
    class Hammer {
    public void hammer(Nail nail) {
    // ow
    }
    }
    :hammer
    libA
    libA
    App

    View full-size slide

  40. API vs Implementation
    class Hammer {
    public void hammer(Nail nail) {
    // ow
    }
    }
    :hammer
    libA
    libA
    App
    :hammer-api
    interface Hammer {
    void hammer(Nail nail)
    }

    View full-size slide

  41. API vs Implementation
    class Hammer {
    public void hammer(Nail nail) {
    // ow
    }
    }
    :hammer
    libA
    libA
    App
    :hammer-api
    interface Hammer {
    void hammer(Nail nail)
    }

    View full-size slide

  42. API vs Implementation
    class Hammer {
    public void hammer(Nail nail) {
    // ow
    }
    }
    :hammer
    libA
    libA
    App
    :hammer-api
    interface Hammer {
    void hammer(Nail nail)
    }

    View full-size slide

  43. BuildConfig
    public class BuildConfig {
    public static final String VERSION_NAME = "1.0";
    public static final int VERSION_CODE = 1;
    public static final boolean DEBUG == false;
    }

    View full-size slide

  44. BuildConfig
    public class BuildConfig {
    public static final String VERSION_NAME = "1.0";
    public static final int VERSION_CODE = 1;
    public static final boolean DEBUG == false;
    }
    public interface AppBuildConfig {
    String versionName();
    int versionCode();
    boolean isDebug();
    }

    View full-size slide

  45. BuildConfig
    final class RealAppBuildConfig implements AppBuildConfig {
    // ...
    }
    public interface AppBuildConfig {
    String versionName();
    int versionCode();
    boolean isDebug();
    }
    :appbuildconfig
    :app

    View full-size slide

  46. BuildConfig
    final class RealAppBuildConfig implements AppBuildConfig {
    // ...
    }
    public interface AppBuildConfig {
    String versionName();
    int versionCode();
    boolean isDebug();
    }
    :appbuildconfig
    :realappbuildconfig
    :app

    View full-size slide

  47. BuildConfig
    final class RealAppBuildConfig implements AppBuildConfig {
    // ...
    }
    public interface AppBuildConfig {
    String versionName();
    int versionCode();
    boolean isDebug();
    }
    :appbuildconfig
    :realappbuildconfig
    :app
    android {
    // or libraryVariants
    applicationVariants {
    generateBuildConfigProvider {
    enabled = true
    }
    }
    }

    View full-size slide

  48. More Resources
    • API Design As An Art: youtube.com/watch?v=Ve9HlnJ0wCM
    • Writing Code That Lasts Forever: youtube.com/watch?v=YZstpc2939s
    • Effective Java
    • AppBuildConfig pattern: github.com/ZacSweers/CatchUp/pull/200

    View full-size slide

  49. Static Analysis

    View full-size slide

  50. Static Analysis

    View full-size slide

  51. Static Analysis

    View full-size slide

  52. Removing Tech Debt

    View full-size slide

  53. Replace in Path (⌘⇧R)
    Find/Replace

    View full-size slide

  54. Find/Replace
    Replace in Path (⌘⇧R)
    Command line tools
    > find . -name "*.java" | xargs sed -i '' “s/import foo/import barr/g”

    View full-size slide

  55. Kotlin ReplaceWith
    class Example {
    fun foo(s: String) {a/* old */ }
    fun usage() {
    foo("hello")
    }
    }

    View full-size slide

  56. Kotlin ReplaceWith
    class Example {
    @Deprecated("Use Example.bar")
    fun foo(s: String) { /* old */ }
    fun bar(s: String) { /* new */ }
    fun usage() {
    foo("hello")
    }
    }

    View full-size slide

  57. Kotlin ReplaceWith
    class Example {
    @Deprecated("Use Example.bar", replaceWith = ReplaceWith(“bar(s)"))
    fun foo(s: String) { /* old */ }
    fun bar(s: String) { /* new */ }
    fun usage() {
    foo("hello")
    }
    }

    View full-size slide

  58. Kotlin ReplaceWith
    class Example {
    @Deprecated("Use Example.bar", replaceWith = ReplaceWith(“bar(s)"))
    fun foo(s: String) { /* old */ }
    fun bar(s: String) { /* new */ }
    fun usage() {
    foo("hello")
    }
    }

    View full-size slide

  59. Kotlin ReplaceWith
    class Example {
    @Deprecated("Use Example.bar", replaceWith = ReplaceWith(“bar(s)"))
    fun foo(s: String) { /* old */ }
    fun bar(s: String) { /* new */ }
    fun usage() {
    bar("hello")
    }
    }

    View full-size slide

  60. import android.util.Pair
    fun doSomething() {
    val pair = Pair.create(“a”, “b”)
    }
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    android:layout_width="match_parent"
    android:layout_height="match_parent">



    Android Lint SuggestedFix

    View full-size slide

  61. import androidx.core.util.Pair
    fun doSomething() {
    val pair = Pair.create(“a”, “b”)
    }
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    android:layout_width="match_parent"
    android:layout_height="match_parent">



    Android Lint SuggestedFix

    View full-size slide

  62. Structural Find & Replace
    void main(
    Example ex,
    Other other) {
    ex.foo()
    ex.foo(“hi")
    other.foo()
    }

    View full-size slide

  63. Structural Find & Replace
    void main(
    Example ex,
    Other other) {
    ex.foo()
    ex.foo(“hi")
    other.foo()
    }
    void main(
    Example ex,
    Other other) {
    ex.foo()
    ex.foo(“hi")
    other.foo()
    }

    View full-size slide

  64. Structural Find & Replace
    void main(
    Example ex,
    Other other) {
    ex.foo()
    ex.foo(“hi")
    other.foo()
    }
    $Instance$ type=com.example.Example
    .foo($Parameter$ [0,∞])
    $Instance$ type=com.example.Example
    .bar($Parameter$ [0,∞])

    View full-size slide

  65. Structural Find & Replace
    void main(
    Example ex,
    Other other) {
    ex.bar()
    ex.bar(“hi")
    other.foo()
    }
    $Instance$ type=com.example.Example
    .foo($Parameter$ [0,∞])
    $Instance$ type=com.example.Example
    .bar($Parameter$ [0,∞])
    youtrack.jetbrains.com/issue/KT-10176

    View full-size slide

  66. IntelliJ Plugin
    • Add your own refactor options
    • Leverage IntelliJ’s tools
    • Good for automating multiple
    refactor operations
    https://cutt.ly/find-matches-action

    View full-size slide

  67. Kotlin Script
    File(args[0]).walkTopDown()
    .filter { it.extension in setOf("java", "kt") }
    .filter { "/test/" !in it.absolutePath }
    .forEach {}

    View full-size slide

  68. Kotlin Script
    File(args[0]).walkTopDown()
    .filter { it.extension in setOf("java", "kt") }
    .filter { "/test/" !in it.absolutePath }
    .forEach { file: File ->
    val fileLines = file.readLines().toMutableList()
    // modify lines of file
    file.writeText(fileLines.joinToString("\n"))
    }

    View full-size slide

  69. Kotlin Script
    File(args[0]).walkTopDown()
    .filter { it.extension in setOf("java", "kt") }
    .filter { "/test/" !in it.absolutePath }
    .forEach { file: File ->
    val fileLines = file.readLines().toMutableList()
    // modify lines of file
    file.writeText(fileLines.joinToString("\n"))
    }
    $ brew install holgerbrandl/tap/kscript
    $ kscript MigrateToAndroidX.kts ~/project-root
    16,866 files changed, 31,454 insertions(+), 31,415 deletions(-)

    View full-size slide

  70. De-risking Change

    View full-size slide

  71. Shadow Jobs
    Android Gradle Plugin 4 beta/4.1 alpha
    Kotlin
    1.4-M1
    Gradle 6.4-rc
    -SNAPSHOT
    Androidx alphas
    JDK
    8-14
    Shadow Jobs

    View full-size slide

  72. Offensive Programming

    View full-size slide

  73. b7cb262 - Migrate lib1
    d43aa98 - Migrate lib2
    a1dc8d1 - Migrate lib3
    d843d8f - Migrate lib4
    vs
    a13ab55 - Migrate all libs

    Safety Without Feature Flags

    View full-size slide

  74. Photo by Webaroo on Unsplash

    View full-size slide

  75. Photo by Pankaj Patel on Unsplash

    View full-size slide

  76. Photo by Margarida CSilva on Unsplash

    View full-size slide

  77. Photo by Hannes Wolf on Unsplash

    View full-size slide

  78. Photo by Gabriel Sollmann on Unsplash
    Lessons From a Librarian
    James Barr - @JBarr21
    Zac Sweers - @ZacSweers

    View full-size slide