Crafting Code Podcast

~/podcast

$ cd episodes/021-crafting-katas

~/podcast/episodes/021-crafting-katas $ ls -1a
. .. episode-summary.txt published.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:30] 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:24] 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 of 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 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-code-podcast, a repo, crafting-kadas, 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.

[00:03:54] Allan Stewart: You can find the link 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#, which Allan happened to write, and he used C# 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 years. I haven't been to a Global Day of Coderetreat for a while. So 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#, 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 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 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# 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. 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 could 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-C# 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 used this is a live app method that allowed me to not have to worry about the boundaries of the grid everywhere, 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'd 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 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 symmetry to it. But there was some weirdness there that I didn't really anticipate as I went in to craft that code. I expected it was going to 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 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.

[00:10:14] Matt Baker: Reading through your tests for common patterns like a block, beehive, and a boat, and plinker. 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 interesting cases that you could test against to ensure that your engine was working correctly. And that's a cool way to do it.

[00:10:35] Allan Stewart: Yeah. 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, there's some boundary there, right? 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? Yeah, 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 I'm using LINQ, and so there's fewer are 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 for rendering like polygon meshes versus ray tracing. And if you're doing ray tracing, like, how do you divide up the world into smaller sections? Potentially it's an infinite world, but all you care about is, like, "I want to make a picture 600 by 300 or whatever, and get those dimensions." Like, that's what matters.

[00:12:11] Matt Baker: Yeah. That's really cool. I read something that said after a few generations, most cells are off on a game of life grid 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

[00:12:31] Allan Stewart: sizable optimization. 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, 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. 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 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 programming language, because that's not the kind of problem we typically solve anymore.

[00:14:27] Allan Stewart: Yeah. So when, 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 a something that's not actually array.

[00:14:39] Dave Adsit: array 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 could be a byte, right?

[00:14:55] 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, uh, represented the state of every neighbor, and then, uh, 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 that was a

[00:15:26] Allan Stewart: pretty neat way to encode a cell. Nice. So every time you look at your cell, you're looking at the

[00:15:32] Matt Baker: neighbors as well. You know what the thing is that's cool. And then there was a, it has a nice property that when the cell is dead, the bit 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 a, 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, like 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, copy things over. And as I did that, almost all of them had to change in some subtle way because the implementation was different. And if there had been a common interface, 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# version, you'll see that I did some similar things, but in a very

[00:18:17] Allan Stewart: different way. Yeah, I think that's really important. You know, we're 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 a private scope package, but you have test packages right 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 does 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. It 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#. I don't know why, but 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. It feels like there's just an elegance to it.

[00:21:11] Allan Stewart: Yeah, I think I've never really loved duct typing. And I can kind of respect where it comes from and why people like it. Like, I can, 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#. 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:29] Dave Adsit: So I learned very recently that the formal name of that style of programming language is structural typing. And after about a week of somebody telling me how exciting it is that Go is structural, type, has structural typing, is structurally typed, this, that, and the other, when they finally explained what they meant by that, I was like, "oh, you mean duck typing?" And it It all clicked. And the person was like, "I don't like to call it that because it sounds condescending." And I'm like, "yeah, but I don't have a structural on my desk. And I do have a rubber duck on my desk."

[00:23:10] Matt Baker: I don't have a structural.

[00:23:12] Dave Adsit: I don't got one of those. I got a duck. I know what a duck is. If it walks like a duck and it quacks like a duck, it's a duck. We're good.

[00:23:21] Matt Baker: 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 rules of a generation in Game of Life, and then returning what the grid looks like after that. I chose to fix my grid to a 3x3. I 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 3x3, 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 you know, 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. 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 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 decrying the practice of one assertion per test, saying it was overkill. I honestly can't remember my opinion on this over the time. I feel like at one point I was totally in agreement. I'm not now. And you'll see that in my test because the test, they use the Python generator syntax. So I'm just yielding a number of, just an arbitrary number of results 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 paper, he talked about factoring a problem and the importance of, like, 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, like maybe, you know, 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 the couple of days later. When I sat down, I already had a plan when I sat down based on my 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, "this testing from generates 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 a test 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, to leave it for a day or so and then come back. And even before 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 and step away way 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 gonna try and do everything, it's only gonna 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 if and when they're needed. Because if you do the big bang approach, the big splash approach, that you're gonna 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, 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 that'll get me a little bit closer to right. I get some feedback, I figure something out, and I'll move forward a little bit more. And I just, I don't know another way to write software at this point, like just lo-fi deliver Deliver something, you know, 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 they're 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 an experienced developer has stopped making mistakes. 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 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. I know. 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 before you get it right. 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 Kerievsky, 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, this shows up for me on a few planes. On the feature plane, did you get the requirements right? Do you understand what the 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?" 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 gonna 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, uh, emergent properties of a system now, where I, 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.

