-
Notifications
You must be signed in to change notification settings - Fork 1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Make ScheduledTransitLeg, FlexibleTransitLeg fully immutable #6386
base: dev-2.x
Are you sure you want to change the base?
Make ScheduledTransitLeg, FlexibleTransitLeg fully immutable #6386
Conversation
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## dev-2.x #6386 +/- ##
=============================================
+ Coverage 69.72% 69.76% +0.03%
- Complexity 18016 18055 +39
=============================================
Files 2057 2059 +2
Lines 76967 77027 +60
Branches 7844 7841 -3
=============================================
+ Hits 53666 53735 +69
+ Misses 20550 20544 -6
+ Partials 2751 2748 -3 ☔ View full report in Codecov by Sentry. |
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.
This is great work. Making these model classes fully immutable makes stuff so much simpler to reason about.
application/src/ext-test/java/org/opentripplanner/ext/flex/FlexibleTransitLegBuilderTest.java
Outdated
Show resolved
Hide resolved
application/src/ext-test/java/org/opentripplanner/ext/flex/FlexibleTransitLegBuilderTest.java
Outdated
Show resolved
Hide resolved
application/src/ext/java/org/opentripplanner/ext/realtimeresolver/RealtimeResolver.java
Outdated
Show resolved
Hide resolved
...cation/src/ext/java/org/opentripplanner/ext/stopconsolidation/model/ConsolidatedStopLeg.java
Outdated
Show resolved
Hide resolved
application/src/main/java/org/opentripplanner/model/plan/ScheduledTransitLeg.java
Show resolved
Hide resolved
application/src/main/java/org/opentripplanner/model/plan/ScheduledTransitLegBuilder.java
Outdated
Show resolved
Hide resolved
…xibleTransitLegBuilderTest.java Co-authored-by: Henrik Abrahamsson <[email protected]>
setDistanceMeters(getDistanceFromCoordinates(transitLegCoordinates)); | ||
this.distanceMeters = | ||
builder | ||
.overrideDistanceMeters() |
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.
I think there is a potential problem with the overrideDistanceMeters thing. If someone were to do this:
// Given an existing leg1 with some geometry
var leg2 = leg1.copy().withAlightStopIndexInPattern(newIndex).build();
// Now leg2 will have another geometry but the same distance as leg1
It's probably not an issue right now but there is a potential for bugs. One solution would be to remove the builder.setOverrideDistanceMeters() and rewrite the test that uses it to generate some actual coordinates.
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.
You're right, the override is ugly and dangerous. I will try to get rid of it.
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.
There is only a single test GroupByDistanceTest
that actually requires the ability to directly set the distance. It will be a bit of work to re-write it. Let's discuss in the meeting.
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.
I think Legs should be reduced to a DTO - carrying data - not responsible for data consistency. For values calculated e.g. in routing we want them to propagate to the output - not recomputing them even if they are slightly off due to some optimization. It is ok to fallback to some default algorithm to compute unset values. The pattern I used in builders to achieve this is like this (pseudo code), b can be derived from a:
class A {
a, b : AType;
}
class ABuilder {
A original;
a = NOT_SET, b=NOT_SET;
withA(...)
withB(...)
computeB() {
if(b != NOT_SET) return b;
if(a != NOT_SET) return <compute b based on a>;
return original.b();
}
}
computeB()
is called in the build() method or A
s constructor. The trick is to keep a ref to the original so you know if a
and b
is changed or not.
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.
I don't follow everything here. Let's find a way forward in the dev meeting.
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.
I think it's a good idea to make the Legs be more of a value object. My suggestion would be to handle the distance calculation in a setter in the builder. That way you will get consistent behavior regardless of whether you copy()
an existing leg or create one from scratch. Something like this:
class ScheduletTransitLegBuilder {
...
/**
* Note: This will override the distance if already set.
*/
public withPatternAndBoardAlightIndices(Pattern pattern, int boardIndex, int alightIndex) {
this.pattern = pattern;
this.boardIndex = boardIndex;
this.alightIndex = alightIndex;
var coords = getCoords(pattorn, boardIndex, alightIndex);
this.legGeometry = createGeometry(coords);
this.distance = calculateDistance(coords);
}
public withOverrideDistanceMeters(double d) {
this.distance = d;
}
}
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.
We discussed today that we will remove the logic to compute the distance from the leg/builder itself. It needs to be done by the callers.
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.
We decided on the developer meeting to accept the issue with the overrideDistance for now and solve it in an upcoming PR.
With that caveat, I'm fine with this PR.
.../org/opentripplanner/routing/algorithm/filterchain/filters/transit/DecorateTransitAlert.java
Show resolved
Hide resolved
application/src/main/java/org/opentripplanner/routing/algorithm/mapping/AlertToLegMapper.java
Outdated
Show resolved
Hide resolved
application/src/ext-test/java/org/opentripplanner/ext/flex/FlexibleTransitLegBuilderTest.java
Outdated
Show resolved
Hide resolved
application/src/ext/java/org/opentripplanner/ext/flex/FlexibleTransitLeg.java
Show resolved
Hide resolved
application/src/ext/java/org/opentripplanner/ext/flex/FlexibleTransitLegBuilder.java
Show resolved
Hide resolved
setDistanceMeters(getDistanceFromCoordinates(transitLegCoordinates)); | ||
this.distanceMeters = | ||
builder | ||
.overrideDistanceMeters() |
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.
I think Legs should be reduced to a DTO - carrying data - not responsible for data consistency. For values calculated e.g. in routing we want them to propagate to the output - not recomputing them even if they are slightly off due to some optimization. It is ok to fallback to some default algorithm to compute unset values. The pattern I used in builders to achieve this is like this (pseudo code), b can be derived from a:
class A {
a, b : AType;
}
class ABuilder {
A original;
a = NOT_SET, b=NOT_SET;
withA(...)
withB(...)
computeB() {
if(b != NOT_SET) return b;
if(a != NOT_SET) return <compute b based on a>;
return original.b();
}
}
computeB()
is called in the build() method or A
s constructor. The trick is to keep a ref to the original so you know if a
and b
is changed or not.
.../src/main/java/org/opentripplanner/model/plan/legreference/ScheduledTransitLegReference.java
Outdated
Show resolved
Hide resolved
application/src/test/java/org/opentripplanner/_support/time/DateTimes.java
Outdated
Show resolved
Hide resolved
application/src/ext/java/org/opentripplanner/ext/fares/FaresToItineraryMapper.java
Show resolved
Hide resolved
21ab826
to
5bf1072
Compare
Summary
Right now the ScheduledTransitLeg is mostly immutable apart from a few fields
distanceMeters
due to testingalerts
because they are added laterfareProducts
they are also added laterBecause Aracadis has a few sandbox features that manipulate the legs over the years I had to fix many bugs because of the leg is half immutable and half mutable. I would like to fix all of them at once and therefore I'm making the ScheduledTransitLeg and FlexibleTransitLeg immutable and all fields must now be set via a builder.
There is also the special sandbox leg
ConsolidatedStopLeg
which also required some special handling but it is now also fully immutable and has a builder.Unit tests
Lots of tests added and some re-written.
Documentation
Javadoc.