I’m fresh from fixing one of the more interesting bugs I’ve encountered thus far in my career.
It was actually two entirely unrelated bugs that had the effect of “cancelling each-other out” and appear as if there was no problem at all.
Reader, let’s go on a journey…
Setting the stage
- I maintain a small python library that handles election timetable logic.
- We’ve just had a UK general election declared for 12th December.
- The 2nd of December is St Andrew’s Day which is a bank holiday in Scotland only.
Finding the bug
There is a specific clause in that bill that ignores St Andrew’s Day in the calculation of voter registration deadlines.
I used this as an opportunity to test the library as I expected modifications to the code would be needed. Otherwise the calculated date would be different in Scotland than the rest of the country.
… but it wasn’t.
The code claimed that all four countries of the UK would publish candidate lists on the same day, the correct day, 14th November.
Bug 1 - Misreading legislation
The implementation only accounted for half the specification.
UK parliamentary elections mostly fall into two categories, general elections and by-elections, and their timetabling is legislated for in the Representation of the People Act 1983.
Election timetables are expressed in terms of working days and thus bank holidays are not counted.
By-Elections are for a specific constituency and the only bank holidays factoring into the timetable are those in the country where the constituency is located. I had tested and implemented this part.
General Elections take place across the entire UK. If there is a bank holiday in one country of the UK then it is factored into the timetable for all countries of the UK. This is to ensure that the timetables are synchronised.
I hadn’t implemented this so England, Wales, and Northern Ireland should have been calculating their dates using their own calendars, and should’ve output the 15th of November.
… but they weren’t.
How was this working?
Bug 2 - Not understanding Python
I built this library in Python but it’s not my strongest suit.
I’m using the data-processing library pandas to perform calendar arithmetic. This involves sub-classing
AbstractHolidayCalendar and adding more rules based on UK bank holidays.
My error was not understanding how inheritance works.
values = 
def __init__(self, value):
a = Child(2)
b = Child(3)
b.values would be
[1,3] but it actually resolves as
[1,2,3] - the previous instance of the
Child class had already appended to the list
I wrongly expected
AbstractHolidayCalendar.rules to be created afresh for each new instance of a calendar.
The end result was that the calendars for England, Wales, Scotland, and Northern Ireland each contained the bank holidays from all other countries too - the calendar for England contained St Patrick’s Day, for example.
Two wrongs making a right
The code was only working because the bug in the calendar unintentionally obeyed the rules that the calculation should have followed.
Calculating the date for a single country, when every country has every bank holiday, is exactly equivalent to calculating the date for each individual country and taking the earliest date.
I deployed the changes to fix both and released the new package:
- 8dc36c9 - fix(parl): Implement date choice using the actual legislation.
- 387cfc2 - fix(parl): Ensure calendars only contain bank holidays for that country.
I didn’t write tests for the classes that build the calendars. I’ve added them now but they would’ve caught this before it became a problem.
I skim-read the legislation straight to the numbers I needed. I’ve updated the documentation to point to the specific part which captures both the timetable numbers and the different types of election.
It was a fun evening diagnosing this and thinking through a good solution, and I hope to encounter serendipity like this in the future!
November is National Blog Posting Month, or NaBloPoMo. I’ll be endeavouring to write one blog post per day in the month of November 2019 - some short and sweet, others long and boring.