Crafting Code Podcast

~/podcast

$ cd episodes/021-crafting-katas

~/podcast/episodes/021-crafting-katas $ ls -1a
. .. episode-summary.txt references.txt themes.txt transcript.txt get-mp3.sh
~/podcast/episodes/021-crafting-katas $ cat episode-summary.txt

In this episode, your hosts (finally!) share some actual code examples to illustrate some of our thoughts about crafting code. Follow along with the code at https://github.com/crafting-code-podcast/crafting-katas as we discuss how we implemented Conway's Game of Life in multiple languages. Testing strategies, optimizations, and feedback loops all demonstrate that crafting code is ultimately an iterative process.

~/podcast/episodes/021-crafting-katas $ cat references.txt ~/podcast/episodes/021-crafting-katas $ cat themes.txt ~/podcast/episodes/021-crafting-katas
$ cat transcript.txt

[00:00:16] Allan Stewart: Welcome to episode 21 of the Crafting Code podcast, where we discuss the importance of doing the right thing at the right time with the right tools. I'm Allan Stewart, a software architect, and lately I've been thinking about how to take care of a puppy.

[00:00:31] Dave Adsit: I'm Dave Adsit, VP of Engineering, and recently I've been thinking about Kaizen versus Kaikaku, or incremental improvement versus radical change in socio-technical systems.

[00:00:43] Matt Baker: My name's Matt. Lately I've been thinking about Vim.

[00:00:48] Allan Stewart: Today's episode topic is crafting katas. We talk a lot about crafting code. It's the name of our podcast. But so far we hadn't actually shared any code or gone over code and talked about what kinds of things we think about and what we believe is involved with crafting code. So we decided to work on a code kata, a small problem that we can practice on that can be used to illustrate concepts and practice as a way to share with all of our listeners what crafting code means to us with some actual code.

[00:01:25] Dave Adsit: The kata that we selected is Conway's Game of Life. Conway's Game of Life is described as a self-playing game where an initial state is set up and then the game follows simple rules to produce sometimes interesting and often very uninteresting results. It was originally proposed in 1970, and there have been many, many, many implementations on many systems with a lot of different programming languages ever since. In fact, I have personally implemented it many times just at code retreats. If you have the opportunity to participate in a code retreat, you'll be like, why are we always doing the same challenge? And the reason behind that is that this challenge, while simple, has enough complexity that it can actually be interesting and useful in a learning environment. So I mentioned that it's a system. It's a self-playing game with simple rules. The rules are that there is an infinite grid of cells. In that grid, cells can be either alive or dead. And if cells have the right number of neighbors, they persist into the next generation. So if you are a living cell with two or three living neighbors, you live on because you've got the right number of neighbors. And if you had too many, you die. As though by overpopulation. If you have too few, you die. As though by loneliness, perhaps. If you have a cell that is dead and it has exactly three living neighbors, it comes to life. Reproduction, who knows? Those are the rules. They're pretty simple, but you have to do some interesting things to implement each tick of the clock or each iteration of the board. So let's jump in and look at the code that we wrote. We have created on. GitHub.com in our crafting code podcast organization, which is crafting dash code dash podcast, a repo crafting dash katas that has a main branch with a readme that explains the rules from that main branch. Each of us has created a branch for an implementation that we tried. So there's a few implementations out there and we're going to talk through them. You can find the link.

[00:03:55] Allan Stewart: To this repository in the show notes, either on our webpage or in your podcast feed.

[00:04:02] Dave Adsit: So the first branch we're going to talk about is Allan C sharp, which Allan happened to write, and he used C sharp as the programming language of choice.

[00:04:13] Allan Stewart: It's a big surprise. I know. When starting this one, I hadn't done game of life in a while. It's probably been a couple of years. I haven't been to a global day of code retreat for a while. So I just. I tried to really start with some small TDD steps. And one thing that I do like about what I did this time around is making sure that I committed each small step that I made. And so if you do look at the commit history, you'll see that there's a whole bunch of commits for a very small amount of work because I was trying to be really intentional about what each step was in the process. So in order to do the test, I wanted to get started. I was thinking about it. In terms of this concept of there's a grid. And so that's really where I started is saying, Hey, well, can I create a grid? And kind of the classical way of doing this is with two dimensional arrays. Two dimensional arrays is a great way to represent the board and it gives you basically direct access to each cell without having to do looping. Two dimensional arrays are kind of odd in C sharp, in my opinion, as compared to some other languages. And so it took me. A little bit to make sure that I was doing what I thought I was doing. So I started off with the grid with columns and rows. And so I just kind of kept that column and row concept going through the whole implementation. Once I had a grid, then I could set some live cells on it and then I could check, Hey, is it there? Is it within the bounds of the game? Because as soon as you start using a two dimensional grid, you've created a finite space for the game of life, which is certainly valid. Yeah. It's like in as much as you can do anything you want. Conway's not going to come over to your house and force you to write it as an infinite array. And the C sharp compiler won't let you create it as an infinite array. Like you have to have some kind of boundaries there. And so that's the way I decided to start off with just making it so that it's pretty flexible in as far as how, how big I could make it. But I don't think I went ever beyond like seven columns and rows. As soon as I had the grid there, then I started. Doing all the tests for conditions. And I really focused on kind of one cell in the middle of a three by three space. What does it do with each rule? And once I had gotten that done, then I kind of expanded it out and I got to a point where I really wanted to test some patterns to make sure that I had done things correctly. There are a number of patterns that exist for Conway's game of life that are documented. They, people have given them names like the boat or the glider. Or a blinker. And so I went to do that and realized I kept getting coordinates mixed up. And so I decided to create a new way of testing so I can have a string representation. So I could visualize what it was that I was testing, making it a little bit easier for me as a human to understand what this thing was and how it was going to behave. So revamped my tests. Finished those tests. So if you look in the Allan dash C sharp. Branch, you'll see all of them there. Grid and grid tests are the first two that I started with. And I got to a place where I was pretty happy with how the tests were going. Pretty happy with the implementation. One of the things that I really liked was when I was trying to count how many live neighbors there are. I use this is a live app method that allowed me to not have to worry about the boundaries of the grid everywhere. Only when I'm checking if a cell is alive, do I care about, so like sometimes. I'll check outside of the bounds of the grid and it's like, Nope, you're not alive. That's the void. Everything's dead out there. This is the bottom of the Minecraft world where you just fall to your death because there's nothing left. I was pretty happy with that, but it did bug me that it was finite. So I put it on the shelf for a little while. I think the next day I got to thinking about it again. It's like, you know what? I could do an infinite grid. And so I created another implementation and I thought that because of how I did the strings. For testing, it would make it easier. I'm just going to basically copy and paste all of my tests and I'll just follow the same testing pattern that I had followed. And it'll be even easier than the first time around because now there's already like this string representation. But it turned out that I had to clip a lot of the strings because now that it's an infinite grid, I had this problem of, Oh, well, how do you represent an infinite grid in a string? And so what I decided to do was, well, what is the furthest. Top left corner of the grid and the bottom right corner. And I'm just going to render that piece of it. And as soon as you do something like a glider that moves across the board over generations, then in some ways it made it more elegant because the glider eventually comes back to its same state, the same shape. But it's in a different part of the board. And so it's moved, but you don't see the movement through the test, but it's returned to its same initial starting condition, which had a kind of a nice simulation. To it, but there, there was some weirdness there, but I didn't, didn't really anticipate as I went in to craft that code, I expected it was gonna be easy. I expected that, I just copy and paste each test, one by one, figure out a few little details and it would be done. And then it took me longer than it anticipated, because, like, pretty much every time that you write code, implementation, details matter a lot, and you start doing something very different than you had planned in your head, which is one of the reasons, why I love TDD because it gives me kind of a context. Don't just go off completely into the weeds on some implementation detail, you know, on some wild harebrained idea that you have, but let the test kind of constrain you to, well, can you make that test pass? Great. Now do the next, now do the next. I'm reading through your tests for common patterns,

