[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)