A typical part of our client onboarding process involves conducting a code assessment. Depending on client needs, a code assessment can be quite thorough, or simply provide an overview of the main characteristics that we should focus on as a team. With each assessment, we provide our recommendations for fixing any issues we spot in order of priority, which helps us plan the best way to get started together.
Digging into the code also helps us become familiar with the app’s structure so that we can begin to provide value right away — whether it’s building out a new feature or refactoring a part of the codebase to solve a pain point. We can also get an overview of code quality and if there is anything that needs to be fixed to ensure that the codebase is scalable.
As consultants, we see and work in a lot of codebases. We have a proven approach to building scalable codebases, so we can quickly determine which codebases are solid, and which ones may need some work to get them up to our standards.
Here are the top five things we look for when assessing Flutter codebases and why they matter:
1. Tests ✅
The number one thing we look for are tests. In order of priority, we look for unit tests, then widget tests, and finally, integration tests. If there are no tests present, this is a huge red flag. No tests means that the code may not be properly functioning, adding new features or performing refactors may break existing code, bugs may be hard to track down, and there may be other unsavory side effects. Flutter comes with a testing suite built into the SDK, so there really is no excuse to forego writing tests.
We strive for 100% code coverage in all of our projects, but when assessing codebases, the presence of some tests is better than none. It’s much easier to raise the code coverage threshold gradually than start writing tests from scratch. Oftentimes, the absence of tests is accompanied by code that is not testable. Comprehensive tests are a great sign of a well-structured, healthy codebase.
2. Latest version of Flutter and dependencies ✅
This may seem obvious, but using the latest stable version of Flutter is crucial. It’s a simple way to ensure you’re taking full advantage of the latest Flutter features and bug fixes. It’s also very important to take time to upgrade when a new version is released to avoid falling far behind and accumulating tech-debt.
In contrast, using an old version of Flutter can have a few undesired outcomes. Dependencies can become outdated, and you could be missing out performance improvements or new features in the latest versions of Flutter and Dart. We recommend using the latest stable version of Flutter whenever possible, as well as keeping all dependencies up to date.
3. Well-organized project structure ✅
A well-organized codebase can be a good indicator of code quality. It can also help separate business logic from the UI, which is something we recommend when building a scalable code base.
🛑 A not-so-great codebase may look like this:
- A single file that contains the entire app.
- Presence of a “common” or “shared” folder in a codebase that contains lots of miscellaneous code.
- Codebase organized by component, rather than feature (e.g. folder called “screens” that contains all of the app screens).
✅ A very good codebase may look like this:
- One folder per feature, with each folder containing only the code for that feature. You should be able to look at the project structure and know exactly where to find each part of the app. For an example of a codebase with this structure, check out the GitHub repo for I/O Photo Booth.
- Layered architecture and reusable code is split into internal packages.
4. CI/CD solution ✅
Not using a CI/CD solution in a codebase is a red flag. This means that critical things like tests, code formatting, and linting are not automatically enforced through the CI/CD. There may as well be no coding standards, as you are relying on manual PR review to enforce standards, which is just not a scalable solution.
It’s much more effective to offload these tasks to a CI/CD solution so that you can ensure tests, lint rules, and whatever else you want to enforce is done so automatically on every pull request. The good news is that there are multiple CI/CD solutions available for Flutter — our favorites are GitHub Actions and Codemagic.* We prefer maintaining our CI/CD configuration within the codebase (rather than in an external tool), in order to track and review changes just like any other code.
5. Analysis options ✅
Linting rules are a great way to enforce best practices across a codebase. Mike Rydstrom has a great comparison of the most popular Flutter lint rule packages from pub.dev (check out our preferred lint rules in very_good_analysis). The most important thing is that you choose the lint rules that make the most sense for your project, so that you can enforce consistency across your codebase.
If you’re currently not using lint rules in your project, adding them and addressing the recommendations from the output can provide a helpful roadmap for improving your codebase.
Maintaining a scalable codebase may be the single most important thing you can do for your app. Implementing the above characteristics will go a long way in ensuring that your code quality remains high.