#begin
Today we are going to look at the next chapter of the Clean Code book by Uncle Bob. In the previous blogs of this series we discussed the first 10 chapters of the book. Next we will look at chapter 11: Systems. Now this chapter is a bit more high level than all the previous chapters in the book. It’s about more abstract matters to make clean software design and architecture. So a if you follow all the advise we talked about in the previous blogs about clean code, you could potentially still end up with a spaghetti monster codebase because you have the dependencies all wrong. This chapter will dive a bit deeper into how to design systems. So let’s have a look.
Systems
Uncle Bob starts this chapter off with an analogy of how we would build a city. Would we manage all this things ourselves? Probably not right? Even managing an existing city is too much for a single human. This process works because there is multiple people involved, all doing specialized tasks that can be abstracted to fit the bigger picture. For example there is an infrastructure team, and a law enforcement team, these are abstracted now, they have responsibilities but we don’t need the details at this moment. This also works in a software or game context. Try to construct your game from abstract components of which you do not need to know concrete types.
So he’s first piece of advise is to always separate constructing things, from using things. So, you might have a class that instantiates a gameobject but than also directly uses it. This is bad practice since you can only create or instantiate things, if you know concrete types. If you only know the interface of the abstract class of the thing you want to use you cannot simply create it. On a language level, it’s impossible. So you would need to add like a factory pattern for example to create these objects. For example, based on some config file you might need to instantiate a weapon for your enemy. So you need to parse that file and create the correct one with the correct stats and ammunition for example. If you put all that logic into the enemy class, your enemy will be dependent on ALL weapon types you have in your game. But if you create a factory class that takes a config and returns an object of type IWeapon, then you have broken these dependencies and only your factory class is dependent on the concrete types. This is a very nice design! Oh, and for those who do not now what a factory is, it’s a design pattern for create complex objects.
I always give the following example; imagine you have a massive factory that makes cars. This factory is also joint factory for multiple car brands let’s say like Volkswagen and Audi. So when one of these need to be created, a product sheet goes into one end of the factory and the correct car comes out on the other end. This product sheet describes that make of the car, colour, what entertainment system is in there, maybe if it has heated chairs or other fancy features. Creating this all is very complex. That’s why we abstract it away inside a factory class. And thus we only care about the product sheet, and the end result that comes from the creation process. So in programming terms that config could be a scriptable object, or some JSON file. The factory is a class that creates cars and returns object of type Icar, with references to things like Ientertainmentsystem, and IChair for example. The factory now has, all the intricate and concrete dependencies to what specific car, entertainment system and chairs. We, don’t care about these concrete implementations when we ask for it.
So when you for example need to instantiate the correct weapon for you enemy. Don’t instantiate it in a method inside the Enemy class because it will then depend on all the concrete objects that are needed to actually create it. This is a really important design concept you need to know. If what I just explained does not make sense. Check the AbstractFactoryPattern out on wikipedia. I’m sure that if you have never used this pattern before you will quickly find out how powerful it is and how it makes you code a lot more clean than it already is.
Uncle Bob also says that the startup sequence is a concern that every application must address. Because, it is during startup you need to spread and setup your dependencies. And, this can be very difficult in a Unity3D project since, well, there is no startup sequence. It’s just all the awakes in the scene right? Well, you should design your own startup sequence and I have used three different approaches for this which I will explain for you guys;
My first approach is to have an “empty” scene at the start of your game. This might be a simple scene that only contains like the logo, sort of a splashscreen. But it also creates the main service hub or wrapper that contains your controllers and manager like classes. This hub is then flagged as [dontdestoyonload], so it does not get destoyed when switching scenes. And now you a way to inject dependencies. So is more of an central way of initialization. You can distribute it but you will find that you have many [dontdestroyonload] objects in the scene, so it’s best to make them a child of some other object that is flagged.
My second approach is to well, simply use the awake and setup dependencies there. So, you use the awake only for SETTING dependencies, and use the start for GETTING them. So you initialize all your Services in the awake, and then you can access them in the start of any gameobject in the scene. So this can be more of a distributed way of initialization since you do not really need the hub object I talked about in the previous example.
And the last, my personal favorite, is to use a function annotated with an
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
attribute. This function is statically called by the Unity3D engine just before a scene loads. So, if you annotate a method, and make sure that the contents of that method are only called once, you can really easily create an entry point or startup sequence for your game. Also note, that you can use this same strategy for any other scene you might load. So if some scene requires different dependency injections, create another function and use the SceneManager to check whether the scene that is loaded is the correct one, and execute your logic.
And there’s a shit ton more approaches to create a smooth startup sequence for your game. Tell me your favorite one, I’m pretty interested. I personally like the last one I described the best since it gives me the most control through code, and not through the engine persee.
So the important concept here is that your startup or what is also often called the Main or Program class is the most dirtiest class in your system because it holds all the concrete dependencies. It has to create the factories and other concrete objects. But than injects these objects as abstracted objects into the things that need them. And sometimes, things cannot be injected since main does not have access so you need to come up with some clever solution like using a static class to access things. It’s not the nicest way but it’s far better than a singleton since you can inject different objects during testing since well, constructing things is separated from using things. We talked about that a minute ago.
Next he talks about the D in SOLID, the dependency inversion principle, let’s call it DIP from now on. This is my favorite principle of the SOLID acronym! It is the at the root of good OO design and without this principle, your OO code will become that spaghetti monster I have referenced many times already now. Through the DIP you are able to turn source code dependencies into run-time dependencies and thus make your system isolated. It took me quite some time to fully understand the power of this principle but once you get it, you can cut through that spaghetti monster like a hot knife through butter.
I think if you have taken some computer science classes or tutorials or whatever on OOP. People will always tell you that you need to program against abstractions, not concrete types since it will lead to having a more understandable system. But what this is really about is removing unnecessary dependencies to concrete things. I wrote some blogs about SOLID before and you can read them on this website, just use the search bar for SOLID and you will find them, so if you are interested go check that out, or at least, check the blog about the Dependency inversion principle if you are not quite sure about what I’m talking. Trust me it’s worth your time investigating this principle, because once you grasp it, you will be able to untangle you spaghetti monster!
The next subject he talks about is that all systems need to scale. And in this context, scale refers to growing your small game architecture into something bigger. So this is not about scaling in a user sense like horizontal or vertical scaling to handle more requests for example. He means that your system must be able to scale properly without making a mess. So you might start out small with your game. Then you add some gameplay systems, maybe some inventory, then a gearing system for the stuff in your inventory so your character can wear them. Next a shop to buy and sell gear and the list goes on, I think you know what I mean right.
The thing is that, your architecture must support all these systems being attached to it. If all of these are truly separate systems there will be some awkward coupling here and these. Remember, in the previous episode we talked about creating facade classes for 3th-party code. So, your facade can live in the domain of your game, but implement a 3th-party package like an inventory system. So, you have an abstract facade that serves as an adapter into some other code. So by doing it this way, you can inject as many “external” systems into your game architecture. Just make sure your game architecture is somehow “aware” that this is happening. That might sound weird but if you for example do a FindObjectOfType in some random class to find that 3th-party inventory system, you have now created a hard source code dependency and your architecture is unaware of this reference since FindObjectOfType is a Unity utility, not one of your own. So there is no way for ANY other class to know that the query happened to find that object. It’s not governed may be the best way to describe it. Make sure that package is injected through the startup sequence of your game somehow.
Next Uncle bob talks about cross-cutting concerns. He gives a simple example about how you should not combine business code with like database access. Seperate this out, create DTO’s or ActiveRecords even. Remember them? They are specifically designed for this purpose. But yeah, what he means is that you should not make a god class that contains everything but we have talked about this in the previous chapter too. Classes must adhere to the single responsibility principle in order to create nice systems. If you follow this principle, you will not have cross-cutting concerns in your code but have them separate out nicely. Generally if you follow Uncle Bob’s SOLID principles you can’t go wrong with Object Oriented design.
The next subject is a rather interesting one, and one I have not heard about for a long time. It’s how to properly use Java dynamic proxies, which follow a proxy design pattern. But a Java Proxy allows you to wrap existing classes into another one and then invoke methods on these wrapped classes through some reflection magic or byte/code or intermediate language package. This is rather complex and also very slow. Doing any kind of reflection logic is always slow. I’ve used this pattern in some very, very limited cases. I can remember we bought an asset on the asset store for implementing really nice loading screens, with mini-games and video’s in them and everything. However, these base-classes for the loadingscreens and their components where all all encapsulated meaning much of the logic was private. I could not extend them, but I had to. So, I created proxies for them because I needed to get those requirements in and I did not want to change any 3th-party code since if I update that package, all my work will be lost. These proxies used reflection to call certain methods in the 3th-party code and although it worked well, it’s still dirty and I don’t really like this solution. But in some very, very rare edge cases, proxies can be a life saver. So remember this, if you find yourself with some code that is poorly extendable, you can write a proxy class for them that uses run-time reflection to call methods and properties. But you should use this pattern vary rarely.
Next Uncle Bob says you should strive to build your domain out of POJO’s. And what the hell are POJO’s, well this is an acronym for Plain Old Java Object. So in our case, since we write C#, it would be POCO’s. But what are they, you might wonder. Well they are classes, that have no outgoing dependencies to any frameworks or external libraries. So it’s just plain old C#, written by you, and not referencing any external stuff like the UnityEngine namespace or any other namespaces than System, or your own. When we build the domain out of these POCO’s it will become far more simpler and easy to manage. Because when you do this, you can push your dependencies to the edges of your system. So only the part that needs data-base access actually has it though some polymorphic interface of course, or only the part that needs the UI, has access to the UI. This is some great advise by the way. Try to follow this rule and make sure your business code, the family jewels, do not have dependencies on any other code, that you do not own or manage.
Something you get when you use POCO’s in your domain is that you can optimize decision making since your design is modular and concerns are separated. They make decentralized decision making possible. A comment you will often hear in software architecture talks, or books is “postpone an architectural decision as long as you can till the last responsible moment”. Designing a system out of POCO’s will definitely help you a lot in that dicision since you can build your entire system through tests for example, and later connect a data-base, UI, or even Unity3D or any other infrastructure. But we also need to be realistic here. We are gamedevs, and we chose Unity3D as our engine, surely we will have some code coupled to the engine since it makes our lives far more easy. But for example, if you are building a system for your inventory, it can be decoupled from Unity, and just the UI, the monobehaviours or UIElements even are dependent on the Unity3D engine. The business logic for the inventory can very well be POCO’s and be managed in your business code. So when you need to make the decision to make the inventory a 3D inventory made from 3D objects, or a classic UI made in the canvas, or a new UI made with the UIToolkit you can postpone that decision till the last responsible moment. This is what Uncle Bob means with optimizing decision making and I’ve taken this approach many times and once you get the hang of it, you will notice you can change things more easily and create very nice systems that do not depend on Unity3D which promotes testability!
Next he talks a bit about domain specific languages but this relates more to Domain Driven Design practices than an actual DSL I think. In DDD this is referred to as Ubiquitous language. He says your project should be build with domain objects that every stakeholder understands. I think I talked about this in the first episode in this series of podcasts. I made the case that when choosing a name for something, all stakeholders should agree with it. Or you might end up with confusion about what certain classes mean, like the Player. Is a player a user playing the game, or is a player some audio or videoplayer? So make sure your domain is known with everyone and have some consensus about it.
But Ok, that’s a wrap for chapter 11: Systems. We have talked about a lot of things; we discussed how a why you should always separate construction and using logic from each other so you don’t end up with concrete dependencies everywhere. We discussed to decouple your startup sequence from the rest of your system and use factories in strategic places. We discussed a bit about dependency injection and how to scale your game. We also discussed how to deal with cross-cutting concerns and what the heck proxy classes are. Remember them? They are clever reflection hacks to drill into 3th-party code for example where you should not have access too. It’s a clever pattern that can sometimes be a real life-saver in some very rare edge-cases in game and software development overall. We finished with a discussion about optimizing decision making and the need for a Ubiquitous language.
Thanks for joining me on yet another blog and hopefully see you in the next one!
#end
01010010 01110101 01100010 01100101 01101110.
Recent Comments