#begin
In the first couple of blogs about clean code we dove into the first five chapters of the book. We talked about what clean code is, why it is important and why we should strive to keep the code clean and always improving. Then we jumped immediately into chapter 2 which was about meaningful names which described how we should name files or classes, functions, arguments to these functions, variables and much more. Next we dove into the chapter about functions. We discussed how important it is that functions must be easy to understand and readable. We don’t for example allow functions to have unknown side-effects and make sure queries and commands are separate.
The fourth chapter was all about comments. We talked about how the best comment there is, is the comment you did not have to write. A comment does not give you an excuse to write bad code, and is essentially a failure of our ability to express ourselves in code. We discussed some of the best practices and what types of comments should always be deleted. So delete commented out code when you see it, don’t read it, just delete it. That’s a nice hashtag I guess. #dontreaditdeleteit
The last chapter we talked about was about formatting, which is highly important as well. However, much of this is automated by our tooling like linters and IDE’s nowadays. I think back in the days when Uncle Bob was just starting out, or even when the book was written, which is late 2000’s, there wasn’t much tooling. But this chapter was fun to discuss and you will definitely learn something.
So for this blog we are going to dive into the next four chapter of the book.
In this entry, we discuss is chapter 6 and that’s about Objects and data structures. We’re going to look at what rules apply to objects and what rules apply to data-structures. There is a distinct difference we will discover and talk about. I remember reading this chapter for the first time. I was blown away by the simplicity of the advice or principles that are described here. OOP principles and values can be really difficult to describe in a way that is really simple and makes sense to even the most junior among us. But Uncle Bob explains it so nicely and if you have knowledge of the SOLID principles, you are going to see a lot of overlap with them. So let’s jump right in!
Objects and Data Structures
So he starts of by saying that Object oriented programming is about hiding implementation. So why do we expose data with getters and setters? He makes a valid point here doesn’t he? How many times have you seen classes that have all of their fields or properties marked public but without valid reason. Then also he says that OOP is about hiding implementation and this is a particular interesting observation. Because hiding implementation is not just about data, but also about hiding concrete implementations by means of polymorphism.
For readers that do not know what polymorphism is; At run time, objects of a derived class may be treated as objects of a base class. So for example we might have a base class called vehicle, that exposes a function called MoveForward. And we have derived classes called Car and Truck. Both derive from vehicle, and thus you can treat them equally and both move them forward with the MoveForward function.
So with polimorphism we are able to operate on objects, of which we do not need to know the runtime concrete type. We don’t care if it’s a car or truck, we just want to move it forward. We can just act on it’s interface called vehicle. And this might be difficult to grasp in the beginning and I have written a blog about this before which you can search for on this blog. So polymorphism is also a really important concept in OOP to write clean code and manage your source code dependencies.
So let’s take a look at the first item on Uncle Bob’s list about Objects and Data Structures which is all about Data Abstraction. And what he essentially talks about in this first paragraph is that you should try to avoid adding automatically generated getters and setters to object and really put some thought into writing a proper interface for an object. Don’t just expose it’s inner data structure, but also enforce policy for accessing and manipulating that data. This might take some effort and refactoring here and there but generally we want to express our data and object in abstract matters.
The problem with exposing all these getters and setters is that you probably get business logic spread all over your application that could have perfectly been hidden away in the object itself. So try to encapsulate that logic into the class and make it accessible through it’s abstract interface. This way your objects will fit your domain better and functionality is localized into the class.
And I guess that what I just explained can be difficult to grasp since it essentially describes how to properly do Object oriented programming. But what it boils down to is to use access modifiers properly and encapsulate code into the objects it belongs to. Don’t just make everything public and thus accessible to everyone. This may be difficult to get started with in Unity3D since I think the current official tutorials still tell you to make fields public to be able to access them in the inspector. Just as a reminder, if you make your fields private, but add a SerializedField attribute to the fields, it will also be accessible from the inspector. But now you cannot practice your pastafism religion and create a giant spaghetti code monster since they are private and thus you cannot access these properties in code. And this can be difficult to start with since, yeah the tutorials still tell people to do it this way. You will need to do some proper architecture and use of design patterns for example to solve accessing data. But be will dive into proper game architecture in later blogs. Let’s continue on with clean code first.
The next item on the list is Data/Object Anti-Symmetry. What the heck is that you might think. I thought the same when I read it the first time a long time ago. Uncle Bob explains that Data Object Anti-Symmetry is the concept that Objects, or well Classes encapsulate their data and hide it behind abstractractions while data-structures expose their data publicly and have no meaning full functions. So they are their complete opposites. We can see this concept in Unity3D as well. Generally, not always but generally, data structures are implemented as Structs in C#. And you are unlikely to find a struct that has one or more public facing functions. Like for example the Vector classes in Unity3D. They do not have any public functions that are actual operations that manipulate the struct. And That’s for a reason of course since Structs are value objects and not reference objects. And I’m not going into the rabbit hole to describe the difference between value and reference objects since I’ll have to explain everything about the stack and the heap, garbage collection and everything. It is worthy of it’s own dedicated podcast however so let me know if you would like to hear a podcast about the stack, heap, garbage collection and maybe even intermediate language. Having an understanding of these topics will improve the performance of your code since you are able to follow the rules better. So you know when to use a struct or a class and when and how to initialize collection type objects for example.
But, uhm yeah, we were talking about data object anit-symmetry and how classes hide and astract their data, and data structures do the opposite and this expose their data and do not have nice abstractions to access that data. This is a nice concept to keep in the back of your mind while designing systems for games. Try to make you classes operate through abstractions and an abstract interface while hiding implementation details and encapsulated data. While on the other hand, try to expose your data if it’s a simple value object or data structure and consider using a struct instead of a class for performance reasons. I just mentioned I wouldn’t go too far into details about this now because I will probably spend the rest of this podcast talking about it but if you want I’ll take a deep dive into this subject in a future podcast. Let’s continue discussing the book for now, shall we?
The next topic is something called the Law of Demeter. And if you have watched some of the popular, high profile people in the software industry like Uncle Bob, Martin Fowler, Kent Beck or Ward Cunningham for example, you might have heard about this law.
It is the law that states, and I’ll quote the Clean Code book: “a module should not know about the innards of the objects it manipulates”. This is essentially what uncle bob already told us in the previous section right? The Law of Demeter refers to objects hiding their data, but exposing operations. So an objects internal structure is not exposed to the outside world. It also states that if we have two object called A and B. A is only allowed to call operations on B or returned or created by B. You are not allowed to call operations through B on let’s say an object called C. I hope this makes sense without source code, it’s a pretty easy concept if you see some source code. So if it doesn’t pause my beautiful voice for a minute and check the law of demeter out on wikipedia for example.
I personally think this a great law to follow in your OOP code. It will make your code less fragile and you will have less dependencies on the structure of the data. So, if you need to refactor something or add new features, you are less likely to break things. It will avoid you making what uncle bob calls train wrecks, which is the next topic on uncle bob’s list of advise for objects and data structures.
So Train Wrecks are what Uncle Bob calls these long successive calls through objects. Like for example doing a call like PlayerSingleton.ActivePlayer.LeftHand.Weapon.Fire(). So this is a long, Train wrecked chain of accessing data and calling a function in the end. You should not do this, this is bad practice and breaks, the Law of Demeter we just talked about plus if anything in that call chain changes, or maybe is destroyed in a Unity sense your Train wreck will crash. Exceptions will be thrown.
Now, having said this we’ve probably seen hybrid solutions. Like Objects that expose really nice and significant functions but also expose some of their internal data structure to the outside world through public accessors. Uncle Bob says you should try to avoid these kinds of classes but I think this is more nuanced. I mean, I will have classes in my code that expose a nice interface but also expose some of their data. Although I try to make the data I expose immutable if I can. Because the problem with exposed data is that everyone can access it and thus potentially change it. But these changes are not governed by the class itself anymore if you access them though their fields for example. So this can lead you to the situation that some object changes the internal state of another but it might not be aware of it. So for example, if you have a classes A B and C, where B holds a reference to C. And A is able to Destroy or Dispose C though simple accessors Like B.C. B will probably throw an exception when trying to operate on C since C is unaware that his wold changed. But if you make C a private member of B, then if you wan to access it you must go through B and thus B has more control over C and A has less control over C.
Alright, again, I hope this makes sense since I get that explaining this without source code is quite hard. So I hope you have a grasp of all I just said. And, if not, please leave me a comment or better even, check out the book 🙂
The next thing Uncle Bob talks about is what he calls Hiding Structure. I think, we talked about this for two times now already. But he dives in a bit deeper now with some very nice example. He says that since we should hide structure of objects and their associations, we need some other way of exposing these associations. So imagine that you have two classes called A and B and you need to access some functionality in B but only have a reference to A. How would we access B when we are supposed to hide B and not expose that to the outside world. You could create a function on A and a function on B and thus you can call it on A. But this will mean that A will get lots and lots of functions based on it’s associations. So if A has 3 associations and all have 5 functions, A will have 15 additional functions that should have been encapsulated some how.
So Uncle bob says that in many cases, you can think of a clever solution to solve this. The example in the book is about getting some filepath to create a stream with for accessing the file system. So there is some code that essentially says A.B.GetFilePath() . He says we can refactor that code to something like A.CreateFileStream(). Where A will thus internally use B to get the filepath, and create a stream for you. It will simply return the stream object for you to work with. This is the sorts of clever solutions Uncle bob means. So we hide the internal structure of A by defining different kinds of functions. A general rule, that is also present in the particular example is that a question about something can often be converted into a command. So the GetFilePath was transformed into CreateFileStream in this example. And in many cases you will find that doing something like this is a perfectly viable option. This is a nice practice to keep into the back of your mind since it will greatly improve your code by creating less dependencies.
So next up is a very, very important topic that everyone should consider heavily, and that is Data Transfer Objects or Request and Response objects I sometimes call them. These kinds of objects are essential for decoupling your code from any outside interactions like the web, databases and the file system. So a Data Transfer Object, let’s call them DTO’s from now on are objects that define the data structures that are used for communication purposes, hence transfer in their name. So a DTO will be used for any serialization like storing them on the file system or serializing them into a request body of an HTTP request. DTO’s should not contain functions and can in many cases be defined as Structs in a C# context. So you use DTO’s are the outside edges of your game to interact with any external software. This is very important because you don’t want your business objects to be dependent on externals. I can’t emphasize this enough. DTO’s are essential for a decoupled system.
I have made the mistake before where we did not use DTO’s for our game and thus our business objects got many unnecessary properties because of it. We were building like low code solution to use in a webbrowser, that interacted with Unity3D to create games or simulations. So a content editor would create some behaviour in his webbrowser, which would then get downloaded by our Unity3D application and deserialized and executed. This is a very, very simple explanation, it was far more complex than I described here in three sentences but OK. For the sake of this example we can keep it simple. So in the backend this behaviour would have many meta-data like the author who created it, lot’s of timestamps, versions and other stuff. This data was totally not needed for actually executing it in Unity. So our business objects depended on the data structures that were being communicated. This also meant that when the backend developers would edit some properies in the objects, or maybe refactor them a little bit, our business objects needed to change as well. This is a very bad design! Trust me, I’ve felt the pain. Always make DTO objects for these scenario’s. And oh, by the way, you might have many different DTO objects depending on the target medium. So for example; you might have a player business object, but have like a PlayerWebDTO for communication with some backend or cloud service, but also have a PlayerSQLDTO for saving it efficiently in a local sql database. This is a viable and very flexible solution since you can now change your business object whenever you want, and you are not dependent on either the web nor the database. So, remember this principle well.
Alright, let’s take a look at the next topic, which is Active Records. And, Active Records are objects that are sort of special forms of DTO’s which have special methods for direct translations to databases or other kinds of data sources. So an Active Records typically has a function like Find or Save. If you use those you must put them in a particular namespace for example, and treat them as simple DTO’s. Don’t build your business logic around ActiveRecords because your business logic will then be dependent on the attached data source, and you don’t want that. When I think of Active Records I think about Rails, Ruby on Rails I mean. I haven’t touched ruby in a very, very long time but I remember that Rails devs sometimes use ActiveRecords as their models in like a Model View Controller architecture for building a website. But now, when they need to write unit tests, their models are dependent on the Rails framework and database. So each test now has additional setup and teardown logic to connect and disconnect database access. I guess there are ways around that now, but if the models were not directly coupled to ActiveRecords, you would not have this problem and the tests would run much faster.
So that’s a wrap for chapter 6 Objects and Data structures. We’ve discussed data abstraction and object data asymmetry which referred to Objects hiding their data while data structures expose their data. The Law of Demeter which says that you should only talk to your direct associations and not to strangers so don’t go calling A.B.C.MagicMethod(). Make that logic accessible from A and avoid making so called trainwrecks. We’ve also talked about what DTO’s are and how important they are for creating a decoupled system. And lastly, Active Records which are special DTO’s for build-in connections to their data source. I hope you enjoyed this chapter and learned something, but let’s have a look at chapter 7 in the next blog.
So that’s it for the sixt part of this blog. The next blog will cover the seventh chapter of the book called error handling. If you have any comments about this blog, please leave them in the comment section.
Thanks for reading, and cya next time!
#end
01010010 01110101 01100010 01100101 01101110.
Recent Comments