Any fool can write code that a computer can understand. Good programmers write code that humans can understand - Martin Fowler.
Why you should care about the design of your code?
As developers, we sometimes tend to think that being productive is related to deliver your task as fast as possible, but for me, that is not the truth.
Be a productive developer is far from just execute your task and deploy your code, is also related to that code quality, how easy to maintain and evolve that feature in the next releases. Because of this, it's important to treat your code, even the minimal changes with attention.
Imagine that you work for a company that has software with a huge source code, too many features, too many teams, different approaches. Today you're working in a determinate feature, next week you focus is another, two weeks from now you return for the first feature... when you work for a big company, this flow is kind of normal.
Always remember, sometimes, you need to prevent your self from your own legacy old.
Avoid hidden structures
- The code is hard to understand and hard to maintain
- You must avoid use index position directly to get some data from an array
An example to avoid:
class ObscuringReferences def initialize(data) @data = data end def calculate data.collect do |cell| cell + (cell * 2) end end private attr_reader :data end ObscuringReferences.new([[622, 20], [622, 23], [559, 30], [559, 40]])
The main problem of this code is that it is using the indexes positions of the array to get some information to be used inside de
calculate method, but we can not be sure of what are these pieces of information because
1 are not some meaningful.
How to avoid hidden structures?
- Use constants to assign values
class RevealingReferences HEIGTH = 0 WIDTH = 1 def initialize(data) @data = data end def calculate data.collect do |value| value[HEIGTH] + (value[WIDTH] * 2) end end private attr_reader :data end
Assigning the indexes positions in a constant will help to improve the code because now, the indexes are meaningful.
- Use a struct to create a class
Shape = Struct.new(:height, :width) class RevealingReferences def initialize(data) @data = prepare(data) end def calculate data.collect do |shape| shape.height + (shape.width * 2) end end private attr_reader :data def prepare(data) data.collect do |value| Shape.new(*value) end end end
Another approach is to use
Struct data structure to represent the data.
Avoid comparison with regular expressions
- Instead of it, assign this regular expression to a meaningful variable name, and use this variable.
# Instead of using directly the regular expression in the code... if string =~ /\d+/ # ...CODE end # Assign it to a variable with a meaningful name, and use it. is_decimal = /\d+/ if string =~ is_decimal # ...CODE end
This simple change could save your time trying to understand what the regular expression is doing.
Avoid directly dependencies
Follow the Demeter's Law
- Avoid high coupled code
- Do not know about the dependencies from a dependency
class Customer def state address.state.name end end
In the example above, the
Customer#state method violates the Demeter's Law, because of the fact that, to access the
state.name we first need to the
customer to get in contact with your relation with
address and then the relationship with
address will contact with
So we can conclude that the class
Customer knows too much about your dependencies. Any error inside the
State class can break the
Customer, even they are not directly related.
An approach to solve this problem is using Delegators.
Tell don' t Ask!
- Tell the objects what to do, do not ask if the can do
- Avoid asking an object which is your current state, to perform something based on this answer
class SocialMediaPost def send SocialMediaPost::API.post(args) end def connected? connected end end post = SocialMediaPost.new(content: 'Some Message') if post.connected? post.send end
In the code, above we first ask to the object if it's connected and then based on this we perform an action to
Now imagine that you need to execute this actions in different points inside your code base and now, you need to add another verification in addition to the current one. Besides, with this code the internal details of the
SocialMediaPost are exposed, we know that
connected? must be true to perform other methods.
- Do not expose the details of an object unnecessarily
class SocialMediaPost def send SocialMediaPost::API.post(args) if connected? end private def connected? connected end end post = SocialMediaPost.new(content: 'Some Message') post.send
This approach will not expose the internal details of the class and follows that Demeter's Law.
- Boy scout rule: Always leave your code better than you found it
- A better design starts on the small changes
- You need to write code for humans, not for computers