20 votes

I is for Intent: why your app turned into spaghetti

9 comments

  1. [6]
    skybrian
    Link
    From the article: ... ... ... (Content warning: near the beginning, there's a homophobic slur attributed to a fictional character. Distracting in an article that has pretty interesting ideas about...

    From the article:

    It should be obvious that any editor that didn't remember which column you were actually on would be a nightmare to use. You know it in your bones. Yet this only works because the editor allows the caret to be placed on a position that "does not exist." What is the caret state in the middle? It is both column 1 and column 6.

    To accommodate this, you need more than just a View that is a pure function of a State, as is now commonly taught. Rather, you need an Intent, which is the source of truth that you mutate... and which is then parsed and validated into a State. Only then can it be used by the View to render the caret in the right place.

    ...

    All of these involve storing and preserving something unknown, invalid or unused, and bringing it back into play later.

    More so, if software matches your expected intent, it's a complete non-event. What looks like a "surprise hidden state transition" to a programmer is actually the exact opposite. It would be an unpleasant surprise if that extra state transition didn't occur. It would only annoy users: they already told the software what they wanted, but it keeps forgetting.

    ...

    So in practice I have a function patch(value, update) which implements a comprehensive superset of a deep recursive merge, with full immutability. It doesn't try to do anything fancy with arrays or strings, they're just treated as atomic values. But it allows for precise overriding of merging behavior at every level, as well as custom lambda-based updates. You can patch tuples by index, but this is risky for dynamic lists. So instead you can express e.g. "append item to list" without the entire list, as a lambda.

    I've been using patch for years now, and the uses are myriad. To overlay a set of overrides onto a base template, patch(base, overrides) is all you need. It's the most effective way I know to erase a metric ton of {...splats} and ?? defaultValues and != null from entire swathes of code. This is a real problem.

    ...

    For List vs Map, you can have this particular cake and eat it too. Just provide a List<Id> for the order and a Map<Id, T> for the values. If you structure a list or tree this way, then you can do both iteration and ID-based traversal in the most natural and efficient way. Don't underestimate how convenient this can be.

    This also has the benefit that "re-ordering items" and "editing items" are fully orthogonal operations. It decomposes the problem of "patching a list of objects" into "patching a list of IDs" and "patching N separate objects". It makes code for manipulating lists and trees universal. It lets you to decide on a case by case basis whether you need to garbage collect the map, or whether preserving unused records is actually desirable.

    (Content warning: near the beginning, there's a homophobic slur attributed to a fictional character. Distracting in an article that has pretty interesting ideas about software architecture.)

    10 votes
    1. talklittle
      Link Parent
      This reminds me of Android's DiffUtil which takes two lists and figures out the operations needed to transition from one to the other, and animates the changes (rows moving up and down, inserts,...

      So in practice I have a function patch(value, update) which implements a comprehensive superset of a deep recursive merge, with full immutability. It doesn't try to do anything fancy with arrays or strings, they're just treated as atomic values. But it allows for precise overriding of merging behavior at every level, as well as custom lambda-based updates. You can patch tuples by index, but this is risky for dynamic lists. So instead you can express e.g. "append item to list" without the entire list, as a lambda.

      This reminds me of Android's DiffUtil which takes two lists and figures out the operations needed to transition from one to the other, and animates the changes (rows moving up and down, inserts, deletes, crossfades).

      The thing is DiffUtil works best with two distinct lists of objects, ideally immutable ones, so it can calculate if an object changes. Otherwise you're comparing an object instance to itself. This may or may not play well with your app's architecture, especially in Java-based object oriented languages, where mutability is the default.

      So, like the article's patch, DiffUtil also provides a callback that lets you customize some comparisons. To avoid having to do a deep copy of an object every time you mutate an object in the list—since it may be slow and cumbersome, and you may want to hold onto the old references to the original objects—setting a dirty flag (or keeping a HashMap of dirty IDs) is another way to do it. Then in the DiffUtil areContentsTheSame() callback, you can check the dirty flag instead of following the assumption that you're comparing two distinct objects.

      3 votes
    2. [4]
      hahnudu
      Link Parent
      Is limp wristed really a homophobic slur? From a cursory search it seems like a generic insult that is being applied to a homosexual person in the article. I see it if it was written as a...

      there's a homophobic slur attributed to a fictional character.

      Is limp wristed really a homophobic slur? From a cursory search it seems like a generic insult that is being applied to a homosexual person in the article. I see it if it was written as a hyphenated word like limp-wristed-homosexual

      5 votes
      1. [2]
        Lapbunny
        Link Parent
        Yes, it's the hackneyed motion. Not sure what was in your cursory search... wiktionary, dictionary.com, and Cambridge note the connotation.

        Yes, it's the hackneyed motion.

        Not sure what was in your cursory search... wiktionary, dictionary.com, and Cambridge note the connotation.

        9 votes
        1. hahnudu
          Link Parent
          Hmm, That is news to me. None of the websites I checked mentioned a connection to homosexuality https://www.merriam-webster.com/thesaurus/limp-wristed...

          Hmm, That is news to me. None of the websites I checked mentioned a connection to homosexuality

          https://www.merriam-webster.com/thesaurus/limp-wristed

          https://www.collinsdictionary.com/dictionary/english/limp-wristed

          https://www.wordreference.com/definition/limp-wristed

          The thing I saw most often was people using the term in reference to pistol shooting but I admit that could be because my browsing habits predisposed my search results toward firearm related answers.

          6 votes
      2. skybrian
        Link Parent
        Not an expert on insults; just didn't want anyone to be surprised.

        Not an expert on insults; just didn't want anyone to be surprised.

        4 votes
  2. [3]
    arqalite
    Link
    Honestly these kinds of articles can be a double-edged sword. For enterprise-level, huge applications this kind of model could work well, but for smaller scale projects this feels more spaghetti...

    Honestly these kinds of articles can be a double-edged sword. For enterprise-level, huge applications this kind of model could work well, but for smaller scale projects this feels more spaghetti than the original approach. You'd have two data structures handling roughly the same thing, passing them around the program logic, when you can have just one, with some conditions peppered where it matters.

    Also I feel like the author sees things too black and white. Strictness and correctness has its place, just as being more lax and permissive has its place. You can be both, and you should be both.

    10 votes
    1. Gaywallet
      Link Parent
      The success of my career hinges on this very true statement. The reason I'm consistently and repeatedly called on by upper management for difficult projects is this. They know that when there's a...

      Also I feel like the author sees things too black and white. Strictness and correctness has its place, just as being more lax and permissive has its place. You can be both, and you should be both.

      The success of my career hinges on this very true statement. The reason I'm consistently and repeatedly called on by upper management for difficult projects is this. They know that when there's a fire, I'm the best fire fighter for it, because when I arrive I am going to quickly and accurately assess the situation to the best of my abilities, speak with the relevant individuals to create action plans or at least understand on a high level what can be done, and then return to leadership with a few potential options. These options always lie on a spectrum of strict and correct to lax and permissive. I am impartial to the solution chosen - my job is merely to explain, in detail, precisely what is being sacrificed and what is being gained by each approach. Yes, there are situations when one approach is almost assuredly the more correct approach, but more often than not the correct approach is the most expensive and timely one and understanding where to set the goalposts between perfection and duct tape is all about getting high quality information on the risks and benefits of each intermittent state.

      9 votes
    2. skybrian
      Link Parent
      He’s making a contrast, but my guess is that he would probably agree with that? Seems like in many cases, the loose data structure is implicit in the environment and you don’t need to write it at...

      He’s making a contrast, but my guess is that he would probably agree with that?

      Seems like in many cases, the loose data structure is implicit in the environment and you don’t need to write it at all. For example, command-line programs can validate their arguments immediately, but there is still a looser data model somewhere that’s probably plain text. It might be in the text editor for the file containing the shell script, or in the shell where the user types a command.

      Similarly for programming itself. The editing model even for a strict language is “it’s arbitrary text files in a directory tree.” You do normally want to be able to save a file that contains syntax errors and to cut and paste code with errors in it, though you probably don’t want to publish it that way.

      In simple cases, you don’t need to write code that fully represents drafts with errors. It depends on how ambitious you get about providing an editing environment. Parsers that run in an IDE need to be more sophisticated about representing draft code than parsers that are just used in compilers, which often throw away comments immediately.

      If you’re providing the editor, there’s a design decision: how far should you go to let multiple users collaborate on drafts? Gmail has a drafts folder but it’s private to each user, while Google Docs is about live editing.

      3 votes