Earlier quoted context omitted.
I don't want my image editor to feel like something in a creative manner though. I want it to rotate an image by 90° when I tap the button that does that. See, this is exactly my point when I say that animations are no end in themselves. They serve a supporting role to better get the actual job done. The actual job is not "feel" it is "do". For vibes, there are movies, Art, and AI hallucinations. Of course, "feel" ca…
I disable all animations everywhere (Android, Windows, Gnome) because I hate that they make me feel like I'm losing time waiting for something that could be instant, and they sometimes make me dizzy. I'm particularly exasperated that iOS doesn't offer that possibility. But rotating an image is one of the rare use cases where I do want the animation. It makes me see what action happened, with which rotation angle, wit…
If you're a button, you have one job
181–190 of 308 posts
Re: If you're a button, you have one job
#182How about when users accidentally click too much, or they believe the first click didn’t register? I am still reminded of a keynote where Steve Jobs was demoing how much faster PDF documents would display on the newer macOS. So he had engineers put a button in for him to click that would scroll through the PDF on the screen, and he accidentally clicked it more than once. Steve wondered aloud if it would scroll all th…
Debouncing exists for a reason. Sometimes when a button is clicked twice, you want it executed twice, sometimes you don't. Distinguishing which is better in which situation is not trivial. At the very least, you should consider which is appropriate for which situation, what if, in your UI, for some buttons one is the obvious choice, for others it's the other, but for some it's not so clear, and both behaviours are de…
Re: If you're a button, you have one job
#183I used to have a device with a physical button which, when you pressed it, would beep and add 30 seconds to the time. However, sometimes it would beep and not add 30 seconds, and sometimes it would add 30 seconds without beeping, so you always had to squint at the dim display to discover whether it had worked or not. I thought this must be a peculiarly bad design ... but since then I have lost count of the number of…
Bad programming. People who have experience with embedded programming knows that reading out a button usually means denouncing. At the speed a microcontroller can read out a button it will change it's state multiple times per press because of contact bounce. Meaning when a user presses a button the program sees off, on, off, off, on, on, off, on, on, on, on, on, on, etc. Now if you just naively read out the current s…
The standard way to debounce is to attach an timer to the button. When you press the button, an ISR runs that temporarily disable the timer from triggering again and starts the timer for a specific period (say 20ms). The processor is free to do whatever it wants for the next 20ms. When that timer expires, another routine checks to see if the button is still being held, sets the button's state accordingly, then re-enables the button Timer so it can be triggered again.
Averaging loops are much better for analog inputs where you may have noise that throws off the reading. You only care about a button being on or off, it doesn't matter if it's been mostly on for that period only that it's still on.
When you get into extremely fast digital inputs that need to be reacted to sooner than the debounce wait period, that's when you need hardware debouncing.
Re: If you're a button, you have one job
#184Hair dryers normally have 2 controls, one to adjust how much air it blows, and another one to adjust how hot the jet is. When they're off, they don't blow any air so it would make sense to me to decide that the button to turn them on is the one that controls how much air it blows. It goes from 0 to 1 to turn on, so you just learned that that button changes how much air comes out of it. But no, most of them do the opp…
Re: If you're a button, you have one job
#185I couldn't disagree more. Every button has two jobs. One is to accurately convey what it will do. Two is to then do it. Several of us can neither remember what your dynamic, curvy, arrowed, action lines do, nor can we extrapolate from them. They are an exercise in frustration. The nod to situational disability is appreciated, but most would have been useless without text descriptions.
You are talking about the label on the button. The button does one thing. The label does one thing. You put the two together and you have UX.
Re: If you're a button, you have one job
#186Re: If you're a button, you have one job
#187And if you have a tray button that needs to e.g reach over the network to a HomeAssistant instance that needs to itself reach out to some fuckass IoT vendor server, you may as well not expect any sort of feedback before you close the tray.
Re: If you're a button, you have one job
#188Better post an overview of everything a good button should do (no it is not just one job).
Even better, post an overview of good GUI design in general.
Re: If you're a button, you have one job
#189Earlier quoted context omitted.
I call it the "doing two things" problem. Your write imperative code, which issues two commands, both of which can fail independently. There are plenty of ways to pretend to 'deal with it'. Firstly it will just pass all tests, so most devs can stop thinking about it right away. A dev might think you can just catch and log the exception. Doesn't fix it. You could run the code in prod for a while, see if it goes wrong.…
> A dev might think you can just catch and log the exception. Doesn't fix it. You've just succinctly made the argument against checked exceptions FWIW (which I agree with you on). Anyone who has used Java in anger (is there any other way?) will be familiar with: try { doSoemthing(); } catch (CheckedException e) { logger.error("Didn't work", e); } Fault tolerance is general is terrible in most software. One of my bigg…
Let's say you want to retry a network request. It's... A bit more complex than it seems, right?
Firstly, you need to know exactly what type of error you ran into. Some errors aren't really recoverable. Maybe a programming issue occurred and you are constructing an invalid URL and the HTTP client is yelling at you. No sense in retrying that 20 times. Maybe it's a network error, that seems like a good candidate to retry. Maybe, the request succeeded and we have a response, but it is a 500 error, again, seems like a good candidate.
Secondly, you need to know if it is safe to retry. If the request is essentially idempotent, like a read-only GET request, then surely it is safe, right? But, what if it isn't safe? Forget about solutions like idempotency tokens; let's assume you don't control that. Now you need to figure out how you can know if the request had side effects. If a well-known 4xx error is returned you might know, but if you get a network error or a 5xx error it's much harder. Did the request fail during a buffered response after the side effects were already applied? Maybe you can check to see if the request applied with another request. Now you have two network requests, and both need error handling.
Finally, and probably most obviously, you have to make sure you don't hammer the server when it is under load. To avoid the thundering herd problem, you'll probably want to use an exponential backoff with some jitter.
What sucks about all of this is that while there are reusable components here, the concerns effortlessly cut through different layers, making them a pain in the ass to deal with. It isn't that it is impossible for a library to handle all of these problems (I anticipate an excited evangelist may reply explaining how their favorite library does it all in one package if this post gets enough visibility) it's just that this is hard and these problems repeat in different forms, in a way that makes it difficult to fully eliminate the repetition. And this is just the most obvious basics, whereas in reality there are almost always case-specific complexities.
You can, for example, encapsulate a reasonable exponential backoff with deadline implementation and apply that as appropriate for different things, but you can't really cheat your way out of having to think about all of these things, especially if you don't control all of the network APIs you might have to interface with.
This is one part of why I don't like try/catch exceptions. They are an appropriate mechanism to use as a failure isolation boundary due to their stack unwinding capability: it would still be bad in most cases if a logic error or upstream error not being handled properly in a single network request handler were able to crash an entire network server, so being able to blanket catch everything that bubbles up an log it is good. But then using this for normal error handling, it makes doing the wrong thing perhaps just a bit too easy. I don't think you should have to self-flaggelate in order to say "just crash if this errors", but I do think that you should have to say it. Try/catch exceptions are backwards by default, just write normal looking control flow and no errors are handled and it's hard to tell if there even are any. Checked exceptions try to fix this but somehow this feels even worse; now you have a flattened list of exceptions that may occur at various different layers of depth, in some cases the same exception can occur at different layers of depth, you may literally need to read source code and map out the call stack in your head to be sure. (Hope it doesn't change later.)
The Result or Expected type concept seems like the way to go in the frame of modern programming languages. Go's error passing also works OK though it has papercuts (that a linter can help you with, at least.) To me it makes more sense to make stack unwinding error handling a more niche feature used for isolating error domains, rather than use them for all error handling.
But even that! Even that doesn't solve the problem. You still have to sit there and think about the types of errors that can occur and their consequences. At best, explicit error handling with value types just encourages you to confront it and makes it visible, even in cases where you still say "OK, pass to caller".
Re: If you're a button, you have one job
#190One similarly egregious UX issue on the latest Android is that pressing buttons in the dropdown tray doesn't give any feedback until the action is complete. I can press the "turn on WiFi" button and receive zero haptics or visual indication that the phone registered my tap, for over a second, and THEN it will decide "okay! let's shake the phone and change button color now". And if you have a tray button that needs to…