• No results found

Reasons to change

In document Ruby Science (Page 184-188)

One of the challenges in identifying reasons to change is that you need to de-cide what granularity to be concerned with.

In our example application, users can invite their friends to take surveys. When an invitation is sent, we encapsulate that invitation in a basic ActiveRecord sub-class:

172

# app/models/invitation.rb

class Invitation < ActiveRecord::Base

EMAIL_REGEX = /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i STATUSES = %w(pending accepted)

belongs_to :sender, class_name:'User' belongs_to :survey

before_create :set_token

validates :recipient_email, presence:true, format: EMAIL_REGEX validates :status, inclusion: {in: STATUSES }

def to_param token end

def deliver

body = InvitationMessage.new(self).body

Mailer.invitation_notification(self, body).deliver end

private

def set_token

self.token = SecureRandom.urlsafe_base64 end

end

Everything in this class has something to do with invitations. You could make the blunt assessment that this class obeys SRP, because it will only change when invitation-related functionality changes. However, looking more carefully at how invitations are implemented, several other reasons to change can be identified:

• The format of invitation tokens changes.

• A bug is identified in our validation of email addresses.

• We need to deliver invitations using some mechanism other than email.

• Invitations need to be persisted in another way, such as in a NoSQL database.

• The API for ActiveRecord or ActiveSupport changes during an update.

• The application switches to a new framework besides Rails.

That gives us half a dozen reasons this class might change, leading to the prob-able conclusion that this class does not follow SRP. So, should this class be refactored?

Stability

Not all reasons to change are created equal.

As a developer, you know which changes are likely from experience or just common sense. For example, attributes and business rules for invitations are likely to change, so we know that this class will change as invitations evolve in the application.

Regular expressions are powerful but tricky beasts, so it’s likely that we’ll have to adjust our regular expression. It might be nice to encapsulate that some-where else, such as in acustom validator.

It would be unwise to guess as to what delivery mechanisms may loom in the distant future, but it’s not out of the realm of possibility that we’ll need to send messages using an internal private messaging system or another service like Facebook or Twitter. Therefore, it may be worthwhile to usedependency in-jectionto remove the details of delivery from this model. This may also make testing easier and make the class easier to understand as a unit, because it will remove distracting details relating to email delivery.

NoSQL databases have their uses, but we have no reason to believe we’ll ever need to move these records into another type of database. ActiveRecord has proven to be a safe and steady default choice, so it’s probably not worth the effort to protect ourselves against that change.

Some of our business logic is expressed using APIs from libraries that could change, such as validations and relationships. We could write our own adapter

to protect ourselves from those changes, but the maintenance burden is un-likely to be worth the benefit, and it will make the code harder to understand, as there will be unnecessary indirection between the model and the framework.

Lastly, we could protect our application against framework changes by pre-venting any business logic from leaking into the framework classes, such as controllers and ActiveRecord models. Again, this would add a thick layer of indirection to protect against an unlikely change.

However, if you’re trying out a new database, object-relational mapper, or framework, it may be worth adding some increased protection. The first time you use a new database, you’ll be less sure of that decision. If you prevent any business logic from mixing with the persistence logic, it will make it easier for you to undo that decision and fall back to a familiar solution like ActiveRecord in case the new database turns against you.

The less sure you are about a decision, the more you should isolate that deci-sion from the rest of your application.

Cohesion

One of the primary goals of SRP is to promote cohesive classes. The more closely related the methods and properties are to each other, the more cohe-sive a class is.

Classes with high cohesion are easier to understand, because the pieces fit naturally together. They’re also easier to change and reuse, because they won’t be coupled to any unexpected dependencies.

Following this principle will lead to high cohesion, but it’s important to focus on the output of each change made to follow the principle. If you notice an extra responsibility in a class, think about the benefits of extracting that responsibility.

If you think noticeably higher cohesion will be the result, charge ahead. If you think it will simply be a way to spend an afternoon, make a note of it and move on.

In document Ruby Science (Page 184-188)

Related documents