[HN Gopher] Good Design is Imperfect Design, Part 1: Honest Names
___________________________________________________________________
Good Design is Imperfect Design, Part 1: Honest Names
Author : dang
Score : 153 points
Date : 2021-08-04 17:15 UTC (5 hours ago)
(HTM) web link (www.domainlanguage.com)
(TXT) w3m dump (www.domainlanguage.com)
| ThinBold wrote:
| That (some date + 1 month) + 1 month [?] some
| date + 2 months
|
| has always been true for floating numbers. This is exactly why
| you never trust fast matrix multiplications---they rely on
| cancelations of the form (a + b) - b = a + (b - b) = a.
|
| I would argue that _1 month_ is just having too few significant
| digits. You can even implement it as returning 30 with
| probability 60% and returning 31 with probability 40%. Then _on
| average_ you would have 1 year [?] sum([1
| month] * 12)
| sib wrote:
| Sure, but I'd guess that neither developers nor "normal people"
| immediately think "floating point" when they think about a
| month.
| jl6 wrote:
| For what it's worth, SAS can add months to time points with the
| aid of an _alignment_ parameter, so 01JAN2021 + 1 month would be
| 01FEB2021 with _beginning_ alignment and 28FEB2021 with _end_
| alignment. There is also _middle_ and _sameday_ alignment.
|
| This is the snappily named intnx function. I guess that's an
| honest name in the sense that it is very suggestive of needing to
| read the documentation before using it.
| pkulak wrote:
| That there is no ISO 8601 duration in Java has always bugged me.
| Duration() is just a wrapper around seconds and a millisecond
| part, so you can't use it for a calendar duration. Even the
| concept of an "hour" or "minute" doesn't work because of leap
| seconds.
| Etheryte wrote:
| Being honest with naming things is also a great roundabout way to
| ensure you write maintainable, readable code. If the name is
| honest and it feels awkward, it's a good red flag that there
| might be a problem with the approach you're taking. I think code
| golf languages (a-la [0]) are a good example of this approach as
| well, when your language is as terse as possible, giving very
| deep consideration to what the language actually does is crucial.
|
| [0] https://github.com/DennisMitchell/jellylanguage/wiki/Quicks
| teknopaul wrote:
| I think I missed something here, does this article really
| suggest that plusExcuseExcuse() would be better? The domain is
| dates, we all know months have different numbers of days so
| _something_ must be done and this is explicit from the domain.
|
| Changing PI to PI_ISH because numbers in a language is limited
| does make code more readable.
|
| Almost everything in a computer is an imperfect model.
|
| i++
|
| is not improved as
|
| incrementUnlessItOverflows(i)
|
| You are not going to change how increment works so avoid making
| it awkward.
|
| You cant change the fact that number of days in a month varies.
|
| I find this as annoying as "mistakes programmers make about
| time" articles. The reality is programmers understand that all
| time in all computers is just a model to be played with.
|
| Recently I saw the people are fretting because there may _have
| to be_ a negative leap second. Leap seconds are a man-made
| concept. You don't _have to_ have a negative leap second, you
| just have to accept the earth's spin is not constant, utc is a
| model, the model is not worse if its out by >1, it was never
| perfect, it exists to be convenient. utc ignores being out by
| >1 millisecond, why is there a problem if the denomination is a
| 1000ms?
|
| The world keeps turning and the model is simpler if _all_ leap
| seconds were ignored, it will take a while until midday over
| Greenwich is affected and it will not matter when it is. It was
| an arbitry location in the first place.
|
| It seems to me JodaTime is correct, if you want to add 30 days
| plus(30 days), if you want to accept that the month model is
| imperfect plus(1 month) and see what you get.
|
| ++ is concise and precise, the domain is a programming
| language.
| dheera wrote:
| Also
|
| ``` .plus(1 month) ```
|
| The unknown behavior of 1-30 + 1 month is a red flag that maybe
| you shouldn't be using "month" as a unit of time because it
| isn't.
|
| Months suck. Bill for services every 10 days or every 25 days
| or every 50 days instead of every month.
| cyborgx7 wrote:
| That is a good point. The awkward thing here isn't really
| "plus" it's "month".
| squeaky-clean wrote:
| But sometimes you need to bill at a certain point in the
| month. I don't pay my rent every X days, I pay it on the
| first of the month.
|
| The best solution for that may be something like establishing
| it as a frequency rather than accumulative addition. But that
| won't work in every situation. Dates are complex and require
| lots of thinking to do correctly in some circumstances.
| dheera wrote:
| > I don't pay my rent every X days, I pay it on the first
| of the month.
|
| I don't want to pay rent that way. It's a good way to get
| scammed because they're charging you a higher per-day rate
| in February than January.
|
| I'd much rather pay rent per day, if possible. If Amazon
| can take over my property manager I'm sure it'll be
| possible to bill it with the same flawless consistency of
| AWS billing, and have a concept of discounted "reversed
| instances" and "spot instances" for real estate.
| [deleted]
| Negitivefrags wrote:
| In New Zealand we pay rent every 2 weeks instead of
| monthly and I've seen someone from the US come here and
| make the opposite argument.
|
| "They are scamming you because you have to pay an extra
| months rent every year"
| squeaky-clean wrote:
| Per-day still has errors when leap-seconds and daylight
| savings get involved. It's just a smaller error. And
| unless you can convince landlords nationwide to change
| how billing works, a company creating a monthly billing
| service is just going to find another developer that
| makes what they want.
|
| edit: Also this may not be typical in every lease, but
| all of my leases have established the rate as both a
| yearly and monthly amount. I'm sure my landlord wouldn't
| complain if I paid all 12 months up front. That's the
| only truly "fair" way.
| dheera wrote:
| They should give you a discount for committing to 12
| months, and a further discount for paying 12 months
| upfront. AWS does.
| mwcampbell wrote:
| > Bill for services every 10 days or every 25 days or every
| 50 days instead of every month.
|
| Isn't that just prioritizing one's laziness as a developer
| over what makes sense to the user? My guess is most users
| would rather see the bill come out on the same day every
| month.
| rahimnathwani wrote:
| What is your definition of 'unit of time'? Neither 'month'
| nor 'day' have fixed durations (the latter due to leap
| seconds). So why should we consider 'day' a unit of time but
| not 'month'?
| bcrosby95 wrote:
| Sure, programming would be infinitely easier if I could
| ignore reality and substitute it with my own.
| stickfigure wrote:
| Strong disagree - 'plus' is quite a good name for Joda Instant,
| and his alternatives are atrocious.
|
| Certain problem domains require baseline familiarity with the
| subject. Far more people can recite the old "30 days has
| September, April, June, and November" rhyme than can explain what
| the words _commutative_ and _associative_ mean. Date math may
| annoy pure mathematicians but normal humans are used to working
| with calendars.
|
| In the problem domain of dates, 'plus' is _analogous to_ (but not
| exactly) its mathematical counterpart, and Joda 's month math is
| almost always exactly what you want. Furthermore,
| _plusIshRoundCeiling_ doesn 't really explain anything; ceiling
| of what? The OP suggests that the cognitive dissonance is
| beneficial to the user. In which case it might as well be
| _plusAsterisk_ or _plusDontForgetToReadTheDocumentation_.
|
| The problem with _plusGoReadTheDocs_ et al is that all problem
| domains have little edge cases like this. Excepting pure math,
| every single _plus_ method is going to have notes. It 'll be
| worse than those useless Prop 65 warnings in California.
|
| Joda did this one right. Date math is simply not associative or
| commutative. Thankfully, most people are familiar with calendars
| and have some intuitive sense of this already. Littering the API
| with special hints doesn't help.
| cactus2093 wrote:
| Exactly right.
|
| Although I think your comment here rather underestimates
| mathematicians :) Regular people are the ones who are only used
| to thinking in real numbers or integers. While some may be
| familiar in a practical way with how dates and times work, they
| would probably struggle to rigorously define the algebra of
| time math where associativity and commutativity don't hold.
| Mathematicians will be familiar with areas like abstract
| algebra and group theory and very capable of understanding the
| concept that date math is not normal integer arithmetic.
|
| Either way though, I agree the plus operator works great here
| given the inherent weirdness of how we have structured human
| time, and everything the author is proposing is worse. Joda
| handles the trickiness of dealing with time far better and in a
| much less error-prone way than any other library I've seen.
| ericevans wrote:
| It is interesting that many of the comments have suggested
| that JodaTime does what a "normal person" would expect in
| most of these odd cases, whereas you are pointing out that
| advanced mathematical concepts can define an algebra where
| associativity doesn't apply. Almost opposite points! But well
| taken.
|
| One thing though: I think I was clear that I like Joda Time.
| It does handle these things better than most libraries. That
| is what makes it interesting to discuss. I could write a fun
| article picking apart some awful library, such as the old
| Java default library, but what would be the point.
| ithkuil wrote:
| The problem is not the "plus" operator but instead lies in the
| "month" duration.
|
| Not all months last the same number of days, and while we are at
| it not all days have 24 hours!
|
| I'd rather move the explicit (and complicated) choice of what
| kind of month are you talking about (and thus also the relevant
| discussion about naming) into the operator that constructs a time
| interval.
| Xophmeister wrote:
| I came across this problem once before and came to the same
| conclusion. "A month" is an ill-defined unit, so I just don't
| allow it to be representable in datetime arithmetic. You make
| the point that "1 day" would also fall under this category, but
| it's less variable and so it being equal to "24 hours" is good
| enough.
| slaymaker1907 wrote:
| That's not really always possible/practical. It's pretty common
| to say "in three months from now" in which case defining a
| month to be 31, 30, 29, or 28 days is not correct. The plain
| English meaning is to be on the same day of the month, but add
| 3 to the month value, probably rounding down if necessary.
| Therefore, 3 months from January 31st 2021 would be April 30th
| despite going through February which has 28 days.
| travisjungroth wrote:
| I think the point of the person you're replying to is to move
| the ambiguity onto the month object and off the plus
| operator. So today.plus(1 monthish) rather than
| today.plusish(1 month).
| ericevans wrote:
| This is definitely the sort of idea that I like when exploring
| and trying to find a better model. The point I was trying to
| make in the article is that there are times when, however you
| try, you don't find a satisfying answer in the time you have.
| mjevans wrote:
| I disagree about "plus" in the JodaTime example. The month
| addition with corner cases did exactly what I expected because
| the whole library has been polished to "do what a normal human
| would do, most of the time". A normal human would not suspect Jan
| + 1 month, is any month other than Feb. However I suspect, not
| even reading the documentation, if it were fed 30 or 31 days,
| rather than '1 month', it would also do exactly that, and mostly
| give dates in March.
| Kinrany wrote:
| I found the argument about lack of associativity convincing:
| it's very counterintuitive that date + 1 month + 1 month is
| different from date + 2 months
| michaelrpeskin wrote:
| One way I suggested that we solve in a different domain was a
| small name change that really helped. To me "plus" is the
| _operator_ and "add" is the _operation_. So to me date.add(1
| month).add(1 month) is actually different than date.add(2
| month) because I can read that as taking two one-month steps
| which _may_ be different.
| underwater wrote:
| The unit of 'month' is not fixed. It doesn't even have a
| value until it's applied to a date.
|
| (It is almost like a quantum state. It can be between 28 and
| 31 days, depending on what it's being applied to. But as soon
| as it's applied to an absolute date the ambiguity
| disappears).
|
| If you expand out the short hand 2000-02-02 + (1 month
| forward from February) + (1 month forward from March), then
| we can see associativing is nonsensical.
| chomp wrote:
| You expected 2021-03-31 + 1 month + 1 month to be different
| from 2021-03-31 + (1 month + 1 month)? I find this behavior
| understandable, but not apparent at first glance.
|
| Also, FYI, this is not how GNU date works: $
| date -d "Jan 28 next month" Sun Feb 28 00:00:00 CST 2021
| $ date -d "Jan 29 next month" Mon Mar 1 00:00:00 CST
| 2021
|
| I could see confusion from this, depending on what libraries
| you are used to.
| mjevans wrote:
| The inconsistency with GNU date is more visible from Feb:
| $ date -d "jan 31 next month" Wed Mar 3 00:00:00 PST
| 2021 $ date -d "jan 30 next month" Tue Mar 2
| 00:00:00 PST 2021 $ date -d "jan 1 next month"
| Mon Feb 1 00:00:00 PST 2021 $ date -d "feb 28 next
| month" Sun Mar 28 00:00:00 PDT 2021
|
| Edit: This was on my unconscious mind for a bit and I came up
| with an additional test case to confirm a suspicion I
| realized. $ date -d "2016-1-31 next month"
| Wed Mar 2 00:00:00 PST 2016 date -d "2016-2-1 next
| month" Tue Mar 1 00:00:00 PST 2016 date -d
| "2016-2-1 next year" Wed Feb 1 00:00:00 PST 2017
| $ date -d "2016-3-1 next year" Wed Mar 1 00:00:00 PST
| 2017
|
| GNU date will add the duration of the CURRENT interval
| (ignoring already occurred deviations, like leap years)
| relative to the specified base date.
|
| The oddity in behavior I observed above is adding the length
| of the month of Jan to dates in Jan. I suspect only a
| programmer would find that inference remotely correct.
| chomp wrote:
| I suspected this when I was playing with it, but good to
| have the confirmation. Thanks!
| alistairSH wrote:
| Disagree.
|
| Jan + 1 month = February, sure.
|
| But, as soon as you add the day, it falls apart for me.
|
| For most dates, if I add a month, in my mental model, the
| answer is the next month with the same date.
|
| Jan 15 + 1 month = Feb 15, etc
|
| But, at the edges, it gets odd quickly.
|
| Jan 31 + 1 month = ??? Not sure, maybe Feb 28, maybe Feb 29,
| maybe Mar 2, maybe Mar 3. Depends on the year and who's asking
| me to solve the problem.
|
| I would expect any reasonable software to fail gracefully when
| asked to solve this problem. And by fail gracefully, I mean ask
| for clarification. Or prevent me from asking silly questions in
| the first place.
| crdrost wrote:
| The point of the article is that it doesn't have to fail
| gracefully.
|
| Consider the API: data Date = Date {
| getYear :: Int, getMonth :: Int, getDay :: Int }
| deriving (Eq, Ord, Show) addDays :: Date -> Int
| -> Date addMonthsRounded :: Date -> Int -> Date
|
| Someone who does d `addMonthsRounded` 3
|
| immediately has a contextual clue that there might be
| something fishy going on, and has a string they can google to
| get to the docs to find out that this "Rounded" business is
| all about "hey, the code let y = (x
| `addMonthsRounded` 1) `addMonthsRounded` (-1) in y
| == x
|
| might sometimes return False because it truncates if your day
| doesn't fit in the given month."
| matheusmoreira wrote:
| > A normal human would not suspect Jan + 1 month, is any month
| other than Feb.
|
| Because humans intentionally reduce the precision of their
| computations to make them easier. Today =
| 2021-08-04 Next year = 2022 The exact month,
| day, hour are all unknown. Next month = 2021-09
| The exact day and hour are unknown. Tomorrow =
| 2021-08-05 The hours and minutes are unknown.
|
| If humans used the same level of precision as computers, they'd
| run into the same problems. Probable date of birth calculation
| is an example.
| FooBarWidget wrote:
| What is 2021-02-28 + 1 month? Is that 2021-03-28 or 2021-03-31?
| It is far from obvious what a normal human would do most of the
| time here.
|
| The lack of associativity is still a problem. If 2021-02-28 + 1
| month = 2021-03-28,then (2021-02-28 + 1 month) + 1 month =
| 2021-03-28 + 1 month = 2021-04-28. While if I ask what is
| 2021-02-28 + 2 months (given 2021-02-28 + 1 month =
| 2021-03-31), most people would say 2021-04-30.
|
| While I am not entirely sold on using awkward/"honest" names by
| default, the author does raise a good point: sometimes concepts
| are inherently messy or full of important edge cases, and we
| shouldn't just brush that aside.
|
| I recently ran into a similar problem. I am implementing a
| distributed lock
| (https://www.joyfulbikeshedding.com/blog/2021-05-19-robust-
| di...) -- like a mutex, but works across processes and
| machines. I try to mimic the language's standard Mutex API as
| much as possible.
|
| A normal Mutex has a query method named "owned?" to check
| whether the calling thread owns the mutex. When I tried
| implementing this method for my distributed lock, it raised a
| question: owned according to who? Owned according to the local
| state that represents the lock, or according to the state that
| lives in the server? Because they can differ (e.g. due to bugs
| in other clients or because an admin manually messed with the
| state). So I opted for "honest names" here too and implemented
| two methods: "owned_according_to_local_state?" and
| "owned_according_to_server_state?"
| taeric wrote:
| I think if you try and confuse folks with this, that is easy
| to do. But, this isn't special. What happens if you add a
| meter to a kilometer? For most measurements, you get a
| kilometer. We teach this with significant digits, but then
| typically ignore it and assume all sorts of conventions that
| are not spelled out.
|
| If it is important in the domain you are working in, take
| extra care to understand the maths that you are built on. And
| don't be surprised to find special cases everywhere.
| da_chicken wrote:
| The problem is that "What is one month after 2021-02-28?"
| doesn't have a single meaning _in plain human language_.
|
| It _could_ mean 30 days in the future. Or the same day of the
| week 4 weeks in the future (i.e., 28 days in the future). Or
| the day of the same cardinality in next month. Or any date in
| the next month. And those are all equally correct.
|
| It's simply not a precise measure of time when spoken from
| one human to another human in plain language. Indeed, I think
| we inherently understand it to be an imprecise measure of
| time just as much as "tomorrow" doesn't mean "exactly 86,400
| seconds from this moment". "Next week" doesn't necessarily
| mean "7 days from now", either. That's why computers don't
| typically use imprecise terms. They provide feedback and say
| "this will occur at this time and date".
|
| You always have to check what the operators actually do and
| what the requirements actually mean when you're working with
| times and dates.
| jimminy wrote:
| Plus/Increment/Add 1 month, does have one single meaning,
| it's that the outcome may be invalid that is the issue.
|
| "2021-01-31".plus(unit="Month", size=1) => "2021-02-31"
|
| But nobody really wants that, because it's not a valid
| date. So implicitly the library is deciding to return a
| valid date.
|
| A library could be written to just provide invalid dates,
| and let the end user handle any errors. That library could
| also include an explicit validation method that takes a
| date and returns a valid one.
|
| "2021-02-31".coerceToValid() => "2021-02-28" // Overflow ==
| Max
|
| "2021-02-31".coerceToValid(asDays=True) => "2021-03-03" //
| Overflow Carries (to the right)
|
| In fact the library, that provides an ignorant response and
| no contract on validity would hold to the associative
| property, it just wouldn't be as ergonomic.
| mjevans wrote:
| You seem to be stuck on: 'what is the value of 1 month' and
| thinking in terms of days, because that appears to be the
| precision of the left value.
|
| Programmers exist in a world where things such as leap
| seconds matter. Normally if you have a timestamp that is just
| before a leap second, then add exactly a day's worth of
| seconds, you'd slide back a little in time. This might matter
| in another context, such as defining the limits of
| neighboring ranges properly. Also, who's to say the
| underlying precision is a second?
|
| The intent of the library in question is to behave the way
| most people would. With imperfect buckets and idealized
| answers; yet also precision where someone makes the attempt
| to be specific.
|
| None of the examples in the article use a more vague syntax,
| such as "0 days before the end of the month". They start with
| what a human might, a rounded but full date; then apply an
| interval. So a more clear contrived example might be.
| Jan 31 .plus(1 months) => Feb 28 Jan 31 .plus(2 months)
| => Mar 31 Jan 31 .plus(3 months) => Apr 30 Jan 31
| .plus(4 months) => May 31 Jan 1 .plus(1 months) => Feb
| 1 Jan 1 .plus(2 months) => Mar 1 Jan 1 .plus(3
| months) => Apr 1 Jan 1 .plus(4 months) => May 1
|
| Note how in the second half there are still variably sized
| months, but the result is what a human would want.
| tmp538394722 wrote:
| You've ignored your critique and answered only the more
| obvious anecdotes.
|
| So I'll repeat:
|
| What's Feb 28th + 1 month?
|
| Does the human expect the last day of March? Or the 28th
| day of March?
|
| The API doesn't make that clear - I think you could
| reasonably argue for either.
| prpl wrote:
| It should be the 28th and there should be a ceiling
| method.
| mjevans wrote:
| I did cover that:
|
| """ None of the examples in the article use a more vague
| syntax, such as "0 days before the end of the month". """
|
| As another reply points out, it's incrementing the Month
| set of buckets. I'll also extend with other results I
| expect: 2020-12-31 .plus(1 months) =>
| 2021-01-31 2020-12-31 .plus(2 months) => 2021-02-28
| 2020-12-31 .plus(3 months) => 2021-03-31 2020-12-31
| .plus(4 months) => 2021-04-30
|
| A normal human has several options, and truncating to
| stay within the month makes the most sense to the most
| people most of the time. It's perfectly reasonable to
| take that step when resolving the indicated date to a
| representable value.
|
| I'll go further: JodaTime probably isn't focused on
| Precision Date Calculations; it behaves very much the way
| I expect someone working with forms and fields, general
| CRUD enterprisy software stuff, would want auto-filled
| dates to work.
| kaishiro wrote:
| Maybe I'm just being thick (it's usually the case), but
| for the life of me I still don't know what your answer to
| the parent's question is, and I can't tell how your
| quoted part is supposed to answer it.
| mjevans wrote:
| My 2 higher levels post post included an alternate
| phrasing of the test case they specified:
|
| """ None of the examples in the article use a more vague
| syntax, such as "0 days before the end of the month". """
|
| ---
|
| They asked:
|
| """ What's Feb 28th + 1 month?
|
| Does the human expect the last day of March? Or the 28th
| day of March? """
|
| ---
|
| It's implicit, the human only expects the month to
| change, because the input isn't a descriptive phrase "the
| end of the month" adjusted or not, it's a literal date.
| That's why my other test cases show the same behavior for
| the end of the month.
| brianpan wrote:
| The article is about naming the method. The fact that
| dates are messy means a clean API is
| difficult/impossible.
|
| I think plus() is a name that is good enough. I can't
| think of a better name that will help the user understand
| what will happen in the 2/28 + 1 month case. That's
| asking too much of a method name. That's what docs are
| for.
| opheliate wrote:
| Perhaps .dateAfter(1 month) would be more appropriate? I
| sympathise with the author in finding the violation of
| associativity of "plus" a bit jarring.
| mcphage wrote:
| I think that's the easier case--most people would expect
| Feb 28th + 1 month = Mar 28th. The tricker one is Jan 31
| + 1 month, because there is no Feb 31st. I don't think
| there is a "correct" answer to that.
| FabHK wrote:
| If I pay you Jan 31, Feb 28, March 31, April 30, etc.,
| don't I pay you monthly? Shouldn't I be able to express
| this as a repeated addition of a month?
|
| At any rate, these problems have been solved in finance,
| with proper date and schedule libraries.
| mjevans wrote:
| No, you should be able to express it the way I mentioned
| in other examples: Base .plus( N months ) where N is
| whichever month after the reference you want.
|
| .plus .plus .plus isn't correct because "x months"
| doesn't have a fixed size. You are NOT saying Base
| .plus(30 days), NOR are you saying Base .plus(4 weeks)
| ((which BTW, I'd expect to stay on the same weekday)).
| You're incrementing by an unstable value.
| jimminy wrote:
| If you're adding 1 month, you're working on the month's
| location, e.g. 2021 - (02) - 28.
|
| When you increment the month, the result would be
| 2021-03-28.
|
| The only time you'd modify the day, is if the day became
| invalid due to an overflow, during that increment. If,
| when, you overflow the days you'd set the value of days
| to the maximum in that month.
|
| If I tell someone, I'll get to that in a month, they
| expect by this day in the next month, the next calendar
| page, not 30/31 days.
| depaya wrote:
| You're setting the same trap as those viral math problems
| people share on Facebook:
|
| 6/2(1+2) = ?
|
| We could argue about what the _right_ answer is to that
| equation, but I call it a trap because it's intentionally
| confusing and devoid of any context (or the ability to
| ask a follow up question). There isn't really a situation
| where you would see that equation and not know the
| intended way to interpret it... just like your question.
|
| There are a couple of ways that context _could_ be
| provided though:
|
| I'm writing an automated task that should run once per
| month, I don't necessarily care what day of the month it
| runs though since it just cleans up some temp files. If
| today happens to be Feb 28th, and I say run today, then
| every month after, I would expect it to run Feb 28th,
| March 28th, April 28th...
|
| I'm writing an 'end of month' task that needs to run at
| the end of every month for some bookkeeping reason. If
| today happens to be Feb 28th, and I say run today, then
| every month after, I would expect it to run Feb 28th,
| March 31th, April 30th...
|
| In both of these situations I would program accordingly.
| Computers don't understand context, that's the job of the
| human programming it.
| CptMauli wrote:
| I still think plus is fine in this case. And really, my
| expectation is, that anybody who thinks about adding a
| months, knows about their own intention. Because I suspect
| just adding the month is not what most people are actually
| doing, I guess in 90% of the cases most developers will add
| another step, rounding to the last (or first) day, or to the
| next same weekday, whatever.
|
| The one thing nobody wants, is to add a month and land in the
| month one over.
|
| So even if the semantic differs between libraries for adding
| a month, it actually doesn't mather that much. Because for
| all the other cases one could imagine, most people will add
| days or weeks, if staying on the same day matters.
| Strs2FillMyDrms wrote:
| I completely agree with this. And reminds me of how Oracle
| brushed all complexity from the word "filter" and simply went and
| named the function as is. A Filter implies a flow direction, it
| also implies two segments, the desired and the unwanted portion
| of the filtering operation. Which one is the one staying? which
| is the one that passes through?
|
| I've seen people argue that one side should be called "sieved"
| while others say it should be named "selected"... to add to this
| , let's add even more complexity, since "selected" implies
| agency.. while filter performs a passive _selection_, in which
| case it should not be considered a selection at all.
|
| It seems easy, but in reality some concepts (If not all) are
| inherently messy, specially on the English language since it
| seems the most abstract of all languages.
| zestyping wrote:
| I think it's possible to be both honest and readable.
|
| One way to look at the issue is that the confusion stems from
| giving the _same name_ to operators of _different types_ , namely
| the "Instant plus Period" operator and the "Period plus Period"
| operator.
|
| Period could be implemented as a vector with independent
| components for days, months, and so on. (I don't know if that's
| how JodaTime does it, but that's what I would do if I wanted it
| to have a "plus" operation.) Then it could have a well-defined
| operator appropriately named "plus", which is both commutative
| and associative as one would expect.
|
| "Instant plus Period", however, is asymmetric, and cannot satisfy
| the identities we associate with "plus". So let's give it a name
| that is also asymmetric. How about "advanceBy"?
|
| 2021-01-30.advanceBy(1 month) = 2021-02-28
|
| 2021-01-30.advanceBy(1 month).advanceBy(1 month) = 2021-03-28
|
| 2021-01-30.advanceBy(1 month plus 1 month) = 2021-03-30
|
| 2021-01-30.advanceBy(2 months) = 2021-03-30
|
| That seems much less mysterious to me. Sure, a casual reader
| wouldn't be immediately confident about what advanceBy returns in
| all cases, but giving it a name that conveys its asymmetry helps
| a lot.
| ericevans wrote:
| Okay, I almost wish I hadn't read this comment because it is so
| similar to what I have in part 2! So please don't be annoyed
| when you see it in a couple of weeks ;-)
|
| I actually separated it into a separate part because it
| undermines my primary point. Sure, we all love it when we have
| a better decomposition, better names, and everything falls into
| place. But it doesn't always. Not in the time we have. So then
| we need ways to deal with the flaws. I decided that if I had
| ended with this it would have communicated: Aw, just keep
| trying and you'll get something nice! That can be very risky.
| stickfigure wrote:
| The only thing _advanceBy_ has going for it is that when the
| programmer inevitably tries to type _add_ (after wondering
| why _plus_ turns up nothing), the IDE _might_ show it in a
| dropdown.
| bottled_poe wrote:
| Ok, but that's when you read the docs. Also, in many
| languages, those expected methods could be mocked out to
| raise compiler errors, redirecting the developer to the
| correct methods.
| crecker wrote:
| Advertisements on your page ruin the experience. Anyway, great
| post!
| ChrisArchitect wrote:
| Classic 'one of the hardest things in Computer Science: naming
| things'
| derjdoj wrote:
| Please don't have a method named plusIshRoundCeiling() in a
| library that "might" be used for years in lots of projects :) The
| plus() one is almost there and you can easily explain the ugly
| bits in javadocs (with lots of warnings around it so that it
| sticks out and is addressed hopefully in some next convenient
| cycle)
| majormajor wrote:
| > (with lots of warnings around it so that it sticks out and is
| addressed hopefully in some next convenient cycle)
|
| How do you expect to address it when there isn't a single
| obvious "right" that everyone will have the same intuition on?
| derjdoj wrote:
| For instance, as in the article, provide the examples so that
| the users have more heads-up as in what is actually
| happening.
|
| Long term, you might deprecate it and solve the problem with
| more intuitive abstraction.
| bspammer wrote:
| plusIshRoundCeiling() forces the reader to look at the javadocs
| to understand the edge cases, and in fact it serves as a
| reminder that there even are any edge cases.
| progman32 wrote:
| Can you explain why you see this name as unacceptable?
| derjdoj wrote:
| I did'n quite intended to sound as if I think it would be
| unacceptable but the method name sounded too comical to me to
| be honest. Almost as if the intention is to say this method
| is a bit of a joke don't use it please.
|
| Maybe move() might work better here or
| moveToCalendarNearest() or something to flag up that in
| certain cases you need to be extra careful as mentioned in
| the article.
|
| It is really hard to get this right and personally I would be
| flagging it up in javadocs.
| ericevans wrote:
| I was trying to be funny here! There is lots of room between
| "plus", which I think is misleading, and the kitchen sink name
| you quote. Although it depends on how serious the problems
| were, in this case I'd pick something shorter that still
| suggested rough edges.
| derjdoj wrote:
| Makes perfect sense, I thought so as well. It just made me
| think hope somebody doesn't assume this would be the best
| name in this case :)
| [deleted]
| namanyayg wrote:
| This is the sort of mistake I see junior devs in my company
| making often. Any recommendations for an article that teaches a
| programmer on how to give honest names to variables and
| functions, ideally with lots of examples?
| AlexCoventry wrote:
| Why not call date.plus date.after?
| lmarcos wrote:
| I was expecting the author to actually give a supposedly better
| name other than 'plus' at the end of the post. Bummer. Perhaps
| there is no better name and actually 'plus' is the perfect
| election?
| SamBorick wrote:
| I think this is exactly the opposite of the takeaway. If the
| underlying concept isn't simple, don't pretend it's simple with
| a simple name.
| ericevans wrote:
| Yes, that was my point. Avoid overly simple names and also
| overly elegant names, unless you've actually found a simple,
| elegant concept.
|
| I do have thoughts on better names. I'll write a follow up
| about that. But I didn't want to include it in this article
| because my most important point was that we need ways to curb
| our perfectionist tendencies, and not by hiding the rough
| spots. And if I had ended on that up note, it would have been
| the usual cheesy ending: Look! I'm so good that I always end
| up with a beautiful design. Which, as you say, was the
| opposite of the takeaway I wanted.
| badJack wrote:
| to go a step further, anyone using time variables based on
| calendar periods should know better than to think the
| relative time of +1 month would be simple. I'm not sure it's
| the fault of using a simple name in this case.
|
| I'm sure there could have been a lot better options to drive
| that point home, but the author picked one that I'm sure
| everyone has an opinion about. That seems like an effective
| way to get people talking about it, but it could be divisive
| in bad ways.
|
| I'm a little put off by the other saying "a clean name shuts
| down our thinking", so maybe I'm just responding in disgust.
| ericevans wrote:
| What would be a better example to make the point, do you
| think? I always use the best example I can think of.
| Obviously there must be better ones.
| qwertox wrote:
| Date arithmetic seem to be a complicated matter.
|
| Around 2.5 years ago I sent the following feedback to the
| Wolfram|Alpha Feedback Team, never heard back from them.
| Message: When I compute "2019-01-31 to 2016-04-04" I get
| "2 years 9 months 26 days" and when I compute the
| reversed input "2016-04-04 to 2019-01-31" I get "2 years
| 9 months 27 days" But when I compute
| "2019-01-31 to 2015-10-21" I get "3 years 3 months 10 days"
| and when I compute the reversed input "2015-10-21 to
| 2019-01-31" I get "3 years 3 months 10 days"
| Shouldn't the very first one ( "2019-01-31 to 2016-04-04" ) also
| return "2 years 9 months 27 days"?
| mjevans wrote:
| Inverting the date range probably confused the logic involved
| in accounting for leap dates. Possibly it fails to account for
| swapping the order of the dates to achieve an always positive
| result?
| qwertox wrote:
| It's not (only?) related to leap years. It appears to be
| related to the month of April and some other weird stuff.
| -- leap year 2012: 2019-01-31 to 2012-01-30 --> 7
| years 1 day 2012-01-30 to 2019-01-31 --> 7 years 1
| day 2019-01-31 to 2012-02-29 --> 6 years 11
| months <---- Weird stuff 2012-02-29 to
| 2019-01-31 --> 6 years 11 months 3 days <---- Weird stuff
| 2019-01-31 to 2012-02-30 --> 6 years 10 months 30 days
| (2012-02-30 does not exist) 2012-02-30 to 2019-01-31
| --> 6 years 10 months 30 days (2012-02-30 does not exist)
| 2019-01-31 to 2012-03-30 --> 6 years 10 months 1 day
| 2012-03-30 to 2019-01-31 --> 6 years 10 months 1 day
| 2019-01-31 to 2012-04-30 --> 6 years 9 months <----
| April (any day in April) 2012-04-30 to 2019-01-31 -->
| 6 years 9 months 1 day 2019-01-31 to 2012-05-01
| --> 6 years 8 months 30 days 2012-05-01 to 2019-01-31
| --> 6 years 8 months 30 days -- non-leap year
| 2013: 2019-01-31 to 2013-04-30 --> 5 years 9 months
| <---- April (any day in April) 2013-04-30 to
| 2019-01-31 --> 5 years 9 months 1 day
|
| I stumbled upon it while testing some JavaScript time and
| date frameworks and wanted to use Wolfram|Alpha because I was
| somewhat confused with the correct interval between two
| dates.
| underwater wrote:
| I think a lot of people here are getting hung up on the
| associativity, or that the name is not fully descriptive.
|
| But remember that this functionality exist ms is a library for
| operating on dates. It is clear to the user that values like "2
| days" or "1 month" are intermediate values that cannot or should
| not be used as output. They need to be applied to an absolute
| date to become resolved and be useful.
|
| The context, and real world experience with dates, makes this
| distinction obvious.
|
| Side note: I created a similar library in the past. I struggled
| more with deciding if the clipping behaviour was even desirable
| than worrying about the naming, but that was merely due to the
| API I used. My function signature was `addMonths(v, 2)`, which
| eliminated ambiguity.
| brundolf wrote:
| I don't normally comment on sites' styling, but this one really
| needs some horizontal padding or margin
| _dwt wrote:
| Without quibbling about the particular (adding a month) example,
| I can vouch that this is a great way to help the people for whom
| the software is being built refine their thinking. I help clients
| often with inherited/legacy/hacked-together code, often written
| by non-expert programmers for "odd" platforms (think: Excel VBA
| macros, which use the spreadsheet itself as the sole data
| structure). Identifying the awkward implicit concepts in this
| code, and giving them corresponding awkward names, can help the
| client recognize places where their tool doesn't map onto their
| domain knowledge the way they thought it did. As a recent
| example: "run of same-named tasks when ordered by date, then
| name" isn't exactly the same concept as "block of time dedicated
| to a specific task"!
| slaymaker1907 wrote:
| I disagree with the author on this one. While it isn't a perfect
| analogue to mathematical addition, neither is floating point
| math. The bigger issue is that calendar math is tricky and
| durations cannot always be converted between one another.
|
| The correct solution in my opinion would be to have distinct
| types for this sort of thing to help clear up the ambiguity. Date
| and time are really tricky concepts to model though and it is a
| difficult balance between honesty/precision and intuitiveness for
| such APIs. One such clunkiness I've seen with KeepassXC is that
| it stores password expirations exclusively as timestamps. This is
| actually not correct for the common use case of passwords
| expiring on a particular day (since you don't know the time)
| because what if you are in a different timezone? This I think
| shows that you can't just convert dates into datetimes without
| problems.
| gugagore wrote:
| If I may elaborate, the comparison to IEEE floats is apt
| because they addition is not associative there either:
|
| julia> (1e16 + 1) + 1 1.0e16
|
| julia> 1e16 + (1 + 1) 1.0000000000000002e16
|
| I think the fundamental issue with dates, however, is deeper
| than "addition is non-associative". It is that "1 month" is a
| context-sensitive duration (so is a "1 day", due to leap
| seconds).
|
| I am curious about using intervals. If "{Year} January (no
| day)" had a representation as "Jan. 1 - Jan. 31", then "Jan. 1
| -- Jan. 31" + "1 month" = "{Year} Feb. 1 -- Feb. 28" (or 29 if
| the {Year} is a leap year".
___________________________________________________________________
(page generated 2021-08-04 23:00 UTC)