[00:10:17] Matt Baker: like a block beehive and a boat and blinker. Those are cool tests. I think that's a neat idea. I feel like you went out and probed the domain a little bit and found some, some interesting cases that you could test against to ensure that your engine was working correctly. And that's a cool

[00:10:34] Allan Stewart: way to do it. Yeah. I've often found like, if I focus too small, I've only thought about the rules, the four rules that are on the readme, then sometimes I don't actually have a proper implementation because when you actually try to run it in a bigger size of grid, or like you get to like boundary cases, especially on the edges of something. Yeah. There's some boundary there, right? Like there's the boundary of like the two-dimensional grid and you just, you fall off the world into the void or else there's the boundary of you're an infinite grid, but what do you do with all of the empty space? And in my implementation, as you see there, I only track the live cells, which is probably more memory efficient in some way. But like, if you were going to actually make it a real infinite grid, there's a ton of looping in my code. And then I'm using links. And so there's fewer for loops. Like you don't see the loop, but there are looping structures happening in there. And so if you had a really, really big grid with lots of live cells, it wouldn't necessarily be very performant, which itself becomes an interesting challenge, right? Like how do you segregate an infinite grid into smaller, like sub grids? It reminds me of when I was in college, I took a class for doing 3d rendering. And we talked about different techniques

[00:12:22] Matt Baker: and fewer than 10% change every round. So I think you're absolutely right. By only storing the alive ones, you, I think, have a sizable optimization.

[00:12:34] Allan Stewart: Which goes back to what Dave was saying, right? Like this is a really cool problem that can extend very far. I've seen cool YouTube videos about Conway's Game of Life, where they show just these massive grids that are doing things, and they've got gliders that are crashing into each other to form particular patterns. And yeah, I think somebody has claimed that it's Turing complete. Oh, cool. So you could create a computer out of the Game of Life.

[00:13:03] Dave Adsit: I have heard that it is Turing complete, and with the correct engine, you can create Game of Life on top of Game of Life. Cool. I was just looking at, specifically when testing a boat, and I was looking at that pattern. I'm like, what does that do? I did not realize that the boat is one of the still lifes, which are the things that just stop changing because they're in perfect harmony with all the cells around them. Looking it up. A boat. So here's a fun way to cheat if you want to see what a shape does, is Google Conway's Game of Life. In the Google search results, there is an overlay that plays the Game of Life with a somewhat interesting start point.

[00:13:51] Matt Baker: I saw that last week.

[00:13:54] Dave Adsit: You can pause it, and you can click on different cells to alter their state to see what they're going to do.

[00:14:00] Matt Baker: Pretty cool. Good job, Google.

[00:14:03] Dave Adsit: Yeah, right? That's one of their better Easter eggs, in my opinion. Yeah, so I really liked it. I think one of the things that I've seen often when I've taught people Game of Life or watched people code Game of Life for the first time is that nobody knows how to do a two-dimensional array in their typical process. because that's not the kind of problem we typically solve anymore.

[00:14:27] Allan Stewart: Yeah. So. And do you even use an array, right? Like in a lot of languages, arrays are fixed length. And so you're using a vector or a list or something that's not actually array.

[00:14:40] Dave Adsit: Yeah. And then what is it an array of? Or what is it a slice of? Should it be a two-dimensional array of booleans or a two-dimensional array of integers? Or bits? Bits?

[00:14:54] Matt Baker: Could be a byte, right?

[00:14:56] Dave Adsit: Yeah.

[00:14:56] Matt Baker: I read about a cool implementation that did byte encoding. So each cell is a byte. And there were some optimizations that I'm not going to go into, but they ended up having the bit zero would be the state of the current cell. And then bits one through four represented the state of every neighbor. And then five through seven were just abandoned. They weren't used. But there were some pretty cool optimizations that they could put together with that encoding. So that was a pretty neat way to encode a cell.

[00:15:28] Allan Stewart: Nice. So every time you look at your cell, you're looking at the neighbors as well. You know what the thing is.

[00:15:34] Matt Baker: That's cool. And then there was a, it has a nice property that when the cell is dead, the bit's zero. And so you can just scan for zero bits to quickly get to cells that are alive. It was this like game of optimizations that I was reading about and they were trying to speed it up. on a certain process or with certain constraints. And this bit encoding gave them, give them some progress.

[00:15:55] Dave Adsit: That's really cool. I thought it was interesting to see your two implementations when I pulled them down originally. And I thought, oh, I also noticed that the tests for grid and infinite grid were not the same. Oh, did he just start all the way over? But no, you tried to use the tests. And it turns out that the tests you start with drive the implementation you end up with. And to some extent, vice versa. The tests for the original grid, the grid test file with all of its test cases, those were pretty straightforward and easy to read. And then when I went to the infinite grid tests, I'm like, well, these are completely different style of testing. And I mean, you introduced test cases for one. I mean, I guess there's test cases on both sides, right? But the tests did not have the same feel to them because they were trying to drive a different implementation, I guess. Sure, makes sense. Things that- Makes sense. Things that you said about not being able to represent the whole grid in the test, but instead have to represent a subset of the whole grid based on what is of interest. Where are there cells that are alive?

