• by misterdata on 10/28/2020, 4:23:38 PM

    Having used both stricter (Rust) and more loose programming languages (JS), I can add that stricter APIs have made me think more about edge cases than the less-strict APIs. In the less-strict languages I often find myself debugging some edge case, whereas in the stricter languages I am more often required to think about edge cases up front (including those that I know will not be relevant, so in some cases this causes more work than necessary!). Still I much prefer the stricter APIs.

  • by Karellen on 10/28/2020, 5:01:33 PM

    I like Rusty Russell's classic "Hard to Misuse Interface Levels". The first couple of items are

    0: Impossible to get wrong/DWIM

    1: Compiler/linker won't let you get it wrong

    ...through

    9: Read the correct LKML thread and you'll get it right - SET_MODULE_OWNER

    ...all the way up to 17. I'll let you discover the rest yourself :-)

    https://ozlabs.org/~rusty/ols-2003-keynote/img39.html

  • by Joker_vD on 10/28/2020, 4:04:37 PM

    I think that's a trick question. If "substring()" has "substring(int start, int end)" semantics, then I prefer the strict version: it must be 0 <= start < len(s), start <= end <= len(s), otherwise it'll throw an exception.

    But if the semantics is "substring(int start, int length)", then I prefer the partial forgiveness for the length parameter: if start + length > len(s), then assume length was actually len(s) - start; but if length < 0, still throw an exception.

  • by aimor on 10/28/2020, 5:15:00 PM

    In my experience forgiving APIs don't play well together. I've inadvertently tried it and wind up having to make everything 'forgiving in the same way'. Otherwise out-of-range inputs mean different things to different functions, and using multiple APIs requires tracking all the forgiving behavior.

  • by z3t4 on 10/28/2020, 6:00:05 PM

    Its not always binary. You want to throw an error at even the slight amount of error. This will annoy users, but over time as bugs and edge cases are fixed - it will lead to a very robust program.

    Meanwhile you want to save time/work for the caller. You dont want the caller to need many lines of boilerplate just to setup the call.

  • by thamer on 10/28/2020, 6:41:00 PM

    For reference, this is what String.substring(int, int) does[1] in Java:

        public String substring(int beginIndex, int endIndex) {
            int length = length();
            checkBoundsBeginEnd(beginIndex, endIndex, length);
            ...
    
    Where checkBoundsBeginEnd[2] does:

        static void checkBoundsBeginEnd(int begin, int end, int length) {
            if (begin < 0 || begin > end || end > length) {
                throw new StringIndexOutOfBoundsException(
                    "begin " + begin + ", end " + end + ", length " + length);
            }
        }
    
    (as it should, in my opinion).

    [1] https://github.com/AdoptOpenJDK/openjdk-jdk11/blob/f0ef2826d...

    [2] https://github.com/AdoptOpenJDK/openjdk-jdk11/blob/f0ef2826d...

  • by madmax108 on 10/28/2020, 5:10:28 PM

    As someone who writes APIs for a living, this is often something interesting that I find a lot of division on:

    Say you have an API that is documented to accept params A,B or C i.e /v1/api?A=1&B=2&C=3

    What should happen if you pass a param D to it? i.e. /v1/api?A=1&B=2&C=3&D=4

    The two most common schools of thought are:

    1) Ignore D

    2) Throw an error

    Both present their own problems, esp. when D may be closely related to A,B,C. Interesting how API design also tends to side with personal preferences for strictness or leniency

  • by postalrat on 10/28/2020, 6:41:56 PM

    This may be unrelated but every time I see a timestamp as a string in json I wonder why. There is so much less to go wrong with a numeric unix timestamp yet 9 times out of 10 a developer will use a string.

    If you are going to make things strict at least make it hard to mess up.

  • by megous on 10/28/2020, 4:00:23 PM

    Something I read in the RFC for email message format (or maybe elsewhere, but I have it associated with email for some reason) is: to be strict in output that you produce, and lenient in the input you accept.

    I think it makes some sense for data formats, if you want max compatibility of independently developed software that processes the data. Not sure about APIs though. Having them fail fast on unexpected input is pretty valuable.

  • by CharlesW on 10/28/2020, 3:57:58 PM

    It seems like "forgiving with warnings" should be another option listed here. This also lets API creators do neat things like warn people ahead of time of future deprecations, etc.

  • by dicroce on 10/28/2020, 3:53:36 PM

    If I had to choose I'd say prefer strict... But reading this made me wonder: what if you could be strict during development but switch to forgiving for deployment?

  • by RocketSyntax on 10/28/2020, 6:17:44 PM

    if it accepts a list of items [], but only a single string is provided "", accept it

  • by ajabhish on 10/28/2020, 2:14:00 PM

    What is preferred by API consumers?

  • by m463 on 10/28/2020, 6:52:12 PM

    obligatory link to postels law[1] and criticism

    https://en.wikipedia.org/wiki/Robustness_principle

  • by kstenerud on 10/28/2020, 4:24:27 PM

    > Suppose it's the early 1990's and you're James Gosling implementing String.substring(int, int) for the first time. What should happen when the index arguments are out-of-range? Should these tests pass? Or throw?

    It depends entirely on what the rules of the API are. Function signatures in most languages lack any form of compiler enforcement of rules, so you must implement them in code, and then list the rules in the function's description. The strictness you apply doesn't matter as much as your description of what argument range is allowed, and how the behaviour is affected.

    For example, substring could allow overshoot with the description "If the substring would go beyond the end of the string, the remainder of the string is returned".

    What you should be concentrating on is the 80% use case of your API. What will 80% of your users need? If the lack of length overshoot support would be cumbersome to the 80%, you support overshoot. If it's useless to the 80%, you leave it out. You can also implement things as layered APIs, with more general lower level functions, and then higher level functions that are more strict. Then the 20% can use the lower level functions for their esoteric use cases, and the 80% can stick to your easy-to-use and hard-to-screw-up high level API.