#begin
So here we are again in another entry of the Clean Code series.
We have talked about why we need clean code, why it’s such an important concept to go by. We’ve talked about meaningful names. How you should name classes, variables and functions. We’ve also talked about how to write functions and to what standards they should abide. We also discussed comments in code, and why most of them can and should be deleted from the code. Uncle Bob says that comments in code is at best a necessary evil and is simply a failure to express ourselves in code.
Next up was chapter 5, formatting. This chapter was all about how to properly format your code so it reads well like using the concepts of horizontal and vertical openness, density and conceptual affinity, putting things together that share the same concepts.
Then chapter 6 was about objects and data structures, where we talked about the distinct differences between them; how objects hide implementation details and their data through functions and data structures do the exact opposite and expose their internals without providing functions.
Then we also discussed error handling and how error handling can be really difficult in Unity3D since we don’t want to start throwing exceptions everywhere because it will impact performance greatly.
Next was chapter 8, boundaries, and it was about how to integrate with 3th-party code. How you explore it by covering it by tests and how you create facade classes around it to minimize dependencies.
And then, the final chapter, we discussed chapter 9 which was all about unit testing. As I said before finding a unity project that is properly unit tested is very very rare, and that’s for a reason of course. We’ve all discussed this in the previous episodes so if you have not listened to it. Go back and read to them, if you have already read to them, fasten your seat belt and enjoy the ride.
So, well during the pas nine chapters the book has focused on writing lines and blocks of code. How to write them well, and in a clean matter. We’ve not really gone further than the scope of a single class but in the next couple of chapters the book is going to describe more about higher levels of code organization.
And I do want to make a little side note here and that is; Uncle bob wrote an entire book about this and it’s called Clean Architecture. This book dives into far more detail than the x number of chapters in this book. The clean code book only scratches the surface of what Uncle Bob knows and has to say about software design. But, OK nonetheless. The clean code book was written far before the clean architecture book. But if you are familiar with Clean Architecture the things I’m going to talk about in the upcoming blogs are probably familiar too.
But that’s fine, let’s refresh some of that knowledge then, and if this is new to you study it well because there’s some great lessons to be learned from this.
Classes
So next up is chapter 10 classes. In an Object oriented language like C#, or Java as in the clean code book. One of the main concepts for organizing code is the concept of a class. A class is a definition of some kind of object that exposes an external interface to interact with it. We already talked about how Classes, or Objects in this matter must always hide their implementation details yet provide a proper interface for interacting with them.
And, well this chapter is all about how to write clean classes that are up to the level of quality Uncle Bob wants to have them. I can’t remember there being any controversial advise in this chapter since all Uncle Bob is going to talk about are generally accepted standards and best practices. In the previous chapters we might have discussed some controversial topics like how function should be as most 4 lines of code for example.
So let’s start of with the topic of class organization. Uncle bob refers to the Java standard convention here but I think we can safely say that in imperative, or the C family of languages like, C, C++, C#, Java and even JS, python or Ruby, it is a standard to start a class with a list of variables, or properties at the top. Starting with some private static or constant variables, and then public variables and or properties. We have discussed how readability suffers when variables are declared in between functions in a class in a previous episode. Always declare your instance variables at the top of your class. I don’t see this rule, or standard broken often but when it is, you notice it immediately. But luckily, in our IDE’s nowadays, when you extract some variable from your code, by some refactoring tooling, the variables are put at the top of the file automatically. At least, that’s what Rider does. But I think, everyone knows about how you should put variables at the top of your class files and not somewhere in between.
Let’s continue on the next topic which is; encapsulation. Yes, this is very important if you want to keep dependencies of you code in check. Classes should hide their implementation details through publicly exposed function and hide everything else by means of encapsulation. Don’t make everything public because it is “convenient”. Trust me, at some point all those public functions and variables are going to bite you in the ass and you are going to suffer.
Note: this also means making things serialized in your inspector private! I’ll remind you that you can expose private fields to the unity inspector by putting a [SerializedField] attribute to the field. Never should exposed fields to Unity3D have to be public. And if you find yourself depending on public fields in other GameObjects, you should do your absolute best to untangle that code because if you don’t the pastafarian inside you will feed the spaghetti monster it will grow into a truly magnificent kraken of the underworld reaching into code everywhere with it’s sauce covered tentacles. So, I can’t stress this enough, never make public fields just for convenience and always to try think of some other mechanism to reach into that data.
Uncle Bob has only one rule for breaking encapsulation, yet these cases are still very rare, and that is when you need to expose something for the sake of testing. He says that sometimes you will need to expose something to be able to reach into it for testing. So he advises to make things protected, or on exposed on the package level. In a C# sense that would mean flagging it as protected or internal. Personally, what I’ve also done some times before is to just reach into the code by means of reflection. Reflection is very cool, it’s often used for code generation and such. With Reflection you can create objects dynamically and you can get information about objects. So for this test example I might have some Player class that has a private reference to his inventory for example. In my test I might use reflection to get that private reference through reflection by a GetField or GetProperty call. Passing the player object as a reference to the reflection method. If you don’t know about reflection, don’t worry it’s not really a general use case you will encounter often. But, having some knowledge about this topic can come in handy in some really specific edge cases.
His next advise is that classes should be small. We have all heard that advise before, I bet ya. In the book Uncle bob shows an example of a class that has 70 public facing functions. It’s a true monstrosity. Even the spaghetti monster might be scared of this one. The funny part is that the class is called “SuperDashboard”. I mean, when I read that name, I do expect it to have 70 public functions haha. It simply has too many responsibilities. But isn’t that the nature of a dashboard? And , not to forget, its a “Super” Dashboard. But, to be honest, super in this case refers to the dashboard being a base class for other classes to inherit from. It’s not super, in the sense of superman being super. But still, 70 public facing function is far too many.
And this is a nice Segway into the next topic in the book and that is the Single Responsibility Principle (SRP). The SRP is a bit of a misnomer since it is defined as: a class or module should have one, and only one reason to change. So a class should only have responsibilities towards one stakeholder, and thus have only one reason to change. Uncle bob says that the SRP is one of the most important principles in OO design yet it is also one of the most abused class design principles. We regularly encounter classes that do too many things. But why? Uncle bob has his suspicions that getting things working and delivering something, good or bad, in time is seen as more important in many cases. So code quality will suffer because of pressure and deadlines. So refactoring for cleanliness is often seen as a second class citizen in software development.
And I think I agree with him. I mean, how often have you been pressured into getting shit done before a specific date, cut major corners, built in dirty hacks, just to reach an impossible deadline. I know I have. And then, often, you are not in the position to return to that code and refactor all the dirty crap to a clean state and thus you have fed the spaghetti monster once again. Uncle bob also has a rule for this, never ask permission to do refactoring, never put it onto the schedule or make like tickets for it. Refactoring is part of your job, it’s you, being a professional and delivering to the best of your ability. So you might cut corners to reach some impossible deadline put onto you by management. But once delivered, you dive back in and undo the damage you have done. Trust me, it’s worth it in the end. I’ve had many cases where I needed to make some deadline all of the sudden because like the marketing team promised something impossible while talking to a client. So, you fix it in a hurry, deliver, and then dive in and maybe even remove all the code and start over. Don’t be afraid to do this. It’s part of your job, you are a professional and because of that, you will want to deliver good, quality code and systems. Because, if you leave those dirty hacks in the code, you will suffer in the future! But always try to stick to your principles and deliver good quality code. Don’t be afraid to say no to an impossible deadline. Tell him or her it’s not possible unless you cut feature x, y or z. Find some middle ground, where everyone is happy.
But let’s turn back to the SRP. Uncle bob says that we should built systems that contain many small classes. Not just a few big ones. Each small class encapsulates a single responsibility, as single reason to change and it collaborates with others to form a system. So don’t throw all your business logic in your gamemanager class. Generally, not always but in general; classes with adjectives like processor, manager, controller or service added to them don’t follow the SRP because they hold to many responsibilities. So keep an eye out for that.
Next up is the topic of cohesion. And I bet you have probably head the concept of high cohesion but low coupling in a Object Oriented context. It’s one of these terms that are always thrown around by consultants or teachers of OO practices. But what is it and why is it important for a clean system?
Well, cohesion is the concept that a class or module is well designed around a single purpose. This relates to SRP again. So cohesion says that a class should be designed for a single goal or purpose. Low coupling on the other hand says that a class must be independent of other classes in a system. A very simple example I can give you are for example all the collection types in C#. So for example the List class is maximally cohesive, it serves one purpose and only has function that match that purpose. But it is also lowly coupled since it does not have dependencies upon many other classes. Imagine that the developers of C# would have combined the List<T> and Dictionary<T1,T2> classes into one. I bet it would have been very difficult to work with because they are simply two totally separate concepts. I mean, a list only have one generic parameter, but a dictionary has two and both have vastly different properties and usecases. Maybe this is a bad example so let’s say that the C# devs combined the stack and the queue logic into one class. That would be very confusing wouldn’t it. So you could pop it and dequeue it.
And this might sound pretty stupid; but there’s probably classes in your codebase that have this symptom. Without you knowing it even. So how do we keep cohesion? Well we write many small classes. Uncle bob says that when you notice that, when you have a class that has a couple of functions and only a couple of functions touch specific member variables. Extract those functions, including these variables out into a new class to increase the cohesion of things. And in the book, Uncle Bob shows an example of how this process works, so if you want to see it please go check it out there.
The next topic is about how you should always design for change. He says that change is continual, or as many people have called it; the only constant is change. He says that every change subjects a risk to your system and that the system no longer works afterwards. So making sure we can cope with changes is of grave importance. We organize systems in a clean way to support change and reduce risk.
He then shows a nice example again of how he would design for change. He has an example of a large class that exposes functions to run SQL queries. So it’s a massive class that has functions like Create(), Insert(), and SelectAll(). He says that when you need to add another function, you must touch that class and thus there is a risk that you break existing code. So he says that you could refactor this class to multiple small classes by using a command pattern. So he would create an abstract base class or interface and than make new classes for each type of query there is. So there is actual classes called CreateSql, InsertSql and DeleteAllSql to be in line wih the previous example.
And I really like this approach personally. I do this kind of design very often. That’s A) because I’m a big fan of using the command pattern to separate things and keep things simple and B) because command patterns easily match with a use-case driven approach. And you follow the SRP by doing this. But there’s also again nuances in a unity3D context. If the particular use-case is in code that highly impacts the framerate then I won’t separate it out in use-cases because you may allocate too much memory and generate too much garbage. And as I said before, you will notice that frame-drop when it GC.Collects. But you can experiment with that a bit and see what fits best. Just make sure that you isolate changes in your design.
But I think we have covered enough about this chapter about classes. We covered class organization, encapsulation and why to keep classes small. We discussed cohesion to maintain small classes that fit a single purpose for low coupling and for isolating change.
I hope you enjoyed blog and learned something valuable.
Thanks for reading, and cya next time!
#end
01010010 01110101 01100010 01100101 01101110.
Recent Comments