[00:17:04] Allan Stewart: Right. And I actually tried to use those same tests. I went one by one and like copied things over. And as I did that, almost all of them had to change in some subtle way because the implementation was different. And you know, if there had been a common, like there was almost a common interface between them, I think I could probably come up with one that would work for both the finite and the infinite grid or at least a subset of them. But I didn't feel like doing that.

[00:17:34] Dave Adsit: So one more thing I'd like to talk about before we move on to the next implementation is that there is nothing in our specification that required you to parse a string or render a string. Those functions, those features seem to have been added specifically to make testing and verification of the required features easier. I like that. I think sometimes we get hung up on not adding aspects of the code that would make it easier for us to accomplish things like testing or just general inspection and debugging. And when we get to my C sharp version, you'll see that I did some similar things, but in a very different way.

[00:18:18] Allan Stewart: Yeah. I think that's really important. You know, we were talking about like the general concept of crafting code. There are a lot of things that you can do to make your life easier, these quality of life improvements that you make to your code. And just because it's not being used by anybody else, that's fine. You can optimize that away later. One of my favorite things for testing is that I will take things that are private in a class and make them public because I tend to use a lot of interfaces with dependency injection. And so on the interface, that method never shows up. You can't call it from the interface, but when you're testing the class directly, you've got an instance of that class specifically, and you can call these other methods that normally would be private, but I've made them public for the purposes of testing.

[00:19:05] Dave Adsit: As I've dug more into other programming languages in the last several months, I have seen similar strategies or rather different implementations of the same concept. For example, Go has packages, you have private scope package, but you have test packages right there. And the test package can read the code from the non-test package and therefore call the private members. So you get a very similar outcome. The package has a public interface that can be accessed, but if you are inside the package at a test, you can access the privates. So very cool. I think it's, again, a critical concept for delivering value in a software system is to consider that the total value we're delivering includes the verification that the code actually tests the right thing.

[00:20:00] Matt Baker: Yeah, the functionality is the deliverable, not the code. If you haven't tested that it worked, I'm not sure what you're delivering. You're delivering a hope. I hope this works.

[00:20:16] Dave Adsit: So speaking of hope, do you want to, show us how you hope your Python code works, Matt?

[00:20:22] Matt Baker: Well, I haven't tested it yet. I might play Pac-Man, I'm not sure. Yeah, I'll talk about mine a little bit. Before I do, there's something really nice about the interface versus implementation thing that Allan talked about in C-sharp. I don't know why, but like the, it soothes something in my brain. When you say, yeah, you can test this thing if you load it up in a test context, like if you compose the concrete, or if you invoke the constructor on the concrete, you can get these test methods. But most of the time when you get this thing, it's going to come through dependency injection and the interface will be applied and they're stripped. There's something really nice about that. Feels like there's just an elegance to it.

[00:21:11] Allan Stewart: Yeah, I think I've never really loved duct typing. Mm-hm. And I can kind of respect where it comes from and why people like it. Like I can see that there's motivations behind it, but for me, it's just never clicked. And so the interface concept is really nice, in my opinion, because that way you can understand, hey, this is how something is supposed to be used without having to worry about the implementation details. And like, there's a structure in the language that helps you do that. And it's really interesting to me then that TypeScript comes along and that's one of the major things that's getting added. You're getting types and the types themselves are a form of interface telling you what you can and cannot do with this object. It's interesting to see the different pendulum swings in the industry as people try different things and what works for different languages and different projects. Because I think even within the same language, there are times where the strict typing sometimes annoys me in C sharp. Mostly I like it, but there are times where, man, I just love jumping into just raw vanilla JavaScript where you can do whatever you want, no matter how terrible of an idea it is.

[00:22:30] Dave Adsit: So I learned very recently that the formal name of that style of programming language is structural typing. Okay. Okay. Okay. Okay. Okay. Okay. duck. I know what a duck is. If it walks like a duck and it quacks like a duck, it's a duck.

[00:23:19] Matt Baker: We're good. Okay. So I will talk about my Python implementation a little bit. The past seven or eight months, I've been writing a lot of Python code and more of a REPL driven development style. And so I thought I would do the same thing here. I did not take as much care as Allan did to commit in a way that would show my thought process. I think I just have one big commit at the end actually. So I'll try and capture some of my evolution. I'll just try and tell the story as I go, but in the Python file or in my implementation at the bottom of it, there's a function called generate. I originally started writing tests for that function and that function is responsible for taking a grid, applying the the rules of a generation and game of life, and then returning what the grid looks like after that. I chose to fix my grid to a three by three, kind of just dodged the infinite plane issue altogether, mainly for time. I wanted to make sure I got it done. So mine's a fixed three by three and my grid wraps. That is to say, if you're on the top row of a three by three matrix and you get to the furthest to the right column, if it's zero based column two, and you need to check the right neighbor, it would wrap to column zero of the first row. That's another property of my grid that you'll see in my code. The generate function, I pretty quickly stopped writing tests for it. I wrote four or five tests and then decided that it was just, I was figuring out too much entering at this generate space, I guess. So I decided to back up and I wrote more granular tests. They kind of fell into two broad categories. My tests are either testing the retrieval of a neighbor. So from a given row and column and a given neighbor, does it retrieve the right state? You'll see a bunch of examples of that in my implementation, starting at line four test, the test get top left neighbor state. So that's saying from some coordinates, give me the top left neighbor. So if we're in the game of life rules, you go up one and then over one. So the first set of tests is the, can I access the neighbor correctly given the grid? And then the second set is for a given cell state and these neighbors, what happens to the cell? So that's sort of how I factored the problem. Once I concluded that once you are able to access the neighbors, it's just a matter of given this state and this many living neighbors, what should I do? So the second group of tests cover that idea. So I drove it mainly. Mainly from the REPL. I didn't call that generate function again, until I wrote all the neighbor access tests and all the cell state tests. Even in the REPL, I just backed off of that. And I wrote a little test runner that you'll see up on line 189. And it just takes a collection of the tests and runs them and appends the results. A little sidebar, my tests do yield, a single test will yield multiple test results. I saw some headline floating around recently that was, under any one of the tests. And I actually liked that pattern. It made the writing the test harness super easy because then I could just loop through each test and then each result.

