The Bike Shed cover image

The Bike Shed

Latest episodes

undefined
Dec 6, 2022 • 34min

364: Constructive vs Predicative Data

Stephanie and Joël attended RubyConf Mini, and both spoke there. They discuss takeaways and highlights from the conference. The core idea for this episode is explained in this article: Constructive vs. Predicative Data. This came up recently in a conversation at thoughtbot about designing a database schema and what constraints could be encoded in the schema directly versus needing some kind of trigger or Rails validation to cover it. This episode is brought to you by Airbrake. Visit Frictionless error monitoring and performance insight for your app stack. RubyConf Mini Episode on CFP - The Bike Shed 352: Case Expressions Podcast panel: The Ruby on Rails Podcast Episode 446: I'm Giving A Talk on Thursday Slides for FP talk: Functional Programming for Fun and Profit!! Episode on language: The Bike Shed - 356: The Value of Specialized Vocabulary Constructive vs. Predicative data Avoid the Three-state Boolean Problem Transcript: JOËL: Hello and welcome to another episode of The Bike Shed, a weekly podcast from your friends at thoughtbot about developing great software. I'm Joël Quenneville. STEPHANIE: And I'm Stephanie Minn. And together, we're here to share a bit of what we've learned along the way. JOËL: So something that's very recent in both of our worlds has been that both you and I, Stephanie, attended RubyConf Mini, and we both spoke there. What are some of your takeaways or highlights from the conference? STEPHANIE: Seeing you in person was definitely a highlight. I really enjoyed that. Because we're working remotely, I don't, you know, get to be in an office with you day to day. And it was really awesome to hang out with you, I think, for the first time as co-hosts of the podcast. And we both, I think, met some people at the conference too that were listeners. And it was really awesome to share that experience with you. JOËL: I had the interesting experience of several people who told me they recognized me by my voice, which I think is a common thing for podcasters, but as a new host, I was surprised by that. STEPHANIE: Yeah, that's weird. As a podcast listener, too, I definitely know exactly what you're talking about where it's like, oh yeah, I can identify someone by their voice. But to then be that person that people can recognize is pretty weird. I also really enjoyed being an audience member of the podcast panel that you are on at the conference with other podcast folks. It was moderated by Brittany Martin. And yeah, I just thought you represented The Bike Shed really well and spoke for both of us about podcasting in a way that I really appreciated. JOËL: And for any of our listeners who were not able to be there in person, Brittany has published that episode as a podcast, and we will link to it in the show notes. STEPHANIE: Another thing I really liked about RubyConf Mini was the smaller scale. I think it was about 150 or so attendees, which felt very different from traditional Ruby Central conferences with several hundreds of people. I heard a lot from other folks there that they really liked the regional aspect of it, the intimacy of the smaller conference. I think I got more of an opportunity to run into people that I'd met at the conference over the next few days. And there was, yeah, definitely a sense of tighter knit community there, you know, when you meet someone, and then you bump into them on the way into a talk, and then you can ask how their day was going and any highlights that they had. And yeah, I guess I haven't really attended a conference that size before, and so that felt like a very special experience for me. JOËL: I 100% agree. I think the smaller format definitely makes it a little bit more intimate, makes it much easier, I think, to build some of those social connections, to meet with people, and to have some good conversations. I think the format of the conference as well favored that. There were, I think, larger breaks between talks that encouraged people to hang out and talk. And, as you said, because it's smaller, you also get to see the same people over the course of a few different breaks instead of being like, oh, I met a stranger on the morning of day one, and then in the afternoon, I met another stranger. And it's just constantly introducing yourself. One thing that was really interesting to me is the experience of being a speaker is very different than just attending. As a speaker, you get to go to the speaker dinner and connect with a lot of the other speakers there. Some of them might be quote, unquote "famous people" that you're not quite comfortable just walking up to and introducing yourself. But in the smaller dinner, you just find yourself sitting next to them and enjoying some food or a drink and getting conversations. It's also much easier to have people come up to you during the conference. Because you're a speaker, people will come and talk to you. So if you tend to be a little bit more introverted, as long as you can get over your fear of being on stage and public speaking, it actually makes social connection interaction much easier to be a speaker. I would recommend to any of our listeners who were wondering how can I get more out of a conference? How can I get better connections, better conversations? Consider being a speaker. STEPHANIE: Yeah, absolutely. We've talked about this before; I think when we chatted about writing our CFPs for this conference that speaking doesn't have to be a really big, scary thing, but everyone has something to say. I think we had mentioned in previous episodes that your talk topic came out of just a discussion that you had internally, and you were like, wow, enumerables are so cool, like, let me dig deeper into them and just share what I learned. So I totally recommend it. And this conference was my first in real-life speaking opportunity as well, and that felt super different from my experience last time doing it virtually, you know, talking about how much I love that sense of community all the time. But it really felt true for me this time around, where I could see the audience react to the things I was saying, like, maybe go off the cuff a little bit. And then yeah, at the end, having people come up to me was really awesome to just talk about pairing, which is what I spoke about, and just share our experiences. And they asked what I thought about some things, and it was really cool to just be able to spread that knowledge around. And one thing I noticed you did a lot was come up to speakers after they wrapped up their talks. You were almost always the first person to get up and congratulate them and just get the ball rolling on following up on the things they talked about. Is that something that you really enjoy doing or find particularly valuable as an audience member or speaker? JOËL: Yes, both. I think, as a speaker, it's really validating to have people come up to you after the talk and either just tell you they liked the talk or ask a question. I generally don't like to do just open questions after a talk from the audience because then you get the classic; this is more of a comment than a question or people who will tell you that you had a typo on one of your code slides. Like, none of that is useful to anyone. So, if you're really interested, come talk to me afterwards. And then that actually makes me feel like my talk connected with people, and people were paying attention, people enjoyed it, people were learning. So I try to pay that forward as well for talks that I listened to, go up to the speaker, and tell them one thing that I appreciated about the talk or a thing that I learned, or something that got me excited in their content. STEPHANIE: Yeah, I'm sure that it's very appreciated. And it also breaks the awkward silence at the end when the speaker finishes and people aren't sure if it's okay for them to get up and start moving around. Yeah, I thought that was a really good way to kind of just encourage people to start chatting with each other and moving into those break times that we mentioned earlier, those opportunities to socialize. JOËL: Another thing that I think is really fun that you can do at in-person conferences, and I know you were doing it a lot, is going to see the talks of friends and colleagues and sitting in the front row and just being there to cheer them on and encourage them. Again, I think that makes a big difference when you are on stage, and you see these people who are your friends and colleagues there to support you. It gives you that boost of confidence. And when you're there in the audience, it's fun to cheer on somebody else. STEPHANIE: Oh yeah. You gave me a lot of thumbs-ups during my talk, and I really appreciated that. [laughs] So I'm curious if there were any talks that stood out to you that you got to see. JOËL: And I was really inspired by your talk, pair programming. I think there are a lot of things that I can take from that to improve the way I pair. I was also inspired by Aji's talk, Aji Slater, on automating manual tasks that you have to do in an iterative way. That one really hit home because, on my current project, I have been doing a lot of manual things. And I just have random snippets of code, like, some shell script lines or Ruby console lines, that I copy-paste out of Slack conversations because I've shared them with other people who are doing similar work. And I realized that a lot of his advice would apply to the work that I'm doing and how that could really make things better. So that was one of those talks I was listening to, and I was like, oh, you know what? Monday morning, when I go back to my project, this is something that I'm going to start doing. This is something I'm going to change in the way I do my day-to-day work. STEPHANIE: Yeah, absolutely. I have so many tasks that I would like to get automated, and think that one day I will magically have more time in my schedule to get to it. But I liked that his talk gave pretty concrete strategies for baking it into your regular, like you said, day-to-day workflow, and that lowers the activation energy to getting them done. And then those things can be iterated on and could eventually become, in an ideal world, a fully-fledged feature that you put together from doing those repetitive tasks. And yeah, they provide a lot of value not just to you but can eventually provide value to your co-workers and then even your users in the future. JOËL: Were there any talks that stood out for you? STEPHANIE: One talk that I really enjoyed was Jenny Shih's about Functional Programming for Fun and Profit. I have attended a lot of functional programming talks within the Ruby realm, at least to try to get a better sense of how it can apply to my work and the languages and paradigms that I use. And honestly, what I liked about it was that it didn't get too in the weeds about functional programming. What she did was provide mental models for understanding the paradigm that I think was a good vehicle for understanding things very generally. And, for me, like,¬¬ a talk, it's really hard to pay attention to lines of code and to read code on the fly while people are presenting. For me, that is just not how I like to consume that information. And so she provided themes and, like I said, those mental models, which I know you really like to use a lot too in teaching people new concepts. For me, I didn't fully learn what a monad was, once again, but at least having that repeated exposure to those foundational aspects, I think, will eventually lead me to be able to grok those things a little more comprehensively the next time I see it or whenever I decide to dig deeper. JOËL: What was a mental model that was shared that connected with you particularly? STEPHANIE: So one of the main mental models that she shared was thinking about a program in terms of these three dimensions: value, behavior, and time. She had a nice slide that showed the difference between the object-oriented paradigm, where value and behavior are contained by objects, where time is kind of inherently wrapped up in those objects that hold information about the state through values and behavior. Whereas in her functional programming example, those three dimensions were a bit separate. And I found that distinction to be really helpful in separating things that felt very implicit before, but it was nice to see them broken out into very clear concepts in terms of building blocks of a program. JOËL: So it's helpful then when thinking...when you look at code, if you can think about it in those three different dimensions to help think about, am I taking a functional or other approach in this particular dimension when working with this code? STEPHANIE: Yeah, exactly. I think it also gave me more of a vocabulary to describe the pros and cons of each and a lens of thinking about which I might want to choose for the particular problem at hand. JOËL: So you mentioned there's a visual for these three dimensions from the slides. Are those slides publicly available? STEPHANIE: They are. I will link to them in the show notes. JOËL: So all of these talks were recorded. They're not yet available to the public, but I think the plan is to publish them on YouTube sometime in the new year, so that means probably January 2023. And a big shout out to the AV team and everyone who is involved in recording these. STEPHANIE: Yeah, I am definitely looking out for a link to my talk so I can send it to my mom. I also wanted to give a little shout-out to the organizers of RubyConf Mini: Jemma Issroff, Emily Samp, and Andy Croll. JOËL: Woo! STEPHANIE: They put on just a really awesome conference, and I feel very grateful that I got a chance to attend with you, Joël. JOËL: It was definitely a delightful experience. STEPHANIE: Delightful. That's a reference to Joël's talk for those of you who are listening. MID-ROLL AD: Debugging errors can be a developer’s worst nightmare...but it doesn’t have to be. Airbrake is an award-winning error monitoring, performance, and deployment tracking tool created by developers for developers that can actually help cut your debugging time in half. So why do developers love Airbrake? It has all of the information that web developers need to monitor their application - including error management, performance insights, and deploy tracking! Airbrake’s debugging tool catches all of your project errors, intelligently groups them, and points you to the issue in the code so you can quickly fix the bug before customers are impacted. In addition to stellar error monitoring, Airbrake’s lightweight APM helps developers to track the performance and availability of their application through metrics like HTTP requests, response times, error occurrences, and user satisfaction. Finally, Airbrake Deploy Tracking helps developers track trends, fix bad deploys, and improve code quality. Since 2008, Airbrake has been a staple in the Ruby community and has grown to cover all major programming languages. Airbrake seamlessly integrates with your favorite apps to include modern features like single sign-on and SDK-based installation. From testing to production, Airbrake notifiers have your back. Your time is valuable, so why waste it combing through logs, waiting for user reports, or retrofitting other tools to monitor your application? You literally have nothing to lose. Head on over to airbrake.io/try/bikeshed to create your FREE developer account today! JOËL: Coming back from the conference, I recently had a really interesting conversation with some other colleagues at thoughtbot. We were looking at a database schema for a new application and talking about some of the trade-offs involved in how that schema is structured, so what tables we want to have. Do we want to have indexes? Things like that. And particularly around some of the assumptions are business rules that would come into play. So we're looking at...we'd drawn out this Entity Relationship Diagram (ERD). In it, we're looking at all the tables, and something that comes up immediately is like, oh, it's possible to have some bad data that could show up in these columns. Or it's possible that this relationship could exist where this table has a foreign key on this table, but really, that should never happen in this particular way of working. And so then the question became, how do we try to prevent these things that currently the schema allows but that are not valid in this particular business domain? Do we want to change the schema somehow and make that stricter or find some way to prevent it? Do we want to add some kind of validation that will check some business rules first before inserting or updating a record? I'm curious, have you ever been in a situation like that where you had to balance those two approaches to enforcing business rules on your database? A classic small example of this is a situation where let's say, you have a users' table and you have a name column on there. And you want to ensure that that name must always be present; all users must have names. Do you try to enforce that via the schema with a NOT NULL constraint? Or maybe you try to enforce that with a validation, maybe a presence validation at the Rails level. Or if you're really into SQL, maybe some fancy trigger, but do it in a validation style rather than trying to force this using the schema. And our particular scenario was a little bit more complex than just one column; it was more to do with associations. But I think this sort of problem shows up even in constraints as small as a required field. STEPHANIE: That's really interesting. I think that, in my experience, when we are spinning up new tables, at that point, we do try to put some intentional thought into what the schema should look like and what requirements we might need to encode at the database level. But things that are more complex might need a little more code, like Ruby code. I have then pushed to an ActiveRecord validation. One thing that I think is important to know is that when you do set those things on the schema, it's harder to change. And so you usually have to feel pretty confident that that's what you want. Otherwise, you'll run into issues later if that does have to change and making changes to whatever existing data you might have. But it's also pretty common to just do your best when you are deciding on a database schema and then having to make adjustments down the line as you know more about your domain. JOËL: This conversation reminds me a little bit of the idea of database normalization. I think that might almost fit as a subset of general tactics of using the schema to ensure your data is more correct. When you are generating new tables, let's say you're creating a greenfield app and you need to create four or five tables; how much emphasis do you put on database normalization when you're initially designing those? STEPHANIE: I think for a greenfield project when you are setting everything up and creating tables for your main domain models, there is an aspect of it that should be considered because you're in this unique position where nothing really is in existence yet. And you do want to try to set yourself up to be successful and hopefully have information about your main use case for this app and can kind of make decisions about the schema then. At least in my experience, that has been part of the conversation, though, to be fair, because it's so early, you do have the opportunity to change things without as much effort or pain. But I think it's worth considering when you're just sitting down and working through what those models are going to look like. JOËL: And for our listeners who may not have heard the term normalization before, it's a series of...you can think of them as rules that you apply to your database design to try to avoid data redundancies in your tables. There are different levels of this; they're typically referred to as normal forms. So you'll see things like first normal form, second normal form, third normal form; those are kind of the fancy terms for them. But they generally involve breaking out other tables so that you don't have data redundancies. And in many ways, this is similar to principles such as the single-responsibility principle that we apply to objects when we're designing our objects in an OO system. But this is more at the table level for databases. STEPHANIE: I do think that it is so hard, maybe even impossible, to plan something out, to not have any of those redundancies, to begin with. And I do think sometimes they are a bit inevitable. But I also have had the experience of having to figure out what the heck I'm looking at when I am querying data and see all these things that are duplicated or maybe slightly different. And yeah, I think when you are in that position of starting a greenfield application, it is really interesting to see how you make those decisions about what needs to be enforced and where. Where did you end up landing, or what did you discuss in this conversation with the co-worker? JOËL: I think we went with a bit of a hybrid approach. Some things, we can use the schema to prevent bad data, and then some things either cannot be represented with a schema, or it's possible, but it's really cumbersome and painful. And so, we chose to try to enforce it with a validation. To me, this feels very similar to a problem in typed languages. So some communities that use a lot of types try to use those types to only allow data to come through that's in a valid shape. And so you'll hear things like make impossible states impossible or make illegal states unrepresentable. And that works for many things, but it's not always possible to enforce all of your business constraints through a schema. Or sometimes it's possible but just not practical. And so, I think there is a balance of finding when you can use the schema or when it's better to use the validation.¬ STEPHANIE: Yeah, I think my general rule of thumb is, like I mentioned earlier, things I feel really confident about that we want to make sure that we have in our database or in our data for sure. I do lean towards requiring those in a schema, and it also communicates that confidence or communicates that intent that it's something that at one point was decided is important. And so, if a future developer comes in, it would take a lot of work for them to write a migration, to remove some database constraint. Whereas I think sometimes validations at the Rails level are potentially a little more open to change and then even more so if you get to validating on the client side. JOËL: That can get to be a really, like, it's a useful tool, but one that you can really hurt yourself with. If you modify your validations at the Rails level or at the front-end level, but then you don't backfill those changes on your data in the database, then you might have records in your database that if you were to load them into memory and hit save on them again, would refuse to save because they no longer match the validations. And on longer-lived applications, I've seen that happen sometimes where not all rows in the database pass the Rails validations. STEPHANIE: Yeah, I think I've seen that be a problem either for developers who then have to backfill that data or write some migration to change some of the data to meet the new requirements, or just unexpected bugs on the users who discover something new but like you said, have been there long enough before those things were implemented. JOËL: The more I think of this, I think maybe constraints that are enforced at a validation level might still require changing the data in your database. So if you had a constraint enforced via a schema, you don't have a choice. You have to write some way to migrate that data so that it fits the new schema. You can kind of lie to yourself with validation and not change the historic data, and sometimes that is the case; you want to keep the old data and only prevent new data from being written in the old format. But if you need consistency, then you probably need a data migration regardless of which approach you take. STEPHANIE: Yeah, that definitely sounds like the more robust way to go about it for sure. JOËL: I have an article that I like to reference a lot by Hillel Wayne on Constructive Versus Predicative Data, which is basically looking at these two general approaches to enforcing data correctness and formalizing them a little bit. So do you try to enforce them based on the construction or the shape of the entity that you're creating, be that a database table, an object, a type, something like that? Or do you enforce it via some kind of predicate? So that could be a validation or other similar logic that runs kind of at runtime to enforce your constraints. STEPHANIE: That's interesting. I hadn't heard of those terms before, but I think they provide a lens through which you can look at the problem. Did the article end up suggesting different strategies for solving that problem, or was it more theoretical in different ways to look at it? JOËL: I think the article does two things. First, like you said, it gives us the words to talk about those approaches. And having those labels now, I start seeing them everywhere. I see them in databases, I see them in objects, I see them when doing types across a variety of languages. So that's already a huge win for me. I think you and I had done an episode a couple of months back where we talked about the value of having labels to put to ideas. And I think for me reading that article gave me those two labels. And all of a sudden, it really helped to make connections that I wasn't seeing before. The second thing that the article does is, I think, explore some of the limitations that each approach has and when you might want to use one versus another. The constructive approach, so using a schema, is more consistent because you know it is impossible for the program to create data that's in the wrong shape. That being said, not all constraints can be represented in a constructive manner, or it might be possible but really cumbersome. Also, sometimes it's not really invalid data; it's just sort of undesirable data. So you might want a looser schema. And let's say that you're storing some kind of intermediate state or some kind of raw input from another system that you might want to layer validations on top of, but you don't want to reject that data out of your database. You want that sort of incomplete or imperfect data in your system. Something that I find myself doing more and more these days when I create new tables is to really lock down the schema as much as possible. I think that might be contrary to maybe the way a lot of people in the community like to work. Some people might prefer to start with a very loose schema with no constraints and then work towards making things stricter as they explore the domain, and that's kind of the default that Rails has. If you're creating a new table, all columns, for example, are nullable by default. Personally, I will put a null false on every column and every migration that I make unless somebody can make a convincing case otherwise, and even then, I might try to think of is there any possible way that we could avoid that scenario and put that null false. Part of the reason for that is that it is much easier to loosen constraints on existing data than to tighten them afterwards. So if I have a column where no value is allowed to be null, and then later on we decide, you know what? It is okay for some of them to be null, I can change the requirement on that column, and I don't need to make any changes to the existing data. It just works. If the reverse happens, if I have a column that allows a bunch of nulls and then I want to make that column required, now I have to go and find a way to backfill all the empty spots in that column. And that could be a very challenging process. It might even be impossible. There might be some values there that it's just like, the user did not supply them at the time because we didn't ask for them. And now there's nothing we can put in there. So do you put in, like, unknown or not available? Then you have to ask yourself some really difficult questions about your data. STEPHANIE: Yeah, absolutely. I think I agree with you there. Another thing I like to do is provide default values for columns, especially ones where they can't be null, because, like you were saying, that helps me have a better understanding of just what is going on in the database. An issue I have seen come up involves a Boolean column where if a default value of false, for example, if that's what we're going with, is not encoded in the schema, you end up with potentially three values for a Boolean, which would be true, false, and null, and that I think has been -- JOËL: The infamous three-state Boolean. STEPHANIE: Yeah, exactly, the three-state problem, which is just inherently contradictory to what a Boolean is, to begin with. And I've definitely run into issues with that where you have to decide, or figure out, or write code to determine is null false? Is that what we mean here? It's not clear. But if you, like you said, locked it down at the beginning, provided those default values, that puts in those guardrails to prevent things from getting out of hand. JOËL: It also makes it easier for users of your database, application, whatever to interact with your code. I've run into this a lot when working with GraphQL APIs. And the default in many GraphQL server implementations is to make all fields nullable by default. When you build your schema, you have to add some extra things there to say, "This field is non-nullable," which means that a client that's now consuming it, anytime they deal with the data they need to check, is it present or not? You can't have the confidence that that data is there. And so it can force a lot of extra checks on the client. Or I guess you could just take it on faith and hope nothing breaks. STEPHANIE: Yeah, it's funny you mention that because I definitely think there's like spheres of impact. So as a developer, you maybe start having to write code that checks those kinds of things, like if it's null or not in your code. Then that can even extend to, like you said, your users or consumers of the API, who then have to contend with data that they have no control over. And I've been there too, and that can be frustrating as well. JOËL: We've talked a lot about data correctness and different ways to achieve it, different strategies. Why is this something that we care so much about? STEPHANIE: I think data correctness is really important from a developer experience perspective. And it's way easier to fix a bug in your code than it is to wrangle a lot of accumulated bad data. JOËL: Yeah, sometimes bad data is not fixable at all, and those are situations where you have a really bad day as a developer. STEPHANIE: Agreed. JOËL: Well, on that note, shall we wrap up? STEPHANIE: Let's wrap up. Show notes for this episode can be found at bikeshed.fm. JOËL: This show has been produced and edited by Mandy Moore. STEPHANIE: If you enjoyed listening, one really easy way to support the show is to leave us a quick rating or even a review in iTunes. It really helps other folks find the show. JOËL: If you have any feedback for this or any of our other episodes, you can reach us @_bikeshed, or you can reach me @joelquen on Twitter. STEPHANIE: Or reach both of us at hosts@bikeshed.fm via email. JOËL: Thanks so much for listening to The Bike Shed, and we'll see you next week. ALL: Byeeeeeeee!!!!!!! ANNOUNCER: This podcast was brought to you by thoughtbot. thoughtbot is your expert design and development partner. Let's make your product and team a success.Sponsored By:Airbrake: Deploy fearlessly and fix bugs faster with Airbrake Error & Performance Monitoring. Airbrake notifiers are available for all major programming languages and frameworks, and install in minutes, with an open-source SDK-based install and near-zero technical debt. Spend less time tracking down bugs and more time developing. Visit Frictionless error monitoring and performance insight for your app stack.Support The Bike Shed
undefined
Nov 22, 2022 • 34min

363: Deployments

