RailsConf 2021

Growing Software From Seed

Growing Software From Seed

by Sweta Sanghavi

In the talk titled "Growing Software From Seed" delivered by Sweta Sanghavi at RailsConf 2021, the speaker draws an insightful parallel between gardening and software development, emphasizing the importance of careful observation, patience, and a methodical approach to problem-solving in both domains.

Key Points Discussed:
- Observation and Debugging: Sweta shares her experiences in her garden, where she initially overlooked small signals of trouble, like caterpillars feasting on her kale. This prompts a discussion on how just like plants, applications also exhibit behaviors that require consistent observation to identify underlying issues.
- Systemic Thinking: The speaker highlights the need to look at issues holistically. In gardening, this means understanding the entire ecosystem and not just the plants. In programming, it involves understanding the entire code system rather than focusing solely on the visible symptoms.
- Learning from Mistakes: Despite initial mistakes in gardening—like using pots too small for healthy vegetable growth—Sweta emphasizes the need for learning from past experiences and assumptions. Focusing too narrowly on specific issues can blind one to the root causes of problems.
- Code Mechanics: Sweta illustrates a debugging scenario where she faces an error in coding. Instead of making hasty assumptions about the problem's source, she encourages a stepwise examination of the issue, similar to how one would investigate problems in a garden.
- Patience in Growth: She describes her journey of planting new seeds (both literally in gardening and metaphorically in software) and waiting to see what sprouts can teach about their needs. This illustrates the necessity for patience in both growing plants and developing robust code.
- Methods to Improve Observation: Sweta suggests practical tools, such as having daily review sessions ("daily diffs") and discussing assumptions aloud while coding, to enhance awareness and understanding of software systems.
- Weeding and Maintenance: She stresses the importance of addressing "weeds" in programming, which are the irrelevant errors and deprecation warnings that can clutter the codebase and distract developers from more critical issues.

In conclusion, the talk offers a compelling perspective on how gardening principles can apply to software development. By cultivating a mindset of observation, patience, and systemic thinking, developers can create more resilient applications and avoid common pitfalls associated with assuming the root causes of problems. The overarching message from Sweta Sanghavi highlights the profound impact of treating software development as a nurturing process, akin to tending a garden.

