• Post Reply Bookmark Topic Watch Topic
  • New Topic
programming forums Java Mobile Certification Databases Caching Books Engineering Micro Controllers OS Languages Paradigms IDEs Build Tools Frameworks Application Servers Open Source This Site Careers Other Pie Elite all forums
this forum made possible by our volunteer staff, including ...
Marshals:
  • Tim Cooke
  • Campbell Ritchie
  • paul wheaton
  • Jeanne Boyarsky
  • Ron McLeod
Sheriffs:
  • Paul Clapham
  • Devaka Cooray
Saloon Keepers:
  • Tim Holloway
  • Carey Brown
  • Piet Souris
Bartenders:

Using TDD and trying to make a simple calculator.

 
Ranch Hand
Posts: 87
2
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
I am trying to figure out how to write the next test, I am holding back from just coding the game class and main method. There has got to be some small tests to do.

Thinking about design, I like your oneTwoThreeShoot and I feel this should have picked a random Gesture. This makes me want to make another randomTest like before except call it through this method and new class. After making this pass, I would want to assert a test through another method which checks our gestures and produces a score. How does this sound?
 
Sheriff
Posts: 17734
302
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Michael Bruce Allen wrote:I like your oneTwoThreeShoot and I feel this should have picked a random Gesture.


Thanks, but it does; see the second statement (line 3 below).

It makes sense to ask the Gesture enum to give a random value since it has the most intimate knowledge of the details of all the possible values. Have you tried to implement that method? It's a one-liner.
 
Junilu Lacar
Sheriff
Posts: 17734
302
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Michael Bruce Allen wrote:
Thinking about design, I like your oneTwoThreeShoot and I feel this should have picked a random Gesture. This makes me want to make another randomTest like before except call it through this method and new class. After making this pass, I would want to assert a test through another method which checks our gestures and produces a score. How does this sound?


There comes a point where I have unit tested the program so much that the high-level code just becomes a conglomeration of the sample code snippets that can be picked out of the test code. As such, I don't worry too much about testing the high-level code. Instead, I concentrate on making sure each method is concise and abundantly clear in its intent.

C.A.R. Hoare, the 1980 Turing Award winner, wrote: "There are two ways of constructing a software design: One way is to make it so simple that there are obviously no deficiencies, and the other way is to make it so complicated that there are no obvious deficiencies. The first method is far more difficult." See The Emperor's Old Clothes

That's what I do as an alternative to testing the high-level code: try to make the code so simple that there are obviously no deficiencies. There's not a whole lot you can mess up when your methods are 1 to 5 lines long and have a high level of abstraction. I'll still run some tests manually but I seldom feel the need to automate them all. There's kind of a fine line to walk when deciding how much to cover with automated tests and at what level to stop and be satisfied with manual tests and clean, simple code. Being able to strike a good balance comes with experience and the decision is guided mostly by gut feel and perhaps a touch of hubris. Luckily, I haven't gotten into too much trouble with the choices I've made over the years.
 
Michael Bruce Allen
Ranch Hand
Posts: 87
2
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Oh sorry, thats a typo. I was trying to walk you through my thought process and see if it is right. Like I said, I was holding back from just coding the rest of it without tests. I want to make sure and not lose focus.

What I meant was I liked your oneTwoThreeShoot() and I liked the idea that it only deals with picking a random gesture. So I can create a test for this but how?

i mean the method doesnt return anything and I feel the next test I would be instantiating a class with a main method, creating a oneTwoThreeShoot() method, creating a instantiation named computer.. Normally, I would be fine with this, but isnt this too much?

?
How can I see the next small test without giving me the complete answer maybe just nudge me. Or is it ok to instantiate all these in one test?
 
Junilu Lacar
Sheriff
Posts: 17734
302
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Michael Bruce Allen wrote:
i mean the method doesnt return anything and I feel the next test I would be instantiating a class with a main method, creating a oneTwoThreeShoot() method, creating a instantiation named computer.. Normally, I would be fine with this, but isnt this too much?


Long story short, I don't feel a need to cover the main method and all the high-level game code with automated tests. I'll run a few manual tests on it but as long as most of the details have been covered already, the simplicity and clarity of the high-level code is good enough to satisfy me that it's working as it should.

There is a diminishing return on investment after reaching a certain point in test code coverage. For me, that point is about 85%. The high-level code usually belongs to the 15% of the code that I don't worry too much about covering with automated tests. Trying to get 100% coverage is wasteful, IMO. Again, you have to figure out when you have it "good enough".
 
Bartender
Posts: 15737
368
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
I'm thoroughly enjoying this thread. Both of you have a cow.
 
Junilu Lacar
Sheriff
Posts: 17734
302
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
What I love about this exercise, besides its simplicity, is that there seems to be quite a few different options for the design that different people can come up with. I've seen this in my workshop and it's again evident in this thread.