[00:27:16] Allan Stewart: I think it's interesting what you're saying earlier that you started at one point, you started with the grid. You're like, okay, all I have to do is generate this grid. And there's only these four rules. It's not that complicated, except for that it kind of is. And how often does that match up with the way we really work on problems at work? It's very common for me where I look at something. It's like, oh, this shouldn't be too hard. I just start going in. I have it in my mind. This is the direction that it's going to take. But when I get into it, it turns out that there's something else needed. And breaking down the problem into smaller pieces helps so much, especially for testing. The setup for a single test can get really massive and complicated if you're trying to test everything all the way down. And so if you can pick a smaller layer of abstraction, test it, put a check mark or a bow on it and say, yep, this one's done. That kind of goes back to our last episode. We talked about Dijkstra and provability and how he didn't love testing. But if you use testing as a form of proof, you say, hey, I know that this thing does the right thing. Then I can just assert a higher level of abstraction, test that without worrying about the low level. I agree.

[00:28:28] Matt Baker: And I like what you said about it being representative of the way we typically work. I think it was actually in that Dijkstra. He talked about factoring a problem and the importance of playing with different decompositions of the problem and finding the right one. And I've been playing with that a little bit. There's another paper I read recently that emphasized the need to immerse yourself in the facts of the problem, whatever they are, like in this case, the rules of Game of Life, right? And then step away from it for a little bit and just let your right brain go to work, kind of digesting that. And eventually you'll have some, you'll have some aha moments that help you understand the problem a little bit better. And when I wrote the generate test first and then backed off, not because I was being intentional, just happened to work out that way. I shut down for the night and came back and did my new approach, you know, that I just talked about a couple of days later. When I sat down, I already had a plan when I sat down based on my experience trying to test generate, just what Allan had said. And it was kind of neat, you know? So I was able to step away and experience what Allan just said. Like testing from generate's too hard. There's too much going on. I don't want to have to account for all this. All I really want to test right now is can I access a neighbor? And then that's what brought the first test, first set of tests about, you know, okay, well, if I can access a neighbor, great. Now, what do I have to do next? Well, I have to be able to use that count of live neighbors to understand the cell state. Okay. Well, that's how I'm going to write that. And that was the rest of the process that I took, but it was kind of neat to leave it for a day or so, and then come back. And even before I booted it, I booted it up and looked at the code. I knew where I was going. Like I already knew I wanted to break it out and to select the neighbors and then test cell state. And I don't know a piece of code I've written that hasn't gone that way. You know, it happens to me every day when I'm writing code for work. I try something, realize what I didn't know about it, step away and try again.

[00:30:23] Allan Stewart: Yeah. The other related aspect that I like about your implementation is that you decided to really focus in and say, hey, this is a three by three grid. And sometimes applying those constraints to a problem can make a huge difference, especially when you're doing feature work. It's really easy to take a new feature and say, oh, well, here's all of the things that we're going to need. Here's all the bells and whistles. These are all the ramifications of doing this. But if you set up a constraint and say, hey, we're not going to try and do everything, it's only going to work in this one particular way for now. It gets you going. You can build it. You can learn from building that, and then you can evolve the other pieces. And then when they're needed, because if you do the big bang approach, the big splash approach, that you're going to release it with all of the bells and whistles, you're adding a ton of risk to the project.

[00:31:16] Matt Baker: I agree. I'm at the point now where I was going to say completely, maybe not completely, but most of the time I value just delivering over delaying and deliberating. Not because I'm not going to be wrong, but it's precisely because I'm going to be wrong. That'll get me a little bit closer to right. Right? I get some feedback. I figure something out, and I'll move forward a little bit more. And I don't know another way to write software at this point. Like just lo-fi deliver something, and then see how it hits you. You'll get your feedback, and then adjust and do it again, and just keep doing that. Not to oversimplify it, but sometimes it feels like that's the playbook I follow every day.

[00:31:58] Allan Stewart: Yeah. Well, it's effective, right? Because we're human. We make mistakes. Instead of trying to say, hey, we're going to not make mistakes anymore, you put up the appropriate guardrails. You make it so it's easy to do the right thing. Because I think there's a lot of junior developers, or at least ones that I've talked to, they're new in their career, and they look at the work that is being done by other people and are like, how? How does this happen? How come you don't have these problems and you don't get stuck? Whereas they're feeling like they're really stuck and unable to move forward. And the trick is not that, you know, you're not going to get stuck, I make the same number of typos as I've ever done. I'm still kind of terrible at typing on a keyboard. But I've learned how to correct it really fast. I figured out what the feedback loops are that tell me, no, you screwed up again, the same way that you always screw up. Or I put a barrier in place. It's like, you can't screw that up again, because there's automation in place or something like that. So it's not the absence of mistakes. It's the. The presence of these other factors that create safety.

[00:33:06] Dave Adsit: So you have been watching me try to copy paste on a Mac keyboard. I will forever hit command B when I try to paste. Which does interesting things in various applications. One of the things I was thinking about as you guys were talking through that is that I've heard a very similar sentiment many times that you have to build the same system five or six times. Or you have to read code 10 times as often as you write it in order to make sure that it's the correct code. And I was like, who said that? Who said that? And then I realized that these same concepts are reiterated over and over and over. I'm pretty sure I've heard Bob Martin and Michael Feathers and Josh, Josh Creevsky. I can't, I don't know how to say his name. All say similar things about not getting it right. The first time. So making sure that you're putting the system together in a way that you can iterate and iterate and iterate until it is actually right. And I love that because I don't ever guess right the first time. And if I do, it's because I've guessed wrong a bunch of other times and a bunch of other projects and this time I just happened to be able to draw on that existing experience.