Joël discovered Bardcore. Stephanie planned and executed an IRL meetup for folks in the WNB.rb virtual community group in Chicago and had a consulting win. Together, they discuss what deployment processes look like for clients in their current workloads. This episode is brought to you by Airbrake. Visit Frictionless error monitoring and performance insight for your app stack. Hildegard von Blingen YouTube Channel Hildegard von Bingen - Historical Character WNB.rb git flow Transcript: STEPHANIE: Hello and welcome to another episode of The Bike Shed, a weekly podcast from your friends at thoughtbot about developing great software. I'm Stephanie Minn. JOËL: And I'm Joël Quenneville. And together, we're here to share a bit of what we've learned along the way. STEPHANIE: So, Joël, what's new in your world? JOËL: I've been getting into something that's kind of fun and quirky. It's a new musical genre called Bardcore. STEPHANIE: Bardcore. JOËL: Yes, it's basically re-mixing pop songs to make them vaguely more medieval, oftentimes using acoustic instruments, something that sounds a little bit more like maybe a lute in some flutes, oftentimes also kind of changing the lyrics a little bit to use more old-timey language. When the lyrics use words from modern life, sometimes changing them to something that would fit more in a more medieval setting. It's a lot of fun. STEPHANIE: That sounds so fun. When are you normally in the mood to listen to Bardcore? JOËL: It can be fun while coding because it's fairly chill as a genre. Honestly, I feel like it can also be good when I'm just sort of feeling a little bit nostalgic or daydreamy. I think it's good for that mood as well. STEPHANIE: I love that. I can't wait to go and listen to some Bardcore after this. JOËL: Let me recommend the YouTube channel Hildegard von Blingin' as a great entry point into the genre. STEPHANIE: Incredible. I can't wait. In fact, I'm going to end up sharing it with all of my D&D friends too. [laughter] JOËL: The channel is a play on words of an actual historical character, Hildegard von Bingen, who is, I want to say, a 12th-century nun but also a polymath. So she wrote on all sorts of topics, from biology and the natural world to theology. She was a musician, just one of these like really talented people that made a mark on the medieval world. So it's kind of fun that they used her name as the inspiration for the channel. STEPHANIE: Yeah, that sounds right up your alley. I knew that you were going to come with a historical tidbit about Bardcore. [laughs] JOËL: Another really cool thing that I appreciate about the channel is it's not just audio. It's also beautifully illustrated. So the creator has created visuals inspired by medieval manuscripts illustrating the contents of the song. So it's kind of funny to see something that...modern pop songs aren't always the most deep lyrics, and to see them given this medieval manuscript treatment is amusing, for me at least. STEPHANIE: That sounds really funny and also kind of calming. I was just thinking about what you said earlier about how they sometimes rewrite the lyrics to be more about medieval life. And I love the idea of taking the things that pop songs are about these days and applying them to historic life back in the day. JOËL: A lot of pop songs also are about love, and romance, and breakups. I think that kind of fits with some of that 12th-century troubadour-style romantic songs because that was definitely the kind of thing that they were singing about back then as well. STEPHANIE: Absolutely. JOËL: So I've been jamming out to Bardcore this week. What is new in your world, Stephanie? STEPHANIE: I'm really excited to share that I had an awesome weekend. One of the things that I had been doing the past couple of weeks was planning an IRL meetup for some folks in Chicago, people in the WNB.rb virtual community group. I've mentioned it on the podcast before when I was a guest. But WNB is a Ruby community group for women and non-binary folks. And we just started creating regional Slack channels. And so I started a little Chicago channel and planned a brunch. So on Sunday, a few of us, I think it was six, some old friends and some new met up for brunch in Logan Square in Chicago. And it was really awesome to do a local meetup. I haven't done something like that since pre-COVID times, and so it felt really special. JOËL: That's exciting. Were you big into the meetup scene pre-COVID? STEPHANIE: So I was working remotely for a previous company when I moved back to Chicago, and so was still trying to meet people here, find a community, find some friends. And I did go to a few community groups, but that was not too soon before COVID started, and so I didn't get to really invest in them the way that I had hoped. So it's really exciting to me to potentially be able to start doing that again. JOËL: This new meetup that you were at, was it focused more on the social aspect of things, or was it a more technical meetup? STEPHANIE: It was definitely more of a social aspect. I would be really curious to know if that group would want to focus on some more technical things. But we had a nice diversity and experience levels and the types of work we were doing. So there were a few of us who were consultants, a few of us at product companies. And I think we shared a lot about our different experiences. We talked a bit about the pros and cons of product versus consulting. And so it was really nice to just learn more about what other people are up to, what tech and framework people are using, and chat casually in that sense. But I also definitely see some more opportunity to focus on technical stuff if that moves us. JOËL: I think that was probably my favorite part of Ruby meetups back when I was attending those a lot here in Boston, where I'm based, getting to chat with other developers in the city, hearing about their experiences on different topics. And oftentimes, it will sort of revolve around tech to a certain extent, but it's not always like a formal have a presentation. Sometimes just socializing is almost more fun or brings more value to me. STEPHANIE: Yeah, I totally agree. I also wanted to share another thing that happened to me this week. It was a bit of a consulting win. So on my client project, we have been having retros every two weeks at the end of the sprint. But I was noticing with a fellow thoughtboter that we weren't really getting a lot of engagement in retros. It was kind of the same folks speaking and bringing up issues because we were doing it in a style that was like a retro board, and then folks could write in cards or raise their hand in the meeting to add something to one of the columns. And so, we ended up proposing to do a round-robin style format for retro. And we just had our first one yesterday using that new format, and it was received really well. Everyone went around and shared things that went well. And then, we went around again and shared things to improve or risks or concerns that we had about the sprint. And it was really nice to have everyone participate, to hear folks piggybacking off of what other people said. And I think we were able to get a better sense of what the group was feeling. And yeah, there was a new hire who was just observing our retro, and she is going to be facilitating these kinds of meetings for other teams. And she seemed really into it and wanted to bring it over to other teams as well and try it there. And so that felt really good to know that we were able to make a change that was an improvement for our team but might even have an impact on other teams at the company as well. JOËL: I love that. I think a lot of what we often bring to the table, because we've seen things at a lot of different companies, is not just code improvements but also process improvements. Every company is different, so you can't always just copy-paste things from one place to another. But being willing to try new things, experiment, and then follow this iterative, continuous improvement approach, not just with the code but with the process as well, I think, is something that is really valuable in the work that we do for our clients. STEPHANIE: Yeah, absolutely. JOËL: And it sounds like here you iterated on their retro process. And everybody seems to really like this new iteration, so that sounds like a big win. Congratulations. STEPHANIE: Thanks. I really appreciated that they were open to trying. That made me feel really good and makes me feel empowered as a consultant to be able to like you're saying, leverage that experience and suggest things that can just improve the quality of life for our clients. JOËL: Another area that I think we've seen a lot of different ways of doing things, and we've actually been able to iterate a lot as far as process goes, is deployments. How do we get our code from, let's say, passes code review, and then, at some point, we want it to go live in production? So what does deployment look like on your current client, Stephanie? STEPHANIE: I'm glad you asked because I'm experiencing a deployment process on this client that's actually a bit different than what I have seen before. So this client is not a super big team, but maybe, I don't know, between 30 or 50 engineers would be my guess. I am working on a smaller team with just four developers. And so I'm seeing a lot of code get merged into our big Rails app pretty frequently by other teams. And we are also merging to the same app. So my client has release managers who rotate each day and go through all of the different teams' pull requests that are ready to be merged. They will merge those pull requests on the developers' behalf. And then once everything is merged into an integration branch, they will then merge all of that stuff into their production branch and kick off a deploy. JOËL: Wow. So does that mean that developers on your team don't merge their code? You just when you get an approval, you ping the release manager and ask them to merge it for you? STEPHANIE: Yeah, so developers don't merge their own code. We might move the card into ready for deployment, and that's how release managers know that that PR is ready to be merged. JOËL: And are you then following something that's roughly like Git flow where you've got this sort of development branch, and then at some point, commits get maybe cherry-picked over to the main branch, which then gets released? Or maybe it's even a special dedicated release branch. What does that look like in terms of the Git workflow? STEPHANIE: Yeah, we have that release branch that you mentioned that eventually gets merged, either through the GitHub GUI or a CLI by the release manager, into the main branch, essentially. And that's what then gets deployed. JOËL: How do you handle situations where a feature goes out to production, and then you realize that there's a bug or there's something that you don't like about it, and you would like to revert that feature? STEPHANIE: Yeah, that's a great question. This has happened to me once now, where I merged some code that ended up introducing a regression. And unfortunately, I wasn't tagged or pinged, so I didn't really know about this until the next business day and caught up with Slack and saw that someone else had to resolve my issue, which was kind of a bummer, I think, because with this process, once that code is, quote, unquote, "done," since I'm not the one merging it, and I'm not the one deploying it, I don't get a chance to follow up on the changes in production and then check to see if they look good. When things go wrong, it seems like it kind of takes a bit of time to figure out how to get it resolved like; who would have the context? And then, if they're not available, someone else might have to jump in and fix it. So it's been interesting because, on one hand, I totally understand that they want to be releasing just once a day. Like, it's nice to have a dedicated person do all of this stuff that is work and would take away time from normal development. But I do sometimes feel like I don't have as much ownership over my feature with this process because, like I said, it just kind of is out of my hands. And oftentimes, I might be done with my work, but that doesn't get deployed for a few days depending on other things going on with the team. JOËL: That's interesting that you mentioned that it might not be deployed for a few days even though it's done and maybe merged. I think, generally, we assume that merging a commit into the main branch and deploying it are going to be more or less the same thing. But oftentimes, you might end up in a situation where there's a feature that's done in development, but we don't want it to actually go live for our customers for a while yet. And that might be for technical reasons because we're waiting for other pieces to be in place, or it might be for business reasons because we did the work, but this feature has to come out on a particular date, and so that's when it's going to go live. So then you end up in that awkward situation, maybe where you want to deploy something else. But you've got a commit already on the master branch that can't go out with the others. And you've got to do an awkward cherry-pick. Have you ever been in that situation? STEPHANIE: I have. I remember being on a project where we had features in our main branch, but that hadn't been deployed to users yet. We actually didn't want that to be live yet but then had an issue with an existing feature that was already live that we had to make a hotfix for. And that was definitely one of those cherry-picking situations that did become a bit hairy and wasn't too fun. It sounds like you have had experience with that type of deployment process as well. JOËL: Yes, I think of a project where that was a very common problem because there were a lot of features on that project that were gated to a particular time. So a lot of the features going live for customers were decoupled to the actual development lifecycle. And on that particular project, we used a lot of feature flags on the commits. So we'd control whether or not a feature was live for the customer. It wasn't, is this commit in the main branch, but it was, is this feature flag on or off? STEPHANIE: Yeah, we're using feature flags on this client project as well. And so, in some ways, I think that if we did have a more continuous deployment process, it would be okay because this big feature that I'm working on on my team we're not trying to go live until a month from now, but we have been slowly, incrementally pushing features underneath the flag. But even then, we do still have a bit of an async process because of this daily release flow. MID-ROLL AD: Debugging errors can be a developer’s worst nightmare...but it doesn’t have to be. Airbrake is an award-winning error monitoring, performance, and deployment tracking tool created by developers for developers that can actually help cut your debugging time in half. So why do developers love Airbrake? It has all of the information that web developers need to monitor their application - including error management, performance insights, and deploy tracking! Airbrake’s debugging tool catches all of your project errors, intelligently groups them, and points you to the issue in the code so you can quickly fix the bug before customers are impacted. In addition to stellar error monitoring, Airbrake’s lightweight APM helps developers to track the performance and availability of their application through metrics like HTTP requests, response times, error occurrences, and user satisfaction. Finally, Airbrake Deploy Tracking helps developers track trends, fix bad deploys, and improve code quality. Since 2008, Airbrake has been a staple in the Ruby community and has grown to cover all major programming languages. Airbrake seamlessly integrates with your favorite apps to include modern features like single sign-on and SDK-based installation. From testing to production, Airbrake notifiers have your back. Your time is valuable, so why waste it combing through logs, waiting for user reports, or retrofitting other tools to monitor your application? You literally have nothing to lose. Head on over to airbrake.io/try/bikeshed to create your FREE developer account today! JOËL: How do you feel about continuous deployment in general? STEPHANIE: So the reason why I found the way I've been describing so surprising is because I am more used to a continuous deployment flow. When I used to work for a product company, and we were a team of, I don't know, like 30 engineers, we'd merge our own work. And then our main branch would automatically be deployed. And so we could make sure our changes looked good in production and then feel a sense of we finished this feature. But we did also run into problems there because our CI build time which had to run for every single time that code was merged into main, took maybe 20-25 minutes. So whenever we merged to main, we would have to wait for CI to build, wait for the deploy to go through, and that might be a few extra minutes depending, and then confirm our changes. And that became more of an issue when there was a backup in the queue of a lot of people trying to merge code. And it's funny because we kind of want to be constantly merging. That's kind of a sign that things are moving along. And it ended up being that deployment was the bottleneck in some instances, especially if there was a CI build that broke, and then it was kind of like a car crash a little bit where there was this huge backup. That wasn't great either because when you have to babysit your deploy like that, I didn't find that I had a ton of focus time to go and pivot to something else. I was just keeping an eye on things the whole time. JOËL: Would you have preferred a workflow that maybe didn't run on every commit but maybe ran once every 30 minutes and just bundled any commits that happened within that? So maybe it's one commit, maybe it's four. Or would you have maybe preferred one where you didn't run the tests before deploying? You're just like, you know what? We trust that we ran them on the branch. It's good. We can just go straight to production. STEPHANIE: Well, that idea, the second one of not running tests before deploying, I've never even thought about that. I think that it does provide some value because when you integrate changes into main, sometimes that might cause unexpected issues. So I think, in my experience, the times when CI failed, it usually was for a valid reason, and it wasn't just a blocker that we then had to retry the build for. But what you were saying about bundling commits or a set of changes and then deploying on a scheduler maybe a few times a day automatically, that sounds really interesting to me. I have never worked on a team that has done it that way. But that sounds like it could be a good, happy medium between the two processes. JOËL: I think that's effectively what the release manager is probably doing manually. But if there was a way to just do that automatically where you just say you merge to the main branch anytime you want, but on a timer every 30 minutes, the latest main will be run on CI, and if it's green, it will get promoted to production. STEPHANIE: I think there's still a sense of whose job is it to follow up if something goes wrong in that sense. JOËL: That's a good point. I think part of that is also it's a coping mechanism because of the slow test suite. We have a big process smell here, and we're trying to find some ways to get around it. And one way where I don't think any amount of process is going to help is when you have to do a hotfix. So let's say there's a really bad bug in production. We need to get that fix out now, and so I make that fix. I live ping you to review it. We get it done in like 10 minutes, and we merge. And now we've got to wait 20 minutes for CI to pass before we can make that patch go live. And we're really hoping this test suite is not flaky because if not, we might be waiting another 20 minutes. And so, in a sense, a slow test suite becomes a huge bottleneck to fixing emergency things. And now we're going to be tempted to say, "This is an emergency. We're going to bypass CI and just ship directly to production because this is on fire. We are corrupting customer data or something. This needs to be fixed now." And hopefully, we did not make the problem worse in our hotfix because we were rushing, which I have definitely done. Luckily, in these situations, it has gotten caught by CI. But there have been situations where I've tried to do a quick hotfix that I thought was going to fix things, and then CI caught it, and I was like, I'm glad I didn't just put that directly in production. STEPHANIE: Yeah, I think what I've come to realize is that the current process that I am experiencing on my client project, you know, I'm sure there's some history there about how it came to be and why they decided to do it that way. And that might be an artifact of something going wrong and them trying to put guardrails to prevent problems from showing up in production. So I do have some understanding there. So if anyone out there has a deployment process that they love, I would love to hear about it. You can tweet us @_bikeshed or send us an email to let us know if you have a deployment process that works well for your team. JOËL: Maybe we'll even feature it on a future episode. STEPHANIE: Yeah, definitely. JOËL: I'd like to get into some of the trade-offs that come with different processes, and one that jumps out at me from what you were talking about earlier is the impact of team size. With a smaller team, when you're, you know, 2,3,4,5 developers, you can have a really simple Git-based approach where merging a PR goes directly onto your main branch and maybe even have it set up to automatically deploy, and that's kind of it. If a commit is on main, it is live in production. And if you want to undo something, you just Git revert, and that goes live. And that's a really simple, effective workflow. But then, as the team starts growing, you start needing something a little bit fancier because there are a lot of commits coming out at once. They might have dependencies on each other. Reverting becomes a little bit more complicated. As the product gets more complicated, too, then you start having to want to have work that's done, but you don't want to just have a PR sitting around waiting until go-live day. So I think that's definitely an axis to think of when you're thinking of trade-offs is some workflows work very well for smaller teams and others are a better fit for larger teams. STEPHANIE: Absolutely. I think when you were talking about smaller teams, almost everyone has knowledge about what is currently being worked on. And so when problems do happen, that work of reverting or figuring out what went wrong isn't as hairy because most folks on a small team would know what changes are being merged and can pitch in to help there. But yeah, I am really interested in the transition between a small team where you feel comfortable just merging the code and having the automatic deployment and when you do need to have a heavier-handed solution, I suppose. Do you think that there's an inflection point that pushes that decision to be made? JOËL: I'm not sure exactly where that inflection point is. I might say as low as maybe 5 or 10 developers on your team, but there are probably some other variables that go along with that. Part of it might even be how good your team is at keeping commits small and focused, and independently deployable. If your team is committing really large commits that potentially break the build or that are tightly coupled to other commits, that might make it really difficult to say that your branch is always deployable. And so, you might want to bring in a heavier process earlier. Whereas if your team is doing a lot of small, atomic commits, which I think we discussed this on last week's episode, I think that could probably allow you to get a lot more mileage out of a very simple workflow where even with a slightly larger team, you're still able to just merge and deploy and also potentially revert very easily because these are atomic commits. STEPHANIE: Yeah. I like what you said about how you can get away with a lighter solution if you are really investing in things like making sure that each commit is green on CI. Because, you know, kind of what we were saying earlier, sometimes adding additional process without really figuring out what we're trying to solve here can lead to some of those trade-offs that we're talking about. JOËL: Agreed. I'm a big fan of using the simplest process that your team can get away with. Maybe we could even extend that more generally to just use the simplest thing that your team can get away with. I think that goes for code complexity, that goes for maybe code optimization. Don't make it more complex just because you're hoping to have this massive scale one day because you don't need it today. So use a process that works for your team at your current team size, and then you can iterate on that and start adding more complex elements as the team starts growing. So, Stephanie, I'm curious; we've talked about a lot of different types of deploy processes. What would be your ultimate favorite way to handle deploys if you had the choice? STEPHANIE: I think I do prefer a more automated process. When I was on a medium-sized team, that was working pretty well for us. We were having deploys be kicked off when we merged to main, but then we had a Slack integration that would tell us, "Hey, your thing is being deployed." It would tell us the results of the CI build, and it would tag us if something went wrong. And so I think that was nice in solving that issue of ownership that I had mentioned where I knew that, oh, there was an issue. I have the most context, and I can solve it the most quickly on this team. And then it was also good to just see what was going out, see what other people were working on. I liked that it made that very transparent. And that sense of feeling like you saw your feature from start to finish and seeing it live on production felt really good and gave me meaning in my development work. JOËL: Yeah, that sounds like it hits a lot of really positive values, like you said, that ownership that you have from beginning to end, even with maybe the revert if something has to happen, the transparency where you get to see if any issues came through. And then the automation and the simplicity because it's just merge your PR and the work goes out. Earlier in the episode, we were talking about trade-offs that come with a workflow. So a workflow like what you're describing, what size team do you think would be best suited for a workflow like that? STEPHANIE: Yeah, I don't know if I have an exact number. I did mention that medium-sized team seemed to feel pretty good where we did have some investment in the infrastructure in place, so, like you were saying, we had guardrails when things went wrong. But it wasn't so much for a really large team where it would have been too noisy in the Slack channel. And also, merge conflicts would come up if we were merging a lot of work during the day. And that did interrupt that queue and that flow and became something that we had to manually work through sometimes with other developers if that was their code that we had conflicts with. And so I can see it also start to not work past...I think I mentioned that the team was 20 to 30. I would be really curious to know how far that can take a team as it grows. JOËL: So, 20 to 30 people, this workflow works pretty well. What about sort of maybe experience level? Do you think this is a workflow that requires a certain level of seniority? You're talking about merge conflicts a lot, so maybe a team that is very disciplined with keeping their commits small. Do you think that's required to make this workflow work well for the team? STEPHANIE: That's a great question. When I was on this team, we did have people with all experience levels. And what I really liked was that it was okay if there were merge conflicts. It was okay if CI was red. People were super helpful in jumping on to work with you to figure it out, also, because they probably had things in the queue that they were waiting to try to go out. But it felt like a team culture where we were all committed to releasing our code smoothly. And so sometimes merge conflicts would happen, but, like I said, you usually could see it and could jump in to help out if someone was maybe stressed out about it or needed an extra hand. JOËL: I love the process you described. And the culture that your team had didn't require everyone to get it right all the time. There's room for mistakes or not even mistakes, but just less experience where you don't always know to scope everything super tightly, or your Git process isn't quite perfect every time. And that's great for a team because there's room to grow, room to bring in people of different levels of experience. STEPHANIE: Yeah, I also think it's more realistic. JOËL: Oh, 100%. I'd like to look at one more axis of trade-offs, and that is product type. What kind of product do you think that this workflow you described would fit well as opposed to maybe a different type of product that wouldn't be as good of a fit? I think what comes to mind for me immediately is maybe situations where you do a lot of work upfront, but then you only want it to go live for clients later, but you do want it merged. And so you decouple the Git history from actually releasing to customers. So that's a product lifecycle that might be a little bit different. It could be a product where you even just do big releases at set intervals. So people don't want continuous change, but you're like, once every season, we release the new version or something like that. STEPHANIE: Yeah, I was thinking that the continuous deployment process worked well for that team who was building a product that was very customer-facing in the sense that people were visiting the site every day. And they were running a lot of A/B tests on those customers as well. And so that was helpful because we could be releasing those tests iteratively and getting continuous feedback that way. JOËL: So, as we discussed in this episode, no process is perfect. There are always trade-offs. So I think it was really fun to look at a concrete example of a process that you liked, Stephanie, and then look at maybe some of the trade-offs for when does it work and when does it not work so well? And with that, shall we wrap up? STEPHANIE: Let's wrap up. Show notes for this episode can be found at bikeshed.fm. JOËL: This show has been produced and edited by Mandy Moore. STEPHANIE: If you enjoyed listening, one really easy way to support the show is to leave us a quick rating or even a review in iTunes. It really helps other folks find the show. JOËL: If you have any feedback for this or any of our other episodes, you can reach us @_bikeshed, or you can reach me @joelquen on Twitter. STEPHANIE: Or reach both of us at hosts@bikeshed.fm via email. JOËL: Thanks so much for listening to The Bike Shed, and we'll see you next week. ALL: Byeeeeeee!!!!!!! ANNOUNCER: This podcast was brought to you by thoughtbot. thoughtbot is your expert design and development partner. Let's make your product and team a success.Support The Bike Shed
undefined
Nov 15, 2022 • 30min

362: Prioritizing Learning

This week, Steph and Joël discuss investment time and keeping track of things they want to learn. How do you, dear listener, keep track of things you want to learn? When investment time rolls around, what do you reach for, or how do you prioritize that list? Are there things you actively decide not to focus on when choosing where to develop deep expertise? Are there things you wish you could spend time on if you could? This episode is brought to you by Airbrake. Visit Frictionless error monitoring and performance insight for your app stack. Bloom's Taxonomy thoughtbot's interview 3 categories of learning Four Thousand Weeks: Time Management for Mortals Transcript: STEPHANIE: Hello and welcome to another episode of The Bike Shed, a weekly podcast from your friends at thoughtbot about developing great software. I'm Stephanie Minn. JOËL: And I'm Joël Quenneville. And together, we're here to share a little bit of what we've learned along the way. STEPHANIE: So, Joël, what's new in your world? JOËL: I was recently having a conversation with another colleague at thoughtbot, and they brought up Bloom's Taxonomy, which is a taxonomy of different phases of learning. It's often visualized as a pyramid with a broad base that starts with remembering facts and then expands up to understanding and then up to applying, and then analyzing, evaluating, and then finally creating. So it's a way to kind of quantify progression of someone who is trying to master a topic. And what really struck me when I saw this diagram was I immediately thought about how the tech industry interviews and a lot of our interviews are focused on the base of that pyramid. It's all about did you memorize certain facts, or APIs, or things like that? But a lot of the value that we create as developers...but to be good at our jobs, we have to actually be active much higher up in that pyramid in the analyze, evaluate, and create layers. But unfortunately, I feel like interviews often don't go that far; they're really just focused on the base. So that was a really interesting realization. We were not talking about interviewing, but this colleague shared the diagram. I looked at it, and the first thing I thought was like, oh, this is the problem with a lot of tech interviews these days. STEPHANIE: Yeah, I think a lot about how in interviews, we want to be showing off our best selves in a sense. Like, we want our interviewers to see the version of ourselves that we bring to work, which is usually like you were saying, at that top layer and isn't recalling particular facts about how our framework works or things we might have learned in computer science class in college. And one thing I actually really like about thoughtbot's interview...even in the job application, I think it says, "We want to see your strengths and see you at your best self." And it asks what can we, as thoughtbot, interview you on in a way that gives you the opportunity to display those skills? And so I really like that. I think I remember when I submitted that application, I might have said something along the lines of debugging a problem because I think that's where I personally shine. I don't know if it ended up being a conscious thing. But I do remember when I was doing the pairing interview, there was an aspect of debugging, and I was like, yes, this is where I can show off what I would normally do in a real-life work situation. So that really resonates with me. JOËL: Debugging is such a core developer skill, and yet I feel it's not often something that we dig into in a process like an interview. Sometimes you have almost like a code review style where you've got, oh, there is one bug hidden in here, find it, and it's almost like a gotcha sort of thing. I don't like those. But a real situation where you could show off your problem-solving and debugging skills sounds like a really good way to play to your strengths. STEPHANIE: Yeah. Where else do you think that higher level of critical analysis and creative output shows up in your day-to-day work? JOËL: I think it has to pervade the day-to-day work. The majority of my job is not remembering what method from enumerable is used to sort an array; it's trying to find a way to translate a problem that the business has into code or a code solution that will satisfy quite a lot of different constraints. This might be something that is doable in one or two days because that's all we have to allocate to this problem. So a lot of that work could be scoping down a problem. There might be some performance-related constraints where it needs to be faster than X. There are certainly some correctness constraints as well that you're trying to work within. So all of that, I think, is much more at that analysis, evaluation, and creation layers of the pyramid. STEPHANIE: Yeah, that's a really good point. I think sometimes I've seen interviews try to replicate that or recreate it in an interview question, even though they may be genuinely based off of real-life experiences that companies might have had. But most often, it's really hard to be evaluated on that situation until you're really just doing that work. JOËL: It is really hard to translate that into an interview format. I think one aspect that I do appreciate, and maybe that's just the consultant in me but having a conversation about trade-offs in a situation where there isn't a single correct answer. And so, maybe the interviewer and the candidate have different conclusions. But as long as they can show their reasoning down that path of why they came to the conclusion that they did, I think that's the important part of that. The hard thing is if the interviewer has their preferred solution, and they're just like, "No, you didn't come to my conclusion," then that's not a good interview. But a situation where a candidate gets to demonstrate their critical thinking skills, their analysis skills, their ability to make difficult decisions to balance trade-offs, I think that's a great way to show off some of those high-level skills that honestly we use on a daily basis. STEPHANIE: Yeah, I agree 100%. JOËL: So that's what I've been kind of excited about recently, just seeing this diagram and having that moment of clarity about interviewing. What's something new in your world, Stephanie? STEPHANIE: That's really interesting that you brought that up because it's kind of related to what I was going to say about what I've been working on on my client project, which is the ambiguity of the rewrite. So I mentioned last week that I've been rewriting some Rails views. And we're working on a pretty old legacy application, so there are a lot of things that, as we're rewriting, we need to figure out whether or not we want to include it in the new version. So it's been a little more challenging than just copying over the functionality that you want because there are a lot of things in this legacy app that were written 10-12 years ago that we don't have any context on, especially as consultants and even the people we're working with on this team, the code might even predate them. So we do our best to ask them questions about, hey, is this still necessary? Do you think we want it in this rewrite? And they don't always know the answers. And so we have to make our best judgment and make a lot of micro-level decisions about what we think is important to bring into this rewrite without a ton of that historical context. So when you were talking about those analytical, critical thinking skills, that seemed like a very relevant experience that I would say has been utilizing those aspects of learning. JOËL: Definitely, especially for a codebase that is that old. I feel like ten years is almost like a generation in software developer terms. Ten years ago would be what? 2012. That's Rails 3 still. I forget when Rails 4 came out. But yeah, that's a long time ago when you talk about technology. And at a company, even the odds of someone sticking around for that long are very low. STEPHANIE: Absolutely. And so sometimes we just choose to leave the code as it is, and we will just copy and paste it. But other times, we might try to rewrite it in a more modern way. One thing that we did recently was migrate a hand-rolled form builder to use Simple Form. And we did our best to retain most of the original functionality. But there were aspects of it, things like browser validation and stuff like that, that had to change because we made the conscious decision to use a more modern form builder. But then there were always going to be some differences, and so we had to reconcile those with the product team, have a lot of communication around what was important to keep and what wasn't. And yeah, really, just try to get the code in a better spot if we can while also acknowledging that some things have been working for ten years, and that's okay too. JOËL: So you're talking about a lot of old code that you're working with and seeing how much things have changed over ten years. And I feel like, as software developers, we're constantly having to learn and hone our skills, but it can really be overwhelming because there's so much to learn. How do you prioritize what you want to learn next? STEPHANIE: At thoughtbot, we're lucky enough to have investment times. So typically, on Fridays, most of us will not be working on client work, but we'll be working on things to improve thoughtbot internally or improve ourselves professionally. So I'm really grateful that I have dedicated learning time, and figuring out how to spend it has been both fun and also fraught in a way because like you were saying, there are so many things I want to learn about, and we internally have so much lively discussion about really cool technical things. But I've kind of accepted that I'm not going to be able to learn it all. And so when Friday does roll around, I do have to figure out, okay, how do I want to spend my precious investment time today? For me, it honestly feels really dependent on how I'm doing that Friday. So I do have a bit of a backlog of talks and articles that I've collected along the way or bookmarked that I might come back to if that is the mode I'm in. I also have bigger themes, I think, around frameworks and technologies that I want to dig a little more deeply into. I've been trying to work through a TypeScript tutorial for a while now, especially because it's not something that I've gotten a chance to spend a ton of time on in client work. And so in some ways, it's like, well, if I want to work on a client project using TypeScript, then I feel like I should brush up on TypeScript first. So that's kind of in the back of my head is just a more nebulous goal. But I also think that it really changes depending on how I'm feeling throughout the year. It could be very well that the TypeScript thing never comes to fruition and maybe something else will grab my attention. JOËL: I'm sure there are lessons, though, that you would learn from TypeScript that you could then use to improve your day-to day-work on a Rails project, for example. STEPHANIE: Yeah, absolutely. I think that's the really cool thing is that everything I learn in some way can connect to other things that I do know, or experience, or come across during my everyday work. So none of it ever feels like a waste of time. I think the best feeling is when you can make that connection as you are experiencing something in the codebase that reminds you of something you read about in a blog post or something like that. JOËL: Connections are one of the most crucial parts of, I think, knowledge creation. And in a past episode on note-taking, we had a whole deep conversation about how sometimes making connections between some of your notes is almost more valuable than taking a note by itself. STEPHANIE: Joël, how do you prioritize your learning? JOËL: I have three broad categories of technical learning that I like to do. The first is anything related to my core language and framework, and as of right now, that is Ruby, Rails. And maybe a little bit more broadly, anything related to the paradigms related to that, so object-oriented design, patterns related to that, all things that will help me to write better Ruby and Rails code. Then there are evergreen skills that are always great to invest in, things like getting better at Git, learning a little bit of SQL, getting better at doing things on the command line. Those are all things that I look to level up every now and then. And then, finally, just whatever interests me right now. I find that the return on investment for the amount of time you put in versus the amount of knowledge you get out is much higher when I'm personally interested. So it might be something completely unrelated to maybe more strategic elements of tech that I'm trying to get, but if I'm interested, it's worth putting a little bit of time into that. And so, for me, several years ago, that was functional programming types. Elm, I went really deep into that. And I think that really unlocked a whole other way of thinking about software for me and helped me...like we were saying earlier, I was able to bring that back to the way I think about Rails applications, the way I think about test-driven development. And that really rounded out my thinking, I think. STEPHANIE: Yeah, I think focusing your energy into where you're interested in makes it easier, for sure. It makes it more fun. I think like you're saying, your learning gets accelerated. And I think it's also really cool that people have different interests that they do like to go deep on. So maybe you might be thinking that you should focus your energy on this other aspect of development that you think would be really cool or useful in your work but doesn't necessarily interest you that much. Chances are that there's someone else who loves learning and talking about it, and you can use them as a resource when you want to know more. JOËL: That is a really important aspect because learning is not necessarily a solo activity. So sometimes, maybe I'm not even just prioritizing things that I think are strategically good for me or even things I'm just interested in. It might be things that my colleagues are interested in. So we have a book club that we run at thoughtbot. We've been going through the book Ruby Science, and there have been some great discussions around that. Recently, we've also been doing watch parties for episodes of I know it is RubyTapas by Avdi Grimm, but I think it rebranded recently, and I forget the new name of it, Graceful...I think Graceful.Dev. STEPHANIE: Graceful Devs, I think, yeah. JOËL: So we've been watching some of these together as a team and then having a conversation afterwards, so that's also been great. STEPHANIE: That's really cool. Yeah, I think getting other people involved makes it a lot more fun. And you have an accountability buddy. And you can have those deep, thoughtful conversations about the things you've learned. MID-ROLL AD: Debugging errors can be a developer’s worst nightmare...but it doesn’t have to be. Airbrake is an award-winning error monitoring, performance, and deployment tracking tool created by developers for developers that can actually help cut your debugging time in half. So why do developers love Airbrake? It has all of the information that web developers need to monitor their application - including error management, performance insights, and deploy tracking! Airbrake’s debugging tool catches all of your project errors, intelligently groups them, and points you to the issue in the code so you can quickly fix the bug before customers are impacted. In addition to stellar error monitoring, Airbrake’s lightweight APM helps developers to track the performance and availability of their application through metrics like HTTP requests, response times, error occurrences, and user satisfaction. Finally, Airbrake Deploy Tracking helps developers track trends, fix bad deploys, and improve code quality. Since 2008, Airbrake has been a staple in the Ruby community and has grown to cover all major programming languages. Airbrake seamlessly integrates with your favorite apps to include modern features like single sign-on and SDK-based installation. From testing to production, Airbrake notifiers have your back. Your time is valuable, so why waste it combing through logs, waiting for user reports, or retrofitting other tools to monitor your application? You literally have nothing to lose. Head on over to airbrake.io/try/bikeshed to create your FREE developer account today! STEPHANIE: I'm curious, have you ever made a conscious effort to not focus on something super deeply? JOËL: I don't know that I've made a decision to be like, I will not spend time here. But I've definitely made a decision to I will invest here and maybe not care quite as much there. So I've done quite a bit of different front-end technologies, starting with jQuery and Backbone.js and moving through a lot of the frameworks. Somehow I have not yet done much React. It's sort of a big hole in that list of frameworks that I have worked with. It's just not something that I've prioritized. I've done other things. I've learned concepts that I think mirror a lot of what React does, but that's not been something that I've dug into. STEPHANIE: That's really interesting because I think a lot of people think that they need to learn React because it's the popular front-end framework of the time. And so they think that it's something that they should know, or if they do ever have to work on a project with React, that kind of contributes to that feeling. But I like what you were saying earlier about how you have experience with other front-end frameworks. And that can help inform you if you ever do have to work in it. And also, there are so many great expert React devs out there. Like, we don't have to all be that dev. JOËL: Yeah. I think there can definitely be a pressure to feel like you have to know it all. And a lot of these tech stacks are changing so quickly that it becomes overwhelming to try to just keep up with everything. STEPHANIE: For sure. I remember having to write some tests for a React app, and the things that I had learned several years ago using Enzyme or something were no longer as relevant today, and having to pick up on the new best practices for writing Jest tests and React Testing Library. It was a lot, even though I was able to identify aspects of it that lined up with what I knew. It can be overwhelming, for sure. And people spend a lot of time digging deep into this framework and like I said, becoming those experts and accepting that I probably won't be that person [laughs] was also a little bit liberating, I think. JOËL: It's also important, I think, to accept that these sorts of labels of I'm that person, or I'm not that person are not permanent. It's I'm not that person now because that's not where I want to prioritize my time. Maybe in two or three years, it will make sense for me to become that person. And I can become that person if I put in the time, but today is not the day for me to be that person. STEPHANIE: That's a really good way of putting that. I like that a lot. JOËL: One struggle that I have, and I've seen a lot of people too is that it's easy to get very scattered in your learning that you'll have a lot of different things you're trying to learn at the same time or you feel like you want to do a little bit of this and a little bit of that. And then maybe you don't go very deep in any of them and feel like you're not being very effective with your time. Do you ever feel that, and do you have any strategies you like to use to make the most out of your learning time? STEPHANIE: I really relate to that. And I think one resource that helped me reframe that conundrum if you will, was this book called Four Thousand Weeks: Time Management for Mortals by Oliver Burkeman. It was really interesting because it kind of turned productivity culture around a bit on its head because his whole thesis is that you won't achieve at all and that by trying to hack your own productivity, what you're really preventing yourself from doing is accepting the fact that time is finite. And that you have to make hard decisions about where to focus your time in a way that will enrich your life the most. And sacrifice the idea that you will get to do everything on your to-do list, that you will learn every framework that you want to learn. And it's still hard for me to totally accept that. But I think I'm inching towards the idea that if I do drop a ball on something that I have had bookmarked for at this point, you know, a year, I'm probably never going to get around to reading that. And that's okay because I'm still getting by with the things that I am learning and applying them in the aspects of my work that are relevant to me today. JOËL: That sounds like a really refreshing take on productivity culture, maybe with some hard truths in there as well. Is 4,000 weeks the human lifespan? STEPHANIE: [laughs] Yeah, it is. It's really funny because I think he even starts off in the book quizzing one of his friends, like, how many weeks do you think we have to live? And his friend very naively answered, "Oh, must be, you know, 500,000 or so," or something like that. But he used that as an illustration of how we inflate how much time we think that we might have in a day, a week, our lifespan. [laughs] JOËL: I'm a big history nerd in my personal time. You see this theme that comes up a lot in medieval European art and the 1400s after a lot of these big plagues have happened where they feature a lot of death or skeletons or those sorts of motifs that are much more prevalent than maybe an earlier art, and this idea that comes with a Latin phrase Memento Mori (remember death). And I think there's maybe an element of that that comes back into this book at least the way you were describing it, the idea that you only have 4,000 weeks, roughly, in your life, so make the best use of it. STEPHANIE: Yeah, absolutely. It's nothing new, for sure. I think it's just one of those things that we've been grappling with as a species for as long as we've existed. [laughs] So I don't know if anyone out there feels slightly relieved that it's okay for them not to get through their list of bookmarked articles about technical things. I hope that feels slightly better for you. JOËL: We give you permission for you, the audience, to go to your bookmarks and those articles that you've been meaning to read for two years and you haven't got to; it's okay to remove them. You will be okay. STEPHANIE: Agreed. So we've talked about how we spend our investment time. But I'm curious, do you have any strategies for people who do most of their learning in their everyday work? JOËL: You know, I think that applies to me as well. We've been heavily emphasizing investment time, but that's only one day a week. And four days a week, I am doing regular application development for clients. And so the majority of my hours in a week are going to be dedicated to that. I find that being very self-aware for the things that you do and trying to notice when I learn something new or when I interact with something new has really helped me get more out of my day-to-day work. And a way to level that, I think, is to be on the lookout for opportunities to share with others. And that can be as small as just put a today I learned message in a group chat, maybe in thoughtbot's Slack developer channel, and just say, "Hey, today I learned this interesting thing about a particular method." Or "Today I learned this weird thing about time zones." Or "Today I learned this interesting fact about testing." And then that might start a discussion, or it might not. But the fact that I took the time to take it out of my head and write it out, I think, makes that more concrete, and it helps me hold on to it. STEPHANIE: I've noticed you are really good about doing that, about sharing things that you encounter in your everyday work in a very low-stakes kind of way. I am not so good at doing that. I tend to be so steeped in client work, and I have to really intentionally, after a project is over, think about what I learned along the way. And oftentimes, they're not as small, incremental atomic bits of information but bigger picture things about, oh, I learned how to navigate this aspect of ambiguity. And maybe the next time, I can point to a past experience or lean on a little bit more on my gut instinct to guide me towards making the right decision. And I think that's an important aspect of learning too, even if it wasn't necessarily a technical tidbit. It is part of becoming a better developer, just as equally as gaining that more concrete technical knowledge. JOËL: Intuition, I think, is really important as developers, and honing that intuition is something that is really valuable. One way that I found helpful is dialogue, just a conversation with one other person, maybe it's asynchronous over Slack, maybe it's a call in person, and just talking through an idea that I have. A recent one and I think I mentioned this on the previous episode of The Bike Shed, was talking about RSpec matchers. And does your choice of matcher impact the sorts of design that will come out of the code that you write? Does EQ tend to push you in a direction maybe where you're less strongly encapsulating data? And so that's just a thought, and then you have a conversation about it. And then that can help sharpen your intuition so that the next time you're writing a test you're not just thoughtlessly bringing in a matcher because whatever; it's the thing to do. And initially, maybe it's not intuition; it's much more explicit. You're thinking, ooh, do I want EQ, or do I want not? But I imagine that after six months of me being hyperaware of that, I will have built up some intuitions to be like, oh, this is the place where we want a custom matcher, or here's the place where I want EQ. And my hope is that that will eventually come to the point where it's so natural. Someone would almost have to stop me and say, hey, wait, why are you choosing that? And then I have to think a little bit and be like, oh, it's because of these things. But I'll have started with a conversation, which then turned into just hyperawareness thinking about it every time I do that action which then turns into intuition. STEPHANIE: Yeah. I think you can also call that experience. I remember having a conversation with someone, and I told them that I could inject their brain with all of the knowledge and information that I had. But that isn't quite the same as having really experienced the process of gaining that knowledge through more conventional learning methods but also that day-to-day client work that you're doing. So I totally agree with you there. JOËL: You took this whole long thing I had to say and were able to condense it down to one word: experience. STEPHANIE: [laughs] JOËL: Which I think, yeah, exactly describes what I'm trying to say. And with that, shall we wrap up? STEPHANIE: Let's wrap up. JOËL: The show notes for this episode can be found at bikeshed.fm. This show is produced and edited by Mandy Moore. If you enjoyed listening, one really easy way to support the show is to leave us a quick rating or even a review in iTunes. It really helps other folks find the show. If you have any feedback, you can reach us at @_bikeshed, or reach me at @joelquen on Twitter, or at hosts@bikeshed.fm via email. Thank you so much for listening to The Bike Shed, and we'll see you next week. Byeeeeeeeee!!!!!! ANNOUNCER: This podcast was brought to you by thoughtbot. thoughtbot is your expert design and development partner. Let's make your product and team a success.Sponsored By:Airbrake: Deploy fearlessly and fix bugs faster with Airbrake Error & Performance Monitoring. Airbrake notifiers are available for all major programming languages and frameworks, and install in minutes, with an open-source SDK-based install and near-zero technical debt. Spend less time tracking down bugs and more time developing. Visit Frictionless error monitoring and performance insight for your app stack.Support The Bike Shed
undefined
Nov 8, 2022 • 30min