Like a survivalist in one of those reality TV survival shows, taking on the challenge of doing TDD can feel a lot like you're getting dropped off in a small clearing in the middle of a large wilderness and told to find your way to the nearest town, with nothing more than a compass, a few basics tools, and your wits. The most important of these, of course, are your wits. Because there are only some very general guidelines to follow (Red-Green-Refactor, SOLID, DRY, SLAP, GRASP, FIRST, clean code practices), you have to learn how read the terrain, become acutely aware of your surroundings, the lay of the land, and your current situation in relation to it all, while keeping yourself able to rapidly adapt to the changing circumstances and keeping yourself safe from harm and confusion.

The great thing about TDD though is that you're building up a safety net as you go, a breadcrumb trail so to speak, embodied in all the tests that you write. There's always the danger of getting even more lost, losing your bearings, or making a wrong turn but despite all this, if you just keep your wits about you and not panic, the tests will always be there to help you get out of trouble and back on track to where you want to be heading.

TDD may not be as physically taxing as surviving out in the wilderness but it can be just as mentally demanding. It certainly requires you to have courage and conviction in what you are doing to be able to push through those moments of self-doubt which inevitable arise and persevere in your quest. The rewards are great for those who do persevere and the feeling of accomplishment you get when you see that final green bar and say "Yup, that'll do. That'll do..." is just as sweet as if you had finally broken through a tree line and found yourself staring at a paved road.

Thanks to the OP, Michael Bruce Allen, for starting a great conversation and reading through my long-winded responses. I do enjoy these a lot and I'm glad you've gotten something out of it, hopefully.
 
Michael Bruce Allen
Ranch Hand
Posts: 87
2
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Well thats pretty cool, thanks for the award! I really appreciate all the advice. As with TDD, SOLID, DRY, Law of Demeter, SLAP, First and now GRASP (which I need to look up), these processes are really nice. As a guitar play and teacher for 7 years, if there is anything I have learned about who learns and who doesn't, it is definitely the people who are willing to have a bit of structure, and interestingly enough some people figure out the structure more naturally then others, but this all reminds me of the same thing. Some people want to move quick and be sloppy because they feel its a waste of time to slow down (to speed up) and I think its a bit silly. I am lucky to of learned this and I have the patients for it ONLY because I learned about it. Even with music, I just started a weekly jam session and improv practice and this deals with a few levels of exercise. The deal is, if you come, you have to be willing to learn and practice only the new things we learned for the first part so: The first 30 minutes is studying chords and arpeggios, and learning why, The next 30 minutes is playing and improvising using ONLY the stuff we just learned (this is the hard part for most people and this is where the attitude usually comes), the last 30+ minutes is freedom to do whatever. Now, in programming I found that it compares to the practice what you just learned, so for instance, TDD. I have seen arguments from people, I have heard people say that they do not need it, I have heard that they don't have time, but being in my 30's, I see clearly the benefit of what I will become of it, I do not have a bad attitude towards it. Me as a musician I think the attitude is funny. Especially the people that say "Oh I only play by ear" and then when you jam with them and ask "hey what was that chord after the the Am7" and they say, "oh yeah, I only play by ear" and you say, "ok well that chord right there is an Am7" and then they say, "Oh yeah, but I only play by ear so I don't know these things" and I am like, "well bro, I just told you that is an Am7 so now you know". People like that LOL. They just do not want to learn. Besides that guy is not as good as he thinks. Ok that might have gotten off track, but what I am saying is attitude is important and a positive open mind comes when you really love something truly. And for Mathematics, proofs, statistics, and programming, this is all amazing stuff.


Ok... back on track here: Would it be a good idea to start adding the new functionality of lizard and spock yet?
 
Junilu Lacar
Sheriff
Posts: 17734
302
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
A lot of good programmers are also pretty good musicians, too. Not that I could ever do it for a living but modesty aside, I'm not too shabby with a guitar. If I'm not mistaken, the same areas of the brain are exercised when you play music as when you are programming: the areas that deal with patterns, logic and mathematics, as well as the abstract and creative thought processes.

If you have the basic RPS program working, go ahead and create another branch to add the Lizard and Spock gestures to the mix.
 
Michael Bruce Allen
Ranch Hand
Posts: 87
2
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
I am thinking that this project is at least violating the open / close principal.

So the first thing I can think of with my limited knowledge is the random() method in Gesture. nextInt() has a fixed amount so I change it to


But this does not solve it all...

I do not like this next code at all, there has got to be another way...


Must the complexity get so overwhelming as you add more weapons? This type of game works with an odd number of weapons and each weapon must have an opportunity to beat exactly half of other weapons as well as lose to the other half. So is this if statement really the best?

I dont know exactly how to use this, but what if you created a constructor for the enum that contained exactly 3 vales, the first value is its own value and the other values are the number of the weapons it trumps. Or some way of doing something like this. Ill branch off on experimental for now, but I am going to try this.
 
