Becoming a better developer by using the SOLID design principles by Katerina Trajchevska

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
[Music] [Applause] [Music] hi everyone I will be talking about solid design principles and how to write better code or how to write code that's easier to maintain an extent and understand okay so this is me I am a software engineer and co-founder of adara it's a developer a global developers network where we connect tech companies to world-class talent and working in organization like that I've had a chance to work with a variety of companies from early-stage startups to Fortune 500 now it seems like two different edges of the software world but they do have something in common and that's the thing that actually brought me here today so how is it like to work with enterprises that usually translates to how is it like to work with legacy code as some of you might know what happens there is that you have to reread the code multiple times you usually have to jump from one file to another just to follow the structure of the code and understand what's going on there and at some times you would find yourself looking in the wrong file which has the same method name because that's the thing that that happens often with legacy code so code is very coupled together you should always jump from one thing to another and once you finally understand what a method does you get to a point where you need to do your fix now this is again something that's not very straightforward because you need the code is not designed for extension it's very hard to extend it because it's again very coupled together essentially what happens is you spent much more time reading code then you spent writing one or much more time understanding the existing solution instead of spending then at that time on writing your own one kinda looks like this except that instead of all of this heads popping up behind you in the virtual world you have endless slug chats and zoom meetings and all of that trying to understand how how the code works so how is it like on the other end working with a start-up or developing a startup solution what happens is you have a clean slate right you can do whatever you want you're usually in charge I had the opportunity to lead a project when I was at the beginning of my career but to be honest I was the only developer that's why I lied it but in any case I was in charge of the whole development process and I could do whatever I wanted with it so you can choose your architecture you can organize the code whatever you want but you really should think about it before you do now in early-stage startups the organization's the organization is not very good it's not very things are not very organized so we ended up constantly adding new features doing daily releases things change it changed a lot from one day to the other and I really didn't have much time to think about the code structure or at least I I wanted to think I didn't have enough time to do it and I always blamed it all on the organization or the client or whatever there was and so after a while the code enter maintain the code the system entered maintenance mode it was an e-commerce product and we had a variety of of products with a complex background processes so we had custom name in generating we had custom pricing everything happened there were a lot of methods and functions executing in the background so two years after the client comes and asked me to do a simple change it was adding a new product to the database now you would say it's an e-commerce website right that's the only purpose it has to remove products to add new products to sell them to the customers right so it should be fairly easy to do and at this point I wish I had as far as image processing package that we heard about yesterday but I didn't so I had to had to go back and dig into everything I've programmed on my own and find a pieces of code that actually were going to enable me to do this change and so I spent today spent today's reading the code and again jumping from one method to the other from one declaration to another from one file to another just to understand what I did two years ago and after that it again took me two more days to develop what I needed to develop because my own code wasn't developed for extension and you could probably understand how frustrating it is to go back to your own code after two years of work and don't understand what you did and so that's what brought me here today combining these two different experiences from two different environments who are essentially the same and who point out one same problem which is not thinking about the code structure when we develop apps so we will talk about solid today and what it means how to use it how not to use it what each of the principle what the purpose of each of the other principles is and for those of you I believe it will be very beneficial for those of you who are not very familiar with what so it does or kind of understand the basics and I really hope that ones were solid and solid would also be able to pick up at least some new perspectives or some learn something new so just like a short overview overview what's all it is what is its purpose so the purpose of solid is to make your code easier to maintain that's one the second one is to make your code easier to extend without actually breaking something else that you've developed earlier and the third one is to make your code easier to understand which at the end will enable you to do the first things better oh I almost forgot one very important part it was introduced by Robert Martin or Uncle Bob how has more familiar to the developer world in his paper are on software design principles and patterns but it wasn't actually named until a while later when Michael fetters rearranged the principles so that they can form the Soviet acronym and right now we are we all reference them as such so we're going to start with the single responsibility principle or a class should have one and only one reason to change I'm going to switch here now because it would be easier to show the code now a good example on the single responsibility principle is one date Uncle Bob usually likes to mention and it's comparing the single responsibility principle to a clean and organized room now what happens here and I had an experience when I was little and I'm sure some of you had it as well my room also always a mess and my clothes were always thrown around to the floor on the chairs and somehow magically I always managed to find what I was looking for or at least I thought so but anyway my mom would then come and she would be very annoyed and she would say okay please clean out your room everything has its own place and everything should be placed there so this is the main sentence that Uncle Bob emphasizes when he talks about the single responsibility principle and concept of clean room that everything should have its own place and everything should be placed in its own place so how do we translate that to code that usually means that a class should always be responsible for one thing or it should only do one thing and usually have multiple classes which are smaller and much more precise and they have very specific names that define what's saved inside there what the purpose of that class is so we have this small classes with very specific names and very specific responsibilities as opposed to large classes with generic names and all of the responsibilities for the representations they have that would an example of that would be like having an employee payroll class and an employee time lock an employee leave for example and those are the specific ones and you always know where to find them and where to do the changes and on the other hand you have an employee class where you have everything thrown inside and you never can arrange through all of the methods it has so let's look into a code example for this this is a simple controller function it's from a resource controller in laravel and it's a simple store method and you can notice that it does basic things so it has a code it has an input validation at the beginning which is defining the the required fields defining the required format of those fields before saving them then we have saving the the user in the database and at the end we are returning a response to our client so basically what a controller class or a controller method should do it's only controlling the flow of the app so it gets an input from the client it returns it operates something on it and it returns the output but it should only communicate with other sections from your code for that for those operations and in between the main responsibilities of the controller should be getting it an input than returning the output so over here we have we know a lot so our controller method knows a lot about what's happening in the background so we have this code validation the controller knows what are the required fields how we are validating them then it knows how we are saving things in the database and at the end of course it knows what type of response to return whether it's JSON or a simple view so the the step that we are going to take here is leaving one responsibility here which is again the flow and moving everything else outside now laravel has a very nice way of organizing validation it and it's with custom form requests so you have an artisan comment for writing this it's very simple and what we do here is we define the validation rules in our rules method and then if we have any authorization checks we do that in the authorized metal but it's intentional and intentionally left it blank for simplicity so you will notice here that we have the whole validation moved into a separate method in affirm validator class and now the second part that we are going to do is separate the database section so the controller doesn't need to know about our database structure or how to handle it and for that purpose we are creating a user repository and the moment this repository is only going to have a create method which will create our user record and we moved everything from our controller to our create function over here now if we go even further we can even remove this bit crypt function because our repository doesn't really need to know what we use for encrypting the password so I would probably create a mutator or something on the elephant side so that we can abstract that part completely but again it's less for simplicity like this and now how does our controller look like we have again the store method we use store user requests instead of the regular requests that we had earlier and we use user repository instead of the user model directly so what this does it most it moves our method from 10 lines of code to 2 lines of code and we we do the validation even before we enter this this method so our store function doesn't doesn't know anything about the input validation that happens even before we we come here and for all that we know that's handled once we arrive to the to the beginning of the store function after that we do the creation again we don't need to worry about how things are created in the background they just work we just know they work and we just know that we have some interface to to death at the end again we we have this return section where we return the proper value to the plant now what's worth to notice here is that our controller now cares so this is only a method in the controller but this will of course pass to the to the whole class and the single responsibility principle works with both with all methods functions and classes so it's all entities that we can imagine it's not only for classes and the main purpose is to have only one thing in there and now our controller will know whether something changes on the client side so it can return a different output but other than that everything else is handled for it so it doesn't need to care about it okay the next one is the open closed principle or an entity should be open for extension but closed for modification now this one is often very confusing when you read it for the first time it's because it's named it's a bit counterintuitive but essentially it's very simple once you understand it and it's the one that would save you most time on most development time in the future if you properly understand it to use it so what does it mean to be open for extension but closed for modification it means that you can extend the functionality of the system by adding new code instead of changing the existing one and a good example of this is an open source library or a composer package that you use so what you do is you install the package that goes straight to your vendors folder you have no idea what happens in there and what you do is you get your interface to that that package that you have somewhere in your source code and then you write your code around it so basically you call methods from there you use something that you know would be beneficial for your program but you don't go ahead and change things inside it you just wrap things around it and use it as you need it to need to use it and what happens there is that with open source libraries people use them constantly they never change them so once they get to a point where a lot of people use where a lot of people are using them they actually get very well tested that they can hardly ever break so there they become as error resistant as possible and they're very stable at some point and the goal with the open closed principle is to get your system to a point where it's very stable just because it's tested often and it's changed almost never okay so let's look into an example about this so this is a simple example again from an e-commerce system it's a pain method that handles the payment processing at the moment we only have two payment types so in a credit card or PayPal and this method is handling handling the payment processing depending on the input type okay let's see the payment class now again it has this both of these methods so we can use them for processing later it doesn't have anything in the source code but the general picture is much more important for this example so what happens when we want to extend our payment options wire transfers are again very popular optional very common and some people in some countries like mine we don't have people we can't pay by paypal but we often require two we don't want to use wire transfers when we pay online so this is a valid option to add and what happens here how we added this is adding another else in our team method which we'll call the appropriate function on the on the payment class and then in the payment class we add the appropriate method for processing the the wire transfer now we have to a class with three different methods they are processing the payment in three different ways and our controller has three different if analysis that call the appropriate functions now what will happen next you probably guess it will need a coupon payment so we'll add another function in the payment class another riff in the in the controller then we'll need cash on delivery payment then we'll do the same and we can do this until we ran out of payment options and this is something that violates the open-closed principle because we get to a point where we change our classes and our methods so often that they can never become stable and even though they seem unrelated they are very coupled together and a change in one function could cause a change in India so how many people here had the experience to do some bug fix everything tested everything works properly deploying it and then it breaks somewhere else like what kai was demonstrating here earlier fixing one rep report and then breaking another come on it's it's okay to confess yeah so this is this is the point of of the open source open post principal to get us to a point where we don't do such such mistakes because our design doesn't offer it doesn't allow us to do such mistakes so how are we going to solve this we're using the factory pattern we have so for those who are not familiar with it it's a behavioral pattern that allows us to create different class instances on on runtime and so we don't need to understand what method we are going to call when writing the code it will happen automatically depending on the on the client selection so we have a payable interface this one will define all the methods that we will need to use in our to implement in our classes and then we have three different classes which are the credit card payments paypal payment and wire payment who all implement the same pay method and now we go with a simple payment factory that has only the purpose to initialize the appropriate payment depending on the selection of the plant so depending on what's chosen it returns the appropriate object type now how does our controller look like it's a simple pay method which doesn't have any of those check-ins that we had earlier it's only initializes the payment factor so it doesn't depend on any concrete class at the moment it depends on something that will tell it what class to use depending on the client selection and then depending on what's selected we initialize the appropriate class and we just call the pay method on it because we know that we have it and what happens now if you want to add the coupon payment or the cash payment or whatever else we want to add we just create a separate class which I'll the factory how to initialize that new class depending on the client selection and everything just works out of the box so we've lowered our time of extending the code significantly just by enabling to do that by adding code instead of change in code it brings us to the third one this is the risk of substitution principle and it's introduced by Barbara Liskov for computer scientists who created this math light formula which is a bit complex to dig into right now but in simple software words it only states that we should be able to change every instant every concrete class instance in our code with anything that implements the same interface that works for derived classes who should substitute there their parent classes and the main reason the main purpose here is that our code should be able to get the expected response no matter what class we send to it what instance was sent to it so our the client should not be aware of any change in our in our source code a good example a good example with this is changing database engine so for example if you're developing an app when you decide to use file system instead of defining the database engine from the beginning and you have a simple repository which handles all faul reading and writing so you transform everything into array and then pull it many played with it and store it or whatever you need to do with that data and now after you finish this app if you want to move to a database engine probably use elephants models then those will return collections instead of arrays so you will create a simple interface you will implement all of the same methods when you use it in your app you've done everything properly but you haven't made sure that your code behaves exactly the same way so you get collections instead of arrays which again fails the program and this is a valuation of Liskov substitution principle that we will try to comply with now this is one very popular name I'm sure most of you have seen it so far it's part of a larger soil series but what it states is that if we have rubber duck or battery duck or rubber duck which looks the same as a regular one quacks the same as a regular one but it can't really does everything it's not alive that then it's definitely the wrong abstraction so I try to translate this into code I have a duck class which is an I mean imagine that I have because I haven't shown it in here but it's a duck abstract class which has this three methods quack fly and swim and now I want to add a rubber duck in my imaginary program I create a class rubber duck accent the existing abstract class and I have to extend these three methods because my rubber duck can't really quick fly or swim the same way that normal duck would so this is your first clue that you're violating the risk of substitution principle because if you need to override all of the methods that your parent class has you're probably doing something wrong because you're not reusing any of the code that your parent has okay next clue that we were doing something wrong is flying method which only throws an exception so our duck rubber duck can quack or swim probably with the help of a person but it can't really fly if I throw it it will fall down so what we do in the fly method we just throw a new exception saying that a rubber duck can fly and this is good but again it fell is the risk of substitution principle because if the client selected a rubber duck and then selected a method fly which was enabled to them and then saw the program rating something is probably off so how we can fix this is coding by contract instead of extending an abstract class so we can have three different interfaces quack above liable and swimmable interface who all implement who all have all these three functions quite clients win and then we will implement on with the interfaces that we need for the rubber duck and in our case that's the quack couple in the swimmable interface so now we get to this we have a rubber duck that implements the quack a ball and swimmable interface it implements quite a and swim in the appropriate way just as it did earlier but it doesn't have the fly method because it didn't really use the fly method so this is cool our client will not we will now not see the fly method is available because our rubber duck can implement it so we're okay we have quack and swim only now let's look into any of these functions they're basically the same so for adaptive swim it needs a person to throw it in a in a tub if there is no person that can do this section then our duck cannot swim on its own so what happens here we have if there is a person to help our duck swim then okay return the expected value the duck is swimming but if there is not a person who can help us do that then our program will fail again and this is the third clue that you are not complying to the risk of substitution principle because you have a condition your your program is behaving the appropriate way inside cases but in some cases it's not so how can you really fix this there's no way to force a fix with a code you've noticed that I've tried doing that with this type hint returns we have shrinks over here that are the required values that the function should should return but they don't really work well with an exception so no matter what your type int is an exception will always pass by it it would work for that database example that I mentioned earlier so in some cases it can help you but in the cases that it can't you can just use it to to understand what your code needs needs to do before writing it so that you can code more with intent and use this type hints to to help you know what you need to do before you complete a method so essentially you need to do everything that you can to make sure that your methods really behave the way they should as they're defined by their interface the fourth one its interface segregation principle or which states that no client should be forced to depend on anything it doesn't use now this is a nice example of the internet interface segregation principles principle here we have a headset with many different flags plugs and we have a phone that we want to connect to them so that we can listen to some music or do a call or whatever we need to do but now we have multiple interfaces attached to the same thing that we want to use and we are not really sure how to do it so with the interface segregation principles principle what you do is you segregate the interfaces you tailor them to the individual needs of the client now with iPhones this is way on the opposite direction because you get to a point where you can reuse code so basically you need to seek your ground somewhere in the middle not to segregate a lot and not to come with an example like this so what the interface segregation principle states is that the Klan should never be forced to depend on methods it doesn't use or it should not depend on anything more than a method it calls and this is a much bigger problem in languages like Java for example which are compiled languages because changing one method in a class will affect all of the classes that have methods which depend anyhow on that class so even though even if I depend on only one method in a large class whatever else changes there it will cause my my class to recompile so basically what we need to do is to replace large interfaces with smaller ones or replace large objects that we depend on with small interfaces this is a code example it's a subscriber model which extends the elephant model but again it has some other functions so has a subscribe and unsubscribe which updates and filter in the database and then it has get notified a mail which is important for us so that we can get the appropriate email sent our subscriber notification now what happens when you want to implement our notifications we have a class named notifications with only one function method sent in this metal sent is depending on the subscriber which means it depends on eloquent it depends on all functions it has to communicate with the database and essentially knows what database engine we use and all of that even though we don't need any of it and it gets some message object so that you can it can send a message another thing that we need to put attention on here is our dependency on the subscriber method if for any reason the model class changed that would cause our notifications class to change even though it doesn't really depend on it so how we are going to fix this we need to increase an interface and now need separate interfaces but I'm just going to elaborate on this one it's a notifiable interface which only has get notify ml method this interface essentially says that our subscriber needs to implement the get notify ml method so that we can get their email and then send them a notification and how it transforms our notifications class our send method does not depend on the whole subscriber object anymore it depends only on the notifiable interface and again calls the same method in the same way so basically we just subtracted this part we depend on an interface and we depend on only one function so now our code here would only change if something inside that function changed this is the last one it's dependency inversion principle which says that high-level modules should not depend on low-level modules that both should depend on abstractions again what does this mean it's more it's better explained with an everyday example so we have a socket here and a lab and if we want to get electricity what we do is we usually go find a socket and plug it in we never dig a hole in the wall and try to find a wiring and then where our lamp directly so how this transfers to code is in our code the interface is the socket which gives us the opportunity to do what we want to achieve what we want to achieve and that interface works the way it should work no matter how everything in the background is handled so no matter we are not interested in how the wiring is handled in the background we just know that we have an interface that can provide us to everything we need from that wiring in the background we need light we need electricity so we only use that interface this is the abstraction part and the concretion part or the low-level modules are the wiring behind the wall so everything that's happening on the low level of our code everything that's like database engine data manipulation and all of those sections which happen in the background they are the low-level part they are the wiring and we don't really care about them or should depend on them and the purpose of this is to be able to change the implementation of the low-level code so for example to change the the database engine like Gaby mentioned that it's very hard to change database databases in in your code but if you code them the proper way then it can be made easier and with this dependency version when you depend on abstractions only then you don't really care about the database you use you just care about how to get that you can get the data from the database and that you have an interface that provides you with it so you don't care about anything and you can change everything in the background so that your code will continue to work properly even after now this is back to our first example with the repository but now we have an index method it's essentially the same what we do is we get all users that were created a day before so in the last day in real world you would probably need some input from from the client here setting a date range or something but this is simpler so let's stick with this so we have aware we have concrete fields from the from the database table and what happens here is our index method knows what our database structure is so if we want to change something in the database structure then we would need to go here in our controller we will need to go probably in many different controllers as well to change the code there too just because our database changed it also knows about how to get the data from the database or what to communicate with directly so that it can pull it for usage you will notice that this again also violates the single responsibility principle but most important for this part is that it violates the dependency inversion so how should we fix this again we have the same repository so we switch this get section to the to the user repository we can forget after date method which returns all of the users that are registered after a certain date and basically does everything that our controller did only it has a separate space for it it complies to the single responsibility principle now what happens if you remember in the first example we depended on the user repository instead of depending on the user model directly and that helped us with a single responsibility but it wouldn't help if we would be changing the the user repository we have right now with a new file a new class that would work with MongoDB for example so that's why we need to abstract this in a in a another level and use an interface depend on an interface which will only tell us what functions to use what methods we need to implement so that we can achieve what we want to achieve and that's interface right now only has two methods it has get after date and create method which help us do what we need and in our controller we just switch the dependency from our user repository to use a repository interface so basically the dependency inversion is an inverted dependency injection where you only depend on the on the abstraction everything else is again the same so what this helps helps us do is when we need to change the database engine we create a new class we implement all of the methods in there that are defined by this user repository interface and then we just change the binding in the in layer well to bind this user repository interface to our new repository class and after doing that everything else continues to work properly given that we've complied to the risk of substitution principle so you'll notice that these principles are all related to one another and that you usually can't implement one without focusing on the other one but what's important is to make sure of the context that you want to use them in and make sure that they really help you achieve what you want to achieve so that brings me to words of caution about solid and this has happened a lot people try to achieve solid because they that makes them look good with their colleagues or other developers and there are always some comments that you're not complying to solve it or your code is not clean or you're not doing test-driven developer development and those comments are simply wrong just because they don't care about the context so what you need to do here is to make sure that solid will help you achieve your bigger goal and the bigger the bigger goal is easier maintainability for your software is your extensibility for it and make it easy to understand what you've done what you've done and other people to understand what you did and this is one example of solid gone bad it's a developer it's a question on Stack Exchange that a developer asked which asks how to refactor away from the single responsibility principle so the big problem here is that your team leads try to comply to solid that much that they over fragmented their code so they had so many different classes with so many specific to specific responsibilities that they ended up depending on too many different classes which made our code hard to maintain so if you get to a point where trying to use solid actually makes it harder for you to maintain your code or to extend it that means that you're probably doing something wrong and that you need to go a bit slower with it so try to think about the context try to think about what you're achieving and whether it really helps you achieve a code that's easier to maintain this is the second example which will probably not be very how do I say lovable but some of you you over here it's the index.php movement which for our engineers is really causing a sick in the stomach sometimes but it's something that really has a bigger point and the point is that if your app is so simple that it it doesn't need any architecture or it doesn't need any formal structure then you probably don't need to exaggerate with any of it now I'm not suggesting that like Peter did here you get a single file and you dump everything inside it but what I suggest is that you can take advantage of the elegant syntax for example I look and structure that laravel has four simple apps you can make use of the eloquent models directly instead of creating unnecessary abstractions with repositories in case you you have a simple app that that doesn't need it again the context the common sense those are the things that are important to you when you think about whether you need to use salat or not so to recap the purpose of solid is to help you make a code that will make your lives easier to make it easy to extend maintain and understand and it requires you to spend much more time writing code so that you can spend less time reading it later so it comes with trade-offs if your code is already easy to read or it's very simple then using so it would probably make it more complicated and will make it hard for you to maintain it later so you should always use it with caution and you should be careful about the constant the context where you use it and use it together with common sense and again there are principles they are not true so you're not always required to use them the final and most important one than at least for me is that solid is your tool is not your goal so it's what it should help you achieve better code better structure better easier development life for you not something that should you should strive to to achieve as such so use solid to builds better code don't try to achieve solid as it is that would be all thanks a lot if you have any questions [Applause] [Music] you [Music]
Info
Channel: Laracon EU
Views: 617,189
Rating: undefined out of 5
Keywords: laravel, laracon, php, amsterdam
Id: rtmFCcjEgEw
Channel Id: undefined
Length: 41min 40sec (2500 seconds)
Published: Mon Jan 28 2019
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.