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

Common pitfalls in engineering teams and how to...

Common pitfalls in engineering teams and how to avoid them

Let’s be real: building amazing tech isn’t just about writing great code; it’s about having a team that works well together. As a freelancer, I’ve faced some of the same issues popping up repeatedly in different teams or companies. I will share the most common struggles tech teams face daily. Whether you’re a developer, a manager, or just someone who wants to improve the workplace, you’ll leave with practical tips to help your team work better together, stay motivated, and hit your goals!

Avatar for Renaud MATHIEU

Renaud MATHIEU

October 29, 2025
Tweet

More Decks by Renaud MATHIEU

Other Decks in Programming

Transcript

  1. Renaud Mathieu Common pitfalls in engineering teams and how to

    avoid them droidcon London 2025 @ r e n a u d m a t h i e u . c o m
  2. PRIDE Symptoms •Pride shows up when we build something overly

    sophisticated, not because it’s needed, but because it looks smart. •When we stop listening to our users or clients because we’re convinced we’re right.
  3. PRIDE Symptoms: over-designing interface WhateverScreenTimeBadgeSetHasSeenUseCase : CompletableUseCase<Boolean> class AScreenObserveStatusUseCaseImpl @Inject

    constructor( private val homeRepository: HomeRepository, private val whateverObserveAvailabilityUseCase: WhateverObserveAvailabilityUseCase, ) : WhateverScreenTimeBadgeSetHasSeenUseCase { override fun execute(params: Unit): Observable<Boolean> = Observable.combineLatest( whateverObserveAvailabilityUseCase.execute(Unit), homeRepository.observeHasSeenBadge(), ) { isAvailable, hasSeenBadge - > isAvailable && !hasSeenBadge } }
  4. PRIDE Symptoms: over-designing interface WhateverScreenTimeBadgeSetHasSeenUseCase : CompletableUseCase<Boolean> class AScreenObserveStatusUseCaseImpl @Inject

    constructor( private val homeRepository: HomeRepository, private val whateverObserveAvailabilityUseCase: WhateverObserveAvailabilityUseCase, ) : WhateverScreenTimeBadgeSetHasSeenUseCase { override fun execute(params: Unit): Observable<Boolean> = Observable.combineLatest( whateverObserveAvailabilityUseCase.execute(Unit), homeRepository.observeHasSeenBadge(), ) { isAvailable, hasSeenBadge - > isAvailable && !hasSeenBadge } }
  5. PRIDE Illustrations import androidx.compose.runtime.Immutable import androidx.compose.ui.graphics.Color import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.persistentListOf

    @Immutable data class CharacterDetailsViewStateUiModel( val id: Int, val topBarColor: Color, val name: String, val status: String, val species: String, val type: String, val gender: String, val imageUrl: String, val episodes: PersistentList<String>, ) { companion object { val DEFAULT = CharacterDetailsViewStateUiModel( id = -1, topBarColor = Color.Transparent, name = "-", status = "-", species = "-", type = "-", gender = "-", imageUrl = "", episodes = persistentListOf(), ) } }
  6. GREED Symptoms •Adding feature a f ter feature to the

    backlog, with no real sense of priority. •Stacking up projects (more repos, more microservices) without having the human or technical capacity to handle them all.
  7. GREED Illustrations •Unclear DoR/DoD: adding user stories to the backlog

    without ever validating the real acceptance criteria or the actual « De fi nition of Ready/Done ». •The backlog gets overloaded with tickets. •We end up with a massive work-in-progress… that never really progresses.
  8. GREED How to fi x this? Sequencer Red bin Pull

    f low Heijunka One Piece Flow Suspended
  9. GREED How to fi x this? Suspended ๏ Make the

    disturbance or anomaly visible
  10. GREED How to fi x this? Red bin ๏ Non-compliant

    parts detected as defective. ๏ Prompts for rapid problem resolution.
  11. GREED How to fi x this? One Piece Flow ๏

    One job at a time ๏ Quickly identify defects
  12. GREED How to fi x this? Heijunka ๏ This involves

    smoothing production based on demand to avoid peaks and troughs in load. ๏ Maintaining a continuous and stable f low
  13. GREED How to fi x this? Pull f low ๏

    VS push-it-in-your-face f low ๏ Instead of producing based on a forecasted plan (o f ten leading to overproduction) we wait for the next step to pull exactly what it needs, when it needs it.
  14. GREED How to fi x this? Timeboxing ๏ Fixed duration

    ๏ Focus on a single task ๏ Evaluation a f ter the timebox
  15. GREED How to fi x this? MoSCoW ๏ Must Have

    (M) ๏ Should Have (S) ๏ Could Have (C) ๏ Won’t Have (W)
  16. GREED How to fi x this? Impact ๏ % User

    Impact ๏ Analytics ๏ Know your users
  17. LUST Symptoms •Jumping on every new tech or framework without

    ever asking, “What’s the real ROI? Who’s going to maintain it?” •Living in a state of permanent proof-of-concept, never taking the time to stabilize a reliable foundation or pipeline.
  18. LUST How to fi x this? 2. Gradle Catalog 3.

    Gradle Convention 4. Gradle Managed Devices 1. Gradle Pro fi ler
  19. LUST How to fi x this? $ brew install gradle-profiler

    $ gradle-profiler -- benchmark assembleDebug ⏳
  20. # Can specify scenarios to use when none are specified

    on the command line default-scenarios = ["assemble"] # Scenarios are run in alphabetical order assemble { # Show a slightly more human-readable title in reports title = "Assemble" # Run the 'assemble' task tasks = ["assemble"] } clean_build { title = "Clean Build" versions = ["3.1", "/Users/me/gradle"] tasks = ["build"] gradle-args = [" -- parallel"] system-properties { "key" = "value" } cleanup-tasks = ["clean"] run-using = tooling-api // value can be "cli" or "tooling-api" daemon = warm // value can be "warm", "cold", or "none" measured-build-ops = ["org.gradle.api.internal.tasks.SnapshotTaskInputsBuildOperationType"] // see -- measure-build-op buck { targets = [" // thing/res_debug"] type = "android_binary" // can be a Buck build rule type or "all" } warm-ups = 10 } ⏳ $ brew install gradle-profiler $ gradle-profiler -- benchmark assembleDebug $ gradle-profiler -- benchmark -- scenario-file performance.scenarios clean_build
  21. run-using = tooling-api // value can be "cli" or "tooling-api"

    daemon = warm // value can be "warm", "cold", or "none" measured-build-ops = ["org.gradle.api.internal.tasks.SnapshotTaskInputsBuildOperationType"] // see -- measure-build-op buck { targets = [" // thing/res_debug"] type = "android_binary" // can be a Buck build rule type or "all" } warm-ups = 10 } ideaModel { title = "IDEA model" # Fetch the IDEA tooling model tooling-api { model = "org.gradle.tooling.model.idea.IdeaProject" } # Can also run tasks # tasks = ["assemble"] } toolingAction { title = "IDEA model" # Fetch the IDEA tooling model tooling-api { action = "org.gradle.profiler.toolingapi.FetchProjectPublications" } # Can also run tasks # tasks = ["assemble"] } androidStudioSync { title = "Android Studio Sync" # Measure an Android studio sync # Note: Android Studio Hedgehog (2023.1.1) or newer is required # Note2: you need to have local.properties file in your project with sdk.dir set android-studio-sync { # Override default Android Studio jvm args # studio-jvm-args = ["-Xms256m", "-Xmx4096m"] # Pass an IDEA properties to Android Studio. This can be used to set a registry values as well # idea-properties = ["gradle.tooling.models.parallel.fetch=true"] } } ⏳ $ brew install gradle-profiler $ gradle-profiler -- benchmark assembleDebug $ gradle-profiler -- benchmark -- scenario-file performance.scenarios clean_build
  22. LUST How to fi x this? 2. Gradle Catalog 3.

    Gradle Convention 4. Gradle Managed Devices 1. Gradle Pro fi ler
  23. 2. Gradle Catalog [versions] kotlin = "2.1.0" coroutines = "1.7.3"

    [libraries] kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" } kotlin-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" } [plugins] kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } dependencies { implementation(libs.kotlin.stdlib) implementation(libs.kotlin.coroutines) } plugins { id(libs.plugins.kotlin) } ✅ Bene fi ts 🎯 Single source of truth for each library 🔍 IDE auto-completion 🔄 Easier updates 🧼 Fewer chances of mistakes
  24. LUST How to fi x this? 2. Gradle Catalog 3.

    Gradle Convention 4. Gradle Managed Devices 1. Gradle Pro fi ler
  25. 3. Gradle Convention 🔁 Avoid duplicating Gradle code 🔧 Enforce

    consistent standards across modules ♻ Easily reuse shared con fi gurations 🧼 Keep each module’s build.gradle.kts clean and readable build-logic/ ├── build.gradle.kts ├── settings.gradle.kts └── convention/ ├── build.gradle.kts └── src/main/kotlin/ ├── AndroidApplicationConventionPlugin.kt ├── AndroidLibraryConventionPlugin.kt └── KotlinLibraryConventionPlugin.kt class AndroidLibraryConventionPlugin : Plugin<Project> { override fun apply(target: Project) { with(target) { pluginManager.apply("com.android.library") pluginManager.apply("org.jetbrains.kotlin.android") extensions.configure<LibraryExtension> { compileSdk = 35 defaultConfig { minSdk = 26 } buildFeatures { compose = true } } } } } // module: feature/home/build.gradle.kts plugins { alias(libs.plugins.nowinandroid.android.library) } // module: build-logic/convention/build.gradle.kts gradlePlugin { plugins { register("androidLibrary") { id = libs.plugins.nowinandroid.android.library.asProvider().get().pluginId implementationClass = "AndroidLibraryConventionPlugin" }
  26. LUST How to fi x this? 2. Gradle Catalog 3.

    Gradle Convention 4. Gradle Managed Devices 1. Gradle Pro fi ler
  27. 4. Gradle Managed Devices internal fun configureGradleManagedDevices( commonExtension: CommonExtension <*

    , *, *, *, *, *> , ) { val pixel4 = DeviceConfig("Pixel 4", 30, "aosp-atd") val pixel6 = DeviceConfig("Pixel 6", 31, "aosp") val pixelC = DeviceConfig("Pixel C", 30, "aosp-atd") val allDevices = listOf(pixel4, pixel6, pixelC) val ciDevices = listOf(pixel4, pixelC) commonExtension.testOptions { managedDevices { devices { allDevices.forEach { deviceConfig -> maybeCreate(deviceConfig.taskName, ManagedVirtualDevice :: class.java).apply { device = deviceConfig.device apiLevel = deviceConfig.apiLevel systemImageSource = deviceConfig.systemImageSource } } } groups { maybeCreate("ci").apply { ciDevices.forEach { deviceConfig - > targetDevices.add(devices[deviceConfig.taskName]) } } } } ./gradlew pixel4api30aospatdDebugAndroidTest ✅ Full automation for UI tests ✅ No need to launch the emulator manually ✅ Works seamlessly with CI (GitHub Actions, GitLab, etc.) ✅ Easy to con fi gure and integrate into your pipeline
  28. LUST How to fi x this? 2. Gradle Catalog 3.

    Gradle Convention 4. Gradle Managed Devices 1. Gradle Pro fi ler
  29. LUST How to fi x this? 2. Gradle Catalog 3.

    Gradle Convention 4. Gradle Managed Devices 1. Gradle Pro fi ler Build time Dependabot / Renovate Modules Tests
  30. ENVY Symptoms •Trying to copy what the “cool” teams are

    doing, without really understanding our own context. •Getting so focused on others’ success that we forget to actually talk to each other.
  31. GLUTTONY Symptoms •Multiplying meetings, reviews and boards (Kanban, Slack, etc.)

    until everyone is overwhelmed. •Constant context switching. •Excessive administrative processes.
  32. WRATH Symptoms •Merge con f licts… not just in Git,

    but between people. •No one’s really sure what “done” means, so we argue instead. •And suddenly, everyone wants to be right: code reviews turn sharp, Slack threads get spicy, and collaboration turns into competition.
  33. Th e bigger the pull request, the longer much the

    review takes. It’s not linear, it’s exponential.
  34. WRATH How to fi x this? • Why? Knowledge sharing.

    • What? PR follows established conventions. • Who? Who ultimately decides if the code is ready for use? • When? To be decided. • Where? Pairing / Remote screen sharing? Again and again
  35. WRATH How to fi x this? • Code Review Best

    Practices PULL_REQUEST_TEMPLATE.md CI Integration SCA / SAST
  36. WRATH Illustrations Hi 10:02 Hello 10:03 sup? 11:42 I’m fi

    ne and you? 11:42 I have a question for you 14:02 But it’s better if we talk about it later. When are you available? 14:02
  37. SLOTH Symptoms •We know we should add tests or monitoring…

    but « we’ll do it later. » •Pushing decisions to « later », delaying retros, and slowly abandoning continuous improvement.
  38. SLOTH Illustrations •« CI’s broken again? » •« We’ll fi

    x it in another ticket. » •« Preprod’s still down? » •« Yeah, just send me the secrets on Slack, don’t bother. » •« I have no idea! It’s Jean-Michel who usually does that. » •« We keep having the same problem… »
  39. SLOTH How to fi x this? Plan ๏ Identify a

    problem ๏ De fi ne an action plan ๏ Measure it!
  40. SLOTH How to fi x this? Do ๏ Implement the

    plan developed on a small scale to test its e f fectiveness.
  41. SLOTH How to fi x this? Act ๏ Evaluate the

    results achieved against the objectives set. ๏ Measure it!
  42. I had a productive day when… I was able to

    accomplish all of my day's tasks I have completed all the goals I set for myself I wrote more than 10 lines of code I wrote at least one line of code I am in a good mood and I slept well I have had enough quiet time to do what I need to do and I don't need to wait for answers from other people. When the weather is nice and the o ffi ce is quiet I didn't waste my time in meetings and I wasn't constantly interrupted.
  43. Kaizen Key principles • Continuous improvement isn’t about big revolutions.

    It’s about tiny steps that compound over time. • It works when everyone takes ownership of making things better, one fi x at a time. • We remove waste, we standardize what works, and we keep the loop going . • That’s how great teams grow, quietly, consistently, together.
  44. Thank you Renaud Mathieu @ r e n a u

    d m a t h i e u . c o m