Now that the mad rush for the book is over and the carpetbaggers have left our otherwise quiet neighborhood of the Ranch, I'd like to develop a little tutorial. This is actually for the benefit of newbie Chad (see this thread) but I think that a few regulars around here might find it interesting too. The goal is to develop an object-oriented calculator. We have two options: 1) to use the existing code or 2) to start from scratch. The former presents a few more challenges than the latter. However, having recently read about the different levels of learning, I will try to keep it simple and start from scratch at level 1 so that it will be easier to follow my line of thinking. Junilu [ March 09, 2002: Message edited by: Junilu Lacar ] [ August 11, 2002: Message edited by: Marilyn de Queiroz ]
Sounds like a great idea!! I'd love to see what sort of patterns would be involved in this project. I created a calculator in java for a class assignment as well. It was really simple, but would love to see this one. Is the code going to be discussed here? /rick
I'm glad I've piqued some interest OK, here are just some of my objectives for this exercise: 1. Demonstrate test-first programming with JUnit 1a. Introduce the Green-Yellow-Red light game 2. Demonstrate refactoring towards patterns. 3. Learn more about the learning process 3a. See how viewing S/D as a game affects our views/approach. 4. Figure out just how light we can go without compromising the current game or the next game. Of course, I will be posting code as we go along. Get ready for a long haul because I started something like this before and it got kind of unwieldy. I'll start the process proper sometime later tonight. Junilu
Some more objectives: - (Hopefully) give newbies an idea of how to start down the road to developing better OO programs. - Inspire involvement from others in the forum to make this exercise more interactive/collaborative rather than unilateral with me saying do-this-and-do-that. I'd like this to spark good discussions about OO and related topics. Junilu
All right, let's start with a ground rule: Please try to follow this ground rule so that we can keep this thread focused on the main problem: developing an OO calculator. If you want to comment or have a question, don't add it as a reply to this thread directly. Rather, start a new thread and then add a reply here to summarize your question or comment then add a link. For example, I am sure at some point some of you who haven't used JUnit will have questions regarding its setup and such. So, instead of posting your JUnit-related question in this thread, start a new thread in the IDE's and Other Tools forum. Then add a short reply here to link to that thread. It might also be a good idea to add a link to this thread there so you can easily jump back and forth. Junilu
Hi Junilu, Thats really a great proactive idea. I guess i can look forward to follow the whole process of OOAD and OOP.
Ram Dhan Yadav (SCJP, SCWCD, SCJA-I, IBM EC(483))
"We are what we repeatedly do. Excellence, then, is not an act, but a habit."
Joined: Mar 25, 2001
OO calculator -- first "gotcha" Started to play around with some code to prep for Junilu's OO calculator tutorial. Not really certain if a "state machine" is the same thing as the "state pattern," I coded a barebones command-line (none of the GUI stuff yet!) calculator using the state pattern to represent the various operations. That's when I learned that if you want to pass an asterisk as a command-line parameter into a Java program, you have to enclose it in quotes (at least in Windows 2000). Otherwise the command interpreter expands the asterisk out to the list of files in the current working directory before invoking java.exe. Just a heads up to anyone else thinking of doing a similar exercise. [ March 09, 2002: Message edited by: Michael Matola ] [ August 11, 2002: Message edited by: Marilyn de Queiroz ]
Sorry to keep you all waiting--I spent a little time with the family. We're having a very blustery night here in the Midwest and we had to keep the kids company in bed for a bit. So, let's get started shall we? First up, make sure you have JUnit set up and ready to roll. I'll set up a sidebar thread to guide you through any difficulties you have in doing so. Next create a directory for the project, say 'calc'. Create another directory for the test classes, say 'test'. Now before anybody suggests a better way to organize the project, remember that one objective is to demonstrate Refactoring. So, right now, let's just start with this simple structure. As complexity increases and the need arises, we will refactor. OK? [ August 11, 2002: Message edited by: Marilyn de Queiroz ]
OK, the first objective I mentioned above was to demonstrate Test-first programming and introduce the Green-Yellow-Red Light game. Notice I said "demonstrate" and not "explain". So, your first assignment, my young Padawans, is to read Bill Wake's article "The Test-First Stoplight". Also, dig around http://www.junit.org for articles about Test-First programming.
The thirdsecond objective is to learn more about how we learn. So I've set up another sidebar in the Process Forum (And yes, it's there to drum up activity in my own bar. Hey, I never said I was above blatant self-promotion ) Edit: we'll get to the second objective a little later. Well, gotta go get some shuteye. See you folks in the morning... [ March 10, 2002: Message edited by: Junilu Lacar ] [ August 11, 2002: Message edited by: Marilyn de Queiroz ]
Some of you may be thinking "Just post some code already!" I will do that shortly but first, one more little detail: What exactly are the requirements for our calculator? Normally, we'd have a user to answer this question. Having none, however, I am going to also simulate the kind of input developers normally get from users: sketchy and incomplete. We've all met (or will meet if you're just new to this business) them. So here are the simulated first cut requirements from our imaginary stereotypical user: - Need to be able to do basic mathematical operations of addition, subtraction, multiplication and division. - Need to be able to CE (clear a wrong entry) - Need to be able to C (clear everything)
Step 1 Finally, we get to see some code! Now for those of you thinking "Huh?! What about doing design first?" here's a sidebar. Test-first programming practice dictates that you write tests before writing your actual classes. So, we start with by creating a TestCase or more accurately, a subclass of TestCase. We make this class a member of the test package. See Step 1 listing Save this code as OOCalcTest.java in the test directory. Assuming you have your environment setup properly, this should compile successfully. Normally, we'd let JUnit tell us if we had a green by running the tests. We won't run JUnit just yet though so this successful compile will be our initial Green light. [ March 13, 2002: Message edited by: Junilu Lacar ] [ August 11, 2002: Message edited by: Marilyn de Queiroz ]
Step 2 OK, so now that we have an initial green light, we make the next move: write a test to get a yellow light (compile errors because we haven't written the actual class). Sorry but I have to start another thread to show the progression of the listing. See Step 2 listing for Yellow Light #1. [ March 13, 2002: Message edited by: Junilu Lacar ] [ August 11, 2002: Message edited by: Marilyn de Queiroz ]
Step 3 Next, we write the first cut for the OOCalc class. Save and compile. Now we go back to OOCalcTest. We try compiling it again and we succeed! hoo-wah! But wait! Didn't we just go from Yellow to Green? Shouldn't we be at Red now? Oops, we were supposed to write a test but all we did was add a fixture (the private OOCalc) and setUp and tearDown methods. We're not really done with this step yet. [ March 13, 2002: Message edited by: Junilu Lacar ] [ August 11, 2002: Message edited by: Marilyn de Queiroz ]
Step 4 So now what? Well, now we put the designer hat on. But instead of going off into a little dark room and emerge some time later with an armful of elaborate diagrams, we simply stick to our keyboards and think: "How do I want to test the OOCalc class?" So, instead of looking for patterns or creating UML diagrams, our first design objective is to make our class testable. Test-driven design, if you will. Now I've heard folks say this is an "ass-backwards" approach and that may well be so. But if you think about it, it actually forces you to take a baby step instead of making that one big leap into the unknown. Besides, if we did more things ass-backwards, wouldn't we all be a little more careful and make sure we know exactly where we're going? Reflection time: Have you ever had that feeling of being overwhelmed by requirements and design possibilities? Have you ever experienced analysis paralysis or design paralysis? What moves did you make to get out of it and what was the end result? Here are some of my reflections [ March 13, 2002: Message edited by: Junilu Lacar ] [ August 11, 2002: Message edited by: Marilyn de Queiroz ]
Step 4 (cont.) To pick up again after that brief moment of reflection, we had gone from Green (OOTestCalc compiles) to Yellow (OOTestCalc compile error because of missing class). So we've fixed that by writing the first cut of OOCalc. Now we run JUnit on test.OOTestCalc and get a red bar. JUnit reports one failure: no tests found. Our first Red light. Now we write a test. How do we want to test OOCalc? Naturally, the first thing we would probably think to do is to give it some input and see if it gives us the expected output. Sounds like common sense, right? (The following is my side of an imaginary conversation with somebody pairing with me) What? How do we test the UI? You were thinking of a user interface already? Oh, so you were forming the theory that the first thing we'd want to test is whether the UI looks right... (Leading him on to my theory...) Well, does the faceplate and buttons on a calculator do the actual calculations? Exactly! So, the UI is really just a way to manipulate the actual machine! Let's work on the faceplate later and concentrate on the circuitry -- that's far more interesting. You know what, just for kicks let's start ass-backwards and see what the calculator should do if we give it an input string of "=" (Notice I don't say "if we press the '=' button"). Naturally, if this were the first time we turn it on, we'd expect it to do nothing, right? How does it indicate that it does nothing? Well, normally when we turn on a calculator, the display shows 0. So if we input "=", then it should still return 0. Let's write a test for that: Listing of OOCalcTest with testInitialResult() Save and compile. Compiler error. OK, back to Yellow light. Are we doing anything wrong? We went from Red to Yellow. We'll ignore that for now since we're still starting out; we haven't gotten into a rhythm yet. Come to think about it, that first Red wasn't really a true Red since we didn't have a test that failed. [ March 13, 2002: Message edited by: Junilu Lacar ] [ August 11, 2002: Message edited by: Marilyn de Queiroz ]
Step 5 - Red Light To make our test class compile after adding the first test case, we add the necessary members to OOCalc We compile both OOCalc and OOCalcTest then hit the Run button in JUnit. We now get our first true Red light: the test fails. More tomorrow... Junilu Continue... [ March 13, 2002: Message edited by: Junilu Lacar ] [ August 11, 2002: Message edited by: Marilyn de Queiroz ]
Joined: Mar 25, 2001
Junilu -- Just a logistical suggestion for those of us taking part in the tutorial. Since we're bouncing around from thread to thread, could you please add a sequence number at the top of each post? That, in combination with the thread links, would make things much easier to navigate.
Step 7 - Refactoring Will nobody question our design decisions up to this point? OK, then I will. Are we using an appropriate type for OOCalc.result and getResult()? What alternatives are there and how would using those instead affect our next design decisions?
Joined: Mar 25, 2001
Originally posted by Junilu Lacar: Are we using an appropriate type for OOCalc.result and getResult()?
Hell no! Having result as a String suggests to me that we're thinking about presentation too early because we know that eventually we're going to setText() a text field and it'll be convenient to have the result already be a String. getResult() I'm a little unsure of. If clients of that method are going to use it for calculations, then it should be some numeric type. If the only clients of that method are for display, then I guess I'm OK with it returning String. Final vote: make result a double and getResult() return double (if we're still keeping with the premise of doing floating-point math). (I'm still waiting to see why we're even storing result -- but figured I'd hold off with that question until we got a little farther along. By the way, I liked the bit about returning zero by default. Hadn't even thought about that.) [Should I have posted this to the design thread? If so, somebody please move it.]
Step 7, cont. - Refactoring Thanks, Michael No need to move your reply since it goes with the flow of our discussion I agree with your assessment. So let's refactor. In terms of our Stoplight game, we should expect to go from Green to Yellow (compile errors because we're changing types) and back to Green. This sequence is OK since we are refactoring. Again, we start with the test. Let's shift gears a little so we can go through the cycle a little faster. Refactoring Steps:
Change testInitialResult() to expect a double. Use assertEquals() in the test.Listing
Compile OOCalcTest. Error (Yellow light!)
Change OOCalc so that result is a double. Stub out getResult() by making it return Double.NaN. Listing
Compile OOCalc and OOCalcTest.
Run JUnit. Test fails (Red light!)
Change OOCalc again, this time making getResult return result. Listing
Run JUnit. Test passes (Green light!)
[ March 13, 2002: Message edited by: Junilu Lacar ] [ August 11, 2002: Message edited by: Marilyn de Queiroz ]
Now we're back at a Green light after changing the design a little bit. We didn't add new functionality though -- we just changed it a bit. This is an important point. You shouldn't add functionality while refactoring. Also, in refactoring the OOCalc class, we had to refactor the OOCalcTest as well. Does anybody have any doubts at this point?
Does anybody have any doubts at this point? What? No takers? I know right now I'd have some doubts. I'll wait a few more hours to see if we have any Thomases around here before I lay my doubts out. Junilu
Well, I don't know if it is a doubt, but the getResult() method bothers me, but that's because I don't see the OOCalc actually doing the operation. getResult() seems like it should be coming from somewhere else, and OOCalc will call it to output the result. Probably more of a design issue, but that's the only thing bothering me at this point.
Originally posted by jason adam: Well, I don't know if it is a doubt, but the getResult() method bothers me, but that's because I don't see the OOCalc actually doing the operation. getResult() seems like it should be coming from somewhere else, and OOCalc will call it to output the result.
I think it doesn't matter much which object is actually doing the operation. The question we should ask at this point is: what interface do clients of OOCalc expect? Do they care about the nature of the output of OOCalc (being a result of calculations), or are they satisfied with getting "just some output"? Regards, Ilja
The soul is dyed the color of its thoughts. Think only on those things that are in line with your principles and can bear the light of day. The content of your character is your choice. Day by day, what you do is who you become. Your integrity is your destiny - it is the light that guides your way. - Heraclitus
Joined: Jul 11, 2001
Originally posted by Junilu Lacar: [...]
Stub out getResult() by making it return Double.NaN. Listing
Compile OOCalc and OOCalcTest.
Run JUnit. Test fails (Red light!)
Why did you take this step? Wouldn't it have been a smaller step to just change the value of result from "0" to 0.0? Remember: while refactoring, you *don't* have to integrate a "go to red"-step - you are rather supposed to go from green to green, imo. Regards, Ilja
Joined: Jul 11, 2001
Originally posted by Michael Matola:
I'm still waiting to see why we're even storing result [...].
Being Tentative Ilja, thanks for joining the party. I was kind of hoping you'd pop in sooner or later. Jason and Michael have been very behaved so far and it always makes it interesting when there are troublemakers like you Yes, going from green directly to green is what you'd normally expect when refactoring (with an occasional yellow in between for the more tentative refactorings). I'll discuss my intentions in this thread about Learning how to Learn Junilu P.S. I'm going to take a break for a day. My kid has been sick and I had to stay home today again so now I really need to catch up on my work. I also need to read a couple of PDFs I downloaded re Test-Driven Design (see the link to the Yahoo group in the thread cited above). These should be really helpful in developing this tutorial. -do- [ August 11, 2002: Message edited by: Marilyn de Queiroz ]
Let me just lay out the doubts I would have at this point. 1. Changing the type of result was not so painful here because there was very little ripple effect. But what about in real-world applications? Changing types could be a lot more painful if there were existing clients. These clients would break and that could cause a ripple effect throughout the system. How do we avoid this kind of effect or at least minimize it? 2. Do we have to keep old tests around, even if only as commented out code? I would think this would provide some sort of trail for maintenance programmers later on to pick up on the "theory" of the change. 3. Wouldn't it be also advisable to write tests for failure conditions such as border conditions, lower and upper bounds checking, and such? And what about checking for exceptions we expect to be thrown in certain cases? 4. I kind of get the concept of test-first programming but how exactly do I come up with meaningful tests? Are there any guidelines? It seems to me that thinking up what tests to write is just as hard or even harder than just going out and writing the actual code to solve the problem 5. And even if we are able to think up meaningful tests, wouldn't it have been easier to think up a design first and then think of what tests we wanted? Are we really going faster by going straight to writing tests? Now, am I the only one in this boat or what? Feel free to post replies to address, support, or add to these concerns. Junilu [ March 16, 2002: Message edited by: Junilu Lacar ]
Chicken Farmer ()
Joined: May 08, 2001
I too share your sentiment on if this is really as productive as it sometimes is made out to be. Perhaps we're just looking at things in the short-term too much, but if we are to write tests for every single behavior that we expect our design to exhibit, that would just get insane for a really large application. I only see this approach functional if you're working on a small system, or are organized enough to break things into logical subsystems and work those out, and then bring it all together into the larger app. Personally, I'd rather just write a simple main method with an if/else statement testing a method of a class than bother with some of this.
Originally posted by jason adam: I too share your sentiment on if this is really as productive as it sometimes is made out to be...
Actually, I am pretty confident of this approach but was just expressing some of my doubts when I first started learning it to show that these doubts, if you have them, are natural to the learning process (Also, as your self-appointed leader/presenter in this tutorial , I have to display a certain level of confidence so you guys will trust me enough to follow me down this unknown path)
I only see this approach functional if you're working on a small system, or are organized enough to break things into logical subsystems and work those out, and then bring it all together into the larger app.
Ahhh...but in expressing your doubt you have inadvertently revealed the answer to your question, have you not? Is this not the essence of the approach What we have to do is to learn how to take baby steps. As one Extremo wrote recently: "If this seems crazy, do it until you're so bloody good at it that you can't remember why it seems crazy. Then decide if it's crazy" -- Ron Jeffries (message in the testdrivendevelopment Yahoo Group) Junilu ( UBB!) [ March 16, 2002: Message edited by: Junilu Lacar ]
Originally posted by jason adam: but if we are to write tests for every single behavior that we expect our design to exhibit, that would just get insane for a really large application.
There's a saying that goes something like "If the code hasn't been tested, it doesn't work." Read what Robert Bender has to say about testing: http://www.rbsc.com/pages/myths.html Junilu [ March 16, 2002: Message edited by: Junilu Lacar ]