00:00:20.990
The title of this presentation is "The Selfish Programmer," and it's an exploration of what you can learn by writing software for yourself as opposed to being part of a team or a larger organization. My research shows that the three most important traits in a successful solo programmer are being antisocial, egotistical, and irresponsible. Today, we're going to discuss all three.
00:00:31.169
You may know me by this old picture that doesn't quite look like me or as Searles on Twitter and GitHub. I'm a self-professed expert in selfish programming, and I come from a consulting company called Test Double. Our double agents join client teams as additional developers to work alongside them and get things done, while also searching for ways to help the whole team improve over time.
00:00:47.879
I'm here today because I have a problem: learning Japanese is really hard. I've been working on it for 15 years, and I still have a long way to go. However, a couple of years ago, I discovered an application called WaniKani. It helps you memorize kanji and vocabulary using a spaced repetition system (SRS) to time your reviews of items, helping you memorize them effectively.
00:01:12.570
The way WaniKani works is that it challenges you to remember something for a day, then assess if you can remember it after three days. If you succeed, you might review it after a week. However, if you get something wrong, you'll review it again soon. Over time, the app might present an item once a month and assume that if you can't remember something after six months, you probably know it, marking it as done. This leaves you to focus on the remaining 8,000 words. The interface functions like a little flashcard game where you see the Japanese and provide a reading and the English meaning. For example, this word means tug-of-war, and if you answer correctly, the timer will push it to a later review date.
00:01:54.690
I found that this application was excellent for teaching me how to recognize Japanese and understand it in English. My reading has improved significantly. However, it didn't help me produce Japanese words from English ideas, as it doesn't practice that muscle. When I talked to my conversation partner, I would often be tongue-tied trying to think of the right word. Therefore, I built an application called Kamisama, which is a companion app to WaniKani, literally doing the reverse. You see an English prompt and use a Japanese keyboard to provide the word.
00:02:31.620
If you get it right or wrong, it uses the same timer system to help you memorize how to produce the word. It was also important to me to make it a progressive web application, allowing it to survive disconnects and offering a great way to practice the kana flick keyboard popular in Japan. Most importantly, my experience with learning through Kamisama taught me a lot about selfish programming.
00:03:08.370
Let's start with why it can be beneficial to be a little bit antisocial. Stage 1: The Selfish Programmer is unambitious. It's easier to stay motivated when your goals are incremental and achievable. When goals are too ambitious, we risk exhausting ourselves without accomplishing anything, which might lead us to quit. This differs from work, where our applications are large with many parts, and we can trust others to cover their areas while we focus on ours.
00:03:42.750
However, our brains aren't big enough to handle everything at once. Quickly moving from area to area incurs memory paging and context switching costs, which are wasteful and inefficient. Over time, people tend to specialize in large systems, sometimes forgetting how to build an entire application by themselves. Talking about this makes me nostalgic for 2005, when I first started using Ruby and Rails, as Ruby enabled developers to create small, useful apps without assistance.
00:04:31.680
Ruby allows you to keep the whole app in your head at once, facilitating quicker movement throughout a codebase. This nostalgia inspired me to create a new solo Ruby app for the first time in years. However, after several days of effort, I only completed a small part of the card app I wanted to build, which felt useless on its own. This realization prompted me to think about how I had unlearned the process of making small things over 14 years, becoming too ambitious.
00:05:09.960
I instituted a 'one weekend rule'—if I can't release a project by Sunday night, I don't do it. This rule is liberating, as it forces me to shrink my dreams down to something small enough to accomplish. As a result, I can work more quickly. I began imagining the tiniest useful thing I could create and decided on a gem to tell me whether my flashcards were ready for review in WaniKani, called WinKani.
00:05:50.490
When executed, it will inform me whether I have flashcards ready or not. If not, it will suggest to come back in 11 hours. When I do have flashcards, it will print the URL where I can study. The code is simple, utilizing Net::HTTP and JSON to implement a method that responds with how many seconds remain until my next review. It checks the URL, parses the response, and provides accurate feedback.
00:06:24.870
The beauty of this project lies in its simplicity, ease of testing, and, importantly, it gave me experience with WaniKani's API. This small accomplishment resolved one part of the larger application I wanted to build, making it easier for me to finish it later. Excited for my next weekend project, I decided to bite the bullet and create the English to Japanese flashcard game, but that felt too ambitious because it involved complex game logic and a complicated user interface.
00:07:12.160
Instead, I focused on building just the game logic and tackled how to make it useful in a small step. I wrapped it in a throwaway command line app, where I spent weeks testing the prompts, iterations, and progress tracking. This intentional simplicity allowed me to design a real system for reviewing data over a while, which ultimately evolved into the actual code behind the web application now.
00:07:52.250
This incremental accomplishment was incredibly motivating. Continuing through my story about The Selfish Programmer, it's important to understand that they are ungrateful when it comes to open source dependencies. Stage 2: They understand that carelessly adding dependencies can lead to unmaintained messes.
00:08:26.469
I like to envision applications as pyramids. At the top is our application code, and below are all our dependencies. While working, we're used to large applications where the marginal cost of adding one more gem seems trivial. However, when you're on your own, you realize that your time is limited, and if you add too many gems, the time spent maintaining upgrades and workarounds may exceed the time you have to support the app.
00:09:07.000
Arbitrarily limiting ourselves to a few gems isn't a real solution, as it obscures the ability to create something useful. This is why I categorize my dependencies based on trust and maintenance. For instance, I trust Rails's core to curate the numerous gems it includes (41 other gems) and help me stay updated. I classify my gem file into two halves: a collection of smaller gems where I'm unsure about the maintainer or maintenance level, and the more popular gems that likely won't introduce issues, as someone else will have already addressed them.
00:09:56.470
At work, I've been conditioned to never reinvent the wheel. If there's existing code to solve a problem, I utilize it. But when I'm solo, I choose to be ungrateful and may resort to writing my own code, even if a gem claims to provide similar functionality. For example, a gem might fetch a whole universe of data for a single feature, leading me to weigh the gem's usefulness against the maintenance burdens of several additional dependencies.
00:10:38.970
Another common issue comes from frustration when a gem is difficult to learn. People often quit their solo projects when they struggle with a framework or library. While reinventing the wheel may be frowned upon, quitting is a far worse outcome. Personally, I had never been responsible for implementing authentication features at work, but when developing my solo app, I had to figure it out. I once tried the popular gem Devise multiple times but consistently struggled to understand it.
00:11:30.080
Concerned I would become frustrated and abandon the entire project, I started simply. I created a basic password field, added bcrypt, and utilized Rails’ secure password function, and it worked! That gentle climb provided everything I needed for the first five months of my app’s existence. Later, I eventually added more custom features, like changing passwords and verifying emails.
00:12:16.470
Despite the fact that those custom features likely took longer to build than it would have taken to learn and implement Devise from the get-go, I take pride in my work. I fully grasp my implementation, and Kamisama provided a safe space for me to practice something I wasn’t comfortable with. Now I feel more confident discussing authentication strategies.
00:12:58.300
You might think that solving every single problem by searching for gems is appropriate, but just because dependencies can be easily installed doesn’t imply they are easy to manage later. This is why The Selfish Programmer is ungrateful, whether at work or alone; it pays to thoughtfully consider the trade-offs with each dependency added.
00:13:45.390
Now, moving to Stage 3, The Selfish Programmer is ungenerous and doesn’t overly concern themselves with making their code reusable. At work, I was trained to share as much code as possible to be a good teammate, often putting code in shared locations like models. While this approach can work, it also can significantly slow teams as multiple features might evolve dependencies on shared code.
00:14:05.310
If various callers need to change that shared code, considerations of its impact on others become time-consuming. High-traffic areas, like Rails applications, are at increased risk of issues stemming from changes in internal private methods that other methods rely on. To counteract this, I aim to soundproof my code by embedding it with the feature it serves.
00:14:58.600
Consequently, the majority of my code remains isolated, meaning I can actively and confidently refactor it. My models are used primarily as dumb value types that I pass in and out with focus on my feature's specific code. This approach impacts how I assign responsibilities. If a controller action involves both a search and formatting task, I split these responsibilities to diminish the complexity of both and allow focused change.
00:15:45.710
Using this soundproofing technique enhances the safety and clarity of my development. I treat Rails models as a declarative language for various configurations instead of actual code repositories. For instance, while adding just one method to my model might seem trivial, it could introduce intertwining dependencies that increase complexity. Instead, creating separate classes with clear contracts can simplify interaction.
00:16:37.680
I have evidence that this soundproofing effectively works because Kamisama began as a completely unrelated code base called Sentence A. Once I layered Kamisama onto Sentence A, there were zero required changes to the original code that enabled both to coexist without issue, proving the value in clear separation of concerns.
00:17:21.570
At work, code is often viewed as a valuable asset, cultivating the mindset that code reuse fosters faster development. However, as the number of call sites for a function increases, we become more cautious about making changes. This is why The Selfish Programmer remains ungenerous. They aren’t afraid to write excess code to afford considerably more flexibility for future modifications.
00:18:14.400
Now, let’s move on to the virtues of egotism. Stage 1: The Selfish Programmer is delusional, believing their code is sufficient for shipping even when it’s not great. This situation occurs often in solo projects where you push to master frequently without pull requests or reviews. The burden of consistency falls entirely on you, leading to lowered standards when you’re not under scrutiny.
00:19:04.290
For instance, the Kamisama homepage is a simple page that displays user progress with XP and levels driven by a straightforward API. However, the implementation wouldn’t have passed a review at any job. In a collaboratory environment, code quality is collectively held to a higher standard. But pushing solo means you may convince yourself it’s acceptable to proceed with messy code.
00:19:54.890
If you strive for perfection beforehand, you're likely only guessing about what the ideal design or optimization needs to be. Instead, by rushing through initial development with working code, you can collect feedback and make adjustments as needed over time. I often find myself enhancing this iterative process, adding and subtracting features while resisting the urge to optimize performance based solely on initial impressions.
00:20:58.080
Once I see that the feature in its original form consistently draws positive responses and functions properly, I then allocate time for performance optimization. For instance, I realized that assembling numerous queries in my initial design presented a significant load on our system. After a while, I implemented an entire feature as SQL views instead.
00:21:42.600
Active Record has the ability to communicate with these SQL views like any other model. This change meant much less Ruby code utilized and simplified the overall architecture. With the complexity moved to the database side, I drastically improved speed through this update. To summarize, I recommend getting your code into users' hands quickly. Observe how it performs, make it functional, and then delegate time for optimizations.
00:22:28.670
Pressure at work can make me a perfectionist, yet my sole additions often lead to wasted time and effort on things prematurely dubbed ideal without adequate data. This is why the selfish programmer believes that messy code is suitable to present to end-users, allowing them to provide meaningful insights through usage.
00:23:30.589
Moving to Stage 2, The Selfish Programmer embodies narcissism, primarily deciding whether to take action based on how beneficial it would be to them. At work, large applications often entail various styles of testing that ascertain components behave appropriately. Various tests fulfill distinct roles and contribute to safety nets, ensuring developers can focus on specific areas without unintentionally disrupting other parts.
00:24:43.550
However, in solo work, the need for extensive testing might not yield equivalent benefits. Since you determine your application direction alone, you might prefer alternative efficient methods for denoting your intentions rather than writing exhaustive tests. As a solution, I approached testing for Kamisama with a method I call AI testing.
00:25:43.310
This method thrives on running tests dynamically adjusted to only cover recent changes to the codebase. Watching a sped-up recording of a test illustrates how it adjusts, only focusing on features that have changed or affected. While this method may yield lower coverage overall, the high confidence I feel from being aware of recent alterations helps to balance any risks and speeds up my build cycle.
00:26:36.820
Yet I must confess that AI testing stands for “average intelligence,” which involved simple video recording rather than actual automated tests. I ultimately did not write many tests for Kamisama as I felt I wouldn’t see a good return on my investment. In a solo context, the most significant return is frequently gained through manual checks established within the app's narrow design.
00:27:24.370
Quality testing generally pays off quickly when you’re part of a team because there are numerous ways to utilize those tests throughout the workflow. However, when you’re solo, the investment costs remain the same while returns are limited, with the value mostly coming from local checks and infrequent continuous integration runs.
00:28:16.150
Practices among developers can generally fall into two categories: maximizing mean time to failure (MTTF), which prevents breakage in systems and minimizing mean time to repair (MTTR), which denotes the efficiency of fixing problems after they arise. Balancing focus on both areas is crucial; often my career has leaned heavily toward maximizing MTTF with tests to prevent production problems.
00:29:10.400
By not writing tests for Kamisama, I seized the opportunity to improve my server monitoring skills and debugging capabilities. Many assume testing always provides value because of its mainstream promotion. But in a solo environment, it’s critical to analyze the costs versus benefits and use time wisely, which is why the selfish programmer embodies narcissism. They expend time on their interests before feeling guilty about the lack of testing.
00:30:05.420
Let’s discuss Stage 3: The Selfish Programmer knows how to set boundaries, being comfortable saying no to people. Good software requires a clear vision that narrows focus, but setting these boundaries as a solo developer is especially hard since you function as your product owner too.
00:30:50.360
In larger settings, product owners navigate responsibilities, balancing the various needs of stakeholders from numerous facets—marketing, finance, and security—while prioritizing application features. I decided early on to offer Kamisama for free, and since launching, I have around 2,300 users who have conducted millions of reviews, but I'm still the only paying customer.
00:31:58.360
This dynamic affords me an easier means to say no. In the beginning, I had clear ideas, but after launch, I sought inspiration and turned to forums where users suggested improvements. I benefitted from Kamisama’s offerings, gaining free ideas from users while declining feature suggestions that did not appeal to me as the sole customer.
00:32:45.769
Sometimes I received requests that prompted additional thoughts, such as a colorblind user expressing concerns about colored kanji distinctions that caused inaccuracies. Though not colorblind myself, I examined the color compositions and realized I could alleviate these issues through a simple validation check added to my setup.
00:33:55.220
Although that feature idea originated with another, the result helped countless users, including me as I naturally move quickly in the app. Many of my favorite Kamisama functionalities came from ideas driven by other users, highlighting how productive collaboration, albeit passive, can yield improvements.
00:34:58.500
In a professional environment, developers might find themselves too distant from users, whereby support roles often act as mediators. This can lead to temporary fixes being erroneously accepted as long-term solutions, allowing problems to persist month after month. In solo work, you directly face every message, notification, and bug report.
00:36:00.620
One day, I encountered an issue where I split a controller into two parts and accidentally deleted one of them. Unexpectedly, I began receiving numerous error reports from users accessing the old controller data. It turned out many individuals hadn’t refreshed their pages in quite some time—leading to unresolved bugs.
00:36:45.990
To eliminate such notifications in the future, I devised a solution using a gem I wrote called 'ToDo or Die.' This tool allows me to tag comments with due dates. If a comment isn’t addressed by its deadline, the gem throws an error to prompt me to revisit the necessary changes, logging a warning in production to avoid user impact. While building this gem took some time, it has effectively reduced the recurrence of previous errors.
00:37:37.3
In conclusion, The Selfish Programmer's qualities of being antisocial, egotistical, and irresponsible work in harmony to create focused creativity. I hope my experiences resonate with you, and you might find ways to incorporate more selfishness into your programming practices. Thank you all so much for your time today.