[00:34:25] Matt Baker: Yeah, there's this shows up for me on a few planes on the feature plane. Did you get the requirements right? Do you understand? user needs and you can use the strategy to short circuit that too right yeah and a lot of cases just put something in front of the customer and then there's also the code factor side for me early in my career i would sometimes get stuck wondering if i was using the right pattern or doing the right approach or whatever and i don't even think about that stuff anymore i write the code i'm not going to say sloppy but i don't spend time thinking like does it need to be this pattern or that and after i write it or enough of it those things just kind of emerge and they almost fall into place if you allow them the time to fall into place it's like oh this is a clearly a good candidate for such and such pattern but you don't you don't lead with that i don't lead with that anymore early in my career i would struggle with wondering if i was writing the code correctly and correctly not in the sense of is it doing what i want it to do but is it written the way that it should be written and that was a like quicksand i could fall into and it would slow me down from delivering and um and it's most chronic i think it causes you to try and like over factor a whole system up front you know because you're trying to anticipate like, well, I'm going to need to do these things and be able to pivot this way. But whatever, you design a lot of crap you don't need. And so just later on in my career, I've noticed that I don't worry as much about that. I'm much more a fan of emergent properties of a system now where I try and deliver it in the most plain way I possibly can. And then if I see a need for a pattern, I'll adopt it. If not, I won't. I like that. All right. So there's a few nitpicks I have with my code after having read it or letting it sit for a little while and reading it now in preparation for this episode. An example of the nitpick is on line 134 in my solution. Each one of my tests yields a little dictionary that contains a few things, the name of the test and this particular test on line 134 and 135, the row I was testing, the cell I was testing, and there's also a state member or a state property. have where I didn't use the correct language there. I should have disambiguated. I think if I could do it again, I might say online 134 instead of state, that'd be called test state. Um, and then I could not have to worry about the overloading of the term. I tried to write my code optimizing for conceptual understanding. Uh, that is to say I wanted it to be easy to read. I didn't want there to be a lot of space between the rules. The rules as stated for game of life and my code. And that, that does some interesting things. It's probably not very performant. I read a couple of chapters out of a book on game programming while writing this, uh, about the Conway's game of life. This guy wrote a, anyway, he wrote a few chapters about different ways that you might optimize game of life for performance. And in the end, it got very far away from conceptual simplicity. Like. There was one guy that generated every possible combination, every single cell in a 200 by 200 grid in assembly code in a lookup. So he could, uh, I can't remember the key of the lookup offhand, but anyway, just this crazy stuff that was super, super for performance, but would probably take me a long time to grok what was going on. And I just think that's an interesting comment when we're writing code. Uh, typically I think we optimize for readability at first. Or like conceptual simplicity, right? We want our abstractions to be as close to the thing that we're trying to build as possible, I think it's just the programmer's way. But then you get in these cases where you're no longer optimizing for readability. And then like, in this case, it might've been performance and it's a trade off. Maybe you put a comment over that line or name the file, something like warning, like heavily optimized for a reason code.

[00:38:49] Allan Stewart: Speaking of those trade-offs one trade-off that probably occurs in the game. And a case like that is, are the rules going to change? Conway's game of life is really well understood. The rules don't change. Actually, there are many variations on it where you can change the rules and learn interesting things about it. But if you're just doing the standard rules, they do not change. And so if you do that huge optimization about, you know, your 200 by 200 blocks. Yeah. You can make an amazing optimization, but if there's a mistake in there, or if somebody says, Hey, we want to make a subtle change. Change to the rule, all that work went away, right? It's just, you have to throw it away because it's not useful anymore. And that's the trade-off that you're making. You can make it super performant as long as you're sure that it's not going to change. But if, if changing code and the malleability of that code is important, then you have to take that into consideration how you develop code.

[00:39:45] Matt Baker: Yeah. I just imagine like this portion of code, hopefully you contain it. If you have to do something like that, but just with big warning signs, her, I'll. You know, and, and very verbose, probably comments that explain exactly why this is why, you know, the hopefully start with something like you probably think this is weird and unreadable, here's my motivation. And then here's step-by-step, you know, how I, how I did it. And I, I hope you would do that. Anyway, if you're being kind to your fellow coworkers have to live in S N code. I'll I'll say one last thing on my implementation. At the very bottom of the file there's a function called default grid. It just returns a grid. Right. Right. Right. are dead. I was using that in my REPL development. I would pull out a grid and flip a few of the bits, so to speak, and then push it through generate and see what it would do. At the end of it, I ended up using it on line 272, my next generation variable. The first thing I do in my generate function is set up the next generation variable. And I set it to default grid. I didn't do this at first. I was modifying the grid in place for about a half an hour wondering why it wasn't working. And I was getting such weird results. Then I realized that I was interweaving the current generation and the next generation, like mid-flight, right? And then I was like, oh, okay, well, I just need a new generation to build off of. And then it's like, oh, I have this default grid function. I'll just use this. It was a cool example of something I wrote purely for the REPL-driven development all of a sudden becoming useful in my implementation. I thought

[00:41:17] Dave Adsit: that was pretty neat. Matt, you fell for one of the classic blunders, using a map. I didn't expect that.

[00:41:28] Matt Baker: It was only half an hour.

[00:41:32] Dave Adsit: It only took a half an hour to realize you were overriding your source data with your target data and you had no idea what was going on. That's not bad.

[00:41:41] Matt Baker: Yeah. And it was one of those things where I'm like, it was kind of late and I'm looking at it like, I think I've screwed this whole thing up. Like nothing is behaving as expected. Like I would run the test individually. I'm like, why is it working? And then finally it's like, oh yeah. So basically line 286, instead of saying next generation row cell said grid row cell. I just sat and the whole thing went to stew pretty quick.

[00:42:12] Allan Stewart: Right. Well, let's next take a look at the Dave C sharp branch.

[00:42:17] Dave Adsit: Awesome. So let's see the very first thing that I did when I started on this. This process was right. A test for the rules. I looked at the whole thing and I thought this feels complex. What's a part of it that I know I can do correctly right away and get a quick win. And the rules for whether a cell would be alive or dead based on the number of neighbors and its current state felt like a place that I could do that. I could implement some code easily and get some progress. And see where that led me and see if the problem could percolate in the back of my brain. And I could come up with some way to address the world and the iterations and all that. So I wrote the either two or 18 tests in rules test first, depending on how you count them.

[00:43:13] Matt Baker: I like your tables. I like the, that's another thing. Not, I'm sorry. I'm just similar to the abstraction. I like the interface and the concrete thing that we're talking about earlier, how Allan concealed his tests. I get the same kind of pleasure from these, I guess you'd call these table driven tests. They're just nice. There's something nice about them where when you write the test and you can just sit and stack these test cases on top of it. And those test cases aren't terribly complex. I feel like that's a sign you factored well.

