413: Developer Tales of Package Management
The Bike Shed - Een podcast door thoughtbot - Dinsdagen
Categorieën:
Stephanie shares her task of retiring a small, internally-used link-shortening app. She describes the process as both celebratory and a bit mournful. Meanwhile, Joël discusses his deep dive into ActiveRecord, particularly in the context of debugging. He explores the complexities of ActiveRecord querying schemas and the additional latency this introduces. Together, the hosts discuss the nuances of package management systems and their implications for developers. They touch upon the differences between system packages and language packages, sharing personal experiences with tools like Homebrew, RubyGems, and Docker. 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, Stephanie, what's new in your world? STEPHANIE: So, this week, I got to have some fun working on some internal thoughtbot work. And what I focused on was retiring one of our just, like, small internal self-hosted on Heroku apps in favor of going with a third-party service for this functionality. We basically had a tiny, little app that we used as a link-shortening service. So, if you've ever seen a tbot.io short link out in the world, we were using our just, like, an in-house app to do that, you know, but for various reasons, we wanted to...just it wasn't worth maintaining anymore. So, we wanted to just use a purchased service. But today, I got to just, like, do the little bit of, like, tidying up, you know, in preparation to archive a repo and kind of delete the app from Heroku, and I hadn't done that before. So, it felt a little bit celebratory and a little bit mournful even [laughs] to, you know, retire something like that. And I was pairing with another thoughtbot developer, and we used a pairing app called Tuple. And you can just send, like, fun reactions to each other. Like, you could send, like, a fire emoji [laughs] or something if that's what you're feeling. And so, I sent some, like, confetti when we clicked the, "I understand what deleting this app means on GitHub." But I joked that "Actually, I feel like what I really needed was a, like, a salute kind of like thank you for your service [laughs] type of reaction." JOËL: I love those moments when you're kind of you're hitting those kind of milestone-y moments, and then you get to send a reaction. I should do that more often in Tuple. Those are fun. STEPHANIE: They are fun. There's also a, like, table flip reaction, too, is one that I really enjoy [laughs], you know, you just have to manifest that energy somehow. And then, after we kind of sent out an email to the company saying like, "Oh yeah, we're not using our app anymore for link shortening," someone had a great suggestion to make our archived repo public instead of private. I kind of liked it as a way of, like, memorializing this application and let community members see, you know, real code in a real...the application that we used here at thoughtbot. So, hopefully, if not me, then someone else will be able to do that and maybe publish a little blog post about that. JOËL: That's exciting. So, it's not currently public, the repo, but it might be at some point in the future. STEPHANIE: Yeah, that's right. JOËL: We'll definitely have to mention it on a future episode if that happens so that people following along with the story can go check out the code. STEPHANIE: So, Joël, what's new in your world? JOËL: I've been doing a deep dive into how ActiveRecord works. Particularly, I am debugging some pretty significant slowdowns in querying ActiveRecord models that are backed not by a regular Postgres database but instead a Snowflake data warehouse via an ODBC connection. So, there's a bunch of moving pieces going on here, and it would just take forever to make any queries. And sure, the actual reported query time is longer than for a local Postgres database, but then there's this sort of mystery extra waiting time, and I couldn't figure out why is it taking so much longer than the actual sort of recorded query time. And I started digging into all of this, and it turns out that in addition to executing queries to pull actual data in, ActiveRecord needs to, at various points, query the schema of your data store to pull things like names of tables and what are the indexes and primary keys and things like that. STEPHANIE: Wow. That sounds really cool and something that I have never needed to do before. I'm curious if you noticed...you said that it takes, I guess, longer to query Snowflake than it would a more common Postgres database. Were you noticing this performance slowness locally or on production? JOËL: Both places. So, the nice thing is I can reproduce it locally, and locally, I mean running the Rails app locally. I'm still talking to a remote Snowflake data warehouse, which is fine. I can reproduce that slowness locally, which has made it much easier to experiment and try things. And so, from there, it's really just been a bit of a detective case trying to, I guess, narrow the possibility space and try to understand what are the parts that trigger slowness. So, I'm printing timestamps in different places. I've got different things that get measured. I've not done, like, a profiling tool to generate a flame graph or anything like that. That might have been something cool to try. I just did old-school print statements in a couple of places where I, like, time before, time after, print the delta, and that's gotten me pretty far. STEPHANIE: That's pretty cool. What do you think will be an outcome of this? Because I remember you saying you're digging a little bit into ActiveRecord internals. So, based on, like, what you're exploring, what do you think you could do as a developer to increase some of the performance there? JOËL: I think probably what this ends up being is finding that the Snowflake adapter that I'm using for ActiveRecord maybe has some sort of small bug in it or some implementation that's a little bit too naive that needs to be fine-tuned. And so, probably what ends up happening here is that this finishes as, like, an open-source pull request to the Snowflake Adapter gem. STEPHANIE: Yeah, that's where I thought maybe that might go. And that's pretty cool, too, and to, you know, just be investigating something on your app and being able to make a contribution that it benefits the community. JOËL: And that's what's so great about open source because not only am I able to get the source to go source diving through all of this, because I absolutely need to do that, but also, then if I make a fix, I can push that fix back out to the community, and everybody gets to benefit. STEPHANIE: Cool. Well, that's another thing that I look forward to hearing more on the development of [laughs] later if it pans out that way. JOËL: One thing that has been interesting with this Snowflake work is that there are a lot of moving parts and multiple different packages that I need to install to get this all to work. So, I mentioned that I might be doing a pull request against the Snowflake Adapter for ActiveRecord, but all of this talks through a sort of lower-level technology protocol called ODBC, which is a sort of generic protocol for speaking to data stores, and that actually has two different pieces. I had to install two different packages. There is a sort of low-level executable that I had to install on my local dev machine and that I have to install on our servers. And on my Mac, I'm installing that via Homebrew, which is a system package. And then to get Ruby bindings for that, there is a Ruby gem that I install that allows Ruby code to talk to ODBC, and that's installed via RubyGems or Bundler. And that got me thinking about sort of these two separate ecosystems that I tend to work with every day. We've got sort of the system packages and the, I don't know what you want to call them, language packages maybe, things like RubyGems, but that could also be NPM or whatever your language of choice is, and realizing that we kind of have things split into two different zones, and sometimes we need both and wondering a little bit about why is that difference necessary. STEPHANIE: Yeah, I don't have an answer to that [laughs] question right now, but I can say that that was an area that really tripped me up, I think, when I was first a fledgling developer. And I was really confused about where all of these dependencies were coming from and going through, you know, setting up my first project and being, like, asked to install Postgres on my machine but then also Bundler, which then also installs more dependencies [laughs]. The lines between those ecosystems were not super clear to me. And, you know, even now, like, I find myself really just kind of, like, learning what I need to know to get by [laughs] with my day-to-day work. But I do like what you said about these are kind of the two main layers that you're working with in terms of package management. And it's really helpful to have that knowledge so you can troubleshoot when there is an issue at one or the other. JOËL: And you mentioned Postgres. That's another one that's interesting because there are components in both of those ecosystems. Postgres itself is typically installed via a system package manager, so something like Homebrew on a Mac or apt-get on a Linux machine. But then, if you're interacting with Postgres in a Ruby app, you're probably also installing the pg gem, which are Ruby's bindings for Postgres to allow Ruby to talk to Postgres, and that lives in the package ecosystem on RubyGems. STEPHANIE: Yeah, I've certainly been in the position of, you know, again, as consultants, we oftentimes are also setting up new laptops entirely [laughs] like client laptops and such and bundling and the pg gem is installed. And then at least I have, you know, I have to give thanks to the very clear error message that [laughs] tells me that I don't have Postgres installed on my machine. Because when I mentioned, you know, troubleshooting earlier, I've certainly been in positions where it was really unclear what was going on in terms of the interaction between what I guess we're calling the Ruby package ecosystem and our system level one. JOËL: Especially for things like the pg gem, which need to compile against some existing libraries, those always get interesting where sometimes they'll fail to compile because there's a path to some C compiler that's not set correctly or something like that. For me, typically, that means I need to update the macOS command line tools or the Xcode command line tools; I forget what the name of that package is. And, usually, that does the trick. That might happen if I've upgraded my OS version recently and haven't downloaded the latest version of the command line tools. STEPHANIE: Yeah. Speaking of OS versions, I have a bit of a story to share about using...I've never said this name out loud, but I am pretty sure that it would just be pronounced as wkhtmltopdf [laughs]. For some reason, whenever I see words like that in my brain, I want to, like, make it into a pronounceable thing [laughs]. JOËL: Right, just insert some vowels in there. STEPHANIE: Yeah, wkhtmltopdf [laughs]. Anyway, that was being used in an app to generate PDF invoices or something. It's a pretty old tool. It's a CLI tool, and it's, as far as I can tell, it's been around for a long time but was recently no longer maintained. And so, as I was working on this app, I was running into a bug where that library was causing some issues with the PDF that was generated. So, I had to go down this route of actually finding a Ruby gem that would figure out which package binary to use, you know, based off of my system. And that worked great locally, and I was like, okay, cool, I fixed the issue. And then, once I pushed my change, it turns out that it did not work on CI because CI was running on Ubuntu. And I guess the binary didn't work with the latest version of Ubuntu that was running on CI, so there was just so many incompatibilities there. And I was wanting to fix this bug. But the next step I took was looking into community-provided packages because there just simply weren't any, like, up-to-date binaries that would likely work with these new operating systems. And I kind of stopped at that point because I just wasn't really sure, like, how trustworthy were these community packages. That was an ecosystem I didn't know enough about. In particular, I was having to install some using apt from, you know, just, like, some Linux community. But yeah, I think I normally have a little bit more experience and confidence in terms of the Ruby package ecosystem and can tell, like, what gems are popular, which ones are trustworthy. There are different heuristics I have for evaluating what dependency to pull in. But here I ended up just kind of bailing out of that endeavor because I just didn't have enough time to go down that rabbit hole. JOËL: It is interesting that learning how to evaluate packages is a skill you have to learn that varies from package community to package community. I know that when I used to be very involved with Elm, we would often have people who would come to the Elm community from the JavaScript community who were used to evaluating NPM packages. And one of the metrics that was very popular in the JavaScript community is just stars on GitHub. That's a really important metric. And that wasn't really much of a thing in the Elm community. And so, people would come and be like, "Wait, how do I know which package is good? I don't see any stars on GitHub." And then, it turns out that there are other metrics that people would use. And similarly, you know, in Ruby, there are different ways that you might use to evaluate Ruby gems that may or may not involve stars on GitHub. It might be something entirely different. STEPHANIE: Yeah. Speaking of that, I wanted to plug a website that I have used before called the Ruby Toolbox, and that gives some suggestions for open-source Ruby libraries of various categories. So, if you're looking for, like, a JSON parser, it has some of the more popular ones. If you're looking for, you know, it stores them by category, and I think it is also based on things like stars and forks like that, so that's a good one to know. JOËL: You could probably also look at something like download numbers to see what's popular, although sometimes it's sort of, like, an emergent gem that's more popular. Some of that almost you just need to be a little bit in the community, like, hearing, you know, maybe listening to podcasts like this one, subscribing to Ruby newsletters, going to conferences, things like that, and to realize, okay, maybe, you know, we had sort of an old staple for JSON parsing, but there's a new thing that's twice as fast. And this is sort of becoming the new standard, and the community is shifting towards that. You might not know that just by looking at raw stats. So, there's a human component to it as well. STEPHANIE: Yeah, absolutely. I think an extension of knowing how to evaluate different package systems is this question of like, how much does an average developer need to know about package management? [laughs] JOËL: Yeah, a little bit to a medium amount, and then if you're writing your own packages, you probably need to know a little bit more. But there are some things that are really maybe best left to the maintainers of package managers. Package managers are actually pretty complex pieces of software in terms of all of the dependency management and making sure that when you say, "Oh, I've got Rails, and this other gem, and this other gem, and it's going to find the exact versions of all those gems that play nicely together," that's non-trivial. As a sort of working developer, you don't need to know all of the algorithms or the graph theory or any of that that underlies a package manager to be able to be productive in your career. And even as a package developer, you probably don't need to really know a whole lot of that. STEPHANIE: Yeah, that makes sense. I actually had referred to our internal at thoughtbot here, our kind of, like, expectations for skill levels for developers. And I would say for an average developer, we kind of just expect a basic understanding of these more complex parts of our toolchain, I think, specifically, like, command line tools and package management. And I think I'd mentioned earlier that, for me, it is a very need-to-know basis. And so, yeah, when I was going down that little bit of exploration around why wkhtmltopdf [chuckles] wasn't working [chuckles], it was a bit of a twisty and turning journey where I, you know, wasn't really sure where to go. I was getting very obtuse error messages, and, you know, I had to dive deep into all these forums [laughs] for all the various platforms [laughs] about why libraries weren't working. And I think what I did come away with was that like, oh, like, even though I'm mostly working on my local machine for development, there was some amount of knowledge I needed to have about the systems that my CI and, you know, production servers are running on. The project I was working on happened to have, like, a Docker file for those environments, and, you know, kind of knowing how to configure them to install the packages I needed to install and just knowing a little bit about the different ways of doing that on systems outside of my usual daily workflows. JOËL: And I think that gets back to some of the interesting distinctions between what we might call language packages versus system packages is that language packages more or less work the same across all operating systems. They might have a build step that's slightly different or something like that, but system packages might be pretty different between different operating systems. So, development, for me, is a Mac, and I'm probably installing system packages via something like Homebrew. If I then want that Rails app to run on CI or some Linux server somewhere, I can't use Homebrew to install things there. It's going to be a slightly different package ecosystem. And so, now I need to find something that will install Postgres for Linux, something that will install, I guess, wkhtmltopdf [laughs] for Linux. And so, when I'm building that Docker file, that might be a little bit different for Mac versus for...or I guess when you run a Docker file, you're running a containerized system. So, the goal there is to make this system the same everywhere for everyone. But when you're setting that up, typically, it's more of a Linux-like system. And so running inside the Docker container versus outside on the native Mac might involve a totally different set of packages and a different package tool. As opposed to something like Bundler, you've got your gem file; you bundle install. It doesn't matter if you're on Linux or macOS. STEPHANIE: Yes, I think you're right. I think we kind of answered our own question at the top of the show [laughs] about differences and what do you need to know about them. And I also like how you pointed out, oh yeah, like, Docker is supposed to [laughs], you know, make sure that we're all developing in the same system, essentially. But, you know, sometimes you have different use cases for it. And, yeah, when you were talking about installing an application on your native Mac and using Homebrew, but even, you know, not everyone even uses Homebrew, right? You can install manually [laughs] through whatever official installer that application might provide. So, there's just so many different ways of doing something. And I had the thought that it's too bad that we both [chuckles] develop on Mac because it could be really interesting to get a Linux user's perspective in here. JOËL: You mentioned not installing via Homebrew. A kind of glaring example of that in my personal setup is that I use Postgres.app to manage Postgres on my machine rather than using Homebrew. I've just...over the years, the Homebrew version every time I upgrade my operating system or something, it's just such a pain to update, and I've lost too many hours to it, and Postgres.app just works, and so I've switched to that. Most other things, I'll use the Homebrew version, but Postgres it's now Postgres.app. It's not even a command line install, and it works fine for me. STEPHANIE: Nice. Yeah. That's interesting. That's a good tip. I'll have to look into that next time because I have also certainly had to just install so many [laughs] various versions of Postgres and figure out what's going on with them every time I upgrade my OS. I'm with you, though, in terms of the packages world I'm looking for, it works [laughs]. JOËL: So, you'd mentioned earlier that packages is sort of an area that's a bit of a need-to-know basis for you. Are there, like, particular moments in your career that you remember like, oh, that's the moment where I needed to, like, take some time and learn a little bit of the next level of packages? STEPHANIE: That's a great question. I think the very beginnings of understanding how package versions work when you have multiple projects on your machine; I just remember that being really confusing for me. When I started out, like, you know, as soon as I cloned my second repo [laughs], and was very confused about, like, I'm sure I went through the process of not installing gems using Bundler, and then just having so much chaos [laughs] wrecked in my development environment and, you know, having to ask someone, "I don't understand how this works. Like, why is it saying I have multiple versions of this library or whatever?" JOËL: Have you ever sudo gem installed a gem? STEPHANIE: Oh yeah, I definitely have. I can't [laughs], like, even give a good reason for why I have done it, but I probably was just, like, pulling my hair out, and that's what Stack Overflow told me to do. I don't know if I can recommend that, but it is [chuckles] one thing to do when you just are kind of totally stuck. JOËL: There was a time where I think that that was in the READMEs for most projects. STEPHANIE: Yeah, that's a really good point. JOËL: So, that's probably why a lot of people end up doing that, but then it tends to install it for your system Ruby rather than for...because if you're using something like Rbenv or RVM or ASDF to manage multiple Ruby versions, those end up being what's using or even Homebrew to manage your Ruby. It wouldn't be installing it for those versions of Ruby. It would be installing it for the one that shipped with your Mac. I actually...you know what? I don't even know if Mac still ships with Ruby. It used to. It used to ship with a really old version of Ruby, and so the advice was like, "Hey, every repo tells you to install it with sudo; don't do that. It will mess you up." STEPHANIE: Huh. I think Mac still does ship with Ruby, but don't quote me on that [laughter]. And I think that's really funny that, like, yeah, people were just writing those instructions in READMEs. And I'm glad that we've collectively [laughs] figured out that difference and want to, hopefully, not let other developers fall into that trap [laughs]. Do you have a particular memory or experience when you had to kind of level up your knowledge about the package ecosystem? JOËL: I think one sort of moment where I really had to level up is when I started really needing to understand how install paths worked, especially when you have, let's say, multiple versions of a gem installed because you have different projects. And you want to know, like, how does it know which one it's using? And then you see, oh, there are different paths that point to different directories with the installs. Or when you might have an executable you've installed via Homebrew, and it's like, oh yeah, so I've got this, like, command that I run on my shell, but actually that points to a very particular path, you know, in my Homebrew directory. But maybe it could also point to some, like, pre-installed system binaries or some other custom things I've done. So, there was a time where I had to really learn about how the path shell variable worked on a machine in order to really understand how the packages I installed were sometimes showing up when I invoked a binary and sometimes not. STEPHANIE: Yeah, that is another really great example that I have memories of [laughs] being really frustrated by, especially if...because, you know, we had talked earlier about all the different ways that you can install applications on your system, and you don't always know where they end up [laughs]. JOËL: And this particular memory is tied to debugging Postgres because, you know, you're installing Postgres, and some paths aren't working. Or maybe you try to update Postgres and now it's like, oh, but, like, I'm still loading the wrong one. And why does PSQL not do the thing that I think it does? And so, that forced me to learn a little bit about, like, under the hood, what happens when I type brew install PostgreSQL? And how does that mesh with the way my shell interprets commands and things like that? So, it was maybe a little bit of a painful experience but eye-opening and definitely then led to me, I think, being able to debug my setup much more effectively in the future. STEPHANIE: Yeah. I like that you also pointed out how it was interacting with your shell because that's, like, another can of worms, right? [laughs] In terms of just the complexity of how these things are talking to each other. JOËL: And for those of our listeners who are not familiar with this, there is a shell command that you can use called which, W-H-I-C-H. And you can prefix that in front of another command, and it will tell you the path that it's using for that binary. So, in my case, if I'm looking like, why is this PSQL behaving weirdly or seems to be using the old version, I can type 'which space psql', and it'll say, "Oh, it's going to this path." And I can look at it and be like, oh, it's using my system install of Postgres. It's not using the Homebrew one. Or, oh, maybe it's using the Homebrew install, not my Postgres.app version. I need to, like, tinker with the paths a little bit. So, that has definitely helped me debug my package system more than once. STEPHANIE: Yeah, that's a really good tip. I can recall just totally uninstalling everything [laughs] and reinstalling and fingers crossed it would figure out a route to the right thing [laughs]. JOËL: You know what? That works. It's not the, like, most precise solution but resetting your environment when all else fails it's not a bad solution. So, we've been talking a lot about what it's like to interact with a package ecosystem as developers, as users of packages, but what if you're a package developer? Sometimes, there's a very clear-cut place where to publish, and sometimes it's a little bit grayer. So, I could see, you know, I'm developing a database, and I want that to be on operating systems, probably should be a system-level package rather than a Ruby gem. But what if I'm building some kind of command line tool, and I write it in Ruby because I like writing Ruby? Should I publish that as a gem, or should I publish that as some kind of system package that's installed via Homebrew? Any opinions or heuristics that you would use to choose where to publish on one side or the other? STEPHANIE: As not a package developer [laughs], I can only answer from that point of view. That is interesting because if you publish on a, you know, like, a system repository, then yeah, like, you might get a lot more people using your tool out there because you're not just targeting a specific language's community. But I don't know if I have always enjoyed downloading various things to my system's OS. I think that actually, like, is a bit complicated for me or, like, I try to avoid it if I can because if something can be categorized or, like, containerized in a way that, like, feels right for my mental model, you know, if it's written in Ruby or something really related to things I use Ruby in, it could be nice to have that installed in my, like, systems RubyGems. But I would be really interested to hear if other people have opinions about where they might want to publish a package and what kind of developers they're hoping to find to use their tool. JOËL: I like the heuristic that you mentioned here, the idea of who the audience is because, yeah, as a Ruby developer who already has a Ruby setup, it might be easier for me to install something via a gem. But if I'm not a Ruby developer who wants to use the packages maybe a little bit more generic, you know, let's say, I don't know, it's some sort of command line tool for interacting with GitHub or something like that. And, like, it happens to be written in Ruby, but you don't particularly care about that as a user of this. Maybe you don't have Ruby installed and now you've got to, like, juggle, like, oh, what is RubyGems, and Bundler, and all this stuff? And I've definitely felt that occasionally downloading packages sort of like, oh, this is a Python package. And you're going to need to, like, set up all this stuff. And it's maybe designed for a Python audience. And so, it's like, oh, you're going to set up a virtual environment and all these things. I'm like, I just want your command line tools. I don't want to install a whole language. And so, sometimes there can be some frustration there. STEPHANIE: Yeah, that is very true. Before you even said that, I was like, oh, I've definitely wanted to download a command line tool and be like, first install [laughs] Python. And I'm like, nope, I'm bailing out of this. JOËL: On the other hand, as a developer, it can be a lot harder to write something that's a bit more cross-platform and managing all that. And I've had to deal a little bit with this for thoughtbot's Parity tool, which is a command-line tool for working with Heroku. It allows you to basically run commands on either staging or production by giving you a staging command and a production command for common Heroku CLI tasks, which makes it really nice if you're working and you're having to do some local, some development, some staging, and some production things all from your command line. It initially started as a gem, and we thought, you know what? This is mostly command line, and it's not just Rubyists who use Heroku. Let's try to put this on Homebrew. But then it depends on Ruby because it's written in Ruby. And now we had to make sure that we marked Ruby as a dependency in Homebrew, which meant that Homebrew would then also pull in Ruby as a dependency. And that got a little bit messy. For a while, we even experimented with sort of briefly available technology called Traveling Ruby that allowed you to embed Ruby in your binary, and you could compile against that. That had some drawbacks. So, we ended up rolling that back as well. And eventually, just for maintenance ease, we went back to making this a Ruby gem and saying, "Look, you install it via RubyGems." It does mean that we're targeting more of the Ruby community. It's going to be a little bit harder for other people to install, but it is easier for us to maintain. STEPHANIE: That's really interesting. I didn't know that history about Parity. It's a tool that I have used recently and really enjoyed. But yeah, I think I remember someone having some issues between installing it as a gem and installing it via Homebrew and some conflicts there as well. So, I can also see how trying to decide or maybe going down one path and then realizing, oh, like, maybe we want to try something else is certainly not trivial. JOËL: I think, in me, I have a little bit of the idealist and the pragmatist that fight. The idealist says, "Hey, if it's not, like, aimed for Ruby developers as a, like, you can pull this into your codebase, if it's just command line tools and the fact that it's written in Ruby is an implementation detail, that should be a system package. Do not distribute binaries via RubyGems." That's the idealist in me. The pragmatist says, "Oh, that's a lot of work and not always worth it for both the maintainers and sometimes for the users, and so it's totally okay to ship binaries as RubyGems." STEPHANIE: I was totally thinking that I'm sure that you've been in that position of being a user and trying to download a system package and then seeing it start to download, like, another language. And you're like, wait, what? [laughter] That's not what I want. JOËL: So, you and I have shared some of our heuristics in the way we approach this problem. Now, I'm curious to hear from the audience. What are some heuristics that you use to decide whether your package is better shipped on RubyGems versus, let's say, Homebrew? Or maybe as a user, what do you prefer to consume? STEPHANIE: Yes. And speaking of getting listener feedback, we're also looking for some listener questions. We're hoping to do a bit of a grab-bag episode where we answer your questions. So, if you have anything that you're wanting to hear me and Joël's thoughts on, write us at [email protected]. JOËL: 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 [email protected] via email. JOËL: Thanks so much for listening to The Bike Shed, and we'll see you next week. ALL: Byeeeeeeee!!!!!!! AD: Did you know thoughtbot has a referral program? If you introduce us to someone looking for a design or development partner, we will compensate you if they decide to work with us. More info on our website at: tbot.io/referral. Or you can email us at [email protected] with any questions.Support The Bike Shed