Second Time's the Charm - Ruby Wizardry: An Introduction to Programming for Kids (2014)

Ruby Wizardry: An Introduction to Programming for Kids (2014)

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.

image with no caption

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.

image with no caption

“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.”

image with no caption

“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.”

“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 trueor 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.”

image with no caption

“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 ifthing 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.”

image with no caption

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'

=> "blue smoke"

>> your_variable ||= 'pink smoke'

=> "blue smoke"

>> your_variable

=> "blue smoke"

You also saw that we can replace if ! with unless:

>> if !my_variable.nil?

>> puts 'Not nil!'

>> end

Not nil!

=> nil

>> unless my_variable.nil?

>> puts 'Not nil!'

>> end

Not nil!

=> nil

And you saw that we can even put an if or unless inline if we don’t need an else:

>> puts 'Not nil!' unless my_variable.nil?

Not nil!

=> nil

If an else is involved, it’s usually better to stick to a regular if/else.

>> if true

>> puts 'True!'

>> else

>> puts 'False!'

>> end

True!

=> nil

However, for very short if/elses, sometimes it makes sense to use the ternary operator, like so:

>> puts true ? 'True!' : 'False!'

True!

=> nil

You learned that we can even use the ternary operator with methods that have question marks in them! Just be sure to use two question marks: one that’s part of the method name and one that’s part of the ternary statement:

>> jill = nil

>> puts jill.nil? ? "Jill's nil!" : "Jill's not nil at all."

>> Jill's nil!

=> nil

We also talked about replacing long chains of if/elsif/else with case statements. A case statement takes a variable and does different things depending on its value:

>> random_trinket = 'plastic cup'

=> "Plastic cup"

>> case random_trinket

>> when 'plastic cup'

>> puts "Plastic cup's on the up and up!"

>> when 'pet ham'

>> puts "A pet ham! What are you, an elf?"

>> when 'star monkey'

>> puts "I've always wanted one of those!"

>> else

>> puts "A #{random_trinket}, huh? Never heard of it!"

>> end

Plastic cup's on the up and up!

=> nil

Next up, we reminisced about Ruby’s implicit return. Ruby methods automatically return the result of the last bit of code they evaluate, so these two methods do exactly the same thing:

>> def number_42?(number)

>> return number == 42

>> end

=> nil

>> number_42?(42)

=> true

>> number_42?(43)

=> false

>> def number_42?(number)

>> number == 42

>> end

=> nil

>> number_42?(42)

=> true

>> number_42?(43)

=> false

Next, you found out that when we’re using variables that are Booleans (true or false), we can just return those variables directly instead of comparing them to true or false with ==. This works:

>> def thing_true?(thing)

>> thing == true

>> end

=> nil

But this does the exact same thing and uses a little less code:

>> def thing_true?(thing)

>> thing

>> end

=> nil

>> the_truest_thing_ever = true

=> true

>> thing_true?(the_truest_thing_ever)

=> true

In fact, we can get the truthiness of any Ruby value by using two “not” symbols (!) in front of the object. A truthy Ruby value will act like true in an if statement, and a falsey one will act like false. All Ruby values are truthy except for false and nil. Truthy values run the code in theif statement:

>> if true

>> puts 'Woohoo!'

>> end

Woohoo!

=> nil

And falsey ones don’t:

>> if false

>> puts 'A waltz.'

>> end

=> nil

Nothing happens! Nothing happens with nil, either:

>> if nil

>> puts 'A dill (pickle).'

>> end

=> nil

Since all values except false and nil are truthy, a regular string will be truthy in an if statement:

>> if 'fancy string'

>> puts 'For a fancy king!'

>> end

For a fancy king!

=> nil

You can always check the truthiness of a value in Ruby with !!:

>> !!nil

=> false

>> !!'fancy string'

=> true

I also mentioned something you probably already knew in the back of your mind: giving good names to our constants, variables, and methods is important! See how much better the second method name is than the first?

>> def is_this_value_truthy?(value)

>> !!value

>> end

=> nil

>> def truthy?(value)

>> !!value

>> end

=> nil

Last but not least, you saw that removing duplicated code and breaking our programs apart into small methods that do very specific jobs can make our Ruby code easier to write, understand, and change. The more code you read, the more you’ll see this is true, so don’t hesitate to ask your local adult to help you find snippets of Ruby code on the Internet to read through!

Speaking of bits of code, we’re about to see a fresh delivery of Ruby syntax when we follow the King, the Queen, Scarlet, Ruben, and Rusty down to the loading docks. The constant picking up and dropping off that occurs down there will be a perfect opportunity to explore Ruby input andoutput—also called I/O—and we just might catch our first glimpse of the evil doers who have been turning this peaceful kingdom completely upside down.