In order to show how I broke down nested if...else structure into smaller, more manageable pieces, I have to show it with an example. The example might be technology specific but person with a little experience will be able to figure out what I did-and how the very same thing could be done with his/her programming language.
I was working on a web based application on PHP and CodeIgniter framework. Application was simple in the beginning. Admin could do certain things. Obviously the user should be allowed only after logging in through the admin login. If the login was successful the we set certain things in the session. All the admin pages should be accessible only after logging in to the system. I thought of some solution that was like "Aspect Oriented Programming" (or AOP) that i was used to do with Java(Spring AOP / AspjecctJ). The very same thing could be achieved in the CodeIgniter framework through what they call hooks.
We would be seeing the same hook that my team mate wrote and later I refactored for better maintainability (we will take it as an example in order to understand the technique).
We created a controller "login_check" and a method named "checkLogin". The checkLogin function from "login_check" class was invoked before any controller(the entry needed to be made in the hooks.php in application/config in php-CodeIgniter project). By doing this we saved ourselves from writing if/else on all the pages in the admin part.
(This method is executed before any controller - controller method execution)
The class looked something like shown in the snippet-1:
It is a normal CodeIgniter controller with a constructor. The line 11 (in code snippet-1) checks if the class(controller in this case) is 'authenticate' (the method check_login is executed before doing anything in the actual controller.) If the class is the authenticate (the one that renders the form) then we should not be redirecting him/her to the login (through appropriate route) (otherwise it would send us in a redirection loop that does not end). If the user has requested login page or anything that has anything to do with authenticate then we are not redirecting the user(eg if the button click on login page invokes a method from authenticate controller indirectly).
I was working on a web based application on PHP and CodeIgniter framework. Application was simple in the beginning. Admin could do certain things. Obviously the user should be allowed only after logging in through the admin login. If the login was successful the we set certain things in the session. All the admin pages should be accessible only after logging in to the system. I thought of some solution that was like "Aspect Oriented Programming" (or AOP) that i was used to do with Java(Spring AOP / AspjecctJ). The very same thing could be achieved in the CodeIgniter framework through what they call hooks.
We would be seeing the same hook that my team mate wrote and later I refactored for better maintainability (we will take it as an example in order to understand the technique).
We created a controller "login_check" and a method named "checkLogin". The checkLogin function from "login_check" class was invoked before any controller(the entry needed to be made in the hooks.php in application/config in php-CodeIgniter project). By doing this we saved ourselves from writing if/else on all the pages in the admin part.
(This method is executed before any controller - controller method execution)
The class looked something like shown in the snippet-1:
It is a normal CodeIgniter controller with a constructor. The line 11 (in code snippet-1) checks if the class(controller in this case) is 'authenticate' (the method check_login is executed before doing anything in the actual controller.) If the class is the authenticate (the one that renders the form) then we should not be redirecting him/her to the login (through appropriate route) (otherwise it would send us in a redirection loop that does not end). If the user has requested login page or anything that has anything to do with authenticate then we are not redirecting the user(eg if the button click on login page invokes a method from authenticate controller indirectly).
On the line 12 in the snippet-1 we get the CodeIgniter instance that is required in order to access the session.
And on the line 13 we check if the session is set and if not set then we redirect the visitor the the login page.
All the pages for admin were with URI "admin/".
Things we good enough so far. Then we started implementing the Student part. Now we were required to write the pages for Student as well. Student also could see pages only after he/she is authenticated. The student can not see pages for admin that is the student can not see the pages for which he/she is not authorized.
Now we had to write the code that would check if the user is logged in or not also we also had to make sure that the user is accessing the intended pages only(that is admin is accessing pages of admin and the student is accessing the pages of student only). If the student tried to see the pages of admin then he was to be redirected to his home page (that is URI "/student").
Also if the user had requested any URI starting with student/* as was not authenticated then we had redirect the user to user's login page of student. Same way if the user tries to access anything with URI matching :admin/*" then we had to redirect the user to admin's login page.
The code looked as shown in Snippet-2.
To understand what is happening here we need to first know the method _get_root_directory_requested() (that I have written in snippet-2). This method returns the top level directory name from the requested URI (if if user has requested '/admin/students/edit/1' then the method would return 'admin'). That method helped in keeping the "checkLogin" function smaller.
The line 24 in snippet-2 does the same thing discussed earlier.
Then on the line 26 we get the requested directory(eg admin,student).
On line 27 we check if the user is logged in as admin and he has requested for /. In this case we redirect him to admin/dashboard.
On line 42 we redirect the user to student/dashboard if the student has logged in and trying to access / URI.
On line 38 we redirect the user to appropriate login page depending on the top level directory he has requested. And on the 45 onward we redirect the student to student/dashboard and admin to admin/dashboard if he/she is trying to access un-authorized pages.
This also worked fine so far. After this we were asked to write the Super-Admin feature. In super admin we had to create a user who could manage the administrators. This introduced the third User-Type. We had to check the very same things discussed above(that is user should be given access to pages for which he/she is authenticated and authorized).
Because of the third user being added I though of adding some more if...else to make the things work. But if I had done it, it would make the function even bigger[1] and difficult to read and in-turn difficult to maintain. Also the function will not be visible on the screen once on normal displays.
So i applied some refactoring to it. After doing it the code looked as it is shown in scnippet-3.
The method '_get_root_directory_requested' stays as it was in the previous snippet and does the same thing.
checkLogin is the method which would be invoked and it would invoke other methods in order to perform its task.
We would now see what changes I did in order to make things easier to read and maintain.
The line 36 checks if the controller before which the method is invoked is not one of authentication controller. We have replaced a complex condition with a method call. We placed the complex condition inside a method call that is easier to read as well as easier to maintain[1]. The complex condition with two 'and' was encapsulated inside a method named '_is_authentication_controller'. This can also be seen as an abstraction we built. It prevents us from getting to the actual detail of names of the controllers. We can just use the method. We are not required to go into the details. In future if new controller for authentication is added (in case of new user being added) then we would be easily able to make the changes.
At line 39 we call a method that checks for login in the session. It returns true if user is logged in and false if the user is not logged in. On the line 40 we get the user type that is logged in. On the line 41 we call method "_authorize_for_url_with" with argument of user type that we have got from previous line.
The method "_authorize_for_url_with" redirects the user to appropriate path if the user is not authorized for the URI and the method does nothing if user is trying to access intended pages.
In the Else part at line 45 we call the method "_redirect_to_appropriate_login" which redirects the user to the login page. The user is redirected to the URI "student/" if the user has requested for "/" URI. This method uses the "Direct Access Tables[2]"(the array- or the data-structure that holds the details is constructed in the constructor at line 20)(although the data-structure would vary according to the language but the concept would remain the same. You could also put that in a method if you wish.). This method relies on the values in the associative array. The key is top directory requested or user-type(ed admin,student,etc) and the value is the page to which the user needs to be redirected in order to login.
Here in this case the length of method was reduced be introducing some methods. In this structure if a new user type is added then we just need to add its entry in the constructor and add its controller class name to method "_is_authentication_controller". No other changes needs to be done on other part. This makes the class more flexible and ready to adopt new user types.
Although I have made some assumption while writing which you can read in the comments written at the top of the class in snippet-3.
This is the first time i have tried writing something. I have started noticing some problems already; but however I am publishing this post. ;) I hope it is entertaining.
References:
[1] Clean Code: A Handbook of Agile Software Craftsmanship by Robert C. Martin (ISBN: 0132350882 )
[2] Code Complete: A Practical Handbook of Software Construction, Second Edition By Steve McConnell (ISBN: 0735619670 )
No comments:
Post a Comment