The main problems of the development of modern interfaces
Hi, Habr! I present to you the translation of the post by Dan Abramov “The Elements of UI Engineering” about current problems and tasks that need to be solved in a good interface. The author examines the fundamental problems in the development of interfaces, the comprehension and solution of which independently - without using ready-made libraries and frameworks - can give a deep understanding of the existing solutions on the market in the field of frontend-development.
In my last publication I wrote about how important it is to be able to recognize gaps in one’s own knowledge. It might seem as though I offer you excuses to be mediocre. Not at all! But in fact, our knowledge is a vast topic of conversation.
I am convinced that you can start your own knowledge "right off the bat" and there is no need to learn technologies (technological stack for web development - approx. Translator) in a certain order. But I also think that the accumulation of experience and professional skills in the chosen field is of great importance. Personally, I have always had the most interest in creating user interfaces.
And I wondered - what did I understand and what did I find important?Of course, I am well acquainted with technologies such as Javascript and React. However, the most important things that come with experience are elusive and usually elude when trying to formulate them precisely. I never tried to express them in words. This is my first attempt to systematize and describe some of them.
Today there are many different ways to learn technology. Which library to bid in 2019? And in 2020? Should you study Vue or React? Or Angular? What about redux or rx? Do I need to learn Apollo? REST or GraphQL? It's easy to get lost here! In addition, the author may also be wrong.
My greatest achievements in knowledge are not related to any particular technology. I started to understand more when I was working on solving a specific UI (User Interface - translator) problem. However, sometimes I found other libraries and patterns that helped me solve the problem. And sometimes he wrote his own decisions (both good and terrible).
This combination - consisting of thinking about the problem , experimenting with the tools and applying various solutions.- gave me the most valuable experience and skills. This post only focuses on issues that I dealt with while creating user interfaces.
If you have been developing user interfaces, then you most likely have encountered some of these problems - directly or when using libraries. In both cases, I recommend that you work on a simple application without libraries at all, try to reproduce and solve these problems. There is no one right solution for any of them. The experience comes with an introduction to these problems and the study of possible solutions, given the strengths and weaknesses of each.
You liked the post and the inscription appeared: “You and 3 of your friends appreciated it”. You clicked the like button again and the caption disappeared. Sounds easy! But it is possible that such an inscription is present in several places on the screen. It is possible that there is also an additional visual indication for the likes (for example, the background color of the button), which must also change. A list of "Likelik", which was previously obtained from the server and displayed when you hover the mouse, should now include your name. And if you moved to another section or clicked the Back button, then the post should not “forget” that it has your like. As you can see, even local integrity for one user creates a number of difficult tasks. At the same time, other users can also interact with the data that is displayed on your site (for example, like the post you are viewing). How do we keep data synchronized in different parts of the screen? How and when should we verify local data with the server and receive / send changes?
People admit the lack of visual feedback for their actions only for a very limited time. For continuous user actions, such as scrolling, no application response is possible only for the shortest period. Even the skipping of one frame in 16 milliseconds already looks buggy and flawed. For discrete(one-time) actions, such as a click, according to some studies, users normally perceive delays in the cast less than 100 milliseconds. If the action takes more time, then you need to show a visual indicator. However, there are several counterintuitive tasks. Indicators that cause a shift in a page template or that go through several alternating stages can make an action take longer to “feel” than it actually was. Similarly, an application’s response within 20 milliseconds by skipping a single frame of animation can be “felt” slower than a full animation within 30 milliseconds. Our consciousness does not work like benchmarks. How do we keep apps responsive?
Computer computing and data transmission over the network takes time. Sometimes we can ignore computation time if it does not affect responsiveness on user devices (however, make sure that you test your code on old and budget devices). However, processing the time of data transmission over the network cannot be avoided - it can be calculated in seconds! An application cannot simply “hang” while we are waiting for the data or code to load. This means that any action requiring new data, code, or assets is potentially asynchronous and should handle the state of its load. This is true for the vast majority of screens and elements. How to properly handle the delay in data transmission, without displaying a cascade of spinning spinners or empty “holes” in the interface? How to avoid page shifts? And how to change asynchronous dependencies without the need for constant rewriting of the code?
We expect the interface to be “stable” when interacting with it. Elements should not suddenly disappear. Navigation, both within the application (for example, links) and external (for example, the Back button in the browser), must also adhere to this principle. For example, switching between tabs
We can make the implementation of the Back button instant by adding a local cache to the application. To do this, we will store the necessary data in the cache (data from the past state - note of the translator). We can even theoretically update the cache to keep the data up to date. However, the implementation of caching entails new problems. The cache may be out of date. If I changed the avatar, then it should be updated, including in the cache. If I published a new post, it should immediately appear in the cache, otherwise the cache will become invalid. Such code may eventually become too complex and difficult to maintain. What if the publishing process fails? How long is the cache stored in memory? When we get the data set again, then we merge the new data with the previously cached ones, or do we get rid of the old cache and cache the entire set again? How should pagination and sorting be cached?
The second law of thermodynamics reads as follows: “Over time, everything turns into a complete mess” (not literally, of course). This is also true for user interfaces. We can not predict the actions of a particular user and their sequence. At any time, our application can be in one of a huge (gigantic!) Number of different states. We try our best to make the result predictable and limited according to our design. We don’t want to look at the screenshot with the bug and think to ourselves: “How did it happen at all?” For N possible states, there are N × (N – 1)possible transitions between them. For example, if a button has five different states possible (normal, active, hover, highlighted and disabled), the code responsible for changing the button should be correct for 5 × 4 = 20 possible transitions - or explicitly prohibit some of them. How do we cope with a combinatorial increase in possible states and create predictable visual output?
Some things are more important than others. Maybe your dialog interface should appear strictly “above” the button with which it was called and go beyond the limits of the parent container. Or a just-scheduled task (i.e., the result of a click) may be more important than a long-running task that has already begun. As the application zooms in, its different parts, written by different people or even teams, begin to compete for limited resources, such as processing power of the processor, network traffic, screen space, or bundle size. Sometimes you can distribute elements along a single “importance” scale, like a CSS rule
Sites that are not adapted for people with disabilities are not a highly specialized issue. For example, in England, every fifth user faces this problem ( herevisual infographics). I felt it on myself. Despite the fact that I am only 26, I hardly use sites with thin fonts and low-contrast colors. I try to use the trackpad less often, but I’m afraid of the day when I have to use a site that is not adapted for this from the keyboard. We must not turn our applications into a nightmare for people with disabilities - and the good news is that it is not so difficult. We need to start with a study of solutions and tools. In addition, we must make it simple and understandable for designers and developers to make the right decisions. What can we do to ensure that the availability of our applications is enabled by default, and is not a belated refinement?
Our applications should work worldwide. Yes, people speak different languages, and besides this, support is needed for writing from right to left, with minimal effort on the part of developers. How do we support different languages and scripts without losing the responsiveness of the application and response time?
We must deliver the application code to the end user's computer. What mode of transmission and format will we use? In this question, each answer will be a compromise with its own set of strengths and weaknesses. For example, native applications have to load all their code in advance because of its huge size. While web applications usually have a much shorter boot time, they are forced to handle a lot of latency and downloads during use. How do we decide which type of delay to choose from these two options? How can we optimize response time based on user usage statistics? What data do we need to have to make the best decision?
No one likes to meet bugs in their own programs. However, some bugs will inevitably get to production. And it is very important - what will happen then. Some bugs cause incorrect, but strictly defined and predefined behavior. For example, your code indicates an inappropriate state for a given condition. But what if, as a result of the bug, the application completely stopped rendering? In this case, we will not be able to continue the meaningful execution of the program, since the visual output will not be defined. The error in drawing one post from a tape should not “break” the drawing of the entire tape or enter the application in an unstable state, which will lead to further errors. How do we write code? which isolates errors when rendering or retrieving data in one of the parts and continues the rest of the application to work correctly? What does fault tolerance mean when creating user interfaces?
In a small application, we can hardcode and solve all the above problems head on. But applications tend to grow. We want to be able to reuse, fork and merge the various parts of the application and share it with other people. We want to define clear boundaries between parts of a whole that will be accepted by different people and at the same time avoid too rigid logic, as it often changes and evolves in the process of work. How do we create abstractions that will hide the details of the UI implementation? How can we avoid the re-emergence of these problems with the growth of the application?
Of course, there are still many problems that I did not mention. This list is by no means complete or exhaustive. For example, I didn’t touch upon the topic of design and development collaboration, debugging or testing. Perhaps we will come back to it another time.
It is tempting to read about these problems, keeping in mind as a solution a specific framework for displaying data or a library for receiving data. But I recommend that you imagine that these solutions do not exist and try to read again. How would you try to resolve these issues? Try to implement your ideas on a simple application. I will be glad to see the results of your experiments on Github. (Just mark Dan Abramov on Twitter and attach links to repositories - approx. Translator).
What is especially interesting in these problems is that most of them manifest themselves on any scale. You may encounter them when working on a small widget, such as a tooltip, and in huge apps like Twitter or Facebook.
Think about the nontrivial user interface elements from the application that you like to use, and run through the list of the above problems again. Can you describe the tradeoffs that the developers went to? Try to reproduce similar behavior from scratch!
I realized a lot about the development of good user interfaces, experimenting with these problems in small applications without using third-party libraries and frameworks. I recommend this to anyone who wants to gain a deep understanding of solutions and trade-offs when developing complex interfaces.
Translator's Note
The text is written and translated from the first person. The original author in English is Dan Abramov , the developer of the React library for building complex user interfaces.
In my last publication I wrote about how important it is to be able to recognize gaps in one’s own knowledge. It might seem as though I offer you excuses to be mediocre. Not at all! But in fact, our knowledge is a vast topic of conversation.
I am convinced that you can start your own knowledge "right off the bat" and there is no need to learn technologies (technological stack for web development - approx. Translator) in a certain order. But I also think that the accumulation of experience and professional skills in the chosen field is of great importance. Personally, I have always had the most interest in creating user interfaces.
And I wondered - what did I understand and what did I find important?Of course, I am well acquainted with technologies such as Javascript and React. However, the most important things that come with experience are elusive and usually elude when trying to formulate them precisely. I never tried to express them in words. This is my first attempt to systematize and describe some of them.
Today there are many different ways to learn technology. Which library to bid in 2019? And in 2020? Should you study Vue or React? Or Angular? What about redux or rx? Do I need to learn Apollo? REST or GraphQL? It's easy to get lost here! In addition, the author may also be wrong.
My greatest achievements in knowledge are not related to any particular technology. I started to understand more when I was working on solving a specific UI (User Interface - translator) problem. However, sometimes I found other libraries and patterns that helped me solve the problem. And sometimes he wrote his own decisions (both good and terrible).
This combination - consisting of thinking about the problem , experimenting with the tools and applying various solutions.- gave me the most valuable experience and skills. This post only focuses on issues that I dealt with while creating user interfaces.
If you have been developing user interfaces, then you most likely have encountered some of these problems - directly or when using libraries. In both cases, I recommend that you work on a simple application without libraries at all, try to reproduce and solve these problems. There is no one right solution for any of them. The experience comes with an introduction to these problems and the study of possible solutions, given the strengths and weaknesses of each.
Integrity (Consistency)
You liked the post and the inscription appeared: “You and 3 of your friends appreciated it”. You clicked the like button again and the caption disappeared. Sounds easy! But it is possible that such an inscription is present in several places on the screen. It is possible that there is also an additional visual indication for the likes (for example, the background color of the button), which must also change. A list of "Likelik", which was previously obtained from the server and displayed when you hover the mouse, should now include your name. And if you moved to another section or clicked the Back button, then the post should not “forget” that it has your like. As you can see, even local integrity for one user creates a number of difficult tasks. At the same time, other users can also interact with the data that is displayed on your site (for example, like the post you are viewing). How do we keep data synchronized in different parts of the screen? How and when should we verify local data with the server and receive / send changes?
Responsiveness (Responsiveness)
People admit the lack of visual feedback for their actions only for a very limited time. For continuous user actions, such as scrolling, no application response is possible only for the shortest period. Even the skipping of one frame in 16 milliseconds already looks buggy and flawed. For discrete(one-time) actions, such as a click, according to some studies, users normally perceive delays in the cast less than 100 milliseconds. If the action takes more time, then you need to show a visual indicator. However, there are several counterintuitive tasks. Indicators that cause a shift in a page template or that go through several alternating stages can make an action take longer to “feel” than it actually was. Similarly, an application’s response within 20 milliseconds by skipping a single frame of animation can be “felt” slower than a full animation within 30 milliseconds. Our consciousness does not work like benchmarks. How do we keep apps responsive?
Response time (latency)
Computer computing and data transmission over the network takes time. Sometimes we can ignore computation time if it does not affect responsiveness on user devices (however, make sure that you test your code on old and budget devices). However, processing the time of data transmission over the network cannot be avoided - it can be calculated in seconds! An application cannot simply “hang” while we are waiting for the data or code to load. This means that any action requiring new data, code, or assets is potentially asynchronous and should handle the state of its load. This is true for the vast majority of screens and elements. How to properly handle the delay in data transmission, without displaying a cascade of spinning spinners or empty “holes” in the interface? How to avoid page shifts? And how to change asynchronous dependencies without the need for constant rewriting of the code?
Navigation
We expect the interface to be “stable” when interacting with it. Elements should not suddenly disappear. Navigation, both within the application (for example, links) and external (for example, the Back button in the browser), must also adhere to this principle. For example, switching between tabs
/profile/likes
and/profile/follows
in the user section should not reset the contents of the search field outside of this section. Even switching to another screen should be like a walk to another room. People expect that after returning back, they will find all the things where they left them (and, perhaps, will be happy with some new things). If you were in the middle of your tape, clicked on the profile tab, then returned back to the tape - then you definitely do not want to re-thumb through the tape from the very beginning or wait until the past state of the tape is loaded. How to design an application to handle arbitrary user navigation without losing important context?Obsolescence (Staleness)
We can make the implementation of the Back button instant by adding a local cache to the application. To do this, we will store the necessary data in the cache (data from the past state - note of the translator). We can even theoretically update the cache to keep the data up to date. However, the implementation of caching entails new problems. The cache may be out of date. If I changed the avatar, then it should be updated, including in the cache. If I published a new post, it should immediately appear in the cache, otherwise the cache will become invalid. Such code may eventually become too complex and difficult to maintain. What if the publishing process fails? How long is the cache stored in memory? When we get the data set again, then we merge the new data with the previously cached ones, or do we get rid of the old cache and cache the entire set again? How should pagination and sorting be cached?
Entropy (Entropy)
The second law of thermodynamics reads as follows: “Over time, everything turns into a complete mess” (not literally, of course). This is also true for user interfaces. We can not predict the actions of a particular user and their sequence. At any time, our application can be in one of a huge (gigantic!) Number of different states. We try our best to make the result predictable and limited according to our design. We don’t want to look at the screenshot with the bug and think to ourselves: “How did it happen at all?” For N possible states, there are N × (N – 1)possible transitions between them. For example, if a button has five different states possible (normal, active, hover, highlighted and disabled), the code responsible for changing the button should be correct for 5 × 4 = 20 possible transitions - or explicitly prohibit some of them. How do we cope with a combinatorial increase in possible states and create predictable visual output?
Priority (Priority)
Some things are more important than others. Maybe your dialog interface should appear strictly “above” the button with which it was called and go beyond the limits of the parent container. Or a just-scheduled task (i.e., the result of a click) may be more important than a long-running task that has already begun. As the application zooms in, its different parts, written by different people or even teams, begin to compete for limited resources, such as processing power of the processor, network traffic, screen space, or bundle size. Sometimes you can distribute elements along a single “importance” scale, like a CSS rule
z-index
. But usually it does not end with anything good.. Any developer sincerely considers his code important. But if everything is equally important, it means that nothing matters. How can we get independent parts of the application to interact , rather than fight for limited resources?Accessibility
Sites that are not adapted for people with disabilities are not a highly specialized issue. For example, in England, every fifth user faces this problem ( herevisual infographics). I felt it on myself. Despite the fact that I am only 26, I hardly use sites with thin fonts and low-contrast colors. I try to use the trackpad less often, but I’m afraid of the day when I have to use a site that is not adapted for this from the keyboard. We must not turn our applications into a nightmare for people with disabilities - and the good news is that it is not so difficult. We need to start with a study of solutions and tools. In addition, we must make it simple and understandable for designers and developers to make the right decisions. What can we do to ensure that the availability of our applications is enabled by default, and is not a belated refinement?
Internationalization
Our applications should work worldwide. Yes, people speak different languages, and besides this, support is needed for writing from right to left, with minimal effort on the part of developers. How do we support different languages and scripts without losing the responsiveness of the application and response time?
Delivery
We must deliver the application code to the end user's computer. What mode of transmission and format will we use? In this question, each answer will be a compromise with its own set of strengths and weaknesses. For example, native applications have to load all their code in advance because of its huge size. While web applications usually have a much shorter boot time, they are forced to handle a lot of latency and downloads during use. How do we decide which type of delay to choose from these two options? How can we optimize response time based on user usage statistics? What data do we need to have to make the best decision?
Flexibility (Resilience)
No one likes to meet bugs in their own programs. However, some bugs will inevitably get to production. And it is very important - what will happen then. Some bugs cause incorrect, but strictly defined and predefined behavior. For example, your code indicates an inappropriate state for a given condition. But what if, as a result of the bug, the application completely stopped rendering? In this case, we will not be able to continue the meaningful execution of the program, since the visual output will not be defined. The error in drawing one post from a tape should not “break” the drawing of the entire tape or enter the application in an unstable state, which will lead to further errors. How do we write code? which isolates errors when rendering or retrieving data in one of the parts and continues the rest of the application to work correctly? What does fault tolerance mean when creating user interfaces?
Abstraction
In a small application, we can hardcode and solve all the above problems head on. But applications tend to grow. We want to be able to reuse, fork and merge the various parts of the application and share it with other people. We want to define clear boundaries between parts of a whole that will be accepted by different people and at the same time avoid too rigid logic, as it often changes and evolves in the process of work. How do we create abstractions that will hide the details of the UI implementation? How can we avoid the re-emergence of these problems with the growth of the application?
Of course, there are still many problems that I did not mention. This list is by no means complete or exhaustive. For example, I didn’t touch upon the topic of design and development collaboration, debugging or testing. Perhaps we will come back to it another time.
It is tempting to read about these problems, keeping in mind as a solution a specific framework for displaying data or a library for receiving data. But I recommend that you imagine that these solutions do not exist and try to read again. How would you try to resolve these issues? Try to implement your ideas on a simple application. I will be glad to see the results of your experiments on Github. (Just mark Dan Abramov on Twitter and attach links to repositories - approx. Translator).
What is especially interesting in these problems is that most of them manifest themselves on any scale. You may encounter them when working on a small widget, such as a tooltip, and in huge apps like Twitter or Facebook.
Think about the nontrivial user interface elements from the application that you like to use, and run through the list of the above problems again. Can you describe the tradeoffs that the developers went to? Try to reproduce similar behavior from scratch!
I realized a lot about the development of good user interfaces, experimenting with these problems in small applications without using third-party libraries and frameworks. I recommend this to anyone who wants to gain a deep understanding of solutions and trade-offs when developing complex interfaces.