00:00:05.819 I'm sitting in my garden, surrounded by new growth, basking in the sun when I see a white butterfly fluttering through the air and landing in front of me. We lock eyes, and I'm seething. There lands my foe, coming here about our long-standing feud and also to teach me some lessons butterflies have about writing code. I'm Sweta Sanghavi, a developer at BackerKit, and I'm also a proud tender of the Earth in sunny Berkeley on Ohlone land. Today, we're going to talk about how careful observation and patience can be pivotal in both gardening and writing software, and I will share a few times when I didn't have either. I will first walk you through some examples from my garden and then reflect on the lessons they led me to in code. We’ll end with some tools that have helped me implement these lessons in my day-to-day coding.
00:00:50.820 This past spring, my friend asked if I wanted her extra veggie seedlings. At this point, I had no space of my own for them and hadn't grown anything edible since I left a basil plant on a radiator all winter. I dreamed of stepping out into my lush garden and picking out whatever was in season for dinner. After some light persuasion from my boyfriend, we were back with twelve Solo cups of seedlings. I quickly repotted them, and we were off. I was committed to making my babies thrive. I researched hours of direct sun, how often to water, and the NPK ratio of different fertilizers. As an avid Stack Overflow reader, I found gardening forums to post all my beginner questions. How do my plants look? Could I expect a bountiful summer harvest?
00:01:39.619 Well, the comments I received spanned from 'that's a lot of information' to insights about how my veggies were growing. You may notice one common theme: my friends from Garden Stew agreed that my pots were too small to grow the flavorful veggies I was looking for. Hank advised me to plant my veggies directly into the ground if I could, saying it's time to give the roots more space. The thing is... as I mentioned earlier, this wasn't my backyard, and I didn't have space for even larger pots, nor could I transplant my plants into the soil. I thought that if I got everything else right, I could make this work. These are all direct quotes to show you how kind, informative, and downright funny gardeners can be.
00:02:31.319 Meanwhile, I diligently watered and waited. One day, I was checking on the growth of my squash blossoms and went on to water my kale. As you can see, my kale has been full of character— a little crinkled, a little misshapen— but this time, I did a double take. It looked like that character morphed right in front of my eyes into holes! They were everywhere. I went into a slight panic. What was causing this? Did the roots not have enough space to grow, and was this impacting the leaves? I took my problem to the people. My friend Brad said it looked like I had caterpillars feasting on my kale.
00:03:06.379 I'm going to be honest; I didn't think that was it. I had looked at my kale every day when I watered it. I would have noticed caterpillars and was still fixated on the crowdsourced advice about the small pot size, but I humored Brad and decided to take a closer look. At first glance, I thought I didn’t see anything, but then I noticed something moving. Suddenly, I saw multiple things moving— wriggly foes munching away on my kale! I didn’t realize I was on a true debugging mission here. I was bewildered; as committed as I was to growing healthy kale, I hadn’t noticed the caterpillars. I moved the caterpillars onto a different plant, but it only got worse when I looked again and found more caterpillars. How was this possible? I recalled a quote by Pam Pierce in 'Golden Gate Gardening': 'Make a conscious effort to look at your garden. Look to spot problems in time to solve them. Develop a feel for the plants— how they grow and how they react to stress. Turn leaves over, notice what's flying or crawling about, and sit down to study a plant or a square foot of ground until you've learned something new.'
00:04:54.780 Taking Pam's advice, I sat down in the dirt. I looked at the soil, turned over a leaf, held it in my hand, and looked closer under the leaves to find some white specks that I certainly would not have noticed while watering my kale. I realized, 'You don't just have caterpillars; you have eggs!' A memory flashed into my head: I was drinking coffee in my garden, watching the hummingbirds and butterflies fly around, feeling like I was really doing it. These beautiful butterflies were the culprits, laying eggs that hatched into caterpillars, munching on the kale I wanted for my dinner— not these adorable caterpillars! It wasn’t enough to move the caterpillars; I needed to remove the eggs from my kale.
00:06:01.320 This experience served as fertile ground for some lessons in and out of the garden. It reminded me that the unknown or weakest part of the system is not necessarily the root of any undesired behavior. I was so fixated on solving the problem of the holes, and then the caterpillars, that I never dug into the root of the problem: the butterflies laying the eggs. Similarly, because I knew my plants didn’t have enough space, it was easy for me to fixate on that. I carried forward the assumption that any problems with my plants would come from the size of the pots. That focus made it harder to look for other signals from my system and to examine my plants holistically.
00:07:32.460 While I was looking at my kale every day, I was only looking for something specific: new kale leaves as I watered them from above. It can be so easy to look at something regularly yet miss key details about it. I wasn’t looking at my kale holistically— I’d neglected to check under the leaves or the soil, nor was I considering my kale as an element of a larger system by examining the critters around it. Perhaps if I had slowed down to look closer at my kale, I might have noticed these holes earlier and identified them as bite marks. I could have asked myself, 'Why are there more butterflies in the backyard recently?' It’s a quieter sign but, in our case, potentially the root of our trouble with the chewed-up kale.
00:08:34.200 This experience taught me the value of looking differently— shifting from scanning for signs of progress to being curious about every change that happens and questioning the why behind it. Through examining my garden and kale holistically, I realized that expert gardeners don’t necessarily know exactly what to do in terms of the inputs like sunlight or fertilizer. Instead, expert gardeners are just expert observers, hearing the signals from their plants. It’s not that these gardeners never face problems; they observe signs early so they can act quickly.
00:09:19.780 I’d love to tell you that I learned my lesson, but alas, I recently wanted to use my leaf grower to remind myself about the sunlight requirements of kale. I called Fetch and attempted to save our new leaf grower into a variable, then keyed into some requirements, only to see this error appearing: 'Was it actually a symbol? Was there no underscore in the key? Or did some requirements not exist on this object?' I felt the urge to go into debug overdrive. But then—wait! Let’s apply our previous lesson. What would it look like to examine our system and look beneath the kale leaf? Let’s closely analyze the error.
00:10:49.200 At first glance, I didn’t even understand this error, so I reactively changed the key. The error was a type error related to 'no implicit conversion of string into integer,' highlighted by the square brackets beneath the error message. The square bracket is just syntactic sugar for a method throwing this error. The keyword passed is the parameter for this method. Then I thought further about the 'no implicit conversion of string into integer.' In this context, the string refers to the key 'sun requirements.' Why are we trying to convert that into an integer? As I slowed down, I considered that my first instinct was to change the key, but if I keyed into a plant object or a kale object with a key that doesn’t exist, we would simply get nil, not an error. I was following an assumption that the key was wrong.
00:12:33.600 I pondered the integer conversion—why was Ruby trying to convert my string 'sun requirements' into an integer to pass back to the square bracket method? Oh man! This made me think about how square brackets typically expect an index. I realized I assumed my method was returning a plant object, but instead, it was an array of plant objects. This was one of those moments where Ruby was trying to help us by assuming we meant to call an index, but that assumption led to an error we didn’t expect. I recollected my lesson from growing kale: it’s easy to assume the issue is due to the size of my pots in one case or the formatting of the key in another. That assumption kept me from investigating further into the caterpillars, leading to the missing eggs, or, in this case, fully understanding the error, both of which were signals from my system.
00:13:27.960 This is a pretty simple example but not an unfamiliar one. I use this as a stand-in for the many times we react to unknown or unexpected behaviors. Countless times I’ve misread a stack trace, misdiagnosed a bug, or misunderstood what a line of code was doing simply because I thought I knew and carried that assumption forward. It’s easy to become numb to specific errors, reflexively changing what I’m most unsure about. Sometimes, the issue is a mock; I don’t feel confident I wrote correctly, or it’s a new API I’m working with and I’ve misunderstood what the keys are. Often, the complexity of the task at hand obscures the simple stepwise process of fully reading the error.
00:14:51.600 So what can we learn about those tricky places we find ourselves in regularly? Sometimes, these issues resolve themselves upon a second glance or a simple fix, but how often do we carry assumptions further, shifting into autopilot mode? I think of being confused by an error and checking if it relates to a bug I saw last week instead of fully reading the error to determine its source. When I commit to observing the signals in our application, I can save time and frustration. So we must ask: what can we learn from our chewed-up kale moments? It’s about developing an eye for the earlier, quieter hints of problems.
00:16:16.740 As spring turns to summer and summer cools into fall, lucky for us East Bay growers, we have a long season of cool-weather gardening ahead. This time, we’re going to grow carrots and beets from seed, and since we last gardened together, we’ve got a backyard. We can grow into the ground now— no more crimped pots for us! To do this, I need to make space in the yard for my new vegetables, so I dig up the weeds growing in a patch of ground, amend the soil with some compost, and then sow carrot and beet seeds. Then we wait.
00:17:31.140 Several weeks pass, and some seedlings start popping up. However, I realize that some of these are weeds from before. Now that we're growing outside of pots, we’re subject to more deeply rooted plants— like volunteer plants that come from seeds that are naturally dispersed, for example, by wind or squirrels. The thing about root vegetables is that they need plenty of room to grow their thick roots. When they’re crowded, their growth can be stunted. I wanted to protect my carrots and beets from other root systems growing too close to them, so I knew I had to diligently weed. But I didn’t know how to differentiate the plants I wanted from the weeds I didn’t.
00:18:56.640 To answer this question, I went to a new gardening group for advice. Harris said, 'I’d wait until those sprouts are big enough to identify before starting to pull weeds.' I thought the gardeners I consulted would point out what to keep and what to discard, but I realized I was getting a larger lesson than just a task at hand. Seedlings look alike when they first sprout, and we need time to pass before we know where the seeds we planted belong. Harris also asked us, 'What is a weed?' At the end of the day, weeds are simply plants.
00:19:44.420 I realized I was labeling anything that wasn’t a sprout from the seeds I'd planted as a weed. I was dismissing anything I didn’t expect as a weed. What was the cost of that demand for growth? When we dictate what grows in our garden based on our own expectations, we let those expectations guide the growth instead of allowing the seedlings themselves to flourish. In weeding my garden before knowing what I actually had or how the new growth would interplay with my beets and carrots, I missed the opportunity for volunteer plants and other great additions to my garden. Taking time to see what was sprouting allows me to instead be dictated by the health of the seedlings or their utility, rather than solely by what I was expecting to see.
00:21:05.640 Naturally, this made me think of the seedlings we plant in our code when we build our applications. When we build based on predictions, we similarly lose this organic growth. Did you know that the butterflies that lay eggs on my kale actually hate the smell of thyme? Planting thyme next to your kale can help deter those butterflies from laying their eggs. Here’s the other gift Harris shared with us: let your seedlings grow to find out what needs to be weeded. Great software is not built— it is grown.
00:22:12.840 Let’s dive into a practical example. We have a new requirement: we need to import data from the plant API, specifically about leaves, and we need to store those leaves locally while associating them with existing stems in the database. So we need to fetch leaf objects and associate them with stems.
00:22:54.300 The first question is, what's the entry point? Where are we going to plant our new code? One option is the root grower, an existing service where we fetch roots from the same API and attach them to stems. There's already a lot of code we’d need to reuse, such as the connection to the API, the code to grab existing stems from the database, and the code that iterates through them to set those associations. I could imagine adding some basic code to that existing service.
00:23:35.700 We could add new code to this existing service, so we first attach the roots and then associate the leaves based on our new requirements. Let's consider the trade-offs of this approach: it may only involve a few lines of code— a quick change in an existing file. However, the downside is that the attachments of the leaves and roots are dependent actions. We must adhere to the existing order of operations and remain coupled with the architecture of the existing root grower. Additionally, we would be adding complexity to our root grower since it now also has to know how to grab leaves and associate them with stems.
00:24:32.700 Another option would be to plant an entirely new object in our app. This process may seem less appealing because it adds a significant amount of new code— new files, new tests, etc.— including a good portion of code we already have in our root grower. Consequently, if we wanted to make any updates to the code connecting to the specific API in the future, we would need to do it in multiple places.
00:25:13.620 However, this approach provides flexibility. Sprouting a new class will allow us to decouple our new feature— growing leaves— from our root grower. It lets this new feature develop independently from previous features where organic forces in our application, like bug fixes, feature additions, and adjacent refactors, can nurture our seedlings. By allowing this object to grow, we can better understand what connects those two surfaces without artificially coupling them in the same space. Taking this idea further, we could even create two new objects— one for fetching leaves and the other for associating stems.
00:26:27.240 We could even introduce two different triggers— not automated like the previous service with a worker— perhaps two distinct buttons for our users to press. Previously, we had to attach the leaves exactly where we were attaching the roots to make use of the existing code, for example, during the iteration of the stems. Now, if we establish two different buttons for each of these triggers, we could give users control of this feature from multiple locations. It might seem like a downside since the user has to click both buttons instead of one and introduces new potential for bugs, but it can also empower them to be more engaged with our system. They reveal important information about how they choose to use our new features— information we’d lose if we dictated when this data would be fetched.
00:27:31.680 This method of planting new objects into our code base also allows us to leave some seams in our application. A seam is a place where you can alter behavior within your program without modifying the actual code in that section. By creating separate objects with varying call sites, we can develop seams for each of these behaviors. A seam can serve as a delineation of a separate feature and possibly point to areas of the code more likely to change. Leaving seams where we plant seedlings provides an edge around flexible, decoupled objects that can adapt to our evolving requirements.
00:28:12.120 Through this process, we enable the abstraction to develop organically. Perhaps we’ll discover that the most valuable abstraction is the one that connects to the stems. We truly see this approach shine when new requirements arise. Rather than cramming everything into the same object as our root grower, we can consider the different seams available to us. This framework can also help when approaching legacy code. When we add to pre-existing code, we can ask: what factors are necessary to leave seams where there weren't any before? This technique really underscores the point made by Sandy Metz that duplication is far cheaper than the wrong abstraction.
00:29:36.660 When we build modular objects in our application, they can grow and evolve in response to the demands of the code. As objects grow towards each other, we can establish abstractions that make sense and that arise in reaction to our changing needs rather than because we’ve crammed our code together or due to superficial similarities. This approach can significantly reduce premature refactoring or coupling features based on implementation specifics. It encourages us to let our seedlings grow, guiding us on which refactors our application truly needs.
00:30:52.740 I wanted to show you what happened from my case three weeks later. I was convinced this seedling was definitely a beet, only to find out I was wrong. I share this experience because one challenge of waiting to weed or transplant your seedlings is that you have to keep some seedlings around that you're still unsure about. Ultimately, it takes patience— the difficulty of this philosophy shared between gardening and coding is that it requires discipline to allow a plant to grow larger, knowing it may become more difficult to move when we are unsure if it belongs where it is. When we revisit code that we know is still developing, it takes discipline to leave it there and wait for new sprouts to inform our abstractions.
00:31:56.280 Action can feel easier than letting time pass and allowing our garden to grow. However, this raises an important question: when is it time to refactor? When we have different objects, each with their own call sites, it can lead to code that looks duplicative or redundant. A prudent rule of thumb is to wait until you have three different places doing the same thing to justify a refactor. To me, the more powerful idea is that the biggest mistake is refactoring too early. Wait until you see what your seedlings are before you weed.
00:33:16.200 Now, let’s discuss some tools that could be helpful. Personally, the most beneficial skill I’ve developed is an awareness of a very specific feeling. Close your eyes and picture this: it’s 4:30 PM, and you've been solving a bug all day, so you're inches from a solution, yet you know you're missing something obvious. Think back to the last time you felt that way and ask yourself— are you truly listening to your application? Are you reading errors fully? Are you being thoughtful about the changes you make in your code? You can open your eyes. This is all about identifying that feeling and having strategies to help us avoid falling back into that desperate place. We want tools to help us emerge from it.
00:34:24.480 The first tool I'll offer is to talk to your plants. In general, incorporating the practice of stating assumptions can naturally slow you down when we work. That might mean articulating how we expect our tests to fail or stating which hypothesis we're testing while debugging. For example, 'I wonder if the variable is not being set correctly in the controller; let’s check that file.' It could also mean reading each error aloud, following the method that threw the error, and working through the stack trace step by step. Additionally, ask yourself, 'What question are we trying to answer right now?' These practices and the resulting slowdown serve as an effective antidote to the sense of panic.
00:35:51.839 This technique can be beneficial in both pair programming and solo coding with your rubber duck. I’ve had pairing sessions where my partner and I became increasingly confused by a bug, each wanting to quickly try our hunches, frantically grabbing at the keyboard. While we might feel we’re making progress, more often than not, this led to spinning our wheels and testing wild theories that would have been quickly invalidated if we had examined the situation more closely. I’ve also experienced moments where I was confused by code and sought a pair for assistance, only to feel frustrated because they made me start at the top and step through each piece of the code path. However, that seemingly unproductive practice often yields the most insight, as they help uncover the assumptions underlying most bugs.
00:37:02.880 Shifting the coding dynamic to be more open to slowing down and striving for the simplest explanation can be incredibly beneficial. Practicing a stepwise approach regularly will make it more likely that you’ll turn to it during frustrating moments. The second tool I recommend is to 'walk in your garden' as Pam suggested. One way to do this is by integrating the practice of daily diffs into your development process. At BackerKit, we spend 15 to 20 minutes after our stand-up meeting reviewing the commits from the past day. This familiarizes the team with all the changes in the codebase, which helps highlight areas of the code that are changing. This can be useful for triaging exceptions that our app is throwing.
00:38:17.640 It’s not uncommon for me to suggest, 'I think we saw something about that in the daily diffs.' Reviewing these commits also enables us to see the patterns evolving within our app. For larger teams, reviewing everything from the previous day may not be feasible, but you could focus on commits for a specific product or epic, or from certain teams. Perhaps labeling commits to review the following day could be beneficial. Another effective approach could be simply to time-box the review of as many commits as possible within 15 to 20 minutes. The general awareness and surfacing of changes can be extremely helpful.
00:39:06.480 The final tool I suggest is weeding. Be mindful of what you label as weeds; some parts of your garden may be more weedy than others. I'm specifically referring to noise in terms of deprecation warnings, console errors, and logging. These can sprout in your terminal or in the console where your tests are running or during continuous integration. It's tempting to ignore them, and it can be easy to become desensitized to that noise, making us less likely to notice when we introduce new issues into the codebase or when errors arise while working on a feature.
00:40:15.960 When it comes to tending to these, I’d say take care of them as soon as you notice them. Make it a habit to take that detour to address them or at least to create a ticket if it appears it will take longer to investigate. This can help keep your garden as close to weed-free as possible. If you’re in an environment already inundated with noise, one effective strategy is to create a ticket for each issue and commit to tackling, say, 20 of them each sprint to manage that debt.
00:41:37.560 I want to thank you for attending 'Growing Software from Seed.' Here's a beet seedling that the butterflies haven’t gotten to yet. Let me know if you try something different in your team or garden. My Discord nickname, Twitter handle, and my email are available. I’ll be checking this channel throughout RailsConf. I also want to give a shout-out to BackerKit, where I get to explore these ideas further with my team. At BackerKit, we create amazing things for creators and crowdfunding, striving to grow both our code and our people. We're currently hiring for developer and designer positions, and I’d love to tell you more. Thanks again, and have a great time!