00:00:00.000
Ready for takeoff.
00:00:17.600
Hello! It's such an honor to be keynoting RubyConf. Thank you to the organizers for having me.
00:00:23.880
I’m Nadia Odunayo, and I'm the founder and CEO of a company called The StoryGraph, a reading tracker and book recommendations app. We help you choose your next book based on your mood and favorite topics and themes.
00:00:31.380
We've got a whole range of stats where you can learn about your reading habits and see how they develop over time.
00:00:38.579
We have machine learning-powered personal recommendations. It's like a trusted go-to friend who knows your preferences but whose brain is also a book encyclopedia.
00:00:45.719
You can find your next perfect read by filtering lists of books by mood, pace, fiction versus non-fiction, genre, book size, and more.
00:00:52.620
You can also read alongside friends, adding live reactions to specific parts of the book. Comments remain locked until your friends reach that specific part in their reading.
00:01:02.280
And there's a whole lot more on offer.
00:01:11.100
We have three million unique monthly visitors, 36 million page views per month, and each month we serve 110 million requests.
00:01:16.560
So, people often ask me, 'What is StoryGraph built in?' and I love that I can say, 'Ruby on Rails.' Thanks!
00:01:23.460
With all of this website and app activity, we get a lot of customer feedback, feature requests, and bug reports.
00:01:30.420
Given that I run the company's Instagram and Twitter accounts, I often receive messages that include something like, 'If you could pass this on to your dev team, that would be great.'
00:01:43.320
Now, while I have an absolutely awesome co-founder in Rob Freelo, the web and app dev team is made up of just me.
00:01:50.460
In fact, I recently gave a talk about this at the inaugural Rails SAS Conference run by Andrew Culver in LA last October. My talk was titled 'Getting to 1 Million Users as a One-Woman Dev Team.'
00:02:05.300
I spoke about the ups and downs in building StoryGraph, and there were some really stressful moments in this journey.
00:02:10.260
There were some very dark times when I felt stuck and questioned whether I could keep going.
00:02:20.520
As the talk blurb says, it was a personal, intimate story. But I saved the real intimate details for you, the RubyConf audience.
00:02:30.420
I have a secret. Don't tell anybody! You see, despite all of the focused hard work and stress that went into building StoryGraph into what it is today, I couldn’t help but keep up a little side gig.
00:02:41.460
I'm not just the founder, CEO, and solo developer of StoryGraph; I'm also a private investigator.
00:02:48.239
And what is it I investigate, you ask? Ruby crimes! Not jewel theft. I investigate crimes and mysteries relating to our favorite programming language.
00:02:56.640
Why did I go into private investigating? Well, there have been rumors circulating of a shady mastermind causing havoc and confusion amongst Ruby developers worldwide with his constant meddling in the source code.
00:03:09.980
I wanted to be the one to put a stop to him, and I'm sure all of us here know just how lucrative the world of Ruby private investigating is.
00:03:18.480
However, I decided that I couldn't do this gig under my normal name. No, I needed to protect my identity.
00:03:27.660
So, I decided to call my Ruby private investigating alter ego Deidre Buck. D for short. Why? Well, isn’t it rather obvious?
00:03:34.080
Now, I've got several stories from my years of doing this work, but for today's keynote, I thought I should tell you all about a case that happened within the very walls of one of our community's most revered institutions.
00:03:41.940
This is the case of the vanished variable.
00:03:53.519
Chapter One: It was June of last year. I'd had an incredibly stressful couple of days at StoryGraph. Rob and I had just finished dealing with our first experience of serious online controversy around our product.
00:04:04.680
Our content warnings feature had come under fire after a series of well-known authors brought to light that inaccurate warnings were being attached to their books. Things were going wild on Twitter.
00:04:14.700
Rob and I stayed calm and mollified the Twitter situation before coming up with a proposal for adjusting our system. We sought feedback from the community.
00:04:22.920
After our ideas were received positively, I coded all day and night to implement the changes. It was a wild and stressful time, but we pulled through.
00:04:30.679
Being the sole software developer, though, had really wiped me out. Despite knowing that wasn't the case, I felt personally attacked through all of this.
00:04:39.420
I needed a break from StoryGraph, and I was in the process of planning a relaxed evening when the phone rang.
00:04:46.020
Immediately upon answering, I heard a breathless, anxious voice say, 'I know I’m the last person you want to hear from, but I need your help!'
00:04:55.320
Oh, I recognized that voice—how could I forget it? Jenny!
00:05:00.720
Some of you may be wondering, who is Jenny? She was 29, a seasoned Ruby developer obsessed with all things Rails. She also betrayed her best friend once during one of my earlier cases.
00:05:06.779
I'd heard through the grapevine that she'd learned the error of her ways and was on a good path now, but I was hesitant to trust her.
00:05:16.560
Jenny worked for the famous, world-renowned Ruby Institute of Professionals, an organization that existed to keep the Ruby language and community alive.
00:05:23.820
It was a job-related issue that had Jenny calling me that day.
00:05:32.040
You see, the RIP had started up this new internal incubator—a competition of sorts—within the organization aimed at improving Ruby's image and usage worldwide.
00:05:40.679
People within the organization could form teams and submit various startup ideas. The winning team would secure a $500,000 investment and the opportunity to work on their startup full-time, fully supported by the RIP.
00:05:51.540
For the very first round of this competition, Jenny's team had made it into the final two.
00:06:02.280
They were building a platform that assigns grants from a pool of money that the RIP had available for this kind of thing to fund Ruby developers and organizations.
00:06:09.780
There were three different types of awards on offer that varied in grant size and could go towards different types of projects.
00:06:17.940
For our demo, we simulated an automated draw for the grants, including doing multiple redraws until the maximum number of grants had been assigned.
00:06:24.339
However, the number of awarded grants came out incorrectly in the semi-finals today, which was embarrassing.
00:06:31.560
We thought we'd be disqualified, but the judges put us through. It's so close between the final two teams that they will be looking for any reason to disqualify one of us.
00:06:39.420
But here's the problem,
00:06:49.560
Jenny sighed heavily. 'Our lead dev, Alex, is now very stressed, and he won't admit that he needs a second pair of eyes. With only two devs on the team—and not only do I have other jobs to get on with—I really hate working with him.'
00:06:56.100
The final is tomorrow, and there's only a few hours left to get to the bottom of this. 'We need you,' she finished.
00:07:04.260
I was quiet while I thought about it. Not only was I wary of helping Jenny due to her dishonest past, but after my incredibly stressful couple of days at StoryGraph, I was meant to be relaxing.
00:07:13.680
But, well, Debug can never resist a challenge, and it's never a good idea to pass up an opportunity to go into the RIP.
00:07:19.920
After all, I was sure they’d pay me handsomely for this work. I agreed to take on the case.
00:07:34.140
Chapter Two: 'I don't need help,' Alex said bluntly after listening to Jenny's explanation of why I was there.
00:07:41.520
He sat in his office chair, arms crossed—a mix between sulking and glaring.
00:07:48.919
Jenny started to protest, but I had an idea. I had dealt with this type several times before.
00:07:54.300
You know the self-proclaimed 10x hero developer who knew everything and declared that they could solve all of the company's problems in an afternoon if only people would leave them alone?
00:08:01.560
I knew what would loosen him up a little. 'I'll tell you what, Alex,' I said. 'I won’t distract you, I won’t say much, I won't touch the keyboard. I'll just sit next to you and be your rubber duck.'
00:08:09.420
'You can just talk me through the code and the bug, bounce ideas off of me, and of course, you’ll find the solution by yourself and get all the credit.'
00:08:16.300
'But this way, you'll find the bug quicker.'
00:08:23.100
We were all silent while Alex thought about it, frowning. Finally, he said, 'Fine.'
00:08:30.900
Alex had already turned back to his keyboard as Jenny gave me a subtle triumphant fist pump and said she’d leave us to it.
00:08:36.300
As I went to take my seat next to Alex, he briskly put an arm out to stop me. 'I've just realized I can't show you the code base as is.'
00:08:44.220
He said, 'There's confidential code in here from some of RIP's clients and partners. Let me get rid of it.'
00:08:50.760
I was wary that a couple of hours had already passed since Jenny’s call, but I couldn’t really argue with the issue of me seeing potentially sensitive client information.
00:08:59.520
So I pulled up a chair a couple of seats down from Alex and decided to make the best use of the time by opening up Duolingo.
00:09:08.640
After all, there was no way I was going to take down the shady mastermind if I didn’t stay on top of my Japanese, right?
00:09:15.240
Forty minutes later, and Alex was finally ready to show me the code. 'Now, I'm only showing you a simplified version of what I saw on that day.'
00:09:21.990
First, each award type had its own file that looked like this: Here, Alex had defined a class named Award A that inherited from another class called Grant.
00:09:27.240
Then, there was a class instance variable called award count which was set to zero. There was also a class method called award count, which returned the value of the class instance variable.
00:09:34.680
There was a class method called assign, which incremented the award count class instance variable by one.
00:09:40.260
Alex quickly showed me that the files for the other two award types were exactly the same except for a change in class name.
00:09:50.040
Then, there was the Grant class itself. There was a constant called Max grants which was set to 100.
00:09:56.640
The Grant class had its own award count class instance variable, which didn’t seem to be used anywhere.
00:10:02.760
The Grant class also had its own award count class method which added up the award count of all three award types.
00:10:09.240
Then, there was an awards remaining class method that was used in the redraw to figure out how many awards still needed to be assigned.
00:10:15.300
It took the maximum number of grants permitted and subtracted the number of awards that had already been given out.
00:10:22.740
Then there were scripts that assigned some awards—50 lots of Award A, 30 lots of Award B, and 20 lots of Award C.
00:10:30.060
Finally, there was a script that checked how many prizes were left and then performed a redraw if necessary.
00:10:37.920
So remember, in the code that Alex showed me, there were a maximum of 100 grants that could be awarded.
00:10:43.500
Fifty plus thirty plus twenty equals one hundred, which equals the maximum number of grants that were available.
00:10:50.520
'Are you following so far, or do you need me to go over it again?' Alex asked.
00:10:56.820
'No, I got it,' I told him. If anything, I've been holding back from suggesting places where his code could have used some improvements.
00:11:02.160
I mean, that wasn’t my style. Usually, I just focus on the problem at hand and avoid making quick judgments about the quality of unfamiliar codebases.
00:11:12.600
But Alex's behavior was frustrating. Anyway, on the whole, his code looked straightforward. I couldn't see what could go wrong.
00:11:22.020
Alex ran the scripts.
00:11:28.140
'What? Three hundred grants awarded?! Maximum number of grants exceeded?!' What a mystery.
00:11:31.980
Chapter Three: I spent a few moments feeling baffled before remembering that I was the great Debug, a private investigator.
00:11:39.960
I had to stay calm and focused, and I had to play my cards with Alex carefully.
00:11:46.140
He seemed to be opening up to me, so my strategy was to let him keep driving and to slowly nudge him into what would hopefully turn out to be the right direction.
00:11:53.640
Something had stuck out to me during the code demo: the class instance variables. Now, I thought class instance variables belonged to one specific class only.
00:12:06.120
But the four classes all had one with the same name. Given that the three award classes all inherited from Grant, and we had three times as many awards being assigned, I wondered if some overwriting was occurring.
00:12:11.760
'What if,' I said cautiously to Alex, 'each class were to have a differently named class instance variable? Perhaps they’re all conflicting with one another.'
00:12:18.420
Alex glared at his display as if he hadn’t heard me, but then he shrugged and said, 'Sure, we can try that.'
00:12:24.300
So he took each award class and adapted the name of their class instance variable to include some reference to its type.
00:12:31.320
He then ran the program again.
00:12:36.420
'Same result,' I noted. So, it wasn't the fact that the class instance variables with the same name in one inheritance chain were interfering with one another.
00:12:41.160
They did, in fact, as I had understood, stick to their own classes. I was wondering what to suggest next when we were interrupted by one of Alex's colleagues.
00:12:49.920
'Alex!' he said. 'You'll never guess who's in the office!'
00:12:56.880
We both turned to look at him. It was Max.
00:13:03.360
Everybody loved him, but I knew different. I knew he was not to be trusted.
00:13:10.680
But in that moment, his appearance worked in my favor, because the way Alex jumped up and sprinted away from the computer towards wherever Max was, you'd think he'd heard that somebody had dropped a billion dollars on the floor.
00:13:17.220
I didn't care, because finally I could get my hands on the keyboard.
00:13:24.360
Alex's fancy clacky keyboard with no labels on the keycaps didn’t faze me. Oh no, now I could finally do what I do best: solve this mystery.
00:13:30.600
While I was all over what to try, I immediately, out of habit due to my excellent git hygiene, typed 'git status' into the terminal.
00:13:36.480
There were changes in the repo, so I typed in 'git diff' and casually looked over the output before it hit me that I probably shouldn't be looking at this.
00:13:44.500
There might be client data in here that the RIP didn’t want me to see, so I quickly exited, but not before something caught my eye.
00:13:52.740
One hundred fifty-one lines added to this simple script that Alex had created to demonstrate the bug.
00:13:58.680
But the file we'd been looking at had been only a few lines long. I quickly tabbed over to the file in question.
00:14:04.200
Hit command and the down arrow to jump to the bottom of the file, and this is what I see:
00:14:10.560
The Grant class had been reopened. The class instance variable award count had been overwritten to be 300.
00:14:21.540
And the class method now read from the class instance variable directly. No wonder the maximum number of grants was exceeded!
00:14:29.520
'What are you looking at?' Alex had returned and wasn't too happy to see me sitting in his chair touching his keyboard.
00:14:36.300
'He looked over my shoulder at the screen and saw what I was looking at. He looked a little alarmed at first, but confusion took over.
00:14:45.840
'Uh, where did you get my old code from?' he asked suspiciously. 'That's from an old demo!'
00:14:53.640
'Well, it was right at the bottom of this file,' I said.
00:14:59.760
'All of a sudden, Alex looked incredibly embarrassed, like he wanted the ground to swallow him up.
00:15:07.260
He explained that when he was getting the repo ready for me, he'd been making quick edits, copying and pasting a bunch of code from one place to the next.
00:15:14.280
He had selected the whole file and deleted waves of it, and must not have noticed this bit of code being left behind in his haste.
00:15:20.640
'But this code must have been hanging around for a while,' I said.
00:15:26.160
'Surely you would have noticed it, unless you never do partial commits.'
00:15:31.680
Now copy-and-paste errors can happen to the best of us. I would certainly be called out by them before.
00:15:38.760
But I have to admit, though, after his behavior throughout the day, it was rather nice seeing Alex humbled a bit.
00:15:44.220
But however trivial the bug, a case isn't over until I see the code working.
00:15:52.680
So I deleted the code from the bottom of the script and ran the program again.
00:15:57.840
While it didn't end up being a meaty problem to sink my teeth into and distract me from my stressful StoryGraph situation, the case was closed. My work here was done.
00:16:06.840
Chapter Four: I was on my way out of the RIP building when I heard someone call my name.
00:16:12.960
It was Jenny. 'I've just heard from Alex that the two of you have figured out what the issue is,' she said.
00:16:19.020
'He's gone off to implement the changes now. I'm not the told-you-so type, but it was the class variables, wasn't it?'
00:16:25.380
'I knew that him using them was a bad idea.'
00:16:31.620
'You mean class instance variables,' I said.
00:16:37.020
'No, class variables!' This is Jenny.
00:16:45.180
'You don't mean class instance variables?' I asked, because there wasn't a single class variable in sight.
00:16:52.320
I didn't much like the expression that Jenny started to give me.
00:16:59.940
'Do you have time to come back upstairs, please?' she asked.
00:17:06.360
'I want to get to the bottom of this.'
00:17:12.300
We headed to her desk where she started to fetch the latest code from GitHub.
00:17:19.380
'Be careful so that I don’t see any confidential code,' I told her.
00:17:24.660
'What, there’s nothing confidential in here!'
00:17:31.260
'We started up a whole new code base just for this project.'
00:17:37.800
Now it's my turn to raise my eyebrows.
00:17:43.560
But it’s not long before the expression on my face turns into one of shock, because Jenny is now showing me the code.
00:17:51.180
It looks exactly the same as the code that Alex showed me, except for the fact that all of the class instance variables in the award classes are, in fact, class variables.
00:17:57.780
And the Grant method no longer has its class instance variable. Apart from that, it looked the same.
00:18:02.880
'Let’s run the code,' I said.
00:18:09.660
'Okay, so it still works! But hey, check this out,' I said to Jenny, pointing at something in the web browser tab visible behind the terminal.
00:18:18.720
Alex had made a commit 14 minutes ago. It was a one-line change; he'd removed a line from the Grant class.
00:18:27.420
'So before his last commit, the Grant class instead of looking like this, looked like that.'
00:18:36.060
'Let’s run this code,' I said. I had my suspicions.
00:18:43.740
And they were proven correct: this code fails in exactly the same way as the earlier bug that Alex had presented to me.
00:18:51.540
And now I'm really confused because there was, in fact, a bug, but Alex knew how to fix it all along but didn’t tell his teammates.
00:18:58.320
'Let's go and find Alex and get to the bottom of this,' Jenny suggested.
00:19:03.840
It was past 8 PM and hardly anybody was left in the office. Alex wasn't at his desk; he wasn't in the kitchen; he wasn't playing ping pong.
00:19:10.680
Max had gone, so we knew that he wasn’t somewhere fawning over him.
00:19:16.620
I spotted the quality control guy who had come over earlier and asked if he’d seen Alex anywhere.
00:19:22.920
'I just saw him! He was trying to find a quiet corner to take a personal call.'