Auditing a mobile codebase before changing it
You have just taken over a mobile app. Maybe the original development team left, the agency contract ended, or your company acquired the product. Whatever the reason, you are now responsible for a codebase you did not write, with decisions you did not make, targeting deadlines you did not set. The first thing you need is a structured audit.
A codebase audit is not about judging the previous team. It is about building an accurate picture of what you are working with so you can make informed decisions about what to fix first, what risks are lurking, and how much effort future changes will require. Here is the checklist we use when inheriting a mobile app codebase.
Step 1: Dependency Inventory
Start with the dependency manifest. On Android, this is your build.gradle or build.gradle.kts files. On iOS, check Podfile, Package.swift, or Cartfile depending on which package manager is in use. Some older projects still use manually dragged-in frameworks with no package manager at all, which is itself a finding worth noting.
For each dependency, record the resolved version in the shipped build, its support status, relevant advisories, and the available upgrade path. Use the package manager, lockfiles, build output, and official release notes that apply to the repository; do not assume an update-report command is configured or complete.
Use the package ecosystem's audit tooling and an advisory database to identify known vulnerabilities, then establish whether the affected code and vulnerable path are present in the shipped application. A CVE identifier is a lead, not a severity decision. Prioritize from exploitability, exposure, data sensitivity, available mitigations, and the vendor's fix.
Flag a dependency when its current version is unsupported, an applicable advisory affects it, its platform compatibility blocks a required release, or its upgrade path crosses documented breaking changes. Record the specific constraint and validation work instead of scoring risk from version distance alone.
Step 2: Architecture Assessment
Map runtime responsibilities before naming an architecture. Trace one critical journey from UI event through state, persistence, network, and error handling. Labels such as MVVM or Clean Architecture are useful only when they help you find ownership and predict the effect of a change.
Check for separation of concerns. Are network calls happening directly in view controllers or activities? Is business logic mixed into the UI layer? Are there distinct data, domain, and presentation layers, or is everything in one place?
Record the UI, persistence, concurrency, and lifecycle mechanisms in use so you can trace runtime ownership and identify unsupported APIs. Framework choice or age is not an architecture grade; verify how state, cancellation, errors, and configuration changes behave along a critical journey.
Map navigation entry points, deep links, state restoration, authentication gates, and ownership of transitions. Exercise critical routes and back-stack behavior. A navigation graph, coordinator, or direct transition is useful evidence about implementation, but none is a quality signal on its own.
Step 3: Test Coverage Analysis
Run the existing test suite and measure what you actually have. On Android, ./gradlew testDebugUnitTest and ./gradlew connectedDebugAndroidTest will execute unit and instrumentation tests respectively. On iOS, xcodebuild test with the appropriate scheme runs the test target. Enable code coverage reporting to get a percentage.
The raw coverage number matters less than where the coverage exists. An app with 40% overall coverage but solid tests around the authentication flow, payment processing, and data persistence layer is in much better shape than an app with 70% coverage that only tests utility functions and data models.
Check whether the tests actually run. In many inherited codebases, the test target has been broken for months. Tests that do not compile or that have been skipped with annotations like @Ignore or disabled are effectively nonexistent. Count only tests that execute and pass.
Step 4: CI/CD Pipeline Review
Look for CI/CD configuration files: .github/workflows, Jenkinsfile, .gitlab-ci.yml, bitrise.yml, fastlane/Fastfile, or .circleci/config.yml. If none exist, determine how builds and releases are currently produced and record the reproducibility, access-control, and signing risks.
If a pipeline exists, check what it actually does. Does it run tests? Does it run lint checks? Does it build both debug and release variants? Does it handle code signing and provisioning profile management? Does it deploy to TestFlight or Google Play internal tracks, or does someone still upload builds manually?
Verify that the pipeline actually works by triggering a build. Stale CI configurations that reference old Xcode versions, expired signing certificates, or removed environment variables are common in inherited projects.
Step 5: Crash Analytics and Production Health
Find the production-health data before changing instrumentation. The app may use Crashlytics, Sentry, Bugsnag, Datadog, a vendor-specific system, or nothing. If telemetry is absent, document the privacy and release work required to add it rather than quietly shipping a new data collector.
Record crash-free users and sessions separately, with their time window, app version, and sample size. Compare releases and critical journeys against the product's own baseline. A universal percentage does not account for traffic, session length, or the consequence of the failure.
Segment failures by app version, OS version, device class, and critical journey. Prioritize statistically meaningful changes against the product's baseline, then reproduce or add diagnostic evidence before assigning a cause.
Step 6: Static Analysis and Change Hygiene
Locate the static-analysis and linter configuration, then run it with the repository's documented toolchain. Separate newly introduced violations from an accepted baseline, confirm which rules run in CI, and review findings by defect and release risk. A raw warning count is not a code-quality score.
Document formatter and editor configuration because inconsistent automation can create noisy diffs and hide meaningful changes. Treat formatting variance as a change-management concern, not evidence about the engineers or the correctness of the application.
Step 7: Build Configuration Audit
Review the build configurations and signing setup. On Android, check the build.gradle for signing configs, build types, product flavors, and ProGuard/R8 rules. On iOS, examine the Xcode project for build configurations, provisioning profiles, entitlements, and Info.plist values.
Verify that debug and release builds are properly separated. Check that API keys, backend URLs, and feature flags differ between environments. Hardcoded production API keys in debug builds or debug logging enabled in release builds are common findings that represent both security and performance risks.
Step 8: Documentation Gaps
Check for a README with build instructions. Can a new developer clone the repo and build the app on the first try? Missing environment variables, undocumented dependencies on local tools, or missing configuration files that are gitignored are the most common blockers.
Look for architecture decision records, API documentation, or any notes from the previous team about known issues or workarounds. The absence of documentation is itself a finding, and documenting what you learn during the audit is one of the most valuable outputs of this process.
Step 9: Technical Debt Scoring
Score each finding using consequence, likelihood, reach, detectability, and cost of delay. “No CI” may be urgent when releases cannot be reproduced and less urgent when the immediate risk is lost user data. Preserve the reasoning alongside the score.
Turn the findings into a backlog with evidence, an owner, a proposed verification method, and dependencies. Stabilize the delivery path before attempting broad refactors so each later change can be built, tested, released, and observed.
This structured approach transforms an unknown codebase into a known quantity with a clear path forward. You may not like everything you find, but at least you will know exactly what you are dealing with.
DEVSFLOW audits inherited applications and defines an evidence-based stabilization and production maintenance scope. Review the engagement structure.