Exceptions in Java Methods
The Hand class specification is read through its signatures and doc comments, which state what each method promises and what it throws when a rule is broken.
Try it: try / except / else / finally
How an exception jumps out of a try block to the first matching except clause, when else runs, and why finally always runs.
How it works
- Statements in try run until one raises an exception; the rest of try is skipped.
- Python checks the except clauses in order and runs the first one whose type matches.
- If nothing was raised, the else block runs instead.
- finally runs in every case.
Default run (9 steps): Call safe_divide('4'). … Return 25.0. Output was: Worked: 25.0 / Done.
Simplified: One fixed function; int() and / are modelled exactly (including how Python prints floats), but nothing is executed as Python.
Loading the simulation…
A Hand That Refuses Bad Input
A card game program needs a way to hold a group of cards that belong to one player. The Hand class is the piece of code that represents that group. Rather than starting with the internal details of how the class stores its cards, this article starts with the specification: the list of public constructors and methods, each with a comment that says what it promises to do and what happens when something goes wrong.
Reading that contract accurately does two things for you. It tells you how to use the class correctly, and it tells you when a program is behaving against the contract. Throughout this article you will predict what a hand should hold after each call, then check your prediction against the documented rules. That habit is the core skill this article is building.
Reading the Hand Specification
The specification names one constructor and a set of public instance methods. Each signature tells you three things: the method's name, its parameter types, and its return type. The return type decides what the caller gets back. The return type void means the method changes the hand but returns nothing to the caller. A return type such as int or Card means the caller receives a value back.
The doc comment above each signature is part of the specification, not decoration. It is where the class states its promises: what the constructor produces, which inputs must be non-null, which positions are valid, and which exception is thrown when a rule is broken. When you need to reason about what a method will do, most of the answer is written in that comment, so read it before you read the code.
For every Hand method, ask two questions: what does the signature say the method takes and returns, and what does the doc comment promise about the state of the hand and about any exception it may throw.
Tracing a Hand Step by Step
Before you read the rules, make a prediction. Work through the trace below and write down, for each call, what you expect the hand to hold afterward. The rules that follow will check your expectations, so keep them in mind as you go.
- 1Create a new Hand with the constructor. Write down what you expect the hand to hold, then check the constructor's doc comment for what it promises to produce.
- 2Call
addCardwith a null value. Write down whether you expect a card to be added, and which exception, if any, you expect. - 3Call
getCardwith a position that does not exist in the hand. Write down the exception you expect and whether the hand changes. - 4Compare each prediction with the documented promise. The first call where your expectation and the documented promise disagree is the point to investigate.
What do you think happens?
What does the hand do when addCard is called with null?
Reveal answer
addCard() rejects a null card by throwing NullPointerException.
A null card is rejected because it is not a card at all. The call ends with an exception rather than a silent change to the hand.
Each of the two calls ends with an exception. Neither call returns normally, and neither silently changes the hand.- The first call passes null, which
addCard()is documented to reject withNullPointerException. - The second call passes a position that does not exist, which
getCard(int)is documented to reject withIllegalArgumentException. - Position 99 is an illustrative value; any position outside the hand's valid range produces the same rule.
Two removeCard Methods, One Name
A player can ask to remove a card in two different ways: by naming the card itself, or by naming where it sits in the hand. The specification supports both by providing two methods with the same name, removeCard. One takes a Card parameter and removes that card if it is in the hand. The other takes an int parameter and removes the card at that position.
The two versions do different jobs but share one name because they express the same intention from the caller's point of view: get a card out of the hand. Overloading lets the caller choose whichever description is more natural at the moment. The caller who knows the card can pass the card. The caller who knows its slot can pass the number.
Overloading works because the two removeCard methods have different parameter types, one Card and one int. The parameter list, not just the name, distinguishes the methods.
When a Position Does Not Exist
The same reasoning applies to positions. Both removeCard(int) and getCard(int) throw IllegalArgumentException when the given position does not exist in the hand. Notice the difference in what is being rejected. A null card is rejected because it is not a card at all. A position is rejected because it refers to a slot that is not there.
The diagram shows a chain of calls. The callers named there are illustrative, not part of the Hand specification. What the specification does guarantee is that the call to removeCard(int) ends in IllegalArgumentException when the position is invalid, rather than quietly doing nothing.
Sorting by Suit or by Value
Players often arrange their cards so that similar cards sit together. The specification provides two sorting methods for this purpose. Both rearrange the cards already in the hand, but they use different ordering rules. sortBySuit() groups cards by suit and orders them by value within each suit. sortByValue() orders the cards by increasing value, breaks ties by suit, and treats aces as lowest.
| Method | Primary ordering rule | Secondary rule | Ace handling |
|---|---|---|---|
| sortBySuit() | Groups cards by suit | Orders by value within each suit | Not stated in this rule |
| sortByValue() | Orders by increasing value | Breaks ties by suit | Aces are lowest |
The two sorting methods use different primary keys, so the same cards can end in different orders.
The difference is the primary key. sortBySuit() uses suit first and value second. sortByValue() uses value first and suit only to break ties.
Finding Where a Hand Departs from Its Contract
When a program produces an unexpected hand, the fastest route to the fault is usually to compare each call against its documented contract. Ask two questions at each step: what did the method promise, and does the state I see match that promise? The first call where the two disagree marks the point of divergence. The fault is usually in that call or in the input given to it.
- 1Write down the sequence of Hand calls exactly as the program made them.
- 2For each call, state what the doc comment promises: the result, the change to the hand, and any exception.
- 3After each call, compare the hand you observe with the promise you wrote down.
- 4Stop at the first call where the observation and the promise disagree. Examine that call and the argument you passed to it.
Mistakes When Reading the Contract
Common mistake Treating the two removeCard methods as duplicates
Why it failsThe methods have different parameter types, one Card and one int, so they are distinct overloads.
Do this insteadRead the parameter list of each signature. The parameter types decide which version a call uses.
Common mistake Assuming an invalid position is silently ignored
Why it failsThe specification says
removeCard(int)andgetCard(int)throwIllegalArgumentExceptionfor a position that does not exist.Do this insteadExpect an exception, and check the doc comment for the exact exception named.
Common mistake Assuming sortBySuit() and sortByValue() order cards the same way
Why it failssortBySuit()uses suit as the primary key, whilesortByValue()uses value as the primary key.Do this insteadState the primary ordering rule for each method before predicting the result.
Common mistake Reading the code before the doc comment
Why it failsThe doc comment states the promises the class makes, so reasoning from the implementation alone can miss the contract.
Do this insteadRead the signature and doc comment first, then predict, then check the actual state.
Practicing Contract Reading
A program calls sortByValue() on a hand that contains an ace and a two, then calls sortBySuit(). Before you check the rules, write down where the ace should appear after each sort. Then state which method's doc comment justifies each placement, and name the exception that a call with an invalid position would throw.
Hints
- Recall the rule that
sortByValue()treats aces as lowest. - Recall that
sortBySuit()puts suit first and value second, so the ace's position depends on its suit as well as its value. - The exception for an invalid position comes from
removeCard(int)orgetCard(int).
Key Takeaways
- The Hand specification is read through its signatures and doc comments, which state what each method promises and what it throws when a rule is broken.
- Two
removeCardmethods can share one name because their parameter types differ: one takes a Card and one takes an int position. addCard()rejects a null card withNullPointerException, whileremoveCard(int)andgetCard(int)reject a nonexistent position withIllegalArgumentException.sortBySuit()groups by suit and orders by value within each suit, whilesortByValue()orders by increasing value, breaks ties by suit, and treats aces as lowest.- To locate a fault, compare each call's documented promise with the observed state and stop at the first call where they disagree.