One of the questions that I have been contemplating as of late is “where does testing fit into the overall organization?” I was given a chance to contemplate this recently because of a chance at SideReel. Our Director of Engineering decided to take on another role at another start-up, so we had a period of transition and I now have a new director. This new director asked me this question directly, and we had an interesting discussion because of it.
Unlike our previous director (a guy I like and respect a lot, so do not take this the wrong way), our new director comes from a strong operations and systems administration background. I say that because he was able to verbally describe something I’ve tried to explain to various organizations for years. Testing is nebulous. There are a lot of things that we do that are, for the most part, invisible to those who chase metrics and measurable hard deliverables. He also appreciates the fact that, like systems administrators, no one really notices you when the systems work well, they only know when things are broken, and then you are public enemy number one. This has been a pet peeve of mine for a number of years, and I’m still trying to come to grips with the best way and approach to raise visibility of testing and what we do. Cost containment is good, but raising the visibility on our value is, I think, ultimately better.
As a Lone Tester, I get both ends of the spectrum. I am often unnoticed (even in this small company) when things are moving along as they should, and I definitely get a center spotlight if something gets missed and makes its way out onto the production site (that’s not a complaint mind you, just a direct observation of fact). Be that as it may, what can we do to help raise that visibility? One of the things I do is right here, this blog. It’s a repository of my ideas and musings, not just for my general readers, but for those I work for, too. If you have a testing blog, don’t just have it be an external resource, but share it with your company and let people know what content would be relevant. I put the URL for the Practicum section for my most recent Performance Review, as well as a link to my book reviews and my articles published. Second, if you have a chance to participate in something where you can share your testing ideas with other teammates (even if they are not testers) take that opportunity. It doesn’t have to be elaborate, but even a quick brown bag on Heuristics or Domain Testing and the reasoning behind why we do them can be very eye-opening for a development team.
While I won’t say that we will be able to change perceptions immediately or that some organizations will really much care either way, there are some simple things we all can do to help raise our visibility and the way that we interact with our teams, and they don’t require us creating reams of documents that no one is going to read. They do require a different way of thinking, and perhaps a little showmanship and salesmanship, but with time and a bit of attention, it will be possible to change perceptions to our advantage.
Wednesday, November 30, 2011
Exercise 30: Else And If: Learn Ruby the Hard Way: Practicum
Yesterday we focused on the if-statement and how it will run certain blocks of code when the conditions are right, and ignore the block of code if the conditions are wrong.
The thing with the if-statement is that, by itself, it is limiting, and it is verbose. If we had to make an if statement for all possibilities, there would be a lot of if statements to traverse. Most of the time, we just want to have a particular condition be taken care of one way, another one taken care of another way, and then if those don't meet the criteria, then do a default action. The if-statement alone won't do that. You need something else to do that and it's called, conveniently enough, "else" :).
What You Should See
$ ruby ex30.rb
We should take the cars.
Maybe we could take the buses.
Alright, let's just take the buses.
$
Extra Credit
Try to guess what elsif and else are doing.
[ elsif is following a secondary branch, and is only followed if the first condition is false, and this second condition is true. If the first condition is false, and the second condition is also false, then it will run the else statement, which will be a default statement to run if the other if and elsif statements are not true. ]
Change the numbers of cars, people, and buses and then trace through each if-statement to see what will be printed.
Try some more complex boolean expressions like cars > people and buses < cars. Above each line write an English description of what the line does.
TESTHEAD's TAKEAWAYS:
The ability to have alternate options and a default help to keep the number of branches to a manageable level. Instead of having lots of if statements that stand alone, collecting them together in an if, elsif and else form would help organize the statements into groups, and the default else statement handles all of the examples that don't match the key areas of interest. With this, we get the ability to really start to make choices and make our programs behave how we want them to.
The thing with the if-statement is that, by itself, it is limiting, and it is verbose. If we had to make an if statement for all possibilities, there would be a lot of if statements to traverse. Most of the time, we just want to have a particular condition be taken care of one way, another one taken care of another way, and then if those don't meet the criteria, then do a default action. The if-statement alone won't do that. You need something else to do that and it's called, conveniently enough, "else" :).
What You Should See
$ ruby ex30.rb
We should take the cars.
Maybe we could take the buses.
Alright, let's just take the buses.
$
Extra Credit
Try to guess what elsif and else are doing.
[ elsif is following a secondary branch, and is only followed if the first condition is false, and this second condition is true. If the first condition is false, and the second condition is also false, then it will run the else statement, which will be a default statement to run if the other if and elsif statements are not true. ]
Change the numbers of cars, people, and buses and then trace through each if-statement to see what will be printed.
Try some more complex boolean expressions like cars > people and buses < cars. Above each line write an English description of what the line does.
TESTHEAD's TAKEAWAYS:
The ability to have alternate options and a default help to keep the number of branches to a manageable level. Instead of having lots of if statements that stand alone, collecting them together in an if, elsif and else form would help organize the statements into groups, and the default else statement handles all of the examples that don't match the key areas of interest. With this, we get the ability to really start to make choices and make our programs behave how we want them to.
Tuesday, November 29, 2011
Cleaning Up Dirty Tests
One of the things I've been trying to master is the ability to make my tests stand on their own and not have dependencies on other tests. This seems like a standard thing to do, and we often think we are doing a good job on this front, but how well are we doing it, really?
As I was looking at some recent tests with one of my co-workers, I was really happy that they were passing as frequently as they did, but my elation turned to frustration the next build when a lot of tests were failing for me. The more maddening factor was that the tests that were failing would all pass if I reran them.
Wait, a little explanation is in order here. In my current work environment, I use Cucumber along with Rspec and Ruby. I also use a Rakefile to handle a number of tagged scenarios and to perform other basic maintenance things. When I run a test the first time, I get an output that tells me the failed scenarios. As a way of doing retesting, I tee the output to a rerun file and then I run a shell script that turns the failed scenarios into individual rake cases.
In almost all of my test runs, if I ran a suite of 50 scenarios, I'd get about 10 failures (pretty high). If I reran those 10 failure cases, on the second try I would get anywhere from 8 - 10 of them to pass. More often than not the full 10 would pass on a second run. As I started to investigate this, I realized that, during the rerun, each test was being run separately, which meant each test would open a browser, test the functionality for that scenario and then close the browser. Each test stood alone, so each test would pass.
Armed with this, I decided to borrow a trick I'd read about in a forum... if you want to see if your tests actually are running as designed, or if there is an unintended ordered precedence to their success rate, the best way to check is to take the tests as they currently exist, and swap their order in how they are run (rename feature files, reorder scenarios, change the mames of folders, etc.). The results from doing this were very telling. I won't go into specifics, but I did discover that certain tests, if all run in the same browser session, would get unusual artifacts to hang around because of the dedicated session. these artifacts were related to things like Session ID's, cookies, and other items our site depends on to operate seamlessly. When I ran each of these tests independently (with their own browser sessions) the issues disappeared. That's great, but it's impractical. The time expense of spawning and killing a browser instance with every test case is just too high. Still, I learned a great deal about the way my scripts were constructed and the setup and teardown details that I had (or didn't have) in my scenarios.
This is a handy little trick, and I encourage you to use it for your tests, too. If there's a way to influence which tests get run and you can manipulate the order, you may learn a lot more about your tests than you thought you knew :).
As I was looking at some recent tests with one of my co-workers, I was really happy that they were passing as frequently as they did, but my elation turned to frustration the next build when a lot of tests were failing for me. The more maddening factor was that the tests that were failing would all pass if I reran them.
Wait, a little explanation is in order here. In my current work environment, I use Cucumber along with Rspec and Ruby. I also use a Rakefile to handle a number of tagged scenarios and to perform other basic maintenance things. When I run a test the first time, I get an output that tells me the failed scenarios. As a way of doing retesting, I tee the output to a rerun file and then I run a shell script that turns the failed scenarios into individual rake cases.
In almost all of my test runs, if I ran a suite of 50 scenarios, I'd get about 10 failures (pretty high). If I reran those 10 failure cases, on the second try I would get anywhere from 8 - 10 of them to pass. More often than not the full 10 would pass on a second run. As I started to investigate this, I realized that, during the rerun, each test was being run separately, which meant each test would open a browser, test the functionality for that scenario and then close the browser. Each test stood alone, so each test would pass.
Armed with this, I decided to borrow a trick I'd read about in a forum... if you want to see if your tests actually are running as designed, or if there is an unintended ordered precedence to their success rate, the best way to check is to take the tests as they currently exist, and swap their order in how they are run (rename feature files, reorder scenarios, change the mames of folders, etc.). The results from doing this were very telling. I won't go into specifics, but I did discover that certain tests, if all run in the same browser session, would get unusual artifacts to hang around because of the dedicated session. these artifacts were related to things like Session ID's, cookies, and other items our site depends on to operate seamlessly. When I ran each of these tests independently (with their own browser sessions) the issues disappeared. That's great, but it's impractical. The time expense of spawning and killing a browser instance with every test case is just too high. Still, I learned a great deal about the way my scripts were constructed and the setup and teardown details that I had (or didn't have) in my scenarios.
This is a handy little trick, and I encourage you to use it for your tests, too. If there's a way to influence which tests get run and you can manipulate the order, you may learn a lot more about your tests than you thought you knew :).
Exercise 29: What If: Learn Ruby the Hard Way: Practicum
Yesterday we focused on being able to determine if statements are True or False (otherwise known as "boolean logic"). The value of boolean logic, or the need to make a choice, comes into play when we start putting conditional branches into our programs. A conditional branch is where one set of code blocks are exercised and run if a condition meets a criteria. If it doesn't, then it ignores that block of code.
Today we get to see the first conditional statement, and that's the "if" statement. "if" gives us a way to exercise a block of code when the condition associated with the "if" statement is true. Should the statement be false, then the code isn't executed. Simple as that.
So here's the example we're given to work with:
And here's what I did with it.
What You Should See
$ ruby ex29.rb
Too many cats! The world is doomed!
The world is dry!
People are greater than or equal to dogs.
People are less than or equal to dogs.
People are dogs.
$
Extra Credit
In this extra credit, try to guess what you think the if-statement is and what it does. Try to answer these questions in your own words before moving onto the next exercise:
1. What do you think the if does to the code under it?
[ The if statement, as explained in my description above, is evaluating the statement associated with it and as it is currently written, testing to see if it is true. If it is true, then it executes the code that is indented within the if statement and then exits the if statement (that's what the "end" represents). If the statement is fale, then the if statement does nothing, it passes control to the next line of code. ]
2. Can you put other boolean expressions from Ex. 27 in the if-statement? Try it.
[ Yes, any boolean expression can be used. For example, we could say, instead of "people < cats", we could also say "not(people > cats)" or any of the other examples reworded for the program. ]
3. What happens if you change the initial variables for people, cats, and dogs?
[ Since each of the answers has to do with the equality or inequality of any of the variables, changing the values of one of them could result in different if statements being run and, subsequently, different test appearing in the program when it is run. ]
TESTHEAD's TAKEAWAYS:
This is the beginning of "choice" in our programs, so to speak. the if-statement lets us run certain steps when the condition is true, and not run certain steps when the condition is not true. The if-statement is also our first "testing" statement. Up until now, our programs just did what we directed them to and the programs executed every line one after the other. With the if-statement, we're testing to make sure the parameters are right to run a statement. If they aren't we don't. Fairly basic, but pretty powerful, too :).
Today we get to see the first conditional statement, and that's the "if" statement. "if" gives us a way to exercise a block of code when the condition associated with the "if" statement is true. Should the statement be false, then the code isn't executed. Simple as that.
So here's the example we're given to work with:
And here's what I did with it.
What You Should See
$ ruby ex29.rb
Too many cats! The world is doomed!
The world is dry!
People are greater than or equal to dogs.
People are less than or equal to dogs.
People are dogs.
$
Extra Credit
In this extra credit, try to guess what you think the if-statement is and what it does. Try to answer these questions in your own words before moving onto the next exercise:
1. What do you think the if does to the code under it?
[ The if statement, as explained in my description above, is evaluating the statement associated with it and as it is currently written, testing to see if it is true. If it is true, then it executes the code that is indented within the if statement and then exits the if statement (that's what the "end" represents). If the statement is fale, then the if statement does nothing, it passes control to the next line of code. ]
2. Can you put other boolean expressions from Ex. 27 in the if-statement? Try it.
[ Yes, any boolean expression can be used. For example, we could say, instead of "people < cats", we could also say "not(people > cats)" or any of the other examples reworded for the program. ]
3. What happens if you change the initial variables for people, cats, and dogs?
[ Since each of the answers has to do with the equality or inequality of any of the variables, changing the values of one of them could result in different if statements being run and, subsequently, different test appearing in the program when it is run. ]
TESTHEAD's TAKEAWAYS:
This is the beginning of "choice" in our programs, so to speak. the if-statement lets us run certain steps when the condition is true, and not run certain steps when the condition is not true. The if-statement is also our first "testing" statement. Up until now, our programs just did what we directed them to and the programs executed every line one after the other. With the if-statement, we're testing to make sure the parameters are right to run a statement. If they aren't we don't. Fairly basic, but pretty powerful, too :).
Monday, November 28, 2011
Bringing Control To Chaos
Over the Thanksgiving Weekend, I had a chance to go down and visit my sisters and their husbands, eat a lot of food, spend some fun quality time with my immediate and extended family, and due to the proximity of where my sister lives (i.e. Huntington Beach), we made it a point over the couple of days while we were down there to check out a number of the beaches.
While it's a bit chilly in my home climate (and yes, I know that when I say a "bit chilly" where I live, I hear some of you snickering vigorously and saying "dude, you have no idea what chilly is!"), it was almost 80 degrees the day after Thanksgiving on the beaches. As we walked about and checked several of the beaches out, I took some time to explain a bit about how surfing works to my daughters. Granted, I was never much of a surfer; I only went a handful of times up in Northern California, but I knew enough to explain how to watch and track waves, and explain to my daughters how a set worked, and the process of "catching a wave" at the right point.
Several of these beaches had municipal piers on them that people could walk down, shop on, tie boats to, fish on, and generally avoid if you are a surfer. They also offered the ability to see the horizon from a different vantage than if you were just on the shoreline or in the water. On some of the larger beaches, it was open water, with a variety of wave patterns and sizes and durations, in these areas, it was harder to predict when a "good wave" would come in.
By contrast, at the much smaller "Seal Beach", we noticed that the waves were much more regular, but they were also typically smaller. I had a hard time understanding why... until I walked out on the pier a ways. It was there that I was able to see the parallel "breakwaters" that were on the far sides of the beach, and extended out about 500 yards or so. These walls of stone and concrete had the ability to "tune" the waves. The waves weren't really big, but they were consistent, and if you wanted to ride smaller waves, then this looked to be an ideal place to do it. It also looked to afford more opportunities to catch numerous smaller waves.
I thought about that in the quest to focus on testing environments, and how, very often, we try to make sense out of really complex systems and try to simplify things in ways that are ultimately ineffective. We take on too much, we cover too broad an area, we don't give ourselves enough time to figure out the rhyme or reason of the system under test. In short, we don't devise proper "breakwaters" for our testing areas. Given a little more time and effort, though, we certainly could chop our environments into domains that are easier to quantify, track and focus our attention. It sometimes takes a lot of work to do this, but by doing so, we can actually get control of situations, and by getting that control, we can more regularly and with better focus look for issues that we might otherwise miss in the broader chaos.
Exercise 28: Boolean Practice: Learn Ruby the Hard Way: Practicum
First, it's good to be back and getting into the groove of writing and practicing again. Since I left for the Thanksgiving Holiday weekend, I decided it would be easier to jut call it a break until Monday morning so that I could focus on travel, spending time with my family and enjoying Southern California with my wife, kids, siblings and parents. I'm back home now and it's time to get back into the swing of things again.
So at this stage of the game we're going to do some practice and focus on "boolean logic" (which is where we say a statement is true or false based on the criteria, and we nly really care about the truth or falseness of the statement.
In this exercise we focus on trying to figure out whether or not the statements we enter are true or false. IRB will be our truth telling oracle for this experiment, and we shall see if our instincts are correct to tell if an expresion is true or false.
So at this stage of the game we're going to do some practice and focus on "boolean logic" (which is where we say a statement is true or false based on the criteria, and we nly really care about the truth or falseness of the statement.
In this exercise we focus on trying to figure out whether or not the statements we enter are true or false. IRB will be our truth telling oracle for this experiment, and we shall see if our instincts are correct to tell if an expresion is true or false.
The Statements:
These statements sart out simple and get more complex as we move along through the examples. Some of the answers are obvious, some take a little more thought.
When statements get to be a bit more complicated, Zed recommends the following approach.
- Find equality test (== or !=) and replace it with its truth.
- Find each and/or inside a parenthesis and solve those first.
- Find each not and invert it.
- Find any remaining and/or and solve it.
- When you are done you should have true or false.
What You Should See
After you have tried to guess at these, this is what your session with IRB might look like:
$ irb
ruby-1.9.2-p180 :001 > true and true
=> true
ruby-1.9.2-p180 :002 > 1 == 1 and 2 == 2
=> true
Extra Credit
There are a lot of operators in Ruby similar to != and ==. Try to find out as many "equality operators" as you can. They should be like: < or <=.
[
<= - less than or equal to
< > - not
>= - greater than or equal to
<=> - looks to be greater than, less than or equal to (an allcharacter match?)
== - equal to
=== - equal to
!= - not equal to
=~ - ???
!~ - ???
]
Write out the names of each of these equality operators. For example, I call != "not equal".
Play with IRB by typing out new boolean operators, and before you hit enter try to shout out what it is. Do not think about it, just the first thing that comes to mind. Write it down then hit enter, and keep track of how many you get right and wrong.
[
1 <= 1 or 2 != 1
true and 1 >= 1
false and 0 != 0
true or 1 <=> 1
"test" == "testing"
1 != 0 and 2 === 1
"test" != "testing"
"test" =~ 1
not (true and false)
not (1 !~ 1 and 0 !~ 1)
]
TESTHEAD's TAKEAWAYS:
So that was interesting! I have to admit that at first I was wondering what the deal was and why so many variations would be needed, but it makes sense, especially if you think about a search parameter or a database query, you want to return a particular value if the statement evaluates to true, and you want to return another one if a statement evaluates to false. A lot of these won't make sense immediately, and that's OK, just keep practicing with them and sure enough, you start to see why they evaluiate to true or false over time.
Thursday, November 24, 2011
Exercise 27: Memorizing Logic: Learn Ruby the Hard Way: Practicum
We have reached the end of the "purely linear" programming lessons. What I mean by purely linear is everything we have learned up to now has been programmed and run in a mostly straight line. Sure, we bounce into functions, and we manipulate values, but the scripts themselves have been orderly, one sided affairs where the lines in the script are just followed and each step run. There's no choice in the matter, each step gets executed.
Real programs don't work that way, though (well, generally they don't). Most programs require users to make decisions and choices, or perform based on conditions. To do that, we have to understand how the computer makes choices. In short, we need to learn and understand "logic".
Most of the logic that we will learn is pretty straightforward. There's some really deep stuff that we could get into, but suffice it to say that for most of what an everyday Ruby programmer will do, there are just a few things to remember. There's also lots of ways of implementing those few things.
So our first step is to, yep, memorize some logic, or "truth" tables. Zed recommends doing this exercise for an entire week. I don't have the luxury of doing that, so I will dedicate some extra time in this day to focus on it. That may be a hindrance later, or I will just have to keep with the drill and see what happens (it's the reason I keep my "all_code" file, too :) ). So let's do this...
From Zed and Rob:
Here’s a tip on how to memorize something without going insane: Do a tiny bit at a time throughout the day and mark down what you need to work on most. Do not try to sit down for two hours straight and memorize these tables. This won’t work. Your brain will really only retain whatever you studied in the first 15 or 30 minutes anyway. Instead, what you should do is create a bunch of index cards with each column on the left on one side (True or False) and the column on the right on the back. You should then pull them out, see the “True or False” and be able to immediately say “True! (or False!)” Keep practicing until you can do this.
Once you can do that, start writing out your own truth tables each night into a notebook. Do not just copy them. Try to do them from memory, and when you get stuck glance quickly at the ones I have here to refresh your memory. Doing this will train your brain to remember the whole table.
Do not spend more than one week on this, because you will be applying it as you go.
So here are the terms Ruby uses to determine if a statement of a condition is "TRUE" or "FALSE". Logic to a computer is basically a series of "gates", and based on whether or not a statement evaluates to TRUE or FALSE determines where a signal will flow. The terms are:
The Truth Table
We will now use these characters to make the truth tables you need to memorize.
TESTHEAD's TAKEAWAYS:
This is interesting, and it requires me to take a step back and think about what I'm actually looking at. Some of this is counter-intuitive, but if we stop to think about it for a bit, these statements make sense. The goal, of course is so that we can recognize these true/false values when we want to cause our instructions to go one place in a program, or got to another. Logic and the confirmation of a situation being true or false is the key to getting programs to go from one point to another and to return if we want it to. I'm going to make a guess at this point that decisions and decision trees are going to be in our near term future :).
Real programs don't work that way, though (well, generally they don't). Most programs require users to make decisions and choices, or perform based on conditions. To do that, we have to understand how the computer makes choices. In short, we need to learn and understand "logic".
Most of the logic that we will learn is pretty straightforward. There's some really deep stuff that we could get into, but suffice it to say that for most of what an everyday Ruby programmer will do, there are just a few things to remember. There's also lots of ways of implementing those few things.
So our first step is to, yep, memorize some logic, or "truth" tables. Zed recommends doing this exercise for an entire week. I don't have the luxury of doing that, so I will dedicate some extra time in this day to focus on it. That may be a hindrance later, or I will just have to keep with the drill and see what happens (it's the reason I keep my "all_code" file, too :) ). So let's do this...
From Zed and Rob:
Here’s a tip on how to memorize something without going insane: Do a tiny bit at a time throughout the day and mark down what you need to work on most. Do not try to sit down for two hours straight and memorize these tables. This won’t work. Your brain will really only retain whatever you studied in the first 15 or 30 minutes anyway. Instead, what you should do is create a bunch of index cards with each column on the left on one side (True or False) and the column on the right on the back. You should then pull them out, see the “True or False” and be able to immediately say “True! (or False!)” Keep practicing until you can do this.
Once you can do that, start writing out your own truth tables each night into a notebook. Do not just copy them. Try to do them from memory, and when you get stuck glance quickly at the ones I have here to refresh your memory. Doing this will train your brain to remember the whole table.
Do not spend more than one week on this, because you will be applying it as you go.
So here are the terms Ruby uses to determine if a statement of a condition is "TRUE" or "FALSE". Logic to a computer is basically a series of "gates", and based on whether or not a statement evaluates to TRUE or FALSE determines where a signal will flow. The terms are:
- and
- or
- not
- != (not equal)
- == (equal)
- >= (greater-than-equal)
- <= (less-than-equal)
- true
- false
The Truth Table
We will now use these characters to make the truth tables you need to memorize.
TESTHEAD's TAKEAWAYS:
This is interesting, and it requires me to take a step back and think about what I'm actually looking at. Some of this is counter-intuitive, but if we stop to think about it for a bit, these statements make sense. The goal, of course is so that we can recognize these true/false values when we want to cause our instructions to go one place in a program, or got to another. Logic and the confirmation of a situation being true or false is the key to getting programs to go from one point to another and to return if we want it to. I'm going to make a guess at this point that decisions and decision trees are going to be in our near term future :).
Wednesday, November 23, 2011
Ready to Double Down?
I had to laugh at today's "Two Leaf Clover". I've been in situations a few times (well, OK, not the half dead in the desert, but finding myself wondering if I were at the halfway point on a road to nowhere, and if it made sense to cut my losses and turn back or forge ahead).
Many times when we "go exploring" in an application, we find information at every turn. That information may be good, it may be bad, but it's still information that informs our decisions. The challenge comes when were not sure if or how we are going to proceed. There have been many times when my "gut" told me I should continue down an avenue of action, only to find out that it didn't give me the information I was hoping for. Another way to say this is "be careful when you start climbing a ladder; you don't want to reach the top to find out you put it against the wrong wall".
Is it possible to hedge your bets and determine if you are going down a rabbit hole, or is it inevitable you will have to power through whatever you are doing to see it through to the end? The truth is, there's a time to have the discussion about going down a rabbit hole, and that's at the beginning of the negotiations. This is where doing good estimates is important, and having the ability to speak to how much time and how much money we will have to spent to accomplish a goal. Making adjustments later is often a lot harder, and it's really a challenge when you get close to a release and you realize that you have a lot more that has to be done, and not enough time to actually do it. We often double down at this stage and we push through to do whatever we have to do, often making ourselves exhausted, run down, and irritable... and very rarely is the result worth it. We don't get better testing done when we double down. In many cases, we actually do worse because we are not sharp and we are stressed out trying to cover our bases.
So as you might guess, my answer for "are you ready to double down?" I hope the answer is "no". I don't want you to double down on a bad bet. Instead, I want to encourage you to focus on covering the highest risk areas and getting the best information you can in the time you can. By doing that, you can let the product managers and the other members of the team with a vested interest make a decision towards what needs to be done. Testers going on death marches aren't going to get us good information, unless the information we want is to find out how to learn the most effective way burn out our testers. If you'll forgive me, I don't really want to know how to do that.
Many times when we "go exploring" in an application, we find information at every turn. That information may be good, it may be bad, but it's still information that informs our decisions. The challenge comes when were not sure if or how we are going to proceed. There have been many times when my "gut" told me I should continue down an avenue of action, only to find out that it didn't give me the information I was hoping for. Another way to say this is "be careful when you start climbing a ladder; you don't want to reach the top to find out you put it against the wrong wall".
Is it possible to hedge your bets and determine if you are going down a rabbit hole, or is it inevitable you will have to power through whatever you are doing to see it through to the end? The truth is, there's a time to have the discussion about going down a rabbit hole, and that's at the beginning of the negotiations. This is where doing good estimates is important, and having the ability to speak to how much time and how much money we will have to spent to accomplish a goal. Making adjustments later is often a lot harder, and it's really a challenge when you get close to a release and you realize that you have a lot more that has to be done, and not enough time to actually do it. We often double down at this stage and we push through to do whatever we have to do, often making ourselves exhausted, run down, and irritable... and very rarely is the result worth it. We don't get better testing done when we double down. In many cases, we actually do worse because we are not sharp and we are stressed out trying to cover our bases.
So as you might guess, my answer for "are you ready to double down?" I hope the answer is "no". I don't want you to double down on a bad bet. Instead, I want to encourage you to focus on covering the highest risk areas and getting the best information you can in the time you can. By doing that, you can let the product managers and the other members of the team with a vested interest make a decision towards what needs to be done. Testers going on death marches aren't going to get us good information, unless the information we want is to find out how to learn the most effective way burn out our testers. If you'll forgive me, I don't really want to know how to do that.
Exercise 26: Congratulations, Take A Test!: Learn Ruby the Hard Way: Practicum
So this marks the mid-way point for the book. 26 chapters, and I suppose, ideally, six months worth of programming. I'm going for broke and doing this in a much shorter time frame (partly because I have to, I need to be finished with Exercise 30 by the end of November. Remember when I first started talking about doing this? I made the ultimate in "bold boasts"; I tied my completion of this book to my annual performance review. What did we agree to? My completing up to Exercises 30 by the end of November and my completing the entire book by the end of January. Yes, it's actually spelled out exactly like that in my review goals... and you thought blogging about it was pressure (LOL!).
I have mixed emotions about posting these entries. In part, I feel like I might be "cheating" others by giving a blow by blow of all this; they may just use my answers and say "Oh, cool, that's how that works", and miss the whole point of going through all of these steps and actually learning the material. Still, my point with the Practicum series is to show the techniques, critique the exercises, and give my own take on these materials as though I were teaching them to someone else. Thus, I guess I will need to leave it up to the reader as to how they want to work with this, and if they want to actually do things "The Hard Way" or cheat and do what they think is an "easy" approach to learning this stuff. "Thems will be that thems will be" (or something like that). Also, I try my best to put the code and my changes into pictures, not into text that can be copied and pasted, so I guess if they want my answer, they will have to type it out anyway :).
So for this section, we are asked to work through a quiz. the quiz is to fix someone else's code and make it work. It's common to deal with other programmers' code. It's also common to deal with their insularity and their belief that their code is "just fine".
Exercises 24 and 25 have been cobbled together and errors have been introduced. Our job is to find the errors, fix the mistakes, and get the program to run correctly... without looking at the previous exercises to do it.
The "broken" file is here.
http://ruby.learncodethehardway.org/book/exercise26.txt
My "fixes" are here.
TESTHEAD's TAKEAWAYS:
What this shows us is that many of the errors we might put into our own code may seem blindingly obvious, but often they are subtle little misses, typos, or otherwise small things that can be infuriatingly difficult to find. There's still one area of the test I'm not sure I did right, and that has to do with the output of the "sorted sentence". I get that it's doing what it's supposed to do, but the repeated word and out of order with the pop is what has me scratching my head at the moment. If anyone else can tell me what I'm doing that's totally dumb, hey, I'll welcome the comments :).
Tuesday, November 22, 2011
Exercise 25: Even More Practice: Learn Ruby the Hard Way: Practicum
Here's a bit of a change-up. We are used to running functions in files and giving them values from a command line or from STDIN (i.e. the keyboard) but we can also interactively pull them into IRB and, well, interact with them. This lesson also introduces us to the idea of a module and the actual code term "module". Have you added that to your list of terms yet :)?
Let's see what happens when we import a module into the Ruby interpreter and run the functions ourselves.
And here's what it looks like in IRB
Let's break this down line by line to make sure you know what's going on:
Line 2 you require your ./ex25.rb Ruby file, just like other requires you have done. Notice you do not need to put the .rb at the end to require it. When you do this you make a module that has all your functions in it to use.
Line 4 you made a sentence to work with.
Line 6 you use the Ex25 module and call your first function Ex25.break_words. The . (dot, period) symbol is how you tell Ruby, "Hey, inside Ex25 there's a function called break_words and I want to run it."
Line 8 we do the same thing with Ex25.sort_words to get a sorted sentence.
Lines 10-15 we use Ex25.print_first_word and Ex25.print_last_word to get the first and last word printed out.
Line 16 is interesting. I made a mistake and typed the words variable as "wrods" so Ruby gave me an error on Lines 17-18.
Lines 19-20 is where we print the modified words list. Notice that since we printed the first and last one, those words are now missing.
The remaining lines are for you to figure out and analyze in the extra credit.
Extra Credit
Take the remaining lines of the WYSS output and figure out what they are doing. Make sure you understand how you are running your functions in the Ex25 module.
[In my file, Ex25.sort_sentence(sentence) prints out the full sentence and lists the words in alphabetical order. Ex.25.print_first_and_last(sentence) does what it says it prints the sorted first word and the last word from the original, unmodified sentence. Ex25.print_first_and_last_sorted(sentence) takes the sorted words and prints out the first and lat words that would be in alphabetical order.]
The reason we put our functions in a module is so they have their own namespace. If someone else writes a function called break_words, we won't collide. However, if typing Ex25. is annoying, you can type include Ex25 which is like saying, "Include everything from the Ex25 module in my current module."
[Your mileage may vary on this one, but when I type "include Ex25" I get an output from the interpreter saying object, but then if I try to type function calls that don't include the Ex25, I get errors:
Actually, I don't mind this, I like the idea of calling a module and having the module be defined and have the leading name. It makes it easier for me to see the connection.]
Try breaking your file and see what it looks like in Ruby when you use it. You will have to quit IRB with CTRL-D to be able to reload it.
[Typos are fun, and changing out statements can lead to interesting errors. I'd still like to know why "include Ex25" isn't behaving like it says it should, but again, for me personally, I don't see it as a huge deal breaker.]
TESTHEAD's TAKEAWAYS:
This is pretty cool. I like the fact that we can define modules in separate files, call them, and have those modules keep separate name space. While it might get confusing, I could imagine a need for very similar functions but those that work slightly different. rather than having one file with multiple descriptions or branching paths, having two separate modules to call and using them with the name designator is a good way to tell which version you are using.
Let's see what happens when we import a module into the Ruby interpreter and run the functions ourselves.
And here's what it looks like in IRB
Let's break this down line by line to make sure you know what's going on:
Line 2 you require your ./ex25.rb Ruby file, just like other requires you have done. Notice you do not need to put the .rb at the end to require it. When you do this you make a module that has all your functions in it to use.
Line 4 you made a sentence to work with.
Line 6 you use the Ex25 module and call your first function Ex25.break_words. The . (dot, period) symbol is how you tell Ruby, "Hey, inside Ex25 there's a function called break_words and I want to run it."
Line 8 we do the same thing with Ex25.sort_words to get a sorted sentence.
Lines 10-15 we use Ex25.print_first_word and Ex25.print_last_word to get the first and last word printed out.
Line 16 is interesting. I made a mistake and typed the words variable as "wrods" so Ruby gave me an error on Lines 17-18.
Lines 19-20 is where we print the modified words list. Notice that since we printed the first and last one, those words are now missing.
The remaining lines are for you to figure out and analyze in the extra credit.
Extra Credit
Take the remaining lines of the WYSS output and figure out what they are doing. Make sure you understand how you are running your functions in the Ex25 module.
[In my file, Ex25.sort_sentence(sentence) prints out the full sentence and lists the words in alphabetical order. Ex.25.print_first_and_last(sentence) does what it says it prints the sorted first word and the last word from the original, unmodified sentence. Ex25.print_first_and_last_sorted(sentence) takes the sorted words and prints out the first and lat words that would be in alphabetical order.]
The reason we put our functions in a module is so they have their own namespace. If someone else writes a function called break_words, we won't collide. However, if typing Ex25. is annoying, you can type include Ex25 which is like saying, "Include everything from the Ex25 module in my current module."
[Your mileage may vary on this one, but when I type "include Ex25" I get an output from the interpreter saying object, but then if I try to type function calls that don't include the Ex25, I get errors:
Actually, I don't mind this, I like the idea of calling a module and having the module be defined and have the leading name. It makes it easier for me to see the connection.]
Try breaking your file and see what it looks like in Ruby when you use it. You will have to quit IRB with CTRL-D to be able to reload it.
[Typos are fun, and changing out statements can lead to interesting errors. I'd still like to know why "include Ex25" isn't behaving like it says it should, but again, for me personally, I don't see it as a huge deal breaker.]
TESTHEAD's TAKEAWAYS:
This is pretty cool. I like the fact that we can define modules in separate files, call them, and have those modules keep separate name space. While it might get confusing, I could imagine a need for very similar functions but those that work slightly different. rather than having one file with multiple descriptions or branching paths, having two separate modules to call and using them with the name designator is a good way to tell which version you are using.
Monday, November 21, 2011
The Pen is Mightier Than... Well, a Lot of Things
Whew! Looks like rubinating myself (wait, that sounds bad)... spending so much time with Ruby is leaving me less time to focus on writing other stuff. That's one neat thing about writing, it's a truly mono-task activity. You can't really do something else while you are doing it (yes, you can listen to music, I suppose, or you can have the TV on in the background, or you could conceivably hold a halting conversation). In many ways, I use this fact to my advantage. If there's something I shouldn't be doing (fill in the blank on what that may be) I often pull out a notepad to help me break whatever it is I'm doing.
This might seem a bit weird, but go with me for a minute. What's one of the first things so called "experts" do when they encourage someone to get in touch with a bad habit or practice? To pull out a pen and paper and write it down (and in this case, I'm happy to substitute a laptop, tablet or smart phone for pen and paper, whatever makes you happy).
There's lots of other things I could mention, but you get the point.
Why is the pen and paper (or its electronic substitute) so powerful? What makes this process so effective? It forces us to step outside of our automatic actions. We have to take the time to pull out the pen and paper and jot down what it is we are about to divert to. When we do this, we get clarity on our actual actions. We see ourselves for what we really are, and what we are about to do. When I am on a diet, I often go back to the "food log" and when I do, the very act of pulling out the pen and paper to write down what I'm about to eat stops me in my tracks. If I actually need it, or it's genuinely time to eat, then great, I'll do so. If it's not, and I'm just mindlessly snacking, I'll stop mid pull, think if it's worth my time to write this all down, and usually decide "naah, I'll pass". The pen defeats the wayward brain :).
Seriously, if you find that you want to conquer an area that is currently getting away from you, or you want to exercise a little more control or "will power" give the "power of the will" the "power of the pen". Let me know how it works for you :).
This might seem a bit weird, but go with me for a minute. What's one of the first things so called "experts" do when they encourage someone to get in touch with a bad habit or practice? To pull out a pen and paper and write it down (and in this case, I'm happy to substitute a laptop, tablet or smart phone for pen and paper, whatever makes you happy).
- If you want to lose weight, the first recommendation is that you write down everything you eat for a week. Amount, frequency, caloric content, etc., it's all game and the more detail you record the better. Have you ever asked why? It's because you really get a feel for what you actually eat. Not what you *think* you eat, but what you really and truly eat. The paper won't lie if you are being totally honest with what you write down.
- Do you want to exercise more? then take a pen and paper and write down all of your current physical activity. If you walk, track the time and the mileage. IF you lift weights, track the poundage and repetition and exercises you do, as well as the time you take. Do aerobic sessions, yoga, plates, biking, swimming, whatever exercise thrills you? Write down what you do. Be specific. Capture it all.
- Do you want to really see what you do with your time? Keep a running log in a text editor (or on a pad of paper, or use a tool like rescue time if you want to see what you are doing online).
- Are you having trouble sleeping? Start keeping a sleep journal and track your to bed's and your to rise's, and everything that happens in between.
There's lots of other things I could mention, but you get the point.
Why is the pen and paper (or its electronic substitute) so powerful? What makes this process so effective? It forces us to step outside of our automatic actions. We have to take the time to pull out the pen and paper and jot down what it is we are about to divert to. When we do this, we get clarity on our actual actions. We see ourselves for what we really are, and what we are about to do. When I am on a diet, I often go back to the "food log" and when I do, the very act of pulling out the pen and paper to write down what I'm about to eat stops me in my tracks. If I actually need it, or it's genuinely time to eat, then great, I'll do so. If it's not, and I'm just mindlessly snacking, I'll stop mid pull, think if it's worth my time to write this all down, and usually decide "naah, I'll pass". The pen defeats the wayward brain :).
Seriously, if you find that you want to conquer an area that is currently getting away from you, or you want to exercise a little more control or "will power" give the "power of the will" the "power of the pen". Let me know how it works for you :).
Exercise 24: More Practice: Learn Ruby the Hard Way: Practicum
At this stage, we are actively engaged in and focusing on getting the core competency down. Much of what we have done at this point has been really basic. I understand that. It's usually about now that, after the basics of the language are presented and looping and case statements are demonstrated, that the beginner books stop and the user is then thrown into the deep end and trying to make sense of real projects. In this case, again, there's a benefit to focusing on the language directly and hammering out the code by typing everything in as it's seen (i.e. doing it the hard way). Do them, get them exactly right, and do your checks. Oh, and it looks like my idea to "copy everything into one file" as a practice was unnecessary. Looks like we'll be doing that here :).
What You Should See
$ ruby ex24.rb
Let's practice everything.
You'd need to know 'bout escapes with \ that do
newlines and tabs.
--------------
The lovely world
with logic so firmly planted
cannot discern
the needs of love
nor comprehend passion from intuition
and requires an explanation
where there is none.
--------------
This should be five: 5
With a starting point of: 10000
We'd have 5000000 beans, 5000 jars, and 50 crates.
We can also do that this way:
We'd have 500000 beans, 500 jars, and 5 crates.
$
Extra Credit
Make sure to do your checks: read it backwards, read it out loud, put comments above confusing parts.
[The one thing I found helpful was the return value for the function being spelled out. Again, I'm used to seeing the return values for functions spelled ut, so seeing the specific return value I felt was a positive. It also helps to make the section where the function is used to show where and how the string values are printed out more easy to understand. Otherwise, I didn't feel I needed to comment anything in the file. It feels pretty straightforward.]
Break the file on purpose, then run it to see what kinds of errors you get. Make sure you can fix it.
[Changed names and added misspellings to the file to see what the outcome would be (changing variable names so that they are unreferenced). The problem with this approach is that I understand what I'm breaking. It's one thing to break something on purpose, see the error and then fix it. It's another thing entirely when you inadvertently break something, and then rack your brain to try to figure out why it's not working. Still, seeing the breaks you create and seeing the error messages can help you understand what and why that may be happening can help you understand what might be causing it.]
TESTHEAD's TAKEAWAYS:
It's important to keep practicing each day and to use the skills that you are developing and do a bit of poking and refactoring each day. I think this will work well with the chart of values we created a couple of days ago. Things we actively do will be better remembered than things we do once and then forget.
What You Should See
$ ruby ex24.rb
Let's practice everything.
You'd need to know 'bout escapes with \ that do
newlines and tabs.
--------------
The lovely world
with logic so firmly planted
cannot discern
the needs of love
nor comprehend passion from intuition
and requires an explanation
where there is none.
--------------
This should be five: 5
With a starting point of: 10000
We'd have 5000000 beans, 5000 jars, and 50 crates.
We can also do that this way:
We'd have 500000 beans, 500 jars, and 5 crates.
$
Extra Credit
Make sure to do your checks: read it backwards, read it out loud, put comments above confusing parts.
[The one thing I found helpful was the return value for the function being spelled out. Again, I'm used to seeing the return values for functions spelled ut, so seeing the specific return value I felt was a positive. It also helps to make the section where the function is used to show where and how the string values are printed out more easy to understand. Otherwise, I didn't feel I needed to comment anything in the file. It feels pretty straightforward.]
Break the file on purpose, then run it to see what kinds of errors you get. Make sure you can fix it.
[Changed names and added misspellings to the file to see what the outcome would be (changing variable names so that they are unreferenced). The problem with this approach is that I understand what I'm breaking. It's one thing to break something on purpose, see the error and then fix it. It's another thing entirely when you inadvertently break something, and then rack your brain to try to figure out why it's not working. Still, seeing the breaks you create and seeing the error messages can help you understand what and why that may be happening can help you understand what might be causing it.]
TESTHEAD's TAKEAWAYS:
It's important to keep practicing each day and to use the skills that you are developing and do a bit of poking and refactoring each day. I think this will work well with the chart of values we created a couple of days ago. Things we actively do will be better remembered than things we do once and then forget.
Saturday, November 19, 2011
Exercise 22: What Do You Know So Far?: Learn Ruby the Hard Way: Practicum
This is kinda' funny. Just as I finished up the Extra Credit project for yesterday, I was thinking to myself "self, you should go through and make a program, a somewhat longer one, that actually uses everything you've learned up until now, and perhaps you should use this program as a longer term "memory repository" so that you can work with it, refactor it, toy some more with it each day so taht it ultimately becomes a sum of the knowledge of your Ruby experience". OK, seems like a simple enough thing to do... why it is considered funny?
Because it seems Zed is of a similar mind, and at this very same point, too (BTW, I am deliberately doing these exercises in order, and I am wherever possible "not reading ahead". This way, I can aproach the exercise fresh, without pre-conceived ideas, and you, dear reader, get to be the beneficiary of my Guinea piggery :) ).
In this Exercise (and the next one), there's no code to learn or pratice with. Instead, we'l be taking a look back at the things we've aleady learned (and it's actully quite a bit, if I do say so myself).
First, go back through every exercise you have done so far and write down every word and symbol (another name for ‘character’) that you have used. Make sure your list of symbols is complete.
[Whoa! Now that's a tall order (LOL!)... let's see how well I did:
# (octothorpe) - used as a "comment" character; anything that follows is not seem by the interpreter
#{$0} - from the command line, the name of the script you are running
#{variable} - used for string interpolation, substitiutes the vaue for variable in a statement
$ - prepends a variable from the command line ($0 is the actual name of the script run)
% - percent (modulus in math, string variables in puts & print statements)
%d - represents a digit in a text string
%s - represents a string of text in a test string
( ) - used to encapsulate arguments to functions
* - asterisk used for multiplication
+ - plus used for additionand string concatenation
- - minus used for subtraction
/ - slash used for division
< - less-than
< < TEXT (ignore the spaces, they are there so this will print) - start code for a block of text. TEXT on its own line ends it.
<= - less-than-equal
> - greater-than
>= - greater-than-equal
ARGV - the argument value (constant), holds arguments entered on the command line
File - used to define and enact file options
File.close() - close a file
File.exists? - check to se if a file already exists or not
File.open() - opens a file for use
File.open(filename, 'w') - specifically opens a file for writing
File.read() - reads the contents of a file
File.readline() - reads a specific numbered line in a file
File.seek() - locate a particular spot in the file
File.truncate() - used to remove the contents of a file
File.write() - write line or characters to a file
STDIN - used with methods when ARGV is used as well, to differentiate between the two
[ ] - used to seprate arguments from puts and print statements
\\ - escapes a backslash
\n - newline character
\t - inserts a tab
chomp - used typically with gets. Removes the newline character froma line of text
do - reflects the start of a block of code to be run together
end - closes a block section of code
gets - get a string from standard input (usually the keyboard)
open - open a file or URL
print - takes text and displays it on the screen, no new line applied.
puts - used to "put" a "string" of characters on the screen along with a newline
variable - a variable value, useful for identity purposes
]
Next to each word or symbol, write its name and what it does. If you can’t find a name for a symbol in this book, then look for it online. If you do not know what a word or symbol does, then go read about it again and try using it in some code.
Once you have your list, spend a few days rewriting the list and double checking that it’s correct. This may get boring but push through and really nail it down.
Once you have memorized the list and what they do, then you should step it up by writing out tables of symbols, their names, and what they do from memory. When you hit some you can’t recall from memory, go back and memorize them again.
What You are Learning
It’s important when you are doing a boring mindless memorization exercise like this to know why. It helps you focus on a goal and know the purpose of all your efforts. In this exercise you are learning the names of symbols so that you can read source code more easily. It’s similar to learning the alphabet and basic words of English, except that Ruby’s alphabet has extra symbols you might not know.
[I can see the value of doing this. Just going through and looking at these options it helped me really think "OK, so *that's* why that is being used there". Honestly, I've never done this with a language before. It's tedious, but I can say I think it'll be worth it. Loks like I'll be doing this for quite some time ;) ]
Because it seems Zed is of a similar mind, and at this very same point, too (BTW, I am deliberately doing these exercises in order, and I am wherever possible "not reading ahead". This way, I can aproach the exercise fresh, without pre-conceived ideas, and you, dear reader, get to be the beneficiary of my Guinea piggery :) ).
In this Exercise (and the next one), there's no code to learn or pratice with. Instead, we'l be taking a look back at the things we've aleady learned (and it's actully quite a bit, if I do say so myself).
First, go back through every exercise you have done so far and write down every word and symbol (another name for ‘character’) that you have used. Make sure your list of symbols is complete.
[Whoa! Now that's a tall order (LOL!)... let's see how well I did:
# (octothorpe) - used as a "comment" character; anything that follows is not seem by the interpreter
#{$0} - from the command line, the name of the script you are running
#{variable} - used for string interpolation, substitiutes the vaue for variable in a statement
$ - prepends a variable from the command line ($0 is the actual name of the script run)
% - percent (modulus in math, string variables in puts & print statements)
%d - represents a digit in a text string
%s - represents a string of text in a test string
( ) - used to encapsulate arguments to functions
* - asterisk used for multiplication
+ - plus used for additionand string concatenation
- - minus used for subtraction
/ - slash used for division
< - less-than
< < TEXT (ignore the spaces, they are there so this will print) - start code for a block of text. TEXT on its own line ends it.
<= - less-than-equal
> - greater-than
>= - greater-than-equal
ARGV - the argument value (constant), holds arguments entered on the command line
File - used to define and enact file options
File.close() - close a file
File.exists? - check to se if a file already exists or not
File.open() - opens a file for use
File.open(filename, 'w') - specifically opens a file for writing
File.read() - reads the contents of a file
File.readline() - reads a specific numbered line in a file
File.seek() - locate a particular spot in the file
File.truncate() - used to remove the contents of a file
File.write() - write line or characters to a file
STDIN - used with methods when ARGV is used as well, to differentiate between the two
[ ] - used to seprate arguments from puts and print statements
\\ - escapes a backslash
\n - newline character
\t - inserts a tab
chomp - used typically with gets. Removes the newline character froma line of text
do - reflects the start of a block of code to be run together
end - closes a block section of code
gets - get a string from standard input (usually the keyboard)
open - open a file or URL
print - takes text and displays it on the screen, no new line applied.
puts - used to "put" a "string" of characters on the screen along with a newline
variable - a variable value, useful for identity purposes
]
Next to each word or symbol, write its name and what it does. If you can’t find a name for a symbol in this book, then look for it online. If you do not know what a word or symbol does, then go read about it again and try using it in some code.
Once you have your list, spend a few days rewriting the list and double checking that it’s correct. This may get boring but push through and really nail it down.
Once you have memorized the list and what they do, then you should step it up by writing out tables of symbols, their names, and what they do from memory. When you hit some you can’t recall from memory, go back and memorize them again.
What You are Learning
It’s important when you are doing a boring mindless memorization exercise like this to know why. It helps you focus on a goal and know the purpose of all your efforts. In this exercise you are learning the names of symbols so that you can read source code more easily. It’s similar to learning the alphabet and basic words of English, except that Ruby’s alphabet has extra symbols you might not know.
[I can see the value of doing this. Just going through and looking at these options it helped me really think "OK, so *that's* why that is being used there". Honestly, I've never done this with a language before. It's tedious, but I can say I think it'll be worth it. Loks like I'll be doing this for quite some time ;) ]
Friday, November 18, 2011
Exercise 21: Functions Can Return Something: Learn Ruby The Hard Way: Practicum
Remember a couple of days ago I talked about the idea of a function being like a guitar pedal where the cord from the guitar (and the signal from the pickups) goes into the pedal, some magic and processing happens, and the sound that comes out the other end goes to your amp? Well, many guitarists use multiple pedals, and through these multiple pedals, the output of one pedal is the input of another, with its own processing, and it sends it down the line until it gets to the amp. this is called "signal flow" and at different times, one pedal, or two pedals, or sometimes five or six, will be active at the same time.
This is a good analogy (I think) for passing values from one function to another, and the way to do that is through the functions "return value". If you have ever programmed in C or Pascal or other procedural languages, you may already be familiar with the return value of a function. Generally, the last value, the final result, the end product is often the return value, but not always. In C, if everything happens correctly, we often return a value of "0" as an "all's well" or a value of "-1" if there's an error. What does Ruby do? Let's find out.
In Ruby, the last evaluated statement in a method is its return value. You can also be more explicit if you want and type "return a + b", but it's not necessary.
What You Should See
$ ruby ex21.rb
Let's do some math with just functions!
ADDING 30 + 5
SUBTRACTING 78 - 4
MULTIPLYING 90 * 2
DIVIDING 100 / 2
Age: 35, Height: 74, Weight: 180, IQ: 50
Here is a puzzle.
DIVIDING 50 / 2
MULTIPLYING 180 * 25
SUBTRACTING 74 - 4500
ADDING 35 + -4426
That becomes: -4391 Can you do it by hand?
$
Extra Credit
If you aren't really sure what return values are, try writing a few of your own functions and have them return some values. You can return anything that you can put to the right of an =.
[The key to notice is that, as was stated earlier, if we want to be explicit, we can say in the final line "return a+b" or any other value we want to reflect. It's what the last statement evaluates to that is returned, and it can be used as a value to feed another function if we want to].
At the end of the script is a puzzle. I'm taking the return value of one function, and using it as the argument of another function. I'm doing this in a chain so that I'm kind of creating a formula using the functions. It looks really weird, but if you run the script you can see the results. What you should do is try to figure out the normal formula that would recreate this same set of operations.
[Well, it's up in the What You Should See section. If we take (50/2), which is 25, then feed 180 * 25, then feed -4500 from 74 (or 74 -4500) gives us -4426 and then adds 35 to it, giving us -4391 as the final answer].
Once you have the formula worked out for the puzzle, get in there and see what happens when you modify the parts of the functions. Try to change it on purpose to make another value.
Finally, do the inverse. Write out a simple formula and use the functions in the same way to calculate it.
TESTHEAD's TAKEAWAYS:
So the cool thing here is any change in any of the values will modify that end result, because each function call passes a return value to the next one in the chain. This might seem contrived, but the truth is, this is a very common practice, taking a value from one function and passing it to another, and then passing that resulting value to another function, and so on and so on. Think of using the pipe command in UNIX. You run a command and get a certain output, you then pipe that output to another command, and it does something,and you then pipe that output to yet another command:
$ grep "value" textfile.txt | sort | uniq
Look somewhat familiar? in may ways, that's exactly the same thing as passing return values from functions into other functions. Again, signal flow for the win :).
This is a good analogy (I think) for passing values from one function to another, and the way to do that is through the functions "return value". If you have ever programmed in C or Pascal or other procedural languages, you may already be familiar with the return value of a function. Generally, the last value, the final result, the end product is often the return value, but not always. In C, if everything happens correctly, we often return a value of "0" as an "all's well" or a value of "-1" if there's an error. What does Ruby do? Let's find out.
In Ruby, the last evaluated statement in a method is its return value. You can also be more explicit if you want and type "return a + b", but it's not necessary.
What You Should See
$ ruby ex21.rb
Let's do some math with just functions!
ADDING 30 + 5
SUBTRACTING 78 - 4
MULTIPLYING 90 * 2
DIVIDING 100 / 2
Age: 35, Height: 74, Weight: 180, IQ: 50
Here is a puzzle.
DIVIDING 50 / 2
MULTIPLYING 180 * 25
SUBTRACTING 74 - 4500
ADDING 35 + -4426
That becomes: -4391 Can you do it by hand?
$
Extra Credit
If you aren't really sure what return values are, try writing a few of your own functions and have them return some values. You can return anything that you can put to the right of an =.
[The key to notice is that, as was stated earlier, if we want to be explicit, we can say in the final line "return a+b" or any other value we want to reflect. It's what the last statement evaluates to that is returned, and it can be used as a value to feed another function if we want to].
At the end of the script is a puzzle. I'm taking the return value of one function, and using it as the argument of another function. I'm doing this in a chain so that I'm kind of creating a formula using the functions. It looks really weird, but if you run the script you can see the results. What you should do is try to figure out the normal formula that would recreate this same set of operations.
[Well, it's up in the What You Should See section. If we take (50/2), which is 25, then feed 180 * 25, then feed -4500 from 74 (or 74 -4500) gives us -4426 and then adds 35 to it, giving us -4391 as the final answer].
Once you have the formula worked out for the puzzle, get in there and see what happens when you modify the parts of the functions. Try to change it on purpose to make another value.
Finally, do the inverse. Write out a simple formula and use the functions in the same way to calculate it.
TESTHEAD's TAKEAWAYS:
So the cool thing here is any change in any of the values will modify that end result, because each function call passes a return value to the next one in the chain. This might seem contrived, but the truth is, this is a very common practice, taking a value from one function and passing it to another, and then passing that resulting value to another function, and so on and so on. Think of using the pipe command in UNIX. You run a command and get a certain output, you then pipe that output to another command, and it does something,and you then pipe that output to yet another command:
$ grep "value" textfile.txt | sort | uniq
Look somewhat familiar? in may ways, that's exactly the same thing as passing return values from functions into other functions. Again, signal flow for the win :).
Thursday, November 17, 2011
A Spare is the Best Defense
This is really just a chance for me to whine a litte bit. One of the most frustrating things to deal with when you are someone who gets into a routine is when something breaks that routine, whether its inadvertantly or because you decide to try something a little different. Case in point, today I was toying around with the idea of using a couple of small tote bags to put my two laptops in (I carry a Macbook Pro and a Toshiba Sattelite with me, well, almost everywhere. Since my accident, that's meant inside of a snowboarding day-pack backpack. Great for size, but lousy for scuff-ups on both systems.
Today I picked up a small tote bag I use for other things and realized it was the perfect size for my PC laptop. I grabbed it, threw the power supply into it, and got on the train. At my stop, I picked up my backback, put in my PC next to my Mac, got up to get off the train... you all know where this is going, right?
Well, when I got to work, and unpacked my systems, the sickening realization dawned on me... no tote bag. Laptop yes, but tote-bag is back on the train, and with it, the AC adapter for the laptop (smacks head!). A couple of calls into both the San Francisco and San Jose office, and now the wait begins. Will someone turn it in? Will they contact me? Will I see the adapter again, or is it just... gone?
Here at work, we have, well, dozens of spare power supplies for the Macbooks, and I've taken advantage of that by rooting one at my desk, one at home, and I carry an adapter in my backpack (and it usually never leaves it). I'd toyed with the idea of having a spare for the PC laptop but never got around to it. Now I'm regretting that. The bigger challenge with the PC market is the large assortment of competing adapters and the lack of a way to get a replacement easily (at least with a Mac, I can go to an Apple store in several locations and know I could pick up a replacement fairly quickly). Again, I'm just whining, I know there's little I can do about this at the immediate moment.
There is something I can do for the longer term, though, and I just did it today... I bought a replacement battery (which I needed anyway, as the original battery is now only lasting me about 25 minutes per charge) and a back up AC adapter. In the mean time, I will have to adapt a few things, and stuff I was doing on my PC I will either have to do on the Mac instead (not entirely practical for some stuff) or just wait it out until the replacement items get here. This way, I have two bases covered. In the best scenario, I get my charger back today or tomorrow, and I have a back-up charger to plug in at home or work and just leave there. Worst case scenario, I am sans my PC for a few days until a new Adapter and battery arrives via UPS. Either way, I'll just have to deal until one or the other happens.
In my daily testing walk, I've also often found myself in situations where "having a spare" would save me a lot of time and hassle. Whether it be a virtual machine image (or two or three), or a couple of backup drives, or an old laptop that in a drawer and doesn't get much use, but it's there just in case, having a judicious "spare few items" around can be the difference between being productive or being totally blocked. If the cost of having a spare seems high, think of what you would do if you were to lose something vital to your efforts, whatever they might be. Is the price still to high?
Exercise 20: Functions And Files: Learn Ruby The Hard Way: Practicum
This is the point where many people, who program casually, start to get glassy eyed (well, at least in the past, this is where I often got that way). Functions are incredibly powerful ways to show what is happening to code in a small and distinct place, and it helps a lot to keep code modular and portable.
The disadvantage is that code can get to be really hard to follow if you are not focusing on and aware of what's happening. This is especially true when we start putting in file actions and the opening/closing/modifying of files both outside of and within functions. So let's take this one slowly and see what's going on.
Pay close attention to how we pass in the current line number each time we run print_a_line.
What You Should See
$ ruby ex20.rb test.txt
First let's print the whole file:
To all the people out there.
I say I don't like my hair.
I need to shave it off.
Now let's rewind, kind of like a tape.
Let's print three lines:
1 To all the people out there.
2 I say I don't like my hair.
3 I need to shave it off.
$
Extra Credit
Go through and write English comments for each line to understand what's going on.
Each time print_a_line is run you are passing in a variable current_line. Write out what current_line is equal to on each function call, and trace how it becomes line_count in print_a_line.
[Current_line starts out with a value of one, and then before each print_a_line() call, current_line gets incremented (added to) by one. For those familiar with C and C type languages, this is where the shorthand variable++ comes from (it means the same thing, take variable and increment it by one). See my second draft for the value of current_line in each case.]
Find each place a function is used, and go check its def to make sure that you are giving it the right arguments.
[In this case, though the function is defined as receiving a value of 'f', that is local in scope. The value being passed to the functions in each case is 'current_file', and in the case of 'print_a_line', 'current_line' is passed into the function as well, and described within the function as 'line_count'.]
Research online what the seek function for file does. Look at the rdoc documentation using the ri command and see if you can figure it out from there.
[The ri documentation doesn't give me much of anything for this, at least not on my PC. I can deduce that seek allows me to pinpoint a position in a file and go to that location. In this case, since the first value is 0, I assume it means go to the beginning of the file, and the execution of the code proves that.]
Research the shorthand notation += and rewrite the script to use that.
[variable += 1 is the equivalent of saying variable = variable + 1. See below for a rewrite.]
TESTHEAD's TAKEAWAYS:
So we have some new material to work with here. We see our first direct reference to using the seek method within the file object, and we are introduced to the idea (and the shorthand) for incrementing variables. For straightforward linear scripts, incrementing works and can be useful, but it really shows its power when we start dealing with looping of code (it's a way to keep track of what we've done and control how many times loops are executed). This example also helps show how the scope of variables changes as a value moves from one function to another, and that values modified outside of the function can affect what goes on inside of a function.
The disadvantage is that code can get to be really hard to follow if you are not focusing on and aware of what's happening. This is especially true when we start putting in file actions and the opening/closing/modifying of files both outside of and within functions. So let's take this one slowly and see what's going on.
Pay close attention to how we pass in the current line number each time we run print_a_line.
What You Should See
$ ruby ex20.rb test.txt
First let's print the whole file:
To all the people out there.
I say I don't like my hair.
I need to shave it off.
Now let's rewind, kind of like a tape.
Let's print three lines:
1 To all the people out there.
2 I say I don't like my hair.
3 I need to shave it off.
$
Extra Credit
Go through and write English comments for each line to understand what's going on.
Each time print_a_line is run you are passing in a variable current_line. Write out what current_line is equal to on each function call, and trace how it becomes line_count in print_a_line.
[Current_line starts out with a value of one, and then before each print_a_line() call, current_line gets incremented (added to) by one. For those familiar with C and C type languages, this is where the shorthand variable++ comes from (it means the same thing, take variable and increment it by one). See my second draft for the value of current_line in each case.]
Find each place a function is used, and go check its def to make sure that you are giving it the right arguments.
[In this case, though the function is defined as receiving a value of 'f', that is local in scope. The value being passed to the functions in each case is 'current_file', and in the case of 'print_a_line', 'current_line' is passed into the function as well, and described within the function as 'line_count'.]
Research online what the seek function for file does. Look at the rdoc documentation using the ri command and see if you can figure it out from there.
[The ri documentation doesn't give me much of anything for this, at least not on my PC. I can deduce that seek allows me to pinpoint a position in a file and go to that location. In this case, since the first value is 0, I assume it means go to the beginning of the file, and the execution of the code proves that.]
Research the shorthand notation += and rewrite the script to use that.
[variable += 1 is the equivalent of saying variable = variable + 1. See below for a rewrite.]
TESTHEAD's TAKEAWAYS:
So we have some new material to work with here. We see our first direct reference to using the seek method within the file object, and we are introduced to the idea (and the shorthand) for incrementing variables. For straightforward linear scripts, incrementing works and can be useful, but it really shows its power when we start dealing with looping of code (it's a way to keep track of what we've done and control how many times loops are executed). This example also helps show how the scope of variables changes as a value moves from one function to another, and that values modified outside of the function can affect what goes on inside of a function.
Subscribe to:
Posts (Atom)