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[0] + (cell[1] * 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 0
and 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
- Rather use Composition over Inheritance
- Embrace SOLID Principles
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 state
.
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 post.send
.
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.
Conclusions
- 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
Top comments (0)