361: Working Incrementally

thoughtbotter Stephanie Minn joins The Bike Shed as co-host! 🎉 Joël and Stephanie talk about continuing on a rewrite and redesign of a legacy Rails app and working incrementally. This episode is brought to you by Airbrake. Visit Frictionless error monitoring and performance insight for your app stack. Stephanie's listener question Stephanie's older episodes Case Expressions Specialized Vocabulary Mike Burns' smelly list Previous episode about note taking systems Transcript: JOËL: Hello and welcome to another episode of The Bike Shed, a weekly podcast from your friends at thoughtbot about developing great software. I'm Joël Quenneville. And today, I'm joined by someone very special, Stephanie Minn, who will be joining the podcast as a co-host. STEPHANIE: Hi, Joël. It's me, Stephanie Minn. [laughs] JOËL: Welcome to The Bike Shed. STEPHANIE: Thanks. I am really excited to be here for the third time, I guess, but now in a more official capacity. JOËL: And together, I think both of us are now excited to share a little bit of what we've learned along the way. So for the first time as a co-host, I'm happy to ask you the question, what's new in your world? STEPHANIE: Well, I think I would have to say co-hosting this podcast. It's pretty big news for me, personally. I'm really pumped and also really nervous. I never thought I would co-host a podcast. But I have been a long-time listener of the show, and it feels very surreal to be here. I have big shoes to fill, even if I do have the same name as former co-host Steph Viccari. It's funny; I listened to a previous episode of The Bike Shed this morning, where I had submitted a listener question about project estimation. It came out earlier this year. It was really funny because Steph and Chris had a whole bit about speaking to this other Stephanie out in the world. That was me. And now it's kind of come full circle that I am in this position now. So I thought that was kind of fun. Now I could just say hello to all the Stephs out there. JOËL: Regular listeners will have recognized you because you've been a recent guest on a couple of different episodes, one where you talked about case expressions and how they fit into our style of programming. And then another one where we talked about the use of domain-specific or industry-specific vocabulary and jargon as a form of communication and how that plays out in the workplace. And for those who have not listened to those, we'll link them in the show notes if you want to catch up on the backlog. STEPHANIE: I really enjoyed being a guest on the last few episodes. It was cool to see things that we talk about internally come up in a way that we want to share with a broader audience because I think some of the things that we get into in book club or in the dev channel on Slack end up being really interesting and sometimes not things that we always see out in the content world, and we can dig really deep into that with this format. So that is really exciting to me. I think, in general, being on this podcast, I'm hoping in some ways will also be a growth opportunity for me because I am wanting to get more comfortable sharing my thoughts and ideas in a public space more frequently and informally. I'm very comfortable hoarding my thoughts until they're perfectly refined. And I'm like, okay, now I finally feel ready to share them in the form of a blog post or a talk. But I kind of want to open myself up to hearing from others, different perspectives, being more comfortable being wrong in public, and changing my mind, and evolving how I work and what I think in this way because I think it's important to normalize that. Yeah, I don't know how I can be any more vulnerable than being on a podcast on the internet. JOËL: Podcasting is really interesting because it's much more raw and unfiltered as opposed to something like a blog post or a conference talk where you've gone through a whole editing phase; you've rehearsed it. And you've spoken at conferences before, and you're speaking again. You'll be at RubyConf Mini in a few weeks. STEPHANIE: Yeah, I was thinking about my background because I actually come from a journalism background; that's what my degree is. And so I'm very comfortable in the role of editor, and I am a very obsessive and tedious editor when it comes to my own personal work in words and in code, I would say. The idea of putting stuff out there in a more unfiltered and less polished way is uncomfortable for me, but I want to get better at it, which is why I'm here because, like you were saying, in some ways, it's more realistic in just how we talk about some of these things at work. I kind of want to remove the Instagram filter of talking about software and technical topics. And sometimes, we are just learning about things along the way. And I think that's one of the things that I've really appreciated about The Bike Shed in the past. JOËL: Have you ever tried improv? STEPHANIE: Oh my God. Once I took an improv class as a team bonding activity at an old job, and that was really something. I think we all collectively were not into the idea, but that's what we were doing. So we went to I a think comedy club or something in New York with my team at my first job. And it was actually a lot more fun than I thought it was going to be. I think the instructors knew that most people would not be super comfortable with improv, and so they did provide a lot of structure in terms of the types of exercises and games we played. And we weren't doing improv comedy; we were just doing the exercises that would make us feel a little more comfortable and just having fun with it. Given some prompts, we would maybe walk across the room in a silly walk. And then the other person that we would walk to would imitate it, but it would be slightly different, and it would kind of evolve that way. So that's funny that you mentioned that. I had to really dig that memory from the archives [laughs] because I probably repressed it at some point. JOËL: I've also done improv a couple of times in a similar setting to you, like a team-building activity. I really enjoyed it. And in many ways, I feel like podcasting can feel like the improv version of the content world. STEPHANIE: Yes, and I agree. [laughter] JOËL: Yes, and very true. STEPHANIE: So, Joël, what's new in your world? JOËL: So you'd mentioned earlier how our developer channel on Slack is just a fantastic resource. There are some great conversations that happen there, and a lot of them eventually I like them so much I want to pull them into The Bike Shed and make an episode inspired by them. And I had a conversation today about the impact of what matchers you choose when you write tests and how that might impact the code that you write. So, for example, an equality matcher is almost like the primitive obsession version of testing matchers in RSpec and how which one you pick might impact the implementation of your code. An example of that might be if you have... let's say you're testing behavior on an order, and you might say you do some things, and you expect the order status to be the string or to equal the string pending. You've now exposed this internal of this order status string where instead you might using some of RSpec's automatic matchers for predicate methods, expect the order to be _pending because you now have a predicate method pending defined on your order. So by choosing to use a more rich matcher, you may have actually improved the encapsulation of your object. I had never thought about matters in that way before. It kind of blew my mind, and so I'm still kind of chewing on that, and maybe some of the implications of it. STEPHANIE: That's really interesting because, in my experience, I think I would reach for the more general matchers, the ones that seem to be more top of mind for me. And it takes an extra intentional thought to be like, oh, actually, I want this particular specific matcher to better reflect the behavior that I'm desiring. JOËL: This just came out of a conversation because fellow thoughtboter, Mike Burns, has a blog post that just lists a bunch of almost code smells or things that make him raise an eyebrow during code review that might lead to a follow-up comment and asking for clarification about why that choice was used. And on that list is the RSpec eq matcher that checks for just regular equality. And so we went a little bit deep into why that might be the case, and that brought up a lot of really interesting ideas that I had not thought about before. STEPHANIE: I'm curious if you are doing test-driven development if using a more specific selector. This is kind of the opposite of what I said earlier, but I guess I'm wondering if there's a possibility that it pigeonholes you into a particular implementation. JOËL: It might pigeonhole you into a particular interface, and I'm still exploring the idea. I think that the richer matchers move you away from implementation. So, in this case, or in the case of the example I talked about earlier, all you know is that there's a predicate method pending on an order. You don't know whether it's implemented as a Boolean internally or if it's a string that is being checked or some other thing. Maybe it's a status code. STEPHANIE: That's cool. Do you think you might explore this in your own work moving forward? JOËL: In a recent episode with Amanda Beiner, we had a whole conversation about note-taking systems, and I talked about having a sort of personal knowledge base where I keep deeper thoughts about code. And I definitely added a couple of entries to that today based off of that conversation. STEPHANIE: That's awesome. I totally know what you're talking about when you learn things or pick up little bits and pieces of information that you want to hold on to. But you might not have a particularly applicable project or codebase you're working in at the moment to apply that, but you want to hold on to it when you encounter that situation in the future. So I really like what you're saying about just adding it to your knowledge base and coming back to it. JOËL: I think the next time I'm writing a test and I feel the need to reach for eq I will immediately think of this and ask myself, is there something else that might be better? And if other matches feel awkward, why? So it's going to definitely cause me to be a lot more thoughtful about the way I write assertions. I'm curious to see if that will have an impact on the types of designs that my tests drive. STEPHANIE: Yeah, so maybe not a code smell but a code whiff. JOËL: Definitely. MID-ROLL AD: Debugging errors can be a developer's worst nightmare...but it doesn't have to be. Airbrake is an award-winning error monitoring, performance, and deployment tracking tool created by developers for developers that can actually help cut your debugging time in half. So why do developers love Airbrake? It has all of the information that web developers need to monitor their application - including error management, performance insights, and deploy tracking! Airbrake's debugging tool catches all of your project errors, intelligently groups them, and points you to the issue in the code so you can quickly fix the bug before customers are impacted. In addition to stellar error monitoring, Airbrake's lightweight APM helps developers to track the performance and availability of their application through metrics like HTTP requests, response times, error occurrences, and user satisfaction. Finally, Airbrake Deploy Tracking helps developers track trends, fix bad deploys, and improve code quality. Since 2008, Airbrake has been a staple in the Ruby community and has grown to cover all major programming languages. Airbrake seamlessly integrates with your favorite apps to include modern features like single sign-on and SDK-based installation. From testing to production, Airbrake notifiers have your back. Your time is valuable, so why waste it combing through logs, waiting for user reports, or retrofitting other tools to monitor your application? You literally have nothing to lose. Head on over to airbrake.io/try/bikeshed to create your FREE developer account today! STEPHANIE: So one thing I've been thinking a lot about in my client project currently is working on features more incrementally. Right now, we are working on a rewrite of the front end of a legacy Rails app. So they did a big, modern refresh on the look of the app. And we are rewriting a bunch of the views to those specs. And all of this is happening behind a feature flag. So we are able to ship work incrementally and not have as much of an issue where all of this work is happening on one big branch. But because a lot of this work builds on top of each other, I have been experiencing folks cutting branches off of feature branches, off of feature branches. And I've been thinking a lot about the intentional steps that we made to be able to deploy this in pieces and more safely but the friction that people still might have to work incrementally. One thing that I've noticed is that there are different levels to how this shows up in our work. You can work incrementally on an individual level where you are in your own work writing small commits that capture individual pieces, and you can feel good about that. But then you also have it at a team level where we have to collectively decide how we want to ship features. And I am trying to figure out how to encourage other people to agree on an approach and encourage the benefits of shipping features in small chunks. Have you ever noticed a discrepancy between individual work and how a team works in that way? JOËL: It sounds like what you're describing is that by encouraging the team to work in smaller chunks, it has made the Git and branching and merging process more complicated for the team as a whole. Maybe there's a bit of that tension whereby increasing the individual granularity; you're making the merging for the team more complex. STEPHANIE: Yeah, we've had a lot of rebasing issues where we do spend a lot of time in that mode because one branch went through code review and had some changes and then went through UAT and had some changes. And then we had to reconcile those changes with another feature that had been started and cut off of that feature branch. JOËL: That's really interesting because I feel like I've almost had the opposite experience. When I was a new developer, I would constantly get Git conflicts, and you have to figure out the merge, and it's a mess. And then eventually, I got decently good at resolving conflicts. Nowadays, I very rarely encounter conflicts, and the ones I get tend to be really minimal. And I think that's because I've started to really work incrementally and to keep my change sets small. And so Git conflicts are not really something I run into very much anymore. And I tend to think of them now as more of a symptom of large patches and code changes that might be bigger than they need to be. Does that line up maybe with your experience as well? STEPHANIE: Yeah, I think it does. But one area of friction that we are experiencing right now is that we may be working incrementally, but those changes don't make it all the way to our integration or production branches, and so they are still just hanging around. And we have had to fix merge conflicts, not necessarily with other people's work but with our own work where changes happened upstream, and then they kind of cascade down. So we are working in pieces. But because there is a little bit of process challenges that we're facing, we haven't quite closed the loop on that feedback cycle in a way that allows us to move as quickly as I think what you're describing. JOËL: It sounds like maybe your team is bundling multiple incremental changes and then trying to merge a bundle of them together. So while it's composed of small, incremental changes, the effect is similar to merging a branch that's got a large change set together. STEPHANIE: Yeah, that's exactly it. JOËL: In my own work, I tend to really view commits as the atomic chunks of the work that I'm doing, so each of them is going to be very tightly scoped and do a single thing. Ideally, also pass all the tests and be independently mergeable. I tend to view PRs not so much as like a unit of work but just a unit of review, a way to get feedback on one or more commits. I don't want to put too many commits in a PR because then it's painful for the reviewer. But also, another reason that you got me thinking about is that trying to shove fewer commits in a PR will also make it easier to do that final merge. STEPHANIE: Yeah, I know that I really appreciate when PRs are just one, maybe a few commits, but the overall diff is small. And in my opinion, I think we are able to move faster that way because reviews are quicker, conflicts are fewer. And it better captures the idea of working incrementally, even if it does involve more than a single, small atomic commit. And I would love to figure out how to move in that direction. Right now, in my client project, one of the barriers is the processes we've built into the agile methodology we're following, where our PRs have to get a couple of approvals and then be tested by folks from product. And so it's sometimes easier to just add a little bit of things to an open PR already. But what we were talking about with incorporating more of that intentionality really pays the cost up front rather than just pushing it until later when we do run into problems with conflicts or having to go back to debug something that went in into a big bundled PR and then having to spend time and energy at that point in the lifecycle of our work. JOËL: It's really interesting that you highlight the organizational impact and the process impacts. Definitely, when you increase the cost of merging when you make it like to merge a single PR, you're going to need to wait at least 24 hours because of all the other checks that need to go through, then people will tend to make larger PRs. And so sometimes it's not about programmer discipline or good habits even; it's about the process pressures that just really incentivize making larger PRs because of how expensive it is to open a new one. I'm curious, though, in a perfect world, when you are reviewing a PR for some code written in Ruby on Rails, what is the max amount of lines that you'd want to see in a diff before you start thinking this is too big; I wish we could split it up? STEPHANIE: I don't know if I have a number in my head. I want to say somewhere in the couple hundreds, maybe. I will love a PR where the diff is less than 100. That feels great to review and just feels right. I don't know; it's I don't open it and be like, ah, I have to now read through tens of files, some of which I have no context about. I like a tidy PR where everything that's changed is related to what the PR title is. What about you? Do you have a heuristic? JOËL: I think I'm probably similar to you, 100 lines is probably about the cut-off for where it starts to become more of a chore to review a PR. I've definitely had moments where somebody sends me a link and says, "Hey, can you review?" And I click the link and I open it up, and I'm like, oh okay, well, I should set aside a half hour here to really get into this because this is not going to be quick. STEPHANIE: For sure. And it also makes addressing that review feedback more difficult because you have to likely make more changes because there's just more to review and more to improve if it wasn't quite right the first time. The only time I do a big diff is if it's all in red. [laughter] JOËL: Yes. STEPHANIE: We have a Slack channel at thoughtbot called Dead Code Society where people post screenshots of their negative diffs, and it's so fun. JOËL: I'm all for that. When I look at code that has been broken down into nice, small commits, it just looks so clean. It looks so natural. But when I try to write code like that, it's anything but; it doesn't feel natural. Have you had a similar experience? STEPHANIE: I completely agree. I think it's really hard, and it's something that I am still practicing. Because when you are first learning to code, no one teaches you to write it incrementally; at least, that was my experience. It requires a lot of discipline to think about code in little, tiny chunks when you are just so excited to get your feature working and seeing it in a browser and playing around with it. When I first started doing it, I thought it was impossible. I thought it was wild to have a single commit be passing CI all the time because when I was writing code, there were so many work in progresses. And then I would run the test suite and be like, ah, 20 test failures. Now I have to go through and fix all of them. I guess what I learned from that was the pain of not working incrementally, and that is what motivates me to be disciplined. And it doesn't always happen. Sometimes I'm lazy and just decide that it's fine for now, and then will usually have to come back through when I am in that kind of headspace where I'm like, okay, let me really get down to business. And I'm able to see the seams of the code that I wrote to be able to extract them out into encapsulated pieces because that doesn't always come supernaturally. So yeah, I would say that it's an upward journey for sure. Do you find that to be true for your work? JOËL: It has definitely been a journey. I think as I have gained experience, as I have discovered new techniques, even picked up different perspectives and mindsets, all of these have helped hone that skill. And I feel like it's one of those skills that feels very mundane, but it's actually one of the more valuable skills I have as a developer is able to take something complex and decompose it into atomic pieces. STEPHANIE: Do you ever find yourselves in a position where there are obstacles that keep you from doing that, or would you say it's just an internal state of mind? JOËL: Maybe a little bit of both. You'd mentioned earlier that there can be process or organizational obstacles that make it much harder to try to scope down the work that you have. It can be internal in that you don't know the tools that you need for this particular scenario. But it's something where you're constantly on the lookout for ways to learn to be better. Over the course of your career, it is a skill that I think you're going to keep improving probably forever. I don't think you'll ever get to the point which is like, yep, I've mastered this. I'm as good as I will ever be, and that's the end. I'm curious, are there some tools or techniques that you like to use when trying to keep your work focused? STEPHANIE: From a commit level, I really love the git add --patch command. I think that it is really helpful because I like to litter my code with random debuggers and little changes that I don't end up wanting to bring into my commit later on. And so that is a great place for me to discard things that I know are distractions or were part of little rabbit holes I went down along the way. And so I highly recommend folks to check that out if that's not something that's already familiar to them. At a higher level, TDD has helped with staying focused because you have that built-in feedback loop. And once you are green, you can commit essentially and know that you did the least amount of work possible to get the behavior that you wanted without starting to sprawl into other territory. Joël, do you have any other tips or tools you want to share with our listeners? JOËL: A sort of mental tool that I've gotten into recently is drawing things out as a directed graph to understand what are the changes that I need to accomplish my goal and then what changes rely on other changes being there first? And a directed graph, you just draw a bunch of circles with arrows pointing towards things that they depend on. Anytime there is a cycle, that chunk of changes all has to go out together. They're all sort of interdependent on each other. And if you can find a way to restructure the graph or introduce a new step in there that will break the cycle, now individual pieces of that can be shipped independently. I've started using that visual approach sometimes to look at changes that feel like they're one sort of big blob of changes that can't be broken down and to say, oh, well, in order to ship this change, I need to introduce this new gem. I'm going to need to change behavior in this part of the system, but that will also need a change in another part of the system. And now that I can see how things are connected to each other, it gives me a clue to where I can rearrange the changes. Maybe there's a refactor that I can ship out first completely separate that makes the follow-up work much easier. Or maybe there's a way that I can introduce some of the changes without needing to do all of them at once. STEPHANIE: That's a really cool way to identify those pieces because it can be nearly impossible to figure it out from files in a repo or to keep in your head. And I think what's even cooler is that you can share those graphs too so someone else can come along and pick up that work, as well as just have the same level of understanding of what things depend on each other just through our clients. JOËL: I've really fallen in love with directed graphs and dependency graphs, in particular over the past year, and I feel like now I see them everywhere. And regular listeners of the show will have noticed that I have mentioned them multiple times. And I almost feel now like I'm that Parks and Recs meme where he's got [laughter] all his conspiracy board with all the threads connecting to each other and just like, let me tell you about graphs and how everything is all connected. They're behind everything. STEPHANIE: I'm with you. I think everything is connected, even just in our informal conversations on the pod and off the pod. We're constantly being like, oh, that's a great idea. Like, we can do a completely different episode if we go down this rabbit hole, but it also still maps back to things we talked about in another episode. And yeah, that feels very true to me in terms of software and also in terms of life, not to make that sound too deep. [laughs] But it's cool that you have found a way to manage that complexity for yourself at work and a way to share it with others. JOËL: I think one of these days, we're going to have to do a dedicated episode all about dependency graphs. STEPHANIE: Yeah, I would love to hear more. Too bad the podcast is just an auditory platform and not a visual one. [laughs] JOËL: That's the challenge, right? Because it's such a visual topic. STEPHANIE: That's cool. I'm really looking forward to hearing more about them. On that note, shall we wrap up? JOËL: Let's wrap up. The show notes for this episode can be found at bikeshed.fm. This show is produced and edited by Mandy Moore. If you enjoyed listening, one really easy way to support the show is to leave us a quick rating or even a review in iTunes. It really helps other folks find the show. If you have any feedback, you can reach us at @_bikeshed, or reach me at @joelquen on Twitter, or at hosts@bikeshed.fm via email. Thank you so much for listening to The Bike Shed, and we'll see you next week. Byeeeeeeeee!!!!!!!!! ANNOUNCER: This podcast was brought to you by thoughtbot. thoughtbot is your expert design and development partner. Let's make your product and team a success.Sponsored By:Airbrake: Deploy fearlessly and fix bugs faster with Airbrake Error & Performance Monitoring. Airbrake notifiers are available for all major programming languages and frameworks, and install in minutes, with an open-source SDK-based install and near-zero technical debt. Spend less time tracking down bugs and more time developing. Visit Frictionless error monitoring and performance insight for your app stack.Support The Bike Shed
undefined
Nov 1, 2022 • 29min

360: ActiveRecord Models