[00:43:43] Dave Adsit: Yeah, I like it. I like the test cases in the in-unit framework really make it easy to say, Hey, all of these things are kind of similar. It's like a class of tests that I want to bundle together. So the first test is living cells. Next status. Okay. So what is the next status? Well, it depends on how many current neighbors you have that are alive. And so I've got one simple assertion. It did create a little bit of complexity. I would say, I think a lot of people who don't like C-based languages, especially Java and C-sharp would say, look, I've got this. Look at all that unnecessary boilerplate where you created a whole class for rules. And then you had a public method. And I went so far as to expose an interface because I thought I was going to need it for testing. In fact, I don't think I exposed the interface here. I think I exposed it later, but it led to really simple code. I was actually a little bit surprised at how simple the rule was for calculating next state. So if you look at the rules CS, two if statements and a bare return. So if the living neighbors are three, you're alive. If the current status alive and the living neighbors is two, then you're alive. So like you're already living, you're going to stay alive, right? That's expressed as a rule in the test. There's a test case for that. And the test makes it pass. And that's, it seems pretty straightforward. Otherwise you're dead. Like there's a lot of tests where the cell ends up dead at the end. More than you might. Expect if you just originally read the rules. So once I felt like I had an early win by implementing the rules, I decided I was going to tackle the harder part, which was testing the world. This is where I actually started down the path that you can see the very first test that I wrote. I left the test in the order that I created them. The test on line nine of world tests is adding a living cell increases the population. It's like, well, And how do you add a living cell? And those are things that I just decided during the implementation of that test. I'm going to have a world and that world is going to have a population and the population is the number of living cells and it starts at zero. And if I tell one of the cells to be alive, then the population increases. And when I look at this now, and in retrospect, thinking about it, set cell status, cell location, status dot alive. That's a little bit more verbose than I would want. I would probably refactor this. If I were going to change this code today, which I'm not, because then all the line numbers would change. I would refactor that, extract a new method, world dot make alive, make live, whatever, that just takes the location and doesn't worry about setting, passing in the status. So that was the first one. And then I was like, oh, well, if you tell a cell that it's not alive, don't increase the population. That's easy. It's the counterpoint to the test I just wrote. I don't even think I had to write any code for that. It just kind of happened because it wasn't increasing the number of living cells. And when I started, I actually was just tracking the population as an integer because that's all the test required. And then I got to the point where I was like, okay, well, let's just check. What is the default state? I want the default state of it to be dead because that makes it easier. Then I want to actually, actually check what the status of a cell is. By the time I got to the fourth test on line 38, cell can live. I was like, okay, now I actually need to keep track of what's alive as opposed to just the number of living things. And when I wrote that test, I refactored my code or rewrote my code. I changed my code in a way that I was keeping track of a list of points. And I used a tuple in wasn't sure that the type mattered. And so if you look at world CS line nine, it's just a read-only list of int int tuples. And I named them because the modern incarnation of C-sharp lets me name them, which helps even though it gets compiled out. And then I was like, okay, well, that's pretty good. Now I can make a cell be alive and I can test the cell to make sure that it's alive. And then I needed to start dealing with the evolution of the world, the next tick of the clock or whatever. Having already previously fallen for the problem that Matt stepped into, my very first for the world is that tick returns a new world. And so the very first thing I did is like, okay, I've got a new world and then I'm going to tick that world. It's going to give me back one. And it's not the same one.

[00:48:50] Matt Baker: Smart, smart. I see you've been here before.

[00:48:55] Dave Adsit: I've been here before. I have been burned by that very same problem in the past. And I knew that I

[00:49:01] Matt Baker: needed to not change the world in place. I feel like I might have too. And I don't even, I can't even say I haven't seen it before. It's just such a dumb mistake. But you would be surprised how

[00:49:16] Dave Adsit: many people make that mistake when they're first trying to tackle this problem. And I think that get caught by certain problems when we are working on a problem, a new, new code for the first time. There's some easy traps. And in retrospect, you're like, how could I not have seen it? And you're like, well, cause you didn't know then what you know now, obviously. And in the future, you could stop knowing it again and fall into the same trap. So don't be so hard on yourself.

[00:49:45] Allan Stewart: Well, and we look at a problem and we're like, that's easy. How could this possibly be very hard? I'm just going to write up some code and pretty much just go with it. But I think that's a good thing. Pretty soon you find out just like this little nuance that turns out to be critical, but you hadn't even thought of it when you would just looked at the list of rules because it doesn't say anything about that nuance, despite it being so critical. Yeah, no, it's true. And hopefully

[00:50:09] Matt Baker: your code adjusts to the learnings easily. Sometimes it doesn't, right? Sometimes it's

[00:50:15] Dave Adsit: almost like, I'll throw it out. Try again. So if you look at line 58, TIC evaluates rules for living cells. This is where I expressed an opinion about classic versus mockest test driven development.

[00:50:29] Matt Baker: Well, now you're stepping in.

[00:50:32] Dave Adsit: I know. I said, Hey, I know what the rules for the game are, but if I can ignore the rules, then I can test that the world works properly with special rules. So my very first rule or my very first test, I was like, okay, I'm going to create an interface on the rules. I'm going to pass into fake rules. And basically every time you ask the rules, if the net, if it's going to be alive, I'm just going to say yes. And that way I can verify a couple of things. First, I can verify that I'm actually checking to see what the next state should be correctly. And I can verify that I think this test actually has a bug in it, but yeah, this test is wrong. You can see online 64. of TIC against 67, but somehow it still worked because I got really lucky. But the idea here is that I can check that I evaluate the rules properly. And I can check that I do the right

[00:51:33] Matt Baker: thing when I evaluate the rules. I was just going to say it passes because you said always return alive. That's right. That is right. Which is kind of like subtly testing what you're saying. You demonstrate, demonstrated that you could make it always stay alive, given the,

[00:51:51] Dave Adsit: you know, given a rule set that sets up. If I were to correct this test, what I should do is on line 64, capture var new world equals world.tic. And then on 67, assert that new world.get cell status is equal to status.alive. That's what the test was intended to do. And because I was working by myself late at night without a pair and my rubber duck didn't catch it, I have a bug in my test.

[00:52:21] Allan Stewart: I think it's an example of that double-edged sword of the mockist style of testing because it's really easy. I mean, I can't tell you how many times I've seen people test mocks instead of test implementation. And so that's, you know, that's one side of the sword, but on the other side, once you get it right with the implementation that you have, you can inject any set of rules. It's like the opposite of the thing that Matt was talking about where somebody had pre-calculated all of the states. Well, now you can put in any, all kinds of new rules and try out things using the same engine, right? The same world engine to run a simulation and you can put in whatever tests you want.