[00:35:55] Allan Stewart: I like that.

[00:35:57] Matt Baker: 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, um, and this particular test on line 134, 135, the row I was testing, the cell I was testing. But there's, and there's also a state member or state property in that dictionary. State is referring to the state of the test. Did it pass or did it fail? Which is established on line 134 and then included in the dictionary on line 135. But the name of the test itself is test get left neighbor state. So I don't think it's unreasonable to look at the response from that function and assume like, "wait a minute, the state is fail for that neighbor," you know, as opposed to, "oh, the test failed." So that's one nitpick I 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 on line 134, instead of state, that'd be called test state. And then I could not have to worry about the overloading of the term. I tried to write my code optimizing for conceptual understanding. 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 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 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, I can't remember the key of the lookup offhand, but anyway, just this crazy stuff that was super, super performant, 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 the programmer's way. But then you get in these cases where you're no longer optimizing for readability. And, 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, heavily optimized for a reason code."

[00:38:49] Allan Stewart: Speaking of those trade-offs, one trade-off that probably occurs in a case like that is are the rules going to change? Conway's Game of Life is really well understood. The rules 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 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 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 changing code and the malleability of that code is important, then you have to take that into consideration how you

[00:39:43] Matt Baker: develop code. 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 around it, you know, and very verbose, probably, comments that explain exactly why this is, why, you know, 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 co-workers, have to live in that same code. Uh, I'll say one last thing on my implementation. At the very bottom of the file there's a function called default grid that just returns a grid where all the cells 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, 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 that was pretty neat.

[00:41:18] Dave Adsit: Matt, you fell for one of the classic blunders, using a mutable data type. I didn't expect that.

[00:41:28] Matt Baker: Hey, 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:40] 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 there but not?" And then finally it's like, "oh yeah." So basically line 286, instead of saying next generation row cell, said grid row cell, I'm just sad the whole thing went to stew pretty quick

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

[00:42:17] Dave Adsit: Awesome. So let's see the very first thing that I did when I started on this process was write 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 rest the world and the iterations and all that. So I wrote the either two or 18 tests in rules test

[00:43:10] Matt Baker: first, depending on how you count them. I like your tables. I like the, that's another thing, not, I'm sorry, I'm just interrupting. The similar to the abstraction, the interface and the concrete thing we're talking about earlier, how Allan concealed his tests, um, 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 it's a sign you factored well.

[00:43:43] Dave Adsit: Yeah, I like it. I like the test cases in the NUnit 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#, would say, "look at all that 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, there's 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. 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, what is a population 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.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.makealive, make live, whatever, that just takes the location and doesn't worry about 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 check what the status of a cell is." By the time I got to the fourth test on 938, 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 a way that I was keeping track of a list of points. And I used a tuple in C# rather than creating a new type because I 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 The modern incarnation of C# 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 realized that I needed to start dealing with the evolution of the world, the next tick of the clock or whatever. Ever 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

[00:48:46] Matt Baker: and it's not the same one. 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 needed to not change the world in place.

[00:49:05] Matt Baker: 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.

[00:49:14] Dave Adsit: But you would be surprised how many people make that mistake when they're first trying to tackle this problem. And I think that that speaks to how easy it is for us to get caught by certain problems when we are working on a problem, a 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, because 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:44] Allan Stewart: 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 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 just looked at the list of rules. Because it doesn't say anything about that nuance, despite it being so critical.

[00:50:06] Matt Baker: Yeah. No, it's true. And hopefully your code adjusts to the learnings easily. Sometimes it doesn't. Sometimes it's almost like, "well, throw it out. Try again."

[00:50:18] Dave Adsit: So if you look at line 58, Tick evaluates rules for living cells. This is where I expressed an opinion about classic versus mockist test-driven development.

[00:50:29] Matt Baker: 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 in fake rules. And basically every time you ask the rules, 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 on line 64, I didn't capture the output of tick and I didn't test the output of tick 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 thing when I evaluate the rules.

[00:51:35] Matt Baker: I was just going to say it passes because you said, "always return a lie."

[00:51:39] Dave Adsit: That's right. That is right.

[00:51:42] Matt Baker: Which is kind of like subtly testing what you're saying. You demonstrated that you could make it always stay alive, given the rule set that said so.

[00:51:52] Dave Adsit: If I were to correct this test, what I should do is on line 64, capture var new world equals world.tick. 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 that could lead to a bug in my code.

[00:52:24] 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

