Here’s one idea: Choose a community-supported code quality tool and/or linter (such as Rubocop for Ruby), and apply it to your codebase, or a portion of your codebase. Pick a single file (preferably one with good test coverage), and fix all the errors.
Maybe tasks that apply design principles (or even just identifying them in your codebase, with the “exercise” more about one’s justification for needing to apply it in the first place)?
Add an automated check for code quality to your pipeline. It could be git hooks, CI steps, test suite specs, automated linter enforcement, etc. These kinds of additions can typically be easy wins that can provide long-term leverage over specific quality issues one by one. You can start with basics and proceed to more advanced rules as the project/team grows and matures.
For example, once I was on a team with a fresh bootcamp graduate who used binding.pry for debugging and would often forget to delete those lines before committing and pushing the changes. After about a week of this I wrote a git hook for him to install that would grep the project directory for lines containing binding.pry and fail if any were detected. He stopped committing debug statements and was able to get more meaningful work done faster from that point on.
Recently, we standardized adding some general code quality checks like rubocop linting and brakeman security analysis, as well as dependency scanning for CVEs, as steps in our CI process. Now we don’t waste time nit-picking code style issues in code review, we get early detection of potential security mistakes, and we automatically get persistent notifications across several projects when a ruby gem needs to be upgraded for production security.
Pick a project dependency (Gem, Node package etc) and update to latest pre version, filing bug report(s) or pull requests as necessary. Development only of course, no need to deploy live.
I like the ideas of creating adequate tests for some code and I also want to add the proposition of a task to optimize a particular functionality (speed, memory, etc.)
There are different ways to define complexity. A super simple starting point could be lines of code. You could then build on top of it adding number of dependencies, cyclomatic complexity, etc.
You could try to find unused files, classes, or methods and remove them. There seems to be tools or IDE supports (vulture, PurifyCSS, IntelliJ IDEA) to find unused classes or functions. You could also remove chunk of code block that has been commented out.
I like the idea to play with design patterns. For example, find places with many if statements and replace them or, at least reduce the amount. Or split code into modules/services for better testing.
Write a new test for an area of the app with poor coverage
Wrap a dependency with your own API
Extract a concern from one object that should be shared between objects - think Commentable, Publishable, Slugable, AttributeEncryptable, Taggable, Visible (visible_to), Trashable.
Replace a nested conditional with a guard clause
Rename a confusing variable
Decompose a confusing conditional with a well named method
If you don’t do caching already, set it up so it’s easy to do later. This should only take about 20 minutes if you’re using a library like Rails, Django, or Laravel. If you already do caching, find an area of the app that isn’t cached well and improve it.
Do you have resources that are being cached separately for each user or for different parts of the app? Find a way to share the same cache key by hiding buttons with CSS and localising dates and times with JavaScript.