Bit Rot Thursday
Part 1: There is a Problem
I don't think I'd have to look for long for someone who'd agree that writing new code is much more fun that fixing bugs in old one. A cool new idea gets written up, while older code is still lacking tests. A new module gets shipped, while there's still that API improvement proposal from 6 months ago in the other. And while you're drafting a design document for the Next Awesome Thing, the rest of your code is being slowly consumed by bit rot.
Having written 250–300 Perl 5 modules and now 32 Perl 6 modules and other ideas, I'm more than aware of what it feels like to be leaving a decaying pile of code in your wake. The problems I notice are these:
- Unfixed bugs
- Lack of compresensive tests
- Lack of documentation
- Bad documentation (too wordy; incorrect; partial)
- Unimplemented new features, even if the proposal for them was approved
- Partial implementation (an FTP client that can only download, for example)
I can think of several reasons why it might be hard to find motivation to take care of bit rot, all of which are perfectly valid:
- You're "too busy." You spent 8 hours at work, hacking on arcane code, and when you come home you want to relax and play Warframe and not spend 3 more hours hacking on arcane code. Your next Killer App is more important than this module you wrote 5 years ago because you were bored. Your interests changed: you no longer do web development with Perl 5 and instead are hacking on Artificial Intelligence software with Perl 6. And maybe you just have too many projects in the first place.
- You don't know what needs to be done. Sure, CPAN Testers send you emails when something's broken, but it's easy to put off until the mythical "tomorrow." Your documentation is missing and users want new features, but you forgot that was brought up a few months ago. And what about Issues that go stale without any plan of action?
- It's too hard. You set out to do a task and you implemented A, B, and C. Now, someone came up to you and said your thing is lacking X. Problem is, you don't have adequate knowledge and experience to implement X. And learning it requires both time and interest. Another example: users are reporting a nasty bug, but you can rarely reproduce it, and when you do, you still have no idea why it occurs.
And while entropy is a tough foe to conquer, I think at least acknowledging there is a problem is a good stepping stone to finding a workable solution.
Part 2: There is a Solution
I toyed with the idea of dedicating a special day to deal with just these sort of problems for a while. Today, I decided to commit myself to it, and I invite anyone willing, to do so themselves and address bit rot in their own software. The plan is simple:
Every Thursday is a "Bit Rot Thursday." You find some time to address bit rot and just do it. Why Thursday? I figure Friday, Saturday, and Sunday are all about relaxing; Monday, Tuesday, Wednesday are a beginning of the week people tend to "hate"; and Thursday is in the sweet spot: do work just before the weekend and feel good about yourself when the weekend comes. Since monotony is boring, you get one Thursday every month that you can "legally" skip and do nothing. Keep in mind, Bit Rot Thursday is not only about dealing with current bit rot, but doing preventative care as well. Let's see how we can accomplish those goals:
Toss It in the Bin
Before you rush off to fix a bug in your lolcat_phrase_generator_5000.pl
, ask yourself: is this script or module still needed? You may feel attached to this "web framework" you wrote 8 years ago, but nobody—even you any more—uses it, so implementing HTTP/2 in it is likely a waste of time.
The modules you delete off CPAN will still be available on BackPAN, so don't be shy. You only have this much time in a day, and it's necessary to regularly throw clutter out. This is quite equivalent to accumulating useless trinkets in your garage, in case you'll need them some day. Just toss it in the bin.
Give It Away
If you don't think your software deserves such a harsh fate as being wiped from the face of Earth, consider putting it up for adoption. You can use blogs, social media, and IRC to advertise that you're looking for a new maintainer who'd be interested in fixing issues. On CPAN, you can transfer the module to user ADOPTME, to enlist your module as adoptable by interested parties. Perl 6 has its own version of the idea: place the META file into the SHELTER and then remove your module from the Ecosystem.
Delegate
Instead of parting with your goods completely, delegate. The Mojolicious project is a good example of how to seek out volunteers: Issue labels future
and help wanted
are used to indicate work to be done and social media is used to advertise and find volunteers willing to do the required work.
Did someone submit a report for a bug? Ask the submitter whether they'd be able to fix it. Did someone request a new feature? Suggest you'd accept a patch. Include details on what you think might be causing the bug and outline the details of how a feature can be implemented. Even if that person is unable to help, you'll have a plan of action already in writing.
Find and Review Reports
Go through RT tickets, GitHub Issues, your notes and reminders. When you log in to RT, the Bugs in My Distributions
panel is third on the left. On GitHub, click the Issues
link in top, center of the page and then change author:
to user:
in the search box. This will show you all open Issues in your code. You can use user:
more than once, if you wish to also include issues from GitHub organizations you belong to. As an example, here's a search query that shows both Issues in my code and in Perl 6 organization: is:open is:issue user:zoffixznet user:perl6
Get a MetaCPAN account and try out the Dashboard MetaCPAN Lab to see the number of open issues and test failures in all your modules: https://metacpan.org/lab/dashboard.
Categorize the issues using tags and labels. Merge duplicates. Close anything that's irrelevant, can't or won't be fixed. If the conversation died out, just close the ticket with a request to reopen it if anyone has the same issue. There's no reason for tickets to be left open for years.
Evaluate Unreported Things
Go to CPAN Testers and browse by Author to your Author page (middle of page, third section). You can then use the Preferences panel on the left to generate a link to show failures only. Bookmark it. Examine any failures and see if you can fix them. When you upload new modules, if their tests are failing, you should be getting an email from CPAN Testers informing you there is a problem. If you're not getting those, check your settings and your spam folder.
Use Devel::Cover to evaluate how well your tests cover your code. If you use Dist::Zilla, there's Dist::Zilla::App::Command::cover that adds cover
command to dzil
.
To ensure your documentation is complete, add Pod::Coverage to your author tests (that is, place it in xt/
directory rather than t/
so it doesn't run during user's installation). Completeness is one thing, but quality is another. Examine your extant documentation. Try to reduce the amount of words in it, while still retaining information. Use examples! A good, clear example can shave off a whole paragraph of dense technical text. There's nothing wrong with actively inquiring your users about the quality of the documentation. Do people frequently ask a particular question? Creating a FAQ is one way to go, but it's often an indicator your actual documentation can be improved in that area.
In Perl 6, Test::META lets you find problems in your META file and Pod::Coverage distribution includes Test::Coverage
module to check completeness of your docs.
Prevent Bit Rot
So far, our Bit Rot Thursday sounds mundane and boring, dealing with tedious problems. However, you can actively work against those problems appearing in the first place.
When you start a new project, do you write down a clear definition of the problem you're attempting to solve? A sure-fire way to waste time is to spend several days adding a ton of configuration options and features that you don't have any use for, simply because you never defined a concrete problem to solve. Worse: you continue to support and maintain all those features for years to come!
When you lay down the first line of code, do you have some form of a "spec" that details the bits and pieces of your project and how they're supposed to interact with each other? I'll leave it to other sources to argue for cost savings of such an approach, but I can tell you that when you realize JSON and not PDF is better suited for the output of your program, rewriting two paragraphs of text in the spec is much simpler than rewriting several subroutines, methods, and tests.
A design spec also makes it much easier to create both tests and user documentation for your project. The test suite is the spec translated into a computer language. The docs are the spec with internal details removed and code examples added.
Speaking of tests, it's often handy to write them first, before your actual module or script. The tests define the problem you're attempting to solve. Failing tests indicate what bits you haven't solved yet. You can also very easily track your progress, as you can see how many tests still fail. Since this approach generates a lot of output on each test run, you may find Test::Most's DIE_ON_FAIL
useful. You can include it in code or use DIE_ON_FAIL=1
environmental variable and this will cause the test suite to stop at the first failure.
So spend your Bit Rot Thursday making templates for specs and tests and outlining plans for what sort of documents need to be prepared for the types of projects you create. It's also a good idea to draft up guidelines for contributors, to make it clearer what sort of contributions you're looking for. You'll spend less time explaining your standards in pull requests!
Growing Yourself
Who is more likely to produce a wall that'll crumble in a year: an experienced bricklayer or a novice? Writing code certainly gives you practice, but you need theory too, as well as to keep yourself up to date with the world. You can write a million lines of code that uses CGI.pm and memorize every word in its documentation, but in 2016 you'll still be unemployable in the field of Web Development if that's all you can do. Technologies change. Standards change. Practices change. We should change too.
And so, it's possible to spend the Bit Rot Thursday away from the keyboard. On a train, plane, bus, or boat, with your phone or tablet, reading programming blogs and articles. At a bar with your fellow programmers, exchanging ideas. In a park, on a sunny day, with a programming book in your hand.
And as you get better, so will your code...
Thanks for this great piece of advice!
Do you think you could link to an example (or two or three) "design spec" of yours? I'd be very interested to see this process. I've never seen anyone else's design spec and it would be interesting to get an idea of what that would look like for someone else?
I see tons of starter code online, lots of "oh it's easy to get started, here try this!" But I never see design specs for that starter code.
I am myself new to the idea, so maybe I'm not the best person to give specs, but I have two examples for consideration (below). Your "spec" basically should outline how the code should work **from the perspective of the user**. So you kinda try to imagine whether using your code will be nice and pleasant or are there issues you've not considered.
1) IRC::Client that I started hacking on without any specs. I now have to rewrite most of the event handling code because when I tried to actually use it, I found it too annoying and low-level.
2) Test::When that I started by writing the spec first. It was originally named Test::Is and had network testing enabled by default. After I wrote the spec (https://github.com/zoffixznet/perl6-Test-When/blob/master/SPECIFICATION.md), I asked for feedback on it and the result was:
You can obviously see that were I forgo the spec in case (2), it be quite similar to my issues with case (1): I'd have a less-descriptive name, I'd waste my time implementing the library checking code, I'd have to modify the API to allow for multi-version checking, and I'd have an unwanted default.
Some time in the near future I plan to write the spec for IRC::Client and will write a blog post about it too!
I recommend seminal "Painless Functional Specifications" series by Joel Spolsky, available free on-line at:
http://www.joelonsoftware.com/articles/fog0000000036.html