[00:53:02] Dave Adsit: whatever tests you want. 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 tick Tick evaluates the rules for living cells. And so the thing that I'm verifying is that I check the next state for a cell 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 Tick 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 checks, the two checks on 80 and 81. The one on 81 says, "okay, so I checked to see what the next state will be for this 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. Life. 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. And then I thought, "these tests are at a very low level. And I think I'm going to have to make a lot of assumptions and assertions to jump to the level I want." 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 where I've started with a world with possibly a horizontal or vertical blinker. I don't know. I didn't 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 in a different programming language.

[00:56:06] Allan Stewart: I'm thinking back to what you said earlier about how 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 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 extra boilerplate sometimes to get those other properties of code cleanliness and readability so you can understand. And I noticed that across all three of what we did. There's written out names for things. It wasn't just X, Y, and Z and A, B, and C and I, J, K as your variable names. Because it'd be easy to do that, but it makes it just that little bit harder to remember, especially in something like Conway and you're using a two-dimensional array. "Okay, wait, is I the rows or is J the rows?" And if you screw it up, it's not going to work properly.

[00:57:56] Matt Baker: I felt similar writing my tests. I, given that I wrote the little test runner, I didn't, I didn't have any options for how to express my tests other than very verbose. And so that's what I did. And I started looking at the tests, and there's a ton of redundancy in there. You'll see it right away if you look at them. And for a minute, I started thinking about how to express it more succinctly. And I backed off of it intentionally because I concluded I liked the verbosity. So I don't mind that there's There's a ton of code that expresses the requirements in the test, because I think it's dead simple to read. Like, there's no confusing it when you read it, and there's no misdirection, no abstraction. And so I ended up leaving it in my files, a couple hundred lines. I think I could have shrunk that dramatically with a little bit of cleverness, but intentionally avoided it for exactly what you're talking about now. And there's, you know, there's value in simple expression.

[00:58:48] Dave Adsit: Yeah. One of the things that for me kind of stood out as I was wrapping this iteration up is that I like a concept of 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, Like, "oh, now I can write an acceptance test." I could have written the two blank tests. 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 test. 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:28] Matt Baker: us. 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 like you're saying, Allan, and they deliver different value at different times for sure.

[01:00:53] Dave Adsit: I think one of the the other things that I got to at the end when I was done writing tests and the code worked is I was looking at the code and thinking, "there's some parts of this code that I do not love." For example, if you world by 28 through 36, I've got two nested for loops. And just to be fully transparent, as I was writing those, I definitely had off by one errors for a while until I got the 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 that you might 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:17] Allan Stewart: again. And my C#, my first C# one, I also had a bunch of four loops. And after I had written the same four 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 four loop one more time.

[01:02:41] Dave Adsit: Nice. Yeah. That makes sense. Well done. Yeah. And in this 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, sitting down and doing it again and seeing how it turned out, I feel like I learned even more than I knew before.

[01:03:20] Allan Stewart: Well, like Dave, I also tried my hand at a second implementation, and I switched to JavaScript this time. So in the Allan-JavaScript branch, 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 endpoint 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 Kata, 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 production scenario for 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# 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. There's nothing quite like seeing it it play out to understand whether or not it did what you needed it to do, you know

[01:06:33] Dave Adsit: It's kind of awesome. I am mesmerized by it and the patterns. Like, if you go and learn about those

[01:06:38] Allan Stewart: patterns, you can start to see them show up. It's like, "oh, there's a blinker." Yeah, oh, a glider is going across the screen. Like, I don't know how that happened because I didn't write a glider

[01:06:48] Dave Adsit: into there, but there he goes, just trucking his way across the screen. That's pretty cool. That's one of the hard things. Like, Game of Life doesn't technically need an interface, but, sure, it's cool

[01:07:00] Allan Stewart: when you add one to it. 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 gonna have to just do it again, and I figured if If I had one other example that might make it nice for any of our listeners who don't really resonate with C# 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:00] Allan Stewart: That is an improvement. Oh yeah, that might be. You were successfully lazier than me in, in the

[01:08:09] Dave Adsit: beneficial definition of lazy developer. That's right. Okay, good to know. Good to see that Well, should I jump into Go?

[01:08:18] Allan Stewart: Yeah. 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 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:35] 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. Whether or not the spec is complete is somebody

[01:09:19] Dave Adsit: else's problem. Yeah, so I had fully intended to implement a world and have the iterations of the world and 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 coded 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 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, um, and I would say that if you are looking at C# and thinking, "look how verbose those attributes are. I don't love them at all," come take a peek at my table testing in 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 in this branch. 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

[01:11:06] Dave Adsit: or something. 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 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 There's some fun things to be had if you can get to a code retreat.

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

[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. 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 published.txt

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

Copyright © 2026 - Crafting Code Podcast