4 ms·
The issue is fundamentally abstractions leak. And if the code is overly "Uncle Bobified" the abstractions will leak bugs. On the other hand if you under "Uncle
by JamesBarney 7y ago
The issue is fundamentally abstractions leak. And if the code is overly "Uncle Bobified" the abstractions will leak bugs. On the other hand if you under "Uncle Bobify" the code will be very difficult to read because you won't be able see the forest for the trees. This is one of the advantages of comments and local functions. You can inline a function an add a comment. With the comment providing the abstraction and the line of code providing the details. Or add a local function which is easy to read but can be easily seen next to the code that's using it.
For instance you might have a function that is int getOverlap(DateTime start1, DateTime end1, DateTime start2, DateTime end2).
Looks pretty reasonable. But there are a lot of unanswered questions like
Is the return value in seconds, minutes or ms?
What happens if there is no overlap?
Do all the date time have to be in UTC?
What happens if we pass in a start time before the end time? What happens if I pass in a start time equal to an endtime?
If I split a block will the overlap of this block with any other block match the combined overlap of any two split blocks?
- blub 7y agoAll of those questions can be answered through comments. I'd vote for a separate function. Some of the answers can be encoded in the type system through the use of time units (like std::chrono), optional return values and design by contract (preconditions). Unit tests could be added to further clarify some edge cases.
- skohan 7y agoI think most of those questions could be answered with a sufficiently strong type system.
- lmm 7y ago> The issue is fundamentally abstractions leak. Only people who've never used a good type system think this. Your function example demonstrates this pretty nicely: use a proper interval type for the two intervals and a proper return type rather than int, and then the answers to all your questions become obvious.
- JamesBarney 7y agoSo say you replace it with a DateRange class so you end up with range1 = new DateRange(start1, end1); range2 = new DateRange(start2, end2); DateRange overlapRange = GetOverlap(range1, range2); You still don't know what happens when start1 is greater than end1? What happens when they have no overlap? What does overlap return if it doesn't return null? What happens if start1 and end1 are in local vs UTC time? What happens if range1 and range 2 are in local vs UTC time? When you get overlapRange.Duration.Days is this days in 24 hr increments or days by date? And this is a really simple example. Things like ORMs are very leaky abstractions which make this mcuh worse.
- lmm 7y ago> You still don't know what happens when start1 is greater than end1? That's DateRange's responsibility, it's not a concern for getOverlap. By the time you get as far as getOverlap you already have a valid DateRange. Small, reusable, compositional pieces. > What happens when they have no overlap? What does overlap return if it doesn't return null? It can't "return null", we're talking about using a decent type system here. The types should express what kind of failures are possible, so perhaps getOverlap returns a maybe, or an either where the left hand side is some structured "reason" value that expresses what went wrong. > What happens if start1 and end1 are in local vs UTC time? You get a compilation error because that distinction is part of the type. > What happens if range1 and range 2 are in local vs UTC time? You get a compilation error because that distinction is part of the type. > When you get overlapRange.Duration. Days is this days in 24 hr increments or days by date? That's Duration's responsibility, not getOverlap's; again, small, reusable, compositional pieces. You shouldn't ever be calling overlapRange.duration.days (obvious law of demeter violation); rather you should be calling something that can do the right thing with overlapRange.duration (e.g. display it, compare whether it's longer than some limit). But to answer the question: check its type, that will tell you. Any value that you actually pass around and use in business logic should have a type that tells you what kind of thing it is; primitives should only ever be used within the very lowest level functions.
- marcosdumay 7y agoJust to add to lmm's comment: > What happens when they have no overlap? You get the empty range. Because any sane modeling of a range will have an empty value for completeness, and modeling it without those overlaps and date specialization clobbering your screen will make it patently obvious wether your type is complete or not. And if you still forgot to include the empty value, you won't be able to write the overlap function, so you will have to go back.
- Gibbon1 7y agoOne of my considerations is you have two types of functions. Ones that are input complete and ones that aren't. The former produces a 'rationally correct' result for every input. And the latter doesn't. Writing the former is hard and proving even harder. Keeping potentially suspect functions local and limited in scope is arguably more conservative.