Ruby wizardry, p.20
Ruby Wizardry, page 20
“All right,” said Rusty, “but try this one on for size. If you have an expression that will give you back a Boolean—that is, it will end up being true or false—you don’t have to compare it to true or false with ==. That’s just an extra step! You can just return the variable that will be true or false itself.” He typed into the Computing Contraption:
>> def true?(idea_we_have) >> idea_we_have >> end => nil >> most_true_variable = true => true >> true?(most_true_variable) => true
“most_true_variable is true, and since our method automatically returns whatever argument gets passed in, it returns true,” the Foreman explained.
“Wonderful!” said the Queen. “I love how simple that method was. But will this work only for variables that are true or false?”
Rusty nodded. “Though there’s another good refactoring that will let us determine whether a Ruby value is truthy or not.”
“Truthy?” asked Ruben and Scarlet together.
“Truthy!” said Rusty. “When I say a Ruby value is truthy, what I mean is: this value is not false or nil. Remember how those two values work with if and unless?” he asked, and he typed:
>> my_variable = true => true >> puts 'Truthy!' if my_variable Truthy! => nil
“Because my_variable is true and true is a truthy value, the if statement code runs and Ruby prints out 'Truthy!',” Rusty said. “Now let’s see what happens if we do the same thing with false.”
>> my_variable = false => false >> puts 'Truthy!' if my_variable => nil
“Nothing!” said the King.
“That’s right,” said the Foreman. “my_variable is false, so 'Truthy!' doesn’t get printed out on the screen. The same thing happens with nil.”
>> my_variable = nil => nil >> puts 'Truthy!' if my_variable => nil
“Nothing was printed for false or nil because they’re falsey values; every other value in Ruby is truthy,” Rusty explained. “Have a look!” He typed some more:
>> my_variable = 99 => 99 >> puts 'Truthy!' if my_variable Truthy! => nil
“You’ll see, though, that nil and false aren’t exactly the same, and 99 and true also aren’t exactly the same.” He typed again:
>> nil == false => false >> 99 == true => false
“But!” he exclaimed, raising a single finger, “we can turn a truthy value into true and a falsey value into false with a simple !!. You see, the first ! makes Ruby return a Boolean, but since ! means ‘not,’ it’s the opposite of what you want. The second ! fixes this by undoing the opposite you got from the first one!” The King, Scarlet, Ruben, and even the Queen looked puzzled. “Here, I’ll show you,” the Foreman offered, and he typed into the Computing Contraption:
>> truthy_value = 'A fancy string' => "A fancy string" >> falsey_value = nil => nil >> truthy_value => "A fancy string" >> !truthy_value => false >> !!truthy_value => true
“So truthy_value is a string,” said Scarlet, “and since it’s not false or nil, if you put it in an if statement, the code will run.”
“Right,” said Rusty.
“So,” Scarlet said, “!truthy_value is false, and not !truthy_value—that is, !!truthy_value—is true!”
“You’ve got it!” said Rusty. “Now, here’s how it works for falsey values.”
>> falsey_value => nil >> !falsey_value => true >> !!falsey_value => false
“It’s just the opposite!” said Ruben. “nil is falsey, so !nil is true and !!nil is false.”
“Exactly,” said the Foreman. “We could even write a method to see if something is truthy, like this.”
>> def truthy?(thing) >> !!thing >> end => nil >> truthy?('A fancy string') => true >> truthy?(nil) => false
“In this case, we’ve defined a truthy? method that takes a single argument, thing,” said Rusty. “Then we call !!thing: the first ! returns false if thing is truthy and true if thing is falsey. Since this is the opposite of what we want, we use !! to make our method return true if thing is truthy and false if thing is falsey.”
“That’s amazing!” said Scarlet.
“Isn’t it?” said Rusty. “Once we’ve defined truthy?, we can call it on 'A fancy string' to see that it’s a truthy value, then on nil to see that nil is falsey.”
“What else can we do to make our Ruby programs shorter and clearer?” Ruben asked.
“Well, this one might seem obvious,” said Rusty, “but it’s actually one of the hardest parts of programming—giving variables, methods, and constants good names!”
“What do you mean?” said Marshall, who was scribbling furiously on his clipboard.
“Well, let’s use our truthy? method as an example,” Rusty said. “Check out what would’ve happened if we’d picked a clumsier name.” He quickly typed:
>> def is_this_a_truthy_thing_or_not?(thing) >> return !!thing >> end => nil
“That looks terrible,” said the King.
“Yes, it does,” said Rusty. “Not only that, but it also has an extra return that we don’t need. The simpler method is much nicer.”
>> def truthy?(thing) >> !!thing >> end => nil
“Aha! I see,” said the King. “We want to give the Ruby objects we create simple, easy-to-remember names so we type less code and make fewer mistakes when we want to reference our code later.”
“Bingo!” said Rusty. “Imagine if we had to type is_this_a_truthy_thing_or_not? every time we wanted to check if a value was truthy. It’d be pure madness!”
“How else can we cut down on rewriting code?” asked Marshall.
De-duplicating Code
“Well, one nice way is to remove duplicated code whenever we can!” said Rusty. “It’s much too easy to cut and paste code all through our programs, which then makes it very hard to change those programs if variable names or values change. Take a look at this,” he said, typing:
>> def king?(dude) >> if dude == 'The King' >> puts 'Royal!' >> else >> puts 'Not royal.' >> end >> end => nil >> def queen?(lady) >> if lady == 'The Queen' >> puts 'Royal!' >> else >> puts 'Not royal.' >> end >> end => nil
“I’ve defined two methods here,” said Rusty. “The first one, king?, checks whether the argument passed in is 'The King'; if so, it puts 'Royal!', and otherwise it puts 'Not royal.'. I’ve also defined a second method, queen?, that checks whether the argument passed in is 'The Queen'. See how much of that code is repeated?” Rusty continued. “It was very boring to type, and what’s more, if we want to change any of the messages that get printed out, we have to do it in two places! I’d much rather type this,” he said, and so he did:
>> royal?(person) >> if person == 'The King' || person == 'The Queen' >> puts 'Royal!' >> else >> puts 'Not royal.' >> end >> end => nil
“Now we’ve got one method that does the work of two,” Rusty said.
>> royal?('The King') Royal! => nil >> royal?('The Queen') Royal! => nil >> royal?('The jester') Not royal. => nil
“I like that a lot better,” said Ruben. “And we could have written that with the ternary operator if we wanted, right?”
“Of course!” said Rusty. “We can get to that in a little while, if you like.”
“Before we do,” interrupted the King, “I worry that if we go too far down this road of combining methods, we might get methods that do too much work and are very hard to think about.”
“Happens all the time!” said the Foreman. “While it’s true that you often want to write the least amount of code you can, sometimes you end up writing very large, hard-to-think-about methods that really should be broken up into smaller pieces. Take a look at this method that came through the Refactory just the other day,” he said, and he typed into the Computing Contraption:
>> list_of_numbers = [1, 2, 3, 4, 5] => [1, 2, 3, 4, 5] >> def tally_odds_and_evens(numbers) >> evens = [] >> odds = [] >> numbers.each do |number| >> if number.even? >> puts 'Even!' >> evens.push(number) >> else >> puts 'Odd!' >> odds.push(number) >> end >> end >> puts "#{evens}" >> puts "#{odds}" >> end => nil
“First, it sets up a few variables,” Rusty said. “The evens array stores even numbers, the odds array stores odd numbers, and the list_of_numbers stores the numbers to check for evenness or oddness.”
“Next, the tally_odds_and_evens method iterates over a list of numbers and checks to see whether each one is even or odd with Ruby’s built-in even? and odd? methods. For each number, tally_odds_and_evens prints out whether it’s even or odd, then adds it to the appropriate array.”
>> tally_odds_and_evens(list_of_numbers) Odd! Even! Odd! Even! Odd! [2, 4] [1, 3, 5] => nil
“As you can see,” Rusty said, “it’s pretty complicated.”
“I’ll say!” said the King. “I can hardly follow a word of it.”
“It might be easier if we broke down this big method, tally_odds_and_evens, into a few smaller, well-named ones,” said Rusty, and he typed:
>> list_of_numbers = [1, 2, 3, 4, 5] => [1, 2, 3, 4, 5] >> def tally_odds_and_evens(numbers) >> evens = [] >> odds = [] >> numbers.each do |number| >> alert_odd_or_even(number) >> update_tally(number, evens, oddsna) >> end >> puts "#{evens}" >> puts "#{odds}" >> end => nil
“First, we’ll rewrite the tally_odds_and_evens method. We’ll move the code that prints Odd! or Even! to its own method, alert_odd_or_even, and we’ll move the code that updates the tally to its own method, update_tally. We’ll write each method in just a minute,” Rusty said.
“That makes sense,” said the King.
“Next, we’ll take out the part that writes Odd! or Even! on the screen and wrap it up in a method called alert_odd_or_even. In fact, we can use the ternary operator we learned about to make it a one-line method!”
>> def alert_odd_or_even(number) >> puts number.even? ? 'Even!' : 'Odd!' >> end => nil
“After that,” Rusty continued, “we’ll put the code that updates the evens and odds arrays into its own method, update_tally.”
>> def update_tally(number, evens, odd) >> if number.even? >> evens.push(number) >> else >> odds.push(number) >> end >> end => nil
“That’s the same code we had before, just wrapped up in its own method. It makes the overall tally_odds_and_evens method look much better, though, and it still works the same way,” Rusty explained.
>> tally_odds_and_evens(list_of_numbers) Odd! Even! Odd! Even! Odd! [2, 4] [1, 3, 5] => nil
“Overall, it’s a bit more code,” Rusty admitted, “but now it’s clearer what’s doing what, and we can change what gets printed out or how we update our lists of even and odd numbers independently from one another if we want to.”
“Excellent!” said the King, beaming. “I like my Ruby methods to be just like me: short and simple!” The Queen, Ruben, and Scarlet stifled a laugh.
Rusty pushed his hard hat up on his head. “That’s all the refactoring I can think of off the top of my head,” he said. He looked at his watch again. “I’m surprised we haven’t heard back from any of the search teams yet. What were you telling me these ne’er-do-wells were after?” He thought for a moment, then snapped his fingers. “Ah, yes! Your Key-a-ma-Jigger. That’s why you’re here in the first place, I take it?”
“Yes!” said Scarlet. “We found this plugged into the Panda Provisionator 3000 over at the Royal Stables, and we thought that it might be the last one our mysterious bad guys had, so they might have come back here for more.” She held the small piece of metal out to the Foreman once more.
Rusty nodded. “Yes, that’s one of ours,” he said. “And if your troublemakers are looking for more, they’d almost certainly be trying to get into the Vault of Tricky Things and Trinkets!”
“My word!” said the Queen. “What’s that?”
“It’s where we keep a large number of items,” said Rusty, “like Ruby code we’ve found particularly hard to refactor and various things and trinkets. It’s also where we keep a lot of our inventory, including our Key-a-ma-Jiggers.”
The King struck his palm with his fist. “If that’s where the Key-a-ma-Jiggers are, I’m sure that’s where we’ll find our culprits!” he said. “Could you call down and have your teams head there right away?”
No sooner had the King asked than the Foreman’s red telephone began ringing off the hook.
Rusty ran to the phone and picked it up. “Hello?” he said. He listened intently for a moment, then gasped. He covered the receiver with his hand. “One of my teams caught four intruders down by the Vault!” he said. He put the phone to his ear again, then sighed deeply. “All right,” he said. “Send every available worker. And hurry!” He hung up.
“What was it?” asked the Queen. “Did your team catch them?”
“No,” groaned the Foreman, “they’ve escaped!” The Queen’s face fell; the King covered his face with his hands; Scarlet and Ruben turned to each other, mouths open.
“But!” Rusty said, holding up a single finger, “every one of my workers is in hot pursuit. Our four villains were seen heading straight for the Refactory’s loading docks, and that’s a one-way street! We’ll have them surrounded faster than you can rename a Ruby method.”
“Then what are we waiting for?” said the Queen. “Let’s go see who we’ve been chasing all this time!” And with that, all five of them charged off to the loading docks in the depths of the Refactory.
Re-refactoring
Practice makes perfect! Now that you’ve learned a whole bunch of ways to make your Ruby code even shorter and simpler to read, it’s time to apply them to a couple of particularly gnarly methods. Not to worry, though: if you didn’t have any trouble with the refactorings we saw earlier, these’ll be a breeze! (Even if you stumbled here and there, you’ll be a refactoring master by the time you’re through with these examples.)
Let’s begin by making a new file called first_try.rb and typing the following code. We’ll actually be making two files this time: one for the initial code and one for the refactoring we’ll do. first_try.rb defines a method, all_about_my_number, and sets the number to 42 if no number is passed in. After that, it prints some information about the number, including what the number is and whether it’s positive, negative, or zero.
first_try.rb
def all_about_my_number(number) if number.nil? number = 42 end puts "My number is: #{number}" if number > 0 == true return 'Positive' elsif number < 0 == true return 'Negative' else return 'Zero' end end
If this doesn’t look like great code to you, don’t worry! We’re about to refactor it. In the same folder on your computer, create another file called refactored.rb and type the following code into it. This code will do exactly the same thing as the code in first_try.rb, but it will look much nicer.
refactored.rb
def describe_number(number) number ||= 42 puts "My number is: #{number}" sign(number) end def sign(number) case when number > 0 'Positive' when number < 0 'Negative' else 'Zero' end end
As always, you can run the code in your file by typing ruby first_try.rb and ruby refactored.rb from the command line. Since we made two files in the previous chapter and there’s no new code here, there shouldn’t be any big surprises! (Though there may be some small ones.)
The first difference you’ll probably notice is in the case statement; earlier, we did something like this:
case number when 0 puts 'Zero!' # ... and so on
And now we’re doing this:
case when number > 0 # ... and so on
These are both 100 percent correct Ruby. If you have a variable and you just want to check whether it equals a certain value, is a certain class, or is in a certain range, you’d use the first syntax; if you want to do specific checks on a value (like number > 0), you’d use the second one.
You probably also saw that we skipped right over some refactorings. For instance, we removed the == check from lines like if number > 0 == true. Sometimes you’ll start to refactor one way, then realize there’s an even better way to do it! Other times there are a whole bunch of ways to refactor your code that are all equally good, and you just happen to pick one over another.
Finally, we managed to pull out a bunch of repetition (including some return statements that we can let Ruby handle implicitly!) and broke out the code that checks the sign of a number (positive, negative, or zero) into its own method.
How could we make this refactoring even more awesome? There are probably an unlimited number of ways, but here are a few to get your gears turning. For example, we refactored the nil? check into an ||=. This works okay, but is there something else we could do? (Hint: We learned about setting default arguments in Chapter 7.) Also, we have an if/else statement that we converted to a case, but would it have made sense to use a ternary operator somewhere instead? Why or why not? Explain your answer in 6,000 words or more. (Hint: Don’t do that—it would be unbelievably boring.)
One more example to bake your noodle: we don’t do any checking to make sure that the argument that gets passed to our method really is a number. What happens if we put in a Boolean? A string? What could we do to refactor our method so it would be okay with non-number inputs?
You Know This!
Okay! It might have seemed at first that this chapter wouldn’t have a whole lot to offer—after all, we’re just rewriting the sort of code that we’ve been writing all along—but it turns out that rewriting our Ruby code can be even more challenging than writing it the first time. Just to make sure you’re up to those challenges (hint: you absolutely are), let’s go over the refactorings we covered one more time.
First, you saw that we can set a value conditionally with ||=. In other words, we can tell Ruby to set a value for a variable if that variable doesn’t already have one, but to use the existing value if it does:
>> my_variable ||= 'pink smoke' => "pink smoke" >> my_variable => "pink smoke"
Here, my_variable isn’t already set, so ||= sets it to 'pink smoke'. If the variable already has a value, though, ||= won’t change it. Check it out!
>> your_variable = 'blue smoke' =>