Junilu Lacar
Sheriff
Posts: 17734
302
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Look it up on Wikipedia, there's a formula that you can use with specific rank values assigned to the different gestures.
 
Michael Bruce Allen
Ranch Hand
Posts: 87
2
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Junilu Lacar wrote:Look it up on Wikipedia, there's a formula that you can use with specific rank values assigned to the different gestures.



Cool! I will check it out when I am off work tonight!

I am pretty proud of this refactoring I have done. I did this so that adding the new gesture choices would be easier. I have an idea for adding a Gesture list to the 2nd perimeter of the constructor.. later though.

I am pretty sure TDD saved my ass, because I started going crazy with code, got lost, then reverted back to a previous commit and started with tests. The test kept me from going to far and staying on track. I realized this when I added the List feature for the second constructor perimeter. This NOT what I was set out to do - yet. I was only working on changing the random method at the time. But I remembered TDD and went back and started with simple test, it kept me on track. I plan to still implement the List in the constructor of Gesture anyway (unless I learn something else from the wiki search idea you sent me).

here is the git link and code below. Please feel free to be hard on me about what I have done. Its ok, I appreciate criticism.
https://github.com/CodeAmend/RockPaperScissors/commits/extendExperiment

this is what I came up with.

EDIT: I see that losesTo doesnt make sense, I reversed the roll and did not refactor yet.


As you can see from the removeSwitch methods, I changed this code as a result. And the difference, I believe, is more extendable.

This is a link to my previous version of Gesture before I merged the new ideas with master.
https://github.com/CodeAmend/RockPaperScissors/blob/d39a00b4ef9e4c0e6d90b0c7dc8333352d6ec213/src/com/rockpaperscissors/Gesture.java


this code is the current commit on master
 
Junilu Lacar
Sheriff
Posts: 17734
302
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Ah, where to start...

There's a lot I have to say about your latest code. I'll start with the easier stuff.

As I pointed out before, this can be dramatically shortened:

There's no need for the code to be this wordy. You already have an expression that evaluates to true or false so the return statements on lines 16 and 18 are redundant. There is a flaw in this logic, however, and it has to do with the semantics of the word "beats". I'll get to that in a bit.

This does the same thing that the above code does:

or to keep closer to your version:

There's a lot of boolean logic going on in these few lines of code and it's subtle because it involves the meaning of "beats" and "losesTo". The "not equal to" doesn't help make the code easy to understand either. This small method is quite the mini brain twister actually.

I'm going to backtrack a bit and remind you of a few things:

1. Testing reveals the presence, not the absence of bugs.
2. All tests should be passing before you start refactoring.
3. As much as possible, avoid double negatives and tricky/verbose boolean expressions.

Having the concept and "beats" and "losesTo" in the same method leads to some flip-flopping logic in the brain. This can easily confuse you and is the source of many subtle bugs. The bug here is that both versions of the code above have beats() returning true when the gestures are the same. This is wrong. When you have a tie, beats() should be false. Attempts to fix this locally will be frustrating and not lead to cleaner code. The main problem is the introduction of the "losesTo" concept.

First of all, losesTo is redundant with beats. They basically say almost the same thing and it would probably be fine if you had one or the other. But to use losesTo inside beats complicates things. It's like trying to mix water and oil -- they just don't want to be together.

When you changed the code, did you run your tests again to make sure they didn't fail? Or did you change your tests?

Refactoring requires a lot of discipline. You either change your code or you change your tests but never both at the same time. Doing so complicates things because you have multiple moving parts that affect the balance of your software.

If you only changed the program code and left your test code alone, there should have been a test that failed. If no test failed when you changed the code and introduced the bug, then you were missing a test. I suppose now we could have used a test that checked for equality:

This test fails with the above implementations of beats() and reveals the bug.

To be safer, this set of tests may be preferable to the above:

While we might be able to say that there is some redundancy in these, that can be addressed later.
 
Junilu Lacar
Sheriff
Posts: 17734
302
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

I wrote:You either change your code or you change your tests but never both at the same time. Doing so complicates things because you have multiple moving parts that affect the balance of your software.


Let me expound on this a little.

When you start of a new TDD cycle on existing code, your tests should all pass and your code should be well factored. When you add a new test or change a test, it should fail. That's the small "shift" that happens in the balance of the code. Now you go to the program code and make the failing test pass. That's the second shift. You went from good to bad with a failing test. Then from bad to good with a change in implementation. Balance shifts one way or another but you should try not to go way off balance for very long. This keeps you on the straight and narrow, never straying too far off the edge.

Now let's say you wanted to change your design, like introduce a "losesTo" concept. Looking at "losesTo" however, it's something that only exists inside Gesture. The concept of losesTo never actually gets out of Gesture but rather there's this funky flip-flop boolean logic that happens before a value comes out of the beats() method. This is where you got in trouble. You added more conceptual weight to the code with the introduction of the losesTo concept but there was no test that would fail and signal that the balance of the code was affected nor was there a test that was failing that started to pass as a result of the new change that you made. In short, you went from good to bad without a clear path back to the good.

