The ability to develop and maintain a clean iOS codebase is essential to becoming a remarkable iOS Developer.
A simple but extremely valuable action you can take is removing unused code—mercilessly.
At the same time, we meet many developers afraid of deleting code because they might need it in the future.
Today, you’ll learn two ways for retrieving deleted code from a git repository and how good practices for commits facilitate the maintenance of a clean iOS codebase.
We teach our students and mentees to break down their changes in tiny/concise commits with explicit and descriptive messages.
Tiny, single-focused, commits with detailed messages:
- Make code easier to review, change, and maintain.
- Serve as documentation.
- Facilitate and reduce the cost of undoing changes.
We recommend you to commit every time the behavior or state of the codebase changes while maintaining a green state (all tests passing), regardless of how small the changes are (the smallest, the better).
As a result, you’ll deal with smaller and less risky changes and become more adaptable as you’ll be free to explore solutions, revert, and restore their changes.
Usually, two is the ideal number of changed files per commit as we’re writing production code and tests at the same time. Refactoring commits change generally only one file but occasionally may affect more when we change a public interface.
Tight coupling between components is often what prevents developers from contributing in small chunks. Thus, the ability to edit only a small number of files per commit can signify a positive loosely-coupled relationship between components of the system!
In other words, a system designed with good abstractions allows you to change specific components without affecting others, making changes in the codebase easier, faster, and less risky. On the contrary, lousy abstractions will force you to edit multiple components, in various files, at the same time, even for minor changes (increasing the risk for mistakes and merge conflicts, slowing down the team).
If you follow the commit good practices (concise changes with a descriptive message), reverting changes becomes very simple and cheap—close to zero undo-cost!
For example, in the persistence module of the Essential Developer Academy: iOS Lead Essentials course, we demonstrated how to create supple abstractions enabling seamless replacement of any underlying caching mechanisms.
By using that abstraction, we created two production-ready caching implementations:
Codableimplementation backed by the file system using the
Core Dataimplementation with a persistent
Although both solutions work and perform well, we need a single persistence implementation.
Core Data is a more robust fit for the app we’re creating in the course; thus, we opted to—fearlessly—delete the
We bundled the file deletions in a concise, single-focus commit, containing only:
- the deletion of the
Codablestore implementation’s production file
- the deletion of the
Codablestore implementation’s test file
- any changes made by Xcode in the project structure
Additionally, the commit held the following message describing the changes:
CodableFeedStorein favor of the
CoreDataFeedStore(we just need one in this project). If needed, of course, we can revert this commit and restore the
By including only the minimal necessary changes in a commit, such as the deletion of a file or a variable rename, you can quickly
revert specific changes if you ever need to. To do so, all is required is the hash of the commit you want to revert.
To revert a commit, simply run
git revert <commit hash>. Git will then create a new commit, reverting the changes introduced by the given commit hash. Git will prompt you to edit the default message for the new commit with the reverted changes. The default message is:
Revert “< given commit message >”
If you are happy to use the default message you can skip the editing by running
git revert <commit hash> --no-edit instead. Notice the
--no-edit flag telling git that you don’t wish to edit the new commit message.
As you develop iOS apps, you make many decisions like deleting code. You should be able to freely change your mind and restore changes with minimum friction and cost.
In our case, we know we may change our minds and decide to bring back the
Codable store. Thus, we could only fearlessly make this change because it’s an easy change to undo (low cost). It’s easy to undo because we’ve created:
- A supple abstraction that decouples our production and test code from the concrete backing store.
- A concise—single-focus—commit for the code deletion.
A concise git history, with carefully written messages, can help developers understand the history of changes, creating a three-dimensional view of the code, where time becomes an axis we can travel on.
A git history with contributions that contain many non-specific changes per single commit, can make reverting a specific change very hard (or impossible!).
For example, you wouldn’t be able to quickly revert only a deleted file if the commit also introduced important business logic changes. If you tried to revert the commit, you would also revert the important business logic changes (which you probably don’t want to do!).
When reverting a specific file deletion in a commit (the easy way) is not an option, you can use an alternative technique by broadening your search scope for the deleted file (the hard way).
In the following process, you’ll learn how to find the deleted files, inspect and restore them. Of course, you can customize this process and optimize your workflow to your needs.
git log --diff-filter=D --summary | grep delete
--diff-filter=D argument, tells git to select only files that are deleted and the
--summary flag to instructs git to format the output to contain a condensed summary of extended header information including the mode changes, which we can then filter the printed deletions with
This command will yield the full path of all deleted files. There you can find the full path of the file you'd like to restore which is required for the next steps.
git log --all --full-history -- <file path>
First, when typing the command, make sure to add a space between the
-- and the file path, as the dashes denote a separator and not an argument for this command.
The command above will yield a list of all the commits containing the deleted file.
--full-history flags tell git to search all the branches containing commits referencing the deleted file, instead of just the current branch. This functionality comes in handy when working in teams and large scale projects that there are multiple branches created in the repository, and you know you're not the only one making changes in the codebase. By not including the
--full-history options you could potentially miss significant changes that you weren't aware.
Now that you have the hash of the deleted commit and the file path of the file you’re looking to restore, you can ask git to show you the file to review its contents. Simply run:
git show <commit hash> -- <file path>
Again, make sure you add a space between the
-- and the file path.
This is an optional step, but can be essential to validate you’re restoring the correct file!
git checkout <commit hash>^ -- <file path>
By running the above command git will restore and stage the addition of the new file and its contents to your current working directory.
The caret symbol () denotes that the parent of the given commit will be used, instead. That's because, in the given commit, the file remains deleted, meaning it doesn't exist; thus, you need to instruct git to look at the commit before it (parent) to fetch the missing file.
Regardless of the version-control system you use, you can apply those simple, but essential techniques to maintain a clean iOS codebase.
The goal is to break down your contributions in tiny, concise batches so you can manipulate them easily!
Clean iOS codebases consist of sequences of carefully thought commits.
By being disciplined with your contributions and keeping the codebase clean and well organized, you gain various degrees of freedom as you move forward, such as easily reviewing, maintaining, deleting, and reverting code.
Unfortunately, we see many codebases needlessly carrying unused code. The “dead” code adds a time overhead with every build and test run. Moreover, the unused code can cause significant confusion to new team members as they have to research about its reference or ask other team members about its lack of usage and existence in the codebase.
As a professional iOS developer, you’re also a risk manager. By proactively thinking about your future needs and spending a few seconds to organize your contributions properly, you and your team save hours of pain and wasted hours in the long run!