[00:53:04] Dave Adsit: You know, as I look at this test a little further, I think that you could also correct the test just by removing line 67, because the name of the test is TIC evaluates the rules for living cells. And so the thing that I'm verifying is that I check the next state that I made alive. That cell has no neighbors that are living and it is in fact alive. So verify on line 66 is really the whole test. And I think I see that more clearly when I look at TIC evaluates rules for the cells around living cells. And I look at setting up a cell so that it's alive and then not worrying about what the return type from the rules is, but looking at the fact that the going to check the next state for the cell that's alive. And then I check the next state for the cell that's alive exactly one time. And then I checked for its eight dead neighbors to see what their next state will be. So that drove me to like, look at all the cells around the cell that I, that's of interest and count the living neighbors and all of that. If one cell is alive, only one cell in the entire world is alive. It's right there in the middle, right? So it has eight neighbors that are all dead. And each of those neighbors has one neighbor that's alive. The test is, did I call next state with a current state of dead and one living neighbor eight times. So once for each of the adjacent cells. Yeah. Yeah. And then I thought these tests are at a very low level. And I, I think I'm going to have to make a lot of assumptions and assertions to jump to the level I want, but, but I had kind of the basic structure of the project or the code in place. And so the next thing I wrote was my acceptance test file, which has two tests. One is a square still life. And so a still life is one of the things from the domain of Conway's game of life, where you have a square and a square of four living cells never changes unless somebody comes in and interrupts it. And so I wrote an acceptance test where I create a, a square of living cells and I tick the world and I verify that it's still a square of living cells in the same place. I tick the world again and verify it again. So I feel like I've got a pretty basic implementation that's working for that. And then I picked the other one, another simple one. It's a line blinker. It's three in a row. If they're horizontal, they flip vertical. If they're vertical, they flip horizontal. And so I wrote the test where I've started with a world with possibly a horizontal or vertical blinker. I don't know. define X and Y, but it flips and then it flips back. And at that point, I felt confident that the code was working as intended. And I committed the final commit and decided to do it again

[00:56:03] Allan Stewart: in a different programming language. I'm thinking back to what you said earlier about how having a separate rules file and separate rules tests, it makes more code. There was a discussion that happened in a Slack group that I'm a part of recently, where they're talking about how if you're doing things like test-driven development and pair programming, if you follow some of these practices around how to write good code or crafting code, you're going to actually write more code. And so it's going to take longer in some ways because you're doing more. But then it turns out that you get these really interesting, elegant solutions where you might have a whole lot of tests that cover the same small bit of code, but you were able to make the production code really small and tight and easy to read because you allowed for there to be some extra boilerplate. You allowed for breaking something up into a separate piece. It might not be completely the most optimized way to do it. If you're trying to do a game engine like Matt was talking about earlier, then maybe that's not the best way to go. But for most problems, we don't know where to optimize it until we've run it a lot of times. And so working your way into a really clean implementation, it's worth the effort. I mean, I think we've all been stuck in this kind of !

[00:58:48] Dave Adsit: One of the things that for me kind of stood out as I was wrapping this iteration up is that I like a concept, acceptance test-driven development. Conceptually, I want to do that. But when I was starting with nothing, jumping straight to an acceptance test would have felt like defining too much of the solution before knowing how it was going to turn out. And so when I finally got to a point where I felt like there was enough structure, I was like, oh, now I can write an acceptance test. I could have written the two blank tests that one said square still life and one said line blinker because I thought at the very beginning I was going to end up there. But I didn't know how I was going to get there, what it was going to be shaped like. And so they would have been empty shells until much later. Or, and I think this would have been worse, I would have filled them in with something and that would have impacted how I implemented the code and might not have been the right way to do the code. It would have been jumping to too much. Architecture or too much structure too early.

[00:59:52] Allan Stewart: And it points to the purpose of having different kinds of tests. I love the concept of the test pyramid. And you've got different kinds of tests and some tests are closer. We call them unit tests, but sometimes we can't all agree on what's a unit test versus what's a UI test and an integration test and a component. And so to a certain degree, that doesn't matter. I think the important concept is that there are different layers to that. You can get a lot of value out of having different kinds of tests that allow you to try things at different levels of abstraction and they're serving a different purpose.

[01:00:29] Matt Baker: Yeah, they kind of act to document the system at different levels too. I love it when a code base has a slew of unit tests to describe every little bit, but then at least a few acceptance tests that show me how to exercise the entire thing. I can look in there to learn how the system works pretty quickly. They're nice and they're serving different purposes. Yeah.

[01:01:17] Dave Adsit: writing those, I definitely had off by one errors for a while until I got the test to work. But I look at that code and A, I don't like the nested for loops and B, that code to me feels like it's going to be super inefficient when the grid gets large because so many of those cells are dead and all their neighbors are dead, but I'm still going to iterate over them and see if they come alive for some reason. I just left it. I didn't have a grid big enough to make it matter whether or not this was optimized. I mean, it's straightforward what's happening. You get this current cell status, you count the number of living neighbors, and then you set the next status on the new world based on the rules. And all of those are conceptually at the same level. They're straightforward to understand. Nothing about this code is going to be complex except for the fact I think you have some off by one errors, but hopefully those are already addressed by the tests. It's just not going to be the most performant and it's not the most elegant.

[01:02:18] Allan Stewart: In my C-sharp, my first C-sharp one, I also had a bunch of for loops. And after I had written the same for loop like four or five times because I had done mine differently, right? And so I had need for it in several places. I ended up creating a function that just iterates it and takes another function like, okay, for every cell, do this thing because I don't want to write a for loop one more time. Yeah.

[01:02:41] Dave Adsit: Nice. Yeah. That makes sense. Well done. Yeah. And in this one, I was just like, okay, what are the maximum bounds of the grid? Because the grid is kind of infinite, as infinite as it can be considering that I'm throwing tuples in a list, right? And eventually I'm going to run out of integers and that's the limit of my computing space. But this could calculate for a long time for a very, very sparse grid. Anyway, it's interesting, even after doing this exercise, probably a hundred times, even even even even even even even even even even even even even even even even even even even even

