Ruby wizardry, p.19
Ruby Wizardry, page 19
So for example, if we had colorize.rb in the same folder as the following Ruby script, we’d write it like this:
require './colorize' class Food < PandaFood include Colorize # ...and so on and so forth end
Finally, you saw that we could use the scope resolution operator to get at particular constants located in modules (even deeply nested ones!), and we can simply use the dot syntax we’re used to for getting ahold of class methods:
MyClass::AModuleInsideThat::YetAnotherModule::MY_CONSTANT MyClass.some_method
With that, you now officially know everything there is to know about Ruby classes and modules! (Okay, okay, there’s always more to learn, but you know all the stuff you’d use to write everyday Ruby programs.) You know so much Ruby, in fact, that we’re going to take a short break from learning new stuff to focus on rewriting some of the code we already know. Rewriting your code so it still does the same thing but looks nicer or runs faster is called refactoring, and—as luck would have it!—that’s exactly what the Refactory is all about.
Chapter 11. Second Time’s the Charm
Refactoring at the Refactory
The King, the Queen, Ruben, and Scarlet leapt from the Loop platform the moment it eased to a halt at the Center o’ the Kingdom station and the doors whooshed open. They made a beeline for the gleaming red metal gates of the Refactory, which they could already see from the station exit.
“Right through here!” said the King. “Quickly now!” As they approached, the two guards manning the gates hastily pulled them open, trying to salute at the same time.
The four of them sped through the gates and down a long paved road. The Refactory loomed ahead: a huge red metal block of a building with a dozen chimneys puffing a pleasant-looking pink smoke.
They arrived at a large set of gleaming double doors that were propped open. A warm red light shone from within. Without hesitating, the King and Queen strode inside, and Ruben and Scarlet followed.
“My good man!” called the King, waving at a man in a hard hat holding a clipboard. “We’ve got an emergency! We need to speak to the Foreman, posthaste!”
The man looked up and nearly dropped his clipboard. “Your Majesty!” he said. “Of course, of course! Right away!” He dashed off into the recesses of the Refactory, clutching his helmet to his head with one hand and his clipboard with the other.
The King, the Queen, Ruben, and Scarlet stood in the entry-way, catching their breath. Scarlet looked around. “Where are we?” she asked.
“This is the main entrance to the Refactory,” said the Queen. She nodded toward the glow coming from farther inside the building. “Over that way is the Refactory floor, where all the actual work takes place.”
“That’s where they make Key-a-ma-Jiggers?” asked Ruben.
The Queen nodded. “Among other things,” she said.
Ruben opened his mouth to ask what else the Refactory made, but at that instant, the young man in the hard hat returned, followed by a much older man with twinkling eyes and a great big bushy beard.
“Your Majesty! Your Highness!” the older man said to the King and Queen, bowing to each in turn. “What can I do for you?”
“Seal the factory!” said the Queen. “We have reason to believe there are intruders in the Refactory, and they must be stopped!”
The bearded man nodded curtly and walked across the Refactory’s narrow entryway to a bright red telephone. He picked up the receiver and dialed a single digit. When he spoke into the phone, his voice echoed throughout the entire Refactory:
SEAL ALL EXITS! THIS IS NOT A DRILL!
SEAL ALL EXITS! THIS IS NOT A DRILL!
The old man placed a hand over the phone’s receiver. “What do these intruders look like?” he asked.
“We’re not sure,” said the Queen.
“but there are four of them.” The man nodded again and got back on the phone:
BEGIN SECTOR-BY-SECTOR SEARCH FOR FOUR INTRUDERS! DETAIN ANY SUSPICIOUS PERSONS AND REPORT IMMEDIATELY!
With that, he hung up the phone and strode back to the rest of them, smiling.
“That should do the trick,” he said. “If there are any intruders in the Refactory, my team will find them and call us at once.”
“Thank you so much!” said Scarlet. “But, um, who exactly are you?”
“Why, I’m the Foreman, Rusty Fourman!” the man said, tipping his hard hat. “I’m in charge of all operations here at the Refactory.” He gestured to the young man who had fetched him. “This is Marshall Fiveman, my right-hand man.”
“Pleased to meet you,” Marshall said.
“Pleased to meet you, too!” said Ruben.
“Rusty has been running the Refactory for as long as I can remember,” said the King.
“How long is that?” asked Scarlet.
“Oh, I don’t know,” said the King. “At least several days.”
“Years and years!” said Rusty, laughing. He tugged on his beard and suddenly became serious. “I imagine these intruders are what brought you out my way. I have no doubt we’ll catch them soon, but do you know what they might be doing here?”
“Yes!” Scarlet said, fishing around in her pocket. “Do you make these?” she asked, holding out the Key-a-ma-Jigger.
Rusty peered at the small piece of metal in her hand. “Well, yes, we do make Key-a-ma-Jiggers here,” he said. “And a few other things. Mostly, though, we’re in the business of refactoring Ruby code.”
“Refactoring?” said Ruben. “What’s that?”
“It’s basically when you rewrite your programs,” Rusty said.
“Rewrite them?!” Ruben said. “But I spent so much time writing them the first time! Why would I do it again?”
“Because you can make your code faster, easier to read, or easier to update, and it still does the same work.” Rusty said. He thought for a moment. “It might be easier if I show you. We can do a few of the more common Ruby refactorings, and I think you’ll get the idea pretty quickly.” He looked at his watch. “With the factory sealed tight, it’s only a matter of time before my crew finds your culprits. In the meantime, let’s refactor a little Ruby!”
The Foreman beckoned them closer and led them deeper into the Refactory, toward the warm glow that turned everything inside the building a deep red. He walked over to a long, arched railing overlooking a gently bubbling pool of what looked like molten red metal and opened up a familiar-looking machine—a Computing Contraption! The King, the Queen, Scarlet, and Ruben walked up to him as he began to type at the keyboard.
“Now then,” said Rusty, scratching his nose with one hand and continuing to type with the other, “in all my years at the Refactory, I’ve seen a lot of Ruby. Over time, I’ve found patterns in the code that work very well, and patterns that don’t work so well. Would you like to see a few of the good ones?” he asked.
“Absolutely!” answered the King.
Variable Assignment Tricks
“For example,” Rusty said, “I often see code where the person who wrote it would like to set a variable to a particular value, but only if the value hasn’t already been set. So I might write something like this that checks whether a particular variable is nil and, if so, sets it to a default value.” And he typed:
>> rubens_number = nil => nil >> if rubens_number.nil? >> rubens_number = 42 >> end => 42
“That looks perfectly all right to me,” said the King.
“Oh, it’s quite correct Ruby,” said Rusty, “and it will do exactly what we think it will—because rubens_number is nil, Ruby sets it to 42. But there’s a much clearer way to write it!” He typed some more:
>> rubens_number ||= 42 => 42 >> rubens_number => 42
“You can think of ||= as being a combination of || for ‘or’ and = for variable assignment,” said Rusty. “That combination says: ‘Set rubens_number to 42 if it doesn’t already have a value.’ It’s the same thing as typing this!” He typed some more:
>> rubens_number = nil => nil >> rubens_number = rubens_number || 43 => 43
“What if the variable does already have a value?” Scarlet asked.
“Let’s find out!” said Rusty. He typed some more:
>> scarlets_number = 700 => 700 >> scarlets_number ||= 42 => 700 >> scarlets_number => 700
“In this case,” Rusty said, “scarlets_number already has a value of 700, so ||= doesn’t do anything. As I mentioned, || means ‘or,’ and you’ve likely seen that = means ‘assign this value to a variable.’” Scarlet and Ruben nodded.
“So,” Rusty continued, “when we write ||=, we’re telling Ruby: ‘Hey! You should conditionally assign this value to this variable.’ That’s just a fancy way of saying we want Ruby to use the value it already knows or use the new value if the variable isn’t set. For rubens_number, there was no value, so 42 was set; for scarlets_number, we’d already set 700 as the value, so ||= 42 did nothing.”
“But couldn’t we write this?” Scarlet asked, and typed:
>> rubens_number = 42 if rubens_number.nil? => 42
“Why, yes!” Rusty said, and his great bushy beard turned upward as he smiled. “I wouldn’t necessarily use that code in this example, since I can just as easily use ||=, but it’s a very common refactoring to use inline ifs and unlesses in Ruby.”
“What do you mean by inline?” asked Scarlet.
“I’ll show you!” said the Foreman, and he typed more code into the Computing Contraption:
>> if !rubens_number.nil? >> puts 'Not nil!' >> end Not nil! => nil
“That’ll get the job done,” said Rusty, “but why use if and ! if we can just use unless?”
>> unless rubens_number.nil? >> puts 'Not nil!' >> end Not nil! => nil
“Now, that’s a bit better,” Rusty continued, “but it’s still more lines of code than we need. If we’ve got an if or unless but no else, we can write the whole thing in one line, like this.”
>> puts 'Not nil!' unless rubens_number.nil? Not nil! => nil
“Now this is the best!” said Rusty. “Not only can we convert if !s to unlesses, but we can also write unless on a single line with the variable we’re testing!”
“And we can do that with if, too?” asked Scarlet.
“You bet!” said Rusty, and he typed:
>> puts '42! My favorite number!' if rubens_number == 42 42! My favorite number! => nil
“Now, just as with if, we can use else with unless,” said Rusty, “but while if/else makes a lot of sense to me, I find unless/else confusing.”
Crystal-Clear Conditionals
“I agree,” said the King, rubbing his head. “So we should convert if !s to unlesses, and we can make if or unless one line if there’s no else?”
“Precisely,” said Rusty. “This is confusing:
>> unless rubens_number.nil? >> puts 'Not nil!' >> else >> puts 'Totally nil.' >> end Not nil! => nil
“But this is clear as day!”
>> if rubens_number.nil? >> puts 'Totally nil.' >> else >> puts 'Not nil!' >> end Not nil! => nil
“In fact,” Rusty continued, “we could write these as two one-line statements—one if and one unless. I don’t think that’s as easy to understand, but I’ll show it to you in case you’re curious.”
>> puts 'Not nil!' unless rubens_number.nil? Not nil! => nil >> puts 'Totally nil.' if rubens_number.nil? => nil
“Remember,” said Rusty, “puts returns nil, so that’s why we see it after the =>. But since rubens_number is 42 and not nil, Ruby doesn’t print 'Totally nil.'.”
“I think the if/else one is the easiest to understand,” said Ruben, “but it’s still a lot of extra lines. If there is an else, is there any simpler way to write it?”
“As it happens, there is,” said Rusty. “We can use a ternary operator. It looks like this!”
>> puts 1 < 2 ? 'One is less than two!' : 'One is greater than two!' One is less than two! => nil
“Sweet limbo of lost twist-ties!” cried the King. “What in our peaceful kingdom is that?”
“It’s not nearly as scary as it looks. We’ll just use a question mark followed by a colon in our code,” said Rusty. “In this case, we want our code to print something out using puts. Next, we give Ruby an expression: something that will either turn out to be true or false. In this case, that’s 1 < 2.” Rusty scratched his beard. “Then we write a question mark, followed by what Ruby should do if the expression is true. Finally, we write a colon, followed by what Ruby should do if the expression is false. Since 1 is less than 2, Ruby prints out One is less than two!” He thought for a moment. “Really, you can think of it as writing an if/else, just all on one line. The ? is like a shorthand if, and the : is like a shorthand else.”
“That’s quite marvelous,” said the Queen, “but don’t you find it a bit hard to read?”
“Sometimes,” admitted Rusty, “so I’ll often stick to a regular if/else. But if it’s a very short bit of code, I’ll sometimes refactor an if/else into a ? :.”
“What if the expression you want to check is a method with a question mark?” Ruben asked. “Will the ternary operator still work?”
“Oh, yes,” said Rusty, and he quickly typed:
>> bill = nil => nil >> puts bill.nil? ? "Bill's nil!" : "Bill's not nil at all." Bill's nil! => nil
“That third line can look tricky with the two question marks so close together,” said Rusty, “so you want to be a bit careful with them. Remember, nil? is a built-in Ruby method that returns true if the object it’s called on is nil and false otherwise.”
“It’s also important to remember that nil gets returned because puts has no return value, not because it’s returning bill!” said the Queen.
“Quite right, quite right,” said Rusty.
“This looks pretty good,” said Scarlet, squinting at the Computing Contraption screen, “but I feel like a whole bunch of ? : symbols in a row—or even if/elses!—would get hard to read. Is there a good way to write code when Ruby should do a lot of different things without our having to write ifs and elses all over the place?”
When You Need a case Statement
“You’ve got a keen eye for refactoring,” said Rusty. “There is something we can use to replace ifs and elses in Ruby. And while I don’t find myself using it a lot,” he continued, “it can be much more readable than a long chain of ifs, elsifs, and elses. It’s called a case statement. Have a look!” He typed:
>> number = 1 >> case number >> when 0 >> puts "Zero!" >> when 1 >> puts "One is fun!" >> when 2 >> puts "Two. It's true!" >> when 3 >> puts "Three for me." >> else >> puts "#{number}? I don't know that one." >> end One is fun! => nil
“We use the case keyword to tell Ruby which variable to pay attention to,” Rusty explained. “Then we can use when to say: when this value is the case—that is, when this value is the variable we’re looking at—do this thing!”
“And just like with if and unless, we use else to have Ruby do something when nothing matches,” Ruben said.
“Exactly right,” said Rusty.
“But is this all case statements can do?” Marshall piped up. “It seems to me it’s not that interesting to just have them check whether a variable is a certain number.”
“Oh my, no,” said Rusty. “They can get mighty fancy!” He typed:
>> number = 7 >> case number ➊ >> when 0 >> puts "That's definitely zero." ➋ >> when 1..10 >> puts "It's a number between 1 and 10, all right." ➌ >> when 42 >> puts "Ah yes, 42. My favorite number!" ➍ >> when String >> puts "What? That's a string!" >> else >> puts "A #{number}? What in the world is a #{number}?" >> end It's a number between 1 and 10, all right. => nil
“We can check whether a number is a certain value like 0 (➊) or 42 (➌), whether it falls in a range (➋), or even whether it’s an instance of a particular class, like String (➍),” said Rusty. “case statements can quickly do a lot of work that if and else would take a long time to handle.”
“That is quite fancy,” said the King, “but if there’s anything I’ve learned from Ruby, it’s that the most delightful moments are when I can get something done without having to write out every last detail. Are there any refactorings like that?”
Simplifying Methods
“I thought you’d never ask,” said the Foreman. “This is an old one, but a good one. Do you know about methods and return?”
They all nodded.
“Perfect,” he said. “As you may or may not know, Ruby methods will automatically return the result of the last bit of code they evaluate. That means that if you want your method to return the last expression it evaluates, you can leave off the return keyword completely. Let’s define a method that simply checks if the argument it gets is true.”
>> def true?(idea_we_have) >> return idea_we_have == true >> end => nil
“Now, that’ll return true if idea_we_have is true and false if it isn’t,” Rusty said, “but it turns out that Ruby automatically returns the result of the last bit of code it runs. We don’t need return at all!”
>> def true?(idea_we_have) >> idea_we_have == true >> end => nil
“Ah, yes!” said the King. “I think we’ve seen this bit of Ruby wizardry before.”
