Video details

Netta Bondy @ ReactNext 22 - Build it Beautiful with React

React
08.03.2022
English

ReactNext 2022 www.react-next.com Israel's Annual React & React-Native conference
Powered by EventHandler ----------------------------------------- Build it Beautiful with React: You know beautiful code. You know it when you see it. Writing it is a whole other issue. Are there guidelines we can set to make our React code beautiful?
----------------------------------------- Netta Bondy: Netta is a Senior Front-End developer at Twang, the co-founder of Baot - Israel’s largest community of women in R&D, and a lover of all things JavaScript. She often codes useless things out of sheer curiosity and stubbornness.
----------------------------------------- #javascript #reactjs #programming #software #development #softwaredevelopment

Transcript

This is me. I actually had a nightmare about this talk last night, but I think my brain was like, let's worry about things that aren't a big deal. So my nightmare was that there's no good coffee. Not, for example, that I would have to download all my slides at the last minute and lose all my funds, which is what actually happened. And not about the fact that I'm actually really nervous to do this talk, because this is the first time for me that I give a talk about my opinions. So it's going to be scary and interesting. Do you know that feeling when somebody says something you've heard a million times before, but for some reason the way they say it makes you see it in a whole new light? So that happened to me a few months ago with a tweet. I read this tweet and I was like, oh, yes, that's exactly what I think. But also at the same time, it made me kind of angry, and I had all these emotions and all these thoughts about this tweet, and then I lost it. I lost the tweet. I've been searching for it, but even though I searched for phrases that specifically involved the word code, the only suggestions I get from Twitter are about tweets that have to do with crystals, and so I just can't find it again. So I had to try and come up with something of my own. And it's a bummer because it's not as good as the original tweet, but it went something like this I can't say what beautiful code is, but I know it when I see it. I don't know why this sentiment, this tweet, really did something to me, or I do know now, but at first it took me a while to realize I always knew that I want to be throughout my career, I would always want to be an IC, an individual contributor. So I don't really want to cross over to management. I don't really care about making or that's maybe a strong statement, but I'm not interested in deciding product things or business things. I know I want to write code, but as I've gained more experience over the years, I realized that the type of expertise that I want to have is a very specific type of expertise. I'm not interested in being the person that knows all the newest technologies. I don't really look to crossing over into full staff development. What I want to do is I want to be a craftsman woman. I want to be a master of my craft, if I can, and my craft is front end. And so part of being a master of that to me is about writing beautiful code consistently. And I think my issue with the tweet or the reason it really got me going is that this sentiment doesn't sit well with me. The sentiment that says, there's something here I'm really interested in I'm really passionate about. But I just don't know how to break it down. I don't know how to understand it. I don't know how to characterize it. That's not what we do as engineers. And my problem with it is that it suggests that mastery is something serendipitous. If the hand of God touches me, I may be able to achieve it. But otherwise and so I decided I would try. I decided I would try and understand what beautiful code is, at least to me. I would try and define it. And from that definition, I would try to infer a list of principles that I can use in my day to day life to really be deliberate about achieving it. And so that's what I decided to do. And my first step, the first thing I needed to do in order to achieve it, is answer this question what is beautiful code? What makes code beautiful? I had a question and so I went to the place we all go when we need to find an answer, which is the Internet. As it turns out. This might shock you, but the Internet has its ups and downs. And so when I went to look for an answer to this question, I found all sorts of things. Things like I like to see code that looks nice. Beautiful code is also an experience. Or my favorite one, if the creator feels good about it. Probably the maintainer who comes in two years later to make updates feels good about it too. And this is what makes code beautiful. Now, I don't know about you, but I am about to show you today code that I wrote four months ago. And I was like, I feel good about this. This is good. Merge it, push it, send it to production. Great. We are going to refactor it today because let me tell you, I do not feel good about it anymore. All of these sources, they also had some good insights. But I think this really speak to this concept that a lot of us share that says because defining beautiful code is hard and subjective, we don't need to be rigorous or exact about doing it. And so we resort to statements like code that looks nice or code I feel good about. But the problem with that, with that is that if we're not deliberate about defining it, we can never be deliberate about achieving it. We have to start with the definition. Right now, I'm going to give you in a few moments a definition that resonated with me. But if it doesn't work for you, I still want to encourage you to go out and try and do this. Define what beautiful code is to you, find a definition online or even make one up yourself. And then use analytical skills to gain some insights from it and some principles from it that you can then actually use in your day to day life. Okay, so this is a definition that I found. I found it online. That really resonated with me. Beautiful. Code is when all of the code present is required and none of it requires an explanation. All of the code present is required and none of it requires an explanation. So there's two parts to this definition, right? Number one is making sure all the code you have is needed so nothing is extra. And then number two is making your code self explanatory. So I've come up with a list of principles that I think helps achieve one or both of these goals. And I want to give you a fair warning. I'm listing them from least controversial to most controversial. So if you're sitting here thinking, oh, this is really basic, I already know this, or if you came in here looking for a fight, I will give you a fight. Just give me ten minutes. Okay, so here are things we can do to make sure all code present is required and none of it requires an explanation. Number one, remove things, right? This is the easiest way to make sure what I have is required, is by removing things that aren't required. And it sounds like it should go without saying, but does it really? Because I can think of more than one occasion when I've looked at a piece of code and I said, you know, we don't need it right now, but we might want to put it back in later. Let me just comment this out, right? Let me just leave this file here. Tree shaking will take care of it. And I think what we need to do is we need to take the principle of the Acne. You aren't going to need it and kind of apply it both ways. So in the same way, you aren't going to need in the future whatever fancy shiny architecture you're putting in right now for the day when you have millions of users, you also are not going to need in the future that one component you built when you thought you had a dashboard that looked a certain way. And so delete code. Delete code. I want to encourage you to delete code because two months down the road, you're far more likely to have forgotten about it than you are to need it. And if you do end up needing it, you'll be smarter about your system. You'll be smarter about the problems you're facing. You'll be smarter about the problems you currently have, and you can build it better. Number two, prefer native functions. This is probably the easiest thing we can do to make sure that our code doesn't require an explanation. This is because vanilla JavaScript and web APIs are the lowest common denominator for all of us. Chances are, if you're using the filter function, whatever developer comes in later has already seen it, they already know what to do, and it will be very easy for them to understand your code. Okay? So the closer I say to native utilities, the more my code is self explanatory. It's at this point that I would like to show you a piece of code that I wrote four months ago felt really good about. So good, right? So beautiful. Do you know that image of Will Smith going like, TADA, isn't she beautiful? So beautiful. We're going to refactor it, but I want to just point out first this one line here. That block of code is pretty shit, right? I don't know if I'm allowed to say that this goes on YouTube. I don't know. But this is not too bad. This line right here is not too bad. You all can look at this and understand what this means. And I haven't told you anything about my code. I haven't told you anything about my system. You don't know what the place I work at does, right? But you can understand this because we all know what fine does. We know it works. We know it works in an array. And so you all can look at this and know that we're looking in this plug in data array for a plugin that has an ID that saved to whatever const is over there. And so, again, using native language functions are the easiest way to make sure that your reader understands what you're writing easily and quickly. So these are my first two principles, and after I came up with them, I actually came up with a list of like, four that were all about functions, right? How to write functions, when to write functions, and so on. And eventually I realized I was just describing functional programming, so I just decided to go with this. To me, functional programming is one of the best ways to make sure your code doesn't require an explanation. And this is because functions do two things. Number one is they hide implementation details, right? But number two is they allow us to write code that tells us what it does, not what it is. And if you think about it, when we look at code, that's the question we're trying to answer, right? What does this code do? So when our code is explicit about it, it goes a long way towards making it more self explanatory. So how do you write functional code? Well, first thing to do is you got to write functions. And again, this is something like, we all know we should do this, right? Encapsulate it in a function. But do we do it? I don't you know what I do? I instantiate variables. I instantiate variables, and I saved some data to it. And I'm like, this is fine. It's two lines of code. Why would I encapsulate it in a function? The function would just be adding more lines of code. And we all know more code is bad. And you can actually really see that in this code. I wrote here you can really see the avoidance of functions. Like look at this line right here. You can see my thought process. You can see me going, this doesn't need a function, it's just taking the keys off an object and then it's filtering them and it's using native functions and it's saved to a well named variable. Like this is fine, but it's not fine, right? Like this code, it doesn't look good, it's not beautiful code. And actually I'm doing kind of the same thing over these two lines together. And the sole purpose of these two lines is to get that second variable called Active plugin config. If I were to encapsulate all of that logic into a function and name it like even not a very inspiring name, right? It's just the getter it already makes my code look that much better and that much more readable. So the first thing I think we should do is I think we should actually prefer functions to variables. Then the next step for making your code more functional is to make your functions pure. So pure functions are functions that have no side effects. All of their dependencies are declared in their inputs and all of their effects are returned in their outputs. And we've been hearing about pure functions for a while now. We mostly talk about them in the context of avoiding data mutation, but they also go a long way towards readability and style. But if we look at our code at this function we just wrote, it kind of looks as though it's doing something encapsulated, right? Like it's this bubble, but in fact under the hood, it's using these two entities of voice app and plug in data. It's just reading them from state or from props. So my function can be just a little bit better at explaining what it does if I were to add these as inputs. Okay? Now when I look at this line, it's very clear. What does this function do? It gets the active plugin configs. Where does it get them from the voice app and the plug in data. And all of that implementation detail is kind of hidden behind that. Now the other value of pure functions is that they allow us to use functional programming patterns. So things like higher order functions, currying and function chaining or piping. Now, I'm not going to go into what all of these are right now, but the important thing to know about them is that they allow us to compose functions, so they allow us to write programs that are made up of only functions. And what happens is the combination of that with some proper naming creates a program that is essentially a step by step sort of manual or recipe of what it does. So again, we're going to look at our code and we can actually maybe start to understand what's happening here is I'm getting some piece of data and then I'm saving it to state. This is a set state function down there and I'm actually doing that twice again here. And the fact that I'm getting some data and then passing it to another function hints that I can use chaining or piping here. And so I'm going to build a pipe helper function. This is pseudocode, but essentially what this function does is it takes a piece of data, it takes a list of functions and then it applies each function to the return value of the function that came before, right? So it calls function one on the data, then it calls function two on the return value of function one. And you can easily see how we can write something more generic here to take as many functions as we want. And so now that I've got this helper going on, I can actually write code that looks like this. Now my code looks like two data pipelines. What is it doing? Well, it's taking plugin data and first it's piping it to GetActive plugin configs and then saving that to stay. By the way, if you're wondering why GetActive plugin configs is getting another argument there, that's just the piece a bit of currying. And then my second data pipeline takes plug in data, gets the settings plugin config and save that to state. And I think you can really see here how this idea of a program that is essentially a list of steps that describe what it does goes a long way towards making my code self explanatory. And so functional programming to me is really a lot about making my code readable selfexplanatory. But we also talked about making sure all our code is required. And I also did promise you some controversial opinions. We're going to start getting into those. So what can we do to make sure all our code is required? The first thing is to avoid premature anything, okay? The big one is premature optimizations. This is very popular. You hear this a lot. Hopefully all of you know this or have heard of it. Let's take a few moments just to explain what it is and what the issues are with it. And we'll use the example of preemptive use of the use memo hook. So that means I use use memo that's so difficult to say in wrap functions that are not actually computationally expensive, that don't actually require memorization. And this is actually a pretty common pattern. And I even used to have a colleague who did this and his reasoning was, why not? Why not memorize every function? I mean, even if there's no performance gain, the cost is nothing. I think that's wrong. I think the cost is not nothing because what Use Memo does is it creates, it communicates a very strong message, a message that is stronger and much more obvious than any code rewrite. Because again, we all know Use memo. And what Use Memo says is whatever is inside of me is computationally expensive. And so when you use that preemptively, what's going to happen is the next developer will come in and they're going to look at it and they're going to do one of three things. Number one, they'll look for the computational cost. They'll look and look and look because they won't find it, but they'll assume it's there. So they'll keep looking. Number two is they'll just assume the function and use memo is computationally expensive and they'll go to great lengths to avoid using it. And number three is they'll assume the pattern itself is needed and they'll start use memo preemptively in their code. And what premature optimizations do is they by default both add code that isn't required, right, memorization of a function that doesn't require memorization. And they also prevent your code for being selfexplanatory. They add a certain meaning to it. But this is not that controversial. We've heard of premature optimizations. I also think you should avoid premature abstractions. This means that I think you should not write Dry code. I think you should write Wet code, which is an acronym I think I got from Guiltyao, who is sitting right here. Wet stands for write everything twice. And what that means is, unless you have three instances of the same thing in your system, you do not try to abstract out any shared code. So if you're starting out in your system and you need to build, say, a dialogue, okay, you have one use case for a dialogue. You build the dialogue you need. The second dialogue comes in. The second use case for a dialogue comes in. You build another dialogue, you copy paste the styles, you copy paste the JavaScript, and you build two separate dialogues. The third dialogue comes in. That's when you start thinking about abstraction. And even there, you need to be very careful and very nitpicky about what you abstract. You need to look at those three dialogues and ask yourself what is actually shared? Do they both have a confirm and cancel button? Maybe only the confirm is shared. Maybe only the footer is shared. Maybe only the container and overlay are shared. And you abstract out only what is shared. The force dialog comes in. You do the same thing again. You look at it and you're very critical and you ask yourself what is actually shared? Not just theoretically, as in, oh, they're all dialogues, but what code is actually repeated at least three times. And that's the thing you abstract out. Because the problem with premature abstractions is we all tend to look for patterns. Not just developers, everybody. Your brain is looking for a pattern in this image right now. And the issue is that when we look for patterns, we tend to see them even where there are none. So you could look at two instances and see a similarity and think to yourself, oh, that's a pattern, let me abstract it out. But it might just be something that's similar between those two things, or it might not even be a similarity at all, but just something you found because you were expecting to find it. And the problem with premature abstractions is that once we've done them, we have committed the instances to sharing code. And when they're not actually similar, what's going to happen over time is we're going to have to write more and more code to make sure that they are separate, that they are different. And I know this sounds, I don't know, kind of theoretical or maybe like a funny idea, but I have to tell you, in all my years in front end, I have seen this happen over and over again. We make bad abstractions. I think the most horrible example I ever saw of this was in fact the dialogue. Which is why I'm so sensitive to those. I was working with this team and the team had no UX designer. And so what often happens in this case is when we need to show multiple types of content on screen, we push them into a dialogue. So this team had all these dialogues going on and they were all being served by one single component. And you have to believe me when I tell you this component, it was a beast, okay? You could do whatever you wanted to. It could have a header or no header and a footer or no footer, and a confirm button or not and the cancel button or not and a close button or not and a title or not and content or not. And sometimes the content was conditional and sometimes the content was a form with externally managed state and sometimes dialogues open other dialogues. And yes, it's that kind of to begin with from a UI standpoint. But also there was one component doing all of these things. And so you know what happened? It received a huge list of props. Most of them were just flags that were designed to turn a style or feature on or off. And 90% of them could have been avoided by making better abstractions, by making abstractions of what is truly shared. But the problem is you don't know what is truly shared unless you have a few instances to infer that from. So think about the next time you want to dry, cold and ask yourself, are you really seeing something that is a pattern or are you just expecting to share? And so that's what you do. Number five, making sure all your presence is required. You don't need a UI library for that. You don't need a UI library for a button. You don't need a UI library for tabs or for a sidebar or for a header. The problem with UI libraries is that they by definition include code that isn't required. They have to serve your use case. They have to serve the use case of huge enterprise level software. They have to serve the use case of a mobile first application and sometimes they serve some edge cases that enough people have been complaining about. And all of that code is code that is not written by you, but is exposed to you and is just not required to you. And the issue with UI Library APIs is that eventually we end up writing code to match. The library's API where I work now use a very popular UI library for react and it forces us to add event listeners where they shouldn't be any because that's how their API works. And the issue with that is that eventually the UI Library is going to win out. You are going to have to fold and write your code in the way that they need to get it. The other thing is, and we probably all know this, but over time with the UI Library, unless you've completely bought into the design system, you are going to need to write some code to override those UI Library defaults. And it's not going to be pretty because they don't expose an API to override their defaults. Okay? And so over time, we end up writing more and more code that's designed to serve that decision that we made originally. That's just not good for us anymore. So I want to encourage you to either undo the original decision, don't use the UI Library's components where they don't fit, or maybe consider not making the decision in the first place. Because you don't need a UI Library for a button, you don't need a UI Library for tabs, you don't need it for a sidebar or a header or a footer, or an input or a toggle. And building those things in house will allow you to build exactly what is required. So this is my list. Those five things were the list that I came up with, the things I can do to make my code more beautiful, that is, to make sure all code present is required and none of it requires an explanation. And when I finished the list, I became a little bit sad because I realized that I'm never going to be able to apply all of those principles in my day to day life. I work in a team. Some decisions we make are there to serve the team. Some decisions we make are there to serve the business. We use a UI library. And I was reminded again of how often it is that my goal to become a master of my craft and the goals I have at work, they just don't match right. The goal for us at work at the end of the day, most of the time, is to deliver it's, not to hone your craft and make something beautiful. And it made me a little bit depressed. And so I decided to come up with one last principle for myself. Do one thing well, do one small thing well. It won't all be beautiful. It won't all be great. I think it's comforting to know that even the greatest artists made commissioned paintings. They weren't all masterpieces. And I think we can take pride and joy and care in the smallest places in our code where we're able to affect change and know that doing it only in a small place doesn't make it any less beautiful. I mean, if I could take that and go from that to this I don't know, that's still pretty beautiful to me. This was me. I want to thank you for listening. I want to encourage you to come talk to me if you have anything to say about this. I'm a strong believer in a strong opinion. Loosely held. Nothing in tech is an absolute truth. So if I made you mad, or if I made you agree with something, or if I just made you think, feel free to comment. Continue the discussion here or on Twitter. Thank you.