I'm not really sure what was bugging you about beats() and why you felt you needed to introduce the losesTo concept. In my opinion, you muddied up the logic and complicated things by doing so. The code did not become simpler or clearer with the changes you introduced. I also fail to see how this would have helped make it easier to add the Lizard and Spock gestures later.

The removeSwitch* test methods you wrote don't help shed any light on the thought process either and I find them quite baffling.

Can you walk me through your thought process when you made these changes?
 
Junilu Lacar
Sheriff
Posts: 17734
302
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

I wrote:
There's a lot of boolean logic going on in these few lines of code and it's subtle because it involves the meaning of "beats" and "losesTo". The "not equal to" doesn't help make the code easy to understand either. This small method is quite the mini brain twister actually.


Something about this was bothering me and I just realized what it was, which proves either that this is indeed a little brain twister or that I'm just full of it -- maybe a little of both. The reason it bothered me was because I have written flip-floppy logic before that seemed perfectly clear and worked perfectly:

This works perfectly but why is this fine and what you wrote buggy? I realize now that done/not done is binary in nature. Underneath, it checks the emptiness of a collection: if the collection is empty, you're done, otherwise, you're not done. On the other hand, beats() is not based on a binary set of values. There are three cases: win, lose, tie. beats() will be true when it's a win and false when it's a loss or tie. The complication arises when you introduce losesTo in beats because losesTo is true only when it's a loss, not when it's a loss or tie. So there's a difference between the opposite of beats (when it's a loss or tie) and losesTo (when it's a loss). So it's wrong because the mathematical logic doesn't jive with the semantics of the words (beats is the opposite of losesTo and vice versa).

So I guess in a very Zen way, what I said about throwing off the balance of the code by introducing the losesTo concept was true.
 
Junilu Lacar
Sheriff
Posts: 17734
302
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
I think I have an inkling of what your thought process was in writing those removeSwitch* tests. You have this code:

This isn't very pretty and I see why you wanted to clean it up. However, the way you went about cleaning it up was a little, well, unorthodox. First, don't name tests after things that you want to refactor in the code and try to avoid names that hint at implementation details. This way you'll avoid the problem of your test code being misleading if for some reason you change the implementation. Always think of isolating the external from changes to the internal.

Test names should help describe behaviors in your software. Go back to the test names that I use. They are all about behaviors: rock_smashes_scissors, paper_covers_rock, random_produces_all_possible_values, etc. all describe things that the software does, not what the programmer did while writing the software.

Here are some hints that will help you clean up the random() method and turn it into a one-liner:
1. You don't have to instantiate Random every time you call random(). You can set up a single instance that is initialized when the Gesture class is loaded.
2. Gesture.values() returns an array containing all the Gesture enum values, in the order that they appear in source code, referred to as "source order".
3. Since it is an array, Gesture.values() has a length. I think you already know this.
4. As with all arrays in Java, Gesture.values() is a zero-based array
5. Random.nextInt(n) fits in very nicely with the above facts.
 
Junilu Lacar
Sheriff
Posts: 17734
302
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Likes 1
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Allow me to walk you through my thought process when I'm refactoring. This isn't directly related to any of the code we're discussing right now but I want to give you an idea of the kind of things I focus on when I'm doing TDD and in particular, when I'm refactoring code.

Take this test code:

You might read it, run it and see that it passes and say "Ok, I'll move on now." I won't, because I refactor ruthlessly.

The first thing I ask myself is "Are there any names that can be made better?"

I read the code out loud and listen carefully. This may seem a little strange to you but for me, hearing the code read out loud helps me detect inconsistencies better than when I just read the code silently. It might just be me but I think there's a biofeedback loop involved that just isn't exercised when I don't actively use my sense of hearing. I think this is why I like pair and group programming because I hear other people read and talk about the code and I find that helps me smell out, or hear out as the case may be, problems much easier.

One of the first things I hear myself say is "I have a set of gestures called allMoves..." and immediately I detect an inconsistency with "allMoves" and "gestures". So, I rename the variable:

Next, I notice that I have an implementation detail in the name "random_gesture_has_three_possible_values", namely, "three". I really shouldn't leak the fact that there are exactly three enum values. That's not really relevant to what I'm trying to describe. All I really care about is that all possible values will eventually be produced by the random() method. And as I say this, I stumble upon a better name:

Next, I look at the for-loop condition. Do I really need to short circuit the loop? Does it really matter if I iterate more times than necessary? The difference between 20 and 50 loops in terms of execution time is negligible so I'll take the performance hit in favor of cleaner code:

Next, I'll look at that 50 in the loop and feel guilty about it. I'll add an implementation note, citing the Birthday Paradox formula.

So there you have it. The code that should be good enough to say what I want to say in this test.

Note: I didn't mention it above but every change I make is punctuated by a running of the tests to make sure that they still pass. When you're refactoring code, re-run the tests after every small change so you know you didn't mess anything up. Even if it's just a simple rename, I still re-run the tests. It's an investment that pays back huge dividends in the long run. If for some reason my change breaks the code and the tests fail, a few keystrokes to undo the small change quickly gets me right back to a Green Bar and I can start over from the last time everything was still good.
 
Junilu Lacar
Sheriff
Posts: 17734
302
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Likes 1
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Of course, I had to re-read that last version and come up with a few more refactorings to really say what I want to say clearly:

And finally,

When you read line 13 out loud, it sounds like you are making a very clear, succinct statement about the program's behavior. ¯\_(ツ)_/¯
 
Michael Bruce Allen
Ranch Hand
Posts: 87
2
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
These last two posts are my favorite post I have ever read. I have learned so many valuable lessons from them. What would you think of me restarting the project and using what I learned? Is that not a good idea to start over? I am open to either way.

Previously you asked what I was thinking and here is what I was thinking. AND I refactored the code using the advice from above. I am not going to assume I did it all correctly, but does this at least look better? Btw... if beatableID was an array then when I add new gestures, it is as simple as adding an array with each new gesture with the ID's of the beatable gestures.

 
Michael Bruce Allen
Ranch Hand
Posts: 87
2
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
I have added LIZARD to the enum Gesture. The lizard can only kill one of the items on this list, so this worked out to put the code as LIZARD(4, 2).
Here is the enum with added LIZARD:


I wrote some tests to see if LIZARD did actually show up as a random choice. And the test also passed. (This is why I was so proud of my refactor, because it was somewhat easily added. Very close to the O in SOLID?
The next test I wrote, I expected to fail and that was does_scissors_beat_lizard()??? It failed. I kinda feel like I am doing this testing right. I have held myself from altering any code until a test failed, Now I think it is time to make the array...

 
Junilu Lacar
Sheriff
Posts: 17734
302
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Michael Bruce Allen wrote:What would you think of me restarting the project and using what I learned? Is that not a good idea to start over? I am open to either way.


That's actually an excellent idea! I have solved this RPS problem many, many times and it seems like every time I try it with someone new, I discover something else I hadn't before.

Start over from scratch with only the lessons learned in the past in mind and as much as possible, forget about the implementation that you had before. Start over as though this were the very first time you've ever tried to solve the problem. There are a number of other "constraints" you can impose on your new start but I suggest for now you just start over and see what happens when you try it with a better understanding of the principles behind each action that you take throughout the process.

Those who know me in these forums might roll their eyes at what I'm about to tell you (that's Ok, I get it, but we're all good virtual friends here ): I study Aikido and have done it long enough to have earned a black belt. It never ceases to amaze me when I work with new students to see the kind of things they can teach me or at least help me discover about the techniques we're practicing together. You see, Aikido practice is usually done in pairs, with the person of higher rank first performing the technique and the person with lower rank receiving it. Then we switch roles back and forth until sensei moves on to the next technique or variation. This gives both people the chance to experience the technique from the giving and the receiving end. And sometimes, maybe even often, I learn more from being on the receiving end of the technique.

That experience in Aikido is not unlike that which I have when pair or group programming, no matter what the other person's/people's level of experience. There's always a give and take, and I learn from the other person just as much as they learn from me.

So go ahead, by all means, start over and see what happens.
 
Junilu Lacar
Sheriff
Posts: 17734
302
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
I'm giving you another cow for your last post. I'll explain with an edit here soon.

The cow is awarded for your curiosity and fearlessness in trying out new things. I see you have adopted the convention I use with the test names. This is something I picked up from other programmers. With this convention, the names are more readable and the JUnit summary reads like an outline for a design specification, assuming you choose good test names.

Now to the matter of the test names you chose. It's great that you're trying out names like does_random_work_with_lizard and does_lizard_beat_paper. Again a little unorthodox, but I love the attitude that it shows you have. You're playing around, trying new things, putting your own spin on it and that's great. However, the question format doesn't fit with the notion of the tests as a detailed design specification. The name is in the interrogative form, and that puts you in different kind of mindset. The test names should be more of the declarative form, stating a desired behavior. The declarative form fits best with the RED BAR / GREEN BAR feedback you get from the testing framework.

Keep experimenting with different ideas though. That's the best way to learn and start getting a feeling for which way is better.

For example, it's a lot easier to see which name is better when you write them all out and see them in code:

IMO, the last one is the best. It's declarative, it's direct, it doesn't leak the implementation detail (using beats() method), and the reader can easily relate to it. I think it's funnier, too.
 
Michael Bruce Allen
Ranch Hand
Posts: 87
2
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
I actually didnt get an answer (if I should restart the whole thing or not) before I started the refactoring, but I did add tests as I added features and feel I follwed TDD better than I ever have.
My goal was to make the code to where I can easily extend, this is why I refactored random as well as beats. Now we can add any odd-amount of gestures we want and all we need to do is add a enum gesture and add to each array!

Here is what I have for tests as well as code


@Test


Gesture enum
 
Michael Bruce Allen
Ranch Hand
Posts: 87
2
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Junilu Lacar wrote:
That's actually an excellent idea! I have solved this RPS problem many, many times and it seems like every time I try it with someone new, I discover something else I hadn't before.

Start over from scratch with only the lessons learned in the past in mind and as much as possible, forget about the implementation that you had before. Start over as though this were the very first time you've ever tried to solve the problem. There are a number of other "constraints" you can impose on your new start but I suggest for now you just start over and see what happens when you try it with a better understanding of the principles behind each action that you take throughout the process.

Those who know me in these forums might roll their eyes at what I'm about to tell you (that's Ok, I get it, but we're all good virtual friends here ): I study Aikido and have done it long enough to have earned a black belt. It never ceases to amaze me when I work with new students to see the kind of things they can teach me or at least help me discover about the techniques we're practicing together. You see, Aikido practice is usually done in pairs, with the person of higher rank first performing the technique and the person with lower rank receiving it. Then we switch roles back and forth until sensei moves on to the next technique or variation. This gives both people the chance to experience the technique from the giving and the receiving end. And sometimes, maybe even often, I learn more from being on the receiving end of the technique.

That experience in Aikido is not unlike that which I have when pair or group programming, no matter what the other person's/people's level of experience. There's always a give and take, and I learn from the other person just as much as they learn from me.

So go ahead, by all means, start over and see what happens.



Ok, I can start over again. That is not a problem. I still would hope you can look at the code and let me know what you think. Aikido sounds really cool I like that style of teaching, it is more open-minded and I think new ideas form in these times in a dynamic way. Thanks for the cow. I am really thankful for all the responses.

btw... here is my commits for the recent testing process
https://github.com/CodeAmend/RockPaperScissors/commits/master
 
Michael Bruce Allen
Ranch Hand
Posts: 87
2
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
As far as starting again...

I am just starting at the test class and wondering what is the best way to start the test?
And I want to repeat the stuff from before.

assertTrue(ROCK.beats(SCISSORS));

Would this be an ok first test?
Gesture is a good name and beats is a good name. I almost want to forcibly change them just to be different, but why would I? So I feel I am stuck in the old world that I learned from. What is there new to think? This might be the first thing I would want to do, test one of the gestures immediately with another gesture.
 
Junilu Lacar
Sheriff
Posts: 17734
302
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
This snippet of code smells. I don't know if there's a previously-catalogued name for it but for lack of a better one, I'll call it "unnecessary complexity". The code looks busy, and my eyes are busy criss-crossing back and forth between the values as I try to understand their relationship with other values and what the parameters passed to their constructors mean.

There's useful information from Wikipedia, namely:

rock-paper-scissors-Spock-lizard (note the different order of the last two moves) may be modeled as a game in which each player picks a number from one to five. Subtract the number chosen by player two from the number chosen by player one, and then take the remainder modulo 5 of the result. Player one is the victor if the difference is one or three, and player two is the victor if the difference is two or four. If the difference is zero, the game is a tie.



Here's an alternative implementation based on the above information:

I have elided lines 12, 16, and 22 so as not to spoil it for you. Those are all one-liners though. This is the complete Gesture enum for rock-paper-scissors-lizard-Spock (or rock-paper-scissors-Spock-lizard, if you must), all 24 lines of it.

Don't worry that it's not what you came up with though. I just want to give you an idea of how little code you really need. The fun part of this exercise is the journey from the complicated code you came up with and refactoring it ruthlessly to get to the clean, ideal code. There are many lessons to be learned along the way.
 
Junilu Lacar
Sheriff
Posts: 17734
302
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Another thing you should know: most, if not all programmers, including programmers who have many years of experience, have a tendency to write more code than is necessary on the first cut. This is natural. Writers have to go through many rough drafts and refinements of their work before they finally publish. Programmers are no different. We are, after all, authors in our own right.

That's the problem with the programming profession today though: most programmers just spew out the first thing that comes to mind and don't go back and clean up their work. This is the source of much pain and suffering in the world today, both because there are other programmers who come in later and have to maintain the bad code and suffer because of it and because users have to live with the buggy software that results from lack of attention to details.

If you want to be a good programmer—and you have all the marks of someone with a lot of potential to be—you need to care about testing, refactoring, and following good coding and design practices and principles. Don't be like the 80% of the programmers I meet who don't care too much about craftsmanship. If you're going to learn to program, learn to program well, even if ends up just being a hobby for you.
 
Junilu Lacar
Sheriff
Posts: 17734
302
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Michael Bruce Allen wrote:
assertTrue(ROCK.beats(SCISSORS));

Would this be an ok first test?
Gesture is a good name and beats is a good name. I almost want to forcibly change them just to be different, but why would I? So I feel I am stuck in the old world that I learned from. What is there new to think? This might be the first thing I would want to do, test one of the gestures immediately with another gesture.


Don't worry too much about repeating a lot of what you did before. That's fine for now. As for starting with bad names and changing to the names you know are better, I think it's a good exercise to run through just so you can develop and solidify those neural pathways in your head.

As my son's viola teacher always liked to say: "Practice doesn't make perfect. It makes habit. Only perfect practice makes perfect."

So get those reps in for going from bad code to great code.
 
Junilu Lacar
Sheriff
Posts: 17734
302
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Practicing by starting out with bad code and then going to better code, and then even better code also helps you develop your sense for code smells. The more bad code you see and the more you refactor it into better and better code, the better you'll become at recognizing when you have opportunities to improve and knowing the best way to get out of a mess.

Edit: BTW, of all the times I've done this RPS exercise before, this is the first time I've used "Gesture". All the other times, I've used Move. I like Gesture. "beats" was a natural pick though. Other choices I've seen include "defeats" and "winsOver" but they were quickly discarded for the more succinct "beats". Like I said, it seems there always something I learn when I do this yet again.
 
Michael Bruce Allen
Ranch Hand
Posts: 87
2
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator


How and why would you fail this code first? Make it return false? Is this an important step in the thought process?
 
Michael Bruce Allen
Ranch Hand
Posts: 87
2
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
This is from my second test, which failed naturally. This is how I made it pass.

EDIT:: I am past this point so I thought I would share my commit record. I am not editing production code until I write a failing test.
https://github.com/CodeAmend/RockPaperScissors/commits/second-time

My tests....



New code...


////////////////////////////////////
////// Code Update ///////
I am not sure if I need to add this.
It is all tracked on a remote git.

created new testing class for features.


 
Michael Bruce Allen
Ranch Hand
Posts: 87
2
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
I have a few questions:

You mentioned that code readability is important to you. But is really a one line modulo logic really readable?
Wouldn't it be better to have a switch case for readability? Or anything else readable?


I am not a fan of this - the test just passed - I would like to refactor
concidering that this is a static method, declaring Random rand = new Random() outside of the method would call for a static instantiation. I do not like that either.

I feel like this can be all cleaned up, I wrote this with the thought of extending the game later. I will try more tomorrow.

 
Marshal
Posts: 80493
455
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Surely it's . . .You now have a redundant pair of () which I have left.
 
Junilu Lacar
Sheriff
Posts: 17734
302
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Michael Bruce Allen wrote:I have a few questions:

You mentioned that code readability is important to you. But is really a one line modulo logic really readable?
Wouldn't it be better to have a switch case for readability? Or anything else readable?


Great questions. I hope this answer doesn't sound too wishy-washy or flip-floppy.

There are several ways you can achieve readability. The main ways I achieve it is by using well-chosen names that clarify intent, abstraction to hide complexity, eliminating duplication, and simplifying of expressions. When a complicated formula is involved, well-chosen names that clarify intent and abstracting away detailed calculations can only take you so far. The intrinsic complexity of the calculation will remain at some level of the code but you try to push that down and away from high-level code. This is why SLAP (Single Level of Abstraction Principle) is important.

The goal in high-level code is clarity of intent. This is where you get most bang for your buck with the kind of readability you're talking about. At the lower levels of the code, where you can't avoid the complexity of the calculation, the goal is succinctness. A short comment might be in order at that point in the code to explain the formula or point out where more information can be found.

Sometimes it comes down to a matter of the programmer's preference versus what the reader prefers. In the case of the one-liner that involves modulo math, I happen to prefer the brevity and elegance of the relatively simple formula. Plus, it's hidden down deep enough away from the high-level code.

Certainly, there's a balance to be struck but there's really no formula (pun intended) I can give you to follow; you just kind of get a feel for these things after much practice and that manifests as part of your coding "style". Your own coding style can and should change over time, hopefully like good wine or brandy, becoming more refined and having more character as it ages.
 
Junilu Lacar
Sheriff
Posts: 17734
302
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Michael's code:
Much of that is redundant because the boolean expression evaluates to the value you need to return.

Campbell's code is the preferred way to write this:

I would be inclined to remove the redundant outermost parentheses but again, that's just my style.
 
Junilu Lacar
Sheriff
Posts: 17734
302
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Michael's refactoring code diff (from github repo):

I like it. A little unorthodox for me but it still reads out loud nicely: "beats opponent's gesture" is proper grammar. The only niggle is that fraction of a second where the reader has to mentally insert the apostrophe to make it possessive instead of plural. See Campbell's and my previous comment about making this a one-liner though.

I'm guessing/hoping you were motivated to refactor because the parameter definition "Gesture opponentsGesture" looks redundant. Exactly the refactoring that I would have made.
 
Michael Bruce Allen
Ranch Hand
Posts: 87
2
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Campbell Ritchie wrote:Surely it's . . .You now have a redundant pair of () which I have left.



Yeah, I will have to pay attention to that sort of thing. An if contains a boolean, so why use the if if there is only a return boolean. Mentally noted. Thanks.


Junilu Lacar wrote:Michael's refactoring code diff (from github repo):

I like it. A little unorthodox for me but it still reads out loud nicely: "beats opponent's gesture" is proper grammar. The only niggle is that fraction of a second where the reader has to mentally insert the apostrophe to make it possessive instead of plural. See Campbell's and my previous comment about making this a one-liner though.

I'm guessing/hoping you were motivated to refactor because the parameter definition "Gesture opponentsGesture" looks redundant. Exactly the refactoring that I would have made.



Your assumption is correct. I felt it was redundant. Why is it unorthodox? What would you have used?

 
Michael Bruce Allen
Ranch Hand
Posts: 87
2
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Michael Bruce Allen wrote:I have a few questions:
..
I am not a fan of this - the test just passed - I would like to refactor
concidering that this is a static method, declaring Random rand = new Random() outside of the method would call for a static instantiation. I do not like that either.

I feel like this can be all cleaned up, I wrote this with the thought of extending the game later. I will try more tomorrow.



The use of a Random in a static context, means that I can access the Random class methods from the outside of Gesture. This was something you mentioned to me previously, to not instantiate Random everytime Gesture.random() is called (previous project). Though moving it to a HAS-A position in the Gesture class makes the class accessible and I think this is unnecessary.. It seemed that getRandomGesture() needed to be static if I wanted to call it directly from. If I am correct, if I remove the static status then I will have to instantiate an extra copy of the Gesture just to use the getRandomGesture, otherwise that would mean using the user or the opponents Gesture to access getRandomGesture and this doesnt make sense to me.

I like the ability to call Gesture.getRandomGesture() without instantiating Gesture to call that. (now that I look at it Gesture.getRandomGesture() looks redundant)
Though you say that I shouldnt instantiate Random everytime Gesture.getRandomGesture() is called.
If I do this, that means Random rand; in a static context meansI can say opponent.rand... and user.rand...
Something seems wrong.

Still though, I do not like this code and I cannot figure out exactly why.
 
Stephan van Hulst
Bartender
Posts: 15737
368
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
I would recommend having a private static Random in the Gesture class, and a static Gesture.random() method that returns a random Gesture using the static random number generator.

As for naming parameters, when I want to compare this instance to a method argument of the same type, I tend to name the parameter 'other', for instance: compareTo(Foo other) or beats(Gesture other).
 
Junilu Lacar
Sheriff
Posts: 17734
302
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Michael Bruce Allen wrote:
How and why would you fail this code first? Make it return false? Is this an important step in the thought process?



Yes, seeing the test fail is very important. The test code needs to agree with the program code. The test failure tells you that something is off, either in the test or the program implementation or both. Whichever way it may be, there's essentially a misalignment or imbalance between the two.

Let's go back to the "theory or hypothesis" analogy. If the test is a statement of your hypothesis of how the software works, running the test is like running an experiment. The subject of the experiment is the program code. So either your hypothesis is wrong or your subject is not behaving as it should within the conditions set up in the experiment or both. You have to go back and ensure that at least one of them is correct and then do something to bring the other back in line.

In your case, the test/hypothesis appears to be correct: "Paper beats Rock"

If this was the very first thing you coded, then you'd get compilation errors because you don't have any PAPER or ROCK enum values. You fix that by creating the Gesture enum and adding the PAPER and ROCK values. You'll still have a compilation error because Gesture has no beats() method. So you add a beats method and make it return false. Now you have a failing test. Since the test is clearly good, the program code must be off. So you go and fix it. Doing the simplest thing that could possibly work (even if it's obviously stupid and wrong), you make beats() return true. Now you have a green bar when you run the test. The test/hypothesis and the program code are now back in balance. At least as far as the tests and code that you currently have in place is concerned.

Imagine I showed you the outline of an object and you see it as a circle. You might say with some confidence that the object is spherical or at least circular. Imagine that I turn the object so you see it at a different angle and you now see a triangle. You now know that your first observation was only partially correct.

The same kind of thing happens when you do TDD: you specify one aspect of behavior with a test, then implement the code to satisfy only those particular parameters set up by the test. Now you add another test to specify a different aspect of the software. Then make the software conform to those new requirements. And so on and so forth.

This is the mindset and approach taken when you do TDD: whittle away at the software, working one angle at a time until you arrive at the complete product that looks/behaves the way you want it to from all possible angles/contexts.

 
Don't get me started about those stupid light bulbs.
reply
    Bookmark Topic Watch Topic
  • New Topic