Sei sulla pagina 1di 7

Use An Ask, Dont Tell Policy With Ruby

The next time you need to develop a new algorithm, ask Ruby for what you want, dont tell it what to do. Dont think of your code as a series of instructions for the computer to follow. Instead, ask Ruby for what you need !our code should state the solution to your problem, even if youre not sure what that solution is yet" Then dive into more and more detail, filling in your solutions gaps as you do. This can lead to a more expressive, functional solution that you might not find otherwise. Too often over the years Ive written code that consists of instructions for the computer to follow. Do this, do that, and then finish by doing this third thing. #s I write code I imagine I am the computer, in a way, asking myself $hat do I need to do first to solve this problem% $hen I decide, this becomes the first line of code in my program. Then I continue, writing each line of code as another instruction for the computer to follow. &ut what does '#sk, Dont Tell( mean exactly% #nd how could Ruby possibly know the answer when I ask it something% #n example will help you understand what I mean.

Parsing a Yeats Poem


)ast week I needed to parse a text file to obtain the lines of text that appeared after a certain word. *y actual task was very boring +separating blog articles from their metadata,, so instead lets work with something more beautiful, The Lake Isle Of Innisfree I will arise and go now, and go to Innisfree, #nd a small cabin build there, of clay and wattles made -ine bean.rows will I have there, a hive for the honeybee, #nd live alone in the bee.loud glade. #nd I shall have some peace there, for peace comes dropping slow, Dropping from the veils of the morning to where the cricket sings/ There midnight0s all a glimmer, and noon a purple glow, #nd evening full of the linnet0s wings. I will arise and go now, for always night and day I hear lake water lapping with low sounds by the shore/ $hile I stand on the roadway, or on the pavements grey, I hear it in the deep heart0s core. *y task is to write a Ruby script to return the line that contains a given word, along with the following lines

Telling Ruby What To Do


$hen I first wrote this script, I put myself in the computers shoes $hat do I need to do to find the target word% I started writing instructions for Ruby to follow. 2irst I need to open the file and read in the poem

3ere 2ile4readlines saves all the lines of text into an array, which the parse method will process, returning the result in another array. )ater I 5oin the result lines together and print them out. 3ow do I implement parse% #gain, I imagine that I am the computer, that I am Ruby. 3ow do I find the lines that follow glimmer% $ell, obviously I need to loop through the array looking for the target word.

7nce I find the word, Ill start saving the lines into a new array called result. 8ince I want to save all the following lines and not 5ust the matching line, Ill also use a boolean flag to keep track of whether Ive already seen the target.

$hats wrong with this code% -othing really. It works 5ust fine, and its even somewhat idiomatic Ruby. In the past, I would have probably considered this done and moved on. 3owever, I can do better than this. I can ask Ruby for what I want, instead of telling Ruby what to do.

Ask Ruby For What You Want


Dont imagine you are the computer. Dont think about how to solve a problem by figuring out what Ruby should do and then writing down instructions for it to follow. Instead, start by asking Ruby for the answer. $hat should my method return% #n array of the lines that appear after the target word. To reflect this, Ill rename my method from parse +telling Ruby what to do, to lines9after +asking Ruby for what I want,. This might seem like an unimportant detail, but naming methods is one of the most difficult and important things a programmer does. :icking a name for a method gives the reader a hint about what the method does, about what your intentions were when you wrote it. Think of writing code the same way you would think of writing an essay or story. !ou want your readers to understand what you are saying, and to be able to follow along. +!ou also want them to en5oy reading enough that they consider the code to be their own someday., To get started Ill write the new method to return an empty array.

