I'm Matt, Godbolt.
And I'm Ben Rady.
And this is Two's Complement: a programming podcast. Hey Ben, last episode, you and I were discussing how we started out with the same goal and that is to be in the games industry and I spent a decade or so doing so, whereas you've through no fault of your own ended up not working in games and then sort of like 20 odd years into our careers. We've, met up and we've been discussing kind of the things that went well, the things that didn't go so well, the things that differ and last time we specifically were talking about testing and how I hadn't really learned how to do testing as a first category of thing. It was like something that I learned much later in my career, whereas you've kind of made a thing about testing.
Yeah, well, yeah, it was it sort of all of the XP practices. 'Cos like, as you said, you know, I, I didn't get to fulfill my dream of becoming a game developer, you know, falling like Icarus covered in feathers and wax from the sky into the reality of the cold harsh world. But what I did do is I got, I got really into sort of like extreme programming and extreme programming engineering practices because that's really what that whole thing was about.
When you say extreme programming, I hear people like bandy it around, but what do you, what do you mean when you say extreme programming? So the capital letters. Yeah,
Yeah, yeah. XP. I don't know why they capitalize the, but you know, maybe EPS is cool. Yeah. It sounds cooler that way. But so XP was, this was this process. I mean, some people still use it, but it's like this process for building software that, you know, was invented or developed kind of like as part of this, um, various projects in sort of the late '90s and early 2000s. And it was an engineering-focused approach to building software as opposed to a lot of the other approaches at that time, which were much more sort of, I don't know, business-oriented, I guess is the way that you describe it, not programming
Waterfall type stuff or not even that?
I'm not really talking about that. Like the, the contrast that I have in my career was the, the first, the thing that I first started doing when I graduated from school was working for this company in Houston, doing data visualization and they use a process they're called RUP: the Rational Unified Process. And I tried that for...
Rational as in Rational Rose?
Yes. As in rational Rose IBM. Yeah, exactly. And so, you know, I tried that for a year and I was like, this is gross and found XP and got way into it from there. So yeah. So my, my career, instead of going to the games industry, I got way into agile engineering practices, like, um, test-driven development, pair-programming, continuous integration and all that stuff. And I've been doing that basically since, you know, for almost 20 years now,
Which is very, very different from the grind oriented programming that I experienced in North London, living a mile away from where my office was; making it home after closing time from the pub; staggering into bed, getting back up again, going into work and working long hours and grinding, and essentially applying human effort of will as opposed to actual sound design practices to making software work, which, you know, we, towards the end, in fairness, I paint this picture towards the end. In fairness, we were, we were starting to pick up on some, some practices. We did have some tests, but I think, like I said, last time, most of them were assert based and being in the games industry, we were working mainly in C and C++, you know, in sort of assembly and some other weird things that we use. But the mainstay of that was developing for consoles that were not standard.
They weren't something you could just run and deploy to. There was, there were steps involved in actually getting it to work. And, you know, in some cases it was, there was a serial cable and you had to like squirt your software down into it to make it work. They had different architectures from the host machine that you were running on. So you couldn't just build and run them locally. And so testing was very much something that we did on a subset of important code that we bothered to get ported, to run on x86, or if we were working on a PC game, then obviously we could do some amount of it, but it was basically assert based testing. But even then even accounting for all those things right now, fast forward 20 odd years. And we're talking about server software for the finance industry and we have all the capabilities open to us and yet testing is still hard in C and C++. And so I thought one thing we could talk about this time specifically is why is that the case and what can we do?
Yeah. Yeah. I think that's a fantastic idea.
You have described to me as like good design can often yield testability or perhaps the other way round, right? If you design for testability, that is a good design. So maybe there's something wrong with our design. So let's start from like what makes something easy to test? What makes something testable and then we can sort of riff on why that maybe doesn't work for me or maybe it does?
Right. Maybe. Yeah. I mean, I think for me, and I mean, this is my own personal belief, but code isn't well-designed, unless it's testable, that's just a criteria of design that it's like, and it's a much more concrete and useful kind of criteria than I think a lot of people use. There's sort of, you know, there's a lot of like, you know, winning by style points that happens in some kinds of software design where it's like, Oh yeah, well this is, has this certain shape to it. Or I don't even know. Can I write tests that run quickly is, are a criteria that is like, if it, if you can't do that, it's not well-designed.
And, and specifically as well, you mentioned quickly, as well as like another thing you've kind of grafted on there, but we, we talked about last time, but that's an important part of it.
Right? Right. No, no, that's a good point. Is it, it's like the kind of tests that I'm talking about, where they are very focused. They can run very quickly. They're decoupled from things like the network, the file system, other services, you know, hardware, even in certain situations. Like if you, if you can't write those kinds of tests, your code is not well designed by my personal definition.
Yeah, no, I, I can see an argument for it. I think, you know, we, we might politely disagree about where the line is under some circumstances because of pragmatism, which is, yeah.
I mean, I was just going to say, I think one of the places where that gets difficult is when you have constraints on your code that are related to hardware related to performance related to multithreading is another dimension we talked about last time. And so, you know, one thing that I would, I would kind of love to get into with, in this episode is talking about like in C++ specifically some of the hurdles to doing things, to increase testability that I would think of as good design, but you would, you know, maybe turn your nose up at and be like, yeah, okay. I get why you're doing that. But if you do that, then it's going to cost you this. Right. And you really want to make that trade off.
Right. I think, I think that's it. I think that you've kind of cut to it there. The, the, usually you only pick C++ if performance is one of your primary goals, I don't think many of us rub our hands with glee and say, I'm going to write a mainly string processing application tha's not performance critical in C++. That's not the first thing we'd go for. You know, you'd pick Python and you pick PERL, you pick Ruby. Any of those things, JavaScript, they work beautifully. They have a great design. So they have a great user experience out of the box. C++ has a lot of hard work to even get it up to the point where you've got a build, let alone an executable that does something useful. But so you, if you're picking performance, then one of the things that C++ leverages is the compiler.
If the compiler knows more about the way your software is put together and how your code fits together, it can do a much better job of optimizing it. So putting direct calls in your code to the, where the source code is visible to the compiler. So for example, if I have a class and I have methods in that class, and some of those class methods are actually even in the header file, and there are compiler technologies to make that less important, but like, you know, the, the implementation is there for everyone to see in your header file. Then the compiler can often inline it, do a lot of great optimizations and give you some super fast code, which is wonderful, but you have tightly bound and coupled the code that calls that function to the implementation of that, the specific implementation of that function.
And that means that there is nowhere to put a little break in between to say what I'd like to observe the interaction between your component and the thing that you're calling there is nowhere to put a test in there now, obviously in other languages. And you can do this in C, C++ as well. You would use a mock often to say, well, I don't care about what you are doing; moreover I just care about the interactions you have with another object. So you mentioned file system: There's a great one. In C++ you probably would just use a std:: filesystem object. And that's great. It comes with the modern versions of C and, um, you get access to files and paths and all those good things, right. But it's a concrete object. There's no seam for me to mock it out unless I go out of my way to add one in. So even where even though file access is going to be slow. We know that it's not a highly performant area of the code by default. We can't interrupt that communication and say, well, I'm not going to give you a real file here. I'm going to give you a pretend file.
Yeah. And I mean, uh, that sort of dependency injection is what people generally refer to it as is a common technique for testing. But I, I kind of wonder, it's like, you know, you kind of just said, it's like, well, if we're going to be talking to a file system, anyway, performance is kind of out the window, right. Like at least in some respects. So, uh, at least in that area, I would imagine like, yeah, you might have to roll that abstraction yourself. You might want to roll that, abstraction yourself, depending on what you're...
It might be a useful thing for you to do, just because...
Yeah. And I mean, you know, I actually think that this is one of the great benefits of testing in general is that it forces you when you're, when you are forced to decouple things, it makes you consider what parts of the dependency you actually need, and which ones are just kind of coming along for the ride. And although it, it seems convenient oftentimes to have the full array of possible ways to interact with an object or a system or anything else, it is often quite nice to focus those interactions to be only what they have to be, because; [A] it reduces the number of possible code paths through your code, right? Like if you're only using one thing, then you just have to make sure that one thing works. You don't have to make sure that everything works, but it's also sometimes makes it easier, a little easier to understand, especially if you're coming into a new code base and you're like, okay, well, I know how this function call works and they're using it all over the place as opposed to, well, there's 12 of them and I don't know what they do. So giving some thought to that, I think it has value.
So you did the kinds of things you're talking about, perhaps are, you know, obviously there's the, essentially the, the, the global space is an object. If you want to think of it in that way, or a scope, it's a scope, you know, like calling just std::filesystem::file on the thing gets you a file is in the global scope. But if I deliberately limit myself and say, well, no, this, this, this component I'm talking to only wants to load cache files. And it only really cares about the contents of those cache files. Why don't I just hand it something for which that is the interface. And now I've kind of documented the interactions and I've limited the interactions in a useful way. So this, the abstraction might actually have above and beyond the testability that might have been the initial reason I put it in place.
I might put it there and I do this all the time. You know, obviously we're, we've we've worked together. We've, we've done these somebodies things together before, but this is the kind of thing that I do. And I've had discussions with other C++ programmers who have, have come down on the side of like saying, I wouldn't necessarily add an abstraction purely for testability. And I've kind of gone back and forth on that because I, I have done I've, I've added stuff like exactly in the file system is usually the arena where this stuff crops up is like, yeah, okay, I'm going to add something which provides a file or provides a config loader or something like that. And then somewhere there's a thing that can do files, but most of the time, it's my fake one for testing. But, um, it, it was an interesting finding that there were people who wouldn't do it just to add testability, but very often when I've done it, I have discovered that it's a useful thing. And perhaps that's what you're alluding to here is, is that, like, you've mentioned something about abstractions before that they are what they discovered. Yeah.
Abstractions are discovered not created, right. You need to sort of see how your system's used and then pull the abstractions out.
Right. And I've worked with a good friend of mine before now who is also referred to bad abstraction layers as obstruction layers. That is which I think we've all been there where you're like, well, I just did the thing on the other side of this wall. So there's always a danger. If you're introducing these things where, you know, maybe you are decoupled from the file system in your config processing system. And you're like, well, I just want to see if the file exists. And now I haven't, I'd love to reach out to the disc and you now you're like, wow, now I have to actually add this to the interface. But to your point, if I add it to the interface, then it, maybe I'm documenting more about the things that I actually need to do.
And I mean, I think you want to make those kinds of changes easy, but make them intentional, right? Like if you're going to sort of break the abstraction a little bit and say, okay, well, I need to see if this file exists, design your code, such as that. It's easy to add that, but then add it only when you need it. And I mean, I think one of the other great benefits that you get out of doing this as you sort of get a, a very low cost, uh, designed for reuse, like in their file example, if you have an abstraction that says, okay, I can, I can read the contents of this file. I can, I can stream it in. Then if you have an abstraction there, that's saying, well, it doesn't really matter if I'm reading from a file or from a socket, uh, the code will work basically the same either way.
But the second that I try to seek, then that abstraction now has been completely destroyed. And, and sometimes we make decisions about how to implement an algorithm. We're almost like looking for a reason to choose one thing over another, right? Like it's, there's eight different ways I could do this. Which one do I do? If you have the whole array of possible file system, uh, you know, tools at your disposal, you might choose one arbitrarily. If you've focused it to be only a certain set of things, the choices may be okay, well, I could arbitrarily pick one, one of these eights, one to choose this one, because it's the one that I've already abstracted. And that naturally leads you toward designs that are more reusable because it's like, Oh, well, you could also do this with a socket. And you never know.
So one of the we're going to go off track from C++ pretty quickly. But we, and I would like to get back to some things I'd like to talk about, but one of the things I've seen in other programming languages, um, mock sort of setups is their actual, their dynamism. So I'm thinking Python specifically here, that dynamism allows for the kind of thing where you can monkey patch out like the global file system routines, and just say, Hey, I want to test this thing. I know nudge, nudge, wink, wink that it's going to open a file using this particular API. You essentially hack the runtime to say, no, no. If you call, um, file dot open, uh, do, do do this instead, and you temporarily install that patch, then you can write your test and then you can say, yeah, okay.
When I called the config system, I know it loaded the file and I was able to provide it the file. And I could assert that that was, but that's so brittle. It's so brittle because I could reasonably refactor. I don't know if refactor is the right thing. Let's not talk about that, but I could, it could easily change without too many external obvious changes the implementation of my config loader to use a different API. There are hundreds, as you say, there's a dozen ways you could have loaded a file. I could mmap the thing I could, you know, open it. I could just path.open. I could use whatever. And now suddenly I've broken a perfectly reasonable test of that system because I just chose not to use the API. But if I force it through an interface that I designed ahead of time to say, well, these are the kinds of things you can do to the files in this context, then necessarily I'm not open to that.
Now. Obviously I don't get the choice of doing that in C++ short of some absolute heroics with the pre-processor and #defining a whole bunch of gunk to be things that they are in, which is so dreadful. I'm not even going to go further down this logic set up, but, but actually that does lead me to the other thing I wanted to talk about, which is when I do testing and C++ I typically do think of adding a virtual layer. So this is a, a class with virtual methods, which has a performance, a runtime performance cost, and I've long argued back and forth various places about what the cost of that really are. And certainly back in the days of the games industry, um, the cost really was the, the indirect call instruction that it necessitated nowadays, that's less the problem.
It's more like it's a barrier to the optimizer. The optimizer can't see across it. It doesn't know what it's doing, but even that's coming down, but let's hold that to one side. But that means that you are definitely designing your software ahead of time with a very specific strict contract. And that's the interface definition. There's another school of thought that lets you use template parameters into a, a function or a class to hand it essentially a policy object that says, this is the thing. When do you want to use a file? Use this object, this type of object. And that's a compile time change. The compiler can see which one you actually handed the implementation and it can therefore generate like again, perfect fast code paths through the virtual is a runtime decision that the compiler doesn't know that you switched out on. So there is as a way of having a lower overhead runtime over ahead, version of, of, uh, essentially dependency injection.
But it's a sort of insidious one because every time type you pass around your program carries with it, the parameters that were given to that type. And so in your like real program you might have, um, oroh I'm using my config loader and it's type is ConflgLoad
And there are so many things tricks you can do in the code that rely on the actual concrete types, because you can interrogate that. And it's a commonly done thing to say like, well, what was my template parameter? Does it have a ::"this"? Does it have a, uh, what, what, what is the type of its ::size_type? I can dispatch on that. That now it's not, the interface is not opaque to the implementation that's using it. You can actually inspect the implementation you were handed and make decisions, which means that you can actually have a different, you know, I could actually say, I can use a trick to detect if it was really a fake one that I'd been instantiated with and do something different in my implementation that could mean that you test one thing and in a non test case, it does something different.
And I don't like that. You know, I like the idea that almost essentially the library that I built, that I link against the test.exe Is the same library. I then link against to create the thing that I'm going to ship off to production, as opposed to completely recompiling it with essentially a set of pre-processor well, compiled time processed, uh, parameters that could change the behavior. I just realized what I've just been doing is ranting for the last two minutes about this particular topic, as I feel so strongly about it, but it is a, it's a valid way of achieving this and I've seen it used a great success. And in fairness, I have used it sparingly in contexts where I feel it doesn't leak outside of a small component, which I guess brings us right back to design. And maybe that's ultimately what makes me feel so uncomfortable about this is that it's an abstraction that leaks throughout the entirety.
Transitively through my design and I don't really want it to, I want to hide stuff away. Without appealing to the kind of, um, virtual method or interaction based testing. Typically what I find myself doing when I am writing testable C++ code is trying to chew off the smallest possible thing and test it as a component and then test it in aggregation and then test the aggregation and then test the aggregation of that. So it's a very sort of build up and I not testing the specific interactions between components, which 'cos I can't see into them, but I know that my grommet works and I know that my screw works and I know that my nut and bolt work. And then I kind of sort of blindly trust a bit that putting them all together into a widget works, but I do test the widget. It's not like I'm I'm there, but I know that I'm leaving, I'm leaving something on the table there by not being able to test all of the interactions between the two. But I, I still think it's, it's not an invalid way of testing.
No, I think that's a perfectly valid way of testing. And certainly dependency injection is not the only way to write testable code far from it. If you're working in a functional language, dependency injection is not even a thing. Right? So, and I mean, I think the pattern that you're describing of building small components, whether they're classes or whether they're functions and then assembling a system out of those and then writing tests for the assembled pieces can work. And I've certainly done testing that way. The thing that you can run into when you do that is you sort of run into the problem of when you make changes, you wind up with a whole bunch of failing tests. So in an ideal world, and not even in an ideal world, that's a stupid phrase. People say that all the time. How about the real world in the real world?
When you do things in a certain way, what you get is when you introduce one bug into your system, you get one failing test. And I have built systems that have that property. It's not impossible. It's not even really that hard, but obviously working in different environments, different languages, different domains can make it a little harder. And if you are working in a language where, for other reasons, you wind up composing smaller components into larger components and then testing through the larger components. In addition to the test that you might write for the smaller components. But if you're trying to test those interactions, the downside of that will be that you will probably wind up with, if you go to change a component, you might have dozens or hundreds of tests failing, right? Because those tests,
Well, I break my widget, then obviously everything that depends on it essentially is fair game for, or rather if I, if I break my grommet, that's in the widget, then all the widget tests are likely to fail as well as are all the things that are further up the chain.
Right. Right. And some of those things may be valid, right? Yeah. Um, but not all of them will. And, and being able to, to this kind of gets back a little bit to what we were, we were talking about with the file system, where if you have those abstractions, you can make a clear distinction between what's relevant and what's not, and sort of prevent the spread of irrelevant changes from going any farther than it needs to. It's it's tricky. Um, and I think that when you're, when you're testing that way, you almost want to, I don't know. I kind of feel like you almost want to aim for a flatter structure. Like the ratio of leaves to nodes in your graph is you want, you want more leaves and, you know, as opposed to the total number of notes,
I suppose this, I just literally now this dawns on me and that the thought is that in something like a C++ application, the higher up the sort of abstraction, or even the composition tree, you get the sort of necessarily the fewer, the interactions between those larger components that you've built are to the point where you probably will get to a pragmatic point where you're like, well, this is my graphic. You know, this is my GPU. The interactions with the GPU are,:turn the screen on ,draw triangles: (here's a giant list of triangles), clear the screen and flip the page buffer. Those are like the top level interactions that I'm going to do. And at that point I'll gladly take the virtual overhead, whatever it is, because I've only got a dozen different interactions. And they're infrequent in both in terms of the, the code and also the runtime.
I don't have to do them very much. So the classic example, I actually, sorry. Did I, the reason I, why GPU is one of the examples I pull out when I talk about this is how to decide when something should be a, a virtual like interface or not specifically in C++. Um, and the example I give is like a, uh, an interaction interface to a, uh, a texture or a screen. So you can get like, how wide are you? How tall are you? You probably don't want to have a plot pixel and get pixel because that's just not, that's too tiny, a piece of work. It's a single machine instruction usually, or two machine instructions. So you're going to necessarily hamstring of the compiler if you implement it as a virtual method. So you think, well, okay, what will I do? I'll, I'll allow access to maybe locking; getting a pointer to a contiguous region of that area.
And then I'm on my own. I've got a monkey with it. I can read and write pixels directly, or I just get a pointer to the whole buffer and whatever. So that's a great sort of like point the number of interactions you're likely to have is, uh, is, is so high. You would probably abstract it, but once you get to the, yeah, flip the page, draw a triangle. Maybe, I don't know, it's probably nowadays one draws so many triangles. It's not a big deal, but, um, once you get further up the hierarchy, that's the point where you say, okay, I'm going to put a seam in here and I'm going to divorce this part of the code from anywhere else in anything else that wants to interact and test with this part of the code. I can always put a fake one of these in here's my fake GPU, here's my fake screen.
And then I can say, sure, you can paint all these pixels. Yeah. Go, go for it, do your thing. And then I can at it afterwards. And there's no, there's no coupling there between the actual implementation and the, and the thing. Whereas the, the, the, the GPU itself may be built out of components that are more tightly coupled and are tested more in that way that we said before we sort of incrementally. And transitively, you kind of like build up this idea that the, the, the, the, the GPU component is okay, because transitively all of its components tested OK. And in aggregate, they tested OK. So maybe that hybrid approach is the right approach anyway, or the pragmatic approach under these circumstances.
Absolutely. And in fact, you know, when I was working at that data visualization company in Houston, that I mentioned, I did exactly that with 2d graphics, you know, we built a layer of abstraction on top of the graphics library. We were actually doing this in Java at the time, which was a little weird, but it would have worked. Um, and we built a layer of abstraction on top of the 2d graphics system for, for testing purposes. But it also let us do a lot of other, like, very interesting things. It made it a lot easier when we needed to add functionality like, Oh, can you make a screen capture? It's like, well, yeah, I can just turn this graphics object that I have into a buffer. And now you've got your screen capture. Or, um, we actually even wound up doing a thing. This was also for testing purposes, though, very different kind of testing where we would test some of our graphics code by rendering it into a buffer and then running it through a vectorization program. And seeing if we approximately got the same vectors out that we put in, and that was, yeah, a little clever but it, it sort of told us some interesting things about how it works.
Are you meaning that in the slightly pejorative sense of the word?
Only slightly pejorative. It was a technique,that was interesting that told us some things about our code. I wouldn't recommend it as a general purpose technique for testing graphics, but it was possible my point here is that it was possible because of this abstraction that we had created. And so those, those kinds of things, I think to, to your point, it's like that is a perfectly valid technique. And I think thinking about where you want to add those sort of those virtualization calls in C++, or, or, you know, the sort of layers of abstraction in whatever language you're working in, it's, it's obviously not just testability is the only, the only thing that you should be thinking about there it's, it's also, you know, what are they going to be the, uh, the other design impacts and the other, you know, performance impacts of doing this, but, but I really like your point about sort of the higher up you get the fewer interactions. There, there really seem to be, I think that's, that's quite insightful
From a C++ point of view as well from a build point of view and whatever the, the, the less coupling you have between components, the, or the fewer coupling
The less coupling. I think it's the less coupling
But the less coupling you have between components. It's certainly possible to leverage that, to make your builds faster, because you can carve the world up into parts that are dependent or not dependent on the interface changing as opposed to some typo in a comment in the implementation of one of those functions or whatever you're at. And that sort of comes back around to the, the, the, the conversation about fast changes to testing. So obviously the big problem, the elephant in the room, uh, with C++, well, it's not really an elephant 'cos we all know it's there and we can all see it. And it's like the build time. And your sort of worldview of testing is I know I've seen you do this. You know, you have a watch command running in one window and you've got VI in another window and you're literally saving.
And as you say, save is a micro commit to you and the watch immediately notices that you save and it runs all the tests and it's essentially a live dashboard of where you are in your process of developing and that's super important. And then it, the rules of eights, which we talked about last time, if it's 800 milliseconds, that's instant. And you're like, I saved. Yeah, that's cool. Or whoops, no, I made a mistake or no, that doesn't work with it. There's a cool feedback loop there. C++ out of the box tends not to have that. And in my experience where at least part of my career, I spent writing my own C++ parsers to try and make a whole new way of like even compiling C++ to try and solve this very problem. My experience, you have to think about that from the get-go and C++ it's not something you graft on at the end a bit, like I say, I guess like tests, right?
You know, it's harder to do come in afterwards and write tasks if you start from the ground up and say, okay, my, my build in the limiting case of having just the, the whatever test library I've picked, you know, I like Catch2, people like GoogleTest. There's, there's a few of them. That linking with a simple test that just fails or just succeeds, or one of each, just to prove to yourself that you are actually catching it, capturing that. Can I even get that building and running in a somewhat interactive amount of time? And then you grow out from there and you take a deep breath every time you cross some threshold, an eight second threshold. And again, we're never going to be 800 milliseconds in our world. I don't think, but, you know, single digit seconds is fine. And then there are ways and means of, of both breaking up your code and making it compile faster, which we could perhaps talk about at length another time, but also making it so that your components are split away from each other so that you can run tests on just that component, be a single file or a single clump of files that are together.
And obviously your build process heavily interacts with this so that you can just say, look, I only changed this file. I know that loads of my code in theory is affected by this change, but I just want you to build the library and the test for the library and ignore everything else. Because right now I'm in that mode where I want to do that quick loop back. And all the other compiler errors can, can wait until I've proven that I've gotten this right.
Yeah. I feel like there's, there's a whole category of things like that, where whatever group of people you're working with, you all have to commit. You have to make a shared commitment to doing a certain way. Otherwise it's really not going to work out super well for you. One of them is testing. One of them I think, is, is a commitment to these sort of fast build times and structuring the code so that you can build things incrementally. You can build things quickly. Another one of those is deployment. Like being able to deploy safely, a running system, you have to decide to do that at the start. It's very difficult to sort of both from an organizational standpoint and from a technical standpoint to sort of add that in later, right? So
Continuous build continuous deployment, all of these things kind of fit together. Well, they all go. They, they, they, they go together really well if you start them all together. But any one that I'm trying to put it in afterwards is, uh, is, uh, is a pain. Yeah.
It's much more difficult to do. It's not that it can't be done. It just takes hard work. And it takes commitment from everybody involved. If you have half your team that's invested in fast builds and the other half is like, yeah, that's fine. I like coffee. Um, you know, they're there, you're just not going to get to where you want to go.
That's that's, that's that is it. I mean, like, there's the whole sword fighting XKCD of, you know, what are you doing? Right. Right. Which
If you see that as an essential part of your job, it's going to be really hard to come along and have someone convince you that you should do a whole bunch of work to take your build time down from, you know, a minute to eight seconds so that you can, you know, work in this fashion. Right. Because A, you've probably never experienced before. So you don't really know what it's like, and you don't really know why it's good. And B it's like, it's kind of a huge risk. It's sort of like, well, how do you know this is really going to turn out like this?
That's a really good point. Certainly. I think there's a certain amount of institutionalization that I've seen with other companies I've worked out with the C++ developers where it is just taken as read that, Oh, well, it's a 20 minute build. Every time you do anything significant, nobody touch "error.h" because if you touch "error.h", everyone's cursed next git pull to have a 30 minute build. So off you go, and of course, you know, projects do get big. And so they weren't, there are necessary things that take a while, but it's, it's, uh, it doesn't have to be that way. And I remember having the same thing about, and we should perhaps even talk about this another time, like IDEs. I was, I, you know, I like IDEs perhaps more than VI, although I use both and I know you're a VI user and, and you know, you've seen IDEs and appreciate them.
I use IntelliJ!
I know "vi" is an IDE to you.
No, that's the emacs you're thinking of, emacs.
Um, but the, the point I want to make is that I remember seeing, um, somebody using IntelIJ as it was at a previous job. And they were just like a Maestro playing... a virtuoso, playing an amazing instrument, the speed that the ideas were forming in their head to code appearing on the screen fully formed, followed by, well, maybe I shouldn't rename it that, you know, called the variable that, Oh, hang on a second. This is an interface, pull out an interface. It was wonderful to watch. And it totally changed the way that I thought about how you could program. And it moved me much further down the explorative tap on the keyboard, look around maybe this, maybe not this versus the, you know, sagely stroke your beard and think about it, approach that we discussed a little bit last time. So there's a, there's definitely something there. And I think that obviously as part of that suite of tools, having a fast build, having fast tests, all factor into that, that sort of local minima in the space of things that you can do to be productive where you and I currently are, which is, you know, fast turnaround on builds, deploys, tests, all those things. Yeah. I mean, that's, that's a good call.
I mean, I think there's a, there's, this is a really deep topic, right? Like we're not gonna, we're not gonna cover this. And unfortunately in 45 minutes, and I think the aspects, you know, the style of we're working that we're talking about with fast builds and fast tests, the unfortunate truth is, is that you can't really see the full picture. I feel like until most of those elements sort of come into play and that makes this, this, uh, uh, a complex topic to talk about. But I definitely think, you know, maybe on the next episode, uh, or any episode after that, getting more into the, into the details of, you know, practically, how do you do these things? It sounds like you've had, you know, through your attempts, you've had some experience with it, but there are also lots of other people that I know do this, um, and have done it quite extensively. Right. So maybe we could, we could mine the, the brain trust of the internet to figure out, figure this out, but actually it's a very deep topic. I'm, I'm looking forward to talking about it more.
Awesome. We'll do another one of these.
We should, Alright!
Cool!
