Healthy Code

I work on a team of software developers that maintains several large codebases — too much code for any one person to easily know what’s going on in every part of it at any particular time. I found myself thinking a lot about how to keep the code healthy and a while ago I set my thoughts down as a list of good practices. Thanks to my coworkers at Endless for input, editing, and debate.

The good practices in this post differ slightly from the ones we adopted at work, which reflect the opinions of the whole team; these are worded to reflect my personal opinions.

Assumptions

I don’t like rules without a rationale. I believe these six assumptions underlie the rules that I set out below. That is, if you don’t agree with these assumptions then you probably won’t agree with the rules… ☺︎

  1. We can never know that our own code is correct.
  2. Left unchecked, we will believe our own code to be correct.
  3. Even small mistakes can lead to catastrophic data loss.
  4. Non-trivial programs have interconnections too complex to keep entirely in one person’s mind.
  5. Modifying non-trivial programs will break code unrelated to the modifications.
  6. The business value of maintainable code is only visible to developers.

Good practices for code health

Use your judgement

As always, rules apply only in the absence of any overriding reason to ignore them. Breaking them should be in mutual agreement between the writer of the code and the reviewer. (This system only works if everyone agrees about what the rules are in the first place, though.)

Example reason to break this rule: If no agreement can be reached, then the default is to follow the coding standards.

Review code

Code gets reviewed by a developer who didn’t write any part of it, because of assumptions #1, #2, and #3 — and to spread familiarity with different parts of the codebase throughout the team. Develop with ease of reading in mind, as if you are writing a letter to an unfamiliar code reviewer. Review code skeptically and with full attention, as if it came from a malicious agent out to erase your hard drive.

Example reason to break this rule: You are committing a trivial fix for a broken build and your continuous integration system acts as the code reviewer.

Observe the style

Code follows the coding style. Coding style is important because when code looks the same it’s quicker to read and errors jump out more easily. Apply automated tools when possible to save the code reviewer from becoming a parenthesis counter.

Example reason to break this rule: The code reviewer agrees with you that deviating from the style is more readable.

Test your code

Code needs automated tests. The rationale for this is assumptions #2 and #5, but could be the subject of an entire blog post itself. Lack of tests can be by itself a reason to fail code review, or at least start a dialogue between developer and reviewer about why tests are not necessary in this particular case.

Example reasons to break this rule: A one-off script. A component that proxies an external resource which can’t easily be mocked out.

Refactor on write

You will always have to deal with legacy code (code on which development has ceased but still must be maintained) and rushed code (code which you were forced by circumstances to check in that didn’t quite work well, works but is difficult to maintain, or is not tested.) By assumption #6, you will probably never set aside time to refactor code for its own sake. Therefore, refactor bit by bit to leave the code in a slightly better state each time it’s touched. In this way, code receives refactoring attention roughly proportional to the benefit you receive from refactoring it. If at all possible, add new code with a unit test even if the rest of the code is not written in a testable way.

Example reasons to break this rule: The code is already in good shape. The feature is critical and cannot be delayed. You are contributing your code to an open source project, in which case it is better to work with the upstream community to refactor.

Refactor only on write

Make your diffs per commit no larger than they have to be, in order to make code review easier. Since diffs go line-by-line, do not fix style errors in lines that that are not already being touched in the same commit. Use separate commits if there is an opportunity to make other style fixes.

Example reason to break this rule: If it makes more sense to fix lines other than the ones being edited in one shot (e.g. large sections with wrong indentation), do so throughout the whole file in a separate commit.

Pay down technical debt

Sometimes it’s not possible to build a feature without doing a large refactor first. Determine this as early as possible and include it in the time estimate for the feature. Do not shy away from paying down this debt; it will only compound if you borrow more on top of it. However, keep the changes incremental, and the functionality unimpeded while making these changes.

Example reason to break this rule: Extreme time constraints force you to take out a second mortgage on the code (even then, do this only with a healthy dose of disgust.)

Advertisement

Moving text messages between Android phones

I recently got a new Android phone secondhand, and after resetting it I wanted to move the text message archive over from my old phone. It turns out that you can do this easily if you have root access. Well, technically you can do anything easily if you have root access, but the trick is knowing how. I hope that by putting this out on the internet, other people will be able to know how too.

I had root access on both phones, as they were flashed with CyanogenMod. The new phone is a Nexus 4, and the old phone is an HTC G1 (Android 2.2 is the highest that could run on it.)

On both (and as far as I know, all) versions of Android, all the text messages are stored in this file, which you need root access to read:

/data/data/com.android.providers.telephony/databases/mmssms.db

Getting the file off the G1 was easy; I entered the Terminal Emulator app (I think it’s installed automatically when you flash CyanogenMod) and copied the file to the SD card:

su
cp /data/data/com.android.providers.telephony/databases/mmssms.db /sdcard/

(su requests superuser permissions, which you have to grant.) Then I connected the G1 to my computer with its USB cable and transferred the file off of it.

Getting the file onto the Nexus 4 was harder. What I did not know is that the Nexus 4 can’t mount its SD card as USB Mass Storage (see the explanation), so I ended up using my Apple laptop to do the transfer, and had to download a program called Android File Transfer. Still, I got it onto the phone’s SD card.

Since the newer version of Cyanogenmod comes with a file manager app, I decided to use that to put the file into the correct place, instead of Terminal Emulator (typing shell commands on a phone is no joke.) The file manager is set to “Safe mode” by default which means it won’t request root access. I changed it to “Prompt User mode” in the settings, then navigated to the above databases/ folder and made a backup copy of the old (empty? 100 KB? It’s a sqlite DB so maybe there are still deleted records in there, but I don’t care to check) database. Then I copied the G1’s mmssms.db file over top of it. Unlike on the G1, there was also a mmssms.db-journal file there, which I hoped wouldn’t mess with things…

I couldn’t see my text messages after going into the messaging app, but after rebooting the phone, they were there.