When I read code with many very short methods, I find that it often takes longer than it takes to understand how everything fits together. Some of the reasons are well illustrated in your sample code. Let's try to break it into tiny methods:
def cart if user_signed_in? process_current_user else setup_cart end end
private def process_current_user @user = current_user if @user.cart.present? assign_cart_when_user_present else assign_cart_when_user_not_present end end def assign_cart_when_user_present @cart = @user.cart end def assign_cart_when_user_not_present @cart = false end def setup_cart cart_id = session[:cart_id] if cart_id.present? assign_cart_when_id_present else assign_cart_when_id_present end end def assign_cart_when_id_present @cart = Cart.find(cart_id.to_i) end def assign_cart_when_id_not_present @cart = false end
Right after the battle, there are a couple of big problems:
- After reading the
cart method, I have no idea what it does. This is partly because values ββare assigned to instance variables rather than returning values ββto a method called cart . - I would like the method names
process_current_user and setup_cart tell the reader what they are doing. These names do not do this. They could probably be improved, but they were the best I could think of. The fact is that it is not always possible to create informational method names.
I would like to do all methods other than cart private. This poses another problem. Am I putting all the private methods together at the end - in this case, I may have to scroll through some unrelated methods to find them, or am I alternating between the public and private methods through the whole code? (Of course, this problem can be mitigated a little if you leave the code files small and use mixins, assuming that I can remember which code file does.)
Consider also what happened with the number of lines of code. With more lines of code, there is more room for error. (I intentionally made a general mistake to illustrate this point. Have you noticed this?) It would be easier to test individual methods, but now we need to verify that many individual methods work correctly.
Now compare this with your method, which has changed a bit:
def cart if user_signed_in? @user = current_user @cart = case @user.cart.present? when true then @user.cart else false end else cart_id = session[:cart_id] @cart = case cart_id.present? when true then Cart.find(cart_id.to_i) else false end end end
This tells the reader at a glance what happens:
@user set to current_user if the user is subscribed; and@cart assigned a value depending on whether the user has been signed and in each case whether the identifier is present.
I donβt think that testing a method of this size is more complicated than testing my previous code breakdown into smaller methods. In fact, it would be easier to ensure that the tests are comprehensive.
We can also return cart instead of assigning it to an instance variable and not assigning a @user value if we only need to define cart if the user signed:
def cart if user_signed_in? cart = current_user.cart case cart.present? when true then cart else false end else cart_id = session[:cart_id] case cart_id.present? when true then Cart.find(cart_id.to_i) else false end end end