Loading ...
Sorry, an error occurred while loading the content.

Easy things first

Expand Messages
  • J. B. Rainsberger
    ... Kent writes about this in his TDD book, and it s so simple: if you don t know what test to write first, write a simple test that will get you going . Many
    Message 1 of 19 , Apr 17, 2002
    • 0 Attachment
      > Ron Jeffries wrote:
      > >
      > > Turns out that in the game (you decide about real life) the stories'
      > > value is randomly distributed over cost. So doing the easy stories
      > > first picked up the bulk of the value of the whole project.
      > >
      > > That surprised us a lot: it was a darn good strategy. Again, you
      > > decide about real life. "When you don't know what to do, do
      > something
      > > easy." How's that sound?
      >
      > Sounds like rationalized laziness (but it's not :-).
      >
      > Also sounds like one of those counterintuitive things which is
      > good to know and have in your back pocket.
      >
      > Also sounds like a good scheduling algorithm: do the shortest
      > tasks first so that the average waiting time per task is minimized.

      Kent writes about this in his TDD book, and it's so simple: if you don't know
      what test to write first, write a simple test that will "get you going". Many
      times (most?) the simplest happy path is not the best place to start. This is
      why I commonly write a bad path test or two to get me rolling, especially when
      creating a new class/interface.

      We know that Ron prefers to code to the main line, so Ron, where do you start?
      Is there any pattern to your first unit test, especially on a brand-new class?

      J. B. (Joe) Rainsberger
      President, Diaspar Software Services
      http://www.diasparsoftware.com
      Let's write software that people understand.
    • Ron Jeffries
      ... Probably there is a pattern. Give me a problem, let s see what I do. Ron Jeffries www.XProgramming.com Do I contradict myself? Very well then I contradict
      Message 2 of 19 , Apr 18, 2002
      • 0 Attachment
        Around Wednesday, April 17, 2002, 1:55:58 PM, J. B. Rainsberger wrote:

        > Kent writes about this in his TDD book, and it's so simple: if you don't know
        > what test to write first, write a simple test that will "get you going". Many
        > times (most?) the simplest happy path is not the best place to start. This is
        > why I commonly write a bad path test or two to get me rolling, especially when
        > creating a new class/interface.

        > We know that Ron prefers to code to the main line, so Ron, where do you start?
        > Is there any pattern to your first unit test, especially on a brand-new class?

        Probably there is a pattern. Give me a problem, let's see what I do.

        Ron Jeffries
        www.XProgramming.com
        Do I contradict myself? Very well then I contradict myself.
        (I am large, I contain multitudes.) --Walt Whitman
      • J. B. Rainsberger
        ... All right, Ron. I know I m quite late with these replies, so I hope this conversation hasn t timed out . Here is a problem: given the wins and losses for
        Message 3 of 19 , Apr 23, 2002
        • 0 Attachment
          Quoting extremeprogramming@yahoogroups.com:
          > From: Ron Jeffries <ronjeffries@...>
          > Subject: Re: Easy things first
          >
          > Around Wednesday, April 17, 2002, 1:55:58 PM, J. B. Rainsberger wrote:
          >
          > > Kent writes about this in his TDD book, and it's so simple: if you
          > don't know
          > > what test to write first, write a simple test that will "get you
          > going". Many
          > > times (most?) the simplest happy path is not the best place to start.
          > This is
          > > why I commonly write a bad path test or two to get me rolling,
          > especially when
          > > creating a new class/interface.
          >
          > > We know that Ron prefers to code to the main line, so Ron, where do
          > you start?
          > > Is there any pattern to your first unit test, especially on a
          > brand-new class?
          >
          > Probably there is a pattern. Give me a problem, let's see what I do.
          >
          > Ron Jeffries

          All right, Ron. I know I'm quite late with these replies, so I hope this
          conversation hasn't "timed out". Here is a problem: given the wins and losses
          for a number of baseball teams, I need a class that computes how many "games
          back" one team is from another. The formula for "games back" is this:

          For each team, compute "wins above even", which is wins minus losses. Team A is
          1/2 of (wins above even for Team A minus wins above even for Team B) "games
          back" of Team B. Example: Team A has 5 wins, 2 losses; Team B has 4 wins, 4
          losses. Team B is 1-1/2 games back of Team A: 1/2 ((5-2) - (4-4)) = 1.5.

          By convention, "games back" is never negative: one almost always talks
          about "games back" from the trailing team's perspective. For our purposes, it
          is OK to say that Team A is -5 games back of Team B to indicate that A is
          actually ahead of B in the standings.

          I'm ready for your questions, in case this is not sufficiently clear.

          J. B. (Joe) Rainsberger
          President, Diaspar Software Services
          http://www.diasparsoftware.com
          Let's write software that people understand.
        • Ron Jeffries
          ... This one is so simple that it s hard to test first. I ll try ... here s the trace of what I did: =============================== I m thinking I ll create
          Message 4 of 19 , Apr 26, 2002
          • 0 Attachment
            Around Tuesday, April 23, 2002, 3:46:54 PM, J. B. Rainsberger wrote:

            >> > We know that Ron prefers to code to the main line, so Ron, where do
            >> you start?
            >> > Is there any pattern to your first unit test, especially on a
            >> brand-new class?
            >>
            >> Probably there is a pattern. Give me a problem, let's see what I do.
            >>
            >> Ron Jeffries

            > All right, Ron. I know I'm quite late with these replies, so I hope this
            > conversation hasn't "timed out". Here is a problem: given the wins and losses
            > for a number of baseball teams, I need a class that computes how many "games
            > back" one team is from another. The formula for "games back" is this:

            > For each team, compute "wins above even", which is wins minus losses. Team A is
            > 1/2 of (wins above even for Team A minus wins above even for Team B) "games
            > back" of Team B. Example: Team A has 5 wins, 2 losses; Team B has 4 wins, 4
            > losses. Team B is 1-1/2 games back of Team A: 1/2 ((5-2) - (4-4)) = 1.5.

            > By convention, "games back" is never negative: one almost always talks
            > about "games back" from the trailing team's perspective. For our purposes, it
            > is OK to say that Team A is -5 games back of Team B to indicate that A is
            > actually ahead of B in the standings.

            > I'm ready for your questions, in case this is not sufficiently clear.

            This one is so simple that it's hard to test first. I'll try ...
            here's the trace of what I did:
            ===============================

            I'm thinking I'll create team objects with win and loss instance
            variables. My first test, and it might be in general too big a bite,
            is to create a single team and see if it is zero games back from
            itself.

            Here's the first test and the code that makes it work:

            class GamesBackTest < TestCase

            def testEven
            team = Team.new(0,0)
            back = team.gamesBack(team)
            assert_equal(0,back)
            end

            end

            class Team
            def initialize(wins, losses)
            end

            def gamesBack(aTeam)
            return 0
            end
            end

            Now I guess I'll put in another team and a real answer. I'll use your example.

            New test ...

            def testAB
            teamA = Team.new(5,2)
            teamB = Team.new(4,4)
            back = teamA.gamesBack(teamB)
            assert_equal(1.5, back)
            end

            Doesn't run (gets zero, of course). I decide to code "by intention" so
            that the answer is the average of the wins above even. I code:

            def gamesBack(aTeam)
            return 0.5 * (self.winsAboveEven - aTeam.winsAboveEven)
            end

            I used 0.5 because I know I have to get this to float. In Smalltalk I
            would just let it be a Fraction. Maybe I'll change it to integer
            later, we'll see ...

            Test still doesn't run, the objects don't know wins above even.

            I decide that wins and losses are instance variables, and code:

            def winsAboveEven
            return @wins - @losses
            end

            This doesn't run, and gives the notice that the instance variables
            aren't initialized. Of course I knew that, even I can read 20 lines of
            code. I ran the test because my habit is always to run the tests, and
            I want to reinforce that habit.

            I'll fix initialize now:

            def initialize(wins, losses)
            @wins = wins
            @losses = losses
            end

            The test runs. Actually I think I'm done. Got any more tests you would like to try?
            ====================================
            Does that help?

            Ron Jeffries
            www.XProgramming.com
            The practices are not the knowing: they are a path to the knowing.
          • J. B. Rainsberger
            ... ... Thanks, Ron. Now I d like to add more to the problem and see what happens. Now I need a class to read a game log from a file and
            Message 5 of 19 , Apr 29, 2002
            • 0 Attachment
              > > JBR:
              > > All right, Ron. I know I'm quite late with these replies, so I hope
              > this
              > > conversation hasn't "timed out". Here is a problem: given the wins and
              > losses
              > > for a number of baseball teams, I need a class that computes how many
              > "games
              > > back" one team is from another. The formula for "games back" is
              > this:
              <snip />

              > Ron:
              > This one is so simple that it's hard to test first. I'll try ...
              <snip />

              Thanks, Ron. Now I'd like to add more to the problem and see what happens.

              Now I need a class to read a "game log" from a file and presents a report with
              the league standings.

              A sample log:
              Toronto 7-2 New York (A)
              Boston 3-4 Tampa Bay
              Baltimore 2-5 Chicago
              ...

              A sample report:

              AMERICAN LEAGUE

              Eastern Division

              Team W L Pct GBL
              Toronto 6 2 .750 --
              Baltimore 5 3 .625 1
              Boston 5 4 .556 1 1/2
              New York 3 5 .375 3
              Tampa Bay 1 6 .143 4 1/2

              Central Division
              [...]

              The class simply reads the game log and spits out a report. The game log will
              usually be stored in a file; the report can be sent to standard output.

              Naturally, assume you still have what you've already done. I'd like to know
              what your next few tests are.

              > The test runs. Actually I think I'm done. Got any more tests you would
              > like to try?

              Nope. It's interesting that you did the whole thing in two tests, because I
              would invariably want to disallow a negative number of wins or losses. I'm
              still not entirely convinced about entirely ignoring this case, but I'm giving
              it some serious thought.

              Thank you for participating in this little experiment for me.

              J. B. (Joe) Rainsberger
              President, Diaspar Software Services
              http://www.diasparsoftware.com
              Let's write software that people understand.
            • Ron Jeffries
              ... Thing one, I want to negotiate the input to something more reasonable to parse. How about Toronto,7,New York (A),2 Thing two, what is (A)? How do I know
              Message 6 of 19 , Apr 29, 2002
              • 0 Attachment
                Around Monday, April 29, 2002, 1:42:54 PM, J. B. Rainsberger wrote:

                > Now I need a class to read a "game log" from a file and presents a report with
                > the league standings.

                > A sample log:
                > Toronto 7-2 New York (A)
                > Boston 3-4 Tampa Bay
                > Baltimore 2-5 Chicago

                Thing one, I want to negotiate the input to something more reasonable
                to parse. How about

                Toronto,7,New York (A),2

                Thing two, what is (A)? How do I know the leagues and divisions?
                Is this information provided in the game log? If so, where and how? If
                not, how is it provided?

                Thing three, what is GBL? (I assume it is games behind leader?) Who is
                the leader, and how is it to be determined? Is it the team with the
                highest percentage? Largest number of wins? Largest number of games?

                Thing four, I think I want to defer leagues and divisions to a
                separate story, at least for the tiny iterations I am doing. I propose
                a game log report that drops all the heading information you provided
                and just gives the detailed lines. Maybe we'll provide each league and
                division in a separate file or something?

                Ron Jeffries
                www.XProgramming.com
                The fact that we know more today, and are more capable today,
                is good news about today, not bad news about yesterday.
              • Ron Jeffries
                ... I always code to the mainline. If this program has to be bulletproof, i.e. deal with bad input, I probably want a story for that. Ron Jeffries
                Message 7 of 19 , Apr 29, 2002
                • 0 Attachment
                  Around Monday, April 29, 2002, 1:42:54 PM, J. B. Rainsberger wrote:

                  >> The test runs. Actually I think I'm done. Got any more tests you would
                  >> like to try?

                  > Nope. It's interesting that you did the whole thing in two tests, because I
                  > would invariably want to disallow a negative number of wins or losses. I'm
                  > still not entirely convinced about entirely ignoring this case, but I'm giving
                  > it some serious thought.

                  I always code to the mainline. If this program has to be bulletproof,
                  i.e. deal with bad input, I probably want a story for that.

                  Ron Jeffries
                  www.XProgramming.com
                  Steering is more important than speed,
                  in driving and in software development.
                • Ron Jeffries
                  ... Here s tonight s installment. Not done yet, just too stupid to be allowed to program. ============================== Now I m supposed to produce a report.
                  Message 8 of 19 , Apr 29, 2002
                  • 0 Attachment
                    Around Monday, April 29, 2002, 1:42:54 PM, J. B. Rainsberger wrote:

                    > Thanks, Ron. Now I'd like to add more to the problem and see what happens.

                    > Now I need a class to read a "game log" from a file and presents a report with
                    > the league standings.

                    > A sample log:
                    > Toronto 7-2 New York (A)
                    > Boston 3-4 Tampa Bay
                    > Baltimore 2-5 Chicago

                    Here's tonight's installment. Not done yet, just too stupid to be
                    allowed to program.
                    ==============================

                    Now I'm supposed to produce a report. The story calls for reading
                    input from a file. I've requested a change to the file format.
                    Meanwhile, I'm going to ignore reading and headers, just produce the
                    detail lines from a collection of Games.

                    I think I'll start by producing the three-digit Pct.

                    def testPct
                    team = Team.new(6,2)
                    assert_equal('.750', team.pctString)
                    end

                    Doesn't work. pctString not defined. So I'll define it. I think I'll
                    get cute here and use scaled integer arithmetic. Might be a mistake.

                    My first implementation was

                    def pctString
                    return (@wins * 1000 / @losses).to_s
                    end

                    which is, of course, totally wrong. It answered 3000. It must be
                    bedtime. I'll try again. If I stay this stupid, it'll be a sign I
                    should stop.

                    def pctString
                    return '.' + (@wins*1000 / (@wins + @losses)).to_s
                    end

                    That's better. However, it's not going to work for a perfect season,
                    which should return 1.000, nor for a really bad season like .010. I
                    need more tests.

                    def testPerfect
                    team = Team.new(8,0)
                    assert_equal('1.000', team.pctString)
                    end

                    First I refactor ...

                    def pctString
                    return '.' + basePctString
                    end

                    def basePctString
                    (@wins*1000 / (@wins + @losses)).to_s
                    end

                    Then I'll beef up pctString:

                    def pctString
                    base = basePctString
                    return '1.000' if base == '1000'
                    return '.' + base
                    end

                    This seems kind of hokey but it works. I'll go with it and do a losing team:

                    def testLosers
                    team = Team.new(1,9)
                    assert_equal('.010', team.pctString)
                    end

                    I expect this to fail with '. 10' or something like that. Running the
                    test ... oops, I need them to be worse losers than that, these guys
                    were .100. Sheesh, I /am/ too tired. I'll make this one work and maybe
                    then stop for the night.

                    def testLosers
                    team = Team.new(1,99)
                    assert_equal('.010', team.pctString)
                    end

                    should do it. Now I get '.10' as the answer. Of course. Why would
                    there be a leading space. Third indication that I'm too tired to be
                    allowed to stay up. I'll make this work, then stop:

                    def pctString
                    base = basePctString
                    base = '0' + base if base.size == 2
                    return '1.000' if base == '1000'
                    return '.' + base
                    end

                    Now this is pretty strange, but on the other hand it is pretty simple.
                    It might have been better to do the whole thing in float, but I have a
                    deep and abiding fear of floating point numbers: they always come out
                    almost right.

                    I'm going to let this ride. All my tests are working, so I can go to
                    bed in good conscience. Tomorrow I'll work on the games up string,
                    then printing a collection. G'nite ...
                    ========================================

                    Regards,

                    Ron Jeffries
                    www.XProgramming.com
                    Accroche toi a ton reve. --ELO
                  • Ron Jeffries
                    ... Decided to convert to float. I like it better: ======================================== I decided to convert to float before I went to bed. Now the
                    Message 9 of 19 , Apr 29, 2002
                    • 0 Attachment
                      Around Monday, April 29, 2002, 1:42:54 PM, J. B. Rainsberger wrote:

                      > Thanks, Ron. Now I'd like to add more to the problem and see what happens.

                      > Now I need a class to read a "game log" from a file and presents a report with
                      > the league standings.

                      > A sample log:
                      > Toronto 7-2 New York (A)
                      > Boston 3-4 Tampa Bay
                      > Baltimore 2-5 Chicago

                      Decided to convert to float. I like it better:

                      ========================================

                      I decided to convert to float before I went to bed. Now the relevant methods look like this:

                      def pctString
                      base = basePctString
                      base = base[1..4] if base[0,2] == '0.'
                      return base
                      end

                      def basePctString
                      pct = (@..._f / (@wins + @losses))
                      return sprintf("%1.3f",pct)
                      end

                      Tests all run. I guess I like it a little better. Would be cleaner if the customer would accept 0.750 instead of .750 as the percent. We'll see how the report looks ...
                      ========================================

                      Ron Jeffries
                      www.XProgramming.com
                      The Great and Powerful Oz has spoken.
                    • Ron Jeffries
                      ... What s it supposed to show for games back other than integer or integer.5? What about games back of 1.333 or 1.451? Ron Jeffries www.XProgramming.com Sorry
                      Message 10 of 19 , Apr 29, 2002
                      • 0 Attachment
                        Around Monday, April 29, 2002, 1:42:54 PM, J. B. Rainsberger wrote:

                        > Team W L Pct GBL
                        > Toronto 6 2 .750 --
                        > Baltimore 5 3 .625 1
                        > Boston 5 4 .556 1 1/2
                        > New York 3 5 .375 3
                        > Tampa Bay 1 6 .143 4 1/2

                        What's it supposed to show for games back other than integer or
                        integer.5? What about games back of 1.333 or 1.451?

                        Ron Jeffries
                        www.XProgramming.com
                        Sorry about your cow ... I didn't know she was sacred.
                      • Ron Jeffries
                        ... Did a little more while I wait for the antihistamines to kick in ... ======================================== Now I m working on the games back string.
                        Message 11 of 19 , Apr 29, 2002
                        • 0 Attachment
                          Around Monday, April 29, 2002, 1:42:54 PM, J. B. Rainsberger wrote:

                          > Now I need a class to read a "game log" from a file and presents a report with
                          > the league standings.

                          > A sample log:
                          > Toronto 7-2 New York (A)
                          > Boston 3-4 Tampa Bay
                          > Baltimore 2-5 Chicago

                          Did a little more while I wait for the antihistamines to kick in ...
                          ========================================

                          Now I'm working on the games back string. Decided to format it in the Team class. Test is:

                          def testAB
                          teamA = Team.new(5,2)
                          teamB = Team.new(4,4)
                          back = teamA.gamesBack(teamB)
                          assert_equal(1.5, back)
                          assert_equal('1 1/2', Team.formatBack(back))
                          end

                          First I "fake it till you make it" with

                          def Team.formatBack(aNumber)
                          '1 1/2'
                          end

                          Then I'll make it work with something a bit trickier:

                          def Team.formatBack(aNumber)
                          int = aNumber.to_i
                          frac = aNumber - int
                          intString = int.to_s
                          fracString = sprintf("%.3f", frac)
                          return intString + ' ' + fracString
                          end

                          This isn't taking me where I want to go. I'm going to go with a simple decimal and see if the customer will deal with it. If not, I'll do something more special. The test now is:

                          def testAB
                          teamA = Team.new(5,2)
                          teamB = Team.new(4,4)
                          back = teamA.gamesBack(teamB)
                          assert_equal(1.5, back)
                          assert_equal('1.500', Team.formatBack(back))
                          end

                          And the code is just

                          def Team.formatBack(aNumber)
                          sprintf("%1.3f", aNumber)
                          end

                          That'll have to do for now.
                          ==============================

                          Ron Jeffries
                          www.XProgramming.com
                          I'm giving the best advice I have. You get to decide whether it's true for you.
                        • Ron Jeffries
                          ... Here s the next installment ... ============================== OK, it s morning. Let s see if I m any more intelligent this morning. I have my doubts. I ve
                          Message 12 of 19 , Apr 30, 2002
                          • 0 Attachment
                            Around Monday, April 29, 2002, 1:42:54 PM, J. B. Rainsberger wrote:

                            > Now I need a class to read a "game log" from a file and presents a report with
                            > the league standings.

                            > A sample log:
                            > Toronto 7-2 New York (A)
                            > Boston 3-4 Tampa Bay
                            > Baltimore 2-5 Chicago

                            Here's the next installment ...

                            ==============================

                            OK, it's morning. Let's see if I'm any more intelligent this morning.
                            I have my doubts. I've got some raw formatting ability in the objects
                            now. Though it's not exactly what the customer wants, so I will
                            probably have to beef it up, I've learned about all I am going to
                            about messing with those strings. What I'm really interested in now is
                            producing the report, which will require a collection of teams, and
                            figuring out which one is the leader. (I have a question to the
                            customer on that, but he hasn't answered it.) What I want to do next
                            is to produce a mockup of the report. I could do that by making a few
                            teams manually and putting them in a list but instead I think I'll
                            start by parsing in some simple input. I also asked the customer if we
                            could use a different format, and I'm going to start with the format I
                            proposed because it's simpler. Even if I have to use the original
                            format, which has no useful punctuation to tell the fields apart, this
                            code will have the basic shape of what is needed.

                            First I'll make a factory method accepting a single input line ...

                            Uh oh. I hadn't reflected yet that he isn't giving me the information
                            I really want, team win/loss data. He wants to give me the individual
                            game scores and have me cumulate the win/loss information, I suppose
                            over the course of the season.

                            Now some people would be upset about not having noticed this. I'm not.
                            I haven't done any work that depends on it, and all the work I've done
                            is perfectly applicable. I also noticed that teams may need a name.
                            I'm pretty sure that they will, but not certain (because I have to
                            have some kind of lookup (I'm thinking a hash) and the name being in
                            there might be sufficient). So I'll move right to building a
                            "database" of teams now. I'll think about that by writing a test.
                            Here's what I sketched:

                            def testTeamsFromGame
                            gameString = 'Toronto,7,New York,2'
                            database = Hash.new
                            Team.update(database,gameString)
                            end

                            I took a game score in my proposed format, created a new "database"
                            hash, and sent a class method to Team to update it. This doesn't seem
                            too bad. Of course it doesn't work yet: I'll have to write update to
                            make it work. Also I don't have any asserts, but they're "obvious".
                            Toronto should have one win and New York one loss. So I'll do the
                            asserts first. (I might not always do that. Sometimes when I'm really
                            unsure, I might create the update method and look at the database to
                            see what is in it. Not this time.)

                            def testTeamsFromGame
                            gameString = 'Toronto,7,New York,2'
                            database = Hash.new
                            Team.update(database,gameString)
                            assert_equal(1, database["Toronto"].wins)
                            assert_equal(1, database["New York", losses)
                            end

                            That should be good enough, except for the typo in the second line
                            that I see now that I pasted the test here. Should be

                            def testTeamsFromGame
                            gameString = 'Toronto,7,New York,2'
                            database = Hash.new
                            Team.update(database,gameString)
                            assert_equal(1, database["Toronto"].wins)
                            assert_equal(1, database["New York"].losses)
                            end

                            The compiler would have told me, but I pasted here before compiling.
                            Note that I'm assuming wins and losses methods on Team. We'll get
                            there. Now to run the test. Undefined method "update". We knew that,
                            of course. I'll write it. Could use "fake it till you make it" but I
                            think I might just write it. Let's see what happens ... before
                            testing, I have this:

                            def Team.update(database,string)
                            team1Name, team1Score, team2Name, team2Score = string.split(',')
                            team1Score = team1Score.to_i
                            team2Score = team2Score.to_i
                            return if team1Score == team2Score
                            if team1Score > team2Score
                            team1 = Team.new(1,0)
                            team2 = Team.new(0,1)
                            else
                            team1 = Team.new(0,1)
                            team2 = Team.new(1,0)
                            end
                            database[team1Name] = team1
                            database[team2Name] = team2
                            end

                            There is some duplication there and the method isn't flat enough, so
                            it will need refactoring. Also I can see that the database is being
                            /set/, not updated. That can come later. This is a big enough bite:
                            let's run the test and get it working. Test tells me wins isn't
                            implemented. Nor is losses. I'll add them with the line

                            attr_reader :wins, :losses

                            Now my test works. Cool, I was getting nervous there. Now I'll extend
                            the test to update the same teams with another score, which will force
                            me to do the update rather than just store the results like I'm doing
                            now.

                            def testTeamsFromGame
                            gameString = 'Toronto,7,New York,2'
                            database = Hash.new
                            Team.update(database,gameString)
                            assert_equal(1, database["Toronto"].wins)
                            assert_equal(1, database["New York"].losses)
                            Team.update(database,gameString)
                            assert_equal(2, database["Toronto"].wins)
                            assert_equal(2, database["New York"].losses)
                            end

                            That should be good enough to force me to do the update. Let's run the
                            test (which won't work), then code.I started to write it as below:
                            note that I was going to do addWin and addLoss. I'm tempted to rewrite
                            that and just pass in both parameters wins, losses, and make them be
                            true or false, 0 or 1. Yeah, I'll try it. A bit out on a limb here.
                            Wish I had a partner ...

                            def Team.update(database,string)
                            team1Name, team1Score, team2Name, team2Score = string.split(',')
                            team1Score = team1Score.to_i
                            team2Score = team2Score.to_i
                            return if team1Score == team2Score
                            if team1Score > team2Score
                            Team.addWin(database,team1)
                            Team.addLoss(database,team2)
                            else
                            team1 = Team.new(0,1)
                            team2 = Team.new(1,0)
                            end
                            database[team1Name] = team1
                            database[team2Name] = team2
                            end

                            The new version of the update is

                            def Team.update(database,string)
                            team1Name, team1Score, team2Name, team2Score = string.split(',')
                            team1Score = team1Score.to_i
                            team2Score = team2Score.to_i
                            return if team1Score == team2Score
                            Team.updateWinLoss(database, team1Name, team1Score, team2Score)
                            Team.updateWinLoss(database, team2Name, team2Score, team1Score)
                            end

                            I decided that updateWinLoss can deal with whether the team won or
                            lost. Let's see what it might want to look like:

                            def Team.updateWinLoss(database, teamName, teamScore, otherScore)
                            team = database.fetch(teamName) { |name| Team.new(0,0) }
                            team.update(teamScore, otherScore)
                            database[teamName] = team
                            end

                            def update(myScore,otherScore)
                            if myScore > otherScore
                            @wins = @wins + 1
                            else
                            @losses = @losses + 1
                            end
                            end

                            Here I just fetch the team from the database, creating one if I need
                            to, and tell it to update itself. I put the team back in the database
                            in case it's new. Tests all run, including the one that updates the
                            database twice. Good place to stop.

                            ==========================================

                            So ... is this boring yet? I'm not writing a book on the subject (Kent
                            is), though this could turn out almost that long. Are you beginning to
                            see that I just peck away at the story, working on the bit that seems
                            interesting but simple enough to do.

                            I'm probably doing some kind of thinking that lets me pick things that
                            are kind of independent, because I haven't had to revise much of
                            anything in the objects yet. I'm not sure if that is happening just
                            because I'm picking small bits, or whether I'm picking bits, and
                            solutions, cleverly. It feels like I'm just pecking away at
                            interesting stuff.

                            Shall I continue?

                            Ron Jeffries
                            www.XProgramming.com
                            Do, or do not. There is no try. --Yoda
                          • Ron Jeffries
                            ... I told this one, didn t I? Can we generalize from that? Maybe. The XP programmer/customer relationship is all about conversation. It is not all about
                            Message 13 of 19 , Apr 30, 2002
                            • 0 Attachment
                              Around Tuesday, April 30, 2002, 10:10:06 AM, Brian Christopher Robinson wrote:

                              > On Mon, 29 Apr 2002, Ron Jeffries wrote:

                              >> I always code to the mainline. If this program has to be bulletproof,
                              >> i.e. deal with bad input, I probably want a story for that.

                              > Ron, do you tell your customers this up front? I could see a customer
                              > expecting this kind of error checking "for free". Then he would never
                              > specifically ask for it.

                              I told this one, didn't I? Can we generalize from that? Maybe. The XP
                              programmer/customer relationship is all about conversation. It is not
                              all about hiding what we're thinking. So when we first think about
                              errors, we bring it up. There's a conversation, or would be if Joe
                              ever comes back ...

                              I'd expect an acceptance test to give us the clue. Given the problem,
                              what would we like to have happen? Throwing an exception is little
                              more use than a bad answer. Treating negative numbers as positive
                              might work better. Customer has to decide.

                              It depends what the program is, what the input is, what the context
                              is, whether one checks for errors, and where. See Ward Cunningham's
                              CHECKS article for some of the thinking that goes into it:
                              http://c2.com/ppr/checks.html .

                              Ron Jeffries
                              www.XProgramming.com
                              First they ignore you, then they laugh at you, then they fight you, then you win.
                              - Ghandi
                            • Brian Christopher Robinson
                              ... Ron, do you tell your customers this up front? I could see a customer expecting this kind of error checking for free . Then he would never specifically
                              Message 14 of 19 , Apr 30, 2002
                              • 0 Attachment
                                On Mon, 29 Apr 2002, Ron Jeffries wrote:

                                > I always code to the mainline. If this program has to be bulletproof,
                                > i.e. deal with bad input, I probably want a story for that.

                                Ron, do you tell your customers this up front? I could see a customer
                                expecting this kind of error checking "for free". Then he would never
                                specifically ask for it.
                              • J. B. Rainsberger
                                ... That s fine for now. We ll probably need something to map the original input to this input in the future. I ll write up a story for that. ... (A)
                                Message 15 of 19 , Apr 30, 2002
                                • 0 Attachment
                                  Quoting extremeprogramming@yahoogroups.com:
                                  > From: Ron Jeffries <ronjeffries@...>
                                  > Subject: Re: Re: Re: Easy things first
                                  >
                                  > Around Monday, April 29, 2002, 1:42:54 PM, J. B. Rainsberger wrote:
                                  >
                                  > Thing one, I want to negotiate the input to something more reasonable
                                  > to parse. How about
                                  >
                                  > Toronto,7,New York (A),2

                                  That's fine for now. We'll probably need something to map the original input to
                                  this input in the future. I'll write up a story for that.

                                  > Thing two, what is (A)? How do I know the leagues and divisions?
                                  > Is this information provided in the game log? If so, where and how? If
                                  > not, how is it provided?

                                  (A) distinguishes the Yankees from the Mets, or the White Sox from the Cubs,
                                  for that matter. (A) denotes "American League" and (N) denotes "National
                                  League". If it weren't for that stupid interleague play, we wouldn't need them.
                                  If it makes things easier, you may assume that you'll only see (A) or (N) for
                                  New York and Chicago.

                                  The info for leagues and divisions changes quite rarely, so it's not provided
                                  in the game log. I don't need to change it often, so here's the list, and you
                                  can do what you need to in order to make it work. When we get to that story,
                                  I'll provide the complete list.

                                  > Thing three, what is GBL? (I assume it is games behind leader?) Who is
                                  > the leader, and how is it to be determined? Is it the team with the
                                  > highest percentage? Largest number of wins? Largest number of games?

                                  Correct: GBL = Games behind leader.

                                  The leader is the team with the highest winning percentage in a division. Note
                                  that it is possible, although quite rare, for the leader to be 1/2 game back of
                                  the second-place team. This happens when the top two teams have played nowhere
                                  near the same number of games.

                                  Example:

                                  A 20-10 .667 1/2
                                  B 23-12 .657 --

                                  > Thing four, I think I want to defer leagues and divisions to a
                                  > separate story, at least for the tiny iterations I am doing. I propose
                                  > a game log report that drops all the heading information you provided
                                  > and just gives the detailed lines. Maybe we'll provide each league and
                                  > division in a separate file or something?

                                  Sounds good. For now, treat the teams as though they were in one very big
                                  division. In my heart of hearts, I would prefer it that way, but you can't get
                                  Bud Selig to listen...

                                  J. B. (Joe) Rainsberger
                                  President, Diaspar Software Services
                                  http://www.diasparsoftware.com
                                  Let's write software that people understand.
                                • Ron Jeffries
                                  ... Wait a minute here! How am I supposed to know who to compare everyone against if it isn t the guy with the highest winning percentage? Ron Jeffries
                                  Message 16 of 19 , Apr 30, 2002
                                  • 0 Attachment
                                    Around Tuesday, April 30, 2002, 12:27:11 PM, J. B. Rainsberger wrote:

                                    > The leader is the team with the highest winning percentage in a division. Note
                                    > that it is possible, although quite rare, for the leader to be 1/2 game back of
                                    > the second-place team. This happens when the top two teams have played nowhere
                                    > near the same number of games.

                                    > Example:

                                    > A 20-10 .667 1/2
                                    > B 23-12 .657 --

                                    Wait a minute here! How am I supposed to know who to compare everyone
                                    against if it isn't the guy with the highest winning percentage?

                                    Ron Jeffries
                                    www.XProgramming.com
                                    You don't need to see my identification.
                                    These aren't the ideas you're looking for. Move along.
                                  • Ron Jeffries
                                    ... I hope we have learned whatever we re trying to learn long before that. If you want to actually /use/ this program you have to pay big bucks. ... I m going
                                    Message 17 of 19 , Apr 30, 2002
                                    • 0 Attachment
                                      Around Tuesday, April 30, 2002, 12:27:11 PM, J. B. Rainsberger wrote:

                                      >> Thing one, I want to negotiate the input to something more reasonable
                                      >> to parse. How about
                                      >>
                                      >> Toronto,7,New York (A),2

                                      > That's fine for now. We'll probably need something to map the original input to
                                      > this input in the future. I'll write up a story for that.

                                      I hope we have learned whatever we're trying to learn long before
                                      that. If you want to actually /use/ this program you have to pay big
                                      bucks.

                                      >> Thing two, what is (A)? How do I know the leagues and divisions?
                                      >> Is this information provided in the game log? If so, where and how? If
                                      >> not, how is it provided?

                                      > (A) distinguishes the Yankees from the Mets, or the White Sox from the Cubs,
                                      > for that matter. (A) denotes "American League" and (N) denotes "National
                                      > League". If it weren't for that stupid interleague play, we wouldn't need them.
                                      > If it makes things easier, you may assume that you'll only see (A) or (N) for
                                      > New York and Chicago.

                                      I'm going to treat the (A) or (N) as part of the name and ignore it
                                      otherwise. I think that should work just fine. We'll see.

                                      Ron Jeffries
                                      www.XProgramming.com
                                      "How do I get to XP?" "Practice, man, practice."
                                    • Ron Jeffries
                                      ... Here s the next installment. It s a long one, not least because it has a complete listing. ========================================== Well, OK, what shall
                                      Message 18 of 19 , Apr 30, 2002
                                      • 0 Attachment
                                        Around Monday, April 29, 2002, 1:42:54 PM, J. B. Rainsberger wrote:

                                        > Team W L Pct GBL
                                        > Toronto 6 2 .750 --
                                        > Baltimore 5 3 .625 1
                                        > Boston 5 4 .556 1 1/2
                                        > New York 3 5 .375 3
                                        > Tampa Bay 1 6 .143 4 1/2

                                        Here's the next installment. It's a long one, not least because it has
                                        a complete listing.

                                        ==========================================

                                        Well, OK, what shall I do now? There aren't really any interesting
                                        problems left that I can see. I guess I'll make a little database,
                                        sort it down and print a report. I wonder where my acceptance test
                                        data is. My customer has been lax in providing a season's worth of
                                        input. I guess I'll just make some up.

                                        Here's my plan. I expect that I'm going to have some kind of object
                                        like a League that holds the teams, but that would depend at least in
                                        part on what form the input will really take, whether I have to know
                                        what league the teams belong to or whether the data is segregated on
                                        arrival, and so on. But I'm not going to create that object for a
                                        while yet, until the code tells me more about what it wants to be. So
                                        I'll probably build something more on the class side of Team for now.
                                        We'll see after I write the test.

                                        def testReport
                                        trueReport = ''
                                        database = Hash.new
                                        Team.update(database,'Toronto,7,New York,2')
                                        Team.update(database,'Boston,3,Tampa Bay,4')
                                        Team.update(database,'Baltimore,2,Chicago,5')
                                        Team.update(database,'Toronto,7,Tampa Bay,2')
                                        Team.update(database,'Boston,3,Chicago,4')
                                        Team.update(database,'Baltimore,2,New York,5')
                                        report = Team.report(database)
                                        puts report
                                        assert_equal(trueReport,report)
                                        end

                                        Here's a case where I don't know the answer and don't want to. I'll
                                        just create the report and verify it after its done. So I'll just
                                        print it.

                                        Here's what I started to write for Team.report:

                                        def Team.report(database)
                                        leader = Team.leader(database)
                                        teams = Team.sort(database)
                                        teams.each {}
                                        end

                                        Clearly I need an object here to send messages to. Also I need the
                                        teams to know their names or I can't print them. I'll try to hold out
                                        a bit longer ...

                                        I coded leader as:

                                        def Team.leader(database)
                                        candidate = database[database.keys.first]
                                        database.each_value { | team | candidate = team if team.winsAboveEven > candidate.winsAboveEven }
                                        candidate
                                        end

                                        which I think will work. Now I need to sort the teams. Also they still
                                        don't have names. I'll sort first

                                        def Team.sort(database)
                                        database.values.sort { | a b | a.pct < b.pct }
                                        end

                                        Of course pct isn't defined yet, but it's in there. I'll extract it.
                                        ... OK, there were a few bugs there. I forgot how sort works in Ruby.
                                        So the code is:

                                        def Team.sort(database)
                                        database.values.sort { | a, b | a.pct <=> b.pct }
                                        end

                                        def basePctString
                                        return sprintf("%1.3f",pct)
                                        end

                                        def pct
                                        (@..._f / (@wins + @losses))
                                        end

                                        The teams just print as object prints:

                                        #<Team:0x2afff30>
                                        #<Team:0x2b00038>
                                        #<Team:0x2b00110>
                                        #<Team:0x2b00008>
                                        #<Team:0x2afff00>
                                        #<Team:0x2b00140>

                                        I need to get the teams named, and printing something more reasonable:

                                        The simple print is

                                        def to_s
                                        "#{@name} #{@wins} #{@losses}"
                                        end

                                        and I need to add the names. I'll do that in the database update
                                        method I think, at least for now. Wait and see, I have to look at the
                                        code. The right thing is to put the name in all my tests, too ...

                                        OK. There were a few changes to the tests, and I added the name as an
                                        instance variable in initialize. Here's the whole program:

                                        class GamesBackTest < TestCase

                                        def testEven
                                        team = Team.new("a", 0,0)
                                        back = team.gamesBack(team)
                                        assert_equal(0,back)
                                        end

                                        def testAB
                                        teamA = Team.new("a", 5,2)
                                        teamB = Team.new("b", 4,4)
                                        back = teamA.gamesBack(teamB)
                                        assert_equal(1.5, back)
                                        assert_equal('1.500', Team.formatBack(back))
                                        end

                                        def testPct
                                        team = Team.new("a", 6,2)
                                        assert_equal('.750', team.pctString)
                                        end

                                        def testPerfect
                                        team = Team.new("a", 8,0)
                                        assert_equal('1.000', team.pctString)
                                        end

                                        def testLosers
                                        team = Team.new("a", 1,99)
                                        assert_equal('.010', team.pctString)
                                        end

                                        def testTeamsFromGame
                                        gameString = 'Toronto,7,New York,2'
                                        database = Hash.new
                                        Team.update(database,gameString)
                                        assert_equal(1, database["Toronto"].wins)
                                        assert_equal(1, database["New York"].losses)
                                        Team.update(database,gameString)
                                        assert_equal(2, database["Toronto"].wins)
                                        assert_equal(2, database["New York"].losses)
                                        end

                                        def testReport
                                        trueReport = ''
                                        database = Hash.new
                                        Team.update(database,'Toronto,7,New York,2')
                                        Team.update(database,'Boston,3,Tampa Bay,4')
                                        Team.update(database,'Baltimore,2,Chicago,5')
                                        Team.update(database,'Toronto,7,Tampa Bay,2')
                                        Team.update(database,'Boston,3,Chicago,4')
                                        Team.update(database,'Baltimore,2,New York,5')
                                        report = Team.report(database)
                                        puts report
                                        assert_equal(trueReport,report)
                                        end

                                        end

                                        class Team
                                        attr_reader :name, :wins, :losses
                                        def initialize(name, wins, losses)
                                        @name = name
                                        @wins = wins
                                        @losses = losses
                                        end

                                        def Team.formatBack(aNumber)
                                        sprintf("%1.3f", aNumber)
                                        end

                                        def Team.update(database,string)
                                        team1Name, team1Score, team2Name, team2Score = string.split(',')
                                        team1Score = team1Score.to_i
                                        team2Score = team2Score.to_i
                                        return if team1Score == team2Score
                                        Team.updateWinLoss(database, team1Name, team1Score, team2Score)
                                        Team.updateWinLoss(database, team2Name, team2Score, team1Score)
                                        end

                                        def Team.updateWinLoss(database, teamName, teamScore, otherScore)
                                        team = database.fetch(teamName) { |name| Team.new(teamName, 0,0) }
                                        team.update(teamScore, otherScore)
                                        database[teamName] = team
                                        end

                                        def Team.report(database)
                                        leader = Team.leader(database)
                                        teams = Team.sort(database)
                                        teams.each {}
                                        end

                                        def Team.leader(database)
                                        candidate = database[database.keys.first]
                                        database.each_value { | team | candidate = team if team.winsAboveEven > candidate.winsAboveEven }
                                        candidate
                                        end

                                        def Team.sort(database)
                                        database.values.sort { | a, b | a.pct <=> b.pct }
                                        end

                                        def update(myScore,otherScore)
                                        if myScore > otherScore
                                        @wins = @wins + 1
                                        else
                                        @losses = @losses + 1
                                        end
                                        end

                                        def gamesBack(aTeam)
                                        return 0.5 * (self.winsAboveEven - aTeam.winsAboveEven)
                                        end

                                        def winsAboveEven
                                        return @wins - @losses
                                        end

                                        def pctString
                                        base = basePctString
                                        base = base[1..4] if base[0,2] == '0.'
                                        return base
                                        end

                                        def basePctString
                                        return sprintf("%1.3f",pct)
                                        end

                                        def pct
                                        (@..._f / (@wins + @losses))
                                        end

                                        def to_s
                                        "#{@name} #{@wins} #{@losses}"
                                        end
                                        end

                                        The output from testReport looks like this:

                                        Baltimore 0 2
                                        Boston 0 2
                                        New York 1 1
                                        Tampa Bay 1 1
                                        Chicago 2 0
                                        Toronto 2 0

                                        So we see that I'm sorting in increasing order of winsOverLosses. I'll
                                        reverse the sense of the compare:

                                        def Team.sort(database)
                                        database.values.sort { | a, b | b.pct <=> a.pct }
                                        end

                                        That does the trick. Now I still have to add the comparison and the
                                        percentage. I can just add the percentage to the printout, so I'll do
                                        that first.

                                        def to_s
                                        "#{@name} #{@wins} #{@losses} #{pctString}"
                                        end

                                        Those two changes give this report:

                                        Toronto 2 0 1.000
                                        Chicago 2 0 1.000
                                        New York 1 1 .500
                                        Tampa Bay 1 1 .500
                                        Boston 0 2 .000
                                        Baltimore 0 2 .000

                                        That looks about right. Now let's see about getting GBL. That'll be
                                        tricky, I will have to pass in the leader team. (I don't know which
                                        team is leader ... never tested that feature. I guess I'm about to.)

                                        I updated report:

                                        def Team.report(database)
                                        leader = Team.leader(database)
                                        teams = Team.sort(database)
                                        report = ''
                                        teams.each {
                                        | team |
                                        report << team.reportAgainst(leader)
                                        report << "\n" }
                                        report
                                        end

                                        Note that I loop over each team, asking it to report against the
                                        leader. reportAgainst looks like this:

                                        def reportAgainst(leader)
                                        string = self.to_s
                                        string << ' '
                                        string << self.gamesBack(leader).to_s
                                        string
                                        end

                                        And the report looks like this:

                                        Toronto 2 0 1.000 0.0
                                        Chicago 2 0 1.000 0.0
                                        New York 1 1 .500 -1.0
                                        Tampa Bay 1 1 .500 -1.0
                                        Boston 0 2 .000 -2.0
                                        Baltimore 0 2 .000 -2.0

                                        Modulo the formatting, it's a done deal. All the interesting problems
                                        I can think of are solved. Maybe after I rest a while, I'll refactor
                                        to make a League or similar object, to get rid of those class methods.

                                        Questions?
                                        ======================

                                        Ron Jeffries
                                        www.XProgramming.com
                                        Steering is more important than speed,
                                        in driving and in software development.
                                      • Bryan Dollery
                                        Hi Brian, ... Never is a long time. I d be willing to bet that if a customer wanted this sort of bulletproofing, and forgot to mention it, he d notice at the
                                        Message 19 of 19 , May 1, 2002
                                        • 0 Attachment
                                          Hi Brian,

                                          > > I always code to the mainline. If this program has to be bulletproof,
                                          > > i.e. deal with bad input, I probably want a story for that.
                                          >
                                          > Ron, do you tell your customers this up front? I could see a customer
                                          > expecting this kind of error checking "for free". Then he would never
                                          > specifically ask for it.

                                          Never is a long time. I'd be willing to bet that if a customer wanted this
                                          sort of bulletproofing, and forgot to mention it, he'd notice at the start
                                          of week 4 when he's examining the first release of the system that it
                                          hasn't got it.

                                          This would provide feedback to him that he hasn't fully communicated his
                                          intent, and so a conversation will ensue in which he would fully specify
                                          his requirements - including bulletproofing. From that point on the
                                          developers can factor the extra time for this style of code, and the
                                          customer can choose to pay for it or not.

                                          Cheers,

                                          Bryan
                                        Your message has been successfully submitted and would be delivered to recipients shortly.