DevQuestions with Tim Corey
I am Tim Corey. I teach coding online and my inbox is full of questions from current and future developers. I wish I could sit down with each of you and share the answers I know will help you go further, faster in the world of development. This podcast is the next best thing. On this podcast, I will answer the biggest questions people are asking! Send us to your suggestion for a future Dev Questions at https://suggestions.iamtimcorey.com/ To keep the podcast coming, like, subscribe, rate, and share it with your friends and colleagues. See why thousands of students have chosen to learn to think and code like a professional developer at www.DevForge.com.
DevQuestions with Tim Corey
312. Why The Worst Code is Working Code
Use Left/Right to seek, Home/End to jump to start or end. Hold shift to jump forward or backward.
The worst code I've ever seen has been working code. Code that looks like it does the job. So why is working code so dangerous? Why is it the worst type of bad code? These are the questions we will answer in today's episode of DevQuestions.
Website: https://www.DevForge.com/
Ask Your Question: https://suggestions.iamtimcorey.com/
Sign Up to Get More Great Developer Content in Your Inbox: https://signup.iamtimcorey.com/
The worst code I've ever seen has always been code that works. I don't mean that all working code is terrible code, but when I find a really bad bug or a major issue, it's almost always in code that looks like it's working. Let's talk about why in today's episode of DevQuestions.
SPEAKER_00Welcome to the DevQuesti podcast with Tim Corey.
SPEAKER_01Software development is more than just writing code. So let's talk about the rest of it. Specifically, let's talk about the dangers of working code. I've been talking about AI a lot recently. I launched a series, and don't worry, this video is not about AI. I launched a series called AI Hurts Everything, where I talk about the downsides of AI and what we can do about them. One of the conversations that comes up a lot, though, when I cover AI is the idea that it creates working code. Every time I hear that, I cringe, but people don't often understand why. So today I thought we'd talk about some times where I encountered major bugs because I've spent years as a consultant. I've seen these bugs numerous times. And this kind of enlightens you maybe on why AI code or even code that you wrote or wrote you found on the internet is really bad sometimes when it's working code. So I'm gonna give you some examples. Now I'm gonna kind of compact multiple examples into uh one talking point in a number of cases, and I'll change the information a bit to protect the maybe not so innocent. Um I don't want to burn anybody. So number one, um, this I've seen a lot, and everyone's like people talk about it, but rolling your own authentication, or even authentication in general. It this becomes a bit of a black box where people think they know how it works, or maybe they have an idea of how it works, or if they roll their own off, they they're sure they know how it works. But the problem is that they don't understand the edge cases. They don't understand what's really going on behind the scenes. I worked for a company once where we had a system that that captured information off the web. And there was some roll your own auth involved. And it wasn't terrible. It actually worked, and it actually authenticated a person. If you didn't have authentication, you couldn't get to the page. So in that way, it all worked, right? Well, yes and no. There was a couple of, well, quite a few problems. First of all, there was SQL injection possible. You can inject SQL into a command even though you're not authenticated and get extra information. And if you were authenticated, you can get even more but uh beyond what you should have had access to. But also, that data was in a place where non-authenticated people also had access if they knew it was there, right? It wasn't something obvious, but it was available if you knew where to look. So potentially that data is also compromised in that way. And then there's the issue of the actual authentication and how it worked, because that authentication was not strong authentication, and therefore it could have been cracked. So that worked, and that's kind of the worst part because what it was capturing was rather sensitive information that it was storing in a database that then is accessible to others, right? So because it worked, what happened is it allowed more and more and more sensitive information to be captured where it was exposing more and more and more sense of information potentially to the world. And so when it came out, the problem was worse than if it happened at the beginning. If it hadn't worked, well, then there'd be no information captured and there'd be nothing exposed to the world. But because it worked, it exposed thousands of people to um to their information to the web. It just wasn't good. Um I've seen that a number of times. Not always the same, not always roll your own auth. Sometimes it's, you know, they use an authentication system, they use OAuth, or use something else. And that's not bad, except for the fact that if you don't understand the entire process and you don't understand where you need to secure and what you need to do, well, then you can expose a key or you can expose something else along the way that either bypasses that authentication or the authentication is just not as secure as you think it is. So that's number one. Um number two, performance as an afterthought. So this one, I'll give you an example from my own experience where I was the problem. Um I have a number of those issues, but I built an application that worked. And it worked, I thought, pretty well. And it did the job it was supposed to do, and all my testing, it worked great. Um, I put it in production and things broke. And the reason why is because it worked. It worked well in my demo setup setup, but once the real-world pressures came in, once it wasn't just some demo data, but it was real-world data that was faster and more of it and you know it built up over time, it had a problem. It couldn't be, it wasn't as performance as it needed to be. And the reason why is because I built it in a way that works. It worked, but performance was an afterthought. It was something where I'm like, oh, well, yeah, that's the best way of writing that, but I didn't think about is that the most performant way of writing that? Have I tested that? Have I looked at alternatives and see which one would be faster in this specific situation? No, I didn't do that. As a result, the the application didn't work long-term. It worked at first, and what happened was it would start to slowly back things up until things crashed, until things were there's a problem, and it started cross-threading information because it was just too far behind. Um so it wasn't like it just wouldn't work at all. It did, but because performance was an afterthought, it worked until it didn't, right? Until this is one of those things I see a lot in a lot of different situations where it works in demo code and it works when you have a thousand customers in your database, and it works when you have two thousand customers and five thousand customers. Once you have 10,000 customers, all of a sudden it doesn't work anymore. And that's a problem because if it had not worked in the beginning, then you would have fixed it before you had your first customer. But now you have five or 10,000 customers in the database, it's kind of too late to start over. It's kind of too late to go, oh, oops, we need to do things totally different. Performance as an afterthought is a big deal. Now that kind of leads into the third point, which this is kind of the same point too, so you can kind of call it 2A and 2B, or I could call it number three. Um, and that is database performance as an afterthought. This one frustrates me a lot. I spent a long time as a DBA. So I was a consultant for a number of years, and one of my roles with a lot of companies, I worked at hundreds of companies, um, and a lot of companies, my role was the database administrator. My job was to make that database as performant as possible, make it uh you know, support the entire organization, grow it, change it, shape it, um, even build new ones, et cetera. And so I know how to do performance in the database. And what I saw a lot of developers do, especially when I came into organizations where I was picking up after a team fell down, um, after something didn't go well, um, they'd call me in, and the first thing I'd notice is that they didn't understand database. And so what they did was they said, it's an afterthought. It's like, you know what, it works. You know, uh select star from database where works. Um, and they don't understand how to optimize that performance. And the thing is, some things are easy, right? So if you were in a query where maybe you put um you know, where first name like uh percent sign, I am percent sign, you know, find Tim and Jim and and you know, anything else with IM in it, but that's a not really good performance query because of the fact that it's a percent sign first. You know, there's other ways you can do that that will be better. And so if you can change that query and that will change performance, and you're good, right? So you could you could potentially change how that query operates just in the query itself and fix your performance so it didn't hurt you. But there are other things where you can't do that because you're dependent on a certain way of it working, and you really have to change how that works, but it's too late. It's it's it's too late. You have to do it that non-performant way. And I've seen this a lot of times. This is one of my kind of signature things I would do is help companies clean up, especially after, and this is, you know, you'll probably understand where this comes from, after entity framework. Uh, not because entity framework is a bad tool. It's not. However, it can be used poorly. And in fact, the default way of using that is poorly. So if you accept the defaults of entity framework, it's gonna work poorly. And too often companies, developers didn't understand that they just thought it was a performance boost for their developers, and they didn't understand how to optimize the data access. It was an afterthought. And by the time it gets to production, it can be too late because it can take re-architecting too much of your application to change how it works. It's not just, oh, well, replace that one call with store procedure. That's not going to fix the problem. So there are things like that where you have to do major rewrites and major changes to how your code works and even have some major downtimes sometimes in order to make these things more performant. That's a big problem because again, it doesn't happen when you have 10,000 customers. It happens when you have a hundred thousand, a million, where it's too late. And you might think, well, I'm never gonna have a million customers. It's a whole lot easier to think. I worked in an organization where the organization had a couple hundred employees and they had 12 million records. And it's not because they had even 12 million customers, but because customers generate multiple records. If you have 10,000 customers and then you have a hundred records per customer, all of a sudden you got a million records from that. So all of a sudden, your database, when you think, well, we're a we're a small company, we got you know a small amount of things, all of a sudden that scales up to the point where your database is bogging down. And what happens is you build, you buy bigger and bigger and bigger servers to run your database on, and you still can't get the performance you need, but that's the only real solution that doesn't take a lot of money and a lot of time. And so you keep buying bigger and more expensive servers until you go to the cloud to get even bigger servers because you just can't handle that. That's a very common refrain where you set database performance as an afterthought. Uh, number four, let's stick with the data theme, but uh data access over permission. So this is one where it works. And this is okay, let's say that the simple example that you've probably seen in your own applications, your own companies, um, is the most extreme one, and that is where you give the system administrator password to your connection string. So your application now has system administrator permissions. Now your application is doing everything right, right? So it's going to only call the queries it should and only do the selects that it should, et cetera. But that permission the application has is over permission, more permission than it needs. And why is that bad? Well, this is where SQL injection really burns an application. Because if you have a web application that has a connection string that is a system administrator connection string, and you have a vulnerability for SQL injection, well, now they can drop tables, they can dump that table data down to their client, which is the worst thing they could do because they could potentially get all your information and then not tell you and not do anything that would kind of tip you off that they have this access. So you can go back and get it again later after you have more customers come through. So again, over time, this is a real problem. Um I see this a lot when companies use desktop applications. Uh one of the ones that I worked on was a desktop application where they had permissions. They had different levels of permissions and who could do what, et cetera. And that system worked, except for the fact that the desktop applications were using a system administrator connection string. And they were encrypted. It was encrypted. The problem is that if you encrypt something but you they you own one end of the encryption, it's not encrypted. It's it's accessible. And so there were things that were changed and stolen because of the fact that that system was had more permission than it needed to. And so when the person got access to that connection string and the unencrypted password, because you unencrypt it on one side of the connection to send it to the other side, um, they were able to access data. And the problem is that it wasn't like we found out immediately because they didn't crash the application. So they have access for a period of time until we caught them doing it. So this over permission is a serious problem. This is why when I build my SQL database, if I build a SQL database, I love using store procedures. And I like them anyways because it allows me to put my SQL code in SQL and just call that from C sharp. But also because of the fact that if I do everything through store procedures, then I can craft a login that only has access to execute those specific store procedures. Then it doesn't matter if I have SQL. Well, I mean, you still don't want to, but it doesn't really matter if you have SQL injection or not, because they can't access anything they couldn't already access. And it doesn't matter if a person gets access to connection string, but the connection string does only the same things that they already had access to do. So the idea of crafting that to just what they need, not even be able to read other tables, not be able to see that the tables exist, nothing. That you can't, you know, this is where uh especially any type of ORM I don't love because of the fact that ORMs typically want to have access to do things like modify tables and drop tables and create tables and things that are great if you are doing them the right way, the happy path. But if you are a malicious actor getting that same access, that's a problem. All right. Number five, this is gonna sound weird: naming conventions. So I have worked with people who didn't like the naming conventions of a certain project. So they were hired on, they were brought in, and they're like, well, I don't, that's not how I do it. This is how I do it. And the the one that really kind of stands out to me is people who are saying, well, the industry standard is to do this. That's what I'm gonna do. If you do that, it doesn't matter if the project is something totally weird. I worked with projects that do things where, like they name a uh a form, like the main form they call it FRM main. We don't do that anymore. It's Hungarian notation. It's bad. There's actually problems with it. But at the same time, I worked on projects where that's the case, and I have changed my way of doing things to conform to that convention. And here's why. When you don't, it's fine, it's fine at first. Um, you know, Bob worked on that that section over there, and he used a different convention, but you know, we use this convention. But what happens is over time, confusion sets in. You know, well, this one is FRM main. We have a main over here. Is that which main are we talking about? You know, that's main form, this is FRM main. And so what happens is it's not right away that you start conflicting, but over time you start to have conflicts. And over time, the other thing that happens is, well, if Bob does it this way, well, I like to do it that way. And all of a sudden, everyone's doing what they feel like rather than conforming to a convention. And one of the things that convention does for you is it allows you to make sure that all things look the same, meaning they all should work the same. Meaning if we do things a certain way, let's just take a simple example. If every time we have a method, the first call we make is to log that method was being called into the debug log. Okay? Um, and the last thing we do is log to the debug log. If you have that pattern and you always do that pattern, then when you look at new code and it doesn't conform to that pattern, it stands out. But if that's my pattern, but your pattern is, well, actually I do this first and then I log to the debug log, then it's it's gonna be harder for people to catch the fact that you didn't log to the debug log at the end because you're doing things a little bit different. But if you always have the same pattern, then it's easier to spot nonconformity and spot a potential issue. It also makes communication a whole lot easier. Like I said, if I say, hey, I want you to modify the main form and you modify form main or main form, but I meant frm main, well, that's confusing because there's two of them, and you modify the wrong one, and that causes problem. So changing conventions actually can cause major issues. But again, it doesn't happen right away. It's not a big deal right away. But over time, it is. And by the time that it is a big deal, it's usually so worked into the system that it's really hard to root out, and it makes changes really difficult. Number six, overuse of patterns. This happens a lot, especially with newer developers, but really anybody can follow in this pattern where they get enamored with a pattern. I've seen senior developers who get really enamored with, say, CQRS, and they really want to use CQRS. And what happens is they use this pattern, and you know, it it's an application that all of a sudden it starts to spiral out of control with how many things are going on. And next thing you know, well, yes, technically kind of things aren't connected anymore. You have loose coupling everywhere, but you also have to make changes in eight different spots if you change one table, right? That's a problem because now what you have is this maybe it's loose coupling in your in the true uh in a standard sense, maybe, but what you have instead is in some ways tighter coupling because the fact that one change in the database means changing in all these different places. And that's not good. You don't want to have to do that. But then the other end of it is you say, well, I don't have to, but then we have you know, it called first name over here and primary name over here, and those two are actually connected, but it's you know, because it's loosely couple, it's not like be careful. There's a time and place for every pattern. So there's a reason why these patterns exist. However, be very careful that you don't overuse patterns. Otherwise, what happens is it works fine at a certain level, but as you grow, it becomes unmanageable to the point where you get locked in. Here's a really simple one. And this is might be shocking. You know, one of the simplest patterns to learn is dry. Don't repeat yourself. However, it's also implemented poorly often because what happens is people say, well, it's gonna be dry, gonna be dry, gonna be dry. So they create a library for everything. And they have a library that everything points to. That way you're dry, you're dry. But then you have this coupling going on where you have all these different applications. I worked at a company once where they really couldn't change how things worked, and they actually had to create duplicates of things. So you'd have, you know, uh my method and my method two and my method three, because of the fact that if they changed my method, it would break everything. And it was, you know, it had his fingers into everything. Then they started doing things like well, we have this DLL, it's called this, and that's a DL we can't change. Change, can't touch it. So we have to create a new DLL with our new logic because this is a new way of doing things, but we can't change the old thing. There's time for reputation. There's time for saying, you know what, this is not the same thing, or this needs to be repeated over here because it actually allows for looser coupling. So overuse of patterns can be a problem. But again, it's not a problem today. It's a problem that happens down the road. Number seven, overuse of packages. So there's a package for everything out there. If you're using npm for the web, or if you're using NuGet for C Sharp applications, whatever you're using, there's a package for practically everything. And we've seen this happen before. The LeftPad incident for JavaScript, where everyone was using this library, almost literally everyone, was using this library called LeftPad, where what it did was if you wanted to have a string that was 10 characters long and it only had two characters in it, you would pad on the left-hand side either spaces or zeros or whatever character you wanted to fill up to 10 spaces. So if you had a number, let's say one, zero, and you wanted to use left pad with zeros, you could say that 10 numbers long would be zero, zero, and then all the zeros to one, zero at the end. That was left pad. It's a whole library, or it was. And the whole world used it. The problem was that when the owner of LeftPad said, you know what? Nope, I'm pulling this out of NPM, it broke everything. It broke the web. And to the point where they actually said, you know what, you can't remove this, and they brought it back because it really was what the whole web depended on. That's a problem. You're creating dependencies where if that goes away, your whole application breaks. A bigger one I see in.NET circles, but in all circles, is when you have dependencies on a bunch of different packages, to the point where when it comes time for.NET 11 to come out, and you go, you know what, we're going to upgrade a.NET 11. And you try to, and what stops you is a few of your packages. Because you have so many, and you're dependent on all those other package maintainers updating their packages to support.NET 11 andor whatever the upgrade is. And you have to wait for all those to be ready before you can get yours. And you know, inevitably one or two are getting slow, and maybe even one decides, you know what, I'm not going to do that. And all of a sudden, you're stuck on a version. And I see a lot of companies that are stuck on versions. I know customers that are or people that are stuck on.NET Framework 3.5 because they can't move beyond it. There's dependencies there that can't get away from. So overuse of packages can be a real issue. And again, it works. It works day one, it works month one, it works year one. But it's only down the road to go, uh-oh, um, they're not updating anymore. We're five years into using this package and they're no longer updating. What do we do? Because we're so dependent on that that now we can't upgrade either. And we can't back out because we now have hundreds of different places where we depend on this particular package. And it takes a lot to undo that, unwind that. Number eight, this is an insidious one, but it's one that, again, happens when it's an overtime thing. That's data corruption. Uh, data corruption, if it happens immediately, you know, so let's say you have a really simple. You have a form that says capture first thing and last name. And you capture first name and the first three letters of the last name, and then just garbage characters. That's gonna be pretty obvious in testing, right? So it probably won't get to production. However, this is what I've seen happen is you have that form, it fills it out, no problem, works. And then you have a background service that every once in a while will clean up old data and move it over to an archive. You need the archive data, but it's not something you access maybe hardly ever. You know, it's it's only used for court cases or for uh discovery, those kind of things, but you have to have it. If that background service moves the data over, but every, and I've seen this happen, every thousandth record, it messes one up, or it don't it doesn't put a thousandth record in and it skips over, that's a big one because it's not something you spot immediately. It's something you spot years down the road. And by that time, it it's too late. Like there's not a there's not a resolution, there's not a uh an undo button, there's not a well, we'll go to the backups. Those backups don't exist anymore. So trying to find how to recreate that data is very, very difficult, if not impossible. And that's again where it works in the you know, in the short term it works. And even the backups usually work for a while, but it's that every thousandth one or every ten thousandth one, or you know, if it's doing a backup and it goes past midnight, that causes a glitch to happen. These are all things that that um we don't necessarily test for, we don't necessarily catch in testing, but what happens is we catch it later, far later, and it's kind of too late. So those are eight examples. There's lots more. I could do a lot more because I've seen a lot in my time. But the bottom line is this a broken application just stops working. That's bad, but it's something you can usually fix. A working application that is broken in a non-obvious way is worse because it often compounds the issue over time. The system that goes down can be rolled back and recovered. But a system that corrupts data or code over time is much harder to restore. So let's talk about some action steps that we can take. And this is where, again, I'll come back to AI a bit, especially in the light of AI, because we allow somebody else, whether it is our coworker or AI, to write code for us that we're responsible for. Well, then we should be taking these steps. Uh, number one, have a clear architectural plan up front. So when you make architectural decisions in the moment, you make mistakes. You don't think it all the way through. Have a plan up front. And if you have to modify that plan, if you're working with a team, get the team together to talk about it. Make sure that you get approval, make sure you get buy-in on something. Don't just say, oh, well, you know what? I think we're gonna use an API for this. You're gonna make some bad mistakes if you're making architectural decisions on the fly. Number two, have clear conventions and stick to them. That's a simple one, right? Have a one sheet. Don't go crazy, but have a one sheet where you say, this is what we're gonna do and stick to it. Um that way when you do review other code, you go, this isn't conforming to our conventions. And when it does conform to conventions, it's easier to read, easier to understand. It's in the same language, it's in the same format as all the rest of your code, and it allows you to see past the uh like formatting of it and see to the actual code and what it's doing. Number three, enable leased permission credentials. This is one that it burns a lot of people. Um when you see things in the news where you see, oh, you know, lost 10,000 records, or you know, expose all customer credentials, or this is the big one, you know, expose over the period of months. Oftentimes, what happens is permissions were exploited that had too much permission, right? Things are exploited that had too many permissions. If you can lock those down, you can shrink the window of potential places that can be um can be exposed. Here's one that people often kind of push back on a little bit, but developers probably shouldn't have any access to production. None. Um this is one that you know seems counterintuitive sometimes because like, well, we gotta deploy code there, we gotta make changes to the code, we gotta review the code. No, no, you don't. Um there are ways to not do that. And that's something that you know we'll talk about in number four, but but you need to be able to say there's a clear dividing line. And so my login can't get you access to production. And so if you do that, if my login gets compromised, maybe I install a bad VS Code extension and it captures my login credentials. Well, that'd be bad, but it wouldn't be access customer data in production bad. Number four, and this goes along with that, have a repeatable process for deployment that's unchangeable. Uh this is one where I really crane from people say that they inject AI into their GitHub actions or AI into their Azure DevOps process. No, um not where it's running AI. Don't do that. Please don't do that. Um because maybe you have something where like when you check something out of dev and AI checks over, that's fine. I'm not talking about that. What I'm talking about is once you get it into dev and once you've got it to the point where you're deploying it through the pipeline, that deployment process should be automated in a repeatable manner. So when you deploy to, let's say you have a staging site, and you deploy that staging site, HP hands off. That deployment should just work. And that deployment should use the same exact steps to deploy to a pre-production environment and then a production. You should have hands-off, automated process that does not change. That way, you don't have to be going into production and monkeying with things because things are a little different there than they are in the other environments. Make sure it's a repeatable process. That will just make things a lot easier, a lot safer, and give you less need to have elevated permissions. Number five, keep code changes small. So when you are submitting a pull request, when you're submitting a even if it's a commit for something else to review, keep it small. When you have 400 lines of code to review, it's super easy to skip over it. It's super easy to glaze over and go, you know what, that looks like you're following the uh code conventions. Yep, yep, you're right, using the right number of tabs or spaces. That's not a good code review. And so what happens is you more easily rubber stamp. There's been studies that prove it. So when you keep your code changes small, you can also do number six, which is review all changes and challenge every line. I don't mean be a pain about this. I don't mean um be miserable. What I'm saying is every line you should approach it with the idea of should this line be here? Is this the right line? Is this the right thing to do? Every line. Which, yes, pull requests are hard to review. And this is where I really think that the AI doesn't do us any favors because it creates a lot of code. First of all, AI is very verbose, um, but it also generates code quickly. And so we're doing more and more pull request reviews. And it's easier and easier to go rubber stamp, rubber stamp. And what happens is these bugs go to production. Like I said, the worst bugs are the ones where they work. Because you go, yep, the application builds, the application runs, and this new thing happens the way I expect it to. Therefore, it must be good. That's a problem. So by keeping code changes small, number five, whether it's the AI writing the code or you writing the code or your friend writing the code. By keeping those changes small, you can more easily review it, more easily review all the changes and challenge every line. Number six, um, so that you get a good result until you have good challenged code before it gets into production. Number seven, test edge cases continuously. Some people tell me, well, yeah, I tested, I tested the edge cases once. No, that should be an every everyday thing. That should be an all-the-time thing. Anytime the code changes, test those edge cases. And I don't mean create unit tests because unit tests don't always capture all the edge cases. And it's really easy to miss things. So what I mean is I want you to think about how could I break this system? How could I, you know, when you have a format, ask for first name and last name, and it has an enter button. That's it. Well, what are the edge cases? Well, first you could just hit enter. You hit the hit submit button, see what see what happens. Then you could um put you know, just a space in first and last name. That's another edge case. You could put um different characters that might conflict, like uh a single tick or the percent sign or um angle brackets, like you know, things that might break where if it's being injected into code direct or into a SQL command directly. Um there's lots of different ways you could look for edge cases in just a simple form. Imagine a full process of an application. There's lots of edge cases. There's lots of things. Your user's gonna find them, your user's gonna call them. Um, but those edge cases, what's really insidious sometimes is those edge cases are really hard to spot, and they're the ones where if they cause a problem, it's not gonna be a problem that happens every single time. It's gonna be a problem that happens uh on leap year or you know, only at the transition from uh 11.59 p.m. to you know 12.01 a.m. Like those weird cases where you go, oh, that's the problem, right? You have to like dig into the logs to figure out why it happened this time, but not these times, if you even see no logs. So testing edge cases continually, continuing to, yes, unit tests are good. Um, and you can use sometimes those to help with some things, but this is more end-to-end time. Sometimes it's it's just really thinking outside the box. Um, give this to a you know a person that you know that's that's just horrible with systems and have them bang on it. Number eight. Test your backups constantly. People will tell me, yep, I got backups. Um, or they'll tell me, I think we have backups. That's the that's the most scary thing ever. But even if a person said, Yeah, we got backups, have you tested them? Um I worked at a company where um the the person be like the the group before I was there, uh, one of the issues that came up was they were doing backups, and it was back when they did tape backups. And they had a series of tapes, and what they do is they'd rotate through the tapes. So, you know, tape one was today, then tape two is tomorrow. And what they'd do is they'd put the tape in a certain spot as the next tape to be used. Um but what one person didn't know was that was a place where tapes were put when they were ready to be used. Instead, what they were doing was they were putting that tape there when it was um they were switching it around. So essentially, but they were overwriting the previous backup with today's backup and putting a blank tape into the list of here's our backups. So they had backups that were just blank tapes and only had one good backup. That's a problem, right? So test your backups constantly. If you are, I've seen us too, they the backups worked mostly, but they didn't get quite everything and they corrupted just a little bit of data. So it made recovery of that data, restoring that data, almost impossible. So test your backups. If you have backups, one of the ways that I was taught to look at it is if you have backups that you have not tested, you don't have backups. So test them. I have a video on this channel about SQL backups where we take a SQL database, a production database, we run some scripts on it to take a backup of it, uh, restore that into a Docker container, run some scripts on it to clean up the data, and then create a new Docker container with just the clean data. And what that does is it creates a developer database that's very close to production, but doesn't have any sensitive data in it. What does that do? It's an automated process. So what does it do? It automatically tests the backup and restore process daily. Plus, it gives my developers a closest possible version of production where they can't, you know, they can test all the like a new bug came in yesterday. Well, we have the data that bug was generated off of, and yet we don't have access to their phone number or their email address that's you know been changed to test at test.com or something like that. Um you have a new database to use. The developers don't have to have access to production. They don't have access to production data, and yet they can still test against most of production data, and you're testing your backups work every day. Because if your developers come in and say, hey, I can't get access to the new developer database, it's something's corrupted. Well, then your backup or restore process is corrupted. So test your backups constantly. Test your restore process. You know, it if you have a web application that's on, let's just say an Azure web app, and you know, you say, well, we're we're backing up here and we're gonna restore to a different uh a different zone. Try it. Do it. Because you might find, oh, we need this this code from over there. Well, then you should have that somewhere documented. This whole process should be documented, because otherwise you're kind of up creak once you know that one zone goes down and you have to restore uh and you don't know how to. Number nine, push your knowledge deeper independent of your teammates or AI. So you want to continue to grow your knowledge, continue to get better, continue to grow, not relying on your teammates or AI. Now, you can learn complementary things, but your teammates aren't always gonna be there. You know, people decide to move to different jobs. So if if that person is the only person that knows how to optimize SQL, that's a problem. Learn that independently, right? Learn how to be better at SQL. Learn from them if you need to, but learn to be better. And you know, sometimes people say, well, I've got AI. You might not always, because it's getting more and more expensive. But even if you do, well, is it right? So learn to be learn independently to be deeper. Also, if you don't have knowledge independent of AI, then what glue are you? Why are we paying you lots of money? We should just pay someone $20 an hour. Because if you're no, if you're saying yes to the AI, well, then you're not worth it. Learn, grow your knowledge and become better at being able to tell the AI no or your teammates, that's not the right way of doing things. Learn how to do that by deepening your knowledge. Number 10, the final one depend on as little as possible with your application. So whenever you create dependencies, you create problems. Because dependencies aren't bad. They're there are the time and place for everything. And so, you know, for example, here's a quick one. I use Dapr a lot. Um, when I'm gonna do data access, that's kind of my go-to. Doesn't matter if I'm using SQLite, MySQL, or a SQL Server. Um, I'm gonna use Dapper. So that's a dependency I take. And I I know that, and I know that at some point Dapper may not be supported anymore. Um and that's something that that I still structure my application in a way where I can replace Dapper with a different data access system or even just use ADO. Um and it would mean rewriting a small layer of my application, but it's not gonna be the end of the world, right? So I write it in a way where I have isolated as much as possible Dapper and yet still rely on it. But it's really easy to go, well, I got a library for this, there's a library for that, there's a UI library for this, there's there's a uh a package for that. And what happens is you end up in an application where you have 20, 30, 50 different dependencies where that ties you down. So that's that's a real problem because it makes you makes it harder to upgrade your application. It makes you it makes it harder to kind of break out of that and say, oh, well, this doesn't do emailing well. We want to switch over, but we get ripped out of everywhere. So try to pat as little as possible. Um but this also goes on to AI as well. Um I see companies that are injecting AI systems right into their application, you know, where they're they're calling out to OpenAI or to Anthropic or something else as part of their application's operation. And the problem is that you're now dependent on that company. So if that applic if that goes down, well then your application goes down. Or if they decide to charge more, you're kind of over a barrel. So be very careful of your dependencies. Try to depend on as little as possible. Here's another one. Um, microservices. People love microservices. They're awesome, right? They're, you know, there's just so many benefits. You depend on a lot of servers. You depend on a lot of different systems running. You're like, well, we got a swarm, we got this, we got that. You're depending on a lot of different systems to be running. And that's not always a good thing. It's not always a bad thing, but it's not always a the good thing. So sometimes your best bet is to have a self-contained EXC or a website that is all on one server. Sometimes, yeah, an API to separate out and then connect to it is okay. Sometimes it's okay to have a microservice architecture if you architect it right. But be very careful to think about your dependencies in terms of servers, in terms of network, in terms of connectivity. All these things can cause problems in your application. So depend on as little as possible. All right. So that's kind of my overview of why you know the working application can often be the scariest. Because when I see code that that works the first time, you know, or works and it's like, well, we're done. It's like, no, you just started to work on that application because there are so many things that can go wrong, and these are the biggest things that will go wrong. Now that it's working, you're in some real deep water if you don't have everything right. So be careful. When you see that something works, don't stop there. Continue on. Make sure it's performant, make sure it's secure, make sure you check the edge cases, make sure that you review what it does, how it does it, how it's architected. Make sure that before it gets too big, you make the changes necessary to have it do it things the right way. All right. So thanks for listening. As always, I am Tim Corey.
SPEAKER_00Thank you for joining us for this episode of Dev Questions. When you're ready to learn to think and code like a professional developer, head over to IamtimCorey.com and enroll in a course.