Check out a free preview of the full Enterprise Architecture Patterns course

The "Dependency Injection" Lesson is part of the full, Enterprise Architecture Patterns course featured in this preview video. Here's what you'd learn in this lesson:

Lukas explains that local, or micro, complexity is addressed at the component level. In order for component code to be testable, it should avoid hidden state and adhere to the single responsibility principle. The first step to fixing issues with hidden state is utilizing dependency injection.


Transcript from the "Dependency Injection" Lesson

>> When I think of complexity, typically, I break it down into micro, mezzo, and macro. So mezzo is kind of that in-between state. I had to Google it where I'm like,I know what macro is, like, what is in between? And, I realized that the word is mezzo, and so, I think of complexity in micro, mezzo, and macro levels.

And, what's interesting is that when I think about managing state and full control, and code volume, is that the principles that I have to adhere to apply at the local code level. So imagine you're inside of a function and you're trying to manage the output of that function, so that's at a micro level.

So I think of that like a component level. And then at a mezzo level, I think of that in terms of components or libraries kind of within intra-component communication, and possibly up to the application. And then when I think about macro complexity is I definitely think it the application or applications level.

So just to set that context is that when I talk about micro complexity, I'm talking about local complexity or really kind of component level complexity, mezzo would be kind of components and libs communicating within an application. And then macro would be application level complexity or applications level complexity.

And so starting with local complexity, the questions that I asked myself, when I look at code is, so imagine you've just opened up a new project. You've just got put on a new project and you open up some code. The first thing I'm gonna ask is, what does this do?

But, as I'm asking that question, I'm mentally going through and I'm asking myself, can I know the result of this code every single time and not only that, can I reuse this code? So just through muscle memory, I'm starting to qualify the code that I'm looking at is I parse through, and I'm asking myself, can I know the result of this code at all times?

And if I can't, then I found that that has a direct direct impact on my ability to figure out what it does. Can I reuse this code? And if the answer is no, then typically, not only do I have to figure out what that code does, but I have to figure out the context in which it exists.

And can I test this code? And so this is the big one. So ultimately, can I test it? Can I reuse it? Or is it portable? Is it testable? Is it portable? And so now this leads into what I affectionately call the axis of evil. And so if I had to write down what I do when I program on a single sheet of paper, this would be at the top.

That when almost on every single project I would say, over 80% of the code that I see out in the wild and the problems and the complexity that come from it are because or exists because that somebody has violated or made a deal with the devil via the access of evil.

And so from here, I wanna start with the statement, it is impossible to write good test for bad code. I just do not believe it is possible. I think you can write hacky test for bad code. It's very, very hard, if not impossible to write good test for bad code.

So if you're committed to writing test which testing is like flossing, that everybody believes that they should floss but not everybody does floss, at least consistently. Like every time I go to the dentist, I just basically have to cross my fingers and lie, like I've totally been flossing every night for the last six weeks that's why my teeth are awesome, and probably not mostly true at all, but yes.

Do you think you should floss? Absolutely. And how many people do it? Well, that's neither here nor there. But, when you go to an engagement and you ask your team members or you ask your client, hey, what do you think about testing? You should totally do it. Okay, what's your test coverage?

And all sudden there's a power outage and Ninja smoke bombs and they disappear, it's just this weird awkward moment of like, well, we don't actually write test, we haven't had time to do it. And one of the reasons is because the barrier to entry to writing test is so high.

There's too much friction to writing good test. Why? Because more often than not, they're not writing testable code. So if you're committed to writing test, then you must commit to writing testable code. And so, I can honestly say that if this is all you took from the workshop today, that you've just paid yourself a ton of money into the future, because this is one of the fundamental things that I run into over and over and over.

This is why we're starting At Volvo complexity. And so, the axis of evil, what is this? So, let's look at this code here. This is a variation of something I've seen a bazillion times, over and over and over. And you basically called recalculate total, you pass in a widget and then based on whatever the mode is, then it does something and then it recalculates the total, all right?

So, on the surface, you could be tricked into thinking like this is okayish code. I've used all of the cool, immutable operators, I'm using ternary functions, fat arrow function. So somebody might think like, yeah, this looks like pretty decent, this is okay. And so the question that I have is, when I run this code, so imagine I have a test-widget.

And I call this method three times with the same test-widget. What am I going to get? The answer is, we have no idea. You can never ever, ever know what you're going to get when you call this method with the same parameter, you'll get a different result potential every single time.

The reason being is because the return value of this function or the output of this function is dependent on a property outside of that method. And so, the ringleader of the axis of evil is hidden state. And what's interesting is we talk about these. These are the worst.

These are scoundrels, in the programming world, they're evil, and yet they're the easiest things to fix. So in this method, this single line is going to bring us about all sorts of pain in your life. It's the single piece of hidden state that makes this function totally unpredictable.

And so the question is, or rather, we'll get to the solution. I'm gonna leave you on a cliffhanger here. So the next one, violating the SRP. And so when I say SRP, I'm talking about the single responsibility principle. And this is a very, very, huge problem. So, you'll notice when I described this function, I said, okay, it takes a widget, then based on the mode, it does something, and then it recalculates the price.

If you are describing a function or a block of code and you say it does this and stop, you are breaking the single responsibility principle. And now for me to test recalculate total, I have to now test essentially two blocks of code or two things within a single test.

All sudden, what do you know? My complexity is going up and nested logic. So within this, we have at least four statements or blocks of code that are doing specific things that first of all l cannot test. How would l test the code in the update block? Well, l would have to step over.

Imagine someone in a movie theater like, excuse me, excuse me, trying to get to their seat. In order to test this, I basically have to set up the super complicated test so that I can hit the update block so I can test my ability to update that collection.

Not only that, is I can never ever, ever, ever, ever reuse that code. And so, this is the problem with nested logic. Is that it's hard to test, air-go, what's really hard to test and in most cases people just give up and that you can never reuse it.

And so I can't tell you how many times that I've seen nested logic in a condition or within a function and then I go to another component or another service and I'm seeing like basically the same exact thing. Well, because there's no way for me to share that code, there's no ability to achieve a certain level of cohesion because this nested logic is coupled to this data structure.

So, now, these are the easiest problems to fix. We do this via dependency injection and extract method. I could even say extract a parameter but this is how you fix or inoculate yourself from the axis of evil. So when we look at this code right here, what's the first thing that we're gonna do?

Well, we're going to go and we're going to take this bit of code here, and we're simply going to extract it to a parameter. And so instead of saying, let me just get back here, this mode. All I have to do is, pass it in via parameter. Problem solved.

You would be amazed at how many times a component or a function has been rendered completely unstable because it has hidden state. All you have to do is extract to a parameter or it's known as dependency injection. So that's the first thing. Super awesome. I think, if you would just start doing this, you've got your money's worth.

Learn Straight from the Experts Who Shape the Modern Web

  • In-depth Courses
  • Industry Leading Experts
  • Learning Paths
  • Live Interactive Workshops
Get Unlimited Access Now