UX of Code: Leveraging UX Principles for Better Coding
Checking session availability…
Hang tight while we load the latest updates.
Code as user interface? Yes! This presentation reveals how UX principles can make developers the happiest users of their own code, leading to cleaner and more maintainable applications.
By applying UX principles, developers can write code that is not only functional but also enjoyable and efficient to work with. Code written with another developers in mind leads to increased developer productivity, better maintability and more positive overall coding experience.
Presentation is targeted to developers on various experience levels. However, everyone who is somehow related to programming or works with developers can benefit from it.
UX of Code: Leveraging UX Principles for Better Coding
Karolina Malanowska-Chmiela at UXDX Community: UX-Driven Code: Building with the User in Mind. Video: https://youtu.be/P-B4HqVU6i8
Readable transcript: edited from the recording's captions for readability (fillers and false starts removed, punctuation and section headings added). Wording is the speaker's own. Timestamps are positions in the video. Names marked [?] could not be verified against the audio.
The UX of code
[00:00:07] Thank you so much. Thank you for having me in the UXDX online series. It's a great pleasure to show something from the field of my expertise, because today we are going to take a look at code from another side. Code is the backbone of every software, every website, every app. But what about the coding experience? What about the developer experience? So today we are going to check if we can utilize UX principles to make coding a better experience.
[00:00:42] And on to the agenda. First we are going to start with a quick refresher on what user experience design is, because we need to set a common ground for our further discussion. Then we will check how we can apply Gestalt principles and Nielsen heuristics, the two most common sets of principles, to code to make it more readable. And then we will move to a practical application of the things we have heard to the code.
[00:01:15] So first let's start with some numbers. 88%. That's a huge number, isn't it? 88% of users are less likely to return to a site after a bad user experience. And yet sometimes we are still forcing developers to experience poorly written code. This sounds like a really bad user experience, a really frustrating thing. So we are going to talk about the UX of code.
UX and UI: the ketchup bottle
[00:01:50] But as I said previously, we need to set a common ground. So first we are going to talk a little bit more on UX itself. UX is commonly stated with the word user interface, UI. This is how we should pronounce it. And we need to know what UX is and what UI is. We need to distinguish these two fields.
[00:02:17] So let's start with UI. We often speak of UI when we mean something related to color theory, to typography, to how things animate and how the visual look and feel of the interface is. And we talk about user experience when we mean the real experience of the product. Often during the design process we conduct usability tests, we talk with users, sometimes we prepare information architecture.
[00:02:54] And to prove this point even more, I have prepared a really reliable example of a ketchup bottle. And yes, this is the UI of the ketchup bottle. So we have this little lid on the top and the stable bottom on the bottom of the table. And the experience of this bottle is as follows. It's quite hard to get the ketchup out because it is at the bottom of the bottle. So we need to flip the bottle upside down and try to get the ketchup out of it.
[00:03:28] But what if we change the UI a little bit? So now we have the lid on the bottom of the bottle, so it basically stands on the lid all the time. So the ketchup flows down and it's much easier to get the ketchup out. So we can enjoy our hot dog with ketchup. So this is our common ground. And now we know that UX focuses on the experience, UI focuses on the visual aspects, and we also know that UI may influence UX and vice versa.
Code is a user interface
[00:04:04] But what does it have to do with programming even? So now we are going to check the UX of code, because the code is a user interface itself. So let's now imagine the following example. This is your first day at work and you've received a task. You need to refactor some part of code, and this is the code you see. Your first thought is, oh, who wrote that? You don't want to work with this.
[00:04:33] But after a short time you need to read what does it mean, and you spend some time and you see, okay, this function is called some stuff, this function returns an error in some conditions, and you can see that there is a 3.14. It probably refers to a circle. It may calculate some circle properties or something. So after a while you will manage to get the meaning of the code.
[00:05:03] But is this the same situation if everything is on fire, the deadlines are coming and the manager is staring at you because you need to know what this code means and time is passing by? It is not. So we need to fix that code so you can see what it means at one glance. And here UX principles come in. UX principles are designed to make sure that the final design meets the needs of the user while providing a smooth user experience. And as mentioned in the agenda, this presentation will cover two sets of UX principles. The first one is Gestalt principles and the second one is Nielsen heuristics for usability.
Gestalt principles: similarity and proximity
[00:05:54] Let's start with the first one. Gestalt is a word borrowed from German which means form or shape, and it refers to the fact that we perceive parts of something like a whole thing, an organized whole that is made of some parts. And to give you an example of this occurrence, I'm sure that your brain has filled in all the gaps. So this is how the Gestalt principle works. And we have a lot of Gestalt principles but we are not going to cover all of them. I have chosen four of them, because I think that these four are the most significant when it comes to coding.
[00:06:42] So let's start with the law of similarity. It says that we humans tend to perceive things that are similar as a group. So in this example on the right side we can clearly see that there is a group of bright dots and we have another group of dark dots, even though we don't have any white space or any other visual grouping.
[00:07:10] And when it comes to code, we can approach this law in two ways. The first one, in this example, is that we have two groups of variables. The first one refers to a person that has first name, last name, age and so on. And the second group refers to something related to the account of this person. So for example max login attempts, or the role of the user. And these variables are written in upper case, so it basically means that these are global variables. So we have local variables related to the person itself and a group of variables related to the account.
[00:08:02] But we can also apply a different kind of visual grouping, which comes from our IDE, and we can apply some coloring. We have other groups of words. For example we have functional words like const, or other marks. And we have a different color for the variable name. We have first name, last name and so on colored in white. And another color comes for the variable values. So that is basically it.
[00:08:38] Let's move to another law, the law of proximity. As I've mentioned before, we haven't seen any visual gap, and here we see a visual gap. So it basically says that objects that are near each other are perceived as a group. We have an extremely visible group of bright dots and another group of dark dots. And when it comes to coding we can do the same thing. We can apply some white space to indicate that we have a group of variables related to a person, a group of variables related to a cat, a group of global variables, and then we have a block for functions. Also, remember to feed your cat if you have a cat.
Closure and Prägnanz
[00:09:24] Okay, and for the next law I have a little quiz for you. So, what do you see? A, four Pac-Men staring at each other, or B, a diamond in front of four circles? I give you a second to think. And okay, if you have selected option B I have bad news for you, because you are affected by the closure law. It says that humans tend to fill the blanks to perceive complete objects even if parts of these objects are missing. So your brain has filled in all the gaps.
[00:10:00] And in programming we have these function blocks. What does it mean? It means that for line number one, on the end of it we have this curly brace, and in line three we have another curly brace. So it indicates a visual boundary that says to us that this is a function, and only among these three lines we have this function. And then we have white space and the function call. So, hello to our audience.
[00:10:34] Let's move to another law, the law of Prägnanz. We tend to see objects in their simplest form. Here we have the logo of the Olympic Games, and human eyes tend to perceive this logo as five circles intersecting with each other rather than some lenses, some C-shapes or arcs or whatsoever.
[00:10:58] And in programming we can see the application of this law for variable names. Here for example we have a very long variable name which is really hard to read. But if we apply some cases, for example camel case, snake case, it's much easier to read when we have some visual separations between the words. All glory to the Prägnanz law.
Nielsen's heuristics, applied to a mug
[00:11:28] All right, let's now move to another set of principles. We are now going to cover Nielsen heuristics for usability. This is the set of 10 general principles for interaction design created by Jakob Nielsen. And these are heuristics because they are really general, they do not specify any specific usability guidelines. And my friend once said that we can apply Nielsen heuristics almost to everything, for example to a coffee mug. So let's now try to apply these principles to a mug filled with a delicious dose of morning coffee, evening tea, or in my example with water.
[00:12:12] So let's start with the first one, user control and freedom. If you take a look at the mug, it's really easy to use. You can see the handle on the side. You can handle it like this or you can use it with both hands, any way you want. So it's quite easy to use this the way you want.
[00:12:30] Then let's move to the visibility of system status. When I look inside, I can see that I have some droplets of water inside. Not too much to not spill anything on the table, but it is easy to tell what is the status of the mug. So I know there's not much water and it's probably safe not to spill everything out.
[00:12:54] Then, match between the system and the real world. So the handle is on the side, the top is open so I can see what's inside. And almost every mug in the world looks like this unless it has a lid to not spill things out. But let's say in general that every mug looks the same. So it's easy to tell how to use the mug, because every mug in the world looks the same.
[00:13:25] And every mug in the world, when you put it on the side, I won't do this live, it will spit the contents out. So this is for consistency and standards, because every mug placed on its side will let the contents out. But to prevent that, as I've done a couple of seconds ago, I put my mug on the table using the stable bottom, so I will avoid spills.
[00:13:57] Then we have recognition rather than recall. As mentioned previously, we have this handle on the side, so I know that I can handle it this way. So I can travel with this mug to, I don't know, for example another kitchen or somewhere else. So it's easy to take this with me anywhere else.
[00:14:20] And then on to aesthetic and minimalist design. My mug has a variation of the "this is fine" meme, but in Polish. But this graphic doesn't hinder the mug's usability, so I still know how to use this mug even if it looks like this.
[00:14:39] And then we have help users recognize, diagnose and recover from errors. A hot mug still can be handled. So from the visibility of system status I know that the contents of the mug are cold, but let's suppose that the content is hot. So I can still handle a hot mug because it has the handle. I don't need to carry it this way.
[00:15:05] And on to the last one, help and instructions. If there is any specific method of cleaning or using the mug, probably some instructions will be given in the form of an icon on the bottom or whatsoever. For this particular mug I don't have any icon, so I don't know if I can wash it in the washing machine, but I will not try to do this.
Heuristics applied to code: structure, naming, efficiency, documentation
[00:15:29] And let's now consider these heuristics from the perspective of code. So let's think about their applicability to code. I have divided these heuristics into four groups. The first one is code structure and formatting, the second naming and coding conventions, third code efficiency, and fourth help and documentation.
[00:15:55] Let's jump into the first one, code structure and formatting. Here we have user control and freedom. In our code we can make it work for the typical task. So if I have a task to add something to the code it should be easy, because the code that is already there should allow me to do that. And also in this point I have mentioned use comments to explain, which is quite a controversial point for developers, because some developers don't like putting comments in code because it makes them longer and so on. But from the usability perspective I recommend to use code comments even if this is a single liner, so the user of your code will know what this function does.
[00:16:49] Moving on, aesthetic and minimalist design. So in contradiction to use comments to explain, we have avoid clutter and do not overload code with comments. So don't write documentation in code, don't make it very long. But as I said, a single liner is just fine.
[00:17:11] And on to the example. Here we have a class for a stack. This is a structure that allows you to put items on and pop them out, and this is basically what this part of code does, because we have used clear naming. We know that this is a stack because we can read that this is the stack, and we don't need to think too much to get the meaning. And we have functions that will allow us to push, pop and check if the stack is empty. And we have these little comments in line 4, 9 and 14, just to let the user know that this is the function to push items on the stack or to check if the stack is empty. And that's it.
[00:17:57] Moving on to another category, naming and coding conventions. Here we have match between the system and the real world. And as said previously, we have known that the stack is the stack because it was named like that. So please use relevant naming to what the function does, and also refer to real life. For example, if you are going to have a function that, I don't know, counts numbers, let's call it counter or number counter or whatsoever.
[00:18:31] And then we have consistency and standards. From my perspective it is important to use consistent formatting that is specific for a given language, but also that is specific to your team, and this is something that you have agreed on. Because common things, or familiar things, are easier to understand for us. So it's good to use consistent formatting that we already know.
[00:18:58] And then we have recognition rather than recall. These previous heuristics are basically the proof for this one, because when we use meaningful names, meaningful comments, consistent formatting, it is easier to recognize what this part of code really does. And on to the example, we have a class called simple calculator. As I said, when we have an object in real life, for example a calculator, it probably has functions like adding numbers, subtracting them, multiplying and so on. So why don't we just name functions inside this class just like that? We have an add function, a subtract function, and these little comments that explain what the function does.
[00:19:46] On to the third category, code efficiency. We have there visibility of system status, and this is quite easy because we can let other developers know what the status of our code is. So for example, if some functionality is missing, we can let other developers know that, oh, there's a to-do, we need to add something to this function, and that's it. We can also provide progress or debug messages when you write some part of code that is displaying something in a terminal, and provide clear error messages that will again let us recognize rather than recall.
[00:20:32] Moving on to another one, error prevention. This is all about writing meaningful error messages that should be self-explanatory, and also a bit of preventing them by providing such explanatory messages. So if something breaks, please clearly let other developers know that this breaks because of this and that.
[00:20:59] And on the flexibility and efficiency of use, we can provide some shortcuts for the experienced user, or we can provide code snippets or optimizations that will allow us to write code faster. And on to the example, we have here a mockup function that is called process file. This mockup function processes a file, as we know from its name. And here we have this little comment in line number six that says, fix me, add checks for file extension. So we clearly know that something is missing, and probably what is missing is some if or check for the file extension. And we also have console logs in, for example, line 8 that display the status of this function, so we know that at this point of time this function is reading a file.
[00:21:55] All right, and on to the last category, help and documentation. We can help users recognize, diagnose and recover from errors by providing clear messages again, but also by storing logs or writing tests for our code. And also we can have help and documentation, because we can use code comments again, but also we can provide some README files. We can use Confluence to store our documentation. And please remember to keep this documentation up to date, because there's nothing more frustrating than legacy documentation. And on to the example, this is a really simple example of a README file for our file processor, which explains how to install it and how to use it.
Practical application: before and after
[00:22:46] And on to the best part, we have this practical application. So if you remember, this was the code from the beginning, this "some stuff", and we can quickly fix it by applying the similarity law. So now we see some color, it's much easier for the eye. And then we can keep our users informed by providing some meaningful messages. So when our function throws an error we can say, ah, please enter a valid positive number. So we now know what happened.
[00:23:18] And then if we have any logs we can provide additional messages, like names for the variables that we are going to print. And then we can match our code with the real world by providing meaningful names for our functions. So our function is now called calculate circle properties and our variables are area, circumference and diameter.
[00:23:46] And then if we throw errors, or if we have any other parts of code that are similar, we can make them more visible by providing some white space around them. So in lines 3, 7, 11 and 17 we have just a white space to make code more readable.
[00:24:08] And then on to user control and freedom. Our function was quite long, so we can divide it into two parts, and the first part is for calculating and the second is for printing properties. So our developer doesn't have to see everything printed on the console if he or she doesn't want to.
[00:24:31] And on to the last one, we can prevent more errors by using types. So for example, if you are using TypeScript, you can define a type called circle properties which includes every property of your circle. And that's it. This is our before and after of code, and I think the second one looks much better. I hope so.
[00:24:55] But after this you may receive a message saying, ah, Karolina, but your code takes much more space. And that is true, but shorter doesn't always mean better in terms of usability, because sometimes we need to take more space to make things readable. However, always consider your project needs, because maybe you don't want your code to be longer than a specified number of lines, and that's fine. You can utilize other principles to make your code better.
[00:25:31] And there is one thing for potential concerns. I've been asked once, should I change AI generated code formatting? And the answer is, it depends. It depends, because if your team uses a very rigid code structure or something that you have agreed on, I think you should change it. But if this is just for your purpose, you don't use any specific formatting, or this AI generated code shows you some way to improve your own code, I think go for it and keep it like that.
[00:26:11] Some of the main key takeaways, something to remember. The first one is write code for humans, not for the machines, because as we have seen we are affected by different biases, laws and so on. Second one, provide clear messages, especially error messages, because it's easier to recognize rather than recall what is going on in your code. And the third one, embrace documentation and keep it up to date so everyone can refer to it. And that is all for key messages, and that is all from my side. So thank you so much. I would like to connect with all of you, and I'm open to questions if you have any and if we have any time left.
Q&A
[00:27:00] Host: Thank you, Karolina. That was great. Yeah, please add Karolina on LinkedIn there. There have been a few questions, but maybe you can reach out to Karolina directly if your question wasn't asked, because we're a little bit tight on time. Interesting question: someone asked about your team structure and the ratio that you work with.
[00:27:25] Karolina: Okay. So my team consists of four separate teams, and we have teams for backend, front end and so on. So my front end team consists of seven people, I think, but the whole team consists of around 25 persons.
[00:27:48] Host: Great, fantastic. And looking at what were some specific results, productivity, what KPIs or metrics were you tracked on to improve this?
[00:28:02] Karolina: Interesting question, because I haven't thought about it, but I think that the key measure is the time spent on beginning the task. We don't actually measure it, but I think this is the interesting one, because we can measure the time that you spend on just thinking what you need to actually do, and this is the time we need to shorten.
[00:28:32] Host: Fantastic, great. Thank you so much, Karolina, for that. Karolina's talk will be available on the website and you can watch it for the moment on the LinkedIn live stream as well as, obviously, the website. Thank you again. It was a pleasure and thanks for all the insights. Appreciate it.
