I am largely dissatisfied with code that I usually write. Usually I write a working version, then a cleaner working version and then finally a cleaner Object Oriented working version. Obviously this takes some time but more on that later. Lately, I found Sandi Metz's rules to be quite practical and reasonable. These are -
- Classes can be no longer than one hundred lines of code (I would even say sixty).
- Methods can be no longer than five lines of code.
- Pass no more than four parameters into a method. Hash options are parameters.
- Controllers can instantiate only one object. Therefore, views can only know about one instance variable.
These are great principle and I think if you combine them with SOLID principles and some functional ones (like immutability and no-side effects) you can write great code.
So I try to apply these principles in the code I write now. Being a Scala learner, I am also impressed by it's functional nature and things like Option, which simply put, saves you from null (or nil) checks. For example in Scala -
case class User(id: Int, name: String, age: Int)
object UserRepository {
private val users = List(User(1, "Rocky", 34), User(2, "Annie", 33))
def findById(id: Int): Option[User] = users.find(u => u.id == id)
}
UserRepository.findById(2).map{u => u.name}.getOrElse("Not Found")
//returns "Annie"
UserRepository.findById(3).map{u => u.name}.getOrElse("Not Found")
//returns "Not Found"
So in the example above, I did not have to check that my User returned is null or not. Usually this will take 2-4 line of code if written in an imperative style. Check for null and do an action and do another action in case a null value is returned.
More on Options here.
Back to Ruby, this is some code that I wrote lately -
def get_recent_tweets_from_favorites(current_user)
tweets = []
if current_user
favs = current_user.get_favs
else
favs = system_favs
end
favs.each do |fav|
tweets << get_last_three_tweets(fav)
end
tweets
end
While this is a relatively simple 4-5 liner method, the actual LoC are 10 when you include the if and each. This makes me a sad panda. I wish I had something like Scala's Option to help me. But wait there is Rumonda! which makes my code above look like -
def get_recent_tweets_from_favorites(current_user)
tweets = []
Option(current_user).map(&:get_favs).get_or_else(system_favs).each do |fav|
tweets << get_last_three_tweets(fav)
end
end
It works! Now we have a simpler method with 4 lines of code (I cheated a bit) and we have used Monads, knowingly or unknowingly :) Happy programming!