-otice on the left I changed the label from 'Instructions ( to '$hat do I want%( This reflects my new way of thinking about the problem. -ow, what does 'appear after the target word( mean exactly% It means the lines that appear in the array after +and including, the line containing the target. #h< in other words, the lines9after method should return a subset or slice of the array. Rewriting the problem in a different way lead me towards a solution I hadnt thought of before. -ow I can rewrite the '$hat do I want%( text like this

I rewrote what I want from Ruby to be more specific I want a 'portion of the array( and I want the portion 'including and following the line containing the target.( I havent written much code yet, but Ive taken a big step forward in how I think about the problem. 7n the right, Ive written code to return a subset of the array, lines=target9index...1>. &ut my solution is still incomplete/ what should target9index be% Thinking about this a bit, its easy to see how to find the line containing the target string I can use detect to find the line that includes the target word.

&ut Im still not done. I need the index of the line containing the target, not the line itself. 3ow can I find target9index% #gain, I shouldnt tell Ruby what to do +maybe create a local

variable and loop through the lines checking each one,. Instead, I should ask Ruby for what I need. $hat do I need% I need the index which corresponds to the line containing the target. In other words, I need to find +to detect, the target index, not the target line. 3eres how to do it

3ere I use Rubys detect method to search a range of index values, not lines. Inside the block I check whether the line corresponding to each index +lines=i>, contains the target. #t the bottom I return the correct slice of the array if I found the target, or an empty array if I didnt.

Learning From Functional Languages


In my opinion this code is better than what I showed earlier. $hy% They both work e@ually well. $hats the difference% )ets take a look at them side.by.side.

2irst of all, I have simpler, more terse code. )ess code is better. The lines9after method contains 5ust ? lines of code while the parse method contains A. 7f course, I could find ways to rewrite parse to use fewer lines, but any way you look at it lines9after is simpler than parse. The parse method contains two local variables which are changed, or mutated, by code inside the loop. This makes the method harder to understand. $hat is the value of flag% $hat about result% To really understand how parse works you almost need to simulate the loop inside your head, thinking about how the flag and result values change over time.

The lines9after method also contains two local variables. 3owever, they arent used in the same way C they arent changed as the program runs. The block parameter, i, while different each time the block is called, doesnt change inside the block. Its meaning is clear and unambiguous while that block is running. 8imilarly, the target9index variable is set once to an intermediate value, not changed once each time around a loop. Terse, simple code that doesnt change values while it is running is the hallmark of functional programming languages like 3askell or Dlo5ure. $hile these languages allow you to write concurrent code without using locks, their chief benefit is that they encourage +Dlo5ure, or even force you +3askell, to write simple, terse code. Dode that asks the computer for what you need, not code that tells the computer what to do. &ut, as weve seen, you dont need to abandon Ruby to write functional code. Update: 8imon ErFger and Gosh Dheek both suggested using drop9while, which gives us an even more readable, functional solution

I also decided to rename the after method to lines9after, based on the comments from TenderHlove and Gohn Eary. I agree with them after would make more sense if I called it as a method on an ob5ect containing the lines +e.g. lines.after,. &ut as a simple function like in this example lines9after is more expressive. Thanks guys"

Learning From Sandi Metz


In her famous book, :ractical 7b5ect.7riented Design in Ruby, 8andi *etI mentions the #sk, Dont Tell policy also, but using slightly different words. $ith her brilliant bicycle examples, 8andy shows us in Dhapter ? of :77DR why we should be Asking for What Instead of Telling How. $hen you send a message to an ob5ect, you should ask it for what you want, not tell it what to do or make assumptions about how it works internally. 8andi shows us how this policy C along with other important design principles C helps us write classes that are more independent and decoupled one from the other. The #sk, Dont Tell policy applies e@ually well to functional programming and ob5ect oriented programming. #t a lower level, it helps us write more terse, functional Ruby

methods. 8tepping back, it can also help us design ob5ect oriented applications that are easier to maintain and extend. Update #2: #pparently Ive +unknowingly, conflated '#sk, Dont Tell( with the 'Tell, Dont #sk,( advice Dave Thomas has been giving us for years to make a different but related point about ob5ect oriented design. Dave explains here Telling, #sking, and the :ower of Gargon. 3e also disagrees with my opinion that the parse9lines example was written in a functional style. :at 8haughnessy

Potrebbero piacerti anche