[01:03:29] Allan Stewart: I'll just touch on a few concepts here. I really liked what Dave had done in testing the rules first. I saw what he had done. I was like, oh man, that's such a brilliant idea. I'm going to do that first. And so I wrote this function will sell live. And I just wrote tests for that. But I also liked what Matt had done with a custom test harness because I kind of debated going back and forth. It's like, okay, do I want this to run in the browser or do I want it to run in node? And I kind of wanted it to run in the browser because I love seeing the game of life go. And I saw Matt had created his own custom test harness. And I was like, that's not that hard. And so I threw one together and it's just its own test.html page. The test harness just does a little bit of adding some divs into a page. So, you know, it's not fancy. It's not React or anything like that, but it gets the job done. And it allowed me to go through a testing loop, get fast feedback, and write the tests for will the cell live given its current state and how many neighbors it has. And then going from that, I went forward to say, okay, well, let's take that infinite grid again. But this time I'm going to deal with the limits of the grid by just asking it to render the next generation from a certain rectangle space. So I give it a start and end point and how many columns and rows it should go. Pushing it in that direction, I was able to put together, a implementation and then I created the game.html. And there's just a little bit of code in there. I decided to just do a random. Here's a random number of cells that are alive. And that's what we're going to start with. And then every second, calculate the next generation and paint it onto a canvas. There's still definitely some things that I would not do in production code. I don't want to write my own test harness for real life. No, no. My test harness does a deep equal. And I was like, oh man, deep equal is hard. I'm just going to JSON stringify these things and make sure they're the same string because that was easy and I'm lazy. And it worked right within the context of I'm working on a small problem. This is a cata and I don't want to create this extra complexity by bringing in a big library. It was fine. But the big library also is probably really well vetted, right? It's been battle tested and hardened. So I wouldn't do this for, an actual production scenario from an employer, but it was fun to play with and just practice some of those concepts that I had seen play out in all three of the implementations that I had seen and just kind of experiment with what does it take? And so I ended up with a few more files than I did in my C sharp version, but you can also open it up in the browser and you see the game of life start to unfold before your eyes.

[01:06:23] Matt Baker: The visual test is pretty cool too. It's like seeing it play out to understand whether or not it did what you needed it to do.

[01:06:33] Dave Adsit: It's kind of awesome. I am mesmerized by it.

[01:06:36] Allan Stewart: And the patterns, like if you go and learn about those patterns, you can start to see them show up. It's like, oh, there's a blinker. Oh, a glider is going across the screen. Like, I don't know how that happened because I didn't write a glider into there, but there he goes just trucking his way across the screen.

[01:06:53] Dave Adsit: That's pretty cool. That's one of the hard things like game of life doesn't technically need to be a game of life. It doesn't need an interface, but sure is cool when you add one to it.

[01:07:01] Allan Stewart: Oh, yeah. There's probably some other things that I would do differently, but I think that's really what I wanted to play around with. I wanted to do one in JavaScript so that I could get that UI. I wanted to try some of those ideas that by working with other people you learn and you're like, oh man, that was such a great idea. I wish if I had been pairing, we could have gotten the best of both worlds. And so I'm going to have to just do it again. And I figured if I had one other example that might make it nice for any of our listeners who don't really resonate with C-sharp or Python, here's one more option. Most developers are forced to confront JavaScript in some manner or another.

[01:07:37] Dave Adsit: Definitely true. I think one of the interesting things is that as we talked about it, I took what I thought of was the path of least resistance by implementing the rules first because I didn't know how else to jump into the problem. And then one of the things you said is that jumping into the rules first seemed like an improvement to you. And I was like, I thought I was just being lazy.

[01:08:01] Allan Stewart: That is an improvement. Oh yeah, that might be. You were successfully lazier than me in the beneficial definition of lazy developer.

[01:08:12] Dave Adsit: That's right. Okay. Good to know. Good to see that. Well, should I jump into Go?

[01:08:18] Allan Stewart: Yeah. Tell us about your Go implementation.

[01:08:21] Dave Adsit: Well, first of all, it's incomplete. And it's incomplete because I, I underestimated the amount of time it would take to actually make it work, which is probably something no other developer has ever done before.

[01:08:35] Matt Baker: Not me.

[01:08:36] Dave Adsit: And I also underestimate the amount of other things that would come up in the last week before we recorded, like traveling out of state for a day for work on short notice. And so all I have here is a test suite for the rules and an implementation of the rules in Go.

[01:08:56] Allan Stewart: You say incomplete, Dave, but I look back at the readme, and actually that's the only thing that the readme said that you had to do. So maybe yours is the most, most accurate, you know, you didn't go above and beyond to do other things that weren't required. I implemented the spec.

[01:09:16] Dave Adsit: Whether or not the spec was complete is somebody else's problem. Yeah. So I had fully intended to do it. I had fully intended to implement a world and have the iterations of the world, the evolutions, et cetera. But what I have instead is just a table test in Go of the rules. One of the things that I learned here is that, you know, I'm fairly new to Go. I've been dabbling in Go for years, but I've actually haven't voted any Go until the last couple of months. I got to learn how to create the Go equivalent of an enumeration, which is in the rules.go It's actually lines three and five through eight. The type cell state is an int. And then the values that are available in cell state are defined as the constants alive and dead. And I got to learn about iota, which is a way of saying, I don't care what the values actually are just so long as they're mutually exclusive at compile time. So that was kind of fun. That's cool. And I would say that if you are looking at C sharp and thinking, look how verbose those attributes are. I don't love them at all. Come take a peek at my table testing in a Go and the 135 lines required to test the 18 states. Somebody who's better at Go than me will probably have a much cleaner and simpler way of doing this, but this is how I've been taught table testing and it seems to work pretty well. It just isn't compact. I probably won't go back and finish this or at least not. I might go back and finish it on my own just to let my mind rest on the problem.

[01:11:02] Matt Baker: Maybe you can add like another branch, like Dave Go dash complete or something.

[01:11:07] Dave Adsit: There you go. Yeah, I could do that. So as we wrap this up, one of the things that I have been thinking about is that if you have the opportunity to go to a code retreat, you should definitely do it. You'll look at the code that we implemented and think, yeah, I've seen this problem before, but there's nothing quite. I like going to a code retreat and testing out some new code and working with somebody you haven't met before. And then adding the fun and complexity of additional constraints like short methods or no loops or no branches or whatever. I think there's some fun things to be had if you can get to a code retreat.

[01:11:49] Matt Baker: Agreed.

[01:11:50] Allan Stewart: Well, I hope that our listeners will find this interesting and get a peek into the process of how we write code. I think if I were to summarize the discussion, coming back to the idea of crafting code, it's really a process. It's not a destination. You don't arrive at the point where you say, oh, now I'm a professional coder. Now I have reached the place where only beautiful code emerges from my fingertips. It's a process. It's a process that you have to follow in order to get to that point of simplicity or elegance or professionalism. Yeah. It's not something that just happens one day. The music for our podcast was generously provided by Todd Fisher. And as always, we recommend that you join a community of professionals by attending a software crafters group or meet up near you. The Utah SC group at utahsc.org has a virtual meeting the first Wednesday of each month. Perhaps we will code the game of life with you there.

~/podcast/episodes/021-crafting-katas $ cat ../../copyright.txt

Copyright © 2026 - Crafting Code Podcast