-
Notifications
You must be signed in to change notification settings - Fork 1.7k
model: convert checked exceptions to runtime exceptions #896
Conversation
pyokagan
commented
Aug 7, 2018
•
edited
Loading
edited
Click here to submit a new iteration when this PR is ready for review. See this repository's contribution guide for more information. |
42def33
to
c2e22a1
Compare
5939b6c
to
1185ff5
Compare
v1@pyokagan submitted v1 for review.
Checkout this PR version locallygit fetch https://github.com/se-edu/addressbook-level4.git refs/pr/896/1/head:BRANCHNAME where |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[10/10] Since UniquePersonList
is in the package seedu.address.model.person;
, UniquePersonListTest
should be correspondingly the same.
@@ -110,13 +110,12 @@ public ReadOnlyAddressBook getAddressBook() { | |||
} | |||
|
|||
@Override | |||
public void deletePerson(Person target) throws PersonNotFoundException { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[1/10] PersonNotFoundException
import not removed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I'll remember to run tests on every commit next time ;_;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[5/10]
nit in last paragraph:
As such, let's making make the tradeoff in the other direction, by making
DuplicatePersonException a RuntimeException. New callers could forget to...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[6/10]
same as [5/10]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[3/10]
@@ -181,6 +182,11 @@ public ReadOnlyAddressBook getAddressBook() { | |||
private class ModelStubAcceptingPersonAdded extends ModelStub { | |||
final ArrayList<Person> personsAdded = new ArrayList<>(); | |||
|
|||
@Override | |||
public boolean hasPerson(Person person) { | |||
return personsAdded.stream().anyMatch(addedPerson -> addedPerson.isSamePerson(person)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lambda expression can be simplified to : person::isSamePerson
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[10/10] Since we are on Java 9, should we use List.of(...)
instead of Arrays.asList(...)
?
@Test | ||
public void setPerson_editedPersonIsSamePerson_success() { | ||
uniquePersonList.add(ALICE); | ||
uniquePersonList.setPerson(ALICE, ALICE); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we do uniquePersonList.setPerson(ALICE, new PersonBuilder(ALICE).build());
instead?
It's unclear here why we are using the same object references.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If they refer to the same object (for whatever reason), the API should still allow it, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, after some more thought, I think I'll keep it. The "different object reference case" is already tested in setPerson_editedPersonHasSameIdentity_success
@yamidark Sorry, please do concentrate on se-edu/addressbook-level2#388 , I think @damithc would prefer that. Sorry for requesting your review and taking your time, this PR is not so important. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[5/10]
throw new AssertionError("sample data cannot contain duplicate persons", e); | ||
AddressBook sampleAb = new AddressBook(); | ||
for (Person samplePerson : getSamplePersons()) { | ||
sampleAb.addPerson(samplePerson); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will this PR be simplifying some of the loops such as this one?
Arrays.stream(getSamplePersons()).forEach(sampleAb::addPerson);
Since you mentioned in your commit message that:
Checked exceptions also don't work very well with the Java Streams API,
since the API doesn't accept lambas which could throw checked
exceptions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Eh let's keep that for another PR, to avoid delaying this one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok! Oh I spotted a spelling mistake for your commit msg: lambas
:p
fyi, this is not a new file, but I'll do it anyway in this PR since it's easy enough to do. |
These redundant throws clauses were identified using IntelliJ's Analyze->Inspect Code feature, and analyzing the whole project.
In the next few commits, we are going to be converting DuplicatePersonException and PersonNotFoundException into runtime exceptions. To do that, we need to be able to query the model to determine if a person exists in the address book. This functionality is already available in `UniquePersonList#contains(Person)`, but was simply not exposed. Let's expose it.
In the next few commits, we will convert DuplicatePersonException into a runtime exception. In preparation for that, let's teach AddCommand to not depend on catching DuplicatePersonException for its control flow. Since DuplicatePersonException is still a checked exception, we need to add an ugly `throw new AssertionError("should not happen")`, but this will be removed in later commits once we convert DuplicatePersonException into a runtime exception.
In the next few commits, we will convert DuplicatePersonException into a runtime exception. In preparation for that, let's teach EditCommand to not depend on catching DuplicatePersonException for its control flow. Since DuplicatePersonException is still a checked exception, we need to add an ugly `throw new AssertionError("should not happen")`, but this will be removed in later commits once we convert DuplicatePersonException into a runtime exception.
DuplicatePersonException is a checked exception. It is thrown by methods such as `UniquePersonList#add(Person)`, `UniquePersonList#setPerson(Person, Person)` etc. to signify that certain preconditions were not met -- namely that the operation would cause the "no duplicate persons in address book/UniquePersonList" constraint to be violated. Using checked exceptions for such a purpose is slightly useful in that it will force callers to handle the case where the above preconditions are not met, such as when the methods are called with invalid user input. However, it imposes a HUGE cost on callers where the preconditions are already known to be met (e.g. in test code, or when the user input has already been validated before hand). In such a case, callers are forced to add try-catch blocks around the method call even if they know that the exception will never be thrown, bloating up the code. It is also impossible to test the catch blocks as well since correct code will ensure that the precondition holds and thus the exception will never be thrown, leading to reduced code coverage. Checked exceptions also don't work very well with the Java Streams API, since the API doesn't accept lambdas which could throw checked exceptions. In AB-4, the amount of code which benefits from DuplicatePersonException being a checked exception is much smaller than the amount of code which is negatively impacted. As such, let's make the tradeoff in the other direction, by making DuplicatePersonException a RuntimeException. New callers _could_ forget to check that the preconditions hold before calling the methods in question (although test cases should catch that), but this is balanced out by the huge benefit of having more concise and testable code.
PersonNotFoundException is a checked exception. It is thrown by methods such as `UniquePersonList#remove(Person)`, `UniquePersonList#setPerson(Person, Person)` etc. to signify that certain preconditions were not met -- namely that the person provided to those methods does not exist in the address book/UniquePersonList. Using checked exceptions for such a purpose is slightly useful in that it will force callers to handle the case where the above preconditions are not met, such as when the methods are called with invalid user input. However, it imposes a HUGE cost on callers where the preconditions are already known to be met (e.g. in test code, or when the user input has already been validated before hand). In such a case, callers are forced to add try-catch blocks around the method call even if they know that the exception will never be thrown, bloating up the code. It is also impossible to test the catch blocks as well since correct code will ensure that the precondition holds and thus the exception will never be thrown, leading to reduced code coverage. Checked exceptions also don't work very well with the Java Streams API, since the API doesn't accept lambdas which could throw checked exceptions. In AB-4, the amount of code which benefits from PersonNotFoundException being a checked exception is much smaller than the amount of code which is negatively impacted. As such, let's make the tradeoff in the other direction, by making PersonNotFoundException a RuntimeException. New callers _could_ forget to check that the preconditions hold before calling the methods in question (although test cases should catch that), but this is balanced out by the huge benefit of having more concise and testable code.
The code in the `try` block does not throw any checked exceptions.
It is not used anymore.
Some methods do not explicitly check that their arguments are non-null with `requireNonNull(...)`. Let's fix them to be consistent with the rest of the code base.
Since UniquePersonList is in the `seedu.address.model.person` package, its corresponding test UniquePersonListTest should be in the same package as well.
I considered that, but we don't use I'll wait for #871 |
1185ff5
to
560a61a
Compare
v2@pyokagan submitted v2 for review.
(📚 Archive) (📈 Interdiff between v1 and v2) (📈 Range-Diff between v1 and v2) Checkout this PR version locallygit fetch https://github.com/se-edu/addressbook-level4.git refs/pr/896/2/head:BRANCHNAME where |
[3/11] Also added some |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a minor nit 👍
@Test | ||
public void setPersons_list_replacesOwnListWithProvidedList() { | ||
uniquePersonList.add(ALICE); | ||
List<Person> personList = Arrays.asList(BOB); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IntelliJ would prefer you to use Collections.singletonList()
here. :p
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Foiled again.
Does it still prefer Collections.singletonList()
over List.of()
, I wonder.
More test coverage is always nice. In particular, we want to check that UniquePersonList's methods throw DuplicatePersonException and PersonNotFoundException where relevant. These cases were not tested by existing test code.
560a61a
to
b2875e0
Compare
v3@pyokagan submitted v3 for review.
(📚 Archive) (📈 Interdiff between v2 and v3) (📈 Range-Diff between v2 and v3) Checkout this PR version locallygit fetch https://github.com/se-edu/addressbook-level4.git refs/pr/896/3/head:BRANCHNAME where |