@emails ||= []
end
end
==>
@emails = []
end
end
Motivation
The motivation for converting attributes to be eagerly initialized is for code readability purposes. Lazily initialized attributes change their value upon access. Lazily initialized attributes can be problematic to debug because their values change upon access. Eagerly Initialized Attributes initialize their attributes in the constructor of the class. This leads to encapsulating all initialization logic in the constructor and consistent results when querying the value of the instance variable.
Discussion
I prefer Lazily Initialized Attributes, but Martin prefers Eagerly Initialized Attributes. I opened up the discussion to the reviewers of Refactoring, Ruby Edition and my current ThoughtWorks team, but in the end it was split 50/50 on preference. Based on that fact, I told Martin I didn't think it was a good candidate for Refactoring, Ruby Edition. Not surprisingly, he had a better solution: Provide examples of both refactoring to Eagerly Initialized Attribute and refactoring to Lazily Initialized Attribute.
Martin and I agree that this isn't something worth being religious about. Additionally, we both think it's valuable for a team to standardize on Lazily or Eagerly Initialized Attributes.
Mechanics
- Move the initialization logic to the constructor.
- Test
The code below is an Employee class with both the email and voice_mail attributes lazily initialized.
@emails ||= []
end
@voice_mails ||= []
end
end
Moving to an Eagerly Initialized Attribute generally means moving the initialization logic from the getter methods into the constructor.
attr_reader :emails, :voice_mails
@emails = []
@voice_mails = []
end
end
One key difference here is regarding performance. If you construct the object in question very frequently, eagerly initializing attributes that might never be used introduces a performance cost. If the objects are generally long-lived, lazily initializing attributes adds the overhead of checking attribute initialization state on every access.
ReplyDeleteIt may be that each approach is better in certain circumstances. I'm generally willing to accept the cost of eager initialization to keep initialization code in one place and because I expect most of my objects' attributes are going to always be accessed.
Eagerly initialized will also scale if your applications grow multi-threaded or the run-time becomes much more complex.
ReplyDeleteVariables which are used within a 'private' context, that is, class variables or the like, may be slightly better candidates for late initialization, but even then, I probably wouldn't.
One of these approaches runs clean with -w. One does not.
ReplyDeleteGuess which one I favor? :)
I question whether including both refactorings is in fact a better solution.
ReplyDeleteAs written, the Eagerly Initialized Attribute refactoring is little more than a description of a design choice. (Understandably, since it isn't your own preference.)
Everybody already knows they have a choice. Where refactorings help is in the guidance as to where that design is appropriate and how it helps.
Unless you can argue convincingly that this refactoring makes code better in some particular situation, then it is just noise and will detract from the rest of your book.
Why not ask one of the 50% of people who favour Eagerly Initialized Attribute to write up this refactoring? Otherwise go with your first choice and leave it out.
Daniel Berger: They both run clean for me on ruby 1.8.6 (2007-03-13 patchlevel 0) [i486-linux].
ReplyDeleteI think the decision to include both refactorings in the book is a good idea. It allows people to see the cost/benefits of each approach and choose the most appropriate for their given situation. Kent Beck did the same thing in Smalltalk Best Practices. In his words "Initialization is an issue for which I cannot in good conscience pick one solution."
ReplyDelete