Fellow thoughtboter Sarah Lima joins Joël to discuss an issue Sarah had when she was doing a code review recently: making HTTP requests in an ActiveRecord model. Her concern with that approach was that a class was having too many responsibilities that would break the single-responsibility principle, and that it would make the class hard to maintain. Because the ActiveRecord layer is a layer that's meant to encapsulate business roles and data, her issue was that adding another responsibility on top of it would be too much. Her solution was to extract a class that would handle the whole HTTP request process. This episode is brought to you by Airbrake. Visit Frictionless error monitoring and performance insight for your app stack. SQL TRIM() Iteration as an anti-pattern WET tests thoughtbot code review guidelines Side effects in tests Active Resource Different strategies for 3rd party requests Transcript: JOËL: Hello and welcome to another episode of The Bike Shed, a weekly podcast from your friends at thoughtbot about developing great software. I'm Joël Quenneville. And today, I'm joined by fellow thoughtboter Sarah Lima. SARAH: Happy to be here. JOËL: And together, we're here to share a little bit of what we've learned along the way. So, Sarah, what's new in your world? SARAH: Well, after a year and a half working on the same thoughtbot client, I have rolled off, and I have joined a new team. And I am learning a lot about not only a new codebase but learning to work with a new team. So that's always challenging, and this time it's not different. JOËL: What is something that you like to do when joining a new team to help smooth the onboarding process? SARAH: Well, I think especially getting to know people with one on ones. This time, I didn't do that right away because I had a bunch of time off scheduled right at the beginning of the project. But I did it right after I came back. And I'm learning a lot about my new colleagues, how they like to work, how they learn best. So, for instance, there are some people that like to learn and grow by reading blog posts, reading books, and there are other people that don't like that as much. JOËL: So when you joined the new project, you just reached out to all of these people and set up a few meetings just to get to know them. SARAH: Yeah, exactly. JOËL: That's really good. I've never done that on a project. And now that you've said it, it kind of seems obvious. Maybe I should do that moving forward to get to know new teammates. SARAH: Yeah. And I think it's easier on my project because it's a very small team. There are four of us thoughtboters, and there are just two client developers. So it was easier. JOËL: What about on the code side of things? Are there any tricks you like to do when you're first getting started in a new codebase? SARAH: Well, I think I really enjoy diving in right away, working on something small, and asking questions. I have also found it helpful in the past, especially on larger codebases, that someone that's experienced on a project gives me an overview showing me the quirks. And, of course, a good README is always a good thing to have, and during the process, always be updating the README. In this recent project, it was not different. I opened a lot of PRs to update the README. So that was good to have a PR right on your first day. JOËL: I love that. I think that's usually my goal when I start on a new project is to have a PR the first day that fixes anything in the setup script that has been broken since the last person onboarded or documentation that was wrong. SARAH: Yeah, absolutely. JOËL: It's always a strong first contribution. SARAH: Yeah. What about you, Joël? What's going on? What's new in your world? JOËL: I've been investigating flaky tests, and I ran across a wild bug this week. I had a test that would fail every now and then. And it was pulling some data from Postgres and then doing some transformations on it. And I couldn't figure out why it was failing. It was a complex query. So it was just pulling out not ActiveRecord objects but a raw array of values. At some point, I was putting a PUT statement in the code with the array of values I expected to get and the array I would actually get. And I was surprised to see that there is a field in there that is a float that was rounded to a different number of decimal places. I was like, that doesn't seem right. And so I was digging into it more, and I found out that this decimal value is from a timestamp that is in a file name for an mp4 video file name. And what is happening is that when we're querying the database, we're trying to extract the timestamp out of the file name by dropping the .mp4 file extension. And we're using the SQL TRIM function. Unfortunately, TRIM does not do whatever the original authors thought it does. It doesn't just remove that substring from the end, but instead, it will remove any of those characters, so in my case, any of dot, M, P, or 4 in any combination from the end of the string. So anytime that my timestamp ended in a four, any fours were just getting chopped off. So if it ended in 44.mp4, the 44 would also get removed, not just the .mp4, which meant that randomly whenever a timestamp happened to end in 4, my test would flake. SARAH: Wow. Do you have any idea how much time you spent debugging that? JOËL: Oh, probably took, I'd say, a day, two days. This is spread over a couple of debugging sessions. But eventually, finding that particular location for the bug probably took us a couple of days. In the end, the bug fix for this is just a couple of lines, a couple of days work, and the diff is only a few lines. But I'm sure that the discussion on the PR is going to be really interesting. There's probably going to be a description that is a lot longer than the actual diff. SARAH: Yeah, 100%. [laughs] JOËL: Have you run across any interesting PRs on your new project? SARAH: Yeah, I did. In fact, I recently reviewed a PR that had three interesting main issues that I wanted to address. And I wanted to lead the person that was working on it to a slightly better solution. So the three issues I saw were that the tests that were added were very DRY, so that was making everything a bit difficult to understand. The second one was that I saw one of the ActiveRecord classes was making HTTP requests, and that didn't sound like a good idea to me. JOËL: That is unusual. SARAH: Yes. The third one was that there were a lot of collections being built iteratively where another innumerable method would be a better fit, such as map instead of an each call. JOËL: Oh, this is a classic situation where you're just using each to go through and transform something, and you've got some sort of external array that you're mutating as part of the each. SARAH: Yes. JOËL: There's a great thought article, I believe, by Joe Ferris on Iteration as an Anti-pattern. SARAH: I think it's by Mike Burns. And I have referred to that article. In fact, I had very good articles for two of these three problems. I referred to a bunch of articles about WET tests as opposed to DRY tests, like how striving for tests that are DRY is not a good idea as opposed to telling a whole story in your tests. And I referred to that other article how iteratively building a collection can be an anti-pattern by Mike Burns. But the second issue about HTTP requests I didn't have anything to refer to. Maybe we should write one. JOËL: This reminds me that in the thoughtbot Slack, we have a custom emoji for you should write a blog post about that. And this would probably be a good time to use it. SARAH: Yes. So, Joël, how do you typically handle a PR that is maybe too long, and you have a lot of concerns about it? And how do you handle delivering that feedback? JOËL: Oh, that is a challenge. I've definitely done it poorly in the past. And I think the wrong way to go about that situation is to go thoroughly through the PR and leave 50, 60 comments. That is overwhelming for the other person. And they're going to have a really bad day when they see 50 comments come through. And there's so much that they can't really address the main things you were talking about anyway. So what I generally try to do, and it's kind of nice now that GitHub doesn't immediately publish your comments, is if I realize...like I start putting some more detailed comments, and then I realize, oh, there's going to be a lot, zoom out a little bit, and try to find are there some higher level trends that I can talk about? And maybe even just summarize in a larger comment at the bottom and say, "Hey, I see some larger structural issues," or "This PR is leaning very heavily on a technique that I think is maybe not the best use here. Maybe we should discuss that," instead of digging into maybe the actual implementation details of the code. SARAH: Yeah, funny, you should mention that. I have recently also started doing that, using the summary version of GitHub reviews. And I used to just go file by file and leaving comments right away. And I'm thinking that this is not a good idea, especially when the PR is long. So I think another thing I would do is also call the person to pair and ask questions and understand where the person is coming from and also explain what are your concerns and how you both can get to a better place with that PR. JOËL: That's really important. You have to remember there's another person on the other end of this. I love the idea of reaching out to them directly. Especially if there's a larger conversation to be had around approach or implementation, it's often easier to resolve those directly rather than back and forth in GitHub comments. So you mentioned situations where the PR is really long. Have you ever had to push back on that in some way? SARAH: Yes. Especially when I saw, whoa, that's going to be difficult to understand, that's going to be difficult to review. And I have reached out to the person to say, "Hey, what about we split that PR in two?" Of course thinking about splitting the PR in a way that makes sense, in a way that still delivers our users’ value as soon as possible. JOËL: I've been in situations like that where it's a really long PR, and the person has already invested a lot of work into it. And maybe it's even gone through a round of reviews. It feels almost too late to ask them to split up the work. But then I've actually regretted not doing that because there's so much complexity going on that then it doesn't work, or there are some bugs in it. We struggle to ship this, or it might just have to go through so many rounds of review and re-review and re-review. And because the PR is so long, it's a huge commitment for me to re-review it every time. So there are situations I've been in where I wish that before even looking at the code at all, I was like, this is too long. We need to either slim down the story of what's being done. Because sometimes that's what happens is that the ticket is not well-defined, and someone goes in and just sort of keeps adding more code. And it becomes a bit of a big ball of mud. So, either helping to refine the ticket first or splitting the PR rather than actually looking at the code. SARAH: Yeah, and pairing often can also help with that. So especially as consultants, our clients may ask us to work on different projects, and you work alone. And you may have tight deadlines, but I think it's always helpful to find time anyway to help your colleagues as well. JOËL: I like that. I think there's a lot of value in the work that we do, where we collaborate with others in addition to whatever we do solo. So, oftentimes, it's great to pair with people at a client where possible to become involved in the code review process to even get involved in maybe some of the more broader system design conversations, sprint planning. All of those things are really good to jump into more than just getting siloed into working on just a solo feature. SARAH: Yes, 100%. MID-ROLL AD: Debugging errors can be a developer’s worst nightmare...but it doesn’t have to be. Airbrake is an award-winning error monitoring, performance, and deployment tracking tool created by developers for developers that can actually help cut your debugging time in half. So why do developers love Airbrake? It has all of the information that web developers need to monitor their application - including error management, performance insights, and deploy tracking! Airbrake’s debugging tool catches all of your project errors, intelligently groups them, and points you to the issue in the code so you can quickly fix the bug before customers are impacted. In addition to stellar error monitoring, Airbrake’s lightweight APM helps developers to track the performance and availability of their application through metrics like HTTP requests, response times, error occurrences, and user satisfaction. Finally, Airbrake Deploy Tracking helps developers track trends, fix bad deploys, and improve code quality. Since 2008, Airbrake has been a staple in the Ruby community and has grown to cover all major programming languages. Airbrake seamlessly integrates with your favorite apps to include modern features like single sign-on and SDK-based installation. From testing to production, Airbrake notifiers have your back. Your time is valuable, so why waste it combing through logs, waiting for user reports, or retrofitting other tools to monitor your application? You literally have nothing to lose. Head on over to airbrake.io/try/bikeshed to create your FREE developer account today! JOËL: So one of the things you mentioned that stood out for you when you were doing some code review recently was making HTTP requests in an ActiveRecord model. Why is that something that sort of caught your eyes, maybe an area to push back on in a particular design? SARAH: That's a good question. My concern with that approach was that our class was having too many responsibilities that would break the SRP principle, the single-responsibility principle, and that would make our class hard to maintain. So the ActiveRecord layer is a layer that's meant to encapsulate business roles and data. So I was worried that adding another responsibility on top of it would be too much. So my idea was that we would extract a class that would handle the whole HTTP request process. JOËL: Yeah, I feel like my instincts typically when I've done third-party integrations is that the ActiveRecord class should not know about the external internet world. It knows about the database. It knows about some of its core model functionality but that knowing about the internet world is somebody else's responsibility and that, ideally, the direction of dependency should flow the other way. So maybe the class that makes an external request knows about the ActiveRecord object if it needs to let's say, instantiate an instance of that model using data from an external request. Or maybe it's even some third-party thing; maybe it's their controller that knows how to make or that will ask another object to make a request to some API and might also make a request to the model and ask it for some database data and then combine those two together. But that the ActiveRecord object only knows about that database area of responsibility and doesn't know that other things are also happening in the system. SARAH: Absolutely. And I was also thinking that that class would have a difficult test to write. So a good idea is to separate our code that is side-effectful into their own classes, and that makes our tests so much easier. JOËL: I actually wrote an article on the topic where one of my realizations at some point was that a lot of the pain points in code are what functional programmers would call side effects, so things like HTTP requests. And these are often things where we need to stub or do other things. And so isolating them as much as possible often simplifies our tests. SARAH: Yeah, certainly. And I refer to that article every time I have the chance. JOËL: Have you encountered the general concept of layered architectures, or hexagonal architectures, or things like that in the world of Rails or maybe elsewhere? SARAH: Not hexagonal architecture. I have heard about it, but I haven't dived into it yet. Can you give us an overview? JOËL: So I've also not worked with an actual hexagonal architecture. But the general idea, I guess, of layered architectures is that you build your code in a variety of layers, and different layers don't have access to or don't know about the ones...and I forget in this model if it's above or below, let's say it's below. So the inner layers don't know about the outer layers, but the outer layers can know about anything below them. And so if the core of your app is the database, your database is most definitely not knowing about anything outside of just its data. And your ActiveRecord models that sit on top of that know about the database, but they don't know if they're being fronted by a web application, or a command line, or anything else. And then, above that, you might have more of a business process layer that knows about the database. It might know about how to make some external requests, but it doesn't know about anything above that. And then, maybe at the final layer, you've got an application layer that handles things like controllers and interactions with users of the site. The core idea is that you split it into layers, and the higher-up layers know about everything below them, but no layer knows about what's above it. I feel like we're loosely applying that to the situation here with ActiveRecord in that it feels like the ActiveRecord layer if you will, shouldn't really know about third-party API requests. SARAH: So, one exception to that is the ActiveResource approach that connects our business objects to REST services. So if you have an external website and you want to connect it via HTTP, you can do it using Rails ActiveResource. JOËL: That is interesting because it functions like an ActiveRecord object, but instead of being backed by the database, it's backed by some kind of API. I almost wonder if...let's refactor our mental model here. And instead of saying that HTTP belongs in a separate layer that's higher up, maybe, in this case, it's almost like a sibling layer. So your ActiveRecord models know about the database, and they make database requests in ActiveResource, or I think there are some gems that provide similar behavior. It might be backed by a particular API, but neither of them should know about the other. So maybe an ActiveResource model should not be making database requests. SARAH: Yes, I like that line of thought. JOËL: I guess the question then becomes, what about interactions between the two where you want to, I don't know, have some kind of association? You know, I don't think I've ever used ActiveResource on a project. SARAH: I did once when trying to work with something close to microservice architecture. So we had a monolith, and we built a small service that was also in Rails, and we needed to consume the data that was stored in the monolith. JOËL: And did you like that approach? SARAH: Yeah. I think in that specific scenario, it was very productive. And I enjoyed a lot the API that Rails provided me via ActiveResources. JOËL: Did you ever have to mix ActiveResource models and ActiveRecord models? SARAH: No, I didn't; thankfully, not. I have never thought about that. JOËL: So maybe in most applications, those two will just sort of naturally fall into maybe separate parts of the app, and they don't need to interact that much. SARAH: Yeah, I think that will be the case. So mixing two of those subjects we're talking about here, that's testing and HTTP requests; we've been having a discussion in our project about the usage of VCR. That's a gem that records your HTTP requests interactions and replays them during tests. We've been discussing if using it is a good idea or not because we've been having issues with cassettes, that's one of VCR's concepts when these cassettes are not valid anymore. So do you have any thoughts on the subject? Maybe that will make a whole episode. JOËL: We could definitely do a whole episode, I think, on testing third-party APIs. VCR is one of multiple different strategies that can be used to not make actual real network requests in your tests which brings some stability. There are also some downsides to it. I have found, in general, that over time, cassettes become brittle. So the idea of VCR is really cool. In practice, I think I've found that a few hand-rolled Webmock stubs usually do the job better for my needs. SARAH: Yeah, I'll be interested in hearing that episode because, at least in my project, we have a lot of HTTP requests to external services, and they return a lot of information. I'm wondering if just dealing with that with Webmock would be too much work. JOËL: One of the really useful things about VCR is that you can just make your request from anywhere, and it will just completely handle it. In some ways, though, I think it maybe hides some of that test pain that we were talking about earlier and allows you to sort of put HTTP in a lot of places that maybe you don't want it to. And by allowing yourself to feel a little bit of that test pain, you can more easily notice the places where maybe an object should not be making a request. Or the actual HTTP logic can be moved to a concentrated place where all the HTTP is done together. And then only that object will need unit tests that actually need to mock the network, and most of your objects are fine. Where it gets interesting is more for things like integration tests, where now you're doing a lot of interactions, and you might have quite a few background requests that need to be made. SARAH: I'm looking forward to the whole episode on this subject because I feel there's so much to talk about. JOËL: There really is. I have a blog post that sort of summarizes a few different common categories of approaches to testing third-party requests, which might be different depending on whether you're doing a unit test or an integration test. But I grouped common solutions into four different categories. We'll make sure to link that in the show notes. So we've been talking a lot about testing. I'm curious when you review PR, do you start with the tests, maybe read through the tests first, and then the implementation? SARAH: That's a good question. I have never thought about starting with tests. I think I'm going to give that a try anytime. But I just start reviewing them like by the first file that comes up. [laughs] JOËL: I'm the same. I normally just do them in order. I have occasionally tried to do a test first, and that is sometimes interesting. Sometimes you read the test and, especially when you don't know what the implementation is going to be, you're like, why is this in the test? And then you jump to the implementation like, oh, that's what's going on. Well, thank you so much, Sarah, for joining us on this whirlwind tour of code review, design of objects, and interacting with HTTP and testing. SARAH: My pleasure. JOËL: Where can people find you online if they would like to follow your work? SARAH: I'm on Twitter @sarahlima_rb. JOËL: We'll make sure to link that in the show notes. And with that, let's wrap up. The show notes for this episode can be found at bikeshed.fm. This show is produced and edited by Mandy Moore. If you enjoyed listening, one really easy way to support the show is to leave us a quick rating or even a review in iTunes. It really helps other folks find the show. If you have any feedback, you can reach us at @_bikeshed, or reach me at @joelquen on Twitter, or at hosts@bikeshed.fm via email. Thank you so much for listening to The Bike Shed, and we'll see you next week. Byeeeeeee!!!!!! ANNOUNCER: This podcast was brought to you by thoughtbot. thoughtbot is your expert design and development partner. Let's make your product and team a success.Sponsored By:Airbrake: Deploy fearlessly and fix bugs faster with Airbrake Error & Performance Monitoring. Airbrake notifiers are available for all major programming languages and frameworks, and install in minutes, with an open-source SDK-based install and near-zero technical debt. Spend less time tracking down bugs and more time developing. Visit Frictionless error monitoring and performance insight for your app stack.Support The Bike Shed
undefined
Oct 25, 2022 • 44min

359: Serializers

Chris Toomey is back! (For an episode.) He talks about what he's been up to since handing off the reins to Joël. He's been playing around with something at Sagewell that he enjoys. At the core of it? Serializers. Primalize gem Derek's talk on code review Inertia.js Phantom types io-ts dry-rb parse don't validate value objects broader perspective on parsing Enumerable#tally RubyConf mini where.missing Transcript: JOËL: Hello and welcome to another episode of The Bike Shed, a weekly podcast from your friends at thoughtbot about developing great software. I'm Joël Quenneville. And today, I'm joined by a very special guest, former host Chris Toomey. CHRIS: Hi, Joël. Thanks for having me. JOËL: And together, we're here to share a little bit of what we've learned along the way. So, Chris, what's new in your world? CHRIS: Being on this podcast is new in my world, or everything old is new again, or something along those lines. But, yeah, thank you so much for having me back. It's a pleasure. Although it's very odd, it feels somehow so different and yet very familiar. But yeah, more generally, what's new in my world? I think this was probably in development as I was winding down my time as a host here on The Bike Shed, but I don't know that I ever got a chance to talk about it. There has been a fun sort of deep-in-the-weeds technical thing that we've been playing around with at Sagewell that I've really enjoyed. So at the core of it, we have serializers. So we take some data structures in our Ruby on Rails code base, and we need to serialize them to JSON to send them to the front end. In our case, we're using Inertia, so it's not quite a JSON API, but it's fine to think about it in that way for the context of this discussion. And what we were finding is our front end has TypeScript. So we're writing Svelte, which is using TypeScript. And so we're stating or asserting that the types like, hey, we're going to get this data in from the back end, and it's going to have this shape to it. And we found that it was really hard to keep those in sync to keep, like, what does the user mean on the front end? What's the data that we're going to get? It's going to have a full name, which is a string, except sometimes that might be null. So how do we make sure that those are keeping up to date? And then we had a growing number of serializers on the back end and determining which serializer we were actually using, and it was just...it was a mess, to put it lightly. And so we had explored a couple of different options around it, and eventually, we found a library called Primalize. So Primalize is a Ruby library. It is for writing JSON serializers. But what's really interesting about it is it has a typing layer. It's like a type system sort of thing at play. So when you define a serializer in Primalize, instead of just saying, here are the fields; there is an ID, a name, et cetera, you say, there is an ID, and it is a string. There is a name, and it is a string, or an optional string, which is the even more interesting bit. You can say array. You can say object. You can say an enum of a couple of different values. And so we looked at that, and we said, ooh, this is very interesting. Astute listeners will know that this is probably useless in a Ruby system, which doesn't have types or a compilation step or anything like that. But what's really cool about this is when you use a Primalize serializer, as you're serializing an object, if there is ever a type mismatch, so the observed type at runtime and the authored type if those ever mismatch, then you can have some sort of notification happen. So in our case, we configured it to send a warning to Sentry to say, "Hey, you said the types were this, but we're actually seeing this other thing." Most often, it will be like an Optional, a null sneaking through, a nil sneaking through on the Ruby side. But what was really interesting is as we were squinting at this, we're like, huh, so now we're going to write all this type information. What if we could somehow get that type information down to the front end? So I had a long weekend, one weekend, and I went away, and I wrote a bunch of code that took all of those serializers, ran through them, and generated the associated TypeScript interfaces. And so now we have a build step that will essentially run that and assert that we're getting the same thing in CI as we have committed to the codebase. But now we have the generated serializer types on the front end that match to the used serializer on the back end, as well as the observed run-time types. So it's a combination of a true compilation step type system on the front end and a run-time type system on the back end, which has been very, very interesting. JOËL: I have a lot of thoughts here. CHRIS: I figured you would. [laughs] JOËL: But the first thing that came to mind is, as a consultant, there's a scenario with especially smaller startups that generally concerns me, and that is the CTO goes away for a weekend and writes a lot of code... CHRIS: [laughs] JOËL: And brings in a new system on Monday, which is exactly what you're describing here. How do you feel about the fact that you've done that? CHRIS: I wasn't ready to go this deep this early on in this episode. JOËL: [laughs] CHRIS: But honestly, that is a fantastic question. It's a thing that I have been truly not struggling with but really thinking about. We're going to go on a slight aside here, but I am finding it really difficult to engage with the actual day-to-day coding work that we're doing and to still stay close to the codebase and not be in the way. There's a pattern that I've seen happen a number of times now where I pick up a piece of work that is, you know, one of the tickets at the top of the backlog. I start to work on it. I get pulled into a meeting, then another meeting, then three more meetings. And suddenly, it's three days later. I haven't completed this piece of work that was defined to be the next most important piece of work. And suddenly, I'm blocking the team. JOËL: Hmmm. CHRIS: So I actually made a rule that I'm not allowed to own critical path work, which feels weird because it's like, I want to be engaged with that work. So the counterpoint to that is I'm now trying to schedule pairing sessions with each of the developers on the team once a week. And in that time, I can work on that sort of stuff with them, and they'll then own it and run with it. So it makes sure that I'm not blocking on those sorts of things, but I'm still connected to the core work that we're doing. But the other thing that you're describing of the CTO goes away for the weekend and then comes back with a new harebrained scheme; I'm very sensitive to that, having worked on; frankly, I think the same project. I can think of a project that you and I worked on where we experienced this. JOËL: I think we're thinking of the same project. CHRIS: So yes. Like, I'm scarred by that and, frankly, a handful of experiences of that nature. So we actually, I think, have a really healthy system in place at Sagewell for capturing, documenting, prioritizing this sort of other work, this developer-centric work. So this is the feature and bug work that gets prioritized and one list over here that is owned by our product manager. Separately, the dev team gets to say, here are the pain points. Here's the stuff that keeps breaking. Here are the things that I wish was better. Here is the observability hard-to-understand bits. And so we have a couple of different systems at play and recurring meetings and sort of unique ceremonies around that, and so this work was very much a fallout of that. It was actually a recurring topic that we kept trying a couple of different stabs at, and we never quite landed it. And then I showed up this one Monday morning, and I was like, "I found a thing; what do we think?" And then, critically, from there, I made sure I paired with other folks on the team as we pushed on the implementation. And then, actually, I mentioned Primalize, the library that we're using. We have now since deprecated Primalize within the app because we kept just adding to it so much that eventually, we're like, at this point, should we own this stuff? So we ended up rewriting the core bits of Primalize to better fit our use cases. And now we've actually removed Primalize, wonderful library. I highly recommend it to anyone who has that particular use case but then the additional type generation for the front end. Plus, we have some custom types within our app, Money being the most interesting one. We decided to model Money as our first-class consideration rather than just letting JavaScript have the sole idea of a number. But yes, in a very long-winded way, yes, I'm very sensitive to the thing you described. And I hope, in this case, I did not fall prey to the CTO goes away for the weekend and made a thing. JOËL: I think what I'm hearing is the key difference here is that you got buy-in from the team around this idea before you went out and implemented it. So you're not off doing your own things disconnected from the team and then imposing it from on high. The team already agreed this is the thing we want to do, and then you just did it for them. CHRIS: Largely, yes. Although I will say there are times that each developer on the team, myself included, have sort of gone away, come back with something, and said, "Hey, here's a WIP PR exploring an area." And there was actually...I'm forgetting what the context was, but there was one that happened recently that I introduced. I was like; I had to do this. And the team talked me out of it, and I ended up closing that PR. Someone else actually made a different PR that was an alternative implementation. I was like, no, that's better; we should absolutely do that. And I think that's really healthy. That's a hard thing to maintain but making sure that everyone feels like they've got a strong voice and that we're considering all of the different ways in which we might consider the work. Most critically, you know, how does this impact users at the end of the day? That's always the primary consideration. How do we make sure we build a robust, maintainable, observable system, all those sorts of things? And primarily, this work should go in that other direction, but I also don't want to stifle that creative spark of I got this thing in my head, and I had to explore it. Like, we shouldn't then need to never mind, throw away the work, put it into a ticket. Like, for as long as we can, that more organic, intuitive process if we can retain that, I like that. Critically, with the ability for everyone to tell me, "No, this is a bad idea. Stop it. What are you doing?" And that has happened recently. I mean, they were kinder about it, but they did talk me out of a bad idea. So here we are. JOËL: So you showed up on Monday morning, not with telling everyone, "Hey, I merged this thing over the weekend." You're showing up with a work-in-progress PR. CHRIS: Yes, definitely. I mean, everything goes through a PR, and everything has discussion and conversation around it. That's a strong, strong like Derek Prior's wonderful talk Building a Culture of Code Review. I forget the exact name of it. But it's one of my favorite talks in talking about the utility of code review as a way to share ideas and all of those wonderful things. So everything goes through code review, and particularly anything that is of that more exploratory architectural space. Often we'll say any one review from anyone on the team is sufficient to merge most things but something like that, I would want to say, "Hey, can everybody take a look at this? And if anyone has any reservations, then let's talk about it more." But if I or anyone else on the team for this sort of work gets everybody approving it, then cool, we're good to go. But yeah, code review critical, critical part of the process. JOËL: I'm curious about Primalize, the gem that you mentioned. It sounds like it's some kind of validation layer between some Ruby data structure and your serializers. CHRIS: It is the serializer, but in the process of serializing, it does run-time type validation, essentially. So as it's accessing, you know, you say first name. You have a user object. You pass it in, and you say, "Serializer, there's a first name, and it's a string." It will call the first name method on that user object. And then, it will check that it has the expected type, and if it doesn't, then, in our case, it sends to Sentry. We have configured it...it's actually interesting. In development and test mode, it will raise for a type mismatch, and in production mode, it will alert Sentry so you can configure that differently. But that ends up being really nice because these type mismatches end up being very loud early on. And it's surprisingly easy to maintain and ends up telling us a lot of truths about our system because, really, what we're doing is connecting data from many different systems and flowing it in and out. And all of the inputs and outputs from our system feel very meaningful to lock down in this way. But yeah, it's been an adventure. JOËL: It seems to me there could almost be two sets of types here, the inputs coming into Primalize from your Ruby data structures and then the outputs that are the actual serialized values. And so you might expect, let's say, an integer on the Ruby side, but maybe at the serialization level, you're serializing it to a string. Do you have that sort of conversion step as part of your serializers sometimes, or is the idea that everything's already the right type on the Ruby side, and then we just, like, to JSON it at the end? CHRIS: Yep. Primalize, I think, probably works a little closer to what you're describing. They have the idea of coercions. So within Primalize, there is the concept of a timestamp; that is one of the types that is available. But a timestamp is sort of the union of a date, a time, or I think they might let through a string; I'm not sure if there is as well. But frankly, for us, that was more ambiguity than we wanted or more blurring across the lines. And in the implementation that we've now built, date and time are distinct. And critically, a string is not a valid date or time; it is a string, that's another thing. And so there's a bunch of plumbing within the way you define the serializers. There are override methods so that you can locally within the serializer say, like, oh, we need to coerce from the shape of data into this other shape of data, even little like in-line proc, so we can do it quickly. But the idea is that the data, once it has been passed to the serializer, should be up the right shape. And so when we get to the type assertion part of the library, we expect that things are in the asserted type and will warn if not. We get surprisingly few warnings, which is interesting now. This whole process has made us pay a little more intention, and it's been less arduous simultaneously than I would have expected because like this is kind of a lot of work that I'm describing. And yet it ends up being very natural when you're the developer in context, like, oh, I've been reading these docs for days. I know the shape of this JSON that I'm working with inside and out, and now I'll just write it down in the serializer. It's very easy to do in that moment, and then it captures it and enforces it in such a useful way. As an aside, as I've been looking at this, I'm like, this is just GraphQL, but inside out, I'm pretty sure. But that is a choice that we have made. We didn't want to adopt the whole GraphQL thing. But just for anyone out there who is listening and is thinking, isn't this just GraphQL but inside out? Kind of. Yes. JOËL: I think my favorite part of GraphQL is the schema, which is not really the selling point for GraphQL, you know, like the idea that you can traverse the graph and get any subset of data that you want and all that. I think I would be more than happy with a REST API that has some kind of schema built around it. And someone told me that maybe what I really just want is SOAP, and I don't know how to feel about that comment. CHRIS: You just got to have some XML, and some WSDLs, and other fun things. I've heard people say good things about SOAP. SOAP seems like a fine idea. If anything, I think a critical part of this is we don't have a JSON API. We have a very tightly coupled front end and back end, and a singular front end, frankly. And so that I think naturally...that makes the thing that I'm describing here a much more comfortable fit. If we had multiple different downstream clients that we're trying to consume from the same back end, then I think a GraphQL API or some other structured JSON schema, whatever it is type of API, and associated documentation and typing layer would be probably a better fit. But as I've said many a time on this here, Bike Shed, Inertia is one of my favorite libraries or frameworks (They're probably more of a framework.) one of my favorite technological approaches that I have ever found. And particularly in buildings Sagewell, it has allowed us to move so rapidly the idea that changes are, you know, one fell swoop changes everything within the codebase. We don't have to think about syncing deploys for the back end and the front end and how to coordinate across them. Our app is so much easier to understand by virtue of that architecture that Inertia implies. JOËL: So, if I understand correctly, you don't serialize to JSON as part of the serializers. You're serializing directly to JavaScript. CHRIS: We do serialize to JSON. At the end of the day, Inertia takes care of this on both the Rails side and the client side. There is a JSON API. Like, if you look at the network inspector, you will see XHR requests happening. But critically, we're not doing that. We're not the ones in charge of it. We're not hitting a specific endpoint. It feels as an application coder much closer to a traditional Rails app. It just happens to be that we're writing our view layer. Instead of an ERB, we're writing them in Svelte files. But otherwise, it feels almost identical to a normal traditional Rails app with controllers and the normal routing and all that kind of stuff. JOËL: One thing that's really interesting about JSON as an interchange format is that it is very restrictive. The primitives it has are even narrower than, say, the primitives that Ruby has. So you'd mentioned sending a date through. There is no JSON date. You have to serialize it to some other type, potentially an integer, potentially a string that has a format that the other side knows how it's going to interpret. And I feel like it's those sorts of richer types when we need to pass them through JSON that serialization and deserialization or parsing on the other end become really interesting. CHRIS: Yeah, I definitely agree with that. It was a struggling point for a while until we found this new approach that we're doing with the serializers in the type system. But so far, the only thing that we've done this with is Money. But on the front end, a while ago, we introduced a specific TypeScript type. So it's a phantom type, and I believe I'm getting this correct. It's a phantom type called Cents, C-E-N-T-S. So it represents...I'm going to say an integer. I know that JavaScript doesn't have integers, but logically, it represents an integer amount of cents. And critically, it is not a number, like, the lowercase number in the type system. We cannot add them together. We can't -- JOËL: I thought you were going to say, NaN. CHRIS: [laughs] It is not a number. I saw a n/a for not applicable somewhere in the application the other day. I was like, oh my God, we have a NaN? It happened? But it wasn't, it was just n/a, and I was fine. But yeah, so we have this idea of Cents within the application. We have a money input, which is a special input designed exactly for this. So to a user, it is formatted to look like you're entering dollars and cents. But under the hood, we are bidirectionally converting that to the integer amount of cents that we need. And we strictly, within the type system, those are cents. And you can't do math on Cents unless you use a special set of helper functions. You cannot generate Cents on the fly unless you use a special set of helper functions, the constructor functions. So we've been really restrictive about that, which was kind of annoying because a lot of the data coming from the server is just, you know, numbers. But now, with this type system that we've introduced on the Ruby side, we can assert and enforce that these are money.new on the Ruby side, so using the Money gem. And they come down to the front end as capital C Cents in the type system on the TypeScript side. So we're able to actually bind that together and then enforce proper usage sort of on both sides. The next step that we plan to do after that is dates and times. And those are actually almost weirder because they end up...we just have to sort of say what they are, and they will be ISO 8601 date and time strings, respectively. But we'll have functions that know this is a date string; that's a thing. It is, again, a phantom type implemented within our TypeScript type system. But we will have custom functions that deal with that and really constrain...lock ourselves down to only working with them correctly. And critically, saying that is the only date and time format that we work with; there is no other. We don't have arbitrary dates. Is this a JSON date or something else? I don't know; there are too many date syntaxes. JOËL: I like the idea of what you're doing in that it sounds like you're very much narrowing that sort of window of where in the stack the data exists in the sort of unstructured, free-floating primitives that could be misinterpreted. And so, at this point, it's almost narrowed to the point where it can't be touched by any user or developer-written code because you've pushed the boundaries on the Rails side down and then on the JavaScript side up to the point where the translation here you define translations on one side or, I guess, a parser on one side and a serializer on the other. And they guarantee that everything is good up until that point. CHRIS: Yep, with the added fun of the runtime reflection on the Ruby side. So it's an interesting thing. Like, TypeScript actually has similar things. You can say what the type is all day long, and your code will consistently conform to that asserted type. But at the end of the day, if your JSON API gets in some different data...unless you're using a library like io-ts, is one that I've looked at, which actually does parsing and returns a result object of did we parse to the thing that you wanted or did we get an error in that data structure? So we could get to that level on the client side as well. We haven't done that yet largely because we've essentially pushed that concern up to the Ruby layer. So where we're authoring the data, because we own that, we're going to do it at that level. There are a bunch of benefits of defining it there and then sort of reflecting it down. But yeah, TypeScript, you can absolutely lie to yourself, whereas Elm, a language that I know you love dearly, you cannot lie to yourself in Elm. You've got to tell the truth. It's the only option. You've got to prove it. Whereas in TypeScript, you can just kind of suggest, and TypeScript will be like, all right, cool, I'll make sure you stay honest on that, but I'm not going to make you prove it, which is an interesting sort of set of related trade-offs there. But I think we found a very comfortable resting spot for right now. Although now, we're starting to look at the edges of the Ruby system where data is coming in. So we have lots of webhooks and other external partners that we're integrating with, and they're sending us data. And that data is of varying shapes. Some will send us a payload with the word amount, and it refers to an integer amount of cents because, of course, it does. Some will send us the word amount in their payload, and it will be a floating amount of dollars. And I get a little sad on those days. But critically, our job is to make sure all of those are the same and that we never pass dollars as cents or cents as dollars because that's where things go sad. That is job number one at Sagewell in the engineering team is never get the decimal place wrong in money. JOËL: That would be a pretty terrible mistake to make. CHRIS: It would. I mean, it happens. In fintech, that problem comes up a lot. And again, the fact that...I'm honestly surprised to see situations out there where we're getting in floating point dollars. That is a surprise to me because I thought we had all agreed sort of as a community that it was integer cents but especially in a language that has integers. JavaScript, it's kind of making it up the whole time. But Ruby has integers. JSON, I guess, doesn't have integers, so I'm sort of mixing concerns here, but you get the idea. JOËL: Despite Ruby not having a static type system, I've found that generally, when I'm integrating with a third-party API, I get to the point where I want something that approximates like Elm's JSON decoders or io-ts or something like that. Because JSON is just a big blob of data that could be of any shape, and I don't really trust it because it's third-party data, and you should not trust third parties. And I find that I end up maybe cobbling something together commonly with like a bunch of usage of hash.fetch, things like that. But I feel like Ruby doesn't have a great approach to parsing and composing these validators for external data. CHRIS: Ruby as a language certainly doesn't, and the ecosystem, I would say, is rather limited in terms of the options here. We have looked a bit at the dry-rb stack of gems, so dry-validation and dry-schema, in particular, both offer potentially useful aspects. We've actually done a little bit of spiking internally around that sort of thing of, like, let's parse this incoming data instead of just coercing to hash and saying that it's got probably the shape that we want. And then similarly, I will fetch all day instead of digging because I want to be quite loud when we get it wrong. But we're already using dry-monads. So we have the idea of result types within the system. We can either succeed or fail at certain operations. And I think it's just a little further down the stack. But probably something that we will implement soon is at those external boundaries where data is coming in doing some form of parsing and validation to make sure that it conforms to unknown data structure. And then, within the app, we can do things more cleanly. That also would allow us to, like, let's push the idea that this is floating point dollars all the way out to the edge. And the minute it hits our system, we convert it into a money.new, which means that cents are properly handled. It's the same type of money or dollar, same type of currency handling as everywhere else in the app. And so pushing that to the very edges of our application is a very interesting idea. And so that could happen in the library or sort of a parsing client, I guess, is probably the best way to think about it. So I'm excited to do that at some point. JOËL: Have you read the article, Parse, Don't Validate? CHRIS: I actually posted that in some code review the other day to one of the developers on the team, and they replied, "You're just going to quietly drop one of my favorite articles of all time in code review?" [laughs] So yes, I've read it; I love it. It's a wonderful idea, definitely something that I'm intrigued by. And sort of bringing dry-monads into Ruby, on the one hand, feels like a forced fit and yet has also been one of the other, I think strongest sort of architectural decisions that we've made within the application. There's so much imperative work that we ended up having to do. Send this off to this external API, then tell this other one, then tell this other one. Put the whole thing in a transaction so that our local data properly handles it. And having dry-monads do notation, in particular, to allow us to make that manageable but fail in all the ways it needs to fail, very expressive in its failure modes, that's been great. And then parse, don't validate we don't quite do it yet. But that's one of the dreams of, like, our codebase really should do that thing. We believe in that. So let's get there soon. JOËL: And the core idea behind parse, don't validate is that instead of just having some data that you don't trust, running a check on it and passing that blob of now checked but still untrusted data down to the next person who might also want to check it. Generally, you want to pass it through some sort of filter that will, one, validate that it's correct but then actually typically convert it into some other trusted shape. In Ruby, that might be something like taking an amorphous blob of JSON and turning it into some kind of value object or something like that. And then anybody downstream that receives, let's say, money object can trust that they're dealing with a well-formed money value as opposed to an arbitrary blob of JSON, which hopefully somebody else has validated, but who knows? So I'm going to validate it again. CHRIS: You can tell that I've been out of the podcasting game for a while because I just started responding to yes; I love that blog post without describing the core premise of it. So kudos to you, Joël; you are a fantastic podcast host over there. I will say one of the things you just described is an interesting...it's been a bit of a struggle for us. We keep sort of talking through what's the architecture. How do we want to build this application? What do we care about? What are the things that really matter within this codebase, and then what is all the other stuff? And we've been good at determining the things that really matter, thinking collectively as a group, and I think coming up with some novel, useful, elegant...I'm saying too many positive adjectives for what we're doing. But I've been very happy with sort of the thing that we decide. And then there's the long-tail work of actually propagating that change throughout the rest of the application. We're, like, okay, here's how it works. Every incoming webhook, we now parse and yield a value object. That sentence that you just said a minute ago is exactly what I want. That's like a bunch of work. It's particularly a bunch of work to convert an existing codebase. It's easy to say, okay, from here forward, any new webhooks, payloads that are coming in, we're going to do in this way. But we have a lot of things in our app now that exist in this half-converted way. There was a brief period where we had three different serializer technologies at play. Just this week, I did the work of killing off the middle ground one, the Primalized-based thing, and we now have only our new hotness and then the very old. We were using Blueprinter as the serializer as the initial sort of stub. And so that still exists within the codebase in some places. But trying to figure out how to prioritize that work, the finishing out those maintenance-type conversions is a tricky one. It's never the priority. But it is really nice to have consistency in a codebase. So it's...yeah, do you have any thoughts on that? JOËL: I think going back to the article and what the meaning of parsing is, I used to always think of parsing as taking strings and turning them into something else, and I think this really broadened my perspective on the idea of parsing. And now, I think of it more as converting from a broader type to a narrower type with failures. So, for example, you could go from a string to an integer, and not all strings are valid integers. So you're narrowing the type. And if you have the string hello world, it will fail, and it will give you an error of some type. But you can have multiple layers of that. So maybe you have a string that you parse into an integer, but then, later on, you might want to parse that integer into something else that requires an integer in a range. Let's say it's a percentage. So you have a value object that is a percentage, but it's encoded in the JSON as a string. So that first pass, you parse it from a string into an integer, and then you parse that integer into a percentage object. But if it's outside the range of valid percentage numbers, then maybe you get an error there as well. So it's a thing that can happen at multiple layers. And I've now really connected it with the primitive obsession smell in code. So oftentimes, when you decide, wait, I don't want a primitive here; I want a richer type, commonly, there's going to be a parsing step that should exist to go from that primitive into the richer type. CHRIS: I like that. That was a classic Joël wildly concise summary of a deeply complex technical topic right there. JOËL: It's like I'm going to connect some ideas from functional programming and a classic object-oriented code smell and, yeah, just kind of mash it all together with a popular article. CHRIS: If only you had a diagram. Podcast is not the best medium for diagrams, but I think you could do it. You could speak one out loud, and everyone would be able to see it in their mind's eye. JOËL: So I will tell you what my diagram is for this because I've actually created it already. I imagine this as a sort of like pyramid with different layers that keep getting smaller and smaller. So the size of type is sort of the width of a layer. And so your strings are a very wide layer. Then on top of that, you have a narrower layer that might be, you know, it could be an integer, or you could even if you're parsing JSON, you first start with a string, then you parse that into a Ruby hash, not all strings are valid hashes. So that's going to be narrower. Then you might extract some values out of that hash. But if the keys aren't right, that might also fail. You're trying to pull the user out of it. And so each layer it gets a richer type, but that richer type, by virtue of being richer, is narrower. And as you're trying to move up that pyramid at every step, there is a possibility for a failure. CHRIS: Have you written a blog post about this with said diagram in it? And is that why you have that so readily at hand? [laughs] JOËL: Yes, that is the case. CHRIS: Okay. Yeah, that made sense to me. [laughs] JOËL: We'll make sure to link to it in the show notes. CHRIS: Now you have to link to Joël blog posts, whereas I used to have to link to them [chuckles] in almost every episode of The Bike Shed that I recorded. JOËL: Another thing I've been thinking about in terms of this parsing is that parsing and serializing are, in a sense, almost opposites of each other. Typically, when you're parsing, you're going from a broad type to a narrow one. And when you're serializing, you're going from a narrow type to a broader one. So you might go from a user into a hash into a string. So you're sort of going down that pyramid rather than going up. CHRIS: It is an interesting observation and one that immediately my brain is like, okay, cool. So can we reuse our serializers but just run them in reverse or? And then I try and talk myself out of that because that's a classic don't repeat yourself sort of failure mode of, like, actually, it's fine. You can repeat a little bit. So long as you can repeat and constrain, that's a fine version. But yeah, feels true, though, at the core. JOËL: I think, in some ways, if you want a single source of truth, what you want is a schema, and then you can derive serializers and parsers from that schema. CHRIS: It's interesting because you used the word derive. That has been an interesting evolution at Sagewell. The engineering team seems to be very collected around the idea of explicitness, almost the Zen of Python; explicit is better than implicit. And we are willing to write a lot of words down a lot of times and be happy with that. I think we actually made the explicit choice at one point that we will not implement an automatic camel case conversion in our serializer, even though we could; this is a knowable piece of code. But what we want is the grepability from the front end to the back end to say, like, where's this data coming from? And being able to say, like, it is this data, which is from this serializer, which comes from this object method, and being able to trace that very literally and very explicitly in the code, even though that is definitely the sort of thing that we could derive or automatically infer or have Ruby do that translation for us. And our codebase is more verbose and a little noisier. But I think overall, I've been very happy with it, and I think the team has been very happy. But it is an interesting one because I've seen plenty of teams where it is the exact opposite. Any repeated characters must be destroyed. We must write code to write the code for us. And so it's fun to be working with a team where we seem to be aligned around an approach on that front. JOËL: That example that you gave is really interesting because I feel like a common thing that happens in a serialization layer is also a form of normalization. And so, for example, you might downcase all strings as part of the serialization, definitely, like dates always get written in ISO 8601 format whenever that happens. And so, regardless of how you might have it stored on the Ruby side, by the time it gets to the JSON, it's always in a standard format. And it sounds like you're not necessarily doing that with capitalization. CHRIS: I think the distinction would be the keys and the values, so we are definitely doing normalization on the values side. So ISO 8601 date and time strings, respectively that, is the direction that we plan to go for the value. But then for the key that's associated with that, what is the name for this data, those we're choosing to be explicit and somewhat repetitive, or not even necessarily repetitive, but the idea of, like, it's first_name on the Ruby side, and it's first capital N name camel case, or it's...I forget the name. It's not quite camel case; it's a different one but lower camel, maybe. But whatever JavaScript uses, we try to bias towards that when we're going to the front end. It does get a little tricky coming back into the Ruby side. So our controllers have a bunch of places where they need to know about what I think is called lower camel case, and so we're not perfect there. But that critical distinction between sort of the names for things, and the values for things, transformations, and normalizations on the values, I'm good with that. But we've chosen to go with a much more explicit version for the names of things or the keys in JSON objects specifically. JOËL: One thing that can be interesting if you have a normalization phase in your serializer is that that can mean that your serializer and parsers are not necessarily symmetric. So you might accept malformed data into your parser and parse it correctly. But then you can't guarantee that the data that gets serialized out is going to identically match the data that got parsed in. CHRIS: Yeah, that is interesting. I'm not quite sure of the ramifications, although I feel like there are some. It almost feels like formatting Prettier and things like that where they need to hold on to whitespace in some cases and throw out in others. I'm thinking about how ASTs work. And, I don't know, there's interesting stuff, but, again, not sure of the ramifications. But actually, to flip the tables just a little bit, and that's an aggressive terminology, but we're going to roll with it. To flip the script, let's go with that, Joël; what's been up in your world? You've been hosting this wonderful show. I've listened in to a number of episodes. You're doing a fantastic job. I want to hear a little bit more of what's new in your world, Joël. JOËL: So I've been working on a project that has a lot of flaky tests, and we're trying to figure out the source of that flakiness. It's easy to just dive into, oh, I saw a flaky Test. Let me try to fix it. But we have so much flakiness that I want to go about it a little bit more systematically. And so my first step has actually been gathering data. So I've actually been able to make API requests to our CI server. And the way we figure out flakiness is looking at the commit hash that a particular test suite run has executed on. And if there's more than one CI build for a given commit hash, we know that's probably some kind of flakiness. It could be a legitimate failure that somebody assumed was flakiness, and so they just re-run CI. But the symptom that we are trying to address is the fact that we have a very high level of people re-verifying their code. And so to do that or to figure out some stats, I made a request to the API grouped by commit hash and then was able to get the stats of how many re-verifications there are and even the distribution. The classic way that you would do that is in Ruby; you would use the GroupBy function from enumerable. And then, you would transform values instead of having, like, say; each commit hash then points to all the builds, an array of builds that match that commit hash. You would then thumb those. So now you have commit hashes that point to counts of how many builds there were for that commit hash. Newer versions of Ruby introduced the tally method, which I love, which allows you to basically do all of that in one step. One thing that I found really interesting, though, is that that will then give me a hash of commit hashes that point to the number of builds that are there. If I want to get the distribution for the whole project over the course of, say, the last week, and I want to say, "How many times do people run only one CI run versus running twice in the same commit versus running three times, or four times, or five or six times?" I want to see that distribution of how many times people are rerunning their build. You're effectively doing that tally process twice. So once you have a list of all the builds, you group by hash. You count, and so you end up with that. You have the Ruby hash of commit SHAs pointing to number of times the build was run on that. And then, you again group by the number of builds for each commit SHA. And so now what you have is you'll have something like one, and then that points to an array of SHA one, SHA two, SHA three, SHA four like all the builds. And then you tally that again, or you transform values, or however, you end up doing it. And what you end up with is saying for running only once, I now have 200 builds that ran only once. For running twice in the same commit SHA, there are 15. For running three times, there are two. For running four times, there is one. And now I've got my distribution broken down by how many times it was run. It took me a while to work through all of that. But now the shortcut in my head is going to be you double tally to get distribution. CHRIS: As an aside, the whole everything you're talking about is interesting and getting to that distribution. I feel like I've tried to solve that problem on data recently and struggled with it. But particularly tally, I just want to spend a minute because tally is such a fantastic addition to the Ruby standard library. I used to have in sort of like loose muscle memory transform value is grouped by ampersand itself, transform values count, sort, reverse to H. That whole string of nonsense gets replaced by tally, and, oof, what a beautiful example of Ruby, and enumerable, and all of the wonder that you can encapsulate there. JOËL: Enumerable is one of the best parts of Ruby. I love it so much. It was one of the first things that just blew my mind about Ruby when I started. I came from a PHP, C++ background and was used to writing for loops for everything and not the nice for each loops that a lot of languages have these days. You're writing like a legit for or while loop, and you're managing the indexes yourself. And there's so much room for things to go wrong. And being introduced to each blew my mind. And I was like, this is so beautiful. I'm not dealing with indexes. I'm not dealing with the raw implementation of the array. I can just say do a thing for each element. This is amazing. And that is when I truly fell in love with Ruby. CHRIS: I want to say I came from Python, most recently before Ruby. And Python has pretty nice list comprehensions and, in fact, in some ways, features that enumerable doesn't have. But, still, coming to Ruby, I was like, oh, this enumerable; this is cool. This is something. And it's only gotten better. It still keeps growing, and the idea of custom enumerables. And yeah, there's some real neat stuff in there. JOËL: I'm going to be speaking at RubyConf Mini this fall in November, and my talk is all about Enumerators and ranges in enumerable and ways you can use those to make the APIs of the objects that you create delightful for other people to use. CHRIS: That sounds like a classic Joël talk right there that I will be happy to listen to when it comes out. A very quick related, a semi-related aside, so, tally, beautiful addition to the Ruby language. On the Rails side, there was one that I used recently, which is where.missing. Have you seen where.missing? JOËL: I have not heard of this. CHRIS: So where.missing is fantastic. Let's assume you've got two related objects, so you've got like a has many blah, so like a user has many posts. I think you can...if I'm remembering it correctly, it's User.where.missing(:posts). So it's where dot missing and then parentheses the symbol posts. And under the hood, Rails will do the whole LEFT OUTER JOIN where the count is null, et cetera. It turns into this wildly complex SQL query or understandably complex, but there's a lot going on there. And yet it compresses down so elegantly into this nice, little ActiveRecord bit. So where.missing is my new favorite addition into the Rails landscape to complement tally on the Ruby side, which I think tally is Ruby 2.7, I want to say. So it's been around for a while. And where.missing might be a Ruby 7 feature. It might be a six-something, but still, wonderful features, ever-evolving these tool sets that we use. JOËL: One of the really nice things about enumerable and family is the fact that they build on a very small amount of primitives, and so as long as you basically understand blocks, you can use enumerable and anything in there. It's not special syntax that you have to memorize. It's just regular functions and blocks. Well, Chris, thank you so much for coming back for a visit. It's been a pleasure. And it's always good to have you share the cool things that you're doing at Sagewell. CHRIS: Well, thank you so much, Joël. It's been an absolute pleasure getting to come back to this whole Bike Shed. And, again, just to add a note here, you're doing a really fantastic job with the show. It's been interesting transitioning back into listener mode for the show. Weirdly, I wasn't listening when I was a host. But now I've regained the ability to listen to The Bike Shed and really enjoy the episodes that you've been doing and the wonderful spectrum of guests that you've had on and variety of topics. So, yeah, thank you for hosting this whole Bike Shed. It's been great. JOËL: And with that, let's wrap up. The show notes for this episode can be found at bikeshed.fm. This show is produced and edited by Mandy Moore. If you enjoyed listening, one really easy way to support the show is to leave us a quick rating or even a review in iTunes. It really helps other folks find the show. If you have any feedback, you can reach us at @_bikeshed, or reach me at @joelquen on Twitter, or at hosts@bikeshed.fm via email. Thank you so much for listening to The Bike Shed, and we'll see you next week. Byeeeeeeeeeee!!!!!!!! ANNOUNCER: This podcast was brought to you by thoughtbot. thoughtbot is your expert design and development partner. Let's make your product and team a success.Support The Bike Shed
undefined
Oct 18, 2022 • 41min

