The Muse codebase is over 5 years old with over 350,000 lines of Swift, and I’m sure is filled with more than a few archeological code-fossils. Like any startup (frankly, like literally every code project), it’s difficult to prune old unused code while keeping up velocity of new features. Code cleanliness is always a tradeoff with velocity, and often comes in second place. That’s why I love tooling that can help automate this otherwise brutally slow manual task.
I was recently told about periphery
, a command line tool to find unused code in Swift projects. What’s a day off work for but for having fun playing with new tools? First up, install:
$ brew install periphery
God bless brew
, amirite.
Next up, the README suggests we run its setup for a handheld first run:
$ periphery scan --setup
Welcome to Periphery!
This guided setup will help you select the appropriate configuration for your project.
* Inspecting project...
Select build targets to analyze:
? Delimit choices with a single space, e.g: 1 2 3, or 'all' to select all options
1 AppKitBridge
2 Muse
3 Muse Integration Tests
4 Muse Tests
5 MuseShare
6 SparklePlugin
...
Super easy to get setup, what a pleasure! I selected the targets I needed, then the schemes. Next it asks about Objective-C code, and I selected Yes to assume obj-c code is in use. Muse has only a little, but some interactions with UIKit or AppKit still reach into Objective-C.
Assume Objective-C accessible declarations are in use?
? Declarations exposed to the Objective-C runtime explicitly with @objc, or implicitly by inheriting NSObject will be assumed to be in use. Choose 'No' if your project is pure Swift.
(Y)es/(N)o > y
Next, it asked about assuming all public declarations are in use – which would be very useful when building a framework or library, for for an app like Muse I answered No.
Assume all 'public' declarations are in use?
? You should choose 'Yes' here if your public interfaces are not used by any selected build target, as may be the case for a framework/library project.
(Y)es/(N)o > n
Last, I saved the configuration to .periphery.yml
and let the first scan run. Luckily, it found the codebase squeaky clean! 😉
I use Sourcery to auto-generate some connector files between the custom Muse sync codebase and Muse-the-application codebase. There’s also a fair bit of old CoreData code that’s still in the codebase so that very old Muse libraries can be migrated to the current sync world. For each of these, I don’t need them included in periphery
‘s output.
To remove them, I used the --report-exclude
command line option to remove a few paths that I don’t need in the report. I’m also not concerned with redundant public
, so I included
too. Last, I’m not worried about unused function parameters, so i used
--disable-redundant-public-analysis--retain-unused-protocol-func-params
. I ran with --verbose
which shows the .yml
configuration changes I needed to make to always run with these excluded.
Last, I setup an Aggregate target in Xcode so that I can run periphery
and see unused code in the Issue navigator. I updated the Build Settings to use iOS, added a User Defined setting for SUPPORTS_MACCATALYST=YES
, and added a Run Script phase with the following:
export PATH="$PATH:/opt/homebrew/bin"
if which periphery >/dev/null; then
periphery scan --config "${SRCROOT}/.periphery.yml"
else
echo "warning: periphery not installed, install from https://github.com/peripheryapp/periphery"
fi
Now, when building the Periphery aggregate target within Xcode, I can see all of the unused code inline right inside Xcode.
Just a little bit of clean-up work to do! 😅