Naming is the key
There are only two hard things in Computer Science: cache invalidation and naming things. — Phil Karlton
We are working in a really complex environment. Usually, the code does not help. One of the most important things in programming is naming. This is one of those computer science topics that has not been cracked yet. Let me share some practices that I use to help me maintain my sanity just a little.
Naming conditionals and the problem of magic numbers
The amount of things that a human brain can hold at the same time is limited. It is called cognitive load. I have seen some people that excel at this, yet they fail to write understandable code. Why so? In my humble opinion, it is because they are too smart for their own good. Let’s take a look at the typical method in Ruby:
There are three problems I see in this code. First of all, I have to parse the conditional in my head and it is really nor relevant to the business logic that I want to achieve. The second problem is what is called “Magic numbers”. These numbers do not have names and we have no idea what they are representing. You wouldn’t call your kids “#1”, “#2”, would you? With the addition of enum fields in Rails, this problem can be mitigated, but it’s not the subject of this blog post. The third problem is the fact that this expression does not tell me why this conditional is there in the first place. In other words, this sucks! Let’s try to apply some refactoring to this code and name things better:
The first thing I extracted here was user_eligible_for_notification? method. It allows us to immediately understand what the intent of this conditional is. It reads as:
Send results for the day if the user is eligible for notification.
Easy as that. I went a little further and extracted all the subsets of the conditionals in their own methods. In the user_is_in_eligable_role? we also give our “magic” numbers a name. Now we know that integer 1 stands for regular user and integer 2 stands for vip user. I have to note that probably this naming should not be the responsibility of this class. The rule of thumb here is — If you these numbers often, there is a good chance that you have to move these to a User class or it’s own small object that can take care of these complex role interactions. I must say that I am always hesitant to add anything that is not related to data consistency and persistence to an ActiveRecord model.
In the next two methods, users_notifications_are_enabled? and user_has_participated_in_any_games?, we are doing queries to find out if this user has notifications enabled and if this user has participated in any games. In fact, I am hesitant to even write about it since the methods speak for themselves.
Naming of classes in Rails app
If you are doing most of the development in Rails, you might have a question in your mind — Where to put things and how to name them? I have not found a perfect answer here as of yet, but there are some practices that worked out pretty fine in the projects that I worked on.
If we assume that the controller is the place to take care of the request and send a response, then we need a thing that can do all the heavy lifting of the business logic. I don’t like the name Service, but I use it, I must admit. For example, if there is a UsersController, I will have a corresponding name-spaced service Users::RegisterService in app/services/users directory. This keeps me sane in a rails app.
I put the classes that are not directly tied to the request/response cycle of the app into the lib folder. This is the place where I have the most freedom and I feel like I am not bound to “The Rails way” For example, if there is a part of the system that is taking care of the synchronisation with some external API and is gathering patient data, then probably I will call the module that will wrap this functionality as PatientDataSync. The classes that are responsible for working with this data will be located under /lib/patient_data_sync and will be called something like PatientDataSync::ThirdPartyProviderGateway, PatientDataSync::Mapper, PatientDataSync::Repo. These classes will be responsible for fetching data, mapping it to our system and storing it.
I must say that here is no golden path here and sometimes I lean toward not calling the classes by pattern names, but sometimes it is a useful tool if you have set of conventions that your team follows. Just don’t abuse the pattern names and try to name the classes by their intent as much as possible.
Don’t write code, solve problems!
Originally published at codingwithaxe.com on June 17, 2015.