358: Class Methods

Inspired by a Slack thread, Joël invites fellow thoughtbotter Aji Slater on the show to talk about when you should use class methods and when you should avoid them. Are there particular anti-patterns to look out for? How does this fit in with good object-oriented programming? What about Rails? What is an "alternate constructor"? What about service objects? So many questions, and friends: Aji and Joël deliver answers! Backbone.js collections Query object Rails is a dialect Meditations on a Class Method Why Ruby Class Methods Resist Refactoring Transcript: JOËL: Hello and welcome to another episode of The Bike Shed, a weekly podcast from your friends at thoughtbot about developing great software. I'm Joël Quenneville. And today, I'm joined by fellow thoughtboter Aji Slater. AJI: Howdy. JOËL: And together, we're here to share a little bit of what we've learned along the way. So, Aji, what's new in your world? AJI: Yeah, well, I just joined a new project, so that's kind of the newest thing in my day-to-day work world. I say just joined, but I guess it was about a month ago now. I'm on the Liftoff team at thoughtbot, which is different than the team that you're on. We do more closer to greenfield ideas and things like that. So there's actually not much to speak about there in that project just yet. Rails new is still just over the horizon for us. So I've been putting a lot of unused brain cycles toward a side project that is sort of a personal knowledge base concept, and that's a whole thing that I could probably host an entire podcast about. So we don't have to go too deep into my theories about that. But suffice it to say I've talked to some other ADHDers like myself who find that that space is not really conducive to the way that we think and have to organize ourselves and our personal knowledge stores. So sort of writing an app that can lend itself to our fast brains a little bit better. JOËL: Nice. I just recently recorded an episode of this podcast talking a little bit about note-taking approaches and knowledge-base systems. So, yeah, it's a topic that's very much top of mind for me right now. AJI: Yeah, what else is going on in your world? JOËL: I'm based in New England in the U.S. East Coast, and it is fall here. I feel like it happened kind of all of a sudden. And the traditional fall thing to do here is to go to an orchard and pick apples. It's a fun activity to do, and so I'm in the middle of planning that. Yeah, it's fun to go out into nature, very artificial space. AJI: [laughs] JOËL: But it's a fun thing to do every fall. AJI: Yeah, we do that here too. There's an orchard up north of us where my wife and I live in Chicago that we try to visit. And Apple Fest in Lincoln Square is this weekend, and we've been really looking forward to that. Try another time at making homemade hard cider this season, I think, and see how that goes. JOËL: Fun. When you say another time, does that mean there was a previous unsuccessful attempt? AJI: Yes. Did the sort of naive approach to it, and there is apparently a lot more subtlety to cidermaking than there is home-brew beer. And we got some real strong funk in that cider that did not make it necessarily an enjoyable experience. Like, it worked but wasn't the tastiest. JOËL: So it got alcoholic. It was just terrible to drink. AJI: Yeah, I would back that up. JOËL: So recently, at thoughtbot, we had a conversation among different team members about the use of Ruby class methods, when they make sense, when they are to be avoided. What is their use case? And different people had different opinions. So I'm curious what your take on class methods are. When do you like to use them? AJI: Yeah, I remember those conversations coming up. I think I might have even started one of those threads because this is something that comes up to me a lot. I'm a long-time listener, first-time caller to The Bike Shed. [laughs] I can remember awaiting new episodes from Sage and Derek to listen to on my way to and from my first dev job. And at one point, Sage had said, "Never put your business logic in something that you can't call .new on." And being a young, impressionable developer at the time, I took that to heart, and that seems something that just has been baked in and stayed very truthful to me. And I think one of the times that I asked that and got some conversation started was I was trying to figure out why did I feel that, and like, why did they say that? And I think, yeah, I try to avoid them. I like making instances of things. What is your stance on the Class Method, capital C, capital M? JOËL: I also generally avoid them. I have sort of two main scenarios that I like to use class methods, first is as an alternate constructor. So new is effectively a class method that's built into Ruby's object model. But sometimes, you want variations on your constructor that maybe sets values by default or that construct things with some slightly different inputs, things like that. And so those almost always make sense as class methods. The other thing that I sometimes use a class method for is as an alias for newing up an instance and then immediately calling an instance method on it. So it's just a slightly shorthand way to call some code. AJI: That's usually been my first line defense of when there's someone who might feel more comfortable doing class methods that sees me making an instance and says, "Well, you don't need an instance, just make a class method here because it'll get too long if you have to .new and then dot this other thing." And so I'll throw in that magic little trick and be like, here you go. You can call it a class method, and you still get all the benefits of your instance. I love that one. JOËL: Do you feel like that maybe defeats the purpose? In terms of the interface that people are using, if you're calling it a class method, do you lose the benefits of trying to do things at the instance level instead? Or is it more in the implementation that the benefits are not at the caller level? AJI: I think that's more true that the benefits are at the instance level, and you're getting all of that that goes along with it. And you're not carrying along a lot of what I see as baggage of the class method version, but you're picking up a little bit of that syntactic sugar. And sometimes it's even easier just to conceptualize, especially in the Rails space because we have all of these different class methods like, you know, Find is one I'm sure that we use all the time to call it on a class, and we get back an instance. And so that feels very natural in the Rails world. JOËL: I think you could make an argument that that is a form of alternate constructor. It's a class method you call to get an instance back. AJI: Yeah, absolutely. JOËL: The fact that it makes a background request to the database is an implementation detail. AJI: For sure. I agree with that. I had a similar need in a recent project where the data was kept on a third-party API. So I treated it the same way as, instead of going out to the database like ActiveRecord does, made a class method that went off to the API and then came back and made the object that was the representation of that idea in our application. So, yeah, I wholeheartedly agree with that. JOËL: So in Rails, we have the scope keyword, which will run some query to get a collection of records. But another way that they're often implemented is as class methods, and they're more or less interchangeable. How do you feel about that kind of use of class methods on an ActiveRecord object? Does that violate some of the ideas that we've been talking about? Does it sort of fit in? AJI: I think when reaching for that sort of need, I sort of fall into the camp of making a class method rather than using a scope. It feels a little less like extending some basic Rails functionality or implying that it's part of the inherent framework and makes it a little more like behavior that's been added that's specific to this domain. And I think that distinction comes into my thinking there. I'm sure there are other reasons. What are your thoughts there? Maybe it'll spark an idea for me. JOËL: For me, I think I also generally prefer to write them as class methods rather than using the scope keyword, even though they're more or less the same thing. What is interesting is that, in a way, they kind of feel like alternate constructors in that they don't give you an instance; they give you back a collection of instances back. So if we bend the rules a little bit...these are not hard and fast rules but the guidelines. If we bend the guidelines a little bit, they kind of fit under the general categories for best uses of class method that we discussed earlier. AJI: Yeah, I can definitely see that. I tend to think, or at least I think when you had first brought up the term of alternate constructors, my first thought was of one instance; you ask for a thing, and it gives you this thing back. But it's the same sort of idea with that collection because you're not getting just one instance; you're getting many instances. But it's the same kind of idea. You've asked the larger concept of the thing, the class, to give you back individuals of that class. So that totally falls in line with how I think about acceptable uses of these class methods the way that we've been talking about them. JOËL: Rails is something really interesting where a lot of the logic that pertains to a single item will live at the instance level. And then logic that pertains to a group of items will live at the class level. So you almost have like two categories of operations that you can run that semantically live either at the class or the instance level. Have you ever noticed that separation before? AJI: I think that separation feels natural to me because I came into programming through Rails. And I might have been colored in my thinking about this by the framework. The way that I conceptualize what a class is being sort of this blueprint or platonic ideal of what an individual might be and sort of describing the potential behaviors of such an individual. Having that kind of larger concept be able to work across multiple instances feels, yeah, it feels sort of natural. Like, if you were to think about this idea of a chair, then if you went in and modified what a chair is to mean, then any chair that you asked for later on would kind of come with that behavior along with it. Or if you ask for several chairs, they would all sort of have that idea. JOËL: I think similar to you; I had that outlook on that's almost like a natural structuring of things. And then, years ago, I got into the hot, new JavaScript framework that was Backbone.js. And it actually separates...it has like a model for individual instances, and then a separate kind of model thing for collections. And that kind of blew my mind. But what was interesting, then, is that you effectively have instance methods that can deal with all things collection-related, any sort of filtering, any sort of transformations. All of those are done, which you have an instance of a collection, basically, that you act on. And I guess if we were trying to translate that into Rails, that's almost like the concept of a query object. AJI: Hmm, it's sort of an interesting way to think about that. And Backbone, I feel like I did a day of that in bootcamp. But it has been some time, so I'm not sure that I've worked with that pattern specifically. But it does sort of bring up the idea of how much do you want to be in one model class? And do you want it to contain both of these concepts? If you have a lot of complex logic that is going to be dealing with a collection, rather than putting that in your model, I think I would probably reach for something like a service object that is going to be specifically doing that and sort of more along that Backboney approach maybe like a query object or something like that. JOËL: Interesting. When you use the term service object, do you mean something that's not a Rails model, just in general? Or are you talking specifically about one of these objects that can respond to call and is... I've heard them sometimes called Command objects or method objects. AJI: Yeah, that's an overloaded term certainly in the real space, isn't it? Service object, and what does that mean? I think generally, when I say it, I'm meaning just a plain, old Ruby object like something that is doing its one thing. You're going to use it to do its implementation details. They're all kind of hidden behind in private methods and return you something useful that you can then plug into what you were doing or what you need going on in some other place in your app. So it, to me, doesn't imply any specific implementation of, like, do you have call? Do you use it this way? Do you use it that way? But it's something that's kind of outside of it is either a model, a view, a controller, and it encapsulates some kind of behavior. So whether that, like we're saying, is a filtering or, you know, it's going to wrap that up. JOËL: I see. So, for you, a query object would be a service object. AJI: Yeah, I think so. You know, maybe this is one of the reasons why I generally don't like the overuse of the term service object in our space. I don't know if that's a hot take, and I'm going to get emails for this. But -- JOËL: Everybody send your angry tweets @Aji. AJI: Yeah, do it to @Aji on Twitter because I've been trying to get that three-letter handle for years. No, but if you want to talk to me, I'm @DoodlingDev. But, yeah, certainly, it does feel sometimes like an overloaded term, and I just want to go back to talking about plain, old Ruby objects. JOËL: So, service object is definitely an overloaded term. It's used for a lot of things. One thing that I've often seen it referring to are objects that respond to call. And just to keep away the confusion, maybe let's call them Command objects for the purposes of this conversation. AJI: Sounds good. JOËL: I commonly see them done where the implementation is done with a class method named call. Sometimes it delegates to an instance that also has call. Sometimes it's all implemented as a class method. How do you feel about that pattern? AJI: I don't mind the idea of a thing that responds to call. It, in a way, sort of implies that the class is sort of named as an action, which I don't like. It has an er name, and that kind of has a class named as a pattern. And that always sort of bugs me a little bit. But what I hope for when I open up one of those sorts of classes or objects is that it's going to delegate to an instance because then you're, again, picking up all of those wonderful benefits of the instance-level programming. JOËL: You keep mentioning the wonderful benefits of instance-level programming. What are some of those benefits? AJI: One of the ones that sort of strikes me most visibly or kind of viscerally when I see it is that they're very easy to understand. You can extract methods pretty easily that don't turn into kind of clumsy code of a bunch of different class methods that all have four arguments passed in because they're all operating on the same context. And when you're all operating on the same context, you have really a shared state. And if you're just passing that shared state around, it just gets super confusing. And you get into the order of your arguments, making a big impact on how you are interacting with these different things. And so I think that's sort of the first thing that comes to mind is just visually noisy, which for me is super hard to get my head around, like, well, how am I supposed to use this thing? Can I extend it? JOËL: Yeah, I would definitely say that if you have a group of class methods that all take, commonly, it's the first argument, the same piece of data and tries to operate on it, that's probably a code smell that points to the fact that these things want to be an instance that lives around it. This could be a form of primitive obsession if you're passing around, let's say, a hash, all of these, and maybe what you really want is to sort of reify that hash into an object. And then all these class methods that used to operate on the hash can now become instance methods on your richer domain object. AJI: Yeah. What do you say to the folks that come from maybe a more functional mindset or are kind of picking up on the wave of functional programming that's out there in the ethos that say that you've got a bunch of side effects when you don't have everything that your method is operating on, being passed on or passed in? JOËL: I think side effect is a broad term. You could refer to it as modifying the internal state of an object. Technically, mutation is a side effect. And then you have things like doing effects out in the outside world, like making an HTTP query, printing to the screen, things like that. I think those are probably two separate concepts. Functional programming is great. I love writing functional code. When you're writing Ruby, Ruby is primarily an object-oriented language with some functional aspects brought in. In my opinion, it's very, you know, a great combination of the two. I think they've gotten the balance well so that the two paradigms play nicely together rather than competing. But I think it's an object-oriented language first with some functional added in. And so you're not going to be, I mean, I guess you could; there is a way to write Ruby where everything is a lambda or where everything is a class method that is pure and takes in inputs. But that's not the idiomatic way to write Ruby. Generally, you're creating objects that have some state. That being said, if an object is mutating a lot of global state, that's going to become problematic. With regards to its internal state, though, because it is very much localized and it's private, nobody else gets to see it; in many ways, an object can mutate itself, and that chain stays pretty local. AJI: Yeah, absolutely. You've tripped onto another one of my favorite rabbit holes of idiomatic code, and, like, what does that mean, and why should we strive for that? But I absolutely agree that when Ruby is written to conform to other paradigms that aren't mostly object-oriented is when it starts to get hard to use. It starts to feel a little off. Maybe it has code smells around it. It's going to give me the heebie-jeebies, whatever that might mean for you or for different developers. I think we all have our things that are sort of this doesn't feel right. And you kind of dig into it, and you can sort of back that up. And whenever Ruby starts to look like something that isn't lots of little objects sending messages, is when I start to get a little on edge, maybe. JOËL: It is worth, I think, calling out the fact that Ruby is a very expressive language. And there are effectively many...you could call them dialects of it. You have sort of your pure sort of OO approach. You have what's typically written in Rails, which has some OO things. But Rails is also, in many ways, it's very DSL-heavy and, in some ways, very class method-heavy. So writing Rails is sort of its own twist on Ruby. And then, some people will try to completely retrofit a functional approach onto Ruby, and that's also a way that some people like to write their code. And some of these, you can't necessarily say they're not valid, but they're not what you'll mostly see in the wild. And they're not necessarily the approach that I would recommend. AJI: Yeah, that's the blessing, and the curse of both programming in general and such an expressive language like Ruby is that there are many different valid ways to do it. And what are your trade-offs going to be when you make those choices? I think that falls kind of smack dab into that idiomatic conversation. And it comes up for me, too, as a consultant because I try to tend towards that idiomatic, those common patterns and practices because I'm not going to live with this code forever. I need to hand this off. And the closer it is to what you might see out there in the wild more commonly, the easier it will be for the next Ruby developer to come pick it up and extend it. JOËL: So you'd mentioned earlier some of the benefits of instance programming. One of the things that I find is maybe a little bit weird when you go heavily into the class method approach is that there is only one instance of the class, and it is globally available. AJI: Are you talking about a singleton there? JOËL: Yes. And, in fact, your class is effectively a singleton, potentially with globally mutable state. I hope not, but potentially with all of the gotchas and warnings that that entails. And so, if you think of your user instance, you need a reference to it, and there can be multiple of them, and you can call methods on them. If everything is happening at the class level, there is a single user class in memory shared by anyone who wants to use it. It's globally accessible. You can all call methods on it. Yeah, in many ways, it does act like a singleton. AJI: And let's not even get into the Ruby chestnut of everything's an object. So it is an instance of a class in and of itself. JOËL: Yes. AJI: But, absolutely, it can start to act that way. But the singleton it's enshrined in the Gang of Four book of patterns. Like, so what's wrong about a singleton? I hope you can understand over the airwaves the devil's advocate that I'm playing here. [laughs] JOËL: Yes. There are little horns that have sprouted on your head right now. I think part of the problem with singletons is that, generally, they are globally accessible. There's the problem of global mutable state again. There was a time, I think, when the OO community went pretty wild with singletons, and people realized that this was not great. And so, over time, a consensus evolved that singletons are a pattern that, while useful, should be used rarely and in moderation. And a lot of warnings have been shared in the community, like, be careful not to overuse the singleton pattern or don't build your system out of singletons. And maybe that's what feels so weird about a system that's built primarily in terms of class methods for me is that it feels like it's built out of singletons. AJI: Yeah. When I think of object-oriented programming, I kind of fall back to maybe one of the ideals of it is that it represents the world more accurately or maybe more understandably. And that sort of idea doesn't fit that paradigm, does it? If you're a factory that is making widgets, there's not the one canonical widget that all of your customers are going to be talking to and using. They are going to each have their own individual widgets. And those customers can be thought of like the consumers of your methods, your objects. JOËL: The idea being the real-world thing you're simulating normally, there are multiple actors of every type rather than a single sort of generic one that stands in for everybody. AJI: If this singleton is going to be your interface or the way that you interact with each of these things that are conceptually different, like a user or something like that, then differentiating between which user becomes a lot harder to do. It takes a lot more setup and involved process in referring to this user when and that kind of thing and creating the little instances. Then you've got more kind of direct reference to a single concept, a single individual. JOËL: So what you've described is a very sort of classic OO mindset. You find the data and the behaviors that go together. You try to oftentimes simulate the world, model it in terms of actors that give and receive messages. In many ways, though, I think when you're building a system out of class methods, you're thinking about the world in an almost different paradigm. In many ways, it feels almost procedural. What are the behaviors that need to happen in my app? What are the things that need to be done? You'd mentioned earlier that oftentimes these classes or the methods on them will end up with E-R; they're all verbs. You have a thing-doer, a thing executor, thing manager. They all do things rather than having domain concepts extracted and pulled out. Would you say that that feels somewhat procedural to you as well? AJI: Yeah. I think a great way to divide it is the way that you have right there; it's these sorts of mindsets. Do you have collections of things that have behaviors, or do you have collections of behaviors that might refer to things? And where you're approaching the design of a system, either from that behavior side or from that object side, is going to be a different mindset. Procedural being more focused on that kind of behavior and telling it what to do rather than putting... I think this is probably a butchered Sandi Metz example, but putting your roommate who hates cats and a cat that doesn't want its tail stepped on in one room, and eventually, things will happen accordingly. And those two mindsets are going to end up with very different architectures, very different designs, very different ways of building these applications that we make. And, again, does that come back to...Ruby, potentially to a lesser extent but still in the same camp, is object-oriented language, and it sort of functions best when considered and then constructed in that mindset. And I often wonder sometimes if language developers and language designers make anti-patterns sort of purposefully awkward to use. Like, if you want to hide a lot of class methods, you can do the class shovels self version of things or have private_class_method littered all the way through your file. And it seems to me like that might be a little bit of a flag that, like, hey, you're working against the system here. You're trying to make it do a thing that it doesn't naturally want to do. JOËL: Yeah, because you'd mentioned this private_class method thing because, by default, it's hard to get class methods to be private. You have to use a special keyword. You can't just write private in the class and then assume that the methods below it are going to be private because that does not apply to class methods. AJI: Exactly. And that friction to making an object that has a smaller interface, that kind of hides its implementation, seems as though it's a purposeful way that Ruby itself was designed to maybe nudge us, developers, into a certain way of working or suggesting a certain mindset. JOËL: There's a classic Code Climate article titled Class Methods Resist Refactoring. And it mentions different ways that when you're relying heavily on class methods, it's harder to do some of the traditional refactors things like just extract method because it is clunkier because you can't have private methods as easily. You can't share state, so you have to thread variables through. I guess, technically, you can share state with things like class variables and class instance variables, but if you do that, you will probably be very sad. AJI: [laughs] Yeah, you're opening yourself up to a whole world of hurt there, aren't you? And, yeah, you're opening yourself up to a whole world of hurt there with that, aren't you? Sort of sharing data so dangerously around your app. JOËL: So I'm a big fan of test-driven development. And one of the things that TDD believes in is that test pain should help guide the design of your system and that, generally, things that are easier to test are better designed. AJI: Yeah. JOËL: It's often easier to test class methods because they are globally available singletons. I can easily stub a class. Whereas if I need to stub an instance, I need to do some uglier things like stub any instance of or stub the constructor to return a double, or do some other kind of dirty tricks like that. Does that mean that TDD would prefer a class method-based approach to writing code? AJI: I think that a surface-level reading of that might say that it does. And I think that maybe the first pass on things, if you're thinking about I want to get this thing done that's right in front of me right now and just move forward, might kind of imply that. But if you start to think about or have come back to something that was implemented in that way, anytime that sort of behavior is going to grow or change, then it's going to start to...the number of backflips that you have to do become a lot more complicated and a lot higher when you've got class methods. Because I find that, yes, you might have to stub out or pass in a created object or something like that. But if you've got a class method, especially if it is calling other class methods inside it, then all of a sudden, you have in your test this setup that looks completely unrelated to anything that you're running and testing, that you have to have all of this insight or knowledge of what those classes are doing just to set up your test framework before you can even run that. Another thing that is looked to as an axiom when writing tests that can imply this class approach is that you shouldn't change your code just for the test. If you're doing dependency injection or something like that, passing around little objects, then you're making your code more complicated to make your tests look a certain way. JOËL: That's interesting. So maybe I'm reacting to some test pain by trying to change my tests first. So I'm trying to deal with some collaborators, and it is tricky to do. And so I decide, well, the thing I want to do is I want to reach for stubbing. But then that's hard to do because it's instances. So in order to make already that compromise in my test work better, now I change the code to be nicer for the test to use mostly classes because those are global. Whereas maybe the correct path to take initially is, say, oh, there isn't test pain here because I'm trying to isolate an object from its collaborators. Maybe we need to pass an object in as an argument rather than hard coding it inside the class. AJI: Yeah, absolutely. JOËL: So I guess you follow the test pain, but maybe the problem is that you've already kind of gone down a path that might not be the best before you got to the point where you decided that you needed a class method. AJI: And I think that idea of following the test pain can be, again, there are only shades of gray; there is no black and white. It can be sort of taken in a lot of different ways. And the way that I think about it is that test pain is also sort of an early warning sign that there's going to be pain if you want to reuse this class or these behaviors somewhere else. And if it was useful somewhere, it's likely it's going to be useful in another place. And there are many different kinds of tests pain. The testing is a little easier with a class method because you're not stubbing out any instance of. You're just stubbing; really, what's the difference between stubbing out any instance of or stubbing out the class? Is that just a semantic difference? Is that -- JOËL: Because someone on the internet said that stubbing any instance of is bad. AJI: Ooh, right, the internet. I should have read that one. The thing that you can do with passing around instances or sending messages to instances as you do when you're calling a method is that you can easily swap in a different object if you need to stub it. It's similar to how you can change the implementation under the hood of an object and pass in an object that responds to the same messages and kind of keep moving forward with your duck typing. If you can go into your tests and pass it sort of an object that's always going to return a thing...because we're not testing what that does; we just need a certain response so that we can move forward with the pathway that is under test. You can do that in so many different ways. You could have FactoryBot, for instance, give you a certain shape of a thing. You can create a tiny, little class right there in your tests that does something specific, that can be easily understood what's going on under the hood here. And instead of having to potentially stub out or create all of these pathways that need to be followed that are overwriting logic that's happening in different class methods or different places otherwhere in the application, you can just pass in this one simplified thing to keep your tests sort of smaller and easier to wrap your head around all in just one go. JOËL: I think what I'm getting here is that when you design your code around instances, you're more likely to build it in a modular way where you pass objects to other objects. And when you build your code using class methods, you're more likely to write it in a hard-coded way. Because you have that globally available class, you just hard-code it and then call it directly rather than passing things in. And so things end up more coupled and, therefore, high coupling leads to more test pain. AJI: Yeah, I think you've really kind of hit on something here that the approach of using class methods is locking that class into kind of a single context or use case. Usually, it is this global thing that is this one way, and that's even kind of backed up by the fact that class methods are load-time logic instead of run-time logic. And it really kind of not only couples but it makes it more brittle and less amenable to kind of reuse. JOËL: That's a really interesting distinction. I often tend to think of runtime versus load time in terms of composition versus inheritance. Composition, you can combine objects together at runtime and get behaviors built on the fly as the code is executing, whereas inheritance sort of inherently freezes you into a particular combination of behaviors at the time of loading the code. It's something that the programmers set up, and so it is much less flexible. And that is one of the arguments why the Gang of Four patterns book recommends composition over inheritance in many situations is because of that runtime versus load time dichotomy. And I hadn't made that connection for class methods versus instance methods, but I think there's a parallel there. AJI: Yeah, absolutely. The composition versus inheritance thing, I think, goes very hand in hand with the conversation that we're having about putting your behavior on a class versus an instance because...and I don't know if this is again yielding my thoughts to 'the internet said' in that composition is preferable to inheritance. But without unpacking that right there, that is certainly something that I strive for as well. And while it might have, much like TDD, some kind of superficial, short-term complexity, it has long-term payoff in that flexibility and that reuse, and that extensibility, and all of those other buzzwords that we developers like to throw around. JOËL: So you've shared a lot of thoughts on the use of class methods. I think this could branch into so many other aspects of object-oriented design that we haven't looked at or that we could go deeper, things like TDD. We could look into how it works with the solid principles, all sorts of things. But I think the big takeaway for me is that class methods are very useful, but it's easy to use them as our single hammer to every problem being a nail. And it's good to diversify your toolset. And some tools are specialized; they're good to be used in very specific situations that don't come across very often, and others are used every day. And maybe class methods are the former. AJI: Absolutely. That hammer-and-nail metaphor was right where I was headed for too. Love it. JOËL: Well, thank you so much, Aji, for joining the conversation today. Where can people find you online? AJI: Yeah, anywhere you want to look for me: Instagram, GitHub, Twitter. I'm @DoodlingDev, so just send all your angry emails that way. JOËL: And with that, let's wrap up. The show notes for this episode can be found at bikeshed.fm. This show is produced and edited by Mandy Moore. If you enjoyed listening, one really easy way to support the show is to leave us a quick rating or even a review in iTunes. It really helps other folks find the show. If you have any feedback, you can reach us at @_bikeshed, or reach me at @joelquen on Twitter, or at hosts@bikeshed.fm via email. Thank you so much for listening to The Bike Shed, and we'll see you next week. Byeeeeee!!!!!!! ANNOUNCER: This podcast was brought to you by thoughtbot. thoughtbot is your expert design and development partner. Let's make your product and team a success.Support The Bike Shed
undefined
Oct 11, 2022 • 31min

