Production-Grade Angular Tips on Managing Complexity
Transcript from the "Tips on Managing Complexity" Lesson
>> This module managing complexity is getting to know you kind of an intro module. In that we are going to talk at a high level, how philosophically, I approach application development. And I'm gonna take everything that I've learned in the last 20 years of programming, I'd say in the last seven or eight years of doing angular.
[00:00:23] And I'm going to just distill some heuristics or some rules of thumbs that you can use right out of the gates that will make you more effective. And so if for some reason you took the day off to watch this workshop, and really what your intention was to watch football or cat videos, I would say if you manage to memorize just this module right here, and you can recite it back to the powers that be, you're probably in a good spot.
[00:00:55] So I believe that managing complexity is the hardest thing about developing software. And simple thought exercise is that or just acknowledgement of just an intrinsic part of software development is that when your application grows in size, that it grows in complexity and typically not in a linear fashion.
[00:01:24] And the reason being is that is you increase in application size. It increases the amount of state that you have to manage. Which then, creates complexity around control flow and most importantly, is that it increases your burden for code volume. So, complexity consists of managing of state, flow control and code volume.
[00:01:49] And so I pulled this from a paper out of the tar pit, and you can google it. It's a free PDF. I'd probably read maybe the first 15 20 pages, but really within the first page or two, the author's they cover or they just touch on this topic.
[00:02:07] And it just resonates with me that every time I see an application that has problems, that it's because there is complexity that's not being managed and this is typically around state management. As a result of failing to manage state, then your flow control gets super wonky. And then your code volume expands as you have to introduce kind of more state to manage state.
[00:02:40] Is your states moving around your application was increased more code volume. And so you can see now how is your application grows in complexity, that it's not a linear progression is that it's actually more of like an exponential progression. And so, here are some general rules for managing complexity.
[00:02:59] First and foremost, there is no silver bullet. There's no language. There's no framework that does everything. Oftentimes, you have to make concessions for the strength of a particular technology. And except the weaknesses that come from it. So for instance, if you're working with a very dynamic language that is not typed well, you're able to do a lot of things at runtime.
[00:03:34] A lot of dynamic programming but you lose the sharpness of having a strongly typed language. On the other hand, you have a very strongly typed classical language and the cost of doing dynamic functional things at runtime. And so, I just at the onset, is that in the beginning of my programming journey, I figured that there has to be just this super framework, that just does everything.
[00:03:59] I believe people have tried to do it, and it just never works because ultimately you have a framework that tries to do everything and you just get handcuffed to that way. And so here is a statement by Kent Beck that I really like, and it's make it work, make it right, make it fast.
[00:04:25] And so you can see this narrative arc in baked into this workshop, is that the first thing we wanna do is when we're building something, we just want to make it work. And from there, then we can make it right. And then ultimately, as we move closer to production, we want to make it fast.
[00:04:41] Now the reason why I say this, is I made an amendment to this. And that it's make it work, make it known, make it right, make it fast. Now the reason why you wanna make it work is because you want to make it known. You want to give your users the ability to give you feedback as soon as possible.
[00:05:04] And from there, once you converge on a set of features that your users are telling you that they want, you've made it known, then you make it right. And so I'm not saying just completely disregard any programming principles that you know to be true, but at the same time I think it's important not to over engineer a solution until you know what you're building.
[00:05:29] I've even met programmers that say, you shouldn't even be programming if you're not doing test driven development. Well, I completely agree. The difference is when I talk about test driven development, that I'm not talking about just unit tests. Until you know what you're building, it just doesn't make sense to invest a ton of money or resources into building something that your users may not even want.
[00:05:51] And so I believe at the onset, start with user testing, and then move into automated testing. So with that said, here are some principles that I adhere to. That I believe will help you write much better code. This isn't even angular specific. First and foremost, code should be fine grained meaning code should do one thing.
[00:06:19] So if you're writing a function, that function should do one thing. And so anytime you describe a function or a unit of code, you say it does this end. The minute you hit that end then you've broken the single responsibility principle, and you should think about refactoring that.
[00:06:40] Code should be self documenting. When you write code that when you read the variables or the function name, and you know exactly what it does, then you don't need to document your code. So get all widgets, have a pretty good idea what that does. Favor pure, immutable functions.
[00:07:02] And this is quite simply that when you have pure immutable functions, it reduces your footprint to manage state across your application. Now, we're going to talk a lot about this when we get into Nx, which are abstractions should reduce complexity. Meaning that if you have a monolithic component or a service that's doing a lot of different things, what you've done is you've coupled everything together.
[00:07:35] And you really do not have a true abstraction within that unit of code. And so abstraction just for the record is really about managing complexity. And so if you look it up in the dictionary, online, it's really abstraction is managing complexity. It's moving things in a way to reduce complexity.
[00:07:55] And how does that work? Well, you reduce coupling. And so if you have a large monolithic service that is using, let's say, six other services, and, you need to change something in one of those services, then it's possible it could affect, any of the other services. And so when you couple things together, it makes it very hard, to make changes within your code base without, breaking something else.
[00:08:22] And so a lot of times you'll see this in the form of I have a component in another component and I want to move it. The problem is that this other component has a dependency on the parent component. At the same time, obstructions should increase cohesion. In other words, you want to group your code together in such a way that it makes it easier to navigate that particular group in.
[00:08:53] And so, the way that I think about this cuz there are already a number of different levels of cohesion or types of cohesion, is that I'm talking about functional cohesion, is that you group code together by its function. And so, I think of this in terms of a dresser.
[00:09:11] That there's a reason why you put your pants in your pants drawer, you put your shirt in your shirt drawer and so on is that you're organizing things kind of by function. So that's one way to do it. Another way you could do it I've seen is that since the night before if I lay my outfit out.
[00:09:28] Then I group it together based on what I'm wearing. And so in terms of the implementation. And so there's different forms of cohesion and it's sometimes a little bit more art than science. But when you're writing code, you should ask yourself, does this logically fit within this grouping that I'm putting it in?
[00:09:49] And you can know this because it increases portability. That if you can pick something up and move it, then you have reduced your coupling and you've increased your cohesion. And how you do this is you refactor through promotion. And so that when you find something that's like I need to share this across multiple pieces, then you simply extract that out and make that available to the interested party.
[00:10:44] And this is kind of a classic design pattern idiom but inheritance is coupling. And then one thing that I really, really, really want to point out and this is in regards to Redux in general Rx, or any library that forces you to have a well structured codebase, is do not confuse convention for repetition.
[00:11:07] Is that when you're doing this something the same way over and over that is convention. Repetition is where you're doing the same exact thing over and over. So there's there should be doing the same exact thing and doing things the same way. And I have never said that before, so that's a bonus.
[00:11:25] And then well structured code would just naturally have a larger surface area. And we're gonna see this when we get into Nx. So, team rules for managing complexity. This is for senior engineers, managers is be mindful of the limitations of your entire team and optimize around that. Is that everybody is different, everybody has a different skill set and it's important to calibrate your actions to your entire team.
[00:11:54] I would say this is really, really important favorite best practices over introducing in idioms, however clever they may be. Consistency is better than righteousness. And I can't tell you how many times I've seen like, I wanna fight you to the death over the semi-colon, or tabs for spaces.
[00:12:15] And ultimately, I find that my convictions tend to modulate to the standards set forth by the team that I'm working with. And to that end, follow the style guide until it doesn't make sense for your situation. In our case, as angular developers, we have a very, very good angular style guide written by the angular core team that we can follow.
[00:12:37] I don't agree with everything, but I agree with most of it and it's a really good starting point. So tactical rules for managing complexity. Eliminate hidden state in functions. And so if you have a function that is dependent on state within that function, then this is, in my opinion, the bastion of evil.
[00:12:59] Because you can call that method 100 times with the same parameters get something different every single time. So we'll talk about this when we get into testing, but hidden state is the devil. Nested logic. Is also really, really hard to reason about. It also increases coupling and it's very, very hard to test.
[00:13:23] So do not break the single responsibility principle. This is where you're doing more than one thing. If you put it inside of nested logic, then you're doing it more than one thing in a really, really tough way. But this is the best part of this. I call this the axis of evil.
[00:13:40] You can fix this very, very simply. So the three worst things that I see in programming, you can fix by extracting to a method. So if you have nested logic or you're breaking the single responsibility principle, what do you do? You extract it to a method. And secondly is if you have hidden state, then you just move that into a parameter.
[00:14:05] And so the three worst things are quite possibly the simplest things to fix in your code. And if you need to clarify your code with comments, then it's probably too complex. And it is impossible to write good tests for bad code. And so a lot of times when I get called into an engagement, that everything is on fire they need us to write test.
[00:14:29] The problem is that all the things that I've said up to this point are very basic general principles that have been violated. And so now, we have this compromise code base which then makes it in my opinion, impossible to write good test for bad code.