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

Developer Productivity On a Budget

Adam Ahmed
October 26, 2023

Developer Productivity On a Budget

Adam Ahmed

October 26, 2023
Tweet

More Decks by Adam Ahmed

Other Decks in Programming

Transcript

  1. Jordan Terry — Senior Android Engineer at Marks & Spencer

    - @jordanterry Developer Productivity On a Budget Adam Ahmed - Senior Android Engineer at TravelPerk — @oheyadam
  2. Background •We both work on Platform/Foundations teams •Mid-sized mobile orgs

    (30+ Engineers) •Our teams’ focus is improving developer productivity
  3. Background •We both work on Platform/Foundations teams •Mid-sized mobile orgs

    (30+ Engineers) •Our teams’ focus is improving developer productivity •The things we’ll talk about were born of necessity to our organizations
  4. What We’ll Cover •Automating bug reporting and triaging •Rolling out

    convention plugins •Automating our release trains
  5. What We’ll Cover •Automating bug reporting and triaging •Rolling out

    convention plugins •Automating our release trains •Reporting build times
  6. What We’ll Cover •Automating bug reporting and triaging •Rolling out

    convention plugins •Automating our release trains •Reporting build times •Improving our CI workflows
  7. What We’ll Cover •Automating bug reporting and triaging •Rolling out

    convention plugins •Automating our release trains •Reporting build times •Improving our CI workflows •Linting!
  8. What was the problem here? •Different squads own different parts

    of the code-base •We wanted a way to immediately find out when a crash happens @oheyadam
  9. What was the problem here? •Different squads own different parts

    of the code-base •We wanted a way to immediately find out when a crash happens •Each squad has their own Jira board @oheyadam
  10. What does it do? •Runs as a cron job •Checks

    if there are any new crashes @oheyadam
  11. What does it do? •Runs as a cron job •Checks

    if there are any new crashes •Parses stack-traces and figures out who the owner is @oheyadam
  12. What does it do? •Runs as a cron job •Checks

    if there are any new crashes •Parses stack-traces and figures out who the owner is •Talks to OpenAI to try to suggest a solution @oheyadam
  13. What does it do? •Runs as a cron job •Checks

    if there are any new crashes •Parses stack-traces and figures out who the owner is •Talks to OpenAI to try to suggest a solution •Opens a detailed Jira ticket on their board @oheyadam
  14. What does it do? •Sends them a Slack notification •Allows

    us to customize bug reporting logic for teams @oheyadam
  15. What does it do? •Sends them a Slack notification •Allows

    us to customize bug reporting logic for teams •Allows us to do the same for other tools like Datadog @oheyadam
  16. What does it do? •Sends them a Slack notification •Allows

    us to customize bug reporting logic for teams •Allows us to do the same for other tools like Datadog •Works for both platforms @oheyadam
  17. Problem Build configuration contained large amounts of copy and pasted

    duplication. As we modularised more, this would become a maintenance problem. @jordanterry
  18. Solution The platform team wanted to ensure we could scale

    our build configuration to achieve our modularisation goals. To achieve this, we adopted Gradle convention plugins. @jordanterry
  19. State of the build files @jordanterry compileOptions { sourceCompatibility =

    JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 }
  20. State of the build files @jordanterry android { compileSdkVersion rootProject.compileSdkVersion

    defaultConfig { minSdkVersion rootProject.minSdkVersion targetSdkVersion rootProject.targetSdkVersion } }
  21. State of the build files @jordanterry • Duplications • Inconsistencies

    • Crucial applied scripts • Maintenance headaches
  22. The Goal plugins { alias(libs.plugins.mns.android.application) } android { namespace =

    “com.mns.some.module” } dependencies { implementation(libs.some.dependency) } @jordanterry
  23. The two types of convention - Script import com.android.build.gradle.LibraryExtension plugins

    { id("com.android.library") kotlin("android") } // Configure Android configuration, including extensions.getByType<LibraryExtension>().apply { compileSdk = 33 defaultConfig { setMinSdkVersion(23) setTargetSdkVersion("33") testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } compileOptions { targetCompatibility = JavaVersion.VERSION_1_8 sourceCompatibility = JavaVersion.VERSION_1_8 } kotlinOptions { jvmTarget = JavaVersion.VERSION_1_8.toString() } } @jordanterry
  24. The two types of convention - A plugin class KotlinAndroidLibraryPlugin

    : Plugin<Project> { override fun apply(target: Project) { with(target) { pluginManager.apply("com.android.library") pluginManager.apply("org.jetbrains.kotlin.android") // Configure Android configuration. extensions.getByType<LibraryExtension>().apply { compileSdk = ANDROID_COMPILE_SDK lint { @Suppress("UnstableApiUsage") baseline = file("baseline-lint.xml") } configureAndroidExtension(ANDROID_MIN_SDK, ANDROID_TARGET_SDK) } configureJavaJvm(JAVA_LANGUAGE_VERSION) configureKotlinJvm(JAVA_LANGUAGE_VERSION) configureJunit() configureSpotless() configureJacocoAndroid(extensions.getByType<LibraryAndroidComponentsExtension>()) } } } @jordanterry
  25. What were the benefits? • Removed 1,000s of lines of

    build configuration from the project @jordanterry
  26. What were the benefits? • Removed 1,000s of lines of

    build configuration from the project • We can roll out new build features with low effort @jordanterry
  27. What were the benefits? • Removed 1,000s of lines of

    build configuration from the project • We can roll out new build features with low effort • We have unit tests, functional and integration tests for our builds
  28. State of a build file now plugins { alias(libs.plugins.mns.android.library) }

    android { namespace = "com.marksandspencer.androidfeatureflaglib" testOptions { unitTests { isReturnDefaultValues = true } } } dependencies { implementation(project(":data:logging:api")) implementation(libs.coroutines) implementation(libs.lifecycle.common.java8) testImplementation(project(":testutils")) testImplementation(libs.bundles.junit5.kotlin) testImplementation(libs.arch.core.testing) testImplementation(libs.mockk) testImplementation(libs.truth) testImplementation(libs.coroutines.test) testRuntimeOnly(libs.bundles.junit5.runtime) }
  29. Step by step? - Move to a script import com.android.build.gradle.LibraryExtension

    plugins { id("com.android.library") kotlin("android") } // Configure Android configuration, including extensions.getByType<LibraryExtension>().apply { compileSdk = 33 defaultConfig { setMinSdkVersion(23) setTargetSdkVersion("33") testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } compileOptions { targetCompatibility = JavaVersion.VERSION_1_8 sourceCompatibility = JavaVersion.VERSION_1_8 } kotlinOptions { jvmTarget = JavaVersion.VERSION_1_8.toString() } } @jordanterry
  30. Step by step? - Move to a script import com.android.build.gradle.LibraryExtension

    plugins { id("com.android.library") kotlin("android") } // Configure Android configuration, including extensions.getByType<LibraryExtension>().apply { compileSdk = 33 defaultConfig { setMinSdkVersion(23) setTargetSdkVersion("33") testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } compileOptions { targetCompatibility = JavaVersion.VERSION_1_8 sourceCompatibility = JavaVersion.VERSION_1_8 } kotlinOptions { jvmTarget = JavaVersion.VERSION_1_8.toString() } } @jordanterry
  31. Lessons learned • Evangelise for idiomatic Gradle concepts • Don’t

    keep your conventions in the build-logic module forever @jordanterry
  32. Lessons learned • Evangelise for idiomatic Gradle concepts • Don’t

    keep your conventions in the build-logic module forever • Think carefully about what you include in your conventions @jordanterry
  33. Who and why would benefit? @jordanterry •The medium sized development

    team • More devs can do their work without worrying about build configuration • Helps the devs help the business
  34. Other resources •Herding Elephants - https:// developer.squareup.com/blog/herding- elephants/ •Stampeding Elephants

    - https:// developer.squareup.com/blog/stampeding- elephants/ •Now in Android - https://github.com/ android/nowinandroid
  35. Other resources •Herding Elephants - https:// developer.squareup.com/blog/herding-elephants/ •Stampeding Elephants -

    https:// developer.squareup.com/blog/stampeding-elephants/ •Now in Android - https://github.com/android/ nowinandroid •Idiomatic Android - https://github.com/jjohannes/ idiomatic-gradle
  36. Other resources •Herding Elephants - https://developer.squareup.com/blog/ herding-elephants/ •Stampeding Elephants -

    https://developer.squareup.com/ blog/stampeding-elephants/ •Now in Android - https://github.com/android/nowinandroid •Idiomatic Android - https://github.com/jjohannes/ idiomatic-gradle •Gradle Docs - https://docs.gradle.org/current/userguide/ userguide.html
  37. What was the problem? Preparing for a new release is

    a lengthy manual process @oheyadam
  38. Preparing for a new release is a lengthy manual process

    • Announce a code freeze @oheyadam
  39. Preparing for a new release is a lengthy manual process

    • Announce a code freeze • Find and notify the release captain @oheyadam
  40. Preparing for a new release is a lengthy manual process

    • Announce a code freeze • Find and notify the release captain • Notify everyone else in the mobile org @oheyadam
  41. Preparing for a new release is a lengthy manual process

    • Announce a code freeze • Find and notify the release captain • Notify everyone else in the mobile org • Create a release branch, update version codes, download latest strings @oheyadam
  42. Preparing for a new release is a lengthy manual process

    • Create a release branch, update version codes, download latest strings • Document the release version and notes somewhere @oheyadam
  43. Preparing for a new release is a lengthy manual process

    • Create a release branch, update version codes, download latest strings • Document the release version and notes somewhere • Ensure tests pass and QA is done @oheyadam
  44. Preparing for a new release is a lengthy manual process

    • Ensure tests pass and QA is done • Upload to Play Store @oheyadam
  45. Preparing for a new release is a lengthy manual process

    • Ensure tests pass and QA is done • Upload to Play Store • Potentially have to repeat some of these steps if someone pushes to the release branch again, or wants to create a hot-fix @oheyadam
  46. Automate it! We drive the release stages using Calendar events,

    Zapier, Fastlane, and our CI provider @oheyadam
  47. Solution Build a plugin to report build data to our

    data engine. This data can now be used to build dashboards, or be directly used in our OKRs. @jordanterry
  48. How? - Gluing it together class ObservabilityPlugin : Plugin<Project> {

    override fun apply(target: Project) { val plugin = TalaiotBasePlugin() plugin.apply(target) target.extensions.configure(BaseExtension::class.java) { publishers { customPublishers(NewRelicBuildPublisher(…)) } } } } @jordanterry
  49. Does this help developer productivity? • Not directly • Dashboarding

    and alerting can act as a canary for bad changes, or celebrations for good changes @jordanterry
  50. Does this help developer productivity? • Not directly • Dashboarding

    and alerting can act as a canary for bad changes, or celebrations for good changes • Data helps build a case for contributing time towards platform initiatives @jordanterry
  51. The elephant in the room • Gradle Enterprise (Devlocity) is

    the best tool for this • Incredible insights into your builds @jordanterry
  52. The elephant in the room • Gradle Enterprise (Devlocity) is

    the best tool for this • Incredible insights into your builds • https://gradle.com/develocity/ @jordanterry
  53. What was the problem here? •CI builds take too long

    •PRs stay up for a long time @oheyadam
  54. What was the problem here? •CI builds take too long

    •PRs stay up for a long time •Product iterations are slow @oheyadam
  55. What was the problem here? •CI builds take too long

    •PRs stay up for a long time •Product iterations are slow •Mobile org doesn’t use the same CI provider as the rest of the company @oheyadam
  56. What did we do? •Investigated our current CI setup •Migrated

    to a different provider •CCI allowed us to tap into existing knowledge and infrastructure @oheyadam
  57. What did we do? •Investigated our current CI setup •Migrated

    to a different provider •CCI allowed us to tap into existing knowledge and infrastructure •Utilized remote build cache on our CI @oheyadam
  58. What did we do? •Utilized remote build cache on our

    CI •Utilized CI parallelism @oheyadam
  59. What did we do? •Utilized CI parallelism •Fan-out •Removed any

    unnecessary steps / downloads •Reduced the execution time of our PR checks by up to 80% @oheyadam
  60. What did we do? •Reduced the execution time of our

    PR checks by up to 80% •Reduced the execution time of our nightly / E2E tests / release jobs by up to 60% @oheyadam
  61. What did we do? •Reduced the execution time of our

    PR checks by up to 80% •Reduced the execution time of our nightly / E2E tests / release jobs by up to 60% •Happy, productive developers! @oheyadam
  62. Problem How can we make our code consistent? Whilst keeping

    the developer experience consistent? @jordanterry
  63. The goal • Reduce bike shedding in pull requests •

    Keep the feedback loop as low as possible @jordanterry
  64. The goal • Reduce bike shedding in pull requests •

    Keep the feedback loop as low as possible •Pick up as many issues as early as possible @jordanterry
  65. Spotless + KtLint https://github.com/di ff plug/spotless/tree/main/plugin-gradle • Adopt Spotless Gradle

    plugin • Adopt KtLint • Highlights issues • Autofixes issues @jordanterry
  66. Spotless + KtLint https://github.com/di ff plug/spotless/tree/main/plugin-gradle • Adopt Spotless Gradle

    plugin • Adopt KtLint • Highlights issues • Autofixes issues @jordanterry
  67. How we rolled it out • Integrate Spotless into our

    conventions plugins @jordanterry
  68. How we rolled it out • Integrate Spotless into our

    conventions plugins • Write some tests! @jordanterry
  69. How we rolled it out • Integrate Spotless into our

    conventions plugins • Write some tests! • Roll out throughout codebase with a version bump @jordanterry
  70. Linting Gradle? • Lint your dependencies • Removing unused dependencies

    helps your project • Be aware of dependency changes @jordanterry
  71. Linting Gradle? • Lint your dependencies • Removing unused dependencies

    helps your project • Be aware of dependency changes • Lint your Gradle plugins @jordanterry
  72. Move to the IDE! • Use IntelliJ’s built in functionality

    • Find tooling that provides plugins @jordanterry
  73. Move to the IDE! • Use IntelliJ’s built in functionality

    • Find tooling that provides plugins • IDEs have the quickest feedback loop @jordanterry
  74. Move to the IDE! • Use IntelliJ’s built in functionality

    • Find tooling that provides plugins • IDEs have the quickest feedback loop • Change your CI to a validation tool @jordanterry