357: Notetaking For Developers

Joël is joined by Amanda Beiner, a Senior Software Engineer at GitHub, who is known for her legendary well-organized notes. They talk about various types of notes: debugging, todos, mental stack, Zetelkasten/evergreen notes, notetaking apps and systems, and visual note-taking and diagramming too! @amandabeiner Mermaid.live Monodraw Zettlekasten Evergreen Notes Notion Obsidian Transcript: JOËL: Hello and welcome to another episode of The Bike Shed, a weekly podcast from your friends at thoughtbot about developing great software. I'm Joël Quenneville. And today, I'm joined by Amanda Beiner, a Senior Software Engineer at GitHub. AMANDA: Hey, Joël. Great to see you. JOËL: And together, we're here to share a little bit of what we've learned along the way. So, Amanda, what is new in your world? AMANDA: Well, one thing I'm really excited about is that my team at GitHub is experimenting with how we're going to incorporate learning and sharing what we've learned with each other in new ways, and I'm really excited to see where people take that. So, one of the things that we're thinking of is that we all get really busy, and we all have exciting projects that we're working on in the day-to-day, and sometimes it can be really hard to pull yourself away from them to do some learning that would be something that will probably help you in the long run. But every time we do do projects like that, people are really excited about it, and people like to collaborate. So we're just trying to figure out how we can make that a more regular thing because it's great for our whole team. JOËL: I love that. Do you have a project or something that you've been getting into recently to learn? AMANDA: Yeah. One of the things that I have been working on is that this is the first backend-focused role that I've had in my entire career. So I feel like I just kind of keep pulling back layers on how different forms of magic work. And I'm just trying to get closer to the metal of what is powering our databases. And that's something that I've been really excited to learn some more about. JOËL: So it's digging into a lot of, like, Postgres and just general database theory. AMANDA: Yeah. So for me, I've spent a lot of time at the Active Record layer as I have been settling into my role and figuring out what our domain models are that we care about. And I'm trying to get a little bit more into the questions of why did these tables end up looking the way that they do? Why are they normalized or denormalized where they are? And trying to get a better idea of the theory behind those decisions. JOËL: And this is a new team that you've joined. AMANDA: This is an existing team that I've joined a year ago now. JOËL: So it sounds like you're dealing with a somewhat unfamiliar codebase. You're looking at a bunch of existing models and database tables. That can be a lot to process and understand when you first join a team. Do you have an approach that you like to use when you're looking at unknown code for the first time? AMANDA: Yeah. I usually like to dive right in as much as I can, even if it's with a very small bug fix or something like that, something that allows me to just get my hands dirty from the beginning and poke around what models I'm dealing with, and maybe some of the adjacent ones that I don't need to know about now but might want to come back to later. JOËL: One thing that I find is really helpful for me are diagramming and note-taking. So if it's something like a database table or ActiveRecord models that I'm not familiar with, if it's more than maybe two or three, which is probably the most I can keep in my head, I have to start drawing some kind of like an entity-relationship diagram or maybe even just a bulleted list somewhere where it's like here are the things and how they connect to each other. Otherwise, I’m like, I don't know, I don't have enough RAM in my brain for that. AMANDA: That sounds like a really helpful approach. How do you approach creating these diagrams? JOËL: Occasionally, I will just draw it out by hand with pen and paper. But more recently, I've been using tools like Mermaid.js and specifically the website mermaid.live that allows you to just put in some names and arrows, and it will build out a diagram for you. And that's been really helpful to explore and understand what is going on with different entities that relate to each other. AMANDA: I've used Mermaid.js recently, and I really enjoyed it as well. I found that writing something that lets me write words or something somewhat like words and takes care of the drawing for me is probably best for everyone involved. JOËL: Yeah, that's a good point. It's kind of like Markdown, the ability to just write a little bit of text and move on and not worry about the size of boxes or the shape of the arrows or whatever. It helps you to really stay in that flow and keep moving. AMANDA: I definitely agree. I feel like I can't have been the only person that somehow ended up very deep into the Figma documentation because I didn't quite know how to do what I was supposed to do, and I forgot what I was trying to draw in the first place. JOËL: Right. It's really easy to put your designer hat on and want to make something like a beautiful diagram when this is really more of a capturing your state of mind. It's a rough note, not something you're necessarily going to publish. So, in addition to visuals, do you find yourself taking a lot of notes when you're exploring code or debugging code? AMANDA: Yeah. I feel like I tend to jot a lot of things down, maybe class names, maybe some links to PRs or issues, or anywhere that might have context about what I'm looking at and how it got in that way. At this point in the process, it feels my notes usually feel like a bit of a bullet point list that doesn't quite make sense to me yet but maybe will get some shaping later. JOËL: What kind of things do you tend to record in those notes? AMANDA: I think one of the things that I'm usually trying to get out of those notes is just a snapshot of what I'm trying to accomplish at the time that I'm creating them. What's the bug that I'm trying to solve, and how did I get into this rabbit hole? So that if it ends up being the wrong one, I can follow my breadcrumbs back out and start a different way. JOËL: Oh, that is really powerful. I love the imagery you used there of following breadcrumbs. And I feel like that's sometimes something I wish I had when I'm either exploring a particular code path or trying to find a bug. And at some point, I've gone a pretty long path, and I need to back up. And I don't remember exactly where I was or how I got to this point, especially if I've gone down a path, backtracked a little bit, gone down a different path, backtracked, gone further down a third path. And so having breadcrumbs, I think, is a really valuable thing that I wish I did more when I was debugging. AMANDA: Yeah. And one of the most helpful breadcrumbs that I found is just a list of questions. What was the question that I was trying to answer when I opened this file or looked at this method, and did it help me solve that question or answer that question? And if the answer is no, then I can refer back to what the question was and try to think about what else might help me solve that question. JOËL: I also love that. It's really easy to get sidetracked by other questions or other ideas when exploring or debugging. And sometimes I find that half hour later, I haven't answered the original question I came here to answer, and I kind of haven't even tried. And so, maybe writing down my questions before I go down a path would help me stay more focused during a debugging session rather than just trying to keep it all in my head. AMANDA: I very much relate to getting nerd sniped by something that looks interesting but ultimately doesn't solve the original problem that you were trying to. JOËL: This even happens to me when I'm pair programming. And so we'll say out loud the question we're trying to answer is this; let's open this file. And then you go into it, and you're like, oh, now that is an unusual line of code right there. AMANDA: [laughs] JOËL: I wonder why they're doing that. Let me check the git blame on this line. Oh, it's from 2015? AMANDA: [laughs] JOËL: I wonder what was happening there. Was that part of a Rails upgrade? And then, at some point, the other person has to interject and be like, "That's all fascinating, but I think the question we're actually trying to answer is..." and we get back on track. AMANDA: I feel like that's a really good opportunity, maybe for a different kind of note of just interesting curiosities in a given codebase. I find that one of the skills that I'm trying to get better at is, rather than building a repository of information or answers to questions, just building a mental map of where the information I'm trying to find lives so that when someone asks me a question or when I have to solve something I don't necessarily know the answer, but I just know the resource to find that will point me in the direction of that answer. And I feel like those kinds of explorations are really helpful for building out that mental model, even if it may be at the time seems like an unrelated rabbit hole. JOËL: So this kind of note is a bit more permanent than a bread crumb style note would be. AMANDA: Yeah, maybe. And I guess maybe it's less of a note, and it feels kind of like an index. JOËL: Hmmm. AMANDA: Like something that's connecting other pieces of information. JOËL: That's really interesting. It's got me thinking about the fact that note-taking can be very different in different situations and for different purposes. So we've talked a little bit about debugging. I think we've mixed debugging and exploration. Maybe those two are not the same, and you treat notes differently. Actually, do you treat those two as different, or do you have different approaches to note-taking when you're exploring a new codebase versus debugging a particular problem? AMANDA: I think that those kinds of notes could probably be a little bit different because I think when I'm onboarding onto a new codebase, I'm trying to cast a pretty wide net and give some overall information about what these things do that by the time I'm very deep in debugging, it might be information that I already know very well. So I feel like maybe debugging notes are a little bit more procedural. They are a little bit more I did X, and I did Y, and I did Z, and these were the questions. And the introductory notes to a new codebase might be more along the lines of this is what this model does, and stuff that will eventually become second nature and might be useful to pass off to someone else who's onboarding but which I might myself not refer back to after a certain amount of time. JOËL: I see. That's an interesting point because not only might the type of notes you take be different in different scenarios, but even their lifespan could be different. The value of a debugging note, that sort of breadcrumbs, might really only be that useful for a few hours or a couple of days. I can imagine notes you're taking while you're exploring a codebase those might be helpful for a much longer period and, as you said, maybe in passing them on to someone else when they're joining a team. AMANDA: So that makes me think of whether the debugging notes should be as short-lived as I'm making them sound because I feel like there are times where you know you've debugged something previously, but you didn't keep the notes. Maybe they were just on a scrap of paper, and now they're gone. And I feel like I'd like to do a better job of digesting those notes a little bit better and eventually turning them into something that can be a little bit longer-lived. JOËL: That's fair. I find that, especially for debugging, I like to capture a lot of what was in my notes in the eventual commit message for the fix. Of course, my random breadcrumbs probably don't make sense in the commit message, but a lot of what I have learned along the way often is helpful. AMANDA: That's a really good point. I hadn't thought of commit messages as notes, but you're right; they totally are. JOËL: One thing I've done is I've sort of taken this idea to the extreme. I was debugging some weird database table ActiveRecord model interactions, and the modeling was just a little bit unusual. There were multiple sources of truth in the relationships. And there were enough models that I struggled to really understand what was going on. And so I drew an entity-relationship diagram. And I felt that that was important to understand for people reviewing the code but also anybody looking back at the commit later on. So I used a tool called Monodraw, which allows you to draw simple diagrams as ASCII art. And so, I have a little ASCII art ERD in my commit message. AMANDA: That's incredible. I feel like if I were a developer git logging and I saw that commit message, I would be both thrilled and terrified of what exactly I was diving into in the git blame. [laughs] JOËL: Definitely both, definitely both. But I have referred back maybe a few months later. Like you said, I had to refer back to that commit because a similar bug had cropped up somewhere else. And I knew that that commit had information that I had gathered that would make the debugging experience easier. AMANDA: I guess the commit message is a really good example of having a note that's very closely tied to its context. Like, it's in the context of like a commit, which is a set of changes at a point in time, and it's really well situated in there. What do you think about the trade-offs of having that as part of a commit message versus something like some other sort of documentation where something like that could live? JOËL: I guess it depends on how you think you're going to use it in the future. Again, for debugging things, it feels like you don't often need to refer back to them, so I don't think you would want to just dump that on a wiki somewhere. It probably makes sense to have that either in just a collection of debugging notes that you have or that you could then dig into if you needed or in a commit message, something like that. But maybe some of the things that you learned along the way could be pulled out and turned into something that lives somewhere else that's maybe less of a note at that point and more of a publication. AMANDA: That sounds like a fine line between note and publication. JOËL: Perhaps it's an artificial line that I'm making. AMANDA: [laughs] JOËL: But yeah, I guess the idea is that sometimes I will look at my own debugging notes and try to turn them into something like either a wiki page for a particular codebase or potentially even a blog post on the thoughtbot blog, something that I've been able to synthesize out of the notes there. But now you've kind of gone a few steps beyond the underlying raw notes. AMANDA: I'm interested in your thoughts on that synthesis of notes into how does something go from a commit message into a blog? What does that process look like for you? JOËL: I have a personal note-taking system that's loosely inspired by a system called Zettelkasten and also another similar system called evergreen notes. The idea is that when you learn things, you capture small atomic notes, so they are an idea in your note-taking application, and then you connect them. You create links between notes. And the idea is that there's a lot of value in making connections between notes that's almost as much part of the knowledge-creating experience as capturing single notes on their own. And as you capture a bunch of these little, tiny notes over time and they become very interconnected, then you can start seeing, oh, this note from this one experience, this note from this conference talk, and this note from this book all connect together. And they maybe even make connections I hadn't seen, or I hadn't thought of individually in those moments. But now I see that they all kind of come together with a theme. And I might then combine those together to make a blog post or to use as the foundation for a conference talk. AMANDA: That's really interesting. I like the concept of being able to capture bits of information at the time that they felt relevant without having to have an entire thesis in this note. Or that idea doesn't have to be fully fleshed out; it can become fleshed out later when you connect the dots. JOËL: That's a really powerful concept. One of the big ideas that I picked up through this was that there are always byproducts of knowledge creation. So if I'm writing a blog post, there are always some things that I cut that didn't make it into the blog post because I'm trying to keep it focused. But those are still things that I learned, things that are valuable, that could be used for something else. And so anytime I'm writing a blog post, preparing for a conference talk, learning some things in a debugging session by reading a book, there are always some things that I don't use necessarily immediately. But I can capture those little chunks, and eventually, I have enough of them that I can combine them together to make some kind of other work. AMANDA: I'm really curious about your process of creating those notes. If you're reading a blog post, say, to learn a new topic and you're taking notes on that, how do you go from this concept that you're learning in the blog post to these really focused notes that can be combined in other ways? JOËL: So the Zettelkasten approach suggests that you have two forms of notes, one it calls literature notes which are just sort of ideas you jot down as you are reading some work. You're reading a book or a blog post or watching a talk, and then, later on, you go and turn those into those atomic-separated, linked notes together, what Zettelkasten calls permanent notes. And so I'll often do that is just focus on the work itself and jot down some notes and then convert those later on into these smaller atomic chunks. AMANDA: That concept of taking a larger theme and then actually spending the energy to distill that into a different kind of artifact that might be helpful later on is really interesting. And I don't do Zettelkasten note-taking, but I've also found that to be useful in other contexts as well. JOËL: One thing that I sort of hold myself to is when I am writing those atomic notes is, I don't write them as bullet points. They're always written in prose and complete sentences. The title is usually a sort of thesis statement, a thing that I think is true or at least a thing that posits that could be true, and then a short paragraph expanding on that idea which I think helps cement a lot of information in my mind but also helps to give me little chunks of things that I can more or less copy-paste into an article and already have almost a rough draft of something I want to say. Do you find that when you synthesize ideas into notes that you do something similar, or do you stick mostly to bullet points? AMANDA: I think I might do a mixture of the two. I think procedurally, I use bullet points a lot, but I think those bullet points tend to be full sentences or several sentences together. I've definitely run up against some of the drawbacks of terseness, where they're less helpful when you refer back to it later. But I do like the visual cues that come with things like bullet points, or numbered lists, or even emoji and note-taking to be a visual cue of what I was thinking of or where I can find this later on. JOËL: I love emoji; emoji is great. AMANDA: I guess actually I've started using emoji as bullet points. That's something that I've found even to be helpful just with remembering or with grouping things thematically in my mind. And when I'm going back through my notes, I find it easier to find the information that I was looking for because it had a list, or an emoji, or an image, or something like that. JOËL: Yeah, that makes it really easy to scan and pick out the things that you're looking for. It's almost like adding metadata to your notes. AMANDA: Totally. JOËL: That's a great tip. I should do that. AMANDA: You can definitely run into the Figma problem of you then spend so much time finding the right emoji to be the bullet point that you forgot what you were doing, [laughs] but that's a problem for a different day. JOËL: So this sort of Zettelkasten evergreen notes approach is a system that I use specifically to help me capture long-term thoughts about software that could eventually turn into content. So this is very much not a debugging note. It's not an exploratory note. It has a very particular purpose, which is why I write it in this particular way. I'm curious; I know you have a lot of different systems that you use for your notes, Amanda. Is there one that you'd like to share with the audience? Maybe tell us a little bit about what the system is and why it's a good fit for the type of scenario that you'd like to use it in. AMANDA: Sure. One situation that I found myself in recently is I have started taking classes on things that I'm interested in, development-related and non-development-related. And that's a formal structure that requires some note-taking that I haven't really done since I was in school. And the tools were very different back then as to what we had available to us for note-taking. It was basically Microsoft Word or bust. So I have found myself having to develop a new note system for that kind of content delivery method, basically of watching a video and taking notes and having something that then makes sense outside of the context of sitting down and watching a video. So that has been a little bit of a process journey that I've been on the last couple of months. JOËL: And what does your note-taking system look like? AMANDA: So it's been a mix of things, actually. I started out just kind of brain-dumping as I went along with the instructor talking trying to type and keep up. And I found that very not scannable to look back on. I was looking for some more visual cues, and I didn't really have time to insert those visual cues as I was trying to keep up with a lecture essentially. I then transitioned back to old school pen and paper, like, got myself a notebook and started writing in it. And obviously, that has some benefits of the free-formness, like, I'm not constrained by the offerings of any specific tool. But the trade-off for that is always that you have different notebooks for everything. And it's like, where's my X class notebook? And so I've been trying to bring those two methods together into something that makes a little bit more sense for me and also bring in some of that synthesis process that you were talking about with your note-taking method of doing the full literature notes and then synthesizing them down into something a little bit more well-scoped for the particular piece of information. JOËL: So you have like a two-step process then. AMANDA: It did end up being a two-step process because one of the things that I found was the grouping of ideas that make sense when you're first learning a concept and the grouping of ideas that make sense when I'm revisiting that concept, later on, aren't necessarily the same. And so, keeping it in the original context doesn't necessarily help me recall the information when I'm referring back to my notes. JOËL: That's really interesting. When you're writing it, it's going to be different than when you're reading it. So we've been talking a lot about the purpose of different notes along the way, and you mentioned the word recall here. Do you use these notes mostly as a way to recall things that you would look back at them and try to remember, or are they more of a way to digest the material as you're going through it? AMANDA: I think at the time that I'm writing them, they definitely served the purpose of helping me digest the information. But at some point, I probably want to be able to look back at them and remember the things that I learned and see if maybe they have new salience now that I have sat on them for a little bit. JOËL: Hmmm, that's good. So it's valuable for both in different contexts. AMANDA: Yeah, definitely. And one of the more surprising things that I've learned through that process has been that when I'm learning something, I really like a chronological kind of step-by-step through that process and building blocks of complexity that basically go one on top of the other. But then, once I've kind of made it to the end when I look back on it, I look back on those notes, and they're usually pretty thorough. They probably have a lot of details that aren't going to be top-level priority at the end. But I didn't really have that concept of priority when I was first learning it. I was kind of grasping onto each bit of information, saying, "I'm going to scroll this away in case I need it later." And then when you get a better understanding of the full picture, you realize, okay, I'm glad that I know that, but it's not necessarily something that I'd want to look back on. So I really like having systems that then allow me to regroup that information once I have built out a fuller picture of what it is I'm trying to learn. JOËL: Interesting. So the sort of digesting step that happens afterwards or the synthesis step, a lot of the value that you're adding there is by putting structure on a lot of the information you captured. AMANDA: Yeah, I think putting structure and changing the structure, and not being afraid to change that structure to fit my new understanding in how I see this concept now instead of just how this concept was explained to me. JOËL: So you mentioned that you'd initially used notebooks and paper and that that felt a little bit constraining in terms of organization. Is there any kind of software or apps that you like to use to organize your notes, and how do they fit in with your approach to note-taking? AMANDA: I've been using Notion for the last few years. I found that that application works well with my visual preferences for note-taking. I think there's a lot of opportunity for visual cues that help me recall things, such as emoji and bullet points. And I like that I can do all of that by writing Markdown without then also having to read Markdown. JOËL: Yeah, I definitely agree that a little visual change there where you can actually see the rendered Markdown is a nice quality-of-life improvement. AMANDA: Absolutely. And I also think that the way that it turns Markdown into blocks that then you can rearrange has served me really well for that synthesis process of maybe this bullet point makes sense, and I want to keep it as is. But I want to rearrange it into these new themes that I'm seeing as I'm reviewing these things that I've learned. JOËL: That's fascinating. So it has some really good tools for evolving your notes and reorganizing them, it sounds like. AMANDA: I like that I can group my notes by concept, and notes can be subsets or sub-notes of other notes. And you can kind of move the individual notes in between those blocks pretty easily, which helps me rearrange things when I see different themes evolving. JOËL: I've heard really good things about Notion, but I've not tried it myself. My app of choice so far has been Obsidian, which I really appreciate its focus on linking between notes. It doesn't have this concept of blocks where you can embed parts of notes as notes into other notes and things like that. But that has been okay for me because I keep my notes very small and atomic. But the focus on hyperlinking between notes has been really useful for me because, in my approach, it's all about the connections. AMANDA: So, what does that process look like when you are referring back to all of these notes that are hyperlinked together? JOËL: That's actually really important because the recall aspect is a big part of how you would use a note-taking system. For me, it's sort of like walking the graph. So I'll use search, or maybe I know a note that's in the general theme of what I care about, and then I'll just follow the links to other related articles or notes that talk about things that are related to it. And I might walk that graph three, four steps out in a few different directions. It's kind of like surfing Wikipedia. You find some entry point, and then you just follow the links until you have the material that you're interested in. AMANDA: It sounds like creating a Wikipedia wormhole of your own. JOËL: It kind of is. I guess, in a way, it's kind of like a little mini personal wiki where the articles are very, very condensed because I have that limitation that everything must be atomic. Wow. So this has been a really fascinating conversation. I feel like one of the big takeaways that I have is that types of notes matter. Note-taking can take very different forms in different contexts. And the way you organize them would be vastly different; how long you care about them is also going to be different. So going into a particular situation, knowing what sort of situation is this that I'm using notes and what is their purpose is going to be really helpful to think in terms of how I want to do my note-keeping. Whereas I think previously, I probably was just like, yeah, notes. You open a document, and you put in some bullet points. AMANDA: I am definitely guilty of doing that as well. And I like the idea of having a purpose for your notes. You mentioned your purpose was ultimately to build a map that would produce content. And I really like how you have found a system that works really well for that purpose. And I'm going to keep thinking about how to be more intentional in what is the purpose of the notes that I'm taking in the future. JOËL: Well, thank you so much for joining the conversation today. Where can people find you on the web? AMANDA: Thanks so much for having me, Joël. You can find me @amandabeiner on Twitter. JOËL: And we'll link to that in the show notes. And with that, let's wrap up. The show notes for this episode can be found at bikeshed.fm. This show is produced and edited by Mandy Moore. If you enjoyed listening, one really easy way to support the show is to leave us a quick rating or even a review in iTunes. It really helps other folks find the show. If you have any feedback, you can reach us at @_bikeshed, or reach me at @joelquen on Twitter, or at hosts@bikeshed.fm via email. Thank you so much for listening to The Bike Shed, and we'll see you next week. Byeeeeeeee!!!!!!! ANNOUNCER: This podcast was brought to you by thoughtbot. thoughtbot is your expert design and development partner. Let's make your product and team a success.Support The Bike Shed
undefined
Sep 27, 2022 • 39min

356: The Value of Specialized Vocabulary

Guest and fellow thoughtbotter Stephanie Minn and Joël chat about how the idea of specialized vocabulary came up during a discussion of the Ruby Science book. We have all these names for code smells and refactors. Before knowing these names, we often have a vague sense of the ideas but having a name makes them more real. They also give us ways to talk precisely about what we mean. However, there is a downside since not everyone is familiar with the jargon. This episode is brought to you by Airbrake. Visit Frictionless error monitoring and performance insight for your app stack. Stephanie's previous talk Non Violent Communication RubyConfMini Ruby Science book Connascence as a vocabulary to discuss coupling Wired series "5 levels of teaching" Transcript: JOËL: Hello and welcome to another episode of The Bike Shed, a weekly podcast from your friends at thoughtbot about developing great software. I'm Joël Quenneville. And today, I'm joined with fellow thoughtboter Stephanie Minn. STEPHANIE: Hey, Joël. JOËL: And together, we're here to share a little bit of what we've learned along the way. Stephanie, what is new in your world? STEPHANIE: Thanks for asking. I am on a new project I just started a few weeks ago, and I'm feeling the new project vibes, I think, kind of scoping out what's going on with the client with the work that they're doing. Trying to make a good impression. I think lately I've been in that mode of where can I find some work to do even when I'm just getting on boarded and learning the domain, trying to make those README updates in the areas that are a bit outdated, and yeah, just kind of along for the ride. One thing that has been surprising already is that in my second week, the project pivoted into a different direction than what I was expecting. So that has been kind of exciting and also pretty interesting to see sometimes this stuff happens. I was brought on thinking that we were working on rebuilding the front end in React and TypeScript, pulling out pieces of their 15-year-old Rails monolith. So that was kind of one area that they decided to start with. But recently, they actually decided to pivot to just revamping the look of the existing pages in the Rails app using the same templates and forms. So it's kind of shifted from more greenfield-esque work to figuring out what the heck's going on in this legacy codebase and making it a little bit more modern-looking and cleaning out the cobwebs, I suppose as we find them. JOËL: As a consultant, how do you deal with that kind of dramatic shift in expectations? STEPHANIE: I think it's tough because I necessarily wasn't in those conversations, and so I have to come at it with the understanding that they have a deep knowledge of the business and things that are going on behind the scenes that I don't, and I am coming in kind of with a fresh set of eyes. And it definitely raises some questions for me, right? Like, why now? What were the trade-offs that were made in the decisions? And I hope that as a consultant, I can poke and prod a little bit to help them with the transition and also figuring out its impact on the rest of the team in a way maybe someone who is more familiar with the situation and more tied to the politics of the org might not have that perspective. JOËL: I have a lot of questions here. But actually, I'm thinking that onboarding as a topic would probably make a good standalone episode. So maybe we'll have to bring you back for a future episode to talk about how to onboard well and how to deal with surprises. STEPHANIE: Yeah, I think that's a great idea. What about you, Joël? What's going on in your world? JOËL: I'm doing an integration with a third-party gem, and I am really struggling. And I've gotten to the point where I'm reading through the source of the gem to try to figure out some weird errors, some things that come up that are not documented. I think that's a really valuable skill. Ideally, you're not having to bring it out too often, but when you do, being able to drop into the code can really help unblock you or at least make some amount of progress. STEPHANIE: Are you having to dig into the gem's code because you weren't able to find what you needed from the documentation? JOËL: That's correct. I'm getting some cryptic errors where the gem is crashing, and I'm finding some lines in my logs that point back to the gem. And now I'm trying to reconstruct what is happening. Why is it not behaving the way it should be based on the documentation that I read? STEPHANIE: Oh, that's tough. Getting into gem code is uncharted territory. JOËL: It's nice when you're working with an open-source gem because the source is there, and you can just follow the stack trace and go through the code. Sometimes it's long and tedious, but it generally gives you results. It definitely is intimidating. STEPHANIE: Yeah. When you're facing this kind of problem where you have no idea where the light at the end of the tunnel might be, how long do you spend with it? At what point do you take away with what you've learned and try to figure out a different approach? JOËL: That's a good observation because digging through the source of a gem can definitely be a time sink. I think how much time I want to budget depends on a variety of other factors. How big of a problem is this? If I can't figure it out through reading the source, do I have alternate approaches to debug the problem, such as asking for help, opening an issue, reaching out to somebody else who's used it, complaining about it on The Bike Shed and hoping someone responds with an answer? There are other options that I can do that might leave me blocked but maybe eventually give me results. The advantage with reading the source is that you're at least feeling like you're making progress. STEPHANIE: Nice. Well, I wish you luck on that journey. [laughs] It sounds pretty tough. I'm sure that you'll get to one of those solutions and figure out how to get unblocked. JOËL: I hope so. I'm pursuing a few strategies in tandem. So I've asked for help, but I'm also reading the source code. And between the two of those, I hope I'll get to a good solution. So, Stephanie, last time you were on the show, you talked about your experience creating talk proposals for RubyConf. Have you heard back from them since then? STEPHANIE: I have. I will be speaking at RubyConf Mini this year. And I'm really excited because this will be my first IRL conference talk. So last time, I recorded my talk for RubyConf, and this time I will be up on a stage in front of real people. JOËL: That's really exciting. Congratulations. STEPHANIE: Thanks. JOËL: What is the topic of your talk? STEPHANIE: I will be talking about pair programming and specifically pair programming through the lens of a framework called Nonviolent Communication, which is a framework I learned about through a friend who recommended the canonical book on it. And it's a self-help book, to be totally frank, but I found it so illuminating. It really changed how I communicated in my relationships in my personal life. And the more time I spent with it, the more I realized that it would be a great application in pair programming because it's so collaborative and intimate. I've experienced the highs and lows of pair programming. You can feel so good when you are super connected with your pair. You make a lot of progress. You meet whatever professional goals that you might be meeting, and you have someone along for the ride the whole time. It can be just so rewarding. But it can also be really challenging when you are having more of those interpersonal conflicts, and navigating that can be tough. And so I'm really excited to share this style of communication that might help others who want to take their pair programming to the next level and get the most out of that experience no matter who they're pairing with. JOËL: I'm excited to hear this talk because pair programming has always been an important part of what we do at thoughtbot. And I think now that we're remote, we do a lot of remote pair programming. And the interpersonal interactions are a little bit different there than when you're physically in a room with each other, or you have to maybe pay a little bit more attention to them. I'm really excited to hear that. I think that's going to be really useful, not just for me but for a lot of the audience who are there. STEPHANIE: Thanks. Joël, you have a talk accepted at RubyConf Mini too. JOËL: Yes, I also had a talk accepted titled Teaching Ruby to Count. And it's going to be all about series, enumerators, enumerables, and ranges in Ruby and the cool things that you can do with them. So I'm really excited to share about that. I've done some deep dives on these topics, and I'm excited to share that with the broader Ruby community. STEPHANIE: Nice. I'm really excited to hear more about it. JOËL: Did you submit more than one proposal this year? STEPHANIE: This year, I didn't. But I would love to get to a point where I have a lot of content on the backburner and can pull it out when CFP season rolls around to just have some more options. Yeah, I have all these ideas in my head. I think we talked about how we come up with content in our last episode. But yeah, having a content bank sounds really nice for the future, so maybe when that season rolls around, it is a lot easier to get the ball rolling on submitting. What about you? Did you submit more than one? JOËL: I submitted two, but this is the one I was most excited about. I think the other idea was maybe a little bit more polished, but this one was a newer one I came up with towards the end of the CFP period. And I was like, ooh, I'm excited about this. I've just done a deep dive on enumerators, and I think there are some cool things to share with the community. And so that's what I'm excited to share about, and maybe that came through the proposal because that is what the committee picked. So I'm super happy to be talking about that. STEPHANIE: Nice. Yeah, I was just thinking the same, that your excitement about it was probably palpable to the committee. JOËL: For any of our viewers who are interested in coming to watch the talks by Stephanie and myself and plenty of other gifted speakers, this will be at RubyConf Mini in Providence, Rhode Island, from November 15th to 17th. And if you can't make it in person, the videos will be published online early in 2023. And we'll definitely share the links to that when they come out. So as we mentioned in your last episode, thoughtbot has a book club where we've been discussing the book Ruby Science, which goes through a lot of code smells and talks about some various refactoring patterns that can be used to fix them. Most recently, we looked at a code smell that has a very evocative name; it's called shotgun surgery. STEPHANIE: Yeah, it's a very visceral name for sure. I think that is what prompted this next topic that we're about to discuss because someone in the book club, another thoughtboter, mentioned that they were learning this term for the first time. But it made a lot of sense to them because they had experienced shotgun surgery and didn't have the term for it previously. Joël, do you mind giving the listeners a recap of what shotgun surgery is? JOËL: So shotgun surgery is when in order to make a change to a codebase, you have to make a bunch of little changes in a lot of different files, a lot of different locations. And that means that all of these little pieces are weirdly coupled to each other in a way that to make one change, you have to make a bunch of little changes in a lot of places. And that results in a very high churn diff, and that's a common symptom of this problem. STEPHANIE: Nice. Thanks for explaining. MID-ROLL AD: Debugging errors can be a developer’s worst nightmare...but it doesn’t have to be. Airbrake is an award-winning error monitoring, performance, and deployment tracking tool created by developers for developers that can actually help cut your debugging time in half. So why do developers love Airbrake? It has all of the information that web developers need to monitor their application - including error management, performance insights, and deploy tracking! Airbrake’s debugging tool catches all of your project errors, intelligently groups them, and points you to the issue in the code so you can quickly fix the bug before customers are impacted. In addition to stellar error monitoring, Airbrake’s lightweight APM helps developers to track the performance and availability of their application through metrics like HTTP requests, response times, error occurrences, and user satisfaction. Finally, Airbrake Deploy Tracking helps developers track trends, fix bad deploys, and improve code quality. Since 2008, Airbrake has been a staple in the Ruby community and has grown to cover all major programming languages. Airbrake seamlessly integrates with your favorite apps to include modern features like single sign-on and SDK-based installation. From testing to production, Airbrake notifiers have your back. Your time is valuable, so why waste it combing through logs, waiting for user reports, or retrofitting other tools to monitor your application? You literally have nothing to lose. Head on over to airbrake.io/try/bikeshed to create your FREE developer account today! STEPHANIE: I think I came away from that conversation thinking about the idea of learning new terms, especially technical ones, and the power that learning those terms can give you as a developer, especially when you're communicating with other people on your team. JOËL: So you mentioned the value in communication there. Some terms have a very precise meaning, and so that allows you to communicate a very specific idea. How do you balance having some jargon and some terminology that allows you to speak very precisely versus having to learn all the terms? Because the more narrow the term is, the more terms you need to talk about all the different things. STEPHANIE: That's a great question. I don't know if I have a great answer because I think I'm still on my journey. I have always noticed when developers I work with have that really precise, articulate technical vocabulary, probably because I don't. I am constantly referring to functions or classes as things, like, that thingy over there talks to this thing over here, and then does something. [laughs] And I think it's because I maybe didn't always have that exposure to very precise technical vocabulary. And so I had an understanding of how things worked in my head, but I couldn't necessarily map that to words. And I'm also from California, so, I don't know, maybe some of that is showing through a little bit. [laughs] But I've been trying to incorporate more technical terms when I speak and also in written form, too, such as in code review, because I want to be able to communicate more clearly my intentions and leave less room for ambiguity. Sometimes I've noticed when you do speak more casually about code, turns out other people can interpret it in different ways. And if you are using, like you said, that narrower technical term for it, that leaves less room for misunderstanding. But in the same vein, I think a lot of people are like me, where they might not know the technical terms for things. And when you start using a lot of jargon like that, it can be a bit exclusive to folks earlier in their career, especially since software as an industry we have folks from all different backgrounds. We don't necessarily have the expectation of assured formal training. We want to be inclusive of people who came to this career from different places and make sure that we are speaking the same language. And so it's been top of mind for me thinking about how we can balance those two things. I don't know, what do you think? JOËL: I want to speak to some of the value of precision first because I think there are a few different points. We have the value of precision, then we have the difficulty of learning vocabulary, and how are we inclusive of everyone. But on the topic of precision, I don't know if you saw not too long ago, another fellow thoughtboter, Matheus Sales, published an article on the thoughtbot blog about the concept of connascence. And he introduces this as a new set of vocabulary, not vocabulary that he's created but a vocabulary that is out there that not that many developers are aware of for different ways to talk about coupling. So it's easy in a pull request to just say, "Oh, well, that thing looks coupled. How about this other way?" And then I respond, "Well, that's also coupled in a different way." And then we just go back and forth like, "Well, mine is more coupled than yours is," or whatever. And connascence provides a more precise, narrow vocabulary to talk about the different ways that things are coupled and which ones are more coupled than others. And so it allows us to break out of maybe those unproductive discussions because now we can talk about things in a more granular way. STEPHANIE: Yeah, I loved that blog post. It was really exciting for me to pick up a new term to describe something that I had experienced, or seen in codebases, or felt the pain of, and be able to describe it more accurately. I'm curious, Joël, if you were to use that term next time, how would you make sure that folks also have the same level of familiarity with it? JOËL: I think on a pull request, I would link to Matheus' article depending on...I might give a little bit of context in a comment. So I might say something like, "This area here is coupled. Here's a suggested refactor. It's also coupled but in a different way. It's because we've moved up this hierarchy of connascence from, you know, connascence of names to some other form" (I don't have them all memorized.) and then link to the article. And hopefully, that becomes the start of a productive discussion. But yeah, having the resources you can link to people is great. And that's one of the nice things about text communication on a pull request is that you can just link to external resources that people can find helpful. STEPHANIE: To continue talking about the value of precision and specialized vocabulary, Joël, I think you are a very articulate communicator. And I'm curious from your perspective if you have always been this way, if you've always wanted to collect technical terms to describe exactly what you want to convey, or if this was a bit of a journey for you to get to this level of clear communication in your technical speaking and writing. JOËL: It's definitely been a journey. I think there are sort of two components to this; one is being able to communicate clearly to others; make sure that they understand what you're talking about. So for that, it's really important to be able to put yourself in somebody else's shoes. So when I'm building a conference talk or writing up a blog post, I will try to read it or go through my slide deck and try to pretend that I am the audience. And then I ask myself the questions: where do I get confused? Where am I going to have questions? Maybe even where am I going to roll my eyes a little bit and be like, eh, I didn't agree with that leap of logic there; where are you going? And then shift back in author mode and say, how can I address these? How can I make my content speak to you in an area where maybe you disagreed, or you were confused? So I kind of jump between moving from the audience seat to back to the author and try to make that material as much as possible resonate with those people. STEPHANIE: Do you do that in more real-time communication, such as in meetings or in pairing? JOËL: I think that's a little bit harder to do. And then it's maybe a little bit more of asking directly, either pausing to let people interject, or you can ask the question directly and say, "Are you familiar with this term?" That can also sometimes be tricky to manage because you don't want to make it sound like you think they don't know anything. But you can also make it sound really natural in a conversation where you're like, "Oh, we're going to do this thing with a strategy pattern. Have you seen a strategy pattern before? Are you familiar with this? Great, let's keep moving." And if not, maybe it's like, "Hey, let's take a few minutes to talk about what the strategy pattern means." STEPHANIE: I think you are really great at asking the audience about their level of familiarity with the content, especially in book club. I have definitely experienced just as a developer pairing, or in meetings, or whatnot times when people don't pause and ask. And usually, I have to muster up the courage to interrupt and ask, "Hey, what is X, Y, and Z?" And that is tough sometimes. I am certainly comfortable with it in a space where there is trust developed in terms of I don't feel worried that people might question my level of familiarity or experience. And I can very enthusiastically say, "Hey, I don't know what this means. Could you please explain it?" But sometimes it can be a little tough when you might not have that relationship with someone, or you haven't talked about it, talked about assumptions about your knowledge or experience level upfront. And so I have found that to be a really good way to build that trust to make sure that we aren't excluding folks is to just talk about some of that stuff, even before we start pairing or before a meeting. And that can really help with some of those miscommunications that might come down later in the process. JOËL: It's interesting that you bring up miscommunication because I think sometimes, even though certain jargon can be very precise, sometimes people will not use it to mean exactly what its dictionary definition is. And so sometimes two people are using the same term, and you're not meaning quite the same thing. And so sometimes I'll be pairing with someone, and I'll have to sort of pause and say, "Hey, wait a minute, you're using the term adapter in a certain way that seems to be a little bit different than the way I'm using it. Can you maybe tell me what your personal definition is? And I'll tell you mine, and we can reconcile those two together." Sometimes that can also feel like a situation where maybe I'm hazy on the topic. Like, I have a vague sense of it, and maybe it does or does not align with the way the other person is using it. And so that's an opportunity for me to ask them to define the term for me without completely having to say, "I have no idea what this term is. Please, oh, great sage, explain the meaning." STEPHANIE: Are there times that you feel more or less comfortable doing that kind of reset? JOËL: I think sometimes the fear is in breaking flow. And so you're doing a thing, and then somebody is trying to explain something, and you don't want to break out of that. Or you're trying to explain something, and you have to decide, is it worth making sure to explain a term, or do you keep moving? So I think that is a big concern. And there is just the interpersonal concern of if there is less trust, do I want to put myself out there? Does somebody else maybe not feel comfortable you asked them to explain a term? Maybe they're using it wrong. It's not always good in a pairing situation to just come up and say, "Hey, that's not technically the adapter pattern; you're wrong. Let me pull out The Gang of Four book. You see on page 54..." that's not productive. STEPHANIE: Yeah, for sure. JOËL: So a lot of it, I think...and maybe this ties into your topic of communication while pairing. But ideally, you're working constructively with a person. And so debating definitions is not generally productive but asking someone, "What do you mean when you say this?" I find is a very helpful way to lead into that type of conversation. STEPHANIE: Yeah, that's a great strategy because you're coming from a place of curiosity rather than a place of this is my definition, and it's the right definition, and so, therefore, you are wrong. [laughs] JOËL: It's interesting the place that jargon occupies in our imagination of expertise. If you've ever seen any movie where they're trying to show that somebody is technically competent, they usually demonstrate that a person is competent by having them just spout out a long chain of jargon, and that makes them sound smart. But I think to a certain extent; maybe we believe it in the industry as well. If somebody can use a lot of terms and talk about a system using this very specific jargon, we tend to think that they're smart or at least look up to them a little bit. STEPHANIE: Yeah, which I think isn't always the best assumption because I've certainly worked with folks who did throw out a lot of jargon but weren't necessarily, like you were saying, using it the way that I understood it, and that made communicating with them challenging. I also think what true expertise really is is having the knowledge that when you use a jargony term that not everyone might be familiar with it, having the awareness to pause and ask someone how they're doing with the vocabulary and be able to tailor how you explain that term to that other person. I think that demonstrates a really deep level of understanding that doesn't get enough credit. JOËL: I 100% agree. Jargon, vocabulary, it's a means to an end, not an end in and of itself. So the goal is to communicate clearly to others and maybe to help yourself in your own learning. And if you're not accomplishing those goals, then what's the point? I guess maybe there is another personal goal which is to sound smart, but that's not really a good goal, [laughs] especially not when the way you do that is by confusing everybody else in the room because they don't understand you, to make you try to feel smarter than them. Like, that's bad communication. STEPHANIE: Yeah, for sure. I've definitely experienced listening to someone explain something and have to really think very hard about every single word that they're saying because they were using terms that are just less common. And so, in my brain, I had to map them to things that made sense to me, and things that I was familiar with that were the same concepts. Like, I was experienced enough to have that shared understanding, but just the words that they used required another layer of brain work. Maybe we could have found a happy medium between them communicating the way that they expressed themselves the best with my ability to understand easily and quickly so that we could get on the same page. JOËL: So you mentioned that there are sometimes situations where you're aware of a particular concept, but maybe you're just not aware that the term that somebody else is using maps to this concept you already understand. And I know that for me, oftentimes, being able to give a name to something that I understand is an incredibly powerful thing. Even though I already know the idea of passing objects to another object in this particular configuration, or of wrapping things in some way or whatever the thing that I'm trying to do, all of a sudden, instead of it being a more nebulous concept in my head or a list of 10 steps or something like that, now I have one thing I can just point to and say it is this. So that's been really helpful for me in my learning to be able to take a label and put it on something that I already know. And somehow, it cements the idea in my head and also then allows me to build on it to the next things that I want to learn. STEPHANIE: Yeah, absolutely. It's really exciting when you're able to have that light-bulb moment when you have that precise term, or you learn that precise term for something that you have been wrestling with or experiencing for a while now. I was just reminded of reading documentation. I have a very vivid memory of the first time I read; I don't know, even the Rails official docs, all of these terms that I didn't understand at the time. But then once I started digging into it, exploring, and just doing the work, when I revisited those docs, I could understand them a lot more comprehensively because I had experience with the things (There I am using things again.) [laughs] and seeing the terms for them and that helping solidify my understanding. JOËL: I'm curious, in your personal learning, do you find it easier to encounter a term first and then learn what it means, or do the reverse, learn the concept first and then cap it off by being able to give it a name? STEPHANIE: That's a good question. I think the latter because I've certainly spent a lot of time Googling terms and then reading whatever first search results came up and being like, okay, I think I got it, and then Googling the same term like two weeks later because I didn't really get it the first time. But whenever I come across a term for a concept I already am familiar with, it is like, oh yes, uh-huh! That really ends up sticking with me. Matheus Sales' blog post that you mentioned earlier is a really great example of that term really standing out to me because I didn't know it at the time, but I suppose was seeking out something to describe the concept of connascence. So that was really cool and really memorable. What about you? Do you have a preferred way of learning new technical terms? JOËL: I think there can be value to both approaches. But I'm with you; I think it generally is easier to add a name to a concept you already understand. And I experienced this pretty dramatically when I tried to get into functional programming. So several years ago, I tried to learn the language Haskell which is notorious for being difficult to learn and very abstract and technical. And the way that the Haskell community typically tries to teach things is learn the fundamentals first, very top-down, learn the theory, and then, later on, you can do things in practice. So it's like before you can write an actual program, let us teach you about applicatives, and monads, and all these things that are really difficult to learn. And they're kind of scary technical terms. So I choked out partway through, gave up on Haskell. A year later, got back into it, tried it again, choked out again. And then, eventually, I pivoted. I started getting into a similar language called Elm, which is similar syntax but compiles to JavaScript for the front end. And that community has the opposite philosophy when it comes to teaching. They want to get you productive as soon as possible. And you can learn some of the theory as you go along. And so with that, I felt like I was learning something new all the time and being productive as well, like, constantly adding new features to things in an application, and that's really exciting. And what's really beautiful there is that you eventually learn a lot of the same concepts that you would learn in something like Haskell because the two languages share a lot of similar concepts. But instead of saying first, you need to learn about monads as a general concept, and then you can build a program; Elm says, build a bunch of programs first. We'll show you the basic syntax. And after you've built a bunch of them, you'll start realizing, wait a minute, these things all kind of look alike. There are patterns I'm starting to recognize. And then you can just point to that and say, hey, that pattern that you started recognizing, and you see a bunch of times that's monad. You've known it all along, and now you can put a label on it. And you've gotten there. And so that's the way that I learned those concepts. And that was much easier for me than the approach of trying to learn the abstract concept first. STEPHANIE: Monad is literally the word I just Googled earlier this week and still have a very, very hazy understanding of. So maybe I'll have to go learn Elm now. [chuckles] JOËL: I recommend a lot of people to use that as their entry point into the statically typed functional programming world, just because of how much more shallow the learning curve is compared to alternatives. And I think a lot of it has to do with that approach of saying, let's get you productive quickly. Let's get you doing things. And eventually, patterns will emerge, and you can put names on them later. But we'll not make you learn all of the theory upfront, all the jargon. STEPHANIE: Now that you do understand all the technical jargon around functional programming, how do you approach communicating about it when you do talk about Elm or those kinds of concepts? JOËL: A lot of it depends on your audience. If you have an audience that already knows these concepts, then being able to use those names is really valuable because it's a shortcut. You can just say, oh yeah, this thing is a monad, and so, therefore, we can do these actions with it. And everybody in the audience just already knows monads have these properties. That's wonderful. Now I can follow to step two instead of having to have a slow build-up. So if I'm writing an article or giving a talk, or even just having a conversation with someone, if I knew they didn't know the term, I would have to really build up to it, and maybe I wouldn't introduce the term at all. I would just talk about some of the properties that are interesting for the purpose of this particular demo. But I would probably have to work up to it and say, "See, we have this simpler thing, and then this more complex thing. But here are the problems that we have with it. Here's a change we can make to our code that will make it work." And you walk through the process without necessarily getting into all of the theory. But with somebody else who did know, I could just say, "Oh, what we need here is monad." And they look at me, and they're like, "Oh, of course," and then we do it. STEPHANIE: What you just described reminds me a lot of the WIRED Video Series, five levels of teaching where they have an expert come in and teach the same concept to different-aged people starting from young kids to an expert in their field as well. And I really liked how you answered that question just with the awareness that you tailor how you explain something to your audience because we could all benefit from just having that intentionality when we communicate in order to get the most value out of our interactions and knowledge sharing, and collaborative working. JOËL: I think a theme that underlies a lot of what you and I have talked about today is just that communication, good communication is the fundamental value that we're going for here. And jargon and vocabulary can be something that empowers that but used poorly; it can also defeat that purpose. And most importantly, good communication starts with the audience, not with you. So when you work back from the audience, you can use the appropriate vocabulary and words that serve everybody and your ultimate goal of communicating. STEPHANIE: I love that. JOËL: So, Stephanie, thank you so much for joining us on The Bike Shed today. And as we wrap up, I wanted to ask you, what is a really fun piece of vocabulary that you’ve learned that you might want to share with the audience? STEPHANIE: So lately, I learned the term WYSIWYG, which stands for What You See Is What You Get to describe text editing software that lets you see and edit the content as it would actually be displayed. So that was a fun, little term that someone brought up when we were paring and looking at some text editing code. And I was really excited because it sounds fun, and also, now I had just an opportunity to say it on a podcast. [laughs] JOËL: It's amazing that an acronym that is that long has enough vowels in the right places that you can just pronounce it. STEPHANIE: Oh yeah. JOËL: WYSIWYG. That's a fun word to say. STEPHANIE: 100%. I also try to pronounce all acronyms, regardless of how pronounceable they actually are. [laughs] Thanks for asking. JOËL: With that, shall we wrap up? STEPHANIE: Let's wrap up. JOËL: The show notes for this episode can be found at bikeshed.fm. This show is produced and edited by Mandy Moore. If you enjoyed listening, one really easy way to support the show is to leave us a quick rating or even a review in iTunes. It really helps other folks find the show. If you have any feedback, you can reach us at @_bikeshed, or reach me at @joelquen on Twitter, or at hosts@bikeshed.fm via email. Thank you so much for listening to The Bike Shed, and we'll see you next week. Byeeeeeeee!!!!!! ANNOUNCER: This podcast was brought to you by thoughtbot. thoughtbot is your expert design and development partner. Let's make your product and team a success.Sponsored By:Airbrake: Deploy fearlessly and fix bugs faster with Airbrake Error & Performance Monitoring. Airbrake notifiers are available for all major programming languages and frameworks, and install in minutes, with an open-source SDK-based install and near-zero technical debt. Spend less time tracking down bugs and more time developing. Visit Frictionless error monitoring and performance insight for your app stack.Support The Bike Shed
undefined
Sep 20, 2022 • 43min

355: Test Performance

Guest Geoff Harcourt, CTO of CommonLit, joins Joël to talk about a thing that comes up with a lot with clients: the performance of their test suite. It's often a concern because with test suites, until it becomes a problem, people tend to not treat it very well, and people ask for help on making their test suites faster. Geoff shares how he handles a scenario like this at CommonLit. This episode is brought to you by Airbrake. Visit Frictionless error monitoring and performance insight for your app stack. Geoff Harcourt Common Lit Cuprite driver Chrome DevTools Protocol (CDP) Factory Doctor Joël's RailsConf talk Formal Methods Rails multi-database support Knapsack pro Prior episode with Eebs Shopify article on skipping specs Transcript: JOËL: Hello and welcome to another episode of The Bike Shed, a weekly podcast from your friends at thoughtbot about developing great software. I'm Joël Quenneville. And today, I'm joined by Geoff Harcourt, who is the CTO of CommonLit. GEOFF: Hi, Joël. JOËL: And together, we're here to share a little bit of what we've learned along the way. Geoff, can you briefly tell us what is CommonLit? What do you do? GEOFF: CommonLit is a 501(c)(3) non-profit that delivers a literacy curriculum in English and Spanish to millions of students around the world. Most of our tools are free. So we take a lot of pride in delivering great tools to teachers and students who need them the most. JOËL: And what does your role as CTO look like there? GEOFF: So we have a small engineering team. There are nine of us, and we run a Rails monolith. I'd say a fair amount of the time; I'm hands down in the code. But I also do the things that an engineering head has to do, so working with vendors, and figuring out infrastructure, and hiring, and things like that. JOËL: So that's quite a variety of things that you have to do. What is new in your world? What's something that you've encountered recently that's been fun or interesting? GEOFF: It's the start of the school year in America, so traffic has gone from a very tiny amount over the summer to almost the highest load that we'll encounter all year. So we're at a new hosting provider this fall. So we're watching our infrastructure and keeping an eye on it. The analogy that we've been using to describe this is like when you set up a bunch of plumbing, it looks like it all works, but until you really pump water through it, you don't see if there are any leaks. So things are in good shape right now, but it's a very exciting time of year for us. JOËL: Have you ever done some actual plumbing yourself? GEOFF: I am very, very bad at home repair. But I have fixed a toilet or two. I've installed a water filter but nothing else. What about you? JOËL: I've done a little bit of it when I was younger with my dad. Like, I actually welded copper pipes and that kind of thing. GEOFF: Oh, that's amazing. That's cool. Nice. JOËL: So I've definitely felt that thing where you turn the water source back on, and it's like, huh, let's see, is this joint going to leak, or are we good? GEOFF: Yeah, they don't have CI for plumbing, right? JOËL: [laughs] You know, test it in production, right? GEOFF: Yeah. [laughs] So we're really watching right now traffic starting to rise as students and teachers are coming back. And we're also figuring out all kinds of things that we want to do to do better monitoring of our application, so some of this is watching metrics to see if things happen. But some of this is also doing some simulated user activity after we do deploys. So we're using some automated browsers with Cypress to log into our application and do some user flows, and then report back on the results. JOËL: So is this kind of like a feature test in CI, except that you're running it in production? GEOFF: Yeah. Smoke test is the word that we've settled on for it, but we run it against our production server every time we deploy. And it's a small suite. It's nowhere as big as our big Capybara suite that we run in CI, but we're trying to get feedback in less than six minutes. That's sort of the goal. In addition to running tests, we also take screenshots with a tool called Percy, and that's a visual regression testing tool. So we get to see the screenshots, and if they differ by more than one pixel, we get a ping that lets us know that maybe our CSS has moved around or something like that. JOËL: Has that caught some visual bugs for you? GEOFF: Definitely. The state of CSS at CommonLit was very messy when I arrived, and it's gotten better, but it still definitely needs some love. There are some false positives, but it's been really, really nice to be able to see visual changes on our production pages and then be able to approve them or know that there's something we have to go back and fix. JOËL: I'm curious, for this smoke test suite, how long does it take to run? GEOFF: We run it in parallel. It runs on Buildkite, which is the same tool that we use to orchestrate our CI, and the longest test takes about five minutes. It signs in as a teacher, creates an account. It creates a class; it invites the student to that class. It then logs out, logs in as that student creates the student account, signs in as the student, joins the class. It then assigns a lesson to the student then the student goes and takes the lesson. And then, when the student submits the lesson, then the test is over. And that confirms all of the most critical flows that we would want someone to drop what they were doing if it's broken, you know, account creation, class creation, lesson creation, and students taking a lesson. JOËL: So you're compressing the first few weeks of school into five minutes. GEOFF: Yes. And I pity the school that has thousands of fake teachers, all named Aaron McCarronson at the school. JOËL: [laughs] GEOFF: But we go through and delete that data every once in a while. But we have a marketer who just started at CommonLit maybe a few weeks ago, and she thought that someone was spamming our signup form because she said, "I see hundreds of teachers named Aaron McCarronson in our user list." JOËL: You had to admit that you were the spammer? GEOFF: Yes, I did. [laughs] We now have some controls to filter those people out of reports. But it's always funny when you look at the list, and you see all these fake people there. JOËL: Do you have any rate limiting on your site? GEOFF: Yeah, we do quite a bit of it, actually. Some of it we do through Cloudflare. We have tools that limit a certain flow, like people trying to credential stuffing our password, our user sign-in forms. But we also do some further stuff to prevent people from hitting key endpoints. We use Rack::Attack, which is a really nice framework. Have you had to do that in client work with clients setting that stuff up? JOËL: I've used Rack:Attack before. GEOFF: Yeah, it's got a reasonably nice interface that you can work with. And I always worry about accidentally setting those things up to be too sensitive, and then you get lots of stuff back. One issue that we sometimes find is that lots of kids at the same school are sharing an IP address. So that's not the thing that we want to use for rate limiting. We want to use some other criteria for rate limiting. JOËL: Right, right. Do you ever find that you rate limit your smoke tests? Or have you had to bypass the rate limiting in the smoke tests? GEOFF: Our smoke tests bypass our rate limiting and our bot detection. So they've got some fingerprints they use to bypass that. JOËL: That must have been an interesting day at the office. GEOFF: Yes. [laughter] With all of these things, I think it's a big challenge to figure out, and it's similar when you're making tests for development, how to make tests that are high signal. So if a test is failing really frequently, even if it's testing something that's worthwhile, if people start ignoring it, then it stops having value as a piece of signal. So we've invested a ton of time in making our test suite as reliable as possible, but you sometimes do have these things that just require a change. I've become a really big fan of...there's a Ruby driver for Capybara called Cuprite, and it doesn't control chrome with Chrome Driver or with Selenium. It controls it with the Chrome DevTools protocol, so it's like a direct connection into the browser. And we find that it's very, very fast and very, very reliable. So we saw that our Capybara specs got significantly more reliable when we started using this as our driver. JOËL: Is this because it's not actually moving the mouse around and clicking but instead issuing commands in the background? GEOFF: Yeah. My understanding of this is a little bit hazy. But I think that Selenium and ChromeDriver are communicating over a network pipe, and sometimes that network pipe is a little bit lossy. And so it results in asynchronous commands where maybe you don't get the feedback back after something happens. And CDP is what Chrome's team and I think what Puppeteer uses to control things directly. So it's great. And you can even do things with it. Like, you can simulate different time zone for a user almost natively. You can speed up or slow down the traveling of time and the direction of time in the browser and all kinds of things like that. You can flip it into mobile mode so that the device reports that it's a touch browser, even though it's not. We have a set of mobile specs where we flip it with CDP into mobile mode, and that's been really good too. Do you find when you're doing client work that you have a demand to build mobile-specific specs for system tests? JOËL: Generally not, no. GEOFF: You've managed to escape it. JOËL: For something that's specific to mobile, maybe one or two tests that have a weird interaction that we know is different on mobile. But in general, we're not doing the whole suite under mobile and the whole suite under desktop. GEOFF: When you hand off a project...it's been a while since you and I have worked together. JOËL: For those who don't know, Geoff used to be with us at thoughtbot. We were colleagues. GEOFF: Yeah, for a while. I remember my very first thoughtbot Summer Summit; you gave a really cool lightning talk about Eleanor of Aquitaine. JOËL: [laughs] GEOFF: That was great. So when you're handing a project off to a client after your ending, do you find that there's a transition period where you're educating them about the norms of the test suite before you leave it in their hands? JOËL: It depends a lot on the client. With many clients, we're working alongside an existing dev team. And so it's not so much one big handoff at the end as it is just building that in the day-to-day, making sure that we are integrating with the team from the outset of the engagement. So one thing that does come up a lot with clients is the performance of their test suite. That's often a concern because the test suite until it becomes a problem, people tend to not treat it very well. And by the time that you're bringing on an external consultant to help, generally, that's one of the areas of the code that's been a little bit neglected. And so people ask for help on making their test suite faster. Is that something that you've had to deal with at CommonLit as well? GEOFF: Yeah, that's a great question. We have struggled a lot with the speed that our test suite...the time it takes for our test suite to run. We've done a few things to improve it. The first is that we have quite a bit of caching that we do in our CI suite around dependencies. So gems get cached separately from NPM packages and browser assets. So all three of those things are independently cached. And then, we run our suites in parallel. Our Jest specs get split up into eight containers. Our Ruby non-system tests...I'd like to say unit tests, but we all know that some of those are actually integration tests. JOËL: [laughs] GEOFF: But those tests run in 15 containers, and they start the moment gems are built. So they don't wait for NPM packages. They don't wait for assets. They immediately start going. And then our system specs as soon as the assets are built kick off and start running. And we actually run that in 40 parallel containers so we can get everything finished. So our CI suite can finish...if there are no dependency bumps and no asset bumps, our specs suite you can finish in just under five minutes. But if you add up all of that time, cumulatively, it's something like 75 minutes is the total execution as it goes. Have you tried FactoryDoctor before for speeding up test suites? JOËL: This is the gem from Evil Martians? GEOFF: Yeah, it's part of TestProf, which is their really, really unbelievable toolkit for improving specs, and they have a whole bunch of things. But one of them will tell you how many invocations of FactoryBot factories each factory got. So you can see a user factory was fired 13,000 times in the test suite. It can even do some tagging where it can go in and add metadata to your specs to show which ones might be candidates for optimization. JOËL: I gave a talk at RailsConf this year titled Your Tests Are Making Too Many Database Calls. GEOFF: Nice. JOËL: And one of the things I talked about was creating a lot more data via factories than you think that you are. And I should give a shout-out to FactoryProf for finding those. GEOFF: Yeah, it's kind of a silent killer with the test suite, and you really don't think that you're doing a whole lot with it, and then you see how many associations. How do you fight that tension between creating enough data that things are realistic versus the streamlining of not creating extraneous things or having maybe mystery guests via associations and things like that? JOËL: I try to have my base factories be as minimal as possible. So if there's a line in there that I can remove, and the factory or the model still saves, then it should be removed. Some associations, you can't do that if there's a foreign key constraint, and so then I'll leave it in. But I am a very hardcore minimalist, at least with the base factory. GEOFF: I think that makes a lot of sense. We use foreign keys all over the place because we're always worried about somehow inserting student data that we can't recover with a bug. So we'd rather blow up than think we recorded it. And as a result, sometimes setting up specs for things like a student answering a multiple choice question on a quiz ends up being this sort of if you give a mouse a cookie thing where it's you need the answer options. You need the question. You need the quiz. You need the activity. You need the roster, the students to be in the roster. There has to be a teacher for the roster. It just balloons out because everything has a foreign key. JOËL: The database requires it, but the test doesn't really care. It's just like, give me a student and make it valid. GEOFF: Yes, yeah. And I find that that challenge is really hard. And sometimes, you don't see how hard it is to enforce things like database integrity until you have a lot of concurrency going on in your application. It was a very rude surprise to me to find out that browser requests if you have multiple servers going on might not necessarily be served in the order that they were made. JOËL: [laughs] So you're talking about a scenario where you're running multiple instances of your app. You make two requests from, say, two browser tabs, and somehow they get served from two different instances? GEOFF: Or not even two browser tabs. Imagine you have a situation where you're auto-saving. JOËL: Oooh, background requests. GEOFF: Yeah. So one of the coolest features we have at CommonLit is that students can annotate and highlight a text. And then, the teachers can see the annotations and highlights they've made, and it's actually part of their assignment often to highlight key evidence in a passage. And those things all fire in the background asynchronously so that it doesn't block the student from doing more stuff. But it also means that potentially if they make two changes to a highlight really quickly that they might arrive out of order. So we've had to do some things to make sure that we're receiving in the right order and that we're not blowing away data that was supposed to be there. Just think about in a Heroku environment, for example, which is where we used to be, you'd have four dynos running. If dyno one takes too long to serve the thing for dyno two, request one may finish after request two. That was a very, very rude surprise to learn that the world was not as clean and neat as I thought. JOËL: I've had to do something similar where I'm making a bunch of background requests to a server. And even with a single dyno, it is possible for your request to come back out of order just because of how TCP works. So if it's waiting for a packet and you have two of these requests that went out not too long before each other, there's no guarantee that all the packets for request one come back before all the packets from request two. GEOFF: Yeah, what are the strategies for on the client side for dealing with that kind of out-of-order response? JOËL: Find some way to effectively version the requests that you make. Timestamp is an easy one. Whenever a request comes in, you take the response from the latest timestamp, and that wins out. GEOFF: Yeah, we've started doing some unique IDs. And part of the unique ID is the browser's timestamp. We figure that no one would try to hack themselves and intentionally screw up their own data by submitting out of order. JOËL: Right, right. GEOFF: It's funny how you have to pick something to trust. [laughs] JOËL: I'd imagine, in this case, if somebody did mess around with it, they would really only just be screwing up their own UI. It's not like that's going to then potentially crash the server because of something, and then you've got a potential vector for a denial of service. GEOFF: Yeah, yeah, that's always what we're worried about, and we have to figure out how to trust these sorts of requests as what's a valid thing and what is, as you're saying, is just the user hurting themselves as opposed to hurting someone else's stuff? MID-ROLL AD: Debugging errors can be a developer’s worst nightmare...but it doesn’t have to be. Airbrake is an award-winning error monitoring, performance, and deployment tracking tool created by developers for developers that can actually help cut your debugging time in half. So why do developers love Airbrake? It has all of the information that web developers need to monitor their application - including error management, performance insights, and deploy tracking! Airbrake’s debugging tool catches all of your project errors, intelligently groups them, and points you to the issue in the code so you can quickly fix the bug before customers are impacted. In addition to stellar error monitoring, Airbrake’s lightweight APM helps developers to track the performance and availability of their application through metrics like HTTP requests, response times, error occurrences, and user satisfaction. Finally, Airbrake Deploy Tracking helps developers track trends, fix bad deploys, and improve code quality. Since 2008, Airbrake has been a staple in the Ruby community and has grown to cover all major programming languages. Airbrake seamlessly integrates with your favorite apps to include modern features like single sign-on and SDK-based installation. From testing to production, Airbrake notifiers have your back. Your time is valuable, so why waste it combing through logs, waiting for user reports, or retrofitting other tools to monitor your application? You literally have nothing to lose. Head on over to airbrake.io/try/bikeshed to create your FREE developer account today! GEOFF: You were talking about test suites. What are some things that you have found are consistently problems in real-world apps, but they're really, really hard to test in a test suite? JOËL: Difficult to test or difficult to optimize for performance? GEOFF: Maybe difficult to test. JOËL: Third-party integrations. Anything that's over the network that's going to be difficult. Complex interactions that involve some heavy frontend but then also need a lot of backend processing potentially with asynchronous workers or something like that, there are a lot of techniques that we can use to make all those play together, but that means there's a lot of complexity in that test. GEOFF: Yeah, definitely. I've taken a deep interest in what I'm sure there's a better technical term for this, but what I call network hostile environments or bandwidth hostile environments. And we see this a lot with kids. Especially during the pandemic, kids would often be trying to do their assignments from home. And maybe there are five kids in the house, and they're all trying to do their homework at the same time. And they're all sharing a home internet connection. Maybe they're in the basement because they're trying to get some peace and quiet so they can do their assignment or something like that. And maybe they're not strongly connected. And the challenge of dealing with intermittent connectivity is such an interesting problem, very frustrating but very interesting to deal with. JOËL: Have you explored at all the concept of Formal Methods to model or verify situations like that? GEOFF: No, but I'm intrigued. Tell me more. JOËL: I've not tried it myself. But I've read some articles on the topic. Hillel Wayne is a good person to follow for this. GEOFF: Oh yeah. JOËL: But it's really fascinating when you'll see, okay, here are some invariants and things. And then here are some things that you set up some basic properties for a system. And then some of these modeling languages will then poke holes and say, hey, it's possible for this 10-step sequence of events to happen that will then crash your server. Because you didn't think that it's possible for five people to be making concurrent requests, and then one of them fails and retries, whatever the steps are. So it's really good at modeling situations that, as developers, we don't always have great intuition, things like parallelism. GEOFF: Yeah, that sounds so interesting. I'm going to add that to my list of reading for the fall. Once the school year calms down, I feel like I can dig into some technical topics again. I've got this book sitting right next to my desk, Designing Data-Intensive Applications. I saw it referenced somewhere on Twitter, and I did the thing where I got really excited about the book, bought it, and then didn't have time to read it. So it's just sitting there unopened next to my desk, taunting me. JOËL: What's the 30-second spiel for what is a data-intensive app, and why should we design for it differently? GEOFF: You know, that's a great question. I'd probably find out if I'd dug further into the book. JOËL: [laughs] GEOFF: I have found at CommonLit that we...I had a couple of clients at thoughtbot that dealt with data at the scale that we deal with here. And I'm sure there are bigger teams doing, quote, "bigger data" than we're doing. But it really does seem like one of our key challenges is making sure that we just move data around fast enough that nothing becomes a bottleneck. We made a really key optimization in our application last year where we changed the way that we autosave students' answers as they go. And it resulted in a massive increase in throughput for us because we went from trying to store updated versions of the students' final answers to just storing essentially a draft and often storing that draft in local storage in the browser and then updating it on the server when we could. And then, as a result of this, we're making key updates to the table where we store a student's answers much less frequently. And that has a huge impact because, in addition to being one of the biggest tables at CommonLit...it's got almost a billion recorded answers that we've gotten from students over the years. But because we're not writing to it as often, it also means that reads that are made from the table, like when the teacher is getting a report for how the students are doing in a class or when a principal is looking at how a school is doing, now, those queries are seeing less contention from ongoing writes. And so we've seen a nice improvement. JOËL: One strategy I've seen for that sort of problem, especially when you have a very write-heavy table but that also has a different set of users that needs to read from it, is to set up a read replica. So you have your main that is being written to, and then the read replica is used for reports and people who need to look at the data without being in contention with the table being written. GEOFF: Yeah, Rails multi-DB support now that it's native to the framework is excellent. It's so nice to be able to just drop that in and fire it up and have it work. We used to use a solution that Instacart had built. It was great for our needs, but it wasn't native to the framework. So every single time we upgraded Rails, we had to cross our fingers and hope that it didn't, you know, whatever private APIs of ActiveRecord it was using hadn't broken. So now that that stuff, which I think was open sourced from GitHub's multi-database implementation, so now that that's all native in Rails, it's really, really nice to be able to use that. JOËL: So these kinds of database tricks can help make the application much more performant. You'd mentioned earlier that when you were trying to make your test performant that you had introduced parallelism, and I feel like that's maybe a bit of an intimidating thing for a lot of people. How would you go about converting a test suite that's just vanilla RSpec, single-threaded, and then moving it in a direction of being more parallel? GEOFF: There's a really, really nice tool called Knapsack, which has a free version. But the pro version, I feel like if you're spending any money at all on CI, it's immediately worth the cost. I think it's something like $75 a month for each suite that you run on it. And Knapsack does this dynamic allocation of tests across containers. And it interfaces with several of the popular CI providers so that it looks at environment variables and can tell how many containers you're splitting across. It'll do some things, like if some of your containers start early and some of them start late, it will distribute the work so that they all end at the same time, which is really nice. We've preferred CI providers that charge by the minute. So rather than just paying for a service that we might not be using, we've used services like Semaphore, and right now, we're on Buildkite, which charge by the minute, which means that you can decide to do as much parallelism as you want. You're just paying for the compute time as you run things. JOËL: So that would mean that two minutes of sequential build time costs just the same as splitting it up in parallel and doing two simultaneous minutes of build time. GEOFF: Yeah, that is almost true. There's a little bit of setup time when a container spins up. And that's one of the key things that we optimize. I guess if we ran 200 containers if we were like Shopify or something like that, we could technically make our CI suite finish faster, but it might cost us three times as much. Because if it takes a container 30 seconds to spin up and to get ready, that's 30 seconds of dead time when you're not testing, but you're paying for the compute. So that's one of the key optimizations that we make is figuring out how many containers do we need to finish fast when we're not just blowing time on starting and finishing? JOËL: Right, because there is a startup cost for each container. GEOFF: Yeah, and during the work day when our engineers are working along, we spin up 200 EC2 machines or 150 EC2 machines, and they're there in the fleet, and they're ready to go to run CI jobs for us. But if you don't have enough machines, then you have jobs that sit around waiting to start, that sort of thing. So there's definitely a tension between figuring out how much parallelism you're going to do. But I feel like to start; you could always break your test suite into four pieces or two pieces and just see if you get some benefit to running a smaller number of tests in parallel. JOËL: So, manually splitting up the test suite. GEOFF: No, no, using something like Knapsack Pro where you're feeding it the suite, and then it's dividing up the tests for you. I think manually splitting up the suite is probably not a good practice overall because I'm guessing you'll probably spend more engineering time on fiddling with which tests go where such that it wouldn't be cost-effective. JOËL: So I've spent a lot of time recently working to improve a parallel test suite. And one of the big problems that you have is trying to make sure that all of your parallel surfaces are being used efficiently, so you have to split the work evenly. So if you said you have 70 minutes worth of work, if you give 50 minutes to one worker and 20 minutes to the other, that means that your total test suite is still 50 minutes, and that's not good. So ideally, you split it as evenly as possible. So I think there are three evolutionary steps on the path here. So you start off, and you're going to manually split things out. So you're going to say our biggest chunk of tests by time are the feature specs. We'll make them almost like a separate suite. Then we'll make the models and controllers and views their own thing, and that's roughly half and half, and run those. And maybe you're off by a little bit, but it's still better than putting them all in one. It becomes difficult, though, to balance all of these because then one might get significantly longer than the other then, you have to manually rebalance it. It works okay if you're only splitting it among two workers. But if you're having to split it among 4, 8, 16, and more, it's not manageable to do this, at least not by hand. If you want to get fancy, you can try to automate that process and record a timing file of how long every file takes. And then when you kick off the build process, look at that timing file and say, okay, we have 70 minutes, and then we'll just split the file so that we have roughly 70 divided by number of workers' files or minutes of work in each process. And that's what gems like parallel_tests do. And Knapsack's Classic mode works like this as well. That's decently good. But the problem is you're working off of past information. And so if the test has changed or just if it's highly variable, you might not get a balanced set of workers. And as you mentioned, there's a startup cost, and so not all of your workers boot up at the same time. And so you might still have a very uneven amount of work done by each worker by statically determining the work to be done via a timing file. So the third evolution here is a dynamic or a self-balancing approach where you just put all of the tests or the files in a queue and then just have every worker pull one or two tests when it's ready to work. So that way, if something takes a lot longer than expected, well, it's just not pulling more from the queue. And everybody else still pulls, and they end up all balancing each other out. And then ideally, every worker finishes work at exactly the same time. And that's how you know you got the most value you could out of your parallel processes. GEOFF: Yeah, there's something about watching all the jobs finish in almost exactly, you know, within 10 seconds of each other. It just feels very, very satisfying. I think in addition to getting this dynamic splitting where you're getting either per file or per example split across to get things finishing at the same time, we've really valued getting fast feedback. So I mentioned before that our Jest specs start the moment NPM packages get built. So as soon as there's JavaScripts that can be executed in test, those kick-off. As soon as our gems are ready, the RSpec non-system tests go off, and they start running specs immediately. So we get that really, really fast feedback. Unfortunately, the browser tests take the longest because they have to wait for the most setup. They have the most dependencies. And then they also run the slowest because they run in the browser and everything. But I think when things are really well-oiled, you watch all of those containers end at roughly the same time, and it feels very satisfying. JOËL: So, a few weeks ago, on an episode of The Bike Shed, I talked with Eebs Kobeissi about dependency graphs and how I'm super excited about it. And I think I see a dependency graph in what you're describing here in that some things only depend on the gem file, and so they can start working. But other things also depend on the NPM packages. And so your build pipeline is not one linear process or one linear process that forks into other linear processes; it's actually a dependency graph. GEOFF: That is very true. And the CI tool we used to use called Semaphore actually does a nice job of drawing the dependency graph between all of your steps. Buildkite does not have that, but we do have a bunch of steps that have to wait for other steps to finish. And we do it in our wiki. On our repo, we do have a diagram of how all of this works. We found that one of the things that was most wasteful for us in CI was rebuilding gems, reinstalling NPM packages (We use Yarn but same thing.), and then rebuilding browser assets. So at the very start of every CI run, we build hashes of a bunch of files in the repository. And then, we use those hashes to name Docker images that contain the outputs of those files so that we are able to skip huge parts of our CI suite if things have already happened. So I'll give an example if Ruby gems have not changed, which we would know by the Gemfile.lock not having changed, then we know that we can reuse a previously built gems image that has the gems that just gets melted in, same thing with yarn.lock. If yarn.lock hasn't changed, then we don't have to build NPM packages. We know that that already exists somewhere in our Docker registry. In addition to skipping steps by not redoing work, we also have started to experiment...actually, in response to a comment that Chris Toomey made in a prior Bike Shed episode, we've started to experiment with skipping irrelevant steps. So I'll give an example of this if no Ruby files have changed in our repository, we don't run our RSpec unit tests. We just know that those are valid. There's nothing that needs to be rerun. Similarly, if no JavaScript has changed, we don't run our Jest tests because we assume that everything is good. We don't lint our views with erb-lint if our view files haven't changed. We don't lint our factories if the model or the database hasn't changed. So we've got all these things to skip key types of processing. I always try to err on the side of not having a false pass. So I'm sure we could shave this even tighter and do even less work and sometimes finish the build even faster. But I don't want to ever have a thing where the build passes and we get false confidence. JOËL: Right. Right. So you're using a heuristic that eliminates the really obvious tests that don't need to be run but the ones that maybe are a little bit more borderline, you keep them in. Shaving two seconds is not worth missing a failure. GEOFF: Yeah. And I've read things about big enterprises doing very sophisticated versions of this where they're guessing at which CI specs might be most relevant and things like that. We're nowhere near that level of sophistication right now. But I do think that once you get your test suite parallelized and you're not doing wasted work in the form of rebuilding dependencies or rebuilding assets that don't need to be rebuilt, there is some maybe not low, maybe medium hanging fruit that you can use to get some extra oomph out of your test suite. JOËL: I really like that you brought up this idea of infrastructure and skipping. I think in my own way of thinking about improving test suites, there are three broad categories of approaches you can take. One variable you get to work with is that total number of time single-threaded, so you mentioned 70 minutes. You can make that 70 minutes shorter by avoiding database writes where you don't need them, all the common tricks that we would do to actually change the test themselves. Then we can change...as another variable; we get to work with parallelism, we talked about that. And then finally, there's all that other stuff that's not actually executing RSpec like you said, loading the gems, installing NPM packages, Docker images. All of those, if we can skip work running migrations, setting up a database, if there are situations where we can improve the speed there, that also improves the total time. GEOFF: Yeah, there are so many little things that you can pick at to...like, one of the slowest things for us is Elasticsearch. And so we really try to limit the number of specs that use Elasticsearch if we can. You actually have to opt-in to using Elasticsearch on a spec, or else we silently mock and disable all of the things that happen there. When you're looking at that first variable that you were talking about, just sort of the overall time, beyond using FactoryDoctor and FactoryProf, is there anything else that you've used to just identify the most egregious offenders in a test suite and then figure out if they're worth it? JOËL: One thing you can do is hook into Active Support notification to try to find database writes. And so you can find, oh, here's where all of the...this test is making way too many database writes for some reason, or it's making a lot, maybe I should take a look at it; it's a hotspot. GEOFF: Oh, that's really nice. There's one that I've always found is like a big offender, which is people doing negative expectations in system specs. JOËL: Oh, for their Capybara wait time. GEOFF: Yeah. So there's a really cool gem, and the name of it is eluding me right now. But there's a gem that raises a special exception if Capybara waits the full time for something to happen. So it lets you know that those things exist. And so we've done a lot of like hunting for...Knapsack will report the slowest examples in your test suite. So we've done some stuff to look for the slowest files and then look to see if there are examples of these negative expectations that are waiting 10 seconds or waiting 8 seconds before they fail. JOËL: Right. Some files are slow, but they're slow for a reason. Like, a feature spec is going to be much slower than a model test. But the model tests might be very wasteful and because you have so many of them, if you're doing the same pattern in a bunch of them or if it's a factory that's reused across a lot of them, then a small fix there can have some pretty big ripple effects. GEOFF: Yeah, I think that's true. Have you ever done any evaluation of test suite to see what files or examples you could throw away? JOËL: Not holistically. I think it's more on an ad hoc basis. You find a place, and you're like, oh, these tests we probably don't need them. We can throw them out. I have found dead tests, tests that are not executed but still committed to the repo. GEOFF: [laughs] JOËL: It's just like, hey, I'm going to get a lot of red in my diff today. GEOFF: That always feels good to have that diff-y check-in, and it's 250 lines or 1,000 lines of red and 1 line of green. JOËL: So that's been a pretty good overview of a lot of different areas related to performance and infrastructure around tests. Thank you so much, Geoff, for joining us today on The Bike Shed to talk about your experience at CommonLit doing this. Do you have any final words for our listeners? GEOFF: Yeah. CommonLit is hiring a senior full-stack engineer, so if you'd like to work on Rails and TypeScript in a place with a great test suite and a great team. I've been here for five years, and it's a really, really excellent place to work. And also, it's been really a pleasure to catch up with you again, Joël. JOËL: And, Geoff, where can people find you online? GEOFF: I'm Geoff with a G, G-E-O-F-F Harcourt, @geoffharcourt. And that's my name on Twitter, and it's my name on GitHub, so you can find me there. JOËL: And we'll make sure to include a link to your Twitter profile in the show notes. The show notes for this episode can be found at bikeshed.fm. This show is produced and edited by Mandy Moore. If you enjoyed listening, one really easy way to support the show is to leave us a quick rating or even a review in iTunes. It really helps other folks find the show. If you have any feedback, you can reach us at @_bikeshed or reach me at @joelquen on Twitter or at hosts@bikeshed.fm via email. Thank you so much for listening to The Bike Shed, and we'll see you next week. Byeeeeeee!!!!!! ANNOUNCER: This podcast was brought to you by thoughtbot. thoughtbot is your expert design and development partner. Let's make your product and team a success.Sponsored By:Airbrake: Deploy fearlessly and fix bugs faster with Airbrake Error & Performance Monitoring. Airbrake notifiers are available for all major programming languages and frameworks, and install in minutes, with an open-source SDK-based install and near-zero technical debt. Spend less time tracking down bugs and more time developing. Visit Frictionless error monitoring and performance insight for your app stack.Support The Bike Shed

Remember Everything You Learn from Podcasts

Save insights instantly, chat with episodes, and build lasting knowledge - all powered by AI.
App store bannerPlay store banner
Get the app